001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /* $Id: TableContentLayoutManager.java 555651 2007-07-12 14:59:06Z vhennebert $ */
019:
020: package org.apache.fop.layoutmgr.table;
021:
022: import java.util.Iterator;
023: import java.util.LinkedList;
024: import java.util.List;
025: import java.util.Map;
026:
027: import org.apache.commons.logging.Log;
028: import org.apache.commons.logging.LogFactory;
029: import org.apache.fop.area.Block;
030: import org.apache.fop.area.Trait;
031: import org.apache.fop.datatypes.PercentBaseContext;
032: import org.apache.fop.fo.Constants;
033: import org.apache.fop.fo.FONode;
034: import org.apache.fop.fo.FObj;
035: import org.apache.fop.fo.flow.Table;
036: import org.apache.fop.fo.flow.TableBody;
037: import org.apache.fop.fo.flow.TableRow;
038: import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
039: import org.apache.fop.fo.properties.LengthRangeProperty;
040: import org.apache.fop.layoutmgr.BreakElement;
041: import org.apache.fop.layoutmgr.ElementListObserver;
042: import org.apache.fop.layoutmgr.ElementListUtils;
043: import org.apache.fop.layoutmgr.KnuthBox;
044: import org.apache.fop.layoutmgr.KnuthElement;
045: import org.apache.fop.layoutmgr.KnuthPenalty;
046: import org.apache.fop.layoutmgr.KnuthPossPosIter;
047: import org.apache.fop.layoutmgr.LayoutContext;
048: import org.apache.fop.layoutmgr.ListElement;
049: import org.apache.fop.layoutmgr.MinOptMaxUtil;
050: import org.apache.fop.layoutmgr.Position;
051: import org.apache.fop.layoutmgr.PositionIterator;
052: import org.apache.fop.layoutmgr.TraitSetter;
053: import org.apache.fop.layoutmgr.SpaceResolver.SpaceHandlingBreakPosition;
054: import org.apache.fop.traits.MinOptMax;
055:
056: /**
057: * Layout manager for table contents, particularly managing the creation of combined element lists.
058: */
059: public class TableContentLayoutManager implements PercentBaseContext {
060:
061: /** Logger **/
062: private static Log log = LogFactory
063: .getLog(TableContentLayoutManager.class);
064:
065: private TableLayoutManager tableLM;
066: private TableRowIterator bodyIter;
067: private TableRowIterator headerIter;
068: private TableRowIterator footerIter;
069: private LinkedList headerList;
070: private LinkedList footerList;
071: private int headerNetHeight = 0;
072: private int footerNetHeight = 0;
073:
074: private int startXOffset;
075: private int usedBPD;
076:
077: private TableStepper stepper = new TableStepper(this );
078:
079: /**
080: * Main constructor
081: * @param parent Parent layout manager
082: */
083: public TableContentLayoutManager(TableLayoutManager parent) {
084: this .tableLM = parent;
085: Table table = getTableLM().getTable();
086: this .bodyIter = new TableRowIterator(table, getTableLM()
087: .getColumns(), TableRowIterator.BODY);
088: if (table.getTableHeader() != null) {
089: headerIter = new TableRowIterator(table, getTableLM()
090: .getColumns(), TableRowIterator.HEADER);
091: }
092: if (table.getTableFooter() != null) {
093: footerIter = new TableRowIterator(table, getTableLM()
094: .getColumns(), TableRowIterator.FOOTER);
095: }
096: }
097:
098: /**
099: * @return the table layout manager
100: */
101: public TableLayoutManager getTableLM() {
102: return this .tableLM;
103: }
104:
105: /** @return true if the table uses the separate border model. */
106: boolean isSeparateBorderModel() {
107: return getTableLM().getTable().isSeparateBorderModel();
108: }
109:
110: /**
111: * @return the column setup of this table
112: */
113: public ColumnSetup getColumns() {
114: return getTableLM().getColumns();
115: }
116:
117: /** @return the net header height */
118: protected int getHeaderNetHeight() {
119: return this .headerNetHeight;
120: }
121:
122: /** @return the net footer height */
123: protected int getFooterNetHeight() {
124: return this .footerNetHeight;
125: }
126:
127: /** @return the header element list */
128: protected LinkedList getHeaderElements() {
129: return this .headerList;
130: }
131:
132: /** @return the footer element list */
133: protected LinkedList getFooterElements() {
134: return this .footerList;
135: }
136:
137: /** @see org.apache.fop.layoutmgr.LayoutManager */
138: public LinkedList getNextKnuthElements(LayoutContext context,
139: int alignment) {
140: log.debug("==> Columns: " + getTableLM().getColumns());
141: KnuthBox headerAsFirst = null;
142: KnuthBox headerAsSecondToLast = null;
143: KnuthBox footerAsLast = null;
144: if (headerIter != null && headerList == null) {
145: this .headerList = getKnuthElementsForRowIterator(
146: headerIter, context, alignment,
147: TableRowIterator.HEADER);
148: ElementListUtils.removeLegalBreaks(this .headerList);
149: this .headerNetHeight = ElementListUtils
150: .calcContentLength(this .headerList);
151: if (log.isDebugEnabled()) {
152: log.debug("==> Header: " + headerNetHeight + " - "
153: + this .headerList);
154: }
155: TableHeaderFooterPosition pos = new TableHeaderFooterPosition(
156: getTableLM(), true, this .headerList);
157: KnuthBox box = new KnuthBox(headerNetHeight, pos, false);
158: if (getTableLM().getTable().omitHeaderAtBreak()) {
159: //We can simply add the table header at the start
160: //of the whole list
161: headerAsFirst = box;
162: } else {
163: headerAsSecondToLast = box;
164: }
165: }
166: if (footerIter != null && footerList == null) {
167: this .footerList = getKnuthElementsForRowIterator(
168: footerIter, context, alignment,
169: TableRowIterator.FOOTER);
170: ElementListUtils.removeLegalBreaks(this .footerList);
171: this .footerNetHeight = ElementListUtils
172: .calcContentLength(this .footerList);
173: if (log.isDebugEnabled()) {
174: log.debug("==> Footer: " + footerNetHeight + " - "
175: + this .footerList);
176: }
177: //We can simply add the table footer at the end of the whole list
178: TableHeaderFooterPosition pos = new TableHeaderFooterPosition(
179: getTableLM(), false, this .footerList);
180: KnuthBox box = new KnuthBox(footerNetHeight, pos, false);
181: footerAsLast = box;
182: }
183: LinkedList returnList = getKnuthElementsForRowIterator(
184: bodyIter, context, alignment, TableRowIterator.BODY);
185: if (headerAsFirst != null) {
186: int insertionPoint = 0;
187: if (returnList.size() > 0
188: && ((ListElement) returnList.getFirst())
189: .isForcedBreak()) {
190: insertionPoint++;
191: }
192: returnList.add(insertionPoint, headerAsFirst);
193: } else if (headerAsSecondToLast != null) {
194: int insertionPoint = returnList.size();
195: if (returnList.size() > 0
196: && ((ListElement) returnList.getLast())
197: .isForcedBreak()) {
198: insertionPoint--;
199: }
200: returnList.add(insertionPoint, headerAsSecondToLast);
201: }
202: if (footerAsLast != null) {
203: int insertionPoint = returnList.size();
204: if (returnList.size() > 0
205: && ((ListElement) returnList.getLast())
206: .isForcedBreak()) {
207: insertionPoint--;
208: }
209: returnList.add(insertionPoint, footerAsLast);
210: }
211: return returnList;
212: }
213:
214: /**
215: * Creates Knuth elements by iterating over a TableRowIterator.
216: * @param iter TableRowIterator instance to fetch rows from
217: * @param context Active LayoutContext
218: * @param alignment alignment indicator
219: * @param bodyType Indicates what kind of body is being processed
220: * (BODY, HEADER or FOOTER)
221: * @return An element list
222: */
223: private LinkedList getKnuthElementsForRowIterator(
224: TableRowIterator iter, LayoutContext context,
225: int alignment, int bodyType) {
226: LinkedList returnList = new LinkedList();
227: EffRow[] rowGroup = null;
228: while ((rowGroup = iter.getNextRowGroup()) != null) {
229: //Check for break-before on the table-row at the start of the row group
230: TableRow rowFO = rowGroup[0].getTableRow();
231: if (rowFO != null
232: && rowFO.getBreakBefore() != Constants.EN_AUTO) {
233: log.info("break-before found");
234: if (returnList.size() > 0) {
235: ListElement last = (ListElement) returnList
236: .getLast();
237: if (last.isPenalty()) {
238: KnuthPenalty pen = (KnuthPenalty) last;
239: pen.setP(-KnuthPenalty.INFINITE);
240: pen.setBreakClass(rowFO.getBreakBefore());
241: } else {//if (last instanceof BreakElement) { // TODO vh: seems the only possibility
242: BreakElement breakPoss = (BreakElement) last;
243: breakPoss
244: .setPenaltyValue(-KnuthPenalty.INFINITE);
245: breakPoss.setBreakClass(rowFO.getBreakBefore());
246: }
247: } else {
248: returnList.add(new BreakElement(new Position(
249: getTableLM()), 0, -KnuthPenalty.INFINITE,
250: rowFO.getBreakBefore(), context));
251: }
252: }
253:
254: //Border resolution
255: if (!isSeparateBorderModel()) {
256: resolveNormalBeforeAfterBordersForRowGroup(rowGroup,
257: iter);
258: }
259:
260: //Reset keep-with-next when remaining inside the table.
261: //The context flag is only used to propagate keep-with-next to the outside.
262: //The clearing is ok here because createElementsForRowGroup already handles
263: //the keep when inside a table.
264: context.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING,
265: false);
266:
267: //Element list creation
268: createElementsForRowGroup(context, alignment, bodyType,
269: returnList, rowGroup);
270:
271: //Handle keeps
272: if (context.isKeepWithNextPending()) {
273: log
274: .debug("child LM (row group) signals pending keep-with-next");
275: }
276: if (context.isKeepWithPreviousPending()) {
277: log
278: .debug("child LM (row group) signals pending keep-with-previous");
279: if (returnList.size() > 0) {
280: //Modify last penalty
281: ListElement last = (ListElement) returnList
282: .getLast();
283: if (last.isPenalty()) {
284: BreakElement breakPoss = (BreakElement) last;
285: //Only honor keep if there's no forced break
286: if (!breakPoss.isForcedBreak()) {
287: breakPoss
288: .setPenaltyValue(KnuthPenalty.INFINITE);
289: }
290: }
291: }
292: }
293:
294: //Check for break-after on the table-row at the end of the row group
295: rowFO = rowGroup[rowGroup.length - 1].getTableRow();
296: if (rowFO != null
297: && rowFO.getBreakAfter() != Constants.EN_AUTO) {
298: if (returnList.size() > 0) {
299: ListElement last = (ListElement) returnList
300: .getLast();
301: if (last instanceof KnuthPenalty) {
302: KnuthPenalty pen = (KnuthPenalty) last;
303: pen.setP(-KnuthPenalty.INFINITE);
304: pen.setBreakClass(rowFO.getBreakAfter());
305: } else if (last instanceof BreakElement) {
306: BreakElement breakPoss = (BreakElement) last;
307: breakPoss
308: .setPenaltyValue(-KnuthPenalty.INFINITE);
309: breakPoss.setBreakClass(rowFO.getBreakAfter());
310: }
311: }
312: }
313: }
314:
315: if (returnList.size() > 0) {
316: //Remove the last penalty produced by the combining algorithm (see TableStepper), for the last step
317: ListElement last = (ListElement) returnList.getLast();
318: if (last.isPenalty() || last instanceof BreakElement) {
319: if (!last.isForcedBreak()) {
320: //Only remove if we don't signal a forced break
321: returnList.removeLast();
322: }
323: }
324: }
325:
326: //fox:widow-content-limit
327: int widowContentLimit = getTableLM().getTable()
328: .getWidowContentLimit().getValue();
329: if (widowContentLimit != 0 && bodyType == TableRowIterator.BODY) {
330: ElementListUtils.removeLegalBreaks(returnList,
331: widowContentLimit);
332: }
333: //fox:orphan-content-limit
334: int orphanContentLimit = getTableLM().getTable()
335: .getOrphanContentLimit().getValue();
336: if (orphanContentLimit != 0
337: && bodyType == TableRowIterator.BODY) {
338: ElementListUtils.removeLegalBreaksFromEnd(returnList,
339: orphanContentLimit);
340: }
341:
342: return returnList;
343: }
344:
345: /**
346: * Resolves normal borders for a row group.
347: * @param iter Table row iterator to operate on
348: */
349: private void resolveNormalBeforeAfterBordersForRowGroup(
350: EffRow[] rowGroup, TableRowIterator iter) {
351: for (int rgi = 0; rgi < rowGroup.length; rgi++) {
352: EffRow row = rowGroup[rgi];
353: EffRow prevRow = iter.getPrecedingRow(row);
354: EffRow nextRow = iter.getFollowingRow(row);
355: if ((prevRow == null) && (iter == this .bodyIter)
356: && (this .headerIter != null)) {
357: prevRow = this .headerIter.getLastRow();
358: }
359: if ((nextRow == null) && (iter == this .headerIter)) {
360: nextRow = this .bodyIter.getFirstRow();
361: }
362: if ((nextRow == null) && (iter == this .bodyIter)
363: && (this .footerIter != null)) {
364: nextRow = this .footerIter.getFirstRow();
365: }
366: if ((prevRow == null) && (iter == this .footerIter)) {
367: //TODO This could be bad for memory consumption because it already causes the
368: //whole body iterator to be prefetched!
369: prevRow = this .bodyIter.getLastRow();
370: }
371: log.debug("prevRow-row-nextRow: " + prevRow + " - " + row
372: + " - " + nextRow);
373:
374: //Determine the grid units necessary for getting all the borders right
375: int guCount = row.getGridUnits().size();
376: if (prevRow != null) {
377: guCount = Math.max(guCount, prevRow.getGridUnits()
378: .size());
379: }
380: if (nextRow != null) {
381: guCount = Math.max(guCount, nextRow.getGridUnits()
382: .size());
383: }
384: GridUnit gu = row.getGridUnit(0);
385: //Create empty grid units to hold resolved borders of neighbouring cells
386: //TODO maybe this needs to be done differently (and sooner)
387: for (int i = 0; i < guCount - row.getGridUnits().size(); i++) {
388: //TODO This block is untested!
389: int pos = row.getGridUnits().size() + i;
390: row.getGridUnits().add(
391: new EmptyGridUnit(gu.getRow(), this .tableLM
392: .getColumns().getColumn(pos + 1), gu
393: .getBody(), pos));
394: }
395:
396: //Now resolve normal borders
397: if (getTableLM().getTable().isSeparateBorderModel()) {
398: //nop, borders are already assigned at this point
399: } else {
400: for (int i = 0; i < row.getGridUnits().size(); i++) {
401: gu = row.getGridUnit(i);
402: GridUnit other;
403: int flags = 0;
404: if (prevRow != null
405: && i < prevRow.getGridUnits().size()) {
406: other = prevRow.getGridUnit(i);
407: } else {
408: other = null;
409: }
410: if (other == null || other.isEmpty()
411: || gu.isEmpty()
412: || gu.getPrimary() != other.getPrimary()) {
413: if ((iter == this .bodyIter)
414: && gu.getFlag(GridUnit.FIRST_IN_TABLE)
415: && (this .headerIter == null)) {
416: flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
417: }
418: if ((iter == this .headerIter)
419: && gu.getFlag(GridUnit.FIRST_IN_TABLE)) {
420: flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
421: }
422: gu.resolveBorder(other,
423: CommonBorderPaddingBackground.BEFORE,
424: flags);
425: }
426:
427: flags = 0;
428: if (nextRow != null
429: && i < nextRow.getGridUnits().size()) {
430: other = nextRow.getGridUnit(i);
431: } else {
432: other = null;
433: }
434: if (other == null || other.isEmpty()
435: || gu.isEmpty()
436: || gu.getPrimary() != other.getPrimary()) {
437: if ((iter == this .bodyIter)
438: && gu.getFlag(GridUnit.LAST_IN_TABLE)
439: && (this .footerIter == null)) {
440: flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
441: }
442: if ((iter == this .footerIter)
443: && gu.getFlag(GridUnit.LAST_IN_TABLE)) {
444: flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
445: }
446: gu.resolveBorder(other,
447: CommonBorderPaddingBackground.AFTER,
448: flags);
449: }
450: }
451: }
452: }
453: }
454:
455: /**
456: * Creates Knuth elements for a row group (see TableRowIterator.getNextRowGroup()).
457: * @param context Active LayoutContext
458: * @param alignment alignment indicator
459: * @param bodyType Indicates what kind of body is being processed (BODY, HEADER or FOOTER)
460: * @param returnList List to received the generated elements
461: * @param rowGroup row group to process
462: */
463: private void createElementsForRowGroup(LayoutContext context,
464: int alignment, int bodyType, LinkedList returnList,
465: EffRow[] rowGroup) {
466: log.debug("Handling row group with " + rowGroup.length
467: + " rows...");
468: MinOptMax[] rowHeights = new MinOptMax[rowGroup.length];
469: MinOptMax[] explicitRowHeights = new MinOptMax[rowGroup.length];
470: EffRow row;
471: int maxColumnCount = 0;
472: List pgus = new java.util.ArrayList(); //holds a list of a row's primary grid units
473: for (int rgi = 0; rgi < rowGroup.length; rgi++) {
474: row = rowGroup[rgi];
475: rowHeights[rgi] = new MinOptMax(0, 0, Integer.MAX_VALUE);
476: explicitRowHeights[rgi] = new MinOptMax(0, 0,
477: Integer.MAX_VALUE);
478:
479: pgus.clear();
480: TableRow tableRow = null;
481: // The row's minimum content height; 0 if the row's height is auto, otherwise
482: // the .minimum component of the explicitely specified value
483: int minContentHeight = 0;
484: int maxCellHeight = 0;
485: int effRowContentHeight = 0;
486: for (int j = 0; j < row.getGridUnits().size(); j++) {
487: // assert maxColumnCount == 0 || maxColumnCount == row.getGridUnits().size(); // TODO vh
488: maxColumnCount = Math.max(maxColumnCount, row
489: .getGridUnits().size());
490: GridUnit gu = row.getGridUnit(j);
491: if ((gu.isPrimary() || (gu.getColSpanIndex() == 0 && gu
492: .isLastGridUnitRowSpan()))
493: && !gu.isEmpty()) {
494: PrimaryGridUnit primary = gu.getPrimary();
495:
496: if (gu.isPrimary()) {
497: primary.getCellLM().setParent(getTableLM());
498:
499: //Determine the table-row if any
500: if (tableRow == null
501: && primary.getRow() != null) {
502: tableRow = primary.getRow();
503:
504: //Check for bpd on row, see CSS21, 17.5.3 Table height algorithms
505: LengthRangeProperty bpd = tableRow
506: .getBlockProgressionDimension();
507: if (!bpd.getMinimum(getTableLM()).isAuto()) {
508: minContentHeight = Math.max(
509: minContentHeight, bpd
510: .getMinimum(
511: getTableLM())
512: .getLength().getValue(
513: getTableLM()));
514: }
515: MinOptMaxUtil.restrict(
516: explicitRowHeights[rgi], bpd,
517: getTableLM());
518:
519: }
520:
521: //Calculate width of cell
522: int spanWidth = 0;
523: for (int i = primary.getStartCol(); i < primary
524: .getStartCol()
525: + primary.getCell()
526: .getNumberColumnsSpanned(); i++) {
527: if (getTableLM().getColumns().getColumn(
528: i + 1) != null) {
529: spanWidth += getTableLM().getColumns()
530: .getColumn(i + 1)
531: .getColumnWidth().getValue(
532: getTableLM());
533: }
534: }
535: LayoutContext childLC = new LayoutContext(0);
536: childLC.setStackLimit(context.getStackLimit()); //necessary?
537: childLC.setRefIPD(spanWidth);
538:
539: //Get the element list for the cell contents
540: LinkedList elems = primary.getCellLM()
541: .getNextKnuthElements(childLC,
542: alignment);
543: //Temporary? Multiple calls in case of break conditions.
544: //TODO Revisit when table layout is restartable
545: while (!primary.getCellLM().isFinished()) {
546: LinkedList additionalElems = primary
547: .getCellLM().getNextKnuthElements(
548: childLC, alignment);
549: elems.addAll(additionalElems);
550: }
551: ElementListObserver
552: .observe(elems, "table-cell", primary
553: .getCell().getId());
554:
555: if ((elems.size() > 0)
556: && ((KnuthElement) elems.getLast())
557: .isForcedBreak()) {
558: // a descendant of this block has break-after
559: log
560: .debug("Descendant of table-cell signals break: "
561: + primary.getCellLM()
562: .isFinished());
563: }
564:
565: primary.setElements(elems);
566:
567: if (childLC.isKeepWithNextPending()) {
568: log
569: .debug("child LM signals pending keep-with-next");
570: primary.setFlag(
571: GridUnit.KEEP_WITH_NEXT_PENDING,
572: true);
573: }
574: if (childLC.isKeepWithPreviousPending()) {
575: log
576: .debug("child LM signals pending keep-with-previous");
577: primary
578: .setFlag(
579: GridUnit.KEEP_WITH_PREVIOUS_PENDING,
580: true);
581: }
582: }
583:
584: //Calculate height of cell contents
585: primary.setContentLength(ElementListUtils
586: .calcContentLength(primary.getElements()));
587: maxCellHeight = Math.max(maxCellHeight, primary
588: .getContentLength());
589:
590: //Calculate height of row, see CSS21, 17.5.3 Table height algorithms
591: if (gu.isLastGridUnitRowSpan()) {
592: int effCellContentHeight = minContentHeight;
593: LengthRangeProperty bpd = primary.getCell()
594: .getBlockProgressionDimension();
595: if (!bpd.getMinimum(getTableLM()).isAuto()) {
596: effCellContentHeight = Math.max(
597: effCellContentHeight, bpd
598: .getMinimum(getTableLM())
599: .getLength().getValue(
600: getTableLM()));
601: }
602: if (!bpd.getOptimum(getTableLM()).isAuto()) {
603: effCellContentHeight = Math.max(
604: effCellContentHeight, bpd
605: .getOptimum(getTableLM())
606: .getLength().getValue(
607: getTableLM()));
608: }
609: if (gu.getRowSpanIndex() == 0) {
610: //TODO ATM only non-row-spanned cells are taken for this
611: MinOptMaxUtil.restrict(
612: explicitRowHeights[rgi], bpd,
613: tableLM);
614: }
615: effCellContentHeight = Math.max(
616: effCellContentHeight, primary
617: .getContentLength());
618:
619: int borderWidths;
620: if (isSeparateBorderModel()) {
621: borderWidths = primary.getBorders()
622: .getBorderBeforeWidth(false)
623: + primary.getBorders()
624: .getBorderAfterWidth(false);
625: } else {
626: borderWidths = primary
627: .getHalfMaxBorderWidth();
628: }
629: int padding = 0;
630: effRowContentHeight = Math.max(
631: effRowContentHeight,
632: effCellContentHeight);
633: CommonBorderPaddingBackground cbpb = primary
634: .getCell()
635: .getCommonBorderPaddingBackground();
636: padding += cbpb.getPaddingBefore(false, primary
637: .getCellLM());
638: padding += cbpb.getPaddingAfter(false, primary
639: .getCellLM());
640: int effRowHeight = effCellContentHeight
641: + padding
642: + borderWidths
643: + 2
644: * getTableLM()
645: .getHalfBorderSeparationBPD();
646: for (int previous = 0; previous < gu
647: .getRowSpanIndex(); previous++) {
648: effRowHeight -= rowHeights[rgi - previous
649: - 1].opt;
650: }
651: if (effRowHeight > rowHeights[rgi].min) {
652: //This is the new height of the (grid) row
653: MinOptMaxUtil.extendMinimum(
654: rowHeights[rgi], effRowHeight,
655: false);
656: }
657: }
658:
659: if (gu.isPrimary()) {
660: pgus.add(primary);
661: }
662: }
663: }
664:
665: row.setHeight(rowHeights[rgi]);
666: row.setExplicitHeight(explicitRowHeights[rgi]);
667: if (effRowContentHeight > row.getExplicitHeight().max) {
668: log
669: .warn(FONode
670: .decorateWithContextInfo(
671: "The contents of row "
672: + (row.getIndex() + 1)
673: + " are taller than they should be (there is a"
674: + " block-progression-dimension or height constraint on the indicated row)."
675: + " Due to its contents the row grows"
676: + " to "
677: + effRowContentHeight
678: + " millipoints, but the row shouldn't get"
679: + " any taller than "
680: + row
681: .getExplicitHeight()
682: + " millipoints.", row
683: .getTableRow()));
684: }
685: }
686: if (log.isDebugEnabled()) {
687: log.debug("rowGroup:");
688: for (int i = 0; i < rowHeights.length; i++) {
689: log.debug(" height=" + rowHeights[i] + " explicit="
690: + explicitRowHeights[i]);
691: }
692: }
693: LinkedList returnedList = this .stepper
694: .getCombinedKnuthElementsForRowGroup(context, rowGroup,
695: maxColumnCount, bodyType);
696: if (returnedList != null) {
697: returnList.addAll(returnedList);
698: }
699:
700: }
701:
702: /**
703: * Retuns the X offset of the given grid unit.
704: * @param gu the grid unit
705: * @return the requested X offset
706: */
707: protected int getXOffsetOfGridUnit(GridUnit gu) {
708: int col = gu.getStartCol();
709: return startXOffset
710: + getTableLM().getColumns().getXOffset(col + 1,
711: getTableLM());
712: }
713:
714: /**
715: * Adds the areas generated by this layout manager to the area tree.
716: * @param parentIter the position iterator
717: * @param layoutContext the layout context for adding areas
718: */
719: public void addAreas(PositionIterator parentIter,
720: LayoutContext layoutContext) {
721: this .usedBPD = 0;
722: RowPainter painter = new RowPainter(this , layoutContext);
723:
724: List positions = new java.util.ArrayList();
725: List headerElements = null;
726: List footerElements = null;
727: Position firstPos = null;
728: Position lastPos = null;
729: Position lastCheckPos = null;
730: while (parentIter.hasNext()) {
731: Position pos = (Position) parentIter.next();
732: if (pos instanceof SpaceHandlingBreakPosition) {
733: //This position has only been needed before addAreas was called, now we need the
734: //original one created by the layout manager.
735: pos = ((SpaceHandlingBreakPosition) pos)
736: .getOriginalBreakPosition();
737: }
738: if (pos == null) {
739: continue;
740: }
741: if (firstPos == null) {
742: firstPos = pos;
743: }
744: lastPos = pos;
745: if (pos.getIndex() >= 0) {
746: lastCheckPos = pos;
747: }
748: if (pos instanceof TableHeaderFooterPosition) {
749: TableHeaderFooterPosition thfpos = (TableHeaderFooterPosition) pos;
750: //these positions need to be unpacked
751: if (thfpos.header) {
752: //Positions for header will be added first
753: headerElements = thfpos.nestedElements;
754: } else {
755: //Positions for footers are simply added at the end
756: footerElements = thfpos.nestedElements;
757: }
758: } else if (pos instanceof TableHFPenaltyPosition) {
759: //ignore for now, see special handling below if break is at a penalty
760: //Only if the last position in this part/page us such a position it will be used
761: } else {
762: //leave order as is for the rest
763: positions.add(pos);
764: }
765: }
766: if (lastPos instanceof TableHFPenaltyPosition) {
767: TableHFPenaltyPosition penaltyPos = (TableHFPenaltyPosition) lastPos;
768: log.debug("Break at penalty!");
769: if (penaltyPos.headerElements != null) {
770: //Header positions for the penalty position are in the last element and need to
771: //be handled first before all other TableContentPositions
772: headerElements = penaltyPos.headerElements;
773: }
774: if (penaltyPos.footerElements != null) {
775: footerElements = penaltyPos.footerElements;
776: }
777: }
778:
779: Map markers = getTableLM().getTable().getMarkers();
780: if (markers != null) {
781: getTableLM().getCurrentPV().addMarkers(markers, true,
782: getTableLM().isFirst(firstPos),
783: getTableLM().isLast(lastCheckPos));
784: }
785:
786: if (headerElements != null) {
787: //header positions for the last part are the second-to-last element and need to
788: //be handled first before all other TableContentPositions
789: PositionIterator nestedIter = new KnuthPossPosIter(
790: headerElements);
791: iterateAndPaintPositions(nestedIter, painter);
792: }
793:
794: //Iterate over all steps
795: Iterator posIter = positions.iterator();
796: iterateAndPaintPositions(posIter, painter);
797:
798: if (footerElements != null) {
799: //Positions for footers are simply added at the end
800: PositionIterator nestedIter = new KnuthPossPosIter(
801: footerElements);
802: iterateAndPaintPositions(nestedIter, painter);
803: }
804:
805: this .usedBPD += painter.getAccumulatedBPD();
806:
807: if (markers != null) {
808: getTableLM().getCurrentPV().addMarkers(markers, false,
809: getTableLM().isFirst(firstPos),
810: getTableLM().isLast(lastCheckPos));
811: }
812: }
813:
814: /**
815: * Iterates over a part of the table (header, footer, body) and paints the related
816: * elements.
817: *
818: * @param iterator iterator over Position elements. Those positions correspond to the
819: * elements of the table present on the current page
820: * @param painter
821: */
822: private void iterateAndPaintPositions(Iterator iterator,
823: RowPainter painter) {
824: List lst = new java.util.ArrayList();
825: boolean firstPos = false;
826: TableBody body = null;
827: while (iterator.hasNext()) {
828: Position pos = (Position) iterator.next();
829: if (pos instanceof TableContentPosition) {
830: TableContentPosition tcpos = (TableContentPosition) pos;
831: lst.add(tcpos);
832: GridUnitPart part = (GridUnitPart) tcpos.gridUnitParts
833: .get(0);
834: if (body == null) {
835: body = part.pgu.getBody();
836: }
837: if (tcpos
838: .getFlag(TableContentPosition.FIRST_IN_ROWGROUP)
839: && tcpos.row.getFlag(EffRow.FIRST_IN_PART)) {
840: firstPos = true;
841:
842: }
843: if (tcpos
844: .getFlag(TableContentPosition.LAST_IN_ROWGROUP)
845: && tcpos.row.getFlag(EffRow.LAST_IN_PART)) {
846: log.trace("LAST_IN_ROWGROUP + LAST_IN_PART");
847: handleMarkersAndPositions(lst, body, firstPos,
848: true, painter);
849: //reset
850: firstPos = false;
851: body = null;
852: lst.clear();
853: }
854: } else {
855: if (log.isDebugEnabled()) {
856: log.debug("Ignoring position: " + pos);
857: }
858: }
859: }
860: if (body != null) {
861: // Entering this block means that the end of the current table-part hasn't
862: // been reached (otherwise it would have been caught by the test above). So
863: // lastPos is necessarily false
864: handleMarkersAndPositions(lst, body, firstPos, false,
865: painter);
866: }
867: painter.addAreasAndFlushRow(true);
868: }
869:
870: private void handleMarkersAndPositions(List positions,
871: TableBody body, boolean firstPos, boolean lastPos,
872: RowPainter painter) {
873: getTableLM().getCurrentPV().addMarkers(body.getMarkers(), true,
874: firstPos, lastPos);
875: int size = positions.size();
876: for (int i = 0; i < size; i++) {
877: painter
878: .handleTableContentPosition((TableContentPosition) positions
879: .get(i));
880: }
881: getTableLM().getCurrentPV().addMarkers(body.getMarkers(),
882: false, firstPos, lastPos);
883: }
884:
885: /**
886: * Get the area for a row for background.
887: * @param row the table-row object or null
888: * @return the row area or null if there's no background to paint
889: */
890: public Block getRowArea(TableRow row) {
891: if (row == null
892: || !row.getCommonBorderPaddingBackground()
893: .hasBackground()) {
894: return null;
895: } else {
896: Block block = new Block();
897: block.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE);
898: block.setPositioning(Block.ABSOLUTE);
899: return block;
900: }
901: }
902:
903: /**
904: * Adds the area for the row background if any.
905: * @param row row for which to generate the background
906: * @param bpd block-progression-dimension of the row
907: * @param ipd inline-progression-dimension of the row
908: * @param yoffset Y offset at which to paint
909: */
910: public void addRowBackgroundArea(TableRow row, int bpd, int ipd,
911: int yoffset) {
912: //Add row background if any
913: Block rowBackground = getRowArea(row);
914: if (rowBackground != null) {
915: rowBackground.setBPD(bpd);
916: rowBackground.setIPD(ipd);
917: rowBackground.setXOffset(this .startXOffset);
918: rowBackground.setYOffset(yoffset);
919: getTableLM().addChildArea(rowBackground);
920: TraitSetter.addBackground(rowBackground, row
921: .getCommonBorderPaddingBackground(), getTableLM());
922: }
923: }
924:
925: /**
926: * Sets the overall starting x-offset. Used for proper placement of cells.
927: * @param startXOffset starting x-offset (table's start-indent)
928: */
929: public void setStartXOffset(int startXOffset) {
930: this .startXOffset = startXOffset;
931: }
932:
933: /**
934: * @return the amount of block-progression-dimension used by the content
935: */
936: public int getUsedBPD() {
937: return this .usedBPD;
938: }
939:
940: // --------- Property Resolution related functions --------- //
941:
942: /**
943: * @see org.apache.fop.datatypes.PercentBaseContext#getBaseLength(int, FObj)
944: */
945: public int getBaseLength(int lengthBase, FObj fobj) {
946: return tableLM.getBaseLength(lengthBase, fobj);
947: }
948:
949: }
|