1<?php 2 3namespace Mpdf; 4 5use setasign\Fpdi\PdfParser\CrossReference\CrossReferenceException; 6use setasign\Fpdi\PdfParser\Filter\AsciiHex; 7use setasign\Fpdi\PdfParser\Type\PdfArray; 8use setasign\Fpdi\PdfParser\Type\PdfIndirectObjectReference; 9use setasign\Fpdi\PdfReader\PageBoundaries; 10use setasign\Fpdi\PdfParser\Type\PdfHexString; 11use setasign\Fpdi\PdfParser\Type\PdfIndirectObject; 12use setasign\Fpdi\PdfParser\Type\PdfNull; 13use setasign\Fpdi\PdfParser\Type\PdfNumeric; 14use setasign\Fpdi\PdfParser\Type\PdfStream; 15use setasign\Fpdi\PdfParser\Type\PdfString; 16use setasign\Fpdi\PdfParser\Type\PdfType; 17use setasign\Fpdi\PdfParser\Type\PdfTypeException; 18 19/** 20 * @mixin Mpdf 21 */ 22trait FpdiTrait 23{ 24 use \setasign\Fpdi\FpdiTrait { 25 writePdfType as fpdiWritePdfType; 26 useImportedPage as fpdiUseImportedPage; 27 importPage as fpdiImportPage; 28 } 29 30 protected $k = Mpdf::SCALE; 31 32 /** 33 * The currently used object number. 34 * 35 * @var int 36 */ 37 public $currentObjectNumber; 38 39 /** 40 * A counter for template ids. 41 * 42 * @var int 43 */ 44 protected $templateId = 0; 45 46 protected function setPageFormat($format, $orientation) 47 { 48 // in mPDF this needs to be "P" (why ever) 49 $orientation = 'P'; 50 $this->_setPageSize([$format['width'], $format['height']], $orientation); 51 52 if ($orientation != $this->DefOrientation) { 53 $this->OrientationChanges[$this->page] = true; 54 } 55 56 $this->wPt = $this->fwPt; 57 $this->hPt = $this->fhPt; 58 $this->w = $this->fw; 59 $this->h = $this->fh; 60 61 $this->CurOrientation = $orientation; 62 $this->ResetMargins(); 63 $this->pgwidth = $this->w - $this->lMargin - $this->rMargin; 64 $this->PageBreakTrigger = $this->h - $this->bMargin; 65 66 $this->pageDim[$this->page]['w'] = $this->w; 67 $this->pageDim[$this->page]['h'] = $this->h; 68 } 69 70 /** 71 * Set the minimal PDF version. 72 * 73 * @param string $pdfVersion 74 */ 75 protected function setMinPdfVersion($pdfVersion) 76 { 77 if (\version_compare($pdfVersion, $this->pdf_version, '>')) { 78 $this->pdf_version = $pdfVersion; 79 } 80 } 81 82 /** 83 * Get the next template id. 84 * 85 * @return int 86 */ 87 protected function getNextTemplateId() 88 { 89 return $this->templateId++; 90 } 91 92 /** 93 * Draws an imported page or a template onto the page or another template. 94 * 95 * Omit one of the size parameters (width, height) to calculate the other one automatically in view to the aspect 96 * ratio. 97 * 98 * @param mixed $tpl The template id 99 * @param float|int|array $x The abscissa of upper-left corner. Alternatively you could use an assoc array 100 * with the keys "x", "y", "width", "height", "adjustPageSize". 101 * @param float|int $y The ordinate of upper-left corner. 102 * @param float|int|null $width The width. 103 * @param float|int|null $height The height. 104 * @param bool $adjustPageSize 105 * @return array The size 106 * @see Fpdi::getTemplateSize() 107 */ 108 public function useTemplate($tpl, $x = 0, $y = 0, $width = null, $height = null, $adjustPageSize = false) 109 { 110 return $this->useImportedPage($tpl, $x, $y, $width, $height, $adjustPageSize); 111 } 112 113 /** 114 * Draws an imported page onto the page. 115 * 116 * Omit one of the size parameters (width, height) to calculate the other one automatically in view to the aspect 117 * ratio. 118 * 119 * @param mixed $pageId The page id 120 * @param float|int|array $x The abscissa of upper-left corner. Alternatively you could use an assoc array 121 * with the keys "x", "y", "width", "height", "adjustPageSize". 122 * @param float|int $y The ordinate of upper-left corner. 123 * @param float|int|null $width The width. 124 * @param float|int|null $height The height. 125 * @param bool $adjustPageSize 126 * @return array The size. 127 * @see Fpdi::getTemplateSize() 128 */ 129 public function useImportedPage($pageId, $x = 0, $y = 0, $width = null, $height = null, $adjustPageSize = false) 130 { 131 if ($this->state == 0) { 132 $this->AddPage(); 133 } 134 135 /* Extract $x if an array */ 136 if (is_array($x)) { 137 unset($x['pageId']); 138 extract($x, EXTR_IF_EXISTS); 139 if (is_array($x)) { 140 $x = 0; 141 } 142 } 143 144 $newSize = $this->fpdiUseImportedPage($pageId, $x, $y, $width, $height, $adjustPageSize); 145 146 $this->setImportedPageLinks($pageId, $x, $y, $newSize); 147 148 return $newSize; 149 } 150 151 /** 152 * Imports a page. 153 * 154 * @param int $pageNumber The page number. 155 * @param string $box The page boundary to import. Default set to PageBoundaries::CROP_BOX. 156 * @param bool $groupXObject Define the form XObject as a group XObject to support transparency (if used). 157 * @return string A unique string identifying the imported page. 158 * @throws CrossReferenceException 159 * @throws FilterException 160 * @throws PdfParserException 161 * @throws PdfTypeException 162 * @throws PdfReaderException 163 * @see PageBoundaries 164 */ 165 public function importPage($pageNumber, $box = PageBoundaries::CROP_BOX, $groupXObject = true) 166 { 167 $pageId = $this->fpdiImportPage($pageNumber, $box, $groupXObject); 168 169 $this->importedPages[$pageId]['externalLinks'] = $this->getImportedExternalPageLinks($pageNumber); 170 171 return $pageId; 172 } 173 174 /** 175 * Imports the external page links 176 * 177 * @param int $pageNumber The page number. 178 * @return array 179 * @throws CrossReferenceException 180 * @throws PdfTypeException 181 * @throws \setasign\Fpdi\PdfParser\PdfParserException 182 */ 183 public function getImportedExternalPageLinks($pageNumber) 184 { 185 $links = []; 186 187 $reader = $this->getPdfReader($this->currentReaderId); 188 $parser = $reader->getParser(); 189 190 $page = $reader->getPage($pageNumber); 191 $page->getPageDictionary(); 192 193 $annotations = $page->getAttribute('Annots'); 194 if ($annotations instanceof PdfIndirectObjectReference) { 195 $annotations = PdfType::resolve($parser->getIndirectObject($annotations->value), $parser); 196 } 197 198 if ($annotations instanceof PdfArray) { 199 200 $getAttribute = function ($array, $key) { 201 if (isset($array[$key]->value)) { 202 return $array[$key]->value; 203 } 204 205 return ''; 206 }; 207 208 foreach ($annotations->value as $annotation) { 209 $annotation = PdfType::resolve($annotation, $parser)->value; 210 211 /* Skip over any annotations that aren't links */ 212 $type = $getAttribute($annotation, 'Type'); 213 $subtype = $getAttribute($annotation, 'Subtype'); 214 if ($type !== 'Annot' || $subtype !== 'Link' || !isset($annotation['A'])) { 215 continue; 216 } 217 218 /* Calculate the link positioning */ 219 $position = $getAttribute($annotation, 'Rect'); 220 221 if (count($position) !== 4) { 222 continue; 223 } 224 225 $x1 = $getAttribute($position, 0) / Mpdf::SCALE; 226 $y1 = $getAttribute($position, 1) / Mpdf::SCALE; 227 $x2 = $getAttribute($position, 2) / Mpdf::SCALE; 228 $y2 = $getAttribute($position, 3) / Mpdf::SCALE; 229 $width = $x2 - $x1; 230 $height = $y2 - $y1; 231 232 $link = $annotation['A'] instanceof PdfIndirectObjectReference ? PdfType::resolve($annotation['A'], $parser)->value : $getAttribute($annotation, 'A'); 233 234 if (isset($link['URI'])) { 235 $links[] = [ 236 'x' => $x1, 237 'y' => $y1, 238 'width' => $width, 239 'height' => $height, 240 'url' => $getAttribute($link, 'URI') 241 ]; 242 } 243 } 244 } 245 246 return $links; 247 } 248 249 /** 250 * @param mixed $pageId The page id 251 * @param int|float $x The abscissa of upper-left corner. 252 * @param int|float $y The ordinate of upper-right corner. 253 * @param array $newSize The size. 254 */ 255 public function setImportedPageLinks($pageId, $x, $y, $newSize) 256 { 257 $originalSize = $this->getTemplateSize($pageId); 258 $pageHeightDifference = $this->h - $newSize['height']; 259 260 /* Handle different aspect ratio */ 261 $widthRatio = $newSize['width'] / $originalSize['width']; 262 $heightRatio = $newSize['height'] / $originalSize['height']; 263 264 foreach ($this->importedPages[$pageId]['externalLinks'] as $item) { 265 266 $item['x'] *= $widthRatio; 267 $item['width'] *= $widthRatio; 268 269 $item['y'] *= $heightRatio; 270 $item['height'] *= $heightRatio; 271 272 $this->Link( 273 $item['x'] + $x, 274 /* convert Y to be measured from the top of the page */ 275 $this->h - $item['y'] - $item['height'] - $pageHeightDifference + $y, 276 $item['width'], 277 $item['height'], 278 $item['url'] 279 ); 280 } 281 } 282 283 /** 284 * Get the size of an imported page or template. 285 * 286 * Omit one of the size parameters (width, height) to calculate the other one automatically in view to the aspect 287 * ratio. 288 * 289 * @param mixed $tpl The template id 290 * @param float|int|null $width The width. 291 * @param float|int|null $height The height. 292 * @return array|bool An array with following keys: width, height, 0 (=width), 1 (=height), orientation (L or P) 293 */ 294 public function getTemplateSize($tpl, $width = null, $height = null) 295 { 296 return $this->getImportedPageSize($tpl, $width, $height); 297 } 298 299 /** 300 * @throws CrossReferenceException 301 * @throws PdfTypeException 302 * @throws \setasign\Fpdi\PdfParser\PdfParserException 303 */ 304 public function writeImportedPagesAndResolvedObjects() 305 { 306 $this->currentReaderId = null; 307 308 foreach ($this->importedPages as $key => $pageData) { 309 $this->writer->object(); 310 $this->importedPages[$key]['objectNumber'] = $this->n; 311 $this->currentReaderId = $pageData['readerId']; 312 $this->writePdfType($pageData['stream']); 313 $this->_put('endobj'); 314 } 315 316 foreach (\array_keys($this->readers) as $readerId) { 317 $parser = $this->getPdfReader($readerId)->getParser(); 318 $this->currentReaderId = $readerId; 319 320 while (($objectNumber = \array_pop($this->objectsToCopy[$readerId])) !== null) { 321 try { 322 $object = $parser->getIndirectObject($objectNumber); 323 324 } catch (CrossReferenceException $e) { 325 if ($e->getCode() === CrossReferenceException::OBJECT_NOT_FOUND) { 326 $object = PdfIndirectObject::create($objectNumber, 0, new PdfNull()); 327 } else { 328 throw $e; 329 } 330 } 331 332 $this->writePdfType($object); 333 } 334 } 335 336 $this->currentReaderId = null; 337 } 338 339 public function getImportedPages() 340 { 341 return $this->importedPages; 342 } 343 344 protected function _put($s, $newLine = true) 345 { 346 if ($newLine) { 347 $this->buffer .= $s . "\n"; 348 } else { 349 $this->buffer .= $s; 350 } 351 } 352 353 /** 354 * Writes a PdfType object to the resulting buffer. 355 * 356 * @param PdfType $value 357 * @throws PdfTypeException 358 */ 359 public function writePdfType(PdfType $value) 360 { 361 if (!$this->encrypted) { 362 if ($value instanceof PdfIndirectObject) { 363 /** 364 * @var $value PdfIndirectObject 365 */ 366 $n = $this->objectMap[$this->currentReaderId][$value->objectNumber]; 367 $this->writer->object($n); 368 $this->writePdfType($value->value); 369 $this->_put('endobj'); 370 return; 371 } 372 373 $this->fpdiWritePdfType($value); 374 return; 375 } 376 377 if ($value instanceof PdfString) { 378 $string = PdfString::unescape($value->value); 379 $string = $this->protection->rc4($this->protection->objectKey($this->currentObjectNumber), $string); 380 $value->value = $this->writer->escape($string); 381 382 } elseif ($value instanceof PdfHexString) { 383 $filter = new AsciiHex(); 384 $string = $filter->decode($value->value); 385 $string = $this->protection->rc4($this->protection->objectKey($this->currentObjectNumber), $string); 386 $value->value = $filter->encode($string, true); 387 388 } elseif ($value instanceof PdfStream) { 389 $stream = $value->getStream(); 390 $stream = $this->protection->rc4($this->protection->objectKey($this->currentObjectNumber), $stream); 391 $dictionary = $value->value; 392 $dictionary->value['Length'] = PdfNumeric::create(\strlen($stream)); 393 $value = PdfStream::create($dictionary, $stream); 394 395 } elseif ($value instanceof PdfIndirectObject) { 396 /** 397 * @var $value PdfIndirectObject 398 */ 399 $this->currentObjectNumber = $this->objectMap[$this->currentReaderId][$value->objectNumber]; 400 /** 401 * @var $value PdfIndirectObject 402 */ 403 $n = $this->objectMap[$this->currentReaderId][$value->objectNumber]; 404 $this->writer->object($n); 405 $this->writePdfType($value->value); 406 $this->_put('endobj'); 407 return; 408 } 409 410 $this->fpdiWritePdfType($value); 411 } 412} 413