[Mathieu Malaterre] > It would be nice to start integrating up to date patch from the famous > issue #2469 in fop so that Debian user get the feeling that auto table > layout is finally supported in fop: > > https://issues.apache.org/jira/browse/FOP-2469
I had a go at this, using the Debian fop git repository from <URL: https://salsa.debian.org/java-team/fop.git >. The patch applied with minimal manual work (two files had changed slightly), but the code do not build. This is the build failure: [javac] /home/pere/src/debian/fop/src/java/org/apache/fop/servlet/FopServlet.java:99: error: cannot find symbol [javac] transFactory.setAttribute(javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD, ""); [javac] ^ [javac] symbol: variable ACCESS_EXTERNAL_DTD [javac] location: class XMLConstants [javac] /home/pere/src/debian/fop/src/java/org/apache/fop/servlet/FopServlet.java:100: error: cannot find symbol [javac] transFactory.setAttribute(javax.xml.XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); [javac] ^ [javac] symbol: variable ACCESS_EXTERNAL_STYLESHEET [javac] location: class XMLConstants I see from <URL: https://docs.oracle.com/javase/tutorial/jaxp/properties/properties.html > these were new in JAXP 1.5, but fail to understand why they are not available when building with javac in jdk 10. :( Any ideas. My refreshed patch is attached. Add it to debian/patches/ and list it in series to give it a go yourself. -- Happy hacking Petter Reinholdtsen
From: https://issues.apache.org/jira/secure/attachment/12738567/2015-06-09-LM-to-LC-refactoring-update.patch Index: fop/src/java/org/apache/fop/fo/flow/table/PrimaryGridUnit.java =================================================================== --- fop.orig/src/java/org/apache/fop/fo/flow/table/PrimaryGridUnit.java 2018-06-20 04:07:13.807266008 +0000 +++ fop/src/java/org/apache/fop/fo/flow/table/PrimaryGridUnit.java 2018-06-20 04:12:35.027757086 +0000 @@ -19,6 +19,8 @@ package org.apache.fop.fo.flow.table; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import org.apache.fop.fo.Constants; @@ -26,7 +28,10 @@ import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.layoutmgr.ElementListUtils; import org.apache.fop.layoutmgr.Keep; +import org.apache.fop.layoutmgr.KnuthElement; +import org.apache.fop.layoutmgr.LayoutContext; import org.apache.fop.layoutmgr.table.TableCellLayoutManager; +import org.apache.fop.layoutmgr.table.TableLayoutManager; /** * This class represents a primary grid unit of a spanned cell. This is the "before-start" @@ -239,11 +244,9 @@ 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; } @@ -413,4 +416,43 @@ this.breakAfter = breakAfter; } + /** + * recursively retrieves (and thereby implicitly calculates the dimensions of) all KnuthElements of all + * LayoutManagers contained by this {@link PrimaryGridUnit} (i.e. by its + * {@link TableCellLayoutManager}).<br> + * Based on {@link #createElementsForRowGroup(LayoutContext, int, int, LinkedList)}. + * @param context the layout context to be used to retrieve the {@link KnuthElement}s + * @param alignment + * @param tableLM + * @return a list of {@link KnuthElement}s + */ + public List getNextKnuthElementsFromPrimary(LayoutContext context, int alignment, TableLayoutManager tableLM) { + this.createCellLM(); + this.getCellLM().setParent(tableLM); + //Calculate width of cell + int spanWidth = 0; + Iterator colIter = tableLM.getTable().getColumns().listIterator( + this.getColIndex()); + for (int i = 0, c = this.getCell().getNumberColumnsSpanned(); i < c; i++) { + spanWidth += ((TableColumn) colIter.next()).getColumnWidth().getValue( + tableLM); + } + LayoutContext childLC = LayoutContext.newInstance(); + childLC.setChildOfAutoLayoutElement(context.isChildOfAutoLayoutElement()); + childLC.setInAutoLayoutDeterminationMode(context.isInAutoLayoutDeterminationMode()); + + childLC.setStackLimitBP(context.getStackLimitBP()); + + // 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 = this.getCellLM().getNextKnuthElements(childLC, alignment); + return elems; + } + } Index: fop/src/java/org/apache/fop/fo/flow/table/Table.java =================================================================== --- fop.orig/src/java/org/apache/fop/fo/flow/table/Table.java 2018-06-20 04:07:13.807266008 +0000 +++ fop/src/java/org/apache/fop/fo/flow/table/Table.java 2018-06-20 04:10:47.518254162 +0000 @@ -152,10 +152,11 @@ eventProducer.nonAutoBPDOnTable(this, getLocator()); // Anyway, the bpd of a table is not used by the layout code } - if (tableLayout == EN_AUTO) { - getFOValidationEventProducer().unimplementedFeature(this, getName(), - "table-layout=\"auto\"", getLocator()); - } + // no 'TBD' message required anymore? +// if (tableLayout == EN_AUTO) { +// getFOValidationEventProducer().unimplementedFeature(this, getName(), +// "table-layout=\"auto\"", getLocator()); +// } if (!isSeparateBorderModel()) { if (borderCollapse == EN_COLLAPSE_WITH_PRECEDENCE) { getFOValidationEventProducer().unimplementedFeature(this, getName(), Index: fop/src/java/org/apache/fop/fo/flow/table/TableColumn.java =================================================================== --- fop.orig/src/java/org/apache/fop/fo/flow/table/TableColumn.java 2018-06-20 04:07:13.807266008 +0000 +++ fop/src/java/org/apache/fop/fo/flow/table/TableColumn.java 2018-06-20 04:10:47.522254218 +0000 @@ -275,4 +275,7 @@ return isHeader; } + public final boolean isAutoLayout() { + return getColumnWidth() instanceof TableColLength; + } } Index: fop/src/java/org/apache/fop/layoutmgr/AbstractBaseLayoutManager.java =================================================================== --- fop.orig/src/java/org/apache/fop/layoutmgr/AbstractBaseLayoutManager.java 2018-06-20 04:07:13.823266232 +0000 +++ fop/src/java/org/apache/fop/layoutmgr/AbstractBaseLayoutManager.java 2018-06-20 04:10:47.522254218 +0000 @@ -20,6 +20,7 @@ package org.apache.fop.layoutmgr; import java.util.List; +import java.util.ListIterator; import java.util.Stack; import org.apache.commons.logging.Log; @@ -280,4 +281,35 @@ public void recreateChildrenLMs() { } + + /** {@inheritDoc} */ + public boolean isAutoLayout() { + final LayoutManager parent = getParent(); + return parent == null ? false : parent.isAutoLayout(); + } + +// /** {@inheritDoc} */ +// public boolean isAutoLayoutDeterminationMode() { +// final LayoutManager parent = getParent(); +// return parent == null ? false : parent.isAutoLayoutDeterminationMode(); +// } + +// /** {@inheritDoc} */ +// public boolean hasAutoLayoutParent() { +// final LayoutManager parent = getParent(); +// return parent == null ? false : parent.hasAutoLayoutParent(); +// } + + /** {@inheritDoc} */ + 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); + } + return minimumIPD; + } + } Index: fop/src/java/org/apache/fop/layoutmgr/BlockLevelEventProducer.java =================================================================== --- fop.orig/src/java/org/apache/fop/layoutmgr/BlockLevelEventProducer.java 2018-06-20 04:07:13.827266288 +0000 +++ fop/src/java/org/apache/fop/layoutmgr/BlockLevelEventProducer.java 2018-06-20 04:10:47.522254218 +0000 @@ -59,6 +59,18 @@ 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 * @param loc the location of the error or null Index: fop/src/java/org/apache/fop/layoutmgr/BlockLevelEventProducer.xml =================================================================== --- fop.orig/src/java/org/apache/fop/layoutmgr/BlockLevelEventProducer.xml 2018-06-20 04:07:13.827266288 +0000 +++ fop/src/java/org/apache/fop/layoutmgr/BlockLevelEventProducer.xml 2018-06-20 04:11:31.658871246 +0000 @@ -33,4 +33,5 @@ <message key="nonRestartableContentFlowingToNarrowerPage">Content that cannot handle IPD changes is flowing to a narrower page. Part of it may be clipped by the page border.</message> <message key="layoutHasReachedParts">A layout has reached {partCount} part(s).</message> <message key="lastPageMasterReferenceMissing">page-position="last" master reference missing.{{locator}}</message> + <message key="columnsInAutoTableTooWide">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}}</message> </catalogue> Index: fop/src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java =================================================================== --- fop.orig/src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java 2018-06-20 04:07:13.827266288 +0000 +++ fop/src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java 2018-06-20 04:10:47.522254218 +0000 @@ -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,7 +198,7 @@ */ protected int updateContentAreaIPDwithOverconstrainedAdjust() { int ipd = referenceIPD - (startIndent + endIndent); - if (ipd < 0) { + if (ipd < 0 && !isAutoLayout()) { //5.3.4, XSL 1.0, Overconstrained Geometry log.debug("Adjusting end-indent based on overconstrained geometry rules for " + fobj); BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( @@ -296,6 +297,21 @@ emptyStack = true; } + final int ipd = childLC.getRefIPD(); + if (currentChildLM instanceof LineLayoutManager) { +// if (getContentAreaIPD() < ipd && (isAutoLayout() || getParent().hasAutoLayoutParent())) { + if (getContentAreaIPD() < ipd && (isAutoLayout() || context.isChildOfAutoLayoutElement())) { + this.referenceIPD = this.startIndent + ipd + this.endIndent; + updateContentAreaIPDwithOverconstrainedAdjust(); + context.setRefIPD(this.referenceIPD); + } +// } else if (this.referenceIPD < ipd && (isAutoLayout() || getParent().hasAutoLayoutParent())) { + } else if (this.referenceIPD < ipd && (isAutoLayout() || context.isChildOfAutoLayoutElement())) { + this.referenceIPD = ipd; + updateContentAreaIPDwithOverconstrainedAdjust(); + context.setRefIPD(this.referenceIPD); + } + if (contentList.isEmpty()) { // propagate keep-with-previous up from the first child context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending()); @@ -388,6 +404,8 @@ childLC.copyPendingMarksFrom(context); childLC.setStackLimitBP(context.getStackLimitBP()); childLC.setRefIPD(referenceIPD); + childLC.setChildOfAutoLayoutElement(context.isChildOfAutoLayoutElement()); + childLC.setInAutoLayoutDeterminationMode(context.isInAutoLayoutDeterminationMode()); return childLC; } Index: fop/src/java/org/apache/fop/layoutmgr/LayoutContext.java =================================================================== --- fop.orig/src/java/org/apache/fop/layoutmgr/LayoutContext.java 2018-06-20 04:07:13.827266288 +0000 +++ fop/src/java/org/apache/fop/layoutmgr/LayoutContext.java 2018-06-20 04:10:47.522254218 +0000 @@ -125,7 +125,11 @@ private int disableColumnBalancing; - public static LayoutContext newInstance() { + private boolean childOfAutoLayoutElement; + + private boolean inAutoLayoutDeterminationMode; + + public static LayoutContext newInstance() { return new LayoutContext(0); } @@ -693,5 +697,20 @@ public void setTreatAsArtifact(boolean treatAsArtifact) { setFlags(TREAT_AS_ARTIFACT, treatAsArtifact); } + + public void setChildOfAutoLayoutElement(boolean b) { + childOfAutoLayoutElement = b; + } + + public boolean isChildOfAutoLayoutElement() { + return childOfAutoLayoutElement; + } + + public boolean isInAutoLayoutDeterminationMode() { + return inAutoLayoutDeterminationMode; + } + public void setInAutoLayoutDeterminationMode(boolean b) { + inAutoLayoutDeterminationMode = b; + } } Index: fop/src/java/org/apache/fop/layoutmgr/LayoutManager.java =================================================================== --- fop.orig/src/java/org/apache/fop/layoutmgr/LayoutManager.java 2018-06-20 04:07:13.827266288 +0000 +++ fop/src/java/org/apache/fop/layoutmgr/LayoutManager.java 2018-06-20 04:10:47.522254218 +0000 @@ -239,6 +239,11 @@ * @return the same Position but with a position index */ Position notifyPos(Position pos); + + /** + * @return true if table-layout="auto" + */ + boolean isAutoLayout(); /** * Re-initializes this layout manager in order to re-generate its Knuth @@ -271,4 +276,21 @@ */ List getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack, Position positionAtIPDChange, LayoutManager restartAtLM); + + + /** + * 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(); + +// /** returns true if any parent of the current {@link LayoutManager} is currently in AutoLayoutDeterminationMode */ +// boolean isAutoLayoutDeterminationMode(); + +// /** +// * returns true if any parent of the current {@link LayoutManager} uses automatic layout (which currently only +// * applies for tables) and false otherwise. +// */ +// boolean hasAutoLayoutParent(); } Index: fop/src/java/org/apache/fop/layoutmgr/inline/ExternalGraphicLayoutManager.java =================================================================== --- fop.orig/src/java/org/apache/fop/layoutmgr/inline/ExternalGraphicLayoutManager.java 2018-06-20 04:07:13.831266343 +0000 +++ fop/src/java/org/apache/fop/layoutmgr/inline/ExternalGraphicLayoutManager.java 2018-06-20 04:10:47.522254218 +0000 @@ -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; @@ -44,6 +45,17 @@ protected Area getChildArea() { return new Image(((ExternalGraphic) fobj).getSrc()); } + + /** {@inheritDoc}<br>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; + } + } } Index: fop/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java =================================================================== --- fop.orig/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java 2018-06-20 04:07:13.831266343 +0000 +++ fop/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java 2018-06-20 04:10:47.522254218 +0000 @@ -170,7 +170,7 @@ 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 +228,13 @@ 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 @@ -443,7 +448,7 @@ // 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 @@ -499,7 +504,7 @@ 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), @@ -507,7 +512,7 @@ availableShrink, availableStretch, difference, ratio, 0, startIndent, endIndent, lineLead + lineFollow, - ipd, spaceBefore, spaceAfter, + ipd.getOpt(), spaceBefore, spaceAfter, lineLead); } } @@ -618,7 +623,7 @@ context.getWritingMode()); } context.setAlignmentContext(alignmentContext); - ipd = context.getRefIPD(); + ipd = MinOptMax.getInstance(context.getRefIPD()); //PHASE 1: Create Knuth elements if (knuthParagraphs == null) { @@ -642,6 +647,7 @@ return createLineBreaks(context.getBPAlignment(), context); } + /** * Get a sequence of KnuthElements representing the content * of the node assigned to the LM. @@ -675,7 +681,7 @@ return null; } - ipd = context.getRefIPD(); + ipd = MinOptMax.getInstance(context.getRefIPD()); //PHASE 2: Create line breaks return createLineBreaks(context.getBPAlignment(), context); } @@ -686,6 +692,8 @@ */ private void collectInlineKnuthElements(LayoutContext context) { LayoutContext inlineLC = LayoutContext.copyOf(context); + inlineLC.setChildOfAutoLayoutElement(context.isChildOfAutoLayoutElement()); + inlineLC.setInAutoLayoutDeterminationMode(context.isInAutoLayoutDeterminationMode()); // convert all the text in a sequence of paragraphs made // of KnuthBox, KnuthGlue and KnuthPenalty objects @@ -695,6 +703,8 @@ Paragraph lastPar = null; + int minimumIPD = 0; + int maxSumIPD = 0; InlineLevelLayoutManager curLM; while ((curLM = (InlineLevelLayoutManager) getChildLM()) != null) { List inlineElements = curLM.getNextKnuthElements(inlineLC, effectiveAlignment); @@ -734,6 +744,22 @@ ListIterator iter = inlineElements.listIterator(); while (iter.hasNext()) { KnuthSequence sequence = (KnuthSequence) iter.next(); + + // get to know the width of the contained elements + if (context.isInAutoLayoutDeterminationMode()) { + final ListIterator i = sequence.listIterator(); + + while (i.hasNext()) { + final KnuthElement element = (KnuthElement) i.next(); + // 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(); + } +// log.debug("Line with minIPD:=" + minimumIPD); + } // the sequence contains inline Knuth elements if (sequence.isInlineSequence()) { // look at the last element @@ -775,7 +801,7 @@ 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); @@ -802,6 +828,22 @@ 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); } @@ -835,6 +877,7 @@ return postProcessLineBreaks(alignment, context); } + /** * Find the optimal linebreaks for a paragraph * @param alignment alignment of the paragraph @@ -855,7 +898,7 @@ 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); @@ -1477,6 +1520,10 @@ Position pos = parentIter.next(); boolean isLastPosition = !parentIter.hasNext(); if (pos instanceof LineBreakPosition) { + // propagate the desired space requirements + if (isAutoLayout()) { + context.setRefIPD(ipd.getOpt()); + } addInlineArea(context, (LineBreakPosition) pos, isLastPosition); } else if ((pos instanceof NonLeafPosition) && pos.generatesAreas()) { addBlockArea(context, pos, isLastPosition); Index: fop/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java =================================================================== --- fop.orig/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java 2018-06-20 04:07:13.831266343 +0000 +++ fop/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java 2018-06-20 04:10:47.522254218 +0000 @@ -131,6 +131,8 @@ private final Position auxiliaryPosition = new LeafPosition(this, -1); + private int minimumIPD = -1; + /** * Create a Text layout manager. * @@ -873,12 +875,42 @@ 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.<br> + * 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)); @@ -1465,5 +1497,15 @@ + ", len = " + foText.length() + "}"; } + + /** + * 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; + } } Index: fop/src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java =================================================================== --- fop.orig/src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java 2018-06-20 04:07:13.835266399 +0000 +++ fop/src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java 2018-06-20 04:10:47.522254218 +0000 @@ -19,13 +19,13 @@ package org.apache.fop.layoutmgr.table; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.apache.fop.datatypes.Length; import org.apache.fop.datatypes.PercentBaseContext; import org.apache.fop.fo.FONode; @@ -33,7 +33,10 @@ 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; @@ -47,8 +50,8 @@ private Table table; private WritingModeTraitsGetter wmTraits; - private List columns = new java.util.ArrayList(); - private List colWidths = new java.util.ArrayList(); + private List<TableColumn> columns = new java.util.ArrayList<TableColumn>(); + private List<Length> colWidths = new java.util.ArrayList<Length>(); private int maxColIndexReferenced; @@ -86,9 +89,9 @@ //Post-processing the list (looking for gaps) //TODO The following block could possibly be removed int pos = 1; - ListIterator ppIter = columns.listIterator(); + ListIterator<TableColumn> ppIter = columns.listIterator(); while (ppIter.hasNext()) { - TableColumn col = (TableColumn)ppIter.next(); + TableColumn col = ppIter.next(); if (col == 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); @@ -125,9 +128,9 @@ } } } - return (TableColumn) columns.get(size - 1); + return columns.get(size - 1); } else { - return (TableColumn) columns.get(index - 1); + return columns.get(index - 1); } } @@ -146,7 +149,7 @@ } /** @return an Iterator over all columns */ - public Iterator iterator() { + public Iterator<TableColumn> iterator() { return this.columns.iterator(); } @@ -174,7 +177,7 @@ for (int i = columns.size(); --i >= 0;) { if (columns.get(i) != null) { - col = (TableColumn) columns.get(i); + col = columns.get(i); colWidth = col.getColumnWidth(); colWidths.add(0, colWidth); } @@ -187,10 +190,11 @@ * [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); } /** @@ -199,9 +203,10 @@ * * @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; @@ -211,8 +216,8 @@ * and work out the total number of factors to use to distribute * the remaining space (if any) */ - for (Iterator i = colWidths.iterator(); i.hasNext();) { - Length colWidth = (Length) i.next(); + for (Iterator<Length> i = colWidths.iterator(); i.hasNext();) { + Length colWidth = i.next(); if (colWidth != null) { sumCols += colWidth.getValue(percentBaseContext); if (colWidth instanceof RelativeNumericProperty) { @@ -230,6 +235,14 @@ if (sumCols < contentAreaIPD) { unit = (contentAreaIPD - sumCols) / factors; } else { + // this warning occurs during the preprocessing (AutoLayoutDeterminationMode) + // and can be ignored in these cases. + if (percentBaseContext instanceof TableLayoutManager) { + TableLayoutManager tlm = (TableLayoutManager) percentBaseContext; + if (context.isInAutoLayoutDeterminationMode()) { + return unit; + } + } log.warn("No space remaining to distribute over columns."); } } @@ -268,7 +281,7 @@ for (int i = (col + nrColSpan - 1), nc = colWidths.size(); ++i < nc;) { int effCol = i; if (colWidths.get(effCol) != null) { - xoffset += ((Length) colWidths.get(effCol)).getValue(context); + xoffset += colWidths.get(effCol).getValue(context); } } return xoffset; @@ -289,7 +302,7 @@ effCol = colWidths.size() - 1; } if (colWidths.get(effCol) != null) { - xoffset += ((Length) colWidths.get(effCol)).getValue(context); + xoffset += colWidths.get(effCol).getValue(context); } } return xoffset; @@ -308,10 +321,185 @@ effIndex = colWidths.size() - 1; } if (colWidths.get(effIndex) != null) { - sum += ((Length) colWidths.get(effIndex)).getValue(context); + sum += colWidths.get(effIndex).getValue(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 + * @return int maximum width to be propagated to containing layout manager or -1 + */ + public int computeOptimalColumnWidthsForAutoLayout(TableLayoutManager tLM, LayoutContext context) { + int maxSumCols = 0; // collects OPT values of the individual columns + int minSumCols = 0; + int contentAreaIPD = tLM.getContentAreaIPD(); + + ListIterator<TableColumn> colIter = columns.listIterator(); + while (colIter.hasNext()) { + TableColumn tcol = colIter.next(); + 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 (tLM.getParent().hasAutoLayoutParent() && tLM.getParent().isAutoLayoutDeterminationMode()) { + 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<TableColumn> columnsToProcess = new ArrayList<TableColumn>(); + 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()); + } + 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 { + 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) + } + } +// return true; + } + } + } +// return false; + 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 + * <ul> + * <li> the remaining columns without the column we just changed</li> + * <li> the remaining area - the minimal width of the column we just changed</li> + * <li> an updated factor (based on the remaining area and the remaining columns)</li> + * </ul> + * @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 + * @param 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<TableColumn> 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 shrinked down 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 shrinked 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; + } + } Index: fop/src/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java =================================================================== --- fop.orig/src/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java 2018-06-20 04:07:13.835266399 +0000 +++ fop/src/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java 2018-06-20 04:10:47.526254273 +0000 @@ -35,6 +35,7 @@ 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.KnuthElement; import org.apache.fop.layoutmgr.LayoutContext; import org.apache.fop.traits.MinOptMax; import org.apache.fop.util.BreakUtil; @@ -115,6 +116,8 @@ LayoutContext childLC = LayoutContext.newInstance(); childLC.setStackLimitBP(context.getStackLimitBP()); //necessary? childLC.setRefIPD(spanWidth); + childLC.setChildOfAutoLayoutElement(context.isChildOfAutoLayoutElement()); + childLC.setInAutoLayoutDeterminationMode(context.isInAutoLayoutDeterminationMode()); //Get the element list for the cell contents List elems = primary.getCellLM().getNextKnuthElements( @@ -130,6 +133,7 @@ returnList.addAll(elements); } + /** * Calculate the heights of the rows in the row group, see CSS21, 17.5.3 Table height * algorithms. Index: fop/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java =================================================================== --- fop.orig/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java 2018-06-20 04:07:13.835266399 +0000 +++ fop/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java 2018-06-20 04:10:47.526254273 +0000 @@ -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; @@ -165,6 +166,36 @@ return startIndent + endIndent; } + /** {@inheritDoc}<br>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} */ + public 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} */ @@ -186,9 +217,21 @@ // curLM is a ? childLC.setStackLimitBP(context.getStackLimitBP().minus(stackLimit)); childLC.setRefIPD(cellIPD); + childLC.setChildOfAutoLayoutElement(context.isChildOfAutoLayoutElement()); + childLC.setInAutoLayoutDeterminationMode(context.isInAutoLayoutDeterminationMode()); // get elements from curLM returnedList = curLM.getNextKnuthElements(childLC, alignment); + + final int ipd = childLC.getRefIPD() + getIPIndents(); + +// if (this.referenceIPD < ipd && (isAutoLayout() || getParent().hasAutoLayoutParent())) { + 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"); } Index: fop/src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java =================================================================== --- fop.orig/src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java 2018-06-20 04:07:13.835266399 +0000 +++ fop/src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java 2018-06-20 04:10:47.526254273 +0000 @@ -19,6 +19,8 @@ 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; @@ -27,15 +29,16 @@ 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.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 +56,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 +81,8 @@ private TableStepper stepper; + private final Map<TableColumn, MinOptMax> baseLength = new HashMap<TableColumn, MinOptMax>(); + private boolean headerIsBeingRepeated; private boolean atLeastOnce; @@ -137,6 +143,267 @@ } /** + * 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 <code>key</code> + */ + 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 + * <code>key</code> + */ + public final MinOptMax getBaseLength(final FObj key) { + return (MinOptMax) 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 + 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<TableColumn> columnsToWiden = new ArrayList<TableColumn>(); + 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.<br> + * 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 (<b>columnsToWiden</b>) 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; + } + + public 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 +424,14 @@ if (headerIter != null && headerList == null) { this.headerList = getKnuthElementsForRowIterator( headerIter, context, alignment, TableRowIterator.HEADER); + + 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 +464,20 @@ if (footerIter != null && footerList == null) { this.footerList = getKnuthElementsForRowIterator( footerIter, context, alignment, TableRowIterator.FOOTER); + + 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 +500,24 @@ } returnList.addAll(getKnuthElementsForRowIterator( bodyIter, context, alignment, TableRowIterator.BODY)); + + 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 && ((ListElement)returnList.getFirst()).isForcedBreak()) { @@ -599,4 +906,100 @@ 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 childLC + * @param alignment + */ + public void determineAutoLayoutWidths(LayoutContext context, int alignment) { + 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); + } + + iterateOverTableRows(tempbodyIter, context, alignment, TableRowIterator.BODY); + + if (table.getTableFooter() != null) { + tempfooterIter = new TableRowIterator(table, TableRowIterator.FOOTER); + iterateOverTableRows(tempfooterIter, context, alignment, TableRowIterator.FOOTER); + } + } + + /** + * 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 <code>bodyType</code>). 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). <br> + * 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) { + EffRow[] rowGroup; + List colspanningPGUs = new LinkedList(); + 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 + */ + EffRow row; + for (int rgi = 0; rgi < rowGroup.length; rgi++) { + row = rowGroup[rgi]; + for (Iterator iterGU = row.getGridUnits().iterator(); iterGU.hasNext();) { + GridUnit gu = (GridUnit) iterGU.next(); + if (gu.isPrimary()) { + PrimaryGridUnit primary = gu.getPrimary(); + + // during this iteration, the width of PGUs in the first row which span multiple columns + // cannot be determined and, thus, have to be finished after all other rows are done. + if ((primary.getRowIndex() == 0) && (primary.getCell().getNumberColumnsSpanned() > 1)) { + LOG.debug("Will revisit first row"); + colspanningPGUs.add(primary); + } + determineWidthOfPrimary(primary, context, alignment); + } + } + } + } + for (Iterator iterPGUs = colspanningPGUs.iterator(); iterPGUs.hasNext();) { + PrimaryGridUnit primary = (PrimaryGridUnit) iterPGUs.next(); + 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 this cell + List elems = primary.getNextKnuthElementsFromPrimary(context, alignment, tableLM); + + // 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); + } + } Index: fop/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java =================================================================== --- fop.orig/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java 2018-06-20 04:07:13.835266399 +0000 +++ fop/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java 2018-06-20 04:10:47.526254273 +0000 @@ -89,6 +89,8 @@ private List columnBackgroundAreas; private Position auxiliaryPosition; + +// private boolean isAutoLayoutDeterminationMode; // this holds a possible list of TCLMs that needed to have their addAreas() repeated private List<TableCellLayoutManager> savedTCLMs; @@ -242,7 +244,7 @@ * for proportional-column-width() */ if (tableUnit == 0.0) { - this.tableUnit = columns.computeTableUnit(this); + this.tableUnit = columns.computeTableUnit(this, context); } if (!firstVisibleMarkServed) { @@ -269,7 +271,25 @@ stackSize));*/ 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); + // 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 Iterator iter = contentKnuthElements.iterator(); @@ -500,6 +520,137 @@ return getTable().getKeepWithNext(); } + + /** + * Takes a {@link TableColumn} and looks up its {@link MinOptMax} width values. + * However, returns <code>null</code> 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 <code>null</code> + */ + public MinOptMax getPossibleWidths(TableColumn tcol, LayoutContext context) { +// if (this.contentLM != null && (isAutoLayout() || getParent().hasAutoLayoutParent())) { + 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. */ +// log.error("Unknown base type for LengthBase. " +// + "In your .fo file, please validate the table starting in line " + getTable().getLocator().getLineNumber()); + return null; + } + + /** {@inheritDoc} */ + public final boolean isAutoLayout() { + return getTable().isAutoLayout(); + } + +// /** +// * indicates whether the current retrieval of KnuthElements is only used to +// * determine the dimensions of tables using an automatic layout. +// * Became recursive to deal with auto-layout tables in fixed-layout tables +// * in auto-layout tables. +// * @return true if the rendering process is still in the preprocessing mode +// */ +// public boolean isAutoLayoutDeterminationMode() { +// return isAutoLayoutDeterminationMode || getParent().isAutoLayoutDeterminationMode(); +// } + +// /** +// * returns true if the table represented by the {@link TableLayoutManager} uses table-layout="auto" or +// * if any parent of this tableLM uses automatic layout +// */ +// public final boolean hasAutoLayoutParent() { +// return getTable().isAutoLayout() || getParent().hasAutoLayoutParent(); +// } + + /** {@inheritDoc}<br>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<TableColumn> 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<TableColumn> 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 (((TableColumn) tcol).isAutoLayout()) { + this.contentLM.setBaseLength(tcol, mom); + } + } + + // --------- Property Resolution related functions --------- // /** @@ -512,6 +663,12 @@ 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."); Index: fop/src/java/org/apache/fop/render/rtf/RTFHandler.java =================================================================== --- fop.orig/src/java/org/apache/fop/render/rtf/RTFHandler.java 2018-06-20 04:07:13.863266791 +0000 +++ fop/src/java/org/apache/fop/render/rtf/RTFHandler.java 2018-06-20 04:10:47.526254273 +0000 @@ -1526,7 +1526,7 @@ 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)); }