Package org.apache.fop.layoutmgr

Source Code of org.apache.fop.layoutmgr.BlockStackingLayoutManager

/*
* 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: BlockStackingLayoutManager.java 638308 2008-03-18 10:26:32Z vhennebert $ */

package org.apache.fop.layoutmgr;

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.Block;
import org.apache.fop.area.BlockParent;
import org.apache.fop.fo.FObj;
import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
import org.apache.fop.fo.properties.SpaceProperty;
import org.apache.fop.layoutmgr.inline.InlineLayoutManager;
import org.apache.fop.layoutmgr.inline.LineLayoutManager;
import org.apache.fop.traits.MinOptMax;

/**
* Base LayoutManager class for all areas which stack their child
* areas in the block-progression direction, such as Flow, Block, ListBlock.
*/
public abstract class BlockStackingLayoutManager extends AbstractLayoutManager
                                                 implements BlockLevelLayoutManager {

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

    /**
     * Reference to FO whose areas it's managing or to the traits
     * of the FO.
     */
    //protected LayoutManager curChildLM = null; AbstractLayoutManager also defines this!
    protected BlockParent parentArea = null;

    /** Value of the block-progression-unit (non-standard property) */
    protected int bpUnit = 0;
    /** space-before value adjusted for block-progression-unit handling */
    protected int adjustedSpaceBefore = 0;
    /** space-after value adjusted for block-progression-unit handling */
    protected int adjustedSpaceAfter = 0;
    /** Only used to store the original list when createUnitElements is called */
    protected LinkedList storedList = null;
    /** Indicates whether break before has been served or not */
    protected boolean breakBeforeServed = false;
    /** Indicates whether the first visible mark has been returned by this LM, yet */
    protected boolean firstVisibleMarkServed = false;
    /** Reference IPD available */
    protected int referenceIPD = 0;
    /** the effective start-indent value */
    protected int startIndent = 0;
    /** the effective end-indent value */
    protected int endIndent = 0;
    /**
     * Holds the (one-time use) fo:block space-before
     * and -after properties.  Large fo:blocks are split
     * into multiple Area. Blocks to accomodate the subsequent
     * regions (pages) they are placed on.  space-before
     * is applied at the beginning of the first
     * Block and space-after at the end of the last Block
     * used in rendering the fo:block.
     */
    protected MinOptMax foSpaceBefore = null;
    /** see foSpaceBefore */
    protected MinOptMax foSpaceAfter = null;

    private Position auxiliaryPosition;

    private int contentAreaIPD = 0;
   
    /**
     * @param node the fo this LM deals with
     */
    public BlockStackingLayoutManager(FObj node) {
        super(node);
        setGeneratesBlockArea(true);
    }

    /**
     * @return current area being filled
     */
    protected BlockParent getCurrentArea() {
        return this.parentArea;
    }


    /**
     * Set the current area being filled.
     * @param parentArea the current area to be filled
     */
    protected void setCurrentArea(BlockParent parentArea) {
        this.parentArea = parentArea;
    }

    /**
     * Add a block spacer for space before and space after a block.
     * This adds an empty Block area that acts as a block space.
     *
     * @param adjust the adjustment value
     * @param minoptmax the min/opt/max value of the spacing
     */
    public void addBlockSpacing(double adjust, MinOptMax minoptmax) {
        int sp = TraitSetter.getEffectiveSpace(adjust, minoptmax);
        if (sp != 0) {
            Block spacer = new Block();
            spacer.setBPD(sp);
            parentLM.addChildArea(spacer);
        }
    }

    /**
     * Add the childArea to the passed area.
     * Called by child LayoutManager when it has filled one of its areas.
     * The LM should already have an Area in which to put the child.
     * See if the area will fit in the current area.
     * If so, add it. Otherwise initiate breaking.
     * @param childArea the area to add: will be some block-stacked Area.
     * @param parentArea the area in which to add the childArea
     */
    protected void addChildToArea(Area childArea,
                                     BlockParent parentArea) {
        // This should be a block-level Area (Block in the generic sense)
        if (!(childArea instanceof Block)) {
            //log.error("Child not a Block in BlockStackingLM!");
        }

        parentArea.addBlock((Block) childArea);
        flush(); // hand off current area to parent
    }


    /**
     * Add the childArea to the current area.
     * Called by child LayoutManager when it has filled one of its areas.
     * The LM should already have an Area in which to put the child.
     * See if the area will fit in the current area.
     * If so, add it. Otherwise initiate breaking.
     * @param childArea the area to add: will be some block-stacked Area.
     */
    public void addChildArea(Area childArea) {
        addChildToArea(childArea, getCurrentArea());
    }

    /**
     * Force current area to be added to parent area.
     */
    protected void flush() {
        if (getCurrentArea() != null) {
            parentLM.addChildArea(getCurrentArea());
        }
    }

    /** @return a cached auxiliary Position instance used for things like spaces. */
    protected Position getAuxiliaryPosition() {
        if (this.auxiliaryPosition == null) {
            this.auxiliaryPosition = new NonLeafPosition(this, null);
        }
        return this.auxiliaryPosition;
    }
   
    /**
     * @param len length in millipoints to span with bp units
     * @return the minimum integer n such that n * bpUnit >= len
     */
    protected int neededUnits(int len) {
        return (int) Math.ceil((float)len / bpUnit);
    }

    /**
     * Determines and sets the content area IPD based on available reference area IPD, start- and
     * end-indent properties.
     * end-indent is adjusted based on overconstrained geometry rules, if necessary.
     *
     * @return the resulting content area IPD
     */
    protected int updateContentAreaIPDwithOverconstrainedAdjust() {
        int ipd = referenceIPD - (startIndent + endIndent);
        if (ipd < 0) {
            //5.3.4, XSL 1.0, Overconstrained Geometry
            log.debug("Adjusting end-indent based on overconstrained geometry rules for " + fobj);
            endIndent += ipd;
            ipd = 0;
            //TODO Should we skip layout for a block that has ipd=0?
        }
        setContentAreaIPD(ipd);
        return ipd;
    }
   
    /**
     * Sets the content area IPD by directly supplying the value.
     * end-indent is adjusted based on overconstrained geometry rules, if necessary.
     *
     * @return the resulting content area IPD
     */
    protected int updateContentAreaIPDwithOverconstrainedAdjust(int contentIPD) {
        int ipd = referenceIPD - (contentIPD + (startIndent + endIndent));
        if (ipd < 0) {
            //5.3.4, XSL 1.0, Overconstrained Geometry
            log.debug("Adjusting end-indent based on overconstrained geometry rules for " + fobj);
            endIndent += ipd;
        }
        setContentAreaIPD(contentIPD);
        return contentIPD;
    }
   
    /** {@inheritDoc} */
    public LinkedList getNextKnuthElements(LayoutContext context, int alignment) {
        //log.debug("BLM.getNextKnuthElements> keep-together = "
              // + layoutProps.keepTogether.getType());
        //log.debug(" keep-with-previous = " +
              // layoutProps.keepWithPrevious.getType());
        //log.debug(" keep-with-next = " +
              // layoutProps.keepWithNext.getType());
        BlockLevelLayoutManager curLM; // currently active LM
        BlockLevelLayoutManager prevLM = null; // previously active LM

        referenceIPD = context.getRefIPD();
       
        updateContentAreaIPDwithOverconstrainedAdjust();

        LinkedList returnedList = null;
        LinkedList contentList = new LinkedList();
        LinkedList returnList = new LinkedList();

        if (!breakBeforeServed) {
            try {
                if (addKnuthElementsForBreakBefore(returnList, context)) {
                    return returnList;
                }
            } finally {
                breakBeforeServed = true;
            }
        }

        if (!firstVisibleMarkServed) {
            addKnuthElementsForSpaceBefore(returnList, alignment);
        }
       
        addKnuthElementsForBorderPaddingBefore(returnList, !firstVisibleMarkServed);
        firstVisibleMarkServed = true;

        //Spaces, border and padding to be repeated at each break
        addPendingMarks(context);
       
        //Used to indicate a special break-after case when all content has already been generated.
        BreakElement forcedBreakAfterLast = null;
       
        while ((curLM = (BlockLevelLayoutManager) getChildLM()) != null) {
            LayoutContext childLC = new LayoutContext(0);
            childLC.copyPendingMarksFrom(context);
            if (curLM instanceof LineLayoutManager) {
                // curLM is a LineLayoutManager
                // set stackLimit for lines (stack limit is now i-p-direction, not b-p-direction!)
                childLC.setStackLimitBP(context.getStackLimitBP());
                childLC.setStackLimitIP(new MinOptMax(getContentAreaIPD()));
                childLC.setRefIPD(getContentAreaIPD());
            } else {
                // curLM is a ?
                //childLC.setStackLimit(MinOptMax.subtract(context
                //        .getStackLimit(), stackSize));
                childLC.setStackLimitBP(context.getStackLimitBP());
                childLC.setRefIPD(referenceIPD);
            }

            // get elements from curLM
            returnedList = curLM.getNextKnuthElements(childLC, alignment);
            if (contentList.size() == 0 && childLC.isKeepWithPreviousPending()) {
                context.setFlags(LayoutContext.KEEP_WITH_PREVIOUS_PENDING);
                childLC.setFlags(LayoutContext.KEEP_WITH_PREVIOUS_PENDING, false);
            }
            if (returnedList != null
                    && returnedList.size() == 1
                    && ((ListElement) returnedList.getFirst()).isForcedBreak()) {

                if (curLM.isFinished() && !hasNextChildLM()) {
                    // a descendant of this block has break-before
                    forcedBreakAfterLast = (BreakElement) returnedList.getFirst();
                    context.clearPendingMarks();
                    break;
                }

                if (contentList.size() == 0) {
                    // Empty fo:block, zero-length box makes sure the IDs and/or markers
                    // are registered and borders/padding are painted.
                    returnList.add(new KnuthBox(0, notifyPos(new Position(this)), false));
                }
                // a descendant of this block has break-before
                contentList.addAll(returnedList);
               
                /* extension: conversione di tutta la sequenza fin'ora ottenuta */
                if (bpUnit > 0) {
                    storedList = contentList;
                    contentList = createUnitElements(contentList);
                }
                /* end of extension */

                // "wrap" the Position inside each element
                // moving the elements from contentList to returnList
                returnedList = new LinkedList();
                wrapPositionElements(contentList, returnList);

                return returnList;
            } else {
                if (prevLM != null) {
                    // there is a block handled by prevLM
                    // before the one handled by curLM
                    if (mustKeepTogether()
                            || context.isKeepWithNextPending()
                            || childLC.isKeepWithPreviousPending()) {
                        // Clear keep pending flag
                        context.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING, false);
                        // add an infinite penalty to forbid a break between
                        // blocks
                        contentList.add(new BreakElement(
                                new Position(this), KnuthElement.INFINITE, context));
                    } else if (!((ListElement) contentList.getLast()).isGlue()) {
                        // add a null penalty to allow a break between blocks
                        contentList.add(new BreakElement(
                                new Position(this), 0, context));
                    } else {
                        // the last element in contentList is a glue;
                        // it is a feasible breakpoint, there is no need to add
                        // a penalty
                        log.warn("glue-type break possibility not handled properly, yet");
                        //TODO Does this happen? If yes, need to deal with border and padding
                        //at the break possibility
                    }
                }
                if (returnedList == null || returnedList.size() == 0) {
                    //Avoid NoSuchElementException below (happens with empty blocks)
                    continue;
                }
                contentList.addAll(returnedList);
                if (((ListElement) returnedList.getLast()).isForcedBreak()) {
                    // a descendant of this block has break-after
                    if (curLM.isFinished() && !hasNextChildLM()) {
                        forcedBreakAfterLast = (BreakElement)contentList.removeLast();
                        context.clearPendingMarks();
                        break;
                    }

                    /* extension: conversione di tutta la sequenza fin'ora ottenuta */
                    if (bpUnit > 0) {
                        storedList = contentList;
                        contentList = createUnitElements(contentList);
                    }
                    /* end of extension */

                    returnedList = new LinkedList();
                    wrapPositionElements(contentList, returnList);

                    return returnList;
                }
            }
            // propagate and clear
            context.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING, childLC.isKeepWithNextPending());
            childLC.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING, false);
            childLC.setFlags(LayoutContext.KEEP_WITH_PREVIOUS_PENDING, false);
            prevLM = curLM;
        }

        /* Extension: conversione di tutta la sequenza fin'ora ottenuta */
        if (bpUnit > 0) {
            storedList = contentList;
            contentList = createUnitElements(contentList);
        }
        /* end of extension */

        returnedList = new LinkedList();
        if (contentList.size() > 0) {
            wrapPositionElements(contentList, returnList);
        } else if (forcedBreakAfterLast == null) {
            // Empty fo:block, zero-length box makes sure the IDs and/or markers
            // are registered.
            returnList.add(new KnuthBox(0, notifyPos(new Position(this)), true));
        }

        addKnuthElementsForBorderPaddingAfter(returnList, true);
        addKnuthElementsForSpaceAfter(returnList, alignment);
       
        //All child content is processed. Only break-after can occur now, so...       
        context.clearPendingMarks();
        if (forcedBreakAfterLast == null) {
            addKnuthElementsForBreakAfter(returnList, context);
        }

        if (forcedBreakAfterLast != null) {
            forcedBreakAfterLast.clearPendingMarks();
            wrapPositionElement(forcedBreakAfterLast, returnList, false);
        }
       
        if (mustKeepWithNext()) {
            context.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING);
        }
        if (mustKeepWithPrevious()) {
            context.setFlags(LayoutContext.KEEP_WITH_PREVIOUS_PENDING);
        }
       
        setFinished(true);

        return returnList;
    }

    /**
     * {@inheritDoc}
     */
    public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) {
/*LF*/  //log.debug("  BLM.negotiateBPDAdjustment> " + adj);
/*LF*/  //log.debug("  lastElement e' " + (lastElement.isPenalty()
        //      ? "penalty" : (lastElement.isGlue() ? "glue" : "box" )));
/*LF*/  //log.debug("  position e' " + lastElement.getPosition().getClass().getName());
/*LF*/  //log.debug("  " + (bpUnit > 0 ? "unit" : ""));
        Position innerPosition = ((NonLeafPosition) lastElement.getPosition()).getPosition();

        if (innerPosition == null && lastElement.isGlue()) {
            // this adjustment applies to space-before or space-after of this block
            if (((KnuthGlue) lastElement).getAdjustmentClass() == SPACE_BEFORE_ADJUSTMENT) {
                // this adjustment applies to space-before
                adjustedSpaceBefore += adj;
/*LF*/          //log.debug("  BLM.negotiateBPDAdjustment> spazio prima: " + adj);
            } else {
                // this adjustment applies to space-after
                adjustedSpaceAfter += adj;
/*LF*/          //log.debug("  BLM.negotiateBPDAdjustment> spazio dopo: " + adj);
            }
            return adj;
        } else if (innerPosition instanceof MappingPosition) {
            // this block has block-progression-unit > 0: the adjustment can concern
            // - the space-before or space-after of this block,
            // - the line number of a descendant of this block
            MappingPosition mappingPos = (MappingPosition)innerPosition;
            if (lastElement.isGlue()) {
                // lastElement is a glue
/*LF*/          //log.debug("  BLM.negotiateBPDAdjustment> bpunit con glue");
                ListIterator storedListIterator = storedList.listIterator(
                        mappingPos.getFirstIndex());
                int newAdjustment = 0;
                while (storedListIterator.nextIndex() <= mappingPos.getLastIndex()) {
                    KnuthElement storedElement = (KnuthElement)storedListIterator.next();
                    if (storedElement.isGlue()) {
                        newAdjustment += ((BlockLevelLayoutManager)storedElement
                                .getLayoutManager()).negotiateBPDAdjustment(
                                        adj - newAdjustment, storedElement);
/*LF*/                  //log.debug("  BLM.negotiateBPDAdjustment> (progressivo) righe: "
                        //  + newAdjustment);
                    }
                }
                newAdjustment = (newAdjustment > 0 ? bpUnit * neededUnits(newAdjustment)
                                                   : -bpUnit * neededUnits(-newAdjustment));
                return newAdjustment;
            } else {
                // lastElement is a penalty: this means that the paragraph
                // has been split between consecutive pages:
                // this may involve a change in the number of lines
/*LF*/          //log.debug("  BLM.negotiateBPDAdjustment> bpunit con penalty");
                KnuthPenalty storedPenalty = (KnuthPenalty)
                                             storedList.get(mappingPos.getLastIndex());
                if (storedPenalty.getW() > 0) {
                    // the original penalty has width > 0
/*LF*/              //log.debug("  BLM.negotiateBPDAdjustment> chiamata passata");
                    return ((BlockLevelLayoutManager)storedPenalty.getLayoutManager())
                           .negotiateBPDAdjustment(storedPenalty.getW(),
                                   (KnuthElement)storedPenalty);
                } else {
                    // the original penalty has width = 0
                    // the adjustment involves only the spaces before and after
/*LF*/              //log.debug("  BLM.negotiateBPDAdjustment> chiamata gestita");
                    return adj;
                }
            }
        } else if (innerPosition.getLM() != this) {
            // this adjustment concerns another LM
            NonLeafPosition savedPos = (NonLeafPosition) lastElement.getPosition();
            lastElement.setPosition(innerPosition);
            int returnValue = ((BlockLevelLayoutManager)lastElement.getLayoutManager())
                    .negotiateBPDAdjustment(adj, lastElement);
            lastElement.setPosition(savedPos);
/*LF*/      //log.debug("  BLM.negotiateBPDAdjustment> righe: " + returnValue);
            return returnValue;
        } else {
            // this should never happen
            log.error("BlockLayoutManager.negotiateBPDAdjustment(): unexpected Position");
            return 0;
        }
    }

    /**
     * {@inheritDoc}
     */
    public void discardSpace(KnuthGlue spaceGlue) {
        //log.debug("  BLM.discardSpace> " + spaceGlue.getPosition().getClass().getName());
        Position innerPosition = ((NonLeafPosition) spaceGlue.getPosition()).getPosition();

        if (innerPosition == null || innerPosition.getLM() == this) {
            // if this block has block-progression-unit > 0, innerPosition can be
            // a MappingPosition
            // spaceGlue represents space before or space after of this block
            if (spaceGlue.getAdjustmentClass() == SPACE_BEFORE_ADJUSTMENT) {
                // space-before must be discarded
                adjustedSpaceBefore = 0;
                foSpaceBefore = new MinOptMax(0);
            } else {
                // space-after must be discarded
                adjustedSpaceAfter = 0;
                foSpaceAfter = new MinOptMax(0);
                //TODO Why are both cases handled in the same way?
            }
        } else {
            // this element was not created by this BlockLM
            NonLeafPosition savedPos = (NonLeafPosition)spaceGlue.getPosition();
            spaceGlue.setPosition(innerPosition);
            ((BlockLevelLayoutManager) spaceGlue.getLayoutManager()).discardSpace(spaceGlue);
            spaceGlue.setPosition(savedPos);
        }
    }

    /**
     * {@inheritDoc}
     */
    public LinkedList getChangedKnuthElements(List oldList, int alignment) {
/*LF*/  //log.debug("");
/*LF*/  //log.debug("  BLM.getChangedKnuthElements> inizio: oldList.size() = "
        //  + oldList.size());
        ListIterator oldListIterator = oldList.listIterator();
        KnuthElement returnedElement;
        KnuthElement currElement = null;
        KnuthElement prevElement = null;
        LinkedList returnedList = new LinkedList();
        LinkedList returnList = new LinkedList();
        int fromIndex = 0;

        // "unwrap" the Positions stored in the elements
        KnuthElement oldElement = null;
        while (oldListIterator.hasNext()) {
            oldElement = (KnuthElement)oldListIterator.next();
            Position innerPosition = ((NonLeafPosition) oldElement.getPosition()).getPosition();
            //log.debug(" BLM> unwrapping: "
            //  + (oldElement.isBox() ? "box    " : (oldElement.isGlue() ? "glue   " : "penalty"))
            //  + " creato da " + oldElement.getLayoutManager().getClass().getName());
            //log.debug(" BLM> unwrapping:         "
            //  + oldElement.getPosition().getClass().getName());
            if (innerPosition != null) {
                // oldElement was created by a descendant of this BlockLM
                oldElement.setPosition(innerPosition);
            } else {
                // thisElement was created by this BlockLM
                // modify its position in order to recognize it was not created
                // by a child
                oldElement.setPosition(new Position(this));
            }
        }

        // create the iterator
        List workList;
        if (bpUnit == 0) {
            workList = oldList;
        } else {
            // the storedList must be used instead of oldList;
            // find the index of the first element of returnedList
            // corresponding to the first element of oldList
            oldListIterator = oldList.listIterator();
            KnuthElement el = (KnuthElement) oldListIterator.next();
            while (!(el.getPosition() instanceof MappingPosition)) {
                el = (KnuthElement) oldListIterator.next();
            }
            int iFirst = ((MappingPosition) el.getPosition()).getFirstIndex();

            // find the index of the last element of returnedList
            // corresponding to the last element of oldList
            oldListIterator = oldList.listIterator(oldList.size());
            el = (KnuthElement) oldListIterator.previous();
            while (!(el.getPosition() instanceof MappingPosition)) {
                el = (KnuthElement) oldListIterator.previous();
            }
            int iLast = ((MappingPosition) el.getPosition()).getLastIndex();

            //log-debug("  si usa storedList da " + iFirst + " a " + iLast
            //  + " compresi su " + storedList.size() + " elementi totali");
            workList = storedList.subList(iFirst, iLast + 1);
        }
        ListIterator workListIterator = workList.listIterator();

        //log.debug("  BLM.getChangedKnuthElements> workList.size() = "
        //  + workList.size() + " da 0 a " + (workList.size() - 1));

        while (workListIterator.hasNext()) {
            currElement = (KnuthElement) workListIterator.next();
            //log.debug("elemento n. " + workListIterator.previousIndex()
            //  + " nella workList");
            if (prevElement != null
                && prevElement.getLayoutManager() != currElement.getLayoutManager()) {
                // prevElement is the last element generated by the same LM
                BlockLevelLayoutManager prevLM = (BlockLevelLayoutManager)
                                                 prevElement.getLayoutManager();
                BlockLevelLayoutManager currLM = (BlockLevelLayoutManager)
                                                 currElement.getLayoutManager();
                boolean bSomethingAdded = false;
                if (prevLM != this) {
                    //log.debug(" BLM.getChangedKnuthElements> chiamata da "
                    //    + fromIndex + " a " + workListIterator.previousIndex() + " su "
                    //    + prevLM.getClass().getName());
                    returnedList.addAll(prevLM.getChangedKnuthElements(workList.subList(
                                fromIndex, workListIterator.previousIndex()), alignment));
                    bSomethingAdded = true;
                } else {
                    // prevLM == this
                    // do nothing
                    //log.debug(" BLM.getChangedKnuthElements> elementi propri, "
                    //  + "ignorati, da " + fromIndex + " a " + workListIterator.previousIndex()
                    //  + " su " + prevLM.getClass().getName());
                }
                fromIndex = workListIterator.previousIndex();

                /*
                 * TODO: why are KnuthPenalties added here,
                 *       while in getNextKE they were changed to BreakElements?
                 */
                // there is another block after this one
                if (bSomethingAdded
                    && (this.mustKeepTogether()
                        || prevLM.mustKeepWithNext()
                        || currLM.mustKeepWithPrevious())) {
                    // add an infinite penalty to forbid a break between blocks
                    returnedList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false,
                            new Position(this), false));
                } else if (bSomethingAdded && !((KnuthElement) returnedList.getLast()).isGlue()) {
                    // add a null penalty to allow a break between blocks
                    returnedList.add(new KnuthPenalty(0, 0, false, new Position(this), false));
                }
            }
            prevElement = currElement;
        }
        if (currElement != null) {
            BlockLevelLayoutManager currLM = (BlockLevelLayoutManager)
                                             currElement.getLayoutManager();
            if (currLM != this) {
                //log.debug(" BLM.getChangedKnuthElements> chiamata da " + fromIndex
                //  + " a " + oldList.size() + " su " + currLM.getClass().getName());
                returnedList.addAll(currLM.getChangedKnuthElements(
                        workList.subList(fromIndex, workList.size()), alignment));
            } else {
                // currLM == this
                // there are no more elements to add
                // remove the last penalty added to returnedList
                if (returnedList.size() > 0) {
                    returnedList.removeLast();
                }
                //log.debug(" BLM.getChangedKnuthElements> elementi propri, ignorati, da "
                //  + fromIndex + " a " + workList.size());
            }
        }

        // append elements representing space-before
        boolean spaceBeforeIsConditional = true;
        if (fobj instanceof org.apache.fop.fo.flow.Block) {
            spaceBeforeIsConditional = ((org.apache.fop.fo.flow.Block)fobj)
                    .getCommonMarginBlock().spaceBefore.getSpace().isDiscard();
        }
        if (bpUnit > 0
            || adjustedSpaceBefore != 0) {
            if (!spaceBeforeIsConditional) {
                // add elements to prevent the glue to be discarded
                returnList.add(new KnuthBox(0,
                                            new NonLeafPosition(this, null), false));
                returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false,
                                                new NonLeafPosition(this, null), false));
            }
            if (bpUnit > 0) {
                returnList.add(new KnuthGlue(0, 0, 0,
                    SPACE_BEFORE_ADJUSTMENT, new NonLeafPosition(this, null), true));
            } else {
                returnList.add(new KnuthGlue(adjustedSpaceBefore, 0, 0,
                    SPACE_BEFORE_ADJUSTMENT, new NonLeafPosition(this, null), true));
            }
        }

        //log.debug("  BLM.getChangedKnuthElements> intermedio: returnedList.size() = "
        //    + returnedList.size());

/* estensione: conversione complessiva */
/*LF*/  if (bpUnit > 0) {
/*LF*/      storedList = returnedList;
/*LF*/      returnedList = createUnitElements(returnedList);
/*LF*/  }
/* estensione */

        // "wrap" the Position stored in each element of returnedList
        // and add elements to returnList
        ListIterator listIter = returnedList.listIterator();
        while (listIter.hasNext()) {
            returnedElement = (KnuthElement)listIter.next();
            returnedElement.setPosition(new NonLeafPosition(this, returnedElement.getPosition()));
            returnList.add(returnedElement);
        }

        // append elements representing space-after
        boolean spaceAfterIsConditional = true;
        if (fobj instanceof org.apache.fop.fo.flow.Block) {
            spaceAfterIsConditional = ((org.apache.fop.fo.flow.Block)fobj)
                        .getCommonMarginBlock().spaceAfter.getSpace().isDiscard();
        }
        if (bpUnit > 0 || adjustedSpaceAfter != 0) {
            if (!spaceAfterIsConditional) {
                returnList.add(new KnuthPenalty(0,
                        KnuthElement.INFINITE, false,
                        new NonLeafPosition(this, null), false));
            }
            if (bpUnit > 0) {
                returnList.add(new KnuthGlue(0, 0, 0,
                        SPACE_AFTER_ADJUSTMENT,
                        new NonLeafPosition(this, null),
                        (!spaceAfterIsConditional) ? false : true));
            } else {
                returnList.add(new KnuthGlue(adjustedSpaceAfter, 0, 0,
                        SPACE_AFTER_ADJUSTMENT,
                        new NonLeafPosition(this, null),
                        (!spaceAfterIsConditional) ? false : true));
            }
            if (!spaceAfterIsConditional) {
                returnList.add(new KnuthBox(0,
                        new NonLeafPosition(this, null), true));
            }
        }

        //log.debug("  BLM.getChangedKnuthElements> finished: returnList.size() = "
        //  + returnList.size());
        return returnList;
    }

    /**
     * {@inheritDoc}
     */
    // default action: ask parentLM
    public boolean mustKeepTogether() {
        return ((getParent() instanceof BlockLevelLayoutManager
                    && ((BlockLevelLayoutManager) getParent()).mustKeepTogether())
                || (getParent() instanceof InlineLayoutManager
                    && ((InlineLayoutManager) getParent()).mustKeepTogether()));
    }

    /**
     * {@inheritDoc}
     */
    public boolean mustKeepWithPrevious() {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public boolean mustKeepWithNext() {
        return false;
    }

    /**
     * Adds the unresolved elements for border and padding to a layout context so break
     * possibilities can be properly constructed.
     * @param context the layout context
     */
    protected void addPendingMarks(LayoutContext context) {
        CommonBorderPaddingBackground borderAndPadding = getBorderPaddingBackground();
        if (borderAndPadding != null) {
            if (borderAndPadding.getBorderBeforeWidth(false) > 0) {
                context.addPendingBeforeMark(new BorderElement(
                        getAuxiliaryPosition(),
                        borderAndPadding.getBorderInfo(
                                CommonBorderPaddingBackground.BEFORE).getWidth(),
                                RelSide.BEFORE,
                                false, false, this));
            }
            if (borderAndPadding.getPaddingBefore(false, this) > 0) {
                context.addPendingBeforeMark(new PaddingElement(
                        getAuxiliaryPosition(),
                        borderAndPadding.getPaddingLengthProperty(
                                CommonBorderPaddingBackground.BEFORE),
                                RelSide.BEFORE,
                                false, false, this));
            }
            if (borderAndPadding.getBorderAfterWidth(false) > 0) {
                context.addPendingAfterMark(new BorderElement(
                        getAuxiliaryPosition(),
                        borderAndPadding.getBorderInfo(
                                CommonBorderPaddingBackground.AFTER).getWidth(),
                                RelSide.AFTER,
                                false, false, this));
            }
            if (borderAndPadding.getPaddingAfter(false, this) > 0) {
                context.addPendingAfterMark(new PaddingElement(
                        getAuxiliaryPosition(),
                        borderAndPadding.getPaddingLengthProperty(
                                CommonBorderPaddingBackground.AFTER),
                                RelSide.AFTER,
                                false, false, this));
            }
        }
    }
   
    /** @return the border, padding and background info structure */
    private CommonBorderPaddingBackground getBorderPaddingBackground() {
        if (fobj instanceof org.apache.fop.fo.flow.Block) {
            return ((org.apache.fop.fo.flow.Block)fobj)
                .getCommonBorderPaddingBackground();
        } else if (fobj instanceof org.apache.fop.fo.flow.BlockContainer) {
            return ((org.apache.fop.fo.flow.BlockContainer)fobj)
                .getCommonBorderPaddingBackground();
        } else if (fobj instanceof org.apache.fop.fo.flow.ListBlock) {
            return ((org.apache.fop.fo.flow.ListBlock)fobj)
                .getCommonBorderPaddingBackground();
        } else if (fobj instanceof org.apache.fop.fo.flow.ListItem) {
            return ((org.apache.fop.fo.flow.ListItem)fobj)
                .getCommonBorderPaddingBackground();
        } else if (fobj instanceof org.apache.fop.fo.flow.table.Table) {
            return ((org.apache.fop.fo.flow.table.Table)fobj)
                .getCommonBorderPaddingBackground();
        } else {
            return null;
        }
    }
   
    /** @return the space-before property */
    private SpaceProperty getSpaceBeforeProperty() {
        if (fobj instanceof org.apache.fop.fo.flow.Block) {
            return ((org.apache.fop.fo.flow.Block)fobj)
                .getCommonMarginBlock().spaceBefore;
        } else if (fobj instanceof org.apache.fop.fo.flow.BlockContainer) {
            return ((org.apache.fop.fo.flow.BlockContainer)fobj)
                .getCommonMarginBlock().spaceBefore;
        } else if (fobj instanceof org.apache.fop.fo.flow.ListBlock) {
            return ((org.apache.fop.fo.flow.ListBlock)fobj)
                .getCommonMarginBlock().spaceBefore;
        } else if (fobj instanceof org.apache.fop.fo.flow.ListItem) {
            return ((org.apache.fop.fo.flow.ListItem)fobj)
                .getCommonMarginBlock().spaceBefore;
        } else if (fobj instanceof org.apache.fop.fo.flow.table.Table) {
            return ((org.apache.fop.fo.flow.table.Table)fobj)
                .getCommonMarginBlock().spaceBefore;
        } else {
            return null;
        }
    }
   
    /** @return the space-after property */
    private SpaceProperty getSpaceAfterProperty() {
        if (fobj instanceof org.apache.fop.fo.flow.Block) {
            return ((org.apache.fop.fo.flow.Block)fobj)
                .getCommonMarginBlock().spaceAfter;
        } else if (fobj instanceof org.apache.fop.fo.flow.BlockContainer) {
            return ((org.apache.fop.fo.flow.BlockContainer)fobj)
                .getCommonMarginBlock().spaceAfter;
        } else if (fobj instanceof org.apache.fop.fo.flow.ListBlock) {
            return ((org.apache.fop.fo.flow.ListBlock)fobj)
                .getCommonMarginBlock().spaceAfter;
        } else if (fobj instanceof org.apache.fop.fo.flow.ListItem) {
            return ((org.apache.fop.fo.flow.ListItem)fobj)
                .getCommonMarginBlock().spaceAfter;
        } else if (fobj instanceof org.apache.fop.fo.flow.table.Table) {
            return ((org.apache.fop.fo.flow.table.Table)fobj)
                .getCommonMarginBlock().spaceAfter;
        } else {
            return null;
        }
    }
   
    /**
     * Creates Knuth elements for before border padding and adds them to the return list.
     * @param returnList return list to add the additional elements to
     * @param isFirst true if this is the first time a layout manager instance needs to generate
     *                border and padding
     */
    protected void addKnuthElementsForBorderPaddingBefore(LinkedList returnList, boolean isFirst) {
        //Border and Padding (before)
        CommonBorderPaddingBackground borderAndPadding = getBorderPaddingBackground();
        if (borderAndPadding != null) {
            if (borderAndPadding.getBorderBeforeWidth(false) > 0) {
                returnList.add(new BorderElement(
                        getAuxiliaryPosition(),
                        borderAndPadding.getBorderInfo(CommonBorderPaddingBackground.BEFORE)
                                .getWidth(),
                        RelSide.BEFORE, isFirst, false, this));
            }
            if (borderAndPadding.getPaddingBefore(false, this) > 0) {
                returnList.add(new PaddingElement(
                        getAuxiliaryPosition(),
                        borderAndPadding.getPaddingLengthProperty(
                                CommonBorderPaddingBackground.BEFORE),
                        RelSide.BEFORE, isFirst, false, this));
            }
        }
    }

    /**
     * Creates Knuth elements for after border padding and adds them to the return list.
     * @param returnList return list to add the additional elements to
     * @param isLast true if this is the last time a layout manager instance needs to generate
     *               border and padding
     */
    protected void addKnuthElementsForBorderPaddingAfter(LinkedList returnList, boolean isLast) {
        //Border and Padding (after)
        CommonBorderPaddingBackground borderAndPadding = getBorderPaddingBackground();
        if (borderAndPadding != null) {
            if (borderAndPadding.getPaddingAfter(false, this) > 0) {
                returnList.add(new PaddingElement(
                        getAuxiliaryPosition(),
                        borderAndPadding.getPaddingLengthProperty(
                                CommonBorderPaddingBackground.AFTER),
                        RelSide.AFTER, false, isLast, this));
            }
            if (borderAndPadding.getBorderAfterWidth(false) > 0) {
                returnList.add(new BorderElement(
                        getAuxiliaryPosition(),
                        borderAndPadding.getBorderInfo(CommonBorderPaddingBackground.AFTER)
                                .getWidth(),
                        RelSide.AFTER, false, isLast, this));
            }
        }
    }

    /**
     * Creates Knuth elements for break-before and adds them to the return list.
     * @param returnList return list to add the additional elements to
     * @param context the layout context
     * @return true if an element has been added due to a break-before.
     */
    protected boolean addKnuthElementsForBreakBefore(LinkedList returnList,
            LayoutContext context) {
        int breakBefore = -1;
        if (fobj instanceof org.apache.fop.fo.flow.Block) {
            breakBefore = ((org.apache.fop.fo.flow.Block) fobj).getBreakBefore();
        } else if (fobj instanceof org.apache.fop.fo.flow.BlockContainer) {
            breakBefore = ((org.apache.fop.fo.flow.BlockContainer) fobj).getBreakBefore();
        } else if (fobj instanceof org.apache.fop.fo.flow.ListBlock) {
            breakBefore = ((org.apache.fop.fo.flow.ListBlock) fobj).getBreakBefore();
        } else if (fobj instanceof org.apache.fop.fo.flow.ListItem) {
            breakBefore = ((org.apache.fop.fo.flow.ListItem) fobj).getBreakBefore();
        } else if (fobj instanceof org.apache.fop.fo.flow.table.Table) {
            breakBefore = ((org.apache.fop.fo.flow.table.Table) fobj).getBreakBefore();
        }
        if (breakBefore == EN_PAGE
                || breakBefore == EN_COLUMN
                || breakBefore == EN_EVEN_PAGE
                || breakBefore == EN_ODD_PAGE) {
            // return a penalty element, representing a forced page break
            returnList.add(new BreakElement(getAuxiliaryPosition(),
                    0, -KnuthElement.INFINITE, breakBefore, context));
            return true;
        } else {
            return false;
        }
    }

    /**
     * Creates Knuth elements for break-after and adds them to the return list.
     * @param returnList return list to add the additional elements to
     * @param context the layout context
     * @return true if an element has been added due to a break-after.
     */
    protected boolean addKnuthElementsForBreakAfter(LinkedList returnList,
            LayoutContext context) {
        int breakAfter = -1;
        if (fobj instanceof org.apache.fop.fo.flow.Block) {
            breakAfter = ((org.apache.fop.fo.flow.Block) fobj).getBreakAfter();
        } else if (fobj instanceof org.apache.fop.fo.flow.BlockContainer) {
            breakAfter = ((org.apache.fop.fo.flow.BlockContainer) fobj).getBreakAfter();
        } else if (fobj instanceof org.apache.fop.fo.flow.ListBlock) {
            breakAfter = ((org.apache.fop.fo.flow.ListBlock) fobj).getBreakAfter();
        } else if (fobj instanceof org.apache.fop.fo.flow.ListItem) {
            breakAfter = ((org.apache.fop.fo.flow.ListItem) fobj).getBreakAfter();
        } else if (fobj instanceof org.apache.fop.fo.flow.table.Table) {
            breakAfter = ((org.apache.fop.fo.flow.table.Table) fobj).getBreakAfter();
        }
        if (breakAfter == EN_PAGE
                || breakAfter == EN_COLUMN
                || breakAfter == EN_EVEN_PAGE
                || breakAfter == EN_ODD_PAGE) {
            // add a penalty element, representing a forced page break
            returnList.add(new BreakElement(getAuxiliaryPosition(),
                    0, -KnuthElement.INFINITE, breakAfter, context));
            return true;
        } else {
            return false;
        }
    }

    /**
     * Creates Knuth elements for space-before and adds them to the return list.
     * @param returnList return list to add the additional elements to
     * @param alignment vertical alignment
     */
    protected void addKnuthElementsForSpaceBefore(LinkedList returnList/*,
            Position returnPosition*/, int alignment) {
        SpaceProperty spaceBefore = getSpaceBeforeProperty();
        // append elements representing space-before
        if (spaceBefore != null
                   && !(spaceBefore.getMinimum(this).getLength().getValue(this) == 0
                        && spaceBefore.getMaximum(this).getLength().getValue(this) == 0)) {
            returnList.add(new SpaceElement(getAuxiliaryPosition(), spaceBefore,
                    RelSide.BEFORE,
                    true, false, this));
        }
        /*
        if (bpUnit > 0
                || spaceBefore != null
                   && !(spaceBefore.getMinimum(this).getLength().getValue(this) == 0
                        && spaceBefore.getMaximum(this).getLength().getValue(this) == 0)) {
            if (spaceBefore != null && !spaceBefore.getSpace().isDiscard()) {
                // add elements to prevent the glue to be discarded
                returnList.add(new KnuthBox(0, getAuxiliaryPosition(), false));
                returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE,
                        false, getAuxiliaryPosition(), false));
            }
            if (bpUnit > 0) {
                returnList.add(new KnuthGlue(0, 0, 0,
                        BlockLevelLayoutManager.SPACE_BEFORE_ADJUSTMENT,
                        getAuxiliaryPosition(), true));
            } else { //if (alignment == EN_JUSTIFY) {
                returnList.add(new KnuthGlue(
                        spaceBefore.getOptimum(this).getLength().getValue(this),
                        spaceBefore.getMaximum(this).getLength().getValue(this)
                                - spaceBefore.getOptimum(this).getLength().getValue(this),
                        spaceBefore.getOptimum(this).getLength().getValue(this)
                                - spaceBefore.getMinimum(this).getLength().getValue(this),
                        BlockLevelLayoutManager.SPACE_BEFORE_ADJUSTMENT,
                        getAuxiliaryPosition(), true));
//            } else {
//                returnList.add(new KnuthGlue(
//                        spaceBefore.getOptimum().getLength().getValue(this),
//                        0, 0, BlockLevelLayoutManager.SPACE_BEFORE_ADJUSTMENT,
//                        returnPosition, true));
            }
        }*/
    }

    /**
     * Creates Knuth elements for space-after and adds them to the return list.
     * @param returnList return list to add the additional elements to
     * @param alignment vertical alignment
     */
    protected void addKnuthElementsForSpaceAfter(LinkedList returnList/*, Position returnPosition*/,
                int alignment) {
        SpaceProperty spaceAfter = getSpaceAfterProperty();
        // append elements representing space-after
        if (spaceAfter != null
                && !(spaceAfter.getMinimum(this).getLength().getValue(this) == 0
                     && spaceAfter.getMaximum(this).getLength().getValue(this) == 0)) {
            returnList.add(new SpaceElement(getAuxiliaryPosition(), spaceAfter,
                    RelSide.AFTER,
                    false, true, this));
        }
        /*
        if (bpUnit > 0
                || spaceAfter != null
                   && !(spaceAfter.getMinimum(this).getLength().getValue(this) == 0
                        && spaceAfter.getMaximum(this).getLength().getValue(this) == 0)) {
            if (spaceAfter != null && !spaceAfter.getSpace().isDiscard()) {
                returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE,
                        false, getAuxiliaryPosition(), false));
            }
            if (bpUnit > 0) {
                returnList.add(new KnuthGlue(0, 0, 0,
                        BlockLevelLayoutManager.SPACE_AFTER_ADJUSTMENT,
                        getAuxiliaryPosition(), true));
            } else { //if (alignment == EN_JUSTIFY) {
                returnList.add(new KnuthGlue(
                        spaceAfter.getOptimum(this).getLength().getValue(this),
                        spaceAfter.getMaximum(this).getLength().getValue(this)
                                - spaceAfter.getOptimum(this).getLength().getValue(this),
                        spaceAfter.getOptimum(this).getLength().getValue(this)
                                - spaceAfter.getMinimum(this).getLength().getValue(this),
                        BlockLevelLayoutManager.SPACE_AFTER_ADJUSTMENT, getAuxiliaryPosition(),
                        (!spaceAfter.getSpace().isDiscard()) ? false : true));
//            } else {
//                returnList.add(new KnuthGlue(
//                        spaceAfter.getOptimum().getLength().getValue(this), 0, 0,
//                        BlockLevelLayoutManager.SPACE_AFTER_ADJUSTMENT, returnPosition,
//                        (!spaceAfter.getSpace().isDiscard()) ? false : true));
            }
            if (spaceAfter != null && !spaceAfter.getSpace().isDiscard()) {
                returnList.add(new KnuthBox(0, getAuxiliaryPosition(), true));
            }
        }*/
    }

    protected LinkedList createUnitElements(LinkedList oldList) {
        //log.debug("Start conversion: " + oldList.size()
        //  + " elements, space-before.min=" + layoutProps.spaceBefore.getSpace().min
        //  + " space-after.min=" + layoutProps.spaceAfter.getSpace().min);
        // add elements at the beginning and at the end of oldList
        // representing minimum spaces
        LayoutManager lm = ((KnuthElement)oldList.getFirst()).getLayoutManager();
        boolean bAddedBoxBefore = false;
        boolean bAddedBoxAfter = false;
        if (adjustedSpaceBefore > 0) {
            oldList.addFirst(new KnuthBox(adjustedSpaceBefore,
                                          new Position(lm), true));
            bAddedBoxBefore = true;
        }
        if (adjustedSpaceAfter > 0) {
            oldList.addLast(new KnuthBox(adjustedSpaceAfter,
                                         new Position(lm), true));
            bAddedBoxAfter = true;
        }

        MinOptMax totalLength = new MinOptMax(0);
        MinOptMax totalUnits = new MinOptMax(0);
        LinkedList newList = new LinkedList();

        //log.debug(" Prima scansione");
        // scan the list once to compute total min, opt and max length
        ListIterator oldListIterator = oldList.listIterator();
        while (oldListIterator.hasNext()) {
            KnuthElement element = (KnuthElement) oldListIterator.next();
            if (element.isBox()) {
                totalLength.add(new MinOptMax(element.getW()));
                //log.debug("box " + element.getW());              
            } else if (element.isGlue()) {
                totalLength.min -= ((KnuthGlue) element).getZ();
                totalLength.max += ((KnuthGlue) element).getY();
                //leafValue = ((LeafPosition) element.getPosition()).getLeafPos();
                //log.debug("glue " + element.getW() + " + "
                //    + ((KnuthGlue) element).getY() + " - " + ((KnuthGlue) element).getZ());
            } else {
                //log.debug((((KnuthPenalty)element).getP() == KnuthElement.INFINITE
                //    ? "PENALTY " : "penalty ") + element.getW());
            }
        }
        // compute the total amount of "units"
        totalUnits = new MinOptMax(neededUnits(totalLength.min),
                                   neededUnits(totalLength.opt),
                                   neededUnits(totalLength.max));
        //log.debug(" totalLength= " + totalLength);
        //log.debug(" unita'= " + totalUnits);

        //log.debug(" Seconda scansione");
        // scan the list once more, stopping at every breaking point
        // in order to compute partial min, opt and max length
        // and create the new elements
        oldListIterator = oldList.listIterator();
        boolean bPrevIsBox = false;
        MinOptMax lengthBeforeBreak = new MinOptMax(0);
        MinOptMax lengthAfterBreak = (MinOptMax) totalLength.clone();
        MinOptMax unitsBeforeBreak;
        MinOptMax unitsAfterBreak;
        MinOptMax unsuppressibleUnits = new MinOptMax(0);
        int firstIndex = 0;
        int lastIndex = -1;
        while (oldListIterator.hasNext()) {
            KnuthElement element = (KnuthElement) oldListIterator.next();
            lastIndex++;
            if (element.isBox()) {
                lengthBeforeBreak.add(new MinOptMax(element.getW()));
                lengthAfterBreak.subtract(new MinOptMax(element.getW()));
                bPrevIsBox = true;
            } else if (element.isGlue()) {
                lengthBeforeBreak.min -= ((KnuthGlue) element).getZ();
                lengthAfterBreak.min += ((KnuthGlue) element).getZ();
                lengthBeforeBreak.max += ((KnuthGlue) element).getY();
                lengthAfterBreak.max -= ((KnuthGlue) element).getY();
                bPrevIsBox = false;
            } else {
                lengthBeforeBreak.add(new MinOptMax(element.getW()));
                bPrevIsBox = false;
            }

            // create the new elements
            if (element.isPenalty() && ((KnuthPenalty) element).getP() < KnuthElement.INFINITE
                    || element.isGlue() && bPrevIsBox
                    || !oldListIterator.hasNext()) {
                // suppress elements after the breaking point
                int iStepsForward = 0;
                while (oldListIterator.hasNext()) {
                    KnuthElement el = (KnuthElement) oldListIterator.next();
                    iStepsForward++;
                    if (el.isGlue()) {
                        // suppressed glue
                        lengthAfterBreak.min += ((KnuthGlue) el).getZ();
                        lengthAfterBreak.max -= ((KnuthGlue) el).getY();
                    } else if (el.isPenalty()) {
                        // suppressed penalty, do nothing
                    } else {
                        // box, end of suppressions
                        break;
                    }
                }
                // compute the partial amount of "units" before and after the break
                unitsBeforeBreak = new MinOptMax(neededUnits(lengthBeforeBreak.min),
                                                 neededUnits(lengthBeforeBreak.opt),
                                                 neededUnits(lengthBeforeBreak.max));
                unitsAfterBreak = new MinOptMax(neededUnits(lengthAfterBreak.min),
                                                neededUnits(lengthAfterBreak.opt),
                                                neededUnits(lengthAfterBreak.max));

                // rewind the iterator and lengthAfterBreak
                for (int i = 0; i < iStepsForward; i++) {
                    KnuthElement el = (KnuthElement) oldListIterator.previous();
                    if (el.isGlue()) {
                        lengthAfterBreak.min -= ((KnuthGlue) el).getZ();
                        lengthAfterBreak.max += ((KnuthGlue) el).getY();
                    }
                }

                // compute changes in length, stretch and shrink
                int uLengthChange = unitsBeforeBreak.opt + unitsAfterBreak.opt - totalUnits.opt;
                int uStretchChange = (unitsBeforeBreak.max + unitsAfterBreak.max - totalUnits.max)
                    - (unitsBeforeBreak.opt + unitsAfterBreak.opt - totalUnits.opt);
                int uShrinkChange = (unitsBeforeBreak.opt + unitsAfterBreak.opt - totalUnits.opt)
                    - (unitsBeforeBreak.min + unitsAfterBreak.min - totalUnits.min);

                // compute the number of normal, stretch and shrink unit
                // that must be added to the new sequence
                int uNewNormal = unitsBeforeBreak.opt - unsuppressibleUnits.opt;
                int uNewStretch = (unitsBeforeBreak.max - unitsBeforeBreak.opt)
                                  - (unsuppressibleUnits.max - unsuppressibleUnits.opt);
                int uNewShrink = (unitsBeforeBreak.opt - unitsBeforeBreak.min)
                                 - (unsuppressibleUnits.opt - unsuppressibleUnits.min);

                //log.debug("("
                //    + unsuppressibleUnits.min + "-" + unsuppressibleUnits.opt + "-"
                //         + unsuppressibleUnits.max + ") "
                //    + " -> " + unitsBeforeBreak.min + "-" + unitsBeforeBreak.opt + "-"
                //         + unitsBeforeBreak.max
                //    + " + " + unitsAfterBreak.min + "-" + unitsAfterBreak.opt + "-"
                //         + unitsAfterBreak.max
                //    + (uLengthChange != 0 ? " [length " + uLengthChange + "] " : "")
                //    + (uStretchChange != 0 ? " [stretch " + uStretchChange + "] " : "")
                //    + (uShrinkChange != 0 ? " [shrink " + uShrinkChange + "]" : ""));

                // create the MappingPosition which will be stored in the new elements
                // correct firstIndex and lastIndex
                int firstIndexCorrection = 0;
                int lastIndexCorrection = 0;
                if (bAddedBoxBefore) {
                    if (firstIndex != 0) {
                        firstIndexCorrection++;
                    }
                    lastIndexCorrection++;
                }
                if (bAddedBoxAfter && lastIndex == (oldList.size() - 1)) {
                    lastIndexCorrection++;
                }
                MappingPosition mappingPos = new MappingPosition(this,
                                                                 firstIndex - firstIndexCorrection,
                                                                 lastIndex - lastIndexCorrection);

                // new box
                newList.add(new KnuthBox((uNewNormal - uLengthChange) * bpUnit,
                                         mappingPos,
                                         false));
                unsuppressibleUnits.add(new MinOptMax(uNewNormal - uLengthChange));
                //log.debug("        box " + (uNewNormal - uLengthChange));

                // new infinite penalty, glue and box, if necessary
                if (uNewStretch - uStretchChange > 0
                    || uNewShrink - uShrinkChange > 0) {
                    int iStretchUnits = (uNewStretch - uStretchChange > 0
                            ? (uNewStretch - uStretchChange) : 0);
                    int iShrinkUnits = (uNewShrink - uShrinkChange > 0
                            ? (uNewShrink - uShrinkChange) : 0);
                    newList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false,
                                                 mappingPos,
                                                 false));
                    newList.add(new KnuthGlue(0,
                                              iStretchUnits * bpUnit,
                                              iShrinkUnits * bpUnit,
                                              LINE_NUMBER_ADJUSTMENT,
                                              mappingPos,
                                              false));
                    //log.debug("        PENALTY");
                    //log.debug("        glue 0 " + iStretchUnits + " " + iShrinkUnits);
                    unsuppressibleUnits.max += iStretchUnits;
                    unsuppressibleUnits.min -= iShrinkUnits;
                    if (!oldListIterator.hasNext()) {
                        newList.add(new KnuthBox(0,
                                                 mappingPos,
                                                 false));
                        //log.debug("        box 0");
                    }
                }

                // new breaking sequence
                if (uStretchChange != 0
                    || uShrinkChange != 0) {
                    // new infinite penalty, glue, penalty and glue
                    newList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false,
                                                 mappingPos,
                                                 false));
                    newList.add(new KnuthGlue(0,
                                              uStretchChange * bpUnit,
                                              uShrinkChange * bpUnit,
                                              LINE_NUMBER_ADJUSTMENT,
                                              mappingPos,
                                              false));
                    newList.add(new KnuthPenalty(uLengthChange * bpUnit,
                                                 0, false, element.getPosition(), false));
                    newList.add(new KnuthGlue(0,
                                              -uStretchChange * bpUnit,
                                              -uShrinkChange * bpUnit,
                                              LINE_NUMBER_ADJUSTMENT,
                                              mappingPos,
                                              false));
                    //log.debug("        PENALTY");
                    //log.debug("        glue 0 " + uStretchChange + " " + uShrinkChange);
                    //log.debug("        penalty " + uLengthChange + " * unit");
                    //log.debug("        glue 0 " + (- uStretchChange) + " "
                    //      + (- uShrinkChange));
                } else if (oldListIterator.hasNext()) {
                    // new penalty
                    newList.add(new KnuthPenalty(uLengthChange * bpUnit,
                                                 0, false,
                                                 mappingPos,
                                                 false));
                    //log.debug("        penalty " + uLengthChange + " * unit");
                }
                // update firstIndex
                firstIndex = lastIndex + 1;
            }

            if (element.isPenalty()) {
                lengthBeforeBreak.add(new MinOptMax(-element.getW()));
            }

        }

        // remove elements at the beginning and at the end of oldList
        // representing minimum spaces
        if (adjustedSpaceBefore > 0) {
            oldList.removeFirst();
        }
        if (adjustedSpaceAfter > 0) {
            oldList.removeLast();
        }

        // if space-before.conditionality is "discard", correct newList
        boolean correctFirstElement = false;
        if (fobj instanceof org.apache.fop.fo.flow.Block) {
            correctFirstElement = ((org.apache.fop.fo.flow.Block)fobj)
                .getCommonMarginBlock().spaceBefore.getSpace().isDiscard();
        }
        if (correctFirstElement) {
            // remove the wrong element
            KnuthBox wrongBox = (KnuthBox) newList.removeFirst();
            // if this paragraph is at the top of a page, the space before
            // must be ignored; compute the length change
            int decreasedLength = (neededUnits(totalLength.opt)
                                   - neededUnits(totalLength.opt - adjustedSpaceBefore))
                                  * bpUnit;
            // insert the correct elements
            newList.addFirst(new KnuthBox(wrongBox.getW() - decreasedLength,
                                          wrongBox.getPosition(), false));
            newList.addFirst(new KnuthGlue(decreasedLength, 0, 0, SPACE_BEFORE_ADJUSTMENT,
                                           wrongBox.getPosition(), false));
            //log.debug("        rimosso box " + neededUnits(wrongBox.getW()));
            //log.debug("        aggiunto glue " + neededUnits(decreasedLength) + " 0 0");
            //log.debug("        aggiunto box " + neededUnits(
            //       wrongBox.getW() - decreasedLength));
        }

        // if space-after.conditionality is "discard", correct newList
        boolean correctLastElement = false;
        if (fobj instanceof org.apache.fop.fo.flow.Block) {
            correctLastElement = ((org.apache.fop.fo.flow.Block)fobj)
                    .getCommonMarginBlock().spaceAfter.getSpace().isDiscard();
        }
        if (correctLastElement) {
            // remove the wrong element
            KnuthBox wrongBox = (KnuthBox) newList.removeLast();
            // if the old sequence is box(h) penalty(inf) glue(x,y,z) box(0)
            // (it cannot be parted and has some stretch or shrink)
            // the wrong box is the first one, not the last one
            LinkedList preserveList = new LinkedList();
            if (wrongBox.getW() == 0) {
                preserveList.add(wrongBox);
                preserveList.addFirst((KnuthGlue) newList.removeLast());
                preserveList.addFirst((KnuthPenalty) newList.removeLast());
                wrongBox = (KnuthBox) newList.removeLast();
            }

            // if this paragraph is at the bottom of a page, the space after
            // must be ignored; compute the length change
            int decreasedLength = (neededUnits(totalLength.opt)
                                   - neededUnits(totalLength.opt - adjustedSpaceAfter))
                                  * bpUnit;
            // insert the correct box
            newList.addLast(new KnuthBox(wrongBox.getW() - decreasedLength,
                                         wrongBox.getPosition(), false));
            // add preserved elements
            if (preserveList.size() > 0) {
                newList.addAll(preserveList);
            }
            // insert the correct glue
            newList.addLast(new KnuthGlue(decreasedLength, 0, 0, SPACE_AFTER_ADJUSTMENT,
                                          wrongBox.getPosition(), false));
            //log.debug("        rimosso box " + neededUnits(wrongBox.getW()));
            //log.debug("        aggiunto box " + neededUnits(
            //      wrongBox.getW() - decreasedLength));
            //log.debug("        aggiunto glue " + neededUnits(decreasedLength) + " 0 0");
        }

        return newList;
    }

    protected static class StackingIter extends PositionIterator {
        StackingIter(Iterator parentIter) {
            super(parentIter);
        }
   
        protected LayoutManager getLM(Object nextObj) {
            return ((Position) nextObj).getLM();
        }
   
        protected Position getPos(Object nextObj) {
            return ((Position) nextObj);
        }
    }

    protected static class MappingPosition extends Position {
        private int iFirstIndex;
        private int iLastIndex;
   
        public MappingPosition(LayoutManager lm, int first, int last) {
            super(lm);
            iFirstIndex = first;
            iLastIndex = last;
        }
       
        public int getFirstIndex() {
            return iFirstIndex;
        }
       
        public int getLastIndex() {
            return iLastIndex;
        }
    }

    /**
     * "wrap" the Position inside each element moving the elements from
     * SourceList to targetList
     * @param sourceList source list
     * @param targetList target list receiving the wrapped position elements
     */
    protected void wrapPositionElements(List sourceList, List targetList) {
        wrapPositionElements(sourceList, targetList, false);
    }
   
    /**
     * "wrap" the Position inside each element moving the elements from
     * SourceList to targetList
     * @param sourceList source list
     * @param targetList target list receiving the wrapped position elements
     * @param force if true, every Position is wrapped regardless of its LM of origin
     */
    protected void wrapPositionElements(List sourceList, List targetList, boolean force) {
         
        ListIterator listIter = sourceList.listIterator();
        while (listIter.hasNext()) {
            ListElement tempElement;
            tempElement = (ListElement) listIter.next();
            wrapPositionElement(tempElement, targetList, force);
        }
    }

    /**
     * "wrap" the Position inside the given element and add it to the target list.
     * @param el the list element
     * @param targetList target list receiving the wrapped position elements
     * @param force if true, every Position is wrapped regardless of its LM of origin
     */
    protected void wrapPositionElement(ListElement el, List targetList, boolean force) {
        if (force || el.getLayoutManager() != this) {
            el.setPosition(notifyPos(new NonLeafPosition(this,
                    el.getPosition())));
        }
        targetList.add(el);
    }

   
    /** @return the sum of start-indent and end-indent */
    protected int getIPIndents() {
        return startIndent + endIndent;
    }
   
    /**
     * Returns the IPD of the content area
     * @return the IPD of the content area
     */
    public int getContentAreaIPD() {
        return contentAreaIPD;
    }
  
    /**
     * Sets the IPD of the content area
     * @param contentAreaIPD the IPD of the content area
     */
    protected void setContentAreaIPD(int contentAreaIPD) {
        this.contentAreaIPD = contentAreaIPD;
    }
   
    /**
     * Returns the BPD of the content area
     * @return the BPD of the content area
     */
    public int getContentAreaBPD() {
        return -1;
    }
   
}
TOP

Related Classes of org.apache.fop.layoutmgr.BlockStackingLayoutManager

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.