diff options
Diffstat (limited to 'src/java/org/apache/fop/render/pdf')
21 files changed, 2521 insertions, 806 deletions
diff --git a/src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java b/src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java index e8988244f..5ddcd06c6 100644 --- a/src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java +++ b/src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java @@ -75,6 +75,18 @@ public class ImageRenderedAdapter extends AbstractImageAdapter { return ((ImageRendered)this.image); } + /** {@inheritDoc} */ + public int getWidth() { + RenderedImage ri = getImage().getRenderedImage(); + return ri.getWidth(); + } + + /** {@inheritDoc} */ + public int getHeight() { + RenderedImage ri = getImage().getRenderedImage(); + return ri.getHeight(); + } + private ColorModel getEffectiveColorModel() { return encodingHelper.getEncodedColorModel(); } diff --git a/src/java/org/apache/fop/render/pdf/PDFBorderPainter.java b/src/java/org/apache/fop/render/pdf/PDFBorderPainter.java new file mode 100644 index 000000000..019ec82a0 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFBorderPainter.java @@ -0,0 +1,325 @@ +/* + * 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.Point; +import java.awt.Rectangle; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.fo.Constants; +import org.apache.fop.render.intermediate.BorderPainter; +import org.apache.fop.traits.RuleStyle; +import org.apache.fop.util.ColorUtil; + +/** + * PDF-specific implementation of the {@code BorderPainter}. + */ +public class PDFBorderPainter extends BorderPainter { + + /** logging instance */ + private static Log log = LogFactory.getLog(PDFBorderPainter.class); + + private PDFContentGenerator generator; + + public PDFBorderPainter(PDFContentGenerator generator) { + this.generator = generator; + } + + /** {@inheritDoc} */ + protected void drawBorderLine(int x1, int y1, int x2, int y2, boolean horz, + boolean startOrBefore, int style, Color col) { + drawBorderLine(generator, x1 / 1000f, y1 / 1000f, x2 / 1000f, y2 / 1000f, + horz, startOrBefore, style, col); + } + + /** {@inheritDoc} */ + public static void drawBorderLine(PDFContentGenerator generator, + float x1, float y1, float x2, float y2, boolean horz, + boolean startOrBefore, int style, Color col) { + float w = x2 - x1; + float h = y2 - y1; + if ((w < 0) || (h < 0)) { + log.error("Negative extent received (w=" + w + ", h=" + h + + "). Border won't be painted."); + return; + } + switch (style) { + case Constants.EN_DASHED: + generator.setColor(col, false); + if (horz) { + float unit = Math.abs(2 * h); + int rep = (int)(w / unit); + if (rep % 2 == 0) { + rep++; + } + unit = w / rep; + generator.add("[" + format(unit) + "] 0 d "); + generator.add(format(h) + " w\n"); + float ym = y1 + (h / 2); + generator.add(format(x1) + " " + format(ym) + " m " + + format(x2) + " " + format(ym) + " l S\n"); + } else { + float unit = Math.abs(2 * w); + int rep = (int)(h / unit); + if (rep % 2 == 0) { + rep++; + } + unit = h / rep; + generator.add("[" + format(unit) + "] 0 d "); + generator.add(format(w) + " w\n"); + float xm = x1 + (w / 2); + generator.add(format(xm) + " " + format(y1) + " m " + + format(xm) + " " + format(y2) + " l S\n"); + } + break; + case Constants.EN_DOTTED: + generator.setColor(col, false); + generator.add("1 J "); + if (horz) { + float unit = Math.abs(2 * h); + int rep = (int)(w / unit); + if (rep % 2 == 0) { + rep++; + } + unit = w / rep; + generator.add("[0 " + format(unit) + "] 0 d "); + generator.add(format(h) + " w\n"); + float ym = y1 + (h / 2); + generator.add(format(x1) + " " + format(ym) + " m " + + format(x2) + " " + format(ym) + " l S\n"); + } else { + float unit = Math.abs(2 * w); + int rep = (int)(h / unit); + if (rep % 2 == 0) { + rep++; + } + unit = h / rep; + generator.add("[0 " + format(unit) + " ] 0 d "); + generator.add(format(w) + " w\n"); + float xm = x1 + (w / 2); + generator.add(format(xm) + " " + format(y1) + " m " + + format(xm) + " " + format(y2) + " l S\n"); + } + break; + case Constants.EN_DOUBLE: + generator.setColor(col, false); + generator.add("[] 0 d "); + if (horz) { + float h3 = h / 3; + generator.add(format(h3) + " w\n"); + float ym1 = y1 + (h3 / 2); + float ym2 = ym1 + h3 + h3; + generator.add(format(x1) + " " + format(ym1) + " m " + + format(x2) + " " + format(ym1) + " l S\n"); + generator.add(format(x1) + " " + format(ym2) + " m " + + format(x2) + " " + format(ym2) + " l S\n"); + } else { + float w3 = w / 3; + generator.add(format(w3) + " w\n"); + float xm1 = x1 + (w3 / 2); + float xm2 = xm1 + w3 + w3; + generator.add(format(xm1) + " " + format(y1) + " m " + + format(xm1) + " " + format(y2) + " l S\n"); + generator.add(format(xm2) + " " + format(y1) + " m " + + format(xm2) + " " + format(y2) + " l S\n"); + } + break; + case Constants.EN_GROOVE: + case Constants.EN_RIDGE: + { + float colFactor = (style == Constants.EN_GROOVE ? 0.4f : -0.4f); + generator.add("[] 0 d "); + if (horz) { + Color uppercol = ColorUtil.lightenColor(col, -colFactor); + Color lowercol = ColorUtil.lightenColor(col, colFactor); + float h3 = h / 3; + generator.add(format(h3) + " w\n"); + float ym1 = y1 + (h3 / 2); + generator.setColor(uppercol, false); + generator.add(format(x1) + " " + format(ym1) + " m " + + format(x2) + " " + format(ym1) + " l S\n"); + generator.setColor(col, false); + generator.add(format(x1) + " " + format(ym1 + h3) + " m " + + format(x2) + " " + format(ym1 + h3) + " l S\n"); + generator.setColor(lowercol, false); + generator.add(format(x1) + " " + format(ym1 + h3 + h3) + " m " + + format(x2) + " " + format(ym1 + h3 + h3) + " l S\n"); + } else { + Color leftcol = ColorUtil.lightenColor(col, -colFactor); + Color rightcol = ColorUtil.lightenColor(col, colFactor); + float w3 = w / 3; + generator.add(format(w3) + " w\n"); + float xm1 = x1 + (w3 / 2); + generator.setColor(leftcol, false); + generator.add(format(xm1) + " " + format(y1) + " m " + + format(xm1) + " " + format(y2) + " l S\n"); + generator.setColor(col, false); + generator.add(format(xm1 + w3) + " " + format(y1) + " m " + + format(xm1 + w3) + " " + format(y2) + " l S\n"); + generator.setColor(rightcol, false); + generator.add(format(xm1 + w3 + w3) + " " + format(y1) + " m " + + format(xm1 + w3 + w3) + " " + format(y2) + " l S\n"); + } + break; + } + case Constants.EN_INSET: + case Constants.EN_OUTSET: + { + float colFactor = (style == Constants.EN_OUTSET ? 0.4f : -0.4f); + generator.add("[] 0 d "); + Color c = col; + if (horz) { + c = ColorUtil.lightenColor(c, (startOrBefore ? 1 : -1) * colFactor); + generator.add(format(h) + " w\n"); + float ym1 = y1 + (h / 2); + generator.setColor(c, false); + generator.add(format(x1) + " " + format(ym1) + " m " + + format(x2) + " " + format(ym1) + " l S\n"); + } else { + c = ColorUtil.lightenColor(c, (startOrBefore ? 1 : -1) * colFactor); + generator.add(format(w) + " w\n"); + float xm1 = x1 + (w / 2); + generator.setColor(c, false); + generator.add(format(xm1) + " " + format(y1) + " m " + + format(xm1) + " " + format(y2) + " l S\n"); + } + break; + } + case Constants.EN_HIDDEN: + break; + default: + generator.setColor(col, false); + generator.add("[] 0 d "); + if (horz) { + generator.add(format(h) + " w\n"); + float ym = y1 + (h / 2); + generator.add(format(x1) + " " + format(ym) + " m " + + format(x2) + " " + format(ym) + " l S\n"); + } else { + generator.add(format(w) + " w\n"); + float xm = x1 + (w / 2); + generator.add(format(xm) + " " + format(y1) + " m " + + format(xm) + " " + format(y2) + " l S\n"); + } + } + } + + /** {@inheritDoc} */ + public void drawLine(Point start, Point end, + int width, Color color, RuleStyle style) { + if (start.y != end.y) { + //TODO Support arbitrary lines if necessary + throw new UnsupportedOperationException( + "Can only deal with horizontal lines right now"); + } + + saveGraphicsState(); + int half = width / 2; + int starty = start.y - half; + Rectangle boundingRect = new Rectangle(start.x, start.y - half, end.x - start.x, width); + switch (style.getEnumValue()) { + case Constants.EN_SOLID: + case Constants.EN_DASHED: + case Constants.EN_DOUBLE: + drawBorderLine(start.x, start.y - half, end.x, end.y + half, + true, true, style.getEnumValue(), color); + break; + case Constants.EN_DOTTED: + generator.clipRect(boundingRect); + //This displaces the dots to the right by half a dot's width + //TODO There's room for improvement here + generator.add("1 0 0 1 " + format(half) + " 0 cm\n"); + drawBorderLine(start.x, start.y - half, end.x, end.y + half, + true, true, style.getEnumValue(), color); + break; + case Constants.EN_GROOVE: + case Constants.EN_RIDGE: + generator.setColor(ColorUtil.lightenColor(color, 0.6f), true); + generator.add(format(start.x) + " " + format(starty) + " m\n"); + generator.add(format(end.x) + " " + format(starty) + " l\n"); + generator.add(format(end.x) + " " + format(starty + 2 * half) + " l\n"); + generator.add(format(start.x) + " " + format(starty + 2 * half) + " l\n"); + generator.add("h\n"); + generator.add("f\n"); + generator.setColor(color, true); + if (style == RuleStyle.GROOVE) { + generator.add(format(start.x) + " " + format(starty) + " m\n"); + generator.add(format(end.x) + " " + format(starty) + " l\n"); + generator.add(format(end.x) + " " + format(starty + half) + " l\n"); + generator.add(format(start.x + half) + " " + format(starty + half) + " l\n"); + generator.add(format(start.x) + " " + format(starty + 2 * half) + " l\n"); + } else { + generator.add(format(end.x) + " " + format(starty) + " m\n"); + generator.add(format(end.x) + " " + format(starty + 2 * half) + " l\n"); + generator.add(format(start.x) + " " + format(starty + 2 * half) + " l\n"); + generator.add(format(start.x) + " " + format(starty + half) + " l\n"); + generator.add(format(end.x - half) + " " + format(starty + half) + " l\n"); + } + generator.add("h\n"); + generator.add("f\n"); + break; + default: + throw new UnsupportedOperationException("rule style not supported"); + } + restoreGraphicsState(); + } + + static final String format(int coordinate) { + return format(coordinate / 1000f); + } + + static final String format(float coordinate) { + return PDFContentGenerator.format(coordinate); + } + + /** {@inheritDoc} */ + protected void moveTo(int x, int y) { + generator.add(format(x) + " " + format(y) + " m "); + } + + /** {@inheritDoc} */ + protected void lineTo(int x, int y) { + generator.add(format(x) + " " + format(y) + " l "); + } + + /** {@inheritDoc} */ + protected void closePath() { + generator.add("h "); + } + + /** {@inheritDoc} */ + protected void clip() { + generator.add("W\n" + "n\n"); + } + + /** {@inheritDoc} */ + protected void saveGraphicsState() { + generator.add("q\n"); + } + + /** {@inheritDoc} */ + protected void restoreGraphicsState() { + generator.add("Q\n"); + } + +} 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/PDFContentGenerator.java b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java new file mode 100644 index 000000000..4b0f35bec --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java @@ -0,0 +1,331 @@ +/* + * 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.Rectangle; +import java.awt.geom.AffineTransform; +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.fop.pdf.PDFColor; +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFFilterList; +import org.apache.fop.pdf.PDFNumber; +import org.apache.fop.pdf.PDFPaintingState; +import org.apache.fop.pdf.PDFResourceContext; +import org.apache.fop.pdf.PDFStream; +import org.apache.fop.pdf.PDFTextUtil; +import org.apache.fop.pdf.PDFXObject; + +/** + * Generator class encapsulating all object references and state necessary to generate a + * PDF content stream. + */ +public class PDFContentGenerator { + + /** Controls whether comments are written to the PDF stream. */ + protected static final boolean WRITE_COMMENTS = true; + + private PDFDocument document; + private OutputStream outputStream; + private PDFResourceContext resourceContext; + + /** the current stream to add PDF commands to */ + private PDFStream currentStream; + + /** drawing state */ + protected PDFPaintingState currentState = null; + /** Text generation utility holding the current font status */ + protected PDFTextUtil textutil; + + + /** + * Main constructor. Creates a new PDF stream and additional helper classes for text painting + * and state management. + * @param document the PDF document + * @param out the output stream the PDF document is generated to + * @param resourceContext the resource context + */ + public PDFContentGenerator(PDFDocument document, OutputStream out, + PDFResourceContext resourceContext) { + this.document = document; + this.outputStream = out; + this.resourceContext = resourceContext; + this.currentStream = document.getFactory() + .makeStream(PDFFilterList.CONTENT_FILTER, false); + this.textutil = new PDFTextUtil() { + protected void write(String code) { + currentStream.add(code); + } + }; + + this.currentState = new PDFPaintingState(); + } + + /** + * Returns the applicable resource context for the generator. + * @return the resource context + */ + public PDFDocument getDocument() { + return this.document; + } + + /** + * Returns the output stream the PDF document is written to. + * @return the output stream + */ + public OutputStream getOutputStream() { + return this.outputStream; + } + + /** + * Returns the applicable resource context for the generator. + * @return the resource context + */ + public PDFResourceContext getResourceContext() { + return this.resourceContext; + } + + /** + * Returns the {@code PDFStream} associated with this instance. + * @return the PDF stream + */ + public PDFStream getStream() { + return this.currentStream; + } + + /** + * Returns the {@code PDFState} associated with this instance. + * @return the PDF state + */ + public PDFPaintingState getState() { + return this.currentState; + } + + /** + * Returns the {@code PDFTextUtil} associated with this instance. + * @return the text utility + */ + public PDFTextUtil getTextUtil() { + return this.textutil; + } + + /** + * Flushes all queued PDF objects ready to be written to the output stream. + * @throws IOException if an error occurs while flushing the PDF objects + */ + public void flushPDFDoc() throws IOException { + this.document.output(this.outputStream); + } + + /** + * Writes out a comment. + * @param text text for the comment + */ + protected void comment(String text) { + if (WRITE_COMMENTS) { + currentStream.add("% " + text + "\n"); + } + } + + /** {@inheritDoc} */ + protected void saveGraphicsState() { + endTextObject(); + currentState.save(); + currentStream.add("q\n"); + } + + /** + * Restored the graphics state valid before the previous {@code #saveGraphicsState()}. + * @param popState true if the state should also be popped, false if only the PDF command + * should be issued + */ + protected void restoreGraphicsState(boolean popState) { + endTextObject(); + currentStream.add("Q\n"); + if (popState) { + currentState.restore(); + } + } + + /** {@inheritDoc} */ + protected void restoreGraphicsState() { + restoreGraphicsState(true); + } + + /** Indicates the beginning of a text object. */ + protected void beginTextObject() { + if (!textutil.isInTextObject()) { + textutil.beginTextObject(); + } + } + + /** Indicates the end of a text object. */ + protected void endTextObject() { + if (textutil.isInTextObject()) { + textutil.endTextObject(); + } + } + + /** + * Concatenates the given transformation matrix with the current one. + * @param transform the transformation matrix (in points) + */ + public void concatenate(AffineTransform transform) { + if (!transform.isIdentity()) { + currentState.concatenate(transform); + currentStream.add(CTMHelper.toPDFString(transform, false) + " cm\n"); + } + } + + /** + * Intersects the current clip region with the given rectangle. + * @param rect the clip rectangle + */ + public void clipRect(Rectangle rect) { + StringBuffer sb = new StringBuffer(); + sb.append(format(rect.x / 1000f)).append(' '); + sb.append(format(rect.y / 1000f)).append(' '); + sb.append(format(rect.width / 1000f)).append(' '); + sb.append(format(rect.height / 1000f)).append(" re W n\n"); + add(sb.toString()); + } + + /** + * Adds content to the stream. + * @param content the PDF content + */ + public void add(String content) { + currentStream.add(content); + } + + /** + * Formats a float value (normally coordinates in points) as Strings. + * @param value the value + * @return the formatted value + */ + public static final String format(float value) { + return PDFNumber.doubleOut(value); + } + + /** + * Sets the current line width in points. + * @param width line width in points + */ + public void updateLineWidth(float width) { + if (currentState.setLineWidth(width)) { + //Only write if value has changed WRT the current line width + currentStream.add(format(width) + " w\n"); + } + } + + /** + * 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; + }*/ + + /** + * 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.document, col); + stream.add(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 + */ + public void setColor(Color col, boolean fill) { + setColor(col, fill, getStream()); + } + + /** + * 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, if null, the code is + * written to the current stream. + */ + protected void setColor(Color col, boolean fill, StringBuffer pdf) { + if (pdf != null) { + PDFColor color = new PDFColor(this.document, col); + pdf.append(color.getColorSpaceOut(fill)); + } else { + setColor(col, fill, this.currentStream); + } + } + + /** + * 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 + * @param pdf StringBuffer to write the PDF code to, if null, the code is + * written to the current stream. + */ + public void updateColor(Color col, boolean fill, StringBuffer pdf) { + if (col == null) { + return; + } + boolean update = false; + if (fill) { + update = getState().setBackColor(col); + } else { + update = getState().setColor(col); + } + + if (update) { + setColor(col, fill, pdf); + } + } + + /** + * Places a previously registered image at a certain place on the page. + * @param x X coordinate + * @param y Y coordinate + * @param w width for image + * @param h height for image + * @param xobj the image XObject + */ + public void placeImage(float x, float y, float w, float h, PDFXObject xobj) { + saveGraphicsState(); + add(format(w) + " 0 0 " + + format(-h) + " " + + format(x) + " " + + format(y + h) + + " cm\n" + xobj.getName() + " Do\n"); + restoreGraphicsState(); + } + + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java new file mode 100644 index 000000000..1358b1c5e --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java @@ -0,0 +1,296 @@ +/* + * 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.Dimension; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.xmp.Metadata; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.apps.MimeConstants; +import org.apache.fop.fo.extensions.xmp.XMPMetadata; +import org.apache.fop.pdf.PDFAction; +import org.apache.fop.pdf.PDFAnnotList; +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFOutline; +import org.apache.fop.pdf.PDFPage; +import org.apache.fop.pdf.PDFReference; +import org.apache.fop.pdf.PDFResourceContext; +import org.apache.fop.pdf.PDFResources; +import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler; +import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator; +import org.apache.fop.render.intermediate.IFException; +import org.apache.fop.render.intermediate.IFPainter; +import org.apache.fop.render.intermediate.extensions.AbstractAction; +import org.apache.fop.render.intermediate.extensions.Bookmark; +import org.apache.fop.render.intermediate.extensions.BookmarkTree; +import org.apache.fop.render.intermediate.extensions.GoToXYAction; +import org.apache.fop.render.intermediate.extensions.NamedDestination; + +/** + * {@code IFDocumentHandler} implementation that produces PDF. + */ +public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { + + /** logging instance */ + private static Log log = LogFactory.getLog(PDFDocumentHandler.class); + + /** 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 content generator */ + protected PDFContentGenerator generator; + + /** the current annotation list to add annotations to */ + protected PDFResourceContext currentContext; + + /** 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; + + /** Used for bookmarks/outlines. */ + protected Map pageReferences = new java.util.HashMap(); + + /** + * Default constructor. + */ + public PDFDocumentHandler() { + } + + /** {@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); + } + + /** {@inheritDoc} */ + public IFDocumentHandlerConfigurator getConfigurator() { + return new PDFRendererConfigurator(getUserAgent()); + } + + 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 endDocumentHeader() throws IFException { + pdfUtil.generateDefaultXMPMetadata(); + } + + /** {@inheritDoc} */ + public void endDocument() throws IFException { + try { + pdfDoc.getResources().addFonts(pdfDoc, fontInfo); + pdfDoc.outputTrailer(this.outputStream); + + this.pdfDoc = null; + + pdfResources = null; + this.generator = null; + currentContext = null; + currentPage = null; + } 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, String pageMasterName, 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(); + this.pageReferences.put(new Integer(index), new PageReference(currentPage, size)); + + this.generator = new PDFContentGenerator(this.pdfDoc, this.outputStream, this.currentPage); + // Transform the PDF's default coordinate system (0,0 at lower left) to the PDFPainter's + AffineTransform basicPageTransform = new AffineTransform(1, 0, 0, -1, 0, + size.height / 1000f); + generator.concatenate(basicPageTransform); + + } + + /** {@inheritDoc} */ + public IFPainter startPageContent() throws IFException { + return new PDFPainter(this); + } + + /** {@inheritDoc} */ + public void endPageContent() throws IFException { + //nop + } + + /** {@inheritDoc} */ + public void endPage() throws IFException { + try { + this.pdfDoc.registerObject(generator.getStream()); + currentPage.setContents(generator.getStream()); + PDFAnnotList annots = currentPage.getAnnotations(); + if (annots != null) { + this.pdfDoc.addObject(annots); + } + this.pdfDoc.addObject(currentPage); + this.generator.flushPDFDoc(); + this.generator = null; + } catch (IOException ioe) { + throw new IFException("I/O error in endPage()", ioe); + } + } + + private void renderBookmarkTree(BookmarkTree tree) { + Iterator iter = tree.getBookmarks().iterator(); + while (iter.hasNext()) { + Bookmark b = (Bookmark)iter.next(); + renderBookmark(b, null); + } + } + + private void renderBookmark(Bookmark bookmark, PDFOutline parent) { + if (parent == null) { + parent = pdfDoc.getOutlineRoot(); + } + PDFAction action = getAction(bookmark.getAction()); + PDFOutline pdfOutline = pdfDoc.getFactory().makeOutline(parent, + bookmark.getTitle(), action, bookmark.isShown()); + Iterator iter = bookmark.getChildBookmarks().iterator(); + while (iter.hasNext()) { + Bookmark b = (Bookmark)iter.next(); + renderBookmark(b, pdfOutline); + } + } + + private void renderNamedDestination(NamedDestination destination) { + PDFAction action = getAction(destination.getAction()); + pdfDoc.getFactory().makeDestination( + destination.getName(), action.makeReference()); + } + + private PDFAction getAction(AbstractAction action) { + if (action instanceof GoToXYAction) { + GoToXYAction a = (GoToXYAction)action; + PageReference pageRef = (PageReference)this.pageReferences.get( + new Integer(a.getPageIndex())); + //Convert target location from millipoints to points and adjust for different + //page origin + Point2D p2d = new Point2D.Double( + a.getTargetLocation().x / 1000.0, + (pageRef.pageDimension.height - a.getTargetLocation().y) / 1000.0); + return pdfDoc.getFactory().getPDFGoTo(pageRef.pageRef.toString(), p2d); + } else { + throw new UnsupportedOperationException("Unsupported action type: " + + action + " (" + action.getClass().getName() + ")"); + } + } + + /** {@inheritDoc} */ + public void handleExtensionObject(Object extension) throws IFException { + if (extension instanceof XMPMetadata) { + pdfUtil.renderXMPMetadata((XMPMetadata)extension); + } else if (extension instanceof Metadata) { + XMPMetadata wrapper = new XMPMetadata(((Metadata)extension)); + pdfUtil.renderXMPMetadata(wrapper); + } else if (extension instanceof BookmarkTree) { + renderBookmarkTree((BookmarkTree)extension); + } else if (extension instanceof NamedDestination) { + renderNamedDestination((NamedDestination)extension); + } else { + log.debug("Don't know how to handle extension object. Ignoring: " + + extension + " (" + extension.getClass().getName() + ")"); + } + } + + private static final class PageReference { + + private PDFReference pageRef; + private Dimension pageDimension; + + private PageReference(PDFPage page, Dimension dim) { + this.pageRef = page.makeReference(); + this.pageDimension = new Dimension(dim); + } + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentHandlerMaker.java b/src/java/org/apache/fop/render/pdf/PDFDocumentHandlerMaker.java new file mode 100644 index 000000000..88ae60d2c --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentHandlerMaker.java @@ -0,0 +1,58 @@ +/* + * 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.AbstractIFDocumentHandlerMaker; +import org.apache.fop.render.intermediate.IFDocumentHandler; +import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator; + +/** + * Intermediate format document handler factory for PDF output. + */ +public class PDFDocumentHandlerMaker extends AbstractIFDocumentHandlerMaker { + + //TODO Revert to normal MIME after stabilization! + private static final String[] MIMES = new String[] {MimeConstants.MIME_PDF + ";mode=painter"}; + + /** {@inheritDoc} */ + public IFDocumentHandler makeIFDocumentHandler(FOUserAgent ua) { + PDFDocumentHandler handler = new PDFDocumentHandler(); + handler.setUserAgent(ua); + return handler; + } + + /** {@inheritDoc} */ + public boolean needsOutputStream() { + return true; + } + + /** {@inheritDoc} */ + public String[] getSupportedMimeTypes() { + return MIMES; + } + + /** {@inheritDoc} */ + public IFDocumentHandlerConfigurator getConfigurator(FOUserAgent userAgent) { + return new PDFRendererConfigurator(userAgent); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml b/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml index fd57d5099..420f16a09 100644 --- a/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml +++ b/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml @@ -1,4 +1,3 @@ -<?xml version="1.0" encoding="UTF-8"?> -<catalogue xml:lang="en"> +<?xml version="1.0" encoding="UTF-8"?><catalogue xml:lang="en"> <message key="org.apache.fop.render.pdf.PDFEventProducer.nonFullyResolvedLinkTargets">{count} link target{count,equals,1,,s} could not be fully resolved and now point{count,equals,1,,s} to the top of the page or {count,equals,1,is,are} dysfunctional.</message> </catalogue> diff --git a/src/java/org/apache/fop/render/pdf/PDFGraphics2DAdapter.java b/src/java/org/apache/fop/render/pdf/PDFGraphics2DAdapter.java index 01d863e6a..102c1ab45 100644 --- a/src/java/org/apache/fop/render/pdf/PDFGraphics2DAdapter.java +++ b/src/java/org/apache/fop/render/pdf/PDFGraphics2DAdapter.java @@ -55,6 +55,7 @@ public class PDFGraphics2DAdapter extends AbstractGraphics2DAdapter { RendererContext context, int x, int y, int width, int height) throws IOException { + PDFContentGenerator generator = renderer.getGenerator(); PDFSVGHandler.PDFInfo pdfInfo = PDFSVGHandler.getPDFInfo(context); float fwidth = width / 1000f; float fheight = height / 1000f; @@ -69,16 +70,17 @@ public class PDFGraphics2DAdapter extends AbstractGraphics2DAdapter { float sx = pdfInfo.paintAsBitmap ? 1.0f : (fwidth / (float)imw); float sy = pdfInfo.paintAsBitmap ? 1.0f : (fheight / (float)imh); - renderer.saveGraphicsState(); - renderer.setColor(Color.black, false, null); - renderer.setColor(Color.black, true, null); + generator.comment("G2D start"); + generator.saveGraphicsState(); + generator.updateColor(Color.black, false, null); + generator.updateColor(Color.black, true, null); //TODO Clip to the image area. // transform so that the coordinates (0,0) is from the top left // and positive is down and to the right. (0,0) is where the // viewBox puts it. - renderer.currentStream.add(sx + " 0 0 " + sy + " " + fx + " " + generator.add(sx + " 0 0 " + sy + " " + fx + " " + fy + " cm\n"); @@ -95,8 +97,8 @@ public class PDFGraphics2DAdapter extends AbstractGraphics2DAdapter { AffineTransform transform = new AffineTransform(); transform.translate(fx, fy); - pdfInfo.pdfPaintingState.concatenate(transform); - graphics.setPaintingState(pdfInfo.pdfPaintingState); + generator.getState().concatenate(transform); + graphics.setPaintingState(generator.getState()); graphics.setOutputStream(pdfInfo.outputStream); if (pdfInfo.paintAsBitmap) { @@ -113,8 +115,9 @@ public class PDFGraphics2DAdapter extends AbstractGraphics2DAdapter { painter.paint(graphics, area); } - pdfInfo.currentStream.add(graphics.getString()); - renderer.restoreGraphicsState(); + generator.add(graphics.getString()); + generator.restoreGraphicsState(); + generator.comment("G2D end"); } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandler.java b/src/java/org/apache/fop/render/pdf/PDFImageHandler.java index 6343d0c50..934d306b9 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandler.java @@ -23,15 +23,16 @@ import java.awt.Point; import java.awt.Rectangle; import java.io.IOException; +import org.apache.xmlgraphics.image.loader.Image; + import org.apache.fop.pdf.PDFXObject; -import org.apache.fop.render.ImageHandler; +import org.apache.fop.render.ImageHandlerBase; import org.apache.fop.render.RendererContext; -import org.apache.xmlgraphics.image.loader.Image; /** * This interface is used for handling all sorts of image type for PDF output. */ -public interface PDFImageHandler extends ImageHandler { +public interface PDFImageHandler extends ImageHandlerBase { /** * Generates the PDF objects for the given {@link Image} instance. If the handler generates diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java index 3e4a9b354..18717809d 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java @@ -19,20 +19,29 @@ package org.apache.fop.render.pdf; +import java.awt.Color; +import java.awt.Dimension; import java.awt.Point; import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; import java.io.IOException; -import org.apache.fop.pdf.PDFXObject; -import org.apache.fop.render.RendererContext; import org.apache.xmlgraphics.image.loader.Image; import org.apache.xmlgraphics.image.loader.ImageFlavor; import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D; +import org.apache.fop.pdf.PDFXObject; +import org.apache.fop.render.AbstractImageHandlerGraphics2D; +import org.apache.fop.render.RendererContext; +import org.apache.fop.render.RenderingContext; +import org.apache.fop.svg.PDFGraphics2D; + /** * PDFImageHandler implementation which handles Graphics2D images. */ -public class PDFImageHandlerGraphics2D implements PDFImageHandler { +public class PDFImageHandlerGraphics2D extends AbstractImageHandlerGraphics2D + implements PDFImageHandler { private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { ImageFlavor.GRAPHICS2D, @@ -43,13 +52,74 @@ public class PDFImageHandlerGraphics2D implements PDFImageHandler { Point origin, Rectangle pos) throws IOException { PDFRenderer renderer = (PDFRenderer)context.getRenderer(); + /* ImageGraphics2D imageG2D = (ImageGraphics2D)image; renderer.getGraphics2DAdapter().paintImage(imageG2D.getGraphics2DImagePainter(), context, origin.x + pos.x, origin.y + pos.y, pos.width, pos.height); + */ + PDFRenderingContext pdfContext = new PDFRenderingContext( + context.getUserAgent(), + renderer.getGenerator(), + renderer.currentPage, + renderer.getFontInfo()); + Rectangle effPos = new Rectangle(origin.x + pos.x, origin.y + pos.y, pos.width, pos.height); + handleImage(pdfContext, image, effPos); return null; } /** {@inheritDoc} */ + public void handleImage(RenderingContext context, Image image, Rectangle pos) + throws IOException { + PDFRenderingContext pdfContext = (PDFRenderingContext)context; + PDFContentGenerator generator = pdfContext.getGenerator(); + ImageGraphics2D imageG2D = (ImageGraphics2D)image; + float fwidth = pos.width / 1000f; + float fheight = pos.height / 1000f; + float fx = pos.x / 1000f; + float fy = pos.y / 1000f; + + // get the 'width' and 'height' attributes of the SVG document + Dimension dim = image.getInfo().getSize().getDimensionMpt(); + float imw = (float)dim.getWidth() / 1000f; + float imh = (float)dim.getHeight() / 1000f; + + float sx = fwidth / (float)imw; + float sy = fheight / (float)imh; + + generator.comment("G2D start"); + generator.saveGraphicsState(); + generator.updateColor(Color.black, false, null); + generator.updateColor(Color.black, true, null); + + //TODO Clip to the image area. + + // transform so that the coordinates (0,0) is from the top left + // and positive is down and to the right. (0,0) is where the + // viewBox puts it. + generator.add(sx + " 0 0 " + sy + " " + fx + " " + fy + " cm\n"); + + final boolean textAsShapes = false; + PDFGraphics2D graphics = new PDFGraphics2D(textAsShapes, + pdfContext.getFontInfo(), generator.getDocument(), + generator.getResourceContext(), pdfContext.getPage().referencePDF(), + "", 0.0f); + graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); + + AffineTransform transform = new AffineTransform(); + transform.translate(fx, fy); + generator.getState().concatenate(transform); + graphics.setPaintingState(generator.getState()); + graphics.setOutputStream(generator.getOutputStream()); + + Rectangle2D area = new Rectangle2D.Double(0.0, 0.0, imw, imh); + imageG2D.getGraphics2DImagePainter().paint(graphics, area); + + generator.add(graphics.getString()); + generator.restoreGraphicsState(); + generator.comment("G2D end"); + } + + /** {@inheritDoc} */ public int getPriority() { return 200; } @@ -64,4 +134,10 @@ public class PDFImageHandlerGraphics2D implements PDFImageHandler { return FLAVORS; } + /** {@inheritDoc} */ + public boolean isCompatible(RenderingContext targetContext, Image image) { + return (image == null || image instanceof ImageGraphics2D) + && targetContext instanceof PDFRenderingContext; + } + } diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawCCITTFax.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawCCITTFax.java index 1ba498ff0..75b1d356e 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawCCITTFax.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawCCITTFax.java @@ -23,19 +23,23 @@ import java.awt.Point; import java.awt.Rectangle; import java.io.IOException; +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.impl.ImageRawCCITTFax; + import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFImage; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFXObject; +import org.apache.fop.render.ImageHandler; import org.apache.fop.render.RendererContext; -import org.apache.xmlgraphics.image.loader.Image; -import org.apache.xmlgraphics.image.loader.ImageFlavor; -import org.apache.xmlgraphics.image.loader.impl.ImageRawCCITTFax; +import org.apache.fop.render.RenderingContext; /** - * PDFImageHandler implementation which handles CCITT encoded images (CCITT fax group 3/4). + * Image handler implementation which handles CCITT encoded images (CCITT fax group 3/4) + * for PDF output. */ -public class PDFImageHandlerRawCCITTFax implements PDFImageHandler { +public class PDFImageHandlerRawCCITTFax implements PDFImageHandler, ImageHandler { private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { ImageFlavor.RAW_CCITTFAX, @@ -65,6 +69,24 @@ public class PDFImageHandlerRawCCITTFax implements PDFImageHandler { } /** {@inheritDoc} */ + public void handleImage(RenderingContext context, Image image, Rectangle pos) + throws IOException { + PDFRenderingContext pdfContext = (PDFRenderingContext)context; + PDFContentGenerator generator = pdfContext.getGenerator(); + ImageRawCCITTFax ccitt = (ImageRawCCITTFax)image; + + PDFImage pdfimage = new ImageRawCCITTFaxAdapter(ccitt, image.getInfo().getOriginalURI()); + PDFXObject xobj = generator.getDocument().addImage( + generator.getResourceContext(), pdfimage); + + float x = (float)pos.getX() / 1000f; + float y = (float)pos.getY() / 1000f; + float w = (float)pos.getWidth() / 1000f; + float h = (float)pos.getHeight() / 1000f; + generator.placeImage(x, y, w, h, xobj); + } + + /** {@inheritDoc} */ public int getPriority() { return 100; } @@ -79,4 +101,10 @@ public class PDFImageHandlerRawCCITTFax implements PDFImageHandler { return FLAVORS; } + /** {@inheritDoc} */ + public boolean isCompatible(RenderingContext targetContext, Image image) { + return (image == null || image instanceof ImageRawCCITTFax) + && targetContext instanceof PDFRenderingContext; + } + } diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java index 41a2d7565..d47d5a439 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java @@ -23,19 +23,22 @@ import java.awt.Point; import java.awt.Rectangle; import java.io.IOException; +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.impl.ImageRawJPEG; + import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFImage; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFXObject; +import org.apache.fop.render.ImageHandler; import org.apache.fop.render.RendererContext; -import org.apache.xmlgraphics.image.loader.Image; -import org.apache.xmlgraphics.image.loader.ImageFlavor; -import org.apache.xmlgraphics.image.loader.impl.ImageRawJPEG; +import org.apache.fop.render.RenderingContext; /** - * PDFImageHandler implementation which handles raw JPEG images. + * Image handler implementation which handles raw JPEG images for PDF output. */ -public class PDFImageHandlerRawJPEG implements PDFImageHandler { +public class PDFImageHandlerRawJPEG implements PDFImageHandler, ImageHandler { private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { ImageFlavor.RAW_JPEG, @@ -65,6 +68,24 @@ public class PDFImageHandlerRawJPEG implements PDFImageHandler { } /** {@inheritDoc} */ + public void handleImage(RenderingContext context, Image image, Rectangle pos) + throws IOException { + PDFRenderingContext pdfContext = (PDFRenderingContext)context; + PDFContentGenerator generator = pdfContext.getGenerator(); + ImageRawJPEG imageJPEG = (ImageRawJPEG)image; + + PDFImage pdfimage = new ImageRawJPEGAdapter(imageJPEG, image.getInfo().getOriginalURI()); + PDFXObject xobj = generator.getDocument().addImage( + generator.getResourceContext(), pdfimage); + + float x = (float)pos.getX() / 1000f; + float y = (float)pos.getY() / 1000f; + float w = (float)pos.getWidth() / 1000f; + float h = (float)pos.getHeight() / 1000f; + generator.placeImage(x, y, w, h, xobj); + } + + /** {@inheritDoc} */ public int getPriority() { return 100; } @@ -79,4 +100,10 @@ public class PDFImageHandlerRawJPEG implements PDFImageHandler { return FLAVORS; } + /** {@inheritDoc} */ + public boolean isCompatible(RenderingContext targetContext, Image image) { + return (image == null || image instanceof ImageRawJPEG) + && targetContext instanceof PDFRenderingContext; + } + } diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java index 268ff8862..3e57c7216 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java @@ -23,19 +23,22 @@ import java.awt.Point; import java.awt.Rectangle; import java.io.IOException; +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.impl.ImageRendered; + import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFImage; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFXObject; +import org.apache.fop.render.ImageHandler; import org.apache.fop.render.RendererContext; -import org.apache.xmlgraphics.image.loader.Image; -import org.apache.xmlgraphics.image.loader.ImageFlavor; -import org.apache.xmlgraphics.image.loader.impl.ImageRendered; +import org.apache.fop.render.RenderingContext; /** - * PDFImageHandler implementation which handles RenderedImage instances. + * Image handler implementation which handles RenderedImage instances for PDF output. */ -public class PDFImageHandlerRenderedImage implements PDFImageHandler { +public class PDFImageHandlerRenderedImage implements PDFImageHandler, ImageHandler { private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { ImageFlavor.BUFFERED_IMAGE, @@ -66,6 +69,24 @@ public class PDFImageHandlerRenderedImage implements PDFImageHandler { } /** {@inheritDoc} */ + public void handleImage(RenderingContext context, Image image, Rectangle pos) + throws IOException { + PDFRenderingContext pdfContext = (PDFRenderingContext)context; + PDFContentGenerator generator = pdfContext.getGenerator(); + ImageRendered imageRend = (ImageRendered)image; + + PDFImage pdfimage = new ImageRenderedAdapter(imageRend, image.getInfo().getOriginalURI()); + PDFXObject xobj = generator.getDocument().addImage( + generator.getResourceContext(), pdfimage); + + float x = (float)pos.getX() / 1000f; + float y = (float)pos.getY() / 1000f; + float w = (float)pos.getWidth() / 1000f; + float h = (float)pos.getHeight() / 1000f; + generator.placeImage(x, y, w, h, xobj); + } + + /** {@inheritDoc} */ public int getPriority() { return 300; } @@ -80,4 +101,10 @@ public class PDFImageHandlerRenderedImage implements PDFImageHandler { return FLAVORS; } + /** {@inheritDoc} */ + public boolean isCompatible(RenderingContext targetContext, Image image) { + return (image == null || image instanceof ImageRendered) + && targetContext instanceof PDFRenderingContext; + } + } diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java new file mode 100644 index 000000000..d1b7aa986 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java @@ -0,0 +1,200 @@ +/* + * 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.Rectangle; +import java.awt.geom.AffineTransform; +import java.io.IOException; + +import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.bridge.GVTBuilder; +import org.apache.batik.dom.svg.SVGDOMImplementation; +import org.apache.batik.gvt.GraphicsNode; +import org.apache.batik.util.SVGConstants; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.image.loader.batik.BatikImageFlavors; +import org.apache.fop.render.ImageHandler; +import org.apache.fop.render.RenderingContext; +import org.apache.fop.svg.PDFAElementBridge; +import org.apache.fop.svg.PDFBridgeContext; +import org.apache.fop.svg.PDFGraphics2D; +import org.apache.fop.svg.SVGEventProducer; +import org.apache.fop.svg.SVGUserAgent; + +/** + * Image Handler implementation which handles SVG images. + */ +public class PDFImageHandlerSVG implements ImageHandler { + + /** logging instance */ + private static Log log = LogFactory.getLog(PDFImageHandlerSVG.class); + + /** {@inheritDoc} */ + public void handleImage(RenderingContext context, Image image, Rectangle pos) + throws IOException { + PDFRenderingContext pdfContext = (PDFRenderingContext)context; + PDFContentGenerator generator = pdfContext.getGenerator(); + ImageXMLDOM imageSVG = (ImageXMLDOM)image; + + FOUserAgent userAgent = context.getUserAgent(); + final float deviceResolution = userAgent.getTargetResolution(); + if (log.isDebugEnabled()) { + log.debug("Generating SVG at " + deviceResolution + "dpi."); + } + + final float uaResolution = userAgent.getSourceResolution(); + SVGUserAgent ua = new SVGUserAgent(userAgent, new AffineTransform()); + + //Scale for higher resolution on-the-fly images from Batik + double s = uaResolution / deviceResolution; + AffineTransform resolutionScaling = new AffineTransform(); + resolutionScaling.scale(s, s); + + GVTBuilder builder = new GVTBuilder(); + + //Controls whether text painted by Batik is generated using text or path operations + boolean strokeText = false; + //TODO connect with configuration elsewhere. + + BridgeContext ctx = new PDFBridgeContext(ua, + (strokeText ? null : pdfContext.getFontInfo()), + userAgent.getFactory().getImageManager(), + userAgent.getImageSessionContext(), + new AffineTransform()); + + GraphicsNode root; + try { + root = builder.build(ctx, imageSVG.getDocument()); + builder = null; + } catch (Exception e) { + SVGEventProducer eventProducer = SVGEventProducer.Provider.get( + context.getUserAgent().getEventBroadcaster()); + eventProducer.svgNotBuilt(this, e, image.getInfo().getOriginalURI()); + return; + } + // get the 'width' and 'height' attributes of the SVG document + float w = (float)ctx.getDocumentSize().getWidth() * 1000f; + float h = (float)ctx.getDocumentSize().getHeight() * 1000f; + + float sx = pos.width / (float)w; + float sy = pos.height / (float)h; + + //Scaling and translation for the bounding box of the image + AffineTransform scaling = new AffineTransform( + sx, 0, 0, sy, pos.x / 1000f, pos.y / 1000f); + + //Transformation matrix that establishes the local coordinate system for the SVG graphic + //in relation to the current coordinate system + AffineTransform imageTransform = new AffineTransform(); + imageTransform.concatenate(scaling); + imageTransform.concatenate(resolutionScaling); + + /* + * Clip to the svg area. + * Note: To have the svg overlay (under) a text area then use + * an fo:block-container + */ + generator.comment("SVG setup"); + generator.saveGraphicsState(); + generator.setColor(Color.black, false); + generator.setColor(Color.black, true); + + if (!scaling.isIdentity()) { + generator.comment("viewbox"); + generator.add(CTMHelper.toPDFString(scaling, false) + " cm\n"); + } + + //SVGSVGElement svg = ((SVGDocument)doc).getRootElement(); + + PDFGraphics2D graphics = new PDFGraphics2D(true, pdfContext.getFontInfo(), + generator.getDocument(), + generator.getResourceContext(), pdfContext.getPage().referencePDF(), + "", 0); + graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); + + if (!resolutionScaling.isIdentity()) { + generator.comment("resolution scaling for " + uaResolution + + " -> " + deviceResolution + "\n"); + generator.add( + CTMHelper.toPDFString(resolutionScaling, false) + " cm\n"); + graphics.scale(1 / s, 1 / s); + } + + generator.comment("SVG start"); + + //Save state and update coordinate system for the SVG image + generator.getState().save(); + generator.getState().concatenate(imageTransform); + + //Now that we have the complete transformation matrix for the image, we can update the + //transformation matrix for the AElementBridge. + PDFAElementBridge aBridge = (PDFAElementBridge)ctx.getBridge( + SVGDOMImplementation.SVG_NAMESPACE_URI, SVGConstants.SVG_A_TAG); + aBridge.getCurrentTransform().setTransform(generator.getState().getTransform()); + + graphics.setPaintingState(generator.getState()); + graphics.setOutputStream(generator.getOutputStream()); + try { + root.paint(graphics); + generator.add(graphics.getString()); + } catch (Exception e) { + SVGEventProducer eventProducer = SVGEventProducer.Provider.get( + context.getUserAgent().getEventBroadcaster()); + eventProducer.svgRenderingError(this, e, image.getInfo().getOriginalURI()); + } + generator.getState().restore(); + generator.restoreGraphicsState(); + generator.comment("SVG end"); + } + + /** {@inheritDoc} */ + public int getPriority() { + return 400; + } + + /** {@inheritDoc} */ + public Class getSupportedImageClass() { + return ImageXMLDOM.class; + } + + /** {@inheritDoc} */ + public ImageFlavor[] getSupportedImageFlavors() { + return new ImageFlavor[] { + BatikImageFlavors.SVG_DOM + }; + } + + /** {@inheritDoc} */ + public boolean isCompatible(RenderingContext targetContext, Image image) { + return (image == null + || (image instanceof ImageXMLDOM + && image.getFlavor().isCompatible(BatikImageFlavors.SVG_DOM))) + && targetContext instanceof PDFRenderingContext; + } + +} 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..8e29ded4c --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFPainter.java @@ -0,0 +1,324 @@ +/* + * 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.Point; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.io.IOException; +import java.util.Map; + +import org.w3c.dom.Document; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fonts.Font; +import org.apache.fop.fonts.FontInfo; +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.PDFDocument; +import org.apache.fop.pdf.PDFNumber; +import org.apache.fop.pdf.PDFTextUtil; +import org.apache.fop.pdf.PDFXObject; +import org.apache.fop.render.RenderingContext; +import org.apache.fop.render.intermediate.AbstractIFPainter; +import org.apache.fop.render.intermediate.IFException; +import org.apache.fop.render.intermediate.IFState; +import org.apache.fop.traits.BorderProps; +import org.apache.fop.traits.RuleStyle; +import org.apache.fop.util.CharUtilities; + +/** + * IFPainter implementation that produces PDF. + */ +public class PDFPainter extends AbstractIFPainter { + + /** logging instance */ + private static Log log = LogFactory.getLog(PDFPainter.class); + + private PDFDocumentHandler documentHandler; + + /** The current content generator */ + protected PDFContentGenerator generator; + + private PDFBorderPainter borderPainter; + + /** + * Default constructor. + * @param documentHandler the parent document handler + */ + public PDFPainter(PDFDocumentHandler documentHandler) { + super(); + this.documentHandler = documentHandler; + this.generator = documentHandler.generator; + this.borderPainter = new PDFBorderPainter(this.generator); + this.state = IFState.create(); + } + + /** {@inheritDoc} */ + protected FOUserAgent getUserAgent() { + return this.documentHandler.getUserAgent(); + } + + PDFRenderingUtil getPDFUtil() { + return this.documentHandler.pdfUtil; + } + + PDFDocument getPDFDoc() { + return this.documentHandler.pdfDoc; + } + + FontInfo getFontInfo() { + return this.documentHandler.getFontInfo(); + } + + /** {@inheritDoc} */ + public void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect) + throws IFException { + generator.saveGraphicsState(); + generator.concatenate(toPoints(transform)); + if (clipRect != null) { + clipRect(clipRect); + } + } + + /** {@inheritDoc} */ + public void endViewport() throws IFException { + generator.restoreGraphicsState(); + } + + /** {@inheritDoc} */ + public void startGroup(AffineTransform transform) throws IFException { + generator.saveGraphicsState(); + generator.concatenate(toPoints(transform)); + } + + /** {@inheritDoc} */ + public void endGroup() throws IFException { + generator.restoreGraphicsState(); + } + + /** {@inheritDoc} */ + public void drawImage(String uri, Rectangle rect, Map foreignAttributes) throws IFException { + PDFXObject xobject = getPDFDoc().getXObject(uri); + if (xobject != null) { + placeImage(rect, xobject); + return; + } + + drawImageUsingURI(uri, rect); + + flushPDFDoc(); + } + + /** {@inheritDoc} */ + protected RenderingContext createRenderingContext() { + PDFRenderingContext pdfContext = new PDFRenderingContext( + getUserAgent(), generator, this.documentHandler.currentPage, getFontInfo()); + return pdfContext; + } + + /** + * Places a previously registered image at a certain place on the page. + * @param x X coordinate + * @param y Y coordinate + * @param w width for image + * @param h height for image + * @param xobj the image XObject + */ + private void placeImage(Rectangle rect, PDFXObject xobj) { + generator.saveGraphicsState(); + generator.add(format(rect.width) + " 0 0 " + + format(-rect.height) + " " + + format(rect.x) + " " + + format(rect.y + rect.height ) + + " cm " + xobj.getName() + " Do\n"); + generator.restoreGraphicsState(); + } + + /** {@inheritDoc} */ + public void drawImage(Document doc, Rectangle rect, Map foreignAttributes) throws IFException { + drawImageUsingDocument(doc, rect); + + flushPDFDoc(); + } + + private void flushPDFDoc() throws IFException { + // output new data + try { + generator.flushPDFDoc(); + } catch (IOException ioe) { + throw new IFException("I/O error flushing the PDF document", ioe); + } + } + + /** + * Formats a integer value (normally coordinates in millipoints) to a String. + * @param value the value (in millipoints) + * @return the formatted value + */ + protected static String format(int value) { + return PDFNumber.doubleOut(value / 1000f); + } + + /** {@inheritDoc} */ + public void clipRect(Rectangle rect) throws IFException { + generator.endTextObject(); + generator.clipRect(rect); + } + + /** {@inheritDoc} */ + public void fillRect(Rectangle rect, Paint fill) throws IFException { + if (fill == null) { + return; + } + if (rect.width != 0 && rect.height != 0) { + generator.endTextObject(); + if (fill != null) { + if (fill instanceof Color) { + generator.updateColor((Color)fill, true, null); + } else { + throw new UnsupportedOperationException("Non-Color paints 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"); + } + /* Removed from method signature as it is currently not used + if (stroke != null) { + sb.append(" S"); + }*/ + sb.append('\n'); + generator.add(sb.toString()); + } + } + + /** {@inheritDoc} */ + public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after, + BorderProps start, BorderProps end) throws IFException { + if (before != null || after != null || start != null || end != null) { + generator.endTextObject(); + this.borderPainter.drawBorders(rect, before, after, start, end); + } + } + + /** {@inheritDoc} */ + public void drawLine(Point start, Point end, int width, Color color, RuleStyle style) + throws IFException { + generator.endTextObject(); + this.borderPainter.drawLine(start, end, width, color, style); + } + + private Typeface getTypeface(String fontName) { + if (fontName == null) { + throw new NullPointerException("fontName must not be null"); + } + Typeface tf = (Typeface)getFontInfo().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 + generator.updateColor(state.getTextColor(), true, null); + generator.beginTextObject(); + FontTriplet triplet = new FontTriplet( + state.getFontFamily(), state.getFontStyle(), state.getFontWeight()); + //TODO Ignored: state.getFontVariant() + //TODO Opportunity for font caching if font state is more heavily used + String fontKey = getFontInfo().getInternalFontKey(triplet); + int sizeMillipoints = state.getFontSize(); + float fontSize = sizeMillipoints / 1000f; + + // 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 = getFontInfo().getFontInstance(triplet, sizeMillipoints); + String fontName = font.getFontName(); + + PDFTextUtil textutil = generator.getTextUtil(); + 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] / 10f); + } + 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); + } + } + } else { + if (CharUtilities.isFixedWidthSpace(orgChar)) { + //Fixed width space are rendered as spaces so copy/paste works in a reader + ch = font.mapChar(CharUtilities.SPACE); + int spaceDiff = font.getCharWidth(ch) - font.getCharWidth(orgChar); + glyphAdjust = -spaceDiff; + } else { + ch = font.mapChar(orgChar); + } + } + textutil.writeTJMappedChar(ch); + + if (dx != null && i < dxl - 1) { + glyphAdjust += dx[i + 1]; + } + + if (glyphAdjust != 0) { + textutil.adjustGlyphTJ(-glyphAdjust / fontSize); + } + + } + textutil.writeTJ(); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java index e31f1eaea..e0e1bab69 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -23,23 +23,23 @@ 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.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +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.commons.io.IOUtils; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.MimeConstants; @@ -64,7 +64,6 @@ import org.apache.fop.area.inline.TextArea; import org.apache.fop.area.inline.WordArea; import org.apache.fop.datatypes.URISpecification; import org.apache.fop.events.ResourceEventProducer; -import org.apache.fop.fo.Constants; import org.apache.fop.fo.extensions.ExtensionAttachment; import org.apache.fop.fo.extensions.xmp.XMPMetadata; import org.apache.fop.fonts.Font; @@ -74,88 +73,40 @@ 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.PDFPaintingState; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFResources; -import org.apache.fop.pdf.PDFStream; import org.apache.fop.pdf.PDFTextUtil; import org.apache.fop.pdf.PDFXMode; import org.apache.fop.pdf.PDFXObject; import org.apache.fop.render.AbstractPathOrientedRenderer; import org.apache.fop.render.Graphics2DAdapter; import org.apache.fop.render.RendererContext; +import org.apache.fop.traits.RuleStyle; import org.apache.fop.util.AbstractPaintingState; import org.apache.fop.util.CharUtilities; -import org.apache.fop.util.ColorProfileUtil; -import org.apache.fop.util.ColorUtil; import org.apache.fop.util.AbstractPaintingState.AbstractData; -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; /** * Renderer that renders areas to PDF. */ -public class PDFRenderer extends AbstractPathOrientedRenderer { +public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConfigurationConstants { - /** - * The mime type for pdf - */ + /** 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; @@ -165,11 +116,11 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { */ 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 @@ -186,7 +137,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 @@ -217,10 +168,9 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { */ protected PDFResources pdfResources; - /** - * the current stream to add PDF commands to - */ - protected PDFStream currentStream; + /** The current content generator to produce PDF commands with */ + protected PDFContentGenerator generator; + private PDFBorderPainter borderPainter; /** * the current annotation list to add annotations to @@ -237,246 +187,44 @@ 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; - - /** Painting state */ - protected PDFPaintingState paintingState = null; - - /** Text generation utility holding the current font status */ - protected PDFTextUtil textutil; /** page height */ protected int pageHeight; - /** Registry of PDF filters */ - protected Map filterMap; - /** Image handler registry */ private final 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} - */ - 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(); - } - + PDFRenderingUtil getPDFUtil() { + return this.pdfUtil; } - 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); - } + PDFContentGenerator getGenerator() { + return this.generator; } - 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(); - } + PDFPaintingState getState() { + return getGenerator().getState(); } - /** - * 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."); + /** {@inheritDoc} */ + public void startRenderer(OutputStream stream) throws IOException { + if (userAgent == null) { + throw new IllegalStateException("UserAgent must be set before starting the renderer"); } - 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); + ostream = stream; + this.pdfDoc = pdfUtil.setupPDFDocument(stream); } /** @@ -499,9 +247,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void stopRenderer() throws IOException { finishOpenGoTos(); @@ -514,13 +260,11 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { pages = null; pageReferences.clear(); - pvReferences.clear(); + //pvReferences.clear(); pdfResources = null; - currentStream = null; + this.generator = null; currentContext = null; currentPage = null; - paintingState = null; - this.textutil = null; idPositions.clear(); idGoTos.clear(); @@ -547,7 +291,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); } } } @@ -607,68 +351,29 @@ 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 - */ - protected void comment(String text) { - if (WRITE_COMMENTS) { - currentStream.add("% " + text + "\n"); - } - } - /** {@inheritDoc} */ protected void saveGraphicsState() { - endTextObject(); - paintingState.save(); - currentStream.add("q\n"); - } - - private void restoreGraphicsState(boolean popState) { - endTextObject(); - currentStream.add("Q\n"); - if (popState) { - paintingState.restore(); - } + generator.saveGraphicsState(); } /** {@inheritDoc} */ protected void restoreGraphicsState() { - restoreGraphicsState(true); + generator.restoreGraphicsState(); } /** Indicates the beginning of a text object. */ protected void beginTextObject() { - if (!textutil.isInTextObject()) { - textutil.beginTextObject(); - } + generator.beginTextObject(); } /** Indicates the end of a text object. */ protected void endTextObject() { - if (textutil.isInTextObject()) { - textutil.endTextObject(); - } + generator.endTextObject(); } /** @@ -699,14 +404,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(); } /** @@ -732,30 +430,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} @@ -775,40 +461,39 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { double h = bounds.getHeight(); pageHeight = (int) h; - currentStream = this.pdfDoc.getFactory() - .makeStream(PDFFilterList.CONTENT_FILTER, false); - this.textutil = new PDFTextUtil() { - protected void write(String code) { - currentStream.add(code); - } - }; + this.generator = new PDFContentGenerator(this.pdfDoc, this.ostream, this.currentPage); + this.borderPainter = new PDFBorderPainter(this.generator); - paintingState = new PDFPaintingState(); // Transform the PDF's default coordinate system (0,0 at lower left) to the PDFRenderer's AffineTransform basicPageTransform = new AffineTransform(1, 0, 0, -1, 0, pageHeight / 1000f); - paintingState.concatenate(basicPageTransform); + generator.concatenate(basicPageTransform); + /* + currentState.concatenate(basicPageTransform); currentStream.add(CTMHelper.toPDFString(basicPageTransform, false) + " cm\n"); + */ super.renderPage(page); - this.pdfDoc.registerObject(currentStream); - currentPage.setContents(currentStream); + this.pdfDoc.registerObject(generator.getStream()); + currentPage.setContents(generator.getStream()); PDFAnnotList annots = currentPage.getAnnotations(); if (annots != null) { this.pdfDoc.addObject(annots); } this.pdfDoc.addObject(currentPage); - this.pdfDoc.output(ostream); - this.textutil = null; + this.generator.flushPDFDoc(); + this.generator = null; } /** {@inheritDoc} */ protected void startVParea(CTM ctm, Rectangle2D clippingRect) { saveGraphicsState(); // Set the given CTM in the graphics state - paintingState.concatenate( + /* + currentState.concatenate( new AffineTransform(CTMHelper.toPDFArray(ctm))); + */ if (clippingRect != null) { clipRect((float)clippingRect.getX() / 1000f, @@ -817,7 +502,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { (float)clippingRect.getHeight() / 1000f); } // multiply with current CTM - currentStream.add(CTMHelper.toPDFString(ctm) + " cm\n"); + generator.concatenate(new AffineTransform(CTMHelper.toPDFArray(ctm))); } /** {@inheritDoc} */ @@ -827,10 +512,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { /** {@inheritDoc} */ protected void concatenateTransformationMatrix(AffineTransform at) { - if (!at.isIdentity()) { - paintingState.concatenate(at); - currentStream.add(CTMHelper.toPDFString(at, false) + " cm\n"); - } + generator.concatenate(at); } /** @@ -845,188 +527,12 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { /** {@inheritDoc} */ 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; - if ((w < 0) || (h < 0)) { - log.error("Negative extent received (w=" + w + ", h=" + h - + "). Border won't be painted."); - return; - } - switch (style) { - case Constants.EN_DASHED: - setColor(col, false, null); - if (horz) { - float unit = Math.abs(2 * h); - int rep = (int)(w / unit); - if (rep % 2 == 0) { - rep++; - } - unit = w / rep; - currentStream.add("[" + format(unit) + "] 0 d "); - currentStream.add(format(h) + " w\n"); - float ym = y1 + (h / 2); - currentStream.add(format(x1) + " " + format(ym) + " m " - + format(x2) + " " + format(ym) + " l S\n"); - } else { - float unit = Math.abs(2 * w); - int rep = (int)(h / unit); - if (rep % 2 == 0) { - rep++; - } - unit = h / rep; - currentStream.add("[" + format(unit) + "] 0 d "); - currentStream.add(format(w) + " w\n"); - float xm = x1 + (w / 2); - currentStream.add(format(xm) + " " + format(y1) + " m " - + format(xm) + " " + format(y2) + " l S\n"); - } - break; - case Constants.EN_DOTTED: - setColor(col, false, null); - currentStream.add("1 J "); - if (horz) { - float unit = Math.abs(2 * h); - int rep = (int)(w / unit); - if (rep % 2 == 0) { - rep++; - } - unit = w / rep; - 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 " - + format(x2) + " " + format(ym) + " l S\n"); - } else { - float unit = Math.abs(2 * w); - int rep = (int)(h / unit); - if (rep % 2 == 0) { - rep++; - } - unit = h / rep; - 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 " - + format(xm) + " " + format(y2) + " l S\n"); - } - break; - case Constants.EN_DOUBLE: - setColor(col, false, null); - currentStream.add("[] 0 d "); - if (horz) { - float h3 = h / 3; - currentStream.add(format(h3) + " w\n"); - float ym1 = y1 + (h3 / 2); - float ym2 = ym1 + h3 + h3; - currentStream.add(format(x1) + " " + format(ym1) + " m " - + format(x2) + " " + format(ym1) + " l S\n"); - 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 " - + format(xm1) + " " + format(y2) + " l S\n"); - currentStream.add(format(xm2) + " " + format(y1) + " m " - + format(xm2) + " " + format(y2) + " l S\n"); - } - break; - case Constants.EN_GROOVE: - case Constants.EN_RIDGE: - { - float colFactor = (style == EN_GROOVE ? 0.4f : -0.4f); - currentStream.add("[] 0 d "); - if (horz) { - Color uppercol = ColorUtil.lightenColor(col, -colFactor); - Color lowercol = ColorUtil.lightenColor(col, colFactor); - float h3 = h / 3; - currentStream.add(format(h3) + " w\n"); - float ym1 = y1 + (h3 / 2); - setColor(uppercol, false, null); - 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 " - + format(x2) + " " + format(ym1 + h3) + " l S\n"); - setColor(lowercol, false, null); - currentStream.add(format(x1) + " " + format(ym1 + h3 + h3) + " m " - + format(x2) + " " + format(ym1 + h3 + h3) + " l S\n"); - } else { - Color leftcol = ColorUtil.lightenColor(col, -colFactor); - Color rightcol = ColorUtil.lightenColor(col, colFactor); - float w3 = w / 3; - currentStream.add(format(w3) + " w\n"); - float xm1 = x1 + (w3 / 2); - setColor(leftcol, false, null); - 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 " - + format(xm1 + w3) + " " + format(y2) + " l S\n"); - setColor(rightcol, false, null); - currentStream.add(format(xm1 + w3 + w3) + " " + format(y1) + " m " - + format(xm1 + w3 + w3) + " " + format(y2) + " l S\n"); - } - break; - } - case Constants.EN_INSET: - case Constants.EN_OUTSET: - { - float colFactor = (style == EN_OUTSET ? 0.4f : -0.4f); - currentStream.add("[] 0 d "); - Color c = col; - if (horz) { - c = ColorUtil.lightenColor(c, (startOrBefore ? 1 : -1) * colFactor); - currentStream.add(format(h) + " w\n"); - float ym1 = y1 + (h / 2); - setColor(c, false, null); - currentStream.add(format(x1) + " " + format(ym1) + " m " - + format(x2) + " " + format(ym1) + " l S\n"); - } else { - c = ColorUtil.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 " - + format(xm1) + " " + format(y2) + " l S\n"); - } - break; - } - case Constants.EN_HIDDEN: - break; - default: - setColor(col, false, null); - currentStream.add("[] 0 d "); - if (horz) { - currentStream.add(format(h) + " w\n"); - float ym = y1 + (h / 2); - 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 " - + format(xm) + " " + format(y2) + " l S\n"); - } - } - } - - /** - * Sets the current line width in points. - * @param width line width in points - */ - private void updateLineWidth(float width) { - if (paintingState.setLineWidth(width)) { - //Only write if value has changed WRT the current line width - currentStream.add(format(width) + " w\n"); - } + PDFBorderPainter.drawBorderLine(generator, x1, y1, x2, y2, horz, startOrBefore, style, col); } /** {@inheritDoc} */ protected void clipRect(float x, float y, float width, float height) { - currentStream.add(format(x) + " " + format(y) + " " + generator.add(format(x) + " " + format(y) + " " + format(width) + " " + format(height) + " re "); clip(); } @@ -1035,8 +541,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * Clip an area. */ protected void clip() { - currentStream.add("W\n"); - currentStream.add("n\n"); + generator.add("W\n" + "n\n"); } /** @@ -1045,7 +550,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param y y coordinate */ protected void moveTo(float x, float y) { - currentStream.add(format(x) + " " + format(y) + " m "); + generator.add(format(x) + " " + format(y) + " m "); } /** @@ -1055,7 +560,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param y y coordinate */ protected void lineTo(float x, float y) { - currentStream.add(format(x) + " " + format(y) + " l "); + generator.add(format(x) + " " + format(y) + " l "); } /** @@ -1063,7 +568,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * the current point to the starting point of the subpath. */ protected void closePath() { - currentStream.add("h "); + generator.add("h "); } /** @@ -1071,7 +576,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { */ protected void fillRect(float x, float y, float width, float height) { if (width > 0 && height > 0) { - currentStream.add(format(x) + " " + format(y) + " " + generator.add(format(x) + " " + format(y) + " " + format(width) + " " + format(height) + " re f\n"); } } @@ -1085,8 +590,8 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param endy the y end position */ private void drawLine(float startx, float starty, float endx, float endy) { - currentStream.add(format(startx) + " " + format(starty) + " m "); - currentStream.add(format(endx) + " " + format(endy) + " l S\n"); + generator.add(format(startx) + " " + format(starty) + " m "); + generator.add(format(endx) + " " + format(endy) + " l S\n"); } /** @@ -1094,7 +599,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @return the saved state stack to recreate later */ protected List breakOutOfStateStack() { -// return currentState.popAll(); + PDFPaintingState paintingState = getState(); List breakOutList = new java.util.ArrayList(); AbstractPaintingState.AbstractData data; while (true) { @@ -1103,10 +608,10 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { break; } if (breakOutList.size() == 0) { - comment("------ break out!"); + generator.comment("------ break out!"); } breakOutList.add(0, data); //Insert because of stack-popping - restoreGraphicsState(false); + generator.restoreGraphicsState(false); } return breakOutList; } @@ -1116,7 +621,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param breakOutList the state stack to restore. */ protected void restoreStateStackAfterBreakOut(List breakOutList) { - comment("------ restoring context after break-out..."); + generator.comment("------ restoring context after break-out..."); // currentState.pushAll(breakOutList); AbstractData data; Iterator i = breakOutList.iterator(); @@ -1129,7 +634,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { //Left out for now because all this painting stuff is very //inconsistent. Some values go over PDFState, some don't. } - comment("------ done."); + generator.comment("------ done."); } /** @@ -1261,7 +766,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { */ protected void saveAbsolutePosition(String id, int relativeIPP, int relativeBPP) { saveAbsolutePosition(id, currentPageRef, - relativeIPP, relativeBPP, paintingState.getTransform()); + relativeIPP, relativeBPP, getState().getTransform()); } /** @@ -1285,8 +790,8 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { bpp += currentBPPosition; } AffineTransform tf = positioning == Block.FIXED - ? paintingState.getBaseTransform() - : paintingState.getTransform(); + ? getState().getBaseTransform() + : getState().getTransform(); saveAbsolutePosition(id, currentPageRef, ipp, bpp, tf); } } @@ -1349,7 +854,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { int bpp = currentBPPosition + ip.getOffset(); ipRect = new Rectangle2D.Float(ipp / 1000f, bpp / 1000f, ip.getIPD() / 1000f, ip.getBPD() / 1000f); - AffineTransform transform = paintingState.getTransform(); + AffineTransform transform = getState().getTransform(); ipRect = transform.createTransformedShape(ipRect).getBounds2D(); factory = pdfDoc.getFactory(); @@ -1425,6 +930,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { // This assumes that *all* CIDFonts use a /ToUnicode mapping Typeface tf = getTypeface(fontName); + PDFTextUtil textutil = generator.getTextUtil(); textutil.updateTf(fontName, size / 1000f, tf.isMultiByte()); @@ -1468,7 +974,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { if (tws != 0) { float adjust = tws / (font.getFontSize() / 1000f); - textutil.adjustGlyphTJ(adjust); + generator.getTextUtil().adjustGlyphTJ(adjust); } } @@ -1507,6 +1013,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { if (tf instanceof SingleByteFont) { singleByteFont = (SingleByteFont)tf; } + PDFTextUtil textutil = generator.getTextUtil(); int l = s.length(); @@ -1552,50 +1059,9 @@ 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 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)); - } else { - currentStream.add(color.getColorSpaceOut(fill)); - } - } - - /** - * 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 - * @param pdf StringBuffer to write the PDF code to, if null, the code is - * written to the current stream. - */ - private void updateColor(Color col, boolean fill, StringBuffer pdf) { - if (col == null) { - return; - } - boolean update = false; - if (fill) { - update = paintingState.setBackColor(col); - } else { - update = paintingState.setColor(col); - } - - if (update) { - setColor(col, fill, pdf); - } - } - /** {@inheritDoc} */ protected void updateColor(Color col, boolean fill) { - updateColor(col, fill, null); + generator.updateColor(col, fill, null); } /** {@inheritDoc} */ @@ -1654,8 +1120,9 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { info = manager.getImageInfo(uri, sessionContext); Map hints = ImageUtil.getDefaultHints(sessionContext); + ImageFlavor[] supportedFlavors = imageHandlerRegistry.getSupportedFlavors(); org.apache.xmlgraphics.image.loader.Image img = manager.getImage( - info, imageHandlerRegistry.getSupportedFlavors(), hints, sessionContext); + info, supportedFlavors, hints, sessionContext); //First check for a dynamically registered handler PDFImageHandler handler @@ -1695,7 +1162,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { // output new data try { - this.pdfDoc.output(ostream); + this.generator.flushPDFDoc(); } catch (IOException ioe) { // ioexception will be caught later log.error(ioe.getMessage()); @@ -1712,7 +1179,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { */ public void placeImage(float x, float y, float w, float h, PDFXObject xobj) { saveGraphicsState(); - currentStream.add(format(w) + " 0 0 " + generator.add(format(w) + " 0 0 " + format(-h) + " " + format(currentIPPosition / 1000f + x) + " " + format(currentBPPosition / 1000f + h + y) @@ -1727,12 +1194,11 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { x, y, width, height, foreignAttributes); context.setProperty(PDFRendererContextConstants.PDF_DOCUMENT, pdfDoc); context.setProperty(PDFRendererContextConstants.OUTPUT_STREAM, ostream); - context.setProperty(PDFRendererContextConstants.PDF_PAINTING_STATE, paintingState); context.setProperty(PDFRendererContextConstants.PDF_PAGE, currentPage); context.setProperty(PDFRendererContextConstants.PDF_CONTEXT, currentContext == null ? currentPage : currentContext); context.setProperty(PDFRendererContextConstants.PDF_CONTEXT, currentContext); - context.setProperty(PDFRendererContextConstants.PDF_STREAM, currentStream); + context.setProperty(PDFRendererContextConstants.PDF_STREAM, generator.getStream()); context.setProperty(PDFRendererContextConstants.PDF_FONT_INFO, fontInfo); context.setProperty(PDFRendererContextConstants.PDF_FONT_NAME, ""); context.setProperty(PDFRendererContextConstants.PDF_FONT_SIZE, new Integer(0)); @@ -1747,66 +1213,18 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { public void renderLeader(Leader area) { renderInlineAreaBackAndBorders(area); - paintingState.save(); - saveGraphicsState(); int style = area.getRuleStyle(); - float startx = (currentIPPosition + area.getBorderAndPaddingWidthStart()) / 1000f; - float starty = (currentBPPosition + area.getOffset()) / 1000f; - float endx = (currentIPPosition + area.getBorderAndPaddingWidthStart() - + area.getIPD()) / 1000f; - float ruleThickness = area.getRuleThickness() / 1000f; + int ruleThickness = area.getRuleThickness(); + int startx = currentIPPosition + area.getBorderAndPaddingWidthStart(); + int starty = currentBPPosition + area.getOffset() + (ruleThickness / 2); + int endx = currentIPPosition + + area.getBorderAndPaddingWidthStart() + + area.getIPD(); Color col = (Color)area.getTrait(Trait.COLOR); - switch (style) { - case EN_SOLID: - case EN_DASHED: - case EN_DOUBLE: - drawBorderLine(startx, starty, endx, starty + ruleThickness, - true, true, style, col); - break; - case EN_DOTTED: - clipRect(startx, starty, endx - startx, ruleThickness); - //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, - true, true, style, col); - break; - case EN_GROOVE: - case EN_RIDGE: - float half = area.getRuleThickness() / 2000f; - - setColor(ColorUtil.lightenColor(col, 0.6f), true, null); - currentStream.add(format(startx) + " " + format(starty) + " m\n"); - currentStream.add(format(endx) + " " + format(starty) + " l\n"); - currentStream.add(format(endx) + " " + format(starty + 2 * half) + " l\n"); - currentStream.add(format(startx) + " " + format(starty + 2 * half) + " l\n"); - currentStream.add("h\n"); - currentStream.add("f\n"); - setColor(col, true, null); - if (style == EN_GROOVE) { - currentStream.add(format(startx) + " " + format(starty) + " m\n"); - currentStream.add(format(endx) + " " + format(starty) + " l\n"); - currentStream.add(format(endx) + " " + format(starty + half) + " l\n"); - currentStream.add(format(startx + half) + " " + format(starty + half) + " l\n"); - currentStream.add(format(startx) + " " + format(starty + 2 * half) + " l\n"); - } else { - currentStream.add(format(endx) + " " + format(starty) + " m\n"); - currentStream.add(format(endx) + " " + format(starty + 2 * half) + " l\n"); - currentStream.add(format(startx) + " " + format(starty + 2 * half) + " l\n"); - currentStream.add(format(startx) + " " + format(starty + half) + " l\n"); - currentStream.add(format(endx - half) + " " + format(starty + half) + " l\n"); - } - currentStream.add("h\n"); - currentStream.add("f\n"); - break; - default: - throw new UnsupportedOperationException("rule style not supported"); - } - - restoreGraphicsState(); - paintingState.restore(); - beginTextObject(); + endTextObject(); + borderPainter.drawLine(new Point(startx, starty), new Point(endx, starty), + ruleThickness, col, RuleStyle.valueOf(style)); super.renderLeader(area); } @@ -1820,7 +1238,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param mode the PDF/A mode */ public void setAMode(PDFAMode mode) { - this.pdfAMode = mode; + this.pdfUtil.setAMode(mode); } /** @@ -1828,7 +1246,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param mode the PDF/X mode */ public void setXMode(PDFXMode mode) { - this.pdfXMode = mode; + this.pdfUtil.setXMode(mode); } /** @@ -1836,7 +1254,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); } /** @@ -1844,7 +1262,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param filterMap the filter map */ public void setFilterMap(Map filterMap) { - this.filterMap = filterMap; + this.pdfUtil.setFilterMap(filterMap); } /** @@ -1852,7 +1270,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @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 8d1042f7f..fccba1c25 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java +++ b/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java @@ -27,18 +27,30 @@ import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fonts.CustomFontCollection; +import org.apache.fop.fonts.FontCollection; +import org.apache.fop.fonts.FontEventAdapter; +import org.apache.fop.fonts.FontEventListener; +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.pdf.PDFAMode; import org.apache.fop.pdf.PDFEncryptionParams; import org.apache.fop.pdf.PDFFilterList; import org.apache.fop.pdf.PDFXMode; +import org.apache.fop.render.DefaultFontResolver; import org.apache.fop.render.PrintRendererConfigurator; import org.apache.fop.render.Renderer; +import org.apache.fop.render.intermediate.IFDocumentHandler; +import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator; import org.apache.fop.util.LogUtil; /** - * PDF renderer configurator + * PDF renderer configurator. */ -public class PDFRendererConfigurator extends PrintRendererConfigurator { +public class PDFRendererConfigurator extends PrintRendererConfigurator + implements IFDocumentHandlerConfigurator { /** * Default constructor @@ -61,78 +73,83 @@ 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); } + } 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 userPasswordConfig = encryptionParamsConfig.getChild( - PDFRenderer.USER_PASSWORD, false); - if (userPasswordConfig != null) { - String userPassword = userPasswordConfig.getValue(null); - if (userPassword != null) { - encryptionParams.setUserPassword(userPassword); - } + 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 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 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 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) { - pdfRenderer.disableSRGBColorSpace - = disableColorSpaceConfig.getValueAsBoolean(false); - } + if (disableColorSpaceConfig != null) { + pdfUtil.setDisableSRGBColorSpace( + disableColorSpaceConfig.getValueAsBoolean(false)); } } @@ -184,4 +201,39 @@ public class PDFRendererConfigurator extends PrintRendererConfigurator { } return filterMap; } + + // ---=== IFDocumentHandler configuration ===--- + + /** {@inheritDoc} */ + public void configure(IFDocumentHandler documentHandler) throws FOPException { + Configuration cfg = super.getRendererConfig(documentHandler.getMimeType()); + if (cfg != null) { + PDFDocumentHandler pdfDocumentHandler = (PDFDocumentHandler)documentHandler; + PDFRenderingUtil pdfUtil = pdfDocumentHandler.getPDFUtil(); + configure(cfg, pdfUtil); + } + } + + /** {@inheritDoc} */ + public void setupFontInfo(IFDocumentHandler documentHandler, FontInfo fontInfo) + throws FOPException { + FontManager fontManager = userAgent.getFactory().getFontManager(); + List fontCollections = new java.util.ArrayList(); + fontCollections.add(new Base14FontCollection(fontManager.isBase14KerningEnabled())); + + Configuration cfg = super.getRendererConfig(documentHandler.getMimeType()); + if (cfg != null) { + FontResolver fontResolver = new DefaultFontResolver(userAgent); + FontEventListener listener = new FontEventAdapter( + userAgent.getEventBroadcaster()); + List fontList = buildFontList(cfg, fontResolver, listener); + fontCollections.add(new CustomFontCollection(fontResolver, fontList)); + } + + fontManager.setup(fontInfo, + (FontCollection[])fontCollections.toArray( + new FontCollection[fontCollections.size()])); + documentHandler.setFontInfo(fontInfo); + } + } diff --git a/src/java/org/apache/fop/render/pdf/PDFRendererContextConstants.java b/src/java/org/apache/fop/render/pdf/PDFRendererContextConstants.java index 33888d442..11380bf59 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRendererContextConstants.java +++ b/src/java/org/apache/fop/render/pdf/PDFRendererContextConstants.java @@ -29,9 +29,6 @@ public interface PDFRendererContextConstants extends RendererContextConstants { /** The PDF document that this image is being drawn into. */ String PDF_DOCUMENT = "pdfDoc"; - /** The current PDF painting state. */ - String PDF_PAINTING_STATE = "pdfPaintingState"; - /** The current PDF page for page renference and as a resource context. */ String PDF_PAGE = "pdfPage"; diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderingContext.java b/src/java/org/apache/fop/render/pdf/PDFRenderingContext.java new file mode 100644 index 000000000..98b0c8203 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFRenderingContext.java @@ -0,0 +1,82 @@ +/* + * 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.xmlgraphics.util.MimeConstants; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.pdf.PDFPage; +import org.apache.fop.render.AbstractRenderingContext; + +/** + * Rendering context for PDF production. + */ +public class PDFRenderingContext extends AbstractRenderingContext { + + private PDFContentGenerator generator; + private FontInfo fontInfo; + private PDFPage page; + + /** + * Main constructor. + * @param userAgent the user agent + * @param generator the PDF content generator + * @param page the current PDF page + * @param fontInfo the font list + */ + public PDFRenderingContext(FOUserAgent userAgent, + PDFContentGenerator generator, PDFPage page, FontInfo fontInfo) { + super(userAgent); + this.generator = generator; + this.page = page; + this.fontInfo = fontInfo; + } + + /** {@inheritDoc} */ + public String getMimeType() { + return MimeConstants.MIME_PDF; + } + + /** + * Returns the PDF content generator. + * @return the PDF content generator + */ + public PDFContentGenerator getGenerator() { + return this.generator; + } + + /** + * Returns the current PDF page. + * @return the PDF page + */ + public PDFPage getPage() { + return this.page; + } + + /** + * Returns the font list. + * @return the font list + */ + public FontInfo getFontInfo() { + return this.fontInfo; + } + +} 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..e44edf8af --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java @@ -0,0 +1,410 @@ +/* + * 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.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.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.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); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFSVGHandler.java b/src/java/org/apache/fop/render/pdf/PDFSVGHandler.java index 41f48df55..8f7aad300 100644 --- a/src/java/org/apache/fop/render/pdf/PDFSVGHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFSVGHandler.java @@ -41,9 +41,7 @@ import org.apache.fop.fonts.FontInfo; import org.apache.fop.image.loader.batik.BatikUtil; import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFPage; -import org.apache.fop.pdf.PDFPaintingState; import org.apache.fop.pdf.PDFResourceContext; -import org.apache.fop.pdf.PDFStream; import org.apache.fop.render.AbstractGenericSVGHandler; import org.apache.fop.render.Renderer; import org.apache.fop.render.RendererContext; @@ -76,10 +74,10 @@ public class PDFSVGHandler extends AbstractGenericSVGHandler PDFInfo pdfi = new PDFInfo(); pdfi.pdfDoc = (PDFDocument)context.getProperty(PDF_DOCUMENT); pdfi.outputStream = (OutputStream)context.getProperty(OUTPUT_STREAM); - pdfi.pdfPaintingState = (PDFPaintingState)context.getProperty(PDF_PAINTING_STATE); + //pdfi.pdfState = (PDFState)context.getProperty(PDF_STATE); pdfi.pdfPage = (PDFPage)context.getProperty(PDF_PAGE); pdfi.pdfContext = (PDFResourceContext)context.getProperty(PDF_CONTEXT); - pdfi.currentStream = (PDFStream)context.getProperty(PDF_STREAM); + //pdfi.currentStream = (PDFStream)context.getProperty(PDF_STREAM); pdfi.width = ((Integer)context.getProperty(WIDTH)).intValue(); pdfi.height = ((Integer)context.getProperty(HEIGHT)).intValue(); pdfi.fi = (FontInfo)context.getProperty(PDF_FONT_INFO); @@ -105,14 +103,12 @@ public class PDFSVGHandler extends AbstractGenericSVGHandler public PDFDocument pdfDoc; /** see OUTPUT_STREAM */ public OutputStream outputStream; - /** see PDF_STATE */ - public PDFPaintingState pdfPaintingState; /** see PDF_PAGE */ public PDFPage pdfPage; /** see PDF_CONTEXT */ public PDFResourceContext pdfContext; /** see PDF_STREAM */ - public PDFStream currentStream; + //public PDFStream currentStream; /** see PDF_WIDTH */ public int width; /** see PDF_HEIGHT */ @@ -216,14 +212,15 @@ public class PDFSVGHandler extends AbstractGenericSVGHandler * Note: To have the svg overlay (under) a text area then use * an fo:block-container */ - pdfInfo.currentStream.add("%SVG setup\n"); - renderer.saveGraphicsState(); - renderer.setColor(Color.black, false, null); - renderer.setColor(Color.black, true, null); + PDFContentGenerator generator = renderer.getGenerator(); + generator.comment("SVG setup"); + generator.saveGraphicsState(); + generator.setColor(Color.black, false); + generator.setColor(Color.black, true); if (!scaling.isIdentity()) { - pdfInfo.currentStream.add("%viewbox\n"); - pdfInfo.currentStream.add(CTMHelper.toPDFString(scaling, false) + " cm\n"); + generator.comment("viewbox"); + generator.add(CTMHelper.toPDFString(scaling, false) + " cm\n"); } //SVGSVGElement svg = ((SVGDocument)doc).getRootElement(); @@ -238,38 +235,38 @@ public class PDFSVGHandler extends AbstractGenericSVGHandler graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); if (!resolutionScaling.isIdentity()) { - pdfInfo.currentStream.add("%resolution scaling for " + uaResolution + generator.comment("resolution scaling for " + uaResolution + " -> " + deviceResolution + "\n"); - pdfInfo.currentStream.add( + generator.add( CTMHelper.toPDFString(resolutionScaling, false) + " cm\n"); graphics.scale(1 / s, 1 / s); } - pdfInfo.currentStream.add("%SVG start\n"); + generator.comment("SVG start"); //Save state and update coordinate system for the SVG image - pdfInfo.pdfPaintingState.save(); - pdfInfo.pdfPaintingState.concatenate(imageTransform); + generator.getState().save(); + generator.getState().concatenate(imageTransform); //Now that we have the complete transformation matrix for the image, we can update the //transformation matrix for the AElementBridge. PDFAElementBridge aBridge = (PDFAElementBridge)ctx.getBridge( SVGDOMImplementation.SVG_NAMESPACE_URI, SVGConstants.SVG_A_TAG); - aBridge.getCurrentTransform().setTransform(pdfInfo.pdfPaintingState.getTransform()); + aBridge.getCurrentTransform().setTransform(generator.getState().getTransform()); - graphics.setPaintingState(pdfInfo.pdfPaintingState); + graphics.setPaintingState(generator.getState()); graphics.setOutputStream(pdfInfo.outputStream); try { root.paint(graphics); - pdfInfo.currentStream.add(graphics.getString()); + generator.add(graphics.getString()); } catch (Exception e) { SVGEventProducer eventProducer = SVGEventProducer.Provider.get( context.getUserAgent().getEventBroadcaster()); eventProducer.svgRenderingError(this, e, getDocumentURI(doc)); } - pdfInfo.pdfPaintingState.restore(); - renderer.restoreGraphicsState(); - pdfInfo.currentStream.add("%SVG end\n"); + generator.getState().restore(); + generator.restoreGraphicsState(); + generator.comment("SVG end"); } /** {@inheritDoc} */ |