Skip to content

Commit

Permalink
Ignore undefined objects in link annotation logic (fixes #191)
Browse files Browse the repository at this point in the history
  • Loading branch information
JanSlabon committed Dec 5, 2023
1 parent 889df5d commit 78d0f74
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 80 deletions.
166 changes: 86 additions & 80 deletions src/PdfReader/Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

namespace setasign\Fpdi\PdfReader;

use setasign\Fpdi\FpdiException;
use setasign\Fpdi\GraphicsState;
use setasign\Fpdi\Math\Vector;
use setasign\Fpdi\PdfParser\Filter\FilterException;
Expand Down Expand Up @@ -281,14 +282,15 @@ public function getContentStream()
* origin is lower-left.
*
* @return array
* @throws CrossReferenceException
* @throws PdfParserException
* @throws PdfTypeException
*/
public function getExternalLinks($box = PageBoundaries::CROP_BOX)
{
$dict = $this->getPageDictionary();
$annotations = PdfType::resolve(PdfDictionary::get($dict, 'Annots'), $this->parser);
try {
$dict = $this->getPageDictionary();
$annotations = PdfType::resolve(PdfDictionary::get($dict, 'Annots'), $this->parser);
} catch (FpdiException $e) {
return [];
}

if (!$annotations instanceof PdfArray) {
return [];
Expand All @@ -297,93 +299,97 @@ public function getExternalLinks($box = PageBoundaries::CROP_BOX)
$links = [];

foreach ($annotations->value as $entry) {
$annotation = PdfType::resolve($entry, $this->parser);
try {
$annotation = PdfType::resolve($entry, $this->parser);

$value = PdfType::resolve(PdfDictionary::get($annotation, 'Subtype'), $this->parser);
if (!$value instanceof PdfName || $value->value !== 'Link') {
continue;
}
$value = PdfType::resolve(PdfDictionary::get($annotation, 'Subtype'), $this->parser);
if (!$value instanceof PdfName || $value->value !== 'Link') {
continue;
}

$dest = PdfType::resolve(PdfDictionary::get($annotation, 'Dest'), $this->parser);
if (!$dest instanceof PdfNull) {
continue;
}
$dest = PdfType::resolve(PdfDictionary::get($annotation, 'Dest'), $this->parser);
if (!$dest instanceof PdfNull) {
continue;
}

$action = PdfType::resolve(PdfDictionary::get($annotation, 'A'), $this->parser);
if (!$action instanceof PdfDictionary) {
continue;
}
$action = PdfType::resolve(PdfDictionary::get($annotation, 'A'), $this->parser);
if (!$action instanceof PdfDictionary) {
continue;
}

$actionType = PdfType::resolve(PdfDictionary::get($action, 'S'), $this->parser);
if (!$actionType instanceof PdfName || $actionType->value !== 'URI') {
continue;
}
$actionType = PdfType::resolve(PdfDictionary::get($action, 'S'), $this->parser);
if (!$actionType instanceof PdfName || $actionType->value !== 'URI') {
continue;
}

$uri = PdfType::resolve(PdfDictionary::get($action, 'URI'), $this->parser);
if ($uri instanceof PdfString) {
$uriValue = PdfString::unescape($uri->value);
} elseif ($uri instanceof PdfHexString) {
$uriValue = \hex2bin($uri->value);
} else {
continue;
}
$uri = PdfType::resolve(PdfDictionary::get($action, 'URI'), $this->parser);
if ($uri instanceof PdfString) {
$uriValue = PdfString::unescape($uri->value);
} elseif ($uri instanceof PdfHexString) {
$uriValue = \hex2bin($uri->value);
} else {
continue;
}

$rect = PdfType::resolve(PdfDictionary::get($annotation, 'Rect'), $this->parser);
if (!$rect instanceof PdfArray || count($rect->value) !== 4) {
continue;
}
$rect = PdfType::resolve(PdfDictionary::get($annotation, 'Rect'), $this->parser);
if (!$rect instanceof PdfArray || count($rect->value) !== 4) {
continue;
}

$rect = Rectangle::byPdfArray($rect, $this->parser);
if ($rect->getWidth() === 0 || $rect->getHeight() === 0) {
continue;
}
$rect = Rectangle::byPdfArray($rect, $this->parser);
if ($rect->getWidth() === 0 || $rect->getHeight() === 0) {
continue;
}

$bbox = $this->getBoundary($box);
$rotation = $this->getRotation();

$gs = new GraphicsState();
$gs->translate(-$bbox->getLlx(), -$bbox->getLly());
$gs->rotate($bbox->getLlx(), $bbox->getLly(), -$rotation);

switch ($rotation) {
case 90:
$gs->translate(-$bbox->getWidth(), 0);
break;
case 180:
$gs->translate(-$bbox->getWidth(), -$bbox->getHeight());
break;
case 270:
$gs->translate(0, -$bbox->getHeight());
break;
}
$bbox = $this->getBoundary($box);
$rotation = $this->getRotation();

$gs = new GraphicsState();
$gs->translate(-$bbox->getLlx(), -$bbox->getLly());
$gs->rotate($bbox->getLlx(), $bbox->getLly(), -$rotation);

switch ($rotation) {
case 90:
$gs->translate(-$bbox->getWidth(), 0);
break;
case 180:
$gs->translate(-$bbox->getWidth(), -$bbox->getHeight());
break;
case 270:
$gs->translate(0, -$bbox->getHeight());
break;
}

$normalizedRect = Rectangle::byVectors(
$gs->toUserSpace(new Vector($rect->getLlx(), $rect->getLly())),
$gs->toUserSpace(new Vector($rect->getUrx(), $rect->getUry()))
);

$quadPoints = PdfType::resolve(PdfDictionary::get($annotation, 'QuadPoints'), $this->parser);
$normalizedQuadPoints = [];
if ($quadPoints instanceof PdfArray) {
$quadPointsCount = count($quadPoints->value);
if ($quadPointsCount % 8 === 0) {
for ($i = 0; ($i + 1) < $quadPointsCount; $i += 2) {
$x = PdfNumeric::ensure(PdfType::resolve($quadPoints->value[$i], $this->parser));
$y = PdfNumeric::ensure(PdfType::resolve($quadPoints->value[$i + 1], $this->parser));

$v = $gs->toUserSpace(new Vector($x->value, $y->value));
$normalizedQuadPoints[] = $v->getX();
$normalizedQuadPoints[] = $v->getY();
$normalizedRect = Rectangle::byVectors(
$gs->toUserSpace(new Vector($rect->getLlx(), $rect->getLly())),
$gs->toUserSpace(new Vector($rect->getUrx(), $rect->getUry()))
);

$quadPoints = PdfType::resolve(PdfDictionary::get($annotation, 'QuadPoints'), $this->parser);
$normalizedQuadPoints = [];
if ($quadPoints instanceof PdfArray) {
$quadPointsCount = count($quadPoints->value);
if ($quadPointsCount % 8 === 0) {
for ($i = 0; ($i + 1) < $quadPointsCount; $i += 2) {
$x = PdfNumeric::ensure(PdfType::resolve($quadPoints->value[$i], $this->parser));
$y = PdfNumeric::ensure(PdfType::resolve($quadPoints->value[$i + 1], $this->parser));

$v = $gs->toUserSpace(new Vector($x->value, $y->value));
$normalizedQuadPoints[] = $v->getX();
$normalizedQuadPoints[] = $v->getY();
}
}
}
}

$links[] = [
'rect' => $normalizedRect,
'quadPoints' => $normalizedQuadPoints,
'uri' => $uriValue,
'pdfObject' => $annotation
];
$links[] = [
'rect' => $normalizedRect,
'quadPoints' => $normalizedQuadPoints,
'uri' => $uriValue,
'pdfObject' => $annotation
];
} catch (FpdiException $e) {
continue;
}
}

return $links;
Expand Down
Binary file not shown.
Binary file added tests/_files/pdfs/links/invalid-annots-reference.pdf
Binary file not shown.
21 changes: 21 additions & 0 deletions tests/functional/PdfReader/PageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,27 @@ public function getExternalLinksProvider()
]
]
]
],
[
__DIR__ . '/../../_files/pdfs/links/annotations-with-invalid-references.pdf',
[
1 => [
[
'uri' => 'https://www.setasign.com/#1',
'rect' => new Rectangle(20, 20, 100, 200)
],
[
'uri' => 'https://www.setasign.com/#2',
'rect' => new Rectangle(140, 140, 100, 200)
]
]
]
],
[
__DIR__ . '/../../_files/pdfs/links/invalid-annots-reference.pdf',
[
1 => []
]
]
];
}
Expand Down

0 comments on commit 78d0f74

Please sign in to comment.