From ecd9851991497df7b4bb2c9f1d72c66d6ac71a29 Mon Sep 17 00:00:00 2001 From: Jeremias Maerki Date: Wed, 14 Jun 2006 14:34:29 +0000 Subject: [PATCH] Added initial support for PDF/X-3:2003 (ISO 15930-6:2003(E)). Fixed bugs in PDF/A support: Transparency is now switched off and the CIDSet object is generated for CID fonts. PDF/A and PDF/X are now "profiles" (see PDFProfile) which are used to control the allowed, required and forbidden features in PDF. PDF/A-1b and PDF/X-3:2003 can both be activated simultaneously as they are compatible. For PDF/X, color handling has been improved a little (PDFColorSpace is now an interface, the old PDFColorSpace is now PDFDeviceColorSpace) and most importantly, the "DefaultRGB" color space is now mapped to the sRGB color space (for all colors in DeviceRGB) which ensures that all sRGB colors from XSL-FO are also sRGB colors in PDF which wasn't necessarily the case before. For PDF/X, added support for a custom, non-sRGB output profile because PDF/X requires a "Output Device Profile" (which sRGB is not). ICC profiles are now shared/cached among images. Added support for fox:conversion-mode="bitmap" in the PDFRenderer to handle SVGs with transparency when transparency is forbidden (PDF/X and PDF/A). Only drawback: the image is not combined with the background. An opaque bitmap with a white background is generated. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@414272 13f79535-47bb-0310-9956-ffa450edef68 --- src/java/org/apache/fop/pdf/BitmapImage.java | 8 +- src/java/org/apache/fop/pdf/PDFColor.java | 24 +- .../org/apache/fop/pdf/PDFColorSpace.java | 154 ++--------- .../apache/fop/pdf/PDFDeviceColorSpace.java | 139 ++++++++++ src/java/org/apache/fop/pdf/PDFDocument.java | 46 +--- src/java/org/apache/fop/pdf/PDFFactory.java | 104 ++++++-- src/java/org/apache/fop/pdf/PDFFont.java | 5 +- .../org/apache/fop/pdf/PDFFontDescriptor.java | 18 ++ .../org/apache/fop/pdf/PDFFontNonBase14.java | 5 +- .../apache/fop/pdf/PDFICCBasedColorSpace.java | 95 +++++++ src/java/org/apache/fop/pdf/PDFICCStream.java | 18 +- src/java/org/apache/fop/pdf/PDFImage.java | 2 +- src/java/org/apache/fop/pdf/PDFInfo.java | 23 +- src/java/org/apache/fop/pdf/PDFLink.java | 3 +- src/java/org/apache/fop/pdf/PDFMetadata.java | 10 +- src/java/org/apache/fop/pdf/PDFPage.java | 6 +- src/java/org/apache/fop/pdf/PDFPathPaint.java | 2 +- src/java/org/apache/fop/pdf/PDFProfile.java | 244 ++++++++++++++++++ src/java/org/apache/fop/pdf/PDFResources.java | 49 ++++ src/java/org/apache/fop/pdf/PDFShading.java | 12 +- src/java/org/apache/fop/pdf/PDFXMode.java | 62 +++++ src/java/org/apache/fop/pdf/PDFXObject.java | 15 +- .../apache/fop/render/RendererContext.java | 5 + .../apache/fop/render/pdf/FopPDFImage.java | 52 ++-- .../fop/render/pdf/PDFGraphics2DAdapter.java | 40 ++- .../apache/fop/render/pdf/PDFRenderer.java | 209 +++++++++++---- .../apache/fop/render/pdf/PDFSVGHandler.java | 60 +++-- .../org/apache/fop/svg/PDFGraphics2D.java | 28 +- status.xml | 8 + 29 files changed, 1097 insertions(+), 349 deletions(-) create mode 100644 src/java/org/apache/fop/pdf/PDFDeviceColorSpace.java create mode 100644 src/java/org/apache/fop/pdf/PDFICCBasedColorSpace.java create mode 100644 src/java/org/apache/fop/pdf/PDFProfile.java create mode 100644 src/java/org/apache/fop/pdf/PDFXMode.java diff --git a/src/java/org/apache/fop/pdf/BitmapImage.java b/src/java/org/apache/fop/pdf/BitmapImage.java index 4e6664cff..c9ecc6f97 100644 --- a/src/java/org/apache/fop/pdf/BitmapImage.java +++ b/src/java/org/apache/fop/pdf/BitmapImage.java @@ -31,7 +31,7 @@ public class BitmapImage implements PDFImage { private int height; private int width; private int bitsPerPixel; - private PDFColorSpace colorSpace; + private PDFDeviceColorSpace colorSpace; private byte[] bitmaps; private String maskRef; private PDFColor transparent = null; @@ -54,7 +54,7 @@ public class BitmapImage implements PDFImage { this.height = height; this.width = width; this.bitsPerPixel = 8; - this.colorSpace = new PDFColorSpace(PDFColorSpace.DEVICE_RGB); + this.colorSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); this.bitmaps = data; maskRef = mask; } @@ -103,7 +103,7 @@ public class BitmapImage implements PDFImage { * * @param cs the pdf color space */ - public void setColorSpace(PDFColorSpace cs) { + public void setColorSpace(PDFDeviceColorSpace cs) { colorSpace = cs; } @@ -113,7 +113,7 @@ public class BitmapImage implements PDFImage { * * @return the pdf doclor space */ - public PDFColorSpace getColorSpace() { + public PDFDeviceColorSpace getColorSpace() { return colorSpace; } diff --git a/src/java/org/apache/fop/pdf/PDFColor.java b/src/java/org/apache/fop/pdf/PDFColor.java index 37e75dcfb..dfa096b5c 100644 --- a/src/java/org/apache/fop/pdf/PDFColor.java +++ b/src/java/org/apache/fop/pdf/PDFColor.java @@ -47,7 +47,7 @@ public class PDFColor extends PDFPathPaint { */ public PDFColor(double theRed, double theGreen, double theBlue) { // super(theNumber); - this.colorSpace = new PDFColorSpace(PDFColorSpace.DEVICE_RGB); + this.colorSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); this.red = theRed; this.green = theGreen; @@ -60,7 +60,7 @@ public class PDFColor extends PDFPathPaint { * @param col the sRGB color */ public PDFColor(java.awt.Color col) { - this.colorSpace = new PDFColorSpace(PDFColorSpace.DEVICE_RGB); + this.colorSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); float[] comps = new float[3]; comps = col.getColorComponents(comps); @@ -94,7 +94,7 @@ public class PDFColor extends PDFPathPaint { double theBlack) { // super(theNumber);//? - this.colorSpace = new PDFColorSpace(PDFColorSpace.DEVICE_CMYK); + this.colorSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_CMYK); this.cyan = theCyan; this.magenta = theMagenta; @@ -110,13 +110,13 @@ public class PDFColor extends PDFPathPaint { */ public List getVector() { List theColorVector = new ArrayList(); - if (this.colorSpace.getColorSpace() == PDFColorSpace.DEVICE_RGB) { + if (this.colorSpace.getColorSpace() == PDFDeviceColorSpace.DEVICE_RGB) { // RGB theColorVector.add(new Double(this.red)); theColorVector.add(new Double(this.green)); theColorVector.add(new Double(this.blue)); } else if (this.colorSpace.getColorSpace() - == PDFColorSpace.DEVICE_CMYK) { + == PDFDeviceColorSpace.DEVICE_CMYK) { // CMYK theColorVector.add(new Double(this.cyan)); theColorVector.add(new Double(this.magenta)); @@ -229,15 +229,15 @@ public class PDFColor extends PDFPathPaint { public void setColorSpace(int theColorSpace) { int theOldColorSpace = this.colorSpace.getColorSpace(); if (theOldColorSpace != theColorSpace) { - if (theOldColorSpace == PDFColorSpace.DEVICE_RGB) { - if (theColorSpace == PDFColorSpace.DEVICE_CMYK) { + if (theOldColorSpace == PDFDeviceColorSpace.DEVICE_RGB) { + if (theColorSpace == PDFDeviceColorSpace.DEVICE_CMYK) { this.convertRGBtoCMYK(); } else { // convert to Gray? this.convertRGBtoGRAY(); } - } else if (theOldColorSpace == PDFColorSpace.DEVICE_CMYK) { - if (theColorSpace == PDFColorSpace.DEVICE_RGB) { + } else if (theOldColorSpace == PDFDeviceColorSpace.DEVICE_CMYK) { + if (theColorSpace == PDFDeviceColorSpace.DEVICE_RGB) { this.convertCMYKtoRGB(); } else { // convert to Gray? @@ -245,7 +245,7 @@ public class PDFColor extends PDFPathPaint { } } else { // used to be Gray - if (theColorSpace == PDFColorSpace.DEVICE_RGB) { + if (theColorSpace == PDFDeviceColorSpace.DEVICE_RGB) { this.convertGRAYtoRGB(); } else { // convert to CMYK? @@ -270,7 +270,7 @@ public class PDFColor extends PDFPathPaint { double tempDouble; if (this.colorSpace.getColorSpace() - == PDFColorSpace.DEVICE_RGB) { // colorspace is RGB + == PDFDeviceColorSpace.DEVICE_RGB) { // colorspace is RGB // according to pdfspec 12.1 p.399 // if the colors are the same then just use the g or G operator boolean same = false; @@ -300,7 +300,7 @@ public class PDFColor extends PDFPathPaint { } } } else if (this.colorSpace.getColorSpace() - == PDFColorSpace.DEVICE_CMYK) { + == PDFDeviceColorSpace.DEVICE_CMYK) { // colorspace is CMYK if (fillNotStroke) { diff --git a/src/java/org/apache/fop/pdf/PDFColorSpace.java b/src/java/org/apache/fop/pdf/PDFColorSpace.java index 780d9d932..8ddda27a2 100644 --- a/src/java/org/apache/fop/pdf/PDFColorSpace.java +++ b/src/java/org/apache/fop/pdf/PDFColorSpace.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2004 The Apache Software Foundation. + * Copyright 2006 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,146 +21,28 @@ package org.apache.fop.pdf; /** * PDF Color space. */ -public class PDFColorSpace { - private boolean hasICCProfile; - private byte[] iccProfile; - private int numComponents; - - // Ok... so I had some grand purpose for this, but I can't recall. - // I'm just writing it - - /** - * Unknown colorspace - */ - public static final int DEVICE_UNKNOWN = -1; - - /** - * Gray colorspace - */ - public static final int DEVICE_GRAY = 1; - - /** - * RGB colorspace - */ - public static final int DEVICE_RGB = 2; - - /** - * CMYK colorspace - */ - public static final int DEVICE_CMYK = 3; - - // Are there any others? - - /** - * Current color space value. - */ - protected int currentColorSpace = DEVICE_UNKNOWN; - - /** - * Create a PDF colorspace object. - * - * @param theColorSpace the current colorspace - */ - public PDFColorSpace(int theColorSpace) { - this.currentColorSpace = theColorSpace; - hasICCProfile = false; - numComponents = calculateNumComponents(); - } - - private int calculateNumComponents() { - if (currentColorSpace == DEVICE_GRAY) { - return 1; - } else if (currentColorSpace == DEVICE_RGB) { - return 3; - } else if (currentColorSpace == DEVICE_CMYK) { - return 4; - } else { - return 0; - } - } - - /** - * Set the current colorspace. - * - * @param theColorSpace the new color space value - */ - public void setColorSpace(int theColorSpace) { - this.currentColorSpace = theColorSpace; - numComponents = calculateNumComponents(); - } - - /** - * Check if this colorspace has an ICC profile. - * - * @return true if this has an ICC profile - */ - public boolean hasICCProfile() { - return hasICCProfile; - } - - /** - * Get the ICC profile for this colorspace - * - * @return the byte array containing the ICC profile data - */ - public byte[] getICCProfile() { - if (hasICCProfile) { - return iccProfile; - } else { - return new byte[0]; - } - } - - /** - * Set the ICC profile for this colorspace. - * - * @param iccProfile the ICC profile data - */ - public void setICCProfile(byte[] iccProfile) { - this.iccProfile = iccProfile; - hasICCProfile = true; - } - - /** - * Get the colorspace value - * - * @return the colorspace value - */ - public int getColorSpace() { - return (this.currentColorSpace); - } - +public interface PDFColorSpace { + /** * Get the number of color components for this colorspace - * * @return the number of components */ - public int getNumComponents() { - return numComponents; - } + int getNumComponents(); + /** @return the name of the color space */ + String getName(); + /** - * Get the PDF string for this colorspace. - * - * @return the PDF string for the colorspace + * @return true if the color space is a device-dependent color space (like DeviceRGB, + * DeviceCMYK and DeviceGray) */ - public String getColorSpacePDFString() { - // shouldn't this be a select-case? I can never remember - // the syntax for that. - switch (currentColorSpace) { - case DEVICE_CMYK: - return "DeviceCMYK"; - //break; - case DEVICE_GRAY: - return "DeviceGray"; - //break; - case DEVICE_RGB: - default: - // unknown... Error. Tell them it's RGB and hope they - // don't notice. - return ("DeviceRGB"); - //break; - } - } - + boolean isDeviceColorSpace(); + + /** @return true if the color space is an RGB color space */ + boolean isRGBColorSpace(); + /** @return true if the color space is an CMYK color space */ + boolean isCMYKColorSpace(); + /** @return true if the color space is an Gray color space */ + boolean isGrayColorSpace(); + } diff --git a/src/java/org/apache/fop/pdf/PDFDeviceColorSpace.java b/src/java/org/apache/fop/pdf/PDFDeviceColorSpace.java new file mode 100644 index 000000000..1cdec346a --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFDeviceColorSpace.java @@ -0,0 +1,139 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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.pdf; + +/** + * Represents a device-specific color space. Used for mapping DeviceRGB, DeviceCMYK and DeviceGray. + */ +public class PDFDeviceColorSpace implements PDFColorSpace { + + private int numComponents; + + /** + * Unknown colorspace + */ + public static final int DEVICE_UNKNOWN = -1; + + /** + * Gray colorspace + */ + public static final int DEVICE_GRAY = 1; + + /** + * RGB colorspace + */ + public static final int DEVICE_RGB = 2; + + /** + * CMYK colorspace + */ + public static final int DEVICE_CMYK = 3; + + // Are there any others? + + /** + * Current color space value. + */ + protected int currentColorSpace = DEVICE_UNKNOWN; + + /** + * Create a PDF colorspace object. + * + * @param theColorSpace the current colorspace + */ + public PDFDeviceColorSpace(int theColorSpace) { + this.currentColorSpace = theColorSpace; + numComponents = calculateNumComponents(); + } + + private int calculateNumComponents() { + if (currentColorSpace == DEVICE_GRAY) { + return 1; + } else if (currentColorSpace == DEVICE_RGB) { + return 3; + } else if (currentColorSpace == DEVICE_CMYK) { + return 4; + } else { + return 0; + } + } + + /** + * Set the current colorspace. + * + * @param theColorSpace the new color space value + */ + public void setColorSpace(int theColorSpace) { + this.currentColorSpace = theColorSpace; + numComponents = calculateNumComponents(); + } + + /** + * Get the colorspace value + * + * @return the colorspace value + */ + public int getColorSpace() { + return (this.currentColorSpace); + } + + /** + * Get the number of color components for this colorspace + * + * @return the number of components + */ + public int getNumComponents() { + return numComponents; + } + + /** @return the name of the color space */ + public String getName() { + switch (currentColorSpace) { + case DEVICE_CMYK: + return "DeviceCMYK"; + case DEVICE_GRAY: + return "DeviceGray"; + case DEVICE_RGB: + return "DeviceRGB"; + default: + throw new IllegalStateException("Unsupported color space in use."); + } + } + + /** @see org.apache.fop.pdf.PDFColorSpace#isDeviceColorSpace() */ + public boolean isDeviceColorSpace() { + return true; + } + + /** @see org.apache.fop.pdf.PDFColorSpace#isRGBColorSpace() */ + public boolean isRGBColorSpace() { + return getColorSpace() == DEVICE_RGB; + } + + /** @see org.apache.fop.pdf.PDFColorSpace#isCMYKColorSpace() */ + public boolean isCMYKColorSpace() { + return getColorSpace() == DEVICE_CMYK; + } + + /** @see org.apache.fop.pdf.PDFColorSpace#isGrayColorSpace() */ + public boolean isGrayColorSpace() { + return getColorSpace() == DEVICE_GRAY; + } + +} diff --git a/src/java/org/apache/fop/pdf/PDFDocument.java b/src/java/org/apache/fop/pdf/PDFDocument.java index f97c498b6..a55252924 100644 --- a/src/java/org/apache/fop/pdf/PDFDocument.java +++ b/src/java/org/apache/fop/pdf/PDFDocument.java @@ -108,10 +108,9 @@ public class PDFDocument { protected int pdfVersion = PDF_VERSION_1_4; /** - * Indicates the PDF/A-1 mode currently active. Defaults to "no restrictions", i.e. - * PDF/A-1 not active. + * Indicates which PDF profiles are active (PDF/A, PDF/X etc.) */ - protected PDFAMode pdfAMode = PDFAMode.DISABLED; + protected PDFProfile pdfProfile = new PDFProfile(this); /** * the /Root object @@ -142,8 +141,8 @@ public class PDFDocument { /** * the colorspace (0=RGB, 1=CMYK) */ - protected PDFColorSpace colorspace = - new PDFColorSpace(PDFColorSpace.DEVICE_RGB); + protected PDFDeviceColorSpace colorspace = + new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); /** * the counter for Pattern name numbering (e.g. 'Pattern1') @@ -268,26 +267,9 @@ public class PDFDocument { } } - /** @return the PDF/A mode currently active. */ - public PDFAMode getPDFAMode() { - return this.pdfAMode; - } - - /** - * Sets the active PDF/A mode. This must be set immediately after calling the constructor so - * the checks will be activated. - * @param mode one of the PDFAMode constants - */ - public void setPDFAMode(PDFAMode mode) { - if (mode == null) { - throw new NullPointerException("mode must not be null"); - } - if (mode == PDFAMode.PDFA_1A) { - throw new UnsupportedOperationException("PDF/A-1a is not implemented, yet"); - } else if (mode == PDFAMode.PDFA_1B) { - //you got the green light! - } - this.pdfAMode = mode; + /** @return the PDF profile currently active. */ + public PDFProfile getProfile() { + return this.pdfProfile; } /** @@ -514,9 +496,7 @@ public class PDFDocument { * @param params The encryption parameters for the pdf file */ public void setEncryption(PDFEncryptionParams params) { - if (getPDFAMode().isPDFA1LevelB()) { - throw new PDFConformanceException("PDF/A-1 doesn't allow encrypted PDFs"); - } + getProfile().verifyEncryptionAllowed(); this.encryption = PDFEncryptionManager.newInstance(++this.objectcount, params); ((PDFObject)this.encryption).setDocument(this); if (encryption != null) { @@ -658,7 +638,7 @@ public class PDFDocument { * * @return the color space */ - public PDFColorSpace getPDFColorSpace() { + public PDFDeviceColorSpace getPDFColorSpace() { return this.colorspace; } @@ -866,9 +846,7 @@ public class PDFDocument { public void outputHeader(OutputStream stream) throws IOException { this.position = 0; - if (getPDFAMode().isPDFA1LevelB() && getPDFVersion() != PDF_VERSION_1_4) { - throw new PDFConformanceException("PDF version must be 1.4 for " + getPDFAMode()); - } + getProfile().verifyPDFVersion(); byte[] pdf = ("%PDF-" + getPDFVersionString() + "\n").getBytes(); stream.write(pdf); @@ -899,10 +877,10 @@ public class PDFDocument { String s = PDFText.toHex(res); return "/ID [" + s + " " + s + "]"; } catch (NoSuchAlgorithmException e) { - if (getPDFAMode().isPDFA1LevelB()) { + if (getProfile().isIDEntryRequired()) { throw new UnsupportedOperationException("MD5 not available: " + e.getMessage()); } else { - return ""; //Entry is optional if PDF/A is not active + return ""; //Entry is optional if PDF/A or PDF/X are not active } } } diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java index 432063f9f..84eb6fd7e 100644 --- a/src/java/org/apache/fop/pdf/PDFFactory.java +++ b/src/java/org/apache/fop/pdf/PDFFactory.java @@ -24,6 +24,8 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; +import java.util.BitSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import javax.xml.transform.Source; @@ -34,6 +36,7 @@ import org.w3c.dom.Document; // Apache libs import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -435,7 +438,7 @@ public class PDFFactory { * @return the PDF shading that was created */ public PDFShading makeShading(PDFResourceContext res, int theShadingType, - PDFColorSpace theColorSpace, + PDFDeviceColorSpace theColorSpace, List theBackground, List theBBox, boolean theAntiAlias, List theDomain, List theMatrix, @@ -487,7 +490,7 @@ public class PDFFactory { * @return the PDF shading that was created */ public PDFShading makeShading(PDFResourceContext res, int theShadingType, - PDFColorSpace theColorSpace, + PDFDeviceColorSpace theColorSpace, List theBackground, List theBBox, boolean theAntiAlias, List theCoords, List theDomain, PDFFunction theFunction, @@ -540,7 +543,7 @@ public class PDFFactory { * @return the PDF shading that was created */ public PDFShading makeShading(PDFResourceContext res, int theShadingType, - PDFColorSpace theColorSpace, + PDFDeviceColorSpace theColorSpace, List theBackground, List theBBox, boolean theAntiAlias, int theBitsPerCoordinate, @@ -595,7 +598,7 @@ public class PDFFactory { * @return the PDF shading that was created */ public PDFShading makeShading(PDFResourceContext res, int theShadingType, - PDFColorSpace theColorSpace, + PDFDeviceColorSpace theColorSpace, List theBackground, List theBBox, boolean theAntiAlias, int theBitsPerCoordinate, @@ -718,7 +721,7 @@ public class PDFFactory { * @return the PDF pattern that was created */ public PDFPattern makeGradient(PDFResourceContext res, boolean radial, - PDFColorSpace theColorspace, + PDFDeviceColorSpace theColorspace, List theColors, List theBounds, List theCoords, List theMatrix) { PDFShading myShad; @@ -887,6 +890,7 @@ public class PDFFactory { } private String getGoToReference(String destination, float yoffset) { + getDocument().getProfile().verifyActionAllowed(); String goToReference = null; PDFGoTo gt = new PDFGoTo(destination); gt.setYPosition(yoffset); @@ -914,6 +918,7 @@ public class PDFFactory { * @return the pdf goto remote object */ private PDFGoToRemote getGoToPDFAction(String file, String dest, int page) { + getDocument().getProfile().verifyActionAllowed(); PDFFileSpec fileSpec = new PDFFileSpec(file); PDFFileSpec oldspec = getDocument().findFileSpec(fileSpec); if (oldspec == null) { @@ -1122,10 +1127,47 @@ public class PDFFactory { descriptor.setFontFile(desc.getFontType(), stream); getDocument().registerObject(stream); } + CustomFont font = getCustomFont(desc); + if (font instanceof CIDFont) { + CIDFont cidFont = (CIDFont)font; + buildCIDSet(descriptor, cidFont); + } } return descriptor; } + private void buildCIDSet(PDFFontDescriptor descriptor, CIDFont cidFont) { + BitSet cidSubset = new BitSet(); + Iterator iter = cidFont.usedGlyphs.keySet().iterator(); + while (iter.hasNext()) { + Integer cid = (Integer)iter.next(); + cidSubset.set(cid.intValue()); + } + PDFStream cidSet = makeStream(null, true); + ByteArrayOutputStream baout = new ByteArrayOutputStream(cidSubset.length() / 8 + 1); + int value = 0; + for (int i = 0, c = cidSubset.length(); i < c; i++) { + int shift = i % 8; + boolean b = cidSubset.get(i); + if (b) { + value |= 1 << 7 - shift; + } + if (shift == 7) { + baout.write(value); + value = 0; + } + } + baout.write(value); + try { + cidSet.setData(baout.toByteArray()); + descriptor.setCIDSet(cidSet); + } catch (IOException ioe) { + log.error( + "Failed to write CIDSet [" + cidFont + "] " + + cidFont.getFontName(), ioe); + } + } + /** * Embeds a font. * @param desc FontDescriptor of the font. @@ -1137,18 +1179,7 @@ public class PDFFactory { + desc.getFontType()); } - Typeface tempFont; - if (desc instanceof LazyFont) { - tempFont = ((LazyFont)desc).getRealFont(); - } else { - tempFont = (Typeface)desc; - } - if (!(tempFont instanceof CustomFont)) { - throw new IllegalArgumentException( - "FontDescriptor must be instance of CustomFont, but is a " - + desc.getClass().getName()); - } - CustomFont font = (CustomFont)tempFont; + CustomFont font = getCustomFont(desc); InputStream in = null; try { @@ -1227,6 +1258,21 @@ public class PDFFactory { } } + private CustomFont getCustomFont(FontDescriptor desc) { + Typeface tempFont; + if (desc instanceof LazyFont) { + tempFont = ((LazyFont)desc).getRealFont(); + } else { + tempFont = (Typeface)desc; + } + if (!(tempFont instanceof CustomFont)) { + throw new IllegalArgumentException( + "FontDescriptor must be instance of CustomFont, but is a " + + desc.getClass().getName()); + } + return (CustomFont)tempFont; + } + /* ========================= streams =================================== */ @@ -1258,7 +1304,7 @@ public class PDFFactory { * Create a PDFICCStream * @see PDFXObject * @see org.apache.fop.image.JpegImage - * @see org.apache.fop.pdf.PDFColorSpace + * @see org.apache.fop.pdf.PDFDeviceColorSpace * @return the new PDF ICC stream object */ public PDFICCStream makePDFICCStream() { @@ -1274,6 +1320,28 @@ public class PDFFactory { /* ========================= misc. objects ============================= */ + /** + * Makes a new ICCBased color space and registers it in the resource context. + * @param res the PDF resource context to add the shading, may be null + * @param explicitName the explicit name for the color space, may be null + * @param iccStream the ICC stream to associate with this color space + * @return the newly instantiated color space + */ + public PDFICCBasedColorSpace makeICCBasedColorSpace(PDFResourceContext res, + String explicitName, PDFICCStream iccStream) { + PDFICCBasedColorSpace cs = new PDFICCBasedColorSpace(explicitName, iccStream); + + getDocument().registerObject(cs); + + if (res != null) { + res.getPDFResources().addColorSpace(cs); + } else { + getDocument().getResources().addColorSpace(cs); + } + + return cs; + } + /** * make an Array object (ex. Widths array for a font) * diff --git a/src/java/org/apache/fop/pdf/PDFFont.java b/src/java/org/apache/fop/pdf/PDFFont.java index b98736487..9d2977298 100644 --- a/src/java/org/apache/fop/pdf/PDFFont.java +++ b/src/java/org/apache/fop/pdf/PDFFont.java @@ -193,9 +193,10 @@ public class PDFFont extends PDFObject { * Validates the PDF object prior to serialization. */ protected void validate() { - if (getDocumentSafely().getPDFAMode().isPDFA1LevelB()) { + if (getDocumentSafely().getProfile().isFontEmbeddingRequired()) { if (this.getClass() == PDFFont.class) { - throw new PDFConformanceException("For PDF/A-1, all fonts, even the base 14" + throw new PDFConformanceException("For " + getDocumentSafely().getProfile() + + ", all fonts, even the base 14" + " fonts, have to be embedded! Offending font: " + this.basefont); } } diff --git a/src/java/org/apache/fop/pdf/PDFFontDescriptor.java b/src/java/org/apache/fop/pdf/PDFFontDescriptor.java index 24e0243f5..51883a015 100644 --- a/src/java/org/apache/fop/pdf/PDFFontDescriptor.java +++ b/src/java/org/apache/fop/pdf/PDFFontDescriptor.java @@ -44,6 +44,7 @@ public class PDFFontDescriptor extends PDFObject { private int maxWidth = 0; private int missingWidth = 0; private AbstractPDFStream fontfile; + private AbstractPDFStream cidSet; // private String charSet = null; private FontType subtype; @@ -119,6 +120,19 @@ public class PDFFontDescriptor extends PDFObject { return this.fontfile; } + /** + * Sets the CIDSet stream for this font descriptor. (Optional) + * @param cidSet the CIDSet stream + */ + public void setCIDSet(AbstractPDFStream cidSet) { + this.cidSet = cidSet; + } + + /** @return the CIDSet stream or null if not applicable */ + public AbstractPDFStream getCIDSet() { + return this.cidSet; + } + // public void setCharSet(){}//for subset fonts /** @@ -177,6 +191,10 @@ public class PDFFontDescriptor extends PDFObject { } p.append(fontfile.referencePDF()); } + if (getCIDSet() != null) { + p.append("\n/CIDSet "); + p.append(getCIDSet().referencePDF()); + } // charSet for subset fonts // not yet implemented // CID optional field fillInPDF(p); diff --git a/src/java/org/apache/fop/pdf/PDFFontNonBase14.java b/src/java/org/apache/fop/pdf/PDFFontNonBase14.java index e32b11ce4..cbdc9390f 100644 --- a/src/java/org/apache/fop/pdf/PDFFontNonBase14.java +++ b/src/java/org/apache/fop/pdf/PDFFontNonBase14.java @@ -95,9 +95,10 @@ public abstract class PDFFontNonBase14 extends PDFFont { /** @see org.apache.fop.pdf.PDFFont#validate() */ protected void validate() { - if (getDocumentSafely().getPDFAMode().isPDFA1LevelB()) { + if (getDocumentSafely().getProfile().isFontEmbeddingRequired()) { if (this.getDescriptor().getFontFile() == null) { - throw new PDFConformanceException("For PDF/A-1, all fonts have to be embedded!"); + throw new PDFConformanceException("For " + getDocumentSafely().getProfile() + + ", all fonts have to be embedded!"); } } } diff --git a/src/java/org/apache/fop/pdf/PDFICCBasedColorSpace.java b/src/java/org/apache/fop/pdf/PDFICCBasedColorSpace.java new file mode 100644 index 000000000..1e2e25810 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFICCBasedColorSpace.java @@ -0,0 +1,95 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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.pdf; + +/** + * Represents an ICCBased color space in PDF. + */ +public class PDFICCBasedColorSpace extends PDFObject implements PDFColorSpace { + + private PDFICCStream iccStream; + private String explicitName; + + /** + * Constructs a the ICCBased color space with an explicit name (ex. "DefaultRGB"). + * @param explicitName an explicit name or null if a name should be generated + * @param iccStream the ICC stream to associate with this color space + */ + public PDFICCBasedColorSpace(String explicitName, PDFICCStream iccStream) { + this.explicitName = explicitName; + this.iccStream = iccStream; + } + + /** + * Constructs a the ICCBased color space. + * @param iccStream the ICC stream to associate with this color space + */ + public PDFICCBasedColorSpace(PDFICCStream iccStream) { + this(null, iccStream); + } + + /** @return the ICC stream associated with this color space */ + public PDFICCStream getICCStream() { + return this.iccStream; + } + + /** @see org.apache.fop.pdf.PDFColorSpace#getNumComponents() */ + public int getNumComponents() { + return iccStream.getICCProfile().getNumComponents(); + } + + /** @see org.apache.fop.pdf.PDFColorSpace#getName() */ + public String getName() { + if (explicitName != null) { + return explicitName; + } else { + return "ICC" + iccStream.getObjectNumber(); + } + } + + /** @see org.apache.fop.pdf.PDFColorSpace#isDeviceColorSpace() */ + public boolean isDeviceColorSpace() { + return false; + } + + /** @see org.apache.fop.pdf.PDFColorSpace#isRGBColorSpace() */ + public boolean isRGBColorSpace() { + return getNumComponents() == 3; + } + + /** @see org.apache.fop.pdf.PDFColorSpace#isCMYKColorSpace() */ + public boolean isCMYKColorSpace() { + return getNumComponents() == 4; + } + + /** @see org.apache.fop.pdf.PDFColorSpace#isGrayColorSpace() */ + public boolean isGrayColorSpace() { + return getNumComponents() == 1; + } + + /** @see org.apache.fop.pdf.PDFObject#toPDFString() */ + protected String toPDFString() { + StringBuffer sb = new StringBuffer(64); + sb.append(getObjectID()); + sb.append("[/ICCBased ").append(getICCStream().referencePDF()).append("]"); + sb.append("\nendobj\n"); + return sb.toString(); + } + +} diff --git a/src/java/org/apache/fop/pdf/PDFICCStream.java b/src/java/org/apache/fop/pdf/PDFICCStream.java index 1289cf244..2d7ca4674 100644 --- a/src/java/org/apache/fop/pdf/PDFICCStream.java +++ b/src/java/org/apache/fop/pdf/PDFICCStream.java @@ -27,11 +27,8 @@ import java.io.OutputStream; */ public class PDFICCStream extends PDFStream { - private int origLength; - private int len1, len3; - private ICC_Profile cp; - private PDFColorSpace pdfColorSpace; + private PDFDeviceColorSpace pdfColorSpace; /** * @see org.apache.fop.pdf.PDFObject#PDFObject() @@ -43,14 +40,19 @@ public class PDFICCStream extends PDFStream { /** * Sets the color space to encode in PDF. - * @param cp the ICC profile + * @param icc the ICC profile * @param alt the PDF color space */ - public void setColorSpace(ICC_Profile cp, PDFColorSpace alt) { - this.cp = cp; + public void setColorSpace(ICC_Profile icc, PDFDeviceColorSpace alt) { + this.cp = icc; pdfColorSpace = alt; } + /** @return the ICC profile */ + public ICC_Profile getICCProfile() { + return this.cp; + } + /** * overload the base object method so we don't have to copy * byte arrays around so much @@ -81,7 +83,7 @@ public class PDFICCStream extends PDFStream { sb.append("/N " + cp.getNumComponents()); if (pdfColorSpace != null) { - sb.append("\n/Alternate /" + pdfColorSpace.getColorSpacePDFString() + " "); + sb.append("\n/Alternate /" + pdfColorSpace.getName() + " "); } sb.append("\n/Length " + lengthEntry); diff --git a/src/java/org/apache/fop/pdf/PDFImage.java b/src/java/org/apache/fop/pdf/PDFImage.java index 23114c656..7c81dc512 100644 --- a/src/java/org/apache/fop/pdf/PDFImage.java +++ b/src/java/org/apache/fop/pdf/PDFImage.java @@ -65,7 +65,7 @@ public interface PDFImage { * * @return the color space */ - PDFColorSpace getColorSpace(); + PDFDeviceColorSpace getColorSpace(); /** * Get the bits per pixel for this image. diff --git a/src/java/org/apache/fop/pdf/PDFInfo.java b/src/java/org/apache/fop/pdf/PDFInfo.java index b12b27efd..b7a4e0e30 100644 --- a/src/java/org/apache/fop/pdf/PDFInfo.java +++ b/src/java/org/apache/fop/pdf/PDFInfo.java @@ -146,14 +146,17 @@ public class PDFInfo extends PDFObject { * @see org.apache.fop.pdf.PDFObject#toPDF() */ public byte[] toPDF() { + PDFProfile profile = getDocumentSafely().getProfile(); ByteArrayOutputStream bout = new ByteArrayOutputStream(128); try { bout.write(encode(getObjectID())); bout.write(encode("<<\n")); - if (title != null) { + if (title != null && title.length() > 0) { bout.write(encode("/Title ")); bout.write(encodeText(this.title)); bout.write(encode("\n")); + } else { + profile.verifyTitleAbsent(); } if (author != null) { bout.write(encode("/Author ")); @@ -187,7 +190,23 @@ public class PDFInfo extends PDFObject { } bout.write(encode("/CreationDate ")); bout.write(encodeString(formatDateTime(creationDate))); - bout.write(encode("\n>>\nendobj\n")); + bout.write(encode("\n")); + + if (profile.isModDateRequired()) { + bout.write(encode("/ModDate ")); + bout.write(encodeString(formatDateTime(creationDate))); + bout.write(encode("\n")); + } + if (profile.isPDFXActive()) { + bout.write(encode("/GTS_PDFXVersion ")); + bout.write(encodeString(profile.getPDFXMode().getName())); + bout.write(encode("\n")); + } + if (profile.isTrappedEntryRequired()) { + bout.write(encode("/Trapped /False\n")); + } + + bout.write(encode(">>\nendobj\n")); } catch (IOException ioe) { log.error("Ignored I/O exception", ioe); } diff --git a/src/java/org/apache/fop/pdf/PDFLink.java b/src/java/org/apache/fop/pdf/PDFLink.java index e0cd2a661..377f4e592 100644 --- a/src/java/org/apache/fop/pdf/PDFLink.java +++ b/src/java/org/apache/fop/pdf/PDFLink.java @@ -71,8 +71,9 @@ public class PDFLink extends PDFObject { * @see org.apache.fop.pdf.PDFObject#toPDFString() */ public String toPDFString() { + getDocumentSafely().getProfile().verifyAnnotAllowed(); String fFlag = ""; - if (getDocumentSafely().getPDFAMode().isPDFA1LevelB()) { + if (getDocumentSafely().getProfile().getPDFAMode().isPDFA1LevelB()) { int f = 0; f |= 1 << (3 - 1); //Print, bit 3 f |= 1 << (4 - 1); //NoZoom, bit 4 diff --git a/src/java/org/apache/fop/pdf/PDFMetadata.java b/src/java/org/apache/fop/pdf/PDFMetadata.java index 42aaed1b0..94e31372d 100644 --- a/src/java/org/apache/fop/pdf/PDFMetadata.java +++ b/src/java/org/apache/fop/pdf/PDFMetadata.java @@ -133,7 +133,7 @@ public class PDFMetadata extends PDFStream { /** @see org.apache.fop.pdf.AbstractPDFStream#buildStreamDict(String) */ protected String buildStreamDict(String lengthEntry) { final String filterEntry = getFilterList().buildFilterDictEntries(); - if (getDocumentSafely().getPDFAMode().isPDFA1LevelB() + if (getDocumentSafely().getProfile().getPDFAMode().isPDFA1LevelB() && filterEntry != null && filterEntry.length() > 0) { throw new PDFConformanceException( "The Filter key is prohibited when PDF/A-1 is active"); @@ -248,6 +248,12 @@ public class PDFMetadata extends PDFStream { el = doc.createElementNS(XMPConstants.XMP_BASIC_NAMESPACE, "xmp:CreateDate"); desc.appendChild(el); el.appendChild(doc.createTextNode(formatISO8601Date(info.getCreationDate()))); + PDFProfile profile = pdfDoc.getProfile(); + if (profile.isModDateRequired()) { + el = doc.createElementNS(XMPConstants.XMP_BASIC_NAMESPACE, "xmp:ModifyDate"); + desc.appendChild(el); + el.appendChild(doc.createTextNode(formatISO8601Date(info.getCreationDate()))); + } if (info.getCreator() != null) { el = doc.createElementNS(XMPConstants.XMP_BASIC_NAMESPACE, "xmp:CreatorTool"); desc.appendChild(el); @@ -274,7 +280,7 @@ public class PDFMetadata extends PDFStream { el.appendChild(doc.createTextNode(pdfDoc.getPDFVersionString())); //PDF/A identification - PDFAMode pdfaMode = pdfDoc.getPDFAMode(); + PDFAMode pdfaMode = pdfDoc.getProfile().getPDFAMode(); if (pdfaMode.isPDFA1LevelB()) { createPDFAIndentification(doc, rdf, XMPConstants.PDF_A_IDENTIFICATION, "pdfaid", pdfaMode); diff --git a/src/java/org/apache/fop/pdf/PDFPage.java b/src/java/org/apache/fop/pdf/PDFPage.java index bd00cbd2d..6fdbc74ad 100644 --- a/src/java/org/apache/fop/pdf/PDFPage.java +++ b/src/java/org/apache/fop/pdf/PDFPage.java @@ -157,11 +157,13 @@ public class PDFPage extends PDFResourceContext { public String toPDFString() { StringBuffer sb = new StringBuffer(); + String box = "[ 0 0 " + getWidth() + " " + getHeight() + " ]"; sb = sb.append(getObjectID() + "<< /Type /Page\n" + "/Parent " + this.parentRef + "\n" - + "/MediaBox [ 0 0 " + getWidth() + " " - + getHeight() + " ]\n" + + "/MediaBox " + box + "\n" + + "/TrimBox " + box + "\n" //Needed for PDF/X + + "/BleedBox " + box + "\n" //Recommended by PDF/X + "/Resources " + this.resources.referencePDF() + "\n" + "/Contents " + this.contents.referencePDF() + "\n"); if (this.annotList != null) { diff --git a/src/java/org/apache/fop/pdf/PDFPathPaint.java b/src/java/org/apache/fop/pdf/PDFPathPaint.java index 5fd34fafb..f4134b81a 100644 --- a/src/java/org/apache/fop/pdf/PDFPathPaint.java +++ b/src/java/org/apache/fop/pdf/PDFPathPaint.java @@ -27,7 +27,7 @@ public abstract class PDFPathPaint extends PDFObject { /** * The color space for this paint */ - protected PDFColorSpace colorSpace; + protected PDFDeviceColorSpace colorSpace; /** * Get the PDF string for setting the path paint. diff --git a/src/java/org/apache/fop/pdf/PDFProfile.java b/src/java/org/apache/fop/pdf/PDFProfile.java new file mode 100644 index 000000000..30319d4d3 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFProfile.java @@ -0,0 +1,244 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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.pdf; + +import java.text.MessageFormat; + +/** + * This class allows tracks the enabled PDF profiles (PDF/A and PDF/X) and provides methods to + * the libarary and its users to enable the generation of PDFs conforming to the enabled PDF + * profiles. + *

+ * Some profile from PDF/X and PDF/A can be active simultaneously (example: PDF/A-1 and + * PDF/X-3:2003). + */ +public class PDFProfile { + + /** + * Indicates the PDF/A mode currently active. Defaults to "no restrictions", i.e. + * PDF/A not active. + */ + protected PDFAMode pdfAMode = PDFAMode.DISABLED; + + /** + * Indicates the PDF/X mode currently active. Defaults to "no restrictions", i.e. + * PDF/X not active. + */ + protected PDFXMode pdfXMode = PDFXMode.DISABLED; + + private PDFDocument doc; + + /** + * Main constructor + * @param doc the PDF document + */ + public PDFProfile(PDFDocument doc) { + this.doc = doc; + } + + /** + * Validates if the requested profile combination is compatible. + */ + protected void validateProfileCombination() { + if (pdfAMode != PDFAMode.DISABLED) { + if (pdfAMode == PDFAMode.PDFA_1A) { + throw new UnsupportedOperationException("PDF/A-1a is not implemented, yet"); + } + if (pdfAMode == PDFAMode.PDFA_1B) { + if (pdfXMode != PDFXMode.DISABLED && pdfXMode != PDFXMode.PDFX_3_2003) { + throw new PDFConformanceException( + pdfAMode + " and " + pdfXMode + " are not compatible!"); + } + } + } + } + + /** @return the PDFDocument this profile is attached to */ + public PDFDocument getDocument() { + return this.doc; + } + + /** @return the PDF/A mode */ + public PDFAMode getPDFAMode() { + return this.pdfAMode; + } + + /** @return true if any PDF/A mode is active */ + public boolean isPDFAActive() { + return getPDFAMode() != PDFAMode.DISABLED; + } + + /** + * Sets the PDF/A mode + * @param mode the PDF/A mode + */ + public void setPDFAMode(PDFAMode mode) { + if (mode == null) { + mode = PDFAMode.DISABLED; + } + this.pdfAMode = mode; + validateProfileCombination(); + } + + /** @return the PDF/X mode */ + public PDFXMode getPDFXMode() { + return this.pdfXMode; + } + + /** @return true if any PDF/X mode is active */ + public boolean isPDFXActive() { + return getPDFXMode() != PDFXMode.DISABLED; + } + + /** + * Sets the PDF/X mode + * @param mode the PDF/X mode + */ + public void setPDFXMode(PDFXMode mode) { + if (mode == null) { + mode = PDFXMode.DISABLED; + } + this.pdfXMode = mode; + validateProfileCombination(); + } + + /** @see java.lang.Object#toString() */ + public String toString() { + StringBuffer sb = new StringBuffer(); + if (isPDFAActive() && isPDFXActive()) { + sb.append("[").append(getPDFAMode()).append(",").append(getPDFXMode()).append("]"); + } else if (isPDFAActive()) { + sb.append(getPDFAMode()); + } else if (isPDFXActive()) { + sb.append(getPDFXMode()); + } else { + sb.append(super.toString()); + } + return sb.toString(); + } + + //---------=== Info and validation methods ===--------- + + private String format(String pattern, Object arg) { + return MessageFormat.format(pattern, new Object[] {arg}); + } + + /** Checks if encryption is allowed. */ + public void verifyEncryptionAllowed() { + final String err = "{0} doesn't allow encrypted PDFs"; + if (isPDFAActive()) { + throw new PDFConformanceException(format(err, getPDFAMode())); + } + if (isPDFXActive()) { + throw new PDFConformanceException(format(err, getPDFXMode())); + } + } + + /** Checks if PostScript XObjects are allowed. */ + public void verifyPSXObjectsAllowed() { + final String err = "PostScript XObjects are prohibited when {0}" + + " is active. Convert EPS graphics to another format."; + if (isPDFAActive()) { + throw new PDFConformanceException(format(err, getPDFAMode())); + } + if (isPDFXActive()) { + throw new PDFConformanceException(format(err, getPDFXMode())); + } + } + + /** + * Checks if the use of transparency is allowed. + * @param context Context information for the user to identify the problem spot + */ + public void verifyTransparencyAllowed(String context) { + final String err = "{0} does not allow the use of transparency. ({1})"; + if (isPDFAActive()) { + throw new PDFConformanceException(MessageFormat.format(err, + new Object[] {getPDFAMode(), context})); + } + if (isPDFXActive()) { + throw new PDFConformanceException(MessageFormat.format(err, + new Object[] {getPDFXMode(), context})); + } + } + + /** Checks if the right PDF version is set. */ + public void verifyPDFVersion() { + final String err = "PDF version must be 1.4 for {0}"; + if (getPDFAMode().isPDFA1LevelB() + && getDocument().getPDFVersion() != PDFDocument.PDF_VERSION_1_4) { + throw new PDFConformanceException(format(err, getPDFAMode())); + } + if (getPDFXMode() == PDFXMode.PDFX_3_2003 + && getDocument().getPDFVersion() != PDFDocument.PDF_VERSION_1_4) { + throw new PDFConformanceException(format(err, getPDFXMode())); + } + } + + /** @return true if the ID entry must be present in the trailer. */ + public boolean isIDEntryRequired() { + return isPDFAActive() || isPDFXActive(); + } + + /** @return true if all fonts need to be embedded. */ + public boolean isFontEmbeddingRequired() { + return isPDFAActive() || isPDFXActive(); + } + + /** Checks if a title may be absent. */ + public void verifyTitleAbsent() { + if (isPDFXActive()) { + final String err = "{0} requires the title to be set."; + throw new PDFConformanceException(format(err, getPDFXMode())); + } + } + + /** @return true if the ModDate Info entry must be present. */ + public boolean isModDateRequired() { + return getPDFXMode() == PDFXMode.PDFX_3_2003; + } + + /** @return true if the Trapped Info entry must be present. */ + public boolean isTrappedEntryRequired() { + return getPDFXMode() == PDFXMode.PDFX_3_2003; + } + + /** @return true if annotations are allowed */ + public boolean isAnnotationAllowed() { + return !isPDFXActive(); + } + + /** Checks if annotations are allowed. */ + public void verifyAnnotAllowed() { + if (!isAnnotationAllowed()) { + final String err = "{0} does not allow annotations inside the printable area."; + //Note: this rule is simplified. Refer to the standard for details. + throw new PDFConformanceException(format(err, getPDFXMode())); + } + } + + /** Checks if Actions are allowed. */ + public void verifyActionAllowed() { + if (isPDFXActive()) { + final String err = "{0} does not allow Actions."; + throw new PDFConformanceException(format(err, getPDFXMode())); + } + } + +} diff --git a/src/java/org/apache/fop/pdf/PDFResources.java b/src/java/org/apache/fop/pdf/PDFResources.java index 0945634dd..80147b0d7 100644 --- a/src/java/org/apache/fop/pdf/PDFResources.java +++ b/src/java/org/apache/fop/pdf/PDFResources.java @@ -21,6 +21,7 @@ package org.apache.fop.pdf; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.Typeface; import org.apache.fop.fonts.FontDescriptor; +import org.apache.fop.util.ColorProfileUtil; // Java import java.util.Iterator; @@ -62,6 +63,12 @@ public class PDFResources extends PDFObject { */ protected Set gstates = new HashSet(); + /** Map of color spaces (key: color space name) */ + protected Map colorSpaces = new HashMap(); + + /** Map of ICC color spaces (key: ICC profile description) */ + protected Map iccColorSpaces = new HashMap(); + /** * create a /Resources object. * @@ -139,6 +146,37 @@ public class PDFResources extends PDFObject { this.xObjects.add(xObject); } + /** + * Add a ColorSpace dictionary to the resources. + * @param colorSpace the color space + */ + public void addColorSpace(PDFICCBasedColorSpace colorSpace) { + this.colorSpaces.put(colorSpace.getName(), colorSpace); + String desc = ColorProfileUtil.getICCProfileDescription( + colorSpace.getICCStream().getICCProfile()); + this.iccColorSpaces.put(desc, colorSpace); + } + + /** + * Returns a ICCBased color space by profile name. + * @param desc the name of the color space + * @return the requested color space or null if it wasn't found + */ + public PDFICCBasedColorSpace getICCColorSpaceByProfileName(String desc) { + PDFICCBasedColorSpace cs = (PDFICCBasedColorSpace)this.iccColorSpaces.get(desc); + return cs; + } + + /** + * Returns a color space by name. + * @param name the name of the color space + * @return the requested color space or null if it wasn't found + */ + public PDFICCBasedColorSpace getColorSpace(String name) { + PDFICCBasedColorSpace cs = (PDFICCBasedColorSpace)this.colorSpaces.get(name); + return cs; + } + /** * represent the object in PDF * This adds the references to all the objects in the current @@ -219,6 +257,17 @@ public class PDFResources extends PDFObject { p = p.append(">>\n"); } + if (!this.colorSpaces.isEmpty()) { + p = p.append("/ColorSpace <<\n"); + for (Iterator iter = colorSpaces.values().iterator(); iter.hasNext();) { + PDFICCBasedColorSpace colorSpace = (PDFICCBasedColorSpace)iter.next(); + p = p.append(" /" + colorSpace.getName() + " " + + colorSpace.referencePDF() + + "\n"); + } + p = p.append(">>\n"); + } + p = p.append(">>\nendobj\n"); return p.toString(); diff --git a/src/java/org/apache/fop/pdf/PDFShading.java b/src/java/org/apache/fop/pdf/PDFShading.java index f6815222a..62b4cc438 100644 --- a/src/java/org/apache/fop/pdf/PDFShading.java +++ b/src/java/org/apache/fop/pdf/PDFShading.java @@ -47,7 +47,7 @@ public class PDFShading extends PDFObject { /** * A ColorSpace representing the colorspace. "DeviceRGB" is an example. */ - protected PDFColorSpace colorSpace = null; + protected PDFDeviceColorSpace colorSpace = null; /** * The background color. Since shading is opaque, @@ -163,7 +163,7 @@ public class PDFShading extends PDFObject { * It's optional, the default is the identity matrix * @param theFunction The PDF Function that maps an (x,y) location to a color */ - public PDFShading(int theShadingType, PDFColorSpace theColorSpace, + public PDFShading(int theShadingType, PDFDeviceColorSpace theColorSpace, List theBackground, List theBBox, boolean theAntiAlias, List theDomain, List theMatrix, PDFFunction theFunction) { @@ -201,7 +201,7 @@ public class PDFShading extends PDFObject { * and end colors past the start and end points * The default is [false, false] */ - public PDFShading(int theShadingType, PDFColorSpace theColorSpace, + public PDFShading(int theShadingType, PDFDeviceColorSpace theColorSpace, List theBackground, List theBBox, boolean theAntiAlias, List theCoords, List theDomain, PDFFunction theFunction, @@ -241,7 +241,7 @@ public class PDFShading extends PDFObject { * @param theDecode List of Doubles see PDF 1.3 spec pages 303 to 312. * @param theFunction the PDFFunction */ - public PDFShading(int theShadingType, PDFColorSpace theColorSpace, + public PDFShading(int theShadingType, PDFDeviceColorSpace theColorSpace, List theBackground, List theBBox, boolean theAntiAlias, int theBitsPerCoordinate, int theBitsPerComponent, int theBitsPerFlag, @@ -280,7 +280,7 @@ public class PDFShading extends PDFObject { * @param theVerticesPerRow number of vertices in each "row" of the lattice. * @param theFunction The PDFFunction that's mapped on to this shape */ - public PDFShading(int theShadingType, PDFColorSpace theColorSpace, + public PDFShading(int theShadingType, PDFDeviceColorSpace theColorSpace, List theBackground, List theBBox, boolean theAntiAlias, int theBitsPerCoordinate, int theBitsPerComponent, List theDecode, @@ -341,7 +341,7 @@ public class PDFShading extends PDFObject { + "<< \n/ShadingType " + this.shadingType + " \n"); if (this.colorSpace != null) { p.append("/ColorSpace /" - + this.colorSpace.getColorSpacePDFString() + " \n"); + + this.colorSpace.getName() + " \n"); } if (this.background != null) { diff --git a/src/java/org/apache/fop/pdf/PDFXMode.java b/src/java/org/apache/fop/pdf/PDFXMode.java new file mode 100644 index 000000000..bfaa4aa39 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFXMode.java @@ -0,0 +1,62 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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.pdf; + +/** Enum class for PDF/X modes. */ +public final class PDFXMode { + + /** PDF/X disabled */ + public static final PDFXMode DISABLED = new PDFXMode("PDF/X disabled"); + /** PDF/X-3:2003 enabled */ + public static final PDFXMode PDFX_3_2003 = new PDFXMode("PDF/X-3:2003"); + + private String name; + + /** + * Constructor to add a new named item. + * @param name Name of the item. + */ + private PDFXMode(String name) { + this.name = name; + } + + /** @return the name of the enum */ + public String getName() { + return this.name; + } + + /** + * Returns the mode enum object given a String. + * @param s the string + * @return the PDFAMode enum object (DISABLED will be returned if no match is found) + */ + public static PDFXMode valueOf(String s) { + if (PDFX_3_2003.getName().equalsIgnoreCase(s)) { + return PDFX_3_2003; + } else { + return DISABLED; + } + } + + /** @see java.lang.Object#toString() */ + public String toString() { + return name; + } + +} diff --git a/src/java/org/apache/fop/pdf/PDFXObject.java b/src/java/org/apache/fop/pdf/PDFXObject.java index 058628b79..66c6fecc5 100644 --- a/src/java/org/apache/fop/pdf/PDFXObject.java +++ b/src/java/org/apache/fop/pdf/PDFXObject.java @@ -94,10 +94,7 @@ public class PDFXObject extends AbstractPDFStream { private String buildDictionaryFromPS(String lengthEntry, String dictEntries) { - if (getDocumentSafely().getPDFAMode().isPDFA1LevelB()) { - throw new PDFConformanceException("PostScript XObjects are prohibited when PDF/A" - + " is active. Convert EPS graphics to another format."); - } + getDocumentSafely().getProfile().verifyPSXObjectsAllowed(); StringBuffer sb = new StringBuffer(128); sb.append(getObjectID()); sb.append("< + + Added support for PDF/A-1b and PDF/X-3:2003. (Note: this may still be a bit + incomplete. Feedback is welcome!) + + + The CIDSet object is now generated for CID fonts which are embedded in PDFs. + (Required by PDF/A). + The Java2DRenderer (and therefore the print and bitmap renderers) is now offering the same quality as the PDF and PS renderers. Note: There can still -- 2.39.5