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