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 35KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866
  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. private LayoutManager originalRestartAtLM;
  38. private Position positionAtBreak;
  39. private List firstElementsForRestart;
  40. /**
  41. * A page break position.
  42. */
  43. public static class PageBreakPosition extends LeafPosition {
  44. // Percentage to adjust (stretch or shrink)
  45. double bpdAdjust;
  46. int difference;
  47. int footnoteFirstListIndex;
  48. int footnoteFirstElementIndex;
  49. int footnoteLastListIndex;
  50. int footnoteLastElementIndex;
  51. PageBreakPosition(LayoutManager lm, int breakIndex,
  52. int ffli, int ffei, int flli, int flei,
  53. double bpdA, int diff) {
  54. super(lm, breakIndex);
  55. bpdAdjust = bpdA;
  56. difference = diff;
  57. footnoteFirstListIndex = ffli;
  58. footnoteFirstElementIndex = ffei;
  59. footnoteLastListIndex = flli;
  60. footnoteLastElementIndex = flei;
  61. }
  62. }
  63. public static class FloatPosition extends LeafPosition {
  64. double bpdAdjust; // Percentage to adjust (stretch or shrink)
  65. int difference;
  66. FloatPosition(LayoutManager lm, int breakIndex, double bpdA, int diff) {
  67. super(lm, breakIndex);
  68. bpdAdjust = bpdA;
  69. difference = diff;
  70. }
  71. }
  72. /**
  73. * Helper method, mainly used to improve debug/trace output
  74. * @param breakClassId the {@link Constants} enum value.
  75. * @return the break class name
  76. */
  77. static String getBreakClassName(int breakClassId) {
  78. switch (breakClassId) {
  79. case Constants.EN_ALL: return "ALL";
  80. case Constants.EN_ANY: return "ANY";
  81. case Constants.EN_AUTO: return "AUTO";
  82. case Constants.EN_COLUMN: return "COLUMN";
  83. case Constants.EN_EVEN_PAGE: return "EVEN PAGE";
  84. case Constants.EN_LINE: return "LINE";
  85. case Constants.EN_NONE: return "NONE";
  86. case Constants.EN_ODD_PAGE: return "ODD PAGE";
  87. case Constants.EN_PAGE: return "PAGE";
  88. default: return "??? (" + String.valueOf(breakClassId) + ")";
  89. }
  90. }
  91. /**
  92. * Helper class, extending the functionality of the
  93. * basic {@link BlockKnuthSequence}.
  94. */
  95. public static class BlockSequence extends BlockKnuthSequence {
  96. private static final long serialVersionUID = -5348831120146774118L;
  97. /** Number of elements to ignore at the beginning of the list. */
  98. int ignoreAtStart;
  99. /** Number of elements to ignore at the end of the list. */
  100. int ignoreAtEnd;
  101. /**
  102. * startOn represents where on the page/which page layout
  103. * should start for this BlockSequence. Acceptable values:
  104. * Constants.EN_ANY (can continue from finished location
  105. * of previous BlockSequence?), EN_COLUMN, EN_ODD_PAGE,
  106. * EN_EVEN_PAGE.
  107. */
  108. private final int startOn;
  109. private final int displayAlign;
  110. /**
  111. * Creates a new BlockSequence.
  112. * @param startOn the kind of page the sequence should start on.
  113. * One of {@link Constants#EN_ANY}, {@link Constants#EN_COLUMN},
  114. * {@link Constants#EN_ODD_PAGE}, or {@link Constants#EN_EVEN_PAGE}.
  115. * @param displayAlign the value for the display-align property
  116. */
  117. public BlockSequence(int startOn, int displayAlign) {
  118. super();
  119. this.startOn = startOn;
  120. this.displayAlign = displayAlign;
  121. }
  122. /**
  123. * @return the kind of page the sequence should start on.
  124. * One of {@link Constants#EN_ANY}, {@link Constants#EN_COLUMN},
  125. * {@link Constants#EN_ODD_PAGE}, or {@link Constants#EN_EVEN_PAGE}.
  126. */
  127. public int getStartOn() {
  128. return this.startOn;
  129. }
  130. /** @return the value for the display-align property */
  131. public int getDisplayAlign() {
  132. return this.displayAlign;
  133. }
  134. /**
  135. * Finalizes a Knuth sequence.
  136. * @return a finalized sequence.
  137. */
  138. @Override
  139. public KnuthSequence endSequence() {
  140. return endSequence(null);
  141. }
  142. /**
  143. * Finalizes a Knuth sequence.
  144. * @param breakPosition a Position instance for the last penalty (may be null)
  145. * @return a finalized sequence.
  146. */
  147. public KnuthSequence endSequence(Position breakPosition) {
  148. // remove glue and penalty item at the end of the paragraph
  149. while (this.size() > ignoreAtStart
  150. && !((KnuthElement) ListUtil.getLast(this)).isBox()) {
  151. ListUtil.removeLast(this);
  152. }
  153. if (this.size() > ignoreAtStart) {
  154. // add the elements representing the space at the end of the last line
  155. // and the forced break
  156. this.add(new KnuthPenalty(0, KnuthElement.INFINITE,
  157. false, null, false));
  158. this.add(new KnuthGlue(0, 10000000, 0, null, false));
  159. this.add(new KnuthPenalty(0, -KnuthElement.INFINITE,
  160. false, breakPosition, false));
  161. ignoreAtEnd = 3;
  162. return this;
  163. } else {
  164. this.clear();
  165. return null;
  166. }
  167. }
  168. /**
  169. * Finalizes a this {@link BlockSequence}, adding a terminating
  170. * penalty-glue-penalty sequence
  171. * @param breakPosition a Position instance pointing to the last penalty
  172. * @return the finalized {@link BlockSequence}
  173. */
  174. public BlockSequence endBlockSequence(Position breakPosition) {
  175. KnuthSequence temp = endSequence(breakPosition);
  176. if (temp != null) {
  177. BlockSequence returnSequence = new BlockSequence(startOn, displayAlign);
  178. returnSequence.addAll(temp);
  179. returnSequence.ignoreAtEnd = this.ignoreAtEnd;
  180. return returnSequence;
  181. } else {
  182. return null;
  183. }
  184. }
  185. }
  186. // used by doLayout and getNextBlockList*
  187. protected List<BlockSequence> blockLists;
  188. private boolean empty = true;
  189. /** blockListIndex of the current BlockSequence in blockLists */
  190. protected int blockListIndex;
  191. /** desired text alignment */
  192. protected int alignment;
  193. private int alignmentLast;
  194. /** footnote separator length */
  195. protected MinOptMax footnoteSeparatorLength = MinOptMax.ZERO;
  196. /** @return current display alignment */
  197. protected abstract int getCurrentDisplayAlign();
  198. /** @return true if content not exhausted */
  199. protected abstract boolean hasMoreContent();
  200. /**
  201. * Tell the layout manager to add all the child areas implied
  202. * by Position objects which will be returned by the
  203. * Iterator.
  204. *
  205. * @param posIter the position iterator
  206. * @param context the context
  207. */
  208. protected abstract void addAreas(PositionIterator posIter, LayoutContext context);
  209. /** @return top level layout manager */
  210. protected abstract LayoutManager getTopLevelLM();
  211. /** @return current child layout manager */
  212. protected abstract LayoutManager getCurrentChildLM();
  213. /**
  214. * Controls the behaviour of the algorithm in cases where the first element of a part
  215. * overflows a line/page.
  216. * @return true if the algorithm should try to send the element to the next line/page.
  217. */
  218. protected boolean isPartOverflowRecoveryActivated() {
  219. return true;
  220. }
  221. /**
  222. * @return true if one a single part should be produced if possible (ex. for block-containers)
  223. */
  224. protected boolean isSinglePartFavored() {
  225. return false;
  226. }
  227. /**
  228. * Returns the PageProvider if any. PageBreaker overrides this method because each
  229. * page may have a different available BPD which needs to be accessible to the breaking
  230. * algorithm.
  231. * @return the applicable PageProvider, or null if not applicable
  232. */
  233. protected PageProvider getPageProvider() {
  234. return null;
  235. }
  236. /**
  237. * Creates and returns a PageBreakingLayoutListener for the PageBreakingAlgorithm to
  238. * notify about layout problems.
  239. * @return the listener instance or null if no notifications are needed
  240. */
  241. protected PageBreakingAlgorithm.PageBreakingLayoutListener createLayoutListener() {
  242. return null;
  243. }
  244. /**
  245. * Get a sequence of KnuthElements representing the content
  246. * of the node assigned to the LM
  247. *
  248. * @param context the LayoutContext used to store layout information
  249. * @param alignment the desired text alignment
  250. * @return the list of KnuthElements
  251. */
  252. protected abstract List<KnuthElement> getNextKnuthElements(LayoutContext context,
  253. int alignment);
  254. /**
  255. * Get a sequence of KnuthElements representing the content
  256. * of the node assigned to the LM
  257. *
  258. * @param context the LayoutContext used to store layout information
  259. * @param alignment the desired text alignment
  260. * @param positionAtIPDChange last element on the part before an IPD change
  261. * @param restartAtLM the layout manager from which to restart, if IPD
  262. * change occurs between two LMs
  263. * @return the list of KnuthElements
  264. */
  265. protected List<KnuthElement> getNextKnuthElements(LayoutContext context, int alignment,
  266. Position positionAtIPDChange, LayoutManager restartAtLM) {
  267. throw new UnsupportedOperationException("TODO: implement acceptable fallback");
  268. }
  269. /** @return true if there's no content that could be handled. */
  270. public boolean isEmpty() {
  271. return empty;
  272. }
  273. /**
  274. * Start part.
  275. * @param list a block sequence
  276. * @param breakClass a break class
  277. */
  278. protected void startPart(BlockSequence list, int breakClass) {
  279. //nop
  280. }
  281. /**
  282. * This method is called when no content is available for a part. Used to force empty pages.
  283. */
  284. protected void handleEmptyContent() {
  285. //nop
  286. }
  287. /**
  288. * Finish part.
  289. * @param alg a page breaking algorithm
  290. * @param pbp a page break posittion
  291. */
  292. protected abstract void finishPart(PageBreakingAlgorithm alg, PageBreakPosition pbp);
  293. /**
  294. * Creates the top-level LayoutContext for the breaker operation.
  295. * @return the top-level LayoutContext
  296. */
  297. protected LayoutContext createLayoutContext() {
  298. return LayoutContext.newInstance();
  299. }
  300. /**
  301. * Used to update the LayoutContext in subclasses prior to starting a new element list.
  302. * @param context the LayoutContext to update
  303. */
  304. protected void updateLayoutContext(LayoutContext context) {
  305. //nop
  306. }
  307. /**
  308. * Used for debugging purposes. Notifies all registered observers about the element list.
  309. * Override to set different parameters.
  310. * @param elementList the Knuth element list
  311. */
  312. protected void observeElementList(List elementList) {
  313. ElementListObserver.observe(elementList, "breaker", null);
  314. }
  315. /**
  316. * Starts the page breaking process.
  317. * @param flowBPD the constant available block-progression-dimension (used for every part)
  318. * @param autoHeight true if warnings about overflows should be disabled because the
  319. * the BPD is really undefined (for footnote-separators, for example)
  320. */
  321. public void doLayout(int flowBPD, boolean autoHeight) {
  322. LayoutContext childLC = createLayoutContext();
  323. childLC.setStackLimitBP(MinOptMax.getInstance(flowBPD));
  324. alignment = Constants.EN_START;
  325. alignmentLast = Constants.EN_START;
  326. childLC.setBPAlignment(alignment);
  327. BlockSequence blockList;
  328. blockLists = new java.util.ArrayList<BlockSequence>();
  329. log.debug("PLM> flow BPD =" + flowBPD);
  330. int nextSequenceStartsOn = Constants.EN_ANY;
  331. while (hasMoreContent()) {
  332. blockLists.clear();
  333. //*** Phase 1: Get Knuth elements ***
  334. nextSequenceStartsOn = getNextBlockList(childLC, nextSequenceStartsOn);
  335. empty = empty && blockLists.size() == 0;
  336. //*** Phases 2 and 3 ***
  337. log.debug("PLM> blockLists.size() = " + blockLists.size());
  338. for (blockListIndex = 0; blockListIndex < blockLists.size(); blockListIndex++) {
  339. blockList = blockLists.get(blockListIndex);
  340. //debug code start
  341. if (log.isDebugEnabled()) {
  342. log.debug(" blockListIndex = " + blockListIndex);
  343. log.debug(" sequence starts on " + getBreakClassName(blockList.startOn));
  344. }
  345. observeElementList(blockList);
  346. //debug code end
  347. //*** Phase 2: Alignment and breaking ***
  348. log.debug("PLM> start of algorithm (" + this.getClass().getName()
  349. + "), flow BPD =" + flowBPD);
  350. PageBreakingAlgorithm alg = new PageBreakingAlgorithm(getTopLevelLM(),
  351. getPageProvider(), createLayoutListener(),
  352. alignment, alignmentLast, footnoteSeparatorLength,
  353. isPartOverflowRecoveryActivated(), autoHeight, isSinglePartFavored());
  354. alg.setConstantLineWidth(flowBPD);
  355. int optimalPageCount = alg.findBreakingPoints(blockList, 1, true,
  356. BreakingAlgorithm.ALL_BREAKS);
  357. boolean ipdChangesOnNextPage = (alg.getIPDdifference() != 0);
  358. boolean onLastPageAndIPDChanges = false;
  359. if (!ipdChangesOnNextPage) {
  360. onLastPageAndIPDChanges = (lastPageHasIPDChange() && !thereIsANonRestartableLM(alg)
  361. && (shouldRedoLayout() || (wasLayoutRedone() && optimalPageCount > 1)));
  362. }
  363. if (alg.handlingFloat()) {
  364. nextSequenceStartsOn = handleFloatLayout(alg, optimalPageCount, blockList, childLC);
  365. } else if (ipdChangesOnNextPage || onLastPageAndIPDChanges) {
  366. boolean visitedBefore = false;
  367. if (onLastPageAndIPDChanges) {
  368. visitedBefore = wasLayoutRedone();
  369. prepareToRedoLayout(alg, optimalPageCount, blockList, blockList);
  370. }
  371. firstElementsForRestart = null;
  372. LayoutManager restartAtLM = getRestartAtLM(alg, ipdChangesOnNextPage, onLastPageAndIPDChanges,
  373. visitedBefore, blockList, 1);
  374. if (restartAtLM == null) {
  375. firstElementsForRestart = null;
  376. restartAtLM = getRestartAtLM(alg, ipdChangesOnNextPage, onLastPageAndIPDChanges,
  377. visitedBefore, blockList, 0);
  378. }
  379. if (ipdChangesOnNextPage) {
  380. addAreas(alg, optimalPageCount, blockList, blockList);
  381. }
  382. blockLists.clear();
  383. blockListIndex = -1;
  384. nextSequenceStartsOn = getNextBlockList(childLC, Constants.EN_COLUMN, positionAtBreak,
  385. restartAtLM, firstElementsForRestart);
  386. } else {
  387. log.debug("PLM> optimalPageCount= " + optimalPageCount
  388. + " pageBreaks.size()= " + alg.getPageBreaks().size());
  389. //*** Phase 3: Add areas ***
  390. doPhase3(alg, optimalPageCount, blockList, blockList);
  391. }
  392. }
  393. }
  394. // done
  395. blockLists = null;
  396. }
  397. private LayoutManager getRestartAtLM(PageBreakingAlgorithm alg, boolean ipdChangesOnNextPage,
  398. boolean onLastPageAndIPDChanges, boolean visitedBefore,
  399. BlockSequence blockList, int start) {
  400. KnuthNode optimalBreak = ipdChangesOnNextPage ? alg.getBestNodeBeforeIPDChange() : alg
  401. .getBestNodeForLastPage();
  402. if (onLastPageAndIPDChanges && visitedBefore && this.originalRestartAtLM == null) {
  403. optimalBreak = null;
  404. }
  405. int positionIndex = (optimalBreak != null) ? optimalBreak.position : start;
  406. KnuthElement elementAtBreak = alg.getElement(positionIndex);
  407. if (elementAtBreak.getPosition() == null) {
  408. elementAtBreak = alg.getElement(0);
  409. }
  410. positionAtBreak = elementAtBreak.getPosition();
  411. /* Retrieve the original position wrapped into this space position */
  412. positionAtBreak = positionAtBreak.getPosition();
  413. if (ipdChangesOnNextPage || (positionAtBreak != null && positionAtBreak.getIndex() > -1)) {
  414. firstElementsForRestart = Collections.EMPTY_LIST;
  415. if (ipdChangesOnNextPage) {
  416. if (containsNonRestartableLM(positionAtBreak)) {
  417. if (alg.getIPDdifference() > 0) {
  418. EventBroadcaster eventBroadcaster = getCurrentChildLM().getFObj()
  419. .getUserAgent().getEventBroadcaster();
  420. BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider
  421. .get(eventBroadcaster);
  422. eventProducer.nonRestartableContentFlowingToNarrowerPage(this);
  423. }
  424. firstElementsForRestart = new LinkedList();
  425. boolean boxFound = false;
  426. Iterator iter = blockList.listIterator(positionIndex + 1);
  427. Position position = null;
  428. while (iter.hasNext()
  429. && (position == null || containsNonRestartableLM(position))) {
  430. positionIndex++;
  431. KnuthElement element = (KnuthElement) iter.next();
  432. position = element.getPosition();
  433. if (element.isBox()) {
  434. boxFound = true;
  435. firstElementsForRestart.add(element);
  436. } else if (boxFound) {
  437. firstElementsForRestart.add(element);
  438. }
  439. }
  440. if (position instanceof SpaceResolver.SpaceHandlingBreakPosition) {
  441. /* Retrieve the original position wrapped into this space position */
  442. positionAtBreak = position.getPosition();
  443. } else {
  444. positionAtBreak = null;
  445. }
  446. }
  447. }
  448. }
  449. LayoutManager restartAtLM = null;
  450. if (ipdChangesOnNextPage || !(positionAtBreak != null && positionAtBreak.getIndex() > -1)) {
  451. if (positionAtBreak != null && positionAtBreak.getIndex() == -1) {
  452. Position position;
  453. Iterator iter = blockList.listIterator(positionIndex + 1);
  454. do {
  455. KnuthElement nextElement = (KnuthElement) iter.next();
  456. position = nextElement.getPosition();
  457. } while (position == null
  458. || position instanceof SpaceResolver.SpaceHandlingPosition
  459. || position instanceof SpaceResolver.SpaceHandlingBreakPosition
  460. && position.getPosition().getIndex() == -1);
  461. LayoutManager surroundingLM = positionAtBreak.getLM();
  462. while (position.getLM() != surroundingLM) {
  463. position = position.getPosition();
  464. }
  465. restartAtLM = position.getPosition().getLM();
  466. }
  467. if (onLastPageAndIPDChanges && restartAtLM != null) {
  468. if (originalRestartAtLM == null) {
  469. originalRestartAtLM = restartAtLM;
  470. } else {
  471. restartAtLM = originalRestartAtLM;
  472. }
  473. firstElementsForRestart = Collections.EMPTY_LIST;
  474. }
  475. }
  476. if (onLastPageAndIPDChanges && !visitedBefore && positionAtBreak.getPosition() != null) {
  477. restartAtLM = positionAtBreak.getPosition().getLM();
  478. }
  479. return restartAtLM;
  480. }
  481. /**
  482. * Returns {@code true} if the given position or one of its descendants
  483. * corresponds to a non-restartable LM.
  484. *
  485. * @param position a position
  486. * @return {@code true} if there is a non-restartable LM in the hierarchy
  487. */
  488. private boolean containsNonRestartableLM(Position position) {
  489. LayoutManager lm = position.getLM();
  490. if (lm != null && !lm.isRestartable()) {
  491. return true;
  492. } else {
  493. Position subPosition = position.getPosition();
  494. return subPosition != null && containsNonRestartableLM(subPosition);
  495. }
  496. }
  497. /**
  498. * Phase 3 of Knuth algorithm: Adds the areas
  499. * @param alg PageBreakingAlgorithm instance which determined the breaks
  500. * @param partCount number of parts (pages) to be rendered
  501. * @param originalList original Knuth element list
  502. * @param effectiveList effective Knuth element list (after adjustments)
  503. */
  504. protected abstract void doPhase3(PageBreakingAlgorithm alg, int partCount,
  505. BlockSequence originalList, BlockSequence effectiveList);
  506. /**
  507. * Phase 3 of Knuth algorithm: Adds the areas
  508. * @param alg PageBreakingAlgorithm instance which determined the breaks
  509. * @param partCount number of parts (pages) to be rendered
  510. * @param originalList original Knuth element list
  511. * @param effectiveList effective Knuth element list (after adjustments)
  512. */
  513. protected void addAreas(PageBreakingAlgorithm alg, int partCount,
  514. BlockSequence originalList, BlockSequence effectiveList) {
  515. addAreas(alg, 0, partCount, originalList, effectiveList);
  516. }
  517. protected void addAreas(PageBreakingAlgorithm alg, int startPart, int partCount,
  518. BlockSequence originalList, BlockSequence effectiveList) {
  519. addAreas(alg, startPart, partCount, originalList, effectiveList, LayoutContext.newInstance());
  520. }
  521. /**
  522. * Phase 3 of Knuth algorithm: Adds the areas
  523. * @param alg PageBreakingAlgorithm instance which determined the breaks
  524. * @param startPart index of the first part (page) to be rendered
  525. * @param partCount number of parts (pages) to be rendered
  526. * @param originalList original Knuth element list
  527. * @param effectiveList effective Knuth element list (after adjustments)
  528. */
  529. protected void addAreas(PageBreakingAlgorithm alg, int startPart, int partCount,
  530. BlockSequence originalList, BlockSequence effectiveList, final LayoutContext childLC) {
  531. int startElementIndex = 0;
  532. int endElementIndex = 0;
  533. int lastBreak = -1;
  534. for (int p = startPart; p < startPart + partCount; p++) {
  535. PageBreakPosition pbp = alg.getPageBreaks().get(p);
  536. // Check the last break position for forced breaks
  537. int lastBreakClass;
  538. if (p == 0) {
  539. lastBreakClass = effectiveList.getStartOn();
  540. } else {
  541. ListElement lastBreakElement = effectiveList.getElement(endElementIndex);
  542. if (lastBreakElement.isPenalty()) {
  543. KnuthPenalty pen = (KnuthPenalty) lastBreakElement;
  544. if (pen.getPenalty() == KnuthPenalty.INFINITE) {
  545. /**
  546. * That means that there was a keep.within-page="always", but that
  547. * it's OK to break at a column. TODO The break class is being
  548. * abused to implement keep.within-column and keep.within-page.
  549. * This is very misleading and must be revised.
  550. */
  551. lastBreakClass = Constants.EN_COLUMN;
  552. } else {
  553. lastBreakClass = pen.getBreakClass();
  554. }
  555. } else {
  556. lastBreakClass = Constants.EN_COLUMN;
  557. }
  558. }
  559. // the end of the new part
  560. endElementIndex = pbp.getLeafPos();
  561. // ignore the first elements added by the
  562. // PageSequenceLayoutManager
  563. startElementIndex += (startElementIndex == 0) ? effectiveList.ignoreAtStart : 0;
  564. log.debug("PLM> part: " + (p + 1)
  565. + ", start at pos " + startElementIndex
  566. + ", break at pos " + endElementIndex
  567. + ", break class = " + getBreakClassName(lastBreakClass));
  568. startPart(effectiveList, lastBreakClass);
  569. int displayAlign = getCurrentDisplayAlign();
  570. // The following is needed by SpaceResolver.performConditionalsNotification()
  571. // further down as there may be important Position elements in the element list trailer
  572. int notificationEndElementIndex = endElementIndex;
  573. // ignore the last elements added by the
  574. // PageSequenceLayoutManager
  575. endElementIndex -= (endElementIndex == (originalList.size() - 1)) ? effectiveList.ignoreAtEnd : 0;
  576. // ignore the last element in the page if it is a KnuthGlue
  577. // object
  578. if (((KnuthElement) effectiveList.get(endElementIndex)).isGlue()) {
  579. endElementIndex--;
  580. }
  581. // ignore KnuthGlue and KnuthPenalty objects
  582. // at the beginning of the line
  583. startElementIndex = alg.par.getFirstBoxIndex(startElementIndex);
  584. if (startElementIndex <= endElementIndex) {
  585. if (log.isDebugEnabled()) {
  586. log.debug(" addAreas from " + startElementIndex
  587. + " to " + endElementIndex);
  588. }
  589. // set the space adjustment ratio
  590. childLC.setSpaceAdjust(pbp.bpdAdjust);
  591. // add space before if display-align is center or bottom
  592. // add space after if display-align is distribute and
  593. // this is not the last page
  594. if (pbp.difference != 0 && displayAlign == Constants.EN_CENTER) {
  595. childLC.setSpaceBefore(pbp.difference / 2);
  596. } else if (pbp.difference != 0 && displayAlign == Constants.EN_AFTER) {
  597. childLC.setSpaceBefore(pbp.difference);
  598. }
  599. // Handle SpaceHandling(Break)Positions, see SpaceResolver!
  600. SpaceResolver.performConditionalsNotification(effectiveList, startElementIndex,
  601. notificationEndElementIndex, lastBreak);
  602. // Add areas now!
  603. addAreas(new KnuthPossPosIter(effectiveList, startElementIndex, endElementIndex + 1), childLC);
  604. } else {
  605. // no content for this part
  606. handleEmptyContent();
  607. }
  608. finishPart(alg, pbp);
  609. lastBreak = endElementIndex;
  610. startElementIndex = pbp.getLeafPos() + 1;
  611. }
  612. if (alg.handlingFloat()) {
  613. addAreasForFloats(alg, startPart, partCount, originalList, effectiveList, childLC, lastBreak,
  614. startElementIndex, endElementIndex);
  615. }
  616. }
  617. /**
  618. * Notifies the layout managers about the space and conditional length situation based on
  619. * the break decisions.
  620. * @param effectiveList Element list to be painted
  621. * @param startElementIndex start index of the part
  622. * @param endElementIndex end index of the part
  623. * @param lastBreak index of the last break element
  624. */
  625. /**
  626. * Handles span changes reported through the <code>LayoutContext</code>.
  627. * Only used by the PSLM and called by <code>getNextBlockList()</code>.
  628. * @param childLC the LayoutContext
  629. * @param nextSequenceStartsOn previous value for break handling
  630. * @return effective value for break handling
  631. */
  632. protected int handleSpanChange(LayoutContext childLC, int nextSequenceStartsOn) {
  633. return nextSequenceStartsOn;
  634. }
  635. /**
  636. * Gets the next block list (sequence) and adds it to a list of block lists if it's not empty.
  637. * @param childLC LayoutContext to use
  638. * @param nextSequenceStartsOn indicates on what page the next sequence should start
  639. * @return the page on which the next content should appear after a hard break
  640. */
  641. protected int getNextBlockList(LayoutContext childLC, int nextSequenceStartsOn) {
  642. return getNextBlockList(childLC, nextSequenceStartsOn, null, null, null);
  643. }
  644. /**
  645. * Gets the next block list (sequence) and adds it to a list of block lists
  646. * if it's not empty.
  647. *
  648. * @param childLC LayoutContext to use
  649. * @param nextSequenceStartsOn indicates on what page the next sequence
  650. * should start
  651. * @param positionAtIPDChange last element on the part before an IPD change
  652. * @param restartAtLM the layout manager from which to restart, if IPD
  653. * change occurs between two LMs
  654. * @param firstElements elements from non-restartable LMs on the new page
  655. * @return the page on which the next content should appear after a hard
  656. * break
  657. */
  658. protected int getNextBlockList(LayoutContext childLC, int nextSequenceStartsOn,
  659. Position positionAtIPDChange, LayoutManager restartAtLM,
  660. List<KnuthElement> firstElements) {
  661. updateLayoutContext(childLC);
  662. //Make sure the span change signal is reset
  663. childLC.signalSpanChange(Constants.NOT_SET);
  664. BlockSequence blockList;
  665. List<KnuthElement> returnedList;
  666. if (firstElements == null) {
  667. returnedList = getNextKnuthElements(childLC, alignment);
  668. } else if (positionAtIPDChange == null) {
  669. /*
  670. * No restartable element found after changing IPD break. Simply add the
  671. * non-restartable elements found after the break.
  672. */
  673. returnedList = firstElements;
  674. /*
  675. * Remove the last 3 penalty-filler-forced break elements that were added by
  676. * the Knuth algorithm. They will be re-added later on.
  677. */
  678. ListIterator iter = returnedList.listIterator(returnedList.size());
  679. for (int i = 0; i < 3; i++) {
  680. iter.previous();
  681. iter.remove();
  682. }
  683. } else {
  684. returnedList = getNextKnuthElements(childLC, alignment, positionAtIPDChange,
  685. restartAtLM);
  686. returnedList.addAll(0, firstElements);
  687. }
  688. if (returnedList != null) {
  689. if (returnedList.isEmpty()) {
  690. nextSequenceStartsOn = handleSpanChange(childLC, nextSequenceStartsOn);
  691. return nextSequenceStartsOn;
  692. }
  693. blockList = new BlockSequence(nextSequenceStartsOn, getCurrentDisplayAlign());
  694. //Only implemented by the PSLM
  695. nextSequenceStartsOn = handleSpanChange(childLC, nextSequenceStartsOn);
  696. Position breakPosition = null;
  697. if (ElementListUtils.endsWithForcedBreak(returnedList)) {
  698. KnuthPenalty breakPenalty = (KnuthPenalty) ListUtil
  699. .removeLast(returnedList);
  700. breakPosition = breakPenalty.getPosition();
  701. log.debug("PLM> break - " + getBreakClassName(breakPenalty.getBreakClass()));
  702. switch (breakPenalty.getBreakClass()) {
  703. case Constants.EN_PAGE:
  704. nextSequenceStartsOn = Constants.EN_ANY;
  705. break;
  706. case Constants.EN_COLUMN:
  707. //TODO Fix this when implementing multi-column layout
  708. nextSequenceStartsOn = Constants.EN_COLUMN;
  709. break;
  710. case Constants.EN_ODD_PAGE:
  711. nextSequenceStartsOn = Constants.EN_ODD_PAGE;
  712. break;
  713. case Constants.EN_EVEN_PAGE:
  714. nextSequenceStartsOn = Constants.EN_EVEN_PAGE;
  715. break;
  716. default:
  717. throw new IllegalStateException("Invalid break class: "
  718. + breakPenalty.getBreakClass());
  719. }
  720. if (ElementListUtils.isEmptyBox(returnedList)) {
  721. ListUtil.removeLast(returnedList);
  722. }
  723. }
  724. blockList.addAll(returnedList);
  725. BlockSequence seq;
  726. seq = blockList.endBlockSequence(breakPosition);
  727. if (seq != null) {
  728. blockLists.add(seq);
  729. }
  730. }
  731. return nextSequenceStartsOn;
  732. }
  733. protected boolean shouldRedoLayout() {
  734. return false;
  735. }
  736. protected void prepareToRedoLayout(PageBreakingAlgorithm alg, int partCount,
  737. BlockSequence originalList, BlockSequence effectiveList) {
  738. return;
  739. }
  740. protected boolean wasLayoutRedone() {
  741. return false;
  742. }
  743. private boolean thereIsANonRestartableLM(PageBreakingAlgorithm alg) {
  744. KnuthNode optimalBreak = alg.getBestNodeForLastPage();
  745. if (optimalBreak != null) {
  746. int positionIndex = optimalBreak.position;
  747. KnuthElement elementAtBreak = alg.getElement(positionIndex);
  748. Position positionAtBreak = elementAtBreak.getPosition();
  749. if (!(positionAtBreak instanceof SpaceResolver.SpaceHandlingBreakPosition)) {
  750. return false;
  751. }
  752. /* Retrieve the original position wrapped into this space position */
  753. positionAtBreak = positionAtBreak.getPosition();
  754. if (positionAtBreak != null && containsNonRestartableLM(positionAtBreak)) {
  755. return true;
  756. }
  757. }
  758. return false;
  759. }
  760. protected boolean lastPageHasIPDChange() {
  761. return false;
  762. }
  763. protected int handleFloatLayout(PageBreakingAlgorithm alg, int optimalPageCount, BlockSequence blockList,
  764. LayoutContext childLC) {
  765. throw new IllegalStateException();
  766. }
  767. protected void addAreasForFloats(PageBreakingAlgorithm alg, int startPart, int partCount,
  768. BlockSequence originalList, BlockSequence effectiveList, final LayoutContext childLC,
  769. int lastBreak, int startElementIndex, int endElementIndex) {
  770. throw new IllegalStateException();
  771. }
  772. }