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