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