org.locationtech.udig.printing.ui.pdf.ExportPDFWizard.java Source code

Java tutorial

Introduction

Here is the source code for org.locationtech.udig.printing.ui.pdf.ExportPDFWizard.java

Source

/*
 *    uDig - User Friendly Desktop Internet GIS client
 *    http://udig.refractions.net
 *    (C) 2012, Refractions Research Inc.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * (http://www.eclipse.org/legal/epl-v10.html), and the Refractions BSD
 * License v1.0 (http://udig.refractions.net/files/bsd3-v10.html).
 */
package org.locationtech.udig.printing.ui.pdf;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.PrinterException;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import javax.imageio.ImageIO;

import org.locationtech.udig.catalog.IGeoResource;
import org.locationtech.udig.core.internal.Icons;
import org.locationtech.udig.printing.model.Box;
import org.locationtech.udig.printing.model.ModelFactory;
import org.locationtech.udig.printing.model.Page;
import org.locationtech.udig.printing.ui.Template;
import org.locationtech.udig.printing.ui.TemplateFactory;
import org.locationtech.udig.printing.ui.internal.Messages;
import org.locationtech.udig.printing.ui.internal.PrintingEngine;
import org.locationtech.udig.printing.ui.internal.PrintingPlugin;
import org.locationtech.udig.project.IBlackboard;
import org.locationtech.udig.project.internal.Layer;
import org.locationtech.udig.project.internal.Map;
import org.locationtech.udig.project.internal.command.navigation.SetViewportBBoxCommand;
import org.locationtech.udig.project.ui.ApplicationGIS;
import org.locationtech.udig.project.ui.BoundsStrategy;
import org.locationtech.udig.project.ui.SelectionStyle;
import org.locationtech.udig.project.ui.ApplicationGIS.DrawMapParameter;
import org.locationtech.udig.project.ui.internal.MapEditorInput;

import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IExportWizard;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PlatformUI;
import org.opengis.coverage.grid.GridCoverageReader;

import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Image;
import com.lowagie.text.PageSize;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfWriter;

/**
 * A user interface for choosing an export template and other export options.
 * The wizard is configurable by placing an ExportPDFWizardConfigBean on the
 * map blackboard (see ExportPDFWizardConfigBean for the blackboard key).
 * <p>
 *
 * </p>
 * @author brocka
 * @since 1.1.0
 */
public class ExportPDFWizard extends Wizard implements IExportWizard {

    private Map map;
    private ExportPDFWizardPage1 page1;

    public ExportPDFWizard() {
        PrintingPlugin plugin = PrintingPlugin.getDefault();

        java.util.Map<String, TemplateFactory> templateFactories = plugin.getTemplateFactories();

        setWindowTitle(Messages.ExportPDFWizard_Title);

        String key = Icons.WIZBAN + "exportpdf_wiz.gif"; //$NON-NLS-1$
        ImageRegistry imageRegistry = plugin.getImageRegistry();
        ImageDescriptor image = imageRegistry.getDescriptor(key);
        if (image == null) {
            URL banURL = plugin.getBundle().getResource("icons/" + key); //$NON-NLS-1$
            image = ImageDescriptor.createFromURL(banURL);
            imageRegistry.put(key, image);
        }
        setDefaultPageImageDescriptor(image);

        //get copy of map
        IEditorInput input = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor()
                .getEditorInput();
        map = (Map) ((MapEditorInput) input).getProjectElement();

        //get configuration for this wizard
        IBlackboard mapBlackboard = map.getBlackboard();
        ExportPDFWizardConfigBean config = (ExportPDFWizardConfigBean) mapBlackboard
                .get(ExportPDFWizardConfigBean.BLACKBOARD_KEY);

        page1 = new ExportPDFWizardPage1(templateFactories, config);
        addPage(page1);
    }

    @Override
    public boolean canFinish() {
        return page1.isPageComplete();
    }

    @Override
    public boolean performFinish() {

        //create the document                 
        Rectangle suggestedPageSize = getITextPageSize(page1.getPageSize());
        Rectangle pageSize = rotatePageIfNecessary(suggestedPageSize); //rotate if we need landscape

        Document document = new Document(pageSize);

        try {

            //Basic setup of the Document, and get instance of the iText Graphics2D
            //   to pass along to uDig's standard "printing" code.
            String outputFile = page1.getDestinationDir() + System.getProperty("file.separator") + //$NON-NLS-1$
                    page1.getOutputFile();
            PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(outputFile));
            document.open();
            Graphics2D graphics = null;
            Template template = getTemplate();

            int i = 0;
            int numPages = 1;
            do {

                //sets the active page
                template.setActivePage(i);

                PdfContentByte cb = writer.getDirectContent();

                Page page = makePage(pageSize, document, template);

                graphics = cb.createGraphics(pageSize.getWidth(), pageSize.getHeight());

                //instantiate a PrinterEngine (pass in the Page instance)
                PrintingEngine engine = new PrintingEngine(page);

                //make page format
                PageFormat pageFormat = new PageFormat();
                pageFormat.setOrientation(PageFormat.PORTRAIT);
                java.awt.print.Paper awtPaper = new java.awt.print.Paper();
                awtPaper.setSize(pageSize.getWidth() * 3, pageSize.getHeight() * 3);
                awtPaper.setImageableArea(0, 0, pageSize.getWidth(), pageSize.getHeight());
                pageFormat.setPaper(awtPaper);

                //run PrinterEngine's print function
                engine.print(graphics, pageFormat, 0);

                graphics.dispose();
                document.newPage();
                if (i == 0) {
                    numPages = template.getNumPages();
                }
                i++;

            } while (i < numPages);

            //cleanup
            document.close();
            writer.close();
        } catch (DocumentException e) {
            e.printStackTrace();
            return false;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } catch (PrinterException e) {
            e.printStackTrace();
        }
        return true;
    }

    /**
     * converts a page size "name" (such as "A3" or "A4" into a 
     * rectangle object that iText will understand.
     */
    private Rectangle getITextPageSize(String pageSizeName) {
        if (pageSizeName.equals("A3")) //$NON-NLS-1$
            return PageSize.A3;
        if (pageSizeName.equals("A4")) //$NON-NLS-1$
            return PageSize.A4;
        throw new IllegalArgumentException(pageSizeName + " is not a supported page size"); //$NON-NLS-1$
    }

    protected Rectangle rotatePageIfNecessary(Rectangle suggestedPageSize) {
        //rotate the page if dimensions are given as portrait, but template prefers landscape
        if (suggestedPageSize.getHeight() > suggestedPageSize.getWidth() && page1.isLandscape()) {
            float temp = suggestedPageSize.getWidth();
            float newWidth = suggestedPageSize.getHeight();
            float newHeight = temp;
            return new Rectangle(suggestedPageSize.getHeight(), suggestedPageSize.getWidth());
        }
        return suggestedPageSize;
    }

    /**
     * Creates a page based on the template selected in the wizard
     * **Note: this function may swap the width and height if the
     * template prefers different page orientation.
     *
     * @return a page
     */
    protected Page makePage(Rectangle pageSize, Document doc, Template template) {

        Map mapOnlyRasterLayers = null;
        Map mapNoRasterLayers = null;

        // **Note: the iText API doesn't render rasters at a high enough resolution if 
        //they are written to the PDF via graphics2d.  To work around this problem, I
        //create two copies of the map: one with only the raster layers, and one with
        //everything else.
        //The "everything else" map gets drawn by a graphics2d.  The other layer must be
        //rasterized and inserted into the PDF via iText's API.  

        //make one copy of the map with no raster layers
        mapNoRasterLayers = (Map) ApplicationGIS.copyMap(map);
        List<Layer> layersNoRasters = mapNoRasterLayers.getLayersInternal();
        List<Layer> toRemove = new ArrayList<Layer>();
        for (Layer layer : layersNoRasters) {
            for (IGeoResource resource : layer.getGeoResources()) {
                if (resource.canResolve(GridCoverageReader.class)) {
                    toRemove.add(layer);
                }
            }
        }
        layersNoRasters.removeAll(toRemove);

        //adjust scale        
        double currentViewportScaleDenom = map.getViewportModel().getScaleDenominator();
        if (currentViewportScaleDenom == -1)
            throw new IllegalStateException("no scale denominator is available from the viewport model"); //$NON-NLS-1$

        if (page1.getScaleOption() == PrintWizardPage1.CUSTOM_MAP_SCALE) {
            float customScale = page1.getCustomScale();
            template.setMapScaleHint(customScale);
        } else if (page1.getScaleOption() == PrintWizardPage1.CURRENT_MAP_SCALE) {
            template.setMapScaleHint(currentViewportScaleDenom);
        } else if (page1.getScaleOption() == PrintWizardPage1.ZOOM_TO_SELECTION) {
            template.setZoomToSelectionHint(true);
            template.setMapScaleHint(currentViewportScaleDenom);
        }

        //3. make the page itself 
        Page page = ModelFactory.eINSTANCE.createPage();
        page.setSize(new Dimension((int) pageSize.getWidth(), (int) pageSize.getHeight()));

        //page name stuff not required, because this page will just get discarded
        MessageFormat formatter = new MessageFormat(Messages.CreatePageAction_newPageName, Locale.getDefault());
        if (page.getName() == null || page.getName().length() == 0) {
            page.setName(formatter.format(new Object[] { mapNoRasterLayers.getName() }));
        }

        template.init(page, mapNoRasterLayers);

        if (page1.getRasterEnabled()) {
            //make another copy with only raster layers
            mapOnlyRasterLayers = (Map) ApplicationGIS.copyMap(map);
            List<Layer> layersOnlyRasters = mapOnlyRasterLayers.getLayersInternal();
            List<Layer> toRemove2 = new ArrayList<Layer>();
            for (Layer layer : layersOnlyRasters) {
                for (IGeoResource resource : layer.getGeoResources()) {
                    if (!resource.canResolve(GridCoverageReader.class)) {
                        toRemove2.add(layer);
                    }
                }
            }
            layersOnlyRasters.removeAll(toRemove2);

            //set bounds to match the other map
            SetViewportBBoxCommand cmdBbox = new SetViewportBBoxCommand(
                    mapNoRasterLayers.getViewportModel().getBounds());
            mapOnlyRasterLayers.sendCommandSync(cmdBbox);

            if (layersNoRasters.size() > 0) {
                writeRasterLayersOnlyToDocument(mapOnlyRasterLayers, template.getMapBounds(), doc, page.getSize(),
                        /*currentViewportScaleDenom*/mapNoRasterLayers.getViewportModel().getScaleDenominator());
            }
        }

        //copy the boxes from the template into the page
        Iterator<Box> iter = template.iterator();
        while (iter.hasNext()) {
            page.getBoxes().add(iter.next());
        }
        return page;

        //TODO Throw some sort of exception if the page can't be created

    }

    /**
     * Gets the template selected in the wizard.
     *
     * @return a template
     */
    private Template getTemplate() {

        TemplateFactory templateFactory = page1.getTemplateFactory();
        Template template = templateFactory.createTemplate();

        return template;
    }

    public void init(IWorkbench workbench, IStructuredSelection selection) {
    }

    /**
     * double scaleDenom = page1.isCustomScale() ? page1.getCustomScale() : map.getViewportModel().getScaleDenominator(); 
     *
     * @param mapWithRasterLayersOnly a map with only raster layers
     * @param mapBoundsInTemplate a rectangle indicating the coordinates of the top left, width and height 
     * (where the coordinate system has (0,0) in the top left.
     * @param doc the PDF document object
     */
    private void writeRasterLayersOnlyToDocument(Map mapWithRasterLayersOnly,
            org.eclipse.swt.graphics.Rectangle mapBoundsInTemplate, Document doc, Dimension pageSize,
            double currentViewportScaleDenom) {

        //set dimensions of the raster image to be the same ratio as
        //the required map bounds within the page, but scaled up so
        //we can achieve a higher DPI when we later insert the image
        //into the page (90 refers to the default DPI) 
        int w = mapBoundsInTemplate.width * page1.getDpi() / 90;
        int h = mapBoundsInTemplate.height * page1.getDpi() / 90;

        BufferedImage imageOfRastersOnly = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = imageOfRastersOnly.createGraphics();

        //define a DrawMapParameter object with a custom BoundsStrategy if a custom scale is set
        DrawMapParameter drawMapParameter = null;

        double scaleDenom = (page1.getScaleOption() == ExportPDFWizardPage1.CUSTOM_MAP_SCALE)
                ? page1.getCustomScale()
                : currentViewportScaleDenom;

        drawMapParameter = new DrawMapParameter(g, new java.awt.Dimension(w, h), mapWithRasterLayersOnly,
                new BoundsStrategy(scaleDenom), page1.getDpi(), SelectionStyle.EXCLUSIVE_ALL, null);

        try {

            //draw the map (at a high resolution as specified above)
            ApplicationGIS.drawMap(drawMapParameter);
            Image img = Image.getInstance(bufferedImage2ByteArray(imageOfRastersOnly));

            //scale the image down to fit into the page
            img.scaleAbsolute(mapBoundsInTemplate.width, mapBoundsInTemplate.height);

            //set the location of the image
            int left = mapBoundsInTemplate.x;
            int bottom = pageSize.height - mapBoundsInTemplate.height - mapBoundsInTemplate.y;
            img.setAbsolutePosition(left, bottom); //(0,0) is bottom left in the PDF coordinate system

            doc.add(img);
            addWhiteMapBorder(img, doc);

        } catch (Exception e) {
            //TODO: fail gracefully.
        }

    }

    /**
     * This function is used to draw very thin white borders over the outer 
     * edge of the raster map.  It's necessary because the map edge "bleeds" into
     * the adjacent pixels, and we need to cover that.
     * 
     * I think this quirky behaviour is possibly an iText bug
     */
    private void addWhiteMapBorder(Image img, Document doc) {

        try {

            Color color = Color.white;
            int borderWidth = 1;

            BufferedImage bufferedTop = new BufferedImage((int) img.getScaledWidth(), borderWidth,
                    BufferedImage.TYPE_INT_RGB);
            Graphics2D g1 = bufferedTop.createGraphics();
            g1.setBackground(color);
            g1.clearRect(0, 0, bufferedTop.getWidth(), bufferedTop.getHeight());
            Image top = Image.getInstance(bufferedImage2ByteArray(bufferedTop));
            top.setAbsolutePosition(img.getAbsoluteX(),
                    img.getAbsoluteY() + img.getScaledHeight() - bufferedTop.getHeight() / 2);

            BufferedImage bufferedBottom = new BufferedImage((int) img.getScaledWidth(), borderWidth,
                    BufferedImage.TYPE_INT_RGB);
            Graphics2D g2 = bufferedBottom.createGraphics();
            g2.setBackground(color);
            g2.clearRect(0, 0, bufferedBottom.getWidth(), bufferedBottom.getHeight());
            Image bottom = Image.getInstance(bufferedImage2ByteArray(bufferedBottom));
            bottom.setAbsolutePosition(img.getAbsoluteX(), img.getAbsoluteY() - bufferedTop.getHeight() / 2);

            BufferedImage bufferedLeft = new BufferedImage(borderWidth, (int) img.getScaledHeight(),
                    BufferedImage.TYPE_INT_RGB);
            Graphics2D g3 = bufferedLeft.createGraphics();
            g3.setBackground(color);
            g3.clearRect(0, 0, bufferedLeft.getWidth(), bufferedLeft.getHeight());
            Image left = Image.getInstance(bufferedImage2ByteArray(bufferedLeft));
            left.setAbsolutePosition(img.getAbsoluteX() - bufferedLeft.getWidth() / 2, img.getAbsoluteY());

            BufferedImage bufferedRight = new BufferedImage(borderWidth, (int) img.getScaledHeight(),
                    BufferedImage.TYPE_INT_RGB);
            Graphics2D g4 = bufferedRight.createGraphics();
            g4.setBackground(color);
            g4.clearRect(0, 0, bufferedRight.getWidth(), bufferedRight.getHeight());
            Image right = Image.getInstance(bufferedImage2ByteArray(bufferedRight));
            right.setAbsolutePosition(img.getAbsoluteX() + img.getScaledWidth() - bufferedRight.getWidth() / 2,
                    img.getAbsoluteY());

            doc.add(top);
            doc.add(bottom);
            doc.add(left);
            doc.add(right);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static byte[] bufferedImage2ByteArray(BufferedImage img) {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream(1000);
            ImageIO.write(img, "png", baos); //$NON-NLS-1$
            baos.flush();
            byte[] result = baos.toByteArray();
            baos.close();
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            return new byte[0];
        }
    }

}