/* * 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.ArrayList; import java.util.List; import java.util.ListIterator; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xmlgraphics.util.QName; import org.apache.fop.area.Area; import org.apache.fop.area.AreaTreeObject; import org.apache.fop.area.PageViewport; import org.apache.fop.fo.Constants; import org.apache.fop.fo.FONode; import org.apache.fop.fo.FObj; import org.apache.fop.fo.flow.Marker; import org.apache.fop.fo.flow.RetrieveMarker; /** * The base class for most LayoutManagers. */ public abstract class AbstractLayoutManager extends AbstractBaseLayoutManager implements Constants { /** logging instance */ private static Log log = LogFactory.getLog(AbstractLayoutManager.class); /** Parent LayoutManager for this LayoutManager */ protected LayoutManager parentLayoutManager; /** List of child LayoutManagers */ protected List<LayoutManager> childLMs; /** Iterator for child LayoutManagers */ protected ListIterator fobjIter; /** Marker map for markers related to this LayoutManager */ private Map<String, Marker> markers; /** True if this LayoutManager has handled all of its content. */ private boolean isFinished; /** child LM during getNextKnuthElement phase */ protected LayoutManager curChildLM; /** child LM iterator during getNextKnuthElement phase */ protected ListIterator<LayoutManager> childLMiter; private int lastGeneratedPosition = -1; private int smallestPosNumberChecked = Integer.MAX_VALUE; private boolean preserveChildrenAtEndOfLayout; /** * Abstract layout manager. */ public AbstractLayoutManager() { } /** * Abstract layout manager. * * @param fo the formatting object for this layout manager */ public AbstractLayoutManager(FObj fo) { super(fo); markers = fo.getMarkers(); fobjIter = fo.getChildNodes(); childLMiter = new LMiter(this); } /** {@inheritDoc} */ public void setParent(LayoutManager lm) { this.parentLayoutManager = lm; } /** {@inheritDoc} */ public LayoutManager getParent() { return this.parentLayoutManager; } /** {@inheritDoc} */ public void initialize() { // Empty } /** * Return currently active child LayoutManager or null if * all children have finished layout. * Note: child must implement LayoutManager! If it doesn't, skip it * and print a warning. * @return the current child LayoutManager */ protected LayoutManager getChildLM() { if (curChildLM != null && !curChildLM.isFinished()) { return curChildLM; } if (childLMiter.hasNext()) { curChildLM = childLMiter.next(); curChildLM.initialize(); return curChildLM; } return null; } /** * Set currently active child layout manager. * @param childLM the child layout manager */ protected void setCurrentChildLM(LayoutManager childLM) { curChildLM = childLM; childLMiter = new LMiter(this); do { curChildLM = childLMiter.next(); } while (curChildLM != childLM); } /** * Return indication if getChildLM will return another LM. * @return true if another child LM is still available */ protected boolean hasNextChildLM() { return childLMiter.hasNext(); } /** * Tell whether this LayoutManager has handled all of its content. * @return True if there are no more break possibilities, * ie. the last one returned represents the end of the content. */ public boolean isFinished() { return isFinished; } /** * Set the flag indicating the LayoutManager has handled all of its content. * @param fin the flag value to be set */ public void setFinished(boolean fin) { isFinished = fin; } /** {@inheritDoc} */ public void addAreas(PositionIterator posIter, LayoutContext context) { } /** {@inheritDoc} */ public List getNextKnuthElements(LayoutContext context, int alignment) { log.warn("null implementation of getNextKnuthElements() called!"); setFinished(true); return null; } /** {@inheritDoc} */ public List getChangedKnuthElements(List oldList, int alignment) { log.warn("null implementation of getChangeKnuthElement() called!"); return null; } /** * 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 the child area for which the parent area is wanted * @return the parent area for the given child */ public Area getParentArea(Area childArea) { return null; } /** * Add a child area to the current area. If this causes the maximum * dimension of the current area to be exceeded, the parent LM is called * to add it. * @param childArea the child area to be added */ public void addChildArea(Area childArea) { } /** * Create the LM instances for the children of the * formatting object being handled by this LM. * @param size the requested number of child LMs * @return the list with the preloaded child LMs */ protected List<LayoutManager> createChildLMs(int size) { if (fobjIter == null) { return null; } List<LayoutManager> newLMs = new ArrayList<LayoutManager>(size); while (fobjIter.hasNext() && newLMs.size() < size) { Object theobj = fobjIter.next(); if (theobj instanceof FONode) { FONode foNode = (FONode) theobj; if (foNode instanceof RetrieveMarker) { foNode = getPSLM().resolveRetrieveMarker( (RetrieveMarker) foNode); } if (foNode != null) { getPSLM().getLayoutManagerMaker() .makeLayoutManagers(foNode, newLMs); } } } return newLMs; } /** {@inheritDoc} */ public PageSequenceLayoutManager getPSLM() { return parentLayoutManager.getPSLM(); } /** * @see PageSequenceLayoutManager#getCurrentPage() * @return the {@link Page} instance corresponding to the current page */ public Page getCurrentPage() { return getPSLM().getCurrentPage(); } /** @return the current page viewport */ public PageViewport getCurrentPV() { return getPSLM().getCurrentPage().getPageViewport(); } /** {@inheritDoc} */ public boolean createNextChildLMs(int pos) { List<LayoutManager> newLMs = createChildLMs(pos + 1 - childLMs.size()); addChildLMs(newLMs); return pos < childLMs.size(); } /** {@inheritDoc} */ public List<LayoutManager> getChildLMs() { if (childLMs == null) { childLMs = new java.util.ArrayList<LayoutManager>(10); } return childLMs; } /** {@inheritDoc} */ public void addChildLM(LayoutManager lm) { if (lm == null) { return; } lm.setParent(this); if (childLMs == null) { childLMs = new java.util.ArrayList<LayoutManager>(10); } childLMs.add(lm); if (log.isTraceEnabled()) { log.trace(this.getClass().getName() + ": Adding child LM " + lm.getClass().getName()); } } /** {@inheritDoc} */ public void addChildLMs(List newLMs) { if (newLMs == null || newLMs.size() == 0) { return; } for (LayoutManager newLM : (Iterable<LayoutManager>) newLMs) { addChildLM(newLM); } } /** * Adds a Position to the Position participating in the first|last determination by assigning * it a unique position index. * @param pos the Position * @return the same Position but with a position index */ public Position notifyPos(Position pos) { if (pos.getIndex() >= 0) { throw new IllegalStateException("Position already got its index"); } pos.setIndex(++lastGeneratedPosition); return pos; } private void verifyNonNullPosition(Position pos) { if (pos == null || pos.getIndex() < 0) { throw new IllegalArgumentException( "Only non-null Positions with an index can be checked"); } } /** * Indicates whether the given Position is the first area-generating Position of this LM. * @param pos the Position (must be one with a position index) * @return True if it is the first Position */ public boolean isFirst(Position pos) { //log.trace("isFirst() smallestPosNumberChecked=" + smallestPosNumberChecked + " " + pos); verifyNonNullPosition(pos); if (pos.getIndex() == this.smallestPosNumberChecked) { return true; } else if (pos.getIndex() < this.smallestPosNumberChecked) { this.smallestPosNumberChecked = pos.getIndex(); return true; } else { return false; } } /** * Indicates whether the given Position is the last area-generating Position of this LM. * @param pos the Position (must be one with a position index) * @return True if it is the last Position */ public boolean isLast(Position pos) { verifyNonNullPosition(pos); return (pos.getIndex() == this.lastGeneratedPosition && isFinished()); } public boolean hasLineAreaDescendant() { if (childLMs == null || childLMs.isEmpty()) { return false; } else { for (LayoutManager childLM : childLMs) { if (childLM.hasLineAreaDescendant()) { return true; } } } return false; } public int getBaselineOffset() { if (childLMs != null) { for (LayoutManager childLM : childLMs) { if (childLM.hasLineAreaDescendant()) { return childLM.getBaselineOffset(); } } } throw newNoLineAreaDescendantException(); } protected IllegalStateException newNoLineAreaDescendantException() { return new IllegalStateException("getBaselineOffset called on an object that has no line-area descendant"); } /** * Transfers foreign attributes from the formatting object to the area. * @param targetArea the area to set the attributes on */ protected void transferForeignAttributes(AreaTreeObject targetArea) { Map<QName, String> atts = fobj.getForeignAttributes(); targetArea.setForeignAttributes(atts); } /** * Transfers extension attachments from the formatting object to the area. * @param targetArea the area to set the extensions on */ protected void transferExtensionAttachments(AreaTreeObject targetArea) { if (fobj.hasExtensionAttachments()) { targetArea.setExtensionAttachments(fobj.getExtensionAttachments()); } } /** * Transfers extensions (foreign attributes and extension attachments) from * the formatting object to the area. * @param targetArea the area to set the extensions on */ protected void transferExtensions(AreaTreeObject targetArea) { transferForeignAttributes(targetArea); transferExtensionAttachments(targetArea); } /** * Registers the FO's markers on the current PageViewport, and if applicable on the parent TableLM. * * @param isStarting boolean indicating whether the markers qualify as 'starting' * @param isFirst boolean indicating whether the markers qualify as 'first' * @param isLast boolean indicating whether the markers qualify as 'last' */ protected void registerMarkers(boolean isStarting, boolean isFirst, boolean isLast) { if (this.markers != null) { getCurrentPV().registerMarkers( this.markers, isStarting, isFirst, isLast); possiblyRegisterMarkersForTables(markers, isStarting, isFirst, isLast); } } /** * Registers the FO's id on the current PageViewport */ protected void addId() { if (fobj != null) { getPSLM().addIDToPage(fobj.getId()); } } /** * Notifies the {@link PageSequenceLayoutManager} that layout * for this LM has ended. */ protected void notifyEndOfLayout() { if (fobj != null) { getPSLM().notifyEndOfLayout(fobj.getId()); } } /** * Checks to see if the incoming {@link Position} * is the last one for this LM, and if so, calls * {@link #notifyEndOfLayout()} and cleans up. * * @param pos the {@link Position} to check */ protected void checkEndOfLayout(Position pos) { if (pos != null && pos.getLM() == this && this.isLast(pos)) { notifyEndOfLayout(); if (!preserveChildrenAtEndOfLayout) { // References to the child LMs are no longer needed childLMs = null; curChildLM = null; childLMiter = null; } /* markers that qualify have been transferred to the page */ markers = null; /* References to the FO's children can be released if the * LM is a descendant of the FlowLM. For static-content * the FO may still be needed on following pages. */ LayoutManager lm = this.parentLayoutManager; while (!(lm instanceof FlowLayoutManager || lm instanceof PageSequenceLayoutManager)) { lm = lm.getParent(); } if (lm instanceof FlowLayoutManager && !preserveChildrenAtEndOfLayout) { fobj.clearChildNodes(); fobjIter = null; } } } /* * Preserves the children LMs at the end of layout. This is necessary if the layout is expected to be * repeated, as when using retrieve-table-markers. */ public void preserveChildrenAtEndOfLayout() { preserveChildrenAtEndOfLayout = true; } /** {@inheritDoc} */ @Override public String toString() { return (super.toString() + (fobj != null ? "{fobj = " + fobj.toString() + "}" : "")); } /** {@inheritDoc} */ @Override public void reset() { isFinished = false; curChildLM = null; childLMiter = new LMiter(this); /* Reset all the children LM that have been created so far. */ for (LayoutManager childLM : getChildLMs()) { childLM.reset(); } if (fobj != null) { markers = fobj.getMarkers(); } lastGeneratedPosition = -1; } public void recreateChildrenLMs() { childLMs = new ArrayList(); isFinished = false; if (fobj == null) { return; } fobjIter = fobj.getChildNodes(); int position = 0; while (createNextChildLMs(position++)) { // } childLMiter = new LMiter(this); for (LMiter iter = new LMiter(this); iter.hasNext();) { AbstractBaseLayoutManager alm = (AbstractBaseLayoutManager) iter.next(); alm.initialize(); alm.recreateChildrenLMs(); alm.preserveChildrenAtEndOfLayout(); } curChildLM = getChildLM(); } protected void possiblyRegisterMarkersForTables(Map<String, Marker> markers, boolean isStarting, boolean isFirst, boolean isLast) { LayoutManager lm = this.parentLayoutManager; if (lm instanceof FlowLayoutManager || lm instanceof PageSequenceLayoutManager || !(lm instanceof AbstractLayoutManager)) { return; } ((AbstractLayoutManager) lm).possiblyRegisterMarkersForTables(markers, isStarting, isFirst, isLast); } public boolean handlingFloat() { if (parentLayoutManager != null && parentLayoutManager instanceof AbstractLayoutManager) { return ((AbstractLayoutManager) parentLayoutManager).handlingFloat(); } return false; } }