KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > fop > layoutmgr > PageSequenceLayoutManager


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: PageSequenceLayoutManager.java 428750 2006-08-04 15:13:53Z jeremias $ */
19
20 package org.apache.fop.layoutmgr;
21
22 import org.apache.commons.logging.Log;
23 import org.apache.commons.logging.LogFactory;
24 import org.apache.fop.apps.FOPException;
25 import org.apache.fop.datatypes.Numeric;
26
27 import org.apache.fop.area.AreaTreeHandler;
28 import org.apache.fop.area.AreaTreeModel;
29 import org.apache.fop.area.Block;
30 import org.apache.fop.area.Footnote;
31 import org.apache.fop.area.PageViewport;
32 import org.apache.fop.area.LineArea;
33 import org.apache.fop.area.Resolvable;
34
35 import org.apache.fop.fo.Constants;
36 import org.apache.fop.fo.FONode;
37 import org.apache.fop.fo.FObj;
38 import org.apache.fop.fo.flow.Marker;
39 import org.apache.fop.fo.flow.RetrieveMarker;
40
41 import org.apache.fop.fo.pagination.Flow;
42 import org.apache.fop.fo.pagination.PageSequence;
43 import org.apache.fop.fo.pagination.Region;
44 import org.apache.fop.fo.pagination.RegionBody;
45 import org.apache.fop.fo.pagination.SideRegion;
46 import org.apache.fop.fo.pagination.SimplePageMaster;
47 import org.apache.fop.fo.pagination.StaticContent;
48 import org.apache.fop.layoutmgr.PageBreakingAlgorithm.PageBreakingLayoutListener;
49 import org.apache.fop.layoutmgr.inline.ContentLayoutManager;
50
51 import org.apache.fop.traits.MinOptMax;
52
53 import java.util.LinkedList JavaDoc;
54 import java.util.List JavaDoc;
55 import java.util.ListIterator JavaDoc;
56
57 /**
58  * LayoutManager for a PageSequence. This class is instantiated by
59  * area.AreaTreeHandler for each fo:page-sequence found in the
60  * input document.
61  */

62 public class PageSequenceLayoutManager extends AbstractLayoutManager {
63
64     private static Log log = LogFactory.getLog(PageSequenceLayoutManager.class);
65
66     /**
67      * AreaTreeHandler which activates the PSLM and controls
68      * the rendering of its pages.
69      */

70     private AreaTreeHandler areaTreeHandler;
71
72     /**
73      * fo:page-sequence formatting object being
74      * processed by this class
75      */

76     private PageSequence pageSeq;
77
78     private PageProvider pageProvider;
79     
80     /**
81      * Current page with page-viewport-area being filled by
82      * the PSLM.
83      */

84     private Page curPage = null;
85
86     /**
87      * The FlowLayoutManager object, which processes
88      * the single fo:flow of the fo:page-sequence
89      */

90     private FlowLayoutManager childFLM = null;
91
92     private int startPageNum = 0;
93     private int currentPageNum = 0;
94
95     private Block separatorArea = null;
96     
97     /**
98      * Constructor
99      *
100      * @param ath the area tree handler object
101      * @param pseq fo:page-sequence to process
102      */

103     public PageSequenceLayoutManager(AreaTreeHandler ath, PageSequence pseq) {
104         super(pseq);
105         this.areaTreeHandler = ath;
106         this.pageSeq = pseq;
107         this.pageProvider = new PageProvider(this.pageSeq);
108     }
109
110     /**
111      * @see org.apache.fop.layoutmgr.LayoutManager
112      * @return the LayoutManagerMaker object
113      */

114     public LayoutManagerMaker getLayoutManagerMaker() {
115         return areaTreeHandler.getLayoutManagerMaker();
116     }
117
118     /** @return the PageProvider applicable to this page-sequence. */
119     public PageProvider getPageProvider() {
120         return this.pageProvider;
121     }
122     
123     /**
124      * Activate the layout of this page sequence.
125      * PageViewports corresponding to each page generated by this
126      * page sequence will be created and sent to the AreaTreeModel
127      * for rendering.
128      */

129     public void activateLayout() {
130         startPageNum = pageSeq.getStartingPageNumber();
131         currentPageNum = startPageNum - 1;
132
133         LineArea title = null;
134
135         if (pageSeq.getTitleFO() != null) {
136             try {
137                 ContentLayoutManager clm = getLayoutManagerMaker().
138                     makeContentLayoutManager(this, pageSeq.getTitleFO());
139                 title = (LineArea) clm.getParentArea(null);
140             } catch (IllegalStateException JavaDoc e) {
141                 // empty title; do nothing
142
}
143         }
144
145         areaTreeHandler.getAreaTreeModel().startPageSequence(title);
146         log.debug("Starting layout");
147
148         curPage = makeNewPage(false, false);
149
150         
151         Flow mainFlow = pageSeq.getMainFlow();
152         childFLM = getLayoutManagerMaker().
153             makeFlowLayoutManager(this, mainFlow);
154
155         PageBreaker breaker = new PageBreaker(this);
156         int flowBPD = (int)getCurrentPV().getBodyRegion().getRemainingBPD();
157         breaker.doLayout(flowBPD);
158
159         finishPage();
160     }
161         
162     /**
163      * Finished the page-sequence and notifies everyone about it.
164      */

165     public void finishPageSequence() {
166         if (!pageSeq.getId().equals("")) {
167             areaTreeHandler.signalIDProcessed(pageSeq.getId());
168         }
169
170         pageSeq.getRoot().notifyPageSequenceFinished(currentPageNum,
171                 (currentPageNum - startPageNum) + 1);
172         areaTreeHandler.notifyPageSequenceFinished(pageSeq,
173                 (currentPageNum - startPageNum) + 1);
174         pageSeq.releasePageSequence();
175         log.debug("Ending layout");
176     }
177
178
179     private class PageBreaker extends AbstractBreaker {
180         
181         private PageSequenceLayoutManager pslm;
182         private boolean firstPart = true;
183         private boolean pageBreakHandled;
184         private boolean needColumnBalancing;
185         
186         private StaticContentLayoutManager footnoteSeparatorLM = null;
187
188         public PageBreaker(PageSequenceLayoutManager pslm) {
189             this.pslm = pslm;
190         }
191         
192         /** @see org.apache.fop.layoutmgr.AbstractBreaker */
193         protected void updateLayoutContext(LayoutContext context) {
194             int flowIPD = getCurrentPV().getCurrentSpan().getColumnWidth();
195             context.setRefIPD(flowIPD);
196         }
197         
198         /** @see org.apache.fop.layoutmgr.AbstractBreaker#getTopLevelLM() */
199         protected LayoutManager getTopLevelLM() {
200             return pslm;
201         }
202         
203         /** @see org.apache.fop.layoutmgr.AbstractBreaker#getPageProvider() */
204         protected PageSequenceLayoutManager.PageProvider getPageProvider() {
205             return pageProvider;
206         }
207         
208         /**
209          * @see org.apache.fop.layoutmgr.AbstractBreaker#getLayoutListener()
210          */

211         protected PageBreakingLayoutListener getLayoutListener() {
212             return new PageBreakingLayoutListener() {
213
214                 public void notifyOverflow(int part, FObj obj) {
215                     Page p = pageProvider.getPage(
216                                 false, part, PageProvider.RELTO_CURRENT_ELEMENT_LIST);
217                     RegionBody body = (RegionBody)p.getSimplePageMaster().getRegion(
218                             Region.FO_REGION_BODY);
219                     String JavaDoc err = FONode.decorateWithContextInfo(
220                             "Content of the region-body on page "
221                             + p.getPageViewport().getPageNumberString()
222                             + " overflows the available area in block-progression dimension.",
223                             obj);
224                     if (body.getOverflow() == Constants.EN_ERROR_IF_OVERFLOW) {
225                         throw new RuntimeException JavaDoc(err);
226                     } else {
227                         PageSequenceLayoutManager.log.warn(err);
228                     }
229                 }
230                 
231             };
232         }
233
234         /** @see org.apache.fop.layoutmgr.AbstractBreaker */
235         protected int handleSpanChange(LayoutContext childLC, int nextSequenceStartsOn) {
236             needColumnBalancing = false;
237             if (childLC.getNextSpan() != Constants.NOT_SET) {
238                 //Next block list will have a different span.
239
nextSequenceStartsOn = childLC.getNextSpan();
240                 needColumnBalancing = (childLC.getNextSpan() == Constants.EN_ALL);
241             }
242             if (needColumnBalancing) {
243                 AbstractBreaker.log.debug(
244                         "Column balancing necessary for the next element list!!!");
245             }
246             return nextSequenceStartsOn;
247         }
248
249         /** @see org.apache.fop.layoutmgr.AbstractBreaker */
250         protected int getNextBlockList(LayoutContext childLC,
251                 int nextSequenceStartsOn,
252                 List JavaDoc blockLists) {
253             if (!firstPart) {
254                 // if this is the first page that will be created by
255
// the current BlockSequence, it could have a break
256
// condition that must be satisfied;
257
// otherwise, we may simply need a new page
258
handleBreakTrait(nextSequenceStartsOn);
259             }
260             firstPart = false;
261             pageBreakHandled = true;
262             pageProvider.setStartOfNextElementList(currentPageNum,
263                     getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
264             return super.getNextBlockList(childLC, nextSequenceStartsOn, blockLists);
265         }
266         
267         /** @see org.apache.fop.layoutmgr.AbstractBreaker */
268         protected LinkedList JavaDoc getNextKnuthElements(LayoutContext context, int alignment) {
269             LinkedList JavaDoc contentList = null;
270             
271             while (!childFLM.isFinished() && contentList == null) {
272                 contentList = childFLM.getNextKnuthElements(context, alignment);
273             }
274
275             // scan contentList, searching for footnotes
276
boolean bFootnotesPresent = false;
277             if (contentList != null) {
278                 ListIterator JavaDoc contentListIterator = contentList.listIterator();
279                 while (contentListIterator.hasNext()) {
280                     ListElement element = (ListElement) contentListIterator.next();
281                     if (element instanceof KnuthBlockBox
282                         && ((KnuthBlockBox) element).hasAnchors()) {
283                         // element represents a line with footnote citations
284
bFootnotesPresent = true;
285                         LayoutContext footnoteContext = new LayoutContext(context);
286                         footnoteContext.setStackLimit(context.getStackLimit());
287                         footnoteContext.setRefIPD(getCurrentPV()
288                                 .getRegionReference(Constants.FO_REGION_BODY).getIPD());
289                         LinkedList JavaDoc footnoteBodyLMs = ((KnuthBlockBox) element).getFootnoteBodyLMs();
290                         ListIterator JavaDoc footnoteBodyIterator = footnoteBodyLMs.listIterator();
291                         // store the lists of elements representing the footnote bodies
292
// in the box representing the line containing their references
293
while (footnoteBodyIterator.hasNext()) {
294                             FootnoteBodyLayoutManager fblm
295                                 = (FootnoteBodyLayoutManager) footnoteBodyIterator.next();
296                             fblm.setParent(childFLM);
297                             fblm.initialize();
298                             ((KnuthBlockBox) element).addElementList(
299                                     fblm.getNextKnuthElements(footnoteContext, alignment));
300                         }
301                     }
302                 }
303             }
304
305             // handle the footnote separator
306
StaticContent footnoteSeparator;
307             if (bFootnotesPresent
308                     && (footnoteSeparator = pageSeq.getStaticContent(
309                                             "xsl-footnote-separator")) != null) {
310                 // the footnote separator can contain page-dependent content such as
311
// page numbers or retrieve markers, so its areas cannot simply be
312
// obtained now and repeated in each page;
313
// we need to know in advance the separator bpd: the actual separator
314
// could be different from page to page, but its bpd would likely be
315
// always the same
316

317                 // create a Block area that will contain the separator areas
318
separatorArea = new Block();
319                 separatorArea.setIPD(pslm.getCurrentPV()
320                             .getRegionReference(Constants.FO_REGION_BODY).getIPD());
321                 // create a StaticContentLM for the footnote separator
322
footnoteSeparatorLM = (StaticContentLayoutManager)
323                     getLayoutManagerMaker().makeStaticContentLayoutManager(
324                     pslm, footnoteSeparator, separatorArea);
325                 footnoteSeparatorLM.doLayout();
326
327                 footnoteSeparatorLength = new MinOptMax(separatorArea.getBPD());
328             }
329             return contentList;
330         }
331         
332         protected int getCurrentDisplayAlign() {
333             return curPage.getSimplePageMaster().getRegion(
334                     Constants.FO_REGION_BODY).getDisplayAlign();
335         }
336         
337         protected boolean hasMoreContent() {
338             return !childFLM.isFinished();
339         }
340         
341         protected void addAreas(PositionIterator posIter, LayoutContext context) {
342             if (footnoteSeparatorLM != null) {
343                 StaticContent footnoteSeparator = pageSeq.getStaticContent(
344                         "xsl-footnote-separator");
345                 // create a Block area that will contain the separator areas
346
separatorArea = new Block();
347                 separatorArea.setIPD(
348                         getCurrentPV().getRegionReference(Constants.FO_REGION_BODY).getIPD());
349                 // create a StaticContentLM for the footnote separator
350
footnoteSeparatorLM = (StaticContentLayoutManager)
351                     getLayoutManagerMaker().makeStaticContentLayoutManager(
352                     pslm, footnoteSeparator, separatorArea);
353                 footnoteSeparatorLM.doLayout();
354             }
355
356             childFLM.addAreas(posIter, context);
357         }
358         
359         protected void doPhase3(PageBreakingAlgorithm alg, int partCount,
360                 BlockSequence originalList, BlockSequence effectiveList) {
361             if (needColumnBalancing) {
362                 doPhase3WithColumnBalancing(alg, partCount, originalList, effectiveList);
363             } else {
364                 if (!hasMoreContent() && pageSeq.hasPagePositionLast()) {
365                     //last part is reached and we have a "last page" condition
366
doPhase3WithLastPage(alg, partCount, originalList, effectiveList);
367                 } else {
368                     //Directly add areas after finding the breaks
369
addAreas(alg, partCount, originalList, effectiveList);
370                 }
371             }
372         }
373
374         private void doPhase3WithLastPage(PageBreakingAlgorithm alg, int partCount,
375                 BlockSequence originalList, BlockSequence effectiveList) {
376             int newStartPos;
377             int restartPoint = pageProvider.getStartingPartIndexForLastPage(partCount);
378             if (restartPoint > 0) {
379                 //Add definitive areas before last page
380
addAreas(alg, restartPoint, originalList, effectiveList);
381                 //Get page break from which we restart
382
PageBreakPosition pbp = (PageBreakPosition)
383                         alg.getPageBreaks().get(restartPoint - 1);
384                 newStartPos = pbp.getLeafPos();
385                 //Handle page break right here to avoid any side-effects
386
if (newStartPos > 0) {
387                     handleBreakTrait(EN_PAGE);
388                 }
389             } else {
390                 newStartPos = 0;
391             }
392             AbstractBreaker.log.debug("Last page handling now!!!");
393             AbstractBreaker.log.debug("===================================================");
394             AbstractBreaker.log.debug("Restarting at " + restartPoint
395                     + ", new start position: " + newStartPos);
396
397             pageBreakHandled = true;
398             //Update so the available BPD is reported correctly
399
pageProvider.setStartOfNextElementList(currentPageNum,
400                     getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
401             pageProvider.setLastPageIndex(currentPageNum);
402
403             //Restart last page
404
PageBreakingAlgorithm algRestart = new PageBreakingAlgorithm(
405                     getTopLevelLM(),
406                     getPageProvider(), getLayoutListener(),
407                     alg.getAlignment(), alg.getAlignmentLast(),
408                     footnoteSeparatorLength,
409                     isPartOverflowRecoveryActivated(), false, false);
410             //alg.setConstantLineWidth(flowBPD);
411
int iOptPageCount = algRestart.findBreakingPoints(effectiveList,
412                         newStartPos,
413                         1, true, BreakingAlgorithm.ALL_BREAKS);
414             AbstractBreaker.log.debug("restart: iOptPageCount= " + iOptPageCount
415                     + " pageBreaks.size()= " + algRestart.getPageBreaks().size());
416             boolean replaceLastPage
417                     = iOptPageCount <= getCurrentPV().getBodyRegion().getColumnCount();
418             if (replaceLastPage) {
419
420                 //Replace last page
421
pslm.curPage = pageProvider.getPage(false, currentPageNum);
422                 //Make sure we only add the areas we haven't added already
423
effectiveList.ignoreAtStart = newStartPos;
424                 addAreas(algRestart, iOptPageCount, originalList, effectiveList);
425             } else {
426                 effectiveList.ignoreAtStart = newStartPos;
427                 addAreas(alg, restartPoint, partCount - restartPoint, originalList, effectiveList);
428                 //Add blank last page
429
pageProvider.setLastPageIndex(currentPageNum + 1);
430                 pslm.curPage = makeNewPage(true, true);
431             }
432             AbstractBreaker.log.debug("===================================================");
433         }
434
435         private void doPhase3WithColumnBalancing(PageBreakingAlgorithm alg, int partCount,
436                 BlockSequence originalList, BlockSequence effectiveList) {
437             AbstractBreaker.log.debug("Column balancing now!!!");
438             AbstractBreaker.log.debug("===================================================");
439             int newStartPos;
440             int restartPoint = pageProvider.getStartingPartIndexForLastPage(partCount);
441             if (restartPoint > 0) {
442                 //Add definitive areas
443
addAreas(alg, restartPoint, originalList, effectiveList);
444                 //Get page break from which we restart
445
PageBreakPosition pbp = (PageBreakPosition)
446                         alg.getPageBreaks().get(restartPoint - 1);
447                 newStartPos = pbp.getLeafPos();
448                 //Handle page break right here to avoid any side-effects
449
if (newStartPos > 0) {
450                     handleBreakTrait(EN_PAGE);
451                 }
452             } else {
453                 newStartPos = 0;
454             }
455             AbstractBreaker.log.debug("Restarting at " + restartPoint
456                     + ", new start position: " + newStartPos);
457
458             pageBreakHandled = true;
459             //Update so the available BPD is reported correctly
460
pageProvider.setStartOfNextElementList(currentPageNum,
461                     getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
462
463             //Restart last page
464
PageBreakingAlgorithm algRestart = new BalancingColumnBreakingAlgorithm(
465                     getTopLevelLM(),
466                     getPageProvider(), getLayoutListener(),
467                     alignment, Constants.EN_START, footnoteSeparatorLength,
468                     isPartOverflowRecoveryActivated(),
469                     getCurrentPV().getBodyRegion().getColumnCount());
470             //alg.setConstantLineWidth(flowBPD);
471
int iOptPageCount = algRestart.findBreakingPoints(effectiveList,
472                         newStartPos,
473                         1, true, BreakingAlgorithm.ALL_BREAKS);
474             AbstractBreaker.log.debug("restart: iOptPageCount= " + iOptPageCount
475                     + " pageBreaks.size()= " + algRestart.getPageBreaks().size());
476             if (iOptPageCount > getCurrentPV().getBodyRegion().getColumnCount()) {
477                 AbstractBreaker.log.warn(
478                         "Breaking algorithm produced more columns than are available.");
479                 /* reenable when everything works
480                 throw new IllegalStateException(
481                         "Breaking algorithm must not produce more columns than available.");
482                 */

483             }
484             //Make sure we only add the areas we haven't added already
485
effectiveList.ignoreAtStart = newStartPos;
486             addAreas(algRestart, iOptPageCount, originalList, effectiveList);
487             AbstractBreaker.log.debug("===================================================");
488         }
489         
490         protected void startPart(BlockSequence list, int breakClass) {
491             AbstractBreaker.log.debug("startPart() breakClass=" + breakClass);
492             if (curPage == null) {
493                 throw new IllegalStateException JavaDoc("curPage must not be null");
494             }
495             if (!pageBreakHandled) {
496                 
497                 //firstPart is necessary because we need the first page before we start the
498
//algorithm so we have a BPD and IPD. This may subject to change later when we
499
//start handling more complex cases.
500
if (!firstPart) {
501                     // if this is the first page that will be created by
502
// the current BlockSequence, it could have a break
503
// condition that must be satisfied;
504
// otherwise, we may simply need a new page
505
handleBreakTrait(breakClass);
506                 }
507                 pageProvider.setStartOfNextElementList(currentPageNum,
508                         getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
509             }
510             pageBreakHandled = false;
511             // add static areas and resolve any new id areas
512
// finish page and add to area tree
513
firstPart = false;
514         }
515         
516         /** @see org.apache.fop.layoutmgr.AbstractBreaker#handleEmptyContent() */
517         protected void handleEmptyContent() {
518             getCurrentPV().getPage().fakeNonEmpty();
519         }
520         
521         protected void finishPart(PageBreakingAlgorithm alg, PageBreakPosition pbp) {
522             // add footnote areas
523
if (pbp.footnoteFirstListIndex < pbp.footnoteLastListIndex
524                 || pbp.footnoteFirstElementIndex <= pbp.footnoteLastElementIndex) {
525                 // call addAreas() for each FootnoteBodyLM
526
for (int i = pbp.footnoteFirstListIndex; i <= pbp.footnoteLastListIndex; i++) {
527                     LinkedList JavaDoc elementList = alg.getFootnoteList(i);
528                     int firstIndex = (i == pbp.footnoteFirstListIndex
529                             ? pbp.footnoteFirstElementIndex : 0);
530                     int lastIndex = (i == pbp.footnoteLastListIndex
531                             ? pbp.footnoteLastElementIndex : elementList.size() - 1);
532
533                     SpaceResolver.performConditionalsNotification(elementList,
534                             firstIndex, lastIndex, -1);
535                     LayoutContext childLC = new LayoutContext(0);
536                     AreaAdditionUtil.addAreas(null,
537                             new KnuthPossPosIter(elementList, firstIndex, lastIndex + 1),
538                             childLC);
539                 }
540                 // set the offset from the top margin
541
Footnote parentArea = (Footnote) getCurrentPV().getBodyRegion().getFootnote();
542                 int topOffset = (int) getCurrentPV().getBodyRegion().getBPD() - parentArea.getBPD();
543                 if (separatorArea != null) {
544                     topOffset -= separatorArea.getBPD();
545                 }
546                 parentArea.setTop(topOffset);
547                 parentArea.setSeparator(separatorArea);
548             }
549             getCurrentPV().getCurrentSpan().notifyFlowsFinished();
550         }
551         
552         protected LayoutManager getCurrentChildLM() {
553             return childFLM;
554         }
555         
556         /** @see org.apache.fop.layoutmgr.AbstractBreaker#observeElementList(java.util.List) */
557         protected void observeElementList(List JavaDoc elementList) {
558             ElementListObserver.observe(elementList, "breaker",
559                     ((PageSequence)pslm.getFObj()).getId());
560         }
561         
562     }
563     
564     /**
565      * Provides access to the current page.
566      * @return the current Page
567      */

568     public Page getCurrentPage() {
569         return curPage;
570     }
571
572     /**
573      * Provides access to the current page viewport.
574      * @return the current PageViewport
575      */
/*
576     public PageViewport getCurrentPageViewport() {
577         return curPage.getPageViewport();
578     }*/

579
580     /**
581      * Provides access to this object
582      * @return this PageSequenceLayoutManager instance
583      */

584     public PageSequenceLayoutManager getPSLM() {
585         return this;
586     }
587     
588     /**
589      * This returns the first PageViewport that contains an id trait
590      * matching the idref argument, or null if no such PV exists.
591      *
592      * @param idref the idref trait needing to be resolved
593      * @return the first PageViewport that contains the ID trait
594      */

595     public PageViewport getFirstPVWithID(String JavaDoc idref) {
596         List JavaDoc list = areaTreeHandler.getPageViewportsContainingID(idref);
597         if (list != null && list.size() > 0) {
598             return (PageViewport) list.get(0);
599         }
600         return null;
601     }
602
603     /**
604      * This returns the last PageViewport that contains an id trait
605      * matching the idref argument, or null if no such PV exists.
606      *
607      * @param idref the idref trait needing to be resolved
608      * @return the last PageViewport that contains the ID trait
609      */

610     public PageViewport getLastPVWithID(String JavaDoc idref) {
611         List JavaDoc list = areaTreeHandler.getPageViewportsContainingID(idref);
612         if (list != null && list.size() > 0) {
613             return (PageViewport) list.get(list.size() - 1);
614         }
615         return null;
616     }
617     
618     /**
619      * Add an ID reference to the current page.
620      * When adding areas the area adds its ID reference.
621      * For the page layout manager it adds the id reference
622      * with the current page to the area tree.
623      *
624      * @param id the ID reference to add
625      */

626     public void addIDToPage(String JavaDoc id) {
627         if (id != null && id.length() > 0) {
628             areaTreeHandler.associateIDWithPageViewport(id, curPage.getPageViewport());
629         }
630     }
631     
632     /**
633      * Add an id reference of the layout manager in the AreaTreeHandler,
634      * if the id hasn't been resolved yet
635      * @param id the id to track
636      * @return a boolean indicating if the id has already been resolved
637      * TODO Maybe give this a better name
638      */

639     public boolean associateLayoutManagerID(String JavaDoc id) {
640         if (log.isDebugEnabled()) {
641             log.debug("associateLayoutManagerID(" + id + ")");
642         }
643         if (!areaTreeHandler.alreadyResolvedID(id)) {
644             areaTreeHandler.signalPendingID(id);
645             return false;
646         } else {
647             return true;
648         }
649     }
650     
651     /**
652      * Notify the areaTreeHandler that the LayoutManagers containing
653      * idrefs have finished creating areas
654      * @param id the id for which layout has finished
655      */

656     public void notifyEndOfLayout(String JavaDoc id) {
657         areaTreeHandler.signalIDProcessed(id);
658     }
659     
660     /**
661      * Identify an unresolved area (one needing an idref to be
662      * resolved, e.g. the internal-destination of an fo:basic-link)
663      * for both the AreaTreeHandler and PageViewport object.
664      *
665      * The AreaTreeHandler keeps a document-wide list of idref's
666      * and the PV's needing them to be resolved. It uses this to
667      * send notifications to the PV's when an id has been resolved.
668      *
669      * The PageViewport keeps lists of id's needing resolving, along
670      * with the child areas (page-number-citation, basic-link, etc.)
671      * of the PV needing their resolution.
672      *
673      * @param id the ID reference to add
674      * @param res the resolvable object that needs resolving
675      */

676     public void addUnresolvedArea(String JavaDoc id, Resolvable res) {
677         curPage.getPageViewport().addUnresolvedIDRef(id, res);
678         areaTreeHandler.addUnresolvedIDRef(id, curPage.getPageViewport());
679     }
680
681     /**
682      * Bind the RetrieveMarker to the corresponding Marker subtree.
683      * If the boundary is page then it will only check the
684      * current page. For page-sequence and document it will
685      * lookup preceding pages from the area tree and try to find
686      * a marker.
687      * If we retrieve a marker from a preceding page,
688      * then the containing page does not have a qualifying area,
689      * and all qualifying areas have ended.
690      * Therefore we use last-ending-within-page (Constants.EN_LEWP)
691      * as the position.
692      *
693      * @param rm the RetrieveMarker instance whose properties are to
694      * used to find the matching Marker.
695      * @return a bound RetrieveMarker instance, or null if no Marker
696      * could be found.
697      */

698     public RetrieveMarker resolveRetrieveMarker(RetrieveMarker rm) {
699         AreaTreeModel areaTreeModel = areaTreeHandler.getAreaTreeModel();
700         String JavaDoc name = rm.getRetrieveClassName();
701         int pos = rm.getRetrievePosition();
702         int boundary = rm.getRetrieveBoundary();
703         
704         // get marker from the current markers on area tree
705
Marker mark = (Marker)getCurrentPV().getMarker(name, pos);
706         if (mark == null && boundary != EN_PAGE) {
707             // go back over pages until mark found
708
// if document boundary then keep going
709
boolean doc = boundary == EN_DOCUMENT;
710             int seq = areaTreeModel.getPageSequenceCount();
711             int page = areaTreeModel.getPageCount(seq) - 1;
712             while (page < 0 && doc && seq > 1) {
713                 seq--;
714                 page = areaTreeModel.getPageCount(seq) - 1;
715             }
716             while (page >= 0) {
717                 PageViewport pv = areaTreeModel.getPage(seq, page);
718                 mark = (Marker)pv.getMarker(name, Constants.EN_LEWP);
719                 if (mark != null) {
720                     break;
721                 }
722                 page--;
723                 if (page < 0 && doc && seq > 1) {
724                     seq--;
725                     page = areaTreeModel.getPageCount(seq) - 1;
726                 }
727             }
728         }
729
730         if (mark == null) {
731             log.debug("found no marker with name: " + name);
732             return null;
733         } else {
734             rm.bindMarker(mark);
735             return rm;
736         }
737     }
738
739     private Page makeNewPage(boolean bIsBlank, boolean bIsLast) {
740         if (curPage != null) {
741             finishPage();
742         }
743
744         currentPageNum++;
745
746         curPage = pageProvider.getPage(bIsBlank,
747                 currentPageNum, PageProvider.RELTO_PAGE_SEQUENCE);
748
749         if (log.isDebugEnabled()) {
750             log.debug("[" + curPage.getPageViewport().getPageNumberString()
751                     + (bIsBlank ? "*" : "") + "]");
752         }
753         
754         addIDToPage(pageSeq.getId());
755         return curPage;
756     }
757
758     private void layoutSideRegion(int regionID) {
759         SideRegion reg = (SideRegion)curPage.getSimplePageMaster().getRegion(regionID);
760         if (reg == null) {
761             return;
762         }
763         StaticContent sc = pageSeq.getStaticContent(reg.getRegionName());
764         if (sc == null) {
765             return;
766         }
767
768         StaticContentLayoutManager lm = (StaticContentLayoutManager)
769             getLayoutManagerMaker().makeStaticContentLayoutManager(
770                     this, sc, reg);
771         lm.doLayout();
772     }
773
774     private void finishPage() {
775         curPage.getPageViewport().dumpMarkers();
776         // Layout side regions
777
layoutSideRegion(FO_REGION_BEFORE);
778         layoutSideRegion(FO_REGION_AFTER);
779         layoutSideRegion(FO_REGION_START);
780         layoutSideRegion(FO_REGION_END);
781         
782         // Try to resolve any unresolved IDs for the current page.
783
//
784
areaTreeHandler.tryIDResolution(curPage.getPageViewport());
785         // Queue for ID resolution and rendering
786
areaTreeHandler.getAreaTreeModel().addPage(curPage.getPageViewport());
787         if (log.isDebugEnabled()) {
788             log.debug("page finished: " + curPage.getPageViewport().getPageNumberString()
789                     + ", current num: " + currentPageNum);
790         }
791         curPage = null;
792     }
793     
794     /**
795      * Depending on the kind of break condition, move to next column
796      * or page. May need to make an empty page if next page would
797      * not have the desired "handedness".
798      * @param breakVal - value of break-before or break-after trait.
799      */

800     private void handleBreakTrait(int breakVal) {
801         if (breakVal == Constants.EN_ALL) {
802             //break due to span change in multi-column layout
803
curPage.getPageViewport().createSpan(true);
804             return;
805         } else if (breakVal == Constants.EN_NONE) {
806             curPage.getPageViewport().createSpan(false);
807             return;
808         } else if (breakVal == Constants.EN_COLUMN || breakVal <= 0) {
809             PageViewport pv = curPage.getPageViewport();
810             
811             //Check if previous page was spanned
812
boolean forceNewPageWithSpan = false;
813             RegionBody rb = (RegionBody)curPage.getSimplePageMaster().getRegion(
814                     Constants.FO_REGION_BODY);
815             if (breakVal < 0
816                     && rb.getColumnCount() > 1
817                     && pv.getCurrentSpan().getColumnCount() == 1) {
818                 forceNewPageWithSpan = true;
819             }
820             
821             if (forceNewPageWithSpan) {
822                 curPage = makeNewPage(false, false);
823                 curPage.getPageViewport().createSpan(true);
824             } else if (pv.getCurrentSpan().hasMoreFlows()) {
825                 pv.getCurrentSpan().moveToNextFlow();
826             } else {
827                 curPage = makeNewPage(false, false);
828             }
829             return;
830         }
831         log.debug("handling break-before after page " + currentPageNum
832             + " breakVal=" + breakVal);
833         if (needBlankPageBeforeNew(breakVal)) {
834             curPage = makeNewPage(true, false);
835         }
836         if (needNewPage(breakVal)) {
837             curPage = makeNewPage(false, false);
838         }
839     }
840
841     /**
842      * Check if a blank page is needed to accomodate
843      * desired even or odd page number.
844      * @param breakVal - value of break-before or break-after trait.
845      */

846     private boolean needBlankPageBeforeNew(int breakVal) {
847         if (breakVal == Constants.EN_PAGE || (curPage.getPageViewport().getPage().isEmpty())) {
848             // any page is OK or we already have an empty page
849
return false;
850         } else {
851             /* IF we are on the kind of page we need, we'll need a new page. */
852             if (currentPageNum % 2 == 0) { // even page
853
return (breakVal == Constants.EN_EVEN_PAGE);
854             } else { // odd page
855
return (breakVal == Constants.EN_ODD_PAGE);
856             }
857         }
858     }
859
860     /**
861      * See if need to generate a new page
862      * @param breakVal - value of break-before or break-after trait.
863      */

864     private boolean needNewPage(int breakVal) {
865         if (curPage.getPageViewport().getPage().isEmpty()) {
866             if (breakVal == Constants.EN_PAGE) {
867                 return false;
868             } else if (currentPageNum % 2 == 0) { // even page
869
return (breakVal == Constants.EN_ODD_PAGE);
870             } else { // odd page
871
return (breakVal == Constants.EN_EVEN_PAGE);
872             }
873         } else {
874             return true;
875         }
876     }
877     
878     
879     /**
880      * <p>This class delivers Page instances. It also caches them as necessary.
881      * </p>
882      * <p>Additional functionality makes sure that surplus instances that are requested by the
883      * page breaker are properly discarded, especially in situations where hard breaks cause
884      * blank pages. The reason for that: The page breaker sometimes needs to preallocate
885      * additional pages since it doesn't know exactly until the end how many pages it really needs.
886      * </p>
887      */

888     public class PageProvider {
889         
890         private Log log = LogFactory.getLog(PageProvider.class);
891
892         /** Indices are evaluated relative to the first page in the page-sequence. */
893         public static final int RELTO_PAGE_SEQUENCE = 0;
894         /** Indices are evaluated relative to the first page in the current element list. */
895         public static final int RELTO_CURRENT_ELEMENT_LIST = 1;
896         
897         private int startPageOfPageSequence;
898         private int startPageOfCurrentElementList;
899         private int startColumnOfCurrentElementList;
900         private List JavaDoc cachedPages = new java.util.ArrayList JavaDoc();
901         
902         private int lastPageIndex = -1;
903         private int indexOfCachedLastPage = -1;
904         
905         //Cache to optimize getAvailableBPD() calls
906
private int lastRequestedIndex = -1;
907         private int lastReportedBPD = -1;
908         
909         /**
910          * Main constructor.
911          * @param ps The page-sequence the provider operates on
912          */

913         public PageProvider(PageSequence ps) {
914             this.startPageOfPageSequence = ps.getStartingPageNumber();
915         }
916         
917         /**
918          * The page breaker notifies the provider about the page number an element list starts
919          * on so it can later retrieve PageViewports relative to this first page.
920          * @param startPage the number of the first page for the element list.
921          * @param startColumn the starting column number for the element list.
922          */

923         public void setStartOfNextElementList(int startPage, int startColumn) {
924             log.debug("start of the next element list is:"
925                     + " page=" + startPage + " col=" + startColumn);
926             this.startPageOfCurrentElementList = startPage - startPageOfPageSequence + 1;
927             this.startColumnOfCurrentElementList = startColumn;
928             //Reset Cache
929
this.lastRequestedIndex = -1;
930             this.lastReportedBPD = -1;
931         }
932         
933         /**
934          * Sets the index of the last page. This is done as soon as the position of the last page
935          * is known or assumed.
936          * @param index the index relative to the first page in the page-sequence
937          */

938         public void setLastPageIndex(int index) {
939             this.lastPageIndex = index;
940         }
941         
942         /**
943          * Returns the available BPD for the part/page indicated by the index parameter.
944          * The index is the part/page relative to the start of the current element list.
945          * This method takes multiple columns into account.
946          * @param index zero-based index of the requested part/page
947          * @return the available BPD
948          */

949         public int getAvailableBPD(int index) {
950             //Special optimization: There may be many equal calls by the BreakingAlgorithm
951
if (this.lastRequestedIndex == index) {
952                 if (log.isTraceEnabled()) {
953                     log.trace("getAvailableBPD(" + index + ") -> (cached) " + lastReportedBPD);
954                 }
955                 return this.lastReportedBPD;
956             }
957             int c = index;
958             int pageIndex = 0;
959             int colIndex = startColumnOfCurrentElementList;
960             Page page = getPage(
961                     false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
962             while (c > 0) {
963                 colIndex++;
964                 if (colIndex >= page.getPageViewport().getCurrentSpan().getColumnCount()) {
965                     colIndex = 0;
966                     pageIndex++;
967                     page = getPage(
968                             false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
969                 }
970                 c--;
971             }
972             this.lastRequestedIndex = index;
973             this.lastReportedBPD = page.getPageViewport().getBodyRegion().getRemainingBPD();
974             if (log.isTraceEnabled()) {
975                 log.trace("getAvailableBPD(" + index + ") -> " + lastReportedBPD);
976             }
977             return this.lastReportedBPD;
978         }
979         
980         /**
981          * Returns the part index (0<x<partCount) which denotes the first part on the last page
982          * generated by the current element list.
983          * @param partCount Number of parts determined by the breaking algorithm
984          * @return the requested part index
985          */

986         public int getStartingPartIndexForLastPage(int partCount) {
987             int result = 0;
988             int idx = 0;
989             int pageIndex = 0;
990             int colIndex = startColumnOfCurrentElementList;
991             Page page = getPage(
992                     false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
993             while (idx < partCount) {
994                 if ((colIndex >= page.getPageViewport().getCurrentSpan().getColumnCount())) {
995                     colIndex = 0;
996                     pageIndex++;
997                     page = getPage(
998                             false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
999                     result = idx;
1000                }
1001                colIndex++;
1002                idx++;
1003            }
1004            return result;
1005        }
1006
1007        /**
1008         * Returns a Page.
1009         * @param isBlank true if this page is supposed to be blank.
1010         * @param index Index of the page (see relativeTo)
1011         * @param relativeTo Defines which value the index parameter should be evaluated relative
1012         * to. (One of PageProvider.RELTO_*)
1013         * @return the requested Page
1014         */

1015        public Page getPage(boolean isBlank, int index, int relativeTo) {
1016            if (relativeTo == RELTO_PAGE_SEQUENCE) {
1017                return getPage(isBlank, index);
1018            } else if (relativeTo == RELTO_CURRENT_ELEMENT_LIST) {
1019                int effIndex = startPageOfCurrentElementList + index;
1020                effIndex += startPageOfPageSequence - 1;
1021                return getPage(isBlank, effIndex);
1022            } else {
1023                throw new IllegalArgumentException JavaDoc(
1024                        "Illegal value for relativeTo: " + relativeTo);
1025            }
1026        }
1027        
1028        private Page getPage(boolean isBlank, int index) {
1029            boolean isLastPage = (lastPageIndex >= 0) && (index == lastPageIndex);
1030            if (log.isTraceEnabled()) {
1031                log.trace("getPage(" + index + " " + (isBlank ? "blank" : "non-blank")
1032                        + (isLastPage ? " <LAST>" : "") + ")");
1033            }
1034            int intIndex = index - startPageOfPageSequence;
1035            if (log.isTraceEnabled()) {
1036                if (isBlank) {
1037                    log.trace("blank page requested: " + index);
1038                }
1039                if (isLastPage) {
1040                    log.trace("last page requested: " + index);
1041                }
1042            }
1043            while (intIndex >= cachedPages.size()) {
1044                if (log.isTraceEnabled()) {
1045                    log.trace("Caching " + index);
1046                }
1047                cacheNextPage(index, isBlank, isLastPage);
1048            }
1049            Page page = (Page)cachedPages.get(intIndex);
1050            boolean replace = false;
1051            if (page.getPageViewport().isBlank() != isBlank) {
1052                log.debug("blank condition doesn't match. Replacing PageViewport.");
1053                replace = true;
1054            }
1055            if ((isLastPage && indexOfCachedLastPage != intIndex)
1056                    || (!isLastPage && indexOfCachedLastPage >= 0)) {
1057                log.debug("last page condition doesn't match. Replacing PageViewport.");
1058                replace = true;
1059                indexOfCachedLastPage = (isLastPage ? intIndex : -1);
1060            }
1061            if (replace) {
1062                disardCacheStartingWith(intIndex);
1063                page = cacheNextPage(index, isBlank, isLastPage);
1064            }
1065            return page;
1066        }
1067
1068        private void disardCacheStartingWith(int index) {
1069            while (index < cachedPages.size()) {
1070                this.cachedPages.remove(cachedPages.size() - 1);
1071                if (!pageSeq.goToPreviousSimplePageMaster()) {
1072                    log.warn("goToPreviousSimplePageMaster() on the first page called!");
1073                }
1074            }
1075        }
1076        
1077        private Page cacheNextPage(int index, boolean isBlank, boolean isLastPage) {
1078            try {
1079                String JavaDoc pageNumberString = pageSeq.makeFormattedPageNumber(index);
1080                SimplePageMaster spm = pageSeq.getNextSimplePageMaster(
1081                        index, (startPageOfPageSequence == index), isLastPage, isBlank);
1082                    
1083                Region body = spm.getRegion(FO_REGION_BODY);
1084                if (!pageSeq.getMainFlow().getFlowName().equals(body.getRegionName())) {
1085                    // this is fine by the XSL Rec (fo:flow's flow-name can be mapped to
1086
// any region), but we don't support it yet.
1087
throw new FOPException("Flow '" + pageSeq.getMainFlow().getFlowName()
1088                        + "' does not map to the region-body in page-master '"
1089                        + spm.getMasterName() + "'. FOP presently "
1090                        + "does not support this.");
1091                }
1092                Page page = new Page(spm, index, pageNumberString, isBlank);
1093                //Set unique key obtained from the AreaTreeHandler
1094
page.getPageViewport().setKey(areaTreeHandler.generatePageViewportKey());
1095                page.getPageViewport().setForeignAttributes(spm.getForeignAttributes());
1096                cachedPages.add(page);
1097                return page;
1098            } catch (FOPException e) {
1099                //TODO Maybe improve. It'll mean to propagate this exception up several
1100
//methods calls.
1101
throw new IllegalStateException JavaDoc(e.getMessage());
1102            }
1103        }
1104        
1105    }
1106
1107    /**
1108     * Act upon the force-page-count trait,
1109     * in relation to the initial-page-number trait of the following page-sequence.
1110     * @param nextPageSeqInitialPageNumber initial-page-number trait of next page-sequence
1111     */

1112    public void doForcePageCount(Numeric nextPageSeqInitialPageNumber) {
1113
1114        int forcePageCount = pageSeq.getForcePageCount();
1115
1116        // xsl-spec version 1.0 (15.oct 2001)
1117
// auto | even | odd | end-on-even | end-on-odd | no-force | inherit
1118
// auto:
1119
// Force the last page in this page-sequence to be an odd-page
1120
// if the initial-page-number of the next page-sequence is even.
1121
// Force it to be an even-page
1122
// if the initial-page-number of the next page-sequence is odd.
1123
// If there is no next page-sequence
1124
// or if the value of its initial-page-number is "auto" do not force any page.
1125

1126
1127        // if force-page-count is auto then set the value of forcePageCount
1128
// depending on the initial-page-number of the next page-sequence
1129
if (nextPageSeqInitialPageNumber != null && forcePageCount == Constants.EN_AUTO) {
1130            if (nextPageSeqInitialPageNumber.getEnum() != 0) {
1131                // auto | auto-odd | auto-even
1132
int nextPageSeqPageNumberType = nextPageSeqInitialPageNumber.getEnum();
1133                if (nextPageSeqPageNumberType == Constants.EN_AUTO_ODD) {
1134                    forcePageCount = Constants.EN_END_ON_EVEN;
1135                } else if (nextPageSeqPageNumberType == Constants.EN_AUTO_EVEN) {
1136                    forcePageCount = Constants.EN_END_ON_ODD;
1137                } else { // auto
1138
forcePageCount = Constants.EN_NO_FORCE;
1139                }
1140            } else { // <integer> for explicit page number
1141
int nextPageSeqPageStart = nextPageSeqInitialPageNumber.getValue();
1142                // spec rule
1143
nextPageSeqPageStart = (nextPageSeqPageStart > 0) ? nextPageSeqPageStart : 1;
1144                if (nextPageSeqPageStart % 2 == 0) { // explicit even startnumber
1145
forcePageCount = Constants.EN_END_ON_ODD;
1146                } else { // explicit odd startnumber
1147
forcePageCount = Constants.EN_END_ON_EVEN;
1148                }
1149            }
1150        }
1151
1152        if (forcePageCount == Constants.EN_EVEN) {
1153            if ((currentPageNum - startPageNum + 1) % 2 != 0) { // we have a odd number of pages
1154
curPage = makeNewPage(true, false);
1155            }
1156        } else if (forcePageCount == Constants.EN_ODD) {
1157            if ((currentPageNum - startPageNum + 1) % 2 == 0) { // we have a even number of pages
1158
curPage = makeNewPage(true, false);
1159            }
1160        } else if (forcePageCount == Constants.EN_END_ON_EVEN) {
1161            if (currentPageNum % 2 != 0) { // we are now on a odd page
1162
curPage = makeNewPage(true, false);
1163            }
1164        } else if (forcePageCount == Constants.EN_END_ON_ODD) {
1165            if (currentPageNum % 2 == 0) { // we are now on a even page
1166
curPage = makeNewPage(true, false);
1167            }
1168        } else if (forcePageCount == Constants.EN_NO_FORCE) {
1169            // i hope: nothing special at all
1170
}
1171
1172        if (curPage != null) {
1173            finishPage();
1174        }
1175    }
1176}
1177
Popular Tags