|
|
@@ -79,6 +79,15 @@ require_once 'Zend/Pdf/Action.php';
|
|
|
/** Zend_Pdf_Destination */
|
|
|
require_once 'Zend/Pdf/Destination.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_Destination */
|
|
|
require_once 'Zend/Pdf/Exception.php';
|
|
|
|
|
|
@@ -174,6 +183,29 @@ class Zend_Pdf
|
|
|
protected $_namedDestinations = array();
|
|
|
|
|
|
/**
|
|
|
+ * Document outlines
|
|
|
+ *
|
|
|
+ * @var array - array of Zend_Pdf_Outline objects
|
|
|
+ */
|
|
|
+ public $outlines = array();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Original document outlines list
|
|
|
+ * Used to track outlines update
|
|
|
+ *
|
|
|
+ * @var array - array of Zend_Pdf_Outline objects
|
|
|
+ */
|
|
|
+ protected $_originalOutlines = array();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Original document outlines open elements count
|
|
|
+ * Used to track outlines update
|
|
|
+ *
|
|
|
+ * @var integer
|
|
|
+ */
|
|
|
+ protected $_originalOpenOutlinesCount = 0;
|
|
|
+
|
|
|
+ /**
|
|
|
* Pdf trailer (last or just created)
|
|
|
*
|
|
|
* @var Zend_Pdf_Trailer
|
|
|
@@ -281,28 +313,6 @@ class Zend_Pdf
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Parse destination structure (array or dictionary) and
|
|
|
- * return it as a Zend_Pdf_Destination or Zend_Pdf_Action object
|
|
|
- *
|
|
|
- * $param Zend_Pdf_Element $destination
|
|
|
- * @return Zend_Pdf_Destination|
|
|
|
- * @throws Zend_Pdf_Exception
|
|
|
- */
|
|
|
- protected function _loadDestination(Zend_Pdf_Element $destination) {
|
|
|
- if ($destination->getType() == Zend_Pdf_Element::TYPE_ARRAY) {
|
|
|
- // Destination is an array, just treat it as an explicit destination array
|
|
|
- return Zend_Pdf_Destination::load($destination);
|
|
|
- } else if ($destination->getType() == Zend_Pdf_Element::TYPE_DICTIONARY) {
|
|
|
- // Load destination as appropriate action
|
|
|
- return Zend_Pdf_Action::load($destination);
|
|
|
- } else {
|
|
|
- require_once 'Zend/Pdf/Exception.php';
|
|
|
- throw new Zend_Pdf_Exception( 'PDF named destination entry must be an array or dictionary.' );
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- /**
|
|
|
* Creates or loads PDF document.
|
|
|
*
|
|
|
* If $source is null, then it creates a new document.
|
|
|
@@ -339,6 +349,9 @@ class Zend_Pdf
|
|
|
$this->_loadPages($this->_trailer->Root->Pages);
|
|
|
}
|
|
|
|
|
|
+ $this->_loadNamedDestinations($this->_trailer->Root, $this->_parser->getPDFVersion());
|
|
|
+ $this->_loadOutlines($this->_trailer->Root);
|
|
|
+
|
|
|
if ($this->_trailer->Info !== null) {
|
|
|
$this->properties = $this->_trailer->Info->toPhp();
|
|
|
|
|
|
@@ -365,77 +378,6 @@ class Zend_Pdf
|
|
|
|
|
|
$this->_originalProperties = $this->properties;
|
|
|
}
|
|
|
-
|
|
|
- // Collect named destinations (exclude not referenced pages)
|
|
|
- $root = $this->_trailer->Root;
|
|
|
-
|
|
|
- $pdfHeaderVersion = $this->_parser->getPDFVersion();
|
|
|
- if ($root->Version !== null && version_compare($root->Version->value, $pdfHeaderVersion, '>')) {
|
|
|
- $versionIs_1_2_plus = version_compare($root->Version->value, '1.1', '>');
|
|
|
- } else {
|
|
|
- $versionIs_1_2_plus = version_compare($pdfHeaderVersion, '1.1', '>');
|
|
|
- }
|
|
|
-
|
|
|
- if ($versionIs_1_2_plus) {
|
|
|
- // 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 = $this->_loadDestination($leafNode->items[$count*2 + 1]);
|
|
|
-
|
|
|
- if ($destination instanceof Zend_Pdf_Action) {
|
|
|
- $this->_namedActions[$destKey] = $destination;
|
|
|
- } else {
|
|
|
- $this->_namedDestinations[$destKey] = $destination;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- // PDF version is 1.1 (or earlier)
|
|
|
- // Look for Destinations sructure at Dest entry of document catalog
|
|
|
- if ($root->Dests !== null) {
|
|
|
- if ($root->Dests->getType() != Zend_Pdf_Element::TYPE_DICTIONARY) {
|
|
|
- require_once 'Zend/Pdf/Exception.php';
|
|
|
- throw new Zend_Pdf_Exception( 'Document catalog Dests entry must be a dictionary.' );
|
|
|
- }
|
|
|
-
|
|
|
- foreach ($root->Dests->getKeys() as $destKey) {
|
|
|
- $destination = $this->_loadDestination($root->Dests->$destKey);
|
|
|
-
|
|
|
- if ($destination instanceof Zend_Pdf_Action) {
|
|
|
- $this->_namedActions[$destKey] = $destination;
|
|
|
- } else {
|
|
|
- $this->_namedDestinations[$destKey] = $destination;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
} else {
|
|
|
$this->_pdfHeaderVersion = Zend_Pdf::PDF_VERSION;
|
|
|
|
|
|
@@ -481,9 +423,9 @@ class Zend_Pdf
|
|
|
*/
|
|
|
public function __destruct()
|
|
|
{
|
|
|
- foreach ($this->_namedActions as $action) {
|
|
|
- $action->clean();
|
|
|
- }
|
|
|
+ foreach ($this->_namedActions as $action) {
|
|
|
+ $action->clean();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -562,7 +504,8 @@ class Zend_Pdf
|
|
|
* If any attribute or dependant object is an indirect object, then it's still
|
|
|
* shared between pages.
|
|
|
*/
|
|
|
- if ($attributes[$property] instanceof Zend_Pdf_Element_Object) {
|
|
|
+ if ($attributes[$property] instanceof Zend_Pdf_Element_Object ||
|
|
|
+ $attributes[$property] instanceof Zend_Pdf_Element_Reference) {
|
|
|
$child->$property = $attributes[$property];
|
|
|
} else {
|
|
|
$child->$property = $this->_objFactory->newObject($attributes[$property]);
|
|
|
@@ -575,6 +518,125 @@ class Zend_Pdf
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * Load named destinations recursively
|
|
|
+ *
|
|
|
+ * @param Zend_Pdf_Element_Reference $root Document catalog entry
|
|
|
+ * @param string $pdfHeaderVersion
|
|
|
+ * @throws Zend_Pdf_Exception
|
|
|
+ */
|
|
|
+ protected function _loadNamedDestinations(Zend_Pdf_Element_Reference $root, $pdfHeaderVersion)
|
|
|
+ {
|
|
|
+ if ($root->Version !== null && version_compare($root->Version->value, $pdfHeaderVersion, '>')) {
|
|
|
+ $versionIs_1_2_plus = version_compare($root->Version->value, '1.1', '>');
|
|
|
+ } else {
|
|
|
+ $versionIs_1_2_plus = version_compare($pdfHeaderVersion, '1.1', '>');
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($versionIs_1_2_plus) {
|
|
|
+ // 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // PDF version is 1.1 (or earlier)
|
|
|
+ // Look for Destinations sructure at Dest entry of document catalog
|
|
|
+ if ($root->Dests !== null) {
|
|
|
+ if ($root->Dests->getType() != Zend_Pdf_Element::TYPE_DICTIONARY) {
|
|
|
+ require_once 'Zend/Pdf/Exception.php';
|
|
|
+ throw new Zend_Pdf_Exception('Document catalog Dests entry must be a dictionary.');
|
|
|
+ }
|
|
|
+
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Load outlines recursively
|
|
|
+ *
|
|
|
+ * @param Zend_Pdf_Element_Reference $root Document catalog entry
|
|
|
+ */
|
|
|
+ protected function _loadOutlines(Zend_Pdf_Element_Reference $root)
|
|
|
+ {
|
|
|
+ if ($root->Outlines === null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($root->Outlines->getType() != Zend_Pdf_Element::TYPE_DICTIONARY) {
|
|
|
+ require_once 'Zend/Pdf/Exception.php';
|
|
|
+ throw new Zend_Pdf_Exception('Document catalog Outlines entry must be a dictionary.');
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($root->Outlines->Type !== null && $root->Outlines->Type->value != 'Outlines') {
|
|
|
+ require_once 'Zend/Pdf/Exception.php';
|
|
|
+ throw new Zend_Pdf_Exception('Outlines Type entry must be an \'Outlines\' string.');
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($root->Outlines->First === null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ $outlineDictionary = $root->Outlines->First;
|
|
|
+ $processedDictionaries = new SplObjectStorage();
|
|
|
+ while ($outlineDictionary !== null && !$processedDictionaries->contains($outlineDictionary)) {
|
|
|
+ $processedDictionaries->attach($outlineDictionary);
|
|
|
+
|
|
|
+ $this->outlines[] = new Zend_Pdf_Outline_Loaded($outlineDictionary);
|
|
|
+
|
|
|
+ $outlineDictionary = $outlineDictionary->Next;
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->_originalOutlines = $this->outlines;
|
|
|
+
|
|
|
+ if ($root->Outlines->Count !== null) {
|
|
|
+ $this->_originalOpenOutlinesCount = $root->Outlines->Count->value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
* Orginize pages to tha pages tree structure.
|
|
|
*
|
|
|
* @todo atomatically attach page to the document, if it's not done yet.
|
|
|
@@ -590,7 +652,7 @@ class Zend_Pdf
|
|
|
$pagesContainer->touch();
|
|
|
$pagesContainer->Kids->items->clear();
|
|
|
|
|
|
- $pageDictionaries = new SplObjectStorage();
|
|
|
+ $pageDictionaries = array();
|
|
|
foreach ($this->pages as $page ) {
|
|
|
$page->render($this->_objFactory);
|
|
|
|
|
|
@@ -600,8 +662,8 @@ class Zend_Pdf
|
|
|
|
|
|
$pagesContainer->Kids->items[] = $pageDictionary;
|
|
|
|
|
|
- // Collect page dictionary
|
|
|
- $pageDictionaries->attach($pageDictionary);
|
|
|
+ // Collect page dictionary references
|
|
|
+ $pageDictionaries[$pageDictionary->toString()] = 1;
|
|
|
}
|
|
|
|
|
|
$pagesContainer->Count->touch();
|
|
|
@@ -609,10 +671,10 @@ class Zend_Pdf
|
|
|
|
|
|
// Refresh named actions list
|
|
|
foreach ($this->_namedActions as $name => $namedAction) {
|
|
|
- $rootAction = $namedAction;
|
|
|
+ $rootAction = $namedAction;
|
|
|
|
|
|
- // Walk through chained actions
|
|
|
- foreach ($namedAction->getAllActions() as $chainedAction) {
|
|
|
+ // 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) {
|
|
|
@@ -620,42 +682,77 @@ class Zend_Pdf
|
|
|
throw new Zend_Pdf_Exception('PDF named actions (destinations) must refer target as an explicit destination.');
|
|
|
}
|
|
|
|
|
|
- $target = $destination->getTarget();
|
|
|
- if ($target instanceof Zend_Pdf_Element) {
|
|
|
- if (!$pageDictionaries->contains($target)) {
|
|
|
+ $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();
|
|
|
}
|
|
|
- } else if ($target > count($this->pages) ) {
|
|
|
- $rootAction = $chainedAction->extract();
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
- if ($rootAction === null) {
|
|
|
- unset($this->_namedActions[$name]);
|
|
|
- } else {
|
|
|
- $rootAction->rebuildSubtree();
|
|
|
- $this->_namedActions[$name] = $rootAction;
|
|
|
- }
|
|
|
+ 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->getTarget();
|
|
|
-
|
|
|
- if ($target instanceof Zend_Pdf_Element) {
|
|
|
- if (!$pageDictionaries->contains($target)) {
|
|
|
+ $target = $destination->getResource()->items[0];
|
|
|
+ if ($target->getType() == Zend_Pdf_Element::TYPE_NUMERIC) {
|
|
|
+ if ($target->value > count($this->pages) ) {
|
|
|
+ unset($this->_namedDestinations[$name]);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (!isset($pageDictionaries[$target->toString()])) {
|
|
|
unset($this->_namedDestinations[$name]);
|
|
|
- }
|
|
|
- } else if ($target > count($this->pages) ) {
|
|
|
- unset($this->_namedDestinations[$name]);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // Refresh outlines
|
|
|
+ $iterator = new RecursiveIteratorIterator(new Zend_Pdf_Outline_Container($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])) {
|
|
|
+ $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 */
|
|
|
+ } else {
|
|
|
+ require_once 'Zend/Pdf/Exception.php';
|
|
|
+ throw new Zend_Pdf_Exception('Wrong outline target.');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
$openAction = $this->getOpenAction();
|
|
|
if ($openAction !== null) {
|
|
|
- if ($openAction instanceof Zend_Pdf_Action) {
|
|
|
+ if ($openAction instanceof Zend_Pdf_Action) {
|
|
|
$rootAction = $openAction;
|
|
|
|
|
|
// Walk through chained actions
|
|
|
@@ -663,48 +760,51 @@ class Zend_Pdf
|
|
|
if ($chainedAction instanceof Zend_Pdf_Action_GoTo) {
|
|
|
$destination = $chainedAction->getDestination();
|
|
|
if (!$destination instanceof Zend_Pdf_Destination) {
|
|
|
- // Look for $destination within named destinations
|
|
|
+ // Look for $destination within named destinations
|
|
|
if (!isset($this->_namedActions[$destination]) && !isset($this->_namedDestinations[$destination])) {
|
|
|
- $rootAction = $chainedAction->extract();
|
|
|
+ $rootAction = $chainedAction->extract();
|
|
|
}
|
|
|
} else {
|
|
|
- // Destination is Zend_Pdf_Destination object
|
|
|
- $target = $destination->getTarget();
|
|
|
- if ($target instanceof Zend_Pdf_Element) {
|
|
|
- // which refers some page dictionary object
|
|
|
- // (check if it's within a collected dictionaries for current document)
|
|
|
- if (!$pageDictionaries->contains($target)) {
|
|
|
+ // 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();
|
|
|
}
|
|
|
- } else if ($target > count($this->pages) ) {
|
|
|
- // it's a page number, check if we have enough pages
|
|
|
- $rootAction = $chainedAction->extract();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if ($rootAction !== null) {
|
|
|
- $rootAction->rebuildSubtree();
|
|
|
- $this->setOpenAction($rootAction);
|
|
|
+ $rootAction->rebuildSubtree();
|
|
|
+ $this->setOpenAction($rootAction);
|
|
|
$rootAction->clean();
|
|
|
} else {
|
|
|
$this->setOpenAction(null);
|
|
|
}
|
|
|
- } else if ($openAction instanceof Zend_Pdf_Destination) {
|
|
|
- $target = $openAction->getTarget();
|
|
|
-
|
|
|
- if ($target instanceof Zend_Pdf_Element) {
|
|
|
- if (!$pageDictionaries->contains($target)) {
|
|
|
+ } 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);
|
|
|
}
|
|
|
- } else if ($target > count($this->pages) ) {
|
|
|
- $this->setOpenAction(null);
|
|
|
}
|
|
|
- } else {
|
|
|
+ } else {
|
|
|
require_once 'Zend/Pdf/Exception.php';
|
|
|
throw new Zend_Pdf_Exception('OpenAction has to be either PDF Action or Destination.');
|
|
|
- }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -721,16 +821,14 @@ class Zend_Pdf
|
|
|
$destArray = $this->_objFactory->newObject(new Zend_Pdf_Element_Array());
|
|
|
$destArrayItems = $destArray->items;
|
|
|
foreach ($namedDestinations as $name => $destination) {
|
|
|
- $destArrayItems[] = new Zend_Pdf_Element_String($name);
|
|
|
+ $destArrayItems[] = new Zend_Pdf_Element_String($name);
|
|
|
|
|
|
- if ($destination instanceof Zend_Pdf_Action) {
|
|
|
- $destArrayItems[] = $destination->getDictionary();
|
|
|
- } else if ($destination instanceof Zend_Pdf_Destination) {
|
|
|
- $destArrayItems[] = $destination->getDestinationArray();
|
|
|
- } else {
|
|
|
+ if ($destination instanceof Zend_Pdf_Target) {
|
|
|
+ $destArrayItems[] = $destination->getResource();
|
|
|
+ } else {
|
|
|
require_once 'Zend/Pdf/Exception.php';
|
|
|
- throw new Zend_Pdf_Exception('PDF named destinations must be Zend_Pdf_Action or Zend_Pdf_Destination objects.');
|
|
|
- }
|
|
|
+ throw new Zend_Pdf_Exception('PDF named destinations must be a Zend_Pdf_Target object.');
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
$DestTree = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
|
|
|
@@ -748,6 +846,72 @@ class Zend_Pdf
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * Dump outlines recursively
|
|
|
+ */
|
|
|
+ protected function _dumpOutlines()
|
|
|
+ {
|
|
|
+ $root = $this->_trailer->Root;
|
|
|
+
|
|
|
+ if ($root->Outlines === null) {
|
|
|
+ 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 {
|
|
|
+ $updateOutlinesNavigation = false;
|
|
|
+ if (count($this->_originalOutlines) != count($this->outlines)) {
|
|
|
+ // If original and current outlines arrays have different size then outlines list was updated
|
|
|
+ $updateOutlinesNavigation = true;
|
|
|
+ } else if ( !(array_keys($this->_originalOutlines) === array_keys($this->outlines)) ) {
|
|
|
+ // If original and current outlines arrays have different keys (with a glance to an order) then outlines list was updated
|
|
|
+ $updateOutlinesNavigation = true;
|
|
|
+ } else {
|
|
|
+ foreach ($this->outlines as $key => $outline) {
|
|
|
+ if ($this->_originalOutlines[$key] !== $outline) {
|
|
|
+ $updateOutlinesNavigation = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $lastOutline = null;
|
|
|
+ $openOutlinesCount = 0;
|
|
|
+ if ($updateOutlinesNavigation) {
|
|
|
+ $root->Outlines->touch();
|
|
|
+ $root->Outlines->First = null;
|
|
|
+
|
|
|
+ foreach ($this->outlines as $outline) {
|
|
|
+ if ($lastOutline === null) {
|
|
|
+ // First pass. Update Outlines dictionary First entry using corresponding value
|
|
|
+ $lastOutline = $outline->dumpOutline($this->_objFactory, $updateOutlinesNavigation, $root->Outlines);
|
|
|
+ $root->Outlines->First = $lastOutline;
|
|
|
+ } else {
|
|
|
+ // Update previous outline dictionary Next entry (Prev is updated within dumpOutline() method)
|
|
|
+ $currentOutlineDictionary = $outline->dumpOutline($this->_objFactory, $updateOutlinesNavigation, $root->Outlines, $lastOutline);
|
|
|
+ $lastOutline->Next = $currentOutlineDictionary;
|
|
|
+ $lastOutline = $currentOutlineDictionary;
|
|
|
+ }
|
|
|
+ $openOutlinesCount += $outline->openOutlinesCount();
|
|
|
+ }
|
|
|
+
|
|
|
+ $root->Outlines->Last = $lastOutline;
|
|
|
+ } else {
|
|
|
+ foreach ($this->outlines as $outline) {
|
|
|
+ $lastOutline = $outline->dumpOutline($this->_objFactory, $updateOutlinesNavigation, $root->Outlines, $lastOutline);
|
|
|
+ $openOutlinesCount += $outline->openOutlinesCount();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($openOutlinesCount != $this->_originalOpenOutlinesCount) {
|
|
|
+ $root->Outlines->touch;
|
|
|
+ $root->Outlines->Count = new Zend_Pdf_Element_Numeric($openOutlinesCount);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
* Create page object, attached to the PDF document.
|
|
|
* Method signatures:
|
|
|
*
|
|
|
@@ -828,9 +992,9 @@ class Zend_Pdf
|
|
|
public function getOpenAction()
|
|
|
{
|
|
|
if ($this->_trailer->Root->OpenAction !== null) {
|
|
|
- return $this->_loadDestination($this->_trailer->Root->OpenAction);
|
|
|
+ return Zend_Pdf_Target::load($this->_trailer->Root->OpenAction);
|
|
|
} else {
|
|
|
- return null;
|
|
|
+ return null;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -845,15 +1009,13 @@ class Zend_Pdf
|
|
|
$root = $this->_trailer->Root;
|
|
|
$root->touch();
|
|
|
|
|
|
- if ($openAction instanceof Zend_Pdf_Destination) {
|
|
|
- $root->OpenAction = $openAction->getDestinationArray();
|
|
|
- } if ($openAction instanceof Zend_Pdf_Action) {
|
|
|
- $root->OpenAction = $openAction->getDictionary();
|
|
|
- } if ($openAction === null) {
|
|
|
- $root->OpenAction = null;
|
|
|
+ if ($openAction === null) {
|
|
|
+ $root->OpenAction = null;
|
|
|
+ } else if ($openAction instanceof Zend_Pdf_Target) {
|
|
|
+ $root->OpenAction = $openAction->getResource();
|
|
|
} else {
|
|
|
require_once 'Zend/Pdf/Exception.php';
|
|
|
- throw new Zend_Pdf_Exception('Open action must be a Zend_Pdf_Destination or Zend_Pdf_Action objects or null.');
|
|
|
+ throw new Zend_Pdf_Exception('Open action must be a Zend_Pdf_Target or null.');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -877,11 +1039,11 @@ class Zend_Pdf
|
|
|
*/
|
|
|
public function getNamedAction($name)
|
|
|
{
|
|
|
- if (isset($this->_namedActions[$name])) {
|
|
|
- return $this->_namedActions[$name];
|
|
|
- } else {
|
|
|
- return null;
|
|
|
- }
|
|
|
+ if (isset($this->_namedActions[$name])) {
|
|
|
+ return $this->_namedActions[$name];
|
|
|
+ } else {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -892,22 +1054,22 @@ class Zend_Pdf
|
|
|
*/
|
|
|
public function setNamedAction($name, Zend_Pdf_Action_GoTo $action)
|
|
|
{
|
|
|
- if (isset($this->_namedActions[$name])) {
|
|
|
- $this->_namedActions[$name]->clean();
|
|
|
- }
|
|
|
- // Clean corresponding named destination if set
|
|
|
+ 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 (!$action->getDestination() 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.');
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
- if ($action !== null) {
|
|
|
+ if ($action !== null) {
|
|
|
$this->_namedActions[$name] = $action;
|
|
|
- } else {
|
|
|
- unset($this->_namedActions[$name]);
|
|
|
- }
|
|
|
+ } else {
|
|
|
+ unset($this->_namedActions[$name]);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -928,11 +1090,11 @@ class Zend_Pdf
|
|
|
*/
|
|
|
public function getNamedDestination($name)
|
|
|
{
|
|
|
- if (isset($this->_namedDestinations[$name])) {
|
|
|
- return $this->_namedDestinations[$name];
|
|
|
- } else {
|
|
|
- return null;
|
|
|
- }
|
|
|
+ if (isset($this->_namedDestinations[$name])) {
|
|
|
+ return $this->_namedDestinations[$name];
|
|
|
+ } else {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -944,15 +1106,15 @@ class Zend_Pdf
|
|
|
public function setNamedDestination($name, Zend_Pdf_Destination $destination)
|
|
|
{
|
|
|
// Clean corresponding named action if set
|
|
|
- if (isset($this->_namedActions[$name])) {
|
|
|
+ if (isset($this->_namedActions[$name])) {
|
|
|
$this->_namedActions[$name]->clean();
|
|
|
unset($this->_namedActions[$name]);
|
|
|
}
|
|
|
|
|
|
if ($destination !== null) {
|
|
|
- $this->_namedDestinations[$name] = $destination;
|
|
|
+ $this->_namedDestinations[$name] = $destination;
|
|
|
} else {
|
|
|
- unset($this->_namedDestinations[$name]);
|
|
|
+ unset($this->_namedDestinations[$name]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1139,6 +1301,7 @@ class Zend_Pdf
|
|
|
|
|
|
$this->_dumpPages();
|
|
|
$this->_dumpNamedDestinations();
|
|
|
+ $this->_dumpOutlines();
|
|
|
|
|
|
// Check, that PDF file was modified
|
|
|
// File is always modified by _dumpPages() now, but future implementations may eliminate this.
|