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.

TableContentLayoutManager.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  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 java.util.List;
  22. import java.util.Map;
  23. import org.apache.commons.logging.Log;
  24. import org.apache.commons.logging.LogFactory;
  25. import org.apache.fop.area.Block;
  26. import org.apache.fop.area.Trait;
  27. import org.apache.fop.datatypes.PercentBaseContext;
  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;
  32. import org.apache.fop.fo.flow.TableBody;
  33. import org.apache.fop.fo.flow.TableRow;
  34. import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
  35. import org.apache.fop.fo.properties.LengthRangeProperty;
  36. import org.apache.fop.layoutmgr.BreakElement;
  37. import org.apache.fop.layoutmgr.ElementListObserver;
  38. import org.apache.fop.layoutmgr.ElementListUtils;
  39. import org.apache.fop.layoutmgr.KnuthBox;
  40. import org.apache.fop.layoutmgr.KnuthElement;
  41. import org.apache.fop.layoutmgr.KnuthPenalty;
  42. import org.apache.fop.layoutmgr.KnuthPossPosIter;
  43. import org.apache.fop.layoutmgr.LayoutContext;
  44. import org.apache.fop.layoutmgr.ListElement;
  45. import org.apache.fop.layoutmgr.MinOptMaxUtil;
  46. import org.apache.fop.layoutmgr.Position;
  47. import org.apache.fop.layoutmgr.PositionIterator;
  48. import org.apache.fop.layoutmgr.TraitSetter;
  49. import org.apache.fop.layoutmgr.SpaceResolver.SpaceHandlingBreakPosition;
  50. import org.apache.fop.traits.MinOptMax;
  51. /**
  52. * Layout manager for table contents, particularly managing the creation of combined element lists.
  53. */
  54. public class TableContentLayoutManager implements PercentBaseContext {
  55. /** Logger **/
  56. private static Log log = LogFactory.getLog(TableContentLayoutManager.class);
  57. private TableLayoutManager tableLM;
  58. private TableRowIterator bodyIter;
  59. private TableRowIterator headerIter;
  60. private TableRowIterator footerIter;
  61. private LinkedList headerList;
  62. private LinkedList footerList;
  63. private int headerNetHeight = 0;
  64. private int footerNetHeight = 0;
  65. private int startXOffset;
  66. private int usedBPD;
  67. private TableStepper stepper = new TableStepper(this);
  68. /**
  69. * Main constructor
  70. * @param parent Parent layout manager
  71. */
  72. public TableContentLayoutManager(TableLayoutManager parent) {
  73. this.tableLM = parent;
  74. Table table = getTableLM().getTable();
  75. this.bodyIter = new TableRowIterator(table, getTableLM().getColumns(),
  76. TableRowIterator.BODY);
  77. if (table.getTableHeader() != null) {
  78. headerIter = new TableRowIterator(table,
  79. getTableLM().getColumns(), TableRowIterator.HEADER);
  80. }
  81. if (table.getTableFooter() != null) {
  82. footerIter = new TableRowIterator(table,
  83. getTableLM().getColumns(), TableRowIterator.FOOTER);
  84. }
  85. }
  86. /**
  87. * @return the table layout manager
  88. */
  89. public TableLayoutManager getTableLM() {
  90. return this.tableLM;
  91. }
  92. /** @return true if the table uses the separate border model. */
  93. boolean isSeparateBorderModel() {
  94. return getTableLM().getTable().isSeparateBorderModel();
  95. }
  96. /**
  97. * @return the column setup of this table
  98. */
  99. public ColumnSetup getColumns() {
  100. return getTableLM().getColumns();
  101. }
  102. /** @return the net header height */
  103. protected int getHeaderNetHeight() {
  104. return this.headerNetHeight;
  105. }
  106. /** @return the net footer height */
  107. protected int getFooterNetHeight() {
  108. return this.footerNetHeight;
  109. }
  110. /** @return the header element list */
  111. protected LinkedList getHeaderElements() {
  112. return this.headerList;
  113. }
  114. /** @return the footer element list */
  115. protected LinkedList getFooterElements() {
  116. return this.footerList;
  117. }
  118. /** @see org.apache.fop.layoutmgr.LayoutManager#getNextKnuthElements(LayoutContext, int) */
  119. public LinkedList getNextKnuthElements(LayoutContext context, int alignment) {
  120. log.debug("==> Columns: " + getTableLM().getColumns());
  121. KnuthBox headerAsFirst = null;
  122. KnuthBox headerAsSecondToLast = null;
  123. KnuthBox footerAsLast = null;
  124. if (headerIter != null && headerList == null) {
  125. this.headerList = getKnuthElementsForRowIterator(
  126. headerIter, context, alignment, TableRowIterator.HEADER);
  127. ElementListUtils.removeLegalBreaks(this.headerList);
  128. this.headerNetHeight
  129. = ElementListUtils.calcContentLength(this.headerList);
  130. if (log.isDebugEnabled()) {
  131. log.debug("==> Header: "
  132. + headerNetHeight + " - " + this.headerList);
  133. }
  134. TableHeaderFooterPosition pos = new TableHeaderFooterPosition(
  135. getTableLM(), true, this.headerList);
  136. KnuthBox box = new KnuthBox(headerNetHeight, pos, false);
  137. if (getTableLM().getTable().omitHeaderAtBreak()) {
  138. //We can simply add the table header at the start
  139. //of the whole list
  140. headerAsFirst = box;
  141. } else {
  142. headerAsSecondToLast = box;
  143. }
  144. }
  145. if (footerIter != null && footerList == null) {
  146. this.footerList = getKnuthElementsForRowIterator(
  147. footerIter, context, alignment, TableRowIterator.FOOTER);
  148. ElementListUtils.removeLegalBreaks(this.footerList);
  149. this.footerNetHeight
  150. = ElementListUtils.calcContentLength(this.footerList);
  151. if (log.isDebugEnabled()) {
  152. log.debug("==> Footer: "
  153. + footerNetHeight + " - " + this.footerList);
  154. }
  155. //We can simply add the table footer at the end of the whole list
  156. TableHeaderFooterPosition pos = new TableHeaderFooterPosition(
  157. getTableLM(), false, this.footerList);
  158. KnuthBox box = new KnuthBox(footerNetHeight, pos, false);
  159. footerAsLast = box;
  160. }
  161. LinkedList returnList = getKnuthElementsForRowIterator(
  162. bodyIter, context, alignment, TableRowIterator.BODY);
  163. if (headerAsFirst != null) {
  164. int insertionPoint = 0;
  165. if (returnList.size() > 0 && ((ListElement)returnList.getFirst()).isForcedBreak()) {
  166. insertionPoint++;
  167. }
  168. returnList.add(insertionPoint, headerAsFirst);
  169. } else if (headerAsSecondToLast != null) {
  170. int insertionPoint = returnList.size();
  171. if (returnList.size() > 0 && ((ListElement)returnList.getLast()).isForcedBreak()) {
  172. insertionPoint--;
  173. }
  174. returnList.add(insertionPoint, headerAsSecondToLast);
  175. }
  176. if (footerAsLast != null) {
  177. int insertionPoint = returnList.size();
  178. if (returnList.size() > 0 && ((ListElement)returnList.getLast()).isForcedBreak()) {
  179. insertionPoint--;
  180. }
  181. returnList.add(insertionPoint, footerAsLast);
  182. }
  183. return returnList;
  184. }
  185. /**
  186. * Creates Knuth elements by iterating over a TableRowIterator.
  187. * @param iter TableRowIterator instance to fetch rows from
  188. * @param context Active LayoutContext
  189. * @param alignment alignment indicator
  190. * @param bodyType Indicates what kind of body is being processed
  191. * (BODY, HEADER or FOOTER)
  192. * @return An element list
  193. */
  194. private LinkedList getKnuthElementsForRowIterator(TableRowIterator iter,
  195. LayoutContext context, int alignment, int bodyType) {
  196. LinkedList returnList = new LinkedList();
  197. EffRow[] rowGroup = null;
  198. while ((rowGroup = iter.getNextRowGroup()) != null) {
  199. returnList.addAll(new RowGroupLayoutManager(getTableLM(), rowGroup, bodyIter,
  200. headerIter, footerIter, iter, stepper).getNextKnuthElements(context, alignment,
  201. bodyType));
  202. }
  203. if (returnList.size() > 0) {
  204. //Remove the last penalty produced by the combining algorithm (see TableStepper), for the last step
  205. ListElement last = (ListElement)returnList.getLast();
  206. if (last.isPenalty() || last instanceof BreakElement) {
  207. if (!last.isForcedBreak()) {
  208. //Only remove if we don't signal a forced break
  209. returnList.removeLast();
  210. }
  211. }
  212. }
  213. //fox:widow-content-limit
  214. int widowContentLimit = getTableLM().getTable().getWidowContentLimit().getValue();
  215. if (widowContentLimit != 0 && bodyType == TableRowIterator.BODY) {
  216. ElementListUtils.removeLegalBreaks(returnList, widowContentLimit);
  217. }
  218. //fox:orphan-content-limit
  219. int orphanContentLimit = getTableLM().getTable().getOrphanContentLimit().getValue();
  220. if (orphanContentLimit != 0 && bodyType == TableRowIterator.BODY) {
  221. ElementListUtils.removeLegalBreaksFromEnd(returnList, orphanContentLimit);
  222. }
  223. return returnList;
  224. }
  225. /**
  226. * Retuns the X offset of the given grid unit.
  227. * @param gu the grid unit
  228. * @return the requested X offset
  229. */
  230. protected int getXOffsetOfGridUnit(GridUnit gu) {
  231. int col = gu.getStartCol();
  232. return startXOffset + getTableLM().getColumns().getXOffset(col + 1, getTableLM());
  233. }
  234. /**
  235. * Adds the areas generated by this layout manager to the area tree.
  236. * @param parentIter the position iterator
  237. * @param layoutContext the layout context for adding areas
  238. */
  239. public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) {
  240. this.usedBPD = 0;
  241. RowPainter painter = new RowPainter(this, layoutContext);
  242. List positions = new java.util.ArrayList();
  243. List headerElements = null;
  244. List footerElements = null;
  245. Position firstPos = null;
  246. Position lastPos = null;
  247. Position lastCheckPos = null;
  248. while (parentIter.hasNext()) {
  249. Position pos = (Position)parentIter.next();
  250. if (pos instanceof SpaceHandlingBreakPosition) {
  251. //This position has only been needed before addAreas was called, now we need the
  252. //original one created by the layout manager.
  253. pos = ((SpaceHandlingBreakPosition)pos).getOriginalBreakPosition();
  254. }
  255. if (pos == null) {
  256. continue;
  257. }
  258. if (firstPos == null) {
  259. firstPos = pos;
  260. }
  261. lastPos = pos;
  262. if (pos.getIndex() >= 0) {
  263. lastCheckPos = pos;
  264. }
  265. if (pos instanceof TableHeaderFooterPosition) {
  266. TableHeaderFooterPosition thfpos = (TableHeaderFooterPosition)pos;
  267. //these positions need to be unpacked
  268. if (thfpos.header) {
  269. //Positions for header will be added first
  270. headerElements = thfpos.nestedElements;
  271. } else {
  272. //Positions for footers are simply added at the end
  273. footerElements = thfpos.nestedElements;
  274. }
  275. } else if (pos instanceof TableHFPenaltyPosition) {
  276. //ignore for now, see special handling below if break is at a penalty
  277. //Only if the last position in this part/page us such a position it will be used
  278. } else {
  279. //leave order as is for the rest
  280. positions.add(pos);
  281. }
  282. }
  283. if (lastPos instanceof TableHFPenaltyPosition) {
  284. TableHFPenaltyPosition penaltyPos = (TableHFPenaltyPosition)lastPos;
  285. log.debug("Break at penalty!");
  286. if (penaltyPos.headerElements != null) {
  287. //Header positions for the penalty position are in the last element and need to
  288. //be handled first before all other TableContentPositions
  289. headerElements = penaltyPos.headerElements;
  290. }
  291. if (penaltyPos.footerElements != null) {
  292. footerElements = penaltyPos.footerElements;
  293. }
  294. }
  295. Map markers = getTableLM().getTable().getMarkers();
  296. if (markers != null) {
  297. getTableLM().getCurrentPV().addMarkers(markers,
  298. true, getTableLM().isFirst(firstPos), getTableLM().isLast(lastCheckPos));
  299. }
  300. if (headerElements != null) {
  301. //header positions for the last part are the second-to-last element and need to
  302. //be handled first before all other TableContentPositions
  303. PositionIterator nestedIter = new KnuthPossPosIter(headerElements);
  304. iterateAndPaintPositions(nestedIter, painter);
  305. }
  306. //Iterate over all steps
  307. Iterator posIter = positions.iterator();
  308. iterateAndPaintPositions(posIter, painter);
  309. if (footerElements != null) {
  310. //Positions for footers are simply added at the end
  311. PositionIterator nestedIter = new KnuthPossPosIter(footerElements);
  312. iterateAndPaintPositions(nestedIter, painter);
  313. }
  314. this.usedBPD += painter.getAccumulatedBPD();
  315. if (markers != null) {
  316. getTableLM().getCurrentPV().addMarkers(markers,
  317. false, getTableLM().isFirst(firstPos), getTableLM().isLast(lastCheckPos));
  318. }
  319. }
  320. /**
  321. * Iterates over a part of the table (header, footer, body) and paints the related
  322. * elements.
  323. *
  324. * @param iterator iterator over Position elements. Those positions correspond to the
  325. * elements of the table present on the current page
  326. * @param painter
  327. */
  328. private void iterateAndPaintPositions(Iterator iterator, RowPainter painter) {
  329. List lst = new java.util.ArrayList();
  330. boolean firstPos = false;
  331. TableBody body = null;
  332. while (iterator.hasNext()) {
  333. Position pos = (Position)iterator.next();
  334. if (pos instanceof TableContentPosition) {
  335. TableContentPosition tcpos = (TableContentPosition)pos;
  336. lst.add(tcpos);
  337. GridUnitPart part = (GridUnitPart)tcpos.gridUnitParts.get(0);
  338. if (body == null) {
  339. body = part.pgu.getBody();
  340. }
  341. if (tcpos.getFlag(TableContentPosition.FIRST_IN_ROWGROUP)
  342. && tcpos.row.getFlag(EffRow.FIRST_IN_PART)) {
  343. firstPos = true;
  344. }
  345. if (tcpos.getFlag(TableContentPosition.LAST_IN_ROWGROUP)
  346. && tcpos.row.getFlag(EffRow.LAST_IN_PART)) {
  347. log.trace("LAST_IN_ROWGROUP + LAST_IN_PART");
  348. handleMarkersAndPositions(lst, body, firstPos, true, painter);
  349. //reset
  350. firstPos = false;
  351. body = null;
  352. lst.clear();
  353. }
  354. } else {
  355. if (log.isDebugEnabled()) {
  356. log.debug("Ignoring position: " + pos);
  357. }
  358. }
  359. }
  360. if (body != null) {
  361. // Entering this block means that the end of the current table-part hasn't
  362. // been reached (otherwise it would have been caught by the test above). So
  363. // lastPos is necessarily false
  364. handleMarkersAndPositions(lst, body, firstPos, false, painter);
  365. }
  366. painter.addAreasAndFlushRow(true);
  367. }
  368. private void handleMarkersAndPositions(List positions, TableBody body, boolean firstPos,
  369. boolean lastPos, RowPainter painter) {
  370. getTableLM().getCurrentPV().addMarkers(body.getMarkers(),
  371. true, firstPos, lastPos);
  372. int size = positions.size();
  373. for (int i = 0; i < size; i++) {
  374. painter.handleTableContentPosition((TableContentPosition)positions.get(i));
  375. }
  376. getTableLM().getCurrentPV().addMarkers(body.getMarkers(),
  377. false, firstPos, lastPos);
  378. }
  379. /**
  380. * Get the area for a row for background.
  381. * @param row the table-row object or null
  382. * @return the row area or null if there's no background to paint
  383. */
  384. public Block getRowArea(TableRow row) {
  385. if (row == null || !row.getCommonBorderPaddingBackground().hasBackground()) {
  386. return null;
  387. } else {
  388. Block block = new Block();
  389. block.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE);
  390. block.setPositioning(Block.ABSOLUTE);
  391. return block;
  392. }
  393. }
  394. /**
  395. * Adds the area for the row background if any.
  396. * @param row row for which to generate the background
  397. * @param bpd block-progression-dimension of the row
  398. * @param ipd inline-progression-dimension of the row
  399. * @param yoffset Y offset at which to paint
  400. */
  401. public void addRowBackgroundArea(TableRow row, int bpd, int ipd, int yoffset) {
  402. //Add row background if any
  403. Block rowBackground = getRowArea(row);
  404. if (rowBackground != null) {
  405. rowBackground.setBPD(bpd);
  406. rowBackground.setIPD(ipd);
  407. rowBackground.setXOffset(this.startXOffset);
  408. rowBackground.setYOffset(yoffset);
  409. getTableLM().addChildArea(rowBackground);
  410. TraitSetter.addBackground(rowBackground,
  411. row.getCommonBorderPaddingBackground(), getTableLM());
  412. }
  413. }
  414. /**
  415. * Sets the overall starting x-offset. Used for proper placement of cells.
  416. * @param startXOffset starting x-offset (table's start-indent)
  417. */
  418. public void setStartXOffset(int startXOffset) {
  419. this.startXOffset = startXOffset;
  420. }
  421. /**
  422. * @return the amount of block-progression-dimension used by the content
  423. */
  424. public int getUsedBPD() {
  425. return this.usedBPD;
  426. }
  427. // --------- Property Resolution related functions --------- //
  428. /**
  429. * {@inheritDoc}
  430. */
  431. public int getBaseLength(int lengthBase, FObj fobj) {
  432. return tableLM.getBaseLength(lengthBase, fobj);
  433. }
  434. }