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

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