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: PageBreaker.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.fop.area.Block;
027: import org.apache.fop.area.Footnote;
028: import org.apache.fop.area.PageViewport;
029: import org.apache.fop.fo.Constants;
030: import org.apache.fop.fo.FONode;
031: import org.apache.fop.fo.FObj;
032: import org.apache.fop.fo.pagination.PageSequence;
033: import org.apache.fop.fo.pagination.Region;
034: import org.apache.fop.fo.pagination.RegionBody;
035: import org.apache.fop.fo.pagination.StaticContent;
036: import org.apache.fop.layoutmgr.PageBreakingAlgorithm.PageBreakingLayoutListener;
037: import org.apache.fop.traits.MinOptMax;
038:
039: /**
040: * Handles the breaking of pages in an fo:flow
041: */
042: public class PageBreaker extends AbstractBreaker {
043:
044: private PageSequenceLayoutManager pslm;
045: private boolean firstPart = true;
046: private boolean pageBreakHandled;
047: private boolean needColumnBalancing;
048: private PageProvider pageProvider;
049: private Block separatorArea;
050:
051: /**
052: * The FlowLayoutManager object, which processes
053: * the single fo:flow of the fo:page-sequence
054: */
055: private FlowLayoutManager childFLM = null;
056:
057: private StaticContentLayoutManager footnoteSeparatorLM = null;
058:
059: public PageBreaker(PageSequenceLayoutManager pslm) {
060: this .pslm = pslm;
061: this .pageProvider = pslm.getPageProvider();
062: this .childFLM = pslm.getLayoutManagerMaker()
063: .makeFlowLayoutManager(pslm,
064: pslm.getPageSequence().getMainFlow());
065: }
066:
067: /** @see org.apache.fop.layoutmgr.AbstractBreaker */
068: protected void updateLayoutContext(LayoutContext context) {
069: int flowIPD = pslm.getCurrentPV().getCurrentSpan()
070: .getColumnWidth();
071: context.setRefIPD(flowIPD);
072: }
073:
074: /** @see org.apache.fop.layoutmgr.AbstractBreaker#getTopLevelLM() */
075: protected LayoutManager getTopLevelLM() {
076: return pslm;
077: }
078:
079: /** @see org.apache.fop.layoutmgr.AbstractBreaker#getPageProvider() */
080: protected PageProvider getPageProvider() {
081: return pslm.getPageProvider();
082: }
083:
084: /**
085: * @see org.apache.fop.layoutmgr.AbstractBreaker#getLayoutListener()
086: */
087: protected PageBreakingLayoutListener getLayoutListener() {
088: return new PageBreakingLayoutListener() {
089:
090: public void notifyOverflow(int part, FObj obj) {
091: Page p = pageProvider.getPage(false, part,
092: PageProvider.RELTO_CURRENT_ELEMENT_LIST);
093: RegionBody body = (RegionBody) p.getSimplePageMaster()
094: .getRegion(Region.FO_REGION_BODY);
095: String err = FONode
096: .decorateWithContextInfo(
097: "Content of the region-body on page "
098: + p.getPageViewport()
099: .getPageNumberString()
100: + " overflows the available area in block-progression dimension.",
101: obj);
102: if (body.getOverflow() == Constants.EN_ERROR_IF_OVERFLOW) {
103: throw new RuntimeException(err);
104: } else {
105: log.warn(err);
106: }
107: }
108:
109: };
110: }
111:
112: /** @see org.apache.fop.layoutmgr.AbstractBreaker */
113: protected int handleSpanChange(LayoutContext childLC,
114: int nextSequenceStartsOn) {
115: needColumnBalancing = false;
116: if (childLC.getNextSpan() != Constants.NOT_SET) {
117: //Next block list will have a different span.
118: nextSequenceStartsOn = childLC.getNextSpan();
119: needColumnBalancing = (childLC.getNextSpan() == Constants.EN_ALL);
120: }
121: if (needColumnBalancing) {
122: AbstractBreaker.log
123: .debug("Column balancing necessary for the next element list!!!");
124: }
125: return nextSequenceStartsOn;
126: }
127:
128: /** @see org.apache.fop.layoutmgr.AbstractBreaker */
129: protected int getNextBlockList(LayoutContext childLC,
130: int nextSequenceStartsOn) {
131: if (!firstPart) {
132: // if this is the first page that will be created by
133: // the current BlockSequence, it could have a break
134: // condition that must be satisfied;
135: // otherwise, we may simply need a new page
136: handleBreakTrait(nextSequenceStartsOn);
137: }
138: firstPart = false;
139: pageBreakHandled = true;
140: pageProvider.setStartOfNextElementList(
141: pslm.getCurrentPageNum(), pslm.getCurrentPV()
142: .getCurrentSpan().getCurrentFlowIndex());
143: return super .getNextBlockList(childLC, nextSequenceStartsOn);
144: }
145:
146: /** @see org.apache.fop.layoutmgr.AbstractBreaker */
147: protected LinkedList getNextKnuthElements(LayoutContext context,
148: int alignment) {
149: LinkedList contentList = null;
150:
151: while (!childFLM.isFinished() && contentList == null) {
152: contentList = childFLM.getNextKnuthElements(context,
153: alignment);
154: }
155:
156: // scan contentList, searching for footnotes
157: boolean bFootnotesPresent = false;
158: if (contentList != null) {
159: ListIterator contentListIterator = contentList
160: .listIterator();
161: while (contentListIterator.hasNext()) {
162: ListElement element = (ListElement) contentListIterator
163: .next();
164: if (element instanceof KnuthBlockBox
165: && ((KnuthBlockBox) element).hasAnchors()) {
166: // element represents a line with footnote citations
167: bFootnotesPresent = true;
168: LayoutContext footnoteContext = new LayoutContext(
169: context);
170: footnoteContext.setStackLimit(context
171: .getStackLimit());
172: footnoteContext.setRefIPD(pslm.getCurrentPV()
173: .getRegionReference(
174: Constants.FO_REGION_BODY).getIPD());
175: LinkedList footnoteBodyLMs = ((KnuthBlockBox) element)
176: .getFootnoteBodyLMs();
177: ListIterator footnoteBodyIterator = footnoteBodyLMs
178: .listIterator();
179: // store the lists of elements representing the footnote bodies
180: // in the box representing the line containing their references
181: while (footnoteBodyIterator.hasNext()) {
182: FootnoteBodyLayoutManager fblm = (FootnoteBodyLayoutManager) footnoteBodyIterator
183: .next();
184: fblm.setParent(childFLM);
185: fblm.initialize();
186: ((KnuthBlockBox) element).addElementList(fblm
187: .getNextKnuthElements(footnoteContext,
188: alignment));
189: }
190: }
191: }
192: }
193:
194: if (bFootnotesPresent) {
195: // handle the footnote separator
196: StaticContent footnoteSeparator;
197: footnoteSeparator = pslm.getPageSequence()
198: .getStaticContent("xsl-footnote-separator");
199: if (footnoteSeparator != null) {
200: // the footnote separator can contain page-dependent content such as
201: // page numbers or retrieve markers, so its areas cannot simply be
202: // obtained now and repeated in each page;
203: // we need to know in advance the separator bpd: the actual separator
204: // could be different from page to page, but its bpd would likely be
205: // always the same
206:
207: // create a Block area that will contain the separator areas
208: separatorArea = new Block();
209: separatorArea.setIPD(pslm.getCurrentPV()
210: .getRegionReference(Constants.FO_REGION_BODY)
211: .getIPD());
212: // create a StaticContentLM for the footnote separator
213: footnoteSeparatorLM = (StaticContentLayoutManager) pslm
214: .getLayoutManagerMaker()
215: .makeStaticContentLayoutManager(pslm,
216: footnoteSeparator, separatorArea);
217: footnoteSeparatorLM.doLayout();
218:
219: footnoteSeparatorLength = new MinOptMax(separatorArea
220: .getBPD());
221: }
222: }
223: return contentList;
224: }
225:
226: /**
227: * @return current display alignment
228: */
229: protected int getCurrentDisplayAlign() {
230: return pslm.getCurrentPage().getSimplePageMaster().getRegion(
231: Constants.FO_REGION_BODY).getDisplayAlign();
232: }
233:
234: /**
235: * @return whether or not this flow has more page break opportunities
236: */
237: protected boolean hasMoreContent() {
238: return !childFLM.isFinished();
239: }
240:
241: /**
242: * Adds an area to the flow layout manager
243: * @param posIter the position iterator
244: * @param context the layout context
245: */
246: protected void addAreas(PositionIterator posIter,
247: LayoutContext context) {
248: if (footnoteSeparatorLM != null) {
249: StaticContent footnoteSeparator = pslm.getPageSequence()
250: .getStaticContent("xsl-footnote-separator");
251: // create a Block area that will contain the separator areas
252: separatorArea = new Block();
253: separatorArea.setIPD(pslm.getCurrentPV()
254: .getRegionReference(Constants.FO_REGION_BODY)
255: .getIPD());
256: // create a StaticContentLM for the footnote separator
257: footnoteSeparatorLM = (StaticContentLayoutManager) pslm
258: .getLayoutManagerMaker()
259: .makeStaticContentLayoutManager(pslm,
260: footnoteSeparator, separatorArea);
261: footnoteSeparatorLM.doLayout();
262: }
263:
264: childFLM.addAreas(posIter, context);
265: }
266:
267: /**
268: * Performs phase 3 operation
269: *
270: * @param alg page breaking algorithm
271: * @param partCount part count
272: * @param originalList the block sequence original list
273: * @param effectiveList the block sequence effective list
274: */
275: protected void doPhase3(PageBreakingAlgorithm alg, int partCount,
276: BlockSequence originalList, BlockSequence effectiveList) {
277: if (needColumnBalancing) {
278: doPhase3WithColumnBalancing(alg, partCount, originalList,
279: effectiveList);
280: } else {
281: if (!hasMoreContent()
282: && pslm.getPageSequence().hasPagePositionLast()) {
283: //last part is reached and we have a "last page" condition
284: doPhase3WithLastPage(alg, partCount, originalList,
285: effectiveList);
286: } else {
287: //Directly add areas after finding the breaks
288: addAreas(alg, partCount, originalList, effectiveList);
289: }
290: }
291: }
292:
293: private void doPhase3WithLastPage(PageBreakingAlgorithm alg,
294: int partCount, BlockSequence originalList,
295: BlockSequence effectiveList) {
296: int newStartPos;
297: int restartPoint = pageProvider
298: .getStartingPartIndexForLastPage(partCount);
299: if (restartPoint > 0) {
300: //Add definitive areas before last page
301: addAreas(alg, restartPoint, originalList, effectiveList);
302: //Get page break from which we restart
303: PageBreakPosition pbp = (PageBreakPosition) alg
304: .getPageBreaks().get(restartPoint - 1);
305: newStartPos = pbp.getLeafPos();
306: //Handle page break right here to avoid any side-effects
307: if (newStartPos > 0) {
308: handleBreakTrait(Constants.EN_PAGE);
309: }
310: } else {
311: newStartPos = 0;
312: }
313: AbstractBreaker.log.debug("Last page handling now!!!");
314: AbstractBreaker.log
315: .debug("===================================================");
316: AbstractBreaker.log.debug("Restarting at " + restartPoint
317: + ", new start position: " + newStartPos);
318:
319: pageBreakHandled = true;
320: //Update so the available BPD is reported correctly
321: int currentPageNum = pslm.getCurrentPageNum();
322: pageProvider.setStartOfNextElementList(currentPageNum, pslm
323: .getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
324: pageProvider.setLastPageIndex(currentPageNum);
325:
326: //Restart last page
327: PageBreakingAlgorithm algRestart = new PageBreakingAlgorithm(
328: getTopLevelLM(), getPageProvider(),
329: getLayoutListener(), alg.getAlignment(), alg
330: .getAlignmentLast(), footnoteSeparatorLength,
331: isPartOverflowRecoveryActivated(), false, false);
332: //alg.setConstantLineWidth(flowBPD);
333: int iOptPageCount = algRestart.findBreakingPoints(
334: effectiveList, newStartPos, 1, true,
335: BreakingAlgorithm.ALL_BREAKS);
336: AbstractBreaker.log.debug("restart: iOptPageCount= "
337: + iOptPageCount + " pageBreaks.size()= "
338: + algRestart.getPageBreaks().size());
339: boolean replaceLastPage = iOptPageCount <= pslm.getCurrentPV()
340: .getBodyRegion().getColumnCount();
341: if (replaceLastPage) {
342: //Replace last page
343: pslm.setCurrentPage(pageProvider.getPage(false,
344: currentPageNum));
345: //Make sure we only add the areas we haven't added already
346: effectiveList.ignoreAtStart = newStartPos;
347: addAreas(algRestart, iOptPageCount, originalList,
348: effectiveList);
349: } else {
350: effectiveList.ignoreAtStart = newStartPos;
351: addAreas(alg, restartPoint, partCount - restartPoint,
352: originalList, effectiveList);
353: //Add blank last page
354: pageProvider.setLastPageIndex(currentPageNum + 1);
355: pslm.setCurrentPage(pslm.makeNewPage(true, true));
356: }
357: AbstractBreaker.log
358: .debug("===================================================");
359: }
360:
361: private void doPhase3WithColumnBalancing(PageBreakingAlgorithm alg,
362: int partCount, BlockSequence originalList,
363: BlockSequence effectiveList) {
364: AbstractBreaker.log.debug("Column balancing now!!!");
365: AbstractBreaker.log
366: .debug("===================================================");
367: int newStartPos;
368: int restartPoint = pageProvider
369: .getStartingPartIndexForLastPage(partCount);
370: if (restartPoint > 0) {
371: //Add definitive areas
372: addAreas(alg, restartPoint, originalList, effectiveList);
373: //Get page break from which we restart
374: PageBreakPosition pbp = (PageBreakPosition) alg
375: .getPageBreaks().get(restartPoint - 1);
376: newStartPos = pbp.getLeafPos();
377: //Handle page break right here to avoid any side-effects
378: if (newStartPos > 0) {
379: handleBreakTrait(Constants.EN_PAGE);
380: }
381: } else {
382: newStartPos = 0;
383: }
384: AbstractBreaker.log.debug("Restarting at " + restartPoint
385: + ", new start position: " + newStartPos);
386:
387: pageBreakHandled = true;
388: //Update so the available BPD is reported correctly
389: pageProvider.setStartOfNextElementList(
390: pslm.getCurrentPageNum(), pslm.getCurrentPV()
391: .getCurrentSpan().getCurrentFlowIndex());
392:
393: //Restart last page
394: PageBreakingAlgorithm algRestart = new BalancingColumnBreakingAlgorithm(
395: getTopLevelLM(), getPageProvider(),
396: getLayoutListener(), alignment, Constants.EN_START,
397: footnoteSeparatorLength,
398: isPartOverflowRecoveryActivated(), pslm.getCurrentPV()
399: .getBodyRegion().getColumnCount());
400: //alg.setConstantLineWidth(flowBPD);
401: int iOptPageCount = algRestart.findBreakingPoints(
402: effectiveList, newStartPos, 1, true,
403: BreakingAlgorithm.ALL_BREAKS);
404: AbstractBreaker.log.debug("restart: iOptPageCount= "
405: + iOptPageCount + " pageBreaks.size()= "
406: + algRestart.getPageBreaks().size());
407: if (iOptPageCount > pslm.getCurrentPV().getBodyRegion()
408: .getColumnCount()) {
409: AbstractBreaker.log
410: .warn("Breaking algorithm produced more columns than are available.");
411: /* reenable when everything works
412: throw new IllegalStateException(
413: "Breaking algorithm must not produce more columns than available.");
414: */
415: }
416: //Make sure we only add the areas we haven't added already
417: effectiveList.ignoreAtStart = newStartPos;
418: addAreas(algRestart, iOptPageCount, originalList, effectiveList);
419: AbstractBreaker.log
420: .debug("===================================================");
421: }
422:
423: protected void startPart(BlockSequence list, int breakClass) {
424: AbstractBreaker.log.debug("startPart() breakClass="
425: + breakClass);
426: if (pslm.getCurrentPage() == null) {
427: throw new IllegalStateException("curPage must not be null");
428: }
429: if (!pageBreakHandled) {
430:
431: //firstPart is necessary because we need the first page before we start the
432: //algorithm so we have a BPD and IPD. This may subject to change later when we
433: //start handling more complex cases.
434: if (!firstPart) {
435: // if this is the first page that will be created by
436: // the current BlockSequence, it could have a break
437: // condition that must be satisfied;
438: // otherwise, we may simply need a new page
439: handleBreakTrait(breakClass);
440: }
441: pageProvider.setStartOfNextElementList(pslm
442: .getCurrentPageNum(), pslm.getCurrentPV()
443: .getCurrentSpan().getCurrentFlowIndex());
444: }
445: pageBreakHandled = false;
446: // add static areas and resolve any new id areas
447: // finish page and add to area tree
448: firstPart = false;
449: }
450:
451: /** @see org.apache.fop.layoutmgr.AbstractBreaker#handleEmptyContent() */
452: protected void handleEmptyContent() {
453: pslm.getCurrentPV().getPage().fakeNonEmpty();
454: }
455:
456: protected void finishPart(PageBreakingAlgorithm alg,
457: PageBreakPosition pbp) {
458: // add footnote areas
459: if (pbp.footnoteFirstListIndex < pbp.footnoteLastListIndex
460: || pbp.footnoteFirstElementIndex <= pbp.footnoteLastElementIndex) {
461: // call addAreas() for each FootnoteBodyLM
462: for (int i = pbp.footnoteFirstListIndex; i <= pbp.footnoteLastListIndex; i++) {
463: LinkedList elementList = alg.getFootnoteList(i);
464: int firstIndex = (i == pbp.footnoteFirstListIndex ? pbp.footnoteFirstElementIndex
465: : 0);
466: int lastIndex = (i == pbp.footnoteLastListIndex ? pbp.footnoteLastElementIndex
467: : elementList.size() - 1);
468:
469: SpaceResolver.performConditionalsNotification(
470: elementList, firstIndex, lastIndex, -1);
471: LayoutContext childLC = new LayoutContext(0);
472: AreaAdditionUtil.addAreas(null, new KnuthPossPosIter(
473: elementList, firstIndex, lastIndex + 1),
474: childLC);
475: }
476: // set the offset from the top margin
477: Footnote parentArea = (Footnote) pslm.getCurrentPV()
478: .getBodyRegion().getFootnote();
479: int topOffset = (int) pslm.getCurrentPV().getBodyRegion()
480: .getBPD()
481: - parentArea.getBPD();
482: if (separatorArea != null) {
483: topOffset -= separatorArea.getBPD();
484: }
485: parentArea.setTop(topOffset);
486: parentArea.setSeparator(separatorArea);
487: }
488: pslm.getCurrentPV().getCurrentSpan().notifyFlowsFinished();
489: }
490:
491: /**
492: * @return the current child flow layout manager
493: */
494: protected LayoutManager getCurrentChildLM() {
495: return childFLM;
496: }
497:
498: /** @see org.apache.fop.layoutmgr.AbstractBreaker#observeElementList(java.util.List) */
499: protected void observeElementList(List elementList) {
500: ElementListObserver.observe(elementList, "breaker",
501: ((PageSequence) pslm.getFObj()).getId());
502: }
503:
504: /**
505: * Depending on the kind of break condition, move to next column
506: * or page. May need to make an empty page if next page would
507: * not have the desired "handedness".
508: * @param breakVal - value of break-before or break-after trait.
509: */
510: private void handleBreakTrait(int breakVal) {
511: Page curPage = pslm.getCurrentPage();
512: if (breakVal == Constants.EN_ALL) {
513: //break due to span change in multi-column layout
514: curPage.getPageViewport().createSpan(true);
515: return;
516: } else if (breakVal == Constants.EN_NONE) {
517: curPage.getPageViewport().createSpan(false);
518: return;
519: } else if (breakVal == Constants.EN_COLUMN || breakVal <= 0) {
520: PageViewport pv = curPage.getPageViewport();
521:
522: //Check if previous page was spanned
523: boolean forceNewPageWithSpan = false;
524: RegionBody rb = (RegionBody) curPage.getSimplePageMaster()
525: .getRegion(Constants.FO_REGION_BODY);
526: if (breakVal < 0 && rb.getColumnCount() > 1
527: && pv.getCurrentSpan().getColumnCount() == 1) {
528: forceNewPageWithSpan = true;
529: }
530:
531: if (forceNewPageWithSpan) {
532: curPage = pslm.makeNewPage(false, false);
533: curPage.getPageViewport().createSpan(true);
534: } else if (pv.getCurrentSpan().hasMoreFlows()) {
535: pv.getCurrentSpan().moveToNextFlow();
536: } else {
537: curPage = pslm.makeNewPage(false, false);
538: }
539: return;
540: }
541: log.debug("handling break-before after page "
542: + pslm.getCurrentPageNum() + " breakVal=" + breakVal);
543: if (needBlankPageBeforeNew(breakVal)) {
544: curPage = pslm.makeNewPage(true, false);
545: }
546: if (needNewPage(breakVal)) {
547: curPage = pslm.makeNewPage(false, false);
548: }
549: }
550:
551: /**
552: * Check if a blank page is needed to accomodate
553: * desired even or odd page number.
554: * @param breakVal - value of break-before or break-after trait.
555: */
556: private boolean needBlankPageBeforeNew(int breakVal) {
557: if (breakVal == Constants.EN_PAGE
558: || (pslm.getCurrentPage().getPageViewport().getPage()
559: .isEmpty())) {
560: // any page is OK or we already have an empty page
561: return false;
562: } else {
563: /* IF we are on the kind of page we need, we'll need a new page. */
564: if (pslm.getCurrentPageNum() % 2 == 0) { // even page
565: return (breakVal == Constants.EN_EVEN_PAGE);
566: } else { // odd page
567: return (breakVal == Constants.EN_ODD_PAGE);
568: }
569: }
570: }
571:
572: /**
573: * See if need to generate a new page
574: * @param breakVal - value of break-before or break-after trait.
575: */
576: private boolean needNewPage(int breakVal) {
577: if (pslm.getCurrentPage().getPageViewport().getPage().isEmpty()) {
578: if (breakVal == Constants.EN_PAGE) {
579: return false;
580: } else if (pslm.getCurrentPageNum() % 2 == 0) { // even page
581: return (breakVal == Constants.EN_ODD_PAGE);
582: } else { // odd page
583: return (breakVal == Constants.EN_EVEN_PAGE);
584: }
585: } else {
586: return true;
587: }
588: }
589: }
|