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: RowPainter.java 555651 2007-07-12 14:59:06Z vhennebert $ */
019:
020: package org.apache.fop.layoutmgr.table;
021:
022: import java.util.Arrays;
023: import java.util.Iterator;
024: import java.util.Map;
025:
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028: import org.apache.fop.fo.flow.TableRow;
029: import org.apache.fop.fo.properties.LengthRangeProperty;
030: import org.apache.fop.layoutmgr.ElementListUtils;
031: import org.apache.fop.layoutmgr.KnuthElement;
032: import org.apache.fop.layoutmgr.KnuthPossPosIter;
033: import org.apache.fop.layoutmgr.LayoutContext;
034: import org.apache.fop.layoutmgr.SpaceResolver;
035:
036: class RowPainter {
037: private static Log log = LogFactory.getLog(RowPainter.class);
038: /** The fo:table-row containing the currently handled grid rows. */
039: private TableRow rowFO = null;
040: private int colCount;
041: private int yoffset = 0;
042: private int accumulatedBPD = 0;
043: /** Currently handled row (= last encountered row). */
044: private EffRow lastRow = null;
045: private LayoutContext layoutContext;
046: /**
047: * For each part of the table (header, footer, body), index of the first row of that
048: * part present on the current page.
049: */
050: private int[] firstRow = new int[3];
051: /**
052: * Keeps track of the y-offsets of each row on a page (for body, header and footer separately).
053: * This is particularly needed for spanned cells where you need to know the y-offset
054: * of the starting row when the area is generated at the time the cell is closed.
055: */
056: private Map[] rowOffsets = new Map[] { new java.util.HashMap(),
057: new java.util.HashMap(), new java.util.HashMap() };
058:
059: //These three variables are our buffer to recombine the individual steps into cells
060: /** Primary grid units corresponding to the currently handled grid units, per row. */
061: private PrimaryGridUnit[] primaryGridUnits;
062: /**
063: * Index, in the corresponding table cell's list of Knuth elements, of the first
064: * element present on the current page, per column.
065: */
066: private int[] start;
067: /**
068: * Index, in the corresponding table cell's list of Knuth elements, of the last
069: * element present on the current page, per column.
070: */
071: private int[] end;
072: /**
073: * Length, for each column, of the elements from the current cell put on the
074: * current page. This is the corresponding area's bpd.
075: */
076: private int[] partBPD;
077: private TableContentLayoutManager tclm;
078:
079: public RowPainter(TableContentLayoutManager tclm,
080: LayoutContext layoutContext) {
081: this .tclm = tclm;
082: this .layoutContext = layoutContext;
083: this .colCount = tclm.getColumns().getColumnCount();
084: this .primaryGridUnits = new PrimaryGridUnit[colCount];
085: this .start = new int[colCount];
086: this .end = new int[colCount];
087: this .partBPD = new int[colCount];
088: Arrays.fill(firstRow, -1);
089: Arrays.fill(end, -1);
090: }
091:
092: public int getAccumulatedBPD() {
093: return this .accumulatedBPD;
094: }
095:
096: /**
097: * Records the fragment of row represented by the given position. If it belongs to
098: * another (grid) row than the current one, that latter is painted and flushed first.
099: *
100: * @param tcpos a position representing the row fragment
101: */
102: public void handleTableContentPosition(TableContentPosition tcpos) {
103: if (lastRow != tcpos.row && lastRow != null) {
104: addAreasAndFlushRow(false);
105: }
106: if (log.isDebugEnabled()) {
107: log.debug("===handleTableContentPosition(" + tcpos);
108: }
109: rowFO = tcpos.row.getTableRow();
110: lastRow = tcpos.row;
111: Iterator partIter = tcpos.gridUnitParts.iterator();
112: //Iterate over all grid units in the current step
113: while (partIter.hasNext()) {
114: GridUnitPart gup = (GridUnitPart) partIter.next();
115: if (log.isDebugEnabled()) {
116: log.debug(">" + gup);
117: }
118: int colIndex = gup.pgu.getStartCol();
119: if (primaryGridUnits[colIndex] != gup.pgu) {
120: if (primaryGridUnits[colIndex] != null) {
121: log.warn("Replacing GU in slot " + colIndex
122: + ". Some content may not be painted.");
123: }
124: primaryGridUnits[colIndex] = gup.pgu;
125: start[colIndex] = gup.start;
126: end[colIndex] = gup.end;
127: } else {
128: if (gup.end < end[colIndex]) {
129: throw new IllegalStateException(
130: "Internal Error: stepper problem");
131: }
132: end[colIndex] = gup.end;
133: }
134: }
135: }
136:
137: /**
138: * Create the areas corresponding to the last row. This method is called either
139: * because the row is finished (all of the elements present on this row have been
140: * added), or because this is the last row on the current page, and the part of it
141: * lying on the current page must be drawn.
142: *
143: * @param forcedFlush true if the elements must be drawn even if the row isn't
144: * finished yet (last row on the page), or if the row is the last of the current table
145: * part
146: * @return the height of the (grid) row
147: */
148: public int addAreasAndFlushRow(boolean forcedFlush) {
149: int actualRowHeight = 0;
150:
151: int bt = lastRow.getBodyType();
152: if (log.isDebugEnabled()) {
153: log.debug("Remembering yoffset for row "
154: + lastRow.getIndex() + ": " + yoffset);
155: }
156: rowOffsets[bt].put(new Integer(lastRow.getIndex()),
157: new Integer(yoffset));
158:
159: for (int i = 0; i < primaryGridUnits.length; i++) {
160: if ((primaryGridUnits[i] != null)
161: && (forcedFlush || (end[i] == primaryGridUnits[i]
162: .getElements().size() - 1))) {
163: actualRowHeight = Math.max(actualRowHeight,
164: computeSpanHeight(primaryGridUnits[i],
165: start[i], end[i], i, bt));
166: }
167: }
168: actualRowHeight += 2 * tclm.getTableLM()
169: .getHalfBorderSeparationBPD();
170:
171: //Add areas for row
172: tclm.addRowBackgroundArea(rowFO, actualRowHeight, layoutContext
173: .getRefIPD(), yoffset);
174: for (int i = 0; i < primaryGridUnits.length; i++) {
175: GridUnit currentGU = lastRow.safelyGetGridUnit(i);
176: //currentGU can be null if there's no grid unit
177: //at this place in the current row (empty cell and no borders to process)
178:
179: if (primaryGridUnits[i] != null) {
180: if (forcedFlush
181: || ((end[i] == primaryGridUnits[i]
182: .getElements().size() - 1) && (currentGU == null || currentGU
183: .isLastGridUnitRowSpan()))) {
184: //the last line in the "if" above is to avoid a premature end of a
185: //row-spanned cell because no GridUnitParts are generated after a cell is
186: //finished with its content.
187: //See table-cell_number-rows-spanned_bug38397.xml
188: addAreasForCell(primaryGridUnits[i], start[i],
189: end[i], lastRow, partBPD[i],
190: actualRowHeight);
191: primaryGridUnits[i] = null;
192: start[i] = 0;
193: end[i] = -1;
194: partBPD[i] = 0;
195: }
196: } else if (currentGU != null
197: && !currentGU.isEmpty()
198: && currentGU.getColSpanIndex() == 0
199: && (forcedFlush || currentGU
200: .isLastGridUnitRowSpan())) {
201: //A row-spanned cell has finished contributing content on the previous page
202: //and now still has to cause grid units to be painted.
203: //See table-cell_page-break_span.xml
204: addAreasForCell(currentGU.getPrimary(), start[i],
205: end[i], lastRow, partBPD[i], actualRowHeight);
206: start[i] = 0;
207: end[i] = -1;
208: partBPD[i] = 0;
209: }
210: }
211: yoffset += actualRowHeight;
212: accumulatedBPD += actualRowHeight;
213: if (forcedFlush) {
214: // Either the end of the page is reached, then this was the last call of this
215: // method and we no longer care about lastRow; or the end of a table-part
216: // (header, footer, body) has been reached, and the next row will anyway be
217: // different from the current one, and this is unnecessary to recall this
218: // method in the first lines of handleTableContentPosition, so we may reset
219: // lastRow
220: lastRow = null;
221: }
222: return actualRowHeight;
223: }
224:
225: /**
226: * Computes the total height of the part of the given cell spanning on the current
227: * active row, including borders and paddings. The bpd is also stored in partBPD, and
228: * it is ensured that the cell's or row's explicit height is respected. yoffset is
229: * updated accordingly.
230: *
231: * @param pgu primary grid unit corresponding to the cell
232: * @param start index of the first element of the cell occuring on the current page
233: * @param end index of the last element of the cell occuring on the current page
234: * @param columnIndex column index of the cell
235: * @param bodyType {@link TableRowIterator#HEADER}, {@link TableRowIterator#FOOTER}, or
236: * {@link TableRowIterator#BODY}
237: * @return the cell's height
238: */
239: private int computeSpanHeight(PrimaryGridUnit pgu, int start,
240: int end, int columnIndex, int bodyType) {
241: if (log.isTraceEnabled()) {
242: log.trace("getting len for " + columnIndex + " " + start
243: + "-" + end);
244: }
245: int actualStart = start;
246: // Skip from the content length calculation glues and penalties occuring at the
247: // beginning of the page
248: while (actualStart <= end
249: && !((KnuthElement) pgu.getElements().get(actualStart))
250: .isBox()) {
251: actualStart++;
252: }
253: int len = ElementListUtils.calcContentLength(pgu.getElements(),
254: actualStart, end);
255: KnuthElement el = (KnuthElement) pgu.getElements().get(end);
256: if (el.isPenalty()) {
257: len += el.getW();
258: }
259: partBPD[columnIndex] = len;
260: if (log.isTraceEnabled()) {
261: log.trace("len of part: " + len);
262: }
263:
264: if (start == 0) {
265: LengthRangeProperty bpd = pgu.getCell()
266: .getBlockProgressionDimension();
267: if (!bpd.getMinimum(tclm.getTableLM()).isAuto()) {
268: int min = bpd.getMinimum(tclm.getTableLM()).getLength()
269: .getValue(tclm.getTableLM());
270: if (min > 0) {
271: len = Math.max(len, min);
272: }
273: }
274: if (!bpd.getOptimum(tclm.getTableLM()).isAuto()) {
275: int opt = bpd.getOptimum(tclm.getTableLM()).getLength()
276: .getValue(tclm.getTableLM());
277: if (opt > 0) {
278: len = Math.max(len, opt);
279: }
280: }
281: if (pgu.getRow() != null) {
282: bpd = pgu.getRow().getBlockProgressionDimension();
283: if (!bpd.getMinimum(tclm.getTableLM()).isAuto()) {
284: int min = bpd.getMinimum(tclm.getTableLM())
285: .getLength().getValue(tclm.getTableLM());
286: if (min > 0) {
287: len = Math.max(len, min);
288: }
289: }
290: }
291: }
292:
293: // Add the padding if any
294: len += pgu.getBorders()
295: .getPaddingBefore(false, pgu.getCellLM());
296: len += pgu.getBorders().getPaddingAfter(false, pgu.getCellLM());
297:
298: //Now add the borders to the contentLength
299: if (tclm.isSeparateBorderModel()) {
300: len += pgu.getBorders().getBorderBeforeWidth(false);
301: len += pgu.getBorders().getBorderAfterWidth(false);
302: } else {
303: len += pgu.getHalfMaxBeforeBorderWidth();
304: len += pgu.getHalfMaxAfterBorderWidth();
305: }
306: int startRow = Math.max(pgu.getStartRow(), firstRow[bodyType]);
307: Integer storedOffset = (Integer) rowOffsets[bodyType]
308: .get(new Integer(startRow));
309: int effYOffset;
310: if (storedOffset != null) {
311: effYOffset = storedOffset.intValue();
312: } else {
313: effYOffset = yoffset;
314: }
315: len -= yoffset - effYOffset;
316: return len;
317: }
318:
319: private void addAreasForCell(PrimaryGridUnit pgu, int startPos,
320: int endPos, EffRow row, int contentHeight, int rowHeight) {
321: int bt = row.getBodyType();
322: if (firstRow[bt] < 0) {
323: firstRow[bt] = row.getIndex();
324: }
325: //Determine the first row in this sequence
326: int startRowIndex = Math.max(pgu.getStartRow(), firstRow[bt]);
327: int lastRowIndex = lastRow.getIndex();
328:
329: // In collapsing-border model, if the cell spans over several columns/rows then
330: // dedicated areas will be created for each grid unit to hold the corresponding
331: // borders. For that we need to know the height of each grid unit, that is of each
332: // grid row spanned over by the cell
333: int[] spannedGridRowHeights = null;
334: if (!tclm.getTableLM().getTable().isSeparateBorderModel()
335: && pgu.hasSpanning()) {
336: spannedGridRowHeights = new int[lastRowIndex
337: - startRowIndex + 1];
338: int prevOffset = ((Integer) rowOffsets[bt].get(new Integer(
339: startRowIndex))).intValue();
340: for (int i = 0; i < lastRowIndex - startRowIndex; i++) {
341: int newOffset = ((Integer) rowOffsets[bt]
342: .get(new Integer(startRowIndex + i + 1)))
343: .intValue();
344: spannedGridRowHeights[i] = newOffset - prevOffset;
345: prevOffset = newOffset;
346: }
347: spannedGridRowHeights[lastRowIndex - startRowIndex] = rowHeight;
348: }
349:
350: //Determine y offset for the cell
351: Integer offset = (Integer) rowOffsets[bt].get(new Integer(
352: startRowIndex));
353: while (offset == null) {
354: //TODO Figure out what this does and when it's triggered
355: //This block is probably never used, at least it's not triggered by any of our tests
356: startRowIndex--;
357: offset = (Integer) rowOffsets[bt].get(new Integer(
358: startRowIndex));
359: }
360: int effYOffset = offset.intValue();
361: int effCellHeight = rowHeight;
362: effCellHeight += yoffset - effYOffset;
363: if (log.isDebugEnabled()) {
364: log.debug("Creating area for cell:");
365: log.debug(" current row: " + row.getIndex());
366: log.debug(" start row: " + pgu.getStartRow() + " "
367: + yoffset + " " + effYOffset);
368: log.debug(" contentHeight: " + contentHeight
369: + " rowHeight=" + rowHeight + " effCellHeight="
370: + effCellHeight);
371: }
372: TableCellLayoutManager cellLM = pgu.getCellLM();
373: cellLM.setXOffset(tclm.getXOffsetOfGridUnit(pgu));
374: cellLM.setYOffset(effYOffset);
375: cellLM.setContentHeight(contentHeight);
376: cellLM.setRowHeight(effCellHeight);
377: //cellLM.setRowHeight(row.getHeight().opt);
378: int prevBreak = ElementListUtils.determinePreviousBreak(pgu
379: .getElements(), startPos);
380: if (endPos >= 0) {
381: SpaceResolver.performConditionalsNotification(pgu
382: .getElements(), startPos, endPos, prevBreak);
383: }
384: cellLM.addAreas(new KnuthPossPosIter(pgu.getElements(),
385: startPos, endPos + 1), layoutContext,
386: spannedGridRowHeights, startRowIndex
387: - pgu.getStartRow(), lastRowIndex
388: - pgu.getStartRow() + 1);
389: }
390: }
|