File indexing completed on 2024-12-22 05:36:57

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Pdf
0017  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0018  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0019  * @version    $Id$
0020  */
0021 
0022 /** Internally used classes */
0023 // require_once 'Zend/Pdf/Element.php';
0024 // require_once 'Zend/Pdf/Element/Array.php';
0025 // require_once 'Zend/Pdf/Element/String/Binary.php';
0026 // require_once 'Zend/Pdf/Element/Boolean.php';
0027 // require_once 'Zend/Pdf/Element/Dictionary.php';
0028 // require_once 'Zend/Pdf/Element/Name.php';
0029 // require_once 'Zend/Pdf/Element/Null.php';
0030 // require_once 'Zend/Pdf/Element/Numeric.php';
0031 // require_once 'Zend/Pdf/Element/String.php';
0032 // require_once 'Zend/Pdf/Resource/Unified.php';
0033 
0034 // require_once 'Zend/Pdf/Canvas/Abstract.php';
0035 
0036 
0037 /**
0038  * PDF Page
0039  *
0040  * @package    Zend_Pdf
0041  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0042  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0043  */
0044 class Zend_Pdf_Page extends Zend_Pdf_Canvas_Abstract
0045 {
0046   /**** Class Constants ****/
0047 
0048 
0049   /* Page Sizes */
0050 
0051     /**
0052      * Size representing an A4 page in portrait (tall) orientation.
0053      */
0054     const SIZE_A4                = '595:842:';
0055 
0056     /**
0057      * Size representing an A4 page in landscape (wide) orientation.
0058      */
0059     const SIZE_A4_LANDSCAPE      = '842:595:';
0060 
0061     /**
0062      * Size representing a US Letter page in portrait (tall) orientation.
0063      */
0064     const SIZE_LETTER            = '612:792:';
0065 
0066     /**
0067      * Size representing a US Letter page in landscape (wide) orientation.
0068      */
0069     const SIZE_LETTER_LANDSCAPE  = '792:612:';
0070 
0071 
0072   /* Shape Drawing */
0073 
0074     /**
0075      * Stroke the path only. Do not fill.
0076      */
0077     const SHAPE_DRAW_STROKE      = 0;
0078 
0079     /**
0080      * Fill the path only. Do not stroke.
0081      */
0082     const SHAPE_DRAW_FILL        = 1;
0083 
0084     /**
0085      * Fill and stroke the path.
0086      */
0087     const SHAPE_DRAW_FILL_AND_STROKE = 2;
0088 
0089 
0090   /* Shape Filling Methods */
0091 
0092     /**
0093      * Fill the path using the non-zero winding rule.
0094      */
0095     const FILL_METHOD_NON_ZERO_WINDING = 0;
0096 
0097     /**
0098      * Fill the path using the even-odd rule.
0099      */
0100     const FILL_METHOD_EVEN_ODD        = 1;
0101 
0102 
0103   /* Line Dash Types */
0104 
0105     /**
0106      * Solid line dash.
0107      */
0108     const LINE_DASHING_SOLID = 0;
0109 
0110 
0111 
0112     /**
0113      * Page dictionary (refers to an inderect Zend_Pdf_Element_Dictionary object).
0114      *
0115      * @var Zend_Pdf_Element_Reference|Zend_Pdf_Element_Object
0116      */
0117     protected $_dictionary;
0118 
0119     /**
0120      * PDF objects factory.
0121      *
0122      * @var Zend_Pdf_ElementFactory_Interface
0123      */
0124     protected $_objFactory = null;
0125 
0126     /**
0127      * Flag which signals, that page is created separately from any PDF document or
0128      * attached to anyone.
0129      *
0130      * @var boolean
0131      */
0132     protected $_attached;
0133 
0134     /**
0135      * Safe Graphics State semafore
0136      *
0137      * If it's false, than we can't be sure Graphics State is restored withing
0138      * context of previous contents stream (ex. drawing coordinate system may be rotated).
0139      * We should encompass existing content with save/restore GS operators
0140      *
0141      * @var boolean
0142      */
0143     protected $_safeGS;
0144 
0145     /**
0146      * Object constructor.
0147      * Constructor signatures:
0148      *
0149      * 1. Load PDF page from a parsed PDF file.
0150      *    Object factory is created by PDF parser.
0151      * ---------------------------------------------------------
0152      * new Zend_Pdf_Page(Zend_Pdf_Element_Dictionary       $pageDict,
0153      *                   Zend_Pdf_ElementFactory_Interface $factory);
0154      * ---------------------------------------------------------
0155      *
0156      * 2. Make a copy of the PDF page.
0157      *    New page is created in the same context as source page. Object factory is shared.
0158      *    Thus it will be attached to the document, but need to be placed into Zend_Pdf::$pages array
0159      *    to be included into output.
0160      * ---------------------------------------------------------
0161      * new Zend_Pdf_Page(Zend_Pdf_Page $page);
0162      * ---------------------------------------------------------
0163      *
0164      * 3. Create new page with a specified pagesize.
0165      *    If $factory is null then it will be created and page must be attached to the document to be
0166      *    included into output.
0167      * ---------------------------------------------------------
0168      * new Zend_Pdf_Page(string $pagesize, Zend_Pdf_ElementFactory_Interface $factory = null);
0169      * ---------------------------------------------------------
0170      *
0171      * 4. Create new page with a specified pagesize (in default user space units).
0172      *    If $factory is null then it will be created and page must be attached to the document to be
0173      *    included into output.
0174      * ---------------------------------------------------------
0175      * new Zend_Pdf_Page(numeric $width, numeric $height, Zend_Pdf_ElementFactory_Interface $factory = null);
0176      * ---------------------------------------------------------
0177      *
0178      *
0179      * @param mixed $param1
0180      * @param mixed $param2
0181      * @param mixed $param3
0182      * @throws Zend_Pdf_Exception
0183      */
0184     public function __construct($param1, $param2 = null, $param3 = null)
0185     {
0186         if (($param1 instanceof Zend_Pdf_Element_Reference ||
0187              $param1 instanceof Zend_Pdf_Element_Object
0188             ) &&
0189             $param2 instanceof Zend_Pdf_ElementFactory_Interface &&
0190             $param3 === null
0191            ) {
0192             switch ($param1->getType()) {
0193                 case Zend_Pdf_Element::TYPE_DICTIONARY:
0194                     $this->_dictionary = $param1;
0195                     $this->_objFactory = $param2;
0196                     $this->_attached   = true;
0197                     $this->_safeGS     = false;
0198                     return;
0199                     break;
0200 
0201                 case Zend_Pdf_Element::TYPE_NULL:
0202                     $this->_objFactory = $param2;
0203                     $pageWidth = $pageHeight = 0;
0204                     break;
0205 
0206                 default:
0207                     // require_once 'Zend/Pdf/Exception.php';
0208                     throw new Zend_Pdf_Exception('Unrecognized object type.');
0209                     break;
0210 
0211             }
0212         } else if ($param1 instanceof Zend_Pdf_Page && $param2 === null && $param3 === null) {
0213             // Duplicate existing page.
0214             // Let already existing content and resources to be shared between pages
0215             // We don't give existing content modification functionality, so we don't need "deep copy"
0216             $this->_objFactory = $param1->_objFactory;
0217             $this->_attached   = &$param1->_attached;
0218             $this->_safeGS     = false;
0219 
0220             $this->_dictionary = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
0221 
0222             foreach ($param1->_dictionary->getKeys() as $key) {
0223                 if ($key == 'Contents') {
0224                     // Clone Contents property
0225 
0226                     $this->_dictionary->Contents = new Zend_Pdf_Element_Array();
0227 
0228                     if ($param1->_dictionary->Contents->getType() != Zend_Pdf_Element::TYPE_ARRAY) {
0229                         // Prepare array of content streams and add existing stream
0230                         $this->_dictionary->Contents->items[] = $param1->_dictionary->Contents;
0231                     } else {
0232                         // Clone array of the content streams
0233                         foreach ($param1->_dictionary->Contents->items as $srcContentStream) {
0234                             $this->_dictionary->Contents->items[] = $srcContentStream;
0235                         }
0236                     }
0237                 } else {
0238                     $this->_dictionary->$key = $param1->_dictionary->$key;
0239                 }
0240             }
0241 
0242             return;
0243         } else if (is_string($param1) &&
0244                    ($param2 === null || $param2 instanceof Zend_Pdf_ElementFactory_Interface) &&
0245                    $param3 === null) {
0246             if ($param2 !== null) {
0247                 $this->_objFactory = $param2;
0248             } else {
0249                 // require_once 'Zend/Pdf/ElementFactory.php';
0250                 $this->_objFactory = Zend_Pdf_ElementFactory::createFactory(1);
0251             }
0252             $this->_attached   = false;
0253             $this->_safeGS     = true; /** New page created. That's users App responsibility to track GS changes */
0254 
0255             switch (strtolower($param1)) {
0256                 case 'a4':
0257                     $param1 = Zend_Pdf_Page::SIZE_A4;
0258                     break;
0259                 case 'a4-landscape':
0260                     $param1 = Zend_Pdf_Page::SIZE_A4_LANDSCAPE;
0261                     break;
0262                 case 'letter':
0263                     $param1 = Zend_Pdf_Page::SIZE_LETTER;
0264                     break;
0265                 case 'letter-landscape':
0266                     $param1 = Zend_Pdf_Page::SIZE_LETTER_LANDSCAPE;
0267                     break;
0268                 default:
0269                     // should be in "x:y" or "x:y:" form
0270             }
0271 
0272             $pageDim = explode(':', $param1);
0273             if(count($pageDim) == 2  ||  count($pageDim) == 3) {
0274                 $pageWidth  = $pageDim[0];
0275                 $pageHeight = $pageDim[1];
0276             } else {
0277                 /**
0278                  * @todo support of user defined pagesize notations, like:
0279                  *       "210x297mm", "595x842", "8.5x11in", "612x792"
0280                  */
0281                 // require_once 'Zend/Pdf/Exception.php';
0282                 throw new Zend_Pdf_Exception('Wrong pagesize notation.');
0283             }
0284             /**
0285              * @todo support of pagesize recalculation to "default user space units"
0286              */
0287 
0288         } else if (is_numeric($param1) && is_numeric($param2) &&
0289                    ($param3 === null || $param3 instanceof Zend_Pdf_ElementFactory_Interface)) {
0290             if ($param3 !== null) {
0291                 $this->_objFactory = $param3;
0292             } else {
0293                 // require_once 'Zend/Pdf/ElementFactory.php';
0294                 $this->_objFactory = Zend_Pdf_ElementFactory::createFactory(1);
0295             }
0296 
0297             $this->_attached = false;
0298             $this->_safeGS   = true; /** New page created. That's users App responsibility to track GS changes */
0299             $pageWidth  = $param1;
0300             $pageHeight = $param2;
0301 
0302         } else {
0303             // require_once 'Zend/Pdf/Exception.php';
0304             throw new Zend_Pdf_Exception('Unrecognized method signature, wrong number of arguments or wrong argument types.');
0305         }
0306 
0307         $this->_dictionary = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
0308         $this->_dictionary->Type         = new Zend_Pdf_Element_Name('Page');
0309         // require_once 'Zend/Pdf.php';
0310         $this->_dictionary->LastModified = new Zend_Pdf_Element_String(Zend_Pdf::pdfDate());
0311         $this->_dictionary->Resources    = new Zend_Pdf_Element_Dictionary();
0312         $this->_dictionary->MediaBox     = new Zend_Pdf_Element_Array();
0313         $this->_dictionary->MediaBox->items[] = new Zend_Pdf_Element_Numeric(0);
0314         $this->_dictionary->MediaBox->items[] = new Zend_Pdf_Element_Numeric(0);
0315         $this->_dictionary->MediaBox->items[] = new Zend_Pdf_Element_Numeric($pageWidth);
0316         $this->_dictionary->MediaBox->items[] = new Zend_Pdf_Element_Numeric($pageHeight);
0317         $this->_dictionary->Contents     = new Zend_Pdf_Element_Array();
0318     }
0319 
0320 
0321     /**
0322      * Attach resource to the canvas
0323      *
0324      * Method returns a name of the resource which can be used
0325      * as a resource reference within drawing instructions stream
0326      * Allowed types: 'ExtGState', 'ColorSpace', 'Pattern', 'Shading',
0327      * 'XObject', 'Font', 'Properties'
0328      *
0329      * @param string $type
0330      * @param Zend_Pdf_Resource $resource
0331      * @return string
0332      */
0333     protected function _attachResource($type, Zend_Pdf_Resource $resource)
0334     {
0335         // Check that Resources dictionary contains appropriate resource set
0336         if ($this->_dictionary->Resources->$type === null) {
0337             $this->_dictionary->Resources->touch();
0338             $this->_dictionary->Resources->$type = new Zend_Pdf_Element_Dictionary();
0339         } else {
0340             $this->_dictionary->Resources->$type->touch();
0341         }
0342 
0343         // Check, that resource is already attached to resource set.
0344         $resObject = $resource->getResource();
0345         foreach ($this->_dictionary->Resources->$type->getKeys() as $ResID) {
0346             if ($this->_dictionary->Resources->$type->$ResID === $resObject) {
0347                 return $ResID;
0348             }
0349         }
0350 
0351         $idCounter = 1;
0352         do {
0353             $newResName = $type[0] . $idCounter++;
0354         } while ($this->_dictionary->Resources->$type->$newResName !== null);
0355 
0356         $this->_dictionary->Resources->$type->$newResName = $resObject;
0357         $this->_objFactory->attach($resource->getFactory());
0358 
0359         return $newResName;
0360     }
0361 
0362     /**
0363      * Add procedureSet to the Page description
0364      *
0365      * @param string $procSetName
0366      */
0367     protected function _addProcSet($procSetName)
0368     {
0369         // Check that Resources dictionary contains ProcSet entry
0370         if ($this->_dictionary->Resources->ProcSet === null) {
0371             $this->_dictionary->Resources->touch();
0372             $this->_dictionary->Resources->ProcSet = new Zend_Pdf_Element_Array();
0373         } else {
0374             $this->_dictionary->Resources->ProcSet->touch();
0375         }
0376 
0377         foreach ($this->_dictionary->Resources->ProcSet->items as $procSetEntry) {
0378             if ($procSetEntry->value == $procSetName) {
0379                 // Procset is already included into a ProcSet array
0380                 return;
0381             }
0382         }
0383 
0384         $this->_dictionary->Resources->ProcSet->items[] = new Zend_Pdf_Element_Name($procSetName);
0385     }
0386 
0387     /**
0388      * Returns dictionaries of used resources.
0389      *
0390      * Used for canvas implementations interoperability
0391      *
0392      * Structure of the returned array:
0393      * array(
0394      *   <resTypeName> => array(
0395      *                      <resName> => <Zend_Pdf_Resource object>,
0396      *                      <resName> => <Zend_Pdf_Resource object>,
0397      *                      <resName> => <Zend_Pdf_Resource object>,
0398      *                      ...
0399      *                    ),
0400      *   <resTypeName> => array(
0401      *                      <resName> => <Zend_Pdf_Resource object>,
0402      *                      <resName> => <Zend_Pdf_Resource object>,
0403      *                      <resName> => <Zend_Pdf_Resource object>,
0404      *                      ...
0405      *                    ),
0406      *   ...
0407      *   'ProcSet' => array()
0408      * )
0409      *
0410      * where ProcSet array is a list of used procedure sets names (strings).
0411      * Allowed procedure set names: 'PDF', 'Text', 'ImageB', 'ImageC', 'ImageI'
0412      *
0413      * @internal
0414      * @return array
0415      */
0416     public function getResources()
0417     {
0418         $resources = array();
0419         $resDictionary = $this->_dictionary->Resources;
0420 
0421         foreach ($resDictionary->getKeys() as $resType) {
0422             $resources[$resType] = array();
0423 
0424             if ($resType == 'ProcSet') {
0425                 foreach ($resDictionary->ProcSet->items as $procSetEntry) {
0426                     $resources[$resType][] = $procSetEntry->value;
0427                 }
0428             } else {
0429                 $resMap = $resDictionary->$resType;
0430 
0431                 foreach ($resMap->getKeys() as $resId) {
0432                     $resources[$resType][$resId] =new Zend_Pdf_Resource_Unified($resMap->$resId);
0433                 }
0434             }
0435         }
0436 
0437         return $resources;
0438     }
0439 
0440     /**
0441      * Get drawing instructions stream
0442      *
0443      * It has to be returned as a PDF stream object to make it reusable.
0444      *
0445      * @internal
0446      * @returns Zend_Pdf_Resource_ContentStream
0447      */
0448     public function getContents()
0449     {
0450         /** @todo implementation */
0451     }
0452 
0453     /**
0454      * Return the height of this page in points.
0455      *
0456      * @return float
0457      */
0458     public function getHeight()
0459     {
0460         return $this->_dictionary->MediaBox->items[3]->value -
0461                $this->_dictionary->MediaBox->items[1]->value;
0462     }
0463 
0464     /**
0465      * Return the width of this page in points.
0466      *
0467      * @return float
0468      */
0469     public function getWidth()
0470     {
0471         return $this->_dictionary->MediaBox->items[2]->value -
0472                $this->_dictionary->MediaBox->items[0]->value;
0473     }
0474 
0475     /**
0476      * Clone page, extract it and dependent objects from the current document,
0477      * so it can be used within other docs.
0478      */
0479     public function __clone()
0480     {
0481         $factory = Zend_Pdf_ElementFactory::createFactory(1);
0482         $processed = array();
0483 
0484         // Clone dictionary object.
0485         // Do it explicitly to prevent sharing page attributes between different
0486         // results of clonePage() operation (other resources are still shared)
0487         $dictionary = new Zend_Pdf_Element_Dictionary();
0488         foreach ($this->_dictionary->getKeys() as $key) {
0489             $dictionary->$key = $this->_dictionary->$key->makeClone($factory->getFactory(),
0490                                                                         $processed,
0491                                                                         Zend_Pdf_Element::CLONE_MODE_SKIP_PAGES);
0492         }
0493 
0494         $this->_dictionary = $factory->newObject($dictionary);
0495         $this->_objFactory     = $factory;
0496         $this->_attached       = false;
0497         $this->_style          = null;
0498         $this->_font           = null;
0499     }
0500 
0501     /**
0502      * Clone page, extract it and dependent objects from the current document,
0503      * so it can be used within other docs.
0504      *
0505      * @internal
0506      * @param Zend_Pdf_ElementFactory_Interface $factory
0507      * @param array $processed
0508      * @return Zend_Pdf_Page
0509      */
0510     public function clonePage($factory, &$processed)
0511     {
0512         // Clone dictionary object.
0513         // Do it explicitly to prevent sharing page attributes between different
0514         // results of clonePage() operation (other resources are still shared)
0515         $dictionary = new Zend_Pdf_Element_Dictionary();
0516         foreach ($this->_dictionary->getKeys() as $key) {
0517             $dictionary->$key = $this->_dictionary->$key->makeClone($factory->getFactory(),
0518                                                                         $processed,
0519                                                                         Zend_Pdf_Element::CLONE_MODE_SKIP_PAGES);
0520         }
0521 
0522         $clonedPage = new Zend_Pdf_Page($factory->newObject($dictionary), $factory);
0523         $clonedPage->_attached = false;
0524 
0525         return $clonedPage;
0526     }
0527 
0528     /**
0529      * Retrive PDF file reference to the page
0530      *
0531      * @internal
0532      * @return Zend_Pdf_Element_Dictionary
0533      */
0534     public function getPageDictionary()
0535     {
0536         return $this->_dictionary;
0537     }
0538 
0539     /**
0540      * Dump current drawing instructions into the content stream.
0541      *
0542      * @todo Don't forget to close all current graphics operations (like path drawing)
0543      *
0544      * @throws Zend_Pdf_Exception
0545      */
0546     public function flush()
0547     {
0548         if ($this->_saveCount != 0) {
0549             // require_once 'Zend/Pdf/Exception.php';
0550             throw new Zend_Pdf_Exception('Saved graphics state is not restored');
0551         }
0552 
0553         if ($this->_contents == '') {
0554             return;
0555         }
0556 
0557         if ($this->_dictionary->Contents->getType() != Zend_Pdf_Element::TYPE_ARRAY) {
0558             /**
0559              * It's a stream object.
0560              * Prepare Contents page attribute for update.
0561              */
0562             $this->_dictionary->touch();
0563 
0564             $currentPageContents = $this->_dictionary->Contents;
0565             $this->_dictionary->Contents = new Zend_Pdf_Element_Array();
0566             $this->_dictionary->Contents->items[] = $currentPageContents;
0567         } else {
0568             $this->_dictionary->Contents->touch();
0569         }
0570 
0571         if ((!$this->_safeGS)  &&  (count($this->_dictionary->Contents->items) != 0)) {
0572             /**
0573              * Page already has some content which is not treated as safe.
0574              *
0575              * Add save/restore GS operators
0576              */
0577             $this->_addProcSet('PDF');
0578 
0579             $newContentsArray = new Zend_Pdf_Element_Array();
0580             $newContentsArray->items[] = $this->_objFactory->newStreamObject(" q\n");
0581             foreach ($this->_dictionary->Contents->items as $contentStream) {
0582                 $newContentsArray->items[] = $contentStream;
0583             }
0584             $newContentsArray->items[] = $this->_objFactory->newStreamObject(" Q\n");
0585 
0586             $this->_dictionary->touch();
0587             $this->_dictionary->Contents = $newContentsArray;
0588 
0589             $this->_safeGS = true;
0590         }
0591 
0592         $this->_dictionary->Contents->items[] =
0593                 $this->_objFactory->newStreamObject($this->_contents);
0594 
0595         $this->_contents = '';
0596     }
0597 
0598     /**
0599      * Prepare page to be rendered into PDF.
0600      *
0601      * @todo Don't forget to close all current graphics operations (like path drawing)
0602      *
0603      * @param Zend_Pdf_ElementFactory_Interface $objFactory
0604      * @throws Zend_Pdf_Exception
0605      */
0606     public function render(Zend_Pdf_ElementFactory_Interface $objFactory)
0607     {
0608         $this->flush();
0609 
0610         if ($objFactory === $this->_objFactory) {
0611             // Page is already attached to the document.
0612             return;
0613         }
0614 
0615         if ($this->_attached) {
0616             // require_once 'Zend/Pdf/Exception.php';
0617             throw new Zend_Pdf_Exception('Page is attached to other documen. Use clone $page to get it context free.');
0618         } else {
0619             $objFactory->attach($this->_objFactory);
0620         }
0621     }
0622 
0623     /**
0624      * Extract resources attached to the page
0625      *
0626      * This method is not intended to be used in userland, but helps to optimize some document wide operations
0627      *
0628      * returns array of Zend_Pdf_Element_Dictionary objects
0629      *
0630      * @internal
0631      * @return array
0632      */
0633     public function extractResources()
0634     {
0635         return $this->_dictionary->Resources;
0636     }
0637 
0638     /**
0639      * Extract fonts attached to the page
0640      *
0641      * returns array of Zend_Pdf_Resource_Font_Extracted objects
0642      *
0643      * @return array
0644      * @throws Zend_Pdf_Exception
0645      */
0646     public function extractFonts()
0647     {
0648         if ($this->_dictionary->Resources->Font === null) {
0649             // Page doesn't have any font attached
0650             // Return empty array
0651             return array();
0652         }
0653 
0654         $fontResources = $this->_dictionary->Resources->Font;
0655 
0656         $fontResourcesUnique = array();
0657         foreach ($fontResources->getKeys() as $fontResourceName) {
0658             $fontDictionary = $fontResources->$fontResourceName;
0659 
0660             if (! ($fontDictionary instanceof Zend_Pdf_Element_Reference  ||
0661                    $fontDictionary instanceof Zend_Pdf_Element_Object) ) {
0662                 // require_once 'Zend/Pdf/Exception.php';
0663                 throw new Zend_Pdf_Exception('Font dictionary has to be an indirect object or object reference.');
0664             }
0665 
0666             $fontResourcesUnique[spl_object_hash($fontDictionary->getObject())] = $fontDictionary;
0667         }
0668 
0669         $fonts = array();
0670         // require_once 'Zend/Pdf/Exception.php';
0671         foreach ($fontResourcesUnique as $resourceId => $fontDictionary) {
0672             try {
0673                 // require_once 'Zend/Pdf/Resource/Font/Extracted.php';
0674                 // Try to extract font
0675                 $extractedFont = new Zend_Pdf_Resource_Font_Extracted($fontDictionary);
0676 
0677                 $fonts[$resourceId] = $extractedFont;
0678             } catch (Zend_Pdf_Exception $e) {
0679                 if ($e->getMessage() != 'Unsupported font type.') {
0680                     throw new Zend_Pdf_Exception($e->getMessage(), $e->getCode(), $e);
0681                 }
0682             }
0683         }
0684 
0685         return $fonts;
0686     }
0687 
0688     /**
0689      * Extract font attached to the page by specific font name
0690      *
0691      * $fontName should be specified in UTF-8 encoding
0692      *
0693      * @return Zend_Pdf_Resource_Font_Extracted|null
0694      * @throws Zend_Pdf_Exception
0695      */
0696     public function extractFont($fontName)
0697     {
0698         if ($this->_dictionary->Resources->Font === null) {
0699             // Page doesn't have any font attached
0700             return null;
0701         }
0702 
0703         $fontResources = $this->_dictionary->Resources->Font;
0704 
0705         $fontResourcesUnique = array();
0706 
0707         // require_once 'Zend/Pdf/Exception.php';
0708         foreach ($fontResources->getKeys() as $fontResourceName) {
0709             $fontDictionary = $fontResources->$fontResourceName;
0710 
0711             if (! ($fontDictionary instanceof Zend_Pdf_Element_Reference  ||
0712                    $fontDictionary instanceof Zend_Pdf_Element_Object) ) {
0713                 // require_once 'Zend/Pdf/Exception.php';
0714                 throw new Zend_Pdf_Exception('Font dictionary has to be an indirect object or object reference.');
0715             }
0716 
0717             $resourceId = spl_object_hash($fontDictionary->getObject());
0718             if (isset($fontResourcesUnique[$resourceId])) {
0719                 continue;
0720             } else {
0721                 // Mark resource as processed
0722                 $fontResourcesUnique[$resourceId] = 1;
0723             }
0724 
0725             if ($fontDictionary->BaseFont->value != $fontName) {
0726                 continue;
0727             }
0728 
0729             try {
0730                 // Try to extract font
0731                 // require_once 'Zend/Pdf/Resource/Font/Extracted.php';
0732                 return new Zend_Pdf_Resource_Font_Extracted($fontDictionary);
0733             } catch (Zend_Pdf_Exception $e) {
0734                 if ($e->getMessage() != 'Unsupported font type.') {
0735                     throw new Zend_Pdf_Exception($e->getMessage(), $e->getCode(), $e);
0736                 }
0737 
0738                 // Continue searhing font with specified name
0739             }
0740         }
0741 
0742         return null;
0743     }
0744 
0745     /**
0746      *
0747      * @param Zend_Pdf_Annotation $annotation
0748      * @return Zend_Pdf_Page
0749      */
0750     public function attachAnnotation(Zend_Pdf_Annotation $annotation)
0751     {
0752         $annotationDictionary = $annotation->getResource();
0753         if (!$annotationDictionary instanceof Zend_Pdf_Element_Object  &&
0754             !$annotationDictionary instanceof Zend_Pdf_Element_Reference) {
0755             $annotationDictionary = $this->_objFactory->newObject($annotationDictionary);
0756         }
0757 
0758         if ($this->_dictionary->Annots === null) {
0759             $this->_dictionary->touch();
0760             $this->_dictionary->Annots = new Zend_Pdf_Element_Array();
0761         } else {
0762             $this->_dictionary->Annots->touch();
0763         }
0764 
0765         $this->_dictionary->Annots->items[] = $annotationDictionary;
0766 
0767         $annotationDictionary->touch();
0768         $annotationDictionary->P = $this->_dictionary;
0769 
0770         return $this;
0771     }
0772 }
0773