]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Test case for Bug 39414
authorManuel Mall <manuel@apache.org>
Thu, 27 Apr 2006 10:21:04 +0000 (10:21 +0000)
committerManuel Mall <manuel@apache.org>
Thu, 27 Apr 2006 10:21:04 +0000 (10:21 +0000)
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@397511 13f79535-47bb-0310-9956-ffa450edef68

test/layoutengine/disabled-testcases.xml
test/layoutengine/standard-testcases/block_bug39414.xml [new file with mode: 0755]

index 03fd4e64c1c22522c033b73c0d41dab1901b9866..3c14b5cb9189f4bd73547e03d1f19474c477a9df 100644 (file)
     <name>linefeed-treatment</name>
     <file>block_linefeed-treatment.xml</file>
     <description>Preserved linefeeds in a fo:character are not handled
-    correctly. Zero width space characters are not handled
     correctly.</description>
   </testcase>
   <testcase>
     <name>white-space-treatment</name>
     <file>block_white-space-treatment_3.xml</file>
-    <description>White space handling incorrectly stops at inline
+    <description>White space handling incorrectly stops at fo:inline
       boundaries when it comes to formatter generated line breaks.</description>
   </testcase>
   <testcase>
     block-level element because its layout manager is written to handle only 
     inline content.</description>
   </testcase>
+  <testcase>
+    <name>Bugzilla #39414: block containing long text</name>
+    <file>block_bug39414.xml</file>
+    <description>Long text cause an IndexOutOfBounds exception</description>
+    <reference>http://issues.apache.org/bugzilla/show_bug.cgi?id=39414</reference>
+  </testcase>
+  <testcase>
 </disabled-testcases>
\ No newline at end of file
diff --git a/test/layoutengine/standard-testcases/block_bug39414.xml b/test/layoutengine/standard-testcases/block_bug39414.xml
new file mode 100755 (executable)
index 0000000..bf6a223
--- /dev/null
@@ -0,0 +1,1872 @@
+<?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>