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.

RowGroupLayoutManager.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  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.LinkedList;
  20. import java.util.List;
  21. import org.apache.commons.logging.Log;
  22. import org.apache.commons.logging.LogFactory;
  23. import org.apache.fop.fo.Constants;
  24. import org.apache.fop.fo.FONode;
  25. import org.apache.fop.fo.flow.TableRow;
  26. import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
  27. import org.apache.fop.fo.properties.LengthRangeProperty;
  28. import org.apache.fop.layoutmgr.BreakElement;
  29. import org.apache.fop.layoutmgr.ElementListObserver;
  30. import org.apache.fop.layoutmgr.ElementListUtils;
  31. import org.apache.fop.layoutmgr.KnuthElement;
  32. import org.apache.fop.layoutmgr.KnuthPenalty;
  33. import org.apache.fop.layoutmgr.LayoutContext;
  34. import org.apache.fop.layoutmgr.ListElement;
  35. import org.apache.fop.layoutmgr.MinOptMaxUtil;
  36. import org.apache.fop.traits.MinOptMax;
  37. class RowGroupLayoutManager {
  38. private static Log log = LogFactory.getLog(TableContentLayoutManager.class);
  39. private EffRow[] rowGroup;
  40. private TableLayoutManager tableLM;
  41. private TableRowIterator bodyIter;
  42. private TableRowIterator headerIter;
  43. private TableRowIterator footerIter;
  44. private TableRowIterator thisIter;
  45. private TableStepper tableStepper;
  46. RowGroupLayoutManager(TableLayoutManager tableLM, EffRow[] rowGroup, TableRowIterator bodyIter,
  47. TableRowIterator headerIter, TableRowIterator footerIter, TableRowIterator thisIter,
  48. TableStepper tableStepper) {
  49. this.tableLM = tableLM;
  50. this.rowGroup = rowGroup;
  51. this.bodyIter = bodyIter;
  52. this.headerIter = headerIter;
  53. this.footerIter = footerIter;
  54. this.thisIter = thisIter;
  55. this.tableStepper = tableStepper;
  56. }
  57. /**
  58. *
  59. * @return one of {@link Constants#EN_AUTO}, {@link Constants#EN_COLUMN},
  60. * {@link Constants#EN_PAGE}, {@link Constants#EN_EVEN_PAGE}, or
  61. * {@link Constants#EN_ODD_PAGE}
  62. */
  63. int getBreakBefore() {
  64. TableRow rowFO = rowGroup[0].getTableRow();
  65. if (rowFO == null) {
  66. return Constants.EN_AUTO;
  67. } else {
  68. return rowFO.getBreakBefore();
  69. }
  70. }
  71. /**
  72. *
  73. * @return one of {@link Constants#EN_AUTO}, {@link Constants#EN_COLUMN},
  74. * {@link Constants#EN_PAGE}, {@link Constants#EN_EVEN_PAGE}, or
  75. * {@link Constants#EN_ODD_PAGE}
  76. */
  77. int getBreakAfter() {
  78. TableRow rowFO = rowGroup[rowGroup.length - 1].getTableRow();
  79. if (rowFO == null) {
  80. return Constants.EN_AUTO;
  81. } else {
  82. return rowFO.getBreakAfter();
  83. }
  84. }
  85. public LinkedList getNextKnuthElements(LayoutContext context, int alignment, int bodyType) {
  86. LinkedList returnList = new LinkedList();
  87. //Border resolution
  88. if (!tableLM.getTable().isSeparateBorderModel()) {
  89. resolveNormalBeforeAfterBordersForRowGroup();
  90. }
  91. //Reset keep-with-next when remaining inside the table.
  92. //The context flag is only used to propagate keep-with-next to the outside.
  93. //The clearing is ok here because createElementsForRowGroup already handles
  94. //the keep when inside a table.
  95. context.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING, false);
  96. //Element list creation
  97. createElementsForRowGroup(context, alignment, bodyType, returnList);
  98. //Handle keeps
  99. if (context.isKeepWithNextPending()) {
  100. log.debug("child LM (row group) signals pending keep-with-next");
  101. }
  102. if (context.isKeepWithPreviousPending()) {
  103. log.debug("child LM (row group) signals pending keep-with-previous");
  104. if (returnList.size() > 0) {
  105. //Modify last penalty
  106. ListElement last = (ListElement)returnList.getLast();
  107. if (last.isPenalty()) {
  108. BreakElement breakPoss = (BreakElement)last;
  109. //Only honor keep if there's no forced break
  110. if (!breakPoss.isForcedBreak()) {
  111. breakPoss.setPenaltyValue(KnuthPenalty.INFINITE);
  112. }
  113. }
  114. }
  115. }
  116. return returnList;
  117. }
  118. /**
  119. * Resolves normal borders for a row group.
  120. * @param iter Table row iterator to operate on
  121. */
  122. private void resolveNormalBeforeAfterBordersForRowGroup() {
  123. for (int rgi = 0; rgi < rowGroup.length; rgi++) {
  124. EffRow row = rowGroup[rgi];
  125. EffRow prevRow = thisIter.getPrecedingRow(row);
  126. EffRow nextRow = thisIter.getFollowingRow(row);
  127. if ((prevRow == null) && (thisIter == bodyIter) && (headerIter != null)) {
  128. prevRow = headerIter.getLastRow();
  129. }
  130. if ((nextRow == null) && (thisIter == headerIter)) {
  131. nextRow = bodyIter.getFirstRow();
  132. }
  133. if ((nextRow == null) && (thisIter == bodyIter) && (footerIter != null)) {
  134. nextRow = footerIter.getFirstRow();
  135. }
  136. if ((prevRow == null) && (thisIter == footerIter)) {
  137. //TODO This could be bad for memory consumption because it already causes the
  138. //whole body iterator to be prefetched!
  139. prevRow = bodyIter.getLastRow();
  140. }
  141. log.debug("prevRow-row-nextRow: " + prevRow + " - " + row + " - " + nextRow);
  142. //Determine the grid units necessary for getting all the borders right
  143. int guCount = row.getGridUnits().size();
  144. if (prevRow != null) {
  145. guCount = Math.max(guCount, prevRow.getGridUnits().size());
  146. }
  147. if (nextRow != null) {
  148. guCount = Math.max(guCount, nextRow.getGridUnits().size());
  149. }
  150. GridUnit gu = row.getGridUnit(0);
  151. //Create empty grid units to hold resolved borders of neighbouring cells
  152. //TODO maybe this needs to be done differently (and sooner)
  153. for (int i = 0; i < guCount - row.getGridUnits().size(); i++) {
  154. //TODO This block is untested!
  155. int pos = row.getGridUnits().size() + i;
  156. row.getGridUnits().add(new EmptyGridUnit(gu.getRow(),
  157. tableLM.getColumns().getColumn(pos + 1), gu.getBody(),
  158. pos));
  159. }
  160. //Now resolve normal borders
  161. if (tableLM.getTable().isSeparateBorderModel()) {
  162. //nop, borders are already assigned at this point
  163. } else {
  164. for (int i = 0; i < row.getGridUnits().size(); i++) {
  165. gu = row.getGridUnit(i);
  166. GridUnit other;
  167. int flags = 0;
  168. if (prevRow != null && i < prevRow.getGridUnits().size()) {
  169. other = prevRow.getGridUnit(i);
  170. } else {
  171. other = null;
  172. }
  173. if (other == null
  174. || other.isEmpty()
  175. || gu.isEmpty()
  176. || gu.getPrimary() != other.getPrimary()) {
  177. if ((thisIter == bodyIter)
  178. && gu.getFlag(GridUnit.FIRST_IN_TABLE)
  179. && (headerIter == null)) {
  180. flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
  181. }
  182. if ((thisIter == headerIter)
  183. && gu.getFlag(GridUnit.FIRST_IN_TABLE)) {
  184. flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
  185. }
  186. gu.resolveBorder(other,
  187. CommonBorderPaddingBackground.BEFORE, flags);
  188. }
  189. flags = 0;
  190. if (nextRow != null && i < nextRow.getGridUnits().size()) {
  191. other = nextRow.getGridUnit(i);
  192. } else {
  193. other = null;
  194. }
  195. if (other == null
  196. || other.isEmpty()
  197. || gu.isEmpty()
  198. || gu.getPrimary() != other.getPrimary()) {
  199. if ((thisIter == bodyIter)
  200. && gu.getFlag(GridUnit.LAST_IN_TABLE)
  201. && (footerIter == null)) {
  202. flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
  203. }
  204. if ((thisIter == footerIter)
  205. && gu.getFlag(GridUnit.LAST_IN_TABLE)) {
  206. flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
  207. }
  208. gu.resolveBorder(other,
  209. CommonBorderPaddingBackground.AFTER, flags);
  210. }
  211. }
  212. }
  213. }
  214. }
  215. /**
  216. * Creates Knuth elements for a row group (see TableRowIterator.getNextRowGroup()).
  217. * @param context Active LayoutContext
  218. * @param alignment alignment indicator
  219. * @param bodyType Indicates what kind of body is being processed (BODY, HEADER or FOOTER)
  220. * @param returnList List to received the generated elements
  221. * @param rowGroup row group to process
  222. */
  223. private void createElementsForRowGroup(LayoutContext context, int alignment,
  224. int bodyType, LinkedList returnList) {
  225. log.debug("Handling row group with " + rowGroup.length + " rows...");
  226. MinOptMax[] rowHeights = new MinOptMax[rowGroup.length];
  227. MinOptMax[] explicitRowHeights = new MinOptMax[rowGroup.length];
  228. EffRow row;
  229. int maxColumnCount = 0;
  230. List pgus = new java.util.ArrayList(); //holds a list of a row's primary grid units
  231. for (int rgi = 0; rgi < rowGroup.length; rgi++) {
  232. row = rowGroup[rgi];
  233. rowHeights[rgi] = new MinOptMax(0, 0, Integer.MAX_VALUE);
  234. explicitRowHeights[rgi] = new MinOptMax(0, 0, Integer.MAX_VALUE);
  235. pgus.clear();
  236. TableRow tableRow = null;
  237. // The row's minimum content height; 0 if the row's height is auto, otherwise
  238. // the .minimum component of the explicitely specified value
  239. int minContentHeight = 0;
  240. int maxCellHeight = 0;
  241. int effRowContentHeight = 0;
  242. for (int j = 0; j < row.getGridUnits().size(); j++) {
  243. // assert maxColumnCount == 0 || maxColumnCount == row.getGridUnits().size(); // TODO vh
  244. maxColumnCount = Math.max(maxColumnCount, row.getGridUnits().size());
  245. GridUnit gu = row.getGridUnit(j);
  246. if ((gu.isPrimary() || (gu.getColSpanIndex() == 0 && gu.isLastGridUnitRowSpan()))
  247. && !gu.isEmpty()) {
  248. PrimaryGridUnit primary = gu.getPrimary();
  249. if (gu.isPrimary()) {
  250. primary.getCellLM().setParent(tableLM);
  251. //Determine the table-row if any
  252. if (tableRow == null && primary.getRow() != null) {
  253. tableRow = primary.getRow();
  254. //Check for bpd on row, see CSS21, 17.5.3 Table height algorithms
  255. LengthRangeProperty bpd = tableRow.getBlockProgressionDimension();
  256. if (!bpd.getMinimum(tableLM).isAuto()) {
  257. minContentHeight = Math.max(
  258. minContentHeight,
  259. bpd.getMinimum(
  260. tableLM).getLength().getValue(tableLM));
  261. }
  262. MinOptMaxUtil.restrict(explicitRowHeights[rgi], bpd, tableLM);
  263. }
  264. //Calculate width of cell
  265. int spanWidth = 0;
  266. for (int i = primary.getStartCol();
  267. i < primary.getStartCol()
  268. + primary.getCell().getNumberColumnsSpanned();
  269. i++) {
  270. if (tableLM.getColumns().getColumn(i + 1) != null) {
  271. spanWidth += tableLM.getColumns().getColumn(i + 1)
  272. .getColumnWidth().getValue(tableLM);
  273. }
  274. }
  275. LayoutContext childLC = new LayoutContext(0);
  276. childLC.setStackLimit(context.getStackLimit()); //necessary?
  277. childLC.setRefIPD(spanWidth);
  278. //Get the element list for the cell contents
  279. LinkedList elems = primary.getCellLM().getNextKnuthElements(
  280. childLC, alignment);
  281. ElementListObserver.observe(elems, "table-cell", primary.getCell().getId());
  282. if ((elems.size() > 0)
  283. && ((KnuthElement)elems.getLast()).isForcedBreak()) {
  284. // a descendant of this block has break-after
  285. log.debug("Descendant of table-cell signals break: "
  286. + primary.getCellLM().isFinished());
  287. }
  288. primary.setElements(elems);
  289. if (childLC.isKeepWithNextPending()) {
  290. log.debug("child LM signals pending keep-with-next");
  291. primary.setFlag(GridUnit.KEEP_WITH_NEXT_PENDING, true);
  292. }
  293. if (childLC.isKeepWithPreviousPending()) {
  294. log.debug("child LM signals pending keep-with-previous");
  295. primary.setFlag(GridUnit.KEEP_WITH_PREVIOUS_PENDING, true);
  296. }
  297. }
  298. //Calculate height of cell contents
  299. primary.setContentLength(ElementListUtils.calcContentLength(
  300. primary.getElements()));
  301. maxCellHeight = Math.max(maxCellHeight, primary.getContentLength());
  302. //Calculate height of row, see CSS21, 17.5.3 Table height algorithms
  303. if (gu.isLastGridUnitRowSpan()) {
  304. int effCellContentHeight = minContentHeight;
  305. LengthRangeProperty bpd = primary.getCell().getBlockProgressionDimension();
  306. if (!bpd.getMinimum(tableLM).isAuto()) {
  307. effCellContentHeight = Math.max(
  308. effCellContentHeight,
  309. bpd.getMinimum(tableLM).getLength().getValue(tableLM));
  310. }
  311. if (!bpd.getOptimum(tableLM).isAuto()) {
  312. effCellContentHeight = Math.max(
  313. effCellContentHeight,
  314. bpd.getOptimum(tableLM).getLength().getValue(tableLM));
  315. }
  316. if (gu.getRowSpanIndex() == 0) {
  317. //TODO ATM only non-row-spanned cells are taken for this
  318. MinOptMaxUtil.restrict(explicitRowHeights[rgi], bpd, tableLM);
  319. }
  320. effCellContentHeight = Math.max(effCellContentHeight,
  321. primary.getContentLength());
  322. int borderWidths;
  323. if (tableLM.getTable().isSeparateBorderModel()) {
  324. borderWidths = primary.getBorders().getBorderBeforeWidth(false)
  325. + primary.getBorders().getBorderAfterWidth(false);
  326. } else {
  327. borderWidths = primary.getHalfMaxBorderWidth();
  328. }
  329. int padding = 0;
  330. effRowContentHeight = Math.max(effRowContentHeight,
  331. effCellContentHeight);
  332. CommonBorderPaddingBackground cbpb
  333. = primary.getCell().getCommonBorderPaddingBackground();
  334. padding += cbpb.getPaddingBefore(false, primary.getCellLM());
  335. padding += cbpb.getPaddingAfter(false, primary.getCellLM());
  336. int effRowHeight = effCellContentHeight
  337. + padding + borderWidths
  338. + 2 * tableLM.getHalfBorderSeparationBPD();
  339. for (int previous = 0; previous < gu.getRowSpanIndex(); previous++) {
  340. effRowHeight -= rowHeights[rgi - previous - 1].opt;
  341. }
  342. if (effRowHeight > rowHeights[rgi].min) {
  343. //This is the new height of the (grid) row
  344. MinOptMaxUtil.extendMinimum(rowHeights[rgi], effRowHeight, false);
  345. }
  346. }
  347. if (gu.isPrimary()) {
  348. pgus.add(primary);
  349. }
  350. }
  351. }
  352. row.setHeight(rowHeights[rgi]);
  353. row.setExplicitHeight(explicitRowHeights[rgi]);
  354. if (effRowContentHeight > row.getExplicitHeight().max) {
  355. log.warn(FONode.decorateWithContextInfo(
  356. "The contents of row " + (row.getIndex() + 1)
  357. + " are taller than they should be (there is a"
  358. + " block-progression-dimension or height constraint on the indicated row)."
  359. + " Due to its contents the row grows"
  360. + " to " + effRowContentHeight + " millipoints, but the row shouldn't get"
  361. + " any taller than " + row.getExplicitHeight() + " millipoints.",
  362. row.getTableRow()));
  363. }
  364. }
  365. if (log.isDebugEnabled()) {
  366. log.debug("rowGroup:");
  367. for (int i = 0; i < rowHeights.length; i++) {
  368. log.debug(" height=" + rowHeights[i] + " explicit=" + explicitRowHeights[i]);
  369. }
  370. }
  371. LinkedList returnedList = tableStepper.getCombinedKnuthElementsForRowGroup(
  372. context, rowGroup, maxColumnCount, bodyType);
  373. if (returnedList != null) {
  374. returnList.addAll(returnedList);
  375. }
  376. }
  377. }