/*
 * 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 1761021 2016-09-16 11:40:57Z 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)
/** * 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
/** * logging instance */
private static Log log = LogFactory.getLog(FlowLayoutManager.class);
Array of areas currently being filled stored by area 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.
Params:
  • pslm – parent PageSequenceLayoutManager object
  • node – Flow object
/** * 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}
/** {@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.
Params:
  • context – the LayoutContext used to store layout information
  • alignment – the desired text alignment
  • restartPosition – Position to restart from
  • restartLM – LayoutManager to restart from
See Also:
Returns:the list of KnuthElements
/** * 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)) { 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}
/** * 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}
/** * 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}
/** {@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}
/** {@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}
/** {@inheritDoc} */
@Override public Keep getKeepTogether() { return Keep.KEEP_AUTO; }
{@inheritDoc}
/** {@inheritDoc} */
@Override public Keep getKeepWithNext() { return Keep.KEEP_AUTO; }
{@inheritDoc}
/** {@inheritDoc} */
@Override public Keep getKeepWithPrevious() { return Keep.KEEP_AUTO; }
{@inheritDoc}
/** {@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}
/** {@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.
Params:
  • childArea – the area to add
/** * 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}
/** {@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
Returns:the IPD of the content area
/** * 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
Returns:the BPD of the content area
/** * Returns the BPD of the content area * @return the BPD of the content area */
@Override public int getContentAreaBPD() { return getCurrentPV().getBodyRegion().getBPD(); }
{@inheritDoc}
/** {@inheritDoc} */
@Override public boolean isRestartable() { return true; } public void handleFloatOn() { handlingFloat = true; } public void handleFloatOff() { handlingFloat = false; } public boolean handlingFloat() { return handlingFloat; } }