PageRenderTime 45ms CodeModel.GetById 4ms RepoModel.GetById 0ms app.codeStats 1ms

/vendor/dompdf/dompdf/lib/Cpdf.php

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

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