123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698 |
- /*
- * 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.Collections;
- 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.FloatContentLayoutManager;
- import org.apache.fop.layoutmgr.FootenoteUtil;
- import org.apache.fop.layoutmgr.FootnoteBodyLayoutManager;
- 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.
- */
- 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(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 int baselineOffset = -1;
-
- private List<KnuthSequence> knuthParagraphs;
-
- private LineLayoutPossibilities lineLayouts;
- private LineLayoutPossibilities[] lineLayoutsList;
- private int ipd;
- /**
- * 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 static class Paragraph extends InlineKnuthSequence {
-
- private static final long serialVersionUID = 5862072380375189105L;
-
- /** Number of elements to ignore at the beginning of the list. */
- private int ignoreAtStart;
- /** Number of elements to ignore at the end of the list. */
- private int ignoreAtEnd;
-
- // 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 (layoutManager.isFirstInBlock && layoutManager.knuthParagraphs.size() == 0
- && textIndent != 0) {
- this.add(new KnuthInlineBox(textIndent, null,
- null, false));
- ignoreAtStart++;
- }
- }
-
- public void endParagraph() {
- KnuthSequence finishedPar = this.endSequence();
- if (finishedPar != null) {
- layoutManager.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 (Object o : this) {
- KnuthElement el = (KnuthElement) o;
- 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(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;
- 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;
- }
-
- 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());
- if (curChildLM.getFObj() == null) {
- eventProducer.lineOverflows(this, getFObj().getName(), bestActiveNode.line,
- -lack, getFObj().getLocator());
- } else {
- eventProducer.lineOverflows(this, curChildLM.getFObj().getName(), bestActiveNode.line,
- -lack, curChildLM.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(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);
- if (paragraph instanceof Paragraph) {
- ((Paragraph) paragraph).ignoreAtStart = 0;
- isFirstInBlock = false;
- }
- 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 = LayoutContext.copyOf(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
- for (Object inlineElement : inlineElements) {
- KnuthSequence sequence = (KnuthSequence) inlineElement;
- // 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>();
- for (Object aSeq : seq) {
- ListElement tempElement;
- tempElement = (ListElement) aSeq;
- LayoutManager lm = tempElement.getLayoutManager();
- if (baselineOffset < 0 && lm != null && lm.hasLineAreaDescendant()) {
- baselineOffset = lm.getBaselineOffset();
- }
- if (lm != 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;
- int previousEndIndex = 0;
- for (int i = 0;
- i < llPoss.getChosenLineCount();
- i++) {
- int orphans = fobj.getOrphans();
- int widows = fobj.getWidows();
- if (handlingFloat()) {
- orphans = 1;
- widows = 1;
- }
- if (returnList.size() > 0
- && i > 0 //if i==0 break generated above already
- && i >= orphans && i <= llPoss.getChosenLineCount() - widows) {
- // 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 = llPoss.getChosenPosition(i).getLeafPos();
- // create a list of the FootnoteBodyLM handling footnotes
- // whose citations are in this line
- List<FootnoteBodyLayoutManager> footnoteList = FootenoteUtil.getFootnotes(
- seq, startIndex, endIndex);
- List<FloatContentLayoutManager> floats = FloatContentLayoutManager.checkForFloats(seq,
- startIndex, endIndex);
- startIndex = endIndex + 1;
- LineBreakPosition lbp = llPoss.getChosenPosition(i);
- if (baselineOffset < 0) {
- baselineOffset = lbp.spaceBefore + lbp.baseline;
- }
- if (floats.isEmpty()) {
- returnList.add(new KnuthBlockBox(lbp.lineHeight + lbp.spaceBefore + lbp.spaceAfter,
- footnoteList, lbp, false));
- } else {
- // add a line with height zero and no content and attach float to it
- returnList.add(new KnuthBlockBox(0, Collections.emptyList(), null, false, floats));
- // add a break element to signal that we should restart LB at this break
- Keep keep = getKeepTogether();
- returnList.add(new BreakElement(new LeafPosition(this, p, previousEndIndex), keep
- .getPenalty(), keep.getContext(), context));
- // add the original line where the float was but without the float now
- returnList.add(new KnuthBlockBox(lbp.lineHeight + lbp.spaceBefore + lbp.spaceAfter,
- footnoteList, lbp, false));
- }
- previousEndIndex = endIndex;
- }
- }
- }
-
- 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) {
- Position lastPos = lastElement.getPosition();
- assert (lastPos instanceof LeafPosition);
- LeafPosition pos = (LeafPosition) lastPos;
- //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());
- int orphans = fobj.getOrphans();
- int widows = fobj.getWidows();
- if (handlingFloat()) {
- orphans = 1;
- widows = 1;
- }
- for (int i = 0; i < llPoss.getChosenLineCount(); i++) {
- if (!((BlockLevelLayoutManager) parentLayoutManager).mustKeepTogether() && i >= orphans
- && i <= llPoss.getChosenLineCount() - widows) {
- // null penalty allowing a page break between lines
- returnList.add(new KnuthPenalty(0, 0, false, new Position(this), false));
- }
- LineBreakPosition lbp = 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);
- }
- // 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().getHyphenationResourceResolver(),
- getFObj().getUserAgent().getHyphenationPatternNames(),
- sbChars.toString(),
- hyphenationProperties.hyphenationRemainCharacterCount.getValue(),
- hyphenationProperties.hyphenationPushCharacterCount.getValue(),
- getFObj().getUserAgent().getEventBroadcaster());
- // 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;
- }
- }
-
- @Override
- public boolean hasLineAreaDescendant() {
- return true;
- }
-
- @Override
- public int getBaselineOffset() {
- return baselineOffset;
- }
-
- /**
- * 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);
- lineArea.setChangeBarList(getChangeBarList());
-
- if (lbp.startIndent != 0) {
- lineArea.addTrait(Trait.START_INDENT, lbp.startIndent);
- }
- if (lbp.endIndent != 0) {
- lineArea.addTrait(Trait.END_INDENT, 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 = LayoutContext.offspringOf(context);
- 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();
- lineArea.setChangeBarList(getChangeBarList());
- setCurrentArea(lineArea);
- LayoutContext lc = LayoutContext.newInstance();
- lc.setAlignmentContext(alignmentContext);
- setChildContext(lc);
-
- PositionIterator childPosIter = new PositionIterator(positionList.listIterator());
- LayoutContext blocklc = LayoutContext.offspringOf(context);
- 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;
- }
- }
|