PageRenderTime 66ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 1ms

/includes/cezpdf/Cpdf.php

https://bitbucket.org/adrianmm/pr-ctica-de-facturaci-n-de-2-daw
PHP | 3899 lines | 2616 code | 358 blank | 925 comment | 573 complexity | e2a5849c82c34ff5b1d6951d0e7fc281 MD5 | raw file
Possible License(s): GPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. @include_once('include/TTFsubset.php');
  3. /**
  4. * Create pdf documents without additional modules
  5. * Note that the companion class Document_CezPdf can be used to extend this class and
  6. * simplify the creation of documents.
  7. * <pre>
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program. If not, see http://www.gnu.org/licenses/
  20. * </pre>
  21. *
  22. * **Document object methods**
  23. *
  24. * There is about one object method for each type of object in the pdf document<br>
  25. * Each function has the same call list ($id,$action,$options).<br>
  26. * <pre>
  27. * $id = the object ID of the object, or what it is to be if it is being created
  28. * $action = a string specifying the action to be performed, though ALL must support:
  29. * 'new' - create the object with the id $id
  30. * 'out' - produce the output for the pdf object
  31. * $options = optional, a string or array containing the various parameters for the object
  32. * </pre>
  33. * These, in conjunction with the output function are the ONLY way for output to be produced
  34. * within the pdf 'file'.
  35. *
  36. * @category Documents
  37. * @package Cpdf
  38. * @version [0.12-rc12] $Id: Cpdf.php 207 2013-11-19 16:18:14Z ole1986 $
  39. * @author Wayne Munro (inactive) <pdf@ros.co.nz>
  40. * @author Lars Olesen <lars@legestue.net>
  41. * @author Sune Jensen <sj@sunet.dk>
  42. * @author Ole Koeckemann <ole1986@users.sourceforge.net>
  43. * @copyright 2007 - 2014 The authors
  44. * @license GNU General Public License v3
  45. * @link http://pdf-php.sf.net
  46. */
  47. class Cpdf
  48. {
  49. /**
  50. * PDF version
  51. * This value may vary dependent on which methods and/or features are used.
  52. * For instance setEncryption may cause the pdf version to increase to $this->pdfversion = 1.4
  53. *
  54. * Minimum 1.3
  55. * @var string $pdfversion default is 1.3
  56. */
  57. protected $pdfversion = 1.3;
  58. /**
  59. * allow the programmer to output debug messages on several places<br>
  60. * 'none' = no debug output at all
  61. * 'error_log' = use error_log
  62. * 'variable' = store in a variable called $this->messages
  63. * @var string $DEBUG Default is error_log
  64. */
  65. public $DEBUG = 'error_log';
  66. /**
  67. * Set the debug level
  68. * E_USER_ERROR = only errors
  69. * E_USER_WARNING = errors and warning
  70. * E_USER_NOTICE = nearly everything
  71. *
  72. * @var int $DEBUGLEVEL Default E_USER_WARNING
  73. */
  74. public $DEBUGLEVEL = E_USER_WARNING;
  75. /**
  76. * Reversed char string to allow arabic or Hebrew
  77. * @todo incomplete implementation
  78. */
  79. public $rtl = false;
  80. /**
  81. * flag to validate the output and if output method has be executed
  82. * This option is not really in use but is set to true in checkAllHere method
  83. * @var bool
  84. */
  85. protected $valid = false;
  86. /**
  87. * temporary path used for image and font caching.
  88. * Need to get changed when using XAMPP
  89. * @var string
  90. */
  91. public $tempPath = '/tmp';
  92. /**
  93. * the current number of pdf objects in the document
  94. * @var integer
  95. */
  96. protected $numObj=0;
  97. /**
  98. * contains pdf objects ready for the final assembly
  99. * @var array
  100. */
  101. protected $objects = array();
  102. /**
  103. * set to true allows object being hashed. Primary used for images
  104. * @var bool
  105. */
  106. public $hashed = true;
  107. /**
  108. * Object hash array used to free pdf from redundancies
  109. * @var array
  110. */
  111. private $objectHash = array();
  112. /**
  113. * the objectId (number within the objects array) of the document catalog
  114. * @var integer
  115. */
  116. private $catalogId;
  117. /**
  118. * default encoding for NON-UNICODE text
  119. @var string default encoding is IS0-8859-1
  120. */
  121. public $targetEncoding = 'ISO-8859-1';
  122. /**
  123. * set this to true allows TTF font being parsed as unicode in PDF output.
  124. * This also converts all text output into utf16_be
  125. * @var boolean default is false
  126. */
  127. public $isUnicode = false;
  128. /**
  129. * define the tags being allowed in any text input, like addText or addTextWrap (default: bold, italic and links)
  130. * @var string
  131. */
  132. public $allowedTags = 'b|strong|i|uline|alink:?.*?|ilink:?.*?|color:?.*?';
  133. /**
  134. * used to either embed or not embed the ttf/pfb font program
  135. * @var boolean default embed the font program
  136. */
  137. protected $embedFont = true;
  138. /**
  139. * font cache timeout in seconds
  140. * @var integer default is 86400 which is 1 day
  141. */
  142. public $cacheTimeout = 86400;
  143. /**
  144. * stores the font family information for either core fonts or any other TTF font program.
  145. * Once the font family is defined, directives like bold and italic
  146. * @var array
  147. */
  148. private $fontFamilies = array(
  149. 'Helvetica' => array(
  150. 'b'=>'Helvetica-Bold',
  151. 'i'=>'Helvetica-Oblique',
  152. 'bi'=>'Helvetica-BoldOblique',
  153. 'ib'=>'Helvetica-BoldOblique',
  154. ),
  155. 'Courier' => array(
  156. 'b'=>'Courier-Bold',
  157. 'i'=>'Courier-Oblique',
  158. 'bi'=>'Courier-BoldOblique',
  159. 'ib'=>'Courier-BoldOblique',
  160. ),
  161. 'Times-Roman' => array(
  162. 'b'=>'Times-Bold',
  163. 'i'=>'Times-Italic',
  164. 'bi'=>'Times-BoldItalic',
  165. 'ib'=>'Times-BoldItalic',
  166. )
  167. );
  168. /**
  169. * all CoreFonts available in PDF by default.
  170. * This array is used check if TTF font need to get attached and/or is unicode
  171. * @var array
  172. */
  173. private $coreFonts = array('courier', 'courier-bold', 'courier-oblique', 'courier-boldoblique',
  174. 'helvetica', 'helvetica-bold', 'helvetica-oblique', 'helvetica-boldoblique',
  175. 'times-roman', 'times-bold', 'times-italic', 'times-bolditalic',
  176. 'symbol', 'zapfdingbats');
  177. /**
  178. * array carrying information about the fonts that the system currently knows about
  179. * used to ensure that a font is not loaded twice, among other things
  180. * @var array
  181. */
  182. protected $fonts = array();
  183. /**
  184. * font path location
  185. * @since 0.12-rc8
  186. */
  187. public $fontPath = './';
  188. /**
  189. * a record of the current font
  190. * @var string
  191. */
  192. protected $currentFont='';
  193. /**
  194. * the current base font
  195. * @var string
  196. */
  197. protected $currentBaseFont='';
  198. /**
  199. * the number of the current font within the font array
  200. * @var integer
  201. */
  202. protected $currentFontNum=0;
  203. /**
  204. * no clue for what this is used
  205. * @var integer
  206. */
  207. private $currentNode;
  208. /**
  209. * object number of the current page
  210. * @var integer
  211. */
  212. protected $currentPage;
  213. /**
  214. * object number of the currently active contents block
  215. * @var integer
  216. */
  217. protected $currentContents;
  218. /**
  219. * number of fonts within the system
  220. * @var integer
  221. */
  222. protected $numFonts = 0;
  223. /**
  224. * current colour for fill operations, defaults to inactive value, all three components should be between 0 and 1 inclusive when active
  225. */
  226. protected $currentColour = array('r' => -1, 'g' => -1, 'b' => -1);
  227. /**
  228. * current colour for stroke operations (lines etc.)
  229. */
  230. protected $currentStrokeColour = array('r' => -1, 'g' => -1, 'b' => -1);
  231. /**
  232. * current style that lines are drawn in
  233. */
  234. protected $currentLineStyle='';
  235. /**
  236. * an array which is used to save the state of the document, mainly the colours and styles
  237. * it is used to temporarily change to another state, the change back to what it was before
  238. */
  239. private $stateStack = array();
  240. /**
  241. * number of elements within the state stack
  242. */
  243. private $nStateStack = 0;
  244. /**
  245. * number of page objects within the document
  246. */
  247. protected $numPages=0;
  248. /**
  249. * object Id storage stack
  250. */
  251. private $stack=array();
  252. /**
  253. * number of elements within the object Id storage stack
  254. */
  255. private $nStack=0;
  256. /**
  257. * an array which contains information about the objects which are not firmly attached to pages
  258. * these have been added with the addObject function
  259. */
  260. private $looseObjects=array();
  261. /**
  262. * array contains infomation about how the loose objects are to be added to the document
  263. */
  264. private $addLooseObjects=array();
  265. /**
  266. * the objectId of the information object for the document
  267. * this contains authorship, title etc.
  268. */
  269. private $infoObject=0;
  270. /**
  271. * number of images being tracked within the document
  272. */
  273. private $numImages=0;
  274. /**
  275. * some additional options while generation
  276. * currently used for compression only
  277. * Default: 'compression' => -1 which will set gzcompress to the default level of 6
  278. */
  279. public $options=array('compression'=>-1);
  280. /**
  281. * the objectId of the first page of the document
  282. */
  283. private $firstPageId;
  284. /**
  285. * used to track the last used value of the inter-word spacing, this is so that it is known
  286. * when the spacing is changed.
  287. */
  288. private $wordSpaceAdjust=0;
  289. /**
  290. * tracks the status of the current font style, like bold or italic
  291. */
  292. private $currentTextState = '';
  293. /**
  294. * messages are stored here during processing, these can be selected afterwards to give some useful debug information
  295. */
  296. public $messages='';
  297. /**
  298. * the encryption array for the document encryption is stored here
  299. */
  300. private $arc4='';
  301. /**
  302. * the object Id of the encryption information
  303. */
  304. private $arc4_objnum=0;
  305. /**
  306. * the file identifier, used to uniquely identify a pdf document
  307. */
  308. public $fileIdentifier='';
  309. /**
  310. * Set the encryption mode
  311. * 0 = no encryption
  312. * 1 = RC40bit
  313. * 2 = RC128bit (since PDF Version 1.4)
  314. */
  315. private $encryptionMode = 0;
  316. /**
  317. * the encryption key for the encryption of all the document content (structure is not encrypted)
  318. *
  319. * @var string
  320. */
  321. private $encryptionKey='';
  322. /**
  323. * encryption padding fetched from the Adobe PDF reference
  324. */
  325. private $encryptionPad;
  326. /**
  327. * array which forms a stack to keep track of nested callback functions
  328. *
  329. * @var array
  330. */
  331. private $callback = array();
  332. /**
  333. * the number of callback functions in the callback array
  334. *
  335. * @var integer
  336. */
  337. private $nCallback = 0;
  338. /**
  339. * store label->id pairs for named destinations, these will be used to replace internal links
  340. * done this way so that destinations can be defined after the location that links to them
  341. *
  342. * @var array
  343. */
  344. private $destinations = array();
  345. /**
  346. * store the stack for the transaction commands, each item in here is a record of the values of all the
  347. * variables within the class, so that the user can rollback at will (from each 'start' command)
  348. * note that this includes the objects array, so these can be large.
  349. *
  350. * @var string
  351. */
  352. protected $checkpoint = '';
  353. /**
  354. * Constructor - start with a new PDF document
  355. *
  356. * @param array $pageSize Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
  357. * @param bool $isUnicode
  358. * @return void
  359. */
  360. public function __construct($pageSize = array(0, 0, 612, 792), $isUnicode = false)
  361. {
  362. $this->isUnicode = $isUnicode;
  363. // set the hardcoded encryption pad
  364. $this->encryptionPad = chr(0x28).chr(0xBF).chr(0x4E).chr(0x5E).chr(0x4E).chr(0x75).chr(0x8A).chr(0x41).chr(0x64).chr(0x00).chr(0x4E).chr(0x56).chr(0xFF).chr(0xFA).chr(0x01).chr(0x08).chr(0x2E).chr(0x2E).chr(0x00).chr(0xB6).chr(0xD0).chr(0x68).chr(0x3E).chr(0x80).chr(0x2F).chr(0x0C).chr(0xA9).chr(0xFE).chr(0x64).chr(0x53).chr(0x69).chr(0x7A);
  365. $this->newDocument($pageSize);
  366. if ( in_array('Windows-1252', mb_list_encodings()) ) {
  367. $this->targetEncoding = 'Windows-1252';
  368. }
  369. // use the md5 to have a unique identifier for all documents created with R&OS pdf class
  370. $this->fileIdentifier = md5('ROSPDF'.time());
  371. // set the default font path to [...]/src/fonts
  372. $this->fontPath = dirname(__FILE__).'/fonts';
  373. }
  374. /**
  375. * destination object, used to specify the location for the user to jump to, presently on opening
  376. */
  377. private function o_destination($id,$action,$options='')
  378. {
  379. if ($action!='new'){
  380. $o =& $this->objects[$id];
  381. }
  382. switch($action){
  383. case 'new':
  384. $this->objects[$id]=array('t'=>'destination','info'=>array());
  385. $tmp = '';
  386. switch ($options['type']){
  387. case 'XYZ':
  388. case 'FitR':
  389. $tmp = ' '.$options['p3'].$tmp;
  390. case 'FitH':
  391. case 'FitV':
  392. case 'FitBH':
  393. case 'FitBV':
  394. $tmp = ' '.$options['p1'].' '.$options['p2'].$tmp;
  395. case 'Fit':
  396. case 'FitB':
  397. $tmp = $options['type'].$tmp;
  398. $this->objects[$id]['info']['string']=$tmp;
  399. $this->objects[$id]['info']['page']=$options['page'];
  400. }
  401. break;
  402. case 'out':
  403. $tmp = $o['info'];
  404. $res="\n".$id." 0 obj\n".'['.$tmp['page'].' 0 R /'.$tmp['string']."]\nendobj";
  405. return $res;
  406. break;
  407. }
  408. }
  409. /**
  410. * sets the viewer preferences
  411. */
  412. private function o_viewerPreferences($id,$action,$options='')
  413. {
  414. if ($action!='new'){
  415. $o =& $this->objects[$id];
  416. }
  417. switch ($action){
  418. case 'new':
  419. $this->objects[$id]=array('t'=>'viewerPreferences','info'=>array());
  420. break;
  421. case 'add':
  422. foreach($options as $k=>$v){
  423. switch ($k){
  424. case 'HideToolbar':
  425. case 'HideMenubar':
  426. case 'HideWindowUI':
  427. case 'FitWindow':
  428. case 'CenterWindow':
  429. case 'DisplayDocTitle': // since PDF 1.4
  430. case 'NonFullScreenPageMode':
  431. case 'Direction':
  432. $o['info'][$k]=$v;
  433. break;
  434. }
  435. }
  436. break;
  437. case 'out':
  438. $res="\n".$id." 0 obj\n".'<< ';
  439. foreach($o['info'] as $k=>$v){
  440. $res.="\n/".$k.' '.$v;
  441. }
  442. $res.="\n>>\n";
  443. return $res;
  444. break;
  445. }
  446. }
  447. /**
  448. * define the document catalog, the overall controller for the document
  449. */
  450. private function o_catalog($id, $action, $options = '')
  451. {
  452. if ($action!='new'){
  453. $o =& $this->objects[$id];
  454. }
  455. switch ($action){
  456. case 'new':
  457. $this->objects[$id]=array('t'=>'catalog','info'=>array());
  458. $this->catalogId=$id;
  459. break;
  460. case 'outlines':
  461. case 'pages':
  462. case 'openHere':
  463. $o['info'][$action]=$options;
  464. break;
  465. case 'viewerPreferences':
  466. if (!isset($o['info']['viewerPreferences'])){
  467. $this->numObj++;
  468. $this->o_viewerPreferences($this->numObj,'new');
  469. $o['info']['viewerPreferences']=$this->numObj;
  470. }
  471. $vp = $o['info']['viewerPreferences'];
  472. $this->o_viewerPreferences($vp,'add',$options);
  473. break;
  474. case 'out':
  475. $res="\n".$id." 0 obj\n".'<< /Type /Catalog';
  476. foreach($o['info'] as $k=>$v){
  477. switch($k){
  478. case 'outlines':
  479. $res.=' /Outlines '.$v.' 0 R';
  480. break;
  481. case 'pages':
  482. $res.=' /Pages '.$v.' 0 R';
  483. break;
  484. case 'viewerPreferences':
  485. $res.=' /ViewerPreferences '.$o['info']['viewerPreferences'].' 0 R';
  486. break;
  487. case 'openHere':
  488. $res.=' /OpenAction '.$o['info']['openHere'].' 0 R';
  489. break;
  490. }
  491. }
  492. $res.=" >>\nendobj";
  493. return $res;
  494. break;
  495. }
  496. }
  497. /**
  498. * object which is a parent to the pages in the document
  499. */
  500. private function o_pages($id,$action,$options='')
  501. {
  502. if ($action!='new'){
  503. $o =& $this->objects[$id];
  504. }
  505. switch ($action){
  506. case 'new':
  507. $this->objects[$id]=array('t'=>'pages','info'=>array());
  508. $this->o_catalog($this->catalogId,'pages',$id);
  509. break;
  510. case 'page':
  511. if (!is_array($options)){
  512. // then it will just be the id of the new page
  513. $o['info']['pages'][]=$options;
  514. } else {
  515. // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
  516. // and pos is either 'before' or 'after', saying where this page will fit.
  517. if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])){
  518. $i = array_search($options['rid'],$o['info']['pages']);
  519. if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i]==$options['rid']){
  520. // then there is a match make a space
  521. switch ($options['pos']){
  522. case 'before':
  523. $k = $i;
  524. break;
  525. case 'after':
  526. $k=$i+1;
  527. break;
  528. default:
  529. $k=-1;
  530. break;
  531. }
  532. if ($k>=0){
  533. for ($j=count($o['info']['pages'])-1;$j>=$k;$j--){
  534. $o['info']['pages'][$j+1]=$o['info']['pages'][$j];
  535. }
  536. $o['info']['pages'][$k]=$options['id'];
  537. }
  538. }
  539. }
  540. }
  541. break;
  542. case 'procset':
  543. $o['info']['procset']=$options;
  544. break;
  545. case 'mediaBox':
  546. $o['info']['mediaBox']=$options; // which should be an array of 4 numbers
  547. break;
  548. case 'font':
  549. $o['info']['fonts'][]=array('objNum'=>$options['objNum'],'fontNum'=>$options['fontNum']);
  550. break;
  551. case 'xObject':
  552. $o['info']['xObjects'][]=array('objNum'=>$options['objNum'],'label'=>$options['label']);
  553. break;
  554. case 'out':
  555. if (count($o['info']['pages'])){
  556. $res="\n".$id." 0 obj\n<< /Type /Pages /Kids [";
  557. foreach($o['info']['pages'] as $k=>$v){
  558. $res.=$v." 0 R ";
  559. }
  560. $res.="] /Count ".count($this->objects[$id]['info']['pages']);
  561. if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) || isset($o['info']['procset'])){
  562. $res.=" /Resources <<";
  563. if (isset($o['info']['procset'])){
  564. $res.=" /ProcSet ".$o['info']['procset'];
  565. }
  566. if (isset($o['info']['fonts']) && count($o['info']['fonts'])){
  567. $res.=" /Font << ";
  568. foreach($o['info']['fonts'] as $finfo){
  569. $res.=" /F".$finfo['fontNum']." ".$finfo['objNum']." 0 R";
  570. }
  571. $res.=" >>";
  572. }
  573. if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])){
  574. $res.=" /XObject << ";
  575. foreach($o['info']['xObjects'] as $finfo){
  576. $res.=" /".$finfo['label']." ".$finfo['objNum']." 0 R";
  577. }
  578. $res.=" >>";
  579. }
  580. $res.=" >>";
  581. if (isset($o['info']['mediaBox'])){
  582. $tmp=$o['info']['mediaBox'];
  583. $res.=" /MediaBox [".sprintf('%.3F',$tmp[0]).' '.sprintf('%.3F',$tmp[1]).' '.sprintf('%.3F',$tmp[2]).' '.sprintf('%.3F',$tmp[3]).']';
  584. }
  585. }
  586. $res.=" >>\nendobj";
  587. } else {
  588. $res="\n".$id." 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
  589. }
  590. return $res;
  591. break;
  592. }
  593. }
  594. /**
  595. * defines the outlines in the doc, empty for now
  596. */
  597. private function o_outlines($id,$action,$options='')
  598. {
  599. if ($action!='new'){
  600. $o =& $this->objects[$id];
  601. }
  602. switch ($action){
  603. case 'new':
  604. $this->objects[$id]=array('t'=>'outlines','info'=>array('outlines'=>array()));
  605. $this->o_catalog($this->catalogId,'outlines',$id);
  606. break;
  607. case 'outline':
  608. $o['info']['outlines'][]=$options;
  609. break;
  610. case 'out':
  611. if (count($o['info']['outlines'])){
  612. $res="\n".$id." 0 obj\n<< /Type /Outlines /Kids [";
  613. foreach($o['info']['outlines'] as $k=>$v){
  614. $res.=$v." 0 R ";
  615. }
  616. $res.="] /Count ".count($o['info']['outlines'])." >>\nendobj";
  617. } else {
  618. $res="\n".$id." 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
  619. }
  620. return $res;
  621. break;
  622. }
  623. }
  624. /**
  625. * an object to hold the font description
  626. */
  627. private function o_font($id,$action,$options=''){
  628. if ($action!='new'){
  629. $o =& $this->objects[$id];
  630. }
  631. switch ($action){
  632. case 'new':
  633. $this->objects[$id]=array('t'=>'font','info'=>array('name'=>$options['name'], 'fontFileName'=> $options['fontFileName'], 'SubType'=>'Type1'));
  634. $fontFileName = &$options['fontFileName'];
  635. $fontNum=$this->numFonts;
  636. $this->objects[$id]['info']['fontNum']=$fontNum;
  637. // deal with the encoding and the differences
  638. if (isset($options['differences'])){
  639. // then we'll need an encoding dictionary
  640. $this->numObj++;
  641. $this->o_fontEncoding($this->numObj,'new',$options);
  642. $this->objects[$id]['info']['encodingDictionary']=$this->numObj;
  643. } else if (isset($options['encoding'])){
  644. // we can specify encoding here
  645. switch($options['encoding']){
  646. case 'WinAnsiEncoding':
  647. case 'MacRomanEncoding':
  648. case 'MacExpertEncoding':
  649. $this->objects[$id]['info']['encoding']=$options['encoding'];
  650. break;
  651. case 'none':
  652. break;
  653. default:
  654. $this->objects[$id]['info']['encoding']='WinAnsiEncoding';
  655. break;
  656. }
  657. } else {
  658. $this->objects[$id]['info']['encoding']='WinAnsiEncoding';
  659. }
  660. if ($this->fonts[$fontFileName]['isUnicode']) {
  661. // For Unicode fonts, we need to incorporate font data into
  662. // sub-sections that are linked from the primary font section.
  663. // Look at o_fontGIDtoCID and o_fontDescendentCID functions
  664. // for more informaiton.
  665. //
  666. // All of this code is adapted from the excellent changes made to
  667. // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
  668. $toUnicodeId = ++$this->numObj;
  669. $this->o_contents($toUnicodeId, 'new', 'raw');
  670. $this->objects[$id]['info']['toUnicode'] = $toUnicodeId;
  671. $stream = "/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo <</Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n1 begincodespacerange\n<0000> <FFFF>\nendcodespacerange\n1 beginbfrange\n<0000> <FFFF> <0000>\nendbfrange\nendcmap\nCMapName currentdict /CMap defineresource pop\nend\nend\n";
  672. $res = "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
  673. $res .= "stream\n" . $stream . "\nendstream";
  674. $this->objects[$toUnicodeId]['c'] = $res;
  675. $cidFontId = ++$this->numObj;
  676. $this->o_fontDescendentCID($cidFontId, 'new', $options);
  677. $this->objects[$id]['info']['cidFont'] = $cidFontId;
  678. }
  679. // also tell the pages node about the new font
  680. $this->o_pages($this->currentNode,'font',array('fontNum'=>$fontNum,'objNum'=>$id));
  681. break;
  682. case 'add':
  683. foreach ($options as $k=>$v){
  684. switch ($k){
  685. case 'BaseFont':
  686. $o['info']['name'] = $v;
  687. break;
  688. case 'FirstChar':
  689. case 'LastChar':
  690. case 'Widths':
  691. case 'FontDescriptor':
  692. case 'SubType':
  693. $this->debug('o_font '.$k." : ".$v, E_USER_NOTICE);
  694. $o['info'][$k] = $v;
  695. break;
  696. }
  697. }
  698. // pass values down to descendent font
  699. if (isset($o['info']['cidFont'])) {
  700. $this->o_fontDescendentCID($o['info']['cidFont'], 'add', $options);
  701. }
  702. break;
  703. case 'out':
  704. $fontFileName = &$o['info']['fontFileName'];
  705. // when font program is embedded and its not a coreFont, attach the font either as subset or completely
  706. if($this->embedFont && !in_array(strtolower($o['info']['name']), $this->coreFonts)){
  707. // when TrueType font is used
  708. if(isset($this->objects[$o['info']['FontDescriptor']]['info']['FontFile2'])){
  709. // find font program id for TTF fonts (FontFile2)
  710. $pfbid = $this->objects[$o['info']['FontDescriptor']]['info']['FontFile2'];
  711. // if subsetting is set
  712. if($this->fonts[$fontFileName]['isSubset'] && $this->fonts[$fontFileName]['isUnicode']){
  713. $this->debug('subset font for ' . $fontFileName, E_USER_NOTICE);
  714. $subsetFontName = "AAAAAD+" . $o['info']['name'];
  715. $o['info']['name'] = $subsetFontName;
  716. // find descendant font
  717. $this->objects[$o['info']['cidFont']]['info']['name'] = $subsetFontName;
  718. // find font descriptor
  719. $this->objects[$o['info']['FontDescriptor']]['info']['FontName'] = $subsetFontName;
  720. // use TTF subset script from http://www.4real.gr/technical-documents-ttf-subset.html
  721. $t = new TTFsubset();
  722. // combine all used characters as string
  723. $s = implode('',array_keys($this->fonts[$fontFileName]['subset']));
  724. // submit the string to TTFsubset class to return the subset (as binary)
  725. $data = $t->doSubset($this->fontPath.'/'.$fontFileName . '.ttf', $s, null);
  726. // $data is the new (subset) of the font font
  727. //file_put_contents('/tmp/'.$o['info']['name'] . '.ttf', $data);
  728. $newcidwidth = array();
  729. $cidwidth = &$this->fonts[$fontFileName]['CIDWidths'];
  730. foreach($t->TTFchars as $TTFchar){
  731. if(!empty($TTFchar->charCode) && isset($cidwidth[$TTFchar->charCode])){
  732. $newcidwidth[$TTFchar->charCode] = $cidwidth[$TTFchar->charCode];
  733. }
  734. }
  735. $cidwidth = $newcidwidth;
  736. } else {
  737. $data = file_get_contents($this->fontPath.'/'.$fontFileName. '.ttf');
  738. }
  739. // TODO: cache the subset
  740. $l1 = strlen($data);
  741. $this->objects[$pfbid]['c'].= $data;
  742. $this->o_contents($pfbid,'add',array('Length1'=>$l1));
  743. } else if(isset($this->objects[$o['info']['FontDescriptor']]['info']['FontFile'])) {
  744. // find FontFile id - used for PFB fonts
  745. $pfbid = $this->objects[$o['info']['FontDescriptor']]['info']['FontFile'];
  746. $data = file_get_contents($this->fontPath.'/'.$fontFileName. '.pfb');
  747. $l1 = strpos($data,'eexec')+6;
  748. $l2 = strpos($data,'00000000')-$l1;
  749. $l3 = strlen($data)-$l2-$l1;
  750. $this->o_contents($pfbid,'add',array('Length1'=>$l1,'Length2'=>$l2,'Length3'=>$l3));
  751. } else {
  752. $this->debug("Failed to select the correct font program", E_USER_WARNING);
  753. }
  754. }
  755. if ($this->fonts[$fontFileName]['isUnicode']) {
  756. // For Unicode fonts, we need to incorporate font data into
  757. // sub-sections that are linked from the primary font section.
  758. // Look at o_fontGIDtoCID and o_fontDescendentCID functions
  759. // for more informaiton.
  760. //
  761. // All of this code is adapted from the excellent changes made to
  762. // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
  763. $res = "\n$id 0 obj\n<</Type /Font /Subtype /Type0 /BaseFont /".$o['info']['name']."";
  764. // The horizontal identity mapping for 2-byte CIDs; may be used
  765. // with CIDFonts using any Registry, Ordering, and Supplement values.
  766. $res.= " /Encoding /Identity-H /DescendantFonts [".$o['info']['cidFont']." 0 R] /ToUnicode ".$o['info']['toUnicode']." 0 R >>\n";
  767. $res.= "endobj";
  768. } else {
  769. $res="\n".$id." 0 obj\n<< /Type /Font /Subtype /".$o['info']['SubType']." ";
  770. $res.="/Name /F".$o['info']['fontNum']." ";
  771. $res.="/BaseFont /".$o['info']['name']." ";
  772. if (isset($o['info']['encodingDictionary'])){
  773. // then place a reference to the dictionary
  774. $res.="/Encoding ".$o['info']['encodingDictionary']." 0 R ";
  775. } else if (isset($o['info']['encoding'])){
  776. // use the specified encoding
  777. $res.="/Encoding /".$o['info']['encoding']." ";
  778. }
  779. if (isset($o['info']['FirstChar'])){
  780. $res.="/FirstChar ".$o['info']['FirstChar']." ";
  781. }
  782. if (isset($o['info']['LastChar'])){
  783. $res.="/LastChar ".$o['info']['LastChar']." ";
  784. }
  785. if (isset($o['info']['Widths'])){
  786. $res.="/Widths ".$o['info']['Widths']." 0 R ";
  787. }
  788. if (isset($o['info']['FontDescriptor'])){
  789. $res.="/FontDescriptor ".$o['info']['FontDescriptor']." 0 R ";
  790. }
  791. $res.=">>\nendobj";
  792. }
  793. return $res;
  794. break;
  795. }
  796. }
  797. /**
  798. * a font descriptor, needed for including additional fonts
  799. */
  800. private function o_fontDescriptor($id, $action, $options = '')
  801. {
  802. if ($action!='new'){
  803. $o =& $this->objects[$id];
  804. }
  805. switch ($action){
  806. case 'new':
  807. $this->objects[$id]=array('t'=>'fontDescriptor','info'=>$options);
  808. break;
  809. case 'out':
  810. $res="\n".$id." 0 obj\n<< /Type /FontDescriptor ";
  811. foreach ($o['info'] as $label => $value){
  812. switch ($label){
  813. case 'Ascent':
  814. case 'CapHeight':
  815. case 'Descent':
  816. case 'Flags':
  817. case 'ItalicAngle':
  818. case 'StemV':
  819. case 'AvgWidth':
  820. case 'Leading':
  821. case 'MaxWidth':
  822. case 'MissingWidth':
  823. case 'StemH':
  824. case 'XHeight':
  825. case 'CharSet':
  826. if (strlen($value)){
  827. $res.='/'.$label.' '.$value." ";
  828. }
  829. break;
  830. case 'FontFile':
  831. case 'FontFile2':
  832. case 'FontFile3':
  833. $res.='/'.$label.' '.$value." 0 R ";
  834. break;
  835. case 'FontBBox':
  836. $res.='/'.$label.' ['.$value[0].' '.$value[1].' '.$value[2].' '.$value[3]."] ";
  837. break;
  838. case 'FontName':
  839. $res.='/'.$label.' /'.$value." ";
  840. break;
  841. }
  842. }
  843. $res.=">>\nendobj";
  844. return $res;
  845. break;
  846. }
  847. }
  848. /**
  849. * the font encoding
  850. */
  851. private function o_fontEncoding($id,$action,$options=''){
  852. if ($action!='new'){
  853. $o =& $this->objects[$id];
  854. }
  855. switch ($action){
  856. case 'new':
  857. // the options array should contain 'differences' and maybe 'encoding'
  858. $this->objects[$id]=array('t'=>'fontEncoding','info'=>$options);
  859. break;
  860. case 'out':
  861. $res="\n".$id." 0 obj\n<< /Type /Encoding ";
  862. if (!isset($o['info']['encoding'])){
  863. $o['info']['encoding']='WinAnsiEncoding';
  864. }
  865. if ($o['info']['encoding']!='none'){
  866. $res.="/BaseEncoding /".$o['info']['encoding']." ";
  867. }
  868. $res.="/Differences [";
  869. $onum=-100;
  870. foreach($o['info']['differences'] as $num=>$label){
  871. if ($num!=$onum+1){
  872. // we cannot make use of consecutive numbering
  873. $res.= " ".$num." /".$label;
  874. } else {
  875. $res.= " /".$label;
  876. }
  877. $onum=$num;
  878. }
  879. $res.="] >>\nendobj";
  880. return $res;
  881. break;
  882. }
  883. }
  884. /**
  885. * a descendent cid font, needed for unicode fonts
  886. */
  887. private function o_fontDescendentCID($id, $action, $options = '') {
  888. if ($action !== 'new') {
  889. $o = & $this->objects[$id];
  890. }
  891. switch ($action) {
  892. case 'new':
  893. $this->objects[$id] = array('t' => 'fontDescendentCID', 'info' => $options);
  894. // and a CID to GID map
  895. if($this->embedFont){
  896. $cidToGidMapId = ++$this->numObj;
  897. $this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options);
  898. $this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId;
  899. }
  900. break;
  901. case 'add':
  902. foreach ($options as $k => $v) {
  903. switch ($k) {
  904. case 'BaseFont':
  905. $o['info']['name'] = $v;
  906. break;
  907. case 'FirstChar':
  908. case 'LastChar':
  909. case 'MissingWidth':
  910. case 'FontDescriptor':
  911. case 'SubType':
  912. $this->debug("o_fontDescendentCID $k : $v", E_USER_NOTICE);
  913. $o['info'][$k] = $v;
  914. break;
  915. }
  916. }
  917. // pass values down to cid to gid map
  918. if($this->embedFont){
  919. $this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options);
  920. }
  921. break;
  922. case 'out':
  923. $fontFileName = &$o['info']['fontFileName'];
  924. $res = "\n$id 0 obj\n";
  925. $res.= "<</Type /Font /Subtype /CIDFontType2 /BaseFont /".$o['info']['name']." /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >>";
  926. if (isset($o['info']['FontDescriptor'])) {
  927. $res.= " /FontDescriptor ".$o['info']['FontDescriptor']." 0 R";
  928. }
  929. if (isset($o['info']['MissingWidth'])) {
  930. $res.= " /DW ".$o['info']['MissingWidth']."";
  931. }
  932. if (isset($fontFileName) && isset($this->fonts[$fontFileName]['CIDWidths'])) {
  933. $cid_widths = &$this->fonts[$fontFileName]['CIDWidths'];
  934. $res.= ' /W [';
  935. reset($cid_widths);
  936. $opened = false;
  937. while (list($k,$v) = each($cid_widths)) {
  938. list($nextk, $nextv) = each($cid_widths);
  939. //echo "\n$k ($v) == $nextk ($nextv)";
  940. if(($k + 1) == $nextk){
  941. if(!$opened){
  942. $res.= " $k [$v";
  943. $opened = true;
  944. } else if($opened) {
  945. $res.= ' '.$v;
  946. }
  947. prev($cid_widths);
  948. } else {
  949. if($opened){
  950. $res.=" $v]";
  951. } else {
  952. $res.= " $k [$v]";
  953. }
  954. $opened = false;
  955. prev($cid_widths);
  956. }
  957. }
  958. if(isset($nextk) && isset($nextv)){
  959. if($opened){
  960. $res.= "]";
  961. }
  962. $res.= " $nextk [$nextv]";
  963. }
  964. $res.= ' ]';
  965. }
  966. if($this->embedFont){
  967. $res.= " /CIDToGIDMap ".$o['info']['cidToGidMap']." 0 R";
  968. }
  969. $res.= " >>\n";
  970. $res.= "endobj";
  971. return $res;
  972. }
  973. }
  974. /**
  975. * a font glyph to character map, needed for unicode fonts
  976. */
  977. private function o_fontGIDtoCIDMap($id, $action, $options = '') {
  978. if ($action !== 'new') {
  979. $o = & $this->objects[$id];
  980. }
  981. switch ($action) {
  982. case 'new':
  983. $this->objects[$id] = array('t' => 'fontGIDtoCIDMap', 'info' => $options);
  984. break;
  985. case 'out':
  986. $res = "\n$id 0 obj\n";
  987. $fontFileName = &$o['info']['fontFileName'];
  988. $tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']);
  989. if (isset($o['raw'])) {
  990. $res.= $tmp;
  991. } else {
  992. $res.= "<<";
  993. if (function_exists('gzcompress') && $this->options['compression']) {
  994. // then implement ZLIB based compression on this content stream
  995. $tmp = gzcompress($tmp, $this->options['compression']);
  996. $res.= " /Filter /FlateDecode";
  997. }
  998. $res.= " /Length ".mb_strlen($tmp, '8bit') .">>\nstream\n$tmp\nendstream";
  999. }
  1000. $res.= "\nendobj";
  1001. return $res;
  1002. }
  1003. }
  1004. /**
  1005. * define the document information
  1006. */
  1007. private function o_info($id,$action,$options=''){
  1008. if ($action!='new'){
  1009. $o =& $this->objects[$id];
  1010. }
  1011. switch ($action){
  1012. case 'new':
  1013. $this->infoObject=$id;
  1014. $date='D:'.date('Ymd');
  1015. $this->objects[$id]=array('t'=>'info','info'=>array('Creator'=>'R&OS php pdf class, http://pdf-php.sf.net/','CreationDate'=>$date));
  1016. break;
  1017. case 'Title':
  1018. case 'Author':
  1019. case 'Subject':
  1020. case 'Keywords':
  1021. case 'Creator':
  1022. case 'Producer':
  1023. case 'CreationDate':
  1024. case 'ModDate':
  1025. case 'Trapped':
  1026. $o['info'][$action]=$options;
  1027. break;
  1028. case 'out':
  1029. if ($this->encryptionMode > 0){
  1030. $this->encryptInit($id);
  1031. }
  1032. $res="\n".$id." 0 obj\n<< ";
  1033. foreach ($o['info'] as $k=>$v){
  1034. $res.='/'.$k.' (';
  1035. if ($this->encryptionMode > 0){
  1036. $res.=$this->filterText($this->ARC4($v), true, false);
  1037. } else {
  1038. $res.=$this->filterText($v, true, false);
  1039. }
  1040. $res.=") ";
  1041. }
  1042. $res.=">>\nendobj";
  1043. return $res;
  1044. break;
  1045. }
  1046. }
  1047. /**
  1048. * an action object, used to link to URLS initially
  1049. * In version >= 0.12.2 internal and external links are handled in o_annotation directly
  1050. * Additional actions, like SubmitForm, ResetForm, ImportData, Javascript will be part of
  1051. * o_actions. Unless we also do not handle them similar to Links.
  1052. *
  1053. */
  1054. private function o_action($id,$action,$options=''){
  1055. if ($action!='new'){
  1056. $o =& $this->objects[$id];
  1057. }
  1058. switch ($action){
  1059. case 'new':
  1060. if (is_array($options)){
  1061. $this->objects[$id]=array('t'=>'action','info'=>$options,'type'=>$options['type']);
  1062. } else {
  1063. // then assume a URI action
  1064. $this->objects[$id]=array('t'=>'action','info'=>$options,'type'=>'URI');
  1065. }
  1066. break;
  1067. case 'out':
  1068. if ($this->encryptionMode > 0){
  1069. $this->encryptInit($id);
  1070. }
  1071. $res="\n".$id." 0 obj\n<< /Type /Action";
  1072. switch($o['type']){
  1073. case 'ilink':
  1074. // there will be an 'label' setting, this is the name of the destination
  1075. $res.=" /S /GoTo /D ".$this->destinations[(string)$o['info']['label']]." 0 R";
  1076. break;
  1077. case 'URI':
  1078. $res.=" /S /URI /URI (";
  1079. if ($this->encryptionMode > 0){
  1080. $res.=$this->filterText($this->ARC4($o['info']), true, false);
  1081. } else {
  1082. $res.=$this->filterText($o['info'], true, false);
  1083. }
  1084. $res.=")";
  1085. break;
  1086. }
  1087. $res.=" >>\nendobj";
  1088. return $res;
  1089. break;
  1090. }
  1091. }
  1092. /**
  1093. * an annotation object, this will add an annotation to the current page.
  1094. * initially will support just link annotations
  1095. */
  1096. private function o_annotation($id,$action,$options=''){
  1097. if ($action!='new'){
  1098. $o =& $this->objects[$id];
  1099. }
  1100. switch ($action){
  1101. case 'new':
  1102. // add the annotation to the current page
  1103. $pageId = $this->currentPage;
  1104. $this->o_page($pageId,'annot',$id);
  1105. // and add the action object which is going to be required
  1106. switch($options['type']){
  1107. case 'link':
  1108. $this->objects[$id]=array('t'=>'annotation','info'=>$options);
  1109. //$this->numObj++;
  1110. //$this->o_action($this->numObj,'new',$options['url']);
  1111. //$this->objects[$id]['info']['actionId']=$this->numObj;
  1112. break;
  1113. case 'ilink':
  1114. // this is to a named internal link
  1115. $label = $options['label'];
  1116. $this->objects[$id]=array('t'=>'annotation','info'=>$options);
  1117. //$this->numObj++;
  1118. //$this->o_action($this->numObj,'new',array('type'=>'ilink','label'=>$label));
  1119. //$this->objects[$id]['info']['actionId']=$this->numObj;
  1120. break;
  1121. case 'text':
  1122. $this->objects[$id]=array('t'=>'annotation','info'=>$options);
  1123. break;
  1124. }
  1125. break;
  1126. case 'out':
  1127. $res="\n".$id." 0 obj\n<< /Type /Annot";
  1128. switch($o['info']['type']){
  1129. case 'link':
  1130. $res.= " /Subtype /Link";
  1131. $res.=" /A << /S /URI /URI (".$o['info']['url'].") >>";
  1132. $res.=" /Border [0 0 0]";
  1133. $res.=" /H /I";
  1134. break;
  1135. case 'ilink':
  1136. $res.= " /Subtype /Link";
  1137. if(isset($this->destinations[(string)$o['info']['label']])){
  1138. $res.=" /A << /S /GoTo /D ".$this->destinations[(string)$o['info']['label']]." 0 R >>";
  1139. }
  1140. $res.=" /Border [0 0 0]";
  1141. $res.=" /H /I";
  1142. break;
  1143. case 'text':
  1144. $res.= " /Subtype /Text";
  1145. $res.= " /T (".$this->filterText($o['info']['title'], false, false).") /Contents (".$o['info']['content'].")";
  1146. break;
  1147. }
  1148. $res.=" /Rect [ ";
  1149. foreach($o['info']['rect'] as $v){
  1150. $res.= sprintf("%.4F ",$v);
  1151. }
  1152. $res.="]";
  1153. $res.=" >>\nendobj";
  1154. return $res;
  1155. break;
  1156. }
  1157. }
  1158. /**
  1159. * a page object, it also creates a contents object to hold its contents
  1160. */
  1161. private function o_page($id,$action,$options=''){
  1162. if ($action!='new'){
  1163. $o =& $this->objects[$id];
  1164. }
  1165. switch ($action){
  1166. case 'new':
  1167. $this->numPages++;
  1168. $this->objects[$id]=array('t'=>'page','info'=>array('parent'=>$this->currentNode,'pageNum'=>$this->numPages));
  1169. if (is_array($options)){
  1170. // then this must be a page insertion, array shoudl contain 'rid','pos'=[before|after]
  1171. $options['id']=$id;
  1172. $this->o_pages($this->currentNode,'page',$options);
  1173. } else {
  1174. $this->o_pages($this->currentNode,'page',$id);
  1175. }
  1176. $this->currentPage=$id;
  1177. // make a contents object to go with this page
  1178. $this->numObj++;
  1179. $this->o_contents($this->numObj,'new',$id);
  1180. $this->currentContents=$this->numObj;
  1181. $this->objects[$id]['info']['contents']=array();
  1182. $this->objects[$id]['info']['contents'][]=$this->numObj;
  1183. $match = ($this->numPages%2 ? 'odd' : 'even');
  1184. foreach($this->addLooseObjects as $oId=>$target){
  1185. if ($target=='all' || $match==$target){
  1186. $this->objects[$id]['info']['contents'][]=$oId;
  1187. }
  1188. }
  1189. break;
  1190. case 'content':
  1191. $o['info']['contents'][]=$options;
  1192. break;
  1193. case 'annot':
  1194. // add an annotation to this page
  1195. if (!isset($o['info']['annot'])){
  1196. $o['info']['annot']=array();
  1197. }
  1198. // $options should contain the id of the annotation dictionary
  1199. $o['info']['annot'][]=$options;
  1200. break;
  1201. case 'out':
  1202. $res="\n".$id." 0 obj\n<< /Type /Page";
  1203. $res.=" /Parent ".$o['info']['parent']." 0 R";
  1204. if (isset($o['info']['annot'])){
  1205. $res.=" /Annots [";
  1206. foreach($o['info']['annot'] as $aId){
  1207. $res.=" ".$aId." 0 R";
  1208. }
  1209. $res.=" ]";
  1210. }
  1211. $count = count($o['info']['contents']);
  1212. if ($count==1){
  1213. $res.=" /Contents ".$o['info']['contents'][0]." 0 R";
  1214. } else if ($count>1){
  1215. $res.=" /Contents [ ";
  1216. foreach ($o['info']['contents'] as $cId){
  1217. $res.=$cId." 0 R …

Large files files are truncated, but you can click here to view the full file