/*
* Copyright 2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* $Id: TableContentLayoutManager.java 357061 2005-12-15 19:17:01Z jeremias $ */
package org.apache.fop.layoutmgr.table;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.area.Block;
import org.apache.fop.area.Trait;
import org.apache.fop.datatypes.PercentBaseContext;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FONode;
import org.apache.fop.fo.FObj;
import org.apache.fop.fo.flow.Table;
import org.apache.fop.fo.flow.TableBody;
import org.apache.fop.fo.flow.TableRow;
import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
import org.apache.fop.fo.properties.LengthRangeProperty;
import org.apache.fop.layoutmgr.BreakElement;
import org.apache.fop.layoutmgr.ElementListObserver;
import org.apache.fop.layoutmgr.ElementListUtils;
import org.apache.fop.layoutmgr.KnuthBox;
import org.apache.fop.layoutmgr.KnuthElement;
import org.apache.fop.layoutmgr.KnuthPenalty;
import org.apache.fop.layoutmgr.KnuthPossPosIter;
import org.apache.fop.layoutmgr.LayoutContext;
import org.apache.fop.layoutmgr.LayoutManager;
import org.apache.fop.layoutmgr.ListElement;
import org.apache.fop.layoutmgr.MinOptMaxUtil;
import org.apache.fop.layoutmgr.Position;
import org.apache.fop.layoutmgr.PositionIterator;
import org.apache.fop.layoutmgr.SpaceResolver;
import org.apache.fop.layoutmgr.TraitSetter;
import org.apache.fop.layoutmgr.SpaceResolver.SpaceHandlingBreakPosition;
import org.apache.fop.traits.MinOptMax;
/**
* Layout manager for table contents, particularly managing the creation of combined element lists.
*/
public class TableContentLayoutManager implements PercentBaseContext {
/** Logger **/
private static Log log = LogFactory.getLog(TableContentLayoutManager.class);
private TableLayoutManager tableLM;
private TableRowIterator trIter;
private TableRowIterator headerIter;
private TableRowIterator footerIter;
private LinkedList headerList;
private LinkedList footerList;
private int headerNetHeight = 0;
private int footerNetHeight = 0;
private boolean firstBreakBeforeServed = false;
private int startXOffset;
private int usedBPD;
/**
* Main constructor
* @param parent Parent layout manager
*/
public TableContentLayoutManager(TableLayoutManager parent) {
this.tableLM = parent;
Table table = getTableLM().getTable();
this.trIter = new TableRowIterator(table, getTableLM().getColumns(), TableRowIterator.BODY);
if (table.getTableHeader() != null) {
headerIter = new TableRowIterator(table,
getTableLM().getColumns(), TableRowIterator.HEADER);
}
if (table.getTableFooter() != null) {
footerIter = new TableRowIterator(table,
getTableLM().getColumns(), TableRowIterator.FOOTER);
}
}
/**
* @return the table layout manager
*/
public TableLayoutManager getTableLM() {
return this.tableLM;
}
/** @return true if the table uses the separate border model. */
private boolean isSeparateBorderModel() {
return getTableLM().getTable().isSeparateBorderModel();
}
/**
* @return the column setup of this table
*/
public ColumnSetup getColumns() {
return getTableLM().getColumns();
}
/** @return the net header height */
protected int getHeaderNetHeight() {
return this.headerNetHeight;
}
/** @return the net footer height */
protected int getFooterNetHeight() {
return this.headerNetHeight;
}
/** @return the header element list */
protected LinkedList getHeaderElements() {
return this.headerList;
}
/** @return the footer element list */
protected LinkedList getFooterElements() {
return this.footerList;
}
/** @see org.apache.fop.layoutmgr.LayoutManager */
public LinkedList getNextKnuthElements(LayoutContext context, int alignment) {
log.debug("==> Columns: " + getTableLM().getColumns());
KnuthBox headerAsFirst = null;
KnuthBox headerAsSecondToLast = null;
KnuthBox footerAsLast = null;
if (headerIter != null && headerList == null) {
this.headerList = getKnuthElementsForRowIterator(
headerIter, context, alignment, TableRowIterator.HEADER);
ElementListUtils.removeLegalBreaks(this.headerList);
this.headerNetHeight = ElementListUtils.calcContentLength(this.headerList);
if (log.isDebugEnabled()) {
log.debug("==> Header: " + headerNetHeight + " - " + this.headerList);
}
TableHeaderFooterPosition pos = new TableHeaderFooterPosition(
getTableLM(), true, this.headerList);
KnuthBox box = new KnuthBox(headerNetHeight, pos, false);
if (getTableLM().getTable().omitHeaderAtBreak()) {
//We can simply add the table header at the beginning of the whole list
headerAsFirst = box;
} else {
headerAsSecondToLast = box;
}
}
if (footerIter != null && footerList == null) {
this.footerList = getKnuthElementsForRowIterator(
footerIter, context, alignment, TableRowIterator.FOOTER);
ElementListUtils.removeLegalBreaks(this.footerList);
this.footerNetHeight = ElementListUtils.calcContentLength(this.footerList);
if (log.isDebugEnabled()) {
log.debug("==> Footer: " + footerNetHeight + " - " + this.footerList);
}
if (true /*getTableLM().getTable().omitFooterAtBreak()*/) {
//We can simply add the table header at the end of the whole list
TableHeaderFooterPosition pos = new TableHeaderFooterPosition(
getTableLM(), false, this.footerList);
KnuthBox box = new KnuthBox(footerNetHeight, pos, false);
footerAsLast = box;
}
}
LinkedList returnList = getKnuthElementsForRowIterator(
trIter, context, alignment, TableRowIterator.BODY);
if (headerAsFirst != null) {
returnList.add(0, headerAsFirst);
} else if (headerAsSecondToLast != null) {
returnList.add(headerAsSecondToLast);
}
if (footerAsLast != null) {
returnList.add(footerAsLast);
}
return returnList;
}
/**
* Creates Knuth elements by iterating over a TableRowIterator.
* @param iter TableRowIterator instance to fetch rows from
* @param context Active LayoutContext
* @param alignment alignment indicator
* @param bodyType Indicates what kind of body is being processed (BODY, HEADER or FOOTER)
* @return An element list
*/
private LinkedList getKnuthElementsForRowIterator(TableRowIterator iter,
LayoutContext context, int alignment, int bodyType) {
LinkedList returnList = new LinkedList();
EffRow[] rowGroup = null;
while ((rowGroup = iter.getNextRowGroup()) != null) {
//Check for break-before on the table-row at the start of the row group
TableRow rowFO = rowGroup[0].getTableRow();
if (rowFO != null && rowFO.getBreakBefore() != Constants.EN_AUTO) {
log.info("break-before found");
if (returnList.size() > 0) {
ListElement last = (ListElement)returnList.getLast();
if (last.isPenalty()) {
KnuthPenalty pen = (KnuthPenalty)last;
pen.setP(-KnuthPenalty.INFINITE);
pen.setBreakClass(rowFO.getBreakBefore());
} else if (last instanceof BreakElement) {
BreakElement breakPoss = (BreakElement)last;
breakPoss.setPenaltyValue(-KnuthPenalty.INFINITE);
breakPoss.setBreakClass(rowFO.getBreakBefore());
}
} else {
if (!firstBreakBeforeServed) {
//returnList.add(new KnuthPenalty(0, -KnuthPenalty.INFINITE,
// false, rowFO.getBreakBefore(), new Position(getTableLM()), true));
returnList.add(new BreakElement(new Position(getTableLM()),
0, -KnuthPenalty.INFINITE, rowFO.getBreakBefore(), context));
iter.backToPreviousRow();
firstBreakBeforeServed = true;
break;
}
}
}
firstBreakBeforeServed = true;
//Border resolution
if (!isSeparateBorderModel()) {
resolveNormalBeforeAfterBordersForRowGroup(rowGroup, iter);
}
//Element list creation
createElementsForRowGroup(context, alignment, bodyType,
returnList, rowGroup);
//Handle keeps
if (context.isKeepWithNextPending()) {
log.debug("child LM (row group) signals pending keep-with-next");
}
if (context.isKeepWithPreviousPending()) {
log.debug("child LM (row group) signals pending keep-with-previous");
if (returnList.size() > 0) {
//Modify last penalty
ListElement last = (ListElement)returnList.getLast();
if (last.isPenalty()) {
BreakElement breakPoss = (BreakElement)last;
//Only honor keep if there's no forced break
if (!breakPoss.isForcedBreak()) {
breakPoss.setPenaltyValue(KnuthPenalty.INFINITE);
}
}
}
}
//Check for break-after on the table-row at the end of the row group
rowFO = rowGroup[rowGroup.length - 1].getTableRow();
if (rowFO != null && rowFO.getBreakAfter() != Constants.EN_AUTO) {
log.info("break-after found");
if (returnList.size() > 0) {
ListElement last = (ListElement)returnList.getLast();
if (last instanceof KnuthPenalty) {
KnuthPenalty pen = (KnuthPenalty)last;
pen.setP(-KnuthPenalty.INFINITE);
pen.setBreakClass(rowFO.getBreakAfter());
} else if (last instanceof BreakElement) {
BreakElement breakPoss = (BreakElement)last;
breakPoss.setPenaltyValue(-KnuthPenalty.INFINITE);
breakPoss.setBreakClass(rowFO.getBreakAfter());
}
}
}
}
if (returnList.size() > 0) {
//Remove last penalty
ListElement last = (ListElement)returnList.getLast();
if (last.isPenalty() || last instanceof BreakElement) {
if (!last.isForcedBreak()) {
//Only remove if we don't signal a forced break
returnList.removeLast();
}
}
}
return returnList;
}
/**
* Resolves normal borders for a row group.
* @param iter Table row iterator to operate on
*/
private void resolveNormalBeforeAfterBordersForRowGroup(EffRow[] rowGroup,
TableRowIterator iter) {
for (int rgi = 0; rgi < rowGroup.length; rgi++) {
EffRow row = rowGroup[rgi];
EffRow prev = iter.getCachedRow(row.getIndex() - 1);
EffRow next = iter.getCachedRow(row.getIndex() + 1);
if (next == null) {
//It wasn't read, yet, or we are at the last row
next = iter.getNextRow();
iter.backToPreviousRow();
}
if ((prev == null) && (iter == this.trIter) && (this.headerIter != null)) {
prev = this.headerIter.getLastRow();
}
if ((next == null) && (iter == this.headerIter)) {
next = this.trIter.getFirstRow();
}
if ((next == null) && (iter == this.trIter) && (this.footerIter != null)) {
next = this.footerIter.getFirstRow();
}
if ((prev == null) && (iter == this.footerIter)) {
//TODO This could be bad for memory consumption because it already causes the
//whole body iterator to be prefetched!
prev = this.trIter.getLastRow();
}
log.debug(prev + " - " + row + " - " + next);
//Determine the grid units necessary for getting all the borders right
int guCount = row.getGridUnits().size();
if (prev != null) {
guCount = Math.max(guCount, prev.getGridUnits().size());
}
if (next != null) {
guCount = Math.max(guCount, next.getGridUnits().size());
}
GridUnit gu = row.getGridUnit(0);
//Create empty grid units to hold resolved borders of neighbouring cells
//TODO maybe this needs to be done differently (and sooner)
for (int i = 0; i < guCount - row.getGridUnits().size(); i++) {
//TODO This block is untested!
int pos = row.getGridUnits().size() + i;
row.getGridUnits().add(new EmptyGridUnit(gu.getRow(),
this.tableLM.getColumns().getColumn(pos + 1), gu.getBody(),
pos));
}
//Now resolve normal borders
if (getTableLM().getTable().isSeparateBorderModel()) {
//nop, borders are already assigned at this point
} else {
for (int i = 0; i < row.getGridUnits().size(); i++) {
gu = row.getGridUnit(i);
GridUnit other;
int flags = 0;
if (prev != null && i < prev.getGridUnits().size()) {
other = prev.getGridUnit(i);
} else {
other = null;
}
if (other == null
|| other.isEmpty()
|| gu.isEmpty()
|| gu.getPrimary() != other.getPrimary()) {
if ((iter == this.trIter)
&& gu.getFlag(GridUnit.FIRST_IN_TABLE)
&& (this.headerIter == null)) {
flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
}
if ((iter == this.headerIter)
&& gu.getFlag(GridUnit.FIRST_IN_TABLE)) {
flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
}
gu.resolveBorder(other,
CommonBorderPaddingBackground.BEFORE, flags);
}
flags = 0;
if (next != null && i < next.getGridUnits().size()) {
other = next.getGridUnit(i);
} else {
other = null;
}
if (other == null
|| other.isEmpty()
|| gu.isEmpty()
|| gu.getPrimary() != other.getPrimary()) {
if ((iter == this.trIter)
&& gu.getFlag(GridUnit.LAST_IN_TABLE)
&& (this.footerIter == null)) {
flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
}
if ((iter == this.footerIter)
&& gu.getFlag(GridUnit.LAST_IN_TABLE)) {
flags |= CollapsingBorderModel.VERTICAL_START_END_OF_TABLE;
}
gu.resolveBorder(other,
CommonBorderPaddingBackground.AFTER, flags);
}
}
}
}
}
/**
* Creates Knuth elements for a row group (see TableRowIterator.getNextRowGroup()).
* @param context Active LayoutContext
* @param alignment alignment indicator
* @param bodyType Indicates what kind of body is being processed (BODY, HEADER or FOOTER)
* @param returnList List to received the generated elements
* @param rowGroup row group to process
*/
private void createElementsForRowGroup(LayoutContext context, int alignment,
int bodyType, LinkedList returnList,
EffRow[] rowGroup) {
log.debug("Handling row group with " + rowGroup.length + " rows...");
MinOptMax[] rowHeights = new MinOptMax[rowGroup.length];
MinOptMax[] explicitRowHeights = new MinOptMax[rowGroup.length];
EffRow row;
int maxColumnCount = 0;
List pgus = new java.util.ArrayList(); //holds a list of a row's primary grid units
for (int rgi = 0; rgi < rowGroup.length; rgi++) {
row = rowGroup[rgi];
rowHeights[rgi] = new MinOptMax(0, 0, Integer.MAX_VALUE);
explicitRowHeights[rgi] = new MinOptMax(0, 0, Integer.MAX_VALUE);
pgus.clear();
TableRow tableRow = null;
int minContentHeight = 0;
int maxCellHeight = 0;
int effRowContentHeight = 0;
for (int j = 0; j < row.getGridUnits().size(); j++) {
maxColumnCount = Math.max(maxColumnCount, row.getGridUnits().size());
GridUnit gu = row.getGridUnit(j);
if ((gu.isPrimary() || (gu.getColSpanIndex() == 0 && gu.isLastGridUnitRowSpan()))
&& !gu.isEmpty()) {
PrimaryGridUnit primary = gu.getPrimary();
if (gu.isPrimary()) {
primary.getCellLM().setParent(getTableLM());
//Determine the table-row if any
if (tableRow == null && primary.getRow() != null) {
tableRow = primary.getRow();
//Check for bpd on row, see CSS21, 17.5.3 Table height algorithms
LengthRangeProperty bpd = tableRow.getBlockProgressionDimension();
if (!bpd.getMinimum(getTableLM()).isAuto()) {
minContentHeight = Math.max(
minContentHeight,
bpd.getMinimum(
getTableLM()).getLength().getValue(getTableLM()));
}
MinOptMaxUtil.restrict(explicitRowHeights[rgi], bpd, getTableLM());
}
//Calculate width of cell
int spanWidth = 0;
for (int i = primary.getStartCol();
i < primary.getStartCol()
+ primary.getCell().getNumberColumnsSpanned();
i++) {
if (getTableLM().getColumns().getColumn(i + 1) != null) {
spanWidth += getTableLM().getColumns().getColumn(i + 1)
.getColumnWidth().getValue(getTableLM());
}
}
LayoutContext childLC = new LayoutContext(0);
childLC.setStackLimit(context.getStackLimit()); //necessary?
childLC.setRefIPD(spanWidth);
//Get the element list for the cell contents
LinkedList elems = primary.getCellLM().getNextKnuthElements(
childLC, alignment);
//Temporary? Multiple calls in case of break conditions.
//TODO Revisit when table layout is restartable
while (!primary.getCellLM().isFinished()) {
LinkedList additionalElems = primary.getCellLM().getNextKnuthElements(
childLC, alignment);
elems.addAll(additionalElems);
}
ElementListObserver.observe(elems, "table-cell", primary.getCell().getId());
if ((elems.size() > 0)
&& ((KnuthElement)elems.getLast()).isForcedBreak()) {
// a descendant of this block has break-after
log.debug("Descendant of table-cell signals break: "
+ primary.getCellLM().isFinished());
}
primary.setElements(elems);
if (childLC.isKeepWithNextPending()) {
log.debug("child LM signals pending keep-with-next");
primary.setFlag(GridUnit.KEEP_WITH_NEXT_PENDING, true);
}
if (childLC.isKeepWithPreviousPending()) {
log.debug("child LM signals pending keep-with-previous");
primary.setFlag(GridUnit.KEEP_WITH_PREVIOUS_PENDING, true);
}
}
//Calculate height of cell contents
primary.setContentLength(ElementListUtils.calcContentLength(
primary.getElements()));
maxCellHeight = Math.max(maxCellHeight, primary.getContentLength());
//Calculate height of row, see CSS21, 17.5.3 Table height algorithms
if (gu.isLastGridUnitRowSpan()) {
int effCellContentHeight = minContentHeight;
LengthRangeProperty bpd = primary.getCell().getBlockProgressionDimension();
if (!bpd.getMinimum(getTableLM()).isAuto()) {
effCellContentHeight = Math.max(
effCellContentHeight,
bpd.getMinimum(getTableLM()).getLength().getValue(getTableLM()));
}
if (!bpd.getOptimum(getTableLM()).isAuto()) {
effCellContentHeight = Math.max(
effCellContentHeight,
bpd.getOptimum(getTableLM()).getLength().getValue(getTableLM()));
}
if (gu.getRowSpanIndex() == 0) {
//TODO ATM only non-row-spanned cells are taken for this
MinOptMaxUtil.restrict(explicitRowHeights[rgi], bpd, tableLM);
}
effCellContentHeight = Math.max(effCellContentHeight,
primary.getContentLength());
int borderWidths;
if (isSeparateBorderModel()) {
borderWidths = primary.getBorders().getBorderBeforeWidth(false)
+ primary.getBorders().getBorderAfterWidth(false);
} else {
borderWidths = primary.getHalfMaxBorderWidth();
}
int padding = 0;
effRowContentHeight = Math.max(effRowContentHeight,
effCellContentHeight);
CommonBorderPaddingBackground cbpb
= primary.getCell().getCommonBorderPaddingBackground();
padding += cbpb.getPaddingBefore(false, primary.getCellLM());
padding += cbpb.getPaddingAfter(false, primary.getCellLM());
int effRowHeight = effCellContentHeight
+ padding + borderWidths
+ 2 * getTableLM().getHalfBorderSeparationBPD();
for (int previous = 0; previous < gu.getRowSpanIndex(); previous++) {
effRowHeight -= rowHeights[rgi - previous - 1].opt;
}
if (effRowHeight > rowHeights[rgi].min) {
//This is the new height of the (grid) row
MinOptMaxUtil.extendMinimum(rowHeights[rgi], effRowHeight, false);
}
}
if (gu.isPrimary()) {
pgus.add(primary);
}
}
}
row.setHeight(rowHeights[rgi]);
row.setExplicitHeight(explicitRowHeights[rgi]);
if (effRowContentHeight > row.getExplicitHeight().max) {
log.warn(FONode.decorateWithContextInfo(
"The contents of row " + (row.getIndex() + 1)
+ " are taller than they should be (there is a"
+ " block-progression-dimension or height constraint on the indicated row)."
+ " Due to its contents the row grows"
+ " to " + effRowContentHeight + " millipoints, but the row shouldn't get"
+ " any taller than " + row.getExplicitHeight() + " millipoints.",
row.getTableRow()));
}
}
if (log.isDebugEnabled()) {
log.debug("rowGroup:");
for (int i = 0; i < rowHeights.length; i++) {
log.debug(" height=" + rowHeights[i] + " explicit=" + explicitRowHeights[i]);
}
}
//TODO It may make sense to reuse the stepper since it allocates quite some space
TableStepper stepper = new TableStepper(this);
LinkedList returnedList = stepper.getCombinedKnuthElementsForRowGroup(
context, rowGroup, maxColumnCount, bodyType);
if (returnedList != null) {
returnList.addAll(returnedList);
}
}
/**
* Retuns the X offset of the given grid unit.
* @param gu the grid unit
* @return the requested X offset
*/
protected int getXOffsetOfGridUnit(GridUnit gu) {
int col = gu.getStartCol();
return startXOffset + getTableLM().getColumns().getXOffset(col + 1, getTableLM());
}
/**
* Adds the areas generated my this layout manager to the area tree.
* @param parentIter the position iterator
* @param layoutContext the layout context for adding areas
*/
public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) {
this.usedBPD = 0;
RowPainter painter = new RowPainter(layoutContext);
List positions = new java.util.ArrayList();
List headerElements = null;
List footerElements = null;
Position firstPos = null;
Position lastPos = null;
Position lastCheckPos = null;
while (parentIter.hasNext()) {
Position pos = (Position)parentIter.next();
if (pos instanceof SpaceHandlingBreakPosition) {
//This position has only been needed before addAreas was called, now we need the
//original one created by the layout manager.
pos = ((SpaceHandlingBreakPosition)pos).getOriginalBreakPosition();
}
if (pos == null) {
continue;
}
if (firstPos == null) {
firstPos = pos;
}
lastPos = pos;
if (pos.getIndex() >= 0) {
lastCheckPos = pos;
}
if (pos instanceof TableHeaderFooterPosition) {
TableHeaderFooterPosition thfpos = (TableHeaderFooterPosition)pos;
//these positions need to be unpacked
if (thfpos.header) {
//Positions for header will be added first
headerElements = thfpos.nestedElements;
} else {
//Positions for footers are simply added at the end
footerElements = thfpos.nestedElements;
}
} else if (pos instanceof TableHFPenaltyPosition) {
//ignore for now, see special handling below if break is at a penalty
//Only if the last position in this part/page us such a position it will be used
} else {
//leave order as is for the rest
positions.add(pos);
}
}
if (lastPos instanceof TableHFPenaltyPosition) {
TableHFPenaltyPosition penaltyPos = (TableHFPenaltyPosition)lastPos;
log.debug("Break at penalty!");
if (penaltyPos.headerElements != null) {
//Header positions for the penalty position are in the last element and need to
//be handled first before all other TableContentPositions
headerElements = penaltyPos.headerElements;
}
if (penaltyPos.footerElements != null) {
footerElements = penaltyPos.footerElements;
}
}
Map markers = getTableLM().getTable().getMarkers();
if (markers != null) {
getTableLM().getCurrentPV().addMarkers(markers,
true, getTableLM().isFirst(firstPos), getTableLM().isLast(lastCheckPos));
}
if (headerElements != null) {
//header positions for the last part are the second-to-last element and need to
//be handled first before all other TableContentPositions
PositionIterator nestedIter = new KnuthPossPosIter(headerElements);
iterateAndPaintPositions(nestedIter, painter);
painter.addAreasAndFlushRow(true);
}
//Iterate over all steps
Iterator posIter = positions.iterator();
iterateAndPaintPositions(posIter, painter);
painter.addAreasAndFlushRow(true);
if (footerElements != null) {
//Positions for footers are simply added at the end
PositionIterator nestedIter = new KnuthPossPosIter(footerElements);
iterateAndPaintPositions(nestedIter, painter);
painter.addAreasAndFlushRow(true);
}
painter.notifyEndOfSequence();
this.usedBPD += painter.getAccumulatedBPD();
if (markers != null) {
getTableLM().getCurrentPV().addMarkers(markers,
false, getTableLM().isFirst(firstPos), getTableLM().isLast(lastCheckPos));
}
}
private void iterateAndPaintPositions(Iterator iterator, RowPainter painter) {
List lst = new java.util.ArrayList();
boolean firstPos = false;
boolean lastPos = false;
TableBody body = null;
while (iterator.hasNext()) {
Position pos = (Position)iterator.next();
if (pos instanceof TableContentPosition) {
TableContentPosition tcpos = (TableContentPosition)pos;
lst.add(tcpos);
GridUnitPart part = (GridUnitPart)tcpos.gridUnitParts.get(0);
if (body == null) {
body = part.pgu.getBody();
}
if (tcpos.getFlag(TableContentPosition.FIRST_IN_ROWGROUP)
&& tcpos.row.getFlag(EffRow.FIRST_IN_BODY)) {
firstPos = true;
}
if (tcpos.getFlag(TableContentPosition.LAST_IN_ROWGROUP)
&& tcpos.row.getFlag(EffRow.LAST_IN_BODY)) {
lastPos = true;
getTableLM().getCurrentPV().addMarkers(body.getMarkers(),
true, firstPos, lastPos);
int size = lst.size();
for (int i = 0; i < size; i++) {
painter.handleTableContentPosition((TableContentPosition)lst.get(i));
}
getTableLM().getCurrentPV().addMarkers(body.getMarkers(),
false, firstPos, lastPos);
//reset
firstPos = false;
lastPos = false;
body = null;
lst.clear();
}
} else {
if (log.isDebugEnabled()) {
log.debug("Ignoring position: " + pos);
}
}
}
if (body != null) {
getTableLM().getCurrentPV().addMarkers(body.getMarkers(),
true, firstPos, lastPos);
int size = lst.size();
for (int i = 0; i < size; i++) {
painter.handleTableContentPosition((TableContentPosition)lst.get(i));
}
getTableLM().getCurrentPV().addMarkers(body.getMarkers(),
false, firstPos, lastPos);
}
}
private class RowPainter {
private TableRow rowFO = null;
private int colCount = getColumns().getColumnCount();
private int yoffset = 0;
private int accumulatedBPD = 0;
private EffRow lastRow = null;
private LayoutContext layoutContext;
private int lastRowHeight = 0;
private int[] firstRow = new int[3];
private Map[] rowOffsets = new Map[] {new java.util.HashMap(),
new java.util.HashMap(), new java.util.HashMap()};
//These three variables are our buffer to recombine the individual steps into cells
private PrimaryGridUnit[] gridUnits = new PrimaryGridUnit[colCount];
private int[] start = new int[colCount];
private int[] end = new int[colCount];
private int[] partLength = new int[colCount];
public RowPainter(LayoutContext layoutContext) {
this.layoutContext = layoutContext;
Arrays.fill(firstRow, -1);
Arrays.fill(end, -1);
}
public int getAccumulatedBPD() {
return this.accumulatedBPD;
}
public void notifyEndOfSequence() {
this.accumulatedBPD += lastRowHeight; //for last row
}
public void handleTableContentPosition(TableContentPosition tcpos) {
if (lastRow != tcpos.row && lastRow != null) {
addAreasAndFlushRow(false);
yoffset += lastRowHeight;
this.accumulatedBPD += lastRowHeight;
}
if (log.isDebugEnabled()) {
log.debug("===handleTableContentPosition(" + tcpos);
}
rowFO = tcpos.row.getTableRow();
lastRow = tcpos.row;
Iterator partIter = tcpos.gridUnitParts.iterator();
//Iterate over all grid units in the current step
while (partIter.hasNext()) {
GridUnitPart gup = (GridUnitPart)partIter.next();
if (log.isDebugEnabled()) {
log.debug(">" + gup);
}
int colIndex = gup.pgu.getStartCol();
if (gridUnits[colIndex] != gup.pgu) {
if (gridUnits[colIndex] != null) {
log.warn("Replacing GU in slot " + colIndex
+ ". Some content may not be painted.");
}
gridUnits[colIndex] = gup.pgu;
start[colIndex] = gup.start;
end[colIndex] = gup.end;
} else {
if (gup.end < end[colIndex]) {
throw new IllegalStateException("Internal Error: stepper problem");
}
end[colIndex] = gup.end;
}
}
}
public int addAreasAndFlushRow(boolean forcedFlush) {
int actualRowHeight = 0;
int readyCount = 0;
int bt = lastRow.getBodyType();
if (log.isDebugEnabled()) {
log.debug("Remembering yoffset for row " + lastRow.getIndex() + ": " + yoffset);
}
rowOffsets[bt].put(new Integer(lastRow.getIndex()), new Integer(yoffset));
for (int i = 0; i < gridUnits.length; i++) {
if ((gridUnits[i] != null)
&& (forcedFlush || (end[i] == gridUnits[i].getElements().size() - 1))) {
if (log.isTraceEnabled()) {
log.trace("getting len for " + i + " "
+ start[i] + "-" + end[i]);
}
readyCount++;
int len = ElementListUtils.calcContentLength(
gridUnits[i].getElements(), start[i], end[i]);
partLength[i] = len;
if (log.isTraceEnabled()) {
log.trace("len of part: " + len);
}
if (start[i] == 0) {
LengthRangeProperty bpd = gridUnits[i].getCell()
.getBlockProgressionDimension();
if (!bpd.getMinimum(getTableLM()).isAuto()) {
int min = bpd.getMinimum(getTableLM())
.getLength().getValue(getTableLM());
if (min > 0) {
len = Math.max(len, bpd.getMinimum(getTableLM())
.getLength().getValue(getTableLM()));
}
}
if (!bpd.getOptimum(getTableLM()).isAuto()) {
int opt = bpd.getOptimum(getTableLM())
.getLength().getValue(getTableLM());
if (opt > 0) {
len = Math.max(len, opt);
}
}
if (gridUnits[i].getRow() != null) {
bpd = gridUnits[i].getRow().getBlockProgressionDimension();
if (!bpd.getMinimum(getTableLM()).isAuto()) {
int min = bpd.getMinimum(getTableLM()).getLength()
.getValue(getTableLM());
if (min > 0) {
len = Math.max(len, min);
}
}
}
}
// Add the padding if any
len += gridUnits[i].getBorders()
.getPaddingBefore(false, gridUnits[i].getCellLM());
len += gridUnits[i].getBorders()
.getPaddingAfter(false, gridUnits[i].getCellLM());
//Now add the borders to the contentLength
if (isSeparateBorderModel()) {
len += gridUnits[i].getBorders().getBorderBeforeWidth(false);
len += gridUnits[i].getBorders().getBorderAfterWidth(false);
}
int startRow = Math.max(gridUnits[i].getStartRow(), firstRow[bt]);
Integer storedOffset = (Integer)rowOffsets[bt].get(new Integer(startRow));
int effYOffset;
if (storedOffset != null) {
effYOffset = storedOffset.intValue();
} else {
effYOffset = yoffset;
}
len -= yoffset - effYOffset;
actualRowHeight = Math.max(actualRowHeight, len);
}
}
if (readyCount == 0) {
return 0;
}
actualRowHeight += 2 * getTableLM().getHalfBorderSeparationBPD();
lastRowHeight = actualRowHeight;
//Add areas for row
addRowBackgroundArea(rowFO, actualRowHeight, layoutContext.getRefIPD(), yoffset);
for (int i = 0; i < gridUnits.length; i++) {
GridUnit currentGU = lastRow.safelyGetGridUnit(i);
if ((gridUnits[i] != null)
&& (forcedFlush || ((end[i] == gridUnits[i].getElements().size() - 1))
&& (currentGU == null || currentGU.isLastGridUnitRowSpan()))
|| (gridUnits[i] == null && currentGU != null)) {
//the last line in the "if" above is to avoid a premature end of an
//row-spanned cell because no GridUnitParts are generated after a cell is
//finished with its content. currentGU can be null if there's no grid unit
//at this place in the current row (empty cell and no borders to process)
if (log.isDebugEnabled()) {
log.debug((forcedFlush ? "FORCED " : "") + "flushing..." + i + " "
+ start[i] + "-" + end[i]);
}
PrimaryGridUnit gu = gridUnits[i];
if (gu == null
&& !currentGU.isEmpty()
&& currentGU.getColSpanIndex() == 0
&& currentGU.isLastGridUnitColSpan()
&& (forcedFlush || currentGU.isLastGridUnitRowSpan())) {
gu = currentGU.getPrimary();
}
if (gu != null) {
addAreasForCell(gu, start[i], end[i],
lastRow,
partLength[i], actualRowHeight);
gridUnits[i] = null;
start[i] = 0;
end[i] = -1;
partLength[i] = 0;
}
}
}
return actualRowHeight;
}
private void addAreasForCell(PrimaryGridUnit pgu, int startPos, int endPos,
EffRow row, int contentHeight, int rowHeight) {
int bt = row.getBodyType();
if (firstRow[bt] < 0) {
firstRow[bt] = row.getIndex();
}
//Determine the first row in this sequence
int startRow = Math.max(pgu.getStartRow(), firstRow[bt]);
//Determine y offset for the cell
Integer offset = (Integer)rowOffsets[bt].get(new Integer(startRow));
while (offset == null) {
startRow--;
offset = (Integer)rowOffsets[bt].get(new Integer(startRow));
}
int effYOffset = offset.intValue();
int effCellHeight = rowHeight;
effCellHeight += yoffset - effYOffset;
if (log.isDebugEnabled()) {
log.debug("Creating area for cell:");
log.debug(" current row: " + row.getIndex());
log.debug(" start row: " + pgu.getStartRow() + " " + yoffset + " " + effYOffset);
log.debug(" contentHeight: " + contentHeight + " rowHeight=" + rowHeight
+ " effCellHeight=" + effCellHeight);
}
TableCellLayoutManager cellLM = pgu.getCellLM();
cellLM.setXOffset(getXOffsetOfGridUnit(pgu));
cellLM.setYOffset(effYOffset);
cellLM.setContentHeight(contentHeight);
cellLM.setRowHeight(effCellHeight);
//cellLM.setRowHeight(row.getHeight().opt);
int prevBreak = ElementListUtils.determinePreviousBreak(pgu.getElements(), startPos);
if (endPos >= 0) {
SpaceResolver.performConditionalsNotification(pgu.getElements(),
startPos, endPos, prevBreak);
}
cellLM.addAreas(new KnuthPossPosIter(pgu.getElements(),
startPos, endPos + 1), layoutContext);
}
}
/**
* Get the area for a row for background.
* @param row the table-row object or null
* @return the row area or null if there's no background to paint
*/
public Block getRowArea(TableRow row) {
if (row == null || !row.getCommonBorderPaddingBackground().hasBackground()) {
return null;
} else {
Block block = new Block();
block.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE);
block.setPositioning(Block.ABSOLUTE);
return block;
}
}
/**
* Adds the area for the row background if any.
* @param row row for which to generate the background
* @param bpd block-progression-dimension of the row
* @param ipd inline-progression-dimension of the row
* @param yoffset Y offset at which to paint
*/
public void addRowBackgroundArea(TableRow row, int bpd, int ipd, int yoffset) {
//Add row background if any
Block rowBackground = getRowArea(row);
if (rowBackground != null) {
rowBackground.setBPD(bpd);
rowBackground.setIPD(ipd);
rowBackground.setXOffset(this.startXOffset);
rowBackground.setYOffset(yoffset);
getTableLM().addChildArea(rowBackground);
TraitSetter.addBackground(rowBackground,
row.getCommonBorderPaddingBackground(), getTableLM());
}
}
/**
* Sets the overall starting x-offset. Used for proper placement of cells.
* @param startXOffset starting x-offset (table's start-indent)
*/
public void setStartXOffset(int startXOffset) {
this.startXOffset = startXOffset;
}
/**
* @return the amount of block-progression-dimension used by the content
*/
public int getUsedBPD() {
return this.usedBPD;
}
/**
* Represents a non-dividable part of a grid unit. Used by the table stepper.
*/
protected static class GridUnitPart {
/** Primary grid unit */
protected PrimaryGridUnit pgu;
/** Index of the starting element of this part */
protected int start;
/** Index of the ending element of this part */
protected int end;
/**
* Creates a new GridUnitPart.
* @param pgu Primary grid unit
* @param start starting element
* @param end ending element
*/
protected GridUnitPart(PrimaryGridUnit pgu, int start, int end) {
this.pgu = pgu;
this.start = start;
this.end = end;
}
/** @return true if this part is the first part of a cell */
public boolean isFirstPart() {
return (start == 0);
}
/** @return true if this part is the last part of a cell */
public boolean isLastPart() {
return (end >= 0 && end == pgu.getElements().size() - 1);
}
/** @see java.lang.Object#toString() */
public String toString() {
StringBuffer sb = new StringBuffer("Part: ");
sb.append(start).append("-").append(end);
sb.append(" [").append(isFirstPart() ? "F" : "-").append(isLastPart() ? "L" : "-");
sb.append("] ").append(pgu);
return sb.toString();
}
}
/**
* This class represents a Position specific to this layout manager. Used for normal content
* cases.
*/
public static class TableContentPosition extends Position {
/** The position is the first of the row group. */
public static final int FIRST_IN_ROWGROUP = 1;
/** The position is the last of the row group. */
public static final int LAST_IN_ROWGROUP = 2;
/** the list of GridUnitParts making up this position */
protected List gridUnitParts;
/** effective row this position belongs to */
protected EffRow row;
/** flags for the position */
protected int flags;
/**
* Creates a new TableContentPosition.
* @param lm applicable layout manager
* @param gridUnitParts the list of GridUnitPart instances
* @param row effective row this position belongs to
*/
protected TableContentPosition(LayoutManager lm, List gridUnitParts,
EffRow row) {
super(lm);
this.gridUnitParts = gridUnitParts;
this.row = row;
}
/**
* Returns a flag for this GridUnit.
* @param which the requested flag
* @return the value of the flag
*/
public boolean getFlag(int which) {
return (flags & (1 << which)) != 0;
}
/**
* Sets a flag on a GridUnit.
* @param which the flag to set
* @param value the new value for the flag
*/
public void setFlag(int which, boolean value) {
if (value) {
flags |= (1 << which); //set flag
} else {
flags &= ~(1 << which); //clear flag
}
}
/** @see java.lang.Object#toString() */
public String toString() {
StringBuffer sb = new StringBuffer("TableContentPosition:");
sb.append(getIndex());
sb.append("[");
sb.append(row.getIndex()).append("/");
sb.append(getFlag(FIRST_IN_ROWGROUP) ? "F" : "-");
sb.append(getFlag(LAST_IN_ROWGROUP) ? "L" : "-").append("]");
sb.append("(");
sb.append(gridUnitParts);
sb.append(")");
return sb.toString();
}
}
/**
* This class represents a Position specific to this layout manager. Used for table
* headers and footers at the beginning and end of a table.
*/
public static class TableHeaderFooterPosition extends Position {
/** True indicates a position for a header, false for a footer. */
protected boolean header;
/** Element list representing the header/footer */
protected List nestedElements;
/**
* Creates a new TableHeaderFooterPosition.
* @param lm applicable layout manager
* @param header True indicates a position for a header, false for a footer.
* @param nestedElements Element list representing the header/footer
*/
protected TableHeaderFooterPosition(LayoutManager lm,
boolean header, List nestedElements) {
super(lm);
this.header = header;
this.nestedElements = nestedElements;
}
/** @see java.lang.Object#toString() */
public String toString() {
StringBuffer sb = new StringBuffer("Table");
sb.append(header ? "Header" : "Footer");
sb.append("Position:");
sb.append(getIndex()).append("(");
sb.append(nestedElements);
sb.append(")");
return sb.toString();
}
}
/**
* This class represents a Position specific to this layout manager. Used for table
* headers and footers at breaks.
*/
public static class TableHFPenaltyPosition extends Position {
/** Element list for the header */
protected List headerElements;
/** Element list for the footer */
protected List footerElements;
/**
* Creates a new TableHFPenaltyPosition
* @param lm applicable layout manager
*/
protected TableHFPenaltyPosition(LayoutManager lm) {
super(lm);
}
/** @see java.lang.Object#toString() */
public String toString() {
StringBuffer sb = new StringBuffer("TableHFPenaltyPosition:");
sb.append(getIndex()).append("(");
sb.append("header:");
sb.append(headerElements);
sb.append(", footer:");
sb.append(footerElements);
sb.append(")");
return sb.toString();
}
}
// --------- Property Resolution related functions --------- //
/**
* @see org.apache.fop.datatypes.PercentBaseContext#getBaseLength(int, FObj)
*/
public int getBaseLength(int lengthBase, FObj fobj) {
return tableLM.getBaseLength(lengthBase, fobj);
}
}