Skip to content

Commit

Permalink
Merge pull request #196 from Setasign/indirect-references-in-annotati…
Browse files Browse the repository at this point in the history
…on-dictionaries

Handle indirect references in annotation dictionaries
  • Loading branch information
JanSlabon authored Dec 8, 2023
2 parents f63773c + 1255ec2 commit a7b6f99
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 42 deletions.
15 changes: 1 addition & 14 deletions src/FpdfTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use setasign\Fpdi\PdfParser\PdfParserException;
use setasign\Fpdi\PdfParser\Type\PdfIndirectObject;
use setasign\Fpdi\PdfParser\Type\PdfNull;
use setasign\Fpdi\PdfParser\Type\PdfType;

/**
* This trait is used for the implementation of FPDI in FPDF and tFPDF.
Expand Down Expand Up @@ -142,20 +143,6 @@ protected function _putlinks($n)
$this->_put('/A <</S /URI /URI ' . $this->_textstring($pl[4]) . '>>');
if (isset($pl['importedLink'])) {
$values = $pl['importedLink']['pdfObject']->value;
unset(
$values['P'],
$values['NM'],
$values['AP'],
$values['AS'],
$values['Type'],
$values['Subtype'],
$values['Rect'],
$values['A'],
$values['QuadPoints'],
$values['Rotate'],
$values['M'],
$values['StructParent']
);

foreach ($values as $name => $entry) {
$this->_put('/' . $name . ' ', false);
Expand Down
28 changes: 28 additions & 0 deletions src/PdfParser/Type/PdfType.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,34 @@ protected static function ensureType($type, $value, $errorMessage)
return $value;
}

/**
* Flatten indirect object references to direct objects.
*
* @param PdfType $value
* @param PdfParser $parser
* @return PdfType
* @throws CrossReferenceException
* @throws PdfParserException
*/
public static function flatten(PdfType $value, PdfParser $parser)
{
if ($value instanceof PdfIndirectObjectReference) {
return self::flatten(self::resolve($value, $parser), $parser);
}

if ($value instanceof PdfDictionary || $value instanceof PdfArray) {
foreach ($value->value as $key => $_value) {
$value->value[$key] = self::flatten($_value, $parser);
}
}

if ($value instanceof PdfStream) {
throw new PdfTypeException('There is a stream object found which cannot be flattened to a direct object.');
}

return $value;
}

/**
* The value of the PDF type.
*
Expand Down
23 changes: 23 additions & 0 deletions src/PdfReader/Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,29 @@ public function getExternalLinks($box = PageBoundaries::CROP_BOX)
}
}

// we remove unsupported/unneeded values here
unset(
$annotation->value['P'],
$annotation->value['NM'],
$annotation->value['AP'],
$annotation->value['AS'],
$annotation->value['Type'],
$annotation->value['Subtype'],
$annotation->value['Rect'],
$annotation->value['A'],
$annotation->value['QuadPoints'],
$annotation->value['Rotate'],
$annotation->value['M'],
$annotation->value['StructParent'],
$annotation->value['OC']
);

// ...and flatten the PDF object to eliminate any indirect references.
// Indirect references are a problem when writing the output in FPDF
// because FPDF uses pre-calculated object numbers while FPDI creates
// them at runtime.
$annotation = PdfType::flatten($annotation, $this->parser);

$links[] = [
'rect' => $normalizedRect,
'quadPoints' => $normalizedQuadPoints,
Expand Down
37 changes: 12 additions & 25 deletions src/Tcpdf/Fpdi.php
Original file line number Diff line number Diff line change
Expand Up @@ -303,21 +303,8 @@ protected function adjustLastLink($externalLink, $xPt, $scaleX, $yPt, $newHeight
// ensure we have a default value - otherwise TCPDF will set it to 4 throughout
$lastAnnotationOpt['f'] = 0;

// values in this dictonary are all direct objects and we don't need to resolve them here again.
$values = $externalLink['pdfObject']->value;
unset(
$values['P'],
$values['NM'],
$values['AP'],
$values['AS'],
$values['Type'],
$values['Subtype'],
$values['Rect'],
$values['A'],
$values['QuadPoints'],
$values['Rotate'],
$values['M'],
$values['StructParent']
);

foreach ($values as $key => $value) {
try {
Expand All @@ -326,17 +313,17 @@ protected function adjustLastLink($externalLink, $xPt, $scaleX, $yPt, $newHeight
$value = PdfDictionary::ensure($value);
$bs = [];
if (isset($value->value['W'])) {
$bs['w'] = PdfNumeric::ensure(PdfType::resolve($value->value['W'], $parser))->value;
$bs['w'] = PdfNumeric::ensure($value->value['W'])->value;
}

if (isset($value->value['S'])) {
$bs['s'] = PdfName::ensure(PdfType::resolve($value->value['S'], $parser))->value;
$bs['s'] = PdfName::ensure($value->value['S'])->value;
}

if (isset($value->value['D'])) {
$d = [];
foreach (PdfArray::ensure(PdfType::resolve($value->value['D'], $parser))->value as $item) {
$d[] = PdfNumeric::ensure(PdfType::resolve($item, $parser))->value;
foreach (PdfArray::ensure($value->value['D'])->value as $item) {
$d[] = PdfNumeric::ensure($item)->value;
}
$bs['d'] = $d;
}
Expand All @@ -345,20 +332,20 @@ protected function adjustLastLink($externalLink, $xPt, $scaleX, $yPt, $newHeight
break;

case 'Border':
$borderArray = PdfArray::ensure(PdfType::resolve($value, $parser))->value;
$borderArray = PdfArray::ensure($value)->value;
if (count($borderArray) < 3) {
continue 2;
}

$border = [
PdfNumeric::ensure(PdfType::resolve($borderArray[0], $parser))->value,
PdfNumeric::ensure(PdfType::resolve($borderArray[1], $parser))->value,
PdfNumeric::ensure(PdfType::resolve($borderArray[2], $parser))->value,
PdfNumeric::ensure($borderArray[0])->value,
PdfNumeric::ensure($borderArray[1])->value,
PdfNumeric::ensure($borderArray[2])->value,
];
if (isset($borderArray[3])) {
$dashArray = [];
foreach (PdfArray::ensure(PdfType::resolve($borderArray[3], $parser))->value as $item) {
$dashArray[] = PdfNumeric::ensure(PdfType::resolve($item, $parser))->value;
foreach (PdfArray::ensure($borderArray[3])->value as $item) {
$dashArray[] = PdfNumeric::ensure($item)->value;
}
$border[] = $dashArray;
}
Expand All @@ -371,7 +358,7 @@ protected function adjustLastLink($externalLink, $xPt, $scaleX, $yPt, $newHeight
$colors = PdfArray::ensure(PdfType::resolve($value, $parser))->value;
$m = count($colors) === 4 ? 100 : 255;
foreach ($colors as $item) {
$c[] = PdfNumeric::ensure(PdfType::resolve($item, $parser))->value * $m;
$c[] = PdfNumeric::ensure($item)->value * $m;
}
$lastAnnotationOpt['c'] = $c;
break;
Expand Down
Binary file not shown.
42 changes: 42 additions & 0 deletions tests/_files/pdfs/links/update-links.pdf.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php
/* A simple helper script to modify links.pdf in view to indirect references in annotation dictionaries.
* We used SetaPDF-Core for doing such low-level modifications.
*/

require_once __DIR__ . '/../../../../../SetaPDF/library/SetaPDF/Autoload.php';

$writer = new SetaPDF_Core_Writer_File('links-with-indirect-references.pdf');
$document = SetaPDF_Core_Document::loadByFilename('links.pdf', $writer);

$pages = $document->getCatalog()->getPages();
$page = $pages->getPage(1);

$annotations = $page->getAnnotations();
$allAnnots = $annotations->getAll();

$dict = $allAnnots[0]->getDictionary();
$bs = $dict->getValue('BS');
$d = $bs->getValue('D');
$bs->offsetSet('D', $document->createNewObject($d));
$dict->offsetSet('BS', $document->createNewObject($bs));

$dict = $allAnnots[1]->getDictionary();
$c = $dict->getValue('C');
$object = $document->createNewObject($c);
$ref = new SetaPDF_Core_Type_IndirectReference($object, 0, $document);
$dict->offsetSet('C', $document->createNewObject($ref));


$dict = $allAnnots[5]->getDictionary();
$border = $dict->getValue('Border');
$dict->offsetSet('Border', $document->createNewObject($border));

$page = $pages->getPage(2);
$annotations = $page->getAnnotations();
$allAnnots = $annotations->getAll();

$dict = $allAnnots[0]->getDictionary();
$rect = $dict->getValue('Rect');
$dict->offsetSet('Rect', $document->createNewObject($rect));

$document->save()->finish();
17 changes: 14 additions & 3 deletions tests/functional/LinkHandling/AbstractTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ protected function compatAssertEqualsWithDelta($expected, $actual, $message = ''
}
}


protected function save($pdf)
{
return $pdf->Output('S');
Expand Down Expand Up @@ -131,11 +130,11 @@ public function testDoNotImportLinks()
* This test simply imports a page with several links including quad-points
* and place it resized and compressed onto a new page.
*/
public function testLinkHandling1()
public function testLinkHandling1($filename = __DIR__ . '/../../_files/pdfs/links/links.pdf')
{
$pdf = $this->getInstance();
$pdf->AddPage();
$pdf->setSourceFile(__DIR__ . '/../../_files/pdfs/links/links.pdf');
$pdf->setSourceFile($filename);
$tplId = $pdf->importPage(1, PageBoundaries::CROP_BOX, true, true);
$pdf->useTemplate($tplId, [
'x' => 20,
Expand Down Expand Up @@ -235,6 +234,18 @@ public function testLinkHandling1()
return $pdfString;
}

/**
* This test imports annotations with indirect references in their properties which should be flattened.
*
* The original file links.pdf was modified appropriately.
*
* @return void
*/
public function testLinkHandlingWithIndirectReferencesInAnnotation()
{
$this->testLinkHandling1(__DIR__ . '/../../_files/pdfs/links/links-with-indirect-references.pdf');
}

/**
* Take the result of testLinkHandling1 and re-place it with the same settings.
* @depends testLinkHandling1
Expand Down

0 comments on commit a7b6f99

Please sign in to comment.