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