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 2015/03/10 12:11:53 UTC

svn commit: r1665472 - in /pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form: AppearanceGeneratorHelper.java AppearancePrimitivesComposer.java AppearanceStyle.java PlainTextFormatter.java

Author: msahyoun
Date: Tue Mar 10 11:11:52 2015
New Revision: 1665472

URL: http://svn.apache.org/r1665472
Log:
PDFBOX-1402, PDFBOX-2333 start factoring out raw commands; enhance box detection 

Added:
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearancePrimitivesComposer.java   (with props)
Modified:
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceStyle.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainTextFormatter.java

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java?rev=1665472&r1=1665471&r2=1665472&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java Tue Mar 10 11:11:52 2015
@@ -19,7 +19,6 @@ package org.apache.pdfbox.pdmodel.intera
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -31,7 +30,6 @@ import org.apache.pdfbox.cos.COSName;
 import org.apache.pdfbox.cos.COSNumber;
 import org.apache.pdfbox.cos.COSStream;
 import org.apache.pdfbox.pdfparser.PDFStreamParser;
-import org.apache.pdfbox.pdfwriter.COSWriter;
 import org.apache.pdfbox.pdfwriter.ContentStreamWriter;
 import org.apache.pdfbox.pdmodel.PDResources;
 import org.apache.pdfbox.pdmodel.common.COSObjectable;
@@ -263,14 +261,27 @@ class AppearanceGeneratorHelper
             PDFont font, List<Object> tokens, PDAppearanceStream appearanceStream)
             throws IOException
     {
-        PrintWriter printWriter = new PrintWriter(output, true);
+        AppearancePrimitivesComposer composer = new AppearancePrimitivesComposer(output);
         float fontSize = 0.0f;
         PDRectangle boundingBox = appearanceStream.getBBox();
         if (boundingBox == null)
         {
             boundingBox = fieldWidget.getRectangle().createRetranslatedRectangle();
         }
-        printWriter.println("BT");
+        
+        // Acrobat calculates the left and right padding dependent on the offset of the border edge
+        // This calculation works for forms having been generated by Acrobat.
+        // The minimum distance is always 1f even if there is no rectangle being drawn around.
+        float lineWidth = getLineWidth(tokens);
+        PDRectangle paddingEdge = applyPadding(boundingBox,Math.max(1f, lineWidth));
+        PDRectangle contentEdge = applyPadding(paddingEdge,Math.max(1f, lineWidth));
+        
+        // add a clipping path to avoid overlapping with the border
+        composer.addRect(paddingEdge);
+        composer.clip();
+        
+        // start the text output
+        composer.beginText();
         if (!defaultAppearanceHandler.getTokens().isEmpty())
         {
             fontSize = calculateFontSize(font, boundingBox, tokens);
@@ -279,16 +290,11 @@ class AppearanceGeneratorHelper
             daWriter.writeTokens(defaultAppearanceHandler.getTokens());
         }
 
-        PDRectangle borderEdge = getSmallestDrawnRectangle(boundingBox, tokens);
-
-        // Acrobat calculates the left and right padding dependent on the offset of the border edge
-        // This calculation works for forms having been generated by Acrobat.
-        // Need to revisit this for forms being generated with other software.
-        float paddingLeft = Math.max(2, Math.round(4 * borderEdge.getLowerLeftX()));
-        float paddingRight = Math.max(2,
-                Math.round(4 * (boundingBox.getUpperRightX() - borderEdge.getUpperRightX())));
+        float paddingLeft = contentEdge.getLowerLeftX();
+        float paddingRight = boundingBox.getUpperRightX() - contentEdge.getUpperRightX();
+        
         float verticalOffset = getVerticalOffset(boundingBox, font, fontSize, tokens);
-
+        
         // Acrobat shifts the value so it aligns to the bottom if
         // the font's caps are larger than the height of the borderEdge
         //
@@ -297,10 +303,10 @@ class AppearanceGeneratorHelper
         // We potentially need to revisit that calculation
         float fontHeight = boundingBox.getHeight() - verticalOffset * 2;
 
-        if (fontHeight + 2 * borderEdge.getLowerLeftX() > borderEdge.getHeight())
+        if (fontHeight + 2 * paddingEdge.getLowerLeftX() > paddingEdge.getHeight())
         {
             verticalOffset = font.getBoundingBox().getHeight() / 1000 * fontSize
-                    - borderEdge.getHeight();
+                    - paddingEdge.getHeight();
         }
 
         float leftOffset = 0f;
@@ -310,7 +316,7 @@ class AppearanceGeneratorHelper
         
         int q = getQ();
         if (q == PDTextField.QUADDING_LEFT
-                || stringWidth > borderEdge.getWidth() - paddingLeft - paddingRight)
+                || stringWidth > contentEdge.getWidth())
         {
             leftOffset = paddingLeft;
         }
@@ -332,34 +338,34 @@ class AppearanceGeneratorHelper
         // show the text
         if (!isMultiLine())
         {
-            printWriter.println(leftOffset + " " + verticalOffset + " Td");
-            printWriter.flush();
-            COSWriter.writeString(font.encode(value), output); 
-            printWriter.println(" Tj");
+            composer.newLineAtOffset(leftOffset, verticalOffset);
+            composer.showText(value, font);
         }
         else
         {
             // adjust offset
             // TODO: offset is dependent on border value if there is one
             verticalOffset = verticalOffset + font.getFontDescriptor().getAscent() / 1000 * fontSize;
-            printWriter.println(leftOffset + " " + verticalOffset + " Td");
+
+            composer.newLineAtOffset(leftOffset, verticalOffset);
+
             PlainText textContent = new PlainText(value);
             AppearanceStyle appearanceStyle = new AppearanceStyle();
             appearanceStyle.setFont(font);
             appearanceStyle.setFontSize(fontSize);
+            
             PlainTextFormatter formatter = new PlainTextFormatter
-                                                .Builder(output)
+                                                .Builder(composer)
                                                     .style(appearanceStyle)
                                                     .text(textContent)
-                                                    .width(borderEdge.getWidth() - paddingLeft - paddingRight)
+                                                    .width(contentEdge.getWidth())
                                                     .wrapLines(true)
                                                     .textAlign(q)
                                                     .build();
             formatter.format();
 
         }
-        printWriter.println("ET");
-        printWriter.flush();
+        composer.endText();
     }
 
     /*
@@ -485,13 +491,13 @@ class AppearanceGeneratorHelper
      */
     private float getLineWidth(List<Object> tokens)
     {
-        float retval = 1;
+        float retval = 0f;
         if (tokens != null)
         {
             int btIndex = tokens.indexOf(Operator.getOperator("BT"));
             int wIndex = tokens.indexOf(Operator.getOperator("w"));
             // the w should only be used if it is before the first BT.
-            if ((wIndex > 0) && (wIndex < btIndex))
+            if ((wIndex > 0) && (wIndex < btIndex || btIndex == -1))
             {
                 retval = ((COSNumber) tokens.get(wIndex - 1)).floatValue();
             }
@@ -499,34 +505,6 @@ class AppearanceGeneratorHelper
         return retval;
     }
 
-    private PDRectangle getSmallestDrawnRectangle(PDRectangle boundingBox, List<Object> tokens)
-    {
-        PDRectangle smallest = boundingBox;
-        for (int i = 0; i < tokens.size(); i++)
-        {
-            Object next = tokens.get(i);
-            if (next == Operator.getOperator("re"))
-            {
-                COSNumber x = (COSNumber) tokens.get(i - 4);
-                COSNumber y = (COSNumber) tokens.get(i - 3);
-                COSNumber width = (COSNumber) tokens.get(i - 2);
-                COSNumber height = (COSNumber) tokens.get(i - 1);
-                PDRectangle potentialSmallest = new PDRectangle();
-                potentialSmallest.setLowerLeftX(x.floatValue());
-                potentialSmallest.setLowerLeftY(y.floatValue());
-                potentialSmallest.setUpperRightX(x.floatValue() + width.floatValue());
-                potentialSmallest.setUpperRightY(y.floatValue() + height.floatValue());
-                if (smallest == null
-                        || smallest.getLowerLeftX() < potentialSmallest.getLowerLeftX()
-                        || smallest.getUpperRightY() > potentialSmallest.getUpperRightY())
-                {
-                    smallest = potentialSmallest;
-                }
-            }
-        }
-        return smallest;
-    }
-
     /**
      * My "not so great" method for calculating the fontsize. It does not work superb, but it
      * handles ok.
@@ -618,4 +596,19 @@ class AppearanceGeneratorHelper
     {
         return boundingBox.getHeight() - 2 * lineWidth;
     }
+    
+    /**
+     * Apply padding to a box.
+     * 
+     * @param original box
+     * @return the padded box.
+     */
+    private PDRectangle applyPadding(PDRectangle box, float padding)
+    {
+        return new PDRectangle(
+                box.getLowerLeftX() +padding, 
+                box.getLowerLeftY() +padding, 
+                box.getWidth()-2*padding, box.getHeight()-2*padding
+                );
+    }
 }

Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearancePrimitivesComposer.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearancePrimitivesComposer.java?rev=1665472&view=auto
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearancePrimitivesComposer.java (added)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearancePrimitivesComposer.java Tue Mar 10 11:11:52 2015
@@ -0,0 +1,184 @@
+/*
+ * 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.form;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.NumberFormat;
+import java.util.Locale;
+
+import org.apache.pdfbox.pdfwriter.COSWriter;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.util.Charsets;
+
+/**
+ * Write the primitives making up a content stream.
+ *
+ * <strong>This class shall be treated internal use only!</strong>
+ */
+class AppearancePrimitivesComposer
+{
+    // the ouput stream to write to
+    private final OutputStream outputstream;
+    
+    // number format for real number output
+    private final NumberFormat formatDecimal = NumberFormat.getNumberInstance(Locale.US);
+    
+    // will be set for operators doing text output
+    private boolean inTextMode = false;
+    
+    AppearancePrimitivesComposer(OutputStream outputstream)
+    {
+        this.outputstream = outputstream;
+    }
+    
+    /**
+     * Add a rectangle to the current path.
+     *
+     * @param rect the rectangle.
+     * @throws IOException If the content stream could not be written.
+     */
+    public void addRect(PDRectangle rect) throws IOException
+    {
+        addRect(rect.getLowerLeftX(), rect.getLowerLeftY(), rect.getWidth(), rect.getHeight());
+    }
+    
+    /**
+     * Add a rectangle to the current path.
+     *
+     * @param x The lower left x coordinate.
+     * @param y The lower left y coordinate.
+     * @param width The width of the rectangle.
+     * @param height The height of the rectangle.
+     * @throws IOException If the content stream could not be written.
+     */
+    public void addRect(float x, float y, float width, float height) throws IOException
+    {
+        if (inTextMode)
+        {
+            throw new IOException("Error: addRect is not allowed within a text block.");
+        }
+        writeOperand(x);
+        writeOperand(y);
+        writeOperand(width);
+        writeOperand(height);
+        writeOperator("re");
+    }
+
+    /**
+     * Begin some text operations.
+     *
+     * @throws IOException If there is an error writing to the stream or if you attempt to
+     *         nest beginText calls.
+     */
+    public void beginText() throws IOException
+    {
+        if (inTextMode)
+        {
+            throw new IOException("Error: Nested beginText() calls are not allowed.");
+        }
+        writeOperator("BT");
+        inTextMode = true;
+    }
+    
+    /**
+     * Intersects the current clipping path with the current path, using the nonzero rule.
+     *
+     * @throws IOException If the content stream could not be written
+     */
+    public void clip() throws IOException
+    {
+        if (inTextMode)
+        {
+            throw new IOException("Error: clip is not allowed within a text block.");
+        }
+        writeOperator("W");
+        writeOperator("n"); // end path without filling or stroking
+    }
+
+    /**
+     * End some text operations.
+     *
+     * @throws IOException If there is an error writing to the stream or if you attempt to
+     *         nest endText calls.
+     */
+    public void endText() throws IOException
+    {
+        if (!inTextMode)
+        {
+            throw new IOException("Error: You must call beginText() before calling endText.");
+        }
+        writeOperator("ET");
+        inTextMode = false;
+    }
+    
+    
+    /**
+     * The Td operator.
+     * Move to the start of the next line, offset from the start of the current line by (tx, ty).
+     *
+     * @param tx The x translation.
+     * @param ty The y translation.
+     * @throws IOException if there is an error writing to the stream.
+     */
+    void newLineAtOffset(float tx, float ty) throws IOException
+    {
+        writeOperand(tx);
+        writeOperand(ty);
+        writeOperator("Td");
+    }
+    
+    /**
+     * Shows the given text at the location specified by the current text matrix.
+     *
+     * @param text The Unicode text to show.
+     * @throws IOException if there is an error writing to the stream.
+     */
+    void showText(String text, PDFont font) throws IOException
+    {
+        COSWriter.writeString(font.encode(text), outputstream);
+        write(" ");
+        writeOperator("Tj");
+    }
+    
+    /**
+     * Writes a string to the content stream as ASCII.
+     */
+    private void write(String text) throws IOException
+    {
+        outputstream.write(text.getBytes(Charsets.US_ASCII));
+    }
+    
+    /**
+     * Writes a string to the content stream as ASCII.
+     */
+    private void writeOperator(String text) throws IOException
+    {
+        outputstream.write(text.getBytes(Charsets.US_ASCII));
+        outputstream.write('\n');
+    }
+    
+    /**
+     * Writes a real real to the content stream.
+     */
+    private void writeOperand(float real) throws IOException
+    {
+        write(formatDecimal.format(real));
+        outputstream.write(' ');
+    }
+}

Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearancePrimitivesComposer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearancePrimitivesComposer.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceStyle.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceStyle.java?rev=1665472&r1=1665471&r2=1665472&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceStyle.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceStyle.java Tue Mar 10 11:11:52 2015
@@ -28,7 +28,7 @@ class AppearanceStyle
     /**
      * The font size to be used for text formatting.
      *
-     * Defaulting to 12 to math Acrobats default.
+     * Defaulting to 12 to match Acrobats default.
      */
     private float fontSize = 12.0f;
     

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainTextFormatter.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainTextFormatter.java?rev=1665472&r1=1665471&r2=1665472&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainTextFormatter.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainTextFormatter.java Tue Mar 10 11:11:52 2015
@@ -17,18 +17,13 @@
 package org.apache.pdfbox.pdmodel.interactive.form;
 
 import java.io.IOException;
-import java.io.OutputStream;
-import java.text.NumberFormat;
 import java.util.List;
-import java.util.Locale;
 
-import org.apache.pdfbox.pdfwriter.COSWriter;
 import org.apache.pdfbox.pdmodel.font.PDFont;
 import org.apache.pdfbox.pdmodel.interactive.form.PlainText.Line;
 import org.apache.pdfbox.pdmodel.interactive.form.PlainText.Paragraph;
 import org.apache.pdfbox.pdmodel.interactive.form.PlainText.TextAttribute;
 import org.apache.pdfbox.pdmodel.interactive.form.PlainText.Word;
-import org.apache.pdfbox.util.Charsets;
 
 /**
  * TextFormatter to handle plain text formatting.
@@ -69,23 +64,19 @@ class PlainTextFormatter
         }
     }
     
-    
     private AppearanceStyle appearanceStyle;
     private final boolean wrapLines;
     private final float width;
-    private final OutputStream outputstream;
+    
+    private final AppearancePrimitivesComposer composer;
     private final PlainText textContent;
     private final TextAlign textAlignment;
     
-    
-    // number format
-    private final NumberFormat formatDecimal = NumberFormat.getNumberInstance(Locale.US);
-    
     static class Builder
     {
 
         // required parameters
-        private OutputStream outputstream;
+        private AppearancePrimitivesComposer composer;
 
         // optional parameters
         private AppearanceStyle appearanceStyle;
@@ -94,9 +85,9 @@ class PlainTextFormatter
         private PlainText textContent;
         private TextAlign textAlignment = TextAlign.LEFT;
         
-        public Builder(OutputStream outputstream)
+        public Builder(AppearancePrimitivesComposer composer)
         {
-            this.outputstream = outputstream;
+            this.composer = composer;
         }
 
         Builder style(AppearanceStyle appearanceStyle)
@@ -147,7 +138,7 @@ class PlainTextFormatter
         appearanceStyle = builder.appearanceStyle;
         wrapLines = builder.wrapLines;
         width = builder.width;
-        outputstream = builder.outputstream;
+        composer = builder.composer;
         textContent = builder.textContent;
         textAlignment = builder.textAlignment;
     }
@@ -174,7 +165,7 @@ class PlainTextFormatter
                 }
                 else
                 {
-                    showText(paragraph.getText(), appearanceStyle.getFont());
+                    composer.showText(paragraph.getText(), appearanceStyle.getFont());
                 }
             }
         }
@@ -219,74 +210,20 @@ class PlainTextFormatter
             }
             
             float offset = -lastPos + startOffset;
-            newLineAtOffset(offset, -appearanceStyle.getLeading());
+            composer.newLineAtOffset(offset, -appearanceStyle.getLeading());
             lastPos = startOffset; 
 
             List<Word> words = line.getWords();
             for (Word word : words)
             {
-                showText(word.getText(), font);
+                composer.showText(word.getText(), font);
                 wordWidth = (Float) word.getAttributes().getIterator().getAttribute(TextAttribute.WIDTH);
                 if (words.indexOf(word) != words.size() -1)
                 {
-                    newLineAtOffset(wordWidth + interWordSpacing, 0f);
+                    composer.newLineAtOffset(wordWidth + interWordSpacing, 0f);
                     lastPos = lastPos + wordWidth + interWordSpacing;
                 }
             }
         }
     }
-    
-    /**
-     * Shows the given text at the location specified by the current text matrix.
-     *
-     * @param text The Unicode text to show.
-     * @throws IOException if there is an error writing to the stream.
-     */
-    private void showText(String text, PDFont font) throws IOException
-    {
-        COSWriter.writeString(font.encode(text), outputstream);
-        write(" ");
-        writeOperator("Tj");
-    }
-    
-    /**
-     * Writes a string to the content stream as ASCII.
-     */
-    private void write(String text) throws IOException
-    {
-        outputstream.write(text.getBytes(Charsets.US_ASCII));
-    }
-    
-    /**
-     * Writes a string to the content stream as ASCII.
-     */
-    private void writeOperator(String text) throws IOException
-    {
-        outputstream.write(text.getBytes(Charsets.US_ASCII));
-        outputstream.write('\n');
-    }
-    
-    /**
-     * The Td operator.
-     * Move to the start of the next line, offset from the start of the current line by (tx, ty).
-     *
-     * @param tx The x translation.
-     * @param ty The y translation.
-     * @throws IOException if there is an error writing to the stream.
-     */
-    public void newLineAtOffset(float tx, float ty) throws IOException
-    {
-        writeOperand(tx);
-        writeOperand(ty);
-        writeOperator("Td");
-    }
-    
-    /**
-     * Writes a real real to the content stream.
-     */
-    private void writeOperand(float real) throws IOException
-    {
-        write(formatDecimal.format(real));
-        outputstream.write(' ');
-    }
 }