/* * 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$ */ package org.apache.fop.layoutmgr.inline; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.fop.area.Area; import org.apache.fop.area.LineArea; import org.apache.fop.area.Trait; import org.apache.fop.area.inline.InlineArea; import org.apache.fop.datatypes.Length; import org.apache.fop.datatypes.Numeric; import org.apache.fop.fo.Constants; import org.apache.fop.fo.flow.Block; import org.apache.fop.fo.properties.CommonHyphenation; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.FontTriplet; import org.apache.fop.hyphenation.Hyphenation; import org.apache.fop.hyphenation.Hyphenator; import org.apache.fop.layoutmgr.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.traits.MinOptMax; /** * LayoutManager for lines. It builds one or more lines containing * inline areas generated by its sub layout managers. * A break is found for each line which may contain one of more * breaks from the child layout managers. * Once a break is found then it is return for the parent layout * manager to handle. * When the areas are being added to the page this manager * creates a line area to contain the inline areas added by the * child layout managers. */ public class LineLayoutManager extends InlineStackingLayoutManager implements BlockLevelLayoutManager { /** * logging instance */ private static Log log = LogFactory.getLog(LineLayoutManager.class); private Block fobj; private boolean isFirstInBlock; /** {@inheritDoc} */ public void initialize() { textAlignment = fobj.getTextAlign(); textAlignmentLast = fobj.getTextAlignLast(); textIndent = fobj.getTextIndent(); lastLineEndIndent = fobj.getLastLineEndIndent(); hyphenationProperties = fobj.getCommonHyphenation(); hyphenationLadderCount = fobj.getHyphenationLadderCount(); wrapOption = fobj.getWrapOption(); whiteSpaceTreament = fobj.getWhitespaceTreatment(); // effectiveAlignment = getEffectiveAlignment(textAlignment, textAlignmentLast); isFirstInBlock = (this == getParent().getChildLMs().get(0)); } private int getEffectiveAlignment(int alignment, int alignmentLast) { if (textAlignment != EN_JUSTIFY && textAlignmentLast == EN_JUSTIFY) { return 0; } else { return textAlignment; } } /** * Private class to store information about inline breaks. * Each value holds the start and end indexes into a List of * inline break positions. */ private static class LineBreakPosition extends LeafPosition { private int 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; // the LM which created the paragraph private LineLayoutManager layoutManager; Paragraph(LineLayoutManager llm, int alignment, int alignmentLast, int indent, int endIndent) { super(); layoutManager = llm; textAlignment = alignment; textAlignmentLast = alignmentLast; textIndent = indent; lastLineEndIndent = endIndent; } public void startSequence() { // set the minimum amount of empty space at the end of the // last line if (textAlignment == EN_CENTER) { lineFiller = new MinOptMax(lastLineEndIndent); } else { lineFiller = new MinOptMax(lastLineEndIndent, lastLineEndIndent, layoutManager.iLineWidth); } // 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); if (super.log.isTraceEnabled()) { super.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 (log.isWarnEnabled()) { int lack = difference + bestActiveNode.availableShrink; if (lack < 0) { InlineLevelEventProducer eventProducer = InlineLevelEventProducer.Provider.get( getFObj().getUserAgent().getEventBroadcaster()); eventProducer.lineOverflows(this, addedPositions + 1, -lack, getFObj().getLocator()); String textDiff = (lack < -50000 ? "more than 50 points" : (-lack) + "mpt"); } } //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) { if (fobj.getLineHeightShiftAdjustment() == EN_CONSIDER_SHIFTS || ac.getBaselineShiftValue() == 0) { int alignmentOffset = ac.getTotalAlignmentBaselineOffset(); if (alignmentOffset + ac.getAltitude() > lineLead) { lineLead = alignmentOffset + ac.getAltitude(); } if (ac.getDepth() - alignmentOffset > lineFollow) { lineFollow = ac.getDepth() - alignmentOffset; } } } else { if (ac.getHeight() > maxIgnoredHeight) { maxIgnoredHeight = ac.getHeight(); } } lastAC = ac; } if (bZeroHeightLine && (!element.isAuxiliary() || ac != null && ac.getHeight() > 0)) { bZeroHeightLine = false; } } } if (lineFollow < maxIgnoredHeight - lineLead) { lineFollow = maxIgnoredHeight - lineLead; } } constantLineHeight = lineLead + lineFollow; if (bZeroHeightLine) { return new LineBreakPosition(thisLLM, knuthParagraphs.indexOf(par), firstElementIndex, lastElementIndex, availableShrink, availableStretch, difference, ratio, 0, indent, 0, 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; } /** {@inheritDoc} */ public LinkedList getNextKnuthElements(LayoutContext context, int alignment) { FontInfo fi = fobj.getFOEventHandler().getFontInfo(); FontTriplet[] fontkeys = fobj.getCommonFont().getFontState(fi); Font fs = fi.getFontInstance(fontkeys[0], fobj.getCommonFont().fontSize.getValue(this)); alignmentContext = new AlignmentContext(fs, lineHeight.getValue(this), context.getWritingMode()); context.setAlignmentContext(alignmentContext); // Get a break from currently active child LM // Set up constraints for inline level managers 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 collectInlineKnuthElements(context); } else { // this method has been called before // all line breaks are already calculated } // return finished when there's no content if (knuthParagraphs.size() == 0) { setFinished(true); return null; } //PHASE 2: Create line breaks return createLineBreaks(context.getBPAlignment(), context); /* 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 */ private void collectInlineKnuthElements(LayoutContext context) { LayoutContext inlineLC = new LayoutContext(context); InlineLevelLayoutManager curLM; LinkedList returnedList = null; iLineWidth = context.getStackLimitIP().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 || returnedList.size() == 0) { /* curLM.getNextKnuthElements() returned null or an empty list; * this can happen if there is nothing more to layout, * so just iterate once more to see if there are other children */ continue; } if (lastPar != null) { KnuthSequence firstSeq = (KnuthSequence) 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 = sequence.getLast(); if (lastElement == null) { throw new NullPointerException( "Sequence was empty! lastElement is null"); } bPrevWasKnuthBox = lastElement.isBox() && !((KnuthElement) lastElement).isAuxiliary() && ((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.startSequence(); if (log.isTraceEnabled()) { trace.append(" ["); } } else { if (log.isTraceEnabled()) { trace.append(" +"); } } lastPar.addAll(sequence); if (log.isTraceEnabled()) { trace.append(" I"); } // finish last paragraph if it was closed with a linefeed if (lastElement.isPenalty() && ((KnuthPenalty) lastElement).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 an auxiliary glue lastPar.add(new KnuthGlue(iLineWidth, 0, iLineWidth, null, true)); } 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.getEnum() == 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.getEnum() == EN_TRUE)); if (hyphenationProperties.hyphenate.getEnum() == EN_TRUE && !(allowedBreaks == BreakingAlgorithm.ONLY_FORCED_BREAKS)) { // consider every hyphenation point as a legal break allowedBreaks = BreakingAlgorithm.ALL_BREAKS; } else { // try with a higher threshold maxAdjustment = 5; } if ((iBPcount = alg.findBreakingPoints(currPar, maxAdjustment, false, allowedBreaks)) == 0) { // the second try failed too, try with a huge threshold // and force the algorithm to find // a set of breaking points if (log.isDebugEnabled()) { log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment + (hyphenationProperties.hyphenate.getEnum() == EN_TRUE ? " and hyphenation" : "")); } maxAdjustment = 20; iBPcount = alg.findBreakingPoints(currPar, maxAdjustment, true, allowedBreaks); } // use non-hyphenated breaks, when possible lineLayouts.restorePossibilities(); /* extension (not in the XSL FO recommendation): if vertical alignment is justify and the paragraph has only one layout, try using shorter or longer lines */ //TODO This code snippet is disabled. Reenable? if (false && alignment == EN_JUSTIFY && textAlignment == EN_JUSTIFY) { //log.debug("LLM.getNextKnuthElements> layouts with more lines? " + lineLayouts.canUseMoreLines()); //log.debug(" layouts with fewer lines? " + lineLayouts.canUseLessLines()); if (!lineLayouts.canUseMoreLines()) { alg.resetAlgorithm(); lineLayouts.savePossibilities(true); // try with shorter lines int savedLineWidth = 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)); } } /** * {@inheritDoc} */ public boolean mustKeepTogether() { return ((BlockLevelLayoutManager) getParent()).mustKeepTogether(); } /** * {@inheritDoc} */ public boolean mustKeepWithPrevious() { return false; } /** * {@inheritDoc} */ public boolean mustKeepWithNext() { return false; } /** * {@inheritDoc} */ 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; } /** * {@inheritDoc} */ public void discardSpace(KnuthGlue spaceGlue) { } /** * {@inheritDoc} */ 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++; } } if (log.isTraceEnabled()) { log.trace(" Word to hyphenate: " + sbChars.toString()); } // find hyphenation points HyphContext hc = getHyphenContext(sbChars); // ask each LM to hyphenate its word fragment if (hc != null) { KnuthElement element = null; for (int i = 0; i < (boxCount + auxCount); i++) { currParIterator.previous(); } for (int i = 0; i < (boxCount + auxCount); i++) { element = (KnuthElement) currParIterator.next(); if (element.isBox() && !element.isAuxiliary()) { ((InlineLevelLayoutManager) element.getLayoutManager()).hyphenate(element.getPosition(), hc); } else { // nothing to do, element is an auxiliary KnuthElement } } } } } // 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.getString(), hyphenationProperties.country.getString(), getFObj().getUserAgent().getFactory().getHyphenationTreeResolver(), sbChars.toString(), hyphenationProperties.hyphenationRemainCharacterCount.getValue(), hyphenationProperties.hyphenationPushCharacterCount.getValue()); // They hyph structure contains the information we need // Now start from prev: reset to that position, ask that LM to get // a Position for the first hyphenation offset. If the offset isn't in // its characters, it returns null, // but must tell how many chars it had. // Keep looking at currentBP using next hyphenation point until the // returned size is greater than the available size // or no more hyphenation points remain. Choose the best break. if (hyph != null) { return new HyphContext(hyph.getHyphenationPoints()); } else { return null; } } /** * Add the areas with the break points. * * @param parentIter the iterator of break positions * @param context the context for adding areas */ public void addAreas(PositionIterator parentIter, LayoutContext context) { while (parentIter.hasNext()) { Position pos = (Position) parentIter.next(); boolean isLastPosition = !parentIter.hasNext(); if (pos instanceof LineBreakPosition) { addInlineArea(context, 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.getStackLimitIP().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.getStackLimitIP().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.getStackLimitIP().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.setStackLimitsFrom(context); // Add the line areas to Area childLM.addAreas(childPosIter, blocklc); blocklc.setLeadingSpace(blocklc.getTrailingSpace()); blocklc.setTrailingSpace(new SpaceSpecifier(false)); } lineArea.updateExtentsFromChildren(); parentLM.addChildArea(lineArea); } /** * {@inheritDoc} */ public void addChildArea(Area childArea) { // Make sure childArea is inline area if (childArea instanceof InlineArea) { Area parent = getCurrentArea(); if (getContext().resolveLeadingSpace()) { addSpace(parent, getContext().getLeadingSpace().resolve(false), getContext().getSpaceAdjust()); } parent.addChildArea(childArea); } } // --------- Property Resolution related functions --------- // /** * {@inheritDoc} */ public boolean getGeneratesBlockArea() { return true; } /** * {@inheritDoc} */ public boolean getGeneratesLineArea() { return true; } }