diff options
author | Jeremias Maerki <jeremias@apache.org> | 2008-07-22 15:48:39 +0000 |
---|---|---|
committer | Jeremias Maerki <jeremias@apache.org> | 2008-07-22 15:48:39 +0000 |
commit | 8091bd11210ac68aa3289532643e42b66545a495 (patch) | |
tree | 0c500f554120284fef9debb77c463d738804ff3b /src/java/org/apache/fop/render | |
parent | cd64af07c5661e82a7121a2ac6ad756a3b2e91e9 (diff) | |
download | xmlgraphics-fop-8091bd11210ac68aa3289532643e42b66545a495.tar.gz xmlgraphics-fop-8091bd11210ac68aa3289532643e42b66545a495.zip |
Started the IFParser.
Started a PDF painter.
Factored out common code to PDFRenderingUtil.
Smaller infrastructure changes for the new IF (like MIME type reporting).
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_AreaTreeNewDesign@678780 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org/apache/fop/render')
17 files changed, 2217 insertions, 496 deletions
diff --git a/src/java/org/apache/fop/render/AbstractRendererConfigurator.java b/src/java/org/apache/fop/render/AbstractRendererConfigurator.java index 982b23f05..09540dfbb 100644 --- a/src/java/org/apache/fop/render/AbstractRendererConfigurator.java +++ b/src/java/org/apache/fop/render/AbstractRendererConfigurator.java @@ -5,9 +5,9 @@ * 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. @@ -23,6 +23,7 @@ import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.apache.fop.apps.FOUserAgent; /** @@ -68,7 +69,7 @@ public abstract class AbstractRendererConfigurator { * @param mimeType the MIME type of the renderer * @return the requested configuration subtree, null if there's no configuration */ - private Configuration getRendererConfig(String mimeType) { + protected Configuration getRendererConfig(String mimeType) { Configuration cfg = userAgent.getFactory().getUserConfig(); if (cfg == null) { if (log.isDebugEnabled()) { diff --git a/src/java/org/apache/fop/render/RendererFactory.java b/src/java/org/apache/fop/render/RendererFactory.java index 2706a723e..3bf4881b9 100644 --- a/src/java/org/apache/fop/render/RendererFactory.java +++ b/src/java/org/apache/fop/render/RendererFactory.java @@ -36,6 +36,7 @@ import org.apache.fop.area.AreaTreeHandler; import org.apache.fop.fo.FOEventHandler; import org.apache.fop.render.intermediate.AbstractIFPainterMaker; import org.apache.fop.render.intermediate.IFPainter; +import org.apache.fop.render.intermediate.IFPainterConfigurator; import org.apache.fop.render.intermediate.IFRenderer; /** @@ -247,7 +248,7 @@ public class RendererFactory { if (painterMaker != null) { IFRenderer rend = new IFRenderer(); rend.setUserAgent(userAgent); - IFPainter painter = painterMaker.makePainter(userAgent); + IFPainter painter = createPainter(userAgent, outputFormat); rend.setPainter(painter); return rend; } else { @@ -281,7 +282,9 @@ public class RendererFactory { boolean outputStreamMissing = (userAgent.getRendererOverride() == null); if (rendMaker == null) { painterMaker = getPainterMaker(outputFormat); - outputStreamMissing &= (out == null) && (painterMaker.needsOutputStream()); + if (painterMaker != null) { + outputStreamMissing &= (out == null) && (painterMaker.needsOutputStream()); + } } else { outputStreamMissing &= (out == null) && (rendMaker.needsOutputStream()); } @@ -325,12 +328,10 @@ public class RendererFactory { } IFPainter painter = maker.makePainter(userAgent); painter.setUserAgent(userAgent); - //TODO Add configuration - /* - RendererConfigurator configurator = maker.getConfigurator(userAgent); + IFPainterConfigurator configurator = maker.getConfigurator(userAgent); if (configurator != null) { configurator.configure(painter); - }*/ + } return painter; //} } diff --git a/src/java/org/apache/fop/render/intermediate/AbstractBinaryWritingIFPainter.java b/src/java/org/apache/fop/render/intermediate/AbstractBinaryWritingIFPainter.java new file mode 100644 index 000000000..860de7946 --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/AbstractBinaryWritingIFPainter.java @@ -0,0 +1,167 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URL; +import java.util.List; + +import javax.xml.transform.Result; +import javax.xml.transform.stream.StreamResult; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; + +import org.apache.fop.fonts.CustomFontCollection; +import org.apache.fop.fonts.FontCollection; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.fonts.FontManager; +import org.apache.fop.fonts.FontResolver; +import org.apache.fop.fonts.base14.Base14FontCollection; +import org.apache.fop.render.DefaultFontResolver; + +/** + * Abstract base class for binary-writing IFPainter implementations. + */ +public abstract class AbstractBinaryWritingIFPainter extends AbstractIFPainter { + + /** The output stream to write the document to */ + protected OutputStream outputStream; + + private boolean ownOutputStream; + + /** Font configuration */ + protected FontInfo fontInfo; + + /** Font resolver */ + protected FontResolver fontResolver = null; + + /** list of fonts */ + protected List/*<EmbedFontInfo>*/ embedFontInfoList = null; + + /** {@inheritDoc} */ + public void setResult(Result result) throws IFException { + if (result instanceof StreamResult) { + StreamResult streamResult = (StreamResult)result; + OutputStream out = streamResult.getOutputStream(); + if (out == null) { + if (streamResult.getWriter() != null) { + throw new IllegalArgumentException( + "FOP cannot use a Writer. Please supply an OutputStream!"); + } + try { + URL url = new URL(streamResult.getSystemId()); + File f = FileUtils.toFile(url); + if (f != null) { + out = new java.io.FileOutputStream(f); + } else { + out = url.openConnection().getOutputStream(); + } + } catch (IOException ioe) { + throw new IFException("I/O error while opening output stream" , ioe); + } + out = new java.io.BufferedOutputStream(out); + this.ownOutputStream = true; + } + if (out == null) { + throw new IllegalArgumentException("Need a StreamResult with an OutputStream"); + } + this.outputStream = out; + } else { + throw new UnsupportedOperationException( + "Unsupported Result subclass: " + result.getClass().getName()); + } + } + + /** + * Adds a font list to current list of fonts + * @param fontList a font info list + */ + public void addFontList(List/*<EmbedFontInfo>*/ fontList) { + if (embedFontInfoList == null) { + setFontList(fontList); + } else { + embedFontInfoList.addAll(fontList); + } + } + + /** + * @param embedFontInfoList list of available fonts + */ + public void setFontList(List/*<EmbedFontInfo>*/ embedFontInfoList) { + this.embedFontInfoList = embedFontInfoList; + } + + /** + * @return list of available embedded fonts + */ + public List/*<EmbedFontInfo>*/ getFontList() { + return this.embedFontInfoList; + } + + /** + * Returns the {@code FontResolver} used by this painter. + * @return the font resolver + */ + public FontResolver getFontResolver() { + if (this.fontResolver == null) { + this.fontResolver = new DefaultFontResolver(getUserAgent()); + } + return this.fontResolver; + } + + /** + * Returns the {@code FontInfo} object. + * @return the font info + */ + public FontInfo getFontInfo() { + return this.fontInfo; + } + + public void setFontInfo(FontInfo fontInfo) { + this.fontInfo = fontInfo; + } + + /** + * Set up the font info + * + * @param inFontInfo font info to set up + */ + public void setupFontInfo(FontInfo inFontInfo) { + setFontInfo(inFontInfo); + FontManager fontManager = getUserAgent().getFactory().getFontManager(); + FontCollection[] fontCollections = new FontCollection[] { + new Base14FontCollection(fontManager.isBase14KerningEnabled()), + new CustomFontCollection(getFontResolver(), getFontList()) + }; + fontManager.setup(getFontInfo(), fontCollections); + } + + /** {@inheritDoc} */ + public void endDocument() throws IFException { + if (this.ownOutputStream) { + IOUtils.closeQuietly(this.outputStream); + this.outputStream = null; + } + } + +} diff --git a/src/java/org/apache/fop/render/intermediate/AbstractIFPainterMaker.java b/src/java/org/apache/fop/render/intermediate/AbstractIFPainterMaker.java index 273f90170..aa653cd3e 100644 --- a/src/java/org/apache/fop/render/intermediate/AbstractIFPainterMaker.java +++ b/src/java/org/apache/fop/render/intermediate/AbstractIFPainterMaker.java @@ -45,15 +45,12 @@ public abstract class AbstractIFPainterMaker { public abstract String[] getSupportedMimeTypes(); /** - * Returns a renderer config object that can be used to + * Returns a configurator object that can be used to * configure the painter. - * @param userAgent user agent - * @return a config object that can be used to configure the painter + * @param userAgent the user agent + * @return a configurator object that can be used to configure the painter */ - /* - public RendererConfigurator getConfigurator(FOUserAgent userAgent) { - return null; - }*/ + public abstract IFPainterConfigurator getConfigurator(FOUserAgent userAgent); /** * Indicates whether a specific MIME type is supported by this painter. diff --git a/src/java/org/apache/fop/render/intermediate/AbstractXMLWritingIFPainter.java b/src/java/org/apache/fop/render/intermediate/AbstractXMLWritingIFPainter.java index 166b6df20..bb226dd0c 100644 --- a/src/java/org/apache/fop/render/intermediate/AbstractXMLWritingIFPainter.java +++ b/src/java/org/apache/fop/render/intermediate/AbstractXMLWritingIFPainter.java @@ -57,11 +57,6 @@ public abstract class AbstractXMLWritingIFPainter extends AbstractIFPainter { protected ContentHandler handler; /** {@inheritDoc} */ - public ContentHandler getContentHandler() { - return this.handler; - } - - /** {@inheritDoc} */ public void setResult(Result result) throws IFException { if (result instanceof SAXResult) { SAXResult saxResult = (SAXResult)result; diff --git a/src/java/org/apache/fop/render/intermediate/AffineTransformArrayParser.java b/src/java/org/apache/fop/render/intermediate/AffineTransformArrayParser.java new file mode 100644 index 000000000..36a783427 --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/AffineTransformArrayParser.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import java.awt.geom.AffineTransform; +import java.io.Reader; +import java.util.List; + +import org.apache.batik.parser.ParseException; +import org.apache.batik.parser.TransformListHandler; +import org.apache.batik.parser.TransformListParser; + +/** + * This class parses a sequence of transformations into an array of {@code AffineTransform} + * instances. + */ +public class AffineTransformArrayParser implements TransformListHandler { + + private List transforms; + + /** + * Utility method for creating an AffineTransform array. + * @param r The reader used to read the transform specification. + * @return the AffineTransform array + * @throws ParseException if there's a parse error + */ + public static AffineTransform[] createAffineTransform(Reader r) + throws ParseException { + TransformListParser p = new TransformListParser(); + AffineTransformArrayParser th = new AffineTransformArrayParser(); + + p.setTransformListHandler(th); + p.parse(r); + + return th.getAffineTransforms(); + } + + /** + * Utility method for creating an AffineTransform. + * @param s The transform specification. + * @return the AffineTransform array + * @throws ParseException if there's a parse error + */ + public static AffineTransform[] createAffineTransform(String s) + throws ParseException { + TransformListParser p = new TransformListParser(); + AffineTransformArrayParser th = new AffineTransformArrayParser(); + + p.setTransformListHandler(th); + p.parse(s); + + return th.getAffineTransforms(); + } + + /** + * Returns the AffineTransform array initialized during the last parsing. + * @return the array or null if this handler has not been used by + * a parser. + */ + public AffineTransform[] getAffineTransforms() { + if (this.transforms == null) { + return null; + } else { + int count = this.transforms.size(); + return (AffineTransform[])this.transforms.toArray(new AffineTransform[count]); + } + } + + /** {@inheritDoc} */ + public void startTransformList() throws ParseException { + this.transforms = new java.util.ArrayList(); + } + + /** {@inheritDoc} */ + public void matrix(float a, float b, float c, float d, float e, float f) + throws ParseException { + this.transforms.add(new AffineTransform(a, b, c, d, e, f)); + } + + /** {@inheritDoc} */ + public void rotate(float theta) throws ParseException { + this.transforms.add(AffineTransform.getRotateInstance(Math.toRadians(theta))); + } + + /** {@inheritDoc} */ + public void rotate(float theta, float cx, float cy) throws ParseException { + AffineTransform at + = AffineTransform.getRotateInstance(Math.toRadians(theta), cx, cy); + this.transforms.add(at); + } + + /** {@inheritDoc} */ + public void translate(float tx) throws ParseException { + AffineTransform at = AffineTransform.getTranslateInstance(tx, 0); + this.transforms.add(at); + } + + /** {@inheritDoc} */ + public void translate(float tx, float ty) throws ParseException { + AffineTransform at = AffineTransform.getTranslateInstance(tx, ty); + this.transforms.add(at); + } + + /** {@inheritDoc} */ + public void scale(float sx) throws ParseException { + this.transforms.add(AffineTransform.getScaleInstance(sx, sx)); + } + + /** {@inheritDoc} */ + public void scale(float sx, float sy) throws ParseException { + this.transforms.add(AffineTransform.getScaleInstance(sx, sy)); + } + + /** {@inheritDoc} */ + public void skewX(float skx) throws ParseException { + this.transforms.add + (AffineTransform.getShearInstance(Math.tan(Math.toRadians(skx)), 0)); + } + + /** {@inheritDoc} */ + public void skewY(float sky) throws ParseException { + this.transforms.add + (AffineTransform.getShearInstance(0, Math.tan(Math.toRadians(sky)))); + } + + /** {@inheritDoc} */ + public void endTransformList() throws ParseException { + } +} diff --git a/src/java/org/apache/fop/render/intermediate/IFPainter.java b/src/java/org/apache/fop/render/intermediate/IFPainter.java index 83045b6bc..44e02fe68 100644 --- a/src/java/org/apache/fop/render/intermediate/IFPainter.java +++ b/src/java/org/apache/fop/render/intermediate/IFPainter.java @@ -97,6 +97,12 @@ public interface IFPainter { boolean supportsPagesOutOfOrder(); /** + * Returns the MIME type of the output format that is generated by this implementation. + * @return the MIME type + */ + String getMimeType(); + + /** * Indicates the start of a document. This method may only be called once before any other * event method. * @throws IFException if an error occurs while handling this event @@ -141,7 +147,7 @@ public interface IFPainter { /** * Indicates the start of a new page. - * @param index the index of the page within the document (0-based) + * @param index the index of the page (0-based) * @param name the page name (usually the formatted page number) * @param size the size of the page (equivalent to the MediaBox in PDF) * @throws IFException if an error occurs while handling this event diff --git a/src/java/org/apache/fop/render/intermediate/IFPainterConfigurator.java b/src/java/org/apache/fop/render/intermediate/IFPainterConfigurator.java new file mode 100644 index 000000000..e42f52665 --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/IFPainterConfigurator.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import org.apache.fop.apps.FOPException; + +/** + * This interface is implemented by classes that configure an {@code IFPainter} instance. + */ +public interface IFPainterConfigurator { + + /** + * Configures a painter. + * @param painter the painter instance + * @throws FOPException if an error occurs while configuring the object + */ + void configure(IFPainter painter) throws FOPException; +} diff --git a/src/java/org/apache/fop/render/intermediate/IFParser.java b/src/java/org/apache/fop/render/intermediate/IFParser.java new file mode 100644 index 000000000..ba0a6c60b --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/IFParser.java @@ -0,0 +1,552 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.util.Map; + +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TransformerHandler; + +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.fop.apps.FOUserAgent; +import org.apache.fop.fo.ElementMappingRegistry; +import org.apache.fop.fo.expr.PropertyException; +import org.apache.fop.fo.extensions.ExtensionAttachment; +import org.apache.fop.util.ColorUtil; +import org.apache.fop.util.ContentHandlerFactory; +import org.apache.fop.util.ContentHandlerFactoryRegistry; +import org.apache.fop.util.ConversionUtils; +import org.apache.fop.util.DefaultErrorListener; + +/** + * This is a parser for the intermediate format XML which converts the intermediate file into + * {@code IFPainter} events. + */ +public class IFParser implements IFConstants { + + /** Logger instance */ + protected static Log log = LogFactory.getLog(IFParser.class); + + private static SAXTransformerFactory tFactory + = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); + + /** + * Parses an intermediate file and paints it. + * @param src the Source instance pointing to the intermediate file + * @param painter the intermediate format painter used to process the IF events + * @param userAgent the user agent + * @throws TransformerException if an error occurs while parsing the area tree XML + */ + public void parse(Source src, IFPainter painter, FOUserAgent userAgent) + throws TransformerException { + Transformer transformer = tFactory.newTransformer(); + transformer.setErrorListener(new DefaultErrorListener(log)); + + SAXResult res = new SAXResult(getContentHandler(painter, userAgent)); + + transformer.transform(src, res); + } + + /** + * Creates a new ContentHandler instance that you can send the area tree XML to. The parsed + * pages are added to the AreaTreeModel instance you pass in as a parameter. + * @param painter the intermediate format painter used to process the IF events + * @param userAgent the user agent + * @return the ContentHandler instance to receive the SAX stream from the area tree XML + */ + public ContentHandler getContentHandler(IFPainter painter, FOUserAgent userAgent) { + ElementMappingRegistry elementMappingRegistry + = userAgent.getFactory().getElementMappingRegistry(); + return new Handler(painter, userAgent, elementMappingRegistry); + } + + private static class Handler extends DefaultHandler { + + private Map elementHandlers = new java.util.HashMap(); + + private IFPainter painter; + private FOUserAgent userAgent; + private ElementMappingRegistry elementMappingRegistry; + + private Attributes lastAttributes; + + private StringBuffer content = new StringBuffer(); + private boolean ignoreCharacters = true; + + //private Stack delegateStack = new Stack(); + private int delegateDepth; + private ContentHandler delegate; + private DOMImplementation domImplementation; + + + public Handler(IFPainter painter, FOUserAgent userAgent, + ElementMappingRegistry elementMappingRegistry) { + this.painter = painter; + this.userAgent = userAgent; + this.elementMappingRegistry = elementMappingRegistry; + elementHandlers.put("document", new DocumentHandler()); + elementHandlers.put("header", new DocumentHeaderHandler()); + elementHandlers.put("page-sequence", new PageSequenceHandler()); + elementHandlers.put("page", new PageHandler()); + elementHandlers.put("page-header", new PageHeaderHandler()); + elementHandlers.put("content", new PageContentHandler()); + elementHandlers.put("page-trailer", new PageTrailerHandler()); + //Page content + elementHandlers.put("box", new BoxHandler()); + elementHandlers.put("font", new FontHandler()); + elementHandlers.put("text", new TextHandler()); + elementHandlers.put("rect", new RectHandler()); + } + + + /** {@inheritDoc} */ + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + if (delegate != null) { + //delegateStack.push(qName); + delegateDepth++; + delegate.startElement(uri, localName, qName, attributes); + } else if (domImplementation != null) { + //domImplementation is set so we need to start a new DOM building sub-process + TransformerHandler handler; + try { + handler = tFactory.newTransformerHandler(); + } catch (TransformerConfigurationException e) { + throw new SAXException("Error creating a new TransformerHandler", e); + } + Document doc = domImplementation.createDocument(uri, qName, null); + //It's easier to work with an empty document, so remove the root element + doc.removeChild(doc.getDocumentElement()); + handler.setResult(new DOMResult(doc)); + //Area parent = (Area)areaStack.peek(); + //((ForeignObject)parent).setDocument(doc); + + //activate delegate for nested foreign document + domImplementation = null; //Not needed anymore now + this.delegate = handler; + //delegateStack.push(qName); + delegateDepth++; + delegate.startDocument(); + delegate.startElement(uri, localName, qName, attributes); + } else { + lastAttributes = attributes; + boolean handled = true; + if (NAMESPACE.equals(uri)) { + ElementHandler elementHandler = (ElementHandler)elementHandlers.get(localName); + content.setLength(0); + ignoreCharacters = true; + if (elementHandler != null) { + ignoreCharacters = elementHandler.ignoreCharacters(); + try { + elementHandler.startElement(attributes); + } catch (IFException ife) { + handleIFException(ife); + } + } else if ("extension-attachments".equals(localName)) { + //TODO implement me + } else { + handled = false; + } + } else { + ContentHandlerFactoryRegistry registry + = userAgent.getFactory().getContentHandlerFactoryRegistry(); + ContentHandlerFactory factory = registry.getFactory(uri); + if (factory != null) { + delegate = factory.createContentHandler(); + //delegateStack.push(qName); + delegateDepth++; + delegate.startDocument(); + delegate.startElement(uri, localName, qName, attributes); + } else { + handled = false; + } + } + if (!handled) { + if (uri == null || uri.length() == 0) { + throw new SAXException("Unhandled element " + localName + + " in namespace: " + uri); + } else { + log.warn("Unhandled element " + localName + + " in namespace: " + uri); + } + } + } + } + + private void handleIFException(IFException ife) throws SAXException { + if (ife.getCause() instanceof SAXException) { + //unwrap + throw (SAXException)ife.getCause(); + } else { + //wrap + throw new SAXException(ife); + } + } + + + /** {@inheritDoc} */ + public void endElement(String uri, String localName, String qName) throws SAXException { + if (delegate != null) { + delegate.endElement(uri, localName, qName); + //delegateStack.pop(); + delegateDepth--; + if (delegateDepth == 0) { + delegate.endDocument(); + if (delegate instanceof ContentHandlerFactory.ObjectSource) { + Object obj = ((ContentHandlerFactory.ObjectSource)delegate).getObject(); + handleExternallyGeneratedObject(obj); + } + delegate = null; //Sub-document is processed, return to normal processing + } + } else { + if (NAMESPACE.equals(uri)) { + ElementHandler elementHandler = (ElementHandler)elementHandlers.get(localName); + if (elementHandler != null) { + try { + elementHandler.endElement(); + } catch (IFException ife) { + handleIFException(ife); + } + content.setLength(0); + } + ignoreCharacters = true; + } else { + //log.debug("Ignoring " + localName + " in namespace: " + uri); + } + } + } + + // ============== Element handlers for the intermediate format ============= + + private static interface ElementHandler { + void startElement(Attributes attributes) throws IFException, SAXException; + void endElement() throws IFException; + boolean ignoreCharacters(); + } + + private abstract class AbstractElementHandler implements ElementHandler { + + public void startElement(Attributes attributes) throws IFException, SAXException { + //nop + } + + public void endElement() throws IFException { + //nop + } + + public boolean ignoreCharacters() { + return true; + } + } + + private class DocumentHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + painter.startDocument(); + } + + public void endElement() throws IFException { + painter.endDocument(); + } + + } + + private class DocumentHeaderHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + painter.startDocumentHeader(); + } + + public void endElement() throws IFException { + painter.endDocumentHeader(); + } + + } + + private class PageSequenceHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + String id = attributes.getValue("id"); + painter.startPageSequence(id); + } + + public void endElement() throws IFException { + painter.endPageSequence(); + } + + } + + private class PageHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + int index = Integer.parseInt(attributes.getValue("index")); + String name = attributes.getValue("name"); + int width = Integer.parseInt(attributes.getValue("width")); + int height = Integer.parseInt(attributes.getValue("height")); + painter.startPage(index, name, new Dimension(width, height)); + } + + public void endElement() throws IFException { + painter.endPage(); + } + + } + + private class PageHeaderHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + painter.startPageHeader(); + } + + public void endElement() throws IFException { + painter.endPageHeader(); + } + + } + + private class PageContentHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + painter.startPageContent(); + } + + public void endElement() throws IFException { + painter.endPageContent(); + } + + } + + private class PageTrailerHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + painter.startPageTrailer(); + } + + public void endElement() throws IFException { + painter.endPageTrailer(); + } + + } + + private class BoxHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + String transform = attributes.getValue("transform"); + AffineTransform[] transforms + = AffineTransformArrayParser.createAffineTransform(transform); + //TODO Incomplete implementation + painter.startBox(transforms, null, false); + } + + public void endElement() throws IFException { + painter.endBox(); + } + + } + + private class FontHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + String family = attributes.getValue("family"); + String style = attributes.getValue("style"); + Integer weight = getAttributeAsInteger(attributes, "weight"); + String variant = attributes.getValue("variant"); + Integer size = getAttributeAsInteger(attributes, "size"); + Color color; + try { + color = getAttributeAsColor(attributes, "color"); + } catch (PropertyException pe) { + throw new IFException("Error parsing the color attribute", pe); + } + painter.setFont(family, style, weight, variant, size, color); + } + + } + + private class TextHandler extends AbstractElementHandler { + + public void endElement() throws IFException { + int x = Integer.parseInt(lastAttributes.getValue("x")); + int y = Integer.parseInt(lastAttributes.getValue("y")); + int[] dx = getAttributeAsIntArray(lastAttributes, "dx"); + int[] dy = getAttributeAsIntArray(lastAttributes, "dy"); + painter.drawText(x, y, dx, dy, content.toString()); + } + + public boolean ignoreCharacters() { + return false; + } + + } + + private class RectHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + int x = Integer.parseInt(lastAttributes.getValue("x")); + int y = Integer.parseInt(lastAttributes.getValue("y")); + int width = Integer.parseInt(lastAttributes.getValue("width")); + int height = Integer.parseInt(lastAttributes.getValue("height")); + Color fillColor; + try { + fillColor = getAttributeAsColor(attributes, "fill"); + } catch (PropertyException pe) { + throw new IFException("Error parsing the fill attribute", pe); + } + Color strokeColor; + try { + strokeColor = getAttributeAsColor(attributes, "stroke"); + } catch (PropertyException pe) { + throw new IFException("Error parsing the stroke attribute", pe); + } + painter.drawRect(new Rectangle(x, y, width, height), fillColor, strokeColor); + } + + } + + + // ==================================================================== + + + private void assertObjectOfClass(Object obj, Class clazz) { + if (!clazz.isInstance(obj)) { + throw new IllegalStateException("Object is not an instance of " + + clazz.getName() + " but of " + obj.getClass().getName()); + } + } + + /** + * Handles objects created by "sub-parsers" that implement the ObjectSource interface. + * An example of object handled here are ExtensionAttachments. + * @param obj the Object to be handled. + */ + protected void handleExternallyGeneratedObject(Object obj) { + if (obj instanceof ExtensionAttachment) { + ExtensionAttachment attachment = (ExtensionAttachment)obj; + //TODO Implement me + /* + if (this.currentPageViewport == null) { + this.treeModel.handleOffDocumentItem( + new OffDocumentExtensionAttachment(attachment)); + } else { + this.currentPageViewport.addExtensionAttachment(attachment); + } + */ + } else { + log.warn("Don't know how to handle externally generated object: " + obj); + } + } + + private 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(); + } + } + + private static int getAttributeAsInteger(Attributes attributes, String name, + int defaultValue) { + String s = attributes.getValue(name); + if (s == null) { + return defaultValue; + } else { + return Integer.parseInt(s); + } + } + + private static Integer getAttributeAsInteger(Attributes attributes, String name) { + String s = attributes.getValue(name); + if (s == null) { + return null; + } else { + return new Integer(s); + } + } + + private Color getAttributeAsColor(Attributes attributes, String name) + throws PropertyException { + String s = attributes.getValue(name); + if (s == null) { + return null; + } else { + return ColorUtil.parseColorString(userAgent, s); + } + } + + private 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]); + } + + private static Rectangle getAttributeAsRectangle(Attributes attributes, String name) { + String s = attributes.getValue(name).trim(); + int[] values = ConversionUtils.toIntArray(s, "\\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]); + } + + private static int[] getAttributeAsIntArray(Attributes attributes, String name) { + String s = attributes.getValue(name); + if (s == null) { + return null; + } else { + return ConversionUtils.toIntArray(s.trim(), "\\s"); + } + } + + /** {@inheritDoc} */ + public void characters(char[] ch, int start, int length) throws SAXException { + if (delegate != null) { + delegate.characters(ch, start, length); + } else if (!ignoreCharacters) { + this.content.append(ch, start, length); + } + } + } +} diff --git a/src/java/org/apache/fop/render/intermediate/IFRenderer.java b/src/java/org/apache/fop/render/intermediate/IFRenderer.java index 83e7b5397..c4243a86a 100644 --- a/src/java/org/apache/fop/render/intermediate/IFRenderer.java +++ b/src/java/org/apache/fop/render/intermediate/IFRenderer.java @@ -121,6 +121,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { public void setupFontInfo(FontInfo inFontInfo) { if (mimic != null) { mimic.setupFontInfo(inFontInfo); + this.fontInfo = inFontInfo; } else { super.setupFontInfo(inFontInfo); } @@ -164,6 +165,10 @@ public class IFRenderer extends AbstractPathOrientedRenderer { this.painter = new IFSerializer(); } this.painter.setUserAgent(getUserAgent()); + if (this.painter instanceof AbstractBinaryWritingIFPainter) { + //TODO THIS IS UGLY. FIX ME!!! + ((AbstractBinaryWritingIFPainter)this.painter).setFontInfo(fontInfo); + } this.painter.setResult(result); } super.startRenderer(null); diff --git a/src/java/org/apache/fop/render/intermediate/IFSerializer.java b/src/java/org/apache/fop/render/intermediate/IFSerializer.java index 41cecd1e7..8cfbcad11 100644 --- a/src/java/org/apache/fop/render/intermediate/IFSerializer.java +++ b/src/java/org/apache/fop/render/intermediate/IFSerializer.java @@ -56,6 +56,11 @@ public class IFSerializer extends AbstractXMLWritingIFPainter implements IFConst } /** {@inheritDoc} */ + public String getMimeType() { + return MIME_TYPE; + } + + /** {@inheritDoc} */ public void startDocument() throws IFException { try { handler.startDocument(); diff --git a/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java b/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java new file mode 100644 index 000000000..841dd7e01 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java @@ -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$ */ + +package org.apache.fop.render.pdf; + +/** + * Constants used for configuring PDF output. + */ +public interface PDFConfigurationConstants { + + /** PDF encryption parameter: all parameters as object, datatype: PDFEncryptionParams */ + String ENCRYPTION_PARAMS = "encryption-params"; + /** PDF encryption parameter: user password, datatype: String */ + String USER_PASSWORD = "user-password"; + /** PDF encryption parameter: owner password, datatype: String */ + String OWNER_PASSWORD = "owner-password"; + /** PDF encryption parameter: Forbids printing, datatype: Boolean or "true"/"false" */ + String NO_PRINT = "noprint"; + /** PDF encryption parameter: Forbids copying content, datatype: Boolean or "true"/"false" */ + String NO_COPY_CONTENT = "nocopy"; + /** PDF encryption parameter: Forbids editing content, datatype: Boolean or "true"/"false" */ + String NO_EDIT_CONTENT = "noedit"; + /** PDF encryption parameter: Forbids annotations, datatype: Boolean or "true"/"false" */ + String NO_ANNOTATIONS = "noannotations"; + /** Rendering Options key for the PDF/A mode. */ + String PDF_A_MODE = "pdf-a-mode"; + /** Rendering Options key for the PDF/X mode. */ + String PDF_X_MODE = "pdf-x-mode"; + /** Rendering Options key for the ICC profile for the output intent. */ + String KEY_OUTPUT_PROFILE = "output-profile"; + /** + * Rendering Options key for disabling the sRGB color space (only possible if no PDF/A or + * PDF/X profile is active). + */ + String KEY_DISABLE_SRGB_COLORSPACE = "disable-srgb-colorspace"; +} diff --git a/src/java/org/apache/fop/render/pdf/PDFPainter.java b/src/java/org/apache/fop/render/pdf/PDFPainter.java new file mode 100644 index 000000000..8d1b80c28 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFPainter.java @@ -0,0 +1,537 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Paint; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.io.IOException; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.apps.MimeConstants; +import org.apache.fop.fo.extensions.xmp.XMPMetadata; +import org.apache.fop.fonts.Font; +import org.apache.fop.fonts.FontTriplet; +import org.apache.fop.fonts.LazyFont; +import org.apache.fop.fonts.SingleByteFont; +import org.apache.fop.fonts.Typeface; +import org.apache.fop.pdf.PDFAnnotList; +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFFilterList; +import org.apache.fop.pdf.PDFNumber; +import org.apache.fop.pdf.PDFPage; +import org.apache.fop.pdf.PDFResourceContext; +import org.apache.fop.pdf.PDFResources; +import org.apache.fop.pdf.PDFState; +import org.apache.fop.pdf.PDFStream; +import org.apache.fop.pdf.PDFTextUtil; +import org.apache.fop.render.intermediate.AbstractBinaryWritingIFPainter; +import org.apache.fop.render.intermediate.IFException; +import org.apache.fop.render.intermediate.IFState; +import org.apache.fop.util.CharUtilities; +import org.apache.fop.util.ColorUtil; + +/** + * IFPainter implementation that produces PDF. + */ +public class PDFPainter extends AbstractBinaryWritingIFPainter { + + /** logging instance */ + private static Log log = LogFactory.getLog(PDFPainter.class); + + /** Holds the intermediate format state */ + protected IFState state; + + /** the PDF Document being created */ + protected PDFDocument pdfDoc; + + /** + * Utility class which enables all sorts of features that are not directly connected to the + * normal rendering process. + */ + protected PDFRenderingUtil pdfUtil; + + /** the /Resources object of the PDF document being created */ + protected PDFResources pdfResources; + + /** the current stream to add PDF commands to */ + protected PDFStream currentStream; + + /** the current annotation list to add annotations to */ + protected PDFResourceContext currentContext; + + /** + * Map of pages using the PageViewport as the key + * this is used for prepared pages that cannot be immediately + * rendered + */ + protected Map pages; + + /** the current page to add annotations to */ + protected PDFPage currentPage; + + /** the current page's PDF reference string (to avoid numerous function calls) */ + protected String currentPageRef; + + /** drawing state */ + protected PDFState currentState; + + /** Text generation utility holding the current font status */ + protected PDFTextUtil textutil; + + + /** Image handler registry */ + private PDFImageHandlerRegistry imageHandlerRegistry = new PDFImageHandlerRegistry(); + + /** + * Default constructor. + */ + public PDFPainter() { + } + + /** {@inheritDoc} */ + public boolean supportsPagesOutOfOrder() { + return true; + } + + /** {@inheritDoc} */ + public String getMimeType() { + return MimeConstants.MIME_PDF; + } + + /** {@inheritDoc} */ + public void setUserAgent(FOUserAgent ua) { + super.setUserAgent(ua); + this.pdfUtil = new PDFRenderingUtil(ua); + } + + PDFRenderingUtil getPDFUtil() { + return this.pdfUtil; + } + + /** {@inheritDoc} */ + public void startDocument() throws IFException { + try { + if (getUserAgent() == null) { + throw new IllegalStateException( + "User agent must be set before starting PDF generation"); + } + if (this.outputStream == null) { + throw new IllegalStateException("OutputStream hasn't been set through setResult()"); + } + this.pdfDoc = pdfUtil.setupPDFDocument(this.outputStream); + } catch (IOException e) { + throw new IFException("I/O error in startDocument()", e); + } + } + + /** {@inheritDoc} */ + public void startDocumentHeader() throws IFException { + } + + /** {@inheritDoc} */ + public void endDocumentHeader() throws IFException { + pdfUtil.generateDefaultXMPMetadata(); + } + + /** {@inheritDoc} */ + public void endDocument() throws IFException { + try { + //finishOpenGoTos(); + + pdfDoc.getResources().addFonts(pdfDoc, fontInfo); + pdfDoc.outputTrailer(this.outputStream); + + this.pdfDoc = null; + + this.pages = null; + + //pageReferences.clear(); + pdfResources = null; + currentStream = null; + currentContext = null; + currentPage = null; + currentState = null; + this.textutil = null; + + //idPositions.clear(); + //idGoTos.clear(); + } catch (IOException ioe) { + throw new IFException("I/O error in endDocument()", ioe); + } + super.endDocument(); + } + + /** {@inheritDoc} */ + public void startPageSequence(String id) throws IFException { + //TODO page sequence title, country and language + } + + /** {@inheritDoc} */ + public void endPageSequence() throws IFException { + //nop + } + + /** {@inheritDoc} */ + public void startPage(int index, String name, Dimension size) throws IFException { + this.pdfResources = this.pdfDoc.getResources(); + + this.currentPage = this.pdfDoc.getFactory().makePage( + this.pdfResources, + (int)Math.round(size.getWidth() / 1000), + (int)Math.round(size.getHeight() / 1000), + index); + //pageReferences.put(new Integer(index)/*page.getKey()*/, currentPage.referencePDF()); + //pvReferences.put(page.getKey(), page); + + pdfUtil.generatePageLabel(index, name); + + currentPageRef = currentPage.referencePDF(); + + currentStream = this.pdfDoc.getFactory() + .makeStream(PDFFilterList.CONTENT_FILTER, false); + this.textutil = new PDFTextUtil() { + protected void write(String code) { + currentStream.add(code); + } + }; + + currentState = new PDFState(); + // 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); + currentState.concatenate(basicPageTransform); + currentStream.add(CTMHelper.toPDFString(basicPageTransform, true) + " cm\n"); + } + + /** {@inheritDoc} */ + public void startPageHeader() throws IFException { + } + + /** {@inheritDoc} */ + public void endPageHeader() throws IFException { + } + + /** {@inheritDoc} */ + public void startPageContent() throws IFException { + this.state = IFState.create(); + } + + /** {@inheritDoc} */ + public void endPageContent() throws IFException { + assert this.state.pop() == null; + } + + /** {@inheritDoc} */ + public void startPageTrailer() throws IFException { + } + + /** {@inheritDoc} */ + public void endPageTrailer() throws IFException { + } + + /** {@inheritDoc} */ + public void endPage() throws IFException { + try { + this.pdfDoc.registerObject(currentStream); + currentPage.setContents(currentStream); + PDFAnnotList annots = currentPage.getAnnotations(); + if (annots != null) { + this.pdfDoc.addObject(annots); + } + this.pdfDoc.addObject(currentPage); + this.pdfDoc.output(this.outputStream); + this.textutil = null; + } catch (IOException ioe) { + throw new IFException("I/O error in endPage()", ioe); + } + } + + /** {@inheritDoc} */ + private void saveGraphicsState() { + //endTextObject(); + currentState.push(); + this.state = this.state.push(); + currentStream.add("q\n"); + } + + private void restoreGraphicsState(boolean popState) { + endTextObject(); + currentStream.add("Q\n"); + if (popState) { + currentState.pop(); + this.state = this.state.pop(); + } + } + + private void restoreGraphicsState() { + restoreGraphicsState(true); + } + + /** {@inheritDoc} */ + public void startBox(AffineTransform transform, Dimension size, boolean clip) + throws IFException { + saveGraphicsState(); + currentStream.add(CTMHelper.toPDFString(transform, true) + " cm\n"); + } + + /** {@inheritDoc} */ + public void startBox(AffineTransform[] transforms, Dimension size, boolean clip) + throws IFException { + AffineTransform at = new AffineTransform(); + for (int i = 0, c = transforms.length; i < c; i++) { + at.concatenate(transforms[i]); + } + startBox(at, size, clip); + } + + /** {@inheritDoc} */ + public void endBox() throws IFException { + restoreGraphicsState(); + } + + /** {@inheritDoc} */ + public void startImage(Rectangle rect) throws IFException { + // TODO Auto-generated method stub + + } + + /** {@inheritDoc} */ + public void drawImage(String uri, Rectangle rect) throws IFException { + // TODO Auto-generated method stub + + } + + /** {@inheritDoc} */ + public void endImage() throws IFException { + // TODO Auto-generated method stub + + } + + /** {@inheritDoc} */ + public void addTarget(String name, int x, int y) throws IFException { + // TODO Auto-generated method stub + + } + + private static String toString(Paint paint) { + if (paint instanceof Color) { + return ColorUtil.colorToString((Color)paint); + } else { + throw new UnsupportedOperationException("Paint not supported: " + paint); + } + } + + /** + * Formats a int value (normally coordinates in millipoints) as Strings. + * @param value the value (in millipoints) + * @return the formatted value + */ + protected static String format(int value) { + return PDFNumber.doubleOut(value / 1000f); + } + + /** + * Establishes a new foreground or fill color. + * @param col the color to apply (null skips this operation) + * @param fill true to set the fill color, false for the foreground color + */ + private void updateColor(Color col, boolean fill) { + if (col == null) { + return; + } + boolean update = false; + if (fill) { + update = currentState.setBackColor(col); + } else { + update = currentState.setColor(col); + } + + if (update) { + pdfUtil.setColor(col, fill, this.currentStream); + } + } + + /** {@inheritDoc} */ + public void drawRect(Rectangle rect, Paint fill, Color stroke) throws IFException { + if (fill == null && stroke == null) { + return; + } + endTextObject(); + if (rect.width != 0 && rect.height != 0) { + if (fill != null) { + if (fill instanceof Color) { + updateColor((Color)fill, true); + } else { + throw new UnsupportedOperationException("Non-Color paints NYI"); + } + } + if (stroke != null) { + throw new UnsupportedOperationException("stroke NYI"); + } + StringBuffer sb = new StringBuffer(); + sb.append(format(rect.x)).append(' '); + sb.append(format(rect.y)).append(' '); + sb.append(format(rect.width)).append(' '); + sb.append(format(rect.height)).append(" re"); + if (fill != null) { + sb.append(" f"); + } + if (stroke != null) { + sb.append(" S"); + } + sb.append('\n'); + currentStream.add(sb.toString()); + } + } + + /** Indicates the beginning of a text object. */ + private void beginTextObject() { + if (!textutil.isInTextObject()) { + textutil.beginTextObject(); + } + } + + /** Indicates the end of a text object. */ + private void endTextObject() { + if (textutil.isInTextObject()) { + textutil.endTextObject(); + } + } + + private Typeface getTypeface(String fontName) { + Typeface tf = (Typeface) fontInfo.getFonts().get(fontName); + if (tf instanceof LazyFont) { + tf = ((LazyFont)tf).getRealFont(); + } + return tf; + } + + /** {@inheritDoc} */ + public void drawText(int x, int y, int[] dx, int[] dy, String text) throws IFException { + //Note: dy is currently ignored + beginTextObject(); + FontTriplet triplet = new FontTriplet( + state.getFontFamily(), state.getFontStyle(), state.getFontWeight()); + //TODO Ignored: state.getFontVariant() + String fontKey = fontInfo.getInternalFontKey(triplet); + int sizeMillipoints = state.getFontSize(); + float fontSize = sizeMillipoints / 1000f; + updateColor(state.getTextColor(), true); + + // This assumes that *all* CIDFonts use a /ToUnicode mapping + Typeface tf = getTypeface(fontKey); + SingleByteFont singleByteFont = null; + if (tf instanceof SingleByteFont) { + singleByteFont = (SingleByteFont)tf; + } + Font font = fontInfo.getFontInstance(triplet, sizeMillipoints); + String fontName = font.getFontName(); + + textutil.updateTf(fontKey, fontSize, tf.isMultiByte()); + + textutil.writeTextMatrix(new AffineTransform(1, 0, 0, -1, x / 1000f, y / 1000f)); + int l = text.length(); + int dxl = (dx != null ? dx.length : 0); + + if (dx != null && dxl > 0 && dx[0] != 0) { + textutil.adjustGlyphTJ(dx[0] / fontSize); + } + for (int i = 0; i < l; i++) { + char orgChar = text.charAt(i); + char ch; + float glyphAdjust = 0; + if (font.hasChar(orgChar)) { + ch = font.mapChar(orgChar); + if (singleByteFont != null && singleByteFont.hasAdditionalEncodings()) { + int encoding = ch / 256; + if (encoding == 0) { + textutil.updateTf(fontName, fontSize, tf.isMultiByte()); + } else { + textutil.updateTf(fontName + "_" + Integer.toString(encoding), + fontSize, tf.isMultiByte()); + ch = (char)(ch % 256); + } + } + //int tls = (i < l - 1 ? parentArea.getTextLetterSpaceAdjust() : 0); + //glyphAdjust -= tls; + } else { + if (CharUtilities.isFixedWidthSpace(orgChar)) { + //Fixed width space are rendered as spaces so copy/paste works in a reader + ch = font.mapChar(CharUtilities.SPACE); + glyphAdjust = font.getCharWidth(ch) - font.getCharWidth(orgChar); + } else { + ch = font.mapChar(orgChar); + } + } + textutil.writeTJMappedChar(ch); + + if (dx != null && i < dxl) { + glyphAdjust += dx[i + 1]; + } + + if (glyphAdjust != 0) { + textutil.adjustGlyphTJ(glyphAdjust / fontSize); + } + + } + textutil.writeTJ(); + } + + /** {@inheritDoc} */ + public void setFont(String family, String style, Integer weight, String variant, Integer size, + Color color) throws IFException { + if (family != null) { + state.setFontFamily(family); + } + if (style != null) { + state.setFontStyle(style); + } + if (weight != null) { + state.setFontWeight(weight.intValue()); + } + if (variant != null) { + state.setFontVariant(variant); + } + if (size != null) { + state.setFontSize(size.intValue()); + } + if (color != null) { + state.setTextColor(color); + } + } + + /** {@inheritDoc} */ + public void handleExtensionObject(Object extension) throws IFException { + if (extension instanceof XMPMetadata) { + pdfUtil.renderXMPMetadata((XMPMetadata)extension); + } else { + throw new UnsupportedOperationException( + "Don't know how to handle extension object: " + extension); + } + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFPainterMaker.java b/src/java/org/apache/fop/render/pdf/PDFPainterMaker.java new file mode 100644 index 000000000..b3fe42824 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFPainterMaker.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.apps.MimeConstants; +import org.apache.fop.render.intermediate.AbstractIFPainterMaker; +import org.apache.fop.render.intermediate.IFPainter; +import org.apache.fop.render.intermediate.IFPainterConfigurator; + +/** + * Painter factory for PDF output. + */ +public class PDFPainterMaker extends AbstractIFPainterMaker { + + private static final String[] MIMES = new String[] {MimeConstants.MIME_PDF}; + + /** {@inheritDoc} */ + public IFPainter makePainter(FOUserAgent ua) { + return new PDFPainter(); + } + + /** {@inheritDoc} */ + public boolean needsOutputStream() { + return true; + } + + /** {@inheritDoc} */ + public String[] getSupportedMimeTypes() { + return MIMES; + } + + public IFPainterConfigurator getConfigurator(FOUserAgent userAgent) { + return new PDFRendererConfigurator(userAgent); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java index 154365209..cfe8b9902 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -23,32 +23,21 @@ package org.apache.fop.render.pdf; import java.awt.Color; import java.awt.Point; import java.awt.Rectangle; -import java.awt.color.ICC_Profile; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; -import java.net.URL; import java.util.Iterator; import java.util.List; import java.util.Map; -import javax.xml.transform.Source; -import javax.xml.transform.stream.StreamSource; - -import org.apache.commons.io.IOUtils; - import org.apache.xmlgraphics.image.loader.ImageException; import org.apache.xmlgraphics.image.loader.ImageInfo; import org.apache.xmlgraphics.image.loader.ImageManager; import org.apache.xmlgraphics.image.loader.ImageSessionContext; import org.apache.xmlgraphics.image.loader.util.ImageUtil; -import org.apache.xmlgraphics.xmp.Metadata; -import org.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter; -import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; @@ -84,26 +73,16 @@ import org.apache.fop.fonts.Typeface; import org.apache.fop.pdf.PDFAMode; import org.apache.fop.pdf.PDFAction; import org.apache.fop.pdf.PDFAnnotList; -import org.apache.fop.pdf.PDFColor; -import org.apache.fop.pdf.PDFConformanceException; -import org.apache.fop.pdf.PDFDictionary; import org.apache.fop.pdf.PDFDocument; -import org.apache.fop.pdf.PDFEncryptionManager; import org.apache.fop.pdf.PDFEncryptionParams; import org.apache.fop.pdf.PDFFactory; import org.apache.fop.pdf.PDFFilterList; import org.apache.fop.pdf.PDFGoTo; -import org.apache.fop.pdf.PDFICCBasedColorSpace; -import org.apache.fop.pdf.PDFICCStream; import org.apache.fop.pdf.PDFInfo; import org.apache.fop.pdf.PDFLink; -import org.apache.fop.pdf.PDFMetadata; import org.apache.fop.pdf.PDFNumber; -import org.apache.fop.pdf.PDFNumsArray; import org.apache.fop.pdf.PDFOutline; -import org.apache.fop.pdf.PDFOutputIntent; import org.apache.fop.pdf.PDFPage; -import org.apache.fop.pdf.PDFPageLabels; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFResources; import org.apache.fop.pdf.PDFState; @@ -115,61 +94,33 @@ import org.apache.fop.render.AbstractPathOrientedRenderer; import org.apache.fop.render.Graphics2DAdapter; import org.apache.fop.render.RendererContext; import org.apache.fop.util.CharUtilities; -import org.apache.fop.util.ColorProfileUtil; /** * Renderer that renders areas to PDF. */ -public class PDFRenderer extends AbstractPathOrientedRenderer { - - /** - * The mime type for pdf - */ +public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConfigurationConstants { + + /** The MIME type for PDF */ public static final String MIME_TYPE = MimeConstants.MIME_PDF; /** Normal PDF resolution (72dpi) */ public static final int NORMAL_PDF_RESOLUTION = 72; - - /** PDF encryption parameter: all parameters as object, datatype: PDFEncryptionParams */ - public static final String ENCRYPTION_PARAMS = "encryption-params"; - /** PDF encryption parameter: user password, datatype: String */ - public static final String USER_PASSWORD = "user-password"; - /** PDF encryption parameter: owner password, datatype: String */ - public static final String OWNER_PASSWORD = "owner-password"; - /** PDF encryption parameter: Forbids printing, datatype: Boolean or "true"/"false" */ - public static final String NO_PRINT = "noprint"; - /** PDF encryption parameter: Forbids copying content, datatype: Boolean or "true"/"false" */ - public static final String NO_COPY_CONTENT = "nocopy"; - /** PDF encryption parameter: Forbids editing content, datatype: Boolean or "true"/"false" */ - public static final String NO_EDIT_CONTENT = "noedit"; - /** PDF encryption parameter: Forbids annotations, datatype: Boolean or "true"/"false" */ - public static final String NO_ANNOTATIONS = "noannotations"; - /** Rendering Options key for the PDF/A mode. */ - public static final String PDF_A_MODE = "pdf-a-mode"; - /** Rendering Options key for the PDF/X mode. */ - public static final String PDF_X_MODE = "pdf-x-mode"; - /** Rendering Options key for the ICC profile for the output intent. */ - public static final String KEY_OUTPUT_PROFILE = "output-profile"; - /** - * Rendering Options key for disabling the sRGB color space (only possible if no PDF/A or - * PDF/X profile is active). - */ - public static final String KEY_DISABLE_SRGB_COLORSPACE = "disable-srgb-colorspace"; + /** Controls whether comments are written to the PDF stream. */ protected static final boolean WRITE_COMMENTS = true; - + /** * the PDF Document being created */ protected PDFDocument pdfDoc; - /** the PDF/A mode (Default: disabled) */ - protected PDFAMode pdfAMode = PDFAMode.DISABLED; - - /** the PDF/X mode (Default: disabled) */ - protected PDFXMode pdfXMode = PDFXMode.DISABLED; - + /** + * Utility class which enables all sorts of features that are not directly connected to the + * normal rendering process. + */ + protected PDFRenderingUtil pdfUtil; + /** * Map of pages using the PageViewport as the key * this is used for prepared pages that cannot be immediately @@ -185,7 +136,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { /** * Maps unique PageViewport key back to PageViewport itself */ - protected Map pvReferences = new java.util.HashMap(); + //protected Map pvReferences = new java.util.HashMap(); /** * Maps XSL-FO element IDs to their on-page XY-positions @@ -236,19 +187,6 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { */ protected String currentPageRef; - /** the (optional) encryption parameters */ - protected PDFEncryptionParams encryptionParams; - - /** the ICC stream used as output profile by this document for PDF/A and PDF/X functionality. */ - protected PDFICCStream outputProfile; - /** the default sRGB color space. */ - protected PDFICCBasedColorSpace sRGBColorSpace; - /** controls whether the sRGB color space should be installed */ - protected boolean disableSRGBColorSpace = false; - - /** Optional URI to an output profile to be used. */ - protected String outputProfileURI; - /** drawing state */ protected PDFState currentState = null; @@ -257,225 +195,32 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { /** page height */ protected int pageHeight; - /** Registry of PDF filters */ - protected Map filterMap; - /** Image handler registry */ private PDFImageHandlerRegistry imageHandlerRegistry = new PDFImageHandlerRegistry(); - + /** * create the PDF renderer */ public PDFRenderer() { } - private boolean booleanValueOf(Object obj) { - if (obj instanceof Boolean) { - return ((Boolean)obj).booleanValue(); - } else if (obj instanceof String) { - return Boolean.valueOf((String)obj).booleanValue(); - } else { - throw new IllegalArgumentException("Boolean or \"true\" or \"false\" expected."); - } - } - - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void setUserAgent(FOUserAgent agent) { super.setUserAgent(agent); - PDFEncryptionParams params - = (PDFEncryptionParams)agent.getRendererOptions().get(ENCRYPTION_PARAMS); - if (params != null) { - this.encryptionParams = params; //overwrite if available - } - String pwd; - pwd = (String)agent.getRendererOptions().get(USER_PASSWORD); - if (pwd != null) { - if (encryptionParams == null) { - this.encryptionParams = new PDFEncryptionParams(); - } - this.encryptionParams.setUserPassword(pwd); - } - pwd = (String)agent.getRendererOptions().get(OWNER_PASSWORD); - if (pwd != null) { - if (encryptionParams == null) { - this.encryptionParams = new PDFEncryptionParams(); - } - this.encryptionParams.setOwnerPassword(pwd); - } - Object setting; - setting = agent.getRendererOptions().get(NO_PRINT); - if (setting != null) { - if (encryptionParams == null) { - this.encryptionParams = new PDFEncryptionParams(); - } - this.encryptionParams.setAllowPrint(!booleanValueOf(setting)); - } - setting = agent.getRendererOptions().get(NO_COPY_CONTENT); - if (setting != null) { - if (encryptionParams == null) { - this.encryptionParams = new PDFEncryptionParams(); - } - this.encryptionParams.setAllowCopyContent(!booleanValueOf(setting)); - } - setting = agent.getRendererOptions().get(NO_EDIT_CONTENT); - if (setting != null) { - if (encryptionParams == null) { - this.encryptionParams = new PDFEncryptionParams(); - } - this.encryptionParams.setAllowEditContent(!booleanValueOf(setting)); - } - setting = agent.getRendererOptions().get(NO_ANNOTATIONS); - if (setting != null) { - if (encryptionParams == null) { - this.encryptionParams = new PDFEncryptionParams(); - } - this.encryptionParams.setAllowEditAnnotations(!booleanValueOf(setting)); - } - String s = (String)agent.getRendererOptions().get(PDF_A_MODE); - if (s != null) { - this.pdfAMode = PDFAMode.valueOf(s); - } - s = (String)agent.getRendererOptions().get(PDF_X_MODE); - if (s != null) { - this.pdfXMode = PDFXMode.valueOf(s); - } - s = (String)agent.getRendererOptions().get(KEY_OUTPUT_PROFILE); - if (s != null) { - this.outputProfileURI = s; - } - setting = agent.getRendererOptions().get(KEY_DISABLE_SRGB_COLORSPACE); - if (setting != null) { - this.disableSRGBColorSpace = booleanValueOf(setting); - } + this.pdfUtil = new PDFRenderingUtil(getUserAgent()); } - /** - * {@inheritDoc} - */ + PDFRenderingUtil getPDFUtil() { + return this.pdfUtil; + } + + /** {@inheritDoc} */ public void startRenderer(OutputStream stream) throws IOException { if (userAgent == null) { throw new IllegalStateException("UserAgent must be set before starting the renderer"); } ostream = stream; - this.pdfDoc = new PDFDocument( - userAgent.getProducer() != null ? userAgent.getProducer() : ""); - this.pdfDoc.getProfile().setPDFAMode(this.pdfAMode); - this.pdfDoc.getProfile().setPDFXMode(this.pdfXMode); - this.pdfDoc.getInfo().setCreator(userAgent.getCreator()); - this.pdfDoc.getInfo().setCreationDate(userAgent.getCreationDate()); - this.pdfDoc.getInfo().setAuthor(userAgent.getAuthor()); - this.pdfDoc.getInfo().setTitle(userAgent.getTitle()); - this.pdfDoc.getInfo().setKeywords(userAgent.getKeywords()); - this.pdfDoc.setFilterMap(filterMap); - this.pdfDoc.outputHeader(ostream); - - //Setup encryption if necessary - PDFEncryptionManager.setupPDFEncryption(encryptionParams, this.pdfDoc); - - addsRGBColorSpace(); - if (this.outputProfileURI != null) { - addDefaultOutputProfile(); - } - if (pdfXMode != PDFXMode.DISABLED) { - log.debug(pdfXMode + " is active."); - log.warn("Note: " + pdfXMode - + " support is work-in-progress and not fully implemented, yet!"); - addPDFXOutputIntent(); - } - if (pdfAMode.isPDFA1LevelB()) { - log.debug("PDF/A is active. Conformance Level: " + pdfAMode); - addPDFA1OutputIntent(); - } - - } - - private void addsRGBColorSpace() throws IOException { - if (disableSRGBColorSpace) { - if (this.pdfAMode != PDFAMode.DISABLED - || this.pdfXMode != PDFXMode.DISABLED - || this.outputProfileURI != null) { - throw new IllegalStateException("It is not possible to disable the sRGB color" - + " space if PDF/A or PDF/X functionality is enabled or an" - + " output profile is set!"); - } - } else { - if (this.sRGBColorSpace != null) { - return; - } - //Map sRGB as default RGB profile for DeviceRGB - this.sRGBColorSpace = PDFICCBasedColorSpace.setupsRGBAsDefaultRGBColorSpace(pdfDoc); - } - } - - private void addDefaultOutputProfile() throws IOException { - if (this.outputProfile != null) { - return; - } - ICC_Profile profile; - InputStream in = null; - if (this.outputProfileURI != null) { - this.outputProfile = pdfDoc.getFactory().makePDFICCStream(); - Source src = userAgent.resolveURI(this.outputProfileURI); - if (src == null) { - throw new IOException("Output profile not found: " + this.outputProfileURI); - } - if (src instanceof StreamSource) { - in = ((StreamSource)src).getInputStream(); - } else { - in = new URL(src.getSystemId()).openStream(); - } - try { - profile = ICC_Profile.getInstance(in); - } finally { - IOUtils.closeQuietly(in); - } - this.outputProfile.setColorSpace(profile, null); - } else { - //Fall back to sRGB profile - outputProfile = sRGBColorSpace.getICCStream(); - } - } - - /** - * Adds an OutputIntent to the PDF as mandated by PDF/A-1 when uncalibrated color spaces - * are used (which is true if we use DeviceRGB to represent sRGB colors). - * @throws IOException in case of an I/O problem - */ - private void addPDFA1OutputIntent() throws IOException { - addDefaultOutputProfile(); - - String desc = ColorProfileUtil.getICCProfileDescription(this.outputProfile.getICCProfile()); - PDFOutputIntent outputIntent = pdfDoc.getFactory().makeOutputIntent(); - outputIntent.setSubtype(PDFOutputIntent.GTS_PDFA1); - outputIntent.setDestOutputProfile(this.outputProfile); - outputIntent.setOutputConditionIdentifier(desc); - outputIntent.setInfo(outputIntent.getOutputConditionIdentifier()); - pdfDoc.getRoot().addOutputIntent(outputIntent); - } - - /** - * Adds an OutputIntent to the PDF as mandated by PDF/X when uncalibrated color spaces - * are used (which is true if we use DeviceRGB to represent sRGB colors). - * @throws IOException in case of an I/O problem - */ - private void addPDFXOutputIntent() throws IOException { - addDefaultOutputProfile(); - - String desc = ColorProfileUtil.getICCProfileDescription(this.outputProfile.getICCProfile()); - int deviceClass = this.outputProfile.getICCProfile().getProfileClass(); - if (deviceClass != ICC_Profile.CLASS_OUTPUT) { - throw new PDFConformanceException(pdfDoc.getProfile().getPDFXMode() + " requires that" - + " the DestOutputProfile be an Output Device Profile. " - + desc + " does not match that requirement."); - } - PDFOutputIntent outputIntent = pdfDoc.getFactory().makeOutputIntent(); - outputIntent.setSubtype(PDFOutputIntent.GTS_PDFX); - outputIntent.setDestOutputProfile(this.outputProfile); - outputIntent.setOutputConditionIdentifier(desc); - outputIntent.setInfo(outputIntent.getOutputConditionIdentifier()); - pdfDoc.getRoot().addOutputIntent(outputIntent); + this.pdfDoc = pdfUtil.setupPDFDocument(stream); } /** @@ -498,9 +243,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void stopRenderer() throws IOException { finishOpenGoTos(); @@ -513,7 +256,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { pages = null; pageReferences.clear(); - pvReferences.clear(); + //pvReferences.clear(); pdfResources = null; currentStream = null; currentContext = null; @@ -546,7 +289,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { } else if (odi instanceof OffDocumentExtensionAttachment) { ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)odi).getAttachment(); if (XMPMetadata.CATEGORY.equals(attachment.getCategory())) { - renderXMPMetadata((XMPMetadata)attachment); + pdfUtil.renderXMPMetadata((XMPMetadata)attachment); } } } @@ -606,27 +349,12 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { } } - private void renderXMPMetadata(XMPMetadata metadata) { - Metadata docXMP = metadata.getMetadata(); - Metadata fopXMP = PDFMetadata.createXMPFromPDFDocument(pdfDoc); - //Merge FOP's own metadata into the one from the XSL-FO document - fopXMP.mergeInto(docXMP); - XMPBasicAdapter xmpBasic = XMPBasicSchema.getAdapter(docXMP); - //Metadata was changed so update metadata date - xmpBasic.setMetadataDate(new java.util.Date()); - PDFMetadata.updateInfoFromMetadata(docXMP, pdfDoc.getInfo()); - - PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata( - docXMP, metadata.isReadOnly()); - pdfDoc.getRoot().setMetadata(pdfMetadata); - } - /** {@inheritDoc} */ public Graphics2DAdapter getGraphics2DAdapter() { return new PDFGraphics2DAdapter(this); } - /** + /** * writes out a comment. * @param text text for the comment */ @@ -698,14 +426,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { pdfDoc.getRoot().setLanguage(langCode); } } - if (pdfDoc.getRoot().getMetadata() == null) { - //If at this time no XMP metadata for the overall document has been set, create it - //from the PDFInfo object. - Metadata xmp = PDFMetadata.createXMPFromPDFDocument(pdfDoc); - PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata( - xmp, true); - pdfDoc.getRoot().setMetadata(pdfMetadata); - } + pdfUtil.generateDefaultXMPMetadata(); } /** @@ -731,30 +452,18 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { Rectangle2D bounds = page.getViewArea(); double w = bounds.getWidth(); double h = bounds.getHeight(); - currentPage = this.pdfDoc.getFactory().makePage( + this.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); - - //Produce page labels - PDFPageLabels pageLabels = this.pdfDoc.getRoot().getPageLabels(); - if (pageLabels == null) { - //Set up PageLabels - pageLabels = this.pdfDoc.getFactory().makePageLabels(); - this.pdfDoc.getRoot().setPageLabels(pageLabels); - } - PDFNumsArray nums = pageLabels.getNums(); - PDFDictionary dict = new PDFDictionary(nums); - dict.put("P", page.getPageNumberString()); - //TODO If the sequence of generated page numbers were inspected, this could be - //expressed in a more space-efficient way - nums.put(page.getPageIndex(), dict); - } - + //pvReferences.put(page.getKey(), page); + + pdfUtil.generatePageLabel(page.getPageIndex(), page.getPageNumberString()); + } + /** - * This method creates a pdf stream for the current page + * This method creates a PDF stream for the current page * uses it as the contents of a new page. The page is written * immediately to the output stream. * {@inheritDoc} @@ -788,7 +497,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { pageHeight / 1000f); currentState.concatenate(basicPageTransform); currentStream.add(CTMHelper.toPDFString(basicPageTransform, false) + " cm\n"); - + super.renderPage(page); this.pdfDoc.registerObject(currentStream); @@ -810,9 +519,9 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { new AffineTransform(CTMHelper.toPDFArray(ctm))); if (clippingRect != null) { - clipRect((float)clippingRect.getX() / 1000f, - (float)clippingRect.getY() / 1000f, - (float)clippingRect.getWidth() / 1000f, + clipRect((float)clippingRect.getX() / 1000f, + (float)clippingRect.getY() / 1000f, + (float)clippingRect.getWidth() / 1000f, (float)clippingRect.getHeight() / 1000f); } // multiply with current CTM @@ -831,7 +540,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { currentStream.add(CTMHelper.toPDFString(at, false) + " cm\n"); } } - + /** * Formats a float value (normally coordinates) as Strings. * @param value the value @@ -840,9 +549,9 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { protected static String format(float value) { return PDFNumber.doubleOut(value); } - + /** {@inheritDoc} */ - protected void drawBorderLine(float x1, float y1, float x2, float y2, + protected void drawBorderLine(float x1, float y1, float x2, float y2, boolean horz, boolean startOrBefore, int style, Color col) { float w = x2 - x1; float h = y2 - y1; @@ -852,7 +561,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { return; } switch (style) { - case Constants.EN_DASHED: + case Constants.EN_DASHED: setColor(col, false, null); if (horz) { float unit = Math.abs(2 * h); @@ -864,7 +573,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { currentStream.add("[" + format(unit) + "] 0 d "); currentStream.add(format(h) + " w\n"); float ym = y1 + (h / 2); - currentStream.add(format(x1) + " " + format(ym) + " m " + currentStream.add(format(x1) + " " + format(ym) + " m " + format(x2) + " " + format(ym) + " l S\n"); } else { float unit = Math.abs(2 * w); @@ -876,7 +585,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { currentStream.add("[" + format(unit) + "] 0 d "); currentStream.add(format(w) + " w\n"); float xm = x1 + (w / 2); - currentStream.add(format(xm) + " " + format(y1) + " m " + currentStream.add(format(xm) + " " + format(y1) + " m " + format(xm) + " " + format(y2) + " l S\n"); } break; @@ -893,7 +602,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { currentStream.add("[0 " + format(unit) + "] 0 d "); currentStream.add(format(h) + " w\n"); float ym = y1 + (h / 2); - currentStream.add(format(x1) + " " + format(ym) + " m " + currentStream.add(format(x1) + " " + format(ym) + " m " + format(x2) + " " + format(ym) + " l S\n"); } else { float unit = Math.abs(2 * w); @@ -905,7 +614,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { currentStream.add("[0 " + format(unit) + " ] 0 d "); currentStream.add(format(w) + " w\n"); float xm = x1 + (w / 2); - currentStream.add(format(xm) + " " + format(y1) + " m " + currentStream.add(format(xm) + " " + format(y1) + " m " + format(xm) + " " + format(y2) + " l S\n"); } break; @@ -917,18 +626,18 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { currentStream.add(format(h3) + " w\n"); float ym1 = y1 + (h3 / 2); float ym2 = ym1 + h3 + h3; - currentStream.add(format(x1) + " " + format(ym1) + " m " + currentStream.add(format(x1) + " " + format(ym1) + " m " + format(x2) + " " + format(ym1) + " l S\n"); - currentStream.add(format(x1) + " " + format(ym2) + " m " + currentStream.add(format(x1) + " " + format(ym2) + " m " + format(x2) + " " + format(ym2) + " l S\n"); } else { float w3 = w / 3; currentStream.add(format(w3) + " w\n"); float xm1 = x1 + (w3 / 2); float xm2 = xm1 + w3 + w3; - currentStream.add(format(xm1) + " " + format(y1) + " m " + currentStream.add(format(xm1) + " " + format(y1) + " m " + format(xm1) + " " + format(y2) + " l S\n"); - currentStream.add(format(xm2) + " " + format(y1) + " m " + currentStream.add(format(xm2) + " " + format(y1) + " m " + format(xm2) + " " + format(y2) + " l S\n"); } break; @@ -944,13 +653,13 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { currentStream.add(format(h3) + " w\n"); float ym1 = y1 + (h3 / 2); setColor(uppercol, false, null); - currentStream.add(format(x1) + " " + format(ym1) + " m " + currentStream.add(format(x1) + " " + format(ym1) + " m " + format(x2) + " " + format(ym1) + " l S\n"); setColor(col, false, null); - currentStream.add(format(x1) + " " + format(ym1 + h3) + " m " + currentStream.add(format(x1) + " " + format(ym1 + h3) + " m " + format(x2) + " " + format(ym1 + h3) + " l S\n"); setColor(lowercol, false, null); - currentStream.add(format(x1) + " " + format(ym1 + h3 + h3) + " m " + currentStream.add(format(x1) + " " + format(ym1 + h3 + h3) + " m " + format(x2) + " " + format(ym1 + h3 + h3) + " l S\n"); } else { Color leftcol = lightenColor(col, -colFactor); @@ -959,13 +668,13 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { currentStream.add(format(w3) + " w\n"); float xm1 = x1 + (w3 / 2); setColor(leftcol, false, null); - currentStream.add(format(xm1) + " " + format(y1) + " m " + currentStream.add(format(xm1) + " " + format(y1) + " m " + format(xm1) + " " + format(y2) + " l S\n"); setColor(col, false, null); - currentStream.add(format(xm1 + w3) + " " + format(y1) + " m " + currentStream.add(format(xm1 + w3) + " " + format(y1) + " m " + format(xm1 + w3) + " " + format(y2) + " l S\n"); setColor(rightcol, false, null); - currentStream.add(format(xm1 + w3 + w3) + " " + format(y1) + " m " + currentStream.add(format(xm1 + w3 + w3) + " " + format(y1) + " m " + format(xm1 + w3 + w3) + " " + format(y2) + " l S\n"); } break; @@ -981,14 +690,14 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { currentStream.add(format(h) + " w\n"); float ym1 = y1 + (h / 2); setColor(c, false, null); - currentStream.add(format(x1) + " " + format(ym1) + " m " + currentStream.add(format(x1) + " " + format(ym1) + " m " + format(x2) + " " + format(ym1) + " l S\n"); } else { c = lightenColor(c, (startOrBefore ? 1 : -1) * colFactor); currentStream.add(format(w) + " w\n"); float xm1 = x1 + (w / 2); setColor(c, false, null); - currentStream.add(format(xm1) + " " + format(y1) + " m " + currentStream.add(format(xm1) + " " + format(y1) + " m " + format(xm1) + " " + format(y2) + " l S\n"); } break; @@ -1001,17 +710,17 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { if (horz) { currentStream.add(format(h) + " w\n"); float ym = y1 + (h / 2); - currentStream.add(format(x1) + " " + format(ym) + " m " + currentStream.add(format(x1) + " " + format(ym) + " m " + format(x2) + " " + format(ym) + " l S\n"); } else { currentStream.add(format(w) + " w\n"); float xm = x1 + (w / 2); - currentStream.add(format(xm) + " " + format(y1) + " m " + currentStream.add(format(xm) + " " + format(y1) + " m " + format(xm) + " " + format(y2) + " l S\n"); } } } - + /** * Sets the current line width in points. * @param width line width in points @@ -1022,10 +731,10 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { currentStream.add(format(width) + " w\n"); } } - + /** {@inheritDoc} */ protected void clipRect(float x, float y, float width, float height) { - currentStream.add(format(x) + " " + format(y) + " " + currentStream.add(format(x) + " " + format(y) + " " + format(width) + " " + format(height) + " re "); clip(); } @@ -1039,42 +748,42 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { } /** - * Moves the current point to (x, y), omitting any connecting line segment. + * Moves the current point to (x, y), omitting any connecting line segment. * @param x x coordinate * @param y y coordinate */ protected void moveTo(float x, float y) { currentStream.add(format(x) + " " + format(y) + " m "); } - + /** - * Appends a straight line segment from the current point to (x, y). The - * new current point is (x, y). + * Appends a straight line segment from the current point to (x, y). The + * new current point is (x, y). * @param x x coordinate * @param y y coordinate */ protected void lineTo(float x, float y) { currentStream.add(format(x) + " " + format(y) + " l "); } - + /** - * Closes the current subpath by appending a straight line segment from + * Closes the current subpath by appending a straight line segment from * the current point to the starting point of the subpath. */ protected void closePath() { currentStream.add("h "); } - /** - * {@inheritDoc} + /** + * {@inheritDoc} */ protected void fillRect(float x, float y, float w, float h) { if (w != 0 && h != 0) { - currentStream.add(format(x) + " " + format(y) + " " + currentStream.add(format(x) + " " + format(y) + " " + format(w) + " " + format(h) + " re f\n"); } } - + /** * Draw a line. * @@ -1130,7 +839,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { } /** - * Returns area's id if it is the first area in the document with that id + * Returns area's id if it is the first area in the document with that id * (i.e. if the area qualifies as a link target). * Otherwise, or if the area has no id, null is returned. * @@ -1215,7 +924,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param pdfPageRef the PDF page reference string * @param relativeIPP the *relative* IP position in millipoints * @param relativeBPP the *relative* BP position in millipoints - * @param tf the transformation to apply once the relative positions have been + * @param tf the transformation to apply once the relative positions have been * converted to points */ protected void saveAbsolutePosition(String id, String pdfPageRef, @@ -1257,13 +966,13 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param relativeBPP the *relative* BP position in millipoints */ protected void saveAbsolutePosition(String id, int relativeIPP, int relativeBPP) { - saveAbsolutePosition(id, currentPageRef, + saveAbsolutePosition(id, currentPageRef, relativeIPP, relativeBPP, currentState.getTransform()); } /** - * If the given block area is a possible link target, its id + absolute position will - * be saved. The saved position is only correct if this function is called at the very + * If the given block area is a possible link target, its id + absolute position will + * be saved. The saved position is only correct if this function is called at the very * start of renderBlock! * * @param block the block area in question @@ -1391,7 +1100,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { // warn if link trait found but not allowed, else create link if (linkTraitFound) { if (!annotsAllowed) { - log.warn("Skipping annotation for a link due to PDF profile: " + log.warn("Skipping annotation for a link due to PDF profile: " + pdfDoc.getProfile()); } else if (action != null) { PDFLink pdfLink = factory.makeLink(ipRect, action); @@ -1407,23 +1116,23 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { } return tf; } - + /** {@inheritDoc} */ public void renderText(TextArea text) { renderInlineAreaBackAndBorders(text); Color ct = (Color) text.getTrait(Trait.COLOR); updateColor(ct, true); - + beginTextObject(); String fontName = getInternalFontNameForArea(text); int size = ((Integer) text.getTrait(Trait.FONT_SIZE)).intValue(); - + // This assumes that *all* CIDFonts use a /ToUnicode mapping Typeface tf = getTypeface(fontName); - + textutil.updateTf(fontName, size / 1000f, tf.isMultiByte()); - + // word.getOffset() = only height of text itself // currentBlockIPPosition: 0 for beginning of line; nonzero @@ -1436,7 +1145,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { super.renderText(text); textutil.writeTJ(); - + renderTextDecoration(tf, size, text, bl, rx); } @@ -1445,7 +1154,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { Font font = getFontFromArea(word.getParentArea()); String s = word.getWord(); - escapeText(s, word.getLetterAdjustArray(), + escapeText(s, word.getLetterAdjustArray(), font, (AbstractTextArea)word.getParentArea()); super.renderWord(word); @@ -1455,7 +1164,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { public void renderSpace(SpaceArea space) { Font font = getFontFromArea(space.getParentArea()); String s = space.getSpace(); - + AbstractTextArea textArea = (AbstractTextArea)space.getParentArea(); escapeText(s, null, font, textArea); @@ -1484,7 +1193,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { Font font, AbstractTextArea parentArea) { escapeText(s, 0, s.length(), letterAdjust, font, parentArea); } - + /** * Escapes text according to PDF rules. * @param s Text to escape @@ -1506,7 +1215,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { } int l = s.length(); - + for (int i = start; i < end; i++) { char orgChar = s.charAt(i); char ch; @@ -1552,21 +1261,19 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { /** * Establishes a new foreground or fill color. In contrast to updateColor * this method does not check the PDFState for optimization possibilities. - * @param col the color to apply + * @param col the color to apply * @param fill true to set the fill color, false for the foreground color * @param pdf StringBuffer to write the PDF code to, if null, the code is * written to the current stream. */ protected void setColor(Color col, boolean fill, StringBuffer pdf) { - PDFColor color = new PDFColor(this.pdfDoc, col); - if (pdf != null) { - pdf.append(color.getColorSpaceOut(fill)); + pdfUtil.setColor(col, fill, pdf); } else { - currentStream.add(color.getColorSpaceOut(fill)); + pdfUtil.setColor(col, fill, this.currentStream); } } - + /** * Establishes a new foreground or fill color. * @param col the color to apply (null skips this operation) @@ -1594,7 +1301,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { protected void updateColor(Color col, boolean fill) { updateColor(col, fill, null); } - + /** {@inheritDoc} */ public void renderImage(Image image, Rectangle2D pos) { endTextObject(); @@ -1607,7 +1314,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { endTextObject(); putImage(url, pos, foreignAttributes); } - + /** * Adds a PDF XObject (a bitmap or form) to the PDF that will later be referenced. * @param uri URL of the bitmap @@ -1617,7 +1324,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { protected void putImage(String uri, Rectangle2D pos) { putImage(uri, pos, null); } - + /** * Adds a PDF XObject (a bitmap or form) to the PDF that will later be referenced. * @param uri URL of the bitmap @@ -1649,11 +1356,11 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { try { ImageSessionContext sessionContext = getUserAgent().getImageSessionContext(); info = manager.getImageInfo(uri, sessionContext); - + Map hints = ImageUtil.getDefaultHints(sessionContext); org.apache.xmlgraphics.image.loader.Image img = manager.getImage( info, imageHandlerRegistry.getSupportedFlavors(), hints, sessionContext); - + //First check for a dynamically registered handler PDFImageHandler handler = imageHandlerRegistry.getHandler(img.getClass()); if (handler != null) { @@ -1710,13 +1417,13 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { currentStream.add(format(w) + " 0 0 " + format(-h) + " " + format(currentIPPosition / 1000f + x) + " " - + format(currentBPPosition / 1000f + h + y) + + format(currentBPPosition / 1000f + h + y) + " cm\n" + xobj.getName() + " Do\n"); restoreGraphicsState(); } /** {@inheritDoc} */ - protected RendererContext createRendererContext(int x, int y, int width, int height, + protected RendererContext createRendererContext(int x, int y, int width, int height, Map foreignAttributes) { RendererContext context = super.createRendererContext( x, y, width, height, foreignAttributes); @@ -1747,7 +1454,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { int style = area.getRuleStyle(); float startx = (currentIPPosition + area.getBorderAndPaddingWidthStart()) / 1000f; float starty = (currentBPPosition + area.getOffset()) / 1000f; - float endx = (currentIPPosition + area.getBorderAndPaddingWidthStart() + float endx = (currentIPPosition + area.getBorderAndPaddingWidthStart() + area.getIPD()) / 1000f; float ruleThickness = area.getRuleThickness() / 1000f; Color col = (Color)area.getTrait(Trait.COLOR); @@ -1756,7 +1463,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { case EN_SOLID: case EN_DASHED: case EN_DOUBLE: - drawBorderLine(startx, starty, endx, starty + ruleThickness, + drawBorderLine(startx, starty, endx, starty + ruleThickness, true, true, style, col); break; case EN_DOTTED: @@ -1764,7 +1471,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { //This displaces the dots to the right by half a dot's width //TODO There's room for improvement here currentStream.add("1 0 0 1 " + format(ruleThickness / 2) + " 0 cm\n"); - drawBorderLine(startx, starty, endx, starty + ruleThickness, + drawBorderLine(startx, starty, endx, starty + ruleThickness, true, true, style, col); break; case EN_GROOVE: @@ -1809,13 +1516,13 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { public String getMimeType() { return MIME_TYPE; } - + /** * Sets the PDF/A mode for the PDF renderer. * @param mode the PDF/A mode */ public void setAMode(PDFAMode mode) { - this.pdfAMode = mode; + this.pdfUtil.setAMode(mode); } /** @@ -1823,7 +1530,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param mode the PDF/X mode */ public void setXMode(PDFXMode mode) { - this.pdfXMode = mode; + this.pdfUtil.setXMode(mode); } /** @@ -1831,7 +1538,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param outputProfileURI the URI to the output color profile */ public void setOutputProfileURI(String outputProfileURI) { - this.outputProfileURI = outputProfileURI; + this.pdfUtil.setOutputProfileURI(outputProfileURI); } /** @@ -1839,15 +1546,15 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param filterMap the filter map */ public void setFilterMap(Map filterMap) { - this.filterMap = filterMap; + this.pdfUtil.setFilterMap(filterMap); } /** * Sets the encryption parameters used by the PDF renderer. - * @param encryptionParams the encryption parameters + * @param encryptionParams the encryption parameters */ public void setEncryptionParams(PDFEncryptionParams encryptionParams) { - this.encryptionParams = encryptionParams; + this.pdfUtil.setEncryptionParams(encryptionParams); } } diff --git a/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java b/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java index 51e13dde1..bee8e1175 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java +++ b/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java @@ -5,9 +5,9 @@ * 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. @@ -33,12 +33,15 @@ import org.apache.fop.pdf.PDFFilterList; import org.apache.fop.pdf.PDFXMode; import org.apache.fop.render.PrintRendererConfigurator; import org.apache.fop.render.Renderer; +import org.apache.fop.render.intermediate.IFPainter; +import org.apache.fop.render.intermediate.IFPainterConfigurator; import org.apache.fop.util.LogUtil; /** - * PDF renderer configurator + * PDF renderer configurator */ -public class PDFRendererConfigurator extends PrintRendererConfigurator { +public class PDFRendererConfigurator extends PrintRendererConfigurator + implements IFPainterConfigurator { /** * Default constructor @@ -59,75 +62,93 @@ public class PDFRendererConfigurator extends PrintRendererConfigurator { Configuration cfg = super.getRendererConfig(renderer); if (cfg != null) { PDFRenderer pdfRenderer = (PDFRenderer)renderer; - //PDF filters - try { - Map filterMap = buildFilterMapFromConfiguration(cfg); - if (filterMap != null) { - pdfRenderer.setFilterMap(filterMap); - } - } catch (ConfigurationException e) { - LogUtil.handleException(log, e, false); - } - super.configure(renderer); - - String s = cfg.getChild(PDFRenderer.PDF_A_MODE, true).getValue(null); - if (s != null) { - pdfRenderer.setAMode(PDFAMode.valueOf(s)); - } - s = cfg.getChild(PDFRenderer.PDF_X_MODE, true).getValue(null); - if (s != null) { - pdfRenderer.setXMode(PDFXMode.valueOf(s)); + + PDFRenderingUtil pdfUtil = pdfRenderer.getPDFUtil(); + configure(cfg, pdfUtil); + } + } + + private void configure(Configuration cfg, PDFRenderingUtil pdfUtil) throws FOPException { + //PDF filters + try { + Map filterMap = buildFilterMapFromConfiguration(cfg); + if (filterMap != null) { + pdfUtil.setFilterMap(filterMap); } - Configuration encryptionParamsConfig = cfg.getChild(PDFRenderer.ENCRYPTION_PARAMS, false); - if (encryptionParamsConfig != null) { - PDFEncryptionParams encryptionParams = new PDFEncryptionParams(); - Configuration ownerPasswordConfig = encryptionParamsConfig.getChild( - PDFRenderer.OWNER_PASSWORD, false); - if (ownerPasswordConfig != null) { - String ownerPassword = ownerPasswordConfig.getValue(null); - if (ownerPassword != null) { - encryptionParams.setOwnerPassword(ownerPassword); - } - } - Configuration userPasswordConfig = encryptionParamsConfig.getChild( - PDFRenderer.USER_PASSWORD, false); - if (userPasswordConfig != null) { - String userPassword = userPasswordConfig.getValue(null); - if (userPassword != null) { - encryptionParams.setUserPassword(userPassword); - } - } - Configuration noPrintConfig = encryptionParamsConfig.getChild( - PDFRenderer.NO_PRINT, false); - if (noPrintConfig != null) { - encryptionParams.setAllowPrint(false); - } - Configuration noCopyContentConfig = encryptionParamsConfig.getChild( - PDFRenderer.NO_COPY_CONTENT, false); - if (noCopyContentConfig != null) { - encryptionParams.setAllowCopyContent(false); - } - Configuration noEditContentConfig = encryptionParamsConfig.getChild( - PDFRenderer.NO_EDIT_CONTENT, false); - if (noEditContentConfig != null) { - encryptionParams.setAllowEditContent(false); + } catch (ConfigurationException e) { + LogUtil.handleException(log, e, false); + } + + String s = cfg.getChild(PDFRenderer.PDF_A_MODE, true).getValue(null); + if (s != null) { + pdfUtil.setAMode(PDFAMode.valueOf(s)); + } + s = cfg.getChild(PDFRenderer.PDF_X_MODE, true).getValue(null); + if (s != null) { + pdfUtil.setXMode(PDFXMode.valueOf(s)); + } + Configuration encryptionParamsConfig = cfg.getChild(PDFRenderer.ENCRYPTION_PARAMS, false); + if (encryptionParamsConfig != null) { + PDFEncryptionParams encryptionParams = new PDFEncryptionParams(); + Configuration ownerPasswordConfig = encryptionParamsConfig.getChild( + PDFRenderer.OWNER_PASSWORD, false); + if (ownerPasswordConfig != null) { + String ownerPassword = ownerPasswordConfig.getValue(null); + if (ownerPassword != null) { + encryptionParams.setOwnerPassword(ownerPassword); } - Configuration noAnnotationsConfig = encryptionParamsConfig.getChild( - PDFRenderer.NO_ANNOTATIONS, false); - if (noAnnotationsConfig != null) { - encryptionParams.setAllowEditAnnotations(false); + } + Configuration userPasswordConfig = encryptionParamsConfig.getChild( + PDFRenderer.USER_PASSWORD, false); + if (userPasswordConfig != null) { + String userPassword = userPasswordConfig.getValue(null); + if (userPassword != null) { + encryptionParams.setUserPassword(userPassword); } - pdfRenderer.setEncryptionParams(encryptionParams); } - s = cfg.getChild(PDFRenderer.KEY_OUTPUT_PROFILE, true).getValue(null); - if (s != null) { - pdfRenderer.setOutputProfileURI(s); + Configuration noPrintConfig = encryptionParamsConfig.getChild( + PDFRenderer.NO_PRINT, false); + if (noPrintConfig != null) { + encryptionParams.setAllowPrint(false); + } + Configuration noCopyContentConfig = encryptionParamsConfig.getChild( + PDFRenderer.NO_COPY_CONTENT, false); + if (noCopyContentConfig != null) { + encryptionParams.setAllowCopyContent(false); + } + Configuration noEditContentConfig = encryptionParamsConfig.getChild( + PDFRenderer.NO_EDIT_CONTENT, false); + if (noEditContentConfig != null) { + encryptionParams.setAllowEditContent(false); } - Configuration disableColorSpaceConfig = cfg.getChild(PDFRenderer.KEY_DISABLE_SRGB_COLORSPACE, false); - if (disableColorSpaceConfig != null) { - pdfRenderer.disableSRGBColorSpace = disableColorSpaceConfig.getValueAsBoolean(false); + Configuration noAnnotationsConfig = encryptionParamsConfig.getChild( + PDFRenderer.NO_ANNOTATIONS, false); + if (noAnnotationsConfig != null) { + encryptionParams.setAllowEditAnnotations(false); } + pdfUtil.setEncryptionParams(encryptionParams); + } + s = cfg.getChild(PDFRenderer.KEY_OUTPUT_PROFILE, true).getValue(null); + if (s != null) { + pdfUtil.setOutputProfileURI(s); + } + Configuration disableColorSpaceConfig + = cfg.getChild(PDFRenderer.KEY_DISABLE_SRGB_COLORSPACE, false); + if (disableColorSpaceConfig != null) { + pdfUtil.setDisableSRGBColorSpace( + disableColorSpaceConfig.getValueAsBoolean(false)); + } + } + + public void configure(IFPainter painter) throws FOPException { + Configuration cfg = super.getRendererConfig(painter.getMimeType()); + if (cfg != null) { + PDFPainter pdfPainter = (PDFPainter)painter; + PDFRenderingUtil pdfUtil = pdfPainter.getPDFUtil(); + configure(cfg, pdfUtil); + + //TODO Configure fonts } } @@ -137,7 +158,7 @@ public class PDFRendererConfigurator extends PrintRendererConfigurator { * @return Map the newly built filter map * @throws ConfigurationException if a filter list is defined twice */ - public static Map buildFilterMapFromConfiguration(Configuration cfg) + public static Map buildFilterMapFromConfiguration(Configuration cfg) throws ConfigurationException { Map filterMap = new java.util.HashMap(); Configuration[] filterLists = cfg.getChildren("filterList"); @@ -150,11 +171,11 @@ public class PDFRendererConfigurator extends PrintRendererConfigurator { String name = filt[j].getValue(); filterList.add(name); } - + if (type == null) { type = PDFFilterList.DEFAULT_FILTER; } - + if (!filterList.isEmpty() && log.isDebugEnabled()) { StringBuffer debug = new StringBuffer("Adding PDF filter"); if (filterList.size() != 1) { @@ -169,13 +190,14 @@ public class PDFRendererConfigurator extends PrintRendererConfigurator { } log.debug(debug.toString()); } - + if (filterMap.get(type) != null) { - throw new ConfigurationException("A filterList of type '" + throw new ConfigurationException("A filterList of type '" + type + "' has already been defined"); } filterMap.put(type, filterList); } - return filterMap; + return filterMap; } + } diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java new file mode 100644 index 000000000..adc3ff771 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java @@ -0,0 +1,439 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; + +import java.awt.Color; +import java.awt.color.ICC_Profile; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.util.Map; + +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.xmp.Metadata; +import org.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter; +import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fo.extensions.xmp.XMPMetadata; +import org.apache.fop.pdf.PDFAMode; +import org.apache.fop.pdf.PDFColor; +import org.apache.fop.pdf.PDFConformanceException; +import org.apache.fop.pdf.PDFDictionary; +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFEncryptionManager; +import org.apache.fop.pdf.PDFEncryptionParams; +import org.apache.fop.pdf.PDFICCBasedColorSpace; +import org.apache.fop.pdf.PDFICCStream; +import org.apache.fop.pdf.PDFInfo; +import org.apache.fop.pdf.PDFMetadata; +import org.apache.fop.pdf.PDFNumsArray; +import org.apache.fop.pdf.PDFOutputIntent; +import org.apache.fop.pdf.PDFPageLabels; +import org.apache.fop.pdf.PDFStream; +import org.apache.fop.pdf.PDFXMode; +import org.apache.fop.util.ColorProfileUtil; + +/** + * Utility class which enables all sorts of features that are not directly connected to the + * normal rendering process. + */ +class PDFRenderingUtil implements PDFConfigurationConstants { + + /** logging instance */ + private static Log log = LogFactory.getLog(PDFRenderingUtil.class); + + private FOUserAgent userAgent; + + /** the PDF Document being created */ + protected PDFDocument pdfDoc; + + /** the PDF/A mode (Default: disabled) */ + protected PDFAMode pdfAMode = PDFAMode.DISABLED; + + /** the PDF/X mode (Default: disabled) */ + protected PDFXMode pdfXMode = PDFXMode.DISABLED; + + /** the (optional) encryption parameters */ + protected PDFEncryptionParams encryptionParams; + + /** Registry of PDF filters */ + protected Map filterMap; + + /** the ICC stream used as output profile by this document for PDF/A and PDF/X functionality. */ + protected PDFICCStream outputProfile; + /** the default sRGB color space. */ + protected PDFICCBasedColorSpace sRGBColorSpace; + /** controls whether the sRGB color space should be installed */ + protected boolean disableSRGBColorSpace = false; + + /** Optional URI to an output profile to be used. */ + protected String outputProfileURI; + + + PDFRenderingUtil(FOUserAgent userAgent) { + this.userAgent = userAgent; + initialize(); + } + + private static boolean booleanValueOf(Object obj) { + if (obj instanceof Boolean) { + return ((Boolean)obj).booleanValue(); + } else if (obj instanceof String) { + return Boolean.valueOf((String)obj).booleanValue(); + } else { + throw new IllegalArgumentException("Boolean or \"true\" or \"false\" expected."); + } + } + + private void initialize() { + PDFEncryptionParams params + = (PDFEncryptionParams)userAgent.getRendererOptions().get(ENCRYPTION_PARAMS); + if (params != null) { + this.encryptionParams = params; //overwrite if available + } + String pwd; + pwd = (String)userAgent.getRendererOptions().get(USER_PASSWORD); + if (pwd != null) { + if (encryptionParams == null) { + this.encryptionParams = new PDFEncryptionParams(); + } + this.encryptionParams.setUserPassword(pwd); + } + pwd = (String)userAgent.getRendererOptions().get(OWNER_PASSWORD); + if (pwd != null) { + if (encryptionParams == null) { + this.encryptionParams = new PDFEncryptionParams(); + } + this.encryptionParams.setOwnerPassword(pwd); + } + Object setting; + setting = userAgent.getRendererOptions().get(NO_PRINT); + if (setting != null) { + if (encryptionParams == null) { + this.encryptionParams = new PDFEncryptionParams(); + } + this.encryptionParams.setAllowPrint(!booleanValueOf(setting)); + } + setting = userAgent.getRendererOptions().get(NO_COPY_CONTENT); + if (setting != null) { + if (encryptionParams == null) { + this.encryptionParams = new PDFEncryptionParams(); + } + this.encryptionParams.setAllowCopyContent(!booleanValueOf(setting)); + } + setting = userAgent.getRendererOptions().get(NO_EDIT_CONTENT); + if (setting != null) { + if (encryptionParams == null) { + this.encryptionParams = new PDFEncryptionParams(); + } + this.encryptionParams.setAllowEditContent(!booleanValueOf(setting)); + } + setting = userAgent.getRendererOptions().get(NO_ANNOTATIONS); + if (setting != null) { + if (encryptionParams == null) { + this.encryptionParams = new PDFEncryptionParams(); + } + this.encryptionParams.setAllowEditAnnotations(!booleanValueOf(setting)); + } + String s = (String)userAgent.getRendererOptions().get(PDF_A_MODE); + if (s != null) { + this.pdfAMode = PDFAMode.valueOf(s); + } + s = (String)userAgent.getRendererOptions().get(PDF_X_MODE); + if (s != null) { + this.pdfXMode = PDFXMode.valueOf(s); + } + s = (String)userAgent.getRendererOptions().get(KEY_OUTPUT_PROFILE); + if (s != null) { + this.outputProfileURI = s; + } + setting = userAgent.getRendererOptions().get(KEY_DISABLE_SRGB_COLORSPACE); + if (setting != null) { + this.disableSRGBColorSpace = booleanValueOf(setting); + } + } + + public FOUserAgent getUserAgent() { + return this.userAgent; + } + + /** + * Sets the PDF/A mode for the PDF renderer. + * @param mode the PDF/A mode + */ + public void setAMode(PDFAMode mode) { + this.pdfAMode = mode; + } + + /** + * Sets the PDF/X mode for the PDF renderer. + * @param mode the PDF/X mode + */ + public void setXMode(PDFXMode mode) { + this.pdfXMode = mode; + } + + /** + * Sets the output color profile for the PDF renderer. + * @param outputProfileURI the URI to the output color profile + */ + public void setOutputProfileURI(String outputProfileURI) { + this.outputProfileURI = outputProfileURI; + } + + /** + * Enables or disables the default sRGB color space needed for the PDF document to preserve + * the sRGB colors used in XSL-FO. + * @param disable true to disable, false to enable + */ + public void setDisableSRGBColorSpace(boolean disable) { + this.disableSRGBColorSpace = disable; + } + + /** + * Sets the filter map to be used by the PDF renderer. + * @param filterMap the filter map + */ + public void setFilterMap(Map filterMap) { + this.filterMap = filterMap; + } + + /** + * Sets the encryption parameters used by the PDF renderer. + * @param encryptionParams the encryption parameters + */ + public void setEncryptionParams(PDFEncryptionParams encryptionParams) { + this.encryptionParams = encryptionParams; + } + + private void updateInfo() { + PDFInfo info = pdfDoc.getInfo(); + info.setCreator(userAgent.getCreator()); + info.setCreationDate(userAgent.getCreationDate()); + info.setAuthor(userAgent.getAuthor()); + info.setTitle(userAgent.getTitle()); + info.setKeywords(userAgent.getKeywords()); + } + + private void updatePDFProfiles() { + pdfDoc.getProfile().setPDFAMode(this.pdfAMode); + pdfDoc.getProfile().setPDFXMode(this.pdfXMode); + } + + private void addsRGBColorSpace() throws IOException { + if (disableSRGBColorSpace) { + if (this.pdfAMode != PDFAMode.DISABLED + || this.pdfXMode != PDFXMode.DISABLED + || this.outputProfileURI != null) { + throw new IllegalStateException("It is not possible to disable the sRGB color" + + " space if PDF/A or PDF/X functionality is enabled or an" + + " output profile is set!"); + } + } else { + if (this.sRGBColorSpace != null) { + return; + } + //Map sRGB as default RGB profile for DeviceRGB + this.sRGBColorSpace = PDFICCBasedColorSpace.setupsRGBAsDefaultRGBColorSpace(pdfDoc); + } + } + + private void addDefaultOutputProfile() throws IOException { + if (this.outputProfile != null) { + return; + } + ICC_Profile profile; + InputStream in = null; + if (this.outputProfileURI != null) { + this.outputProfile = pdfDoc.getFactory().makePDFICCStream(); + Source src = getUserAgent().resolveURI(this.outputProfileURI); + if (src == null) { + throw new IOException("Output profile not found: " + this.outputProfileURI); + } + if (src instanceof StreamSource) { + in = ((StreamSource)src).getInputStream(); + } else { + in = new URL(src.getSystemId()).openStream(); + } + try { + profile = ICC_Profile.getInstance(in); + } finally { + IOUtils.closeQuietly(in); + } + this.outputProfile.setColorSpace(profile, null); + } else { + //Fall back to sRGB profile + outputProfile = sRGBColorSpace.getICCStream(); + } + } + + /** + * Adds an OutputIntent to the PDF as mandated by PDF/A-1 when uncalibrated color spaces + * are used (which is true if we use DeviceRGB to represent sRGB colors). + * @throws IOException in case of an I/O problem + */ + private void addPDFA1OutputIntent() throws IOException { + addDefaultOutputProfile(); + + String desc = ColorProfileUtil.getICCProfileDescription(this.outputProfile.getICCProfile()); + PDFOutputIntent outputIntent = pdfDoc.getFactory().makeOutputIntent(); + outputIntent.setSubtype(PDFOutputIntent.GTS_PDFA1); + outputIntent.setDestOutputProfile(this.outputProfile); + outputIntent.setOutputConditionIdentifier(desc); + outputIntent.setInfo(outputIntent.getOutputConditionIdentifier()); + pdfDoc.getRoot().addOutputIntent(outputIntent); + } + + /** + * Adds an OutputIntent to the PDF as mandated by PDF/X when uncalibrated color spaces + * are used (which is true if we use DeviceRGB to represent sRGB colors). + * @throws IOException in case of an I/O problem + */ + private void addPDFXOutputIntent() throws IOException { + addDefaultOutputProfile(); + + String desc = ColorProfileUtil.getICCProfileDescription(this.outputProfile.getICCProfile()); + int deviceClass = this.outputProfile.getICCProfile().getProfileClass(); + if (deviceClass != ICC_Profile.CLASS_OUTPUT) { + throw new PDFConformanceException(pdfDoc.getProfile().getPDFXMode() + " requires that" + + " the DestOutputProfile be an Output Device Profile. " + + desc + " does not match that requirement."); + } + PDFOutputIntent outputIntent = pdfDoc.getFactory().makeOutputIntent(); + outputIntent.setSubtype(PDFOutputIntent.GTS_PDFX); + outputIntent.setDestOutputProfile(this.outputProfile); + outputIntent.setOutputConditionIdentifier(desc); + outputIntent.setInfo(outputIntent.getOutputConditionIdentifier()); + pdfDoc.getRoot().addOutputIntent(outputIntent); + } + + public void renderXMPMetadata(XMPMetadata metadata) { + Metadata docXMP = metadata.getMetadata(); + Metadata fopXMP = PDFMetadata.createXMPFromPDFDocument(pdfDoc); + //Merge FOP's own metadata into the one from the XSL-FO document + fopXMP.mergeInto(docXMP); + XMPBasicAdapter xmpBasic = XMPBasicSchema.getAdapter(docXMP); + //Metadata was changed so update metadata date + xmpBasic.setMetadataDate(new java.util.Date()); + PDFMetadata.updateInfoFromMetadata(docXMP, pdfDoc.getInfo()); + + PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata( + docXMP, metadata.isReadOnly()); + pdfDoc.getRoot().setMetadata(pdfMetadata); + } + + public void generateDefaultXMPMetadata() { + if (pdfDoc.getRoot().getMetadata() == null) { + //If at this time no XMP metadata for the overall document has been set, create it + //from the PDFInfo object. + Metadata xmp = PDFMetadata.createXMPFromPDFDocument(pdfDoc); + PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata( + xmp, true); + pdfDoc.getRoot().setMetadata(pdfMetadata); + } + } + + public PDFDocument setupPDFDocument(OutputStream out) throws IOException { + if (this.pdfDoc != null) { + throw new IllegalStateException("PDFDocument already set up"); + } + this.pdfDoc = new PDFDocument( + userAgent.getProducer() != null ? userAgent.getProducer() : ""); + updateInfo(); + updatePDFProfiles(); + pdfDoc.setFilterMap(filterMap); + pdfDoc.outputHeader(out); + + //Setup encryption if necessary + PDFEncryptionManager.setupPDFEncryption(encryptionParams, pdfDoc); + + addsRGBColorSpace(); + if (this.outputProfileURI != null) { + addDefaultOutputProfile(); + } + if (pdfXMode != PDFXMode.DISABLED) { + log.debug(pdfXMode + " is active."); + log.warn("Note: " + pdfXMode + + " support is work-in-progress and not fully implemented, yet!"); + addPDFXOutputIntent(); + } + if (pdfAMode.isPDFA1LevelB()) { + log.debug("PDF/A is active. Conformance Level: " + pdfAMode); + addPDFA1OutputIntent(); + } + return this.pdfDoc; + } + + /** + * Generates a page label in the PDF document. + * @param pageIndex the index of the page + * @param pageNumber the formatted page number + */ + public void generatePageLabel(int pageIndex, String pageNumber) { + //Produce page labels + PDFPageLabels pageLabels = this.pdfDoc.getRoot().getPageLabels(); + if (pageLabels == null) { + //Set up PageLabels + pageLabels = this.pdfDoc.getFactory().makePageLabels(); + this.pdfDoc.getRoot().setPageLabels(pageLabels); + } + PDFNumsArray nums = pageLabels.getNums(); + PDFDictionary dict = new PDFDictionary(nums); + dict.put("P", pageNumber); + //TODO If the sequence of generated page numbers were inspected, this could be + //expressed in a more space-efficient way + nums.put(pageIndex, dict); + } + + /** + * Establishes a new foreground or fill color. In contrast to updateColor + * this method does not check the PDFState for optimization possibilities. + * @param col the color to apply + * @param fill true to set the fill color, false for the foreground color + * @param pdf StringBuffer to write the PDF code to + */ + public void setColor(Color col, boolean fill, StringBuffer pdf) { + assert pdf != null; + PDFColor color = new PDFColor(this.pdfDoc, col); + pdf.append(color.getColorSpaceOut(fill)); + } + + /** + * Establishes a new foreground or fill color. + * @param col the color to apply + * @param fill true to set the fill color, false for the foreground color + * @param stream the PDFStream to write the PDF code to + */ + public void setColor(Color col, boolean fill, PDFStream stream) { + assert stream != null; + PDFColor color = new PDFColor(this.pdfDoc, col); + stream.add(color.getColorSpaceOut(fill)); + } + + +} |