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: PageViewport.java 535866 2007-05-07 12:22:22Z jeremias $ */
019:
020: package org.apache.fop.area;
021:
022: import java.awt.Rectangle;
023: import java.awt.geom.Rectangle2D;
024: import java.io.ObjectOutputStream;
025: import java.io.ObjectInputStream;
026: import java.util.ArrayList;
027: import java.util.Collections;
028: import java.util.List;
029: import java.util.Map;
030: import java.util.HashMap;
031: import java.util.Iterator;
032: import java.util.Set;
033:
034: import org.apache.commons.logging.Log;
035: import org.apache.commons.logging.LogFactory;
036:
037: import org.apache.fop.fo.Constants;
038: import org.apache.fop.fo.extensions.ExtensionAttachment;
039: import org.apache.fop.fo.pagination.SimplePageMaster;
040:
041: /**
042: * Page viewport that specifies the viewport area and holds the page contents.
043: * This is the top level object for a page and remains valid for the life
044: * of the document and the area tree.
045: * This object may be used as a key to reference a page.
046: * This is the level that creates the page.
047: * The page (reference area) is then rendered inside the page object
048: */
049: public class PageViewport extends AreaTreeObject implements Resolvable,
050: Cloneable {
051:
052: private Page page;
053: private Rectangle2D viewArea;
054: private String simplePageMasterName;
055:
056: /**
057: * Unique key to identify the page. pageNumberString and pageIndex are both no option
058: * for this.
059: */
060: private String pageKey;
061:
062: private int pageNumber = -1;
063: private String pageNumberString = null;
064: private int pageIndex = -1; //-1 = undetermined
065: private boolean blank;
066:
067: private transient PageSequence pageSequence;
068:
069: // list of id references and the rectangle on the page
070: //private Map idReferences = null;
071:
072: // set of IDs that appear first (or exclusively) on this page:
073: private Set idFirsts = new java.util.HashSet();
074:
075: // this keeps a list of currently unresolved areas or extensions
076: // once an idref is resolved it is removed
077: // when this is empty the page can be rendered
078: private Map unresolvedIDRefs = new java.util.HashMap();
079:
080: private Map pendingResolved = null;
081:
082: // hashmap of markers for this page
083: // start and end are added by the fo that contains the markers
084: private Map markerFirstStart = null;
085: private Map markerLastStart = null;
086: private Map markerFirstAny = null;
087: private Map markerLastEnd = null;
088: private Map markerLastAny = null;
089:
090: //Arbitrary attachments to the page from extensions that need to pass information
091: //down to the renderers.
092: private List extensionAttachments = null;
093:
094: /**
095: * logging instance
096: */
097: protected static Log log = LogFactory.getLog(PageViewport.class);
098:
099: /**
100: * Create a page viewport.
101: * @param spm SimplePageMaster indicating the page and region dimensions
102: * @param pageNumber the page number
103: * @param pageStr String representation of the page number
104: * @param blank true if this is a blank page
105: */
106: public PageViewport(SimplePageMaster spm, int pageNumber,
107: String pageStr, boolean blank) {
108: this .simplePageMasterName = spm.getMasterName();
109: this .extensionAttachments = spm.getExtensionAttachments();
110: this .blank = blank;
111: int pageWidth = spm.getPageWidth().getValue();
112: int pageHeight = spm.getPageHeight().getValue();
113: this .pageNumber = pageNumber;
114: this .pageNumberString = pageStr;
115: this .viewArea = new Rectangle(0, 0, pageWidth, pageHeight);
116: this .page = new Page(spm);
117: createSpan(false);
118: }
119:
120: /**
121: * Copy constructor.
122: * @param original the original PageViewport to copy from
123: */
124: public PageViewport(PageViewport original) {
125: if (original.extensionAttachments != null) {
126: this .extensionAttachments = new java.util.ArrayList(
127: original.extensionAttachments);
128: }
129: this .pageNumber = original.pageNumber;
130: this .pageNumberString = original.pageNumberString;
131: this .page = (Page) original.page.clone();
132: this .viewArea = (Rectangle2D) original.viewArea.clone();
133: this .simplePageMasterName = original.simplePageMasterName;
134: this .blank = original.blank;
135: }
136:
137: /**
138: * Constructor used by the area tree parser.
139: * @param viewArea the view area
140: * @param pageNumber the page number
141: * @param pageStr String representation of the page number
142: * @param simplePageMasterName name of the original simple-page-master that generated this page
143: * @param blank true if this is a blank page
144: */
145: public PageViewport(Rectangle2D viewArea, int pageNumber,
146: String pageStr, String simplePageMasterName, boolean blank) {
147: this .viewArea = viewArea;
148: this .pageNumber = pageNumber;
149: this .pageNumberString = pageStr;
150: this .simplePageMasterName = simplePageMasterName;
151: this .blank = blank;
152: }
153:
154: /**
155: * Sets the page sequence this page belongs to
156: * @param seq the page sequence
157: */
158: public void setPageSequence(PageSequence seq) {
159: this .pageSequence = seq;
160: }
161:
162: /** @return the page sequence this page belongs to */
163: public PageSequence getPageSequence() {
164: return this .pageSequence;
165: }
166:
167: /**
168: * Get the view area rectangle of this viewport.
169: * @return the rectangle for this viewport
170: */
171: public Rectangle2D getViewArea() {
172: return viewArea;
173: }
174:
175: /**
176: * Get the page reference area with the contents.
177: * @return the page reference area
178: */
179: public Page getPage() {
180: return page;
181: }
182:
183: /**
184: * Sets the page object for this PageViewport.
185: * @param page the page
186: */
187: public void setPage(Page page) {
188: this .page = page;
189: }
190:
191: /**
192: * Get the page number of this page.
193: * @return the integer value that represents this page
194: */
195: public int getPageNumber() {
196: return pageNumber;
197: }
198:
199: /**
200: * Get the page number of this page.
201: * @return the string that represents this page
202: */
203: public String getPageNumberString() {
204: return pageNumberString;
205: }
206:
207: /**
208: * Sets the page index of the page in this rendering run.
209: * (This is not the same as the page number!)
210: * @param index the page index (zero-based), -1 if it is undetermined
211: */
212: public void setPageIndex(int index) {
213: this .pageIndex = index;
214: }
215:
216: /**
217: * @return the overall page index of the page in this rendering run (zero-based,
218: * -1 if it is undetermined).
219: */
220: public int getPageIndex() {
221: return this .pageIndex;
222: }
223:
224: /**
225: * Sets the unique key for this PageViewport that will be used to reference this page.
226: * @param key the unique key.
227: */
228: public void setKey(String key) {
229: this .pageKey = key;
230: }
231:
232: /**
233: * Get the key for this page viewport.
234: * This is used so that a serializable key can be used to
235: * lookup the page or some other reference.
236: *
237: * @return a unique page viewport key for this area tree
238: */
239: public String getKey() {
240: if (this .pageKey == null) {
241: throw new IllegalStateException(
242: "No page key set on the PageViewport: "
243: + toString());
244: }
245: return this .pageKey;
246: }
247:
248: /**
249: * Add an "ID-first" to this page.
250: * This is typically called by the AreaTreeHandler when associating
251: * an ID with a PageViewport.
252: *
253: * @param id the id to be registered as first appearing on this page
254: */
255: public void setFirstWithID(String id) {
256: if (id != null) {
257: idFirsts.add(id);
258: }
259: }
260:
261: /**
262: * Check whether a certain id first appears on this page
263: *
264: * @param id the id to be checked
265: * @return true if this page is the first where the id appears
266: */
267: public boolean isFirstWithID(String id) {
268: return idFirsts.contains(id);
269: }
270:
271: /**
272: * Add an idref to this page.
273: * All idrefs found for child areas of this PageViewport are added
274: * to unresolvedIDRefs, for subsequent resolution by AreaTreeHandler
275: * calls to this object's resolveIDRef().
276: *
277: * @param idref the idref
278: * @param res the child element of this page that needs this
279: * idref resolved
280: */
281: public void addUnresolvedIDRef(String idref, Resolvable res) {
282: if (unresolvedIDRefs == null) {
283: unresolvedIDRefs = new HashMap();
284: }
285: List list = (List) unresolvedIDRefs.get(idref);
286: if (list == null) {
287: list = new ArrayList();
288: unresolvedIDRefs.put(idref, list);
289: }
290: list.add(res);
291: }
292:
293: /**
294: * Check if this page has been fully resolved.
295: * @return true if the page is resolved and can be rendered
296: */
297: public boolean isResolved() {
298: return unresolvedIDRefs == null || unresolvedIDRefs.size() == 0;
299: }
300:
301: /**
302: * Get the unresolved idrefs for this page.
303: * @return String array of idref's that still have not been resolved
304: */
305: public String[] getIDRefs() {
306: return (unresolvedIDRefs == null) ? null
307: : (String[]) unresolvedIDRefs.keySet().toArray(
308: new String[] {});
309: }
310:
311: /**
312: * @see org.apache.fop.area.Resolvable#resolveIDRef(String, List)
313: */
314: public void resolveIDRef(String id, List pages) {
315: if (page == null) {
316: if (pendingResolved == null) {
317: pendingResolved = new HashMap();
318: }
319: pendingResolved.put(id, pages);
320: } else {
321: if (unresolvedIDRefs != null) {
322: List todo = (List) unresolvedIDRefs.get(id);
323: if (todo != null) {
324: for (int count = 0; count < todo.size(); count++) {
325: Resolvable res = (Resolvable) todo.get(count);
326: res.resolveIDRef(id, pages);
327: }
328: }
329: }
330: }
331: if (unresolvedIDRefs != null && pages != null) {
332: unresolvedIDRefs.remove(id);
333: if (unresolvedIDRefs.isEmpty()) {
334: unresolvedIDRefs = null;
335: }
336: }
337: }
338:
339: /**
340: * Add the markers for this page.
341: * Only the required markers are kept.
342: * For "first-starting-within-page" it adds the markers
343: * that are starting only if the marker class name is not
344: * already added.
345: * For "first-including-carryover" it adds any starting marker
346: * if the marker class name is not already added.
347: * For "last-starting-within-page" it adds all marks that
348: * are starting, replacing earlier markers.
349: * For "last-ending-within-page" it adds all markers that
350: * are ending, replacing earlier markers.
351: *
352: * Should this logic be placed in the Page layout manager.
353: *
354: * @param marks the map of markers to add
355: * @param starting if the area being added is starting or ending
356: * @param isfirst if the area being added has is-first trait
357: * @param islast if the area being added has is-last trait
358: */
359: public void addMarkers(Map marks, boolean starting,
360: boolean isfirst, boolean islast) {
361:
362: if (marks == null) {
363: return;
364: }
365: if (log.isDebugEnabled()) {
366: log.debug("--" + marks.keySet() + ": "
367: + (starting ? "starting" : "ending")
368: + (isfirst ? ", first" : "")
369: + (islast ? ", last" : ""));
370: }
371:
372: // at the start of the area, register is-first and any areas
373: if (starting) {
374: if (isfirst) {
375: if (markerFirstStart == null) {
376: markerFirstStart = new HashMap();
377: }
378: if (markerFirstAny == null) {
379: markerFirstAny = new HashMap();
380: }
381: // first on page: only put in new values, leave current
382: for (Iterator iter = marks.keySet().iterator(); iter
383: .hasNext();) {
384: Object key = iter.next();
385: if (!markerFirstStart.containsKey(key)) {
386: markerFirstStart.put(key, marks.get(key));
387: if (log.isTraceEnabled()) {
388: log.trace("page " + pageNumberString + ": "
389: + "Adding marker " + key
390: + " to FirstStart");
391: }
392: }
393: if (!markerFirstAny.containsKey(key)) {
394: markerFirstAny.put(key, marks.get(key));
395: if (log.isTraceEnabled()) {
396: log.trace("page " + pageNumberString + ": "
397: + "Adding marker " + key
398: + " to FirstAny");
399: }
400: }
401: }
402: if (markerLastStart == null) {
403: markerLastStart = new HashMap();
404: }
405: // last on page: replace all
406: markerLastStart.putAll(marks);
407: if (log.isTraceEnabled()) {
408: log.trace("page " + pageNumberString + ": "
409: + "Adding all markers to LastStart");
410: }
411: } else {
412: if (markerFirstAny == null) {
413: markerFirstAny = new HashMap();
414: }
415: // first on page: only put in new values, leave current
416: for (Iterator iter = marks.keySet().iterator(); iter
417: .hasNext();) {
418: Object key = iter.next();
419: if (!markerFirstAny.containsKey(key)) {
420: markerFirstAny.put(key, marks.get(key));
421: if (log.isTraceEnabled()) {
422: log.trace("page " + pageNumberString + ": "
423: + "Adding marker " + key
424: + " to FirstAny");
425: }
426: }
427: }
428: }
429: } else {
430: // at the end of the area, register is-last and any areas
431: if (islast) {
432: if (markerLastEnd == null) {
433: markerLastEnd = new HashMap();
434: }
435: // last on page: replace all
436: markerLastEnd.putAll(marks);
437: if (log.isTraceEnabled()) {
438: log.trace("page " + pageNumberString + ": "
439: + "Adding all markers to LastEnd");
440: }
441: }
442: if (markerLastAny == null) {
443: markerLastAny = new HashMap();
444: }
445: // last on page: replace all
446: markerLastAny.putAll(marks);
447: if (log.isTraceEnabled()) {
448: log.trace("page " + pageNumberString + ": "
449: + "Adding all markers to LastAny");
450: }
451: }
452: }
453:
454: /**
455: * Get a marker from this page.
456: * This will retrieve a marker with the class name
457: * and position.
458: *
459: * @param name The class name of the marker to retrieve
460: * @param pos the position to retrieve
461: * @return Object the marker found or null
462: */
463: public Object getMarker(String name, int pos) {
464: Object mark = null;
465: String posName = null;
466: switch (pos) {
467: case Constants.EN_FSWP:
468: if (markerFirstStart != null) {
469: mark = markerFirstStart.get(name);
470: posName = "FSWP";
471: }
472: if (mark == null && markerFirstAny != null) {
473: mark = markerFirstAny.get(name);
474: posName = "FirstAny after " + posName;
475: }
476: break;
477: case Constants.EN_FIC:
478: if (markerFirstAny != null) {
479: mark = markerFirstAny.get(name);
480: posName = "FIC";
481: }
482: break;
483: case Constants.EN_LSWP:
484: if (markerLastStart != null) {
485: mark = markerLastStart.get(name);
486: posName = "LSWP";
487: }
488: if (mark == null && markerLastAny != null) {
489: mark = markerLastAny.get(name);
490: posName = "LastAny after " + posName;
491: }
492: break;
493: case Constants.EN_LEWP:
494: if (markerLastEnd != null) {
495: mark = markerLastEnd.get(name);
496: posName = "LEWP";
497: }
498: if (mark == null && markerLastAny != null) {
499: mark = markerLastAny.get(name);
500: posName = "LastAny after " + posName;
501: }
502: break;
503: default:
504: throw new RuntimeException();
505: }
506: if (log.isTraceEnabled()) {
507: log.trace("page " + pageNumberString + ": "
508: + "Retrieving marker " + name + " at position "
509: + posName);
510: }
511: return mark;
512: }
513:
514: /** Dumps the current marker data to the logger. */
515: public void dumpMarkers() {
516: if (log.isTraceEnabled()) {
517: log.trace("FirstAny: " + this .markerFirstAny);
518: log.trace("FirstStart: " + this .markerFirstStart);
519: log.trace("LastAny: " + this .markerLastAny);
520: log.trace("LastEnd: " + this .markerLastEnd);
521: log.trace("LastStart: " + this .markerLastStart);
522: }
523: }
524:
525: /**
526: * Save the page contents to an object stream.
527: * The map of unresolved references are set on the page so that
528: * the resolvers can be properly serialized and reloaded.
529: * @param out the object output stream to write the contents
530: * @throws Exception if there is a problem saving the page
531: */
532: public void savePage(ObjectOutputStream out) throws Exception {
533: // set the unresolved references so they are serialized
534: page.setUnresolvedReferences(unresolvedIDRefs);
535: out.writeObject(page);
536: page = null;
537: }
538:
539: /**
540: * Load the page contents from an object stream.
541: * This loads the page contents from the stream and
542: * if there are any unresolved references that were resolved
543: * while saved they will be resolved on the page contents.
544: * @param in the object input stream to read the page from
545: * @throws Exception if there is an error loading the page
546: */
547: public void loadPage(ObjectInputStream in) throws Exception {
548: page = (Page) in.readObject();
549: unresolvedIDRefs = page.getUnresolvedReferences();
550: if (unresolvedIDRefs != null && pendingResolved != null) {
551: for (Iterator iter = pendingResolved.keySet().iterator(); iter
552: .hasNext();) {
553: String id = (String) iter.next();
554: resolveIDRef(id, (List) pendingResolved.get(id));
555: }
556: pendingResolved = null;
557: }
558: }
559:
560: /**
561: * Clone this page.
562: * Used by the page master to create a copy of an original page.
563: * @return a copy of this page and associated viewports
564: */
565: public Object clone() {
566: return new PageViewport(this );
567: }
568:
569: /**
570: * Clear the page contents to save memory.
571: * This object is kept for the life of the area tree since
572: * it holds id and marker information and is used as a key.
573: */
574: public void clear() {
575: page = null;
576: }
577:
578: /**
579: * @see java.lang.Object#toString()
580: */
581: public String toString() {
582: StringBuffer sb = new StringBuffer(64);
583: sb.append("PageViewport: page=");
584: sb.append(getPageNumberString());
585: return sb.toString();
586: }
587:
588: /** @return the name of the simple-page-master that created this page */
589: public String getSimplePageMasterName() {
590: return this .simplePageMasterName;
591: }
592:
593: /**
594: * Adds a new ExtensionAttachment instance to this page.
595: * @param attachment the ExtensionAttachment
596: */
597: public void addExtensionAttachment(ExtensionAttachment attachment) {
598: if (this .extensionAttachments == null) {
599: this .extensionAttachments = new java.util.ArrayList();
600: }
601: extensionAttachments.add(attachment);
602: }
603:
604: /** @return the list of extension attachments for this page */
605: public List getExtensionAttachments() {
606: if (this .extensionAttachments == null) {
607: return Collections.EMPTY_LIST;
608: } else {
609: return this .extensionAttachments;
610: }
611: }
612:
613: /** @return True if this is a blank page. */
614: public boolean isBlank() {
615: return this .blank;
616: }
617:
618: /**
619: * Convenience method to get BodyRegion of this PageViewport
620: * @return BodyRegion object
621: */
622: public BodyRegion getBodyRegion() {
623: return (BodyRegion) getPage().getRegionViewport(
624: Constants.FO_REGION_BODY).getRegionReference();
625: }
626:
627: /**
628: * Convenience method to create a new Span for this
629: * this PageViewport.
630: *
631: * @param spanAll whether this is a single-column span
632: * @return Span object created
633: */
634: public Span createSpan(boolean spanAll) {
635: return getBodyRegion().getMainReference().createSpan(spanAll);
636: }
637:
638: /**
639: * Convenience method to get the span-reference-area currently
640: * being processed
641: *
642: * @return span currently being processed.
643: */
644: public Span getCurrentSpan() {
645: return getBodyRegion().getMainReference().getCurrentSpan();
646: }
647:
648: /**
649: * Convenience method to get the normal-flow-reference-area
650: * currently being processed
651: *
652: * @return span currently being processed.
653: */
654: public NormalFlow getCurrentFlow() {
655: return getCurrentSpan().getCurrentFlow();
656: }
657:
658: /**
659: * Convenience method to increment the Span to the
660: * next NormalFlow to be processed, and to return that flow.
661: *
662: * @return the next NormalFlow in the Span.
663: */
664: public NormalFlow moveToNextFlow() {
665: return getCurrentSpan().moveToNextFlow();
666: }
667:
668: /**
669: * Convenience method to return a given region-reference-area,
670: * keyed by the Constants class identifier for the corresponding
671: * formatting object (ie. Constants.FO_REGION_BODY, FO_REGION_START,
672: * etc.)
673: *
674: * @param id the Constants class identifier for the region.
675: * @return the corresponding region-reference-area for this page.
676: */
677: public RegionReference getRegionReference(int id) {
678: return getPage().getRegionViewport(id).getRegionReference();
679: }
680: }
|