693 rindas
22KB

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