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.

AbstractBreaker.java 46KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083
  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;
  19. import java.util.Collections;
  20. import java.util.Iterator;
  21. import java.util.LinkedList;
  22. import java.util.List;
  23. import java.util.ListIterator;
  24. import org.apache.commons.logging.Log;
  25. import org.apache.commons.logging.LogFactory;
  26. import org.apache.fop.events.EventBroadcaster;
  27. import org.apache.fop.fo.Constants;
  28. import org.apache.fop.layoutmgr.BreakingAlgorithm.KnuthNode;
  29. import org.apache.fop.traits.MinOptMax;
  30. import org.apache.fop.util.ListUtil;
  31. /**
  32. * Abstract base class for breakers (page breakers, static region handlers etc.).
  33. */
  34. public abstract class AbstractBreaker {
  35. /** logging instance */
  36. protected static final Log log = LogFactory.getLog(AbstractBreaker.class);
  37. /**
  38. * A page break position.
  39. */
  40. public static class PageBreakPosition extends LeafPosition {
  41. // Percentage to adjust (stretch or shrink)
  42. double bpdAdjust; // CSOK: VisibilityModifier
  43. int difference; // CSOK: VisibilityModifier
  44. int footnoteFirstListIndex; // CSOK: VisibilityModifier
  45. int footnoteFirstElementIndex; // CSOK: VisibilityModifier
  46. int footnoteLastListIndex; // CSOK: VisibilityModifier
  47. int footnoteLastElementIndex; // CSOK: VisibilityModifier
  48. PageBreakPosition(LayoutManager lm, int breakIndex, // CSOK: ParameterNumber
  49. int ffli, int ffei, int flli, int flei,
  50. double bpdA, int diff) {
  51. super(lm, breakIndex);
  52. bpdAdjust = bpdA;
  53. difference = diff;
  54. footnoteFirstListIndex = ffli;
  55. footnoteFirstElementIndex = ffei;
  56. footnoteLastListIndex = flli;
  57. footnoteLastElementIndex = flei;
  58. }
  59. }
  60. /**
  61. * Helper method, mainly used to improve debug/trace output
  62. * @param breakClassId the {@link Constants} enum value.
  63. * @return the break class name
  64. */
  65. static String getBreakClassName(int breakClassId) {
  66. switch (breakClassId) {
  67. case Constants.EN_ALL: return "ALL";
  68. case Constants.EN_ANY: return "ANY";
  69. case Constants.EN_AUTO: return "AUTO";
  70. case Constants.EN_COLUMN: return "COLUMN";
  71. case Constants.EN_EVEN_PAGE: return "EVEN PAGE";
  72. case Constants.EN_LINE: return "LINE";
  73. case Constants.EN_NONE: return "NONE";
  74. case Constants.EN_ODD_PAGE: return "ODD PAGE";
  75. case Constants.EN_PAGE: return "PAGE";
  76. default: return "??? (" + String.valueOf(breakClassId) + ")";
  77. }
  78. }
  79. /**
  80. * Helper class, extending the functionality of the
  81. * basic {@link BlockKnuthSequence}.
  82. */
  83. public class BlockSequence extends BlockKnuthSequence {
  84. private static final long serialVersionUID = -5348831120146774118L;
  85. /** Number of elements to ignore at the beginning of the list. */
  86. int ignoreAtStart = 0; // CSOK: VisibilityModifier
  87. /** Number of elements to ignore at the end of the list. */
  88. int ignoreAtEnd = 0; // CSOK: VisibilityModifier
  89. /**
  90. * startOn represents where on the page/which page layout
  91. * should start for this BlockSequence. Acceptable values:
  92. * Constants.EN_ANY (can continue from finished location
  93. * of previous BlockSequence?), EN_COLUMN, EN_ODD_PAGE,
  94. * EN_EVEN_PAGE.
  95. */
  96. private int startOn;
  97. private int displayAlign;
  98. /**
  99. * Creates a new BlockSequence.
  100. * @param startOn the kind of page the sequence should start on.
  101. * One of {@link Constants#EN_ANY}, {@link Constants#EN_COLUMN},
  102. * {@link Constants#EN_ODD_PAGE}, or {@link Constants#EN_EVEN_PAGE}.
  103. * @param displayAlign the value for the display-align property
  104. */
  105. public BlockSequence(int startOn, int displayAlign) {
  106. super();
  107. this.startOn = startOn;
  108. this.displayAlign = displayAlign;
  109. }
  110. /**
  111. * @return the kind of page the sequence should start on.
  112. * One of {@link Constants#EN_ANY}, {@link Constants#EN_COLUMN},
  113. * {@link Constants#EN_ODD_PAGE}, or {@link Constants#EN_EVEN_PAGE}.
  114. */
  115. public int getStartOn() {
  116. return this.startOn;
  117. }
  118. /** @return the value for the display-align property */
  119. public int getDisplayAlign() {
  120. return this.displayAlign;
  121. }
  122. /**
  123. * Finalizes a Knuth sequence.
  124. * @return a finalized sequence.
  125. */
  126. public KnuthSequence endSequence() {
  127. return endSequence(null);
  128. }
  129. /**
  130. * Finalizes a Knuth sequence.
  131. * @param breakPosition a Position instance for the last penalty (may be null)
  132. * @return a finalized sequence.
  133. */
  134. public KnuthSequence endSequence(Position breakPosition) {
  135. // remove glue and penalty item at the end of the paragraph
  136. while (this.size() > ignoreAtStart
  137. && !((KnuthElement) ListUtil.getLast(this)).isBox()) {
  138. ListUtil.removeLast(this);
  139. }
  140. if (this.size() > ignoreAtStart) {
  141. // add the elements representing the space at the end of the last line
  142. // and the forced break
  143. if (getDisplayAlign() == Constants.EN_X_DISTRIBUTE && isSinglePartFavored()) {
  144. this.add(new KnuthPenalty(0, -KnuthElement.INFINITE,
  145. false, breakPosition, false));
  146. ignoreAtEnd = 1;
  147. } else {
  148. this.add(new KnuthPenalty(0, KnuthElement.INFINITE,
  149. false, null, false));
  150. this.add(new KnuthGlue(0, 10000000, 0, null, false));
  151. this.add(new KnuthPenalty(0, -KnuthElement.INFINITE,
  152. false, breakPosition, false));
  153. ignoreAtEnd = 3;
  154. }
  155. return this;
  156. } else {
  157. this.clear();
  158. return null;
  159. }
  160. }
  161. /**
  162. * Finalizes a this {@link BlockSequence}, adding a terminating
  163. * penalty-glue-penalty sequence
  164. * @param breakPosition a Position instance pointing to the last penalty
  165. * @return the finalized {@link BlockSequence}
  166. */
  167. public BlockSequence endBlockSequence(Position breakPosition) {
  168. KnuthSequence temp = endSequence(breakPosition);
  169. if (temp != null) {
  170. BlockSequence returnSequence = new BlockSequence(startOn, displayAlign);
  171. returnSequence.addAll(temp);
  172. returnSequence.ignoreAtEnd = this.ignoreAtEnd;
  173. return returnSequence;
  174. } else {
  175. return null;
  176. }
  177. }
  178. }
  179. // used by doLayout and getNextBlockList*
  180. private List<BlockSequence> blockLists;
  181. private boolean empty = true;
  182. /** desired text alignment */
  183. protected int alignment;
  184. private int alignmentLast;
  185. /** footnote separator length */
  186. protected MinOptMax footnoteSeparatorLength = MinOptMax.ZERO;
  187. /** @return current display alignment */
  188. protected abstract int getCurrentDisplayAlign();
  189. /** @return true if content not exhausted */
  190. protected abstract boolean hasMoreContent();
  191. /**
  192. * Tell the layout manager to add all the child areas implied
  193. * by Position objects which will be returned by the
  194. * Iterator.
  195. *
  196. * @param posIter the position iterator
  197. * @param context the context
  198. */
  199. protected abstract void addAreas(PositionIterator posIter, LayoutContext context);
  200. /** @return top level layout manager */
  201. protected abstract LayoutManager getTopLevelLM();
  202. /** @return current child layout manager */
  203. protected abstract LayoutManager getCurrentChildLM();
  204. /**
  205. * Controls the behaviour of the algorithm in cases where the first element of a part
  206. * overflows a line/page.
  207. * @return true if the algorithm should try to send the element to the next line/page.
  208. */
  209. protected boolean isPartOverflowRecoveryActivated() {
  210. return true;
  211. }
  212. /**
  213. * @return true if one a single part should be produced if possible (ex. for block-containers)
  214. */
  215. protected boolean isSinglePartFavored() {
  216. return false;
  217. }
  218. /**
  219. * Returns the PageProvider if any. PageBreaker overrides this method because each
  220. * page may have a different available BPD which needs to be accessible to the breaking
  221. * algorithm.
  222. * @return the applicable PageProvider, or null if not applicable
  223. */
  224. protected PageProvider getPageProvider() {
  225. return null;
  226. }
  227. /**
  228. * Creates and returns a PageBreakingLayoutListener for the PageBreakingAlgorithm to
  229. * notify about layout problems.
  230. * @return the listener instance or null if no notifications are needed
  231. */
  232. protected PageBreakingAlgorithm.PageBreakingLayoutListener createLayoutListener() {
  233. return null;
  234. }
  235. /**
  236. * Get a sequence of KnuthElements representing the content
  237. * of the node assigned to the LM
  238. *
  239. * @param context the LayoutContext used to store layout information
  240. * @param alignment the desired text alignment
  241. * @return the list of KnuthElements
  242. */
  243. protected abstract List<KnuthElement> getNextKnuthElements(LayoutContext context,
  244. int alignment);
  245. /**
  246. * Get a sequence of KnuthElements representing the content
  247. * of the node assigned to the LM
  248. *
  249. * @param context the LayoutContext used to store layout information
  250. * @param alignment the desired text alignment
  251. * @param positionAtIPDChange last element on the part before an IPD change
  252. * @param restartAtLM the layout manager from which to restart, if IPD
  253. * change occurs between two LMs
  254. * @return the list of KnuthElements
  255. */
  256. protected List<KnuthElement> getNextKnuthElements(LayoutContext context, int alignment,
  257. Position positionAtIPDChange, LayoutManager restartAtLM) {
  258. throw new UnsupportedOperationException("TODO: implement acceptable fallback");
  259. }
  260. /** @return true if there's no content that could be handled. */
  261. public boolean isEmpty() {
  262. return empty;
  263. }
  264. /**
  265. * Start part.
  266. * @param list a block sequence
  267. * @param breakClass a break class
  268. */
  269. protected void startPart(BlockSequence list, int breakClass) {
  270. //nop
  271. }
  272. /**
  273. * This method is called when no content is available for a part. Used to force empty pages.
  274. */
  275. protected void handleEmptyContent() {
  276. //nop
  277. }
  278. /**
  279. * Finish part.
  280. * @param alg a page breaking algorithm
  281. * @param pbp a page break posittion
  282. */
  283. protected abstract void finishPart(PageBreakingAlgorithm alg, PageBreakPosition pbp);
  284. /**
  285. * Creates the top-level LayoutContext for the breaker operation.
  286. * @return the top-level LayoutContext
  287. */
  288. protected LayoutContext createLayoutContext() {
  289. return new LayoutContext(0);
  290. }
  291. /**
  292. * Used to update the LayoutContext in subclasses prior to starting a new element list.
  293. * @param context the LayoutContext to update
  294. */
  295. protected void updateLayoutContext(LayoutContext context) {
  296. //nop
  297. }
  298. /**
  299. * Used for debugging purposes. Notifies all registered observers about the element list.
  300. * Override to set different parameters.
  301. * @param elementList the Knuth element list
  302. */
  303. protected void observeElementList(List elementList) {
  304. ElementListObserver.observe(elementList, "breaker", null);
  305. }
  306. /**
  307. * Starts the page breaking process.
  308. * @param flowBPD the constant available block-progression-dimension (used for every part)
  309. * @param autoHeight true if warnings about overflows should be disabled because the
  310. * the BPD is really undefined (for footnote-separators, for example)
  311. */
  312. public void doLayout(int flowBPD, boolean autoHeight) {
  313. LayoutContext childLC = createLayoutContext();
  314. childLC.setStackLimitBP(MinOptMax.getInstance(flowBPD));
  315. if (getCurrentDisplayAlign() == Constants.EN_X_FILL) {
  316. //EN_X_FILL is non-standard (by LF)
  317. alignment = Constants.EN_JUSTIFY;
  318. } else if (getCurrentDisplayAlign() == Constants.EN_X_DISTRIBUTE) {
  319. //EN_X_DISTRIBUTE is non-standard (by LF)
  320. alignment = Constants.EN_JUSTIFY;
  321. } else {
  322. alignment = Constants.EN_START;
  323. }
  324. alignmentLast = Constants.EN_START;
  325. if (isSinglePartFavored() && alignment == Constants.EN_JUSTIFY) {
  326. alignmentLast = Constants.EN_JUSTIFY;
  327. }
  328. childLC.setBPAlignment(alignment);
  329. BlockSequence blockList;
  330. blockLists = new java.util.ArrayList<BlockSequence>();
  331. log.debug("PLM> flow BPD =" + flowBPD);
  332. int nextSequenceStartsOn = Constants.EN_ANY;
  333. while (hasMoreContent()) {
  334. blockLists.clear();
  335. //*** Phase 1: Get Knuth elements ***
  336. nextSequenceStartsOn = getNextBlockList(childLC, nextSequenceStartsOn);
  337. empty = empty && blockLists.size() == 0;
  338. //*** Phases 2 and 3 ***
  339. log.debug("PLM> blockLists.size() = " + blockLists.size());
  340. for (int blockListIndex = 0; blockListIndex < blockLists.size(); blockListIndex++) {
  341. blockList = blockLists.get(blockListIndex);
  342. //debug code start
  343. if (log.isDebugEnabled()) {
  344. log.debug(" blockListIndex = " + blockListIndex);
  345. log.debug(" sequence starts on " + getBreakClassName(blockList.startOn));
  346. }
  347. observeElementList(blockList);
  348. //debug code end
  349. //*** Phase 2: Alignment and breaking ***
  350. log.debug("PLM> start of algorithm (" + this.getClass().getName()
  351. + "), flow BPD =" + flowBPD);
  352. PageBreakingAlgorithm alg = new PageBreakingAlgorithm(getTopLevelLM(),
  353. getPageProvider(), createLayoutListener(),
  354. alignment, alignmentLast, footnoteSeparatorLength,
  355. isPartOverflowRecoveryActivated(), autoHeight, isSinglePartFavored());
  356. BlockSequence effectiveList;
  357. if (getCurrentDisplayAlign() == Constants.EN_X_FILL) {
  358. /* justification */
  359. effectiveList = justifyBoxes(blockList, alg, flowBPD);
  360. } else {
  361. /* no justification */
  362. effectiveList = blockList;
  363. }
  364. alg.setConstantLineWidth(flowBPD);
  365. int optimalPageCount = alg.findBreakingPoints(effectiveList, 1, true,
  366. BreakingAlgorithm.ALL_BREAKS);
  367. if ( Math.abs ( alg.getIPDdifference() ) > 1 ) {
  368. addAreas(alg, optimalPageCount, blockList, effectiveList);
  369. // *** redo Phase 1 ***
  370. log.trace("IPD changes after page " + optimalPageCount);
  371. blockLists.clear();
  372. nextSequenceStartsOn = getNextBlockListChangedIPD(childLC, alg,
  373. effectiveList);
  374. blockListIndex = -1;
  375. } else {
  376. log.debug("PLM> optimalPageCount= " + optimalPageCount
  377. + " pageBreaks.size()= " + alg.getPageBreaks().size());
  378. //*** Phase 3: Add areas ***
  379. doPhase3(alg, optimalPageCount, blockList, effectiveList);
  380. }
  381. }
  382. }
  383. // done
  384. blockLists = null;
  385. }
  386. /**
  387. * Returns {@code true} if the given position or one of its descendants
  388. * corresponds to a non-restartable LM.
  389. *
  390. * @param position a position
  391. * @return {@code true} if there is a non-restartable LM in the hierarchy
  392. */
  393. private boolean containsNonRestartableLM(Position position) {
  394. LayoutManager lm = position.getLM();
  395. if (lm != null && !lm.isRestartable()) {
  396. return true;
  397. } else {
  398. Position subPosition = position.getPosition();
  399. return subPosition != null && containsNonRestartableLM(subPosition);
  400. }
  401. }
  402. /**
  403. * Phase 3 of Knuth algorithm: Adds the areas
  404. * @param alg PageBreakingAlgorithm instance which determined the breaks
  405. * @param partCount number of parts (pages) to be rendered
  406. * @param originalList original Knuth element list
  407. * @param effectiveList effective Knuth element list (after adjustments)
  408. */
  409. protected abstract void doPhase3(PageBreakingAlgorithm alg, int partCount,
  410. BlockSequence originalList, BlockSequence effectiveList);
  411. /**
  412. * Phase 3 of Knuth algorithm: Adds the areas
  413. * @param alg PageBreakingAlgorithm instance which determined the breaks
  414. * @param partCount number of parts (pages) to be rendered
  415. * @param originalList original Knuth element list
  416. * @param effectiveList effective Knuth element list (after adjustments)
  417. */
  418. protected void addAreas(PageBreakingAlgorithm alg, int partCount,
  419. BlockSequence originalList, BlockSequence effectiveList) {
  420. addAreas(alg, 0, partCount, originalList, effectiveList);
  421. }
  422. /**
  423. * Phase 3 of Knuth algorithm: Adds the areas
  424. * @param alg PageBreakingAlgorithm instance which determined the breaks
  425. * @param startPart index of the first part (page) to be rendered
  426. * @param partCount number of parts (pages) to be rendered
  427. * @param originalList original Knuth element list
  428. * @param effectiveList effective Knuth element list (after adjustments)
  429. */
  430. protected void addAreas(PageBreakingAlgorithm alg, int startPart, int partCount,
  431. BlockSequence originalList, BlockSequence effectiveList) {
  432. LayoutContext childLC;
  433. int startElementIndex = 0;
  434. int endElementIndex = 0;
  435. int lastBreak = -1;
  436. for (int p = startPart; p < startPart + partCount; p++) {
  437. PageBreakPosition pbp = alg.getPageBreaks().get(p);
  438. //Check the last break position for forced breaks
  439. int lastBreakClass;
  440. if (p == 0) {
  441. lastBreakClass = effectiveList.getStartOn();
  442. } else {
  443. ListElement lastBreakElement = effectiveList.getElement(endElementIndex);
  444. if (lastBreakElement.isPenalty()) {
  445. KnuthPenalty pen = (KnuthPenalty)lastBreakElement;
  446. lastBreakClass = pen.getBreakClass();
  447. } else {
  448. lastBreakClass = Constants.EN_COLUMN;
  449. }
  450. }
  451. //the end of the new part
  452. endElementIndex = pbp.getLeafPos();
  453. // ignore the first elements added by the
  454. // PageSequenceLayoutManager
  455. startElementIndex += (startElementIndex == 0)
  456. ? effectiveList.ignoreAtStart
  457. : 0;
  458. log.debug("PLM> part: " + (p + 1)
  459. + ", start at pos " + startElementIndex
  460. + ", break at pos " + endElementIndex
  461. + ", break class = " + getBreakClassName(lastBreakClass));
  462. startPart(effectiveList, lastBreakClass);
  463. int displayAlign = getCurrentDisplayAlign();
  464. //The following is needed by SpaceResolver.performConditionalsNotification()
  465. //further down as there may be important Position elements in the element list trailer
  466. int notificationEndElementIndex = endElementIndex;
  467. // ignore the last elements added by the
  468. // PageSequenceLayoutManager
  469. endElementIndex -= (endElementIndex == (originalList.size() - 1))
  470. ? effectiveList.ignoreAtEnd
  471. : 0;
  472. // ignore the last element in the page if it is a KnuthGlue
  473. // object
  474. if (((KnuthElement) effectiveList.get(endElementIndex))
  475. .isGlue()) {
  476. endElementIndex--;
  477. }
  478. // ignore KnuthGlue and KnuthPenalty objects
  479. // at the beginning of the line
  480. startElementIndex = alg.par.getFirstBoxIndex(startElementIndex);
  481. if (startElementIndex <= endElementIndex) {
  482. if (log.isDebugEnabled()) {
  483. log.debug(" addAreas from " + startElementIndex
  484. + " to " + endElementIndex);
  485. }
  486. childLC = new LayoutContext(0);
  487. // set the space adjustment ratio
  488. childLC.setSpaceAdjust(pbp.bpdAdjust);
  489. // add space before if display-align is center or bottom
  490. // add space after if display-align is distribute and
  491. // this is not the last page
  492. if (pbp.difference != 0 && displayAlign == Constants.EN_CENTER) {
  493. childLC.setSpaceBefore(pbp.difference / 2);
  494. } else if (pbp.difference != 0 && displayAlign == Constants.EN_AFTER) {
  495. childLC.setSpaceBefore(pbp.difference);
  496. } else if (pbp.difference != 0 && displayAlign == Constants.EN_X_DISTRIBUTE
  497. && p < (partCount - 1)) {
  498. // count the boxes whose width is not 0
  499. int boxCount = 0;
  500. @SuppressWarnings("unchecked")
  501. ListIterator<KnuthElement> effectiveListIterator = effectiveList
  502. .listIterator(startElementIndex);
  503. while (effectiveListIterator.nextIndex() <= endElementIndex) {
  504. KnuthElement tempEl = effectiveListIterator.next();
  505. if (tempEl.isBox() && tempEl.getWidth() > 0) {
  506. boxCount++;
  507. }
  508. }
  509. // split the difference
  510. if (boxCount >= 2) {
  511. childLC.setSpaceAfter(pbp.difference / (boxCount - 1));
  512. }
  513. }
  514. /* *** *** non-standard extension *** *** */
  515. if (displayAlign == Constants.EN_X_FILL) {
  516. int averageLineLength = optimizeLineLength(effectiveList,
  517. startElementIndex, endElementIndex);
  518. if (averageLineLength != 0) {
  519. childLC.setStackLimitBP(MinOptMax.getInstance(averageLineLength));
  520. }
  521. }
  522. /* *** *** non-standard extension *** *** */
  523. // Handle SpaceHandling(Break)Positions, see SpaceResolver!
  524. SpaceResolver.performConditionalsNotification(effectiveList,
  525. startElementIndex, notificationEndElementIndex, lastBreak);
  526. // Add areas now!
  527. addAreas(new KnuthPossPosIter(effectiveList,
  528. startElementIndex, endElementIndex + 1), childLC);
  529. } else {
  530. //no content for this part
  531. handleEmptyContent();
  532. }
  533. finishPart(alg, pbp);
  534. lastBreak = endElementIndex;
  535. startElementIndex = pbp.getLeafPos() + 1;
  536. }
  537. }
  538. /**
  539. * Notifies the layout managers about the space and conditional length situation based on
  540. * the break decisions.
  541. * @param effectiveList Element list to be painted
  542. * @param startElementIndex start index of the part
  543. * @param endElementIndex end index of the part
  544. * @param lastBreak index of the last break element
  545. */
  546. /**
  547. * Handles span changes reported through the <code>LayoutContext</code>.
  548. * Only used by the PSLM and called by <code>getNextBlockList()</code>.
  549. * @param childLC the LayoutContext
  550. * @param nextSequenceStartsOn previous value for break handling
  551. * @return effective value for break handling
  552. */
  553. protected int handleSpanChange(LayoutContext childLC, int nextSequenceStartsOn) {
  554. return nextSequenceStartsOn;
  555. }
  556. /**
  557. * Gets the next block list (sequence) and adds it to a list of block lists if it's not empty.
  558. * @param childLC LayoutContext to use
  559. * @param nextSequenceStartsOn indicates on what page the next sequence should start
  560. * @return the page on which the next content should appear after a hard break
  561. */
  562. protected int getNextBlockList(LayoutContext childLC, int nextSequenceStartsOn) {
  563. return getNextBlockList(childLC, nextSequenceStartsOn, null, null, null);
  564. }
  565. /**
  566. * Gets the next block list (sequence) and adds it to a list of block lists
  567. * if it's not empty.
  568. *
  569. * @param childLC LayoutContext to use
  570. * @param nextSequenceStartsOn indicates on what page the next sequence
  571. * should start
  572. * @param positionAtIPDChange last element on the part before an IPD change
  573. * @param restartAtLM the layout manager from which to restart, if IPD
  574. * change occurs between two LMs
  575. * @param firstElements elements from non-restartable LMs on the new page
  576. * @return the page on which the next content should appear after a hard
  577. * break
  578. */
  579. protected int getNextBlockList(LayoutContext childLC, int nextSequenceStartsOn,
  580. Position positionAtIPDChange, LayoutManager restartAtLM,
  581. List<KnuthElement> firstElements) {
  582. updateLayoutContext(childLC);
  583. //Make sure the span change signal is reset
  584. childLC.signalSpanChange(Constants.NOT_SET);
  585. BlockSequence blockList;
  586. List<KnuthElement> returnedList;
  587. if (firstElements == null) {
  588. returnedList = getNextKnuthElements(childLC, alignment);
  589. } else if (positionAtIPDChange == null) {
  590. /*
  591. * No restartable element found after changing IPD break. Simply add the
  592. * non-restartable elements found after the break.
  593. */
  594. returnedList = firstElements;
  595. /*
  596. * Remove the last 3 penalty-filler-forced break elements that were added by
  597. * the Knuth algorithm. They will be re-added later on.
  598. */
  599. ListIterator iter = returnedList.listIterator(returnedList.size());
  600. for (int i = 0; i < 3; i++) {
  601. iter.previous();
  602. iter.remove();
  603. }
  604. } else {
  605. returnedList = getNextKnuthElements(childLC, alignment, positionAtIPDChange,
  606. restartAtLM);
  607. returnedList.addAll(0, firstElements);
  608. }
  609. if (returnedList != null) {
  610. if (returnedList.isEmpty()) {
  611. nextSequenceStartsOn = handleSpanChange(childLC, nextSequenceStartsOn);
  612. return nextSequenceStartsOn;
  613. }
  614. blockList = new BlockSequence(nextSequenceStartsOn, getCurrentDisplayAlign());
  615. //Only implemented by the PSLM
  616. nextSequenceStartsOn = handleSpanChange(childLC, nextSequenceStartsOn);
  617. Position breakPosition = null;
  618. if (ElementListUtils.endsWithForcedBreak(returnedList)) {
  619. KnuthPenalty breakPenalty = (KnuthPenalty) ListUtil
  620. .removeLast(returnedList);
  621. breakPosition = breakPenalty.getPosition();
  622. log.debug("PLM> break - " + getBreakClassName(breakPenalty.getBreakClass()));
  623. switch (breakPenalty.getBreakClass()) {
  624. case Constants.EN_PAGE:
  625. nextSequenceStartsOn = Constants.EN_ANY;
  626. break;
  627. case Constants.EN_COLUMN:
  628. //TODO Fix this when implementing multi-column layout
  629. nextSequenceStartsOn = Constants.EN_COLUMN;
  630. break;
  631. case Constants.EN_ODD_PAGE:
  632. nextSequenceStartsOn = Constants.EN_ODD_PAGE;
  633. break;
  634. case Constants.EN_EVEN_PAGE:
  635. nextSequenceStartsOn = Constants.EN_EVEN_PAGE;
  636. break;
  637. default:
  638. throw new IllegalStateException("Invalid break class: "
  639. + breakPenalty.getBreakClass());
  640. }
  641. }
  642. blockList.addAll(returnedList);
  643. BlockSequence seq;
  644. seq = blockList.endBlockSequence(breakPosition);
  645. if (seq != null) {
  646. blockLists.add(seq);
  647. }
  648. }
  649. return nextSequenceStartsOn;
  650. }
  651. /**
  652. * @param childLC LayoutContext to use
  653. * @param alg the pagebreaking algorithm
  654. * @param effectiveList the list of Knuth elements to be reused
  655. * @return the page on which the next content should appear after a hard break
  656. */
  657. private int getNextBlockListChangedIPD(LayoutContext childLC, PageBreakingAlgorithm alg,
  658. BlockSequence effectiveList) {
  659. int nextSequenceStartsOn;
  660. KnuthNode optimalBreak = alg.getBestNodeBeforeIPDChange();
  661. int positionIndex = optimalBreak.position;
  662. log.trace("IPD changes at index " + positionIndex);
  663. KnuthElement elementAtBreak = alg.getElement(positionIndex);
  664. Position positionAtBreak = elementAtBreak.getPosition();
  665. if (!(positionAtBreak instanceof SpaceResolver.SpaceHandlingBreakPosition)) {
  666. throw new UnsupportedOperationException(
  667. "Don't know how to restart at position " + positionAtBreak);
  668. }
  669. /* Retrieve the original position wrapped into this space position */
  670. positionAtBreak = positionAtBreak.getPosition();
  671. LayoutManager restartAtLM = null;
  672. List<KnuthElement> firstElements = Collections.emptyList();
  673. if (containsNonRestartableLM(positionAtBreak)) {
  674. if (alg.getIPDdifference() > 0) {
  675. EventBroadcaster eventBroadcaster = getCurrentChildLM().getFObj()
  676. .getUserAgent().getEventBroadcaster();
  677. BlockLevelEventProducer eventProducer
  678. = BlockLevelEventProducer.Provider.get(eventBroadcaster);
  679. eventProducer.nonRestartableContentFlowingToNarrowerPage(this);
  680. }
  681. firstElements = new LinkedList<KnuthElement>();
  682. boolean boxFound = false;
  683. Iterator<KnuthElement> iter = effectiveList.listIterator(positionIndex + 1);
  684. Position position = null;
  685. while (iter.hasNext()
  686. && (position == null || containsNonRestartableLM(position))) {
  687. positionIndex++;
  688. KnuthElement element = iter.next();
  689. position = element.getPosition();
  690. if (element.isBox()) {
  691. boxFound = true;
  692. firstElements.add(element);
  693. } else if (boxFound) {
  694. firstElements.add(element);
  695. }
  696. }
  697. if (position instanceof SpaceResolver.SpaceHandlingBreakPosition) {
  698. /* Retrieve the original position wrapped into this space position */
  699. positionAtBreak = position.getPosition();
  700. } else {
  701. positionAtBreak = null;
  702. }
  703. }
  704. if (positionAtBreak != null && positionAtBreak.getIndex() == -1) {
  705. /*
  706. * This is an indication that we are between two blocks
  707. * (possibly surrounded by another block), not inside a
  708. * paragraph.
  709. */
  710. Position position;
  711. Iterator<KnuthElement> iter = effectiveList.listIterator(positionIndex + 1);
  712. do {
  713. KnuthElement nextElement = iter.next();
  714. position = nextElement.getPosition();
  715. } while (position == null
  716. || position instanceof SpaceResolver.SpaceHandlingPosition
  717. || position instanceof SpaceResolver.SpaceHandlingBreakPosition
  718. && position.getPosition().getIndex() == -1);
  719. LayoutManager surroundingLM = positionAtBreak.getLM();
  720. while (position.getLM() != surroundingLM) {
  721. position = position.getPosition();
  722. }
  723. restartAtLM = position.getPosition().getLM();
  724. }
  725. nextSequenceStartsOn = getNextBlockList(childLC, Constants.EN_COLUMN,
  726. positionAtBreak, restartAtLM, firstElements);
  727. return nextSequenceStartsOn;
  728. }
  729. /**
  730. * Returns the average width of all the lines in the given range.
  731. * @param effectiveList effective block list to work on
  732. * @param startElementIndex index of the element starting the range
  733. * @param endElementIndex index of the element ending the range
  734. * @return the average line length, 0 if there's no content
  735. */
  736. private int optimizeLineLength(KnuthSequence effectiveList, int startElementIndex,
  737. int endElementIndex) {
  738. ListIterator<KnuthElement> effectiveListIterator;
  739. // optimize line length
  740. int boxCount = 0;
  741. int accumulatedLineLength = 0;
  742. int greatestMinimumLength = 0;
  743. effectiveListIterator = effectiveList.listIterator(startElementIndex);
  744. while (effectiveListIterator.nextIndex() <= endElementIndex) {
  745. KnuthElement tempEl = effectiveListIterator
  746. .next();
  747. if (tempEl instanceof KnuthBlockBox) {
  748. KnuthBlockBox blockBox = (KnuthBlockBox) tempEl;
  749. if (blockBox.getBPD() > 0) {
  750. log.debug("PSLM> nominal length of line = " + blockBox.getBPD());
  751. log.debug(" range = "
  752. + blockBox.getIPDRange());
  753. boxCount++;
  754. accumulatedLineLength += ((KnuthBlockBox) tempEl)
  755. .getBPD();
  756. }
  757. if (blockBox.getIPDRange().getMin() > greatestMinimumLength) {
  758. greatestMinimumLength = blockBox
  759. .getIPDRange().getMin();
  760. }
  761. }
  762. }
  763. int averageLineLength = 0;
  764. if (accumulatedLineLength > 0 && boxCount > 0) {
  765. averageLineLength = (int) (accumulatedLineLength / boxCount);
  766. log.debug("Average line length = " + averageLineLength);
  767. if (averageLineLength < greatestMinimumLength) {
  768. averageLineLength = greatestMinimumLength;
  769. log.debug(" Correction to: " + averageLineLength);
  770. }
  771. }
  772. return averageLineLength;
  773. }
  774. /**
  775. * Justifies the boxes and returns them as a new KnuthSequence.
  776. * @param blockList block list to justify
  777. * @param alg reference to the algorithm instance
  778. * @param availableBPD the available BPD
  779. * @return the effective list
  780. */
  781. private BlockSequence justifyBoxes // CSOK: MethodLength
  782. (BlockSequence blockList, PageBreakingAlgorithm alg, int availableBPD) {
  783. int optimalPageCount;
  784. alg.setConstantLineWidth(availableBPD);
  785. optimalPageCount = alg.findBreakingPoints(blockList, /*availableBPD,*/
  786. 1, true, BreakingAlgorithm.ALL_BREAKS);
  787. log.debug("PLM> optimalPageCount= " + optimalPageCount);
  788. //
  789. ListIterator<KnuthElement> sequenceIterator = blockList.listIterator();
  790. ListIterator<PageBreakPosition> breakIterator = alg.getPageBreaks().listIterator();
  791. KnuthElement thisElement = null;
  792. PageBreakPosition thisBreak;
  793. int adjustedDiff; // difference already adjusted
  794. while (breakIterator.hasNext()) {
  795. thisBreak = breakIterator.next();
  796. if (log.isDebugEnabled()) {
  797. log.debug("| first page: break= "
  798. + thisBreak.getLeafPos() + " difference= "
  799. + thisBreak.difference + " ratio= "
  800. + thisBreak.bpdAdjust);
  801. }
  802. adjustedDiff = 0;
  803. // glue and penalty items at the beginning of the page must
  804. // be ignored:
  805. // the first element returned by sequenceIterator.next()
  806. // inside the
  807. // while loop must be a box
  808. KnuthElement firstElement;
  809. while (sequenceIterator.hasNext()) {
  810. firstElement = sequenceIterator.next();
  811. if ( !firstElement.isBox() ) {
  812. log.debug("PLM> ignoring glue or penalty element "
  813. + "at the beginning of the sequence");
  814. if (firstElement.isGlue()) {
  815. ((BlockLevelLayoutManager) firstElement
  816. .getLayoutManager())
  817. .discardSpace((KnuthGlue) firstElement);
  818. }
  819. } else {
  820. break;
  821. }
  822. }
  823. sequenceIterator.previous();
  824. // scan the sub-sequence representing a page,
  825. // collecting information about potential adjustments
  826. MinOptMax lineNumberMaxAdjustment = MinOptMax.ZERO;
  827. MinOptMax spaceMaxAdjustment = MinOptMax.ZERO;
  828. LinkedList<KnuthGlue> blockSpacesList = new LinkedList<KnuthGlue>();
  829. LinkedList<KnuthGlue> unconfirmedList = new LinkedList<KnuthGlue>();
  830. LinkedList<KnuthGlue> adjustableLinesList = new LinkedList<KnuthGlue>();
  831. boolean bBoxSeen = false;
  832. while (sequenceIterator.hasNext()
  833. && sequenceIterator.nextIndex() <= thisBreak.getLeafPos()) {
  834. thisElement = sequenceIterator.next();
  835. if (thisElement.isGlue()) {
  836. // glue elements are used to represent adjustable
  837. // lines
  838. // and adjustable spaces between blocks
  839. KnuthGlue thisGlue = (KnuthGlue) thisElement;
  840. Adjustment adjustment = thisGlue.getAdjustmentClass();
  841. if (adjustment.equals(Adjustment.SPACE_BEFORE_ADJUSTMENT)
  842. || adjustment.equals(Adjustment.SPACE_AFTER_ADJUSTMENT)) {
  843. // potential space adjustment
  844. // glue items before the first box or after the
  845. // last one
  846. // must be ignored
  847. unconfirmedList.add(thisGlue);
  848. } else if (adjustment.equals(Adjustment.LINE_NUMBER_ADJUSTMENT)) {
  849. // potential line number adjustment
  850. lineNumberMaxAdjustment
  851. = lineNumberMaxAdjustment.plusMax(thisElement.getStretch());
  852. lineNumberMaxAdjustment
  853. = lineNumberMaxAdjustment.minusMin(thisElement.getShrink());
  854. adjustableLinesList.add(thisGlue);
  855. } else if (adjustment.equals(Adjustment.LINE_HEIGHT_ADJUSTMENT)) {
  856. // potential line height adjustment
  857. }
  858. } else if (thisElement.isBox()) {
  859. if (!bBoxSeen) {
  860. // this is the first box met in this page
  861. bBoxSeen = true;
  862. } else {
  863. while (!unconfirmedList.isEmpty()) {
  864. // glue items in unconfirmedList were not after
  865. // the last box
  866. // in this page; they must be added to
  867. // blockSpaceList
  868. KnuthGlue blockSpace = unconfirmedList.removeFirst();
  869. spaceMaxAdjustment
  870. = spaceMaxAdjustment.plusMax(blockSpace.getStretch());
  871. spaceMaxAdjustment
  872. = spaceMaxAdjustment.minusMin(blockSpace.getShrink());
  873. blockSpacesList.add(blockSpace);
  874. }
  875. }
  876. }
  877. }
  878. log.debug("| line number adj= "
  879. + lineNumberMaxAdjustment);
  880. log.debug("| space adj = "
  881. + spaceMaxAdjustment);
  882. if (thisElement.isPenalty() && thisElement.getWidth() > 0) {
  883. log.debug(" mandatory variation to the number of lines!");
  884. ((BlockLevelLayoutManager) thisElement
  885. .getLayoutManager()).negotiateBPDAdjustment(
  886. thisElement.getWidth(), thisElement);
  887. }
  888. if (thisBreak.bpdAdjust != 0
  889. && (thisBreak.difference > 0 && thisBreak.difference <= spaceMaxAdjustment
  890. .getMax())
  891. || (thisBreak.difference < 0 && thisBreak.difference >= spaceMaxAdjustment
  892. .getMin())) {
  893. // modify only the spaces between blocks
  894. adjustedDiff += adjustBlockSpaces(
  895. blockSpacesList,
  896. thisBreak.difference,
  897. (thisBreak.difference > 0 ? spaceMaxAdjustment.getMax()
  898. : -spaceMaxAdjustment.getMin()));
  899. log.debug("single space: "
  900. + (adjustedDiff == thisBreak.difference
  901. || thisBreak.bpdAdjust == 0 ? "ok"
  902. : "ERROR"));
  903. } else if (thisBreak.bpdAdjust != 0) {
  904. adjustedDiff += adjustLineNumbers(
  905. adjustableLinesList,
  906. thisBreak.difference,
  907. (thisBreak.difference > 0 ? lineNumberMaxAdjustment.getMax()
  908. : -lineNumberMaxAdjustment.getMin()));
  909. adjustedDiff += adjustBlockSpaces(
  910. blockSpacesList,
  911. thisBreak.difference - adjustedDiff,
  912. ((thisBreak.difference - adjustedDiff) > 0 ? spaceMaxAdjustment.getMax()
  913. : -spaceMaxAdjustment.getMin()));
  914. log.debug("lines and space: "
  915. + (adjustedDiff == thisBreak.difference
  916. || thisBreak.bpdAdjust == 0 ? "ok"
  917. : "ERROR"));
  918. }
  919. }
  920. // create a new sequence: the new elements will contain the
  921. // Positions
  922. // which will be used in the addAreas() phase
  923. BlockSequence effectiveList = new BlockSequence(blockList.getStartOn(),
  924. blockList.getDisplayAlign());
  925. effectiveList.addAll(getCurrentChildLM().getChangedKnuthElements(
  926. blockList.subList(0, blockList.size() - blockList.ignoreAtEnd),
  927. /* 0, */0));
  928. //effectiveList.add(new KnuthPenalty(0, -KnuthElement.INFINITE,
  929. // false, new Position(this), false));
  930. effectiveList.endSequence();
  931. ElementListObserver.observe(effectiveList, "breaker-effective", null);
  932. alg.getPageBreaks().clear(); //Why this?
  933. return effectiveList;
  934. }
  935. private int adjustBlockSpaces(LinkedList<KnuthGlue> spaceList, int difference, int total) {
  936. if (log.isDebugEnabled()) {
  937. log.debug("AdjustBlockSpaces: difference " + difference + " / " + total
  938. + " on " + spaceList.size() + " spaces in block");
  939. }
  940. ListIterator<KnuthGlue> spaceListIterator = spaceList.listIterator();
  941. int adjustedDiff = 0;
  942. int partial = 0;
  943. while (spaceListIterator.hasNext()) {
  944. KnuthGlue blockSpace = spaceListIterator.next();
  945. partial += (difference > 0 ? blockSpace.getStretch() : blockSpace.getShrink());
  946. if (log.isDebugEnabled()) {
  947. log.debug("available = " + partial + " / " + total);
  948. log.debug("competenza = "
  949. + (((int)((float) partial * difference / total)) - adjustedDiff)
  950. + " / " + difference);
  951. }
  952. int newAdjust = ((BlockLevelLayoutManager) blockSpace.getLayoutManager())
  953. .negotiateBPDAdjustment
  954. (((int) ((float) partial * difference / total)) - adjustedDiff, blockSpace);
  955. adjustedDiff += newAdjust;
  956. }
  957. return adjustedDiff;
  958. }
  959. private int adjustLineNumbers(LinkedList<KnuthGlue> lineList, int difference, int total) {
  960. if (log.isDebugEnabled()) {
  961. log.debug("AdjustLineNumbers: difference "
  962. + difference
  963. + " / "
  964. + total
  965. + " on "
  966. + lineList.size()
  967. + " elements");
  968. }
  969. ListIterator<KnuthGlue> lineListIterator = lineList.listIterator();
  970. int adjustedDiff = 0;
  971. int partial = 0;
  972. while (lineListIterator.hasNext()) {
  973. KnuthGlue line = lineListIterator.next();
  974. partial += (difference > 0 ? line.getStretch() : line.getShrink());
  975. int newAdjust = ((BlockLevelLayoutManager) line.getLayoutManager())
  976. .negotiateBPDAdjustment
  977. (((int) ((float) partial * difference / total)) - adjustedDiff, line);
  978. adjustedDiff += newAdjust;
  979. }
  980. return adjustedDiff;
  981. }
  982. }