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;
public class TableCellLayoutManager extends BlockStackingLayoutManager {
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 ;
private boolean ;
private boolean hasRetrieveTableMarker;
private boolean ;
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;
private boolean flushArea = true;
private boolean isLastTrait;
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();
}
public TableCell getTableCell() {
return (TableCell)this.fobj;
}
private boolean isSeparateBorderModel() {
return getTable().isSeparateBorderModel();
}
public Table getTable() {
return getTableCell().getTable();
}
public void (boolean hasRepeatedHeader) {
this.hasRepeatedHeader = hasRepeatedHeader;
}
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;
}
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;
LayoutManager prevLM = null;
while ((curLM = getChildLM()) != null) {
LayoutContext childLC = LayoutContext.newInstance();
childLC.setStackLimitBP(context.getStackLimitBP().minus(stackLimit));
childLC.setRefIPD(cellIPD);
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)) {
addInBetweenBreak(contentList, context, childLC);
}
contentList.addAll(returnedList);
if (returnedList.isEmpty()) {
continue;
}
if (childLC.isKeepWithNextPending()) {
context.updateKeepWithNextPending(childLC.getKeepWithNextPending());
childLC.clearKeepWithNextPending();
}
prevLM = curLM;
}
primaryGridUnit.setKeepWithNext(context.getKeepWithNextPending());
returnedList = new LinkedList();
if (!contentList.isEmpty()) {
wrapPositionElements(contentList, returnList);
} else {
returnList.add(new KnuthBox(0, notifyPos(new Position(this)), true));
}
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;
}
public void setYOffset(int off) {
yoffset = off;
}
public void setXOffset(int off) {
xoffset = off;
}
public void setContentHeight(int h) {
usedBPD = h;
}
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);
}
}
}
private boolean () {
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) {
if (savedAddAreasArguments) {
return;
}
if (isDescendantOfTableHeader) {
savedAddAreasArguments = true;
savedParentIter = null ;
savedLayoutContext = null ;
savedSpannedGridRowHeights = spannedGridRowHeights;
savedStartRow = startRow;
savedEndRow = endRow;
savedBorderBeforeWhich = borderBeforeWhich;
savedBorderAfterWhich = borderAfterWhich;
savedFirstOnPage = firstOnPage;
savedLastOnPage = lastOnPage;
savedPainter = painter;
savedFirstRowHeight = firstRowHeight;
TableLayoutManager parentTableLayoutManager = getTableLayoutManager();
parentTableLayoutManager.saveTableHeaderTableCellLayoutManagers(this);
flushArea = false;
}
}
private TableLayoutManager getTableLayoutManager() {
LayoutManager parentLM = getParent();
while (!(parentLM instanceof TableLayoutManager)) {
parentLM = parentLM.getParent();
}
TableLayoutManager tlm = (TableLayoutManager) parentLM;
return tlm;
}
protected void repeatAddAreas() {
if (savedAddAreasArguments) {
addAreas(savedParentIter, savedLayoutContext, savedSpannedGridRowHeights, savedStartRow,
savedEndRow, savedBorderBeforeWhich, savedBorderAfterWhich, savedFirstOnPage,
savedLastOnPage, savedPainter, savedFirstRowHeight);
savedAddAreasArguments = false;
}
}
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);
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);
if (usedBPD < cellBPD) {
if (getTableCell().getDisplayAlign() == EN_CENTER) {
Block space = new Block();
space.setBPD((cellBPD - usedBPD) / 2);
space.setBidiLevel(getTableCell().getBidiLevelRecursive());
curBlockArea.addBlock(space);
} else if (getTableCell().getDisplayAlign() == EN_AFTER) {
Block space = new Block();
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);
clearRetrieveTableMarkerChildNodes(getChildLMs());
}
}
if (!hasRetrieveTableMarker) {
AreaAdditionUtil.addAreas(this, parentIter, layoutContext);
}
curBlockArea.setBPD(cellBPD);
if (!isSeparateBorderModel() || !emptyCell || getTableCell().showEmptyCells()) {
TraitSetter.addBackground(curBlockArea,
getTableCell().getCommonBorderPaddingBackground(), this);
}
if (flushArea) {
flush();
} else {
flushArea = true;
}
curBlockArea = null;
notifyEndOfLayout();
}
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].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();
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;
}
public Area getParentArea(Area childArea) {
if (curBlockArea == null) {
curBlockArea = new Block();
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());
parentLayoutManager.getParentArea(curBlockArea);
setCurrentArea(curBlockArea);
}
return curBlockArea;
}
public void addChildArea(Area childArea) {
if (curBlockArea != null) {
curBlockArea.addBlock((Block) childArea);
}
}
public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) {
return 0;
}
public void discardSpace(KnuthGlue spaceGlue) {
}
public Keep getKeepTogether() {
return Keep.KEEP_AUTO;
}
public Keep getKeepWithNext() {
return Keep.KEEP_AUTO;
}
public Keep getKeepWithPrevious() {
return Keep.KEEP_AUTO;
}
public int getContentAreaIPD() {
return cellIPD;
}
public int getContentAreaBPD() {
if (curBlockArea != null) {
return curBlockArea.getBPD();
} else {
log.error("getContentAreaBPD called on unknown BPD");
return -1;
}
}
public boolean getGeneratesReferenceArea() {
return true;
}
public boolean getGeneratesBlockArea() {
return true;
}
private static class TableCellBreaker extends LocalBreaker {
public TableCellBreaker(TableCellLayoutManager lm, int ipd, int displayAlign) {
super(lm, ipd, displayAlign);
}
protected void observeElementList(List elementList) {
String elementListID = lm.getParent().getFObj().getId() + "-" + lm.getFObj().getId();
ElementListObserver.observe(elementList, "table-cell", elementListID);
}
}
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;
}
public void setParent(LayoutManager lm) {
this.parentLayoutManager = lm;
if (this.hasRetrieveTableMarker) {
this.getTableLayoutManager().flagAsHavingRetrieveTableMarker();
}
}
}