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 893238 2009-12-22 17:20:51Z vhennebert $ */

package org.apache.fop.layoutmgr;

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

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.Constants;
import org.apache.fop.fo.FObj;
import org.apache.fop.fo.properties.BreakPropertySet;
import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
import org.apache.fop.fo.properties.KeepProperty;
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;
import org.apache.fop.util.BreakUtil;
import org.apache.fop.util.ListUtil;

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

    protected BlockParent parentArea;

    /** Value of the block-progression-unit (non-standard property) */
    protected int bpUnit;
    /** space-before value adjusted for block-progression-unit handling */
    protected int adjustedSpaceBefore;
    /** space-after value adjusted for block-progression-unit handling */
    protected int adjustedSpaceAfter;
    /** Only used to store the original list when createUnitElements is called */
    protected List storedList;
    /** Indicates whether break before has been served or not */
    protected boolean breakBeforeServed;
    /** Indicates whether the first visible mark has been returned by this LM, yet */
    protected boolean firstVisibleMarkServed;
    /** Reference IPD available */
    protected int referenceIPD;
    /** the effective start-indent value */
    protected int startIndent;
    /** the effective end-indent value */
    protected int endIndent;
    /**
     * 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;
    /** see foSpaceBefore */
    protected MinOptMax foSpaceAfter;

    private Position auxiliaryPosition;

    private int contentAreaIPD;

    /**
     * @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);
            parentLayoutManager.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());
    }

    /** {@inheritDoc} */
    protected void notifyEndOfLayout() {
        super.notifyEndOfLayout();
        // Free memory of the area tree
        //this.parentArea = null;
    }

    /**
     * Force current area to be added to parent area.
     */
    protected void flush() {
        if (getCurrentArea() != null) {
            parentLayoutManager.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);
            BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
                    getFObj().getUserAgent().getEventBroadcaster());
            eventProducer.overconstrainedAdjustEndIndent(this,
                    getFObj().getName(), ipd, getFObj().getLocator());
            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.
     * @param contentIPD the IPD of the content
     * @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);
            BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
                    getFObj().getUserAgent().getEventBroadcaster());
            eventProducer.overconstrainedAdjustEndIndent(this,
                    getFObj().getName(), ipd, getFObj().getLocator());
            endIndent += ipd;
        }
        setContentAreaIPD(contentIPD);
        return contentIPD;
    }

    /** {@inheritDoc} */
    public List getNextKnuthElements(LayoutContext context, int alignment) {
        referenceIPD = context.getRefIPD();
        updateContentAreaIPDwithOverconstrainedAdjust();

        List contentList = new LinkedList();
        List elements = new LinkedList();

        if (!breakBeforeServed) {
            breakBeforeServed = true;
            if (!context.suppressBreakBefore()) {
                if (addKnuthElementsForBreakBefore(elements, context)) {
                    return elements;
                }
            }
        }

        if (!firstVisibleMarkServed) {
            addKnuthElementsForSpaceBefore(elements, alignment);
            context.updateKeepWithPreviousPending(getKeepWithPrevious());
        }

        addKnuthElementsForBorderPaddingBefore(elements, !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;

        LayoutManager currentChildLM;
        while ((currentChildLM = (LayoutManager) getChildLM()) != null) {
            LayoutContext childLC = new LayoutContext(0);

            List childrenElements = getNextChildElements(currentChildLM, context, childLC,
                    alignment);

            if (contentList.isEmpty()) {
                //Propagate keep-with-previous up from the first child
                context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending());
            }
            if (childrenElements != null && !childrenElements.isEmpty()) {
                if (!contentList.isEmpty()
                        && !ElementListUtils.startsWithForcedBreak(childrenElements)) {
                    // there is a block handled by prevLM before the one
                    // handled by curLM, and the one handled
                    // by the current LM does not begin with a break
                    addInBetweenBreak(contentList, context, childLC);
                }
                if (childrenElements.size() == 1
                        && ElementListUtils.startsWithForcedBreak(childrenElements)) {

                    if (currentChildLM.isFinished() && !hasNextChildLM()) {
                        // a descendant of this block has break-before
                        forcedBreakAfterLast = (BreakElement) childrenElements.get(0);
                        context.clearPendingMarks();
                        break;
                    }

                    if (contentList.isEmpty()) {
                        // Empty fo:block, zero-length box makes sure the IDs and/or markers
                        // are registered and borders/padding are painted.
                        elements.add(new KnuthBox(0, notifyPos(new Position(this)), false));
                    }
                    // a descendant of this block has break-before
                    contentList.addAll(childrenElements);

                    wrapPositionElements(contentList, elements);

                    return elements;
                } else {
                    contentList.addAll(childrenElements);
                    if (ElementListUtils.endsWithForcedBreak(childrenElements)) {
                        // a descendant of this block has break-after
                        if (currentChildLM.isFinished() && !hasNextChildLM()) {
                            forcedBreakAfterLast = (BreakElement) ListUtil.removeLast(contentList);
                            context.clearPendingMarks();
                            break;
                        }

                        wrapPositionElements(contentList, elements);

                        return elements;
                    }
                }
                context.updateKeepWithNextPending(childLC.getKeepWithNextPending());
            }
        }

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

        addKnuthElementsForBorderPaddingAfter(elements, true);
        addKnuthElementsForSpaceAfter(elements, alignment);

        //All child content is processed. Only break-after can occur now, so...
        context.clearPendingMarks();
        if (forcedBreakAfterLast == null) {
            addKnuthElementsForBreakAfter(elements, context);
        } else {
            forcedBreakAfterLast.clearPendingMarks();
            elements.add(forcedBreakAfterLast);
        }

        context.updateKeepWithNextPending(getKeepWithNext());

        setFinished(true);

        return elements;
    }

    /** {@inheritDoc} */
    public List getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack,
            Position restartPosition, LayoutManager restartAtLM) {
        referenceIPD = context.getRefIPD();
        updateContentAreaIPDwithOverconstrainedAdjust();

        List contentList = new LinkedList();
        List elements = new LinkedList();

        if (!breakBeforeServed) {
            breakBeforeServed = true;
            if (!context.suppressBreakBefore()) {
                if (addKnuthElementsForBreakBefore(elements, context)) {
                    return elements;
                }
            }
        }

        if (!firstVisibleMarkServed) {
            addKnuthElementsForSpaceBefore(elements, alignment);
            context.updateKeepWithPreviousPending(getKeepWithPrevious());
        }

        addKnuthElementsForBorderPaddingBefore(elements, !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;

        LayoutContext childLC = new LayoutContext(0);
        List childrenElements;
        LayoutManager currentChildLM;
        if (lmStack.isEmpty()) {
            assert restartAtLM != null && restartAtLM.getParent() == this;
            currentChildLM = restartAtLM;
            currentChildLM.reset();
            setCurrentChildLM(currentChildLM);

            childrenElements = getNextChildElements(currentChildLM, context, childLC,
                    alignment);
        } else {
            currentChildLM = (BlockLevelLayoutManager) lmStack.pop();
            setCurrentChildLM(currentChildLM);
            childrenElements = getNextChildElements(currentChildLM, context, childLC, alignment,
                    lmStack, restartPosition, restartAtLM);
        }

        if (contentList.isEmpty()) {
            //Propagate keep-with-previous up from the first child
            context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending());
        }
        if (childrenElements != null && !childrenElements.isEmpty()) {
            if (!contentList.isEmpty()
                    && !ElementListUtils.startsWithForcedBreak(childrenElements)) {
                // there is a block handled by prevLM before the one
                // handled by curLM, and the one handled
                // by the current LM does not begin with a break
                addInBetweenBreak(contentList, context, childLC);
            }
            if (childrenElements.size() == 1
                    && ElementListUtils.startsWithForcedBreak(childrenElements)) {

                if (currentChildLM.isFinished() && !hasNextChildLM()) {
                    // a descendant of this block has break-before
                    forcedBreakAfterLast = (BreakElement) childrenElements.get(0);
                    context.clearPendingMarks();
//                    break; TODO
                }

                if (contentList.isEmpty()) {
                    // Empty fo:block, zero-length box makes sure the IDs and/or markers
                    // are registered and borders/padding are painted.
                    elements.add(new KnuthBox(0, notifyPos(new Position(this)), false));
                }
                // a descendant of this block has break-before
                contentList.addAll(childrenElements);

                wrapPositionElements(contentList, elements);

                return elements;
            } else {
                contentList.addAll(childrenElements);
                if (ElementListUtils.endsWithForcedBreak(childrenElements)) {
                    // a descendant of this block has break-after
                    if (currentChildLM.isFinished() && !hasNextChildLM()) {
                        forcedBreakAfterLast = (BreakElement) ListUtil.removeLast(contentList);
                        context.clearPendingMarks();
//                        break; TODO
                    }

                    wrapPositionElements(contentList, elements);

                    return elements;
                }
            }
            context.updateKeepWithNextPending(childLC.getKeepWithNextPending());
        }

        while ((currentChildLM = (LayoutManager) getChildLM()) != null) {
            currentChildLM.reset(); // TODO won't work with forced breaks

            childLC = new LayoutContext(0);

            childrenElements = getNextChildElements(currentChildLM, context, childLC,
                    alignment);

            if (contentList.isEmpty()) {
                //Propagate keep-with-previous up from the first child
                context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending());
            }
            if (childrenElements != null && !childrenElements.isEmpty()) {
                if (!contentList.isEmpty()
                        && !ElementListUtils.startsWithForcedBreak(childrenElements)) {
                    // there is a block handled by prevLM before the one
                    // handled by curLM, and the one handled
                    // by the current LM does not begin with a break
                    addInBetweenBreak(contentList, context, childLC);
                }
                if (childrenElements.size() == 1
                        && ElementListUtils.startsWithForcedBreak(childrenElements)) {

                    if (currentChildLM.isFinished() && !hasNextChildLM()) {
                        // a descendant of this block has break-before
                        forcedBreakAfterLast = (BreakElement) childrenElements.get(0);
                        context.clearPendingMarks();
                        break;
                    }

                    if (contentList.isEmpty()) {
                        // Empty fo:block, zero-length box makes sure the IDs and/or markers
                        // are registered and borders/padding are painted.
                        elements.add(new KnuthBox(0, notifyPos(new Position(this)), false));
                    }
                    // a descendant of this block has break-before
                    contentList.addAll(childrenElements);

                    wrapPositionElements(contentList, elements);

                    return elements;
                } else {
                    contentList.addAll(childrenElements);
                    if (ElementListUtils.endsWithForcedBreak(childrenElements)) {
                        // a descendant of this block has break-after
                        if (currentChildLM.isFinished() && !hasNextChildLM()) {
                            forcedBreakAfterLast = (BreakElement) ListUtil.removeLast(contentList);
                            context.clearPendingMarks();
                            break;
                        }

                        wrapPositionElements(contentList, elements);

                        return elements;
                    }
                }
                context.updateKeepWithNextPending(childLC.getKeepWithNextPending());
            }
        }

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

        addKnuthElementsForBorderPaddingAfter(elements, true);
        addKnuthElementsForSpaceAfter(elements, alignment);

        //All child content is processed. Only break-after can occur now, so...
        context.clearPendingMarks();
        if (forcedBreakAfterLast == null) {
            addKnuthElementsForBreakAfter(elements, context);
        } else {
            forcedBreakAfterLast.clearPendingMarks();
            elements.add(forcedBreakAfterLast);
        }

        context.updateKeepWithNextPending(getKeepWithNext());

        setFinished(true);

        return elements;
    }

    private List getNextChildElements(LayoutManager childLM, LayoutContext context,
            LayoutContext childLC, int alignment) {
        return getNextChildElements(childLM, context, childLC, alignment, null, null, null);
    }

    private List getNextChildElements(LayoutManager childLM, LayoutContext context,
            LayoutContext childLC, int alignment, Stack lmStack, Position restartPosition,
            LayoutManager restartAtLM) {
        childLC.copyPendingMarksFrom(context);
        childLC.setStackLimitBP(context.getStackLimitBP());
        if (childLM instanceof LineLayoutManager) {
            childLC.setRefIPD(getContentAreaIPD());
        } else {
            childLC.setRefIPD(referenceIPD);
        }
        if (childLM == this.childLMs.get(0)) {
            childLC.setFlags(LayoutContext.SUPPRESS_BREAK_BEFORE);
            //Handled already by the parent (break collapsing, see above)
        }

        if (lmStack == null) {
            return childLM.getNextKnuthElements(childLC, alignment);
        } else {
            if (childLM instanceof LineLayoutManager) {
                return ((LineLayoutManager) childLM).getNextKnuthElements(childLC, alignment,
                        (LeafPosition) restartPosition);
            } else {
                return childLM.getNextKnuthElements(childLC, alignment,
                        lmStack, restartPosition, restartAtLM);
            }
        }
    }

    /**
     * Adds a break element to the content list between individual child elements.
     * @param contentList
     * @param parentLC
     * @param childLC the currently active child layout context
     */
    protected void addInBetweenBreak(List contentList, LayoutContext parentLC,
                                     LayoutContext childLC) {

        if (mustKeepTogether()
                || parentLC.isKeepWithNextPending()
                || childLC.isKeepWithPreviousPending()) {

            Keep keep = getKeepTogether();

            //Handle pending keep-with-next
            keep = keep.compare(parentLC.getKeepWithNextPending());
            parentLC.clearKeepWithNextPending();

            //Handle pending keep-with-previous from child LM
            keep = keep.compare(childLC.getKeepWithPreviousPending());
            childLC.clearKeepWithPreviousPending();

            // add a penalty to forbid or discourage a break between blocks
            contentList.add(new BreakElement(
                    new Position(this), keep.getPenalty(),
                    keep.getContext(), parentLC));
            return;
        }

        ListElement last = (ListElement) ListUtil.getLast(contentList);
        if (last.isGlue()) {
            // 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
        } else if (!ElementListUtils.endsWithNonInfinitePenalty(contentList)) {

            // TODO vh: this is hacky
            // The getNextKnuthElements method of TableCellLM must not be called
            // twice, otherwise some settings like indents or borders will be
            // counted several times and lead to a wrong output. Anyway the
            // getNextKnuthElements methods should be called only once eventually
            // (i.e., when multi-threading the code), even when there are forced
            // breaks.
            // If we add a break possibility after a forced break the
            // AreaAdditionUtil.addAreas method will act on a sequence starting
            // with a SpaceResolver.SpaceHandlingBreakPosition element, having no
            // LM associated to it. Thus it will stop early instead of adding
            // areas for following Positions. The above test aims at preventing
            // such a situation from occurring. add a null penalty to allow a break
            // between blocks

            // add a null penalty to allow a break between blocks
            contentList.add(new BreakElement(
                    new Position(this), 0, Constants.EN_AUTO, parentLC));
        }
    }

    /**
     * {@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 = lastElement.getPosition().getPosition();

        if (innerPosition == null && lastElement.isGlue()) {
            // this adjustment applies to space-before or space-after of this block
            if (((KnuthGlue) lastElement).getAdjustmentClass()
                    == Adjustment.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.getWidth() > 0) {
                    // the original penalty has width > 0
/*LF*/              //log.debug("  BLM.negotiateBPDAdjustment> chiamata passata");
                    return ((BlockLevelLayoutManager)storedPenalty.getLayoutManager())
                           .negotiateBPDAdjustment(storedPenalty.getWidth(),
                                   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() == Adjustment.SPACE_BEFORE_ADJUSTMENT) {
                // space-before must be discarded
                adjustedSpaceBefore = 0;
                foSpaceBefore = MinOptMax.ZERO;
            } else {
                // space-after must be discarded
                adjustedSpaceAfter = 0;
                foSpaceAfter = MinOptMax.ZERO;
                //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 List 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;
        List returnedList = new LinkedList();
        List 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) ListUtil.getLast(returnedList))
                                .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.isEmpty()) {
                    ListUtil.removeLast(returnedList);
                }
                //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,
                    Adjustment.SPACE_BEFORE_ADJUSTMENT, new NonLeafPosition(this, null), true));
            } else {
                returnList.add(new KnuthGlue(adjustedSpaceBefore, 0, 0,
                    Adjustment.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,
                        Adjustment.SPACE_AFTER_ADJUSTMENT,
                        new NonLeafPosition(this, null),
                        spaceAfterIsConditional));
            } else {
                returnList.add(new KnuthGlue(adjustedSpaceAfter, 0, 0,
                        Adjustment.SPACE_AFTER_ADJUSTMENT,
                        new NonLeafPosition(this, null),
                        spaceAfterIsConditional));
            }
            if (!spaceAfterIsConditional) {
                returnList.add(new KnuthBox(0,
                        new NonLeafPosition(this, null), true));
            }
        }

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

    /**
     * Retrieves and returns the keep-together strength from the parent element.
     * @return the keep-together strength
     */
    protected Keep getParentKeepTogether() {
        Keep keep = Keep.KEEP_AUTO;
        if (getParent() instanceof BlockLevelLayoutManager) {
            keep = ((BlockLevelLayoutManager)getParent()).getKeepTogether();
        } else if (getParent() instanceof InlineLayoutManager) {
            if (((InlineLayoutManager) getParent()).mustKeepTogether()) {
                keep = Keep.KEEP_ALWAYS;
            }
            //TODO Fix me
            //strength = ((InlineLayoutManager) getParent()).getKeepTogetherStrength();
        }
        return keep;
    }

    /** {@inheritDoc} */
    public boolean mustKeepTogether() {
        return !getKeepTogether().isAuto();
    }

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

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

    /** {@inheritDoc} */
    public Keep getKeepTogether() {
        Keep keep = Keep.getKeep(getKeepTogetherProperty());
        keep = keep.compare(getParentKeepTogether());
        return keep;
    }

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

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

    /**
     * {@inheritDoc}
     * Default implementation throws a {@link IllegalStateException}.
     * Must be implemented by the subclass, if applicable.
     */
    public KeepProperty getKeepTogetherProperty() {
        throw new IllegalStateException();
    }

    /**
     * {@inheritDoc}
     * Default implementation throws a {@link IllegalStateException}.
     * Must be implemented by the subclass, if applicable.
     */
    public KeepProperty getKeepWithPreviousProperty() {
        throw new IllegalStateException();
    }

    /**
     * {@inheritDoc}
     * Default implementation throws a {@link IllegalStateException}.
     * Must be implemented by the subclass, if applicable.
     */
    public KeepProperty getKeepWithNextProperty() {
        throw new IllegalStateException();
    }

    /**
     * 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(List 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(List 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(List returnList,
            LayoutContext context) {
        int breakBefore = 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;
        }
    }

    /**
     * Returns the break-before value of the current formatting object.
     * @return the break-before value (Constants.EN_*)
     */
    private int getBreakBefore() {
        int breakBefore = EN_AUTO;
        if (fobj instanceof BreakPropertySet) {
            breakBefore = ((BreakPropertySet)fobj).getBreakBefore();
        }
        if (true /* uncomment to only partially merge: && breakBefore != EN_AUTO*/) {
            LayoutManager lm = getChildLM();
            //It is assumed this is only called when the first LM is active.
            if (lm instanceof BlockStackingLayoutManager) {
                BlockStackingLayoutManager bslm = (BlockStackingLayoutManager)lm;
                breakBefore = BreakUtil.compareBreakClasses(
                        breakBefore, bslm.getBreakBefore());
            }
        }
        return breakBefore;
    }

    /**
     * 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(List returnList,
            LayoutContext context) {
        int breakAfter = -1;
        if (fobj instanceof BreakPropertySet) {
            breakAfter = ((BreakPropertySet)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(List 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(List 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 List createUnitElements(List 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.get(0)).getLayoutManager();
        boolean bAddedBoxBefore = false;
        boolean bAddedBoxAfter = false;
        if (adjustedSpaceBefore > 0) {
            oldList.add(0, new KnuthBox(adjustedSpaceBefore,
                                          new Position(lm), true));
            bAddedBoxBefore = true;
        }
        if (adjustedSpaceAfter > 0) {
            oldList.add(new KnuthBox(adjustedSpaceAfter,
                                         new Position(lm), true));
            bAddedBoxAfter = true;
        }

        MinOptMax totalLength = MinOptMax.ZERO;
        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 = totalLength.plus(element.getWidth());
                //log.debug("box " + element.getWidth());
            } else if (element.isGlue()) {
                totalLength = totalLength.minusMin(element.getShrink());
                totalLength = totalLength.plusMax(element.getStretch());
                //leafValue = ((LeafPosition) element.getPosition()).getLeafPos();
                //log.debug("glue " + element.getWidth() + " + "
                //    + ((KnuthGlue) element).getStretch() + " - "
                //    + ((KnuthGlue) element).getShrink());
            } else {
                //log.debug((((KnuthPenalty)element).getPenalty() == KnuthElement.INFINITE
                //    ? "PENALTY " : "penalty ") + element.getWidth());
            }
        }
        // compute the total amount of "units"
        MinOptMax totalUnits = MinOptMax.getInstance(neededUnits(totalLength.getMin()),
                                   neededUnits(totalLength.getOpt()),
                                   neededUnits(totalLength.getMax()));
        //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 prevIsBox;
        MinOptMax lengthBeforeBreak = MinOptMax.ZERO;
        MinOptMax lengthAfterBreak = totalLength;
        MinOptMax unitsBeforeBreak;
        MinOptMax unitsAfterBreak;
        MinOptMax unsuppressibleUnits = MinOptMax.ZERO;
        int firstIndex = 0;
        int lastIndex = -1;
        while (oldListIterator.hasNext()) {
            KnuthElement element = (KnuthElement) oldListIterator.next();
            lastIndex++;
            if (element.isBox()) {
                lengthBeforeBreak = lengthBeforeBreak.plus(element.getWidth());
                lengthAfterBreak = lengthAfterBreak.minus(element.getWidth());
                prevIsBox = true;
            } else if (element.isGlue()) {
                lengthBeforeBreak = lengthBeforeBreak.minusMin(element.getShrink());
                lengthAfterBreak = lengthAfterBreak.plusMin(element.getShrink());
                lengthBeforeBreak = lengthBeforeBreak.plusMax(element.getStretch());
                lengthAfterBreak = lengthAfterBreak.minusMax(element.getStretch());
                prevIsBox = false;
            } else {
                lengthBeforeBreak = lengthBeforeBreak.plus(element.getWidth());
                prevIsBox = false;
            }

            // create the new elements
            if (element.isPenalty() && element.getPenalty() < KnuthElement.INFINITE
                    || element.isGlue() && prevIsBox
                    || !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 = lengthAfterBreak.plusMin(el.getShrink());
                        lengthAfterBreak = lengthAfterBreak.minusMax(el.getStretch());
                    } 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 = MinOptMax.getInstance(neededUnits(lengthBeforeBreak.getMin()),
                                                 neededUnits(lengthBeforeBreak.getOpt()),
                                                 neededUnits(lengthBeforeBreak.getMax()));
                unitsAfterBreak = MinOptMax.getInstance(neededUnits(lengthAfterBreak.getMin()),
                                                neededUnits(lengthAfterBreak.getOpt()),
                                                neededUnits(lengthAfterBreak.getMax()));

                // rewind the iterator and lengthAfterBreak
                for (int i = 0; i < iStepsForward; i++) {
                    KnuthElement el = (KnuthElement) oldListIterator.previous();
                    if (el.isGlue()) {
                        lengthAfterBreak = lengthAfterBreak.minusMin(el.getShrink());
                        lengthAfterBreak = lengthAfterBreak.plusMax(el.getStretch());
                    }
                }

                // compute changes in length, stretch and shrink
                int uLengthChange = unitsBeforeBreak.getOpt() + unitsAfterBreak.getOpt()
                        - totalUnits.getOpt();
                int uStretchChange = unitsBeforeBreak.getStretch()
                        + unitsAfterBreak.getStretch() - totalUnits.getStretch();
                int uShrinkChange = unitsBeforeBreak.getShrink()
                        + unitsAfterBreak.getShrink() - totalUnits.getShrink();

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

                //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 = unsuppressibleUnits.plus(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,
                                              Adjustment.LINE_NUMBER_ADJUSTMENT,
                                              mappingPos,
                                              false));
                    //log.debug("        PENALTY");
                    //log.debug("        glue 0 " + iStretchUnits + " " + iShrinkUnits);
                    unsuppressibleUnits = unsuppressibleUnits.plusMax(iStretchUnits);
                    unsuppressibleUnits = unsuppressibleUnits.minusMin(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,
                                              Adjustment.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,
                                              Adjustment.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 = lengthBeforeBreak.minus(element.getWidth());
            }

        }

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

        // 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.getOpt())
                                   - neededUnits(totalLength.getOpt() - adjustedSpaceBefore))
                                  * bpUnit;
            // insert the correct elements
            newList.addFirst(new KnuthBox(wrongBox.getWidth() - decreasedLength,
                                          wrongBox.getPosition(), false));
            newList.addFirst(new KnuthGlue(decreasedLength, 0, 0, Adjustment.SPACE_BEFORE_ADJUSTMENT,
                                           wrongBox.getPosition(), false));
            //log.debug("        rimosso box " + neededUnits(wrongBox.getWidth()));
            //log.debug("        aggiunto glue " + neededUnits(decreasedLength) + " 0 0");
            //log.debug("        aggiunto box " + neededUnits(
            //       wrongBox.getWidth() - 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.getWidth() == 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.getOpt())
                                   - neededUnits(totalLength.getOpt() - adjustedSpaceAfter))
                                  * bpUnit;
            // insert the correct box
            newList.addLast(new KnuthBox(wrongBox.getWidth() - decreasedLength,
                                         wrongBox.getPosition(), false));
            // add preserved elements
            if (!preserveList.isEmpty()) {
                newList.addAll(preserveList);
            }
            // insert the correct glue
            newList.addLast(new KnuthGlue(decreasedLength, 0, 0, Adjustment.SPACE_AFTER_ADJUSTMENT,
                                          wrongBox.getPosition(), false));
            //log.debug("        rimosso box " + neededUnits(wrongBox.getWidth()));
            //log.debug("        aggiunto box " + neededUnits(
            //      wrongBox.getWidth() - 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();
        Object tempElement;
        while (listIter.hasNext()) {
            tempElement = listIter.next();
            if (tempElement instanceof ListElement) {
                wrapPositionElement(
                        (ListElement) tempElement,
                        targetList,
                        force);
            } else if (tempElement instanceof List) {
                wrapPositionElements(
                        (List) 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;
    }

    /** {@inheritDoc} */
    public void reset() {
        super.reset();
        breakBeforeServed = false;
        firstVisibleMarkServed = false;
        // TODO startIndent, endIndent
    }

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