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-ffa450edef68tags/fop-1_0
@@ -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()) { |
@@ -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; | |||
//} | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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. |
@@ -56,11 +56,6 @@ public abstract class AbstractXMLWritingIFPainter extends AbstractIFPainter { | |||
/** Main SAX ContentHandler to receive the generated SAX events. */ | |||
protected ContentHandler handler; | |||
/** {@inheritDoc} */ | |||
public ContentHandler getContentHandler() { | |||
return this.handler; | |||
} | |||
/** {@inheritDoc} */ | |||
public void setResult(Result result) throws IFException { | |||
if (result instanceof SAXResult) { |
@@ -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 { | |||
} | |||
} |
@@ -96,6 +96,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. | |||
@@ -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 |
@@ -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; | |||
} |
@@ -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); | |||
} | |||
} | |||
} | |||
} |
@@ -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); |
@@ -55,6 +55,11 @@ public class IFSerializer extends AbstractXMLWritingIFPainter implements IFConst | |||
//rendering the IF to the final format later on | |||
} | |||
/** {@inheritDoc} */ | |||
public String getMimeType() { | |||
return MIME_TYPE; | |||
} | |||
/** {@inheritDoc} */ | |||
public void startDocument() throws IFException { | |||
try { |
@@ -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"; | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -5,7 +5,7 @@ | |||
* 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 | |||
@@ -14,7 +14,9 @@ | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
/* $Id$ */ | |||
package org.apache.fop.util; | |||
/** | |||
@@ -27,84 +29,84 @@ public final class ConversionUtils { | |||
* Converts the given base <code>String</code> into | |||
* an array of <code>int</code>, splitting the base along the | |||
* given separator pattern. | |||
* <em>Note: this method assumes the input is a string containing | |||
* <em>Note: this method assumes the input is a string containing | |||
* only decimal integers, signed or unsigned, that are parsable | |||
* by <code>java.lang.Integer.parseInt(String)</code>. If this | |||
* is not the case, the resulting <code>NumberFormatException</code> | |||
* will have to be handled by the caller.</em> | |||
* | |||
* | |||
* @param baseString the base string | |||
* @param separatorPattern the pattern separating the integer values | |||
* (if this is <code>null</code>, the baseString is parsed as one | |||
* integer value) | |||
* integer value) | |||
* @return an array of <code>int</code> whose size is equal to the number | |||
* values in the input string; <code>null</code> if this number | |||
* is equal to zero. | |||
*/ | |||
public static int[] toIntArray(String baseString, String separatorPattern) { | |||
if (baseString == null || "".equals(baseString)) { | |||
return null; | |||
} | |||
if (separatorPattern == null || "".equals(separatorPattern)) { | |||
return new int[] { Integer.parseInt(baseString) }; | |||
return new int[] {Integer.parseInt(baseString)}; | |||
} | |||
String[] values = baseString.split(separatorPattern); | |||
int numValues = values.length; | |||
if (numValues == 0) { | |||
return null; | |||
} | |||
int[] returnArray = new int[numValues]; | |||
for (int i = 0; i < numValues; ++i) { | |||
returnArray[i] = Integer.parseInt(values[i]); | |||
} | |||
return returnArray; | |||
} | |||
/** | |||
* Converts the given base <code>String</code> into | |||
* an array of <code>double</code>, splitting the base along the | |||
* given separator pattern. | |||
* <em>Note: this method assumes the input is a string containing | |||
* <em>Note: this method assumes the input is a string containing | |||
* only decimal doubles, signed or unsigned, that are parsable | |||
* by <code>java.lang.Double.parseDouble(String)</code>. If this | |||
* is not the case, the resulting <code>NumberFormatException</code> | |||
* will have to be handled by the caller.</em> | |||
* | |||
* | |||
* @param baseString the base string | |||
* @param separatorPattern the pattern separating the integer values | |||
* (if this is <code>null</code>, the baseString is parsed as one | |||
* double value) | |||
* double value) | |||
* @return an array of <code>double</code> whose size is equal to the number | |||
* values in the input string; <code>null</code> if this number | |||
* is equal to zero. | |||
*/ | |||
public static double[] toDoubleArray(String baseString, String separatorPattern) { | |||
if (baseString == null || "".equals(baseString)) { | |||
return null; | |||
} | |||
if (separatorPattern == null || "".equals(separatorPattern)) { | |||
return new double[] { Double.parseDouble(baseString) }; | |||
return new double[] {Double.parseDouble(baseString)}; | |||
} | |||
String[] values = baseString.split(separatorPattern); | |||
int numValues = values.length; | |||
if (numValues == 0) { | |||
return null; | |||
} | |||
double[] returnArray = new double[numValues]; | |||
for (int i = 0; i < numValues; ++i) { | |||
returnArray[i] = Double.parseDouble(values[i]); | |||
} | |||
return returnArray; | |||
} | |||
} |
@@ -77,6 +77,11 @@ public class SVGPainter extends AbstractSVGPainter { | |||
return true; | |||
} | |||
/** {@inheritDoc} */ | |||
public String getMimeType() { | |||
return MIME_TYPE; | |||
} | |||
/** {@inheritDoc} */ | |||
public void setResult(Result result) throws IFException { | |||
if (result instanceof StreamResult) { |
@@ -22,6 +22,7 @@ package org.apache.fop.render.svg; | |||
import org.apache.fop.apps.FOUserAgent; | |||
import org.apache.fop.render.intermediate.AbstractIFPainterMaker; | |||
import org.apache.fop.render.intermediate.IFPainter; | |||
import org.apache.fop.render.intermediate.IFPainterConfigurator; | |||
/** | |||
* Painter factory for SVG output. | |||
@@ -45,4 +46,10 @@ public class SVGPainterMaker extends AbstractIFPainterMaker { | |||
return MIMES; | |||
} | |||
/** {@inheritDoc} */ | |||
public IFPainterConfigurator getConfigurator(FOUserAgent userAgent) { | |||
// TODO Auto-generated method stub | |||
return null; | |||
} | |||
} |
@@ -56,6 +56,11 @@ public class SVGPrintPainter extends AbstractSVGPainter { | |||
return false; | |||
} | |||
/** {@inheritDoc} */ | |||
public String getMimeType() { | |||
return MIME_SVG_PRINT; | |||
} | |||
/** {@inheritDoc} */ | |||
public void startDocument() throws IFException { | |||
try { |
@@ -22,6 +22,7 @@ package org.apache.fop.render.svg; | |||
import org.apache.fop.apps.FOUserAgent; | |||
import org.apache.fop.render.intermediate.AbstractIFPainterMaker; | |||
import org.apache.fop.render.intermediate.IFPainter; | |||
import org.apache.fop.render.intermediate.IFPainterConfigurator; | |||
/** | |||
* Painter factory for SVG Print output. | |||
@@ -45,4 +46,10 @@ public class SVGPrintPainterMaker extends AbstractIFPainterMaker { | |||
return MIMES; | |||
} | |||
/** {@inheritDoc} */ | |||
public IFPainterConfigurator getConfigurator(FOUserAgent userAgent) { | |||
// TODO Auto-generated method stub | |||
return null; | |||
} | |||
} |