/* * Copyright (c) 2012 European Synchrotron Radiation Facility, * Diamond Light Source Ltd. * * 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 */ package fable.framework.imageprint; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Drawable; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.printing.PrintDialog; import org.eclipse.swt.printing.Printer; import org.eclipse.swt.printing.PrinterData; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Shell; import fable.framework.imageprint.PrintSettings.Orientation; import fable.framework.toolbox.SWTUtils; public class ImagePrintUtils { private static final boolean debug = false; /** * Brings up a PrintDialog to print the given Control with the default print * settings. Calls printControl(control, null). * * @param control * The control to print. * @return */ public static boolean printControl(Control control) { return printControl(control, null); } /** * Brings up a PrintDialog to print the given Control. * * @param control * The control to print. * @param settings * The desired PrinterSettings or null to use the default. * @return If the operation was canceled or not. */ public static boolean printControl(Control control, PrintSettings settings) { // Get the Image if (control == null) return false; if (settings == null) { settings = new PrintSettings(); } Point size = control.getSize(); Image image = new Image(control.getDisplay(), size.x, size.y); if (image == null) return false; GC gc1 = new GC(control); gc1.copyArea(image, 0, 0); gc1.dispose(); boolean res = dialogPrintImage(control.getShell(), image, control .getDisplay().getDPI(), settings); // We create it, we dispose it if (image != null && !image.isDisposed()) image.dispose(); return res; } /** * Prints an image, first bringing up a PrintDialog. Works with a copy of * the image and disposes the copy when done. * * @param shell * The Shell parent for the dialog. * @param image * The image. Cannot be null. * @param imageDPI * The image DPI, typically the the Display DPI. Use (72, 72) if * you don't know what else to use. * @param settings * The desired PrinterSettings or null to use the default. * @return If the operation was canceled or not. */ public static boolean dialogPrintImage(Shell shell, Image image, Point imageDPI, PrintSettings settings) { if (image == null || image.isDisposed()) return false; if (settings == null) { settings = new PrintSettings(); } // Make a copy of the image so we can control its disposal and not worry // about whether it is changed under us Image image1 = new Image(image.getDevice(), image, SWT.IMAGE_COPY); // Start a print dialog PrintDialog dialog = new PrintDialog(shell, SWT.NONE); PrinterData printerData = settings.getPrinterData(); if (printerData != null) { dialog.setPrinterData(printerData); } PrinterData newPrinterData = dialog.open(); if (newPrinterData == null) { // User canceled if (image1 != null && !image1.isDisposed()) image1.dispose(); return false; } // Ask for a file if printToFile was selected if (newPrinterData.printToFile) { FileDialog fileDialog = new FileDialog(shell, SWT.OPEN); String file = fileDialog.open(); if (file != null) { newPrinterData.fileName = file; } else { if (image1 != null && !image1.isDisposed()) image1.dispose(); return false; } } // Get a printer Printer printer = new Printer(newPrinterData); if (printer == null) return false; // We are committed, set the printerData in the settings settings.setPrinterData(newPrinterData); printImage(image1, shell.getDisplay().getDPI(), settings); if (!printer.isDisposed()) printer.dispose(); return true; } /** * Prints an image without bringing up a PrintDialog. Runs a print job in a * separate thread. Works with a copy of the image and disposes the copy * when done. * * @param image * The image. Cannot be null. * @param imageDPI * The image DPI, typically the the Display DPI. Use (72, 72) if * you don't know what else to use. * @param settings * The desired PrinterSettings or null to use the default. */ public static void printImage(final Image image, final Point imageDPI, PrintSettings settings) { if (image == null || image.isDisposed()) return; final PrintSettings settings1; if (settings == null) { settings1 = new PrintSettings(); } else { settings1 = settings; } final Printer printer = new Printer(settings1.getPrinterData()); if (printer == null) return; final Image image1 = new Image(image.getDevice(), image, SWT.IMAGE_COPY); Thread printThread = new Thread() { public void run() { if (!printer.startJob("JavaImagePrinting")) { SWTUtils.errMsgAsync("Failed to start print job!"); if (!printer.isDisposed()) printer.dispose(); return; } drawImage(printer, printer.getDPI(), printer.getBounds(), image1, imageDPI, settings1); if (image1 != null && !image1.isDisposed()) image1.dispose(); printer.endPage(); printer.endJob(); if (!printer.isDisposed()) printer.dispose(); if (debug) System.out.println("Printing job done!"); } }; printThread.start(); } /** * A general method for drawing an image on a Drawable using parameters from * a PrintSettings. The image will not be disposed. * * @param drawable * Where to draw the image. Cannot be null. * @param drawableDPI * The DPI of the Drawable. If null will use (72,72). * @param bounds * The bounds of the area on the Drawable to use. * @param image * The image. Cannot be null. * @param imageDPI * The image DPI, typically the the Display DPI. If null will use * (72,72). * @param settings * The desired PrinterSettings or null to use the default. */ public static void drawImage(Drawable drawable, Point drawableDPI, Rectangle bounds, Image image, Point imageDPI, PrintSettings settings) { if (drawable == null || image == null || image.isDisposed()) return; if (settings == null) { settings = new PrintSettings(); } if (drawableDPI == null) { drawableDPI = new Point(72, 72); } if (imageDPI == null) { imageDPI = new Point(72, 72); } // Calculate parameters int imageWidth = image.getBounds().width; int imageHeight = image.getBounds().height; double dpiScaleFactorX = drawableDPI.x * 1.0 / imageDPI.x; double dpiScaleFactorY = drawableDPI.y * 1.0 / imageDPI.y; double left = settings.getLeft() * drawableDPI.x; double right = settings.getRight() * drawableDPI.x; double top = settings.getTop() * drawableDPI.y; double bottom = settings.getBottom() * drawableDPI.y; int drawableWidth = bounds.width; int drawableHeight = bounds.height; Rectangle trim; if (drawable instanceof Printer) { trim = ((Printer) drawable).computeTrim(0, 0, 0, 0); } else { trim = new Rectangle(0, 0, 0, 0); } int leftMargin = (int) (left) + trim.x; int rightMargin = drawableWidth - (int) (right) + trim.x; int topMargin = (int) (top) + trim.y; int bottomMargin = drawableHeight - (int) (bottom) + trim.y; int availableWidth = rightMargin - leftMargin; int availableHeight = bottomMargin - topMargin; if (availableWidth <= 0) { SWTUtils.errMsgAsync("Horizontal margins are too large!"); return; } if (availableHeight <= 0) { SWTUtils.errMsgAsync("Vertical margins are too large!"); return; } // If the image is too large to draw on a page, reduce its // width and height proportionally. double imageSizeFactor = Math.min(1, (rightMargin - leftMargin) * 1.0 / (dpiScaleFactorX * imageWidth)); imageSizeFactor = Math.min(imageSizeFactor, (bottomMargin - topMargin) * 1.0 / (dpiScaleFactorY * imageHeight)); int drawnWidth = (int) (dpiScaleFactorX * imageSizeFactor * imageWidth); int drawnHeight = (int) (dpiScaleFactorX * imageSizeFactor * imageHeight); if (debug) { System.out.println("drawImage\n"); System.out .println("dpi=" + imageDPI + " printerDPI=" + drawableDPI); System.out.println("dpiScaleFactorX=" + dpiScaleFactorX + " dpiScaleFactorY=" + dpiScaleFactorY); System.out.println("printerWidth=" + drawableWidth + " printerHeight=" + drawableHeight); System.out.println("drawnWidth=" + drawnWidth + " drawnHeight=" + drawnHeight); System.out.println("availableWidth=" + availableWidth + " availableHeight=" + availableHeight); System.out.println("left=" + left + " right=" + right + " top=" + top + " bottom=" + bottom); System.out.println("leftMargin=" + leftMargin + " rightMargin=" + rightMargin + "topMargin=" + topMargin + " bottomMargin=" + bottomMargin); System.out.println("imageSizeFactor=" + imageSizeFactor); } // Handle FILL if (settings.getHorizontalAlign() == SWT.FILL) { drawnWidth = availableWidth; } if (settings.getVerticalAlign() == SWT.FILL) { drawnHeight = availableHeight; } // Align if (drawnWidth < availableWidth) { switch (settings.getHorizontalAlign()) { case SWT.LEFT: break; case SWT.CENTER: leftMargin += (availableWidth - drawnWidth) / 2; break; case SWT.RIGHT: leftMargin += (availableWidth - drawnWidth); break; } } if (drawnHeight < availableHeight) { switch (settings.getVerticalAlign()) { case SWT.TOP: break; case SWT.CENTER: topMargin += (availableHeight - drawnHeight) / 2; break; case SWT.BOTTOM: topMargin += (availableHeight - drawnHeight); break; } } // Draw the image to the drawable GC gc = new GC(drawable); gc.drawImage(image, 0, 0, imageWidth, imageHeight, bounds.x + leftMargin, bounds.y + topMargin, drawnWidth, drawnHeight); gc.dispose(); } /** * A general method for painting a canvas with a print preview in response * to a PaintEvent. This method draws the page area on the canvas, scaling * it appropriately, then calls drawImage, which handles the drawing of the * image on the page. The image will not be disposed. * * @param ev * The paint event. * @param canvas * The canvas. * @param gc * The GC to use. * @param printer * The desired Printer or null to use the default printer. * @param image * The image. Cannot be null. * @param settings * The desired PrinterSettings or null to use the default. */ public static void paintPreview(GC gc, Canvas canvas, Rectangle canvasBounds, Image image, PrintSettings settings) { // Handle paint events coming after things are disposed if (canvas == null || canvas.isDisposed() || image == null || image.isDisposed() || settings == null || canvasBounds == null) { return; } Printer printer = new Printer(settings.getPrinterData()); Display display = canvas.getDisplay(); Point displayDPI = display.getDPI(); Point printerDPI = printer.getDPI(); // The bounds are the size of the printer, not the printable area, which // comes from getClientArea Rectangle printerBounds = printer.getBounds(); if (settings.getOrientation() == Orientation.LANDSCAPE) { printerBounds = new Rectangle(printerBounds.y, printerBounds.x, printerBounds.height, printerBounds.width); // TODO This may not be right, but presumably they are the same most // of // the time anyway printerDPI.x = printer.getDPI().y; printerDPI.y = printer.getDPI().x; } // Adjust the canvas bounds to have the same aspect ratio as the printer // and center inside the given bounds double printerAspect = (double) printerBounds.height / (double) printerBounds.width; double canvasAspect = (double) canvasBounds.height / (double) canvasBounds.width; if (canvasAspect > printerAspect) { // Canvas bounds is higher int newHeight = (int) (canvasBounds.height * printerAspect / canvasAspect + .5); canvasBounds.y += (canvasBounds.height - newHeight) / 2; canvasBounds.height = newHeight; } else { // Canvas bounds is wider int newWidth = (int) (canvasBounds.width * canvasAspect / printerAspect + .5); canvasBounds.x += (canvasBounds.width - newWidth) / 2; canvasBounds.width = newWidth; } // Adjust the print margins double scaleFactor = (double) canvasBounds.width / printerBounds.width * printerDPI.x / displayDPI.x; PrintSettings scaledSettings = settings.clone(); scaledSettings.setLeft(scaleFactor * scaledSettings.getLeft()); scaledSettings.setRight(scaleFactor * scaledSettings.getRight()); scaledSettings.setTop(scaleFactor * scaledSettings.getTop()); scaledSettings.setBottom(scaleFactor * scaledSettings.getBottom()); // Draw on the canvas gc.setBackground(display.getSystemColor(SWT.COLOR_WHITE)); gc.fillRectangle(canvasBounds); ImagePrintUtils.drawImage(canvas, displayDPI, canvasBounds, image, display.getDPI(), scaledSettings); // Dispose the printer (but not the GC) if (!printer.isDisposed()) printer.dispose(); } }