PageRenderTime 71ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/mainapp/plugins/dompdf/lib/class.pdf.php

https://bitbucket.org/fauzilhaqqi/freestay-module
PHP | 4517 lines | 2698 code | 742 blank | 1077 comment | 570 complexity | e57ed8ce5220b70700ec80c637e99b5d 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 217 2010-03-11 23:03:57Z ryan.masten $ */
  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 = array('r'=>-1, 'g'=>-1, 'b'=>-1);
  106. /**
  107. * current colour for stroke operations (lines etc.)
  108. */
  109. public $currentStrokeColour = array('r'=>-1, 'g'=>-1, 'b'=>-1);
  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. public $javascript = '';
  261. /**
  262. * class constructor
  263. * this will start a new document
  264. * @var array array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
  265. * @var boolean whether text will be treated as Unicode or not.
  266. */
  267. function Cpdf ($pageSize = array(0, 0, 612, 792), $isUnicode = false, $fontcache = '', $tmp = '') {
  268. $this->isUnicode = $isUnicode;
  269. $this->fontcache = $fontcache;
  270. $this->tmp = $tmp;
  271. $this->newDocument($pageSize);
  272. // also initialize the font families that are known about already
  273. $this->setFontFamily('init');
  274. // $this->fileIdentifier = md5('xxxxxxxx'.time());
  275. }
  276. /**
  277. * Document object methods (internal use only)
  278. *
  279. * There is about one object method for each type of object in the pdf document
  280. * Each function has the same call list ($id,$action,$options).
  281. * $id = the object ID of the object, or what it is to be if it is being created
  282. * $action = a string specifying the action to be performed, though ALL must support:
  283. * 'new' - create the object with the id $id
  284. * 'out' - produce the output for the pdf object
  285. * $options = optional, a string or array containing the various parameters for the object
  286. *
  287. * These, in conjunction with the output function are the ONLY way for output to be produced
  288. * within the pdf 'file'.
  289. */
  290. /**
  291. *destination object, used to specify the location for the user to jump to, presently on opening
  292. */
  293. protected function o_destination($id, $action, $options = '') {
  294. if ($action !== 'new') {
  295. $o = & $this->objects[$id];
  296. }
  297. switch ($action) {
  298. case 'new':
  299. $this->objects[$id] = array('t'=>'destination', 'info'=>array());
  300. $tmp = '';
  301. switch ($options['type']) {
  302. case 'XYZ':
  303. case 'FitR':
  304. $tmp = ' '.$options['p3'].$tmp;
  305. case 'FitH':
  306. case 'FitV':
  307. case 'FitBH':
  308. case 'FitBV':
  309. $tmp = ' '.$options['p1'].' '.$options['p2'].$tmp;
  310. case 'Fit':
  311. case 'FitB':
  312. $tmp = $options['type'].$tmp;
  313. $this->objects[$id]['info']['string'] = $tmp;
  314. $this->objects[$id]['info']['page'] = $options['page'];
  315. }
  316. break;
  317. case 'out':
  318. $tmp = $o['info'];
  319. $res = "\n".$id." 0 obj\n".'['.$tmp['page'].' 0 R /'.$tmp['string']."]\nendobj";
  320. return $res;
  321. }
  322. }
  323. /**
  324. * set the viewer preferences
  325. */
  326. protected function o_viewerPreferences($id, $action, $options = '') {
  327. if ($action !== 'new') {
  328. $o = & $this->objects[$id];
  329. }
  330. switch ($action) {
  331. case 'new':
  332. $this->objects[$id] = array('t'=>'viewerPreferences', 'info'=>array());
  333. break;
  334. case 'add':
  335. foreach($options as $k=>$v) {
  336. switch ($k) {
  337. case 'HideToolbar':
  338. case 'HideMenubar':
  339. case 'HideWindowUI':
  340. case 'FitWindow':
  341. case 'CenterWindow':
  342. case 'NonFullScreenPageMode':
  343. case 'Direction':
  344. $o['info'][$k] = $v;
  345. break;
  346. }
  347. }
  348. break;
  349. case 'out':
  350. $res = "\n".$id." 0 obj\n".'<< ';
  351. foreach($o['info'] as $k=>$v) {
  352. $res.= "\n/".$k.' '.$v;
  353. }
  354. $res.= "\n>>\n";
  355. return $res;
  356. }
  357. }
  358. /**
  359. * define the document catalog, the overall controller for the document
  360. */
  361. protected function o_catalog($id, $action, $options = '') {
  362. if ($action !== 'new') {
  363. $o = & $this->objects[$id];
  364. }
  365. switch ($action) {
  366. case 'new':
  367. $this->objects[$id] = array('t'=>'catalog', 'info'=>array());
  368. $this->catalogId = $id;
  369. break;
  370. case 'outlines':
  371. case 'pages':
  372. case 'openHere':
  373. case 'javascript':
  374. $o['info'][$action] = $options;
  375. break;
  376. case 'viewerPreferences':
  377. if (!isset($o['info']['viewerPreferences'])) {
  378. $this->numObj++;
  379. $this->o_viewerPreferences($this->numObj, 'new');
  380. $o['info']['viewerPreferences'] = $this->numObj;
  381. }
  382. $vp = $o['info']['viewerPreferences'];
  383. $this->o_viewerPreferences($vp, 'add', $options);
  384. break;
  385. case 'out':
  386. $res = "\n".$id." 0 obj\n".'<< /Type /Catalog';
  387. foreach($o['info'] as $k=>$v) {
  388. switch ($k) {
  389. case 'outlines':
  390. $res.= "\n".'/Outlines '.$v.' 0 R';
  391. break;
  392. case 'pages':
  393. $res.= "\n".'/Pages '.$v.' 0 R';
  394. break;
  395. case 'viewerPreferences':
  396. $res.= "\n".'/ViewerPreferences '.$v.' 0 R';
  397. break;
  398. case 'openHere':
  399. $res.= "\n".'/OpenAction '.$v.' 0 R';
  400. break;
  401. case 'javascript':
  402. $res.= "\n".'/Names <</JavaScript '.$v.' 0 R>>';
  403. break;
  404. }
  405. }
  406. $res.= " >>\nendobj";
  407. return $res;
  408. }
  409. }
  410. /**
  411. * object which is a parent to the pages in the document
  412. */
  413. protected function o_pages($id, $action, $options = '') {
  414. if ($action !== 'new') {
  415. $o = & $this->objects[$id];
  416. }
  417. switch ($action) {
  418. case 'new':
  419. $this->objects[$id] = array('t'=>'pages', 'info'=>array());
  420. $this->o_catalog($this->catalogId, 'pages', $id);
  421. break;
  422. case 'page':
  423. if (!is_array($options)) {
  424. // then it will just be the id of the new page
  425. $o['info']['pages'][] = $options;
  426. } else {
  427. // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
  428. // and pos is either 'before' or 'after', saying where this page will fit.
  429. if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) {
  430. $i = array_search($options['rid'], $o['info']['pages']);
  431. if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i] == $options['rid']) {
  432. // then there is a match
  433. // make a space
  434. switch ($options['pos']) {
  435. case 'before':
  436. $k = $i;
  437. break;
  438. case 'after':
  439. $k = $i+1;
  440. break;
  441. default:
  442. $k = -1;
  443. break;
  444. }
  445. if ($k >= 0) {
  446. for ($j = count($o['info']['pages']) -1;$j >= $k;$j--) {
  447. $o['info']['pages'][$j+1] = $o['info']['pages'][$j];
  448. }
  449. $o['info']['pages'][$k] = $options['id'];
  450. }
  451. }
  452. }
  453. }
  454. break;
  455. case 'procset':
  456. $o['info']['procset'] = $options;
  457. break;
  458. case 'mediaBox':
  459. $o['info']['mediaBox'] = $options;
  460. // which should be an array of 4 numbers
  461. break;
  462. case 'font':
  463. $o['info']['fonts'][] = array('objNum'=>$options['objNum'], 'fontNum'=>$options['fontNum']);
  464. break;
  465. case 'extGState':
  466. $o['info']['extGStates'][] = array('objNum' => $options['objNum'], 'stateNum' => $options['stateNum']);
  467. break;
  468. case 'xObject':
  469. $o['info']['xObjects'][] = array('objNum'=>$options['objNum'], 'label'=>$options['label']);
  470. break;
  471. case 'out':
  472. if (count($o['info']['pages'])) {
  473. $res = "\n".$id." 0 obj\n<< /Type /Pages\n/Kids [";
  474. foreach($o['info']['pages'] as $k=>$v) {
  475. $res.= $v." 0 R\n";
  476. }
  477. $res.= "]\n/Count ".count($this->objects[$id]['info']['pages']);
  478. if ( (isset($o['info']['fonts']) && count($o['info']['fonts'])) ||
  479. isset($o['info']['procset']) ||
  480. (isset($o['info']['extGStates']) && count($o['info']['extGStates']))) {
  481. $res.= "\n/Resources <<";
  482. if (isset($o['info']['procset'])) {
  483. $res.= "\n/ProcSet ".$o['info']['procset']." 0 R";
  484. }
  485. if (isset($o['info']['fonts']) && count($o['info']['fonts'])) {
  486. $res.= "\n/Font << ";
  487. foreach($o['info']['fonts'] as $finfo) {
  488. $res.= "\n/F".$finfo['fontNum']." ".$finfo['objNum']." 0 R";
  489. }
  490. $res.= "\n>>";
  491. }
  492. if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) {
  493. $res.= "\n/XObject << ";
  494. foreach($o['info']['xObjects'] as $finfo) {
  495. $res.= "\n/".$finfo['label']." ".$finfo['objNum']." 0 R";
  496. }
  497. $res.= "\n>>";
  498. }
  499. if ( isset($o['info']['extGStates']) && count($o['info']['extGStates'])) {
  500. $res.= "\n/ExtGState << ";
  501. foreach ($o['info']['extGStates'] as $gstate) {
  502. $res.= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
  503. }
  504. $res.= "\n>>";
  505. }
  506. $res.= "\n>>";
  507. if (isset($o['info']['mediaBox'])) {
  508. $tmp = $o['info']['mediaBox'];
  509. $res.= "\n/MediaBox [".sprintf('%.3F', $tmp[0]) .' '.sprintf('%.3F', $tmp[1]) .' '.sprintf('%.3F', $tmp[2]) .' '.sprintf('%.3F', $tmp[3]) .']';
  510. }
  511. }
  512. $res.= "\n >>\nendobj";
  513. } else {
  514. $res = "\n".$id." 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
  515. }
  516. return $res;
  517. }
  518. }
  519. /**
  520. * define the outlines in the doc, empty for now
  521. */
  522. protected function o_outlines($id, $action, $options = '') {
  523. if ($action !== 'new') {
  524. $o = & $this->objects[$id];
  525. }
  526. switch ($action) {
  527. case 'new':
  528. $this->objects[$id] = array('t'=>'outlines', 'info'=>array('outlines'=>array()));
  529. $this->o_catalog($this->catalogId, 'outlines', $id);
  530. break;
  531. case 'outline':
  532. $o['info']['outlines'][] = $options;
  533. break;
  534. case 'out':
  535. if (count($o['info']['outlines'])) {
  536. $res = "\n".$id." 0 obj\n<< /Type /Outlines /Kids [";
  537. foreach($o['info']['outlines'] as $k=>$v) {
  538. $res.= $v." 0 R ";
  539. }
  540. $res.= "] /Count ".count($o['info']['outlines']) ." >>\nendobj";
  541. } else {
  542. $res = "\n".$id." 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
  543. }
  544. return $res;
  545. }
  546. }
  547. /**
  548. * an object to hold the font description
  549. */
  550. protected function o_font($id, $action, $options = '') {
  551. if ($action !== 'new') {
  552. $o = & $this->objects[$id];
  553. }
  554. switch ($action) {
  555. case 'new':
  556. $this->objects[$id] = array('t' => 'font', 'info' => array('name' => $options['name'], 'fontFileName' => $options['fontFileName'], 'SubType' => 'Type1'));
  557. $fontNum = $this->numFonts;
  558. $this->objects[$id]['info']['fontNum'] = $fontNum;
  559. // deal with the encoding and the differences
  560. if (isset($options['differences'])) {
  561. // then we'll need an encoding dictionary
  562. $this->numObj++;
  563. $this->o_fontEncoding($this->numObj, 'new', $options);
  564. $this->objects[$id]['info']['encodingDictionary'] = $this->numObj;
  565. } else if (isset($options['encoding'])) {
  566. // we can specify encoding here
  567. switch ($options['encoding']) {
  568. case 'WinAnsiEncoding':
  569. case 'MacRomanEncoding':
  570. case 'MacExpertEncoding':
  571. $this->objects[$id]['info']['encoding'] = $options['encoding'];
  572. break;
  573. case 'none':
  574. break;
  575. default:
  576. $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
  577. break;
  578. }
  579. } else {
  580. $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
  581. }
  582. if ($this->fonts[$options['fontFileName']]['isUnicode']) {
  583. // For Unicode fonts, we need to incorporate font data into
  584. // sub-sections that are linked from the primary font section.
  585. // Look at o_fontGIDtoCID and o_fontDescendentCID functions
  586. // for more informaiton.
  587. //
  588. // All of this code is adapted from the excellent changes made to
  589. // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
  590. $toUnicodeId = ++$this->numObj;
  591. $this->o_contents($toUnicodeId, 'new', 'raw');
  592. $this->objects[$id]['info']['toUnicode'] = $toUnicodeId;
  593. $stream = "/CIDInit /ProcSet findresource begin\n";
  594. $stream.= "12 dict begin\n";
  595. $stream.= "begincmap\n";
  596. $stream.= "/CIDSystemInfo\n";
  597. $stream.= "<</Registry (Adobe)\n";
  598. $stream.= "/Ordering (UCS)\n";
  599. $stream.= "/Supplement 0\n";
  600. $stream.= ">> def\n";
  601. $stream.= "/CMapName /Adobe-Identity-UCS def\n";
  602. $stream.= "/CMapType 2 def\n";
  603. $stream.= "1 begincodespacerange\n";
  604. $stream.= "<0000> <FFFF>\n";
  605. $stream.= "endcodespacerange\n";
  606. $stream.= "1 beginbfrange\n";
  607. $stream.= "<0000> <FFFF> <0000>\n";
  608. $stream.= "endbfrange\n";
  609. $stream.= "endcmap\n";
  610. $stream.= "CMapName currentdict /CMap defineresource pop\n";
  611. $stream.= "end\n";
  612. $stream.= "end\n";
  613. $res = "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
  614. $res .= "stream\n" . $stream . "endstream";
  615. $this->objects[$toUnicodeId]['c'] = $res;
  616. $cidFontId = ++$this->numObj;
  617. $this->o_fontDescendentCID($cidFontId, 'new', $options);
  618. $this->objects[$id]['info']['cidFont'] = $cidFontId;
  619. }
  620. // also tell the pages node about the new font
  621. $this->o_pages($this->currentNode, 'font', array('fontNum' => $fontNum, 'objNum' => $id));
  622. break;
  623. case 'add':
  624. foreach ($options as $k => $v) {
  625. switch ($k) {
  626. case 'BaseFont':
  627. $o['info']['name'] = $v;
  628. break;
  629. case 'FirstChar':
  630. case 'LastChar':
  631. case 'Widths':
  632. case 'FontDescriptor':
  633. case 'SubType':
  634. $this->addMessage('o_font '.$k." : ".$v);
  635. $o['info'][$k] = $v;
  636. break;
  637. }
  638. }
  639. // pass values down to descendent font
  640. if (isset($o['info']['cidFont'])) {
  641. $this->o_fontDescendentCID($o['info']['cidFont'], 'add', $options);
  642. }
  643. break;
  644. case 'out':
  645. if ($this->fonts[$this->objects[$id]['info']['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. $res = "\n".$id." 0 obj\n<</Type /Font\n/Subtype /Type0\n";
  654. $res.= "/BaseFont /".$o['info']['name']."\n";
  655. // The horizontal identity mapping for 2-byte CIDs; may be used
  656. // with CIDFonts using any Registry, Ordering, and Supplement values.
  657. $res.= "/Encoding /Identity-H\n";
  658. $res.= "/DescendantFonts [".$o['info']['cidFont']." 0 R]\n";
  659. $res.= "/ToUnicode ".$o['info']['toUnicode']." 0 R\n";
  660. $res.= ">>\n";
  661. $res.= "endobj";
  662. } else {
  663. $res = "\n".$id." 0 obj\n<< /Type /Font\n/Subtype /".$o['info']['SubType']."\n";
  664. $res.= "/Name /F".$o['info']['fontNum']."\n";
  665. $res.= "/BaseFont /".$o['info']['name']."\n";
  666. if (isset($o['info']['encodingDictionary'])) {
  667. // then place a reference to the dictionary
  668. $res.= "/Encoding ".$o['info']['encodingDictionary']." 0 R\n";
  669. } else if (isset($o['info']['encoding'])) {
  670. // use the specified encoding
  671. $res.= "/Encoding /".$o['info']['encoding']."\n";
  672. }
  673. if (isset($o['info']['FirstChar'])) {
  674. $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
  675. }
  676. if (isset($o['info']['LastChar'])) {
  677. $res.= "/LastChar ".$o['info']['LastChar']."\n";
  678. }
  679. if (isset($o['info']['Widths'])) {
  680. $res.= "/Widths ".$o['info']['Widths']." 0 R\n";
  681. }
  682. if (isset($o['info']['FontDescriptor'])) {
  683. $res.= "/FontDescriptor ".$o['info']['FontDescriptor']." 0 R\n";
  684. }
  685. $res.= ">>\n";
  686. $res.= "endobj";
  687. }
  688. return $res;
  689. }
  690. }
  691. /**
  692. * a font descriptor, needed for including additional fonts
  693. */
  694. protected function o_fontDescriptor($id, $action, $options = '') {
  695. if ($action !== 'new') {
  696. $o = & $this->objects[$id];
  697. }
  698. switch ($action) {
  699. case 'new':
  700. $this->objects[$id] = array('t'=>'fontDescriptor', 'info'=>$options);
  701. break;
  702. case 'out':
  703. $res = "\n".$id." 0 obj\n<< /Type /FontDescriptor\n";
  704. foreach ($o['info'] as $label => $value) {
  705. switch ($label) {
  706. case 'Ascent':
  707. case 'CapHeight':
  708. case 'Descent':
  709. case 'Flags':
  710. case 'ItalicAngle':
  711. case 'StemV':
  712. case 'AvgWidth':
  713. case 'Leading':
  714. case 'MaxWidth':
  715. case 'MissingWidth':
  716. case 'StemH':
  717. case 'XHeight':
  718. case 'CharSet':
  719. if (mb_strlen($value, '8bit')) {
  720. $res.= '/'.$label.' '.$value."\n";
  721. }
  722. break;
  723. case 'FontFile':
  724. case 'FontFile2':
  725. case 'FontFile3':
  726. $res.= '/'.$label.' '.$value." 0 R\n";
  727. break;
  728. case 'FontBBox':
  729. $res.= '/'.$label.' ['.$value[0].' '.$value[1].' '.$value[2].' '.$value[3]."]\n";
  730. break;
  731. case 'FontName':
  732. $res.= '/'.$label.' /'.$value."\n";
  733. break;
  734. }
  735. }
  736. $res.= ">>\nendobj";
  737. return $res;
  738. }
  739. }
  740. /**
  741. * the font encoding
  742. */
  743. protected function o_fontEncoding($id, $action, $options = '') {
  744. if ($action !== 'new') {
  745. $o = & $this->objects[$id];
  746. }
  747. switch ($action) {
  748. case 'new':
  749. // the options array should contain 'differences' and maybe 'encoding'
  750. $this->objects[$id] = array('t'=>'fontEncoding', 'info'=>$options);
  751. break;
  752. case 'out':
  753. $res = "\n".$id." 0 obj\n<< /Type /Encoding\n";
  754. if (!isset($o['info']['encoding'])) {
  755. $o['info']['encoding'] = 'WinAnsiEncoding';
  756. }
  757. if ($o['info']['encoding'] !== 'none') {
  758. $res.= "/BaseEncoding /".$o['info']['encoding']."\n";
  759. }
  760. $res.= "/Differences \n[";
  761. $onum = -100;
  762. foreach($o['info']['differences'] as $num=>$label) {
  763. if ($num != $onum+1) {
  764. // we cannot make use of consecutive numbering
  765. $res.= "\n".$num." /".$label;
  766. } else {
  767. $res.= " /".$label;
  768. }
  769. $onum = $num;
  770. }
  771. $res.= "\n]\n>>\nendobj";
  772. return $res;
  773. }
  774. }
  775. /**
  776. * a descendent cid font, needed for unicode fonts
  777. */
  778. protected function o_fontDescendentCID($id, $action, $options = '') {
  779. if ($action !== 'new') {
  780. $o = & $this->objects[$id];
  781. }
  782. switch ($action) {
  783. case 'new':
  784. $this->objects[$id] = array('t'=>'fontDescendentCID', 'info'=>$options);
  785. // we need a CID system info section
  786. $cidSystemInfoId = ++$this->numObj;
  787. $this->o_contents($cidSystemInfoId, 'new', 'raw');
  788. $this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId;
  789. $res= "<</Registry (Adobe)\n"; // A string identifying an issuer of character collections
  790. $res.= "/Ordering (UCS)\n"; // A string that uniquely names a character collection issued by a specific registry
  791. $res.= "/Supplement 0\n"; // The supplement number of the character collection.
  792. $res.= ">>";
  793. $this->objects[$cidSystemInfoId]['c'] = $res;
  794. // and a CID to GID map
  795. $cidToGidMapId = ++$this->numObj;
  796. $this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options);
  797. $this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId;
  798. break;
  799. case 'add':
  800. foreach ($options as $k => $v) {
  801. switch ($k) {
  802. case 'BaseFont':
  803. $o['info']['name'] = $v;
  804. break;
  805. case 'FirstChar':
  806. case 'LastChar':
  807. case 'MissingWidth':
  808. case 'FontDescriptor':
  809. case 'SubType':
  810. $this->addMessage('o_fontDescendentCID '.$k." : ".$v);
  811. $o['info'][$k] = $v;
  812. break;
  813. }
  814. }
  815. // pass values down to cid to gid map
  816. $this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options);
  817. break;
  818. case 'out':
  819. $res = "\n".$id." 0 obj\n";
  820. $res.= "<</Type /Font\n";
  821. $res.= "/Subtype /CIDFontType2\n";
  822. $res.= "/BaseFont /".$o['info']['name']."\n";
  823. $res.= "/CIDSystemInfo ".$o['info']['cidSystemInfo']." 0 R\n";
  824. // if (isset($o['info']['FirstChar'])) {
  825. // $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
  826. // }
  827. // if (isset($o['info']['LastChar'])) {
  828. // $res.= "/LastChar ".$o['info']['LastChar']."\n";
  829. // }
  830. if (isset($o['info']['FontDescriptor'])) {
  831. $res.= "/FontDescriptor ".$o['info']['FontDescriptor']." 0 R\n";
  832. }
  833. if (isset($o['info']['MissingWidth'])) {
  834. $res.= "/DW ".$o['info']['MissingWidth']."\n";
  835. }
  836. if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) {
  837. $cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths'];
  838. $w = '';
  839. foreach ($cid_widths as $cid => $width) {
  840. $w .= $cid.' ['.$width.'] ';
  841. }
  842. $res.= "/W [".$w."]\n";
  843. }
  844. $res.= "/CIDToGIDMap ".$o['info']['cidToGidMap']." 0 R\n";
  845. $res.= ">>\n";
  846. $res.= "endobj";
  847. return $res;
  848. }
  849. }
  850. /**
  851. * a font glyph to character map, needed for unicode fonts
  852. */
  853. protected function o_fontGIDtoCIDMap($id, $action, $options = '') {
  854. if ($action !== 'new') {
  855. $o = & $this->objects[$id];
  856. }
  857. switch ($action) {
  858. case 'new':
  859. $this->objects[$id] = array('t'=>'fontGIDtoCIDMap', 'info'=>$options);
  860. break;
  861. case 'out':
  862. $res = "\n".$id." 0 obj\n";
  863. $tmp = $this->fonts[$o['info']['fontFileName']]['CIDtoGID'] = base64_decode($this->fonts[$o['info']['fontFileName']]['CIDtoGID']);
  864. $compressed = isset($this->fonts[$o['info']['fontFileName']]['CIDtoGID_Compressed']) &&
  865. $this->fonts[$o['info']['fontFileName']]['CIDtoGID_Compressed'];
  866. if (!$compressed && isset($o['raw'])) {
  867. $res.= $tmp;
  868. } else {
  869. $res.= "<<";
  870. if (!$compressed && function_exists('gzcompress') && $this->options['compression']) {
  871. // then implement ZLIB based compression on this content stream
  872. $compressed = true;
  873. $tmp = gzcompress($tmp, 6);
  874. }
  875. if ($compressed) {
  876. $res.= "\n/Filter /FlateDecode";
  877. }
  878. $res.= "\n/Length ".mb_strlen($tmp, '8bit') .">>\nstream\n".$tmp."\nendstream";
  879. }
  880. $res.= "\nendobj";
  881. return $res;
  882. }
  883. }
  884. /**
  885. * the document procset, solves some problems with printing to old PS printers
  886. */
  887. protected function o_procset($id, $action, $options = '') {
  888. if ($action !== 'new') {
  889. $o = & $this->objects[$id];
  890. }
  891. switch ($action) {
  892. case 'new':
  893. $this->objects[$id] = array('t'=>'procset', 'info'=>array('PDF'=>1, 'Text'=>1));
  894. $this->o_pages($this->currentNode, 'procset', $id);
  895. $this->procsetObjectId = $id;
  896. break;
  897. case 'add':
  898. // this is to add new items to the procset list, despite the fact that this is considered
  899. // obselete, the items are required for printing to some postscript printers
  900. switch ($options) {
  901. case 'ImageB':
  902. case 'ImageC':
  903. case 'ImageI':
  904. $o['info'][$options] = 1;
  905. break;
  906. }
  907. break;
  908. case 'out':
  909. $res = "\n".$id." 0 obj\n[";
  910. foreach ($o['info'] as $label=>$val) {
  911. $res.= '/'.$label.' ';
  912. }
  913. $res.= "]\nendobj";
  914. return $res;
  915. }
  916. }
  917. /**
  918. * define the document information
  919. */
  920. protected function o_info($id, $action, $options = '') {
  921. if ($action !== 'new') {
  922. $o = & $this->objects[$id];
  923. }
  924. switch ($action) {
  925. case 'new':
  926. $this->infoObject = $id;
  927. $date = 'D:'.@date('Ymd');
  928. $this->objects[$id] = array('t'=>'info', 'info'=>array('Creator'=>'R and OS php pdf writer, http://www.ros.co.nz', 'CreationDate'=>$date));
  929. break;
  930. case 'Title':
  931. case 'Author':
  932. case 'Subject':
  933. case 'Keywords':
  934. case 'Creator':
  935. case 'Producer':
  936. case 'CreationDate':
  937. case 'ModDate':
  938. case 'Trapped':
  939. $o['info'][$action] = $options;
  940. break;
  941. case 'out':
  942. if ($this->encrypted) {
  943. $this->encryptInit($id);
  944. }
  945. $res = "\n".$id." 0 obj\n<<\n";
  946. foreach ($o['info'] as $k=>$v) {
  947. $res.= '/'.$k.' (';
  948. // dates must be outputted as-is, without Unicode transformations
  949. $raw = ($k === 'CreationDate' || $k === 'ModDate');
  950. $c = $v;
  951. if ($this->encrypted) {
  952. $c = $this->ARC4($c);
  953. }
  954. $res.= ($raw) ? $c : $this->filterText($c);
  955. $res.= ")\n";
  956. }
  957. $res.= ">>\nendobj";
  958. return $res;
  959. }
  960. }
  961. /**
  962. * an action object, used to link to URLS initially
  963. */
  964. protected function o_action($id, $action, $options = '') {
  965. if ($action !== 'new') {
  966. $o = & $this->objects[$id];
  967. }
  968. switch ($action) {
  969. case 'new':
  970. if (is_array($options)) {
  971. $this->objects[$id] = array('t'=>'action', 'info'=>$options, 'type'=>$options['type']);
  972. } else {
  973. // then assume a URI action
  974. $this->objects[$id] = array('t'=>'action', 'info'=>$options, 'type'=>'URI');
  975. }
  976. break;
  977. case 'out':
  978. if ($this->encrypted) {
  979. $this->encryptInit($id);
  980. }
  981. $res = "\n".$id." 0 obj\n<< /Type /Action";
  982. switch ($o['type']) {
  983. case 'ilink':
  984. // there will be an 'label' setting, this is the name of the destination
  985. $res.= "\n/S /GoTo\n/D ".$this->destinations[(string)$o['info']['label']]." 0 R";
  986. break;
  987. case 'URI':
  988. $res.= "\n/S /URI\n/URI (";
  989. if ($this->encrypted) {
  990. $res.= $this->filterText($this->ARC4($o['info']));
  991. } else {
  992. $res.= $this->filterText($o['info']);
  993. }
  994. $res.= ")";
  995. break;
  996. }
  997. $res.= "\n>>\nendobj";
  998. return $res;
  999. }
  1000. }
  1001. /**
  1002. * an annotation object, this will add an annotation to the current page.
  1003. * initially will support just link annotations
  1004. */
  1005. protected function o_annotation($id, $action, $options = '') {
  1006. if ($action !== 'new') {
  1007. $o = & $this->objects[$id];
  1008. }
  1009. switch ($action) {
  1010. case 'new':
  1011. // add the annotation to the current page
  1012. $pageId = $this->currentPage;
  1013. $this->o_page($pageId, 'annot', $id);
  1014. // and add the action object which is going to be required
  1015. switch ($options['type']) {
  1016. case 'link':
  1017. $this->objects[$id] = array('t'=>'annotation', 'info'=>$options);
  1018. $this->numObj++;
  1019. $this->o_action($this->numObj, 'new', $options['url']);
  1020. $this->objects[$id]['info']['actionId'] = $this->numObj;
  1021. break;
  1022. case 'ilink':
  1023. // this is to a named internal link
  1024. $label = $options['label'];
  1025. $this->objects[$id] = array('t'=>'annotation', 'info'=>$options);
  1026. $this->numObj++;
  1027. $this->o_action($this->numObj, 'new', array('type'=>'ilink', 'label'=>$label));
  1028. $this->objects[$id]['info']['actionId'] = $this->numObj;
  1029. break;
  1030. }
  1031. break;
  1032. case 'out':
  1033. $res = "\n".$id." 0 obj\n<< /Type /Annot";
  1034. switch ($o['info']['type']) {
  1035. case 'link':
  1036. case 'ilink':
  1037. $res.= "\n/Subtype /Link";
  1038. break;
  1039. }
  1040. $res.= "\n/A ".$o['info']['actionId']." 0 R";
  1041. $res.= "\n/Border [0 0 0]";
  1042. $res.= "\n/H /I";
  1043. $res.= "\n/Rect [ ";
  1044. foreach($o['info']['rect'] as $v) {
  1045. $res.= sprintf("%.4F ", $v);
  1046. }
  1047. $res.= "]";
  1048. $res.= "\n>>\nendobj";
  1049. return $res;
  1050. }
  1051. }
  1052. /**
  1053. * a page object, it also creates a contents object to hold its contents
  1054. */
  1055. protected function o_page($id, $action, $options = '') {
  1056. if ($action !== 'new') {
  1057. $o = & $this->objects[$id];
  1058. }
  1059. switch ($action) {
  1060. case 'new':
  1061. $this->numPages++;
  1062. $this->objects[$id] = array('t'=>'page', 'info'=>array('parent'=>$this->currentNode, 'pageNum'=>$this->numPages));
  1063. if (is_array($options)) {
  1064. // then this must be a page insertion, array should contain 'rid','pos'=[before|after]
  1065. $options['id'] = $id;
  1066. $this->o_pages($this->currentNode, 'page', $options);
  1067. } else {
  1068. $this->o_pages($this->currentNode, 'page', $id);
  1069. }
  1070. $this->currentPage = $id;
  1071. //make a contents object to go with this page
  1072. $this->numObj++;
  1073. $this->o_contents($this->numObj, 'new', $id);
  1074. $this->currentContents = $this->numObj;
  1075. $this->objects[$id]['info']['contents'] = array();
  1076. $this->objects[$id]['info']['contents'][] = $this->numObj;
  1077. $match = ($this->numPages%2 ? 'odd' : 'even');
  1078. foreach($this->addLooseObjects as $oId=>$target) {
  1079. if ($target === 'all' || $match === $target) {
  1080. $this->objects[$id]['info']['contents'][] = $oId;
  1081. }
  1082. }
  1083. break;
  1084. case 'content':
  1085. $o['info']['contents'][] = $options;
  1086. break;
  1087. case 'annot':
  1088. // add an annotation to this page
  1089. if (!isset($o['info']['annot'])) {
  1090. $o['info']['annot'] = array();
  1091. }
  1092. // $options should contain the id of the annotation dictionary
  1093. $o['info']['annot'][] = $options;
  1094. break;
  1095. case 'out':
  1096. $res = "\n".$id." 0 obj\n<< /Type /Page";
  1097. $res.= "\n/Parent ".$o['info']['parent']." 0 R";
  1098. if (isset($o['info']['annot'])) {
  1099. $res.= "\n/Annots [";
  1100. foreach($o['info']['annot'] as $aId) {
  1101. $res.= " ".$aId." 0 R";
  1102. }
  1103. $res.= " ]";
  1104. }
  1105. $count = count($o['info']['contents']);
  1106. if ($count == 1) {
  1107. $res.= "\n/Contents ".$o['info']['contents'][0]." 0 R";
  1108. } else if ($count>1) {
  1109. $res.= "\n/Contents [\n";
  1110. // reverse the page contents so added objects are below normal content
  1111. //foreach (array_reverse($o['info']['contents']) as $cId) {
  1112. // Back to normal now that I've got transparency working --Benj
  1113. foreach ($o['info']['contents'] as $cId) {
  1114. $res.= $cId." 0 R\n";
  1115. }
  1116. $res.= "]";
  1117. }
  1118. $res.= "\n>>\nendobj";
  1119. return $res;
  1120. }
  1121. }
  1122. /**
  1123. * the contents objects hold all of the content which appears on pages
  1124. */
  1125. protected function o_contents($id, $action, $options = '') {
  1126. if ($action !== 'new') {
  1127. $o = & $this->objects[$id];
  1128. }
  1129. switch ($action) {
  1130. case 'new':
  1131. $this->objects[$id] = array('t'=>'contents', 'c'=>'', 'info'=>array());
  1132. if (mb_strlen($options, '8bit') && intval($options)) {
  1133. // then this contents is the primary for a page
  1134. $this->objects[$id]['onPage'] = $options;
  1135. } else if ($options === 'raw') {
  1136. // then this page contains some other type of system object
  1137. $this->objects[$id]['raw'] = 1;
  1138. }
  1139. break;
  1140. case 'add':
  1141. // add more options to the decleration
  1142. foreach ($options as $k=>$v) {
  1143. $o['info'][$k] = $v;
  1144. }
  1145. case 'out':
  1146. $tmp = $o['c'];
  1147. $res = "\n".$id." 0 obj\n";
  1148. if (isset($this->objects[$id]['raw'])) {
  1149. $res.= $tmp;
  1150. } else {
  1151. $res.= "<<";
  1152. if (function_exists('gzcompress') && $this->options['compression']) {
  1153. // then implement ZLIB based compression on this content stream
  1154. $res.= " /Filter /FlateDecode";
  1155. $tmp = gzcompress($tmp, 6);
  1156. }
  1157. if ($this->encrypted) {
  1158. $this->encryptInit($id);
  1159. $tmp = $this->ARC4($tmp);
  1160. }
  1161. foreach($o['info'] as $k=>$v) {
  1162. $res.= "\n/".$k.' '.$v;
  1163. }
  1164. $res.= "\n/Length ".mb_strlen($tmp, '8bit') ." >>\nstream\n".$tmp."\nendstream";
  1165. }
  1166. $res.= "\nendobj";
  1167. return $res;
  1168. }
  1169. }
  1170. protected function o_embedjs($id, $action, $code = '') {
  1171. if ($action !== 'new') {
  1172. $o = & $this->objects[$id];
  1173. }
  1174. switch ($action) {
  1175. case 'new':
  1176. $this->objects[$id] = array('t'=>'embedjs', 'info'=>array(
  1177. 'Names' => '[(EmbeddedJS) '.($id+1).' 0 R]'
  1178. ));
  1179. break;
  1180. case 'out':
  1181. $res .= "\n".$id." 0 obj\n".'<< ';
  1182. foreach($o['info'] as $k=>$v) {
  1183. $res.= "\n/".$k.' '.$v;
  1184. }
  1185. $res.= "\n>>\nendobj";
  1186. return $res;
  1187. }
  1188. }
  1189. protected function o_javascript($id, $action, $code = '') {
  1190. if ($action !== 'new') {
  1191. $o = & $this->objects[$id];
  1192. }
  1193. switch ($action) {
  1194. case 'new':
  1195. $this->objects[$id] = array('t'=>'javascript', 'info'=>array(
  1196. 'S' => '/JavaScript',
  1197. 'JS' => '('.$this->filterText($code).')',
  1198. ));
  1199. break;
  1200. case 'out':
  1201. $res = "\n".$id." 0 obj\n".'<< ';
  1202. foreach($o['info'] as $k=>$v) {
  1203. $res.= "\n/".$k.' '.$v;
  1204. }
  1205. $res.= "\n>>\nendobj";
  1206. return $res;
  1207. }
  1208. }
  1209. /**
  1210. * an image object, will be an XObject in the document, includes description and data
  1211. */
  1212. protected function o_image($id, $action, $options = '') {
  1213. if ($action !== 'new') {
  1214. $o = & $this->objects[$id];
  1215. }
  1216. switch ($action) {
  1217. case 'new':
  1218. // make the new object
  1219. $this->objects[$id] = array('t'=>'image', 'data'=>&$options['data'], 'info'=>array());
  1220. $this->objects[$id]['info']['Type'] = '/XObject';
  1221. $this->objects[$id]['info']['Subtype'] = '/Image';
  1222. $this->objects[$id]['info']['Width'] = $options['iw'];
  1223. $this->objects[$id]['info']['Height'] = $options['ih'];
  1224. if (!isset($options['type']) || $options['type'] === 'jpg') {
  1225. if (!isset($options['channels'])) {
  1226. $options['channels'] = 3;
  1227. }
  1228. switch ($options['channels']) {
  1229. case 1:
  1230. $this->objects[$id]['info']['ColorSpace'] = '/DeviceGray';
  1231. break;
  1232. default:
  1233. $this->objects[$id]['info']['ColorSpace'] = '/DeviceRGB';
  1234. break;
  1235. }
  1236. $this->objects[$id]['info']['Filter'] = '/DCTDecode';
  1237. $this->objects[$id]['info']['BitsPerComponent'] = 8;
  1238. } else if ($options['type'] === 'png') {
  1239. $this->objects[$id]['info']['Filter'] = '/FlateDecode';
  1240. $this->objects[$id]['info']['DecodeParms'] = '<< /Predictor 15 /Colors '.$options['ncolor'].' /Columns '.$options['iw'].' /BitsPerComponent '.$options['bitsPerComponent'].'>>';
  1241. if (mb_strlen($options['pdata'], '8bit')) {
  1242. $tmp = ' [ /Indexed /DeviceRGB '.(mb_strlen($options['pdata'], '8bit') /3-1) .' ';
  1243. $this->numObj++;
  1244. $this->o_contents($this->numObj, 'new');
  1245. $this->objects[$this->numObj]['c'] = $options['pdata'];
  1246. $tmp.= $this->numObj.' 0 R';
  1247. $tmp.= ' ]';
  1248. $this->objects[$id]['info']['ColorSpace'] = $tmp;
  1249. if (isset($options['transparency'])) {
  1250. switch ($options['transparency']['type']) {
  1251. case 'indexed':
  1252. $tmp = ' [ '.$options['transparency']['data'].' '.$options['transparency']['data'].'] ';
  1253. $this->objects[$id]['info']['Mask'] = $tmp;
  1254. break;
  1255. case 'color-key':
  1256. $tmp = ' [ '.
  1257. $options['transparency']['r'] . ' ' . $options['transparency']['r'] .
  1258. $options['transparency']['g'] . ' ' . $options['transparency']['g'] .
  1259. $options['transparency']['b'] . ' ' . $options['transparency']['b'] .
  1260. ' ] ';
  1261. $this->objects[$id]['info']['Mask'] = $tmp;
  1262. pre_r($tmp);
  1263. break;
  1264. }
  1265. }
  1266. } else {
  1267. if (isset($options['transparency'])) {
  1268. switch ($options['transparency']['type']) {
  1269. case 'indexed':
  1270. $tmp = ' [ '.$options['transparency']['data'].' '.$options['transparency']['data'].'] ';
  1271. $this->objects[$id]['info']['Mask'] = $tmp;
  1272. break;
  1273. case 'color-key':
  1274. $tmp = ' [ '.
  1275. $options['transparency']['r'] . ' ' . $options['transparency']['r'] . ' ' .
  1276. $options['transparency']['g'] . ' ' . $options['transparency']['g'] . ' ' .
  1277. $options['transparency']['b'] . ' ' . $options['transparency']['b'] .
  1278. ' ] ';
  1279. $this->objects[$id]['info']['Mask'] = $tmp;
  1280. break;
  1281. }
  1282. }
  1283. $this->objects[$id]['info']['ColorSpace'] = '/'.$options['color'];
  1284. }
  1285. $this->objects[$id]['info']['BitsPerComponent'] = $options['bitsPerComponent'];
  1286. }
  1287. // assign it a place in the named resource dictionary as an external object, according to
  1288. // the label passed in with it.
  1289. $this->o_pages($this->currentNode, 'xObject', array('label'=>$options['label'], 'objNum'=>$id));
  1290. // also make sure that we have the right procset object for it.
  1291. $this->o_procset($this->procsetObjectId, 'add', 'ImageC');
  1292. break;
  1293. case 'out':
  1294. $tmp = &$o['data'];
  1295. $res = "\n".$id." 0 obj\n<<";
  1296. foreach($o['info'] as $k=>$v) {
  1297. $res.= "\n/".$k.' '.$v;
  1298. }
  1299. if ($this->encrypted) {
  1300. $this->encryptInit($id);
  1301. $tmp = $this->ARC4($tmp);
  1302. }
  1303. $res.= "\n/Length ".mb_strlen($tmp, '8bit') .">>\nstream\n".$tmp."\nendstream\nendobj";
  1304. return $res;
  1305. }
  1306. }
  1307. /**
  1308. * graphics state object
  1309. */
  1310. protected function o_extGState($id, $action, $options = "") {
  1311. static $valid_params = array("LW", "LC", "LC", "LJ", "ML",
  1312. "D", "RI", "OP", "op", "OPM",
  1313. "Font", "BG", "BG2", "UCR",
  1314. "TR", "TR2", "HT", "FL",
  1315. "SM", "SA", "BM", "SMask",
  1316. "CA", "ca", "AIS", "TK");
  1317. if ($action !== "new") {
  1318. $o = & $this->objects[$id];
  1319. }
  1320. switch ($action) {
  1321. case "new":
  1322. $this->objects[$id] = array('t' => 'extGState', 'info' => $options);
  1323. // Tell the pages about the new resource
  1324. $this->numStates++;
  1325. $this->o_pages($this->currentNode, 'extGState', array("objNum" => $id, "stateNum" => $this->numStates));
  1326. break;
  1327. case "out":
  1328. $res =
  1329. "\n" . $id . " 0 obj\n".
  1330. "<< /Type /ExtGState\n";
  1331. foreach ($o["info"] as $parameter => $value) {
  1332. if ( !in_array($parameter, $valid_params))
  1333. continue;
  1334. $res.= "/$parameter $value\n";
  1335. }
  1336. $res.=
  1337. ">>\n".
  1338. "endobj";
  1339. return $res;
  1340. }
  1341. }
  1342. /**
  1343. * encryption object.
  1344. */
  1345. protected function o_encryption($id, $action, $options = '') {
  1346. if ($action !== 'new') {
  1347. $o = & $this->objects[$id];
  1348. }
  1349. switch ($action) {
  1350. case 'new':
  1351. // make the new object
  1352. $this->objects[$id] = array('t'=>'encryption', 'info'=>$options);
  1353. $this->arc4_objnum = $id;
  1354. // figure out the additional paramaters required
  1355. $pad = chr(0x28) .chr(0xBF) .chr(0x4E) .chr(0x5E) .chr(0x4E) .chr(0x75) .chr(0x8A) .chr(0x41) .chr(0x64) .chr(0x00) .chr(0x4E) .chr(0x56) .chr(0xFF) .chr(0xFA) .chr(0x01) .chr(0x08) .chr(0x2E) .chr(0x2E) .chr(0x00) .chr(0xB6) .chr(0xD0) .chr(0x68) .chr(0x3E) .chr(0x80) .chr(0x2F) .chr(0x0C) .chr(0xA9) .chr(0xFE) .chr(0x64) .chr(0x53) .chr(0x69) .chr(0x7A);
  1356. $len = mb_strlen($options['owner'], '8bit');
  1357. if ($len>32) {
  1358. $owner = substr($options['owner'], 0, 32);
  1359. } else if ($len<32) {
  1360. $owner = $options['owner'].substr($pad, 0, 32-$len);
  1361. } else {
  1362. $owner = $options['owner'];
  1363. }
  1364. $len = mb_strlen($options['user'], '8bit');
  1365. if ($len>32) {
  1366. $user = substr($options['user'], 0, 32);
  1367. } else if ($len<32) {
  1368. $user = $options['user'].substr($pad, 0, 32-$len);
  1369. } else {
  1370. $user = $options['user'];
  1371. }
  1372. $tmp = $this->md5_16($owner);
  1373. $okey = substr($tmp, 0, 5);
  1374. $this->ARC4_init($okey);
  1375. $ovalue = $this->ARC4($user);
  1376. $this->objects[$id]['info']['O'] = $ovalue;
  1377. // now make the u value, phew.
  1378. $tmp = $this->md5_16($user.$ovalue.chr($options['p']) .chr(255) .chr(255) .chr(255) .$this->fileIdentifier);
  1379. $ukey = substr($tmp, 0, 5);
  1380. $this->ARC4_init($ukey);
  1381. $this->encryptionKey = $ukey;
  1382. $this->encrypted = 1;
  1383. $uvalue = $this->ARC4($pad);
  1384. $this->objects[$id]['info']['U'] = $uvalue;
  1385. $this->encryptionKey = $ukey;
  1386. // initialize the arc4 array
  1387. break;
  1388. case 'out':
  1389. $res = "\n".$id." 0 obj\n<<";
  1390. $res.= "\n/Filter /Standard";
  1391. $res.= "\n/V 1";
  1392. $res.= "\n/R 2";
  1393. $res.= "\n/O (".$

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