PageRenderTime 70ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/smartpdf/code/lib/Cpdf.php

https://bitbucket.org/1blankz7/bibioteka
PHP | 4611 lines | 2759 code | 758 blank | 1094 comment | 199 complexity | 29fe5c875e09f9e085cb7cea570f8041 MD5 | raw file
Possible License(s): LGPL-2.1

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

  1. <?php
  2. /**
  3. * Cpdf
  4. *
  5. * http://www.ros.co.nz/pdf
  6. *
  7. * A PHP class to provide the basic functionality to create a pdf document without
  8. * any requirement for additional modules.
  9. *
  10. * Note that the companion class CezPdf can be used to extend this class and dramatically
  11. * simplify the creation of documents.
  12. *
  13. * Extended by Orion Richardson to support Unicode / UTF-8 characters using
  14. * TCPDF and others as a guide.
  15. *
  16. * IMPORTANT NOTE
  17. * there is no warranty, implied or otherwise with this software.
  18. *
  19. * LICENCE
  20. * This code has been placed in the Public Domain for all to enjoy.
  21. *
  22. * @author Wayne Munro <pdf@ros.co.nz>
  23. * @contributor Orion Richardson <orionr@yahoo.com>
  24. * @contributor Helmut Tischer <htischer@weihenstephan.org>
  25. * @contributor Ryan H. Masten <ryan.masten@gmail.com>
  26. * @version 009
  27. * @package Cpdf
  28. *
  29. * Changes
  30. * @contributor Helmut Tischer <htischer@weihenstephan.org>
  31. * @version 0.5.1.htischer.20090507
  32. * - On multiple identical png and jpg images, put only one copy into the pdf file and refer to it.
  33. * This reduces file size and rendering time.
  34. * - Allow font metrics cache to be a different folder as the font metrics. This allows a read only installation.
  35. * - Allow adding images directly from a gd object. This increases performance by avoiding temporary files.
  36. * - On png image files remove alpa channel to allow display of typical png files in pdf.
  37. * - On addImage avoid temporary file. Todo: Duplicate Image (currently not used)
  38. * - Add a check function, whether image is already cached, This avoids double creation by caller which saves
  39. * CPU time and memory.
  40. * @contributor Helmut Tischer <htischer@weihenstephan.org>
  41. * @version dompdf_trunk_with_helmut_mods.20090524
  42. * - Allow temp and fontcache folders to be passed in by class creator
  43. * @version dompdf_trunk_with_helmut_mods.20090528
  44. * - typo 'decent' instead of 'descent' at various locations made getFontDescender worthless
  45. */
  46. /* $Id: class.pdf.php 313 2010-09-10 16:18:44Z fabien.menager $ */
  47. class Cpdf {
  48. /**
  49. * the current number of pdf objects in the document
  50. */
  51. public $numObj = 0;
  52. /**
  53. * this array contains all of the pdf objects, ready for final assembly
  54. */
  55. public $objects = array();
  56. /**
  57. * the objectId (number within the objects array) of the document catalog
  58. */
  59. public $catalogId;
  60. /**
  61. * array carrying information about the fonts that the system currently knows about
  62. * used to ensure that a font is not loaded twice, among other things
  63. */
  64. public $fonts = array();
  65. /**
  66. * the default font metrics file to use if no other font has been loaded
  67. * the path to the directory containing the font metrics should be included
  68. */
  69. public $defaultFont = './fonts/Helvetica.afm';
  70. /**
  71. * a record of the current font
  72. */
  73. public $currentFont = '';
  74. /**
  75. * the current base font
  76. */
  77. public $currentBaseFont = '';
  78. /**
  79. * the number of the current font within the font array
  80. */
  81. public $currentFontNum = 0;
  82. /**
  83. *
  84. */
  85. public $currentNode;
  86. /**
  87. * object number of the current page
  88. */
  89. public $currentPage;
  90. /**
  91. * object number of the currently active contents block
  92. */
  93. public $currentContents;
  94. /**
  95. * number of fonts within the system
  96. */
  97. public $numFonts = 0;
  98. /**
  99. * Number of graphic state resources used
  100. */
  101. private $numStates = 0;
  102. /**
  103. * current colour for fill operations, defaults to inactive value, all three components should be between 0 and 1 inclusive when active
  104. */
  105. public $currentColour = null;
  106. /**
  107. * current colour for stroke operations (lines etc.)
  108. */
  109. public $currentStrokeColour = null;
  110. /**
  111. * current style that lines are drawn in
  112. */
  113. public $currentLineStyle = '';
  114. /**
  115. * current line transparency (partial graphics state)
  116. */
  117. public $currentLineTransparency = array("mode" => "Normal", "opacity" => 1.0);
  118. /**
  119. * current fill transparency (partial graphics state)
  120. */
  121. public $currentFillTransparency = array("mode" => "Normal", "opacity" => 1.0);
  122. /**
  123. * an array which is used to save the state of the document, mainly the colours and styles
  124. * it is used to temporarily change to another state, the change back to what it was before
  125. */
  126. public $stateStack = array();
  127. /**
  128. * number of elements within the state stack
  129. */
  130. public $nStateStack = 0;
  131. /**
  132. * number of page objects within the document
  133. */
  134. public $numPages = 0;
  135. /**
  136. * object Id storage stack
  137. */
  138. public $stack = array();
  139. /**
  140. * number of elements within the object Id storage stack
  141. */
  142. public $nStack = 0;
  143. /**
  144. * an array which contains information about the objects which are not firmly attached to pages
  145. * these have been added with the addObject function
  146. */
  147. public $looseObjects = array();
  148. /**
  149. * array contains infomation about how the loose objects are to be added to the document
  150. */
  151. public $addLooseObjects = array();
  152. /**
  153. * the objectId of the information object for the document
  154. * this contains authorship, title etc.
  155. */
  156. public $infoObject = 0;
  157. /**
  158. * number of images being tracked within the document
  159. */
  160. public $numImages = 0;
  161. /**
  162. * an array containing options about the document
  163. * it defaults to turning on the compression of the objects
  164. */
  165. public $options = array('compression'=>1);
  166. /**
  167. * the objectId of the first page of the document
  168. */
  169. public $firstPageId;
  170. /**
  171. * used to track the last used value of the inter-word spacing, this is so that it is known
  172. * when the spacing is changed.
  173. */
  174. public $wordSpaceAdjust = 0;
  175. /**
  176. * the object Id of the procset object
  177. */
  178. public $procsetObjectId;
  179. /**
  180. * store the information about the relationship between font families
  181. * this used so that the code knows which font is the bold version of another font, etc.
  182. * the value of this array is initialised in the constuctor function.
  183. */
  184. public $fontFamilies = array();
  185. /**
  186. * folder for php serialized formats of font metrics files.
  187. * If empty string, use same folder as original metrics files.
  188. * This can be passed in from class creator.
  189. * If this folder does not exist or is not writable, Cpdf will be **much** slower.
  190. * Because of potential trouble with php safe mode, folder cannot be created at runtime.
  191. */
  192. public $fontcache = '';
  193. /**
  194. * The version of the font metrics cache file.
  195. * This value must be manually incremented whenever the internal font data structure is modified.
  196. */
  197. public $fontcacheVersion = 2;
  198. /**
  199. * temporary folder.
  200. * If empty string, will attempty system tmp folder.
  201. * This can be passed in from class creator.
  202. * Only used for conversion of gd images to jpeg images.
  203. */
  204. public $tmp = '';
  205. /**
  206. * track if the current font is bolded or italicised
  207. */
  208. public $currentTextState = '';
  209. /**
  210. * messages are stored here during processing, these can be selected afterwards to give some useful debug information
  211. */
  212. public $messages = '';
  213. /**
  214. * the ancryption array for the document encryption is stored here
  215. */
  216. public $arc4 = '';
  217. /**
  218. * the object Id of the encryption information
  219. */
  220. public $arc4_objnum = 0;
  221. /**
  222. * the file identifier, used to uniquely identify a pdf document
  223. */
  224. public $fileIdentifier = '';
  225. /**
  226. * a flag to say if a document is to be encrypted or not
  227. */
  228. public $encrypted = 0;
  229. /**
  230. * the ancryption key for the encryption of all the document content (structure is not encrypted)
  231. */
  232. public $encryptionKey = '';
  233. /**
  234. * array which forms a stack to keep track of nested callback functions
  235. */
  236. public $callback = array();
  237. /**
  238. * the number of callback functions in the callback array
  239. */
  240. public $nCallback = 0;
  241. /**
  242. * store label->id pairs for named destinations, these will be used to replace internal links
  243. * done this way so that destinations can be defined after the location that links to them
  244. */
  245. public $destinations = array();
  246. /**
  247. * store the stack for the transaction commands, each item in here is a record of the values of all the
  248. * publiciables within the class, so that the user can rollback at will (from each 'start' command)
  249. * note that this includes the objects array, so these can be large.
  250. */
  251. public $checkpoint = '';
  252. /* Table of Image origin filenames and image labels which were already added with o_image().
  253. * Allows to merge identical images
  254. */
  255. public $imagelist = array();
  256. /**
  257. * whether the text passed in should be treated as Unicode or just local character set.
  258. */
  259. public $isUnicode = false;
  260. /**
  261. * @var string the JavaScript code of the document
  262. */
  263. public $javascript = '';
  264. /**
  265. * @var boolean whether the compression is possible
  266. */
  267. protected $compressionReady = false;
  268. /**
  269. * class constructor
  270. * this will start a new document
  271. * @var array array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
  272. * @var boolean whether text will be treated as Unicode or not.
  273. */
  274. function Cpdf ($pageSize = array(0, 0, 612, 792), $isUnicode = false, $fontcache = '', $tmp = '') {
  275. $this->isUnicode = $isUnicode;
  276. $this->fontcache = $fontcache;
  277. $this->tmp = $tmp;
  278. $this->newDocument($pageSize);
  279. $this->compressionReady = function_exists('gzcompress');
  280. // also initialize the font families that are known about already
  281. $this->setFontFamily('init');
  282. // $this->fileIdentifier = md5('xxxxxxxx'.time());
  283. }
  284. /**
  285. * Class destructor
  286. */
  287. function __destruct() {
  288. // @BLU_MOD
  289. // clear_object($this);
  290. unset($this->objects);
  291. unset($this->fonts);
  292. unset($this->stateStack);
  293. unset($this->stack);
  294. unset($this->callback);
  295. unset($this->destinations);
  296. unset($this->checkpoint);
  297. // @END_BLU_MOD
  298. }
  299. /**
  300. * Document object methods (internal use only)
  301. *
  302. * There is about one object method for each type of object in the pdf document
  303. * Each function has the same call list ($id,$action,$options).
  304. * $id = the object ID of the object, or what it is to be if it is being created
  305. * $action = a string specifying the action to be performed, though ALL must support:
  306. * 'new' - create the object with the id $id
  307. * 'out' - produce the output for the pdf object
  308. * $options = optional, a string or array containing the various parameters for the object
  309. *
  310. * These, in conjunction with the output function are the ONLY way for output to be produced
  311. * within the pdf 'file'.
  312. */
  313. /**
  314. *destination object, used to specify the location for the user to jump to, presently on opening
  315. */
  316. protected function o_destination($id, $action, $options = '') {
  317. if ($action !== 'new') {
  318. $o = & $this->objects[$id];
  319. }
  320. switch ($action) {
  321. case 'new':
  322. $this->objects[$id] = array('t'=>'destination', 'info'=>array());
  323. $tmp = '';
  324. switch ($options['type']) {
  325. case 'XYZ':
  326. case 'FitR':
  327. $tmp = ' '.$options['p3'].$tmp;
  328. case 'FitH':
  329. case 'FitV':
  330. case 'FitBH':
  331. case 'FitBV':
  332. $tmp = ' '.$options['p1'].' '.$options['p2'].$tmp;
  333. case 'Fit':
  334. case 'FitB':
  335. $tmp = $options['type'].$tmp;
  336. $this->objects[$id]['info']['string'] = $tmp;
  337. $this->objects[$id]['info']['page'] = $options['page'];
  338. }
  339. break;
  340. case 'out':
  341. $tmp = $o['info'];
  342. $res = "\n$id 0 obj\n".'['.$tmp['page'].' 0 R /'.$tmp['string']."]\nendobj";
  343. return $res;
  344. }
  345. }
  346. /**
  347. * set the viewer preferences
  348. */
  349. protected function o_viewerPreferences($id, $action, $options = '') {
  350. if ($action !== 'new') {
  351. $o = & $this->objects[$id];
  352. }
  353. switch ($action) {
  354. case 'new':
  355. $this->objects[$id] = array('t'=>'viewerPreferences', 'info'=>array());
  356. break;
  357. case 'add':
  358. foreach($options as $k=>$v) {
  359. switch ($k) {
  360. case 'HideToolbar':
  361. case 'HideMenubar':
  362. case 'HideWindowUI':
  363. case 'FitWindow':
  364. case 'CenterWindow':
  365. case 'NonFullScreenPageMode':
  366. case 'Direction':
  367. $o['info'][$k] = $v;
  368. break;
  369. }
  370. }
  371. break;
  372. case 'out':
  373. $res = "\n$id 0 obj\n<< ";
  374. foreach($o['info'] as $k=>$v) {
  375. $res.= "\n/$k $v";
  376. }
  377. $res.= "\n>>\n";
  378. return $res;
  379. }
  380. }
  381. /**
  382. * define the document catalog, the overall controller for the document
  383. */
  384. protected function o_catalog($id, $action, $options = '') {
  385. if ($action !== 'new') {
  386. $o = & $this->objects[$id];
  387. }
  388. switch ($action) {
  389. case 'new':
  390. $this->objects[$id] = array('t'=>'catalog', 'info'=>array());
  391. $this->catalogId = $id;
  392. break;
  393. case 'outlines':
  394. case 'pages':
  395. case 'openHere':
  396. case 'javascript':
  397. $o['info'][$action] = $options;
  398. break;
  399. case 'viewerPreferences':
  400. if (!isset($o['info']['viewerPreferences'])) {
  401. $this->numObj++;
  402. $this->o_viewerPreferences($this->numObj, 'new');
  403. $o['info']['viewerPreferences'] = $this->numObj;
  404. }
  405. $vp = $o['info']['viewerPreferences'];
  406. $this->o_viewerPreferences($vp, 'add', $options);
  407. break;
  408. case 'out':
  409. $res = "\n$id 0 obj\n<< /Type /Catalog";
  410. foreach($o['info'] as $k=>$v) {
  411. switch ($k) {
  412. case 'outlines':
  413. $res.= "\n/Outlines $v 0 R";
  414. break;
  415. case 'pages':
  416. $res.= "\n/Pages $v 0 R";
  417. break;
  418. case 'viewerPreferences':
  419. $res.= "\n/ViewerPreferences $v 0 R";
  420. break;
  421. case 'openHere':
  422. $res.= "\n/OpenAction $v 0 R";
  423. break;
  424. case 'javascript':
  425. $res.= "\n/Names <</JavaScript $v 0 R>>";
  426. break;
  427. }
  428. }
  429. $res.= " >>\nendobj";
  430. return $res;
  431. }
  432. }
  433. /**
  434. * object which is a parent to the pages in the document
  435. */
  436. protected function o_pages($id, $action, $options = '') {
  437. if ($action !== 'new') {
  438. $o = & $this->objects[$id];
  439. }
  440. switch ($action) {
  441. case 'new':
  442. $this->objects[$id] = array('t'=>'pages', 'info'=>array());
  443. $this->o_catalog($this->catalogId, 'pages', $id);
  444. break;
  445. case 'page':
  446. if (!is_array($options)) {
  447. // then it will just be the id of the new page
  448. $o['info']['pages'][] = $options;
  449. } else {
  450. // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
  451. // and pos is either 'before' or 'after', saying where this page will fit.
  452. if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) {
  453. $i = array_search($options['rid'], $o['info']['pages']);
  454. if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i] == $options['rid']) {
  455. // then there is a match
  456. // make a space
  457. switch ($options['pos']) {
  458. case 'before':
  459. $k = $i;
  460. break;
  461. case 'after':
  462. $k = $i+1;
  463. break;
  464. default:
  465. $k = -1;
  466. break;
  467. }
  468. if ($k >= 0) {
  469. for ($j = count($o['info']['pages']) -1;$j >= $k;$j--) {
  470. $o['info']['pages'][$j+1] = $o['info']['pages'][$j];
  471. }
  472. $o['info']['pages'][$k] = $options['id'];
  473. }
  474. }
  475. }
  476. }
  477. break;
  478. case 'procset':
  479. $o['info']['procset'] = $options;
  480. break;
  481. case 'mediaBox':
  482. $o['info']['mediaBox'] = $options;
  483. // which should be an array of 4 numbers
  484. break;
  485. case 'font':
  486. $o['info']['fonts'][] = array('objNum'=>$options['objNum'], 'fontNum'=>$options['fontNum']);
  487. break;
  488. case 'extGState':
  489. $o['info']['extGStates'][] = array('objNum' => $options['objNum'], 'stateNum' => $options['stateNum']);
  490. break;
  491. case 'xObject':
  492. $o['info']['xObjects'][] = array('objNum'=>$options['objNum'], 'label'=>$options['label']);
  493. break;
  494. case 'out':
  495. if (count($o['info']['pages'])) {
  496. $res = "\n$id 0 obj\n<< /Type /Pages\n/Kids [";
  497. foreach($o['info']['pages'] as $v) {
  498. $res.= "$v 0 R\n";
  499. }
  500. $res.= "]\n/Count ".count($this->objects[$id]['info']['pages']);
  501. if ( (isset($o['info']['fonts']) && count($o['info']['fonts'])) ||
  502. isset($o['info']['procset']) ||
  503. (isset($o['info']['extGStates']) && count($o['info']['extGStates']))) {
  504. $res.= "\n/Resources <<";
  505. if (isset($o['info']['procset'])) {
  506. $res.= "\n/ProcSet ".$o['info']['procset']." 0 R";
  507. }
  508. if (isset($o['info']['fonts']) && count($o['info']['fonts'])) {
  509. $res.= "\n/Font << ";
  510. foreach($o['info']['fonts'] as $finfo) {
  511. $res.= "\n/F".$finfo['fontNum']." ".$finfo['objNum']." 0 R";
  512. }
  513. $res.= "\n>>";
  514. }
  515. if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) {
  516. $res.= "\n/XObject << ";
  517. foreach($o['info']['xObjects'] as $finfo) {
  518. $res.= "\n/".$finfo['label']." ".$finfo['objNum']." 0 R";
  519. }
  520. $res.= "\n>>";
  521. }
  522. if ( isset($o['info']['extGStates']) && count($o['info']['extGStates'])) {
  523. $res.= "\n/ExtGState << ";
  524. foreach ($o['info']['extGStates'] as $gstate) {
  525. $res.= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
  526. }
  527. $res.= "\n>>";
  528. }
  529. $res.= "\n>>";
  530. if (isset($o['info']['mediaBox'])) {
  531. $tmp = $o['info']['mediaBox'];
  532. $res.= "\n/MediaBox [".sprintf('%.3F %.3F %.3F %.3F', $tmp[0], $tmp[1], $tmp[2], $tmp[3]) .']';
  533. }
  534. }
  535. $res.= "\n >>\nendobj";
  536. } else {
  537. $res = "\n$id 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
  538. }
  539. return $res;
  540. }
  541. }
  542. /**
  543. * define the outlines in the doc, empty for now
  544. */
  545. protected function o_outlines($id, $action, $options = '') {
  546. if ($action !== 'new') {
  547. $o = & $this->objects[$id];
  548. }
  549. switch ($action) {
  550. case 'new':
  551. $this->objects[$id] = array('t'=>'outlines', 'info'=>array('outlines'=>array()));
  552. $this->o_catalog($this->catalogId, 'outlines', $id);
  553. break;
  554. case 'outline':
  555. $o['info']['outlines'][] = $options;
  556. break;
  557. case 'out':
  558. if (count($o['info']['outlines'])) {
  559. $res = "\n$id 0 obj\n<< /Type /Outlines /Kids [";
  560. foreach($o['info']['outlines'] as $v) {
  561. $res.= "$v 0 R ";
  562. }
  563. $res.= "] /Count ".count($o['info']['outlines']) ." >>\nendobj";
  564. } else {
  565. $res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
  566. }
  567. return $res;
  568. }
  569. }
  570. /**
  571. * an object to hold the font description
  572. */
  573. protected function o_font($id, $action, $options = '') {
  574. if ($action !== 'new') {
  575. $o = & $this->objects[$id];
  576. }
  577. switch ($action) {
  578. case 'new':
  579. $this->objects[$id] = array('t' => 'font', 'info' => array('name' => $options['name'], 'fontFileName' => $options['fontFileName'], 'SubType' => 'Type1'));
  580. $fontNum = $this->numFonts;
  581. $this->objects[$id]['info']['fontNum'] = $fontNum;
  582. // deal with the encoding and the differences
  583. if (isset($options['differences'])) {
  584. // then we'll need an encoding dictionary
  585. $this->numObj++;
  586. $this->o_fontEncoding($this->numObj, 'new', $options);
  587. $this->objects[$id]['info']['encodingDictionary'] = $this->numObj;
  588. } else if (isset($options['encoding'])) {
  589. // we can specify encoding here
  590. switch ($options['encoding']) {
  591. case 'WinAnsiEncoding':
  592. case 'MacRomanEncoding':
  593. case 'MacExpertEncoding':
  594. $this->objects[$id]['info']['encoding'] = $options['encoding'];
  595. break;
  596. case 'none':
  597. break;
  598. default:
  599. $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
  600. break;
  601. }
  602. } else {
  603. $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
  604. }
  605. if ($this->fonts[$options['fontFileName']]['isUnicode']) {
  606. // For Unicode fonts, we need to incorporate font data into
  607. // sub-sections that are linked from the primary font section.
  608. // Look at o_fontGIDtoCID and o_fontDescendentCID functions
  609. // for more informaiton.
  610. //
  611. // All of this code is adapted from the excellent changes made to
  612. // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
  613. $toUnicodeId = ++$this->numObj;
  614. $this->o_contents($toUnicodeId, 'new', 'raw');
  615. $this->objects[$id]['info']['toUnicode'] = $toUnicodeId;
  616. $stream = "/CIDInit /ProcSet findresource begin\n";
  617. $stream.= "12 dict begin\n";
  618. $stream.= "begincmap\n";
  619. $stream.= "/CIDSystemInfo\n";
  620. $stream.= "<</Registry (Adobe)\n";
  621. $stream.= "/Ordering (UCS)\n";
  622. $stream.= "/Supplement 0\n";
  623. $stream.= ">> def\n";
  624. $stream.= "/CMapName /Adobe-Identity-UCS def\n";
  625. $stream.= "/CMapType 2 def\n";
  626. $stream.= "1 begincodespacerange\n";
  627. $stream.= "<0000> <FFFF>\n";
  628. $stream.= "endcodespacerange\n";
  629. $stream.= "1 beginbfrange\n";
  630. $stream.= "<0000> <FFFF> <0000>\n";
  631. $stream.= "endbfrange\n";
  632. $stream.= "endcmap\n";
  633. $stream.= "CMapName currentdict /CMap defineresource pop\n";
  634. $stream.= "end\n";
  635. $stream.= "end\n";
  636. $res = "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
  637. $res .= "stream\n" . $stream . "endstream";
  638. $this->objects[$toUnicodeId]['c'] = $res;
  639. $cidFontId = ++$this->numObj;
  640. $this->o_fontDescendentCID($cidFontId, 'new', $options);
  641. $this->objects[$id]['info']['cidFont'] = $cidFontId;
  642. }
  643. // also tell the pages node about the new font
  644. $this->o_pages($this->currentNode, 'font', array('fontNum' => $fontNum, 'objNum' => $id));
  645. break;
  646. case 'add':
  647. foreach ($options as $k => $v) {
  648. switch ($k) {
  649. case 'BaseFont':
  650. $o['info']['name'] = $v;
  651. break;
  652. case 'FirstChar':
  653. case 'LastChar':
  654. case 'Widths':
  655. case 'FontDescriptor':
  656. case 'SubType':
  657. $this->addMessage('o_font '.$k." : ".$v);
  658. $o['info'][$k] = $v;
  659. break;
  660. }
  661. }
  662. // pass values down to descendent font
  663. if (isset($o['info']['cidFont'])) {
  664. $this->o_fontDescendentCID($o['info']['cidFont'], 'add', $options);
  665. }
  666. break;
  667. case 'out':
  668. if ($this->fonts[$this->objects[$id]['info']['fontFileName']]['isUnicode']) {
  669. // For Unicode fonts, we need to incorporate font data into
  670. // sub-sections that are linked from the primary font section.
  671. // Look at o_fontGIDtoCID and o_fontDescendentCID functions
  672. // for more informaiton.
  673. //
  674. // All of this code is adapted from the excellent changes made to
  675. // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
  676. $res = "\n$id 0 obj\n<</Type /Font\n/Subtype /Type0\n";
  677. $res.= "/BaseFont /".$o['info']['name']."\n";
  678. // The horizontal identity mapping for 2-byte CIDs; may be used
  679. // with CIDFonts using any Registry, Ordering, and Supplement values.
  680. $res.= "/Encoding /Identity-H\n";
  681. $res.= "/DescendantFonts [".$o['info']['cidFont']." 0 R]\n";
  682. $res.= "/ToUnicode ".$o['info']['toUnicode']." 0 R\n";
  683. $res.= ">>\n";
  684. $res.= "endobj";
  685. } else {
  686. $res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /".$o['info']['SubType']."\n";
  687. $res.= "/Name /F".$o['info']['fontNum']."\n";
  688. $res.= "/BaseFont /".$o['info']['name']."\n";
  689. if (isset($o['info']['encodingDictionary'])) {
  690. // then place a reference to the dictionary
  691. $res.= "/Encoding ".$o['info']['encodingDictionary']." 0 R\n";
  692. } else if (isset($o['info']['encoding'])) {
  693. // use the specified encoding
  694. $res.= "/Encoding /".$o['info']['encoding']."\n";
  695. }
  696. if (isset($o['info']['FirstChar'])) {
  697. $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
  698. }
  699. if (isset($o['info']['LastChar'])) {
  700. $res.= "/LastChar ".$o['info']['LastChar']."\n";
  701. }
  702. if (isset($o['info']['Widths'])) {
  703. $res.= "/Widths ".$o['info']['Widths']." 0 R\n";
  704. }
  705. if (isset($o['info']['FontDescriptor'])) {
  706. $res.= "/FontDescriptor ".$o['info']['FontDescriptor']." 0 R\n";
  707. }
  708. $res.= ">>\n";
  709. $res.= "endobj";
  710. }
  711. return $res;
  712. }
  713. }
  714. /**
  715. * a font descriptor, needed for including additional fonts
  716. */
  717. protected function o_fontDescriptor($id, $action, $options = '') {
  718. if ($action !== 'new') {
  719. $o = & $this->objects[$id];
  720. }
  721. switch ($action) {
  722. case 'new':
  723. $this->objects[$id] = array('t'=>'fontDescriptor', 'info'=>$options);
  724. break;
  725. case 'out':
  726. $res = "\n$id 0 obj\n<< /Type /FontDescriptor\n";
  727. foreach ($o['info'] as $label => $value) {
  728. switch ($label) {
  729. case 'Ascent':
  730. case 'CapHeight':
  731. case 'Descent':
  732. case 'Flags':
  733. case 'ItalicAngle':
  734. case 'StemV':
  735. case 'AvgWidth':
  736. case 'Leading':
  737. case 'MaxWidth':
  738. case 'MissingWidth':
  739. case 'StemH':
  740. case 'XHeight':
  741. case 'CharSet':
  742. if (mb_strlen($value, '8bit')) {
  743. $res.= "/$label $value\n";
  744. }
  745. break;
  746. case 'FontFile':
  747. case 'FontFile2':
  748. case 'FontFile3':
  749. $res.= "/$label $value 0 R\n";
  750. break;
  751. case 'FontBBox':
  752. $res.= "/$label [".$value[0].' '.$value[1].' '.$value[2].' '.$value[3]."]\n";
  753. break;
  754. case 'FontName':
  755. $res.= "/$label /$value\n";
  756. break;
  757. }
  758. }
  759. $res.= ">>\nendobj";
  760. return $res;
  761. }
  762. }
  763. /**
  764. * the font encoding
  765. */
  766. protected function o_fontEncoding($id, $action, $options = '') {
  767. if ($action !== 'new') {
  768. $o = & $this->objects[$id];
  769. }
  770. switch ($action) {
  771. case 'new':
  772. // the options array should contain 'differences' and maybe 'encoding'
  773. $this->objects[$id] = array('t'=>'fontEncoding', 'info'=>$options);
  774. break;
  775. case 'out':
  776. $res = "\n$id 0 obj\n<< /Type /Encoding\n";
  777. if (!isset($o['info']['encoding'])) {
  778. $o['info']['encoding'] = 'WinAnsiEncoding';
  779. }
  780. if ($o['info']['encoding'] !== 'none') {
  781. $res.= "/BaseEncoding /".$o['info']['encoding']."\n";
  782. }
  783. $res.= "/Differences \n[";
  784. $onum = -100;
  785. foreach($o['info']['differences'] as $num=>$label) {
  786. if ($num != $onum+1) {
  787. // we cannot make use of consecutive numbering
  788. $res.= "\n$num /$label";
  789. } else {
  790. $res.= " /$label";
  791. }
  792. $onum = $num;
  793. }
  794. $res.= "\n]\n>>\nendobj";
  795. return $res;
  796. }
  797. }
  798. /**
  799. * a descendent cid font, needed for unicode fonts
  800. */
  801. protected function o_fontDescendentCID($id, $action, $options = '') {
  802. if ($action !== 'new') {
  803. $o = & $this->objects[$id];
  804. }
  805. switch ($action) {
  806. case 'new':
  807. $this->objects[$id] = array('t'=>'fontDescendentCID', 'info'=>$options);
  808. // we need a CID system info section
  809. $cidSystemInfoId = ++$this->numObj;
  810. $this->o_contents($cidSystemInfoId, 'new', 'raw');
  811. $this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId;
  812. $res= "<</Registry (Adobe)\n"; // A string identifying an issuer of character collections
  813. $res.= "/Ordering (UCS)\n"; // A string that uniquely names a character collection issued by a specific registry
  814. $res.= "/Supplement 0\n"; // The supplement number of the character collection.
  815. $res.= ">>";
  816. $this->objects[$cidSystemInfoId]['c'] = $res;
  817. // and a CID to GID map
  818. $cidToGidMapId = ++$this->numObj;
  819. $this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options);
  820. $this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId;
  821. break;
  822. case 'add':
  823. foreach ($options as $k => $v) {
  824. switch ($k) {
  825. case 'BaseFont':
  826. $o['info']['name'] = $v;
  827. break;
  828. case 'FirstChar':
  829. case 'LastChar':
  830. case 'MissingWidth':
  831. case 'FontDescriptor':
  832. case 'SubType':
  833. $this->addMessage("o_fontDescendentCID $k : $v");
  834. $o['info'][$k] = $v;
  835. break;
  836. }
  837. }
  838. // pass values down to cid to gid map
  839. $this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options);
  840. break;
  841. case 'out':
  842. $res = "\n$id 0 obj\n";
  843. $res.= "<</Type /Font\n";
  844. $res.= "/Subtype /CIDFontType2\n";
  845. $res.= "/BaseFont /".$o['info']['name']."\n";
  846. $res.= "/CIDSystemInfo ".$o['info']['cidSystemInfo']." 0 R\n";
  847. // if (isset($o['info']['FirstChar'])) {
  848. // $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
  849. // }
  850. // if (isset($o['info']['LastChar'])) {
  851. // $res.= "/LastChar ".$o['info']['LastChar']."\n";
  852. // }
  853. if (isset($o['info']['FontDescriptor'])) {
  854. $res.= "/FontDescriptor ".$o['info']['FontDescriptor']." 0 R\n";
  855. }
  856. if (isset($o['info']['MissingWidth'])) {
  857. $res.= "/DW ".$o['info']['MissingWidth']."\n";
  858. }
  859. if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) {
  860. $cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths'];
  861. $w = '';
  862. foreach ($cid_widths as $cid => $width) {
  863. $w .= $cid.' ['.$width.'] ';
  864. }
  865. $res.= "/W [$w]\n";
  866. }
  867. $res.= "/CIDToGIDMap ".$o['info']['cidToGidMap']." 0 R\n";
  868. $res.= ">>\n";
  869. $res.= "endobj";
  870. return $res;
  871. }
  872. }
  873. /**
  874. * a font glyph to character map, needed for unicode fonts
  875. */
  876. protected function o_fontGIDtoCIDMap($id, $action, $options = '') {
  877. if ($action !== 'new') {
  878. $o = & $this->objects[$id];
  879. }
  880. switch ($action) {
  881. case 'new':
  882. $this->objects[$id] = array('t'=>'fontGIDtoCIDMap', 'info'=>$options);
  883. break;
  884. case 'out':
  885. $res = "\n$id 0 obj\n";
  886. $tmp = $this->fonts[$o['info']['fontFileName']]['CIDtoGID'] = base64_decode($this->fonts[$o['info']['fontFileName']]['CIDtoGID']);
  887. $compressed = isset($this->fonts[$o['info']['fontFileName']]['CIDtoGID_Compressed']) &&
  888. $this->fonts[$o['info']['fontFileName']]['CIDtoGID_Compressed'];
  889. if (!$compressed && isset($o['raw'])) {
  890. $res.= $tmp;
  891. } else {
  892. $res.= "<<";
  893. if (!$compressed && $this->compressionReady && $this->options['compression']) {
  894. // then implement ZLIB based compression on this content stream
  895. $compressed = true;
  896. $tmp = gzcompress($tmp, 6);
  897. }
  898. if ($compressed) {
  899. $res.= "\n/Filter /FlateDecode";
  900. }
  901. $res.= "\n/Length ".mb_strlen($tmp, '8bit') .">>\nstream\n$tmp\nendstream";
  902. }
  903. $res.= "\nendobj";
  904. return $res;
  905. }
  906. }
  907. /**
  908. * the document procset, solves some problems with printing to old PS printers
  909. */
  910. protected function o_procset($id, $action, $options = '') {
  911. if ($action !== 'new') {
  912. $o = & $this->objects[$id];
  913. }
  914. switch ($action) {
  915. case 'new':
  916. $this->objects[$id] = array('t'=>'procset', 'info'=>array('PDF'=>1, 'Text'=>1));
  917. $this->o_pages($this->currentNode, 'procset', $id);
  918. $this->procsetObjectId = $id;
  919. break;
  920. case 'add':
  921. // this is to add new items to the procset list, despite the fact that this is considered
  922. // obselete, the items are required for printing to some postscript printers
  923. switch ($options) {
  924. case 'ImageB':
  925. case 'ImageC':
  926. case 'ImageI':
  927. $o['info'][$options] = 1;
  928. break;
  929. }
  930. break;
  931. case 'out':
  932. $res = "\n$id 0 obj\n[";
  933. foreach ($o['info'] as $label=>$val) {
  934. $res.= "/$label ";
  935. }
  936. $res.= "]\nendobj";
  937. return $res;
  938. }
  939. }
  940. /**
  941. * define the document information
  942. */
  943. protected function o_info($id, $action, $options = '') {
  944. if ($action !== 'new') {
  945. $o = & $this->objects[$id];
  946. }
  947. switch ($action) {
  948. case 'new':
  949. $this->infoObject = $id;
  950. $date = 'D:'.@date('Ymd');
  951. $this->objects[$id] = array('t'=>'info', 'info'=>array('Creator'=>'R and OS php pdf writer, http://www.ros.co.nz', 'CreationDate'=>$date));
  952. break;
  953. case 'Title':
  954. case 'Author':
  955. case 'Subject':
  956. case 'Keywords':
  957. case 'Creator':
  958. case 'Producer':
  959. case 'CreationDate':
  960. case 'ModDate':
  961. case 'Trapped':
  962. $o['info'][$action] = $options;
  963. break;
  964. case 'out':
  965. if ($this->encrypted) {
  966. $this->encryptInit($id);
  967. }
  968. $res = "\n$id 0 obj\n<<\n";
  969. foreach ($o['info'] as $k=>$v) {
  970. $res.= "/$k (";
  971. // dates must be outputted as-is, without Unicode transformations
  972. $raw = ($k === 'CreationDate' || $k === 'ModDate');
  973. $c = $v;
  974. if ($this->encrypted) {
  975. $c = $this->ARC4($c);
  976. }
  977. $res.= ($raw) ? $c : $this->filterText($c);
  978. $res.= ")\n";
  979. }
  980. $res.= ">>\nendobj";
  981. return $res;
  982. }
  983. }
  984. /**
  985. * an action object, used to link to URLS initially
  986. */
  987. protected function o_action($id, $action, $options = '') {
  988. if ($action !== 'new') {
  989. $o = & $this->objects[$id];
  990. }
  991. switch ($action) {
  992. case 'new':
  993. if (is_array($options)) {
  994. $this->objects[$id] = array('t'=>'action', 'info'=>$options, 'type'=>$options['type']);
  995. } else {
  996. // then assume a URI action
  997. $this->objects[$id] = array('t'=>'action', 'info'=>$options, 'type'=>'URI');
  998. }
  999. break;
  1000. case 'out':
  1001. if ($this->encrypted) {
  1002. $this->encryptInit($id);
  1003. }
  1004. $res = "\n$id 0 obj\n<< /Type /Action";
  1005. switch ($o['type']) {
  1006. case 'ilink':
  1007. // there will be an 'label' setting, this is the name of the destination
  1008. $res.= "\n/S /GoTo\n/D ".$this->destinations[(string)$o['info']['label']]." 0 R";
  1009. break;
  1010. case 'URI':
  1011. $res.= "\n/S /URI\n/URI (";
  1012. if ($this->encrypted) {
  1013. $res.= $this->filterText($this->ARC4($o['info']));
  1014. } else {
  1015. $res.= $this->filterText($o['info']);
  1016. }
  1017. $res.= ")";
  1018. break;
  1019. }
  1020. $res.= "\n>>\nendobj";
  1021. return $res;
  1022. }
  1023. }
  1024. /**
  1025. * an annotation object, this will add an annotation to the current page.
  1026. * initially will support just link annotations
  1027. */
  1028. protected function o_annotation($id, $action, $options = '') {
  1029. if ($action !== 'new') {
  1030. $o = & $this->objects[$id];
  1031. }
  1032. switch ($action) {
  1033. case 'new':
  1034. // add the annotation to the current page
  1035. $pageId = $this->currentPage;
  1036. $this->o_page($pageId, 'annot', $id);
  1037. // and add the action object which is going to be required
  1038. switch ($options['type']) {
  1039. case 'link':
  1040. $this->objects[$id] = array('t'=>'annotation', 'info'=>$options);
  1041. $this->numObj++;
  1042. $this->o_action($this->numObj, 'new', $options['url']);
  1043. $this->objects[$id]['info']['actionId'] = $this->numObj;
  1044. break;
  1045. case 'ilink':
  1046. // this is to a named internal link
  1047. $label = $options['label'];
  1048. $this->objects[$id] = array('t'=>'annotation', 'info'=>$options);
  1049. $this->numObj++;
  1050. $this->o_action($this->numObj, 'new', array('type'=>'ilink', 'label'=>$label));
  1051. $this->objects[$id]['info']['actionId'] = $this->numObj;
  1052. break;
  1053. }
  1054. break;
  1055. case 'out':
  1056. $res = "\n$id 0 obj\n<< /Type /Annot";
  1057. switch ($o['info']['type']) {
  1058. case 'link':
  1059. case 'ilink':
  1060. $res.= "\n/Subtype /Link";
  1061. break;
  1062. }
  1063. $res.= "\n/A ".$o['info']['actionId']." 0 R";
  1064. $res.= "\n/Border [0 0 0]";
  1065. $res.= "\n/H /I";
  1066. $res.= "\n/Rect [ ";
  1067. foreach($o['info']['rect'] as $v) {
  1068. $res.= sprintf("%.4F ", $v);
  1069. }
  1070. $res.= "]";
  1071. $res.= "\n>>\nendobj";
  1072. return $res;
  1073. }
  1074. }
  1075. /**
  1076. * a page object, it also creates a contents object to hold its contents
  1077. */
  1078. protected function o_page($id, $action, $options = '') {
  1079. if ($action !== 'new') {
  1080. $o = & $this->objects[$id];
  1081. }
  1082. switch ($action) {
  1083. case 'new':
  1084. $this->numPages++;
  1085. $this->objects[$id] = array('t'=>'page', 'info'=>array('parent'=>$this->currentNode, 'pageNum'=>$this->numPages));
  1086. if (is_array($options)) {
  1087. // then this must be a page insertion, array should contain 'rid','pos'=[before|after]
  1088. $options['id'] = $id;
  1089. $this->o_pages($this->currentNode, 'page', $options);
  1090. } else {
  1091. $this->o_pages($this->currentNode, 'page', $id);
  1092. }
  1093. $this->currentPage = $id;
  1094. //make a contents object to go with this page
  1095. $this->numObj++;
  1096. $this->o_contents($this->numObj, 'new', $id);
  1097. $this->currentContents = $this->numObj;
  1098. $this->objects[$id]['info']['contents'] = array();
  1099. $this->objects[$id]['info']['contents'][] = $this->numObj;
  1100. $match = ($this->numPages%2 ? 'odd' : 'even');
  1101. foreach($this->addLooseObjects as $oId=>$target) {
  1102. if ($target === 'all' || $match === $target) {
  1103. $this->objects[$id]['info']['contents'][] = $oId;
  1104. }
  1105. }
  1106. break;
  1107. case 'content':
  1108. $o['info']['contents'][] = $options;
  1109. break;
  1110. case 'annot':
  1111. // add an annotation to this page
  1112. if (!isset($o['info']['annot'])) {
  1113. $o['info']['annot'] = array();
  1114. }
  1115. // $options should contain the id of the annotation dictionary
  1116. $o['info']['annot'][] = $options;
  1117. break;
  1118. case 'out':
  1119. $res = "\n$id 0 obj\n<< /Type /Page";
  1120. $res.= "\n/Parent ".$o['info']['parent']." 0 R";
  1121. if (isset($o['info']['annot'])) {
  1122. $res.= "\n/Annots [";
  1123. foreach($o['info']['annot'] as $aId) {
  1124. $res.= " $aId 0 R";
  1125. }
  1126. $res.= " ]";
  1127. }
  1128. $count = count($o['info']['contents']);
  1129. if ($count == 1) {
  1130. $res.= "\n/Contents ".$o['info']['contents'][0]." 0 R";
  1131. } else if ($count>1) {
  1132. $res.= "\n/Contents [\n";
  1133. // reverse the page contents so added objects are below normal content
  1134. //foreach (array_reverse($o['info']['contents']) as $cId) {
  1135. // Back to normal now that I've got transparency working --Benj
  1136. foreach ($o['info']['contents'] as $cId) {
  1137. $res.= "$cId 0 R\n";
  1138. }
  1139. $res.= "]";
  1140. }
  1141. $res.= "\n>>\nendobj";
  1142. return $res;
  1143. }
  1144. }
  1145. /**
  1146. * the contents objects hold all of the content which appears on pages
  1147. */
  1148. protected function o_contents($id, $action, $options = '') {
  1149. if ($action !== 'new') {
  1150. $o = & $this->objects[$id];
  1151. }
  1152. switch ($action) {
  1153. case 'new':
  1154. $this->objects[$id] = array('t'=>'contents', 'c'=>'', 'info'=>array());
  1155. if (mb_strlen($options, '8bit') && intval($options)) {
  1156. // then this contents is the primary for a page
  1157. $this->objects[$id]['onPage'] = $options;
  1158. } else if ($options === 'raw') {
  1159. // then this page contains some other type of system object
  1160. $this->objects[$id]['raw'] = 1;
  1161. }
  1162. break;
  1163. case 'add':
  1164. // add more options to the decleration
  1165. foreach ($options as $k=>$v) {
  1166. $o['info'][$k] = $v;
  1167. }
  1168. case 'out':
  1169. $tmp = $o['c'];
  1170. $res = "\n$id 0 obj\n";
  1171. if (isset($this->objects[$id]['raw'])) {
  1172. $res.= $tmp;
  1173. } else {
  1174. $res.= "<<";
  1175. if ($this->compressionReady && $this->options['compression']) {
  1176. // then implement ZLIB based compression on this content stream
  1177. $res.= " /Filter /FlateDecode";
  1178. $tmp = gzcompress($tmp, 6);
  1179. }
  1180. if ($this->encrypted) {
  1181. $this->encryptInit($id);
  1182. $tmp = $this->ARC4($tmp);
  1183. }
  1184. foreach($o['info'] as $k=>$v) {
  1185. $res.= "\n/$k $v";
  1186. }
  1187. $res.= "\n/Length ".mb_strlen($tmp, '8bit') ." >>\nstream\n$tmp\nendstream";
  1188. }
  1189. $res.= "\nendobj";
  1190. return $res;
  1191. }
  1192. }
  1193. protected function o_embedjs($id, $action, $code = '') {
  1194. if ($action !== 'new') {
  1195. $o = & $this->objects[$id];
  1196. }
  1197. switch ($action) {
  1198. case 'new':
  1199. $this->objects[$id] = array('t'=>'embedjs', 'info'=>array(
  1200. 'Names' => '[(EmbeddedJS) '.($id+1).' 0 R]'
  1201. ));
  1202. break;
  1203. case 'out':
  1204. $res = "\n$id 0 obj\n<< ";
  1205. foreach($o['info'] as $k=>$v) {
  1206. $res.= "\n/$k $v";
  1207. }
  1208. $res.= "\n>>\nendobj";
  1209. return $res;
  1210. }
  1211. }
  1212. protected function o_javascript($id, $action, $code = '') {
  1213. if ($action !== 'new') {
  1214. $o = & $this->objects[$id];
  1215. }
  1216. switch ($action) {
  1217. case 'new':
  1218. $this->objects[$id] = array('t'=>'javascript', 'info'=>array(
  1219. 'S' => '/JavaScript',
  1220. 'JS' => '('.$this->filterText($code).')',
  1221. ));
  1222. break;
  1223. case 'out':
  1224. $res = "\n$id 0 obj\n<< ";
  1225. foreach($o['info'] as $k=>$v) {
  1226. $res.= "\n/$k $v";
  1227. }
  1228. $res.= "\n>>\nendobj";
  1229. return $res;
  1230. }
  1231. }
  1232. /**
  1233. * an image object, will be an XObject in the document, includes description and data
  1234. */
  1235. protected function o_image($id, $action, $options = '') {
  1236. if ($action !== 'new') {
  1237. $o = & $this->objects[$id];
  1238. }
  1239. switch ($action) {
  1240. case 'new':
  1241. // make the new object
  1242. $this->objects[$id] = array('t'=>'image', 'data'=>&$options['data'], 'info'=>array());
  1243. $info =& $this->objects[$id]['info'];
  1244. $info['Type'] = '/XObject';
  1245. $info['Subtype'] = '/Image';
  1246. $info['Width'] = $options['iw'];
  1247. $info['Height'] = $options['ih'];
  1248. if (!isset($options['type']) || $options['type'] === 'jpg') {
  1249. if (!isset($options['channels'])) {
  1250. $options['channels'] = 3;
  1251. }
  1252. switch ($options['channels']) {
  1253. case 1: $info['ColorSpace'] = '/DeviceGray'; break;
  1254. case 4: $info['ColorSpace'] = '/DeviceCMYK'; break;
  1255. default: $info['ColorSpace'] = '/DeviceRGB'; break;
  1256. }
  1257. if ($info['ColorSpace'] === '/DeviceCMYK') {
  1258. $info['Decode'] = '[1 0 1 0 1 0 1 0]';
  1259. }
  1260. $info['Filter'] = '/DCTDecode';
  1261. $info['BitsPerComponent'] = 8;
  1262. }
  1263. else if ($options['type'] === 'png') {
  1264. $info['Filter'] = '/FlateDecode';
  1265. $info['DecodeParms'] = '<< /Predictor 15 /Colors '.$options['ncolor'].' /Columns '.$options['iw'].' /BitsPerComponent '.$options['bitsPerComponent'].'>>';
  1266. if (mb_strlen($options['pdata'], '8bit')) {
  1267. $tmp = ' [ /Indexed /DeviceRGB '.(mb_strlen($options['pdata'], '8bit') /3-1) .' ';
  1268. $this->numObj++;
  1269. $this->o_contents($this->numObj, 'new');
  1270. $this->objects[$this->numObj]['c'] = $options['pdata'];
  1271. $tmp.= $this->numObj.' 0 R';
  1272. $tmp.= ' ]';
  1273. $info['ColorSpace'] = $tmp;
  1274. if (isset($options['transparency'])) {
  1275. switch ($options['transparency']['type']) {
  1276. case 'indexed':
  1277. $tmp = ' [ '.$options['transparency']['data'].' '.$options['transparency']['data'].'] ';
  1278. $info['Mask'] = $tmp;
  1279. break;
  1280. case 'color-key':
  1281. $tmp = ' [ '.
  1282. $options['transparency']['r'] . ' ' . $options['transparency']['r'] .
  1283. $options['transparency']['g'] . ' ' . $options['transparency']['g'] .
  1284. $options['transparency']['b'] . ' ' . $options['transparency']['b'] .
  1285. ' ] ';
  1286. $info['Mask'] = $tmp;
  1287. break;
  1288. }
  1289. }
  1290. } else {
  1291. if (isset($options['transparency'])) {
  1292. switch ($options['transparency']['type']) {
  1293. case 'indexed':
  1294. $tmp = ' [ '.$options['transparency']['data'].' '.$options['transparency']['data'].'] ';
  1295. $info['Mask'] = $tmp;
  1296. break;
  1297. case 'color-key':
  1298. $tmp = ' [ '.
  1299. $options['transparency']['r'] . ' ' . $options['transparency']['r'] . ' ' .
  1300. $options['transparency']['g'] . ' ' . $options['transparency']['g'] . ' ' .
  1301. $options['transparency']['b'] . ' ' . $options['transparency']['b'] .
  1302. ' ] ';
  1303. $info['Mask'] = $tmp;
  1304. break;
  1305. }
  1306. }
  1307. $info['ColorSpace'] = '/'.$options['color'];
  1308. }
  1309. $info['BitsPerComponent'] = $options['bitsPerComponent'];
  1310. }
  1311. // assign it a place in the named resource dictionary as an external object, according to
  1312. // the label passed in with it.
  1313. $this->o_pages($this->currentNode, 'xObject', array('label'=>$options['label'], 'objNum'=>$id));
  1314. // also make sure that we have the right procset object for it.
  1315. $this->o_procset($this->procsetObjectId, 'add', 'ImageC');
  1316. break;
  1317. case 'out':
  1318. $tmp = &$o['data'];
  1319. $res = "\n$id 0 obj\n<<";
  1320. foreach($o['info'] as $k=>$v) {
  1321. $res.= "\n/$k $v";
  1322. }
  1323. if ($this->encrypted) {
  1324. $this->encryptInit($id);
  1325. $tmp = $this->ARC4($tmp);
  1326. }
  1327. $res.= "\n/Length ".mb_strlen($tmp, '8bit') .">>\nstream\n$tmp\nendstream\nendobj";
  1328. return $res;
  1329. }
  1330. }
  1331. /**
  1332. * graphics state object
  1333. */
  1334. protected function o_extGState($id, $action, $options = "") {
  1335. static $valid_params = array("LW", "LC", "LC", "LJ", "ML",
  1336. "D", "RI", "OP", "op", "OPM",
  1337. "Font", "BG", "BG2", "UCR",
  1338. "TR", "TR2", "HT", "FL",
  1339. "SM", "SA", "BM", "SMask",
  1340. "CA", "ca", "AIS", "TK");
  1341. if ($action !== "new") {
  1342. $o = & $this->objects[$id];
  1343. }
  1344. switch ($action) {
  1345. case "new":
  1346. $this->objects[$id] = array('t' => 'extGState', 'info' => $options);
  1347. // Tell the pages about the new resource
  1348. $this->numStates++;
  1349. $this->o_pages($this->currentNode, 'extGState', array("objNum" => $id, "stateNum" => $this->numStates));
  1350. break;
  1351. case "out":
  1352. $res = "\n$id 0 obj\n<< /Type /ExtGState\n";
  1353. foreach ($o["info"] as $k => $v) {
  1354. if ( !in_array($k, $valid_params))
  1355. continue;
  1356. $res.= "/$k $v\n";
  1357. }
  1358. $res.= ">>\nendobj";
  1359. return $res;
  1360. }
  1361. }
  1362. /**
  1363. * encryption object.
  1364. */
  1365. protected function o_encryption($id, $action, $options = '') {
  1366. if ($action !== 'new') {
  1367. $o = & $this->objects[$id];
  1368. }
  1369. switch ($action) {
  1370. case 'new':
  1371. // make the new object
  1372. $this->objects[$id] = array('t'=>'encryption', 'info'=>$options);
  1373. $this->arc4_objnum = $id;
  1374. // figure out the additional paramaters required
  1375. $pad = chr(0x28) .chr(0xBF) .chr(0x4E) .chr(0x5E) .chr(0x4E) .chr(0x75) .chr(0x8A) .chr(0x41)
  1376. .chr(0x64) .chr(0x00) .chr(0x4E) .chr(0x56) .chr(0xFF) .chr(0xFA) .chr(0x01) .chr(0x08)
  1377. .chr(0x2E) .chr(0x2E) .chr(0x00) .chr(0xB6) .chr(0xD0) .chr(0x68) .chr(0x3E) .chr(0x80)
  1378. .chr(0x2F) .chr(0x0C) .chr(0xA9) .chr(0xFE) .chr(0x64) .chr(0x53) .chr(0x69) .chr(0x7A);
  1379. $len = mb_strlen($options['owner'], '8bit');
  1380. if ($len>32) {
  1381. $owner = substr($options['owner'], 0, 32);
  1382. } else if ($len<32) {
  1383. $owner = $options['owner'].substr($pad, 0, 32-$len);
  1384. } else {
  1385. $owner = $options['owner'];
  1386. }
  1387. $len = mb_strlen($options['user'], '8bit');
  1388. if ($len>32) {
  1389. $user = substr($options['user'], 0, 32);
  1390. } else if ($len<32) {
  1391. $user = $options['user'].substr($pad, 0, 32-$len);
  1392. } else {
  1393. $user = $options['user'];
  1394. }
  1395. $tmp = $this->md5_16($owner);
  1396. $okey = substr($tmp, 0, 5);
  1397. $this->ARC4_init($okey);
  1398. $ovalue = $this->ARC4($user);
  1399. $this->objects[$id]['info']['O'] = $ovalue;
  1400. // now make the u value, phew.
  1401. $tmp = $this->md5_16($user.$ovalue.chr($options['p']) .chr(255) .chr(255) .chr(255) .$this->fileIdentifier);
  1402. $ukey = substr($tmp, 0, 5);
  1403. $this->ARC4_init($ukey);
  1404. $this->encryptionKey = $ukey;
  1405. $this->encrypted = 1;
  1406. $uvalue = $this->ARC4($pad);
  1407. $this->objects[$id]['info']['U'] = $uvalue;
  1408. $this->encryptionKey = $ukey;
  1409. // initialize the arc4 array
  1410. break;
  1411. case 'out':
  1412. $res = "\n$id 0 obj\n<<";
  1413. $res.= "\n/Filter /Standard";
  1414. $res.= "\n/V 1";
  1415. $res.= "\n/R 2";
  1416. $res.= "\n/O (".$this->filterText($o['info']['O']) .')';
  1417. $res.= "\n/U (".$this->filterText($o['info']['U']) .')';
  1418. // and the p-value needs to be converted to account for the twos-complement approach
  1419. $o['info']['p'] = (($o['info']['p']^255) +1) *-1;
  1420. $res.= "\n/P ".($o['info']['p']);
  1421. $res.= "\n>>\nendobj";
  1422. return $res;
  1423. }
  1424. }
  1425. /**
  1426. * ARC4 functions
  1427. * A series of function to implement ARC4 encoding in PHP
  1428. */
  1429. /**
  1430. * calculate the 16 byte version of the 128 bit md5 digest of the string
  1431. */
  1432. function md5_16($string) {
  1433. $tmp = md5($string);
  1434. $out = '';
  1435. for ($i = 0;$i <= 30;$i = $i+2) {
  1436. $out.= chr(hexdec(substr($tmp, $i, 2)));
  1437. }
  1438. return $out;
  1439. }
  1440. /**
  1441. * initialize the encryption for processing a particular object
  1442. */
  1443. function encryptInit($id) {
  1444. $tmp = $this->encryptionKey;
  1445. $hex = dechex($id);
  1446. if (mb_strlen($hex, '8bit') <6) {
  1447. $hex = substr('000000', 0, 6-mb_strlen($hex, '8bit')) .$hex;
  1448. }
  1449. $tmp.= chr(hexdec(substr($hex, 4, 2))) .chr(hexdec(substr($hex, 2, 2))) .chr(hexdec(substr($hex, 0, 2))) .chr(0) .chr(0);
  1450. $key = $this->md5_16($tmp);
  1451. $this->ARC4_init(substr($key, 0, 10));
  1452. }
  1453. /**
  1454. * initialize the ARC4 encryption
  1455. */
  1456. function ARC4_init($key = '') {
  1457. $this->arc4 = '';
  1458. // setup the control array
  1459. if (mb_strlen($key, '8bit') == 0) {
  1460. return;
  1461. }
  1462. $k = '';
  1463. while (mb_strlen($k, '8bit') <256) {
  1464. $k.= $key;
  1465. }
  1466. $k = substr($k, 0, 256);
  1467. for ($i = 0;$i<256;$i++) {
  1468. $this->arc4.= chr($i);
  1469. }
  1470. $j = 0;
  1471. for ($i = 0;$i<256;$i++) {
  1472. $t = $this->arc4[$i];
  1473. $j = ($j + ord($t) + ord($k[$i])) %256;
  1474. $this->arc4[$i] = $this->arc4[$j];
  1475. $this->arc4[$j] = $t;
  1476. }
  1477. }
  1478. /**
  1479. * ARC4 encrypt a text string
  1480. */
  1481. function ARC4($text) {
  1482. $len = mb_strlen($text, '8bit');
  1483. $a = 0;
  1484. $b = 0;
  1485. $c = $this->arc4;
  1486. $out = '';
  1487. for ($i = 0;$i<$len;$i++) {
  1488. $a = ($a+1) %256;
  1489. $t = $c[$a];
  1490. $b = ($b+ord($t)) %256;
  1491. $c[$a] = $c[$b];
  1492. $c[$b] = $t;
  1493. $k = ord($c[(ord($c[$a]) +ord($c[$b])) %256]);
  1494. $out.= chr(ord($text[$i]) ^ $k);
  1495. }
  1496. return $out;
  1497. }
  1498. /**
  1499. * functions which can be called to adjust or add to the document
  1500. */
  1501. /**
  1502. * add a link in the document to an external URL
  1503. */
  1504. function addLink($url, $x0, $y0, $x1, $y1) {
  1505. $this->numObj++;
  1506. $info = array('type'=>'link', 'url'=>$url, 'rect'=>array($x0, $y0, $x1, $y1));
  1507. $this->o_annotation($this->numObj, 'new', $info);
  1508. }
  1509. /**
  1510. * add a link in the document to an internal destination (ie. within the document)
  1511. */
  1512. function addInternalLink($label, $x0, $y0, $x1, $y1) {
  1513. $this->numObj++;
  1514. $info = array('type'=>'ilink', 'label'=>$label, 'rect'=>array($x0, $y0, $x1, $y1));
  1515. $this->o_annotation($this->numObj, 'new', $info);
  1516. }
  1517. /**
  1518. * set the encryption of the document
  1519. * can be used to turn it on and/or set the passwords which it will have.
  1520. * also the functions that the user will have are set here, such as print, modify, add
  1521. */
  1522. function setEncryption($userPass = '', $ownerPass = '', $pc = array()) {
  1523. $p = bindec("11000000");
  1524. $options = array('print'=>4, 'modify'=>8, 'copy'=>16, 'add'=>32);
  1525. foreach($pc as $k=>$v) {
  1526. if ($v && isset($options[$k])) {
  1527. $p+= $options[$k];
  1528. } else if (isset($options[$v])) {
  1529. $p+= $options[$v];
  1530. }
  1531. }
  1532. // implement encryption on the document
  1533. if ($this->arc4_objnum == 0) {
  1534. // then the block does not exist already, add it.
  1535. $this->numObj++;
  1536. if (mb_strlen($ownerPass) == 0) {
  1537. $ownerPass = $userPass;
  1538. }
  1539. $this->o_encryption($this->numObj, 'new', array('user'=>$userPass, 'owner'=>$ownerPass, 'p'=>$p));
  1540. }
  1541. }
  1542. /**
  1543. * should be used for internal checks, not implemented as yet
  1544. */
  1545. function checkAllHere() {
  1546. }
  1547. /**
  1548. * return the pdf stream as a string returned from the function
  1549. */
  1550. function output($debug = false) {
  1551. if ($debug) {
  1552. // turn compression off
  1553. $this->options['compression'] = 0;
  1554. }
  1555. if ($this->javascript) {
  1556. $this->numObj++;
  1557. $js_id = $this->numObj;
  1558. $this->o_embedjs($js_id, 'new');
  1559. $this->o_javascript(++$this->numObj, 'new', $this->javascript);
  1560. $id = $this->catalogId;
  1561. $this->o_catalog($id, 'javascript', $js_id);
  1562. }
  1563. if ($this->arc4_objnum) {
  1564. $this->ARC4_init($this->encryptionKey);
  1565. }
  1566. $this->checkAllHere();
  1567. $xref = array();
  1568. $content = '%PDF-1.3';
  1569. $pos = mb_strlen($content, '8bit');
  1570. foreach($this->objects as $k=>$v) {
  1571. $tmp = 'o_'.$v['t'];
  1572. $cont = $this->$tmp($k, 'out');
  1573. $content.= $cont;
  1574. $xref[] = $pos;
  1575. $pos+= mb_strlen($cont, '8bit');
  1576. }
  1577. $content.= "\nxref\n0 ".(count($xref) +1) ."\n0000000000 65535 f \n";
  1578. foreach($xref as $p) {
  1579. $content.= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n";
  1580. }
  1581. $content.= "trailer\n<<\n/Size ".(count($xref) +1

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