001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /* $Id: PageBreakingAlgorithm.java 554094 2007-07-07 00:04:25Z adelmelle $ */
019:
020: package org.apache.fop.layoutmgr;
021:
022: import java.util.ArrayList;
023: import java.util.LinkedList;
024: import java.util.ListIterator;
025:
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028: import org.apache.fop.fo.Constants;
029: import org.apache.fop.fo.FONode;
030: import org.apache.fop.fo.FObj;
031: import org.apache.fop.layoutmgr.AbstractBreaker.PageBreakPosition;
032:
033: import org.apache.fop.traits.MinOptMax;
034:
035: class PageBreakingAlgorithm extends BreakingAlgorithm {
036:
037: /** the logger for the class */
038: private static Log log = LogFactory
039: .getLog(PageBreakingAlgorithm.class);
040:
041: private LayoutManager topLevelLM;
042: private PageProvider pageProvider;
043: private PageBreakingLayoutListener layoutListener;
044: /** List of PageBreakPosition elements. */
045: private LinkedList pageBreaks = null;
046:
047: /** Footnotes which are cited between the currently considered active node (previous
048: * break) and the current considered break. Its type is
049: * List<List<KnuthElement>>, it contains the sequences of KnuthElement
050: * representing the footnotes bodies.
051: */
052: private ArrayList footnotesList = null;
053: /** Cumulated bpd of unhandled footnotes. */
054: private ArrayList lengthList = null;
055: /** Length of all the footnotes which will be put on the current page. */
056: private int totalFootnotesLength = 0;
057: /**
058: * Length of all the footnotes which have already been inserted, up to the currently
059: * considered element. That is, footnotes from the currently considered page plus
060: * footnotes from its preceding pages.
061: */
062: private int insertedFootnotesLength = 0;
063: /** True if footnote citations have been met since the beginning of the page sequence. */
064: private boolean footnotesPending = false;
065: /**
066: * True if the elements met after the previous break point contain footnote citations.
067: */
068: private boolean newFootnotes = false;
069: /**
070: * Index of the first footnote met after the previous break point.
071: */
072: private int firstNewFootnoteIndex = 0;
073: /** Index of the last footnote inserted on the current page. */
074: private int footnoteListIndex = 0;
075: /** Index of the last element of the last footnote inserted on the current page. */
076: private int footnoteElementIndex = -1;
077:
078: // demerits for a page break that splits a footnote
079: private int splitFootnoteDemerits = 5000;
080: // demerits for a page break that defers a whole footnote to the following page
081: private int deferredFootnoteDemerits = 10000;
082: private MinOptMax footnoteSeparatorLength = null;
083:
084: // the method noBreakBetween(int, int) uses these variables
085: // to store parameters and result of the last call, in order
086: // to reuse them and take less time
087: private int storedPrevBreakIndex = -1;
088: private int storedBreakIndex = -1;
089: private boolean storedValue = false;
090:
091: //Controls whether overflows should be warned about or not
092: private boolean autoHeight = false;
093:
094: //Controls whether a single part should be forced if possible (ex. block-container)
095: private boolean favorSinglePart = false;
096:
097: public PageBreakingAlgorithm(LayoutManager topLevelLM,
098: PageProvider pageProvider,
099: PageBreakingLayoutListener layoutListener, int alignment,
100: int alignmentLast, 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
109: .clone();
110: // add some stretch, to avoid a restart for every page containing footnotes
111: if (footnoteSeparatorLength.min == footnoteSeparatorLength.max) {
112: footnoteSeparatorLength.max += 10000;
113: }
114: this .autoHeight = autoHeight;
115: this .favorSinglePart = favorSinglePart;
116: }
117:
118: /**
119: * This class represents a feasible breaking point
120: * with extra information about footnotes.
121: */
122: protected class KnuthPageNode extends KnuthNode {
123:
124: /** Additional length due to footnotes. */
125: public int totalFootnotes;
126:
127: /** Index of the last inserted footnote. */
128: public int footnoteListIndex;
129:
130: /** Index of the last inserted element of the last inserted footnote. */
131: public int footnoteElementIndex;
132:
133: public KnuthPageNode(int position, int line, int fitness,
134: int totalWidth, int totalStretch, int totalShrink,
135: int totalFootnotes, int footnoteListIndex,
136: int footnoteElementIndex, double adjustRatio,
137: int availableShrink, int availableStretch,
138: int difference, double totalDemerits, KnuthNode previous) {
139: super (position, line, fitness, totalWidth, totalStretch,
140: totalShrink, adjustRatio, availableShrink,
141: availableStretch, difference, totalDemerits,
142: previous);
143: this .totalFootnotes = totalFootnotes;
144: this .footnoteListIndex = footnoteListIndex;
145: this .footnoteElementIndex = footnoteElementIndex;
146: }
147:
148: }
149:
150: /**
151: * this class stores information about how the nodes
152: * which could start a line ending at the current element
153: */
154: protected class BestPageRecords extends BestRecords {
155:
156: private int[] bestFootnotesLength = new int[4];
157: private int[] bestFootnoteListIndex = new int[4];
158: private int[] bestFootnoteElementIndex = new int[4];
159:
160: public void addRecord(double demerits, KnuthNode node,
161: double adjust, int availableShrink,
162: int availableStretch, int difference, int fitness) {
163: super .addRecord(demerits, node, adjust, availableShrink,
164: availableStretch, difference, fitness);
165: bestFootnotesLength[fitness] = insertedFootnotesLength;
166: bestFootnoteListIndex[fitness] = footnoteListIndex;
167: bestFootnoteElementIndex[fitness] = footnoteElementIndex;
168: }
169:
170: public int getFootnotesLength(int fitness) {
171: return bestFootnotesLength[fitness];
172: }
173:
174: public int getFootnoteListIndex(int fitness) {
175: return bestFootnoteListIndex[fitness];
176: }
177:
178: public int getFootnoteElementIndex(int fitness) {
179: return bestFootnoteElementIndex[fitness];
180: }
181: }
182:
183: protected void initialize() {
184: super .initialize();
185: insertedFootnotesLength = 0;
186: footnoteListIndex = 0;
187: footnoteElementIndex = -1;
188: }
189:
190: protected KnuthNode createNode(int position, int line, int fitness,
191: int totalWidth, int totalStretch, int totalShrink,
192: double adjustRatio, int availableShrink,
193: int availableStretch, int difference, double totalDemerits,
194: KnuthNode previous) {
195: return new KnuthPageNode(position, line, fitness, totalWidth,
196: totalStretch, totalShrink, insertedFootnotesLength,
197: footnoteListIndex, footnoteElementIndex, adjustRatio,
198: availableShrink, availableStretch, difference,
199: totalDemerits, previous);
200: }
201:
202: protected KnuthNode createNode(int position, int line, int fitness,
203: int totalWidth, int totalStretch, int totalShrink) {
204: return new KnuthPageNode(position, line, fitness, totalWidth,
205: totalStretch, totalShrink, ((BestPageRecords) best)
206: .getFootnotesLength(fitness),
207: ((BestPageRecords) best).getFootnoteListIndex(fitness),
208: ((BestPageRecords) best)
209: .getFootnoteElementIndex(fitness), best
210: .getAdjust(fitness), best
211: .getAvailableShrink(fitness), best
212: .getAvailableStretch(fitness), best
213: .getDifference(fitness), best
214: .getDemerits(fitness), best.getNode(fitness));
215: }
216:
217: /**
218: * Page-breaking specific handling of the given box. Currently it adds the footnotes
219: * cited in the given box to the list of to-be-handled footnotes.
220: * @param box a block-level element possibly containing foonotes citations
221: */
222: protected void handleBox(KnuthBox box) {
223: if (box instanceof KnuthBlockBox
224: && ((KnuthBlockBox) box).hasAnchors()) {
225: handleFootnotes(((KnuthBlockBox) box).getElementLists());
226: if (!newFootnotes) {
227: newFootnotes = true;
228: firstNewFootnoteIndex = footnotesList.size() - 1;
229: }
230: }
231: }
232:
233: /**
234: * Handles the footnotes cited inside a block-level box. Updates footnotesList and the
235: * value of totalFootnotesLength with the lengths of the given footnotes.
236: * @param elementLists list of KnuthElement sequences corresponding to the footnotes
237: * bodies
238: */
239: private void handleFootnotes(LinkedList elementLists) {
240: // initialization
241: if (!footnotesPending) {
242: footnotesPending = true;
243: footnotesList = new ArrayList();
244: lengthList = new ArrayList();
245: totalFootnotesLength = 0;
246: }
247: if (!newFootnotes) {
248: newFootnotes = true;
249: firstNewFootnoteIndex = footnotesList.size();
250: }
251:
252: // compute the total length of the footnotes
253: ListIterator elementListsIterator = elementLists.listIterator();
254: while (elementListsIterator.hasNext()) {
255: LinkedList noteList = (LinkedList) elementListsIterator
256: .next();
257:
258: //Space resolution (Note: this does not respect possible stacking constraints
259: //between footnotes!)
260: SpaceResolver.resolveElementList(noteList);
261:
262: int noteLength = 0;
263: footnotesList.add(noteList);
264: ListIterator noteListIterator = noteList.listIterator();
265: while (noteListIterator.hasNext()) {
266: KnuthElement element = (KnuthElement) noteListIterator
267: .next();
268: if (element.isBox() || element.isGlue()) {
269: noteLength += element.getW();
270: }
271: }
272: int prevLength = (lengthList.size() == 0 ? 0
273: : ((Integer) lengthList.get(lengthList.size() - 1))
274: .intValue());
275: lengthList.add(new Integer(prevLength + noteLength));
276: totalFootnotesLength += noteLength;
277: }
278: }
279:
280: protected int restartFrom(KnuthNode restartingNode, int currentIndex) {
281: int returnValue = super .restartFrom(restartingNode,
282: currentIndex);
283: newFootnotes = false;
284: if (footnotesPending) {
285: // remove from footnotesList the note lists that will be met
286: // after the restarting point
287: for (int j = currentIndex; j >= restartingNode.position; j--) {
288: KnuthElement resettedElement = getElement(j);
289: if (resettedElement instanceof KnuthBlockBox
290: && ((KnuthBlockBox) resettedElement)
291: .hasAnchors()) {
292: resetFootnotes(((KnuthBlockBox) resettedElement)
293: .getElementLists());
294: }
295: }
296: }
297: return returnValue;
298: }
299:
300: private void resetFootnotes(LinkedList elementLists) {
301: for (int i = 0; i < elementLists.size(); i++) {
302: LinkedList removedList = (LinkedList) footnotesList
303: .remove(footnotesList.size() - 1);
304: lengthList.remove(lengthList.size() - 1);
305:
306: // update totalFootnotesLength
307: if (lengthList.size() > 0) {
308: totalFootnotesLength = ((Integer) lengthList
309: .get(lengthList.size() - 1)).intValue();
310: } else {
311: totalFootnotesLength = 0;
312: }
313: }
314: // update footnotesPending;
315: if (footnotesList.size() == 0) {
316: footnotesPending = false;
317: }
318: }
319:
320: protected void considerLegalBreak(KnuthElement element,
321: int elementIdx) {
322: super .considerLegalBreak(element, elementIdx);
323: newFootnotes = false;
324: }
325:
326: protected int computeDifference(KnuthNode activeNode,
327: KnuthElement element, int elementIndex) {
328: KnuthPageNode pageNode = (KnuthPageNode) activeNode;
329: int actualWidth = totalWidth - pageNode.totalWidth;
330: int footnoteSplit;
331: boolean canDeferOldFootnotes;
332: if (element.isPenalty()) {
333: actualWidth += element.getW();
334: }
335: if (footnotesPending) {
336: // compute the total length of the footnotes not yet inserted
337: int allFootnotes = totalFootnotesLength
338: - pageNode.totalFootnotes;
339: if (allFootnotes > 0) {
340: // this page contains some footnote citations
341: // add the footnote separator width
342: actualWidth += footnoteSeparatorLength.opt;
343: if (actualWidth + allFootnotes <= getLineWidth()) {
344: // there is enough space to insert all footnotes:
345: // add the whole allFootnotes length
346: actualWidth += allFootnotes;
347: insertedFootnotesLength = pageNode.totalFootnotes
348: + allFootnotes;
349: footnoteListIndex = footnotesList.size() - 1;
350: footnoteElementIndex = ((LinkedList) footnotesList
351: .get(footnoteListIndex)).size() - 1;
352: } else if (((canDeferOldFootnotes = checkCanDeferOldFootnotes(
353: pageNode, elementIndex)) || newFootnotes)
354: && (footnoteSplit = getFootnoteSplit(pageNode,
355: getLineWidth() - actualWidth,
356: canDeferOldFootnotes)) > 0) {
357: // it is allowed to break or even defer footnotes if either:
358: // - there are new footnotes in the last piece of content, and
359: // there is space to add at least a piece of the first one
360: // - or the previous page break deferred some footnote lines, and
361: // this is the first feasible break; in this case it is allowed
362: // to break and defer, if necessary, old and new footnotes
363: actualWidth += footnoteSplit;
364: insertedFootnotesLength = pageNode.totalFootnotes
365: + footnoteSplit;
366: // footnoteListIndex has been set in getFootnoteSplit()
367: // footnoteElementIndex has been set in getFootnoteSplit()
368: } else {
369: // there is no space to add the smallest piece of footnote,
370: // or we are trying to add a piece of content with no footnotes and
371: // it does not fit in the page, because of previous footnote bodies
372: // that cannot be broken:
373: // add the whole allFootnotes length, so this breakpoint will be discarded
374: actualWidth += allFootnotes;
375: insertedFootnotesLength = pageNode.totalFootnotes
376: + allFootnotes;
377: footnoteListIndex = footnotesList.size() - 1;
378: footnoteElementIndex = ((LinkedList) footnotesList
379: .get(footnoteListIndex)).size() - 1;
380: }
381: } else {
382: // all footnotes have already been placed on previous pages
383: }
384: } else {
385: // there are no footnotes
386: }
387: return getLineWidth(activeNode.line) - actualWidth;
388: }
389:
390: /** Checks whether footnotes from preceding pages may be deferred to the page after
391: * the given element.
392: * @param node active node for the preceding page break
393: * @param contentElementIndex index of the Knuth element considered for the
394: * current page break
395: */
396: private boolean checkCanDeferOldFootnotes(KnuthPageNode node,
397: int contentElementIndex) {
398: return (noBreakBetween(node.position, contentElementIndex) && deferredFootnotes(
399: node.footnoteListIndex, node.footnoteElementIndex,
400: node.totalFootnotes));
401: }
402:
403: /**
404: * Returns true if there may be no breakpoint between the two given elements.
405: * @param prevBreakIndex index of the element from the currently considered active
406: * node
407: * @param breakIndex index of the currently considered breakpoint
408: * @return true if no element between the two can be a breakpoint
409: */
410: private boolean noBreakBetween(int prevBreakIndex, int breakIndex) {
411: // this method stores the parameters and the return value from previous calls
412: // in order to avoid scanning the element list unnecessarily:
413: // - if there is no break between element #i and element #j
414: // there will not be a break between #(i+h) and #j too
415: // - if there is a break between element #i and element #j
416: // there will be a break between #(i-h) and #(j+k) too
417: if (storedPrevBreakIndex != -1
418: && ((prevBreakIndex >= storedPrevBreakIndex
419: && breakIndex == storedBreakIndex && storedValue) || (prevBreakIndex <= storedPrevBreakIndex
420: && breakIndex >= storedBreakIndex && !storedValue))) {
421: // use the stored value, do nothing
422: } else {
423: // compute the new value
424: int index;
425: // ignore suppressed elements
426: for (index = prevBreakIndex + 1; !par.getElement(index)
427: .isBox(); index++) {
428: //nop
429: }
430: // find the next break
431: for (; index < breakIndex; index++) {
432: if (par.getElement(index).isGlue()
433: && par.getElement(index - 1).isBox()
434: || par.getElement(index).isPenalty()
435: && ((KnuthElement) par.getElement(index))
436: .getP() < KnuthElement.INFINITE) {
437: // break found
438: break;
439: }
440: }
441: // update stored parameters and value
442: storedPrevBreakIndex = prevBreakIndex;
443: storedBreakIndex = breakIndex;
444: storedValue = (index == breakIndex);
445: }
446: return storedValue;
447: }
448:
449: /**
450: * Returns true if their are (pieces of) footnotes to be typeset on the current page.
451: * @param listIndex index of the last inserted footnote for the currently considered
452: * active node
453: * @param elementIndex index of the last element of the last inserted footnote
454: * @param length total length of all footnotes inserted so far
455: */
456: private boolean deferredFootnotes(int listIndex, int elementIndex,
457: int length) {
458: return ((newFootnotes && firstNewFootnoteIndex != 0 && (listIndex < firstNewFootnoteIndex - 1 || elementIndex < ((LinkedList) footnotesList
459: .get(listIndex)).size() - 1)) || length < totalFootnotesLength);
460: }
461:
462: /**
463: * Tries to split the flow of footnotes to put one part on the current page.
464: * @param activeNode currently considered previous page break
465: * @param availableLength available space for footnotes
466: * @param canDeferOldFootnotes
467: */
468: private int getFootnoteSplit(KnuthPageNode activeNode,
469: int availableLength, boolean canDeferOldFootnotes) {
470: return getFootnoteSplit(activeNode.footnoteListIndex,
471: activeNode.footnoteElementIndex,
472: activeNode.totalFootnotes, availableLength,
473: canDeferOldFootnotes);
474: }
475:
476: /**
477: * Tries to split the flow of footnotes to put one part on the current page.
478: * @param prevListIndex index of the last footnote on the previous page
479: * @param prevElementIndex index of the last element of the last footnote
480: * @param prevLength total length of footnotes inserted so far
481: * @param availableLength available space for footnotes on this page
482: * @param canDeferOldFootnotes
483: */
484: private int getFootnoteSplit(int prevListIndex,
485: int prevElementIndex, int prevLength, int availableLength,
486: boolean canDeferOldFootnotes) {
487: if (availableLength <= 0) {
488: return 0;
489: } else {
490: // the split should contain a piece of the last footnote
491: // together with all previous, not yet inserted footnotes;
492: // but if this is not possible, try adding as much content as possible
493: int splitLength = 0;
494: ListIterator noteListIterator = null;
495: KnuthElement element = null;
496: boolean somethingAdded = false;
497:
498: // prevListIndex and prevElementIndex points to the last footnote element
499: // already placed in a page: advance to the next element
500: int listIndex = prevListIndex;
501: int elementIndex = prevElementIndex;
502: if (elementIndex == ((LinkedList) footnotesList
503: .get(listIndex)).size() - 1) {
504: listIndex++;
505: elementIndex = 0;
506: } else {
507: elementIndex++;
508: }
509:
510: // try adding whole notes
511: if (footnotesList.size() - 1 > listIndex) {
512: // add the previous footnotes: these cannot be broken or deferred
513: if (!canDeferOldFootnotes && newFootnotes
514: && firstNewFootnoteIndex > 0) {
515: splitLength = ((Integer) lengthList
516: .get(firstNewFootnoteIndex - 1)).intValue()
517: - prevLength;
518: listIndex = firstNewFootnoteIndex;
519: elementIndex = 0;
520: }
521: // try adding the new footnotes
522: while (((Integer) lengthList.get(listIndex)).intValue()
523: - prevLength <= availableLength) {
524: splitLength = ((Integer) lengthList.get(listIndex))
525: .intValue()
526: - prevLength;
527: somethingAdded = true;
528: listIndex++;
529: elementIndex = 0;
530: }
531: // as this method is called only if it is not possible to insert
532: // all footnotes, at this point listIndex and elementIndex points to
533: // an existing element, the next one we will try to insert
534: }
535:
536: // try adding a split of the next note
537: noteListIterator = ((LinkedList) footnotesList
538: .get(listIndex)).listIterator(elementIndex);
539:
540: int prevSplitLength = 0;
541: int prevIndex = -1;
542: int index = -1;
543:
544: while (!(somethingAdded && splitLength > availableLength)) {
545: if (!somethingAdded) {
546: somethingAdded = true;
547: } else {
548: prevSplitLength = splitLength;
549: prevIndex = index;
550: }
551: // get a sub-sequence from the note element list
552: boolean bPrevIsBox = false;
553: while (noteListIterator.hasNext()) {
554: // as this method is called only if it is not possible to insert
555: // all footnotes, and we have already tried (and failed) to insert
556: // this whole footnote, the while loop will never reach the end
557: // of the note sequence
558: element = (KnuthElement) noteListIterator.next();
559: if (element.isBox()) {
560: // element is a box
561: splitLength += element.getW();
562: bPrevIsBox = true;
563: } else if (element.isGlue()) {
564: // element is a glue
565: if (bPrevIsBox) {
566: // end of the sub-sequence
567: index = noteListIterator.previousIndex();
568: break;
569: }
570: bPrevIsBox = false;
571: splitLength += element.getW();
572: } else {
573: // element is a penalty
574: if (element.getP() < KnuthElement.INFINITE) {
575: // end of the sub-sequence
576: index = noteListIterator.previousIndex();
577: break;
578: }
579: }
580: }
581: }
582: // if prevSplitLength is 0, this means that the available length isn't enough
583: // to insert even the smallest split of the last footnote, so we cannot end a
584: // page here
585: // if prevSplitLength is > 0 we can insert some footnote content in this page
586: // and insert the remaining in the following one
587: if (!somethingAdded) {
588: // there was not enough space to add a piece of the first new footnote
589: // this is not a good break
590: prevSplitLength = 0;
591: } else if (prevSplitLength > 0) {
592: // prevIndex is -1 if we have added only some whole footnotes
593: footnoteListIndex = (prevIndex != -1) ? listIndex
594: : listIndex - 1;
595: footnoteElementIndex = (prevIndex != -1) ? prevIndex
596: : ((LinkedList) footnotesList
597: .get(footnoteListIndex)).size() - 1;
598: }
599: return prevSplitLength;
600: }
601: }
602:
603: protected double computeAdjustmentRatio(KnuthNode activeNode,
604: int difference) {
605: // compute the adjustment ratio
606: if (difference > 0) {
607: int maxAdjustment = totalStretch - activeNode.totalStretch;
608: // add the footnote separator stretch if some footnote content will be added
609: if (((KnuthPageNode) activeNode).totalFootnotes < totalFootnotesLength) {
610: maxAdjustment += footnoteSeparatorLength.max
611: - footnoteSeparatorLength.opt;
612: }
613: if (maxAdjustment > 0) {
614: return (double) difference / maxAdjustment;
615: } else {
616: return INFINITE_RATIO;
617: }
618: } else if (difference < 0) {
619: int maxAdjustment = totalShrink - activeNode.totalShrink;
620: // add the footnote separator shrink if some footnote content will be added
621: if (((KnuthPageNode) activeNode).totalFootnotes < totalFootnotesLength) {
622: maxAdjustment += footnoteSeparatorLength.opt
623: - footnoteSeparatorLength.min;
624: }
625: if (maxAdjustment > 0) {
626: return (double) difference / maxAdjustment;
627: } else {
628: return -INFINITE_RATIO;
629: }
630: } else {
631: return 0;
632: }
633: }
634:
635: protected double computeDemerits(KnuthNode activeNode,
636: KnuthElement element, int fitnessClass, double r) {
637: double demerits = 0;
638: // compute demerits
639: double f = Math.abs(r);
640: f = 1 + 100 * f * f * f;
641: if (element.isPenalty() && element.getP() >= 0) {
642: f += element.getP();
643: demerits = f * f;
644: } else if (element.isPenalty() && !element.isForcedBreak()) {
645: double penalty = element.getP();
646: demerits = f * f - penalty * penalty;
647: } else {
648: demerits = f * f;
649: }
650:
651: if (element.isPenalty()
652: && ((KnuthPenalty) element).isFlagged()
653: && getElement(activeNode.position).isPenalty()
654: && ((KnuthPenalty) getElement(activeNode.position))
655: .isFlagged()) {
656: // add demerit for consecutive breaks at flagged penalties
657: demerits += repeatedFlaggedDemerit;
658: }
659: if (Math.abs(fitnessClass - activeNode.fitness) > 1) {
660: // add demerit for consecutive breaks
661: // with very different fitness classes
662: demerits += incompatibleFitnessDemerit;
663: }
664:
665: if (footnotesPending) {
666: if (footnoteListIndex < footnotesList.size() - 1) {
667: // add demerits for the deferred footnotes
668: demerits += (footnotesList.size() - 1 - footnoteListIndex)
669: * deferredFootnoteDemerits;
670: }
671: if (footnoteElementIndex < ((LinkedList) footnotesList
672: .get(footnoteListIndex)).size() - 1) {
673: // add demerits for the footnote split between pages
674: demerits += splitFootnoteDemerits;
675: }
676: }
677: demerits += activeNode.totalDemerits;
678: return demerits;
679: }
680:
681: protected void finish() {
682: for (int i = startLine; i < endLine; i++) {
683: for (KnuthPageNode node = (KnuthPageNode) getNode(i); node != null; node = (KnuthPageNode) node.next) {
684: if (node.totalFootnotes < totalFootnotesLength) {
685: // layout remaining footnote bodies
686: createFootnotePages(node);
687: }
688: }
689: }
690: }
691:
692: private void createFootnotePages(KnuthPageNode lastNode) {
693: insertedFootnotesLength = lastNode.totalFootnotes;
694: footnoteListIndex = lastNode.footnoteListIndex;
695: footnoteElementIndex = lastNode.footnoteElementIndex;
696: int availableBPD = getLineWidth();
697: int split = 0;
698: KnuthPageNode prevNode = lastNode;
699:
700: // create pages containing the remaining footnote bodies
701: while (insertedFootnotesLength < totalFootnotesLength) {
702: // try adding some more content
703: if (((Integer) lengthList.get(footnoteListIndex))
704: .intValue()
705: - insertedFootnotesLength <= availableBPD) {
706: // add a whole footnote
707: availableBPD -= ((Integer) lengthList
708: .get(footnoteListIndex)).intValue()
709: - insertedFootnotesLength;
710: insertedFootnotesLength = ((Integer) lengthList
711: .get(footnoteListIndex)).intValue();
712: footnoteElementIndex = ((LinkedList) footnotesList
713: .get(footnoteListIndex)).size() - 1;
714: } else if ((split = getFootnoteSplit(footnoteListIndex,
715: footnoteElementIndex, insertedFootnotesLength,
716: availableBPD, true)) > 0) {
717: // add a piece of a footnote
718: availableBPD -= split;
719: insertedFootnotesLength += split;
720: // footnoteListIndex has already been set in getFootnoteSplit()
721: // footnoteElementIndex has already been set in getFootnoteSplit()
722: } else {
723: // cannot add any content: create a new node and start again
724: KnuthPageNode node = (KnuthPageNode) createNode(
725: lastNode.position, prevNode.line + 1, 1,
726: insertedFootnotesLength
727: - prevNode.totalFootnotes, 0, 0, 0, 0,
728: 0, 0, 0, prevNode);
729: addNode(node.line, node);
730: removeNode(prevNode.line, prevNode);
731:
732: prevNode = node;
733: availableBPD = getLineWidth();
734: }
735: }
736: // create the last node
737: KnuthPageNode node = (KnuthPageNode) createNode(
738: lastNode.position, prevNode.line + 1, 1,
739: totalFootnotesLength - prevNode.totalFootnotes, 0, 0,
740: 0, 0, 0, 0, 0, prevNode);
741: addNode(node.line, node);
742: removeNode(prevNode.line, prevNode);
743: }
744:
745: /**
746: * @return a list of PageBreakPosition elements
747: */
748: public LinkedList getPageBreaks() {
749: return pageBreaks;
750: }
751:
752: public void insertPageBreakAsFirst(PageBreakPosition pageBreak) {
753: if (pageBreaks == null) {
754: pageBreaks = new LinkedList();
755: }
756: pageBreaks.addFirst(pageBreak);
757: }
758:
759: private int getPartCount() {
760: if (pageBreaks == null) {
761: return 0;
762: } else {
763: return pageBreaks.size();
764: }
765: }
766:
767: public void updateData1(int total, double demerits) {
768: }
769:
770: public void updateData2(KnuthNode bestActiveNode,
771: KnuthSequence sequence, int total) {
772: //int difference = (bestActiveNode.line < total)
773: // ? bestActiveNode.difference : bestActiveNode.difference + fillerMinWidth;
774: int difference = bestActiveNode.difference;
775: if (difference + bestActiveNode.availableShrink < 0) {
776: if (!autoHeight) {
777: if (layoutListener != null) {
778: layoutListener.notifyOverflow(
779: bestActiveNode.line - 1, getFObj());
780: } else if (log.isWarnEnabled()) {
781: log
782: .warn(FONode
783: .decorateWithContextInfo(
784: "Part/page "
785: + (bestActiveNode.line - 1)
786: + " overflows the available area in block-progression dimension.",
787: getFObj()));
788: }
789: }
790: }
791: boolean isNonLastPage = (bestActiveNode.line < total);
792: int blockAlignment = isNonLastPage ? alignment : alignmentLast;
793: // it is always allowed to adjust space, so the ratio must be set regardless of
794: // the value of the property display-align; the ratio must be <= 1
795: double ratio = bestActiveNode.adjustRatio;
796: if (ratio < 0) {
797: // page break with a negative difference:
798: // spaces always have enough shrink
799: difference = 0;
800: } else if (ratio <= 1 && isNonLastPage) {
801: // not-last page break with a positive difference smaller than the available stretch:
802: // spaces can stretch to fill the whole difference
803: difference = 0;
804: } else if (ratio > 1) {
805: // not-last page with a positive difference greater than the available stretch
806: // spaces can stretch to fill the difference only partially
807: ratio = 1;
808: difference -= bestActiveNode.availableStretch;
809: } else {
810: // last page with a positive difference:
811: // spaces do not need to stretch
812: if (blockAlignment != Constants.EN_JUSTIFY) {
813: ratio = 0;
814: } else {
815: //Stretch as much as possible on last page
816: difference = 0;
817: }
818: }
819: // compute the indexes of the first footnote list and the first element in that list
820: int firstListIndex = ((KnuthPageNode) bestActiveNode.previous).footnoteListIndex;
821: int firstElementIndex = ((KnuthPageNode) bestActiveNode.previous).footnoteElementIndex;
822: if (footnotesList != null
823: && firstElementIndex == ((LinkedList) footnotesList
824: .get(firstListIndex)).size() - 1) {
825: // advance to the next list
826: firstListIndex++;
827: firstElementIndex = 0;
828: } else {
829: firstElementIndex++;
830: }
831:
832: // add nodes at the beginning of the list, as they are found
833: // backwards, from the last one to the first one
834: if (log.isDebugEnabled()) {
835: log.debug("BBA> difference=" + difference + " ratio="
836: + ratio + " position=" + bestActiveNode.position);
837: }
838: insertPageBreakAsFirst(new PageBreakPosition(this .topLevelLM,
839: bestActiveNode.position, firstListIndex,
840: firstElementIndex,
841: ((KnuthPageNode) bestActiveNode).footnoteListIndex,
842: ((KnuthPageNode) bestActiveNode).footnoteElementIndex,
843: ratio, difference));
844: }
845:
846: protected int filterActiveNodes() {
847: // leave only the active node with fewest total demerits
848: KnuthNode bestActiveNode = null;
849: for (int i = startLine; i < endLine; i++) {
850: for (KnuthNode node = getNode(i); node != null; node = node.next) {
851: if (favorSinglePart
852: && node.line > 1
853: && bestActiveNode != null
854: && Math.abs(bestActiveNode.difference) < bestActiveNode.availableShrink) {
855: //favor current best node, so just skip the current node because it would
856: //result in more than one part
857: } else {
858: bestActiveNode = compareNodes(bestActiveNode, node);
859: }
860: if (node != bestActiveNode) {
861: removeNode(i, node);
862: }
863: }
864: }
865: return bestActiveNode.line;
866: }
867:
868: public LinkedList getFootnoteList(int index) {
869: return (LinkedList) footnotesList.get(index);
870: }
871:
872: /** @return the associated top-level formatting object. */
873: public FObj getFObj() {
874: return topLevelLM.getFObj();
875: }
876:
877: /** @see org.apache.fop.layoutmgr.BreakingAlgorithm#getLineWidth(int) */
878: protected int getLineWidth(int line) {
879: int bpd;
880: if (pageProvider != null) {
881: bpd = pageProvider.getAvailableBPD(line);
882: } else {
883: bpd = super .getLineWidth(line);
884: }
885: if (log.isTraceEnabled()) {
886: log.trace("getLineWidth(" + line + ") -> " + bpd);
887: }
888: return bpd;
889: }
890:
891: /**
892: * Interface to notify about layout events during page breaking.
893: */
894: public interface PageBreakingLayoutListener {
895:
896: /**
897: * Issued when an overflow is detected
898: * @param part the number of the part (page) this happens on
899: * @param obj the root FO object where this happens
900: */
901: void notifyOverflow(int part, FObj obj);
902:
903: }
904:
905: }
|