Browse Source

Zend_Pdf: ZF-5832, ZF-6911, ZF-6914, ZF-6915 related commit.

git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@17181 44c647ce-9c0f-0410-b52a-842ac1e357ba
alexander 16 years ago
parent
commit
b9e87282d4
36 changed files with 1542 additions and 1340 deletions
  1. 207 242
      library/Zend/Pdf.php
  2. 224 228
      library/Zend/Pdf/Action.php
  3. 66 53
      library/Zend/Pdf/Action/GoTo.php
  4. 30 112
      library/Zend/Pdf/Destination.php
  5. 121 0
      library/Zend/Pdf/Destination/Explicit.php
  6. 9 17
      library/Zend/Pdf/Destination/Fit.php
  7. 8 16
      library/Zend/Pdf/Destination/FitBoundingBox.php
  8. 8 16
      library/Zend/Pdf/Destination/FitBoundingBoxHorizontally.php
  9. 8 16
      library/Zend/Pdf/Destination/FitBoundingBoxVertically.php
  10. 8 16
      library/Zend/Pdf/Destination/FitHorizontally.php
  11. 8 16
      library/Zend/Pdf/Destination/FitRectangle.php
  12. 8 16
      library/Zend/Pdf/Destination/FitVertically.php
  13. 97 0
      library/Zend/Pdf/Destination/Named.php
  14. 4 4
      library/Zend/Pdf/Destination/Unknown.php
  15. 32 40
      library/Zend/Pdf/Destination/Zoom.php
  16. 1 1
      library/Zend/Pdf/Element/Numeric.php
  17. 11 1
      library/Zend/Pdf/Element/Object.php
  18. 17 5
      library/Zend/Pdf/Element/Reference.php
  19. 19 4
      library/Zend/Pdf/ElementFactory.php
  20. 9 1
      library/Zend/Pdf/ElementFactory/Interface.php
  21. 12 0
      library/Zend/Pdf/ElementFactory/Proxy.php
  22. 5 5
      library/Zend/Pdf/FileParser/Image.php
  23. 154 0
      library/Zend/Pdf/NameTree.php
  24. 126 120
      library/Zend/Pdf/Outline.php
  25. 0 137
      library/Zend/Pdf/Outline/Container.php
  26. 85 57
      library/Zend/Pdf/Outline/Created.php
  27. 136 113
      library/Zend/Pdf/Outline/Loaded.php
  28. 18 6
      library/Zend/Pdf/Page.php
  29. 1 1
      library/Zend/Pdf/Parser.php
  30. 1 2
      library/Zend/Pdf/PhpArray.php
  31. 45 0
      library/Zend/Pdf/RecursivelyIteratableObjectsContainer.php
  32. 7 5
      library/Zend/Pdf/Target.php
  33. 50 82
      tests/Zend/Pdf/ActionTest.php
  34. 2 2
      tests/Zend/Pdf/DestinationTest.php
  35. 4 5
      tests/Zend/Pdf/NamedDestinationsTest.php
  36. 1 1
      tests/Zend/Pdf/ProcessingTest.php

+ 207 - 242
library/Zend/Pdf.php

@@ -79,14 +79,23 @@ require_once 'Zend/Pdf/Action.php';
 /** Zend_Pdf_Destination */
 require_once 'Zend/Pdf/Destination.php';
 
+/** Zend_Pdf_Destination_Explicit */
+require_once 'Zend/Pdf/Destination/Explicit.php';
+
+/** Zend_Pdf_Destination_Named */
+require_once 'Zend/Pdf/Destination/Named.php';
+
 /** Zend_Pdf_Outline_Created */
 require_once 'Zend/Pdf/Outline/Created.php';
 
 /** Zend_Pdf_Outline_Loaded */
 require_once 'Zend/Pdf/Outline/Loaded.php';
 
-/** Zend_Pdf_Outline_Container */
-require_once 'Zend/Pdf/Outline/Container.php';
+/** Zend_Pdf_RecursivelyIteratableObjectsContainer */
+require_once 'Zend/Pdf/RecursivelyIteratableObjectsContainer.php';
+
+/** Zend_Pdf_NameTree */
+require_once 'Zend/Pdf/NameTree.php';
 
 /** Zend_Pdf_Destination */
 require_once 'Zend/Pdf/Exception.php';
@@ -167,20 +176,12 @@ class Zend_Pdf
     protected $_javaScript = null;
 
     /**
-     * Document named actions
-     * "GoTo..." actions, used to refer document parts
-     * from outside PDF
-     *
-     * @var array   - array of Zend_Pdf_Action objects
-     */
-    protected $_namedActions = array();
-
-    /**
-     * Document named destinations
+     * Document named destinations or "GoTo..." actions, used to refer
+     * document parts from outside PDF
      *
-     * @var array   - array of Zend_Pdf_Destinations objects
+     * @var array   - array of Zend_Pdf_Target objects
      */
-    protected $_namedDestinations = array();
+    protected $_namedTargets = array();
 
     /**
      * Document outlines
@@ -418,17 +419,6 @@ class Zend_Pdf
     }
 
     /**
-     * Destructor
-     * Clean up resources
-     */
-    public function __destruct()
-    {
-        foreach ($this->_namedActions as $action) {
-            $action->clean();
-        }
-    }
-
-    /**
      * Retrive number of revisions.
      *
      * @return integer
@@ -536,40 +526,8 @@ class Zend_Pdf
             // PDF version is 1.2+
             // Look for Destinations structure at Name dictionary
             if ($root->Names !== null  &&  $root->Names->Dests !== null) {
-                $intermediateNodes = array();
-                $leafNodes         = array();
-                if ($root->Names->Dests->Kids !== null) {
-                    $intermediateNodes[] = $root->Names->Dests;
-                } else {
-                    $leafNodes[] = $root->Names->Dests;
-                }
-
-                while (count($intermediateNodes) != 0) {
-                    $newIntermediateNodes = array();
-                    foreach ($intermediateNodes as $node) {
-                        foreach ($node->Kids->items as $childNode) {
-                            if ($childNode->Kids !== null) {
-                                $newIntermediateNodes[] = $childNode;
-                            } else {
-                                $leafNodes[] = $childNode;
-                            }
-                        }
-                    }
-                    $intermediateNodes = $newIntermediateNodes;
-                }
-
-                foreach ($leafNodes as $leafNode) {
-                    $destinationsCount = count($leafNode->items)/2;
-                    for ($count = 0; $count < $destinationsCount; $count++) {
-                        $destinationName = $leafNode->items[$count*2];
-                        $destination     = Zend_Pdf_Target::load($leafNode->items[$count*2 + 1]);
-
-                        if ($destination instanceof Zend_Pdf_Action) {
-                            $this->_namedActions[$destKey]      = $destination;
-                        } else {
-                            $this->_namedDestinations[$destKey] = $destination;
-                        }
-                    }
+                foreach (new Zend_Pdf_NameTree($root->Names->Dests) as $name => $destination) {
+                    $this->_namedTargets[$name] = Zend_Pdf_Target::load($destination);
                 }
             }
         } else {
@@ -582,13 +540,7 @@ class Zend_Pdf
                 }
 
                 foreach ($root->Dests->getKeys() as $destKey) {
-                    $destination = Zend_Pdf_Target::load($root->Dests->$destKey);
-
-                    if ($destination instanceof Zend_Pdf_Action) {
-                        $this->_namedActions[$destKey]      = $destination;
-                    } else {
-                        $this->_namedDestinations[$destKey] = $destination;
-                    }
+                    $this->_namedTargets[$destKey] = Zend_Pdf_Target::load($root->Dests->$destKey);
                 }
             }
         }
@@ -632,7 +584,7 @@ class Zend_Pdf
         $this->_originalOutlines = $this->outlines;
 
         if ($root->Outlines->Count !== null) {
-        	$this->_originalOpenOutlinesCount = $root->Outlines->Count->value;
+            $this->_originalOpenOutlinesCount = $root->Outlines->Count->value;
         }
     }
 
@@ -652,7 +604,6 @@ class Zend_Pdf
         $pagesContainer->touch();
         $pagesContainer->Kids->items->clear();
 
-        $pageDictionaries = array();
         foreach ($this->pages as $page ) {
             $page->render($this->_objFactory);
 
@@ -661,88 +612,50 @@ class Zend_Pdf
             $pageDictionary->Parent = $pagesContainer;
 
             $pagesContainer->Kids->items[] = $pageDictionary;
-
-            // Collect page dictionary references
-            $pageDictionaries[$pageDictionary->toString()] = 1;
         }
 
+        $this->_refreshPagesHash();
+
         $pagesContainer->Count->touch();
         $pagesContainer->Count->value = count($this->pages);
 
-        // Refresh named actions list
-        foreach ($this->_namedActions as $name => $namedAction) {
-            $rootAction = $namedAction;
-
-            // Walk through chained actions
-            foreach ($namedAction->getAllActions() as $chainedAction) {
-                if ($chainedAction instanceof Zend_Pdf_Action_GoTo) {
-                    $destination = $chainedAction->getDestination();
-                    if (!$destination instanceof Zend_Pdf_Destination) {
-                        require_once 'Zend/Pdf/Exception.php';
-                        throw new Zend_Pdf_Exception('PDF named actions (destinations) must refer target as an explicit destination.');
-                    }
-
-                    $target = $destination->getResource()->items[0];
-                    if ($target->getType() == Zend_Pdf_Element::TYPE_NUMERIC) {
-                        if ($target->value > count($this->pages)) {
-                            $rootAction = $chainedAction->extract();
-                        }
-                    } else {
-                        if (!isset($pageDictionaries[$target->toString()])) {
-                            $rootAction = $chainedAction->extract();
-                        }
-                    }
-                }
-            }
-
-            if ($rootAction === null) {
-                unset($this->_namedActions[$name]);
-            } else {
-                $rootAction->rebuildSubtree();
-                $this->_namedActions[$name] = $rootAction;
-            }
-        }
 
         // Refresh named destinations list
-        foreach ($this->_namedDestinations as $name => $destination) {
-            $target = $destination->getResource()->items[0];
-            if ($target->getType() == Zend_Pdf_Element::TYPE_NUMERIC) {
-                if ($target->value > count($this->pages) ) {
-                    unset($this->_namedDestinations[$name]);
+        foreach ($this->_namedTargets as $name => $namedTarget) {
+            if ($namedTarget instanceof Zend_Pdf_Destination_Explicit) {
+                // Named target is an explicit destination
+                if ($this->resolveDestination($namedTarget, false) === null) {
+                    unset($this->_namedTargets[$name]);
                 }
-            } else {
-                if (!isset($pageDictionaries[$target->toString()])) {
-                    unset($this->_namedDestinations[$name]);
+            } else if ($namedTarget instanceof Zend_Pdf_Action) {
+                // Named target is an action
+                if ($this->_cleanUpAction($namedTarget, false) === null) {
+                    // Action is a GoTo action with an unresolved destination
+                    unset($this->_namedTargets[$name]);
                 }
+            } else {
+                require_once 'Zend/Pdf/Exception.php';
+                throw new Zend_Pdf_Exception('Wrong type of named targed (\'' . get_class($namedTarget) . '\').');
             }
         }
 
         // Refresh outlines
-        $iterator = new RecursiveIteratorIterator(new Zend_Pdf_Outline_Container($this->outlines), RecursiveIteratorIterator::SELF_FIRST);
+        $iterator = new RecursiveIteratorIterator(new Zend_Pdf_RecursivelyIteratableObjectsContainer($this->outlines), RecursiveIteratorIterator::SELF_FIRST);
         foreach ($iterator as $outline) {
             $target = $outline->getTarget();
 
             if ($target !== null) {
-                if (is_string($target)) {
-                    if (!isset($this->_namedActions[$target])  &&  !isset($this->_namedDestinations[$target])) {
+                if ($target instanceof Zend_Pdf_Destination) {
+                    // Outline target is a destination
+                    if ($this->resolveDestination($target, false) === null) {
                         $outline->setTarget(null);
                     }
-                } else if ($target instanceof Zend_Pdf_Destination) {
-                    $destinationPage = $target->getResource()->items[0];
-                    if ($destinationPage->getType() == Zend_Pdf_Element::TYPE_DICTIONARY) {
-                        if (!isset($pageDictionaries[$destinationPage->toString()])) {
-                            $outline->setTarget(null);
-                        }
-                    } else if ($destinationPage->getType() == Zend_Pdf_Element::TYPE_NUMERIC) {
-                        if ($destinationPage->value > count($this->pages)) {
-                            $outline->setTarget(null);
-                        }
-                    } else {
-                        require_once 'Zend/Pdf/Exception.php';
-                        throw new Zend_Pdf_Exception('Wrong destination page reference.');
-                    }
                 } else if ($target instanceof Zend_Pdf_Action) {
-                    /** @todo Implementation using iteration */
+                    // Outline target is an action
+                    if ($this->_cleanUpAction($target, false) === null) {
+                        // Action is a GoTo action with an unresolved destination
+                        $outline->setTarget(null);
+                    }
                 } else {
                     require_once 'Zend/Pdf/Exception.php';
                     throw new Zend_Pdf_Exception('Wrong outline target.');
@@ -753,53 +666,15 @@ class Zend_Pdf
         $openAction = $this->getOpenAction();
         if ($openAction !== null) {
             if ($openAction instanceof Zend_Pdf_Action) {
-                $rootAction = $openAction;
-
-                // Walk through chained actions
-                foreach ($openAction->getAllActions() as $chainedAction) {
-                    if ($chainedAction instanceof Zend_Pdf_Action_GoTo) {
-                        $destination = $chainedAction->getDestination();
-                        if (!$destination instanceof Zend_Pdf_Destination) {
-                            // Look for $destination within named destinations
-                            if (!isset($this->_namedActions[$destination])  &&  !isset($this->_namedDestinations[$destination])) {
-                                $rootAction = $chainedAction->extract();
-                            }
-                        } else {
-                            // Destination is Zend_Pdf_Destination object
-                            $target = $destination->getResource()->items[0];
-                            if ($target->getType() == Zend_Pdf_Element::TYPE_NUMERIC) {
-                                if ($target->value > count($this->pages) ) {
-                                    // it's a page number, check if we have enough pages
-                                    $rootAction = $chainedAction->extract();
-                                }
-                            } else {
-                                // which refers some page dictionary object
-                                // (check if it's within a collected dictionaries for current document)
-                                if (!isset($pageDictionaries[$target->toString()])) {
-                                    $rootAction = $chainedAction->extract();
-                                }
-                            }
-                        }
-                    }
-                }
-
-                if ($rootAction !== null) {
-                    $rootAction->rebuildSubtree();
-                    $this->setOpenAction($rootAction);
-                    $rootAction->clean();
-                } else {
+                // OpenAction is an action
+                if ($this->_cleanUpAction($openAction, false) === null) {
+                    // Action is a GoTo action with an unresolved destination
                     $this->setOpenAction(null);
                 }
             } else if ($openAction instanceof Zend_Pdf_Destination) {
-                $target = $openAction->getResource()->items[0];
-                if ($target->getType() == Zend_Pdf_Element::TYPE_NUMERIC) {
-                    if ($target->value > count($this->pages) ) {
-                        $this->setOpenAction(null);
-                    }
-                } else {
-                    if (!isset($pageDictionaries[$target->toString()])) {
-                        $this->setOpenAction(null);
-                    }
+                // OpenAction target is a destination
+                if ($this->resolveDestination($openAction, false) === null) {
+                    $this->setOpenAction(null);
                 }
             } else {
                 require_once 'Zend/Pdf/Exception.php';
@@ -815,12 +690,11 @@ class Zend_Pdf
      */
     protected function _dumpNamedDestinations()
     {
-        $namedDestinations = $this->_namedActions + $this->_namedDestinations;
-        ksort($namedDestinations, SORT_STRING);
+        ksort($this->_namedTargets, SORT_STRING);
 
         $destArray = $this->_objFactory->newObject(new Zend_Pdf_Element_Array());
-        $destArrayItems = $destArray->items;
-        foreach ($namedDestinations as $name => $destination) {
+        $destArrayItems = &$destArray->items;
+        foreach ($this->_namedTargets as $name => $destination) {
             $destArrayItems[] = new Zend_Pdf_Element_String($name);
 
             if ($destination instanceof Zend_Pdf_Target) {
@@ -838,11 +712,11 @@ class Zend_Pdf
 
         if ($root->Names === null) {
             $root->touch();
-            $root->Names = new Zend_Pdf_Element_Dictionary();
+            $root->Names = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
         } else {
             $root->Names->touch();
         }
-        $root->Names = $DestTree;
+        $root->Names->Dests = $DestTree;
     }
 
     /**
@@ -853,13 +727,13 @@ class Zend_Pdf
         $root = $this->_trailer->Root;
 
         if ($root->Outlines === null) {
-        	if (count($this->outlines) == 0) {
+            if (count($this->outlines) == 0) {
                 return;
-        	} else {
-        		$root->Outlines = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
-        		$root->Outlines->Type = new Zend_Pdf_Element_Name('Outlines');
-        		$updateOutlinesNavigation = true;
-        	}
+            } else {
+                $root->Outlines = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
+                $root->Outlines->Type = new Zend_Pdf_Element_Name('Outlines');
+                $updateOutlinesNavigation = true;
+            }
         } else {
             $updateOutlinesNavigation = false;
             if (count($this->_originalOutlines) != count($this->outlines)) {
@@ -1013,6 +887,7 @@ class Zend_Pdf
             $root->OpenAction = null;
         } else if ($openAction instanceof Zend_Pdf_Target) {
             $root->OpenAction = $openAction->getResource();
+            $openAction->dumpAction($this->_objFactory);
         } else {
             require_once 'Zend/Pdf/Exception.php';
             throw new Zend_Pdf_Exception('Open action must be a Zend_Pdf_Target or null.');
@@ -1020,102 +895,188 @@ class Zend_Pdf
     }
 
     /**
-     * Return an associative array containing all the named actions in the PDF.
-     * Named actions (it's always "GoTo" actions) can be used to reference from outside
+     * Return an associative array containing all the named destinations (or GoTo actions) in the PDF.
+     * Named targets can be used to reference from outside
      * the PDF, ex: 'http://www.something.com/mydocument.pdf#MyAction'
      *
      * @return array
      */
-    public function getNamedActions()
+    public function getNamedDestinations()
     {
-        return $this->_namedActions;
+        return $this->_namedTargets;
     }
 
     /**
-     * Return specified named action
+     * Return specified named destination
      *
      * @param string $name
-     * @return Zend_Pdf_Action
+     * @return Zend_Pdf_Destination_Explicit|Zend_Pdf_Action_GoTo
      */
-    public function getNamedAction($name)
+    public function getNamedDestination($name)
     {
-        if (isset($this->_namedActions[$name])) {
-            return $this->_namedActions[$name];
+        if (isset($this->_namedTargets[$name])) {
+            return $this->_namedTargets[$name];
         } else {
             return null;
         }
     }
 
     /**
-     * Set specified named action
+     * Set specified named destination
      *
      * @param string $name
-     * @param Zend_Pdf_Action_GoTo $action
+     * @param Zend_Pdf_Destination_Explicit|Zend_Pdf_Action_GoTo $target
      */
-    public function setNamedAction($name, Zend_Pdf_Action_GoTo $action)
+    public function setNamedDestination($name, $destination = null)
     {
-        if (isset($this->_namedActions[$name])) {
-            $this->_namedActions[$name]->clean();
-        }
-        // Clean corresponding named destination if set
-        unset($this->_namedDestinations[$name]);
-
-        if (!$action->getDestination() instanceof Zend_Pdf_Destination) {
+        if ($destination !== null  &&
+            !$destination instanceof Zend_Pdf_Action_GoTo  &&
+            !$destination instanceof Zend_Pdf_Destination_Explicit) {
             require_once 'Zend/Pdf/Exception.php';
-            throw new Zend_Pdf_Exception('PDF named actions (destinations) must refer target as an explicit destination.');
+            throw new Zend_Pdf_Exception('PDF named destination must refer an explicit destination or a GoTo PDF action.');
         }
 
-        if ($action !== null) {
-            $this->_namedActions[$name] = $action;
+        if ($destination !== null) {
+           $this->_namedTargets[$name] = $destination;
         } else {
-            unset($this->_namedActions[$name]);
+            unset($this->_namedTargets[$name]);
         }
     }
 
     /**
-     * Return an associative array containing all the named destinationss in the PDF.
+     * Pages collection hash:
+     * <page dictionary object hash id> => Zend_Pdf_Page
      *
-     * @return array
+     * @var SplObjectStorage
      */
-    public function getNamedDestinations()
+    protected $_pageReferences = null;
+
+    /**
+     * Pages collection hash:
+     * <page number> => Zend_Pdf_Page
+     *
+     * @var array
+     */
+    protected $_pageNumbers = null;
+
+    /**
+     * Refresh page collection hashes
+     *
+     * @return Zend_Pdf
+     */
+    protected function _refreshPagesHash()
     {
-        return $this->_namedDestinations;
+        $this->_pageReferences = array();
+        $this->_pageNumbers    = array();
+        $count = 1;
+        foreach ($this->pages as $page) {
+            $pageDictionaryHashId = spl_object_hash($page->getPageDictionary()->getObject());
+            $this->_pageReferences[$pageDictionaryHashId] = $page;
+            $this->_pageNumbers[$count++]                 = $page;
+        }
+
+        return $this;
     }
 
     /**
-     * Return specified named destination
+     * Resolve destination.
      *
-     * @param string $name
-     * @return Zend_Pdf_Destination
+     * Returns Zend_Pdf_Page page object or null if destination is not found within PDF document.
+     *
+     * @param Zend_Pdf_Destination $destination  Destination to resolve
+     * @param boolean $refreshPagesHash  Refresh page collection hashes before processing
+     * @return Zend_Pdf_Page|null
+     * @throws Zend_Pdf_Exception
      */
-    public function getNamedDestination($name)
+    public function resolveDestination(Zend_Pdf_Destination $destination, $refreshPageCollectionHashes = true)
     {
-        if (isset($this->_namedDestinations[$name])) {
-            return $this->_namedDestinations[$name];
-        } else {
+        if ($this->_pageReferences === null  ||  $refreshPageCollectionHashes) {
+            $this->_refreshPagesHash();
+        }
+
+        if ($destination instanceof Zend_Pdf_Destination_Named) {
+            if (!isset($this->_namedTargets[$destination->getName()])) {
+                return null;
+            }
+            $destination = $this->getNamedDestination($destination->getName());
+
+            if ($destination instanceof Zend_Pdf_Action) {
+                if (!$destination instanceof Zend_Pdf_Action_GoTo) {
+                    return null;
+                }
+                $destination = $destination->getDestination();
+            }
+
+            if (!$destination instanceof Zend_Pdf_Destination_Explicit) {
+                require_once 'Zend/Pdf/Exception.php';
+                throw new Zend_Pdf_Exception('Named destination target has to be an explicit destination.');
+            }
+        }
+
+        // Named target is an explicit destination
+        $pageElement = $destination->getResource()->items[0];
+
+        if ($pageElement->getType() == Zend_Pdf_Element::TYPE_NUMERIC) {
+            // Page reference is a PDF number
+            if (!isset($this->_pageNumbers[$pageElement->value])) {
+                return null;
+            }
+
+            return $this->_pageNumbers[$pageElement->value];
+        }
+
+        // Page reference is a PDF page dictionary reference
+        $pageDictionaryHashId = spl_object_hash($pageElement->getObject());
+        if (!isset($this->_pageReferences[$pageDictionaryHashId])) {
             return null;
         }
+        return $this->_pageReferences[$pageDictionaryHashId];
     }
 
     /**
-     * Set specified named action
+     * Walk through action and its chained actions tree and remove nodes
+     * if they are GoTo actions with an unresolved target.
      *
-     * @param string $name
-     * @param Zend_Pdf_Destination $destination
+     * Returns null if root node is deleted or updated action overwise.
+     *
+     * @todo Give appropriate name and make method public
+     *
+     * @param $action
+     * @param boolean $refreshPagesHash  Refresh page collection hashes before processing
+     * @return Zend_Pdf_Action|null
      */
-    public function setNamedDestination($name, Zend_Pdf_Destination $destination)
+    protected function _cleanUpAction(Zend_Pdf_Action $action, $refreshPageCollectionHashes = true)
     {
-        // Clean corresponding named action if set
-        if (isset($this->_namedActions[$name])) {
-            $this->_namedActions[$name]->clean();
-            unset($this->_namedActions[$name]);
+        if ($this->_pageReferences === null  ||  $refreshPageCollectionHashes) {
+            $this->_refreshPagesHash();
         }
 
-        if ($destination !== null) {
-           $this->_namedDestinations[$name] = $destination;
-        } else {
-            unset($this->_namedDestinations[$name]);
+        // Named target is an action
+        if ($action instanceof Zend_Pdf_Action_GoTo  &&
+            $this->resolveDestination($action->getDestination(), false) === null) {
+            // Action itself is a GoTo action with an unresolved destination
+            return null;
+        }
+
+        // Walk through child actions
+        $iterator = new RecursiveIteratorIterator($action, RecursiveIteratorIterator::SELF_FIRST);
+
+        $actionsToClean        = array();
+        $deletionCandidateKeys = array();
+        foreach ($iterator as $chainedAction) {
+            if ($chainedAction instanceof Zend_Pdf_Action_GoTo  &&
+                $this->resolveDestination($chainedAction->getDestination(), false) === null) {
+                // Some child action is a GoTo action with an unresolved destination
+                // Mark it as a candidate for deletion
+                $actionsToClean[]        = $iterator->getSubIterator();
+                $deletionCandidateKeys[] = $iterator->getSubIterator()->key();
+            }
         }
+        foreach ($actionsToClean as $id => $action) {
+            unset($action->next[$deletionCandidateKeys[$id]]);
+        }
+
+        return $action;
     }
 
     /**
@@ -1124,6 +1085,7 @@ class Zend_Pdf
      * returns array of Zend_Pdf_Resource_Font_Extracted objects
      *
      * @return array
+     * @throws Zend_Pdf_Exception
      */
     public function extractFonts()
     {
@@ -1143,22 +1105,22 @@ class Zend_Pdf
 
                 if (! ($fontDictionary instanceof Zend_Pdf_Element_Reference  ||
                        $fontDictionary instanceof Zend_Pdf_Element_Object) ) {
-                    // Font dictionary has to be an indirect object or object reference
-                    continue;
+                    require_once 'Zend/Pdf/Exception.php';
+                    throw new Zend_Pdf_Exception('Font dictionary has to be an indirect object or object reference.');
                 }
 
-                $fontResourcesUnique[$fontDictionary->toString($this->_objFactory)] = $fontDictionary;
+                $fontResourcesUnique[spl_object_hash($fontDictionary->getObject())] = $fontDictionary;
             }
         }
 
         $fonts = array();
         require_once 'Zend/Pdf/Exception.php';
-        foreach ($fontResourcesUnique as $resourceReference => $fontDictionary) {
+        foreach ($fontResourcesUnique as $resourceId => $fontDictionary) {
             try {
                 // Try to extract font
                 $extractedFont = new Zend_Pdf_Resource_Font_Extracted($fontDictionary);
 
-                $fonts[$resourceReference] = $extractedFont;
+                $fonts[$resourceId] = $extractedFont;
             } catch (Zend_Pdf_Exception $e) {
                 if ($e->getMessage() != 'Unsupported font type.') {
                     throw $e;
@@ -1175,6 +1137,7 @@ class Zend_Pdf
      * $fontName should be specified in UTF-8 encoding
      *
      * @return Zend_Pdf_Resource_Font_Extracted|null
+     * @throws Zend_Pdf_Exception
      */
     public function extractFont($fontName)
     {
@@ -1195,16 +1158,16 @@ class Zend_Pdf
 
                 if (! ($fontDictionary instanceof Zend_Pdf_Element_Reference  ||
                        $fontDictionary instanceof Zend_Pdf_Element_Object) ) {
-                    // Font dictionary has to be an indirect object or object reference
-                    continue;
+                    require_once 'Zend/Pdf/Exception.php';
+                    throw new Zend_Pdf_Exception('Font dictionary has to be an indirect object or object reference.');
                 }
 
-                $resourceReference = $fontDictionary->toString($this->_objFactory);
-                if (isset($fontResourcesUnique[$resourceReference])) {
+                $resourceId = spl_object_hash($fontDictionary->getObject());
+                if (isset($fontResourcesUnique[$resourceId])) {
                     continue;
                 } else {
                     // Mark resource as processed
-                    $fontResourcesUnique[$resourceReference] = 1;
+                    $fontResourcesUnique[$resourceId] = 1;
                 }
 
                 if ($fontDictionary->BaseFont->value != $fontName) {
@@ -1407,6 +1370,8 @@ class Zend_Pdf
                  . "startxref\n" . $offset . "\n"
                  . "%%EOF\n";
 
+        $this->_objFactory->cleanEnumerationShiftCache();
+
         if ($outputStream === null) {
             $pdfSegmentBlocks[] = $pdfBlock;
 

+ 224 - 228
library/Zend/Pdf/Action.php

@@ -35,85 +35,81 @@ require_once 'Zend/Pdf/Target.php';
  * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  * @license    http://framework.zend.com/license/new-bsd     New BSD License
  */
-abstract class Zend_Pdf_Action extends Zend_Pdf_Target
+abstract class Zend_Pdf_Action extends Zend_Pdf_Target implements RecursiveIterator, Countable
 {
-	/**
-	 * Action dictionary
-	 *
-	 * @var Zend_Pdf_Element_Dictionary|Zend_Pdf_Element_Object|Zend_Pdf_Element_Reference
-	 */
-	protected $_actionDictionary;
-
-	/**
-	 * A list of next actions in actions tree (used for actions chaining)
-	 *
-	 * @var SplObjectStorage  Contains Zend_Pdf_Action objects
-	 */
-	protected $_next;
-
-	/**
-	 * Parent object in Actions tree (or null if it's a root object)
-	 *
-	 * @var Zend_Pdf_Action
-	 */
-	protected $_parent;
-
-	/**
+    /**
+     * Action dictionary
+     *
+     * @var Zend_Pdf_Element_Dictionary|Zend_Pdf_Element_Object|Zend_Pdf_Element_Reference
+     */
+    protected $_actionDictionary;
+
+
+    /**
+     * An original list of chained actions
+     *
+     * @var array  Array of Zend_Pdf_Action objects
+     */
+    protected $_originalNextList;
+
+    /**
+     * A list of next actions in actions tree (used for actions chaining)
+     *
+     * @var array  Array of Zend_Pdf_Action objects
+     */
+    public $next = array();
+
+    /**
      * Object constructor
      *
      * @param Zend_Pdf_Element_Dictionary $dictionary
-     * @param Zend_Pdf_Action|null        $parentAction
      * @param SplObjectStorage            $processedActions  list of already processed action dictionaries, used to avoid cyclic references
      * @throws Zend_Pdf_Exception
-	 */
-	public function __construct(Zend_Pdf_Element $dictionary, $parentAction, SplObjectStorage $processedActions)
-	{
+     */
+    public function __construct(Zend_Pdf_Element $dictionary, SplObjectStorage $processedActions)
+    {
         if ($dictionary->getType() != Zend_Pdf_Element::TYPE_DICTIONARY) {
             require_once 'Zend/Pdf/Exception.php';
             throw new Zend_Pdf_Exception('$dictionary mast be a direct or an indirect dictionary object.');
         }
-        if ($parentAction !== null  &&  !$parentAction instanceof Zend_Pdf_Action) {
-            require_once 'Zend/Pdf/Exception.php';
-            throw new Zend_Pdf_Exception('Zend_Pdf_Action constructor $parentAction parameter must be a Zend_Pdf_Action object.');
-        }
 
         $this->_actionDictionary = $dictionary;
-		$this->_parent           = $parentAction;
-        $this->_next             = new SplObjectStorage();
-
-		if ($dictionary->Next !== null) {
-			if ($dictionary->Next instanceof Zend_Pdf_Element_Dictionary) {
-				// Check if dictionary object is not already processed
-				if (!$processedActions->contains($dictionary->Next)) {
-					$processedActions->attach($dictionary->Next);
-					$this->_next->attach(Zend_Pdf_Action::load($dictionary->Next, $this, $processedActions));
-				}
-			} else if ($dictionary->Next instanceof Zend_Pdf_Element_Array) {
-				foreach ($dictionary->Next->items as $chainedActionDictionary) {
-					// Check if dictionary object is not already processed
-					if (!$processedActions->contains($chainedActionDictionary)) {
+
+        if ($dictionary->Next !== null) {
+            if ($dictionary->Next instanceof Zend_Pdf_Element_Dictionary) {
+                // Check if dictionary object is not already processed
+                if (!$processedActions->contains($dictionary->Next)) {
+                    $processedActions->attach($dictionary->Next);
+                    $this->next[] = Zend_Pdf_Action::load($dictionary->Next, $processedActions);
+                }
+            } else if ($dictionary->Next instanceof Zend_Pdf_Element_Array) {
+                foreach ($dictionary->Next->items as $chainedActionDictionary) {
+                    // Check if dictionary object is not already processed
+                    if (!$processedActions->contains($chainedActionDictionary)) {
                         $processedActions->attach($chainedActionDictionary);
-                        $this->_next->attach(Zend_Pdf_Action::load($chainedActionDictionary, $this, $processedActions));
-					}
-				}
-			} else {
+                        $this->next[] = Zend_Pdf_Action::load($chainedActionDictionary, $processedActions);
+                    }
+                }
+            } else {
                 require_once 'Zend/Pdf/Exception.php';
                 throw new Zend_Pdf_Exception('PDF Action dictionary Next entry must be a dictionary or an array.');
-			}
-		}
-	}
+            }
+        }
+
+        $this->_originalNextList = $this->next;
+    }
 
-	/**
-	 * Load PDF action object using specified dictionary
-	 *
+    /**
+     * Load PDF action object using specified dictionary
+     *
+     * @internal
      * @param Zend_Pdf_Element $dictionary (It's actually Dictionary or Dictionary Object or Reference to a Dictionary Object)
-     * @param Zend_Pdf_Action  $parentAction
      * @param SplObjectStorage $processedActions  list of already processed action dictionaries, used to avoid cyclic references
-	 * @return Zend_Pdf_Action
-	 * @throws Zend_Pdf_Exception
-	 */
-	public static function load(Zend_Pdf_Element $dictionary, $parentAction = null, SplObjectStorage $processedActions = null)
-	{
+     * @return Zend_Pdf_Action
+     * @throws Zend_Pdf_Exception
+     */
+    public static function load(Zend_Pdf_Element $dictionary, SplObjectStorage $processedActions = null)
+    {
         if ($processedActions === null) {
             $processedActions = new SplObjectStorage();
         }
@@ -128,275 +124,275 @@ abstract class Zend_Pdf_Action extends Zend_Pdf_Target
         }
 
         if ($dictionary->S === null) {
-			require_once 'Zend/Pdf/Exception.php';
-			throw new Zend_Pdf_Exception('Action dictionary must have S entry');
-		}
+            require_once 'Zend/Pdf/Exception.php';
+            throw new Zend_Pdf_Exception('Action dictionary must contain S entry');
+        }
 
-		switch ($dictionary->S->value) {
-			case 'GoTo':
-				require_once 'Zend/Pdf/Action/GoTo.php';
-				return new Zend_Pdf_Action_GoTo($dictionary, $parentAction, $processedActions);
-				brake;
+        switch ($dictionary->S->value) {
+            case 'GoTo':
+                require_once 'Zend/Pdf/Action/GoTo.php';
+                return new Zend_Pdf_Action_GoTo($dictionary, $processedActions);
+                brake;
 
             case 'GoToR':
                 require_once 'Zend/Pdf/Action/GoToR.php';
-                return new Zend_Pdf_Action_GoToR($dictionary, $parentAction, $processedActions);
+                return new Zend_Pdf_Action_GoToR($dictionary, $processedActions);
                 brake;
 
             case 'GoToE':
                 require_once 'Zend/Pdf/Action/GoToE.php';
-                return new Zend_Pdf_Action_GoToE($dictionary, $parentAction, $processedActions);
+                return new Zend_Pdf_Action_GoToE($dictionary, $processedActions);
                 brake;
 
             case 'Launch':
                 require_once 'Zend/Pdf/Action/Launch.php';
-                return new Zend_Pdf_Action_Launch($dictionary, $parentAction, $processedActions);
+                return new Zend_Pdf_Action_Launch($dictionary, $processedActions);
                 brake;
 
             case 'Thread':
                 require_once 'Zend/Pdf/Action/Thread.php';
-                return new Zend_Pdf_Action_Thread($dictionary, $parentAction, $processedActions);
+                return new Zend_Pdf_Action_Thread($dictionary, $processedActions);
                 brake;
 
             case 'URI':
                 require_once 'Zend/Pdf/Action/URI.php';
-                return new Zend_Pdf_Action_URI($dictionary, $parentAction, $processedActions);
+                return new Zend_Pdf_Action_URI($dictionary, $processedActions);
                 brake;
 
             case 'Sound':
                 require_once 'Zend/Pdf/Action/Sound.php';
-                return new Zend_Pdf_Action_Sound($dictionary, $parentAction, $processedActions);
+                return new Zend_Pdf_Action_Sound($dictionary, $processedActions);
                 brake;
 
             case 'Movie':
                 require_once 'Zend/Pdf/Action/Movie.php';
-                return new Zend_Pdf_Action_Movie($dictionary, $parentAction, $processedActions);
+                return new Zend_Pdf_Action_Movie($dictionary, $processedActions);
                 brake;
 
             case 'Hide':
                 require_once 'Zend/Pdf/Action/Hide.php';
-                return new Zend_Pdf_Action_Hide($dictionary, $parentAction, $processedActions);
+                return new Zend_Pdf_Action_Hide($dictionary, $processedActions);
                 brake;
 
             case 'Named':
                 require_once 'Zend/Pdf/Action/Named.php';
-                return new Zend_Pdf_Action_Named($dictionary, $parentAction, $processedActions);
+                return new Zend_Pdf_Action_Named($dictionary, $processedActions);
                 brake;
 
             case 'SubmitForm':
                 require_once 'Zend/Pdf/Action/SubmitForm.php';
-                return new Zend_Pdf_Action_SubmitForm($dictionary, $parentAction, $processedActions);
+                return new Zend_Pdf_Action_SubmitForm($dictionary, $processedActions);
                 brake;
 
             case 'ResetForm':
                 require_once 'Zend/Pdf/Action/ResetForm.php';
-                return new Zend_Pdf_Action_ResetForm($dictionary, $parentAction, $processedActions);
+                return new Zend_Pdf_Action_ResetForm($dictionary, $processedActions);
                 brake;
 
             case 'ImportData':
                 require_once 'Zend/Pdf/Action/ImportData.php';
-                return new Zend_Pdf_Action_ImportData($dictionary, $parentAction, $processedActions);
+                return new Zend_Pdf_Action_ImportData($dictionary, $processedActions);
                 brake;
 
             case 'JavaScript':
                 require_once 'Zend/Pdf/Action/JavaScript.php';
-                return new Zend_Pdf_Action_JavaScript($dictionary, $parentAction, $processedActions);
+                return new Zend_Pdf_Action_JavaScript($dictionary, $processedActions);
                 brake;
 
             case 'SetOCGState':
                 require_once 'Zend/Pdf/Action/SetOCGState.php';
-                return new Zend_Pdf_Action_SetOCGState($dictionary, $parentAction, $processedActions);
+                return new Zend_Pdf_Action_SetOCGState($dictionary, $processedActions);
                 brake;
 
             case 'Rendition':
                 require_once 'Zend/Pdf/Action/Rendition.php';
-                return new Zend_Pdf_Action_Rendition($dictionary, $parentAction, $processedActions);
+                return new Zend_Pdf_Action_Rendition($dictionary, $processedActions);
                 brake;
 
             case 'Trans':
                 require_once 'Zend/Pdf/Action/Trans.php';
-                return new Zend_Pdf_Action_Trans($dictionary, $parentAction, $processedActions);
+                return new Zend_Pdf_Action_Trans($dictionary, $processedActions);
                 brake;
 
             case 'GoTo3DView':
                 require_once 'Zend/Pdf/Action/GoTo3DView.php';
-                return new Zend_Pdf_Action_GoTo3DView($dictionary, $parentAction, $processedActions);
+                return new Zend_Pdf_Action_GoTo3DView($dictionary, $processedActions);
                 brake;
 
             default:
                 require_once 'Zend/Pdf/Action/Unknown.php';
-                return new Zend_Pdf_Action_Unknown($dictionary, $parentAction, $processedActions);
+                return new Zend_Pdf_Action_Unknown($dictionary, $processedActions);
                 brake;
-		}
-	}
-
-	/**
-	 * Extract action from the chain
-	 *
-	 * Returns root of the updated actions tree
-	 *
-	 * @return Zend_Pdf_Action
-	 */
-	public function extract()
-	{
-		if (($parent = $this->_parent) !== null) {
-			$parent->_next->detach($this);
-
-            foreach ($this->_next as $chainedAction) {
-            	$parent->_next->attach($chainedAction);
-            	$chainedAction->_parent = $parent;
+        }
+    }
+
+    /**
+     * Get resource
+     *
+     * @internal
+     * @return Zend_Pdf_Element
+     */
+    public function getResource()
+    {
+        return $this->_actionDictionary;
+    }
+
+    /**
+     * Dump Action and its child actions into PDF structures
+     *
+     * Returns dictionary indirect object or reference
+     *
+     * @internal
+     * @param Zend_Pdf_ElementFactory $factory   Object factory for newly created indirect objects
+     * @param SplObjectStorage $processedActions  list of already processed actions (used to prevent infinity loop caused by cyclic references)
+     * @return Zend_Pdf_Element_Object|Zend_Pdf_Element_Reference   Dictionary indirect object
+     */
+    public function dumpAction(Zend_Pdf_ElementFactory_Interface $factory, SplObjectStorage $processedActions = null)
+    {
+        if ($processedActions === null) {
+            $processedActions = new SplObjectStorage();
+        }
+        if ($processedActions->contains($this)) {
+            require_once 'Zend/Pdf/Exception.php';
+            throw new Zend_Pdf_Exception('Action chain cyclyc reference is detected.');
+        }
+        $processedActions->attach($this);
+
+        $childListUpdated = false;
+        if (count($this->_originalNextList) != count($this->next)) {
+            // If original and current children arrays have different size then children list was updated
+            $childListUpdated = true;
+        } else if ( !(array_keys($this->_originalNextList) === array_keys($this->next)) ) {
+            // If original and current children arrays have different keys (with a glance to an order) then children list was updated
+            $childListUpdated = true;
+        } else {
+            foreach ($this->next as $key => $childAction) {
+                if ($this->_originalNextList[$key] !== $childAction) {
+                    $childListUpdated = true;
+                    break;
+                }
+            }
+        }
+
+        if ($childListUpdated) {
+            $this->_actionDictionary->touch();
+            switch (count($this->next)) {
+                case 0:
+                    $this->_actionDictionary->Next = null;
+                    break;
+
+                case 1:
+                    $child = reset($this->next);
+                    $this->_actionDictionary->Next = $child->dumpAction($factory, $processedActions);
+                    break;
+
+                default:
+                    $pdfChildArray = new Zend_Pdf_Element_Array();
+                    foreach ($this->next as $child) {
+
+                        $pdfChildArray->items[] = $child->dumpAction($factory, $processedActions);
+                    }
+                    $this->_actionDictionary->Next = $pdfChildArray;
+                    break;
+            }
+        } else {
+            foreach ($this->next as $child) {
+                $child->dumpAction($factory, $processedActions);
             }
+        }
+
+        if ($this->_actionDictionary instanceof Zend_Pdf_Element_Dictionary) {
+            // It's a newly created action. Register it within object factory and return indirect object
+            return $factory->newObject($this->_actionDictionary);
+        } else {
+            // It's a loaded object
+            return $this->_actionDictionary;
+        }
+    }
+
+
+    ////////////////////////////////////////////////////////////////////////
+    //  RecursiveIterator interface methods
+    //////////////
 
-            $this->_parent = null;
-            $this->_next   = new SplObjectStorage();
-
-            return $parent->getRoot();
-		} else {
-			// This is a root node. Treat first subaction as a new root
-
-			if ($this->_next->count() == 0) {
-				// There is no any action in a tree now
-				return null;
-			}
-
-			$this->_next->rewind();
-			$root = $this->_next->current();
-			$this->_next->detach($root);
-
-			$root->_parent = null;
-
-			foreach ($this->_next as $chainedAction) {
-				$root->_next->attach($chainedAction);
-				$chainedAction->_parent = $root;
-			}
-
-            $this->_next = new SplObjectStorage();
-
-			return $root;
-		}
-	}
-
-	/**
-	 * Destroy actions subtree
-	 *
-	 * Method has to be used to clean up resources after actions tree usage
-	 * since PHP doesn't do it automatically for objects with cyclic references
-	 */
-	public function clean()
-	{
-		$this->_parent = null;
-
-		foreach ($this->_next as $chainedAction) {
-			$chainedAction->clean();
-		}
-
-		$this->_next = new SplObjectStorage();
-	}
-
-	/**
-	 * Get root of actions tree
-	 *
-	 * @return Zend_Pdf_Action
-	 */
-	public function getRoot()
-	{
-		$root = $this;
-		while ($root->_parent !== null) {
-			$root = $root->_parent;
-		}
-		return $root;
-	}
-
-	/**
-	 * Return all subactions including this one
-	 *
-	 * @return SplObjectStorage
-	 */
-	public function getAllActions()
-	{
-		$actions = new SplObjectStorage();
-
-		$actions->attach($this);
-
-		foreach ($this->_next as $chainedAction) {
-			/** @todo Change  to $actions->addAll($subAction->allActions()) when PHP 5.3.0+ is required for ZF */
-			foreach ($chainedAction->getAllActions() as $action) {
-				$actions->attach($action);
-			}
-		}
-
-		return $actions;
-	}
+    /**
+     * Returns current child action.
+     *
+     * @return Zend_Pdf_Action
+     */
+    public function current()
+    {
+        return current($this->next);
+    }
 
     /**
-     * Get handler
+     * Returns current iterator key
      *
-     * @param string $property
-     * @return Zend_Pdf_Element | null
+     * @return integer
+     */
+    public function key()
+    {
+        return key($this->next);
+    }
+
+    /**
+     * Go to next child
+     */
+    public function next()
+    {
+        return next($this->next);
+    }
+
+    /**
+     * Rewind children
      */
-    public function __get($property)
+    public function rewind()
     {
-        return $this->_actionDictionary->$property;
+        return reset($this->next);
     }
 
     /**
-     * Set handler
+     * Check if current position is valid
      *
-     * @param string $property
-     * @param  mixed $value
+     * @return boolean
      */
-    public function __set($item, $value)
+    public function valid()
     {
-        $this->_actionDictionary->$property = $value;
+        return current($this->next) !== false;
     }
 
     /**
-     * Attach chained action
+     * Returns the child action.
      *
-     * @param Zend_Pdf_Action $action
+     * @return Zend_Pdf_Outline|null
      */
-    public function attach(Zend_Pdf_Action $action)
+    public function getChildren()
     {
-    	$this->_next->attach($action);
-    	$action->_parent = $this;
+        return current($this->next);
     }
 
     /**
-     * Rebuild PDF dictionaries corresponding to the current tree structure
+     * Implements RecursiveIterator interface.
+     *
+     * @return bool  whether container has any pages
      */
-    public function rebuildSubtree()
+    public function hasChildren()
     {
-    	switch (count($this->_next)) {
-    		case 0:
-    			$this->_actionDictionary->Next = null;
-    			break;
-
-    		case 1:
-    			$this->_next->rewind();
-    			$chainedAction = $this->_next->current();
-                $chainedAction->rebuildSubtree();
-    			$this->_actionDictionary->Next = $chainedAction->_actionDictionary;
-    			break;
-
-    		default:
-    			$nextArray = new Zend_Pdf_Element_Array();
-    			foreach ($this->_next as $chainedAction) {
-                    $chainedAction->rebuildSubtree();
-    				$nextArray->items[] = $chainedAction->_actionDictionary;
-    			}
-    			$this->_actionDictionary->Next = $nextArray;
-    			break;
-    	}
+        return count($this->next) > 0;
     }
 
+
+    ////////////////////////////////////////////////////////////////////////
+    //  Countable interface methods
+    //////////////
+
     /**
-     * Get resource
+     * count()
      *
-     * @internal
-     * @return Zend_Pdf_Element
+     * @return int
      */
-    public function getResource()
+    public function count()
     {
-    	return $this->_actionDictionary;
+        return count($this->childOutlines);
     }
 }

+ 66 - 53
library/Zend/Pdf/Action/GoTo.php

@@ -37,65 +37,78 @@ require_once 'Zend/Pdf/Destination.php';
  */
 class Zend_Pdf_Action_GoTo extends Zend_Pdf_Action
 {
-	/**
-	 * Create new Zend_Pdf_Action_GoTo object using specified destination
-	 *
-	 * Object is created based on a provided dictionary. That allows to choose if it's
-	 * a direct or inderect object attached to some objects factory
-	 *
-	 * @param Zend_Pdf_Element|Zend_Pdf_Destination|string $destination
-	 * @param Zend_Pdf_ElementFactory $objectFactory
-	 * @return Zend_Pdf_Action_GoTo
-	 * @throws Zend_Pdf_Exception
-	 */
-	public static function create($destination, $objectFactory = null)
+    /**
+     * GoTo Action destination
+     *
+     * @var Zend_Pdf_Destination
+     */
+    protected $_destination;
+
+
+    /**
+     * Object constructor
+     *
+     * @param Zend_Pdf_Element_Dictionary $dictionary
+     * @param SplObjectStorage            $processedActions  list of already processed action dictionaries, used to avoid cyclic references
+     * @throws Zend_Pdf_Exception
+     */
+    public function __construct(Zend_Pdf_Element $dictionary, SplObjectStorage $processedActions)
+    {
+        parent::__construct($dictionary, $processedActions);
+
+        $this->_destination = Zend_Pdf_Destination::load($dictionary->D);
+    }
+
+    /**
+     * Create new Zend_Pdf_Action_GoTo object using specified destination
+     *
+     * @param Zend_Pdf_Destination|string $destination
+     * @return Zend_Pdf_Action_GoTo
+     */
+    public static function create($destination)
     {
-    	if ($objectFactory === null) {
-    		$dictionary = new Zend_Pdf_Element_Dictionary();
-    	} else {
-    		$dictionary = $objectFactory->newObject(new Zend_Pdf_Element_Dictionary());
-    	}
+        if (is_string($destination)) {
+            require_once 'Zend/Pdf/Destination/Named.php';
+            $destination = Zend_Pdf_Destination_Named::create($destination);
+        }
 
-        if ($dictionary->getType() != Zend_Pdf_Element::TYPE_DICTIONARY) {
+        if (!$destination instanceof Zend_Pdf_Destination) {
             require_once 'Zend/Pdf/Exception.php';
-            throw new Zend_Pdf_Exception('$dictionary mast be a direct or an indirect dictionary object.');
+            throw new Zend_Pdf_Exception('$destination parameter must be a Zend_Pdf_Destination object or string.');
         }
-    	$dictionary->Type = new Zend_Pdf_Element_Name('Action');
-    	$dictionary->S    = new Zend_Pdf_Element_Name('GoTo');
-    	$dictionary->Next = null;
-
-    	if (is_string($destination)) {
-            // Named destination
-            $dictionary->D = new Zend_Pdf_Element_String($destination);
-    	} else if ($destination instanceof Zend_Pdf_Element_Array) {
-    		// DestinationArray
-            $dictionary->D = $destination;
-    	} else if ($destination instanceof Zend_Pdf_Destination) {
-    		$dictionary->D = $destination->getResource();
-    	} else {
-    		require_once 'Zend/Pdf/Exception.php';
-    		throw new Zend_Pdf_Exception('Wrong $destination parameter type');
-    	}
-
-    	return new Zend_Pdf_Action_GoTo($dictionary, null, new SplObjectStorage());
+
+        $dictionary = new Zend_Pdf_Element_Dictionary();
+        $dictionary->Type = new Zend_Pdf_Element_Name('Action');
+        $dictionary->S    = new Zend_Pdf_Element_Name('GoTo');
+        $dictionary->Next = null;
+           $dictionary->D    = $destination->getResource();
+
+        return new Zend_Pdf_Action_GoTo($dictionary, new SplObjectStorage());
     }
 
-	/**
-	 * Returns goto action destination
-	 * (Zend_Pdf_Element_Name or Zend_Pdf_Element_String for named destinations
-	 * or Zend_Pdf_Array for explicit destinations)
-	 *
-	 * @return Zend_Pdf_Destination|string
-	 */
-	public function getDestination()
-	{
-		$destination = $this->_actionDictionary->D;
-
-		if ($destination instanceof Zend_Pdf_Element_Name  ||  $destination instanceof Zend_Pdf_Element_String) {
-			return $destination->value;
-        }
+    /**
+     * Set goto action destination
+     *
+     * @return Zend_Pdf_Action_GoTo $destination
+     */
+    public function setDestination(Zend_Pdf_Destination $destination)
+    {
+        $this->_destination = $destination;
+
+        $this->_actionDictionary->touch();
+        $this->_actionDictionary->D = $destination->getResource();
 
-		return Zend_Pdf_Destination::load($this->_actionDictionary->D);
-	}
+        return $this;
+    }
+
+    /**
+     * Get goto action destination
+     *
+     * @return Zend_Pdf_Destination
+     */
+    public function getDestination()
+    {
+        return $this->_destination;
+    }
 }
 

+ 30 - 112
library/Zend/Pdf/Destination.php

@@ -31,7 +31,7 @@ require_once 'Zend/Pdf/Target.php';
 
 
 /**
- * Abstract PDF explicit destination representation class
+ * Abstract PDF destination representation class
  *
  * @package    Zend_Pdf
  * @subpackage Destination
@@ -40,156 +40,74 @@ require_once 'Zend/Pdf/Target.php';
  */
 abstract class Zend_Pdf_Destination extends Zend_Pdf_Target
 {
-	/**
-	 * Destination description array
-	 *
-	 * @var Zend_Pdf_Element_Array
-	 */
-	protected $_destinationArray;
-
-	/**
-	 * True if it's a remote destination
-	 *
-	 * @var boolean
-	 */
-	protected $_isRemote;
-
-	/**
-	 * Destination object constructor
-	 *
-	 * @param Zend_Pdf_Element $destinationArray
-	 * @throws Zend_Pdf_Exception
-	 */
-	public function __construct(Zend_Pdf_Element $destinationArray)
-	{
-        if ($destinationArray->getType() != Zend_Pdf_Element::TYPE_ARRAY) {
-            require_once 'Zend/Pdf/Exception.php';
-            throw new Zend_Pdf_Exception('$destinationArray mast be direct or indirect array object.');
-        }
-
-        $this->_destinationArray = $destinationArray;
-
-        if (count($this->_destinationArray->items) == 0) {
-        	require_once 'Zend/Pdf/Exception.php';
-        	throw new Zend_Pdf_Exception('Destination array must contain a page reference.');
-        }
-        if (count($this->_destinationArray->items) == 1) {
-            require_once 'Zend/Pdf/Exception.php';
-            throw new Zend_Pdf_Exception('Destination array must contain a destination type name.');
-        }
-
-        switch ($this->_destinationArray->items[0]->getType()) {
-        	case Zend_Pdf_Element::TYPE_NUMERIC:
-        		$this->_isRemote = true;
-        		break;
-
-            case Zend_Pdf_Element::TYPE_DICTIONARY:
-            	$this->_isRemote = false;
-                break;
-
-            default:
-            	require_once 'Zend/Pdf/Exception.php';
-                throw new Zend_Pdf_Exception('Destination target must be a page number or page dictionary object.');
-            	break;
-        }
-	}
-
-
-    public static function load(Zend_Pdf_Element $destinationArray)
+    /**
+     * Load Destination object from a specified resource
+     *
+     * @internal
+     * @param $destinationArray
+     * @return Zend_Pdf_Destination
+     */
+    public static function load(Zend_Pdf_Element $resource)
     {
-        if ($destinationArray->getType() != Zend_Pdf_Element::TYPE_ARRAY) {
-            require_once 'Zend/Pdf/Exception.php';
-            throw new Zend_Pdf_Exception('$destinationArray mast be direct or indirect array object.');
+        if ($resource->getType() == Zend_Pdf_Element::TYPE_NAME  ||  $resource->getType() == Zend_Pdf_Element::TYPE_STRING) {
+            require_once 'Zend/Pdf/Destination/Named.php';
+            return new Zend_Pdf_Destination_Named($resource);
         }
-        if (count($destinationArray->items) == 0) {
+
+        if ($resource->getType() != Zend_Pdf_Element::TYPE_ARRAY) {
             require_once 'Zend/Pdf/Exception.php';
-            throw new Zend_Pdf_Exception('Destination array must contain a page reference.');
+            throw new Zend_Pdf_Exception('An explicit destination must be a direct or an indirect array object.');
         }
-        if (count($destinationArray->items) == 1) {
+        if (count($resource->items) < 2) {
             require_once 'Zend/Pdf/Exception.php';
-            throw new Zend_Pdf_Exception('Destination array must contain a destination type name.');
+            throw new Zend_Pdf_Exception('An explicit destination array must contain at least two elements.');
         }
 
-        switch ($destinationArray->items[1]->value) {
-        	case 'XYZ':
-        		require_once 'Zend/Pdf/Destination/Zoom.php';
-                return new Zend_Pdf_Destination_Zoom($destinationArray);
-        		break;
+        switch ($resource->items[1]->value) {
+            case 'XYZ':
+                require_once 'Zend/Pdf/Destination/Zoom.php';
+                return new Zend_Pdf_Destination_Zoom($resource);
+                break;
 
             case 'Fit':
                 require_once 'Zend/Pdf/Destination/Fit.php';
-                return new Zend_Pdf_Destination_Fit($destinationArray);
+                return new Zend_Pdf_Destination_Fit($resource);
                 break;
 
             case 'FitH':
                 require_once 'Zend/Pdf/Destination/FitHorizontally.php';
-                return new Zend_Pdf_Destination_FitHorizontally($destinationArray);
+                return new Zend_Pdf_Destination_FitHorizontally($resource);
                 break;
 
             case 'FitV':
                 require_once 'Zend/Pdf/Destination/FitVertically.php';
-                return new Zend_Pdf_Destination_FitVertically($destinationArray);
+                return new Zend_Pdf_Destination_FitVertically($resource);
                 break;
 
             case 'FitR':
                 require_once 'Zend/Pdf/Destination/FitRectangle.php';
-                return new Zend_Pdf_Destination_FitRectangle($destinationArray);
+                return new Zend_Pdf_Destination_FitRectangle($resource);
                 break;
 
             case 'FitB':
                 require_once 'Zend/Pdf/Destination/FitBoundingBox.php';
-                return new Zend_Pdf_Destination_FitBoundingBox($destinationArray);
+                return new Zend_Pdf_Destination_FitBoundingBox($resource);
                 break;
 
             case 'FitBH':
                 require_once 'Zend/Pdf/Destination/FitBoundingBoxHorizontally.php';
-                return new Zend_Pdf_Destination_FitBoundingBoxHorizontally($destinationArray);
+                return new Zend_Pdf_Destination_FitBoundingBoxHorizontally($resource);
                 break;
 
             case 'FitBV':
                 require_once 'Zend/Pdf/Destination/FitBoundingBoxVertically.php';
-                return new Zend_Pdf_Destination_FitBoundingBoxVertically($destinationArray);
+                return new Zend_Pdf_Destination_FitBoundingBoxVertically($resource);
                 break;
 
             default:
                 require_once 'Zend/Pdf/Destination/Unknown.php';
-                return new Zend_Pdf_Destination_Unknown($destinationArray);
+                return new Zend_Pdf_Destination_Unknown($resource);
                 break;
         }
     }
-
-    /**
-     * Returns true if it's a remote destination
-     *
-     * @return boolean
-     */
-    public function isRemote()
-    {
-    	return $this->_isRemote;
-    }
-
-    /**
-     * Returns destination target
-     *
-     * Returns page number for remote destinations and
-     * page dictionary object reference otherwise
-     *
-     * @internal
-     * @return integer|Zend_Pdf_Element_Dictionary
-     */
-    public function getTarget()
-    {
-        return $this->_destinationArray->items[0];
-    }
-
-    /**
-     * Get resource
-     *
-     * @internal
-     * @return Zend_Pdf_Element
-     */
-    public function getResource()
-    {
-        return $this->_destinationArray;
-    }
 }

+ 121 - 0
library/Zend/Pdf/Destination/Explicit.php

@@ -0,0 +1,121 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Pdf
+ * @subpackage Destination
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id: Destination.php 16978 2009-07-22 19:59:40Z alexander $
+ */
+
+/** Zend_Pdf_Page */
+require_once 'Zend/Pdf/Page.php';
+
+/** Zend_Pdf_Destination */
+require_once 'Zend/Pdf/Destination.php';
+
+
+/**
+ * Abstract PDF explicit destination representation class
+ *
+ * @package    Zend_Pdf
+ * @subpackage Destination
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+abstract class Zend_Pdf_Destination_Explicit extends Zend_Pdf_Destination
+{
+    /**
+     * Destination description array
+     *
+     * @var Zend_Pdf_Element_Array
+     */
+    protected $_destinationArray;
+
+    /**
+     * True if it's a remote destination
+     *
+     * @var boolean
+     */
+    protected $_isRemote;
+
+    /**
+     * Explicit destination object constructor
+     *
+     * @param Zend_Pdf_Element $destinationArray
+     * @throws Zend_Pdf_Exception
+     */
+    public function __construct(Zend_Pdf_Element $destinationArray)
+    {
+        if ($destinationArray->getType() != Zend_Pdf_Element::TYPE_ARRAY) {
+            require_once 'Zend/Pdf/Exception.php';
+            throw new Zend_Pdf_Exception('Explicit destination resource Array must be a direct or an indirect array object.');
+        }
+
+        $this->_destinationArray = $destinationArray;
+
+        switch (count($this->_destinationArray->items)) {
+            case 0:
+                require_once 'Zend/Pdf/Exception.php';
+                throw new Zend_Pdf_Exception('Destination array must contain a page reference.');
+                break;
+
+            case 1:
+                require_once 'Zend/Pdf/Exception.php';
+                throw new Zend_Pdf_Exception('Destination array must contain a destination type name.');
+                break;
+
+            default:
+                // Do nothing
+                break;
+        }
+
+        switch ($this->_destinationArray->items[0]->getType()) {
+            case Zend_Pdf_Element::TYPE_NUMERIC:
+                $this->_isRemote = true;
+                break;
+
+            case Zend_Pdf_Element::TYPE_DICTIONARY:
+                $this->_isRemote = false;
+                break;
+
+            default:
+                require_once 'Zend/Pdf/Exception.php';
+                throw new Zend_Pdf_Exception('Destination target must be a page number or page dictionary object.');
+                break;
+        }
+    }
+
+    /**
+     * Returns true if it's a remote destination
+     *
+     * @return boolean
+     */
+    public function isRemote()
+    {
+        return $this->_isRemote;
+    }
+
+    /**
+     * Get resource
+     *
+     * @internal
+     * @return Zend_Pdf_Element
+     */
+    public function getResource()
+    {
+        return $this->_destinationArray;
+    }
+}

+ 9 - 17
library/Zend/Pdf/Destination/Fit.php

@@ -20,11 +20,13 @@
  * @version    $Id$
  */
 
-/** Zend_Pdf_Destination */
-require_once 'Zend/Pdf/Destination.php';
+/** Zend_Pdf_Destination_Explicit */
+require_once 'Zend/Pdf/Destination/Explicit.php';
 
 
 /**
+ * Zend_Pdf_Destination_Fit explicit detination
+ *
  * Destination array: [page /Fit]
  *
  * Display the page designated by page, with its contents magnified just enough
@@ -38,14 +40,12 @@ require_once 'Zend/Pdf/Destination.php';
  * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  * @license    http://framework.zend.com/license/new-bsd     New BSD License
  */
-class Zend_Pdf_Destination_Fit extends Zend_Pdf_Destination
+class Zend_Pdf_Destination_Fit extends Zend_Pdf_Destination_Explicit
 {
     /**
      * Create destination object
      *
-     * @param Zend_Pdf_Page|Zend_Pdf_Element|integer $page  Page object,
-     *                                                      page number (integer or Zend_Pdf_Element_Numeric object) or
-     *                                                      page dictionary object
+     * @param Zend_Pdf_Page|integer $page  Page object or page number
      * @return Zend_Pdf_Destination_Fit
      * @throws Zend_Pdf_Exception
      */
@@ -53,21 +53,13 @@ class Zend_Pdf_Destination_Fit extends Zend_Pdf_Destination
     {
         $destinationArray = new Zend_Pdf_Element_Array();
 
-        if ($page instanceof Zend_Pdf_Element) {
-        	if ($page->getType() == Zend_Pdf_Element::TYPE_NUMERIC  ||  $page->getType() == Zend_Pdf_Element::TYPE_DICTIONARY) {
-        		// Page destination entry is a page number or page dictionary object
-        		$destinationArray->items[] = $page;
-        	} else {
-                require_once 'Zend/Pdf/Exception.php';
-                throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object, page number or page dictionary reference.');
-            }
-        } else if ($page instanceof Zend_Pdf_Page) {
+        if ($page instanceof Zend_Pdf_Page) {
             $destinationArray->items[] = $page->getPageDictionary();
         } else if (is_integer($page)) {
-        	$destinationArray->items[] = new Zend_Pdf_Element_Numeric($page);
+            $destinationArray->items[] = new Zend_Pdf_Element_Numeric($page);
         } else {
             require_once 'Zend/Pdf/Exception.php';
-            throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object, page number or page dictionary reference.');
+            throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object or a page number.');
         }
 
         $destinationArray->items[] = new Zend_Pdf_Element_Name('Fit');

+ 8 - 16
library/Zend/Pdf/Destination/FitBoundingBox.php

@@ -20,11 +20,13 @@
  * @version    $Id$
  */
 
-/** Zend_Pdf_Destination */
-require_once 'Zend/Pdf/Destination.php';
+/** Zend_Pdf_Destination_Explicit */
+require_once 'Zend/Pdf/Destination/Explicit.php';
 
 
 /**
+ * Zend_Pdf_Destination_FitBoundingBox explicit detination
+ *
  * Destination array: [page /FitB]
  *
  * (PDF 1.1) Display the page designated by page, with its contents magnified
@@ -38,14 +40,12 @@ require_once 'Zend/Pdf/Destination.php';
  * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  * @license    http://framework.zend.com/license/new-bsd     New BSD License
  */
-class Zend_Pdf_Destination_FitBoundingBox extends Zend_Pdf_Destination
+class Zend_Pdf_Destination_FitBoundingBox extends Zend_Pdf_Destination_Explicit
 {
     /**
      * Create destination object
      *
-     * @param Zend_Pdf_Page|Zend_Pdf_Element|integer $page  Page object,
-     *                                                      page number (integer or Zend_Pdf_Element_Numeric object) or
-     *                                                      page dictionary object
+     * @param Zend_Pdf_Page|integer $page  Page object or page number
      * @return Zend_Pdf_Destination_FitBoundingBox
      * @throws Zend_Pdf_Exception
      */
@@ -53,21 +53,13 @@ class Zend_Pdf_Destination_FitBoundingBox extends Zend_Pdf_Destination
     {
         $destinationArray = new Zend_Pdf_Element_Array();
 
-        if ($page instanceof Zend_Pdf_Element) {
-            if ($page->getType() == Zend_Pdf_Element::TYPE_NUMERIC  ||  $page->getType() == Zend_Pdf_Element::TYPE_DICTIONARY) {
-                // Page destination entry is a page number or page dictionary object
-                $destinationArray->items[] = $page;
-            } else {
-                require_once 'Zend/Pdf/Exception.php';
-                throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object, page number or page dictionary reference.');
-            }
-        } else if ($page instanceof Zend_Pdf_Page) {
+        if ($page instanceof Zend_Pdf_Page) {
             $destinationArray->items[] = $page->getPageDictionary();
         } else if (is_integer($page)) {
             $destinationArray->items[] = new Zend_Pdf_Element_Numeric($page);
         } else {
             require_once 'Zend/Pdf/Exception.php';
-            throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object, page number or page dictionary reference.');
+            throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object or a page number.');
         }
 
         $destinationArray->items[] = new Zend_Pdf_Element_Name('FitB');

+ 8 - 16
library/Zend/Pdf/Destination/FitBoundingBoxHorizontally.php

@@ -20,11 +20,13 @@
  * @version    $Id$
  */
 
-/** Zend_Pdf_Destination */
-require_once 'Zend/Pdf/Destination.php';
+/** Zend_Pdf_Destination_Explicit */
+require_once 'Zend/Pdf/Destination/Explicit.php';
 
 
 /**
+ * Zend_Pdf_Destination_FitBoundingBoxHorizontally explicit detination
+ *
  * Destination array: [page /FitBH top]
  *
  * (PDF 1.1) Display the page designated by page, with the vertical coordinate
@@ -37,14 +39,12 @@ require_once 'Zend/Pdf/Destination.php';
  * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  * @license    http://framework.zend.com/license/new-bsd     New BSD License
  */
-class Zend_Pdf_Destination_FitBoundingBoxHorizontally extends Zend_Pdf_Destination
+class Zend_Pdf_Destination_FitBoundingBoxHorizontally extends Zend_Pdf_Destination_Explicit
 {
     /**
      * Create destination object
      *
-     * @param Zend_Pdf_Page|Zend_Pdf_Element|integer $page  Page object,
-     *                                                      page number (integer or Zend_Pdf_Element_Numeric object) or
-     *                                                      page dictionary object
+     * @param Zend_Pdf_Page|integer $page  Page object or page number
      * @param float $top   Top edge of displayed page
      * @return Zend_Pdf_Destination_FitBoundingBoxHorizontally
      * @throws Zend_Pdf_Exception
@@ -53,21 +53,13 @@ class Zend_Pdf_Destination_FitBoundingBoxHorizontally extends Zend_Pdf_Destinati
     {
         $destinationArray = new Zend_Pdf_Element_Array();
 
-        if ($page instanceof Zend_Pdf_Element) {
-            if ($page->getType() == Zend_Pdf_Element::TYPE_NUMERIC  ||  $page->getType() == Zend_Pdf_Element::TYPE_DICTIONARY) {
-                // Page destination entry is a page number or page dictionary object
-                $destinationArray->items[] = $page;
-            } else {
-                require_once 'Zend/Pdf/Exception.php';
-                throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object, page number or page dictionary reference.');
-            }
-        } else if ($page instanceof Zend_Pdf_Page) {
+        if ($page instanceof Zend_Pdf_Page) {
             $destinationArray->items[] = $page->getPageDictionary();
         } else if (is_integer($page)) {
             $destinationArray->items[] = new Zend_Pdf_Element_Numeric($page);
         } else {
             require_once 'Zend/Pdf/Exception.php';
-            throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object, page number or page dictionary reference.');
+            throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object or a page number.');
         }
 
         $destinationArray->items[] = new Zend_Pdf_Element_Name('FitBH');

+ 8 - 16
library/Zend/Pdf/Destination/FitBoundingBoxVertically.php

@@ -20,11 +20,13 @@
  * @version    $Id$
  */
 
-/** Zend_Pdf_Destination */
-require_once 'Zend/Pdf/Destination.php';
+/** Zend_Pdf_Destination_Explicit */
+require_once 'Zend/Pdf/Destination/Explicit.php';
 
 
 /**
+ * Zend_Pdf_Destination_FitBoundingBoxVertically explicit detination
+ *
  * Destination array: [page /FitBV left]
  *
  * (PDF 1.1) Display the page designated by page, with the horizontal coordinate
@@ -37,14 +39,12 @@ require_once 'Zend/Pdf/Destination.php';
  * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  * @license    http://framework.zend.com/license/new-bsd     New BSD License
  */
-class Zend_Pdf_Destination_FitBoundingBoxVertically extends Zend_Pdf_Destination
+class Zend_Pdf_Destination_FitBoundingBoxVertically extends Zend_Pdf_Destination_Explicit
 {
     /**
      * Create destination object
      *
-     * @param Zend_Pdf_Page|Zend_Pdf_Element|integer $page  Page object,
-     *                                                      page number (integer or Zend_Pdf_Element_Numeric object) or
-     *                                                      page dictionary object
+     * @param Zend_Pdf_Page|integer $page  Page object or page number
      * @param float $left  Left edge of displayed page
      * @return Zend_Pdf_Destination_FitBoundingBoxVertically
      * @throws Zend_Pdf_Exception
@@ -53,21 +53,13 @@ class Zend_Pdf_Destination_FitBoundingBoxVertically extends Zend_Pdf_Destination
     {
         $destinationArray = new Zend_Pdf_Element_Array();
 
-        if ($page instanceof Zend_Pdf_Element) {
-            if ($page->getType() == Zend_Pdf_Element::TYPE_NUMERIC  ||  $page->getType() == Zend_Pdf_Element::TYPE_DICTIONARY) {
-                // Page destination entry is a page number or page dictionary object
-                $destinationArray->items[] = $page;
-            } else {
-                require_once 'Zend/Pdf/Exception.php';
-                throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object, page number or page dictionary reference.');
-            }
-        } else if ($page instanceof Zend_Pdf_Page) {
+        if ($page instanceof Zend_Pdf_Page) {
             $destinationArray->items[] = $page->getPageDictionary();
         } else if (is_integer($page)) {
             $destinationArray->items[] = new Zend_Pdf_Element_Numeric($page);
         } else {
             require_once 'Zend/Pdf/Exception.php';
-            throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object, page number or page dictionary reference.');
+            throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object or a page number.');
         }
 
         $destinationArray->items[] = new Zend_Pdf_Element_Name('FitBV');

+ 8 - 16
library/Zend/Pdf/Destination/FitHorizontally.php

@@ -20,11 +20,13 @@
  * @version    $Id$
  */
 
-/** Zend_Pdf_Destination */
-require_once 'Zend/Pdf/Destination.php';
+/** Zend_Pdf_Destination_Explicit */
+require_once 'Zend/Pdf/Destination/Explicit.php';
 
 
 /**
+ * Zend_Pdf_Destination_FitHorizontally explicit detination
+ *
  * Destination array: [page /FitH top]
  *
  * Display the page designated by page, with the vertical coordinate top positioned
@@ -36,14 +38,12 @@ require_once 'Zend/Pdf/Destination.php';
  * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  * @license    http://framework.zend.com/license/new-bsd     New BSD License
  */
-class Zend_Pdf_Destination_FitHorizontally extends Zend_Pdf_Destination
+class Zend_Pdf_Destination_FitHorizontally extends Zend_Pdf_Destination_Explicit
 {
     /**
      * Create destination object
      *
-     * @param Zend_Pdf_Page|Zend_Pdf_Element|integer $page  Page object,
-     *                                                      page number (integer or Zend_Pdf_Element_Numeric object) or
-     *                                                      page dictionary object
+     * @param Zend_Pdf_Page|integer $page  Page object or page number
      * @param float $top  Top edge of displayed page
      * @return Zend_Pdf_Destination_FitHorizontally
      * @throws Zend_Pdf_Exception
@@ -52,21 +52,13 @@ class Zend_Pdf_Destination_FitHorizontally extends Zend_Pdf_Destination
     {
         $destinationArray = new Zend_Pdf_Element_Array();
 
-        if ($page instanceof Zend_Pdf_Element) {
-            if ($page->getType() == Zend_Pdf_Element::TYPE_NUMERIC  ||  $page->getType() == Zend_Pdf_Element::TYPE_DICTIONARY) {
-                // Page destination entry is a page number or page dictionary object
-                $destinationArray->items[] = $page;
-            } else {
-                require_once 'Zend/Pdf/Exception.php';
-                throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object, page number or page dictionary reference.');
-            }
-        } else if ($page instanceof Zend_Pdf_Page) {
+        if ($page instanceof Zend_Pdf_Page) {
             $destinationArray->items[] = $page->getPageDictionary();
         } else if (is_integer($page)) {
             $destinationArray->items[] = new Zend_Pdf_Element_Numeric($page);
         } else {
             require_once 'Zend/Pdf/Exception.php';
-            throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object, page number or page dictionary reference.');
+            throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object or a page number.');
         }
 
         $destinationArray->items[] = new Zend_Pdf_Element_Name('FitH');

+ 8 - 16
library/Zend/Pdf/Destination/FitRectangle.php

@@ -20,11 +20,13 @@
  * @version    $Id$
  */
 
-/** Zend_Pdf_Destination */
-require_once 'Zend/Pdf/Destination.php';
+/** Zend_Pdf_Destination_Explicit */
+require_once 'Zend/Pdf/Destination/Explicit.php';
 
 
 /**
+ * Zend_Pdf_Destination_FitRectangle explicit detination
+ *
  * Destination array: [page /FitR left bottom right top]
  *
  * Display the page designated by page, with its contents magnified just enough
@@ -38,14 +40,12 @@ require_once 'Zend/Pdf/Destination.php';
  * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  * @license    http://framework.zend.com/license/new-bsd     New BSD License
  */
-class Zend_Pdf_Destination_FitRectangle extends Zend_Pdf_Destination
+class Zend_Pdf_Destination_FitRectangle extends Zend_Pdf_Destination_Explicit
 {
     /**
      * Create destination object
      *
-     * @param Zend_Pdf_Page|Zend_Pdf_Element|integer $page  Page object,
-     *                                                      page number (integer or Zend_Pdf_Element_Numeric object) or
-     *                                                      page dictionary object
+     * @param Zend_Pdf_Page|integer $page  Page object or page number
      * @param float $left    Left edge of displayed page
      * @param float $bottom  Bottom edge of displayed page
      * @param float $right   Right edge of displayed page
@@ -57,21 +57,13 @@ class Zend_Pdf_Destination_FitRectangle extends Zend_Pdf_Destination
     {
         $destinationArray = new Zend_Pdf_Element_Array();
 
-        if ($page instanceof Zend_Pdf_Element) {
-            if ($page->getType() == Zend_Pdf_Element::TYPE_NUMERIC  ||  $page->getType() == Zend_Pdf_Element::TYPE_DICTIONARY) {
-                // Page destination entry is a page number or page dictionary object
-                $destinationArray->items[] = $page;
-            } else {
-                require_once 'Zend/Pdf/Exception.php';
-                throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object, page number or page dictionary reference.');
-            }
-        } else if ($page instanceof Zend_Pdf_Page) {
+        if ($page instanceof Zend_Pdf_Page) {
             $destinationArray->items[] = $page->getPageDictionary();
         } else if (is_integer($page)) {
             $destinationArray->items[] = new Zend_Pdf_Element_Numeric($page);
         } else {
             require_once 'Zend/Pdf/Exception.php';
-            throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object, page number or page dictionary reference.');
+            throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object or a page number.');
         }
 
         $destinationArray->items[] = new Zend_Pdf_Element_Name('FitR');

+ 8 - 16
library/Zend/Pdf/Destination/FitVertically.php

@@ -20,11 +20,13 @@
  * @version    $Id$
  */
 
-/** Zend_Pdf_Destination */
-require_once 'Zend/Pdf/Destination.php';
+/** Zend_Pdf_Destination_Explicit */
+require_once 'Zend/Pdf/Destination/Explicit.php';
 
 
 /**
+ * Zend_Pdf_Destination_FitVertically explicit detination
+ *
  * Destination array: [page /FitV left]
  *
  * Display the page designated by page, with the horizontal coordinate left positioned
@@ -36,14 +38,12 @@ require_once 'Zend/Pdf/Destination.php';
  * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  * @license    http://framework.zend.com/license/new-bsd     New BSD License
  */
-class Zend_Pdf_Destination_FitVertically extends Zend_Pdf_Destination
+class Zend_Pdf_Destination_FitVertically extends Zend_Pdf_Destination_Explicit
 {
     /**
      * Create destination object
      *
-     * @param Zend_Pdf_Page|Zend_Pdf_Element|integer $page  Page object,
-     *                                                      page number (integer or Zend_Pdf_Element_Numeric object) or
-     *                                                      page dictionary object
+     * @param Zend_Pdf_Page|integer $page  Page object or page number
      * @param float $left  Left edge of displayed page
      * @return Zend_Pdf_Destination_FitVertically
      * @throws Zend_Pdf_Exception
@@ -52,21 +52,13 @@ class Zend_Pdf_Destination_FitVertically extends Zend_Pdf_Destination
     {
         $destinationArray = new Zend_Pdf_Element_Array();
 
-        if ($page instanceof Zend_Pdf_Element) {
-            if ($page->getType() == Zend_Pdf_Element::TYPE_NUMERIC  ||  $page->getType() == Zend_Pdf_Element::TYPE_DICTIONARY) {
-                // Page destination entry is a page number or page dictionary object
-                $destinationArray->items[] = $page;
-            } else {
-                require_once 'Zend/Pdf/Exception.php';
-                throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object, page number or page dictionary reference.');
-            }
-        } else if ($page instanceof Zend_Pdf_Page) {
+        if ($page instanceof Zend_Pdf_Page) {
             $destinationArray->items[] = $page->getPageDictionary();
         } else if (is_integer($page)) {
             $destinationArray->items[] = new Zend_Pdf_Element_Numeric($page);
         } else {
             require_once 'Zend/Pdf/Exception.php';
-            throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object, page number or page dictionary reference.');
+            throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object or page number.');
         }
 
         $destinationArray->items[] = new Zend_Pdf_Element_Name('FitV');

+ 97 - 0
library/Zend/Pdf/Destination/Named.php

@@ -0,0 +1,97 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Pdf
+ * @subpackage Destination
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id: Fit.php 16971 2009-07-22 18:05:45Z mikaelkael $
+ */
+
+/** Zend_Pdf_Destination */
+require_once 'Zend/Pdf/Destination.php';
+
+
+/**
+ * Destination array: [page /Fit]
+ *
+ * Display the page designated by page, with its contents magnified just enough
+ * to fit the entire page within the window both horizontally and vertically. If
+ * the required horizontal and vertical magnification factors are different, use
+ * the smaller of the two, centering the page within the window in the other
+ * dimension.
+ *
+ * @package    Zend_Pdf
+ * @subpackage Destination
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Pdf_Destination_Named extends Zend_Pdf_Destination
+{
+    /**
+     * Destination name
+     *
+     * @var Zend_Pdf_Element_Name|Zend_Pdf_Element_String
+     */
+    protected $_nameElement;
+
+    /**
+     * Named destination object constructor
+     *
+     * @param $resource
+     * @throws Zend_Pdf_Exception
+     */
+    public function __construct(Zend_Pdf_Element $resource)
+    {
+        if ($resource->getType() != Zend_Pdf_Element::TYPE_NAME  &&  $resource->getType() != Zend_Pdf_Element::TYPE_STRING) {
+            require_once 'Zend/Pdf/Exception.php';
+            throw new Zend_Pdf_Exception('Named destination resource must be a PDF name or a PDF string.');
+        }
+
+        $this->_nameElement = $resource;
+    }
+
+    /**
+     * Create named destination object
+     *
+     * @param string $name
+     * @return Zend_Pdf_Destination_Named
+     */
+    public static function create($name)
+    {
+        return new Zend_Pdf_Destination_Named(new Zend_Pdf_Element_String($name));
+    }
+
+    /**
+     * Get name
+     *
+     * @return Zend_Pdf_Element
+     */
+    public function getName()
+    {
+        return $this->_nameElement->value;
+    }
+
+    /**
+     * Get resource
+     *
+     * @internal
+     * @return Zend_Pdf_Element
+     */
+    public function getResource()
+    {
+        return $this->_nameElement;
+    }
+}

+ 4 - 4
library/Zend/Pdf/Destination/Unknown.php

@@ -20,18 +20,18 @@
  * @version    $Id$
  */
 
-/** Zend_Pdf_Destination */
-require_once 'Zend/Pdf/Destination.php';
+/** Zend_Pdf_Destination_Explicit */
+require_once 'Zend/Pdf/Destination/Explicit.php';
 
 
 /**
- * Unrecognized destination representation class
+ * Unrecognized explicit destination representation class
  *
  * @package    Zend_Pdf
  * @subpackage Destination
  * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  * @license    http://framework.zend.com/license/new-bsd     New BSD License
  */
-class Zend_Pdf_Destination_Unknown extends Zend_Pdf_Destination
+class Zend_Pdf_Destination_Unknown extends Zend_Pdf_Destination_Explicit
 {
 }

+ 32 - 40
library/Zend/Pdf/Destination/Zoom.php

@@ -20,11 +20,13 @@
  * @version    $Id$
  */
 
-/** Zend_Pdf_Destination */
-require_once 'Zend/Pdf/Destination.php';
+/** Zend_Pdf_Destination_Explicit */
+require_once 'Zend/Pdf/Destination/Explicit.php';
 
 
 /**
+ * Zend_Pdf_Destination_Zoom explicit detination
+ *
  * Destination array: [page /XYZ left top zoom]
  *
  * Display the page designated by page, with the coordinates (left, top) positioned
@@ -38,48 +40,38 @@ require_once 'Zend/Pdf/Destination.php';
  * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  * @license    http://framework.zend.com/license/new-bsd     New BSD License
  */
-class Zend_Pdf_Destination_Zoom extends Zend_Pdf_Destination
+class Zend_Pdf_Destination_Zoom extends Zend_Pdf_Destination_Explicit
 {
-	/**
-	 * Create destination object
-	 *
-     * @param Zend_Pdf_Page|Zend_Pdf_Element|integer $page  Page object,
-     *                                                      page number (integer or Zend_Pdf_Element_Numeric object) or
-     *                                                      page dictionary object
-	 * @param float $left  Left edge of displayed page
-	 * @param float $top   Top edge of displayed page
-	 * @param float $zoom  Zoom factor
-	 * @return Zend_Pdf_Destination_Zoom
-	 * @throws Zend_Pdf_Exception
-	 */
+    /**
+     * Create destination object
+     *
+     * @param Zend_Pdf_Page|integer $page  Page object or page number
+     * @param float $left  Left edge of displayed page
+     * @param float $top   Top edge of displayed page
+     * @param float $zoom  Zoom factor
+     * @return Zend_Pdf_Destination_Zoom
+     * @throws Zend_Pdf_Exception
+     */
     public static function create($page, $left = null, $top = null, $zoom = null)
     {
-    	$destinationArray = new Zend_Pdf_Element_Array();
-
-        if ($page instanceof Zend_Pdf_Element) {
-            if ($page->getType() == Zend_Pdf_Element::TYPE_NUMERIC  ||  $page->getType() == Zend_Pdf_Element::TYPE_DICTIONARY) {
-                // Page destination entry is a page number or page dictionary object
-                $destinationArray->items[] = $page;
-            } else {
-                require_once 'Zend/Pdf/Exception.php';
-                throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object, page number or page dictionary reference.');
-            }
-        } else if ($page instanceof Zend_Pdf_Page) {
+        $destinationArray = new Zend_Pdf_Element_Array();
+
+        if ($page instanceof Zend_Pdf_Page) {
             $destinationArray->items[] = $page->getPageDictionary();
         } else if (is_integer($page)) {
             $destinationArray->items[] = new Zend_Pdf_Element_Numeric($page);
         } else {
             require_once 'Zend/Pdf/Exception.php';
-            throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object, page number or page dictionary reference.');
+            throw new Zend_Pdf_Exception('Page entry must be a Zend_Pdf_Page object or a page number.');
         }
 
-    	$destinationArray->items[] = new Zend_Pdf_Element_Name('XYZ');
+        $destinationArray->items[] = new Zend_Pdf_Element_Name('XYZ');
 
-    	if ($left === null) {
-    		$destinationArray->items[] = new Zend_Pdf_Element_Null();
-    	} else {
-    		$destinationArray->items[] = new Zend_Pdf_Element_Numeric($left);
-    	}
+        if ($left === null) {
+            $destinationArray->items[] = new Zend_Pdf_Element_Null();
+        } else {
+            $destinationArray->items[] = new Zend_Pdf_Element_Numeric($left);
+        }
 
         if ($top === null) {
             $destinationArray->items[] = new Zend_Pdf_Element_Null();
@@ -93,7 +85,7 @@ class Zend_Pdf_Destination_Zoom extends Zend_Pdf_Destination
             $destinationArray->items[] = new Zend_Pdf_Element_Numeric($zoom);
         }
 
-    	return new Zend_Pdf_Destination_Zoom($destinationArray);
+        return new Zend_Pdf_Destination_Zoom($destinationArray);
     }
 
     /**
@@ -114,11 +106,11 @@ class Zend_Pdf_Destination_Zoom extends Zend_Pdf_Destination
      */
     public function setLeftEdge($left)
     {
-    	if ($left === null) {
-    		$this->_destinationArray->items[2] = new Zend_Pdf_Element_Null();
-    	} else {
-    		$this->_destinationArray->items[2] = new Zend_Pdf_Element_Numeric($left);
-    	}
+        if ($left === null) {
+            $this->_destinationArray->items[2] = new Zend_Pdf_Element_Null();
+        } else {
+            $this->_destinationArray->items[2] = new Zend_Pdf_Element_Numeric($left);
+        }
 
         return $this;
     }
@@ -171,7 +163,7 @@ class Zend_Pdf_Destination_Zoom extends Zend_Pdf_Destination
         if ($zoom === null) {
             $this->_destinationArray->items[4] = new Zend_Pdf_Element_Null();
         } else {
-        	$this->_destinationArray->items[4] = new Zend_Pdf_Element_Numeric($zoom);
+            $this->_destinationArray->items[4] = new Zend_Pdf_Element_Numeric($zoom);
         }
 
         return $this;

+ 1 - 1
library/Zend/Pdf/Element/Numeric.php

@@ -51,7 +51,7 @@ class Zend_Pdf_Element_Numeric extends Zend_Pdf_Element
     public function __construct($val)
     {
         if ( !is_numeric($val) ) {
-        	require_once 'Zend/Pdf/Exception.php';
+            require_once 'Zend/Pdf/Exception.php';
             throw new Zend_Pdf_Exception('Argument must be numeric');
         }
 

+ 11 - 1
library/Zend/Pdf/Element/Object.php

@@ -208,7 +208,7 @@ class Zend_Pdf_Element_Object extends Zend_Pdf_Element
      */
     public function __call($method, $args)
     {
-    	return call_user_func_array(array($this->_value, $method), $args);
+        return call_user_func_array(array($this->_value, $method), $args);
     }
 
 
@@ -221,6 +221,16 @@ class Zend_Pdf_Element_Object extends Zend_Pdf_Element
     }
 
     /**
+     * Return object, which can be used to identify object and its references identity
+     *
+     * @return Zend_Pdf_Element_Object
+     */
+    public function getObject()
+    {
+        return $this;
+    }
+
+    /**
      * Clean up resources, used by object
      */
     public function cleanUp()

+ 17 - 5
library/Zend/Pdf/Element/Reference.php

@@ -165,12 +165,12 @@ class Zend_Pdf_Element_Reference extends Zend_Pdf_Element
      */
     private function _dereference()
     {
-    	if (($obj = $this->_factory->fetchObject($this->_objNum . ' ' . $this->_genNum)) === null) {
+        if (($obj = $this->_factory->fetchObject($this->_objNum . ' ' . $this->_genNum)) === null) {
             $obj = $this->_context->getParser()->getObject(
                            $this->_context->getRefTable()->getOffset($this->_objNum . ' ' . $this->_genNum . ' R'),
                            $this->_context
                                                           );
-    	}
+        }
 
         if ($obj === null ) {
             $this->_ref = new Zend_Pdf_Element_Null();
@@ -181,10 +181,9 @@ class Zend_Pdf_Element_Reference extends Zend_Pdf_Element
             throw new Zend_Pdf_Exception('Incorrect reference to the object');
         }
 
-        $this->_ref = $obj;
-        $this->setParentObject($obj);
-
         $this->_factory->registerObject($obj, $this->_objNum . ' ' . $this->_genNum);
+
+        $this->_ref = $obj;
     }
 
     /**
@@ -199,6 +198,19 @@ class Zend_Pdf_Element_Reference extends Zend_Pdf_Element
         $this->_ref->touch();
     }
 
+    /**
+     * Return object, which can be used to identify object and its references identity
+     *
+     * @return Zend_Pdf_Element_Object
+     */
+    public function getObject()
+    {
+        if ($this->_ref === null) {
+            $this->_dereference();
+        }
+
+        return $this->_ref;
+    }
 
     /**
      * Get handler

+ 19 - 4
library/Zend/Pdf/ElementFactory.php

@@ -257,7 +257,6 @@ class Zend_Pdf_ElementFactory implements Zend_Pdf_ElementFactory_Interface
     /**
      * Calculate object enumeration shift.
      *
-     * @internal
      * @param Zend_Pdf_ElementFactory_Interface $factory
      * @return integer
      */
@@ -290,6 +289,22 @@ class Zend_Pdf_ElementFactory implements Zend_Pdf_ElementFactory_Interface
     }
 
     /**
+     * Clean enumeration shift cache.
+     * Has to be used after PDF render operation to let followed updates be correct.
+     *
+     * @param Zend_Pdf_ElementFactory_Interface $factory
+     * @return integer
+     */
+    public function cleanEnumerationShiftCache()
+    {
+        $this->_shiftCalculationCache = array();
+
+        foreach ($this->_attachedFactories as $attached) {
+            $attached->cleanEnumerationShiftCache();
+        }
+    }
+
+    /**
      * Retrive object enumeration shift.
      *
      * @param Zend_Pdf_ElementFactory_Interface $factory
@@ -429,9 +444,9 @@ class Zend_Pdf_ElementFactory implements Zend_Pdf_ElementFactory_Interface
      */
     public function fetchObject($refString)
     {
-    	if (!isset($this->_registeredObjects[$refString])) {
-    		return null;
-    	}
+        if (!isset($this->_registeredObjects[$refString])) {
+            return null;
+        }
         return $this->_registeredObjects[$refString];
     }
 

+ 9 - 1
library/Zend/Pdf/ElementFactory/Interface.php

@@ -74,13 +74,21 @@ interface Zend_Pdf_ElementFactory_Interface
     /**
      * Calculate object enumeration shift.
      *
-     * @internal
      * @param Zend_Pdf_ElementFactory_Interface $factory
      * @return integer
      */
     public function calculateShift(Zend_Pdf_ElementFactory_Interface $factory);
 
     /**
+     * Clean enumeration shift cache.
+     * Has to be used after PDF render operation to let followed updates be correct.
+     *
+     * @param Zend_Pdf_ElementFactory_Interface $factory
+     * @return integer
+     */
+    public function cleanEnumerationShiftCache();
+
+    /**
      * Retrive object enumeration shift.
      *
      * @param Zend_Pdf_ElementFactory_Interface $factory

+ 12 - 0
library/Zend/Pdf/ElementFactory/Proxy.php

@@ -130,6 +130,18 @@ class Zend_Pdf_ElementFactory_Proxy implements Zend_Pdf_ElementFactory_Interface
     }
 
     /**
+     * Clean enumeration shift cache.
+     * Has to be used after PDF render operation to let followed updates be correct.
+     *
+     * @param Zend_Pdf_ElementFactory_Interface $factory
+     * @return integer
+     */
+    public function cleanEnumerationShiftCache()
+    {
+        return $this->_factory->cleanEnumerationShiftCache();
+    }
+
+    /**
      * Retrive object enumeration shift.
      *
      * @param Zend_Pdf_ElementFactory_Interface $factory

+ 5 - 5
library/Zend/Pdf/FileParser/Image.php

@@ -35,11 +35,11 @@ require_once 'Zend/Pdf/FileParser.php';
  */
 abstract class Zend_Pdf_FileParser_Image extends Zend_Pdf_FileParser
 {
-	/**
-	 * Image Type
-	 *
-	 * @var integer
-	 */
+    /**
+     * Image Type
+     *
+     * @var integer
+     */
     protected $imageType;
 
     /**

+ 154 - 0
library/Zend/Pdf/NameTree.php

@@ -0,0 +1,154 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Pdf
+ * @subpackage Actions
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id: Action.php 16978 2009-07-22 19:59:40Z alexander $
+ */
+
+/** Zend_Pdf_ElementFactory */
+require_once 'Zend/Pdf/ElementFactory.php';
+
+
+/**
+ * PDF name tree representation class
+ *
+ * @todo implement lazy resource loading so resources will be really loaded at access time
+ *
+ * @package    Zend_Pdf
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Pdf_NameTree implements ArrayAccess, Iterator, Countable
+{
+    /**
+     * Elements
+     * Array of name => object tree entries
+     *
+     * @var array
+     */
+    protected $_items = array();
+
+    /**
+     * Object constructor
+     *
+     * @param $rootDictionary root of name dictionary
+     */
+    public function __construct(Zend_Pdf_Element $rootDictionary)
+    {
+        if ($rootDictionary->getType() != Zend_Pdf_Element::TYPE_DICTIONARY) {
+            require_once 'Zend/Pdf/Exception.php';
+            throw new Zend_Pdf_Exception('Name tree root must be a dictionary.');
+        }
+
+        $intermediateNodes = array();
+        $leafNodes         = array();
+        if ($rootDictionary->Kids !== null) {
+            $intermediateNodes[] = $rootDictionary;
+        } else {
+            $leafNodes[] = $rootDictionary;
+        }
+
+        while (count($intermediateNodes) != 0) {
+            $newIntermediateNodes = array();
+            foreach ($intermediateNodes as $node) {
+                foreach ($node->Kids->items as $childNode) {
+                    if ($childNode->Kids !== null) {
+                        $newIntermediateNodes[] = $childNode;
+                    } else {
+                        $leafNodes[] = $childNode;
+                    }
+                }
+            }
+            $intermediateNodes = $newIntermediateNodes;
+        }
+
+        foreach ($leafNodes as $leafNode) {
+            $destinationsCount = count($leafNode->Names->items)/2;
+            for ($count = 0; $count < $destinationsCount; $count++) {
+                $this->_items[$leafNode->Names->items[$count*2]->value] = $leafNode->Names->items[$count*2 + 1];
+            }
+        }
+    }
+
+    public function current()
+    {
+        return current($this->_items);
+    }
+
+
+    public function next()
+    {
+        return next($this->_items);
+    }
+
+
+    public function key()
+    {
+        return key($this->_items);
+    }
+
+
+    public function valid() {
+        return current($this->_items)!==false;
+    }
+
+
+    public function rewind()
+    {
+        reset($this->_items);
+    }
+
+
+    public function offsetExists($offset)
+    {
+        return array_key_exists($offset, $this->_items);
+    }
+
+
+    public function offsetGet($offset)
+    {
+        return $this->_items[$offset];
+    }
+
+
+    public function offsetSet($offset, $value)
+    {
+        if ($offset === null) {
+            $this->_items[]        = $value;
+        } else {
+            $this->_items[$offset] = $value;
+        }
+    }
+
+
+    public function offsetUnset($offset)
+    {
+        unset($this->_items[$offset]);
+    }
+
+
+    public function clear()
+    {
+        $this->_items = array();
+    }
+
+    public function count()
+    {
+        return count($this->_items);
+    }
+}

+ 126 - 120
library/Zend/Pdf/Outline.php

@@ -36,12 +36,12 @@ require_once 'Zend/Pdf/ElementFactory.php';
  */
 abstract class Zend_Pdf_Outline implements RecursiveIterator, Countable
 {
-	/**
-	 * True if outline is open.
-	 *
-	 * @var boolean
-	 */
-	protected $_open = false;
+    /**
+     * True if outline is open.
+     *
+     * @var boolean
+     */
+    protected $_open = false;
 
     /**
      * Array of child outlines (array of Zend_Pdf_Outline objects)
@@ -67,14 +67,14 @@ abstract class Zend_Pdf_Outline implements RecursiveIterator, Countable
     abstract public function setTitle($title);
 
     /**
-	 * Returns true if outline item is open by default
-	 *
-	 * @return boolean
-	 */
-	public function isOpen()
-	{
-		return $this->_open;
-	}
+     * Returns true if outline item is open by default
+     *
+     * @return boolean
+     */
+    public function isOpen()
+    {
+        return $this->_open;
+    }
 
     /**
      * Sets 'isOpen' outline flag
@@ -138,7 +138,7 @@ abstract class Zend_Pdf_Outline implements RecursiveIterator, Countable
     /**
      * Get outline target.
      *
-     * @return Zend_Pdf_Destination|Zend_Pdf_Action|string
+     * @return Zend_Pdf_Target
      */
     abstract public function getTarget();
 
@@ -146,10 +146,10 @@ abstract class Zend_Pdf_Outline implements RecursiveIterator, Countable
      * Set outline target.
      * Null means no target
      *
-     * @param Zend_Pdf_Destination|Zend_Pdf_Action|string $target
+     * @param Zend_Pdf_Target|string $target
      * @return Zend_Pdf_Outline
      */
-    abstract public function setTarget($target);
+    abstract public function setTarget($target = null);
 
     /**
      * Get outline options
@@ -158,28 +158,28 @@ abstract class Zend_Pdf_Outline implements RecursiveIterator, Countable
      */
     public function getOptions()
     {
-    	return array('title'  => $this->_title,
-    	             'open'   => $this->_open,
-    	             'color'  => $this->_color,
-    	             'italic' => $this->_italic,
-    	             'bold'   => $this->_bold,
-    	             'target' => $this->_target);
+        return array('title'  => $this->_title,
+                     'open'   => $this->_open,
+                     'color'  => $this->_color,
+                     'italic' => $this->_italic,
+                     'bold'   => $this->_bold,
+                     'target' => $this->_target);
     }
 
-	/**
-	 * Set outline options
-	 *
-	 * @param array $options
-	 * @return Zend_Pdf_Actions
+    /**
+     * Set outline options
+     *
+     * @param array $options
+     * @return Zend_Pdf_Actions
      * @throws Zend_Pdf_Exception
-	 */
-	public function setOptions(array $options)
-	{
-		foreach ($options as $key => $value) {
-			switch ($key) {
+     */
+    public function setOptions(array $options)
+    {
+        foreach ($options as $key => $value) {
+            switch ($key) {
                 case 'title':
-                	$this->setTitle($value);
-                	break;
+                    $this->setTitle($value);
+                    break;
 
                 case 'open':
                     $this->setIsOpen($value);
@@ -203,94 +203,100 @@ abstract class Zend_Pdf_Outline implements RecursiveIterator, Countable
                 default:
                     require_once 'Zend/Pdf/Exception.php';
                     throw new Zend_Pdf_Exception("Unknown option name - '$key'.");
-                	break;
-			}
-		}
-
-		return $this;
-	}
-
-	/**
-	 * Create new Outline object
-	 *
-	 * It provides two forms of input parameters:
-	 *
-	 * 1. Zend_Pdf_Outline::create(string $title[, Zend_Pdf_Destination $destination])
-	 * 2. Zend_Pdf_Outline::create(array $options)
-	 *
-	 * Second form allows to provide outline options as an array.
-	 * The followed options are supported:
-	 *   'title'  - string, outline title, required
-	 *   'open'   - boolean, true if outline entry is open (default value is false)
-	 *   'color'  - Zend_Pdf_Color_Rgb object, true if outline entry is open (default value is null - black)
-	 *   'italic' - boolean, true if outline entry is displayed in italic (default value is false)
-	 *   'bold'   - boolean, true if outline entry is displayed in bold (default value is false)
-	 *   'target' - Zend_Pdf_Destination object, outline item destination
-	 *
-	 * @return Zend_Pdf_Outline
-	 * @throws Zend_Pdf_Exception
-	 */
-	public static function create($param1, $param2 = null)
-	{
-		if (is_string($param1)) {
-			if ($param2 !== null  &&  !$param2 instanceof Zend_Pdf_Destination) {
-				require_once 'Zend/Pdf/Exception.php';
-				throw new Zend_Pdf_Exception('Outline create method takes $title (string) and $destination (Zend_Pdf_Destination) or an array as an input');
-			}
-
-			require_once 'Zend/Pdf/Outline/Created.php';
-			return new Zend_Pdf_Outline_Created(array('title'  => $param1,
-			                                          'target' => $param2));
-		} else {
-			if (!is_array($param1)  ||  $param2 !== null) {
+                    break;
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * Create new Outline object
+     *
+     * It provides two forms of input parameters:
+     *
+     * 1. Zend_Pdf_Outline::create(string $title[, Zend_Pdf_Destination $destination])
+     * 2. Zend_Pdf_Outline::create(array $options)
+     *
+     * Second form allows to provide outline options as an array.
+     * The followed options are supported:
+     *   'title'  - string, outline title, required
+     *   'open'   - boolean, true if outline entry is open (default value is false)
+     *   'color'  - Zend_Pdf_Color_Rgb object, true if outline entry is open (default value is null - black)
+     *   'italic' - boolean, true if outline entry is displayed in italic (default value is false)
+     *   'bold'   - boolean, true if outline entry is displayed in bold (default value is false)
+     *   'target' - Zend_Pdf_Destination object, outline item destination
+     *
+     * @return Zend_Pdf_Outline
+     * @throws Zend_Pdf_Exception
+     */
+    public static function create($param1, $param2 = null)
+    {
+        if (is_string($param1)) {
+            if ($param2 !== null  &&  !$param2 instanceof Zend_Pdf_Destination) {
                 require_once 'Zend/Pdf/Exception.php';
                 throw new Zend_Pdf_Exception('Outline create method takes $title (string) and $destination (Zend_Pdf_Destination) or an array as an input');
-			}
-
-			return new Zend_Pdf_Outline_Created($param1);
-		}
-	}
-
-	/**
-	 * Returns number of the total number of open items at all levels of the outline.
-	 *
-	 * @internal
-	 * @return integer
-	 */
-	public function openOutlinesCount()
-	{
-		$count = 1; // Include this outline
-
-		if ($this->isOpen()) {
-			foreach ($this->childOutlines as $child) {
-				$count += $child->openOutlinesCount();
-			}
-		}
-
-		return $count;
-	}
-
-	/**
-	 * Dump Outline and it's child outlines into PDF structures
-	 *
-	 * Returns dictionary indirect object or reference
-	 *
-	 * @param Zend_Pdf_ElementFactory $factory object factory for newly created indirect objects
-	 * @param boolean $updateNavigation
-	 * @param
-	 * @return Zend_Pdf_Element
-	 */
-	abstract public function dumpOutline(Zend_Pdf_ElementFactory_Interface $factory, $updateNavigation, Zend_Pdf_Element $parent, Zend_Pdf_Element $prev = null);
-
-
-	////////////////////////////////////////////////////////////////////////
-	//  RecursiveIterator interface methods
-	//////////////
-
-	/**
+            }
+
+            require_once 'Zend/Pdf/Outline/Created.php';
+            return new Zend_Pdf_Outline_Created(array('title'  => $param1,
+                                                      'target' => $param2));
+        } else {
+            if (!is_array($param1)  ||  $param2 !== null) {
+                require_once 'Zend/Pdf/Exception.php';
+                throw new Zend_Pdf_Exception('Outline create method takes $title (string) and $destination (Zend_Pdf_Destination) or an array as an input');
+            }
+
+            return new Zend_Pdf_Outline_Created($param1);
+        }
+    }
+
+    /**
+     * Returns number of the total number of open items at all levels of the outline.
+     *
+     * @internal
+     * @return integer
+     */
+    public function openOutlinesCount()
+    {
+        $count = 1; // Include this outline
+
+        if ($this->isOpen()) {
+            foreach ($this->childOutlines as $child) {
+                $count += $child->openOutlinesCount();
+            }
+        }
+
+        return $count;
+    }
+
+    /**
+     * Dump Outline and its child outlines into PDF structures
+     *
+     * Returns dictionary indirect object or reference
+     *
+     * @param Zend_Pdf_ElementFactory    $factory object factory for newly created indirect objects
+     * @param boolean $updateNavigation  Update navigation flag
+     * @param Zend_Pdf_Element $parent   Parent outline dictionary reference
+     * @param Zend_Pdf_Element $prev     Previous outline dictionary reference
+     * @param SplObjectStorage $processedOutlines  List of already processed outlines
+     * @return Zend_Pdf_Element
+     */
+    abstract public function dumpOutline(Zend_Pdf_ElementFactory_Interface $factory,
+                                                                           $updateNavigation,
+                                                          Zend_Pdf_Element $parent,
+                                                          Zend_Pdf_Element $prev = null,
+                                                          SplObjectStorage $processedOutlines = null);
+
+
+    ////////////////////////////////////////////////////////////////////////
+    //  RecursiveIterator interface methods
+    //////////////
+
+    /**
      * Returns the child outline.
      *
-     * @return Zend_Pdf_Outline|null
+     * @return Zend_Pdf_Outline
      */
     public function current()
     {
@@ -333,7 +339,7 @@ abstract class Zend_Pdf_Outline implements RecursiveIterator, Countable
         return current($this->childOutlines) !== false;
     }
 
-	/**
+    /**
      * Returns the child outline.
      *
      * @return Zend_Pdf_Outline|null

+ 0 - 137
library/Zend/Pdf/Outline/Container.php

@@ -1,137 +0,0 @@
-<?php
-/**
- * Zend Framework
- *
- * LICENSE
- *
- * This source file is subject to the new BSD license that is bundled
- * with this package in the file LICENSE.txt.
- * It is also available through the world-wide-web at this URL:
- * http://framework.zend.com/license/new-bsd
- * If you did not receive a copy of the license and are unable to
- * obtain it through the world-wide-web, please send an email
- * to license@zend.com so we can send you a copy immediately.
- *
- * @category   Zend
- * @package    Zend_Pdf
- * @subpackage Actions
- * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
- * @license    http://framework.zend.com/license/new-bsd     New BSD License
- * @version    $Id$
- */
-
-/**
- * Iteratable outlines container
- *
- * @todo Implement an ability to associate an outline item with a structure element (PDF 1.3 feature)
- *
- * @package    Zend_Pdf
- * @subpackage Outlines
- * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
- * @license    http://framework.zend.com/license/new-bsd     New BSD License
- */
-class Zend_Pdf_Outline_Container implements RecursiveIterator, Countable
-{
-    /**
-     * Array of outlines (array of Zend_Pdf_Outline objects)
-     *
-     * @var array
-     */
-    protected $_outlines = array();
-
-
-    /**
-     * Object constructor
-     *
-     * @param array $outlines
-     */
-    public function __construct(array $outlines)
-    {
-    	$this->_outlines = $outlines;
-    }
-
-	////////////////////////////////////////////////////////////////////////
-	//  RecursiveIterator interface methods
-	//////////////
-
-	/**
-     * Returns the child outline.
-     *
-     * @return Zend_Pdf_Outline|null
-     */
-    public function current()
-    {
-        return current($this->_outlines);
-    }
-
-    /**
-     * Returns current iterator key
-     *
-     * @return integer
-     */
-    public function key()
-    {
-        return key($this->_outlines);
-    }
-
-    /**
-     * Go to next child
-     */
-    public function next()
-    {
-        return next($this->_outlines);
-    }
-
-    /**
-     * Rewind children
-     */
-    public function rewind()
-    {
-        return reset($this->_outlines);
-    }
-
-    /**
-     * Check if current position is valid
-     *
-     * @return boolean
-     */
-    public function valid()
-    {
-        return current($this->_outlines) !== false;
-    }
-
-	/**
-     * Returns the child outline.
-     *
-     * @return Zend_Pdf_Outline|null
-     */
-    public function getChildren()
-    {
-        return current($this->_outlines);
-    }
-
-    /**
-     * Implements RecursiveIterator interface.
-     *
-     * @return bool  whether container has any pages
-     */
-    public function hasChildren()
-    {
-        return count($this->_outlines) > 0;
-    }
-
-
-    ////////////////////////////////////////////////////////////////////////
-    //  Countable interface methods
-    //////////////
-
-    /**
-     * count()
-     *
-     * @return int
-     */
-    public function count()
-    {
-        return count($this->_outlines);
-    }
-}

+ 85 - 57
library/Zend/Pdf/Outline/Created.php

@@ -26,6 +26,12 @@ require_once 'Zend/Pdf/ElementFactory.php';
 /** Zend_Pdf_Outline */
 require_once 'Zend/Pdf/Outline.php';
 
+/** Zend_Pdf_Destination */
+require_once 'Zend/Pdf/Destination.php';
+
+/** Zend_Pdf_Action */
+require_once 'Zend/Pdf/Action.php';
+
 
 /**
  * PDF outline representation class
@@ -39,12 +45,12 @@ require_once 'Zend/Pdf/Outline.php';
  */
 class Zend_Pdf_Outline_Created extends Zend_Pdf_Outline
 {
-	/**
-	 * Outline title.
-	 *
-	 * @var string
-	 */
-	protected $_title;
+    /**
+     * Outline title.
+     *
+     * @var string
+     */
+    protected $_title;
 
     /**
      * Color to be used for the outline entry’s text.
@@ -84,26 +90,26 @@ class Zend_Pdf_Outline_Created extends Zend_Pdf_Outline
 
 
     /**
-	 * Get outline title.
-	 *
-	 * @return string
-	 */
-	public function getTitle()
-	{
-		return $this->_title;
-	}
-
-	/**
-	 * Set outline title
-	 *
-	 * @param string $title
+     * Get outline title.
+     *
+     * @return string
+     */
+    public function getTitle()
+    {
+        return $this->_title;
+    }
+
+    /**
+     * Set outline title
+     *
+     * @param string $title
      * @return Zend_Pdf_Outline
-	 */
-	public function setTitle($title)
-	{
-		$this->_title = $title;
-		return $this;
-	}
+     */
+    public function setTitle($title)
+    {
+        $this->_title = $title;
+        return $this;
+    }
 
     /**
      * Returns true if outline item is displayed in italic
@@ -176,7 +182,7 @@ class Zend_Pdf_Outline_Created extends Zend_Pdf_Outline
     /**
      * Get outline target.
      *
-     * @return Zend_Pdf_Destination|Zend_Pdf_Action|string
+     * @return Zend_Pdf_Target
      */
     public function getTarget()
     {
@@ -187,38 +193,46 @@ class Zend_Pdf_Outline_Created extends Zend_Pdf_Outline
      * Set outline target.
      * Null means no target
      *
-     * @param Zend_Pdf_Destination|Zend_Pdf_Action|string $target
+     * @param Zend_Pdf_Target|string $target
      * @return Zend_Pdf_Outline
+     * @throws Zend_Pdf_Exception
      */
-    public function setTarget($target)
+    public function setTarget($target = null)
     {
-    	if ($target !== null  && is_string($target) &&  !$target instanceof Zend_Pdf_Destination  &&  !$target instanceof Zend_Pdf_Action) {
-    		require_once 'Zend/Pdf/Exception.php';
-    		throw new Zend_Pdf_Exception('Outline target has to be Zend_Pdf_Destination or Zend_Pdf_Action object, string or null');
-    	}
-        $this->_target = $target;
+        if (is_string($target)) {
+            require_once 'Zend/Pdf/Destination/Named.php';
+            $target = new Zend_Pdf_Destination_Named($target);
+        }
+
+        if ($target === null  ||  $target instanceof Zend_Pdf_Target) {
+            $this->_target = $target;
+        } else {
+            require_once 'Zend/Pdf/Exception.php';
+            throw new Zend_Pdf_Exception('Outline target has to be Zend_Pdf_Destination or Zend_Pdf_Action object or string');
+        }
+
         return $this;
     }
 
 
-	/**
+    /**
      * Object constructor
      *
      * @param array $options
      * @throws Zend_Pdf_Exception
-	 */
-	public function __construct($options = array())
-	{
-		if (!isset($options['title'])) {
-			require_once 'Zend/Pdf/Exception.php';
-			throw new Zend_Pdf_Exception('Title parameter is required.');
-		}
+     */
+    public function __construct($options = array())
+    {
+        if (!isset($options['title'])) {
+            require_once 'Zend/Pdf/Exception.php';
+            throw new Zend_Pdf_Exception('Title parameter is required.');
+        }
 
-		$this->setOptions($options);
-	}
+        $this->setOptions($options);
+    }
 
     /**
-     * Dump Outline and it's child outlines into PDF structures
+     * Dump Outline and its child outlines into PDF structures
      *
      * Returns dictionary indirect object or reference
      *
@@ -227,26 +241,35 @@ class Zend_Pdf_Outline_Created extends Zend_Pdf_Outline
      * @param boolean $updateNavigation  Update navigation flag
      * @param Zend_Pdf_Element $parent   Parent outline dictionary reference
      * @param Zend_Pdf_Element $prev     Previous outline dictionary reference
+     * @param SplObjectStorage $processedOutlines  List of already processed outlines
      * @return Zend_Pdf_Element
+     * @throws Zend_Pdf_Exception
      */
-    public function dumpOutline(Zend_Pdf_ElementFactory_Interface $factory, $updateNavigation, Zend_Pdf_Element $parent, Zend_Pdf_Element $prev = null)
+    public function dumpOutline(Zend_Pdf_ElementFactory_Interface $factory,
+                                                                  $updateNavigation,
+                                                 Zend_Pdf_Element $parent,
+                                                 Zend_Pdf_Element $prev = null,
+                                                 SplObjectStorage $processedOutlines = null)
     {
-    	$outlineDictionary = $factory->newObject(new Zend_Pdf_Element_Dictionary());
+        if ($processedOutlines === null) {
+            $processedOutlines = new SplObjectStorage();
+        }
+        $processedOutlines->attach($this);
+
+        $outlineDictionary = $factory->newObject(new Zend_Pdf_Element_Dictionary());
 
-    	$outlineDictionary->Title = new Zend_Pdf_Element_String($this->getTitle());
+        $outlineDictionary->Title = new Zend_Pdf_Element_String($this->getTitle());
 
-    	$target = $this->getTarget();
+        $target = $this->getTarget();
         if ($target === null) {
             // Do nothing
-        } else if (is_string($target)) {
-            $outlineDictionary->Dest = new Zend_Pdf_Element_String($target);
         } else if ($target instanceof Zend_Pdf_Destination) {
-            $outlineDictionary->Dest = $target->getTarget();
+            $outlineDictionary->Dest = $target->getResource();
         } else if ($target instanceof Zend_Pdf_Action) {
-            $outlineDictionary->A    = $target->getDestinationArray()->items[0];
+            $outlineDictionary->A    = $target->getResource();
         } else {
             require_once 'Zend/Pdf/Exception.php';
-            throw new Zend_Pdf_Exception('Outline target has to be Zend_Pdf_Destination or Zend_Pdf_Action object, string or null');
+            throw new Zend_Pdf_Exception('Outline target has to be Zend_Pdf_Destination, Zend_Pdf_Action object or null');
         }
 
         $color = $this->getColor();
@@ -259,8 +282,8 @@ class Zend_Pdf_Outline_Created extends Zend_Pdf_Outline
         }
 
         if ($this->isItalic()  ||  $this->isBold()) {
-        	$outlineDictionary->F = new Zend_Pdf_Element_Numeric(($this->isItalic()? 1 : 0)  |   // Bit 1 - Italic
-        	                                                     ($this->isBold()?   2 : 0));    // Bit 2 - Bold
+            $outlineDictionary->F = new Zend_Pdf_Element_Numeric(($this->isItalic()? 1 : 0)  |   // Bit 1 - Italic
+                                                                 ($this->isBold()?   2 : 0));    // Bit 2 - Bold
         }
 
 
@@ -269,11 +292,16 @@ class Zend_Pdf_Outline_Created extends Zend_Pdf_Outline
 
         $lastChild = null;
         foreach ($this->childOutlines as $childOutline) {
+            if ($processedOutlines->contains($childOutline)) {
+                require_once 'Zend/Pdf/Exception.php';
+                throw new Zend_Pdf_Exception('Outlines cyclyc reference is detected.');
+            }
+
             if ($lastChild === null) {
-                $lastChild = $childOutline->dumpOutline($factory, true, $outlineDictionary);
+                $lastChild = $childOutline->dumpOutline($factory, true, $outlineDictionary, null, $processedOutlines);
                 $outlineDictionary->First = $lastChild;
             } else {
-                $childOutlineDictionary = $childOutline->dumpOutline($factory, true, $outlineDictionary, $lastChild);
+                $childOutlineDictionary = $childOutline->dumpOutline($factory, true, $outlineDictionary, $lastChild, $processedOutlines);
                 $lastChild->Next = $childOutlineDictionary;
                 $lastChild       = $childOutlineDictionary;
             }

+ 136 - 113
library/Zend/Pdf/Outline/Loaded.php

@@ -26,6 +26,13 @@ require_once 'Zend/Pdf/ElementFactory.php';
 /** Zend_Pdf_Outline */
 require_once 'Zend/Pdf/Outline.php';
 
+/** Zend_Pdf_Destination */
+require_once 'Zend/Pdf/Destination.php';
+
+/** Zend_Pdf_Action */
+require_once 'Zend/Pdf/Action.php';
+
+
 /**
  * Traceable PDF outline representation class
  *
@@ -46,12 +53,12 @@ class Zend_Pdf_Outline_Loaded extends Zend_Pdf_Outline
      */
     protected $_outlineDictionary;
 
-	/**
+    /**
      * original array of child outlines
      *
      * @var array
      */
-	protected $_originalChildOutlines = array();
+    protected $_originalChildOutlines = array();
 
     /**
      * Get outline title.
@@ -61,10 +68,10 @@ class Zend_Pdf_Outline_Loaded extends Zend_Pdf_Outline
      */
     public function getTitle()
     {
-    	if ($this->_outlineDictionary->Title === null) {
-    		require_once 'Zend/Pdf/Exception.php';
-    		throw new Zend_Pdf_Exception('Outline dictionary Title entry is required.');
-    	}
+        if ($this->_outlineDictionary->Title === null) {
+            require_once 'Zend/Pdf/Exception.php';
+            throw new Zend_Pdf_Exception('Outline dictionary Title entry is required.');
+        }
         return $this->_outlineDictionary->Title->value;
     }
 
@@ -76,7 +83,7 @@ class Zend_Pdf_Outline_Loaded extends Zend_Pdf_Outline
      */
     public function setTitle($title)
     {
-    	$this->_outlineDictionary->Title->touch();
+        $this->_outlineDictionary->Title->touch();
         $this->_outlineDictionary->Title = new Zend_Pdf_Element_String($title);
         return $this;
     }
@@ -89,9 +96,9 @@ class Zend_Pdf_Outline_Loaded extends Zend_Pdf_Outline
      */
     public function setIsOpen($isOpen)
     {
-    	parent::setIsOpen($isOpen);
+        parent::setIsOpen($isOpen);
 
-    	if ($this->_outlineDictionary->Count === null) {
+        if ($this->_outlineDictionary->Count === null) {
             // Do Nothing.
             return this;
         }
@@ -99,8 +106,8 @@ class Zend_Pdf_Outline_Loaded extends Zend_Pdf_Outline
         $childrenCount = $this->_outlineDictionary->Count->value;
         $isOpenCurrentState = ($childrenCount > 0);
         if ($isOpen != $isOpenCurrentState) {
-        	$this->_outlineDictionary->Count->touch();
-        	$this->_outlineDictionary->Count->value = ($isOpen? 1 : -1)*abs($childrenCount);
+            $this->_outlineDictionary->Count->touch();
+            $this->_outlineDictionary->Count->value = ($isOpen? 1 : -1)*abs($childrenCount);
         }
 
         return $this;
@@ -127,17 +134,17 @@ class Zend_Pdf_Outline_Loaded extends Zend_Pdf_Outline
      */
     public function setIsItalic($isItalic)
     {
-    	if ($this->_outlineDictionary->F === null) {
-    		$this->_outlineDictionary->touch();
-    		$this->_outlineDictionary->F = new Zend_Pdf_Element_Numeric($isItalic? 1 : 0);
-    	} else {
-    		$this->_outlineDictionary->F->touch();
-    		if ($isItalic) {
-    			$this->_outlineDictionary->F->value = $this->_outlineDictionary->F->value | 1;
-    		} else {
-    			$this->_outlineDictionary->F->value = $this->_outlineDictionary->F->value | ~1;
-    		}
-    	}
+        if ($this->_outlineDictionary->F === null) {
+            $this->_outlineDictionary->touch();
+            $this->_outlineDictionary->F = new Zend_Pdf_Element_Numeric($isItalic? 1 : 0);
+        } else {
+            $this->_outlineDictionary->F->touch();
+            if ($isItalic) {
+                $this->_outlineDictionary->F->value = $this->_outlineDictionary->F->value | 1;
+            } else {
+                $this->_outlineDictionary->F->value = $this->_outlineDictionary->F->value | ~1;
+            }
+        }
         return $this;
     }
 
@@ -185,13 +192,13 @@ class Zend_Pdf_Outline_Loaded extends Zend_Pdf_Outline
     public function getColor()
     {
         if ($this->_outlineDictionary->C === null) {
-        	return null;
+            return null;
         }
 
         $components = $this->_outlineDictionary->C->items;
 
         require_once 'Zend/Pdf/Color/Rgb.php';
-    	return new Zend_Pdf_Color_Rgb($components[0], $components[1], $components[2]);
+        return new Zend_Pdf_Color_Rgb($components[0], $components[1], $components[2]);
     }
 
     /**
@@ -203,25 +210,25 @@ class Zend_Pdf_Outline_Loaded extends Zend_Pdf_Outline
      */
     public function setColor(Zend_Pdf_Color_Rgb $color)
     {
-    	$this->_outlineDictionary->touch();
-
-    	if ($color === null) {
-    		$this->_outlineDictionary->C = null;
-    	} else {
-    		$components = $color->getComponents();
-    		$colorComponentElements = array(new Zend_Pdf_Element_Numeric($components[0]),
-    		                                new Zend_Pdf_Element_Numeric($components[1]),
-    		                                new Zend_Pdf_Element_Numeric($components[2]));
-    		$this->_outlineDictionary->C = new Zend_Pdf_Element_Array($colorComponentElements);
-    	}
-
-    	return $this;
+        $this->_outlineDictionary->touch();
+
+        if ($color === null) {
+            $this->_outlineDictionary->C = null;
+        } else {
+            $components = $color->getComponents();
+            $colorComponentElements = array(new Zend_Pdf_Element_Numeric($components[0]),
+                                            new Zend_Pdf_Element_Numeric($components[1]),
+                                            new Zend_Pdf_Element_Numeric($components[2]));
+            $this->_outlineDictionary->C = new Zend_Pdf_Element_Array($colorComponentElements);
+        }
+
+        return $this;
     }
 
     /**
      * Get outline target.
      *
-     * @return Zend_Pdf_Destination|Zend_Pdf_Action|string
+     * @return Zend_Pdf_Target
      * @thows Zend_Pdf_Exception
      */
     public function getTarget()
@@ -232,14 +239,7 @@ class Zend_Pdf_Outline_Loaded extends Zend_Pdf_Outline
                 throw new Zend_Pdf_Exception('Outline dictionary may contain Dest or A entry, but not both.');
             }
 
-            if ($this->_outlineDictionary->Dest->getType() == Zend_Pdf_Element::TYPE_NAME  ||
-                $this->_outlineDictionary->Dest->getType() == Zend_Pdf_Element::TYPE_STRING) {
-                // It's a named destination
-                /** @todo return named destination object */
-                return $this->_outlineDictionary->Dest->value;
-            } else {
-                return Zend_Pdf_Destination::load($this->_outlineDictionary->Dest);
-            }
+            return Zend_Pdf_Destination::load($this->_outlineDictionary->Dest);
         } else if ($this->_outlineDictionary->A !== null) {
             return Zend_Pdf_Action::load($this->_outlineDictionary->A);
         }
@@ -251,29 +251,31 @@ class Zend_Pdf_Outline_Loaded extends Zend_Pdf_Outline
      * Set outline target.
      * Null means no target
      *
-     * @param Zend_Pdf_Destination|Zend_Pdf_Action|string $target
+     * @param Zend_Pdf_Target|string $target
      * @return Zend_Pdf_Outline
      * @throws Zend_Pdf_Exception
      */
-    public function setTarget($target)
+    public function setTarget($target = null)
     {
-    	$this->_outlineDictionary->touch();
+        $this->_outlineDictionary->touch();
+
+        if (is_string($target)) {
+            require_once 'Zend/Pdf/Destination/Named.php';
+            $target = Zend_Pdf_Destination_Named::create($target);
+        }
 
         if ($target === null) {
             $this->_outlineDictionary->Dest = null;
             $this->_outlineDictionary->A    = null;
-        } else if (is_string($target)) {
-            $this->_outlineDictionary->Dest = new Zend_Pdf_Element_String($target);
-            $this->_outlineDictionary->A    = null;
         } else if ($target instanceof Zend_Pdf_Destination) {
-            $this->_outlineDictionary->Dest = $target->getDestinationArray()->items[0];
+            $this->_outlineDictionary->Dest = $target->getResource();
             $this->_outlineDictionary->A    = null;
         } else if ($target instanceof Zend_Pdf_Action) {
             $this->_outlineDictionary->Dest = null;
-            $this->_outlineDictionary->A    = $target->getDictionary();
+            $this->_outlineDictionary->A    = $target->getResource();
         } else {
             require_once 'Zend/Pdf/Exception.php';
-            throw new Zend_Pdf_Exception('Outline target has to be Zend_Pdf_Destination or Zend_Pdf_Action object, string or null');
+            throw new Zend_Pdf_Exception('Outline target has to be Zend_Pdf_Destination or Zend_Pdf_Action object or string');
         }
 
         return $this;
@@ -288,26 +290,26 @@ class Zend_Pdf_Outline_Loaded extends Zend_Pdf_Outline
      */
     public function setOptions(array $options)
     {
-    	parent::setOptions($options);
+        parent::setOptions($options);
 
         return $this;
     }
 
 
 
-	/**
-	 * Create PDF outline object using specified dictionary
-	 *
-	 * @internal
+    /**
+     * Create PDF outline object using specified dictionary
+     *
+     * @internal
      * @param Zend_Pdf_Element $dictionary (It's actually Dictionary or Dictionary Object or Reference to a Dictionary Object)
      * @param Zend_Pdf_Action  $parentAction
      * @param SplObjectStorage $processedOutlines  List of already processed Outline dictionaries,
      *                                             used to avoid cyclic references
-	 * @return Zend_Pdf_Action
-	 * @throws Zend_Pdf_Exception
-	 */
-	public function __construct(Zend_Pdf_Element $dictionary, SplObjectStorage $processedDictionaries = null)
-	{
+     * @return Zend_Pdf_Action
+     * @throws Zend_Pdf_Exception
+     */
+    public function __construct(Zend_Pdf_Element $dictionary, SplObjectStorage $processedDictionaries = null)
+    {
         if ($dictionary->getType() != Zend_Pdf_Element::TYPE_DICTIONARY) {
             require_once 'Zend/Pdf/Exception.php';
             throw new Zend_Pdf_Exception('$dictionary mast be an indirect dictionary object.');
@@ -328,22 +330,22 @@ class Zend_Pdf_Outline_Loaded extends Zend_Pdf_Outline
 
             $childOutlinesCount = $dictionary->Count->value;
             if ($childOutlinesCount > 0) {
-            	$this->_open = true;
+                $this->_open = true;
             }
             $childOutlinesCount = abs($childOutlinesCount);
 
             $childDictionary = $dictionary->First;
             for ($count = 0; $count < $childOutlinesCount; $count++) {
-            	if ($childDictionary === null) {
-            		require_once 'Zend/Pdf/Exception.php';
+                if ($childDictionary === null) {
+                    require_once 'Zend/Pdf/Exception.php';
                     throw new Zend_Pdf_Exception('Outline childs load error.');
-            	}
+                }
 
-            	if (!$processedDictionaries->contains($childDictionary)) {
+                if (!$processedDictionaries->contains($childDictionary)) {
                     $this->childOutlines[] = new Zend_Pdf_Outline_Loaded($childDictionary, $processedDictionaries);
-            	}
+                }
 
-            	$childDictionary = $childDictionary->Next;
+                $childDictionary = $childDictionary->Next;
             }
 
             if ($childDictionary !== null) {
@@ -353,10 +355,10 @@ class Zend_Pdf_Outline_Loaded extends Zend_Pdf_Outline
 
             $this->_originalChildOutlines = $this->childOutlines;
         }
-	}
+    }
 
     /**
-     * Dump Outline and it's child outlines into PDF structures
+     * Dump Outline and its child outlines into PDF structures
      *
      * Returns dictionary indirect object or reference
      *
@@ -365,65 +367,86 @@ class Zend_Pdf_Outline_Loaded extends Zend_Pdf_Outline
      * @param boolean $updateNavigation  Update navigation flag
      * @param Zend_Pdf_Element $parent   Parent outline dictionary reference
      * @param Zend_Pdf_Element $prev     Previous outline dictionary reference
+     * @param SplObjectStorage $processedOutlines  List of already processed outlines
      * @return Zend_Pdf_Element
+     * @throws Zend_Pdf_Exception
      */
-    public function dumpOutline(Zend_Pdf_ElementFactory_Interface $factory, $updateNavigation, Zend_Pdf_Element $parent, Zend_Pdf_Element $prev = null)
+    public function dumpOutline(Zend_Pdf_ElementFactory_Interface $factory,
+                                                                  $updateNavigation,
+                                                 Zend_Pdf_Element $parent,
+                                                 Zend_Pdf_Element $prev = null,
+                                                 SplObjectStorage $processedOutlines = null)
     {
-    	if ($updateNavigation) {
-    		$this->_outlineDictionary->touch();
-
-    		$this->_outlineDictionary->Parent = $parent;
-    		$this->_outlineDictionary->Prev   = $prev;
-    		$this->_outlineDictionary->Next   = null;
-    	}
-
-    	$updateChildNavigation = false;
-    	if (count($this->_originalChildOutlines) != count($this->childOutlines)) {
-    		// If original and current children arrays have different size then children list was updated
-    		$updateChildNavigation = true;
-    	} else if ( !(array_keys($this->_originalChildOutlines) === array_keys($this->childOutlines)) ) {
-    		// If original and current children arrays have different keys (with a glance to an order) then children list was updated
+        if ($processedOutlines === null) {
+            $processedOutlines = new SplObjectStorage();
+        }
+        $processedOutlines->attach($this);
+
+        if ($updateNavigation) {
+            $this->_outlineDictionary->touch();
+
+            $this->_outlineDictionary->Parent = $parent;
+            $this->_outlineDictionary->Prev   = $prev;
+            $this->_outlineDictionary->Next   = null;
+        }
+
+        $updateChildNavigation = false;
+        if (count($this->_originalChildOutlines) != count($this->childOutlines)) {
+            // If original and current children arrays have different size then children list was updated
+            $updateChildNavigation = true;
+        } else if ( !(array_keys($this->_originalChildOutlines) === array_keys($this->childOutlines)) ) {
+            // If original and current children arrays have different keys (with a glance to an order) then children list was updated
             $updateChildNavigation = true;
-    	} else {
-    		foreach ($this->childOutlines as $key => $childOutline) {
-    			if ($this->_originalChildOutlines[$key] !== $childOutline) {
-    				$updateChildNavigation = true;
-    			}
-    		}
-    	}
-
-    	$lastChild = null;
-    	if ($updateChildNavigation) {
-    		$this->_outlineDictionary->touch();
-    		$this->_outlineDictionary->First = null;
-
-    		foreach ($this->childOutlines as $childOutline) {
-    			if ($lastChild === null) {
+        } else {
+            foreach ($this->childOutlines as $key => $childOutline) {
+                if ($this->_originalChildOutlines[$key] !== $childOutline) {
+                    $updateChildNavigation = true;
+                    break;
+                }
+            }
+        }
+
+        $lastChild = null;
+        if ($updateChildNavigation) {
+            $this->_outlineDictionary->touch();
+            $this->_outlineDictionary->First = null;
+
+            foreach ($this->childOutlines as $childOutline) {
+                if ($processedOutlines->contains($childOutline)) {
+                    require_once 'Zend/Pdf/Exception.php';
+                    throw new Zend_Pdf_Exception('Outlines cyclyc reference is detected.');
+                }
+
+                if ($lastChild === null) {
                     // First pass. Update Outlines dictionary First entry using corresponding value
-                    $lastChild = $childOutline->dumpOutline($factory, $updateChildNavigation, $this->_outlineDictionary);
+                    $lastChild = $childOutline->dumpOutline($factory, $updateChildNavigation, $this->_outlineDictionary, null, $processedOutlines);
                     $this->_outlineDictionary->First = $lastChild;
-    			} else {
+                } else {
                     // Update previous outline dictionary Next entry (Prev is updated within dumpOutline() method)
-    				$childOutlineDictionary = $childOutline->dumpOutline($factory, $updateChildNavigation, $this->_outlineDictionary, $lastChild);
+                    $childOutlineDictionary = $childOutline->dumpOutline($factory, $updateChildNavigation, $this->_outlineDictionary, $lastChild, $processedOutlines);
                     $lastChild->Next = $childOutlineDictionary;
                     $lastChild       = $childOutlineDictionary;
-    			}
-    		}
+                }
+            }
 
-    		$this->_outlineDictionary->Last  = $lastChild;
+            $this->_outlineDictionary->Last  = $lastChild;
 
             if (count($this->childOutlines) != 0) {
                 $this->_outlineDictionary->Count = new Zend_Pdf_Element_Numeric(($this->isOpen()? 1 : -1)*count($this->childOutlines));
             } else {
-            	$this->_outlineDictionary->Count = null;
+                $this->_outlineDictionary->Count = null;
             }
         } else {
             foreach ($this->childOutlines as $childOutline) {
-                $lastChild = $childOutline->dumpOutline($factory, $updateChildNavigation, $this->_outlineDictionary, $lastChild);
+                if ($processedOutlines->contains($childOutline)) {
+                    require_once 'Zend/Pdf/Exception.php';
+                    throw new Zend_Pdf_Exception('Outlines cyclyc reference is detected.');
+                }
+                $lastChild = $childOutline->dumpOutline($factory, $updateChildNavigation, $this->_outlineDictionary, $lastChild, $processedOutlines);
             }
         }
 
-    	return $this->_outlineDictionary;
+        return $this->_outlineDictionary;
     }
 
     public function dump($level = 0)

+ 18 - 6
library/Zend/Pdf/Page.php

@@ -726,6 +726,7 @@ class Zend_Pdf_Page
      * returns array of Zend_Pdf_Resource_Font_Extracted objects
      *
      * @return array
+     * @throws Zend_Pdf_Exception
      */
     public function extractFonts()
     {
@@ -743,21 +744,21 @@ class Zend_Pdf_Page
 
             if (! ($fontDictionary instanceof Zend_Pdf_Element_Reference  ||
                    $fontDictionary instanceof Zend_Pdf_Element_Object) ) {
-                // Font dictionary has to be an indirect object or object reference
-                continue;
+                require_once 'Zend/Pdf/Exception.php';
+                throw new Zend_Pdf_Exception('Font dictionary has to be an indirect object or object reference.');
             }
 
-            $fontResourcesUnique[$fontDictionary->toString($this->_objFactory)] = $fontDictionary;
+            $fontResourcesUnique[spl_object_hash($fontDictionary->getObject())] = $fontDictionary;
         }
 
         $fonts = array();
         require_once 'Zend/Pdf/Exception.php';
-        foreach ($fontResourcesUnique as $resourceReference => $fontDictionary) {
+        foreach ($fontResourcesUnique as $resourceId => $fontDictionary) {
             try {
                 // Try to extract font
                 $extractedFont = new Zend_Pdf_Resource_Font_Extracted($fontDictionary);
 
-                $fonts[$resourceReference] = $extractedFont;
+                $fonts[$resourceId] = $extractedFont;
             } catch (Zend_Pdf_Exception $e) {
                 if ($e->getMessage() != 'Unsupported font type.') {
                     throw $e;
@@ -774,6 +775,7 @@ class Zend_Pdf_Page
      * $fontName should be specified in UTF-8 encoding
      *
      * @return Zend_Pdf_Resource_Font_Extracted|null
+     * @throws Zend_Pdf_Exception
      */
     public function extractFont($fontName)
     {
@@ -784,14 +786,24 @@ class Zend_Pdf_Page
 
         $fontResources = $this->_pageDictionary->Resources->Font;
 
+        $fontResourcesUnique = array();
+
         require_once 'Zend/Pdf/Exception.php';
         foreach ($fontResources->getKeys() as $fontResourceName) {
             $fontDictionary = $fontResources->$fontResourceName;
 
             if (! ($fontDictionary instanceof Zend_Pdf_Element_Reference  ||
                    $fontDictionary instanceof Zend_Pdf_Element_Object) ) {
-                // Font dictionary has to be an indirect object or object reference
+                require_once 'Zend/Pdf/Exception.php';
+                throw new Zend_Pdf_Exception('Font dictionary has to be an indirect object or object reference.');
+            }
+
+            $resourceId = spl_object_hash($fontDictionary->getObject());
+            if (isset($fontResourcesUnique[$resourceId])) {
                 continue;
+            } else {
+                // Mark resource as processed
+                $fontResourcesUnique[$resourceId] = 1;
             }
 
             if ($fontDictionary->BaseFont->value != $fontName) {

+ 1 - 1
library/Zend/Pdf/Parser.php

@@ -135,7 +135,7 @@ class Zend_Pdf_Parser
      */
     public function getPDFVersion()
     {
-    	return $this->_pdfVersion;
+        return $this->_pdfVersion;
     }
 
     /**

+ 1 - 2
library/Zend/Pdf/PhpArray.php

@@ -115,7 +115,7 @@ class Zend_Pdf_PhpArray implements ArrayAccess, Iterator, Countable {
     {
         $this->_items = array();
     }
-    
+
     /**
      * Defined by Countable interface
      *
@@ -125,6 +125,5 @@ class Zend_Pdf_PhpArray implements ArrayAccess, Iterator, Countable {
     {
         return count($this->_items);
     }
-    
 }
 

+ 45 - 0
library/Zend/Pdf/RecursivelyIteratableObjectsContainer.php

@@ -0,0 +1,45 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Pdf
+ * @subpackage Actions
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+/**
+ * Iteratable objects container
+ *
+ * @package    Zend_Pdf
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Pdf_RecursivelyIteratableObjectsContainer implements RecursiveIterator, Countable
+{
+    protected $_objects = array();
+
+    public function __construct(array $objects) { $this->_objects = $objects; }
+
+    public function current()      { return current($this->_objects);            }
+    public function key()          { return key($this->_objects);                }
+    public function next()         { return next($this->_objects);               }
+    public function rewind()       { return reset($this->_objects);              }
+    public function valid()        { return current($this->_objects) !== false;  }
+    public function getChildren()  { return current($this->_objects);            }
+    public function hasChildren()  { return count($this->_objects) > 0;          }
+
+    public function count() { return count($this->_objects); }
+}

+ 7 - 5
library/Zend/Pdf/Target.php

@@ -42,15 +42,17 @@ abstract class Zend_Pdf_Target
      * @throws Zend_Pdf_Exception
      */
     public static function load(Zend_Pdf_Element $resource) {
-        if ($resource->getType() == Zend_Pdf_Element::TYPE_ARRAY) {
-            // Resource is an array, just treat it as an explicit destination array
-            return Zend_Pdf_Destination::load($resource);
-        } else if ($resource->getType() == Zend_Pdf_Element::TYPE_DICTIONARY) {
+        if ($resource->getType() == Zend_Pdf_Element::TYPE_DICTIONARY) {
             // Load destination as appropriate action
             return Zend_Pdf_Action::load($resource);
+        } else if ($resource->getType() == Zend_Pdf_Element::TYPE_ARRAY  ||
+                   $resource->getType() == Zend_Pdf_Element::TYPE_NAME   ||
+                   $resource->getType() == Zend_Pdf_Element::TYPE_STRING) {
+            // Resource is an array, just treat it as an explicit destination array
+            return Zend_Pdf_Destination::load($resource);
         } else {
             require_once 'Zend/Pdf/Exception.php';
-            throw new Zend_Pdf_Exception( 'PDF named destination entry must be an array or dictionary.' );
+            throw new Zend_Pdf_Exception( 'Wrong resource type.' );
         }
     }
 

+ 50 - 82
tests/Zend/Pdf/ActionTest.php

@@ -14,6 +14,12 @@ require_once 'Zend/Pdf/ElementFactory.php';
 /** Zend_Pdf */
 require_once 'Zend/Pdf.php';
 
+/** Zend_Pdf_RecursivelyIteratableObjectsContainer */
+require_once 'Zend/Pdf/RecursivelyIteratableObjectsContainer.php';
+
+/** Zend_Pdf_ElementFactory */
+require_once 'Zend/Pdf/ElementFactory.php';
+
 
 /** PHPUnit Test Case */
 require_once 'PHPUnit/Framework/TestCase.php';
@@ -140,16 +146,21 @@ class Zend_Pdf_ActionTest extends PHPUnit_Framework_TestCase
 
         $action = Zend_Pdf_Action::load($dictionary);
 
-        $this->assertEquals(20, count($action->getAllActions()));
+        $actionsCount = 0;
+        $iterator = new RecursiveIteratorIterator(new Zend_Pdf_RecursivelyIteratableObjectsContainer(array($action)),
+                                                  RecursiveIteratorIterator::SELF_FIRST);
+        foreach ($iterator as $chainedAction) {
+        	$actionsCount++;
+        }
 
-        $action->clean();
+        $this->assertEquals(20, $actionsCount);
     }
 
     public function testExtract()
     {
         $dictionary = new Zend_Pdf_Element_Dictionary();
         $dictionary->Type = new Zend_Pdf_Element_Name('Action');
-        $dictionary->S    = new Zend_Pdf_Element_Name('GoTo');
+        $dictionary->S    = new Zend_Pdf_Element_Name('GoToR');
         $dictionary->D    = new Zend_Pdf_Element_String('SomeNamedDestination');
 
         $action2Dictionary = new Zend_Pdf_Element_Dictionary();
@@ -255,86 +266,44 @@ class Zend_Pdf_ActionTest extends PHPUnit_Framework_TestCase
 
         $action = Zend_Pdf_Action::load($dictionary);
 
-        foreach ($action->getAllActions() as $action) {
-            if ($action instanceof Zend_Pdf_Action_Thread) {
-                $root = $action->extract();
+        $actionsToClean        = array();
+        $deletionCandidateKeys = array();
+        $iterator = new RecursiveIteratorIterator($action, RecursiveIteratorIterator::SELF_FIRST);
+        foreach ($iterator as $chainedAction) {
+            if ($chainedAction instanceof Zend_Pdf_Action_GoTo) {
+                $actionsToClean[]        = $iterator->getSubIterator();
+                $deletionCandidateKeys[] = $iterator->getSubIterator()->key();
             }
         }
-        $this->assertTrue($root instanceof Zend_Pdf_Action_GoTo);
-        $this->assertEquals(18, count($root->getAllActions()));
-
-        foreach ($root->getAllActions() as $action) {
-            if ($action instanceof Zend_Pdf_Action_Goto) {
-                $root = $action->extract();
-            }
+        foreach ($actionsToClean as $id => $action) {
+            unset($action->next[$deletionCandidateKeys[$id]]);
         }
-        $this->assertTrue($root instanceof Zend_Pdf_Action_GoToR);
-        $this->assertEquals(16, count($root->getAllActions()));
-
-        $root->rebuildSubtree();
-
-        $this->assertEquals(
-            $root->getResource()->toString(),
-            '<</Type /Action /S /GoToR '
-            . "/Next [<</Type /Action /S /GoToE >> <</Type /Action /S /Launch >> <</Type /Action /S /URI >> <</Type /Action /S /Sound >> <</Type /Action /S /Movie >> \n"
-            .        "<</Type /Action /S /Hide >> <</Type /Action /S /Named >> <</Type /Action /S /SubmitForm >> <</Type /Action /S /ResetForm >> <</Type /Action /S /ImportData >> \n"
-            .        "<</Type /Action /S /JavaScript >> <</Type /Action /S /SetOCGState >> <</Type /Action /S /Rendition >> <</Type /Action /S /Trans >> \n"
-            .        '<</Type /Action /S /GoTo3DView >> ] >>');
-
-        $root->clean();
-    }
-
-    public function testAttach()
-    {
-        $action1Dictionary = new Zend_Pdf_Element_Dictionary();
-        $action1Dictionary->Type = new Zend_Pdf_Element_Name('Action');
-        $action1Dictionary->S    = new Zend_Pdf_Element_Name('GoTo');
-        $action1Dictionary->D    = new Zend_Pdf_Element_String('Destination 1');
-        $action1 = Zend_Pdf_Action::load($action1Dictionary);
-
-
-        $action2Dictionary = new Zend_Pdf_Element_Dictionary();
-        $action2Dictionary->Type = new Zend_Pdf_Element_Name('Action');
-        $action2Dictionary->S    = new Zend_Pdf_Element_Name('Thread');
-        $action2Dictionary->D    = new Zend_Pdf_Element_String('Destination 2');
-        $action2Dictionary->Next = new Zend_Pdf_Element_Array();
-
-        $leafAction = new Zend_Pdf_Element_Dictionary();
-        $leafAction->Type = new Zend_Pdf_Element_Name('Action');
-        $leafAction->S    = new Zend_Pdf_Element_Name('GoTo');
-        $leafAction->D    = new Zend_Pdf_Element_String('Destination 3');
-        $action2Dictionary->Next->items[] = $leafAction;
-
-        $leafAction = new Zend_Pdf_Element_Dictionary();
-        $leafAction->Type = new Zend_Pdf_Element_Name('Action');
-        $leafAction->S    = new Zend_Pdf_Element_Name('GoToR');
-        $action2Dictionary->Next->items[] = $leafAction;
-
-        $action2 = Zend_Pdf_Action::load($action2Dictionary);
-
-        $action1->attach($action2);
-        $action1->rebuildSubtree();
+        $actionsCount = 0;
+        $iterator = new RecursiveIteratorIterator(new Zend_Pdf_RecursivelyIteratableObjectsContainer(array($action)),
+                                                  RecursiveIteratorIterator::SELF_FIRST);
+        foreach ($iterator as $chainedAction) {
+            $actionsCount++;
+        }
+        $this->assertEquals(18, $actionsCount);
 
+        $action->dumpAction(new Zend_Pdf_ElementFactory(1));
         $this->assertEquals(
-            $action1->getResource()->toString(),
-            '<</Type /Action /S /GoTo /D (Destination 1) '
-            . '/Next <</Type /Action /S /Thread /D (Destination 2) '
-            .         '/Next [<</Type /Action /S /GoTo /D (Destination 3) >> <</Type /Action /S /GoToR >> ] >> >>');
-
-        $action1->clean();
+            $action->getResource()->toString(),
+            '<</Type /Action '
+            . '/S /Thread '
+            . '/D (NamedDestination 2) '
+            . '/Next [1 0 R 2 0 R 3 0 R 4 0 R 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R 16 0 R 17 0 R ] >>');
     }
 
     public function testCreate()
     {
     	$action1 = Zend_Pdf_Action_GoTo::create('SomeNamedDestination');
-    	$action1->attach(Zend_Pdf_Action_GoTo::create('AnotherNamedDestination'));
+    	$action1->next[] = Zend_Pdf_Action_GoTo::create('AnotherNamedDestination');
 
-    	$action1->rebuildSubtree();
+        $action1->dumpAction(new Zend_Pdf_ElementFactory(1));
 
     	$this->assertEquals($action1->getResource()->toString(),
-    	                    '<</Type /Action /S /GoTo /D (SomeNamedDestination) /Next <</Type /Action /S /GoTo /D (AnotherNamedDestination) >> >>');
-
-    	$action1->clean();
+    	                    '<</Type /Action /S /GoTo /D (SomeNamedDestination) /Next 1 0 R >>');
     }
 
     public function testCreate1()
@@ -347,12 +316,11 @@ class Zend_Pdf_ActionTest extends PHPUnit_Framework_TestCase
     	$destination = Zend_Pdf_Destination_Fit::create($page2);
 
         $action = Zend_Pdf_Action_GoTo::create($destination);
-        $action->rebuildSubtree();
+
+        $action->dumpAction(new Zend_Pdf_ElementFactory(1));
 
         $this->assertEquals($action->getResource()->toString(),
                             '<</Type /Action /S /GoTo /D [4 0 R /Fit ] >>');
-
-        $action->clean();
     }
 
     public function testGetDestination()
@@ -364,9 +332,7 @@ class Zend_Pdf_ActionTest extends PHPUnit_Framework_TestCase
 
         $action = Zend_Pdf_Action::load($dictionary);
 
-        $this->assertEquals($action->getDestination(), 'SomeNamedDestination');
-
-        $action->clean();
+        $this->assertEquals($action->getDestination()->getName(), 'SomeNamedDestination');
     }
 
     public function testGetDestination2()
@@ -374,14 +340,16 @@ class Zend_Pdf_ActionTest extends PHPUnit_Framework_TestCase
         $pdf = new Zend_Pdf();
         $page1 = $pdf->newPage(Zend_Pdf_Page::SIZE_A4);
         $page2 = $pdf->newPage(Zend_Pdf_Page::SIZE_A4);
+        $page3 = $pdf->newPage(Zend_Pdf_Page::SIZE_A4);  // Page created, but not included into pages list
 
-        require_once 'Zend/Pdf/Destination/Fit.php';
-        $destination = Zend_Pdf_Destination_Fit::create($page2);
+        $pdf->pages[] = $page1;
+        $pdf->pages[] = $page2;
 
-        $action = Zend_Pdf_Action_GoTo::create($destination);
-
-        $this->assertTrue($action->getDestination() == $destination);
+        require_once 'Zend/Pdf/Destination/Fit.php';
+        $action1 = Zend_Pdf_Action_GoTo::create(Zend_Pdf_Destination_Fit::create($page2));
+        $action2 = Zend_Pdf_Action_GoTo::create(Zend_Pdf_Destination_Fit::create($page3));
 
-        $action->clean();
+        $this->assertTrue($pdf->resolveDestination($action1->getDestination()) === $page2);
+        $this->assertTrue($pdf->resolveDestination($action2->getDestination()) === null);
     }
 }

+ 2 - 2
tests/Zend/Pdf/DestinationTest.php

@@ -256,11 +256,11 @@ class Zend_Pdf_DestinationTest extends PHPUnit_Framework_TestCase
         $this->assertTrue($destination instanceof Zend_Pdf_Destination_Zoom);
         $this->assertEquals($destination->getResource()->toString(), '[4 0 R /XYZ 0 842 0.5 ]');
 
-        $destination = Zend_Pdf_Destination_Fit::create($page2->getPageDictionary());
+        $destination = Zend_Pdf_Destination_Fit::create($page2);
         $this->assertTrue($destination instanceof Zend_Pdf_Destination_Fit);
         $this->assertEquals($destination->getResource()->toString(), '[4 0 R /Fit ]');
 
-        $destination = Zend_Pdf_Destination_FitHorizontally::create($page2->getPageDictionary(), 842);
+        $destination = Zend_Pdf_Destination_FitHorizontally::create($page2, 842);
         $this->assertTrue($destination instanceof Zend_Pdf_Destination_FitHorizontally);
         $this->assertEquals($destination->getResource()->toString(), '[4 0 R /FitH 842 ]');
 

+ 4 - 5
tests/Zend/Pdf/NamedDestinationsTest.php

@@ -35,7 +35,6 @@ class Zend_Pdf_NamedDestinationsTest extends PHPUnit_Framework_TestCase
         $pdf->pages[] = $page2;
 
 
-        $this->assertTrue(count($pdf->getNamedActions()) == 0);
         $this->assertTrue(count($pdf->getNamedDestinations()) == 0);
 
         require_once 'Zend/Pdf/Destination/Fit.php';
@@ -43,9 +42,9 @@ class Zend_Pdf_NamedDestinationsTest extends PHPUnit_Framework_TestCase
         $destination2 = Zend_Pdf_Destination_Fit::create($page2);
         $action1 = Zend_Pdf_Action_GoTo::create($destination1);
 
-        $pdf->setNamedAction('GoToPage1', $action1);
-        $this->assertTrue($pdf->getNamedAction('GoToPage1') === $action1);
-        $this->assertTrue($pdf->getNamedAction('GoToPage9') === null);
+        $pdf->setNamedDestination('GoToPage1', $action1);
+        $this->assertTrue($pdf->getNamedDestination('GoToPage1') === $action1);
+        $this->assertTrue($pdf->getNamedDestination('GoToPage9') === null);
 
         $pdf->setNamedDestination('Page2', $destination2);
         $this->assertTrue($pdf->getNamedDestination('Page2') === $destination2);
@@ -56,7 +55,7 @@ class Zend_Pdf_NamedDestinationsTest extends PHPUnit_Framework_TestCase
         $pdf->setNamedDestination('Page9_1', Zend_Pdf_Destination_Fit::create(9)); // will be egnored
 
         $action3 = Zend_Pdf_Action_GoTo::create(Zend_Pdf_Destination_Fit::create($page3));
-        $pdf->setNamedAction('GoToPage3', $action3);
+        $pdf->setNamedDestination('GoToPage3', $action3);
 
         $this->assertTrue(strpos($pdf->render(), '[(GoToPage1) <</Type /Action /S /GoTo /D [3 0 R /Fit ] >> (Page1) [3 0 R /Fit ] (Page1_1) [1 /Fit ] (Page2) [4 0 R /Fit ] ]') !== false);
     }

+ 1 - 1
tests/Zend/Pdf/ProcessingTest.php

@@ -318,7 +318,7 @@ class Zend_Pdf_ProcessingTest extends PHPUnit_Framework_TestCase
 
         // Test accessing protected variables and their default content
         $this->assertEquals(array(), $pdf->_originalProperties);
-        $this->assertEquals(array(), $pdf->_namedActions);
+        $this->assertEquals(array(), $pdf->_namedTargets);
 
         $pdfpage = new ExtendedZendPdfPage(Zend_Pdf_Page::SIZE_A4);
         // Test accessing protected variables and their default content