~ubuntu-branches/ubuntu/karmic/batik/karmic

« back to all changes in this revision

Viewing changes to pdf-transcoder/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java

  • Committer: Bazaar Package Importer
  • Author(s): Matvey Kozhev, Onkar Shinde, Matvey Kozhev
  • Date: 2008-07-19 01:03:05 UTC
  • mfrom: (1.1.3 upstream)
  • Revision ID: james.westby@ubuntu.com-20080719010305-0b24skqy185kdsb9
Tags: 1.7.dfsg-0ubuntu1
[ Onkar Shinde ]
* New upstream version (LP: #147818)
* debian/control
  - Add Sun JDK 1.5 as build dependency. Fixes FTBFS on buildd. (LP: #150484)
    Also add Sun JRE as runtime dependencies.
  - Add ant-optional as build dependency.
  - Add libxml-commons-external-java and libxmlgraphics-commons-java as
    build and runtime dependencies.
  - Add 'Homepage' field and correct the url.
  - Change standards version to 3.8.0.
  - Modify Maintainer value to match the DebianMaintainerField
    specification.
* debian/rules
  - Change JAVA_HOME_DIRS for Sun JDK.
  - Add jar file names to DEB_JARS to match new build requirements.
  - Extract version from changelog.
  - Delete bundled jar files in clean target.
  - Don't use hardcoded version in install target.
  - Add get-orig-source target.
* debian/README.Debian-source
  - Change the fo tag name to the one used for this version.
* debian/watch
  - Change expression to match src distribution.
* debian/patches/
  - 01_build_xml.patch, 02_fix_jar_target.patch - Refresh for current source.
  - 03_fix_lib_dirs.patch - Fix the library and classpath references needed
    for pdf transcoder build.
  - 04_fix_transcoder_pkg.patch - Fix transcoder-pkg target to not copy
    files from other jar files.

[ Matvey Kozhev ]
* debian/changelog:
  - Added ".dfsg" to version.
* debian/control, debian/rules:
  - Build with openjdk-6-jdk, depend on openjdk-6-jre.
  - Added java-6-sun as an alternate build JAVA_HOME directory.
  - Fixed get-orig-source to not include debian/ and delete the source dir,
    and made it delete jars from lib/, as done in the current batik package.
* debian/wrappers.sh:
  - Changed java-7-icedtea reference to java-6-openjdk, as the former has been
    removed back in Hardy.
  - Added newline.
* debian/libbatik-java.install:
  - Added newline.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 554094 2007-07-07 00:04:25Z adelmelle $ */
 
19
 
 
20
package org.apache.fop.layoutmgr;
 
21
 
 
22
import java.util.ArrayList;
 
23
import java.util.LinkedList;
 
24
import java.util.ListIterator;
 
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 PageProvider pageProvider;
 
42
    private PageBreakingLayoutListener layoutListener;
 
43
    /** List of PageBreakPosition elements. */
 
44
    private LinkedList 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 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;
 
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
                                 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 elementLists) {
 
235
        // initialization
 
236
        if (!footnotesPending) {
 
237
            footnotesPending = true;
 
238
            footnotesList = new ArrayList();
 
239
            lengthList = new ArrayList();
 
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 elementListsIterator = elementLists.listIterator();
 
249
        while (elementListsIterator.hasNext()) {
 
250
            LinkedList noteList = (LinkedList) 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 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) lengthList.get(lengthList.size() - 1)).intValue());
 
268
            lengthList.add(new Integer(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 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);
 
294
 
 
295
            // update totalFootnotesLength
 
296
            if (lengthList.size() > 0) {
 
297
                totalFootnotesLength = ((Integer) 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) 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) 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) 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 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) 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) lengthList.get(firstNewFootnoteIndex - 1)).intValue()
 
498
                                  - prevLength;
 
499
                    listIndex = firstNewFootnoteIndex;
 
500
                    elementIndex = 0;
 
501
                }
 
502
                // try adding the new footnotes
 
503
                while (((Integer) lengthList.get(listIndex)).intValue() - prevLength
 
504
                       <= availableLength) {
 
505
                    splitLength = ((Integer) 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) 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) 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) 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) lengthList.get(footnoteListIndex)).intValue() - insertedFootnotesLength
 
679
                <= availableBPD) {
 
680
                // add a whole footnote
 
681
                availableBPD -= ((Integer) lengthList.get(footnoteListIndex)).intValue()
 
682
                                - insertedFootnotesLength;
 
683
                insertedFootnotesLength = ((Integer)lengthList.get(footnoteListIndex)).intValue();
 
684
                footnoteElementIndex
 
685
                    = ((LinkedList)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 getPageBreaks() {
 
723
        return pageBreaks;
 
724
    }
 
725
 
 
726
    public void insertPageBreakAsFirst(PageBreakPosition pageBreak) {
 
727
        if (pageBreaks == null) {
 
728
            pageBreaks = new LinkedList();
 
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) 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 getFootnoteList(int index) {
 
839
        return (LinkedList) 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
}