/* * 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.awt.geom.Rectangle2D; import java.io.Serializable; import java.util.Iterator; import java.util.Map; import org.apache.fop.datatypes.FODimension; import org.apache.fop.datatypes.LengthBase; import org.apache.fop.datatypes.SimplePercentBaseContext; import org.apache.fop.fo.Constants; import org.apache.fop.fo.pagination.Region; import org.apache.fop.fo.pagination.RegionBody; import org.apache.fop.fo.pagination.SimplePageMaster; import org.apache.fop.fo.properties.CommonMarginBlock; import org.apache.fop.layoutmgr.TraitSetter; /** * The page. * This holds the contents of the page. Each region is added. * The unresolved references area added so that if the page is * serialized then it will handle the resolving properly after * being reloaded. * This is serializable so it can be saved to cache to save * memory if there are forward references. * The page is cloneable so the page master can make copies of * the top level page and regions. */ public class Page extends AreaTreeObject implements Serializable, Cloneable { // contains before, start, body, end and after regions private RegionViewport regionBefore = null; private RegionViewport regionStart = null; private RegionViewport regionBody = null; private RegionViewport regionEnd = null; private RegionViewport regionAfter = null; // temporary map of unresolved objects used when serializing the page private Map unresolved = null; /** Set to true to make this page behave as if it were not empty. */ private boolean fakeNonEmpty = false; /** * Empty constructor, for cloning */ public Page() { } /** * Constructor * @param spm SimplePageMaster containing the dimensions for this * page-reference-area */ public Page(SimplePageMaster spm) { // Width and Height of the page view port FODimension pageViewPortDims = new FODimension(spm.getPageWidth().getValue() , spm.getPageHeight().getValue()); // Get absolute margin properties (top, left, bottom, right) CommonMarginBlock mProps = spm.getCommonMarginBlock(); /* * Create the page reference area rectangle (0,0 is at top left * of the "page media" and y increases * when moving towards the bottom of the page. * The media rectangle itself is (0,0,pageWidth,pageHeight). */ /* Special rules apply to resolving margins in the page context. * Contrary to normal margins in this case top and bottom margin * are resolved relative to the height. In the property subsystem * all margin properties are configured to using BLOCK_WIDTH. * That's why we 'cheat' here and setup a context for the height but * use the LengthBase.BLOCK_WIDTH. */ SimplePercentBaseContext pageWidthContext = new SimplePercentBaseContext(null, LengthBase.CONTAINING_BLOCK_WIDTH , pageViewPortDims.ipd); SimplePercentBaseContext pageHeightContext = new SimplePercentBaseContext(null, LengthBase.CONTAINING_BLOCK_WIDTH , pageViewPortDims.bpd); Rectangle pageRefRect = new Rectangle(mProps.marginLeft.getValue(pageWidthContext) , mProps.marginTop.getValue(pageHeightContext) , pageViewPortDims.ipd - mProps.marginLeft.getValue(pageWidthContext) - mProps.marginRight.getValue(pageWidthContext) , pageViewPortDims.bpd - mProps.marginTop.getValue(pageHeightContext) - mProps.marginBottom.getValue(pageHeightContext)); // Set up the CTM on the page reference area based on writing-mode // and reference-orientation FODimension reldims = new FODimension(0, 0); CTM pageCTM = CTM.getCTMandRelDims(spm.getReferenceOrientation(), spm.getWritingMode(), pageRefRect, reldims); // Create a RegionViewport/ reference area pair for each page region RegionReference rr = null; for (Iterator regenum = spm.getRegions().values().iterator(); regenum.hasNext();) { Region r = (Region)regenum.next(); RegionViewport rvp = makeRegionViewport(r, reldims, pageCTM, spm); if (r.getNameId() == Constants.FO_REGION_BODY) { rr = new BodyRegion((RegionBody) r, rvp); } else { rr = new RegionReference(r, rvp); } // set borders and padding traits // (a little extensions wrt what prescribed by the specs at 6.4.14) TraitSetter.addBorders(rr, r.getCommonBorderPaddingBackground(), false, false, false, false, null); TraitSetter.addPadding(rr, r.getCommonBorderPaddingBackground(), false, false, false, false, null); setRegionReferencePosition(rr, r, rvp.getViewArea()); rvp.setRegionReference(rr); setRegionViewport(r.getNameId(), rvp); } } /** * Call this method to force this page to pretend not to be empty. */ public void fakeNonEmpty() { this.fakeNonEmpty = true; } /** * Creates a RegionViewport Area object for this pagination Region. * @param r the region the viewport is to be created for * @param reldims relative dimensions * @param pageCTM page coordinate transformation matrix * @param spm the simple-page-master for this page * @return the new region viewport */ private RegionViewport makeRegionViewport(Region r, FODimension reldims, CTM pageCTM, SimplePageMaster spm) { Rectangle2D relRegionRect = r.getViewportRectangle(reldims, spm); Rectangle2D absRegionRect = pageCTM.transform(relRegionRect); // Get the region viewport rectangle in absolute coords by // transforming it using the page CTM RegionViewport rv = new RegionViewport(absRegionRect); rv.setBPD((int)relRegionRect.getHeight()); rv.setIPD((int)relRegionRect.getWidth()); TraitSetter.addBackground(rv, r.getCommonBorderPaddingBackground(), null); rv.setClip(r.getOverflow() == Constants.EN_HIDDEN || r.getOverflow() == Constants.EN_ERROR_IF_OVERFLOW); return rv; } /** * Set the region reference position within the region viewport. * This sets the transform that is used to place the contents of * the region reference. * * @param rr the region reference area * @param r the region-xxx formatting object * @param absRegVPRect The region viewport rectangle in "absolute" coordinates * where x=distance from left, y=distance from bottom, width=right-left * height=top-bottom */ private void setRegionReferencePosition(RegionReference rr, Region r, Rectangle2D absRegVPRect) { FODimension reldims = new FODimension(0, 0); rr.setCTM(CTM.getCTMandRelDims(r.getReferenceOrientation(), r.getWritingMode(), absRegVPRect, reldims)); rr.setIPD(reldims.ipd); rr.setBPD(reldims.bpd); } /** * Set the region on this page. * * @param areaclass the area class of the region to set * @param port the region viewport to set */ public void setRegionViewport(int areaclass, RegionViewport port) { if (areaclass == Constants.FO_REGION_BEFORE) { regionBefore = port; } else if (areaclass == Constants.FO_REGION_START) { regionStart = port; } else if (areaclass == Constants.FO_REGION_BODY) { regionBody = port; } else if (areaclass == Constants.FO_REGION_END) { regionEnd = port; } else if (areaclass == Constants.FO_REGION_AFTER) { regionAfter = port; } } /** * Get the region from this page. * * @param areaclass the region area class * @return the region viewport or null if none */ public RegionViewport getRegionViewport(int areaclass) { if (areaclass == Constants.FO_REGION_BEFORE) { return regionBefore; } else if (areaclass == Constants.FO_REGION_START) { return regionStart; } else if (areaclass == Constants.FO_REGION_BODY) { return regionBody; } else if (areaclass == Constants.FO_REGION_END) { return regionEnd; } else if (areaclass == Constants.FO_REGION_AFTER) { return regionAfter; } throw new IllegalArgumentException("No such area class with ID = " + areaclass); } /** * indicates whether any FOs have been added to the body region * * @return whether any FOs have been added to the body region */ public boolean isEmpty() { if (fakeNonEmpty) { return false; } else if (regionBody == null) { return true; } else { BodyRegion body = (BodyRegion)regionBody.getRegionReference(); return body.isEmpty(); } } /** * Clone this page. * This returns a new page with a clone of all the regions. * * @return a new clone of this page */ public Object clone() { Page p = new Page(); if (regionBefore != null) { p.regionBefore = (RegionViewport)regionBefore.clone(); } if (regionStart != null) { p.regionStart = (RegionViewport)regionStart.clone(); } if (regionBody != null) { p.regionBody = (RegionViewport)regionBody.clone(); } if (regionEnd != null) { p.regionEnd = (RegionViewport)regionEnd.clone(); } if (regionAfter != null) { p.regionAfter = (RegionViewport)regionAfter.clone(); } return p; } /** * Set the unresolved references on this page for serializing. * * @param unres the Map of unresolved objects */ public void setUnresolvedReferences(Map unres) { unresolved = unres; } /** * Get the map unresolved references from this page. * This should be called after deserializing to retrieve * the map of unresolved references that were serialized. * * @return the de-serialized HashMap of unresolved objects */ public Map getUnresolvedReferences() { return unresolved; } }