/* * 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.List; 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.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; import org.apache.fop.traits.WritingModeTraitsGetter; import static org.apache.fop.fo.Constants.EN_ERROR_IF_OVERFLOW; import static org.apache.fop.fo.Constants.EN_HIDDEN; import static org.apache.fop.fo.Constants.FO_REGION_AFTER; import static org.apache.fop.fo.Constants.FO_REGION_BEFORE; import static org.apache.fop.fo.Constants.FO_REGION_BODY; import static org.apache.fop.fo.Constants.FO_REGION_END; import static org.apache.fop.fo.Constants.FO_REGION_START; /** * 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 { private static final long serialVersionUID = 6272157047421543866L; // contains before, start, body, end and after regions private RegionViewport regionBefore; private RegionViewport regionStart; private RegionViewport regionBody; private RegionViewport regionEnd; private RegionViewport regionAfter; // temporary map of unresolved objects used when serializing the page private Map<String, List<Resolvable>> unresolved; /** Set to true to make this page behave as if it were not empty. */ private boolean fakeNonEmpty; /** * Empty constructor */ 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; for (Region r : spm.getRegions().values()) { RegionViewport rvp = makeRegionViewport(r, reldims, pageCTM); if (r.getNameId() == 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 * @return the new region viewport */ private static RegionViewport makeRegionViewport(Region r, FODimension reldims, CTM pageCTM) { Rectangle2D relRegionRect = r.getViewportRectangle(reldims); 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() == EN_HIDDEN || r.getOverflow() == 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 static 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.getBorderAndPaddingWidthStart() - rr.getBorderAndPaddingWidthEnd()); rr.setBPD(reldims.bpd - rr.getBorderAndPaddingWidthBefore() - rr.getBorderAndPaddingWidthAfter()); } /** * 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 == FO_REGION_BEFORE) { regionBefore = port; } else if (areaclass == FO_REGION_START) { regionStart = port; } else if (areaclass == FO_REGION_BODY) { regionBody = port; } else if (areaclass == FO_REGION_END) { regionEnd = port; } else if (areaclass == 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) { switch (areaClass) { case FO_REGION_BEFORE: return regionBefore; case FO_REGION_START: return regionStart; case FO_REGION_BODY: return regionBody; case FO_REGION_END: return regionEnd; case FO_REGION_AFTER: return regionAfter; default: 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 { return regionBody.getRegionReference().isEmpty(); } } /** {@inheritDoc} */ public Object clone() throws CloneNotSupportedException { Page p = (Page) super.clone(); 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<String, List<Resolvable>> 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<String, List<Resolvable>> getUnresolvedReferences() { return unresolved; } /** * Sets the writing mode traits for the region viewports of * this page. * @param wmtg a WM traits getter */ public void setWritingModeTraits(WritingModeTraitsGetter wmtg) { if (regionBefore != null) { regionBefore.setWritingModeTraits(wmtg); } if (regionStart != null) { regionStart.setWritingModeTraits(wmtg); } if (regionBody != null) { regionBody.setWritingModeTraits(wmtg); } if (regionEnd != null) { regionEnd.setWritingModeTraits(wmtg); } if (regionAfter != null) { regionAfter.setWritingModeTraits(wmtg); } } }