Chris@2: /*
Chris@2: Copyright (C) 2001, 2006 by Simon Dixon
Chris@2:
Chris@2: This program is free software; you can redistribute it and/or modify
Chris@2: it under the terms of the GNU General Public License as published by
Chris@2: the Free Software Foundation; either version 2 of the License, or
Chris@2: (at your option) any later version.
Chris@2:
Chris@2: This program is distributed in the hope that it will be useful,
Chris@2: but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@2: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@2: GNU General Public License for more details.
Chris@2:
Chris@2: You should have received a copy of the GNU General Public License along
Chris@2: with this program (the file gpl.txt); if not, download it from
Chris@2: http://www.gnu.org/licenses/gpl.txt or write to the
Chris@2: Free Software Foundation, Inc.,
Chris@2: 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Chris@2: */
Chris@2:
Chris@2: package at.ofai.music.util;
Chris@2:
Chris@2: import java.awt.Component;
Chris@2: import java.awt.Graphics;
Chris@2: import java.awt.Graphics2D;
Chris@2: import java.awt.geom.AffineTransform;
Chris@2: import java.awt.print.PageFormat;
Chris@2: import java.awt.print.Printable;
Chris@2: import java.awt.print.PrinterException;
Chris@2: import java.awt.print.PrinterJob;
Chris@2:
Chris@2: import at.ofai.music.util.Format;
Chris@2:
Chris@2: /** A utility class for converting graphical user interface components to
Chris@2: * PostScript, which can be sent directly to a printer or printed to a file.
Chris@2: * This gives much higher quality illustrations for articles
Chris@2: * than if a screenshot is used, since scaling should not reduce quality.
Chris@2: * The only requirement is that the component to be printed has a
Chris@2: * paint(Graphics) method.
Chris@2: *
There are some bugs in this code which require manual editing of Chris@2: * the PostScript file. First, there doesn't seem to be any way to include Chris@2: * the bounding box, although it is possible to calculate it. Second, the Chris@2: * cliprect produced in the PostScript output is wrong. Chris@2: * (Check: has this been fixed in more recent Java versions? Chris@2: * Apparently not, as of 1.5.0, but if scaling is not performed, the cliprect Chris@2: * is OK and the bounding box correct.) Chris@2: * See {@link PSPrinter#print(Graphics, PageFormat, int)} Chris@2: */ Chris@2: public class PSPrinter implements Printable { Chris@2: Chris@2: /** the component to be converted */ Chris@2: Component component; Chris@2: /** the desired graphical resolution in pixels per inch */ Chris@2: int resolution; // can't work out how to ask the system Chris@2: Chris@2: /** Print a GUI component to a PostScript printer or file. Chris@2: * The 2 forms of this method are the normal ways of accessing this class. Chris@2: * This form has problems printing some components. It is recommended to Chris@2: * use the other version. Chris@2: * @param c the component to be rendered in PostScript Chris@2: * @param r the resolution of the printer in pixels per inch Chris@2: */ Chris@2: public static void print(Component c, int r) { Chris@2: new PSPrinter(c, r).doPrint(); Chris@2: } Chris@2: Chris@2: /** Print a GUI component to a PostScript printer or file. Chris@2: * The 2 forms of this method are the normal ways of accessing this class. Chris@2: * If no resolution is given, the picture is not scaled, and the cliprect Chris@2: * is then correct. This is the recommended version to use. Chris@2: * @param c the component to be rendered in PostScript Chris@2: */ Chris@2: public static void print(Component c) { Chris@2: new PSPrinter(c, -1).doPrint(); Chris@2: } Chris@2: Chris@2: /** Constructs a PSPrinter for a given graphical component and resolution. Chris@2: * @param c the component to be rendered in PostScript Chris@2: * @param res the resolution of the printer in pixels per inch; set res to Chris@2: * -1 for no scaling (avoids the apparently buggy cliprect) Chris@2: */ Chris@2: public PSPrinter(Component c, int res) { Chris@2: component = c; Chris@2: resolution = res; Chris@2: } // constructor Chris@2: Chris@2: /** Produces a print dialog and executes the requested print job. Chris@2: * The print job performs its task by callback of the Chris@2: * {@link PSPrinter#print(Graphics, PageFormat, int)} method. Chris@2: */ Chris@2: public void doPrint() { Chris@2: PrinterJob printJob = PrinterJob.getPrinterJob(); Chris@2: printJob.setPrintable(this); // tell it where the rendering code is Chris@2: if (printJob.printDialog()) { Chris@2: try { Chris@2: printJob.print(); Chris@2: } catch (Exception ex) { Chris@2: ex.printStackTrace(); Chris@2: } Chris@2: } Chris@2: } // doPrint() Chris@2: Chris@2: /** The callback method for performing the printing / Postscript conversion. Chris@2: * The resulting PostScript file requires some post-editing. Chris@2: * In particular, there are two problems to be dealt with: Chris@2: *
1) The file has no bounding box. This method prints the correct Chris@2: * bounding box to stardard output, and it must then be cut and pasted Chris@2: * into the PostScript file. (There must be a better way!) Chris@2: *
2) The cliprect is wrong (but only if the resolution is specified).
Chris@2: * This is solved by using resolution = -1 or by deleting the lines
Chris@2: * in the PostScript file from newpath to clip.
Chris@2: * (I don't know if this causes problems for components that try to draw
Chris@2: * outside of their area.)
Chris@2: * @param g the graphics object used for painting
Chris@2: * @param f the requested page format (e.g. A4)
Chris@2: * @param pg the page number (must be 0, or we report an error)
Chris@2: * @return the error status; if the page is successfully rendered,
Chris@2: * Printable.PAGE_EXISTS is returned, otherwise if a page number greater
Chris@2: * than 0 is requested, Printable.NO_SUCH_PAGE is returned
Chris@2: * @throws PrinterException thrown when the print job is terminated
Chris@2: */
Chris@2: public int print(Graphics g, PageFormat f, int pg) throws PrinterException {
Chris@2: if (pg >= 1)
Chris@2: return Printable.NO_SUCH_PAGE;
Chris@2: Graphics2D g2 = (Graphics2D) g;
Chris@2: double wd = component.getWidth();
Chris@2: double ht = component.getHeight();
Chris@2: double imwd = f.getImageableWidth();
Chris@2: double imht = f.getImageableHeight();
Chris@2: double corr = resolution / 72.0;
Chris@2: double scaleFactor = corr * Math.min(imwd / wd, imht / ht);
Chris@2: double xmin = f.getImageableX();
Chris@2: double ymin = f.getImageableY();
Chris@2: AffineTransform scale = new AffineTransform(scaleFactor, 0,
Chris@2: 0, scaleFactor,
Chris@2: corr * xmin, corr * ymin);
Chris@2: Format.setGroupingUsed(false);
Chris@2: double pgHt = f.getHeight();
Chris@2: if (resolution > 0) {
Chris@2: g2.setTransform(scale);
Chris@2: System.out.println("%%BoundingBox: " +
Chris@2: Format.d(xmin, 0) + " " +
Chris@2: Format.d(pgHt - ymin - ht * scaleFactor / corr, 0) + " " +
Chris@2: Format.d(xmin + wd * scaleFactor / corr, 0) + " " +
Chris@2: Format.d(pgHt - ymin, 0));
Chris@2: } else {
Chris@2: g2.setClip(0, 0, (int)wd, (int)ht);
Chris@2: System.out.println("%%BoundingBox: " +
Chris@2: Format.d(0, 0) + " " +
Chris@2: Format.d(pgHt - ht, 0) + " " +
Chris@2: Format.d(wd, 0) + " " +
Chris@2: Format.d(pgHt, 0));
Chris@2: }
Chris@2:
Chris@2: // System.out.println(f.getWidth() + " " + f.getHeight() + " " +
Chris@2: // f.getImageableX() + " " + f.getImageableY() + " " +
Chris@2: // f.getImageableWidth() + " " + f.getImageableHeight());
Chris@2: // Letter = 8.5x11" 612x792pt DEFAULT
Chris@2: // A4 = 210x297mm 595x842pt
Chris@2:
Chris@2: // AffineTransform scale = new AffineTransform(2.5, 0, 0, 2.5, 200, 1000);
Chris@2: // g2.setTransform(scale); // The figures need some fiddling.
Chris@2: // In particular, the PostScript file has:
Chris@2: // 1) no bounding box (add manually)
Chris@2: // 2) wrong cliprect (delete lines from
Chris@2: // "newpath" to "clip")
Chris@2: component.printAll(g2);
Chris@2: return Printable.PAGE_EXISTS;
Chris@2: } // print()
Chris@2:
Chris@2: } // class PSPrinter