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: AbstractBreaker.java 554094 2007-07-07 00:04:25Z adelmelle $ */
019:
020: package org.apache.fop.layoutmgr;
021:
022: import java.util.LinkedList;
023: import java.util.List;
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.traits.MinOptMax;
030:
031: /**
032: * Abstract base class for breakers (page breakers, static region handlers etc.).
033: */
034: public abstract class AbstractBreaker {
035:
036: /** logging instance */
037: protected static Log log = LogFactory.getLog(AbstractBreaker.class);
038:
039: public static class PageBreakPosition extends LeafPosition {
040: double bpdAdjust; // Percentage to adjust (stretch or shrink)
041: int difference;
042: int footnoteFirstListIndex;
043: int footnoteFirstElementIndex;
044: int footnoteLastListIndex;
045: int footnoteLastElementIndex;
046:
047: PageBreakPosition(LayoutManager lm, int iBreakIndex, int ffli,
048: int ffei, int flli, int flei, double bpdA, int diff) {
049: super (lm, iBreakIndex);
050: bpdAdjust = bpdA;
051: difference = diff;
052: footnoteFirstListIndex = ffli;
053: footnoteFirstElementIndex = ffei;
054: footnoteLastListIndex = flli;
055: footnoteLastElementIndex = flei;
056: }
057: }
058:
059: public class BlockSequence extends BlockKnuthSequence {
060:
061: /** Number of elements to ignore at the beginning of the list. */
062: public int ignoreAtStart = 0;
063: /** Number of elements to ignore at the end of the list. */
064: public int ignoreAtEnd = 0;
065:
066: /**
067: * startOn represents where on the page/which page layout
068: * should start for this BlockSequence. Acceptable values:
069: * Constants.EN_ANY (can continue from finished location
070: * of previous BlockSequence?), EN_COLUMN, EN_ODD_PAGE,
071: * EN_EVEN_PAGE.
072: */
073: private int startOn;
074:
075: private int displayAlign;
076:
077: /**
078: * Creates a new BlockSequence.
079: * @param iStartOn the kind of page the sequence should start on. One of EN_ANY, EN_COLUMN,
080: * EN_ODD_PAGE, EN_EVEN_PAGE.
081: * @param displayAlign the value for the display-align property
082: */
083: public BlockSequence(int iStartOn, int displayAlign) {
084: super ();
085: startOn = iStartOn;
086: this .displayAlign = displayAlign;
087: }
088:
089: /**
090: * @return the kind of page the sequence should start on. One of EN_ANY, EN_COLUMN,
091: * EN_ODD_PAGE, EN_EVEN_PAGE.
092: */
093: public int getStartOn() {
094: return this .startOn;
095: }
096:
097: /** @return the value for the display-align property */
098: public int getDisplayAlign() {
099: return this .displayAlign;
100: }
101:
102: /**
103: * Finalizes a Knuth sequence.
104: * @return a finalized sequence.
105: */
106: public KnuthSequence endSequence() {
107: return endSequence(null);
108: }
109:
110: /**
111: * Finalizes a Knuth sequence.
112: * @param breakPosition a Position instance for the last penalty (may be null)
113: * @return a finalized sequence.
114: */
115: public KnuthSequence endSequence(Position breakPosition) {
116: // remove glue and penalty item at the end of the paragraph
117: while (this .size() > ignoreAtStart
118: && !((KnuthElement) this .get(this .size() - 1))
119: .isBox()) {
120: this .remove(this .size() - 1);
121: }
122: if (this .size() > ignoreAtStart) {
123: // add the elements representing the space at the end of the last line
124: // and the forced break
125: if (getDisplayAlign() == Constants.EN_X_DISTRIBUTE
126: && isSinglePartFavored()) {
127: this .add(new KnuthPenalty(0,
128: -KnuthElement.INFINITE, false,
129: breakPosition, false));
130: ignoreAtEnd = 1;
131: } else {
132: this .add(new KnuthPenalty(0, KnuthElement.INFINITE,
133: false, null, false));
134: this
135: .add(new KnuthGlue(0, 10000000, 0, null,
136: false));
137: this .add(new KnuthPenalty(0,
138: -KnuthElement.INFINITE, false,
139: breakPosition, false));
140: ignoreAtEnd = 3;
141: }
142: return this ;
143: } else {
144: this .clear();
145: return null;
146: }
147: }
148:
149: public BlockSequence endBlockSequence(Position breakPosition) {
150: KnuthSequence temp = endSequence(breakPosition);
151: if (temp != null) {
152: BlockSequence returnSequence = new BlockSequence(
153: startOn, displayAlign);
154: returnSequence.addAll(temp);
155: returnSequence.ignoreAtEnd = this .ignoreAtEnd;
156: return returnSequence;
157: } else {
158: return null;
159: }
160: }
161:
162: }
163:
164: /** blockListIndex of the current BlockSequence in blockLists */
165: private int blockListIndex = 0;
166:
167: private List blockLists = null;
168:
169: protected int alignment;
170: private int alignmentLast;
171:
172: protected MinOptMax footnoteSeparatorLength = new MinOptMax(0);
173:
174: protected abstract int getCurrentDisplayAlign();
175:
176: protected abstract boolean hasMoreContent();
177:
178: protected abstract void addAreas(PositionIterator posIter,
179: LayoutContext context);
180:
181: protected abstract LayoutManager getTopLevelLM();
182:
183: protected abstract LayoutManager getCurrentChildLM();
184:
185: /**
186: * Controls the behaviour of the algorithm in cases where the first element of a part
187: * overflows a line/page.
188: * @return true if the algorithm should try to send the element to the next line/page.
189: */
190: protected boolean isPartOverflowRecoveryActivated() {
191: return true;
192: }
193:
194: /**
195: * @return true if one a single part should be produced if possible (ex. for block-containers)
196: */
197: protected boolean isSinglePartFavored() {
198: return false;
199: }
200:
201: /**
202: * Returns the PageProvider if any. PageBreaker overrides this method because each
203: * page may have a different available BPD which needs to be accessible to the breaking
204: * algorithm.
205: * @return the applicable PageProvider, or null if not applicable
206: */
207: protected PageProvider getPageProvider() {
208: return null;
209: }
210:
211: /**
212: * Returns a PageBreakingLayoutListener for the PageBreakingAlgorithm to notify about layout
213: * problems.
214: * @return the listener instance or null if no notifications are needed
215: */
216: protected PageBreakingAlgorithm.PageBreakingLayoutListener getLayoutListener() {
217: return null;
218: }
219:
220: /*
221: * This method is to contain the logic to determine the LM's
222: * getNextKnuthElements() implementation(s) that are to be called.
223: * @return LinkedList of Knuth elements.
224: */
225: protected abstract LinkedList getNextKnuthElements(
226: LayoutContext context, int alignment);
227:
228: /** @return true if there's no content that could be handled. */
229: public boolean isEmpty() {
230: return (this .blockLists.size() == 0);
231: }
232:
233: protected void startPart(BlockSequence list, int breakClass) {
234: //nop
235: }
236:
237: /**
238: * This method is called when no content is available for a part. Used to force empty pages.
239: */
240: protected void handleEmptyContent() {
241: //nop
242: }
243:
244: protected abstract void finishPart(PageBreakingAlgorithm alg,
245: PageBreakPosition pbp);
246:
247: /**
248: * Creates the top-level LayoutContext for the breaker operation.
249: * @return the top-level LayoutContext
250: */
251: protected LayoutContext createLayoutContext() {
252: return new LayoutContext(0);
253: }
254:
255: /**
256: * Used to update the LayoutContext in subclasses prior to starting a new element list.
257: * @param context the LayoutContext to update
258: */
259: protected void updateLayoutContext(LayoutContext context) {
260: //nop
261: }
262:
263: /**
264: * Used for debugging purposes. Notifies all registered observers about the element list.
265: * Override to set different parameters.
266: * @param elementList the Knuth element list
267: */
268: protected void observeElementList(List elementList) {
269: ElementListObserver.observe(elementList, "breaker", null);
270: }
271:
272: /**
273: * Starts the page breaking process.
274: * @param flowBPD the constant available block-progression-dimension (used for every part)
275: */
276: public void doLayout(int flowBPD) {
277: doLayout(flowBPD, false);
278: }
279:
280: /**
281: * Starts the page breaking process.
282: * @param flowBPD the constant available block-progression-dimension (used for every part)
283: * @param autoHeight true if warnings about overflows should be disabled because the
284: * the BPD is really undefined (for footnote-separators, for example)
285: */
286: public void doLayout(int flowBPD, boolean autoHeight) {
287: LayoutContext childLC = createLayoutContext();
288: childLC.setStackLimit(new MinOptMax(flowBPD));
289:
290: if (getCurrentDisplayAlign() == Constants.EN_X_FILL) {
291: //EN_X_FILL is non-standard (by LF)
292: alignment = Constants.EN_JUSTIFY;
293: } else if (getCurrentDisplayAlign() == Constants.EN_X_DISTRIBUTE) {
294: //EN_X_DISTRIBUTE is non-standard (by LF)
295: alignment = Constants.EN_JUSTIFY;
296: } else {
297: alignment = Constants.EN_START;
298: }
299: alignmentLast = Constants.EN_START;
300: if (isSinglePartFavored() && alignment == Constants.EN_JUSTIFY) {
301: alignmentLast = Constants.EN_JUSTIFY;
302: }
303: childLC.setBPAlignment(alignment);
304:
305: BlockSequence blockList;
306: this .blockLists = new java.util.ArrayList();
307:
308: log.debug("PLM> flow BPD =" + flowBPD);
309:
310: //*** Phase 1: Get Knuth elements ***
311: int nextSequenceStartsOn = Constants.EN_ANY;
312: while (hasMoreContent()) {
313: blockLists.clear();
314:
315: nextSequenceStartsOn = getNextBlockList(childLC,
316: nextSequenceStartsOn);
317:
318: //*** Phase 2: Alignment and breaking ***
319: log.debug("PLM> blockLists.size() = " + blockLists.size());
320: for (blockListIndex = 0; blockListIndex < blockLists.size(); blockListIndex++) {
321: blockList = (BlockSequence) blockLists
322: .get(blockListIndex);
323:
324: //debug code start
325: if (log.isDebugEnabled()) {
326: log.debug(" blockListIndex = " + blockListIndex);
327: String pagina = (blockList.startOn == Constants.EN_ANY) ? "any page"
328: : (blockList.startOn == Constants.EN_ODD_PAGE) ? "odd page"
329: : "even page";
330: log.debug(" sequence starts on " + pagina);
331: }
332: observeElementList(blockList);
333: //debug code end
334:
335: log.debug("PLM> start of algorithm ("
336: + this .getClass().getName() + "), flow BPD ="
337: + flowBPD);
338: PageBreakingAlgorithm alg = new PageBreakingAlgorithm(
339: getTopLevelLM(), getPageProvider(),
340: getLayoutListener(), alignment, alignmentLast,
341: footnoteSeparatorLength,
342: isPartOverflowRecoveryActivated(), autoHeight,
343: isSinglePartFavored());
344: int iOptPageCount;
345:
346: BlockSequence effectiveList;
347: if (getCurrentDisplayAlign() == Constants.EN_X_FILL) {
348: /* justification */
349: effectiveList = justifyBoxes(blockList, alg,
350: flowBPD);
351: } else {
352: /* no justification */
353: effectiveList = blockList;
354: }
355:
356: //iOptPageCount = alg.firstFit(effectiveList, flowBPD, 1, true);
357: alg.setConstantLineWidth(flowBPD);
358: iOptPageCount = alg.findBreakingPoints(effectiveList, /*flowBPD,*/
359: 1, true, BreakingAlgorithm.ALL_BREAKS);
360: log.debug("PLM> iOptPageCount= " + iOptPageCount
361: + " pageBreaks.size()= "
362: + alg.getPageBreaks().size());
363:
364: //*** Phase 3: Add areas ***
365: doPhase3(alg, iOptPageCount, blockList, effectiveList);
366: }
367: }
368:
369: }
370:
371: /**
372: * Phase 3 of Knuth algorithm: Adds the areas
373: * @param alg PageBreakingAlgorithm instance which determined the breaks
374: * @param partCount number of parts (pages) to be rendered
375: * @param originalList original Knuth element list
376: * @param effectiveList effective Knuth element list (after adjustments)
377: */
378: protected abstract void doPhase3(PageBreakingAlgorithm alg,
379: int partCount, BlockSequence originalList,
380: BlockSequence effectiveList);
381:
382: /**
383: * Phase 3 of Knuth algorithm: Adds the areas
384: * @param alg PageBreakingAlgorithm instance which determined the breaks
385: * @param partCount number of parts (pages) to be rendered
386: * @param originalList original Knuth element list
387: * @param effectiveList effective Knuth element list (after adjustments)
388: */
389: protected void addAreas(PageBreakingAlgorithm alg, int partCount,
390: BlockSequence originalList, BlockSequence effectiveList) {
391: addAreas(alg, 0, partCount, originalList, effectiveList);
392: }
393:
394: /**
395: * Phase 3 of Knuth algorithm: Adds the areas
396: * @param alg PageBreakingAlgorithm instance which determined the breaks
397: * @param startPart index of the first part (page) to be rendered
398: * @param partCount number of parts (pages) to be rendered
399: * @param originalList original Knuth element list
400: * @param effectiveList effective Knuth element list (after adjustments)
401: */
402: protected void addAreas(PageBreakingAlgorithm alg, int startPart,
403: int partCount, BlockSequence originalList,
404: BlockSequence effectiveList) {
405: LayoutContext childLC;
406: // add areas
407: ListIterator effectiveListIterator = effectiveList
408: .listIterator();
409: int startElementIndex = 0;
410: int endElementIndex = 0;
411: int lastBreak = -1;
412: for (int p = startPart; p < startPart + partCount; p++) {
413: PageBreakPosition pbp = (PageBreakPosition) alg
414: .getPageBreaks().get(p);
415:
416: //Check the last break position for forced breaks
417: int lastBreakClass;
418: if (p == 0) {
419: lastBreakClass = effectiveList.getStartOn();
420: } else {
421: ListElement lastBreakElement = effectiveList
422: .getElement(endElementIndex);
423: if (lastBreakElement.isPenalty()) {
424: KnuthPenalty pen = (KnuthPenalty) lastBreakElement;
425: lastBreakClass = pen.getBreakClass();
426: } else {
427: lastBreakClass = Constants.EN_COLUMN;
428: }
429: }
430:
431: //the end of the new part
432: endElementIndex = pbp.getLeafPos();
433:
434: // ignore the first elements added by the
435: // PageSequenceLayoutManager
436: startElementIndex += (startElementIndex == 0) ? effectiveList.ignoreAtStart
437: : 0;
438:
439: log.debug("PLM> part: " + (p + 1) + ", start at pos "
440: + startElementIndex + ", break at pos "
441: + endElementIndex + ", break class = "
442: + lastBreakClass);
443:
444: startPart(effectiveList, lastBreakClass);
445:
446: int displayAlign = getCurrentDisplayAlign();
447:
448: //The following is needed by SpaceResolver.performConditionalsNotification()
449: //further down as there may be important Position elements in the element list trailer
450: int notificationEndElementIndex = endElementIndex;
451:
452: // ignore the last elements added by the
453: // PageSequenceLayoutManager
454: endElementIndex -= (endElementIndex == (originalList.size() - 1)) ? effectiveList.ignoreAtEnd
455: : 0;
456:
457: // ignore the last element in the page if it is a KnuthGlue
458: // object
459: if (((KnuthElement) effectiveList.get(endElementIndex))
460: .isGlue()) {
461: endElementIndex--;
462: }
463:
464: // ignore KnuthGlue and KnuthPenalty objects
465: // at the beginning of the line
466: effectiveListIterator = effectiveList
467: .listIterator(startElementIndex);
468: KnuthElement firstElement;
469: while (effectiveListIterator.hasNext()
470: && !(firstElement = (KnuthElement) effectiveListIterator
471: .next()).isBox()) {
472: /*
473: if (firstElement.isGlue() && firstElement.getLayoutManager() != null) {
474: // discard the space representd by the glue element
475: ((BlockLevelLayoutManager) firstElement
476: .getLayoutManager())
477: .discardSpace((KnuthGlue) firstElement);
478: }*/
479: startElementIndex++;
480: }
481:
482: if (startElementIndex <= endElementIndex) {
483: if (log.isDebugEnabled()) {
484: log.debug(" addAreas from " + startElementIndex
485: + " to " + endElementIndex);
486: }
487: childLC = new LayoutContext(0);
488: // set the space adjustment ratio
489: childLC.setSpaceAdjust(pbp.bpdAdjust);
490: // add space before if display-align is center or bottom
491: // add space after if display-align is distribute and
492: // this is not the last page
493: if (pbp.difference != 0
494: && displayAlign == Constants.EN_CENTER) {
495: childLC.setSpaceBefore(pbp.difference / 2);
496: } else if (pbp.difference != 0
497: && displayAlign == Constants.EN_AFTER) {
498: childLC.setSpaceBefore(pbp.difference);
499: } else if (pbp.difference != 0
500: && displayAlign == Constants.EN_X_DISTRIBUTE
501: && p < (partCount - 1)) {
502: // count the boxes whose width is not 0
503: int boxCount = 0;
504: effectiveListIterator = effectiveList
505: .listIterator(startElementIndex);
506: while (effectiveListIterator.nextIndex() <= endElementIndex) {
507: KnuthElement tempEl = (KnuthElement) effectiveListIterator
508: .next();
509: if (tempEl.isBox() && tempEl.getW() > 0) {
510: boxCount++;
511: }
512: }
513: // split the difference
514: if (boxCount >= 2) {
515: childLC.setSpaceAfter(pbp.difference
516: / (boxCount - 1));
517: }
518: }
519:
520: /* *** *** non-standard extension *** *** */
521: if (displayAlign == Constants.EN_X_FILL) {
522: int averageLineLength = optimizeLineLength(
523: effectiveList, startElementIndex,
524: endElementIndex);
525: if (averageLineLength != 0) {
526: childLC.setStackLimit(new MinOptMax(
527: averageLineLength));
528: }
529: }
530: /* *** *** non-standard extension *** *** */
531:
532: // Handle SpaceHandling(Break)Positions, see SpaceResolver!
533: SpaceResolver.performConditionalsNotification(
534: effectiveList, startElementIndex,
535: notificationEndElementIndex, lastBreak);
536:
537: // Add areas now!
538: addAreas(new KnuthPossPosIter(effectiveList,
539: startElementIndex, endElementIndex + 1),
540: childLC);
541: } else {
542: //no content for this part
543: handleEmptyContent();
544: }
545:
546: finishPart(alg, pbp);
547:
548: lastBreak = endElementIndex;
549: startElementIndex = pbp.getLeafPos() + 1;
550: }
551: }
552:
553: /**
554: * Notifies the layout managers about the space and conditional length situation based on
555: * the break decisions.
556: * @param effectiveList Element list to be painted
557: * @param startElementIndex start index of the part
558: * @param endElementIndex end index of the part
559: * @param lastBreak index of the last break element
560: */
561: /**
562: * Handles span changes reported through the <code>LayoutContext</code>.
563: * Only used by the PSLM and called by <code>getNextBlockList()</code>.
564: * @param childLC the LayoutContext
565: * @param nextSequenceStartsOn previous value for break handling
566: * @return effective value for break handling
567: */
568: protected int handleSpanChange(LayoutContext childLC,
569: int nextSequenceStartsOn) {
570: return nextSequenceStartsOn;
571: }
572:
573: /**
574: * Gets the next block list (sequence) and adds it to a list of block lists if it's not empty.
575: * @param childLC LayoutContext to use
576: * @param nextSequenceStartsOn indicates on what page the next sequence should start
577: * @return the page on which the next content should appear after a hard break
578: */
579: protected int getNextBlockList(LayoutContext childLC,
580: int nextSequenceStartsOn) {
581: updateLayoutContext(childLC);
582: //Make sure the span change signal is reset
583: childLC.signalSpanChange(Constants.NOT_SET);
584:
585: BlockSequence blockList;
586: LinkedList returnedList = getNextKnuthElements(childLC,
587: alignment);
588: if (returnedList != null) {
589: if (returnedList.size() == 0) {
590: nextSequenceStartsOn = handleSpanChange(childLC,
591: nextSequenceStartsOn);
592: return nextSequenceStartsOn;
593: }
594: blockList = new BlockSequence(nextSequenceStartsOn,
595: getCurrentDisplayAlign());
596:
597: //Only implemented by the PSLM
598: nextSequenceStartsOn = handleSpanChange(childLC,
599: nextSequenceStartsOn);
600:
601: Position breakPosition = null;
602: if (((KnuthElement) returnedList.getLast()).isPenalty()
603: && ((KnuthPenalty) returnedList.getLast()).getP() == -KnuthElement.INFINITE) {
604: KnuthPenalty breakPenalty = (KnuthPenalty) returnedList
605: .removeLast();
606: breakPosition = breakPenalty.getPosition();
607: switch (breakPenalty.getBreakClass()) {
608: case Constants.EN_PAGE:
609: log.debug("PLM> break - PAGE");
610: nextSequenceStartsOn = Constants.EN_ANY;
611: break;
612: case Constants.EN_COLUMN:
613: log.debug("PLM> break - COLUMN");
614: //TODO Fix this when implementing multi-column layout
615: nextSequenceStartsOn = Constants.EN_COLUMN;
616: break;
617: case Constants.EN_ODD_PAGE:
618: log.debug("PLM> break - ODD PAGE");
619: nextSequenceStartsOn = Constants.EN_ODD_PAGE;
620: break;
621: case Constants.EN_EVEN_PAGE:
622: log.debug("PLM> break - EVEN PAGE");
623: nextSequenceStartsOn = Constants.EN_EVEN_PAGE;
624: break;
625: default:
626: throw new IllegalStateException(
627: "Invalid break class: "
628: + breakPenalty.getBreakClass());
629: }
630: }
631: blockList.addAll(returnedList);
632: BlockSequence seq = null;
633: seq = blockList.endBlockSequence(breakPosition);
634: if (seq != null) {
635: this .blockLists.add(seq);
636: }
637: }
638: return nextSequenceStartsOn;
639: }
640:
641: /**
642: * Returns the average width of all the lines in the given range.
643: * @param effectiveList effective block list to work on
644: * @param startElementIndex
645: * @param endElementIndex
646: * @return the average line length, 0 if there's no content
647: */
648: private int optimizeLineLength(KnuthSequence effectiveList,
649: int startElementIndex, int endElementIndex) {
650: ListIterator effectiveListIterator;
651: // optimize line length
652: int boxCount = 0;
653: int accumulatedLineLength = 0;
654: int greatestMinimumLength = 0;
655: effectiveListIterator = effectiveList
656: .listIterator(startElementIndex);
657: while (effectiveListIterator.nextIndex() <= endElementIndex) {
658: KnuthElement tempEl = (KnuthElement) effectiveListIterator
659: .next();
660: if (tempEl instanceof KnuthBlockBox) {
661: KnuthBlockBox blockBox = (KnuthBlockBox) tempEl;
662: if (blockBox.getBPD() > 0) {
663: log.debug("PSLM> nominal length of line = "
664: + blockBox.getBPD());
665: log
666: .debug(" range = "
667: + blockBox.getIPDRange());
668: boxCount++;
669: accumulatedLineLength += ((KnuthBlockBox) tempEl)
670: .getBPD();
671: }
672: if (blockBox.getIPDRange().min > greatestMinimumLength) {
673: greatestMinimumLength = blockBox.getIPDRange().min;
674: }
675: }
676: }
677: int averageLineLength = 0;
678: if (accumulatedLineLength > 0 && boxCount > 0) {
679: averageLineLength = (int) (accumulatedLineLength / boxCount);
680: log.debug("Average line length = " + averageLineLength);
681: if (averageLineLength < greatestMinimumLength) {
682: averageLineLength = greatestMinimumLength;
683: log.debug(" Correction to: " + averageLineLength);
684: }
685: }
686: return averageLineLength;
687: }
688:
689: /**
690: * Justifies the boxes and returns them as a new KnuthSequence.
691: * @param blockList block list to justify
692: * @param alg reference to the algorithm instance
693: * @param availableBPD the available BPD
694: * @return the effective list
695: */
696: private BlockSequence justifyBoxes(BlockSequence blockList,
697: PageBreakingAlgorithm alg, int availableBPD) {
698: int iOptPageNumber;
699: alg.setConstantLineWidth(availableBPD);
700: iOptPageNumber = alg.findBreakingPoints(blockList, /*availableBPD,*/
701: 1, true, BreakingAlgorithm.ALL_BREAKS);
702: log.debug("PLM> iOptPageNumber= " + iOptPageNumber);
703:
704: //
705: ListIterator sequenceIterator = blockList.listIterator();
706: ListIterator breakIterator = alg.getPageBreaks().listIterator();
707: KnuthElement this Element = null;
708: PageBreakPosition this Break;
709: int accumulatedS; // accumulated stretch or shrink
710: int adjustedDiff; // difference already adjusted
711: int firstElementIndex;
712:
713: while (breakIterator.hasNext()) {
714: this Break = (PageBreakPosition) breakIterator.next();
715: if (log.isDebugEnabled()) {
716: log.debug("| first page: break= "
717: + this Break.getLeafPos() + " difference= "
718: + this Break.difference + " ratio= "
719: + this Break.bpdAdjust);
720: }
721: accumulatedS = 0;
722: adjustedDiff = 0;
723:
724: // glue and penalty items at the beginning of the page must
725: // be ignored:
726: // the first element returned by sequenceIterator.next()
727: // inside the
728: // while loop must be a box
729: KnuthElement firstElement;
730: while (!(firstElement = (KnuthElement) sequenceIterator
731: .next()).isBox()) {
732: //
733: log.debug("PLM> ignoring glue or penalty element "
734: + "at the beginning of the sequence");
735: if (firstElement.isGlue()) {
736: ((BlockLevelLayoutManager) firstElement
737: .getLayoutManager())
738: .discardSpace((KnuthGlue) firstElement);
739: }
740: }
741: firstElementIndex = sequenceIterator.previousIndex();
742: sequenceIterator.previous();
743:
744: // scan the sub-sequence representing a page,
745: // collecting information about potential adjustments
746: MinOptMax lineNumberMaxAdjustment = new MinOptMax(0);
747: MinOptMax spaceMaxAdjustment = new MinOptMax(0);
748: double spaceAdjustmentRatio = 0.0;
749: LinkedList blockSpacesList = new LinkedList();
750: LinkedList unconfirmedList = new LinkedList();
751: LinkedList adjustableLinesList = new LinkedList();
752: boolean bBoxSeen = false;
753: while (sequenceIterator.hasNext()
754: && sequenceIterator.nextIndex() <= this Break
755: .getLeafPos()) {
756: this Element = (KnuthElement) sequenceIterator.next();
757: if (this Element.isGlue()) {
758: // glue elements are used to represent adjustable
759: // lines
760: // and adjustable spaces between blocks
761: switch (((KnuthGlue) this Element)
762: .getAdjustmentClass()) {
763: case BlockLevelLayoutManager.SPACE_BEFORE_ADJUSTMENT:
764: // fall through
765: case BlockLevelLayoutManager.SPACE_AFTER_ADJUSTMENT:
766: // potential space adjustment
767: // glue items before the first box or after the
768: // last one
769: // must be ignored
770: unconfirmedList.add(this Element);
771: break;
772: case BlockLevelLayoutManager.LINE_NUMBER_ADJUSTMENT:
773: // potential line number adjustment
774: lineNumberMaxAdjustment.max += ((KnuthGlue) this Element)
775: .getY();
776: lineNumberMaxAdjustment.min -= ((KnuthGlue) this Element)
777: .getZ();
778: adjustableLinesList.add(this Element);
779: break;
780: case BlockLevelLayoutManager.LINE_HEIGHT_ADJUSTMENT:
781: // potential line height adjustment
782: break;
783: default:
784: // nothing
785: }
786: } else if (this Element.isBox()) {
787: if (!bBoxSeen) {
788: // this is the first box met in this page
789: bBoxSeen = true;
790: } else if (unconfirmedList.size() > 0) {
791: // glue items in unconfirmedList were not after
792: // the last box
793: // in this page; they must be added to
794: // blockSpaceList
795: while (unconfirmedList.size() > 0) {
796: KnuthGlue blockSpace = (KnuthGlue) unconfirmedList
797: .removeFirst();
798: spaceMaxAdjustment.max += ((KnuthGlue) blockSpace)
799: .getY();
800: spaceMaxAdjustment.min -= ((KnuthGlue) blockSpace)
801: .getZ();
802: blockSpacesList.add(blockSpace);
803: }
804: }
805: }
806: }
807: log.debug("| line number adj= " + lineNumberMaxAdjustment);
808: log.debug("| space adj = " + spaceMaxAdjustment);
809:
810: if (this Element.isPenalty() && this Element.getW() > 0) {
811: log
812: .debug(" mandatory variation to the number of lines!");
813: ((BlockLevelLayoutManager) this Element
814: .getLayoutManager()).negotiateBPDAdjustment(
815: this Element.getW(), this Element);
816: }
817:
818: if (this Break.bpdAdjust != 0
819: && (this Break.difference > 0 && this Break.difference <= spaceMaxAdjustment.max)
820: || (this Break.difference < 0 && this Break.difference >= spaceMaxAdjustment.min)) {
821: // modify only the spaces between blocks
822: spaceAdjustmentRatio = ((double) this Break.difference / (this Break.difference > 0 ? spaceMaxAdjustment.max
823: : spaceMaxAdjustment.min));
824: adjustedDiff += adjustBlockSpaces(
825: blockSpacesList,
826: this Break.difference,
827: (this Break.difference > 0 ? spaceMaxAdjustment.max
828: : -spaceMaxAdjustment.min));
829: log.debug("single space: "
830: + (adjustedDiff == this Break.difference
831: || this Break.bpdAdjust == 0 ? "ok"
832: : "ERROR"));
833: } else if (this Break.bpdAdjust != 0) {
834: adjustedDiff += adjustLineNumbers(
835: adjustableLinesList,
836: this Break.difference,
837: (this Break.difference > 0 ? lineNumberMaxAdjustment.max
838: : -lineNumberMaxAdjustment.min));
839: adjustedDiff += adjustBlockSpaces(
840: blockSpacesList,
841: this Break.difference - adjustedDiff,
842: ((this Break.difference - adjustedDiff) > 0 ? spaceMaxAdjustment.max
843: : -spaceMaxAdjustment.min));
844: log.debug("lines and space: "
845: + (adjustedDiff == this Break.difference
846: || this Break.bpdAdjust == 0 ? "ok"
847: : "ERROR"));
848:
849: }
850: }
851:
852: // create a new sequence: the new elements will contain the
853: // Positions
854: // which will be used in the addAreas() phase
855: BlockSequence effectiveList = new BlockSequence(blockList
856: .getStartOn(), blockList.getDisplayAlign());
857: effectiveList.addAll(getCurrentChildLM()
858: .getChangedKnuthElements(
859: blockList.subList(0, blockList.size()
860: - blockList.ignoreAtEnd),
861: /* 0, */0));
862: //effectiveList.add(new KnuthPenalty(0, -KnuthElement.INFINITE,
863: // false, new Position(this), false));
864: effectiveList.endSequence();
865:
866: ElementListObserver.observe(effectiveList, "breaker-effective",
867: null);
868:
869: alg.getPageBreaks().clear(); //Why this?
870: return effectiveList;
871: }
872:
873: private int adjustBlockSpaces(LinkedList spaceList, int difference,
874: int total) {
875: if (log.isDebugEnabled()) {
876: log.debug("AdjustBlockSpaces: difference " + difference
877: + " / " + total + " on " + spaceList.size()
878: + " spaces in block");
879: }
880: ListIterator spaceListIterator = spaceList.listIterator();
881: int adjustedDiff = 0;
882: int partial = 0;
883: while (spaceListIterator.hasNext()) {
884: KnuthGlue blockSpace = (KnuthGlue) spaceListIterator.next();
885: partial += (difference > 0 ? blockSpace.getY() : blockSpace
886: .getZ());
887: if (log.isDebugEnabled()) {
888: log.debug("available = " + partial + " / " + total);
889: log
890: .debug("competenza = "
891: + (((int) ((float) partial * difference / total)) - adjustedDiff)
892: + " / " + difference);
893: }
894: int newAdjust = ((BlockLevelLayoutManager) blockSpace
895: .getLayoutManager()).negotiateBPDAdjustment(
896: ((int) ((float) partial * difference / total))
897: - adjustedDiff, blockSpace);
898: adjustedDiff += newAdjust;
899: }
900: return adjustedDiff;
901: }
902:
903: private int adjustLineNumbers(LinkedList lineList, int difference,
904: int total) {
905: if (log.isDebugEnabled()) {
906: log.debug("AdjustLineNumbers: difference " + difference
907: + " / " + total + " on " + lineList.size()
908: + " elements");
909: }
910:
911: // int adjustedDiff = 0;
912: // int partial = 0;
913: // KnuthGlue prevLine = null;
914: // KnuthGlue currLine = null;
915: // ListIterator lineListIterator = lineList.listIterator();
916: // while (lineListIterator.hasNext()) {
917: // currLine = (KnuthGlue)lineListIterator.next();
918: // if (prevLine != null
919: // && prevLine.getLayoutManager() != currLine.getLayoutManager()) {
920: // int newAdjust = ((BlockLevelLayoutManager) prevLine.getLayoutManager())
921: // .negotiateBPDAdjustment(((int) ((float) partial * difference / total)) - adjustedDiff, prevLine);
922: // adjustedDiff += newAdjust;
923: // }
924: // partial += (difference > 0 ? currLine.getY() : currLine.getZ());
925: // prevLine = currLine;
926: // }
927: // if (currLine != null) {
928: // int newAdjust = ((BlockLevelLayoutManager) currLine.getLayoutManager())
929: // .negotiateBPDAdjustment(((int) ((float) partial * difference / total)) - adjustedDiff, currLine);
930: // adjustedDiff += newAdjust;
931: // }
932: // return adjustedDiff;
933:
934: ListIterator lineListIterator = lineList.listIterator();
935: int adjustedDiff = 0;
936: int partial = 0;
937: while (lineListIterator.hasNext()) {
938: KnuthGlue line = (KnuthGlue) lineListIterator.next();
939: partial += (difference > 0 ? line.getY() : line.getZ());
940: int newAdjust = ((BlockLevelLayoutManager) line
941: .getLayoutManager()).negotiateBPDAdjustment(
942: ((int) ((float) partial * difference / total))
943: - adjustedDiff, line);
944: adjustedDiff += newAdjust;
945: }
946: return adjustedDiff;
947: }
948:
949: }
|