diff --git a/fop-core/src/main/java/org/apache/fop/fo/flow/table/EffRow.java b/fop-core/src/main/java/org/apache/fop/fo/flow/table/EffRow.java index 450aaec14d8..6383fca96c7 100644 --- a/fop-core/src/main/java/org/apache/fop/fo/flow/table/EffRow.java +++ b/fop-core/src/main/java/org/apache/fop/fo/flow/table/EffRow.java @@ -38,7 +38,7 @@ public class EffRow { /** Indicates that the row is the last in a table-body */ public static final int LAST_IN_PART = GridUnit.LAST_IN_PART; - private List gridUnits = new java.util.ArrayList(); + private List gridUnits = new java.util.ArrayList(); private int index; /** One of HEADER, FOOTER, BODY */ private int bodyType; @@ -51,12 +51,12 @@ public class EffRow { * @param bodyType type of body (one of HEADER, FOOTER, BODY as found on TableRowIterator) * @param gridUnits the grid units this row is made of */ - public EffRow(int index, int bodyType, List gridUnits) { + public EffRow(int index, int bodyType, List gridUnits) { this.index = index; this.bodyType = bodyType; this.gridUnits = gridUnits; // TODO this is ugly, but we may eventually be able to do without that index - for (Object gu : gridUnits) { + for (GridUnit gu : gridUnits) { if (gu instanceof PrimaryGridUnit) { ((PrimaryGridUnit) gu).setRowIndex(index); } @@ -116,7 +116,7 @@ public void setExplicitHeight(MinOptMax mom) { } /** @return the list of GridUnits for this EffRow */ - public List getGridUnits() { + public List getGridUnits() { return gridUnits; } @@ -126,7 +126,7 @@ public List getGridUnits() { * @return the requested grid unit. */ public GridUnit getGridUnit(int column) { - return (GridUnit)gridUnits.get(column); + return gridUnits.get(column); } /** @@ -138,7 +138,7 @@ public GridUnit getGridUnit(int column) { */ public GridUnit safelyGetGridUnit(int column) { if (column < gridUnits.size()) { - return (GridUnit)gridUnits.get(column); + return gridUnits.get(column); } else { return null; } @@ -173,8 +173,7 @@ public Keep getKeepWithPrevious() { if (row != null) { keep = Keep.getKeep(row.getKeepWithPrevious()); } - for (Object gridUnit : gridUnits) { - GridUnit gu = (GridUnit) gridUnit; + for (GridUnit gu : gridUnits) { if (gu.isPrimary()) { keep = keep.compare(gu.getPrimary().getKeepWithPrevious()); } @@ -194,8 +193,7 @@ public Keep getKeepWithNext() { if (row != null) { keep = Keep.getKeep(row.getKeepWithNext()); } - for (Object gridUnit : gridUnits) { - GridUnit gu = (GridUnit) gridUnit; + for (GridUnit gu : gridUnits) { if (!gu.isEmpty() && gu.getColSpanIndex() == 0 && gu.isLastGridUnitRowSpan()) { keep = keep.compare(gu.getPrimary().getKeepWithNext()); } @@ -231,8 +229,7 @@ public Keep getKeepTogether() { */ public int getBreakBefore() { int breakBefore = Constants.EN_AUTO; - for (Object gridUnit : gridUnits) { - GridUnit gu = (GridUnit) gridUnit; + for (GridUnit gu : gridUnits) { if (gu.isPrimary()) { breakBefore = BreakUtil.compareBreakClasses(breakBefore, gu.getPrimary().getBreakBefore()); @@ -255,8 +252,7 @@ public int getBreakBefore() { */ public int getBreakAfter() { int breakAfter = Constants.EN_AUTO; - for (Object gridUnit : gridUnits) { - GridUnit gu = (GridUnit) gridUnit; + for (GridUnit gu : gridUnits) { if (!gu.isEmpty() && gu.getColSpanIndex() == 0 && gu.isLastGridUnitRowSpan()) { breakAfter = BreakUtil.compareBreakClasses(breakAfter, gu.getPrimary().getBreakAfter()); @@ -267,7 +263,7 @@ public int getBreakAfter() { /** {@inheritDoc} */ public String toString() { - StringBuffer sb = new StringBuffer("EffRow {"); + StringBuilder sb = new StringBuilder("EffRow {"); sb.append(index); if (getBodyType() == TableRowIterator.BODY) { sb.append(" in body"); diff --git a/fop-core/src/main/java/org/apache/fop/fo/flow/table/PrimaryGridUnit.java b/fop-core/src/main/java/org/apache/fop/fo/flow/table/PrimaryGridUnit.java index fd490dd214e..8b5eb3c6af8 100644 --- a/fop-core/src/main/java/org/apache/fop/fo/flow/table/PrimaryGridUnit.java +++ b/fop-core/src/main/java/org/apache/fop/fo/flow/table/PrimaryGridUnit.java @@ -240,11 +240,9 @@ public int getAfterBorderWidth(int which) { return getAfterBorderWidth(getCell().getNumberRowsSpanned() - 1, which); } - /** @return the length of the cell content */ + /** @return the length of the cell content, auto-layout disallows caching */ public int getContentLength() { - if (contentLength < 0) { - contentLength = ElementListUtils.calcContentLength(elements); - } + contentLength = ElementListUtils.calcContentLength(elements); return contentLength; } diff --git a/fop-core/src/main/java/org/apache/fop/fo/flow/table/Table.java b/fop-core/src/main/java/org/apache/fop/fo/flow/table/Table.java index 5fa17cf1739..d9dc103eab4 100644 --- a/fop-core/src/main/java/org/apache/fop/fo/flow/table/Table.java +++ b/fop-core/src/main/java/org/apache/fop/fo/flow/table/Table.java @@ -72,6 +72,11 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder, Break private int tableOmitFooterAtBreak; private int tableOmitHeaderAtBreak; private WritingModeTraits writingModeTraits; + + // Used to determine when a table has a width attribute + // For example + private Length width; + // Unused but valid items, commented out for performance: // private CommonAural commonAural; // private CommonRelativePosition commonRelativePosition; @@ -141,6 +146,7 @@ public void bind(PropertyList pList) throws FOPException { writingModeTraits = new WritingModeTraits( WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum()), pList.getExplicit(PR_WRITING_MODE) != null); + width = pList.get(PR_WIDTH).getLength(); //Bind extension properties widowContentLimit = pList.get(PR_X_WIDOW_CONTENT_LIMIT).getLength(); @@ -152,10 +158,10 @@ public void bind(PropertyList pList) throws FOPException { eventProducer.nonAutoBPDOnTable(this, getLocator()); // Anyway, the bpd of a table is not used by the layout code } - if (tableLayout == EN_AUTO) { + /*if (tableLayout == EN_AUTO) { getFOValidationEventProducer().unimplementedFeature(this, getName(), "table-layout=\"auto\"", getLocator()); - } + }*/ if (!isSeparateBorderModel()) { if (borderCollapse == EN_COLLAPSE_WITH_PRECEDENCE) { getFOValidationEventProducer().unimplementedFeature(this, getName(), @@ -400,6 +406,11 @@ public boolean isAutoLayout() { return (tableLayout == EN_AUTO); } + /** @return the table width. It is EN_AUTO when the width attribute is not provided in the tag */ + public Length getWidth() { + return width; + } + /** * Returns the list of table-column elements. * diff --git a/fop-core/src/main/java/org/apache/fop/fo/flow/table/TableColumn.java b/fop-core/src/main/java/org/apache/fop/fo/flow/table/TableColumn.java index abfc8cd4dbf..027c2f61081 100644 --- a/fop-core/src/main/java/org/apache/fop/fo/flow/table/TableColumn.java +++ b/fop-core/src/main/java/org/apache/fop/fo/flow/table/TableColumn.java @@ -275,4 +275,7 @@ public boolean isHeader() { return isHeader; } + public final boolean isAutoLayout() { + return getColumnWidth() instanceof TableColLength; + } } diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/AbstractBaseLayoutManager.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/AbstractBaseLayoutManager.java index dba4723c938..9648b2cae94 100644 --- a/fop-core/src/main/java/org/apache/fop/layoutmgr/AbstractBaseLayoutManager.java +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/AbstractBaseLayoutManager.java @@ -298,4 +298,14 @@ public boolean isFromFootnote() { public void setFromFootnote(boolean fromFootnote) { this.fromFootnote = fromFootnote; } + + /** {@inheritDoc} */ + public int getMinimumIPD() { + int minimumIPD = -1; + for (LayoutManager childLM : getChildLMs()) { + int curMinIPD = childLM.getMinimumIPD(); + minimumIPD = Math.max(minimumIPD, curMinIPD); + } + return minimumIPD; + } } diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java index 6e92935799d..66d49c6d74e 100644 --- a/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java @@ -338,7 +338,7 @@ private void setupAreaDimensions(LayoutContext context) { referenceIPD = context.getRefIPD(); if (width.getEnum() == EN_AUTO) { - updateContentAreaIPDwithOverconstrainedAdjust(); + updateContentAreaIPDwithOverconstrainedAdjust(context); } else { int contentWidth = width.getValue(this); updateContentAreaIPDwithOverconstrainedAdjust(contentWidth); diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockLayoutManager.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockLayoutManager.java index afa90ef640a..e58e1942464 100644 --- a/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockLayoutManager.java +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockLayoutManager.java @@ -137,20 +137,23 @@ protected List getNextChildElements(LayoutManager childLM, LayoutCo //Handled already by the parent (break collapsing, see above) } + List childElements; + if (lmStack == null) { - return childLM.getNextKnuthElements(childLC, alignment); + childElements = childLM.getNextKnuthElements(childLC, alignment); } else { if (childLM instanceof LineLayoutManager) { if (!(restartPosition instanceof LeafPosition)) { restartPosition = null; } - return ((LineLayoutManager) childLM).getNextKnuthElements(childLC, alignment, + childElements = ((LineLayoutManager) childLM).getNextKnuthElements(childLC, alignment, (LeafPosition) restartPosition); } else { - return childLM.getNextKnuthElements(childLC, alignment, + childElements = childLM.getNextKnuthElements(childLC, alignment, lmStack, restartPosition, restartAtLM); } } + return childElements; } private void resetSpaces() { diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockLevelEventProducer.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockLevelEventProducer.java index d043456be07..ca98a8649b5 100644 --- a/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockLevelEventProducer.java +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockLevelEventProducer.java @@ -58,6 +58,18 @@ public static BlockLevelEventProducer get(EventBroadcaster broadcaster) { */ void rowTooTall(Object source, int row, int effCellBPD, int maxCellBPD, Locator loc); + /** + * The minimal width required for the auto-layout of a table's columns is bigger than the available space. + * Alternatively: even using the minimal width required for the auto-layout table, its content overflows the + * available area by (effIPD - maxIPD) millipoints + * @param source the event source + * @param effIPD the effective extent in inline-progression direction of the table contents + * @param maxIPD the maximum extent in inline-progression direction available + * @param loc the location of the error or null + * @event.severity WARN + */ + void columnsInAutoTableTooWide(Object source, int effIPD, int maxIPD, Locator loc); + /** * Auto-table layout is not supported, yet. * @param source the event source diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java index c7ca3a6c4db..cb746f9332a 100644 --- a/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java @@ -37,6 +37,7 @@ import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.fo.properties.SpaceProperty; import org.apache.fop.layoutmgr.inline.InlineContainerLayoutManager; +import org.apache.fop.layoutmgr.inline.LineLayoutManager; import org.apache.fop.layoutmgr.inline.InlineLayoutManager; import org.apache.fop.traits.MinOptMax; import org.apache.fop.util.ListUtil; @@ -197,10 +198,10 @@ protected int neededUnits(int len) { * * @return the resulting content area IPD */ - protected int updateContentAreaIPDwithOverconstrainedAdjust() { + protected int updateContentAreaIPDwithOverconstrainedAdjust(LayoutContext context) { int ipd = referenceIPD - (startIndent + endIndent); - if (ipd < 0) { - //5.3.4, XSL 1.0, Overconstrained Geometry + if (ipd < 0 && !context.isChildOfAutoLayoutElement()) { + //5.3.4, XSL 1.1, Overconstrained Geometry log.debug("Adjusting end-indent based on overconstrained geometry rules for " + fobj); BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( getFObj().getUserAgent().getEventBroadcaster()); @@ -223,7 +224,7 @@ protected int updateContentAreaIPDwithOverconstrainedAdjust() { protected int updateContentAreaIPDwithOverconstrainedAdjust(int contentIPD) { int ipd = referenceIPD - (contentIPD + (startIndent + endIndent)); if (ipd < 0) { - //5.3.4, XSL 1.0, Overconstrained Geometry + //5.3.4, XSL 1.1, Overconstrained Geometry log.debug("Adjusting end-indent based on overconstrained geometry rules for " + fobj); BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( getFObj().getUserAgent().getEventBroadcaster()); @@ -247,7 +248,7 @@ public List getNextKnuthElements(LayoutContext context, int alignme Stack lmStack, Position restartPosition, LayoutManager restartAtLM) { isRestartAtLM = restartAtLM != null; referenceIPD = context.getRefIPD(); - updateContentAreaIPDwithOverconstrainedAdjust(); + updateContentAreaIPDwithOverconstrainedAdjust(context); boolean isRestart = (lmStack != null); boolean emptyStack = (!isRestart || lmStack.isEmpty()); @@ -299,6 +300,17 @@ public List getNextKnuthElements(LayoutContext context, int alignme emptyStack = true; } + final int ipd = childLC.getRefIPD(); + if (getContentAreaIPD() < ipd && context.isChildOfAutoLayoutElement()) { + if (currentChildLM instanceof LineLayoutManager) { + referenceIPD = startIndent + ipd + endIndent; + } else { + referenceIPD = ipd; + } + updateContentAreaIPDwithOverconstrainedAdjust(context); + context.setRefIPD(this.referenceIPD); + } + if (contentList.isEmpty()) { // propagate keep-with-previous up from the first child context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending()); @@ -387,7 +399,7 @@ public List getNextKnuthElements(LayoutContext context, int alignme * @return a new child layout context */ protected LayoutContext makeChildLayoutContext(LayoutContext context) { - LayoutContext childLC = LayoutContext.newInstance(); + LayoutContext childLC = LayoutContext.offspringOf(context); childLC.copyPendingMarksFrom(context); childLC.setStackLimitBP(context.getStackLimitBP()); childLC.setRefIPD(referenceIPD); diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/BreakingAlgorithm.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/BreakingAlgorithm.java index 260d32ac81a..420ab99fe65 100644 --- a/fop-core/src/main/java/org/apache/fop/layoutmgr/BreakingAlgorithm.java +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/BreakingAlgorithm.java @@ -1405,7 +1405,7 @@ protected KnuthNode getNode(int line) { * @return the width/length in millipoints */ protected int getLineWidth(int line) { - assert lineWidth >= 0; +// assert lineWidth >= 0; return this.lineWidth; } diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/LayoutContext.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/LayoutContext.java index c2082108dfc..6ce8cabbc2b 100644 --- a/fop-core/src/main/java/org/apache/fop/layoutmgr/LayoutContext.java +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/LayoutContext.java @@ -52,6 +52,23 @@ public final class LayoutContext { private static final int TREAT_AS_ARTIFACT = 0x20; + /** + * Flags related to "auto" table layout + */ + private static final int IN_AUTO_LAYOUT_DETERMINATION_MODE = 0x40; + + private static final int IS_CHILD_OF_AUTO_LAYOUT_ELEMENT = 0x80; + + /** + * Flag related to "fox:disable-column-balancing" + */ + private static final int DISABLE_COLUMN_BALANCING = 0x100; + + private static final int PROPAGATED_FLAGS = + TREAT_AS_ARTIFACT + | IN_AUTO_LAYOUT_DETERMINATION_MODE + | IS_CHILD_OF_AUTO_LAYOUT_ELEMENT; + private int flags; // Contains some set of flags defined above /** @@ -141,7 +158,7 @@ public static LayoutContext copyOf(LayoutContext copy) { */ public static LayoutContext offspringOf(LayoutContext parent) { LayoutContext offspring = new LayoutContext(0); - offspring.setTreatAsArtifact(parent.treatAsArtifact()); + offspring.propagateFlagsFrom(parent); return offspring; } @@ -169,9 +186,9 @@ private LayoutContext(LayoutContext parentLC) { private LayoutContext(int flags) { this.flags = flags; this.refIPD = 0; - stackLimitBP = MinOptMax.ZERO; - leadingSpace = null; - trailingSpace = null; + this.stackLimitBP = MinOptMax.ZERO; + this.leadingSpace = null; + this.trailingSpace = null; } /** @param source from which pending marks are copied */ @@ -206,6 +223,15 @@ public void unsetFlags(int flags) { setFlags(flags, false); } + /** + * Propagate flags from the given parent context + * + * @param parentLC the parent context + */ + public void propagateFlagsFrom(LayoutContext parentLC) { + this.flags = (parentLC.flags & PROPAGATED_FLAGS); + } + /** @return true if new area is set */ public boolean isStart() { return ((this.flags & NEW_AREA) != 0); @@ -231,6 +257,74 @@ public boolean suppressBreakBefore() { return ((this.flags & SUPPRESS_BREAK_BEFORE) != 0); } + /** @return true if "treat as artifact" is set */ + public boolean treatAsArtifact() { + return (flags & TREAT_AS_ARTIFACT) != 0; + } + + /** + * Sets the "treat as artifact" flag + * @param treatAsArtifact the flag boolean value + */ + public void setTreatAsArtifact(boolean treatAsArtifact) { + setFlags(TREAT_AS_ARTIFACT, treatAsArtifact); + } + + + /** @return whether the column balancer should be disabled before a spanning block. */ + public boolean isColumnBalancingDisabled() { + return (flags & DISABLE_COLUMN_BALANCING) != 0; + } + + /** + * Disables column balancing before a spanning block + * + * @see #isColumnBalancingDisabled() + */ + public void disableColumnBalancing() { + setFlags(DISABLE_COLUMN_BALANCING, true); + } + + /** + * Enables column balancing before a spanning block + * + * @see #isColumnBalancingDisabled() + */ + public void enableColumnBalancing() { + unsetFlags(DISABLE_COLUMN_BALANCING); + } + + /** + * @param b + */ + public void setChildOfAutoLayoutElement(boolean b) { + setFlags(IS_CHILD_OF_AUTO_LAYOUT_ELEMENT, b); + } + + /** + * + * @return + */ + public boolean isChildOfAutoLayoutElement() { + return (flags & IS_CHILD_OF_AUTO_LAYOUT_ELEMENT) != 0; + } + + /** + * + * @return + */ + public boolean isInAutoLayoutDeterminationMode() { + return (flags & IN_AUTO_LAYOUT_DETERMINATION_MODE) != 0; + } + + /** + * + * @param b + */ + public void setInAutoLayoutDeterminationMode(boolean b) { + setFlags(IN_AUTO_LAYOUT_DETERMINATION_MODE, b); + } + /** * Returns the strength of a keep-with-next currently pending. * @return the keep-with-next strength @@ -686,12 +780,5 @@ public void setDisableColumnBalancing(int disableColumnBalancing) { this.disableColumnBalancing = disableColumnBalancing; } - public boolean treatAsArtifact() { - return (flags & TREAT_AS_ARTIFACT) != 0; - } - - public void setTreatAsArtifact(boolean treatAsArtifact) { - setFlags(TREAT_AS_ARTIFACT, treatAsArtifact); - } } diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/LayoutManager.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/LayoutManager.java index 065b89863fa..36a4ce4a990 100644 --- a/fop-core/src/main/java/org/apache/fop/layoutmgr/LayoutManager.java +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/LayoutManager.java @@ -275,4 +275,12 @@ List getNextKnuthElements(LayoutContext context, int alignment, Sta boolean isFromFootnote(); void setFromFootnote(boolean fromFootnote); + + /** + * Iterates over all childLMs to obtain the minimal width required to render all of them (i.e. their content) + * without an overflow. + * @return returns the longest required minimal width of all contained layout managers + */ + int getMinimumIPD(); + } diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/inline/ExternalGraphicLayoutManager.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/inline/ExternalGraphicLayoutManager.java index 8a204607e6d..171c5049b25 100644 --- a/fop-core/src/main/java/org/apache/fop/layoutmgr/inline/ExternalGraphicLayoutManager.java +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/inline/ExternalGraphicLayoutManager.java @@ -21,6 +21,7 @@ import org.apache.fop.area.Area; import org.apache.fop.area.inline.Image; +import org.apache.fop.area.inline.InlineViewport; import org.apache.fop.fo.flow.ExternalGraphic; @@ -47,5 +48,16 @@ protected Area getChildArea() { return im; } + /** {@inheritDoc}
TODO: currently only defined for one specific type of image. */ + public int getMinimumIPD() { + if (curArea instanceof InlineViewport) { + InlineViewport iVP = (InlineViewport) curArea; + return (int) iVP.getContentPosition().getWidth(); + } else { + log.warn("this type does not provide its dimensions, yet."); + return 0; + } + } + } diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java index 66f92a7f4d6..284f700916c 100644 --- a/fop-core/src/main/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java @@ -71,6 +71,7 @@ import org.apache.fop.layoutmgr.Position; import org.apache.fop.layoutmgr.PositionIterator; import org.apache.fop.layoutmgr.SpaceSpecifier; +import org.apache.fop.layoutmgr.UnresolvedListElementWithLength; import org.apache.fop.traits.MinOptMax; /** @@ -170,7 +171,7 @@ public static class LineBreakPosition extends LeafPosition { private LineLayoutPossibilities lineLayouts; private LineLayoutPossibilities[] lineLayoutsList; - private int ipd; + private MinOptMax ipd = null; /** * When layout must be re-started due to a change of IPD, there is no need * to perform hyphenation on the remaining Knuth sequence once again. @@ -228,8 +229,12 @@ public void startSequence() { if (textAlignment == EN_CENTER) { lineFiller = MinOptMax.getInstance(lastLineEndIndent); } else { - lineFiller = MinOptMax.getInstance(lastLineEndIndent, lastLineEndIndent, - layoutManager.ipd); + if (layoutManager.ipd.getOpt() < lastLineEndIndent) { + lineFiller = MinOptMax.getInstance(lastLineEndIndent); + } else { + lineFiller = MinOptMax.getInstance(lastLineEndIndent, lastLineEndIndent, + layoutManager.ipd.getOpt()); + } } // add auxiliary elements at the beginning of the paragraph @@ -441,7 +446,7 @@ private LineBreakPosition makeLineBreakPosition(KnuthSequence par, int firstElem // true if this line contains only zero-height, auxiliary boxes // and the actual line width is 0; in this case, the line "collapses" // i.e. the line area will have bpd = 0 - boolean isZeroHeightLine = (difference == ipd); + boolean isZeroHeightLine = (difference == ipd.getOpt()); // if line-stacking-strategy is "font-height", the line height // is not affected by its content @@ -497,7 +502,7 @@ private LineBreakPosition makeLineBreakPosition(KnuthSequence par, int firstElem firstElementIndex, lastElementIndex, availableShrink, availableStretch, difference, ratio, 0, startIndent, endIndent, - 0, ipd, 0, 0, 0); + 0, ipd.getOpt(), 0, 0, 0); } else { return new LineBreakPosition(thisLLM, knuthParagraphs.indexOf(par), @@ -505,7 +510,7 @@ private LineBreakPosition makeLineBreakPosition(KnuthSequence par, int firstElem availableShrink, availableStretch, difference, ratio, 0, startIndent, endIndent, lineLead + lineFollow, - ipd, spaceBefore, spaceAfter, + ipd.getOpt(), spaceBefore, spaceAfter, lineLead); } } @@ -616,7 +621,7 @@ public List getNextKnuthElements(LayoutContext context, int alignme context.getWritingMode()); } context.setAlignmentContext(alignmentContext); - ipd = context.getRefIPD(); + ipd = MinOptMax.getInstance(context.getRefIPD()); //PHASE 1: Create Knuth elements if (knuthParagraphs == null) { @@ -686,7 +691,7 @@ public List getNextKnuthElements(LayoutContext context, int alignme return null; } - ipd = context.getRefIPD(); + ipd = MinOptMax.getInstance(context.getRefIPD()); //PHASE 2: Create line breaks return createLineBreaks(context.getBPAlignment(), context); } @@ -706,6 +711,9 @@ private void collectInlineKnuthElements(LayoutContext context) { Paragraph lastPar = null; + int minimumIPD = 0; + int maxSumIPD = 0; + InlineLevelLayoutManager curLM; while ((curLM = (InlineLevelLayoutManager) getChildLM()) != null) { List inlineElements = curLM.getNextKnuthElements(inlineLC, effectiveAlignment); @@ -744,6 +752,32 @@ private void collectInlineKnuthElements(LayoutContext context) { // loop over the KnuthSequences (and single KnuthElements) in returnedList for (Object inlineElement : inlineElements) { KnuthSequence sequence = (KnuthSequence) inlineElement; + + // get to know the width of the contained elements + if (context.isInAutoLayoutDeterminationMode()) { + final ListIterator i = sequence.listIterator(); + + while (i.hasNext()) { + Object object = i.next(); + if (object instanceof KnuthElement) { + final KnuthElement element = (KnuthElement) object; + // retrieve minimum width for this lineLM along the way + if (element instanceof KnuthBox) { + // TODO: this is already calculated during the collection of the childLM's elements + minimumIPD = Math.max(minimumIPD, element.getWidth()); + } + maxSumIPD += element.getWidth(); + } else { + if (object instanceof UnresolvedListElementWithLength) { + final UnresolvedListElementWithLength unresolved = (UnresolvedListElementWithLength) object; + MinOptMax minmax = unresolved.getLength(); + maxSumIPD += minmax.getMax(); + } + } + + } +// log.debug("Line with minIPD:=" + minimumIPD); + } // the sequence contains inline Knuth elements if (sequence.isInlineSequence()) { // look at the last element @@ -785,7 +819,7 @@ private void collectInlineKnuthElements(LayoutContext context) { if (!lastPar.containsBox()) { //only a forced linefeed on this line //-> compensate with an auxiliary glue - lastPar.add(new KnuthGlue(ipd, 0, ipd, null, true)); + lastPar.add(new KnuthGlue(ipd.getOpt(), 0, ipd.getOpt(), null, true)); } lastPar.endParagraph(); ElementListObserver.observe(lastPar, "line", null); @@ -812,6 +846,22 @@ private void collectInlineKnuthElements(LayoutContext context) { trace.append(" ]"); } } + + /** + * at this point, localIPD represents the maximum value for how wide the line should be. + */ + if (ipd.getMax() < maxSumIPD && context.isInAutoLayoutDeterminationMode()) { + ipd = MinOptMax.getInstance(minimumIPD, maxSumIPD, maxSumIPD); + + final MinOptMax stackLimitBP = context.getStackLimitBP(); + int max = stackLimitBP.getMax(); + if (max < maxSumIPD) { + max = maxSumIPD; + } + MinOptMax newStackLimitBP = MinOptMax.getInstance(stackLimitBP.getMin(), maxSumIPD, max); + context.setStackLimitBP(newStackLimitBP); + context.setRefIPD(ipd.getOpt()); + } log.trace(trace); } @@ -865,7 +915,7 @@ private LineLayoutPossibilities findOptimalBreakingPoints(int alignment, Paragra hyphenationLadderCount.getEnum() == EN_NO_LIMIT ? 0 : hyphenationLadderCount.getValue(), this); - alg.setConstantLineWidth(ipd); + alg.setConstantLineWidth(ipd.getOpt()); boolean canWrap = (wrapOption != EN_NO_WRAP); boolean canHyphenate = (canWrap && hyphenationProperties.hyphenate.getEnum() == EN_TRUE); @@ -1343,7 +1393,7 @@ private void findHyphenationPoints(Paragraph currPar) { } } if (log.isTraceEnabled()) { - log.trace(" Word to hyphenate: " + sbChars); + log.trace(" Word to hyphenate: " + sbChars.toString()); } // find hyphenation points HyphContext hc = getHyphenContext(sbChars); diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java index 8f4e671fa1d..034d47503cf 100644 --- a/fop-core/src/main/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java @@ -135,6 +135,9 @@ private PendingChange(final GlyphMapping mapping, final int index) { private final Position auxiliaryPosition = new LeafPosition(this, -1); private FOUserAgent userAgent; + + private int minimumIPD = -1; + /** * Create a Text layout manager. * @@ -910,12 +913,42 @@ public List getNextKnuthElements(final LayoutContext context, fin if (returnList.isEmpty()) { return null; } else { + determineMinIPD(returnList, context); return returnList; } } + /** + * Determines the minIPD of the textLM's {@link #foText} by returning the width of its longest string.
+ * TODO: Currently, this dedicated iteration is rather wasteful and should be integrated + * into {@link #getNextKnuthElements(LayoutContext, int)}, if possible. Additionally, the + * algorithm is quite trivial and does not take any linebreak possibilities etc. into account. + * + * @param returnList KnuthSequence of KnuthElements representing the object's {@link #foText} + * @param context + */ + private void determineMinIPD(List returnList, LayoutContext context) { + minimumIPD = 0; + ListIterator iter = returnList.listIterator(); + while (iter.hasNext()) { + KnuthSequence sequence = (KnuthSequence) iter.next(); + if (context.isInAutoLayoutDeterminationMode()) { + final ListIterator i = sequence.listIterator(); + + while (i.hasNext()) { + final KnuthElement element = (KnuthElement) i.next(); + if (element instanceof KnuthBox) { + //TODO: improve algorithm! + minimumIPD = Math.max(minimumIPD, element.getWidth()); + } + } + log.debug("TextLayoutManager with minIPD:=" + minimumIPD); + } + } + } + private KnuthSequence processLinebreak(List returnList, KnuthSequence sequence) { if (lineEndBAP != 0) { sequence.add(new KnuthGlue(lineEndBAP, 0, 0, auxiliaryPosition, true)); @@ -1530,4 +1563,16 @@ protected MinOptMax getLetterSpaceAdjustment(int i) { } return letterSpaceAdjustArray[i]; } + + /** + * returns the minimum IPD (aka 'the longest box') required for the line. + * Must only be used for table with table-layout="auto". + * + * @return the longest KnuthBox encountered + */ + public int getMinimumIPD() { + assert minimumIPD > -1; + return minimumIPD; + } + } diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/table/ColumnSetup.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/table/ColumnSetup.java index f219dc9aa50..2a94a40e810 100644 --- a/fop-core/src/main/java/org/apache/fop/layoutmgr/table/ColumnSetup.java +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/table/ColumnSetup.java @@ -19,6 +19,7 @@ package org.apache.fop.layoutmgr.table; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -27,12 +28,16 @@ import org.apache.fop.datatypes.Length; import org.apache.fop.datatypes.PercentBaseContext; +import org.apache.fop.fo.Constants; import org.apache.fop.fo.FONode; import org.apache.fop.fo.expr.RelativeNumericProperty; import org.apache.fop.fo.flow.table.Table; import org.apache.fop.fo.flow.table.TableColumn; import org.apache.fop.fo.properties.TableColLength; +import org.apache.fop.layoutmgr.BlockLevelEventProducer; +import org.apache.fop.layoutmgr.LayoutContext; import org.apache.fop.traits.Direction; +import org.apache.fop.traits.MinOptMax; import org.apache.fop.traits.WritingModeTraits; import org.apache.fop.traits.WritingModeTraitsGetter; @@ -84,9 +89,8 @@ private void prepareColumns() { //Post-processing the list (looking for gaps) //TODO The following block could possibly be removed int pos = 1; - for (Object column : columns) { - TableColumn col = (TableColumn) column; - if (col == null) { + for (TableColumn column : columns) { + if (column == null) { assert false; //Gaps are filled earlier by fo.flow.table.Table.finalizeColumns() //log.error("Found a gap in the table-columns at position " + pos); } @@ -143,7 +147,7 @@ public int getColumnCount() { } /** @return an Iterator over all columns */ - public Iterator iterator() { + public Iterator iterator() { return this.columns.iterator(); } @@ -184,10 +188,11 @@ private void initializeColumnWidths() { * [p-c-w(x) = x * base_unit_ipd] * * @param tlm the TableLayoutManager + * @param context * @return the computed base unit (in millipoint) */ - protected double computeTableUnit(TableLayoutManager tlm) { - return computeTableUnit(tlm, tlm.getContentAreaIPD()); + protected double computeTableUnit(TableLayoutManager tlm, LayoutContext context) { + return computeTableUnit(tlm, tlm.getContentAreaIPD(), context); } /** @@ -196,9 +201,10 @@ protected double computeTableUnit(TableLayoutManager tlm) { * * @param percentBaseContext the percent base context for relative values * @param contentAreaIPD the IPD of the available content area + * @param context * @return the computed base unit (in millipoints) */ - public float computeTableUnit(PercentBaseContext percentBaseContext, int contentAreaIPD) { + public float computeTableUnit(PercentBaseContext percentBaseContext, int contentAreaIPD, LayoutContext context) { int sumCols = 0; float factors = 0; @@ -208,8 +214,7 @@ public float computeTableUnit(PercentBaseContext percentBaseContext, int content * and work out the total number of factors to use to distribute * the remaining space (if any) */ - for (Object colWidth1 : colWidths) { - Length colWidth = (Length) colWidth1; + for (Length colWidth : colWidths) { if (colWidth != null) { sumCols += colWidth.getValue(percentBaseContext); if (colWidth instanceof RelativeNumericProperty) { @@ -227,6 +232,13 @@ public float computeTableUnit(PercentBaseContext percentBaseContext, int content if (sumCols < contentAreaIPD) { unit = (contentAreaIPD - sumCols) / factors; } else { + // this warning occurs during the pre-processing (AutoLayoutDeterminationMode) + // and can be ignored in these cases. + if (percentBaseContext instanceof TableLayoutManager) { + if (context.isInAutoLayoutDeterminationMode()) { + return unit; + } + } log.warn("No space remaining to distribute over columns."); } } @@ -311,4 +323,241 @@ public int getSumOfColumnWidths(PercentBaseContext context) { return sum; } + /** + * Computes for each of the table's columns the optimal width and adjusts each column if necessary. + * This method relies on the fact, that a column's OPT value is initialized equal to its MAX value. + * + * @param tLM the TableLayoutManager + * @param context + * @param width the Table width + * @return int maximum width to be propagated to containing layout manager or -1 + */ + public int computeOptimalColumnWidthsForAutoLayout(TableLayoutManager tLM, LayoutContext context, Length width) { + int maxSumCols = 0; // collects OPT values of the individual columns + int minSumCols = 0; + int contentAreaIPD = tLM.getContentAreaIPD(); + + for (TableColumn tcol : columns) { + if (tcol != null) { + if (tcol.isAutoLayout()) { + MinOptMax possibleWidth = tLM.getPossibleWidths(tcol, context); + if (possibleWidth == null) { + // this column does not have a PrimaryGridUnit by itself + // Just assume that no space is required for such an 'empty' column + // TODO: validate this assumption (looks good after rendering it!) + } else { + maxSumCols += possibleWidth.getOpt(); + minSumCols += possibleWidth.getMin(); + } + } else { + int staticWidth = tcol.getColumnWidth().getValue(tLM); + maxSumCols += staticWidth; + minSumCols += staticWidth; + } + } + } + + /* + * distribute the remaining space over the accumulated factors (if any) + */ + // TODO: DO NOT DO THIS IN CASE WE ARE IN AN AUTOMATIC LAYOUT PARENT WHICH NEEDS + // AUTHENTIC MIN/MAX VALUES TO DETERMINE ITS OWN WIDTH REQUIREMENTS + if (context.isChildOfAutoLayoutElement() && context.isInAutoLayoutDeterminationMode()) { + return maxSumCols; + } else { + if (maxSumCols > contentAreaIPD) { + if (minSumCols < contentAreaIPD) { + // redistribute by setting OPT values + if (log.isDebugEnabled()) { + log.debug("Sum (" + maxSumCols + ") > Available Area (" + contentAreaIPD + "): Redistributing"); + } + + // create a second list from which we can remove individual items after we are done with them + List columnsToProcess = new ArrayList(); + columnsToProcess.addAll(columns); + redistribute(tLM, contentAreaIPD, maxSumCols, columnsToProcess, context); + } else { + // set all OPTs to the respective MINIMUM of each column + if (minSumCols != contentAreaIPD) { + // communicate this case as a warning to the user + Table table = tLM.getTable(); + BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( + table.getUserAgent().getEventBroadcaster()); + eventProducer.columnsInAutoTableTooWide(this, minSumCols, + contentAreaIPD, table.getLocator()); + } + boolean computeAuto = false; + for (TableColumn tcol : columns) { + if (tcol.isAutoLayout()) { + MinOptMax possibleWidth = tLM.getPossibleWidths(tcol, context); + if (possibleWidth == null) { + // ignore columns which do not contain PGUs -> their width is zero + } else { + computeAuto = true; + int min = possibleWidth.getMin(); + int max = possibleWidth.getMax(); + MinOptMax minWidth = MinOptMax.getInstance(min, min, max); + tLM.setPossibleWidths(tcol, minWidth); + } + } else { + // DO NOT CHANGE THE OPT-VALUE OF A COLUMN WITH STATIC WIDTH - IT IS + // ALREADY THE STATIC VALUE WE MUST USE (AS DEFINED IN THE FO-FILE) + } + } + if (computeAuto) { + // table overflows over the max content area. Redistribute the columns with none fixed width + // create a second list from which we can remove individual items after we are done with them + List columnsToProcess = new ArrayList(); + columnsToProcess.addAll(columns); + redistributeAuto(tLM, contentAreaIPD, columnsToProcess, context); + } + } + } else { + if (width.getEnum() != Constants.EN_AUTO) { + // create a second list from which we can remove individual items after we are done with them + List columnsToProcess = new ArrayList(); + columnsToProcess.addAll(columns); + redistributeAuto(tLM, contentAreaIPD, columnsToProcess, context); + } + } + } + return -1; + } + + /** + * This method redistributes the remaining width of the table recursively. + * At first, all static columns are excluded (i.e., the redistribution is invoked + * for all but these columns), since we cannot shrink them. + * Afterwards, we try to proportionally shrink each remaining column by a factor of + * factor = remainingArea / sum of max width of remaining columns + * After applying this factor to a column's MAX width, we check if the result is less + * than the column's minimal width - if so, this minimal width is used instead and we + * invoke the method again with + *
    + *
  • the remaining columns without the column we just changed
  • + *
  • the remaining area - the minimal width of the column we just changed
  • + *
  • an updated factor (based on the remaining area and the remaining columns)
  • + *
+ * + * @param tLM the TableLayoutManager which is used to store the dimensions of all columns + * @param remainingArea the remaining width which we may still distribute among the columnsToProcess + * @param columnsToProcess list of table columns to process + * @param context the layout context + * @return boolean The return value indicates whether the layout changed due to the redistribution. + */ + private boolean redistribute(TableLayoutManager tLM, int remainingArea, int maxSumCols, List columnsToProcess, LayoutContext context) { + double factor = (double) remainingArea / maxSumCols; + + // step 1: check if applying the current factor leads to a value below the minimum of a column + for (TableColumn tcol : columnsToProcess) { + // ignore columns which have a static width since we must use their assigned value + // ignoring them = excluding them from the columns we want to shrink + if (!tcol.isAutoLayout()) { + int staticWidth = tcol.getColumnWidth().getValue(tLM); + remainingArea -= staticWidth; + maxSumCols -= staticWidth; + columnsToProcess.remove(tcol); + if (log.isDebugEnabled()) { + log.debug("| Col " + tcol.getColumnNumber() + " -> STATIC(" + staticWidth + ") |"); + } + return redistribute(tLM, remainingArea, maxSumCols, columnsToProcess, context); + } else { + MinOptMax possibleWidth = tLM.getPossibleWidths(tcol, context); + if (possibleWidth == null) { + // no PrimaryGridUnits in this column + columnsToProcess.remove(tcol); + if (log.isDebugEnabled()) { + log.debug("| Col " + tcol.getColumnNumber() + " -> EMPTY (0) |"); + } + return redistribute(tLM, remainingArea, maxSumCols, columnsToProcess, context); + } + int max = possibleWidth.getMax(); + int min = possibleWidth.getMin(); + + if ((max * factor) < min) { + // for this column: opt = min + MinOptMax newWidths = MinOptMax.getInstance(min, min, max); + tLM.setPossibleWidths(tcol, newWidths); + // remove this column from the list, decrease the remaining area (-min), and recalculate the factor + remainingArea -= min; + maxSumCols -= max; + // continue with all other columns which may still be shrinked + columnsToProcess.remove(tcol); + if (log.isDebugEnabled()) { + log.debug("| Col " + tcol.getColumnNumber() + " -> MIN(" + min + ") |"); + } + return redistribute(tLM, remainingArea, maxSumCols, columnsToProcess, context); + } else { + // current column could be shrunk using the current factor + // however, subsequent columns might not be -> wait until such columns are sorted out + } + } + } + + // step 2: now we know that all remaining columns can be shrunk by the factor + for (TableColumn tcol : columnsToProcess) { + MinOptMax possibleWidth = tLM.getPossibleWidths(tcol, context); + int max = possibleWidth.getMax(); + int min = possibleWidth.getMin(); + int newOpt = (int) (max * factor); + if (log.isDebugEnabled()) { + log.debug("| Col " + tcol.getColumnNumber() + " -> OPT(" + newOpt + ") |"); + } + MinOptMax newWidths = MinOptMax.getInstance(min, newOpt, max); + // ASSIGN to column + tLM.setPossibleWidths(tcol, newWidths); + } + if (log.isDebugEnabled()) { + log.debug("Redistribution finished"); + } + return true; + } + + /** + * This method redistributes the remaining width of the table to honor the table width. + * At first, all static columns are excluded, since we cannot change them. + * Afterwards, we try to proportionally increase each remaining column by a factor of + * factor = column width / sum of max width of remaining columns + * + * @param tLM the TableLayoutManager which is used to store the dimensions of all columns + * @param maxArea the table width + * @param columnsToProcess list of table columns to process + * @param context the layout context + * @return boolean The return value indicates whether the layout changed due to the redistribution. + */ + private boolean redistributeAuto(TableLayoutManager tLM, int maxArea, List columnsToProcess, LayoutContext context) { + + int autoWidth = 0; + int fixedWidth = 0; + + for (TableColumn tcol : columnsToProcess) { + int width = tcol.getColumnWidth().getValue(tLM); + if (tcol.isAutoLayout()) { + autoWidth += width; + } else { + fixedWidth += width; + } + } + + int remainingArea = maxArea - fixedWidth; + + if (remainingArea > 0) { + for (TableColumn tcol : columnsToProcess) { + if (tcol.isAutoLayout()) { + MinOptMax possibleWidth = tLM.getPossibleWidths(tcol, context); + int max = possibleWidth.getMax(); + int min = possibleWidth.getMin(); + double width = tcol.getColumnWidth().getValue(tLM); + double factor = width / autoWidth; + int newMax = (int) (factor * remainingArea); + MinOptMax newWidths = MinOptMax.getInstance(newMax, newMax, newMax); + // ASSIGN to column + tLM.setPossibleWidths(tcol, newWidths); + } + } + } + return true; + } + + } diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java index 8b74f635df8..732dd934b00 100644 --- a/fop-core/src/main/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java @@ -95,11 +95,8 @@ public LinkedList getNextKnuthElements(LayoutContext context, int a 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; + for (EffRow row : rowGroup) { + for (GridUnit gu : row.getGridUnits()) { if (gu.isPrimary()) { PrimaryGridUnit primary = gu.getPrimary(); // TODO a new LM must be created for every new static-content @@ -113,7 +110,7 @@ private void createElementsForRowGroup(LayoutContext context, int alignment, spanWidth += ((TableColumn) colIter.next()).getColumnWidth().getValue( tableLM); } - LayoutContext childLC = LayoutContext.newInstance(); + LayoutContext childLC = LayoutContext.offspringOf(context); childLC.setStackLimitBP(context.getStackLimitBP()); //necessary? childLC.setRefIPD(spanWidth); @@ -156,8 +153,7 @@ private void computeRowHeights() { rowHeights[rgi] = rowBPD.toMinOptMax(tableLM); explicitRowHeight = rowBPD.toMinOptMax(tableLM); } - for (Object o : row.getGridUnits()) { - GridUnit gu = (GridUnit) o; + for (GridUnit gu : row.getGridUnits()) { if (!gu.isEmpty() && gu.getColSpanIndex() == 0 && gu.isLastGridUnitRowSpan()) { PrimaryGridUnit primary = gu.getPrimary(); int effectiveCellBPD = 0; diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/table/RowPainter.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/table/RowPainter.java index 6de85c3a68c..8e2ade80fff 100644 --- a/fop-core/src/main/java/org/apache/fop/layoutmgr/table/RowPainter.java +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/table/RowPainter.java @@ -121,7 +121,9 @@ void startTablePart(TablePart tablePart) { * in the outer mode */ void endTablePart(boolean lastInBody, boolean lastOnPage) { - addAreasAndFlushRow(lastInBody, lastOnPage); + if (currentRow != null) { + addAreasAndFlushRow(lastInBody, lastOnPage); + } if (tablePartBackground != null) { TableLayoutManager tableLM = tclm.getTableLM(); @@ -155,8 +157,10 @@ void handleTableContentPosition(TableContentPosition tcpos) { } else { EffRow row = tcpos.getRow(); if (row.getIndex() > currentRow.getIndex()) { - addAreasAndFlushRow(false, false); - currentRow = row; + if (currentRow != null) { + addAreasAndFlushRow(false, false); + currentRow = row; + } } } if (firstRowIndex < 0) { diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java index 7e369e5261d..87ae7ff39c9 100644 --- a/fop-core/src/main/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java @@ -22,6 +22,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.ListIterator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -166,6 +167,36 @@ protected int getIPIndents() { return startIndent + endIndent; } + /** {@inheritDoc}
Also adds any indents required by the tablecell */ + public int getMinimumIPD() { + int minimumIPD = -1; + ListIterator iterLM = getChildLMs().listIterator(); + while (iterLM.hasNext()) { + LayoutManager childLM = (LayoutManager)iterLM.next(); + int curMinIPD = childLM.getMinimumIPD(); + minimumIPD = Math.max(minimumIPD, curMinIPD); + } + minimumIPD += getIPIndents(); + return minimumIPD; + } + + final int getRefIPD() { + return this.referenceIPD; + } + + /** {@inheritDoc} */ + final boolean isAutoLayout() { + final Table table = getTable(); + + if (table.isAutoLayout()) { + final int index = this.primaryGridUnit.getColIndex(); + final TableColumn column = table.getColumn(index); + return column.isAutoLayout(); + } + + return false; + } + /** * {@inheritDoc} */ @@ -183,13 +214,22 @@ public List getNextKnuthElements(LayoutContext context, int alignme LayoutManager curLM; // currently active LM LayoutManager prevLM = null; // previously active LM while ((curLM = getChildLM()) != null) { - LayoutContext childLC = LayoutContext.newInstance(); + LayoutContext childLC = LayoutContext.offspringOf(context); // curLM is a ? childLC.setStackLimitBP(context.getStackLimitBP().minus(stackLimit)); childLC.setRefIPD(cellIPD); // get elements from curLM returnedList = curLM.getNextKnuthElements(childLC, alignment); + + final int ipd = childLC.getRefIPD() + getIPIndents(); + + if (this.referenceIPD < ipd && (isAutoLayout() || context.isChildOfAutoLayoutElement())) { + this.referenceIPD = ipd; + this.cellIPD = getRefIPD() - getIPIndents(); + context.setRefIPD(ipd); + } + if (childLC.isKeepWithNextPending()) { log.debug("child LM signals pending keep with next"); } diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java index 053ce737a11..47dfb48fd76 100644 --- a/fop-core/src/main/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java @@ -19,23 +19,29 @@ package org.apache.fop.layoutmgr.table; +import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; +import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.fop.datatypes.PercentBaseContext; import org.apache.fop.fo.Constants; +import org.apache.fop.fo.FONode; import org.apache.fop.fo.FObj; import org.apache.fop.fo.flow.Marker; 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.Table; import org.apache.fop.fo.flow.table.TableBody; +import org.apache.fop.fo.flow.table.TableColumn; import org.apache.fop.fo.flow.table.TablePart; import org.apache.fop.layoutmgr.BreakElement; import org.apache.fop.layoutmgr.ElementListUtils; @@ -53,6 +59,7 @@ import org.apache.fop.layoutmgr.Position; import org.apache.fop.layoutmgr.PositionIterator; import org.apache.fop.layoutmgr.SpaceResolver.SpaceHandlingBreakPosition; +import org.apache.fop.traits.MinOptMax; import org.apache.fop.util.BreakUtil; /** @@ -77,6 +84,8 @@ public class TableContentLayoutManager implements PercentBaseContext { private TableStepper stepper; + private final Map baseLength = new HashMap(); + private boolean headerIsBeingRepeated; private boolean atLeastOnce; @@ -136,6 +145,272 @@ protected LinkedList getFooterElements() { return this.footerList; } + /** + * assigns a {@link MinOptMax} object to a specific {@link TableColumn} + * @param key a {@link TableColumn} + * @param mom a {@link MinOptMax} representing the width requirements of the key + */ + public void setBaseLength(TableColumn key, MinOptMax mom) { + this.baseLength.put(key, mom); + } + + /** + * returns the {@link MinOptMax} assigned to a table's {@link TableColumn}. + * @param key a {@link TableColumn} + * @return {@link MinOptMax} object representing the minimal, optimal and maximum width required for the + * key + */ + public final MinOptMax getBaseLength(final FObj key) { + return this.baseLength.get(key); + } + + /** + * Compute and set a set of {@link MinOptMax} widths for a {@link PrimaryGridUnit} (PGU). + * Now also covers PGUs spanning multiple columns. However, if such a PGU is encountered + * in the first row already, the table requires a second determination run. + * @param primary + * @return + */ + private boolean setBaseLength(final PrimaryGridUnit primary, LayoutContext context) { + final Table table = this.tableLM.getTable(); + final int index = primary.getColIndex(); + final int n = index + primary.getCell().getNumberColumnsSpanned(); + final TableColumn key = table.getColumn(index); + + int availableSpanWidth = 0; + int minSpanWidth = 0; + + int min; + int span; + + // calculate width (min and opt) of all columns spanned by primary + for (int i = index; i < n; i++) { + final TableColumn column = table.getColumn(i); + span = column.getColumnWidth().getValue(this.tableLM); + availableSpanWidth += span; + + min = span; + if (column.isAutoLayout()) { + final MinOptMax length = getBaseLength(column); + if (length != null) { + min = length.getMin(); + } + } + minSpanWidth += min; + } + + // retrieve the maximum width of the cell's content - problematic if col-span >1 for a static first column? + int ipd = primary.getCellLM().getRefIPD(); + + // retrieve the minimum width of the cell's content - also works for cells spanning columns + int minIPD = primary.getCellLM().getMinimumIPD(); + + final MinOptMax length = getBaseLength(key); + if ((availableSpanWidth == 0) || (length == null)) { + // TODO: remove the following IF as soon as the computation of minIPD is corrected + if (minIPD > ipd) { // happens e.g. for cells containing a space: ipd=0, minIPD=len(" ") + ipd = minIPD; + } + // |_____c1_____| <- width for both: minSpanWidth <= optimal <= availableSpanWidth + // |__c2__||___c3___| <- width for spanning cell: minIPD <= optimal <= ipd + MinOptMax initialMinOptMax = MinOptMax.getInstance(minIPD, ipd, ipd); + this.baseLength.put(key, initialMinOptMax); + } else { + if (index == n - 1) { // a primary without col-span > 1 + if ((availableSpanWidth < ipd) || (length.getMin() < minIPD)) { // cell needs more space + + if (minIPD > ipd) { + ipd = minIPD; // See: fop/test/layoutengine/standard-testcases/table-layout_auto_simple_nested.xml + } + + MinOptMax possibleWidths = + MinOptMax.getInstance( + Math.max(length.getMin(), minIPD), + Math.max(length.getOpt(), ipd), + Math.max(length.getMax(), ipd) + ); + return length == this.baseLength.put(key, possibleWidths); + } + } else { + // this primary spans multiple columns which may have to be resized! + // |__c1__||__c2__| <- width for both: minSpanWidth <= optimal <= availableSpanWidth + // |__c3 (span=2)___| <- width for spanning cell: minIPD <= optimal <= ipd + // thus, if any of the following booleans are true, the spanned columns need to be widened! + boolean isNotSufficientForMinIPD = minSpanWidth < minIPD; // overflow even after linebreaks + boolean isNotSufficientForIPD = availableSpanWidth < ipd; // overflow because of more content + + if (isNotSufficientForMinIPD || isNotSufficientForIPD) { + // columns spanned by the primary do not offer enough width and, thus, need to + // be widened. + + // first step: ignore static columns which cannot be resized + // this includes removing their width from the widths to process + List columnsToWiden = new ArrayList<>(); + for (Iterator iter = table.getColumns().subList(index, n).iterator(); iter.hasNext();) { + TableColumn column = (TableColumn)iter.next(); + if (column.isAutoLayout()) { // column can be resized + int width = column.getColumnWidth().getValue(this.tableLM); + + if (tableLM.getPossibleWidths(column, context) == null) { + // ignore columns without PrimaryGridUnits + } else { + columnsToWiden.add(column); + } + } else { // column is static and cannot be resized + int width = column.getColumnWidth().getValue(this.tableLM); + availableSpanWidth -= width; + minSpanWidth -= width; + ipd -= width; + minIPD -= width; + } + } + + // true if only static columns are spanned -> no columns left to resize! + if (columnsToWiden.isEmpty()) { + LOG.warn("No columns to resize to fit a table-cell spanning these columns, expect overflows"); + return false; + } + + // minimal width reserved by the spanned columns insufficient -> resize + if (minSpanWidth < minIPD) { + if (LOG.isDebugEnabled()) { + LOG.warn("Cell (" + primary.getColIndex() + "," + primary.getRowIndex() + ") spanning " + + primary.getCell().getNumberColumnsSpanned() + " columns requires at least " + + minIPD + " -> widening MIN/OPT/MAX its spanned columns: " + columnsToWiden); + } + + int totalIncrease = increaseMinimumWidthOfSpannedColumns(columnsToWiden, minSpanWidth, minIPD, context); + // resizing the columns led to additional space being reserved by these columns! + availableSpanWidth += totalIncrease; + } + + // maximum width reserved by the spanned columns insufficient -> resize + if (availableSpanWidth < ipd) { + if (LOG.isDebugEnabled()) { + LOG.warn("Cell (" + primary.getColIndex() + "," + primary.getRowIndex() + ") spanning " + + primary.getCell().getNumberColumnsSpanned() + " columns requires up to " + + ipd + " -> widening OPT/MAX of its spanned columns: " + columnsToWiden); + } + increaseOptimalWidthOfSpannedColumns(columnsToWiden, availableSpanWidth, ipd); + } + } + + } + } + return false; + } + + /** + * Takes a set of columns (minSpanWidthOfSpannedCells = sum of their minIPDs) which are spanned + * by the cell we are currently processing. Since this current cell requires a wider minIPD than + * all spanned columns combined, this method increases the min. width of these columns proportionally + * in such a way that the sum of their min. widths is >= the minIPD of the current cell.
+ * Please note that for each column, all three values of its {@link MinOptMax} are increased accordingly. + * After all columns were processed and widened, the sum of additional space reserved by these columns + * is returned. + * @param columnsToWiden set of non-static columns which can be resized + * @param minSpanWidthOfSpannedCells sum of the minIPDs of the columns in columnsToWiden + * @param minIPD minimal width required by the current cell + * @param context + * @return the total amount of width which was added to the columns in columnsToWiden + */ + private int increaseMinimumWidthOfSpannedColumns(List columnsToWiden, int minSpanWidthOfSpannedCells, int minIPD, LayoutContext context) { + int totalIncrease = 0; + + for (Iterator iter = columnsToWiden.iterator(); iter.hasNext();) { + final TableColumn column = (TableColumn) iter.next(); + MinOptMax length = tableLM.getPossibleWidths(column, context); + + // calculate factor for increase of width + double factor = (double)length.getMin() / minSpanWidthOfSpannedCells; + + // how much more space is required to display the spanning cell + int totalMissingMinSpace = minIPD - minSpanWidthOfSpannedCells; + + int increaseForMinimum = (int) Math.ceil(factor * totalMissingMinSpace); + + MinOptMax newMom = + MinOptMax.getInstance( + length.getMin() + increaseForMinimum, + length.getOpt() + increaseForMinimum, + length.getMax() + increaseForMinimum + ); + setBaseLength(column, newMom); + totalIncrease += increaseForMinimum; + } + return totalIncrease; + } + + /** + * takes a set of columns (columnsToWiden) spanned by one cell (represented via a + * {@link PrimaryGridUnit}) and increases their minimum width value (in case the spanning cell's + * minIPD is bigger than the sum of + * goes through a subset of the columns + * @param columnsToWiden + * @param availableWidth + * @param requiredWidth + * @return + */ + private boolean increaseOptimalWidthOfSpannedColumns(List columnsToWiden, int availableWidth, int requiredWidth) { + for (Iterator iter = columnsToWiden.iterator(); iter.hasNext();) { + final TableColumn column = (TableColumn) iter.next(); + MinOptMax length = getBaseLength(column); + + // calculate factor for increase of width + double factor = (double) length.getOpt() / availableWidth; + + // how much more space is required to display the spanning cell + int totalMissingMaxSpace = requiredWidth - availableWidth; + + // ensure the content will fit by getting the ceiling of the product + int increase = (int) Math.ceil(factor * totalMissingMaxSpace); + MinOptMax newMom = + MinOptMax.getInstance( + length.getMin(), + length.getOpt() + increase, + length.getMax() + increase + ); + setBaseLength(column, newMom); + } + return false; + } + + private boolean setBaseLength(final TableContentPosition position, LayoutContext context) { + boolean done = false; + final EffRow row = position.getRow(); + final Iterator grid = row.getGridUnits().iterator(); + + while (grid.hasNext()) { + final GridUnit unit = (GridUnit) grid.next(); + + if (unit instanceof PrimaryGridUnit) { + done = setBaseLength((PrimaryGridUnit) unit, context) || done; + } + } + + return done; + } + + private boolean setBaseLength(final Iterator content,LayoutContext context) { + boolean done = false; + + while (content.hasNext()) { + final ListElement element = (ListElement) content.next(); + final Position position = element.getPosition(); + + if (position instanceof TableContentPosition) { + done = setBaseLength((TableContentPosition) position, context) || done; + } + } + + return done; + } + + private boolean setBaseLength(final List content, LayoutContext context) { + final Table table = this.tableLM.getTable(); + return table.isAutoLayout() && setBaseLength(content.iterator(), context); + } + /** * Get a sequence of KnuthElements representing the content * of the node assigned to the LM. @@ -157,6 +432,18 @@ public List getNextKnuthElements(LayoutContext context, int alignme if (headerIter != null && headerList == null) { this.headerList = getKnuthElementsForRowIterator( headerIter, context, alignment, TableRowIterator.HEADER); + + setBaseLength(this.headerList, context); + /* NOT sure why we need to recreate header iterator, this can lead to an endless loop since we get the same + table over and over again! + if (setBaseLength(this.headerList, context)) { + final Table table = this.tableLM.getTable(); + this.headerIter = new TableRowIterator(table, TableRowIterator.HEADER); + this.headerList = null; + return getNextKnuthElements(context, alignment); + } + */ + this.headerNetHeight = ElementListUtils.calcContentLength(this.headerList); if (LOG.isDebugEnabled()) { @@ -189,6 +476,24 @@ public List getNextKnuthElements(LayoutContext context, int alignme if (footerIter != null && footerList == null) { this.footerList = getKnuthElementsForRowIterator( footerIter, context, alignment, TableRowIterator.FOOTER); + + setBaseLength(this.footerList, context); + /* NOT sure why we need to recreate footer iterator, this can lead to an endless loop since we get the same + table over and over again! + if (setBaseLength(this.footerList, context)) { + final Table table = this.tableLM.getTable(); + + if (this.headerIter != null) { + this.headerIter = new TableRowIterator(table, TableRowIterator.HEADER); + this.headerList = null; + } + + this.footerIter = new TableRowIterator(table, TableRowIterator.FOOTER); + this.footerList = null; + return getNextKnuthElements(context, alignment); + } + */ + this.footerNetHeight = ElementListUtils.calcContentLength(this.footerList); if (LOG.isDebugEnabled()) { @@ -211,6 +516,29 @@ public List getNextKnuthElements(LayoutContext context, int alignme } returnList.addAll(getKnuthElementsForRowIterator( bodyIter, context, alignment, TableRowIterator.BODY)); + + setBaseLength(returnList, context); + /* NOT sure why we need to recreate table iterators, this can lead to an endless loop since we get the same + table over and over again! + causes java.lang.StackOverflowError: see fop/test/layoutengine/standard-testcases/table-layout_auto_nested_2.xml + if (setBaseLength(returnList, context)) { + final Table table = this.tableLM.getTable(); + + if (this.headerIter != null) { + this.headerIter = new TableRowIterator(table, TableRowIterator.HEADER); + this.headerList = null; + } + + if (this.footerIter != null) { + this.footerIter = new TableRowIterator(table, TableRowIterator.FOOTER); + this.footerList = null; + } + + this.bodyIter = new TableRowIterator(table, TableRowIterator.BODY); + return getNextKnuthElements(context, alignment); + } + */ + if (headerAsFirst != null) { int insertionPoint = 0; if (returnList.size() > 0 && returnList.getFirst().isForcedBreak()) { @@ -514,7 +842,7 @@ private void addHeaderFooterAreas(List elements, TablePart part, RowPainter pain * TableStepper haven't been removed yet. */ if (pos instanceof TableContentPosition) { - lst.add((TableContentPosition) pos); + lst.add(pos); } } addTablePartAreas(lst, painter, part, true, true, true, lastOnPage); @@ -601,4 +929,124 @@ public int getBaseLength(int lengthBase, FObj fobj) { return tableLM.getBaseLength(lengthBase, fobj); } + /** + * essentially, do the same things as {@link TableContentLayoutManager#getNextKnuthElements(LayoutContext, int)}, + * but only do the bare minimum required to get the {@link MinOptMax} values for each column of the + * table with automatic layout. Thus, this computation must not have any side effects on the/any + * subsequent call to {@link TableContentLayoutManager#getNextKnuthElements(LayoutContext, int)}. + * @param context + * @param alignment + */ + public void determineAutoLayoutWidths(LayoutContext context, int alignment) { + List colspanningPGUs = new LinkedList(); + Table table = getTableLM().getTable(); + + TableRowIterator tempbodyIter = new TableRowIterator(table, TableRowIterator.BODY); + TableRowIterator tempheaderIter = null; + TableRowIterator tempfooterIter = null; + + if (table.getTableHeader() != null) { + tempheaderIter = new TableRowIterator(table, TableRowIterator.HEADER); + iterateOverTableRows(tempheaderIter, context, alignment, TableRowIterator.HEADER, colspanningPGUs); + } + + iterateOverTableRows(tempbodyIter, context, alignment, TableRowIterator.BODY, colspanningPGUs); + + if (table.getTableFooter() != null) { + tempfooterIter = new TableRowIterator(table, TableRowIterator.FOOTER); + iterateOverTableRows(tempfooterIter, context, alignment, TableRowIterator.FOOTER, colspanningPGUs); + } + + for (PrimaryGridUnit primary : colspanningPGUs) { + determineWidthOfPrimary(primary, context, alignment); + } + } + + /** + * To be used only during the preprocessing run which determines the dimensions of Tables with table-layout="auto". + * Iterates over all rows of the provided iterator (either for the header, footer or body of a table depending + * on the parameter bodyType). For each row, the contained {@link PrimaryGridUnit}s are taken to + * determine their widths based on their content. These widths are then propagated to the individual + * {@link TableColumn}s via {@link #setBaseLength(PrimaryGridUnit)}. Afterwards, the {@link PrimaryGridUnit}'s + * elements reset so that they are properly processed during the rendering run (this is necessary since the + * dimensions obtained for a column are still subject to changes).
+ * Based on + * {@link TableContentLayoutManager#getKnuthElementsForRowIterator(TableRowIterator, LayoutContext, int, int)} + * However, since we are only interested in the widths of the contained PGUs, most of the original method was + * removed. + * @param iter Iterator providing access to rows which belong either to the table's body, header or footer + * @param context layout context + * @param alignment + * @param bodyType indicates which part of a table is processed (actually not required) + */ + private void iterateOverTableRows(TableRowIterator iter, // returns indiv. rows + LayoutContext context, int alignment, int bodyType, + List colspanningPGUs) { + EffRow[] rowGroup; + while ((rowGroup = iter.getNextRowGroup()) != null) { + //RowGroupLayoutManager rowGroupLM = new RowGroupLayoutManager(getTableLM(), rowGroup, + // null); // the actual tablestepper might lead to undesired side effects! + /** + * based on RowGroupLayoutManager#createElementsForRowGroup + * initializes the PGUs of one row at a time + */ + for (EffRow row : rowGroup) { + for (GridUnit gu : row.getGridUnits()) { + if (gu.isPrimary()) { + PrimaryGridUnit primary = gu.getPrimary(); + + // during this iteration, the width of PGUs in entries which span multiple columns + // cannot be determined + if (primary.getCell().getNumberColumnsSpanned() > 1) { + LOG.debug("Will revisit later"); + colspanningPGUs.add(primary); + } else { + determineWidthOfPrimary(primary, context, alignment); + } + } + } + } + } + } + + private void determineWidthOfPrimary(PrimaryGridUnit primary, LayoutContext context, int alignment) { + // recursively retrieve (and thereby calculate the dimensions of) + // all KnuthElements of all contained LayoutManagers for the given cell + primary.createCellLM(); + TableCellLayoutManager cellLM = primary.getCellLM(); + cellLM.setParent(tableLM); + //Calculate width of cell + int spanWidth = 0; + Iterator colIter = tableLM.getTable().getColumns().stream() + .map(c -> (TableColumn) c) + .collect(Collectors.toList()) + .listIterator(primary.getColIndex()); + for (int i = 0, c = primary.getCell().getNumberColumnsSpanned(); i < c; i++) { + spanWidth += colIter.next().getColumnWidth().getValue(tableLM); + } + + LayoutContext childLC = LayoutContext.offspringOf(context); + childLC.setStackLimitBP(context.getStackLimitBP()); + childLC.setRefIPD(spanWidth); + + /* Works fine. See: fop/test/layoutengine/standard-testcases/table-layout_auto_single_column.xml + // TODO: ugly workaround to deal with one-column tables which would be rendered broken otherwise + if (tableLM.getTable().getColumns().size() == 1) { + childLC.setRefIPD(context.getRefIPD()); + } else { + childLC.setRefIPD(spanWidth); + } + */ + + //Get the element list for the cell contents + List elems = cellLM.getNextKnuthElements(childLC, alignment); + // temporarily assign these KnuthElements to the PGU to calculate its dimensions + primary.setElements(elems); + setBaseLength(primary, context); + + // reset the PGU (and thereby reset (even destroy?) all contained LayoutManagers) + // the dimensions, however, are still present in form of a MinOptMax! + primary.setElements(null); + } + } diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java index 284dbdede66..99e005151b4 100644 --- a/fop-core/src/main/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -221,7 +222,7 @@ public List getNextKnuthElements(LayoutContext context, int alignme getTable().getUserAgent().getEventBroadcaster()); eventProducer.tableFixedAutoWidthNotSupported(this, getTable().getLocator()); } - updateContentAreaIPDwithOverconstrainedAdjust(); + updateContentAreaIPDwithOverconstrainedAdjust(context); } int sumOfColumns = columns.getSumOfColumnWidths(this); if (!autoLayout && sumOfColumns > getContentAreaIPD()) { @@ -243,7 +244,7 @@ public List getNextKnuthElements(LayoutContext context, int alignme * for proportional-column-width() */ if (tableUnit == 0.0) { - tableUnit = columns.computeTableUnit(this); + tableUnit = columns.computeTableUnit(this, context); if (oldTableUnit > tableUnit && supportResize(fobj)) { tableUnit = oldTableUnit; } @@ -274,6 +275,24 @@ public List getNextKnuthElements(LayoutContext context, int alignme childLC.setRefIPD(context.getRefIPD()); childLC.copyPendingMarksFrom(context); + // width determination is required for any elements in auto-layout containers + if (isAutoLayout() || context.isChildOfAutoLayoutElement()) { + childLC.setChildOfAutoLayoutElement(true); + childLC.setInAutoLayoutDeterminationMode(true); + contentLM.determineAutoLayoutWidths(childLC, alignment); + + // determination mode ends only if the parent is not still in this mode + if (!context.isInAutoLayoutDeterminationMode()) { + childLC.setInAutoLayoutDeterminationMode(false); + } + + int maxCol = columns.computeOptimalColumnWidthsForAutoLayout(this, context, getTable().getWidth()); + // report the determined maximum width to the enquiring parent + if (context.isChildOfAutoLayoutElement() && context.isInAutoLayoutDeterminationMode()) { + context.setRefIPD(maxCol); + } + } + contentKnuthElements = contentLM.getNextKnuthElements(childLC, alignment); //Set index values on elements coming from the content LM for (Object contentKnuthElement : contentKnuthElements) { @@ -408,8 +427,7 @@ public void addAreas(PositionIterator parentIter, curBlockArea.setBPD(tableHeight); if (columnBackgroundAreas != null) { - for (Object columnBackgroundArea : columnBackgroundAreas) { - ColumnBackgroundInfo b = (ColumnBackgroundInfo) columnBackgroundArea; + for (ColumnBackgroundInfo b : columnBackgroundAreas) { TraitSetter.addBackground(b.backgroundArea, b.column.getCommonBorderPaddingBackground(), this, b.xShift, -b.backgroundArea.getYOffset(), @@ -524,6 +542,116 @@ public KeepProperty getKeepWithNextProperty() { return getTable().getKeepWithNext(); } + + /** + * Takes a {@link TableColumn} and looks up its {@link MinOptMax} width values. + * However, returns null for {@link TableColumn}s which have a static + * width value or any columns which do not have a {@link MinOptMax} object associated + * with them (i.e. columns which do not contain a {@link PrimaryGridUnit} but only + * {@link GridUnit}s which do not define a width themselves). + * + * @param tcol + * @param context + * @return {@link MinOptMax} or null + */ + public MinOptMax getPossibleWidths(TableColumn tcol, LayoutContext context) { + if (this.contentLM != null && (isAutoLayout() || context.isChildOfAutoLayoutElement())) { + if (tcol.isAutoLayout()) { + final MinOptMax length = this.contentLM.getBaseLength(tcol); + if (length != null) { + return length; + } + } else { + // column uses a static value instead + return null; + } + } + /** we may end up here if the table contains cells with invalid columns-spanned attribute + * UPDATE: since there is no such thing as an invalid columns-spanned attribute (columns + * are simply dynamically added as required) this case cannot be considered to be an error + * anymore - simply return null and let the caller take care of this case. */ + return null; + } + + /** {@inheritDoc} */ + final boolean isAutoLayout() { + return getTable().isAutoLayout(); + } + + /** {@inheritDoc}
Determination is based on the type of layout used for the table. */ + public int getMinimumIPD() { + int minimumIPD = -1; + int curMinIPD = 0; + if (contentLM != null) { + if (this.isAutoLayout()) { + curMinIPD = getMinimumIPDforAutoLayout(); + } else { + curMinIPD = getMinimumIPDforFixedLayout(); + } + // TODO: add Indents/Paddings/... + minimumIPD = Math.max(minimumIPD, curMinIPD); + } + return minimumIPD; + } + + /** + * obtains the width of the widest column and returns this width times the number of columns + * @return width of widest columns times number of columns + */ + private int getMinimumIPDforFixedLayout() { + int staticWidth = 0; + int widestMinWidthForAutoColumns = 0; + int curMinIPD = 0; + int autoColumns = 0; + for (Iterator iter = columns.iterator(); iter.hasNext(); ) { + TableColumn tcol = iter.next(); + + if (!tcol.isAutoLayout()) { + // a static column should always requires the same amount of space + staticWidth += tcol.getColumnWidth().getValue(this); + } else { + MinOptMax width = contentLM.getBaseLength(tcol); + if (width != null) { + widestMinWidthForAutoColumns = Math.max(widestMinWidthForAutoColumns, width.getMin()); + } + autoColumns++; + } + } + return widestMinWidthForAutoColumns * autoColumns + staticWidth; + } + + /** + * obtains the width of each individual column and returns the sum of these widths + * @return sum of the width of all columns + */ + private int getMinimumIPDforAutoLayout() { + int curMinIPD = 0; + for (Iterator iter = columns.iterator(); iter.hasNext(); ) { + TableColumn tcol = iter.next(); + MinOptMax width = contentLM.getBaseLength(tcol); + if (width != null) { + curMinIPD += width.getMin(); + } + } + return curMinIPD; + } + + /** + * Used only for auto layout tables. + * Forwards the new width values ({@link MinOptMax} widths) + * of a column to the appropriate {@link TableContentLayoutManager}. + * @param tcol {@link TableColumn} which acts as key to get/set the width + * @param mom {@link MinOptMax} containing the appropriate values for the column + */ + public void setPossibleWidths(TableColumn tcol, MinOptMax mom) { + assert this.contentLM != null; + //assert isAutoLayout(); + if (tcol.isAutoLayout()) { + this.contentLM.setBaseLength(tcol, mom); + } + } + + // --------- Property Resolution related functions --------- // /** @@ -536,6 +664,12 @@ public int getBaseLength(int lengthBase, FObj fobj) { case LengthBase.CONTAINING_BLOCK_WIDTH: return getContentAreaIPD(); case LengthBase.TABLE_UNITS: + if (this.contentLM != null && isAutoLayout()) { + if (((TableColumn) fobj).isAutoLayout()) { + final MinOptMax length = this.contentLM.getBaseLength(fobj); + return length == null ? 0 : length.getOpt(); + } + } return (int) this.tableUnit; default: log.error("Unknown base type for LengthBase."); diff --git a/fop-core/src/main/java/org/apache/fop/render/rtf/RTFHandler.java b/fop-core/src/main/java/org/apache/fop/render/rtf/RTFHandler.java index 08960172a5d..d23f185a7ac 100644 --- a/fop-core/src/main/java/org/apache/fop/render/rtf/RTFHandler.java +++ b/fop-core/src/main/java/org/apache/fop/render/rtf/RTFHandler.java @@ -1520,7 +1520,7 @@ private void prepareTable(Table tab) { ColumnSetup columnSetup = new ColumnSetup(tab); //int sumOfColumns = columnSetup.getSumOfColumnWidths(percentManager); float tableWidth = percentManager.getBaseLength(LengthBase.CONTAINING_BLOCK_WIDTH, tab); - float tableUnit = columnSetup.computeTableUnit(percentManager, Math.round(tableWidth)); + float tableUnit = columnSetup.computeTableUnit(percentManager, Math.round(tableWidth), null); percentManager.setTableUnit(tab, Math.round(tableUnit)); } diff --git a/fop-core/src/main/resources/org/apache/fop/layoutmgr/BlockLevelEventProducer.xml b/fop-core/src/main/resources/org/apache/fop/layoutmgr/BlockLevelEventProducer.xml index de040bdfe8d..33569d176d7 100644 --- a/fop-core/src/main/resources/org/apache/fop/layoutmgr/BlockLevelEventProducer.xml +++ b/fop-core/src/main/resources/org/apache/fop/layoutmgr/BlockLevelEventProducer.xml @@ -33,4 +33,5 @@ Content that cannot handle IPD changes is flowing to a narrower page. Part of it may be clipped by the page border. A layout has reached {partCount} part(s). page-position="last" master reference missing.{{locator}} + The minimal width required for the auto-layout of a table's columns is bigger than the available space ({effIPD}mpt > {maxIPD}mpt). Part of it may overflow the page border. {{locator}} diff --git a/fop/test/layoutengine/standard-testcases/table-layout_auto_nested_1.xml b/fop/test/layoutengine/standard-testcases/table-layout_auto_nested_1.xml new file mode 100644 index 00000000000..14bd6cd7946 --- /dev/null +++ b/fop/test/layoutengine/standard-testcases/table-layout_auto_nested_1.xml @@ -0,0 +1,122 @@ + + + + + +

+

+
+ + + + + + + + + + + + + + + + + + A: layout="auto" and a lot of additional text to enforce a linebreak + + + B with fixed table + + + + + + B1: layout="fixed" AndNoCutoff 1234567890123456789 + + + B2 + + + + + B3 + + + + B4 with auto table + + + + + B4A: layout="auto" + + + B4B + + + + + B4C with more text + + + B4D only + + + + + + + + + + + + + + + C + + + D + + + + + + + + + + + +
\ No newline at end of file diff --git a/fop/test/layoutengine/standard-testcases/table-layout_auto_nested_2.xml b/fop/test/layoutengine/standard-testcases/table-layout_auto_nested_2.xml new file mode 100644 index 00000000000..0c6cb96fbef --- /dev/null +++ b/fop/test/layoutengine/standard-testcases/table-layout_auto_nested_2.xml @@ -0,0 +1,124 @@ + + + + + +

+

+
+ + + + + + + + + + + + + + + + + + A: layout="auto" and a lot of additional text to enforce a linebreak + + + B with auto table + + + + + + + + B1: layout="auto" AndNoCutoff 1234567890123456789 + + + B2 + + + + + B3 + + + + B4 with auto table + + + + + B4A: layout="auto" + + + B4B + + + + + B4C with more text + + + B4D only + + + + + + + + + + + + + + + C + + + D + + + + + + + + + + + +
\ No newline at end of file diff --git a/fop/test/layoutengine/standard-testcases/table-layout_auto_one_column.xml b/fop/test/layoutengine/standard-testcases/table-layout_auto_one_column.xml new file mode 100644 index 00000000000..3fecbec96c3 --- /dev/null +++ b/fop/test/layoutengine/standard-testcases/table-layout_auto_one_column.xml @@ -0,0 +1,77 @@ + + + + + +

+

+
+ + + + + + + + + + + + + + + + + + a1 + + + + + + + b1bbbbbbbbbbbbb + + + + + + + c1 + + + + + + + + + + + + +
\ No newline at end of file diff --git a/fop/test/layoutengine/standard-testcases/table-layout_auto_simple_nested.xml b/fop/test/layoutengine/standard-testcases/table-layout_auto_simple_nested.xml new file mode 100644 index 00000000000..7c3c9aadc08 --- /dev/null +++ b/fop/test/layoutengine/standard-testcases/table-layout_auto_simple_nested.xml @@ -0,0 +1,100 @@ + + + + + +

+

+
+ + + + + + + + + + + + + + + + entry1 + + + + + + + + + + + + inner1 + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/fop/test/layoutengine/standard-testcases/table-layout_auto_spanned-columns_1.xml b/fop/test/layoutengine/standard-testcases/table-layout_auto_spanned-columns_1.xml new file mode 100644 index 00000000000..92d92d8e34b --- /dev/null +++ b/fop/test/layoutengine/standard-testcases/table-layout_auto_spanned-columns_1.xml @@ -0,0 +1,81 @@ + + + + + +

+

+
+ + + + + + + + + + + + + + + In this table, cell E spans over columns 2 and 3, but requires more width than these two provide. + Therefore, both columns are widened proportionally so that the required width can be provided. + + + + + + + + A + + + B + + + C + + + + + + + D + + + This cell needs more width than width('B') + width('C') + + + + + + + + + + + +
diff --git a/fop/test/layoutengine/standard-testcases/table-layout_auto_static-columns_1.xml b/fop/test/layoutengine/standard-testcases/table-layout_auto_static-columns_1.xml new file mode 100644 index 00000000000..117f7ef4aa3 --- /dev/null +++ b/fop/test/layoutengine/standard-testcases/table-layout_auto_static-columns_1.xml @@ -0,0 +1,88 @@ + + + + + +

+

+
+ + + + + + + + + + + + + + Columns 2 and 4 have a static width of '1cm' and, thus, are not widened to fit the content of the col-spanning cell 'G' - only column 3 may be widened + + + + + + + + + A + + + BB + + + CC + + + DD + + + EEEEEEEEEEE EEEEEEEEEEEEEEEEEEEE EEEEEEEEEEEEEEEEEEEEEEEE EEEEEEEEEEEEE + + + + + + F + + + GGGGGGGGGGGGGGGGGG + + + H + + + + + + + + + + + +
\ No newline at end of file diff --git a/fop/test/layoutengine/standard-testcases/table-layout_auto_static-columns_2.xml b/fop/test/layoutengine/standard-testcases/table-layout_auto_static-columns_2.xml new file mode 100644 index 00000000000..91501b9ce31 --- /dev/null +++ b/fop/test/layoutengine/standard-testcases/table-layout_auto_static-columns_2.xml @@ -0,0 +1,98 @@ + + + + + +

+

+
+ + + + + + + + + + + + + + + In this table, columns 2,3, and 4 have a static width of '1cm' and cannot be widened. + The col-spanning PGU in cell G requires more space, than these three columns provide (3 * '1cm' + table cell indentations < minIPD of G). + With no columns to expand, the content of column G overflows into cell H. + + + + + + + + + + + + A + + + 1cm + + + 1cm + + + 1cm + + + EEEEEEEEEEE EEEEEEEEEEEEEEEEEEEE EEEEEEEEEEEEEEEEEEEEEEEE EEEEEEEEEEEEE + + + + + + F + + + overflowing_cell (minIPD>3cm) + + + H + + + + + + Expected warning1: 'No columns to resize to fit a table-cell spanning these columns, expect overflows' + Expected warning2: 'The contents of fo:block line 1 exceed the available area in the inline-progression direction by 5318 millipoints. (See position 56:26)' + + + + + + + + +
\ No newline at end of file diff --git a/fop/test/layoutengine/standard-testcases/table-layout_auto_two_columns.xml b/fop/test/layoutengine/standard-testcases/table-layout_auto_two_columns.xml new file mode 100644 index 00000000000..3f1068563d4 --- /dev/null +++ b/fop/test/layoutengine/standard-testcases/table-layout_auto_two_columns.xml @@ -0,0 +1,93 @@ + + + + + +

+

+
+ + + + + + + + + + + + + + + + + + + a1 + + + + + a1 + + + + + + + b1bbbbbbbbbbbbb + + + + + b1 + + + + + + + c1 + + + + + ccccccccccccccccc1 + + + + + + + + + + + + +
\ No newline at end of file