/* * 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$ */ 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); /** parent area */ 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 // CSOK: MethodLength (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 the content list * @param parentLC the parent layout context * @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) { // CSOK: MethodLength /*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)); } }*/ } /** * Create unit elements. * @param oldList the old list * @return the new list */ protected List createUnitElements(List oldList) { // CSOK: MethodLength //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; } /** A stack iterator. */ protected static class StackingIter extends PositionIterator { /** * Construct a stacking iterator. * @param parentIter the parent iterator */ StackingIter(Iterator parentIter) { super(parentIter); } /** * @param nextObj the next position * @return the layout manager of the next position */ protected LayoutManager getLM(Object nextObj) { return ((Position) nextObj).getLM(); } /** * @param nextObj the next position * @return the next position */ protected Position getPos(Object nextObj) { return ((Position) nextObj); } } /** A mapping position. */ protected static class MappingPosition extends Position { private int iFirstIndex; private int iLastIndex; /** * Construct mapping position. * @param lm layout manager * @param first position * @param last position */ public MappingPosition(LayoutManager lm, int first, int last) { super(lm); iFirstIndex = first; iLastIndex = last; } /** @return first index */ public int getFirstIndex() { return iFirstIndex; } /** @return last index */ 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 } }