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