/* * Copyright (c) 2006 Matthew Hall and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Matthew Hall - initial API and implementation */ package org.eclipse.nebula.paperclips.core; import java.util.ArrayList; import java.util.List; import org.eclipse.nebula.paperclips.core.internal.util.PaperClipsUtil; import org.eclipse.nebula.paperclips.core.internal.util.Util; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.printing.Printer; import org.eclipse.swt.printing.PrinterData; /** * This class contains static constants and methods for preparing and printing * documents. Methods in this class supersede those in PrintUtil. * * @author Matthew Hall */ public class PaperClips { private PaperClips() { } // no instances static boolean debug = false; /** * Indicates that the printer's default page orientation should be used. */ public static final int ORIENTATION_DEFAULT = SWT.DEFAULT; /** * Indicates portrait page orientation. */ public static final int ORIENTATION_PORTRAIT = SWT.VERTICAL; /** * Indicates landscape page orientation. */ public static final int ORIENTATION_LANDSCAPE = SWT.HORIZONTAL; /** * Triggers an appropriate exception based on the passed in error code. * * @param code * the SWT error code. */ public static void error(int code) { SWT.error(code, null); } /** * Triggers an unspecified exception with the passed in detail. * * @param detail * more information about error. */ public static void error(String detail) { SWT.error(SWT.ERROR_UNSPECIFIED, null, detail); } /** * Triggers an appropriate exception based on the passed in error code. * * @param code * the SWT error code. * @param detail * more information about error. */ public static void error(int code, String detail) { SWT.error(code, null, detail); } /** * <b>EXPERIMENTAL</b>: Sets whether debug mode is enabled. This mode may be * used for troubleshooting documents that cannot be laid out for some * reason (e.g. "Cannot layout page x" error occurs). * * <p> * <b>THIS API IS EXPERIMENTAL AND MAY BE REMOVED OR CHANGED IN THE * FUTURE.</b> * * @param debug * true to enable debug mode, false to disable it. */ public static void setDebug(boolean debug) { PaperClips.debug = debug; } /** * <b>EXPERIMENTAL</b>: Returns whether debug mode is enabled. * * <p> * <b>THIS API IS EXPERIMENTAL AND MAY BE REMOVED OR CHANGED IN THE * FUTURE.</b> * * @return whether debug mode is enabled. */ public static boolean getDebug() { return debug; } /** * Returns a PrinterData for the system-default printer, or the first * printer if no default printer is configured. * * @return a PrinterData for the system-default printer, or the first * printer if no default printer is configured. */ public static PrinterData getDefaultPrinterData() { PrinterData printerData = Printer.getDefaultPrinterData(); if (printerData == null) { // Linux may have one or more printers without a default printer PrinterData[] list = Printer.getPrinterList(); if (list.length > 0) printerData = list[0]; } return printerData; } /** * Calls iterator.next(width, height) and returns the result. This method * checks multiple conditions to ensure proper usage and behavior of * PrintIterators. * <p> * This method is intended to be used by PrintIterator classes, as a * results-checking alternative to calling next(int, int) directly on the * target iterator. All PrintIterator classes in the PaperClips library use * this method instead of directly calling the * {@link PrintIterator#next(int, int)} method. * * @param iterator * the PrintIterator * @param width * the available width. * @param height * the available height. * @return the next portion of the Print, or null if the width and height * are not enough to display any of the iterator's contents. */ public static PrintPiece next(PrintIterator iterator, int width, int height) { Util.notNull(iterator); if (width < 0 || height < 0) error(SWT.ERROR_INVALID_ARGUMENT, "PrintPiece size " + width + "x" + height + " not possible"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ if (!iterator.hasNext()) error("Iterator " + iterator + " has no more content."); //$NON-NLS-1$ //$NON-NLS-2$ PrintPiece result = iterator.next(width, height); if (result != null) { Point size = result.getSize(); if (size.x > width || size.y > height) error("Iterator " + iterator + " produced a " + size.x + "x" + size.y + " piece for a " + width //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + "x" + height + " area."); //$NON-NLS-1$//$NON-NLS-2$ } else if (debug) { return new NullPrintPiece(); } return result; } /** * Prints the print job to the given printer. This method constructs a * Printer, forwards to {@link #print(PrintJob, Printer)}, and disposes the * printer before returning. * * @param printJob * the print job. * @param printerData * the PrinterData of the selected printer. */ public static void print(PrintJob printJob, PrinterData printerData) { Printer printer = new Printer(printerData); try { print(printJob, printer); } finally { printer.dispose(); } } /** * Prints the print job to the given printer. * * @param printJob * the print job. * @param printer * the printer device. */ public static void print(PrintJob printJob, Printer printer) { // Bug in SWT on OSX: If Printer.startJob() is not called first, the GC // will be disposed by // default. startJob(printer, printJob.getName()); boolean completed = false; try { GC gc = createAndConfigureGC(printer); try { print(printJob, printer, gc); } finally { gc.dispose(); } printer.endJob(); completed = true; } finally { if (!completed) cancelJob(printer); } } private static void startJob(Printer printer, String jobName) { if (!printer.startJob(jobName)) error("Unable to start print job"); //$NON-NLS-1$ } private static void cancelJob(Printer printer) { if (isGTK()) printer.endJob(); // Printer.cancelJob() not implemented on GTK else printer.cancelJob(); } private static GC createAndConfigureGC(Printer printer) { GC gc = new GC(printer); gc.setAdvanced(true); return gc; } /** * Prints the print job to the specified printer using the GC. This method * does not manage the print job lifecycle (it does not call startJob or * endJob). * * @param printJob * the print job * @param printer * the printer * @param gc * the GC */ private static void print(PrintJob printJob, Printer printer, final GC gc) { final PrinterData printerData = printer.getPrinterData(); PrintPiece[] pages = getPages(printJob, printer, gc); int startPage = 0; int endPage = pages.length - 1; if (printerData.scope == PrinterData.PAGE_RANGE) { // Convert from PrinterData's one-based indices to our zero-based // indices startPage = Math.max(startPage, printerData.startPage - 1); endPage = Math.min(endPage, printerData.endPage - 1); } final int collatedCopies; final int noncollatedCopies; if (printerData.collate) { // always false if printer driver performs // collation collatedCopies = printerData.copyCount; // always 1 if printer // driver handles copy count noncollatedCopies = 1; } else { noncollatedCopies = printerData.copyCount; // always 1 if printer // driver handles copy // count collatedCopies = 1; } printPages(printer, gc, pages, startPage, endPage, collatedCopies, noncollatedCopies); } private static void printPages(final Printer printer, final GC gc, final PrintPiece[] pages, final int startPage, final int endPage, final int collatedCopies, final int noncollatedCopies) { disposeUnusedPages(pages, startPage, endPage); Rectangle paperBounds = getPaperBounds(printer); final int x = paperBounds.x; final int y = paperBounds.y; try { for (int collated = 0; collated < collatedCopies; collated++) { for (int pageIndex = startPage; pageIndex <= endPage; pageIndex++) { for (int noncollated = 0; noncollated < noncollatedCopies; noncollated++) { if (printer.startPage()) { pages[pageIndex].paint(gc, x, y); pages[pageIndex].dispose(); printer.endPage(); } else { error("Unable to start page " + pageIndex); //$NON-NLS-1$ } } } } } finally { PaperClipsUtil.dispose(pages); } } private static void disposeUnusedPages(PrintPiece[] pages, int startPage, int endPage) { PaperClipsUtil.dispose(pages, 0, startPage); PaperClipsUtil.dispose(pages, endPage + 1, pages.length); } /** * Processes the print job and returns an array of pages for the given * printer device. Each element in the returned array has already had the * page orientation and page margins applied. Therefore, when calling the * paint(GC, int, int) method on each page, the printer's trim should be * provided as the x and y arguments. In other words, the trim is taken as a * minimum margin while applying calculating margins, but the position where * the page's content is drawn is determined solely by the margin, and is * not offset by the trim. This behavior is helpful for screen display, and * is already compensated for in the {@link #print(PrintJob, Printer)} * method. * * @param printer * the printing device. * @param printJob * the print job. * @return an array of all pages of the print job. Each element of the * returned array represents one page in the printed document. */ public static PrintPiece[] getPages(PrintJob printJob, Printer printer) { startDummyJob(printer, printJob.getName()); try { GC gc = createAndConfigureGC(printer); try { return getPages(printJob, printer, gc); } finally { gc.dispose(); } } finally { endDummyJob(printer); } } /** * Starts a dummy job on the given Printer if the platform requires it. * Dummy jobs allow the various Print components of PaperClips to perform * measurements required for document layout, without actually sending a job * to the printer. Only Mac OS X Carbon and Linux GTK+ are known to require * dummy jobs. * * @param printer * the Printer hosting the dummy print job. * @param name * the name of the dummy print job. */ public static void startDummyJob(Printer printer, String name) { // On Mac OS X Carbon and Linux GTK+, created GC is disposed unless // Printer.startJob() is called // first. if (isCarbon() || isGTK()) startJob(printer, name); } /** * Ends a dummy job on the given Printer if the platform requires a dummy * job. * * @param printer * the Printer hosting the dummy print job. */ public static void endDummyJob(Printer printer) { if (isGTK()) { // Linux GTK // Printer.cancelJob() is not implemented in SWT since GTK has no // API for cancelling a print job. For now we must use endJob(), // even though it spits out an empty page. // printer.cancelJob(); // Not implemented in SWT on GTK printer.endJob(); // See also: // http://bugzilla.gnome.org/show_bug.cgi?id=339323 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=212594 } else if (isCarbon()) // Mac OSX // 2007-04-30: A bug in SWT on Mac OSX prior to 3.3 renders Printer // instances useless after a call to cancelJob(). // Therefore on Mac OSX we call endJob() instead of cancelJob(). if (SWT.getVersion() < 3346) { // Version 3.3 printer.endJob(); } else { printer.cancelJob(); } } private static boolean isCarbon() { return SWT.getPlatform().equals("carbon"); //$NON-NLS-1$ } private static boolean isGTK() { return SWT.getPlatform().equals("gtk"); //$NON-NLS-1$ } private static PrintPiece[] getPages(PrintJob printJob, Printer printer, GC gc) { PageEnumeration enumeration = new PageEnumeration(printJob, printer, gc); List pages = new ArrayList(); while (enumeration.hasNext()) { PrintPiece page = enumeration.nextPage(); if (page == null) { int pageNumber = pages.size() + 1; PaperClipsUtil.dispose(pages); error("Unable to layout page " + pageNumber); //$NON-NLS-1$ } pages.add(page); } return (PrintPiece[]) pages.toArray(new PrintPiece[pages.size()]); } /** * Returns a {@link PageEnumeration} for the passed in PrintJob on the given * Printer, using the given GC. The Printer and GC must not be disposed * while the enumeration is in use. * * @param printJob * the print job * @param printer * the Printer device, which must not be disposed while the * PageEnumeration is in use. * @param gc * the GC, which must not be disposed while the PageEnumeration * is in use. * @return a {@link PageEnumeration} for the passed in PrintJob. */ public static PageEnumeration getPageEnumeration(PrintJob printJob, Printer printer, GC gc) { return new PageEnumeration(printJob, printer, gc); } /** * Returns the bounding rectangle of the paper, including non-printable * margins. * * @param printer * the printer device. * @return a rectangle whose edges correspond to the edges of the paper. */ public static Rectangle getPaperBounds(Printer printer) { Rectangle rect = getPrintableBounds(printer); return printer.computeTrim(rect.x, rect.y, rect.width, rect.height); } /** * Returns the bounding rectangle of the printable area on the paper. * * @param printer * the printer device. * @return the bounding rectangle of the printable area on the paper. */ public static Rectangle getPrintableBounds(Printer printer) { return printer.getClientArea(); } /** * Returns the bounding rectangle of the printable area which is inside the * given margins on the paper. The printer's minimum margins are reflected * in the returned rectangle. * * @param printer * the printer device. * @param margins * the desired page margins. * @return the bounding rectangle on the printable area which is within the * margins. */ public static Rectangle getMarginBounds(Margins margins, Printer printer) { Rectangle paperBounds = getPaperBounds(printer); // Calculate the pixel coordinates for the margins Point dpi = printer.getDPI(); int top = paperBounds.y + (margins.top * dpi.y / 72); int left = paperBounds.x + (margins.left * dpi.x / 72); int right = paperBounds.x + paperBounds.width - (margins.right * dpi.x / 72); int bottom = paperBounds.y + paperBounds.height - (margins.bottom * dpi.y / 72); // Enforce the printer's minimum margins. Rectangle printableBounds = getPrintableBounds(printer); if (top < printableBounds.y) top = printableBounds.y; if (left < printableBounds.x) left = printableBounds.x; if (right > printableBounds.x + printableBounds.width) right = printableBounds.x + printableBounds.width; if (bottom > printableBounds.y + printableBounds.height) bottom = printableBounds.y + printableBounds.height; return new Rectangle(left, top, right - left, bottom - top); } }