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