Index: src/java/org/apache/fop/area/PageViewport.java =================================================================== --- src/java/org/apache/fop/area/PageViewport.java (revision 775666) +++ src/java/org/apache/fop/area/PageViewport.java (working copy) @@ -100,6 +100,7 @@ public PageViewport(SimplePageMaster spm, int pageNumber, String pageStr, boolean blank) { this.simplePageMasterName = spm.getMasterName(); setExtensionAttachments(spm.getExtensionAttachments()); + setForeignAttributes(spm.getForeignAttributes()); this.blank = blank; int pageWidth = spm.getPageWidth().getValue(); int pageHeight = spm.getPageHeight().getValue(); @@ -118,6 +119,9 @@ 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; Index: src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java =================================================================== --- src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java (revision 775666) +++ src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java (working copy) @@ -21,12 +21,17 @@ import java.util.HashMap; import java.util.Set; +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.awt.geom.Rectangle2D; +import java.awt.geom.Point2D; import org.apache.xmlgraphics.util.QName; import org.apache.fop.fo.ElementMapping; import org.apache.fop.fo.FONode; import org.apache.fop.fo.UnknownXMLObj; +import org.apache.fop.fo.properties.FixedLength; import org.apache.fop.fo.extensions.destination.Destination; /** @@ -37,6 +42,20 @@ /** The FOP extension namespace URI */ public static final String URI = "http://xmlgraphics.apache.org/fop/extensions"; + /** The extension attribute for the PDF BleedBox area */ + public static final QName EXT_BLEED_BOX = new QName(ExtensionElementMapping.URI, null, "bleed-box"); + + /** The extension attribute for the PDF TrimBox area */ + public static final QName EXT_TRIM_BOX = new QName(ExtensionElementMapping.URI, null, "trim-box"); + + /** The extension attribute for the PDF CropBox area */ + public static final QName EXT_CROP_BOX = new QName(ExtensionElementMapping.URI, null, "crop-box"); + + /** The extension 'scale' attribute for simple-page-master element */ + public static final QName EXT_PAGE_SCALE = new QName(ExtensionElementMapping.URI, null, "scale"); + + private static final Pattern SIZE_UNIT_PATTERN = Pattern.compile("^(\\d*\\.?\\d*)(px|in|cm|mm|pt|pc|mpt)$"); + private static final Set propertyAttributes = new java.util.HashSet(); static { @@ -80,6 +99,81 @@ } } + /** + * Calculates the original size of extension box attributes + * @param coord string that contains coordinates of box area, format: left [right [ widht [height]]]] + * @param bounds the outer area containing given box. The box should be in this area completely + * @return the rectangle from the given coordinates, check that it is in the given bounds + */ + public static Rectangle2D getRectangle(String coord, Rectangle2D bounds) { + if (bounds == null) return null; + if (coord == null) return bounds; // if this box is undefined then we will return the outer box coordinates + + String tmp[] = coord.split(" "); + double dbl[] = new double[tmp.length]; + for (int i=0; i 0) { + x = Math.max(x, dbl[0]); + } + if (dbl.length > 1) { + y = Math.max(y, dbl[1]); + } + + double w = bounds.getMaxX() - x; + double h = bounds.getMaxY() - y; + if (dbl.length > 2) { + if (x + dbl[2] <= bounds.getMaxX()) { + w = dbl[2]; + } + } + if (dbl.length > 3) { + if (y + dbl[3] <= bounds.getMaxY()) { + h = dbl[3]; + } + } + + return new Rectangle2D.Double(x, y, w, h); + } + + /** + * Compute scale parameters from given fox:scale attribute which has format: scaleX [scaleY] + * If scaleY is not defined, it equals scaleX + */ + public static Point2D.Double getScaleAttributes(String scale) { + if (scale == null) { + return null; + } + + Point2D.Double result = null; + + try { + String[] scales = scale.split(" "); + if (scales.length > 0) { + result = new Point2D.Double(Double.parseDouble(scales[0]), Double.parseDouble(scales[0])); + } + if (scales.length > 1) { + result.y = Double.parseDouble(scales[1]); + } + if (result.x <= 0 || result.x > 1 || result.y <= 0 || result.y > 1) { + throw new IllegalArgumentException("Extension 'scale' variable has incorrect value(s): " + scale); + } + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("Extension 'scale' variable has incorrect value(s): " + scale); + } + + return result; + } + /** {@inheritDoc} */ public String getStandardPrefix() { return "fox"; Index: src/java/org/apache/fop/pdf/PDFFactory.java =================================================================== --- src/java/org/apache/fop/pdf/PDFFactory.java (revision 775666) +++ src/java/org/apache/fop/pdf/PDFFactory.java (working copy) @@ -174,6 +174,30 @@ * PDFDocument later using addObject(). * * @param resources resources object to use + * @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 pageIndex, + Rectangle2D mediaBox, Rectangle2D cropBox, + Rectangle2D bleedBox, Rectangle2D trimBox) { + PDFPage page = new PDFPage(resources, pageIndex, mediaBox, cropBox, bleedBox, trimBox); + + getDocument().assignObjectNumber(page); + getDocument().getPages().addPage(page); + return page; + } + + /** + * 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 * @param pageIndex index of the page (zero-based) Index: src/java/org/apache/fop/pdf/PDFPage.java =================================================================== --- src/java/org/apache/fop/pdf/PDFPage.java (revision 775666) +++ src/java/org/apache/fop/pdf/PDFPage.java (working copy) @@ -60,6 +60,28 @@ * Create a /Page object * * @param resources the /Resources object + * @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 + * @param bleedBox the BleedBox + * @param trimBox the TrimBox + */ + 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 */ + 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) @@ -71,41 +93,47 @@ 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 + setPageBox("MediaBox", box); + setPageBox("BleedBox", box); //Recommended by PDF/X + setPageBox("TrimBox", box); //Needed for PDF/X } + private void setSimplePageSize(Rectangle2D mediaBox, Rectangle2D cropBox, Rectangle2D bleedBox, Rectangle2D trimBox) { + setPageBox("MediaBox", mediaBox); + + if (cropBox == null) { + cropBox = mediaBox; + } + setPageBox("CropBox", cropBox); + + if (bleedBox == null) { + bleedBox = cropBox; + } + setPageBox("BleedBox", bleedBox); //Recommended by PDF/X + + if (trimBox == null) { + trimBox = bleedBox; + } + setPageBox("TrimBox", trimBox); //Needed for PDF/X + } + private PDFArray toPDFArray(Rectangle2D box) { +// return new PDFArray(this, new double[] { +// box.getX()/2, box.getY()/2, box.getMaxX()/2, box.getMaxY()/2}); return new PDFArray(this, new double[] { box.getX(), box.getY(), box.getMaxX(), box.getMaxY()}); } /** - * Sets the "MediaBox" entry - * @param box the media rectangle + * Sets the page box entry + * @param type the type of page box + * @param box the box rectangle */ - public void setMediaBox(Rectangle2D box) { - put("MediaBox", toPDFArray(box)); + public void setPageBox(String type, Rectangle2D box) { + put(type, toPDFArray(box)); } /** - * Sets the "TrimBox" entry - * @param box the trim rectangle - */ - public void setTrimBox(Rectangle2D box) { - put("TrimBox", toPDFArray(box)); - } - - /** - * Sets the "BleedBox" entry - * @param box the bleed rectangle - */ - public void setBleedBox(Rectangle2D box) { - put("BleedBox", toPDFArray(box)); - } - - /** * set this page contents * * @param contents the contents of the page Index: src/java/org/apache/fop/render/java2d/Java2DRenderer.java =================================================================== --- src/java/org/apache/fop/render/java2d/Java2DRenderer.java (revision 775666) +++ src/java/org/apache/fop/render/java2d/Java2DRenderer.java (working copy) @@ -68,6 +68,7 @@ import org.apache.fop.datatypes.URISpecification; import org.apache.fop.events.ResourceEventProducer; import org.apache.fop.fo.Constants; +import org.apache.fop.fo.extensions.ExtensionElementMapping; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontCollection; import org.apache.fop.fonts.FontInfo; @@ -299,11 +300,25 @@ + " (pageWidth " + pageWidth + ", pageHeight " + pageHeight + ")"); - double scale = scaleFactor + // set scale factor + double scaleX = scaleFactor; + double scaleY = scaleFactor; + String scale = (String) currentPageViewport.getForeignAttributes().get(ExtensionElementMapping.EXT_PAGE_SCALE); + Point2D scales = ExtensionElementMapping.getScaleAttributes(scale); + if (scales != null) { + scaleX = scales.getX(); + scaleY = scales.getY(); + } + + + scaleX = scaleX * (25.4f / FopFactoryConfigurator.DEFAULT_TARGET_RESOLUTION) / userAgent.getTargetPixelUnitToMillimeter(); - int bitmapWidth = (int) ((pageWidth * scale) + 0.5); - int bitmapHeight = (int) ((pageHeight * scale) + 0.5); + scaleY = scaleY + * (25.4f / FopFactoryConfigurator.DEFAULT_TARGET_RESOLUTION) + / userAgent.getTargetPixelUnitToMillimeter(); + int bitmapWidth = (int) ((pageWidth * scaleX) + 0.5); + int bitmapHeight = (int) ((pageHeight * scaleY) + 0.5); BufferedImage currentPageImage = getBufferedImage(bitmapWidth, bitmapHeight); @@ -326,7 +341,7 @@ // transform page based on scale factor supplied AffineTransform at = graphics.getTransform(); - at.scale(scale, scale); + at.scale(scaleX, scaleY); graphics.setTransform(at); // draw page frame @@ -353,7 +368,15 @@ state = null; } - return currentPageImage; + // crop the bitmap image using cropBox extension page attribute + String crop = (String) currentPageViewport.getForeignAttributes().get(ExtensionElementMapping.EXT_CROP_BOX); + Rectangle2D cropArea = ExtensionElementMapping.getRectangle(crop, currentPageViewport.getViewArea()); + + return currentPageImage.getSubimage( + (int) (cropArea.getX() / 1000f * scaleX + 0.5), + (int) (cropArea.getY() / 1000f * scaleY + 0.5), + (int) (cropArea.getWidth() / 1000f * scaleX + 0.5), + (int) (cropArea.getHeight() / 1000f * scaleY + 0.5)); } finally { this.currentPageViewport = null; } Index: src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java =================================================================== --- src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java (revision 775666) +++ src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java (working copy) @@ -21,6 +21,8 @@ import java.awt.Dimension; import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.geom.Point2D; import java.io.IOException; import java.util.Map; @@ -31,6 +33,7 @@ import org.apache.fop.apps.MimeConstants; import org.apache.fop.fo.extensions.xmp.XMPMetadata; +import org.apache.fop.fo.extensions.ExtensionElementMapping; import org.apache.fop.pdf.PDFAnnotList; import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFPage; @@ -166,11 +169,33 @@ throws IFException { this.pdfResources = this.pdfDoc.getResources(); + String crop = (String) getContext().getForeignAttribute(ExtensionElementMapping.EXT_CROP_BOX); + String bleed = (String) getContext().getForeignAttribute(ExtensionElementMapping.EXT_BLEED_BOX); + String trim = (String) getContext().getForeignAttribute(ExtensionElementMapping.EXT_TRIM_BOX); + // make sure that CropBox in the MediaBox + Rectangle2D cropBox = ExtensionElementMapping.getRectangle(crop, new Rectangle2D.Double(0, 0, size.getWidth(), size.getHeight())); + // make sure that BleedBox in the CropBox + Rectangle2D bleedBox = ExtensionElementMapping.getRectangle(bleed, cropBox); + // make sure that TrimBox in the BleedBox + Rectangle2D trimBox = ExtensionElementMapping.getRectangle(trim, bleedBox); + + // set scale attributes + double scaleX = 1; + double scaleY = 1; + String scale = (String) getContext().getForeignAttribute(ExtensionElementMapping.EXT_PAGE_SCALE); + Point2D scales = ExtensionElementMapping.getScaleAttributes(scale); + if (scales != null) { + scaleX = scales.getX(); + scaleY = scales.getY(); + } + this.currentPage = this.pdfDoc.getFactory().makePage( this.pdfResources, - (int)Math.round(size.getWidth() / 1000), - (int)Math.round(size.getHeight() / 1000), - index); + index, + new Rectangle2D.Double(0, 0, (scaleX * size.getWidth()) / 1000, (scaleY * size.getHeight()) / 1000), + new Rectangle2D.Double((scaleX * cropBox.getX()) / 1000, (scaleY * cropBox.getY()) / 1000, (scaleX * cropBox.getWidth()) / 1000, (scaleY * cropBox.getHeight()) / 1000), + new Rectangle2D.Double((scaleX * bleedBox.getX()) / 1000, (scaleY * bleedBox.getY()) / 1000, (scaleX * bleedBox.getWidth()) / 1000, (scaleY * bleedBox.getHeight()) / 1000), + new Rectangle2D.Double((scaleX * trimBox.getX()) / 1000, (scaleY * trimBox.getY()) / 1000, (scaleX * trimBox.getWidth()) / 1000, (scaleY * trimBox.getHeight()) / 1000)); //pageReferences.put(new Integer(index)/*page.getKey()*/, currentPage.referencePDF()); //pvReferences.put(page.getKey(), page); @@ -182,7 +207,8 @@ this.generator = new PDFContentGenerator(this.pdfDoc, this.outputStream, this.currentPage); // Transform the PDF's default coordinate system (0,0 at lower left) to the PDFPainter's AffineTransform basicPageTransform = new AffineTransform(1, 0, 0, -1, 0, - size.height / 1000f); + (scaleY * size.height) / 1000f); + basicPageTransform.scale(scaleX, scaleY); generator.concatenate(basicPageTransform); }