This test checks Bug 39414 - Index overflow on long texts
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;
}
}
]]>