blob: c3d06d435bc13fae80269cd817546bc66b6fe091 [file] [log] [blame]
/*
* Copyright 1999-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$ */
package org.apache.fop.layoutmgr.table;
import org.apache.fop.fo.FONode;
import org.apache.fop.fo.flow.Table;
import org.apache.fop.fo.flow.TableBody;
import org.apache.fop.fo.flow.TableCell;
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.BlockStackingLayoutManager;
import org.apache.fop.layoutmgr.LayoutManager;
import org.apache.fop.layoutmgr.LeafPosition;
import org.apache.fop.layoutmgr.BreakPoss;
import org.apache.fop.layoutmgr.LayoutContext;
import org.apache.fop.layoutmgr.MinOptMaxUtil;
import org.apache.fop.layoutmgr.PositionIterator;
import org.apache.fop.layoutmgr.BreakPossPosIter;
import org.apache.fop.layoutmgr.Position;
import org.apache.fop.layoutmgr.TraitSetter;
import org.apache.fop.area.Area;
import org.apache.fop.area.Block;
import org.apache.fop.area.Trait;
import org.apache.fop.traits.MinOptMax;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
/**
* LayoutManager for a table-row FO.
* The row contains cells that are organised according to the columns.
* A break in a table row will contain breaks for each table cell.
* If there are row spanning cells then these cells belong to this row
* but effect the occupied columns of future rows.
*/
public class Row extends BlockStackingLayoutManager {
private TableRow fobj;
private List gridUnits = null;
private List columns = null;
private int referenceIPD;
private int rowHeight;
private int xoffset;
private int yoffset;
private class RowPosition extends LeafPosition {
protected List cellBreaks;
protected RowPosition(LayoutManager lm, int pos, List l) {
super(lm, pos);
cellBreaks = l;
}
}
/**
* Create a new row layout manager.
*
*/
public Row(TableRow node) {
super(node);
fobj = node;
}
/** @return the table-row FO */
public TableRow getFObj() {
return this.fobj;
}
/**
* @return the table owning this row
*/
public Table getTable() {
FONode node = fobj.getParent();
while (!(node instanceof Table)) {
node = node.getParent();
}
return (Table)node;
}
/**
* Set the columns from the table.
*
* @param cols the list of columns for this table
*/
public void setColumns(List cols) {
columns = cols;
}
/** @return true if this is the layout manager for the first row in a body. */
public boolean isFirstInBody() {
return ((TableBody)getFObj().getParent()).isFirst(getFObj());
}
/** @return true if this is the layout manager for the last row in a body. */
public boolean isLastInBody() {
return ((TableBody)getFObj().getParent()).isLast(getFObj());
}
/**
* Gets the Column at a given index.
* @param index index of the column (index must be >= 1)
* @return the requested Column
*/
private Column getColumn(int index) {
int size = columns.size();
if (index > size - 1) {
return (Column)columns.get(size - 1);
} else {
return (Column)columns.get(index - 1);
}
}
private void prepareGridUnits() {
gridUnits = new java.util.ArrayList();
List availableCells = new java.util.ArrayList();
// add cells to list
while (childLMiter.hasNext()) {
curChildLM = (LayoutManager) childLMiter.next();
curChildLM.setParent(this);
curChildLM.initialize();
availableCells.add(curChildLM);
}
//Transfer available cells to their slots
int colnum = 1;
ListIterator iter = availableCells.listIterator();
while (iter.hasNext()) {
Cell cellLM = (Cell)iter.next();
TableCell cell = cellLM.getFObj();
if (cell.hasColumnNumber()) {
colnum = cell.getColumnNumber();
}
while (colnum > gridUnits.size()) {
gridUnits.add(null);
}
if (gridUnits.get(colnum - 1) != null) {
log.error("Overlapping cell at position " + colnum);
}
//Add cell info for primary slot
GridUnit info = new GridUnit(cellLM);
info.row = this;
gridUnits.set(colnum - 1, info);
info.column = getColumn(colnum);
//Add cell infos on spanned slots if any
for (int j = 1; j < cell.getNumberColumnsSpanned(); j++) {
colnum++;
GridUnit infoSpan = new GridUnit(cellLM, j);
infoSpan.row = this;
infoSpan.column = getColumn(colnum);
if (colnum > gridUnits.size()) {
gridUnits.add(infoSpan);
} else {
if (gridUnits.get(colnum - 1) != null) {
log.error("Overlapping cell at position " + colnum);
//TODO throw layout exception
}
gridUnits.set(colnum - 1, infoSpan);
}
}
colnum++;
}
//Post-processing the list (looking for gaps and resolve start and end borders)
postProcessGridUnits();
}
private void postProcessGridUnits() {
for (int pos = 1; pos <= gridUnits.size(); pos++) {
GridUnit gu = (GridUnit)gridUnits.get(pos - 1);
//Empty grid units
if (gu == null) {
//Add grid unit
gu = new GridUnit(null);
gu.row = this;
gu.column = getColumn(pos);
gridUnits.set(pos - 1, gu);
}
}
//Border resolution now that the empty grid units are filled
for (int pos = 1; pos <= gridUnits.size(); pos++) {
GridUnit starting = (GridUnit)gridUnits.get(pos - 1);
//Border resolution
if (getTable().isSeparateBorderModel()) {
starting.assignBorder(starting.layoutManager);
} else {
//Neighbouring grid unit at start edge
GridUnit start = null;
int find = pos - 1;
while (find >= 1) {
GridUnit candidate = (GridUnit)gridUnits.get(find - 1);
if (candidate.isLastGridUnitColSpan()) {
start = candidate;
break;
}
find--;
}
//Ending grid unit for current cell
GridUnit ending = null;
if (starting.layoutManager != null) {
pos += starting.layoutManager.getFObj().getNumberColumnsSpanned() - 1;
}
ending = (GridUnit)gridUnits.get(pos - 1);
//Neighbouring grid unit at end edge
GridUnit end = null;
find = pos + 1;
while (find <= gridUnits.size()) {
GridUnit candidate = (GridUnit)gridUnits.get(find - 1);
if (candidate.isPrimaryGridUnit()) {
end = candidate;
break;
}
find++;
}
CommonBorderPaddingBackground borders = new CommonBorderPaddingBackground();
GridUnit.resolveBorder(getTable(), borders, starting,
(start != null ? start : null),
CommonBorderPaddingBackground.START);
starting.effBorders = borders;
if (starting != ending) {
borders = new CommonBorderPaddingBackground();
}
GridUnit.resolveBorder(getTable(), borders, ending,
(end != null ? end : null),
CommonBorderPaddingBackground.END);
ending.effBorders = borders;
//Only start and end borders here, before and after during layout
//TODO resolve before and after borders during layout
}
}
}
/**
* Get the cell info for a cell.
*
* @param pos the position of the cell (must be >= 1)
* @return the cell info object
*/
protected GridUnit getCellInfo(int pos) {
if (gridUnits == null) {
prepareGridUnits();
}
if (pos <= gridUnits.size()) {
return (GridUnit)gridUnits.get(pos - 1);
} else {
return null;
}
}
/**
* Get the next break possibility.
* A row needs to get the possible breaks for each cell
* in the row and find a suitable break across all cells.
*
* @param context the layout context for getting breaks
* @return the next break possibility
*/
public BreakPoss getNextBreakPoss(LayoutContext context) {
//LayoutManager curLM; // currently active LM
GridUnit curGridUnit; //currently active grid unit
BreakPoss lastPos = null;
List breakList = new java.util.ArrayList();
int min = 0;
int opt = 0;
int max = 0;
// This is used for the displacement of the individual cells
int ipdOffset = 0;
int startColumn = 1;
boolean over = false;
while ((curGridUnit = getCellInfo(startColumn)) != null) {
Cell cellLM = curGridUnit.layoutManager;
if (curGridUnit.isColSpan()) {
//skip spanned slots
startColumn++;
continue;
}
List childBreaks = new ArrayList();
MinOptMax stackSize = new MinOptMax();
// Set up a LayoutContext
// the ipd is from the current column
referenceIPD = context.getRefIPD();
BreakPoss bp;
LayoutContext childLC = new LayoutContext(0);
childLC.setStackLimit(
MinOptMax.subtract(context.getStackLimit(),
stackSize));
//Determine which columns this cell will occupy
List spannedGridUnits = new java.util.ArrayList();
getGridUnitsForCell(cellLM, startColumn, spannedGridUnits);
int childRefIPD = 0;
for (int i = 0; i < spannedGridUnits.size(); i++) {
Column col = ((GridUnit)spannedGridUnits.get(i)).column;
childRefIPD += col.getWidth().getValue();
}
childLC.setRefIPD(childRefIPD);
if (cellLM != null) {
cellLM.addGridUnitsFromRow(spannedGridUnits);
cellLM.setInRowIPDOffset(ipdOffset);
while (!cellLM.isFinished()) {
if ((bp = cellLM.getNextBreakPoss(childLC)) != null) {
if (stackSize.opt + bp.getStackingSize().opt > context.getStackLimit().max) {
// reset to last break
if (lastPos != null) {
LayoutManager lm = lastPos.getLayoutManager();
lm.resetPosition(lastPos.getPosition());
if (lm != cellLM) {
cellLM.resetPosition(null);
}
} else {
cellLM.resetPosition(null);
}
over = true;
break;
}
stackSize.add(bp.getStackingSize());
lastPos = bp;
childBreaks.add(bp);
if (bp.nextBreakOverflows()) {
over = true;
break;
}
childLC.setStackLimit(MinOptMax.subtract(
context.getStackLimit(), stackSize));
}
}
startColumn += cellLM.getFObj().getNumberColumnsSpanned();
} else {
//Skipping empty cells
//log.debug("empty cell at pos " + startColumn);
startColumn++;
}
//Adjust in-row x offset for individual cells
ipdOffset += childRefIPD;
// the min is the maximum min of all cells
if (stackSize.min > min) {
min = stackSize.min;
}
// the optimum is the maximum of all optimums
if (stackSize.opt > opt) {
opt = stackSize.opt;
}
// the maximum is the largest maximum
if (stackSize.max > max) {
max = stackSize.max;
}
if (childBreaks.size() > 0) {
breakList.add(childBreaks);
}
}
MinOptMax rowSize = new MinOptMax(min, opt, max);
LengthRangeProperty specifiedBPD = fobj.getBlockProgressionDimension();
if (specifiedBPD.getEnum() != EN_AUTO) {
if ((specifiedBPD.getMaximum().getEnum() != EN_AUTO)
&& (specifiedBPD.getMaximum().getLength().getValue() < rowSize.min)) {
log.warn("maximum height of row is smaller than the minimum "
+ "height of its contents");
}
MinOptMaxUtil.restrict(rowSize, specifiedBPD);
}
rowHeight = rowSize.opt;
boolean fin = true;
startColumn = 1;
//Check if any of the cell LMs haven't finished, yet
while ((curGridUnit = getCellInfo(startColumn)) != null) {
Cell cellLM = curGridUnit.layoutManager;
if (cellLM == null) {
//skip empty cell
startColumn++;
continue;
}
if (!cellLM.isFinished()) {
fin = false;
break;
}
startColumn += cellLM.getFObj().getNumberColumnsSpanned();
}
setFinished(fin);
RowPosition rp = new RowPosition(this, breakList.size() - 1, breakList);
BreakPoss breakPoss = new BreakPoss(rp);
if (over) {
breakPoss.setFlag(BreakPoss.NEXT_OVERFLOWS, true);
}
breakPoss.setStackingSize(rowSize);
return breakPoss;
}
/**
* Determines the grid units that are spanned by the given cell.
* @param cellLM table-cell LM
* @param startCell starting cell index (must be >= 1)
* @param spannedGridUnits List to receive the applicable grid units
*/
private void getGridUnitsForCell(Cell cellLM, int startCell, List spannedGridUnits) {
int count;
if (cellLM != null) {
count = cellLM.getFObj().getNumberColumnsSpanned();
} else {
count = 1;
}
spannedGridUnits.clear();
for (int i = 0; i < count; i++) {
spannedGridUnits.add(this.gridUnits.get(startCell + i - 1));
}
}
/**
* Reset the layoutmanager "iterator" so that it will start
* with the passed Position's generating LM
* on the next call to getChildLM.
* @param pos a Position returned by a child layout manager
* representing a potential break decision.
* If pos is null, then back up to the first child LM.
*/
protected void reset(Position pos) {
//LayoutManager curLM; // currently active LM
GridUnit curGridUnit;
int cellIndex = 1;
if (pos == null) {
while ((curGridUnit = getCellInfo(cellIndex)) != null) {
if (curGridUnit.layoutManager != null) {
curGridUnit.layoutManager.resetPosition(null);
}
cellIndex++;
}
} else {
RowPosition rpos = (RowPosition)pos;
List breaks = rpos.cellBreaks;
while ((curGridUnit = getCellInfo(cellIndex)) != null) {
if (curGridUnit.layoutManager != null) {
List childbreaks = (List)breaks.get(cellIndex);
curGridUnit.layoutManager.resetPosition(
(Position)childbreaks.get(childbreaks.size() - 1));
}
cellIndex++;
}
}
setFinished(false);
}
/**
* Set the x position offset of this row.
* This is used to set the position of the areas returned by this row.
*
* @param off the x offset
*/
public void setXOffset(int off) {
xoffset = off;
}
/**
* Set the y position offset of this row.
* This is used to set the position of the areas returned by this row.
*
* @param off the y offset
*/
public void setYOffset(int off) {
yoffset = off;
}
/**
* Add the areas for the break points.
* This sets the offset of each cell as it is added.
*
* @param parentIter the position iterator
* @param layoutContext the layout context for adding areas
*/
public void addAreas(PositionIterator parentIter,
LayoutContext layoutContext) {
getParentArea(null);
BreakPoss bp1 = (BreakPoss)parentIter.peekNext();
bBogus = !bp1.generatesAreas();
if (!isBogus()) {
addID(fobj.getId());
}
Cell childLM;
int iStartPos = 0;
LayoutContext lc = new LayoutContext(0);
while (parentIter.hasNext()) {
RowPosition lfp = (RowPosition) parentIter.next();
//area exclusively for painting the row background
Block rowArea = getRowArea();
if (rowArea != null) {
rowArea.setBPD(rowHeight);
rowArea.setIPD(referenceIPD);
rowArea.setXOffset(xoffset);
rowArea.setYOffset(yoffset);
parentLM.addChildArea(rowArea);
}
for (Iterator iter = lfp.cellBreaks.iterator(); iter.hasNext();) {
List cellsbr = (List)iter.next();
BreakPossPosIter breakPosIter;
breakPosIter = new BreakPossPosIter(cellsbr, 0, cellsbr.size());
iStartPos = lfp.getLeafPos() + 1;
while ((childLM = (Cell)breakPosIter.getNextChildLM()) != null) {
childLM.setXOffset(xoffset);
childLM.setYOffset(yoffset);
childLM.setRowHeight(rowHeight);
childLM.addAreas(breakPosIter, lc);
}
}
}
flush();
}
/**
* Get the row height of the row after adjusting.
* Should only be called after adding the row areas.
*
* @return the row height of this row after adjustment
*/
public int getRowHeight() {
return rowHeight;
}
/**
* Return an Area which can contain the passed childArea. The childArea
* may not yet have any content, but it has essential traits set.
* In general, if the LayoutManager already has an Area it simply returns
* it. Otherwise, it makes a new Area of the appropriate class.
* It gets a parent area for its area by calling its parent LM.
* Finally, based on the dimensions of the parent area, it initializes
* its own area. This includes setting the content IPD and the maximum
* BPD.
*
* @param childArea the child area
* @return the parent are for the child
*/
public Area getParentArea(Area childArea) {
return parentLM.getParentArea(childArea);
}
/**
* Add the child.
* Rows return the areas returned by the child elements.
* This simply adds the area to the parent layout manager.
*
* @param childArea the child area
*/
public void addChildArea(Area childArea) {
parentLM.addChildArea(childArea);
}
/**
* Reset the position of this layout manager.
*
* @param resetPos the position to reset to
*/
public void resetPosition(Position resetPos) {
if (resetPos == null) {
reset(null);
}
}
/**
* Get the area for this row for background.
*
* @return the row area
*/
public Block getRowArea() {
if (fobj.getCommonBorderPaddingBackground().hasBackground()) {
Block block = new Block();
block.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE);
block.setPositioning(Block.ABSOLUTE);
TraitSetter.addBackground(block, fobj.getCommonBorderPaddingBackground());
return block;
} else {
return null;
}
}
}