emptyList()
+ : footnoteInfo.footnoteList);
}
/**
- * @return true if this box contains footnote citations.
+ * Returns {@code true} if this box has anchors for footnotes.
+ * @return {@code true} if this box has anchors for footnotes, {@code false} otherwise
*/
public boolean hasAnchors() {
- return (footnoteList.size() > 0);
+ return (footnoteInfo != null
+ && footnoteInfo.footnoteList != null
+ && !footnoteInfo.footnoteList.isEmpty());
}
/**
* Adds the given list of Knuth elements to this box' list of elements.
+ * Note: if the box is not associated with footnotes, the parameter is ignored.
*
* @param list elements corresponding to a footnote body
*/
- public void addElementList(List list) {
- if (elementLists == null) {
- elementLists = new LinkedList();
+ void addElementList(List list) {
+ assert (hasAnchors());
+ footnoteInfo.addElementList(list);
- }
+ }
- elementLists.add(list);
- }
/**
* Returns the list of Knuth sequences registered by this box.
*
* @return a list of KnuthElement sequences corresponding to footnotes cited in this box
*/
- public List getElementLists() {
- return elementLists;
+ List getElementLists() {
+ return footnoteInfo == null
+ ? Collections.>emptyList()
+ : footnoteInfo.elementLists;
}
/**
@@ -122,4 +194,12 @@
public int getBPD() {
return bpd;
}
+
+ /**
+ * Returns the (maximum) number of lines of content that this box represents
+ * @return the number of lines in this box
+ */
+ public int getLineCount() {
+ return lineCount;
-}
+ }
+}
--- src/java/org/apache/fop/layoutmgr/KnuthBox.java (revision 893238)
+++ src/java/org/apache/fop/layoutmgr/KnuthBox.java (revision )
@@ -45,12 +45,12 @@
super(width, pos, auxiliary);
}
- /** {@inheritDoc} */
+ @Override
public boolean isBox() {
return true;
}
- /** {@inheritDoc} */
+ @Override
public String toString() {
StringBuffer buffer = new StringBuffer(64);
if (isAuxiliary()) {
@@ -61,5 +61,4 @@
buffer.append(getWidth());
return buffer.toString();
}
-
}
--- src/java/org/apache/fop/layoutmgr/KnuthPenalty.java (revision 1079013)
+++ src/java/org/apache/fop/layoutmgr/KnuthPenalty.java (revision )
@@ -46,6 +46,9 @@
/** Dummy, zero-width penalty */
public static final KnuthPenalty DUMMY_ZERO_PENALTY
= new KnuthPenalty(0, 0, false, null, true);
+ /** Dummy break inhibitor */
+ public static final KnuthPenalty DUMMY_INFINITE_PENALTY
+ = new KnuthPenalty(0, KnuthElement.INFINITE, false, null, true);
private int penalty;
private boolean penaltyFlagged;
--- src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java (revision 1073116)
+++ src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java (revision )
@@ -66,6 +66,9 @@
private MinOptMax effSpaceBefore;
private MinOptMax effSpaceAfter;
+ private int orphans = 2;
+ private int widows = 2;
+
/**
* Creates a new BlockLayoutManager.
* @param inBlock the block FO object to create the layout manager for.
@@ -97,6 +100,8 @@
.getOptimum(this).getLength().getValue(this);
adjustedSpaceAfter = fo.getCommonMarginBlock().spaceAfter.getSpace()
.getOptimum(this).getLength().getValue(this);
+ orphans = fo.getOrphans();
+ widows = fo.getWidows();
}
/** {@inheritDoc} */
@@ -110,10 +115,42 @@
public List getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack,
Position restartPosition, LayoutManager restartAtLM) {
resetSpaces();
- return super.getNextKnuthElements(
+
+ List contentList = super.getNextKnuthElements(
context, alignment, lmStack, restartPosition, restartAtLM);
+
+ if (!this.hasNextChildLM()) {
+ // handle widows
+ int lineCount = 0, boxCount = 0, index = contentList.size();
+ ListElement current;
+ for (ListIterator it = contentList.listIterator(contentList.size());
+ it.hasPrevious();) {
+ current = it.previous();
+ index--;
+ if (current.isBox() && !((KnuthBox) current).isAuxiliary()) {
+ boxCount++;
+ // non-auxiliary box => increase lineCount
+ if (current instanceof KnuthBlockBox) {
+ lineCount += ((KnuthBlockBox) current).getLineCount();
+ } else {
+ // assume one line
+ lineCount++;
- }
+ }
+ if (lineCount >= widows) {
+ break;
+ }
+ }
+ }
+ // if the sublist only consists of boxes, nothing to remove
+ if (index <= (contentList.size() - boxCount)) {
+ ElementListUtils.removeLegalBreaks(
+ contentList.subList(index, contentList.size()));
+ }
+ }
+ return contentList;
+ }
+
/**
* Overridden to take into account that the childLM may be the block's
* {@link LineLayoutManager}.
@@ -132,25 +169,82 @@
// nop; will have been properly set by makeChildLayoutContext()
}
- if (childLM == this.childLMs.get(0)) {
+ boolean isFirst = childLM == this.childLMs.get(0);
+ if (isFirst) {
childLC.setFlags(LayoutContext.SUPPRESS_BREAK_BEFORE);
//Handled already by the parent (break collapsing, see above)
}
+ List childElements;
if (lmStack == null) {
- return childLM.getNextKnuthElements(childLC, alignment);
+ childElements = childLM.getNextKnuthElements(childLC, alignment);
} else {
if (childLM instanceof LineLayoutManager) {
assert (restartPosition instanceof LeafPosition);
- return ((LineLayoutManager) childLM).getNextKnuthElements(childLC, alignment,
+ childElements
+ = ((LineLayoutManager) childLM).getNextKnuthElements(childLC, alignment,
- (LeafPosition) restartPosition);
+ (LeafPosition) restartPosition);
} else {
- return childLM.getNextKnuthElements(childLC, alignment,
+ childElements = childLM.getNextKnuthElements(childLC, alignment,
lmStack, restartPosition, restartAtLM);
}
}
+
+ if (childElements != null && isFirst) {
+ // handle orphans
+ int lineCount = 0, boxCount = 0, endIndex = 0;
+ boolean orphansSatisfied = false;
+ ListElement current;
+ for (ListIterator it = childElements.listIterator(); it.hasNext();) {
+ current = it.next();
+ endIndex++;
+ if (current.isBox() && !((KnuthBox) current).isAuxiliary()) {
+ boxCount++;
+ // non-auxiliary box => increase lineCount
+ if (current instanceof KnuthBlockBox) {
+ lineCount += ((KnuthBlockBox) current).getLineCount();
+ } else {
+ // assume one line
+ lineCount++;
- }
+ }
+ if (lineCount >= orphans) {
+ break;
+ }
+ }
+ }
+ // if the sublist only consists of boxes, nothing to remove
+ if (endIndex > boxCount) {
+ ElementListUtils.removeLegalBreaks(childElements.subList(0, endIndex));
+ }
+ }
+ return childElements;
+ }
+
+ /**
+ * Overridden to deal with a special case for the "orphans" property.
+ * {@inheritDoc}
+ */
+ @Override
+ protected void addInBetweenBreak(List contentList,
+ LayoutContext parentLC, LayoutContext childLC) {
+
+ if (this.childLMs.size() > 1
+ && this.curChildLM == this.childLMs.get(1)) {
+ // Special case - second childLM:
+ // If the content so far did not produce enough lines to satisfy
+ // this LM's orphans constraint, avoid adding a break possibility.
+ if (ElementListUtils.getLineCount(contentList) < orphans) {
+ // Make sure pending keeps are cleared (= satisfied by the orphans constraint)
+ parentLC.clearKeepWithNextPending();
+ childLC.clearKeepWithPreviousPending();
+ return;
+ }
+ }
+
+ super.addInBetweenBreak(contentList, parentLC, childLC);
+ }
+
private void resetSpaces() {
this.discardBorderBefore = false;
this.discardBorderAfter = false;
--- src/java/org/apache/fop/layoutmgr/ElementListUtils.java (revision 1056513)
+++ src/java/org/apache/fop/layoutmgr/ElementListUtils.java (revision )
@@ -133,6 +133,87 @@
}
/**
+ * Removes all legal break points from the given {@code List}.
+ * That is, this method:
+ *
+ * - removes penalties in between two boxes
+ * - converts other penalties to inhibit breaks
+ * - converts glues following a box into auxiliary boxes
+ * or penalty-glue sequences
+ *
+ *
+ * Note: leading glues will not be treated, as it is assumed nothing precedes
+ * the first element of the given list. Similarly, trailing penalties would just be set
+ * to {@link KnuthElement#INFINITE}, as there is never a following box.
+ * Care should be taken to provide as much context elements as required. Ideally, the
+ * list should start with a box and end with a box or glue.
+ *
+ * @param elements the element list from which the breaks will be removed
+ */
+ public static void removeLegalBreaks(List elements) {
+
+ ListElement current;
+ boolean previousIsBox = false;
+
+ for (ListIterator it = elements.listIterator(); it.hasNext();) {
+
+ current = it.next();
+
+ if (current.isBox()) {
+ previousIsBox = true;
+ continue;
+ } else if (current.isGlue() && previousIsBox) {
+ KnuthGlue glue = (KnuthGlue)current;
+ if (glue.getStretch() == 0 && glue.getShrink() == 0) {
+ // non-stretchable glue => replace with a box of the same width
+ it.set(new KnuthBox(glue.getWidth(), glue.getPosition(), true));
+ } else {
+ // stretchable glue => add break-inhibitor
+ it.previous();
+ it.add(KnuthPenalty.DUMMY_INFINITE_PENALTY);
+ it.next();
+ }
+ } else if (current.isPenalty() || current instanceof BreakElement
+ && !current.isForcedBreak()) {
+ boolean nextIsBox = (it.hasNext() && it.next().isBox());
+ it.previous();
+ if (previousIsBox && nextIsBox) {
+ // penalty or BreakElement in between boxes => remove
+ it.previous();
+ it.remove();
+ } else {
+ if (current.isPenalty()) {
+ ((KnuthPenalty)current).setPenalty(KnuthElement.INFINITE);
+ } else {
+ ((BreakElement)current).setPenaltyValue(KnuthElement.INFINITE);
+ }
+ }
+ }
+ previousIsBox = false;
+ }
+ }
+
+ /**
+ * Obtains the (maximum) number of lines in the given list.
+ * @param elements the list to count the lines for
+ * @return the number of lines
+ */
+ public static int getLineCount(List elements) {
+ int lineCount = 0;
+ for (ListElement current : elements) {
+ if (current.isBox() && !((KnuthBox) current).isAuxiliary()) {
+ if (current instanceof KnuthBlockBox) {
+ lineCount += ((KnuthBlockBox) current).getLineCount();
+ } else {
+ // assume one line
+ lineCount++;
+ }
+ }
+ }
+ return lineCount;
+ }
+
+ /**
* Calculates the content length of the given element list. Warning: It doesn't take any
* stretch and shrink possibilities into account.
* @param elems the element list
--- src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java (revision 1088950)
+++ src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java (revision )
@@ -656,6 +656,7 @@
/* curLM.getNextKnuthElements() returned null or an empty list;
* this can happen if there is nothing more to layout,
* so just iterate once more to see if there are other children */
+ curLM = (InlineLevelLayoutManager) getChildLM();
continue;
}
@@ -919,10 +920,7 @@
for (int i = 0;
i < llPoss.getChosenLineCount();
i++) {
- if (returnList.size() > 0
- && i > 0 //if i==0 break generated above already
- && i >= fobj.getOrphans()
- && i <= llPoss.getChosenLineCount() - fobj.getWidows()) {
+ if (returnList.size() > 0 && i > 0) {
// penalty allowing a page break between lines
Keep keep = getKeepTogether();
returnList.add(new BreakElement(
@@ -932,24 +930,28 @@
context));
}
endIndex = ((LineBreakPosition) llPoss.getChosenPosition(i)).getLeafPos();
+
// create a list of the FootnoteBodyLM handling footnotes
// whose citations are in this line
List footnoteList = new LinkedList();
- ListIterator elementIterator = seq.listIterator(startIndex);
- while (elementIterator.nextIndex() <= endIndex) {
- KnuthElement element = elementIterator.next();
- if (element instanceof KnuthInlineBox
- && ((KnuthInlineBox) element).isAnchor()) {
- footnoteList.add(((KnuthInlineBox) element).getFootnoteBodyLM());
- } else if (element instanceof KnuthBlockBox) {
- footnoteList.addAll(((KnuthBlockBox) element).getFootnoteBodyLMs());
+ for (Object o : seq.subList(startIndex, endIndex)) {
+ //TODO: remove cast once KnuthSequence can be made type-safe
+ KnuthElement el = (KnuthElement) o;
+ if (el instanceof KnuthInlineBox
+ && ((KnuthInlineBox) el).isAnchor()) {
+ footnoteList.add(((KnuthInlineBox) el).getFootnoteBodyLM());
+ } else if (el instanceof KnuthBlockBox
+ && ((KnuthBlockBox) el).hasAnchors()) {
+ footnoteList.addAll(((KnuthBlockBox) el).getFootnoteBodyLMs());
}
}
- startIndex = endIndex + 1;
+
LineBreakPosition lbp = (LineBreakPosition) llPoss.getChosenPosition(i);
- returnList.add(new KnuthBlockBox
- (lbp.lineHeight + lbp.spaceBefore + lbp.spaceAfter,
- footnoteList, lbp, false));
+ KnuthBox newBox = new KnuthBlockBox(
+ lbp.lineHeight + lbp.spaceBefore + lbp.spaceAfter, lbp, false, 1, footnoteList);
+ returnList.add(newBox);
+
+ startIndex = endIndex + 1;
}
}
}
@@ -1155,9 +1157,7 @@
LineLayoutPossibilities llPoss = lineLayoutsList[p];
//log.debug("demerits of the chosen layout: " + llPoss.getChosenDemerits());
for (int i = 0; i < llPoss.getChosenLineCount(); i++) {
- if (!((BlockLevelLayoutManager) parentLayoutManager).mustKeepTogether()
- && i >= fobj.getOrphans()
- && i <= llPoss.getChosenLineCount() - fobj.getWidows()) {
+ if (!((BlockLevelLayoutManager) parentLayoutManager).mustKeepTogether()) {
// null penalty allowing a page break between lines
returnList.add(new KnuthPenalty(0, 0, false, new Position(this), false));
}
@@ -1182,9 +1182,9 @@
contentIPD
= MinOptMax.getInstance(lbp.lineWidth - lbp.difference + lbp.startIndent);
}
- returnList.add(new KnuthBlockBox(lbp.lineHeight, contentIPD, (lbp.ipdAdjust != 0
- ? lbp.lineWidth - lbp.difference : 0),
- lbp, false));
+ returnList.add(new KnuthBlockBox(lbp.lineHeight, lbp, false,
+ contentIPD,
+ (lbp.ipdAdjust != 0 ? lbp.lineWidth - lbp.difference : 0)));
}
}
return returnList;