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 17KB

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