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

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