fpdi.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. <?php
  2. /**
  3. * This file is part of FPDI
  4. *
  5. * @package FPDI
  6. * @copyright Copyright (c) 2017 Setasign - Jan Slabon (https://www.setasign.com)
  7. * @license http://opensource.org/licenses/mit-license The MIT License
  8. * @version 1.6.2
  9. */
  10. if (!class_exists('FPDF_TPL')) {
  11. require_once('fpdf_tpl.php');
  12. }
  13. /**
  14. * Class FPDI
  15. */
  16. class FPDI extends FPDF_TPL
  17. {
  18. /**
  19. * FPDI version
  20. *
  21. * @string
  22. */
  23. const VERSION = '1.6.2';
  24. /**
  25. * Actual filename
  26. *
  27. * @var string
  28. */
  29. public $currentFilename;
  30. /**
  31. * Parser-Objects
  32. *
  33. * @var fpdi_pdf_parser[]
  34. */
  35. public $parsers = array();
  36. /**
  37. * Current parser
  38. *
  39. * @var fpdi_pdf_parser
  40. */
  41. public $currentParser;
  42. /**
  43. * The name of the last imported page box
  44. *
  45. * @var string
  46. */
  47. public $lastUsedPageBox;
  48. /**
  49. * Object stack
  50. *
  51. * @var array
  52. */
  53. protected $_objStack;
  54. /**
  55. * Done object stack
  56. *
  57. * @var array
  58. */
  59. protected $_doneObjStack;
  60. /**
  61. * Current Object Id.
  62. *
  63. * @var integer
  64. */
  65. protected $_currentObjId;
  66. /**
  67. * Cache for imported pages/template ids
  68. *
  69. * @var array
  70. */
  71. protected $_importedPages = array();
  72. /**
  73. * Set a source-file.
  74. *
  75. * Depending on the PDF version of the used document the PDF version of the resulting document will
  76. * be adjusted to the higher version.
  77. *
  78. * @param string $filename A valid path to the PDF document from which pages should be imported from
  79. * @return int The number of pages in the document
  80. * @throws Exception
  81. */
  82. public function setSourceFile($filename)
  83. {
  84. $_filename = realpath($filename);
  85. if (false !== $_filename)
  86. $filename = $_filename;
  87. $currentFilename = $this->currentFilename;
  88. $currentParser = $this->currentParser;
  89. try {
  90. $this->currentFilename = $filename;
  91. if (!isset($this->parsers[$filename])) {
  92. $this->parsers[$filename] = $this->_getPdfParser($filename);
  93. $this->setPdfVersion(
  94. max($this->getPdfVersion(), $this->parsers[$filename]->getPdfVersion())
  95. );
  96. }
  97. $this->currentParser = $this->parsers[$filename];
  98. } catch (Exception $e) {
  99. unset($this->parsers[$filename]);
  100. $this->currentFilename = $currentFilename;
  101. $this->currentParser = $currentParser;
  102. throw $e;
  103. }
  104. return $this->parsers[$filename]->getPageCount();
  105. }
  106. /**
  107. * Returns a PDF parser object
  108. *
  109. * @param string $filename
  110. * @return fpdi_pdf_parser
  111. */
  112. protected function _getPdfParser($filename)
  113. {
  114. if (!class_exists('fpdi_pdf_parser')) {
  115. require_once('fpdi_pdf_parser.php');
  116. }
  117. return new fpdi_pdf_parser($filename);
  118. }
  119. /**
  120. * Get the current PDF version.
  121. *
  122. * @return string
  123. */
  124. public function getPdfVersion()
  125. {
  126. return $this->PDFVersion;
  127. }
  128. /**
  129. * Set the PDF version.
  130. *
  131. * @param string $version
  132. */
  133. public function setPdfVersion($version = '1.3')
  134. {
  135. $this->PDFVersion = sprintf('%.1F', $version);
  136. }
  137. /**
  138. * Import a page.
  139. *
  140. * The second parameter defines the bounding box that should be used to transform the page into a
  141. * form XObject.
  142. *
  143. * Following values are available: MediaBox, CropBox, BleedBox, TrimBox, ArtBox.
  144. * If a box is not especially defined its default box will be used:
  145. *
  146. * <ul>
  147. * <li>CropBox: Default -> MediaBox</li>
  148. * <li>BleedBox: Default -> CropBox</li>
  149. * <li>TrimBox: Default -> CropBox</li>
  150. * <li>ArtBox: Default -> CropBox</li>
  151. * </ul>
  152. *
  153. * It is possible to get the used page box by the {@link getLastUsedPageBox()} method.
  154. *
  155. * @param int $pageNo The page number
  156. * @param string $boxName The boundary box to use when transforming the page into a form XObject
  157. * @param boolean $groupXObject Define the form XObject as a group XObject to support transparency (if used)
  158. * @return int An id of the imported page/template to use with e.g. fpdf_tpl::useTemplate()
  159. * @throws LogicException|InvalidArgumentException
  160. * @see getLastUsedPageBox()
  161. */
  162. public function importPage($pageNo, $boxName = 'CropBox', $groupXObject = true)
  163. {
  164. if ($this->_inTpl) {
  165. throw new LogicException('Please import the desired pages before creating a new template.');
  166. }
  167. $fn = $this->currentFilename;
  168. $boxName = '/' . ltrim($boxName, '/');
  169. // check if page already imported
  170. $pageKey = $fn . '-' . ((int)$pageNo) . $boxName;
  171. if (isset($this->_importedPages[$pageKey])) {
  172. return $this->_importedPages[$pageKey];
  173. }
  174. $parser = $this->parsers[$fn];
  175. $parser->setPageNo($pageNo);
  176. if (!in_array($boxName, $parser->availableBoxes)) {
  177. throw new InvalidArgumentException(sprintf('Unknown box: %s', $boxName));
  178. }
  179. $pageBoxes = $parser->getPageBoxes($pageNo, $this->k);
  180. /**
  181. * MediaBox
  182. * CropBox: Default -> MediaBox
  183. * BleedBox: Default -> CropBox
  184. * TrimBox: Default -> CropBox
  185. * ArtBox: Default -> CropBox
  186. */
  187. if (!isset($pageBoxes[$boxName]) && ($boxName == '/BleedBox' || $boxName == '/TrimBox' || $boxName == '/ArtBox'))
  188. $boxName = '/CropBox';
  189. if (!isset($pageBoxes[$boxName]) && $boxName == '/CropBox')
  190. $boxName = '/MediaBox';
  191. if (!isset($pageBoxes[$boxName]))
  192. return false;
  193. $this->lastUsedPageBox = $boxName;
  194. $box = $pageBoxes[$boxName];
  195. $this->tpl++;
  196. $this->_tpls[$this->tpl] = array();
  197. $tpl =& $this->_tpls[$this->tpl];
  198. $tpl['parser'] = $parser;
  199. $tpl['resources'] = $parser->getPageResources();
  200. $tpl['buffer'] = $parser->getContent();
  201. $tpl['box'] = $box;
  202. $tpl['groupXObject'] = $groupXObject;
  203. if ($groupXObject) {
  204. $this->setPdfVersion(max($this->getPdfVersion(), 1.4));
  205. }
  206. // To build an array that can be used by PDF_TPL::useTemplate()
  207. $this->_tpls[$this->tpl] = array_merge($this->_tpls[$this->tpl], $box);
  208. // An imported page will start at 0,0 all the time. Translation will be set in _putformxobjects()
  209. $tpl['x'] = 0;
  210. $tpl['y'] = 0;
  211. // handle rotated pages
  212. $rotation = $parser->getPageRotation($pageNo);
  213. $tpl['_rotationAngle'] = 0;
  214. if (isset($rotation[1]) && ($angle = $rotation[1] % 360) != 0) {
  215. $steps = $angle / 90;
  216. $_w = $tpl['w'];
  217. $_h = $tpl['h'];
  218. $tpl['w'] = $steps % 2 == 0 ? $_w : $_h;
  219. $tpl['h'] = $steps % 2 == 0 ? $_h : $_w;
  220. if ($angle < 0)
  221. $angle += 360;
  222. $tpl['_rotationAngle'] = $angle * -1;
  223. }
  224. $this->_importedPages[$pageKey] = $this->tpl;
  225. return $this->tpl;
  226. }
  227. /**
  228. * Returns the last used page boundary box.
  229. *
  230. * @return string The used boundary box: MediaBox, CropBox, BleedBox, TrimBox or ArtBox
  231. */
  232. public function getLastUsedPageBox()
  233. {
  234. return $this->lastUsedPageBox;
  235. }
  236. /**
  237. * Use a template or imported page in current page or other template.
  238. *
  239. * You can use a template in a page or in another template.
  240. * You can give the used template a new size. All parameters are optional.
  241. * The width or height is calculated automatically if one is given. If no
  242. * parameter is given the origin size as defined in beginTemplate() or of
  243. * the imported page is used.
  244. *
  245. * The calculated or used width and height are returned as an array.
  246. *
  247. * @param int $tplIdx A valid template-id
  248. * @param int $x The x-position
  249. * @param int $y The y-position
  250. * @param int $w The new width of the template
  251. * @param int $h The new height of the template
  252. * @param boolean $adjustPageSize If set to true the current page will be resized to fit the dimensions
  253. * of the template
  254. *
  255. * @return array The height and width of the template (array('w' => ..., 'h' => ...))
  256. * @throws LogicException|InvalidArgumentException
  257. */
  258. public function useTemplate($tplIdx, $x = null, $y = null, $w = 0, $h = 0, $adjustPageSize = false)
  259. {
  260. if ($adjustPageSize == true && is_null($x) && is_null($y)) {
  261. $size = $this->getTemplateSize($tplIdx, $w, $h);
  262. $orientation = $size['w'] > $size['h'] ? 'L' : 'P';
  263. $size = array($size['w'], $size['h']);
  264. if (is_subclass_of($this, 'TCPDF')) {
  265. $this->setPageFormat($size, $orientation);
  266. } else {
  267. $size = $this->_getpagesize($size);
  268. if($orientation != $this->CurOrientation ||
  269. $size[0] != $this->CurPageSize[0] ||
  270. $size[1] != $this->CurPageSize[1]
  271. ) {
  272. // New size or orientation
  273. if ($orientation=='P') {
  274. $this->w = $size[0];
  275. $this->h = $size[1];
  276. } else {
  277. $this->w = $size[1];
  278. $this->h = $size[0];
  279. }
  280. $this->wPt = $this->w * $this->k;
  281. $this->hPt = $this->h * $this->k;
  282. $this->PageBreakTrigger = $this->h - $this->bMargin;
  283. $this->CurOrientation = $orientation;
  284. $this->CurPageSize = $size;
  285. if (FPDF_VERSION >= 1.8) {
  286. $this->PageInfo[$this->page]['size'] = array($this->wPt, $this->hPt);
  287. } else {
  288. $this->PageSizes[$this->page] = array($this->wPt, $this->hPt);
  289. }
  290. }
  291. }
  292. }
  293. $this->_out('q 0 J 1 w 0 j 0 G 0 g'); // reset standard values
  294. $size = parent::useTemplate($tplIdx, $x, $y, $w, $h);
  295. $this->_out('Q');
  296. return $size;
  297. }
  298. /**
  299. * Copy all imported objects to the resulting document.
  300. */
  301. protected function _putimportedobjects()
  302. {
  303. foreach($this->parsers AS $filename => $p) {
  304. $this->currentParser = $p;
  305. if (!isset($this->_objStack[$filename]) || !is_array($this->_objStack[$filename])) {
  306. continue;
  307. }
  308. while(($n = key($this->_objStack[$filename])) !== null) {
  309. try {
  310. $nObj = $this->currentParser->resolveObject($this->_objStack[$filename][$n][1]);
  311. } catch (Exception $e) {
  312. $nObj = array(pdf_parser::TYPE_OBJECT, pdf_parser::TYPE_NULL);
  313. }
  314. $this->_newobj($this->_objStack[$filename][$n][0]);
  315. if ($nObj[0] == pdf_parser::TYPE_STREAM) {
  316. $this->_writeValue($nObj);
  317. } else {
  318. $this->_writeValue($nObj[1]);
  319. }
  320. $this->_out("\nendobj");
  321. $this->_objStack[$filename][$n] = null; // free memory
  322. unset($this->_objStack[$filename][$n]);
  323. reset($this->_objStack[$filename]);
  324. }
  325. }
  326. }
  327. /**
  328. * Writes the form XObjects to the PDF document.
  329. */
  330. protected function _putformxobjects()
  331. {
  332. $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
  333. reset($this->_tpls);
  334. foreach($this->_tpls AS $tplIdx => $tpl) {
  335. $this->_newobj();
  336. $currentN = $this->n; // TCPDF/Protection: rem current "n"
  337. $this->_tpls[$tplIdx]['n'] = $this->n;
  338. $this->_out('<<' . $filter . '/Type /XObject');
  339. $this->_out('/Subtype /Form');
  340. $this->_out('/FormType 1');
  341. $this->_out(sprintf('/BBox [%.2F %.2F %.2F %.2F]',
  342. (isset($tpl['box']['llx']) ? $tpl['box']['llx'] : $tpl['x']) * $this->k,
  343. (isset($tpl['box']['lly']) ? $tpl['box']['lly'] : -$tpl['y']) * $this->k,
  344. (isset($tpl['box']['urx']) ? $tpl['box']['urx'] : $tpl['w'] + $tpl['x']) * $this->k,
  345. (isset($tpl['box']['ury']) ? $tpl['box']['ury'] : $tpl['h'] - $tpl['y']) * $this->k
  346. ));
  347. $c = 1;
  348. $s = 0;
  349. $tx = 0;
  350. $ty = 0;
  351. if (isset($tpl['box'])) {
  352. $tx = -$tpl['box']['llx'];
  353. $ty = -$tpl['box']['lly'];
  354. if ($tpl['_rotationAngle'] <> 0) {
  355. $angle = $tpl['_rotationAngle'] * M_PI/180;
  356. $c = cos($angle);
  357. $s = sin($angle);
  358. switch($tpl['_rotationAngle']) {
  359. case -90:
  360. $tx = -$tpl['box']['lly'];
  361. $ty = $tpl['box']['urx'];
  362. break;
  363. case -180:
  364. $tx = $tpl['box']['urx'];
  365. $ty = $tpl['box']['ury'];
  366. break;
  367. case -270:
  368. $tx = $tpl['box']['ury'];
  369. $ty = -$tpl['box']['llx'];
  370. break;
  371. }
  372. }
  373. } else if ($tpl['x'] != 0 || $tpl['y'] != 0) {
  374. $tx = -$tpl['x'] * 2;
  375. $ty = $tpl['y'] * 2;
  376. }
  377. $tx *= $this->k;
  378. $ty *= $this->k;
  379. if ($c != 1 || $s != 0 || $tx != 0 || $ty != 0) {
  380. $this->_out(sprintf('/Matrix [%.5F %.5F %.5F %.5F %.5F %.5F]',
  381. $c, $s, -$s, $c, $tx, $ty
  382. ));
  383. }
  384. $this->_out('/Resources ');
  385. if (isset($tpl['resources'])) {
  386. $this->currentParser = $tpl['parser'];
  387. $this->_writeValue($tpl['resources']); // "n" will be changed
  388. } else {
  389. $this->_out('<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
  390. if (isset($this->_res['tpl'][$tplIdx])) {
  391. $res = $this->_res['tpl'][$tplIdx];
  392. if (isset($res['fonts']) && count($res['fonts'])) {
  393. $this->_out('/Font <<');
  394. foreach ($res['fonts'] as $font)
  395. $this->_out('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R');
  396. $this->_out('>>');
  397. }
  398. if (isset($res['images']) && count($res['images']) ||
  399. isset($res['tpls']) && count($res['tpls']))
  400. {
  401. $this->_out('/XObject <<');
  402. if (isset($res['images'])) {
  403. foreach ($res['images'] as $image)
  404. $this->_out('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R');
  405. }
  406. if (isset($res['tpls'])) {
  407. foreach ($res['tpls'] as $i => $_tpl)
  408. $this->_out($this->tplPrefix . $i . ' ' . $_tpl['n'] . ' 0 R');
  409. }
  410. $this->_out('>>');
  411. }
  412. $this->_out('>>');
  413. }
  414. }
  415. if (isset($tpl['groupXObject']) && $tpl['groupXObject']) {
  416. $this->_out('/Group <</Type/Group/S/Transparency>>');
  417. }
  418. $newN = $this->n; // TCPDF: rem new "n"
  419. $this->n = $currentN; // TCPDF: reset to current "n"
  420. $buffer = ($this->compress) ? gzcompress($tpl['buffer']) : $tpl['buffer'];
  421. if (is_subclass_of($this, 'TCPDF')) {
  422. $buffer = $this->_getrawstream($buffer);
  423. $this->_out('/Length ' . strlen($buffer) . ' >>');
  424. $this->_out("stream\n" . $buffer . "\nendstream");
  425. } else {
  426. $this->_out('/Length ' . strlen($buffer) . ' >>');
  427. $this->_putstream($buffer);
  428. }
  429. $this->_out('endobj');
  430. $this->n = $newN; // TCPDF: reset to new "n"
  431. }
  432. $this->_putimportedobjects();
  433. }
  434. /**
  435. * Creates and optionally write the object definition to the document.
  436. *
  437. * Rewritten to handle existing own defined objects
  438. *
  439. * @param bool $objId
  440. * @param bool $onlyNewObj
  441. * @return bool|int
  442. */
  443. public function _newobj($objId = false, $onlyNewObj = false)
  444. {
  445. if (!$objId) {
  446. $objId = ++$this->n;
  447. }
  448. // Begin a new object
  449. if (!$onlyNewObj) {
  450. $this->offsets[$objId] = is_subclass_of($this, 'TCPDF') ? $this->bufferlen : strlen($this->buffer);
  451. $this->_out($objId . ' 0 obj');
  452. $this->_currentObjId = $objId; // for later use with encryption
  453. }
  454. return $objId;
  455. }
  456. /**
  457. * Writes a PDF value to the resulting document.
  458. *
  459. * Needed to rebuild the source document
  460. *
  461. * @param mixed $value A PDF-Value. Structure of values see cases in this method
  462. */
  463. protected function _writeValue(&$value)
  464. {
  465. if (is_subclass_of($this, 'TCPDF')) {
  466. parent::_prepareValue($value);
  467. }
  468. switch ($value[0]) {
  469. case pdf_parser::TYPE_TOKEN:
  470. $this->_straightOut($value[1] . ' ');
  471. break;
  472. case pdf_parser::TYPE_NUMERIC:
  473. case pdf_parser::TYPE_REAL:
  474. if (is_float($value[1]) && $value[1] != 0) {
  475. $this->_straightOut(rtrim(rtrim(sprintf('%F', $value[1]), '0'), '.') . ' ');
  476. } else {
  477. $this->_straightOut($value[1] . ' ');
  478. }
  479. break;
  480. case pdf_parser::TYPE_ARRAY:
  481. // An array. Output the proper
  482. // structure and move on.
  483. $this->_straightOut('[');
  484. for ($i = 0; $i < count($value[1]); $i++) {
  485. $this->_writeValue($value[1][$i]);
  486. }
  487. $this->_out(']');
  488. break;
  489. case pdf_parser::TYPE_DICTIONARY:
  490. // A dictionary.
  491. $this->_straightOut('<<');
  492. reset ($value[1]);
  493. while (list($k, $v) = each($value[1])) {
  494. $this->_straightOut($k . ' ');
  495. $this->_writeValue($v);
  496. }
  497. $this->_straightOut('>>');
  498. break;
  499. case pdf_parser::TYPE_OBJREF:
  500. // An indirect object reference
  501. // Fill the object stack if needed
  502. $cpfn =& $this->currentParser->filename;
  503. if (!isset($this->_doneObjStack[$cpfn][$value[1]])) {
  504. $this->_newobj(false, true);
  505. $this->_objStack[$cpfn][$value[1]] = array($this->n, $value);
  506. $this->_doneObjStack[$cpfn][$value[1]] = array($this->n, $value);
  507. }
  508. $objId = $this->_doneObjStack[$cpfn][$value[1]][0];
  509. $this->_out($objId . ' 0 R');
  510. break;
  511. case pdf_parser::TYPE_STRING:
  512. // A string.
  513. $this->_straightOut('(' . $value[1] . ')');
  514. break;
  515. case pdf_parser::TYPE_STREAM:
  516. // A stream. First, output the
  517. // stream dictionary, then the
  518. // stream data itself.
  519. $this->_writeValue($value[1]);
  520. $this->_out('stream');
  521. $this->_out($value[2][1]);
  522. $this->_straightOut("endstream");
  523. break;
  524. case pdf_parser::TYPE_HEX:
  525. $this->_straightOut('<' . $value[1] . '>');
  526. break;
  527. case pdf_parser::TYPE_BOOLEAN:
  528. $this->_straightOut($value[1] ? 'true ' : 'false ');
  529. break;
  530. case pdf_parser::TYPE_NULL:
  531. // The null object.
  532. $this->_straightOut('null ');
  533. break;
  534. }
  535. }
  536. /**
  537. * Modified _out() method so not each call will add a newline to the output.
  538. */
  539. protected function _straightOut($s)
  540. {
  541. if (!is_subclass_of($this, 'TCPDF')) {
  542. if ($this->state == 2) {
  543. $this->pages[$this->page] .= $s;
  544. } else {
  545. $this->buffer .= $s;
  546. }
  547. } else {
  548. if ($this->state == 2) {
  549. if ($this->inxobj) {
  550. // we are inside an XObject template
  551. $this->xobjects[$this->xobjid]['outdata'] .= $s;
  552. } else if ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
  553. // puts data before page footer
  554. $pagebuff = $this->getPageBuffer($this->page);
  555. $page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
  556. $footer = substr($pagebuff, -$this->footerlen[$this->page]);
  557. $this->setPageBuffer($this->page, $page . $s . $footer);
  558. // update footer position
  559. $this->footerpos[$this->page] += strlen($s);
  560. } else {
  561. // set page data
  562. $this->setPageBuffer($this->page, $s, true);
  563. }
  564. } else if ($this->state > 0) {
  565. // set general data
  566. $this->setBuffer($s);
  567. }
  568. }
  569. }
  570. /**
  571. * Ends the document
  572. *
  573. * Overwritten to close opened parsers
  574. */
  575. public function _enddoc()
  576. {
  577. parent::_enddoc();
  578. $this->_closeParsers();
  579. }
  580. /**
  581. * Close all files opened by parsers.
  582. *
  583. * @return boolean
  584. */
  585. protected function _closeParsers()
  586. {
  587. if ($this->state > 2) {
  588. $this->cleanUp();
  589. return true;
  590. }
  591. return false;
  592. }
  593. /**
  594. * Removes cycled references and closes the file handles of the parser objects.
  595. */
  596. public function cleanUp()
  597. {
  598. while (($parser = array_pop($this->parsers)) !== null) {
  599. /**
  600. * @var fpdi_pdf_parser $parser
  601. */
  602. $parser->closeFile();
  603. }
  604. }
  605. }