| /* |
| * 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); |
| } |
| } |
| |
| } |
| |