fpdi.php 21 KB

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