/* ****************************************************************************** * Copyright (c) 2006-2012 XMind Ltd. and others. * * This file is a part of XMind 3. XMind releases 3 and * above are dual-licensed under the Eclipse Public License (EPL), * which is available at http://www.eclipse.org/legal/epl-v10.html * and the GNU Lesser General Public License (LGPL), * which is available at http://www.gnu.org/licenses/lgpl.html * See http://www.xmind.net/license.html for details. * * Contributors: * XMind Ltd. - initial API and implementation *******************************************************************************/ package org.xmind.ui.internal.print.multipage; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Stack; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.PositionConstants; import org.eclipse.draw2d.SWTGraphics; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Insets; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.ProgressMonitorDialog; import org.eclipse.jface.operation.IRunnableWithProgress; 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.Transform; import org.eclipse.swt.printing.Printer; import org.eclipse.swt.printing.PrinterData; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.xmind.gef.GEF; import org.xmind.gef.IGraphicalViewer; import org.xmind.gef.draw2d.RotatableWrapLabel; import org.xmind.gef.draw2d.graphics.Rotate90Graphics; import org.xmind.gef.image.FigureRenderer; import org.xmind.gef.image.IExportSourceProvider; import org.xmind.gef.util.Properties; import org.xmind.ui.internal.MindMapMessages; import org.xmind.ui.internal.print.PrintConstants; import org.xmind.ui.internal.print.PrintUtils; import org.xmind.ui.mindmap.GhostShellProvider; import org.xmind.ui.mindmap.IMindMap; import org.xmind.ui.mindmap.IMindMapViewer; import org.xmind.ui.mindmap.MindMapExportViewer; import org.xmind.ui.mindmap.MindMapUI; import org.xmind.ui.mindmap.MindMapViewerExportSourceProvider; import org.xmind.ui.resources.FontUtils; import org.xmind.ui.util.Logger; import org.xmind.ui.util.UnitConvertor; public class MultipagePrintClient extends FigureRenderer { private static class MindMapViewerPrintSourceProvider extends MindMapViewerExportSourceProvider { private IDialogSettings settings; public MindMapViewerPrintSourceProvider(IGraphicalViewer viewer, IDialogSettings settings) { super(viewer); this.settings = settings; } public MindMapViewerPrintSourceProvider(IGraphicalViewer viewer, int margins, IDialogSettings settings) { super(viewer, margins); this.settings = settings; } @Override protected void collectContents(List<IFigure> figures) { if (settings != null && !settings.getBoolean(PrintConstants.NO_BACKGROUND)) { figures.add(getViewer().getLayer(GEF.LAYER_BACKGROUND)); } figures.add(getViewer().getLayer(GEF.LAYER_CONTENTS)); figures.add(getViewer().getLayer(MindMapUI.LAYER_TITLE)); } } // private static final int TEXT_MARGIN = 5; private static final int TEXT_MARGIN = 0; private String jobName; private Shell parentShell; private PrinterData printerData; private IDialogSettings settings; private IMindMap sourceMap; private Printer printer; private Rectangle pageClientArea; private Point dpi; private IExportSourceProvider source; private boolean jobStarted = false; private Transform transform; public MultipagePrintClient(String jobName, Shell parentShell, PrinterData printerData, IDialogSettings settings) { this.jobName = jobName; this.parentShell = parentShell; this.printerData = printerData; this.settings = settings; } public void print(IMindMap sourceMap) { this.sourceMap = sourceMap; if (!start()) return; try { new ProgressMonitorDialog(Display.getCurrent().getActiveShell()) .run(false, false, new IRunnableWithProgress() { public void run(final IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { parentShell.getDisplay().syncExec(new Runnable() { public void run() { print(monitor); } }); } }); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } private void print(IProgressMonitor monitor) { Properties properties = new Properties(); properties.set(IMindMapViewer.VIEWER_GRADIENT, Boolean.FALSE); //set plus minus visibility boolean plusVisible = getBoolean(settings, PrintConstants.PLUS_VISIBLE, PrintConstants.DEFAULT_PLUS_VISIBLE); boolean minusVisible = getBoolean(settings, PrintConstants.MINUS_VISIBLE, PrintConstants.DEFAULT_MINUS_VISIBLE); properties.set(IMindMapViewer.PLUS_VISIBLE, plusVisible); properties.set(IMindMapViewer.MINUS_VISIBLE, minusVisible); GhostShellProvider shell = new GhostShellProvider( parentShell.getDisplay()); IGraphicalViewer exportViewer = new MindMapExportViewer(shell, sourceMap, properties); source = new MindMapViewerPrintSourceProvider(exportViewer, 0, settings); setFigures(source.getContents()); int margin = PrintMultipageUtils.getMargin(source.getSourceArea()); setBounds(new Rectangle(source.getSourceArea()) .expand(new Insets(margin))); internalPrint(monitor); } private boolean getBoolean(IDialogSettings settings, String key, boolean defaultValue) { boolean value = defaultValue; if (settings.get(key) != null) { value = settings.getBoolean(key); } return value; } private void internalPrint(IProgressMonitor monitor) { Rectangle bounds = getBounds(); int sourceWidth = bounds.width; int sourceHeight = bounds.height; int leftMarginPixel = PrintConstants.toPixel(getDouble( PrintConstants.LEFT_MARGIN, PrintConstants.DEFAULT_MARGIN)); int rightMarginPixel = PrintConstants.toPixel(getDouble( PrintConstants.RIGHT_MARGIN, PrintConstants.DEFAULT_MARGIN)); int topMarginPixel = PrintConstants.toPixel(getDouble( PrintConstants.TOP_MARGIN, PrintConstants.DEFAULT_MARGIN)); int bottomMarginPixel = PrintConstants.toPixel(getDouble( PrintConstants.BOTTOM_MARGIN, PrintConstants.DEFAULT_MARGIN)); //trim per page print content by header and footer height int headerHeight = PrintUtils.getHeaderHeight(settings, PrintConstants.DEFAULT_DPI); int footerHeight = PrintUtils.getBottomHeight(settings, PrintConstants.DEFAULT_DPI); //Calculate the actual needed printed content of the each page according to the default paper size(A4) //Then use the print content to fit different actual paper size int orientation = getInteger(PrintConstants.ORIENTATION, PrintConstants.DEFAULT_ORIENTATION); int perPageWidth = orientation == PrinterData.LANDSCAPE ? PrintConstants.PAGE_LENGTH : PrintConstants.PAGE_SHORT; int usefulPerPageWidth = perPageWidth - leftMarginPixel - rightMarginPixel; int perPageHeight = orientation == PrinterData.PORTRAIT ? PrintConstants.PAGE_LENGTH : PrintConstants.PAGE_SHORT; int usefulPerPageHeight = perPageHeight - topMarginPixel - bottomMarginPixel; usefulPerPageHeight -= headerHeight + footerHeight; int widthPages = getInteger(PrintConstants.WIDTH_PAGES, 1); int heightPages = getInteger(PrintConstants.HEIGHT_PAGES, 1); boolean isAspectRatio = settings .getBoolean(PrintConstants.ASPECT_RATIO_LOCKED); boolean fullWidth = !settings.getBoolean(PrintConstants.FILL_HEIGHT); if (!isAspectRatio) { double fillWidthratio = (double) usefulPerPageWidth * widthPages / sourceWidth; double fillHeightRatio = (double) usefulPerPageHeight * heightPages / sourceHeight; fullWidth = (fillWidthratio <= fillHeightRatio) ? true : false; } double ratio = fullWidth ? ((double) usefulPerPageWidth * widthPages / sourceWidth) : ((double) usefulPerPageHeight * heightPages / sourceHeight); //The actual needed printed content of the each page calculated by the default paper size(A4) int usefulPerPageWidthByRatio = (int) (usefulPerPageWidth / ratio); int usefulPerPageHeightByRatio = (int) (usefulPerPageHeight / ratio); int usefulWidthPages = widthPages; int usefulHeightPages = heightPages; if (fullWidth) { usefulHeightPages = sourceHeight / usefulPerPageHeightByRatio; usefulHeightPages = (sourceHeight % usefulPerPageHeightByRatio == 0) ? usefulHeightPages : usefulHeightPages + 1; } else { usefulWidthPages = sourceWidth / usefulPerPageWidthByRatio; usefulWidthPages = (sourceWidth % usefulPerPageWidthByRatio == 0) ? usefulWidthPages : usefulWidthPages + 1; } //use the print content to fit different actual paper size //actual scale double widthScale = (double) pageClientArea.width / usefulPerPageWidthByRatio; double heightScale = (double) pageClientArea.height / usefulPerPageHeightByRatio; double scale = widthScale < heightScale ? widthScale : heightScale; setScale(scale); //Center then actual printing content after adapting it //page origin int originX = (int) (pageClientArea.width - usefulPerPageWidthByRatio * scale) / 2 + pageClientArea.x; int originY = (int) (pageClientArea.height - usefulPerPageHeightByRatio * scale) / 2 + pageClientArea.y; Rectangle realPageClientArea = new Rectangle(originX, originY, (int) (usefulPerPageWidthByRatio * scale), (int) (usefulPerPageHeightByRatio * scale)); monitor.beginTask(MindMapMessages.MultipagePrint_Printing, widthPages * heightPages); for (int j = 0; j < heightPages; j++) { int y = j * usefulPerPageHeightByRatio; for (int i = 0; i < widthPages; i++) { int x = i * usefulPerPageWidthByRatio; int pageNumber = j * widthPages + i + 1; boolean isValidPage = i < usefulWidthPages && j < usefulHeightPages; render(realPageClientArea, new Point(-x, -y), pageNumber, isValidPage); monitor.worked(1); } } monitor.done(); } private void render(Rectangle realPageClientArea, final Point origin, int pageNumber, boolean isValidPage) { if (!printer.startPage()) { return; } pageClientArea = new Rectangle(realPageClientArea); GC gc = new GC(printer); try { if (isValidPage) { pushState(gc); render(gc, origin); popState(gc); } //remove clipping gc.setClipping((org.eclipse.swt.graphics.Rectangle) null); String headerText = settings.get(PrintConstants.HEADER_TEXT); if (headerText != null && !"".equals(headerText)) { //$NON-NLS-1$ drawHeader(gc, headerText); } String footerText = settings.get(PrintConstants.FOOTER_TEXT); if (footerText != null && !"".equals(footerText)) { //$NON-NLS-1$ drawFooter(gc, footerText); } //remove clipping gc.setClipping((org.eclipse.swt.graphics.Rectangle) null); //draw border using adapted bounds boolean hasBorder = settings.getBoolean(PrintConstants.BORDER); if (hasBorder) { gc.setLineWidth(1); gc.setForeground( Display.getCurrent().getSystemColor(SWT.COLOR_BLACK)); gc.drawRectangle(pageClientArea.x - 1, pageClientArea.y - 1, pageClientArea.width + 2, pageClientArea.height + 2); } //draw page number drawPageNumber(gc, "- " + pageNumber + " -"); //$NON-NLS-1$ //$NON-NLS-2$ } finally { gc.dispose(); } printer.endPage(); } private void pushState(GC gc) { Transform tempTransform = new Transform(gc.getDevice()); gc.getTransform(tempTransform); float[] elements = new float[6]; tempTransform.getElements(elements); if (transform != null && !transform.isDisposed()) { transform.dispose(); } transform = new Transform(gc.getDevice(), elements); tempTransform.dispose(); } private void popState(GC gc) { gc.setTransform(transform); } private void drawHeader(GC gc, String text) { Font font = getFont(PrintConstants.HEADER_FONT); try { drawText(gc, text, font, getAlign(PrintConstants.HEADER_ALIGN, PositionConstants.CENTER), true); } finally { font.dispose(); } } private void drawFooter(GC gc, String text) { Font font = getFont(PrintConstants.FOOTER_FONT); try { drawText(gc, text, font, getAlign(PrintConstants.FOOTER_ALIGN, PositionConstants.RIGHT), false); } finally { font.dispose(); } } private void drawPageNumber(GC gc, String text) { int footerAlign = getAlign(PrintConstants.FOOTER_ALIGN, PositionConstants.RIGHT); int pageNumberAlign = (footerAlign == PositionConstants.CENTER ? PositionConstants.RIGHT : PositionConstants.CENTER); drawPageNumber(gc, text, pageNumberAlign); } private Font getFont(String fontKey) { Font font = null; String fontValue = settings.get(fontKey); if (fontValue != null) { FontData[] fontData = FontUtils.toFontData(fontValue); if (fontData != null) { for (FontData fd : fontData) { fd.setHeight(fd.getHeight() * dpi.y / UnitConvertor.getScreenDpi().y); } font = new Font(Display.getCurrent(), fontData); } } if (font == null) { FontData[] defaultFontData = JFaceResources .getDefaultFontDescriptor().getFontData(); int defaultHeight = defaultFontData[0].getHeight(); font = new Font(Display.getCurrent(), FontUtils.newHeight( defaultFontData, defaultHeight * dpi.y / UnitConvertor.getScreenDpi().y)); } return font; } private int getAlign(String alignKey, int defaultAlign) { return PrintConstants.toDraw2DAlignment(settings.get(alignKey), defaultAlign); } private void drawText(GC gc, String text, Font font, int alignment, boolean top) { RotatableWrapLabel label = new RotatableWrapLabel(); label.setText(text); label.setFont(font); label.setTextAlignment(alignment); label.setForegroundColor( parentShell.getDisplay().getSystemColor(SWT.COLOR_BLACK)); int width = pageClientArea.width; int marginWidth = TEXT_MARGIN * dpi.x / UnitConvertor.getScreenDpi().x; width -= marginWidth * 2; Dimension size = label.getPreferredSize(width, -1); int x = -width / 2; org.eclipse.swt.graphics.Rectangle pageBounds = printer.getClientArea(); int y; if (top) { y = -pageClientArea.height / 2 - (pageClientArea.y - pageBounds.y) + Math.max( (pageClientArea.y - pageBounds.y - size.height) / 2, marginWidth); } else { y = pageClientArea.height / 2 + (pageBounds.y + pageBounds.height - (pageClientArea.y + pageClientArea.height)) - size.height - Math.max((pageBounds.y + pageBounds.height - (pageClientArea.y + pageClientArea.height) - size.height) / 2, marginWidth); } label.setBounds(new Rectangle(x, y, width, size.height)); SWTGraphics baseGraphics = new SWTGraphics(gc); baseGraphics.translate(pageClientArea.x + pageClientArea.width / 2, pageClientArea.y + pageClientArea.height / 2); Graphics graphics = baseGraphics; Rotate90Graphics rotatedGraphics = null; try { label.paint(graphics); } catch (Throwable e) { Logger.log(e, "Error occurred while printing"); //$NON-NLS-1$ } finally { if (rotatedGraphics != null) { rotatedGraphics.dispose(); } baseGraphics.dispose(); } } private void drawPageNumber(GC gc, String text, int alignment) { RotatableWrapLabel label = new RotatableWrapLabel(); label.setText(text); Font font = Display.getCurrent().getSystemFont(); font = FontUtils.getNewHeight(font, (font.getFontData())[0].getHeight() * dpi.y / UnitConvertor.getScreenDpi().y); label.setFont(font); label.setTextAlignment(alignment); label.setForegroundColor( parentShell.getDisplay().getSystemColor(SWT.COLOR_BLACK)); Rectangle pageBounds = new Rectangle(printer.getClientArea()); int width = pageClientArea.width; int marginWidth = TEXT_MARGIN * dpi.x / UnitConvertor.getScreenDpi().x; width -= marginWidth * 2; Dimension size = label.getPreferredSize(width, -1); int x = -width / 2; int y = pageBounds.height / 2 - size.height - Math.max((pageBounds.y + pageBounds.height - (pageClientArea.y + pageClientArea.height) - size.height) / 2, marginWidth); label.setBounds(new Rectangle(x, y, width, size.height)); SWTGraphics baseGraphics = new SWTGraphics(gc); baseGraphics.translate(pageBounds.x + pageBounds.width / 2, pageBounds.y + pageBounds.height / 2); Graphics graphics = baseGraphics; Rotate90Graphics rotatedGraphics = null; try { label.paint(graphics); } catch (Throwable e) { Logger.log(e, "Error occurred while printing"); //$NON-NLS-1$ } finally { if (rotatedGraphics != null) { rotatedGraphics.dispose(); } baseGraphics.dispose(); } } private boolean start() { if (printer == null) { printer = new Printer(printerData); } receivePrinterInfo(); if (pageClientArea.width <= 0 || pageClientArea.height <= 0) { Display.getCurrent().asyncExec(new Runnable() { public void run() { MessageDialog.openInformation( Display.getDefault().getActiveShell(), MindMapMessages.MultipagePrint_InvalidMargin_title, MindMapMessages.MultipagePrint_InvalidMargin_message); } }); return false; } if (!jobStarted) { if (!printer.startJob(jobName)) return false; jobStarted = true; } return jobStarted; } private void receivePrinterInfo() { dpi = new Point(printer.getDPI()); pageClientArea = new Rectangle(printer.getClientArea()); int leftMargin = getUserMargin(PrintConstants.LEFT_MARGIN); int rightMargin = getUserMargin(PrintConstants.RIGHT_MARGIN); int topMargin = getUserMargin(PrintConstants.TOP_MARGIN); int bottomMargin = getUserMargin(PrintConstants.BOTTOM_MARGIN); pageClientArea.x += leftMargin; pageClientArea.y += topMargin; pageClientArea.width -= leftMargin + rightMargin; pageClientArea.height -= topMargin + bottomMargin; //trim clientArea by header and footer height int headerHeight = PrintUtils.getHeaderHeight(settings, dpi.y); int footerHeight = PrintUtils.getBottomHeight(settings, dpi.y); pageClientArea.expand(new Insets(-headerHeight, 0, -footerHeight, 0)); } private int getUserMargin(String key) { double marginInch; try { marginInch = settings.getDouble(key); } catch (NumberFormatException e) { marginInch = PrintConstants.DEFAULT_MARGIN; } double dpi; if (PrintConstants.LEFT_MARGIN.equals(key) || PrintConstants.RIGHT_MARGIN.equals(key)) { dpi = this.dpi.x; } else { dpi = this.dpi.y; } return (int) (marginInch * dpi); } public void dispose() { if (printer != null) { if (!printer.isDisposed()) { printer.endJob(); } printer.dispose(); printer = null; } jobStarted = false; if (transform != null) { transform.dispose(); } } protected void createGraphics(Graphics graphics, Stack<Graphics> stack) { graphics.clipRect(new Rectangle(pageClientArea.x, pageClientArea.y, pageClientArea.width, pageClientArea.height)); graphics.translate(pageClientArea.x, pageClientArea.y); if (getScale() > 0) { graphics.scale(getScale()); stack.push(graphics); } Rectangle bounds = getBounds(); graphics.translate(-bounds.x, -bounds.y); } private double getDouble(String key, double defaultValue) { try { return settings.getDouble(key); } catch (NumberFormatException e) { } return defaultValue; } private int getInteger(String key, int defaultValue) { try { return settings.getInt(key); } catch (NumberFormatException e) { } return defaultValue; } }