diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java index 925f69ea83a..817f5712742 100644 --- a/fop-core/src/main/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java +++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java @@ -514,10 +514,57 @@ protected void considerLegalBreak(KnuthElement element, int elementIdx) { //nop } } + // A side float's edge is a break after which the content is read again from its layout manager, + // which a table cannot do (it is not restartable): a break between two boxes of one such layout + // manager is not the edge, which waits for the first legal break after it. The rows below the + // float keep the table's width, which the table set once for all its rows, so nothing is drawn + // differently. + boolean edgeDeferred = handlingEndOfFloat && isInsideNonRestartableLM(elementIdx); + if (edgeDeferred) { + handlingEndOfFloat = false; + } super.considerLegalBreak(element, elementIdx); + if (edgeDeferred) { + handlingEndOfFloat = true; + } newFootnotes = false; } + /** Whether the boxes either side of a break both belong to one layout manager that cannot be restarted. */ + private boolean isInsideNonRestartableLM(int elementIdx) { + LayoutManager next = null; + for (int i = elementIdx; i < par.size() && next == null; i++) { + KnuthElement e = getElement(i); + if (e.isBox()) { + next = nonRestartableLM(e.getPosition()); + if (next == null) { + return false; + } + } + } + if (next == null) { + return false; + } + for (int i = elementIdx - 1; i >= 0; i--) { + KnuthElement e = getElement(i); + if (e.isBox()) { + return next == nonRestartableLM(e.getPosition()); + } + } + return false; + } + + /** The outermost layout manager in a position's chain that cannot be restarted, or null. */ + private static LayoutManager nonRestartableLM(Position position) { + for (Position p = position; p != null; p = p.getPosition()) { + LayoutManager lm = p.getLM(); + if (lm != null && !lm.isRestartable()) { + return lm; + } + } + return null; + } + /** {@inheritDoc} */ @Override protected boolean elementCanEndLine(KnuthElement element, int line, int difference) { diff --git a/fop/test/layoutengine/standard-testcases/float_edge-inside-table.xml b/fop/test/layoutengine/standard-testcases/float_edge-inside-table.xml new file mode 100644 index 00000000000..ad7c45df16a --- /dev/null +++ b/fop/test/layoutengine/standard-testcases/float_edge-inside-table.xml @@ -0,0 +1,53 @@ + + + + + + +

+ A side float outlasts the paragraph it is anchored in and its space-after, so its end falls between two + rows of the table after it. A table cannot be read again from a row, so the float's edge waits for the + first break after the table: the rows keep the table's width, and the block after the table is laid out + at full width. Before, this threw NoSuchElementException in LMiter.next. +

+
+ + + + + + + + + + BOXHeading + + + cell 1cell 2cell 3cell 4cell 5 + + after + + + + + + + + + +