| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* $Id$ */ |
| |
| package org.apache.fop.area; |
| |
| import java.awt.Rectangle; |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| import org.apache.fop.apps.FOPException; |
| import org.apache.fop.fo.flow.AbstractRetrieveMarker; |
| import org.apache.fop.fo.flow.Marker; |
| import org.apache.fop.fo.flow.Markers; |
| import org.apache.fop.fo.pagination.SimplePageMaster; |
| import org.apache.fop.traits.WritingModeTraitsGetter; |
| |
| import static org.apache.fop.fo.Constants.FO_REGION_BODY; |
| |
| /** |
| * Page viewport that specifies the viewport area and holds the page contents. |
| * This is the top level object for a page and remains valid for the life |
| * of the document and the area tree. |
| * This object may be used as a key to reference a page. |
| * This is the level that creates the page. |
| * The page (reference area) is then rendered inside the page object |
| */ |
| public class PageViewport extends AreaTreeObject implements Resolvable { |
| |
| private Page page; |
| private Rectangle viewArea; |
| private String simplePageMasterName; |
| |
| /** |
| * Unique key to identify the page. pageNumberString and pageIndex are both no option |
| * for this. |
| */ |
| private String pageKey; |
| |
| private int pageNumber = -1; |
| private String pageNumberString; |
| private int pageIndex = -1; //-1 = undetermined |
| private boolean blank; |
| |
| private transient PageSequence pageSequence; |
| |
| // set of IDs that appear first (or exclusively) on this page: |
| private Set<String> idFirsts = new java.util.HashSet<String>(); |
| |
| // this keeps a list of currently unresolved areas or extensions |
| // once an idref is resolved it is removed |
| // when this is empty the page can be rendered |
| private Map<String, List<Resolvable>> unresolvedIDRefs |
| = new java.util.HashMap<String, List<Resolvable>>(); |
| |
| private Map<String, List<PageViewport>> pendingResolved; |
| |
| private Markers pageMarkers; |
| |
| /** |
| * logging instance |
| */ |
| protected static final Log log = LogFactory.getLog(PageViewport.class); |
| |
| /** |
| * Create a page viewport. |
| * @param spm SimplePageMaster indicating the page and region dimensions |
| * @param pageNumber the page number |
| * @param pageStr String representation of the page number |
| * @param blank true if this is a blank page |
| * @param spanAll true if the first span area spans all columns |
| */ |
| public PageViewport(SimplePageMaster spm, int pageNumber, String pageStr, |
| boolean blank, boolean spanAll) { |
| this.simplePageMasterName = spm.getMasterName(); |
| setExtensionAttachments(spm.getExtensionAttachments()); |
| setForeignAttributes(spm.getForeignAttributes()); |
| this.blank = blank; |
| int pageWidth = spm.getPageWidth().getValue(); |
| int pageHeight = spm.getPageHeight().getValue(); |
| this.pageNumber = pageNumber; |
| this.pageNumberString = pageStr; |
| this.viewArea = new Rectangle(0, 0, pageWidth, pageHeight); |
| this.page = new Page(spm); |
| createSpan(spanAll); |
| } |
| |
| /** |
| * Create a page viewport. |
| * @param spm SimplePageMaster indicating the page and region dimensions |
| * @param pageNumber the page number |
| * @param pageStr String representation of the page number |
| * @param blank true if this is a blank page |
| */ |
| public PageViewport(SimplePageMaster spm, int pageNumber, String pageStr, boolean blank) { |
| this(spm, pageNumber, pageStr, blank, false); |
| } |
| |
| /** |
| * Copy constructor. |
| * @param original the original PageViewport to copy from |
| * @throws FOPException when cloning of the page is not supported |
| */ |
| public PageViewport(PageViewport original) throws FOPException { |
| if (original.extensionAttachments != null) { |
| setExtensionAttachments(original.extensionAttachments); |
| } |
| if (original.foreignAttributes != null) { |
| setForeignAttributes(original.foreignAttributes); |
| } |
| this.pageIndex = original.pageIndex; |
| this.pageNumber = original.pageNumber; |
| this.pageNumberString = original.pageNumberString; |
| try { |
| this.page = (Page) original.page.clone(); |
| } catch (CloneNotSupportedException e) { |
| throw new FOPException(e); |
| } |
| this.viewArea = new Rectangle(original.viewArea); |
| this.simplePageMasterName = original.simplePageMasterName; |
| this.blank = original.blank; |
| } |
| |
| /** |
| * Constructor used by the area tree parser. |
| * @param viewArea the view area |
| * @param pageNumber the page number |
| * @param pageStr String representation of the page number |
| * @param simplePageMasterName name of the original simple-page-master that generated this page |
| * @param blank true if this is a blank page |
| */ |
| public PageViewport(Rectangle viewArea, int pageNumber, String pageStr, |
| String simplePageMasterName, boolean blank) { |
| this.viewArea = viewArea; |
| this.pageNumber = pageNumber; |
| this.pageNumberString = pageStr; |
| this.simplePageMasterName = simplePageMasterName; |
| this.blank = blank; |
| } |
| |
| /** |
| * Sets the page sequence this page belongs to |
| * @param seq the page sequence |
| */ |
| public void setPageSequence(PageSequence seq) { |
| this.pageSequence = seq; |
| } |
| |
| /** @return the page sequence this page belongs to */ |
| public PageSequence getPageSequence() { |
| return this.pageSequence; |
| } |
| |
| /** |
| * Get the view area rectangle of this viewport. |
| * @return the rectangle for this viewport |
| */ |
| public Rectangle getViewArea() { |
| return viewArea; |
| } |
| |
| /** |
| * Get the page reference area with the contents. |
| * @return the page reference area |
| */ |
| public Page getPage() { |
| return page; |
| } |
| |
| /** |
| * Sets the page object for this PageViewport. |
| * @param page the page |
| */ |
| public void setPage(Page page) { |
| this.page = page; |
| } |
| |
| /** |
| * Get the page number of this page. |
| * @return the integer value that represents this page |
| */ |
| public int getPageNumber() { |
| return pageNumber; |
| } |
| |
| /** |
| * Get the page number of this page. |
| * @return the string that represents this page |
| */ |
| public String getPageNumberString() { |
| return pageNumberString; |
| } |
| |
| /** |
| * Sets the page index of the page in this rendering run. |
| * (This is not the same as the page number!) |
| * @param index the page index (zero-based), -1 if it is undetermined |
| */ |
| public void setPageIndex(int index) { |
| this.pageIndex = index; |
| } |
| |
| /** |
| * @return the overall page index of the page in this rendering run (zero-based, |
| * -1 if it is undetermined). |
| */ |
| public int getPageIndex() { |
| return this.pageIndex; |
| } |
| |
| /** |
| * Sets the unique key for this PageViewport that will be used to reference this page. |
| * @param key the unique key. |
| */ |
| public void setKey(String key) { |
| this.pageKey = key; |
| } |
| |
| /** |
| * Get the key for this page viewport. |
| * This is used so that a serializable key can be used to |
| * lookup the page or some other reference. |
| * |
| * @return a unique page viewport key for this area tree |
| */ |
| public String getKey() { |
| if (this.pageKey == null) { |
| throw new IllegalStateException("No page key set on the PageViewport: " + toString()); |
| } |
| return this.pageKey; |
| } |
| |
| /** |
| * Add an "ID-first" to this page. |
| * This is typically called by the {@link AreaTreeHandler} when associating |
| * an ID with a {@link PageViewport}. |
| * |
| * @param id the id to be registered as first appearing on this page |
| */ |
| public void setFirstWithID(String id) { |
| if (id != null) { |
| idFirsts.add(id); |
| } |
| } |
| |
| /** |
| * Check whether a certain id first appears on this page |
| * |
| * @param id the id to be checked |
| * @return true if this page is the first where the id appears |
| */ |
| public boolean isFirstWithID(String id) { |
| return idFirsts.contains(id); |
| } |
| |
| /** |
| * Replace the old view port. This copies all ID related fields from the old view port |
| * to the current one. |
| * @param oldViewPort old view port |
| */ |
| public void replace(PageViewport oldViewPort) { |
| this.idFirsts.addAll(oldViewPort.idFirsts); |
| if (oldViewPort.unresolvedIDRefs != null) { |
| this.unresolvedIDRefs.putAll(oldViewPort.unresolvedIDRefs); |
| } |
| if (oldViewPort.pendingResolved != null) { |
| this.pendingResolved.putAll(oldViewPort.pendingResolved); |
| } |
| } |
| |
| /** |
| * Add an idref to this page. |
| * All idrefs found for child areas of this {@link PageViewport} are added |
| * to unresolvedIDRefs, for subsequent resolution by {@link AreaTreeHandler} |
| * calls to this object's {@code resolveIDRef()}. |
| * |
| * @param idref the idref |
| * @param res the child element of this page that needs this |
| * idref resolved |
| */ |
| public void addUnresolvedIDRef(String idref, Resolvable res) { |
| if (unresolvedIDRefs == null) { |
| unresolvedIDRefs = new HashMap<String, List<Resolvable>>(); |
| } |
| List<Resolvable> pageViewports = unresolvedIDRefs.get(idref); |
| if (pageViewports == null) { |
| pageViewports = new ArrayList<Resolvable>(); |
| unresolvedIDRefs.put(idref, pageViewports); |
| } |
| pageViewports.add(res); |
| } |
| |
| /** |
| * Check if this page has been fully resolved. |
| * @return true if the page is resolved and can be rendered |
| */ |
| public boolean isResolved() { |
| return unresolvedIDRefs == null |
| || unresolvedIDRefs.size() == 0; |
| } |
| |
| /** |
| * Get the unresolved idrefs for this page. |
| * @return String array of idref's that still have not been resolved |
| */ |
| public String[] getIDRefs() { |
| return (unresolvedIDRefs == null) ? null |
| : unresolvedIDRefs.keySet().toArray( |
| new String[unresolvedIDRefs.keySet().size()]); |
| } |
| |
| /** {@inheritDoc} */ |
| public void resolveIDRef(String id, List<PageViewport> pages) { |
| if (page == null) { |
| if (pendingResolved == null) { |
| pendingResolved = new HashMap<String, List<PageViewport>>(); |
| } |
| pendingResolved.put(id, pages); |
| } else { |
| if (unresolvedIDRefs != null) { |
| List<Resolvable> todo = unresolvedIDRefs.get(id); |
| if (todo != null) { |
| for (Resolvable res : todo) { |
| res.resolveIDRef(id, pages); |
| } |
| } |
| } |
| } |
| if (unresolvedIDRefs != null && pages != null) { |
| unresolvedIDRefs.remove(id); |
| if (unresolvedIDRefs.isEmpty()) { |
| unresolvedIDRefs = null; |
| } |
| } |
| } |
| |
| /** |
| * Register the markers for this page. |
| * |
| * @param marks the map of markers to add |
| * @param starting if the area being added is starting or ending |
| * @param isfirst if the area being added has is-first trait |
| * @param islast if the area being added has is-last trait |
| */ |
| public void registerMarkers(Map<String, Marker> marks, boolean starting, boolean isfirst, boolean islast) { |
| if (pageMarkers == null) { |
| pageMarkers = new Markers(); |
| } |
| pageMarkers.register(marks, starting, isfirst, islast); |
| } |
| |
| |
| /** |
| * Resolve a marker from this page. |
| * This will retrieve a marker with the class name |
| * and position. |
| * |
| * @param rm the retrieve-marker instance |
| * @return Object the marker found or null |
| */ |
| public Marker resolveMarker(AbstractRetrieveMarker rm) { |
| if (pageMarkers == null) { |
| return null; |
| } |
| return pageMarkers.resolve(rm); |
| } |
| |
| /** Dumps the current marker data to the logger. */ |
| public void dumpMarkers() { |
| if (pageMarkers != null) { |
| pageMarkers.dump(); |
| } |
| } |
| |
| /** |
| * Save the page contents to an object stream. |
| * The map of unresolved references are set on the page so that |
| * the resolvers can be properly serialized and reloaded. |
| * @param out the object output stream to write the contents |
| * @throws IOException in case of an I/O error while serializing the page |
| */ |
| public void savePage(ObjectOutputStream out) throws IOException { |
| // set the unresolved references so they are serialized |
| page.setUnresolvedReferences(unresolvedIDRefs); |
| out.writeObject(page); |
| page = null; |
| } |
| |
| /** |
| * Load the page contents from an object stream. |
| * This loads the page contents from the stream and |
| * if there are any unresolved references that were resolved |
| * while saved they will be resolved on the page contents. |
| * @param in the object input stream to read the page from |
| * @throws ClassNotFoundException if a class was not found while loading the page |
| * @throws IOException if an I/O error occurred while loading the page |
| */ |
| public void loadPage(ObjectInputStream in) throws IOException, ClassNotFoundException { |
| page = (Page) in.readObject(); |
| unresolvedIDRefs = page.getUnresolvedReferences(); |
| if (unresolvedIDRefs != null && pendingResolved != null) { |
| for (Map.Entry<String, List<PageViewport>> e : pendingResolved.entrySet()) { |
| resolveIDRef(e.getKey(), e.getValue()); |
| } |
| pendingResolved = null; |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public Object clone() throws CloneNotSupportedException { |
| PageViewport pvp = (PageViewport) super.clone(); |
| pvp.page = (Page) page.clone(); |
| pvp.viewArea = (Rectangle) viewArea.clone(); |
| return pvp; |
| } |
| |
| /** |
| * Clear the page contents to save memory. |
| * This object is kept for the life of the area tree since |
| * it holds id and marker information and is used as a key. |
| */ |
| public void clear() { |
| page = null; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public String toString() { |
| StringBuffer sb = new StringBuffer(64); |
| sb.append("PageViewport: page="); |
| sb.append(getPageNumberString()); |
| return sb.toString(); |
| } |
| |
| /** @return the name of the simple-page-master that created this page */ |
| public String getSimplePageMasterName() { |
| return this.simplePageMasterName; |
| } |
| |
| /** @return True if this is a blank page. */ |
| public boolean isBlank() { |
| return this.blank; |
| } |
| |
| /** |
| * Convenience method to get BodyRegion of this PageViewport |
| * @return BodyRegion object |
| */ |
| public BodyRegion getBodyRegion() { |
| RegionReference regionReference = getPage().getRegionViewport(FO_REGION_BODY).getRegionReference(); |
| assert (regionReference instanceof BodyRegion); |
| return (BodyRegion) regionReference; |
| } |
| |
| /** |
| * Convenience method to create a new Span for this |
| * this PageViewport. |
| * |
| * @param spanAll whether this is a single-column span |
| * @return Span object created |
| */ |
| public Span createSpan(boolean spanAll) { |
| return getBodyRegion().getMainReference().createSpan(spanAll); |
| } |
| |
| /** |
| * Convenience method to get the span-reference-area currently |
| * being processed |
| * |
| * @return span currently being processed. |
| */ |
| public Span getCurrentSpan() { |
| return getBodyRegion().getMainReference().getCurrentSpan(); |
| } |
| |
| /** |
| * Convenience method to get the normal-flow-reference-area |
| * currently being processed |
| * |
| * @return span currently being processed. |
| */ |
| public NormalFlow getCurrentFlow() { |
| return getCurrentSpan().getCurrentFlow(); |
| } |
| |
| /** |
| * Convenience method to increment the Span to the |
| * next NormalFlow to be processed, and to return that flow. |
| * |
| * @return the next NormalFlow in the Span. |
| */ |
| public NormalFlow moveToNextFlow() { |
| return getCurrentSpan().moveToNextFlow(); |
| } |
| |
| /** |
| * Convenience method to return a given region-reference-area, |
| * keyed by the Constants class identifier for the corresponding |
| * formatting object (ie. Constants.FO_REGION_BODY, FO_REGION_START, |
| * etc.) |
| * |
| * @param id the Constants class identifier for the region. |
| * @return the corresponding region-reference-area for this page. |
| */ |
| public RegionReference getRegionReference(int id) { |
| return getPage().getRegionViewport(id).getRegionReference(); |
| } |
| |
| /** |
| * Sets the writing mode traits for the page associated with this viewport. |
| * @param wmtg a WM traits getter |
| */ |
| public void setWritingModeTraits(WritingModeTraitsGetter wmtg) { |
| if (page != null) { |
| page.setWritingModeTraits(wmtg); |
| } |
| } |
| |
| } |