1<?php 2// 3// TCPDI - Version 1.0 4// Based on FPDI - Version 1.4.4 5// 6// Copyright 2004-2013 Setasign - Jan Slabon 7// 8// Licensed under the Apache License, Version 2.0 (the "License"); 9// you may not use this file except in compliance with the License. 10// You may obtain a copy of the License at 11// 12// https://www.apache.org/licenses/LICENSE-2.0 13// 14// Unless required by applicable law or agreed to in writing, software 15// distributed under the License is distributed on an "AS IS" BASIS, 16// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17// See the License for the specific language governing permissions and 18// limitations under the License. 19// 20 21// Dummy shim to allow unmodified use of fpdf_tpl 22class FPDF extends TCPDF {} 23 24require_once('fpdf_tpl.php'); 25 26require_once('tcpdi_parser.php'); 27 28 29class TCPDI extends FPDF_TPL { 30 /** 31 * Actual filename 32 * @var string 33 */ 34 var $current_filename; 35 36 /** 37 * Parser-Objects 38 * @var array 39 */ 40 var $parsers; 41 42 /** 43 * Current parser 44 * @var object 45 */ 46 var $current_parser; 47 48 /** 49 * object stack 50 * @var array 51 */ 52 var $_obj_stack; 53 54 /** 55 * done object stack 56 * @var array 57 */ 58 var $_don_obj_stack; 59 60 /** 61 * Current Object Id. 62 * @var integer 63 */ 64 var $_current_obj_id; 65 66 /** 67 * The name of the last imported page box 68 * @var string 69 */ 70 var $lastUsedPageBox; 71 72 /** 73 * Cache for imported pages/template ids 74 * @var array 75 */ 76 var $_importedPages = array(); 77 78 /** 79 * Set a source-file 80 * 81 * @param string $filename a valid filename 82 * @return int number of available pages 83 */ 84 function setSourceFile($filename) { 85 $this->current_filename = $filename; 86 87 if (!isset($this->parsers[$filename])) 88 $this->parsers[$filename] = $this->_getPdfParser($filename); 89 $this->current_parser =& $this->parsers[$filename]; 90 $this->setPDFVersion(max($this->getPDFVersion(), $this->current_parser->getPDFVersion())); 91 92 return $this->parsers[$filename]->getPageCount(); 93 } 94 95 /** 96 * Set a source-file PDF data 97 * 98 * @param string $pdfdata The PDF file content 99 * @return int number of available pages 100 */ 101 function setSourceData($pdfdata) { 102 $filename = uniqid('tcpdi-'); 103 $this->current_filename = $filename; 104 105 if (!isset($this->parsers[$filename])) 106 $this->parsers[$filename] = new tcpdi_parser($pdfdata, $filename); 107 $this->current_parser =& $this->parsers[$filename]; 108 $this->setPDFVersion(max($this->getPDFVersion(), $this->current_parser->getPDFVersion())); 109 110 return $this->parsers[$filename]->getPageCount(); 111 } 112 113 /** 114 * Returns a PDF parser object 115 * 116 * @param string $filename 117 * @return fpdi_pdf_parser 118 */ 119 function _getPdfParser($filename) { 120 $data = file_get_contents($filename); 121 return new tcpdi_parser($data, $filename); 122 } 123 124 /** 125 * Get the current PDF version 126 * 127 * @return string 128 */ 129 function getPDFVersion() { 130 return $this->PDFVersion; 131 } 132 133 /** 134 * Set the PDF version 135 * 136 * @return string 137 */ 138 function setPDFVersion($version = '1.7') { 139 $this->PDFVersion = $version; 140 } 141 142 /** 143 * Import a page 144 * 145 * @param int $pageno pagenumber 146 * @return int Index of imported page - to use with fpdf_tpl::useTemplate() 147 */ 148 function importPage($pageno, $boxName = '/CropBox') { 149 if ($this->_intpl) { 150 return $this->error('Please import the desired pages before creating a new template.'); 151 } 152 153 $fn = $this->current_filename; 154 155 // check if page already imported 156 $pageKey = $fn . '-' . ((int)$pageno) . $boxName; 157 if (isset($this->_importedPages[$pageKey])) 158 return $this->_importedPages[$pageKey]; 159 160 $parser =& $this->parsers[$fn]; 161 $parser->setPageno($pageno); 162 163 if (!in_array($boxName, $parser->availableBoxes)) 164 return $this->Error(sprintf('Unknown box: %s', $boxName)); 165 166 $pageboxes = $parser->getPageBoxes($pageno, $this->k); 167 168 /** 169 * MediaBox 170 * CropBox: Default -> MediaBox 171 * BleedBox: Default -> CropBox 172 * TrimBox: Default -> CropBox 173 * ArtBox: Default -> CropBox 174 */ 175 if (!isset($pageboxes[$boxName]) && ($boxName == '/BleedBox' || $boxName == '/TrimBox' || $boxName == '/ArtBox')) 176 $boxName = '/CropBox'; 177 if (!isset($pageboxes[$boxName]) && $boxName == '/CropBox') 178 $boxName = '/MediaBox'; 179 180 if (!isset($pageboxes[$boxName])) 181 return false; 182 183 $this->lastUsedPageBox = $boxName; 184 185 $box = $pageboxes[$boxName]; 186 187 $this->tpl++; 188 $this->tpls[$this->tpl] = array(); 189 $tpl =& $this->tpls[$this->tpl]; 190 $tpl['parser'] =& $parser; 191 $tpl['resources'] = $parser->getPageResources(); 192 $tpl['buffer'] = $parser->getContent(); 193 $tpl['box'] = $box; 194 195 // To build an array that can be used by PDF_TPL::useTemplate() 196 $this->tpls[$this->tpl] = array_merge($this->tpls[$this->tpl], $box); 197 198 // An imported page will start at 0,0 everytime. Translation will be set in _putformxobjects() 199 $tpl['x'] = 0; 200 $tpl['y'] = 0; 201 202 // handle rotated pages 203 $rotation = $parser->getPageRotation($pageno); 204 $tpl['_rotationAngle'] = 0; 205 if (isset($rotation[1]) && ($angle = $rotation[1] % 360) != 0) { 206 $steps = $angle / 90; 207 208 $_w = $tpl['w']; 209 $_h = $tpl['h']; 210 $tpl['w'] = $steps % 2 == 0 ? $_w : $_h; 211 $tpl['h'] = $steps % 2 == 0 ? $_h : $_w; 212 213 if ($angle < 0) 214 $angle += 360; 215 216 $tpl['_rotationAngle'] = $angle * -1; 217 } 218 219 $this->_importedPages[$pageKey] = $this->tpl; 220 221 return $this->tpl; 222 } 223 224 /** 225 * Returns the last used page box 226 * 227 * @return string 228 */ 229 function getLastUsedPageBox() { 230 return $this->lastUsedPageBox; 231 } 232 233 234 function useTemplate($tplidx, $_x = null, $_y = null, $_w = 0, $_h = 0, $adjustPageSize = false) { 235 if ($adjustPageSize == true && is_null($_x) && is_null($_y)) { 236 $size = $this->getTemplateSize($tplidx, $_w, $_h); 237 $orientation = $size['w'] > $size['h'] ? 'L' : 'P'; 238 $size = array($size['w'], $size['h']); 239 240 $this->setPageFormat($size, $orientation); 241 } 242 243 $this->_out('q 0 J 1 w 0 j 0 G 0 g'); // reset standard values 244 $s = parent::useTemplate($tplidx, $_x, $_y, $_w, $_h); 245 $this->_out('Q'); 246 247 return $s; 248 } 249 250 /** 251 * Private method, that rebuilds all needed objects of source files 252 */ 253 function _putimportedobjects() { 254 if (is_array($this->parsers) && count($this->parsers) > 0) { 255 foreach($this->parsers AS $filename => $p) { 256 $this->current_parser =& $this->parsers[$filename]; 257 if (isset($this->_obj_stack[$filename]) && is_array($this->_obj_stack[$filename])) { 258 while(($n = key($this->_obj_stack[$filename])) !== null) { 259 $nObj = $this->current_parser->getObjectVal($this->_obj_stack[$filename][$n][1]); 260 261 $this->_newobj($this->_obj_stack[$filename][$n][0]); 262 263 if ($nObj[0] == PDF_TYPE_STREAM) { 264 $this->pdf_write_value($nObj); 265 } else { 266 $this->pdf_write_value($nObj[1]); 267 } 268 269 $this->_out('endobj'); 270 $this->_obj_stack[$filename][$n] = null; // free memory 271 unset($this->_obj_stack[$filename][$n]); 272 reset($this->_obj_stack[$filename]); 273 } 274 } 275 276 // We're done with this parser. Clean it up to free a bit of RAM. 277 $this->current_parser->cleanUp(); 278 unset($this->parsers[$filename]); 279 } 280 } 281 } 282 283 284 /** 285 * Private Method that writes the form xobjects 286 */ 287 function _putformxobjects() { 288 $filter=($this->compress) ? '/Filter /FlateDecode ' : ''; 289 reset($this->tpls); 290 foreach($this->tpls AS $tplidx => $tpl) { 291 $p=($this->compress) ? gzcompress($tpl['buffer']) : $tpl['buffer']; 292 $this->_newobj(); 293 $cN = $this->n; // TCPDF/Protection: rem current "n" 294 295 $this->tpls[$tplidx]['n'] = $this->n; 296 $this->_out('<<' . $filter . '/Type /XObject'); 297 $this->_out('/Subtype /Form'); 298 $this->_out('/FormType 1'); 299 300 $this->_out(sprintf('/BBox [%.2F %.2F %.2F %.2F]', 301 (isset($tpl['box']['llx']) ? $tpl['box']['llx'] : $tpl['x']) * $this->k, 302 (isset($tpl['box']['lly']) ? $tpl['box']['lly'] : -$tpl['y']) * $this->k, 303 (isset($tpl['box']['urx']) ? $tpl['box']['urx'] : $tpl['w'] + $tpl['x']) * $this->k, 304 (isset($tpl['box']['ury']) ? $tpl['box']['ury'] : $tpl['h'] - $tpl['y']) * $this->k 305 )); 306 307 $c = 1; 308 $s = 0; 309 $tx = 0; 310 $ty = 0; 311 312 if (isset($tpl['box'])) { 313 $tx = -$tpl['box']['llx']; 314 $ty = -$tpl['box']['lly']; 315 316 if ($tpl['_rotationAngle'] <> 0) { 317 $angle = $tpl['_rotationAngle'] * M_PI/180; 318 $c=cos($angle); 319 $s=sin($angle); 320 321 switch($tpl['_rotationAngle']) { 322 case -90: 323 $tx = -$tpl['box']['lly']; 324 $ty = $tpl['box']['urx']; 325 break; 326 case -180: 327 $tx = $tpl['box']['urx']; 328 $ty = $tpl['box']['ury']; 329 break; 330 case -270: 331 $tx = $tpl['box']['ury']; 332 $ty = -$tpl['box']['llx']; 333 break; 334 } 335 } 336 } elseif ($tpl['x'] != 0 || $tpl['y'] != 0) { 337 $tx = -$tpl['x'] * 2; 338 $ty = $tpl['y'] * 2; 339 } 340 341 $tx *= $this->k; 342 $ty *= $this->k; 343 344 if ($c != 1 || $s != 0 || $tx != 0 || $ty != 0) { 345 $this->_out(sprintf('/Matrix [%.5F %.5F %.5F %.5F %.5F %.5F]', 346 $c, $s, -$s, $c, $tx, $ty 347 )); 348 } 349 350 $this->_out('/Resources '); 351 352 if (isset($tpl['resources'])) { 353 $this->current_parser =& $tpl['parser']; 354 $this->pdf_write_value($tpl['resources']); // "n" will be changed 355 } else { 356 $this->_out('<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]'); 357 if (isset($this->_res['tpl'][$tplidx]['fonts']) && count($this->_res['tpl'][$tplidx]['fonts'])) { 358 $this->_out('/Font <<'); 359 foreach($this->_res['tpl'][$tplidx]['fonts'] as $font) 360 $this->_out('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R'); 361 $this->_out('>>'); 362 } 363 if(isset($this->_res['tpl'][$tplidx]['images']) && count($this->_res['tpl'][$tplidx]['images']) || 364 isset($this->_res['tpl'][$tplidx]['tpls']) && count($this->_res['tpl'][$tplidx]['tpls'])) 365 { 366 $this->_out('/XObject <<'); 367 if (isset($this->_res['tpl'][$tplidx]['images']) && count($this->_res['tpl'][$tplidx]['images'])) { 368 foreach($this->_res['tpl'][$tplidx]['images'] as $image) 369 $this->_out('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R'); 370 } 371 if (isset($this->_res['tpl'][$tplidx]['tpls']) && count($this->_res['tpl'][$tplidx]['tpls'])) { 372 foreach($this->_res['tpl'][$tplidx]['tpls'] as $i => $tpl) 373 $this->_out($this->tplprefix . $i . ' ' . $tpl['n'] . ' 0 R'); 374 } 375 $this->_out('>>'); 376 } 377 $this->_out('>>'); 378 } 379 380 $this->_out('/Group <</Type/Group/S/Transparency>>'); 381 382 $nN = $this->n; // TCPDF: rem new "n" 383 $this->n = $cN; // TCPDF: reset to current "n" 384 385 $p = $this->_getrawstream($p); 386 $this->_out('/Length ' . strlen($p) . ' >>'); 387 $this->_out("stream\n" . $p . "\nendstream"); 388 389 $this->_out('endobj'); 390 $this->n = $nN; // TCPDF: reset to new "n" 391 } 392 393 $this->_putimportedobjects(); 394 } 395 396 /** 397 * Rewritten to handle existing own defined objects 398 */ 399 function _newobj($obj_id = false, $onlynewobj = false) { 400 if (!$obj_id) { 401 $obj_id = ++$this->n; 402 } 403 404 //Begin a new object 405 if (!$onlynewobj) { 406 $this->offsets[$obj_id] = $this->bufferlen; 407 $this->_out($obj_id . ' 0 obj'); 408 $this->_current_obj_id = $obj_id; // for later use with encryption 409 } 410 411 return $obj_id; 412 } 413 414 /** 415 * Writes a value 416 * Needed to rebuild the source document 417 * 418 * @param mixed $value A PDF-Value. Structure of values see cases in this method 419 */ 420 function pdf_write_value(&$value) 421 { 422 switch ($value[0]) { 423 case PDF_TYPE_STRING: 424 if ($this->encrypted) { 425 $value[1] = $this->_unescape($value[1]); 426 $value[1] = $this->_encrypt_data($this->_current_obj_id, $value[1]); 427 $value[1] = TCPDF_STATIC::_escape($value[1]); 428 } 429 break; 430 431 case PDF_TYPE_STREAM: 432 if ($this->encrypted) { 433 $value[2][1] = $this->_encrypt_data($this->_current_obj_id, $value[2][1]); 434 $value[1][1]['/Length'] = array( 435 PDF_TYPE_NUMERIC, 436 strlen($value[2][1]) 437 ); 438 } 439 break; 440 441 case PDF_TYPE_HEX: 442 if ($this->encrypted) { 443 $value[1] = $this->hex2str($value[1]); 444 $value[1] = $this->_encrypt_data($this->_current_obj_id, $value[1]); 445 446 // remake hexstring of encrypted string 447 $value[1] = $this->str2hex($value[1]); 448 } 449 break; 450 } 451 452 switch ($value[0]) { 453 454 case PDF_TYPE_TOKEN: 455 $this->_straightOut('/'.$value[1] . ' '); 456 break; 457 case PDF_TYPE_NUMERIC: 458 case PDF_TYPE_REAL: 459 if (is_float($value[1]) && $value[1] != 0) { 460 $this->_straightOut(rtrim(rtrim(sprintf('%F', $value[1]), '0'), '.') . ' '); 461 } else { 462 $this->_straightOut($value[1] . ' '); 463 } 464 break; 465 466 case PDF_TYPE_ARRAY: 467 468 // An array. Output the proper 469 // structure and move on. 470 471 $this->_straightOut('['); 472 for ($i = 0; $i < count($value[1]); $i++) { 473 $this->pdf_write_value($value[1][$i]); 474 } 475 476 $this->_out(']'); 477 break; 478 479 case PDF_TYPE_DICTIONARY: 480 481 // A dictionary. 482 $this->_straightOut('<<'); 483 484 reset ($value[1]); 485 486 while (list($k, $v) = each($value[1])) { 487 $this->_straightOut($k . ' '); 488 $this->pdf_write_value($v); 489 } 490 491 $this->_straightOut('>>'); 492 break; 493 494 case PDF_TYPE_OBJREF: 495 496 // An indirect object reference 497 // Fill the object stack if needed 498 $cpfn =& $this->current_parser->uniqueid; 499 500 if (!isset($this->_don_obj_stack[$cpfn][$value[1]])) { 501 $this->_newobj(false, true); 502 $this->_obj_stack[$cpfn][$value[1]] = array($this->n, $value); 503 $this->_don_obj_stack[$cpfn][$value[1]] = array($this->n, $value); // Value is maybee obsolete!!! 504 } 505 $objid = $this->_don_obj_stack[$cpfn][$value[1]][0]; 506 507 $this->_out($objid . ' 0 R'); 508 break; 509 510 case PDF_TYPE_STRING: 511 512 // A string. 513 $this->_straightOut('(' . $value[1] . ')'); 514 515 break; 516 517 case PDF_TYPE_STREAM: 518 519 // A stream. First, output the 520 // stream dictionary, then the 521 // stream data itself. 522 $this->pdf_write_value($value[1]); 523 $this->_out('stream'); 524 $this->_out($value[2][1]); 525 $this->_out('endstream'); 526 break; 527 528 case PDF_TYPE_HEX: 529 $this->_straightOut('<' . $value[1] . '>'); 530 break; 531 532 case PDF_TYPE_BOOLEAN: 533 $this->_straightOut($value[1] ? 'true ' : 'false '); 534 break; 535 536 case PDF_TYPE_NULL: 537 // The null object. 538 539 $this->_straightOut('null '); 540 break; 541 } 542 } 543 544 /** 545 * Modified so not each call will add a newline to the output. 546 */ 547 function _straightOut($s) { 548 if ($this->state == 2) { 549 if ($this->inxobj) { 550 // we are inside an XObject template 551 $this->xobjects[$this->xobjid]['outdata'] .= $s; 552 } elseif ((!$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 } elseif ($this->state > 0) { 565 // set general data 566 $this->setBuffer($s); 567 } 568 } 569 570 /** 571 * rewritten to close opened parsers 572 * 573 */ 574 function _enddoc() { 575 parent::_enddoc(); 576 $this->_closeParsers(); 577 } 578 579 /** 580 * close all files opened by parsers 581 */ 582 function _closeParsers() { 583 if ($this->state > 2 && is_array($this->parsers) && count($this->parsers) > 0) { 584 $this->cleanUp(); 585 return true; 586 } 587 return false; 588 } 589 590 /** 591 * Removes cylced references and closes the file handles of the parser objects 592 */ 593 function cleanUp() { 594 foreach ($this->parsers as $k => $_){ 595 $this->parsers[$k]->cleanUp(); 596 $this->parsers[$k] = null; 597 unset($this->parsers[$k]); 598 } 599 } 600 601 // Functions from here on are taken from FPDI's fpdi2tcpdf_bridge.php to remove dependence on it 602 function _putstream($s, $n=0) { 603 $this->_out($this->_getstream($s, $n)); 604 } 605 606 function _getxobjectdict() { 607 $out = parent::_getxobjectdict(); 608 if (count($this->tpls)) { 609 foreach($this->tpls as $tplidx => $tpl) { 610 $out .= sprintf('%s%d %d 0 R', $this->tplprefix, $tplidx, $tpl['n']); 611 } 612 } 613 614 return $out; 615 } 616 617 /** 618 * Unescapes a PDF string 619 * 620 * @param string $s 621 * @return string 622 */ 623 function _unescape($s) { 624 $out = ''; 625 for ($count = 0, $n = strlen($s); $count < $n; $count++) { 626 if ($s[$count] != '\\' || $count == $n-1) { 627 $out .= $s[$count]; 628 } else { 629 switch ($s[++$count]) { 630 case ')': 631 case '(': 632 case '\\': 633 $out .= $s[$count]; 634 break; 635 case 'f': 636 $out .= chr(0x0C); 637 break; 638 case 'b': 639 $out .= chr(0x08); 640 break; 641 case 't': 642 $out .= chr(0x09); 643 break; 644 case 'r': 645 $out .= chr(0x0D); 646 break; 647 case 'n': 648 $out .= chr(0x0A); 649 break; 650 case "\r": 651 if ($count != $n-1 && $s[$count+1] == "\n") 652 $count++; 653 break; 654 case "\n": 655 break; 656 default: 657 // Octal-Values 658 if (ord($s[$count]) >= ord('0') && 659 ord($s[$count]) <= ord('9')) { 660 $oct = ''. $s[$count]; 661 662 if (ord($s[$count+1]) >= ord('0') && 663 ord($s[$count+1]) <= ord('9')) { 664 $oct .= $s[++$count]; 665 666 if (ord($s[$count+1]) >= ord('0') && 667 ord($s[$count+1]) <= ord('9')) { 668 $oct .= $s[++$count]; 669 } 670 } 671 672 $out .= chr(octdec($oct)); 673 } else { 674 $out .= $s[$count]; 675 } 676 } 677 } 678 } 679 return $out; 680 } 681 682 /** 683 * Hexadecimal to string 684 * 685 * @param string $hex 686 * @return string 687 */ 688 function hex2str($hex) { 689 return pack('H*', str_replace(array("\r", "\n", ' '), '', $hex)); 690 } 691 692 /** 693 * String to hexadecimal 694 * 695 * @param string $str 696 * @return string 697 */ 698 function str2hex($str) { 699 return current(unpack('H*', $str)); 700 } 701} 702