--- src/java/org/apache/fop/area/AreaTreeParser.java (revision 830403) +++ src/java/org/apache/fop/area/AreaTreeParser.java (working copy) @@ -20,6 +20,7 @@ package org.apache.fop.area; import java.awt.Color; +import java.awt.Rectangle; import java.awt.geom.Rectangle2D; import java.util.List; import java.util.Map; @@ -36,17 +37,17 @@ import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.util.*; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; - import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - import org.apache.xmlgraphics.image.loader.ImageInfo; import org.apache.xmlgraphics.image.loader.ImageManager; import org.apache.xmlgraphics.image.loader.ImageSessionContext; @@ -74,11 +75,6 @@ import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; import org.apache.fop.traits.BorderProps; -import org.apache.fop.util.ColorUtil; -import org.apache.fop.util.ContentHandlerFactory; -import org.apache.fop.util.ContentHandlerFactoryRegistry; -import org.apache.fop.util.DefaultErrorListener; -import org.apache.fop.util.QName; /** * This is a parser for the area tree XML (intermediate format) which is used to reread an area @@ -384,7 +380,7 @@ if (currentPageViewport != null) { throw new IllegalStateException("currentPageViewport must be null"); } - Rectangle2D viewArea = parseRect(attributes.getValue("bounds")); + Rectangle viewArea = XMLUtil.getAttributeAsRectangle(attributes, "bounds"); int pageNumber = getAttributeAsInteger(attributes, "nr", -1); String key = attributes.getValue("key"); String pageNumberString = attributes.getValue("formatted-nr"); --- src/java/org/apache/fop/area/PageViewport.java (revision 830403) +++ src/java/org/apache/fop/area/PageViewport.java (working copy) @@ -20,7 +20,6 @@ package org.apache.fop.area; import java.awt.Rectangle; -import java.awt.geom.Rectangle2D; import java.io.ObjectOutputStream; import java.io.ObjectInputStream; import java.util.ArrayList; @@ -49,7 +48,7 @@ public class PageViewport extends AreaTreeObject implements Resolvable, Cloneable { private Page page; - private Rectangle2D viewArea; + private Rectangle viewArea; private String simplePageMasterName; /** @@ -105,6 +104,7 @@ public PageViewport(SimplePageMaster spm, int pageNumber, String pageStr, boolean blank) { this.simplePageMasterName = spm.getMasterName(); this.extensionAttachments = spm.getExtensionAttachments(); + setForeignAttributes(spm.getForeignAttributes()); this.blank = blank; int pageWidth = spm.getPageWidth().getValue(); int pageHeight = spm.getPageHeight().getValue(); @@ -123,10 +123,13 @@ if (original.extensionAttachments != null) { this.extensionAttachments = new java.util.ArrayList(original.extensionAttachments); } + if (original.foreignAttributes != null) { + setForeignAttributes(original.foreignAttributes); + } this.pageNumber = original.pageNumber; this.pageNumberString = original.pageNumberString; this.page = (Page)original.page.clone(); - this.viewArea = (Rectangle2D)original.viewArea.clone(); + this.viewArea = new Rectangle(original.viewArea); this.simplePageMasterName = original.simplePageMasterName; this.blank = original.blank; } @@ -139,7 +142,7 @@ * @param simplePageMasterName name of the original simple-page-master that generated this page * @param blank true if this is a blank page */ - public PageViewport(Rectangle2D viewArea, int pageNumber, String pageStr, + public PageViewport(Rectangle viewArea, int pageNumber, String pageStr, String simplePageMasterName, boolean blank) { this.viewArea = viewArea; this.pageNumber = pageNumber; @@ -165,7 +168,7 @@ * Get the view area rectangle of this viewport. * @return the rectangle for this viewport */ - public Rectangle2D getViewArea() { + public Rectangle getViewArea() { return viewArea; } --- src/java/org/apache/fop/layoutmgr/Page.java (revision 830403) +++ src/java/org/apache/fop/layoutmgr/Page.java (working copy) @@ -19,7 +19,7 @@ package org.apache.fop.layoutmgr; -import java.awt.geom.Rectangle2D; +import java.awt.Rectangle; import org.apache.fop.area.PageViewport; import org.apache.fop.fo.pagination.SimplePageMaster; @@ -54,7 +54,7 @@ * @param pageNumberStr the page number (as a String) * @param blank true if this is a blank page */ - public Page(Rectangle2D viewArea, int pageNumber, String pageNumberStr, boolean blank) { + public Page(Rectangle viewArea, int pageNumber, String pageNumberStr, boolean blank) { this.spm = null; this.pageViewport = new PageViewport(viewArea, pageNumber, pageNumberStr, null, blank); } --- src/java/org/apache/fop/pdf/PDFFactory.java (revision 830403) +++ src/java/org/apache/fop/pdf/PDFFactory.java (working copy) @@ -171,22 +171,19 @@ * PDFDocument later using addObject(). * * @param resources resources object to use - * @param pageWidth width of the page in points - * @param pageHeight height of the page in points * @param pageIndex index of the page (zero-based) + * @param mediaBox the MediaBox area + * @param cropBox the CropBox area + * @param bleedBox the BleedBox area + * @param trimBox the TrimBox area * * @return the created /Page object */ - public PDFPage makePage(PDFResources resources, - int pageWidth, int pageHeight, int pageIndex) { + public PDFPage makePage(PDFResources resources, int pageIndex, + Rectangle2D mediaBox, Rectangle2D cropBox, + Rectangle2D bleedBox, Rectangle2D trimBox) { + PDFPage page = new PDFPage(resources, pageIndex, mediaBox, cropBox, bleedBox, trimBox); - /* - * create a PDFPage with the next object number, the given - * resources, contents and dimensions - */ - PDFPage page = new PDFPage(resources, - pageWidth, pageHeight, pageIndex); - getDocument().assignObjectNumber(page); getDocument().getPages().addPage(page); return page; @@ -200,10 +197,28 @@ * @param resources resources object to use * @param pageWidth width of the page in points * @param pageHeight height of the page in points + * @param pageIndex index of the page (zero-based) * * @return the created /Page object */ public PDFPage makePage(PDFResources resources, + int pageWidth, int pageHeight, int pageIndex) { + Rectangle2D mediaBox = new Rectangle2D.Double(0, 0, pageWidth, pageHeight); + return makePage(resources, pageIndex, mediaBox, mediaBox, mediaBox, mediaBox); + } + + /** + * Make a /Page object. The page is assigned an object number immediately + * so references can already be made. The page must be added to the + * PDFDocument later using addObject(). + * + * @param resources resources object to use + * @param pageWidth width of the page in points + * @param pageHeight height of the page in points + * + * @return the created /Page object + */ + public PDFPage makePage(PDFResources resources, int pageWidth, int pageHeight) { return makePage(resources, pageWidth, pageHeight, -1); } --- src/java/org/apache/fop/pdf/PDFPage.java (revision 830403) +++ src/java/org/apache/fop/pdf/PDFPage.java (working copy) @@ -38,42 +38,42 @@ * Create a /Page object * * @param resources the /Resources object - * @param contents the content stream - * @param pageWidth the page's width in points - * @param pageHeight the page's height in points * @param pageIndex the page's zero-based index (or -1 if the page number is auto-determined) + * @param mediaBox the MediaBox + * @param cropBox the CropBox. If null, mediaBox is used. + * @param bleedBox the BleedBox. If null, cropBox is used. + * @param trimBox the TrimBox. If null, bleedBox is used. */ - public PDFPage(PDFResources resources, PDFStream contents, - int pageWidth, int pageHeight, int pageIndex) { - + public PDFPage(PDFResources resources, int pageIndex, + Rectangle2D mediaBox, Rectangle2D cropBox, + Rectangle2D bleedBox, Rectangle2D trimBox) { /* generic creation of object */ super(resources); put("Type", new PDFName("Page")); /* set fields using parameters */ - setContents(contents); - setSimplePageSize(pageWidth, pageHeight); + setSimplePageSize(mediaBox, cropBox, bleedBox, trimBox); this.pageIndex = pageIndex; } - /** - * Create a /Page object - * - * @param resources the /Resources object - * @param pageWidth the page's width in points - * @param pageHeight the page's height in points - * @param pageIndex the page's zero-based index (or -1 if the page number is auto-determined) - */ - public PDFPage(PDFResources resources, - int pageWidth, int pageHeight, int pageIndex) { - this(resources, null, pageWidth, pageHeight, pageIndex); - } + private void setSimplePageSize(Rectangle2D mediaBox, Rectangle2D cropBox, + Rectangle2D bleedBox, Rectangle2D trimBox) { + setMediaBox(mediaBox); - private void setSimplePageSize(int width, int height) { - Rectangle2D box = new Rectangle2D.Double(0, 0, width, height); - setMediaBox(box); - setBleedBox(box); //Recommended by PDF/X - setTrimBox(box); //Needed for PDF/X + if (cropBox == null) { + cropBox = mediaBox; + } + setCropBox(cropBox); + + if (bleedBox == null) { + bleedBox = cropBox; + } + setBleedBox(bleedBox); //Recommended by PDF/X + + if (trimBox == null) { + trimBox = bleedBox; + } + setTrimBox(trimBox); //Needed for PDF/X } private PDFArray toPDFArray(Rectangle2D box) { @@ -88,6 +88,14 @@ public void setMediaBox(Rectangle2D box) { put("MediaBox", toPDFArray(box)); } + + /** + * Sets the "CropBox" entry + * @param box the bleed rectangle + */ + public void setCropBox(Rectangle2D box) { + put("CropBox", toPDFArray(box)); + } /** * Sets the "TrimBox" entry --- src/java/org/apache/fop/render/awt/AWTRenderer.java (revision 830403) +++ src/java/org/apache/fop/render/awt/AWTRenderer.java (working copy) @@ -30,6 +30,7 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.geom.Rectangle2D; +import java.awt.geom.Point2D; import java.awt.print.PageFormat; import java.awt.print.Pageable; import java.awt.print.Paper; @@ -46,6 +47,7 @@ import org.apache.fop.render.awt.viewer.Renderable; import org.apache.fop.render.awt.viewer.StatusListener; import org.apache.fop.render.java2d.Java2DRenderer; +import org.apache.fop.render.extensions.prepress.PageScale; /** * The AWTRender outputs the pages generated by the layout engine to a Swing @@ -155,6 +157,15 @@ double scaleY = scaleFactor * (25.4 / FopFactoryConfigurator.DEFAULT_TARGET_RESOLUTION) / userAgent.getTargetPixelUnitToMillimeter(); + if (getPageViewport(pageNum).getForeignAttributes() != null) { + String scale = (String) getPageViewport(pageNum).getForeignAttributes().get( + PageScale.EXT_PAGE_SCALE); + Point2D scales = PageScale.getScale(scale); + if (scales != null) { + scaleX *= scales.getX(); + scaleY *= scales.getY(); + } + } int bitmapWidth = (int) ((pageWidth * scaleX) + 0.5); int bitmapHeight = (int) ((pageHeight * scaleY) + 0.5); return new Dimension(bitmapWidth, bitmapHeight); --- src/java/org/apache/fop/render/extensions/prepress/PageBoundaries.java (revision 0) +++ src/java/org/apache/fop/render/extensions/prepress/PageBoundaries.java (revision 0) @@ -0,0 +1,216 @@ +package org.apache.fop.render.extensions.prepress; + +import java.awt.*; +import java.text.MessageFormat; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.fop.util.QName; + +import org.apache.fop.fo.extensions.ExtensionElementMapping; +import org.apache.fop.fo.properties.FixedLength; + + +/** + * This class is used to calculate the effective boundaries of a page including special-purpose + * boxes used in prepress. These are specified using extension attributes: + * bleedBox, trimBox and cropBox. The semantics are further described on the website. + */ +public class PageBoundaries { + + /** + * The extension attribute for calculating the PDF BleedBox area - specifies the bleed width. + */ + public static final QName EXT_BLEED + = new QName(ExtensionElementMapping.URI, null, "bleed"); + + /** + * The extension attribute for the PDF CropBox area. + */ + public static final QName EXT_CROP_OFFSET + = new QName(ExtensionElementMapping.URI, null, "crop-offset"); + + /** + * The extension attribute for the PDF CropBox area. + */ + public static final QName EXT_CROP_BOX + = new QName(ExtensionElementMapping.URI, null, "crop-box"); + + + private static final Pattern SIZE_UNIT_PATTERN + = Pattern.compile("^(-?\\d*\\.?\\d*)(px|in|cm|mm|pt|pc|mpt)$"); + + private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); + + private Rectangle trimBox; + private Rectangle bleedBox; + private Rectangle mediaBox; + private Rectangle cropBox; + + /** + * Creates a new instance. + * @param pageSize the page size (in mpt) defined by the simple-page-master. + * @param bleed the bleed value (raw value as given in the property value) + * @param cropOffset the crop-offset value (raw value as given in the property value) + * @param cropBoxSelector the crop-box, valid values: (trim-box|bleed-box|media-box) + */ + public PageBoundaries(Dimension pageSize, String bleed, String cropOffset, + String cropBoxSelector) { + calculate(pageSize, bleed, cropOffset, cropBoxSelector); + } + + /** + * Creates a new instance. + * @param pageSize the page size (in mpt) defined by the simple-page-master. + * @param foreignAttributes the foreign attributes for the page + * (used to extract the extension attribute values) + */ + public PageBoundaries(Dimension pageSize, Map foreignAttributes) { + String bleed = (String)foreignAttributes.get(EXT_BLEED); + String cropOffset = (String)foreignAttributes.get(EXT_CROP_OFFSET); + String cropBoxSelector = (String)foreignAttributes.get(EXT_CROP_BOX); + calculate(pageSize, bleed, cropOffset, cropBoxSelector); + } + + private void calculate(Dimension pageSize, String bleed, String cropOffset, + String cropBoxSelector) { + this.trimBox = new Rectangle(pageSize); + this.bleedBox = getBleedBoxRectangle(this.trimBox, bleed); + Rectangle cropMarksBox = getCropMarksAreaRectangle(trimBox, cropOffset); + + //MediaBox includes all of the following three rectangles + this.mediaBox = new Rectangle(); + this.mediaBox.add(this.trimBox); + this.mediaBox.add(this.bleedBox); + this.mediaBox.add(cropMarksBox); + + if ("trim-box".equals(cropBoxSelector)) { + this.cropBox = this.trimBox; + } else if ("bleed-box".equals(cropBoxSelector)) { + this.cropBox = this.bleedBox; + } else if ("media-box".equals(cropBoxSelector) + || cropBoxSelector == null + || "".equals(cropBoxSelector)) { + this.cropBox = this.mediaBox; + } else { + final String err = "The crop-box has invalid value: {0}, " + + "possible values of crop-box: (trim-box|bleed-box|media-box)"; + throw new IllegalArgumentException(MessageFormat.format(err, + new Object[]{cropBoxSelector})); + } + } + + /** + * Returns the trim box for the page. This is equal to the page size given in XSL-FO. + * After production the printed media is trimmed to this rectangle. + * @return the trim box + */ + public Rectangle getTrimBox() { + return this.trimBox; + } + + /** + * Returns the bleed box for the page. + * @return the bleed box + */ + public Rectangle getBleedBox() { + return this.bleedBox; + } + + /** + * Returns the media box for the page. + * @return the media box + */ + public Rectangle getMediaBox() { + return this.mediaBox; + } + + /** + * Returns the crop box for the page. The crop box is used by Adobe Acrobat to select which + * parts of the document shall be displayed and it also defines the rectangle to which a + * RIP will clip the document. For bitmap output, this defines the size of the bitmap. + * @return the crop box + */ + public Rectangle getCropBox() { + return this.cropBox; + } + + /** + * The BleedBox is calculated by expanding the TrimBox by the bleed widths. + * + * @param trimBox the TrimBox rectangle + * @param bleed the given bleed widths + * @return the calculated BleedBox rectangle + */ + private static Rectangle getBleedBoxRectangle(Rectangle trimBox, String bleed) { + return getRectangleUsingOffset(trimBox, bleed); + } + + /** + * The MediaBox is calculated by expanding the TrimBox by the crop offsets. + * + * @param trimBox the TrimBox rectangle + * @param cropOffsets the given crop offsets + * @return the calculated MediaBox rectangle + */ + private static Rectangle getCropMarksAreaRectangle(Rectangle trimBox, String cropOffsets) { + return getRectangleUsingOffset(trimBox, cropOffsets); + } + + private static Rectangle getRectangleUsingOffset(Rectangle originalRect, String offset) { + if (offset == null || "".equals(offset) || originalRect == null) { + return originalRect; + } + + String[] offsets = WHITESPACE_PATTERN.split(offset); + int[] coords = new int[4]; // top, right, bottom, left + switch (offsets.length) { + case 1: + coords[0] = getLengthIntValue(offsets[0]); + coords[1] = coords[0]; + coords[2] = coords[0]; + coords[3] = coords[0]; + break; + case 2: + coords[0] = getLengthIntValue(offsets[0]); + coords[1] = getLengthIntValue(offsets[1]); + coords[2] = coords[0]; + coords[3] = coords[1]; + break; + case 3: + coords[0] = getLengthIntValue(offsets[0]); + coords[1] = getLengthIntValue(offsets[1]); + coords[2] = getLengthIntValue(offsets[2]); + coords[3] = coords[1]; + break; + case 4: + coords[0] = getLengthIntValue(offsets[0]); + coords[1] = getLengthIntValue(offsets[1]); + coords[2] = getLengthIntValue(offsets[2]); + coords[3] = getLengthIntValue(offsets[3]); + break; + default: + // TODO throw appropriate exception that can be caught by the event + // notification mechanism + throw new IllegalArgumentException("Too many arguments"); + } + return new Rectangle(originalRect.x - coords[3], + originalRect.y - coords[2], + originalRect.width + coords[3] + coords[1], + originalRect.height + coords[0] + coords[2]); + } + + private static int getLengthIntValue(final String length) { + final String err = "Incorrect length value: {0}"; + Matcher m = SIZE_UNIT_PATTERN.matcher(length); + + if (m.find()) { + return FixedLength.getInstance(Double.parseDouble(m.group(1)), + m.group(2)).getLength().getValue(); + } else { + throw new IllegalArgumentException(MessageFormat.format(err, new Object[]{length})); + } + } + +} --- src/java/org/apache/fop/render/extensions/prepress/PageScale.java (revision 0) +++ src/java/org/apache/fop/render/extensions/prepress/PageScale.java (revision 0) @@ -0,0 +1,93 @@ +/* + * 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: PageScaleAttributes.java 800142 2009-08-02 19:41:37Z jeremias $ */ + +package org.apache.fop.render.extensions.prepress; + +import java.awt.geom.Point2D; +import java.text.MessageFormat; +import java.util.regex.Pattern; + +import org.apache.fop.util.QName; + +import org.apache.fop.fo.extensions.ExtensionElementMapping; + + +/** + * This class provides utility methods to parse the 'fox:scale' extension attribute. + */ +public final class PageScale { + + /** + * The extension 'scale' attribute for the simple-page-master element. + */ + public static final QName EXT_PAGE_SCALE + = new QName(ExtensionElementMapping.URI, null, "scale"); + + private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); + + /** + * Utility classes should not have a public or default constructor + */ + private PageScale() { + } + + /** + * Compute scale parameters from given fox:scale attribute which has the format: scaleX [scaleY] + * If scaleY is not defined, it equals scaleX. + * @param scale scale attribute, input format: scaleX [scaleY] + * @return the pair of (sx, sy) values + */ + public static Point2D getScale(String scale) { + // TODO throw appropriate exceptions that can be caught by the event + // notification mechanism + final String err = "Extension 'scale' attribute has incorrect value(s): {0}"; + + if (scale == null || scale.equals("")) { + return null; + } + + String[] scales = WHITESPACE_PATTERN.split(scale); + double scaleX; + try { + scaleX = Double.parseDouble(scales[0]); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException(MessageFormat.format(err, new Object[]{scale})); + } + double scaleY; + switch (scales.length) { + case 1: + scaleY = scaleX; + break; + case 2: + try { + scaleY = Double.parseDouble(scales[1]); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException(MessageFormat.format(err, new Object[]{scale})); + } + break; + default: + throw new IllegalArgumentException("Too many arguments"); + } + if (scaleX <= 0 || scaleY <= 0) { + throw new IllegalArgumentException(MessageFormat.format(err, new Object[]{scale})); + } + + return new Point2D.Double(scaleX, scaleY); + } +} --- src/java/org/apache/fop/render/java2d/Java2DRenderer.java (revision 830403) +++ src/java/org/apache/fop/render/java2d/Java2DRenderer.java (working copy) @@ -20,11 +20,7 @@ package org.apache.fop.render.java2d; // Java -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.RenderingHints; +import java.awt.*; import java.awt.font.GlyphVector; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; @@ -73,6 +69,8 @@ import org.apache.fop.render.AbstractPathOrientedRenderer; import org.apache.fop.render.Graphics2DAdapter; import org.apache.fop.render.RendererContext; +import org.apache.fop.render.extensions.prepress.PageBoundaries; +import org.apache.fop.render.extensions.prepress.PageScale; import org.apache.fop.render.pdf.CTMHelper; import org.apache.fop.util.CharUtilities; @@ -278,7 +276,10 @@ this.currentPageViewport = pageViewport; try { - Rectangle2D bounds = pageViewport.getViewArea(); + PageBoundaries boundaries = new PageBoundaries( + pageViewport.getViewArea().getSize(), pageViewport.getForeignAttributes()); + Rectangle bounds = boundaries.getCropBox(); + Rectangle bleedBox = boundaries.getBleedBox(); pageWidth = (int) Math.round(bounds.getWidth() / 1000f); pageHeight = (int) Math.round(bounds.getHeight() / 1000f); @@ -287,11 +288,22 @@ + " (pageWidth " + pageWidth + ", pageHeight " + pageHeight + ")"); - double scaleX = scaleFactor - * (25.4 / FopFactoryConfigurator.DEFAULT_TARGET_RESOLUTION) + // set scale factor + double scaleX = scaleFactor; + double scaleY = scaleFactor; + String scale = (String) currentPageViewport.getForeignAttributes().get( + PageScale.EXT_PAGE_SCALE); + Point2D scales = PageScale.getScale(scale); + if (scales != null) { + scaleX *= scales.getX(); + scaleY *= scales.getY(); + } + + scaleX = scaleX + * (25.4f / FopFactoryConfigurator.DEFAULT_TARGET_RESOLUTION) / userAgent.getTargetPixelUnitToMillimeter(); - double scaleY = scaleFactor - * (25.4 / FopFactoryConfigurator.DEFAULT_TARGET_RESOLUTION) + scaleY = scaleY + * (25.4f / FopFactoryConfigurator.DEFAULT_TARGET_RESOLUTION) / userAgent.getTargetPixelUnitToMillimeter(); int bitmapWidth = (int) ((pageWidth * scaleX) + 0.5); int bitmapHeight = (int) ((pageHeight * scaleY) + 0.5); @@ -318,19 +330,26 @@ // transform page based on scale factor supplied AffineTransform at = graphics.getTransform(); at.scale(scaleX, scaleY); + at.translate(bounds.getMinX() / -1000f, bounds.getMinY() / -1000f); graphics.setTransform(at); // draw page frame if (!transparentPageBackground) { graphics.setColor(Color.white); - graphics.fillRect(0, 0, pageWidth, pageHeight); + graphics.fillRect( + (int)Math.round(bleedBox.getMinX() / 1000f), + (int)Math.round(bleedBox.getMinY() / 1000f), + (int)Math.round(bleedBox.getWidth() / 1000f), + (int)Math.round(bleedBox.getHeight() / 1000f)); } + /* why did we have this??? graphics.setColor(Color.black); graphics.drawRect(-1, -1, pageWidth + 2, pageHeight + 2); graphics.drawLine(pageWidth + 2, 0, pageWidth + 2, pageHeight + 2); graphics.drawLine(pageWidth + 3, 1, pageWidth + 3, pageHeight + 3); graphics.drawLine(0, pageHeight + 2, pageWidth + 2, pageHeight + 2); graphics.drawLine(1, pageHeight + 3, pageWidth + 3, pageHeight + 3); + */ state = new Java2DGraphicsState(graphics, this.fontInfo, at); try { --- src/java/org/apache/fop/render/pdf/PDFRenderer.java (revision 830403) +++ src/java/org/apache/fop/render/pdf/PDFRenderer.java (working copy) @@ -27,6 +27,7 @@ import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; +import java.awt.geom.Rectangle2D.Double; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -41,6 +42,8 @@ import org.apache.commons.io.IOUtils; +import org.apache.fop.render.extensions.prepress.PageBoundaries; +import org.apache.fop.render.extensions.prepress.PageScale; import org.apache.xmlgraphics.image.loader.ImageException; import org.apache.xmlgraphics.image.loader.ImageInfo; import org.apache.xmlgraphics.image.loader.ImageManager; @@ -739,13 +742,38 @@ private void setupPage(PageViewport page) { this.pdfResources = this.pdfDoc.getResources(); - Rectangle2D bounds = page.getViewArea(); - double w = bounds.getWidth(); - double h = bounds.getHeight(); - currentPage = this.pdfDoc.getFactory().makePage( - this.pdfResources, - (int) Math.round(w / 1000), (int) Math.round(h / 1000), - page.getPageIndex()); + PageBoundaries boundaries = new PageBoundaries(page.getViewArea().getSize(), page.getForeignAttributes()); + + Rectangle trimBox = boundaries.getTrimBox(); + Rectangle bleedBox = boundaries.getBleedBox(); + Rectangle mediaBox = boundaries.getMediaBox(); + Rectangle cropBox = boundaries.getCropBox(); + + // set scale attributes + double scaleX = 1; + double scaleY = 1; + String scale = (String) page.getForeignAttributes().get( + PageScale.EXT_PAGE_SCALE); + Point2D scales = PageScale.getScale(scale); + if (scales != null) { + scaleX = scales.getX(); + scaleY = scales.getY(); + } + +// double w = bounds.getWidth(); +// double h = bounds.getHeight(); + this.currentPage = this.pdfDoc.getFactory().makePage( + this.pdfResources, + page.getPageIndex(), + toPointAndScale(mediaBox, scaleX, scaleY), + toPointAndScale(cropBox, scaleX, scaleY), + toPointAndScale(bleedBox, scaleX, scaleY), + toPointAndScale(trimBox, scaleX, scaleY)); + +// currentPage = this.pdfDoc.getFactory().makePage( +// this.pdfResources, +// (int) Math.round(w / 1000), (int) Math.round(h / 1000), +// page.getPageIndex()); pageReferences.put(page.getKey(), currentPage.referencePDF()); pvReferences.put(page.getKey(), page); @@ -763,6 +791,13 @@ //expressed in a more space-efficient way nums.put(page.getPageIndex(), dict); } + + private Double toPointAndScale(Rectangle box, double scaleX, double scaleY) { + return new Rectangle2D.Double(box.getX() * scaleX / 1000, + box.getY() * scaleY / 1000, + box.getWidth() * scaleX / 1000, + box.getHeight() * scaleY / 1000); + } /** * This method creates a pdf stream for the current page @@ -781,9 +816,18 @@ } currentPageRef = currentPage.referencePDF(); - Rectangle2D bounds = page.getViewArea(); - double h = bounds.getHeight(); - pageHeight = (int) h; + Rectangle bounds = page.getViewArea(); + // set scale attributes + double scaleX = 1; + double scaleY = 1; + String scale = (String) page.getForeignAttributes().get( + PageScale.EXT_PAGE_SCALE); + Point2D scales = PageScale.getScale(scale); + if (scales != null) { + scaleX = scales.getX(); + scaleY = scales.getY(); + } + pageHeight = bounds.height; currentStream = this.pdfDoc.getFactory() .makeStream(PDFFilterList.CONTENT_FILTER, false); @@ -791,7 +835,8 @@ currentState = new PDFState(); // Transform the PDF's default coordinate system (0,0 at lower left) to the PDFRenderer's AffineTransform basicPageTransform = new AffineTransform(1, 0, 0, -1, 0, - pageHeight / 1000f); + (scaleY * pageHeight) / 1000f); + basicPageTransform.scale(scaleX, scaleY); currentState.concatenate(basicPageTransform); currentStream.add(CTMHelper.toPDFString(basicPageTransform, false) + " cm\n"); --- src/java/org/apache/fop/render/ps/PSRenderer.java (revision 830403) +++ src/java/org/apache/fop/render/ps/PSRenderer.java (working copy) @@ -1128,8 +1128,8 @@ {page.getPageNumberString(), new Integer(this.currentPageNumber)}); - double pageWidth = Math.round(page.getViewArea().getWidth()) / 1000f; - double pageHeight = Math.round(page.getViewArea().getHeight()) / 1000f; + double pageWidth = page.getViewArea().width / 1000f; + double pageHeight = page.getViewArea().height / 1000f; boolean rotate = false; List pageSizes = new java.util.ArrayList(); if (this.autoRotateLandscape && (pageHeight < pageWidth)) { --- src/java/org/apache/fop/util/ConversionUtils.java (revision 0) +++ src/java/org/apache/fop/util/ConversionUtils.java (revision 0) @@ -0,0 +1,112 @@ +/* + * 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: ConversionUtils.java 746664 2009-02-22 12:40:44Z jeremias $ */ + +package org.apache.fop.util; + +/** + * This class contains utility methods for conversions, like + * a java.lang.String to an array of int or double. + */ +public final class ConversionUtils { + + /** + * Converts the given base String into + * an array of int, splitting the base along the + * given separator pattern. + * Note: this method assumes the input is a string containing + * only decimal integers, signed or unsigned, that are parsable + * by java.lang.Integer.parseInt(String). If this + * is not the case, the resulting NumberFormatException + * will have to be handled by the caller. + * + * @param baseString the base string + * @param separatorPattern the pattern separating the integer values + * (if this is null, the baseString is parsed as one + * integer value) + * @return an array of int whose size is equal to the number + * values in the input string; null if this number + * is equal to zero. + */ + public static int[] toIntArray(String baseString, String separatorPattern) { + + if (baseString == null || "".equals(baseString)) { + return null; + } + + if (separatorPattern == null || "".equals(separatorPattern)) { + return new int[] {Integer.parseInt(baseString)}; + } + + String[] values = baseString.split(separatorPattern); + int numValues = values.length; + if (numValues == 0) { + return null; + } + + int[] returnArray = new int[numValues]; + for (int i = 0; i < numValues; ++i) { + returnArray[i] = Integer.parseInt(values[i]); + } + return returnArray; + + } + + /** + * Converts the given base String into + * an array of double, splitting the base along the + * given separator pattern. + * Note: this method assumes the input is a string containing + * only decimal doubles, signed or unsigned, that are parsable + * by java.lang.Double.parseDouble(String). If this + * is not the case, the resulting NumberFormatException + * will have to be handled by the caller. + * + * @param baseString the base string + * @param separatorPattern the pattern separating the integer values + * (if this is null, the baseString is parsed as one + * double value) + * @return an array of double whose size is equal to the number + * values in the input string; null if this number + * is equal to zero. + */ + public static double[] toDoubleArray(String baseString, String separatorPattern) { + + if (baseString == null || "".equals(baseString)) { + return null; + } + + if (separatorPattern == null || "".equals(separatorPattern)) { + return new double[] {Double.parseDouble(baseString)}; + } + + String[] values = baseString.split(separatorPattern); + int numValues = values.length; + if (numValues == 0) { + return null; + } + + double[] returnArray = new double[numValues]; + for (int i = 0; i < numValues; ++i) { + returnArray[i] = Double.parseDouble(values[i]); + } + return returnArray; + + } + +} --- src/java/org/apache/fop/util/XMLConstants.java (revision 0) +++ src/java/org/apache/fop/util/XMLConstants.java (revision 0) @@ -0,0 +1,52 @@ +/* + * 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: XMLConstants.java 746664 2009-02-22 12:40:44Z jeremias $ */ + +package org.apache.fop.util; + + +/** + * A collection of constants for XML handling. + */ +public interface XMLConstants { + + /** "CDATA" constant */ + String CDATA = "CDATA"; + + /** XML namespace prefix */ + String XML_PREFIX = "xml"; + /** XML namespace URI */ + String XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace"; + /** xml:space attribute */ + org.apache.xmlgraphics.util.QName XML_SPACE = new org.apache.xmlgraphics.util.QName( + XML_NAMESPACE, XML_PREFIX, "space"); + + /** XMLNS namespace prefix */ + String XMLNS_PREFIX = "xmlns"; + /** XMLNS namespace URI */ + String XMLNS_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/"; + + /** Namespace prefix for XLink */ + String XLINK_PREFIX = "xlink"; + /** XML namespace for XLink */ + String XLINK_NAMESPACE = "http://www.w3.org/1999/xlink"; + /** xlink:href attribute */ + org.apache.xmlgraphics.util.QName XLINK_HREF = new org.apache.xmlgraphics.util.QName( + XLINK_NAMESPACE, XLINK_PREFIX, "href"); + +} --- src/java/org/apache/fop/util/XMLUtil.java (revision 0) +++ src/java/org/apache/fop/util/XMLUtil.java (revision 0) @@ -0,0 +1,173 @@ +/* + * 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: XMLUtil.java 820672 2009-10-01 14:48:27Z jeremias $ */ + +package org.apache.fop.util; + +import java.awt.Rectangle; +import java.awt.geom.Rectangle2D; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +/** + * A collection of utility method for XML handling. + */ +public class XMLUtil implements XMLConstants { + + /** + * Returns an attribute value as a boolean value. + * @param attributes the Attributes object + * @param name the name of the attribute + * @param defaultValue the default value if the attribute is not specified + * @return the attribute value as a boolean + */ + public static boolean getAttributeAsBoolean(Attributes attributes, String name, + boolean defaultValue) { + String s = attributes.getValue(name); + if (s == null) { + return defaultValue; + } else { + return Boolean.valueOf(s).booleanValue(); + } + } + + /** + * Returns an attribute value as a int value. + * @param attributes the Attributes object + * @param name the name of the attribute + * @param defaultValue the default value if the attribute is not specified + * @return the attribute value as an int + */ + public static int getAttributeAsInt(Attributes attributes, String name, + int defaultValue) { + String s = attributes.getValue(name); + if (s == null) { + return defaultValue; + } else { + return Integer.parseInt(s); + } + } + + /** + * Returns an attribute value as a int value. + * @param attributes the Attributes object + * @param name the name of the attribute + * @return the attribute value as an int + * @throws SAXException if the attribute is missing + */ + public static int getAttributeAsInt(Attributes attributes, String name) throws SAXException { + String s = attributes.getValue(name); + if (s == null) { + throw new SAXException("Attribute '" + name + "' is missing"); + } else { + return Integer.parseInt(s); + } + } + + /** + * Returns an attribute value as a Integer value. + * @param attributes the Attributes object + * @param name the name of the attribute + * @return the attribute value as an Integer or null if the attribute is missing + */ + public static Integer getAttributeAsInteger(Attributes attributes, String name) { + String s = attributes.getValue(name); + if (s == null) { + return null; + } else { + return new Integer(s); + } + } + + /** + * Returns an attribute value as a Rectangle2D value. The string value is expected as 4 + * double-precision numbers separated by whitespace. + * @param attributes the Attributes object + * @param name the name of the attribute + * @return the attribute value as an Rectangle2D + */ + public static Rectangle2D getAttributeAsRectangle2D(Attributes attributes, String name) { + String s = attributes.getValue(name).trim(); + double[] values = ConversionUtils.toDoubleArray(s, "\\s"); + if (values.length != 4) { + throw new IllegalArgumentException("Rectangle must consist of 4 double values!"); + } + return new Rectangle2D.Double(values[0], values[1], values[2], values[3]); + } + + /** + * Returns an attribute value as a Rectangle value. The string value is expected as 4 + * integer numbers separated by whitespace. + * @param attributes the Attributes object + * @param name the name of the attribute + * @return the attribute value as an Rectangle + */ + public static Rectangle getAttributeAsRectangle(Attributes attributes, String name) { + String s = attributes.getValue(name); + if (s == null) { + return null; + } + int[] values = ConversionUtils.toIntArray(s.trim(), "\\s"); + if (values.length != 4) { + throw new IllegalArgumentException("Rectangle must consist of 4 int values!"); + } + return new Rectangle(values[0], values[1], values[2], values[3]); + } + + /** + * Returns an attribute value as a integer array. The string value is expected as 4 + * integer numbers separated by whitespace. + * @param attributes the Attributes object + * @param name the name of the attribute + * @return the attribute value as an int array + */ + public static int[] getAttributeAsIntArray(Attributes attributes, String name) { + String s = attributes.getValue(name); + if (s == null) { + return null; + } else { + return ConversionUtils.toIntArray(s.trim(), "\\s"); + } + } + + /** + * Adds an attribute to a given {@link AttributesImpl} instance. + * @param atts the attributes collection + * @param attribute the attribute to add + * @param value the attribute's CDATA value + */ + public static void addAttribute(AttributesImpl atts, + org.apache.xmlgraphics.util.QName attribute, String value) { + atts.addAttribute(attribute.getNamespaceURI(), + attribute.getLocalName(), attribute.getQName(), XMLUtil.CDATA, value); + } + + /** + * Adds an attribute to a given {@link AttributesImpl} instance. The attribute will be + * added in the default namespace. + * @param atts the attributes collection + * @param localName the local name of the attribute + * @param value the attribute's CDATA value + */ + public static void addAttribute(AttributesImpl atts, String localName, String value) { + atts.addAttribute("", localName, localName, XMLUtil.CDATA, value); + } + +} --- test/java/org/apache/fop/render/extensions/prepress/PageScaleTest.java (revision 0) +++ test/java/org/apache/fop/render/extensions/prepress/PageScaleTest.java (revision 0) @@ -0,0 +1,79 @@ +/* + * 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: PageScaleTest.java 803440 2009-08-12 10:46:39Z vhennebert $ */ + +package org.apache.fop.render.extensions.prepress; + +import java.awt.geom.Point2D; + +import junit.framework.TestCase; + +/** + * Tests for the fox:scale extension property. + */ +public class PageScaleTest extends TestCase { + + /** + * Default constructor. + */ + public PageScaleTest() { + super(); + } + + /** + * Creates a test case with the given name. + * + * @param name name for the test case + */ + public PageScaleTest(String name) { + super(name); + } + + /** 1 value is used for both x and y. */ + public void testScale1() { + Point2D res = PageScale.getScale(".5"); + assertEquals(0.5, res.getX(), 0.0); + assertEquals(0.5, res.getY(), 0.0); + } + + /** Two values, used resp. for x and y. */ + public void testScale2() { + Point2D res = PageScale.getScale("1. \t \n 1.2"); + assertEquals(1.0, res.getX(), 0.0); + assertEquals(1.2, res.getY(), 0.0); + } + + /** Scale must not contain units. */ + public void testScaleFail() { + try { + PageScale.getScale("0.5mm 0.5cm"); + fail("Expected IllegalArgumentException. Scale shouldn't contain units"); + } catch (IllegalArgumentException iae) { + // Good! + } + } + + /** @{code null} is returned when scale is unspecified. */ + public void testScaleNull() { + Point2D res = PageScale.getScale(null); + assertNull("Result should be null", res); + res = PageScale.getScale(""); + assertNull("Result should be null", res); + } + +} --- test/java/org/apache/fop/StandardTestSuite.java (revision 830403) +++ test/java/org/apache/fop/StandardTestSuite.java (working copy) @@ -28,6 +28,8 @@ import org.apache.fop.render.pdf.PDFEncodingTestCase; import org.apache.fop.render.pdf.PDFsRGBSettingsTestCase; import org.apache.fop.render.rtf.RichTextFormatTestSuite; +import org.apache.fop.render.extensions.prepress.PageBoundariesTest; +import org.apache.fop.render.extensions.prepress.PageScaleTest; /** * Test suite for basic functionality of FOP. @@ -50,6 +52,8 @@ suite.addTest(new TestSuite(PDFsRGBSettingsTestCase.class)); suite.addTest(new TestSuite(TrueTypeAnsiTestCase.class)); suite.addTest(RichTextFormatTestSuite.suite()); + suite.addTest(new TestSuite(PageBoundariesTest.class)); + suite.addTest(new TestSuite(PageScaleTest.class)); //$JUnit-END$ return suite; }