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