Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

AbstractBreaker.java 46KB

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