blob: 921e12256f885529bbafe7706d96d78337b205e5 [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8"?>
<!--
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$ -->
<testcase>
<info>
<p>
This test checks Bug 39414 - Index overflow on long texts
</p>
</info>
<fo>
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:svg="http://www.w3.org/2000/svg">
<fo:layout-master-set>
<fo:simple-page-master master-name="normal" page-width="10in" page-height="5in">
<fo:region-body/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="normal" white-space-collapse="true">
<fo:flow flow-name="xsl-region-body">
<fo:block font-family="monospace" font-size="8pt" white-space="pre"><![CDATA[
/*
* Copyright 1999-2006 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$ */
package org.apache.fop.layoutmgr.inline;
import org.apache.fop.datatypes.Length;
import org.apache.fop.datatypes.Numeric;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FONode;
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.InlineKnuthSequence;
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.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.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;
private boolean isFirstInBlock;
/** @see org.apache.fop.layoutmgr.LayoutManager#initialize() */
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 iParIndex; // index of the Paragraph this Position refers to
private int iStartIndex; //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 iStartIndex, 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;
this.iStartIndex = iStartIndex;
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 = null;
private List knuthParagraphs = null;
private int iReturnedLBP = 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 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;
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();
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 (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.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;
}
}
/**
* @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 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,
int maxFlagCount, LineLayoutManager llm) {
super(textAlign, textAlignLast, first, false, maxFlagCount);
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 && 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 (difference + bestActiveNode.availableShrink < 0) {
if (log.isWarnEnabled()) {
log.warn(FONode.decorateWithContextInfo(
"Line " + (addedPositions + 1)
+ " of a paragraph overflows the available area.", getFObj()));
}
}
//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),
firstElementIndex, lastElementIndex,
availableShrink, availableStretch,
difference, ratio, 0, indent,
0, iLineWidth, 0, 0, 0);
} else {
return new LineBreakPosition(thisLLM,
knuthParagraphs.indexOf(par),
firstElementIndex, 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
// 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) {
returnedList = curLM.getNextKnuthElements(inlineLC, effectiveAlignment);
if (returnedList == null) {
// 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
continue;
}
if (returnedList.size() == 0) {
continue;
}
if (lastPar != null) {
KnuthSequence firstSeq = (KnuthSequence) returnedList.getFirst();
// 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(" ]");
}
bPrevWasKnuthBox = 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()
&& bPrevWasKnuthBox) {
lastPar.addALetterSpace();
}
}
}
// loop over the KnuthSequences (and single KnuthElements) in returnedList
ListIterator iter = returnedList.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;
lastElement = sequence.getLast();
if (lastElement == null) {
throw new NullPointerException(
"Sequence was empty! lastElement is null");
}
bPrevWasKnuthBox = lastElement.isBox() && ((KnuthElement) lastElement).getW() != 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.startParagraph(availIPD.opt);
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).getP()
== -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 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
// 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);
}
/**
* 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());
LineLayoutPossibilities llPoss;
while (paragraphsIterator.hasPrevious()) {
KnuthSequence seq = (KnuthSequence) paragraphsIterator.previous();
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.add(0, 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.opt,
lineHeight.getValue(this), lead, follow,
(knuthParagraphs.indexOf(currPar) == 0),
hyphenationLadderCount.getEnum() == EN_NO_LIMIT
? 0 : hyphenationLadderCount.getValue(),
this);
if (hyphenationProperties.hyphenate == EN_TRUE
&& fobj.getWrapOption() != EN_NO_WRAP) {
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));
}
LineLayoutPossibilities llPoss;
llPoss = (LineLayoutPossibilities) lineLayoutsList.get(p);
KnuthSequence seq = (KnuthSequence) knuthParagraphs.get(p);
if (!seq.isInlineSequence()) {
LinkedList 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 */
Position returnPosition = new LeafPosition(this, p);
int startIndex = 0;
for (int i = 0;
i < llPoss.getChosenLineCount();
i++) {
if (!((BlockLevelLayoutManager) parentLM).mustKeepTogether()
&& i >= fobj.getOrphans()
&& i <= llPoss.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) llPoss.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) 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 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() <= llPoss.getMinLineCount()) {
nInnerLines = llPoss.getMinLineCount()
- (fobj.getOrphans() + fobj.getWidows());
nOptionalLines = llPoss.getMaxLineCount()
- llPoss.getOptLineCount();
nEliminableLines = llPoss.getOptLineCount()
- llPoss.getMinLineCount();
} else if (fobj.getOrphans() + fobj.getWidows() <= llPoss.getOptLineCount()) {
nOptionalLines = llPoss.getMaxLineCount()
- llPoss.getOptLineCount();
nEliminableLines = llPoss.getOptLineCount()
- (fobj.getOrphans() + fobj.getWidows());
nConditionalEliminableLines = (fobj.getOrphans() + fobj.getWidows())
- llPoss.getMinLineCount();
} else if (fobj.getOrphans() + fobj.getWidows() <= llPoss.getMaxLineCount()) {
nOptionalLines = llPoss.getMaxLineCount()
- (fobj.getOrphans() + fobj.getWidows());
nConditionalOptionalLines = (fobj.getOrphans() + fobj.getWidows())
- llPoss.getOptLineCount();
nConditionalEliminableLines = llPoss.getOptLineCount()
- llPoss.getMinLineCount();
nFirstLines -= nConditionalOptionalLines;
} else {
nConditionalOptionalLines = llPoss.getMaxLineCount()
- llPoss.getOptLineCount();
nConditionalEliminableLines = llPoss.getOptLineCount()
- llPoss.getMinLineCount();
nFirstLines = llPoss.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 ((BlockLevelLayoutManager) getParent()).mustKeepTogether();
}
/**
* @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);
LineLayoutPossibilities llPoss;
llPoss = (LineLayoutPossibilities) lineLayoutsList.get(pos.getLeafPos());
lineNumberDifference = llPoss.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++) {
LineLayoutPossibilities llPoss;
llPoss = (LineLayoutPossibilities)lineLayoutsList.get(p);
//log.debug("demerits of the chosen layout: " + llPoss.getChosenDemerits());
for (int i = 0; i < llPoss.getChosenLineCount(); i++) {
if (!((BlockLevelLayoutManager) parentLM).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 = 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;
}
} else if (currLM == null) {
break;
}
//TODO Something's not right here. See block_hyphenation_linefeed_preserve.xml
// 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,
getFObj().getUserAgent().getFactory().getHyphenationTreeResolver(),
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--;
}
// It is not clear that the member lineLayouts has the correct value;
// because the method is not called, this cannot be checked.
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) {
while (parentIter.hasNext()) {
Position pos = (Position) parentIter.next();
boolean isLastPosition = !parentIter.hasNext();
if (pos instanceof LineBreakPosition) {
addInlineArea(context, 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 pos 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, Position pos, boolean isLastPosition) {
ListIterator seqIterator = null;
KnuthElement tempElement = null;
// the TLM which created the last KnuthElement in this line
LayoutManager lastLM = null;
LineBreakPosition lbp = (LineBreakPosition) pos;
int iCurrParIndex;
iCurrParIndex = lbp.iParIndex;
KnuthSequence seq = (KnuthSequence) knuthParagraphs.get(iCurrParIndex);
int iStartElement = lbp.iStartIndex;
int iEndElement = 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
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));
}
}
// 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
seqIterator = seq.listIterator(iEndElement);
tempElement = (KnuthElement) seqIterator.next();
if (tempElement.isGlue()) {
iEndElement--;
// this returns the same KnuthElement
seqIterator.previous();
if (seqIterator.hasPrevious()) {
tempElement = (KnuthElement) seqIterator.previous();
} else {
tempElement = null;
}
}
if (tempElement != null) {
lastLM = tempElement.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
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;
}
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.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.addTrait(Trait.START_INDENT, new Integer(updatedIndent));
} else if (false && textAlignment == EN_END) {
// re-compute indent
int updatedIndent = lbp.startIndent
+ (context.getStackLimit().opt - 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));
}
// when can this be null?
// if display-align is distribute, add space after
if (context.getSpaceAfter() > 0
&& (!context.isLastArea() || !isLastPosition)) {
lineArea.setBPD(lineArea.getBPD() + context.getSpaceAfter());
}
lineArea.finalise();
parentLM.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;
innerPosition = ((NonLeafPosition) 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.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);
}
/**
* @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;
}
}
]]>
</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
</fo>
<checks>
<!-- Not a proper test !! -->
<eval expected="0 0 720000 360000" xpath="/areaTree/pageSequence/pageViewport/@bounds" desc="page size"/>
</checks>
</testcase>