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