Index: src/java/org/apache/fop/fo/flow/table/PrimaryGridUnit.java
===================================================================
--- src/java/org/apache/fop/fo/flow/table/PrimaryGridUnit.java (revision 1660662)
+++ src/java/org/apache/fop/fo/flow/table/PrimaryGridUnit.java (working copy)
@@ -238,11 +238,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);
- }
return contentLength;
}
Index: src/java/org/apache/fop/fo/flow/table/Table.java
===================================================================
--- src/java/org/apache/fop/fo/flow/table/Table.java (revision 1660662)
+++ src/java/org/apache/fop/fo/flow/table/Table.java (working copy)
@@ -151,10 +151,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: src/java/org/apache/fop/fo/flow/table/TableColumn.java
===================================================================
--- src/java/org/apache/fop/fo/flow/table/TableColumn.java (revision 1660662)
+++ src/java/org/apache/fop/fo/flow/table/TableColumn.java (working copy)
@@ -262,5 +262,9 @@
protected void releasePropertyList() {
this.pList = null;
}
+
+ public final boolean isAutoLayout() {
+ return getColumnWidth() instanceof TableColLength;
+ }
}
Index: src/java/org/apache/fop/layoutmgr/AbstractBaseLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/AbstractBaseLayoutManager.java (revision 1660662)
+++ src/java/org/apache/fop/layoutmgr/AbstractBaseLayoutManager.java (working copy)
@@ -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;
@@ -273,4 +274,34 @@
throw new UnsupportedOperationException("Not implemented");
}
+ /** {@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: src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java (revision 1660662)
+++ src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java (working copy)
@@ -37,6 +37,7 @@
import org.apache.fop.fo.properties.KeepProperty;
import org.apache.fop.fo.properties.SpaceProperty;
import org.apache.fop.layoutmgr.inline.InlineLayoutManager;
+import org.apache.fop.layoutmgr.inline.LineLayoutManager;
import org.apache.fop.traits.MinOptMax;
import org.apache.fop.util.ListUtil;
@@ -196,7 +197,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(
@@ -295,6 +296,19 @@
emptyStack = true;
}
+ final int ipd = childLC.getRefIPD();
+ if (currentChildLM instanceof LineLayoutManager) {
+ if (getContentAreaIPD() < ipd && (isAutoLayout() || getParent().hasAutoLayoutParent())) {
+ this.referenceIPD = this.startIndent + ipd + this.endIndent;
+ updateContentAreaIPDwithOverconstrainedAdjust();
+ context.setRefIPD(this.referenceIPD);
+ }
+ } else if (this.referenceIPD < ipd && (isAutoLayout() || getParent().hasAutoLayoutParent())) {
+ this.referenceIPD = ipd;
+ updateContentAreaIPDwithOverconstrainedAdjust();
+ context.setRefIPD(this.referenceIPD);
+ }
+
if (contentList.isEmpty()) {
// propagate keep-with-previous up from the first child
context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending());
Index: src/java/org/apache/fop/layoutmgr/LayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/LayoutManager.java (revision 1660662)
+++ src/java/org/apache/fop/layoutmgr/LayoutManager.java (working copy)
@@ -220,6 +220,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
@@ -252,4 +257,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: src/java/org/apache/fop/layoutmgr/inline/ExternalGraphicLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/inline/ExternalGraphicLayoutManager.java (revision 1660662)
+++ src/java/org/apache/fop/layoutmgr/inline/ExternalGraphicLayoutManager.java (working copy)
@@ -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}
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: src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java (revision 1660662)
+++ src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java (working copy)
@@ -165,7 +165,7 @@
private LineLayoutPossibilities lineLayouts;
private LineLayoutPossibilities[] lineLayoutsList;
- private int ipd = 0;
+ 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.
@@ -223,8 +223,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
@@ -435,7 +440,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
@@ -491,7 +496,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),
@@ -499,7 +504,7 @@
availableShrink, availableStretch,
difference, ratio, 0, startIndent, endIndent,
lineLead + lineFollow,
- ipd, spaceBefore, spaceAfter,
+ ipd.getOpt(), spaceBefore, spaceAfter,
lineLead);
}
}
@@ -611,7 +616,7 @@
context.getWritingMode());
}
context.setAlignmentContext(alignmentContext);
- ipd = context.getRefIPD();
+ ipd = MinOptMax.getInstance(context.getRefIPD());
//PHASE 1: Create Knuth elements
if (knuthParagraphs == null) {
@@ -635,6 +640,7 @@
return createLineBreaks(context.getBPAlignment(), context);
}
+
/**
* Get a sequence of KnuthElements representing the content
* of the node assigned to the LM.
@@ -664,7 +670,7 @@
return null;
}
- ipd = context.getRefIPD();
+ ipd = MinOptMax.getInstance(context.getRefIPD());
//PHASE 2: Create line breaks
return createLineBreaks(context.getBPAlignment(), context);
}
@@ -684,6 +690,8 @@
Paragraph lastPar = null;
+ int minimumIPD = 0;
+ int maxSumIPD = 0;
InlineLevelLayoutManager curLM;
while ((curLM = (InlineLevelLayoutManager) getChildLM()) != null) {
List inlineElements = curLM.getNextKnuthElements(inlineLC, effectiveAlignment);
@@ -723,6 +731,22 @@
ListIterator iter = inlineElements.listIterator();
while (iter.hasNext()) {
KnuthSequence sequence = (KnuthSequence) iter.next();
+
+ // get to know the width of the contained elements
+ if (isAutoLayoutDeterminationMode()) {
+ 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
@@ -764,7 +788,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);
@@ -791,6 +815,22 @@
trace.append(" ]");
}
}
+
+ /**
+ * at this point, localIPD represents the maximum value for how wide the line should be.
+ */
+ if (ipd.getMax() < maxSumIPD && isAutoLayoutDeterminationMode()) {
+ 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);
}
@@ -824,6 +864,7 @@
return postProcessLineBreaks(alignment, context);
}
+
/**
* Find the optimal linebreaks for a paragraph
* @param alignment alignment of the paragraph
@@ -844,7 +885,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);
@@ -1432,6 +1473,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: src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java (revision 1660662)
+++ src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java (working copy)
@@ -27,7 +27,6 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.apache.fop.area.Trait;
import org.apache.fop.area.inline.TextArea;
import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
@@ -208,6 +207,8 @@
private final Position auxiliaryPosition = new LeafPosition(this, -1);
+ private int minimumIPD = -1;
+
/**
* Create a Text layout manager.
*
@@ -926,6 +927,7 @@
if (returnList.isEmpty()) {
return null;
} else {
+ determineMinIPD(returnList);
return returnList;
}
@@ -932,6 +934,34 @@
}
+ /**
+ * 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}
+ */
+ private void determineMinIPD(List returnList) {
+ minimumIPD = 0;
+ ListIterator iter = returnList.listIterator();
+ while (iter.hasNext()) {
+ KnuthSequence sequence = (KnuthSequence) iter.next();
+
+ if (isAutoLayoutDeterminationMode()) {
+ 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));
@@ -1703,5 +1733,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: src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java (revision 1660662)
+++ src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java (working copy)
@@ -19,6 +19,7 @@
package org.apache.fop.layoutmgr.table;
+import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
@@ -34,6 +35,7 @@
import org.apache.fop.fo.flow.table.TableColumn;
import org.apache.fop.fo.properties.TableColLength;
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 +49,8 @@
private Table table;
private WritingModeTraitsGetter wmTraits;
- private List columns = new java.util.ArrayList();
- private List colWidths = new java.util.ArrayList();
+ private List columns = new java.util.ArrayList();
+ private List colWidths = new java.util.ArrayList();
private int maxColIndexReferenced = 0;
@@ -86,9 +88,9 @@
//Post-processing the list (looking for gaps)
//TODO The following block could possibly be removed
int pos = 1;
- ListIterator ppIter = columns.listIterator();
+ ListIterator 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 +127,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 +148,7 @@
}
/** @return an Iterator over all columns */
- public Iterator iterator() {
+ public Iterator iterator() {
return this.columns.iterator();
}
@@ -174,7 +176,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);
}
@@ -211,8 +213,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 i = colWidths.iterator(); i.hasNext();) {
+ Length colWidth = i.next();
if (colWidth != null) {
sumCols += colWidth.getValue(percentBaseContext);
if (colWidth instanceof RelativeNumericProperty) {
@@ -230,6 +232,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 (tlm.isAutoLayoutDeterminationMode()) {
+ return unit;
+ }
+ }
log.warn("No space remaining to distribute over columns.");
}
}
@@ -268,7 +278,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 +299,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 +318,178 @@
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 boolean indicates whether the computation changed any column width values
+ */
+ public int computeOptimalColumnWidthsForAutoLayout(TableLayoutManager tLM) {
+ int maxSumCols = 0; // collects OPT values of the individual columns
+ int minSumCols = 0;
+ int contentAreaIPD = tLM.getContentAreaIPD();
+
+ ListIterator colIter = columns.listIterator();
+ while (colIter.hasNext()) {
+ TableColumn tcol = colIter.next();
+ if (tcol != null) {
+ if (tcol.isAutoLayout()) {
+ MinOptMax possibleWidth = tLM.getPossibleWidths(tcol);
+ 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()) {
+ 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);
+ } else {
+ // set all OPTs to the respective MINIMUM of each column
+ if (minSumCols != contentAreaIPD) {
+ log.warn("Automatic Table Layout: No space remaining to distribute over columns -> will use minimum widths (sum=" + minSumCols + ", available: "+ contentAreaIPD +")");
+ }
+ for (TableColumn tcol : columns) {
+ if (tcol.isAutoLayout()) {
+ MinOptMax possibleWidth = tLM.getPossibleWidths(tcol);
+ 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
+ *
+ * - 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
+ * @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) {
+ 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);
+ } else {
+ MinOptMax possibleWidth = tLM.getPossibleWidths(tcol);
+ 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);
+ }
+ 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);
+ } 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);
+ 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: src/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java (revision 1660662)
+++ src/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java (working copy)
@@ -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;
@@ -131,6 +132,42 @@
}
/**
+ * recursively retrieves (and thereby implicitly calculates the dimensions of) all KnuthElements of all
+ * LayoutManagers contained by the {@link PrimaryGridUnit} primary
(i.e. by its
+ * {@link TableCellLayoutManager}).
+ * Based on {@link #createElementsForRowGroup(LayoutContext, int, int, LinkedList)}.
+ * @param primary representation of the {@link TableCell} whose dimensions are obtained
+ * @param context the layout context to be used to retrieve the {@link KnuthElement}s
+ * @param alignment
+ * @return a list of {@link KnuthElement}s
+ */
+ public List getNextKnuthElementsFromPrimary(PrimaryGridUnit primary, LayoutContext context, int alignment) {
+ primary.createCellLM();
+ primary.getCellLM().setParent(tableLM);
+ //Calculate width of cell
+ int spanWidth = 0;
+ Iterator colIter = tableLM.getTable().getColumns().listIterator(
+ primary.getColIndex());
+ for (int i = 0, c = primary.getCell().getNumberColumnsSpanned(); i < c; i++) {
+ spanWidth += ((TableColumn) colIter.next()).getColumnWidth().getValue(
+ tableLM);
+ }
+ LayoutContext childLC = new LayoutContext(0);
+ 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 = primary.getCellLM().getNextKnuthElements(childLC, alignment);
+ return elems;
+ }
+
+ /**
* Calculate the heights of the rows in the row group, see CSS21, 17.5.3 Table height
* algorithms.
*
Index: src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java (revision 1660662)
+++ src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java (working copy)
@@ -21,6 +21,7 @@
import java.util.LinkedList;
import java.util.List;
+import java.util.ListIterator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -127,6 +128,36 @@
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} */
+ 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}
*/
@@ -151,6 +182,15 @@
// get elements from curLM
returnedList = curLM.getNextKnuthElements(childLC, alignment);
+
+ final int ipd = childLC.getRefIPD() + getIPIndents();
+
+ if (this.referenceIPD < ipd && (isAutoLayout() || getParent().hasAutoLayoutParent())) {
+ this.referenceIPD = ipd;
+ this.cellIPD = getRefIPD() - getIPIndents();
+ context.setRefIPD(ipd);
+ }
+
if (childLC.isKeepWithNextPending()) {
log.debug("child LM signals pending keep with next");
}
Index: src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java (revision 1660662)
+++ src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java (working copy)
@@ -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;
@@ -32,8 +34,10 @@
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FObj;
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.TableColumn;
import org.apache.fop.fo.flow.table.TablePart;
import org.apache.fop.layoutmgr.BreakElement;
import org.apache.fop.layoutmgr.ElementListUtils;
@@ -47,6 +51,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;
/**
@@ -71,7 +76,15 @@
private TableStepper stepper;
+ private final Map baseLength = new HashMap();
+
/**
+ * indicates whether this auto-layout table requires another preprocessing run to correctly
+ * determine the width of all columns.
+ */
+ private boolean requiresSecondDeterminationRun = false;
+
+ /**
* Main constructor
* @param parent Parent layout manager
*/
@@ -128,6 +141,266 @@
}
/**
+ * 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 (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) {
+ 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 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) == 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);
+ // 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
+ * @return the total amount of width which was added to the columns in columnsToWiden
+ */
+ private int increaseMinimumWidthOfSpannedColumns(List columnsToWiden, int minSpanWidthOfSpannedCells, int minIPD) {
+ int totalIncrease = 0;
+
+ for (Iterator iter = columnsToWiden.iterator(); iter.hasNext();) {
+ final TableColumn column = (TableColumn) iter.next();
+ MinOptMax length = tableLM.getPossibleWidths(column);
+
+ // 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) {
+ 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) || done;
+ }
+ }
+
+ return done;
+ }
+
+ private boolean setBaseLength(final Iterator content) {
+ 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) || done;
+ }
+ }
+
+ return done;
+ }
+
+ public boolean setBaseLength(final List content) {
+ final Table table = this.tableLM.getTable();
+ return table.isAutoLayout() && setBaseLength(content.iterator());
+ }
+
+ /**
* Get a sequence of KnuthElements representing the content
* of the node assigned to the LM.
*
@@ -146,6 +419,14 @@
if (headerIter != null && headerList == null) {
this.headerList = getKnuthElementsForRowIterator(
headerIter, context, alignment, TableRowIterator.HEADER);
+
+ if (setBaseLength(this.headerList)) {
+ 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()) {
@@ -166,6 +447,20 @@
if (footerIter != null && footerList == null) {
this.footerList = getKnuthElementsForRowIterator(
footerIter, context, alignment, TableRowIterator.FOOTER);
+
+ if (setBaseLength(this.footerList)) {
+ 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()) {
@@ -180,6 +475,24 @@
}
LinkedList returnList = getKnuthElementsForRowIterator(
bodyIter, context, alignment, TableRowIterator.BODY);
+
+ if (setBaseLength(returnList)) {
+ 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()) {
@@ -523,4 +836,114 @@
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 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) {
+ 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
+ */
+ 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 spanning multiple columns cannot
+ // be determined and, thus, another determination run is required
+ // TODO: eliminate the need for a second determination run
+ if ((!requiresSecondDeterminationRun)
+ && (primary.getRowIndex() == 0)
+ && (primary.getCell().getNumberColumnsSpanned() > 1)) {
+ LOG.debug("Table will require a second determination run");
+ requiresSecondDeterminationRun = true;
+ } else {
+ // recursively retrieve (and thereby calculate the dimensions of)
+ // all KnuthElements of all contained LayoutManagers for this cell
+ List elems = rowGroupLM.getNextKnuthElementsFromPrimary(primary, context, alignment);
+
+ // temporarily assign these KnuthElements to the PGU to calculate its dimensions
+ primary.setElements(elems);
+ setBaseLength(primary);
+
+ // 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);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Indicates whether the table requires another run to finalize the computations required for
+ * table-layout=auto.
+ * Currently, this is only the case if the very first row contains a table cell which spans
+ * over several columns for which no dimensions are known. However, other problematic table
+ * versions may also require a second run.
+ * @return the requiresSecondDeterminationRun indicator for special table layouts
+ */
+ public boolean requiresSecondDeterminationRun() {
+ return requiresSecondDeterminationRun;
+ }
+
+ /**
+ * sets the indicator whether this table requires another determination run to deal with troublesome
+ * structures within the table.
+ * @param requiresSecondDeterminationRun the requiresSecondDeterminationRun to set
+ */
+ public void setRequiresSecondDeterminationRun(boolean requiresSecondDeterminationRun) {
+ this.requiresSecondDeterminationRun = requiresSecondDeterminationRun;
+ }
}
Index: src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java (revision 1660662)
+++ src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java (working copy)
@@ -92,6 +92,8 @@
private Position auxiliaryPosition;
+ private boolean isAutoLayoutDeterminationMode;
+
/**
* Temporary holder of column background informations for a table-cell's area.
*
@@ -250,6 +252,21 @@
childLC.setRefIPD(context.getRefIPD());
childLC.copyPendingMarksFrom(context);
+ if (isAutoLayout() || getParent().hasAutoLayoutParent()) {
+ isAutoLayoutDeterminationMode = true;
+ contentLM.determineAutoLayoutWidths(childLC, alignment);
+ if (contentLM.requiresSecondDeterminationRun()) {
+ contentLM.determineAutoLayoutWidths(childLC, alignment);
+ contentLM.setRequiresSecondDeterminationRun(false);
+ }
+ isAutoLayoutDeterminationMode = false;
+ int maxCol = columns.computeOptimalColumnWidthsForAutoLayout(this);
+
+ if (getParent().hasAutoLayoutParent() && getParent().isAutoLayoutDeterminationMode()) {
+ context.setRefIPD(maxCol);
+ }
+ }
+
contentKnuthElements = contentLM.getNextKnuthElements(childLC, alignment);
//Set index values on elements coming from the content LM
Iterator iter = contentKnuthElements.iterator();
@@ -492,6 +509,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.");
@@ -507,6 +530,51 @@
}
}
+ /**
+ * 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
+ * @return {@link MinOptMax} or null
+ */
+ public MinOptMax getPossibleWidths(TableColumn tcol) {
+ if (this.contentLM != null && (isAutoLayout() || getParent().hasAutoLayoutParent())) {
+ 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;
+ }
+
+ /**
+ * 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);
+ }
+ }
+
/** {@inheritDoc} */
public void notifySpace(RelSide side, MinOptMax effectiveLength) {
if (RelSide.BEFORE == side) {
@@ -559,4 +627,86 @@
tableUnit = 0.0;
}
+ /** {@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}
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;
+ }
+
}