org.apache.fop.layoutmgr.FlowLayoutManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.apache.fop Show documentation
Show all versions of org.apache.fop Show documentation
The core maven build properties
The newest version!
/*
* 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: FlowLayoutManager.java 1893708 2021-09-28 14:28:08Z ssteiner $ */
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 elements = new LinkedList();
boolean isRestart = (restartPosition != null);
// always reset in case of restart (exception: see below)
boolean doReset = isRestart;
LayoutManager currentChildLM;
Stack lmStack = new Stack();
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 addChildElements(List elements,
LayoutManager childLM, LayoutContext context, int alignment,
Stack lmStack, Position position, LayoutManager restartAtLM) {
if (handleSpanChange(childLM, context) && position == null) {
SpaceResolver.resolveElementList(elements);
return elements;
}
LayoutContext childLC = makeChildLayoutContext(context);
List 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 getNextChildElements(LayoutManager childLM, LayoutContext context,
LayoutContext childLC, int alignment, Stack lmStack,
Position restartPosition, LayoutManager restartLM) {
List 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();
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 getChangedKnuthElements(List oldList, int alignment) {
ListIterator oldListIterator = oldList.listIterator();
KnuthElement returnedElement;
List returnedList = new LinkedList();
List returnList = new LinkedList();
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;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy