KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > fop > layoutmgr > PageBreakingAlgorithm


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: PageBreakingAlgorithm.java 453310 2006-10-05 18:44:15Z spepping $ */
19
20 package org.apache.fop.layoutmgr;
21
22 import java.util.ArrayList JavaDoc;
23 import java.util.LinkedList JavaDoc;
24 import java.util.ListIterator JavaDoc;
25
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.apache.fop.fo.Constants;
29 import org.apache.fop.fo.FONode;
30 import org.apache.fop.fo.FObj;
31 import org.apache.fop.layoutmgr.AbstractBreaker.PageBreakPosition;
32
33 import org.apache.fop.traits.MinOptMax;
34
35 class PageBreakingAlgorithm extends BreakingAlgorithm {
36
37     /** the logger for the class */
38     private static Log log = LogFactory.getLog(PageBreakingAlgorithm.class);
39
40     private LayoutManager topLevelLM;
41     private PageSequenceLayoutManager.PageProvider pageProvider;
42     private PageBreakingLayoutListener layoutListener;
43     /** List of PageBreakPosition elements. */
44     private LinkedList JavaDoc pageBreaks = null;
45
46     /** Footnotes which are cited between the currently considered active node (previous
47      * break) and the current considered break. Its type is
48      * List<List<KnuthElement>>, it contains the sequences of KnuthElement
49      * representing the footnotes bodies.
50      */

51     private ArrayList JavaDoc footnotesList = null;
52     /** Cumulated bpd of unhandled footnotes. */
53     private ArrayList JavaDoc lengthList = null;
54     /** Length of all the footnotes which will be put on the current page. */
55     private int totalFootnotesLength = 0;
56     /**
57      * Length of all the footnotes which have already been inserted, up to the currently
58      * considered element. That is, footnotes from the currently considered page plus
59      * footnotes from its preceding pages.
60      */

61     private int insertedFootnotesLength = 0;
62     /** True if footnote citations have been met since the beginning of the page sequence. */
63     private boolean footnotesPending = false;
64     /**
65      * True if the elements met after the previous break point contain footnote citations.
66      */

67     private boolean newFootnotes = false;
68     /**
69      * Index of the first footnote met after the previous break point.
70      */

71     private int firstNewFootnoteIndex = 0;
72     /** Index of the last footnote inserted on the current page. */
73     private int footnoteListIndex = 0;
74     /** Index of the last element of the last footnote inserted on the current page. */
75     private int footnoteElementIndex = -1;
76
77     // demerits for a page break that splits a footnote
78
private int splitFootnoteDemerits = 5000;
79     // demerits for a page break that defers a whole footnote to the following page
80
private int deferredFootnoteDemerits = 10000;
81     private MinOptMax footnoteSeparatorLength = null;
82
83     // the method noBreakBetween(int, int) uses these variables
84
// to store parameters and result of the last call, in order
85
// to reuse them and take less time
86
private int storedPrevBreakIndex = -1;
87     private int storedBreakIndex = -1;
88     private boolean storedValue = false;
89
90     //Controls whether overflows should be warned about or not
91
private boolean autoHeight = false;
92     
93     //Controls whether a single part should be forced if possible (ex. block-container)
94
private boolean favorSinglePart = false;
95     
96     public PageBreakingAlgorithm(LayoutManager topLevelLM,
97                                  PageSequenceLayoutManager.PageProvider pageProvider,
98                                  PageBreakingLayoutListener layoutListener,
99                                  int alignment, int alignmentLast,
100                                  MinOptMax footnoteSeparatorLength,
101                                  boolean partOverflowRecovery, boolean autoHeight,
102                                  boolean favorSinglePart) {
103         super(alignment, alignmentLast, true, partOverflowRecovery, 0);
104         this.topLevelLM = topLevelLM;
105         this.pageProvider = pageProvider;
106         this.layoutListener = layoutListener;
107         best = new BestPageRecords();
108         this.footnoteSeparatorLength = (MinOptMax) footnoteSeparatorLength.clone();
109         // add some stretch, to avoid a restart for every page containing footnotes
110
if (footnoteSeparatorLength.min == footnoteSeparatorLength.max) {
111             footnoteSeparatorLength.max += 10000;
112         }
113         this.autoHeight = autoHeight;
114         this.favorSinglePart = favorSinglePart;
115     }
116
117     /**
118      * This class represents a feasible breaking point
119      * with extra information about footnotes.
120      */

121     protected class KnuthPageNode extends KnuthNode {
122
123         /** Additional length due to footnotes. */
124         public int totalFootnotes;
125
126         /** Index of the last inserted footnote. */
127         public int footnoteListIndex;
128
129         /** Index of the last inserted element of the last inserted footnote. */
130         public int footnoteElementIndex;
131
132         public KnuthPageNode(int position, int line, int fitness,
133                              int totalWidth, int totalStretch, int totalShrink,
134                              int totalFootnotes, int footnoteListIndex, int footnoteElementIndex,
135                              double adjustRatio, int availableShrink, int availableStretch,
136                              int difference, double totalDemerits, KnuthNode previous) {
137             super(position, line, fitness,
138                   totalWidth, totalStretch, totalShrink,
139                   adjustRatio, availableShrink, availableStretch,
140                   difference, totalDemerits, previous);
141             this.totalFootnotes = totalFootnotes;
142             this.footnoteListIndex = footnoteListIndex;
143             this.footnoteElementIndex = footnoteElementIndex;
144         }
145
146     }
147
148     /**
149      * this class stores information about how the nodes
150      * which could start a line ending at the current element
151      */

152     protected class BestPageRecords extends BestRecords {
153
154         private int[] bestFootnotesLength = new int[4];
155         private int[] bestFootnoteListIndex = new int[4];
156         private int[] bestFootnoteElementIndex = new int[4];
157
158         public void addRecord(double demerits, KnuthNode node, double adjust,
159                               int availableShrink, int availableStretch,
160                               int difference, int fitness) {
161             super.addRecord(demerits, node, adjust,
162                             availableShrink, availableStretch,
163                             difference, fitness);
164             bestFootnotesLength[fitness] = insertedFootnotesLength;
165             bestFootnoteListIndex[fitness] = footnoteListIndex;
166             bestFootnoteElementIndex[fitness] = footnoteElementIndex;
167         }
168
169         public int getFootnotesLength(int fitness) {
170             return bestFootnotesLength[fitness];
171         }
172
173         public int getFootnoteListIndex(int fitness) {
174             return bestFootnoteListIndex[fitness];
175         }
176
177         public int getFootnoteElementIndex(int fitness) {
178             return bestFootnoteElementIndex[fitness];
179         }
180     }
181
182     protected void initialize() {
183         super.initialize();
184         insertedFootnotesLength = 0;
185         footnoteListIndex = 0;
186         footnoteElementIndex = -1;
187     }
188
189     protected KnuthNode createNode(int position, int line, int fitness,
190                                    int totalWidth, int totalStretch, int totalShrink,
191                                    double adjustRatio, int availableShrink, int availableStretch,
192                                    int difference, double totalDemerits, KnuthNode previous) {
193         return new KnuthPageNode(position, line, fitness,
194                                  totalWidth, totalStretch, totalShrink,
195                                  insertedFootnotesLength, footnoteListIndex, footnoteElementIndex,
196                                  adjustRatio, availableShrink, availableStretch,
197                                  difference, totalDemerits, previous);
198     }
199
200     protected KnuthNode createNode(int position, int line, int fitness,
201                                    int totalWidth, int totalStretch, int totalShrink) {
202         return new KnuthPageNode(position, line, fitness,
203                                  totalWidth, totalStretch, totalShrink,
204                                  ((BestPageRecords) best).getFootnotesLength(fitness),
205                                  ((BestPageRecords) best).getFootnoteListIndex(fitness),
206                                  ((BestPageRecords) best).getFootnoteElementIndex(fitness),
207                                  best.getAdjust(fitness), best.getAvailableShrink(fitness),
208                                  best.getAvailableStretch(fitness), best.getDifference(fitness),
209                                  best.getDemerits(fitness), best.getNode(fitness));
210     }
211
212     /**
213      * Page-breaking specific handling of the given box. Currently it adds the footnotes
214      * cited in the given box to the list of to-be-handled footnotes.
215      * @param box a block-level element possibly containing foonotes citations
216      */

217     protected void handleBox(KnuthBox box) {
218         if (box instanceof KnuthBlockBox
219             && ((KnuthBlockBox) box).hasAnchors()) {
220             handleFootnotes(((KnuthBlockBox) box).getElementLists());
221             if (!newFootnotes) {
222                 newFootnotes = true;
223                 firstNewFootnoteIndex = footnotesList.size() - 1;
224             }
225         }
226     }
227
228     /**
229      * Handles the footnotes cited inside a block-level box. Updates footnotesList and the
230      * value of totalFootnotesLength with the lengths of the given footnotes.
231      * @param elementLists list of KnuthElement sequences corresponding to the footnotes
232      * bodies
233      */

234     private void handleFootnotes(LinkedList JavaDoc elementLists) {
235         // initialization
236
if (!footnotesPending) {
237             footnotesPending = true;
238             footnotesList = new ArrayList JavaDoc();
239             lengthList = new ArrayList JavaDoc();
240             totalFootnotesLength = 0;
241         }
242         if (!newFootnotes) {
243             newFootnotes = true;
244             firstNewFootnoteIndex = footnotesList.size();
245         }
246
247         // compute the total length of the footnotes
248
ListIterator JavaDoc elementListsIterator = elementLists.listIterator();
249         while (elementListsIterator.hasNext()) {
250             LinkedList JavaDoc noteList = (LinkedList JavaDoc) elementListsIterator.next();
251             
252             //Space resolution (Note: this does not respect possible stacking constraints
253
//between footnotes!)
254
SpaceResolver.resolveElementList(noteList);
255             
256             int noteLength = 0;
257             footnotesList.add(noteList);
258             ListIterator JavaDoc noteListIterator = noteList.listIterator();
259             while (noteListIterator.hasNext()) {
260                 KnuthElement element = (KnuthElement) noteListIterator.next();
261                 if (element.isBox() || element.isGlue()) {
262                     noteLength += element.getW();
263                 }
264             }
265             int prevLength = (lengthList.size() == 0
266                     ? 0
267                     : ((Integer JavaDoc) lengthList.get(lengthList.size() - 1)).intValue());
268             lengthList.add(new Integer JavaDoc(prevLength + noteLength));
269             totalFootnotesLength += noteLength;
270         }
271     }
272
273     protected int restartFrom(KnuthNode restartingNode, int currentIndex) {
274         int returnValue = super.restartFrom(restartingNode, currentIndex);
275         newFootnotes = false;
276         if (footnotesPending) {
277             // remove from footnotesList the note lists that will be met
278
// after the restarting point
279
for (int j = currentIndex; j >= restartingNode.position; j--) {
280                 KnuthElement resettedElement = getElement(j);
281                 if (resettedElement instanceof KnuthBlockBox
282                     && ((KnuthBlockBox) resettedElement).hasAnchors()) {
283                     resetFootnotes(((KnuthBlockBox) resettedElement).getElementLists());
284                 }
285             }
286         }
287         return returnValue;
288     }
289
290     private void resetFootnotes(LinkedList JavaDoc elementLists) {
291         for (int i = 0; i < elementLists.size(); i++) {
292             LinkedList JavaDoc removedList = (LinkedList JavaDoc) footnotesList.remove(footnotesList.size() - 1);
293             lengthList.remove(lengthList.size() - 1);
294
295             // update totalFootnotesLength
296
if (lengthList.size() > 0) {
297                 totalFootnotesLength = ((Integer JavaDoc) lengthList.get(lengthList.size() - 1)).intValue();
298             } else {
299                 totalFootnotesLength = 0;
300             }
301         }
302         // update footnotesPending;
303
if (footnotesList.size() == 0) {
304             footnotesPending = false;
305         }
306     }
307
308     protected void considerLegalBreak(KnuthElement element, int elementIdx) {
309         super.considerLegalBreak(element, elementIdx);
310         newFootnotes = false;
311     }
312
313     protected int computeDifference(KnuthNode activeNode, KnuthElement element,
314                                     int elementIndex) {
315         KnuthPageNode pageNode = (KnuthPageNode) activeNode;
316         int actualWidth = totalWidth - pageNode.totalWidth;
317         int footnoteSplit;
318         boolean canDeferOldFootnotes;
319         if (element.isPenalty()) {
320             actualWidth += element.getW();
321         }
322         if (footnotesPending) {
323             // compute the total length of the footnotes not yet inserted
324
int allFootnotes = totalFootnotesLength - pageNode.totalFootnotes;
325             if (allFootnotes > 0) {
326                 // this page contains some footnote citations
327
// add the footnote separator width
328
actualWidth += footnoteSeparatorLength.opt;
329                 if (actualWidth + allFootnotes <= getLineWidth()) {
330                     // there is enough space to insert all footnotes:
331
// add the whole allFootnotes length
332
actualWidth += allFootnotes;
333                     insertedFootnotesLength = pageNode.totalFootnotes + allFootnotes;
334                     footnoteListIndex = footnotesList.size() - 1;
335                     footnoteElementIndex = ((LinkedList JavaDoc) footnotesList.get(footnoteListIndex)).size() - 1;
336                 } else if (((canDeferOldFootnotes = checkCanDeferOldFootnotes(pageNode, elementIndex))
337                             || newFootnotes)
338                            && (footnoteSplit = getFootnoteSplit(pageNode, getLineWidth() - actualWidth,
339                                                                 canDeferOldFootnotes)) > 0) {
340                     // it is allowed to break or even defer footnotes if either:
341
// - there are new footnotes in the last piece of content, and
342
// there is space to add at least a piece of the first one
343
// - or the previous page break deferred some footnote lines, and
344
// this is the first feasible break; in this case it is allowed
345
// to break and defer, if necessary, old and new footnotes
346
actualWidth += footnoteSplit;
347                     insertedFootnotesLength = pageNode.totalFootnotes + footnoteSplit;
348                     // footnoteListIndex has been set in getFootnoteSplit()
349
// footnoteElementIndex has been set in getFootnoteSplit()
350
} else {
351                     // there is no space to add the smallest piece of footnote,
352
// or we are trying to add a piece of content with no footnotes and
353
// it does not fit in the page, because of previous footnote bodies
354
// that cannot be broken:
355
// add the whole allFootnotes length, so this breakpoint will be discarded
356
actualWidth += allFootnotes;
357                     insertedFootnotesLength = pageNode.totalFootnotes + allFootnotes;
358                     footnoteListIndex = footnotesList.size() - 1;
359                     footnoteElementIndex = ((LinkedList JavaDoc) footnotesList.get(footnoteListIndex)).size() - 1;
360                 }
361             } else {
362                 // all footnotes have already been placed on previous pages
363
}
364         } else {
365             // there are no footnotes
366
}
367         return getLineWidth(activeNode.line) - actualWidth;
368     }
369
370     /** Checks whether footnotes from preceding pages may be deferred to the page after
371      * the given element.
372      * @param node active node for the preceding page break
373      * @param contentElementIndex index of the Knuth element considered for the
374      * current page break
375      */

376     private boolean checkCanDeferOldFootnotes(KnuthPageNode node, int contentElementIndex) {
377         return (noBreakBetween(node.position, contentElementIndex)
378                 && deferredFootnotes(node.footnoteListIndex, node.footnoteElementIndex, node.totalFootnotes));
379     }
380
381     /**
382      * Returns true if there may be no breakpoint between the two given elements.
383      * @param prevBreakIndex index of the element from the currently considered active
384      * node
385      * @param breakIndex index of the currently considered breakpoint
386      * @return true if no element between the two can be a breakpoint
387      */

388     private boolean noBreakBetween(int prevBreakIndex, int breakIndex) {
389         // this method stores the parameters and the return value from previous calls
390
// in order to avoid scanning the element list unnecessarily:
391
// - if there is no break between element #i and element #j
392
// there will not be a break between #(i+h) and #j too
393
// - if there is a break between element #i and element #j
394
// there will be a break between #(i-h) and #(j+k) too
395
if (storedPrevBreakIndex != -1
396             && ((prevBreakIndex >= storedPrevBreakIndex
397                  && breakIndex == storedBreakIndex
398                  && storedValue)
399                 || (prevBreakIndex <= storedPrevBreakIndex
400                     && breakIndex >= storedBreakIndex
401                     && !storedValue))) {
402             // use the stored value, do nothing
403
} else {
404             // compute the new value
405
int index;
406             // ignore suppressed elements
407
for (index = prevBreakIndex + 1;
408                     !par.getElement(index).isBox();
409                     index++) {
410                 //nop
411
}
412             // find the next break
413
for (;
414                  index < breakIndex;
415                  index++) {
416                 if (par.getElement(index).isGlue() && par.getElement(index - 1).isBox()
417                     || par.getElement(index).isPenalty()
418                        && ((KnuthElement) par.getElement(index)).getP() < KnuthElement.INFINITE) {
419                     // break found
420
break;
421                 }
422             }
423             // update stored parameters and value
424
storedPrevBreakIndex = prevBreakIndex;
425             storedBreakIndex = breakIndex;
426             storedValue = (index == breakIndex);
427         }
428         return storedValue;
429     }
430
431     /**
432      * Returns true if their are (pieces of) footnotes to be typeset on the current page.
433      * @param listIndex index of the last inserted footnote for the currently considered
434      * active node
435      * @param elementIndex index of the last element of the last inserted footnote
436      * @param length total length of all footnotes inserted so far
437      */

438     private boolean deferredFootnotes(int listIndex, int elementIndex, int length) {
439         return ((newFootnotes
440                  && firstNewFootnoteIndex != 0
441                  && (listIndex < firstNewFootnoteIndex - 1
442                      || elementIndex < ((LinkedList JavaDoc) footnotesList.get(listIndex)).size() - 1))
443                 || length < totalFootnotesLength);
444     }
445
446     /**
447      * Tries to split the flow of footnotes to put one part on the current page.
448      * @param activeNode currently considered previous page break
449      * @param availableLength available space for footnotes
450      * @param canDeferOldFootnotes
451      */

452     private int getFootnoteSplit(KnuthPageNode activeNode, int availableLength, boolean canDeferOldFootnotes) {
453         return getFootnoteSplit(activeNode.footnoteListIndex,
454                                 activeNode.footnoteElementIndex,
455                                 activeNode.totalFootnotes,
456                                 availableLength, canDeferOldFootnotes);
457     }
458
459     /**
460      * Tries to split the flow of footnotes to put one part on the current page.
461      * @param prevListIndex index of the last footnote on the previous page
462      * @param prevElementIndex index of the last element of the last footnote
463      * @param prevLength total length of footnotes inserted so far
464      * @param availableLength available space for footnotes on this page
465      * @param canDeferOldFootnotes
466      */

467     private int getFootnoteSplit(int prevListIndex, int prevElementIndex, int prevLength,
468                                  int availableLength, boolean canDeferOldFootnotes) {
469         if (availableLength <= 0) {
470             return 0;
471         } else {
472             // the split should contain a piece of the last footnote
473
// together with all previous, not yet inserted footnotes;
474
// but if this is not possible, try adding as much content as possible
475
int splitLength = 0;
476             ListIterator JavaDoc noteListIterator = null;
477             KnuthElement element = null;
478             boolean somethingAdded = false;
479
480             // prevListIndex and prevElementIndex points to the last footnote element
481
// already placed in a page: advance to the next element
482
int listIndex = prevListIndex;
483             int elementIndex = prevElementIndex;
484             if (elementIndex == ((LinkedList JavaDoc) footnotesList.get(listIndex)).size() - 1) {
485                 listIndex++;
486                 elementIndex = 0;
487             } else {
488                 elementIndex++;
489             }
490
491             // try adding whole notes
492
if (footnotesList.size() - 1 > listIndex) {
493                 // add the previous footnotes: these cannot be broken or deferred
494
if (!canDeferOldFootnotes
495                     && newFootnotes
496                     && firstNewFootnoteIndex > 0) {
497                     splitLength = ((Integer JavaDoc) lengthList.get(firstNewFootnoteIndex - 1)).intValue()
498                                   - prevLength;
499                     listIndex = firstNewFootnoteIndex;
500                     elementIndex = 0;
501                 }
502                 // try adding the new footnotes
503
while (((Integer JavaDoc) lengthList.get(listIndex)).intValue() - prevLength
504                        <= availableLength) {
505                     splitLength = ((Integer JavaDoc) lengthList.get(listIndex)).intValue()
506                                   - prevLength;
507                     somethingAdded = true;
508                     listIndex++;
509                     elementIndex = 0;
510                 }
511                 // as this method is called only if it is not possible to insert
512
// all footnotes, at this point listIndex and elementIndex points to
513
// an existing element, the next one we will try to insert
514
}
515
516             // try adding a split of the next note
517
noteListIterator = ((LinkedList JavaDoc) footnotesList.get(listIndex)).listIterator(elementIndex);
518
519             int prevSplitLength = 0;
520             int prevIndex = -1;
521             int index = -1;
522
523             while (!(somethingAdded && splitLength > availableLength)) {
524                 if (!somethingAdded) {
525                     somethingAdded = true;
526                 } else {
527                     prevSplitLength = splitLength;
528                     prevIndex = index;
529                 }
530                 // get a sub-sequence from the note element list
531
boolean bPrevIsBox = false;
532                 while (noteListIterator.hasNext()) {
533                     // as this method is called only if it is not possible to insert
534
// all footnotes, and we have already tried (and failed) to insert
535
// this whole footnote, the while loop will never reach the end
536
// of the note sequence
537
element = (KnuthElement) noteListIterator.next();
538                     if (element.isBox()) {
539                         // element is a box
540
splitLength += element.getW();
541                         bPrevIsBox = true;
542                     } else if (element.isGlue()) {
543                         // element is a glue
544
if (bPrevIsBox) {
545                             // end of the sub-sequence
546
index = noteListIterator.previousIndex();
547                             break;
548                         }
549                         bPrevIsBox = false;
550                         splitLength += element.getW();
551                     } else {
552                         // element is a penalty
553
if (element.getP() < KnuthElement.INFINITE) {
554                             // end of the sub-sequence
555
index = noteListIterator.previousIndex();
556                             break;
557                         }
558                     }
559                 }
560             }
561             // if prevSplitLength is 0, this means that the available length isn't enough
562
// to insert even the smallest split of the last footnote, so we cannot end a
563
// page here
564
// if prevSplitLength is > 0 we can insert some footnote content in this page
565
// and insert the remaining in the following one
566
if (!somethingAdded) {
567                 // there was not enough space to add a piece of the first new footnote
568
// this is not a good break
569
prevSplitLength = 0;
570             } else if (prevSplitLength > 0) {
571                 // prevIndex is -1 if we have added only some whole footnotes
572
footnoteListIndex = (prevIndex != -1) ? listIndex : listIndex - 1;
573                 footnoteElementIndex = (prevIndex != -1)
574                     ? prevIndex
575                     : ((LinkedList JavaDoc) footnotesList.get(footnoteListIndex)).size() - 1;
576             }
577             return prevSplitLength;
578         }
579     }
580
581     protected double computeAdjustmentRatio(KnuthNode activeNode, int difference) {
582         // compute the adjustment ratio
583
if (difference > 0) {
584             int maxAdjustment = totalStretch - activeNode.totalStretch;
585             // add the footnote separator stretch if some footnote content will be added
586
if (((KnuthPageNode) activeNode).totalFootnotes < totalFootnotesLength) {
587                 maxAdjustment += footnoteSeparatorLength.max - footnoteSeparatorLength.opt;
588             }
589             if (maxAdjustment > 0) {
590                 return (double) difference / maxAdjustment;
591             } else {
592                 return INFINITE_RATIO;
593             }
594         } else if (difference < 0) {
595             int maxAdjustment = totalShrink - activeNode.totalShrink;
596             // add the footnote separator shrink if some footnote content will be added
597
if (((KnuthPageNode) activeNode).totalFootnotes < totalFootnotesLength) {
598                 maxAdjustment += footnoteSeparatorLength.opt - footnoteSeparatorLength.min;
599             }
600             if (maxAdjustment > 0) {
601                 return (double) difference / maxAdjustment;
602             } else {
603                 return -INFINITE_RATIO;
604             }
605         } else {
606             return 0;
607         }
608     }
609
610     protected double computeDemerits(KnuthNode activeNode, KnuthElement element,
611                                     int fitnessClass, double r) {
612         double demerits = 0;
613         // compute demerits
614
double f = Math.abs(r);
615         f = 1 + 100 * f * f * f;
616         if (element.isPenalty() && element.getP() >= 0) {
617             f += element.getP();
618             demerits = f * f;
619         } else if (element.isPenalty() && !element.isForcedBreak()) {
620             double penalty = element.getP();
621             demerits = f * f - penalty * penalty;
622         } else {
623             demerits = f * f;
624         }
625
626         if (element.isPenalty() && ((KnuthPenalty) element).isFlagged()
627             && getElement(activeNode.position).isPenalty()
628             && ((KnuthPenalty) getElement(activeNode.position)).isFlagged()) {
629             // add demerit for consecutive breaks at flagged penalties
630
demerits += repeatedFlaggedDemerit;
631         }
632         if (Math.abs(fitnessClass - activeNode.fitness) > 1) {
633             // add demerit for consecutive breaks
634
// with very different fitness classes
635
demerits += incompatibleFitnessDemerit;
636         }
637
638         if (footnotesPending) {
639             if (footnoteListIndex < footnotesList.size() - 1) {
640                 // add demerits for the deferred footnotes
641
demerits += (footnotesList.size() - 1 - footnoteListIndex)
642                                 * deferredFootnoteDemerits;
643             }
644             if (footnoteElementIndex
645                     < ((LinkedList JavaDoc) footnotesList.get(footnoteListIndex)).size() - 1) {
646                 // add demerits for the footnote split between pages
647
demerits += splitFootnoteDemerits;
648             }
649         }
650         demerits += activeNode.totalDemerits;
651         return demerits;
652     }
653
654     protected void finish() {
655         for (int i = startLine; i < endLine; i++) {
656             for (KnuthPageNode node = (KnuthPageNode) getNode(i);
657                  node != null;
658                  node = (KnuthPageNode) node.next) {
659                 if (node.totalFootnotes < totalFootnotesLength) {
660                     // layout remaining footnote bodies
661
createFootnotePages(node);
662                 }
663             }
664         }
665     }
666
667     private void createFootnotePages(KnuthPageNode lastNode) {
668         insertedFootnotesLength = lastNode.totalFootnotes;
669         footnoteListIndex = lastNode.footnoteListIndex;
670         footnoteElementIndex = lastNode.footnoteElementIndex;
671         int availableBPD = getLineWidth();
672         int split = 0;
673         KnuthPageNode prevNode = lastNode;
674
675         // create pages containing the remaining footnote bodies
676
while (insertedFootnotesLength < totalFootnotesLength) {
677             // try adding some more content
678
if (((Integer JavaDoc) lengthList.get(footnoteListIndex)).intValue() - insertedFootnotesLength
679                 <= availableBPD) {
680                 // add a whole footnote
681
availableBPD -= ((Integer JavaDoc) lengthList.get(footnoteListIndex)).intValue()
682                                 - insertedFootnotesLength;
683                 insertedFootnotesLength = ((Integer JavaDoc)lengthList.get(footnoteListIndex)).intValue();
684                 footnoteElementIndex
685                     = ((LinkedList JavaDoc)footnotesList.get(footnoteListIndex)).size() - 1;
686             } else if ((split = getFootnoteSplit(footnoteListIndex, footnoteElementIndex,
687                                                  insertedFootnotesLength, availableBPD, true))
688                        > 0) {
689                 // add a piece of a footnote
690
availableBPD -= split;
691                 insertedFootnotesLength += split;
692                 // footnoteListIndex has already been set in getFootnoteSplit()
693
// footnoteElementIndex has already been set in getFootnoteSplit()
694
} else {
695                 // cannot add any content: create a new node and start again
696
KnuthPageNode node = (KnuthPageNode)
697                                      createNode(lastNode.position, prevNode.line + 1, 1,
698                                                 insertedFootnotesLength - prevNode.totalFootnotes,
699                                                 0, 0,
700                                                 0, 0, 0,
701                                                 0, 0, prevNode);
702                 addNode(node.line, node);
703                 removeNode(prevNode.line, prevNode);
704
705                 prevNode = node;
706                 availableBPD = getLineWidth();
707             }
708         }
709         // create the last node
710
KnuthPageNode node = (KnuthPageNode)
711                              createNode(lastNode.position, prevNode.line + 1, 1,
712                                         totalFootnotesLength - prevNode.totalFootnotes, 0, 0,
713                                         0, 0, 0,
714                                         0, 0, prevNode);
715         addNode(node.line, node);
716         removeNode(prevNode.line, prevNode);
717     }
718
719     /**
720      * @return a list of PageBreakPosition elements
721      */

722     public LinkedList JavaDoc getPageBreaks() {
723         return pageBreaks;
724     }
725
726     public void insertPageBreakAsFirst(PageBreakPosition pageBreak) {
727         if (pageBreaks == null) {
728             pageBreaks = new LinkedList JavaDoc();
729         }
730         pageBreaks.addFirst(pageBreak);
731     }
732     
733     private int getPartCount() {
734         if (pageBreaks == null) {
735             return 0;
736         } else {
737             return pageBreaks.size();
738         }
739     }
740     
741     public void updateData1(int total, double demerits) {
742     }
743
744     public void updateData2(KnuthNode bestActiveNode,
745                             KnuthSequence sequence,
746                             int total) {
747         //int difference = (bestActiveNode.line < total)
748
// ? bestActiveNode.difference : bestActiveNode.difference + fillerMinWidth;
749
int difference = bestActiveNode.difference;
750         if (difference + bestActiveNode.availableShrink < 0) {
751             if (!autoHeight) {
752                 if (layoutListener != null) {
753                     layoutListener.notifyOverflow(bestActiveNode.line - 1, getFObj());
754                 } else if (log.isWarnEnabled()) {
755                     log.warn(FONode.decorateWithContextInfo(
756                             "Part/page " + (bestActiveNode.line - 1)
757                             + " overflows the available area in block-progression dimension.",
758                             getFObj()));
759                 }
760             }
761         }
762         boolean isNonLastPage = (bestActiveNode.line < total);
763         int blockAlignment = isNonLastPage ? alignment : alignmentLast;
764         // it is always allowed to adjust space, so the ratio must be set regardless of
765
// the value of the property display-align; the ratio must be <= 1
766
double ratio = bestActiveNode.adjustRatio;
767         if (ratio < 0) {
768             // page break with a negative difference:
769
// spaces always have enough shrink
770
difference = 0;
771         } else if (ratio <= 1 && isNonLastPage) {
772             // not-last page break with a positive difference smaller than the available stretch:
773
// spaces can stretch to fill the whole difference
774
difference = 0;
775         } else if (ratio > 1) {
776             // not-last page with a positive difference greater than the available stretch
777
// spaces can stretch to fill the difference only partially
778
ratio = 1;
779             difference -= bestActiveNode.availableStretch;
780         } else {
781             // last page with a positive difference:
782
// spaces do not need to stretch
783
if (blockAlignment != Constants.EN_JUSTIFY) {
784                 ratio = 0;
785             } else {
786                 //Stretch as much as possible on last page
787
difference = 0;
788             }
789         }
790         // compute the indexes of the first footnote list and the first element in that list
791
int firstListIndex = ((KnuthPageNode) bestActiveNode.previous).footnoteListIndex;
792         int firstElementIndex = ((KnuthPageNode) bestActiveNode.previous).footnoteElementIndex;
793         if (footnotesList != null
794             && firstElementIndex == ((LinkedList JavaDoc) footnotesList.get(firstListIndex)).size() - 1) {
795             // advance to the next list
796
firstListIndex++;
797             firstElementIndex = 0;
798         } else {
799             firstElementIndex++;
800         }
801
802         // add nodes at the beginning of the list, as they are found
803
// backwards, from the last one to the first one
804
if (log.isDebugEnabled()) {
805             log.debug("BBA> difference=" + difference + " ratio=" + ratio
806                     + " position=" + bestActiveNode.position);
807         }
808         insertPageBreakAsFirst(new PageBreakPosition(this.topLevelLM,
809                 bestActiveNode.position,
810                 firstListIndex, firstElementIndex,
811                 ((KnuthPageNode) bestActiveNode).footnoteListIndex,
812                 ((KnuthPageNode) bestActiveNode).footnoteElementIndex,
813                 ratio, difference));
814     }
815
816     protected int filterActiveNodes() {
817         // leave only the active node with fewest total demerits
818
KnuthNode bestActiveNode = null;
819         for (int i = startLine; i < endLine; i++) {
820             for (KnuthNode node = getNode(i); node != null; node = node.next) {
821                 if (favorSinglePart
822                         && node.line > 1
823                         && bestActiveNode != null
824                         && Math.abs(bestActiveNode.difference) < bestActiveNode.availableShrink) {
825                     //favor current best node, so just skip the current node because it would
826
//result in more than one part
827
} else {
828                     bestActiveNode = compareNodes(bestActiveNode, node);
829                 }
830                 if (node != bestActiveNode) {
831                     removeNode(i, node);
832                 }
833             }
834         }
835         return bestActiveNode.line;
836     }
837
838     public LinkedList JavaDoc getFootnoteList(int index) {
839         return (LinkedList JavaDoc) footnotesList.get(index);
840     }
841     
842     /** @return the associated top-level formatting object. */
843     public FObj getFObj() {
844         return topLevelLM.getFObj();
845     }
846     
847     /** @see org.apache.fop.layoutmgr.BreakingAlgorithm#getLineWidth(int) */
848     protected int getLineWidth(int line) {
849         int bpd;
850         if (pageProvider != null) {
851             bpd = pageProvider.getAvailableBPD(line);
852         } else {
853             bpd = super.getLineWidth(line);
854         }
855         if (log.isTraceEnabled()) {
856             log.trace("getLineWidth(" + line + ") -> " + bpd);
857         }
858         return bpd;
859     }
860     
861     /**
862      * Interface to notify about layout events during page breaking.
863      */

864     public interface PageBreakingLayoutListener {
865
866         /**
867          * Issued when an overflow is detected
868          * @param part the number of the part (page) this happens on
869          * @param obj the root FO object where this happens
870          */

871         void notifyOverflow(int part, FObj obj);
872         
873     }
874     
875 }
876
Popular Tags