diff options
author | Manuel Mall <manuel@apache.org> | 2006-04-27 10:21:04 +0000 |
---|---|---|
committer | Manuel Mall <manuel@apache.org> | 2006-04-27 10:21:04 +0000 |
commit | d479f880b8c4dd9071b9e4a5b78aedd6a4b97b8f (patch) | |
tree | 20a3d06c27432889b9b25b01dc5adaa7512c7604 /test | |
parent | fc466abd9704dd97881d8650495e23eb947307fe (diff) | |
download | xmlgraphics-fop-d479f880b8c4dd9071b9e4a5b78aedd6a4b97b8f.tar.gz xmlgraphics-fop-d479f880b8c4dd9071b9e4a5b78aedd6a4b97b8f.zip |
Test case for Bug 39414
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@397511 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'test')
-rw-r--r-- | test/layoutengine/disabled-testcases.xml | 10 | ||||
-rwxr-xr-x | test/layoutengine/standard-testcases/block_bug39414.xml | 1872 |
2 files changed, 1880 insertions, 2 deletions
diff --git a/test/layoutengine/disabled-testcases.xml b/test/layoutengine/disabled-testcases.xml index 03fd4e64c..3c14b5cb9 100644 --- a/test/layoutengine/disabled-testcases.xml +++ b/test/layoutengine/disabled-testcases.xml @@ -47,13 +47,12 @@ <name>linefeed-treatment</name> <file>block_linefeed-treatment.xml</file> <description>Preserved linefeeds in a fo:character are not handled - correctly. Zero width space characters are not handled correctly.</description> </testcase> <testcase> <name>white-space-treatment</name> <file>block_white-space-treatment_3.xml</file> - <description>White space handling incorrectly stops at inline + <description>White space handling incorrectly stops at fo:inline boundaries when it comes to formatter generated line breaks.</description> </testcase> <testcase> @@ -338,4 +337,11 @@ block-level element because its layout manager is written to handle only inline content.</description> </testcase> + <testcase> + <name>Bugzilla #39414: block containing long text</name> + <file>block_bug39414.xml</file> + <description>Long text cause an IndexOutOfBounds exception</description> + <reference>http://issues.apache.org/bugzilla/show_bug.cgi?id=39414</reference> + </testcase> + <testcase> </disabled-testcases>
\ No newline at end of file diff --git a/test/layoutengine/standard-testcases/block_bug39414.xml b/test/layoutengine/standard-testcases/block_bug39414.xml new file mode 100755 index 000000000..bf6a22362 --- /dev/null +++ b/test/layoutengine/standard-testcases/block_bug39414.xml @@ -0,0 +1,1872 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright 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$ --> +<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="5in" 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" 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 360000 360000" xpath="/areaTree/pageSequence/pageViewport/@bounds" desc="page size"/> + </checks> +</testcase> |