You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ti...@apache.org on 2018/06/12 16:35:12 UTC

svn commit: r1833411 - /pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDTextAppearanceHandler.java

Author: tilman
Date: Tue Jun 12 16:35:12 2018
New Revision: 1833411

URL: http://svn.apache.org/viewvc?rev=1833411&view=rev
Log:
PDFBOX-3353: support /Paragraph and /NewParagraph; use fixed sizes for BBox, adjust rectangle only if NoZoom isn't set, set flags if missing

Modified:
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDTextAppearanceHandler.java

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDTextAppearanceHandler.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDTextAppearanceHandler.java?rev=1833411&r1=1833410&r2=1833411&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDTextAppearanceHandler.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDTextAppearanceHandler.java Tue Jun 12 16:35:12 2018
@@ -23,6 +23,7 @@ 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.pdmodel.PDAppearanceContentStream;
 import org.apache.pdfbox.pdmodel.common.PDRectangle;
 import org.apache.pdfbox.pdmodel.font.PDType1Font;
@@ -50,6 +51,8 @@ public class PDTextAppearanceHandler ext
         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);
     }
 
     public PDTextAppearanceHandler(PDAnnotation annotation)
@@ -71,7 +74,10 @@ public class PDTextAppearanceHandler ext
         PDAnnotationText annotation = (PDAnnotationText) getAnnotation();
         if (!SUPPORTED_NAMES.contains(annotation.getName()))
         {
-            //TODO Comment, Key, NewParagraph, Paragraph
+            //TODO Comment, Key
+            // BBox values:
+            // key 18 18
+            // Comment 18 18
             return;
         }
 
@@ -91,26 +97,28 @@ public class PDTextAppearanceHandler ext
 
             setOpacity(contentStream, annotation.getConstantOpacity());
 
-            PDRectangle rect = getRectangle();
-            PDRectangle bbox = rect.createRetranslatedRectangle();
-            annotation.getNormalAppearanceStream().setBBox(bbox);
-
             switch (annotation.getName())
             {
                 case PDAnnotationText.NAME_NOTE:
-                    drawNote(contentStream, bbox);
+                    drawNote(annotation, contentStream);
                     break;
                 case PDAnnotationText.NAME_CROSS:
-                    drawCross(contentStream, bbox);
+                    drawCross(annotation, contentStream);
                     break;
                 case PDAnnotationText.NAME_CIRCLE:
-                    drawCircles(contentStream, bbox);
+                    drawCircles(annotation, contentStream);
                     break;
                 case PDAnnotationText.NAME_INSERT:
-                    drawInsert(contentStream, bbox);
+                    drawInsert(annotation, contentStream);
                     break;
                 case PDAnnotationText.NAME_HELP:
-                    drawHelp(contentStream, bbox);
+                    drawHelp(annotation, contentStream);
+                    break;
+                case PDAnnotationText.NAME_PARAGRAPH:
+                    drawParagraph(annotation, contentStream);
+                    break;
+                case PDAnnotationText.NAME_NEW_PARAGRAPH:
+                    drawNewParagraph(annotation, contentStream);
                     break;
                 default:
                     break;
@@ -122,10 +130,44 @@ public class PDTextAppearanceHandler ext
         }
     }
 
-    private void drawNote(final PDAppearanceContentStream contentStream, PDRectangle bbox)
+    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
     {
-        contentStream.setLineJoinStyle(1); // get round edge the easy way
+        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);
@@ -139,9 +181,11 @@ public class PDTextAppearanceHandler ext
         contentStream.fillAndStroke();
     }
 
-    private void drawCircles(final PDAppearanceContentStream contentStream, PDRectangle bbox)
+    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
@@ -150,10 +194,8 @@ public class PDTextAppearanceHandler ext
         // 5) stroke + fill
         // with square width 20 small r = 6.36, large r = 9.756
 
-        // should be a square, but who knows...
-        float min = Math.min(bbox.getWidth(), bbox.getHeight());
-        float smallR = min / 20 * 6.36f;
-        float largeR = min / 20 * 9.756f;
+        float smallR = 6.36f;
+        float largeR = 9.756f;
 
         contentStream.setMiterLimit(4);
         contentStream.setLineJoinStyle(1);
@@ -177,9 +219,11 @@ public class PDTextAppearanceHandler ext
         contentStream.fillAndStroke();
     }
 
-    private void drawInsert(final PDAppearanceContentStream contentStream, PDRectangle bbox)
+    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);
@@ -190,9 +234,11 @@ public class PDTextAppearanceHandler ext
         contentStream.closeAndFillAndStroke();
     }
 
-    private void drawCross(final PDAppearanceContentStream contentStream, PDRectangle bbox)
+    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());
 
@@ -221,9 +267,11 @@ public class PDTextAppearanceHandler ext
         contentStream.closeAndFillAndStroke();
     }
 
-    private void drawHelp(final PDAppearanceContentStream contentStream, PDRectangle bbox)
+    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);
@@ -249,11 +297,89 @@ public class PDTextAppearanceHandler ext
         // 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(540, 375));
+        contentStream.transform(Matrix.getTranslateInstance(555, 375));
 
         // 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("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();
+    }
+
+    //TODO this is mostly identical to drawHelp, except for scale, translation and symbol
+     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();
+        // draw the outer circle counterclockwise to fill area between circle and "?"
+        drawCircle2(contentStream, min / 2, min / 2, min / 2 - 1);
+        contentStream.fillAndStroke();
+    }
+
+    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 addPath(final PDAppearanceContentStream contentStream, GeneralPath path) throws IOException
+    {
         PathIterator it = path.getPathIterator(new AffineTransform());
         double[] coords = new double[6];
         while (!it.isDone())
@@ -283,10 +409,6 @@ public class PDTextAppearanceHandler ext
             }
             it.next();
         }
-        contentStream.restoreGraphicsState();
-        // draw the outer circle counterclockwise to fill area between circle and "?"
-        drawCircle2(contentStream, min / 2, min / 2, min / 2 - 1);
-        contentStream.fillAndStroke();
     }
 
     @Override