PageRenderTime 76ms CodeModel.GetById 39ms RepoModel.GetById 0ms app.codeStats 1ms

/vendor/phenx/php-svg-lib/src/Svg/Surface/CPdf.php

https://bitbucket.org/openemr/openemr
PHP | 4768 lines | 3050 code | 741 blank | 977 comment | 535 complexity | 24eddef2f88f4cde5f504dcb06ef925d MD5 | raw file
Possible License(s): Apache-2.0, AGPL-1.0, GPL-2.0, LGPL-3.0, BSD-3-Clause, Unlicense, MPL-2.0, GPL-3.0, LGPL-2.1

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

  1. <?php
  2. /**
  3. * A PHP class to provide the basic functionality to create a pdf document without
  4. * any requirement for additional modules.
  5. *
  6. * Extended by Orion Richardson to support Unicode / UTF-8 characters using
  7. * TCPDF and others as a guide.
  8. *
  9. * @author Wayne Munro <pdf@ros.co.nz>
  10. * @author Orion Richardson <orionr@yahoo.com>
  11. * @author Helmut Tischer <htischer@weihenstephan.org>
  12. * @author Ryan H. Masten <ryan.masten@gmail.com>
  13. * @author Brian Sweeney <eclecticgeek@gmail.com>
  14. * @author Fabien MĂŠnager <fabien.menager@gmail.com>
  15. * @license Public Domain http://creativecommons.org/licenses/publicdomain/
  16. * @package Cpdf
  17. */
  18. namespace Svg\Surface;
  19. class CPdf
  20. {
  21. /**
  22. * @var integer The current number of pdf objects in the document
  23. */
  24. public $numObj = 0;
  25. /**
  26. * @var array This array contains all of the pdf objects, ready for final assembly
  27. */
  28. public $objects = array();
  29. /**
  30. * @var integer The objectId (number within the objects array) of the document catalog
  31. */
  32. public $catalogId;
  33. /**
  34. * @var array Array carrying information about the fonts that the system currently knows about
  35. * Used to ensure that a font is not loaded twice, among other things
  36. */
  37. public $fonts = array();
  38. /**
  39. * @var string The default font metrics file to use if no other font has been loaded.
  40. * The path to the directory containing the font metrics should be included
  41. */
  42. public $defaultFont = './fonts/Helvetica.afm';
  43. /**
  44. * @string A record of the current font
  45. */
  46. public $currentFont = '';
  47. /**
  48. * @var string The current base font
  49. */
  50. public $currentBaseFont = '';
  51. /**
  52. * @var integer The number of the current font within the font array
  53. */
  54. public $currentFontNum = 0;
  55. /**
  56. * @var integer
  57. */
  58. public $currentNode;
  59. /**
  60. * @var integer Object number of the current page
  61. */
  62. public $currentPage;
  63. /**
  64. * @var integer Object number of the currently active contents block
  65. */
  66. public $currentContents;
  67. /**
  68. * @var integer Number of fonts within the system
  69. */
  70. public $numFonts = 0;
  71. /**
  72. * @var integer Number of graphic state resources used
  73. */
  74. private $numStates = 0;
  75. /**
  76. * @var array Current color for fill operations, defaults to inactive value,
  77. * all three components should be between 0 and 1 inclusive when active
  78. */
  79. public $currentColor = null;
  80. /**
  81. * @var string Fill rule (nonzero or evenodd)
  82. */
  83. public $fillRule = "nonzero";
  84. /**
  85. * @var array Current color for stroke operations (lines etc.)
  86. */
  87. public $currentStrokeColor = null;
  88. /**
  89. * @var string Current style that lines are drawn in
  90. */
  91. public $currentLineStyle = '';
  92. /**
  93. * @var array Current line transparency (partial graphics state)
  94. */
  95. public $currentLineTransparency = array("mode" => "Normal", "opacity" => 1.0);
  96. /**
  97. * array Current fill transparency (partial graphics state)
  98. */
  99. public $currentFillTransparency = array("mode" => "Normal", "opacity" => 1.0);
  100. /**
  101. * @var array An array which is used to save the state of the document, mainly the colors and styles
  102. * it is used to temporarily change to another state, the change back to what it was before
  103. */
  104. public $stateStack = array();
  105. /**
  106. * @var integer Number of elements within the state stack
  107. */
  108. public $nStateStack = 0;
  109. /**
  110. * @var integer Number of page objects within the document
  111. */
  112. public $numPages = 0;
  113. /**
  114. * @var array Object Id storage stack
  115. */
  116. public $stack = array();
  117. /**
  118. * @var integer Number of elements within the object Id storage stack
  119. */
  120. public $nStack = 0;
  121. /**
  122. * an array which contains information about the objects which are not firmly attached to pages
  123. * these have been added with the addObject function
  124. */
  125. public $looseObjects = array();
  126. /**
  127. * array contains infomation about how the loose objects are to be added to the document
  128. */
  129. public $addLooseObjects = array();
  130. /**
  131. * @var integer The objectId of the information object for the document
  132. * this contains authorship, title etc.
  133. */
  134. public $infoObject = 0;
  135. /**
  136. * @var integer Number of images being tracked within the document
  137. */
  138. public $numImages = 0;
  139. /**
  140. * @var array An array containing options about the document
  141. * it defaults to turning on the compression of the objects
  142. */
  143. public $options = array('compression' => true);
  144. /**
  145. * @var integer The objectId of the first page of the document
  146. */
  147. public $firstPageId;
  148. /**
  149. * @var float Used to track the last used value of the inter-word spacing, this is so that it is known
  150. * when the spacing is changed.
  151. */
  152. public $wordSpaceAdjust = 0;
  153. /**
  154. * @var float Used to track the last used value of the inter-letter spacing, this is so that it is known
  155. * when the spacing is changed.
  156. */
  157. public $charSpaceAdjust = 0;
  158. /**
  159. * @var integer The object Id of the procset object
  160. */
  161. public $procsetObjectId;
  162. /**
  163. * @var array Store the information about the relationship between font families
  164. * this used so that the code knows which font is the bold version of another font, etc.
  165. * the value of this array is initialised in the constuctor function.
  166. */
  167. public $fontFamilies = array();
  168. /**
  169. * @var string Folder for php serialized formats of font metrics files.
  170. * If empty string, use same folder as original metrics files.
  171. * This can be passed in from class creator.
  172. * If this folder does not exist or is not writable, Cpdf will be **much** slower.
  173. * Because of potential trouble with php safe mode, folder cannot be created at runtime.
  174. */
  175. public $fontcache = '';
  176. /**
  177. * @var integer The version of the font metrics cache file.
  178. * This value must be manually incremented whenever the internal font data structure is modified.
  179. */
  180. public $fontcacheVersion = 6;
  181. /**
  182. * @var string Temporary folder.
  183. * If empty string, will attempty system tmp folder.
  184. * This can be passed in from class creator.
  185. * Only used for conversion of gd images to jpeg images.
  186. */
  187. public $tmp = '';
  188. /**
  189. * @var string Track if the current font is bolded or italicised
  190. */
  191. public $currentTextState = '';
  192. /**
  193. * @var string Messages are stored here during processing, these can be selected afterwards to give some useful debug information
  194. */
  195. public $messages = '';
  196. /**
  197. * @var string The ancryption array for the document encryption is stored here
  198. */
  199. public $arc4 = '';
  200. /**
  201. * @var integer The object Id of the encryption information
  202. */
  203. public $arc4_objnum = 0;
  204. /**
  205. * @var string The file identifier, used to uniquely identify a pdf document
  206. */
  207. public $fileIdentifier = '';
  208. /**
  209. * @var boolean A flag to say if a document is to be encrypted or not
  210. */
  211. public $encrypted = false;
  212. /**
  213. * @var string The encryption key for the encryption of all the document content (structure is not encrypted)
  214. */
  215. public $encryptionKey = '';
  216. /**
  217. * @var array Array which forms a stack to keep track of nested callback functions
  218. */
  219. public $callback = array();
  220. /**
  221. * @var integer The number of callback functions in the callback array
  222. */
  223. public $nCallback = 0;
  224. /**
  225. * @var array Store label->id pairs for named destinations, these will be used to replace internal links
  226. * done this way so that destinations can be defined after the location that links to them
  227. */
  228. public $destinations = array();
  229. /**
  230. * @var array Store the stack for the transaction commands, each item in here is a record of the values of all the
  231. * publiciables within the class, so that the user can rollback at will (from each 'start' command)
  232. * note that this includes the objects array, so these can be large.
  233. */
  234. public $checkpoint = '';
  235. /**
  236. * @var array Table of Image origin filenames and image labels which were already added with o_image().
  237. * Allows to merge identical images
  238. */
  239. public $imagelist = array();
  240. /**
  241. * @var boolean Whether the text passed in should be treated as Unicode or just local character set.
  242. */
  243. public $isUnicode = false;
  244. /**
  245. * @var string the JavaScript code of the document
  246. */
  247. public $javascript = '';
  248. /**
  249. * @var boolean whether the compression is possible
  250. */
  251. protected $compressionReady = false;
  252. /**
  253. * @var array Current page size
  254. */
  255. protected $currentPageSize = array("width" => 0, "height" => 0);
  256. /**
  257. * @var array All the chars that will be required in the font subsets
  258. */
  259. protected $stringSubsets = array();
  260. /**
  261. * @var string The target internal encoding
  262. */
  263. static protected $targetEncoding = 'iso-8859-1';
  264. /**
  265. * @var array The list of the core fonts
  266. */
  267. static protected $coreFonts = array(
  268. 'courier',
  269. 'courier-bold',
  270. 'courier-oblique',
  271. 'courier-boldoblique',
  272. 'helvetica',
  273. 'helvetica-bold',
  274. 'helvetica-oblique',
  275. 'helvetica-boldoblique',
  276. 'times-roman',
  277. 'times-bold',
  278. 'times-italic',
  279. 'times-bolditalic',
  280. 'symbol',
  281. 'zapfdingbats'
  282. );
  283. /**
  284. * Class constructor
  285. * This will start a new document
  286. *
  287. * @param array $pageSize Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
  288. * @param boolean $isUnicode Whether text will be treated as Unicode or not.
  289. * @param string $fontcache The font cache folder
  290. * @param string $tmp The temporary folder
  291. */
  292. function __construct($pageSize = array(0, 0, 612, 792), $isUnicode = false, $fontcache = '', $tmp = '')
  293. {
  294. $this->isUnicode = $isUnicode;
  295. $this->fontcache = $fontcache;
  296. $this->tmp = $tmp;
  297. $this->newDocument($pageSize);
  298. $this->compressionReady = function_exists('gzcompress');
  299. if (in_array('Windows-1252', mb_list_encodings())) {
  300. self::$targetEncoding = 'Windows-1252';
  301. }
  302. // also initialize the font families that are known about already
  303. $this->setFontFamily('init');
  304. // $this->fileIdentifier = md5('xxxxxxxx'.time());
  305. }
  306. /**
  307. * Document object methods (internal use only)
  308. *
  309. * There is about one object method for each type of object in the pdf document
  310. * Each function has the same call list ($id,$action,$options).
  311. * $id = the object ID of the object, or what it is to be if it is being created
  312. * $action = a string specifying the action to be performed, though ALL must support:
  313. * 'new' - create the object with the id $id
  314. * 'out' - produce the output for the pdf object
  315. * $options = optional, a string or array containing the various parameters for the object
  316. *
  317. * These, in conjunction with the output function are the ONLY way for output to be produced
  318. * within the pdf 'file'.
  319. */
  320. /**
  321. * Destination object, used to specify the location for the user to jump to, presently on opening
  322. */
  323. protected function o_destination($id, $action, $options = '')
  324. {
  325. if ($action !== 'new') {
  326. $o = &$this->objects[$id];
  327. }
  328. switch ($action) {
  329. case 'new':
  330. $this->objects[$id] = array('t' => 'destination', 'info' => array());
  331. $tmp = '';
  332. switch ($options['type']) {
  333. case 'XYZ':
  334. case 'FitR':
  335. $tmp = ' ' . $options['p3'] . $tmp;
  336. case 'FitH':
  337. case 'FitV':
  338. case 'FitBH':
  339. case 'FitBV':
  340. $tmp = ' ' . $options['p1'] . ' ' . $options['p2'] . $tmp;
  341. case 'Fit':
  342. case 'FitB':
  343. $tmp = $options['type'] . $tmp;
  344. $this->objects[$id]['info']['string'] = $tmp;
  345. $this->objects[$id]['info']['page'] = $options['page'];
  346. }
  347. break;
  348. case 'out':
  349. $tmp = $o['info'];
  350. $res = "\n$id 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj";
  351. return $res;
  352. }
  353. }
  354. /**
  355. * set the viewer preferences
  356. */
  357. protected function o_viewerPreferences($id, $action, $options = '')
  358. {
  359. if ($action !== 'new') {
  360. $o = &$this->objects[$id];
  361. }
  362. switch ($action) {
  363. case 'new':
  364. $this->objects[$id] = array('t' => 'viewerPreferences', 'info' => array());
  365. break;
  366. case 'add':
  367. foreach ($options as $k => $v) {
  368. switch ($k) {
  369. case 'HideToolbar':
  370. case 'HideMenubar':
  371. case 'HideWindowUI':
  372. case 'FitWindow':
  373. case 'CenterWindow':
  374. case 'NonFullScreenPageMode':
  375. case 'Direction':
  376. $o['info'][$k] = $v;
  377. break;
  378. }
  379. }
  380. break;
  381. case 'out':
  382. $res = "\n$id 0 obj\n<< ";
  383. foreach ($o['info'] as $k => $v) {
  384. $res .= "\n/$k $v";
  385. }
  386. $res .= "\n>>\n";
  387. return $res;
  388. }
  389. }
  390. /**
  391. * define the document catalog, the overall controller for the document
  392. */
  393. protected function o_catalog($id, $action, $options = '')
  394. {
  395. if ($action !== 'new') {
  396. $o = &$this->objects[$id];
  397. }
  398. switch ($action) {
  399. case 'new':
  400. $this->objects[$id] = array('t' => 'catalog', 'info' => array());
  401. $this->catalogId = $id;
  402. break;
  403. case 'outlines':
  404. case 'pages':
  405. case 'openHere':
  406. case 'javascript':
  407. $o['info'][$action] = $options;
  408. break;
  409. case 'viewerPreferences':
  410. if (!isset($o['info']['viewerPreferences'])) {
  411. $this->numObj++;
  412. $this->o_viewerPreferences($this->numObj, 'new');
  413. $o['info']['viewerPreferences'] = $this->numObj;
  414. }
  415. $vp = $o['info']['viewerPreferences'];
  416. $this->o_viewerPreferences($vp, 'add', $options);
  417. break;
  418. case 'out':
  419. $res = "\n$id 0 obj\n<< /Type /Catalog";
  420. foreach ($o['info'] as $k => $v) {
  421. switch ($k) {
  422. case 'outlines':
  423. $res .= "\n/Outlines $v 0 R";
  424. break;
  425. case 'pages':
  426. $res .= "\n/Pages $v 0 R";
  427. break;
  428. case 'viewerPreferences':
  429. $res .= "\n/ViewerPreferences $v 0 R";
  430. break;
  431. case 'openHere':
  432. $res .= "\n/OpenAction $v 0 R";
  433. break;
  434. case 'javascript':
  435. $res .= "\n/Names <</JavaScript $v 0 R>>";
  436. break;
  437. }
  438. }
  439. $res .= " >>\nendobj";
  440. return $res;
  441. }
  442. }
  443. /**
  444. * object which is a parent to the pages in the document
  445. */
  446. protected function o_pages($id, $action, $options = '')
  447. {
  448. if ($action !== 'new') {
  449. $o = &$this->objects[$id];
  450. }
  451. switch ($action) {
  452. case 'new':
  453. $this->objects[$id] = array('t' => 'pages', 'info' => array());
  454. $this->o_catalog($this->catalogId, 'pages', $id);
  455. break;
  456. case 'page':
  457. if (!is_array($options)) {
  458. // then it will just be the id of the new page
  459. $o['info']['pages'][] = $options;
  460. } else {
  461. // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
  462. // and pos is either 'before' or 'after', saying where this page will fit.
  463. if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) {
  464. $i = array_search($options['rid'], $o['info']['pages']);
  465. if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i] == $options['rid']) {
  466. // then there is a match
  467. // make a space
  468. switch ($options['pos']) {
  469. case 'before':
  470. $k = $i;
  471. break;
  472. case 'after':
  473. $k = $i + 1;
  474. break;
  475. default:
  476. $k = -1;
  477. break;
  478. }
  479. if ($k >= 0) {
  480. for ($j = count($o['info']['pages']) - 1; $j >= $k; $j--) {
  481. $o['info']['pages'][$j + 1] = $o['info']['pages'][$j];
  482. }
  483. $o['info']['pages'][$k] = $options['id'];
  484. }
  485. }
  486. }
  487. }
  488. break;
  489. case 'procset':
  490. $o['info']['procset'] = $options;
  491. break;
  492. case 'mediaBox':
  493. $o['info']['mediaBox'] = $options;
  494. // which should be an array of 4 numbers
  495. $this->currentPageSize = array('width' => $options[2], 'height' => $options[3]);
  496. break;
  497. case 'font':
  498. $o['info']['fonts'][] = array('objNum' => $options['objNum'], 'fontNum' => $options['fontNum']);
  499. break;
  500. case 'extGState':
  501. $o['info']['extGStates'][] = array('objNum' => $options['objNum'], 'stateNum' => $options['stateNum']);
  502. break;
  503. case 'xObject':
  504. $o['info']['xObjects'][] = array('objNum' => $options['objNum'], 'label' => $options['label']);
  505. break;
  506. case 'out':
  507. if (count($o['info']['pages'])) {
  508. $res = "\n$id 0 obj\n<< /Type /Pages\n/Kids [";
  509. foreach ($o['info']['pages'] as $v) {
  510. $res .= "$v 0 R\n";
  511. }
  512. $res .= "]\n/Count " . count($this->objects[$id]['info']['pages']);
  513. if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) ||
  514. isset($o['info']['procset']) ||
  515. (isset($o['info']['extGStates']) && count($o['info']['extGStates']))
  516. ) {
  517. $res .= "\n/Resources <<";
  518. if (isset($o['info']['procset'])) {
  519. $res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R";
  520. }
  521. if (isset($o['info']['fonts']) && count($o['info']['fonts'])) {
  522. $res .= "\n/Font << ";
  523. foreach ($o['info']['fonts'] as $finfo) {
  524. $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
  525. }
  526. $res .= "\n>>";
  527. }
  528. if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) {
  529. $res .= "\n/XObject << ";
  530. foreach ($o['info']['xObjects'] as $finfo) {
  531. $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
  532. }
  533. $res .= "\n>>";
  534. }
  535. if (isset($o['info']['extGStates']) && count($o['info']['extGStates'])) {
  536. $res .= "\n/ExtGState << ";
  537. foreach ($o['info']['extGStates'] as $gstate) {
  538. $res .= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
  539. }
  540. $res .= "\n>>";
  541. }
  542. $res .= "\n>>";
  543. if (isset($o['info']['mediaBox'])) {
  544. $tmp = $o['info']['mediaBox'];
  545. $res .= "\n/MediaBox [" . sprintf(
  546. '%.3F %.3F %.3F %.3F',
  547. $tmp[0],
  548. $tmp[1],
  549. $tmp[2],
  550. $tmp[3]
  551. ) . ']';
  552. }
  553. }
  554. $res .= "\n >>\nendobj";
  555. } else {
  556. $res = "\n$id 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
  557. }
  558. return $res;
  559. }
  560. }
  561. /**
  562. * define the outlines in the doc, empty for now
  563. */
  564. protected function o_outlines($id, $action, $options = '')
  565. {
  566. if ($action !== 'new') {
  567. $o = &$this->objects[$id];
  568. }
  569. switch ($action) {
  570. case 'new':
  571. $this->objects[$id] = array('t' => 'outlines', 'info' => array('outlines' => array()));
  572. $this->o_catalog($this->catalogId, 'outlines', $id);
  573. break;
  574. case 'outline':
  575. $o['info']['outlines'][] = $options;
  576. break;
  577. case 'out':
  578. if (count($o['info']['outlines'])) {
  579. $res = "\n$id 0 obj\n<< /Type /Outlines /Kids [";
  580. foreach ($o['info']['outlines'] as $v) {
  581. $res .= "$v 0 R ";
  582. }
  583. $res .= "] /Count " . count($o['info']['outlines']) . " >>\nendobj";
  584. } else {
  585. $res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
  586. }
  587. return $res;
  588. }
  589. }
  590. /**
  591. * an object to hold the font description
  592. */
  593. protected function o_font($id, $action, $options = '')
  594. {
  595. if ($action !== 'new') {
  596. $o = &$this->objects[$id];
  597. }
  598. switch ($action) {
  599. case 'new':
  600. $this->objects[$id] = array(
  601. 't' => 'font',
  602. 'info' => array(
  603. 'name' => $options['name'],
  604. 'fontFileName' => $options['fontFileName'],
  605. 'SubType' => 'Type1'
  606. )
  607. );
  608. $fontNum = $this->numFonts;
  609. $this->objects[$id]['info']['fontNum'] = $fontNum;
  610. // deal with the encoding and the differences
  611. if (isset($options['differences'])) {
  612. // then we'll need an encoding dictionary
  613. $this->numObj++;
  614. $this->o_fontEncoding($this->numObj, 'new', $options);
  615. $this->objects[$id]['info']['encodingDictionary'] = $this->numObj;
  616. } else {
  617. if (isset($options['encoding'])) {
  618. // we can specify encoding here
  619. switch ($options['encoding']) {
  620. case 'WinAnsiEncoding':
  621. case 'MacRomanEncoding':
  622. case 'MacExpertEncoding':
  623. $this->objects[$id]['info']['encoding'] = $options['encoding'];
  624. break;
  625. case 'none':
  626. break;
  627. default:
  628. $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
  629. break;
  630. }
  631. } else {
  632. $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
  633. }
  634. }
  635. if ($this->fonts[$options['fontFileName']]['isUnicode']) {
  636. // For Unicode fonts, we need to incorporate font data into
  637. // sub-sections that are linked from the primary font section.
  638. // Look at o_fontGIDtoCID and o_fontDescendentCID functions
  639. // for more informaiton.
  640. //
  641. // All of this code is adapted from the excellent changes made to
  642. // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
  643. $toUnicodeId = ++$this->numObj;
  644. $this->o_contents($toUnicodeId, 'new', 'raw');
  645. $this->objects[$id]['info']['toUnicode'] = $toUnicodeId;
  646. $stream = <<<EOT
  647. /CIDInit /ProcSet findresource begin
  648. 12 dict begin
  649. begincmap
  650. /CIDSystemInfo
  651. <</Registry (Adobe)
  652. /Ordering (UCS)
  653. /Supplement 0
  654. >> def
  655. /CMapName /Adobe-Identity-UCS def
  656. /CMapType 2 def
  657. 1 begincodespacerange
  658. <0000> <FFFF>
  659. endcodespacerange
  660. 1 beginbfrange
  661. <0000> <FFFF> <0000>
  662. endbfrange
  663. endcmap
  664. CMapName currentdict /CMap defineresource pop
  665. end
  666. end
  667. EOT;
  668. $res = "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
  669. $res .= "stream\n" . $stream . "endstream";
  670. $this->objects[$toUnicodeId]['c'] = $res;
  671. $cidFontId = ++$this->numObj;
  672. $this->o_fontDescendentCID($cidFontId, 'new', $options);
  673. $this->objects[$id]['info']['cidFont'] = $cidFontId;
  674. }
  675. // also tell the pages node about the new font
  676. $this->o_pages($this->currentNode, 'font', array('fontNum' => $fontNum, 'objNum' => $id));
  677. break;
  678. case 'add':
  679. foreach ($options as $k => $v) {
  680. switch ($k) {
  681. case 'BaseFont':
  682. $o['info']['name'] = $v;
  683. break;
  684. case 'FirstChar':
  685. case 'LastChar':
  686. case 'Widths':
  687. case 'FontDescriptor':
  688. case 'SubType':
  689. $this->addMessage('o_font ' . $k . " : " . $v);
  690. $o['info'][$k] = $v;
  691. break;
  692. }
  693. }
  694. // pass values down to descendent font
  695. if (isset($o['info']['cidFont'])) {
  696. $this->o_fontDescendentCID($o['info']['cidFont'], 'add', $options);
  697. }
  698. break;
  699. case 'out':
  700. if ($this->fonts[$this->objects[$id]['info']['fontFileName']]['isUnicode']) {
  701. // For Unicode fonts, we need to incorporate font data into
  702. // sub-sections that are linked from the primary font section.
  703. // Look at o_fontGIDtoCID and o_fontDescendentCID functions
  704. // for more informaiton.
  705. //
  706. // All of this code is adapted from the excellent changes made to
  707. // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
  708. $res = "\n$id 0 obj\n<</Type /Font\n/Subtype /Type0\n";
  709. $res .= "/BaseFont /" . $o['info']['name'] . "\n";
  710. // The horizontal identity mapping for 2-byte CIDs; may be used
  711. // with CIDFonts using any Registry, Ordering, and Supplement values.
  712. $res .= "/Encoding /Identity-H\n";
  713. $res .= "/DescendantFonts [" . $o['info']['cidFont'] . " 0 R]\n";
  714. $res .= "/ToUnicode " . $o['info']['toUnicode'] . " 0 R\n";
  715. $res .= ">>\n";
  716. $res .= "endobj";
  717. } else {
  718. $res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /" . $o['info']['SubType'] . "\n";
  719. $res .= "/Name /F" . $o['info']['fontNum'] . "\n";
  720. $res .= "/BaseFont /" . $o['info']['name'] . "\n";
  721. if (isset($o['info']['encodingDictionary'])) {
  722. // then place a reference to the dictionary
  723. $res .= "/Encoding " . $o['info']['encodingDictionary'] . " 0 R\n";
  724. } else {
  725. if (isset($o['info']['encoding'])) {
  726. // use the specified encoding
  727. $res .= "/Encoding /" . $o['info']['encoding'] . "\n";
  728. }
  729. }
  730. if (isset($o['info']['FirstChar'])) {
  731. $res .= "/FirstChar " . $o['info']['FirstChar'] . "\n";
  732. }
  733. if (isset($o['info']['LastChar'])) {
  734. $res .= "/LastChar " . $o['info']['LastChar'] . "\n";
  735. }
  736. if (isset($o['info']['Widths'])) {
  737. $res .= "/Widths " . $o['info']['Widths'] . " 0 R\n";
  738. }
  739. if (isset($o['info']['FontDescriptor'])) {
  740. $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
  741. }
  742. $res .= ">>\n";
  743. $res .= "endobj";
  744. }
  745. return $res;
  746. }
  747. }
  748. /**
  749. * a font descriptor, needed for including additional fonts
  750. */
  751. protected function o_fontDescriptor($id, $action, $options = '')
  752. {
  753. if ($action !== 'new') {
  754. $o = &$this->objects[$id];
  755. }
  756. switch ($action) {
  757. case 'new':
  758. $this->objects[$id] = array('t' => 'fontDescriptor', 'info' => $options);
  759. break;
  760. case 'out':
  761. $res = "\n$id 0 obj\n<< /Type /FontDescriptor\n";
  762. foreach ($o['info'] as $label => $value) {
  763. switch ($label) {
  764. case 'Ascent':
  765. case 'CapHeight':
  766. case 'Descent':
  767. case 'Flags':
  768. case 'ItalicAngle':
  769. case 'StemV':
  770. case 'AvgWidth':
  771. case 'Leading':
  772. case 'MaxWidth':
  773. case 'MissingWidth':
  774. case 'StemH':
  775. case 'XHeight':
  776. case 'CharSet':
  777. if (mb_strlen($value, '8bit')) {
  778. $res .= "/$label $value\n";
  779. }
  780. break;
  781. case 'FontFile':
  782. case 'FontFile2':
  783. case 'FontFile3':
  784. $res .= "/$label $value 0 R\n";
  785. break;
  786. case 'FontBBox':
  787. $res .= "/$label [$value[0] $value[1] $value[2] $value[3]]\n";
  788. break;
  789. case 'FontName':
  790. $res .= "/$label /$value\n";
  791. break;
  792. }
  793. }
  794. $res .= ">>\nendobj";
  795. return $res;
  796. }
  797. }
  798. /**
  799. * the font encoding
  800. */
  801. protected function o_fontEncoding($id, $action, $options = '')
  802. {
  803. if ($action !== 'new') {
  804. $o = &$this->objects[$id];
  805. }
  806. switch ($action) {
  807. case 'new':
  808. // the options array should contain 'differences' and maybe 'encoding'
  809. $this->objects[$id] = array('t' => 'fontEncoding', 'info' => $options);
  810. break;
  811. case 'out':
  812. $res = "\n$id 0 obj\n<< /Type /Encoding\n";
  813. if (!isset($o['info']['encoding'])) {
  814. $o['info']['encoding'] = 'WinAnsiEncoding';
  815. }
  816. if ($o['info']['encoding'] !== 'none') {
  817. $res .= "/BaseEncoding /" . $o['info']['encoding'] . "\n";
  818. }
  819. $res .= "/Differences \n[";
  820. $onum = -100;
  821. foreach ($o['info']['differences'] as $num => $label) {
  822. if ($num != $onum + 1) {
  823. // we cannot make use of consecutive numbering
  824. $res .= "\n$num /$label";
  825. } else {
  826. $res .= " /$label";
  827. }
  828. $onum = $num;
  829. }
  830. $res .= "\n]\n>>\nendobj";
  831. return $res;
  832. }
  833. }
  834. /**
  835. * a descendent cid font, needed for unicode fonts
  836. */
  837. protected function o_fontDescendentCID($id, $action, $options = '')
  838. {
  839. if ($action !== 'new') {
  840. $o = &$this->objects[$id];
  841. }
  842. switch ($action) {
  843. case 'new':
  844. $this->objects[$id] = array('t' => 'fontDescendentCID', 'info' => $options);
  845. // we need a CID system info section
  846. $cidSystemInfoId = ++$this->numObj;
  847. $this->o_contents($cidSystemInfoId, 'new', 'raw');
  848. $this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId;
  849. $res = "<</Registry (Adobe)\n"; // A string identifying an issuer of character collections
  850. $res .= "/Ordering (UCS)\n"; // A string that uniquely names a character collection issued by a specific registry
  851. $res .= "/Supplement 0\n"; // The supplement number of the character collection.
  852. $res .= ">>";
  853. $this->objects[$cidSystemInfoId]['c'] = $res;
  854. // and a CID to GID map
  855. $cidToGidMapId = ++$this->numObj;
  856. $this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options);
  857. $this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId;
  858. break;
  859. case 'add':
  860. foreach ($options as $k => $v) {
  861. switch ($k) {
  862. case 'BaseFont':
  863. $o['info']['name'] = $v;
  864. break;
  865. case 'FirstChar':
  866. case 'LastChar':
  867. case 'MissingWidth':
  868. case 'FontDescriptor':
  869. case 'SubType':
  870. $this->addMessage("o_fontDescendentCID $k : $v");
  871. $o['info'][$k] = $v;
  872. break;
  873. }
  874. }
  875. // pass values down to cid to gid map
  876. $this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options);
  877. break;
  878. case 'out':
  879. $res = "\n$id 0 obj\n";
  880. $res .= "<</Type /Font\n";
  881. $res .= "/Subtype /CIDFontType2\n";
  882. $res .= "/BaseFont /" . $o['info']['name'] . "\n";
  883. $res .= "/CIDSystemInfo " . $o['info']['cidSystemInfo'] . " 0 R\n";
  884. // if (isset($o['info']['FirstChar'])) {
  885. // $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
  886. // }
  887. // if (isset($o['info']['LastChar'])) {
  888. // $res.= "/LastChar ".$o['info']['LastChar']."\n";
  889. // }
  890. if (isset($o['info']['FontDescriptor'])) {
  891. $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
  892. }
  893. if (isset($o['info']['MissingWidth'])) {
  894. $res .= "/DW " . $o['info']['MissingWidth'] . "\n";
  895. }
  896. if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) {
  897. $cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths'];
  898. $w = '';
  899. foreach ($cid_widths as $cid => $width) {
  900. $w .= "$cid [$width] ";
  901. }
  902. $res .= "/W [$w]\n";
  903. }
  904. $res .= "/CIDToGIDMap " . $o['info']['cidToGidMap'] . " 0 R\n";
  905. $res .= ">>\n";
  906. $res .= "endobj";
  907. return $res;
  908. }
  909. }
  910. /**
  911. * a font glyph to character map, needed for unicode fonts
  912. */
  913. protected function o_fontGIDtoCIDMap($id, $action, $options = '')
  914. {
  915. if ($action !== 'new') {
  916. $o = &$this->objects[$id];
  917. }
  918. switch ($action) {
  919. case 'new':
  920. $this->objects[$id] = array('t' => 'fontGIDtoCIDMap', 'info' => $options);
  921. break;
  922. case 'out':
  923. $res = "\n$id 0 obj\n";
  924. $fontFileName = $o['info']['fontFileName'];
  925. $tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']);
  926. $compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) &&
  927. $this->fonts[$fontFileName]['CIDtoGID_Compressed'];
  928. if (!$compressed && isset($o['raw'])) {
  929. $res .= $tmp;
  930. } else {
  931. $res .= "<<";
  932. if (!$compressed && $this->compressionReady && $this->options['compression']) {
  933. // then implement ZLIB based compression on this content stream
  934. $compressed = true;
  935. $tmp = gzcompress($tmp, 6);
  936. }
  937. if ($compressed) {
  938. $res .= "\n/Filter /FlateDecode";
  939. }
  940. $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream";
  941. }
  942. $res .= "\nendobj";
  943. return $res;
  944. }
  945. }
  946. /**
  947. * the document procset, solves some problems with printing to old PS printers
  948. */
  949. protected function o_procset($id, $action, $options = '')
  950. {
  951. if ($action !== 'new') {
  952. $o = &$this->objects[$id];
  953. }
  954. switch ($action) {
  955. case 'new':
  956. $this->objects[$id] = array('t' => 'procset', 'info' => array('PDF' => 1, 'Text' => 1));
  957. $this->o_pages($this->currentNode, 'procset', $id);
  958. $this->procsetObjectId = $id;
  959. break;
  960. case 'add':
  961. // this is to add new items to the procset list, despite the fact that this is considered
  962. // obselete, the items are required for printing to some postscript printers
  963. switch ($options) {
  964. case 'ImageB':
  965. case 'ImageC':
  966. case 'ImageI':
  967. $o['info'][$options] = 1;
  968. break;
  969. }
  970. break;
  971. case 'out':
  972. $res = "\n$id 0 obj\n[";
  973. foreach ($o['info'] as $label => $val) {
  974. $res .= "/$label ";
  975. }
  976. $res .= "]\nendobj";
  977. return $res;
  978. }
  979. }
  980. /**
  981. * define the document information
  982. */
  983. protected function o_info($id, $action, $options = '')
  984. {
  985. if ($action !== 'new') {
  986. $o = &$this->objects[$id];
  987. }
  988. switch ($action) {
  989. case 'new':
  990. $this->infoObject = $id;
  991. $date = 'D:' . @date('Ymd');
  992. $this->objects[$id] = array(
  993. 't' => 'info',
  994. 'info' => array(
  995. 'Creator' => 'R and OS php pdf writer, http://www.ros.co.nz',
  996. 'CreationDate' => $date
  997. )
  998. );
  999. break;
  1000. case 'Title':
  1001. case 'Author':
  1002. case 'Subject':
  1003. case 'Keywords':
  1004. case 'Creator':
  1005. case 'Producer':
  1006. case 'CreationDate':
  1007. case 'ModDate':
  1008. case 'Trapped':
  1009. $o['info'][$action] = $options;
  1010. break;
  1011. case 'out':
  1012. if ($this->encrypted) {
  1013. $this->encryptInit($id);
  1014. }
  1015. $res = "\n$id 0 obj\n<<\n";
  1016. foreach ($o['info'] as $k => $v) {
  1017. $res .= "/$k (";
  1018. if ($this->encrypted) {
  1019. $v = $this->ARC4($v);
  1020. } // dates must be outputted as-is, without Unicode transformations
  1021. elseif (!in_array($k, array('CreationDate', 'ModDate'))) {
  1022. $v = $this->filterText($v);
  1023. }
  1024. $res .= $v;
  1025. $res .= ")\n";
  1026. }
  1027. $res .= ">>\nendobj";
  1028. return $res;
  1029. }
  1030. }
  1031. /**
  1032. * an action object, used to link to URLS initially
  1033. */
  1034. protected function o_action($id, $action, $options = '')
  1035. {
  1036. if ($action !== 'new') {
  1037. $o = &$this->objects[$id];
  1038. }
  1039. switch ($action) {
  1040. case 'new':
  1041. if (is_array($options)) {
  1042. $this->objects[$id] = array('t' => 'action', 'info' => $options, 'type' => $options['type']);
  1043. } else {
  1044. // then assume a URI action
  1045. $this->objects[$id] = array('t' => 'action', 'info' => $options, 'type' => 'URI');
  1046. }
  1047. break;
  1048. case 'out':
  1049. if ($this->encrypted) {
  1050. $this->encryptInit($id);
  1051. }
  1052. $res = "\n$id 0 obj\n<< /Type /Action";
  1053. switch ($o['type']) {
  1054. case 'ilink':
  1055. if (!isset($this->destinations[(string)$o['info']['label']])) {
  1056. break;
  1057. }
  1058. // there will be an 'label' setting, this is the name of the destination
  1059. $res .= "\n/S /GoTo\n/D " . $this->destinations[(string)$o['info']['label']] . " 0 R";
  1060. break;
  1061. case 'URI':
  1062. $res .= "\n/S /URI\n/URI (";
  1063. if ($this->encrypted) {
  1064. $res .= $this->filterText($this->ARC4($o['info']), true, false);
  1065. } else {
  1066. $res .= $this->filterText($o['info'], true, false);
  1067. }
  1068. $res .= ")";
  1069. break;
  1070. }
  1071. $res .= "\n>>\nendobj";
  1072. return $res;
  1073. }
  1074. }
  1075. /**
  1076. * an annotation object, this will add an annotation to the current page.
  1077. * initially will support just link annotations
  1078. */
  1079. protected function o_annotation($id, $action, $options = '')
  1080. {
  1081. if ($action !== 'new') {
  1082. $o = &$this->objects[$id];
  1083. }
  1084. switch ($action) {
  1085. case 'new':
  1086. // add the annotation to the current page
  1087. $pageId = $this->currentPage;
  1088. $this->o_page($pageId, 'annot', $id);
  1089. // and add the action object which is going to be required
  1090. switch ($options['type']) {
  1091. case 'link':
  1092. $this->objects[$id] = array('t' => 'annotation', 'info' => $options);
  1093. $this->numObj++;
  1094. $this->o_action($this->numObj, 'new', $options['url']);
  1095. $this->objects[$id]['info']['actionId'] = $this->numObj;
  1096. break;
  1097. case 'ilink':
  1098. // this is to a named internal link
  1099. $label = $options['label'];
  1100. $this->objects[$id] = array('t' => 'annotation', 'info' => $options);
  1101. $this->numObj++;
  1102. $this->o_action($this->numObj, 'new', array('type' => 'ilink', 'label' => $label));
  1103. $this->objects[$id]['info']['actionId'] = $this->numObj;
  1104. break;
  1105. }
  1106. break;
  1107. case 'out':
  1108. $res = "\n$id 0 obj\n<< /Type /Annot";
  1109. switch ($o['info']['type']) {
  1110. case 'link':
  1111. case 'ilink':
  1112. $res .= "\n/Subtype /Link";
  1113. break;
  1114. }
  1115. $res .= "\n/A " . $o['info']['actionId'] . " 0 R";
  1116. $res .= "\n/Border [0 0 0]";
  1117. $res .= "\n/H /I";
  1118. $res .= "\n/Rect [ ";
  1119. foreach ($o['info']['rect'] as $v) {
  1120. $res .= sprintf("%.4F ", $v);
  1121. }
  1122. $res .= "]";
  1123. $res .= "\n>>\nendobj";
  1124. return $res;
  1125. }
  1126. }
  1127. /**
  1128. * a page object, it also creates a contents object to hold its contents
  1129. */
  1130. protected function o_page($id, $action, $options = '')
  1131. {
  1132. if ($action !== 'new') {
  1133. $o = &$this->objects[$id];
  1134. }
  1135. switch ($action) {
  1136. case 'new':
  1137. $this->numPages++;
  1138. $this->objects[$id] = array(
  1139. 't' => 'page',
  1140. 'info' => array(
  1141. 'parent' => $this->currentNode,
  1142. 'pageNum' => $this->numPages
  1143. )
  1144. );
  1145. if (is_array($options)) {
  1146. // then this must be a page insertion, array should contain 'rid','pos'=[before|after]
  1147. $options['id'] = $id;
  1148. $this->o_pages($this->currentNode, 'page', $options);
  1149. } else {
  1150. $this->o_pages($this->currentNode, 'page', $id);
  1151. }
  1152. $this->currentPage = $id;
  1153. //make a contents object to go with this page
  1154. $this->numObj++;
  1155. $this->o_contents($this->numObj, 'new', $id);
  1156. $this->currentContents = $this->numObj;
  1157. $this->objects[$id]['info']['contents'] = array();
  1158. $this->objects[$id]['info']['contents'][] = $this->numObj;
  1159. $match = ($this->numPages % 2 ? 'odd' : 'even');
  1160. foreach ($this->addLooseObjects as $oId => $target) {
  1161. if ($target === 'all' || $match === $target) {
  1162. $this->objects[$id]['info']['contents'][] = $oId;
  1163. }
  1164. }
  1165. break;
  1166. case 'content':
  1167. $o['info']['contents'][] = $options;
  1168. break;
  1169. case 'annot':
  1170. // add an annotation to this page
  1171. if (!isset($o['info']['annot'])) {
  1172. $o['info']['annot'] = array();
  1173. }
  1174. // $options should contain the id of the annotation dictionary
  1175. $o['info']['annot'][] = $options;
  1176. break;
  1177. case 'out':
  1178. $res = "\n$id 0 obj\n<< /Type /Page";
  1179. $res .= "\n/Parent " . $o['info']['parent'] . " 0 R";
  1180. if (isset($o['info']['annot'])) {
  1181. $res .= "\n/Annots [";
  1182. foreach ($o['info']['annot'] as $aId) {
  1183. $res .= " $aId 0 R";
  1184. }
  1185. $res .= " ]";
  1186. }
  1187. $count = count($o['info']['contents']);
  1188. if ($count == 1) {
  1189. $res .= "\n/Contents " . $o['info']['contents'][0] . " 0 R";
  1190. } else {
  1191. if ($count > 1) {
  1192. $res .= "\n/Contents [\n";
  1193. // reverse the page contents so added objects are below normal content
  1194. //foreach (array_reverse($o['info']['contents']) as $cId) {
  1195. // Back to normal now that I've got transparency working --Benj
  1196. foreach ($o['info']['contents'] as $cId) {
  1197. $res .= "$cId 0 R\n";
  1198. }
  1199. $res .= "]";
  1200. }
  1201. }
  1202. $res .= "\n>>\nendobj";
  1203. return $res;
  1204. }
  1205. }
  1206. /**
  1207. * the contents objects hold all of the content which appears on pages
  1208. */
  1209. protected function o_contents($id, $action, $options = '')
  1210. {
  1211. if ($action !== 'new') {
  1212. $o = &$this->objects[$id];
  1213. }
  1214. switch ($action) {
  1215. case 'n…

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