Index: test/layoutengine/standard-testcases/region-body_column-count_footnote_2.xml =================================================================== --- test/layoutengine/standard-testcases/region-body_column-count_footnote_2.xml (revision ) +++ test/layoutengine/standard-testcases/region-body_column-count_footnote_2.xml (revision ) @@ -0,0 +1,158 @@ + + + + + +

+ This test checks multi-column documents. This test: footnotes in multi-column documents (no spans). +

+
+ + + + + + + + + + + + + + + + + line 1 + line 2 + line 3 + [*] + + + + + + + [*] + + + + footnote footnote footnote footnote footnote footnote footnote footnote + + + + + + + + line 4 + line 5 + line 6 + line 7 + line 8 + line 9 + [*] + + + + + + + [*] + + + + footnote footnote footnote footnote footnote footnote footnote footnote + + + + + + + + line 10 + line 11 + line 12 + line 13 + line 14 + line 15 + line 16 + line 17 + [*] + + + + + + + [*] + + + + footnote footnote footnote footnote footnote footnote footnote footnote + + + + + + + + line 18 + line 19 + line 20 + line 21 + line 22 + line 23 + line 24 + line 25 + line 26 + line 27 + line 28 + line 29 + line 30 + line 31 + line 32 + line 33 + line 34 + line 35 + + + + + + + + + + + + + + + + + + + + + + + + + +
Index: src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java =================================================================== --- src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java (revision 1067762) +++ src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java (revision ) @@ -20,7 +20,6 @@ package org.apache.fop.layoutmgr; import java.util.ArrayList; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; @@ -45,40 +44,346 @@ /** List of PageBreakPosition elements. */ private LinkedList pageBreaks = null; + private final class Footnotes { + + // demerits for a page break that splits a footnote + static final int SPLIT_FOOTNOTE_DEMERITS = 5000; + // demerits for a page break that defers a whole footnote to the following page + static final int DEFERRED_FOOTNOTE_DEMERITS = 10000; + + // the length of the footnote separator + private MinOptMax separatorLength = null; + - /** Footnotes which are cited between the currently considered active node (previous - * break) and the current considered break. Its type is - * List<List<KnuthElement>>, it contains the sequences of KnuthElement - * representing the footnotes bodies. - */ - private List> footnotesList = null; - /** Cumulated bpd of unhandled footnotes. */ - private List lengthList = null; - /** Length of all the footnotes which will be put on the current page. */ + /** Footnotes which are cited between the currently considered active node (previous + * break) and the current considered break. Its type is + * List<List<KnuthElement>>, it contains the sequences of KnuthElement + * representing the footnotes bodies. + */ + private List> footnotesList = null; + /** Cumulated bpd of unhandled footnotes. */ + private List lengthList = null; + /** Length of all the footnotes which will be put on the current page. */ - private int totalFootnotesLength = 0; + private int totalLength = 0; + - /** - * Length of all the footnotes which have already been inserted, up to the currently - * considered element. That is, footnotes from the currently considered page plus - * footnotes from its preceding pages. - */ + /** + * Length of all the footnotes which have already been inserted, up to the currently + * considered element. That is, footnotes from the currently considered page plus + * footnotes from its preceding pages. + */ - private int insertedFootnotesLength = 0; + private int insertedFootnotesLength; - /** True if footnote citations have been met since the beginning of the page sequence. */ - private boolean footnotesPending = false; - /** True if the elements met after the previous break point contain footnote citations. */ - private boolean newFootnotes = false; - /** Index of the first footnote met after the previous break point. */ - private int firstNewFootnoteIndex = 0; - /** Index of the last footnote inserted on the current page. */ + /** True if footnote citations have been met since the beginning of the page sequence. */ + private boolean footnotesPending = false; + /** True if the elements met after the previous break point contain footnote citations. */ + private boolean newFootnotes = false; + /** Index of the first footnote met after the previous break point. */ + private int firstNewFootnoteIndex = 0; + /** Index of the last footnote inserted on the current page. */ - private int footnoteListIndex = 0; + private int listIndex; - /** Index of the last element of the last footnote inserted on the current page. */ + /** Index of the last element of the last footnote inserted on the current page. */ - private int footnoteElementIndex = -1; + private int elementIndex; - // demerits for a page break that splits a footnote - private int splitFootnoteDemerits = 5000; - // demerits for a page break that defers a whole footnote to the following page - private int deferredFootnoteDemerits = 10000; - private MinOptMax footnoteSeparatorLength = null; + private Footnotes() { + } + void initialize() { + footnotes.insertedFootnotesLength = 0; + footnotes.listIndex = 0; + footnotes.elementIndex = -1; + } + + /** + * Handles the footnotes cited inside a block-level box. Updates footnotesList and the + * value of totalLength with the lengths of the given footnotes. + * @param elementLists list of KnuthElement sequences corresponding to the footnotes + * bodies + */ + void handleFootnotes(List> elementLists) { + // initialization + if (!footnotesPending) { + footnotesPending = true; + footnotesList = new ArrayList>(); + lengthList = new ArrayList(); + totalLength = 0; + } + if (!newFootnotes) { + newFootnotes = true; + firstNewFootnoteIndex = footnotesList.size(); + } + + // compute the total length of the footnotes + for (List noteList : elementLists) { + + //Space resolution (Note: this does not respect possible stacking constraints + //between footnotes!) + SpaceResolver.resolveElementList(noteList); + + footnotesList.add(noteList); + int noteLength = ElementListUtils.calcContentLength(noteList); + int prevLength = (lengthList == null || lengthList.isEmpty()) + ? 0 + : ListUtil.getLast(lengthList); + if (lengthList != null) { + lengthList.add(prevLength + noteLength); + } + totalLength += noteLength; + } + } + + void resetFootnotes(int fromIndex, int toIndex) { + newFootnotes = false; + if (footnotesPending) { + // remove from footnotesList the note lists that will be met + // after the restarting point + KnuthElement resetElement; + int start = footnotesList.size(); + int end = start; + for (int j = toIndex; j >= fromIndex; j--) { + resetElement = getElement(j); + if (resetElement instanceof KnuthBlockBox + && ((KnuthBlockBox) resetElement).hasAnchors()) { + start -= ((KnuthBlockBox) resetElement).getElementLists().size(); + } + } + footnotesList.subList(start, end).clear(); + lengthList.subList(start, end).clear(); + // update totalLength + totalLength = (lengthList.isEmpty() ? 0 : ListUtil.getLast(lengthList)); + // update footnotesPending; + footnotesPending = !footnotesList.isEmpty(); + listIndex = footnotesList.size() - 1; + elementIndex = (listIndex < 0 + ? -1 : getList(listIndex).size() - 1); + } + } + + int getDifference(KnuthPageNode pageNode, int elementIndex) { + + int contentWidth = totalWidth - pageNode.totalWidth; + + int totalIntrusions = 0; + + // compute the total length of the footnotes not yet inserted + int allFootnotes = totalLength; + KnuthPageNode lastPageNode = pageNode; + while (lastPageNode != null + && !pageProvider.endPage(lastPageNode.line - 1)) { + lastPageNode = (KnuthPageNode) lastPageNode.previous; + } + allFootnotes -= (lastPageNode == null ? 0 : lastPageNode.totalFootnotes); + + if (allFootnotes > 0) { + // this page/column contains some footnote citations + // add the footnote separator width + totalIntrusions += separatorLength.getOpt(); + totalIntrusions += allFootnotes; + insertedFootnotesLength = totalLength; + listIndex = footnotesList.size() - 1; + this.elementIndex = getList(listIndex).size() - 1; + + int availableWidth = getLineWidth(pageNode.line); + if ((contentWidth + totalIntrusions) > availableWidth) { + // see if part of the footnotes can be deferred + boolean canDeferOldFN = canDeferOldFootnotes(pageNode, elementIndex); + if (canDeferOldFN || newFootnotes) { + availableWidth -= (contentWidth + separatorLength.getOpt()); + int footnoteSplit = (availableWidth <= 0 ? 0 + : getFootnoteSplit(pageNode, availableWidth, canDeferOldFN)); + if (footnoteSplit > 0) { + // the total of content + separator + all footnotes does not fit, but + // it is allowed to break or even defer footnotes if either: + // - there are new footnotes in the last piece of content, and + // there is space to add at least a piece of the first one + // - or the previous page break deferred some footnote lines, and + // this is the first feasible break; in this case it is allowed + // to break and defer, if necessary, old and new footnotes + totalIntrusions -= allFootnotes; + totalIntrusions += footnoteSplit; + insertedFootnotesLength = pageNode.totalFootnotes + footnoteSplit; + // listIndex has been set in getFootnoteSplit() + // elementIndex has been set in getFootnoteSplit() + } else { + // there is no space to add even the smallest piece of footnote, + // the whole allFootnotes length was added, so this breakpoint + // will be discarded + } + } else { + // we are trying to add a piece of content with no footnotes and + // it does not fit in the page, because of previous footnote bodies + // that cannot be broken: + // the whole allFootnotes length was added, so this breakpoint + // will be discarded + } + } + } else { + // all footnotes have already been placed on previous pages + } + + return totalIntrusions; + } + + int getDemerits() { + int demerits = 0; + if (footnotes.listIndex < footnotes.footnotesList.size() - 1) { + // add demerits for the deferred footnotes + demerits += (footnotes.footnotesList.size() - 1 - footnotes.listIndex) + * DEFERRED_FOOTNOTE_DEMERITS; + } + if (footnotes.elementIndex + < getList(footnotes.listIndex).size() - 1) { + // add demerits for the footnote split between pages + demerits += SPLIT_FOOTNOTE_DEMERITS; + } + return demerits; + } + + boolean deferredFootnotes(int listIndex, int elementIndex, int length) { + return ((newFootnotes + && firstNewFootnoteIndex != 0 + && (listIndex < firstNewFootnoteIndex - 1 + || elementIndex < getList(listIndex).size() - 1)) + || length < totalLength); + } + + int getFootnoteSplit(int availableLength) { + return getFootnoteSplit(listIndex, + elementIndex, + totalLength, + availableLength, true); + } + + int getFootnoteSplit(KnuthPageNode activeNode, int availableLength, + boolean canDeferOldFootnotes) { + return getFootnoteSplit(activeNode.footnoteListIndex, + activeNode.footnoteElementIndex, + activeNode.totalFootnotes, + availableLength, canDeferOldFootnotes); + } + + int getFootnoteSplit(int prevListIndex, int prevElementIndex, int prevLength, + int availableLength, boolean canDeferOldFootnotes) { + + // the split should contain a piece of the last footnote + // together with all previous, not yet inserted footnotes; + // but if this is not possible, try adding as much content as possible + int splitLength = 0; + ListIterator noteListIterator; + KnuthElement element; + boolean somethingAdded = false; + + // prevListIndex and prevElementIndex points to the last footnote element + // already placed in a page: advance to the next element + int listIndex = prevListIndex; + int elementIndex = prevElementIndex; + if (elementIndex == getList(listIndex).size() - 1) { + listIndex++; + elementIndex = 0; + } else { + elementIndex++; + } + + // try adding whole notes + if (footnotesList.size() - 1 > listIndex) { + // add the previous footnotes: these cannot be broken or deferred + if (!canDeferOldFootnotes && newFootnotes && firstNewFootnoteIndex > 0) { + splitLength = lengthList.get(firstNewFootnoteIndex - 1) - prevLength; + listIndex = firstNewFootnoteIndex; + elementIndex = 0; + } + // try adding the new footnotes + while (lengthList.get(listIndex) - prevLength <= availableLength) { + splitLength = lengthList.get(listIndex) - prevLength; + somethingAdded = true; + listIndex++; + elementIndex = 0; + } + // as this method is called only if it is not possible to insert + // all footnotes, at this point listIndex and elementIndex points to + // an existing element, the next one we will try to insert + } + + // try adding a split of the next note + noteListIterator = getList(listIndex).listIterator(elementIndex); + + int prevSplitLength = 0; + int prevIndex = -1; + int index = -1; + + while (!somethingAdded || splitLength <= availableLength) { + if (!somethingAdded) { + somethingAdded = true; + } else { + prevSplitLength = splitLength; + prevIndex = index; + } + + // get a sub-sequence from the note element list + boolean boxPreceding = false; + while (noteListIterator.hasNext()) { + // as this method is called only if it is not possible to insert + // all footnotes, and we have already tried (and failed) to insert + // this whole footnote, the while loop will never reach the end + // of the note sequence + element = noteListIterator.next(); + if (element.isBox()) { + // element is a box + splitLength += element.getWidth(); + boxPreceding = true; + } else if (element.isGlue()) { + // element is a glue + if (boxPreceding) { + // end of the sub-sequence + index = noteListIterator.previousIndex(); + break; + } + boxPreceding = false; + splitLength += element.getWidth(); + } else { + // element is a penalty + if (element.getPenalty() < KnuthElement.INFINITE) { + // end of the sub-sequence + index = noteListIterator.previousIndex(); + break; + } + } + } + } + + // if prevSplitLength is 0, this means that the available length isn't enough + // to insert even the smallest split of the last footnote, so we cannot end a + // page here + // if prevSplitLength is > 0 we can insert some footnote content in this page + // and insert the remaining in the following one + //TODO: check this conditional, as the first one is always false...? + if (!somethingAdded) { + // there was not enough space to add a piece of the first new footnote + // this is not a good break + prevSplitLength = 0; + } else if (prevSplitLength > 0) { + // prevIndex is -1 if we have added only some whole footnotes + this.listIndex = (prevIndex != -1) ? listIndex : listIndex - 1; + this.elementIndex = (prevIndex != -1) + ? prevIndex + : getList(this.listIndex).size() - 1; + } + return prevSplitLength; + } + + boolean footnotesRemaining() { + return insertedFootnotesLength < totalLength; + } + + int currentLength() { + return lengthList.get(listIndex); + } + + List getList(int index) { + return footnotesList.get(index); + } + } + + private Footnotes footnotes; + // the method noBreakBetween(int, int) uses these variables // to store parameters and result of the last call, in order // to reuse them and take less time @@ -130,7 +435,8 @@ this.pageProvider = pageProvider; this.layoutListener = layoutListener; best = new BestPageRecords(); - this.footnoteSeparatorLength = footnoteSeparatorLength; + this.footnotes = new Footnotes(); + this.footnotes.separatorLength = footnoteSeparatorLength; this.autoHeight = autoHeight; this.favorSinglePart = favorSinglePart; } @@ -165,7 +471,16 @@ this.footnoteElementIndex = footnoteElementIndex; } + @Override + public String toString() { + StringBuilder sb = new StringBuilder(super.toString()); + sb.deleteCharAt(sb.length() - 1); + sb.append(" totalFootnotes:").append(totalFootnotes); + sb.append(" footnoteList:").append(footnoteListIndex); + sb.append(" footnoteElement:").append(footnoteElementIndex).append('>'); + return sb.toString(); - } + } + } /** * this class stores information about how the nodes @@ -183,9 +498,9 @@ super.addRecord(demerits, node, adjust, availableShrink, availableStretch, difference, fitness); - bestFootnotesLength[fitness] = insertedFootnotesLength; - bestFootnoteListIndex[fitness] = footnoteListIndex; - bestFootnoteElementIndex[fitness] = footnoteElementIndex; + bestFootnotesLength[fitness] = footnotes.insertedFootnotesLength; + bestFootnoteListIndex[fitness] = footnotes.listIndex; + bestFootnoteElementIndex[fitness] = footnotes.elementIndex; } public int getFootnotesLength(int fitness) { @@ -205,9 +520,7 @@ @Override protected void initialize() { super.initialize(); - insertedFootnotesLength = 0; - footnoteListIndex = 0; - footnoteElementIndex = -1; + footnotes.initialize(); } /** @@ -279,7 +592,6 @@ return super.compareNodes(node1, node2); } - /** {@inheritDoc} */ @Override protected KnuthNode createNode(int position, // CSOK: ParameterNumber int line, int fitness, @@ -288,12 +600,13 @@ int difference, double totalDemerits, KnuthNode previous) { return new KnuthPageNode(position, line, fitness, totalWidth, totalStretch, totalShrink, - insertedFootnotesLength, footnoteListIndex, footnoteElementIndex, + footnotes.insertedFootnotesLength, + footnotes.listIndex, + footnotes.elementIndex, adjustRatio, availableShrink, availableStretch, difference, totalDemerits, previous); } - /** {@inheritDoc} */ @Override protected KnuthNode createNode(int position, int line, int fitness, int totalWidth, int totalStretch, int totalShrink) { @@ -317,10 +630,10 @@ super.handleBox(box); if (box instanceof KnuthBlockBox && ((KnuthBlockBox) box).hasAnchors()) { - handleFootnotes(((KnuthBlockBox) box).getElementLists()); - if (!newFootnotes) { - newFootnotes = true; - firstNewFootnoteIndex = footnotesList.size() - 1; + footnotes.handleFootnotes(((KnuthBlockBox) box).getElementLists()); + if (!footnotes.newFootnotes) { + footnotes.newFootnotes = true; + footnotes.firstNewFootnoteIndex = footnotes.footnotesList.size() - 1; } } } @@ -348,87 +661,13 @@ } } - /** - * Handles the footnotes cited inside a block-level box. Updates footnotesList and the - * value of totalFootnotesLength with the lengths of the given footnotes. - * @param elementLists list of KnuthElement sequences corresponding to the footnotes - * bodies - */ - private void handleFootnotes(List> elementLists) { - // initialization - if (!footnotesPending) { - footnotesPending = true; - footnotesList = new ArrayList>(); - lengthList = new ArrayList(); - totalFootnotesLength = 0; - } - if (!newFootnotes) { - newFootnotes = true; - firstNewFootnoteIndex = footnotesList.size(); - } - - // compute the total length of the footnotes - for (List noteList : elementLists) { - - //Space resolution (Note: this does not respect possible stacking constraints - //between footnotes!) - SpaceResolver.resolveElementList(noteList); - - int noteLength = 0; - footnotesList.add(noteList); - for (KnuthElement element : noteList) { - if (element.isBox() || element.isGlue()) { - noteLength += element.getWidth(); - } - } - int prevLength = (lengthList == null || lengthList.isEmpty()) - ? 0 - : ListUtil.getLast(lengthList); - if (lengthList != null) { - lengthList.add(prevLength + noteLength); - } - totalFootnotesLength += noteLength; - } - } - - /** {@inheritDoc} */ @Override protected int restartFrom(KnuthNode restartingNode, int currentIndex) { int returnValue = super.restartFrom(restartingNode, currentIndex); - newFootnotes = false; - if (footnotesPending) { - // remove from footnotesList the note lists that will be met - // after the restarting point - for (int j = currentIndex; j >= restartingNode.position; j--) { - final KnuthElement resetElement = getElement(j); - if (resetElement instanceof KnuthBlockBox - && ((KnuthBlockBox) resetElement).hasAnchors()) { - resetFootnotes(((KnuthBlockBox) resetElement).getElementLists()); - } - } - } + footnotes.resetFootnotes(restartingNode.position, currentIndex); return returnValue; } - private void resetFootnotes(List> elementLists) { - for (int i = 0; i < elementLists.size(); i++) { - ListUtil.removeLast(footnotesList); - ListUtil.removeLast(lengthList); - - // update totalFootnotesLength - if (!lengthList.isEmpty()) { - totalFootnotesLength = ListUtil.getLast(lengthList); - } else { - totalFootnotesLength = 0; - } - } - // update footnotesPending; - if (footnotesList.size() == 0) { - footnotesPending = false; - } - } - - /** {@inheritDoc} */ @Override protected void considerLegalBreak(KnuthElement element, int elementIdx) { if (element.isPenalty()) { @@ -454,10 +693,9 @@ } } super.considerLegalBreak(element, elementIdx); - newFootnotes = false; + footnotes.newFootnotes = false; } - /** {@inheritDoc} */ @Override protected boolean elementCanEndLine(KnuthElement element, int line, int difference) { if (!(element.isPenalty()) || pageProvider == null) { @@ -490,74 +728,36 @@ } } - /** {@inheritDoc} */ @Override protected int computeDifference(KnuthNode activeNode, KnuthElement element, int elementIndex) { - KnuthPageNode pageNode = (KnuthPageNode) activeNode; - int actualWidth = totalWidth - pageNode.totalWidth; - int footnoteSplit; - boolean canDeferOldFN; - if (element.isPenalty()) { - actualWidth += element.getWidth(); + + int diff = super.computeDifference(activeNode, element, elementIndex); + //getLineWidth() for auto-height parts return 0 so the diff will be negative + //...but we don't want to shrink in this case. Stick to optimum. + return (autoHeight && (diff < 0) ? 0 : diff); - } + } - if (footnotesPending) { - // compute the total length of the footnotes not yet inserted - int allFootnotes = totalFootnotesLength - pageNode.totalFootnotes; - if (allFootnotes > 0) { - // this page contains some footnote citations - // add the footnote separator width - actualWidth += footnoteSeparatorLength.getOpt(); - if (actualWidth + allFootnotes <= getLineWidth(activeNode.line)) { - // there is enough space to insert all footnotes: - // add the whole allFootnotes length - actualWidth += allFootnotes; - insertedFootnotesLength = pageNode.totalFootnotes + allFootnotes; - footnoteListIndex = footnotesList.size() - 1; - footnoteElementIndex - = getFootnoteList(footnoteListIndex).size() - 1; - } else if (((canDeferOldFN = canDeferOldFootnotes // CSOK: InnerAssignment - (pageNode, elementIndex)) - || newFootnotes) - && (footnoteSplit = getFootnoteSplit // CSOK: InnerAssignment - (pageNode, getLineWidth(activeNode.line) - actualWidth, - canDeferOldFN)) > 0) { - // it is allowed to break or even defer footnotes if either: - // - there are new footnotes in the last piece of content, and - // there is space to add at least a piece of the first one - // - or the previous page break deferred some footnote lines, and - // this is the first feasible break; in this case it is allowed - // to break and defer, if necessary, old and new footnotes - actualWidth += footnoteSplit; - insertedFootnotesLength = pageNode.totalFootnotes + footnoteSplit; - // footnoteListIndex has been set in getFootnoteSplit() - // footnoteElementIndex has been set in getFootnoteSplit() - } else { - // there is no space to add the smallest piece of footnote, - // or we are trying to add a piece of content with no footnotes and - // it does not fit in the page, because of previous footnote bodies - // that cannot be broken: - // add the whole allFootnotes length, so this breakpoint will be discarded - actualWidth += allFootnotes; - insertedFootnotesLength = pageNode.totalFootnotes + allFootnotes; - footnoteListIndex = footnotesList.size() - 1; - footnoteElementIndex - = getFootnoteList(footnoteListIndex).size() - 1; + + @Override + protected boolean hasOutOfLineIntrusions() { + return footnotes.footnotesPending; - } + } + + @Override + protected int getOutOfLineIntrusions(KnuthNode activeNode, int elementIndex) { + + assert (activeNode != null); + KnuthPageNode pageNode = (KnuthPageNode) activeNode; + int totalIntrusions = 0; + + if (footnotes.footnotesPending) { + totalIntrusions += footnotes.getDifference(pageNode, elementIndex); - } else { + } else { - // all footnotes have already been placed on previous pages - } - } else { // there are no footnotes } - int diff = getLineWidth(activeNode.line) - actualWidth; - if (autoHeight && diff < 0) { - //getLineWidth() for auto-height parts return 0 so the diff will be negative - return 0; //...but we don't want to shrink in this case. Stick to optimum. - } else { - return diff; + + return totalIntrusions; - } + } - } /** * Checks whether footnotes from preceding pages may be deferred to the page after @@ -569,8 +769,8 @@ */ private boolean canDeferOldFootnotes(KnuthPageNode node, int contentElementIndex) { return (noBreakBetween(node.position, contentElementIndex) - && deferredFootnotes(node.footnoteListIndex, + && footnotes.deferredFootnotes(node.footnoteListIndex, - node.footnoteElementIndex, node.totalFootnotes)); + node.footnoteElementIndex, node.totalFootnotes)); } /** @@ -624,166 +824,14 @@ return storedValue; } - /** - * Returns true if their are (pieces of) footnotes to be typeset on the current page. - * @param listIndex index of the last inserted footnote for the currently considered - * active node - * @param elementIndex index of the last element of the last inserted footnote - * @param length total length of all footnotes inserted so far - */ - private boolean deferredFootnotes(int listIndex, int elementIndex, int length) { - return ((newFootnotes - && firstNewFootnoteIndex != 0 - && (listIndex < firstNewFootnoteIndex - 1 - || elementIndex < getFootnoteList(listIndex).size() - 1)) - || length < totalFootnotesLength); - } - - /** - * Tries to split the flow of footnotes to put one part on the current page. - * @param activeNode currently considered previous page break - * @param availableLength available space for footnotes - * @param canDeferOldFootnotes - * @return ... - */ - private int getFootnoteSplit(KnuthPageNode activeNode, int availableLength, - boolean canDeferOldFootnotes) { - return getFootnoteSplit(activeNode.footnoteListIndex, - activeNode.footnoteElementIndex, - activeNode.totalFootnotes, - availableLength, canDeferOldFootnotes); - } - - /** - * Tries to split the flow of footnotes to put one part on the current page. - * @param prevListIndex index of the last footnote on the previous page - * @param prevElementIndex index of the last element of the last footnote - * @param prevLength total length of footnotes inserted so far - * @param availableLength available space for footnotes on this page - * @param canDeferOldFootnotes - * @return ... - */ - private int getFootnoteSplit(int prevListIndex, int prevElementIndex, int prevLength, - int availableLength, boolean canDeferOldFootnotes) { - if (availableLength <= 0) { - return 0; - } else { - // the split should contain a piece of the last footnote - // together with all previous, not yet inserted footnotes; - // but if this is not possible, try adding as much content as possible - int splitLength = 0; - ListIterator noteListIterator; - KnuthElement element; - boolean somethingAdded = false; - - // prevListIndex and prevElementIndex points to the last footnote element - // already placed in a page: advance to the next element - int listIndex = prevListIndex; - int elementIndex = prevElementIndex; - if (elementIndex == getFootnoteList(listIndex).size() - 1) { - listIndex++; - elementIndex = 0; - } else { - elementIndex++; - } - - // try adding whole notes - if (footnotesList.size() - 1 > listIndex) { - // add the previous footnotes: these cannot be broken or deferred - if (!canDeferOldFootnotes && newFootnotes && firstNewFootnoteIndex > 0) { - splitLength = lengthList.get(firstNewFootnoteIndex - 1) - prevLength; - listIndex = firstNewFootnoteIndex; - elementIndex = 0; - } - // try adding the new footnotes - while (lengthList.get(listIndex) - prevLength - <= availableLength) { - splitLength = lengthList.get(listIndex) - prevLength; - somethingAdded = true; - listIndex++; - elementIndex = 0; - } - // as this method is called only if it is not possible to insert - // all footnotes, at this point listIndex and elementIndex points to - // an existing element, the next one we will try to insert - } - - // try adding a split of the next note - noteListIterator = getFootnoteList(listIndex).listIterator(elementIndex); - - int prevSplitLength = 0; - int prevIndex = -1; - int index = -1; - - while (!(somethingAdded && splitLength > availableLength)) { - if (!somethingAdded) { - somethingAdded = true; - } else { - prevSplitLength = splitLength; - prevIndex = index; - } - // get a sub-sequence from the note element list - boolean boxPreceding = false; - while (noteListIterator.hasNext()) { - // as this method is called only if it is not possible to insert - // all footnotes, and we have already tried (and failed) to insert - // this whole footnote, the while loop will never reach the end - // of the note sequence - element = noteListIterator.next(); - if (element.isBox()) { - // element is a box - splitLength += element.getWidth(); - boxPreceding = true; - } else if (element.isGlue()) { - // element is a glue - if (boxPreceding) { - // end of the sub-sequence - index = noteListIterator.previousIndex(); - break; - } - boxPreceding = false; - splitLength += element.getWidth(); - } else { - // element is a penalty - if (element.getPenalty() < KnuthElement.INFINITE) { - // end of the sub-sequence - index = noteListIterator.previousIndex(); - break; - } - } - } - } - - // if prevSplitLength is 0, this means that the available length isn't enough - // to insert even the smallest split of the last footnote, so we cannot end a - // page here - // if prevSplitLength is > 0 we can insert some footnote content in this page - // and insert the remaining in the following one - //TODO: check this conditional, as the first one is always false...? - if (!somethingAdded) { - // there was not enough space to add a piece of the first new footnote - // this is not a good break - prevSplitLength = 0; - } else if (prevSplitLength > 0) { - // prevIndex is -1 if we have added only some whole footnotes - footnoteListIndex = (prevIndex != -1) ? listIndex : listIndex - 1; - footnoteElementIndex = (prevIndex != -1) - ? prevIndex - : getFootnoteList(footnoteListIndex).size() - 1; - } - return prevSplitLength; - } - } - - /** {@inheritDoc} */ @Override protected double computeAdjustmentRatio(KnuthNode activeNode, int difference) { // compute the adjustment ratio if (difference > 0) { int maxAdjustment = totalStretch - activeNode.totalStretch; // add the footnote separator stretch if some footnote content will be added - if (((KnuthPageNode) activeNode).totalFootnotes < totalFootnotesLength) { - maxAdjustment += footnoteSeparatorLength.getStretch(); + if (((KnuthPageNode) activeNode).totalFootnotes < footnotes.totalLength) { + maxAdjustment += footnotes.separatorLength.getStretch(); } if (maxAdjustment > 0) { return (double) difference / maxAdjustment; @@ -793,8 +841,8 @@ } else if (difference < 0) { int maxAdjustment = totalShrink - activeNode.totalShrink; // add the footnote separator shrink if some footnote content will be added - if (((KnuthPageNode) activeNode).totalFootnotes < totalFootnotesLength) { - maxAdjustment += footnoteSeparatorLength.getShrink(); + if (((KnuthPageNode) activeNode).totalFootnotes < footnotes.totalLength) { + maxAdjustment += footnotes.separatorLength.getShrink(); } if (maxAdjustment > 0) { return (double) difference / maxAdjustment; @@ -806,7 +854,6 @@ } } - /** {@inheritDoc} */ @Override protected double computeDemerits(KnuthNode activeNode, KnuthElement element, int fitnessClass, double r) { @@ -814,19 +861,16 @@ // compute demerits double f = Math.abs(r); f = 1 + 100 * f * f * f; + if (element.isPenalty()) { double penalty = element.getPenalty(); if (penalty >= 0) { f += penalty; - demerits = f * f; } else if (!element.isForcedBreak()) { - demerits = f * f - penalty * penalty; - } else { - demerits = f * f; + demerits -= penalty * penalty; } - } else { - demerits = f * f; } + demerits += f * f; if (element.isPenalty() && ((KnuthPenalty) element).isPenaltyFlagged() && getElement(activeNode.position).isPenalty() @@ -840,34 +884,20 @@ demerits += incompatibleFitnessDemerit; } - if (footnotesPending) { - if (footnoteListIndex < footnotesList.size() - 1) { - // add demerits for the deferred footnotes - demerits += (footnotesList.size() - 1 - footnoteListIndex) - * deferredFootnoteDemerits; + if (footnotes.footnotesPending) { + demerits += footnotes.getDemerits(); - } + } - if (footnoteListIndex < footnotesList.size()) { - if (footnoteElementIndex - < getFootnoteList(footnoteListIndex).size() - 1) { - // add demerits for the footnote split between pages - demerits += splitFootnoteDemerits; - } - } else { - //TODO Why can this happen in the first place? Does anybody know? See #44160 - } - } demerits += activeNode.totalDemerits; return demerits; } - /** {@inheritDoc} */ @Override protected void finish() { for (int i = startLine; i < endLine; i++) { for (KnuthPageNode node = (KnuthPageNode) getNode(i); node != null; node = (KnuthPageNode) node.next) { - if (node.totalFootnotes < totalFootnotesLength) { + if (node.totalFootnotes < footnotes.totalLength) { // layout remaining footnote bodies createFootnotePages(node); } @@ -877,50 +907,52 @@ private void createFootnotePages(KnuthPageNode lastNode) { - insertedFootnotesLength = lastNode.totalFootnotes; - footnoteListIndex = lastNode.footnoteListIndex; - footnoteElementIndex = lastNode.footnoteElementIndex; + footnotes.insertedFootnotesLength = lastNode.totalFootnotes; + footnotes.listIndex = lastNode.footnoteListIndex; + footnotes.elementIndex = lastNode.footnoteElementIndex; int availableBPD = getLineWidth(lastNode.line); - int split = 0; KnuthPageNode prevNode = lastNode; // create pages containing the remaining footnote bodies - while (insertedFootnotesLength < totalFootnotesLength) { - final int tmpLength = lengthList.get(footnoteListIndex); + while (footnotes.footnotesRemaining()) { + final int remaining = footnotes.currentLength() - footnotes.insertedFootnotesLength; + // try adding some more content - if ((tmpLength - insertedFootnotesLength) <= availableBPD) { - // add a whole footnote - availableBPD -= tmpLength - insertedFootnotesLength; - insertedFootnotesLength = tmpLength; - footnoteElementIndex - = getFootnoteList(footnoteListIndex).size() - 1; - } else if ((split = getFootnoteSplit // CSOK: InnerAssignment - (footnoteListIndex, footnoteElementIndex, - insertedFootnotesLength, availableBPD, true)) > 0) { + if (remaining <= availableBPD) { + // all fits, so just add everything + availableBPD -= remaining; + footnotes.insertedFootnotesLength = footnotes.currentLength(); + footnotes.elementIndex + = getFootnoteList(footnotes.listIndex).size() - 1; + } else { + int split = footnotes.getFootnoteSplit(availableBPD); + if (split > 0) { - // add a piece of a footnote - availableBPD -= split; + // add a piece of a footnote + availableBPD -= split; - insertedFootnotesLength += split; - // footnoteListIndex has already been set in getFootnoteSplit() - // footnoteElementIndex has already been set in getFootnoteSplit() + footnotes.insertedFootnotesLength += split; + // listIndex has already been set in getFootnoteSplit() + // elementIndex has already been set in getFootnoteSplit() - } else { - // cannot add any content: create a new node and start again - KnuthPageNode node = (KnuthPageNode) - createNode(lastNode.position, prevNode.line + 1, 1, + } else { + // cannot add any content: create a new node and start again + KnuthPageNode node = (KnuthPageNode) + createNode(lastNode.position, prevNode.line + 1, 1, - insertedFootnotesLength - prevNode.totalFootnotes, + footnotes.insertedFootnotesLength - prevNode.totalFootnotes, - 0, 0, - 0, 0, 0, - 0, 0, prevNode); - addNode(node.line, node); - removeNode(prevNode.line, prevNode); + 0, 0, + 0, 0, 0, + 0, 0, prevNode); + addNode(node.line, node); + removeNode(prevNode.line, prevNode); - prevNode = node; - availableBPD = getLineWidth(node.line); - } - } + prevNode = node; + availableBPD = getLineWidth(node.line); + } + } + } + // create the last node KnuthPageNode node = (KnuthPageNode) createNode(lastNode.position, prevNode.line + 1, 1, - totalFootnotesLength - prevNode.totalFootnotes, 0, 0, + footnotes.totalLength - prevNode.totalFootnotes, 0, 0, 0, 0, 0, 0, 0, prevNode); addNode(node.line, node); @@ -960,12 +992,10 @@ pageBreaks.subList(0, pageBreaks.size() - 1).clear(); } - /** {@inheritDoc} */ @Override public void updateData1(int total, double demerits) { } - /** {@inheritDoc} */ @Override public void updateData2(KnuthNode bestActiveNode, KnuthSequence sequence, @@ -1011,7 +1041,7 @@ // compute the indexes of the first footnote list and the first element in that list int firstListIndex = ((KnuthPageNode) bestActiveNode.previous).footnoteListIndex; int firstElementIndex = ((KnuthPageNode) bestActiveNode.previous).footnoteElementIndex; - if (footnotesList != null + if (footnotes.footnotesList != null && firstElementIndex == getFootnoteList(firstListIndex).size() - 1) { // advance to the next list firstListIndex++; @@ -1034,7 +1064,6 @@ ratio, difference)); } - /** {@inheritDoc} */ @Override protected int filterActiveNodes() { // leave only the active node with fewest total demerits @@ -1066,7 +1095,7 @@ * @return the element-list */ protected final List getFootnoteList(int index) { - return footnotesList.get(index); + return footnotes.getList(index); } /** @return the associated top-level formatting object. */ @@ -1074,7 +1103,6 @@ return topLevelLM.getFObj(); } - /** {@inheritDoc} */ @Override protected int getLineWidth(int line) { int bpd; @@ -1104,13 +1132,11 @@ } - /** {@inheritDoc} */ @Override protected int getIPDdifference() { return ipdDifference; } - /** {@inheritDoc} */ @Override protected int handleIpdChange() { log.trace("Best node for ipd change:" + bestNodeForIPDChange); Index: src/java/org/apache/fop/layoutmgr/BreakingAlgorithm.java =================================================================== --- src/java/org/apache/fop/layoutmgr/BreakingAlgorithm.java (revision 1079013) +++ src/java/org/apache/fop/layoutmgr/BreakingAlgorithm.java (revision ) @@ -304,13 +304,19 @@ this.previous = previous; } - /** {@inheritDoc} */ + @Override public String toString() { - return ""; + return new StringBuilder(64).append("<") + .append(this.getClass().getSimpleName()) + .append(" at ").append(position) + .append(" ").append(totalWidth) + .append("+").append(totalStretch) + .append("-").append(totalShrink) + .append(" line:").append(line) + .append(" prev:").append(previous != null ? previous.position : -1) + .append(" dem:").append(totalDemerits) + .append(" fitness:").append(FitnessClasses.NAMES[fitness]) + .append(">").toString(); } } @@ -640,7 +646,7 @@ this.par.add(0, KnuthPenalty.DUMMY_ZERO_PENALTY); } } - + // content would overflow, insert empty line/page and try again return createNode( lastTooLong.previous.position, lastTooLong.previous.line + 1, 1, @@ -1168,20 +1174,49 @@ */ protected int computeDifference(KnuthNode activeNode, KnuthElement element, int elementIndex) { - // compute the adjustment ratio - int actualWidth = totalWidth - activeNode.totalWidth; + // compute the difference + int diff = getLineWidth(activeNode.line); + diff -= (totalWidth - activeNode.totalWidth); if (element.isPenalty()) { - actualWidth += element.getWidth(); + diff -= element.getWidth(); } - return getLineWidth() - actualWidth; + if (hasOutOfLineIntrusions()) { + diff -= getOutOfLineIntrusions(activeNode, elementIndex); - } + } + return diff; + } /** + * Hook for subclasses to handle out-of-line content (floats/footnotes). + *
Default implementation returns {@code false}. Should be overridden + * by subclasses if/when appropriate. + * @return {@code true} if the currently active node would have out-of-line + * content intruding on the line + */ + protected boolean hasOutOfLineIntrusions() { + return false; + } + + /** + * Hook for subclasses to handle out-of-line content (floats/footnotes). + * This method is called by {@link #computeDifference(KnuthNode, KnuthElement, int)}, + * if {@link #hasOutOfLineIntrusions()} returns {@code true}. + * + * @param activeNode the currently active node + * @param elementIndex the element's index + * @return the width corresponding to the total out-of-line intrusions + * (including static separators) + */ + protected int getOutOfLineIntrusions(KnuthNode activeNode, int elementIndex) { + return 0; + } + + /** * Return the adjustment ratio needed to make up for the difference. A ratio of *
    *
  • 0 means that the break has the exact right width
  • *
  • >= -1 && < 0 means that the break is wider than the line, - * but within the minimim values of the glues.
  • + * but within the minimum values of the glues. *
  • >0 && < 1 means that the break is smaller than the line width, * but within the maximum values of the glues.
  • *
  • > 1 means that the break is too small to make up for the glues.