You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

LineLayoutManager.java 74KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.layoutmgr.inline;
  19. import java.util.ArrayList;
  20. import java.util.Collections;
  21. import java.util.Iterator;
  22. import java.util.LinkedList;
  23. import java.util.List;
  24. import java.util.ListIterator;
  25. import org.apache.commons.logging.Log;
  26. import org.apache.commons.logging.LogFactory;
  27. import org.apache.fop.area.Area;
  28. import org.apache.fop.area.LineArea;
  29. import org.apache.fop.area.Trait;
  30. import org.apache.fop.area.inline.InlineArea;
  31. import org.apache.fop.complexscripts.bidi.BidiResolver;
  32. import org.apache.fop.datatypes.Length;
  33. import org.apache.fop.datatypes.Numeric;
  34. import org.apache.fop.fo.Constants;
  35. import org.apache.fop.fo.flow.Block;
  36. import org.apache.fop.fo.properties.CommonHyphenation;
  37. import org.apache.fop.fo.properties.KeepProperty;
  38. import org.apache.fop.fonts.Font;
  39. import org.apache.fop.fonts.FontInfo;
  40. import org.apache.fop.fonts.FontTriplet;
  41. import org.apache.fop.hyphenation.Hyphenation;
  42. import org.apache.fop.hyphenation.Hyphenator;
  43. import org.apache.fop.layoutmgr.Adjustment;
  44. import org.apache.fop.layoutmgr.BlockLayoutManager;
  45. import org.apache.fop.layoutmgr.BlockLevelLayoutManager;
  46. import org.apache.fop.layoutmgr.BreakElement;
  47. import org.apache.fop.layoutmgr.BreakingAlgorithm;
  48. import org.apache.fop.layoutmgr.ElementListObserver;
  49. import org.apache.fop.layoutmgr.FloatContentLayoutManager;
  50. import org.apache.fop.layoutmgr.FootenoteUtil;
  51. import org.apache.fop.layoutmgr.FootnoteBodyLayoutManager;
  52. import org.apache.fop.layoutmgr.InlineKnuthSequence;
  53. import org.apache.fop.layoutmgr.Keep;
  54. import org.apache.fop.layoutmgr.KnuthBlockBox;
  55. import org.apache.fop.layoutmgr.KnuthBox;
  56. import org.apache.fop.layoutmgr.KnuthElement;
  57. import org.apache.fop.layoutmgr.KnuthGlue;
  58. import org.apache.fop.layoutmgr.KnuthPenalty;
  59. import org.apache.fop.layoutmgr.KnuthPossPosIter;
  60. import org.apache.fop.layoutmgr.KnuthSequence;
  61. import org.apache.fop.layoutmgr.LayoutContext;
  62. import org.apache.fop.layoutmgr.LayoutManager;
  63. import org.apache.fop.layoutmgr.LeafPosition;
  64. import org.apache.fop.layoutmgr.ListElement;
  65. import org.apache.fop.layoutmgr.NonLeafPosition;
  66. import org.apache.fop.layoutmgr.Position;
  67. import org.apache.fop.layoutmgr.PositionIterator;
  68. import org.apache.fop.layoutmgr.SpaceSpecifier;
  69. import org.apache.fop.traits.MinOptMax;
  70. /**
  71. * LayoutManager for lines. It builds one or more lines containing
  72. * inline areas generated by its sub layout managers.
  73. * A break is found for each line which may contain one of more
  74. * breaks from the child layout managers.
  75. * Once a break is found then it is return for the parent layout
  76. * manager to handle.
  77. * When the areas are being added to the page this manager
  78. * creates a line area to contain the inline areas added by the
  79. * child layout managers.
  80. */
  81. public class LineLayoutManager extends InlineStackingLayoutManager
  82. implements BlockLevelLayoutManager {
  83. /**
  84. * this constant is used to create elements when text-align is center:
  85. * every TextLM descendant of LineLM must use the same value,
  86. * otherwise the line breaking algorithm does not find the right
  87. * break point
  88. */
  89. public static final int DEFAULT_SPACE_WIDTH = 3336;
  90. /**
  91. * logging instance
  92. */
  93. private static Log log = LogFactory.getLog(LineLayoutManager.class);
  94. private final Block fobj;
  95. private boolean isFirstInBlock;
  96. /**
  97. * Private class to store information about inline breaks.
  98. * Each value holds the start and end indexes into a List of
  99. * inline break positions.
  100. */
  101. static class LineBreakPosition extends LeafPosition {
  102. private final int parIndex; // index of the Paragraph this Position refers to
  103. private final int startIndex; //index of the first element this Position refers to
  104. private final int availableShrink;
  105. private final int availableStretch;
  106. private final int difference;
  107. private final double dAdjust; // Percentage to adjust (stretch or shrink)
  108. private final double ipdAdjust; // Percentage to adjust (stretch or shrink)
  109. private final int startIndent;
  110. private final int endIndent;
  111. private final int lineHeight;
  112. private final int lineWidth;
  113. private final int spaceBefore;
  114. private final int spaceAfter;
  115. private final int baseline;
  116. LineBreakPosition(LayoutManager lm, int index, int startIndex, int breakIndex,
  117. int shrink, int stretch, int diff, double ipdA, double adjust, int si,
  118. int ei, int lh, int lw, int sb, int sa, int bl) {
  119. super(lm, breakIndex);
  120. availableShrink = shrink;
  121. availableStretch = stretch;
  122. difference = diff;
  123. parIndex = index;
  124. this.startIndex = startIndex;
  125. ipdAdjust = ipdA;
  126. dAdjust = adjust;
  127. startIndent = si;
  128. endIndent = ei;
  129. lineHeight = lh;
  130. lineWidth = lw;
  131. spaceBefore = sb;
  132. spaceAfter = sa;
  133. baseline = bl;
  134. }
  135. }
  136. private int bidiLevel = -1;
  137. private int textAlignment = EN_JUSTIFY;
  138. private int textAlignmentLast;
  139. private int effectiveAlignment;
  140. private Length textIndent;
  141. private Length lastLineEndIndent;
  142. private CommonHyphenation hyphenationProperties;
  143. private Numeric hyphenationLadderCount;
  144. private int wrapOption = EN_WRAP;
  145. private int whiteSpaceTreament;
  146. //private LayoutProps layoutProps;
  147. private final Length lineHeight;
  148. private final int lead;
  149. private final int follow;
  150. private AlignmentContext alignmentContext;
  151. private int baselineOffset = -1;
  152. private List<KnuthSequence> knuthParagraphs;
  153. private LineLayoutPossibilities lineLayouts;
  154. private LineLayoutPossibilities[] lineLayoutsList;
  155. private int ipd;
  156. /**
  157. * When layout must be re-started due to a change of IPD, there is no need
  158. * to perform hyphenation on the remaining Knuth sequence once again.
  159. */
  160. private boolean hyphenationPerformed;
  161. /**
  162. * This class is used to remember
  163. * which was the first element in the paragraph
  164. * returned by each LM.
  165. */
  166. private final class Update {
  167. private final InlineLevelLayoutManager inlineLM;
  168. private final int firstIndex;
  169. private Update(InlineLevelLayoutManager lm, int index) {
  170. inlineLM = lm;
  171. firstIndex = index;
  172. }
  173. }
  174. // this class represents a paragraph
  175. private static class Paragraph extends InlineKnuthSequence {
  176. private static final long serialVersionUID = 5862072380375189105L;
  177. /** Number of elements to ignore at the beginning of the list. */
  178. private int ignoreAtStart;
  179. /** Number of elements to ignore at the end of the list. */
  180. private int ignoreAtEnd;
  181. // space at the end of the last line (in millipoints)
  182. private MinOptMax lineFiller;
  183. private final int textAlignment;
  184. private final int textAlignmentLast;
  185. private final int textIndent;
  186. private final int lastLineEndIndent;
  187. // the LM which created the paragraph
  188. private final LineLayoutManager layoutManager;
  189. Paragraph(LineLayoutManager llm, int alignment, int alignmentLast,
  190. int indent, int endIndent) {
  191. super();
  192. layoutManager = llm;
  193. textAlignment = alignment;
  194. textAlignmentLast = alignmentLast;
  195. textIndent = indent;
  196. lastLineEndIndent = endIndent;
  197. }
  198. @Override
  199. public void startSequence() {
  200. // set the minimum amount of empty space at the end of the
  201. // last line
  202. if (textAlignment == EN_CENTER) {
  203. lineFiller = MinOptMax.getInstance(lastLineEndIndent);
  204. } else {
  205. lineFiller = MinOptMax.getInstance(lastLineEndIndent, lastLineEndIndent,
  206. layoutManager.ipd);
  207. }
  208. // add auxiliary elements at the beginning of the paragraph
  209. if (textAlignment == EN_CENTER && textAlignmentLast != EN_JUSTIFY) {
  210. this.add(new KnuthGlue(0, 3 * DEFAULT_SPACE_WIDTH, 0,
  211. null, false));
  212. ignoreAtStart++;
  213. }
  214. // add the element representing text indentation
  215. // at the beginning of the first paragraph
  216. if (layoutManager.isFirstInBlock && layoutManager.knuthParagraphs.size() == 0
  217. && textIndent != 0) {
  218. this.add(new KnuthInlineBox(textIndent, null,
  219. null, false));
  220. ignoreAtStart++;
  221. }
  222. }
  223. public void endParagraph() {
  224. KnuthSequence finishedPar = this.endSequence();
  225. if (finishedPar != null) {
  226. layoutManager.knuthParagraphs.add(finishedPar);
  227. }
  228. }
  229. @Override
  230. public KnuthSequence endSequence() {
  231. if (this.size() > ignoreAtStart) {
  232. if (textAlignment == EN_CENTER
  233. && textAlignmentLast != EN_JUSTIFY) {
  234. this.add(new KnuthGlue(0, 3 * DEFAULT_SPACE_WIDTH, 0,
  235. null, false));
  236. this.add(new KnuthPenalty(lineFiller.getOpt(), -KnuthElement.INFINITE,
  237. false, null, false));
  238. ignoreAtEnd = 2;
  239. } else if (textAlignmentLast != EN_JUSTIFY) {
  240. // add the elements representing the space
  241. // at the end of the last line
  242. // and the forced break
  243. this.add(new KnuthPenalty(0, KnuthElement.INFINITE,
  244. false, null, false));
  245. this.add(new KnuthGlue(0,
  246. lineFiller.getStretch(),
  247. lineFiller.getShrink(), null, false));
  248. this.add(new KnuthPenalty(lineFiller.getOpt(), -KnuthElement.INFINITE,
  249. false, null, false));
  250. ignoreAtEnd = 3;
  251. } else {
  252. // add only the element representing the forced break
  253. this.add(new KnuthPenalty(lineFiller.getOpt(), -KnuthElement.INFINITE,
  254. false, null, false));
  255. ignoreAtEnd = 1;
  256. }
  257. return this;
  258. } else {
  259. this.clear();
  260. return null;
  261. }
  262. }
  263. /**
  264. * @return true if the sequence contains a box
  265. */
  266. public boolean containsBox() {
  267. for (Object o : this) {
  268. KnuthElement el = (KnuthElement) o;
  269. if (el.isBox()) {
  270. return true;
  271. }
  272. }
  273. return false;
  274. }
  275. }
  276. private class LineBreakingAlgorithm extends BreakingAlgorithm {
  277. private final LineLayoutManager thisLLM;
  278. private final int pageAlignment;
  279. private int activePossibility;
  280. private int addedPositions;
  281. private final int textIndent;
  282. private final int lineHeight;
  283. private final int lead;
  284. private final int follow;
  285. private static final double MAX_DEMERITS = 10e6;
  286. public LineBreakingAlgorithm(int pageAlign, int textAlign, int textAlignLast, int indent, int fillerWidth,
  287. int lh, int ld, int fl, boolean first, int maxFlagCount, LineLayoutManager llm) {
  288. super(textAlign, textAlignLast, first, false, maxFlagCount);
  289. pageAlignment = pageAlign;
  290. textIndent = indent;
  291. lineHeight = lh;
  292. lead = ld;
  293. follow = fl;
  294. thisLLM = llm;
  295. activePossibility = -1;
  296. }
  297. @Override
  298. public void updateData1(int lineCount, double demerits) {
  299. lineLayouts.addPossibility(lineCount, demerits);
  300. if (log.isTraceEnabled()) {
  301. log.trace("Layout possibility in " + lineCount + " lines; break at position:");
  302. }
  303. }
  304. @Override
  305. public void updateData2(KnuthNode bestActiveNode,
  306. KnuthSequence par,
  307. int total) {
  308. // compute indent and adjustment ratio, according to
  309. // the value of text-align and text-align-last
  310. int startIndent;
  311. int endIndent;
  312. int difference = bestActiveNode.difference;
  313. int textAlign = (bestActiveNode.line < total) ? alignment : alignmentLast;
  314. switch (textAlign) {
  315. case Constants.EN_START:
  316. startIndent = 0;
  317. endIndent = difference > 0 ? difference : 0;
  318. break;
  319. case Constants.EN_END:
  320. startIndent = difference;
  321. endIndent = 0;
  322. break;
  323. case Constants.EN_CENTER:
  324. startIndent = difference / 2;
  325. endIndent = startIndent;
  326. break;
  327. default:
  328. case Constants.EN_JUSTIFY:
  329. startIndent = 0;
  330. endIndent = 0;
  331. break;
  332. }
  333. /*
  334. startIndent += (textAlign == Constants.EN_CENTER)
  335. ? difference / 2 : (textAlign == Constants.EN_END) ? difference : 0;
  336. */
  337. startIndent += (bestActiveNode.line == 1 && indentFirstPart && isFirstInBlock)
  338. ? textIndent : 0;
  339. double ratio = (textAlign == Constants.EN_JUSTIFY
  340. || difference < 0 && -difference <= bestActiveNode.availableShrink)
  341. ? bestActiveNode.adjustRatio : 0;
  342. // add nodes at the beginning of the list, as they are found
  343. // backwards, from the last one to the first one
  344. // the first time this method is called, initialize activePossibility
  345. if (activePossibility == -1) {
  346. activePossibility = 0;
  347. addedPositions = 0;
  348. }
  349. if (addedPositions == lineLayouts.getLineCount(activePossibility)) {
  350. activePossibility++;
  351. addedPositions = 0;
  352. }
  353. int lack = difference + bestActiveNode.availableShrink;
  354. // if this LLM is nested inside a BlockContainerLayoutManager that is constraining
  355. // the available width and thus responsible for the overflow then we do not issue
  356. // warning event here and instead let the BCLM handle that at a later stage
  357. if (lack < 0 && !handleOverflow(-lack)) {
  358. InlineLevelEventProducer eventProducer
  359. = InlineLevelEventProducer.Provider.get(
  360. getFObj().getUserAgent().getEventBroadcaster());
  361. if (curChildLM.getFObj() == null) {
  362. eventProducer.lineOverflows(this, getFObj().getName(), bestActiveNode.line,
  363. -lack, getFObj().getLocator());
  364. } else {
  365. eventProducer.lineOverflows(this, curChildLM.getFObj().getName(), bestActiveNode.line,
  366. -lack, curChildLM.getFObj().getLocator());
  367. }
  368. }
  369. //log.debug("LLM> (" + (lineLayouts.getLineNumber(activePossibility) - addedPositions)
  370. // + ") difference = " + difference + " ratio = " + ratio);
  371. lineLayouts.addBreakPosition(makeLineBreakPosition(par,
  372. (bestActiveNode.line > 1 ? bestActiveNode.previous.position + 1 : 0),
  373. bestActiveNode.position,
  374. bestActiveNode.availableShrink - (addedPositions > 0
  375. ? 0 : ((Paragraph) par).lineFiller.getShrink()),
  376. bestActiveNode.availableStretch,
  377. difference, ratio, startIndent, endIndent), activePossibility);
  378. addedPositions++;
  379. }
  380. /* reset activePossibility, as if breakpoints have not yet been computed
  381. */
  382. public void resetAlgorithm() {
  383. activePossibility = -1;
  384. }
  385. private LineBreakPosition makeLineBreakPosition(KnuthSequence par, int firstElementIndex, int lastElementIndex,
  386. int availableShrink, int availableStretch, int difference, double ratio,
  387. int startIndent, int endIndent) {
  388. // line height calculation - spaceBefore may differ from spaceAfter
  389. // by 1mpt due to rounding
  390. int spaceBefore = (lineHeight - lead - follow) / 2;
  391. int spaceAfter = lineHeight - lead - follow - spaceBefore;
  392. // height before the main baseline
  393. int lineLead = lead;
  394. // maximum follow
  395. int lineFollow = follow;
  396. // true if this line contains only zero-height, auxiliary boxes
  397. // and the actual line width is 0; in this case, the line "collapses"
  398. // i.e. the line area will have bpd = 0
  399. boolean isZeroHeightLine = (difference == ipd);
  400. // if line-stacking-strategy is "font-height", the line height
  401. // is not affected by its content
  402. if (fobj.getLineStackingStrategy() != EN_FONT_HEIGHT) {
  403. ListIterator inlineIterator
  404. = par.listIterator(firstElementIndex);
  405. AlignmentContext lastAC = null;
  406. int maxIgnoredHeight = 0; // See spec 7.13
  407. for (int j = firstElementIndex;
  408. j <= lastElementIndex;
  409. j++) {
  410. KnuthElement element = (KnuthElement) inlineIterator.next();
  411. if (element instanceof KnuthInlineBox) {
  412. AlignmentContext ac = ((KnuthInlineBox) element).getAlignmentContext();
  413. if (ac != null && lastAC != ac) {
  414. if (!ac.usesInitialBaselineTable()
  415. || ac.getAlignmentBaselineIdentifier() != EN_BEFORE_EDGE
  416. && ac.getAlignmentBaselineIdentifier() != EN_AFTER_EDGE) {
  417. if (fobj.getLineHeightShiftAdjustment() == EN_CONSIDER_SHIFTS
  418. || ac.getBaselineShiftValue() == 0) {
  419. int alignmentOffset = ac.getTotalAlignmentBaselineOffset();
  420. if (alignmentOffset + ac.getAltitude() > lineLead) {
  421. lineLead = alignmentOffset + ac.getAltitude();
  422. }
  423. if (ac.getDepth() - alignmentOffset > lineFollow) {
  424. lineFollow = ac.getDepth() - alignmentOffset;
  425. }
  426. }
  427. } else {
  428. if (ac.getHeight() > maxIgnoredHeight) {
  429. maxIgnoredHeight = ac.getHeight();
  430. }
  431. }
  432. lastAC = ac;
  433. }
  434. if (isZeroHeightLine
  435. && (!element.isAuxiliary() || ac != null && ac.getHeight() > 0)) {
  436. isZeroHeightLine = false;
  437. }
  438. }
  439. }
  440. if (lineFollow < maxIgnoredHeight - lineLead) {
  441. lineFollow = maxIgnoredHeight - lineLead;
  442. }
  443. }
  444. constantLineHeight = lineLead + lineFollow;
  445. if (isZeroHeightLine) {
  446. return new LineBreakPosition(thisLLM,
  447. knuthParagraphs.indexOf(par),
  448. firstElementIndex, lastElementIndex,
  449. availableShrink, availableStretch,
  450. difference, ratio, 0, startIndent, endIndent,
  451. 0, ipd, 0, 0, 0);
  452. } else {
  453. return new LineBreakPosition(thisLLM,
  454. knuthParagraphs.indexOf(par),
  455. firstElementIndex, lastElementIndex,
  456. availableShrink, availableStretch,
  457. difference, ratio, 0, startIndent, endIndent,
  458. lineLead + lineFollow,
  459. ipd, spaceBefore, spaceAfter,
  460. lineLead);
  461. }
  462. }
  463. @Override
  464. protected int filterActiveNodes() {
  465. KnuthNode bestActiveNode = null;
  466. if (pageAlignment == EN_JUSTIFY) {
  467. // leave all active nodes and find the optimum line number
  468. //log.debug("LBA.filterActiveNodes> " + activeNodeCount + " layouts");
  469. for (int i = startLine; i < endLine; i++) {
  470. for (KnuthNode node = getNode(i); node != null; node = node.next) {
  471. //log.debug(" + lines = "
  472. //+ node.line + " demerits = " + node.totalDemerits);
  473. bestActiveNode = compareNodes(bestActiveNode, node);
  474. }
  475. }
  476. // scan the node set once again and remove some nodes
  477. //log.debug("LBA.filterActiveList> layout selection");
  478. for (int i = startLine; i < endLine; i++) {
  479. for (KnuthNode node = getNode(i); node != null; node = node.next) {
  480. //if (Math.abs(node.line - bestActiveNode.line) > maxDiff) {
  481. //if (false) {
  482. if (node.line != bestActiveNode.line
  483. && node.totalDemerits > MAX_DEMERITS) {
  484. //log.debug(" XXX lines = "
  485. //+ node.line + " demerits = " + node.totalDemerits);
  486. removeNode(i, node);
  487. } else {
  488. //log.debug(" ok lines = "
  489. //+ node.line + " demerits = " + node.totalDemerits);
  490. }
  491. }
  492. }
  493. } else {
  494. // leave only the active node with fewest total demerits
  495. for (int i = startLine; i < endLine; i++) {
  496. for (KnuthNode node = getNode(i); node != null; node = node.next) {
  497. bestActiveNode = compareNodes(bestActiveNode, node);
  498. if (node != bestActiveNode) {
  499. removeNode(i, node);
  500. }
  501. }
  502. }
  503. }
  504. return bestActiveNode.line;
  505. }
  506. }
  507. private int constantLineHeight = 12000;
  508. /**
  509. * Create a new Line Layout Manager.
  510. * This is used by the block layout manager to create
  511. * line managers for handling inline areas flowing into line areas.
  512. * @param block the block formatting object
  513. * @param lh the default line height
  514. * @param l the default lead, from top to baseline
  515. * @param f the default follow, from baseline to bottom
  516. */
  517. public LineLayoutManager(Block block, Length lh, int l, int f) {
  518. super(block);
  519. fobj = block;
  520. // the child FObj are owned by the parent BlockLM
  521. // this LM has all its childLMs preloaded
  522. fobjIter = null;
  523. lineHeight = lh;
  524. lead = l;
  525. follow = f;
  526. }
  527. /** {@inheritDoc} */
  528. @Override
  529. public void initialize() {
  530. bidiLevel = fobj.getBidiLevel();
  531. textAlignment = fobj.getTextAlign();
  532. textAlignmentLast = fobj.getTextAlignLast();
  533. textIndent = fobj.getTextIndent();
  534. lastLineEndIndent = fobj.getLastLineEndIndent();
  535. hyphenationProperties = fobj.getCommonHyphenation();
  536. hyphenationLadderCount = fobj.getHyphenationLadderCount();
  537. wrapOption = fobj.getWrapOption();
  538. whiteSpaceTreament = fobj.getWhitespaceTreatment();
  539. //
  540. effectiveAlignment = getEffectiveAlignment(textAlignment, textAlignmentLast);
  541. isFirstInBlock = (this == getParent().getChildLMs().get(0));
  542. }
  543. private int getEffectiveAlignment(int alignment, int alignmentLast) {
  544. if (textAlignment != EN_JUSTIFY && textAlignmentLast == EN_JUSTIFY) {
  545. return 0;
  546. } else {
  547. return textAlignment;
  548. }
  549. }
  550. /** {@inheritDoc} */
  551. @Override
  552. public List getNextKnuthElements(LayoutContext context, int alignment) {
  553. if (alignmentContext == null) {
  554. FontInfo fi = fobj.getFOEventHandler().getFontInfo();
  555. FontTriplet[] fontkeys = fobj.getCommonFont().getFontState(fi);
  556. Font fs = fi.getFontInstance(fontkeys[0], fobj.getCommonFont().fontSize.getValue(this));
  557. alignmentContext = new AlignmentContext(fs, lineHeight.getValue(this),
  558. context.getWritingMode());
  559. }
  560. context.setAlignmentContext(alignmentContext);
  561. ipd = context.getRefIPD();
  562. //PHASE 1: Create Knuth elements
  563. if (knuthParagraphs == null) {
  564. // it's the first time this method is called
  565. knuthParagraphs = new ArrayList<KnuthSequence>();
  566. // here starts Knuth's algorithm
  567. collectInlineKnuthElements(context);
  568. } else {
  569. // this method has been called before
  570. // all line breaks are already calculated
  571. }
  572. // return finished when there's no content
  573. if (knuthParagraphs.size() == 0) {
  574. setFinished(true);
  575. return null;
  576. }
  577. //PHASE 2: Create line breaks
  578. return createLineBreaks(context.getBPAlignment(), context);
  579. }
  580. /**
  581. * Get a sequence of KnuthElements representing the content
  582. * of the node assigned to the LM.
  583. * @param context the LayoutContext used to store layout information
  584. * @param alignment the desired text alignment
  585. * @param restartPosition position at restart
  586. * @return the list of KnuthElements
  587. * @see LayoutManager#getNextKnuthElements(LayoutContext,int)
  588. */
  589. public List getNextKnuthElements(LayoutContext context, int alignment,
  590. LeafPosition restartPosition) {
  591. log.trace("Restarting line breaking from index " + restartPosition.getIndex());
  592. int parIndex = restartPosition.getLeafPos();
  593. KnuthSequence paragraph = knuthParagraphs.get(parIndex);
  594. if (paragraph instanceof Paragraph) {
  595. ((Paragraph) paragraph).ignoreAtStart = 0;
  596. isFirstInBlock = false;
  597. }
  598. paragraph.subList(0, restartPosition.getIndex() + 1).clear();
  599. Iterator<KnuthElement> iter = paragraph.iterator();
  600. while (iter.hasNext() && !iter.next().isBox()) {
  601. iter.remove();
  602. }
  603. if (!iter.hasNext()) {
  604. knuthParagraphs.remove(parIndex);
  605. }
  606. // return finished when there's no content
  607. if (knuthParagraphs.size() == 0) {
  608. setFinished(true);
  609. return null;
  610. }
  611. ipd = context.getRefIPD();
  612. //PHASE 2: Create line breaks
  613. return createLineBreaks(context.getBPAlignment(), context);
  614. }
  615. /**
  616. * Phase 1 of Knuth algorithm: Collect all inline Knuth elements before determining line breaks.
  617. * @param context the LayoutContext
  618. */
  619. private void collectInlineKnuthElements(LayoutContext context) {
  620. LayoutContext inlineLC = LayoutContext.copyOf(context);
  621. // convert all the text in a sequence of paragraphs made
  622. // of KnuthBox, KnuthGlue and KnuthPenalty objects
  623. boolean previousIsBox = false;
  624. StringBuffer trace = new StringBuffer("LineLM:");
  625. Paragraph lastPar = null;
  626. InlineLevelLayoutManager curLM;
  627. while ((curLM = (InlineLevelLayoutManager) getChildLM()) != null) {
  628. List inlineElements = curLM.getNextKnuthElements(inlineLC, effectiveAlignment);
  629. if (inlineElements == null || inlineElements.size() == 0) {
  630. /* curLM.getNextKnuthElements() returned null or an empty list;
  631. * this can happen if there is nothing more to layout,
  632. * so just iterate once more to see if there are other children */
  633. continue;
  634. }
  635. if (lastPar != null) {
  636. KnuthSequence firstSeq = (KnuthSequence) inlineElements.get(0);
  637. // finish last paragraph before a new block sequence
  638. if (!firstSeq.isInlineSequence()) {
  639. lastPar.endParagraph();
  640. ElementListObserver.observe(lastPar, "line", null);
  641. lastPar = null;
  642. if (log.isTraceEnabled()) {
  643. trace.append(" ]");
  644. }
  645. previousIsBox = false;
  646. }
  647. // does the first element of the first paragraph add to an existing word?
  648. if (lastPar != null) {
  649. KnuthElement thisElement;
  650. thisElement = (KnuthElement) firstSeq.get(0);
  651. if (thisElement.isBox() && !thisElement.isAuxiliary()
  652. && previousIsBox) {
  653. lastPar.addALetterSpace();
  654. }
  655. }
  656. }
  657. // loop over the KnuthSequences (and single KnuthElements) in returnedList
  658. for (Object inlineElement : inlineElements) {
  659. KnuthSequence sequence = (KnuthSequence) inlineElement;
  660. // the sequence contains inline Knuth elements
  661. if (sequence.isInlineSequence()) {
  662. // look at the last element
  663. ListElement lastElement = sequence.getLast();
  664. assert lastElement != null;
  665. previousIsBox = lastElement.isBox()
  666. && !((KnuthElement) lastElement).isAuxiliary()
  667. && ((KnuthElement) lastElement).getWidth() != 0;
  668. // if last paragraph is open, add the new elements to the paragraph
  669. // else this is the last paragraph
  670. if (lastPar == null) {
  671. lastPar = new Paragraph(this,
  672. textAlignment, textAlignmentLast,
  673. textIndent.getValue(this),
  674. lastLineEndIndent.getValue(this));
  675. lastPar.startSequence();
  676. if (log.isTraceEnabled()) {
  677. trace.append(" [");
  678. }
  679. } else {
  680. if (log.isTraceEnabled()) {
  681. trace.append(" +");
  682. }
  683. }
  684. lastPar.addAll(sequence);
  685. if (log.isTraceEnabled()) {
  686. trace.append(" I");
  687. }
  688. // finish last paragraph if it was closed with a linefeed
  689. if (lastElement.isPenalty()
  690. && ((KnuthPenalty) lastElement).getPenalty()
  691. == -KnuthPenalty.INFINITE) {
  692. // a penalty item whose value is -inf
  693. // represents a preserved linefeed,
  694. // which forces a line break
  695. lastPar.removeLast();
  696. if (!lastPar.containsBox()) {
  697. //only a forced linefeed on this line
  698. //-> compensate with an auxiliary glue
  699. lastPar.add(new KnuthGlue(ipd, 0, ipd, null, true));
  700. }
  701. lastPar.endParagraph();
  702. ElementListObserver.observe(lastPar, "line", null);
  703. lastPar = null;
  704. if (log.isTraceEnabled()) {
  705. trace.append(" ]");
  706. }
  707. previousIsBox = false;
  708. }
  709. } else { // the sequence is a block sequence
  710. // the positions will be wrapped with this LM in postProcessLineBreaks
  711. knuthParagraphs.add(sequence);
  712. if (log.isTraceEnabled()) {
  713. trace.append(" B");
  714. }
  715. }
  716. } // end of loop over returnedList
  717. }
  718. if (lastPar != null) {
  719. lastPar.endParagraph();
  720. ElementListObserver.observe(lastPar, "line", fobj.getId());
  721. if (log.isTraceEnabled()) {
  722. trace.append(" ]");
  723. }
  724. }
  725. log.trace(trace);
  726. }
  727. /**
  728. * Phase 2 of Knuth algorithm: find optimal break points.
  729. * @param alignment alignment in BP direction of the paragraph
  730. * @param context the layout context
  731. * @return a list of Knuth elements representing broken lines
  732. */
  733. private List<ListElement> createLineBreaks(int alignment, LayoutContext context) {
  734. // find the optimal line breaking points for each paragraph
  735. Iterator<KnuthSequence> paragraphsIterator = knuthParagraphs.iterator();
  736. lineLayoutsList = new LineLayoutPossibilities[knuthParagraphs.size()];
  737. LineLayoutPossibilities llPoss;
  738. for (int i = 0; paragraphsIterator.hasNext(); i++) {
  739. KnuthSequence seq = paragraphsIterator.next();
  740. if (!seq.isInlineSequence()) {
  741. // This set of line layout possibilities does not matter;
  742. // we only need an entry in lineLayoutsList.
  743. llPoss = new LineLayoutPossibilities();
  744. } else {
  745. llPoss = findOptimalBreakingPoints(alignment, (Paragraph) seq,
  746. !paragraphsIterator.hasNext());
  747. }
  748. lineLayoutsList[i] = llPoss;
  749. }
  750. setFinished(true);
  751. //Post-process the line breaks found
  752. return postProcessLineBreaks(alignment, context);
  753. }
  754. /**
  755. * Find the optimal linebreaks for a paragraph
  756. * @param alignment alignment of the paragraph
  757. * @param currPar the Paragraph for which the linebreaks are found
  758. * @param isLastPar flag indicating whether currPar is the last paragraph
  759. * @return the line layout possibilities for the paragraph
  760. */
  761. private LineLayoutPossibilities findOptimalBreakingPoints(int alignment, Paragraph currPar,
  762. boolean isLastPar) {
  763. // use the member lineLayouts, which is read by LineBreakingAlgorithm.updateData1 and 2
  764. lineLayouts = new LineLayoutPossibilities();
  765. double maxAdjustment = 1;
  766. LineBreakingAlgorithm alg = new LineBreakingAlgorithm(alignment,
  767. textAlignment, textAlignmentLast,
  768. textIndent.getValue(this), currPar.lineFiller.getOpt(),
  769. lineHeight.getValue(this), lead, follow,
  770. (knuthParagraphs.indexOf(currPar) == 0),
  771. hyphenationLadderCount.getEnum() == EN_NO_LIMIT
  772. ? 0 : hyphenationLadderCount.getValue(),
  773. this);
  774. alg.setConstantLineWidth(ipd);
  775. boolean canWrap = (wrapOption != EN_NO_WRAP);
  776. boolean canHyphenate = (canWrap && hyphenationProperties.hyphenate.getEnum() == EN_TRUE);
  777. // find hyphenation points, if allowed and not yet done
  778. if (canHyphenate && !hyphenationPerformed) {
  779. // make sure findHyphenationPoints() is bypassed if
  780. // the method is called twice (e.g. due to changing page-ipd)
  781. hyphenationPerformed = isLastPar;
  782. findHyphenationPoints(currPar);
  783. }
  784. // first try: do not consider hyphenation points as legal breaks
  785. int allowedBreaks = (canWrap ? BreakingAlgorithm.NO_FLAGGED_PENALTIES
  786. : BreakingAlgorithm.ONLY_FORCED_BREAKS);
  787. int breakingPoints = alg.findBreakingPoints(currPar, maxAdjustment, false, allowedBreaks);
  788. if (breakingPoints == 0 || alignment == EN_JUSTIFY) {
  789. // if the first try found a set of breaking points, save them
  790. if (breakingPoints > 0) {
  791. alg.resetAlgorithm();
  792. lineLayouts.savePossibilities(false);
  793. } else {
  794. // the first try failed
  795. log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment);
  796. }
  797. // now try something different
  798. log.debug("Hyphenation possible? " + canHyphenate);
  799. // Note: if allowedBreaks is guaranteed to be unchanged by alg.findBreakingPoints(),
  800. // the below check can be simplified to 'if (canHyphenate) ...'
  801. if (canHyphenate && allowedBreaks != BreakingAlgorithm.ONLY_FORCED_BREAKS) {
  802. // consider every hyphenation point as a legal break
  803. allowedBreaks = BreakingAlgorithm.ALL_BREAKS;
  804. } else {
  805. // try with a higher threshold
  806. maxAdjustment = 5;
  807. }
  808. breakingPoints = alg.findBreakingPoints(currPar, maxAdjustment, false, allowedBreaks);
  809. if (breakingPoints == 0) {
  810. // the second try failed too, try with a huge threshold
  811. // and force the algorithm to find a set of breaking points
  812. if (log.isDebugEnabled()) {
  813. log.debug("No set of breaking points found with maxAdjustment = "
  814. + maxAdjustment + (canHyphenate ? " and hyphenation" : ""));
  815. }
  816. maxAdjustment = 20;
  817. alg.findBreakingPoints(currPar, maxAdjustment, true, allowedBreaks);
  818. }
  819. // use non-hyphenated breaks, when possible
  820. lineLayouts.restorePossibilities();
  821. }
  822. return lineLayouts;
  823. }
  824. /**
  825. * Creates the element list in BP direction for the broken lines.
  826. * @param alignment the currently applicable vertical alignment
  827. * @param context the layout context
  828. * @return the newly built element list
  829. */
  830. private List<ListElement> postProcessLineBreaks(int alignment, LayoutContext context) {
  831. List<ListElement> returnList = new LinkedList<ListElement>();
  832. int endIndex = -1;
  833. for (int p = 0; p < knuthParagraphs.size(); p++) {
  834. // penalty between paragraphs
  835. if (p > 0) {
  836. Keep keep = getKeepTogether();
  837. returnList.add(new BreakElement(
  838. new Position(this),
  839. keep.getPenalty(),
  840. keep.getContext(),
  841. context));
  842. }
  843. LineLayoutPossibilities llPoss = lineLayoutsList[p];
  844. KnuthSequence seq = knuthParagraphs.get(p);
  845. if (!seq.isInlineSequence()) {
  846. List<ListElement> targetList = new LinkedList<ListElement>();
  847. for (Object aSeq : seq) {
  848. ListElement tempElement;
  849. tempElement = (ListElement) aSeq;
  850. LayoutManager lm = tempElement.getLayoutManager();
  851. if (baselineOffset < 0 && lm != null && lm.hasLineAreaDescendant()) {
  852. baselineOffset = lm.getBaselineOffset();
  853. }
  854. if (lm != this) {
  855. tempElement.setPosition(notifyPos(new NonLeafPosition(this,
  856. tempElement.getPosition())));
  857. }
  858. targetList.add(tempElement);
  859. }
  860. returnList.addAll(targetList);
  861. } else if (seq.isInlineSequence() && alignment == EN_JUSTIFY) {
  862. /* justified vertical alignment (not in the XSL FO recommendation):
  863. create a multi-layout sequence whose elements will contain
  864. a conventional Position */
  865. Position returnPosition = new LeafPosition(this, p);
  866. createElements(returnList, llPoss, returnPosition);
  867. } else {
  868. /* "normal" vertical alignment: create a sequence whose boxes
  869. represent effective lines, and contain LineBreakPositions */
  870. int startIndex = 0;
  871. int previousEndIndex = 0;
  872. for (int i = 0;
  873. i < llPoss.getChosenLineCount();
  874. i++) {
  875. int orphans = fobj.getOrphans();
  876. int widows = fobj.getWidows();
  877. if (handlingFloat()) {
  878. orphans = 1;
  879. widows = 1;
  880. }
  881. if (returnList.size() > 0
  882. && i > 0 //if i==0 break generated above already
  883. && i >= orphans && i <= llPoss.getChosenLineCount() - widows) {
  884. // penalty allowing a page break between lines
  885. Keep keep = getKeepTogether();
  886. returnList.add(new BreakElement(
  887. new LeafPosition(this, p, endIndex),
  888. keep.getPenalty(),
  889. keep.getContext(),
  890. context));
  891. }
  892. endIndex = llPoss.getChosenPosition(i).getLeafPos();
  893. // create a list of the FootnoteBodyLM handling footnotes
  894. // whose citations are in this line
  895. List<FootnoteBodyLayoutManager> footnoteList = FootenoteUtil.getFootnotes(
  896. seq, startIndex, endIndex);
  897. List<FloatContentLayoutManager> floats = FloatContentLayoutManager.checkForFloats(seq,
  898. startIndex, endIndex);
  899. startIndex = endIndex + 1;
  900. LineBreakPosition lbp = llPoss.getChosenPosition(i);
  901. if (baselineOffset < 0) {
  902. baselineOffset = lbp.spaceBefore + lbp.baseline;
  903. }
  904. if (floats.isEmpty()) {
  905. returnList.add(new KnuthBlockBox(lbp.lineHeight + lbp.spaceBefore + lbp.spaceAfter,
  906. footnoteList, lbp, false));
  907. } else {
  908. // add a line with height zero and no content and attach float to it
  909. returnList.add(new KnuthBlockBox(0, Collections.emptyList(), null, false, floats));
  910. // add a break element to signal that we should restart LB at this break
  911. Keep keep = getKeepTogether();
  912. returnList.add(new BreakElement(new LeafPosition(this, p, previousEndIndex), keep
  913. .getPenalty(), keep.getContext(), context));
  914. // add the original line where the float was but without the float now
  915. returnList.add(new KnuthBlockBox(lbp.lineHeight + lbp.spaceBefore + lbp.spaceAfter,
  916. footnoteList, lbp, false));
  917. }
  918. previousEndIndex = endIndex;
  919. }
  920. }
  921. }
  922. return returnList;
  923. }
  924. private void createElements(List<ListElement> list, LineLayoutPossibilities llPoss,
  925. Position elementPosition) {
  926. /* number of normal, inner lines */
  927. int innerLines = 0;
  928. /* number of lines that can be used in order to fill more space */
  929. int optionalLines = 0;
  930. /* number of lines that can be used in order to fill more space
  931. only if the paragraph is not parted */
  932. int conditionalOptionalLines = 0;
  933. /* number of lines that can be omitted in order to fill less space */
  934. int eliminableLines = 0;
  935. /* number of lines that can be omitted in order to fill less space
  936. only if the paragraph is not parted */
  937. int conditionalEliminableLines = 0;
  938. /* number of the first unbreakable lines */
  939. int firstLines = fobj.getOrphans();
  940. /* number of the last unbreakable lines */
  941. int lastLines = fobj.getWidows();
  942. /* sub-sequence used to separate the elements representing different lines */
  943. List<KnuthElement> breaker = new LinkedList<KnuthElement>();
  944. /* comment out the next lines in order to test particular situations */
  945. if (fobj.getOrphans() + fobj.getWidows() <= llPoss.getMinLineCount()) {
  946. innerLines = llPoss.getMinLineCount() - (fobj.getOrphans() + fobj.getWidows());
  947. optionalLines = llPoss.getMaxLineCount() - llPoss.getOptLineCount();
  948. eliminableLines = llPoss.getOptLineCount() - llPoss.getMinLineCount();
  949. } else if (fobj.getOrphans() + fobj.getWidows() <= llPoss.getOptLineCount()) {
  950. optionalLines = llPoss.getMaxLineCount() - llPoss.getOptLineCount();
  951. eliminableLines = llPoss.getOptLineCount() - (fobj.getOrphans() + fobj.getWidows());
  952. conditionalEliminableLines
  953. = (fobj.getOrphans() + fobj.getWidows()) - llPoss.getMinLineCount();
  954. } else if (fobj.getOrphans() + fobj.getWidows() <= llPoss.getMaxLineCount()) {
  955. optionalLines = llPoss.getMaxLineCount() - (fobj.getOrphans() + fobj.getWidows());
  956. conditionalOptionalLines
  957. = (fobj.getOrphans() + fobj.getWidows()) - llPoss.getOptLineCount();
  958. conditionalEliminableLines = llPoss.getOptLineCount() - llPoss.getMinLineCount();
  959. firstLines -= conditionalOptionalLines;
  960. } else {
  961. conditionalOptionalLines = llPoss.getMaxLineCount() - llPoss.getOptLineCount();
  962. conditionalEliminableLines = llPoss.getOptLineCount() - llPoss.getMinLineCount();
  963. firstLines = llPoss.getOptLineCount();
  964. lastLines = 0;
  965. }
  966. /* comment out the previous lines in order to test particular situations */
  967. /* use these lines to test particular situations
  968. innerLines = 0;
  969. optionalLines = 1;
  970. conditionalOptionalLines = 2;
  971. eliminableLines = 0;
  972. conditionalEliminableLines = 0;
  973. firstLines = 1;
  974. lastLines = 3;
  975. */
  976. if (lastLines != 0
  977. && (conditionalOptionalLines > 0 || conditionalEliminableLines > 0)) {
  978. breaker.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
  979. breaker.add(new KnuthGlue(0, -conditionalOptionalLines * constantLineHeight,
  980. -conditionalEliminableLines * constantLineHeight,
  981. Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
  982. breaker.add(new KnuthPenalty(conditionalOptionalLines * constantLineHeight,
  983. 0, false, elementPosition, false));
  984. breaker.add(new KnuthGlue(0, conditionalOptionalLines * constantLineHeight,
  985. conditionalEliminableLines * constantLineHeight,
  986. Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
  987. } else if (lastLines != 0) {
  988. breaker.add(new KnuthPenalty(0, 0, false, elementPosition, false));
  989. }
  990. // creation of the elements:
  991. // first group of lines
  992. list.add(new KnuthBox(firstLines * constantLineHeight, elementPosition,
  993. (lastLines == 0
  994. && conditionalOptionalLines == 0
  995. && conditionalEliminableLines == 0)));
  996. if (conditionalOptionalLines > 0
  997. || conditionalEliminableLines > 0) {
  998. list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
  999. list.add(new KnuthGlue(0, conditionalOptionalLines * constantLineHeight,
  1000. conditionalEliminableLines * constantLineHeight,
  1001. Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
  1002. list.add(new KnuthBox(0, elementPosition, (lastLines == 0)));
  1003. }
  1004. // optional lines
  1005. for (int i = 0; i < optionalLines; i++) {
  1006. list.addAll(breaker);
  1007. list.add(new KnuthBox(0, elementPosition, false));
  1008. list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
  1009. list.add(new KnuthGlue(0, constantLineHeight, 0,
  1010. Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
  1011. list.add(new KnuthBox(0, elementPosition, false));
  1012. }
  1013. // eliminable lines
  1014. for (int i = 0; i < eliminableLines; i++) {
  1015. list.addAll(breaker);
  1016. list.add(new KnuthBox(constantLineHeight, elementPosition, false));
  1017. list.add(new KnuthPenalty(0, KnuthElement.INFINITE, false, elementPosition, false));
  1018. list.add(new KnuthGlue(0, 0, constantLineHeight,
  1019. Adjustment.LINE_NUMBER_ADJUSTMENT, elementPosition, false));
  1020. list.add(new KnuthBox(0, elementPosition, false));
  1021. }
  1022. // inner lines
  1023. for (int i = 0; i < innerLines; i++) {
  1024. list.addAll(breaker);
  1025. list.add(new KnuthBox(constantLineHeight, elementPosition, false));
  1026. }
  1027. // last group of lines
  1028. if (lastLines > 0) {
  1029. list.addAll(breaker);
  1030. list.add(new KnuthBox(lastLines * constantLineHeight,
  1031. elementPosition, true));
  1032. }
  1033. }
  1034. /** {@inheritDoc} */
  1035. public boolean mustKeepTogether() {
  1036. return ((BlockLevelLayoutManager) getParent()).mustKeepTogether();
  1037. }
  1038. /** {@inheritDoc} */
  1039. public KeepProperty getKeepTogetherProperty() {
  1040. return ((BlockLevelLayoutManager) getParent()).getKeepTogetherProperty();
  1041. }
  1042. /** {@inheritDoc} */
  1043. public KeepProperty getKeepWithPreviousProperty() {
  1044. return ((BlockLevelLayoutManager) getParent()).getKeepWithPreviousProperty();
  1045. }
  1046. /** {@inheritDoc} */
  1047. public KeepProperty getKeepWithNextProperty() {
  1048. return ((BlockLevelLayoutManager) getParent()).getKeepWithNextProperty();
  1049. }
  1050. /** {@inheritDoc} */
  1051. public Keep getKeepTogether() {
  1052. return ((BlockLevelLayoutManager) getParent()).getKeepTogether();
  1053. }
  1054. /** {@inheritDoc} */
  1055. public boolean mustKeepWithPrevious() {
  1056. return !getKeepWithPrevious().isAuto();
  1057. }
  1058. /** {@inheritDoc} */
  1059. public boolean mustKeepWithNext() {
  1060. return !getKeepWithNext().isAuto();
  1061. }
  1062. /** {@inheritDoc} */
  1063. public Keep getKeepWithNext() {
  1064. return Keep.KEEP_AUTO;
  1065. }
  1066. /** {@inheritDoc} */
  1067. public Keep getKeepWithPrevious() {
  1068. return Keep.KEEP_AUTO;
  1069. }
  1070. /** {@inheritDoc} */
  1071. public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) {
  1072. Position lastPos = lastElement.getPosition();
  1073. assert (lastPos instanceof LeafPosition);
  1074. LeafPosition pos = (LeafPosition) lastPos;
  1075. //if (lastElement.isPenalty()) {
  1076. // totalAdj += lastElement.getWidth();
  1077. //}
  1078. //int lineNumberDifference = (int)((double) totalAdj / constantLineHeight);
  1079. int lineNumberDifference = (int) Math.round((double) adj / constantLineHeight
  1080. + (adj > 0 ? -0.4 : 0.4));
  1081. //log.debug(" LLM> variazione calcolata = " + ((double) totalAdj / constantLineHeight)
  1082. //+ " variazione applicata = " + lineNumberDifference);
  1083. LineLayoutPossibilities llPoss;
  1084. llPoss = lineLayoutsList[pos.getLeafPos()];
  1085. lineNumberDifference = llPoss.applyLineCountAdjustment(lineNumberDifference);
  1086. return lineNumberDifference * constantLineHeight;
  1087. }
  1088. /** {@inheritDoc} */
  1089. public void discardSpace(KnuthGlue spaceGlue) {
  1090. }
  1091. /** {@inheritDoc} */
  1092. @Override
  1093. public List getChangedKnuthElements(List oldList, int alignment, int depth) {
  1094. return getChangedKnuthElements(oldList, alignment);
  1095. }
  1096. /** {@inheritDoc} */
  1097. @Override
  1098. public List getChangedKnuthElements(List oldList, int alignment) {
  1099. List<KnuthElement> returnList = new LinkedList<KnuthElement>();
  1100. for (int p = 0; p < knuthParagraphs.size(); p++) {
  1101. LineLayoutPossibilities llPoss = lineLayoutsList[p];
  1102. //log.debug("demerits of the chosen layout: " + llPoss.getChosenDemerits());
  1103. int orphans = fobj.getOrphans();
  1104. int widows = fobj.getWidows();
  1105. if (handlingFloat()) {
  1106. orphans = 1;
  1107. widows = 1;
  1108. }
  1109. for (int i = 0; i < llPoss.getChosenLineCount(); i++) {
  1110. if (!((BlockLevelLayoutManager) parentLayoutManager).mustKeepTogether() && i >= orphans
  1111. && i <= llPoss.getChosenLineCount() - widows) {
  1112. // null penalty allowing a page break between lines
  1113. returnList.add(new KnuthPenalty(0, 0, false, new Position(this), false));
  1114. }
  1115. LineBreakPosition lbp = llPoss.getChosenPosition(i);
  1116. //log.debug("LLM.getChangedKnuthElements> lineWidth= "
  1117. // + lbp.lineWidth + " difference= " + lbp.difference);
  1118. //log.debug(" shrink= "
  1119. // + lbp.availableShrink + " stretch= " + lbp.availableStretch);
  1120. //log.debug("linewidth= " + lbp.lineWidth + " difference= "
  1121. //+ lbp.difference + " indent= " + lbp.startIndent);
  1122. MinOptMax contentIPD;
  1123. if (alignment == EN_JUSTIFY) {
  1124. contentIPD = MinOptMax.getInstance(
  1125. lbp.lineWidth - lbp.difference - lbp.availableShrink,
  1126. lbp.lineWidth - lbp.difference,
  1127. lbp.lineWidth - lbp.difference + lbp.availableStretch);
  1128. } else if (alignment == EN_CENTER) {
  1129. contentIPD = MinOptMax.getInstance(lbp.lineWidth - 2 * lbp.startIndent);
  1130. } else if (alignment == EN_END) {
  1131. contentIPD = MinOptMax.getInstance(lbp.lineWidth - lbp.startIndent);
  1132. } else {
  1133. contentIPD
  1134. = MinOptMax.getInstance(lbp.lineWidth - lbp.difference + lbp.startIndent);
  1135. }
  1136. returnList.add(new KnuthBlockBox(lbp.lineHeight, contentIPD, (lbp.ipdAdjust != 0
  1137. ? lbp.lineWidth - lbp.difference : 0),
  1138. lbp, false));
  1139. }
  1140. }
  1141. return returnList;
  1142. }
  1143. /**
  1144. * Find hyphenation points for every word in the current paragraph.
  1145. *
  1146. * @param currPar the paragraph whose words will be hyphenated
  1147. */
  1148. private void findHyphenationPoints(Paragraph currPar) {
  1149. // hyphenate every word
  1150. ListIterator currParIterator = currPar.listIterator(currPar.ignoreAtStart);
  1151. // list of TLM involved in hyphenation
  1152. List updateList = new LinkedList();
  1153. KnuthElement firstElement;
  1154. KnuthElement nextElement;
  1155. // current InlineLevelLayoutManager
  1156. InlineLevelLayoutManager currLM = null;
  1157. // number of KnuthBox elements containing word fragments
  1158. int boxCount;
  1159. // number of auxiliary KnuthElements between KnuthBoxes
  1160. int auxCount;
  1161. StringBuffer sbChars;
  1162. // find all hyphenation points
  1163. while (currParIterator.hasNext()) {
  1164. firstElement = (KnuthElement) currParIterator.next();
  1165. //
  1166. if (firstElement.getLayoutManager() != currLM) {
  1167. currLM = (InlineLevelLayoutManager) firstElement.getLayoutManager();
  1168. if (currLM != null) {
  1169. updateList.add(new Update(currLM, currParIterator.previousIndex()));
  1170. } else {
  1171. break;
  1172. }
  1173. } else if (currLM == null) {
  1174. break;
  1175. }
  1176. // collect word fragments, ignoring auxiliary elements;
  1177. // each word fragment was created by a different TextLM
  1178. if (firstElement.isBox() && !firstElement.isAuxiliary()) {
  1179. boxCount = 1;
  1180. auxCount = 0;
  1181. sbChars = new StringBuffer();
  1182. sbChars.append(currLM.getWordChars(firstElement.getPosition()));
  1183. // look if next elements are boxes too
  1184. while (currParIterator.hasNext()) {
  1185. nextElement = (KnuthElement) currParIterator.next();
  1186. if (nextElement.isBox() && !nextElement.isAuxiliary()) {
  1187. // a non-auxiliary KnuthBox: append word chars
  1188. if (currLM != nextElement.getLayoutManager()) {
  1189. currLM = (InlineLevelLayoutManager) nextElement.getLayoutManager();
  1190. updateList.add(new Update(currLM, currParIterator.previousIndex()));
  1191. }
  1192. // append text to recreate the whole word
  1193. boxCount++;
  1194. sbChars.append(currLM.getWordChars(nextElement.getPosition()));
  1195. } else if (!nextElement.isAuxiliary()) {
  1196. // a non-auxiliary non-box KnuthElement: stop
  1197. // go back to the last box or auxiliary element
  1198. currParIterator.previous();
  1199. break;
  1200. } else {
  1201. if (currLM != nextElement.getLayoutManager()) {
  1202. currLM = (InlineLevelLayoutManager) nextElement.getLayoutManager();
  1203. updateList.add(new Update(currLM, currParIterator.previousIndex()));
  1204. }
  1205. // an auxiliary KnuthElement: simply ignore it
  1206. auxCount++;
  1207. }
  1208. }
  1209. if (log.isTraceEnabled()) {
  1210. log.trace(" Word to hyphenate: " + sbChars);
  1211. }
  1212. // find hyphenation points
  1213. HyphContext hc = getHyphenContext(sbChars);
  1214. // ask each LM to hyphenate its word fragment
  1215. if (hc != null) {
  1216. KnuthElement element = null;
  1217. for (int i = 0; i < (boxCount + auxCount); i++) {
  1218. currParIterator.previous();
  1219. }
  1220. for (int i = 0; i < (boxCount + auxCount); i++) {
  1221. element = (KnuthElement) currParIterator.next();
  1222. if (element.isBox() && !element.isAuxiliary()) {
  1223. ((InlineLevelLayoutManager)
  1224. element.getLayoutManager()).hyphenate(element.getPosition(), hc);
  1225. } else {
  1226. // nothing to do, element is an auxiliary KnuthElement
  1227. }
  1228. }
  1229. }
  1230. }
  1231. }
  1232. processUpdates(currPar, updateList);
  1233. }
  1234. private void processUpdates(Paragraph par, List updateList) {
  1235. // create iterator for the updateList
  1236. ListIterator updateListIterator = updateList.listIterator();
  1237. Update currUpdate;
  1238. int elementsAdded = 0;
  1239. while (updateListIterator.hasNext()) {
  1240. // ask the LMs to apply the changes and return
  1241. // the new KnuthElements to replace the old ones
  1242. currUpdate = (Update) updateListIterator.next();
  1243. int fromIndex = currUpdate.firstIndex;
  1244. int toIndex;
  1245. if (updateListIterator.hasNext()) {
  1246. Update nextUpdate = (Update) updateListIterator.next();
  1247. toIndex = nextUpdate.firstIndex;
  1248. updateListIterator.previous();
  1249. } else {
  1250. // maybe this is not always correct!
  1251. toIndex = par.size() - par.ignoreAtEnd
  1252. - elementsAdded;
  1253. }
  1254. // applyChanges() returns true if the LM modifies its data,
  1255. // so it must return new KnuthElements to replace the old ones
  1256. if (currUpdate.inlineLM
  1257. .applyChanges(par.subList(fromIndex + elementsAdded,
  1258. toIndex + elementsAdded))) {
  1259. // insert the new KnuthElements
  1260. List newElements = currUpdate.inlineLM.getChangedKnuthElements(
  1261. par.subList(fromIndex + elementsAdded,
  1262. toIndex + elementsAdded),
  1263. /*flaggedPenalty,*/ effectiveAlignment);
  1264. // remove the old elements
  1265. par.subList(fromIndex + elementsAdded,
  1266. toIndex + elementsAdded).clear();
  1267. // insert the new elements
  1268. par.addAll(fromIndex + elementsAdded, newElements);
  1269. elementsAdded += newElements.size() - (toIndex - fromIndex);
  1270. }
  1271. }
  1272. updateList.clear();
  1273. }
  1274. /**
  1275. * Line area is always considered to act as a fence.
  1276. * @param isNotFirst ignored
  1277. * @return always true
  1278. */
  1279. @Override
  1280. protected boolean hasLeadingFence(boolean isNotFirst) {
  1281. return true;
  1282. }
  1283. /**
  1284. * Line area is always considered to act as a fence.
  1285. * @param isNotLast ignored
  1286. * @return always true
  1287. */
  1288. @Override
  1289. protected boolean hasTrailingFence(boolean isNotLast) {
  1290. return true;
  1291. }
  1292. private HyphContext getHyphenContext(StringBuffer sbChars) {
  1293. // Find all hyphenation points in this word
  1294. // (get in an array of offsets)
  1295. // hyphenationProperties are from the block level?.
  1296. // Note that according to the spec,
  1297. // they also "apply to" fo:character.
  1298. // I don't know what that means, since
  1299. // if we change language in the middle of a "word",
  1300. // the effect would seem quite strange!
  1301. // Or perhaps in that case, we say that it's several words.
  1302. // We probably should bring the hyphenation props up from the actual
  1303. // TextLM which generate the hyphenation buffer,
  1304. // since these properties inherit and could be specified
  1305. // on an inline or wrapper below the block level.
  1306. Hyphenation hyph = Hyphenator.hyphenate(hyphenationProperties.language.getString(),
  1307. hyphenationProperties.country.getString(),
  1308. getFObj().getUserAgent().getHyphenationResourceResolver(),
  1309. getFObj().getUserAgent().getHyphenationPatternNames(),
  1310. sbChars.toString(),
  1311. hyphenationProperties.hyphenationRemainCharacterCount.getValue(),
  1312. hyphenationProperties.hyphenationPushCharacterCount.getValue(),
  1313. getFObj().getUserAgent());
  1314. // They hyph structure contains the information we need
  1315. // Now start from prev: reset to that position, ask that LM to get
  1316. // a Position for the first hyphenation offset. If the offset isn't in
  1317. // its characters, it returns null,
  1318. // but must tell how many chars it had.
  1319. // Keep looking at currentBP using next hyphenation point until the
  1320. // returned size is greater than the available size
  1321. // or no more hyphenation points remain. Choose the best break.
  1322. if (hyph != null) {
  1323. return new HyphContext(hyph.getHyphenationPoints());
  1324. } else {
  1325. return null;
  1326. }
  1327. }
  1328. @Override
  1329. public boolean hasLineAreaDescendant() {
  1330. return true;
  1331. }
  1332. @Override
  1333. public int getBaselineOffset() {
  1334. return baselineOffset;
  1335. }
  1336. /**
  1337. * Add the areas with the break points.
  1338. *
  1339. * @param parentIter the iterator of break positions
  1340. * @param context the context for adding areas
  1341. */
  1342. @Override
  1343. public void addAreas(PositionIterator parentIter,
  1344. LayoutContext context) {
  1345. while (parentIter.hasNext()) {
  1346. Position pos = parentIter.next();
  1347. boolean isLastPosition = !parentIter.hasNext();
  1348. if (pos instanceof LineBreakPosition) {
  1349. addInlineArea(context, (LineBreakPosition) pos, isLastPosition);
  1350. } else if ((pos instanceof NonLeafPosition) && pos.generatesAreas()) {
  1351. addBlockArea(context, pos, isLastPosition);
  1352. } else {
  1353. /*
  1354. * pos was the Position inside a penalty item, nothing to do;
  1355. * or Pos does not generate an area,
  1356. * i.e. it stand for spaces, borders and padding.
  1357. */
  1358. }
  1359. }
  1360. setCurrentArea(null); // ?? necessary
  1361. }
  1362. /**
  1363. * Add a line with inline content
  1364. * @param context the context for adding areas
  1365. * @param lbp the position for which the line is generated
  1366. * @param isLastPosition true if this is the last position of this LM
  1367. */
  1368. private void addInlineArea(LayoutContext context, LineBreakPosition lbp,
  1369. boolean isLastPosition) {
  1370. KnuthSequence seq = knuthParagraphs.get(lbp.parIndex);
  1371. int startElementIndex = lbp.startIndex;
  1372. int endElementIndex = lbp.getLeafPos();
  1373. LineArea lineArea = new LineArea(
  1374. (lbp.getLeafPos() < seq.size() - 1 ? textAlignment : textAlignmentLast),
  1375. lbp.difference, lbp.availableStretch, lbp.availableShrink);
  1376. lineArea.setChangeBarList(getChangeBarList());
  1377. if (lbp.startIndent != 0) {
  1378. lineArea.addTrait(Trait.START_INDENT, lbp.startIndent);
  1379. }
  1380. if (lbp.endIndent != 0) {
  1381. lineArea.addTrait(Trait.END_INDENT, lbp.endIndent);
  1382. }
  1383. lineArea.setBPD(lbp.lineHeight);
  1384. lineArea.setIPD(lbp.lineWidth);
  1385. lineArea.setBidiLevel(bidiLevel);
  1386. lineArea.addTrait(Trait.SPACE_BEFORE, lbp.spaceBefore);
  1387. lineArea.addTrait(Trait.SPACE_AFTER, lbp.spaceAfter);
  1388. alignmentContext.resizeLine(lbp.lineHeight, lbp.baseline);
  1389. if (seq instanceof Paragraph) {
  1390. Paragraph currPar = (Paragraph) seq;
  1391. // ignore the first elements added by the LineLayoutManager
  1392. startElementIndex += (startElementIndex == 0) ? currPar.ignoreAtStart : 0;
  1393. // if this is the last line area that for this paragraph,
  1394. // ignore the last elements added by the LineLayoutManager and
  1395. // subtract the last-line-end-indent from the area ipd
  1396. if (endElementIndex == (currPar.size() - 1)) {
  1397. endElementIndex -= currPar.ignoreAtEnd;
  1398. lineArea.setIPD(lineArea.getIPD() - lastLineEndIndent.getValue(this));
  1399. }
  1400. }
  1401. // ignore the last element in the line if it is a KnuthGlue object
  1402. ListIterator seqIterator = seq.listIterator(endElementIndex);
  1403. KnuthElement lastElement = (KnuthElement) seqIterator.next();
  1404. // the TLM which created the last KnuthElement in this line
  1405. LayoutManager lastLM = lastElement.getLayoutManager();
  1406. if (lastElement.isGlue()) {
  1407. // Remove trailing spaces if allowed so
  1408. if (whiteSpaceTreament == EN_IGNORE_IF_SURROUNDING_LINEFEED
  1409. || whiteSpaceTreament == EN_IGNORE
  1410. || whiteSpaceTreament == EN_IGNORE_IF_BEFORE_LINEFEED) {
  1411. endElementIndex--;
  1412. // this returns the same KnuthElement
  1413. seqIterator.previous();
  1414. if (seqIterator.hasPrevious()) {
  1415. lastLM = ((KnuthElement) seqIterator.previous()).getLayoutManager();
  1416. }
  1417. }
  1418. }
  1419. // Remove leading spaces if allowed so
  1420. if (whiteSpaceTreament == EN_IGNORE_IF_SURROUNDING_LINEFEED
  1421. || whiteSpaceTreament == EN_IGNORE
  1422. || whiteSpaceTreament == EN_IGNORE_IF_AFTER_LINEFEED) {
  1423. // ignore KnuthGlue and KnuthPenalty objects
  1424. // at the beginning of the line
  1425. seqIterator = seq.listIterator(startElementIndex);
  1426. while (seqIterator.hasNext() && !((KnuthElement) seqIterator.next()).isBox()) {
  1427. startElementIndex++;
  1428. }
  1429. }
  1430. // Add the inline areas to lineArea
  1431. PositionIterator inlinePosIter = new KnuthPossPosIter(seq, startElementIndex,
  1432. endElementIndex + 1);
  1433. LayoutContext lc = LayoutContext.offspringOf(context);
  1434. lc.setAlignmentContext(alignmentContext);
  1435. lc.setSpaceAdjust(lbp.dAdjust);
  1436. lc.setIPDAdjust(lbp.ipdAdjust);
  1437. lc.setLeadingSpace(new SpaceSpecifier(true));
  1438. lc.setTrailingSpace(new SpaceSpecifier(false));
  1439. lc.setFlags(LayoutContext.RESOLVE_LEADING_SPACE, true);
  1440. setCurrentArea(lineArea);
  1441. setChildContext(lc);
  1442. LayoutManager childLM;
  1443. while ((childLM = inlinePosIter.getNextChildLM()) != null) {
  1444. lc.setFlags(LayoutContext.LAST_AREA, (childLM == lastLM));
  1445. childLM.addAreas(inlinePosIter, lc);
  1446. lc.setLeadingSpace(lc.getTrailingSpace());
  1447. lc.setTrailingSpace(new SpaceSpecifier(false));
  1448. }
  1449. // if display-align is distribute, add space after
  1450. if (context.getSpaceAfter() > 0
  1451. && (!context.isLastArea() || !isLastPosition)) {
  1452. lineArea.setBPD(lineArea.getBPD() + context.getSpaceAfter());
  1453. }
  1454. lineArea.finish();
  1455. if (lineArea.getBidiLevel() >= 0) {
  1456. BidiResolver.reorder(lineArea);
  1457. }
  1458. parentLayoutManager.addChildArea(lineArea);
  1459. }
  1460. /**
  1461. * Add a line with block content
  1462. * @param context the context for adding areas
  1463. * @param pos the position for which the line is generated
  1464. * @param isLastPosition true if this is the last position of this LM
  1465. */
  1466. private void addBlockArea(LayoutContext context, Position pos, boolean isLastPosition) {
  1467. /* Nested block-level content;
  1468. * go down the LM stack again;
  1469. * "unwrap" the positions and put the child positions in a new list.
  1470. * The positionList must contain one area-generating position,
  1471. * which creates one line area.
  1472. */
  1473. List positionList = new ArrayList(1);
  1474. Position innerPosition = pos.getPosition();
  1475. positionList.add(innerPosition);
  1476. // do we have the last LM?
  1477. LayoutManager lastLM = null;
  1478. if (isLastPosition) {
  1479. lastLM = innerPosition.getLM();
  1480. }
  1481. LineArea lineArea = new LineArea();
  1482. lineArea.setChangeBarList(getChangeBarList());
  1483. setCurrentArea(lineArea);
  1484. LayoutContext lc = LayoutContext.newInstance();
  1485. lc.setAlignmentContext(alignmentContext);
  1486. setChildContext(lc);
  1487. PositionIterator childPosIter = new PositionIterator(positionList.listIterator());
  1488. LayoutContext blocklc = LayoutContext.offspringOf(context);
  1489. blocklc.setLeadingSpace(new SpaceSpecifier(true));
  1490. blocklc.setTrailingSpace(new SpaceSpecifier(false));
  1491. blocklc.setFlags(LayoutContext.RESOLVE_LEADING_SPACE, true);
  1492. LayoutManager childLM;
  1493. while ((childLM = childPosIter.getNextChildLM()) != null) {
  1494. // set last area flag
  1495. blocklc.setFlags(LayoutContext.LAST_AREA,
  1496. (context.isLastArea() && childLM == lastLM));
  1497. blocklc.setStackLimitBP(context.getStackLimitBP());
  1498. // Add the line areas to Area
  1499. childLM.addAreas(childPosIter, blocklc);
  1500. blocklc.setLeadingSpace(blocklc.getTrailingSpace());
  1501. blocklc.setTrailingSpace(new SpaceSpecifier(false));
  1502. }
  1503. lineArea.updateExtentsFromChildren();
  1504. if (lineArea.getBidiLevel() >= 0) {
  1505. BidiResolver.reorder(lineArea);
  1506. }
  1507. parentLayoutManager.addChildArea(lineArea);
  1508. }
  1509. /** {@inheritDoc} */
  1510. @Override
  1511. public void addChildArea(Area childArea) {
  1512. // Make sure childArea is inline area
  1513. if (childArea instanceof InlineArea) {
  1514. Area parent = getCurrentArea();
  1515. if (getContext().resolveLeadingSpace()) {
  1516. addSpace(parent, getContext().getLeadingSpace().resolve(false),
  1517. getContext().getSpaceAdjust());
  1518. }
  1519. parent.addChildArea(childArea);
  1520. }
  1521. }
  1522. // --------- Property Resolution related functions --------- //
  1523. /** {@inheritDoc} */
  1524. @Override
  1525. public boolean getGeneratesBlockArea() {
  1526. return true;
  1527. }
  1528. /** {@inheritDoc} */
  1529. @Override
  1530. public boolean getGeneratesLineArea() {
  1531. return true;
  1532. }
  1533. /** {@inheritDoc} */
  1534. @Override
  1535. public boolean isRestartable() {
  1536. return true;
  1537. }
  1538. /**
  1539. * Whether this LM can handle horizontal overflow error messages (only a BlockContainerLayoutManager can).
  1540. * @param milliPoints horizontal overflow
  1541. * @return true if handled by a BlockContainerLayoutManager
  1542. */
  1543. public boolean handleOverflow(int milliPoints) {
  1544. if (getParent() instanceof BlockLayoutManager) {
  1545. return ((BlockLayoutManager) getParent()).handleOverflow(milliPoints);
  1546. }
  1547. return false;
  1548. }
  1549. }