org.eclipse.gmf.runtime.diagram.ui.printing.internal.util.SWTDiagramPrinter.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.gmf.runtime.diagram.ui.printing.internal.util.SWTDiagramPrinter.java

Source

/******************************************************************************
 * Copyright (c) 2008, 2009 IBM Corporation 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:
 *    IBM Corporation - initial API and implementation 
 ****************************************************************************/

package org.eclipse.gmf.runtime.diagram.ui.printing.internal.util;

import java.util.Iterator;

import org.eclipse.core.runtime.Assert;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.SWTGraphics;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.LayerConstants;
import org.eclipse.gef.RootEditPart;
import org.eclipse.gmf.runtime.common.ui.util.DisplayUtils;
import org.eclipse.gmf.runtime.diagram.core.preferences.PreferencesHint;
import org.eclipse.gmf.runtime.diagram.core.util.ViewUtil;
import org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramRootEditPart;
import org.eclipse.gmf.runtime.diagram.ui.internal.pagesetup.PageInfoHelper;
import org.eclipse.gmf.runtime.diagram.ui.internal.pagesetup.PageInfoHelper.PageMargins;
import org.eclipse.gmf.runtime.diagram.ui.internal.properties.WorkspaceViewerProperties;
import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramEditor;
import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramGraphicalViewer;
import org.eclipse.gmf.runtime.diagram.ui.util.DiagramEditorUtil;
import org.eclipse.gmf.runtime.draw2d.ui.internal.graphics.MapModeGraphics;
import org.eclipse.gmf.runtime.draw2d.ui.internal.graphics.PrinterGraphics;
import org.eclipse.gmf.runtime.draw2d.ui.internal.graphics.ScaledGraphics;
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode;
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil;
import org.eclipse.gmf.runtime.draw2d.ui.render.internal.graphics.RenderedScaledGraphics;
import org.eclipse.gmf.runtime.notation.Diagram;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.printing.Printer;
import org.eclipse.swt.printing.PrinterData;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;

/**
 * This class supports printing using the SWT printing constructs.
 * Much of the paging code was derived from the previous <code> DiagramPrinter </code>.
 *
 * @author Wayne Diu, wdiu
 */
public class SWTDiagramPrinter extends DiagramPrinter implements Runnable {

    protected Printer printer;

    private GC gc;

    private PrinterGraphics printerGraphics;

    private Point printerOffset;

    private Rectangle logicalClientArea;

    /**
     * Creates a new instance. The following variables must be initialized
     * before calling <code>run()</code>:
     * <li><code>printer</code></li>
     * <li><code>display_dpi</code></li>
     * <li><code>diagrams</code></li>
     * @param mm <code>IMapMode</code> to do the coordinate mapping
     */
    public SWTDiagramPrinter(PreferencesHint preferencesHint, IMapMode mm) {
        super(preferencesHint, mm);
    }

    /**
     * Creates a new instance. The following variables must be initialized
     * before calling <code>run()</code>:
     * <li><code>printer</code></li>
     * <li><code>display_dpi</code></li>
     * <li><code>diagrams</code></li>
     * @param mapMode <code>IMapMode</code> to do the coordinate mapping
     */
    public SWTDiagramPrinter(PreferencesHint preferencesHint) {
        this(preferencesHint, MapModeUtil.getMapMode());
    }

    /**
    * Sets the printer.
    * 
    * @param printer
    *            The printer to set.
    */
    public void setPrinter(Printer printer) {
        this.printer = printer;
    }

    /**
     * Prints the contents of the diagram editor part.
     */
    public void run() {
        assert null != printer : "printer must be set"; //$NON-NLS-1$
        if (!(printer.startJob("Printing"))) { //$NON-NLS-1$
            return;
        }

        assert diagrams != null;
        Iterator<Diagram> it = diagrams.iterator();

        Shell shell = new Shell();
        try {
            while (it.hasNext()) {
                Object obj = it.next();
                //the diagrams List is only supposed to have Diagram objects
                Assert.isTrue(obj instanceof Diagram);
                Diagram diagram = (Diagram) obj;
                DiagramEditor openedDiagramEditor = DiagramEditorUtil
                        .findOpenedDiagramEditorForID(ViewUtil.getIdStr(diagram));
                DiagramEditPart dgrmEP = openedDiagramEditor == null
                        ? PrintHelperUtil.createDiagramEditPart(diagram, preferencesHint, shell)
                        : openedDiagramEditor.getDiagramEditPart();

                boolean loadedPreferences = openedDiagramEditor != null
                        || PrintHelperUtil.initializePreferences(dgrmEP, preferencesHint);

                RootEditPart rep = dgrmEP.getRoot();
                if (rep instanceof DiagramRootEditPart)
                    this.mapMode = ((DiagramRootEditPart) rep).getMapMode();

                initialize();

                IPreferenceStore pref = null;

                assert dgrmEP.getViewer() instanceof DiagramGraphicalViewer;

                pref = ((DiagramGraphicalViewer) dgrmEP.getViewer()).getWorkspaceViewerPreferenceStore();

                if (pref.getBoolean(WorkspaceViewerProperties.PREF_USE_WORKSPACE_SETTINGS)) {

                    //get workspace settings...
                    if (dgrmEP.getDiagramPreferencesHint().getPreferenceStore() != null) {
                        pref = (IPreferenceStore) dgrmEP.getDiagramPreferencesHint().getPreferenceStore();
                    }
                }

                // Ensure the preference value is properly updated when the
                // user overrides the preference store with values from the
                // print settings.
                // Printing and preview use the preference settings, to
                // calculate page size.
                PrinterData printerData = printer.getPrinterData();
                if (printerData != null) {
                    boolean useLandscape = (printerData.orientation == PrinterData.LANDSCAPE);
                    if (pref.getBoolean(WorkspaceViewerProperties.PREF_USE_LANDSCAPE) != useLandscape) {
                        pref.setValue(WorkspaceViewerProperties.PREF_USE_LANDSCAPE, useLandscape);
                    }
                    if (pref.getBoolean(WorkspaceViewerProperties.PREF_USE_PORTRAIT) == useLandscape) {
                        pref.setValue(WorkspaceViewerProperties.PREF_USE_PORTRAIT, !useLandscape);
                    }
                }

                doPrintDiagram(dgrmEP, loadedPreferences, pref);

                dispose();
            }
            printer.endJob();
        } finally {
            shell.dispose();
        }
    }

    /**
     * Prints to scale or prints to rows x columns pages
     */
    protected void doPrintDiagram(DiagramEditPart dgrmEP, boolean loadedPreferences,
            IPreferenceStore fPreferences) {
        this.graphics.pushState();
        if (isScaledPercent) {
            printToScale(dgrmEP, loadedPreferences, fPreferences);
        } else {
            printToPages(dgrmEP, loadedPreferences, fPreferences);
        }
        this.graphics.popState();
    }

    protected void initialize() {

        assert null != printer : "printer must be set"; //$NON-NLS-1$

        //check for rtl orientation...
        int style = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell().getStyle();
        if ((style & SWT.MIRRORED) != 0)
            this.gc = new GC(printer, SWT.RIGHT_TO_LEFT);
        else
            this.gc = new GC(printer);

        gc.setXORMode(false);

        this.swtGraphics = new SWTGraphics(gc);
        this.printerGraphics = createPrinterGraphics(swtGraphics);
        this.graphics = createMapModeGraphics(printerGraphics);
        this.graphics.scale(computePrinterDisplayScale());

        this.logicalClientArea = this.graphics.getClip(new Rectangle(this.printer.getClientArea()));

    }

    /**
     * Usually, the printable area is less than the page.
     * This method returns the offset for each x margin and each y margin.
     * x margins are left and right
     * y margins are top and bottom
     * 
     * We'll assume the left and right offsets are the same and the
     * top and bottom offsets are the same.
     * 
     * @return Point with x and y offsets
     */
    protected Point getPrinterOffset() {
        if (printerOffset == null) {
            int offsetX = this.printer.getBounds().width - this.printer.getClientArea().width;
            int offsetY = this.printer.getBounds().height - this.printer.getClientArea().height;

            // assume half on each side
            offsetX = (int) (getMapMode().DPtoLP((int) (offsetX / 2.0f * display_dpi.x / printer.getDPI().x))
                    / userScale);
            offsetY = (int) (getMapMode().DPtoLP((int) (offsetY / 2.0f * display_dpi.y / printer.getDPI().y))
                    / userScale);

            printerOffset = new Point(offsetX, offsetY);
        }

        return printerOffset;
    }

    /**
     * Print the diagram figure using specified scale factor.
     * 
     * @param dgrmEP the DiagramEditPart that will be printed
     * @param loadedPreferences true if existing prefs could be loaded
     * successfully, false if not and defaults are being used.  This parameter
     * is important to obtain the correct page break bounds.
     * @param fPreferences the preferenceStore that could either contain
     * existing preferences or defaults
     */
    protected void printToScale(DiagramEditPart dgrmEP, boolean loadedPreferences, IPreferenceStore fPreferences) {

        assert null != printer : "printer must be set"; //$NON-NLS-1$
        Rectangle figureBounds = PrintHelperUtil.getPageBreakBounds(dgrmEP, loadedPreferences);
        org.eclipse.draw2d.geometry.Point pageBounds = PageInfoHelper.getPageSize(fPreferences, getMapMode());

        //translate to offset initial figure position
        translated = new Point((int) (-figureBounds.x * userScale), (int) (-figureBounds.y * userScale));

        //calculate the number of page rows and columns
        int numRows = 0, numCols = 0;

        PageMargins margins = PageInfoHelper.getPageMargins(fPreferences, getMapMode());
        adjustMargins(margins, userScale, getPrinterOffset());

        GC gc_ = new GC(DisplayUtils.getDisplay(), this.gc.getStyle());
        gc_.setAntialias(this.gc.getAntialias());

        FontData fontData = JFaceResources.getDefaultFont().getFontData()[0];
        Font font = new Font(printer, fontData);

        org.eclipse.draw2d.geometry.Point pageCount = getPageCount(dgrmEP, figureBounds, pageBounds, true);
        numCols = pageCount.x;
        numRows = pageCount.y;

        //finalRow and finalColumn will be used if we are printing within a page range...
        int row = 1, col = 1, finalRow = 0, finalColumn = 0;

        if (this.printRangePageSelection) {
            //print only the pages specified in the page range...
            row = calculateRowFromPage(this.pageFrom, numCols);
            col = calculateColumnFromPage(this.pageFrom, numCols, row);

            finalRow = calculateRowFromPage(this.pageTo, numCols);
            finalColumn = calculateColumnFromPage(this.pageTo, numCols, finalRow);
        }

        try {
            //print the pages in row, column order
            for (; row <= numRows; row++) {
                for (; col <= numCols; col++) {
                    printer.startPage();
                    drawPage(gc_, dgrmEP, fPreferences, figureBounds, margins, font, row, col);
                    printer.endPage();

                    if (row == finalRow && col == finalColumn && this.printRangePageSelection == true)
                        break;
                }

                if (row == finalRow && col == finalColumn && this.printRangePageSelection == true)
                    break;

                col = 1;
            }
        } finally {
            //must dispose resources
            font.dispose();
            gc_.dispose();
        }
    }

    /**
     * Draw the header and footer
     * 
     * @param gc_,
     *            a graphics context that is not null which this method will use
     *            for figuring ouyt the font's extent
     * @param figureBounds,
     *            Rectangle with the bounds of the figure
     * @param rowIndex,
     *            int
     * @param colIndex,
     *            int
     */
    protected void drawHeaderAndFooter(GC gc_, DiagramEditPart dgrmEP, Rectangle figureBounds, Font font,
            int rowIndex, int colIndex) {

        int width = this.logicalClientArea.width;
        int height = this.logicalClientArea.height;

        this.graphics.pushState(); //draw text, don't make it too small or big
        this.graphics.setFont(font);

        this.graphics.scale(1.0f / userScale);
        this.graphics.translate(-translated.x, -translated.y);

        String headerOrFooter = HeaderAndFooterHelper
                .makeHeaderOrFooterString(WorkspaceViewerProperties.HEADER_PREFIX, rowIndex, colIndex, dgrmEP);

        this.graphics.drawText(headerOrFooter,
                getMapMode().DPtoLP(HeaderAndFooterHelper.LEFT_MARGIN_DP)
                        + (width - getMapMode().DPtoLP(gc_.textExtent(headerOrFooter).x)) / 2,
                getMapMode().DPtoLP(HeaderAndFooterHelper.TOP_MARGIN_DP));

        headerOrFooter = HeaderAndFooterHelper.makeHeaderOrFooterString(WorkspaceViewerProperties.FOOTER_PREFIX,
                rowIndex, colIndex, dgrmEP);

        this.graphics.drawText(headerOrFooter,
                getMapMode().DPtoLP(HeaderAndFooterHelper.LEFT_MARGIN_DP)
                        + (width - getMapMode().DPtoLP(gc_.textExtent(headerOrFooter).x)) / 2,
                height - getMapMode().DPtoLP(HeaderAndFooterHelper.BOTTOM_MARGIN_DP));

        this.graphics.popState(); //for drawing the text

    }

    /**
     * This method paints a portion of the diagram. (The area painted
     * representing one page.)
     * 
     * @param gc_ a graphics context that is not null which this method will use
     * for figuring out the font's extent
     * @param dgrmEP the DiagramEditPart that will be printed
     * @param fPreferences the preferenceStore that could either contain
     * existing preferences or defaults
     * @param figureBounds the page break bounds we'll have to offset by
     * @param font the Font to print the header or footer with
     * @param rowIndex index of row we're printing
     * @param colIndex index of column we're priniting
     * to check if it is the first time the method is getting called for the current
     * print.
     */
    protected void drawPage(GC gc_, DiagramEditPart dgrmEP, IPreferenceStore fPreferences, Rectangle figureBounds,
            PageMargins margins, Font font, int rowIndex, int colIndex) {

        org.eclipse.draw2d.geometry.Point pageSize = PageInfoHelper.getPageSize(fPreferences, false, getMapMode());
        boolean rtlEnabled = (this.gc != null) && ((this.gc.getStyle() & SWT.MIRRORED) != 0);
        if (rtlEnabled) {
            // draw everything on an offscreen image first and then draw that image
            // onto the printer gc...this takes care of certain drawing bugs.
            // This causes issues with printing resolution as it uses a display image
            // which is typically 72dpi
            // This code should be removed when a resolution to Bugzilla 162459 is found

            Image image = new Image(DisplayUtils.getDisplay(), getMapMode().LPtoDP(pageSize.x),
                    getMapMode().LPtoDP(pageSize.y));

            GC imgGC = new GC(image, (rtlEnabled) ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT);
            imgGC.setXORMode(false);

            SWTGraphics sg = new SWTGraphics(imgGC);

            //for scaling
            ScaledGraphics g1 = new RenderedScaledGraphics(sg);

            //for himetrics and svg
            MapModeGraphics mmg = createMapModeGraphics(g1);

            //if mmg's font is null, gc.setFont will use a default font
            imgGC.setFont(mmg.getFont());

            internalDrawPage(dgrmEP, figureBounds, fPreferences, margins, mmg, rowIndex, colIndex, true);

            this.graphics.pushState();

            this.graphics.drawImage(image, 0, 0);

            this.graphics.popState();

            //draw the header and footer after drawing the image to avoid getting the image getting drawn over them
            drawHeaderAndFooter(gc_, dgrmEP, figureBounds, font, rowIndex, colIndex);
            disposeImageVars(imgGC, image, sg, g1, mmg);
        } else {
            internalDrawPage(dgrmEP, figureBounds, fPreferences, margins, this.graphics, rowIndex, colIndex, false);
            //draw the header and footer after drawing the image to avoid getting the image getting drawn over them
            drawHeaderAndFooter(gc_, dgrmEP, figureBounds, font, rowIndex, colIndex);
        }
    }

    protected void internalDrawPage(DiagramEditPart dgrmEP, Rectangle figureBounds, IPreferenceStore fPreferences,
            PageMargins margins, Graphics g, int rowIndex, int colIndex, boolean RTL_ENABLED) {
        org.eclipse.draw2d.geometry.Point pageSize = PageInfoHelper.getPageSize(fPreferences, false, getMapMode());

        int width = pageSize.x, height = pageSize.y;

        g.pushState();

        g.translate(translated.x, translated.y);
        g.scale(userScale);

        int translateX = -(width * (colIndex - 1));
        int translateY = -(height * (rowIndex - 1));

        int scaledTranslateX = (int) (translateX / userScale);
        int scaledTranslateY = (int) (translateY / userScale);

        int scaledWidth = (int) (width / userScale);
        int scaledHeight = (int) (height / userScale);

        if (RTL_ENABLED) {
            scaledTranslateX += (margins.left * (colIndex - 1)) + (margins.right * (colIndex));
            scaledTranslateY += ((margins.top * rowIndex) + (margins.bottom * (rowIndex - 1)));
        } else {
            scaledTranslateX += ((margins.left * colIndex) + (margins.right * (colIndex - 1)));
            scaledTranslateY += ((margins.top * rowIndex) + (margins.bottom * (rowIndex - 1)));
        }

        g.translate(scaledTranslateX, scaledTranslateY);

        Rectangle clip = new Rectangle(
                (scaledWidth - margins.left - margins.right) * (colIndex - 1) + figureBounds.x,
                (scaledHeight - margins.bottom - margins.top) * (rowIndex - 1) + figureBounds.y,
                scaledWidth - margins.right - margins.left, scaledHeight - margins.top - margins.bottom);
        g.clipRect(clip);

        dgrmEP.getLayer(LayerConstants.PRINTABLE_LAYERS).paint(g);

        g.popState();
    }

    /**
     * Print the diagram figure to fit the number and rows and columns
     * specified by the user.
     * 
     * @param dgrmEP the DiagramEditPart that will be printed
     * @param loadedPreferences true if existing prefs could be loaded
     * successfully, false if not and defaults are being used.  This parameter
     * is important to obtain the correct page break bounds.
     * @param fPreferences the preferenceStore that could either contain
     * existing preferences or defaults
     */
    protected void printToPages(DiagramEditPart dgrmEP, boolean loadedPreferences, IPreferenceStore fPreferences) {
        assert null != printer : "printer must be set"; //$NON-NLS-1$

        Rectangle figureBounds = PrintHelperUtil.getPageBreakBounds(dgrmEP, loadedPreferences);

        PageMargins margins = PageInfoHelper.getPageMargins(fPreferences, getMapMode());
        //do not include margins
        org.eclipse.draw2d.geometry.Point pageBounds = PageInfoHelper.getPageSize(fPreferences, getMapMode());
        org.eclipse.draw2d.geometry.Point pageCount = getPageCount(dgrmEP, figureBounds, pageBounds, false);
        int numCols = pageCount.x;
        int numRows = pageCount.y;

        float actualWidth = 0;
        float actualHeight = 0;
        if (this.rows == 1 && this.columns == 1 && fitToPage) {
            figureBounds = dgrmEP.getChildrenBounds();
            actualWidth = figureBounds.width;
            actualHeight = figureBounds.height;
        } else {
            actualWidth = numCols * pageBounds.x;
            actualHeight = numRows * pageBounds.y;
        }

        int totalHeight = (this.rows * pageBounds.y);
        int totalWidth = (this.columns * pageBounds.x);

        float vScale = totalHeight / actualHeight;
        float hScale = totalWidth / actualWidth;

        this.userScale = Math.min(hScale, vScale);

        // translate to offset figure position
        translated = new Point((int) (-figureBounds.x * userScale), (int) (-figureBounds.y * userScale));

        adjustMargins(margins, userScale, getPrinterOffset());

        GC gc_ = new GC(DisplayUtils.getDisplay());

        FontData fontData = JFaceResources.getDefaultFont().getFontData()[0];
        Font font = new Font(printer, fontData);

        int row = 1, col = 1, finalRow = 0, finalColumn = 0;

        if (this.printRangePageSelection) {
            //print only the pages specified in the page range
            //this corresponds to the physical pages, not the print range of pages on one physical page.
            row = calculateRowFromPage(this.pageFrom, this.columns);
            col = calculateColumnFromPage(this.pageFrom, this.columns, row);

            finalRow = calculateRowFromPage(this.pageTo, this.columns);
            finalColumn = calculateColumnFromPage(this.pageTo, this.columns, finalRow);
        }

        try {
            // print the pages in row, column order
            for (; row <= rows; row++) {
                for (; col <= columns; col++) {
                    printer.startPage();
                    drawPage(gc_, dgrmEP, fPreferences, figureBounds, margins, font, row, col);
                    printer.endPage();

                    if (row == finalRow && col == finalColumn && this.printRangePageSelection == true)
                        break;
                }

                if (row == finalRow && col == finalColumn && this.printRangePageSelection == true)
                    break;

                col = 1;
            }
        } finally {
            // must dispose resources
            font.dispose();
            gc_.dispose();
        }
    }

    /**
     * Return scale factor between printer and display.
     * 
     * @return float
     */
    private float computePrinterDisplayScale() {
        assert null != printer : "printer must be set"; //$NON-NLS-1$
        assert null != display_dpi : "display_dpi must be set"; //$NON-NLS-1$

        Point dpi = printer.getDPI();
        float scale = dpi.x / (float) display_dpi.x;

        return scale;
    }

    /**
     * Disposes of the resources.
     */
    protected void dispose() {
        if (this.graphics != null) {
            try {
                this.graphics.dispose();
            } catch (NullPointerException e) {
                //do nothing
            } finally {
                this.graphics = null;
            }
        }

        if (this.printerGraphics != null) {
            try {
                this.printerGraphics.dispose();
            } catch (NullPointerException e) {
                //do nothing
            } finally {
                this.printerGraphics = null;
            }
        }

        if (this.swtGraphics != null) {
            try {
                this.swtGraphics.dispose();
            } catch (NullPointerException e) {
                //do nothing
            } finally {
                this.swtGraphics = null;
            }
        }

        if (this.gc != null) {
            try {
                this.gc.dispose();
            } catch (NullPointerException e) {
                //do nothing
            } finally {
                this.gc = null;
            }
        }

        //reset the printer offset, just in case the next diagram to be printed 
        //uses a different map mode.
        printerOffset = null;

    }

    private void disposeImageVars(GC imgGC, Image image, SWTGraphics sg, ScaledGraphics g1, MapModeGraphics mmg) {

        if (mmg != null) {
            try {
                mmg.dispose();
            } catch (NullPointerException e) {
                //do nothing
            } finally {
                mmg = null;
            }
        }

        if (g1 != null) {
            try {
                g1.dispose();
            } catch (NullPointerException e) {
                //do nothing
            } finally {
                g1 = null;
            }
        }

        if (sg != null) {
            try {
                sg.dispose();
            } catch (NullPointerException e) {
                //do nothing
            } finally {
                sg = null;
            }
        }

        if (imgGC != null) {
            try {
                imgGC.dispose();
            } catch (NullPointerException e) {
                //do nothing
            } finally {
                imgGC = null;
            }
        }

        if (image != null) {
            try {
                image.dispose();
            } catch (NullPointerException e) {
                //do nothing
            } finally {
                image = null;
            }
        }
    }

    /**
     * Creates the <code>PrinterGraphics</code>.
     * 
     * @param theGraphics
     *          the <code>Graphics</code> object
     * @return the new <code>PrinterGraphics</code>
     */
    protected PrinterGraphics createPrinterGraphics(Graphics theGraphics) {
        return new PrinterGraphics(theGraphics, printer, true);
    }

    /**
     * Adjust the given PageMargins by the scale and offset
     * 
     * @param margins PageMargins to adjust
     * @param scale margins will be scaled by this amount
     * @param offset to adjust margins by
     */
    protected void adjustMargins(PageMargins margins, float scale, Point offset) {
        //scale
        margins.left /= scale;
        margins.top /= scale;
        margins.right /= scale;
        margins.bottom /= scale;

        //offsets
        margins.left -= offset.x;
        margins.right += offset.x;
        margins.top -= offset.y;
        margins.bottom += offset.y;

        // this is more readable than doing Math.min for all the above
        if (margins.left < 0)
            margins.left = 0;
        if (margins.right < 0)
            margins.right = 0;
        if (margins.top < 0)
            margins.top = 0;
        if (margins.bottom < 0)
            margins.bottom = 0;
    }
}