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