12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652 |
- /*
- * 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.Iterator;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.ListIterator;
-
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
-
- import org.apache.fop.area.Area;
- import org.apache.fop.area.LineArea;
- import org.apache.fop.area.Trait;
- import org.apache.fop.area.inline.InlineArea;
- import org.apache.fop.complexscripts.bidi.BidiResolver;
- import org.apache.fop.datatypes.Length;
- import org.apache.fop.datatypes.Numeric;
- import org.apache.fop.fo.Constants;
- import org.apache.fop.fo.flow.Block;
- import org.apache.fop.fo.properties.CommonHyphenation;
- import org.apache.fop.fo.properties.KeepProperty;
- import org.apache.fop.fonts.Font;
- import org.apache.fop.fonts.FontInfo;
- import org.apache.fop.fonts.FontTriplet;
- import org.apache.fop.hyphenation.Hyphenation;
- import org.apache.fop.hyphenation.Hyphenator;
- import org.apache.fop.layoutmgr.Adjustment;
- import org.apache.fop.layoutmgr.BlockLayoutManager;
- import org.apache.fop.layoutmgr.BlockLevelLayoutManager;
- import org.apache.fop.layoutmgr.BreakElement;
- import org.apache.fop.layoutmgr.BreakingAlgorithm;
- import org.apache.fop.layoutmgr.ElementListObserver;
- import org.apache.fop.layoutmgr.InlineKnuthSequence;
- import org.apache.fop.layoutmgr.Keep;
- import org.apache.fop.layoutmgr.KnuthBlockBox;
- import org.apache.fop.layoutmgr.KnuthBox;
- import org.apache.fop.layoutmgr.KnuthElement;
- import org.apache.fop.layoutmgr.KnuthGlue;
- import org.apache.fop.layoutmgr.KnuthPenalty;
- import org.apache.fop.layoutmgr.KnuthPossPosIter;
- import org.apache.fop.layoutmgr.KnuthSequence;
- import org.apache.fop.layoutmgr.LayoutContext;
- import org.apache.fop.layoutmgr.LayoutManager;
- import org.apache.fop.layoutmgr.LeafPosition;
- import org.apache.fop.layoutmgr.ListElement;
- import org.apache.fop.layoutmgr.NonLeafPosition;
- import org.apache.fop.layoutmgr.Position;
- import org.apache.fop.layoutmgr.PositionIterator;
- import org.apache.fop.layoutmgr.SpaceSpecifier;
- import org.apache.fop.traits.MinOptMax;
-
- /**
- * LayoutManager for lines. It builds one or more lines containing
- * inline areas generated by its sub layout managers.
- * A break is found for each line which may contain one of more
- * breaks from the child layout managers.
- * Once a break is found then it is return for the parent layout
- * manager to handle.
- * When the areas are being added to the page this manager
- * creates a line area to contain the inline areas added by the
- * child layout managers.
- */
- public class LineLayoutManager extends InlineStackingLayoutManager
- implements BlockLevelLayoutManager {
-
- /**
- * this constant is used to create elements when text-align is center:
- * every TextLM descendant of LineLM must use the same value,
- * otherwise the line breaking algorithm does not find the right
- * break point
- */
- public static final int DEFAULT_SPACE_WIDTH = 3336;
-
- /**
- * logging instance
- */
- private static Log log = LogFactory.getLog(LineLayoutManager.class);
-
- private final Block fobj;
- private boolean isFirstInBlock;
-
- /**
- * Private class to store information about inline breaks.
- * Each value holds the start and end indexes into a List of
- * inline break positions.
- */
- private static class LineBreakPosition extends LeafPosition {
- private final int parIndex; // index of the Paragraph this Position refers to
- private final int startIndex; //index of the first element this Position refers to
- private final int availableShrink;
- private final int availableStretch;
- private final int difference;
- private final double dAdjust; // Percentage to adjust (stretch or shrink)
- private final double ipdAdjust; // Percentage to adjust (stretch or shrink)
- private final int startIndent;
- private final int endIndent;
- private final int lineHeight;
- private final int lineWidth;
- private final int spaceBefore;
- private final int spaceAfter;
- private final int baseline;
-
- LineBreakPosition( // CSOK: ParameterNumber
- LayoutManager lm, int index, int startIndex, int breakIndex,
- int shrink, int stretch, int diff, double ipdA, double adjust, int si,
- int ei, int lh, int lw, int sb, int sa, int bl) {
- super(lm, breakIndex);
- availableShrink = shrink;
- availableStretch = stretch;
- difference = diff;
- parIndex = index;
- this.startIndex = startIndex;
- ipdAdjust = ipdA;
- dAdjust = adjust;
- startIndent = si;
- endIndent = ei;
- lineHeight = lh;
- lineWidth = lw;
- spaceBefore = sb;
- spaceAfter = sa;
- baseline = bl;
- }
-
- }
-
-
- private int bidiLevel = -1;
- private int textAlignment = EN_JUSTIFY;
- private int textAlignmentLast;
- private int effectiveAlignment;
- private Length textIndent;
- private Length lastLineEndIndent;
- private CommonHyphenation hyphenationProperties;
- private Numeric hyphenationLadderCount;
- private int wrapOption = EN_WRAP;
- private int whiteSpaceTreament;
- //private LayoutProps layoutProps;
-
- private final Length lineHeight;
- private final int lead;
- private final int follow;
- private AlignmentContext alignmentContext;
-
- private List<KnuthSequence> knuthParagraphs;
-
- private LineLayoutPossibilities lineLayouts;
- private LineLayoutPossibilities[] lineLayoutsList;
- private int ipd = 0;
- /**
- * When layout must be re-started due to a change of IPD, there is no need
- * to perform hyphenation on the remaining Knuth sequence once again.
- */
- private boolean hyphenationPerformed;
-
- /**
- * This class is used to remember
- * which was the first element in the paragraph
- * returned by each LM.
- */
- private final class Update {
- private final InlineLevelLayoutManager inlineLM;
- private final int firstIndex;
-
- private Update(InlineLevelLayoutManager lm, int index) {
- inlineLM = lm;
- firstIndex = index;
- }
- }
-
- // this class represents a paragraph
- private class Paragraph extends InlineKnuthSequence {
-
- private static final long serialVersionUID = 5862072380375189105L;
-
- /** Number of elements to ignore at the beginning of the list. */
- private int ignoreAtStart = 0;
- /** Number of elements to ignore at the end of the list. */
- private int ignoreAtEnd = 0;
-
- // space at the end of the last line (in millipoints)
- private MinOptMax lineFiller;
- private final int textAlignment;
- private final int textAlignmentLast;
- private final int textIndent;
- private final int lastLineEndIndent;
- // the LM which created the paragraph
- private final LineLayoutManager layoutManager;
-
- Paragraph(LineLayoutManager llm, int alignment, int alignmentLast,
- int indent, int endIndent) {
- super();
- layoutManager = llm;
- textAlignment = alignment;
- textAlignmentLast = alignmentLast;
- textIndent = indent;
- lastLineEndIndent = endIndent;
- }
-
- @Override
- public void startSequence() {
- // set the minimum amount of empty space at the end of the
- // last line
- if (textAlignment == EN_CENTER) {
- lineFiller = MinOptMax.getInstance(lastLineEndIndent);
- } else {
- lineFiller = MinOptMax.getInstance(lastLineEndIndent, lastLineEndIndent,
- layoutManager.ipd);
- }
-
- // add auxiliary elements at the beginning of the paragraph
- if (textAlignment == EN_CENTER && textAlignmentLast != EN_JUSTIFY) {
- this.add(new KnuthGlue(0, 3 * DEFAULT_SPACE_WIDTH, 0,
- null, false));
- ignoreAtStart++;
- }
-
- // add the element representing text indentation
- // at the beginning of the first paragraph
- if (isFirstInBlock && knuthParagraphs.size() == 0
- && textIndent != 0) {
- this.add(new KnuthInlineBox(textIndent, null,
- null, false));
- ignoreAtStart++;
- }
- }
-
- public void endParagraph() {
- KnuthSequence finishedPar = this.endSequence();
- if (finishedPar != null) {
- knuthParagraphs.add(finishedPar);
- }
- }
-
- @Override
- public KnuthSequence endSequence() {
- if (this.size() > ignoreAtStart) {
- if (textAlignment == EN_CENTER
- && textAlignmentLast != EN_JUSTIFY) {
- this.add(new KnuthGlue(0, 3 * DEFAULT_SPACE_WIDTH, 0,
- null, false));
- this.add(new KnuthPenalty(lineFiller.getOpt(), -KnuthElement.INFINITE,
- false, null, false));
- ignoreAtEnd = 2;
- } else if (textAlignmentLast != EN_JUSTIFY) {
- // add the elements representing the space
- // at the end of the last line
- // and the forced break
- this.add(new KnuthPenalty(0, KnuthElement.INFINITE,
- false, null, false));
- this.add(new KnuthGlue(0,
- lineFiller.getStretch(),
- lineFiller.getShrink(), null, false));
- this.add(new KnuthPenalty(lineFiller.getOpt(), -KnuthElement.INFINITE,
- false, null, false));
- ignoreAtEnd = 3;
- } else {
- // add only the element representing the forced break
- this.add(new KnuthPenalty(lineFiller.getOpt(), -KnuthElement.INFINITE,
- false, null, false));
- ignoreAtEnd = 1;
- }
- return this;
- } else {
- this.clear();
- return null;
- }
- }
-
- /**
- * @return true if the sequence contains a box
- */
- public boolean containsBox() {
- for (int i = 0; i < this.size(); i++) {
- KnuthElement el = (KnuthElement)this.get(i);
- if (el.isBox()) {
- return true;
- }
- }
- return false;
- }
- }
-
- private class LineBreakingAlgorithm extends BreakingAlgorithm {
- private final LineLayoutManager thisLLM;
- private final int pageAlignment;
- private int activePossibility;
- private int addedPositions;
- private final int textIndent;
- private final int lineHeight;
- private final int lead;
- private final int follow;
- private static final double MAX_DEMERITS = 10e6;
-
- public LineBreakingAlgorithm( // CSOK: ParameterNumber
- int pageAlign, int textAlign, int textAlignLast, int indent, int fillerWidth,
- int lh, int ld, int fl, boolean first, int maxFlagCount, LineLayoutManager llm) {
- super(textAlign, textAlignLast, first, false, maxFlagCount);
- pageAlignment = pageAlign;
- textIndent = indent;
- lineHeight = lh;
- lead = ld;
- follow = fl;
- thisLLM = llm;
- activePossibility = -1;
- }
-
- @Override
- public void updateData1(int lineCount, double demerits) {
- lineLayouts.addPossibility(lineCount, demerits);
- if (log.isTraceEnabled()) {
- log.trace("Layout possibility in " + lineCount + " lines; break at position:");
- }
- }
-
- @Override
- public void updateData2(KnuthNode bestActiveNode,
- KnuthSequence par,
- int total) {
- // compute indent and adjustment ratio, according to
- // the value of text-align and text-align-last
- int startIndent;
- int endIndent;
- int difference = bestActiveNode.difference;
- int textAlign = (bestActiveNode.line < total) ? alignment : alignmentLast;
-
- switch ( textAlign ) {
- case Constants.EN_START:
- startIndent = 0;
- endIndent = difference > 0 ? difference : 0;
- break;
- case Constants.EN_END:
- startIndent = difference > 0 ? difference : 0;
- endIndent = 0;
- break;
- case Constants.EN_CENTER:
- startIndent = difference / 2;
- endIndent = startIndent;
- break;
- default:
- case Constants.EN_JUSTIFY:
- startIndent = 0;
- endIndent = 0;
- break;
- }
-
- /*
- startIndent += (textAlign == Constants.EN_CENTER)
- ? difference / 2 : (textAlign == Constants.EN_END) ? difference : 0;
- */
- startIndent += (bestActiveNode.line == 1 && indentFirstPart && isFirstInBlock)
- ? textIndent : 0;
-
- double ratio = (textAlign == Constants.EN_JUSTIFY
- || difference < 0 && -difference <= bestActiveNode.availableShrink)
- ? bestActiveNode.adjustRatio : 0;
-
- // add nodes at the beginning of the list, as they are found
- // backwards, from the last one to the first one
-
- // the first time this method is called, initialize activePossibility
- if (activePossibility == -1) {
- activePossibility = 0;
- addedPositions = 0;
- }
-
- if (addedPositions == lineLayouts.getLineCount(activePossibility)) {
- activePossibility++;
- addedPositions = 0;
- }
-
- if (log.isWarnEnabled()) {
- int lack = difference + bestActiveNode.availableShrink;
- // if this LLM is nested inside a BlockContainerLayoutManager that is constraining
- // the available width and thus responsible for the overflow then we do not issue
- // warning event here and instead let the BCLM handle that at a later stage
- if (lack < 0 && !handleOverflow(-lack)) {
- InlineLevelEventProducer eventProducer
- = InlineLevelEventProducer.Provider.get(
- getFObj().getUserAgent().getEventBroadcaster());
- eventProducer.lineOverflows(this, bestActiveNode.line,
- -lack, getFObj().getLocator());
- }
- }
-
- //log.debug("LLM> (" + (lineLayouts.getLineNumber(activePossibility) - addedPositions)
- // + ") difference = " + difference + " ratio = " + ratio);
- lineLayouts.addBreakPosition(makeLineBreakPosition(par,
- (bestActiveNode.line > 1 ? bestActiveNode.previous.position + 1 : 0),
- bestActiveNode.position,
- bestActiveNode.availableShrink - (addedPositions > 0
- ? 0 : ((Paragraph) par).lineFiller.getShrink()),
- bestActiveNode.availableStretch,
- difference, ratio, startIndent, endIndent), activePossibility);
- addedPositions++;
- }
-
- /* reset activePossibility, as if breakpoints have not yet been computed
- */
- public void resetAlgorithm() {
- activePossibility = -1;
- }
-
- private LineBreakPosition makeLineBreakPosition( // CSOK: ParameterNumber
- KnuthSequence par, int firstElementIndex, int lastElementIndex, int availableShrink,
- int availableStretch, int difference, double ratio, int startIndent,
- int endIndent) {
- // line height calculation - spaceBefore may differ from spaceAfter
- // by 1mpt due to rounding
- int spaceBefore = (lineHeight - lead - follow) / 2;
- int spaceAfter = lineHeight - lead - follow - spaceBefore;
- // height before the main baseline
- int lineLead = lead;
- // maximum follow
- int lineFollow = follow;
- // true if this line contains only zero-height, auxiliary boxes
- // and the actual line width is 0; in this case, the line "collapses"
- // i.e. the line area will have bpd = 0
- boolean isZeroHeightLine = (difference == ipd);
-
- // if line-stacking-strategy is "font-height", the line height
- // is not affected by its content
- if (fobj.getLineStackingStrategy() != EN_FONT_HEIGHT) {
- ListIterator inlineIterator
- = par.listIterator(firstElementIndex);
- AlignmentContext lastAC = null;
- int maxIgnoredHeight = 0; // See spec 7.13
- for (int j = firstElementIndex;
- j <= lastElementIndex;
- j++) {
- KnuthElement element = (KnuthElement) inlineIterator.next();
- if (element instanceof KnuthInlineBox ) {
- AlignmentContext ac = ((KnuthInlineBox) element).getAlignmentContext();
- if (ac != null && lastAC != ac) {
- if (!ac.usesInitialBaselineTable()
- || ac.getAlignmentBaselineIdentifier() != EN_BEFORE_EDGE
- && ac.getAlignmentBaselineIdentifier() != EN_AFTER_EDGE) {
- if (fobj.getLineHeightShiftAdjustment() == EN_CONSIDER_SHIFTS
- || ac.getBaselineShiftValue() == 0) {
- int alignmentOffset = ac.getTotalAlignmentBaselineOffset();
- if (alignmentOffset + ac.getAltitude() > lineLead) {
- lineLead = alignmentOffset + ac.getAltitude();
- }
- if (ac.getDepth() - alignmentOffset > lineFollow) {
- lineFollow = ac.getDepth() - alignmentOffset;
- }
- }
- } else {
- if (ac.getHeight() > maxIgnoredHeight) {
- maxIgnoredHeight = ac.getHeight();
- }
- }
- lastAC = ac;
- }
- if (isZeroHeightLine
- && (!element.isAuxiliary() || ac != null && ac.getHeight() > 0)) {
- isZeroHeightLine = false;
- }
- }
- }
-
- if (lineFollow < maxIgnoredHeight - lineLead) {
- lineFollow = maxIgnoredHeight - lineLead;
- }
- }
-
- constantLineHeight = lineLead + lineFollow;
-
- if (isZeroHeightLine) {
- return new LineBreakPosition(thisLLM,
- knuthParagraphs.indexOf(par),
- firstElementIndex, lastElementIndex,
- availableShrink, availableStretch,
- difference, ratio, 0, startIndent, endIndent,
- 0, ipd, 0, 0, 0);
- } else {
- return new LineBreakPosition(thisLLM,
- knuthParagraphs.indexOf(par),
- firstElementIndex, lastElementIndex,
- availableShrink, availableStretch,
- difference, ratio, 0, startIndent, endIndent,
- lineLead + lineFollow,
- ipd, spaceBefore, spaceAfter,
- lineLead);
- }
- }
-
- @Override
- protected int filterActiveNodes() {
- KnuthNode bestActiveNode = null;
-
- if (pageAlignment == EN_JUSTIFY) {
- // leave all active nodes and find the optimum line number
- //log.debug("LBA.filterActiveNodes> " + activeNodeCount + " layouts");
- for (int i = startLine; i < endLine; i++) {
- for (KnuthNode node = getNode(i); node != null; node = node.next) {
- //log.debug(" + lines = "
- //+ node.line + " demerits = " + node.totalDemerits);
- bestActiveNode = compareNodes(bestActiveNode, node);
- }
- }
-
- // scan the node set once again and remove some nodes
- //log.debug("LBA.filterActiveList> layout selection");
- for (int i = startLine; i < endLine; i++) {
- for (KnuthNode node = getNode(i); node != null; node = node.next) {
- //if (Math.abs(node.line - bestActiveNode.line) > maxDiff) {
- //if (false) {
- if (node.line != bestActiveNode.line
- && node.totalDemerits > MAX_DEMERITS) {
- //log.debug(" XXX lines = "
- //+ node.line + " demerits = " + node.totalDemerits);
- removeNode(i, node);
- } else {
- //log.debug(" ok lines = "
- //+ node.line + " demerits = " + node.totalDemerits);
- }
- }
- }
- } else {
- // leave only the active node with fewest total demerits
- for (int i = startLine; i < endLine; i++) {
- for (KnuthNode node = getNode(i); node != null; node = node.next) {
- bestActiveNode = compareNodes(bestActiveNode, node);
- if (node != bestActiveNode) {
- removeNode(i, node);
- }
- }
- }
- }
- return bestActiveNode.line;
- }
- }
-
-
- private int constantLineHeight = 12000;
-
-
- /**
- * Create a new Line Layout Manager.
- * This is used by the block layout manager to create
- * line managers for handling inline areas flowing into line areas.
- * @param block the block formatting object
- * @param lh the default line height
- * @param l the default lead, from top to baseline
- * @param f the default follow, from baseline to bottom
- */
- public LineLayoutManager(Block block, Length lh, int l, int f) {
- super(block);
- fobj = block;
- // the child FObj are owned by the parent BlockLM
- // this LM has all its childLMs preloaded
- fobjIter = null;
- lineHeight = lh;
- lead = l;
- follow = f;
- }
-
- /** {@inheritDoc} */
- @Override
- public void initialize() {
- bidiLevel = fobj.getBidiLevel();
- textAlignment = fobj.getTextAlign();
- textAlignmentLast = fobj.getTextAlignLast();
- textIndent = fobj.getTextIndent();
- lastLineEndIndent = fobj.getLastLineEndIndent();
- hyphenationProperties = fobj.getCommonHyphenation();
- hyphenationLadderCount = fobj.getHyphenationLadderCount();
- wrapOption = fobj.getWrapOption();
- whiteSpaceTreament = fobj.getWhitespaceTreatment();
- //
- effectiveAlignment = getEffectiveAlignment(textAlignment, textAlignmentLast);
- isFirstInBlock = (this == getParent().getChildLMs().get(0));
- }
-
- private int getEffectiveAlignment(int alignment, int alignmentLast) {
- if (textAlignment != EN_JUSTIFY && textAlignmentLast == EN_JUSTIFY) {
- return 0;
- } else {
- return textAlignment;
- }
- }
-
- /** {@inheritDoc} */
- @Override
- public List getNextKnuthElements(LayoutContext context, int alignment) {
- if (alignmentContext == null) {
- FontInfo fi = fobj.getFOEventHandler().getFontInfo();
- FontTriplet[] fontkeys = fobj.getCommonFont().getFontState(fi);
- Font fs = fi.getFontInstance(fontkeys[0], fobj.getCommonFont().fontSize.getValue(this));
- alignmentContext = new AlignmentContext(fs, lineHeight.getValue(this),
- context.getWritingMode());
- }
- context.setAlignmentContext(alignmentContext);
- ipd = context.getRefIPD();
-
- //PHASE 1: Create Knuth elements
- if (knuthParagraphs == null) {
- // it's the first time this method is called
- knuthParagraphs = new ArrayList<KnuthSequence>();
-
- // here starts Knuth's algorithm
- collectInlineKnuthElements(context);
- } else {
- // this method has been called before
- // all line breaks are already calculated
- }
-
- // return finished when there's no content
- if (knuthParagraphs.size() == 0) {
- setFinished(true);
- return null;
- }
-
- //PHASE 2: Create line breaks
- return createLineBreaks(context.getBPAlignment(), context);
- }
-
- /**
- * Get a sequence of KnuthElements representing the content
- * of the node assigned to the LM.
- * @param context the LayoutContext used to store layout information
- * @param alignment the desired text alignment
- * @param restartPosition position at restart
- * @return the list of KnuthElements
- * @see LayoutManager#getNextKnuthElements(LayoutContext,int)
- */
- public List getNextKnuthElements(LayoutContext context, int alignment,
- LeafPosition restartPosition) {
- log.trace("Restarting line breaking from index " + restartPosition.getIndex());
- int parIndex = restartPosition.getLeafPos();
- KnuthSequence paragraph = knuthParagraphs.get(parIndex);
- paragraph.subList(0, restartPosition.getIndex() + 1).clear();
- Iterator<KnuthElement> iter = paragraph.iterator();
- while (iter.hasNext() && !iter.next().isBox()) {
- iter.remove();
- }
- if (!iter.hasNext()) {
- knuthParagraphs.remove(parIndex);
- }
-
- // return finished when there's no content
- if (knuthParagraphs.size() == 0) {
- setFinished(true);
- return null;
- }
-
- ipd = context.getRefIPD();
- //PHASE 2: Create line breaks
- return createLineBreaks(context.getBPAlignment(), context);
- }
-
- /**
- * Phase 1 of Knuth algorithm: Collect all inline Knuth elements before determining line breaks.
- * @param context the LayoutContext
- */
- private void collectInlineKnuthElements(LayoutContext context) {
- LayoutContext inlineLC = new LayoutContext(context);
-
- // convert all the text in a sequence of paragraphs made
- // of KnuthBox, KnuthGlue and KnuthPenalty objects
- boolean previousIsBox = false;
-
- StringBuffer trace = new StringBuffer("LineLM:");
-
- Paragraph lastPar = null;
-
- InlineLevelLayoutManager curLM;
- while ((curLM = (InlineLevelLayoutManager) getChildLM()) != null) {
- List inlineElements = curLM.getNextKnuthElements(inlineLC, effectiveAlignment);
- if (inlineElements == null || inlineElements.size() == 0) {
- /* curLM.getNextKnuthElements() returned null or an empty list;
- * this can happen if there is nothing more to layout,
- * so just iterate once more to see if there are other children */
- continue;
- }
-
- if (lastPar != null) {
- KnuthSequence firstSeq = (KnuthSequence) inlineElements.get(0);
-
- // finish last paragraph before a new block sequence
- if (!firstSeq.isInlineSequence()) {
- lastPar.endParagraph();
- ElementListObserver.observe(lastPar, "line", null);
- lastPar = null;
- if (log.isTraceEnabled()) {
- trace.append(" ]");
- }
- previousIsBox = false;
- }
-
- // does the first element of the first paragraph add to an existing word?
- if (lastPar != null) {
- KnuthElement thisElement;
- thisElement = (KnuthElement) firstSeq.get(0);
- if (thisElement.isBox() && !thisElement.isAuxiliary()
- && previousIsBox) {
- lastPar.addALetterSpace();
- }
- }
- }
-
- // loop over the KnuthSequences (and single KnuthElements) in returnedList
- ListIterator iter = inlineElements.listIterator();
- while (iter.hasNext()) {
- KnuthSequence sequence = (KnuthSequence) iter.next();
- // the sequence contains inline Knuth elements
- if (sequence.isInlineSequence()) {
- // look at the last element
- ListElement lastElement = sequence.getLast();
- assert lastElement != null;
- previousIsBox = lastElement.isBox()
- && !((KnuthElement) lastElement).isAuxiliary()
- && ((KnuthElement) lastElement).getWidth() != 0;
-
- // if last paragraph is open, add the new elements to the paragraph
- // else this is the last paragraph
- if (lastPar == null) {
- lastPar = new Paragraph(this,
- textAlignment, textAlignmentLast,
- textIndent.getValue(this),
- lastLineEndIndent.getValue(this));
- lastPar.startSequence();
- if (log.isTraceEnabled()) {
- trace.append(" [");
- }
- } else {
- if (log.isTraceEnabled()) {
- trace.append(" +");
- }
- }
- lastPar.addAll(sequence);
- if (log.isTraceEnabled()) {
- trace.append(" I");
- }
-
- // finish last paragraph if it was closed with a linefeed
- if (lastElement.isPenalty()
- && ((KnuthPenalty) lastElement).getPenalty()
- == -KnuthPenalty.INFINITE) {
- // a penalty item whose value is -inf
- // represents a preserved linefeed,
- // which forces a line break
- lastPar.removeLast();
- if (!lastPar.containsBox()) {
- //only a forced linefeed on this line
- //-> compensate with an auxiliary glue
- lastPar.add(new KnuthGlue(ipd, 0, ipd, null, true));
- }
- lastPar.endParagraph();
- ElementListObserver.observe(lastPar, "line", null);
- lastPar = null;
- if (log.isTraceEnabled()) {
- trace.append(" ]");
- }
- previousIsBox = false;
- }
- } else { // the sequence is a block sequence
- // the positions will be wrapped with this LM in postProcessLineBreaks
- knuthParagraphs.add(sequence);
- if (log.isTraceEnabled()) {
- trace.append(" B");
- }
- }
- } // end of loop over returnedList
- }
-
- if (lastPar != null) {
- lastPar.endParagraph();
- ElementListObserver.observe(lastPar, "line", fobj.getId());
- if (log.isTraceEnabled()) {
- trace.append(" ]");
- }
- }
- log.trace(trace);
- }
-
- /**
- * Phase 2 of Knuth algorithm: find optimal break points.
- * @param alignment alignment in BP direction of the paragraph
- * @param context the layout context
- * @return a list of Knuth elements representing broken lines
- */
- private List<ListElement> createLineBreaks(int alignment, LayoutContext context) {
- // find the optimal line breaking points for each paragraph
- Iterator<KnuthSequence> paragraphsIterator = knuthParagraphs.iterator();
- lineLayoutsList = new LineLayoutPossibilities[knuthParagraphs.size()];
- LineLayoutPossibilities llPoss;
- for (int i = 0; paragraphsIterator.hasNext(); i++) {
- KnuthSequence seq = paragraphsIterator.next();
- if (!seq.isInlineSequence()) {
- // This set of line layout possibilities does not matter;
- // we only need an entry in lineLayoutsList.
- llPoss = new LineLayoutPossibilities();
- } else {
- llPoss = findOptimalBreakingPoints(alignment, (Paragraph) seq,
- !paragraphsIterator.hasNext());
- }
- lineLayoutsList[i] = llPoss;
- }
-
- setFinished(true);
-
- //Post-process the line breaks found
- return postProcessLineBreaks(alignment, context);
- }
-
- /**
- * Find the optimal linebreaks for a paragraph
- * @param alignment alignment of the paragraph
- * @param currPar the Paragraph for which the linebreaks are found
- * @param isLastPar flag indicating whether currPar is the last paragraph
- * @return the line layout possibilities for the paragraph
- */
- private LineLayoutPossibilities findOptimalBreakingPoints(int alignment, Paragraph currPar,
- boolean isLastPar) {
- // use the member lineLayouts, which is read by LineBreakingAlgorithm.updateData1 and 2
- lineLayouts = new LineLayoutPossibilities();
- double maxAdjustment = 1;
- LineBreakingAlgorithm alg = new LineBreakingAlgorithm(alignment,
- textAlignment, textAlignmentLast,
- textIndent.getValue(this), currPar.lineFiller.getOpt(),
- lineHeight.getValue(this), lead, follow,
- (knuthParagraphs.indexOf(currPar) == 0),
- hyphenationLadderCount.getEnum() == EN_NO_LIMIT
- ? 0 : hyphenationLadderCount.getValue(),
- this);
- alg.setConstantLineWidth(ipd);
- boolean canWrap = (wrapOption != EN_NO_WRAP);
- boolean canHyphenate = (canWrap && hyphenationProperties.hyphenate.getEnum() == EN_TRUE);
-
- // find hyphenation points, if allowed and not yet done
- if (canHyphenate && !hyphenationPerformed) {
- // make sure findHyphenationPoints() is bypassed if
- // the method is called twice (e.g. due to changing page-ipd)
- hyphenationPerformed = isLastPar;
- findHyphenationPoints(currPar);
- }
-
- // first try: do not consider hyphenation points as legal breaks
- int allowedBreaks = (canWrap ? BreakingAlgorithm.NO_FLAGGED_PENALTIES
- : BreakingAlgorithm.ONLY_FORCED_BREAKS);
- int breakingPoints = alg.findBreakingPoints(currPar, maxAdjustment, false, allowedBreaks);
-
- if (breakingPoints == 0 || alignment == EN_JUSTIFY) {
- // if the first try found a set of breaking points, save them
- if (breakingPoints > 0) {
- alg.resetAlgorithm();
- lineLayouts.savePossibilities(false);
- } else {
- // the first try failed
- log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment);
- }
-
- // now try something different
- log.debug("Hyphenation possible? " + canHyphenate);
- // Note: if allowedBreaks is guaranteed to be unchanged by alg.findBreakingPoints(),
- // the below check can be simplified to 'if (canHyphenate) ...'
- if (canHyphenate && allowedBreaks != BreakingAlgorithm.ONLY_FORCED_BREAKS) {
- // consider every hyphenation point as a legal break
- allowedBreaks = BreakingAlgorithm.ALL_BREAKS;
- } else {
- // try with a higher threshold
- maxAdjustment = 5;
- }
-
- breakingPoints = alg.findBreakingPoints(currPar, maxAdjustment, false, allowedBreaks);
- if (breakingPoints == 0) {
- // the second try failed too, try with a huge threshold
- // and force the algorithm to find a set of breaking points
- if (log.isDebugEnabled()) {
- log.debug("No set of breaking points found with maxAdjustment = "
- + maxAdjustment + (canHyphenate ? " and hyphenation" : ""));
- }
- maxAdjustment = 20;
- alg.findBreakingPoints(currPar, maxAdjustment, true, allowedBreaks);
- }
-
- // use non-hyphenated breaks, when possible
- lineLayouts.restorePossibilities();
- }
-
- return lineLayouts;
- }
-
- /**
- * Creates the element list in BP direction for the broken lines.
- * @param alignment the currently applicable vertical alignment
- * @param context the layout context
- * @return the newly built element list
- */
- private List<ListElement> postProcessLineBreaks(int alignment, LayoutContext context) {
-
- List<ListElement> returnList = new LinkedList<ListElement>();
-
- int endIndex = -1;
- for (int p = 0; p < knuthParagraphs.size(); p++) {
- // penalty between paragraphs
- if (p > 0) {
- Keep keep = getKeepTogether();
- returnList.add(new BreakElement(
- new Position(this),
- keep.getPenalty(),
- keep.getContext(),
- context));
- }
-
- LineLayoutPossibilities llPoss = lineLayoutsList[p];
- KnuthSequence seq = knuthParagraphs.get(p);
-
- if (!seq.isInlineSequence()) {
- List<ListElement> targetList = new LinkedList<ListElement>();
- ListIterator listIter = seq.listIterator();
- while (listIter.hasNext()) {
- ListElement tempElement;
- tempElement = (ListElement) listIter.next();
- if (tempElement.getLayoutManager() != this) {
- tempElement.setPosition(notifyPos(new NonLeafPosition(this,
- tempElement.getPosition())));
- }
- targetList.add(tempElement);
- }
- returnList.addAll(targetList);
- } else if (seq.isInlineSequence() && alignment == EN_JUSTIFY) {
- /* justified vertical alignment (not in the XSL FO recommendation):
- create a multi-layout sequence whose elements will contain
- a conventional Position */
- Position returnPosition = new LeafPosition(this, p);
- createElements(returnList, llPoss, returnPosition);
- } else {
- /* "normal" vertical alignment: create a sequence whose boxes
- represent effective lines, and contain LineBreakPositions */
- int startIndex = 0;
- for (int i = 0;
- i < llPoss.getChosenLineCount();
- i++) {
- if (returnList.size() > 0
- && i > 0 //if i==0 break generated above already
- && i >= fobj.getOrphans()
- && i <= llPoss.getChosenLineCount() - fobj.getWidows()) {
- // penalty allowing a page break between lines
- Keep keep = getKeepTogether();
- returnList.add(new BreakElement(
- new LeafPosition(this, p, endIndex),
- keep.getPenalty(),
- keep.getContext(),
- context));
- }
- endIndex = ((LineBreakPosition) llPoss.getChosenPosition(i)).getLeafPos();
- // create a list of the FootnoteBodyLM handling footnotes
- // whose citations are in this line
- List<LayoutManager> footnoteList = new LinkedList<LayoutManager>();
- ListIterator<KnuthElement> elementIterator = seq.listIterator(startIndex);
- while (elementIterator.nextIndex() <= endIndex) {
- KnuthElement element = elementIterator.next();
- if (element instanceof KnuthInlineBox
- && ((KnuthInlineBox) element).isAnchor()) {
- footnoteList.add(((KnuthInlineBox) element).getFootnoteBodyLM());
- } else if (element instanceof KnuthBlockBox) {
- footnoteList.addAll(((KnuthBlockBox) element).getFootnoteBodyLMs());
- }
- }
- startIndex = endIndex + 1;
- LineBreakPosition lbp = (LineBreakPosition) llPoss.getChosenPosition(i);
- returnList.add(new KnuthBlockBox
- (lbp.lineHeight + lbp.spaceBefore + lbp.spaceAfter,
- footnoteList, lbp, false));
- }
- }
- }
-
- return returnList;
- }
-
- private void createElements(List<ListElement> list, LineLayoutPossibilities llPoss,
- Position elementPosition) {
- /* number of normal, inner lines */
- int innerLines = 0;
- /* number of lines that can be used in order to fill more space */
- int optionalLines = 0;
- /* number of lines that can be used in order to fill more space
- only if the paragraph is not parted */
- int conditionalOptionalLines = 0;
- /* number of lines that can be omitted in order to fill less space */
- int eliminableLines = 0;
- /* number of lines that can be omitted in order to fill less space
- only if the paragraph is not parted */
- int conditionalEliminableLines = 0;
- /* number of the first unbreakable lines */
- int firstLines = fobj.getOrphans();
- /* number of the last unbreakable lines */
- int lastLines = fobj.getWidows();
- /* sub-sequence used to separate the elements representing different lines */
- List<KnuthElement> breaker = new LinkedList<KnuthElement>();
-
- /* comment out the next lines in order to test particular situations */
- if (fobj.getOrphans() + fobj.getWidows() <= llPoss.getMinLineCount()) {
- innerLines = llPoss.getMinLineCount() - (fobj.getOrphans() + fobj.getWidows());
- optionalLines = llPoss.getMaxLineCount() - llPoss.getOptLineCount();
- eliminableLines = llPoss.getOptLineCount() - llPoss.getMinLineCount();
- } else if (fobj.getOrphans() + fobj.getWidows() <= llPoss.getOptLineCount()) {
- optionalLines = llPoss.getMaxLineCount() - llPoss.getOptLineCount();
- eliminableLines = llPoss.getOptLineCount() - (fobj.getOrphans() + fobj.getWidows());
- conditionalEliminableLines
- = (fobj.getOrphans() + fobj.getWidows()) - llPoss.getMinLineCount();
- } else if (fobj.getOrphans() + fobj.getWidows() <= llPoss.getMaxLineCount()) {
- optionalLines = llPoss.getMaxLineCount() - (fobj.getOrphans() + fobj.getWidows());
- conditionalOptionalLines
- = (fobj.getOrphans() + fobj.getWidows()) - llPoss.getOptLineCount();
- conditionalEliminableLines = llPoss.getOptLineCount() - llPoss.getMinLineCount();
- firstLines -= conditionalOptionalLines;
- } else {
- conditionalOptionalLines = llPoss.getMaxLineCount() - llPoss.getOptLineCount();
- conditionalEliminableLines = llPoss.getOptLineCount() - llPoss.getMinLineCount();
- firstLines = llPoss.getOptLineCount();
- lastLines = 0;
- }
- /* comment out the previous lines in order to test particular situations */
-
- /* use these lines to test particular situations
- innerLines = 0;
- optionalLines = 1;
- conditionalOptionalLines = 2;
- eliminableLines = 0;
- conditionalEliminableLines = 0;
- firstLines = 1;
- lastLines = 3;
- */
-
- if (lastLines != 0
- && (conditionalOptionalLines > 0 || conditionalEliminableLines > 0)) {
- breaker.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
- breaker.add(new KnuthGlue(0, -conditionalOptionalLines * constantLineHeight,
- -conditionalEliminableLines * constantLineHeight,
- Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
- breaker.add(new KnuthPenalty(conditionalOptionalLines * constantLineHeight,
- 0, false, elementPosition, false));
- breaker.add(new KnuthGlue(0, conditionalOptionalLines * constantLineHeight,
- conditionalEliminableLines * constantLineHeight,
- Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
- } else if (lastLines != 0) {
- breaker.add(new KnuthPenalty(0, 0, false, elementPosition, false));
- }
-
- // creation of the elements:
- // first group of lines
- list.add(new KnuthBox(firstLines * constantLineHeight, elementPosition,
- (lastLines == 0
- && conditionalOptionalLines == 0
- && conditionalEliminableLines == 0)));
- if (conditionalOptionalLines > 0
- || conditionalEliminableLines > 0) {
- list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
- list.add(new KnuthGlue(0, conditionalOptionalLines * constantLineHeight,
- conditionalEliminableLines * constantLineHeight,
- Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
- list.add(new KnuthBox(0, elementPosition, (lastLines == 0)));
- }
-
- // optional lines
- for (int i = 0; i < optionalLines; i++) {
- list.addAll(breaker);
- list.add(new KnuthBox(0, elementPosition, false));
- list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
- list.add(new KnuthGlue(0, constantLineHeight, 0,
- Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
- list.add(new KnuthBox(0, elementPosition, false));
- }
-
- // eliminable lines
- for (int i = 0; i < eliminableLines; i++) {
- list.addAll(breaker);
- list.add(new KnuthBox(constantLineHeight, elementPosition, false));
- list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
- list.add(new KnuthGlue(0, 0, constantLineHeight,
- Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
- list.add(new KnuthBox(0, elementPosition, false));
- }
-
- // inner lines
- for (int i = 0; i < innerLines; i++) {
- list.addAll(breaker);
- list.add(new KnuthBox(constantLineHeight, elementPosition, false));
- }
-
- // last group of lines
- if (lastLines > 0) {
- list.addAll(breaker);
- list.add(new KnuthBox(lastLines * constantLineHeight,
- elementPosition, true));
- }
- }
-
- /** {@inheritDoc} */
- public boolean mustKeepTogether() {
- return ((BlockLevelLayoutManager) getParent()).mustKeepTogether();
- }
-
- /** {@inheritDoc} */
- public KeepProperty getKeepTogetherProperty() {
- return ((BlockLevelLayoutManager) getParent()).getKeepTogetherProperty();
- }
-
- /** {@inheritDoc} */
- public KeepProperty getKeepWithPreviousProperty() {
- return ((BlockLevelLayoutManager) getParent()).getKeepWithPreviousProperty();
- }
-
- /** {@inheritDoc} */
- public KeepProperty getKeepWithNextProperty() {
- return ((BlockLevelLayoutManager) getParent()).getKeepWithNextProperty();
- }
-
- /** {@inheritDoc} */
- public Keep getKeepTogether() {
- return ((BlockLevelLayoutManager) getParent()).getKeepTogether();
- }
-
- /** {@inheritDoc} */
- public boolean mustKeepWithPrevious() {
- return !getKeepWithPrevious().isAuto();
- }
-
- /** {@inheritDoc} */
- public boolean mustKeepWithNext() {
- return !getKeepWithNext().isAuto();
- }
-
- /** {@inheritDoc} */
- public Keep getKeepWithNext() {
- return Keep.KEEP_AUTO;
- }
-
- /** {@inheritDoc} */
- public Keep getKeepWithPrevious() {
- return Keep.KEEP_AUTO;
- }
-
- /** {@inheritDoc} */
- public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) {
- LeafPosition pos = (LeafPosition)lastElement.getPosition();
- //if (lastElement.isPenalty()) {
- // totalAdj += lastElement.getWidth();
- //}
- //int lineNumberDifference = (int)((double) totalAdj / constantLineHeight);
- int lineNumberDifference = (int) Math.round((double) adj / constantLineHeight
- + (adj > 0 ? -0.4 : 0.4));
- //log.debug(" LLM> variazione calcolata = " + ((double) totalAdj / constantLineHeight)
- //+ " variazione applicata = " + lineNumberDifference);
- LineLayoutPossibilities llPoss;
- llPoss = lineLayoutsList[pos.getLeafPos()];
- lineNumberDifference = llPoss.applyLineCountAdjustment(lineNumberDifference);
- return lineNumberDifference * constantLineHeight;
- }
-
- /** {@inheritDoc} */
- public void discardSpace(KnuthGlue spaceGlue) {
- }
-
- /** {@inheritDoc} */
- @Override
- public List getChangedKnuthElements(List oldList, int alignment, int depth) {
- return getChangedKnuthElements(oldList, alignment);
- }
-
- /** {@inheritDoc} */
- @Override
- public List getChangedKnuthElements(List oldList, int alignment) {
- List<KnuthElement> returnList = new LinkedList<KnuthElement>();
- for (int p = 0; p < knuthParagraphs.size(); p++) {
- LineLayoutPossibilities llPoss = lineLayoutsList[p];
- //log.debug("demerits of the chosen layout: " + llPoss.getChosenDemerits());
- for (int i = 0; i < llPoss.getChosenLineCount(); i++) {
- if (!((BlockLevelLayoutManager) parentLayoutManager).mustKeepTogether()
- && i >= fobj.getOrphans()
- && i <= llPoss.getChosenLineCount() - fobj.getWidows()) {
- // null penalty allowing a page break between lines
- returnList.add(new KnuthPenalty(0, 0, false, new Position(this), false));
- }
- LineBreakPosition lbp = (LineBreakPosition) llPoss.getChosenPosition(i);
- //log.debug("LLM.getChangedKnuthElements> lineWidth= "
- // + lbp.lineWidth + " difference= " + lbp.difference);
- //log.debug(" shrink= "
- // + lbp.availableShrink + " stretch= " + lbp.availableStretch);
- //log.debug("linewidth= " + lbp.lineWidth + " difference= "
- //+ lbp.difference + " indent= " + lbp.startIndent);
- MinOptMax contentIPD;
- if (alignment == EN_JUSTIFY) {
- contentIPD = MinOptMax.getInstance(
- lbp.lineWidth - lbp.difference - lbp.availableShrink,
- lbp.lineWidth - lbp.difference,
- lbp.lineWidth - lbp.difference + lbp.availableStretch);
- } else if (alignment == EN_CENTER) {
- contentIPD = MinOptMax.getInstance(lbp.lineWidth - 2 * lbp.startIndent);
- } else if (alignment == EN_END) {
- contentIPD = MinOptMax.getInstance(lbp.lineWidth - lbp.startIndent);
- } else {
- contentIPD
- = MinOptMax.getInstance(lbp.lineWidth - lbp.difference + lbp.startIndent);
- }
- returnList.add(new KnuthBlockBox(lbp.lineHeight, contentIPD, (lbp.ipdAdjust != 0
- ? lbp.lineWidth - lbp.difference : 0),
- lbp, false));
- }
- }
- return returnList;
- }
-
- /**
- * Find hyphenation points for every word in the current paragraph.
- *
- * @param currPar the paragraph whose words will be hyphenated
- */
- private void findHyphenationPoints(Paragraph currPar) {
- // hyphenate every word
- ListIterator currParIterator = currPar.listIterator(currPar.ignoreAtStart);
- // list of TLM involved in hyphenation
- List updateList = new LinkedList();
- KnuthElement firstElement;
- KnuthElement nextElement;
- // current InlineLevelLayoutManager
- InlineLevelLayoutManager currLM = null;
- // number of KnuthBox elements containing word fragments
- int boxCount;
- // number of auxiliary KnuthElements between KnuthBoxes
- int auxCount;
- StringBuffer sbChars;
-
- // find all hyphenation points
- while (currParIterator.hasNext()) {
- firstElement = (KnuthElement) currParIterator.next();
- //
- if (firstElement.getLayoutManager() != currLM) {
- currLM = (InlineLevelLayoutManager) firstElement.getLayoutManager();
- if (currLM != null) {
- updateList.add(new Update(currLM, currParIterator.previousIndex()));
- } else {
- break;
- }
- } else if (currLM == null) {
- break;
- }
-
- // collect word fragments, ignoring auxiliary elements;
- // each word fragment was created by a different TextLM
- if (firstElement.isBox() && !firstElement.isAuxiliary()) {
- boxCount = 1;
- auxCount = 0;
- sbChars = new StringBuffer();
- sbChars.append(currLM.getWordChars(firstElement.getPosition()));
- // look if next elements are boxes too
- while (currParIterator.hasNext()) {
- nextElement = (KnuthElement) currParIterator.next();
- if (nextElement.isBox() && !nextElement.isAuxiliary()) {
- // a non-auxiliary KnuthBox: append word chars
- if (currLM != nextElement.getLayoutManager()) {
- currLM = (InlineLevelLayoutManager) nextElement.getLayoutManager();
- updateList.add(new Update(currLM, currParIterator.previousIndex()));
- }
- // append text to recreate the whole word
- boxCount++;
- sbChars.append(currLM.getWordChars(nextElement.getPosition()));
- } else if (!nextElement.isAuxiliary()) {
- // a non-auxiliary non-box KnuthElement: stop
- // go back to the last box or auxiliary element
- currParIterator.previous();
- break;
- } else {
- if (currLM != nextElement.getLayoutManager()) {
- currLM = (InlineLevelLayoutManager) nextElement.getLayoutManager();
- updateList.add(new Update(currLM, currParIterator.previousIndex()));
- }
- // an auxiliary KnuthElement: simply ignore it
- auxCount++;
- }
- }
- if (log.isTraceEnabled()) {
- log.trace(" Word to hyphenate: " + sbChars.toString());
- }
- // find hyphenation points
- HyphContext hc = getHyphenContext(sbChars);
- // ask each LM to hyphenate its word fragment
- if (hc != null) {
- KnuthElement element = null;
- for (int i = 0; i < (boxCount + auxCount); i++) {
- currParIterator.previous();
- }
- for (int i = 0; i < (boxCount + auxCount); i++) {
- element = (KnuthElement) currParIterator.next();
- if (element.isBox() && !element.isAuxiliary()) {
- ((InlineLevelLayoutManager)
- element.getLayoutManager()).hyphenate(element.getPosition(), hc);
- } else {
- // nothing to do, element is an auxiliary KnuthElement
- }
- }
- }
- }
- }
- processUpdates(currPar, updateList);
- }
-
- private void processUpdates(Paragraph par, List updateList) {
- // create iterator for the updateList
- ListIterator updateListIterator = updateList.listIterator();
- Update currUpdate;
- int elementsAdded = 0;
-
- while (updateListIterator.hasNext()) {
- // ask the LMs to apply the changes and return
- // the new KnuthElements to replace the old ones
- currUpdate = (Update) updateListIterator.next();
- int fromIndex = currUpdate.firstIndex;
- int toIndex;
- if (updateListIterator.hasNext()) {
- Update nextUpdate = (Update) updateListIterator.next();
- toIndex = nextUpdate.firstIndex;
- updateListIterator.previous();
- } else {
- // maybe this is not always correct!
- toIndex = par.size() - par.ignoreAtEnd
- - elementsAdded;
- }
-
- // applyChanges() returns true if the LM modifies its data,
- // so it must return new KnuthElements to replace the old ones
- if (currUpdate.inlineLM
- .applyChanges(par.subList(fromIndex + elementsAdded,
- toIndex + elementsAdded))) {
- // insert the new KnuthElements
- List newElements = currUpdate.inlineLM.getChangedKnuthElements
- (par.subList(fromIndex + elementsAdded,
- toIndex + elementsAdded),
- /*flaggedPenalty,*/ effectiveAlignment);
- // remove the old elements
- par.subList(fromIndex + elementsAdded,
- toIndex + elementsAdded).clear();
- // insert the new elements
- par.addAll(fromIndex + elementsAdded, newElements);
- elementsAdded += newElements.size() - (toIndex - fromIndex);
- }
- }
- updateList.clear();
- }
-
- /**
- * Line area is always considered to act as a fence.
- * @param isNotFirst ignored
- * @return always true
- */
- @Override
- protected boolean hasLeadingFence(boolean isNotFirst) {
- return true;
- }
-
- /**
- * Line area is always considered to act as a fence.
- * @param isNotLast ignored
- * @return always true
- */
- @Override
- protected boolean hasTrailingFence(boolean isNotLast) {
- return true;
- }
-
- private HyphContext getHyphenContext(StringBuffer sbChars) {
- // Find all hyphenation points in this word
- // (get in an array of offsets)
- // hyphenationProperties are from the block level?.
- // Note that according to the spec,
- // they also "apply to" fo:character.
- // I don't know what that means, since
- // if we change language in the middle of a "word",
- // the effect would seem quite strange!
- // Or perhaps in that case, we say that it's several words.
- // We probably should bring the hyphenation props up from the actual
- // TextLM which generate the hyphenation buffer,
- // since these properties inherit and could be specified
- // on an inline or wrapper below the block level.
- Hyphenation hyph = Hyphenator.hyphenate(hyphenationProperties.language.getString(),
- hyphenationProperties.country.getString(),
- getFObj().getUserAgent().getNewURIResolver(),
- getFObj().getUserAgent().getHyphPatNames(),
- sbChars.toString(),
- hyphenationProperties.hyphenationRemainCharacterCount.getValue(),
- hyphenationProperties.hyphenationPushCharacterCount.getValue());
- // They hyph structure contains the information we need
- // Now start from prev: reset to that position, ask that LM to get
- // a Position for the first hyphenation offset. If the offset isn't in
- // its characters, it returns null,
- // but must tell how many chars it had.
- // Keep looking at currentBP using next hyphenation point until the
- // returned size is greater than the available size
- // or no more hyphenation points remain. Choose the best break.
- if (hyph != null) {
- return new HyphContext(hyph.getHyphenationPoints());
- } else {
- return null;
- }
- }
-
- /**
- * Add the areas with the break points.
- *
- * @param parentIter the iterator of break positions
- * @param context the context for adding areas
- */
- @Override
- public void addAreas(PositionIterator parentIter,
- LayoutContext context) {
- while (parentIter.hasNext()) {
- Position pos = parentIter.next();
- boolean isLastPosition = !parentIter.hasNext();
- if (pos instanceof LineBreakPosition) {
- addInlineArea(context, (LineBreakPosition) pos, isLastPosition);
- } else if ((pos instanceof NonLeafPosition) && pos.generatesAreas()) {
- addBlockArea(context, pos, isLastPosition);
- } else {
- /*
- * pos was the Position inside a penalty item, nothing to do;
- * or Pos does not generate an area,
- * i.e. it stand for spaces, borders and padding.
- */
- }
- }
- setCurrentArea(null); // ?? necessary
- }
-
- /**
- * Add a line with inline content
- * @param context the context for adding areas
- * @param lbp the position for which the line is generated
- * @param isLastPosition true if this is the last position of this LM
- */
- private void addInlineArea(LayoutContext context, LineBreakPosition lbp,
- boolean isLastPosition) {
-
- KnuthSequence seq = knuthParagraphs.get(lbp.parIndex);
- int startElementIndex = lbp.startIndex;
- int endElementIndex = lbp.getLeafPos();
-
- LineArea lineArea = new LineArea(
- (lbp.getLeafPos() < seq.size() - 1 ? textAlignment : textAlignmentLast),
- lbp.difference, lbp.availableStretch, lbp.availableShrink);
- if (lbp.startIndent != 0) {
- lineArea.addTrait(Trait.START_INDENT, lbp.startIndent);
- }
- if (lbp.endIndent != 0) {
- lineArea.addTrait(Trait.END_INDENT, new Integer(lbp.endIndent));
- }
- lineArea.setBPD(lbp.lineHeight);
- lineArea.setIPD(lbp.lineWidth);
- lineArea.setBidiLevel(bidiLevel);
- lineArea.addTrait(Trait.SPACE_BEFORE, lbp.spaceBefore);
- lineArea.addTrait(Trait.SPACE_AFTER, lbp.spaceAfter);
- alignmentContext.resizeLine(lbp.lineHeight, lbp.baseline);
-
- if (seq instanceof Paragraph) {
- Paragraph currPar = (Paragraph) seq;
- // ignore the first elements added by the LineLayoutManager
- startElementIndex += (startElementIndex == 0) ? currPar.ignoreAtStart : 0;
-
- // if this is the last line area that for this paragraph,
- // ignore the last elements added by the LineLayoutManager and
- // subtract the last-line-end-indent from the area ipd
- if (endElementIndex == (currPar.size() - 1)) {
- endElementIndex -= currPar.ignoreAtEnd;
- lineArea.setIPD(lineArea.getIPD() - lastLineEndIndent.getValue(this));
- }
- }
-
- // ignore the last element in the line if it is a KnuthGlue object
- ListIterator seqIterator = seq.listIterator(endElementIndex);
- KnuthElement lastElement = (KnuthElement) seqIterator.next();
- // the TLM which created the last KnuthElement in this line
- LayoutManager lastLM = lastElement.getLayoutManager();
- if (lastElement.isGlue()) {
- // Remove trailing spaces if allowed so
- if (whiteSpaceTreament == EN_IGNORE_IF_SURROUNDING_LINEFEED
- || whiteSpaceTreament == EN_IGNORE
- || whiteSpaceTreament == EN_IGNORE_IF_BEFORE_LINEFEED) {
- endElementIndex--;
- // this returns the same KnuthElement
- seqIterator.previous();
- if (seqIterator.hasPrevious()) {
- lastLM = ((KnuthElement) seqIterator.previous()).getLayoutManager();
- }
- }
- }
-
- // Remove leading spaces if allowed so
- if (whiteSpaceTreament == EN_IGNORE_IF_SURROUNDING_LINEFEED
- || whiteSpaceTreament == EN_IGNORE
- || whiteSpaceTreament == EN_IGNORE_IF_AFTER_LINEFEED) {
- // ignore KnuthGlue and KnuthPenalty objects
- // at the beginning of the line
- seqIterator = seq.listIterator(startElementIndex);
- while (seqIterator.hasNext() && !((KnuthElement) seqIterator.next()).isBox()) {
- startElementIndex++;
- }
- }
- // Add the inline areas to lineArea
- PositionIterator inlinePosIter = new KnuthPossPosIter(seq, startElementIndex,
- endElementIndex + 1);
-
- LayoutContext lc = new LayoutContext(0);
- lc.setAlignmentContext(alignmentContext);
- lc.setSpaceAdjust(lbp.dAdjust);
- lc.setIPDAdjust(lbp.ipdAdjust);
- lc.setLeadingSpace(new SpaceSpecifier(true));
- lc.setTrailingSpace(new SpaceSpecifier(false));
- lc.setFlags(LayoutContext.RESOLVE_LEADING_SPACE, true);
-
- setCurrentArea(lineArea);
- setChildContext(lc);
- LayoutManager childLM;
- while ((childLM = inlinePosIter.getNextChildLM()) != null) {
- lc.setFlags(LayoutContext.LAST_AREA, (childLM == lastLM));
- childLM.addAreas(inlinePosIter, lc);
- lc.setLeadingSpace(lc.getTrailingSpace());
- lc.setTrailingSpace(new SpaceSpecifier(false));
- }
-
- // if display-align is distribute, add space after
- if (context.getSpaceAfter() > 0
- && (!context.isLastArea() || !isLastPosition)) {
- lineArea.setBPD(lineArea.getBPD() + context.getSpaceAfter());
- }
- lineArea.finish();
- if ( lineArea.getBidiLevel() >= 0 ) {
- BidiResolver.reorder ( lineArea );
- }
- parentLayoutManager.addChildArea(lineArea);
- }
-
- /**
- * Add a line with block content
- * @param context the context for adding areas
- * @param pos the position for which the line is generated
- * @param isLastPosition true if this is the last position of this LM
- */
- private void addBlockArea(LayoutContext context, Position pos, boolean isLastPosition) {
- /* Nested block-level content;
- * go down the LM stack again;
- * "unwrap" the positions and put the child positions in a new list.
- * The positionList must contain one area-generating position,
- * which creates one line area.
- */
- List positionList = new ArrayList(1);
- Position innerPosition = pos.getPosition();
- positionList.add(innerPosition);
-
- // do we have the last LM?
- LayoutManager lastLM = null;
- if (isLastPosition) {
- lastLM = innerPosition.getLM();
- }
-
- LineArea lineArea = new LineArea();
- setCurrentArea(lineArea);
- LayoutContext lc = new LayoutContext(0);
- lc.setAlignmentContext(alignmentContext);
- setChildContext(lc);
-
- PositionIterator childPosIter = new PositionIterator(positionList.listIterator());
- LayoutContext blocklc = new LayoutContext(0);
- blocklc.setLeadingSpace(new SpaceSpecifier(true));
- blocklc.setTrailingSpace(new SpaceSpecifier(false));
- blocklc.setFlags(LayoutContext.RESOLVE_LEADING_SPACE, true);
- LayoutManager childLM;
- while ((childLM = childPosIter.getNextChildLM()) != null) {
- // set last area flag
- blocklc.setFlags(LayoutContext.LAST_AREA,
- (context.isLastArea() && childLM == lastLM));
- blocklc.setStackLimitBP(context.getStackLimitBP());
- // Add the line areas to Area
- childLM.addAreas(childPosIter, blocklc);
- blocklc.setLeadingSpace(blocklc.getTrailingSpace());
- blocklc.setTrailingSpace(new SpaceSpecifier(false));
- }
- lineArea.updateExtentsFromChildren();
- if ( lineArea.getBidiLevel() >= 0 ) {
- BidiResolver.reorder ( lineArea );
- }
- parentLayoutManager.addChildArea(lineArea);
- }
-
- /** {@inheritDoc} */
- @Override
- public void addChildArea(Area childArea) {
- // Make sure childArea is inline area
- if (childArea instanceof InlineArea) {
- Area parent = getCurrentArea();
- if (getContext().resolveLeadingSpace()) {
- addSpace(parent, getContext().getLeadingSpace().resolve(false),
- getContext().getSpaceAdjust());
- }
- parent.addChildArea(childArea);
- }
- }
-
- // --------- Property Resolution related functions --------- //
-
- /** {@inheritDoc} */
- @Override
- public boolean getGeneratesBlockArea() {
- return true;
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean getGeneratesLineArea() {
- return true;
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean isRestartable() {
- return true;
- }
-
- /**
- * Whether this LM can handle horizontal overflow error messages (only a BlockContainerLayoutManager can).
- * @param milliPoints horizontal overflow
- * @return true if handled by a BlockContainerLayoutManager
- */
- public boolean handleOverflow(int milliPoints) {
- if (getParent() instanceof BlockLayoutManager) {
- return ((BlockLayoutManager) getParent()).handleOverflow(milliPoints);
- }
- return false;
- }
- }
|