| /* |
| * 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(); |
| } |
| } |
| |
| } |