airviewer.AnnotationGenerator.java Source code

Java tutorial

Introduction

Here is the source code for airviewer.AnnotationGenerator.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package airviewer;

import java.awt.geom.AffineTransform;
import java.io.IOException;
import static java.lang.Float.max;
import java.rmi.server.UID;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationMarkup;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationSquareCircle;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationText;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary;
import org.apache.pdfbox.util.Matrix;

/**
 *
 * @author erik
 */
public class AnnotationGenerator {

    /**
     * 
     */
    public static final float SIZE_UNITS_PER_PDF_POINT = 1000.0f;

    /**
     * 
     */
    public static final float FONT_SIZE_PDF_POINTS = 14.0f;

    /**
     * 
     */
    public static final float MARGIN_SIZE_PDF_POINTS = 6.0f;

    /**
     * 
     */
    public static final float LINE_SPACE_SIZE_PDF_POINTS = 4.0f;

    /**
     * 
     */
    public static final PDFont FONT = PDType1Font.HELVETICA_OBLIQUE;

    /**
     * 
     */
    public static final PDColor BLACK = new PDColor(new float[] { 0, 0, 0 }, PDDeviceRGB.INSTANCE);

    /**
     * 
     */
    public static final PDColor GRAY = new PDColor(new float[] { .8f, .8f, .8f }, PDDeviceRGB.INSTANCE);

    /**
     * 
     */
    public static final PDColor RED = new PDColor(new float[] { 1, 0, 0 }, PDDeviceRGB.INSTANCE);

    /**
     * 
     */
    public static final PDColor GREEN = new PDColor(new float[] { 0, 1, 0 }, PDDeviceRGB.INSTANCE);

    /**
     * 
     */
    public static final PDColor BLUE = new PDColor(new float[] { 0, 0, 1 }, PDDeviceRGB.INSTANCE);

    /**
     *
     */
    @FunctionalInterface
    private interface GeneratorFunction {

        PDAppearanceStream generate(PDAnnotation a, PDDocument d, PDPage p, boolean shouldResize);
    }

    /**
     *
     */
    private static final HashMap<String, GeneratorFunction> SUBTYPE_TO_GENERATOR_MAP;

    /**
     *
     */
    static {
        SUBTYPE_TO_GENERATOR_MAP = new HashMap<>();
        SUBTYPE_TO_GENERATOR_MAP.put(PDAnnotationSquareCircle.SUB_TYPE_SQUARE,
                AnnotationGenerator::generateSquareAppearance);
        SUBTYPE_TO_GENERATOR_MAP.put(PDAnnotationSquareCircle.SUB_TYPE_CIRCLE,
                AnnotationGenerator::generateCircleAppearance);
        SUBTYPE_TO_GENERATOR_MAP.put(PDAnnotationText.SUB_TYPE_FREETEXT,
                AnnotationGenerator::generateTextAppearance);
    }

    /**
     *
     * @param a
     * @param d
     * @param p
     * @param shouldResize
     * @return
     */
    public static PDAppearanceStream generateAppearanceStream(PDAnnotation a, PDDocument d, PDPage p,
            boolean shouldResize) {
        assert null != a;
        assert null != d;
        assert null != p;

        PDAppearanceStream result = null;

        if (SUBTYPE_TO_GENERATOR_MAP.containsKey(a.getSubtype())) {
            result = SUBTYPE_TO_GENERATOR_MAP.get(a.getSubtype()).generate(a, d, p, shouldResize);
        }

        return result;
    }

    /**
     *
     * @param a
     * @throws IOException
     */
    public static void resizeAnnotationToContent(PDAnnotation a) throws IOException {
        assert null != a;

        final String contents = a.getContents();
        final boolean hasContents = null != contents && 0 < contents.length();
        if (hasContents) {
            float borderWidth = 0;
            if (null != a.getColor() && a instanceof PDAnnotationMarkup) {
                final PDBorderStyleDictionary borderStyle = ((PDAnnotationMarkup) a).getBorderStyle();
                if (null != borderStyle) {
                    borderWidth = Math.abs(borderStyle.getWidth());
                }
            }
            final float margin = MARGIN_SIZE_PDF_POINTS;
            final float fontSize = FONT_SIZE_PDF_POINTS;
            final float textHeight = fontSize + LINE_SPACE_SIZE_PDF_POINTS;
            PDRectangle position = a.getRectangle();
            final float lowerLeftX = position.getLowerLeftX();
            final float lowerLeftY = position.getLowerLeftY();

            float width = FONT.getStringWidth(contents) * fontSize / SIZE_UNITS_PER_PDF_POINT;
            float height = textHeight;

            // Reserve enough width for border and margin at start and end
            width += (borderWidth + MARGIN_SIZE_PDF_POINTS) * 2.0f;
            height += (borderWidth + MARGIN_SIZE_PDF_POINTS) * 2.0f;

            // Replace the annotations existing rectangle
            a.setRectangle(new PDRectangle(lowerLeftX, lowerLeftY, width, height));
        }

    }

    /**
     *
     * @param a
     * @param d
     * @param p
     * @param shouldResize
     * @return
     */
    public static PDAppearanceStream generateSquareAppearance(PDAnnotation a, PDDocument d, PDPage p,
            boolean shouldResize) {
        assert null != a;
        assert null != d;
        assert null != p;

        PDAppearanceStream annotationAppearanceStream = null;

        try {
            if (shouldResize) {
                resizeAnnotationToContent(a);
            }

            final String contents = a.getContents();
            final boolean hasContents = null != contents && 0 < contents.length();
            float borderWidth = 0;
            if (a instanceof PDAnnotationMarkup) {
                final PDBorderStyleDictionary borderStyle = ((PDAnnotationMarkup) a).getBorderStyle();
                if (null != a.getColor() && null != borderStyle) {
                    borderWidth = Math.abs(borderStyle.getWidth());
                }
            }
            final float fontSize = FONT_SIZE_PDF_POINTS;
            final float textHeight = fontSize;
            final float margin = MARGIN_SIZE_PDF_POINTS;

            PDRectangle position = a.getRectangle();
            final float lowerLeftX = position.getLowerLeftX();
            final float lowerLeftY = position.getLowerLeftY();
            float width = position.getWidth();
            float height = position.getHeight();

            annotationAppearanceStream = new PDAppearanceStream(d);

            annotationAppearanceStream.setBBox(position);
            annotationAppearanceStream.setMatrix(new AffineTransform());
            annotationAppearanceStream.setResources(p.getResources());

            try (PDPageContentStream appearanceContent = new PDPageContentStream(d, annotationAppearanceStream)) {
                appearanceContent.transform(new Matrix()); // Identity transform

                // Rect is inset by half border width to prevent border leaking
                // outside bounding box
                final float insetLowerLeftX = lowerLeftX + borderWidth * 0.5f;
                final float insetLowerLeftY = lowerLeftY + borderWidth * 0.5f;
                final float insetWidth = width - borderWidth;
                final float insetheight = height - borderWidth;
                appearanceContent.addRect(insetLowerLeftX, insetLowerLeftY, insetWidth, insetheight);
                appearanceContent.setLineWidth(borderWidth);
                appearanceContent.setNonStrokingColor(GRAY);

                if (null != a.getColor() && 0 < borderWidth) {
                    appearanceContent.setStrokingColor(a.getColor());
                    appearanceContent.fillAndStroke();
                } else {
                    appearanceContent.fill();

                }

                if (hasContents) {
                    appearanceContent.moveTo(0, 0);
                    appearanceContent.beginText();

                    // Center vertically, left justified inside border with margin
                    appearanceContent.newLineAtOffset(lowerLeftX + borderWidth + margin,
                            lowerLeftY + (height + LINE_SPACE_SIZE_PDF_POINTS) * 0.5f - textHeight * 0.5f);
                    appearanceContent.setFont(FONT, fontSize);
                    if (null != a.getColor()) {
                        appearanceContent.setNonStrokingColor(a.getColor()); // Sets color of text
                    } else {
                        appearanceContent.setNonStrokingColor(BLACK); // Sets color of text

                    }
                    appearanceContent.showText(a.getContents());
                    appearanceContent.endText();
                }
            }
        } catch (IOException ex) {
            Logger.getLogger(AnnotationGenerator.class.getName()).log(Level.SEVERE, null, ex);
        }

        return annotationAppearanceStream;
    }

    /**
     *
     * @param a
     * @param d
     * @param p
     * @param shouldResize
     * @return
     */
    public static PDAppearanceStream generateCircleAppearance(PDAnnotation a, PDDocument d, PDPage p,
            boolean shouldResize) {
        assert null != a;
        assert null != d;
        assert null != p;

        PDAppearanceStream annotationAppearanceStream = null;

        try {
            if (shouldResize) {
                resizeAnnotationToContent(a);
            }

            final String contents = a.getContents();
            final boolean hasContents = null != contents && 0 < contents.length();
            float borderWidth = 0;
            if (a instanceof PDAnnotationMarkup) {
                final PDBorderStyleDictionary borderStyle = ((PDAnnotationMarkup) a).getBorderStyle();
                if (null != a.getColor() && null != borderStyle) {
                    borderWidth = Math.abs(borderStyle.getWidth());
                }
            }
            final float fontSize = FONT_SIZE_PDF_POINTS;
            final float textHeight = fontSize;
            final float margin = MARGIN_SIZE_PDF_POINTS;

            PDRectangle position = a.getRectangle();
            final float lowerLeftX = position.getLowerLeftX();
            final float lowerLeftY = position.getLowerLeftY();
            float width = position.getWidth();
            float height = position.getHeight();

            annotationAppearanceStream = new PDAppearanceStream(d);

            annotationAppearanceStream.setBBox(position);
            annotationAppearanceStream.setMatrix(new AffineTransform());
            annotationAppearanceStream.setResources(p.getResources());

            try (PDPageContentStream appearanceContent = new PDPageContentStream(d, annotationAppearanceStream)) {
                appearanceContent.transform(new Matrix()); // Identity transform

                // Rect is inset by half border width to prevent border leaking
                // outside bounding box
                final float insetLowerLeftX = lowerLeftX + borderWidth * 0.5f;
                final float insetLowerLeftY = lowerLeftY + borderWidth * 0.5f;
                final float insetWidth = width - borderWidth;
                final float insetheight = height - borderWidth;

                if (null != a.getColor()) {
                    appearanceContent.setLineWidth(borderWidth);
                    appearanceContent.moveTo(insetLowerLeftX, insetLowerLeftY + insetheight * 0.5f);
                    appearanceContent.curveTo(insetLowerLeftX, insetLowerLeftY + insetheight * 0.75f,
                            insetLowerLeftX + insetWidth * 0.25f, insetLowerLeftY + insetheight,
                            insetLowerLeftX + insetWidth * 0.5f, insetLowerLeftY + insetheight);
                    appearanceContent.curveTo(insetLowerLeftX + insetWidth * 0.75f, insetLowerLeftY + insetheight,
                            insetLowerLeftX + insetWidth, insetLowerLeftY + insetheight * 0.75f,
                            insetLowerLeftX + insetWidth, insetLowerLeftY + insetheight * 0.5f);
                    appearanceContent.curveTo(insetLowerLeftX + insetWidth, insetLowerLeftY + insetheight * 0.25f,
                            insetLowerLeftX + insetWidth * 0.75f, insetLowerLeftY,
                            insetLowerLeftX + insetWidth * 0.5f, insetLowerLeftY);
                    appearanceContent.curveTo(insetLowerLeftX + insetWidth * 0.25f, insetLowerLeftY,
                            insetLowerLeftX, insetLowerLeftY + insetheight * 0.25f, insetLowerLeftX,
                            insetLowerLeftY + insetheight * 0.5f);
                }
                appearanceContent.setNonStrokingColor(GRAY);

                if (null != a.getColor() && 0 < borderWidth) {
                    appearanceContent.setStrokingColor(a.getColor());
                    appearanceContent.fillAndStroke();
                } else {
                    appearanceContent.fill();

                }

                if (hasContents) {
                    appearanceContent.moveTo(0, 0);
                    appearanceContent.beginText();

                    // Center text vertically, left justified inside border with margin
                    appearanceContent.newLineAtOffset(lowerLeftX + borderWidth + margin,
                            lowerLeftY + (height + LINE_SPACE_SIZE_PDF_POINTS) * 0.5f - textHeight * 0.5f);
                    appearanceContent.setFont(FONT, fontSize);
                    appearanceContent.setNonStrokingColor(BLACK); // Sets color of text
                    appearanceContent.showText(a.getContents());
                    appearanceContent.endText();
                }
            }
        } catch (IOException ex) {
            Logger.getLogger(AnnotationGenerator.class.getName()).log(Level.SEVERE, null, ex);
        }

        return annotationAppearanceStream;
    }

    /**
     *
     * @param a
     * @param d
     * @param p
     * @param shouldResize
     * @return
     */
    public static PDAppearanceStream generateTextAppearance(PDAnnotation a, PDDocument d, PDPage p,
            boolean shouldResize) {
        assert null != a;
        assert null != d;
        assert null != p;

        return generateSquareAppearance(a, d, p, shouldResize);
    }

    /**
     * 
     * @param document
     * @param page
     * @param subtype
     * @param position
     * @param borderColor
     * @param fillColor
     * @param borderWidth
     * @param contents
     * @return 
     */
    public static PDAnnotationMarkup makeAnnotation(PDDocument document, PDPage page, String subtype,
            PDRectangle position, PDColor borderColor, PDColor fillColor, float borderWidth, String contents) {
        assert null != document;
        assert null != page;
        assert null != subtype;
        assert null != position;

        PDAnnotationMarkup result = null;

        try {
            if (null != contents && 0 < contents.length()) {
                PDFont font = FONT;
                final float fontSize = FONT_SIZE_PDF_POINTS;
                final float lineSpacing = LINE_SPACE_SIZE_PDF_POINTS;
                float width = max(position.getWidth(),
                        font.getStringWidth(contents) * fontSize / AnnotationGenerator.SIZE_UNITS_PER_PDF_POINT);
                position.setUpperRightX(position.getLowerLeftX() + width);
            }

            PDAnnotationSquareCircle a = new PDAnnotationSquareCircle(subtype);
            a.setAnnotationName(new UID().toString());
            a.setContents(contents);
            a.setRectangle(position);

            if (null != fillColor) {
                a.setInteriorColor(fillColor);
            }

            if (0 < borderWidth) {
                PDBorderStyleDictionary borderStyle = new PDBorderStyleDictionary();
                borderStyle.setWidth(borderWidth);
                a.setBorderStyle(borderStyle);
                if (null != borderColor) {
                    a.setColor(borderColor);

                }
            }

            // The following lines are needed for PDFRenderer to render 
            // annotations. Preview and Acrobat don't seem to need these.
            if (null == a.getAppearance()) {
                a.setAppearance(new PDAppearanceDictionary());
                PDAppearanceStream annotationAppearanceStream = AnnotationGenerator.generateAppearanceStream(a,
                        document, page, true);
                a.getAppearance().setNormalAppearance(annotationAppearanceStream);
            }

            result = a;

        } catch (IOException | NullPointerException ex) {
            Logger.getLogger(EllipseAnnotationMaker.class.getName()).log(Level.SEVERE, null, ex);
        }

        return result;
    }
}