blob: 2dd18e4ee381e5019d73d02fe214705fab5e3ad0 [file] [log] [blame]
/*
* 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);
if (fo == null) {
throw new IllegalStateException("Null formatting object found.");
}
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;
}
ListIterator<LayoutManager> iter = newLMs.listIterator();
while (iter.hasNext()) {
addChildLM(iter.next());
}
}
/**
* 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);
}
}