/*
* 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: InlineStackingLayoutManager.java 426576 2006-07-28 15:44:37Z jeremias $ */
package org.apache.fop.layoutmgr.inline;
import java.util.LinkedList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.HashMap;
import org.apache.fop.fo.FObj;
import org.apache.fop.fo.properties.SpaceProperty;
import org.apache.fop.layoutmgr.AbstractLayoutManager;
import org.apache.fop.layoutmgr.KnuthElement;
import org.apache.fop.layoutmgr.LayoutContext;
import org.apache.fop.layoutmgr.LayoutManager;
import org.apache.fop.layoutmgr.NonLeafPosition;
import org.apache.fop.layoutmgr.Position;
import org.apache.fop.layoutmgr.PositionIterator;
import org.apache.fop.area.Area;
import org.apache.fop.area.inline.Space;
import org.apache.fop.traits.MinOptMax;
/**
* Class modelling the commonalities of layoutmanagers for objects
* which stack children in the inline direction, such as Inline or
* Line. It should not be instantiated directly.
*/
public class InlineStackingLayoutManager extends AbstractLayoutManager
implements InlineLevelLayoutManager {
protected static class StackingIter extends PositionIterator {
StackingIter(Iterator parentIter) {
super(parentIter);
}
protected LayoutManager getLM(Object nextObj) {
return ((Position) nextObj).getLM();
}
protected Position getPos(Object nextObj) {
return ((Position) nextObj);
}
}
/**
* Size of any start or end borders and padding.
*/
private MinOptMax allocIPD = new MinOptMax(0);
/**
* Size of border and padding in BPD (ie, before and after).
*/
protected MinOptMax extraBPD;
private Area currentArea; // LineArea or InlineParent
//private BreakPoss prevBP;
/** The child layout context */
protected LayoutContext childLC;
private boolean bAreaCreated = false;
//private LayoutManager currentLM = null;
/** Used to store previous content IPD for each child LM. */
private HashMap hmPrevIPD = new HashMap();
/**
* Create an inline stacking layout manager.
* This is used for fo's that create areas that
* contain inline areas.
*
* @param node the formatting object that creates the area
*/
protected InlineStackingLayoutManager(FObj node) {
super(node);
extraBPD = new MinOptMax(0);
}
/**
* Set the iterator.
*
* @param iter the iterator for this LM
*/
public void setLMiter(ListIterator iter) {
childLMiter = iter;
}
/**
* Returns the extra IPD needed for any leading or trailing fences for the
* current area.
* @param bNotFirst true if not the first area for this layout manager
* @param bNotLast true if not the last area for this layout manager
* @return the extra IPD as a MinOptMax spec
*/
protected MinOptMax getExtraIPD(boolean bNotFirst, boolean bNotLast) {
return new MinOptMax(0);
}
/**
* Indication if the current area has a leading fence.
* @param bNotFirst true if not the first area for this layout manager
* @return the leading fence flag
*/
protected boolean hasLeadingFence(boolean bNotFirst) {
return false;
}
/**
* Indication if the current area has a trailing fence.
* @param bNotLast true if not the last area for this layout manager
* @return the trailing fence flag
*/
protected boolean hasTrailingFence(boolean bNotLast) {
return false;
}
/**
* Get the space at the start of the inline area.
* @return the space property describing the space
*/
protected SpaceProperty getSpaceStart() {
return null;
}
/**
* Get the space at the end of the inline area.
* @return the space property describing the space
*/
protected SpaceProperty getSpaceEnd() {
return null;
}
/**
* Reset position for returning next BreakPossibility.
* @param prevPos a Position returned by this layout manager
* representing a potential break decision.
*/
public void resetPosition(Position prevPos) {
if (prevPos != null) {
// ASSERT (prevPos.getLM() == this)
if (prevPos.getLM() != this) {
//getLogger().error(
// "InlineStackingLayoutManager.resetPosition: " +
// "LM mismatch!!!");
}
// Back up the child LM Position
Position childPos = prevPos.getPosition();
reset(childPos);
/*
if (prevBP != null
&& prevBP.getLayoutManager() != childPos.getLM()) {
childLC = null;
}
prevBP = new BreakPoss(childPos);
*/
} else {
// Backup to start of first child layout manager
//prevBP = null;
// super.resetPosition(prevPos);
reset(prevPos);
// If any areas created, we are restarting!
bAreaCreated = false;
}
// Do we need to reset some context like pending or prevContent?
// What about prevBP?
}
/**
* TODO: Explain this method
* @param lm ???
* @return ???
*/
protected MinOptMax getPrevIPD(LayoutManager lm) {
return (MinOptMax) hmPrevIPD.get(lm);
}
/**
* Clear the previous IPD calculation.
*/
protected void clearPrevIPD() {
hmPrevIPD.clear();
}
/**
* This method is called by addAreas() so IDs can be added to a page for FOs that
* support the 'id' property.
*/
protected void addId() {
// Do nothing here, overriden in subclasses that have an 'id' property.
}
/**
* Returns the current area.
* @return the current area
*/
protected Area getCurrentArea() {
return currentArea;
}
/**
* Set the current area.
* @param area the current area
*/
protected void setCurrentArea(Area area) {
currentArea = area;
}
/**
* Trait setter to be overridden by subclasses.
* @param bNotFirst true if this is not the first child area added
* @param bNotLast true if this is not the last child area added
*/
protected void setTraits(boolean bNotFirst, boolean bNotLast) {
}
/**
* Set the current child layout context
* @param lc the child layout context
*/
protected void setChildContext(LayoutContext lc) {
childLC = lc;
}
/**
* Current child layout context
* @return the current child layout context
*/
protected LayoutContext getContext() {
return childLC;
}
/**
* Adds a space to the area
* @param parentArea the area to which to add the space
* @param spaceRange the space range specifier
* @param dSpaceAdjust the factor by which to stretch or shrink the space
*/
protected void addSpace(Area parentArea, MinOptMax spaceRange,
double dSpaceAdjust) {
if (spaceRange != null) {
int iAdjust = spaceRange.opt;
if (dSpaceAdjust > 0.0) {
// Stretch by factor
iAdjust += (int) ((double) (spaceRange.max
- spaceRange.opt) * dSpaceAdjust);
} else if (dSpaceAdjust < 0.0) {
// Shrink by factor
iAdjust += (int) ((double) (spaceRange.opt
- spaceRange.min) * dSpaceAdjust);
}
if (iAdjust != 0) {
//getLogger().debug("Add leading space: " + iAdjust);
Space ls = new Space();
ls.setIPD(iAdjust);
parentArea.addChildArea(ls);
}
}
}
/** @see InlineLevelLayoutManager#addALetterSpaceTo(List) */
public List addALetterSpaceTo(List oldList) {
// old list contains only a box, or the sequence: box penalty glue box
ListIterator oldListIterator = oldList.listIterator();
KnuthElement element = null;
// "unwrap" the Position stored in each element of oldList
while (oldListIterator.hasNext()) {
element = (KnuthElement) oldListIterator.next();
element.setPosition(((NonLeafPosition)element.getPosition()).getPosition());
}
// The last element may not have a layout manager (its position == null);
// this may happen if it is a padding box; see bug 39571.
InlineLevelLayoutManager LM =
(InlineLevelLayoutManager) element.getLayoutManager();
if (LM != null) {
oldList = LM.addALetterSpaceTo(oldList);
}
// "wrap" again the Position stored in each element of oldList
oldListIterator = oldList.listIterator();
while (oldListIterator.hasNext()) {
element = (KnuthElement) oldListIterator.next();
element.setPosition(notifyPos(new NonLeafPosition(this, element.getPosition())));
}
return oldList;
}
/**
* remove the AreaInfo object represented by the given elements,
* so that it won't generate any element when getChangedKnuthElements
* will be called
*
* @param oldList the elements representing the word space
*/
public void removeWordSpace(List oldList) {
ListIterator oldListIterator = oldList.listIterator();
KnuthElement element = null;
// "unwrap" the Position stored in each element of oldList
while (oldListIterator.hasNext()) {
element = (KnuthElement) oldListIterator.next();
element.setPosition(((NonLeafPosition)element.getPosition()).getPosition());
}
((InlineLevelLayoutManager)
element.getLayoutManager()).removeWordSpace(oldList);
}
/** @see InlineLevelLayoutManager#getWordChars(StringBuffer, Position) */
public void getWordChars(StringBuffer sbChars, Position pos) {
Position newPos = ((NonLeafPosition) pos).getPosition();
((InlineLevelLayoutManager)
newPos.getLM()).getWordChars(sbChars, newPos);
}
/** @see InlineLevelLayoutManager#hyphenate(Position, HyphContext) */
public void hyphenate(Position pos, HyphContext hc) {
Position newPos = ((NonLeafPosition) pos).getPosition();
((InlineLevelLayoutManager)
newPos.getLM()).hyphenate(newPos, hc);
}
/** @see InlineLevelLayoutManager#applyChanges(List) */
public boolean applyChanges(List oldList) {
// "unwrap" the Positions stored in the elements
ListIterator oldListIterator = oldList.listIterator();
KnuthElement oldElement;
while (oldListIterator.hasNext()) {
oldElement = (KnuthElement) oldListIterator.next();
oldElement.setPosition
(((NonLeafPosition) oldElement.getPosition()).getPosition());
}
// reset the iterator
oldListIterator = oldList.listIterator();
InlineLevelLayoutManager prevLM = null;
InlineLevelLayoutManager currLM;
int fromIndex = 0;
boolean bSomethingChanged = false;
while (oldListIterator.hasNext()) {
oldElement = (KnuthElement) oldListIterator.next();
currLM = (InlineLevelLayoutManager) oldElement.getLayoutManager();
// initialize prevLM
if (prevLM == null) {
prevLM = currLM;
}
if (currLM != prevLM || !oldListIterator.hasNext()) {
if (prevLM == this || currLM == this) {
prevLM = currLM;
} else if (oldListIterator.hasNext()) {
bSomethingChanged
= prevLM.applyChanges(oldList.subList(fromIndex
, oldListIterator.previousIndex()))
|| bSomethingChanged;
prevLM = currLM;
fromIndex = oldListIterator.previousIndex();
} else if (currLM == prevLM) {
bSomethingChanged
= prevLM.applyChanges(oldList.subList(fromIndex, oldList.size()))
|| bSomethingChanged;
} else {
bSomethingChanged
= prevLM.applyChanges(oldList.subList(fromIndex
, oldListIterator.previousIndex()))
|| bSomethingChanged;
if (currLM != null) {
bSomethingChanged
= currLM.applyChanges(oldList.subList(oldListIterator.previousIndex()
, oldList.size()))
|| bSomethingChanged;
}
}
}
}
// "wrap" again the Positions stored in the elements
oldListIterator = oldList.listIterator();
while (oldListIterator.hasNext()) {
oldElement = (KnuthElement) oldListIterator.next();
oldElement.setPosition
(notifyPos(new NonLeafPosition(this, oldElement.getPosition())));
}
return bSomethingChanged;
}
/**
* @see org.apache.fop.layoutmgr.LayoutManager#getChangedKnuthElements(List, int)
*/
public LinkedList getChangedKnuthElements(List oldList, int alignment) {
// "unwrap" the Positions stored in the elements
ListIterator oldListIterator = oldList.listIterator();
KnuthElement oldElement;
while (oldListIterator.hasNext()) {
oldElement = (KnuthElement) oldListIterator.next();
oldElement.setPosition
(((NonLeafPosition) oldElement.getPosition()).getPosition());
}
// reset the iterator
oldListIterator = oldList.listIterator();
KnuthElement returnedElement;
LinkedList returnedList = new LinkedList();
LinkedList returnList = new LinkedList();
InlineLevelLayoutManager prevLM = null;
InlineLevelLayoutManager currLM;
int fromIndex = 0;
while (oldListIterator.hasNext()) {
oldElement = (KnuthElement) oldListIterator.next();
currLM = (InlineLevelLayoutManager) oldElement.getLayoutManager();
if (prevLM == null) {
prevLM = currLM;
}
if (currLM != prevLM || !oldListIterator.hasNext()) {
if (oldListIterator.hasNext()) {
returnedList.addAll
(prevLM.getChangedKnuthElements
(oldList.subList(fromIndex,
oldListIterator.previousIndex()),
/*flaggedPenalty,*/ alignment));
prevLM = currLM;
fromIndex = oldListIterator.previousIndex();
} else if (currLM == prevLM) {
returnedList.addAll
(prevLM.getChangedKnuthElements
(oldList.subList(fromIndex, oldList.size()),
/*flaggedPenalty,*/ alignment));
} else {
returnedList.addAll
(prevLM.getChangedKnuthElements
(oldList.subList(fromIndex,
oldListIterator.previousIndex()),
/*flaggedPenalty,*/ alignment));
if (currLM != null) {
returnedList.addAll
(currLM.getChangedKnuthElements
(oldList.subList(oldListIterator.previousIndex(),
oldList.size()),
/*flaggedPenalty,*/ alignment));
}
}
}
}
// "wrap" the Position stored in each element of returnedList
ListIterator listIter = returnedList.listIterator();
while (listIter.hasNext()) {
returnedElement = (KnuthElement) listIter.next();
returnedElement.setPosition
(notifyPos(new NonLeafPosition(this, returnedElement.getPosition())));
returnList.add(returnedElement);
}
return returnList;
}
}