pdfbox-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From msahy...@apache.org
Subject svn commit: r1861478 - in /pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation: PDAnnotationText.java PDAnnotationTextMarkup.java handlers/PDTextAppearanceHandler.java
Date Sun, 16 Jun 2019 17:19:16 GMT
Author: msahyoun
Date: Sun Jun 16 17:19:16 2019
New Revision: 1861478

URL: http://svn.apache.org/viewvc?rev=1861478&view=rev
Log:
PDFBOX-4574: add support for Text appearance handler

Added:
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDTextAppearanceHandler.java
Modified:
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationText.java
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationTextMarkup.java

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationText.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationText.java?rev=1861478&r1=1861477&r2=1861478&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationText.java
(original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationText.java
Sun Jun 16 17:19:16 2019
@@ -18,6 +18,8 @@ package org.apache.pdfbox.pdmodel.intera
 
 import org.apache.pdfbox.cos.COSDictionary;
 import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDAppearanceHandler;
+import org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDTextAppearanceHandler;
 
 /**
  * This is the class that represents a text annotation.
@@ -26,6 +28,7 @@ import org.apache.pdfbox.cos.COSName;
  */
 public class PDAnnotationText extends PDAnnotationMarkup
 {
+    private PDAppearanceHandler customAppearanceHandler;
 
     /*
      * The various values of the Text as defined in the PDF 1.7 reference Table 172
@@ -67,6 +70,51 @@ public class PDAnnotationText extends PD
     public static final String NAME_INSERT = "Insert";
 
     /**
+     * Constant for the name of a circle annotation.
+     */
+    public static final String NAME_CIRCLE = "Circle";
+
+    /**
+     * Constant for the name of a cross annotation.
+     */
+    public static final String NAME_CROSS = "Cross";
+    
+    /**
+     * Constant for the name of a star annotation.
+     */
+    public static final String NAME_STAR = "Star";
+
+    /**
+     * Constant for the name of a check annotation.
+     */
+    public static final String NAME_CHECK = "Check";
+
+    /**
+     * Constant for the name of a right arrow annotation.
+     */
+    public static final String NAME_RIGHT_ARROW = "RightArrow";
+
+    /**
+     * Constant for the name of a right pointer annotation.
+     */
+    public static final String NAME_RIGHT_POINTER = "RightPointer";
+
+    /**
+     * Constant for the name of a crosshairs annotation.
+     */
+    public static final String NAME_UP_ARROW = "UpArrow";        
+
+    /**
+     * Constant for the name of a crosshairs annotation.
+     */
+    public static final String NAME_UP_LEFT_ARROW = "UpLeftArrow";        
+
+    /**
+     * Constant for the name of a crosshairs annotation.
+     */
+    public static final String NAME_CROSS_HAIRS = "CrossHairs";        
+
+    /**
      * The type of annotation.
      */
     public static final String SUB_TYPE = "Text";
@@ -171,4 +219,28 @@ public class PDAnnotationText extends PD
         this.getCOSObject().setString(COSName.STATE_MODEL, stateModel);
     }
 
+    /**
+     * Set a custom appearance handler for generating the annotations appearance streams.
+     * 
+     * @param appearanceHandler
+     */
+    public void setCustomAppearanceHandler(PDAppearanceHandler appearanceHandler)
+    {
+        customAppearanceHandler = appearanceHandler;
+    }
+
+    @Override
+    public void constructAppearances()
+    {
+        if (customAppearanceHandler == null)
+        {
+            PDTextAppearanceHandler appearanceHandler = new PDTextAppearanceHandler(this);
+            appearanceHandler.generateAppearanceStreams();
+        }
+        else
+        {
+            customAppearanceHandler.generateAppearanceStreams();
+        }
+    }
+
 }

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationTextMarkup.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationTextMarkup.java?rev=1861478&r1=1861477&r2=1861478&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationTextMarkup.java
(original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationTextMarkup.java
Sun Jun 16 17:19:16 2019
@@ -23,6 +23,7 @@ import org.apache.pdfbox.cos.COSName;
 import org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDAppearanceHandler;
 import org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDHighlightAppearanceHandler;
 import org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDSquigglyAppearanceHandler;
+import org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDStrikeoutAppearanceHandler;
 import org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDUnderlineAppearanceHandler;
 
 /**

Added: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDTextAppearanceHandler.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDTextAppearanceHandler.java?rev=1861478&view=auto
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDTextAppearanceHandler.java
(added)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDTextAppearanceHandler.java
Sun Jun 16 17:19:16 2019
@@ -0,0 +1,768 @@
+/*
+ * Copyright 2018 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.interactive.annotation.handlers;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.PathIterator;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.io.IOUtils;
+import org.apache.pdfbox.pdmodel.PDAppearanceContentStream;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+import org.apache.pdfbox.pdmodel.graphics.blend.BlendMode;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
+import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationText;
+import org.apache.pdfbox.util.Matrix;
+
+/**
+ *
+ * @author Tilman Hausherr
+ */
+public class PDTextAppearanceHandler extends PDAbstractAppearanceHandler
+{
+    private static final Log LOG = LogFactory.getLog(PDTextAppearanceHandler.class);
+
+    private static final Set<String> SUPPORTED_NAMES = new HashSet<String>();
+
+    static
+    {
+        SUPPORTED_NAMES.add(PDAnnotationText.NAME_NOTE);
+        SUPPORTED_NAMES.add(PDAnnotationText.NAME_INSERT);
+        SUPPORTED_NAMES.add(PDAnnotationText.NAME_CROSS);
+        SUPPORTED_NAMES.add(PDAnnotationText.NAME_HELP);
+        SUPPORTED_NAMES.add(PDAnnotationText.NAME_CIRCLE);
+        SUPPORTED_NAMES.add(PDAnnotationText.NAME_PARAGRAPH);
+        SUPPORTED_NAMES.add(PDAnnotationText.NAME_NEW_PARAGRAPH);
+        SUPPORTED_NAMES.add(PDAnnotationText.NAME_CHECK);
+        SUPPORTED_NAMES.add(PDAnnotationText.NAME_STAR);
+        SUPPORTED_NAMES.add(PDAnnotationText.NAME_RIGHT_ARROW);
+        SUPPORTED_NAMES.add(PDAnnotationText.NAME_RIGHT_POINTER);
+        SUPPORTED_NAMES.add(PDAnnotationText.NAME_CROSS_HAIRS);
+        SUPPORTED_NAMES.add(PDAnnotationText.NAME_UP_ARROW);
+        SUPPORTED_NAMES.add(PDAnnotationText.NAME_UP_LEFT_ARROW);
+        SUPPORTED_NAMES.add(PDAnnotationText.NAME_COMMENT);
+        SUPPORTED_NAMES.add(PDAnnotationText.NAME_KEY);
+    }
+
+    public PDTextAppearanceHandler(PDAnnotation annotation)
+    {
+        super(annotation);
+    }
+
+    @Override
+    public void generateAppearanceStreams()
+    {
+        generateNormalAppearance();
+        generateRolloverAppearance();
+        generateDownAppearance();
+    }
+
+    @Override
+    public void generateNormalAppearance()
+    {
+        PDAnnotationText annotation = (PDAnnotationText) getAnnotation();
+        if (!SUPPORTED_NAMES.contains(annotation.getName()))
+        {
+            return;
+        }
+
+        PDAppearanceContentStream contentStream = null; 
+
+        try
+        {
+            contentStream = getNormalAppearanceAsContentStream();
+
+            PDColor bgColor = getColor();
+            if (bgColor == null)
+            {
+                // White is used by Adobe when /C entry is missing
+                contentStream.setNonStrokingColor(1f);
+            }
+            else
+            {
+                contentStream.setNonStrokingColor(bgColor);
+            }
+            // stroking color is always black which is the PDF default
+
+            setOpacity(contentStream, annotation.getConstantOpacity());
+
+            String annotationTypeName =  annotation.getName();
+
+            if (PDAnnotationText.NAME_NOTE.equals(annotationTypeName))
+            {
+                drawNote(annotation, contentStream);
+            }
+            else if (PDAnnotationText.NAME_CROSS.equals(annotationTypeName))
+            {
+                drawCross(annotation, contentStream);
+            }
+            else if (PDAnnotationText.NAME_CIRCLE.equals(annotationTypeName))
+            {
+                drawCircles(annotation, contentStream);
+            }
+            else if (PDAnnotationText.NAME_INSERT.equals(annotationTypeName))
+            {
+                drawInsert(annotation, contentStream);
+            }
+            else if (PDAnnotationText.NAME_HELP.equals(annotationTypeName))
+            {
+                drawHelp(annotation, contentStream);
+            }
+            else if (PDAnnotationText.NAME_PARAGRAPH.equals(annotationTypeName))
+            {
+                drawParagraph(annotation, contentStream);
+            }
+            else if (PDAnnotationText.NAME_NEW_PARAGRAPH.equals(annotationTypeName))
+            {
+                drawNewParagraph(annotation, contentStream);
+            }
+            else if (PDAnnotationText.NAME_STAR.equals(annotationTypeName))
+            {
+                drawStar(annotation, contentStream);
+            }
+            else if (PDAnnotationText.NAME_CHECK.equals(annotationTypeName))
+            {
+                drawCheck(annotation, contentStream);
+            }
+            else if (PDAnnotationText.NAME_RIGHT_ARROW.equals(annotationTypeName))
+            {
+                drawRightArrow(annotation, contentStream);
+            }
+            else if (PDAnnotationText.NAME_RIGHT_POINTER.equals(annotationTypeName))
+            {
+                drawRightPointer(annotation, contentStream);
+            }
+            else if (PDAnnotationText.NAME_CROSS_HAIRS.equals(annotationTypeName))
+            {
+                drawCrossHairs(annotation, contentStream);
+            }
+            else if (PDAnnotationText.NAME_UP_ARROW.equals(annotationTypeName))
+            {
+                drawUpArrow(annotation, contentStream);
+            }
+            else if (PDAnnotationText.NAME_UP_LEFT_ARROW.equals(annotationTypeName))
+            {
+                drawUpLeftArrow(annotation, contentStream);
+            }
+            else if (PDAnnotationText.NAME_COMMENT.equals(annotationTypeName))
+            {
+                drawComment(annotation, contentStream);
+            }
+            else if (PDAnnotationText.NAME_KEY.equals(annotationTypeName))
+            {
+                drawKey(annotation, contentStream);
+            }
+        }
+        catch (IOException e)
+        {
+            LOG.error(e);
+        }
+        finally
+        {
+            IOUtils.closeQuietly(contentStream);
+        }
+    }
+
+    private PDRectangle adjustRectAndBBox(PDAnnotationText annotation, float width, float
height)
+    {
+        // For /Note (other types have different values):
+        // Adobe takes the left upper bound as anchor, and adjusts the rectangle to 18 x
20.
+        // Observed with files 007071.pdf, 038785.pdf, 038787.pdf,
+        // but not with 047745.pdf p133 and 084374.pdf p48, both have the NoZoom flag.
+        // there the BBox is also set to fixed values, but the rectangle is left untouched.
+        // When no flags are there, Adobe sets /F 24 = NoZoom NoRotate.
+            
+        PDRectangle rect = getRectangle();
+        PDRectangle bbox;
+        if (!annotation.isNoZoom())
+        {
+            rect.setUpperRightX(rect.getLowerLeftX() + width);
+            rect.setLowerLeftY(rect.getUpperRightY() - height);
+            annotation.setRectangle(rect);
+        }
+        if (!annotation.getCOSObject().containsKey(COSName.F))
+        {
+            // We set these flags because Adobe does so, but PDFBox doesn't support them
when rendering.
+            annotation.setNoRotate(true);
+            annotation.setNoZoom(true);
+        }
+        bbox = new PDRectangle(width, height);
+        annotation.getNormalAppearanceStream().setBBox(bbox);
+        return bbox;
+    }
+
+    private void drawNote(PDAnnotationText annotation, final PDAppearanceContentStream contentStream)
+            throws IOException
+    {
+        PDRectangle bbox = adjustRectAndBBox(annotation, 18, 20);
+        contentStream.setMiterLimit(4);
+
+        // get round edge the easy way. Adobe uses 4 lines with 4 arcs of radius 0.785 which
is bigger.
+        contentStream.setLineJoinStyle(1);
+
+        contentStream.setLineCapStyle(0);
+        contentStream.setLineWidth(0.61f); // value from Adobe
+        contentStream.addRect(1, 1, bbox.getWidth() - 2, bbox.getHeight() - 2);
+        contentStream.moveTo(bbox.getWidth() / 4,         bbox.getHeight() / 7 * 2);
+        contentStream.lineTo(bbox.getWidth() * 3 / 4 - 1, bbox.getHeight() / 7 * 2);
+        contentStream.moveTo(bbox.getWidth() / 4,         bbox.getHeight() / 7 * 3);
+        contentStream.lineTo(bbox.getWidth() * 3 / 4 - 1, bbox.getHeight() / 7 * 3);
+        contentStream.moveTo(bbox.getWidth() / 4,         bbox.getHeight() / 7 * 4);
+        contentStream.lineTo(bbox.getWidth() * 3 / 4 - 1, bbox.getHeight() / 7 * 4);
+        contentStream.moveTo(bbox.getWidth() / 4,         bbox.getHeight() / 7 * 5);
+        contentStream.lineTo(bbox.getWidth() * 3 / 4 - 1, bbox.getHeight() / 7 * 5);
+        contentStream.fillAndStroke();
+    }
+
+    private void drawCircles(PDAnnotationText annotation, final PDAppearanceContentStream
contentStream)
+            throws IOException
+    {
+        PDRectangle bbox = adjustRectAndBBox(annotation, 20, 20);
+
+        // strategy used by Adobe:
+        // 1) add small circle in white using /ca /CA 0.6 and width 1
+        // 2) fill
+        // 3) add small circle in one direction
+        // 4) add large circle in other direction
+        // 5) stroke + fill
+        // with square width 20 small r = 6.36, large r = 9.756
+
+        float smallR = 6.36f;
+        float largeR = 9.756f;
+
+        contentStream.setMiterLimit(4);
+        contentStream.setLineJoinStyle(1);
+        contentStream.setLineCapStyle(0);
+        contentStream.saveGraphicsState();
+        contentStream.setLineWidth(1);
+        PDExtendedGraphicsState gs = new PDExtendedGraphicsState();
+        gs.setAlphaSourceFlag(false);
+        gs.setStrokingAlphaConstant(0.6f);
+        gs.setNonStrokingAlphaConstant(0.6f);
+        gs.setBlendMode(BlendMode.NORMAL);
+        contentStream.setGraphicsStateParameters(gs);
+        contentStream.setNonStrokingColor(1f);
+        drawCircle(contentStream, bbox.getWidth() / 2, bbox.getHeight() / 2, smallR);
+        contentStream.fill();
+        contentStream.restoreGraphicsState();
+
+        contentStream.setLineWidth(0.59f); // value from Adobe
+        drawCircle(contentStream, bbox.getWidth() / 2, bbox.getHeight() / 2, smallR);
+        drawCircle2(contentStream, bbox.getWidth() / 2, bbox.getHeight() / 2, largeR);
+        contentStream.fillAndStroke();
+    }
+
+    private void drawInsert(PDAnnotationText annotation, final PDAppearanceContentStream
contentStream)
+            throws IOException
+    {
+        PDRectangle bbox = adjustRectAndBBox(annotation, 17, 20);
+
+        contentStream.setMiterLimit(4);
+        contentStream.setLineJoinStyle(0);
+        contentStream.setLineCapStyle(0);
+        contentStream.setLineWidth(0.59f); // value from Adobe
+        contentStream.moveTo(bbox.getWidth() / 2 - 1, bbox.getHeight() - 2);
+        contentStream.lineTo(1, 1);
+        contentStream.lineTo(bbox.getWidth() - 2, 1);
+        contentStream.closeAndFillAndStroke();
+    }
+
+    private void drawCross(PDAnnotationText annotation, final PDAppearanceContentStream contentStream)
+            throws IOException
+    {
+        PDRectangle bbox = adjustRectAndBBox(annotation, 19, 19);
+
+        // should be a square, but who knows...
+        float min = Math.min(bbox.getWidth(), bbox.getHeight());
+
+        // small = offset nearest bbox edge
+        // large = offset second nearest bbox edge
+        float small = min / 10;
+        float large = min / 5;
+
+        contentStream.setMiterLimit(4);
+        contentStream.setLineJoinStyle(1);
+        contentStream.setLineCapStyle(0);
+        contentStream.setLineWidth(0.59f); // value from Adobe
+
+        contentStream.moveTo(small, large);
+        contentStream.lineTo(large, small);
+        contentStream.lineTo(min / 2, min / 2 - small);
+        contentStream.lineTo(min - large, small);
+        contentStream.lineTo(min - small, large);
+        contentStream.lineTo(min / 2 + small, min / 2);
+        contentStream.lineTo(min - small, min - large);
+        contentStream.lineTo(min - large, min - small);
+        contentStream.lineTo(min / 2, min / 2 + small);
+        contentStream.lineTo(large, min - small);
+        contentStream.lineTo(small, min - large);
+        contentStream.lineTo(min / 2 - small, min / 2);
+        contentStream.closeAndFillAndStroke();
+        
+        // alternatively, this could also be drawn with Zapf Dingbats "a21"
+        // see DrawStar()
+    }
+
+    private void drawHelp(PDAnnotationText annotation, final PDAppearanceContentStream contentStream)
+            throws IOException
+    {
+        PDRectangle bbox = adjustRectAndBBox(annotation, 20, 20);
+
+        float min = Math.min(bbox.getWidth(), bbox.getHeight());
+
+        contentStream.setMiterLimit(4);
+        contentStream.setLineJoinStyle(1);
+        contentStream.setLineCapStyle(0);
+        contentStream.setLineWidth(0.59f); // value from Adobe
+
+        // Adobe first fills a white circle with CA ca 0.6, so do we
+        contentStream.saveGraphicsState();
+        contentStream.setLineWidth(1);
+        PDExtendedGraphicsState gs = new PDExtendedGraphicsState();
+        gs.setAlphaSourceFlag(false);
+        gs.setStrokingAlphaConstant(0.6f);
+        gs.setNonStrokingAlphaConstant(0.6f);
+        gs.setBlendMode(BlendMode.NORMAL);
+        contentStream.setGraphicsStateParameters(gs);
+        contentStream.setNonStrokingColor(1f);
+        drawCircle2(contentStream, min / 2, min / 2, min / 2 - 1);
+        contentStream.fill();
+        contentStream.restoreGraphicsState();
+
+        contentStream.saveGraphicsState();
+        // rescale so that "?" fits into circle and move "?" to circle center
+        // values gathered by trial and error
+        contentStream.transform(Matrix.getScaleInstance(0.001f * min / 2.25f, 0.001f * min
/ 2.25f));
+        contentStream.transform(Matrix.getTranslateInstance(500, 375));
+
+        // we get the shape of an Helvetica bold "?" and use that one.
+        // Adobe uses a different font (which one?), or created the shape from scratch.
+        GeneralPath path = PDType1Font.HELVETICA_BOLD.getPath("question");
+        addPath(contentStream, path);
+        contentStream.restoreGraphicsState();
+        // draw the outer circle counterclockwise to fill area between circle and "?"
+        drawCircle2(contentStream, min / 2, min / 2, min / 2 - 1);
+        contentStream.fillAndStroke();
+    }
+
+    private void drawParagraph(PDAnnotationText annotation, final PDAppearanceContentStream
contentStream)
+            throws IOException
+    {
+        PDRectangle bbox = adjustRectAndBBox(annotation, 20, 20);
+
+        float min = Math.min(bbox.getWidth(), bbox.getHeight());
+
+        contentStream.setMiterLimit(4);
+        contentStream.setLineJoinStyle(1);
+        contentStream.setLineCapStyle(0);
+        contentStream.setLineWidth(0.59f); // value from Adobe
+
+        // Adobe first fills a white circle with CA ca 0.6, so do we
+        contentStream.saveGraphicsState();
+        contentStream.setLineWidth(1);
+        PDExtendedGraphicsState gs = new PDExtendedGraphicsState();
+        gs.setAlphaSourceFlag(false);
+        gs.setStrokingAlphaConstant(0.6f);
+        gs.setNonStrokingAlphaConstant(0.6f);
+        gs.setBlendMode(BlendMode.NORMAL);
+        contentStream.setGraphicsStateParameters(gs);
+        contentStream.setNonStrokingColor(1f);
+        drawCircle2(contentStream, min / 2, min / 2, min / 2 - 1);
+        contentStream.fill();
+        contentStream.restoreGraphicsState();
+
+        contentStream.saveGraphicsState();
+        // rescale so that "?" fits into circle and move "?" to circle center
+        // values gathered by trial and error
+        contentStream.transform(Matrix.getScaleInstance(0.001f * min / 3, 0.001f * min /
3));
+        contentStream.transform(Matrix.getTranslateInstance(850, 900));
+
+        // we get the shape of an Helvetica "?" and use that one.
+        // Adobe uses a different font (which one?), or created the shape from scratch.
+        GeneralPath path = PDType1Font.HELVETICA.getPath("paragraph");
+        addPath(contentStream, path);
+        contentStream.restoreGraphicsState();
+        contentStream.fillAndStroke();
+        drawCircle(contentStream, min / 2, min / 2, min / 2 - 1);
+        contentStream.stroke();
+    }
+
+    private void drawNewParagraph(PDAnnotationText annotation, final PDAppearanceContentStream
contentStream)
+            throws IOException
+    {
+        adjustRectAndBBox(annotation, 13, 20);
+
+        contentStream.setMiterLimit(4);
+        contentStream.setLineJoinStyle(0);
+        contentStream.setLineCapStyle(0);
+        contentStream.setLineWidth(0.59f); // value from Adobe
+
+        // small triangle (values from Adobe)
+        contentStream.moveTo(6.4995f, 20);
+        contentStream.lineTo(0.295f, 7.287f);
+        contentStream.lineTo(12.705f, 7.287f);
+        contentStream.closeAndFillAndStroke();
+
+        // rescale and translate so that "NP" fits below the triangle
+        // values gathered by trial and error
+        contentStream.transform(Matrix.getScaleInstance(0.001f * 4, 0.001f * 4));
+        contentStream.transform(Matrix.getTranslateInstance(200, 0));
+        addPath(contentStream, PDType1Font.HELVETICA_BOLD.getPath("N"));
+        contentStream.transform(Matrix.getTranslateInstance(1300, 0));
+        addPath(contentStream, PDType1Font.HELVETICA_BOLD.getPath("P"));
+        contentStream.fill();
+    }
+
+    private void drawStar(PDAnnotationText annotation, final PDAppearanceContentStream contentStream)
+            throws IOException
+    {
+        PDRectangle bbox = adjustRectAndBBox(annotation, 20, 19);
+
+        float min = Math.min(bbox.getWidth(), bbox.getHeight());
+
+        contentStream.setMiterLimit(4);
+        contentStream.setLineJoinStyle(1);
+        contentStream.setLineCapStyle(0);
+        contentStream.setLineWidth(0.59f); // value from Adobe
+
+        contentStream.transform(Matrix.getScaleInstance(0.001f * min / 0.8f, 0.001f * min
/ 0.8f));
+
+        // we get the shape of a Zapf Dingbats star (0x2605) and use that one.
+        // Adobe uses a different font (which one?), or created the shape from scratch.
+        GeneralPath path = PDType1Font.ZAPF_DINGBATS.getPath("a35");
+        addPath(contentStream, path);
+        contentStream.fillAndStroke();
+    }
+
+    //TODO this is mostly identical to drawStar, except for scale, translation and symbol
+    // maybe use a table with all values and draw from there
+    // this could also optionally use outer circle
+    private void drawCheck(PDAnnotationText annotation, final PDAppearanceContentStream contentStream)
+            throws IOException
+    {
+        PDRectangle bbox = adjustRectAndBBox(annotation, 20, 19);
+
+        float min = Math.min(bbox.getWidth(), bbox.getHeight());
+
+        contentStream.setMiterLimit(4);
+        contentStream.setLineJoinStyle(1);
+        contentStream.setLineCapStyle(0);
+        contentStream.setLineWidth(0.59f); // value from Adobe
+
+        contentStream.transform(Matrix.getScaleInstance(0.001f * min / 0.8f, 0.001f * min
/ 0.8f));
+        contentStream.transform(Matrix.getTranslateInstance(0, 50));
+
+        // we get the shape of a Zapf Dingbats check (0x2714) and use that one.
+        // Adobe uses a different font (which one?), or created the shape from scratch.
+        GeneralPath path = PDType1Font.ZAPF_DINGBATS.getPath("a20");
+        addPath(contentStream, path);
+        contentStream.fillAndStroke();
+    }
+
+    //TODO this is mostly identical to drawStar, except for scale, translation and symbol
+    private void drawRightPointer(PDAnnotationText annotation, final PDAppearanceContentStream
contentStream)
+            throws IOException
+    {
+        PDRectangle bbox = adjustRectAndBBox(annotation, 20, 17);
+
+        float min = Math.min(bbox.getWidth(), bbox.getHeight());
+
+        contentStream.setMiterLimit(4);
+        contentStream.setLineJoinStyle(1);
+        contentStream.setLineCapStyle(0);
+        contentStream.setLineWidth(0.59f); // value from Adobe
+
+        contentStream.transform(Matrix.getScaleInstance(0.001f * min / 0.8f, 0.001f * min
/ 0.8f));
+        contentStream.transform(Matrix.getTranslateInstance(0, 50));
+
+        // we get the shape of a Zapf Dingbats right pointer (0x27A4) and use that one.
+        // Adobe uses a different font (which one?), or created the shape from scratch.
+        GeneralPath path = PDType1Font.ZAPF_DINGBATS.getPath("a174");
+        addPath(contentStream, path);
+        contentStream.fillAndStroke();
+    }
+
+    private void drawCrossHairs(PDAnnotationText annotation, final PDAppearanceContentStream
contentStream)
+            throws IOException
+    {
+        PDRectangle bbox = adjustRectAndBBox(annotation, 20, 20);
+
+        float min = Math.min(bbox.getWidth(), bbox.getHeight());
+
+        contentStream.setMiterLimit(4);
+        contentStream.setLineJoinStyle(0);
+        contentStream.setLineCapStyle(0);
+        contentStream.setLineWidth(0.61f); // value from Adobe
+
+        contentStream.transform(Matrix.getScaleInstance(0.001f * min / 1.5f, 0.001f * min
/ 1.5f));
+        contentStream.transform(Matrix.getTranslateInstance(0, 50));
+
+        // we get the shape of a Symbol crosshair (0x2295) and use that one.
+        // Adobe uses a different font (which one?), or created the shape from scratch.
+        GeneralPath path = PDType1Font.SYMBOL.getPath("circleplus");
+        addPath(contentStream, path);
+        contentStream.fillAndStroke();
+    }
+
+    private void drawUpArrow(PDAnnotationText annotation, final PDAppearanceContentStream
contentStream)
+                 throws IOException
+    {
+        adjustRectAndBBox(annotation, 17, 20);
+
+        contentStream.setMiterLimit(4);
+        contentStream.setLineJoinStyle(1);
+        contentStream.setLineCapStyle(0);
+        contentStream.setLineWidth(0.59f); // value from Adobe
+
+        contentStream.moveTo(1, 7);
+        contentStream.lineTo(5, 7);
+        contentStream.lineTo(5, 1);
+        contentStream.lineTo(12, 1);
+        contentStream.lineTo(12, 7);
+        contentStream.lineTo(16, 7);
+        contentStream.lineTo(8.5f, 19);
+        contentStream.closeAndFillAndStroke();
+    }
+
+    private void drawUpLeftArrow(PDAnnotationText annotation, final PDAppearanceContentStream
contentStream)
+                 throws IOException
+    {
+        adjustRectAndBBox(annotation, 17, 17);
+
+        contentStream.setMiterLimit(4);
+        contentStream.setLineJoinStyle(1);
+        contentStream.setLineCapStyle(0);
+        contentStream.setLineWidth(0.59f); // value from Adobe
+        
+        contentStream.transform(Matrix.getRotateInstance(Math.toRadians(45), 8, -4));
+
+        contentStream.moveTo(1, 7);
+        contentStream.lineTo(5, 7);
+        contentStream.lineTo(5, 1);
+        contentStream.lineTo(12, 1);
+        contentStream.lineTo(12, 7);
+        contentStream.lineTo(16, 7);
+        contentStream.lineTo(8.5f, 19);
+        contentStream.closeAndFillAndStroke();
+    }
+    
+    private void drawRightArrow(PDAnnotationText annotation, final PDAppearanceContentStream
contentStream)
+            throws IOException
+    {
+        PDRectangle bbox = adjustRectAndBBox(annotation, 20, 20);
+
+        float min = Math.min(bbox.getWidth(), bbox.getHeight());
+
+        contentStream.setMiterLimit(4);
+        contentStream.setLineJoinStyle(1);
+        contentStream.setLineCapStyle(0);
+        contentStream.setLineWidth(0.59f); // value from Adobe
+
+        // Adobe first fills a white circle with CA ca 0.6, so do we
+        contentStream.saveGraphicsState();
+        contentStream.setLineWidth(1);
+        PDExtendedGraphicsState gs = new PDExtendedGraphicsState();
+        gs.setAlphaSourceFlag(false);
+        gs.setStrokingAlphaConstant(0.6f);
+        gs.setNonStrokingAlphaConstant(0.6f);
+        gs.setBlendMode(BlendMode.NORMAL);
+        contentStream.setGraphicsStateParameters(gs);
+        contentStream.setNonStrokingColor(1f);
+        drawCircle2(contentStream, min / 2, min / 2, min / 2 - 1);
+        contentStream.fill();
+        contentStream.restoreGraphicsState();
+
+        contentStream.saveGraphicsState();
+        // rescale so that the glyph fits into circle and move it to circle center
+        // values gathered by trial and error
+        contentStream.transform(Matrix.getScaleInstance(0.001f * min / 1.3f, 0.001f * min
/ 1.3f));
+        contentStream.transform(Matrix.getTranslateInstance(200, 300));
+
+        // we get the shape of a Zapf Dingbats right arrow (0x2794) and use that one.
+        // Adobe uses a different font (which one?), or created the shape from scratch.
+        GeneralPath path = PDType1Font.ZAPF_DINGBATS.getPath("a160");
+        addPath(contentStream, path);
+        contentStream.restoreGraphicsState();
+        // surprisingly, this one not counterclockwise.
+        drawCircle(contentStream, min / 2, min / 2, min / 2 - 1);
+        contentStream.fillAndStroke();
+    }
+
+    private void drawComment(PDAnnotationText annotation, final PDAppearanceContentStream
contentStream)
+                 throws IOException
+    {
+        adjustRectAndBBox(annotation, 18, 18);
+
+        contentStream.setMiterLimit(4);
+        contentStream.setLineJoinStyle(1);
+        contentStream.setLineCapStyle(0);
+        contentStream.setLineWidth(200);
+
+        // Adobe first fills a white rectangle with CA ca 0.6, so do we
+        contentStream.saveGraphicsState();
+        contentStream.setLineWidth(1);
+        PDExtendedGraphicsState gs = new PDExtendedGraphicsState();
+        gs.setAlphaSourceFlag(false);
+        gs.setStrokingAlphaConstant(0.6f);
+        gs.setNonStrokingAlphaConstant(0.6f);
+        gs.setBlendMode(BlendMode.NORMAL);
+        contentStream.setGraphicsStateParameters(gs);
+        contentStream.setNonStrokingColor(1f);
+        contentStream.addRect(0.3f, 0.3f, 18-0.6f, 18-0.6f);
+        contentStream.fill();
+        contentStream.restoreGraphicsState();
+
+        contentStream.transform(Matrix.getScaleInstance(0.003f, 0.003f));
+        contentStream.transform(Matrix.getTranslateInstance(500, -300));
+
+        // outer shape was gathered from Font Awesome by "printing" comment.svg
+        // into a PDF and looking at the content stream
+        contentStream.moveTo(2549, 5269);
+        contentStream.curveTo(1307, 5269, 300, 4451, 300, 3441);
+        contentStream.curveTo(300, 3023, 474, 2640, 764, 2331);
+        contentStream.curveTo(633, 1985, 361, 1691, 357, 1688);
+        contentStream.curveTo(299, 1626, 283, 1537, 316, 1459);
+        contentStream.curveTo(350, 1382, 426, 1332, 510, 1332);
+        contentStream.curveTo(1051, 1332, 1477, 1558, 1733, 1739);
+        contentStream.curveTo(1987, 1659, 2261, 1613, 2549, 1613);
+        contentStream.curveTo(3792, 1613, 4799, 2431, 4799, 3441);
+        contentStream.curveTo(4799, 4451, 3792, 5269, 2549, 5269);
+        contentStream.closePath();
+
+        // can't use addRect: if we did that, we wouldn't get the donut effect
+        contentStream.moveTo(0.3f / 0.003f - 500, 0.3f / 0.003f + 300);
+        contentStream.lineTo(0.3f / 0.003f - 500, 0.3f / 0.003f + 300 + 17.4f / 0.003f);
+        contentStream.lineTo(0.3f / 0.003f - 500 + 17.4f / 0.003f, 0.3f / 0.003f + 300 +
17.4f / 0.003f);
+        contentStream.lineTo(0.3f / 0.003f - 500 + 17.4f / 0.003f, 0.3f / 0.003f + 300);
+
+        contentStream.closeAndFillAndStroke();
+    }
+    
+    private void drawKey(PDAnnotationText annotation, final PDAppearanceContentStream contentStream)
+                 throws IOException
+    {
+        adjustRectAndBBox(annotation, 13, 18);
+
+        contentStream.setMiterLimit(4);
+        contentStream.setLineJoinStyle(1);
+        contentStream.setLineCapStyle(0);
+        contentStream.setLineWidth(200);
+
+        contentStream.transform(Matrix.getScaleInstance(0.003f, 0.003f));
+        contentStream.transform(Matrix.getRotateInstance(Math.toRadians(45), 2500, -800));
+
+        // shape was gathered from Font Awesome by "printing" key.svg into a PDF
+        // and looking at the content stream
+        contentStream.moveTo(4799, 4004);
+        contentStream.curveTo(4799, 3149, 4107, 2457, 3253, 2457);
+        contentStream.curveTo(3154, 2457, 3058, 2466, 2964, 2484);
+        contentStream.lineTo(2753, 2246);
+        contentStream.curveTo(2713, 2201, 2656, 2175, 2595, 2175);
+        contentStream.lineTo(2268, 2175);
+        contentStream.lineTo(2268, 1824);
+        contentStream.curveTo(2268, 1707, 2174, 1613, 2057, 1613);
+        contentStream.lineTo(1706, 1613);
+        contentStream.lineTo(1706, 1261);
+        contentStream.curveTo(1706, 1145, 1611, 1050, 1495, 1050);
+        contentStream.lineTo(510, 1050);
+        contentStream.curveTo(394, 1050, 300, 1145, 300, 1261);
+        contentStream.lineTo(300, 1947);
+        contentStream.curveTo(300, 2003, 322, 2057, 361, 2097);
+        contentStream.lineTo(1783, 3519);
+        contentStream.curveTo(1733, 3671, 1706, 3834, 1706, 4004);
+        contentStream.curveTo(1706, 4858, 2398, 5550, 3253, 5550);
+        contentStream.curveTo(4109, 5550, 4799, 4860, 4799, 4004);
+        contentStream.closePath();
+        contentStream.moveTo(3253, 4425);
+        contentStream.curveTo(3253, 4192, 3441, 4004, 3674, 4004);
+        contentStream.curveTo(3907, 4004, 4096, 4192, 4096, 4425);
+        contentStream.curveTo(4096, 4658, 3907, 4847, 3674, 4847);
+        contentStream.curveTo(3441, 4847, 3253, 4658, 3253, 4425);
+        contentStream.fillAndStroke();
+    }
+    
+    private void addPath(final PDAppearanceContentStream contentStream, GeneralPath path)
throws IOException
+    {
+        double curX = 0;
+        double curY = 0;
+        PathIterator it = path.getPathIterator(new AffineTransform());
+        double[] coords = new double[6];
+        while (!it.isDone())
+        {
+            int type = it.currentSegment(coords);
+            switch (type)
+            {
+                case PathIterator.SEG_CLOSE:
+                    contentStream.closePath();
+                    break;
+                case PathIterator.SEG_CUBICTO:
+                    contentStream.curveTo((float) coords[0], (float) coords[1], (float) coords[2],
+                                          (float) coords[3], (float) coords[4], (float) coords[5]);
+                    curX = coords[4];
+                    curY = coords[5];
+                    break;
+                case PathIterator.SEG_QUADTO:
+                    // Convert quadratic Bézier curve to cubic
+                    // https://fontforge.github.io/bezier.html
+                    // CP1 = QP0 + 2/3 *(QP1-QP0)
+                    // CP2 = QP2 + 2/3 *(QP1-QP2)
+                    double cp1x = curX + 2d / 3d * (coords[0] - curX);
+                    double cp1y = curY + 2d / 3d * (coords[1] - curY);
+                    double cp2x = coords[2] + 2d / 3d * (coords[0] - coords[2]);
+                    double cp2y = coords[3] + 2d / 3d * (coords[1] - coords[3]);
+                    contentStream.curveTo((float) cp1x, (float) cp1y,
+                                          (float) cp2x, (float) cp2y,
+                                          (float) coords[2], (float) coords[3]);
+                    curX = coords[2];
+                    curY = coords[3];
+                    break;
+                case PathIterator.SEG_LINETO:
+                    contentStream.lineTo((float) coords[0], (float) coords[1]);
+                    curX = coords[0];
+                    curY = coords[1];
+                    break;
+                case PathIterator.SEG_MOVETO:
+                    contentStream.moveTo((float) coords[0], (float) coords[1]);
+                    curX = coords[0];
+                    curY = coords[1];
+                    break;
+                default:
+                    break;
+            }
+            it.next();
+        }
+    }
+
+    @Override
+    public void generateRolloverAppearance()
+    {
+        // No rollover appearance generated
+    }
+
+    @Override
+    public void generateDownAppearance()
+    {
+        // No down appearance generated
+    }
+}



Mime
View raw message