Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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: LineLayoutManager.java 956271 2010-06-19 19:10:10Z adelmelle $ */
package org.apache.fop.layoutmgr.inline;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.area.Area;
import org.apache.fop.area.LineArea;
import org.apache.fop.area.Trait;
import org.apache.fop.area.inline.InlineArea;
import org.apache.fop.datatypes.Length;
import org.apache.fop.datatypes.Numeric;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.flow.Block;
import org.apache.fop.fo.properties.CommonHyphenation;
import org.apache.fop.fo.properties.KeepProperty;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.fonts.FontTriplet;
import org.apache.fop.hyphenation.Hyphenation;
import org.apache.fop.hyphenation.Hyphenator;
import org.apache.fop.layoutmgr.Adjustment;
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.InlineKnuthSequence;
import org.apache.fop.layoutmgr.Keep;
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.ListElement;
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.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 {
/**
* logging instance
*/
private static Log log = LogFactory.getLog(LineLayoutManager.class);
private Block fobj;
private boolean isFirstInBlock;
/** {@inheritDoc} */
public void initialize() {
textAlignment = fobj.getTextAlign();
textAlignmentLast = fobj.getTextAlignLast();
textIndent = fobj.getTextIndent();
lastLineEndIndent = fobj.getLastLineEndIndent();
hyphenationProperties = fobj.getCommonHyphenation();
hyphenationLadderCount = fobj.getHyphenationLadderCount();
wrapOption = fobj.getWrapOption();
whiteSpaceTreament = fobj.getWhitespaceTreatment();
//
effectiveAlignment = getEffectiveAlignment(textAlignment, textAlignmentLast);
isFirstInBlock = (this == getParent().getChildLMs().get(0));
}
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 parIndex; // index of the Paragraph this Position refers to
private int startIndex; //index of the first element 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 startIndex, int breakIndex,
int shrink, int stretch, int diff,
double ipdA, double adjust, int ind,
int lh, int lw, int sb, int sa, int bl) {
super(lm, breakIndex);
availableShrink = shrink;
availableStretch = stretch;
difference = diff;
parIndex = index;
this.startIndex = startIndex;
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 CommonHyphenation hyphenationProperties;
private Numeric hyphenationLadderCount;
private int wrapOption = EN_WRAP;
private int whiteSpaceTreament;
//private LayoutProps layoutProps;
private Length lineHeight;
private int lead;
private int follow;
private AlignmentContext alignmentContext;
private List knuthParagraphs;
private LineLayoutPossibilities lineLayouts;
private LineLayoutPossibilities[] lineLayoutsList;
private int ipd = 0;
/**
* When layout must be re-started due to a change of IPD, there is no need
* to perform hyphenation on the remaining Knuth sequence once again.
*/
private boolean hyphenationPerformed;
/**
* 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 firstIndex;
public Update(InlineLevelLayoutManager lm, int index) {
inlineLM = lm;
firstIndex = index;
}
}
// this class represents a paragraph
private class Paragraph extends InlineKnuthSequence {
/** Number of elements to ignore at the beginning of the list. */
private int ignoreAtStart = 0;
/** Number of elements to ignore at the end of the list. */
private int ignoreAtEnd = 0;
// 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;
// the LM which created the paragraph
private LineLayoutManager layoutManager;
Paragraph(LineLayoutManager llm, int alignment, int alignmentLast,
int indent, int endIndent) {
super();
layoutManager = llm;
textAlignment = alignment;
textAlignmentLast = alignmentLast;
textIndent = indent;
lastLineEndIndent = endIndent;
}
public void startSequence() {
// set the minimum amount of empty space at the end of the
// last line
if (textAlignment == EN_CENTER) {
lineFiller = MinOptMax.getInstance(lastLineEndIndent);
} else {
lineFiller = MinOptMax.getInstance(lastLineEndIndent, lastLineEndIndent,
layoutManager.ipd);
}
// 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 (isFirstInBlock && knuthParagraphs.size() == 0
&& textIndent != 0) {
this.add(new KnuthInlineBox(textIndent, null,
null, false));
ignoreAtStart++;
}
}
public void endParagraph() {
KnuthSequence finishedPar = this.endSequence();
if (finishedPar != null) {
knuthParagraphs.add(finishedPar);
}
}
public KnuthSequence endSequence() {
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.getOpt(), -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.getStretch(),
lineFiller.getShrink(), null, false));
this.add(new KnuthPenalty(lineFiller.getOpt(), -KnuthElement.INFINITE,
false, null, false));
ignoreAtEnd = 3;
} else {
// add only the element representing the forced break
this.add(new KnuthPenalty(lineFiller.getOpt(), -KnuthElement.INFINITE,
false, null, false));
ignoreAtEnd = 1;
}
return this;
} else {
this.clear();
return null;
}
}
/**
* @return true if the sequence contains a box
*/
public boolean containsBox() {
for (int i = 0; i < this.size(); i++) {
KnuthElement el = (KnuthElement)this.get(i);
if (el.isBox()) {
return true;
}
}
return false;
}
}
private class LineBreakingAlgorithm extends BreakingAlgorithm {
private LineLayoutManager thisLLM;
private int pageAlignment;
private int activePossibility;
private int addedPositions;
private int textIndent;
private int lineHeight;
private int lead;
private int follow;
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,
int maxFlagCount, LineLayoutManager llm) {
super(textAlign, textAlignLast, first, false, maxFlagCount);
pageAlignment = pageAlign;
textIndent = indent;
lineHeight = lh;
lead = ld;
follow = fl;
thisLLM = llm;
activePossibility = -1;
}
public void updateData1(int lineCount, double demerits) {
lineLayouts.addPossibility(lineCount, demerits);
if (log.isTraceEnabled()) {
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 && indentFirstPart && isFirstInBlock) ? 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;
}
if (log.isWarnEnabled()) {
int lack = difference + bestActiveNode.availableShrink;
if (lack < 0) {
InlineLevelEventProducer eventProducer
= InlineLevelEventProducer.Provider.get(
getFObj().getUserAgent().getEventBroadcaster());
eventProducer.lineOverflows(this, bestActiveNode.line,
-lack, getFObj().getLocator());
}
}
//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.getShrink()),
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 == ipd);
// 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) {
if (fobj.getLineHeightShiftAdjustment() == EN_CONSIDER_SHIFTS
|| ac.getBaselineShiftValue() == 0) {
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),
firstElementIndex, lastElementIndex,
availableShrink, availableStretch,
difference, ratio, 0, indent,
0, ipd, 0, 0, 0);
} else {
return new LineBreakPosition(thisLLM,
knuthParagraphs.indexOf(par),
firstElementIndex, lastElementIndex,
availableShrink, availableStretch,
difference, ratio, 0, indent,
lineLead + lineFollow,
ipd, spaceBefore, spaceAfter,
lineLead);
}
}
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;
}
/** {@inheritDoc} */
public List getNextKnuthElements(LayoutContext context, int alignment) {
FontInfo fi = fobj.getFOEventHandler().getFontInfo();
FontTriplet[] fontkeys = fobj.getCommonFont().getFontState(fi);
Font fs = fi.getFontInstance(fontkeys[0], fobj.getCommonFont().fontSize.getValue(this));
alignmentContext = new AlignmentContext(fs, lineHeight.getValue(this),
context.getWritingMode());
context.setAlignmentContext(alignmentContext);
ipd = context.getRefIPD();
//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
collectInlineKnuthElements(context);
} 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);
}
public List getNextKnuthElements(LayoutContext context, int alignment,
LeafPosition restartPosition) {
log.trace("Restarting line breaking from index " + restartPosition.getIndex());
int parIndex = restartPosition.getLeafPos();
Paragraph paragraph = (Paragraph) knuthParagraphs.get(parIndex);
for (int i = 0; i <= restartPosition.getIndex(); i++) {
paragraph.remove(0);
}
Iterator iter = paragraph.iterator();
while (iter.hasNext() && !((KnuthElement) iter.next()).isBox()) {
iter.remove();
}
if (!iter.hasNext()) {
knuthParagraphs.remove(parIndex);
}
// return finished when there's no content
if (knuthParagraphs.size() == 0) {
setFinished(true);
return null;
}
ipd = context.getRefIPD();
//PHASE 2: Create line breaks
return createLineBreaks(context.getBPAlignment(), context);
}
/**
* Phase 1 of Knuth algorithm: Collect all inline Knuth elements before determining line breaks.
* @param context the LayoutContext
*/
private void collectInlineKnuthElements(LayoutContext context) {
LayoutContext inlineLC = new LayoutContext(context);
// convert all the text in a sequence of paragraphs made
// of KnuthBox, KnuthGlue and KnuthPenalty objects
boolean previousIsBox = false;
StringBuffer trace = new StringBuffer("LineLM:");
Paragraph lastPar = null;
InlineLevelLayoutManager curLM;
while ((curLM = (InlineLevelLayoutManager) getChildLM()) != null) {
List inlineElements = curLM.getNextKnuthElements(inlineLC, effectiveAlignment);
if (inlineElements == null || inlineElements.size() == 0) {
/* curLM.getNextKnuthElements() returned null or an empty list;
* this can happen if there is nothing more to layout,
* so just iterate once more to see if there are other children */
continue;
}
if (lastPar != null) {
KnuthSequence firstSeq = (KnuthSequence) inlineElements.get(0);
// finish last paragraph before a new block sequence
if (!firstSeq.isInlineSequence()) {
lastPar.endParagraph();
ElementListObserver.observe(lastPar, "line", null);
lastPar = null;
if (log.isTraceEnabled()) {
trace.append(" ]");
}
previousIsBox = false;
}
// does the first element of the first paragraph add to an existing word?
if (lastPar != null) {
KnuthElement thisElement;
thisElement = (KnuthElement) firstSeq.get(0);
if (thisElement.isBox() && !thisElement.isAuxiliary()
&& previousIsBox) {
lastPar.addALetterSpace();
}
}
}
// loop over the KnuthSequences (and single KnuthElements) in returnedList
ListIterator iter = inlineElements.listIterator();
while (iter.hasNext()) {
KnuthSequence sequence = (KnuthSequence) iter.next();
// the sequence contains inline Knuth elements
if (sequence.isInlineSequence()) {
// look at the last element
ListElement lastElement = sequence.getLast();
assert lastElement != null;
previousIsBox = lastElement.isBox()
&& !((KnuthElement) lastElement).isAuxiliary()
&& ((KnuthElement) lastElement).getWidth() != 0;
// 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.startSequence();
if (log.isTraceEnabled()) {
trace.append(" [");
}
} else {
if (log.isTraceEnabled()) {
trace.append(" +");
}
}
lastPar.addAll(sequence);
if (log.isTraceEnabled()) {
trace.append(" I");
}
// finish last paragraph if it was closed with a linefeed
if (lastElement.isPenalty()
&& ((KnuthPenalty) lastElement).getPenalty() == -KnuthPenalty.INFINITE) {
// a penalty item whose value is -inf
// represents a preserved linefeed,
// which forces a line break
lastPar.removeLast();
if (!lastPar.containsBox()) {
//only a forced linefeed on this line
//-> compensate with an auxiliary glue
lastPar.add(new KnuthGlue(ipd, 0, ipd, null, true));
}
lastPar.endParagraph();
ElementListObserver.observe(lastPar, "line", null);
lastPar = null;
if (log.isTraceEnabled()) {
trace.append(" ]");
}
previousIsBox = false;
}
} else { // the sequence is a block sequence
// the positions will be wrapped with this LM in postProcessLineBreaks
knuthParagraphs.add(sequence);
if (log.isTraceEnabled()) {
trace.append(" B");
}
}
} // end of loop over returnedList
}
if (lastPar != null) {
lastPar.endParagraph();
ElementListObserver.observe(lastPar, "line", fobj.getId());
if (log.isTraceEnabled()) {
trace.append(" ]");
}
}
log.trace(trace);
}
/**
* 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 List createLineBreaks(int alignment, LayoutContext context) {
// find the optimal line breaking points for each paragraph
Iterator paragraphsIterator = knuthParagraphs.iterator();
lineLayoutsList = new LineLayoutPossibilities[knuthParagraphs.size()];
LineLayoutPossibilities llPoss;
for (int i = 0; paragraphsIterator.hasNext(); i++) {
KnuthSequence seq = (KnuthSequence) paragraphsIterator.next();
if (!seq.isInlineSequence()) {
// This set of line layout possibilities does not matter;
// we only need an entry in lineLayoutsList.
llPoss = new LineLayoutPossibilities();
} else {
llPoss = findOptimalBreakingPoints(alignment, (Paragraph) seq);
}
lineLayoutsList[i] = llPoss;
}
setFinished(true);
//Post-process the line breaks found
return postProcessLineBreaks(alignment, context);
}
/**
* 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) {
// use the member lineLayouts, which is read by LineBreakingAlgorithm.updateData1 and 2
lineLayouts = new LineLayoutPossibilities();
double maxAdjustment = 1;
int iBPcount = 0;
LineBreakingAlgorithm alg = new LineBreakingAlgorithm(alignment,
textAlignment, textAlignmentLast,
textIndent.getValue(this), currPar.lineFiller.getOpt(),
lineHeight.getValue(this), lead, follow,
(knuthParagraphs.indexOf(currPar) == 0),
hyphenationLadderCount.getEnum() == EN_NO_LIMIT
? 0 : hyphenationLadderCount.getValue(),
this);
if (hyphenationProperties.hyphenate.getEnum() == EN_TRUE
&& fobj.getWrapOption() != EN_NO_WRAP && !hyphenationPerformed) {
hyphenationPerformed = 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(ipd);
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.getEnum() == EN_TRUE));
if (hyphenationProperties.hyphenate.getEnum() == 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
if (log.isDebugEnabled()) {
log.debug("No set of breaking points found with maxAdjustment = "
+ maxAdjustment
+ (hyphenationProperties.hyphenate.getEnum() == 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 = ipd;
ipd = (int) (ipd * 0.95);
iBPcount = alg.findBreakingPoints(currPar,
maxAdjustment, true, allowedBreaks);
// use normal lines, when possible
lineLayouts.restorePossibilities();
ipd = savedLineWidth;
}
if (!lineLayouts.canUseLessLines()) {
alg.resetAlgorithm();
lineLayouts.savePossibilities(true);
// try with longer lines
int savedLineWidth = ipd;
ipd = (int) (ipd * 1.05);
alg.setConstantLineWidth(ipd);
iBPcount = alg.findBreakingPoints(currPar,
maxAdjustment, true, allowedBreaks);
// use normal lines, when possible
lineLayouts.restorePossibilities();
ipd = 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 List postProcessLineBreaks(int alignment, LayoutContext context) {
List returnList = new LinkedList();
int endIndex = -1;
for (int p = 0; p < knuthParagraphs.size(); p++) {
// penalty between paragraphs
if (p > 0) {
Keep keep = getKeepTogether();
returnList.add(new BreakElement(
new Position(this),
keep.getPenalty(),
keep.getContext(),
context));
}
LineLayoutPossibilities llPoss = lineLayoutsList[p];
KnuthSequence seq = (KnuthSequence) knuthParagraphs.get(p);
if (!seq.isInlineSequence()) {
List targetList = new LinkedList();
ListIterator listIter = seq.listIterator();
while (listIter.hasNext()) {
ListElement tempElement;
tempElement = (ListElement) 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, llPoss, returnPosition);
} else {
/* "normal" vertical alignment: create a sequence whose boxes
represent effective lines, and contain LineBreakPositions */
int startIndex = 0;
for (int i = 0;
i < llPoss.getChosenLineCount();
i++) {
if (returnList.size() > 0
&& i > 0 //if i==0 break generated above already
&& i >= fobj.getOrphans()
&& i <= llPoss.getChosenLineCount() - fobj.getWidows()) {
// penalty allowing a page break between lines
Keep keep = getKeepTogether();
returnList.add(new BreakElement(
new LeafPosition(this, p, endIndex),
keep.getPenalty(),
keep.getContext(),
context));
}
endIndex = ((LineBreakPosition) llPoss.getChosenPosition(i)).getLeafPos();
// create a list of the FootnoteBodyLM handling footnotes
// whose citations are in this line
List 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) llPoss.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 llPoss,
Position elementPosition) {
/* number of normal, inner lines */
int innerLines = 0;
/* number of lines that can be used in order to fill more space */
int optionalLines = 0;
/* number of lines that can be used in order to fill more space
only if the paragraph is not parted */
int conditionalOptionalLines = 0;
/* number of lines that can be omitted in order to fill less space */
int eliminableLines = 0;
/* number of lines that can be omitted in order to fill less space
only if the paragraph is not parted */
int conditionalEliminableLines = 0;
/* number of the first unbreakable lines */
int firstLines = fobj.getOrphans();
/* number of the last unbreakable lines */
int lastLines = 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() <= llPoss.getMinLineCount()) {
innerLines = llPoss.getMinLineCount()
- (fobj.getOrphans() + fobj.getWidows());
optionalLines = llPoss.getMaxLineCount()
- llPoss.getOptLineCount();
eliminableLines = llPoss.getOptLineCount()
- llPoss.getMinLineCount();
} else if (fobj.getOrphans() + fobj.getWidows() <= llPoss.getOptLineCount()) {
optionalLines = llPoss.getMaxLineCount()
- llPoss.getOptLineCount();
eliminableLines = llPoss.getOptLineCount()
- (fobj.getOrphans() + fobj.getWidows());
conditionalEliminableLines = (fobj.getOrphans() + fobj.getWidows())
- llPoss.getMinLineCount();
} else if (fobj.getOrphans() + fobj.getWidows() <= llPoss.getMaxLineCount()) {
optionalLines = llPoss.getMaxLineCount()
- (fobj.getOrphans() + fobj.getWidows());
conditionalOptionalLines = (fobj.getOrphans() + fobj.getWidows())
- llPoss.getOptLineCount();
conditionalEliminableLines = llPoss.getOptLineCount()
- llPoss.getMinLineCount();
firstLines -= conditionalOptionalLines;
} else {
conditionalOptionalLines = llPoss.getMaxLineCount()
- llPoss.getOptLineCount();
conditionalEliminableLines = llPoss.getOptLineCount()
- llPoss.getMinLineCount();
firstLines = llPoss.getOptLineCount();
lastLines = 0;
}
/* comment out the previous lines in order to test particular situations */
/* use these lines to test particular situations
innerLines = 0;
optionalLines = 1;
conditionalOptionalLines = 2;
eliminableLines = 0;
conditionalEliminableLines = 0;
firstLines = 1;
lastLines = 3;
*/
if (lastLines != 0
&& (conditionalOptionalLines > 0 || conditionalEliminableLines > 0)) {
breaker.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
breaker.add(new KnuthGlue(0, -conditionalOptionalLines * constantLineHeight,
-conditionalEliminableLines * constantLineHeight,
Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
breaker.add(new KnuthPenalty(conditionalOptionalLines * constantLineHeight,
0, false, elementPosition, false));
breaker.add(new KnuthGlue(0, conditionalOptionalLines * constantLineHeight,
conditionalEliminableLines * constantLineHeight,
Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
} else if (lastLines != 0) {
breaker.add(new KnuthPenalty(0, 0, false, elementPosition, false));
}
//log.debug("first=" + firstLines + " inner=" + innerLines
// + " optional=" + optionalLines + " eliminable=" + eliminableLines
// + " last=" + lastLines
// + " (condOpt=" + conditionalOptionalLines + " condEl=" + conditionalEliminableLines + ")");
// creation of the elements:
// first group of lines
list.add(new KnuthBox(firstLines * constantLineHeight, elementPosition,
(lastLines == 0
&& conditionalOptionalLines == 0
&& conditionalEliminableLines == 0)));
if (conditionalOptionalLines > 0
|| conditionalEliminableLines > 0) {
list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
list.add(new KnuthGlue(0, conditionalOptionalLines * constantLineHeight,
conditionalEliminableLines * constantLineHeight,
Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
list.add(new KnuthBox(0, elementPosition, (lastLines == 0)));
}
// optional lines
for (int i = 0; i < optionalLines; 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, constantLineHeight, 0,
Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
list.add(new KnuthBox(0, elementPosition, false));
}
// eliminable lines
for (int i = 0; i < eliminableLines; i++) {
list.addAll(breaker);
list.add(new KnuthBox(constantLineHeight, elementPosition, false));
list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
list.add(new KnuthGlue(0, 0, constantLineHeight,
Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
list.add(new KnuthBox(0, elementPosition, false));
}
// inner lines
for (int i = 0; i < innerLines; i++) {
list.addAll(breaker);
list.add(new KnuthBox(constantLineHeight, elementPosition, false));
}
// last group of lines
if (lastLines > 0) {
list.addAll(breaker);
list.add(new KnuthBox(lastLines * constantLineHeight,
elementPosition, true));
}
}
/** {@inheritDoc} */
public boolean mustKeepTogether() {
return ((BlockLevelLayoutManager) getParent()).mustKeepTogether();
}
/** {@inheritDoc} */
public KeepProperty getKeepTogetherProperty() {
return ((BlockLevelLayoutManager) getParent()).getKeepTogetherProperty();
}
/** {@inheritDoc} */
public KeepProperty getKeepWithPreviousProperty() {
return ((BlockLevelLayoutManager) getParent()).getKeepWithPreviousProperty();
}
/** {@inheritDoc} */
public KeepProperty getKeepWithNextProperty() {
return ((BlockLevelLayoutManager) getParent()).getKeepWithNextProperty();
}
/** {@inheritDoc} */
public Keep getKeepTogether() {
return ((BlockLevelLayoutManager) getParent()).getKeepTogether();
}
/** {@inheritDoc} */
public boolean mustKeepWithPrevious() {
return !getKeepWithPrevious().isAuto();
}
/** {@inheritDoc} */
public boolean mustKeepWithNext() {
return !getKeepWithNext().isAuto();
}
/** {@inheritDoc} */
public Keep getKeepWithNext() {
return Keep.KEEP_AUTO;
}
/** {@inheritDoc} */
public Keep getKeepWithPrevious() {
return Keep.KEEP_AUTO;
}
/** {@inheritDoc} */
public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) {
LeafPosition pos = (LeafPosition)lastElement.getPosition();
//if (lastElement.isPenalty()) {
// totalAdj += lastElement.getWidth();
//}
//int lineNumberDifference = (int)((double) totalAdj / constantLineHeight);
int lineNumberDifference = (int) Math.round((double) adj / constantLineHeight
+ (adj > 0 ? - 0.4 : 0.4));
//log.debug(" LLM> variazione calcolata = " + ((double) totalAdj / constantLineHeight) + " variazione applicata = " + lineNumberDifference);
LineLayoutPossibilities llPoss;
llPoss = lineLayoutsList[pos.getLeafPos()];
lineNumberDifference = llPoss.applyLineCountAdjustment(lineNumberDifference);
return lineNumberDifference * constantLineHeight;
}
/** {@inheritDoc} */
public void discardSpace(KnuthGlue spaceGlue) {
}
/** {@inheritDoc} */
public List getChangedKnuthElements(List oldList, int alignment) {
List returnList = new LinkedList();
for (int p = 0; p < knuthParagraphs.size(); p++) {
LineLayoutPossibilities llPoss = lineLayoutsList[p];
//log.debug("demerits of the chosen layout: " + llPoss.getChosenDemerits());
for (int i = 0; i < llPoss.getChosenLineCount(); i++) {
if (!((BlockLevelLayoutManager) parentLayoutManager).mustKeepTogether()
&& i >= fobj.getOrphans()
&& i <= llPoss.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) llPoss.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 = MinOptMax.getInstance(
lbp.lineWidth - lbp.difference - lbp.availableShrink,
lbp.lineWidth - lbp.difference,
lbp.lineWidth - lbp.difference + lbp.availableStretch);
} else if (alignment == EN_CENTER) {
contentIPD = MinOptMax.getInstance(lbp.lineWidth - 2 * lbp.startIndent);
} else if (alignment == EN_END) {
contentIPD = MinOptMax.getInstance(lbp.lineWidth - lbp.startIndent);
} else {
contentIPD = MinOptMax.getInstance(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 in 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
List updateList = new LinkedList();
KnuthElement firstElement, nextElement;
// current InlineLevelLayoutManager
InlineLevelLayoutManager currLM = null;
// number of KnuthBox elements containing word fragments
int boxCount;
// number of auxiliary KnuthElements between KnuthBoxes
int auxCount;
StringBuffer sbChars;
// 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;
}
} else if (currLM == null) {
break;
}
//TODO Something's not right here. See block_hyphenation_linefeed_preserve.xml
//for more info: see also https://issues.apache.org/bugzilla/show_bug.cgi?id=38264
// 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();
sbChars.append(currLM.getWordChars(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++;
sbChars.append(currLM.getWordChars(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++;
}
}
if (log.isTraceEnabled()) {
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
}
}
}
}
}
processUpdates(currPar, updateList);
}
private void processUpdates(Paragraph par, List updateList) {
// create iterator for the updateList
ListIterator updateListIterator = updateList.listIterator();
Update currUpdate;
int elementsAdded = 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.firstIndex;
int toIndex;
if (updateListIterator.hasNext()) {
Update nextUpdate = (Update) updateListIterator.next();
toIndex = nextUpdate.firstIndex;
updateListIterator.previous();
} else {
// maybe this is not always correct!
toIndex = par.size() - par.ignoreAtEnd
- elementsAdded;
}
// applyChanges() returns true if the LM modifies its data,
// so it must return new KnuthElements to replace the old ones
if (currUpdate.inlineLM
.applyChanges(par.subList(fromIndex + elementsAdded,
toIndex + elementsAdded))) {
// insert the new KnuthElements
List newElements = currUpdate.inlineLM.getChangedKnuthElements
(par.subList(fromIndex + elementsAdded,
toIndex + elementsAdded),
/*flaggedPenalty,*/ effectiveAlignment);
// remove the old elements
par.subList(fromIndex + elementsAdded,
toIndex + elementsAdded).clear();
// insert the new elements
par.addAll(fromIndex + elementsAdded, newElements);
elementsAdded += newElements.size() - (toIndex - fromIndex);
}
}
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.getString(),
hyphenationProperties.country.getString(),
getFObj().getUserAgent().getFactory().getHyphenationTreeResolver(),
sbChars.toString(),
hyphenationProperties.hyphenationRemainCharacterCount.getValue(),
hyphenationProperties.hyphenationPushCharacterCount.getValue());
// 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;
}
}
/**
* 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) {
while (parentIter.hasNext()) {
Position pos = (Position) parentIter.next();
boolean isLastPosition = !parentIter.hasNext();
if (pos instanceof LineBreakPosition) {
addInlineArea(context, (LineBreakPosition) pos, isLastPosition);
} else if ((pos instanceof NonLeafPosition) && pos.generatesAreas()) {
addBlockArea(context, pos, isLastPosition);
} else {
/*
* pos was the Position inside a penalty item, nothing to do;
* or Pos does not generate an area,
* i.e. it stand for spaces, borders and padding.
*/
}
}
setCurrentArea(null); // ?? necessary
}
/**
* Add a line with inline content
* @param context the context for adding areas
* @param lbp the position for which the line is generated
* @param isLastPosition true if this is the last position of this LM
*/
private void addInlineArea(LayoutContext context, LineBreakPosition lbp,
boolean isLastPosition) {
// the TLM which created the last KnuthElement in this line
LayoutManager lastLM = null;
KnuthSequence seq = (KnuthSequence) knuthParagraphs.get(lbp.parIndex);
int startElementIndex = lbp.startIndex;
int endElementIndex = lbp.getLeafPos();
LineArea lineArea = new LineArea(
(lbp.getLeafPos() < seq.size() - 1 ? textAlignment : textAlignmentLast),
lbp.difference, lbp.availableStretch, lbp.availableShrink);
if (lbp.startIndent != 0) {
lineArea.addTrait(Trait.START_INDENT, new Integer(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
startElementIndex += (startElementIndex == 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 (endElementIndex == (currPar.size() - 1)) {
endElementIndex -= currPar.ignoreAtEnd;
lineArea.setIPD(lineArea.getIPD() - lastLineEndIndent.getValue(this));
}
}
// Remove trailing spaces if allowed so
if (whiteSpaceTreament == EN_IGNORE_IF_SURROUNDING_LINEFEED
|| whiteSpaceTreament == EN_IGNORE
|| whiteSpaceTreament == EN_IGNORE_IF_BEFORE_LINEFEED) {
// ignore the last element in the line if it is a KnuthGlue object
ListIterator seqIterator = seq.listIterator(endElementIndex);
KnuthElement lastElement = (KnuthElement) seqIterator.next();
lastLM = lastElement.getLayoutManager();
if (lastElement.isGlue()) {
endElementIndex--;
// this returns the same KnuthElement
seqIterator.previous();
if (seqIterator.hasPrevious()) {
lastLM = ((KnuthElement) seqIterator.previous()).getLayoutManager();
}
}
}
// Remove leading spaces if allowed so
if (whiteSpaceTreament == EN_IGNORE_IF_SURROUNDING_LINEFEED
|| whiteSpaceTreament == EN_IGNORE
|| whiteSpaceTreament == EN_IGNORE_IF_AFTER_LINEFEED) {
// ignore KnuthGlue and KnuthPenalty objects
// at the beginning of the line
ListIterator seqIterator = seq.listIterator(startElementIndex);
while (seqIterator.hasNext() && !((KnuthElement) seqIterator.next()).isBox()) {
startElementIndex++;
}
}
// Add the inline areas to lineArea
PositionIterator inlinePosIter = new KnuthPossPosIter(seq, startElementIndex,
endElementIndex + 1);
LayoutContext lc = new LayoutContext(0);
lc.setAlignmentContext(alignmentContext);
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.getRefIPD()
- 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.getRefIPD() - lbp.lineWidth) / 2;
lineArea.addTrait(Trait.START_INDENT, new Integer(updatedIndent));
} else if (false && textAlignment == EN_END) {
// re-compute indent
int updatedIndent = lbp.startIndent
+ (context.getRefIPD() - lbp.lineWidth);
lineArea.addTrait(Trait.START_INDENT, new Integer(updatedIndent));
}
setCurrentArea(lineArea);
setChildContext(lc);
LayoutManager childLM;
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));
}
// if display-align is distribute, add space after
if (context.getSpaceAfter() > 0
&& (!context.isLastArea() || !isLastPosition)) {
lineArea.setBPD(lineArea.getBPD() + context.getSpaceAfter());
}
lineArea.finalise();
parentLayoutManager.addChildArea(lineArea);
}
/**
* Add a line with block content
* @param context the context for adding areas
* @param pos the position for which the line is generated
* @param isLastPosition true if this is the last position of this LM
*/
private void addBlockArea(LayoutContext context, Position pos, boolean isLastPosition) {
/* Nested block-level content;
* go down the LM stack again;
* "unwrap" the positions and put the child positions in a new list.
* The positionList must contain one area-generating position,
* which creates one line area.
*/
List positionList = new ArrayList(1);
Position innerPosition = pos.getPosition();
positionList.add(innerPosition);
// do we have the last LM?
LayoutManager lastLM = null;
if (isLastPosition) {
lastLM = innerPosition.getLM();
}
LineArea lineArea = new LineArea();
setCurrentArea(lineArea);
LayoutContext lc = new LayoutContext(0);
lc.setAlignmentContext(alignmentContext);
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);
LayoutManager childLM;
while ((childLM = childPosIter.getNextChildLM()) != null) {
// set last area flag
blocklc.setFlags(LayoutContext.LAST_AREA,
(context.isLastArea() && childLM == lastLM));
blocklc.setStackLimitBP(context.getStackLimitBP());
// Add the line areas to Area
childLM.addAreas(childPosIter, blocklc);
blocklc.setLeadingSpace(blocklc.getTrailingSpace());
blocklc.setTrailingSpace(new SpaceSpecifier(false));
}
lineArea.updateExtentsFromChildren();
parentLayoutManager.addChildArea(lineArea);
}
/** {@inheritDoc} */
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 --------- //
/** {@inheritDoc} */
public boolean getGeneratesBlockArea() {
return true;
}
/** {@inheritDoc} */
public boolean getGeneratesLineArea() {
return true;
}
/** {@inheritDoc} */
public boolean isRestartable() {
return true;
}
}