--- test/layoutengine/standard-testcases/block_widows.xml (revision ) +++ test/layoutengine/standard-testcases/block_widows.xml (revision ) @@ -0,0 +1,143 @@ + + + + + +

+ This test checks the default behavior of the "widows" property. +

+
+ + + + + + + + + + + + Lorem ipsum dolor sit amet, consectetur + adipiscing elit. Cras placerat, lectus vel + iaculis euismod, ipsum enim dapibus + urna, eu pellentesque velit dolor ac + purus. Maecenas vitae pulvinar turpis. + Duis venenatis tincidunt velit, fringilla + dignissim sapien faucibus vel. Quisque + placerat ornare consectetur. Aenean + dui tortor, tempor ut convallis in, + fermentum tempor nunc. Class aptent + taciti sociosqu ad litora torquent per + + + + Lorem ipsum dolor sit amet, consectetur + adipiscing elit. Cras placerat, lectus vel + iaculis euismod, ipsum enim dapibus + urna, eu pellentesque velit dolor ac + purus. Maecenas vitae pulvinar turpis. + Duis venenatis tincidunt velit, fringilla + dignissim sapien faucibus vel. Quisque + placerat ornare consectetur. Aenean + dui tortor, tempor ut convallis in, + fermentum tempor nunc. Class aptent + taciti sociosqu ad litora torquent per + + Lorem ipsum dolor sit amet, consectetur + adipiscing elit. Cras placerat, lectus vel + iaculis euismod, ipsum enim dapibus + urna, eu pellentesque velit dolor ac + + + + + + Lorem ipsum + Lorem ipsum + Lorem ipsum + Lorem ipsum + Lorem ipsum + Lorem ipsum + Lorem ipsum + Lorem ipsum + Lorem ipsum + Lorem ipsum + Lorem ipsum + + + + + Lorem ipsum + dolor sit amet, + consectetur + adipiscing elit. + Cras placerat, + lectus vel + iaculis euismod, + ipsum enim + dapibus urna, + eu pellentesque + velit dolor + + + + + Lorem ipsum dolor sit amet, consectetur + adipiscing elit. Cras placerat, lectus vel + iaculis euismod, ipsum enim dapibus + urna, eu pellentesque velit dolor ac + purus. Maecenas vitae pulvinar turpis. + Duis venenatis tincidunt velit, fringilla + dignissim sapien faucibus vel. Quisque + placerat ornare consectetur. Aenean + dui tortor, tempor ut convallis in, + fermentum tempor nunc. Class aptent + + Lorem ipsum + + + + + + + + + + + + + + + + + +
--- test/layoutengine/standard-testcases/block_orphans.xml (revision ) +++ test/layoutengine/standard-testcases/block_orphans.xml (revision ) @@ -0,0 +1,167 @@ + + + + + +

+ This test checks the default behavior of the "orphans" property. +

+
+ + + + + + + + + + + + + Lorem ipsum dolor sit amet, consectetur + adipiscing elit. Cras placerat, lectus vel + iaculis euismod, ipsum enim dapibus + urna, eu pellentesque velit dolor ac + purus. Maecenas vitae pulvinar turpis. + Duis venenatis tincidunt velit, fringilla + dignissim sapien faucibus vel. Quisque + placerat ornare consectetur. Aenean + dui tortor, tempor ut convallis in, + + + Lorem ipsum dolor sit amet, consectetur + adipiscing elit. Cras placerat, lectus vel + iaculis euismod, ipsum enim dapibus + urna, eu pellentesque velit dolor ac + + + + + + Lorem ipsum dolor sit amet, consectetur + adipiscing elit. Cras placerat, lectus vel + iaculis euismod, ipsum enim dapibus + urna, eu pellentesque velit dolor ac + purus. Maecenas vitae pulvinar turpis. + Duis venenatis tincidunt velit, fringilla + dignissim sapien faucibus vel. Quisque + placerat ornare consectetur. Aenean + dui tortor, tempor ut convallis in, + + Lorem ipsum dolor sit amet, consectetur + adipiscing elit. Cras placerat, lectus vel + iaculis euismod, ipsum enim dapibus + urna, eu pellentesque velit dolor ac + + + + + Lorem ipsum dolor sit amet, consectetur + adipiscing elit. Cras placerat, lectus vel + iaculis euismod, ipsum enim dapibus + urna, eu pellentesque velit dolor ac + purus. Maecenas vitae pulvinar turpis. + Duis venenatis tincidunt velit, fringilla + dignissim sapien faucibus vel. Quisque + placerat ornare consectetur. Aenean + dui tortor, tempor ut convallis in, + + + Lorem ipsum + Lorem ipsum + Lorem ipsum + Lorem ipsum + + + + + + Lorem ipsum dolor sit amet, consectetur + adipiscing elit. Cras placerat, lectus vel + iaculis euismod, ipsum enim dapibus + urna, eu pellentesque velit dolor ac + purus. Maecenas vitae pulvinar turpis. + Duis venenatis tincidunt velit, fringilla + dignissim sapien faucibus vel. Quisque + placerat ornare consectetur. Aenean + dui tortor, tempor ut convallis in, + + Lorem ipsum + dolor sit amet, + consectetur + velit dolor + + + + Lorem ipsum + Lorem ipsum + Lorem ipsum + Lorem ipsum + Lorem ipsum + Lorem ipsum + Lorem ipsum + Lorem ipsum + Lorem ipsum + Lorem ipsum + Lorem ipsum + + Lorem ipsum + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
--- 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,35 @@ 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 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()) { + // non-auxiliary box => assume a 'line' and increase boxCount + boxCount++; + if (boxCount >= widows) { + break; - } + } + } + } + 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 +162,78 @@ // 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); } else { - return childLM.getNextKnuthElements(childLC, alignment, + childElements = childLM.getNextKnuthElements(childLC, alignment, lmStack, restartPosition, restartAtLM); } } + + if (isFirst) { + // handle orphans + int boxCount = 0, endIndex = 0; + ListElement current; + for (ListIterator it = childElements.listIterator(); it.hasNext();) { + current = it.next(); + endIndex++; + if (current.isBox() && !((KnuthBox) current).isAuxiliary()) { + // non-auxiliary box => assume a 'line' and increase boxCount + boxCount++; + if (boxCount >= orphans) { + break; - } + } + } + } + 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 first childLM did not produce enough + // lines to satisfy this LM's orphans constraint, avoid adding a break possibility + int boxCount = 0; + for (ListElement el : contentList) { + if (el.isBox() && !((KnuthBox) el).isAuxiliary()) { + boxCount++; + if (boxCount >= orphans) { + break; + } + } + } + + if (boxCount < orphans) { + 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,61 @@ } /** + * 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
  • + *
+ * + * @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; + } + } + + /** * 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 @@ -218,7 +273,8 @@ int prevBreak = startIndex - 1; while (prevBreak >= 0) { KnuthElement el = (KnuthElement)elems.get(prevBreak); - if (el.isPenalty() && el.getPenalty() < KnuthElement.INFINITE) { + if (el.isPenalty() + && ((KnuthElement)el).getPenalty() < KnuthElement.INFINITE) { break; } prevBreak--; --- src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java (revision 1088079) +++ src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java (revision ) @@ -918,10 +918,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(