| /* |
| * 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.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.InlineContainerLayoutManager; |
| import org.apache.fop.layoutmgr.inline.InlineLayoutManager; |
| import org.apache.fop.traits.MinOptMax; |
| 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<KnuthElement> 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; |
| private boolean isRestartAtLM; |
| |
| /** |
| * @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.setChangeBarList(getChangeBarList()); |
| 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. |
| */ |
| @Override |
| public void addChildArea(Area childArea) { |
| addChildToArea(childArea, getCurrentArea()); |
| } |
| |
| /** |
| * 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} */ |
| @Override |
| public List getNextKnuthElements(LayoutContext context, int alignment) { |
| return getNextKnuthElements(context, alignment, null, null, null); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public List getNextKnuthElements(LayoutContext context, int alignment, |
| Stack lmStack, Position restartPosition, LayoutManager restartAtLM) { |
| isRestartAtLM = restartAtLM != null; |
| referenceIPD = context.getRefIPD(); |
| updateContentAreaIPDwithOverconstrainedAdjust(); |
| |
| boolean isRestart = (lmStack != null); |
| boolean emptyStack = (!isRestart || lmStack.isEmpty()); |
| List<ListElement> contentList = new LinkedList<ListElement>(); |
| List<ListElement> elements = new LinkedList<ListElement>(); |
| |
| if (!breakBeforeServed(context, elements)) { |
| // if this FO has break-before specified, and it |
| // has not yet been processed, return now |
| return elements; |
| } |
| |
| addFirstVisibleMarks(elements, context, alignment); |
| |
| //Used to indicate a special break-after case when all content has already been generated. |
| BreakElement forcedBreakAfterLast = null; |
| |
| LayoutContext childLC; |
| List<ListElement> childElements; |
| LayoutManager currentChildLM; |
| if (isRestart) { |
| if (emptyStack) { |
| assert restartAtLM != null && restartAtLM.getParent() == this; |
| currentChildLM = restartAtLM; |
| } else { |
| currentChildLM = (LayoutManager) lmStack.pop(); |
| } |
| setCurrentChildLM(currentChildLM); |
| } else { |
| currentChildLM = getChildLM(); |
| } |
| |
| while (currentChildLM != null) { |
| |
| childLC = makeChildLayoutContext(context); |
| |
| if (!isRestart || emptyStack) { |
| if (isRestart) { |
| currentChildLM.reset(); // TODO won't work with forced breaks |
| } |
| |
| childElements = getNextChildElements(currentChildLM, context, childLC, alignment, |
| null, null, null); |
| } else { |
| // restart && non-empty LM stack |
| childElements = getNextChildElements(currentChildLM, context, childLC, alignment, |
| lmStack, restartPosition, restartAtLM); |
| // once encountered, irrelevant for following child LMs |
| emptyStack = true; |
| } |
| |
| if (contentList.isEmpty()) { |
| // propagate keep-with-previous up from the first child |
| context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending()); |
| } |
| |
| // handle non-empty child |
| if (childElements != null && !childElements.isEmpty()) { |
| if (!contentList.isEmpty() |
| && !ElementListUtils.startsWithForcedBreak(childElements)) { |
| // 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 (childElements.size() == 1 |
| && ElementListUtils.startsWithForcedBreak(childElements)) { |
| // a descendant of this block has break-before |
| if (currentChildLM.isFinished() && !hasNextChildLM()) { |
| // if there is no more content, make sure pending |
| // marks are cleared |
| forcedBreakAfterLast = (BreakElement) childElements.get(0); |
| context.clearPendingMarks(); |
| // break without adding the child elements |
| 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(makeAuxiliaryZeroWidthBox()); |
| } |
| // add the forced break |
| contentList.addAll(childElements); |
| // wrap position and return |
| wrapPositionElements(contentList, elements); |
| return elements; |
| } else { |
| // add all accumulated child elements |
| contentList.addAll(childElements); |
| if (ElementListUtils.endsWithForcedBreak(childElements)) { |
| // a descendant of this block has break-after |
| if (currentChildLM.isFinished() && !hasNextChildLM()) { |
| // if there is no more content, make sure any |
| // pending marks are cleared |
| forcedBreakAfterLast = (BreakElement) ListUtil.removeLast(contentList); |
| context.clearPendingMarks(); |
| break; |
| } |
| //wrap positions and return |
| wrapPositionElements(contentList, elements); |
| return elements; |
| } |
| } |
| context.updateKeepWithNextPending(childLC.getKeepWithNextPending()); |
| } |
| currentChildLM = getChildLM(); |
| } |
| |
| if (contentList.isEmpty()) { |
| if (forcedBreakAfterLast == null) { |
| // empty fo:block: zero-length box makes sure the IDs and/or markers |
| // are registered. |
| elements.add(makeAuxiliaryZeroWidthBox()); |
| } |
| } else { |
| // wrap child positions |
| wrapPositionElements(contentList, elements); |
| } |
| |
| addLastVisibleMarks(elements, context, alignment); |
| |
| if (forcedBreakAfterLast == null) { |
| addKnuthElementsForBreakAfter(elements, context); |
| } else { |
| forcedBreakAfterLast.clearPendingMarks(); |
| elements.add(forcedBreakAfterLast); |
| } |
| |
| context.updateKeepWithNextPending(getKeepWithNext()); |
| setFinished(true); |
| return elements; |
| } |
| |
| /** |
| * Creates and initializes a {@link LayoutContext} to pass to the child LM |
| * @param context the parent {@link LayoutContext} |
| * @return a new child layout context |
| */ |
| protected LayoutContext makeChildLayoutContext(LayoutContext context) { |
| LayoutContext childLC = LayoutContext.newInstance(); |
| childLC.copyPendingMarksFrom(context); |
| childLC.setStackLimitBP(context.getStackLimitBP()); |
| childLC.setRefIPD(referenceIPD); |
| return childLC; |
| } |
| |
| /** |
| * Checks if this LM's first "visible marks" (= borders, padding, spaces) have |
| * already been processed, and if necessary, adds corresponding elements to |
| * the specified list, and updates the given layout context accordingly. |
| * @param elements the element list |
| * @param context the layout context |
| * @param alignment the vertical alignment |
| */ |
| protected void addFirstVisibleMarks(List<ListElement> elements, |
| LayoutContext context, int alignment) { |
| 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); |
| } |
| |
| /** |
| * Adds elements the LM's last/closing marks to the specified list, and |
| * updates the layout context accordingly. |
| * @param elements the element list |
| * @param context the layout context |
| * @param alignment the vertical alignment |
| */ |
| protected void addLastVisibleMarks(List<ListElement> elements, |
| LayoutContext context, int alignment) { |
| addKnuthElementsForBorderPaddingAfter(elements, true); |
| addKnuthElementsForSpaceAfter(elements, alignment); |
| |
| // All child content processed. Only break-after can occur now, so... |
| context.clearPendingMarks(); |
| } |
| |
| /** |
| * Check whether there is a break-before condition. If so, and |
| * the specified {@code context} allows it, add the necessary elements |
| * to the given {@code elements} list. |
| * @param context the layout context |
| * @param elements the element list |
| * @return {@code false} if there is a break-before condition, and it has not been served; |
| * {@code true} otherwise |
| */ |
| protected boolean breakBeforeServed(LayoutContext context, List<ListElement> elements) { |
| if (!breakBeforeServed) { |
| breakBeforeServed = true; |
| if (!context.suppressBreakBefore()) { |
| if (addKnuthElementsForBreakBefore(elements, context)) { |
| return false; |
| } |
| } |
| } |
| return breakBeforeServed; |
| } |
| |
| private KnuthBox makeZeroWidthBox() { |
| return new KnuthBox(0, new NonLeafPosition(this, null), false); |
| } |
| |
| private KnuthBox makeAuxiliaryZeroWidthBox() { |
| return new KnuthBox(0, notifyPos(new Position(this)), true); |
| } |
| |
| private KnuthPenalty makeZeroWidthPenalty(int penaltyValue) { |
| return new KnuthPenalty(0, penaltyValue, false, new NonLeafPosition(this, null), false); |
| } |
| |
| private KnuthGlue makeSpaceAdjustmentGlue(int width, Adjustment adjustmentClass, |
| boolean isAuxiliary) { |
| return new KnuthGlue(width, 0, 0, |
| adjustmentClass, |
| new NonLeafPosition(this, null), |
| isAuxiliary); |
| } |
| |
| /** |
| * Gets the next set of child elements for the given childLM. |
| * The default implementation basically copies the pending marks to the child layout context, |
| * and subsequently calls the appropriate variant of {@code childLM.getNextKnuthElements()}, |
| * passing it all relevant parameters. |
| * @param childLM the current child LM |
| * @param context the layout context |
| * @param childLC the child layout context |
| * @param alignment the vertical alignment |
| * @param lmStack the stack of currently active LMs (if any) |
| * @param restartPosition the position to restart from (if any) |
| * @param restartAtLM the LM to restart from (if any) |
| * @return list of elements corresponding to the content generated by childLM |
| */ |
| protected List<ListElement> getNextChildElements(LayoutManager childLM, LayoutContext context, |
| LayoutContext childLC, int alignment, Stack<LayoutManager> lmStack, |
| Position restartPosition, LayoutManager restartAtLM) { |
| |
| if (childLM == this.childLMs.get(0)) { |
| childLC.setFlags(LayoutContext.SUPPRESS_BREAK_BEFORE); |
| //Handled already by the parent (break collapsing, see above) |
| } |
| |
| if (lmStack == null) { |
| // route to default implementation, in case childLM does not provide |
| // an override similar to this class |
| return childLM.getNextKnuthElements(childLC, alignment); |
| } 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<ListElement> 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 = 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) { |
| assert (lastElement != null && lastElement.getPosition() != null); |
| 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; |
| } else { |
| // this adjustment applies to space-after |
| adjustedSpaceAfter += 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 |
| 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); |
| } |
| } |
| 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 |
| KnuthPenalty storedPenalty = (KnuthPenalty) |
| storedList.get(mappingPos.getLastIndex()); |
| if (storedPenalty.getWidth() > 0) { |
| // the original penalty has width > 0 |
| return ((BlockLevelLayoutManager)storedPenalty.getLayoutManager()) |
| .negotiateBPDAdjustment(storedPenalty.getWidth(), |
| storedPenalty); |
| } else { |
| // the original penalty has width = 0 |
| // the adjustment involves only the spaces before and after |
| return adj; |
| } |
| } |
| } else if (innerPosition != null && innerPosition.getLM() != this) { |
| Position lastPosition = lastElement.getPosition(); |
| assert (lastPosition instanceof NonLeafPosition); |
| // this adjustment concerns another LM |
| NonLeafPosition savedPos = (NonLeafPosition) lastPosition; |
| lastElement.setPosition(innerPosition); |
| int returnValue = ((BlockLevelLayoutManager) lastElement.getLayoutManager()) |
| .negotiateBPDAdjustment(adj, lastElement); |
| lastElement.setPosition(savedPos); |
| return returnValue; |
| } else { |
| // this should never happen |
| log.error("BlockLayoutManager.negotiateBPDAdjustment(): unexpected Position"); |
| return 0; |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public void discardSpace(KnuthGlue spaceGlue) { |
| assert (spaceGlue != null && spaceGlue.getPosition() != null); |
| Position mainPosition = spaceGlue.getPosition(); |
| Position innerPosition = mainPosition.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 { |
| assert (mainPosition instanceof NonLeafPosition); |
| // this element was not created by this BlockLM |
| NonLeafPosition savedPos = (NonLeafPosition) mainPosition; |
| spaceGlue.setPosition(innerPosition); |
| ((BlockLevelLayoutManager) spaceGlue.getLayoutManager()).discardSpace(spaceGlue); |
| spaceGlue.setPosition(savedPos); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public List getChangedKnuthElements(List oldList, int alignment) { |
| ListIterator<KnuthElement> oldListIterator = oldList.listIterator(); |
| KnuthElement currElement = null; |
| KnuthElement prevElement = null; |
| List<KnuthElement> returnedList = new LinkedList<KnuthElement>(); |
| List<KnuthElement> returnList = new LinkedList<KnuthElement>(); |
| int fromIndex = 0; |
| |
| // "unwrap" the Positions stored in the elements |
| KnuthElement oldElement; |
| while (oldListIterator.hasNext()) { |
| oldElement = oldListIterator.next(); |
| assert oldElement.getPosition() != null; |
| Position innerPosition = oldElement.getPosition().getPosition(); |
| if (innerPosition != null) { |
| // oldElement was created by a descendant |
| oldElement.setPosition(innerPosition); |
| } else { |
| // oldElement was created by this LM: |
| // modify its position in order to recognize it was not created |
| // by a child |
| oldElement.setPosition(new Position(this)); |
| } |
| } |
| |
| // create the iterator |
| ListIterator<KnuthElement> workListIterator = oldList.listIterator(); |
| while (workListIterator.hasNext()) { |
| currElement = workListIterator.next(); |
| 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 somethingAdded = false; |
| if (prevLM != this) { |
| returnedList.addAll( |
| prevLM.getChangedKnuthElements( |
| oldList.subList(fromIndex, workListIterator.previousIndex()), |
| alignment)); |
| somethingAdded = true; |
| } else { |
| // do nothing |
| } |
| 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 (somethingAdded |
| && (this.mustKeepTogether() |
| || prevLM.mustKeepWithNext() |
| || currLM.mustKeepWithPrevious())) { |
| // add an infinite penalty to forbid a break between blocks |
| returnedList.add(makeZeroWidthPenalty(KnuthPenalty.INFINITE)); |
| } else if (somethingAdded |
| && !ListUtil.getLast(returnedList).isGlue()) { |
| // add a null penalty to allow a break between blocks |
| returnedList.add(makeZeroWidthPenalty(KnuthPenalty.INFINITE)); |
| } |
| } |
| prevElement = currElement; |
| } |
| if (currElement != null) { |
| LayoutManager currLM = currElement.getLayoutManager(); |
| if (currLM != this) { |
| returnedList.addAll(currLM.getChangedKnuthElements( |
| oldList.subList(fromIndex, oldList.size()), alignment)); |
| } else { |
| // there are no more elements to add |
| // remove the last penalty added to returnedList |
| if (!returnedList.isEmpty()) { |
| ListUtil.removeLast(returnedList); |
| } |
| } |
| } |
| |
| // append elements representing space-before |
| boolean spaceBeforeIsConditional = true; |
| if (fobj instanceof org.apache.fop.fo.flow.Block) { |
| spaceBeforeIsConditional = getSpaceBeforeProperty().isDiscard(); |
| } |
| if (adjustedSpaceBefore != 0) { |
| if (!spaceBeforeIsConditional) { |
| // add elements to prevent the glue to be discarded |
| returnList.add(makeZeroWidthBox()); |
| returnList.add(makeZeroWidthPenalty(KnuthPenalty.INFINITE)); |
| } |
| |
| returnList.add(makeSpaceAdjustmentGlue(adjustedSpaceBefore, |
| Adjustment.SPACE_BEFORE_ADJUSTMENT, |
| false)); |
| } |
| |
| // "wrap" the Position stored in each element of returnedList |
| // and add elements to returnList |
| for (KnuthElement el : returnedList) { |
| el.setPosition(new NonLeafPosition(this, el.getPosition())); |
| returnList.add(el); |
| } |
| |
| // append elements representing space-after |
| boolean spaceAfterIsConditional = true; |
| if (fobj instanceof org.apache.fop.fo.flow.Block) { |
| spaceAfterIsConditional = getSpaceAfterProperty().isDiscard(); |
| } |
| if (adjustedSpaceAfter != 0) { |
| if (!spaceAfterIsConditional) { |
| returnList.add(makeZeroWidthPenalty(KnuthPenalty.INFINITE)); |
| } |
| |
| returnList.add(makeSpaceAdjustmentGlue(adjustedSpaceAfter, |
| Adjustment.SPACE_AFTER_ADJUSTMENT, |
| spaceAfterIsConditional)); |
| |
| if (!spaceAfterIsConditional) { |
| returnList.add(makeZeroWidthBox()); |
| } |
| } |
| |
| 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()); |
| if (getFObj().isForceKeepTogether()) { |
| keep = Keep.KEEP_ALWAYS; |
| } |
| 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 */ |
| protected 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 */ |
| protected 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 highest priority break-before value on this layout manager or its |
| * relevant descendants. |
| * |
| * @return the break-before value (Constants.EN_*) |
| * @see BreakOpportunity#getBreakBefore() |
| */ |
| public int getBreakBefore() { |
| return BreakOpportunityHelper.getBreakBefore(this); |
| } |
| |
| /** |
| * 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, 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)); |
| } |
| } |
| |
| /** |
| * 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, 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)); |
| } |
| } |
| |
| /** A mapping position. */ |
| protected static class MappingPosition extends Position { |
| |
| private int firstIndex; |
| private int lastIndex; |
| |
| /** |
| * Construct mapping position. |
| * @param lm layout manager |
| * @param first position |
| * @param last position |
| */ |
| public MappingPosition(LayoutManager lm, int first, int last) { |
| super(lm); |
| firstIndex = first; |
| lastIndex = last; |
| } |
| |
| /** @return first index */ |
| public int getFirstIndex() { |
| return firstIndex; |
| } |
| |
| /** @return last index */ |
| public int getLastIndex() { |
| return lastIndex; |
| } |
| } |
| |
| /** |
| * "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 |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| public int getContentAreaBPD() { |
| return -1; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void reset() { |
| super.reset(); |
| breakBeforeServed = false; |
| firstVisibleMarkServed = false; |
| // TODO startIndent, endIndent |
| } |
| |
| /** |
| * Whether this LM can handle horizontal overflow error messages (only a BlockContainerLayoutManager can). |
| * @param milliPoints horizontal overflow |
| * @return true if handled by a BlockContainerLayoutManager |
| */ |
| public boolean handleOverflow(int milliPoints) { |
| if (getParent() instanceof BlockStackingLayoutManager) { |
| return ((BlockStackingLayoutManager) getParent()).handleOverflow(milliPoints); |
| } else if (getParent() instanceof InlineContainerLayoutManager) { |
| return ((InlineContainerLayoutManager) getParent()).handleOverflow(milliPoints); |
| } |
| return false; |
| } |
| |
| public boolean isRestartAtLM() { |
| return isRestartAtLM; |
| } |
| } |
| |