blob: cba4c2fae03a319a2c7238546ee7a724830ef798 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 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.Area;
import org.apache.fop.area.Block;
import org.apache.fop.area.Trait;
import org.apache.fop.fo.flow.Marker;
import org.apache.fop.fo.flow.table.ConditionalBorder;
import org.apache.fop.fo.flow.table.GridUnit;
import org.apache.fop.fo.flow.table.PrimaryGridUnit;
import org.apache.fop.fo.flow.table.Table;
import org.apache.fop.fo.flow.table.TableCell;
import org.apache.fop.fo.flow.table.TableColumn;
import org.apache.fop.fo.flow.table.TableFooter;
import org.apache.fop.fo.flow.table.TableHeader;
import org.apache.fop.fo.flow.table.TablePart;
import org.apache.fop.fo.flow.table.TableRow;
import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
import org.apache.fop.fo.properties.CommonBorderPaddingBackground.BorderInfo;
import org.apache.fop.layoutmgr.AbstractLayoutManager;
import org.apache.fop.layoutmgr.AreaAdditionUtil;
import org.apache.fop.layoutmgr.BlockStackingLayoutManager;
import org.apache.fop.layoutmgr.ElementListObserver;
import org.apache.fop.layoutmgr.ElementListUtils;
import org.apache.fop.layoutmgr.Keep;
import org.apache.fop.layoutmgr.KnuthBox;
import org.apache.fop.layoutmgr.KnuthElement;
import org.apache.fop.layoutmgr.KnuthGlue;
import org.apache.fop.layoutmgr.KnuthPenalty;
import org.apache.fop.layoutmgr.LayoutContext;
import org.apache.fop.layoutmgr.LayoutManager;
import org.apache.fop.layoutmgr.LocalBreaker;
import org.apache.fop.layoutmgr.Position;
import org.apache.fop.layoutmgr.PositionIterator;
import org.apache.fop.layoutmgr.RetrieveTableMarkerLayoutManager;
import org.apache.fop.layoutmgr.SpaceResolver;
import org.apache.fop.layoutmgr.TraitSetter;
import org.apache.fop.traits.BorderProps;
import org.apache.fop.traits.MinOptMax;
import org.apache.fop.util.ListUtil;
/**
* LayoutManager for a table-cell FO.
* A cell contains blocks. These blocks fill the cell.
*/
public class TableCellLayoutManager extends BlockStackingLayoutManager {
/**
* logging instance
*/
private static Log log = LogFactory.getLog(TableCellLayoutManager.class);
private PrimaryGridUnit primaryGridUnit;
private Block curBlockArea;
private int xoffset;
private int yoffset;
private int cellIPD;
private int totalHeight;
private int usedBPD;
private boolean emptyCell = true;
private boolean isDescendantOfTableFooter;
private boolean isDescendantOfTableHeader;
private boolean hasRetrieveTableMarker;
private boolean hasRepeatedHeader;
// place holder for the addAreas arguments
private boolean savedAddAreasArguments;
private PositionIterator savedParentIter;
private LayoutContext savedLayoutContext;
private int[] savedSpannedGridRowHeights;
private int savedStartRow;
private int savedEndRow;
private int savedBorderBeforeWhich;
private int savedBorderAfterWhich;
private boolean savedFirstOnPage;
private boolean savedLastOnPage;
private RowPainter savedPainter;
private int savedFirstRowHeight;
// this is set to false when the table-cell has a retrieve-table-marker and is in the table-header
private boolean flushArea = true;
// this information is set by the RowPainter
private boolean isLastTrait;
/**
* Create a new Cell layout manager.
* @param node table-cell FO for which to create the LM
* @param pgu primary grid unit for the cell
*/
public TableCellLayoutManager(TableCell node, PrimaryGridUnit pgu) {
super(node);
setGeneratesBlockArea(true);
this.primaryGridUnit = pgu;
this.isDescendantOfTableHeader = node.getParent().getParent() instanceof TableHeader
|| node.getParent() instanceof TableHeader;
this.isDescendantOfTableFooter = node.getParent().getParent() instanceof TableFooter
|| node.getParent() instanceof TableFooter;
this.hasRetrieveTableMarker = node.hasRetrieveTableMarker();
}
/** @return the table-cell FO */
public TableCell getTableCell() {
return (TableCell)this.fobj;
}
private boolean isSeparateBorderModel() {
return getTable().isSeparateBorderModel();
}
/**
* @return the table owning this cell
*/
public Table getTable() {
return getTableCell().getTable();
}
public void setHasRepeatedHeader(boolean hasRepeatedHeader) {
this.hasRepeatedHeader = hasRepeatedHeader;
}
/** {@inheritDoc} */
protected int getIPIndents() {
int[] startEndBorderWidths = primaryGridUnit.getStartEndBorderWidths();
startIndent = startEndBorderWidths[0];
endIndent = startEndBorderWidths[1];
if (isSeparateBorderModel()) {
int borderSep = getTable().getBorderSeparation().getLengthPair().getIPD().getLength()
.getValue(this);
startIndent += borderSep / 2;
endIndent += borderSep / 2;
} else {
startIndent /= 2;
endIndent /= 2;
}
startIndent += getTableCell().getCommonBorderPaddingBackground().getPaddingStart(false,
this);
endIndent += getTableCell().getCommonBorderPaddingBackground().getPaddingEnd(false, this);
return startIndent + endIndent;
}
/**
* {@inheritDoc}
*/
public List getNextKnuthElements(LayoutContext context, int alignment) {
MinOptMax stackLimit = context.getStackLimitBP();
referenceIPD = context.getRefIPD();
cellIPD = referenceIPD;
cellIPD -= getIPIndents();
List returnedList;
List contentList = new LinkedList();
List returnList = new LinkedList();
LayoutManager curLM; // currently active LM
LayoutManager prevLM = null; // previously active LM
while ((curLM = getChildLM()) != null) {
LayoutContext childLC = LayoutContext.newInstance();
// curLM is a ?
childLC.setStackLimitBP(context.getStackLimitBP().minus(stackLimit));
childLC.setRefIPD(cellIPD);
// get elements from curLM
returnedList = curLM.getNextKnuthElements(childLC, alignment);
if (childLC.isKeepWithNextPending()) {
log.debug("child LM signals pending keep with next");
}
if (contentList.isEmpty() && childLC.isKeepWithPreviousPending()) {
primaryGridUnit.setKeepWithPrevious(childLC.getKeepWithPreviousPending());
childLC.clearKeepWithPreviousPending();
}
if (prevLM != null
&& !ElementListUtils.endsWithForcedBreak(contentList)) {
// there is a block handled by prevLM
// before the one handled by curLM
addInBetweenBreak(contentList, context, childLC);
}
contentList.addAll(returnedList);
if (returnedList.isEmpty()) {
//Avoid NoSuchElementException below (happens with empty blocks)
continue;
}
if (childLC.isKeepWithNextPending()) {
//Clear and propagate
context.updateKeepWithNextPending(childLC.getKeepWithNextPending());
childLC.clearKeepWithNextPending();
}
prevLM = curLM;
}
primaryGridUnit.setKeepWithNext(context.getKeepWithNextPending());
returnedList = new LinkedList();
if (!contentList.isEmpty()) {
wrapPositionElements(contentList, returnList);
} else {
// In relaxed validation mode, table-cells having no children are authorised.
// Add a zero-width block here to not have to take this special case into
// account later
// Copied from BlockStackingLM
returnList.add(new KnuthBox(0, notifyPos(new Position(this)), true));
}
//Space resolution
SpaceResolver.resolveElementList(returnList);
if (((KnuthElement) returnList.get(0)).isForcedBreak()) {
primaryGridUnit.setBreakBefore(((KnuthPenalty) returnList.get(0)).getBreakClass());
returnList.remove(0);
assert !returnList.isEmpty();
}
final KnuthElement lastItem = (KnuthElement) ListUtil
.getLast(returnList);
if (lastItem.isForcedBreak()) {
KnuthPenalty p = (KnuthPenalty) lastItem;
primaryGridUnit.setBreakAfter(p.getBreakClass());
p.setPenalty(0);
}
setFinished(true);
return returnList;
}
/**
* Set the y offset of this cell.
* This offset is used to set the absolute position of the cell.
*
* @param off the y direction offset
*/
public void setYOffset(int off) {
yoffset = off;
}
/**
* Set the x offset of this cell (usually the same as its parent row).
* This offset is used to determine the absolute position of the cell.
*
* @param off the x offset
*/
public void setXOffset(int off) {
xoffset = off;
}
/**
* Set the content height for this cell. This method is used during
* addAreas() stage.
*
* @param h the height of the contents of this cell
*/
public void setContentHeight(int h) {
usedBPD = h;
}
/**
* Sets the total height of this cell on the current page. That is, the cell's bpd
* plus before and after borders and paddings, plus the table's border-separation.
*
* @param h the height of cell
*/
public void setTotalHeight(int h) {
totalHeight = h;
}
private void clearRetrieveTableMarkerChildNodes(List<LayoutManager> childrenLMs) {
if (childrenLMs == null) {
return;
}
int n = childrenLMs.size();
for (LayoutManager lm : childrenLMs) {
if (lm == null) {
return;
} else if (lm instanceof RetrieveTableMarkerLayoutManager) {
((AbstractLayoutManager) lm).getFObj().clearChildNodes();
} else {
List<LayoutManager> lms = lm.getChildLMs();
clearRetrieveTableMarkerChildNodes(lms);
}
}
}
/**
* Checks whether the associated table cell of this LM is in a table header or footer.
* @return true if descendant of table header or footer
*/
private boolean isDescendantOfTableHeaderOrFooter() {
return (isDescendantOfTableFooter || isDescendantOfTableHeader);
}
private void saveAddAreasArguments(PositionIterator parentIter, LayoutContext layoutContext,
int[] spannedGridRowHeights, int startRow, int endRow, int borderBeforeWhich,
int borderAfterWhich, boolean firstOnPage, boolean lastOnPage, RowPainter painter,
int firstRowHeight) {
// checks for savedAddAreasArguments and isDescendantOfTableHeader were already made but repeat them
if (savedAddAreasArguments) {
return;
}
if (isDescendantOfTableHeader) {
savedAddAreasArguments = true;
savedParentIter = null /* parentIter */;
savedLayoutContext = null /* layoutContext */;
savedSpannedGridRowHeights = spannedGridRowHeights;
savedStartRow = startRow;
savedEndRow = endRow;
savedBorderBeforeWhich = borderBeforeWhich;
savedBorderAfterWhich = borderAfterWhich;
savedFirstOnPage = firstOnPage;
savedLastOnPage = lastOnPage;
savedPainter = painter;
savedFirstRowHeight = firstRowHeight;
TableLayoutManager parentTableLayoutManager = getTableLayoutManager();
parentTableLayoutManager.saveTableHeaderTableCellLayoutManagers(this);
// this saving is done the first time the addArea() is called; since the retrieve-table-markers
// cannot be resolved at this time we do not want to flush the area; the area needs nevertheless
// be built so that space is allocated for it.
flushArea = false;
}
}
private TableLayoutManager getTableLayoutManager() {
LayoutManager parentLM = getParent();
while (!(parentLM instanceof TableLayoutManager)) {
parentLM = parentLM.getParent();
}
TableLayoutManager tlm = (TableLayoutManager) parentLM;
return tlm;
}
/**
* Calls the addAreas() using the original arguments.
*/
protected void repeatAddAreas() {
if (savedAddAreasArguments) {
addAreas(savedParentIter, savedLayoutContext, savedSpannedGridRowHeights, savedStartRow,
savedEndRow, savedBorderBeforeWhich, savedBorderAfterWhich, savedFirstOnPage,
savedLastOnPage, savedPainter, savedFirstRowHeight);
// so that the arguments of the next table fragment header can be saved
savedAddAreasArguments = false;
}
}
/**
* Add the areas for the break points. The cell contains block stacking layout
* managers that add block areas.
*
* <p>In the collapsing-border model, the borders of a cell that spans over several
* rows or columns are drawn separately for each grid unit. Therefore we must know the
* height of each grid row spanned over by the cell. Also, if the cell is broken over
* two pages we must know which spanned grid rows are present on the current page.</p>
*
* @param parentIter the iterator of the break positions
* @param layoutContext the layout context for adding the areas
* @param spannedGridRowHeights in collapsing-border model for a spanning cell, height
* of each spanned grid row
* @param startRow first grid row on the current page spanned over by the cell,
* inclusive
* @param endRow last grid row on the current page spanned over by the cell, inclusive
* @param borderBeforeWhich one of {@link ConditionalBorder#NORMAL},
* {@link ConditionalBorder#LEADING_TRAILING} or {@link ConditionalBorder#REST}
* @param borderAfterWhich one of {@link ConditionalBorder#NORMAL},
* {@link ConditionalBorder#LEADING_TRAILING} or {@link ConditionalBorder#REST}
* @param firstOnPage true if the cell will be the very first one on the page, in
* which case collapsed before borders must be drawn in the outer mode
* @param lastOnPage true if the cell will be the very last one on the page, in which
* case collapsed after borders must be drawn in the outer mode
* @param painter painter
* @param firstRowHeight height of the first row spanned by this cell (may be zero if
* this row is placed on a previous page). Used to calculate the placement of the
* row's background image if any
*/
public void addAreas(PositionIterator parentIter, LayoutContext layoutContext, int[] spannedGridRowHeights,
int startRow, int endRow, int borderBeforeWhich, int borderAfterWhich,
boolean firstOnPage, boolean lastOnPage, RowPainter painter, int firstRowHeight) {
getParentArea(null);
addId();
int borderBeforeWidth = primaryGridUnit.getBeforeBorderWidth(startRow, borderBeforeWhich);
int borderAfterWidth = primaryGridUnit.getAfterBorderWidth(endRow, borderAfterWhich);
CommonBorderPaddingBackground padding = primaryGridUnit.getCell()
.getCommonBorderPaddingBackground();
int paddingRectBPD = totalHeight - borderBeforeWidth - borderAfterWidth;
int cellBPD = paddingRectBPD;
cellBPD -= padding.getPaddingBefore(borderBeforeWhich == ConditionalBorder.REST, this);
cellBPD -= padding.getPaddingAfter(borderAfterWhich == ConditionalBorder.REST, this);
addBackgroundAreas(painter, firstRowHeight, borderBeforeWidth, paddingRectBPD);
if (isSeparateBorderModel()) {
if (!emptyCell || getTableCell().showEmptyCells()) {
if (borderBeforeWidth > 0) {
int halfBorderSepBPD = getTableCell().getTable().getBorderSeparation().getBPD()
.getLength().getValue() / 2;
adjustYOffset(curBlockArea, halfBorderSepBPD);
}
TraitSetter.addBorders(curBlockArea,
getTableCell().getCommonBorderPaddingBackground(),
borderBeforeWidth == 0, borderAfterWidth == 0,
false, false, this);
}
} else {
boolean inFirstColumn = (primaryGridUnit.getColIndex() == 0);
boolean inLastColumn = (primaryGridUnit.getColIndex()
+ getTableCell().getNumberColumnsSpanned() == getTable()
.getNumberOfColumns());
if (!primaryGridUnit.hasSpanning()) {
adjustYOffset(curBlockArea, -borderBeforeWidth);
//Can set the borders directly if there's no span
boolean[] outer = new boolean[] {firstOnPage, lastOnPage, inFirstColumn,
inLastColumn};
TraitSetter.addCollapsingBorders(curBlockArea,
primaryGridUnit.getBorderBefore(borderBeforeWhich),
primaryGridUnit.getBorderAfter(borderAfterWhich),
primaryGridUnit.getBorderStart(),
primaryGridUnit.getBorderEnd(), outer);
} else {
adjustYOffset(curBlockArea, borderBeforeWidth);
Block[][] blocks = new Block[getTableCell().getNumberRowsSpanned()][getTableCell()
.getNumberColumnsSpanned()];
GridUnit[] gridUnits = primaryGridUnit.getRows().get(startRow);
int level = getTableCell().getBidiLevelRecursive();
for (int x = 0; x < getTableCell().getNumberColumnsSpanned(); x++) {
GridUnit gu = gridUnits[x];
BorderInfo border = gu.getBorderBefore(borderBeforeWhich);
int borderWidth = border.getRetainedWidth() / 2;
if (borderWidth > 0) {
addBorder(blocks, startRow, x, Trait.BORDER_BEFORE, border,
firstOnPage, level);
adjustYOffset(blocks[startRow][x], -borderWidth);
adjustBPD(blocks[startRow][x], -borderWidth);
}
}
gridUnits = primaryGridUnit.getRows().get(endRow);
for (int x = 0; x < getTableCell().getNumberColumnsSpanned(); x++) {
GridUnit gu = gridUnits[x];
BorderInfo border = gu.getBorderAfter(borderAfterWhich);
int borderWidth = border.getRetainedWidth() / 2;
if (borderWidth > 0) {
addBorder(blocks, endRow, x, Trait.BORDER_AFTER, border,
lastOnPage, level);
adjustBPD(blocks[endRow][x], -borderWidth);
}
}
for (int y = startRow; y <= endRow; y++) {
gridUnits = primaryGridUnit.getRows().get(y);
BorderInfo border = gridUnits[0].getBorderStart();
int borderWidth = border.getRetainedWidth() / 2;
if (borderWidth > 0) {
if (level == 1) {
addBorder(blocks, y, gridUnits.length - 1, Trait.BORDER_START, border,
inFirstColumn, level);
adjustIPD(blocks[y][gridUnits.length - 1], -borderWidth);
} else {
addBorder(blocks, y, 0, Trait.BORDER_START, border,
inFirstColumn, level);
adjustXOffset(blocks[y][0], borderWidth);
adjustIPD(blocks[y][0], -borderWidth);
}
}
border = gridUnits[gridUnits.length - 1].getBorderEnd();
borderWidth = border.getRetainedWidth() / 2;
if (borderWidth > 0) {
if (level == 1) {
addBorder(blocks, y, 0, Trait.BORDER_END, border,
inLastColumn, level);
adjustXOffset(blocks[y][0], borderWidth);
adjustIPD(blocks[y][0], -borderWidth);
} else {
addBorder(blocks, y, gridUnits.length - 1, Trait.BORDER_END, border,
inLastColumn, level);
adjustIPD(blocks[y][gridUnits.length - 1], -borderWidth);
}
}
}
int dy = yoffset;
for (int y = startRow; y <= endRow; y++) {
int bpd = spannedGridRowHeights[y - startRow];
int dx = xoffset;
for (int x = 0; x < gridUnits.length; x++) {
int ipd = getTable().getColumn(primaryGridUnit.getColIndex() + x)
.getColumnWidth().getValue(getParent());
if (blocks[y][x] != null) {
Block block = blocks[y][x];
adjustYOffset(block, dy);
adjustXOffset(block, dx);
adjustIPD(block, ipd);
adjustBPD(block, bpd);
parentLayoutManager.addChildArea(block);
}
dx += ipd;
}
dy += bpd;
}
}
}
TraitSetter.addPadding(curBlockArea,
padding,
borderBeforeWhich == ConditionalBorder.REST,
borderAfterWhich == ConditionalBorder.REST,
false, false, this);
//Handle display-align
if (usedBPD < cellBPD) {
if (getTableCell().getDisplayAlign() == EN_CENTER) {
Block space = new Block();
space.setChangeBarList(getChangeBarList());
space.setBPD((cellBPD - usedBPD) / 2);
space.setBidiLevel(getTableCell().getBidiLevelRecursive());
curBlockArea.addBlock(space);
} else if (getTableCell().getDisplayAlign() == EN_AFTER) {
Block space = new Block();
space.setChangeBarList(getChangeBarList());
space.setBPD(cellBPD - usedBPD);
space.setBidiLevel(getTableCell().getBidiLevelRecursive());
curBlockArea.addBlock(space);
}
}
if (isDescendantOfTableHeaderOrFooter()) {
if (hasRetrieveTableMarker) {
if (isDescendantOfTableHeader && !savedAddAreasArguments) {
saveAddAreasArguments(parentIter, layoutContext, spannedGridRowHeights, startRow, endRow,
borderBeforeWhich, borderAfterWhich, firstOnPage, lastOnPage, painter,
firstRowHeight);
}
recreateChildrenLMs();
int displayAlign = ((TableCell) this.getFObj()).getDisplayAlign();
TableCellBreaker breaker = new TableCellBreaker(this, cellIPD, displayAlign);
breaker.setDescendantOfTableFooter(isDescendantOfTableHeader);
if (isDescendantOfTableHeader) {
breaker.setRepeatedHeader(hasRepeatedHeader);
} else {
breaker.setRepeatedFooter(layoutContext.treatAsArtifact());
}
breaker.doLayout(usedBPD, false);
// this is needed so the next time the LMs are recreated they look like the originals; this
// is due to the fact that during the doLayout() above the FO tree changes when the
// retrieve-table-markers are resolved
clearRetrieveTableMarkerChildNodes(getChildLMs());
}
}
// if hasRetrieveTableMarker == true the areas were already added when the re-layout was done above
if (!hasRetrieveTableMarker) {
AreaAdditionUtil.addAreas(this, parentIter, layoutContext);
}
// Re-adjust the cell's bpd as it may have been modified by the previous call
// for some reason (?)
curBlockArea.setBPD(cellBPD);
// Add background after we know the BPD
if (!isSeparateBorderModel() || !emptyCell || getTableCell().showEmptyCells()) {
TraitSetter.addBackground(curBlockArea,
getTableCell().getCommonBorderPaddingBackground(), this);
}
if (flushArea) {
flush();
} else {
flushArea = true;
}
curBlockArea = null;
notifyEndOfLayout();
}
/** Adds background areas for the column, body and row, if any. */
private void addBackgroundAreas(RowPainter painter, int firstRowHeight, int borderBeforeWidth,
int paddingRectBPD) {
TableColumn column = getTable().getColumn(primaryGridUnit.getColIndex());
if (column.getCommonBorderPaddingBackground().hasBackground()) {
Block colBackgroundArea = getBackgroundArea(paddingRectBPD, borderBeforeWidth);
((TableLayoutManager) parentLayoutManager).registerColumnBackgroundArea(column,
colBackgroundArea, -startIndent);
}
TablePart body = primaryGridUnit.getTablePart();
if (body.getCommonBorderPaddingBackground().hasBackground()) {
painter.registerPartBackgroundArea(
getBackgroundArea(paddingRectBPD, borderBeforeWidth));
}
TableRow row = primaryGridUnit.getRow();
if (row != null && row.getCommonBorderPaddingBackground().hasBackground()) {
Block rowBackgroundArea = getBackgroundArea(paddingRectBPD, borderBeforeWidth);
((TableLayoutManager) parentLayoutManager).addBackgroundArea(rowBackgroundArea);
TraitSetter.addBackground(rowBackgroundArea, row.getCommonBorderPaddingBackground(),
parentLayoutManager,
-xoffset - startIndent, -borderBeforeWidth,
parentLayoutManager.getContentAreaIPD(), firstRowHeight);
}
}
private void addBorder(Block[][] blocks, int i, int j, Integer side, BorderInfo border,
boolean outer, int level) {
if (blocks[i][j] == null) {
blocks[i][j] = new Block();
blocks[i][j].setChangeBarList(getChangeBarList());
blocks[i][j].addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE);
blocks[i][j].setPositioning(Block.ABSOLUTE);
blocks[i][j].setBidiLevel(level);
}
blocks[i][j].addTrait(side, BorderProps.makeRectangular(border.getStyle(),
border.getRetainedWidth(), border.getColor(),
outer ? BorderProps.Mode.COLLAPSE_OUTER : BorderProps.Mode.COLLAPSE_INNER));
}
private static void adjustXOffset(Block block, int amount) {
block.setXOffset(block.getXOffset() + amount);
}
private static void adjustYOffset(Block block, int amount) {
block.setYOffset(block.getYOffset() + amount);
}
private static void adjustIPD(Block block, int amount) {
block.setIPD(block.getIPD() + amount);
}
private static void adjustBPD(Block block, int amount) {
block.setBPD(block.getBPD() + amount);
}
private Block getBackgroundArea(int bpd, int borderBeforeWidth) {
CommonBorderPaddingBackground padding = getTableCell().getCommonBorderPaddingBackground();
int paddingStart = padding.getPaddingStart(false, this);
int paddingEnd = padding.getPaddingEnd(false, this);
Block block = new Block();
block.setChangeBarList(getChangeBarList());
TraitSetter.setProducerID(block, getTable().getId());
block.setPositioning(Block.ABSOLUTE);
block.setIPD(cellIPD + paddingStart + paddingEnd);
block.setBPD(bpd);
block.setXOffset(xoffset + startIndent - paddingStart);
block.setYOffset(yoffset + borderBeforeWidth);
block.setBidiLevel(getTableCell().getBidiLevelRecursive());
return block;
}
/**
* 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 to get the parent for
* @return the parent area
*/
public Area getParentArea(Area childArea) {
if (curBlockArea == null) {
curBlockArea = new Block();
curBlockArea.setChangeBarList(getChangeBarList());
curBlockArea.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE);
TraitSetter.setProducerID(curBlockArea, getTableCell().getId());
curBlockArea.setPositioning(Block.ABSOLUTE);
curBlockArea.setXOffset(xoffset + startIndent);
curBlockArea.setYOffset(yoffset);
curBlockArea.setIPD(cellIPD);
curBlockArea.setBidiLevel(getTableCell().getBidiLevelRecursive());
/*Area parentArea =*/ parentLayoutManager.getParentArea(curBlockArea);
// Get reference IPD from parentArea
setCurrentArea(curBlockArea); // ??? for generic operations
}
return curBlockArea;
}
/**
* Add the child to the cell block area.
*
* @param childArea the child to add to the cell
*/
public void addChildArea(Area childArea) {
if (curBlockArea != null && childArea instanceof Block) {
curBlockArea.addBlock((Block) childArea);
}
}
/**
* {@inheritDoc}
*/
public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) {
// TODO Auto-generated method stub
return 0;
}
/**
* {@inheritDoc}
*/
public void discardSpace(KnuthGlue spaceGlue) {
// TODO Auto-generated method stub
}
/** {@inheritDoc} */
public Keep getKeepTogether() {
// keep-together does not apply to fo:table-cell
return Keep.KEEP_AUTO;
}
/** {@inheritDoc} */
public Keep getKeepWithNext() {
return Keep.KEEP_AUTO; //TODO FIX ME (table-cell has no keep-with-next!)
}
/** {@inheritDoc} */
public Keep getKeepWithPrevious() {
return Keep.KEEP_AUTO; //TODO FIX ME (table-cell has no keep-with-previous!)
}
// --------- Property Resolution related functions --------- //
/**
* Returns the IPD of the content area
* @return the IPD of the content area
*/
public int getContentAreaIPD() {
return cellIPD;
}
/**
* Returns the BPD of the content area
* @return the BPD of the content area
*/
public int getContentAreaBPD() {
if (curBlockArea != null) {
return curBlockArea.getBPD();
} else {
log.error("getContentAreaBPD called on unknown BPD");
return -1;
}
}
/**
* {@inheritDoc}
*/
public boolean getGeneratesReferenceArea() {
return true;
}
/**
* {@inheritDoc}
*/
public boolean getGeneratesBlockArea() {
return true;
}
private static class TableCellBreaker extends LocalBreaker {
public TableCellBreaker(TableCellLayoutManager lm, int ipd, int displayAlign) {
super(lm, ipd, displayAlign);
}
/**
* {@inheritDoc}
*/
protected void observeElementList(List elementList) {
String elementListID = lm.getParent().getFObj().getId() + "-" + lm.getFObj().getId();
ElementListObserver.observe(elementList, "table-cell", elementListID);
}
}
/**
* Registers the FO's markers on the current PageViewport and parent Table.
*
* @param isStarting boolean indicating whether the markers qualify as 'starting'
* @param isFirst boolean indicating whether the markers qualify as 'first'
* @param isLast boolean indicating whether the markers qualify as 'last'
*/
protected void registerMarkers(boolean isStarting, boolean isFirst, boolean isLast) {
Map<String, Marker> markers = getTableCell().getMarkers();
if (markers != null) {
getCurrentPV().registerMarkers(markers, isStarting, isFirst, isLast && isLastTrait);
if (!isDescendantOfTableHeaderOrFooter()) {
getTableLayoutManager().registerMarkers(markers, isStarting, isFirst, isLast && isLastTrait);
}
}
}
void setLastTrait(boolean isLast) {
isLastTrait = isLast;
}
/** {@inheritDoc} */
public void setParent(LayoutManager lm) {
this.parentLayoutManager = lm;
if (this.hasRetrieveTableMarker) {
this.getTableLayoutManager().flagAsHavingRetrieveTableMarker();
}
}
}