blob: 9a625ad73f46ed80b1a2060ec88cc27e2b896bf3 [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.BlockParent;
import org.apache.fop.fo.pagination.Flow;
import org.apache.fop.util.ListUtil;
/**
* LayoutManager for an fo:flow object.
* Its parent LM is the PageSequenceLayoutManager.
* This LM is responsible for getting columns of the appropriate size
* and filling them with block-level areas generated by its children.
* TODO Reintroduce emergency counter (generate error to avoid endless loop)
*/
public class FlowLayoutManager extends BlockStackingLayoutManager {
/**
* logging instance
*/
private static Log log = LogFactory.getLog(FlowLayoutManager.class);
/** Array of areas currently being filled stored by area class */
private final BlockParent[] currentAreas = new BlockParent[Area.CLASS_MAX];
private boolean handlingFloat;
/**
* This is the top level layout manager.
* It is created by the PageSequence FO.
* @param pslm parent PageSequenceLayoutManager object
* @param node Flow object
*/
public FlowLayoutManager(PageSequenceLayoutManager pslm, Flow node) {
super(node);
setGeneratesBlockArea(true);
setParent(pslm);
}
/** {@inheritDoc} */
@Override
public List getNextKnuthElements(LayoutContext context, int alignment) {
return getNextKnuthElements(context, alignment, null, null);
}
/**
* Get a sequence of KnuthElements representing the content
* of the node assigned to the LM.
* @param context the LayoutContext used to store layout information
* @param alignment the desired text alignment
* @param restartPosition {@link Position} to restart from
* @param restartLM {@link LayoutManager} to restart from
* @return the list of KnuthElements
* @see LayoutManager#getNextKnuthElements(LayoutContext,int)
*/
List getNextKnuthElements(LayoutContext context, int alignment,
Position restartPosition, LayoutManager restartLM) {
List<ListElement> elements = new LinkedList<ListElement>();
boolean isRestart = (restartPosition != null);
// always reset in case of restart (exception: see below)
boolean doReset = isRestart;
LayoutManager currentChildLM;
Stack<LayoutManager> lmStack = new Stack<LayoutManager>();
if (isRestart) {
currentChildLM = restartPosition.getLM();
if (currentChildLM == null) {
throw new IllegalStateException("Cannot find layout manager to restart from");
}
if (restartLM != null && restartLM.getParent() == this) {
currentChildLM = restartLM;
} else {
while (currentChildLM.getParent() != this) {
lmStack.push(currentChildLM);
currentChildLM = currentChildLM.getParent();
}
doReset = false;
}
setCurrentChildLM(currentChildLM);
} else {
currentChildLM = getChildLM();
}
while (currentChildLM != null) {
if (!isRestart || doReset) {
if (doReset) {
currentChildLM.reset(); // TODO won't work with forced breaks
}
if (addChildElements(elements, currentChildLM, context, alignment,
null, null, null) != null) {
return elements;
}
} else {
if (addChildElements(elements, currentChildLM, context, alignment, lmStack,
restartPosition, restartLM) != null) {
return elements;
}
// restarted; force reset as of next child
doReset = true;
}
currentChildLM = getChildLM();
}
SpaceResolver.resolveElementList(elements);
setFinished(true);
assert !elements.isEmpty();
return elements;
}
private List<ListElement> addChildElements(List<ListElement> elements,
LayoutManager childLM, LayoutContext context, int alignment,
Stack<LayoutManager> lmStack, Position position, LayoutManager restartAtLM) {
if (handleSpanChange(childLM, context) && position == null) {
SpaceResolver.resolveElementList(elements);
return elements;
}
LayoutContext childLC = makeChildLayoutContext(context);
List<ListElement> childElements
= getNextChildElements(childLM, context, childLC, alignment, lmStack,
position, restartAtLM);
if (elements.isEmpty()) {
context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending());
}
if (!elements.isEmpty()
&& !ElementListUtils.startsWithForcedBreak(childElements)) {
addInBetweenBreak(elements, context, childLC);
}
context.updateKeepWithNextPending(childLC.getKeepWithNextPending());
elements.addAll(childElements);
if (ElementListUtils.endsWithForcedBreak(elements)) {
// a descendant of this flow has break-before or break-after
if (childLM.isFinished() && !hasNextChildLM()) {
setFinished(true);
}
SpaceResolver.resolveElementList(elements);
return elements;
}
return null;
}
private boolean handleSpanChange(LayoutManager childLM, LayoutContext context) {
int span = EN_NONE;
int disableColumnBalancing = EN_FALSE;
if (childLM instanceof BlockLayoutManager) {
span = ((BlockLayoutManager)childLM).getBlockFO().getSpan();
disableColumnBalancing = ((BlockLayoutManager) childLM).getBlockFO()
.getDisableColumnBalancing();
} else if (childLM instanceof BlockContainerLayoutManager) {
span = ((BlockContainerLayoutManager)childLM).getBlockContainerFO().getSpan();
disableColumnBalancing = ((BlockContainerLayoutManager) childLM).getBlockContainerFO()
.getDisableColumnBalancing();
}
int currentSpan = context.getCurrentSpan();
if (currentSpan != span) {
if (span == EN_ALL) {
context.setDisableColumnBalancing(disableColumnBalancing);
}
log.debug("span change from " + currentSpan + " to " + span);
context.signalSpanChange(span);
return true;
} else {
return false;
}
}
/**
* Overridden to take into account the current page-master's
* writing-mode
* {@inheritDoc}
*/
@Override
protected LayoutContext makeChildLayoutContext(LayoutContext context) {
LayoutContext childLC = LayoutContext.newInstance();
childLC.setStackLimitBP(context.getStackLimitBP());
childLC.setRefIPD(context.getRefIPD());
childLC.setWritingMode(getCurrentPage().getSimplePageMaster().getWritingMode());
return childLC;
}
/**
* Overridden to wrap the child positions before returning the list
* {@inheritDoc}
*/
@Override
protected List<ListElement> getNextChildElements(LayoutManager childLM, LayoutContext context,
LayoutContext childLC, int alignment, Stack<LayoutManager> lmStack,
Position restartPosition, LayoutManager restartLM) {
List<ListElement> childElements;
if (lmStack == null) {
childElements = childLM.getNextKnuthElements(childLC, alignment);
} else {
childElements = childLM.getNextKnuthElements(childLC, alignment,
lmStack, restartPosition, restartLM);
}
assert !childElements.isEmpty();
// "wrap" the Position inside each element
List tempList = childElements;
childElements = new LinkedList<ListElement>();
wrapPositionElements(tempList, childElements);
return childElements;
}
/** {@inheritDoc} */
@Override
public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) {
log.debug(" FLM.negotiateBPDAdjustment> " + adj);
Position lastPosition = lastElement.getPosition();
if (lastPosition instanceof NonLeafPosition) {
// this element was not created by this FlowLM
NonLeafPosition savedPos = (NonLeafPosition) lastPosition;
lastElement.setPosition(savedPos.getPosition());
int returnValue = ((BlockLevelLayoutManager)lastElement.getLayoutManager())
.negotiateBPDAdjustment(adj, lastElement);
lastElement.setPosition(savedPos);
log.debug(" FLM.negotiateBPDAdjustment> result " + returnValue);
return returnValue;
} else {
return 0;
}
}
/** {@inheritDoc} */
@Override
public void discardSpace(KnuthGlue spaceGlue) {
log.debug(" FLM.discardSpace> ");
Position gluePosition = spaceGlue.getPosition();
if (gluePosition instanceof NonLeafPosition) {
// this element was not created by this FlowLM
NonLeafPosition savedPos = (NonLeafPosition) gluePosition;
spaceGlue.setPosition(savedPos.getPosition());
((BlockLevelLayoutManager) spaceGlue.getLayoutManager()).discardSpace(spaceGlue);
spaceGlue.setPosition(savedPos);
}
}
/** {@inheritDoc} */
@Override
public Keep getKeepTogether() {
return Keep.KEEP_AUTO;
}
/** {@inheritDoc} */
@Override
public Keep getKeepWithNext() {
return Keep.KEEP_AUTO;
}
/** {@inheritDoc} */
@Override
public Keep getKeepWithPrevious() {
return Keep.KEEP_AUTO;
}
/** {@inheritDoc} */
@Override
public List<KnuthElement> getChangedKnuthElements(List oldList, int alignment) {
ListIterator<KnuthElement> oldListIterator = oldList.listIterator();
KnuthElement returnedElement;
List<KnuthElement> returnedList = new LinkedList<KnuthElement>();
List<KnuthElement> returnList = new LinkedList<KnuthElement>();
KnuthElement prevElement = null;
KnuthElement currElement = null;
int fromIndex = 0;
// "unwrap" the Positions stored in the elements
KnuthElement oldElement;
while (oldListIterator.hasNext()) {
oldElement = oldListIterator.next();
if (oldElement.getPosition() instanceof NonLeafPosition) {
// oldElement was created by a descendant of this FlowLM
oldElement.setPosition((oldElement.getPosition()).getPosition());
} else {
// thisElement was created by this FlowLM, remove it
oldListIterator.remove();
}
}
// reset the iterator
oldListIterator = oldList.listIterator();
while (oldListIterator.hasNext()) {
currElement = oldListIterator.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();
returnedList.addAll(prevLM.getChangedKnuthElements(
oldList.subList(fromIndex, oldListIterator.previousIndex()), alignment));
fromIndex = oldListIterator.previousIndex();
// there is another block after this one
if (prevLM.mustKeepWithNext()
|| currLM.mustKeepWithPrevious()) {
// add an infinite penalty to forbid a break between blocks
returnedList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false,
new Position(this), false));
} else if (!ListUtil.getLast(returnedList).isGlue()) {
// add a null penalty to allow a break between blocks
returnedList.add(new KnuthPenalty(0, 0, false, new Position(this), false));
}
}
prevElement = currElement;
}
if (currElement != null) {
BlockLevelLayoutManager currLM = (BlockLevelLayoutManager)
currElement.getLayoutManager();
returnedList.addAll(currLM.getChangedKnuthElements(
oldList.subList(fromIndex, oldList.size()), alignment));
}
// "wrap" the Position stored in each element of returnedList
// and add elements to returnList
for (KnuthElement aReturnedList : returnedList) {
returnedElement = aReturnedList;
if (returnedElement.getLayoutManager() != this) {
returnedElement.setPosition(
new NonLeafPosition(this, returnedElement.getPosition()));
}
returnList.add(returnedElement);
}
return returnList;
}
/** {@inheritDoc} */
@Override
public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) {
AreaAdditionUtil.addAreas(this, parentIter, layoutContext);
flush();
}
/**
* Add child area to a the correct container, depending on its
* area class. A Flow can fill at most one area container of any class
* at any one time. The actual work is done by BlockStackingLM.
*
* @param childArea the area to add
*/
@Override
public void addChildArea(Area childArea) {
if (childArea instanceof BlockParent && handlingFloat()) {
BlockParent bp = (BlockParent) childArea;
bp.setXOffset(getPSLM().getStartIntrusionAdjustment());
}
getParentArea(childArea);
addChildToArea(childArea,
this.currentAreas[childArea.getAreaClass()]);
}
/** {@inheritDoc} */
@Override
public Area getParentArea(Area childArea) {
BlockParent parentArea = null;
int aclass = childArea.getAreaClass();
if (aclass == Area.CLASS_NORMAL || aclass == Area.CLASS_SIDE_FLOAT) {
parentArea = getCurrentPV().getCurrentFlow();
} else if (aclass == Area.CLASS_BEFORE_FLOAT) {
parentArea = getCurrentPV().getBodyRegion().getBeforeFloat();
} else if (aclass == Area.CLASS_FOOTNOTE) {
parentArea = getCurrentPV().getBodyRegion().getFootnote();
} else {
throw new IllegalStateException("(internal error) Invalid "
+ "area class (" + aclass + ") requested.");
}
this.currentAreas[aclass] = parentArea;
setCurrentArea(parentArea);
return parentArea;
}
/**
* Returns the IPD of the content area
* @return the IPD of the content area
*/
@Override
public int getContentAreaIPD() {
int flowIPD = getPSLM().getCurrentColumnWidth();
return flowIPD;
}
/**
* Returns the BPD of the content area
* @return the BPD of the content area
*/
@Override
public int getContentAreaBPD() {
return getCurrentPV().getBodyRegion().getBPD();
}
/** {@inheritDoc} */
@Override
public boolean isRestartable() {
return true;
}
public void handleFloatOn() {
handlingFloat = true;
}
public void handleFloatOff() {
handlingFloat = false;
}
public boolean handlingFloat() {
return handlingFloat;
}
}