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
9
* http://www.apache.org/licenses/LICENSE-2.0
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.
18
/* $Id: PageBreakingAlgorithm.java 554094 2007-07-07 00:04:25Z adelmelle $ */
20
package org.apache.fop.layoutmgr;
22
import java.util.ArrayList;
23
import java.util.LinkedList;
24
import java.util.ListIterator;
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;
33
import org.apache.fop.traits.MinOptMax;
35
class PageBreakingAlgorithm extends BreakingAlgorithm {
37
/** the logger for the class */
38
private static Log log = LogFactory.getLog(PageBreakingAlgorithm.class);
40
private LayoutManager topLevelLM;
41
private PageProvider pageProvider;
42
private PageBreakingLayoutListener layoutListener;
43
/** List of PageBreakPosition elements. */
44
private LinkedList pageBreaks = null;
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.
51
private ArrayList footnotesList = null;
52
/** Cumulated bpd of unhandled footnotes. */
53
private ArrayList lengthList = null;
54
/** Length of all the footnotes which will be put on the current page. */
55
private int totalFootnotesLength = 0;
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.
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;
65
* True if the elements met after the previous break point contain footnote citations.
67
private boolean newFootnotes = false;
69
* Index of the first footnote met after the previous break point.
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;
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;
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;
90
//Controls whether overflows should be warned about or not
91
private boolean autoHeight = false;
93
//Controls whether a single part should be forced if possible (ex. block-container)
94
private boolean favorSinglePart = false;
96
public PageBreakingAlgorithm(LayoutManager topLevelLM,
97
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;
113
this.autoHeight = autoHeight;
114
this.favorSinglePart = favorSinglePart;
118
* This class represents a feasible breaking point
119
* with extra information about footnotes.
121
protected class KnuthPageNode extends KnuthNode {
123
/** Additional length due to footnotes. */
124
public int totalFootnotes;
126
/** Index of the last inserted footnote. */
127
public int footnoteListIndex;
129
/** Index of the last inserted element of the last inserted footnote. */
130
public int footnoteElementIndex;
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;
149
* this class stores information about how the nodes
150
* which could start a line ending at the current element
152
protected class BestPageRecords extends BestRecords {
154
private int[] bestFootnotesLength = new int[4];
155
private int[] bestFootnoteListIndex = new int[4];
156
private int[] bestFootnoteElementIndex = new int[4];
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;
169
public int getFootnotesLength(int fitness) {
170
return bestFootnotesLength[fitness];
173
public int getFootnoteListIndex(int fitness) {
174
return bestFootnoteListIndex[fitness];
177
public int getFootnoteElementIndex(int fitness) {
178
return bestFootnoteElementIndex[fitness];
182
protected void initialize() {
184
insertedFootnotesLength = 0;
185
footnoteListIndex = 0;
186
footnoteElementIndex = -1;
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);
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));
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
217
protected void handleBox(KnuthBox box) {
218
if (box instanceof KnuthBlockBox
219
&& ((KnuthBlockBox) box).hasAnchors()) {
220
handleFootnotes(((KnuthBlockBox) box).getElementLists());
223
firstNewFootnoteIndex = footnotesList.size() - 1;
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
234
private void handleFootnotes(LinkedList elementLists) {
236
if (!footnotesPending) {
237
footnotesPending = true;
238
footnotesList = new ArrayList();
239
lengthList = new ArrayList();
240
totalFootnotesLength = 0;
244
firstNewFootnoteIndex = footnotesList.size();
247
// compute the total length of the footnotes
248
ListIterator elementListsIterator = elementLists.listIterator();
249
while (elementListsIterator.hasNext()) {
250
LinkedList noteList = (LinkedList) elementListsIterator.next();
252
//Space resolution (Note: this does not respect possible stacking constraints
253
//between footnotes!)
254
SpaceResolver.resolveElementList(noteList);
257
footnotesList.add(noteList);
258
ListIterator noteListIterator = noteList.listIterator();
259
while (noteListIterator.hasNext()) {
260
KnuthElement element = (KnuthElement) noteListIterator.next();
261
if (element.isBox() || element.isGlue()) {
262
noteLength += element.getW();
265
int prevLength = (lengthList.size() == 0
267
: ((Integer) lengthList.get(lengthList.size() - 1)).intValue());
268
lengthList.add(new Integer(prevLength + noteLength));
269
totalFootnotesLength += noteLength;
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());
290
private void resetFootnotes(LinkedList elementLists) {
291
for (int i = 0; i < elementLists.size(); i++) {
292
LinkedList removedList = (LinkedList) footnotesList.remove(footnotesList.size() - 1);
293
lengthList.remove(lengthList.size() - 1);
295
// update totalFootnotesLength
296
if (lengthList.size() > 0) {
297
totalFootnotesLength = ((Integer) lengthList.get(lengthList.size() - 1)).intValue();
299
totalFootnotesLength = 0;
302
// update footnotesPending;
303
if (footnotesList.size() == 0) {
304
footnotesPending = false;
308
protected void considerLegalBreak(KnuthElement element, int elementIdx) {
309
super.considerLegalBreak(element, elementIdx);
310
newFootnotes = false;
313
protected int computeDifference(KnuthNode activeNode, KnuthElement element,
315
KnuthPageNode pageNode = (KnuthPageNode) activeNode;
316
int actualWidth = totalWidth - pageNode.totalWidth;
318
boolean canDeferOldFootnotes;
319
if (element.isPenalty()) {
320
actualWidth += element.getW();
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) footnotesList.get(footnoteListIndex)).size() - 1;
336
} else if (((canDeferOldFootnotes = checkCanDeferOldFootnotes(pageNode, elementIndex))
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()
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) footnotesList.get(footnoteListIndex)).size() - 1;
362
// all footnotes have already been placed on previous pages
365
// there are no footnotes
367
return getLineWidth(activeNode.line) - actualWidth;
370
/** Checks whether footnotes from preceding pages may be deferred to the page after
372
* @param node active node for the preceding page break
373
* @param contentElementIndex index of the Knuth element considered for the
376
private boolean checkCanDeferOldFootnotes(KnuthPageNode node, int contentElementIndex) {
377
return (noBreakBetween(node.position, contentElementIndex)
378
&& deferredFootnotes(node.footnoteListIndex, node.footnoteElementIndex, node.totalFootnotes));
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
385
* @param breakIndex index of the currently considered breakpoint
386
* @return true if no element between the two can be a breakpoint
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
399
|| (prevBreakIndex <= storedPrevBreakIndex
400
&& breakIndex >= storedBreakIndex
402
// use the stored value, do nothing
404
// compute the new value
406
// ignore suppressed elements
407
for (index = prevBreakIndex + 1;
408
!par.getElement(index).isBox();
412
// find the next break
416
if (par.getElement(index).isGlue() && par.getElement(index - 1).isBox()
417
|| par.getElement(index).isPenalty()
418
&& ((KnuthElement) par.getElement(index)).getP() < KnuthElement.INFINITE) {
423
// update stored parameters and value
424
storedPrevBreakIndex = prevBreakIndex;
425
storedBreakIndex = breakIndex;
426
storedValue = (index == breakIndex);
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
435
* @param elementIndex index of the last element of the last inserted footnote
436
* @param length total length of all footnotes inserted so far
438
private boolean deferredFootnotes(int listIndex, int elementIndex, int length) {
439
return ((newFootnotes
440
&& firstNewFootnoteIndex != 0
441
&& (listIndex < firstNewFootnoteIndex - 1
442
|| elementIndex < ((LinkedList) footnotesList.get(listIndex)).size() - 1))
443
|| length < totalFootnotesLength);
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
452
private int getFootnoteSplit(KnuthPageNode activeNode, int availableLength, boolean canDeferOldFootnotes) {
453
return getFootnoteSplit(activeNode.footnoteListIndex,
454
activeNode.footnoteElementIndex,
455
activeNode.totalFootnotes,
456
availableLength, canDeferOldFootnotes);
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
467
private int getFootnoteSplit(int prevListIndex, int prevElementIndex, int prevLength,
468
int availableLength, boolean canDeferOldFootnotes) {
469
if (availableLength <= 0) {
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
476
ListIterator noteListIterator = null;
477
KnuthElement element = null;
478
boolean somethingAdded = false;
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) footnotesList.get(listIndex)).size() - 1) {
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
496
&& firstNewFootnoteIndex > 0) {
497
splitLength = ((Integer) lengthList.get(firstNewFootnoteIndex - 1)).intValue()
499
listIndex = firstNewFootnoteIndex;
502
// try adding the new footnotes
503
while (((Integer) lengthList.get(listIndex)).intValue() - prevLength
504
<= availableLength) {
505
splitLength = ((Integer) lengthList.get(listIndex)).intValue()
507
somethingAdded = true;
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
516
// try adding a split of the next note
517
noteListIterator = ((LinkedList) footnotesList.get(listIndex)).listIterator(elementIndex);
519
int prevSplitLength = 0;
523
while (!(somethingAdded && splitLength > availableLength)) {
524
if (!somethingAdded) {
525
somethingAdded = true;
527
prevSplitLength = splitLength;
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()) {
540
splitLength += element.getW();
542
} else if (element.isGlue()) {
545
// end of the sub-sequence
546
index = noteListIterator.previousIndex();
550
splitLength += element.getW();
552
// element is a penalty
553
if (element.getP() < KnuthElement.INFINITE) {
554
// end of the sub-sequence
555
index = noteListIterator.previousIndex();
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
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
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)
575
: ((LinkedList) footnotesList.get(footnoteListIndex)).size() - 1;
577
return prevSplitLength;
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;
589
if (maxAdjustment > 0) {
590
return (double) difference / maxAdjustment;
592
return INFINITE_RATIO;
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;
600
if (maxAdjustment > 0) {
601
return (double) difference / maxAdjustment;
603
return -INFINITE_RATIO;
610
protected double computeDemerits(KnuthNode activeNode, KnuthElement element,
611
int fitnessClass, double r) {
614
double f = Math.abs(r);
615
f = 1 + 100 * f * f * f;
616
if (element.isPenalty() && element.getP() >= 0) {
619
} else if (element.isPenalty() && !element.isForcedBreak()) {
620
double penalty = element.getP();
621
demerits = f * f - penalty * penalty;
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;
632
if (Math.abs(fitnessClass - activeNode.fitness) > 1) {
633
// add demerit for consecutive breaks
634
// with very different fitness classes
635
demerits += incompatibleFitnessDemerit;
638
if (footnotesPending) {
639
if (footnoteListIndex < footnotesList.size() - 1) {
640
// add demerits for the deferred footnotes
641
demerits += (footnotesList.size() - 1 - footnoteListIndex)
642
* deferredFootnoteDemerits;
644
if (footnoteElementIndex
645
< ((LinkedList) footnotesList.get(footnoteListIndex)).size() - 1) {
646
// add demerits for the footnote split between pages
647
demerits += splitFootnoteDemerits;
650
demerits += activeNode.totalDemerits;
654
protected void finish() {
655
for (int i = startLine; i < endLine; i++) {
656
for (KnuthPageNode node = (KnuthPageNode) getNode(i);
658
node = (KnuthPageNode) node.next) {
659
if (node.totalFootnotes < totalFootnotesLength) {
660
// layout remaining footnote bodies
661
createFootnotePages(node);
667
private void createFootnotePages(KnuthPageNode lastNode) {
668
insertedFootnotesLength = lastNode.totalFootnotes;
669
footnoteListIndex = lastNode.footnoteListIndex;
670
footnoteElementIndex = lastNode.footnoteElementIndex;
671
int availableBPD = getLineWidth();
673
KnuthPageNode prevNode = lastNode;
675
// create pages containing the remaining footnote bodies
676
while (insertedFootnotesLength < totalFootnotesLength) {
677
// try adding some more content
678
if (((Integer) lengthList.get(footnoteListIndex)).intValue() - insertedFootnotesLength
680
// add a whole footnote
681
availableBPD -= ((Integer) lengthList.get(footnoteListIndex)).intValue()
682
- insertedFootnotesLength;
683
insertedFootnotesLength = ((Integer)lengthList.get(footnoteListIndex)).intValue();
685
= ((LinkedList)footnotesList.get(footnoteListIndex)).size() - 1;
686
} else if ((split = getFootnoteSplit(footnoteListIndex, footnoteElementIndex,
687
insertedFootnotesLength, availableBPD, true))
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()
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,
702
addNode(node.line, node);
703
removeNode(prevNode.line, prevNode);
706
availableBPD = getLineWidth();
709
// create the last node
710
KnuthPageNode node = (KnuthPageNode)
711
createNode(lastNode.position, prevNode.line + 1, 1,
712
totalFootnotesLength - prevNode.totalFootnotes, 0, 0,
715
addNode(node.line, node);
716
removeNode(prevNode.line, prevNode);
720
* @return a list of PageBreakPosition elements
722
public LinkedList getPageBreaks() {
726
public void insertPageBreakAsFirst(PageBreakPosition pageBreak) {
727
if (pageBreaks == null) {
728
pageBreaks = new LinkedList();
730
pageBreaks.addFirst(pageBreak);
733
private int getPartCount() {
734
if (pageBreaks == null) {
737
return pageBreaks.size();
741
public void updateData1(int total, double demerits) {
744
public void updateData2(KnuthNode bestActiveNode,
745
KnuthSequence sequence,
747
//int difference = (bestActiveNode.line < total)
748
// ? bestActiveNode.difference : bestActiveNode.difference + fillerMinWidth;
749
int difference = bestActiveNode.difference;
750
if (difference + bestActiveNode.availableShrink < 0) {
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.",
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;
768
// page break with a negative difference:
769
// spaces always have enough shrink
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
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
779
difference -= bestActiveNode.availableStretch;
781
// last page with a positive difference:
782
// spaces do not need to stretch
783
if (blockAlignment != Constants.EN_JUSTIFY) {
786
//Stretch as much as possible on last page
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) footnotesList.get(firstListIndex)).size() - 1) {
795
// advance to the next list
797
firstElementIndex = 0;
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);
808
insertPageBreakAsFirst(new PageBreakPosition(this.topLevelLM,
809
bestActiveNode.position,
810
firstListIndex, firstElementIndex,
811
((KnuthPageNode) bestActiveNode).footnoteListIndex,
812
((KnuthPageNode) bestActiveNode).footnoteElementIndex,
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) {
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
828
bestActiveNode = compareNodes(bestActiveNode, node);
830
if (node != bestActiveNode) {
835
return bestActiveNode.line;
838
public LinkedList getFootnoteList(int index) {
839
return (LinkedList) footnotesList.get(index);
842
/** @return the associated top-level formatting object. */
843
public FObj getFObj() {
844
return topLevelLM.getFObj();
847
/** @see org.apache.fop.layoutmgr.BreakingAlgorithm#getLineWidth(int) */
848
protected int getLineWidth(int line) {
850
if (pageProvider != null) {
851
bpd = pageProvider.getAvailableBPD(line);
853
bpd = super.getLineWidth(line);
855
if (log.isTraceEnabled()) {
856
log.trace("getLineWidth(" + line + ") -> " + bpd);
862
* Interface to notify about layout events during page breaking.
864
public interface PageBreakingLayoutListener {
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
871
void notifyOverflow(int part, FObj obj);