Dashboard sipadu mbip
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

fpdf_merge.php 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785
  1. <?php
  2. /**
  3. * FPDF_Merge
  4. * Tool to merge PDFs created by fpdf 1.6
  5. *
  6. * @version 1.0
  7. * @date 2011-02-18
  8. * @author DEMONTE Jean-Baptiste <jbdemonte@gmail.com>
  9. * @copyright © Digitick <www.digitick.net> 2011
  10. * @license GNU Lesser General Public License v3.0
  11. *
  12. * Why this tool ?
  13. *
  14. * All the library tested (fpdi, ...) produce too heavy pdf
  15. * because, these are not optimized.
  16. * This library parses the pages, get the objects included (font, images)
  17. * generate a hash to store only once these objects or reuse previous stored ones.
  18. *
  19. *
  20. * Notes
  21. *
  22. * - links are not supported in this version
  23. * - all pages are included (because this was what we needed) but update it
  24. * to create an add(array pages) should be easy
  25. *
  26. *
  27. * I tried to optimize a lot this tool using X-Debug, let me know if you do best :)
  28. * If you get trouble or want to comment, feel free to send me an email.
  29. *
  30. * Use
  31. *
  32. * $merge = new FPDF_Merge();
  33. * $merge->add('/tmp/pdf-1.pdf');
  34. * $merge->add('/tmp/pdf-2.pdf');
  35. * $merge->output('/tmp/pdf-merge.pdf'); // or $merge->output(); to open it directly in the browser
  36. *
  37. **/
  38. class FPDF_Merge{
  39. const TYPE_NULL = 0,
  40. TYPE_TOKEN = 1,
  41. TYPE_REFERENCE = 2,
  42. TYPE_REFERENCE_F= 3,
  43. TYPE_NUMERIC = 4,
  44. TYPE_HEX = 5,
  45. TYPE_BOOL = 6,
  46. TYPE_STRING = 7,
  47. TYPE_ARRAY = 8,
  48. TYPE_DICTIONARY = 9,
  49. TYPE_STREAM = 10;
  50. private $buffer, $compress, $fonts, $objects, $pages, $ref, $n, $xref;
  51. /**************************************************
  52. /* CONSTRUCTOR
  53. /**************************************************/
  54. public function __construct(){
  55. $this->buffer = '';
  56. $this->fonts = array();
  57. $this->objects = array();
  58. $this->pages = array();
  59. $this->ref = array();
  60. $this->xref = array();
  61. $this->n = 0;
  62. $this->compress = function_exists('gzcompress');
  63. }
  64. /**************************************************
  65. /* PRIVATE
  66. /**************************************************/
  67. private function error($msg){
  68. throw new Exception($msg);
  69. die;
  70. }
  71. //================================================
  72. // FONCTIONS D'IMPORT
  73. //================================================
  74. private function parse($buffer, &$len, &$off){
  75. if ($len === $off) {
  76. return null;
  77. }
  78. if (!preg_match('`\s*(.)`', $buffer, $m, PREG_OFFSET_CAPTURE, $off)) return null;
  79. $off = $m[1][1];
  80. switch($buffer[$off]){
  81. case '<':
  82. if ($buffer[$off+1] === '<'){
  83. // dictionnary
  84. $v = array();
  85. $off+=2;
  86. while(1){
  87. $key = $this->parse($buffer, $len, $off);
  88. if ($key === null) break;
  89. if ($key[0] !== self::TYPE_TOKEN) break;
  90. $value = $this->parse($buffer, $len, $off);
  91. $v[$key[1]] = $value;
  92. }
  93. $off+=2;
  94. return array(self::TYPE_DICTIONARY, $v);
  95. } else {
  96. // hex
  97. $p = strpos($buffer, '>', $off);
  98. if ($p !== false){
  99. $v = substr($buffer, $off+1, $p - $off - 1);
  100. $off = $p + 1;
  101. return array(self::TYPE_HEX, $v);
  102. }
  103. }
  104. break;
  105. case '(':
  106. // string
  107. $p = $off;
  108. while(1){
  109. $p++;
  110. if ($p === $len) break;
  111. if (($buffer[$p] === ')') && ($buffer[$p-1] !== '\\')) break;
  112. }
  113. if ($p < $len){
  114. $v = substr($buffer, $off+1, $p - $off - 1);
  115. $off = $p + 1;
  116. return array(self::TYPE_STRING, $v);
  117. }
  118. break;
  119. case '[':
  120. $v = array();
  121. $off++; // jump the [
  122. while(1){
  123. $value = $this->parse($buffer, $len, $off);
  124. if ($value === null) break;
  125. $v[] = $value;
  126. }
  127. $off++; // jump the ]
  128. return array(self::TYPE_ARRAY, $v);
  129. break;
  130. case '>': // dictionnary : end
  131. case ']': // array : end
  132. return null;
  133. break;
  134. case '%': // comments : jump
  135. $p = strpos($buffer, "\n", $off);
  136. if ($p !== false){
  137. $off = $p + 1;
  138. return $this->parse($buffer, $len, $off);
  139. }
  140. break;
  141. default:
  142. if (preg_match('`^\s*([0-9]+) 0 R`', substr($buffer, $off, 32), $m)){
  143. $off += strlen($m[0]);
  144. return array(self::TYPE_REFERENCE, $m[1]);
  145. } else {
  146. $p = strcspn($buffer, " %[]<>()\r\n\t/", $off+1);
  147. $v = substr($buffer, $off, $p+1);
  148. $off += $p+1;
  149. if ( is_numeric($v) ){
  150. $type = self::TYPE_NUMERIC;
  151. } else if ( ($v === 'true') || ($v === 'false') ){
  152. $type = self::TYPE_BOOL;
  153. } else if ( $v === 'null' ){
  154. $type = self::TYPE_NULL;
  155. } else {
  156. $type = self::TYPE_TOKEN;
  157. }
  158. return array($type, $v);
  159. }
  160. break;
  161. }
  162. return null;
  163. }
  164. private function getObject($f, $xref, $index, $includeSubObject = false){
  165. $type = self::TYPE_TOKEN;
  166. if (!isset($xref[$index])){
  167. $this->error('reference d\'object inconnue');
  168. }
  169. fseek($f, $xref[$index]);
  170. $data = '';
  171. $len = 0;
  172. $offset = 0;
  173. $expLen = 1024;
  174. do{
  175. $prev = $len;
  176. $data .= fread($f, $expLen);
  177. $len = strlen($data);
  178. $p = strpos($data, "endobj", $offset);
  179. if ($p !== false){
  180. if ( $data[$p-1] !== "\n" ){
  181. $offset = $p + 6;
  182. $p = false;
  183. } else {
  184. if ($len < $p + 8){
  185. $data .= fread($f, 1);
  186. $len = strlen($data);
  187. }
  188. if ($data[$p+6] !== "\n"){
  189. $offset = $p + 6; // not the endobj markup, maybe a string content
  190. $p = false;
  191. }
  192. }
  193. }
  194. $expLen *= 2;
  195. }while( ($p === false) && ($prev !== $len) );
  196. if ($p === false){
  197. $this->error('object ['.$index.'] non trouve');
  198. }
  199. $p--;
  200. $data = substr($data,0, $p);
  201. if (!preg_match('`^([0-9]+ 0 obj)`', $data, $m, PREG_OFFSET_CAPTURE)){
  202. $this->error('object ['.$index.'] invalide');
  203. }
  204. $p = $m[0][1] + strlen($m[1][0]) + 1;
  205. $data = substr($data, $p);
  206. if (substr($data, 0, 2) === '<<') {
  207. $type = self::TYPE_DICTIONARY;
  208. $off = 0;
  209. $len = strlen($data);
  210. $dictionary = $this->parse($data, $len, $off);
  211. $off++;
  212. $data = substr($data, $off);
  213. if ($data === false) {
  214. $data = '';
  215. } else if (substr($data, 0, 7) === "stream\n"){
  216. $data = substr($data, 7, strlen($data) - 17);
  217. $type = self::TYPE_STREAM;
  218. }
  219. if ( $includeSubObject ){
  220. $dictionary = $this->_resolveValues($f, $xref, $dictionary);
  221. }
  222. } else {
  223. $dictionary = null;
  224. }
  225. return array($type, $dictionary, $data);
  226. }
  227. private function _resolveValues($f, $xref, $item){
  228. switch($item[0]){
  229. case self::TYPE_REFERENCE:
  230. $object = $this->getObject($f, $xref, $item[1], true);
  231. if ($object[0] === self::TYPE_TOKEN){
  232. return array(self::TYPE_TOKEN, $object[2]);
  233. }
  234. $ref = $this->storeObject($object);
  235. return array(self::TYPE_REFERENCE_F, $this->_getObjectType($object), $ref);
  236. break;
  237. case self::TYPE_ARRAY:
  238. case self::TYPE_DICTIONARY:
  239. $r = array();
  240. foreach($item[1] as $key => $val){
  241. if ( ($val[0] == self::TYPE_REFERENCE) ||
  242. ($val[0] == self::TYPE_ARRAY) ||
  243. ($val[0] == self::TYPE_DICTIONARY) ){
  244. $r[$key] = $this->_resolveValues($f, $xref, $val);
  245. } else {
  246. $r[$key] = $val;
  247. }
  248. }
  249. return array($item[0], $r);
  250. break;
  251. default:
  252. return $item;
  253. }
  254. }
  255. private function getResources($f, $xref, $page){
  256. if ($page[0] !== self::TYPE_DICTIONARY){
  257. $this->error('getResources necessite un dictionaire');
  258. }
  259. if (isset($page[1]['/Resources'])){
  260. if ($page[1]['/Resources'][0] === self::TYPE_REFERENCE){
  261. return $this->getObject($f, $xref, $page[1]['/Resources'][1]);
  262. } else {
  263. return array($page[1]['/Resources'][1]);
  264. }
  265. } else if (isset($page[1]['/Parent'])){
  266. return $this->getResources($f, $xref, $page[1]['/Parent']);
  267. }
  268. return null;
  269. }
  270. private function getContent($f, $xref, $page){
  271. if ($page[0] !== self::TYPE_DICTIONARY){
  272. $this->error('getContent necessite un dictionaire');
  273. }
  274. $stream = '';
  275. if (isset($page[1]['/Contents'])){
  276. $stream = $this->_getContent($f, $xref, $page[1]['/Contents']);
  277. }
  278. return $stream;
  279. }
  280. private function _getContent($f, $xref, $content){
  281. $stream = '';
  282. if ($content[0] === self::TYPE_REFERENCE){
  283. $stream .= $this->getStream($f, $xref, $this->getObject($f, $xref, $content[1]));
  284. } else if ($content[0] === self::TYPE_ARRAY){
  285. foreach($content[1] as $sub){
  286. $stream .= $this->_getContent($f, $xref, $sub);
  287. }
  288. } else {
  289. $stream .= $this->getStream($f, $xref, $item);
  290. }
  291. return $stream;
  292. }
  293. private function getCompression($f, $xref, $item){
  294. if ($item[0] === self::TYPE_TOKEN){
  295. return array($item[1]);
  296. } else if ($item[0] === self::TYPE_ARRAY){
  297. $r = array();
  298. foreach($item[1] as $sub){
  299. $r = array_merge($r, $this->getCompression($f, $xref, $sub));
  300. }
  301. return $r;
  302. } else if ($item[0] === self::TYPE_REFERENCE){
  303. return $this->getCompression($f, $xref, $this->getObject($f, $xref, $item[1]));
  304. }
  305. return array();
  306. }
  307. private function getStream($f, $xref, $item){
  308. $methods = isset($item[1][1]['/Filter']) ? $this->getCompression($f, $xref, $item[1][1]['/Filter']) : array();
  309. $raw = $item[2];
  310. foreach($methods as $method){
  311. switch ($method) {
  312. case '/FlateDecode':
  313. if (function_exists('gzuncompress')) {
  314. $raw = !empty($raw) ? @gzuncompress($raw) : '';
  315. } else {
  316. $this->error('gzuncompress necessaire pour decompresser ce stream');
  317. }
  318. if ($raw === false) {
  319. $this->error('erreur de decompression du stream');
  320. }
  321. break;
  322. default:
  323. $this->error($method . ' necessaire pour decompresser ce stream');
  324. }
  325. }
  326. return $raw;
  327. }
  328. private function storeObject($item, $type = false){
  329. $md5 = md5(serialize($item));
  330. if ($type === '/Font'){
  331. $array = & $this->fonts;
  332. $prefix = '/F';
  333. } else {
  334. $array = & $this->objects;
  335. $prefix = '/Obj';
  336. }
  337. if (!isset($array[$md5])){
  338. $index = count($array) + 1;
  339. $array[$md5] = array(
  340. 'name' => $prefix . $index,
  341. 'item' => $item,
  342. 'type' => $type,
  343. 'index' => $index
  344. );
  345. } else if ($type){
  346. $array[$md5]['type'] = $type;
  347. }
  348. return $array[$md5][$type ? 'name' : 'index'];
  349. }
  350. //================================================
  351. // FONCTIONS D'IMPRESSION
  352. //================================================
  353. private function _out($raw){
  354. $this->buffer .= $raw . "\n";
  355. }
  356. private function _strval($value){
  357. $value+=0;
  358. if ($value){
  359. return strval($value);
  360. }
  361. return '0';
  362. }
  363. private function _toStream($item){
  364. switch($item[0]){
  365. case self::TYPE_NULL : return 'null';
  366. case self::TYPE_TOKEN : return $item[1];
  367. case self::TYPE_REFERENCE : return $this->_strval($item[1]) . ' 0 R';
  368. case self::TYPE_REFERENCE_F :
  369. if (!isset($this->ref[ $item[1] ][ $item[2] ])){
  370. $this->error('reference vers un object non sauve');
  371. }
  372. return $this->_strval($this->ref[ $item[1] ][ $item[2] ]) . ' 0 R';
  373. case self::TYPE_NUMERIC : return $this->_strval($item[1]);
  374. case self::TYPE_HEX : return '<'.strval($item[1]).'>';
  375. case self::TYPE_BOOL : return $item[1] ? 'true' : 'false';
  376. case self::TYPE_STRING : return '(' . str_replace(array('\\', '(', ')'), array('\\\\', '\\(', '\\)'), strval($item[1])) . ')';
  377. case self::TYPE_ARRAY :
  378. $r = array();
  379. foreach($item[1] as $val){
  380. $r[] = $this->_toStream($val);
  381. }
  382. return '[' . implode(' ', $r) . ']';
  383. case self::TYPE_DICTIONARY :
  384. $r = array();
  385. foreach($item[1] as $key => $val){
  386. $val = $this->_toStream($val);
  387. $r[] = $key . ' ' . $val;
  388. }
  389. return '<<' . implode("\n", $r) . '>>';
  390. break;
  391. }
  392. return '';
  393. }
  394. private function _newobj($n = null){
  395. if ( ($n === null) || ($n === true) ){
  396. $this->n++;
  397. $id = $this->n;
  398. } else {
  399. $id = $n;
  400. }
  401. if ($n !== true){
  402. $this->xref[ $id ] = strlen($this->buffer);
  403. $this->_out($id . ' 0 obj');
  404. }
  405. return $id;
  406. }
  407. private function _addObj($dico = null, $buf = null){
  408. $ref = $this->_newobj();
  409. $buf = empty($buf) && ($buf !== 0) && ($buf !== '0') ? null : $buf;
  410. if (is_array($dico)){
  411. if ($buf !== null){
  412. if ($this->compress && !isset($dico['/Filter'])) {
  413. $buf = gzcompress($buf);
  414. $dico['/Filter'] = array(self::TYPE_TOKEN, '/FlateDecode');
  415. }
  416. $dico['/Length'] = array(self::TYPE_NUMERIC, strlen($buf));
  417. }
  418. $this->_out($this->_toStream(array(self::TYPE_DICTIONARY, $dico)));
  419. }
  420. if ($buf !== null){
  421. $this->_out('stream');
  422. $this->_out($buf);
  423. $this->_out('endstream');
  424. }
  425. $this->_out('endobj');
  426. return $ref;
  427. }
  428. private function _getObjectType($object){
  429. return isset($object['type']) && !empty($object['type']) ? $object['type'] : 'default';
  430. }
  431. private function _putObject($object){
  432. $type = $this->_getObjectType($object);
  433. if (!isset($this->ref[$type])){
  434. $this->ref[$type] = array();
  435. }
  436. $this->ref[$type][ $object['index'] ] = $this->_addObj($object['item'][1][1], $object['item'][2]);
  437. }
  438. private function _putObjects(){
  439. foreach($this->objects as $object){
  440. if ($object['type']) continue;
  441. $this->_putObject($object);
  442. }
  443. foreach($this->objects as $object){
  444. if (!$object['type']) continue;
  445. $this->_putObject($object);
  446. }
  447. foreach($this->fonts as $object){
  448. $this->_putObject($object);
  449. }
  450. }
  451. private function _putResources(){
  452. $dico = array(
  453. '/ProcSet' => array(
  454. self::TYPE_ARRAY,
  455. array(
  456. array(self::TYPE_TOKEN, '/PDF'),
  457. array(self::TYPE_TOKEN, '/Text'),
  458. array(self::TYPE_TOKEN, '/ImageB'),
  459. array(self::TYPE_TOKEN, '/ImageC'),
  460. array(self::TYPE_TOKEN, '/ImageI')
  461. )
  462. )
  463. );
  464. $xObjects = array();
  465. foreach($this->objects as $index => $object){
  466. if ($object['type'] === false){
  467. continue;
  468. }
  469. $value = array(
  470. self::TYPE_TOKEN,
  471. $this->_toStream(array(self::TYPE_REFERENCE, $this->ref[ $object['type'] ][ $object['index'] ]))
  472. );
  473. if ($object['type'] === '/XObject'){
  474. $xObjects[$object['name']] = $value;
  475. }
  476. }
  477. if (!empty($xObjects)){
  478. $dico['/XObject'] = array(self::TYPE_DICTIONARY, $xObjects);
  479. }
  480. $fonts = array();
  481. foreach($this->fonts as $index => $object){
  482. $value = array(
  483. self::TYPE_TOKEN,
  484. $this->_toStream(array(self::TYPE_REFERENCE, $this->ref[ '/Font' ][ $object['index'] ]))
  485. );
  486. $fonts[$object['name']] = $value;
  487. }
  488. if (!empty($fonts)){
  489. $dico['/Font'] = array(self::TYPE_DICTIONARY, $fonts);
  490. }
  491. return $this->_addObj($dico);
  492. }
  493. /**************************************************
  494. /* PUBLIC
  495. /**************************************************/
  496. public function add( $filename ){
  497. $f = @fopen($filename, 'rb');
  498. if (!$f) {
  499. $this->error('impossible d\'ouvrir le fichier');
  500. }
  501. fseek($f, 0, SEEK_END);
  502. $fileLength = ftell($f);
  503. // Localisation de xref
  504. //-------------------------------------------------
  505. fseek($f, -128, SEEK_END);
  506. $data = fread($f, 128);
  507. if ($data === false) {
  508. return $this->error('erreur de lecture dans le fichier');
  509. }
  510. $p = strripos($data, 'startxref');
  511. if ($p === false){
  512. return $this->error('startxref absent');
  513. }
  514. $startxref = substr($data, $p+10, strlen($data) - $p - 17);
  515. $posStartxref = $fileLength - 128 + $p;
  516. // extraction de xref + trailer
  517. //-------------------------------------------------
  518. fseek($f, $startxref);
  519. $data = fread($f, $posStartxref - $startxref);
  520. // extraction du trailer
  521. //-------------------------------------------------
  522. $p = stripos($data, 'trailer');
  523. if ($p === false){
  524. return $this->error('trailer absent');
  525. }
  526. $dataTrailer = substr($data, $p + 8);
  527. $len = strlen($dataTrailer);
  528. $off = 0;
  529. $trailer = $this->parse($dataTrailer, $len, $off);
  530. // extraction du xref
  531. //-------------------------------------------------
  532. $data = explode("\n", trim(substr($data, 0, $p)));
  533. array_shift($data); // "xref"
  534. $cnt = 0;
  535. $xref = array();
  536. foreach($data as $line){
  537. if (!$cnt) {
  538. if (preg_match('`^([0-9]+) ([0-9]+)$`', $line, $m)){
  539. $index = intval($m[1]) - 1;
  540. $cnt = intval($m[2]);
  541. } else {
  542. $this->error('erreur dans xref');
  543. }
  544. } else {
  545. $index++;
  546. $cnt--;
  547. if (preg_match('`^([0-9]{10}) [0-9]{5} ([n|f])`', $line, $m)){
  548. if ($m[2] === 'f') {
  549. continue;
  550. }
  551. $xref[ $index ] = $m[1];
  552. } else {
  553. $this->error('erreur dans xref : ' . $line);
  554. }
  555. }
  556. }
  557. // Lecture des pages
  558. //-------------------------------------------------
  559. $root = $this->getObject($f, $xref, $trailer[1]['/Root'][1]);
  560. $root = $root[1][1];
  561. $pages = $this->getObject($f, $xref, $root['/Pages'][1]);
  562. $pages = $pages[1][1];
  563. foreach($pages['/Kids'][1] as $kid){
  564. $kid = $this->getObject($f, $xref, $kid[1]);
  565. $kid = $kid[1];
  566. $resources = $this->getResources($f, $xref, $kid);
  567. $resources = $resources[1][1];
  568. $content = $this->getContent($f, $xref, $kid);
  569. // traitement des fonts
  570. //-------------------------------------------------
  571. $newFonts = array();
  572. if (isset($resources['/Font']) && !empty($resources['/Font'])){
  573. if (preg_match_all("`(/F[0-9]+)\s+-?[0-9\.]+\s+Tf`", $content, $matches, PREG_OFFSET_CAPTURE)){
  574. $newContent = '';
  575. $offset = 0;
  576. $cnt = count($matches[0]);
  577. for($i=0; $i<$cnt; $i++){
  578. $position = $matches[0][$i][1];
  579. $name = $matches[1][$i][0];
  580. if (!isset($newFonts[$name])){
  581. $object = $this->getObject($f, $xref, $resources['/Font'][1][$name][1], true);
  582. $newFonts[$name] = $this->storeObject($object, '/Font');
  583. }
  584. if ($newFonts[$name] !== $name){
  585. $newContent .= substr($content, $offset, $position - $offset);
  586. $newContent .= $newFonts[$name];
  587. $offset = $position + strlen($name);
  588. }
  589. }
  590. $content = $newContent . substr($content, $offset);
  591. }
  592. }
  593. // traitement des XObjets
  594. //-------------------------------------------------
  595. $newXObjects = array();
  596. if (isset($resources['/XObject']) && !empty($resources['/XObject'])){
  597. if (preg_match_all("`(/[^%\[\]<>\(\)\r\n\t/]+) Do`", $content, $matches, PREG_OFFSET_CAPTURE)){
  598. $newContent = '';
  599. $offset = 0;
  600. foreach($matches[1] as $m){
  601. $name = $m[0];
  602. $position = $m[1];
  603. if (!isset($newXObjects[$name])){
  604. $object = $this->getObject($f, $xref, $resources['/XObject'][1][$name][1], true);
  605. $newXObjects[$name] = $this->storeObject($object, '/XObject');
  606. }
  607. if ($newXObjects[$name] !== $name){
  608. $newContent .= substr($content, $offset, $position - $offset);
  609. $newContent .= $newXObjects[$name];
  610. $offset = $position + strlen($name);
  611. }
  612. }
  613. $content = $newContent . substr($content, $offset);
  614. }
  615. }
  616. $mediaBox = isset($kid[1]['/MediaBox']) ? $kid[1]['/MediaBox'] : (isset($pages['/MediaBox']) ? $pages['/MediaBox'] : null);
  617. if ($mediaBox[0] !== self::TYPE_ARRAY){
  618. $this->error('MediaBox non definie');
  619. }
  620. $this->pages[] = array(
  621. 'content' => $content,
  622. '/XObject' => array_values($newXObjects),
  623. '/Font' => array_values($newFonts),
  624. '/MediaBox' => $mediaBox
  625. );
  626. }
  627. fclose($f);
  628. }
  629. public function output($filename = null){
  630. $this->_out('%PDF-1.6');
  631. $this->_putObjects();
  632. $rsRef = $this->_putResources();
  633. $ptRef = $this->_newobj(true);
  634. $kids = array();
  635. // Ajout des pages
  636. $n = count($this->pages);
  637. for($i=0; $i<$n; $i++){
  638. $ctRef = $this->_addObj(array(), $this->pages[$i]['content']);
  639. $dico = array(
  640. '/Type' => array(self::TYPE_TOKEN, '/Page'),
  641. '/Parent' => array(self::TYPE_REFERENCE, $ptRef),
  642. '/MediaBox' => $this->pages[$i]['/MediaBox'],
  643. '/Resources'=> array(self::TYPE_REFERENCE, $rsRef),
  644. '/Contents' => array(self::TYPE_REFERENCE, $ctRef),
  645. );
  646. $kids[] = array(self::TYPE_REFERENCE, $this->_addObj($dico));
  647. }
  648. // Ajout du page tree
  649. $ptDico = array(
  650. self::TYPE_DICTIONARY,
  651. array(
  652. '/Type' => array(self::TYPE_TOKEN, '/Pages'),
  653. '/Kids' => array(self::TYPE_ARRAY, $kids),
  654. '/Count' => array(self::TYPE_NUMERIC, count($kids))
  655. )
  656. );
  657. $this->_newobj($ptRef);
  658. $this->_out($this->_toStream($ptDico));
  659. $this->_out('endobj');
  660. // Ajout du catalogue
  661. $ctDico = array(
  662. self::TYPE_DICTIONARY,
  663. array(
  664. '/Type' => array(self::TYPE_TOKEN, '/Calalog'),
  665. '/Pages'=> array(self::TYPE_REFERENCE, $ptRef)
  666. )
  667. );
  668. $ctRef = $this->_newobj();
  669. $this->_out($this->_toStream($ctDico));
  670. $this->_out('endobj');
  671. // Ajout du xref
  672. $xrefOffset = strlen($this->buffer);
  673. $count = count($this->xref);
  674. $this->_out('xref');
  675. $this->_out('0 ' . ($count+1));
  676. $this->_out('0000000000 65535 f ');
  677. for($i=0; $i<$count; $i++){
  678. $this->_out(sprintf('%010d 00000 n ',$this->xref[$i+1]));
  679. }
  680. // Ajout du trailer
  681. $dico = array(
  682. '/Size' => array(self::TYPE_NUMERIC, 1+count($this->xref)),
  683. '/Root' => array(self::TYPE_REFERENCE, $ctRef)
  684. );
  685. $this->_out('trailer');
  686. $this->_out($this->_toStream(array(self::TYPE_DICTIONARY, $dico)));
  687. // Ajout du startxref
  688. $this->_out('startxref');
  689. $this->_out($xrefOffset);
  690. $this->_out('%%EOF');
  691. if ($filename === null){
  692. header('Content-Type: application/pdf');
  693. header('Content-Length: '.strlen($this->buffer));
  694. header('Cache-Control: private, max-age=0, must-revalidate');
  695. header('Pragma: public');
  696. ini_set('zlib.output_compression','0');
  697. echo $this->buffer;
  698. die;
  699. } else {
  700. file_put_contents($filename, $this->buffer);
  701. }
  702. }
  703. }
  704. ?>