/* * 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; /** * 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 implements BlockLevelLayoutManager { /** * logging instance */ private static Log log = LogFactory.getLog(FlowLayoutManager.class); /** Array of areas currently being filled stored by area class */ private BlockParent[] currentAreas = new BlockParent[Area.CLASS_MAX]; /** * 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); setParent(pslm); } /** {@inheritDoc} */ public List getNextKnuthElements(LayoutContext context, int alignment) { List elements = new LinkedList(); LayoutManager currentChildLM; while ((currentChildLM = getChildLM()) != null) { if (addChildElements(elements, currentChildLM, context, alignment) != null) { return elements; } } SpaceResolver.resolveElementList(elements); setFinished(true); assert !elements.isEmpty(); return elements; } /** * 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 positionAtIPDChange position at ipd change * @param restartAtLM restart at this layout manager * @return the list of KnuthElements * @see LayoutManager#getNextKnuthElements(LayoutContext,int) */ public List getNextKnuthElements(LayoutContext context, int alignment, Position positionAtIPDChange, LayoutManager restartAtLM) { List elements = new LinkedList(); LayoutManager currentChildLM = positionAtIPDChange.getLM(); if (currentChildLM == null) { throw new IllegalStateException( "Cannot find layout manager from where to re-start layout after IPD change"); } if (restartAtLM != null && restartAtLM.getParent() == this) { currentChildLM = restartAtLM; setCurrentChildLM(currentChildLM); currentChildLM.reset(); if (addChildElements(elements, currentChildLM, context, alignment) != null) { return elements; } } else { Stack lmStack = new Stack(); while (currentChildLM.getParent() != this) { lmStack.push(currentChildLM); currentChildLM = currentChildLM.getParent(); } setCurrentChildLM(currentChildLM); if (addChildElements(elements, currentChildLM, context, alignment, lmStack, positionAtIPDChange, restartAtLM) != null) { return elements; } } while ((currentChildLM = getChildLM()) != null) { currentChildLM.reset(); // TODO won't work with forced breaks if (addChildElements(elements, currentChildLM, context, alignment) != null) { return elements; } } SpaceResolver.resolveElementList(elements); setFinished(true); assert !elements.isEmpty(); return elements; } private List addChildElements(List elements, LayoutManager childLM, LayoutContext context, int alignment) { return addChildElements(elements, childLM, context, alignment, null, null, null); } private List addChildElements(List elements, LayoutManager childLM, LayoutContext context, int alignment, Stack lmStack, Position position, LayoutManager restartAtLM) { if (handleSpanChange(childLM, elements, context)) { SpaceResolver.resolveElementList(elements); return elements; } LayoutContext childLC = new LayoutContext(0); List childrenElements = getNextChildElements(childLM, context, childLC, alignment, lmStack, position, restartAtLM); if (elements.isEmpty()) { context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending()); } if (!elements.isEmpty() && !ElementListUtils.startsWithForcedBreak(childrenElements)) { addInBetweenBreak(elements, context, childLC); } context.updateKeepWithNextPending(childLC.getKeepWithNextPending()); elements.addAll(childrenElements); 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, List elements, 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; } } private List getNextChildElements(LayoutManager childLM, LayoutContext context, LayoutContext childLC, int alignment, Stack lmStack, Position restartPosition, LayoutManager restartLM) { childLC.setStackLimitBP(context.getStackLimitBP()); childLC.setRefIPD(context.getRefIPD()); childLC.setWritingMode(getCurrentPage().getSimplePageMaster().getWritingMode()); List childrenElements; if (lmStack == null) { childrenElements = childLM.getNextKnuthElements(childLC, alignment); } else { childrenElements = childLM.getNextKnuthElements(childLC, alignment, lmStack, restartPosition, restartLM); } assert !childrenElements.isEmpty(); // "wrap" the Position inside each element List tempList = childrenElements; childrenElements = new LinkedList(); wrapPositionElements(tempList, childrenElements); return childrenElements; } /** * {@inheritDoc} */ public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) { log.debug(" FLM.negotiateBPDAdjustment> " + adj); if (lastElement.getPosition() instanceof NonLeafPosition) { // this element was not created by this FlowLM NonLeafPosition savedPos = (NonLeafPosition)lastElement.getPosition(); 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} */ public void discardSpace(KnuthGlue spaceGlue) { log.debug(" FLM.discardSpace> "); if (spaceGlue.getPosition() instanceof NonLeafPosition) { // this element was not created by this FlowLM NonLeafPosition savedPos = (NonLeafPosition)spaceGlue.getPosition(); spaceGlue.setPosition(savedPos.getPosition()); ((BlockLevelLayoutManager) spaceGlue.getLayoutManager()).discardSpace(spaceGlue); spaceGlue.setPosition(savedPos); } } /** {@inheritDoc} */ public Keep getKeepTogether() { return Keep.KEEP_AUTO; } /** {@inheritDoc} */ public Keep getKeepWithNext() { return Keep.KEEP_AUTO; } /** {@inheritDoc} */ public Keep getKeepWithPrevious() { return Keep.KEEP_AUTO; } /** {@inheritDoc} */ public List getChangedKnuthElements(List oldList, /*int flaggedPenalty,*/ 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 = (KnuthElement)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 = (KnuthElement) 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 (!((KnuthElement) returnedList.get(returnedList .size() - 1)).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 ListIterator listIter = returnedList.listIterator(); while (listIter.hasNext()) { returnedElement = (KnuthElement)listIter.next(); if (returnedElement.getLayoutManager() != this) { returnedElement.setPosition( new NonLeafPosition(this, returnedElement.getPosition())); } returnList.add(returnedElement); } return returnList; } /** * {@inheritDoc} */ 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 */ public void addChildArea(Area childArea) { getParentArea(childArea); addChildToArea(childArea, this.currentAreas[childArea.getAreaClass()]); } /** * {@inheritDoc} */ public Area getParentArea(Area childArea) { BlockParent parentArea = null; int aclass = childArea.getAreaClass(); if (aclass == Area.CLASS_NORMAL) { 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 */ public int getContentAreaIPD() { return getCurrentPV().getCurrentSpan().getColumnWidth(); } /** * Returns the BPD of the content area * @return the BPD of the content area */ public int getContentAreaBPD() { return getCurrentPV().getBodyRegion().getBPD(); } /** {@inheritDoc} */ public boolean isRestartable() { return true; } }