Cpdf

A PHP class to provide the basic functionality to create a pdf document without any requirement for additional modules.

Defined (1)

The class is defined in the following location(s).

/lib/dompdf/lib/class.pdf.php  
  1. class Cpdf { 
  2.  
  3. /** 
  4. * @var integer The current number of pdf objects in the document 
  5. */ 
  6. public $numObj = 0; 
  7.  
  8. /** 
  9. * @var array This array contains all of the pdf objects, ready for final assembly 
  10. */ 
  11. public $objects = array(); 
  12.  
  13. /** 
  14. * @var integer The objectId (number within the objects array) of the document catalog 
  15. */ 
  16. public $catalogId; 
  17.  
  18. /** 
  19. * @var array Array carrying information about the fonts that the system currently knows about 
  20. * Used to ensure that a font is not loaded twice, among other things 
  21. */ 
  22. public $fonts = array(); 
  23.  
  24. /** 
  25. * @var string The default font metrics file to use if no other font has been loaded. 
  26. * The path to the directory containing the font metrics should be included 
  27. */ 
  28. public $defaultFont = './fonts/Helvetica.afm'; 
  29.  
  30. /** 
  31. * @string A record of the current font 
  32. */ 
  33. public $currentFont = ''; 
  34.  
  35. /** 
  36. * @var string The current base font 
  37. */ 
  38. public $currentBaseFont = ''; 
  39.  
  40. /** 
  41. * @var integer The number of the current font within the font array 
  42. */ 
  43. public $currentFontNum = 0; 
  44.  
  45. /** 
  46. * @var integer 
  47. */ 
  48. public $currentNode; 
  49.  
  50. /** 
  51. * @var integer Object number of the current page 
  52. */ 
  53. public $currentPage; 
  54.  
  55. /** 
  56. * @var integer Object number of the currently active contents block 
  57. */ 
  58. public $currentContents; 
  59.  
  60. /** 
  61. * @var integer Number of fonts within the system 
  62. */ 
  63. public $numFonts = 0; 
  64.  
  65. /** 
  66. * @var integer Number of graphic state resources used 
  67. */ 
  68. private $numStates = 0; 
  69.  
  70. /** 
  71. * @var array Current color for fill operations, defaults to inactive value,  
  72. * all three components should be between 0 and 1 inclusive when active 
  73. */ 
  74. public $currentColor = null; 
  75.  
  76. /** 
  77. * @var array Current color for stroke operations (lines etc.) 
  78. */ 
  79. public $currentStrokeColor = null; 
  80.  
  81. /** 
  82. * @var string Current style that lines are drawn in 
  83. */ 
  84. public $currentLineStyle = ''; 
  85.  
  86. /** 
  87. * @var array Current line transparency (partial graphics state) 
  88. */ 
  89. public $currentLineTransparency = array("mode" => "Normal", "opacity" => 1.0); 
  90.  
  91. /** 
  92. * array Current fill transparency (partial graphics state) 
  93. */ 
  94. public $currentFillTransparency = array("mode" => "Normal", "opacity" => 1.0); 
  95.  
  96. /** 
  97. * @var array An array which is used to save the state of the document, mainly the colors and styles 
  98. * it is used to temporarily change to another state, the change back to what it was before 
  99. */ 
  100. public $stateStack = array(); 
  101.  
  102. /** 
  103. * @var integer Number of elements within the state stack 
  104. */ 
  105. public $nStateStack = 0; 
  106.  
  107. /** 
  108. * @var integer Number of page objects within the document 
  109. */ 
  110. public $numPages = 0; 
  111.  
  112. /** 
  113. * @var array Object Id storage stack 
  114. */ 
  115. public $stack = array(); 
  116.  
  117. /** 
  118. * @var integer Number of elements within the object Id storage stack 
  119. */ 
  120. public $nStack = 0; 
  121.  
  122. /** 
  123. * an array which contains information about the objects which are not firmly attached to pages 
  124. * these have been added with the addObject function 
  125. */ 
  126. public $looseObjects = array(); 
  127.  
  128. /** 
  129. * array contains infomation about how the loose objects are to be added to the document 
  130. */ 
  131. public $addLooseObjects = array(); 
  132.  
  133. /** 
  134. * @var integer The objectId of the information object for the document 
  135. * this contains authorship, title etc. 
  136. */ 
  137. public $infoObject = 0; 
  138.  
  139. /** 
  140. * @var integer Number of images being tracked within the document 
  141. */ 
  142. public $numImages = 0; 
  143.  
  144. /** 
  145. * @var array An array containing options about the document 
  146. * it defaults to turning on the compression of the objects 
  147. */ 
  148. public $options = array('compression' => true); 
  149.  
  150. /** 
  151. * @var integer The objectId of the first page of the document 
  152. */ 
  153. public $firstPageId; 
  154.  
  155. /** 
  156. * @var float Used to track the last used value of the inter-word spacing, this is so that it is known 
  157. * when the spacing is changed. 
  158. */ 
  159. public $wordSpaceAdjust = 0; 
  160.  
  161. /** 
  162. * @var float Used to track the last used value of the inter-letter spacing, this is so that it is known 
  163. * when the spacing is changed. 
  164. */ 
  165. public $charSpaceAdjust = 0; 
  166.  
  167. /** 
  168. * @var integer The object Id of the procset object 
  169. */ 
  170. public $procsetObjectId; 
  171.  
  172. /** 
  173. * @var array Store the information about the relationship between font families 
  174. * this used so that the code knows which font is the bold version of another font, etc. 
  175. * the value of this array is initialised in the constuctor function. 
  176. */ 
  177. public $fontFamilies = array(); 
  178.  
  179. /** 
  180. * @var string Folder for php serialized formats of font metrics files. 
  181. * If empty string, use same folder as original metrics files. 
  182. * This can be passed in from class creator. 
  183. * If this folder does not exist or is not writable, Cpdf will be **much** slower. 
  184. * Because of potential trouble with php safe mode, folder cannot be created at runtime. 
  185. */ 
  186. public $fontcache = ''; 
  187.  
  188. /** 
  189. * @var integer The version of the font metrics cache file. 
  190. * This value must be manually incremented whenever the internal font data structure is modified. 
  191. */ 
  192. public $fontcacheVersion = 6; 
  193.  
  194. /** 
  195. * @var string Temporary folder. 
  196. * If empty string, will attempty system tmp folder. 
  197. * This can be passed in from class creator. 
  198. * Only used for conversion of gd images to jpeg images. 
  199. */ 
  200. public $tmp = ''; 
  201.  
  202. /** 
  203. * @var string Track if the current font is bolded or italicised 
  204. */ 
  205. public $currentTextState = ''; 
  206.  
  207. /** 
  208. * @var string Messages are stored here during processing, these can be selected afterwards to give some useful debug information 
  209. */ 
  210. public $messages = ''; 
  211.  
  212. /** 
  213. * @var string The ancryption array for the document encryption is stored here 
  214. */ 
  215. public $arc4 = ''; 
  216.  
  217. /** 
  218. * @var integer The object Id of the encryption information 
  219. */ 
  220. public $arc4_objnum = 0; 
  221.  
  222. /** 
  223. * @var string The file identifier, used to uniquely identify a pdf document 
  224. */ 
  225. public $fileIdentifier = ''; 
  226.  
  227. /** 
  228. * @var boolean A flag to say if a document is to be encrypted or not 
  229. */ 
  230. public $encrypted = false; 
  231.  
  232. /** 
  233. * @var string The encryption key for the encryption of all the document content (structure is not encrypted) 
  234. */ 
  235. public $encryptionKey = ''; 
  236.  
  237. /** 
  238. * @var array Array which forms a stack to keep track of nested callback functions 
  239. */ 
  240. public $callback = array(); 
  241.  
  242. /** 
  243. * @var integer The number of callback functions in the callback array 
  244. */ 
  245. public $nCallback = 0; 
  246.  
  247. /** 
  248. * @var array Store label->id pairs for named destinations, these will be used to replace internal links 
  249. * done this way so that destinations can be defined after the location that links to them 
  250. */ 
  251. public $destinations = array(); 
  252.  
  253. /** 
  254. * @var array Store the stack for the transaction commands, each item in here is a record of the values of all the 
  255. * publiciables within the class, so that the user can rollback at will (from each 'start' command) 
  256. * note that this includes the objects array, so these can be large. 
  257. */ 
  258. public $checkpoint = ''; 
  259.  
  260. /** 
  261. * @var array Table of Image origin filenames and image labels which were already added with o_image(). 
  262. * Allows to merge identical images 
  263. */ 
  264. public $imagelist = array(); 
  265.  
  266. /** 
  267. * @var boolean Whether the text passed in should be treated as Unicode or just local character set. 
  268. */ 
  269. public $isUnicode = false; 
  270.  
  271. /** 
  272. * @var string the JavaScript code of the document 
  273. */ 
  274. public $javascript = ''; 
  275.  
  276. /** 
  277. * @var boolean whether the compression is possible 
  278. */ 
  279. protected $compressionReady = false; 
  280.  
  281. /** 
  282. * @var array Current page size 
  283. */ 
  284. protected $currentPageSize = array("width" => 0, "height" => 0); 
  285.  
  286. /** 
  287. * @var array All the chars that will be required in the font subsets 
  288. */ 
  289. protected $stringSubsets = array(); 
  290.  
  291. /** 
  292. * @var string The target internal encoding 
  293. */ 
  294. static protected $targetEncoding = 'iso-8859-1'; 
  295.  
  296. /** 
  297. * @var array The list of the core fonts 
  298. */ 
  299. static protected $coreFonts = array( 
  300. 'courier', 'courier-bold', 'courier-oblique', 'courier-boldoblique',  
  301. 'helvetica', 'helvetica-bold', 'helvetica-oblique', 'helvetica-boldoblique',  
  302. 'times-roman', 'times-bold', 'times-italic', 'times-bolditalic',  
  303. 'symbol', 'zapfdingbats' 
  304. ); 
  305.  
  306. /** 
  307. * Class constructor 
  308. * This will start a new document 
  309. * @param array $pageSize Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero. 
  310. * @param boolean $isUnicode Whether text will be treated as Unicode or not. 
  311. * @param string $fontcache The font cache folder 
  312. * @param string $tmp The temporary folder 
  313. */ 
  314. function __construct($pageSize = array(0, 0, 612, 792), $isUnicode = false, $fontcache = '', $tmp = '') { 
  315. $this->isUnicode = $isUnicode; 
  316. $this->fontcache = $fontcache; 
  317. $this->tmp = $tmp; 
  318. $this->newDocument($pageSize); 
  319.  
  320. $this->compressionReady = function_exists('gzcompress'); 
  321.  
  322. if ( in_array('Windows-1252', mb_list_encodings()) ) { 
  323. self::$targetEncoding = 'Windows-1252'; 
  324.  
  325. // also initialize the font families that are known about already 
  326. $this->setFontFamily('init'); 
  327. // $this->fileIdentifier = md5('xxxxxxxx'.time()); 
  328.  
  329. /** 
  330. * Document object methods (internal use only) 
  331. * There is about one object method for each type of object in the pdf document 
  332. * Each function has the same call list ($id, $action, $options). 
  333. * $id = the object ID of the object, or what it is to be if it is being created 
  334. * $action = a string specifying the action to be performed, though ALL must support: 
  335. * 'new' - create the object with the id $id 
  336. * 'out' - produce the output for the pdf object 
  337. * $options = optional, a string or array containing the various parameters for the object 
  338. * These, in conjunction with the output function are the ONLY way for output to be produced 
  339. * within the pdf 'file'. 
  340. */ 
  341.  
  342. /** 
  343. * Destination object, used to specify the location for the user to jump to, presently on opening 
  344. */ 
  345. protected function o_destination($id, $action, $options = '') { 
  346. if ($action !== 'new') { 
  347. $o = &$this->objects[$id]; 
  348.  
  349. switch ($action) { 
  350. case 'new': 
  351. $this->objects[$id] = array('t' => 'destination', 'info' => array()); 
  352. $tmp = ''; 
  353. switch ($options['type']) { 
  354. case 'XYZ': 
  355. case 'FitR': 
  356. $tmp = ' '.$options['p3'].$tmp; 
  357. case 'FitH': 
  358. case 'FitV': 
  359. case 'FitBH': 
  360. case 'FitBV': 
  361. $tmp = ' '.$options['p1'].' '.$options['p2'].$tmp; 
  362. case 'Fit': 
  363. case 'FitB': 
  364. $tmp = $options['type'].$tmp; 
  365. $this->objects[$id]['info']['string'] = $tmp; 
  366. $this->objects[$id]['info']['page'] = $options['page']; 
  367. break; 
  368.  
  369. case 'out': 
  370. $tmp = $o['info']; 
  371. $res = "\n$id 0 obj\n".'['.$tmp['page'].' 0 R /'.$tmp['string']."]\nendobj"; 
  372. return $res; 
  373.  
  374. /** 
  375. * set the viewer preferences 
  376. */ 
  377. protected function o_viewerPreferences($id, $action, $options = '') { 
  378. if ($action !== 'new') { 
  379. $o = & $this->objects[$id]; 
  380.  
  381. switch ($action) { 
  382. case 'new': 
  383. $this->objects[$id] = array('t' => 'viewerPreferences', 'info' => array()); 
  384. break; 
  385.  
  386. case 'add': 
  387. foreach ($options as $k => $v) { 
  388. switch ($k) { 
  389. case 'HideToolbar': 
  390. case 'HideMenubar': 
  391. case 'HideWindowUI': 
  392. case 'FitWindow': 
  393. case 'CenterWindow': 
  394. case 'NonFullScreenPageMode': 
  395. case 'Direction': 
  396. $o['info'][$k] = $v; 
  397. break; 
  398. break; 
  399.  
  400. case 'out': 
  401. $res = "\n$id 0 obj\n<< "; 
  402. foreach ($o['info'] as $k => $v) { 
  403. $res.= "\n/$k $v"; 
  404. $res.= "\n>>\n"; 
  405. return $res; 
  406.  
  407. /** 
  408. * define the document catalog, the overall controller for the document 
  409. */ 
  410. protected function o_catalog($id, $action, $options = '') { 
  411. if ($action !== 'new') { 
  412. $o = & $this->objects[$id]; 
  413.  
  414. switch ($action) { 
  415. case 'new': 
  416. $this->objects[$id] = array('t' => 'catalog', 'info' => array()); 
  417. $this->catalogId = $id; 
  418. break; 
  419.  
  420. case 'outlines': 
  421. case 'pages': 
  422. case 'openHere': 
  423. case 'javascript': 
  424. $o['info'][$action] = $options; 
  425. break; 
  426.  
  427. case 'viewerPreferences': 
  428. if (!isset($o['info']['viewerPreferences'])) { 
  429. $this->numObj++; 
  430. $this->o_viewerPreferences($this->numObj, 'new'); 
  431. $o['info']['viewerPreferences'] = $this->numObj; 
  432.  
  433. $vp = $o['info']['viewerPreferences']; 
  434. $this->o_viewerPreferences($vp, 'add', $options); 
  435.  
  436. break; 
  437.  
  438. case 'out': 
  439. $res = "\n$id 0 obj\n<< /Type /Catalog"; 
  440.  
  441. foreach ($o['info'] as $k => $v) { 
  442. switch ($k) { 
  443. case 'outlines': 
  444. $res.= "\n/Outlines $v 0 R"; 
  445. break; 
  446.  
  447. case 'pages': 
  448. $res.= "\n/Pages $v 0 R"; 
  449. break; 
  450.  
  451. case 'viewerPreferences': 
  452. $res.= "\n/ViewerPreferences $v 0 R"; 
  453. break; 
  454.  
  455. case 'openHere': 
  456. $res.= "\n/OpenAction $v 0 R"; 
  457. break; 
  458.  
  459. case 'javascript': 
  460. $res.= "\n/Names <</JavaScript $v 0 R>>"; 
  461. break; 
  462.  
  463. $res.= " >>\nendobj"; 
  464. return $res; 
  465.  
  466. /** 
  467. * object which is a parent to the pages in the document 
  468. */ 
  469. protected function o_pages($id, $action, $options = '') { 
  470. if ($action !== 'new') { 
  471. $o = & $this->objects[$id]; 
  472.  
  473. switch ($action) { 
  474. case 'new': 
  475. $this->objects[$id] = array('t' => 'pages', 'info' => array()); 
  476. $this->o_catalog($this->catalogId, 'pages', $id); 
  477. break; 
  478.  
  479. case 'page': 
  480. if (!is_array($options)) { 
  481. // then it will just be the id of the new page 
  482. $o['info']['pages'][] = $options; 
  483. else { 
  484. // then it should be an array having 'id', 'rid', 'pos', where rid=the page to which this one will be placed relative 
  485. // and pos is either 'before' or 'after', saying where this page will fit. 
  486. if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) { 
  487. $i = array_search($options['rid'], $o['info']['pages']); 
  488. if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i] == $options['rid']) { 
  489.  
  490. // then there is a match 
  491. // make a space 
  492. switch ($options['pos']) { 
  493. case 'before': 
  494. $k = $i; 
  495. break; 
  496.  
  497. case 'after': 
  498. $k = $i+1; 
  499. break; 
  500.  
  501. default: 
  502. $k = -1; 
  503. break; 
  504.  
  505. if ($k >= 0) { 
  506. for ($j = count($o['info']['pages'])-1; $j >= $k; $j--) { 
  507. $o['info']['pages'][$j+1] = $o['info']['pages'][$j]; 
  508.  
  509. $o['info']['pages'][$k] = $options['id']; 
  510. break; 
  511.  
  512. case 'procset': 
  513. $o['info']['procset'] = $options; 
  514. break; 
  515.  
  516. case 'mediaBox': 
  517. $o['info']['mediaBox'] = $options; 
  518. // which should be an array of 4 numbers 
  519. $this->currentPageSize = array('width' => $options[2], 'height' => $options[3]); 
  520. break; 
  521.  
  522. case 'font': 
  523. $o['info']['fonts'][] = array('objNum' => $options['objNum'], 'fontNum' => $options['fontNum']); 
  524. break; 
  525.  
  526. case 'extGState': 
  527. $o['info']['extGStates'][] = array('objNum' => $options['objNum'], 'stateNum' => $options['stateNum']); 
  528. break; 
  529.  
  530. case 'xObject': 
  531. $o['info']['xObjects'][] = array('objNum' => $options['objNum'], 'label' => $options['label']); 
  532. break; 
  533.  
  534. case 'out': 
  535. if (count($o['info']['pages'])) { 
  536. $res = "\n$id 0 obj\n<< /Type /Pages\n/Kids ["; 
  537. foreach ($o['info']['pages'] as $v) { 
  538. $res.= "$v 0 R\n"; 
  539.  
  540. $res.= "]\n/Count ".count($this->objects[$id]['info']['pages']); 
  541.  
  542. if ( (isset($o['info']['fonts']) && count($o['info']['fonts'])) || 
  543. isset($o['info']['procset']) || 
  544. (isset($o['info']['extGStates']) && count($o['info']['extGStates']))) { 
  545. $res.= "\n/Resources <<"; 
  546.  
  547. if (isset($o['info']['procset'])) { 
  548. $res.= "\n/ProcSet ".$o['info']['procset']." 0 R"; 
  549.  
  550. if (isset($o['info']['fonts']) && count($o['info']['fonts'])) { 
  551. $res.= "\n/Font << "; 
  552. foreach ($o['info']['fonts'] as $finfo) { 
  553. $res.= "\n/F".$finfo['fontNum']." ".$finfo['objNum']." 0 R"; 
  554. $res.= "\n>>"; 
  555.  
  556. if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) { 
  557. $res.= "\n/XObject << "; 
  558. foreach ($o['info']['xObjects'] as $finfo) { 
  559. $res.= "\n/".$finfo['label']." ".$finfo['objNum']." 0 R"; 
  560. $res.= "\n>>"; 
  561.  
  562. if ( isset($o['info']['extGStates']) && count($o['info']['extGStates'])) { 
  563. $res.= "\n/ExtGState << "; 
  564. foreach ($o['info']['extGStates'] as $gstate) { 
  565. $res.= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R"; 
  566. $res.= "\n>>"; 
  567.  
  568. $res.= "\n>>"; 
  569. if (isset($o['info']['mediaBox'])) { 
  570. $tmp = $o['info']['mediaBox']; 
  571. $res.= "\n/MediaBox [".sprintf('%.3F %.3F %.3F %.3F', $tmp[0], $tmp[1], $tmp[2], $tmp[3]) .']'; 
  572.  
  573. $res.= "\n >>\nendobj"; 
  574. else { 
  575. $res = "\n$id 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj"; 
  576.  
  577. return $res; 
  578.  
  579. /** 
  580. * define the outlines in the doc, empty for now 
  581. */ 
  582. protected function o_outlines($id, $action, $options = '') { 
  583. if ($action !== 'new') { 
  584. $o = &$this->objects[$id]; 
  585.  
  586. switch ($action) { 
  587. case 'new': 
  588. $this->objects[$id] = array('t' => 'outlines', 'info' => array('outlines' => array())); 
  589. $this->o_catalog($this->catalogId, 'outlines', $id); 
  590. break; 
  591.  
  592. case 'outline': 
  593. $o['info']['outlines'][] = $options; 
  594. break; 
  595.  
  596. case 'out': 
  597. if (count($o['info']['outlines'])) { 
  598. $res = "\n$id 0 obj\n<< /Type /Outlines /Kids ["; 
  599. foreach ($o['info']['outlines'] as $v) { 
  600. $res.= "$v 0 R "; 
  601.  
  602. $res.= "] /Count ".count($o['info']['outlines']) ." >>\nendobj"; 
  603. } else { 
  604. $res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj"; 
  605.  
  606. return $res; 
  607.  
  608. /** 
  609. * an object to hold the font description 
  610. */ 
  611. protected function o_font($id, $action, $options = '') { 
  612. if ($action !== 'new') { 
  613. $o = &$this->objects[$id]; 
  614.  
  615. switch ($action) { 
  616. case 'new': 
  617. $this->objects[$id] = array('t' => 'font', 'info' => array('name' => $options['name'], 'fontFileName' => $options['fontFileName'], 'SubType' => 'Type1')); 
  618. $fontNum = $this->numFonts; 
  619. $this->objects[$id]['info']['fontNum'] = $fontNum; 
  620.  
  621. // deal with the encoding and the differences 
  622. if (isset($options['differences'])) { 
  623. // then we'll need an encoding dictionary 
  624. $this->numObj++; 
  625. $this->o_fontEncoding($this->numObj, 'new', $options); 
  626. $this->objects[$id]['info']['encodingDictionary'] = $this->numObj; 
  627. else if (isset($options['encoding'])) { 
  628. // we can specify encoding here 
  629. switch ($options['encoding']) { 
  630. case 'WinAnsiEncoding': 
  631. case 'MacRomanEncoding': 
  632. case 'MacExpertEncoding': 
  633. $this->objects[$id]['info']['encoding'] = $options['encoding']; 
  634. break; 
  635.  
  636. case 'none': 
  637. break; 
  638.  
  639. default: 
  640. $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding'; 
  641. break; 
  642. else { 
  643. $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding'; 
  644.  
  645. if ($this->fonts[$options['fontFileName']]['isUnicode']) { 
  646. // For Unicode fonts, we need to incorporate font data into 
  647. // sub-sections that are linked from the primary font section. 
  648. // Look at o_fontGIDtoCID and o_fontDescendentCID functions 
  649. // for more informaiton. 
  650. // 
  651. // All of this code is adapted from the excellent changes made to 
  652. // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/) 
  653.  
  654. $toUnicodeId = ++$this->numObj; 
  655. $this->o_contents($toUnicodeId, 'new', 'raw'); 
  656. $this->objects[$id]['info']['toUnicode'] = $toUnicodeId; 
  657.  
  658. $stream = <<<EOT 
  659. /CIDInit /ProcSet findresource begin 
  660. 12 dict begin 
  661. begincmap 
  662. /CIDSystemInfo 
  663. <</Registry (Adobe) 
  664. /Ordering (UCS) 
  665. /Supplement 0 
  666. >> def 
  667. /CMapName /Adobe-Identity-UCS def 
  668. /CMapType 2 def 
  669. 1 begincodespacerange 
  670. <0000> <FFFF> 
  671. endcodespacerange 
  672. 1 beginbfrange 
  673. <0000> <FFFF> <0000> 
  674. endbfrange 
  675. endcmap 
  676. CMapName currentdict /CMap defineresource pop 
  677. end 
  678. end 
  679. EOT; 
  680.  
  681. $res = "<</Length " . mb_strlen($stream, '8bit') . " >>\n"; 
  682. $res .= "stream\n" . $stream . "endstream"; 
  683.  
  684. $this->objects[$toUnicodeId]['c'] = $res; 
  685.  
  686. $cidFontId = ++$this->numObj; 
  687. $this->o_fontDescendentCID($cidFontId, 'new', $options); 
  688. $this->objects[$id]['info']['cidFont'] = $cidFontId; 
  689.  
  690. // also tell the pages node about the new font 
  691. $this->o_pages($this->currentNode, 'font', array('fontNum' => $fontNum, 'objNum' => $id)); 
  692. break; 
  693.  
  694. case 'add': 
  695. foreach ($options as $k => $v) { 
  696. switch ($k) { 
  697. case 'BaseFont': 
  698. $o['info']['name'] = $v; 
  699. break; 
  700. case 'FirstChar': 
  701. case 'LastChar': 
  702. case 'Widths': 
  703. case 'FontDescriptor': 
  704. case 'SubType': 
  705. $this->addMessage('o_font '.$k." : ".$v); 
  706. $o['info'][$k] = $v; 
  707. break; 
  708.  
  709. // pass values down to descendent font 
  710. if (isset($o['info']['cidFont'])) { 
  711. $this->o_fontDescendentCID($o['info']['cidFont'], 'add', $options); 
  712. break; 
  713.  
  714. case 'out': 
  715. if ($this->fonts[$this->objects[$id]['info']['fontFileName']]['isUnicode']) { 
  716. // For Unicode fonts, we need to incorporate font data into 
  717. // sub-sections that are linked from the primary font section. 
  718. // Look at o_fontGIDtoCID and o_fontDescendentCID functions 
  719. // for more informaiton. 
  720. // 
  721. // All of this code is adapted from the excellent changes made to 
  722. // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/) 
  723.  
  724. $res = "\n$id 0 obj\n<</Type /Font\n/Subtype /Type0\n"; 
  725. $res.= "/BaseFont /".$o['info']['name']."\n"; 
  726.  
  727. // The horizontal identity mapping for 2-byte CIDs; may be used 
  728. // with CIDFonts using any Registry, Ordering, and Supplement values. 
  729. $res.= "/Encoding /Identity-H\n"; 
  730. $res.= "/DescendantFonts [".$o['info']['cidFont']." 0 R]\n"; 
  731. $res.= "/ToUnicode ".$o['info']['toUnicode']." 0 R\n"; 
  732. $res.= ">>\n"; 
  733. $res.= "endobj"; 
  734. } else { 
  735. $res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /".$o['info']['SubType']."\n"; 
  736. $res.= "/Name /F".$o['info']['fontNum']."\n"; 
  737. $res.= "/BaseFont /".$o['info']['name']."\n"; 
  738.  
  739. if (isset($o['info']['encodingDictionary'])) { 
  740. // then place a reference to the dictionary 
  741. $res.= "/Encoding ".$o['info']['encodingDictionary']." 0 R\n"; 
  742. } else if (isset($o['info']['encoding'])) { 
  743. // use the specified encoding 
  744. $res.= "/Encoding /".$o['info']['encoding']."\n"; 
  745.  
  746. if (isset($o['info']['FirstChar'])) { 
  747. $res.= "/FirstChar ".$o['info']['FirstChar']."\n"; 
  748.  
  749. if (isset($o['info']['LastChar'])) { 
  750. $res.= "/LastChar ".$o['info']['LastChar']."\n"; 
  751.  
  752. if (isset($o['info']['Widths'])) { 
  753. $res.= "/Widths ".$o['info']['Widths']." 0 R\n"; 
  754.  
  755. if (isset($o['info']['FontDescriptor'])) { 
  756. $res.= "/FontDescriptor ".$o['info']['FontDescriptor']." 0 R\n"; 
  757.  
  758. $res.= ">>\n"; 
  759. $res.= "endobj"; 
  760.  
  761. return $res; 
  762.  
  763. /** 
  764. * a font descriptor, needed for including additional fonts 
  765. */ 
  766. protected function o_fontDescriptor($id, $action, $options = '') { 
  767. if ($action !== 'new') { 
  768. $o = & $this->objects[$id]; 
  769.  
  770. switch ($action) { 
  771. case 'new': 
  772. $this->objects[$id] = array('t' => 'fontDescriptor', 'info' => $options); 
  773. break; 
  774.  
  775. case 'out': 
  776. $res = "\n$id 0 obj\n<< /Type /FontDescriptor\n"; 
  777. foreach ($o['info'] as $label => $value) { 
  778. switch ($label) { 
  779. case 'Ascent': 
  780. case 'CapHeight': 
  781. case 'Descent': 
  782. case 'Flags': 
  783. case 'ItalicAngle': 
  784. case 'StemV': 
  785. case 'AvgWidth': 
  786. case 'Leading': 
  787. case 'MaxWidth': 
  788. case 'MissingWidth': 
  789. case 'StemH': 
  790. case 'XHeight': 
  791. case 'CharSet': 
  792. if (mb_strlen($value, '8bit')) { 
  793. $res.= "/$label $value\n"; 
  794.  
  795. break; 
  796. case 'FontFile': 
  797. case 'FontFile2': 
  798. case 'FontFile3': 
  799. $res.= "/$label $value 0 R\n"; 
  800. break; 
  801.  
  802. case 'FontBBox': 
  803. $res.= "/$label [$value[0] $value[1] $value[2] $value[3]]\n"; 
  804. break; 
  805.  
  806. case 'FontName': 
  807. $res.= "/$label /$value\n"; 
  808. break; 
  809.  
  810. $res.= ">>\nendobj"; 
  811.  
  812. return $res; 
  813.  
  814. /** 
  815. * the font encoding 
  816. */ 
  817. protected function o_fontEncoding($id, $action, $options = '') { 
  818. if ($action !== 'new') { 
  819. $o = & $this->objects[$id]; 
  820.  
  821. switch ($action) { 
  822. case 'new': 
  823. // the options array should contain 'differences' and maybe 'encoding' 
  824. $this->objects[$id] = array('t' => 'fontEncoding', 'info' => $options); 
  825. break; 
  826.  
  827. case 'out': 
  828. $res = "\n$id 0 obj\n<< /Type /Encoding\n"; 
  829. if (!isset($o['info']['encoding'])) { 
  830. $o['info']['encoding'] = 'WinAnsiEncoding'; 
  831.  
  832. if ($o['info']['encoding'] !== 'none') { 
  833. $res.= "/BaseEncoding /".$o['info']['encoding']."\n"; 
  834.  
  835. $res.= "/Differences \n["; 
  836.  
  837. $onum = -100; 
  838.  
  839. foreach ($o['info']['differences'] as $num => $label) { 
  840. if ($num != $onum+1) { 
  841. // we cannot make use of consecutive numbering 
  842. $res.= "\n$num /$label"; 
  843. } else { 
  844. $res.= " /$label"; 
  845.  
  846. $onum = $num; 
  847.  
  848. $res.= "\n]\n>>\nendobj"; 
  849. return $res; 
  850.  
  851. /** 
  852. * a descendent cid font, needed for unicode fonts 
  853. */ 
  854. protected function o_fontDescendentCID($id, $action, $options = '') { 
  855. if ($action !== 'new') { 
  856. $o = & $this->objects[$id]; 
  857.  
  858. switch ($action) { 
  859. case 'new': 
  860. $this->objects[$id] = array('t' => 'fontDescendentCID', 'info' => $options); 
  861.  
  862. // we need a CID system info section 
  863. $cidSystemInfoId = ++$this->numObj; 
  864. $this->o_contents($cidSystemInfoId, 'new', 'raw'); 
  865. $this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId; 
  866. $res = "<</Registry (Adobe)\n"; // A string identifying an issuer of character collections 
  867. $res.= "/Ordering (UCS)\n"; // A string that uniquely names a character collection issued by a specific registry 
  868. $res.= "/Supplement 0\n"; // The supplement number of the character collection. 
  869. $res.= ">>"; 
  870. $this->objects[$cidSystemInfoId]['c'] = $res; 
  871.  
  872. // and a CID to GID map 
  873. $cidToGidMapId = ++$this->numObj; 
  874. $this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options); 
  875. $this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId; 
  876. break; 
  877.  
  878. case 'add': 
  879. foreach ($options as $k => $v) { 
  880. switch ($k) { 
  881. case 'BaseFont': 
  882. $o['info']['name'] = $v; 
  883. break; 
  884.  
  885. case 'FirstChar': 
  886. case 'LastChar': 
  887. case 'MissingWidth': 
  888. case 'FontDescriptor': 
  889. case 'SubType': 
  890. $this->addMessage("o_fontDescendentCID $k : $v"); 
  891. $o['info'][$k] = $v; 
  892. break; 
  893.  
  894. // pass values down to cid to gid map 
  895. $this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options); 
  896. break; 
  897.  
  898. case 'out': 
  899. $res = "\n$id 0 obj\n"; 
  900. $res.= "<</Type /Font\n"; 
  901. $res.= "/Subtype /CIDFontType2\n"; 
  902. $res.= "/BaseFont /".$o['info']['name']."\n"; 
  903. $res.= "/CIDSystemInfo ".$o['info']['cidSystemInfo']." 0 R\n"; 
  904. // if (isset($o['info']['FirstChar'])) { 
  905. // $res.= "/FirstChar ".$o['info']['FirstChar']."\n"; 
  906. // } 
  907.  
  908. // if (isset($o['info']['LastChar'])) { 
  909. // $res.= "/LastChar ".$o['info']['LastChar']."\n"; 
  910. // } 
  911. if (isset($o['info']['FontDescriptor'])) { 
  912. $res.= "/FontDescriptor ".$o['info']['FontDescriptor']." 0 R\n"; 
  913.  
  914. if (isset($o['info']['MissingWidth'])) { 
  915. $res.= "/DW ".$o['info']['MissingWidth']."\n"; 
  916.  
  917. if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) { 
  918. $cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths']; 
  919. $w = ''; 
  920. foreach ($cid_widths as $cid => $width) { 
  921. $w .= "$cid [$width] "; 
  922. $res.= "/W [$w]\n"; 
  923.  
  924. $res.= "/CIDToGIDMap ".$o['info']['cidToGidMap']." 0 R\n"; 
  925. $res.= ">>\n"; 
  926. $res.= "endobj"; 
  927.  
  928. return $res; 
  929.  
  930. /** 
  931. * a font glyph to character map, needed for unicode fonts 
  932. */ 
  933. protected function o_fontGIDtoCIDMap($id, $action, $options = '') { 
  934. if ($action !== 'new') { 
  935. $o = & $this->objects[$id]; 
  936.  
  937. switch ($action) { 
  938. case 'new': 
  939. $this->objects[$id] = array('t' => 'fontGIDtoCIDMap', 'info' => $options); 
  940. break; 
  941.  
  942. case 'out': 
  943. $res = "\n$id 0 obj\n"; 
  944. $fontFileName = $o['info']['fontFileName']; 
  945. $tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']); 
  946.  
  947. $compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) && 
  948. $this->fonts[$fontFileName]['CIDtoGID_Compressed']; 
  949.  
  950. if (!$compressed && isset($o['raw'])) { 
  951. $res.= $tmp; 
  952. } else { 
  953. $res.= "<<"; 
  954.  
  955. if (!$compressed && $this->compressionReady && $this->options['compression']) { 
  956. // then implement ZLIB based compression on this content stream 
  957. $compressed = true; 
  958. $tmp = gzcompress($tmp, 6); 
  959. if ($compressed) { 
  960. $res.= "\n/Filter /FlateDecode"; 
  961.  
  962. $res.= "\n/Length ".mb_strlen($tmp, '8bit') .">>\nstream\n$tmp\nendstream"; 
  963.  
  964. $res.= "\nendobj"; 
  965. return $res; 
  966.  
  967. /** 
  968. * the document procset, solves some problems with printing to old PS printers 
  969. */ 
  970. protected function o_procset($id, $action, $options = '') { 
  971. if ($action !== 'new') { 
  972. $o = & $this->objects[$id]; 
  973.  
  974. switch ($action) { 
  975. case 'new': 
  976. $this->objects[$id] = array('t' => 'procset', 'info' => array('PDF' => 1, 'Text' => 1)); 
  977. $this->o_pages($this->currentNode, 'procset', $id); 
  978. $this->procsetObjectId = $id; 
  979. break; 
  980.  
  981. case 'add': 
  982. // this is to add new items to the procset list, despite the fact that this is considered 
  983. // obselete, the items are required for printing to some postscript printers 
  984. switch ($options) { 
  985. case 'ImageB': 
  986. case 'ImageC': 
  987. case 'ImageI': 
  988. $o['info'][$options] = 1; 
  989. break; 
  990. break; 
  991.  
  992. case 'out': 
  993. $res = "\n$id 0 obj\n["; 
  994. foreach ($o['info'] as $label => $val) { 
  995. $res.= "/$label "; 
  996. $res.= "]\nendobj"; 
  997. return $res; 
  998.  
  999. /** 
  1000. * define the document information 
  1001. */ 
  1002. protected function o_info($id, $action, $options = '') { 
  1003. if ($action !== 'new') { 
  1004. $o = & $this->objects[$id]; 
  1005.  
  1006. switch ($action) { 
  1007. case 'new': 
  1008. $this->infoObject = $id; 
  1009. $date = 'D:'.@date('Ymd'); 
  1010. $this->objects[$id] = array('t' => 'info', 'info' => array('Creator' => 'R and OS php pdf writer, http://www.ros.co.nz', 'CreationDate' => $date)); 
  1011. break; 
  1012. case 'Title': 
  1013. case 'Author': 
  1014. case 'Subject': 
  1015. case 'Keywords': 
  1016. case 'Creator': 
  1017. case 'Producer': 
  1018. case 'CreationDate': 
  1019. case 'ModDate': 
  1020. case 'Trapped': 
  1021. $o['info'][$action] = $options; 
  1022. break; 
  1023.  
  1024. case 'out': 
  1025. if ($this->encrypted) { 
  1026. $this->encryptInit($id); 
  1027.  
  1028. $res = "\n$id 0 obj\n<<\n"; 
  1029. foreach ($o['info'] as $k => $v) { 
  1030. $res.= "/$k ("; 
  1031.  
  1032. if ($this->encrypted) { 
  1033. $v = $this->ARC4($v); 
  1034.  
  1035. // dates must be outputted as-is, without Unicode transformations 
  1036. elseif (!in_array($k, array('CreationDate', 'ModDate'))) { 
  1037. $v = $this->filterText($v); 
  1038.  
  1039. $res.= $v; 
  1040. $res.= ")\n"; 
  1041.  
  1042. $res.= ">>\nendobj"; 
  1043. return $res; 
  1044.  
  1045. /** 
  1046. * an action object, used to link to URLS initially 
  1047. */ 
  1048. protected function o_action($id, $action, $options = '') { 
  1049. if ($action !== 'new') { 
  1050. $o = & $this->objects[$id]; 
  1051.  
  1052. switch ($action) { 
  1053. case 'new': 
  1054. if (is_array($options)) { 
  1055. $this->objects[$id] = array('t' => 'action', 'info' => $options, 'type' => $options['type']); 
  1056. } else { 
  1057. // then assume a URI action 
  1058. $this->objects[$id] = array('t' => 'action', 'info' => $options, 'type' => 'URI'); 
  1059. break; 
  1060.  
  1061. case 'out': 
  1062. if ($this->encrypted) { 
  1063. $this->encryptInit($id); 
  1064.  
  1065. $res = "\n$id 0 obj\n<< /Type /Action"; 
  1066. switch ($o['type']) { 
  1067. case 'ilink': 
  1068. if (!isset($this->destinations[(string)$o['info']['label']])) break; 
  1069.  
  1070. // there will be an 'label' setting, this is the name of the destination 
  1071. $res.= "\n/S /GoTo\n/D ".$this->destinations[(string)$o['info']['label']]." 0 R"; 
  1072. break; 
  1073.  
  1074. case 'URI': 
  1075. $res.= "\n/S /URI\n/URI ("; 
  1076. if ($this->encrypted) { 
  1077. $res.= $this->filterText($this->ARC4($o['info']), true, false); 
  1078. } else { 
  1079. $res.= $this->filterText($o['info'], true, false); 
  1080.  
  1081. $res.= ")"; 
  1082. break; 
  1083.  
  1084. $res.= "\n>>\nendobj"; 
  1085. return $res; 
  1086.  
  1087. /** 
  1088. * an annotation object, this will add an annotation to the current page. 
  1089. * initially will support just link annotations 
  1090. */ 
  1091. protected function o_annotation($id, $action, $options = '') { 
  1092. if ($action !== 'new') { 
  1093. $o = & $this->objects[$id]; 
  1094.  
  1095. switch ($action) { 
  1096. case 'new': 
  1097. // add the annotation to the current page 
  1098. $pageId = $this->currentPage; 
  1099. $this->o_page($pageId, 'annot', $id); 
  1100.  
  1101. // and add the action object which is going to be required 
  1102. switch ($options['type']) { 
  1103. case 'link': 
  1104. $this->objects[$id] = array('t' => 'annotation', 'info' => $options); 
  1105. $this->numObj++; 
  1106. $this->o_action($this->numObj, 'new', $options['url']); 
  1107. $this->objects[$id]['info']['actionId'] = $this->numObj; 
  1108. break; 
  1109.  
  1110. case 'ilink': 
  1111. // this is to a named internal link 
  1112. $label = $options['label']; 
  1113. $this->objects[$id] = array('t' => 'annotation', 'info' => $options); 
  1114. $this->numObj++; 
  1115. $this->o_action($this->numObj, 'new', array('type' => 'ilink', 'label' => $label)); 
  1116. $this->objects[$id]['info']['actionId'] = $this->numObj; 
  1117. break; 
  1118. break; 
  1119.  
  1120. case 'out': 
  1121. $res = "\n$id 0 obj\n<< /Type /Annot"; 
  1122. switch ($o['info']['type']) { 
  1123. case 'link': 
  1124. case 'ilink': 
  1125. $res.= "\n/Subtype /Link"; 
  1126. break; 
  1127. $res.= "\n/A ".$o['info']['actionId']." 0 R"; 
  1128. $res.= "\n/Border [0 0 0]"; 
  1129. $res.= "\n/H /I"; 
  1130. $res.= "\n/Rect [ "; 
  1131.  
  1132. foreach ($o['info']['rect'] as $v) { 
  1133. $res.= sprintf("%.4F ", $v); 
  1134.  
  1135. $res.= "]"; 
  1136. $res.= "\n>>\nendobj"; 
  1137. return $res; 
  1138.  
  1139. /** 
  1140. * a page object, it also creates a contents object to hold its contents 
  1141. */ 
  1142. protected function o_page($id, $action, $options = '') { 
  1143. if ($action !== 'new') { 
  1144. $o = & $this->objects[$id]; 
  1145.  
  1146. switch ($action) { 
  1147. case 'new': 
  1148. $this->numPages++; 
  1149. $this->objects[$id] = array('t' => 'page', 'info' => array('parent' => $this->currentNode, 'pageNum' => $this->numPages)); 
  1150.  
  1151. if (is_array($options)) { 
  1152. // then this must be a page insertion, array should contain 'rid', 'pos'=[before|after] 
  1153. $options['id'] = $id; 
  1154. $this->o_pages($this->currentNode, 'page', $options); 
  1155. } else { 
  1156. $this->o_pages($this->currentNode, 'page', $id); 
  1157.  
  1158. $this->currentPage = $id; 
  1159. //make a contents object to go with this page 
  1160. $this->numObj++; 
  1161. $this->o_contents($this->numObj, 'new', $id); 
  1162. $this->currentContents = $this->numObj; 
  1163. $this->objects[$id]['info']['contents'] = array(); 
  1164. $this->objects[$id]['info']['contents'][] = $this->numObj; 
  1165.  
  1166. $match = ($this->numPages%2 ? 'odd' : 'even'); 
  1167. foreach ($this->addLooseObjects as $oId => $target) { 
  1168. if ($target === 'all' || $match === $target) { 
  1169. $this->objects[$id]['info']['contents'][] = $oId; 
  1170. break; 
  1171.  
  1172. case 'content': 
  1173. $o['info']['contents'][] = $options; 
  1174. break; 
  1175.  
  1176. case 'annot': 
  1177. // add an annotation to this page 
  1178. if (!isset($o['info']['annot'])) { 
  1179. $o['info']['annot'] = array(); 
  1180.  
  1181. // $options should contain the id of the annotation dictionary 
  1182. $o['info']['annot'][] = $options; 
  1183. break; 
  1184.  
  1185. case 'out': 
  1186. $res = "\n$id 0 obj\n<< /Type /Page"; 
  1187. $res.= "\n/Parent ".$o['info']['parent']." 0 R"; 
  1188.  
  1189. if (isset($o['info']['annot'])) { 
  1190. $res.= "\n/Annots ["; 
  1191. foreach ($o['info']['annot'] as $aId) { 
  1192. $res.= " $aId 0 R"; 
  1193. $res.= " ]"; 
  1194.  
  1195. $count = count($o['info']['contents']); 
  1196. if ($count == 1) { 
  1197. $res.= "\n/Contents ".$o['info']['contents'][0]." 0 R"; 
  1198. } else if ($count > 1) { 
  1199. $res.= "\n/Contents [\n"; 
  1200.  
  1201. // reverse the page contents so added objects are below normal content 
  1202. //foreach (array_reverse($o['info']['contents']) as $cId) { 
  1203. // Back to normal now that I've got transparency working --Benj 
  1204. foreach ($o['info']['contents'] as $cId) { 
  1205. $res.= "$cId 0 R\n"; 
  1206. $res.= "]"; 
  1207.  
  1208. $res.= "\n>>\nendobj"; 
  1209. return $res; 
  1210.  
  1211. /** 
  1212. * the contents objects hold all of the content which appears on pages 
  1213. */ 
  1214. protected function o_contents($id, $action, $options = '') { 
  1215. if ($action !== 'new') { 
  1216. $o = & $this->objects[$id]; 
  1217.  
  1218. switch ($action) { 
  1219. case 'new': 
  1220. $this->objects[$id] = array('t' => 'contents', 'c' => '', 'info' => array()); 
  1221. if (mb_strlen($options, '8bit') && intval($options)) { 
  1222. // then this contents is the primary for a page 
  1223. $this->objects[$id]['onPage'] = $options; 
  1224. } else if ($options === 'raw') { 
  1225. // then this page contains some other type of system object 
  1226. $this->objects[$id]['raw'] = 1; 
  1227. break; 
  1228.  
  1229. case 'add': 
  1230. // add more options to the decleration 
  1231. foreach ($options as $k => $v) { 
  1232. $o['info'][$k] = $v; 
  1233.  
  1234. case 'out': 
  1235. $tmp = $o['c']; 
  1236. $res = "\n$id 0 obj\n"; 
  1237.  
  1238. if (isset($this->objects[$id]['raw'])) { 
  1239. $res.= $tmp; 
  1240. } else { 
  1241. $res.= "<<"; 
  1242. if ($this->compressionReady && $this->options['compression']) { 
  1243. // then implement ZLIB based compression on this content stream 
  1244. $res.= " /Filter /FlateDecode"; 
  1245. $tmp = gzcompress($tmp, 6); 
  1246.  
  1247. if ($this->encrypted) { 
  1248. $this->encryptInit($id); 
  1249. $tmp = $this->ARC4($tmp); 
  1250.  
  1251. foreach ($o['info'] as $k => $v) { 
  1252. $res.= "\n/$k $v"; 
  1253.  
  1254. $res.= "\n/Length ".mb_strlen($tmp, '8bit') ." >>\nstream\n$tmp\nendstream"; 
  1255.  
  1256. $res.= "\nendobj"; 
  1257. return $res; 
  1258.  
  1259. protected function o_embedjs($id, $action) { 
  1260. if ($action !== 'new') { 
  1261. $o = & $this->objects[$id]; 
  1262.  
  1263. switch ($action) { 
  1264. case 'new': 
  1265. $this->objects[$id] = array('t' => 'embedjs', 'info' => array( 
  1266. 'Names' => '[(EmbeddedJS) '.($id+1).' 0 R]' 
  1267. )); 
  1268. break; 
  1269.  
  1270. case 'out': 
  1271. $res = "\n$id 0 obj\n<< "; 
  1272. foreach ($o['info'] as $k => $v) { 
  1273. $res.= "\n/$k $v"; 
  1274. $res.= "\n>>\nendobj"; 
  1275. return $res; 
  1276.  
  1277. protected function o_javascript($id, $action, $code = '') { 
  1278. if ($action !== 'new') { 
  1279. $o = & $this->objects[$id]; 
  1280.  
  1281. switch ($action) { 
  1282. case 'new': 
  1283. $this->objects[$id] = array('t' => 'javascript', 'info' => array( 
  1284. 'S' => '/JavaScript',  
  1285. 'JS' => '('.$this->filterText($code).')',  
  1286. )); 
  1287. break; 
  1288.  
  1289. case 'out': 
  1290. $res = "\n$id 0 obj\n<< "; 
  1291. foreach ($o['info'] as $k => $v) { 
  1292. $res.= "\n/$k $v"; 
  1293. $res.= "\n>>\nendobj"; 
  1294. return $res; 
  1295.  
  1296. /** 
  1297. * an image object, will be an XObject in the document, includes description and data 
  1298. */ 
  1299. protected function o_image($id, $action, $options = '') { 
  1300. if ($action !== 'new') { 
  1301. $o = & $this->objects[$id]; 
  1302.  
  1303. switch ($action) { 
  1304. case 'new': 
  1305. // make the new object 
  1306. $this->objects[$id] = array('t' => 'image', 'data' => &$options['data'], 'info' => array()); 
  1307.  
  1308. $info =& $this->objects[$id]['info']; 
  1309.  
  1310. $info['Type'] = '/XObject'; 
  1311. $info['Subtype'] = '/Image'; 
  1312. $info['Width'] = $options['iw']; 
  1313. $info['Height'] = $options['ih']; 
  1314.  
  1315. if (isset($options['masked']) && $options['masked']) { 
  1316. $info['SMask'] = ($this->numObj-1).' 0 R'; 
  1317.  
  1318. if (!isset($options['type']) || $options['type'] === 'jpg') { 
  1319. if (!isset($options['channels'])) { 
  1320. $options['channels'] = 3; 
  1321.  
  1322. switch ($options['channels']) { 
  1323. case 1: $info['ColorSpace'] = '/DeviceGray'; break; 
  1324. case 4: $info['ColorSpace'] = '/DeviceCMYK'; break; 
  1325. default: $info['ColorSpace'] = '/DeviceRGB'; break; 
  1326.  
  1327. if ($info['ColorSpace'] === '/DeviceCMYK') { 
  1328. $info['Decode'] = '[1 0 1 0 1 0 1 0]'; 
  1329.  
  1330. $info['Filter'] = '/DCTDecode'; 
  1331. $info['BitsPerComponent'] = 8; 
  1332. }  
  1333.  
  1334. else if ($options['type'] === 'png') { 
  1335. $info['Filter'] = '/FlateDecode'; 
  1336. $info['DecodeParms'] = '<< /Predictor 15 /Colors '.$options['ncolor'].' /Columns '.$options['iw'].' /BitsPerComponent '.$options['bitsPerComponent'].'>>'; 
  1337.  
  1338. if ($options['isMask']) { 
  1339. $info['ColorSpace'] = '/DeviceGray'; 
  1340. else { 
  1341. if (mb_strlen($options['pdata'], '8bit')) { 
  1342. $tmp = ' [ /Indexed /DeviceRGB '.(mb_strlen($options['pdata'], '8bit') /3-1) .' '; 
  1343. $this->numObj++; 
  1344. $this->o_contents($this->numObj, 'new'); 
  1345. $this->objects[$this->numObj]['c'] = $options['pdata']; 
  1346. $tmp.= $this->numObj.' 0 R'; 
  1347. $tmp.= ' ]'; 
  1348. $info['ColorSpace'] = $tmp; 
  1349.  
  1350. if (isset($options['transparency'])) { 
  1351. $transparency = $options['transparency']; 
  1352. switch ($transparency['type']) { 
  1353. case 'indexed': 
  1354. $tmp = ' [ '.$transparency['data'].' '.$transparency['data'].'] '; 
  1355. $info['Mask'] = $tmp; 
  1356. break; 
  1357.  
  1358. case 'color-key': 
  1359. $tmp = ' [ '. 
  1360. $transparency['r'] . ' ' . $transparency['r'] . 
  1361. $transparency['g'] . ' ' . $transparency['g'] . 
  1362. $transparency['b'] . ' ' . $transparency['b'] . 
  1363. ' ] '; 
  1364. $info['Mask'] = $tmp; 
  1365. break; 
  1366. } else { 
  1367. if (isset($options['transparency'])) { 
  1368. $transparency = $options['transparency']; 
  1369.  
  1370. switch ($transparency['type']) { 
  1371. case 'indexed': 
  1372. $tmp = ' [ '.$transparency['data'].' '.$transparency['data'].'] '; 
  1373. $info['Mask'] = $tmp; 
  1374. break; 
  1375.  
  1376. case 'color-key': 
  1377. $tmp = ' [ '. 
  1378. $transparency['r'] . ' ' . $transparency['r'] . ' ' . 
  1379. $transparency['g'] . ' ' . $transparency['g'] . ' ' . 
  1380. $transparency['b'] . ' ' . $transparency['b'] . 
  1381. ' ] '; 
  1382. $info['Mask'] = $tmp; 
  1383. break; 
  1384. $info['ColorSpace'] = '/'.$options['color']; 
  1385.  
  1386. $info['BitsPerComponent'] = $options['bitsPerComponent']; 
  1387.  
  1388. // assign it a place in the named resource dictionary as an external object, according to 
  1389. // the label passed in with it. 
  1390. $this->o_pages($this->currentNode, 'xObject', array('label' => $options['label'], 'objNum' => $id)); 
  1391.  
  1392. // also make sure that we have the right procset object for it. 
  1393. $this->o_procset($this->procsetObjectId, 'add', 'ImageC'); 
  1394. break; 
  1395.  
  1396. case 'out': 
  1397. $tmp = &$o['data']; 
  1398. $res = "\n$id 0 obj\n<<"; 
  1399.  
  1400. foreach ($o['info'] as $k => $v) { 
  1401. $res.= "\n/$k $v"; 
  1402.  
  1403. if ($this->encrypted) { 
  1404. $this->encryptInit($id); 
  1405. $tmp = $this->ARC4($tmp); 
  1406.  
  1407. $res.= "\n/Length ".mb_strlen($tmp, '8bit') .">>\nstream\n$tmp\nendstream\nendobj"; 
  1408.  
  1409. return $res; 
  1410.  
  1411. /** 
  1412. * graphics state object 
  1413. */ 
  1414. protected function o_extGState($id, $action, $options = "") { 
  1415. static $valid_params = array("LW", "LC", "LC", "LJ", "ML",  
  1416. "D", "RI", "OP", "op", "OPM",  
  1417. "Font", "BG", "BG2", "UCR",  
  1418. "TR", "TR2", "HT", "FL",  
  1419. "SM", "SA", "BM", "SMask",  
  1420. "CA", "ca", "AIS", "TK"); 
  1421.  
  1422. if ($action !== "new") { 
  1423. $o = & $this->objects[$id]; 
  1424.  
  1425. switch ($action) { 
  1426. case "new": 
  1427. $this->objects[$id] = array('t' => 'extGState', 'info' => $options); 
  1428.  
  1429. // Tell the pages about the new resource 
  1430. $this->numStates++; 
  1431. $this->o_pages($this->currentNode, 'extGState', array("objNum" => $id, "stateNum" => $this->numStates)); 
  1432. break; 
  1433.  
  1434. case "out": 
  1435. $res = "\n$id 0 obj\n<< /Type /ExtGState\n"; 
  1436.  
  1437. foreach ($o["info"] as $k => $v) { 
  1438. if ( !in_array($k, $valid_params)) 
  1439. continue; 
  1440. $res.= "/$k $v\n"; 
  1441.  
  1442. $res.= ">>\nendobj"; 
  1443. return $res; 
  1444.  
  1445. /** 
  1446. * encryption object. 
  1447. */ 
  1448. protected function o_encryption($id, $action, $options = '') { 
  1449. if ($action !== 'new') { 
  1450. $o = & $this->objects[$id]; 
  1451.  
  1452. switch ($action) { 
  1453. case 'new': 
  1454. // make the new object 
  1455. $this->objects[$id] = array('t' => 'encryption', 'info' => $options); 
  1456. $this->arc4_objnum = $id; 
  1457.  
  1458. // figure out the additional paramaters required 
  1459. $pad = chr(0x28) .chr(0xBF) .chr(0x4E) .chr(0x5E) .chr(0x4E) .chr(0x75) .chr(0x8A) .chr(0x41) 
  1460. .chr(0x64) .chr(0x00) .chr(0x4E) .chr(0x56) .chr(0xFF) .chr(0xFA) .chr(0x01) .chr(0x08) 
  1461. .chr(0x2E) .chr(0x2E) .chr(0x00) .chr(0xB6) .chr(0xD0) .chr(0x68) .chr(0x3E) .chr(0x80) 
  1462. .chr(0x2F) .chr(0x0C) .chr(0xA9) .chr(0xFE) .chr(0x64) .chr(0x53) .chr(0x69) .chr(0x7A); 
  1463.  
  1464. $len = mb_strlen($options['owner'], '8bit'); 
  1465.  
  1466. if ($len > 32) { 
  1467. $owner = substr($options['owner'], 0, 32); 
  1468. } else if ($len < 32) { 
  1469. $owner = $options['owner'].substr($pad, 0, 32-$len); 
  1470. } else { 
  1471. $owner = $options['owner']; 
  1472.  
  1473. $len = mb_strlen($options['user'], '8bit'); 
  1474. if ($len > 32) { 
  1475. $user = substr($options['user'], 0, 32); 
  1476. } else if ($len < 32) { 
  1477. $user = $options['user'].substr($pad, 0, 32-$len); 
  1478. } else { 
  1479. $user = $options['user']; 
  1480.  
  1481. $tmp = $this->md5_16($owner); 
  1482. $okey = substr($tmp, 0, 5); 
  1483. $this->ARC4_init($okey); 
  1484. $ovalue = $this->ARC4($user); 
  1485. $this->objects[$id]['info']['O'] = $ovalue; 
  1486.  
  1487. // now make the u value, phew. 
  1488. $tmp = $this->md5_16($user.$ovalue.chr($options['p']) .chr(255) .chr(255) .chr(255) .$this->fileIdentifier); 
  1489.  
  1490. $ukey = substr($tmp, 0, 5); 
  1491. $this->ARC4_init($ukey); 
  1492. $this->encryptionKey = $ukey; 
  1493. $this->encrypted = true; 
  1494. $uvalue = $this->ARC4($pad); 
  1495. $this->objects[$id]['info']['U'] = $uvalue; 
  1496. $this->encryptionKey = $ukey; 
  1497. // initialize the arc4 array 
  1498. break; 
  1499.  
  1500. case 'out': 
  1501. $res = "\n$id 0 obj\n<<"; 
  1502. $res.= "\n/Filter /Standard"; 
  1503. $res.= "\n/V 1"; 
  1504. $res.= "\n/R 2"; 
  1505. $res.= "\n/O (".$this->filterText($o['info']['O'], true, false) .')'; 
  1506. $res.= "\n/U (".$this->filterText($o['info']['U'], true, false) .')'; 
  1507. // and the p-value needs to be converted to account for the twos-complement approach 
  1508. $o['info']['p'] = (($o['info']['p']^255) +1) *-1; 
  1509. $res.= "\n/P ".($o['info']['p']); 
  1510. $res.= "\n>>\nendobj"; 
  1511. return $res; 
  1512.  
  1513. /** 
  1514. * ARC4 functions 
  1515. * A series of function to implement ARC4 encoding in PHP 
  1516. */ 
  1517.  
  1518. /** 
  1519. * calculate the 16 byte version of the 128 bit md5 digest of the string 
  1520. */ 
  1521. function md5_16($string) { 
  1522. $tmp = md5($string); 
  1523. $out = ''; 
  1524. for ($i = 0; $i <= 30; $i = $i+2) { 
  1525. $out.= chr(hexdec(substr($tmp, $i, 2))); 
  1526. return $out; 
  1527.  
  1528. /** 
  1529. * initialize the encryption for processing a particular object 
  1530. */ 
  1531. function encryptInit($id) { 
  1532. $tmp = $this->encryptionKey; 
  1533. $hex = dechex($id); 
  1534. if (mb_strlen($hex, '8bit') < 6) { 
  1535. $hex = substr('000000', 0, 6-mb_strlen($hex, '8bit')) .$hex; 
  1536. $tmp.= chr(hexdec(substr($hex, 4, 2))) .chr(hexdec(substr($hex, 2, 2))) .chr(hexdec(substr($hex, 0, 2))) .chr(0) .chr(0); 
  1537. $key = $this->md5_16($tmp); 
  1538. $this->ARC4_init(substr($key, 0, 10)); 
  1539.  
  1540. /** 
  1541. * initialize the ARC4 encryption 
  1542. */ 
  1543. function ARC4_init($key = '') { 
  1544. $this->arc4 = ''; 
  1545.  
  1546. // setup the control array 
  1547. if (mb_strlen($key, '8bit') == 0) { 
  1548. return; 
  1549.  
  1550. $k = ''; 
  1551. while (mb_strlen($k, '8bit') < 256) { 
  1552. $k.= $key; 
  1553.  
  1554. $k = substr($k, 0, 256); 
  1555. for ($i = 0; $i < 256; $i++) { 
  1556. $this->arc4.= chr($i); 
  1557.  
  1558. $j = 0; 
  1559.  
  1560. for ($i = 0; $i < 256; $i++) { 
  1561. $t = $this->arc4[$i]; 
  1562. $j = ($j + ord($t) + ord($k[$i])) %256; 
  1563. $this->arc4[$i] = $this->arc4[$j]; 
  1564. $this->arc4[$j] = $t; 
  1565.  
  1566. /** 
  1567. * ARC4 encrypt a text string 
  1568. */ 
  1569. function ARC4($text) { 
  1570. $len = mb_strlen($text, '8bit'); 
  1571. $a = 0; 
  1572. $b = 0; 
  1573. $c = $this->arc4; 
  1574. $out = ''; 
  1575. for ($i = 0; $i < $len; $i++) { 
  1576. $a = ($a+1) %256; 
  1577. $t = $c[$a]; 
  1578. $b = ($b+ord($t)) %256; 
  1579. $c[$a] = $c[$b]; 
  1580. $c[$b] = $t; 
  1581. $k = ord($c[(ord($c[$a]) + ord($c[$b])) %256]); 
  1582. $out.= chr(ord($text[$i]) ^ $k); 
  1583. return $out; 
  1584.  
  1585. /** 
  1586. * functions which can be called to adjust or add to the document 
  1587. */ 
  1588.  
  1589. /** 
  1590. * add a link in the document to an external URL 
  1591. */ 
  1592. function addLink($url, $x0, $y0, $x1, $y1) { 
  1593. $this->numObj++; 
  1594. $info = array('type' => 'link', 'url' => $url, 'rect' => array($x0, $y0, $x1, $y1)); 
  1595. $this->o_annotation($this->numObj, 'new', $info); 
  1596.  
  1597. /** 
  1598. * add a link in the document to an internal destination (ie. within the document) 
  1599. */ 
  1600. function addInternalLink($label, $x0, $y0, $x1, $y1) { 
  1601. $this->numObj++; 
  1602. $info = array('type' => 'ilink', 'label' => $label, 'rect' => array($x0, $y0, $x1, $y1)); 
  1603. $this->o_annotation($this->numObj, 'new', $info); 
  1604.  
  1605. /** 
  1606. * set the encryption of the document 
  1607. * can be used to turn it on and/or set the passwords which it will have. 
  1608. * also the functions that the user will have are set here, such as print, modify, add 
  1609. */ 
  1610. function setEncryption($userPass = '', $ownerPass = '', $pc = array()) { 
  1611. $p = bindec("11000000"); 
  1612.  
  1613. $options = array('print' => 4, 'modify' => 8, 'copy' => 16, 'add' => 32); 
  1614.  
  1615. foreach ($pc as $k => $v) { 
  1616. if ($v && isset($options[$k])) { 
  1617. $p+= $options[$k]; 
  1618. } else if (isset($options[$v])) { 
  1619. $p+= $options[$v]; 
  1620.  
  1621. // implement encryption on the document 
  1622. if ($this->arc4_objnum == 0) { 
  1623. // then the block does not exist already, add it. 
  1624. $this->numObj++; 
  1625. if (mb_strlen($ownerPass) == 0) { 
  1626. $ownerPass = $userPass; 
  1627.  
  1628. $this->o_encryption($this->numObj, 'new', array('user' => $userPass, 'owner' => $ownerPass, 'p' => $p)); 
  1629.  
  1630. /** 
  1631. * should be used for internal checks, not implemented as yet 
  1632. */ 
  1633. function checkAllHere() { 
  1634.  
  1635. /** 
  1636. * return the pdf stream as a string returned from the function 
  1637. */ 
  1638. function output($debug = false) { 
  1639. if ($debug) { 
  1640. // turn compression off 
  1641. $this->options['compression'] = false; 
  1642.  
  1643. if ($this->javascript) { 
  1644. $this->numObj++; 
  1645.  
  1646. $js_id = $this->numObj; 
  1647. $this->o_embedjs($js_id, 'new'); 
  1648. $this->o_javascript(++$this->numObj, 'new', $this->javascript); 
  1649.  
  1650. $id = $this->catalogId; 
  1651.  
  1652. $this->o_catalog($id, 'javascript', $js_id); 
  1653.  
  1654. if ($this->arc4_objnum) { 
  1655. $this->ARC4_init($this->encryptionKey); 
  1656.  
  1657. $this->checkAllHere(); 
  1658.  
  1659. $xref = array(); 
  1660. $content = '%PDF-1.3'; 
  1661. $pos = mb_strlen($content, '8bit'); 
  1662.  
  1663. foreach ($this->objects as $k => $v) { 
  1664. $tmp = 'o_'.$v['t']; 
  1665. $cont = $this->$tmp($k, 'out'); 
  1666. $content.= $cont; 
  1667. $xref[] = $pos; 
  1668. $pos+= mb_strlen($cont, '8bit'); 
  1669.  
  1670. $content.= "\nxref\n0 ".(count($xref) +1) ."\n0000000000 65535 f \n"; 
  1671.  
  1672. foreach ($xref as $p) { 
  1673. $content.= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n"; 
  1674.  
  1675. $content.= "trailer\n<<\n/Size ".(count($xref) +1) ."\n/Root 1 0 R\n/Info $this->infoObject 0 R\n"; 
  1676.  
  1677. // if encryption has been applied to this document then add the marker for this dictionary 
  1678. if ($this->arc4_objnum > 0) { 
  1679. $content.= "/Encrypt $this->arc4_objnum 0 R\n"; 
  1680.  
  1681. if (mb_strlen($this->fileIdentifier, '8bit')) { 
  1682. $content.= "/ID[<$this->fileIdentifier><$this->fileIdentifier>]\n"; 
  1683.  
  1684. // account for \n added at start of xref table 
  1685. $pos++; 
  1686.  
  1687. $content.= ">>\nstartxref\n$pos\n%%EOF\n"; 
  1688.  
  1689. return $content; 
  1690.  
  1691. /** 
  1692. * intialize a new document 
  1693. * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum 
  1694. * this function is called automatically by the constructor function 
  1695. */ 
  1696. private function newDocument($pageSize = array(0, 0, 612, 792)) { 
  1697. $this->numObj = 0; 
  1698. $this->objects = array(); 
  1699.  
  1700. $this->numObj++; 
  1701. $this->o_catalog($this->numObj, 'new'); 
  1702.  
  1703. $this->numObj++; 
  1704. $this->o_outlines($this->numObj, 'new'); 
  1705.  
  1706. $this->numObj++; 
  1707. $this->o_pages($this->numObj, 'new'); 
  1708.  
  1709. $this->o_pages($this->numObj, 'mediaBox', $pageSize); 
  1710. $this->currentNode = 3; 
  1711.  
  1712. $this->numObj++; 
  1713. $this->o_procset($this->numObj, 'new'); 
  1714.  
  1715. $this->numObj++; 
  1716. $this->o_info($this->numObj, 'new'); 
  1717.  
  1718. $this->numObj++; 
  1719. $this->o_page($this->numObj, 'new'); 
  1720.  
  1721. // need to store the first page id as there is no way to get it to the user during 
  1722. // startup 
  1723. $this->firstPageId = $this->currentContents; 
  1724.  
  1725. /** 
  1726. * open the font file and return a php structure containing it. 
  1727. * first check if this one has been done before and saved in a form more suited to php 
  1728. * note that if a php serialized version does not exist it will try and make one, but will 
  1729. * require write access to the directory to do it... it is MUCH faster to have these serialized 
  1730. * files. 
  1731. */ 
  1732. private function openFont($font) { 
  1733. // assume that $font contains the path and file but not the extension 
  1734. $pos = strrpos($font, '/'); 
  1735.  
  1736. if ($pos === false) { 
  1737. $dir = './'; 
  1738. $name = $font; 
  1739. } else { 
  1740. $dir = substr($font, 0, $pos+1); 
  1741. $name = substr($font, $pos+1); 
  1742.  
  1743. $fontcache = $this->fontcache; 
  1744. if ($fontcache == '') { 
  1745. $fontcache = $dir; 
  1746.  
  1747. //$name filename without folder and extension of font metrics 
  1748. //$dir folder of font metrics 
  1749. //$fontcache folder of runtime created php serialized version of font metrics. 
  1750. // If this is not given, the same folder as the font metrics will be used. 
  1751. // Storing and reusing serialized versions improves speed much 
  1752.  
  1753. $this->addMessage("openFont: $font - $name"); 
  1754.  
  1755. if ( !$this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts) ) { 
  1756. $metrics_name = "$name.afm"; 
  1757. else { 
  1758. $metrics_name = "$name.ufm"; 
  1759.  
  1760. $cache_name = "$metrics_name.php"; 
  1761. $this->addMessage("metrics: $metrics_name, cache: $cache_name"); 
  1762.  
  1763. if (file_exists($fontcache . $cache_name)) { 
  1764. $this->addMessage("openFont: php file exists $fontcache$cache_name"); 
  1765. $this->fonts[$font] = require($fontcache . $cache_name); 
  1766.  
  1767. if (!isset($this->fonts[$font]['_version_']) || $this->fonts[$font]['_version_'] != $this->fontcacheVersion) { 
  1768. // if the font file is old, then clear it out and prepare for re-creation 
  1769. $this->addMessage('openFont: clear out, make way for new version.'); 
  1770. $this->fonts[$font] = null; 
  1771. unset($this->fonts[$font]); 
  1772. else { 
  1773. $old_cache_name = "php_$metrics_name"; 
  1774. if (file_exists($fontcache . $old_cache_name)) { 
  1775. $this->addMessage("openFont: php file doesn't exist $fontcache$cache_name, creating it from the old format"); 
  1776. $old_cache = file_get_contents($fontcache . $old_cache_name); 
  1777. file_put_contents($fontcache . $cache_name, '<?php return ' . $old_cache . ';'); 
  1778. return $this->openFont($font); 
  1779.  
  1780. if (!isset($this->fonts[$font]) && file_exists($dir . $metrics_name)) { 
  1781. // then rebuild the php_<font>.afm file from the <font>.afm file 
  1782. $this->addMessage("openFont: build php file from $dir$metrics_name"); 
  1783. $data = array(); 
  1784.  
  1785. // 20 => 'space' 
  1786. $data['codeToName'] = array();  
  1787.  
  1788. // Since we're not going to enable Unicode for the core fonts we need to use a font-based 
  1789. // setting for Unicode support rather than a global setting. 
  1790. $data['isUnicode'] = (strtolower(substr($metrics_name, -3)) !== 'afm'); 
  1791.  
  1792. $cidtogid = ''; 
  1793. if ($data['isUnicode']) { 
  1794. $cidtogid = str_pad('', 256*256*2, "\x00"); 
  1795.  
  1796. $file = file($dir . $metrics_name); 
  1797.  
  1798. foreach ($file as $rowA) { 
  1799. $row = trim($rowA); 
  1800. $pos = strpos($row, ' '); 
  1801.  
  1802. if ($pos) { 
  1803. // then there must be some keyword 
  1804. $key = substr($row, 0, $pos); 
  1805. switch ($key) { 
  1806. case 'FontName': 
  1807. case 'FullName': 
  1808. case 'FamilyName': 
  1809. case 'PostScriptName': 
  1810. case 'Weight': 
  1811. case 'ItalicAngle': 
  1812. case 'IsFixedPitch': 
  1813. case 'CharacterSet': 
  1814. case 'UnderlinePosition': 
  1815. case 'UnderlineThickness': 
  1816. case 'Version': 
  1817. case 'EncodingScheme': 
  1818. case 'CapHeight': 
  1819. case 'XHeight': 
  1820. case 'Ascender': 
  1821. case 'Descender': 
  1822. case 'StdHW': 
  1823. case 'StdVW': 
  1824. case 'StartCharMetrics': 
  1825. case 'FontHeightOffset': // OAR - Added so we can offset the height calculation of a Windows font. Otherwise it's too big. 
  1826. $data[$key] = trim(substr($row, $pos)); 
  1827. break; 
  1828.  
  1829. case 'FontBBox': 
  1830. $data[$key] = explode(' ', trim(substr($row, $pos))); 
  1831. break; 
  1832.  
  1833. //C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ; 
  1834. case 'C': // Found in AFM files 
  1835. $bits = explode(';', trim($row)); 
  1836. $dtmp = array(); 
  1837.  
  1838. foreach ($bits as $bit) { 
  1839. $bits2 = explode(' ', trim($bit)); 
  1840. if (mb_strlen($bits2[0], '8bit') == 0) continue; 
  1841.  
  1842. if (count($bits2) > 2) { 
  1843. $dtmp[$bits2[0]] = array(); 
  1844. for ($i = 1; $i < count($bits2); $i++) { 
  1845. $dtmp[$bits2[0]][] = $bits2[$i]; 
  1846. } else if (count($bits2) == 2) { 
  1847. $dtmp[$bits2[0]] = $bits2[1]; 
  1848.  
  1849. $c = (int)$dtmp['C']; 
  1850. $n = $dtmp['N']; 
  1851. $width = floatval($dtmp['WX']); 
  1852.  
  1853. if ($c >= 0) { 
  1854. if ($c != hexdec($n)) { 
  1855. $data['codeToName'][$c] = $n; 
  1856. $data['C'][$c] = $width; 
  1857. } else { 
  1858. $data['C'][$n] = $width; 
  1859.  
  1860. if (!isset($data['MissingWidth']) && $c == -1 && $n === '.notdef') { 
  1861. $data['MissingWidth'] = $width; 
  1862.  
  1863. break; 
  1864.  
  1865. // U 827 ; WX 0 ; N squaresubnosp ; G 675 ; 
  1866. case 'U': // Found in UFM files 
  1867. if (!$data['isUnicode']) break; 
  1868.  
  1869. $bits = explode(';', trim($row)); 
  1870. $dtmp = array(); 
  1871.  
  1872. foreach ($bits as $bit) { 
  1873. $bits2 = explode(' ', trim($bit)); 
  1874. if (mb_strlen($bits2[0], '8bit') === 0) continue; 
  1875.  
  1876. if (count($bits2) > 2) { 
  1877. $dtmp[$bits2[0]] = array(); 
  1878. for ($i = 1; $i < count($bits2); $i++) { 
  1879. $dtmp[$bits2[0]][] = $bits2[$i]; 
  1880. } else if (count($bits2) == 2) { 
  1881. $dtmp[$bits2[0]] = $bits2[1]; 
  1882.  
  1883. $c = (int)$dtmp['U']; 
  1884. $n = $dtmp['N']; 
  1885. $glyph = $dtmp['G']; 
  1886. $width = floatval($dtmp['WX']); 
  1887.  
  1888. if ($c >= 0) { 
  1889. // Set values in CID to GID map 
  1890. if ($c >= 0 && $c < 0xFFFF && $glyph) { 
  1891. $cidtogid[$c*2] = chr($glyph >> 8); 
  1892. $cidtogid[$c*2 + 1] = chr($glyph & 0xFF); 
  1893.  
  1894. if ($c != hexdec($n)) { 
  1895. $data['codeToName'][$c] = $n; 
  1896. $data['C'][$c] = $width; 
  1897. } else { 
  1898. $data['C'][$n] = $width; 
  1899.  
  1900. if (!isset($data['MissingWidth']) && $c == -1 && $n === '.notdef') { 
  1901. $data['MissingWidth'] = $width; 
  1902.  
  1903. break; 
  1904.  
  1905. case 'KPX': 
  1906. break; // don't include them as they are not used yet 
  1907. //KPX Adieresis yacute -40 
  1908. $bits = explode(' ', trim($row)); 
  1909. $data['KPX'][$bits[1]][$bits[2]] = $bits[3]; 
  1910. break; 
  1911.  
  1912. if ($this->compressionReady && $this->options['compression']) { 
  1913. // then implement ZLIB based compression on CIDtoGID string 
  1914. $data['CIDtoGID_Compressed'] = true; 
  1915. $cidtogid = gzcompress($cidtogid, 6); 
  1916. $data['CIDtoGID'] = base64_encode($cidtogid); 
  1917. $data['_version_'] = $this->fontcacheVersion; 
  1918. $this->fonts[$font] = $data; 
  1919.  
  1920. //Because of potential trouble with php safe mode, expect that the folder already exists. 
  1921. //If not existing, this will hit performance because of missing cached results. 
  1922. if ( is_dir(substr($fontcache, 0, -1)) && is_writable(substr($fontcache, 0, -1)) ) { 
  1923. file_put_contents($fontcache . $cache_name, '<?php return ' . var_export($data, true) . ';'); 
  1924. $data = null; 
  1925.  
  1926. if (!isset($this->fonts[$font])) { 
  1927. $this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?"); 
  1928.  
  1929. //pre_r($this->messages); 
  1930.  
  1931. /** 
  1932. * if the font is not loaded then load it and make the required object 
  1933. * else just make it the current font 
  1934. * the encoding array can contain 'encoding'=> 'none', 'WinAnsiEncoding', 'MacRomanEncoding' or 'MacExpertEncoding' 
  1935. * note that encoding='none' will need to be used for symbolic fonts 
  1936. * and 'differences' => an array of mappings between numbers 0->255 and character names. 
  1937. */ 
  1938. function selectFont($fontName, $encoding = '', $set = true) { 
  1939. $ext = substr($fontName, -4); 
  1940. if ($ext === '.afm' || $ext === '.ufm') { 
  1941. $fontName = substr($fontName, 0, mb_strlen($fontName)-4); 
  1942.  
  1943. if (!isset($this->fonts[$fontName])) { 
  1944. $this->addMessage("selectFont: selecting - $fontName - $encoding, $set"); 
  1945.  
  1946. // load the file 
  1947. $this->openFont($fontName); 
  1948.  
  1949. if (isset($this->fonts[$fontName])) { 
  1950. $this->numObj++; 
  1951. $this->numFonts++; 
  1952.  
  1953. $font = &$this->fonts[$fontName]; 
  1954.  
  1955. //$this->numFonts = md5($fontName); 
  1956. $pos = strrpos($fontName, '/'); 
  1957. // $dir = substr($fontName, 0, $pos+1); 
  1958. $name = substr($fontName, $pos+1); 
  1959. $options = array('name' => $name, 'fontFileName' => $fontName); 
  1960.  
  1961. if (is_array($encoding)) { 
  1962. // then encoding and differences might be set 
  1963. if (isset($encoding['encoding'])) { 
  1964. $options['encoding'] = $encoding['encoding']; 
  1965.  
  1966. if (isset($encoding['differences'])) { 
  1967. $options['differences'] = $encoding['differences']; 
  1968. } else if (mb_strlen($encoding, '8bit')) { 
  1969. // then perhaps only the encoding has been set 
  1970. $options['encoding'] = $encoding; 
  1971.  
  1972. $fontObj = $this->numObj; 
  1973. $this->o_font($this->numObj, 'new', $options); 
  1974. $font['fontNum'] = $this->numFonts; 
  1975.  
  1976. // if this is a '.afm' font, and there is a '.pfa' file to go with it ( as there 
  1977. // should be for all non-basic fonts), then load it into an object and put the 
  1978. // references into the font object 
  1979. $basefile = $fontName; 
  1980.  
  1981. $fbtype = ''; 
  1982. if (file_exists("$basefile.pfb")) { 
  1983. $fbtype = 'pfb'; 
  1984. }  
  1985. else if (file_exists("$basefile.ttf")) { 
  1986. $fbtype = 'ttf'; 
  1987.  
  1988. $fbfile = "$basefile.$fbtype"; 
  1989.  
  1990. // $pfbfile = substr($fontName, 0, strlen($fontName)-4).'.pfb'; 
  1991. // $ttffile = substr($fontName, 0, strlen($fontName)-4).'.ttf'; 
  1992. $this->addMessage('selectFont: checking for - '.$fbfile); 
  1993.  
  1994. // OAR - I don't understand this old check 
  1995. // if (substr($fontName, -4) === '.afm' && strlen($fbtype)) { 
  1996. if ($fbtype) { 
  1997. $adobeFontName = isset($font['PostScriptName']) ? $font['PostScriptName'] : $font['FontName']; 
  1998. // $fontObj = $this->numObj; 
  1999. $this->addMessage("selectFont: adding font file - $fbfile - $adobeFontName"); 
  2000.  
  2001. // find the array of font widths, and put that into an object. 
  2002. $firstChar = -1; 
  2003. $lastChar = 0; 
  2004. $widths = array(); 
  2005. $cid_widths = array(); 
  2006.  
  2007. foreach ($font['C'] as $num => $d) { 
  2008. if (intval($num) > 0 || $num == '0') { 
  2009. if (!$font['isUnicode']) { 
  2010. // With Unicode, widths array isn't used 
  2011. if ($lastChar>0 && $num>$lastChar+1) { 
  2012. for ($i = $lastChar+1; $i<$num; $i++) { 
  2013. $widths[] = 0; 
  2014.  
  2015. $widths[] = $d; 
  2016.  
  2017. if ($font['isUnicode']) { 
  2018. $cid_widths[$num] = $d; 
  2019.  
  2020. if ($firstChar == -1) { 
  2021. $firstChar = $num; 
  2022.  
  2023. $lastChar = $num; 
  2024.  
  2025. // also need to adjust the widths for the differences array 
  2026. if (isset($options['differences'])) { 
  2027. foreach ($options['differences'] as $charNum => $charName) { 
  2028. if ($charNum > $lastChar) { 
  2029. if (!$font['isUnicode']) { 
  2030. // With Unicode, widths array isn't used 
  2031. for ($i = $lastChar + 1; $i <= $charNum; $i++) { 
  2032. $widths[] = 0; 
  2033.  
  2034. $lastChar = $charNum; 
  2035.  
  2036. if (isset($font['C'][$charName])) { 
  2037. $widths[$charNum-$firstChar] = $font['C'][$charName]; 
  2038. if ($font['isUnicode']) { 
  2039. $cid_widths[$charName] = $font['C'][$charName]; 
  2040.  
  2041. if ($font['isUnicode']) { 
  2042. $font['CIDWidths'] = $cid_widths; 
  2043.  
  2044. $this->addMessage('selectFont: FirstChar = '.$firstChar); 
  2045. $this->addMessage('selectFont: LastChar = '.$lastChar); 
  2046.  
  2047. $widthid = -1; 
  2048.  
  2049. if (!$font['isUnicode']) { 
  2050. // With Unicode, widths array isn't used 
  2051.  
  2052. $this->numObj++; 
  2053. $this->o_contents($this->numObj, 'new', 'raw'); 
  2054. $this->objects[$this->numObj]['c'].= '['.implode(' ', $widths).']'; 
  2055. $widthid = $this->numObj; 
  2056.  
  2057. $missing_width = 500; 
  2058. $stemV = 70; 
  2059.  
  2060. if (isset($font['MissingWidth'])) { 
  2061. $missing_width = $font['MissingWidth']; 
  2062. if (isset($font['StdVW'])) { 
  2063. $stemV = $font['StdVW']; 
  2064. } else if (isset($font['Weight']) && preg_match('!(bold|black)!i', $font['Weight'])) { 
  2065. $stemV = 120; 
  2066.  
  2067. // load the pfb file, and put that into an object too. 
  2068. // note that pdf supports only binary format type 1 font files, though there is a 
  2069. // simple utility to convert them from pfa to pfb. 
  2070. // FIXME: should we move font subset creation to CPDF::output? See notes in issue #750. 
  2071. if (!$this->isUnicode || $fbtype !== 'ttf' || empty($this->stringSubsets)) { 
  2072. $data = file_get_contents($fbfile); 
  2073. else { 
  2074. $this->stringSubsets[$fontName][] = 32; // Force space if not in yet 
  2075.  
  2076. $subset = $this->stringSubsets[$fontName]; 
  2077. sort($subset); 
  2078.  
  2079. // Load font 
  2080. $font_obj = Font::load($fbfile); 
  2081. $font_obj->parse(); 
  2082.  
  2083. // Define subset 
  2084. $font_obj->setSubset($subset); 
  2085. $font_obj->reduce(); 
  2086.  
  2087. // Write new font 
  2088. $tmp_name = "$fbfile.tmp.".uniqid(); 
  2089. $font_obj->open($tmp_name, Font_Binary_Stream::modeWrite); 
  2090. $font_obj->encode(array("OS/2")); 
  2091. $font_obj->close(); 
  2092.  
  2093. // Parse the new font to get cid2gid and widths 
  2094. $font_obj = Font::load($tmp_name); 
  2095.  
  2096. // Find Unicode char map table 
  2097. $subtable = null; 
  2098. foreach ($font_obj->getData("cmap", "subtables") as $_subtable) { 
  2099. if ($_subtable["platformID"] == 0 || $_subtable["platformID"] == 3 && $_subtable["platformSpecificID"] == 1) { 
  2100. $subtable = $_subtable; 
  2101. break; 
  2102.  
  2103. if ($subtable) { 
  2104. $glyphIndexArray = $subtable["glyphIndexArray"]; 
  2105. $hmtx = $font_obj->getData("hmtx"); 
  2106.  
  2107. unset($glyphIndexArray[0xFFFF]); 
  2108.  
  2109. $cidtogid = str_pad('', max(array_keys($glyphIndexArray))*2+1, "\x00"); 
  2110. $font['CIDWidths'] = array(); 
  2111. foreach ($glyphIndexArray as $cid => $gid) { 
  2112. if ($cid >= 0 && $cid < 0xFFFF && $gid) { 
  2113. $cidtogid[$cid*2] = chr($gid >> 8); 
  2114. $cidtogid[$cid*2 + 1] = chr($gid & 0xFF); 
  2115.  
  2116. $width = $font_obj->normalizeFUnit(isset($hmtx[$gid]) ? $hmtx[$gid][0] : $hmtx[0][0]); 
  2117. $font['CIDWidths'][$cid] = $width; 
  2118.  
  2119. $font['CIDtoGID'] = base64_encode(gzcompress($cidtogid)); 
  2120. $font['CIDtoGID_Compressed'] = true; 
  2121.  
  2122. $data = file_get_contents($tmp_name); 
  2123. else { 
  2124. $data = file_get_contents($fbfile); 
  2125.  
  2126. $font_obj->close(); 
  2127. unlink($tmp_name); 
  2128.  
  2129. // create the font descriptor 
  2130. $this->numObj++; 
  2131. $fontDescriptorId = $this->numObj; 
  2132.  
  2133. $this->numObj++; 
  2134. $pfbid = $this->numObj; 
  2135.  
  2136. // determine flags (more than a little flakey, hopefully will not matter much) 
  2137. $flags = 0; 
  2138.  
  2139. if ($font['ItalicAngle'] != 0) { 
  2140. $flags+= pow(2, 6); 
  2141.  
  2142. if ($font['IsFixedPitch'] === 'true') { 
  2143. $flags+= 1; 
  2144.  
  2145. $flags+= pow(2, 5); // assume non-sybolic 
  2146. $list = array( 
  2147. 'Ascent' => 'Ascender',  
  2148. 'CapHeight' => 'CapHeight',  
  2149. 'MissingWidth' => 'MissingWidth',  
  2150. 'Descent' => 'Descender',  
  2151. 'FontBBox' => 'FontBBox',  
  2152. 'ItalicAngle' => 'ItalicAngle' 
  2153. ); 
  2154. $fdopt = array( 
  2155. 'Flags' => $flags,  
  2156. 'FontName' => $adobeFontName,  
  2157. 'StemV' => $stemV 
  2158. ); 
  2159.  
  2160. foreach ($list as $k => $v) { 
  2161. if (isset($font[$v])) { 
  2162. $fdopt[$k] = $font[$v]; 
  2163.  
  2164. if ($fbtype === 'pfb') { 
  2165. $fdopt['FontFile'] = $pfbid; 
  2166. } else if ($fbtype === 'ttf') { 
  2167. $fdopt['FontFile2'] = $pfbid; 
  2168.  
  2169. $this->o_fontDescriptor($fontDescriptorId, 'new', $fdopt); 
  2170.  
  2171. // embed the font program 
  2172. $this->o_contents($this->numObj, 'new'); 
  2173. $this->objects[$pfbid]['c'].= $data; 
  2174.  
  2175. // determine the cruicial lengths within this file 
  2176. if ($fbtype === 'pfb') { 
  2177. $l1 = strpos($data, 'eexec') +6; 
  2178. $l2 = strpos($data, '00000000') -$l1; 
  2179. $l3 = mb_strlen($data, '8bit') -$l2-$l1; 
  2180. $this->o_contents($this->numObj, 'add', array('Length1' => $l1, 'Length2' => $l2, 'Length3' => $l3)); 
  2181. } else if ($fbtype == 'ttf') { 
  2182. $l1 = mb_strlen($data, '8bit'); 
  2183. $this->o_contents($this->numObj, 'add', array('Length1' => $l1)); 
  2184.  
  2185. // tell the font object about all this new stuff 
  2186. $tmp = array( 
  2187. 'BaseFont' => $adobeFontName,  
  2188. 'MissingWidth' => $missing_width,  
  2189. 'Widths' => $widthid,  
  2190. 'FirstChar' => $firstChar,  
  2191. 'LastChar' => $lastChar,  
  2192. 'FontDescriptor' => $fontDescriptorId 
  2193. ); 
  2194.  
  2195. if ($fbtype === 'ttf') { 
  2196. $tmp['SubType'] = 'TrueType'; 
  2197.  
  2198. $this->addMessage("adding extra info to font.($fontObj)"); 
  2199.  
  2200. foreach ($tmp as $fk => $fv) { 
  2201. $this->addMessage("$fk : $fv"); 
  2202.  
  2203. $this->o_font($fontObj, 'add', $tmp); 
  2204. } else { 
  2205. $this->addMessage('selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts'); 
  2206.  
  2207. // also set the differences here, note that this means that these will take effect only the 
  2208. //first time that a font is selected, else they are ignored 
  2209. if (isset($options['differences'])) { 
  2210. $font['differences'] = $options['differences']; 
  2211.  
  2212. if ($set && isset($this->fonts[$fontName])) { 
  2213. // so if for some reason the font was not set in the last one then it will not be selected 
  2214. $this->currentBaseFont = $fontName; 
  2215.  
  2216. // the next lines mean that if a new font is selected, then the current text state will be 
  2217. // applied to it as well. 
  2218. $this->currentFont = $this->currentBaseFont; 
  2219. $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum']; 
  2220.  
  2221. //$this->setCurrentFont(); 
  2222.  
  2223. return $this->currentFontNum; 
  2224. //return $this->numObj; 
  2225.  
  2226. /** 
  2227. * sets up the current font, based on the font families, and the current text state 
  2228. * note that this system is quite flexible, a bold-italic font can be completely different to a 
  2229. * italic-bold font, and even bold-bold will have to be defined within the family to have meaning 
  2230. * This function is to be called whenever the currentTextState is changed, it will update 
  2231. * the currentFont setting to whatever the appropriatte family one is. 
  2232. * If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont 
  2233. * This function will change the currentFont to whatever it should be, but will not change the 
  2234. * currentBaseFont. 
  2235. */ 
  2236. private function setCurrentFont() { 
  2237. // if (strlen($this->currentBaseFont) == 0) { 
  2238. // // then assume an initial font 
  2239. // $this->selectFont($this->defaultFont); 
  2240. // } 
  2241. // $cf = substr($this->currentBaseFont, strrpos($this->currentBaseFont, '/')+1); 
  2242. // if (strlen($this->currentTextState) 
  2243. // && isset($this->fontFamilies[$cf]) 
  2244. // && isset($this->fontFamilies[$cf][$this->currentTextState])) { 
  2245. // // then we are in some state or another 
  2246. // // and this font has a family, and the current setting exists within it 
  2247. // // select the font, then return it 
  2248. // $nf = substr($this->currentBaseFont, 0, strrpos($this->currentBaseFont, '/')+1).$this->fontFamilies[$cf][$this->currentTextState]; 
  2249. // $this->selectFont($nf, '', 0); 
  2250. // $this->currentFont = $nf; 
  2251. // $this->currentFontNum = $this->fonts[$nf]['fontNum']; 
  2252. // } else { 
  2253. // // the this font must not have the right family member for the current state 
  2254. // // simply assume the base font 
  2255. $this->currentFont = $this->currentBaseFont; 
  2256. $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum']; 
  2257. // } 
  2258.  
  2259. /** 
  2260. * function for the user to find out what the ID is of the first page that was created during 
  2261. * startup - useful if they wish to add something to it later. 
  2262. */ 
  2263. function getFirstPageId() { 
  2264. return $this->firstPageId; 
  2265.  
  2266. /** 
  2267. * add content to the currently active object 
  2268. */ 
  2269. private function addContent($content) { 
  2270. $this->objects[$this->currentContents]['c'] .= $content; 
  2271.  
  2272. /** 
  2273. * sets the color for fill operations 
  2274. */ 
  2275. function setColor($color, $force = false) { 
  2276. $new_color = array($color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null); 
  2277.  
  2278. if (!$force && $this->currentColor == $new_color) { 
  2279. return; 
  2280.  
  2281. if (isset($new_color[3])) { 
  2282. $this->currentColor = $new_color; 
  2283. $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F k", $this->currentColor)); 
  2284.  
  2285. else if (isset($new_color[2])) { 
  2286. $this->currentColor = $new_color; 
  2287. $this->addContent(vsprintf("\n%.3F %.3F %.3F rg", $this->currentColor)); 
  2288.  
  2289. /** 
  2290. * sets the color for stroke operations 
  2291. */ 
  2292. function setStrokeColor($color, $force = false) { 
  2293. $new_color = array($color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null); 
  2294.  
  2295. if (!$force && $this->currentStrokeColor == $new_color) return; 
  2296.  
  2297. if (isset($new_color[3])) { 
  2298. $this->currentStrokeColor = $new_color; 
  2299. $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F K", $this->currentStrokeColor)); 
  2300.  
  2301. else if (isset($new_color[2])) { 
  2302. $this->currentStrokeColor = $new_color; 
  2303. $this->addContent(vsprintf("\n%.3F %.3F %.3F RG", $this->currentStrokeColor)); 
  2304.  
  2305. /** 
  2306. * Set the graphics state for compositions 
  2307. */ 
  2308. function setGraphicsState($parameters) { 
  2309. // Create a new graphics state object 
  2310. // FIXME: should actually keep track of states that have already been created... 
  2311. $this->numObj++; 
  2312. $this->o_extGState($this->numObj, 'new', $parameters); 
  2313. $this->addContent("\n/GS$this->numStates gs"); 
  2314.  
  2315. /** 
  2316. * Set current blend mode & opacity for lines. 
  2317. * Valid blend modes are: 
  2318. * Normal, Multiply, Screen, Overlay, Darken, Lighten,  
  2319. * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,  
  2320. * Exclusion 
  2321. * @param string $mode the blend mode to use 
  2322. * @param float $opacity 0.0 fully transparent, 1.0 fully opaque 
  2323. */ 
  2324. function setLineTransparency($mode, $opacity) { 
  2325. static $blend_modes = array("Normal", "Multiply", "Screen",  
  2326. "Overlay", "Darken", "Lighten",  
  2327. "ColorDogde", "ColorBurn", "HardLight",  
  2328. "SoftLight", "Difference", "Exclusion"); 
  2329.  
  2330. if ( !in_array($mode, $blend_modes) ) 
  2331. $mode = "Normal"; 
  2332.  
  2333. // Only create a new graphics state if required 
  2334. if ( $mode === $this->currentLineTransparency["mode"] && 
  2335. $opacity == $this->currentLineTransparency["opacity"] ) 
  2336. return; 
  2337.  
  2338. $this->currentLineTransparency["mode"] = $mode; 
  2339. $this->currentLineTransparency["opacity"] = $opacity; 
  2340.  
  2341. $options = array("BM" => "/$mode",  
  2342. "CA" => (float)$opacity); 
  2343.  
  2344. $this->setGraphicsState($options); 
  2345.  
  2346. /** 
  2347. * Set current blend mode & opacity for filled objects. 
  2348. * Valid blend modes are: 
  2349. * Normal, Multiply, Screen, Overlay, Darken, Lighten,  
  2350. * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,  
  2351. * Exclusion 
  2352. * @param string $mode the blend mode to use 
  2353. * @param float $opacity 0.0 fully transparent, 1.0 fully opaque 
  2354. */ 
  2355. function setFillTransparency($mode, $opacity) { 
  2356. static $blend_modes = array("Normal", "Multiply", "Screen",  
  2357. "Overlay", "Darken", "Lighten",  
  2358. "ColorDogde", "ColorBurn", "HardLight",  
  2359. "SoftLight", "Difference", "Exclusion"); 
  2360.  
  2361. if ( !in_array($mode, $blend_modes) ) { 
  2362. $mode = "Normal"; 
  2363.  
  2364. if ( $mode === $this->currentFillTransparency["mode"] && 
  2365. $opacity == $this->currentFillTransparency["opacity"] ) { 
  2366. return; 
  2367.  
  2368. $this->currentFillTransparency["mode"] = $mode; 
  2369. $this->currentFillTransparency["opacity"] = $opacity; 
  2370.  
  2371. $options = array( 
  2372. "BM" => "/$mode",  
  2373. "ca" => (float)$opacity,  
  2374. ); 
  2375.  
  2376. $this->setGraphicsState($options); 
  2377.  
  2378. /** 
  2379. * draw a line from one set of coordinates to another 
  2380. */ 
  2381. function line($x1, $y1, $x2, $y2, $stroke = true) { 
  2382. $this->addContent(sprintf("\n%.3F %.3F m %.3F %.3F l", $x1, $y1, $x2, $y2)); 
  2383.  
  2384. if ($stroke) { 
  2385. $this->addContent(' S'); 
  2386.  
  2387. /** 
  2388. * draw a bezier curve based on 4 control points 
  2389. */ 
  2390. function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3) { 
  2391. // in the current line style, draw a bezier curve from (x0, y0) to (x3, y3) using the other two points 
  2392. // as the control points for the curve. 
  2393. $this->addContent(sprintf("\n%.3F %.3F m %.3F %.3F %.3F %.3F %.3F %.3F c S", $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)); 
  2394.  
  2395. /** 
  2396. * draw a part of an ellipse 
  2397. */ 
  2398. function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8) { 
  2399. $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, false); 
  2400.  
  2401. /** 
  2402. * draw a filled ellipse 
  2403. */ 
  2404. function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360) { 
  2405. return $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, true, true); 
  2406.  
  2407. /** 
  2408. * draw an ellipse 
  2409. * note that the part and filled ellipse are just special cases of this function 
  2410. * draws an ellipse in the current line style 
  2411. * centered at $x0, $y0, radii $r1, $r2 
  2412. * if $r2 is not set, then a circle is drawn 
  2413. * from $astart to $afinish, measured in degrees, running anti-clockwise from the right hand side of the ellipse. 
  2414. * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a 
  2415. * pretty crappy shape at 2, as we are approximating with bezier curves. 
  2416. */ 
  2417. function ellipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360, $close = true, $fill = false, $stroke = true, $incomplete = false) { 
  2418. if ($r1 == 0) { 
  2419. return; 
  2420.  
  2421. if ($r2 == 0) { 
  2422. $r2 = $r1; 
  2423.  
  2424. if ($nSeg < 2) { 
  2425. $nSeg = 2; 
  2426.  
  2427. $astart = deg2rad((float)$astart); 
  2428. $afinish = deg2rad((float)$afinish); 
  2429. $totalAngle = $afinish-$astart; 
  2430.  
  2431. $dt = $totalAngle/$nSeg; 
  2432. $dtm = $dt/3; 
  2433.  
  2434. if ($angle != 0) { 
  2435. $a = -1*deg2rad((float)$angle); 
  2436.  
  2437. $this->addContent(sprintf("\n q %.3F %.3F %.3F %.3F %.3F %.3F cm", cos($a), -sin($a), sin($a), cos($a), $x0, $y0)); 
  2438.  
  2439. $x0 = 0; 
  2440. $y0 = 0; 
  2441.  
  2442. $t1 = $astart; 
  2443. $a0 = $x0 + $r1*cos($t1); 
  2444. $b0 = $y0 + $r2*sin($t1); 
  2445. $c0 = -$r1 * sin($t1); 
  2446. $d0 = $r2 * cos($t1); 
  2447.  
  2448. if (!$incomplete) { 
  2449. $this->addContent(sprintf("\n%.3F %.3F m ", $a0, $b0)); 
  2450.  
  2451. for ($i = 1; $i <= $nSeg; $i++) { 
  2452. // draw this bit of the total curve 
  2453. $t1 = $i * $dt + $astart; 
  2454. $a1 = $x0 + $r1 * cos($t1); 
  2455. $b1 = $y0 + $r2 * sin($t1); 
  2456. $c1 = -$r1 * sin($t1); 
  2457. $d1 = $r2 * cos($t1); 
  2458.  
  2459. $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F %.3F %.3F c", ($a0+$c0*$dtm), ($b0+$d0*$dtm), ($a1-$c1*$dtm), ($b1-$d1*$dtm), $a1, $b1)); 
  2460.  
  2461. $a0 = $a1; 
  2462. $b0 = $b1; 
  2463. $c0 = $c1; 
  2464. $d0 = $d1; 
  2465.  
  2466. if (!$incomplete) { 
  2467. if ($fill) { 
  2468. $this->addContent(' f'); 
  2469. else if ($close) { 
  2470. $this->addContent(' s'); // small 's' signifies closing the path as well 
  2471. else if ($stroke) { 
  2472. $this->addContent(' S'); 
  2473.  
  2474. if ($angle != 0) { 
  2475. $this->addContent(' Q'); 
  2476.  
  2477. /** 
  2478. * this sets the line drawing style. 
  2479. * width, is the thickness of the line in user units 
  2480. * cap is the type of cap to put on the line, values can be 'butt', 'round', 'square' 
  2481. * where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the 
  2482. * end of the line. 
  2483. * join can be 'miter', 'round', 'bevel' 
  2484. * dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the 
  2485. * on and off dashes. 
  2486. * (2) represents 2 on, 2 off, 2 on , 2 off ... 
  2487. * (2, 1) is 2 on, 1 off, 2 on, 1 off.. etc 
  2488. * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts. 
  2489. */ 
  2490. function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0) { 
  2491. // this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day 
  2492. $string = ''; 
  2493.  
  2494. if ($width > 0) { 
  2495. $string.= "$width w"; 
  2496.  
  2497. $ca = array('butt' => 0, 'round' => 1, 'square' => 2); 
  2498.  
  2499. if (isset($ca[$cap])) { 
  2500. $string.= " $ca[$cap] J"; 
  2501.  
  2502. $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2); 
  2503.  
  2504. if (isset($ja[$join])) { 
  2505. $string.= " $ja[$join] j"; 
  2506.  
  2507. if (is_array($dash)) { 
  2508. $string.= ' [ ' . implode(' ', $dash) . " ] $phase d"; 
  2509.  
  2510. $this->currentLineStyle = $string; 
  2511. $this->addContent("\n$string"); 
  2512.  
  2513. /** 
  2514. * draw a polygon, the syntax for this is similar to the GD polygon command 
  2515. */ 
  2516. function polygon($p, $np, $f = false) { 
  2517. $this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1])); 
  2518.  
  2519. for ($i = 2; $i < $np * 2; $i = $i + 2) { 
  2520. $this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i+1])); 
  2521.  
  2522. if ($f) { 
  2523. $this->addContent(' f'); 
  2524. } else { 
  2525. $this->addContent(' S'); 
  2526.  
  2527. /** 
  2528. * a filled rectangle, note that it is the width and height of the rectangle which are the secondary paramaters, not 
  2529. * the coordinates of the upper-right corner 
  2530. */ 
  2531. function filledRectangle($x1, $y1, $width, $height) { 
  2532. $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re f", $x1, $y1, $width, $height)); 
  2533.  
  2534. /** 
  2535. * draw a rectangle, note that it is the width and height of the rectangle which are the secondary paramaters, not 
  2536. * the coordinates of the upper-right corner 
  2537. */ 
  2538. function rectangle($x1, $y1, $width, $height) { 
  2539. $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re S", $x1, $y1, $width, $height)); 
  2540.  
  2541. /** 
  2542. * save the current graphic state 
  2543. */ 
  2544. function save() { 
  2545. // we must reset the color cache or it will keep bad colors after clipping 
  2546. $this->currentColor = null; 
  2547. $this->currentStrokeColor = null; 
  2548. $this->addContent("\nq"); 
  2549.  
  2550. /** 
  2551. * restore the last graphic state 
  2552. */ 
  2553. function restore() { 
  2554. // we must reset the color cache or it will keep bad colors after clipping 
  2555. $this->currentColor = null; 
  2556. $this->currentStrokeColor = null; 
  2557. $this->addContent("\nQ"); 
  2558.  
  2559. /** 
  2560. * draw a clipping rectangle, all the elements added after this will be clipped 
  2561. */ 
  2562. function clippingRectangle($x1, $y1, $width, $height) { 
  2563. $this->save(); 
  2564. $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re W n", $x1, $y1, $width, $height)); 
  2565.  
  2566. /** 
  2567. * draw a clipping rounded rectangle, all the elements added after this will be clipped 
  2568. */ 
  2569. function clippingRectangleRounded($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL) { 
  2570. $this->save(); 
  2571.  
  2572. // start: top edge, left end 
  2573. $this->addContent(sprintf("\n%.3F %.3F m ", $x1, $y1 - $rTL + $h)); 
  2574.  
  2575. // curve: bottom-left corner 
  2576. $this->ellipse($x1 + $rBL, $y1 + $rBL, $rBL, 0, 0, 8, 180, 270, false, false, false, true); 
  2577.  
  2578. // line: right edge, bottom end 
  2579. $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w - $rBR, $y1)); 
  2580.  
  2581. // curve: bottom-right corner 
  2582. $this->ellipse($x1 + $w - $rBR, $y1 + $rBR, $rBR, 0, 0, 8, 270, 360, false, false, false, true); 
  2583.  
  2584. // line: right edge, top end 
  2585. $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w, $y1 + $h - $rTR)); 
  2586.  
  2587. // curve: bottom-right corner 
  2588. $this->ellipse($x1 + $w - $rTR, $y1 + $h - $rTR, $rTR, 0, 0, 8, 0, 90, false, false, false, true); 
  2589.  
  2590. // line: bottom edge, right end 
  2591. $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rTL, $y1 + $h)); 
  2592.  
  2593. // curve: top-right corner 
  2594. $this->ellipse($x1 + $rTL, $y1 + $h - $rTL, $rTL, 0, 0, 8, 90, 180, false, false, false, true); 
  2595.  
  2596. // line: top edge, left end 
  2597. $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rBL, $y1)); 
  2598.  
  2599. // Close & clip 
  2600. $this->addContent(" W n"); 
  2601.  
  2602. /** 
  2603. * ends the last clipping shape 
  2604. */ 
  2605. function clippingEnd() { 
  2606. $this->restore(); 
  2607.  
  2608. /** 
  2609. * scale 
  2610. * @param float $s_x scaling factor for width as percent 
  2611. * @param float $s_y scaling factor for height as percent 
  2612. * @param float $x Origin abscisse 
  2613. * @param float $y Origin ordinate 
  2614. */ 
  2615. function scale($s_x, $s_y, $x, $y) { 
  2616. $y = $this->currentPageSize["height"] - $y; 
  2617.  
  2618. $tm = array( 
  2619. $s_x, 0,  
  2620. 0, $s_y,  
  2621. $x*(1-$s_x), $y*(1-$s_y) 
  2622. ); 
  2623.  
  2624. $this->transform($tm); 
  2625.  
  2626. /** 
  2627. * translate 
  2628. * @param float $t_x movement to the right 
  2629. * @param float $t_y movement to the bottom 
  2630. */ 
  2631. function translate($t_x, $t_y) { 
  2632. $tm = array( 
  2633. 1, 0,  
  2634. 0, 1,  
  2635. $t_x, -$t_y 
  2636. ); 
  2637.  
  2638. $this->transform($tm); 
  2639.  
  2640. /** 
  2641. * rotate 
  2642. * @param float $angle angle in degrees for counter-clockwise rotation 
  2643. * @param float $x Origin abscisse 
  2644. * @param float $y Origin ordinate 
  2645. */ 
  2646. function rotate($angle, $x, $y) { 
  2647. $y = $this->currentPageSize["height"] - $y; 
  2648.  
  2649. $a = deg2rad($angle); 
  2650. $cos_a = cos($a); 
  2651. $sin_a = sin($a); 
  2652.  
  2653. $tm = array( 
  2654. $cos_a, -$sin_a,  
  2655. $sin_a, $cos_a,  
  2656. $x - $sin_a*$y - $cos_a*$x, $y - $cos_a*$y + $sin_a*$x,  
  2657. ); 
  2658.  
  2659. $this->transform($tm); 
  2660.  
  2661. /** 
  2662. * skew 
  2663. * @param float $angle_x 
  2664. * @param float $angle_y 
  2665. * @param float $x Origin abscisse 
  2666. * @param float $y Origin ordinate 
  2667. */ 
  2668. function skew($angle_x, $angle_y, $x, $y) { 
  2669. $y = $this->currentPageSize["height"] - $y; 
  2670.  
  2671. $tan_x = tan(deg2rad($angle_x)); 
  2672. $tan_y = tan(deg2rad($angle_y)); 
  2673.  
  2674. $tm = array( 
  2675. 1, -$tan_y,  
  2676. -$tan_x, 1,  
  2677. $tan_x*$y, $tan_y*$x,  
  2678. ); 
  2679.  
  2680. $this->transform($tm); 
  2681.  
  2682. /** 
  2683. * apply graphic transformations 
  2684. * @param array $tm transformation matrix 
  2685. */ 
  2686. function transform($tm) { 
  2687. $this->addContent(vsprintf("\n %.3F %.3F %.3F %.3F %.3F %.3F cm", $tm)); 
  2688.  
  2689. /** 
  2690. * add a new page to the document 
  2691. * this also makes the new page the current active object 
  2692. */ 
  2693. function newPage($insert = 0, $id = 0, $pos = 'after') { 
  2694. // if there is a state saved, then go up the stack closing them 
  2695. // then on the new page, re-open them with the right setings 
  2696.  
  2697. if ($this->nStateStack) { 
  2698. for ($i = $this->nStateStack; $i >= 1; $i--) { 
  2699. $this->restoreState($i); 
  2700.  
  2701. $this->numObj++; 
  2702.  
  2703. if ($insert) { 
  2704. // the id from the ezPdf class is the id of the contents of the page, not the page object itself 
  2705. // query that object to find the parent 
  2706. $rid = $this->objects[$id]['onPage']; 
  2707. $opt = array('rid' => $rid, 'pos' => $pos); 
  2708. $this->o_page($this->numObj, 'new', $opt); 
  2709. } else { 
  2710. $this->o_page($this->numObj, 'new'); 
  2711.  
  2712. // if there is a stack saved, then put that onto the page 
  2713. if ($this->nStateStack) { 
  2714. for ($i = 1; $i <= $this->nStateStack; $i++) { 
  2715. $this->saveState($i); 
  2716.  
  2717. // and if there has been a stroke or fill color set, then transfer them 
  2718. if (isset($this->currentColor)) { 
  2719. $this->setColor($this->currentColor, true); 
  2720.  
  2721. if (isset($this->currentStrokeColor)) { 
  2722. $this->setStrokeColor($this->currentStrokeColor, true); 
  2723.  
  2724. // if there is a line style set, then put this in too 
  2725. if (mb_strlen($this->currentLineStyle, '8bit')) { 
  2726. $this->addContent("\n$this->currentLineStyle"); 
  2727.  
  2728. // the call to the o_page object set currentContents to the present page, so this can be returned as the page id 
  2729. return $this->currentContents; 
  2730.  
  2731. /** 
  2732. * output the pdf code, streaming it to the browser 
  2733. * the relevant headers are set so that hopefully the browser will recognise it 
  2734. */ 
  2735. function stream($options = '') { 
  2736. // setting the options allows the adjustment of the headers 
  2737. // values at the moment are: 
  2738. // 'Content-Disposition' => 'filename' - sets the filename, though not too sure how well this will 
  2739. // work as in my trial the browser seems to use the filename of the php file with .pdf on the end 
  2740. // 'Accept-Ranges' => 1 or 0 - if this is not set to 1, then this header is not included, off by default 
  2741. // this header seems to have caused some problems despite tha fact that it is supposed to solve 
  2742. // them, so I am leaving it off by default. 
  2743. // 'compress' = > 1 or 0 - apply content stream compression, this is on (1) by default 
  2744. // 'Attachment' => 1 or 0 - if 1, force the browser to open a download dialog 
  2745. if (!is_array($options)) { 
  2746. $options = array(); 
  2747.  
  2748. if ( headers_sent()) { 
  2749. die("Unable to stream pdf: headers already sent"); 
  2750.  
  2751. $debug = empty($options['compression']); 
  2752. $tmp = ltrim($this->output($debug)); 
  2753.  
  2754. header("Cache-Control: private"); 
  2755. header("Content-type: application/pdf"); 
  2756.  
  2757. //FIXME: I don't know that this is sufficient for determining content length (i.e. what about transport compression?) 
  2758. header("Content-Length: " . mb_strlen($tmp, '8bit')); 
  2759. $fileName = (isset($options['Content-Disposition']) ? $options['Content-Disposition'] : 'file.pdf'); 
  2760.  
  2761. if ( !isset($options["Attachment"])) 
  2762. $options["Attachment"] = true; 
  2763.  
  2764. $attachment = $options["Attachment"] ? "attachment" : "inline"; 
  2765.  
  2766. // detect the character encoding of the incoming file 
  2767. $encoding = mb_detect_encoding($fileName); 
  2768. $fallbackfilename = mb_convert_encoding($fileName, "ISO-8859-1", $encoding);  
  2769. $encodedfallbackfilename = rawurlencode($fallbackfilename); 
  2770. $encodedfilename = rawurlencode($fileName); 
  2771.  
  2772. header("Content-Disposition: $attachment; filename=". $encodedfallbackfilename ."; filename*=UTF-8''$encodedfilename"); 
  2773.  
  2774. if (isset($options['Accept-Ranges']) && $options['Accept-Ranges'] == 1) { 
  2775. //FIXME: Is this the correct value ... spec says 1#range-unit 
  2776. header("Accept-Ranges: " . mb_strlen($tmp, '8bit')); 
  2777.  
  2778. echo $tmp; 
  2779. flush(); 
  2780.  
  2781. /** 
  2782. * return the height in units of the current font in the given size 
  2783. */ 
  2784. function getFontHeight($size) { 
  2785. if (!$this->numFonts) { 
  2786. $this->selectFont($this->defaultFont); 
  2787.  
  2788. $font = $this->fonts[$this->currentFont]; 
  2789.  
  2790. // for the current font, and the given size, what is the height of the font in user units 
  2791. if ( isset($font['Ascender']) && isset($font['Descender']) ) { 
  2792. $h = $font['Ascender']-$font['Descender']; 
  2793. else { 
  2794. $h = $font['FontBBox'][3]-$font['FontBBox'][1]; 
  2795.  
  2796. // have to adjust by a font offset for Windows fonts. unfortunately it looks like 
  2797. // the bounding box calculations are wrong and I don't know why. 
  2798. if (isset($font['FontHeightOffset'])) { 
  2799. // For CourierNew from Windows this needs to be -646 to match the 
  2800. // Adobe native Courier font. 
  2801. // 
  2802. // For FreeMono from GNU this needs to be -337 to match the 
  2803. // Courier font. 
  2804. // 
  2805. // Both have been added manually to the .afm and .ufm files. 
  2806. $h += (int)$font['FontHeightOffset']; 
  2807.  
  2808. return $size*$h/1000; 
  2809.  
  2810. function getFontXHeight($size) { 
  2811. if (!$this->numFonts) { 
  2812. $this->selectFont($this->defaultFont); 
  2813.  
  2814. $font = $this->fonts[$this->currentFont]; 
  2815.  
  2816. // for the current font, and the given size, what is the height of the font in user units 
  2817. if ( isset($font['XHeight']) ) { 
  2818. $xh = $font['Ascender']-$font['Descender']; 
  2819. else { 
  2820. $xh = $this->getFontHeight($size) / 2; 
  2821.  
  2822. return $size*$xh/1000; 
  2823.  
  2824. /** 
  2825. * return the font descender, this will normally return a negative number 
  2826. * if you add this number to the baseline, you get the level of the bottom of the font 
  2827. * it is in the pdf user units 
  2828. */ 
  2829. function getFontDescender($size) { 
  2830. // note that this will most likely return a negative value 
  2831. if (!$this->numFonts) { 
  2832. $this->selectFont($this->defaultFont); 
  2833.  
  2834. //$h = $this->fonts[$this->currentFont]['FontBBox'][1]; 
  2835. $h = $this->fonts[$this->currentFont]['Descender']; 
  2836.  
  2837. return $size*$h/1000; 
  2838.  
  2839. /** 
  2840. * filter the text, this is applied to all text just before being inserted into the pdf document 
  2841. * it escapes the various things that need to be escaped, and so on 
  2842. * @access private 
  2843. */ 
  2844. function filterText($text, $bom = true, $convert_encoding = true) { 
  2845. if (!$this->numFonts) { 
  2846. $this->selectFont($this->defaultFont); 
  2847.  
  2848. if ($convert_encoding) { 
  2849. $cf = $this->currentFont; 
  2850. if (isset($this->fonts[$cf]) && $this->fonts[$cf]['isUnicode']) { 
  2851. //$text = html_entity_decode($text, ENT_QUOTES, 'UTF-8'); 
  2852. $text = $this->utf8toUtf16BE($text, $bom); 
  2853. } else { 
  2854. //$text = html_entity_decode($text, ENT_QUOTES); 
  2855. $text = mb_convert_encoding($text, self::$targetEncoding, 'UTF-8'); 
  2856.  
  2857. // the chr(13) substitution fixes a bug seen in TCPDF (bug #1421290) 
  2858. return strtr($text, array(')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r')); 
  2859.  
  2860. /** 
  2861. * return array containing codepoints (UTF-8 character values) for the 
  2862. * string passed in. 
  2863. * based on the excellent TCPDF code by Nicola Asuni and the 
  2864. * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html 
  2865. * @access private 
  2866. * @author Orion Richardson 
  2867. * @since January 5, 2008 
  2868. * @param string $text UTF-8 string to process 
  2869. * @return array UTF-8 codepoints array for the string 
  2870. */ 
  2871. function utf8toCodePointsArray(&$text) { 
  2872. $length = mb_strlen($text, '8bit'); // http://www.php.net/manual/en/function.mb-strlen.php#77040 
  2873. $unicode = array(); // array containing unicode values 
  2874. $bytes = array(); // array containing single character byte sequences 
  2875. $numbytes = 1; // number of octetc needed to represent the UTF-8 character 
  2876.  
  2877. for ($i = 0; $i < $length; $i++) { 
  2878. $c = ord($text[$i]); // get one string character at time 
  2879. if (count($bytes) === 0) { // get starting octect 
  2880. if ($c <= 0x7F) { 
  2881. $unicode[] = $c; // use the character "as is" because is ASCII 
  2882. $numbytes = 1; 
  2883. } elseif (($c >> 0x05) === 0x06) { // 2 bytes character (0x06 = 110 BIN) 
  2884. $bytes[] = ($c - 0xC0) << 0x06; 
  2885. $numbytes = 2; 
  2886. } elseif (($c >> 0x04) === 0x0E) { // 3 bytes character (0x0E = 1110 BIN) 
  2887. $bytes[] = ($c - 0xE0) << 0x0C; 
  2888. $numbytes = 3; 
  2889. } elseif (($c >> 0x03) === 0x1E) { // 4 bytes character (0x1E = 11110 BIN) 
  2890. $bytes[] = ($c - 0xF0) << 0x12; 
  2891. $numbytes = 4; 
  2892. } else { 
  2893. // use replacement character for other invalid sequences 
  2894. $unicode[] = 0xFFFD; 
  2895. $bytes = array(); 
  2896. $numbytes = 1; 
  2897. } elseif (($c >> 0x06) === 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN 
  2898. $bytes[] = $c - 0x80; 
  2899. if (count($bytes) === $numbytes) { 
  2900. // compose UTF-8 bytes to a single unicode value 
  2901. $c = $bytes[0]; 
  2902. for ($j = 1; $j < $numbytes; $j++) { 
  2903. $c += ($bytes[$j] << (($numbytes - $j - 1) * 0x06)); 
  2904. if ((($c >= 0xD800) AND ($c <= 0xDFFF)) OR ($c >= 0x10FFFF)) { 
  2905. // The definition of UTF-8 prohibits encoding character numbers between 
  2906. // U+D800 and U+DFFF, which are reserved for use with the UTF-16 
  2907. // encoding form (as surrogate pairs) and do not directly represent 
  2908. // characters. 
  2909. $unicode[] = 0xFFFD; // use replacement character 
  2910. } else { 
  2911. $unicode[] = $c; // add char to array 
  2912. // reset data for next char 
  2913. $bytes = array(); 
  2914. $numbytes = 1; 
  2915. } else { 
  2916. // use replacement character for other invalid sequences 
  2917. $unicode[] = 0xFFFD; 
  2918. $bytes = array(); 
  2919. $numbytes = 1; 
  2920. return $unicode; 
  2921.  
  2922. /** 
  2923. * convert UTF-8 to UTF-16 with an additional byte order marker 
  2924. * at the front if required. 
  2925. * based on the excellent TCPDF code by Nicola Asuni and the 
  2926. * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html 
  2927. * @access private 
  2928. * @author Orion Richardson 
  2929. * @since January 5, 2008 
  2930. * @param string $text UTF-8 string to process 
  2931. * @param boolean $bom whether to add the byte order marker 
  2932. * @return string UTF-16 result string 
  2933. */ 
  2934. function utf8toUtf16BE(&$text, $bom = true) { 
  2935. $cf = $this->currentFont; 
  2936. if (!$this->fonts[$cf]['isUnicode']) return $text; 
  2937. $out = $bom ? "\xFE\xFF" : ''; 
  2938.  
  2939. $unicode = $this->utf8toCodePointsArray($text); 
  2940. foreach ($unicode as $c) { 
  2941. if ($c === 0xFFFD) { 
  2942. $out .= "\xFF\xFD"; // replacement character 
  2943. } elseif ($c < 0x10000) { 
  2944. $out .= chr($c >> 0x08) . chr($c & 0xFF); 
  2945. } else { 
  2946. $c -= 0x10000; 
  2947. $w1 = 0xD800 | ($c >> 0x10); 
  2948. $w2 = 0xDC00 | ($c & 0x3FF); 
  2949. $out .= chr($w1 >> 0x08) . chr($w1 & 0xFF) . chr($w2 >> 0x08) . chr($w2 & 0xFF); 
  2950. return $out; 
  2951.  
  2952. /** 
  2953. * given a start position and information about how text is to be laid out, calculate where 
  2954. * on the page the text will end 
  2955. */ 
  2956. private function getTextPosition($x, $y, $angle, $size, $wa, $text) { 
  2957. // given this information return an array containing x and y for the end position as elements 0 and 1 
  2958. $w = $this->getTextWidth($size, $text); 
  2959.  
  2960. // need to adjust for the number of spaces in this text 
  2961. $words = explode(' ', $text); 
  2962. $nspaces = count($words) -1; 
  2963. $w+= $wa*$nspaces; 
  2964. $a = deg2rad((float)$angle); 
  2965.  
  2966. return array(cos($a) *$w+$x, -sin($a) *$w+$y); 
  2967.  
  2968. /** 
  2969. * Callback method used by smallCaps 
  2970. *  
  2971. * @param array $matches 
  2972. * @return string 
  2973. */ 
  2974. function toUpper($matches) { 
  2975. return mb_strtoupper($matches[0]); 
  2976.  
  2977. function concatMatches($matches) { 
  2978. $str = ""; 
  2979. foreach ($matches as $match) { 
  2980. $str .= $match[0]; 
  2981. return $str; 
  2982.  
  2983. /** 
  2984. * add text to the document, at a specified location, size and angle on the page 
  2985. */ 
  2986. function registerText($font, $text) { 
  2987. if ( !$this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts) ) { 
  2988. return; 
  2989.  
  2990. if ( !isset($this->stringSubsets[$font]) ) { 
  2991. $this->stringSubsets[$font] = array(); 
  2992.  
  2993. $this->stringSubsets[$font] = array_unique(array_merge($this->stringSubsets[$font], $this->utf8toCodePointsArray($text))); 
  2994.  
  2995. /** 
  2996. * add text to the document, at a specified location, size and angle on the page 
  2997. */ 
  2998. function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false) { 
  2999. if (!$this->numFonts) { 
  3000. $this->selectFont($this->defaultFont); 
  3001.  
  3002. $text = str_replace(array("\r", "\n"), "", $text); 
  3003.  
  3004. if ( $smallCaps ) { 
  3005. preg_match_all("/(\P{Ll}+)/u", $text, $matches, PREG_SET_ORDER); 
  3006. $lower = $this->concatMatches($matches); 
  3007. d($lower); 
  3008.  
  3009. preg_match_all("/(\p{Ll}+)/u", $text, $matches, PREG_SET_ORDER); 
  3010. $other = $this->concatMatches($matches); 
  3011. d($other); 
  3012.  
  3013. //$text = preg_replace_callback("/\p{Ll}/u", array($this, "toUpper"), $text); 
  3014.  
  3015. // if there are any open callbacks, then they should be called, to show the start of the line 
  3016. if ($this->nCallback > 0) { 
  3017. for ($i = $this->nCallback; $i > 0; $i--) { 
  3018. // call each function 
  3019. $info = array( 
  3020. 'x' => $x,  
  3021. 'y' => $y,  
  3022. 'angle' => $angle,  
  3023. 'status' => 'sol',  
  3024. 'p' => $this->callback[$i]['p'],  
  3025. 'nCallback' => $this->callback[$i]['nCallback'],  
  3026. 'height' => $this->callback[$i]['height'],  
  3027. 'descender' => $this->callback[$i]['descender'] 
  3028. ); 
  3029.  
  3030. $func = $this->callback[$i]['f']; 
  3031. $this->$func($info); 
  3032.  
  3033. if ($angle == 0) { 
  3034. $this->addContent(sprintf("\nBT %.3F %.3F Td", $x, $y)); 
  3035. } else { 
  3036. $a = deg2rad((float)$angle); 
  3037. $this->addContent(sprintf("\nBT %.3F %.3F %.3F %.3F %.3F %.3F Tm", cos($a), -sin($a), sin($a), cos($a), $x, $y)); 
  3038.  
  3039. if ($wordSpaceAdjust != 0 || $wordSpaceAdjust != $this->wordSpaceAdjust) { 
  3040. $this->wordSpaceAdjust = $wordSpaceAdjust; 
  3041. $this->addContent(sprintf(" %.3F Tw", $wordSpaceAdjust)); 
  3042.  
  3043. if ($charSpaceAdjust != 0 || $charSpaceAdjust != $this->charSpaceAdjust) { 
  3044. $this->charSpaceAdjust = $charSpaceAdjust; 
  3045. $this->addContent(sprintf(" %.3F Tc", $charSpaceAdjust)); 
  3046.  
  3047. $len = mb_strlen($text); 
  3048. $start = 0; 
  3049.  
  3050. if ($start < $len) { 
  3051. $part = $text; // OAR - Don't need this anymore, given that $start always equals zero. substr($text, $start); 
  3052. $place_text = $this->filterText($part, false); 
  3053. // modify unicode text so that extra word spacing is manually implemented (bug #) 
  3054. $cf = $this->currentFont; 
  3055. if ($this->fonts[$cf]['isUnicode'] && $wordSpaceAdjust != 0) { 
  3056. $space_scale = 1000 / $size; 
  3057. //$place_text = str_replace(' ', ') ( ) '.($this->getTextWidth($size, chr(32), $wordSpaceAdjust)*-75).' (', $place_text); 
  3058. $place_text = str_replace(' ', ' ) '.(-round($space_scale*$wordSpaceAdjust)).' (', $place_text); 
  3059. $this->addContent(" /F$this->currentFontNum ".sprintf('%.1F Tf ', $size)); 
  3060. $this->addContent(" [($place_text)] TJ"); 
  3061.  
  3062. $this->addContent(' ET'); 
  3063.  
  3064. // if there are any open callbacks, then they should be called, to show the end of the line 
  3065. if ($this->nCallback > 0) { 
  3066. for ($i = $this->nCallback; $i > 0; $i--) { 
  3067. // call each function 
  3068. $tmp = $this->getTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, $text); 
  3069. $info = array( 
  3070. 'x' => $tmp[0],  
  3071. 'y' => $tmp[1],  
  3072. 'angle' => $angle,  
  3073. 'status' => 'eol',  
  3074. 'p' => $this->callback[$i]['p'],  
  3075. 'nCallback' => $this->callback[$i]['nCallback'],  
  3076. 'height' => $this->callback[$i]['height'],  
  3077. 'descender' => $this->callback[$i]['descender'] 
  3078. ); 
  3079. $func = $this->callback[$i]['f']; 
  3080. $this->$func($info); 
  3081.  
  3082. /** 
  3083. * calculate how wide a given text string will be on a page, at a given size. 
  3084. * this can be called externally, but is also used by the other class functions 
  3085. */ 
  3086. function getTextWidth($size, $text, $word_spacing = 0, $char_spacing = 0) { 
  3087. static $ord_cache = array(); 
  3088.  
  3089. // this function should not change any of the settings, though it will need to 
  3090. // track any directives which change during calculation, so copy them at the start 
  3091. // and put them back at the end. 
  3092. $store_currentTextState = $this->currentTextState; 
  3093.  
  3094. if (!$this->numFonts) { 
  3095. $this->selectFont($this->defaultFont); 
  3096.  
  3097. $text = str_replace(array("\r", "\n"), "", $text); 
  3098.  
  3099. // converts a number or a float to a string so it can get the width 
  3100. $text = "$text"; 
  3101.  
  3102. // hmm, this is where it all starts to get tricky - use the font information to 
  3103. // calculate the width of each character, add them up and convert to user units 
  3104. $w = 0; 
  3105. $cf = $this->currentFont; 
  3106. $current_font = $this->fonts[$cf]; 
  3107. $space_scale = 1000 / ( $size > 0 ? $size : 1 ); 
  3108. $n_spaces = 0; 
  3109.  
  3110. if ( $current_font['isUnicode']) { 
  3111. // for Unicode, use the code points array to calculate width rather 
  3112. // than just the string itself 
  3113. $unicode = $this->utf8toCodePointsArray($text); 
  3114.  
  3115. foreach ($unicode as $char) { 
  3116. // check if we have to replace character 
  3117. if ( isset($current_font['differences'][$char])) { 
  3118. $char = $current_font['differences'][$char]; 
  3119.  
  3120. if ( isset($current_font['C'][$char]) ) { 
  3121. $char_width = $current_font['C'][$char]; 
  3122.  
  3123. // add the character width 
  3124. $w += $char_width; 
  3125.  
  3126. // add additional padding for space 
  3127. if ( isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space' ) { // Space 
  3128. $w += $word_spacing * $space_scale; 
  3129. $n_spaces++; 
  3130.  
  3131. // add additionnal char spacing 
  3132. if ( $char_spacing != 0 ) { 
  3133. $w += $char_spacing * $space_scale * (count($unicode) + $n_spaces); 
  3134.  
  3135. } else { 
  3136. // If CPDF is in Unicode mode but the current font does not support Unicode we need to convert the character set to Windows-1252 
  3137. if ( $this->isUnicode ) {  
  3138. $text = mb_convert_encoding($text, 'Windows-1252', 'UTF-8'); 
  3139.  
  3140. $len = mb_strlen($text, 'Windows-1252'); 
  3141.  
  3142. for ($i = 0; $i < $len; $i++) { 
  3143. $c = $text[$i]; 
  3144. $char = isset($ord_cache[$c]) ? $ord_cache[$c] : ($ord_cache[$c] = ord($c)); 
  3145.  
  3146. // check if we have to replace character 
  3147. if ( isset($current_font['differences'][$char])) { 
  3148. $char = $current_font['differences'][$char]; 
  3149.  
  3150. if ( isset($current_font['C'][$char]) ) { 
  3151. $char_width = $current_font['C'][$char]; 
  3152.  
  3153. // add the character width 
  3154. $w += $char_width; 
  3155.  
  3156. // add additional padding for space 
  3157. if ( isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space' ) { // Space 
  3158. $w += $word_spacing * $space_scale; 
  3159. $n_spaces++; 
  3160.  
  3161. // add additionnal char spacing 
  3162. if ( $char_spacing != 0 ) { 
  3163. $w += $char_spacing * $space_scale * ($len + $n_spaces); 
  3164.  
  3165. $this->currentTextState = $store_currentTextState; 
  3166. $this->setCurrentFont(); 
  3167.  
  3168. return $w*$size/1000; 
  3169.  
  3170. /** 
  3171. * this will be called at a new page to return the state to what it was on the 
  3172. * end of the previous page, before the stack was closed down 
  3173. * This is to get around not being able to have open 'q' across pages 
  3174. */ 
  3175. function saveState($pageEnd = 0) { 
  3176. if ($pageEnd) { 
  3177. // this will be called at a new page to return the state to what it was on the 
  3178. // end of the previous page, before the stack was closed down 
  3179. // This is to get around not being able to have open 'q' across pages 
  3180. $opt = $this->stateStack[$pageEnd]; 
  3181. // ok to use this as stack starts numbering at 1 
  3182. $this->setColor($opt['col'], true); 
  3183. $this->setStrokeColor($opt['str'], true); 
  3184. $this->addContent("\n".$opt['lin']); 
  3185. // $this->currentLineStyle = $opt['lin']; 
  3186. } else { 
  3187. $this->nStateStack++; 
  3188. $this->stateStack[$this->nStateStack] = array( 
  3189. 'col' => $this->currentColor,  
  3190. 'str' => $this->currentStrokeColor,  
  3191. 'lin' => $this->currentLineStyle 
  3192. ); 
  3193.  
  3194. $this->save(); 
  3195.  
  3196. /** 
  3197. * restore a previously saved state 
  3198. */ 
  3199. function restoreState($pageEnd = 0) { 
  3200. if (!$pageEnd) { 
  3201. $n = $this->nStateStack; 
  3202. $this->currentColor = $this->stateStack[$n]['col']; 
  3203. $this->currentStrokeColor = $this->stateStack[$n]['str']; 
  3204. $this->addContent("\n".$this->stateStack[$n]['lin']); 
  3205. $this->currentLineStyle = $this->stateStack[$n]['lin']; 
  3206. $this->stateStack[$n] = null; 
  3207. unset($this->stateStack[$n]); 
  3208. $this->nStateStack--; 
  3209.  
  3210. $this->restore(); 
  3211.  
  3212. /** 
  3213. * make a loose object, the output will go into this object, until it is closed, then will revert to 
  3214. * the current one. 
  3215. * this object will not appear until it is included within a page. 
  3216. * the function will return the object number 
  3217. */ 
  3218. function openObject() { 
  3219. $this->nStack++; 
  3220. $this->stack[$this->nStack] = array('c' => $this->currentContents, 'p' => $this->currentPage); 
  3221. // add a new object of the content type, to hold the data flow 
  3222. $this->numObj++; 
  3223. $this->o_contents($this->numObj, 'new'); 
  3224. $this->currentContents = $this->numObj; 
  3225. $this->looseObjects[$this->numObj] = 1; 
  3226.  
  3227. return $this->numObj; 
  3228.  
  3229. /** 
  3230. * open an existing object for editing 
  3231. */ 
  3232. function reopenObject($id) { 
  3233. $this->nStack++; 
  3234. $this->stack[$this->nStack] = array('c' => $this->currentContents, 'p' => $this->currentPage); 
  3235. $this->currentContents = $id; 
  3236.  
  3237. // also if this object is the primary contents for a page, then set the current page to its parent 
  3238. if (isset($this->objects[$id]['onPage'])) { 
  3239. $this->currentPage = $this->objects[$id]['onPage']; 
  3240.  
  3241. /** 
  3242. * close an object 
  3243. */ 
  3244. function closeObject() { 
  3245. // close the object, as long as there was one open in the first place, which will be indicated by 
  3246. // an objectId on the stack. 
  3247. if ($this->nStack > 0) { 
  3248. $this->currentContents = $this->stack[$this->nStack]['c']; 
  3249. $this->currentPage = $this->stack[$this->nStack]['p']; 
  3250. $this->nStack--; 
  3251. // easier to probably not worry about removing the old entries, they will be overwritten 
  3252. // if there are new ones. 
  3253.  
  3254. /** 
  3255. * stop an object from appearing on pages from this point on 
  3256. */ 
  3257. function stopObject($id) { 
  3258. // if an object has been appearing on pages up to now, then stop it, this page will 
  3259. // be the last one that could contian it. 
  3260. if (isset($this->addLooseObjects[$id])) { 
  3261. $this->addLooseObjects[$id] = ''; 
  3262.  
  3263. /** 
  3264. * after an object has been created, it wil only show if it has been added, using this function. 
  3265. */ 
  3266. function addObject($id, $options = 'add') { 
  3267. // add the specified object to the page 
  3268. if (isset($this->looseObjects[$id]) && $this->currentContents != $id) { 
  3269. // then it is a valid object, and it is not being added to itself 
  3270. switch ($options) { 
  3271. case 'all': 
  3272. // then this object is to be added to this page (done in the next block) and 
  3273. // all future new pages. 
  3274. $this->addLooseObjects[$id] = 'all'; 
  3275.  
  3276. case 'add': 
  3277. if (isset($this->objects[$this->currentContents]['onPage'])) { 
  3278. // then the destination contents is the primary for the page 
  3279. // (though this object is actually added to that page) 
  3280. $this->o_page($this->objects[$this->currentContents]['onPage'], 'content', $id); 
  3281. break; 
  3282.  
  3283. case 'even': 
  3284. $this->addLooseObjects[$id] = 'even'; 
  3285. $pageObjectId = $this->objects[$this->currentContents]['onPage']; 
  3286. if ($this->objects[$pageObjectId]['info']['pageNum']%2 == 0) { 
  3287. $this->addObject($id); 
  3288. // hacky huh :) 
  3289. break; 
  3290.  
  3291. case 'odd': 
  3292. $this->addLooseObjects[$id] = 'odd'; 
  3293. $pageObjectId = $this->objects[$this->currentContents]['onPage']; 
  3294. if ($this->objects[$pageObjectId]['info']['pageNum']%2 == 1) { 
  3295. $this->addObject($id); 
  3296. // hacky huh :) 
  3297. break; 
  3298.  
  3299. case 'next': 
  3300. $this->addLooseObjects[$id] = 'all'; 
  3301. break; 
  3302.  
  3303. case 'nexteven': 
  3304. $this->addLooseObjects[$id] = 'even'; 
  3305. break; 
  3306.  
  3307. case 'nextodd': 
  3308. $this->addLooseObjects[$id] = 'odd'; 
  3309. break; 
  3310.  
  3311. /** 
  3312. * return a storable representation of a specific object 
  3313. */ 
  3314. function serializeObject($id) { 
  3315. if ( array_key_exists($id, $this->objects)) { 
  3316. return serialize($this->objects[$id]); 
  3317.  
  3318. /** 
  3319. * restore an object from its stored representation. returns its new object id. 
  3320. */ 
  3321. function restoreSerializedObject($obj) { 
  3322. $obj_id = $this->openObject(); 
  3323. $this->objects[$obj_id] = unserialize($obj); 
  3324. $this->closeObject(); 
  3325. return $obj_id; 
  3326.  
  3327. /** 
  3328. * add content to the documents info object 
  3329. */ 
  3330. function addInfo($label, $value = 0) { 
  3331. // this will only work if the label is one of the valid ones. 
  3332. // modify this so that arrays can be passed as well. 
  3333. // if $label is an array then assume that it is key => value pairs 
  3334. // else assume that they are both scalar, anything else will probably error 
  3335. if (is_array($label)) { 
  3336. foreach ($label as $l => $v) { 
  3337. $this->o_info($this->infoObject, $l, $v); 
  3338. } else { 
  3339. $this->o_info($this->infoObject, $label, $value); 
  3340.  
  3341. /** 
  3342. * set the viewer preferences of the document, it is up to the browser to obey these. 
  3343. */ 
  3344. function setPreferences($label, $value = 0) { 
  3345. // this will only work if the label is one of the valid ones. 
  3346. if (is_array($label)) { 
  3347. foreach ($label as $l => $v) { 
  3348. $this->o_catalog($this->catalogId, 'viewerPreferences', array($l => $v)); 
  3349. } else { 
  3350. $this->o_catalog($this->catalogId, 'viewerPreferences', array($label => $value)); 
  3351.  
  3352. /** 
  3353. * extract an integer from a position in a byte stream 
  3354. */ 
  3355. private function getBytes(&$data, $pos, $num) { 
  3356. // return the integer represented by $num bytes from $pos within $data 
  3357. $ret = 0; 
  3358. for ($i = 0; $i < $num; $i++) { 
  3359. $ret *= 256; 
  3360. $ret += ord($data[$pos+$i]); 
  3361.  
  3362. return $ret; 
  3363.  
  3364. /** 
  3365. * Check if image already added to pdf image directory. 
  3366. * If yes, need not to create again (pass empty data) 
  3367. */ 
  3368. function image_iscached($imgname) { 
  3369. return isset($this->imagelist[$imgname]); 
  3370.  
  3371. /** 
  3372. * add a PNG image into the document, from a GD object 
  3373. * this should work with remote files 
  3374. * @param string $file The PNG file 
  3375. * @param float $x X position 
  3376. * @param float $y Y position 
  3377. * @param float $w Width 
  3378. * @param float $h Height 
  3379. * @param resource $img A GD resource 
  3380. * @param bool $is_mask true if the image is a mask 
  3381. * @param bool $mask true if the image is masked 
  3382. */ 
  3383. function addImagePng($file, $x, $y, $w = 0.0, $h = 0.0, &$img, $is_mask = false, $mask = null) { 
  3384. if (!function_exists("imagepng")) { 
  3385. throw new Exception("The PHP GD extension is required, but is not installed."); 
  3386.  
  3387. //if already cached, need not to read again 
  3388. if ( isset($this->imagelist[$file]) ) { 
  3389. $data = null; 
  3390. else { 
  3391. // Example for transparency handling on new image. Retain for current image 
  3392. // $tIndex = imagecolortransparent($img); 
  3393. // if ($tIndex > 0) { 
  3394. // $tColor = imagecolorsforindex($img, $tIndex); 
  3395. // $new_tIndex = imagecolorallocate($new_img, $tColor['red'], $tColor['green'], $tColor['blue']); 
  3396. // imagefill($new_img, 0, 0, $new_tIndex); 
  3397. // imagecolortransparent($new_img, $new_tIndex); 
  3398. // } 
  3399. // blending mode (literal/blending) on drawing into current image. not relevant when not saved or not drawn 
  3400. //imagealphablending($img, true); 
  3401.  
  3402. //default, but explicitely set to ensure pdf compatibility 
  3403. imagesavealpha($img, false/**!$is_mask && !$mask*/); 
  3404.  
  3405. $error = 0; 
  3406. //DEBUG_IMG_TEMP 
  3407. //debugpng 
  3408. if (DEBUGPNG) print '[addImagePng '.$file.']'; 
  3409.  
  3410. ob_start(); 
  3411. @imagepng($img); 
  3412. $data = ob_get_clean(); 
  3413.  
  3414. if ($data == '') { 
  3415. $error = 1; 
  3416. $errormsg = 'trouble writing file from GD'; 
  3417. //DEBUG_IMG_TEMP 
  3418. //debugpng 
  3419. if (DEBUGPNG) print 'trouble writing file from GD'; 
  3420.  
  3421. if ($error) { 
  3422. $this->addMessage('PNG error - ('.$file.') '.$errormsg); 
  3423. return; 
  3424. } //End isset($this->imagelist[$file]) (png Duplicate removal) 
  3425.  
  3426. $this->addPngFromBuf($file, $x, $y, $w, $h, $data, $is_mask, $mask); 
  3427.  
  3428. protected function addImagePngAlpha($file, $x, $y, $w, $h, $byte) { 
  3429. // generate images 
  3430. $img = imagecreatefrompng($file); 
  3431.  
  3432. if ($img === false) { 
  3433. return; 
  3434.  
  3435. // FIXME The pixel transformation doesn't work well with 8bit PNGs 
  3436. $eight_bit = ($byte & 4) !== 4; 
  3437.  
  3438. $wpx = imagesx($img); 
  3439. $hpx = imagesy($img); 
  3440.  
  3441. imagesavealpha($img, false); 
  3442.  
  3443. // create temp alpha file 
  3444. $tempfile_alpha = tempnam($this->tmp, "cpdf_img_"); 
  3445. @unlink($tempfile_alpha); 
  3446. $tempfile_alpha = "$tempfile_alpha.png"; 
  3447.  
  3448. // create temp plain file 
  3449. $tempfile_plain = tempnam($this->tmp, "cpdf_img_"); 
  3450. @unlink($tempfile_plain); 
  3451. $tempfile_plain = "$tempfile_plain.png"; 
  3452.  
  3453. $imgalpha = imagecreate($wpx, $hpx); 
  3454. imagesavealpha($imgalpha, false); 
  3455.  
  3456. // generate gray scale palette (0 -> 255) 
  3457. for ($c = 0; $c < 256; ++$c) { 
  3458. imagecolorallocate($imgalpha, $c, $c, $c); 
  3459.  
  3460. // Use PECL gmagick + Graphics Magic to process transparent PNG images 
  3461. if (extension_loaded("gmagick")) { 
  3462. $gmagick = new Gmagick($file); 
  3463. $gmagick->setimageformat('png'); 
  3464.  
  3465. // Get opacity channel (negative of alpha channel) 
  3466. $alpha_channel_neg = clone $gmagick; 
  3467. $alpha_channel_neg->separateimagechannel(Gmagick::CHANNEL_OPACITY); 
  3468.  
  3469. // Negate opacity channel 
  3470. $alpha_channel = new Gmagick(); 
  3471. $alpha_channel->newimage($wpx, $hpx, "#FFFFFF", "png"); 
  3472. $alpha_channel->compositeimage($alpha_channel_neg, Gmagick::COMPOSITE_DIFFERENCE, 0, 0); 
  3473. $alpha_channel->separateimagechannel(Gmagick::CHANNEL_RED); 
  3474. $alpha_channel->writeimage($tempfile_alpha); 
  3475.  
  3476. // Cast to 8bit+palette 
  3477. $imgalpha_ = imagecreatefrompng($tempfile_alpha); 
  3478. imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx); 
  3479. imagedestroy($imgalpha_); 
  3480. imagepng($imgalpha, $tempfile_alpha); 
  3481.  
  3482. // Make opaque image 
  3483. $color_channels = new Gmagick(); 
  3484. $color_channels->newimage($wpx, $hpx, "#FFFFFF", "png"); 
  3485. $color_channels->compositeimage($gmagick, Gmagick::COMPOSITE_COPYRED, 0, 0); 
  3486. $color_channels->compositeimage($gmagick, Gmagick::COMPOSITE_COPYGREEN, 0, 0); 
  3487. $color_channels->compositeimage($gmagick, Gmagick::COMPOSITE_COPYBLUE, 0, 0); 
  3488. $color_channels->writeimage($tempfile_plain); 
  3489.  
  3490. $imgplain = imagecreatefrompng($tempfile_plain); 
  3491.  
  3492. // Use PECL imagick + ImageMagic to process transparent PNG images 
  3493. elseif (extension_loaded("imagick")) { 
  3494. // Native cloning was added to pecl-imagick in svn commit 263814 
  3495. // the first version containing it was 3.0.1RC1 
  3496. static $imagickClonable = null; 
  3497. if($imagickClonable === null) { 
  3498. $imagickClonable = version_compare(phpversion('imagick'), '3.0.1rc1') > 0; 
  3499.  
  3500. $imagick = new Imagick($file); 
  3501. $imagick->setFormat('png'); 
  3502.  
  3503. // Get opacity channel (negative of alpha channel) 
  3504. $alpha_channel = $imagickClonable ? clone $imagick : $imagick->clone(); 
  3505. $alpha_channel->separateImageChannel(Imagick::CHANNEL_ALPHA); 
  3506. $alpha_channel->negateImage(true); 
  3507. $alpha_channel->writeImage($tempfile_alpha); 
  3508.  
  3509. // Cast to 8bit+palette 
  3510. $imgalpha_ = imagecreatefrompng($tempfile_alpha); 
  3511. imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx); 
  3512. imagedestroy($imgalpha_); 
  3513. imagepng($imgalpha, $tempfile_alpha); 
  3514.  
  3515. // Make opaque image 
  3516. $color_channels = new Imagick(); 
  3517. $color_channels->newImage($wpx, $hpx, "#FFFFFF", "png"); 
  3518. $color_channels->compositeImage($imagick, Imagick::COMPOSITE_COPYRED, 0, 0); 
  3519. $color_channels->compositeImage($imagick, Imagick::COMPOSITE_COPYGREEN, 0, 0); 
  3520. $color_channels->compositeImage($imagick, Imagick::COMPOSITE_COPYBLUE, 0, 0); 
  3521. $color_channels->writeImage($tempfile_plain); 
  3522.  
  3523. $imgplain = imagecreatefrompng($tempfile_plain); 
  3524. else { 
  3525. // allocated colors cache 
  3526. $allocated_colors = array(); 
  3527.  
  3528. // extract alpha channel 
  3529. for ($xpx = 0; $xpx < $wpx; ++$xpx) { 
  3530. for ($ypx = 0; $ypx < $hpx; ++$ypx) { 
  3531. $color = imagecolorat($img, $xpx, $ypx); 
  3532. $col = imagecolorsforindex($img, $color); 
  3533. $alpha = $col['alpha']; 
  3534.  
  3535. if ($eight_bit) { 
  3536. // with gamma correction 
  3537. $gammacorr = 2.2; 
  3538. $pixel = pow((((127 - $alpha) * 255 / 127) / 255), $gammacorr) * 255; 
  3539.  
  3540. else { 
  3541. // without gamma correction 
  3542. $pixel = (127 - $alpha) * 2; 
  3543.  
  3544. $key = $col['red'].$col['green'].$col['blue']; 
  3545.  
  3546. if (!isset($allocated_colors[$key])) { 
  3547. $pixel_img = imagecolorallocate($img, $col['red'], $col['green'], $col['blue']); 
  3548. $allocated_colors[$key] = $pixel_img; 
  3549. else { 
  3550. $pixel_img = $allocated_colors[$key];  
  3551.  
  3552. imagesetpixel($img, $xpx, $ypx, $pixel_img); 
  3553.  
  3554. imagesetpixel($imgalpha, $xpx, $ypx, $pixel); 
  3555.  
  3556. // extract image without alpha channel 
  3557. $imgplain = imagecreatetruecolor($wpx, $hpx); 
  3558. imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx); 
  3559. imagedestroy($img); 
  3560.  
  3561. imagepng($imgalpha, $tempfile_alpha); 
  3562. imagepng($imgplain, $tempfile_plain); 
  3563.  
  3564. // embed mask image 
  3565. $this->addImagePng($tempfile_alpha, $x, $y, $w, $h, $imgalpha, true); 
  3566. imagedestroy($imgalpha); 
  3567.  
  3568. // embed image, masked with previously embedded mask 
  3569. $this->addImagePng($tempfile_plain, $x, $y, $w, $h, $imgplain, false, true); 
  3570. imagedestroy($imgplain); 
  3571.  
  3572. // remove temp files 
  3573. unlink($tempfile_alpha); 
  3574. unlink($tempfile_plain); 
  3575.  
  3576. /** 
  3577. * add a PNG image into the document, from a file 
  3578. * this should work with remote files 
  3579. */ 
  3580. function addPngFromFile($file, $x, $y, $w = 0, $h = 0) { 
  3581. if (!function_exists("imagecreatefrompng")) { 
  3582. throw new Exception("The PHP GD extension is required, but is not installed."); 
  3583.  
  3584. //if already cached, need not to read again 
  3585. if ( isset($this->imagelist[$file]) ) { 
  3586. $img = null; 
  3587. }  
  3588.  
  3589. else { 
  3590. $info = file_get_contents ($file, false, null, 24, 5); 
  3591. $meta = unpack("CbitDepth/CcolorType/CcompressionMethod/CfilterMethod/CinterlaceMethod", $info); 
  3592. $bit_depth = $meta["bitDepth"]; 
  3593. $color_type = $meta["colorType"]; 
  3594.  
  3595. // http://www.w3.org/TR/PNG/#11IHDR 
  3596. // 3 => indexed 
  3597. // 4 => greyscale with alpha 
  3598. // 6 => fullcolor with alpha 
  3599. $is_alpha = in_array($color_type, array(4, 6)) || ($color_type == 3 && $bit_depth != 4); 
  3600.  
  3601. if ($is_alpha) { // exclude grayscale alpha 
  3602. return $this->addImagePngAlpha($file, $x, $y, $w, $h, $color_type); 
  3603.  
  3604. //png files typically contain an alpha channel. 
  3605. //pdf file format or class.pdf does not support alpha blending. 
  3606. //on alpha blended images, more transparent areas have a color near black. 
  3607. //This appears in the result on not storing the alpha channel. 
  3608. //Correct would be the box background image or its parent when transparent. 
  3609. //But this would make the image dependent on the background. 
  3610. //Therefore create an image with white background and copy in 
  3611. //A more natural background than black is white. 
  3612. //Therefore create an empty image with white background and merge the 
  3613. //image in with alpha blending. 
  3614. $imgtmp = @imagecreatefrompng($file); 
  3615. if (!$imgtmp) { 
  3616. return; 
  3617. $sx = imagesx($imgtmp); 
  3618. $sy = imagesy($imgtmp); 
  3619. $img = imagecreatetruecolor($sx, $sy); 
  3620. imagealphablending($img, true); 
  3621.  
  3622. // @todo is it still needed ?? 
  3623. $ti = imagecolortransparent($imgtmp); 
  3624. if ($ti >= 0) { 
  3625. $tc = imagecolorsforindex($imgtmp, $ti); 
  3626. $ti = imagecolorallocate($img, $tc['red'], $tc['green'], $tc['blue']); 
  3627. imagefill($img, 0, 0, $ti); 
  3628. imagecolortransparent($img, $ti); 
  3629. } else { 
  3630. imagefill($img, 1, 1, imagecolorallocate($img, 255, 255, 255)); 
  3631.  
  3632. imagecopy($img, $imgtmp, 0, 0, 0, 0, $sx, $sy); 
  3633. imagedestroy($imgtmp); 
  3634. $this->addImagePng($file, $x, $y, $w, $h, $img); 
  3635.  
  3636. if ( $img ) { 
  3637. imagedestroy($img); 
  3638.  
  3639. /** 
  3640. * add a PNG image into the document, from a memory buffer of the file 
  3641. */ 
  3642. function addPngFromBuf($file, $x, $y, $w = 0.0, $h = 0.0, &$data, $is_mask = false, $mask = null) { 
  3643. if ( isset($this->imagelist[$file]) ) { 
  3644. $data = null; 
  3645. $info['width'] = $this->imagelist[$file]['w']; 
  3646. $info['height'] = $this->imagelist[$file]['h']; 
  3647. $label = $this->imagelist[$file]['label']; 
  3648.  
  3649. else { 
  3650. if ($data == null) { 
  3651. $this->addMessage('addPngFromBuf error - data not present!'); 
  3652. return; 
  3653.  
  3654. $error = 0; 
  3655.  
  3656. if (!$error) { 
  3657. $header = chr(137) .chr(80) .chr(78) .chr(71) .chr(13) .chr(10) .chr(26) .chr(10); 
  3658.  
  3659. if (mb_substr($data, 0, 8, '8bit') != $header) { 
  3660. $error = 1; 
  3661.  
  3662. if (DEBUGPNG) print '[addPngFromFile this file does not have a valid header '.$file.']'; 
  3663.  
  3664. $errormsg = 'this file does not have a valid header'; 
  3665.  
  3666. if (!$error) { 
  3667. // set pointer 
  3668. $p = 8; 
  3669. $len = mb_strlen($data, '8bit'); 
  3670.  
  3671. // cycle through the file, identifying chunks 
  3672. $haveHeader = 0; 
  3673. $info = array(); 
  3674. $idata = ''; 
  3675. $pdata = ''; 
  3676.  
  3677. while ($p < $len) { 
  3678. $chunkLen = $this->getBytes($data, $p, 4); 
  3679. $chunkType = mb_substr($data, $p+4, 4, '8bit'); 
  3680.  
  3681. switch ($chunkType) { 
  3682. case 'IHDR': 
  3683. // this is where all the file information comes from 
  3684. $info['width'] = $this->getBytes($data, $p+8, 4); 
  3685. $info['height'] = $this->getBytes($data, $p+12, 4); 
  3686. $info['bitDepth'] = ord($data[$p+16]); 
  3687. $info['colorType'] = ord($data[$p+17]); 
  3688. $info['compressionMethod'] = ord($data[$p+18]); 
  3689. $info['filterMethod'] = ord($data[$p+19]); 
  3690. $info['interlaceMethod'] = ord($data[$p+20]); 
  3691.  
  3692. //print_r($info); 
  3693. $haveHeader = 1; 
  3694. if ($info['compressionMethod'] != 0) { 
  3695. $error = 1; 
  3696.  
  3697. //debugpng 
  3698. if (DEBUGPNG) print '[addPngFromFile unsupported compression method '.$file.']'; 
  3699.  
  3700. $errormsg = 'unsupported compression method'; 
  3701.  
  3702. if ($info['filterMethod'] != 0) { 
  3703. $error = 1; 
  3704.  
  3705. //debugpng 
  3706. if (DEBUGPNG) print '[addPngFromFile unsupported filter method '.$file.']'; 
  3707.  
  3708. $errormsg = 'unsupported filter method'; 
  3709. break; 
  3710.  
  3711. case 'PLTE': 
  3712. $pdata.= mb_substr($data, $p+8, $chunkLen, '8bit'); 
  3713. break; 
  3714.  
  3715. case 'IDAT': 
  3716. $idata.= mb_substr($data, $p+8, $chunkLen, '8bit'); 
  3717. break; 
  3718.  
  3719. case 'tRNS': 
  3720. //this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk 
  3721. //print "tRNS found, color type = ".$info['colorType']."\n"; 
  3722. $transparency = array(); 
  3723.  
  3724. switch ($info['colorType']) { 
  3725. // indexed color, rbg 
  3726. case 3: 
  3727. /** corresponding to entries in the plte chunk 
  3728. Alpha for palette index 0: 1 byte 
  3729. Alpha for palette index 1: 1 byte 
  3730. ...etc... 
  3731. */ 
  3732. // there will be one entry for each palette entry. up until the last non-opaque entry. 
  3733. // set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent) 
  3734. $transparency['type'] = 'indexed'; 
  3735. $trans = 0; 
  3736.  
  3737. for ($i = $chunkLen; $i >= 0; $i--) { 
  3738. if (ord($data[$p+8+$i]) == 0) { 
  3739. $trans = $i; 
  3740.  
  3741. $transparency['data'] = $trans; 
  3742. break; 
  3743.  
  3744. // grayscale 
  3745. case 0: 
  3746. /** corresponding to entries in the plte chunk 
  3747. Gray: 2 bytes, range 0 .. (2^bitdepth)-1 
  3748. */ 
  3749. // $transparency['grayscale'] = $this->PRVT_getBytes($data, $p+8, 2); // g = grayscale 
  3750. $transparency['type'] = 'indexed'; 
  3751. $transparency['data'] = ord($data[$p+8+1]); 
  3752. break; 
  3753.  
  3754. // truecolor 
  3755. case 2:  
  3756. /** corresponding to entries in the plte chunk 
  3757. Red: 2 bytes, range 0 .. (2^bitdepth)-1 
  3758. Green: 2 bytes, range 0 .. (2^bitdepth)-1 
  3759. Blue: 2 bytes, range 0 .. (2^bitdepth)-1 
  3760. */ 
  3761. $transparency['r'] = $this->getBytes($data, $p+8, 2); 
  3762. // r from truecolor 
  3763. $transparency['g'] = $this->getBytes($data, $p+10, 2); 
  3764. // g from truecolor 
  3765. $transparency['b'] = $this->getBytes($data, $p+12, 2); 
  3766. // b from truecolor 
  3767.  
  3768. $transparency['type'] = 'color-key'; 
  3769. break; 
  3770.  
  3771. //unsupported transparency type 
  3772. default: 
  3773. if (DEBUGPNG) print '[addPngFromFile unsupported transparency type '.$file.']'; 
  3774. break; 
  3775.  
  3776. // KS End new code 
  3777. break; 
  3778.  
  3779. default: 
  3780. break; 
  3781.  
  3782. $p += $chunkLen+12; 
  3783.  
  3784. if (!$haveHeader) { 
  3785. $error = 1; 
  3786.  
  3787. //debugpng 
  3788. if (DEBUGPNG) print '[addPngFromFile information header is missing '.$file.']'; 
  3789.  
  3790. $errormsg = 'information header is missing'; 
  3791.  
  3792. if (isset($info['interlaceMethod']) && $info['interlaceMethod']) { 
  3793. $error = 1; 
  3794.  
  3795. //debugpng 
  3796. if (DEBUGPNG) print '[addPngFromFile no support for interlaced images in pdf '.$file.']'; 
  3797.  
  3798. $errormsg = 'There appears to be no support for interlaced images in pdf.'; 
  3799.  
  3800. if (!$error && $info['bitDepth'] > 8) { 
  3801. $error = 1; 
  3802.  
  3803. //debugpng 
  3804. if (DEBUGPNG) print '[addPngFromFile bit depth of 8 or less is supported '.$file.']'; 
  3805.  
  3806. $errormsg = 'only bit depth of 8 or less is supported'; 
  3807.  
  3808. if (!$error) { 
  3809. switch ($info['colorType']) { 
  3810. case 3: 
  3811. $color = 'DeviceRGB'; 
  3812. $ncolor = 1; 
  3813. break; 
  3814.  
  3815. case 2: 
  3816. $color = 'DeviceRGB'; 
  3817. $ncolor = 3; 
  3818. break; 
  3819.  
  3820. case 0: 
  3821. $color = 'DeviceGray'; 
  3822. $ncolor = 1; 
  3823. break; 
  3824.  
  3825. default:  
  3826. $error = 1; 
  3827.  
  3828. //debugpng 
  3829. if (DEBUGPNG) print '[addPngFromFile alpha channel not supported: '.$info['colorType'].' '.$file.']'; 
  3830.  
  3831. $errormsg = 'transparancey alpha channel not supported, transparency only supported for palette images.'; 
  3832.  
  3833. if ($error) { 
  3834. $this->addMessage('PNG error - ('.$file.') '.$errormsg); 
  3835. return; 
  3836.  
  3837. //print_r($info); 
  3838. // so this image is ok... add it in. 
  3839. $this->numImages++; 
  3840. $im = $this->numImages; 
  3841. $label = "I$im"; 
  3842. $this->numObj++; 
  3843.  
  3844. // $this->o_image($this->numObj, 'new', array('label' => $label, 'data' => $idata, 'iw' => $w, 'ih' => $h, 'type' => 'png', 'ic' => $info['width'])); 
  3845. $options = array( 
  3846. 'label' => $label,  
  3847. 'data' => $idata,  
  3848. 'bitsPerComponent' => $info['bitDepth'],  
  3849. 'pdata' => $pdata,  
  3850. 'iw' => $info['width'],  
  3851. 'ih' => $info['height'],  
  3852. 'type' => 'png',  
  3853. 'color' => $color,  
  3854. 'ncolor' => $ncolor,  
  3855. 'masked' => $mask,  
  3856. 'isMask' => $is_mask 
  3857. ); 
  3858.  
  3859. if (isset($transparency)) { 
  3860. $options['transparency'] = $transparency; 
  3861.  
  3862. $this->o_image($this->numObj, 'new', $options); 
  3863. $this->imagelist[$file] = array('label' =>$label, 'w' => $info['width'], 'h' => $info['height']); 
  3864.  
  3865. if ($is_mask) { 
  3866. return; 
  3867.  
  3868. if ($w <= 0 && $h <= 0) { 
  3869. $w = $info['width']; 
  3870. $h = $info['height']; 
  3871.  
  3872. if ($w <= 0) { 
  3873. $w = $h/$info['height']*$info['width']; 
  3874.  
  3875. if ($h <= 0) { 
  3876. $h = $w*$info['height']/$info['width']; 
  3877.  
  3878. $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ", $w, $h, $x, $y, $label)); 
  3879.  
  3880. /** 
  3881. * add a JPEG image into the document, from a file 
  3882. */ 
  3883. function addJpegFromFile($img, $x, $y, $w = 0, $h = 0) { 
  3884. // attempt to add a jpeg image straight from a file, using no GD commands 
  3885. // note that this function is unable to operate on a remote file. 
  3886.  
  3887. if (!file_exists($img)) { 
  3888. return; 
  3889.  
  3890. if ( $this->image_iscached($img) ) { 
  3891. $data = null; 
  3892. $imageWidth = $this->imagelist[$img]['w']; 
  3893. $imageHeight = $this->imagelist[$img]['h']; 
  3894. $channels = $this->imagelist[$img]['c']; 
  3895. }  
  3896. else { 
  3897. $tmp = getimagesize($img); 
  3898. $imageWidth = $tmp[0]; 
  3899. $imageHeight = $tmp[1]; 
  3900.  
  3901. if ( isset($tmp['channels']) ) { 
  3902. $channels = $tmp['channels']; 
  3903. } else { 
  3904. $channels = 3; 
  3905.  
  3906. $data = file_get_contents($img); 
  3907.  
  3908. if ($w <= 0 && $h <= 0) { 
  3909. $w = $imageWidth; 
  3910.  
  3911. if ($w == 0) { 
  3912. $w = $h/$imageHeight*$imageWidth; 
  3913.  
  3914. if ($h == 0) { 
  3915. $h = $w*$imageHeight/$imageWidth; 
  3916.  
  3917. $this->addJpegImage_common($data, $x, $y, $w, $h, $imageWidth, $imageHeight, $channels, $img); 
  3918.  
  3919. /** 
  3920. * common code used by the two JPEG adding functions 
  3921. */ 
  3922. private function addJpegImage_common(&$data, $x, $y, $w = 0, $h = 0, $imageWidth, $imageHeight, $channels = 3, $imgname) { 
  3923. if ( $this->image_iscached($imgname) ) { 
  3924. $label = $this->imagelist[$imgname]['label']; 
  3925. //debugpng 
  3926. //if (DEBUGPNG) print '[addJpegImage_common Duplicate '.$imgname.']'; 
  3927.  
  3928. } else { 
  3929. if ($data == null) { 
  3930. $this->addMessage('addJpegImage_common error - ('.$imgname.') data not present!'); 
  3931. return; 
  3932.  
  3933. // note that this function is not to be called externally 
  3934. // it is just the common code between the GD and the file options 
  3935. $this->numImages++; 
  3936. $im = $this->numImages; 
  3937. $label = "I$im"; 
  3938. $this->numObj++; 
  3939.  
  3940. $this->o_image($this->numObj, 'new', array( 
  3941. 'label' => $label,  
  3942. 'data' => &$data,  
  3943. 'iw' => $imageWidth,  
  3944. 'ih' => $imageHeight,  
  3945. 'channels' => $channels 
  3946. )); 
  3947.  
  3948. $this->imagelist[$imgname] = array('label' =>$label, 'w' => $imageWidth, 'h' => $imageHeight, 'c'=> $channels); 
  3949.  
  3950. $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ ", $w, $h, $x, $y, $label)); 
  3951.  
  3952. /** 
  3953. * specify where the document should open when it first starts 
  3954. */ 
  3955. function openHere($style, $a = 0, $b = 0, $c = 0) { 
  3956. // this function will open the document at a specified page, in a specified style 
  3957. // the values for style, and the required paramters are: 
  3958. // 'XYZ' left, top, zoom 
  3959. // 'Fit' 
  3960. // 'FitH' top 
  3961. // 'FitV' left 
  3962. // 'FitR' left, bottom, right 
  3963. // 'FitB' 
  3964. // 'FitBH' top 
  3965. // 'FitBV' left 
  3966. $this->numObj++; 
  3967. $this->o_destination($this->numObj, 'new', array('page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c)); 
  3968. $id = $this->catalogId; 
  3969. $this->o_catalog($id, 'openHere', $this->numObj); 
  3970.  
  3971. /** 
  3972. * Add JavaScript code to the PDF document 
  3973. *  
  3974. * @param string $code 
  3975. * @return void 
  3976. */ 
  3977. function addJavascript($code) { 
  3978. $this->javascript .= $code; 
  3979.  
  3980. /** 
  3981. * create a labelled destination within the document 
  3982. */ 
  3983. function addDestination($label, $style, $a = 0, $b = 0, $c = 0) { 
  3984. // associates the given label with the destination, it is done this way so that a destination can be specified after 
  3985. // it has been linked to 
  3986. // styles are the same as the 'openHere' function 
  3987. $this->numObj++; 
  3988. $this->o_destination($this->numObj, 'new', array('page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c)); 
  3989. $id = $this->numObj; 
  3990.  
  3991. // store the label->idf relationship, note that this means that labels can be used only once 
  3992. $this->destinations["$label"] = $id; 
  3993.  
  3994. /** 
  3995. * define font families, this is used to initialize the font families for the default fonts 
  3996. * and for the user to add new ones for their fonts. The default bahavious can be overridden should 
  3997. * that be desired. 
  3998. */ 
  3999. function setFontFamily($family, $options = '') { 
  4000. if (!is_array($options)) { 
  4001. if ($family === 'init') { 
  4002. // set the known family groups 
  4003. // these font families will be used to enable bold and italic markers to be included 
  4004. // within text streams. html forms will be used... <b></b> <i></i> 
  4005. $this->fontFamilies['Helvetica.afm'] = 
  4006. array( 
  4007. 'b' => 'Helvetica-Bold.afm',  
  4008. 'i' => 'Helvetica-Oblique.afm',  
  4009. 'bi' => 'Helvetica-BoldOblique.afm',  
  4010. 'ib' => 'Helvetica-BoldOblique.afm' 
  4011. ); 
  4012.  
  4013. $this->fontFamilies['Courier.afm'] = 
  4014. array( 
  4015. 'b' => 'Courier-Bold.afm',  
  4016. 'i' => 'Courier-Oblique.afm',  
  4017. 'bi' => 'Courier-BoldOblique.afm',  
  4018. 'ib' => 'Courier-BoldOblique.afm' 
  4019. ); 
  4020.  
  4021. $this->fontFamilies['Times-Roman.afm'] = 
  4022. array( 
  4023. 'b' => 'Times-Bold.afm',  
  4024. 'i' => 'Times-Italic.afm',  
  4025. 'bi' => 'Times-BoldItalic.afm',  
  4026. 'ib' => 'Times-BoldItalic.afm' 
  4027. ); 
  4028. } else { 
  4029.  
  4030. // the user is trying to set a font family 
  4031. // note that this can also be used to set the base ones to something else 
  4032. if (mb_strlen($family)) { 
  4033. $this->fontFamilies[$family] = $options; 
  4034.  
  4035. /** 
  4036. * used to add messages for use in debugging 
  4037. */ 
  4038. function addMessage($message) { 
  4039. $this->messages.= $message."\n"; 
  4040.  
  4041. /** 
  4042. * a few functions which should allow the document to be treated transactionally. 
  4043. */ 
  4044. function transaction($action) { 
  4045. switch ($action) { 
  4046. case 'start': 
  4047. // store all the data away into the checkpoint variable 
  4048. $data = get_object_vars($this); 
  4049. $this->checkpoint = $data; 
  4050. unset($data); 
  4051. break; 
  4052.  
  4053. case 'commit': 
  4054. if (is_array($this->checkpoint) && isset($this->checkpoint['checkpoint'])) { 
  4055. $tmp = $this->checkpoint['checkpoint']; 
  4056. $this->checkpoint = $tmp; 
  4057. unset($tmp); 
  4058. } else { 
  4059. $this->checkpoint = ''; 
  4060. break; 
  4061.  
  4062. case 'rewind': 
  4063. // do not destroy the current checkpoint, but move us back to the state then, so that we can try again 
  4064. if (is_array($this->checkpoint)) { 
  4065. // can only abort if were inside a checkpoint 
  4066. $tmp = $this->checkpoint; 
  4067.  
  4068. foreach ($tmp as $k => $v) { 
  4069. if ($k !== 'checkpoint') { 
  4070. $this->$k = $v; 
  4071. unset($tmp); 
  4072. break; 
  4073.  
  4074. case 'abort': 
  4075. if (is_array($this->checkpoint)) { 
  4076. // can only abort if were inside a checkpoint 
  4077. $tmp = $this->checkpoint; 
  4078. foreach ($tmp as $k => $v) { 
  4079. $this->$k = $v; 
  4080. unset($tmp); 
  4081. break;