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: PageSequenceLayoutManager.java 554094 2007-07-07 00:04:25Z adelmelle $ */
019:
020: package org.apache.fop.layoutmgr;
021:
022: import org.apache.commons.logging.Log;
023: import org.apache.commons.logging.LogFactory;
024: import org.apache.fop.datatypes.Numeric;
025:
026: import org.apache.fop.area.AreaTreeHandler;
027: import org.apache.fop.area.AreaTreeModel;
028: import org.apache.fop.area.IDTracker;
029: import org.apache.fop.area.PageViewport;
030: import org.apache.fop.area.LineArea;
031: import org.apache.fop.area.Resolvable;
032:
033: import org.apache.fop.fo.Constants;
034: import org.apache.fop.fo.flow.Marker;
035: import org.apache.fop.fo.flow.RetrieveMarker;
036:
037: import org.apache.fop.fo.pagination.PageSequence;
038: import org.apache.fop.fo.pagination.PageSequenceMaster;
039: import org.apache.fop.fo.pagination.SideRegion;
040: import org.apache.fop.fo.pagination.StaticContent;
041: import org.apache.fop.layoutmgr.inline.ContentLayoutManager;
042:
043: import java.util.List;
044:
045: /**
046: * LayoutManager for a PageSequence. This class is instantiated by
047: * area.AreaTreeHandler for each fo:page-sequence found in the
048: * input document.
049: */
050: public class PageSequenceLayoutManager extends AbstractLayoutManager {
051:
052: private static Log log = LogFactory
053: .getLog(PageSequenceLayoutManager.class);
054:
055: /**
056: * AreaTreeHandler which activates the PSLM and controls
057: * the rendering of its pages.
058: */
059: private AreaTreeHandler areaTreeHandler;
060:
061: /**
062: * fo:page-sequence formatting object being
063: * processed by this class
064: */
065: private PageSequence pageSeq;
066:
067: private PageProvider pageProvider;
068:
069: private IDTracker idTracker;
070:
071: /**
072: * Current page with page-viewport-area being filled by
073: * the PSLM.
074: */
075: private Page curPage;
076:
077: private int startPageNum = 0;
078: private int currentPageNum = 0;
079:
080: /**
081: * Constructor
082: *
083: * @param ath the area tree handler object
084: * @param pseq fo:page-sequence to process
085: */
086: public PageSequenceLayoutManager(AreaTreeHandler ath,
087: PageSequence pseq) {
088: super (pseq);
089: this .areaTreeHandler = ath;
090: this .idTracker = ath.getIDTracker();
091: this .pageSeq = pseq;
092: this .pageProvider = new PageProvider(ath, pseq);
093: }
094:
095: /**
096: * @see org.apache.fop.layoutmgr.LayoutManager
097: * @return the LayoutManagerMaker object
098: */
099: public LayoutManagerMaker getLayoutManagerMaker() {
100: return areaTreeHandler.getLayoutManagerMaker();
101: }
102:
103: /** @return the PageProvider applicable to this page-sequence. */
104: public PageProvider getPageProvider() {
105: return this .pageProvider;
106: }
107:
108: /**
109: * @return the PageSequence being managed by this layout manager
110: */
111: protected PageSequence getPageSequence() {
112: return pageSeq;
113: }
114:
115: /**
116: * Activate the layout of this page sequence.
117: * PageViewports corresponding to each page generated by this
118: * page sequence will be created and sent to the AreaTreeModel
119: * for rendering.
120: */
121: public void activateLayout() {
122: startPageNum = pageSeq.getStartingPageNumber();
123: currentPageNum = startPageNum - 1;
124:
125: LineArea title = null;
126:
127: if (pageSeq.getTitleFO() != null) {
128: try {
129: ContentLayoutManager clm = getLayoutManagerMaker()
130: .makeContentLayoutManager(this ,
131: pageSeq.getTitleFO());
132: title = (LineArea) clm.getParentArea(null);
133: } catch (IllegalStateException e) {
134: // empty title; do nothing
135: }
136: }
137:
138: areaTreeHandler.getAreaTreeModel().startPageSequence(title);
139: if (log.isDebugEnabled()) {
140: log.debug("Starting layout");
141: }
142:
143: curPage = makeNewPage(false, false);
144:
145: PageBreaker breaker = new PageBreaker(this );
146: int flowBPD = (int) getCurrentPV().getBodyRegion()
147: .getRemainingBPD();
148: breaker.doLayout(flowBPD);
149:
150: finishPage();
151: }
152:
153: /**
154: * Finished the page-sequence and notifies everyone about it.
155: */
156: public void finishPageSequence() {
157: if (pageSeq.hasId()) {
158: idTracker.signalIDProcessed(pageSeq.getId());
159: }
160:
161: pageSeq.getRoot().notifyPageSequenceFinished(currentPageNum,
162: (currentPageNum - startPageNum) + 1);
163: areaTreeHandler.notifyPageSequenceFinished(pageSeq,
164: (currentPageNum - startPageNum) + 1);
165: pageSeq.releasePageSequence();
166:
167: // If this sequence has a page sequence master so we must reset
168: // it in preparation for the next sequence
169: String masterReference = pageSeq.getMasterReference();
170: PageSequenceMaster pageSeqMaster = pageSeq.getRoot()
171: .getLayoutMasterSet().getPageSequenceMaster(
172: masterReference);
173: if (pageSeqMaster != null) {
174: pageSeqMaster.reset();
175: }
176:
177: if (log.isDebugEnabled()) {
178: log.debug("Ending layout");
179: }
180: }
181:
182: /**
183: * Provides access to the current page.
184: * @return the current Page
185: */
186: public Page getCurrentPage() {
187: return curPage;
188: }
189:
190: /**
191: * Provides access for setting the current page.
192: * @param currentPage the new current Page
193: */
194: protected void setCurrentPage(Page currentPage) {
195: this .curPage = currentPage;
196: }
197:
198: /**
199: * Provides access to the current page number
200: * @return the current page number
201: */
202: protected int getCurrentPageNum() {
203: return currentPageNum;
204: }
205:
206: /**
207: * Provides access to the current page viewport.
208: * @return the current PageViewport
209: */
210: /*
211: public PageViewport getCurrentPageViewport() {
212: return curPage.getPageViewport();
213: }*/
214:
215: /**
216: * Provides access to this object
217: * @return this PageSequenceLayoutManager instance
218: */
219: public PageSequenceLayoutManager getPSLM() {
220: return this ;
221: }
222:
223: /**
224: * This returns the first PageViewport that contains an id trait
225: * matching the idref argument, or null if no such PV exists.
226: *
227: * @param idref the idref trait needing to be resolved
228: * @return the first PageViewport that contains the ID trait
229: */
230: public PageViewport getFirstPVWithID(String idref) {
231: List list = idTracker.getPageViewportsContainingID(idref);
232: if (list != null && list.size() > 0) {
233: return (PageViewport) list.get(0);
234: }
235: return null;
236: }
237:
238: /**
239: * This returns the last PageViewport that contains an id trait
240: * matching the idref argument, or null if no such PV exists.
241: *
242: * @param idref the idref trait needing to be resolved
243: * @return the last PageViewport that contains the ID trait
244: */
245: public PageViewport getLastPVWithID(String idref) {
246: List list = idTracker.getPageViewportsContainingID(idref);
247: if (list != null && list.size() > 0) {
248: return (PageViewport) list.get(list.size() - 1);
249: }
250: return null;
251: }
252:
253: /**
254: * Add an ID reference to the current page.
255: * When adding areas the area adds its ID reference.
256: * For the page layout manager it adds the id reference
257: * with the current page to the area tree.
258: *
259: * @param id the ID reference to add
260: */
261: public void addIDToPage(String id) {
262: if (id != null && id.length() > 0) {
263: idTracker.associateIDWithPageViewport(id, curPage
264: .getPageViewport());
265: }
266: }
267:
268: /**
269: * Add an id reference of the layout manager in the AreaTreeHandler,
270: * if the id hasn't been resolved yet
271: * @param id the id to track
272: * @return a boolean indicating if the id has already been resolved
273: * TODO Maybe give this a better name
274: */
275: public boolean associateLayoutManagerID(String id) {
276: if (log.isDebugEnabled()) {
277: log.debug("associateLayoutManagerID(" + id + ")");
278: }
279: if (!idTracker.alreadyResolvedID(id)) {
280: idTracker.signalPendingID(id);
281: return false;
282: } else {
283: return true;
284: }
285: }
286:
287: /**
288: * Notify the areaTreeHandler that the LayoutManagers containing
289: * idrefs have finished creating areas
290: * @param id the id for which layout has finished
291: */
292: public void notifyEndOfLayout(String id) {
293: idTracker.signalIDProcessed(id);
294: }
295:
296: /**
297: * Identify an unresolved area (one needing an idref to be
298: * resolved, e.g. the internal-destination of an fo:basic-link)
299: * for both the AreaTreeHandler and PageViewport object.
300: *
301: * The IDTracker keeps a document-wide list of idref's
302: * and the PV's needing them to be resolved. It uses this to
303: * send notifications to the PV's when an id has been resolved.
304: *
305: * The PageViewport keeps lists of id's needing resolving, along
306: * with the child areas (page-number-citation, basic-link, etc.)
307: * of the PV needing their resolution.
308: *
309: * @param id the ID reference to add
310: * @param res the resolvable object that needs resolving
311: */
312: public void addUnresolvedArea(String id, Resolvable res) {
313: curPage.getPageViewport().addUnresolvedIDRef(id, res);
314: idTracker.addUnresolvedIDRef(id, curPage.getPageViewport());
315: }
316:
317: /**
318: * Bind the RetrieveMarker to the corresponding Marker subtree.
319: * If the boundary is page then it will only check the
320: * current page. For page-sequence and document it will
321: * lookup preceding pages from the area tree and try to find
322: * a marker.
323: * If we retrieve a marker from a preceding page,
324: * then the containing page does not have a qualifying area,
325: * and all qualifying areas have ended.
326: * Therefore we use last-ending-within-page (Constants.EN_LEWP)
327: * as the position.
328: *
329: * @param rm the RetrieveMarker instance whose properties are to
330: * used to find the matching Marker.
331: * @return a bound RetrieveMarker instance, or null if no Marker
332: * could be found.
333: */
334: public RetrieveMarker resolveRetrieveMarker(RetrieveMarker rm) {
335: AreaTreeModel areaTreeModel = areaTreeHandler
336: .getAreaTreeModel();
337: String name = rm.getRetrieveClassName();
338: int pos = rm.getRetrievePosition();
339: int boundary = rm.getRetrieveBoundary();
340:
341: // get marker from the current markers on area tree
342: Marker mark = (Marker) getCurrentPV().getMarker(name, pos);
343: if (mark == null && boundary != EN_PAGE) {
344: // go back over pages until mark found
345: // if document boundary then keep going
346: boolean doc = boundary == EN_DOCUMENT;
347: int seq = areaTreeModel.getPageSequenceCount();
348: int page = areaTreeModel.getPageCount(seq) - 1;
349: while (page < 0 && doc && seq > 1) {
350: seq--;
351: page = areaTreeModel.getPageCount(seq) - 1;
352: }
353: while (page >= 0) {
354: PageViewport pv = areaTreeModel.getPage(seq, page);
355: mark = (Marker) pv.getMarker(name, Constants.EN_LEWP);
356: if (mark != null) {
357: break;
358: }
359: page--;
360: if (page < 0 && doc && seq > 1) {
361: seq--;
362: page = areaTreeModel.getPageCount(seq) - 1;
363: }
364: }
365: }
366:
367: if (mark == null) {
368: log.debug("found no marker with name: " + name);
369: return null;
370: } else {
371: rm.bindMarker(mark);
372: return rm;
373: }
374: }
375:
376: /**
377: * Makes a new page
378: *
379: * @param bIsBlank whether this page is blank or not
380: * @param bIsLast whether this page is the last page or not
381: * @return a new page
382: */
383: protected Page makeNewPage(boolean bIsBlank, boolean bIsLast) {
384: if (curPage != null) {
385: finishPage();
386: }
387:
388: currentPageNum++;
389:
390: curPage = pageProvider.getPage(bIsBlank, currentPageNum,
391: PageProvider.RELTO_PAGE_SEQUENCE);
392:
393: if (log.isDebugEnabled()) {
394: log.debug("["
395: + curPage.getPageViewport().getPageNumberString()
396: + (bIsBlank ? "*" : "") + "]");
397: }
398:
399: addIDToPage(pageSeq.getId());
400: return curPage;
401: }
402:
403: private void layoutSideRegion(int regionID) {
404: SideRegion reg = (SideRegion) curPage.getSimplePageMaster()
405: .getRegion(regionID);
406: if (reg == null) {
407: return;
408: }
409: StaticContent sc = pageSeq
410: .getStaticContent(reg.getRegionName());
411: if (sc == null) {
412: return;
413: }
414:
415: StaticContentLayoutManager lm = (StaticContentLayoutManager) getLayoutManagerMaker()
416: .makeStaticContentLayoutManager(this , sc, reg);
417: lm.doLayout();
418: }
419:
420: private void finishPage() {
421: if (log.isTraceEnabled()) {
422: curPage.getPageViewport().dumpMarkers();
423: }
424: // Layout side regions
425: layoutSideRegion(FO_REGION_BEFORE);
426: layoutSideRegion(FO_REGION_AFTER);
427: layoutSideRegion(FO_REGION_START);
428: layoutSideRegion(FO_REGION_END);
429:
430: // Try to resolve any unresolved IDs for the current page.
431: //
432: idTracker.tryIDResolution(curPage.getPageViewport());
433: // Queue for ID resolution and rendering
434: areaTreeHandler.getAreaTreeModel().addPage(
435: curPage.getPageViewport());
436: if (log.isDebugEnabled()) {
437: log.debug("page finished: "
438: + curPage.getPageViewport().getPageNumberString()
439: + ", current num: " + currentPageNum);
440: }
441: curPage = null;
442: }
443:
444: /**
445: * Act upon the force-page-count trait,
446: * in relation to the initial-page-number trait of the following page-sequence.
447: * @param nextPageSeqInitialPageNumber initial-page-number trait of next page-sequence
448: */
449: public void doForcePageCount(Numeric nextPageSeqInitialPageNumber) {
450:
451: int forcePageCount = pageSeq.getForcePageCount();
452:
453: // xsl-spec version 1.0 (15.oct 2001)
454: // auto | even | odd | end-on-even | end-on-odd | no-force | inherit
455: // auto:
456: // Force the last page in this page-sequence to be an odd-page
457: // if the initial-page-number of the next page-sequence is even.
458: // Force it to be an even-page
459: // if the initial-page-number of the next page-sequence is odd.
460: // If there is no next page-sequence
461: // or if the value of its initial-page-number is "auto" do not force any page.
462:
463: // if force-page-count is auto then set the value of forcePageCount
464: // depending on the initial-page-number of the next page-sequence
465: if (nextPageSeqInitialPageNumber != null
466: && forcePageCount == Constants.EN_AUTO) {
467: if (nextPageSeqInitialPageNumber.getEnum() != 0) {
468: // auto | auto-odd | auto-even
469: int nextPageSeqPageNumberType = nextPageSeqInitialPageNumber
470: .getEnum();
471: if (nextPageSeqPageNumberType == Constants.EN_AUTO_ODD) {
472: forcePageCount = Constants.EN_END_ON_EVEN;
473: } else if (nextPageSeqPageNumberType == Constants.EN_AUTO_EVEN) {
474: forcePageCount = Constants.EN_END_ON_ODD;
475: } else { // auto
476: forcePageCount = Constants.EN_NO_FORCE;
477: }
478: } else { // <integer> for explicit page number
479: int nextPageSeqPageStart = nextPageSeqInitialPageNumber
480: .getValue();
481: // spec rule
482: nextPageSeqPageStart = (nextPageSeqPageStart > 0) ? nextPageSeqPageStart
483: : 1;
484: if (nextPageSeqPageStart % 2 == 0) { // explicit even startnumber
485: forcePageCount = Constants.EN_END_ON_ODD;
486: } else { // explicit odd startnumber
487: forcePageCount = Constants.EN_END_ON_EVEN;
488: }
489: }
490: }
491:
492: if (forcePageCount == Constants.EN_EVEN) {
493: if ((currentPageNum - startPageNum + 1) % 2 != 0) { // we have an odd number of pages
494: curPage = makeNewPage(true, false);
495: }
496: } else if (forcePageCount == Constants.EN_ODD) {
497: if ((currentPageNum - startPageNum + 1) % 2 == 0) { // we have an even number of pages
498: curPage = makeNewPage(true, false);
499: }
500: } else if (forcePageCount == Constants.EN_END_ON_EVEN) {
501: if (currentPageNum % 2 != 0) { // we are now on an odd page
502: curPage = makeNewPage(true, false);
503: }
504: } else if (forcePageCount == Constants.EN_END_ON_ODD) {
505: if (currentPageNum % 2 == 0) { // we are now on an even page
506: curPage = makeNewPage(true, false);
507: }
508: } else if (forcePageCount == Constants.EN_NO_FORCE) {
509: // i hope: nothing special at all
510: }
511:
512: if (curPage != null) {
513: finishPage();
514: }
515: }
516: }
|