You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TableLayoutManager.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.layoutmgr.table;
  19. import java.util.ArrayList;
  20. import java.util.Iterator;
  21. import java.util.LinkedList;
  22. import java.util.List;
  23. import org.apache.commons.logging.Log;
  24. import org.apache.commons.logging.LogFactory;
  25. import org.apache.fop.area.Area;
  26. import org.apache.fop.area.Block;
  27. import org.apache.fop.datatypes.LengthBase;
  28. import org.apache.fop.fo.Constants;
  29. import org.apache.fop.fo.FONode;
  30. import org.apache.fop.fo.FObj;
  31. import org.apache.fop.fo.flow.table.Table;
  32. import org.apache.fop.fo.flow.table.TableColumn;
  33. import org.apache.fop.fo.properties.KeepProperty;
  34. import org.apache.fop.layoutmgr.BlockLevelEventProducer;
  35. import org.apache.fop.layoutmgr.BlockStackingLayoutManager;
  36. import org.apache.fop.layoutmgr.BreakElement;
  37. import org.apache.fop.layoutmgr.BreakOpportunity;
  38. import org.apache.fop.layoutmgr.ConditionalElementListener;
  39. import org.apache.fop.layoutmgr.KnuthElement;
  40. import org.apache.fop.layoutmgr.KnuthGlue;
  41. import org.apache.fop.layoutmgr.LayoutContext;
  42. import org.apache.fop.layoutmgr.LeafPosition;
  43. import org.apache.fop.layoutmgr.ListElement;
  44. import org.apache.fop.layoutmgr.Position;
  45. import org.apache.fop.layoutmgr.PositionIterator;
  46. import org.apache.fop.layoutmgr.RelSide;
  47. import org.apache.fop.layoutmgr.TraitSetter;
  48. import org.apache.fop.traits.MinOptMax;
  49. import org.apache.fop.traits.SpaceVal;
  50. import org.apache.fop.util.BreakUtil;
  51. /**
  52. * LayoutManager for a table FO.
  53. * A table consists of columns, table header, table footer and multiple
  54. * table bodies.
  55. * The header, footer and body add the areas created from the table cells.
  56. * The table then creates areas for the columns, bodies and rows
  57. * the render background.
  58. */
  59. public class TableLayoutManager extends BlockStackingLayoutManager
  60. implements ConditionalElementListener, BreakOpportunity {
  61. /**
  62. * logging instance
  63. */
  64. private static Log log = LogFactory.getLog(TableLayoutManager.class);
  65. private TableContentLayoutManager contentLM;
  66. private ColumnSetup columns = null;
  67. private Block curBlockArea;
  68. private double tableUnit;
  69. private boolean autoLayout = true;
  70. private boolean discardBorderBefore;
  71. private boolean discardBorderAfter;
  72. private boolean discardPaddingBefore;
  73. private boolean discardPaddingAfter;
  74. private MinOptMax effSpaceBefore;
  75. private MinOptMax effSpaceAfter;
  76. private int halfBorderSeparationBPD;
  77. private int halfBorderSeparationIPD;
  78. /** See {@link TableLayoutManager#registerColumnBackgroundArea(TableColumn, Block, int)}. */
  79. private List columnBackgroundAreas;
  80. private Position auxiliaryPosition;
  81. /**
  82. * Temporary holder of column background informations for a table-cell's area.
  83. *
  84. * @see TableLayoutManager#registerColumnBackgroundArea(TableColumn, Block, int)
  85. */
  86. private static final class ColumnBackgroundInfo {
  87. private TableColumn column;
  88. private Block backgroundArea;
  89. private int xShift;
  90. private ColumnBackgroundInfo(TableColumn column, Block backgroundArea, int xShift) {
  91. this.column = column;
  92. this.backgroundArea = backgroundArea;
  93. this.xShift = xShift;
  94. }
  95. }
  96. /**
  97. * Create a new table layout manager.
  98. * @param node the table FO
  99. */
  100. public TableLayoutManager(Table node) {
  101. super(node);
  102. this.columns = new ColumnSetup(node);
  103. }
  104. /** @return the table FO */
  105. public Table getTable() {
  106. return (Table)this.fobj;
  107. }
  108. /**
  109. * @return the column setup for this table.
  110. */
  111. public ColumnSetup getColumns() {
  112. return this.columns;
  113. }
  114. /** {@inheritDoc} */
  115. public void initialize() {
  116. foSpaceBefore = new SpaceVal(
  117. getTable().getCommonMarginBlock().spaceBefore, this).getSpace();
  118. foSpaceAfter = new SpaceVal(
  119. getTable().getCommonMarginBlock().spaceAfter, this).getSpace();
  120. startIndent = getTable().getCommonMarginBlock().startIndent.getValue(this);
  121. endIndent = getTable().getCommonMarginBlock().endIndent.getValue(this);
  122. if (getTable().isSeparateBorderModel()) {
  123. this.halfBorderSeparationBPD = getTable().getBorderSeparation().getBPD().getLength()
  124. .getValue(this) / 2;
  125. this.halfBorderSeparationIPD = getTable().getBorderSeparation().getIPD().getLength()
  126. .getValue(this) / 2;
  127. } else {
  128. this.halfBorderSeparationBPD = 0;
  129. this.halfBorderSeparationIPD = 0;
  130. }
  131. if (!getTable().isAutoLayout()
  132. && getTable().getInlineProgressionDimension().getOptimum(this).getEnum()
  133. != EN_AUTO) {
  134. autoLayout = false;
  135. }
  136. }
  137. private void resetSpaces() {
  138. this.discardBorderBefore = false;
  139. this.discardBorderAfter = false;
  140. this.discardPaddingBefore = false;
  141. this.discardPaddingAfter = false;
  142. this.effSpaceBefore = null;
  143. this.effSpaceAfter = null;
  144. }
  145. /**
  146. * @return half the value of border-separation.block-progression-dimension, or 0 if
  147. * border-collapse="collapse".
  148. */
  149. public int getHalfBorderSeparationBPD() {
  150. return halfBorderSeparationBPD;
  151. }
  152. /**
  153. * @return half the value of border-separation.inline-progression-dimension, or 0 if
  154. * border-collapse="collapse".
  155. */
  156. public int getHalfBorderSeparationIPD() {
  157. return halfBorderSeparationIPD;
  158. }
  159. /** {@inheritDoc} */
  160. public List getNextKnuthElements(LayoutContext context, int alignment) {
  161. List returnList = new LinkedList();
  162. /*
  163. * Compute the IPD and adjust it if necessary (overconstrained)
  164. */
  165. referenceIPD = context.getRefIPD();
  166. if (getTable().getInlineProgressionDimension().getOptimum(this).getEnum() != EN_AUTO) {
  167. int contentIPD = getTable().getInlineProgressionDimension().getOptimum(this)
  168. .getLength().getValue(this);
  169. updateContentAreaIPDwithOverconstrainedAdjust(contentIPD);
  170. } else {
  171. if (!getTable().isAutoLayout()) {
  172. BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
  173. getTable().getUserAgent().getEventBroadcaster());
  174. eventProducer.tableFixedAutoWidthNotSupported(this, getTable().getLocator());
  175. }
  176. updateContentAreaIPDwithOverconstrainedAdjust();
  177. }
  178. int sumOfColumns = columns.getSumOfColumnWidths(this);
  179. if (!autoLayout && sumOfColumns > getContentAreaIPD()) {
  180. log.debug(FONode.decorateWithContextInfo(
  181. "The sum of all column widths is larger than the specified table width.",
  182. getTable()));
  183. updateContentAreaIPDwithOverconstrainedAdjust(sumOfColumns);
  184. }
  185. int availableIPD = referenceIPD - getIPIndents();
  186. if (getContentAreaIPD() > availableIPD) {
  187. BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
  188. getTable().getUserAgent().getEventBroadcaster());
  189. eventProducer.objectTooWide(this, getTable().getName(),
  190. getContentAreaIPD(), context.getRefIPD(),
  191. getTable().getLocator());
  192. }
  193. /* initialize unit to determine computed values
  194. * for proportional-column-width()
  195. */
  196. if (tableUnit == 0.0) {
  197. this.tableUnit = columns.computeTableUnit(this);
  198. }
  199. if (!firstVisibleMarkServed) {
  200. addKnuthElementsForSpaceBefore(returnList, alignment);
  201. }
  202. if (getTable().isSeparateBorderModel()) {
  203. addKnuthElementsForBorderPaddingBefore(returnList, !firstVisibleMarkServed);
  204. firstVisibleMarkServed = true;
  205. // Border and padding to be repeated at each break
  206. // This must be done only in the separate-border model, as in collapsing
  207. // tables have no padding and borders are determined at the cell level
  208. addPendingMarks(context);
  209. }
  210. // Elements for the table-header/footer/body
  211. List contentKnuthElements;
  212. contentLM = new TableContentLayoutManager(this);
  213. LayoutContext childLC = LayoutContext.newInstance();
  214. /*
  215. childLC.setStackLimit(
  216. MinOptMax.subtract(context.getStackLimit(),
  217. stackSize));*/
  218. childLC.setRefIPD(context.getRefIPD());
  219. childLC.copyPendingMarksFrom(context);
  220. contentKnuthElements = contentLM.getNextKnuthElements(childLC, alignment);
  221. //Set index values on elements coming from the content LM
  222. Iterator iter = contentKnuthElements.iterator();
  223. while (iter.hasNext()) {
  224. ListElement el = (ListElement)iter.next();
  225. notifyPos(el.getPosition());
  226. }
  227. log.debug(contentKnuthElements);
  228. wrapPositionElements(contentKnuthElements, returnList);
  229. context.updateKeepWithPreviousPending(getKeepWithPrevious());
  230. context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending());
  231. context.updateKeepWithNextPending(getKeepWithNext());
  232. context.updateKeepWithNextPending(childLC.getKeepWithNextPending());
  233. if (getTable().isSeparateBorderModel()) {
  234. addKnuthElementsForBorderPaddingAfter(returnList, true);
  235. }
  236. addKnuthElementsForSpaceAfter(returnList, alignment);
  237. if (!context.suppressBreakBefore()) {
  238. //addKnuthElementsForBreakBefore(returnList, context);
  239. int breakBefore = BreakUtil.compareBreakClasses(getTable().getBreakBefore(),
  240. childLC.getBreakBefore());
  241. if (breakBefore != Constants.EN_AUTO) {
  242. returnList.add(0, new BreakElement(getAuxiliaryPosition(), 0,
  243. -KnuthElement.INFINITE, breakBefore, context));
  244. }
  245. }
  246. //addKnuthElementsForBreakAfter(returnList, context);
  247. int breakAfter = BreakUtil.compareBreakClasses(getTable().getBreakAfter(),
  248. childLC.getBreakAfter());
  249. if (breakAfter != Constants.EN_AUTO) {
  250. returnList.add(new BreakElement(getAuxiliaryPosition(),
  251. 0, -KnuthElement.INFINITE, breakAfter, context));
  252. }
  253. setFinished(true);
  254. resetSpaces();
  255. return returnList;
  256. }
  257. /** {@inheritDoc} */
  258. public Position getAuxiliaryPosition() {
  259. /*
  260. * Redefined to return a LeafPosition instead of a NonLeafPosition. The
  261. * SpaceResolver.SpaceHandlingBreakPosition constructors unwraps all
  262. * NonLeafPositions, which can lead to a NPE when a break in a table occurs at a
  263. * page with different ipd.
  264. */
  265. if (auxiliaryPosition == null) {
  266. auxiliaryPosition = new LeafPosition(this, 0);
  267. }
  268. return auxiliaryPosition;
  269. }
  270. /**
  271. * Registers the given area, that will be used to render the part of column background
  272. * covered by a table-cell. If percentages are used to place the background image, the
  273. * final bpd of the (fraction of) table that will be rendered on the current page must
  274. * be known. The traits can't then be set when the areas for the cell are created
  275. * since at that moment this bpd is yet unknown. So they will instead be set in
  276. * TableLM's {@link #addAreas(PositionIterator, LayoutContext)} method.
  277. *
  278. * @param column the table-column element from which the cell gets background
  279. * informations
  280. * @param backgroundArea the block of the cell's dimensions that will hold the column
  281. * background
  282. * @param xShift additional amount by which the image must be shifted to be correctly
  283. * placed (to counterbalance the cell's start border)
  284. */
  285. void registerColumnBackgroundArea(TableColumn column, Block backgroundArea, int xShift) {
  286. addBackgroundArea(backgroundArea);
  287. if (columnBackgroundAreas == null) {
  288. columnBackgroundAreas = new ArrayList();
  289. }
  290. columnBackgroundAreas.add(new ColumnBackgroundInfo(column, backgroundArea, xShift));
  291. }
  292. /**
  293. * The table area is a reference area that contains areas for
  294. * columns, bodies, rows and the contents are in cells.
  295. *
  296. * @param parentIter the position iterator
  297. * @param layoutContext the layout context for adding areas
  298. */
  299. public void addAreas(PositionIterator parentIter,
  300. LayoutContext layoutContext) {
  301. getParentArea(null);
  302. addId();
  303. // add space before, in order to implement display-align = "center" or "after"
  304. if (layoutContext.getSpaceBefore() != 0) {
  305. addBlockSpacing(0.0, MinOptMax.getInstance(layoutContext.getSpaceBefore()));
  306. }
  307. int startXOffset = getTable().getCommonMarginBlock().startIndent.getValue(this);
  308. // add column, body then row areas
  309. // BPD of the table, i.e., height of its content; table's borders and paddings not counted
  310. int tableHeight = 0;
  311. //Body childLM;
  312. LayoutContext lc = LayoutContext.offspringOf(layoutContext);
  313. lc.setRefIPD(getContentAreaIPD());
  314. contentLM.setStartXOffset(startXOffset);
  315. contentLM.addAreas(parentIter, lc);
  316. tableHeight += contentLM.getUsedBPD();
  317. curBlockArea.setBPD(tableHeight);
  318. if (columnBackgroundAreas != null) {
  319. for (Iterator iter = columnBackgroundAreas.iterator(); iter.hasNext();) {
  320. ColumnBackgroundInfo b = (ColumnBackgroundInfo) iter.next();
  321. TraitSetter.addBackground(b.backgroundArea,
  322. b.column.getCommonBorderPaddingBackground(), this,
  323. b.xShift, -b.backgroundArea.getYOffset(),
  324. b.column.getColumnWidth().getValue(this), tableHeight);
  325. }
  326. columnBackgroundAreas.clear();
  327. }
  328. if (getTable().isSeparateBorderModel()) {
  329. TraitSetter.addBorders(curBlockArea,
  330. getTable().getCommonBorderPaddingBackground(),
  331. discardBorderBefore, discardBorderAfter, false, false, this);
  332. TraitSetter.addPadding(curBlockArea,
  333. getTable().getCommonBorderPaddingBackground(),
  334. discardPaddingBefore, discardPaddingAfter, false, false, this);
  335. }
  336. TraitSetter.addBackground(curBlockArea,
  337. getTable().getCommonBorderPaddingBackground(),
  338. this);
  339. TraitSetter.addMargins(curBlockArea,
  340. getTable().getCommonBorderPaddingBackground(),
  341. startIndent, endIndent,
  342. this);
  343. TraitSetter.addBreaks(curBlockArea,
  344. getTable().getBreakBefore(), getTable().getBreakAfter());
  345. TraitSetter.addSpaceBeforeAfter(curBlockArea, layoutContext.getSpaceAdjust(),
  346. effSpaceBefore, effSpaceAfter);
  347. flush();
  348. resetSpaces();
  349. curBlockArea = null;
  350. notifyEndOfLayout();
  351. }
  352. /**
  353. * Return an Area which can contain the passed childArea. The childArea
  354. * may not yet have any content, but it has essential traits set.
  355. * In general, if the LayoutManager already has an Area it simply returns
  356. * it. Otherwise, it makes a new Area of the appropriate class.
  357. * It gets a parent area for its area by calling its parent LM.
  358. * Finally, based on the dimensions of the parent area, it initializes
  359. * its own area. This includes setting the content IPD and the maximum
  360. * BPD.
  361. *
  362. * @param childArea the child area
  363. * @return the parent area of the child
  364. */
  365. public Area getParentArea(Area childArea) {
  366. if (curBlockArea == null) {
  367. curBlockArea = new Block();
  368. // Set up dimensions
  369. // Must get dimensions from parent area
  370. /*Area parentArea =*/ parentLayoutManager.getParentArea(curBlockArea);
  371. TraitSetter.setProducerID(curBlockArea, getTable().getId());
  372. curBlockArea.setIPD(getContentAreaIPD());
  373. setCurrentArea(curBlockArea);
  374. }
  375. return curBlockArea;
  376. }
  377. /**
  378. * Add the child area to this layout manager.
  379. *
  380. * @param childArea the child area to add
  381. */
  382. public void addChildArea(Area childArea) {
  383. if (curBlockArea != null) {
  384. curBlockArea.addBlock((Block) childArea);
  385. }
  386. }
  387. /**
  388. * Adds the given area to this layout manager's area, without updating the used bpd.
  389. *
  390. * @param background an area
  391. */
  392. void addBackgroundArea(Block background) {
  393. curBlockArea.addChildArea(background);
  394. }
  395. /** {@inheritDoc} */
  396. public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) {
  397. // TODO Auto-generated method stub
  398. return 0;
  399. }
  400. /** {@inheritDoc} */
  401. public void discardSpace(KnuthGlue spaceGlue) {
  402. // TODO Auto-generated method stub
  403. }
  404. /** {@inheritDoc} */
  405. public KeepProperty getKeepTogetherProperty() {
  406. return getTable().getKeepTogether();
  407. }
  408. /** {@inheritDoc} */
  409. public KeepProperty getKeepWithPreviousProperty() {
  410. return getTable().getKeepWithPrevious();
  411. }
  412. /** {@inheritDoc} */
  413. public KeepProperty getKeepWithNextProperty() {
  414. return getTable().getKeepWithNext();
  415. }
  416. // --------- Property Resolution related functions --------- //
  417. /**
  418. * {@inheritDoc}
  419. */
  420. public int getBaseLength(int lengthBase, FObj fobj) {
  421. // Special handler for TableColumn width specifications
  422. if (fobj instanceof TableColumn && fobj.getParent() == getFObj()) {
  423. switch (lengthBase) {
  424. case LengthBase.CONTAINING_BLOCK_WIDTH:
  425. return getContentAreaIPD();
  426. case LengthBase.TABLE_UNITS:
  427. return (int) this.tableUnit;
  428. default:
  429. log.error("Unknown base type for LengthBase.");
  430. return 0;
  431. }
  432. } else {
  433. switch (lengthBase) {
  434. case LengthBase.TABLE_UNITS:
  435. return (int) this.tableUnit;
  436. default:
  437. return super.getBaseLength(lengthBase, fobj);
  438. }
  439. }
  440. }
  441. /** {@inheritDoc} */
  442. public void notifySpace(RelSide side, MinOptMax effectiveLength) {
  443. if (RelSide.BEFORE == side) {
  444. if (log.isDebugEnabled()) {
  445. log.debug(this + ": Space " + side + ", "
  446. + this.effSpaceBefore + "-> " + effectiveLength);
  447. }
  448. this.effSpaceBefore = effectiveLength;
  449. } else {
  450. if (log.isDebugEnabled()) {
  451. log.debug(this + ": Space " + side + ", "
  452. + this.effSpaceAfter + "-> " + effectiveLength);
  453. }
  454. this.effSpaceAfter = effectiveLength;
  455. }
  456. }
  457. /** {@inheritDoc} */
  458. public void notifyBorder(RelSide side, MinOptMax effectiveLength) {
  459. if (effectiveLength == null) {
  460. if (RelSide.BEFORE == side) {
  461. this.discardBorderBefore = true;
  462. } else {
  463. this.discardBorderAfter = true;
  464. }
  465. }
  466. if (log.isDebugEnabled()) {
  467. log.debug(this + ": Border " + side + " -> " + effectiveLength);
  468. }
  469. }
  470. /** {@inheritDoc} */
  471. public void notifyPadding(RelSide side, MinOptMax effectiveLength) {
  472. if (effectiveLength == null) {
  473. if (RelSide.BEFORE == side) {
  474. this.discardPaddingBefore = true;
  475. } else {
  476. this.discardPaddingAfter = true;
  477. }
  478. }
  479. if (log.isDebugEnabled()) {
  480. log.debug(this + ": Padding " + side + " -> " + effectiveLength);
  481. }
  482. }
  483. /** {@inheritDoc} */
  484. public void reset() {
  485. super.reset();
  486. curBlockArea = null;
  487. tableUnit = 0.0;
  488. }
  489. }