/* * 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.svg; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.Shape; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.geom.AffineTransform; import java.io.IOException; import java.io.OutputStream; import java.io.StringWriter; import org.apache.xmlgraphics.image.GraphicsConstants; import org.apache.fop.Version; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.FontSetup; import org.apache.fop.pdf.PDFAnnotList; import org.apache.fop.pdf.PDFColorHandler; import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFFilterList; import org.apache.fop.pdf.PDFNumber; import org.apache.fop.pdf.PDFPage; import org.apache.fop.pdf.PDFPaintingState; import org.apache.fop.pdf.PDFReference; import org.apache.fop.pdf.PDFResources; import org.apache.fop.pdf.PDFStream; /** * This class is a wrapper for the {@link PDFGraphics2D} that * is used to create a full document around the PDF rendering from * {@link PDFGraphics2D}. * * @see org.apache.fop.svg.PDFGraphics2D */ public class PDFDocumentGraphics2D extends PDFGraphics2D { private final PDFContext pdfContext; private int width; private int height; //for SVG scaling private float svgWidth; private float svgHeight; /** Normal PDF resolution (72dpi) */ public static final int NORMAL_PDF_RESOLUTION = 72; /** Default device resolution (300dpi is a resonable quality for most purposes) */ public static final int DEFAULT_NATIVE_DPI = GraphicsConstants.DEFAULT_SAMPLE_DPI; /** * The device resolution may be different from the normal target resolution. See * http://issues.apache.org/bugzilla/show_bug.cgi?id=37305 */ private float deviceDPI = DEFAULT_NATIVE_DPI; /** Initial clipping area, used to restore to original setting * when a new page is started. */ protected Shape initialClip; /** * Initial transformation matrix, used to restore to original * setting when a new page is started. */ protected AffineTransform initialTransform; /** * Create a new PDFDocumentGraphics2D. * This is used to create a new pdf document, the height, * width and output stream can be setup later. * For use by the transcoder which needs font information * for the bridge before the document size is known. * The resulting document is written to the stream after rendering. * * @param textAsShapes set this to true so that text will be rendered * using curves and not the font. */ public PDFDocumentGraphics2D(boolean textAsShapes) { super(textAsShapes); this.pdfDoc = new PDFDocument("Apache FOP Version " + Version.getVersion() + ": PDFDocumentGraphics2D"); this.pdfContext = new PDFContext(); this.colorHandler = new PDFColorHandler(this.pdfDoc.getResources()); } /** * Create a new PDFDocumentGraphics2D. * This is used to create a new pdf document of the given height * and width. * The resulting document is written to the stream after rendering. * * @param textAsShapes set this to true so that text will be rendered * using curves and not the font. * @param stream the stream that the final document should be written to. * @param width the width of the document (in points) * @param height the height of the document (in points) * @throws IOException an io exception if there is a problem * writing to the output stream */ public PDFDocumentGraphics2D(boolean textAsShapes, OutputStream stream, int width, int height) throws IOException { this(textAsShapes); setupDocument(stream, width, height); } /** * Create a new PDFDocumentGraphics2D. * This is used to create a new pdf document. * For use by the transcoder which needs font information * for the bridge before the document size is known. * The resulting document is written to the stream after rendering. * This constructor is Avalon-style. */ public PDFDocumentGraphics2D() { this(false); } /** * Setup the document. * @param stream the output stream to write the document * @param width the width of the page * @param height the height of the page * @throws IOException an io exception if there is a problem * writing to the output stream */ public void setupDocument(OutputStream stream, int width, int height) throws IOException { this.width = width; this.height = height; pdfDoc.outputHeader(stream); setOutputStream(stream); } /** * Setup a default FontInfo instance if none has been setup before. */ public void setupDefaultFontInfo() { if (fontInfo == null) { //Default minimal fonts FontInfo fontInfo = new FontInfo(); boolean base14Kerning = false; FontSetup.setup(fontInfo, base14Kerning); setFontInfo(fontInfo); } } /** * Set the device resolution for rendering. Will take effect at the * start of the next page. * @param deviceDPI the device resolution (in dpi) */ public void setDeviceDPI(float deviceDPI) { this.deviceDPI = deviceDPI; } /** * @return the device resolution (in dpi) for rendering. */ public float getDeviceDPI() { return deviceDPI; } /** * Sets the font info for this PDF document. * @param fontInfo the font info object with all the fonts */ public void setFontInfo(FontInfo fontInfo) { this.fontInfo = fontInfo; } /** * Get the font info for this pdf document. * @return the font information */ public FontInfo getFontInfo() { return fontInfo; } /** * Get the pdf document created by this class. * @return the pdf document */ public PDFDocument getPDFDocument() { return this.pdfDoc; } /** * Return the PDFContext for this instance. * @return the PDFContext */ public PDFContext getPDFContext() { return this.pdfContext; } /** * Set the dimensions of the svg document that will be drawn. * This is useful if the dimensions of the svg document are different * from the pdf document that is to be created. * The result is scaled so that the svg fits correctly inside the * pdf document. * @param w the width of the page * @param h the height of the page */ public void setSVGDimension(float w, float h) { this.svgWidth = w; this.svgHeight = h; } /** * Set the background of the pdf document. * This is used to set the background for the pdf document * Rather than leaving it as the default white. * @param col the background colour to fill */ public void setBackgroundColor(Color col) { StringBuffer sb = new StringBuffer(); sb.append("q\n"); this.colorHandler.establishColor(sb, col, true); sb.append("0 0 ").append(width).append(" ").append(height).append(" re\n"); sb.append("f\n"); sb.append("Q\n"); currentStream.write(sb.toString()); } /** * Is called to prepare the PDFDocumentGraphics2D for the next page to be painted. Basically, * this closes the current page. A new page is prepared as soon as painting starts. */ public void nextPage() { closePage(); } /** * Is called to prepare the PDFDocumentGraphics2D for the next page to be painted. Basically, * this closes the current page. A new page is prepared as soon as painting starts. * This method allows to start the new page (and following pages) with a different page size. * @param width the width of the new page (in points) * @param height the height of the new page (in points) */ public void nextPage(int width, int height) { this.width = width; this.height = height; nextPage(); } /** * Closes the current page and adds it to the PDF file. */ protected void closePage() { if (!pdfContext.isPagePending()) { return; //ignore } currentStream.write("Q\n"); //Finish page PDFStream pdfStream = this.pdfDoc.getFactory().makeStream( PDFFilterList.CONTENT_FILTER, false); pdfStream.add(getString()); this.pdfDoc.registerObject(pdfStream); pdfContext.getCurrentPage().setContents(new PDFReference(pdfStream)); PDFAnnotList annots = pdfContext.getCurrentPage().getAnnotations(); if (annots != null) { this.pdfDoc.addObject(annots); } this.pdfDoc.addObject(pdfContext.getCurrentPage()); pdfContext.clearCurrentPage(); } /** {@inheritDoc} */ @Override protected void preparePainting() { if (pdfContext.isPagePending()) { return; } //Setup default font info if no more font configuration has been done by the user. if (!this.textAsShapes && getFontInfo() == null) { setupDefaultFontInfo(); } try { startPage(); } catch (IOException ioe) { handleIOException(ioe); } } /** * Called to prepare a new page * @throws IOException if starting the new page fails due to I/O errors. */ protected void startPage() throws IOException { if (pdfContext.isPagePending()) { throw new IllegalStateException("Close page first before starting another"); } //Start page paintingState = new PDFPaintingState(); if (this.initialTransform == null) { //Save initial transformation matrix this.initialTransform = getTransform(); this.initialClip = getClip(); } else { //Reset transformation matrix setTransform(this.initialTransform); setClip(this.initialClip); } currentFontName = ""; currentFontSize = 0; if (currentStream == null) { currentStream = new StringWriter(); } PDFResources pdfResources = this.pdfDoc.getResources(); PDFPage page = this.pdfDoc.getFactory().makePage(pdfResources, width, height); resourceContext = page; pdfContext.setCurrentPage(page); pageRef = page.makeReference(); currentStream.write("q\n"); AffineTransform at = new AffineTransform(1.0, 0.0, 0.0, -1.0, 0.0, height); currentStream.write("1 0 0 -1 0 " + height + " cm\n"); if (svgWidth != 0) { double scaleX = width / svgWidth; double scaleY = height / svgHeight; at.scale(scaleX, scaleY); currentStream.write("" + PDFNumber.doubleOut(scaleX) + " 0 0 " + PDFNumber.doubleOut(scaleY) + " 0 0 cm\n"); } if (deviceDPI != NORMAL_PDF_RESOLUTION) { double s = NORMAL_PDF_RESOLUTION / deviceDPI; at.scale(s, s); currentStream.write("" + PDFNumber.doubleOut(s) + " 0 0 " + PDFNumber.doubleOut(s) + " 0 0 cm\n"); scale(1 / s, 1 / s); } // Remember the transform we installed. paintingState.concatenate(at); pdfContext.increasePageCount(); } /** * The rendering process has finished. * This should be called after the rendering has completed as there is * no other indication it is complete. * This will then write the results to the output stream. * @throws IOException an io exception if there is a problem * writing to the output stream */ public void finish() throws IOException { // restorePDFState(); closePage(); if (fontInfo != null) { pdfDoc.getResources().addFonts(pdfDoc, fontInfo); } this.pdfDoc.output(outputStream); pdfDoc.outputTrailer(outputStream); outputStream.flush(); } /** * This constructor supports the create method * @param g the pdf document graphics to make a copy of */ public PDFDocumentGraphics2D(PDFDocumentGraphics2D g) { super(g); this.pdfContext = g.pdfContext; this.width = g.width; this.height = g.height; this.svgWidth = g.svgWidth; this.svgHeight = g.svgHeight; } /** * Creates a new <code>Graphics</code> object that is * a copy of this <code>Graphics</code> object. * @return a new graphics context that is a copy of * this graphics context. */ @Override public Graphics create() { preparePainting(); return new PDFDocumentGraphics2D(this); } /** * Draw a string to the pdf document. * This either draws the string directly or if drawing text as * shapes it converts the string into shapes and draws that. * @param s the string to draw * @param x the x position * @param y the y position */ @Override public void drawString(String s, float x, float y) { if (super.textAsShapes) { Font font = super.getFont(); FontRenderContext frc = super.getFontRenderContext(); GlyphVector gv = font.createGlyphVector(frc, s); Shape glyphOutline = gv.getOutline(x, y); super.fill(glyphOutline); } else { super.drawString(s, x, y); } } }