org.geomajas.plugin.print.component.PdfContext.java Source code

Java tutorial

Introduction

Here is the source code for org.geomajas.plugin.print.component.PdfContext.java

Source

/*
 * This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
 *
 * Copyright 2008-2014 Geosparc nv, http://www.geosparc.com/, Belgium.
 *
 * The program is available in open source according to the GNU Affero
 * General Public License. All contributions in this program are covered
 * by the Geomajas Contributors License Agreement. For full licensing
 * details, see LICENSE.txt in the project root.
 */
package org.geomajas.plugin.print.component;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

import org.geomajas.configuration.SymbolInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.lowagie.text.BadElementException;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Image;
import com.lowagie.text.Rectangle;
import com.lowagie.text.Utilities;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.DefaultFontMapper;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfGState;
import com.lowagie.text.pdf.PdfTemplate;
import com.lowagie.text.pdf.PdfWriter;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;

/**
 * Context for PDF printing. This is a wrapper on a PdfWriter on which components of a print template can render
 * themselves. Components do not need to know about the template's layout and can draw in a relative coordinate system
 * in which their bottom-left origin corresponds to (0,0).
 *
 * @author Jan De Moerloose
 */
public class PdfContext {

    private PdfWriter writer;

    private PdfTemplate template;

    // current origin
    private float origX;

    private float origY;

    // stack of previous origins
    private Stack<Float> prevOrigX = new Stack<Float>();

    private Stack<Float> prevOrigY = new Stack<Float>();

    private final Logger log = LoggerFactory.getLogger(PdfContext.class);

    private Map<String, Color> predefinedColors = new HashMap<String, Color>();

    /**
     * Constructs a context for the specified writer and application.
     *
     * @param writer writer
     */
    public PdfContext(PdfWriter writer) {
        this.writer = writer;
        predefinedColors.put("black", Color.BLACK);
        predefinedColors.put("blue", Color.BLUE);
        predefinedColors.put("cyan", Color.CYAN);
        predefinedColors.put("darkgray", Color.DARK_GRAY);
        predefinedColors.put("gray", Color.GRAY);
        predefinedColors.put("green", Color.GREEN);
        predefinedColors.put("lightgray", Color.LIGHT_GRAY);
        predefinedColors.put("magenta", Color.MAGENTA);
        predefinedColors.put("orange", Color.ORANGE);
        predefinedColors.put("pink", Color.PINK);
        predefinedColors.put("red", Color.RED);
        predefinedColors.put("white", Color.WHITE);
        predefinedColors.put("yellow", Color.YELLOW);
    }

    /**
     * Initializes context size.
     *
     * @param rectangle rectangle
     */
    public void initSize(Rectangle rectangle) {
        template = writer.getDirectContent().createTemplate(rectangle.getWidth(), rectangle.getHeight());
    }

    public void setOrigin(float x, float y) {
        this.origX = x;
        this.origY = y;
    }

    /**
     * Return the text box for the specified text and font.
     *
     * @param text text
     * @param font font
     * @return text box
     */
    public Rectangle getTextSize(String text, Font font) {
        template.saveState();
        // get the font
        DefaultFontMapper mapper = new DefaultFontMapper();
        BaseFont bf = mapper.awtToPdf(font);
        template.setFontAndSize(bf, font.getSize());
        // calculate text width and height
        float textWidth = template.getEffectiveStringWidth(text, false);
        float ascent = bf.getAscentPoint(text, font.getSize());
        float descent = bf.getDescentPoint(text, font.getSize());
        float textHeight = ascent - descent;
        template.restoreState();
        return new Rectangle(0, 0, textWidth, textHeight);
    }

    /**
     * Draw text in the center of the specified box.
     *
     * @param text text
     * @param font font
     * @param box box to put text int
     * @param fontColor colour
     */
    public void drawText(String text, Font font, Rectangle box, Color fontColor) {
        template.saveState();
        // get the font
        DefaultFontMapper mapper = new DefaultFontMapper();
        BaseFont bf = mapper.awtToPdf(font);
        template.setFontAndSize(bf, font.getSize());

        // calculate descent
        float descent = 0;
        if (text != null) {
            descent = bf.getDescentPoint(text, font.getSize());
        }

        // calculate the fitting size
        Rectangle fit = getTextSize(text, font);

        // draw text if necessary
        template.setColorFill(fontColor);
        template.beginText();
        template.showTextAligned(PdfContentByte.ALIGN_LEFT, text,
                origX + box.getLeft() + 0.5f * (box.getWidth() - fit.getWidth()),
                origY + box.getBottom() + 0.5f * (box.getHeight() - fit.getHeight()) - descent, 0);
        template.endText();
        template.restoreState();
    }

    /**
     * Draw a rectangular boundary.
     *
     * @param rect rectangle
     */
    public void strokeRectangle(Rectangle rect) {
        strokeRectangle(rect, Color.black, 1, null);
    }

    /**
     * Draw a rectangular boundary with this color and linewidth.
     *
     * @param rect
     *            rectangle
     * @param color
     *            color
     * @param linewidth
     *            line width
     */
    public void strokeRectangle(Rectangle rect, Color color, float linewidth) {
        strokeRectangle(rect, color, linewidth, null);
    }

    public void strokeRectangle(Rectangle rect, Color color, float linewidth, float[] dashArray) {
        template.saveState();
        setStroke(color, linewidth, dashArray);
        template.rectangle(origX + rect.getLeft(), origY + rect.getBottom(), rect.getWidth(), rect.getHeight());
        template.stroke();
        template.restoreState();
    }

    /**
     * Draw a rounded rectangular boundary.
     *
     * @param rect rectangle
     * @param color colour
     * @param linewidth line width
     * @param r radius for rounded corners
     */
    public void strokeRoundRectangle(Rectangle rect, Color color, float linewidth, float r) {
        template.saveState();
        setStroke(color, linewidth, null);
        template.roundRectangle(origX + rect.getLeft(), origY + rect.getBottom(), rect.getWidth(), rect.getHeight(),
                r);
        template.stroke();
        template.restoreState();
    }

    /**
     * Draw a rectangle's interior.
     *
     * @param rect rectangle
     */
    public void fillRectangle(Rectangle rect) {
        fillRectangle(rect, Color.white);
    }

    /**
     * Draw a rectangle's interior with this color.
     *
     * @param rect rectangle
     * @param color colour
     */
    public void fillRectangle(Rectangle rect, Color color) {
        template.saveState();
        setFill(color);
        template.rectangle(origX + rect.getLeft(), origY + rect.getBottom(), rect.getWidth(), rect.getHeight());
        template.fill();
        template.restoreState();
    }

    public void fillRoundRectangle(Rectangle rect, Color color, float r) {
        template.saveState();
        setFill(color);
        template.roundRectangle(origX + rect.getLeft(), origY + rect.getBottom(), rect.getWidth(), rect.getHeight(),
                r);
        template.fill();
        template.restoreState();
    }

    /**
     * Draw an elliptical exterior with this color.
     *
     * @param rect rectangle in which ellipse should fit
     * @param color colour to use for stroking
     * @param linewidth line width
     */
    public void strokeEllipse(Rectangle rect, Color color, float linewidth) {
        template.saveState();
        setStroke(color, linewidth, null);
        template.ellipse(origX + rect.getLeft(), origY + rect.getBottom(), origX + rect.getRight(),
                origY + rect.getTop());
        template.stroke();
        template.restoreState();
    }

    /**
     * Draw an elliptical interior with this color.
     *
     * @param rect rectangle in which ellipse should fit
     * @param color colour to use for filling
     */
    public void fillEllipse(Rectangle rect, Color color) {
        template.saveState();
        setFill(color);
        template.ellipse(origX + rect.getLeft(), origY + rect.getBottom(), origX + rect.getRight(),
                origY + rect.getTop());
        template.fill();
        template.restoreState();
    }

    /**
     * Move this rectangle to the specified bottom-left point.
     *
     * @param rect rectangle to move
     * @param x new x origin
     * @param y new y origin
     */
    public void moveRectangleTo(Rectangle rect, float x, float y) {
        float width = rect.getWidth();
        float height = rect.getHeight();
        rect.setLeft(x);
        rect.setBottom(y);
        rect.setRight(rect.getLeft() + width);
        rect.setTop(rect.getBottom() + height);
    }

    /**
     * Translate this rectangle over the specified following distances.
     *
     * @param rect rectangle to move
     * @param dx delta x
     * @param dy delta y
     */
    public void translateRectangle(Rectangle rect, float dx, float dy) {
        float width = rect.getWidth();
        float height = rect.getHeight();
        rect.setLeft(rect.getLeft() + dx);
        rect.setBottom(rect.getBottom() + dy);
        rect.setRight(rect.getLeft() + dx + width);
        rect.setTop(rect.getBottom() + dy + height);
    }

    public Color getColor(String css, float opacity) {
        return getColor(css, opacity, Color.white);
    }

    public Color getColor(String css, float opacity, Color defaultColor) {
        Color color;
        if (null == css) {
            color = defaultColor;
        } else {
            String lc = css.toLowerCase();
            if (predefinedColors.containsKey(lc)) {
                return predefinedColors.get(lc);
            } else {
                try {
                    color = Color.decode(css);
                } catch (NumberFormatException e) {
                    color = defaultColor;
                }
            }
        }
        return new Color(color.getRed(), color.getGreen(), color.getBlue(), (int) (opacity * 255));
    }

    public float[] getDashArray(String dashArrayString) {
        if (dashArrayString == null || "".equals(dashArrayString.trim()) || "none".equals(dashArrayString)) {
            return null;
        }

        try {
            String[] res = dashArrayString.split(",");
            float[] dasharr = new float[res.length];
            for (int i = 0; i < res.length; i++) {
                dasharr[i] = Float.parseFloat(res[i]);
            }
            return dasharr;
        } catch (Exception e) { // NOSONAR
            log.warn("Error in dashArrayString: " + dashArrayString);
            return null;
        }
    }

    public Image getImage(String path) {
        try {
            URL url = getClass().getResource(path);
            if (url == null) {
                url = Utilities.toURL(path);
            }
            return Image.getInstance(url);
        } catch (Exception e) { // NOSONAR
            log.warn("could not fetch image", e);
            return null;
        }
    }

    /**
     * Draws the specified image with the first rectangle's bounds, clipping with the second one and adding
     * transparency.
     *
     * @param img image
     * @param rect rectangle
     * @param clipRect clipping bounds
     */
    public void drawImage(Image img, Rectangle rect, Rectangle clipRect) {
        drawImage(img, rect, clipRect, 1);
    }

    /**
     * Draws the specified image with the first rectangle's bounds, clipping with the second one.
     *
     * @param img image
     * @param rect rectangle
     * @param clipRect clipping bounds
     * @param opacity opacity of the image (1 = opaque, 0= transparent)
     */
    public void drawImage(Image img, Rectangle rect, Rectangle clipRect, float opacity) {
        try {
            template.saveState();
            // opacity
            PdfGState state = new PdfGState();
            state.setFillOpacity(opacity);
            state.setBlendMode(PdfGState.BM_NORMAL);
            template.setGState(state);
            // clipping code
            if (clipRect != null) {
                template.rectangle(clipRect.getLeft() + origX, clipRect.getBottom() + origY, clipRect.getWidth(),
                        clipRect.getHeight());
                template.clip();
                template.newPath();
            }
            template.addImage(img, rect.getWidth(), 0, 0, rect.getHeight(), origX + rect.getLeft(),
                    origY + rect.getBottom());
        } catch (DocumentException e) {
            log.warn("could not draw image", e);
        } finally {
            template.restoreState();
        }
    }

    /**
     * Draw a path specified by relative coordinates in [0,1] range wrt the specified rectangle.
     *
     * @param x x-ordinate
     * @param y y-ordinate
     * @param rect rectangle
     * @param color color to use
     * @param lineWidth line width
     * @param dashArray lengths for dashed and white area
     */
    public void drawRelativePath(float[] x, float[] y, Rectangle rect, Color color, float lineWidth,
            float[] dashArray) {
        template.saveState();
        setStroke(color, lineWidth, dashArray);
        template.moveTo(origX + getAbsoluteX(x[0], rect), origY + getAbsoluteY(y[0], rect));
        for (int i = 1; i < x.length; i++) {
            template.lineTo(origX + getAbsoluteX(x[i], rect), origY + getAbsoluteY(y[i], rect));
        }
        template.stroke();
        template.restoreState();
    }

    private float getAbsoluteX(float f, Rectangle rect) {
        return rect.getLeft() + f * rect.getWidth();
    }

    private float getAbsoluteY(float f, Rectangle rect) {
        return rect.getBottom() + f * rect.getHeight();
    }

    private void setStroke(Color color, float linewidth, float[] dashArray) {
        // Color and transparency
        PdfGState state = new PdfGState();
        state.setStrokeOpacity(color.getAlpha());
        state.setBlendMode(PdfGState.BM_NORMAL);
        template.setGState(state);
        template.setColorStroke(color);
        // linewidth
        template.setLineWidth(linewidth);
        if (dashArray != null) {
            template.setLineDash(dashArray, 0f);
        }
    }

    private void setFill(Color color) {
        // Color and transparency
        PdfGState state = new PdfGState();
        state.setFillOpacity(color.getAlpha() / 255f);
        state.setBlendMode(PdfGState.BM_NORMAL);
        template.setGState(state);
        template.setColorFill(color);
    }

    /**
     * Draw the specified geometry.
     *
     * @param geometry geometry to draw
     * @param symbol symbol for geometry
     * @param fillColor fill colour
     * @param strokeColor stroke colour
     * @param lineWidth line width
     * @param clipRect clipping rectangle
     */
    public void drawGeometry(Geometry geometry, SymbolInfo symbol, Color fillColor, Color strokeColor,
            float lineWidth, float[] dashArray, Rectangle clipRect) {
        template.saveState();
        // clipping code
        if (clipRect != null) {
            template.rectangle(clipRect.getLeft() + origX, clipRect.getBottom() + origY, clipRect.getWidth(),
                    clipRect.getHeight());
            template.clip();
            template.newPath();
        }
        setStroke(strokeColor, lineWidth, dashArray);
        setFill(fillColor);
        drawGeometry(geometry, symbol);
        template.restoreState();
    }

    private void drawGeometry(Geometry g, SymbolInfo symbol) {
        if (g instanceof MultiPolygon) {
            MultiPolygon mpoly = (MultiPolygon) g;
            for (int i = 0; i < mpoly.getNumGeometries(); i++) {
                drawGeometry(mpoly.getGeometryN(i), symbol);
            }
        } else if (g instanceof MultiLineString) {
            MultiLineString mline = (MultiLineString) g;
            for (int i = 0; i < mline.getNumGeometries(); i++) {
                drawGeometry(mline.getGeometryN(i), symbol);
            }
        } else if (g instanceof MultiPoint) {
            MultiPoint mpoint = (MultiPoint) g;
            for (int i = 0; i < mpoint.getNumGeometries(); i++) {
                drawGeometry(mpoint.getGeometryN(i), symbol);
            }
        } else if (g instanceof Polygon) {
            Polygon poly = (Polygon) g;
            LineString shell = poly.getExteriorRing();
            int nHoles = poly.getNumInteriorRing();
            drawPathContent(shell.getCoordinates());
            for (int j = 0; j < nHoles; j++) {
                drawPathContent(poly.getInteriorRingN(j).getCoordinates());
            }
            template.closePathEoFillStroke();
        } else if (g instanceof LineString) {
            LineString line = (LineString) g;
            drawPathContent(line.getCoordinates());
            template.stroke();
        } else if (g instanceof Point) {
            Point point = (Point) g;
            drawPoint(point.getCoordinate(), symbol);
            template.fillStroke();
        }
    }

    private void drawPathContent(Coordinate[] coord) {
        template.moveTo(origX + (float) coord[0].x, origY + (float) coord[0].y);
        for (int i = 1; i < coord.length; i++) {
            template.lineTo(origX + (float) coord[i].x, origY + (float) coord[i].y);
        }
    }

    private void drawPoint(Coordinate coord, SymbolInfo symbol) {
        if (symbol.getImage() != null) {
            try {
                Image pointImage = Image.getInstance(symbol.getImage().getHref());
                //template.addImage(image, width, 0, 0, height, x, y)
                template.addImage(pointImage, symbol.getImage().getWidth(), 0, 0, symbol.getImage().getHeight(),
                        origX + (float) coord.x, origY + (float) coord.y);
            } catch (Exception ex) { // NOSONAR
                log.error("Not able to create POINT image for rendering", ex);
            }
        } else if (symbol.getCircle() != null) {
            float radius = symbol.getCircle().getR();

            template.circle(origX + (float) coord.x, origY + (float) coord.y, radius);

        } else if (symbol.getRect() != null) {
            float width = symbol.getRect().getW();
            float height = symbol.getRect().getW();

            template.rectangle(origX + (float) coord.x - (width / 2), origY + (float) coord.y - (height / 2), width,
                    height);

        }

    }

    /**
     * Return this context as an image.
     *
     * @return this context as image
     * @throws BadElementException oops
     */
    public Image getImage() throws BadElementException {
        return Image.getInstance(template);
    }

    /**
     * Push the current origin on the stack.
     */
    public void saveOrigin() {
        prevOrigX.push(origX);
        prevOrigY.push(origY);
    }

    /**
     * Pop the previous origin from the stack.
     */
    public void restoreOrigin() {
        origX = prevOrigX.pop();
        origY = prevOrigY.pop();
    }

    public Color makeTransparent(Color color, float alpha) {
        return new Color(color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, alpha);
    }

    public void drawLine(float x0, float y0, float x1, float y1, Color color, float lineWidth) {
        template.saveState();
        setStroke(color, lineWidth, null);
        template.moveTo(origX + x0, origY + y0);
        template.lineTo(origX + x1, origY + y1);
        template.stroke();
        template.restoreState();

    }

    /**
     * Converts an absolute rectangle to a relative one wrt to the current coordinate system.
     *
     * @param rect absolute rectangle
     * @return relative rectangle
     */
    public Rectangle toRelative(Rectangle rect) {
        return new Rectangle(rect.getLeft() - origX, rect.getBottom() - origY, rect.getRight() - origX,
                rect.getTop() - origY);
    }

    public Graphics2D getGraphics2D(Rectangle clipRect) {
        Graphics2D graphics = template.createGraphics(template.getWidth(), template.getHeight());
        graphics.translate(origX, template.getHeight() - origY - clipRect.getHeight());
        graphics.clipRect(0, 0, (int) clipRect.getWidth(), (int) clipRect.getHeight());
        return graphics;
    }

}