diff --git a/Makefile b/Makefile index fed964d..6a46dff 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ SHELL ?= /bin/bash endif #JAR_VERSION := $(shell mvn -q -Dexec.executable="echo" -Dexec.args='$${project.version}' --non-recursive exec:exec -DforceStdout) -JAR_VERSION := 2.10 +JAR_VERSION := 2.11 JAR_FILE := mn2pdf-$(JAR_VERSION).jar all: target/$(JAR_FILE) diff --git a/README.adoc b/README.adoc index b4a2031..bd0ca43 100644 --- a/README.adoc +++ b/README.adoc @@ -17,14 +17,14 @@ You will need the Java Development Kit (JDK) version 8, Update 241 (8u241) or hi [source,sh] ---- -java -Xss5m -Xmx2048m -jar target/mn2pdf-2.10.jar --xml-file --xsl-file --pdf-file [--syntax-highlight] +java -Xss5m -Xmx2048m -jar target/mn2pdf-2.11.jar --xml-file --xsl-file --pdf-file [--syntax-highlight] ---- e.g. [source,sh] ---- -java -Xss5m -Xmx2048m -jar target/mn2pdf-2.10.jar --xml-file tests/G.191.xml --xsl-file tests/itu.recommendation.xsl --pdf-file tests/G.191.pdf +java -Xss5m -Xmx2048m -jar target/mn2pdf-2.11.jar --xml-file tests/G.191.xml --xsl-file tests/itu.recommendation.xsl --pdf-file tests/G.191.pdf ---- === PDF encryption features @@ -100,7 +100,7 @@ Update version in `pom.xml`, e.g.: ---- org.metanorma.fop mn2pdf -2.10 +2.11 Metanorma XML to PDF converter ---- @@ -111,8 +111,8 @@ Tag the same version in Git: [source,xml] ---- -git tag v2.10 -git push origin v2.10 +git tag v2.11 +git push origin v2.11 ---- Then the corresponding GitHub release will be automatically created at: diff --git a/pom.xml b/pom.xml index 9cec266..c1f3329 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.metanorma.fop mn2pdf - 2.10 + 2.11 Metanorma XML to PDF converter jar https://www.metanorma.org diff --git a/src/main/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java b/src/main/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java index 8e30a15..43e83ca 100644 --- a/src/main/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java +++ b/src/main/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java @@ -202,7 +202,8 @@ protected LayoutContext makeChildLayoutContext(LayoutContext context) { childLC.setStackLimitBP( context.getStackLimitBP().minus(MinOptMax.getInstance(relDims.bpd))); childLC.setRefIPD(relDims.ipd); - childLC.setWritingMode(getBlockContainerFO().getWritingMode()); + //childLC.setWritingMode(getBlockContainerFO().getWritingMode()); + childLC.setWritingMode(context.getWritingMode()); return childLC; } diff --git a/src/main/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java b/src/main/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java new file mode 100644 index 0000000..6e02cfa --- /dev/null +++ b/src/main/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java @@ -0,0 +1,217 @@ +/* + * 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.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.flow.table.EffRow; +import org.apache.fop.fo.flow.table.GridUnit; +import org.apache.fop.fo.flow.table.PrimaryGridUnit; +import org.apache.fop.fo.flow.table.TableColumn; +import org.apache.fop.fo.flow.table.TableRow; +import org.apache.fop.fo.properties.CommonBorderPaddingBackground; +import org.apache.fop.fo.properties.LengthRangeProperty; +import org.apache.fop.layoutmgr.ElementListObserver; +import org.apache.fop.layoutmgr.LayoutContext; +import org.apache.fop.traits.MinOptMax; +import org.apache.fop.util.BreakUtil; + +class RowGroupLayoutManager { + + private static Log log = LogFactory.getLog(RowGroupLayoutManager.class); + + private static final MinOptMax MAX_STRETCH = MinOptMax.getInstance(0, 0, Integer.MAX_VALUE); + + private EffRow[] rowGroup; + + private TableLayoutManager tableLM; + + private TableStepper tableStepper; + + RowGroupLayoutManager(TableLayoutManager tableLM, EffRow[] rowGroup, + TableStepper tableStepper) { + this.tableLM = tableLM; + this.rowGroup = rowGroup; + this.tableStepper = tableStepper; + } + + public LinkedList getNextKnuthElements(LayoutContext context, int alignment, int bodyType) { + LinkedList returnList = new LinkedList(); + createElementsForRowGroup(context, alignment, bodyType, returnList); + + context.updateKeepWithPreviousPending(rowGroup[0].getKeepWithPrevious()); + context.updateKeepWithNextPending(rowGroup[rowGroup.length - 1].getKeepWithNext()); + + int breakBefore = Constants.EN_AUTO; + TableRow firstRow = rowGroup[0].getTableRow(); + if (firstRow != null) { + breakBefore = firstRow.getBreakBefore(); + } + context.setBreakBefore(BreakUtil.compareBreakClasses(breakBefore, + rowGroup[0].getBreakBefore())); + + int breakAfter = Constants.EN_AUTO; + TableRow lastRow = rowGroup[rowGroup.length - 1].getTableRow(); + if (lastRow != null) { + breakAfter = lastRow.getBreakAfter(); + } + context.setBreakAfter(BreakUtil.compareBreakClasses(breakAfter, + rowGroup[rowGroup.length - 1].getBreakAfter())); + + return returnList; + } + + /** + * Creates Knuth elements for a row group (see TableRowIterator.getNextRowGroup()). + * @param context Active LayoutContext + * @param alignment alignment indicator + * @param bodyType Indicates what kind of body is being processed (BODY, HEADER or FOOTER) + * @param returnList List to received the generated elements + */ + private void createElementsForRowGroup(LayoutContext context, int alignment, + int bodyType, LinkedList returnList) { + log.debug("Handling row group with " + rowGroup.length + " rows..."); + EffRow row; + for (EffRow aRowGroup : rowGroup) { + row = aRowGroup; + for (Object o : row.getGridUnits()) { + GridUnit gu = (GridUnit) o; + if (gu.isPrimary()) { + PrimaryGridUnit primary = gu.getPrimary(); + // TODO a new LM must be created for every new static-content + primary.createCellLM(); + primary.getCellLM().setParent(tableLM); + //Calculate width of cell + int spanWidth = 0; + Iterator colIter = tableLM.getTable().getColumns().listIterator( + primary.getColIndex()); + for (int i = 0, c = primary.getCell().getNumberColumnsSpanned(); i < c; i++) { + spanWidth += ((TableColumn) colIter.next()).getColumnWidth().getValue( + tableLM); + } + LayoutContext childLC = LayoutContext.newInstance(); + childLC.setStackLimitBP(context.getStackLimitBP()); //necessary? + childLC.setRefIPD(spanWidth); + childLC.setWritingMode(context.getWritingMode()); + + //Get the element list for the cell contents + List elems = primary.getCellLM().getNextKnuthElements( + childLC, alignment); + ElementListObserver.observe(elems, "table-cell", primary.getCell().getId()); + primary.setElements(elems); + } + } + } + computeRowHeights(); + List elements = tableStepper.getCombinedKnuthElementsForRowGroup(context, + rowGroup, bodyType); + returnList.addAll(elements); + } + + /** + * Calculate the heights of the rows in the row group, see CSS21, 17.5.3 Table height + * algorithms. + * + * TODO this method will need to be adapted once clarification has been made by the + * W3C regarding whether borders or border-separation must be included or not + */ + private void computeRowHeights() { + log.debug("rowGroup:"); + MinOptMax[] rowHeights = new MinOptMax[rowGroup.length]; + EffRow row; + for (int rgi = 0; rgi < rowGroup.length; rgi++) { + row = rowGroup[rgi]; + // The BPD of the biggest cell in the row +// int maxCellBPD = 0; + MinOptMax explicitRowHeight; + TableRow tableRowFO = rowGroup[rgi].getTableRow(); + if (tableRowFO == null) { + rowHeights[rgi] = MAX_STRETCH; + explicitRowHeight = MAX_STRETCH; + } else { + LengthRangeProperty rowBPD = tableRowFO.getBlockProgressionDimension(); + rowHeights[rgi] = rowBPD.toMinOptMax(tableLM); + explicitRowHeight = rowBPD.toMinOptMax(tableLM); + } + for (Object o : row.getGridUnits()) { + GridUnit gu = (GridUnit) o; + if (!gu.isEmpty() && gu.getColSpanIndex() == 0 && gu.isLastGridUnitRowSpan()) { + PrimaryGridUnit primary = gu.getPrimary(); + int effectiveCellBPD = 0; + LengthRangeProperty cellBPD = primary.getCell().getBlockProgressionDimension(); + if (!cellBPD.getMinimum(tableLM).isAuto()) { + effectiveCellBPD = cellBPD.getMinimum(tableLM).getLength() + .getValue(tableLM); + } + if (!cellBPD.getOptimum(tableLM).isAuto()) { + effectiveCellBPD = cellBPD.getOptimum(tableLM).getLength() + .getValue(tableLM); + } + if (gu.getRowSpanIndex() == 0) { + effectiveCellBPD = Math.max(effectiveCellBPD, explicitRowHeight.getOpt()); + } + effectiveCellBPD = Math.max(effectiveCellBPD, primary.getContentLength()); + int borderWidths = primary.getBeforeAfterBorderWidth(); + int padding = 0; + CommonBorderPaddingBackground cbpb = primary.getCell() + .getCommonBorderPaddingBackground(); + padding += cbpb.getPaddingBefore(false, primary.getCellLM()); + padding += cbpb.getPaddingAfter(false, primary.getCellLM()); + int effRowHeight = effectiveCellBPD + padding + borderWidths; + for (int prev = rgi - 1; prev >= rgi - gu.getRowSpanIndex(); prev--) { + effRowHeight -= rowHeights[prev].getOpt(); + } + if (effRowHeight > rowHeights[rgi].getMin()) { + // This is the new height of the (grid) row + rowHeights[rgi] = rowHeights[rgi].extendMinimum(effRowHeight); + } + } + } + + row.setHeight(rowHeights[rgi]); + row.setExplicitHeight(explicitRowHeight); + // TODO re-enable and improve after clarification + //See http://markmail.org/message/h25ycwwu7qglr4k4 +// if (maxCellBPD > row.getExplicitHeight().max) { +//old: +// log.warn(FONode.decorateWithContextInfo( +// "The contents of row " + (row.getIndex() + 1) +// + " are taller than they should be (there is a" +// + " block-progression-dimension or height constraint +// + " on the indicated row)." +// + " Due to its contents the row grows" +// + " to " + maxCellBPD + " millipoints, but the row shouldn't get" +// + " any taller than " + row.getExplicitHeight() + " millipoints.", +// row.getTableRow())); +//new (with events): +// BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Factory.create( +// tableRow.getUserAgent().getEventBroadcaster()); +// eventProducer.rowTooTall(this, row.getIndex() + 1, +// maxCellBPD, row.getExplicitHeight().max, tableRow.getLocator()); +// } + } + } +} diff --git a/src/main/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java b/src/main/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java new file mode 100644 index 0000000..682313a --- /dev/null +++ b/src/main/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java @@ -0,0 +1,826 @@ +/* + * 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(); + childLC.setWritingMode(context.getWritingMode()); + + // 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 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 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. + * + *

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.

+ * + * @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 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(); + } + } + +} diff --git a/src/main/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java b/src/main/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java new file mode 100644 index 0000000..868cc2d --- /dev/null +++ b/src/main/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java @@ -0,0 +1,693 @@ +/* + * 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.ArrayList; +import java.util.Collections; +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.datatypes.LengthBase; +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.FObj; +import org.apache.fop.fo.flow.AbstractGraphics; +import org.apache.fop.fo.flow.Marker; +import org.apache.fop.fo.flow.Markers; +import org.apache.fop.fo.flow.RetrieveTableMarker; +import org.apache.fop.fo.flow.table.Table; +import org.apache.fop.fo.flow.table.TableColumn; +import org.apache.fop.fo.properties.CommonBorderPaddingBackground; +import org.apache.fop.fo.properties.KeepProperty; +import org.apache.fop.layoutmgr.BlockLevelEventProducer; +import org.apache.fop.layoutmgr.BreakElement; +import org.apache.fop.layoutmgr.BreakOpportunity; +import org.apache.fop.layoutmgr.KnuthElement; +import org.apache.fop.layoutmgr.KnuthGlue; +import org.apache.fop.layoutmgr.LayoutContext; +import org.apache.fop.layoutmgr.LeafPosition; +import org.apache.fop.layoutmgr.ListElement; +import org.apache.fop.layoutmgr.Position; +import org.apache.fop.layoutmgr.PositionIterator; +import org.apache.fop.layoutmgr.SpacedBorderedPaddedBlockLayoutManager; +import org.apache.fop.layoutmgr.TraitSetter; +import org.apache.fop.traits.MinOptMax; +import org.apache.fop.traits.SpaceVal; +import org.apache.fop.util.BreakUtil; + +/** + * LayoutManager for a table FO. + * A table consists of columns, table header, table footer and multiple + * table bodies. + * The header, footer and body add the areas created from the table cells. + * The table then creates areas for the columns, bodies and rows + * the render background. + */ +public class TableLayoutManager extends SpacedBorderedPaddedBlockLayoutManager + implements BreakOpportunity { + + /** + * logging instance + */ + private static Log log = LogFactory.getLog(TableLayoutManager.class); + + private TableContentLayoutManager contentLM; + private ColumnSetup columns; + + private Block curBlockArea; + + private double tableUnit; + private double oldTableUnit; + private boolean autoLayout = true; + + private int halfBorderSeparationBPD; + private int halfBorderSeparationIPD; + + /** See {@link TableLayoutManager#registerColumnBackgroundArea(TableColumn, Block, int)}. */ + private List columnBackgroundAreas; + + private Position auxiliaryPosition; + + // this holds a possible list of TCLMs that needed to have their addAreas() repeated + private List savedTCLMs; + private boolean areAllTCLMsSaved; + + private Markers tableMarkers; + private Markers tableFragmentMarkers; + + private boolean hasRetrieveTableMarker; + + private boolean repeatedHeader; + + private List> headerFootnotes = Collections.emptyList(); + + private List> footerFootnotes = Collections.emptyList(); + + /** + * Temporary holder of column background informations for a table-cell's area. + * + * @see TableLayoutManager#registerColumnBackgroundArea(TableColumn, Block, int) + */ + private static final class ColumnBackgroundInfo { + private TableColumn column; + private Block backgroundArea; + private int xShift; + + private ColumnBackgroundInfo(TableColumn column, Block backgroundArea, int xShift) { + this.column = column; + this.backgroundArea = backgroundArea; + this.xShift = xShift; + } + } + + /** + * Create a new table layout manager. + * @param node the table FO + */ + public TableLayoutManager(Table node) { + super(node); + this.columns = new ColumnSetup(node); + } + + + @Override + protected CommonBorderPaddingBackground getCommonBorderPaddingBackground() { + return getTable().getCommonBorderPaddingBackground(); + } + + + /** @return the table FO */ + public Table getTable() { + return (Table)this.fobj; + } + + /** + * @return the column setup for this table. + */ + public ColumnSetup getColumns() { + return this.columns; + } + + /** {@inheritDoc} */ + public void initialize() { + foSpaceBefore = new SpaceVal( + getTable().getCommonMarginBlock().spaceBefore, this).getSpace(); + foSpaceAfter = new SpaceVal( + getTable().getCommonMarginBlock().spaceAfter, this).getSpace(); + startIndent = getTable().getCommonMarginBlock().startIndent.getValue(this); + endIndent = getTable().getCommonMarginBlock().endIndent.getValue(this); + + if (getTable().isSeparateBorderModel()) { + this.halfBorderSeparationBPD = getTable().getBorderSeparation().getBPD().getLength() + .getValue(this) / 2; + this.halfBorderSeparationIPD = getTable().getBorderSeparation().getIPD().getLength() + .getValue(this) / 2; + } else { + this.halfBorderSeparationBPD = 0; + this.halfBorderSeparationIPD = 0; + } + + if (!getTable().isAutoLayout() + && getTable().getInlineProgressionDimension().getOptimum(this).getEnum() + != EN_AUTO) { + autoLayout = false; + } + } + + private void resetSpaces() { + this.discardBorderBefore = false; + this.discardBorderAfter = false; + this.discardPaddingBefore = false; + this.discardPaddingAfter = false; + this.effSpaceBefore = null; + this.effSpaceAfter = null; + } + + /** + * @return half the value of border-separation.block-progression-dimension, or 0 if + * border-collapse="collapse". + */ + public int getHalfBorderSeparationBPD() { + return halfBorderSeparationBPD; + } + + /** + * @return half the value of border-separation.inline-progression-dimension, or 0 if + * border-collapse="collapse". + */ + public int getHalfBorderSeparationIPD() { + return halfBorderSeparationIPD; + } + + /** {@inheritDoc} */ + public List getNextKnuthElements(LayoutContext context, int alignment) { + + List returnList = new LinkedList(); + + /* + * Compute the IPD and adjust it if necessary (overconstrained) + */ + referenceIPD = context.getRefIPD(); + if (getTable().getInlineProgressionDimension().getOptimum(this).getEnum() != EN_AUTO) { + int contentIPD = getTable().getInlineProgressionDimension().getOptimum(this) + .getLength().getValue(this); + updateContentAreaIPDwithOverconstrainedAdjust(contentIPD); + } else { + if (!getTable().isAutoLayout()) { + BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( + getTable().getUserAgent().getEventBroadcaster()); + eventProducer.tableFixedAutoWidthNotSupported(this, getTable().getLocator()); + } + updateContentAreaIPDwithOverconstrainedAdjust(); + } + int sumOfColumns = columns.getSumOfColumnWidths(this); + if (!autoLayout && sumOfColumns > getContentAreaIPD()) { + log.debug(FONode.decorateWithContextInfo( + "The sum of all column widths is larger than the specified table width.", + getTable())); + updateContentAreaIPDwithOverconstrainedAdjust(sumOfColumns); + } + int availableIPD = referenceIPD - getIPIndents(); + if (getContentAreaIPD() > availableIPD) { + BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( + getTable().getUserAgent().getEventBroadcaster()); + eventProducer.objectTooWide(this, getTable().getName(), + getContentAreaIPD(), context.getRefIPD(), + getTable().getLocator()); + } + + /* initialize unit to determine computed values + * for proportional-column-width() + */ + if (tableUnit == 0.0) { + tableUnit = columns.computeTableUnit(this); + if (oldTableUnit > tableUnit && supportResize(fobj)) { + tableUnit = oldTableUnit; + } + } + + if (!firstVisibleMarkServed) { + addKnuthElementsForSpaceBefore(returnList, alignment); + } + + if (getTable().isSeparateBorderModel()) { + addKnuthElementsForBorderPaddingBefore(returnList, !firstVisibleMarkServed); + firstVisibleMarkServed = true; + // Border and padding to be repeated at each break + // This must be done only in the separate-border model, as in collapsing + // tables have no padding and borders are determined at the cell level + addPendingMarks(context); + } + + + // Elements for the table-header/footer/body + List contentKnuthElements; + contentLM = new TableContentLayoutManager(this); + LayoutContext childLC = LayoutContext.newInstance(); + /* + childLC.setStackLimit( + MinOptMax.subtract(context.getStackLimit(), + stackSize));*/ + childLC.setRefIPD(context.getRefIPD()); + childLC.copyPendingMarksFrom(context); + childLC.setWritingMode(context.getWritingMode()); + + contentKnuthElements = contentLM.getNextKnuthElements(childLC, alignment); + //Set index values on elements coming from the content LM + for (Object contentKnuthElement : contentKnuthElements) { + ListElement el = (ListElement) contentKnuthElement; + notifyPos(el.getPosition()); + } + log.debug(contentKnuthElements); + wrapPositionElements(contentKnuthElements, returnList); + + context.updateKeepWithPreviousPending(getKeepWithPrevious()); + context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending()); + + context.updateKeepWithNextPending(getKeepWithNext()); + context.updateKeepWithNextPending(childLC.getKeepWithNextPending()); + + if (getTable().isSeparateBorderModel()) { + addKnuthElementsForBorderPaddingAfter(returnList, true); + } + addKnuthElementsForSpaceAfter(returnList, alignment); + + if (!context.suppressBreakBefore()) { + //addKnuthElementsForBreakBefore(returnList, context); + int breakBefore = BreakUtil.compareBreakClasses(getTable().getBreakBefore(), + childLC.getBreakBefore()); + if (breakBefore != Constants.EN_AUTO) { + returnList.add(0, new BreakElement(new LeafPosition(getParent(), 0), 0, + -KnuthElement.INFINITE, breakBefore, context)); + } + } + + //addKnuthElementsForBreakAfter(returnList, context); + int breakAfter = BreakUtil.compareBreakClasses(getTable().getBreakAfter(), + childLC.getBreakAfter()); + if (breakAfter != Constants.EN_AUTO) { + returnList.add(new BreakElement(new LeafPosition(getParent(), 0), + 0, -KnuthElement.INFINITE, breakAfter, context)); + } + + setFinished(true); + resetSpaces(); + return returnList; + } + + private boolean supportResize(FONode node) { + if (node instanceof AbstractGraphics || contentLM.bodyIter.rowIndex == 1) { + return false; + } + FONode.FONodeIterator iterator = node.getChildNodes(); + while (iterator != null && iterator.hasNext()) { + FONode x = iterator.next(); + if (!supportResize(x)) { + return false; + } + } + return true; + } + + /** {@inheritDoc} */ + public Position getAuxiliaryPosition() { + /* + * Redefined to return a LeafPosition instead of a NonLeafPosition. The + * SpaceResolver.SpaceHandlingBreakPosition constructors unwraps all + * NonLeafPositions, which can lead to a NPE when a break in a table occurs at a + * page with different ipd. + */ + if (auxiliaryPosition == null) { + auxiliaryPosition = new LeafPosition(this, 0); + } + return auxiliaryPosition; + } + + /** + * Registers the given area, that will be used to render the part of column background + * covered by a table-cell. If percentages are used to place the background image, the + * final bpd of the (fraction of) table that will be rendered on the current page must + * be known. The traits can't then be set when the areas for the cell are created + * since at that moment this bpd is yet unknown. So they will instead be set in + * TableLM's {@link #addAreas(PositionIterator, LayoutContext)} method. + * + * @param column the table-column element from which the cell gets background + * informations + * @param backgroundArea the block of the cell's dimensions that will hold the column + * background + * @param xShift additional amount by which the image must be shifted to be correctly + * placed (to counterbalance the cell's start border) + */ + void registerColumnBackgroundArea(TableColumn column, Block backgroundArea, int xShift) { + addBackgroundArea(backgroundArea); + if (columnBackgroundAreas == null) { + columnBackgroundAreas = new ArrayList(); + } + columnBackgroundAreas.add(new ColumnBackgroundInfo(column, backgroundArea, xShift)); + } + + /** + * The table area is a reference area that contains areas for + * columns, bodies, rows and the contents are in cells. + * + * @param parentIter the position iterator + * @param layoutContext the layout context for adding areas + */ + public void addAreas(PositionIterator parentIter, + LayoutContext layoutContext) { + getParentArea(null); + addId(); + + // add space before, in order to implement display-align = "center" or "after" + if (layoutContext.getSpaceBefore() != 0) { + addBlockSpacing(0.0, MinOptMax.getInstance(layoutContext.getSpaceBefore())); + } + + int startXOffset = getTable().getCommonMarginBlock().startIndent.getValue(this); + + // add column, body then row areas + + // BPD of the table, i.e., height of its content; table's borders and paddings not counted + int tableHeight = 0; + //Body childLM; + LayoutContext lc = LayoutContext.offspringOf(layoutContext); + + + lc.setRefIPD(getContentAreaIPD()); + contentLM.setStartXOffset(startXOffset); + contentLM.addAreas(parentIter, lc); + + if (fobj.getUserAgent().isTableBorderOverpaint()) { + new OverPaintBorders(curBlockArea); + } + + tableHeight += contentLM.getUsedBPD(); + + curBlockArea.setBPD(tableHeight); + + if (columnBackgroundAreas != null) { + for (Object columnBackgroundArea : columnBackgroundAreas) { + ColumnBackgroundInfo b = (ColumnBackgroundInfo) columnBackgroundArea; + TraitSetter.addBackground(b.backgroundArea, + b.column.getCommonBorderPaddingBackground(), this, + b.xShift, -b.backgroundArea.getYOffset(), + b.column.getColumnWidth().getValue(this), tableHeight); + } + columnBackgroundAreas.clear(); + } + + if (getTable().isSeparateBorderModel()) { + TraitSetter.addBorders(curBlockArea, + getTable().getCommonBorderPaddingBackground(), + discardBorderBefore, discardBorderAfter, false, false, this); + TraitSetter.addPadding(curBlockArea, + getTable().getCommonBorderPaddingBackground(), + discardPaddingBefore, discardPaddingAfter, false, false, this); + } + TraitSetter.addBackground(curBlockArea, + getTable().getCommonBorderPaddingBackground(), + this); + TraitSetter.addMargins(curBlockArea, + getTable().getCommonBorderPaddingBackground(), + startIndent, endIndent, + this); + TraitSetter.addBreaks(curBlockArea, + getTable().getBreakBefore(), getTable().getBreakAfter()); + TraitSetter.addSpaceBeforeAfter(curBlockArea, layoutContext.getSpaceAdjust(), + effSpaceBefore, effSpaceAfter); + + flush(); + + resetSpaces(); + curBlockArea = null; + + notifyEndOfLayout(); + } + + /** + * 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 area of the child + */ + public Area getParentArea(Area childArea) { + if (curBlockArea == null) { + curBlockArea = new Block(); + curBlockArea.setChangeBarList(getChangeBarList()); + + // Set up dimensions + // Must get dimensions from parent area + /*Area parentArea =*/ parentLayoutManager.getParentArea(curBlockArea); + + TraitSetter.setProducerID(curBlockArea, getTable().getId()); + + curBlockArea.setIPD(getContentAreaIPD()); + + setCurrentArea(curBlockArea); + } + return curBlockArea; + } + + /** + * Add the child area to this layout manager. + * + * @param childArea the child area to add + */ + public void addChildArea(Area childArea) { + if (curBlockArea != null) { + curBlockArea.addBlock((Block) childArea); + } + } + + /** + * Adds the given area to this layout manager's area, without updating the used bpd. + * + * @param background an area + */ + void addBackgroundArea(Block background) { + curBlockArea.addChildArea(background); + } + + /** {@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 KeepProperty getKeepTogetherProperty() { + return getTable().getKeepTogether(); + } + + /** {@inheritDoc} */ + public KeepProperty getKeepWithPreviousProperty() { + return getTable().getKeepWithPrevious(); + } + + /** {@inheritDoc} */ + public KeepProperty getKeepWithNextProperty() { + return getTable().getKeepWithNext(); + } + + // --------- Property Resolution related functions --------- // + + /** + * {@inheritDoc} + */ + public int getBaseLength(int lengthBase, FObj fobj) { + // Special handler for TableColumn width specifications + if (fobj instanceof TableColumn && fobj.getParent() == getFObj()) { + switch (lengthBase) { + case LengthBase.CONTAINING_BLOCK_WIDTH: + return getContentAreaIPD(); + case LengthBase.TABLE_UNITS: + return (int) this.tableUnit; + default: + log.error("Unknown base type for LengthBase."); + return 0; + } + } else { + switch (lengthBase) { + case LengthBase.TABLE_UNITS: + return (int) this.tableUnit; + default: + return super.getBaseLength(lengthBase, fobj); + } + } + } + + /** {@inheritDoc} */ + public void reset() { + super.reset(); + curBlockArea = null; + oldTableUnit = tableUnit; + tableUnit = 0.0; + } + + /** + * Saves a TableCellLayoutManager for later use. + * + * @param tclm a TableCellLayoutManager that has a RetrieveTableMarker + */ + protected void saveTableHeaderTableCellLayoutManagers(TableCellLayoutManager tclm) { + if (savedTCLMs == null) { + savedTCLMs = new ArrayList(); + } + if (!areAllTCLMsSaved) { + savedTCLMs.add(tclm); + } + } + + /** + * Calls addAreas() for each of the saved TableCellLayoutManagers. + */ + protected void repeatAddAreasForSavedTableHeaderTableCellLayoutManagers() { + if (savedTCLMs == null) { + return; + } + // if we get to this stage then we are at the footer of the table fragment; this means that no more + // different TCLM need to be saved (we already have all); we flag the list as being complete then + areAllTCLMsSaved = true; + for (TableCellLayoutManager tclm : savedTCLMs) { + if (this.repeatedHeader) { + tclm.setHasRepeatedHeader(true); + } + tclm.repeatAddAreas(); + } + } + + /** + * Resolves a RetrieveTableMarker by finding a qualifying Marker to which it is bound to. + * @param rtm the RetrieveTableMarker to be resolved + * @return a bound RetrieveTableMarker instance or null if no qualifying Marker found + */ + public RetrieveTableMarker resolveRetrieveTableMarker(RetrieveTableMarker rtm) { + String name = rtm.getRetrieveClassName(); + int originalPosition = rtm.getPosition(); + boolean changedPosition = false; + + Marker mark = null; + // try the primary retrieve scope area, which is the same as table-fragment + mark = (tableFragmentMarkers == null) ? null : tableFragmentMarkers.resolve(rtm); + if (mark == null && rtm.getBoundary() != Constants.EN_TABLE_FRAGMENT) { + rtm.changePositionTo(Constants.EN_LAST_ENDING); + changedPosition = true; + // try the page scope area + mark = getCurrentPV().resolveMarker(rtm); + if (mark == null && rtm.getBoundary() != Constants.EN_PAGE) { + // try the table scope area + mark = (tableMarkers == null) ? null : tableMarkers.resolve(rtm); + } + } + if (changedPosition) { + // so that the next time it is called looks unchanged + rtm.changePositionTo(originalPosition); + } + if (mark == null) { + log.debug("found no marker with name: " + name); + return null; + } else { + rtm.bindMarker(mark); + return rtm; + } + } + + /** + * Register the markers for this table. + * + * @param marks the map of markers to add + * @param starting if the area being added is starting or ending + * @param isfirst if the area being added has is-first trait + * @param islast if the area being added has is-last trait + */ + public void registerMarkers(Map marks, boolean starting, boolean isfirst, + boolean islast) { + if (tableMarkers == null) { + tableMarkers = new Markers(); + } + tableMarkers.register(marks, starting, isfirst, islast); + if (tableFragmentMarkers == null) { + tableFragmentMarkers = new Markers(); + } + tableFragmentMarkers.register(marks, starting, isfirst, islast); + } + + /** + * Clears the list of markers in the current table fragment. Should be called just before starting a new + * header (that belongs to the next table fragment). + */ + protected void clearTableFragmentMarkers() { + tableFragmentMarkers = null; + } + + public void flagAsHavingRetrieveTableMarker() { + hasRetrieveTableMarker = true; + } + + protected void possiblyRegisterMarkersForTables(Map markers, boolean isStarting, + boolean isFirst, boolean isLast) { + // note: if we allow table-footer after a table-body this check should not be made and the markers + // should be registered regardless because the retrieval may be done only in the footer + if (hasRetrieveTableMarker) { + registerMarkers(markers, isStarting, isFirst, isLast); + } + super.possiblyRegisterMarkersForTables(markers, isStarting, isFirst, isLast); + } + + void setHeaderFootnotes(List> footnotes) { + this.headerFootnotes = footnotes; + } + + List> getHeaderFootnotes() { + return headerFootnotes; + } + + void setFooterFootnotes(List> footnotes) { + this.footerFootnotes = footnotes; + } + + public void setRepeateHeader(boolean repeateHeader) { + this.repeatedHeader = repeateHeader; + } + + List> getFooterFootnotes() { + return footerFootnotes; + } + +}