001/* ======================================================
002 * JFreeChart : a chart library for the Java(tm) platform
003 * ======================================================
004 *
005 * (C) Copyright 2000-present, by David Gilbert and Contributors.
006 *
007 * Project Info:  https://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022 * USA.
023 *
024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ----------------
028 * ExportUtils.java
029 * ----------------
030 * (C) Copyright 2014-present, by David Gilbert and Contributors.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   -;
034 *
035 */
036
037package org.jfree.chart.util;
038
039import java.awt.Graphics2D;
040import java.awt.Rectangle;
041import java.awt.geom.Rectangle2D;
042import java.awt.image.BufferedImage;
043import java.io.BufferedOutputStream;
044import java.io.File;
045import java.io.FileNotFoundException;
046import java.io.FileOutputStream;
047import java.io.IOException;
048import java.io.OutputStream;
049import java.lang.reflect.Constructor;
050import java.lang.reflect.InvocationTargetException;
051import java.lang.reflect.Method;
052import javax.imageio.ImageIO;
053import org.jfree.chart.ui.Drawable;
054
055/**
056 * Utility functions for exporting charts to SVG and PDF format.
057 */
058public class ExportUtils {
059
060    private ExportUtils() {
061        // no need to instantiate
062    }
063
064    /**
065     * Returns {@code true} if JFreeSVG is on the classpath, and 
066     * {@code false} otherwise.  The JFreeSVG library can be found at
067     * https://www.jfree.org/jfreesvg/
068     *
069     * @return A boolean.
070     */
071    public static boolean isJFreeSVGAvailable() {
072        Class<?> svgClass = null;
073        try {
074            svgClass = Class.forName("org.jfree.svg.SVGGraphics2D");
075        } catch (ClassNotFoundException e) {
076            // see if there is maybe an older version of JFreeSVG (different package name)
077            try {
078                svgClass = Class.forName("org.jfree.graphics2d.svg.SVGGraphics2D");
079            } catch (ClassNotFoundException e2) {
080                // svgClass will be null so the function will return false
081            }
082        }
083        return (svgClass != null);
084    }
085
086    /**
087     * Returns {@code true} if OrsonPDF (or JFreePDF) is on the classpath, and 
088     * {@code false} otherwise.  The OrsonPDF library can be found at
089     * https://github.com/jfree/orsonpdf.  JFreePDF is a modular version of
090     * the same library, requiring Java 11 or later.  Since JFreeChart might
091     * be used in a modular context, this function has been modified (in version
092     * 1.5.3) to detect JFreePDF also.
093     * 
094     * @return A boolean.
095     */
096    public static boolean isOrsonPDFAvailable() {
097        Class<?> pdfDocumentClass = null;
098        try {
099            pdfDocumentClass = Class.forName("com.orsonpdf.PDFDocument");
100        } catch (ClassNotFoundException e) {
101            // check also for JFreePDF, which is the new modular version of OrsonPDF
102            try {
103                pdfDocumentClass = Class.forName("org.jfree.pdf.PDFDocument");
104            } catch (ClassNotFoundException e2) {
105                // pdfDocumentClass will be null so the function will return false
106            }
107        }
108        return (pdfDocumentClass != null);
109    }
110
111    /**
112     * Writes the current content to the specified file in SVG format.  This 
113     * will only work when the JFreeSVG library is found on the classpath.
114     * Reflection is used to ensure there is no compile-time dependency on
115     * JFreeSVG.
116     * 
117     * @param drawable  the drawable ({@code null} not permitted).
118     * @param w  the chart width.
119     * @param h  the chart height.
120     * @param file  the output file ({@code null} not permitted).
121     */
122    public static void writeAsSVG(Drawable drawable, int w, int h, File file) {
123        if (!ExportUtils.isJFreeSVGAvailable()) {
124            throw new IllegalStateException("JFreeSVG is not present on the classpath.");
125        }
126        Args.nullNotPermitted(drawable, "drawable");
127        Args.nullNotPermitted(file, "file");
128        try {
129            Class<?> svg2Class;
130            try {
131                svg2Class = Class.forName("org.jfree.graphics2d.svg.SVGGraphics2D");
132            } catch (ClassNotFoundException ex) {
133                svg2Class = Class.forName("org.jfree.svg.SVGGraphics2D");
134            }
135            
136            Constructor<?> c1 = svg2Class.getConstructor(int.class, int.class);
137            Graphics2D svg2 = (Graphics2D) c1.newInstance(w, h);
138            Rectangle2D drawArea = new Rectangle2D.Double(0, 0, w, h);
139            drawable.draw(svg2, drawArea);
140            Class<?> svgUtilsClass;
141            try {
142                svgUtilsClass = Class.forName("org.jfree.graphics2d.svg.SVGUtils");
143            } catch (ClassNotFoundException ex) {
144                svgUtilsClass = Class.forName("org.jfree.svg.SVGUtils");
145            }
146            Method m1 = svg2Class.getMethod("getSVGElement", (Class[]) null);
147            String element = (String) m1.invoke(svg2, (Object[]) null);
148            Method m2 = svgUtilsClass.getMethod("writeToSVG", File.class, 
149                    String.class);
150            m2.invoke(svgUtilsClass, file, element);
151        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException |
152                NoSuchMethodException | SecurityException | IllegalArgumentException |
153                InvocationTargetException ex) {
154            throw new RuntimeException(ex);
155        }
156    }
157
158    /**
159     * Writes a {@link Drawable} to the specified file in PDF format.  This 
160     * will only work when the OrsonPDF library is found on the classpath.
161     * Reflection is used to ensure there is no compile-time dependency on
162     * OrsonPDF.
163     * 
164     * @param drawable  the drawable ({@code null} not permitted).
165     * @param w  the chart width.
166     * @param h  the chart height.
167     * @param file  the output file ({@code null} not permitted).
168     */
169    public static void writeAsPDF(Drawable drawable,
170                                  int w, int h, File file) {
171        if (!ExportUtils.isOrsonPDFAvailable()) {
172            throw new IllegalStateException("Neither OrsonPDF nor JFreePDF is present on the classpath.");
173        }
174        Args.nullNotPermitted(drawable, "drawable");
175        Args.nullNotPermitted(file, "file");
176        try {
177            Class<?> pdfDocClass;
178            try {
179                pdfDocClass = Class.forName("com.orsonpdf.PDFDocument");
180            } catch (ClassNotFoundException e) {                
181                pdfDocClass = Class.forName("org.jfree.pdf.PDFDocument");
182            }
183            Object pdfDoc = pdfDocClass.getDeclaredConstructor().newInstance();
184            Method m = pdfDocClass.getMethod("createPage", Rectangle2D.class);
185            Rectangle2D rect = new Rectangle(w, h);
186            Object page = m.invoke(pdfDoc, rect);
187            Method m2 = page.getClass().getMethod("getGraphics2D");
188            Graphics2D g2 = (Graphics2D) m2.invoke(page);
189            Rectangle2D drawArea = new Rectangle2D.Double(0, 0, w, h);
190            drawable.draw(g2, drawArea);
191            Method m3 = pdfDocClass.getMethod("writeToFile", File.class);
192            m3.invoke(pdfDoc, file);
193        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException |
194                NoSuchMethodException | SecurityException | IllegalArgumentException |
195                InvocationTargetException ex) {
196            throw new RuntimeException(ex);
197        }
198    }
199    
200    /**
201     * Writes the current content to the specified file in PNG format.
202     * 
203     * @param drawable  the drawable ({@code null} not permitted).
204     * @param w  the chart width.
205     * @param h  the chart height.
206     * @param file  the output file ({@code null} not permitted).
207     * 
208     * @throws FileNotFoundException if the file is not found.
209     * @throws IOException if there is an I/O problem.
210     */
211    public static void writeAsPNG(Drawable drawable, int w, int h, 
212            File file) throws FileNotFoundException, IOException {
213        BufferedImage image = new BufferedImage(w, h, 
214                BufferedImage.TYPE_INT_ARGB);
215        Graphics2D g2 = image.createGraphics();
216        drawable.draw(g2, new Rectangle(w, h));
217        try (OutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
218            ImageIO.write(image, "png", out);
219        }
220    }
221
222    /**
223     * Writes the current content to the specified file in JPEG format.
224     * 
225     * @param drawable  the drawable ({@code null} not permitted).
226     * @param w  the chart width.
227     * @param h  the chart height.
228     * @param file  the output file ({@code null} not permitted).
229     * 
230     * @throws FileNotFoundException if the file is not found.
231     * @throws IOException if there is an I/O problem.
232     */
233    public static void writeAsJPEG(Drawable drawable, int w, int h, 
234            File file) throws FileNotFoundException, IOException {
235        BufferedImage image = new BufferedImage(w, h, 
236                BufferedImage.TYPE_INT_RGB);
237        Graphics2D g2 = image.createGraphics();
238        drawable.draw(g2, new Rectangle(w, h));
239        try (OutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
240            ImageIO.write(image, "jpg", out);
241        }
242    }
243 
244}