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

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093
  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. if (pen.getPenalty() == KnuthPenalty.INFINITE) {
  447. /**
  448. * That means that there was a keep.within-page="always", but that
  449. * it's OK to break at a column. TODO The break class is being
  450. * abused to implement keep.within-column and keep.within-page.
  451. * This is very misleading and must be revised.
  452. */
  453. lastBreakClass = Constants.EN_COLUMN;
  454. } else {
  455. lastBreakClass = pen.getBreakClass();
  456. }
  457. } else {
  458. lastBreakClass = Constants.EN_COLUMN;
  459. }
  460. }
  461. //the end of the new part
  462. endElementIndex = pbp.getLeafPos();
  463. // ignore the first elements added by the
  464. // PageSequenceLayoutManager
  465. startElementIndex += (startElementIndex == 0)
  466. ? effectiveList.ignoreAtStart
  467. : 0;
  468. log.debug("PLM> part: " + (p + 1)
  469. + ", start at pos " + startElementIndex
  470. + ", break at pos " + endElementIndex
  471. + ", break class = " + getBreakClassName(lastBreakClass));
  472. startPart(effectiveList, lastBreakClass);
  473. int displayAlign = getCurrentDisplayAlign();
  474. //The following is needed by SpaceResolver.performConditionalsNotification()
  475. //further down as there may be important Position elements in the element list trailer
  476. int notificationEndElementIndex = endElementIndex;
  477. // ignore the last elements added by the
  478. // PageSequenceLayoutManager
  479. endElementIndex -= (endElementIndex == (originalList.size() - 1))
  480. ? effectiveList.ignoreAtEnd
  481. : 0;
  482. // ignore the last element in the page if it is a KnuthGlue
  483. // object
  484. if (((KnuthElement) effectiveList.get(endElementIndex))
  485. .isGlue()) {
  486. endElementIndex--;
  487. }
  488. // ignore KnuthGlue and KnuthPenalty objects
  489. // at the beginning of the line
  490. startElementIndex = alg.par.getFirstBoxIndex(startElementIndex);
  491. if (startElementIndex <= endElementIndex) {
  492. if (log.isDebugEnabled()) {
  493. log.debug(" addAreas from " + startElementIndex
  494. + " to " + endElementIndex);
  495. }
  496. childLC = new LayoutContext(0);
  497. // set the space adjustment ratio
  498. childLC.setSpaceAdjust(pbp.bpdAdjust);
  499. // add space before if display-align is center or bottom
  500. // add space after if display-align is distribute and
  501. // this is not the last page
  502. if (pbp.difference != 0 && displayAlign == Constants.EN_CENTER) {
  503. childLC.setSpaceBefore(pbp.difference / 2);
  504. } else if (pbp.difference != 0 && displayAlign == Constants.EN_AFTER) {
  505. childLC.setSpaceBefore(pbp.difference);
  506. } else if (pbp.difference != 0 && displayAlign == Constants.EN_X_DISTRIBUTE
  507. && p < (partCount - 1)) {
  508. // count the boxes whose width is not 0
  509. int boxCount = 0;
  510. @SuppressWarnings("unchecked")
  511. ListIterator<KnuthElement> effectiveListIterator = effectiveList
  512. .listIterator(startElementIndex);
  513. while (effectiveListIterator.nextIndex() <= endElementIndex) {
  514. KnuthElement tempEl = effectiveListIterator.next();
  515. if (tempEl.isBox() && tempEl.getWidth() > 0) {
  516. boxCount++;
  517. }
  518. }
  519. // split the difference
  520. if (boxCount >= 2) {
  521. childLC.setSpaceAfter(pbp.difference / (boxCount - 1));
  522. }
  523. }
  524. /* *** *** non-standard extension *** *** */
  525. if (displayAlign == Constants.EN_X_FILL) {
  526. int averageLineLength = optimizeLineLength(effectiveList,
  527. startElementIndex, endElementIndex);
  528. if (averageLineLength != 0) {
  529. childLC.setStackLimitBP(MinOptMax.getInstance(averageLineLength));
  530. }
  531. }
  532. /* *** *** non-standard extension *** *** */
  533. // Handle SpaceHandling(Break)Positions, see SpaceResolver!
  534. SpaceResolver.performConditionalsNotification(effectiveList,
  535. startElementIndex, notificationEndElementIndex, lastBreak);
  536. // Add areas now!
  537. addAreas(new KnuthPossPosIter(effectiveList,
  538. startElementIndex, endElementIndex + 1), childLC);
  539. } else {
  540. //no content for this part
  541. handleEmptyContent();
  542. }
  543. finishPart(alg, pbp);
  544. lastBreak = endElementIndex;
  545. startElementIndex = pbp.getLeafPos() + 1;
  546. }
  547. }
  548. /**
  549. * Notifies the layout managers about the space and conditional length situation based on
  550. * the break decisions.
  551. * @param effectiveList Element list to be painted
  552. * @param startElementIndex start index of the part
  553. * @param endElementIndex end index of the part
  554. * @param lastBreak index of the last break element
  555. */
  556. /**
  557. * Handles span changes reported through the <code>LayoutContext</code>.
  558. * Only used by the PSLM and called by <code>getNextBlockList()</code>.
  559. * @param childLC the LayoutContext
  560. * @param nextSequenceStartsOn previous value for break handling
  561. * @return effective value for break handling
  562. */
  563. protected int handleSpanChange(LayoutContext childLC, int nextSequenceStartsOn) {
  564. return nextSequenceStartsOn;
  565. }
  566. /**
  567. * Gets the next block list (sequence) and adds it to a list of block lists if it's not empty.
  568. * @param childLC LayoutContext to use
  569. * @param nextSequenceStartsOn indicates on what page the next sequence should start
  570. * @return the page on which the next content should appear after a hard break
  571. */
  572. protected int getNextBlockList(LayoutContext childLC, int nextSequenceStartsOn) {
  573. return getNextBlockList(childLC, nextSequenceStartsOn, null, null, null);
  574. }
  575. /**
  576. * Gets the next block list (sequence) and adds it to a list of block lists
  577. * if it's not empty.
  578. *
  579. * @param childLC LayoutContext to use
  580. * @param nextSequenceStartsOn indicates on what page the next sequence
  581. * should start
  582. * @param positionAtIPDChange last element on the part before an IPD change
  583. * @param restartAtLM the layout manager from which to restart, if IPD
  584. * change occurs between two LMs
  585. * @param firstElements elements from non-restartable LMs on the new page
  586. * @return the page on which the next content should appear after a hard
  587. * break
  588. */
  589. protected int getNextBlockList(LayoutContext childLC, int nextSequenceStartsOn,
  590. Position positionAtIPDChange, LayoutManager restartAtLM,
  591. List<KnuthElement> firstElements) {
  592. updateLayoutContext(childLC);
  593. //Make sure the span change signal is reset
  594. childLC.signalSpanChange(Constants.NOT_SET);
  595. BlockSequence blockList;
  596. List<KnuthElement> returnedList;
  597. if (firstElements == null) {
  598. returnedList = getNextKnuthElements(childLC, alignment);
  599. } else if (positionAtIPDChange == null) {
  600. /*
  601. * No restartable element found after changing IPD break. Simply add the
  602. * non-restartable elements found after the break.
  603. */
  604. returnedList = firstElements;
  605. /*
  606. * Remove the last 3 penalty-filler-forced break elements that were added by
  607. * the Knuth algorithm. They will be re-added later on.
  608. */
  609. ListIterator iter = returnedList.listIterator(returnedList.size());
  610. for (int i = 0; i < 3; i++) {
  611. iter.previous();
  612. iter.remove();
  613. }
  614. } else {
  615. returnedList = getNextKnuthElements(childLC, alignment, positionAtIPDChange,
  616. restartAtLM);
  617. returnedList.addAll(0, firstElements);
  618. }
  619. if (returnedList != null) {
  620. if (returnedList.isEmpty()) {
  621. nextSequenceStartsOn = handleSpanChange(childLC, nextSequenceStartsOn);
  622. return nextSequenceStartsOn;
  623. }
  624. blockList = new BlockSequence(nextSequenceStartsOn, getCurrentDisplayAlign());
  625. //Only implemented by the PSLM
  626. nextSequenceStartsOn = handleSpanChange(childLC, nextSequenceStartsOn);
  627. Position breakPosition = null;
  628. if (ElementListUtils.endsWithForcedBreak(returnedList)) {
  629. KnuthPenalty breakPenalty = (KnuthPenalty) ListUtil
  630. .removeLast(returnedList);
  631. breakPosition = breakPenalty.getPosition();
  632. log.debug("PLM> break - " + getBreakClassName(breakPenalty.getBreakClass()));
  633. switch (breakPenalty.getBreakClass()) {
  634. case Constants.EN_PAGE:
  635. nextSequenceStartsOn = Constants.EN_ANY;
  636. break;
  637. case Constants.EN_COLUMN:
  638. //TODO Fix this when implementing multi-column layout
  639. nextSequenceStartsOn = Constants.EN_COLUMN;
  640. break;
  641. case Constants.EN_ODD_PAGE:
  642. nextSequenceStartsOn = Constants.EN_ODD_PAGE;
  643. break;
  644. case Constants.EN_EVEN_PAGE:
  645. nextSequenceStartsOn = Constants.EN_EVEN_PAGE;
  646. break;
  647. default:
  648. throw new IllegalStateException("Invalid break class: "
  649. + breakPenalty.getBreakClass());
  650. }
  651. }
  652. blockList.addAll(returnedList);
  653. BlockSequence seq;
  654. seq = blockList.endBlockSequence(breakPosition);
  655. if (seq != null) {
  656. blockLists.add(seq);
  657. }
  658. }
  659. return nextSequenceStartsOn;
  660. }
  661. /**
  662. * @param childLC LayoutContext to use
  663. * @param alg the pagebreaking algorithm
  664. * @param effectiveList the list of Knuth elements to be reused
  665. * @return the page on which the next content should appear after a hard break
  666. */
  667. private int getNextBlockListChangedIPD(LayoutContext childLC, PageBreakingAlgorithm alg,
  668. BlockSequence effectiveList) {
  669. int nextSequenceStartsOn;
  670. KnuthNode optimalBreak = alg.getBestNodeBeforeIPDChange();
  671. int positionIndex = optimalBreak.position;
  672. log.trace("IPD changes at index " + positionIndex);
  673. KnuthElement elementAtBreak = alg.getElement(positionIndex);
  674. Position positionAtBreak = elementAtBreak.getPosition();
  675. if (!(positionAtBreak instanceof SpaceResolver.SpaceHandlingBreakPosition)) {
  676. throw new UnsupportedOperationException(
  677. "Don't know how to restart at position " + positionAtBreak);
  678. }
  679. /* Retrieve the original position wrapped into this space position */
  680. positionAtBreak = positionAtBreak.getPosition();
  681. LayoutManager restartAtLM = null;
  682. List<KnuthElement> firstElements = Collections.emptyList();
  683. if (containsNonRestartableLM(positionAtBreak)) {
  684. if (alg.getIPDdifference() > 0) {
  685. EventBroadcaster eventBroadcaster = getCurrentChildLM().getFObj()
  686. .getUserAgent().getEventBroadcaster();
  687. BlockLevelEventProducer eventProducer
  688. = BlockLevelEventProducer.Provider.get(eventBroadcaster);
  689. eventProducer.nonRestartableContentFlowingToNarrowerPage(this);
  690. }
  691. firstElements = new LinkedList<KnuthElement>();
  692. boolean boxFound = false;
  693. Iterator<KnuthElement> iter = effectiveList.listIterator(positionIndex + 1);
  694. Position position = null;
  695. while (iter.hasNext()
  696. && (position == null || containsNonRestartableLM(position))) {
  697. positionIndex++;
  698. KnuthElement element = iter.next();
  699. position = element.getPosition();
  700. if (element.isBox()) {
  701. boxFound = true;
  702. firstElements.add(element);
  703. } else if (boxFound) {
  704. firstElements.add(element);
  705. }
  706. }
  707. if (position instanceof SpaceResolver.SpaceHandlingBreakPosition) {
  708. /* Retrieve the original position wrapped into this space position */
  709. positionAtBreak = position.getPosition();
  710. } else {
  711. positionAtBreak = null;
  712. }
  713. }
  714. if (positionAtBreak != null && positionAtBreak.getIndex() == -1) {
  715. /*
  716. * This is an indication that we are between two blocks
  717. * (possibly surrounded by another block), not inside a
  718. * paragraph.
  719. */
  720. Position position;
  721. Iterator<KnuthElement> iter = effectiveList.listIterator(positionIndex + 1);
  722. do {
  723. KnuthElement nextElement = iter.next();
  724. position = nextElement.getPosition();
  725. } while (position == null
  726. || position instanceof SpaceResolver.SpaceHandlingPosition
  727. || position instanceof SpaceResolver.SpaceHandlingBreakPosition
  728. && position.getPosition().getIndex() == -1);
  729. LayoutManager surroundingLM = positionAtBreak.getLM();
  730. while (position.getLM() != surroundingLM) {
  731. position = position.getPosition();
  732. }
  733. restartAtLM = position.getPosition().getLM();
  734. }
  735. nextSequenceStartsOn = getNextBlockList(childLC, Constants.EN_COLUMN,
  736. positionAtBreak, restartAtLM, firstElements);
  737. return nextSequenceStartsOn;
  738. }
  739. /**
  740. * Returns the average width of all the lines in the given range.
  741. * @param effectiveList effective block list to work on
  742. * @param startElementIndex index of the element starting the range
  743. * @param endElementIndex index of the element ending the range
  744. * @return the average line length, 0 if there's no content
  745. */
  746. private int optimizeLineLength(KnuthSequence effectiveList, int startElementIndex,
  747. int endElementIndex) {
  748. ListIterator<KnuthElement> effectiveListIterator;
  749. // optimize line length
  750. int boxCount = 0;
  751. int accumulatedLineLength = 0;
  752. int greatestMinimumLength = 0;
  753. effectiveListIterator = effectiveList.listIterator(startElementIndex);
  754. while (effectiveListIterator.nextIndex() <= endElementIndex) {
  755. KnuthElement tempEl = effectiveListIterator
  756. .next();
  757. if (tempEl instanceof KnuthBlockBox) {
  758. KnuthBlockBox blockBox = (KnuthBlockBox) tempEl;
  759. if (blockBox.getBPD() > 0) {
  760. log.debug("PSLM> nominal length of line = " + blockBox.getBPD());
  761. log.debug(" range = "
  762. + blockBox.getIPDRange());
  763. boxCount++;
  764. accumulatedLineLength += ((KnuthBlockBox) tempEl)
  765. .getBPD();
  766. }
  767. if (blockBox.getIPDRange().getMin() > greatestMinimumLength) {
  768. greatestMinimumLength = blockBox
  769. .getIPDRange().getMin();
  770. }
  771. }
  772. }
  773. int averageLineLength = 0;
  774. if (accumulatedLineLength > 0 && boxCount > 0) {
  775. averageLineLength = (int) (accumulatedLineLength / boxCount);
  776. log.debug("Average line length = " + averageLineLength);
  777. if (averageLineLength < greatestMinimumLength) {
  778. averageLineLength = greatestMinimumLength;
  779. log.debug(" Correction to: " + averageLineLength);
  780. }
  781. }
  782. return averageLineLength;
  783. }
  784. /**
  785. * Justifies the boxes and returns them as a new KnuthSequence.
  786. * @param blockList block list to justify
  787. * @param alg reference to the algorithm instance
  788. * @param availableBPD the available BPD
  789. * @return the effective list
  790. */
  791. private BlockSequence justifyBoxes // CSOK: MethodLength
  792. (BlockSequence blockList, PageBreakingAlgorithm alg, int availableBPD) {
  793. int optimalPageCount;
  794. alg.setConstantLineWidth(availableBPD);
  795. optimalPageCount = alg.findBreakingPoints(blockList, /*availableBPD,*/
  796. 1, true, BreakingAlgorithm.ALL_BREAKS);
  797. log.debug("PLM> optimalPageCount= " + optimalPageCount);
  798. //
  799. ListIterator<KnuthElement> sequenceIterator = blockList.listIterator();
  800. ListIterator<PageBreakPosition> breakIterator = alg.getPageBreaks().listIterator();
  801. KnuthElement thisElement = null;
  802. PageBreakPosition thisBreak;
  803. int adjustedDiff; // difference already adjusted
  804. while (breakIterator.hasNext()) {
  805. thisBreak = breakIterator.next();
  806. if (log.isDebugEnabled()) {
  807. log.debug("| first page: break= "
  808. + thisBreak.getLeafPos() + " difference= "
  809. + thisBreak.difference + " ratio= "
  810. + thisBreak.bpdAdjust);
  811. }
  812. adjustedDiff = 0;
  813. // glue and penalty items at the beginning of the page must
  814. // be ignored:
  815. // the first element returned by sequenceIterator.next()
  816. // inside the
  817. // while loop must be a box
  818. KnuthElement firstElement;
  819. while (sequenceIterator.hasNext()) {
  820. firstElement = sequenceIterator.next();
  821. if ( !firstElement.isBox() ) {
  822. log.debug("PLM> ignoring glue or penalty element "
  823. + "at the beginning of the sequence");
  824. if (firstElement.isGlue()) {
  825. ((BlockLevelLayoutManager) firstElement
  826. .getLayoutManager())
  827. .discardSpace((KnuthGlue) firstElement);
  828. }
  829. } else {
  830. break;
  831. }
  832. }
  833. sequenceIterator.previous();
  834. // scan the sub-sequence representing a page,
  835. // collecting information about potential adjustments
  836. MinOptMax lineNumberMaxAdjustment = MinOptMax.ZERO;
  837. MinOptMax spaceMaxAdjustment = MinOptMax.ZERO;
  838. LinkedList<KnuthGlue> blockSpacesList = new LinkedList<KnuthGlue>();
  839. LinkedList<KnuthGlue> unconfirmedList = new LinkedList<KnuthGlue>();
  840. LinkedList<KnuthGlue> adjustableLinesList = new LinkedList<KnuthGlue>();
  841. boolean bBoxSeen = false;
  842. while (sequenceIterator.hasNext()
  843. && sequenceIterator.nextIndex() <= thisBreak.getLeafPos()) {
  844. thisElement = sequenceIterator.next();
  845. if (thisElement.isGlue()) {
  846. // glue elements are used to represent adjustable
  847. // lines
  848. // and adjustable spaces between blocks
  849. KnuthGlue thisGlue = (KnuthGlue) thisElement;
  850. Adjustment adjustment = thisGlue.getAdjustmentClass();
  851. if (adjustment.equals(Adjustment.SPACE_BEFORE_ADJUSTMENT)
  852. || adjustment.equals(Adjustment.SPACE_AFTER_ADJUSTMENT)) {
  853. // potential space adjustment
  854. // glue items before the first box or after the
  855. // last one
  856. // must be ignored
  857. unconfirmedList.add(thisGlue);
  858. } else if (adjustment.equals(Adjustment.LINE_NUMBER_ADJUSTMENT)) {
  859. // potential line number adjustment
  860. lineNumberMaxAdjustment
  861. = lineNumberMaxAdjustment.plusMax(thisElement.getStretch());
  862. lineNumberMaxAdjustment
  863. = lineNumberMaxAdjustment.minusMin(thisElement.getShrink());
  864. adjustableLinesList.add(thisGlue);
  865. } else if (adjustment.equals(Adjustment.LINE_HEIGHT_ADJUSTMENT)) {
  866. // potential line height adjustment
  867. }
  868. } else if (thisElement.isBox()) {
  869. if (!bBoxSeen) {
  870. // this is the first box met in this page
  871. bBoxSeen = true;
  872. } else {
  873. while (!unconfirmedList.isEmpty()) {
  874. // glue items in unconfirmedList were not after
  875. // the last box
  876. // in this page; they must be added to
  877. // blockSpaceList
  878. KnuthGlue blockSpace = unconfirmedList.removeFirst();
  879. spaceMaxAdjustment
  880. = spaceMaxAdjustment.plusMax(blockSpace.getStretch());
  881. spaceMaxAdjustment
  882. = spaceMaxAdjustment.minusMin(blockSpace.getShrink());
  883. blockSpacesList.add(blockSpace);
  884. }
  885. }
  886. }
  887. }
  888. log.debug("| line number adj= "
  889. + lineNumberMaxAdjustment);
  890. log.debug("| space adj = "
  891. + spaceMaxAdjustment);
  892. if (thisElement.isPenalty() && thisElement.getWidth() > 0) {
  893. log.debug(" mandatory variation to the number of lines!");
  894. ((BlockLevelLayoutManager) thisElement
  895. .getLayoutManager()).negotiateBPDAdjustment(
  896. thisElement.getWidth(), thisElement);
  897. }
  898. if (thisBreak.bpdAdjust != 0
  899. && (thisBreak.difference > 0 && thisBreak.difference <= spaceMaxAdjustment
  900. .getMax())
  901. || (thisBreak.difference < 0 && thisBreak.difference >= spaceMaxAdjustment
  902. .getMin())) {
  903. // modify only the spaces between blocks
  904. adjustedDiff += adjustBlockSpaces(
  905. blockSpacesList,
  906. thisBreak.difference,
  907. (thisBreak.difference > 0 ? spaceMaxAdjustment.getMax()
  908. : -spaceMaxAdjustment.getMin()));
  909. log.debug("single space: "
  910. + (adjustedDiff == thisBreak.difference
  911. || thisBreak.bpdAdjust == 0 ? "ok"
  912. : "ERROR"));
  913. } else if (thisBreak.bpdAdjust != 0) {
  914. adjustedDiff += adjustLineNumbers(
  915. adjustableLinesList,
  916. thisBreak.difference,
  917. (thisBreak.difference > 0 ? lineNumberMaxAdjustment.getMax()
  918. : -lineNumberMaxAdjustment.getMin()));
  919. adjustedDiff += adjustBlockSpaces(
  920. blockSpacesList,
  921. thisBreak.difference - adjustedDiff,
  922. ((thisBreak.difference - adjustedDiff) > 0 ? spaceMaxAdjustment.getMax()
  923. : -spaceMaxAdjustment.getMin()));
  924. log.debug("lines and space: "
  925. + (adjustedDiff == thisBreak.difference
  926. || thisBreak.bpdAdjust == 0 ? "ok"
  927. : "ERROR"));
  928. }
  929. }
  930. // create a new sequence: the new elements will contain the
  931. // Positions
  932. // which will be used in the addAreas() phase
  933. BlockSequence effectiveList = new BlockSequence(blockList.getStartOn(),
  934. blockList.getDisplayAlign());
  935. effectiveList.addAll(getCurrentChildLM().getChangedKnuthElements(
  936. blockList.subList(0, blockList.size() - blockList.ignoreAtEnd),
  937. /* 0, */0));
  938. //effectiveList.add(new KnuthPenalty(0, -KnuthElement.INFINITE,
  939. // false, new Position(this), false));
  940. effectiveList.endSequence();
  941. ElementListObserver.observe(effectiveList, "breaker-effective", null);
  942. alg.getPageBreaks().clear(); //Why this?
  943. return effectiveList;
  944. }
  945. private int adjustBlockSpaces(LinkedList<KnuthGlue> spaceList, int difference, int total) {
  946. if (log.isDebugEnabled()) {
  947. log.debug("AdjustBlockSpaces: difference " + difference + " / " + total
  948. + " on " + spaceList.size() + " spaces in block");
  949. }
  950. ListIterator<KnuthGlue> spaceListIterator = spaceList.listIterator();
  951. int adjustedDiff = 0;
  952. int partial = 0;
  953. while (spaceListIterator.hasNext()) {
  954. KnuthGlue blockSpace = spaceListIterator.next();
  955. partial += (difference > 0 ? blockSpace.getStretch() : blockSpace.getShrink());
  956. if (log.isDebugEnabled()) {
  957. log.debug("available = " + partial + " / " + total);
  958. log.debug("competenza = "
  959. + (((int)((float) partial * difference / total)) - adjustedDiff)
  960. + " / " + difference);
  961. }
  962. int newAdjust = ((BlockLevelLayoutManager) blockSpace.getLayoutManager())
  963. .negotiateBPDAdjustment
  964. (((int) ((float) partial * difference / total)) - adjustedDiff, blockSpace);
  965. adjustedDiff += newAdjust;
  966. }
  967. return adjustedDiff;
  968. }
  969. private int adjustLineNumbers(LinkedList<KnuthGlue> lineList, int difference, int total) {
  970. if (log.isDebugEnabled()) {
  971. log.debug("AdjustLineNumbers: difference "
  972. + difference
  973. + " / "
  974. + total
  975. + " on "
  976. + lineList.size()
  977. + " elements");
  978. }
  979. ListIterator<KnuthGlue> lineListIterator = lineList.listIterator();
  980. int adjustedDiff = 0;
  981. int partial = 0;
  982. while (lineListIterator.hasNext()) {
  983. KnuthGlue line = lineListIterator.next();
  984. partial += (difference > 0 ? line.getStretch() : line.getShrink());
  985. int newAdjust = ((BlockLevelLayoutManager) line.getLayoutManager())
  986. .negotiateBPDAdjustment
  987. (((int) ((float) partial * difference / total)) - adjustedDiff, line);
  988. adjustedDiff += newAdjust;
  989. }
  990. return adjustedDiff;
  991. }
  992. }