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: PageBreaker.java 1880106 2020-07-21 13:28:42Z ssteiner $ */ 19 20 package org.apache.fop.layoutmgr; 21 22 import java.util.ArrayList; 23 import java.util.Collections; 24 import java.util.Iterator; 25 import java.util.List; 26 27 import org.apache.fop.area.Block; 28 import org.apache.fop.area.BodyRegion; 29 import org.apache.fop.area.Footnote; 30 import org.apache.fop.area.PageViewport; 31 import org.apache.fop.fo.Constants; 32 import org.apache.fop.fo.FObj; 33 import org.apache.fop.fo.pagination.Region; 34 import org.apache.fop.fo.pagination.RegionBody; 35 import org.apache.fop.fo.pagination.StaticContent; 36 import org.apache.fop.layoutmgr.BreakingAlgorithm.KnuthNode; 37 import org.apache.fop.layoutmgr.PageBreakingAlgorithm.PageBreakingLayoutListener; 38 import org.apache.fop.layoutmgr.list.ListItemLayoutManager; 39 import org.apache.fop.traits.MinOptMax; 40 41 /** 42 * Handles the breaking of pages in an fo:flow 43 */ 44 public class PageBreaker extends AbstractBreaker { 45 46 private boolean firstPart = true; 47 private boolean pageBreakHandled; 48 private boolean needColumnBalancing; 49 private PageProvider pageProvider; 50 private Block separatorArea; 51 private boolean spanAllActive; 52 private boolean layoutRedone; 53 private int previousIndex; 54 private boolean handlingStartOfFloat; 55 private boolean handlingEndOfFloat; 56 private int floatHeight; 57 private int floatYOffset; 58 59 private List relayedFootnotesList; 60 private List relayedLengthList; 61 private int relayedTotalFootnotesLength; 62 private int relayedInsertedFootnotesLength; 63 private boolean relayedFootnotesPending; 64 private boolean relayedNewFootnotes; 65 private int relayedFirstNewFootnoteIndex; 66 private int relayedFootnoteListIndex; 67 private int relayedFootnoteElementIndex = -1; 68 private MinOptMax relayedFootnoteSeparatorLength; 69 private int previousFootnoteListIndex = -2; 70 private int previousFootnoteElementIndex = -2; 71 private int prevousColumnCount; 72 73 /** 74 * The FlowLayoutManager object, which processes 75 * the single fo:flow of the fo:page-sequence 76 */ 77 private FlowLayoutManager childFLM; 78 79 private StaticContentLayoutManager footnoteSeparatorLM; 80 81 /** 82 * Construct page breaker. 83 * @param pslm the page sequence layout manager 84 */ PageBreaker(PageSequenceLayoutManager pslm)85 public PageBreaker(PageSequenceLayoutManager pslm) { 86 this.pslm = pslm; 87 this.pageProvider = pslm.getPageProvider(); 88 this.childFLM = pslm.getLayoutManagerMaker().makeFlowLayoutManager( 89 pslm, pslm.getPageSequence().getMainFlow()); 90 } 91 92 /** {@inheritDoc} */ updateLayoutContext(LayoutContext context)93 protected void updateLayoutContext(LayoutContext context) { 94 int flowIPD = pslm.getCurrentColumnWidth(); 95 context.setRefIPD(flowIPD); 96 } 97 98 /** {@inheritDoc} */ getTopLevelLM()99 protected LayoutManager getTopLevelLM() { 100 return pslm; 101 } 102 103 /** {@inheritDoc} */ getPageProvider()104 protected PageProvider getPageProvider() { 105 return pslm.getPageProvider(); 106 } 107 108 /** 109 * Starts the page breaking process. 110 * @param flowBPD the constant available block-progression-dimension (used for every part) 111 */ doLayout(int flowBPD)112 boolean doLayout(int flowBPD) { 113 return doLayout(flowBPD, false); 114 } 115 116 /** {@inheritDoc} */ createLayoutListener()117 protected PageBreakingLayoutListener createLayoutListener() { 118 return new PageBreakingLayoutListener() { 119 120 public void notifyOverflow(int part, int amount, FObj obj) { 121 Page p = pageProvider.getPageFromColumnIndex(part); 122 RegionBody body = (RegionBody)p.getSimplePageMaster().getRegion( 123 Region.FO_REGION_BODY); 124 BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( 125 body.getUserAgent().getEventBroadcaster()); 126 127 boolean canRecover = (body.getOverflow() != Constants.EN_ERROR_IF_OVERFLOW); 128 boolean needClip = (body.getOverflow() == Constants.EN_HIDDEN 129 || body.getOverflow() == Constants.EN_ERROR_IF_OVERFLOW); 130 eventProducer.regionOverflow(this, body.getName(), 131 p.getPageViewport().getPageNumberString(), 132 amount, needClip, canRecover, 133 body.getLocator()); 134 } 135 136 }; 137 } 138 139 /** {@inheritDoc} */ 140 protected int handleSpanChange(LayoutContext childLC, int nextSequenceStartsOn) { 141 needColumnBalancing = false; 142 if (childLC.getNextSpan() != Constants.NOT_SET) { 143 //Next block list will have a different span. 144 nextSequenceStartsOn = childLC.getNextSpan(); 145 needColumnBalancing = childLC.getNextSpan() == Constants.EN_ALL 146 && childLC.getDisableColumnBalancing() == Constants.EN_FALSE; 147 148 } 149 if (needColumnBalancing) { 150 log.debug( 151 "Column balancing necessary for the next element list!!!"); 152 } 153 return nextSequenceStartsOn; 154 } 155 156 /** {@inheritDoc} */ 157 protected int getNextBlockList(LayoutContext childLC, 158 int nextSequenceStartsOn) { 159 return getNextBlockList(childLC, nextSequenceStartsOn, null, null, null); 160 } 161 162 /** {@inheritDoc} */ 163 protected int getNextBlockList(LayoutContext childLC, int nextSequenceStartsOn, 164 Position positionAtIPDChange, LayoutManager restartLM, List firstElements) { 165 if (!layoutRedone && !handlingFloat()) { 166 if (!firstPart) { 167 // if this is the first page that will be created by 168 // the current BlockSequence, it could have a break 169 // condition that must be satisfied; 170 // otherwise, we may simply need a new page 171 handleBreakTrait(nextSequenceStartsOn); 172 } 173 firstPart = false; 174 pageBreakHandled = true; 175 176 pageProvider.setStartOfNextElementList(pslm.getCurrentPageNum(), pslm.getCurrentPV() 177 .getCurrentSpan().getCurrentFlowIndex(), this.spanAllActive); 178 } 179 return super.getNextBlockList(childLC, nextSequenceStartsOn, positionAtIPDChange, 180 restartLM, firstElements); 181 } 182 183 private boolean containsFootnotes(List contentList, LayoutContext context) { 184 boolean containsFootnotes = false; 185 if (contentList != null) { 186 for (Object aContentList : contentList) { 187 ListElement element = (ListElement) aContentList; 188 if (element instanceof KnuthBlockBox 189 && ((KnuthBlockBox) element).hasAnchors()) { 190 // element represents a line with footnote citations 191 containsFootnotes = true; 192 KnuthBlockBox box = (KnuthBlockBox) element; 193 List<List<KnuthElement>> footnotes = getFootnoteKnuthElements(childFLM, context, 194 box.getFootnoteBodyLMs()); 195 for (List<KnuthElement> footnote : footnotes) { 196 box.addElementList(footnote); 197 } 198 } 199 } 200 } 201 return containsFootnotes; 202 } 203 204 public static List<List<KnuthElement>> getFootnoteKnuthElements(FlowLayoutManager flowLM, LayoutContext context, 205 List<FootnoteBodyLayoutManager> footnoteBodyLMs) { 206 List<List<KnuthElement>> footnotes = new ArrayList<List<KnuthElement>>(); 207 LayoutContext footnoteContext = LayoutContext.copyOf(context); 208 footnoteContext.setStackLimitBP(context.getStackLimitBP()); 209 footnoteContext.setRefIPD(flowLM.getPSLM() 210 .getCurrentPV().getRegionReference(Constants.FO_REGION_BODY).getIPD()); 211 for (FootnoteBodyLayoutManager fblm : footnoteBodyLMs) { 212 fblm.setParent(flowLM); 213 fblm.initialize(); 214 List<KnuthElement> footnote = fblm.getNextKnuthElements(footnoteContext, Constants.EN_START); 215 // TODO this does not respect possible stacking constraints between footnotes 216 SpaceResolver.resolveElementList(footnote); 217 footnotes.add(footnote); 218 } 219 return footnotes; 220 } 221 222 private void handleFootnoteSeparator() { 223 StaticContent footnoteSeparator; 224 footnoteSeparator = pslm.getPageSequence().getStaticContent("xsl-footnote-separator"); 225 if (footnoteSeparator != null) { 226 // the footnote separator can contain page-dependent content such as 227 // page numbers or retrieve markers, so its areas cannot simply be 228 // obtained now and repeated in each page; 229 // we need to know in advance the separator bpd: the actual separator 230 // could be different from page to page, but its bpd would likely be 231 // always the same 232 233 // create a Block area that will contain the separator areas 234 separatorArea = new Block(); 235 separatorArea.setIPD(pslm.getCurrentPV() 236 .getRegionReference(Constants.FO_REGION_BODY).getIPD()); 237 // create a StaticContentLM for the footnote separator 238 footnoteSeparatorLM 239 = pslm.getLayoutManagerMaker().makeStaticContentLayoutManager( 240 pslm, footnoteSeparator, separatorArea); 241 footnoteSeparatorLM.doLayout(); 242 243 footnoteSeparatorLength = MinOptMax.getInstance(separatorArea.getBPD()); 244 } 245 } 246 247 /** {@inheritDoc} */ 248 protected List getNextKnuthElements(LayoutContext context, int alignment) { 249 List contentList = null; 250 251 while (!childFLM.isFinished() && contentList == null) { 252 contentList = childFLM.getNextKnuthElements(context, alignment); 253 } 254 255 // scan contentList, searching for footnotes 256 if (containsFootnotes(contentList, context)) { 257 // handle the footnote separator 258 handleFootnoteSeparator(); 259 } 260 261 return contentList; 262 } 263 264 /** {@inheritDoc} */ 265 protected List getNextKnuthElements(LayoutContext context, int alignment, 266 Position positionAtIPDChange, LayoutManager restartAtLM) { 267 List contentList = null; 268 269 do { 270 contentList = childFLM.getNextKnuthElements(context, alignment, positionAtIPDChange, 271 restartAtLM); 272 } while (!childFLM.isFinished() && contentList == null); 273 274 // scan contentList, searching for footnotes 275 if (containsFootnotes(contentList, context)) { 276 // handle the footnote separator 277 handleFootnoteSeparator(); 278 } 279 return contentList; 280 } 281 282 /** 283 * @return current display alignment 284 */ 285 protected int getCurrentDisplayAlign() { 286 return pslm.getCurrentPage().getSimplePageMaster().getRegion( 287 Constants.FO_REGION_BODY).getDisplayAlign(); 288 } 289 290 /** 291 * @return whether or not this flow has more page break opportunities 292 */ 293 protected boolean hasMoreContent() { 294 return !childFLM.isFinished(); 295 } 296 297 /** 298 * Adds an area to the flow layout manager 299 * @param posIter the position iterator 300 * @param context the layout context 301 */ 302 protected void addAreas(PositionIterator posIter, LayoutContext context) { 303 if (footnoteSeparatorLM != null) { 304 StaticContent footnoteSeparator = pslm.getPageSequence().getStaticContent( 305 "xsl-footnote-separator"); 306 // create a Block area that will contain the separator areas 307 separatorArea = new Block(); 308 separatorArea.setIPD( 309 pslm.getCurrentPV().getRegionReference(Constants.FO_REGION_BODY).getIPD()); 310 // create a StaticContentLM for the footnote separator 311 footnoteSeparatorLM = pslm.getLayoutManagerMaker().makeStaticContentLayoutManager( 312 pslm, footnoteSeparator, separatorArea); 313 footnoteSeparatorLM.doLayout(); 314 } 315 316 childFLM.addAreas(posIter, context); 317 } 318 319 /** 320 * {@inheritDoc} 321 * This implementation checks whether to trigger column-balancing, 322 * or whether to take into account a 'last-page' condition. 323 */ 324 protected void doPhase3(PageBreakingAlgorithm alg, int partCount, 325 BlockSequence originalList, BlockSequence effectiveList) { 326 327 if (needColumnBalancing) { 328 //column balancing for the last part 329 redoLayout(alg, partCount, originalList, effectiveList); 330 return; 331 } 332 333 if (shouldRedoLayout(partCount)) { 334 redoLayout(alg, partCount, originalList, effectiveList); 335 return; 336 } 337 338 //nothing special: just add the areas now 339 addAreas(alg, partCount, originalList, effectiveList); 340 } 341 342 protected void prepareToRedoLayout(PageBreakingAlgorithm alg, int partCount, 343 BlockSequence originalList, 344 BlockSequence effectiveList) { 345 int newStartPos = 0; 346 int restartPoint = pageProvider.getStartingPartIndexForLastPage(partCount); 347 if (restartPoint > 0 && !layoutRedone) { 348 // Add definitive areas for the parts before the 349 // restarting point 350 addAreas(alg, restartPoint, originalList, effectiveList); 351 // Get page break from which we restart 352 PageBreakPosition pbp = alg.getPageBreaks().get(restartPoint - 1); 353 newStartPos = alg.par.getFirstBoxIndex(pbp.getLeafPos() + 1); 354 // Handle page break right here to avoid any side-effects 355 if (newStartPos > 0) { 356 handleBreakTrait(Constants.EN_PAGE); 357 } 358 } 359 pageBreakHandled = true; 360 // Update so the available BPD is reported correctly 361 int currentPageNum = pslm.getCurrentPageNum(); 362 int currentColumn = pslm.getCurrentPV().getCurrentSpan().getCurrentFlowIndex(); 363 pageProvider.setStartOfNextElementList(currentPageNum, currentColumn, spanAllActive); 364 365 // Make sure we only add the areas we haven't added already 366 effectiveList.ignoreAtStart = newStartPos; 367 if (!layoutRedone) { 368 // Handle special page-master for last page 369 setLastPageIndex(currentPageNum); 370 // BodyRegion lastBody = pageProvider.getPage(false, currentPageNum).getPageViewport().getBodyRegion(); 371 pslm.setCurrentPage(pageProvider.getPage(false, currentPageNum)); 372 previousIndex = pageProvider.getIndexOfCachedLastPage(); 373 } else { 374 setLastPageIndex(currentPageNum + 1); 375 // pslm.setCurrentPage(previousPage); 376 pageProvider.discardCacheStartingWith(previousIndex); 377 pslm.setCurrentPage(pageProvider.getPage(false, currentPageNum)); 378 } 379 layoutRedone = true; 380 } 381 382 /** 383 * Restart the algorithm at the break corresponding to the given partCount. Used to 384 * re-do the part after the last break in case of either column-balancing or a last 385 * page-master. 386 */ 387 private void redoLayout(PageBreakingAlgorithm alg, int partCount, 388 BlockSequence originalList, BlockSequence effectiveList) { 389 390 int newStartPos = 0; 391 int restartPoint = pageProvider.getStartingPartIndexForLastPage(partCount); 392 if (restartPoint > 0) { 393 //Add definitive areas for the parts before the 394 //restarting point 395 addAreas(alg, restartPoint, originalList, effectiveList); 396 //Get page break from which we restart 397 PageBreakPosition pbp = alg.getPageBreaks().get(restartPoint - 1); 398 newStartPos = alg.par.getFirstBoxIndex(pbp.getLeafPos() + 1); 399 //Handle page break right here to avoid any side-effects 400 if (newStartPos > 0) { 401 handleBreakTrait(Constants.EN_PAGE); 402 } 403 } 404 405 log.debug("Restarting at " + restartPoint 406 + ", new start position: " + newStartPos); 407 408 pageBreakHandled = true; 409 //Update so the available BPD is reported correctly 410 int currentPageNum = pslm.getCurrentPageNum(); 411 412 pageProvider.setStartOfNextElementList(currentPageNum, 413 pslm.getCurrentPV().getCurrentSpan().getCurrentFlowIndex(), this.spanAllActive); 414 415 //Make sure we only add the areas we haven't added already 416 effectiveList.ignoreAtStart = newStartPos; 417 418 PageBreakingAlgorithm algRestart; 419 if (needColumnBalancing) { 420 log.debug("Column balancing now!!!"); 421 log.debug("==================================================="); 422 423 //Restart last page 424 algRestart = new BalancingColumnBreakingAlgorithm( 425 getTopLevelLM(), getPageProvider(), createLayoutListener(), 426 alignment, Constants.EN_START, footnoteSeparatorLength, 427 isPartOverflowRecoveryActivated(), 428 pslm.getCurrentPV().getBodyRegion().getColumnCount()); 429 log.debug("==================================================="); 430 } else { 431 // Handle special page-master for last page 432 BodyRegion currentBody = pageProvider.getPage(false, currentPageNum) 433 .getPageViewport().getBodyRegion(); 434 435 setLastPageIndex(currentPageNum); 436 437 BodyRegion lastBody = pageProvider.getPage(false, currentPageNum) 438 .getPageViewport().getBodyRegion(); 439 lastBody.getMainReference().setSpans(currentBody.getMainReference().getSpans()); 440 log.debug("Last page handling now!!!"); 441 log.debug("==================================================="); 442 //Restart last page 443 algRestart = new PageBreakingAlgorithm( 444 getTopLevelLM(), getPageProvider(), createLayoutListener(), 445 alg.getAlignment(), alg.getAlignmentLast(), 446 footnoteSeparatorLength, 447 isPartOverflowRecoveryActivated(), false, false); 448 log.debug("==================================================="); 449 } 450 451 int optimalPageCount = algRestart.findBreakingPoints(effectiveList, 452 newStartPos, 453 1, true, BreakingAlgorithm.ALL_BREAKS); 454 log.debug("restart: optimalPageCount= " + optimalPageCount 455 + " pageBreaks.size()= " + algRestart.getPageBreaks().size()); 456 457 boolean fitsOnePage 458 = optimalPageCount <= pslm.getCurrentPV() 459 .getBodyRegion().getMainReference().getCurrentSpan().getColumnCount(); 460 461 if (needColumnBalancing) { 462 if (!fitsOnePage) { 463 log.warn( 464 "Breaking algorithm produced more columns than are available."); 465 /* reenable when everything works 466 throw new IllegalStateException( 467 "Breaking algorithm must not produce more columns than available."); 468 */ 469 } 470 } else { 471 boolean ipdChange = algRestart.getIPDdifference() != 0; 472 if (fitsOnePage && !ipdChange) { 473 //Replace last page 474 pslm.setCurrentPage(pageProvider.getPage(false, currentPageNum)); 475 } else { 476 //Last page-master cannot hold the content. 477 //Add areas now... 478 addAreas(alg, restartPoint, partCount - restartPoint, originalList, effectiveList); 479 if (!ipdChange) { 480 //...and add a blank last page 481 setLastPageIndex(currentPageNum + 1); 482 pslm.setCurrentPage(pslm.makeNewPage(true)); 483 } 484 return; 485 } 486 } 487 488 addAreas(algRestart, optimalPageCount, originalList, effectiveList); 489 } 490 491 private void setLastPageIndex(int currentPageNum) { 492 int lastPageIndex = pslm.getForcedLastPageNum(currentPageNum); 493 pageProvider.setLastPageIndex(lastPageIndex); 494 } 495 496 /** {@inheritDoc} */ 497 protected void startPart(BlockSequence list, int breakClass, boolean emptyContent) { 498 log.debug("startPart() breakClass=" + getBreakClassName(breakClass)); 499 if (pslm.getCurrentPage() == null) { 500 throw new IllegalStateException("curPage must not be null"); 501 } 502 if (!pageBreakHandled) { 503 504 //firstPart is necessary because we need the first page before we start the 505 //algorithm so we have a BPD and IPD. This may subject to change later when we 506 //start handling more complex cases. 507 if (!firstPart) { 508 // if this is the first page that will be created by 509 // the current BlockSequence, it could have a break 510 // condition that must be satisfied; 511 // otherwise, we may simply need a new page 512 handleBreakTrait(breakClass, emptyContent); 513 } 514 pageProvider.setStartOfNextElementList(pslm.getCurrentPageNum(), 515 pslm.getCurrentPV().getCurrentSpan().getCurrentFlowIndex(), 516 this.spanAllActive); 517 } 518 pageBreakHandled = false; 519 // add static areas and resolve any new id areas 520 // finish page and add to area tree 521 firstPart = false; 522 } 523 524 /** {@inheritDoc} */ 525 protected void handleEmptyContent() { 526 pslm.getCurrentPV().getPage().fakeNonEmpty(); 527 } 528 529 /** {@inheritDoc} */ 530 protected void finishPart(PageBreakingAlgorithm alg, PageBreakPosition pbp) { 531 // add footnote areas 532 if (!pslm.getTableHeaderFootnotes().isEmpty() 533 || pbp.footnoteFirstListIndex < pbp.footnoteLastListIndex 534 || pbp.footnoteFirstElementIndex <= pbp.footnoteLastElementIndex 535 || !pslm.getTableFooterFootnotes().isEmpty()) { 536 for (List<KnuthElement> footnote : pslm.getTableHeaderFootnotes()) { 537 addFootnoteAreas(footnote); 538 } 539 // call addAreas() for each FootnoteBodyLM 540 for (int i = pbp.footnoteFirstListIndex; i <= pbp.footnoteLastListIndex; i++) { 541 List elementList = alg.getFootnoteList(i); 542 int firstIndex = (i == pbp.footnoteFirstListIndex 543 ? pbp.footnoteFirstElementIndex : 0); 544 int lastIndex = (i == pbp.footnoteLastListIndex 545 ? pbp.footnoteLastElementIndex : elementList.size() - 1); 546 addFootnoteAreas(elementList, firstIndex, lastIndex + 1); 547 } 548 for (List<KnuthElement> footnote : pslm.getTableFooterFootnotes()) { 549 addFootnoteAreas(footnote); 550 } 551 // set the offset from the top margin 552 Footnote parentArea = pslm.getCurrentPV().getBodyRegion().getFootnote(); 553 int topOffset = pslm.getCurrentPV().getBodyRegion().getBPD() - parentArea.getBPD(); 554 if (separatorArea != null) { 555 topOffset -= separatorArea.getBPD(); 556 } 557 parentArea.setTop(topOffset); 558 parentArea.setSeparator(separatorArea); 559 } 560 pslm.getCurrentPV().getCurrentSpan().notifyFlowsFinished(); 561 pslm.clearTableHeadingFootnotes(); 562 } 563 564 private void addFootnoteAreas(List<KnuthElement> footnote) { 565 addFootnoteAreas(footnote, 0, footnote.size()); 566 } 567 568 private void addFootnoteAreas(List<KnuthElement> footnote, int startIndex, int endIndex) { 569 SpaceResolver.performConditionalsNotification(footnote, startIndex, endIndex - 1, -1); 570 LayoutContext childLC = LayoutContext.newInstance(); 571 AreaAdditionUtil.addAreas(null, new KnuthPossPosIter(footnote, startIndex, endIndex), childLC); 572 } 573 574 /** {@inheritDoc} */ 575 protected FlowLayoutManager getCurrentChildLM() { 576 return childFLM; 577 } 578 579 /** {@inheritDoc} */ 580 protected void observeElementList(List elementList) { 581 ElementListObserver.observe(elementList, "breaker", 582 pslm.getFObj().getId()); 583 } 584 585 /** 586 * Depending on the kind of break condition, move to next column 587 * or page. May need to make an empty page if next page would 588 * not have the desired "handedness". 589 * @param breakVal - value of break-before or break-after trait. 590 */ 591 private void handleBreakTrait(int breakVal) { 592 handleBreakTrait(breakVal, false); 593 } 594 595 private void handleBreakTrait(int breakVal, boolean emptyContent) { 596 Page curPage = pslm.getCurrentPage(); 597 switch (breakVal) { 598 case Constants.EN_ALL: 599 //break due to span change in multi-column layout 600 curPage.getPageViewport().createSpan(true); 601 this.spanAllActive = true; 602 return; 603 case Constants.EN_NONE: 604 curPage.getPageViewport().createSpan(false); 605 this.spanAllActive = false; 606 return; 607 case Constants.EN_COLUMN: 608 case Constants.EN_AUTO: 609 case Constants.EN_PAGE: 610 case -1: 611 PageViewport pv = curPage.getPageViewport(); 612 613 //Check if previous page was spanned 614 boolean forceNewPageWithSpan = false; 615 RegionBody rb = (RegionBody)curPage.getSimplePageMaster().getRegion( 616 Constants.FO_REGION_BODY); 617 forceNewPageWithSpan 618 = (rb.getColumnCount() > 1 619 && pv.getCurrentSpan().getColumnCount() == 1); 620 621 if (forceNewPageWithSpan) { 622 log.trace("Forcing new page with span"); 623 curPage = pslm.makeNewPage(false); 624 curPage.getPageViewport().createSpan(true); 625 } else { 626 if (breakVal == Constants.EN_PAGE) { 627 handleBreakBeforeFollowingPage(breakVal); 628 } else { 629 if (pv.getCurrentSpan().hasMoreFlows()) { 630 log.trace("Moving to next flow"); 631 pv.getCurrentSpan().moveToNextFlow(); 632 } else { 633 log.trace("Making new page"); 634 pslm.makeNewPage(false, emptyContent); 635 } 636 } 637 } 638 return; 639 default: 640 handleBreakBeforeFollowingPage(breakVal); 641 } 642 } 643 644 private void handleBreakBeforeFollowingPage(int breakVal) { 645 log.debug("handling break-before after page " + pslm.getCurrentPageNum() + " breakVal=" 646 + getBreakClassName(breakVal)); 647 if (needBlankPageBeforeNew(breakVal)) { 648 log.trace("Inserting blank page"); 649 /* curPage = */pslm.makeNewPage(true); 650 } 651 if (needNewPage(breakVal)) { 652 log.trace("Making new page"); 653 /* curPage = */pslm.makeNewPage(false); 654 } 655 } 656 657 /** 658 * Check if a blank page is needed to accommodate 659 * desired even or odd page number. 660 * @param breakVal - value of break-before or break-after trait. 661 */ 662 private boolean needBlankPageBeforeNew(int breakVal) { 663 if (breakVal == Constants.EN_PAGE 664 || (pslm.getCurrentPage().getPageViewport().getPage().isEmpty())) { 665 // any page is OK or we already have an empty page 666 return false; 667 } else { 668 /* IF we are on the kind of page we need, we'll need a new page. */ 669 if (pslm.getCurrentPageNum() % 2 == 0) { // even page 670 return (breakVal == Constants.EN_EVEN_PAGE); 671 } else { // odd page 672 return (breakVal == Constants.EN_ODD_PAGE); 673 } 674 } 675 } 676 677 /** 678 * See if need to generate a new page 679 * @param breakVal - value of break-before or break-after trait. 680 */ 681 private boolean needNewPage(int breakVal) { 682 if (pslm.getCurrentPage().getPageViewport().getPage().isEmpty()) { 683 if (breakVal == Constants.EN_PAGE) { 684 return false; 685 } else if (pslm.getCurrentPageNum() % 2 == 0) { // even page 686 return (breakVal == Constants.EN_ODD_PAGE); 687 } else { // odd page 688 return (breakVal == Constants.EN_EVEN_PAGE); 689 } 690 } else { 691 return true; 692 } 693 } 694 695 protected boolean shouldRedoLayout() { 696 return shouldRedoLayout(-1); 697 } 698 699 protected boolean shouldRedoLayout(int partCount) { 700 boolean lastPageMasterDefined = pslm.getPageSequence().hasPagePositionLast(); 701 if (!lastPageMasterDefined && partCount != -1) { 702 lastPageMasterDefined = pslm.getPageSequence().hasPagePositionOnly() && pslm.isOnFirstPage(partCount - 1); 703 } 704 return (!hasMoreContent() && lastPageMasterDefined && !layoutRedone); 705 } 706 707 protected boolean wasLayoutRedone() { 708 return layoutRedone; 709 } 710 711 protected boolean lastPageHasIPDChange(int optimalPageCount) { 712 boolean lastPageMasterDefined = pslm.getPageSequence().hasPagePositionLast(); 713 boolean onlyPageMasterDefined = pslm.getPageSequence().hasPagePositionOnly(); 714 if (lastPageMasterDefined && !onlyPageMasterDefined) { 715 // code not very robust and unable to handle situations were only and last are defined 716 int currentColumnCount = pageProvider.getCurrentColumnCount(); 717 boolean changeInColumnCount = prevousColumnCount > 0 && prevousColumnCount != currentColumnCount; 718 prevousColumnCount = currentColumnCount; 719 if ((currentColumnCount > 1 && optimalPageCount % currentColumnCount == 0) || changeInColumnCount) { 720 return false; 721 } 722 int currentIPD = this.pageProvider.getCurrentIPD(); 723 int lastPageIPD = this.pageProvider.getLastPageIPD(); 724 return lastPageIPD != -1 && currentIPD != lastPageIPD; 725 } 726 return false; 727 } 728 729 protected boolean handlingStartOfFloat() { 730 return handlingStartOfFloat; 731 } 732 733 protected void handleStartOfFloat(int fHeight, int fYOffset) { 734 handlingStartOfFloat = true; 735 handlingEndOfFloat = false; 736 floatHeight = fHeight; 737 floatYOffset = fYOffset; 738 childFLM.handleFloatOn(); 739 } 740 741 protected int getFloatHeight() { 742 return floatHeight; 743 } 744 745 protected int getFloatYOffset() { 746 return floatYOffset; 747 } 748 749 protected boolean handlingEndOfFloat() { 750 return handlingEndOfFloat; 751 } 752 753 protected void handleEndOfFloat(int fHeight) { 754 handlingEndOfFloat = true; 755 handlingStartOfFloat = false; 756 floatHeight = fHeight; 757 childFLM.handleFloatOff(); 758 } 759 760 protected boolean handlingFloat() { 761 return (handlingStartOfFloat || handlingEndOfFloat); 762 } 763 764 public int getOffsetDueToFloat() { 765 handlingEndOfFloat = false; 766 return floatHeight + floatYOffset; 767 } 768 769 protected int handleFloatLayout(PageBreakingAlgorithm alg, int optimalPageCount, BlockSequence blockList, 770 LayoutContext childLC) { 771 pageBreakHandled = true; 772 List firstElements = Collections.EMPTY_LIST; 773 KnuthNode floatNode = alg.getBestFloatEdgeNode(); 774 int floatPosition = floatNode.position; 775 KnuthElement floatElem = alg.getElement(floatPosition); 776 Position positionAtBreak = floatElem.getPosition(); 777 if (!(positionAtBreak instanceof SpaceResolver.SpaceHandlingBreakPosition)) { 778 throw new UnsupportedOperationException("Don't know how to restart at position" + positionAtBreak); 779 } 780 /* Retrieve the original position wrapped into this space position */ 781 positionAtBreak = positionAtBreak.getPosition(); 782 addAreas(alg, optimalPageCount, blockList, blockList); 783 blockLists.clear(); 784 blockListIndex = -1; 785 LayoutManager restartAtLM = null; 786 if (positionAtBreak != null && positionAtBreak.getIndex() == -1) { 787 if (positionAtBreak instanceof ListItemLayoutManager.ListItemPosition) { 788 restartAtLM = positionAtBreak.getLM(); 789 } else { 790 Position position; 791 Iterator iter = blockList.listIterator(floatPosition + 1); 792 do { 793 KnuthElement nextElement = (KnuthElement) iter.next(); 794 position = nextElement.getPosition(); 795 } while (position == null || position instanceof SpaceResolver.SpaceHandlingPosition 796 || position instanceof SpaceResolver.SpaceHandlingBreakPosition 797 && position.getPosition().getIndex() == -1); 798 LayoutManager surroundingLM = positionAtBreak.getLM(); 799 while (position.getLM() != surroundingLM) { 800 position = position.getPosition(); 801 } 802 restartAtLM = position.getPosition().getLM(); 803 } 804 } 805 int nextSequenceStartsOn = getNextBlockList(childLC, Constants.EN_COLUMN, positionAtBreak, 806 restartAtLM, firstElements); 807 return nextSequenceStartsOn; 808 } 809 810 protected void addAreasForFloats(PageBreakingAlgorithm alg, int startPart, int partCount, 811 BlockSequence originalList, BlockSequence effectiveList, final LayoutContext childLC, 812 int lastBreak, int startElementIndex, int endElementIndex) { 813 FloatPosition pbp = alg.getFloatPosition(); 814 815 // Check the last break position for forced breaks 816 int lastBreakClass; 817 if (startElementIndex == 0) { 818 lastBreakClass = effectiveList.getStartOn(); 819 } else { 820 ListElement lastBreakElement = effectiveList.getElement(endElementIndex); 821 if (lastBreakElement.isPenalty()) { 822 KnuthPenalty pen = (KnuthPenalty) lastBreakElement; 823 if (pen.getPenalty() == KnuthPenalty.INFINITE) { 824 /** 825 * That means that there was a keep.within-page="always", but that 826 * it's OK to break at a column. TODO The break class is being 827 * abused to implement keep.within-column and keep.within-page. 828 * This is very misleading and must be revised. 829 */ 830 lastBreakClass = Constants.EN_COLUMN; 831 } else { 832 lastBreakClass = pen.getBreakClass(); 833 } 834 } else { 835 lastBreakClass = Constants.EN_COLUMN; 836 } 837 } 838 839 // the end of the new part 840 endElementIndex = pbp.getLeafPos(); 841 842 // ignore the first elements added by the 843 // PageSequenceLayoutManager 844 startElementIndex += (startElementIndex == 0) ? effectiveList.ignoreAtStart : 0; 845 846 log.debug("PLM> part: " + (startPart + partCount + 1) + ", start at pos " + startElementIndex 847 + ", break at pos " + endElementIndex + ", break class = " 848 + getBreakClassName(lastBreakClass)); 849 850 startPart(effectiveList, lastBreakClass, false); 851 852 int displayAlign = getCurrentDisplayAlign(); 853 854 // The following is needed by SpaceResolver.performConditionalsNotification() 855 // further down as there may be important Position elements in the element list trailer 856 int notificationEndElementIndex = endElementIndex; 857 858 // ignore the last elements added by the 859 // PageSequenceLayoutManager 860 endElementIndex -= (endElementIndex == (originalList.size() - 1)) ? effectiveList.ignoreAtEnd : 0; 861 862 // ignore the last element in the page if it is a KnuthGlue 863 // object 864 if (((KnuthElement) effectiveList.get(endElementIndex)).isGlue()) { 865 endElementIndex--; 866 } 867 868 // ignore KnuthGlue and KnuthPenalty objects 869 // at the beginning of the line 870 startElementIndex = alg.par.getFirstBoxIndex(startElementIndex); 871 872 if (startElementIndex <= endElementIndex) { 873 if (log.isDebugEnabled()) { 874 log.debug(" addAreas from " + startElementIndex + " to " + endElementIndex); 875 } 876 // set the space adjustment ratio 877 childLC.setSpaceAdjust(pbp.bpdAdjust); 878 // add space before if display-align is center or bottom 879 // add space after if display-align is distribute and 880 // this is not the last page 881 if (pbp.difference != 0 && displayAlign == Constants.EN_CENTER) { 882 childLC.setSpaceBefore(pbp.difference / 2); 883 } else if (pbp.difference != 0 && displayAlign == Constants.EN_AFTER) { 884 childLC.setSpaceBefore(pbp.difference); 885 } 886 887 // Handle SpaceHandling(Break)Positions, see SpaceResolver! 888 SpaceResolver.performConditionalsNotification(effectiveList, startElementIndex, 889 notificationEndElementIndex, lastBreak); 890 // Add areas of lines, in the current page, before the float or during float 891 addAreas(new KnuthPossPosIter(effectiveList, startElementIndex, endElementIndex + 1), childLC); 892 // add areas for the float, if applicable 893 if (alg.handlingStartOfFloat()) { 894 for (int k = startElementIndex; k < endElementIndex + 1; k++) { 895 ListElement le = effectiveList.getElement(k); 896 if (le instanceof KnuthBlockBox) { 897 KnuthBlockBox kbb = (KnuthBlockBox) le; 898 for (FloatContentLayoutManager fclm : kbb.getFloatContentLMs()) { 899 fclm.processAreas(childLC); 900 int floatHeight = fclm.getFloatHeight(); 901 int floatYOffset = fclm.getFloatYOffset(); 902 PageSequenceLayoutManager pslm = (PageSequenceLayoutManager) getTopLevelLM(); 903 pslm.recordStartOfFloat(floatHeight, floatYOffset); 904 } 905 } 906 } 907 } 908 if (alg.handlingEndOfFloat()) { 909 PageSequenceLayoutManager pslm = (PageSequenceLayoutManager) getTopLevelLM(); 910 pslm.setEndIntrusionAdjustment(0); 911 pslm.setStartIntrusionAdjustment(0); 912 int effectiveFloatHeight = alg.getFloatHeight(); 913 pslm.recordEndOfFloat(effectiveFloatHeight); 914 } 915 if (alg.handlingFloat()) { 916 PageSequenceLayoutManager pslm = (PageSequenceLayoutManager) getTopLevelLM(); 917 alg.relayFootnotes(pslm); 918 } 919 } else { 920 // no content for this part 921 handleEmptyContent(); 922 } 923 924 pageBreakHandled = true; 925 } 926 927 public void holdFootnotes(List fl, List ll, int tfl, int ifl, boolean fp, boolean nf, int fnfi, int fli, 928 int fei, MinOptMax fsl, int pfli, int pfei) { 929 relayedFootnotesList = fl; 930 relayedLengthList = ll; 931 relayedTotalFootnotesLength = tfl; 932 relayedInsertedFootnotesLength = ifl; 933 relayedFootnotesPending = fp; 934 relayedNewFootnotes = nf; 935 relayedFirstNewFootnoteIndex = fnfi; 936 relayedFootnoteListIndex = fli; 937 relayedFootnoteElementIndex = fei; 938 relayedFootnoteSeparatorLength = fsl; 939 previousFootnoteListIndex = pfli; 940 previousFootnoteElementIndex = pfei; 941 } 942 943 public void retrieveFootones(PageBreakingAlgorithm alg) { 944 if (relayedFootnotesList != null && relayedFootnotesList.size() > 0) { 945 alg.loadFootnotes(relayedFootnotesList, relayedLengthList, relayedTotalFootnotesLength, 946 relayedInsertedFootnotesLength, relayedFootnotesPending, relayedNewFootnotes, 947 relayedFirstNewFootnoteIndex, relayedFootnoteListIndex, relayedFootnoteElementIndex, 948 relayedFootnoteSeparatorLength, previousFootnoteListIndex, 949 previousFootnoteElementIndex); 950 relayedFootnotesList = null; 951 relayedLengthList = null; 952 relayedTotalFootnotesLength = 0; 953 relayedInsertedFootnotesLength = 0; 954 relayedFootnotesPending = false; 955 relayedNewFootnotes = false; 956 relayedFirstNewFootnoteIndex = 0; 957 relayedFootnoteListIndex = 0; 958 relayedFootnoteElementIndex = -1; 959 relayedFootnoteSeparatorLength = null; 960 } 961 } 962 } 963