/* * 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.Block; import org.apache.fop.area.LineArea; import org.apache.fop.datatypes.Length; import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.FontTriplet; import org.apache.fop.layoutmgr.inline.InlineLevelLayoutManager; import org.apache.fop.layoutmgr.inline.LineLayoutManager; import org.apache.fop.traits.MinOptMax; import org.apache.fop.traits.SpaceVal; /** * LayoutManager for a block FO. */ public class BlockLayoutManager extends BlockStackingLayoutManager implements ConditionalElementListener { /** * logging instance */ private static Log log = LogFactory.getLog(BlockLayoutManager.class); private Block curBlockArea; /** Iterator over the child layout managers. */ protected ListIterator proxyLMiter; private int lead = 12000; private Length lineHeight; private int follow = 2000; //private int middleShift = 0; private boolean discardBorderBefore; private boolean discardBorderAfter; private boolean discardPaddingBefore; private boolean discardPaddingAfter; private MinOptMax effSpaceBefore; private MinOptMax effSpaceAfter; /** * Creates a new BlockLayoutManager. * @param inBlock the block FO object to create the layout manager for. */ public BlockLayoutManager(org.apache.fop.fo.flow.Block inBlock) { super(inBlock); proxyLMiter = new ProxyLMiter(); } /** {@inheritDoc} */ public void initialize() { super.initialize(); FontInfo fi = getBlockFO().getFOEventHandler().getFontInfo(); FontTriplet[] fontkeys = getBlockFO().getCommonFont().getFontState(fi); Font initFont = fi.getFontInstance(fontkeys[0], getBlockFO().getCommonFont().fontSize.getValue(this)); lead = initFont.getAscender(); follow = -initFont.getDescender(); //middleShift = -fs.getXHeight() / 2; lineHeight = getBlockFO().getLineHeight().getOptimum(this).getLength(); startIndent = getBlockFO().getCommonMarginBlock().startIndent.getValue(this); endIndent = getBlockFO().getCommonMarginBlock().endIndent.getValue(this); foSpaceBefore = new SpaceVal(getBlockFO().getCommonMarginBlock().spaceBefore, this) .getSpace(); foSpaceAfter = new SpaceVal(getBlockFO().getCommonMarginBlock().spaceAfter, this) .getSpace(); bpUnit = 0; // non-standard extension if (bpUnit == 0) { // use optimum space values adjustedSpaceBefore = getBlockFO().getCommonMarginBlock().spaceBefore.getSpace() .getOptimum(this).getLength().getValue(this); adjustedSpaceAfter = getBlockFO().getCommonMarginBlock().spaceAfter.getSpace() .getOptimum(this).getLength().getValue(this); } else { // use minimum space values adjustedSpaceBefore = getBlockFO().getCommonMarginBlock().spaceBefore.getSpace() .getMinimum(this).getLength().getValue(this); adjustedSpaceAfter = getBlockFO().getCommonMarginBlock().spaceAfter.getSpace() .getMinimum(this).getLength().getValue(this); } } /** {@inheritDoc} */ public List getNextKnuthElements(LayoutContext context, int alignment) { return getNextKnuthElements(context, alignment, null, null, null); } /** {@inheritDoc} */ public List getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack, Position restartPosition, LayoutManager restartAtLM) { resetSpaces(); if (lmStack == null) { return super.getNextKnuthElements(context, alignment); } else { return super.getNextKnuthElements(context, alignment, lmStack, restartPosition, restartAtLM); } } private void resetSpaces() { this.discardBorderBefore = false; this.discardBorderAfter = false; this.discardPaddingBefore = false; this.discardPaddingAfter = false; this.effSpaceBefore = null; this.effSpaceAfter = null; } /** * Proxy iterator for Block LM. * This iterator creates and holds the complete list * of child LMs. * It uses fobjIter as its base iterator. * Block LM's createNextChildLMs uses this iterator * as its base iterator. */ protected class ProxyLMiter extends LMiter { /** * Constructs a proxy iterator for Block LM. */ public ProxyLMiter() { super(BlockLayoutManager.this); listLMs = new java.util.ArrayList(10); } /** * @return true if there are more child lms */ public boolean hasNext() { return (curPos < listLMs.size()) || createNextChildLMs(curPos); } /** * @param pos ... * @return true if new child lms were added */ protected boolean createNextChildLMs(int pos) { List newLMs = createChildLMs(pos + 1 - listLMs.size()); if (newLMs != null) { listLMs.addAll(newLMs); } return pos < listLMs.size(); } } /** * {@inheritDoc} */ public boolean createNextChildLMs(int pos) { while (proxyLMiter.hasNext()) { LayoutManager lm = (LayoutManager) proxyLMiter.next(); if (lm instanceof InlineLevelLayoutManager) { LineLayoutManager lineLM = createLineManager(lm); addChildLM(lineLM); } else { addChildLM(lm); } if (pos < childLMs.size()) { return true; } } return false; } /** * Create a new LineLM, and collect all consecutive * inline generating LMs as its child LMs. * @param firstlm First LM in new LineLM * @return the newly created LineLM */ private LineLayoutManager createLineManager(LayoutManager firstlm) { LineLayoutManager llm; llm = new LineLayoutManager(getBlockFO(), lineHeight, lead, follow); List inlines = new java.util.ArrayList(); inlines.add(firstlm); while (proxyLMiter.hasNext()) { LayoutManager lm = (LayoutManager) proxyLMiter.next(); if (lm instanceof InlineLevelLayoutManager) { inlines.add(lm); } else { proxyLMiter.previous(); break; } } llm.addChildLMs(inlines); return llm; } /** {@inheritDoc} */ public KeepProperty getKeepTogetherProperty() { return getBlockFO().getKeepTogether(); } /** {@inheritDoc} */ public KeepProperty getKeepWithPreviousProperty() { return getBlockFO().getKeepWithPrevious(); } /** {@inheritDoc} */ public KeepProperty getKeepWithNextProperty() { return getBlockFO().getKeepWithNext(); } /** {@inheritDoc} */ public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) { getParentArea(null); // if this will create the first block area in a page // and display-align is after or center, add space before if (layoutContext.getSpaceBefore() > 0) { addBlockSpacing(0.0, new MinOptMax(layoutContext.getSpaceBefore())); } LayoutManager childLM; LayoutManager lastLM = null; LayoutContext lc = new LayoutContext(0); lc.setSpaceAdjust(layoutContext.getSpaceAdjust()); // set space after in the LayoutContext for children if (layoutContext.getSpaceAfter() > 0) { lc.setSpaceAfter(layoutContext.getSpaceAfter()); } PositionIterator childPosIter; // "unwrap" the NonLeafPositions stored in parentIter // and put them in a new list; LinkedList positionList = new LinkedList(); Position pos; boolean spaceBefore = false; boolean spaceAfter = false; Position firstPos = null; Position lastPos = null; while (parentIter.hasNext()) { pos = (Position) parentIter.next(); //log.trace("pos = " + pos.getClass().getName() + "; " + pos); if (pos.getIndex() >= 0) { if (firstPos == null) { firstPos = pos; } lastPos = pos; } Position innerPosition = pos; if (pos instanceof NonLeafPosition) { //Not all elements are wrapped innerPosition = pos.getPosition(); } if (innerPosition == null) { // pos was created by this BlockLM and was inside an element // representing space before or after // this means the space was not discarded if (positionList.size() == 0) { // pos was in the element representing space-before spaceBefore = true; //log.trace(" space before"); } else { // pos was in the element representing space-after spaceAfter = true; //log.trace(" space-after"); } } else if (innerPosition.getLM() == this && !(innerPosition instanceof MappingPosition)) { // pos was created by this BlockLM and was inside a penalty // allowing or forbidding a page break // nothing to do //log.trace(" penalty"); } else { // innerPosition was created by another LM positionList.add(innerPosition); lastLM = innerPosition.getLM(); //log.trace(" " + innerPosition.getClass().getName()); } } addId(); addMarkersToPage(true, isFirst(firstPos), isLast(lastPos)); if (bpUnit == 0) { // the Positions in positionList were inside the elements // created by the LineLM childPosIter = new StackingIter(positionList.listIterator()); } else { // the Positions in positionList were inside the elements // created by the BlockLM in the createUnitElements() method //if (((Position) positionList.getLast()) instanceof // LeafPosition) { // // the last item inside positionList is a LeafPosition // // (a LineBreakPosition, more precisely); this means that // // the whole paragraph is on the same page // childPosIter = new KnuthPossPosIter(storedList, 0, // storedList.size()); //} else { // // the last item inside positionList is a Position; // // this means that the paragraph has been split // // between consecutive pages LinkedList splitList = new LinkedList(); int splitLength = 0; int iFirst = ((MappingPosition) positionList.getFirst()).getFirstIndex(); int iLast = ((MappingPosition) positionList.getLast()).getLastIndex(); // copy from storedList to splitList all the elements from // iFirst to iLast ListIterator storedListIterator = storedList.listIterator(iFirst); while (storedListIterator.nextIndex() <= iLast) { KnuthElement element = (KnuthElement) storedListIterator .next(); // some elements in storedList (i.e. penalty items) were created // by this BlockLM, and must be ignored if (element.getLayoutManager() != this) { splitList.add(element); splitLength += element.getW(); lastLM = element.getLayoutManager(); } } //log.debug("Adding areas from " + iFirst + " to " + iLast); //log.debug("splitLength= " + splitLength // + " (" + neededUnits(splitLength) + " units') " // + (neededUnits(splitLength) * bpUnit - splitLength) // + " spacing"); // add space before and / or after the paragraph // to reach a multiple of bpUnit if (spaceBefore && spaceAfter) { foSpaceBefore = new SpaceVal(getBlockFO() .getCommonMarginBlock().spaceBefore, this).getSpace(); foSpaceAfter = new SpaceVal(getBlockFO() .getCommonMarginBlock().spaceAfter, this).getSpace(); adjustedSpaceBefore = (neededUnits(splitLength + foSpaceBefore.min + foSpaceAfter.min) * bpUnit - splitLength) / 2; adjustedSpaceAfter = neededUnits(splitLength + foSpaceBefore.min + foSpaceAfter.min) * bpUnit - splitLength - adjustedSpaceBefore; } else if (spaceBefore) { adjustedSpaceBefore = neededUnits(splitLength + foSpaceBefore.min) * bpUnit - splitLength; } else { adjustedSpaceAfter = neededUnits(splitLength + foSpaceAfter.min) * bpUnit - splitLength; } //log.debug("spazio prima = " + adjustedSpaceBefore // + " spazio dopo = " + adjustedSpaceAfter + " totale = " + // (adjustedSpaceBefore + adjustedSpaceAfter + splitLength)); childPosIter = new KnuthPossPosIter(splitList, 0, splitList .size()); //} } while ((childLM = childPosIter.getNextChildLM()) != null) { // set last area flag lc.setFlags(LayoutContext.LAST_AREA, (layoutContext.isLastArea() && childLM == lastLM)); lc.setStackLimitBP(layoutContext.getStackLimitBP()); // Add the line areas to Area childLM.addAreas(childPosIter, lc); } addMarkersToPage(false, isFirst(firstPos), isLast(lastPos)); TraitSetter.addSpaceBeforeAfter(curBlockArea, layoutContext.getSpaceAdjust(), effSpaceBefore, effSpaceAfter); flush(); curBlockArea = null; resetSpaces(); //Notify end of block layout manager to the PSLM checkEndOfLayout(lastPos); } /** * Return an Area which can contain the passed childArea. The childArea * may not yet have any content, but it has essential traits set. * In general, if the LayoutManager already has an Area it simply returns * it. Otherwise, it makes a new Area of the appropriate class. * It gets a parent area for its area by calling its parent LM. * Finally, based on the dimensions of the parent area, it initializes * its own area. This includes setting the content IPD and the maximum * BPD. * @param childArea area to get the parent area for * @return the parent area */ public Area getParentArea(Area childArea) { if (curBlockArea == null) { curBlockArea = new Block(); curBlockArea.setIPD(super.getContentAreaIPD()); TraitSetter.addBreaks(curBlockArea, getBlockFO().getBreakBefore(), getBlockFO().getBreakAfter()); // Must get dimensions from parent area //Don't optimize this line away. It can have ugly side-effects. /*Area parentArea =*/ parentLM.getParentArea(curBlockArea); // set traits TraitSetter.setProducerID(curBlockArea, getBlockFO().getId()); TraitSetter.addBorders(curBlockArea, getBlockFO().getCommonBorderPaddingBackground(), discardBorderBefore, discardBorderAfter, false, false, this); TraitSetter.addPadding(curBlockArea, getBlockFO().getCommonBorderPaddingBackground(), discardPaddingBefore, discardPaddingAfter, false, false, this); TraitSetter.addMargins(curBlockArea, getBlockFO().getCommonBorderPaddingBackground(), startIndent, endIndent, this); setCurrentArea(curBlockArea); // ??? for generic operations } return curBlockArea; } /** * {@inheritDoc} */ public void addChildArea(Area childArea) { if (curBlockArea != null) { if (childArea instanceof LineArea) { curBlockArea.addLineArea((LineArea) childArea); } else { curBlockArea.addBlock((Block) childArea); } } } /** * Force current area to be added to parent area. * {@inheritDoc} */ protected void flush() { if (curBlockArea != null) { TraitSetter.addBackground(curBlockArea, getBlockFO().getCommonBorderPaddingBackground(), this); super.flush(); } } /** * convenience method that returns the Block node * @return the block node */ protected org.apache.fop.fo.flow.Block getBlockFO() { return (org.apache.fop.fo.flow.Block) fobj; } // --------- Property Resolution related functions --------- // /** * Returns the IPD of the content area * @return the IPD of the content area */ public int getContentAreaIPD() { if (curBlockArea != null) { return curBlockArea.getIPD(); } return super.getContentAreaIPD(); } /** * Returns the BPD of the content area * @return the BPD of the content area */ public int getContentAreaBPD() { if (curBlockArea != null) { return curBlockArea.getBPD(); } return -1; } /** * {@inheritDoc} */ public boolean getGeneratesBlockArea() { return true; } /** {@inheritDoc} */ public void notifySpace(RelSide side, MinOptMax effectiveLength) { if (RelSide.BEFORE == side) { if (log.isDebugEnabled()) { log.debug(this + ": Space " + side + ", " + this.effSpaceBefore + "-> " + effectiveLength); } this.effSpaceBefore = effectiveLength; } else { if (log.isDebugEnabled()) { log.debug(this + ": Space " + side + ", " + this.effSpaceAfter + "-> " + effectiveLength); } this.effSpaceAfter = effectiveLength; } } /** {@inheritDoc} */ public void notifyBorder(RelSide side, MinOptMax effectiveLength) { if (effectiveLength == null) { if (RelSide.BEFORE == side) { this.discardBorderBefore = true; } else { this.discardBorderAfter = true; } } if (log.isDebugEnabled()) { log.debug(this + ": Border " + side + " -> " + effectiveLength); } } /** {@inheritDoc} */ public void notifyPadding(RelSide side, MinOptMax effectiveLength) { if (effectiveLength == null) { if (RelSide.BEFORE == side) { this.discardPaddingBefore = true; } else { this.discardPaddingAfter = true; } } if (log.isDebugEnabled()) { log.debug(this + ": Padding " + side + " -> " + effectiveLength); } } /** {@inheritDoc} */ public boolean isRestartable() { return true; } }