Package org.apache.fop.layoutmgr.inline

Source Code of org.apache.fop.layoutmgr.inline.LineLayoutManager

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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: LineLayoutManager.java 956271 2010-06-19 19:10:10Z adelmelle $ */

package org.apache.fop.layoutmgr.inline;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.fop.area.Area;
import org.apache.fop.area.LineArea;
import org.apache.fop.area.Trait;
import org.apache.fop.area.inline.InlineArea;
import org.apache.fop.datatypes.Length;
import org.apache.fop.datatypes.Numeric;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.flow.Block;
import org.apache.fop.fo.properties.CommonHyphenation;
import org.apache.fop.fo.properties.KeepProperty;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.fonts.FontTriplet;
import org.apache.fop.hyphenation.Hyphenation;
import org.apache.fop.hyphenation.Hyphenator;
import org.apache.fop.layoutmgr.Adjustment;
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.Keep;
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.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 {

    /**
     * logging instance
     */
    private static Log log = LogFactory.getLog(LineLayoutManager.class);

    private Block fobj;
    private boolean isFirstInBlock;

    /** {@inheritDoc} */
    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 parIndex; // index of the Paragraph this Position refers to
        private int startIndex; //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 startIndex, int breakIndex,
                          int shrink, int stretch, int diff,
                          double ipdA, double adjust, int ind,
                          int lh, int lw, int sb, int sa, int bl) {
            super(lm, breakIndex);
            availableShrink = shrink;
            availableStretch = stretch;
            difference = diff;
            parIndex = index;
            this.startIndex = startIndex;
            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;

    private List knuthParagraphs;

    private LineLayoutPossibilities lineLayouts;
    private LineLayoutPossibilities[] lineLayoutsList;
    private int ipd = 0;
    /**
     * When layout must be re-started due to a change of IPD, there is no need
     * to perform hyphenation on the remaining Knuth sequence once again.
     */
    private boolean hyphenationPerformed;

    /**
     * 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 firstIndex;

        public Update(InlineLevelLayoutManager lm, int index) {
            inlineLM = lm;
            firstIndex = 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;
        // the LM which created the paragraph
        private LineLayoutManager layoutManager;

        Paragraph(LineLayoutManager llm, int alignment, int alignmentLast,
                         int indent, int endIndent) {
            super();
            layoutManager = llm;
            textAlignment = alignment;
            textAlignmentLast = alignmentLast;
            textIndent = indent;
            lastLineEndIndent = endIndent;
        }

        public void startSequence() {
            // set the minimum amount of empty space at the end of the
            // last line
            if (textAlignment == EN_CENTER) {
                lineFiller = MinOptMax.getInstance(lastLineEndIndent);
            } else {
                lineFiller = MinOptMax.getInstance(lastLineEndIndent, lastLineEndIndent,
                        layoutManager.ipd);
            }

            // 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.getOpt(), -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.getStretch(),
                            lineFiller.getShrink(), null, false));
                    this.add(new KnuthPenalty(lineFiller.getOpt(), -KnuthElement.INFINITE,
                                              false, null, false));
                    ignoreAtEnd = 3;
                } else {
                    // add only the element representing the forced break
                    this.add(new KnuthPenalty(lineFiller.getOpt(), -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 lineHeight;
        private int lead;
        private int follow;
        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;
            lineHeight = lh;
            lead = ld;
            follow = fl;
            thisLLM = llm;
            activePossibility = -1;
        }

        public void updateData1(int lineCount, double demerits) {
            lineLayouts.addPossibility(lineCount, demerits);
            if (log.isTraceEnabled()) {
                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 && indentFirstPart && 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 (log.isWarnEnabled()) {
                int lack = difference + bestActiveNode.availableShrink;
                if (lack < 0) {
                    InlineLevelEventProducer eventProducer
                        = InlineLevelEventProducer.Provider.get(
                            getFObj().getUserAgent().getEventBroadcaster());
                    eventProducer.lineOverflows(this, bestActiveNode.line,
                            -lack, getFObj().getLocator());
                }
            }

            //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.getShrink()),
                   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 == ipd);

            // 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) {
                                if (fobj.getLineHeightShiftAdjustment() == EN_CONSIDER_SHIFTS
                                    || ac.getBaselineShiftValue() == 0) {
                                    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, ipd, 0, 0, 0);
            } else {
                return new LineBreakPosition(thisLLM,
                                             knuthParagraphs.indexOf(par),
                                             firstElementIndex, lastElementIndex,
                                             availableShrink, availableStretch,
                                             difference, ratio, 0, indent,
                                             lineLead + lineFollow,
                                             ipd, spaceBefore, spaceAfter,
                                             lineLead);
            }
        }

        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;
    }

    /** {@inheritDoc} */
    public List getNextKnuthElements(LayoutContext context, int alignment) {
        FontInfo fi = fobj.getFOEventHandler().getFontInfo();
        FontTriplet[] fontkeys = fobj.getCommonFont().getFontState(fi);
        Font fs = fi.getFontInstance(fontkeys[0], fobj.getCommonFont().fontSize.getValue(this));
        alignmentContext = new AlignmentContext(fs, lineHeight.getValue(this),
                context.getWritingMode());
        context.setAlignmentContext(alignmentContext);
        ipd = context.getRefIPD();

        //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
            collectInlineKnuthElements(context);
        } 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);
    }

    public List getNextKnuthElements(LayoutContext context, int alignment,
            LeafPosition restartPosition) {
        log.trace("Restarting line breaking from index " + restartPosition.getIndex());
        int parIndex = restartPosition.getLeafPos();
        Paragraph paragraph = (Paragraph) knuthParagraphs.get(parIndex);
        for (int i = 0; i <= restartPosition.getIndex(); i++) {
            paragraph.remove(0);
        }
        Iterator iter = paragraph.iterator();
        while (iter.hasNext() && !((KnuthElement) iter.next()).isBox()) {
            iter.remove();
        }
        if (!iter.hasNext()) {
            knuthParagraphs.remove(parIndex);
        }

        // return finished when there's no content
        if (knuthParagraphs.size() == 0) {
            setFinished(true);
            return null;
        }

        ipd = context.getRefIPD();
        //PHASE 2: Create line breaks
        return createLineBreaks(context.getBPAlignment(), context);
    }

    /**
     * Phase 1 of Knuth algorithm: Collect all inline Knuth elements before determining line breaks.
     * @param context the LayoutContext
     */
    private void collectInlineKnuthElements(LayoutContext context) {
        LayoutContext inlineLC = new LayoutContext(context);

        // convert all the text in a sequence of paragraphs made
        // of KnuthBox, KnuthGlue and KnuthPenalty objects
        boolean previousIsBox = false;

        StringBuffer trace = new StringBuffer("LineLM:");

        Paragraph lastPar = null;

        InlineLevelLayoutManager curLM;
        while ((curLM = (InlineLevelLayoutManager) getChildLM()) != null) {
            List inlineElements = curLM.getNextKnuthElements(inlineLC, effectiveAlignment);
            if (inlineElements == null || inlineElements.size() == 0) {
                /* curLM.getNextKnuthElements() returned null or an empty list;
                 * this can happen if there is nothing more to layout,
                 * so just iterate once more to see if there are other children */
                continue;
            }

            if (lastPar != null) {
                KnuthSequence firstSeq = (KnuthSequence) inlineElements.get(0);

                // 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(" ]");
                    }
                    previousIsBox = 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()
                            && previousIsBox) {
                        lastPar.addALetterSpace();
                    }
                }
            }

            // loop over the KnuthSequences (and single KnuthElements) in returnedList
            ListIterator iter = inlineElements.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 = sequence.getLast();
                    assert lastElement != null;
                    previousIsBox = lastElement.isBox()
                            && !((KnuthElement) lastElement).isAuxiliary()
                            && ((KnuthElement) lastElement).getWidth() != 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.startSequence();
                        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).getPenalty() == -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 an auxiliary glue
                            lastPar.add(new KnuthGlue(ipd, 0, ipd, null, true));
                        }
                        lastPar.endParagraph();
                        ElementListObserver.observe(lastPar, "line", null);
                        lastPar = null;
                        if (log.isTraceEnabled()) {
                            trace.append(" ]");
                        }
                        previousIsBox = 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);
    }

    /**
     * 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 List createLineBreaks(int alignment, LayoutContext context) {
        // find the optimal line breaking points for each paragraph
        Iterator paragraphsIterator = knuthParagraphs.iterator();
        lineLayoutsList = new LineLayoutPossibilities[knuthParagraphs.size()];
        LineLayoutPossibilities llPoss;
        for (int i = 0; paragraphsIterator.hasNext(); i++) {
            KnuthSequence seq = (KnuthSequence) paragraphsIterator.next();
            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[i] = 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.getOpt(),
                                        lineHeight.getValue(this), lead, follow,
                                        (knuthParagraphs.indexOf(currPar) == 0),
                                        hyphenationLadderCount.getEnum() == EN_NO_LIMIT
                                        ? 0 : hyphenationLadderCount.getValue(),
                                        this);

        if (hyphenationProperties.hyphenate.getEnum() == EN_TRUE
                && fobj.getWrapOption() != EN_NO_WRAP && !hyphenationPerformed) {
            hyphenationPerformed = true;
            findHyphenationPoints(currPar);
        }

        // first try
        int allowedBreaks;
        if (wrapOption == EN_NO_WRAP) {
            allowedBreaks = BreakingAlgorithm.ONLY_FORCED_BREAKS;
        } else {
            allowedBreaks = BreakingAlgorithm.NO_FLAGGED_PENALTIES;
        }
        alg.setConstantLineWidth(ipd);
        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.getEnum() == EN_TRUE));
            if (hyphenationProperties.hyphenate.getEnum() == 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
                if (log.isDebugEnabled()) {
                    log.debug("No set of breaking points found with maxAdjustment = "
                              + maxAdjustment
                              + (hyphenationProperties.hyphenate.getEnum() == 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 = ipd;
                    ipd = (int) (ipd * 0.95);
                    iBPcount = alg.findBreakingPoints(currPar,
                            maxAdjustment, true, allowedBreaks);
                    // use normal lines, when possible
                    lineLayouts.restorePossibilities();
                    ipd = savedLineWidth;
                }
                if (!lineLayouts.canUseLessLines()) {
                    alg.resetAlgorithm();
                    lineLayouts.savePossibilities(true);
                    // try with longer lines
                    int savedLineWidth = ipd;
                    ipd = (int) (ipd * 1.05);
                    alg.setConstantLineWidth(ipd);
                    iBPcount = alg.findBreakingPoints(currPar,
                            maxAdjustment, true, allowedBreaks);
                    // use normal lines, when possible
                    lineLayouts.restorePossibilities();
                    ipd = 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 List postProcessLineBreaks(int alignment, LayoutContext context) {

        List returnList = new LinkedList();

        int endIndex = -1;
        for (int p = 0; p < knuthParagraphs.size(); p++) {
            // penalty between paragraphs
            if (p > 0) {
                Keep keep = getKeepTogether();
                returnList.add(new BreakElement(
                            new Position(this),
                            keep.getPenalty(),
                            keep.getContext(),
                            context));
            }

            LineLayoutPossibilities llPoss = lineLayoutsList[p];
            KnuthSequence seq = (KnuthSequence) knuthParagraphs.get(p);

            if (!seq.isInlineSequence()) {
                List 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 */
                int startIndex = 0;
                for (int i = 0;
                        i < llPoss.getChosenLineCount();
                        i++) {
                    if (returnList.size() > 0
                            && i > 0 //if i==0 break generated above already
                            && i >= fobj.getOrphans()
                            && i <= llPoss.getChosenLineCount() - fobj.getWidows()) {
                        // penalty allowing a page break between lines
                        Keep keep = getKeepTogether();
                        returnList.add(new BreakElement(
                                new LeafPosition(this, p, endIndex),
                                    keep.getPenalty(),
                                    keep.getContext(),
                                    context));
                    }
                    endIndex = ((LineBreakPosition) llPoss.getChosenPosition(i)).getLeafPos();
                    // create a list of the FootnoteBodyLM handling footnotes
                    // whose citations are in this line
                    List footnoteList = new LinkedList();
                    ListIterator elementIterator = seq.listIterator(startIndex);
                    while (elementIterator.nextIndex() <= endIndex) {
                        KnuthElement element = (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 innerLines = 0;
        /* number of lines that can be used in order to fill more space */
        int optionalLines = 0;
        /* number of lines that can be used in order to fill more space
           only if the paragraph is not parted */
        int conditionalOptionalLines = 0;
        /* number of lines that can be omitted in order to fill less space */
        int eliminableLines = 0;
        /* number of lines that can be omitted in order to fill less space
           only if the paragraph is not parted */
        int conditionalEliminableLines = 0;
        /* number of the first unbreakable lines */
        int firstLines = fobj.getOrphans();
        /* number of the last unbreakable lines */
        int lastLines = 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()) {
            innerLines = llPoss.getMinLineCount()
                          - (fobj.getOrphans() + fobj.getWidows());
            optionalLines = llPoss.getMaxLineCount()
                             - llPoss.getOptLineCount();
            eliminableLines = llPoss.getOptLineCount()
                               - llPoss.getMinLineCount();
        } else if (fobj.getOrphans() + fobj.getWidows() <= llPoss.getOptLineCount()) {
            optionalLines = llPoss.getMaxLineCount()
                             - llPoss.getOptLineCount();
            eliminableLines = llPoss.getOptLineCount()
                               - (fobj.getOrphans() + fobj.getWidows());
            conditionalEliminableLines = (fobj.getOrphans() + fobj.getWidows())
                                          - llPoss.getMinLineCount();
        } else if (fobj.getOrphans() + fobj.getWidows() <= llPoss.getMaxLineCount()) {
            optionalLines = llPoss.getMaxLineCount()
                             - (fobj.getOrphans() + fobj.getWidows());
            conditionalOptionalLines = (fobj.getOrphans() + fobj.getWidows())
                                        - llPoss.getOptLineCount();
            conditionalEliminableLines = llPoss.getOptLineCount()
                                          - llPoss.getMinLineCount();
            firstLines -= conditionalOptionalLines;
        } else {
            conditionalOptionalLines = llPoss.getMaxLineCount()
                                        - llPoss.getOptLineCount();
            conditionalEliminableLines = llPoss.getOptLineCount()
                                          - llPoss.getMinLineCount();
            firstLines = llPoss.getOptLineCount();
            lastLines = 0;
        }
        /* comment out the previous lines in order to test particular situations */

        /* use these lines to test particular situations
        innerLines = 0;
        optionalLines = 1;
        conditionalOptionalLines = 2;
        eliminableLines = 0;
        conditionalEliminableLines = 0;
        firstLines = 1;
        lastLines = 3;
        */

        if (lastLines != 0
            && (conditionalOptionalLines > 0 || conditionalEliminableLines > 0)) {
            breaker.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
            breaker.add(new KnuthGlue(0, -conditionalOptionalLines * constantLineHeight,
                                        -conditionalEliminableLines * constantLineHeight,
                                        Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
            breaker.add(new KnuthPenalty(conditionalOptionalLines * constantLineHeight,
                                           0, false, elementPosition, false));
            breaker.add(new KnuthGlue(0, conditionalOptionalLines * constantLineHeight,
                                        conditionalEliminableLines * constantLineHeight,
                                        Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
        } else if (lastLines != 0) {
            breaker.add(new KnuthPenalty(0, 0, false, elementPosition, false));
        }

        //log.debug("first=" + firstLines + " inner=" + innerLines
        //                   + " optional=" + optionalLines + " eliminable=" + eliminableLines
        //                   + " last=" + lastLines
        //                   + " (condOpt=" + conditionalOptionalLines + " condEl=" + conditionalEliminableLines + ")");

        // creation of the elements:
        // first group of lines
        list.add(new KnuthBox(firstLines * constantLineHeight, elementPosition,
                              (lastLines == 0
                               && conditionalOptionalLines == 0
                               && conditionalEliminableLines == 0)));
        if (conditionalOptionalLines > 0
            || conditionalEliminableLines > 0) {
            list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
            list.add(new KnuthGlue(0, conditionalOptionalLines * constantLineHeight,
                                   conditionalEliminableLines * constantLineHeight,
                                   Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
            list.add(new KnuthBox(0, elementPosition, (lastLines == 0)));
        }

        // optional lines
        for (int i = 0; i < optionalLines; 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, constantLineHeight, 0,
                                   Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
            list.add(new KnuthBox(0, elementPosition, false));
        }

        // eliminable lines
        for (int i = 0; i < eliminableLines; i++) {
            list.addAll(breaker);
            list.add(new KnuthBox(constantLineHeight, elementPosition, false));
            list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
            list.add(new KnuthGlue(0, 0, constantLineHeight,
                                   Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
            list.add(new KnuthBox(0, elementPosition, false));
        }

        // inner lines
        for (int i = 0; i < innerLines; i++) {
            list.addAll(breaker);
            list.add(new KnuthBox(constantLineHeight, elementPosition, false));
        }

        // last group of lines
        if (lastLines > 0) {
            list.addAll(breaker);
            list.add(new KnuthBox(lastLines * constantLineHeight,
                                  elementPosition, true));
        }
    }

    /** {@inheritDoc} */
    public boolean mustKeepTogether() {
        return ((BlockLevelLayoutManager) getParent()).mustKeepTogether();
    }

    /** {@inheritDoc} */
    public KeepProperty getKeepTogetherProperty() {
        return ((BlockLevelLayoutManager) getParent()).getKeepTogetherProperty();
    }

    /** {@inheritDoc} */
    public KeepProperty getKeepWithPreviousProperty() {
        return ((BlockLevelLayoutManager) getParent()).getKeepWithPreviousProperty();
    }

    /** {@inheritDoc} */
    public KeepProperty getKeepWithNextProperty() {
        return ((BlockLevelLayoutManager) getParent()).getKeepWithNextProperty();
    }

    /** {@inheritDoc} */
    public Keep getKeepTogether() {
        return ((BlockLevelLayoutManager) getParent()).getKeepTogether();
    }

    /** {@inheritDoc} */
    public boolean mustKeepWithPrevious() {
        return !getKeepWithPrevious().isAuto();
    }

    /** {@inheritDoc} */
    public boolean mustKeepWithNext() {
        return !getKeepWithNext().isAuto();
    }

    /** {@inheritDoc} */
    public Keep getKeepWithNext() {
        return Keep.KEEP_AUTO;
    }

    /** {@inheritDoc} */
    public Keep getKeepWithPrevious() {
        return Keep.KEEP_AUTO;
    }

    /** {@inheritDoc} */
    public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) {
        LeafPosition pos = (LeafPosition)lastElement.getPosition();
        //if (lastElement.isPenalty()) {
        //    totalAdj += lastElement.getWidth();
        //}
        //int lineNumberDifference = (int)((double) totalAdj / constantLineHeight);
        int lineNumberDifference = (int) Math.round((double) adj / constantLineHeight
                                                    + (adj > 0 ? - 0.4 : 0.4));
        //log.debug("   LLM> variazione calcolata = " + ((double) totalAdj / constantLineHeight) + " variazione applicata = " + lineNumberDifference);
        LineLayoutPossibilities llPoss;
        llPoss = lineLayoutsList[pos.getLeafPos()];
        lineNumberDifference = llPoss.applyLineCountAdjustment(lineNumberDifference);
        return lineNumberDifference * constantLineHeight;
    }

    /** {@inheritDoc} */
    public void discardSpace(KnuthGlue spaceGlue) {
    }

    /** {@inheritDoc} */
    public List getChangedKnuthElements(List oldList, int alignment) {
        List returnList = new LinkedList();
        for (int p = 0; p < knuthParagraphs.size(); p++) {
            LineLayoutPossibilities llPoss = lineLayoutsList[p];
            //log.debug("demerits of the chosen layout: " + llPoss.getChosenDemerits());
            for (int i = 0; i < llPoss.getChosenLineCount(); i++) {
                if (!((BlockLevelLayoutManager) parentLayoutManager).mustKeepTogether()
                    && i >= fobj.getOrphans()
                    && i <= llPoss.getChosenLineCount() - fobj.getWidows()) {
                    // 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 = MinOptMax.getInstance(
                        lbp.lineWidth - lbp.difference - lbp.availableShrink,
                        lbp.lineWidth - lbp.difference,
                        lbp.lineWidth - lbp.difference + lbp.availableStretch);
                } else if (alignment == EN_CENTER) {
                    contentIPD = MinOptMax.getInstance(lbp.lineWidth - 2 * lbp.startIndent);
                } else if (alignment == EN_END) {
                    contentIPD = MinOptMax.getInstance(lbp.lineWidth - lbp.startIndent);
                } else {
                    contentIPD = MinOptMax.getInstance(lbp.lineWidth - lbp.difference + lbp.startIndent);
                }
                returnList.add(new KnuthBlockBox(lbp.lineHeight, contentIPD, (lbp.ipdAdjust != 0
                        ? lbp.lineWidth - lbp.difference : 0),
                                                 lbp, false));
            }
        }
        return returnList;
    }

    /**
     * Find hyphenation points for every word in 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
        List updateList = new LinkedList();
        KnuthElement firstElement, nextElement;
        // current InlineLevelLayoutManager
        InlineLevelLayoutManager currLM = null;
        // number of KnuthBox elements containing word fragments
        int boxCount;
        // number of auxiliary KnuthElements between KnuthBoxes
        int auxCount;
        StringBuffer sbChars;

        // 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
            //for more info: see also https://issues.apache.org/bugzilla/show_bug.cgi?id=38264

            // 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();
                sbChars.append(currLM.getWordChars(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++;
                        sbChars.append(currLM.getWordChars(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++;
                    }
                }
                if (log.isTraceEnabled()) {
                    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
                        }
                    }
                }
            }
        }
        processUpdates(currPar, updateList);
    }

    private void processUpdates(Paragraph par, List updateList) {
        // create iterator for the updateList
        ListIterator updateListIterator = updateList.listIterator();
        Update currUpdate;
        int elementsAdded = 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.firstIndex;
            int toIndex;
            if (updateListIterator.hasNext()) {
                Update nextUpdate = (Update) updateListIterator.next();
                toIndex = nextUpdate.firstIndex;
                updateListIterator.previous();
            } else {
                // maybe this is not always correct!
                toIndex = par.size() - par.ignoreAtEnd
                    - elementsAdded;
            }

            // applyChanges() returns true if the LM modifies its data,
            // so it must return new KnuthElements to replace the old ones
            if (currUpdate.inlineLM
                .applyChanges(par.subList(fromIndex + elementsAdded,
                                              toIndex + elementsAdded))) {
                // insert the new KnuthElements
                List newElements = currUpdate.inlineLM.getChangedKnuthElements
                    (par.subList(fromIndex + elementsAdded,
                                     toIndex + elementsAdded),
                     /*flaggedPenalty,*/ effectiveAlignment);
                // remove the old elements
                par.subList(fromIndex + elementsAdded,
                                toIndex + elementsAdded).clear();
                // insert the new elements
                par.addAll(fromIndex + elementsAdded, newElements);
                elementsAdded += newElements.size() - (toIndex - fromIndex);
            }
        }
        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.getString(),
                               hyphenationProperties.country.getString(),
                               getFObj().getUserAgent().getFactory().getHyphenationTreeResolver(),
                               sbChars.toString(),
                               hyphenationProperties.hyphenationRemainCharacterCount.getValue(),
                               hyphenationProperties.hyphenationPushCharacterCount.getValue());
        // 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;
        }
    }

    /**
     * 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, (LineBreakPosition) 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 lbp 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, LineBreakPosition lbp,
            boolean isLastPosition) {
        // the TLM which created the last KnuthElement in this line
        LayoutManager lastLM = null;

        KnuthSequence seq = (KnuthSequence) knuthParagraphs.get(lbp.parIndex);
        int startElementIndex = lbp.startIndex;
        int endElementIndex = 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
            startElementIndex += (startElementIndex == 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 (endElementIndex == (currPar.size() - 1)) {
                endElementIndex -= 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
            ListIterator seqIterator = seq.listIterator(endElementIndex);
            KnuthElement lastElement = (KnuthElement) seqIterator.next();
            lastLM = lastElement.getLayoutManager();
            if (lastElement.isGlue()) {
                endElementIndex--;
                // this returns the same KnuthElement
                seqIterator.previous();
                if (seqIterator.hasPrevious()) {
                    lastLM = ((KnuthElement) seqIterator.previous()).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
            ListIterator seqIterator = seq.listIterator(startElementIndex);
            while (seqIterator.hasNext() && !((KnuthElement) seqIterator.next()).isBox()) {
                startElementIndex++;
            }
        }
        // Add the inline areas to lineArea
        PositionIterator inlinePosIter = new KnuthPossPosIter(seq, startElementIndex,
                endElementIndex + 1);

        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.getRefIPD()
            - 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.getRefIPD() - 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.getRefIPD() - 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));
        }

        // if display-align is distribute, add space after
        if (context.getSpaceAfter() > 0
                && (!context.isLastArea() || !isLastPosition)) {
            lineArea.setBPD(lineArea.getBPD() + context.getSpaceAfter());
        }
        lineArea.finalise();
        parentLayoutManager.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 = 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.setStackLimitBP(context.getStackLimitBP());
            // Add the line areas to Area
            childLM.addAreas(childPosIter, blocklc);
            blocklc.setLeadingSpace(blocklc.getTrailingSpace());
            blocklc.setTrailingSpace(new SpaceSpecifier(false));
        }
        lineArea.updateExtentsFromChildren();
        parentLayoutManager.addChildArea(lineArea);
    }

    /** {@inheritDoc} */
    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 --------- //

    /** {@inheritDoc} */
    public boolean getGeneratesBlockArea() {
        return true;
    }

    /** {@inheritDoc} */
    public boolean getGeneratesLineArea() {
        return true;
    }

    /** {@inheritDoc} */
    public boolean isRestartable() {
        return true;
    }

}
TOP

Related Classes of org.apache.fop.layoutmgr.inline.LineLayoutManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.