Просмотр исходного кода

Merge pull request #98 from stephenorr/patch-1

Allow editing and flattening of text form fields within PDF documents
Frank Brückner 11 лет назад
Родитель
Сommit
a4455d2d12
3 измененных файлов с 207 добавлено и 15 удалено
  1. 86 0
      library/Zend/Pdf.php
  2. BIN
      tests/Zend/Pdf/_files/PdfWithFields.pdf
  3. 121 15
      tests/Zend/PdfTest.php

+ 86 - 0
library/Zend/Pdf.php

@@ -96,6 +96,13 @@ class Zend_Pdf
     const PDF_HEADER  = "%PDF-1.4\n%\xE2\xE3\xCF\xD3\n";
 
     /**
+     * Form field options
+     */
+    const PDF_FORM_FIELD_READONLY = 1;
+    const PDF_FORM_FIELD_REQUIRED = 2;
+    const PDF_FORM_FIELD_NOEXPORT = 4;
+
+    /**
      * Pages collection
      *
      * @todo implement it as a class, which supports ArrayAccess and Iterator interfaces,
@@ -208,6 +215,13 @@ class Zend_Pdf
     protected static $_inheritableAttributes = array('Resources', 'MediaBox', 'CropBox', 'Rotate');
 
     /**
+     * List of form fields
+     *
+     * @var array - Associative array, key: name of form field, value: Zend_Pdf_Element
+     */
+    protected $_formFields = array();
+	
+    /**
      * True if the object is a newly created PDF document (affects save() method behavior)
      * False otherwise
      *
@@ -329,6 +343,7 @@ class Zend_Pdf
             $this->_loadNamedDestinations($this->_trailer->Root, $this->_parser->getPDFVersion());
             $this->_loadOutlines($this->_trailer->Root);
             $this->_loadJavaScript($this->_trailer->Root);
+            $this->_loadFormFields($this->_trailer->Root);
 
             if ($this->_trailer->Info !== null) {
                 $this->properties = $this->_trailer->Info->toPhp();
@@ -598,6 +613,77 @@ class Zend_Pdf
             }
         }
     }
+  
+    /**
+     * Load form fields
+     * Populates the _formFields array, for later lookup of fields by name
+     *
+     * @param Zend_Pdf_Element_Reference $root Document catalog entry
+     */
+    protected function _loadFormFields(Zend_Pdf_Element_Reference $root)
+    {
+        if ($root->AcroForm === null || $root->AcroForm->Fields === null) {
+            return;
+        }
+        
+        foreach ($root->AcroForm->Fields->items as $field)
+        {
+            /* We only support fields that are textfields and have a name */
+            if ( $field->FT && $field->FT->value == 'Tx' && $field->T && $field->T !== null ) 
+            {
+                $this->_formFields[$field->T->value] = $field;
+            }
+        }
+		
+        if ( !$root->AcroForm->NeedAppearances || !$root->AcroForm->NeedAppearances->value )
+        {
+            /* Ask the .pdf viewer to generate its own appearance data, so we do not have to */
+            $root->AcroForm->add(new Zend_Pdf_Element_Name('NeedAppearances'), new Zend_Pdf_Element_Boolean(true) );
+            $root->AcroForm->touch();
+        }    
+    }
+	
+    /**
+     * Retrieves a list with the names of the AcroForm textfields in the PDF
+     *
+     * @return array of strings
+     */
+    public function getTextFieldNames()
+    {
+        return array_keys($this->_formFields);
+    }
+	
+    /**
+     * Sets the value of an AcroForm text field
+     *
+     * @param string $name Name of textfield
+     * @param string $value Value
+     * @throws Zend_Pdf_Exception if the textfield does not exist in the pdf
+     */
+    public function setTextField($name, $value)
+    {
+        if ( !isset($this->_formFields[$name]))
+            throw new Zend_Pdf_Exception("Field '$name' does not exist or is not a textfield");
+		
+        $field = $this->_formFields[$name];
+        $field->add(new Zend_Pdf_Element_Name('V'), new Zend_Pdf_Element_String($value) );
+        $field->touch();      
+    }
+    
+    public function setTextFieldProperties($name, $bitmask)
+    {
+        if ( !isset($this->_formFields[$name]))
+            throw new Zend_Pdf_Exception("Field '$name' does not exist or is not a textfield");
+
+        $field = $this->_formFields[$name];
+        $field->add(new Zend_Pdf_Element_Name('Ff'), new Zend_Pdf_Element_Numeric($bitmask));
+        $field->touch();
+    }
+    
+    public function markTextFieldAsReadOnly($name)
+    {
+        $this->setTextFieldProperties($name, self::PDF_FORM_FIELD_READONLY);
+    }
 
     /**
      * Orginize pages to tha pages tree structure.

BIN
tests/Zend/Pdf/_files/PdfWithFields.pdf


+ 121 - 15
tests/Zend/PdfTest.php

@@ -39,6 +39,111 @@ require_once 'Zend/Pdf/Exception.php';
  */
 class Zend_PdfTest extends PHPUnit_Framework_TestCase
 {
+    /**
+     *
+     * @var NULL|Zend_Pdf
+     */
+    private $_pdf = NULL;
+
+    protected function setUp()
+    {
+        $this->_pdf = Zend_Pdf::load(dirname(__FILE__) . '/Pdf/_files/PdfWithFields.pdf');
+    }
+
+    public function testGetTextFieldNames()
+    {
+        $fieldNames = $this->_pdf->getTextFieldNames();
+        //PDF with text fields must return array of text field names
+        $this->assertEquals(array('Field1', 'Field2'), $fieldNames);
+    }
+
+    public function testGetTextFieldNamesNoFieldsEmptyArray()
+    {
+        $pdf = new Zend_Pdf();
+        $fieldNames = $pdf->getTextFieldNames();
+        //PDF with no text fields must return empty array
+        $this->assertEquals(array(), $fieldNames);
+    }
+
+    public function testSetTextField()
+    {
+        try {
+            $this->_pdf->setTextField('Field1', 'Value1');
+            $this->assertTrue(TRUE);    //in case of --strict
+        } catch (\Exception $e) {
+            $this->fail('Failed to set an existing text field');
+        }
+    }
+
+    /**
+     * Asserts: Setting a non-existent field shouls throw an exception
+     * 
+     * @expectedException Zend_Pdf_Exception
+     * @expectedExceptionMessage Field 'FieldNotExists' does not exist or is not
+     *                           a textfield
+     */
+    public function testSetTextFieldNonExistent()
+    {
+        $this->_pdf->setTextField('FieldNotExists', 'Value1');
+    }
+
+    public function testSetTextFieldProperties()
+    {
+        try {
+            $this->_pdf->setTextFieldProperties(
+                    'Field1', Zend_Pdf::PDF_FORM_FIELD_READONLY
+            );
+            $this->_pdf->setTextFieldProperties(
+                    'Field1', Zend_Pdf::PDF_FORM_FIELD_REQUIRED
+            );
+            $this->_pdf->setTextFieldProperties(
+                    'Field1', Zend_Pdf::PDF_FORM_FIELD_NOEXPORT
+            );
+            $this->_pdf->setTextFieldProperties(
+                    'Field1', Zend_Pdf::PDF_FORM_FIELD_READONLY
+                    | Zend_Pdf::PDF_FORM_FIELD_REQUIRED
+                    | Zend_Pdf::PDF_FORM_FIELD_NOEXPORT
+            );
+            $this->assertTrue(TRUE);    //in case of --strict
+        } catch (\Exception $e) {
+            $this->fail('Failed to set property of an existing text field');
+        }
+    }
+
+    /**
+     * Asserts setting property of non-existent field shouls throw an exception
+     * 
+     * @expectedException Zend_Pdf_Exception
+     * @expectedExceptionMessage Field 'FieldNotExists' does not exist or is not
+     *                           a textfield
+     */
+    public function testSetTextFieldPropertiesNonExistent()
+    {
+        $this->_pdf->setTextFieldProperties('FieldNotExists', Zend_Pdf::PDF_FORM_FIELD_REQUIRED);
+    }
+
+    public function testMarkTextFieldAsReadOnly()
+    {
+        try {
+            $this->_pdf->markTextFieldAsReadOnly('Field1');
+            $this->_pdf->markTextFieldAsReadOnly('Field2');
+            $this->assertTrue(TRUE);    //in case of --strict
+        } catch (\Exception $e) {
+            $this->fail('Failed to set an existing text field as read-only');
+        }
+    }
+
+    /**
+     * Asserts setting property of non-existent field shouls throw an exception
+     * 
+     * @expectedException Zend_Pdf_Exception
+     * @expectedExceptionMessage Field 'FieldNotExists' does not exist or is not
+     *                           a textfield
+     */
+    public function testMarkTextFieldAsReadOnlyNonExistent()
+    {
+        $this->_pdf->markTextFieldAsReadOnly('FieldNotExists');
+    }
 
     public function testGetJavasriptNull()
     {
@@ -93,28 +198,30 @@ class Zend_PdfTest extends PHPUnit_Framework_TestCase
         $this->assertEquals($javaScriptArray, $pdf->getJavaScript());
     }
 
+    /**
+     * Asserts setting empty JavaScript string throws exception
+     * 
+     * @expectedException Zend_Pdf_Exception
+     * @expectedExceptionMessage JavaScript must be a non empty string or array
+     *                           of strings
+     */
     public function testSetJavaScriptEmptyString()
     {
-        // setting empty JavaScript string throws exception
         $pdf = new Zend_Pdf();
-        try {
-            $pdf->setJavaScript('');
-            $this->fail('Expected exception when trying to set empty string.');
-        } catch (Zend_Pdf_Exception $e) {
-            $this->assertContains('JavaScript must be a non empty string or array of strings', $e->getMessage());
-        }
+        $pdf->setJavaScript('');
     }
 
+    /**
+     * Asserts setting empty JavaScript array throws exception
+     * 
+     * @expectedException Zend_Pdf_Exception
+     * @expectedExceptionMessage JavaScript must be a non empty string or array
+     *                           of strings
+     */
     public function testSetJavaScriptEmptyArray()
     {
-        // setting empty JavaScript string throws exception
         $pdf = new Zend_Pdf();
-        try {
-            $pdf->setJavaScript(array());
-            $this->fail('Expected exception when trying to set empty array.');
-        } catch (Zend_Pdf_Exception $e) {
-            $this->assertContains('JavaScript must be a non empty string or array of strings', $e->getMessage());
-        }
+        $pdf->setJavaScript(array());
     }
 
     public function testSetAndSaveLoadAndGetJavaScript()
@@ -155,5 +262,4 @@ class Zend_PdfTest extends PHPUnit_Framework_TestCase
 
         $this->assertNull($pdf->getJavaScript());
     }
-
 }