--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2006 The Apache Software Foundation
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!-- $Id$ -->
+<testcase>
+ <info>
+ <p>
+ This test checks Bug 39414 - Index overflow on long texts
+ </p>
+ </info>
+ <fo>
+ <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:svg="http://www.w3.org/2000/svg">
+ <fo:layout-master-set>
+ <fo:simple-page-master master-name="normal" page-width="5in" page-height="5in">
+ <fo:region-body/>
+ </fo:simple-page-master>
+ </fo:layout-master-set>
+ <fo:page-sequence master-reference="normal" white-space-collapse="true">
+ <fo:flow flow-name="xsl-region-body">
+ <fo:block font-family="monospace" white-space="pre"><![CDATA[
+/*
+ * Copyright 1999-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.layoutmgr.inline;
+
+import org.apache.fop.datatypes.Length;
+import org.apache.fop.datatypes.Numeric;
+import org.apache.fop.fo.Constants;
+import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.flow.Block;
+import org.apache.fop.fo.properties.CommonHyphenation;
+import org.apache.fop.hyphenation.Hyphenation;
+import org.apache.fop.hyphenation.Hyphenator;
+import org.apache.fop.layoutmgr.BlockLevelLayoutManager;
+import org.apache.fop.layoutmgr.BreakElement;
+import org.apache.fop.layoutmgr.BreakingAlgorithm;
+import org.apache.fop.layoutmgr.ElementListObserver;
+import org.apache.fop.layoutmgr.InlineKnuthSequence;
+import org.apache.fop.layoutmgr.KnuthBlockBox;
+import org.apache.fop.layoutmgr.KnuthBox;
+import org.apache.fop.layoutmgr.KnuthElement;
+import org.apache.fop.layoutmgr.KnuthGlue;
+import org.apache.fop.layoutmgr.KnuthPenalty;
+import org.apache.fop.layoutmgr.KnuthPossPosIter;
+import org.apache.fop.layoutmgr.KnuthSequence;
+import org.apache.fop.layoutmgr.LayoutContext;
+import org.apache.fop.layoutmgr.LayoutManager;
+import org.apache.fop.layoutmgr.LeafPosition;
+import org.apache.fop.layoutmgr.ListElement;
+import org.apache.fop.layoutmgr.NonLeafPosition;
+import org.apache.fop.layoutmgr.Position;
+import org.apache.fop.layoutmgr.PositionIterator;
+import org.apache.fop.layoutmgr.SpaceSpecifier;
+import org.apache.fop.area.Area;
+import org.apache.fop.area.LineArea;
+import org.apache.fop.area.inline.InlineArea;
+
+import java.util.ListIterator;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import org.apache.fop.area.Trait;
+import org.apache.fop.fonts.Font;
+
+import org.apache.fop.traits.MinOptMax;
+
+/**
+ * LayoutManager for lines. It builds one or more lines containing
+ * inline areas generated by its sub layout managers.
+ * A break is found for each line which may contain one of more
+ * breaks from the child layout managers.
+ * Once a break is found then it is return for the parent layout
+ * manager to handle.
+ * When the areas are being added to the page this manager
+ * creates a line area to contain the inline areas added by the
+ * child layout managers.
+ */
+public class LineLayoutManager extends InlineStackingLayoutManager
+ implements BlockLevelLayoutManager {
+
+ private Block fobj;
+ private boolean isFirstInBlock;
+
+ /** @see org.apache.fop.layoutmgr.LayoutManager#initialize() */
+ public void initialize() {
+ textAlignment = fobj.getTextAlign();
+ textAlignmentLast = fobj.getTextAlignLast();
+ textIndent = fobj.getTextIndent();
+ lastLineEndIndent = fobj.getLastLineEndIndent();
+ hyphenationProperties = fobj.getCommonHyphenation();
+ hyphenationLadderCount = fobj.getHyphenationLadderCount();
+ wrapOption = fobj.getWrapOption();
+ whiteSpaceTreament = fobj.getWhitespaceTreatment();
+ //
+ effectiveAlignment = getEffectiveAlignment(textAlignment, textAlignmentLast);
+ isFirstInBlock = (this == getParent().getChildLMs().get(0));
+ }
+
+ private int getEffectiveAlignment(int alignment, int alignmentLast) {
+ if (textAlignment != EN_JUSTIFY && textAlignmentLast == EN_JUSTIFY) {
+ return 0;
+ } else {
+ return textAlignment;
+ }
+ }
+
+ /**
+ * Private class to store information about inline breaks.
+ * Each value holds the start and end indexes into a List of
+ * inline break positions.
+ */
+ private static class LineBreakPosition extends LeafPosition {
+ private int iParIndex; // index of the Paragraph this Position refers to
+ private int iStartIndex; //index of the first element this Position refers to
+ private int availableShrink;
+ private int availableStretch;
+ private int difference;
+ private double dAdjust; // Percentage to adjust (stretch or shrink)
+ private double ipdAdjust; // Percentage to adjust (stretch or shrink)
+ private int startIndent;
+ private int lineHeight;
+ private int lineWidth;
+ private int spaceBefore;
+ private int spaceAfter;
+ private int baseline;
+
+ LineBreakPosition(LayoutManager lm, int index, int iStartIndex, int iBreakIndex,
+ int shrink, int stretch, int diff,
+ double ipdA, double adjust, int ind,
+ int lh, int lw, int sb, int sa, int bl) {
+ super(lm, iBreakIndex);
+ availableShrink = shrink;
+ availableStretch = stretch;
+ difference = diff;
+ iParIndex = index;
+ this.iStartIndex = iStartIndex;
+ ipdAdjust = ipdA;
+ dAdjust = adjust;
+ startIndent = ind;
+ lineHeight = lh;
+ lineWidth = lw;
+ spaceBefore = sb;
+ spaceAfter = sa;
+ baseline = bl;
+ }
+
+ }
+
+
+ private int textAlignment = EN_JUSTIFY;
+ private int textAlignmentLast;
+ private int effectiveAlignment;
+ private Length textIndent;
+ private Length lastLineEndIndent;
+ private CommonHyphenation hyphenationProperties;
+ private Numeric hyphenationLadderCount;
+ private int wrapOption = EN_WRAP;
+ private int whiteSpaceTreament;
+ //private LayoutProps layoutProps;
+
+ private Length lineHeight;
+ private int lead;
+ private int follow;
+ private AlignmentContext alignmentContext = null;
+
+ private List knuthParagraphs = null;
+ private int iReturnedLBP = 0;
+
+ // parameters of Knuth's algorithm:
+ // penalty value for flagged penalties
+ private int flaggedPenalty = 50;
+
+ private LineLayoutPossibilities lineLayouts;
+ private List lineLayoutsList;
+ private int iLineWidth = 0;
+
+ /**
+ * this constant is used to create elements when text-align is center:
+ * every TextLM descendant of LineLM must use the same value,
+ * otherwise the line breaking algorithm does not find the right
+ * break point
+ */
+ public static final int DEFAULT_SPACE_WIDTH = 3336;
+
+
+ /**
+ * This class is used to remember
+ * which was the first element in the paragraph
+ * returned by each LM.
+ */
+ private class Update {
+ private InlineLevelLayoutManager inlineLM;
+ private int iFirstIndex;
+
+ public Update(InlineLevelLayoutManager lm, int index) {
+ inlineLM = lm;
+ iFirstIndex = index;
+ }
+ }
+
+ // this class represents a paragraph
+ private class Paragraph extends InlineKnuthSequence {
+ /** Number of elements to ignore at the beginning of the list. */
+ private int ignoreAtStart = 0;
+ /** Number of elements to ignore at the end of the list. */
+ private int ignoreAtEnd = 0;
+
+ // space at the end of the last line (in millipoints)
+ private MinOptMax lineFiller;
+ private int textAlignment;
+ private int textAlignmentLast;
+ private int textIndent;
+ private int lastLineEndIndent;
+ private int lineWidth;
+ // the LM which created the paragraph
+ private LineLayoutManager layoutManager;
+
+ public Paragraph(LineLayoutManager llm, int alignment, int alignmentLast,
+ int indent, int endIndent) {
+ super();
+ layoutManager = llm;
+ textAlignment = alignment;
+ textAlignmentLast = alignmentLast;
+ textIndent = indent;
+ lastLineEndIndent = endIndent;
+ }
+
+ public void startParagraph(int lw) {
+ lineWidth = lw;
+ startSequence();
+ }
+
+ public void startSequence() {
+ // set the minimum amount of empty space at the end of the
+ // last line
+ if (textAlignment == EN_CENTER) {
+ lineFiller = new MinOptMax(lastLineEndIndent);
+ } else {
+ lineFiller = new MinOptMax(lastLineEndIndent, lastLineEndIndent, lineWidth);
+ }
+
+ // add auxiliary elements at the beginning of the paragraph
+ if (textAlignment == EN_CENTER && textAlignmentLast != EN_JUSTIFY) {
+ this.add(new KnuthGlue(0, 3 * DEFAULT_SPACE_WIDTH, 0,
+ null, false));
+ ignoreAtStart++;
+ }
+
+ // add the element representing text indentation
+ // at the beginning of the first paragraph
+ if (isFirstInBlock && knuthParagraphs.size() == 0
+ && textIndent != 0) {
+ this.add(new KnuthInlineBox(textIndent, null,
+ null, false));
+ ignoreAtStart++;
+ }
+ }
+
+ public void endParagraph() {
+ KnuthSequence finishedPar = this.endSequence();
+ if (finishedPar != null) {
+ knuthParagraphs.add(finishedPar);
+ }
+ }
+
+ public KnuthSequence endSequence() {
+ if (this.size() > ignoreAtStart) {
+ if (textAlignment == EN_CENTER
+ && textAlignmentLast != EN_JUSTIFY) {
+ this.add(new KnuthGlue(0, 3 * DEFAULT_SPACE_WIDTH, 0,
+ null, false));
+ this.add(new KnuthPenalty(lineFiller.opt, -KnuthElement.INFINITE,
+ false, null, false));
+ ignoreAtEnd = 2;
+ } else if (textAlignmentLast != EN_JUSTIFY) {
+ // add the elements representing the space
+ // at the end of the last line
+ // and the forced break
+ this.add(new KnuthPenalty(0, KnuthElement.INFINITE,
+ false, null, false));
+ this.add(new KnuthGlue(0,
+ lineFiller.max - lineFiller.opt,
+ lineFiller.opt - lineFiller.min, null, false));
+ this.add(new KnuthPenalty(lineFiller.opt, -KnuthElement.INFINITE,
+ false, null, false));
+ ignoreAtEnd = 3;
+ } else {
+ // add only the element representing the forced break
+ this.add(new KnuthPenalty(lineFiller.opt, -KnuthElement.INFINITE,
+ false, null, false));
+ ignoreAtEnd = 1;
+ }
+ return this;
+ } else {
+ this.clear();
+ return null;
+ }
+ }
+
+ /**
+ * @return true if the sequence contains a box
+ */
+ public boolean containsBox() {
+ for (int i = 0; i < this.size(); i++) {
+ KnuthElement el = (KnuthElement)this.get(i);
+ if (el.isBox()) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private class LineBreakingAlgorithm extends BreakingAlgorithm {
+ private LineLayoutManager thisLLM;
+ private int pageAlignment;
+ private int activePossibility;
+ private int addedPositions;
+ private int textIndent;
+ private int fillerMinWidth;
+ private int lineHeight;
+ private int lead;
+ private int follow;
+ private int maxDiff;
+ private static final double MAX_DEMERITS = 10e6;
+
+ public LineBreakingAlgorithm (int pageAlign,
+ int textAlign, int textAlignLast,
+ int indent, int fillerWidth,
+ int lh, int ld, int fl, boolean first,
+ int maxFlagCount, LineLayoutManager llm) {
+ super(textAlign, textAlignLast, first, false, maxFlagCount);
+ pageAlignment = pageAlign;
+ textIndent = indent;
+ fillerMinWidth = fillerWidth;
+ lineHeight = lh;
+ lead = ld;
+ follow = fl;
+ thisLLM = llm;
+ activePossibility = -1;
+ maxDiff = fobj.getWidows() >= fobj.getOrphans()
+ ? fobj.getWidows()
+ : fobj.getOrphans();
+ }
+
+ public void updateData1(int lineCount, double demerits) {
+ lineLayouts.addPossibility(lineCount, demerits);
+ log.trace("Layout possibility in " + lineCount + " lines; break at position:");
+ }
+
+ public void updateData2(KnuthNode bestActiveNode,
+ KnuthSequence par,
+ int total) {
+ // compute indent and adjustment ratio, according to
+ // the value of text-align and text-align-last
+ int indent = 0;
+ int difference = bestActiveNode.difference;
+ int textAlign = (bestActiveNode.line < total) ? alignment : alignmentLast;
+ indent += (textAlign == Constants.EN_CENTER)
+ ? difference / 2 : (textAlign == Constants.EN_END) ? difference : 0;
+ indent += (bestActiveNode.line == 1 && bFirst && isFirstInBlock) ? textIndent : 0;
+ double ratio = (textAlign == Constants.EN_JUSTIFY
+ || difference < 0 && -difference <= bestActiveNode.availableShrink)
+ ? bestActiveNode.adjustRatio : 0;
+
+ // add nodes at the beginning of the list, as they are found
+ // backwards, from the last one to the first one
+
+ // the first time this method is called, initialize activePossibility
+ if (activePossibility == -1) {
+ activePossibility = 0;
+ addedPositions = 0;
+ }
+
+ if (addedPositions == lineLayouts.getLineCount(activePossibility)) {
+ activePossibility++;
+ addedPositions = 0;
+ }
+
+ if (difference + bestActiveNode.availableShrink < 0) {
+ if (log.isWarnEnabled()) {
+ log.warn(FONode.decorateWithContextInfo(
+ "Line " + (addedPositions + 1)
+ + " of a paragraph overflows the available area.", getFObj()));
+ }
+ }
+
+ //log.debug("LLM> (" + (lineLayouts.getLineNumber(activePossibility) - addedPositions)
+ // + ") difference = " + difference + " ratio = " + ratio);
+ lineLayouts.addBreakPosition(makeLineBreakPosition(par,
+ (bestActiveNode.line > 1 ? bestActiveNode.previous.position + 1 : 0),
+ bestActiveNode.position,
+ bestActiveNode.availableShrink - (addedPositions > 0
+ ? 0 : ((Paragraph)par).lineFiller.opt - ((Paragraph)par).lineFiller.min),
+ bestActiveNode.availableStretch,
+ difference, ratio, indent), activePossibility);
+ addedPositions++;
+ }
+
+ /* reset activePossibility, as if breakpoints have not yet been computed
+ */
+ public void resetAlgorithm() {
+ activePossibility = -1;
+ }
+
+ private LineBreakPosition makeLineBreakPosition(KnuthSequence par,
+ int firstElementIndex,
+ int lastElementIndex,
+ int availableShrink,
+ int availableStretch,
+ int difference,
+ double ratio,
+ int indent) {
+ // line height calculation - spaceBefore may differ from spaceAfter
+ // by 1mpt due to rounding
+ int spaceBefore = (lineHeight - lead - follow) / 2;
+ int spaceAfter = lineHeight - lead - follow - spaceBefore;
+ // height before the main baseline
+ int lineLead = lead;
+ // maximum follow
+ int lineFollow = follow;
+ // 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 bZeroHeightLine = (difference == iLineWidth);
+
+ // if line-stacking-strategy is "font-height", the line height
+ // is not affected by its content
+ if (fobj.getLineStackingStrategy() != EN_FONT_HEIGHT) {
+ ListIterator inlineIterator
+ = par.listIterator(firstElementIndex);
+ AlignmentContext lastAC = null;
+ int maxIgnoredHeight = 0; // See spec 7.13
+ for (int j = firstElementIndex;
+ j <= lastElementIndex;
+ j++) {
+ KnuthElement element = (KnuthElement) inlineIterator.next();
+ if (element instanceof KnuthInlineBox ) {
+ AlignmentContext ac = ((KnuthInlineBox) element).getAlignmentContext();
+ if (ac != null && lastAC != ac) {
+ if (!ac.usesInitialBaselineTable()
+ || ac.getAlignmentBaselineIdentifier() != EN_BEFORE_EDGE
+ && ac.getAlignmentBaselineIdentifier() != EN_AFTER_EDGE) {
+ int alignmentOffset = ac.getTotalAlignmentBaselineOffset();
+ if (alignmentOffset + ac.getAltitude() > lineLead) {
+ lineLead = alignmentOffset + ac.getAltitude();
+ }
+ if (ac.getDepth() - alignmentOffset > lineFollow) {
+ lineFollow = ac.getDepth() - alignmentOffset;
+ }
+ } else {
+ if (ac.getHeight() > maxIgnoredHeight) {
+ maxIgnoredHeight = ac.getHeight();
+ }
+ }
+ lastAC = ac;
+ }
+ if (bZeroHeightLine
+ && (!element.isAuxiliary() || ac != null && ac.getHeight() > 0)) {
+ bZeroHeightLine = false;
+ }
+ }
+ }
+
+ if (lineFollow < maxIgnoredHeight - lineLead) {
+ lineFollow = maxIgnoredHeight - lineLead;
+ }
+ }
+
+ constantLineHeight = lineLead + lineFollow;
+
+ if (bZeroHeightLine) {
+ return new LineBreakPosition(thisLLM,
+ knuthParagraphs.indexOf(par),
+ firstElementIndex, lastElementIndex,
+ availableShrink, availableStretch,
+ difference, ratio, 0, indent,
+ 0, iLineWidth, 0, 0, 0);
+ } else {
+ return new LineBreakPosition(thisLLM,
+ knuthParagraphs.indexOf(par),
+ firstElementIndex, lastElementIndex,
+ availableShrink, availableStretch,
+ difference, ratio, 0, indent,
+ lineLead + lineFollow,
+ iLineWidth, spaceBefore, spaceAfter,
+ lineLead);
+ }
+ }
+
+ public int findBreakingPoints(Paragraph par, /*int lineWidth,*/
+ double threshold, boolean force,
+ int allowedBreaks) {
+ return super.findBreakingPoints(par, /*lineWidth,*/
+ threshold, force, allowedBreaks);
+ }
+
+ protected int filterActiveNodes() {
+ KnuthNode bestActiveNode = null;
+
+ if (pageAlignment == EN_JUSTIFY) {
+ // leave all active nodes and find the optimum line number
+ //log.debug("LBA.filterActiveNodes> " + activeNodeCount + " layouts");
+ for (int i = startLine; i < endLine; i++) {
+ for (KnuthNode node = getNode(i); node != null; node = node.next) {
+ //log.debug(" + lines = " + node.line + " demerits = " + node.totalDemerits);
+ bestActiveNode = compareNodes(bestActiveNode, node);
+ }
+ }
+
+ // scan the node set once again and remove some nodes
+ //log.debug("LBA.filterActiveList> layout selection");
+ for (int i = startLine; i < endLine; i++) {
+ for (KnuthNode node = getNode(i); node != null; node = node.next) {
+ //if (Math.abs(node.line - bestActiveNode.line) > maxDiff) {
+ //if (false) {
+ if (node.line != bestActiveNode.line
+ && node.totalDemerits > MAX_DEMERITS) {
+ //log.debug(" XXX lines = " + node.line + " demerits = " + node.totalDemerits);
+ removeNode(i, node);
+ } else {
+ //log.debug(" ok lines = " + node.line + " demerits = " + node.totalDemerits);
+ }
+ }
+ }
+ } else {
+ // leave only the active node with fewest total demerits
+ for (int i = startLine; i < endLine; i++) {
+ for (KnuthNode node = getNode(i); node != null; node = node.next) {
+ bestActiveNode = compareNodes(bestActiveNode, node);
+ if (node != bestActiveNode) {
+ removeNode(i, node);
+ }
+ }
+ }
+ }
+ return bestActiveNode.line;
+ }
+ }
+
+
+ private int constantLineHeight = 12000;
+
+
+ /**
+ * Create a new Line Layout Manager.
+ * This is used by the block layout manager to create
+ * line managers for handling inline areas flowing into line areas.
+ * @param block the block formatting object
+ * @param lh the default line height
+ * @param l the default lead, from top to baseline
+ * @param f the default follow, from baseline to bottom
+ */
+ public LineLayoutManager(Block block, Length lh, int l, int f) {
+ super(block);
+ fobj = block;
+ // the child FObj are owned by the parent BlockLM
+ // this LM has all its childLMs preloaded
+ fobjIter = null;
+ lineHeight = lh;
+ lead = l;
+ follow = f;
+ }
+
+ /** @see org.apache.fop.layoutmgr.LayoutManager */
+ public LinkedList getNextKnuthElements(LayoutContext context, int alignment) {
+ Font fs = fobj.getCommonFont().getFontState(fobj.getFOEventHandler().getFontInfo(), this);
+ alignmentContext
+ = new AlignmentContext(fs, lineHeight.getValue(this), context.getWritingMode());
+ context.setAlignmentContext(alignmentContext);
+ // Get a break from currently active child LM
+ // Set up constraints for inline level managers
+
+ // IPD remaining in line
+ MinOptMax availIPD = context.getStackLimit();
+
+ clearPrevIPD();
+
+ //PHASE 1: Create Knuth elements
+ if (knuthParagraphs == null) {
+ // it's the first time this method is called
+ knuthParagraphs = new ArrayList();
+
+ // here starts Knuth's algorithm
+ //TODO availIPD should not really be used here, so we can later support custom line
+ //widths for for each line (side-floats, differing available IPD after page break)
+ collectInlineKnuthElements(context, availIPD);
+ } else {
+ // this method has been called before
+ // all line breaks are already calculated
+ }
+
+ // return finished when there's no content
+ if (knuthParagraphs.size() == 0) {
+ setFinished(true);
+ return null;
+ }
+
+ //PHASE 2: Create line breaks
+ return createLineBreaks(context.getBPAlignment(), context);
+ /*
+ LineBreakPosition lbp = null;
+ if (breakpoints == null) {
+ // find the optimal line breaking points for each paragraph
+ breakpoints = new ArrayList();
+ ListIterator paragraphsIterator
+ = knuthParagraphs.listIterator(knuthParagraphs.size());
+ Paragraph currPar = null;
+ while (paragraphsIterator.hasPrevious()) {
+ currPar = (Paragraph) paragraphsIterator.previous();
+ findBreakingPoints(currPar, context.getStackLimit().opt);
+ }
+ }*/
+
+ //PHASE 3: Return lines
+
+ /*
+ // get a break point from the list
+ lbp = (LineBreakPosition) breakpoints.get(iReturnedLBP ++);
+ if (iReturnedLBP == breakpoints.size()) {
+ setFinished(true);
+ }
+
+ BreakPoss curLineBP = new BreakPoss(lbp);
+ curLineBP.setFlag(BreakPoss.ISLAST, isFinished());
+ curLineBP.setStackingSize(new MinOptMax(lbp.lineHeight));
+ return curLineBP;
+ */
+ }
+
+ /**
+ * Phase 1 of Knuth algorithm: Collect all inline Knuth elements before determining line breaks.
+ * @param context the LayoutContext
+ * @param availIPD available IPD for line (should be removed!)
+ */
+ private void collectInlineKnuthElements(LayoutContext context, MinOptMax availIPD) {
+ LayoutContext inlineLC = new LayoutContext(context);
+
+ InlineLevelLayoutManager curLM;
+ LinkedList returnedList = null;
+ iLineWidth = context.getStackLimit().opt;
+
+ // convert all the text in a sequence of paragraphs made
+ // of KnuthBox, KnuthGlue and KnuthPenalty objects
+ boolean bPrevWasKnuthBox = false;
+
+ StringBuffer trace = new StringBuffer("LineLM:");
+
+ Paragraph lastPar = null;
+
+ while ((curLM = (InlineLevelLayoutManager) getChildLM()) != null) {
+ returnedList = curLM.getNextKnuthElements(inlineLC, effectiveAlignment);
+ if (returnedList == null) {
+ // curLM returned null; this can happen
+ // if it has nothing more to layout,
+ // so just iterate once more to see
+ // if there are other children
+ continue;
+ }
+ if (returnedList.size() == 0) {
+ continue;
+ }
+
+ if (lastPar != null) {
+ KnuthSequence firstSeq = (KnuthSequence) returnedList.getFirst();
+
+ // finish last paragraph before a new block sequence
+ if (!firstSeq.isInlineSequence()) {
+ lastPar.endParagraph();
+ ElementListObserver.observe(lastPar, "line", null);
+ lastPar = null;
+ if (log.isTraceEnabled()) {
+ trace.append(" ]");
+ }
+ bPrevWasKnuthBox = false;
+ }
+
+ // does the first element of the first paragraph add to an existing word?
+ if (lastPar != null) {
+ KnuthElement thisElement;
+ thisElement = (KnuthElement) firstSeq.get(0);
+ if (thisElement.isBox() && !thisElement.isAuxiliary()
+ && bPrevWasKnuthBox) {
+ lastPar.addALetterSpace();
+ }
+ }
+ }
+
+ // loop over the KnuthSequences (and single KnuthElements) in returnedList
+ ListIterator iter = returnedList.listIterator();
+ while (iter.hasNext()) {
+ KnuthSequence sequence = (KnuthSequence) iter.next();
+ // the sequence contains inline Knuth elements
+ if (sequence.isInlineSequence()) {
+ // look at the last element
+ ListElement lastElement;
+ lastElement = sequence.getLast();
+ if (lastElement == null) {
+ throw new NullPointerException(
+ "Sequence was empty! lastElement is null");
+ }
+ bPrevWasKnuthBox = lastElement.isBox() && ((KnuthElement) lastElement).getW() != 0;
+
+ // if last paragraph is open, add the new elements to the paragraph
+ // else this is the last paragraph
+ if (lastPar == null) {
+ lastPar = new Paragraph(this,
+ textAlignment, textAlignmentLast,
+ textIndent.getValue(this),
+ lastLineEndIndent.getValue(this));
+ lastPar.startParagraph(availIPD.opt);
+ if (log.isTraceEnabled()) {
+ trace.append(" [");
+ }
+ } else {
+ if (log.isTraceEnabled()) {
+ trace.append(" +");
+ }
+ }
+ lastPar.addAll(sequence);
+ if (log.isTraceEnabled()) {
+ trace.append(" I");
+ }
+
+ // finish last paragraph if it was closed with a linefeed
+ if (lastElement.isPenalty()
+ && ((KnuthPenalty) lastElement).getP()
+ == -KnuthPenalty.INFINITE) {
+ // a penalty item whose value is -inf
+ // represents a preserved linefeed,
+ // which forces a line break
+ lastPar.removeLast();
+ if (!lastPar.containsBox()) {
+ //only a forced linefeed on this line
+ //-> compensate with a zero width box
+ lastPar.add(new KnuthInlineBox(0, null, null, false));
+ }
+ lastPar.endParagraph();
+ ElementListObserver.observe(lastPar, "line", null);
+ lastPar = null;
+ if (log.isTraceEnabled()) {
+ trace.append(" ]");
+ }
+ bPrevWasKnuthBox = false;
+ }
+ } else { // the sequence is a block sequence
+ // the positions will be wrapped with this LM in postProcessLineBreaks
+ knuthParagraphs.add(sequence);
+ if (log.isTraceEnabled()) {
+ trace.append(" B");
+ }
+ }
+ } // end of loop over returnedList
+ }
+ if (lastPar != null) {
+ lastPar.endParagraph();
+ ElementListObserver.observe(lastPar, "line", fobj.getId());
+ if (log.isTraceEnabled()) {
+ trace.append(" ]");
+ }
+ }
+ log.trace(trace);
+ }
+
+ /**
+ * Find a set of breaking points.
+ * This method is called only once by getNextBreakPoss, and it
+ * subsequently calls the other findBreakingPoints() method with
+ * different parameters, until a set of breaking points is found.
+ *
+ * @param par the list of elements that must be parted
+ * into lines
+ * @param lineWidth the desired length ot the lines
+ */
+ /*
+ private void findBreakingPoints(Paragraph par, int lineWidth) {
+ // maximum adjustment ratio permitted
+ float maxAdjustment = 1;
+
+ // first try
+ if (!findBreakingPoints(par, lineWidth, maxAdjustment, false)) {
+ // the first try failed, now try something different
+ log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment);
+ if (hyphenationProperties.hyphenate == Constants.EN_TRUE) {
+ // consider every hyphenation point as a legal break
+ findHyphenationPoints(par);
+ } else {
+ // try with a higher threshold
+ maxAdjustment = 5;
+ }
+
+ if (!findBreakingPoints(par, lineWidth, maxAdjustment, false)) {
+ // the second try failed too, try with a huge threshold;
+ // if this fails too, use a different algorithm
+ log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment
+ + (hyphenationProperties.hyphenate == Constants.EN_TRUE ? " and hyphenation" : ""));
+ maxAdjustment = 20;
+ if (!findBreakingPoints(par, lineWidth, maxAdjustment, true)) {
+ log.debug("No set of breaking points found, using first-fit algorithm");
+ }
+ }
+ }
+ }
+
+ private boolean findBreakingPoints(Paragraph par, int lineWidth,
+ double threshold, boolean force) {
+ KnuthParagraph knuthPara = new KnuthParagraph(par);
+ int lines = knuthPara.findBreakPoints(lineWidth, threshold, force);
+ if (lines == 0) {
+ return false;
+ }
+
+ for (int i = lines-1; i >= 0; i--) {
+ int line = i+1;
+ if (log.isTraceEnabled()) {
+ log.trace("Making line from " + knuthPara.getStart(i) + " to " +
+ knuthPara.getEnd(i));
+ }
+ // compute indent and adjustment ratio, according to
+ // the value of text-align and text-align-last
+
+ int difference = knuthPara.getDifference(i);
+ if (line == lines) {
+ difference += par.lineFillerWidth;
+ }
+ int textAlign = (line < lines)
+ ? textAlignment : textAlignmentLast;
+ int indent = (textAlign == EN_CENTER)
+ ? difference / 2
+ : (textAlign == EN_END) ? difference : 0;
+ indent += (line == 1 && knuthParagraphs.indexOf(par) == 0)
+ ? textIndent.getValue(this) : 0;
+ double ratio = (textAlign == EN_JUSTIFY)
+ ? knuthPara.getAdjustRatio(i) : 0;
+
+ int start = knuthPara.getStart(i);
+ int end = knuthPara.getEnd(i);
+ makeLineBreakPosition(par, start, end, 0, ratio, indent);
+ }
+ return true;
+ }
+
+ private void makeLineBreakPosition(Paragraph par,
+ int firstElementIndex, int lastElementIndex,
+ int insertIndex, double ratio, int indent) {
+ // line height calculation
+
+ int halfLeading = (lineHeight - lead - follow) / 2;
+ // height above the main baseline
+ int lineLead = lead + halfLeading;
+ // maximum size of top and bottom alignment
+ int lineFollow = follow + halfLeading;
+
+ ListIterator inlineIterator
+ = par.listIterator(firstElementIndex);
+ for (int j = firstElementIndex;
+ j <= lastElementIndex;
+ j++) {
+ KnuthElement element = (KnuthElement) inlineIterator.next();
+ if (element.isBox()) {
+ KnuthInlineBox box = (KnuthInlineBox)element;
+ if (box.getLead() > lineLead) {
+ lineLead = box.getLead();
+ }
+ if (box.getTotal() > lineFollow) {
+ lineFollow = box.getTotal();
+ }
+ if (box.getMiddle() > lineLead + middleShift) {
+ lineLead += box.getMiddle()
+ - lineLead - middleShift;
+ }
+ if (box.getMiddle() > middlefollow - middleShift) {
+ middlefollow += box.getMiddle()
+ - middlefollow + middleShift;
+ }
+ }
+ }
+
+ if (lineFollow - lineLead > middlefollow) {
+ middlefollow = lineFollow - lineLead;
+ }
+
+ breakpoints.add(insertIndex,
+ new LineBreakPosition(this,
+ knuthParagraphs.indexOf(par),
+ lastElementIndex ,
+ ratio, 0, indent,
+ lineLead + middlefollow,
+ lineLead));
+ }*/
+
+
+ /**
+ * Phase 2 of Knuth algorithm: find optimal break points.
+ * @param alignment alignment in BP direction of the paragraph
+ * @param context the layout context
+ * @return a list of Knuth elements representing broken lines
+ */
+ private LinkedList createLineBreaks(int alignment, LayoutContext context) {
+
+ // find the optimal line breaking points for each paragraph
+ ListIterator paragraphsIterator
+ = knuthParagraphs.listIterator(knuthParagraphs.size());
+ lineLayoutsList = new ArrayList(knuthParagraphs.size());
+ LineLayoutPossibilities llPoss;
+ while (paragraphsIterator.hasPrevious()) {
+ KnuthSequence seq = (KnuthSequence) paragraphsIterator.previous();
+ if (!seq.isInlineSequence()) {
+ // This set of line layout possibilities does not matter;
+ // we only need an entry in lineLayoutsList.
+ llPoss = new LineLayoutPossibilities();
+ } else {
+ llPoss = findOptimalBreakingPoints(alignment, (Paragraph) seq);
+ }
+ lineLayoutsList.add(0, llPoss);
+ }
+
+ setFinished(true);
+
+ //Post-process the line breaks found
+ return postProcessLineBreaks(alignment, context);
+ }
+
+ /**
+ * Fint the optimal linebreaks for a paragraph
+ * @param alignment alignment of the paragraph
+ * @param currPar the Paragraph for which the linebreaks are found
+ * @return the line layout possibilities for the paragraph
+ */
+ private LineLayoutPossibilities findOptimalBreakingPoints(int alignment, Paragraph currPar) {
+ // use the member lineLayouts, which is read by LineBreakingAlgorithm.updateData1 and 2
+ lineLayouts = new LineLayoutPossibilities();
+ double maxAdjustment = 1;
+ int iBPcount = 0;
+ LineBreakingAlgorithm alg = new LineBreakingAlgorithm(alignment,
+ textAlignment, textAlignmentLast,
+ textIndent.getValue(this), currPar.lineFiller.opt,
+ lineHeight.getValue(this), lead, follow,
+ (knuthParagraphs.indexOf(currPar) == 0),
+ hyphenationLadderCount.getEnum() == EN_NO_LIMIT
+ ? 0 : hyphenationLadderCount.getValue(),
+ this);
+
+ if (hyphenationProperties.hyphenate == EN_TRUE
+ && fobj.getWrapOption() != EN_NO_WRAP) {
+ findHyphenationPoints(currPar);
+ }
+
+ // first try
+ int allowedBreaks;
+ if (wrapOption == EN_NO_WRAP) {
+ allowedBreaks = BreakingAlgorithm.ONLY_FORCED_BREAKS;
+ } else {
+ allowedBreaks = BreakingAlgorithm.NO_FLAGGED_PENALTIES;
+ }
+ alg.setConstantLineWidth(iLineWidth);
+ iBPcount = alg.findBreakingPoints(currPar,
+ maxAdjustment, false, allowedBreaks);
+ if (iBPcount == 0 || alignment == EN_JUSTIFY) {
+ // if the first try found a set of breaking points, save them
+ if (iBPcount > 0) {
+ alg.resetAlgorithm();
+ lineLayouts.savePossibilities(false);
+ } else {
+ // the first try failed
+ log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment);
+ }
+
+ // now try something different
+ log.debug("Hyphenation possible? " + (hyphenationProperties.hyphenate == EN_TRUE));
+ if (hyphenationProperties.hyphenate == EN_TRUE
+ && !(allowedBreaks == BreakingAlgorithm.ONLY_FORCED_BREAKS)) {
+ // consider every hyphenation point as a legal break
+ allowedBreaks = BreakingAlgorithm.ALL_BREAKS;
+ } else {
+ // try with a higher threshold
+ maxAdjustment = 5;
+ }
+
+ if ((iBPcount
+ = alg.findBreakingPoints(currPar,
+ maxAdjustment, false, allowedBreaks)) == 0) {
+ // the second try failed too, try with a huge threshold
+ // and force the algorithm to find
+ // a set of breaking points
+ log.debug("No set of breaking points found with maxAdjustment = "
+ + maxAdjustment
+ + (hyphenationProperties.hyphenate == EN_TRUE
+ ? " and hyphenation" : ""));
+ maxAdjustment = 20;
+ iBPcount
+ = alg.findBreakingPoints(currPar,
+ maxAdjustment, true, allowedBreaks);
+ }
+
+ // use non-hyphenated breaks, when possible
+ lineLayouts.restorePossibilities();
+
+ /* extension (not in the XSL FO recommendation): if vertical alignment
+ is justify and the paragraph has only one layout, try using
+ shorter or longer lines */
+ //TODO This code snippet is disabled. Reenable?
+ if (false && alignment == EN_JUSTIFY && textAlignment == EN_JUSTIFY) {
+ //log.debug("LLM.getNextKnuthElements> layouts with more lines? " + lineLayouts.canUseMoreLines());
+ //log.debug(" layouts with fewer lines? " + lineLayouts.canUseLessLines());
+ if (!lineLayouts.canUseMoreLines()) {
+ alg.resetAlgorithm();
+ lineLayouts.savePossibilities(true);
+ // try with shorter lines
+ int savedLineWidth = iLineWidth;
+ iLineWidth = (int) (iLineWidth * 0.95);
+ iBPcount = alg.findBreakingPoints(currPar,
+ maxAdjustment, true, allowedBreaks);
+ // use normal lines, when possible
+ lineLayouts.restorePossibilities();
+ iLineWidth = savedLineWidth;
+ }
+ if (!lineLayouts.canUseLessLines()) {
+ alg.resetAlgorithm();
+ lineLayouts.savePossibilities(true);
+ // try with longer lines
+ int savedLineWidth = iLineWidth;
+ iLineWidth = (int) (iLineWidth * 1.05);
+ alg.setConstantLineWidth(iLineWidth);
+ iBPcount = alg.findBreakingPoints(currPar,
+ maxAdjustment, true, allowedBreaks);
+ // use normal lines, when possible
+ lineLayouts.restorePossibilities();
+ iLineWidth = savedLineWidth;
+ }
+ //log.debug("LLM.getNextKnuthElements> now, layouts with more lines? " + lineLayouts.canUseMoreLines());
+ //log.debug(" now, layouts with fewer lines? " + lineLayouts.canUseLessLines());
+ }
+ }
+ return lineLayouts;
+ }
+
+ /**
+ * Creates the element list in BP direction for the broken lines.
+ * @param alignment the currently applicable vertical alignment
+ * @param context the layout context
+ * @return the newly built element list
+ */
+ private LinkedList postProcessLineBreaks(int alignment, LayoutContext context) {
+
+ LinkedList returnList = new LinkedList();
+
+ for (int p = 0; p < knuthParagraphs.size(); p++) {
+ // null penalty between paragraphs
+ if (p > 0 && !((BlockLevelLayoutManager) parentLM).mustKeepTogether()) {
+ returnList.add(new BreakElement(
+ new Position(this), 0, context));
+ //returnList.add(new KnuthPenalty(0, 0, false, new Position(this), false));
+ }
+
+ LineLayoutPossibilities llPoss;
+ llPoss = (LineLayoutPossibilities) lineLayoutsList.get(p);
+ KnuthSequence seq = (KnuthSequence) knuthParagraphs.get(p);
+
+ if (!seq.isInlineSequence()) {
+ LinkedList targetList = new LinkedList();
+ ListIterator listIter = seq.listIterator();
+ while (listIter.hasNext()) {
+ ListElement tempElement;
+ tempElement = (ListElement) listIter.next();
+ if (tempElement.getLayoutManager() != this) {
+ tempElement.setPosition(notifyPos(new NonLeafPosition(this,
+ tempElement.getPosition())));
+ }
+ targetList.add(tempElement);
+ }
+ returnList.addAll(targetList);
+ } else if (seq.isInlineSequence() && alignment == EN_JUSTIFY) {
+ /* justified vertical alignment (not in the XSL FO recommendation):
+ create a multi-layout sequence whose elements will contain
+ a conventional Position */
+ Position returnPosition = new LeafPosition(this, p);
+ createElements(returnList, llPoss, returnPosition);
+ } else {
+ /* "normal" vertical alignment: create a sequence whose boxes
+ represent effective lines, and contain LineBreakPositions */
+ Position returnPosition = new LeafPosition(this, p);
+ int startIndex = 0;
+ for (int i = 0;
+ i < llPoss.getChosenLineCount();
+ i++) {
+ if (!((BlockLevelLayoutManager) parentLM).mustKeepTogether()
+ && i >= fobj.getOrphans()
+ && i <= llPoss.getChosenLineCount() - fobj.getWidows()
+ && returnList.size() > 0) {
+ // null penalty allowing a page break between lines
+ returnList.add(new BreakElement(
+ returnPosition, 0, context));
+ //returnList.add(new KnuthPenalty(0, 0, false, returnPosition, false));
+ }
+ int endIndex
+ = ((LineBreakPosition) llPoss.getChosenPosition(i)).getLeafPos();
+ // create a list of the FootnoteBodyLM handling footnotes
+ // whose citations are in this line
+ LinkedList footnoteList = new LinkedList();
+ ListIterator elementIterator = seq.listIterator(startIndex);
+ while (elementIterator.nextIndex() <= endIndex) {
+ KnuthElement element = (KnuthElement) elementIterator.next();
+ if (element instanceof KnuthInlineBox
+ && ((KnuthInlineBox) element).isAnchor()) {
+ footnoteList.add(((KnuthInlineBox) element).getFootnoteBodyLM());
+ } else if (element instanceof KnuthBlockBox) {
+ footnoteList.addAll(((KnuthBlockBox) element).getFootnoteBodyLMs());
+ }
+ }
+ startIndex = endIndex + 1;
+ LineBreakPosition lbp
+ = (LineBreakPosition) llPoss.getChosenPosition(i);
+ returnList.add(new KnuthBlockBox
+ (lbp.lineHeight + lbp.spaceBefore + lbp.spaceAfter,
+ footnoteList, lbp, false));
+ /* // add stretch and shrink to the returnlist
+ if (!seq.isInlineSequence()
+ && lbp.availableStretch != 0 || lbp.availableShrink != 0) {
+ returnList.add(new KnuthPenalty(0, -KnuthElement.INFINITE,
+ false, new Position(this), false));
+ returnList.add(new KnuthGlue(0, lbp.availableStretch, lbp.availableShrink,
+ new Position(this), false));
+ }
+ */
+ }
+ }
+ }
+
+ return returnList;
+ }
+
+
+ private void createElements(List list, LineLayoutPossibilities llPoss,
+ Position elementPosition) {
+ /* number of normal, inner lines */
+ int nInnerLines = 0;
+ /* number of lines that can be used in order to fill more space */
+ int nOptionalLines = 0;
+ /* number of lines that can be used in order to fill more space
+ only if the paragraph is not parted */
+ int nConditionalOptionalLines = 0;
+ /* number of lines that can be omitted in order to fill less space */
+ int nEliminableLines = 0;
+ /* number of lines that can be omitted in order to fill less space
+ only if the paragraph is not parted */
+ int nConditionalEliminableLines = 0;
+ /* number of the first unbreakable lines */
+ int nFirstLines = fobj.getOrphans();
+ /* number of the last unbreakable lines */
+ int nLastLines = fobj.getWidows();
+ /* sub-sequence used to separate the elements representing different lines */
+ List breaker = new LinkedList();
+
+ /* comment out the next lines in order to test particular situations */
+ if (fobj.getOrphans() + fobj.getWidows() <= llPoss.getMinLineCount()) {
+ nInnerLines = llPoss.getMinLineCount()
+ - (fobj.getOrphans() + fobj.getWidows());
+ nOptionalLines = llPoss.getMaxLineCount()
+ - llPoss.getOptLineCount();
+ nEliminableLines = llPoss.getOptLineCount()
+ - llPoss.getMinLineCount();
+ } else if (fobj.getOrphans() + fobj.getWidows() <= llPoss.getOptLineCount()) {
+ nOptionalLines = llPoss.getMaxLineCount()
+ - llPoss.getOptLineCount();
+ nEliminableLines = llPoss.getOptLineCount()
+ - (fobj.getOrphans() + fobj.getWidows());
+ nConditionalEliminableLines = (fobj.getOrphans() + fobj.getWidows())
+ - llPoss.getMinLineCount();
+ } else if (fobj.getOrphans() + fobj.getWidows() <= llPoss.getMaxLineCount()) {
+ nOptionalLines = llPoss.getMaxLineCount()
+ - (fobj.getOrphans() + fobj.getWidows());
+ nConditionalOptionalLines = (fobj.getOrphans() + fobj.getWidows())
+ - llPoss.getOptLineCount();
+ nConditionalEliminableLines = llPoss.getOptLineCount()
+ - llPoss.getMinLineCount();
+ nFirstLines -= nConditionalOptionalLines;
+ } else {
+ nConditionalOptionalLines = llPoss.getMaxLineCount()
+ - llPoss.getOptLineCount();
+ nConditionalEliminableLines = llPoss.getOptLineCount()
+ - llPoss.getMinLineCount();
+ nFirstLines = llPoss.getOptLineCount();
+ nLastLines = 0;
+ }
+ /* comment out the previous lines in order to test particular situations */
+
+ /* use these lines to test particular situations
+ nInnerLines = 0;
+ nOptionalLines = 1;
+ nConditionalOptionalLines = 2;
+ nEliminableLines = 0;
+ nConditionalEliminableLines = 0;
+ nFirstLines = 1;
+ nLastLines = 3;
+ */
+
+ if (nLastLines != 0
+ && (nConditionalOptionalLines > 0 || nConditionalEliminableLines > 0)) {
+ breaker.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
+ breaker.add(new KnuthGlue(0, -nConditionalOptionalLines * constantLineHeight,
+ -nConditionalEliminableLines * constantLineHeight,
+ LINE_NUMBER_ADJUSTMENT, elementPosition, false));
+ breaker.add(new KnuthPenalty(nConditionalOptionalLines * constantLineHeight,
+ 0, false, elementPosition, false));
+ breaker.add(new KnuthGlue(0, nConditionalOptionalLines * constantLineHeight,
+ nConditionalEliminableLines * constantLineHeight,
+ LINE_NUMBER_ADJUSTMENT, elementPosition, false));
+ } else if (nLastLines != 0) {
+ breaker.add(new KnuthPenalty(0, 0, false, elementPosition, false));
+ }
+
+ //log.debug("first=" + nFirstLines + " inner=" + nInnerLines
+ // + " optional=" + nOptionalLines + " eliminable=" + nEliminableLines
+ // + " last=" + nLastLines
+ // + " (condOpt=" + nConditionalOptionalLines + " condEl=" + nConditionalEliminableLines + ")");
+
+ // creation of the elements:
+ // first group of lines
+ list.add(new KnuthBox(nFirstLines * constantLineHeight, elementPosition,
+ (nLastLines == 0
+ && nConditionalOptionalLines == 0
+ && nConditionalEliminableLines == 0 ? true : false)));
+ if (nConditionalOptionalLines > 0
+ || nConditionalEliminableLines > 0) {
+ list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
+ list.add(new KnuthGlue(0, nConditionalOptionalLines * constantLineHeight,
+ nConditionalEliminableLines * constantLineHeight,
+ LINE_NUMBER_ADJUSTMENT, elementPosition, false));
+ list.add(new KnuthBox(0, elementPosition,
+ (nLastLines == 0 ? true : false)));
+ }
+
+ // optional lines
+ for (int i = 0; i < nOptionalLines; i++) {
+ list.addAll(breaker);
+ list.add(new KnuthBox(0, elementPosition, false));
+ list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
+ list.add(new KnuthGlue(0, 1 * constantLineHeight, 0,
+ LINE_NUMBER_ADJUSTMENT, elementPosition, false));
+ list.add(new KnuthBox(0, elementPosition, false));
+ }
+
+ // eliminable lines
+ for (int i = 0; i < nEliminableLines; i++) {
+ list.addAll(breaker);
+ list.add(new KnuthBox(1 * constantLineHeight, elementPosition, false));
+ list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
+ list.add(new KnuthGlue(0, 0, 1 * constantLineHeight,
+ LINE_NUMBER_ADJUSTMENT, elementPosition, false));
+ list.add(new KnuthBox(0, elementPosition, false));
+ }
+
+ // inner lines
+ for (int i = 0; i < nInnerLines; i++) {
+ list.addAll(breaker);
+ list.add(new KnuthBox(1 * constantLineHeight, elementPosition, false));
+ }
+
+ // last group of lines
+ if (nLastLines > 0) {
+ list.addAll(breaker);
+ list.add(new KnuthBox(nLastLines * constantLineHeight,
+ elementPosition, true));
+ }
+ }
+
+ /**
+ * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepTogether
+ */
+ public boolean mustKeepTogether() {
+ return ((BlockLevelLayoutManager) getParent()).mustKeepTogether();
+ }
+
+ /**
+ * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithPrevious
+ */
+ public boolean mustKeepWithPrevious() {
+ return false;
+ }
+
+ /**
+ * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithNext
+ */
+ public boolean mustKeepWithNext() {
+ return false;
+ }
+
+ /**
+ * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#negotiateBPDAdjustment(int, org.apache.fop.layoutmgr.KnuthElement)
+ */
+ public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) {
+ LeafPosition pos = (LeafPosition)lastElement.getPosition();
+ int totalAdj = adj;
+ //if (lastElement.isPenalty()) {
+ // totalAdj += lastElement.getW();
+ //}
+ //int lineNumberDifference = (int)((double) totalAdj / constantLineHeight);
+ int lineNumberDifference = (int) Math.round((double) totalAdj / constantLineHeight
+ + (adj > 0 ? - 0.4 : 0.4));
+ //log.debug(" LLM> variazione calcolata = " + ((double) totalAdj / constantLineHeight) + " variazione applicata = " + lineNumberDifference);
+ LineLayoutPossibilities llPoss;
+ llPoss = (LineLayoutPossibilities) lineLayoutsList.get(pos.getLeafPos());
+ lineNumberDifference = llPoss.applyLineCountAdjustment(lineNumberDifference);
+ return lineNumberDifference * constantLineHeight;
+ }
+
+ /**
+ * @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#discardSpace(KnuthGlue)
+ */
+ public void discardSpace(KnuthGlue spaceGlue) {
+ }
+
+ /**
+ * @see org.apache.fop.layoutmgr.LayoutManager#getChangedKnuthElements(List, int)
+ */
+ public LinkedList getChangedKnuthElements(List oldList, int alignment) {
+ LinkedList returnList = new LinkedList();
+ for (int p = 0; p < knuthParagraphs.size(); p++) {
+ LineLayoutPossibilities llPoss;
+ llPoss = (LineLayoutPossibilities)lineLayoutsList.get(p);
+ //log.debug("demerits of the chosen layout: " + llPoss.getChosenDemerits());
+ for (int i = 0; i < llPoss.getChosenLineCount(); i++) {
+ if (!((BlockLevelLayoutManager) parentLM).mustKeepTogether()
+ && i >= fobj.getOrphans()
+ && i <= llPoss.getChosenLineCount() - fobj.getWidows()) {
+ // null penalty allowing a page break between lines
+ returnList.add(new KnuthPenalty(0, 0, false, new Position(this), false));
+ }
+ LineBreakPosition lbp = (LineBreakPosition) llPoss.getChosenPosition(i);
+ //log.debug("LLM.getChangedKnuthElements> lineWidth= " + lbp.lineWidth + " difference= " + lbp.difference);
+ //log.debug(" shrink= " + lbp.availableShrink + " stretch= " + lbp.availableStretch);
+
+ //log.debug("linewidth= " + lbp.lineWidth + " difference= " + lbp.difference + " indent= " + lbp.startIndent);
+ MinOptMax contentIPD;
+ if (alignment == EN_JUSTIFY) {
+ contentIPD = new MinOptMax(
+ lbp.lineWidth - lbp.difference - lbp.availableShrink,
+ lbp.lineWidth - lbp.difference,
+ lbp.lineWidth - lbp.difference + lbp.availableStretch);
+ } else if (alignment == EN_CENTER) {
+ contentIPD = new MinOptMax(lbp.lineWidth - 2 * lbp.startIndent);
+ } else if (alignment == EN_END) {
+ contentIPD = new MinOptMax(lbp.lineWidth - lbp.startIndent);
+ } else {
+ contentIPD = new MinOptMax(lbp.lineWidth - lbp.difference + lbp.startIndent);
+ }
+ returnList.add(new KnuthBlockBox(lbp.lineHeight,
+ contentIPD,
+ (lbp.ipdAdjust != 0
+ ? lbp.lineWidth - lbp.difference : 0),
+ lbp, false));
+ }
+ }
+ return returnList;
+ }
+
+ /**
+ * find hyphenation points for every word int the current paragraph
+ * @ param currPar the paragraph whose words will be hyphenated
+ */
+ private void findHyphenationPoints(Paragraph currPar) {
+ // hyphenate every word
+ ListIterator currParIterator
+ = currPar.listIterator(currPar.ignoreAtStart);
+ // list of TLM involved in hyphenation
+ LinkedList updateList = new LinkedList();
+ KnuthElement firstElement = null;
+ KnuthElement nextElement = null;
+ // current InlineLevelLayoutManager
+ InlineLevelLayoutManager currLM = null;
+ // number of KnuthBox elements containing word fragments
+ int boxCount;
+ // number of auxiliary KnuthElements between KnuthBoxes
+ int auxCount;
+ StringBuffer sbChars = null;
+
+ // find all hyphenation points
+ while (currParIterator.hasNext()) {
+ firstElement = (KnuthElement) currParIterator.next();
+ //
+ if (firstElement.getLayoutManager() != currLM) {
+ currLM = (InlineLevelLayoutManager) firstElement.getLayoutManager();
+ if (currLM != null) {
+ updateList.add(new Update(currLM, currParIterator.previousIndex()));
+ } else {
+ break;
+ }
+ } else if (currLM == null) {
+ break;
+ }
+ //TODO Something's not right here. See block_hyphenation_linefeed_preserve.xml
+
+ // collect word fragments, ignoring auxiliary elements;
+ // each word fragment was created by a different TextLM
+ if (firstElement.isBox() && !firstElement.isAuxiliary()) {
+ boxCount = 1;
+ auxCount = 0;
+ sbChars = new StringBuffer();
+ currLM.getWordChars(sbChars, firstElement.getPosition());
+ // look if next elements are boxes too
+ while (currParIterator.hasNext()) {
+ nextElement = (KnuthElement) currParIterator.next();
+ if (nextElement.isBox() && !nextElement.isAuxiliary()) {
+ // a non-auxiliary KnuthBox: append word chars
+ if (currLM != nextElement.getLayoutManager()) {
+ currLM = (InlineLevelLayoutManager) nextElement.getLayoutManager();
+ updateList.add(new Update(currLM, currParIterator.previousIndex()));
+ }
+ // append text to recreate the whole word
+ boxCount++;
+ currLM.getWordChars(sbChars, nextElement.getPosition());
+ } else if (!nextElement.isAuxiliary()) {
+ // a non-auxiliary non-box KnuthElement: stop
+ // go back to the last box or auxiliary element
+ currParIterator.previous();
+ break;
+ } else {
+ if (currLM != nextElement.getLayoutManager()) {
+ currLM = (InlineLevelLayoutManager) nextElement.getLayoutManager();
+ updateList.add(new Update(currLM, currParIterator.previousIndex()));
+ }
+ // an auxiliary KnuthElement: simply ignore it
+ auxCount++;
+ }
+ }
+ log.trace(" Word to hyphenate: " + sbChars.toString());
+ // find hyphenation points
+ HyphContext hc = getHyphenContext(sbChars);
+ // ask each LM to hyphenate its word fragment
+ if (hc != null) {
+ KnuthElement element = null;
+ for (int i = 0; i < (boxCount + auxCount); i++) {
+ currParIterator.previous();
+ }
+ for (int i = 0; i < (boxCount + auxCount); i++) {
+ element = (KnuthElement) currParIterator.next();
+ if (element.isBox() && !element.isAuxiliary()) {
+ ((InlineLevelLayoutManager)
+ element.getLayoutManager()).hyphenate(element.getPosition(), hc);
+ } else {
+ // nothing to do, element is an auxiliary KnuthElement
+ }
+ }
+ }
+ }
+ }
+
+ // create iterator for the updateList
+ ListIterator updateListIterator = updateList.listIterator();
+ Update currUpdate = null;
+ //int iPreservedElements = 0;
+ int iAddedElements = 0;
+ //int iRemovedElements = 0;
+
+ while (updateListIterator.hasNext()) {
+ // ask the LMs to apply the changes and return
+ // the new KnuthElements to replace the old ones
+ currUpdate = (Update) updateListIterator.next();
+ int fromIndex = currUpdate.iFirstIndex;
+ int toIndex;
+ if (updateListIterator.hasNext()) {
+ Update nextUpdate = (Update) updateListIterator.next();
+ toIndex = nextUpdate.iFirstIndex;
+ updateListIterator.previous();
+ } else {
+ // maybe this is not always correct!
+ toIndex = currPar.size() - currPar.ignoreAtEnd
+ - iAddedElements;
+ }
+
+ // applyChanges() returns true if the LM modifies its data,
+ // so it must return new KnuthElements to replace the old ones
+ if (((InlineLevelLayoutManager) currUpdate.inlineLM)
+ .applyChanges(currPar.subList(fromIndex + iAddedElements,
+ toIndex + iAddedElements))) {
+ // insert the new KnuthElements
+ LinkedList newElements = null;
+ newElements
+ = currUpdate.inlineLM.getChangedKnuthElements
+ (currPar.subList(fromIndex + iAddedElements,
+ toIndex + iAddedElements),
+ /*flaggedPenalty,*/ effectiveAlignment);
+ // remove the old elements
+ currPar.subList(fromIndex + iAddedElements,
+ toIndex + iAddedElements).clear();
+ // insert the new elements
+ currPar.addAll(fromIndex + iAddedElements, newElements);
+ iAddedElements += newElements.size() - (toIndex - fromIndex);
+ }
+ }
+ updateListIterator = null;
+ updateList.clear();
+ }
+
+ /**
+ * Line area is always considered to act as a fence.
+ * @param isNotFirst ignored
+ * @return always true
+ */
+ protected boolean hasLeadingFence(boolean isNotFirst) {
+ return true;
+ }
+
+ /**
+ * Line area is always considered to act as a fence.
+ * @param isNotLast ignored
+ * @return always true
+ */
+ protected boolean hasTrailingFence(boolean isNotLast) {
+ return true;
+ }
+
+ private HyphContext getHyphenContext(StringBuffer sbChars) {
+ // Find all hyphenation points in this word
+ // (get in an array of offsets)
+ // hyphenationProperties are from the block level?.
+ // Note that according to the spec,
+ // they also "apply to" fo:character.
+ // I don't know what that means, since
+ // if we change language in the middle of a "word",
+ // the effect would seem quite strange!
+ // Or perhaps in that case, we say that it's several words.
+ // We probably should bring the hyphenation props up from the actual
+ // TextLM which generate the hyphenation buffer,
+ // since these properties inherit and could be specified
+ // on an inline or wrapper below the block level.
+ Hyphenation hyph
+ = Hyphenator.hyphenate(hyphenationProperties.language,
+ hyphenationProperties.country,
+ getFObj().getUserAgent().getFactory().getHyphenationTreeResolver(),
+ sbChars.toString(),
+ hyphenationProperties.hyphenationRemainCharacterCount,
+ hyphenationProperties.hyphenationPushCharacterCount);
+ // They hyph structure contains the information we need
+ // Now start from prev: reset to that position, ask that LM to get
+ // a Position for the first hyphenation offset. If the offset isn't in
+ // its characters, it returns null,
+ // but must tell how many chars it had.
+ // Keep looking at currentBP using next hyphenation point until the
+ // returned size is greater than the available size
+ // or no more hyphenation points remain. Choose the best break.
+ if (hyph != null) {
+ return new HyphContext(hyph.getHyphenationPoints());
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Reset the positions to the given position.
+ *
+ * @param resetPos the position to reset to
+ */
+ public void resetPosition(Position resetPos) {
+ if (resetPos == null) {
+ setFinished(false);
+ iReturnedLBP = 0;
+ } else {
+ if (isFinished()) {
+ // if isFinished is true, iReturned LBP == breakpoints.size()
+ // and breakpoints.get(iReturnedLBP) would generate
+ // an IndexOutOfBoundException
+ setFinished(false);
+ iReturnedLBP--;
+ }
+ // It is not clear that the member lineLayouts has the correct value;
+ // because the method is not called, this cannot be checked.
+ while ((LineBreakPosition) lineLayouts.getChosenPosition(iReturnedLBP)
+ != (LineBreakPosition) resetPos) {
+ iReturnedLBP--;
+ }
+ iReturnedLBP++;
+ }
+ }
+
+ /**
+ * Add the areas with the break points.
+ *
+ * @param parentIter the iterator of break positions
+ * @param context the context for adding areas
+ */
+ public void addAreas(PositionIterator parentIter,
+ LayoutContext context) {
+ while (parentIter.hasNext()) {
+ Position pos = (Position) parentIter.next();
+ boolean isLastPosition = !parentIter.hasNext();
+ if (pos instanceof LineBreakPosition) {
+ addInlineArea(context, pos, isLastPosition);
+ } else if ((pos instanceof NonLeafPosition) && pos.generatesAreas()) {
+ addBlockArea(context, pos, isLastPosition);
+ } else {
+ /*
+ * pos was the Position inside a penalty item, nothing to do;
+ * or Pos does not generate an area,
+ * i.e. it stand for spaces, borders and padding.
+ */
+ }
+ }
+ setCurrentArea(null); // ?? necessary
+ }
+
+ /**
+ * Add a line with inline content
+ * @param context the context for adding areas
+ * @param pos the position for which the line is generated
+ * @param isLastPosition true if this is the last position of this LM
+ */
+ private void addInlineArea(LayoutContext context, Position pos, boolean isLastPosition) {
+ ListIterator seqIterator = null;
+ KnuthElement tempElement = null;
+ // the TLM which created the last KnuthElement in this line
+ LayoutManager lastLM = null;
+
+ LineBreakPosition lbp = (LineBreakPosition) pos;
+ int iCurrParIndex;
+ iCurrParIndex = lbp.iParIndex;
+ KnuthSequence seq = (KnuthSequence) knuthParagraphs.get(iCurrParIndex);
+ int iStartElement = lbp.iStartIndex;
+ int iEndElement = lbp.getLeafPos();
+
+ LineArea lineArea
+ = new LineArea((lbp.getLeafPos() < seq.size() - 1
+ ? textAlignment : textAlignmentLast),
+ lbp.difference, lbp.availableStretch, lbp.availableShrink);
+ if (lbp.startIndent != 0) {
+ lineArea.addTrait(Trait.START_INDENT, new Integer(lbp.startIndent));
+ }
+ lineArea.setBPD(lbp.lineHeight);
+ lineArea.setIPD(lbp.lineWidth);
+ lineArea.addTrait(Trait.SPACE_BEFORE, new Integer(lbp.spaceBefore));
+ lineArea.addTrait(Trait.SPACE_AFTER, new Integer(lbp.spaceAfter));
+ alignmentContext.resizeLine(lbp.lineHeight, lbp.baseline);
+
+ if (seq instanceof Paragraph) {
+ Paragraph currPar = (Paragraph) seq;
+ // ignore the first elements added by the LineLayoutManager
+ iStartElement += (iStartElement == 0) ? currPar.ignoreAtStart : 0;
+
+ // if this is the last line area that for this paragraph,
+ // ignore the last elements added by the LineLayoutManager and
+ // subtract the last-line-end-indent from the area ipd
+ if (iEndElement == (currPar.size() - 1)) {
+ iEndElement -= currPar.ignoreAtEnd;
+ lineArea.setIPD(lineArea.getIPD() - lastLineEndIndent.getValue(this));
+ }
+ }
+
+ // Remove trailing spaces if allowed so
+ if (whiteSpaceTreament == EN_IGNORE_IF_SURROUNDING_LINEFEED
+ || whiteSpaceTreament == EN_IGNORE
+ || whiteSpaceTreament == EN_IGNORE_IF_BEFORE_LINEFEED) {
+ // ignore the last element in the line if it is a KnuthGlue object
+ seqIterator = seq.listIterator(iEndElement);
+ tempElement = (KnuthElement) seqIterator.next();
+ if (tempElement.isGlue()) {
+ iEndElement--;
+ // this returns the same KnuthElement
+ seqIterator.previous();
+ if (seqIterator.hasPrevious()) {
+ tempElement = (KnuthElement) seqIterator.previous();
+ } else {
+ tempElement = null;
+ }
+ }
+ if (tempElement != null) {
+ lastLM = tempElement.getLayoutManager();
+ }
+ }
+
+ // Remove leading spaces if allowed so
+ if (whiteSpaceTreament == EN_IGNORE_IF_SURROUNDING_LINEFEED
+ || whiteSpaceTreament == EN_IGNORE
+ || whiteSpaceTreament == EN_IGNORE_IF_AFTER_LINEFEED) {
+ // ignore KnuthGlue and KnuthPenalty objects
+ // at the beginning of the line
+ seqIterator = seq.listIterator(iStartElement);
+ tempElement = (KnuthElement) seqIterator.next();
+ while (!tempElement.isBox() && seqIterator.hasNext()) {
+ tempElement = (KnuthElement) seqIterator.next();
+ iStartElement++;
+ }
+ }
+ // Add the inline areas to lineArea
+ PositionIterator inlinePosIter
+ = new KnuthPossPosIter(seq, iStartElement, iEndElement + 1);
+
+ iStartElement = lbp.getLeafPos() + 1;
+ if (iStartElement == seq.size()) {
+ // advance to next paragraph
+ iStartElement = 0;
+ }
+
+ LayoutContext lc = new LayoutContext(0);
+ lc.setAlignmentContext(alignmentContext);
+ lc.setSpaceAdjust(lbp.dAdjust);
+ lc.setIPDAdjust(lbp.ipdAdjust);
+ lc.setLeadingSpace(new SpaceSpecifier(true));
+ lc.setTrailingSpace(new SpaceSpecifier(false));
+ lc.setFlags(LayoutContext.RESOLVE_LEADING_SPACE, true);
+
+ /*
+ * extension (not in the XSL FO recommendation): if the left and right margins
+ * have been optimized, recompute indents and / or adjust ratio, according
+ * to the paragraph horizontal alignment
+ */
+ if (false && textAlignment == EN_JUSTIFY) {
+ // re-compute space adjust ratio
+ int updatedDifference = context.getStackLimit().opt
+ - lbp.lineWidth + lbp.difference;
+ double updatedRatio = 0.0;
+ if (updatedDifference > 0) {
+ updatedRatio = (float) updatedDifference / lbp.availableStretch;
+ } else if (updatedDifference < 0) {
+ updatedRatio = (float) updatedDifference / lbp.availableShrink;
+ }
+ lc.setIPDAdjust(updatedRatio);
+ //log.debug("LLM.addAreas> old difference = " + lbp.difference + " new difference = " + updatedDifference);
+ //log.debug(" old ratio = " + lbp.ipdAdjust + " new ratio = " + updatedRatio);
+ } else if (false && textAlignment == EN_CENTER) {
+ // re-compute indent
+ int updatedIndent = lbp.startIndent
+ + (context.getStackLimit().opt - lbp.lineWidth) / 2;
+ lineArea.addTrait(Trait.START_INDENT, new Integer(updatedIndent));
+ } else if (false && textAlignment == EN_END) {
+ // re-compute indent
+ int updatedIndent = lbp.startIndent
+ + (context.getStackLimit().opt - lbp.lineWidth);
+ lineArea.addTrait(Trait.START_INDENT, new Integer(updatedIndent));
+ }
+
+ setCurrentArea(lineArea);
+ setChildContext(lc);
+ LayoutManager childLM;
+ while ((childLM = inlinePosIter.getNextChildLM()) != null) {
+ lc.setFlags(LayoutContext.LAST_AREA, (childLM == lastLM));
+ childLM.addAreas(inlinePosIter, lc);
+ lc.setLeadingSpace(lc.getTrailingSpace());
+ lc.setTrailingSpace(new SpaceSpecifier(false));
+ }
+
+ // when can this be null?
+ // if display-align is distribute, add space after
+ if (context.getSpaceAfter() > 0
+ && (!context.isLastArea() || !isLastPosition)) {
+ lineArea.setBPD(lineArea.getBPD() + context.getSpaceAfter());
+ }
+ lineArea.finalise();
+ parentLM.addChildArea(lineArea);
+ }
+
+ /**
+ * Add a line with block content
+ * @param context the context for adding areas
+ * @param pos the position for which the line is generated
+ * @param isLastPosition true if this is the last position of this LM
+ */
+ private void addBlockArea(LayoutContext context, Position pos, boolean isLastPosition) {
+ /* Nested block-level content;
+ * go down the LM stack again;
+ * "unwrap" the positions and put the child positions in a new list.
+ * The positionList must contain one area-generating position,
+ * which creates one line area.
+ */
+ List positionList = new ArrayList(1);
+ Position innerPosition;
+ innerPosition = ((NonLeafPosition) pos).getPosition();
+ positionList.add(innerPosition);
+
+ // do we have the last LM?
+ LayoutManager lastLM = null;
+ if (isLastPosition) {
+ lastLM = innerPosition.getLM();
+ }
+
+ LineArea lineArea = new LineArea();
+ setCurrentArea(lineArea);
+ LayoutContext lc = new LayoutContext(0);
+ lc.setAlignmentContext(alignmentContext);
+ setChildContext(lc);
+
+ PositionIterator childPosIter = new StackingIter(positionList.listIterator());
+ LayoutContext blocklc = new LayoutContext(0);
+ blocklc.setLeadingSpace(new SpaceSpecifier(true));
+ blocklc.setTrailingSpace(new SpaceSpecifier(false));
+ blocklc.setFlags(LayoutContext.RESOLVE_LEADING_SPACE, true);
+ LayoutManager childLM;
+ while ((childLM = childPosIter.getNextChildLM()) != null) {
+ // set last area flag
+ blocklc.setFlags(LayoutContext.LAST_AREA,
+ (context.isLastArea() && childLM == lastLM));
+ blocklc.setStackLimit(context.getStackLimit());
+ // Add the line areas to Area
+ childLM.addAreas(childPosIter, blocklc);
+ blocklc.setLeadingSpace(blocklc.getTrailingSpace());
+ blocklc.setTrailingSpace(new SpaceSpecifier(false));
+ }
+ lineArea.updateExtentsFromChildren();
+ parentLM.addChildArea(lineArea);
+ }
+
+ /**
+ * @see org.apache.fop.layoutmgr.LayoutManager#addChildArea(Area)
+ */
+ public void addChildArea(Area childArea) {
+ // Make sure childArea is inline area
+ if (childArea instanceof InlineArea) {
+ Area parent = getCurrentArea();
+ if (getContext().resolveLeadingSpace()) {
+ addSpace(parent,
+ getContext().getLeadingSpace().resolve(false),
+ getContext().getSpaceAdjust());
+ }
+ parent.addChildArea(childArea);
+ }
+ }
+
+ // --------- Property Resolution related functions --------- //
+
+ /**
+ * @see org.apache.fop.layoutmgr.LayoutManager#getGeneratesBlockArea
+ */
+ public boolean getGeneratesBlockArea() {
+ return true;
+ }
+
+ /**
+ * @see org.apache.fop.layoutmgr.LayoutManager#getGeneratesLineArea
+ */
+ public boolean getGeneratesLineArea() {
+ return true;
+ }
+}
+]]>
+ </fo:block>
+ </fo:flow>
+ </fo:page-sequence>
+ </fo:root>
+ </fo>
+ <checks>
+ <!-- Not a proper test !! -->
+ <eval expected="0 0 360000 360000" xpath="/areaTree/pageSequence/pageViewport/@bounds" desc="page size"/>
+ </checks>
+</testcase>