blob: 4dddbcf116fdc7b6f595b377d6f9b65e77153fc6 [file] [log] [blame]
/*
* 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 &gt;= 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;
}
}