Java tutorial
/* * 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: LineLayoutManager.java 1327157 2012-04-17 16:15:44Z gadams $ */ package org.apache.fop.layoutmgr.inline; import java.util.ArrayList; import java.util.Iterator; 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.complexscripts.bidi.BidiResolver; 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.fo.properties.KeepProperty; 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.Adjustment; import org.apache.fop.layoutmgr.BlockLayoutManager; 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.Keep; 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 { /** * 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; /** * logging instance */ private static Log log = LogFactory.getLog(LineLayoutManager.class); private final Block fobj; private boolean isFirstInBlock; /** * 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 final int parIndex; // index of the Paragraph this Position refers to private final int startIndex; //index of the first element this Position refers to private final int availableShrink; private final int availableStretch; private final int difference; private final double dAdjust; // Percentage to adjust (stretch or shrink) private final double ipdAdjust; // Percentage to adjust (stretch or shrink) private final int startIndent; private final int endIndent; private final int lineHeight; private final int lineWidth; private final int spaceBefore; private final int spaceAfter; private final int baseline; LineBreakPosition( // CSOK: ParameterNumber LayoutManager lm, int index, int startIndex, int breakIndex, int shrink, int stretch, int diff, double ipdA, double adjust, int si, int ei, int lh, int lw, int sb, int sa, int bl) { super(lm, breakIndex); availableShrink = shrink; availableStretch = stretch; difference = diff; parIndex = index; this.startIndex = startIndex; ipdAdjust = ipdA; dAdjust = adjust; startIndent = si; endIndent = ei; lineHeight = lh; lineWidth = lw; spaceBefore = sb; spaceAfter = sa; baseline = bl; } } private int bidiLevel = -1; 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 final Length lineHeight; private final int lead; private final int follow; private AlignmentContext alignmentContext; private List<KnuthSequence> knuthParagraphs; private LineLayoutPossibilities lineLayouts; private LineLayoutPossibilities[] lineLayoutsList; private int ipd = 0; /** * When layout must be re-started due to a change of IPD, there is no need * to perform hyphenation on the remaining Knuth sequence once again. */ private boolean hyphenationPerformed; /** * This class is used to remember * which was the first element in the paragraph * returned by each LM. */ private final class Update { private final InlineLevelLayoutManager inlineLM; private final int firstIndex; private Update(InlineLevelLayoutManager lm, int index) { inlineLM = lm; firstIndex = index; } } // this class represents a paragraph private class Paragraph extends InlineKnuthSequence { private static final long serialVersionUID = 5862072380375189105L; /** 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 final int textAlignment; private final int textAlignmentLast; private final int textIndent; private final int lastLineEndIndent; // the LM which created the paragraph private final LineLayoutManager layoutManager; Paragraph(LineLayoutManager llm, int alignment, int alignmentLast, int indent, int endIndent) { super(); layoutManager = llm; textAlignment = alignment; textAlignmentLast = alignmentLast; textIndent = indent; lastLineEndIndent = endIndent; } @Override public void startSequence() { // set the minimum amount of empty space at the end of the // last line if (textAlignment == EN_CENTER) { lineFiller = MinOptMax.getInstance(lastLineEndIndent); } else { lineFiller = MinOptMax.getInstance(lastLineEndIndent, lastLineEndIndent, layoutManager.ipd); } // 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); } } @Override 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.getOpt(), -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.getStretch(), lineFiller.getShrink(), null, false)); this.add(new KnuthPenalty(lineFiller.getOpt(), -KnuthElement.INFINITE, false, null, false)); ignoreAtEnd = 3; } else { // add only the element representing the forced break this.add(new KnuthPenalty(lineFiller.getOpt(), -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 final LineLayoutManager thisLLM; private final int pageAlignment; private int activePossibility; private int addedPositions; private final int textIndent; private final int lineHeight; private final int lead; private final int follow; private static final double MAX_DEMERITS = 10e6; public LineBreakingAlgorithm( // CSOK: ParameterNumber 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; lineHeight = lh; lead = ld; follow = fl; thisLLM = llm; activePossibility = -1; } @Override public void updateData1(int lineCount, double demerits) { lineLayouts.addPossibility(lineCount, demerits); if (log.isTraceEnabled()) { log.trace("Layout possibility in " + lineCount + " lines; break at position:"); } } @Override 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 startIndent; int endIndent; int difference = bestActiveNode.difference; int textAlign = (bestActiveNode.line < total) ? alignment : alignmentLast; switch (textAlign) { case Constants.EN_START: startIndent = 0; endIndent = difference > 0 ? difference : 0; break; case Constants.EN_END: startIndent = difference > 0 ? difference : 0; endIndent = 0; break; case Constants.EN_CENTER: startIndent = difference / 2; endIndent = startIndent; break; default: case Constants.EN_JUSTIFY: startIndent = 0; endIndent = 0; break; } /* startIndent += (textAlign == Constants.EN_CENTER) ? difference / 2 : (textAlign == Constants.EN_END) ? difference : 0; */ startIndent += (bestActiveNode.line == 1 && indentFirstPart && 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 this LLM is nested inside a BlockContainerLayoutManager that is constraining // the available width and thus responsible for the overflow then we do not issue // warning event here and instead let the BCLM handle that at a later stage if (lack < 0 && !handleOverflow(-lack)) { InlineLevelEventProducer eventProducer = InlineLevelEventProducer.Provider .get(getFObj().getUserAgent().getEventBroadcaster()); eventProducer.lineOverflows(this, bestActiveNode.line, -lack, getFObj().getLocator()); } } //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.getShrink()), bestActiveNode.availableStretch, difference, ratio, startIndent, endIndent), activePossibility); addedPositions++; } /* reset activePossibility, as if breakpoints have not yet been computed */ public void resetAlgorithm() { activePossibility = -1; } private LineBreakPosition makeLineBreakPosition( // CSOK: ParameterNumber KnuthSequence par, int firstElementIndex, int lastElementIndex, int availableShrink, int availableStretch, int difference, double ratio, int startIndent, int endIndent) { // 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 isZeroHeightLine = (difference == ipd); // 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 (isZeroHeightLine && (!element.isAuxiliary() || ac != null && ac.getHeight() > 0)) { isZeroHeightLine = false; } } } if (lineFollow < maxIgnoredHeight - lineLead) { lineFollow = maxIgnoredHeight - lineLead; } } constantLineHeight = lineLead + lineFollow; if (isZeroHeightLine) { return new LineBreakPosition(thisLLM, knuthParagraphs.indexOf(par), firstElementIndex, lastElementIndex, availableShrink, availableStretch, difference, ratio, 0, startIndent, endIndent, 0, ipd, 0, 0, 0); } else { return new LineBreakPosition(thisLLM, knuthParagraphs.indexOf(par), firstElementIndex, lastElementIndex, availableShrink, availableStretch, difference, ratio, 0, startIndent, endIndent, lineLead + lineFollow, ipd, spaceBefore, spaceAfter, lineLead); } } @Override 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} */ @Override public void initialize() { bidiLevel = fobj.getBidiLevel(); 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; } } /** {@inheritDoc} */ @Override public List getNextKnuthElements(LayoutContext context, int alignment) { if (alignmentContext == null) { 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); ipd = context.getRefIPD(); //PHASE 1: Create Knuth elements if (knuthParagraphs == null) { // it's the first time this method is called knuthParagraphs = new ArrayList<KnuthSequence>(); // 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); } /** * Get a sequence of KnuthElements representing the content * of the node assigned to the LM. * @param context the LayoutContext used to store layout information * @param alignment the desired text alignment * @param restartPosition position at restart * @return the list of KnuthElements * @see LayoutManager#getNextKnuthElements(LayoutContext,int) */ public List getNextKnuthElements(LayoutContext context, int alignment, LeafPosition restartPosition) { log.trace("Restarting line breaking from index " + restartPosition.getIndex()); int parIndex = restartPosition.getLeafPos(); KnuthSequence paragraph = knuthParagraphs.get(parIndex); paragraph.subList(0, restartPosition.getIndex() + 1).clear(); Iterator<KnuthElement> iter = paragraph.iterator(); while (iter.hasNext() && !iter.next().isBox()) { iter.remove(); } if (!iter.hasNext()) { knuthParagraphs.remove(parIndex); } // return finished when there's no content if (knuthParagraphs.size() == 0) { setFinished(true); return null; } ipd = context.getRefIPD(); //PHASE 2: Create line breaks return createLineBreaks(context.getBPAlignment(), context); } /** * 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); // convert all the text in a sequence of paragraphs made // of KnuthBox, KnuthGlue and KnuthPenalty objects boolean previousIsBox = false; StringBuffer trace = new StringBuffer("LineLM:"); Paragraph lastPar = null; InlineLevelLayoutManager curLM; while ((curLM = (InlineLevelLayoutManager) getChildLM()) != null) { List inlineElements = curLM.getNextKnuthElements(inlineLC, effectiveAlignment); if (inlineElements == null || inlineElements.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) inlineElements.get(0); // 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(" ]"); } previousIsBox = 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() && previousIsBox) { lastPar.addALetterSpace(); } } } // loop over the KnuthSequences (and single KnuthElements) in returnedList ListIterator iter = inlineElements.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(); assert lastElement != null; previousIsBox = lastElement.isBox() && !((KnuthElement) lastElement).isAuxiliary() && ((KnuthElement) lastElement).getWidth() != 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).getPenalty() == -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(ipd, 0, ipd, null, true)); } lastPar.endParagraph(); ElementListObserver.observe(lastPar, "line", null); lastPar = null; if (log.isTraceEnabled()) { trace.append(" ]"); } previousIsBox = 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); } /** * 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 List<ListElement> createLineBreaks(int alignment, LayoutContext context) { // find the optimal line breaking points for each paragraph Iterator<KnuthSequence> paragraphsIterator = knuthParagraphs.iterator(); lineLayoutsList = new LineLayoutPossibilities[knuthParagraphs.size()]; LineLayoutPossibilities llPoss; for (int i = 0; paragraphsIterator.hasNext(); i++) { KnuthSequence seq = paragraphsIterator.next(); 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, !paragraphsIterator.hasNext()); } lineLayoutsList[i] = llPoss; } setFinished(true); //Post-process the line breaks found return postProcessLineBreaks(alignment, context); } /** * Find the optimal linebreaks for a paragraph * @param alignment alignment of the paragraph * @param currPar the Paragraph for which the linebreaks are found * @param isLastPar flag indicating whether currPar is the last paragraph * @return the line layout possibilities for the paragraph */ private LineLayoutPossibilities findOptimalBreakingPoints(int alignment, Paragraph currPar, boolean isLastPar) { // use the member lineLayouts, which is read by LineBreakingAlgorithm.updateData1 and 2 lineLayouts = new LineLayoutPossibilities(); double maxAdjustment = 1; LineBreakingAlgorithm alg = new LineBreakingAlgorithm(alignment, textAlignment, textAlignmentLast, textIndent.getValue(this), currPar.lineFiller.getOpt(), lineHeight.getValue(this), lead, follow, (knuthParagraphs.indexOf(currPar) == 0), hyphenationLadderCount.getEnum() == EN_NO_LIMIT ? 0 : hyphenationLadderCount.getValue(), this); alg.setConstantLineWidth(ipd); boolean canWrap = (wrapOption != EN_NO_WRAP); boolean canHyphenate = (canWrap && hyphenationProperties.hyphenate.getEnum() == EN_TRUE); // find hyphenation points, if allowed and not yet done if (canHyphenate && !hyphenationPerformed) { // make sure findHyphenationPoints() is bypassed if // the method is called twice (e.g. due to changing page-ipd) hyphenationPerformed = isLastPar; findHyphenationPoints(currPar); } // first try: do not consider hyphenation points as legal breaks int allowedBreaks = (canWrap ? BreakingAlgorithm.NO_FLAGGED_PENALTIES : BreakingAlgorithm.ONLY_FORCED_BREAKS); int breakingPoints = alg.findBreakingPoints(currPar, maxAdjustment, false, allowedBreaks); if (breakingPoints == 0 || alignment == EN_JUSTIFY) { // if the first try found a set of breaking points, save them if (breakingPoints > 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? " + canHyphenate); // Note: if allowedBreaks is guaranteed to be unchanged by alg.findBreakingPoints(), // the below check can be simplified to 'if (canHyphenate) ...' if (canHyphenate && 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; } breakingPoints = alg.findBreakingPoints(currPar, maxAdjustment, false, allowedBreaks); if (breakingPoints == 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 + (canHyphenate ? " and hyphenation" : "")); } maxAdjustment = 20; alg.findBreakingPoints(currPar, maxAdjustment, true, allowedBreaks); } // use non-hyphenated breaks, when possible lineLayouts.restorePossibilities(); } 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 List<ListElement> postProcessLineBreaks(int alignment, LayoutContext context) { List<ListElement> returnList = new LinkedList<ListElement>(); int endIndex = -1; for (int p = 0; p < knuthParagraphs.size(); p++) { // penalty between paragraphs if (p > 0) { Keep keep = getKeepTogether(); returnList.add(new BreakElement(new Position(this), keep.getPenalty(), keep.getContext(), context)); } LineLayoutPossibilities llPoss = lineLayoutsList[p]; KnuthSequence seq = knuthParagraphs.get(p); if (!seq.isInlineSequence()) { List<ListElement> targetList = new LinkedList<ListElement>(); 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 */ int startIndex = 0; for (int i = 0; i < llPoss.getChosenLineCount(); i++) { if (returnList.size() > 0 && i > 0 //if i==0 break generated above already && i >= fobj.getOrphans() && i <= llPoss.getChosenLineCount() - fobj.getWidows()) { // penalty allowing a page break between lines Keep keep = getKeepTogether(); returnList.add(new BreakElement(new LeafPosition(this, p, endIndex), keep.getPenalty(), keep.getContext(), context)); } endIndex = ((LineBreakPosition) llPoss.getChosenPosition(i)).getLeafPos(); // create a list of the FootnoteBodyLM handling footnotes // whose citations are in this line List<LayoutManager> footnoteList = new LinkedList<LayoutManager>(); ListIterator<KnuthElement> elementIterator = seq.listIterator(startIndex); while (elementIterator.nextIndex() <= endIndex) { KnuthElement element = 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)); } } } return returnList; } private void createElements(List<ListElement> list, LineLayoutPossibilities llPoss, Position elementPosition) { /* number of normal, inner lines */ int innerLines = 0; /* number of lines that can be used in order to fill more space */ int optionalLines = 0; /* number of lines that can be used in order to fill more space only if the paragraph is not parted */ int conditionalOptionalLines = 0; /* number of lines that can be omitted in order to fill less space */ int eliminableLines = 0; /* number of lines that can be omitted in order to fill less space only if the paragraph is not parted */ int conditionalEliminableLines = 0; /* number of the first unbreakable lines */ int firstLines = fobj.getOrphans(); /* number of the last unbreakable lines */ int lastLines = fobj.getWidows(); /* sub-sequence used to separate the elements representing different lines */ List<KnuthElement> breaker = new LinkedList<KnuthElement>(); /* comment out the next lines in order to test particular situations */ if (fobj.getOrphans() + fobj.getWidows() <= llPoss.getMinLineCount()) { innerLines = llPoss.getMinLineCount() - (fobj.getOrphans() + fobj.getWidows()); optionalLines = llPoss.getMaxLineCount() - llPoss.getOptLineCount(); eliminableLines = llPoss.getOptLineCount() - llPoss.getMinLineCount(); } else if (fobj.getOrphans() + fobj.getWidows() <= llPoss.getOptLineCount()) { optionalLines = llPoss.getMaxLineCount() - llPoss.getOptLineCount(); eliminableLines = llPoss.getOptLineCount() - (fobj.getOrphans() + fobj.getWidows()); conditionalEliminableLines = (fobj.getOrphans() + fobj.getWidows()) - llPoss.getMinLineCount(); } else if (fobj.getOrphans() + fobj.getWidows() <= llPoss.getMaxLineCount()) { optionalLines = llPoss.getMaxLineCount() - (fobj.getOrphans() + fobj.getWidows()); conditionalOptionalLines = (fobj.getOrphans() + fobj.getWidows()) - llPoss.getOptLineCount(); conditionalEliminableLines = llPoss.getOptLineCount() - llPoss.getMinLineCount(); firstLines -= conditionalOptionalLines; } else { conditionalOptionalLines = llPoss.getMaxLineCount() - llPoss.getOptLineCount(); conditionalEliminableLines = llPoss.getOptLineCount() - llPoss.getMinLineCount(); firstLines = llPoss.getOptLineCount(); lastLines = 0; } /* comment out the previous lines in order to test particular situations */ /* use these lines to test particular situations innerLines = 0; optionalLines = 1; conditionalOptionalLines = 2; eliminableLines = 0; conditionalEliminableLines = 0; firstLines = 1; lastLines = 3; */ if (lastLines != 0 && (conditionalOptionalLines > 0 || conditionalEliminableLines > 0)) { breaker.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false)); breaker.add(new KnuthGlue(0, -conditionalOptionalLines * constantLineHeight, -conditionalEliminableLines * constantLineHeight, Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false)); breaker.add(new KnuthPenalty(conditionalOptionalLines * constantLineHeight, 0, false, elementPosition, false)); breaker.add(new KnuthGlue(0, conditionalOptionalLines * constantLineHeight, conditionalEliminableLines * constantLineHeight, Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false)); } else if (lastLines != 0) { breaker.add(new KnuthPenalty(0, 0, false, elementPosition, false)); } // creation of the elements: // first group of lines list.add(new KnuthBox(firstLines * constantLineHeight, elementPosition, (lastLines == 0 && conditionalOptionalLines == 0 && conditionalEliminableLines == 0))); if (conditionalOptionalLines > 0 || conditionalEliminableLines > 0) { list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false)); list.add(new KnuthGlue(0, conditionalOptionalLines * constantLineHeight, conditionalEliminableLines * constantLineHeight, Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false)); list.add(new KnuthBox(0, elementPosition, (lastLines == 0))); } // optional lines for (int i = 0; i < optionalLines; 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, constantLineHeight, 0, Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false)); list.add(new KnuthBox(0, elementPosition, false)); } // eliminable lines for (int i = 0; i < eliminableLines; i++) { list.addAll(breaker); list.add(new KnuthBox(constantLineHeight, elementPosition, false)); list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false)); list.add(new KnuthGlue(0, 0, constantLineHeight, Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false)); list.add(new KnuthBox(0, elementPosition, false)); } // inner lines for (int i = 0; i < innerLines; i++) { list.addAll(breaker); list.add(new KnuthBox(constantLineHeight, elementPosition, false)); } // last group of lines if (lastLines > 0) { list.addAll(breaker); list.add(new KnuthBox(lastLines * constantLineHeight, elementPosition, true)); } } /** {@inheritDoc} */ public boolean mustKeepTogether() { return ((BlockLevelLayoutManager) getParent()).mustKeepTogether(); } /** {@inheritDoc} */ public KeepProperty getKeepTogetherProperty() { return ((BlockLevelLayoutManager) getParent()).getKeepTogetherProperty(); } /** {@inheritDoc} */ public KeepProperty getKeepWithPreviousProperty() { return ((BlockLevelLayoutManager) getParent()).getKeepWithPreviousProperty(); } /** {@inheritDoc} */ public KeepProperty getKeepWithNextProperty() { return ((BlockLevelLayoutManager) getParent()).getKeepWithNextProperty(); } /** {@inheritDoc} */ public Keep getKeepTogether() { return ((BlockLevelLayoutManager) getParent()).getKeepTogether(); } /** {@inheritDoc} */ public boolean mustKeepWithPrevious() { return !getKeepWithPrevious().isAuto(); } /** {@inheritDoc} */ public boolean mustKeepWithNext() { return !getKeepWithNext().isAuto(); } /** {@inheritDoc} */ public Keep getKeepWithNext() { return Keep.KEEP_AUTO; } /** {@inheritDoc} */ public Keep getKeepWithPrevious() { return Keep.KEEP_AUTO; } /** {@inheritDoc} */ public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) { LeafPosition pos = (LeafPosition) lastElement.getPosition(); //if (lastElement.isPenalty()) { // totalAdj += lastElement.getWidth(); //} //int lineNumberDifference = (int)((double) totalAdj / constantLineHeight); int lineNumberDifference = (int) Math.round((double) adj / constantLineHeight + (adj > 0 ? -0.4 : 0.4)); //log.debug(" LLM> variazione calcolata = " + ((double) totalAdj / constantLineHeight) //+ " variazione applicata = " + lineNumberDifference); LineLayoutPossibilities llPoss; llPoss = lineLayoutsList[pos.getLeafPos()]; lineNumberDifference = llPoss.applyLineCountAdjustment(lineNumberDifference); return lineNumberDifference * constantLineHeight; } /** {@inheritDoc} */ public void discardSpace(KnuthGlue spaceGlue) { } /** {@inheritDoc} */ @Override public List getChangedKnuthElements(List oldList, int alignment, int depth) { return getChangedKnuthElements(oldList, alignment); } /** {@inheritDoc} */ @Override public List getChangedKnuthElements(List oldList, int alignment) { List<KnuthElement> returnList = new LinkedList<KnuthElement>(); for (int p = 0; p < knuthParagraphs.size(); p++) { LineLayoutPossibilities llPoss = lineLayoutsList[p]; //log.debug("demerits of the chosen layout: " + llPoss.getChosenDemerits()); for (int i = 0; i < llPoss.getChosenLineCount(); i++) { if (!((BlockLevelLayoutManager) parentLayoutManager).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 = MinOptMax.getInstance(lbp.lineWidth - lbp.difference - lbp.availableShrink, lbp.lineWidth - lbp.difference, lbp.lineWidth - lbp.difference + lbp.availableStretch); } else if (alignment == EN_CENTER) { contentIPD = MinOptMax.getInstance(lbp.lineWidth - 2 * lbp.startIndent); } else if (alignment == EN_END) { contentIPD = MinOptMax.getInstance(lbp.lineWidth - lbp.startIndent); } else { contentIPD = MinOptMax.getInstance(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 in 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 List updateList = new LinkedList(); KnuthElement firstElement; KnuthElement nextElement; // current InlineLevelLayoutManager InlineLevelLayoutManager currLM = null; // number of KnuthBox elements containing word fragments int boxCount; // number of auxiliary KnuthElements between KnuthBoxes int auxCount; StringBuffer sbChars; // 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; } // 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(); sbChars.append(currLM.getWordChars(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++; sbChars.append(currLM.getWordChars(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 } } } } } processUpdates(currPar, updateList); } private void processUpdates(Paragraph par, List updateList) { // create iterator for the updateList ListIterator updateListIterator = updateList.listIterator(); Update currUpdate; int elementsAdded = 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.firstIndex; int toIndex; if (updateListIterator.hasNext()) { Update nextUpdate = (Update) updateListIterator.next(); toIndex = nextUpdate.firstIndex; updateListIterator.previous(); } else { // maybe this is not always correct! toIndex = par.size() - par.ignoreAtEnd - elementsAdded; } // applyChanges() returns true if the LM modifies its data, // so it must return new KnuthElements to replace the old ones if (currUpdate.inlineLM.applyChanges(par.subList(fromIndex + elementsAdded, toIndex + elementsAdded))) { // insert the new KnuthElements List newElements = currUpdate.inlineLM.getChangedKnuthElements( par.subList(fromIndex + elementsAdded, toIndex + elementsAdded), /*flaggedPenalty,*/ effectiveAlignment); // remove the old elements par.subList(fromIndex + elementsAdded, toIndex + elementsAdded).clear(); // insert the new elements par.addAll(fromIndex + elementsAdded, newElements); elementsAdded += newElements.size() - (toIndex - fromIndex); } } updateList.clear(); } /** * Line area is always considered to act as a fence. * @param isNotFirst ignored * @return always true */ @Override protected boolean hasLeadingFence(boolean isNotFirst) { return true; } /** * Line area is always considered to act as a fence. * @param isNotLast ignored * @return always true */ @Override 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(), getFObj().getUserAgent().getFactory().getHyphPatNames(), 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 */ @Override public void addAreas(PositionIterator parentIter, LayoutContext context) { while (parentIter.hasNext()) { Position pos = parentIter.next(); boolean isLastPosition = !parentIter.hasNext(); if (pos instanceof LineBreakPosition) { addInlineArea(context, (LineBreakPosition) 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 lbp 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, LineBreakPosition lbp, boolean isLastPosition) { KnuthSequence seq = knuthParagraphs.get(lbp.parIndex); int startElementIndex = lbp.startIndex; int endElementIndex = 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, lbp.startIndent); } if (lbp.endIndent != 0) { lineArea.addTrait(Trait.END_INDENT, new Integer(lbp.endIndent)); } lineArea.setBPD(lbp.lineHeight); lineArea.setIPD(lbp.lineWidth); lineArea.setBidiLevel(bidiLevel); lineArea.addTrait(Trait.SPACE_BEFORE, lbp.spaceBefore); lineArea.addTrait(Trait.SPACE_AFTER, lbp.spaceAfter); alignmentContext.resizeLine(lbp.lineHeight, lbp.baseline); if (seq instanceof Paragraph) { Paragraph currPar = (Paragraph) seq; // ignore the first elements added by the LineLayoutManager startElementIndex += (startElementIndex == 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 (endElementIndex == (currPar.size() - 1)) { endElementIndex -= currPar.ignoreAtEnd; lineArea.setIPD(lineArea.getIPD() - lastLineEndIndent.getValue(this)); } } // ignore the last element in the line if it is a KnuthGlue object ListIterator seqIterator = seq.listIterator(endElementIndex); KnuthElement lastElement = (KnuthElement) seqIterator.next(); // the TLM which created the last KnuthElement in this line LayoutManager lastLM = lastElement.getLayoutManager(); if (lastElement.isGlue()) { // Remove trailing spaces if allowed so if (whiteSpaceTreament == EN_IGNORE_IF_SURROUNDING_LINEFEED || whiteSpaceTreament == EN_IGNORE || whiteSpaceTreament == EN_IGNORE_IF_BEFORE_LINEFEED) { endElementIndex--; // this returns the same KnuthElement seqIterator.previous(); if (seqIterator.hasPrevious()) { lastLM = ((KnuthElement) seqIterator.previous()).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(startElementIndex); while (seqIterator.hasNext() && !((KnuthElement) seqIterator.next()).isBox()) { startElementIndex++; } } // Add the inline areas to lineArea PositionIterator inlinePosIter = new KnuthPossPosIter(seq, startElementIndex, endElementIndex + 1); 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); 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)); } // if display-align is distribute, add space after if (context.getSpaceAfter() > 0 && (!context.isLastArea() || !isLastPosition)) { lineArea.setBPD(lineArea.getBPD() + context.getSpaceAfter()); } lineArea.finish(); if (lineArea.getBidiLevel() >= 0) { BidiResolver.reorder(lineArea); } parentLayoutManager.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 = 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 PositionIterator(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.setStackLimitBP(context.getStackLimitBP()); // Add the line areas to Area childLM.addAreas(childPosIter, blocklc); blocklc.setLeadingSpace(blocklc.getTrailingSpace()); blocklc.setTrailingSpace(new SpaceSpecifier(false)); } lineArea.updateExtentsFromChildren(); if (lineArea.getBidiLevel() >= 0) { BidiResolver.reorder(lineArea); } parentLayoutManager.addChildArea(lineArea); } /** {@inheritDoc} */ @Override 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} */ @Override public boolean getGeneratesBlockArea() { return true; } /** {@inheritDoc} */ @Override public boolean getGeneratesLineArea() { return true; } /** {@inheritDoc} */ @Override public boolean isRestartable() { return true; } /** * Whether this LM can handle horizontal overflow error messages (only a BlockContainerLayoutManager can). * @param milliPoints horizontal overflow * @return true if handled by a BlockContainerLayoutManager */ public boolean handleOverflow(int milliPoints) { if (getParent() instanceof BlockLayoutManager) { return ((BlockLayoutManager) getParent()).handleOverflow(milliPoints); } return false; } }