/*
* Copyright 1999-2005 The Apache Software Foundation.
*
* Licensed 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: LineLayoutManager.java,v 1.17 2004/04/02 10:38:29 cbowditch Exp $ */
package org.apache.fop.layoutmgr.inline;
import org.apache.fop.datatypes.Length;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.flow.Block;
import org.apache.fop.fo.properties.CommonHyphenation;
import org.apache.fop.hyphenation.Hyphenation;
import org.apache.fop.hyphenation.Hyphenator;
import org.apache.fop.layoutmgr.BlockLevelLayoutManager;
import org.apache.fop.layoutmgr.BreakElement;
import org.apache.fop.layoutmgr.BreakingAlgorithm;
import org.apache.fop.layoutmgr.ElementListObserver;
import org.apache.fop.layoutmgr.KnuthBlockBox;
import org.apache.fop.layoutmgr.KnuthBox;
import org.apache.fop.layoutmgr.KnuthElement;
import org.apache.fop.layoutmgr.KnuthGlue;
import org.apache.fop.layoutmgr.KnuthPenalty;
import org.apache.fop.layoutmgr.KnuthPossPosIter;
import org.apache.fop.layoutmgr.KnuthSequence;
import org.apache.fop.layoutmgr.LayoutContext;
import org.apache.fop.layoutmgr.LayoutManager;
import org.apache.fop.layoutmgr.LeafPosition;
import org.apache.fop.layoutmgr.NonLeafPosition;
import org.apache.fop.layoutmgr.Position;
import org.apache.fop.layoutmgr.PositionIterator;
import org.apache.fop.layoutmgr.SpaceSpecifier;
import org.apache.fop.area.Area;
import org.apache.fop.area.LineArea;
import org.apache.fop.area.inline.InlineArea;
import java.util.ListIterator;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
import org.apache.fop.area.Trait;
import org.apache.fop.fonts.Font;
import org.apache.fop.layoutmgr.BreakingAlgorithm.KnuthNode;
import org.apache.fop.layoutmgr.inline.InlineStackingLayoutManager.StackingIter;
import org.apache.fop.traits.MinOptMax;
/**
* LayoutManager for lines. It builds one or more lines containing
* inline areas generated by its sub layout managers.
* A break is found for each line which may contain one of more
* breaks from the child layout managers.
* Once a break is found then it is return for the parent layout
* manager to handle.
* When the areas are being added to the page this manager
* creates a line area to contain the inline areas added by the
* child layout managers.
*/
public class LineLayoutManager extends InlineStackingLayoutManager
implements BlockLevelLayoutManager {
private Block fobj;
public void initialize() {
textAlignment = fobj.getTextAlign();
textAlignmentLast = fobj.getTextAlignLast();
textIndent = fobj.getTextIndent();
lastLineEndIndent = fobj.getLastLineEndIndent();
hyphenationProperties = fobj.getCommonHyphenation();
wrapOption = fobj.getWrapOption();
//
effectiveAlignment = getEffectiveAlignment(textAlignment, textAlignmentLast);
}
private int getEffectiveAlignment(int alignment, int alignmentLast) {
if (textAlignment != EN_JUSTIFY && textAlignmentLast == EN_JUSTIFY) {
return 0;
} else {
return textAlignment;
}
}
/**
* Private class to store information about inline breaks.
* Each value holds the start and end indexes into a List of
* inline break positions.
*/
private static class LineBreakPosition extends LeafPosition {
private int iParIndex; // index of the Paragraph this Position refers to
private int availableShrink;
private int availableStretch;
private int difference;
private double dAdjust; // Percentage to adjust (stretch or shrink)
private double ipdAdjust; // Percentage to adjust (stretch or shrink)
private int startIndent;
private int lineHeight;
private int lineWidth;
private int spaceBefore;
private int spaceAfter;
private int baseline;
LineBreakPosition(LayoutManager lm, int index, int iBreakIndex,
int shrink, int stretch, int diff,
double ipdA, double adjust, int ind,
int lh, int lw, int sb, int sa, int bl) {
super(lm, iBreakIndex);
availableShrink = shrink;
availableStretch = stretch;
difference = diff;
iParIndex = index;
ipdAdjust = ipdA;
dAdjust = adjust;
startIndent = ind;
lineHeight = lh;
lineWidth = lw;
spaceBefore = sb;
spaceAfter = sa;
baseline = bl;
}
}
private int textAlignment = EN_JUSTIFY;
private int textAlignmentLast;
private int effectiveAlignment;
private Length textIndent;
private Length lastLineEndIndent;
private int iIndents = 0;
private CommonHyphenation hyphenationProperties;
private int wrapOption = EN_WRAP;
//private LayoutProps layoutProps;
private Length lineHeight;
private int lead;
private int follow;
private AlignmentContext alignmentContext = null;
private List knuthParagraphs = null;
private int iReturnedLBP = 0;
private int iStartElement = 0;
private int iEndElement = 0;
// parameters of Knuth's algorithm:
// penalty value for flagged penalties
private int flaggedPenalty = 50;
private LineLayoutPossibilities lineLayouts;
private List lineLayoutsList;
private int iLineWidth = 0;
/**
* this constant is used to create elements when text-align is center:
* every TextLM descendant of LineLM must use the same value,
* otherwise the line breaking algorithm does not find the right
* break point
*/
public static final int DEFAULT_SPACE_WIDTH = 3336;
/**
* This class is used to remember
* which was the first element in the paragraph
* returned by each LM.
*/
private class Update {
private InlineLevelLayoutManager inlineLM;
private int iFirstIndex;
public Update(InlineLevelLayoutManager lm, int index) {
inlineLM = lm;
iFirstIndex = index;
}
}
// this class represents a paragraph
private class Paragraph extends KnuthSequence {
// space at the end of the last line (in millipoints)
private MinOptMax lineFiller;
private int textAlignment;
private int textAlignmentLast;
private int textIndent;
private int lastLineEndIndent;
private int lineWidth;
// the LM which created the paragraph
private LineLayoutManager layoutManager;
public Paragraph(LineLayoutManager llm, int alignment, int alignmentLast,
int indent, int endIndent) {
super(true);
layoutManager = llm;
textAlignment = alignment;
textAlignmentLast = alignmentLast;
textIndent = indent;
lastLineEndIndent = endIndent;
}
public void startParagraph(int lw) {
lineWidth = lw;
startSequence();
}
public void startSequence() {
// set the minimum amount of empty space at the end of the
// last line
if (textAlignment == EN_CENTER) {
lineFiller = new MinOptMax(lastLineEndIndent);
} else {
lineFiller = new MinOptMax(lastLineEndIndent, lastLineEndIndent, lineWidth);
}
// add auxiliary elements at the beginning of the paragraph
if (textAlignment == EN_CENTER && textAlignmentLast != EN_JUSTIFY) {
this.add(new KnuthGlue(0, 3 * DEFAULT_SPACE_WIDTH, 0,
null, false));
ignoreAtStart++;
}
// add the element representing text indentation
// at the beginning of the first paragraph
if (knuthParagraphs.size() == 0
&& fobj.getTextIndent().getValue(layoutManager) != 0) {
this.add(new KnuthInlineBox(fobj.getTextIndent().getValue(layoutManager), null,
null, false));
ignoreAtStart++;
}
}
public void endParagraph() {
KnuthSequence finishedPar = this.endSequence();
if (finishedPar != null) {
knuthParagraphs.add(finishedPar);
}
}
public KnuthSequence endSequence() {
// remove elements representig spaces at the end of the paragraph
removeElementsForTrailingSpaces();
if (this.size() > ignoreAtStart) {
if (textAlignment == EN_CENTER
&& textAlignmentLast != EN_JUSTIFY) {
this.add(new KnuthGlue(0, 3 * DEFAULT_SPACE_WIDTH, 0,
null, false));
this.add(new KnuthPenalty(lineFiller.opt, -KnuthElement.INFINITE,
false, null, false));
ignoreAtEnd = 2;
} else if (textAlignmentLast != EN_JUSTIFY) {
// add the elements representing the space
// at the end of the last line
// and the forced break
this.add(new KnuthPenalty(0, KnuthElement.INFINITE,
false, null, false));
this.add(new KnuthGlue(0,
lineFiller.max - lineFiller.opt,
lineFiller.opt - lineFiller.min, null, false));
this.add(new KnuthPenalty(lineFiller.opt, -KnuthElement.INFINITE,
false, null, false));
ignoreAtEnd = 3;
} else {
// add only the element representing the forced break
this.add(new KnuthPenalty(lineFiller.opt, -KnuthElement.INFINITE,
false, null, false));
ignoreAtEnd = 1;
}
return this;
} else {
this.clear();
return null;
}
}
/**
* remove elements representing spaces at the end of the paragraph;
* the text could have one or more trailing spaces, each of them
* being either a normal space or a non-breaking space;
* according to the alignment, the sub-sequence of elements
* representing each space has a different "pattern"
*/
private void removeElementsForTrailingSpaces() {
LinkedList removedElements;
InlineLevelLayoutManager inlineLM;
int effectiveAlignment = getEffectiveAlignment(textAlignment, textAlignmentLast);
while (this.size() > ignoreAtStart
&& ((KnuthElement) this.get(this.size() - 1)).isGlue()) {
removedElements = new LinkedList();
inlineLM = (InlineLevelLayoutManager)
((KnuthElement) this.get(this.size() - 1)).getLayoutManager();
if (effectiveAlignment == EN_CENTER
|| this.size() > 6
&& ((KnuthElement) this.get(this.size() - 6)).isGlue()
&& ((KnuthElement) this.get(this.size() - 5)).isPenalty()
&& ((KnuthElement) this.get(this.size() - 4)).isGlue()
&& ((KnuthElement) this.get(this.size() - 3)).isBox()
&& ((KnuthElement) this.get(this.size() - 2)).isPenalty()) {
// centered text (or text with inline borders and padding): the pattern is
// <glue> <penaly> <glue> <box> <penaly> <glue>
removedElements.addFirst((KnuthGlue) this.remove(this.size() - 1));
removedElements.addFirst((KnuthPenalty) this.remove(this.size() - 1));
removedElements.addFirst((KnuthBox) this.remove(this.size() - 1));
removedElements.addFirst((KnuthGlue) this.remove(this.size() - 1));
removedElements.addFirst((KnuthPenalty) this.remove(this.size() - 1));
removedElements.addFirst((KnuthGlue) this.remove(this.size() - 1));
} else if (effectiveAlignment == EN_START || effectiveAlignment == EN_END) {
// left- or right-aligned text: the pattern is
// <glue> <penalty> <glue>
removedElements.addFirst((KnuthGlue) this.remove(this.size() - 1));
removedElements.addFirst((KnuthPenalty) this.remove(this.size() - 1));
removedElements.addFirst((KnuthGlue) this.remove(this.size() - 1));
} else {
// justified text: the pattern is
// <glue>
removedElements.add((KnuthGlue) this.remove(this.size() - 1));
}
// if the space was a non-breaking one, there is also a penalty
if (this.size() > ignoreAtStart
&& ((KnuthElement) this.get(this.size() - 1)).isPenalty()) {
removedElements.addFirst((KnuthPenalty) this.remove(this.size() - 1));
}
inlineLM.removeWordSpace(removedElements);
}
}
private void addALetterSpace() {
KnuthBox prevBox = (KnuthBox) removeLast();
LinkedList oldList = new LinkedList();
// if there are two consecutive KnuthBoxes the
// first one does not represent a whole word,
// so it must be given one more letter space
if (!prevBox.isAuxiliary()) {
// if letter spacing is constant,
// only prevBox needs to be replaced;
oldList.add(prevBox);
} else {
// prevBox is the last element
// in the sub-sequence
// <box> <aux penalty> <aux glue> <aux box>
// the letter space is added to <aux glue>,
// while the other elements are not changed
oldList.add(prevBox);
oldList.addFirst((KnuthGlue) removeLast());
oldList.addFirst((KnuthPenalty) removeLast());
oldList.addFirst((KnuthBox) removeLast());
}
// adding a letter space could involve, according to the text
// represented by oldList, replacing a glue element or adding
// new elements
addAll(((InlineLevelLayoutManager)
prevBox.getLayoutManager())
.addALetterSpaceTo(oldList));
if (((KnuthInlineBox) prevBox).isAnchor()) {
// prevBox represents a footnote citation: copy footnote info
// from prevBox to the new box
KnuthInlineBox newBox = (KnuthInlineBox) getLast();
newBox.setFootnoteBodyLM(((KnuthInlineBox) prevBox).getFootnoteBodyLM());
}
}
}
private class LineBreakingAlgorithm extends BreakingAlgorithm {
private LineLayoutManager thisLLM;
private int pageAlignment;
private int activePossibility;
private int addedPositions;
private int textIndent;
private int fillerMinWidth;
private int lineHeight;
private int lead;
private int follow;
private int maxDiff;
private static final double MAX_DEMERITS = 10e6;
public LineBreakingAlgorithm (int pageAlign,
int textAlign, int textAlignLast,
int indent, int fillerWidth,
int lh, int ld, int fl, boolean first,
LineLayoutManager llm) {
super(textAlign, textAlignLast, first, false);
pageAlignment = pageAlign;
textIndent = indent;
fillerMinWidth = fillerWidth;
lineHeight = lh;
lead = ld;
follow = fl;
thisLLM = llm;
activePossibility = -1;
maxDiff = fobj.getWidows() >= fobj.getOrphans()
? fobj.getWidows()
: fobj.getOrphans();
}
public void updateData1(int lineCount, double demerits) {
lineLayouts.addPossibility(lineCount, demerits);
log.trace("Layout possibility in " + lineCount + " lines; break at position:");
}
public void updateData2(KnuthNode bestActiveNode,
KnuthSequence par,
int total) {
// compute indent and adjustment ratio, according to
// the value of text-align and text-align-last
int indent = 0;
int difference = bestActiveNode.difference;
int textAlign = (bestActiveNode.line < total) ? alignment : alignmentLast;
indent += (textAlign == Constants.EN_CENTER) ?
difference / 2 : (textAlign == Constants.EN_END) ? difference : 0;
indent += (bestActiveNode.line == 1 && bFirst) ? textIndent : 0;
double ratio = (textAlign == Constants.EN_JUSTIFY
|| difference < 0 && -difference <= bestActiveNode.availableShrink)
? bestActiveNode.adjustRatio : 0;
// add nodes at the beginning of the list, as they are found
// backwards, from the last one to the first one
// the first time this method is called, initialize activePossibility
if (activePossibility == -1) {
activePossibility = 0;
addedPositions = 0;
}
if (addedPositions == lineLayouts.getLineCount(activePossibility)) {
activePossibility++;
addedPositions = 0;
}
//log.debug("LLM> (" + (lineLayouts.getLineNumber(activePossibility) - addedPositions) + ") difference = " + difference + " ratio = " + ratio);
lineLayouts.addBreakPosition(makeLineBreakPosition(par,
(bestActiveNode.line > 1 ? bestActiveNode.previous.position + 1: 0),
bestActiveNode.position,
bestActiveNode.availableShrink - (addedPositions > 0 ? 0 : ((Paragraph)par).lineFiller.opt - ((Paragraph)par).lineFiller.min), bestActiveNode.availableStretch, difference, ratio, indent),
activePossibility);
addedPositions++;
}
/* reset activePossibility, as if breakpoints have not yet been computed
*/
public void resetAlgorithm() {
activePossibility = -1;
}
private LineBreakPosition makeLineBreakPosition(KnuthSequence par,
int firstElementIndex,
int lastElementIndex,
int availableShrink, int availableStretch, int difference,
double ratio,
int indent) {
// line height calculation - spaceBefore may differ from spaceAfter
// by 1mpt due to rounding
int spaceBefore = (lineHeight - lead - follow) / 2;
int spaceAfter = lineHeight - lead - follow - spaceBefore;
// height before the main baseline
int lineLead = lead;
// maximum follow
int lineFollow = follow;
// true if this line contains only zero-height, auxiliary boxes
// and the actual line width is 0; in this case, the line "collapses"
// i.e. the line area will have bpd = 0
boolean bZeroHeightLine = (difference == iLineWidth);
// if line-stacking-strategy is "font-height", the line height
// is not affected by its content
if (fobj.getLineStackingStrategy() != EN_FONT_HEIGHT) {
ListIterator inlineIterator
= par.listIterator(firstElementIndex);
AlignmentContext lastAC = null;
int maxIgnoredHeight = 0; // See spec 7.13
for (int j = firstElementIndex;
j <= lastElementIndex;
j++) {
KnuthElement element = (KnuthElement) inlineIterator.next();
if (element instanceof KnuthInlineBox ) {
AlignmentContext ac = ((KnuthInlineBox) element).getAlignmentContext();
if (ac != null && lastAC != ac) {
if (!ac.usesInitialBaselineTable()
|| ac.getAlignmentBaselineIdentifier() != EN_BEFORE_EDGE
&& ac.getAlignmentBaselineIdentifier() != EN_AFTER_EDGE) {
int alignmentOffset = ac.getTotalAlignmentBaselineOffset();
if (alignmentOffset + ac.getAltitude() > lineLead) {
lineLead = alignmentOffset + ac.getAltitude();
}
if (ac.getDepth() - alignmentOffset > lineFollow) {
lineFollow = ac.getDepth() - alignmentOffset;
}
} else {
if (ac.getHeight() > maxIgnoredHeight) {
maxIgnoredHeight = ac.getHeight();
}
}
lastAC = ac;
}
if (bZeroHeightLine
&& (!element.isAuxiliary() || ac != null && ac.getHeight() > 0)) {
bZeroHeightLine = false;
}
}
}
if (lineFollow < maxIgnoredHeight - lineLead) {
lineFollow = maxIgnoredHeight - lineLead;
}
}
constantLineHeight = lineLead + lineFollow;
if (bZeroHeightLine) {
return new LineBreakPosition(thisLLM,
knuthParagraphs.indexOf(par),
lastElementIndex,
availableShrink, availableStretch, difference, ratio, 0, indent,
0, iLineWidth, 0, 0, 0);
} else {
return new LineBreakPosition(thisLLM,
knuthParagraphs.indexOf(par),
lastElementIndex,
availableShrink, availableStretch, difference, ratio, 0, indent,
lineLead + lineFollow,
iLineWidth, spaceBefore, spaceAfter,
lineLead);
}
}
public int findBreakingPoints(Paragraph par, /*int lineWidth,*/
double threshold, boolean force,
int allowedBreaks) {
return super.findBreakingPoints(par, /*lineWidth,*/
threshold, force, allowedBreaks);
}
protected int filterActiveNodes() {
KnuthNode bestActiveNode = null;
if (pageAlignment == EN_JUSTIFY) {
// leave all active nodes and find the optimum line number
//log.debug("LBA.filterActiveNodes> " + activeNodeCount + " layouts");
for (int i = startLine; i < endLine; i++) {
for (KnuthNode node = getNode(i); node != null; node = node.next) {
//log.debug(" + lines = " + node.line + " demerits = " + node.totalDemerits);
bestActiveNode = compareNodes(bestActiveNode, node);
}
}
// scan the node set once again and remove some nodes
//log.debug("LBA.filterActiveList> layout selection");
for (int i = startLine; i < endLine; i++) {
for (KnuthNode node = getNode(i); node != null; node = node.next) {
//if (Math.abs(node.line - bestActiveNode.line) > maxDiff) {
//if (false) {
if (node.line != bestActiveNode.line
&& node.totalDemerits > MAX_DEMERITS) {
//log.debug(" XXX lines = " + node.line + " demerits = " + node.totalDemerits);
removeNode(i, node);
} else {
//log.debug(" ok lines = " + node.line + " demerits = " + node.totalDemerits);
}
}
}
} else {
// leave only the active node with fewest total demerits
for (int i = startLine; i < endLine; i++) {
for (KnuthNode node = getNode(i); node != null; node = node.next) {
bestActiveNode = compareNodes(bestActiveNode, node);
if (node != bestActiveNode) {
removeNode(i, node);
}
}
}
}
return bestActiveNode.line;
}
}
private int constantLineHeight = 12000;
/**
* Create a new Line Layout Manager.
* This is used by the block layout manager to create
* line managers for handling inline areas flowing into line areas.
* @param block the block formatting object
* @param lh the default line height
* @param l the default lead, from top to baseline
* @param f the default follow, from baseline to bottom
*/
public LineLayoutManager(Block block, Length lh, int l, int f) {
super(block);
fobj = block;
// the child FObj are owned by the parent BlockLM
// this LM has all its childLMs preloaded
fobjIter = null;
lineHeight = lh;
lead = l;
follow = f;
}
/** @see org.apache.fop.layoutmgr.LayoutManager */
public LinkedList getNextKnuthElements(LayoutContext context, int alignment) {
Font fs = fobj.getCommonFont().getFontState(fobj.getFOEventHandler().getFontInfo(), this);
alignmentContext = new AlignmentContext(fs, lineHeight.getValue(this), context.getWritingMode());
context.setAlignmentContext(alignmentContext);
// Get a break from currently active child LM
// Set up constraints for inline level managers
InlineLevelLayoutManager curLM; // currently active LM
// IPD remaining in line
MinOptMax availIPD = context.getStackLimit();
clearPrevIPD();
//PHASE 1: Create Knuth elements
if (knuthParagraphs == null) {
// it's the first time this method is called
knuthParagraphs = new ArrayList();
// here starts Knuth's algorithm
//TODO availIPD should not really be used here, so we can later support custom line
//widths for for each line (side-floats, differing available IPD after page break)
collectInlineKnuthElements(context, availIPD);
} else {
// this method has been called before
// all line breaks are already calculated
}
// return finished when there's no content
if (knuthParagraphs.size() == 0) {
setFinished(true);
return null;
}
//PHASE 2: Create line breaks
return createLineBreaks(context.getBPAlignment(), context);
/*
LineBreakPosition lbp = null;
if (breakpoints == null) {
// find the optimal line breaking points for each paragraph
breakpoints = new ArrayList();
ListIterator paragraphsIterator
= knuthParagraphs.listIterator(knuthParagraphs.size());
Paragraph currPar = null;
while (paragraphsIterator.hasPrevious()) {
currPar = (Paragraph) paragraphsIterator.previous();
findBreakingPoints(currPar, context.getStackLimit().opt);
}
}*/
//PHASE 3: Return lines
/*
// get a break point from the list
lbp = (LineBreakPosition) breakpoints.get(iReturnedLBP ++);
if (iReturnedLBP == breakpoints.size()) {
setFinished(true);
}
BreakPoss curLineBP = new BreakPoss(lbp);
curLineBP.setFlag(BreakPoss.ISLAST, isFinished());
curLineBP.setStackingSize(new MinOptMax(lbp.lineHeight));
return curLineBP;
*/
}
/**
* Phase 1 of Knuth algorithm: Collect all inline Knuth elements before determining line breaks.
* @param context the LayoutContext
* @param availIPD available IPD for line (should be removed!)
*/
private void collectInlineKnuthElements(LayoutContext context, MinOptMax availIPD) {
LayoutContext inlineLC = new LayoutContext(context);
InlineLevelLayoutManager curLM;
LinkedList returnedList = null;
iLineWidth = context.getStackLimit().opt;
// convert all the text in a sequence of paragraphs made
// of KnuthBox, KnuthGlue and KnuthPenalty objects
boolean bPrevWasKnuthBox = false;
StringBuffer trace = new StringBuffer("LineLM:");
Paragraph lastPar = null;
while ((curLM = (InlineLevelLayoutManager) getChildLM()) != null) {
if ((returnedList
= curLM.getNextKnuthElements(inlineLC,
effectiveAlignment))
!= null) {
if (returnedList.size() == 0) {
continue;
}
if (lastPar != null) {
Object firstObj;
KnuthSequence firstSeq = null;
firstObj = returnedList.getFirst();
if (firstObj instanceof KnuthSequence) {
firstSeq = (KnuthSequence) firstObj;
}
// finish last paragraph before a new block sequence
if (firstSeq != null && !firstSeq.isInlineSequence()) {
lastPar.endParagraph();
ElementListObserver.observe(lastPar, "line", null);
lastPar = null;
if (log.isTraceEnabled()) {
trace.append(" ]");
}
bPrevWasKnuthBox = false;
}
// does the first element of the first paragraph add to an existing word?
if (lastPar != null) {
KnuthElement thisElement;
if (firstObj instanceof KnuthElement) {
thisElement = (KnuthElement) firstObj;
} else {
thisElement = (KnuthElement) firstSeq.get(0);
}
if (thisElement.isBox() && !thisElement.isAuxiliary()
&& bPrevWasKnuthBox) {
lastPar.addALetterSpace();
}
}
}
// loop over the KnuthSequences (and single KnuthElements) in returnedList
// (LeafNodeLM descendants may also skip wrapping elements in KnuthSequences
// to cause fewer container structures)
// TODO the mixture here adds a little to the complexity. Decide whether:
// - to leave as is and save some container instances
// - to use KnuthSequences exclusively (adjustments on leaf-type LMs necessary)
// See also FootnoteLM.addAnchor() as well as right above this comment
// for similar code. Or see http://svn.apache.org/viewcvs?rev=230779&view=rev
ListIterator iter = returnedList.listIterator();
while (iter.hasNext()) {
Object obj = iter.next();
KnuthElement singleElement = null;
KnuthSequence sequence = null;
if (obj instanceof KnuthElement) {
singleElement = (KnuthElement)obj;
} else {
sequence = (KnuthSequence)obj;
}
// the sequence contains inline Knuth elements
if (singleElement != null || sequence.isInlineSequence()) {
// look at the last element
KnuthElement lastElement;
if (singleElement != null) {
lastElement = singleElement;
} else {
lastElement = (KnuthElement) sequence.getLast();
if (lastElement == null) {
throw new NullPointerException(
"Sequence was empty! lastElement is null");
}
}
bPrevWasKnuthBox = lastElement.isBox();
// if last paragraph is open, add the new elements to the paragraph
// else this is the last paragraph
if (lastPar == null) {
lastPar = new Paragraph(this,
textAlignment, textAlignmentLast,
textIndent.getValue(this), lastLineEndIndent.getValue(this));
lastPar.startParagraph(availIPD.opt);
if (log.isTraceEnabled()) {
trace.append(" [");
}
} else {
if (log.isTraceEnabled()) {
trace.append(" +");
}
}
if (singleElement != null) {
lastPar.add(singleElement);
} else {
lastPar.addAll(sequence);
}
if (log.isTraceEnabled()) {
trace.append(" I");
}
// finish last paragraph if it was closed with a linefeed
if (lastElement.isPenalty()
&& ((KnuthPenalty) lastElement).getP()
== -KnuthPenalty.INFINITE) {
// a penalty item whose value is -inf
// represents a preserved linefeed,
// wich forces a line break
lastPar.removeLast();
if (lastPar.size() == 0) {
//only a forced linefeed on this line
//-> compensate with a zero width box
lastPar.add(new KnuthInlineBox(0, null, null, false));
}
lastPar.endParagraph();
ElementListObserver.observe(lastPar, "line", null);
lastPar = null;
if (log.isTraceEnabled()) {
trace.append(" ]");
}
bPrevWasKnuthBox = false;
}
} else { // the sequence is a block sequence
/* // "wrap" the Position stored in each element of returnedList
ListIterator listIter = sequence.listIterator();
while (listIter.hasNext()) {
KnuthElement returnedElement = (KnuthElement) listIter.next();
returnedElement.setPosition
(new NonLeafPosition(this,
returnedElement.getPosition()));
}
*/ knuthParagraphs.add(sequence);
if (log.isTraceEnabled()) {
trace.append(" B");
}
}
} // end of loop over returnedList
} else {
// curLM returned null; this can happen
// if it has nothing more to layout,
// so just iterate once more to see
// if there are other children
}
}
if (lastPar != null) {
lastPar.endParagraph();
ElementListObserver.observe(lastPar, "line", null);
if (log.isTraceEnabled()) {
trace.append(" ]");
}
}
log.trace(trace);
}
/**
* Find a set of breaking points.
* This method is called only once by getNextBreakPoss, and it
* subsequently calls the other findBreakingPoints() method with
* different parameters, until a set of breaking points is found.
*
* @param par the list of elements that must be parted
* into lines
* @param lineWidth the desired length ot the lines
*/
/*
private void findBreakingPoints(Paragraph par, int lineWidth) {
// maximum adjustment ratio permitted
float maxAdjustment = 1;
// first try
if (!findBreakingPoints(par, lineWidth, maxAdjustment, false)) {
// the first try failed, now try something different
log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment);
if (hyphenationProperties.hyphenate == Constants.EN_TRUE) {
// consider every hyphenation point as a legal break
findHyphenationPoints(par);
} else {
// try with a higher threshold
maxAdjustment = 5;
}
if (!findBreakingPoints(par, lineWidth, maxAdjustment, false)) {
// the second try failed too, try with a huge threshold;
// if this fails too, use a different algorithm
log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment
+ (hyphenationProperties.hyphenate == Constants.EN_TRUE ? " and hyphenation" : ""));
maxAdjustment = 20;
if (!findBreakingPoints(par, lineWidth, maxAdjustment, true)) {
log.debug("No set of breaking points found, using first-fit algorithm");
}
}
}
}
private boolean findBreakingPoints(Paragraph par, int lineWidth,
double threshold, boolean force) {
KnuthParagraph knuthPara = new KnuthParagraph(par);
int lines = knuthPara.findBreakPoints(lineWidth, threshold, force);
if (lines == 0) {
return false;
}
for (int i = lines-1; i >= 0; i--) {
int line = i+1;
if (log.isTraceEnabled()) {
log.trace("Making line from " + knuthPara.getStart(i) + " to " +
knuthPara.getEnd(i));
}
// compute indent and adjustment ratio, according to
// the value of text-align and text-align-last
int difference = knuthPara.getDifference(i);
if (line == lines) {
difference += par.lineFillerWidth;
}
int textAlign = (line < lines)
? textAlignment : textAlignmentLast;
int indent = (textAlign == EN_CENTER)
? difference / 2
: (textAlign == EN_END) ? difference : 0;
indent += (line == 1 && knuthParagraphs.indexOf(par) == 0)
? textIndent.getValue(this) : 0;
double ratio = (textAlign == EN_JUSTIFY)
? knuthPara.getAdjustRatio(i) : 0;
int start = knuthPara.getStart(i);
int end = knuthPara.getEnd(i);
makeLineBreakPosition(par, start, end, 0, ratio, indent);
}
return true;
}
private void makeLineBreakPosition(Paragraph par,
int firstElementIndex, int lastElementIndex,
int insertIndex, double ratio, int indent) {
// line height calculation
int halfLeading = (lineHeight - lead - follow) / 2;
// height above the main baseline
int lineLead = lead + halfLeading;
// maximum size of top and bottom alignment
int lineFollow = follow + halfLeading;
ListIterator inlineIterator
= par.listIterator(firstElementIndex);
for (int j = firstElementIndex;
j <= lastElementIndex;
j++) {
KnuthElement element = (KnuthElement) inlineIterator.next();
if (element.isBox()) {
KnuthInlineBox box = (KnuthInlineBox)element;
if (box.getLead() > lineLead) {
lineLead = box.getLead();
}
if (box.getTotal() > lineFollow) {
lineFollow = box.getTotal();
}
if (box.getMiddle() > lineLead + middleShift) {
lineLead += box.getMiddle()
- lineLead - middleShift;
}
if (box.getMiddle() > middlefollow - middleShift) {
middlefollow += box.getMiddle()
- middlefollow + middleShift;
}
}
}
if (lineFollow - lineLead > middlefollow) {
middlefollow = lineFollow - lineLead;
}
breakpoints.add(insertIndex,
new LineBreakPosition(this,
knuthParagraphs.indexOf(par),
lastElementIndex ,
ratio, 0, indent,
lineLead + middlefollow,
lineLead));
}*/
/**
* Phase 2 of Knuth algorithm: find optimal break points.
* @param alignment alignment in BP direction of the paragraph
* @param context the layout context
* @return a list of Knuth elements representing broken lines
*/
private LinkedList createLineBreaks(int alignment, LayoutContext context) {
// find the optimal line breaking points for each paragraph
ListIterator paragraphsIterator
= knuthParagraphs.listIterator(knuthParagraphs.size());
lineLayoutsList = new ArrayList(knuthParagraphs.size());
while (paragraphsIterator.hasPrevious()) {
KnuthSequence seq = (KnuthSequence) paragraphsIterator.previous();
if (!seq.isInlineSequence()) {
lineLayouts = createBlockLineBreak(seq);
} else {
lineLayouts = findOptimalBreakingPoints(alignment, (Paragraph) seq);
}
lineLayoutsList.add(0, lineLayouts);
}
setFinished(true);
//Post-process the line breaks found
return postProcessLineBreaks(alignment, context);
}
/**
* create a single line layout possibility with a single linebreak
* for a block sequence
* @param seq the Knuth sequence for which the linebreak is created
* @return the line layout possibilities for the paragraph
*/
private LineLayoutPossibilities createBlockLineBreak(KnuthSequence seq) {
//TODO Should this really create only a single LineBreakPosition???
//This creates an implicit keep-together on the nested block-level FOs.
lineLayouts = new LineLayoutPossibilities();
lineLayouts.addPossibility(1, 0);
int lineHeight = 0, lineStretch = 0, lineShrink = 0;
ListIterator seqIterator = seq.listIterator();
while (seqIterator.hasNext()) {
KnuthElement element = (KnuthElement) seqIterator.next();
lineHeight += element.getW();
if (element.isGlue()) {
lineStretch += element.getY();
lineShrink += element.getZ();
}
}
LineBreakPosition lbp = new LineBreakPosition(this,
knuthParagraphs.indexOf(seq), seq.size() - 1,
lineShrink, lineStretch, 0, 0, 0, 0, lineHeight,
iLineWidth, 0, 0, 0);
lineLayouts.addBreakPosition(lbp, 0);
return lineLayouts;
}
/**
* Fint the optimal linebreaks for a paragraph
* @param alignment alignment of the paragraph
* @param currPar the Paragraph for which the linebreaks are found
* @return the line layout possibilities for the paragraph
*/
private LineLayoutPossibilities findOptimalBreakingPoints(int alignment, Paragraph currPar) {
lineLayouts = new LineLayoutPossibilities();
double maxAdjustment = 1;
int iBPcount = 0;
LineBreakingAlgorithm alg = new LineBreakingAlgorithm(alignment,
textAlignment, textAlignmentLast,
textIndent.getValue(this), currPar.lineFiller.opt,
lineHeight.getValue(this), lead, follow,
(knuthParagraphs.indexOf(currPar) == 0),
this);
if (hyphenationProperties.hyphenate == EN_TRUE) {
findHyphenationPoints(currPar);
}
// first try
int allowedBreaks;
if (wrapOption == EN_NO_WRAP) {
allowedBreaks = BreakingAlgorithm.ONLY_FORCED_BREAKS;
} else {
allowedBreaks = BreakingAlgorithm.NO_FLAGGED_PENALTIES;
}
alg.setConstantLineWidth(iLineWidth);
iBPcount = alg.findBreakingPoints(currPar,
maxAdjustment, false, allowedBreaks);
if (iBPcount == 0 || alignment == EN_JUSTIFY) {
// if the first try found a set of breaking points, save them
if (iBPcount > 0) {
alg.resetAlgorithm();
lineLayouts.savePossibilities(false);
} else {
// the first try failed
log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment);
}
// now try something different
log.debug("Hyphenation possible? " + (hyphenationProperties.hyphenate == EN_TRUE));
if (hyphenationProperties.hyphenate == EN_TRUE
&& !(allowedBreaks == BreakingAlgorithm.ONLY_FORCED_BREAKS)) {
// consider every hyphenation point as a legal break
allowedBreaks = BreakingAlgorithm.ALL_BREAKS;
} else {
// try with a higher threshold
maxAdjustment = 5;
}
if ((iBPcount
= alg.findBreakingPoints(currPar,
maxAdjustment, false, allowedBreaks)) == 0) {
// the second try failed too, try with a huge threshold
// and force the algorithm to find
// a set of breaking points
log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment
+ (hyphenationProperties.hyphenate == EN_TRUE ? " and hyphenation" : ""));
maxAdjustment = 20;
iBPcount
= alg.findBreakingPoints(currPar,
maxAdjustment, true, allowedBreaks);
}
// use non-hyphenated breaks, when possible
lineLayouts.restorePossibilities();
/* extension (not in the XSL FO recommendation): if vertical alignment
is justify and the paragraph has only one layout, try using
shorter or longer lines */
//TODO This code snippet is disabled. Reenable?
if (false && alignment == EN_JUSTIFY && textAlignment == EN_JUSTIFY) {
//log.debug("LLM.getNextKnuthElements> layouts with more lines? " + lineLayouts.canUseMoreLines());
//log.debug(" layouts with fewer lines? " + lineLayouts.canUseLessLines());
if (!lineLayouts.canUseMoreLines()) {
alg.resetAlgorithm();
lineLayouts.savePossibilities(true);
// try with shorter lines
int savedLineWidth = iLineWidth;
iLineWidth = (int) (iLineWidth * 0.95);
iBPcount = alg.findBreakingPoints(currPar,
maxAdjustment, true, allowedBreaks);
// use normal lines, when possible
lineLayouts.restorePossibilities();
iLineWidth = savedLineWidth;
}
if (!lineLayouts.canUseLessLines()) {
alg.resetAlgorithm();
lineLayouts.savePossibilities(true);
// try with longer lines
int savedLineWidth = iLineWidth;
iLineWidth = (int) (iLineWidth * 1.05);
alg.setConstantLineWidth(iLineWidth);
iBPcount = alg.findBreakingPoints(currPar,
maxAdjustment, true, allowedBreaks);
// use normal lines, when possible
lineLayouts.restorePossibilities();
iLineWidth = savedLineWidth;
}
//log.debug("LLM.getNextKnuthElements> now, layouts with more lines? " + lineLayouts.canUseMoreLines());
//log.debug(" now, layouts with fewer lines? " + lineLayouts.canUseLessLines());
}
}
return lineLayouts;
}
/**
* Creates the element list in BP direction for the broken lines.
* @param alignment the currently applicable vertical alignment
* @param context the layout context
* @return the newly built element list
*/
private LinkedList postProcessLineBreaks(int alignment, LayoutContext context) {
LinkedList returnList = new LinkedList();
for (int p = 0; p < knuthParagraphs.size(); p++) {
// null penalty between paragraphs
if (p > 0 && !((BlockLevelLayoutManager) parentLM).mustKeepTogether()) {
returnList.add(new BreakElement(
new Position(this), 0, context));
//returnList.add(new KnuthPenalty(0, 0, false, new Position(this), false));
}
lineLayouts = (LineLayoutPossibilities)lineLayoutsList.get(p);
KnuthSequence seq = (KnuthSequence) knuthParagraphs.get(p);
if (!seq.isInlineSequence()) {
LinkedList targetList = new LinkedList();
ListIterator listIter = seq.listIterator();
while (listIter.hasNext()) {
KnuthElement tempElement;
tempElement = (KnuthElement) listIter.next();
if (tempElement.getLayoutManager() != this) {
tempElement.setPosition(notifyPos(new NonLeafPosition(this,
tempElement.getPosition())));
}
targetList.add(tempElement);
}
returnList.addAll(targetList);
} else if (seq.isInlineSequence() && alignment == EN_JUSTIFY) {
/* justified vertical alignment (not in the XSL FO recommendation):
create a multi-layout sequence whose elements will contain
a conventional Position */
Position returnPosition = new LeafPosition(this, p);
createElements(returnList, lineLayouts, returnPosition);
} else {
/* "normal" vertical alignment: create a sequence whose boxes
represent effective lines, and contain LineBreakPositions */
Position returnPosition = new LeafPosition(this, p);
int startIndex = 0;
for (int i = 0;
i < lineLayouts.getChosenLineCount();
i++) {
if (!((BlockLevelLayoutManager) parentLM).mustKeepTogether()
&& i >= fobj.getOrphans()
&& i <= lineLayouts.getChosenLineCount() - fobj.getWidows()
&& returnList.size() > 0) {
// null penalty allowing a page break between lines
returnList.add(new BreakElement(
returnPosition, 0, context));
//returnList.add(new KnuthPenalty(0, 0, false, returnPosition, false));
}
int endIndex = ((LineBreakPosition) lineLayouts.getChosenPosition(i)).getLeafPos();
// create a list of the FootnoteBodyLM handling footnotes
// whose citations are in this line
LinkedList footnoteList = new LinkedList();
ListIterator elementIterator = seq.listIterator(startIndex);
while (elementIterator.nextIndex() <= endIndex) {
KnuthElement element = (KnuthElement) elementIterator.next();
if (element instanceof KnuthInlineBox
&& ((KnuthInlineBox) element).isAnchor()) {
footnoteList.add(((KnuthInlineBox) element).getFootnoteBodyLM());
} else if (element instanceof KnuthBlockBox) {
footnoteList.addAll(((KnuthBlockBox) element).getFootnoteBodyLMs());
}
}
startIndex = endIndex + 1;
LineBreakPosition lbp = (LineBreakPosition) lineLayouts.getChosenPosition(i);
returnList.add(new KnuthBlockBox(lbp.lineHeight + lbp.spaceBefore + lbp.spaceAfter
, footnoteList, lbp, false));
/* // add stretch and shrink to the returnlist
if (!seq.isInlineSequence()
&& lbp.availableStretch != 0 || lbp.availableShrink != 0) {
returnList.add(new KnuthPenalty(0, -KnuthElement.INFINITE,
false, new Position(this), false));
returnList.add(new KnuthGlue(0, lbp.availableStretch, lbp.availableShrink,
new Position(this), false));
}
*/
}
}
}
return returnList;
}
private void createElements(List list, LineLayoutPossibilities lineLayouts,
Position elementPosition) {
/* number of normal, inner lines */
int nInnerLines = 0;
/* number of lines that can be used in order to fill more space */
int nOptionalLines = 0;
/* number of lines that can be used in order to fill more space
only if the paragraph is not parted */
int nConditionalOptionalLines = 0;
/* number of lines that can be omitted in order to fill less space */
int nEliminableLines = 0;
/* number of lines that can be omitted in order to fill less space
only if the paragraph is not parted */
int nConditionalEliminableLines = 0;
/* number of the first unbreakable lines */
int nFirstLines = fobj.getOrphans();
/* number of the last unbreakable lines */
int nLastLines = fobj.getWidows();
/* sub-sequence used to separate the elements representing different lines */
List breaker = new LinkedList();
/* comment out the next lines in order to test particular situations */
if (fobj.getOrphans() + fobj.getWidows() <= lineLayouts.getMinLineCount()) {
nInnerLines = lineLayouts.getMinLineCount() - (fobj.getOrphans() + fobj.getWidows());
nOptionalLines = lineLayouts.getMaxLineCount() - lineLayouts.getOptLineCount();
nEliminableLines = lineLayouts.getOptLineCount() - lineLayouts.getMinLineCount();
} else if (fobj.getOrphans() + fobj.getWidows() <= lineLayouts.getOptLineCount()) {
nOptionalLines = lineLayouts.getMaxLineCount() - lineLayouts.getOptLineCount();
nEliminableLines = lineLayouts.getOptLineCount() - (fobj.getOrphans() + fobj.getWidows());
nConditionalEliminableLines = (fobj.getOrphans() + fobj.getWidows()) - lineLayouts.getMinLineCount();
} else if (fobj.getOrphans() + fobj.getWidows() <= lineLayouts.getMaxLineCount()) {
nOptionalLines = lineLayouts.getMaxLineCount() - (fobj.getOrphans() + fobj.getWidows());
nConditionalOptionalLines = (fobj.getOrphans() + fobj.getWidows()) - lineLayouts.getOptLineCount();
nConditionalEliminableLines = lineLayouts.getOptLineCount() - lineLayouts.getMinLineCount();
nFirstLines -= nConditionalOptionalLines;
} else {
nConditionalOptionalLines = lineLayouts.getMaxLineCount() - lineLayouts.getOptLineCount();
nConditionalEliminableLines = lineLayouts.getOptLineCount() - lineLayouts.getMinLineCount();
nFirstLines = lineLayouts.getOptLineCount();
nLastLines = 0;
}
/* comment out the previous lines in order to test particular situations */
/* use these lines to test particular situations
nInnerLines = 0;
nOptionalLines = 1;
nConditionalOptionalLines = 2;
nEliminableLines = 0;
nConditionalEliminableLines = 0;
nFirstLines = 1;
nLastLines = 3;
*/
if (nLastLines != 0
&& (nConditionalOptionalLines > 0 || nConditionalEliminableLines > 0)) {
breaker.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
breaker.add(new KnuthGlue(0, -nConditionalOptionalLines * constantLineHeight,
-nConditionalEliminableLines * constantLineHeight,
LINE_NUMBER_ADJUSTMENT, elementPosition, false));
breaker.add(new KnuthPenalty(nConditionalOptionalLines * constantLineHeight,
0, false, elementPosition, false));
breaker.add(new KnuthGlue(0, nConditionalOptionalLines * constantLineHeight,
nConditionalEliminableLines * constantLineHeight,
LINE_NUMBER_ADJUSTMENT, elementPosition, false));
} else if (nLastLines != 0) {
breaker.add(new KnuthPenalty(0, 0, false, elementPosition, false));
}
//log.debug("first=" + nFirstLines + " inner=" + nInnerLines
// + " optional=" + nOptionalLines + " eliminable=" + nEliminableLines
// + " last=" + nLastLines
// + " (condOpt=" + nConditionalOptionalLines + " condEl=" + nConditionalEliminableLines + ")");
// creation of the elements:
// first group of lines
list.add(new KnuthBox(nFirstLines * constantLineHeight, elementPosition,
(nLastLines == 0
&& nConditionalOptionalLines == 0
&& nConditionalEliminableLines == 0 ? true : false)));
if (nConditionalOptionalLines > 0
|| nConditionalEliminableLines > 0) {
list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
list.add(new KnuthGlue(0, nConditionalOptionalLines * constantLineHeight,
nConditionalEliminableLines * constantLineHeight,
LINE_NUMBER_ADJUSTMENT, elementPosition, false));
list.add(new KnuthBox(0, elementPosition,
(nLastLines == 0 ? true : false)));
}
// optional lines
for (int i = 0; i < nOptionalLines; i++) {
list.addAll(breaker);
list.add(new KnuthBox(0, elementPosition, false));
list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
list.add(new KnuthGlue(0, 1 * constantLineHeight, 0,
LINE_NUMBER_ADJUSTMENT, elementPosition, false));
list.add(new KnuthBox(0, elementPosition, false));
}
// eliminable lines
for (int i = 0; i < nEliminableLines; i++) {
list.addAll(breaker);
list.add(new KnuthBox(1 * constantLineHeight, elementPosition, false));
list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
list.add(new KnuthGlue(0, 0, 1 * constantLineHeight,
LINE_NUMBER_ADJUSTMENT, elementPosition, false));
list.add(new KnuthBox(0, elementPosition, false));
}
// inner lines
for (int i = 0; i < nInnerLines; i++) {
list.addAll(breaker);
list.add(new KnuthBox(1 * constantLineHeight, elementPosition, false));
}
// last group of lines
if (nLastLines > 0) {
list.addAll(breaker);
list.add(new KnuthBox(nLastLines * constantLineHeight,
elementPosition, true));
}
}
/**
* @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepTogether
*/
public boolean mustKeepTogether() {
return false;
}
/**
* @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithPrevious
*/
public boolean mustKeepWithPrevious() {
return false;
}
/**
* @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#mustKeepWithNext
*/
public boolean mustKeepWithNext() {
return false;
}
/**
* @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#negotiateBPDAdjustment(int, org.apache.fop.layoutmgr.KnuthElement)
*/
public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) {
LeafPosition pos = (LeafPosition)lastElement.getPosition();
int totalAdj = adj;
//if (lastElement.isPenalty()) {
// totalAdj += lastElement.getW();
//}
//int lineNumberDifference = (int)((double) totalAdj / constantLineHeight);
int lineNumberDifference = (int) Math.round((double) totalAdj / constantLineHeight + (adj > 0 ? - 0.4 : 0.4));
//log.debug(" LLM> variazione calcolata = " + ((double) totalAdj / constantLineHeight) + " variazione applicata = " + lineNumberDifference);
lineLayouts = (LineLayoutPossibilities)lineLayoutsList.get(pos.getLeafPos());
lineNumberDifference = lineLayouts.applyLineCountAdjustment(lineNumberDifference);
return lineNumberDifference * constantLineHeight;
}
/**
* @see org.apache.fop.layoutmgr.BlockLevelLayoutManager#discardSpace(KnuthGlue)
*/
public void discardSpace(KnuthGlue spaceGlue) {
}
/**
* @see org.apache.fop.layoutmgr.LayoutManager#getChangedKnuthElements(List, int)
*/
public LinkedList getChangedKnuthElements(List oldList, int alignment) {
LinkedList returnList = new LinkedList();
for (int p = 0; p < knuthParagraphs.size(); p++) {
lineLayouts = (LineLayoutPossibilities)lineLayoutsList.get(p);
//log.debug("demerits of the chosen layout: " + lineLayouts.getChosenDemerits());
for (int i = 0; i < lineLayouts.getChosenLineCount(); i++) {
if (!((BlockLevelLayoutManager) parentLM).mustKeepTogether()
&& i >= fobj.getOrphans()
&& i <= lineLayouts.getChosenLineCount() - fobj.getWidows()) {
// null penalty allowing a page break between lines
returnList.add(new KnuthPenalty(0, 0, false, new Position(this), false));
}
LineBreakPosition lbp = (LineBreakPosition) lineLayouts.getChosenPosition(i);
//log.debug("LLM.getChangedKnuthElements> lineWidth= " + lbp.lineWidth + " difference= " + lbp.difference);
//log.debug(" shrink= " + lbp.availableShrink + " stretch= " + lbp.availableStretch);
//log.debug("linewidth= " + lbp.lineWidth + " difference= " + lbp.difference + " indent= " + lbp.startIndent);
MinOptMax contentIPD;
if (alignment == EN_JUSTIFY) {
contentIPD = new MinOptMax(
lbp.lineWidth - lbp.difference - lbp.availableShrink,
lbp.lineWidth - lbp.difference,
lbp.lineWidth - lbp.difference + lbp.availableStretch);
} else if (alignment == EN_CENTER) {
contentIPD = new MinOptMax(lbp.lineWidth - 2 * lbp.startIndent);
} else if (alignment == EN_END) {
contentIPD = new MinOptMax(lbp.lineWidth - lbp.startIndent);
} else {
contentIPD = new MinOptMax(lbp.lineWidth - lbp.difference + lbp.startIndent);
}
returnList.add(new KnuthBlockBox(lbp.lineHeight,
contentIPD,
(lbp.ipdAdjust != 0 ? lbp.lineWidth - lbp.difference : 0),
lbp, false));
}
}
return returnList;
}
/**
* find hyphenation points for every word int the current paragraph
* @ param currPar the paragraph whose words will be hyphenated
*/
private void findHyphenationPoints(Paragraph currPar) {
// hyphenate every word
ListIterator currParIterator
= currPar.listIterator(currPar.ignoreAtStart);
// list of TLM involved in hyphenation
LinkedList updateList = new LinkedList();
KnuthElement firstElement = null;
KnuthElement nextElement = null;
// current InlineLevelLayoutManager
InlineLevelLayoutManager currLM = null;
// number of KnuthBox elements containing word fragments
int boxCount;
// number of auxiliary KnuthElements between KnuthBoxes
int auxCount;
StringBuffer sbChars = null;
// find all hyphenation points
while (currParIterator.hasNext()) {
firstElement = (KnuthElement) currParIterator.next();
//
if (firstElement.getLayoutManager() != currLM) {
currLM = (InlineLevelLayoutManager) firstElement.getLayoutManager();
if (currLM != null) {
updateList.add(new Update(currLM, currParIterator.previousIndex()));
} else {
break;
}
}
// collect word fragments, ignoring auxiliary elements;
// each word fragment was created by a different TextLM
if (firstElement.isBox() && !firstElement.isAuxiliary()) {
boxCount = 1;
auxCount = 0;
sbChars = new StringBuffer();
currLM.getWordChars(sbChars, firstElement.getPosition());
// look if next elements are boxes too
while (currParIterator.hasNext()) {
nextElement = (KnuthElement) currParIterator.next();
if (nextElement.isBox() && !nextElement.isAuxiliary()) {
// a non-auxiliary KnuthBox: append word chars
if (currLM != nextElement.getLayoutManager()) {
currLM = (InlineLevelLayoutManager) nextElement.getLayoutManager();
updateList.add(new Update(currLM, currParIterator.previousIndex()));
}
// append text to recreate the whole word
boxCount++;
currLM.getWordChars(sbChars, nextElement.getPosition());
} else if (!nextElement.isAuxiliary()) {
// a non-auxiliary non-box KnuthElement: stop
// go back to the last box or auxiliary element
currParIterator.previous();
break;
} else {
if (currLM != nextElement.getLayoutManager()) {
currLM = (InlineLevelLayoutManager) nextElement.getLayoutManager();
updateList.add(new Update(currLM, currParIterator.previousIndex()));
}
// an auxiliary KnuthElement: simply ignore it
auxCount++;
}
}
log.trace(" Word to hyphenate: " + sbChars.toString());
// find hyphenation points
HyphContext hc = getHyphenContext(sbChars);
// ask each LM to hyphenate its word fragment
if (hc != null) {
KnuthElement element = null;
for (int i = 0; i < (boxCount + auxCount); i++) {
currParIterator.previous();
}
for (int i = 0; i < (boxCount + auxCount); i++) {
element = (KnuthElement) currParIterator.next();
if (element.isBox() && !element.isAuxiliary()) {
((InlineLevelLayoutManager)
element.getLayoutManager()).hyphenate(element.getPosition(), hc);
} else {
// nothing to do, element is an auxiliary KnuthElement
}
}
}
}
}
// create iterator for the updateList
ListIterator updateListIterator = updateList.listIterator();
Update currUpdate = null;
//int iPreservedElements = 0;
int iAddedElements = 0;
//int iRemovedElements = 0;
while (updateListIterator.hasNext()) {
// ask the LMs to apply the changes and return
// the new KnuthElements to replace the old ones
currUpdate = (Update) updateListIterator.next();
int fromIndex = currUpdate.iFirstIndex;
int toIndex;
if (updateListIterator.hasNext()) {
Update nextUpdate = (Update) updateListIterator.next();
toIndex = nextUpdate.iFirstIndex;
updateListIterator.previous();
} else {
// maybe this is not always correct!
toIndex = currPar.size() - currPar.ignoreAtEnd
- iAddedElements;
}
// applyChanges() returns true if the LM modifies its data,
// so it must return new KnuthElements to replace the old ones
if (((InlineLevelLayoutManager) currUpdate.inlineLM)
.applyChanges(currPar.subList(fromIndex + iAddedElements,
toIndex + iAddedElements))) {
// insert the new KnuthElements
LinkedList newElements = null;
newElements
= currUpdate.inlineLM.getChangedKnuthElements
(currPar.subList(fromIndex + iAddedElements,
toIndex + iAddedElements),
/*flaggedPenalty,*/ effectiveAlignment);
// remove the old elements
currPar.subList(fromIndex + iAddedElements,
toIndex + iAddedElements).clear();
// insert the new elements
currPar.addAll(fromIndex + iAddedElements, newElements);
iAddedElements += newElements.size() - (toIndex - fromIndex);
}
}
updateListIterator = null;
updateList.clear();
}
/**
* Line area is always considered to act as a fence.
* @param isNotFirst ignored
* @return always true
*/
protected boolean hasLeadingFence(boolean isNotFirst) {
return true;
}
/**
* Line area is always considered to act as a fence.
* @param isNotLast ignored
* @return always true
*/
protected boolean hasTrailingFence(boolean isNotLast) {
return true;
}
private HyphContext getHyphenContext(StringBuffer sbChars) {
// Find all hyphenation points in this word
// (get in an array of offsets)
// hyphenationProperties are from the block level?.
// Note that according to the spec,
// they also "apply to" fo:character.
// I don't know what that means, since
// if we change language in the middle of a "word",
// the effect would seem quite strange!
// Or perhaps in that case, we say that it's several words.
// We probably should bring the hyphenation props up from the actual
// TextLM which generate the hyphenation buffer,
// since these properties inherit and could be specified
// on an inline or wrapper below the block level.
Hyphenation hyph
= Hyphenator.hyphenate(hyphenationProperties.language,
hyphenationProperties.country, sbChars.toString(),
hyphenationProperties.hyphenationRemainCharacterCount,
hyphenationProperties.hyphenationPushCharacterCount);
// They hyph structure contains the information we need
// Now start from prev: reset to that position, ask that LM to get
// a Position for the first hyphenation offset. If the offset isn't in
// its characters, it returns null,
// but must tell how many chars it had.
// Keep looking at currentBP using next hyphenation point until the
// returned size is greater than the available size
// or no more hyphenation points remain. Choose the best break.
if (hyph != null) {
return new HyphContext(hyph.getHyphenationPoints());
} else {
return null;
}
}
/**
* Reset the positions to the given position.
*
* @param resetPos the position to reset to
*/
public void resetPosition(Position resetPos) {
if (resetPos == null) {
setFinished(false);
iReturnedLBP = 0;
} else {
if (isFinished()) {
// if isFinished is true, iReturned LBP == breakpoints.size()
// and breakpoints.get(iReturnedLBP) would generate
// an IndexOutOfBoundException
setFinished(false);
iReturnedLBP--;
}
while ((LineBreakPosition) lineLayouts.getChosenPosition(iReturnedLBP)
!= (LineBreakPosition) resetPos) {
iReturnedLBP--;
}
iReturnedLBP++;
}
}
/**
* Add the areas with the break points.
*
* @param parentIter the iterator of break positions
* @param context the context for adding areas
*/
public void addAreas(PositionIterator parentIter,
LayoutContext context) {
LayoutManager childLM;
LayoutContext lc = new LayoutContext(0);
lc.setAlignmentContext(alignmentContext);
int iCurrParIndex;
while (parentIter.hasNext()) {
Position pos = (Position) parentIter.next();
if (pos instanceof LineBreakPosition) {
ListIterator seqIterator = null;
KnuthElement tempElement = null;
// the TLM which created the last KnuthElement in this line
LayoutManager lastLM = null;
LineBreakPosition lbp = (LineBreakPosition) pos;
iCurrParIndex = lbp.iParIndex;
KnuthSequence seq = (KnuthSequence) knuthParagraphs.get(iCurrParIndex);
iEndElement = lbp.getLeafPos();
LineArea lineArea = new LineArea((lbp.getLeafPos() < seq.size() - 1 ? textAlignment : textAlignmentLast),
lbp.difference, lbp.availableStretch, lbp.availableShrink);
lineArea.setStartIndent(lbp.startIndent);
lineArea.setBPD(lbp.lineHeight);
lineArea.setIPD(lbp.lineWidth);
lineArea.addTrait(Trait.SPACE_BEFORE, new Integer(lbp.spaceBefore));
lineArea.addTrait(Trait.SPACE_AFTER, new Integer(lbp.spaceAfter));
alignmentContext.resizeLine(lbp.lineHeight, lbp.baseline);
if (seq instanceof Paragraph) {
Paragraph currPar = (Paragraph) seq;
// ignore the first elements added by the LineLayoutManager
iStartElement += (iStartElement == 0) ? currPar.ignoreAtStart : 0;
// if this is the last line area that for this paragraph,
// ignore the last elements added by the LineLayoutManager and
// subtract the last-line-end-indent from the area ipd
if (iEndElement == (currPar.size() - 1)) {
iEndElement -= currPar.ignoreAtEnd;
lineArea.setIPD(lineArea.getIPD() - lastLineEndIndent.getValue(this));
}
}
// ignore the last element in the line if it is a KnuthGlue object
seqIterator = seq.listIterator(iEndElement);
tempElement = (KnuthElement) seqIterator.next();
if (tempElement.isGlue()) {
iEndElement--;
// this returns the same KnuthElement
seqIterator.previous();
tempElement = (KnuthElement) seqIterator.previous();
}
lastLM = tempElement.getLayoutManager();
// ignore KnuthGlue and KnuthPenalty objects
// at the beginning of the line
seqIterator = seq.listIterator(iStartElement);
tempElement = (KnuthElement) seqIterator.next();
while (!tempElement.isBox() && seqIterator.hasNext()) {
tempElement = (KnuthElement) seqIterator.next();
iStartElement++;
}
// Add the inline areas to lineArea
PositionIterator inlinePosIter
= new KnuthPossPosIter(seq, iStartElement,
iEndElement + 1);
iStartElement = lbp.getLeafPos() + 1;
if (iStartElement == seq.size()) {
// advance to next paragraph
iStartElement = 0;
}
lc.setSpaceAdjust(lbp.dAdjust);
lc.setIPDAdjust(lbp.ipdAdjust);
lc.setLeadingSpace(new SpaceSpecifier(true));
lc.setTrailingSpace(new SpaceSpecifier(false));
lc.setFlags(LayoutContext.RESOLVE_LEADING_SPACE, true);
/* extension (not in the XSL FO recommendation): if the left and right margins
have been optimized, recompute indents and / or adjust ratio, according
to the paragraph horizontal alignment */
if (false && textAlignment == EN_JUSTIFY) {
// re-compute space adjust ratio
int updatedDifference = context.getStackLimit().opt - lbp.lineWidth + lbp.difference;
double updatedRatio = 0.0;
if (updatedDifference > 0) {
updatedRatio = (float) updatedDifference / lbp.availableStretch;
} else if (updatedDifference < 0) {
updatedRatio = (float) updatedDifference / lbp.availableShrink;
}
lc.setIPDAdjust(updatedRatio);
//log.debug("LLM.addAreas> old difference = " + lbp.difference + " new difference = " + updatedDifference);
//log.debug(" old ratio = " + lbp.ipdAdjust + " new ratio = " + updatedRatio);
} else if (false && textAlignment == EN_CENTER) {
// re-compute indent
int updatedIndent = lbp.startIndent
+ (context.getStackLimit().opt - lbp.lineWidth) / 2;
lineArea.setStartIndent(updatedIndent);
} else if (false && textAlignment == EN_END) {
// re-compute indent
int updatedIndent = lbp.startIndent
+ (context.getStackLimit().opt - lbp.lineWidth);
lineArea.setStartIndent(updatedIndent);
}
setCurrentArea(lineArea);
setChildContext(lc);
while ((childLM = inlinePosIter.getNextChildLM()) != null) {
lc.setFlags(LayoutContext.LAST_AREA, (childLM == lastLM));
childLM.addAreas(inlinePosIter, lc);
lc.setLeadingSpace(lc.getTrailingSpace());
lc.setTrailingSpace(new SpaceSpecifier(false));
}
// when can this be null?
// if display-align is distribute, add space after
if (context.getSpaceAfter() > 0
&& (!context.isLastArea() || parentIter.hasNext())) {
lineArea.setBPD(lineArea.getBPD() + context.getSpaceAfter());
}
lineArea.finalize();
parentLM.addChildArea(lineArea);
} else if (pos instanceof NonLeafPosition) {
// Nested block-level content;
// go down the LM stack again;
// collect all consecutive NonLeafPosition objects,
// "unwrap" them and put the child positions in a new list.
LinkedList positionList = new LinkedList();
Position innerPosition;
innerPosition = ((NonLeafPosition) pos).getPosition();
positionList.add(innerPosition);
while (parentIter.hasNext()) {
pos = (Position)parentIter.peekNext();
if (!(pos instanceof NonLeafPosition)) {
break;
}
pos = (Position) parentIter.next();
innerPosition = ((NonLeafPosition) pos).getPosition();
positionList.add(innerPosition);
}
// do we have the last LM?
LayoutManager lastLM = null;
if (!parentIter.hasNext()) {
lastLM = innerPosition.getLM();
}
// this may be wrong; not all areas belong inside a single line area
// see InlineStackingLM.addChildArea
LineArea lineArea = new LineArea();
setCurrentArea(lineArea);
setChildContext(lc);
PositionIterator childPosIter = new StackingIter(positionList.listIterator());
LayoutContext blocklc = new LayoutContext(0);
blocklc.setLeadingSpace(new SpaceSpecifier(true));
blocklc.setTrailingSpace(new SpaceSpecifier(false));
blocklc.setFlags(LayoutContext.RESOLVE_LEADING_SPACE, true);
while ((childLM = childPosIter.getNextChildLM()) != null) {
// set last area flag
blocklc.setFlags(LayoutContext.LAST_AREA,
(context.isLastArea() && childLM == lastLM));
blocklc.setStackLimit(context.getStackLimit());
// Add the line areas to Area
childLM.addAreas(childPosIter, blocklc);
blocklc.setLeadingSpace(blocklc.getTrailingSpace());
blocklc.setTrailingSpace(new SpaceSpecifier(false));
}
lineArea.updateExtentsFromChildren();
parentLM.addChildArea(lineArea);
} else {
// pos was the Position inside a penalty item, nothing to do
}
}
setCurrentArea(null); // ?? necessary
}
/**
* @see org.apache.fop.layoutmgr.LayoutManager#addChildArea(Area)
*/
public void addChildArea(Area childArea) {
// Make sure childArea is inline area
if (childArea instanceof InlineArea) {
Area parent = getCurrentArea();
if (getContext().resolveLeadingSpace()) {
addSpace(parent,
getContext().getLeadingSpace().resolve(false),
getContext().getSpaceAdjust());
}
parent.addChildArea(childArea);
}
}
// --------- Property Resolution related functions --------- //
/**
* @see org.apache.fop.layoutmgr.LayoutManager#getGeneratesBlockArea
*/
public boolean getGeneratesBlockArea() {
return true;
}
/**
* @see org.apache.fop.layoutmgr.LayoutManager#getGeneratesLineArea
*/
public boolean getGeneratesLineArea() {
return true;
}
}