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

svn commit: r1861089 [2/2] - in /pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel: ./ interactive/annotation/ interactive/annotation/handlers/

Added: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDAbstractAppearanceHandler.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDAbstractAppearanceHandler.java?rev=1861089&view=auto
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDAbstractAppearanceHandler.java (added)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDAbstractAppearanceHandler.java Wed Jun 12 06:08:45 2019
@@ -0,0 +1,536 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+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.PDAppearanceContentStream;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLine;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationSquareCircle;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceEntry;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
+
+/**
+ * Generic handler to generate the fields appearance.
+ * 
+ * Individual handler will provide specific implementations for different field
+ * types.
+ * 
+ */
+public abstract class PDAbstractAppearanceHandler implements PDAppearanceHandler
+{
+    private final PDAnnotation annotation;
+
+    /**
+     * Line ending styles where the line has to be drawn shorter (minus line width).
+     */
+    protected static final Set<String> SHORT_STYLES = createShortStyles();
+
+    static final double ARROW_ANGLE = Math.toRadians(30);
+
+    /**
+     * Line ending styles where there is an interior color.
+     */
+    protected static final Set<String> INTERIOR_COLOR_STYLES = createInteriorColorStyles();
+    
+    /**
+     * Line ending styles where the shape changes its angle, e.g. arrows.
+     */
+    protected static final Set<String> ANGLED_STYLES = createAngledStyles();
+
+    public PDAbstractAppearanceHandler(PDAnnotation annotation)
+    {
+        this.annotation = annotation;
+    }
+
+    @Override
+    public abstract void generateNormalAppearance();
+
+    @Override
+    public abstract void generateRolloverAppearance();
+
+    @Override
+    public abstract void generateDownAppearance();
+
+    PDAnnotation getAnnotation()
+    {
+        return annotation;
+    }
+
+    PDColor getColor()
+    {
+        return annotation.getColor();
+    }
+
+    PDRectangle getRectangle()
+    {
+        return annotation.getRectangle();
+    }
+
+    /**
+     * Get the annotations appearance dictionary.
+     * 
+     * <p>
+     * This will get the annotations appearance dictionary. If this is not
+     * existent an empty appearance dictionary will be created.
+     * 
+     * @return the annotations appearance dictionary
+     */
+    PDAppearanceDictionary getAppearance()
+    {
+        PDAppearanceDictionary appearanceDictionary = annotation.getAppearance();
+        if (appearanceDictionary == null)
+        {
+            appearanceDictionary = new PDAppearanceDictionary();
+            annotation.setAppearance(appearanceDictionary);
+        }
+        return appearanceDictionary;
+    }
+
+    /**
+     * Get the annotations normal appearance content stream.
+     * 
+     * <p>
+     * This will get the annotations normal appearance content stream, to 'draw' to. It will be
+     * uncompressed.
+     *
+     * @return the appearance entry representing the normal appearance.
+     * @throws IOException 
+     */
+    PDAppearanceContentStream getNormalAppearanceAsContentStream() throws IOException
+    {
+        return getNormalAppearanceAsContentStream(false);
+    }
+    
+    /**
+     * Get the annotations normal appearance content stream.
+     * 
+     * <p>
+     * This will get the annotations normal appearance content stream, to 'draw' to.
+     * 
+     * @param compress whether the content stream is to be compressed. Set this to true when
+     * creating long content streams.
+     * @return the appearance entry representing the normal appearance.
+     * @throws IOException
+     */
+    PDAppearanceContentStream getNormalAppearanceAsContentStream(boolean compress) throws IOException
+    {
+        PDAppearanceEntry appearanceEntry = getNormalAppearance();
+        return getAppearanceEntryAsContentStream(appearanceEntry, compress);
+    }
+    
+    /**
+     * Get the annotations down appearance.
+     * 
+     * <p>
+     * This will get the annotations down appearance. If this is not existent an
+     * empty appearance entry will be created.
+     * 
+     * @return the appearance entry representing the down appearance.
+     */
+    PDAppearanceEntry getDownAppearance()
+    {
+        PDAppearanceDictionary appearanceDictionary = getAppearance();
+        PDAppearanceEntry downAppearanceEntry = appearanceDictionary.getDownAppearance();
+
+        if (downAppearanceEntry.isSubDictionary())
+        {
+            //TODO replace with "document.getDocument().createCOSStream()" 
+            downAppearanceEntry = new PDAppearanceEntry(new COSStream());
+            appearanceDictionary.setDownAppearance(downAppearanceEntry);
+        }
+
+        return downAppearanceEntry;
+    }
+
+    /**
+     * Get the annotations rollover appearance.
+     * 
+     * <p>
+     * This will get the annotations rollover appearance. If this is not
+     * existent an empty appearance entry will be created.
+     * 
+     * @return the appearance entry representing the rollover appearance.
+     */
+    PDAppearanceEntry getRolloverAppearance()
+    {
+        PDAppearanceDictionary appearanceDictionary = getAppearance();
+        PDAppearanceEntry rolloverAppearanceEntry = appearanceDictionary.getRolloverAppearance();
+
+        if (rolloverAppearanceEntry.isSubDictionary())
+        {
+            //TODO replace with "document.getDocument().createCOSStream()" 
+            rolloverAppearanceEntry = new PDAppearanceEntry(new COSStream());
+            appearanceDictionary.setRolloverAppearance(rolloverAppearanceEntry);
+        }
+
+        return rolloverAppearanceEntry;
+    }
+
+    /**
+     * Get a padded rectangle.
+     * 
+     * <p>Creates a new rectangle with padding applied to each side.
+     * .
+     * @param rectangle the rectangle.
+     * @param padding the padding to apply.
+     * @return the padded rectangle.
+     */
+    PDRectangle getPaddedRectangle(PDRectangle rectangle, float padding)
+    {
+        return new PDRectangle(rectangle.getLowerLeftX() + padding, rectangle.getLowerLeftY() + padding,
+                rectangle.getWidth() - 2 * padding, rectangle.getHeight() - 2 * padding);
+    }
+    
+    /**
+     * Get a rectangle enlarged by the differences.
+     *
+     * <p>
+     * Creates a new rectangle with differences added to each side. If there are no valid
+     * differences, then the original rectangle is returned.
+     *
+     * @param rectangle the rectangle.
+     * @param differences the differences to apply.
+     * @return the padded rectangle.
+     */
+    PDRectangle addRectDifferences(PDRectangle rectangle, float[] differences)
+    {
+        if (differences == null || differences.length != 4)
+        {
+            return rectangle;
+        }
+        
+        return new PDRectangle(rectangle.getLowerLeftX() - differences[0],
+                rectangle.getLowerLeftY() - differences[1],
+                rectangle.getWidth() + differences[0] + differences[2],
+                rectangle.getHeight() + differences[1] + differences[3]);
+    }
+    
+    /**
+     * Get a rectangle with the differences applied to each side.
+     *
+     * <p>
+     * Creates a new rectangle with differences added to each side. If there are no valid
+     * differences, then the original rectangle is returned.
+     *
+     * @param rectangle the rectangle.
+     * @param differences the differences to apply.
+     * @return the padded rectangle.
+     */
+    PDRectangle applyRectDifferences(PDRectangle rectangle, float[] differences)
+    {
+        if (differences == null || differences.length != 4)
+        {
+            return rectangle;
+        }
+        return new PDRectangle(rectangle.getLowerLeftX() + differences[0],
+                rectangle.getLowerLeftY() + differences[1],
+                rectangle.getWidth() - differences[0] - differences[2],
+                rectangle.getHeight() - differences[1] - differences[3]);
+    }
+
+    void setOpacity(PDAppearanceContentStream contentStream, float opacity) throws IOException
+    {
+        if (opacity < 1)
+        {
+            PDExtendedGraphicsState gs = new PDExtendedGraphicsState();
+            gs.setStrokingAlphaConstant(opacity);
+            gs.setNonStrokingAlphaConstant(opacity);
+
+            contentStream.setGraphicsStateParameters(gs);
+        }
+    }
+
+    /**
+     * Draw a line ending style.
+     * 
+     * @param style
+     * @param cs
+     * @param x
+     * @param y
+     * @param width
+     * @param hasStroke
+     * @param hasBackground
+     * @param ending false if left, true if right of an imagined horizontal line (important for
+     * arrows).
+     *
+     * @throws IOException
+     */
+    void drawStyle(String style, final PDAppearanceContentStream cs, float x, float y,
+                   float width, boolean hasStroke, boolean hasBackground, boolean ending) throws IOException
+    {
+        int sign = ending ? -1 : 1;
+
+        if (PDAnnotationLine.LE_OPEN_ARROW.equals(style) || PDAnnotationLine.LE_CLOSED_ARROW.equals(style))
+        {
+            drawArrow(cs, x + sign * width, y, sign * width * 9);
+        }
+        else if (PDAnnotationLine.LE_BUTT.equals(style))
+        {
+            cs.moveTo(x, y - width * 3);
+            cs.lineTo(x, y + width * 3);
+        }
+        else if (PDAnnotationLine.LE_DIAMOND.equals(style))
+        {
+            drawDiamond(cs, x, y, width * 3);
+        }
+        else if (PDAnnotationLine.LE_SQUARE.equals(style))
+        {
+            cs.addRect(x - width * 3, y - width * 3, width * 6, width * 6);
+        }
+        else if (PDAnnotationLine.LE_CIRCLE.equals(style))
+        {
+            drawCircle(cs, x, y, width * 3);
+        }
+        else if (PDAnnotationLine.LE_R_OPEN_ARROW.equals(style) || PDAnnotationLine.LE_R_CLOSED_ARROW.equals(style))
+        {
+            drawArrow(cs, x + (0 - sign) * width, y, (0 - sign) * width * 9);
+        }
+        else if (PDAnnotationLine.LE_SLASH.equals(style))
+        {
+            // the line is 18 x linewidth at an angle of 60°
+            cs.moveTo(x + (float) (Math.cos(Math.toRadians(60)) * width * 9),
+            y + (float) (Math.sin(Math.toRadians(60)) * width * 9));
+            cs.lineTo(x + (float) (Math.cos(Math.toRadians(240)) * width * 9),
+            y + (float) (Math.sin(Math.toRadians(240)) * width * 9));
+        }
+
+
+
+        if (PDAnnotationLine.LE_R_CLOSED_ARROW.equals(style) || 
+            PDAnnotationLine.LE_CLOSED_ARROW.equals(style))
+        {
+            cs.closePath();
+        }
+        cs.drawShape(width, hasStroke, 
+                     // make sure to only paint a background color (/IC value) 
+                     // for interior color styles, even if an /IC value is set.
+                     INTERIOR_COLOR_STYLES.contains(style) ? hasBackground : false);
+    }
+
+    /**
+     * Add the two arms of a horizontal arrow.
+     * 
+     * @param cs Content stream
+     * @param x
+     * @param y
+     * @param len The arm length. Positive goes to the right, negative goes to the left.
+     * 
+     * @throws IOException If the content stream could not be written
+     */
+    void drawArrow(PDAppearanceContentStream cs, float x, float y, float len) throws IOException
+    {
+        // strategy for arrows: angle 30°, arrow arm length = 9 * line width
+        // cos(angle) = x position
+        // sin(angle) = y position
+        // this comes very close to what Adobe is doing
+        cs.moveTo(x + (float) (Math.cos(ARROW_ANGLE) * len), y + (float) (Math.sin(ARROW_ANGLE) * len));
+        cs.lineTo(x, y);
+        cs.lineTo(x + (float) (Math.cos(ARROW_ANGLE) * len), y - (float) (Math.sin(ARROW_ANGLE) * len));
+    }
+
+    /**
+     * Add a square diamond shape (corner on top) to the path.
+     *
+     * @param cs Content stream
+     * @param x
+     * @param y
+     * @param r Radius (to a corner)
+     * 
+     * @throws IOException If the content stream could not be written
+     */
+    void drawDiamond(PDAppearanceContentStream cs, float x, float y, float r) throws IOException
+    {
+        cs.moveTo(x - r, y);
+        cs.lineTo(x, y + r);
+        cs.lineTo(x + r, y);
+        cs.lineTo(x, y - r);
+        cs.closePath();
+    }
+
+    /**
+     * Add a circle shape to the path in clockwise direction.
+     *
+     * @param cs Content stream
+     * @param x
+     * @param y
+     * @param r Radius
+     * 
+     * @throws IOException If the content stream could not be written.
+     */
+    void drawCircle(PDAppearanceContentStream cs, float x, float y, float r) throws IOException
+    {
+        // http://stackoverflow.com/a/2007782/535646
+        float magic = r * 0.551784f;
+        cs.moveTo(x, y + r);
+        cs.curveTo(x + magic, y + r, x + r, y + magic, x + r, y);
+        cs.curveTo(x + r, y - magic, x + magic, y - r, x, y - r);
+        cs.curveTo(x - magic, y - r, x - r, y - magic, x - r, y);
+        cs.curveTo(x - r, y + magic, x - magic, y + r, x, y + r);
+        cs.closePath();
+    }
+
+    /**
+     * Add a circle shape to the path in counterclockwise direction. You'll need this e.g. when
+     * drawing a doughnut shape. See "Nonzero Winding Number Rule" for more information.
+     *
+     * @param cs Content stream
+     * @param x
+     * @param y
+     * @param r Radius
+     *
+     * @throws IOException If the content stream could not be written.
+     */
+    void drawCircle2(PDAppearanceContentStream cs, float x, float y, float r) throws IOException
+    {
+        // http://stackoverflow.com/a/2007782/535646
+        float magic = r * 0.551784f;
+        cs.moveTo(x, y + r);
+        cs.curveTo(x - magic, y + r, x - r, y + magic, x - r, y);
+        cs.curveTo(x - r, y - magic, x - magic, y - r, x, y - r);
+        cs.curveTo(x + magic, y - r, x + r, y - magic, x + r, y);
+        cs.curveTo(x + r, y + magic, x + magic, y + r, x, y + r);
+        cs.closePath();
+    }
+
+    private static Set<String> createShortStyles()
+    {
+        Set<String> shortStyles = new HashSet<String>();
+        shortStyles.add(PDAnnotationLine.LE_OPEN_ARROW);
+        shortStyles.add(PDAnnotationLine.LE_CLOSED_ARROW);
+        shortStyles.add(PDAnnotationLine.LE_SQUARE);
+        shortStyles.add(PDAnnotationLine.LE_CIRCLE);
+        shortStyles.add(PDAnnotationLine.LE_DIAMOND);
+        return Collections.unmodifiableSet(shortStyles);
+    }
+
+    private static Set<String> createInteriorColorStyles()
+    {
+        Set<String> interiorColorStyles = new HashSet<String>();
+        interiorColorStyles.add(PDAnnotationLine.LE_CLOSED_ARROW);
+        interiorColorStyles.add(PDAnnotationLine.LE_CIRCLE);
+        interiorColorStyles.add(PDAnnotationLine.LE_DIAMOND);
+        interiorColorStyles.add(PDAnnotationLine.LE_R_CLOSED_ARROW);
+        interiorColorStyles.add(PDAnnotationLine.LE_SQUARE);
+        return Collections.unmodifiableSet(interiorColorStyles);
+    }
+
+    private static Set<String> createAngledStyles()
+    {
+        Set<String> angledStyles = new HashSet<String>();
+        angledStyles.add(PDAnnotationLine.LE_CLOSED_ARROW);
+        angledStyles.add(PDAnnotationLine.LE_OPEN_ARROW);
+        angledStyles.add(PDAnnotationLine.LE_R_CLOSED_ARROW);
+        angledStyles.add(PDAnnotationLine.LE_R_OPEN_ARROW);
+        angledStyles.add(PDAnnotationLine.LE_BUTT);
+        angledStyles.add(PDAnnotationLine.LE_SLASH);
+        return Collections.unmodifiableSet(angledStyles);
+    }
+
+    /**
+     * Get the annotations normal appearance.
+     * 
+     * <p>
+     * This will get the annotations normal appearance. If this is not existent
+     * an empty appearance entry will be created.
+     * 
+     * @return the appearance entry representing the normal appearance.
+     */
+    private PDAppearanceEntry getNormalAppearance()
+    {
+        PDAppearanceDictionary appearanceDictionary = getAppearance();
+        PDAppearanceEntry normalAppearanceEntry = appearanceDictionary.getNormalAppearance();
+
+        if (normalAppearanceEntry == null || normalAppearanceEntry.isSubDictionary())
+        {
+            //TODO replace with "document.getDocument().createCOSStream()" 
+            normalAppearanceEntry = new PDAppearanceEntry(new COSStream());
+            appearanceDictionary.setNormalAppearance(normalAppearanceEntry);
+        }
+
+        return normalAppearanceEntry;
+    }
+    
+    
+    private PDAppearanceContentStream getAppearanceEntryAsContentStream(
+              PDAppearanceEntry appearanceEntry, boolean compress) throws IOException
+    {
+        PDAppearanceStream appearanceStream = appearanceEntry.getAppearanceStream();
+        setTransformationMatrix(appearanceStream);
+
+        // ensure there are resources
+        PDResources resources = appearanceStream.getResources();
+        if (resources == null)
+        {
+            resources = new PDResources();
+            appearanceStream.setResources(resources);
+        }
+
+        return new PDAppearanceContentStream(appearanceStream, compress);
+    }
+    
+    private void setTransformationMatrix(PDAppearanceStream appearanceStream)
+    {
+        PDRectangle bbox = getRectangle();
+        appearanceStream.setBBox(bbox);
+        AffineTransform transform = AffineTransform.getTranslateInstance(-bbox.getLowerLeftX(),
+                -bbox.getLowerLeftY());
+        appearanceStream.setMatrix(transform);
+    }
+
+    PDRectangle handleBorderBox(PDAnnotationSquareCircle annotation, float lineWidth)
+    {
+        // There are two options. The handling is not part of the PDF specification but
+        // implementation specific to Adobe Reader
+        // - if /RD is set the border box is the /Rect entry inset by the respective
+        //   border difference.
+        // - if /RD is not set the border box is defined by the /Rect entry. The /RD entry will
+        //   be set to be the line width and the /Rect is enlarged by the /RD amount
+        PDRectangle borderBox;
+        float[] rectDifferences = annotation.getRectDifferences();
+        if (rectDifferences.length == 0)
+        {
+            borderBox = getPaddedRectangle(getRectangle(), lineWidth / 2);
+            // the differences rectangle
+            annotation.setRectDifferences(lineWidth / 2);
+            annotation.setRectangle(addRectDifferences(getRectangle(), annotation.getRectDifferences()));
+            // when the normal appearance stream was generated BBox and Matrix have been set to the
+            // values of the original /Rect. As the /Rect was changed that needs to be adjusted too.
+            annotation.getNormalAppearanceStream().setBBox(getRectangle());
+            AffineTransform transform = AffineTransform.getTranslateInstance(-getRectangle().getLowerLeftX(), -getRectangle().getLowerLeftY());
+            annotation.getNormalAppearanceStream().setMatrix(transform);
+        }
+        else
+        {
+            borderBox = applyRectDifferences(getRectangle(), rectDifferences);
+            borderBox = getPaddedRectangle(borderBox, lineWidth / 2);
+        }
+        return borderBox;
+    }
+}

Added: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDAppearanceHandler.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDAppearanceHandler.java?rev=1861089&view=auto
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDAppearanceHandler.java (added)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDAppearanceHandler.java Wed Jun 12 06:08:45 2019
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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;
+
+public interface PDAppearanceHandler
+{
+    void generateAppearanceStreams();
+    
+    void generateNormalAppearance();
+
+    void generateRolloverAppearance();
+
+    void generateDownAppearance();
+}