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

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