Java tutorial
/* * 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; } }