PageRenderTime 79ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/includes/dompdf/lib/class.pdf.php

https://bitbucket.org/themattreid/kontrollbase
PHP | 4972 lines | 2219 code | 1870 blank | 883 comment | 492 complexity | bb16251f4981d460b5177eb1346f11a8 MD5 | raw file
Possible License(s): LGPL-2.1
  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 they companion class CezPdf can be used to extend this class and dramatically
  11. * simplify the creation of documents.
  12. *
  13. * IMPORTANT NOTE
  14. * there is no warranty, implied or otherwise with this software.
  15. *
  16. * LICENCE
  17. * This code has been placed in the Public Domain for all to enjoy.
  18. *
  19. * @author Wayne Munro <pdf@ros.co.nz>
  20. * @version 009
  21. * @package Cpdf
  22. */
  23. class Cpdf {
  24. /**
  25. * the current number of pdf objects in the document
  26. */
  27. public $numObj = 0;
  28. /**
  29. * this array contains all of the pdf objects, ready for final assembly
  30. */
  31. public $objects = array();
  32. /**
  33. * the objectId (number within the objects array) of the document catalog
  34. */
  35. public $catalogId;
  36. /**
  37. * array carrying information about the fonts that the system currently knows about
  38. * used to ensure that a font is not loaded twice, among other things
  39. */
  40. public $fonts = array();
  41. /**
  42. * a record of the current font
  43. */
  44. public $currentFont = '';
  45. /**
  46. * the current base font
  47. */
  48. public $currentBaseFont = '';
  49. /**
  50. * the number of the current font within the font array
  51. */
  52. public $currentFontNum = 0;
  53. /**
  54. *
  55. */
  56. public $currentNode;
  57. /**
  58. * object number of the current page
  59. */
  60. public $currentPage;
  61. /**
  62. * object number of the currently active contents block
  63. */
  64. public $currentContents;
  65. /**
  66. * number of fonts within the system
  67. */
  68. public $numFonts = 0;
  69. /**
  70. * Number of graphic state resources used
  71. */
  72. private $numStates = 0;
  73. /**
  74. * current colour for fill operations, defaults to inactive value, all three components should be between 0 and 1 inclusive when active
  75. */
  76. public $currentColour = array('r'=>-1, 'g'=>-1, 'b'=>-1);
  77. /**
  78. * current colour for stroke operations (lines etc.)
  79. */
  80. public $currentStrokeColour = array('r'=>-1, 'g'=>-1, 'b'=>-1);
  81. /**
  82. * current style that lines are drawn in
  83. */
  84. public $currentLineStyle = '';
  85. /**
  86. * current line transparency (partial graphics state)
  87. */
  88. public $currentLineTransparency = array("mode" => "Normal", "opacity" => 1.0);
  89. /**
  90. * current fill transparency (partial graphics state)
  91. */
  92. public $currentFillTransparency = array("mode" => "Normal", "opacity" => 1.0);
  93. /**
  94. * an array which is used to save the state of the document, mainly the colours and styles
  95. * it is used to temporarily change to another state, the change back to what it was before
  96. */
  97. public $stateStack = array();
  98. /**
  99. * number of elements within the state stack
  100. */
  101. public $nStateStack = 0;
  102. /**
  103. * number of page objects within the document
  104. */
  105. public $numPages = 0;
  106. /**
  107. * object Id storage stack
  108. */
  109. public $stack = array();
  110. /**
  111. * number of elements within the object Id storage stack
  112. */
  113. public $nStack = 0;
  114. /**
  115. * an array which contains information about the objects which are not firmly attached to pages
  116. * these have been added with the addObject function
  117. */
  118. public $looseObjects = array();
  119. /**
  120. * array contains infomation about how the loose objects are to be added to the document
  121. */
  122. public $addLooseObjects = array();
  123. /**
  124. * the objectId of the information object for the document
  125. * this contains authorship, title etc.
  126. */
  127. public $infoObject = 0;
  128. /**
  129. * number of images being tracked within the document
  130. */
  131. public $numImages = 0;
  132. /**
  133. * an array containing options about the document
  134. * it defaults to turning on the compression of the objects
  135. */
  136. public $options = array('compression'=>1);
  137. /**
  138. * the objectId of the first page of the document
  139. */
  140. public $firstPageId;
  141. /**
  142. * used to track the last used value of the inter-word spacing, this is so that it is known
  143. * when the spacing is changed.
  144. */
  145. public $wordSpaceAdjust = 0;
  146. /**
  147. * the object Id of the procset object
  148. */
  149. public $procsetObjectId;
  150. /**
  151. * store the information about the relationship between font families
  152. * this used so that the code knows which font is the bold version of another font, etc.
  153. * the value of this array is initialised in the constuctor function.
  154. */
  155. public $fontFamilies = array();
  156. /**
  157. * track if the current font is bolded or italicised
  158. */
  159. public $currentTextState = '';
  160. /**
  161. * messages are stored here during processing, these can be selected afterwards to give some useful debug information
  162. */
  163. public $messages = '';
  164. /**
  165. * the ancryption array for the document encryption is stored here
  166. */
  167. public $arc4 = '';
  168. /**
  169. * the object Id of the encryption information
  170. */
  171. public $arc4_objnum = 0;
  172. /**
  173. * the file identifier, used to uniquely identify a pdf document
  174. */
  175. public $fileIdentifier = '';
  176. /**
  177. * a flag to say if a document is to be encrypted or not
  178. */
  179. public $encrypted = 0;
  180. /**
  181. * the ancryption key for the encryption of all the document content (structure is not encrypted)
  182. */
  183. public $encryptionKey = '';
  184. /**
  185. * array which forms a stack to keep track of nested callback functions
  186. */
  187. public $callback = array();
  188. /**
  189. * the number of callback functions in the callback array
  190. */
  191. public $nCallback = 0;
  192. /**
  193. * store label->id pairs for named destinations, these will be used to replace internal links
  194. * done this way so that destinations can be defined after the location that links to them
  195. */
  196. public $destinations = array();
  197. /**
  198. * store the stack for the transaction commands, each item in here is a record of the values of all the
  199. * publiciables within the class, so that the user can rollback at will (from each 'start' command)
  200. * note that this includes the objects array, so these can be large.
  201. */
  202. public $checkpoint = '';
  203. /**
  204. * class constructor
  205. * this will start a new document
  206. * @var array array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
  207. */
  208. function Cpdf ($pageSize = array(0, 0, 612, 792)) {
  209. $this->newDocument($pageSize);
  210. // also initialize the font families that are known about already
  211. $this->setFontFamily('init');
  212. // $this->fileIdentifier = md5('xxxxxxxx'.time());
  213. }
  214. /**
  215. * Document object methods (internal use only)
  216. *
  217. * There is about one object method for each type of object in the pdf document
  218. * Each function has the same call list ($id,$action,$options).
  219. * $id = the object ID of the object, or what it is to be if it is being created
  220. * $action = a string specifying the action to be performed, though ALL must support:
  221. * 'new' - create the object with the id $id
  222. * 'out' - produce the output for the pdf object
  223. * $options = optional, a string or array containing the various parameters for the object
  224. *
  225. * These, in conjunction with the output function are the ONLY way for output to be produced
  226. * within the pdf 'file'.
  227. */
  228. /**
  229. *destination object, used to specify the location for the user to jump to, presently on opening
  230. */
  231. function o_destination($id, $action, $options = '') {
  232. if ($action != 'new') {
  233. $o = & $this->objects[$id];
  234. }
  235. switch ($action) {
  236. case 'new':
  237. $this->objects[$id] = array('t'=>'destination', 'info'=>array());
  238. $tmp = '';
  239. switch ($options['type']) {
  240. case 'XYZ':
  241. case 'FitR':
  242. $tmp = ' '.$options['p3'].$tmp;
  243. case 'FitH':
  244. case 'FitV':
  245. case 'FitBH':
  246. case 'FitBV':
  247. $tmp = ' '.$options['p1'].' '.$options['p2'].$tmp;
  248. case 'Fit':
  249. case 'FitB':
  250. $tmp = $options['type'].$tmp;
  251. $this->objects[$id]['info']['string'] = $tmp;
  252. $this->objects[$id]['info']['page'] = $options['page'];
  253. }
  254. break;
  255. case 'out':
  256. $tmp = $o['info'];
  257. $res = "\n".$id." 0 obj\n".'['.$tmp['page'].' 0 R /'.$tmp['string']."]\nendobj\n";
  258. return $res;
  259. break;
  260. }
  261. }
  262. /**
  263. * set the viewer preferences
  264. */
  265. function o_viewerPreferences($id, $action, $options = '') {
  266. if ($action != 'new') {
  267. $o = & $this->objects[$id];
  268. }
  269. switch ($action) {
  270. case 'new':
  271. $this->objects[$id] = array('t'=>'viewerPreferences', 'info'=>array());
  272. break;
  273. case 'add':
  274. foreach($options as $k=>$v) {
  275. switch ($k) {
  276. case 'HideToolbar':
  277. case 'HideMenubar':
  278. case 'HideWindowUI':
  279. case 'FitWindow':
  280. case 'CenterWindow':
  281. case 'NonFullScreenPageMode':
  282. case 'Direction':
  283. $o['info'][$k] = $v;
  284. break;
  285. }
  286. }
  287. break;
  288. case 'out':
  289. $res = "\n".$id." 0 obj\n".'<< ';
  290. foreach($o['info'] as $k=>$v) {
  291. $res.= "\n/".$k.' '.$v;
  292. }
  293. $res.= "\n>>\n";
  294. return $res;
  295. break;
  296. }
  297. }
  298. /**
  299. * define the document catalog, the overall controller for the document
  300. */
  301. function o_catalog($id, $action, $options = '') {
  302. if ($action != 'new') {
  303. $o = & $this->objects[$id];
  304. }
  305. switch ($action) {
  306. case 'new':
  307. $this->objects[$id] = array('t'=>'catalog', 'info'=>array());
  308. $this->catalogId = $id;
  309. break;
  310. case 'outlines':
  311. case 'pages':
  312. case 'openHere':
  313. $o['info'][$action] = $options;
  314. break;
  315. case 'viewerPreferences':
  316. if (!isset($o['info']['viewerPreferences'])) {
  317. $this->numObj++;
  318. $this->o_viewerPreferences($this->numObj, 'new');
  319. $o['info']['viewerPreferences'] = $this->numObj;
  320. }
  321. $vp = $o['info']['viewerPreferences'];
  322. $this->o_viewerPreferences($vp, 'add', $options);
  323. break;
  324. case 'out':
  325. $res = "\n".$id." 0 obj\n".'<< /Type /Catalog';
  326. foreach($o['info'] as $k=>$v) {
  327. switch ($k) {
  328. case 'outlines':
  329. $res.= "\n".'/Outlines '.$v.' 0 R';
  330. break;
  331. case 'pages':
  332. $res.= "\n".'/Pages '.$v.' 0 R';
  333. break;
  334. case 'viewerPreferences':
  335. $res.= "\n".'/ViewerPreferences '.$o['info']['viewerPreferences'].' 0 R';
  336. break;
  337. case 'openHere':
  338. $res.= "\n".'/OpenAction '.$o['info']['openHere'].' 0 R';
  339. break;
  340. }
  341. }
  342. $res.= " >>\nendobj";
  343. return $res;
  344. break;
  345. }
  346. }
  347. /**
  348. * object which is a parent to the pages in the document
  349. */
  350. function o_pages($id, $action, $options = '') {
  351. if ($action != 'new') {
  352. $o = & $this->objects[$id];
  353. }
  354. switch ($action) {
  355. case 'new':
  356. $this->objects[$id] = array('t'=>'pages', 'info'=>array());
  357. $this->o_catalog($this->catalogId, 'pages', $id);
  358. break;
  359. case 'page':
  360. if (!is_array($options)) {
  361. // then it will just be the id of the new page
  362. $o['info']['pages'][] = $options;
  363. } else {
  364. // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
  365. // and pos is either 'before' or 'after', saying where this page will fit.
  366. if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) {
  367. $i = array_search($options['rid'], $o['info']['pages']);
  368. if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i] == $options['rid']) {
  369. // then there is a match
  370. // make a space
  371. switch ($options['pos']) {
  372. case 'before':
  373. $k = $i;
  374. break;
  375. case 'after':
  376. $k = $i+1;
  377. break;
  378. default:
  379. $k = -1;
  380. break;
  381. }
  382. if ($k >= 0) {
  383. for ($j = count($o['info']['pages']) -1;$j >= $k;$j--) {
  384. $o['info']['pages'][$j+1] = $o['info']['pages'][$j];
  385. }
  386. $o['info']['pages'][$k] = $options['id'];
  387. }
  388. }
  389. }
  390. }
  391. break;
  392. case 'procset':
  393. $o['info']['procset'] = $options;
  394. break;
  395. case 'mediaBox':
  396. $o['info']['mediaBox'] = $options;
  397. // which should be an array of 4 numbers
  398. break;
  399. case 'font':
  400. $o['info']['fonts'][] = array('objNum'=>$options['objNum'], 'fontNum'=>$options['fontNum']);
  401. break;
  402. case 'extGState':
  403. $o['info']['extGStates'][] = array('objNum' => $options['objNum'], 'stateNum' => $options['stateNum']);
  404. break;
  405. case 'xObject':
  406. $o['info']['xObjects'][] = array('objNum'=>$options['objNum'], 'label'=>$options['label']);
  407. break;
  408. case 'out':
  409. if (count($o['info']['pages'])) {
  410. $res = "\n".$id." 0 obj\n<< /Type /Pages\n/Kids [";
  411. foreach($o['info']['pages'] as $k=>$v) {
  412. $res.= $v." 0 R\n";
  413. }
  414. $res.= "]\n/Count ".count($this->objects[$id]['info']['pages']);
  415. if ( (isset($o['info']['fonts']) && count($o['info']['fonts'])) ||
  416. isset($o['info']['procset']) ||
  417. (isset($o['info']['extGStates']) && count($o['info']['extGStates']))) {
  418. $res.= "\n/Resources <<";
  419. if (isset($o['info']['procset'])) {
  420. $res.= "\n/ProcSet ".$o['info']['procset']." 0 R";
  421. }
  422. if (isset($o['info']['fonts']) && count($o['info']['fonts'])) {
  423. $res.= "\n/Font << ";
  424. foreach($o['info']['fonts'] as $finfo) {
  425. $res.= "\n/F".$finfo['fontNum']." ".$finfo['objNum']." 0 R";
  426. }
  427. $res.= " >>";
  428. }
  429. if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) {
  430. $res.= "\n/XObject << ";
  431. foreach($o['info']['xObjects'] as $finfo) {
  432. $res.= "\n/".$finfo['label']." ".$finfo['objNum']." 0 R";
  433. }
  434. $res.= " >>";
  435. }
  436. if ( isset($o['info']['extGStates']) && count($o['info']['extGStates'])) {
  437. $res.= "\n/ExtGState << ";
  438. foreach ($o['info']['extGStates'] as $gstate) {
  439. $res.= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
  440. }
  441. $res.= " >>";
  442. }
  443. $res.= "\n>>";
  444. if (isset($o['info']['mediaBox'])) {
  445. $tmp = $o['info']['mediaBox'];
  446. $res.= "\n/MediaBox [".sprintf('%.3f', $tmp[0]) .' '.sprintf('%.3f', $tmp[1]) .' '.sprintf('%.3f', $tmp[2]) .' '.sprintf('%.3f', $tmp[3]) .']';
  447. }
  448. }
  449. $res.= "\n >>\nendobj";
  450. } else {
  451. $res = "\n".$id." 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
  452. }
  453. return $res;
  454. break;
  455. }
  456. }
  457. /**
  458. * define the outlines in the doc, empty for now
  459. */
  460. function o_outlines($id, $action, $options = '') {
  461. if ($action != 'new') {
  462. $o = & $this->objects[$id];
  463. }
  464. switch ($action) {
  465. case 'new':
  466. $this->objects[$id] = array('t'=>'outlines', 'info'=>array('outlines'=>array()));
  467. $this->o_catalog($this->catalogId, 'outlines', $id);
  468. break;
  469. case 'outline':
  470. $o['info']['outlines'][] = $options;
  471. break;
  472. case 'out':
  473. if (count($o['info']['outlines'])) {
  474. $res = "\n".$id." 0 obj\n<< /Type /Outlines /Kids [";
  475. foreach($o['info']['outlines'] as $k=>$v) {
  476. $res.= $v." 0 R ";
  477. }
  478. $res.= "] /Count ".count($o['info']['outlines']) ." >>\nendobj";
  479. } else {
  480. $res = "\n".$id." 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
  481. }
  482. return $res;
  483. break;
  484. }
  485. }
  486. /**
  487. * an object to hold the font description
  488. */
  489. function o_font($id, $action, $options = '') {
  490. if ($action != 'new') {
  491. $o = & $this->objects[$id];
  492. }
  493. switch ($action) {
  494. case 'new':
  495. $this->objects[$id] = array('t' => 'font', 'info' => array('name' => $options['name'], 'SubType' => 'Type1'));
  496. $fontNum = $this->numFonts;
  497. $this->objects[$id]['info']['fontNum'] = $fontNum;
  498. // deal with the encoding and the differences
  499. if (isset($options['differences'])) {
  500. // then we'll need an encoding dictionary
  501. $this->numObj++;
  502. $this->o_fontEncoding($this->numObj, 'new', $options);
  503. $this->objects[$id]['info']['encodingDictionary'] = $this->numObj;
  504. } else if (isset($options['encoding'])) {
  505. // we can specify encoding here
  506. switch ($options['encoding']) {
  507. case 'WinAnsiEncoding':
  508. case 'MacRomanEncoding':
  509. case 'MacExpertEncoding':
  510. $this->objects[$id]['info']['encoding'] = $options['encoding'];
  511. break;
  512. case 'none':
  513. break;
  514. default:
  515. $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
  516. break;
  517. }
  518. } else {
  519. $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
  520. }
  521. // also tell the pages node about the new font
  522. $this->o_pages($this->currentNode, 'font', array('fontNum' => $fontNum, 'objNum' => $id));
  523. break;
  524. case 'add':
  525. foreach ($options as $k => $v) {
  526. switch ($k) {
  527. case 'BaseFont':
  528. $o['info']['name'] = $v;
  529. break;
  530. case 'FirstChar':
  531. case 'LastChar':
  532. case 'Widths':
  533. case 'FontDescriptor':
  534. case 'SubType':
  535. $this->addMessage('o_font '.$k." : ".$v);
  536. $o['info'][$k] = $v;
  537. break;
  538. }
  539. }
  540. break;
  541. case 'out':
  542. $res = "\n".$id." 0 obj\n<< /Type /Font\n/Subtype /".$o['info']['SubType']."\n";
  543. $res.= "/Name /F".$o['info']['fontNum']."\n";
  544. $res.= "/BaseFont /".$o['info']['name']."\n";
  545. if (isset($o['info']['encodingDictionary'])) {
  546. // then place a reference to the dictionary
  547. $res.= "/Encoding ".$o['info']['encodingDictionary']." 0 R\n";
  548. } else if (isset($o['info']['encoding'])) {
  549. // use the specified encoding
  550. $res.= "/Encoding /".$o['info']['encoding']."\n";
  551. }
  552. if (isset($o['info']['FirstChar'])) {
  553. $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
  554. }
  555. if (isset($o['info']['LastChar'])) {
  556. $res.= "/LastChar ".$o['info']['LastChar']."\n";
  557. }
  558. if (isset($o['info']['Widths'])) {
  559. $res.= "/Widths ".$o['info']['Widths']." 0 R\n";
  560. }
  561. if (isset($o['info']['FontDescriptor'])) {
  562. $res.= "/FontDescriptor ".$o['info']['FontDescriptor']." 0 R\n";
  563. }
  564. $res.= ">>\nendobj";
  565. return $res;
  566. break;
  567. }
  568. }
  569. /**
  570. * a font descriptor, needed for including additional fonts
  571. */
  572. function o_fontDescriptor($id, $action, $options = '') {
  573. if ($action != 'new') {
  574. $o = & $this->objects[$id];
  575. }
  576. switch ($action) {
  577. case 'new':
  578. $this->objects[$id] = array('t'=>'fontDescriptor', 'info'=>$options);
  579. break;
  580. case 'out':
  581. $res = "\n".$id." 0 obj\n<< /Type /FontDescriptor\n";
  582. foreach ($o['info'] as $label => $value) {
  583. switch ($label) {
  584. case 'Ascent':
  585. case 'CapHeight':
  586. case 'Descent':
  587. case 'Flags':
  588. case 'ItalicAngle':
  589. case 'StemV':
  590. case 'AvgWidth':
  591. case 'Leading':
  592. case 'MaxWidth':
  593. case 'MissingWidth':
  594. case 'StemH':
  595. case 'XHeight':
  596. case 'CharSet':
  597. if (strlen($value)) {
  598. $res.= '/'.$label.' '.$value."\n";
  599. }
  600. break;
  601. case 'FontFile':
  602. case 'FontFile2':
  603. case 'FontFile3':
  604. $res.= '/'.$label.' '.$value." 0 R\n";
  605. break;
  606. case 'FontBBox':
  607. $res.= '/'.$label.' ['.$value[0].' '.$value[1].' '.$value[2].' '.$value[3]."]\n";
  608. break;
  609. case 'FontName':
  610. $res.= '/'.$label.' /'.$value."\n";
  611. break;
  612. }
  613. }
  614. $res.= ">>\nendobj";
  615. return $res;
  616. break;
  617. }
  618. }
  619. /**
  620. * the font encoding
  621. */
  622. function o_fontEncoding($id, $action, $options = '') {
  623. if ($action != 'new') {
  624. $o = & $this->objects[$id];
  625. }
  626. switch ($action) {
  627. case 'new':
  628. // the options array should contain 'differences' and maybe 'encoding'
  629. $this->objects[$id] = array('t'=>'fontEncoding', 'info'=>$options);
  630. break;
  631. case 'out':
  632. $res = "\n".$id." 0 obj\n<< /Type /Encoding\n";
  633. if (!isset($o['info']['encoding'])) {
  634. $o['info']['encoding'] = 'WinAnsiEncoding';
  635. }
  636. if ($o['info']['encoding'] != 'none') {
  637. $res.= "/BaseEncoding /".$o['info']['encoding']."\n";
  638. }
  639. $res.= "/Differences \n[";
  640. $onum = -100;
  641. foreach($o['info']['differences'] as $num=>$label) {
  642. if ($num != $onum+1) {
  643. // we cannot make use of consecutive numbering
  644. $res.= "\n".$num." /".$label;
  645. } else {
  646. $res.= " /".$label;
  647. }
  648. $onum = $num;
  649. }
  650. $res.= "\n]\n>>\nendobj";
  651. return $res;
  652. break;
  653. }
  654. }
  655. /**
  656. * the document procset, solves some problems with printing to old PS printers
  657. */
  658. function o_procset($id, $action, $options = '') {
  659. if ($action != 'new') {
  660. $o = & $this->objects[$id];
  661. }
  662. switch ($action) {
  663. case 'new':
  664. $this->objects[$id] = array('t'=>'procset', 'info'=>array('PDF'=>1, 'Text'=>1));
  665. $this->o_pages($this->currentNode, 'procset', $id);
  666. $this->procsetObjectId = $id;
  667. break;
  668. case 'add':
  669. // this is to add new items to the procset list, despite the fact that this is considered
  670. // obselete, the items are required for printing to some postscript printers
  671. switch ($options) {
  672. case 'ImageB':
  673. case 'ImageC':
  674. case 'ImageI':
  675. $o['info'][$options] = 1;
  676. break;
  677. }
  678. break;
  679. case 'out':
  680. $res = "\n".$id." 0 obj\n[";
  681. foreach ($o['info'] as $label=>$val) {
  682. $res.= '/'.$label.' ';
  683. }
  684. $res.= "]\nendobj";
  685. return $res;
  686. break;
  687. }
  688. }
  689. /**
  690. * define the document information
  691. */
  692. function o_info($id, $action, $options = '') {
  693. if ($action != 'new') {
  694. $o = & $this->objects[$id];
  695. }
  696. switch ($action) {
  697. case 'new':
  698. $this->infoObject = $id;
  699. $date = 'D:'.@date('Ymd');
  700. $this->objects[$id] = array('t'=>'info', 'info'=>array('Creator'=>'R and OS php pdf writer, http://www.ros.co.nz', 'CreationDate'=>$date));
  701. break;
  702. case 'Title':
  703. case 'Author':
  704. case 'Subject':
  705. case 'Keywords':
  706. case 'Creator':
  707. case 'Producer':
  708. case 'CreationDate':
  709. case 'ModDate':
  710. case 'Trapped':
  711. $o['info'][$action] = $options;
  712. break;
  713. case 'out':
  714. if ($this->encrypted) {
  715. $this->encryptInit($id);
  716. }
  717. $res = "\n".$id." 0 obj\n<<\n";
  718. foreach ($o['info'] as $k=>$v) {
  719. $res.= '/'.$k.' (';
  720. if ($this->encrypted) {
  721. $res.= $this->filterText($this->ARC4($v));
  722. } else {
  723. $res.= $this->filterText($v);
  724. }
  725. $res.= ")\n";
  726. }
  727. $res.= ">>\nendobj";
  728. return $res;
  729. break;
  730. }
  731. }
  732. /**
  733. * an action object, used to link to URLS initially
  734. */
  735. function o_action($id, $action, $options = '') {
  736. if ($action != 'new') {
  737. $o = & $this->objects[$id];
  738. }
  739. switch ($action) {
  740. case 'new':
  741. if (is_array($options)) {
  742. $this->objects[$id] = array('t'=>'action', 'info'=>$options, 'type'=>$options['type']);
  743. } else {
  744. // then assume a URI action
  745. $this->objects[$id] = array('t'=>'action', 'info'=>$options, 'type'=>'URI');
  746. }
  747. break;
  748. case 'out':
  749. if ($this->encrypted) {
  750. $this->encryptInit($id);
  751. }
  752. $res = "\n".$id." 0 obj\n<< /Type /Action";
  753. switch ($o['type']) {
  754. case 'ilink':
  755. // there will be an 'label' setting, this is the name of the destination
  756. $res.= "\n/S /GoTo\n/D ".$this->destinations[(string)$o['info']['label']]." 0 R";
  757. break;
  758. case 'URI':
  759. $res.= "\n/S /URI\n/URI (";
  760. if ($this->encrypted) {
  761. $res.= $this->filterText($this->ARC4($o['info']));
  762. } else {
  763. $res.= $this->filterText($o['info']);
  764. }
  765. $res.= ")";
  766. break;
  767. }
  768. $res.= "\n>>\nendobj";
  769. return $res;
  770. break;
  771. }
  772. }
  773. /**
  774. * an annotation object, this will add an annotation to the current page.
  775. * initially will support just link annotations
  776. */
  777. function o_annotation($id, $action, $options = '') {
  778. if ($action != 'new') {
  779. $o = & $this->objects[$id];
  780. }
  781. switch ($action) {
  782. case 'new':
  783. // add the annotation to the current page
  784. $pageId = $this->currentPage;
  785. $this->o_page($pageId, 'annot', $id);
  786. // and add the action object which is going to be required
  787. switch ($options['type']) {
  788. case 'link':
  789. $this->objects[$id] = array('t'=>'annotation', 'info'=>$options);
  790. $this->numObj++;
  791. $this->o_action($this->numObj, 'new', $options['url']);
  792. $this->objects[$id]['info']['actionId'] = $this->numObj;
  793. break;
  794. case 'ilink':
  795. // this is to a named internal link
  796. $label = $options['label'];
  797. $this->objects[$id] = array('t'=>'annotation', 'info'=>$options);
  798. $this->numObj++;
  799. $this->o_action($this->numObj, 'new', array('type'=>'ilink', 'label'=>$label));
  800. $this->objects[$id]['info']['actionId'] = $this->numObj;
  801. break;
  802. }
  803. break;
  804. case 'out':
  805. $res = "\n".$id." 0 obj\n<< /Type /Annot";
  806. switch ($o['info']['type']) {
  807. case 'link':
  808. case 'ilink':
  809. $res.= "\n/Subtype /Link";
  810. break;
  811. }
  812. $res.= "\n/A ".$o['info']['actionId']." 0 R";
  813. $res.= "\n/Border [0 0 0]";
  814. $res.= "\n/H /I";
  815. $res.= "\n/Rect [ ";
  816. foreach($o['info']['rect'] as $v) {
  817. $res.= sprintf("%.4f ", $v);
  818. }
  819. $res.= "]";
  820. $res.= "\n>>\nendobj";
  821. return $res;
  822. break;
  823. }
  824. }
  825. /**
  826. * a page object, it also creates a contents object to hold its contents
  827. */
  828. function o_page($id, $action, $options = '') {
  829. if ($action != 'new') {
  830. $o = & $this->objects[$id];
  831. }
  832. switch ($action) {
  833. case 'new':
  834. $this->numPages++;
  835. $this->objects[$id] = array('t'=>'page', 'info'=>array('parent'=>$this->currentNode, 'pageNum'=>$this->numPages));
  836. if (is_array($options)) {
  837. // then this must be a page insertion, array shoudl contain 'rid','pos'=[before|after]
  838. $options['id'] = $id;
  839. $this->o_pages($this->currentNode, 'page', $options);
  840. } else {
  841. $this->o_pages($this->currentNode, 'page', $id);
  842. }
  843. $this->currentPage = $id;
  844. //make a contents object to go with this page
  845. $this->numObj++;
  846. $this->o_contents($this->numObj, 'new', $id);
  847. $this->currentContents = $this->numObj;
  848. $this->objects[$id]['info']['contents'] = array();
  849. $this->objects[$id]['info']['contents'][] = $this->numObj;
  850. $match = ($this->numPages%2 ? 'odd' : 'even');
  851. foreach($this->addLooseObjects as $oId=>$target) {
  852. if ($target == 'all' || $match == $target) {
  853. $this->objects[$id]['info']['contents'][] = $oId;
  854. }
  855. }
  856. break;
  857. case 'content':
  858. $o['info']['contents'][] = $options;
  859. break;
  860. case 'annot':
  861. // add an annotation to this page
  862. if (!isset($o['info']['annot'])) {
  863. $o['info']['annot'] = array();
  864. }
  865. // $options should contain the id of the annotation dictionary
  866. $o['info']['annot'][] = $options;
  867. break;
  868. case 'out':
  869. $res = "\n".$id." 0 obj\n<< /Type /Page";
  870. $res.= "\n/Parent ".$o['info']['parent']." 0 R";
  871. if (isset($o['info']['annot'])) {
  872. $res.= "\n/Annots [";
  873. foreach($o['info']['annot'] as $aId) {
  874. $res.= " ".$aId." 0 R";
  875. }
  876. $res.= " ]";
  877. }
  878. $count = count($o['info']['contents']);
  879. if ($count == 1) {
  880. $res.= "\n/Contents ".$o['info']['contents'][0]." 0 R";
  881. } else if ($count>1) {
  882. $res.= "\n/Contents [\n";
  883. // reverse the page contents so added objects are below normal content
  884. //foreach (array_reverse($o['info']['contents']) as $cId) {
  885. // Back to normal now that I've got transparency working --Benj
  886. foreach ($o['info']['contents'] as $cId) {
  887. $res.= $cId." 0 R\n";
  888. }
  889. $res.= "]";
  890. }
  891. $res.= "\n>>\nendobj";
  892. return $res;
  893. break;
  894. }
  895. }
  896. /**
  897. * the contents objects hold all of the content which appears on pages
  898. */
  899. function o_contents($id, $action, $options = '') {
  900. if ($action != 'new') {
  901. $o = & $this->objects[$id];
  902. }
  903. switch ($action) {
  904. case 'new':
  905. $this->objects[$id] = array('t'=>'contents', 'c'=>'', 'info'=>array());
  906. if (strlen($options) && intval($options)) {
  907. // then this contents is the primary for a page
  908. $this->objects[$id]['onPage'] = $options;
  909. } else if ($options == 'raw') {
  910. // then this page contains some other type of system object
  911. $this->objects[$id]['raw'] = 1;
  912. }
  913. break;
  914. case 'add':
  915. // add more options to the decleration
  916. foreach ($options as $k=>$v) {
  917. $o['info'][$k] = $v;
  918. }
  919. case 'out':
  920. $tmp = $o['c'];
  921. $res = "\n".$id." 0 obj\n";
  922. if (isset($this->objects[$id]['raw'])) {
  923. $res.= $tmp;
  924. } else {
  925. $res.= "<<";
  926. if (function_exists('gzcompress') && $this->options['compression']) {
  927. // then implement ZLIB based compression on this content stream
  928. $res.= " /Filter /FlateDecode";
  929. $tmp = gzcompress($tmp, 6);
  930. }
  931. if ($this->encrypted) {
  932. $this->encryptInit($id);
  933. $tmp = $this->ARC4($tmp);
  934. }
  935. foreach($o['info'] as $k=>$v) {
  936. $res.= "\n/".$k.' '.$v;
  937. }
  938. $res.= "\n/Length ".strlen($tmp) ." >>\nstream\n".$tmp."\nendstream";
  939. }
  940. $res.= "\nendobj\n";
  941. return $res;
  942. break;
  943. }
  944. }
  945. /**
  946. * an image object, will be an XObject in the document, includes description and data
  947. */
  948. function o_image($id, $action, $options = '') {
  949. if ($action != 'new') {
  950. $o = & $this->objects[$id];
  951. }
  952. switch ($action) {
  953. case 'new':
  954. // make the new object
  955. $this->objects[$id] = array('t'=>'image', 'data'=>&$options['data'], 'info'=>array());
  956. $this->objects[$id]['info']['Type'] = '/XObject';
  957. $this->objects[$id]['info']['Subtype'] = '/Image';
  958. $this->objects[$id]['info']['Width'] = $options['iw'];
  959. $this->objects[$id]['info']['Height'] = $options['ih'];
  960. if (!isset($options['type']) || $options['type'] == 'jpg') {
  961. if (!isset($options['channels'])) {
  962. $options['channels'] = 3;
  963. }
  964. switch ($options['channels']) {
  965. case 1:
  966. $this->objects[$id]['info']['ColorSpace'] = '/DeviceGray';
  967. break;
  968. default:
  969. $this->objects[$id]['info']['ColorSpace'] = '/DeviceRGB';
  970. break;
  971. }
  972. $this->objects[$id]['info']['Filter'] = '/DCTDecode';
  973. $this->objects[$id]['info']['BitsPerComponent'] = 8;
  974. } else if ($options['type'] == 'png') {
  975. $this->objects[$id]['info']['Filter'] = '/FlateDecode';
  976. $this->objects[$id]['info']['DecodeParms'] = '<< /Predictor 15 /Colors '.$options['ncolor'].' /Columns '.$options['iw'].' /BitsPerComponent '.$options['bitsPerComponent'].'>>';
  977. if (strlen($options['pdata'])) {
  978. $tmp = ' [ /Indexed /DeviceRGB '.(strlen($options['pdata']) /3-1) .' ';
  979. $this->numObj++;
  980. $this->o_contents($this->numObj, 'new');
  981. $this->objects[$this->numObj]['c'] = $options['pdata'];
  982. $tmp.= $this->numObj.' 0 R';
  983. $tmp.= ' ]';
  984. $this->objects[$id]['info']['ColorSpace'] = $tmp;
  985. if (isset($options['transparency'])) {
  986. switch ($options['transparency']['type']) {
  987. case 'indexed':
  988. $tmp = ' [ '.$options['transparency']['data'].' '.$options['transparency']['data'].'] ';
  989. $this->objects[$id]['info']['Mask'] = $tmp;
  990. break;
  991. case 'color-key':
  992. $tmp = ' [ '.
  993. $options['transparency']['r'] . ' ' . $options['transparency']['r'] .
  994. $options['transparency']['g'] . ' ' . $options['transparency']['g'] .
  995. $options['transparency']['b'] . ' ' . $options['transparency']['b'] .
  996. ' ] ';
  997. $this->objects[$id]['info']['Mask'] = $tmp;
  998. pre_r($tmp);
  999. break;
  1000. }
  1001. }
  1002. } else {
  1003. if (isset($options['transparency'])) {
  1004. switch ($options['transparency']['type']) {
  1005. case 'indexed':
  1006. $tmp = ' [ '.$options['transparency']['data'].' '.$options['transparency']['data'].'] ';
  1007. $this->objects[$id]['info']['Mask'] = $tmp;
  1008. break;
  1009. case 'color-key':
  1010. $tmp = ' [ '.
  1011. $options['transparency']['r'] . ' ' . $options['transparency']['r'] . ' ' .
  1012. $options['transparency']['g'] . ' ' . $options['transparency']['g'] . ' ' .
  1013. $options['transparency']['b'] . ' ' . $options['transparency']['b'] .
  1014. ' ] ';
  1015. $this->objects[$id]['info']['Mask'] = $tmp;
  1016. break;
  1017. }
  1018. }
  1019. $this->objects[$id]['info']['ColorSpace'] = '/'.$options['color'];
  1020. }
  1021. $this->objects[$id]['info']['BitsPerComponent'] = $options['bitsPerComponent'];
  1022. }
  1023. // assign it a place in the named resource dictionary as an external object, according to
  1024. // the label passed in with it.
  1025. $this->o_pages($this->currentNode, 'xObject', array('label'=>$options['label'], 'objNum'=>$id));
  1026. // also make sure that we have the right procset object for it.
  1027. $this->o_procset($this->procsetObjectId, 'add', 'ImageC');
  1028. break;
  1029. case 'out':
  1030. $tmp = &$o['data'];
  1031. $res = "\n".$id." 0 obj\n<<";
  1032. foreach($o['info'] as $k=>$v) {
  1033. $res.= "\n/".$k.' '.$v;
  1034. }
  1035. if ($this->encrypted) {
  1036. $this->encryptInit($id);
  1037. $tmp = $this->ARC4($tmp);
  1038. }
  1039. $res.= "\n/Length ".strlen($tmp) ." >>\nstream\n".$tmp."\nendstream\nendobj\n";
  1040. return $res;
  1041. break;
  1042. }
  1043. }
  1044. /**
  1045. * graphics state object
  1046. */
  1047. function o_extGState($id, $action, $options = "") {
  1048. static $valid_params = array("LW", "LC", "LC", "LJ", "ML",
  1049. "D", "RI", "OP", "op", "OPM",
  1050. "Font", "BG", "BG2", "UCR",
  1051. "TR", "TR2", "HT", "FL",
  1052. "SM", "SA", "BM", "SMask",
  1053. "CA", "ca", "AIS", "TK");
  1054. if ( $action != "new") {
  1055. $o = & $this->objects[$id];
  1056. }
  1057. switch ($action) {
  1058. case "new":
  1059. $this->objects[$id] = array('t' => 'extGState', 'info' => $options);
  1060. // Tell the pages about the new resource
  1061. $this->numStates++;
  1062. $this->o_pages($this->currentNode, 'extGState', array("objNum" => $id, "stateNum" => $this->numStates));
  1063. break;
  1064. case "out":
  1065. $res =
  1066. "\n" . $id . " 0 obj\n".
  1067. "<< /Type /ExtGState\n";
  1068. foreach ($o["info"] as $parameter => $value) {
  1069. if ( !in_array($parameter, $valid_params))
  1070. continue;
  1071. $res.= "/$parameter $value\n";
  1072. }
  1073. $res.=
  1074. ">>\n".
  1075. "endobj";
  1076. return $res;
  1077. }
  1078. }
  1079. /**
  1080. * encryption object.
  1081. */
  1082. function o_encryption($id, $action, $options = '') {
  1083. if ($action != 'new') {
  1084. $o = & $this->objects[$id];
  1085. }
  1086. switch ($action) {
  1087. case 'new':
  1088. // make the new object
  1089. $this->objects[$id] = array('t'=>'encryption', 'info'=>$options);
  1090. $this->arc4_objnum = $id;
  1091. // figure out the additional paramaters required
  1092. $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);
  1093. $len = strlen($options['owner']);
  1094. if ($len>32) {
  1095. $owner = substr($options['owner'], 0, 32);
  1096. } else if ($len<32) {
  1097. $owner = $options['owner'].substr($pad, 0, 32-$len);
  1098. } else {
  1099. $owner = $options['owner'];
  1100. }
  1101. $len = strlen($options['user']);
  1102. if ($len>32) {
  1103. $user = substr($options['user'], 0, 32);
  1104. } else if ($len<32) {
  1105. $user = $options['user'].substr($pad, 0, 32-$len);
  1106. } else {
  1107. $user = $options['user'];
  1108. }
  1109. $tmp = $this->md5_16($owner);
  1110. $okey = substr($tmp, 0, 5);
  1111. $this->ARC4_init($okey);
  1112. $ovalue = $this->ARC4($user);
  1113. $this->objects[$id]['info']['O'] = $ovalue;
  1114. // now make the u value, phew.
  1115. $tmp = $this->md5_16($user.$ovalue.chr($options['p']) .chr(255) .chr(255) .chr(255) .$this->fileIdentifier);
  1116. $ukey = substr($tmp, 0, 5);
  1117. $this->ARC4_init($ukey);
  1118. $this->encryptionKey = $ukey;
  1119. $this->encrypted = 1;
  1120. $uvalue = $this->ARC4($pad);
  1121. $this->objects[$id]['info']['U'] = $uvalue;
  1122. $this->encryptionKey = $ukey;
  1123. // initialize the arc4 array
  1124. break;
  1125. case 'out':
  1126. $res = "\n".$id." 0 obj\n<<";
  1127. $res.= "\n/Filter /Standard";
  1128. $res.= "\n/V 1";
  1129. $res.= "\n/R 2";
  1130. $res.= "\n/O (".$this->filterText($o['info']['O']) .')';
  1131. $res.= "\n/U (".$this->filterText($o['info']['U']) .')';
  1132. // and the p-value needs to be converted to account for the twos-complement approach
  1133. $o['info']['p'] = (($o['info']['p']^255) +1) *-1;
  1134. $res.= "\n/P ".($o['info']['p']);
  1135. $res.= "\n>>\nendobj\n";
  1136. return $res;
  1137. break;
  1138. }
  1139. }
  1140. /**
  1141. * ARC4 functions
  1142. * A series of function to implement ARC4 encoding in PHP
  1143. */
  1144. /**
  1145. * calculate the 16 byte version of the 128 bit md5 digest of the string
  1146. */
  1147. function md5_16($string) {
  1148. $tmp = md5($string);
  1149. $out = '';
  1150. for ($i = 0;$i <= 30;$i = $i+2) {
  1151. $out.= chr(hexdec(substr($tmp, $i, 2)));
  1152. }
  1153. return $out;
  1154. }
  1155. /**
  1156. * initialize the encryption for processing a particular object
  1157. */
  1158. function encryptInit($id) {
  1159. $tmp = $this->encryptionKey;
  1160. $hex = dechex($id);
  1161. if (strlen($hex) <6) {
  1162. $hex = substr('000000', 0, 6-strlen($hex)) .$hex;
  1163. }
  1164. $tmp.= chr(hexdec(substr($hex, 4, 2))) .chr(hexdec(substr($hex, 2, 2))) .chr(hexdec(substr($hex, 0, 2))) .chr(0) .chr(0);
  1165. $key = $this->md5_16($tmp);
  1166. $this->ARC4_init(substr($key, 0, 10));
  1167. }
  1168. /**
  1169. * initialize the ARC4 encryption
  1170. */
  1171. function ARC4_init($key = '') {
  1172. $this->arc4 = '';
  1173. // setup the control array
  1174. if (strlen($key) == 0) {
  1175. return;
  1176. }
  1177. $k = '';
  1178. while (strlen($k) <256) {
  1179. $k.= $key;
  1180. }
  1181. $k = substr($k, 0, 256);
  1182. for ($i = 0;$i<256;$i++) {
  1183. $this->arc4.= chr($i);
  1184. }
  1185. $j = 0;
  1186. for ($i = 0;$i<256;$i++) {
  1187. $t = $this->arc4[$i];
  1188. $j = ($j + ord($t) + ord($k[$i])) %256;
  1189. $this->arc4[$i] = $this->arc4[$j];
  1190. $this->arc4[$j] = $t;
  1191. }
  1192. }
  1193. /**
  1194. * ARC4 encrypt a text string
  1195. */
  1196. function ARC4($text) {
  1197. $len = strlen($text);
  1198. $a = 0;
  1199. $b = 0;
  1200. $c = $this->arc4;
  1201. $out = '';
  1202. for ($i = 0;$i<$len;$i++) {
  1203. $a = ($a+1) %256;
  1204. $t = $c[$a];
  1205. $b = ($b+ord($t)) %256;
  1206. $c[$a] = $c[$b];
  1207. $c[$b] = $t;
  1208. $k = ord($c[(ord($c[$a]) +ord($c[$b])) %256]);
  1209. $out.= chr(ord($text[$i]) ^ $k);
  1210. }
  1211. return $out;
  1212. }
  1213. /**
  1214. * functions which can be called to adjust or add to the document
  1215. */
  1216. /**
  1217. * add a link in the document to an external URL
  1218. */
  1219. function addLink($url, $x0, $y0, $x1, $y1) {
  1220. $this->numObj++;
  1221. $info = array('type'=>'link', 'url'=>$url, 'rect'=>array($x0, $y0, $x1, $y1));
  1222. $this->o_annotation($this->numObj, 'new', $info);
  1223. }
  1224. /**
  1225. * add a link in the document to an internal destination (ie. within the document)
  1226. */
  1227. function addInternalLink($label, $x0, $y0, $x1, $y1) {
  1228. $this->numObj++;
  1229. $info = array('type'=>'ilink', 'label'=>$label, 'rect'=>array($x0, $y0, $x1, $y1));
  1230. $this->o_annotation($this->numObj, 'new', $info);
  1231. }
  1232. /**
  1233. * set the encryption of the document
  1234. * can be used to turn it on and/or set the passwords which it will have.
  1235. * also the functions that the user will have are set here, such as print, modify, add
  1236. */
  1237. function setEncryption($userPass = '', $ownerPass = '', $pc = array()) {
  1238. $p = bindec(11000000);
  1239. $options = array(
  1240. 'print'=>4, 'modify'=>8, 'copy'=>16, 'add'=>32);
  1241. foreach($pc as $k=>$v) {
  1242. if ($v && isset($options[$k])) {
  1243. $p+= $options[$k];
  1244. } else if (isset($options[$v])) {
  1245. $p+= $options[$v];
  1246. }
  1247. }
  1248. // implement encryption on the document
  1249. if ($this->arc4_objnum == 0) {
  1250. // then the block does not exist already, add it.
  1251. $this->numObj++;
  1252. if (strlen($ownerPass) == 0) {
  1253. $ownerPass = $userPass;
  1254. }
  1255. $this->o_encryption($this->numObj, 'new', array('user'=>$userPass, 'owner'=>$ownerPass, 'p'=>$p));
  1256. }
  1257. }
  1258. /**
  1259. * should be used for internal checks, not implemented as yet
  1260. */
  1261. function checkAllHere() {
  1262. }
  1263. /**
  1264. * return the pdf stream as a string returned from the function
  1265. */
  1266. function output($debug = 0) {
  1267. if ($debug) {
  1268. // turn compression off
  1269. $this->options['compression'] = 0;
  1270. }
  1271. if ($this->arc4_objnum) {
  1272. $this->ARC4_init($this->encryptionKey);
  1273. }
  1274. $this->checkAllHere();
  1275. $xref = array();
  1276. $content = "%PDF-1.3\n%â??Ó\n";
  1277. // $content="%PDF-1.3\n";
  1278. $pos = strlen($content);
  1279. foreach($this->objects as $k=>$v) {
  1280. $tmp = 'o_'.$v['t'];
  1281. $cont = $this->$tmp($k, 'out');
  1282. $content.= $cont;
  1283. $xref[] = $pos;
  1284. $pos+= strlen($cont);
  1285. }
  1286. $content.= "\nxref\n0 ".(count($xref) +1) ."\n0000000000 65535 f \n";
  1287. foreach($xref as $p) {
  1288. $content.= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n";
  1289. }
  1290. $content.= "trailer\n<<\n/Size ".(count($xref) +1) ."\n/Root 1 0 R\n/Info ".$this->infoObject." 0 R\n";
  1291. // if encryption has been applied to this document then add the marker for this dictionary
  1292. if ($this->arc4_objnum > 0) {
  1293. $content.= "/Encrypt ".$this->arc4_objnum." 0 R\n";
  1294. }
  1295. if (strlen($this->fileIdentifier)) {
  1296. $content.= "/ID[<".$this->fileIdentifier."><".$this->fileIdentifier.">]\n";
  1297. }
  1298. $content.= ">>\nstartxref\n".$pos."\n%%EOF\n";
  1299. return $content;
  1300. }
  1301. /**
  1302. * intialize a new document
  1303. * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum
  1304. * this function is called automatically by the constructor function
  1305. *
  1306. * @access private
  1307. */
  1308. function newDocument($pageSize = array(0, 0, 612, 792)) {
  1309. $this->numObj = 0;
  1310. $this->objects = array();
  1311. $this->numObj++;
  1312. $this->o_catalog($this->numObj, 'new');
  1313. $this->numObj++;
  1314. $this->o_outlines($this->numObj, 'new');
  1315. $this->numObj++;
  1316. $this->o_pages($this->numObj, 'new');
  1317. $this->o_pages($this->numObj, 'mediaBox', $pageSize);
  1318. $this->currentNode = 3;
  1319. $this->numObj++;
  1320. $this->o_procset($this->numObj, 'new');
  1321. $this->numObj++;
  1322. $this->o_info($this->numObj, 'new');
  1323. $this->numObj++;
  1324. $this->o_page($this->numObj, 'new');
  1325. // need to store the first page id as there is no way to get it to the user during
  1326. // startup
  1327. $this->firstPageId = $this->currentContents;
  1328. }
  1329. /**
  1330. * open the font file and return a php structure containing it.
  1331. * first check if this one has been done before and saved in a form more suited to php
  1332. * note that if a php serialized version does not exist it will try and make one, but will
  1333. * require write access to the directory to do it... it is MUCH faster to have these serialized
  1334. * files.
  1335. *
  1336. * @access private
  1337. */
  1338. function openFont($font) {
  1339. // assume that $font contains both the path and perhaps the extension to the file, split them
  1340. $pos = strrpos($font, '/');
  1341. if ($pos === false) {
  1342. $dir = './';
  1343. $name = $font;
  1344. } else {
  1345. $dir = substr($font, 0, $pos+1);
  1346. $name = substr($font, $pos+1);
  1347. }
  1348. if (substr($name, -4) == '.afm') {
  1349. $name = substr($name, 0, strlen($name) -4);
  1350. }
  1351. $this->addMessage('openFont: '.$font.' - '.$name);
  1352. if (file_exists($dir . 'php_' . $name . '.afm')) {
  1353. $this->addMessage('openFont: php file exists ' . $dir . 'php_' . $name.'.afm');
  1354. $tmp = file_get_contents($dir.'php_'.$name.'.afm');
  1355. eval($tmp);
  1356. if (!isset($this->fonts[$font]['_version_']) || $this->fonts[$font]['_version_']<1) {
  1357. // if the font file is old, then clear it out and prepare for re-creation
  1358. $this->addMessage('openFont: clear out, make way for new version.');
  1359. unset($this->fonts[$font]);
  1360. }
  1361. }
  1362. if (!isset($this->fonts[$font]) && file_exists($dir.$name.'.afm')) {
  1363. // then rebuild the php_<font>.afm file from the <font>.afm file
  1364. $this->addMessage('openFont: build php file from '.$dir.$name.'.afm');
  1365. $data = array();
  1366. $file = file($dir.$name.'.afm');
  1367. foreach ($file as $rowA) {
  1368. $row = trim($rowA);
  1369. $pos = strpos($row, ' ');
  1370. if ($pos) {
  1371. // then there must be some keyword
  1372. $key = substr($row, 0, $pos);
  1373. switch ($key) {
  1374. case 'FontName':
  1375. case 'FullName':
  1376. case 'FamilyName':
  1377. case 'Weight':
  1378. case 'ItalicAngle':
  1379. case 'IsFixedPitch':
  1380. case 'CharacterSet':
  1381. case 'UnderlinePosition':
  1382. case 'UnderlineThickness':
  1383. case 'Version':
  1384. case 'EncodingScheme':
  1385. case 'CapHeight':
  1386. case 'XHeight':
  1387. case 'Ascender':
  1388. case 'Descender':
  1389. case 'StdHW':
  1390. case 'StdVW':
  1391. case 'StartCharMetrics':
  1392. $data[$key] = trim(substr($row, $pos));
  1393. break;
  1394. case 'FontBBox':
  1395. $data[$key] = explode(' ', trim(substr($row, $pos)));
  1396. break;
  1397. case 'C':
  1398. //C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
  1399. $bits = explode(';', trim($row));
  1400. $dtmp = array();
  1401. foreach($bits as $bit) {
  1402. $bits2 = explode(' ', trim($bit));
  1403. if (strlen($bits2[0])) {
  1404. if (count($bits2) >2) {
  1405. $dtmp[$bits2[0]] = array();
  1406. for ($i = 1;$i<count($bits2);$i++) {
  1407. $dtmp[$bits2[0]][] = $bits2[$i];
  1408. }
  1409. } else if (count($bits2) == 2) {
  1410. $dtmp[$bits2[0]] = $bits2[1];
  1411. }
  1412. }
  1413. }
  1414. if ($dtmp['C'] >= 0) {
  1415. $data['C'][$dtmp['C']] = $dtmp;
  1416. $data['C'][$dtmp['N']] = $dtmp;
  1417. } else {
  1418. $data['C'][$dtmp['N']] = $dtmp;
  1419. }
  1420. break;
  1421. case 'KPX':
  1422. //KPX Adieresis yacute -40
  1423. $bits = explode(' ', trim($row));
  1424. $data['KPX'][$bits[1]][$bits[2]] = $bits[3];
  1425. break;
  1426. }
  1427. }
  1428. }
  1429. $data['_version_'] = 1;
  1430. $this->fonts[$font] = $data;
  1431. file_put_contents($dir . 'php_' . $name . '.afm', '$this->fonts[$font]=' . var_export($data, true) . ';');
  1432. } else if (!isset($this->fonts[$font])) {
  1433. $this->addMessage('openFont: no font file found');
  1434. // echo 'Font not Found '.$font;
  1435. }
  1436. }
  1437. /**
  1438. * if the font is not loaded then load it and make the required object
  1439. * else just make it the current font
  1440. * the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding'
  1441. * note that encoding='none' will need to be used for symbolic fonts
  1442. * and 'differences' => an array of mappings between numbers 0->255 and character names.
  1443. *
  1444. */
  1445. function selectFont($fontName, $encoding = '', $set = 1) {
  1446. if (!isset($this->fonts[$fontName])) {
  1447. // load the file
  1448. $this->openFont($fontName);
  1449. if (isset($this->fonts[$fontName])) {
  1450. $this->numObj++;
  1451. $this->numFonts++;
  1452. //$this->numFonts = md5($fontName);
  1453. $pos = strrpos($fontName, '/');
  1454. // $dir = substr($fontName,0,$pos+1);
  1455. $name = substr($fontName, $pos+1);
  1456. if (substr($name, -4) == '.afm') {
  1457. $name = substr($name, 0, strlen($name) -4);
  1458. }
  1459. $options = array('name' => $name);
  1460. if (is_array($encoding)) {
  1461. // then encoding and differences might be set
  1462. if (isset($encoding['encoding'])) {
  1463. $options['encoding'] = $encoding['encoding'];
  1464. }
  1465. if (isset($encoding['differences'])) {
  1466. $options['differences'] = $encoding['differences'];
  1467. }
  1468. } else if (strlen($encoding)) {
  1469. // then perhaps only the encoding has been set
  1470. $options['encoding'] = $encoding;
  1471. }
  1472. $fontObj = $this->numObj;
  1473. $this->o_font($this->numObj, 'new', $options);
  1474. $this->fonts[$fontName]['fontNum'] = $this->numFonts;
  1475. // if this is a '.afm' font, and there is a '.pfa' file to go with it ( as there
  1476. // should be for all non-basic fonts), then load it into an object and put the
  1477. // references into the font object
  1478. $basefile = substr($fontName, 0, strlen($fontName) -4);
  1479. if (file_exists($basefile.'.pfb')) {
  1480. $fbtype = 'pfb';
  1481. } else if (file_exists($basefile.'.ttf')) {
  1482. $fbtype = 'ttf';
  1483. } else {
  1484. $fbtype = '';
  1485. }
  1486. $fbfile = $basefile.'.'.$fbtype;
  1487. // $pfbfile = substr($fontName,0,strlen($fontName)-4).'.pfb';
  1488. // $ttffile = substr($fontName,0,strlen($fontName)-4).'.ttf';
  1489. $this->addMessage('selectFont: checking for - '.$fbfile);
  1490. if (substr($fontName, -4) == '.afm' && strlen($fbtype)) {
  1491. $adobeFontName = $this->fonts[$fontName]['FontName'];
  1492. // $fontObj = $this->numObj;
  1493. $this->addMessage('selectFont: adding font file - '.$fbfile.' - '.$adobeFontName);
  1494. // find the array of fond widths, and put that into an object.
  1495. $firstChar = -1;
  1496. $lastChar = 0;
  1497. $widths = array();
  1498. foreach ($this->fonts[$fontName]['C'] as $num => $d) {
  1499. if (intval($num) >0 || $num == '0') {
  1500. if ($lastChar>0 && $num>$lastChar+1) {
  1501. for ($i = $lastChar+1;$i<$num;$i++) {
  1502. $widths[] = 0;
  1503. }
  1504. }
  1505. $widths[] = $d['WX'];
  1506. if ($firstChar == -1) {
  1507. $firstChar = $num;
  1508. }
  1509. $lastChar = $num;
  1510. }
  1511. }
  1512. // also need to adjust the widths for the differences array
  1513. if (isset($options['differences'])) {
  1514. foreach($options['differences'] as $charNum => $charName) {
  1515. if ($charNum > $lastChar) {
  1516. for ($i = $lastChar + 1; $i <= $charNum; $i++) {
  1517. $widths[] = 0;
  1518. }
  1519. $lastChar = $charNum;
  1520. }
  1521. if (isset($this->fonts[$fontName]['C'][$charName])) {
  1522. $widths[$charNum-$firstChar] = $this->fonts[$fontName]['C'][$charName]['WX'];
  1523. }
  1524. }
  1525. }
  1526. $this->addMessage('selectFont: FirstChar = '.$firstChar);
  1527. $this->addMessage('selectFont: LastChar = '.$lastChar);
  1528. $this->numObj++;
  1529. $this->o_contents($this->numObj, 'new', 'raw');
  1530. $this->objects[$this->numObj]['c'].= '[';
  1531. foreach($widths as $width) {
  1532. $this->objects[$this->numObj]['c'].= ' '.$width;
  1533. }
  1534. $this->objects[$this->numObj]['c'].= ' ]';
  1535. $widthid = $this->numObj;
  1536. // load the pfb file, and put that into an object too.
  1537. // note that pdf supports only binary format type 1 font files, though there is a
  1538. // simple utility to convert them from pfa to pfb.
  1539. $tmp = get_magic_quotes_runtime();
  1540. set_magic_quotes_runtime(0);
  1541. $data = file_get_contents($fbfile);
  1542. set_magic_quotes_runtime($tmp);
  1543. // create the font descriptor
  1544. $this->numObj++;
  1545. $fontDescriptorId = $this->numObj;
  1546. $this->numObj++;
  1547. $pfbid = $this->numObj;
  1548. // determine flags (more than a little flakey, hopefully will not matter much)
  1549. $flags = 0;
  1550. if ($this->fonts[$fontName]['ItalicAngle'] != 0) {
  1551. $flags+= pow(2, 6);
  1552. }
  1553. if ($this->fonts[$fontName]['IsFixedPitch'] == 'true') {
  1554. $flags+= 1;
  1555. }
  1556. $flags+= pow(2, 5);
  1557. // assume non-sybolic
  1558. $list = array('Ascent' => 'Ascender', 'CapHeight' => 'CapHeight', 'Descent' => 'Descender', 'FontBBox' => 'FontBBox', 'ItalicAngle' => 'ItalicAngle');
  1559. $fdopt = array(
  1560. 'Flags' => $flags, 'FontName' => $adobeFontName, 'StemV' => 100 // don't know what the value for this should be!
  1561. );
  1562. foreach($list as $k => $v) {
  1563. if (isset($this->fonts[$fontName][$v])) {
  1564. $fdopt[$k] = $this->fonts[$fontName][$v];
  1565. }
  1566. }
  1567. if ($fbtype == 'pfb') {
  1568. $fdopt['FontFile'] = $pfbid;
  1569. } else if ($fbtype == 'ttf') {
  1570. $fdopt['FontFile2'] = $pfbid;
  1571. }
  1572. $this->o_fontDescriptor($fontDescriptorId, 'new', $fdopt);
  1573. // embed the font program
  1574. $this->o_contents($this->numObj, 'new');
  1575. $this->objects[$pfbid]['c'].= $data;
  1576. // determine the cruicial lengths within this file
  1577. if ($fbtype == 'pfb') {
  1578. $l1 = strpos($data, 'eexec') +6;
  1579. $l2 = strpos($data, '00000000') -$l1;
  1580. $l3 = strlen($data) -$l2-$l1;
  1581. $this->o_contents($this->numObj, 'add', array('Length1' => $l1, 'Length2' => $l2, 'Length3' => $l3));
  1582. } else if ($fbtype == 'ttf') {
  1583. $l1 = strlen($data);
  1584. $this->o_contents($this->numObj, 'add', array('Length1' => $l1));
  1585. }
  1586. // tell the font object about all this new stuff
  1587. $tmp = array('BaseFont' => $adobeFontName, 'Widths' => $widthid, 'FirstChar' => $firstChar, 'LastChar' => $lastChar, 'FontDescriptor' => $fontDescriptorId);
  1588. if ($fbtype == 'ttf') {
  1589. $tmp['SubType'] = 'TrueType';
  1590. }
  1591. $this->addMessage('adding extra info to font.('.$fontObj.')');
  1592. foreach($tmp as $fk => $fv) {
  1593. $this->addMessage($fk." : ".$fv);
  1594. }
  1595. $this->o_font($fontObj, 'add', $tmp);
  1596. } else {
  1597. $this->addMessage('selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts');
  1598. }
  1599. // also set the differences here, note that this means that these will take effect only the
  1600. //first time that a font is selected, else they are ignored
  1601. if (isset($options['differences'])) {
  1602. $this->fonts[$fontName]['differences'] = $options['differences'];
  1603. }
  1604. }
  1605. }
  1606. if ($set && isset($this->fonts[$fontName])) {
  1607. // so if for some reason the font was not set in the last one then it will not be selected
  1608. $this->currentBaseFont = $fontName;
  1609. // the next lines mean that if a new font is selected, then the current text state will be
  1610. // applied to it as well.
  1611. $this->currentFont = $this->currentBaseFont;
  1612. $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
  1613. //$this->setCurrentFont();
  1614. }
  1615. return $this->currentFontNum;
  1616. //return $this->numObj;
  1617. }
  1618. /**
  1619. * sets up the current font, based on the font families, and the current text state
  1620. * note that this system is quite flexible, a bold-italic font can be completely different to a
  1621. * italic-bold font, and even bold-bold will have to be defined within the family to have meaning
  1622. * This function is to be called whenever the currentTextState is changed, it will update
  1623. * the currentFont setting to whatever the appropriatte family one is.
  1624. * If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont
  1625. * This function will change the currentFont to whatever it should be, but will not change the
  1626. * currentBaseFont.
  1627. *
  1628. * @access private
  1629. */
  1630. function setCurrentFont() {
  1631. // if (strlen($this->currentBaseFont) == 0){
  1632. // // then assume an initial font
  1633. // $this->selectFont('./fonts/Helvetica.afm');
  1634. // }
  1635. // $cf = substr($this->currentBaseFont,strrpos($this->currentBaseFont,'/')+1);
  1636. // if (strlen($this->currentTextState)
  1637. // && isset($this->fontFamilies[$cf])
  1638. // && isset($this->fontFamilies[$cf][$this->currentTextState])){
  1639. // // then we are in some state or another
  1640. // // and this font has a family, and the current setting exists within it
  1641. // // select the font, then return it
  1642. // $nf = substr($this->currentBaseFont,0,strrpos($this->currentBaseFont,'/')+1).$this->fontFamilies[$cf][$this->currentTextState];
  1643. // $this->selectFont($nf,'',0);
  1644. // $this->currentFont = $nf;
  1645. // $this->currentFontNum = $this->fonts[$nf]['fontNum'];
  1646. // } else {
  1647. // // the this font must not have the right family member for the current state
  1648. // // simply assume the base font
  1649. $this->currentFont = $this->currentBaseFont;
  1650. $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
  1651. // }
  1652. }
  1653. /**
  1654. * function for the user to find out what the ID is of the first page that was created during
  1655. * startup - useful if they wish to add something to it later.
  1656. */
  1657. function getFirstPageId() {
  1658. return $this->firstPageId;
  1659. }
  1660. /**
  1661. * add content to the currently active object
  1662. *
  1663. * @access private
  1664. */
  1665. function addContent($content) {
  1666. $this->objects[$this->currentContents]['c'].= $content;
  1667. }
  1668. /**
  1669. * sets the colour for fill operations
  1670. */
  1671. function setColor($r, $g, $b, $force = 0) {
  1672. if ($r >= 0 && ($force || $r != $this->currentColour['r'] || $g != $this->currentColour['g'] || $b != $this->currentColour['b'])) {
  1673. $this->objects[$this->currentContents]['c'].= "\n".sprintf('%.3f', $r) .' '.sprintf('%.3f', $g) .' '.sprintf('%.3f', $b) .' rg';
  1674. $this->currentColour = array('r' => $r, 'g' => $g, 'b' => $b);
  1675. }
  1676. }
  1677. /**
  1678. * sets the colour for stroke operations
  1679. */
  1680. function setStrokeColor($r, $g, $b, $force = 0) {
  1681. if ($r >= 0 && ($force || $r != $this->currentStrokeColour['r'] || $g != $this->currentStrokeColour['g'] || $b != $this->currentStrokeColour['b'])) {
  1682. $this->objects[$this->currentContents]['c'].= "\n".sprintf('%.3f', $r) .' '.sprintf('%.3f', $g) .' '.sprintf('%.3f', $b) .' RG';
  1683. $this->currentStrokeColour = array('r' => $r, 'g' => $g, 'b' => $b);
  1684. }
  1685. }
  1686. /**
  1687. * Set the graphics state for compositions
  1688. */
  1689. function setGraphicsState($parameters) {
  1690. // Create a new graphics state object
  1691. // FIXME: should actually keep track of states that have already been created...
  1692. $this->numObj++;
  1693. $this->o_extGState($this->numObj, 'new', $parameters);
  1694. $this->objects[ $this->currentContents ]['c'].= "\n/GS" . $this->numStates . " gs";
  1695. }
  1696. /**
  1697. * Set current blend mode & opacity for lines.
  1698. *
  1699. * Valid blend modes are:
  1700. *
  1701. * Normal, Multiply, Screen, Overlay, Darken, Lighten,
  1702. * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
  1703. * Exclusion
  1704. *
  1705. * @param string $mode the blend mode to use
  1706. * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
  1707. */
  1708. function setLineTransparency($mode, $opacity) {
  1709. static $blend_modes = array("Normal", "Multiply", "Screen",
  1710. "Overlay", "Darken", "Lighten",
  1711. "ColorDogde", "ColorBurn", "HardLight",
  1712. "SoftLight", "Difference", "Exclusion");
  1713. if ( !in_array($mode, $blend_modes) )
  1714. $mode = "Normal";
  1715. // Only create a new graphics state if required
  1716. if ( $mode == $this->currentLineTransparency["mode"] &&
  1717. $opacity == $this->currentLineTransparency["opacity"] )
  1718. return;
  1719. $options = array("BM" => "/$mode",
  1720. "CA" => (float)$opacity);
  1721. $this->setGraphicsState($options);
  1722. }
  1723. /**
  1724. * Set current blend mode & opacity for filled objects.
  1725. *
  1726. * Valid blend modes are:
  1727. *
  1728. * Normal, Multiply, Screen, Overlay, Darken, Lighten,
  1729. * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
  1730. * Exclusion
  1731. *
  1732. * @param string $mode the blend mode to use
  1733. * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
  1734. */
  1735. function setFillTransparency($mode, $opacity) {
  1736. static $blend_modes = array("Normal", "Multiply", "Screen",
  1737. "Overlay", "Darken", "Lighten",
  1738. "ColorDogde", "ColorBurn", "HardLight",
  1739. "SoftLight", "Difference", "Exclusion");
  1740. if ( !in_array($mode, $blend_modes) )
  1741. $mode = "Normal";
  1742. if ( $mode == $this->currentFillTransparency["mode"] &&
  1743. $opacity == $this->currentFillTransparency["opacity"] )
  1744. return;
  1745. $options = array("BM" => "/$mode",
  1746. "ca" => (float)$opacity);
  1747. $this->setGraphicsState($options);
  1748. }
  1749. /**
  1750. * draw a line from one set of coordinates to another
  1751. */
  1752. function line($x1, $y1, $x2, $y2) {
  1753. $this->objects[$this->currentContents]['c'] .=
  1754. "\n".sprintf('%.3f', $x1) .' '.sprintf('%.3f', $y1) .' m '.sprintf('%.3f', $x2) .' '.sprintf('%.3f', $y2) .' l S';
  1755. }
  1756. /**
  1757. * draw a bezier curve based on 4 control points
  1758. */
  1759. function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3) {
  1760. // in the current line style, draw a bezier curve from (x0,y0) to (x3,y3) using the other two points
  1761. // as the control points for the curve.
  1762. $this->objects[$this->currentContents]['c'] .=
  1763. "\n".sprintf('%.3f', $x0) .' '.sprintf('%.3f', $y0) .' m '.sprintf('%.3f', $x1) .' '.sprintf('%.3f', $y1);
  1764. $this->objects[$this->currentContents]['c'] .=
  1765. ' '.sprintf('%.3f', $x2) .' '.sprintf('%.3f', $y2) .' '.sprintf('%.3f', $x3) .' '.sprintf('%.3f', $y3) .' c S';
  1766. }
  1767. /**
  1768. * draw a part of an ellipse
  1769. */
  1770. function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8) {
  1771. $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, 0);
  1772. }
  1773. /**
  1774. * draw a filled ellipse
  1775. */
  1776. function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360) {
  1777. return $this->ellipse($x0, $y0, $r1, $r2 = 0, $angle, $nSeg, $astart, $afinish, 1, 1);
  1778. }
  1779. /**
  1780. * draw an ellipse
  1781. * note that the part and filled ellipse are just special cases of this function
  1782. *
  1783. * draws an ellipse in the current line style
  1784. * centered at $x0,$y0, radii $r1,$r2
  1785. * if $r2 is not set, then a circle is drawn
  1786. * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a
  1787. * pretty crappy shape at 2, as we are approximating with bezier curves.
  1788. */
  1789. function ellipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360, $close = 1, $fill = 0) {
  1790. if ($r1 == 0) {
  1791. return;
  1792. }
  1793. if ($r2 == 0) {
  1794. $r2 = $r1;
  1795. }
  1796. if ($nSeg < 2) {
  1797. $nSeg = 2;
  1798. }
  1799. $astart = deg2rad((float)$astart);
  1800. $afinish = deg2rad((float)$afinish);
  1801. $totalAngle = $afinish-$astart;
  1802. $dt = $totalAngle/$nSeg;
  1803. $dtm = $dt/3;
  1804. if ($angle != 0) {
  1805. $a = -1*deg2rad((float)$angle);
  1806. $tmp = "\n q ";
  1807. $tmp .= sprintf('%.3f', cos($a)) .' '.sprintf('%.3f', (-1.0*sin($a))) .' '.sprintf('%.3f', sin($a)) .' '.sprintf('%.3f', cos($a)) .' ';
  1808. $tmp .= sprintf('%.3f', $x0) .' '.sprintf('%.3f', $y0) .' cm';
  1809. $this->objects[$this->currentContents]['c'].= $tmp;
  1810. $x0 = 0;
  1811. $y0 = 0;
  1812. }
  1813. $t1 = $astart;
  1814. $a0 = $x0 + $r1*cos($t1);
  1815. $b0 = $y0 + $r2*sin($t1);
  1816. $c0 = -$r1 * sin($t1);
  1817. $d0 = $r2 * cos($t1);
  1818. $this->objects[$this->currentContents]['c'] .= "\n".sprintf('%.3f', $a0) .' '.sprintf('%.3f', $b0) .' m ';
  1819. for ($i = 1; $i <= $nSeg; $i++) {
  1820. // draw this bit of the total curve
  1821. $t1 = $i * $dt + $astart;
  1822. $a1 = $x0 + $r1 * cos($t1);
  1823. $b1 = $y0 + $r2 * sin($t1);
  1824. $c1 = -$r1 * sin($t1);
  1825. $d1 = $r2 * cos($t1);
  1826. $this->objects[$this->currentContents]['c']
  1827. .= "\n".sprintf('%.3f', ($a0+$c0*$dtm)) .' '.sprintf('%.3f', ($b0 + $d0 * $dtm));
  1828. $this->objects[$this->currentContents]['c'] .=
  1829. ' '.sprintf('%.3f', ($a1-$c1*$dtm)) .' '.sprintf('%.3f', ($b1-$d1*$dtm)) .' '.sprintf('%.3f', $a1) .' '.sprintf('%.3f', $b1) .' c';
  1830. $a0 = $a1;
  1831. $b0 = $b1;
  1832. $c0 = $c1;
  1833. $d0 = $d1;
  1834. }
  1835. if ($fill) {
  1836. $this->objects[$this->currentContents]['c'].= ' f';
  1837. } else if ($close) {
  1838. $this->objects[$this->currentContents]['c'].= ' s';
  1839. // small 's' signifies closing the path as well
  1840. } else {
  1841. $this->objects[$this->currentContents]['c'].= ' S';
  1842. }
  1843. if ($angle != 0) {
  1844. $this->objects[$this->currentContents]['c'].= ' Q';
  1845. }
  1846. }
  1847. /**
  1848. * this sets the line drawing style.
  1849. * width, is the thickness of the line in user units
  1850. * cap is the type of cap to put on the line, values can be 'butt','round','square'
  1851. * where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the
  1852. * end of the line.
  1853. * join can be 'miter', 'round', 'bevel'
  1854. * dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the
  1855. * on and off dashes.
  1856. * (2) represents 2 on, 2 off, 2 on , 2 off ...
  1857. * (2,1) is 2 on, 1 off, 2 on, 1 off.. etc
  1858. * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts.
  1859. */
  1860. function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0) {
  1861. // this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day
  1862. $string = '';
  1863. if ($width>0) {
  1864. $string.= $width.' w';
  1865. }
  1866. $ca = array('butt' => 0, 'round' => 1, 'square' => 2);
  1867. if (isset($ca[$cap])) {
  1868. $string.= ' '.$ca[$cap].' J';
  1869. }
  1870. $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
  1871. if (isset($ja[$join])) {
  1872. $string.= ' '.$ja[$join].' j';
  1873. }
  1874. if (is_array($dash)) {
  1875. $string.= ' [';
  1876. foreach ($dash as $len) {
  1877. $string.= ' '.$len;
  1878. }
  1879. $string.= ' ] '.$phase.' d';
  1880. }
  1881. $this->currentLineStyle = $string;
  1882. $this->objects[$this->currentContents]['c'].= "\n".$string;
  1883. }
  1884. /**
  1885. * draw a polygon, the syntax for this is similar to the GD polygon command
  1886. */
  1887. function polygon($p, $np, $f = 0) {
  1888. $this->objects[$this->currentContents]['c'].= "\n";
  1889. $this->objects[$this->currentContents]['c'].= sprintf('%.3f', $p[0]) .' '.sprintf('%.3f', $p[1]) .' m ';
  1890. for ($i = 2; $i < $np * 2; $i = $i + 2) {
  1891. $this->objects[$this->currentContents]['c'].= sprintf('%.3f', $p[$i]) .' '.sprintf('%.3f', $p[$i+1]) .' l ';
  1892. }
  1893. if ($f == 1) {
  1894. $this->objects[$this->currentContents]['c'].= ' f';
  1895. } else {
  1896. $this->objects[$this->currentContents]['c'].= ' S';
  1897. }
  1898. }
  1899. /**
  1900. * a filled rectangle, note that it is the width and height of the rectangle which are the secondary paramaters, not
  1901. * the coordinates of the upper-right corner
  1902. */
  1903. function filledRectangle($x1, $y1, $width, $height) {
  1904. $this->objects[$this->currentContents]['c'].= "\n".sprintf('%.3f', $x1) .' '.sprintf('%.3f', $y1) .' '.sprintf('%.3f', $width) .' '.sprintf('%.3f', $height) .' re f';
  1905. }
  1906. /**
  1907. * draw a rectangle, note that it is the width and height of the rectangle which are the secondary paramaters, not
  1908. * the coordinates of the upper-right corner
  1909. */
  1910. function rectangle($x1, $y1, $width, $height) {
  1911. $this->objects[$this->currentContents]['c'].= "\n".sprintf('%.3f', $x1) .' '.sprintf('%.3f', $y1) .' '.sprintf('%.3f', $width) .' '.sprintf('%.3f', $height) .' re S';
  1912. }
  1913. /**
  1914. * add a new page to the document
  1915. * this also makes the new page the current active object
  1916. */
  1917. function newPage($insert = 0, $id = 0, $pos = 'after') {
  1918. // if there is a state saved, then go up the stack closing them
  1919. // then on the new page, re-open them with the right setings
  1920. if ($this->nStateStack) {
  1921. for ($i = $this->nStateStack;$i >= 1;$i--) {
  1922. $this->restoreState($i);
  1923. }
  1924. }
  1925. $this->numObj++;
  1926. if ($insert) {
  1927. // the id from the ezPdf class is the od of the contents of the page, not the page object itself
  1928. // query that object to find the parent
  1929. $rid = $this->objects[$id]['onPage'];
  1930. $opt = array('rid' => $rid, 'pos' => $pos);
  1931. $this->o_page($this->numObj, 'new', $opt);
  1932. } else {
  1933. $this->o_page($this->numObj, 'new');
  1934. }
  1935. // if there is a stack saved, then put that onto the page
  1936. if ($this->nStateStack) {
  1937. for ($i = 1;$i <= $this->nStateStack;$i++) {
  1938. $this->saveState($i);
  1939. }
  1940. }
  1941. // and if there has been a stroke or fill colour set, then transfer them
  1942. if ($this->currentColour['r'] >= 0) {
  1943. $this->setColor($this->currentColour['r'], $this->currentColour['g'], $this->currentColour['b'], 1);
  1944. }
  1945. if ($this->currentStrokeColour['r'] >= 0) {
  1946. $this->setStrokeColor($this->currentStrokeColour['r'], $this->currentStrokeColour['g'], $this->currentStrokeColour['b'], 1);
  1947. }
  1948. // if there is a line style set, then put this in too
  1949. if (strlen($this->currentLineStyle)) {
  1950. $this->objects[$this->currentContents]['c'].= "\n".$this->currentLineStyle;
  1951. }
  1952. // the call to the o_page object set currentContents to the present page, so this can be returned as the page id
  1953. return $this->currentContents;
  1954. }
  1955. /**
  1956. * output the pdf code, streaming it to the browser
  1957. * the relevant headers are set so that hopefully the browser will recognise it
  1958. */
  1959. function stream($options = '') {
  1960. // setting the options allows the adjustment of the headers
  1961. // values at the moment are:
  1962. // 'Content-Disposition' => 'filename' - sets the filename, though not too sure how well this will
  1963. // work as in my trial the browser seems to use the filename of the php file with .pdf on the end
  1964. // 'Accept-Ranges' => 1 or 0 - if this is not set to 1, then this header is not included, off by default
  1965. // this header seems to have caused some problems despite tha fact that it is supposed to solve
  1966. // them, so I am leaving it off by default.
  1967. // 'compress' = > 1 or 0 - apply content stream compression, this is on (1) by default
  1968. // 'Attachment' => 1 or 0 - if 1, force the browser to open a download dialog
  1969. if (!is_array($options)) {
  1970. $options = array();
  1971. }
  1972. if ( headers_sent())
  1973. die("Unable to stream pdf: headers already sent");
  1974. if ( isset($options['compress']) && $options['compress'] == 0) {
  1975. $tmp = ltrim($this->output(1));
  1976. } else {
  1977. $tmp = ltrim($this->output());
  1978. }
  1979. header("Cache-Control: private");
  1980. header("Content-type: application/pdf");
  1981. //header("Content-Length: " . strlen($tmp));
  1982. $fileName = (isset($options['Content-Disposition']) ? $options['Content-Disposition'] : 'file.pdf');
  1983. if ( !isset($options["Attachment"]))
  1984. $options["Attachment"] = true;
  1985. $attachment = $options["Attachment"] ? "attachment" : "inline";
  1986. header("Content-Disposition: $attachment; filename=\"$fileName\"");
  1987. if (isset($options['Accept-Ranges']) && $options['Accept-Ranges'] == 1) {
  1988. header("Accept-Ranges: " . strlen($tmp));
  1989. }
  1990. echo $tmp;
  1991. flush();
  1992. }
  1993. /**
  1994. * return the height in units of the current font in the given size
  1995. */
  1996. function getFontHeight($size) {
  1997. if (!$this->numFonts) {
  1998. $this->selectFont('./fonts/Helvetica');
  1999. }
  2000. // for the current font, and the given size, what is the height of the font in user units
  2001. $h = $this->fonts[$this->currentFont]['FontBBox'][3]-$this->fonts[$this->currentFont]['FontBBox'][1];
  2002. return $size*$h/1000;
  2003. }
  2004. /**
  2005. * return the font decender, this will normally return a negative number
  2006. * if you add this number to the baseline, you get the level of the bottom of the font
  2007. * it is in the pdf user units
  2008. */
  2009. function getFontDecender($size) {
  2010. // note that this will most likely return a negative value
  2011. if (!$this->numFonts) {
  2012. $this->selectFont('./fonts/Helvetica');
  2013. }
  2014. $h = $this->fonts[$this->currentFont]['FontBBox'][1];
  2015. return $size*$h/1000;
  2016. }
  2017. /**
  2018. * filter the text, this is applied to all text just before being inserted into the pdf document
  2019. * it escapes the various things that need to be escaped, and so on
  2020. *
  2021. * @access private
  2022. */
  2023. function filterText($text) {
  2024. $search = array("\\", "(", ")", "&lt;", "&gt;", "&#039;", "&quot;", "&amp;");
  2025. $replace = array("\\\\", "\(", "\)", "<", ">", "\'", '"', "&");
  2026. $text = str_replace($search, $replace, $text);
  2027. return $text;
  2028. }
  2029. /**
  2030. * given a start position and information about how text is to be laid out, calculate where
  2031. * on the page the text will end
  2032. *
  2033. * @access private
  2034. */
  2035. function PRVTgetTextPosition($x, $y, $angle, $size, $wa, $text) {
  2036. // given this information return an array containing x and y for the end position as elements 0 and 1
  2037. $w = $this->getTextWidth($size, $text);
  2038. // need to adjust for the number of spaces in this text
  2039. $words = explode(' ', $text);
  2040. $nspaces = count($words) -1;
  2041. $w+= $wa*$nspaces;
  2042. $a = deg2rad((float)$angle);
  2043. return array(cos($a) *$w+$x, -sin($a) *$w+$y);
  2044. }
  2045. /**
  2046. * wrapper function for PRVTcheckTextDirective1
  2047. *
  2048. * @access private
  2049. */
  2050. function PRVTcheckTextDirective(&$text, $i, &$f) {
  2051. return 0;
  2052. $x = 0;
  2053. $y = 0;
  2054. return $this->PRVTcheckTextDirective1($text, $i, $f, 0, $x, $y);
  2055. }
  2056. /**
  2057. * checks if the text stream contains a control directive
  2058. * if so then makes some changes and returns the number of characters involved in the directive
  2059. * this has been re-worked to include everything neccesary to fins the current writing point, so that
  2060. * the location can be sent to the callback function if required
  2061. * if the directive does not require a font change, then $f should be set to 0
  2062. *
  2063. * @access private
  2064. */
  2065. function PRVTcheckTextDirective1(&$text, $i, &$f, $final, &$x, &$y, $size = 0, $angle = 0, $wordSpaceAdjust = 0) {
  2066. return 0;
  2067. $directive = 0;
  2068. $j = $i;
  2069. if ($text[$j] == '<') {
  2070. $j++;
  2071. switch ($text[$j]) {
  2072. case '/':
  2073. $j++;
  2074. if (strlen($text) <= $j) {
  2075. return $directive;
  2076. }
  2077. switch ($text[$j]) {
  2078. case 'b':
  2079. case 'i':
  2080. $j++;
  2081. if ($text[$j] == '>') {
  2082. $p = strrpos($this->currentTextState, $text[$j-1]);
  2083. if ($p !== false) {
  2084. // then there is one to remove
  2085. $this->currentTextState = substr($this->currentTextState, 0, $p) .substr($this->currentTextState, $p+1);
  2086. }
  2087. $directive = $j-$i+1;
  2088. }
  2089. break;
  2090. case 'c':
  2091. // this this might be a callback function
  2092. $j++;
  2093. $k = strpos($text, '>', $j);
  2094. if ($k !== false && $text[$j] == ':') {
  2095. // then this will be treated as a callback directive
  2096. $directive = $k-$i+1;
  2097. $f = 0;
  2098. // split the remainder on colons to get the function name and the paramater
  2099. $tmp = substr($text, $j+1, $k-$j-1);
  2100. $b1 = strpos($tmp, ':');
  2101. if ($b1 !== false) {
  2102. $func = substr($tmp, 0, $b1);
  2103. $parm = substr($tmp, $b1+1);
  2104. } else {
  2105. $func = $tmp;
  2106. $parm = '';
  2107. }
  2108. if (!isset($func) || !strlen(trim($func))) {
  2109. $directive = 0;
  2110. } else {
  2111. // only call the function if this is the final call
  2112. if ($final) {
  2113. // need to assess the text position, calculate the text width to this point
  2114. // can use getTextWidth to find the text width I think
  2115. $tmp = $this->PRVTgetTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, substr($text, 0, $i));
  2116. $info = array('x' => $tmp[0], 'y' => $tmp[1], 'angle' => $angle, 'status' => 'end', 'p' => $parm, 'nCallback' => $this->nCallback);
  2117. $x = $tmp[0];
  2118. $y = $tmp[1];
  2119. $ret = $this->$func($info);
  2120. if (is_array($ret)) {
  2121. // then the return from the callback function could set the position, to start with, later will do font colour, and font
  2122. foreach($ret as $rk => $rv) {
  2123. switch ($rk) {
  2124. case 'x':
  2125. case 'y':
  2126. $$rk = $rv;
  2127. break;
  2128. }
  2129. }
  2130. }
  2131. // also remove from to the stack
  2132. // for simplicity, just take from the end, fix this another day
  2133. $this->nCallback--;
  2134. if ($this->nCallback<0) {
  2135. $this->nCallBack = 0;
  2136. }
  2137. }
  2138. }
  2139. }
  2140. break;
  2141. }
  2142. break;
  2143. case 'b':
  2144. case 'i':
  2145. $j++;
  2146. if ($text[$j] == '>') {
  2147. $this->currentTextState.= $text[$j-1];
  2148. $directive = $j-$i+1;
  2149. }
  2150. break;
  2151. case 'C':
  2152. $noClose = 1;
  2153. case 'c':
  2154. // this this might be a callback function
  2155. $j++;
  2156. $k = strpos($text, '>', $j);
  2157. if ($k !== false && $text[$j] == ':') {
  2158. // then this will be treated as a callback directive
  2159. $directive = $k-$i+1;
  2160. $f = 0;
  2161. // split the remainder on colons to get the function name and the paramater
  2162. // $bits = explode(':',substr($text,$j+1,$k-$j-1));
  2163. $tmp = substr($text, $j+1, $k-$j-1);
  2164. $b1 = strpos($tmp, ':');
  2165. if ($b1 !== false) {
  2166. $func = substr($tmp, 0, $b1);
  2167. $parm = substr($tmp, $b1+1);
  2168. } else {
  2169. $func = $tmp;
  2170. $parm = '';
  2171. }
  2172. if (!isset($func) || !strlen(trim($func))) {
  2173. $directive = 0;
  2174. } else {
  2175. // only call the function if this is the final call, ie, the one actually doing printing, not measurement
  2176. if ($final) {
  2177. // need to assess the text position, calculate the text width to this point
  2178. // can use getTextWidth to find the text width I think
  2179. // also add the text height and decender
  2180. $tmp = $this->PRVTgetTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, substr($text, 0, $i));
  2181. $info = array('x' => $tmp[0], 'y' => $tmp[1], 'angle' => $angle, 'status' => 'start', 'p' => $parm, 'f' => $func, 'height' => $this->getFontHeight($size), 'decender' => $this->getFontDecender($size));
  2182. $x = $tmp[0];
  2183. $y = $tmp[1];
  2184. if (!isset($noClose) || !$noClose) {
  2185. // only add to the stack if this is a small 'c', therefore is a start-stop pair
  2186. $this->nCallback++;
  2187. $info['nCallback'] = $this->nCallback;
  2188. $this->callback[$this->nCallback] = $info;
  2189. }
  2190. $ret = $this->$func($info);
  2191. if (is_array($ret)) {
  2192. // then the return from the callback function could set the position, to start with, later will do font colour, and font
  2193. foreach($ret as $rk => $rv) {
  2194. switch ($rk) {
  2195. case 'x':
  2196. case 'y':
  2197. $$rk = $rv;
  2198. break;
  2199. }
  2200. }
  2201. }
  2202. }
  2203. }
  2204. }
  2205. break;
  2206. }
  2207. }
  2208. return $directive;
  2209. }
  2210. /**
  2211. * add text to the document, at a specified location, size and angle on the page
  2212. */
  2213. function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0) {
  2214. if (!$this->numFonts) {
  2215. $this->selectFont('./fonts/Helvetica');
  2216. }
  2217. // if there are any open callbacks, then they should be called, to show the start of the line
  2218. if ($this->nCallback>0) {
  2219. for ($i = $this->nCallback;$i>0;$i--) {
  2220. // call each function
  2221. $info = array('x' => $x,
  2222. 'y' => $y,
  2223. 'angle' => $angle,
  2224. 'status' => 'sol',
  2225. 'p' => $this->callback[$i]['p'],
  2226. 'nCallback' => $this->callback[$i]['nCallback'],
  2227. 'height' => $this->callback[$i]['height'],
  2228. 'decender' => $this->callback[$i]['decender']);
  2229. $func = $this->callback[$i]['f'];
  2230. $this->$func($info);
  2231. }
  2232. }
  2233. if ($angle == 0) {
  2234. $this->objects[$this->currentContents]['c'].= "\n".'BT '.sprintf('%.3f', $x) .' '.sprintf('%.3f', $y) .' Td';
  2235. } else {
  2236. $a = deg2rad((float)$angle);
  2237. $tmp = "\n".'BT ';
  2238. $tmp.= sprintf('%.3f', cos($a)) .' '.sprintf('%.3f', (-1.0*sin($a))) .' '.sprintf('%.3f', sin($a)) .' '.sprintf('%.3f', cos($a)) .' ';
  2239. $tmp.= sprintf('%.3f', $x) .' '.sprintf('%.3f', $y) .' Tm';
  2240. $this->objects[$this->currentContents]['c'].= $tmp;
  2241. }
  2242. if ($wordSpaceAdjust != 0 || $wordSpaceAdjust != $this->wordSpaceAdjust) {
  2243. $this->wordSpaceAdjust = $wordSpaceAdjust;
  2244. $this->objects[$this->currentContents]['c'].= ' '.sprintf('%.3f', $wordSpaceAdjust) .' Tw';
  2245. }
  2246. $len = strlen($text);
  2247. $start = 0;
  2248. /*
  2249. for ($i = 0;$i<$len;$i++){
  2250. $f = 1;
  2251. $directive = 0; //$this->PRVTcheckTextDirective($text,$i,$f);
  2252. if ($directive){
  2253. // then we should write what we need to
  2254. if ($i>$start){
  2255. $part = substr($text,$start,$i-$start);
  2256. $this->objects[$this->currentContents]['c'] .= ' /F'.$this->currentFontNum.' '.sprintf('%.1f',$size).' Tf ';
  2257. $this->objects[$this->currentContents]['c'] .= ' ('.$this->filterText($part).') Tj';
  2258. }
  2259. if ($f){
  2260. // then there was nothing drastic done here, restore the contents
  2261. $this->setCurrentFont();
  2262. } else {
  2263. $this->objects[$this->currentContents]['c'] .= ' ET';
  2264. $f = 1;
  2265. $xp = $x;
  2266. $yp = $y;
  2267. $directive = 0; //$this->PRVTcheckTextDirective1($text,$i,$f,1,$xp,$yp,$size,$angle,$wordSpaceAdjust);
  2268. // restart the text object
  2269. if ($angle == 0){
  2270. $this->objects[$this->currentContents]['c'] .= "\n".'BT '.sprintf('%.3f',$xp).' '.sprintf('%.3f',$yp).' Td';
  2271. } else {
  2272. $a = deg2rad((float)$angle);
  2273. $tmp = "\n".'BT ';
  2274. $tmp .= sprintf('%.3f',cos($a)).' '.sprintf('%.3f',(-1.0*sin($a))).' '.sprintf('%.3f',sin($a)).' '.sprintf('%.3f',cos($a)).' ';
  2275. $tmp .= sprintf('%.3f',$xp).' '.sprintf('%.3f',$yp).' Tm';
  2276. $this->objects[$this->currentContents]['c'] .= $tmp;
  2277. }
  2278. if ($wordSpaceAdjust != 0 || $wordSpaceAdjust != $this->wordSpaceAdjust){
  2279. $this->wordSpaceAdjust = $wordSpaceAdjust;
  2280. $this->objects[$this->currentContents]['c'] .= ' '.sprintf('%.3f',$wordSpaceAdjust).' Tw';
  2281. }
  2282. }
  2283. // and move the writing point to the next piece of text
  2284. $i = $i+$directive-1;
  2285. $start = $i+1;
  2286. }
  2287. }
  2288. */
  2289. if ($start < $len) {
  2290. $part = substr($text, $start);
  2291. $this->objects[$this->currentContents]['c'].= ' /F'.$this->currentFontNum.' '.sprintf('%.1f', $size) .' Tf ';
  2292. $this->objects[$this->currentContents]['c'].= ' ('.$this->filterText($part) .') Tj';
  2293. }
  2294. $this->objects[$this->currentContents]['c'].= ' ET';
  2295. // if there are any open callbacks, then they should be called, to show the end of the line
  2296. if ($this->nCallback>0) {
  2297. for ($i = $this->nCallback;$i>0;$i--) {
  2298. // call each function
  2299. $tmp = $this->PRVTgetTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, $text);
  2300. $info = array('x' => $tmp[0], 'y' => $tmp[1], 'angle' => $angle, 'status' => 'eol', 'p' => $this->callback[$i]['p'], 'nCallback' => $this->callback[$i]['nCallback'], 'height' => $this->callback[$i]['height'], 'decender' => $this->callback[$i]['decender']);
  2301. $func = $this->callback[$i]['f'];
  2302. $this->$func($info);
  2303. }
  2304. }
  2305. }
  2306. /**
  2307. * calculate how wide a given text string will be on a page, at a given size.
  2308. * this can be called externally, but is alse used by the other class functions
  2309. */
  2310. function getTextWidth($size, $text, $spacing = 0) {
  2311. // this function should not change any of the settings, though it will need to
  2312. // track any directives which change during calculation, so copy them at the start
  2313. // and put them back at the end.
  2314. $store_currentTextState = $this->currentTextState;
  2315. if (!$this->numFonts) {
  2316. $this->selectFont('./fonts/Helvetica');
  2317. }
  2318. // converts a number or a float to a string so it can get the width
  2319. $text = "$text";
  2320. // hmm, this is where it all starts to get tricky - use the font information to
  2321. // calculate the width of each character, add them up and convert to user units
  2322. $w = 0;
  2323. $len = strlen($text);
  2324. $cf = $this->currentFont;
  2325. $space_scale = 1000 / $size;
  2326. for ($i = 0; $i < $len; $i++) {
  2327. // $f = 1;
  2328. // $directive = 0; //$this->PRVTcheckTextDirective($text,$i,$f);
  2329. // if ($directive){
  2330. // if ($f){
  2331. // $this->setCurrentFont();
  2332. // $cf = $this->currentFont;
  2333. // }
  2334. // $i = $i+$directive-1;
  2335. // } else {
  2336. $char = ord($text{$i});
  2337. if ( isset($this->fonts[$cf]['differences'][$char])) {
  2338. // then this character is being replaced by another
  2339. $name = $this->fonts[$cf]['differences'][$char];
  2340. if ( isset($this->fonts[$cf]['C'][$name]['WX']))
  2341. $w+= $this->fonts[$cf]['C'][$name]['WX'];
  2342. } else if (isset($this->fonts[$cf]['C'][$char]['WX']))
  2343. $w+= $this->fonts[$cf]['C'][$char]['WX'];
  2344. if ( $char == 32) // Space
  2345. $w+= $spacing * $space_scale;
  2346. }
  2347. $this->currentTextState = $store_currentTextState;
  2348. $this->setCurrentFont();
  2349. return $w*$size/1000;
  2350. }
  2351. /**
  2352. * do a part of the calculation for sorting out the justification of the text
  2353. *
  2354. * @access private
  2355. */
  2356. function PRVTadjustWrapText($text, $actual, $width, &$x, &$adjust, $justification) {
  2357. switch ($justification) {
  2358. case 'left':
  2359. return;
  2360. break;
  2361. case 'right':
  2362. $x+= $width-$actual;
  2363. break;
  2364. case 'center':
  2365. case 'centre':
  2366. $x+= ($width-$actual) /2;
  2367. break;
  2368. case 'full':
  2369. // count the number of words
  2370. $words = explode(' ', $text);
  2371. $nspaces = count($words) -1;
  2372. if ($nspaces>0) {
  2373. $adjust = ($width-$actual) /$nspaces;
  2374. } else {
  2375. $adjust = 0;
  2376. }
  2377. break;
  2378. }
  2379. }
  2380. /**
  2381. * add text to the page, but ensure that it fits within a certain width
  2382. * if it does not fit then put in as much as possible, splitting at word boundaries
  2383. * and return the remainder.
  2384. * justification and angle can also be specified for the text
  2385. */
  2386. function addTextWrap($x, $y, $width, $size, $text, $justification = 'left', $angle = 0, $test = 0) {
  2387. // this will display the text, and if it goes beyond the width $width, will backtrack to the
  2388. // previous space or hyphen, and return the remainder of the text.
  2389. // $justification can be set to 'left','right','center','centre','full'
  2390. // need to store the initial text state, as this will change during the width calculation
  2391. // but will need to be re-set before printing, so that the chars work out right
  2392. $store_currentTextState = $this->currentTextState;
  2393. if (!$this->numFonts) {
  2394. $this->selectFont('./fonts/Helvetica');
  2395. }
  2396. if ($width <= 0) {
  2397. // error, pretend it printed ok, otherwise risking a loop
  2398. return '';
  2399. }
  2400. $w = 0;
  2401. $break = 0;
  2402. $breakWidth = 0;
  2403. $len = strlen($text);
  2404. $cf = $this->currentFont;
  2405. $tw = $width/$size*1000;
  2406. for ($i = 0;$i<$len;$i++) {
  2407. $f = 1;
  2408. $directive = 0;
  2409. //$this->PRVTcheckTextDirective($text,$i,$f);
  2410. if ($directive) {
  2411. if ($f) {
  2412. $this->setCurrentFont();
  2413. $cf = $this->currentFont;
  2414. }
  2415. $i = $i+$directive-1;
  2416. } else {
  2417. $cOrd = ord($text[$i]);
  2418. if (isset($this->fonts[$cf]['differences'][$cOrd])) {
  2419. // then this character is being replaced by another
  2420. $cOrd2 = $this->fonts[$cf]['differences'][$cOrd];
  2421. } else {
  2422. $cOrd2 = $cOrd;
  2423. }
  2424. if (isset($this->fonts[$cf]['C'][$cOrd2]['WX'])) {
  2425. $w+= $this->fonts[$cf]['C'][$cOrd2]['WX'];
  2426. }
  2427. if ($w>$tw) {
  2428. // then we need to truncate this line
  2429. if ($break>0) {
  2430. // then we have somewhere that we can split :)
  2431. if ($text[$break] == ' ') {
  2432. $tmp = substr($text, 0, $break);
  2433. } else {
  2434. $tmp = substr($text, 0, $break+1);
  2435. }
  2436. $adjust = 0;
  2437. $this->PRVTadjustWrapText($tmp, $breakWidth, $width, $x, $adjust, $justification);
  2438. // reset the text state
  2439. $this->currentTextState = $store_currentTextState;
  2440. $this->setCurrentFont();
  2441. if (!$test) {
  2442. $this->addText($x, $y, $size, $tmp, $angle, $adjust);
  2443. }
  2444. return substr($text, $break+1);
  2445. } else {
  2446. // just split before the current character
  2447. $tmp = substr($text, 0, $i);
  2448. $adjust = 0;
  2449. $ctmp = ord($text[$i]);
  2450. if (isset($this->fonts[$cf]['differences'][$ctmp])) {
  2451. $ctmp = $this->fonts[$cf]['differences'][$ctmp];
  2452. }
  2453. $tmpw = ($w-$this->fonts[$cf]['C'][$ctmp]['WX']) *$size/1000;
  2454. $this->PRVTadjustWrapText($tmp, $tmpw, $width, $x, $adjust, $justification);
  2455. // reset the text state
  2456. $this->currentTextState = $store_currentTextState;
  2457. $this->setCurrentFont();
  2458. if (!$test) {
  2459. $this->addText($x, $y, $size, $tmp, $angle, $adjust);
  2460. }
  2461. return substr($text, $i);
  2462. }
  2463. }
  2464. if ($text[$i] == '-') {
  2465. $break = $i;
  2466. $breakWidth = $w*$size/1000;
  2467. }
  2468. if ($text[$i] == ' ') {
  2469. $break = $i;
  2470. $ctmp = ord($text[$i]);
  2471. if (isset($this->fonts[$cf]['differences'][$ctmp])) {
  2472. $ctmp = $this->fonts[$cf]['differences'][$ctmp];
  2473. }
  2474. $breakWidth = ($w-$this->fonts[$cf]['C'][$ctmp]['WX']) *$size/1000;
  2475. }
  2476. }
  2477. }
  2478. // then there was no need to break this line
  2479. if ($justification == 'full') {
  2480. $justification = 'left';
  2481. }
  2482. $adjust = 0;
  2483. $tmpw = $w*$size/1000;
  2484. $this->PRVTadjustWrapText($text, $tmpw, $width, $x, $adjust, $justification);
  2485. // reset the text state
  2486. $this->currentTextState = $store_currentTextState;
  2487. $this->setCurrentFont();
  2488. if (!$test) {
  2489. $this->addText($x, $y, $size, $text, $angle, $adjust, $angle);
  2490. }
  2491. return '';
  2492. }
  2493. /**
  2494. * this will be called at a new page to return the state to what it was on the
  2495. * end of the previous page, before the stack was closed down
  2496. * This is to get around not being able to have open 'q' across pages
  2497. *
  2498. */
  2499. function saveState($pageEnd = 0) {
  2500. if ($pageEnd) {
  2501. // this will be called at a new page to return the state to what it was on the
  2502. // end of the previous page, before the stack was closed down
  2503. // This is to get around not being able to have open 'q' across pages
  2504. $opt = $this->stateStack[$pageEnd];
  2505. // ok to use this as stack starts numbering at 1
  2506. $this->setColor($opt['col']['r'], $opt['col']['g'], $opt['col']['b'], 1);
  2507. $this->setStrokeColor($opt['str']['r'], $opt['str']['g'], $opt['str']['b'], 1);
  2508. $this->objects[$this->currentContents]['c'].= "\n".$opt['lin'];
  2509. // $this->currentLineStyle = $opt['lin'];
  2510. } else {
  2511. $this->nStateStack++;
  2512. $this->stateStack[$this->nStateStack] = array(
  2513. 'col' => $this->currentColour, 'str' => $this->currentStrokeColour, 'lin' => $this->currentLineStyle);
  2514. }
  2515. $this->objects[$this->currentContents]['c'].= "\nq";
  2516. }
  2517. /**
  2518. * restore a previously saved state
  2519. */
  2520. function restoreState($pageEnd = 0) {
  2521. if (!$pageEnd) {
  2522. $n = $this->nStateStack;
  2523. $this->currentColour = $this->stateStack[$n]['col'];
  2524. $this->currentStrokeColour = $this->stateStack[$n]['str'];
  2525. $this->objects[$this->currentContents]['c'].= "\n".$this->stateStack[$n]['lin'];
  2526. $this->currentLineStyle = $this->stateStack[$n]['lin'];
  2527. unset($this->stateStack[$n]);
  2528. $this->nStateStack--;
  2529. }
  2530. $this->objects[$this->currentContents]['c'].= "\nQ";
  2531. }
  2532. /**
  2533. * make a loose object, the output will go into this object, until it is closed, then will revert to
  2534. * the current one.
  2535. * this object will not appear until it is included within a page.
  2536. * the function will return the object number
  2537. */
  2538. function openObject() {
  2539. $this->nStack++;
  2540. $this->stack[$this->nStack] = array('c' => $this->currentContents, 'p' => $this->currentPage);
  2541. // add a new object of the content type, to hold the data flow
  2542. $this->numObj++;
  2543. $this->o_contents($this->numObj, 'new');
  2544. $this->currentContents = $this->numObj;
  2545. $this->looseObjects[$this->numObj] = 1;
  2546. return $this->numObj;
  2547. }
  2548. /**
  2549. * open an existing object for editing
  2550. */
  2551. function reopenObject($id) {
  2552. $this->nStack++;
  2553. $this->stack[$this->nStack] = array('c' => $this->currentContents, 'p' => $this->currentPage);
  2554. $this->currentContents = $id;
  2555. // also if this object is the primary contents for a page, then set the current page to its parent
  2556. if (isset($this->objects[$id]['onPage'])) {
  2557. $this->currentPage = $this->objects[$id]['onPage'];
  2558. }
  2559. }
  2560. /**
  2561. * close an object
  2562. */
  2563. function closeObject() {
  2564. // close the object, as long as there was one open in the first place, which will be indicated by
  2565. // an objectId on the stack.
  2566. if ($this->nStack>0) {
  2567. $this->currentContents = $this->stack[$this->nStack]['c'];
  2568. $this->currentPage = $this->stack[$this->nStack]['p'];
  2569. $this->nStack--;
  2570. // easier to probably not worry about removing the old entries, they will be overwritten
  2571. // if there are new ones.
  2572. }
  2573. }
  2574. /**
  2575. * stop an object from appearing on pages from this point on
  2576. */
  2577. function stopObject($id) {
  2578. // if an object has been appearing on pages up to now, then stop it, this page will
  2579. // be the last one that could contian it.
  2580. if (isset($this->addLooseObjects[$id])) {
  2581. $this->addLooseObjects[$id] = '';
  2582. }
  2583. }
  2584. /**
  2585. * after an object has been created, it wil only show if it has been added, using this function.
  2586. */
  2587. function addObject($id, $options = 'add') {
  2588. // add the specified object to the page
  2589. if (isset($this->looseObjects[$id]) && $this->currentContents != $id) {
  2590. // then it is a valid object, and it is not being added to itself
  2591. switch ($options) {
  2592. case 'all':
  2593. // then this object is to be added to this page (done in the next block) and
  2594. // all future new pages.
  2595. $this->addLooseObjects[$id] = 'all';
  2596. case 'add':
  2597. if (isset($this->objects[$this->currentContents]['onPage'])) {
  2598. // then the destination contents is the primary for the page
  2599. // (though this object is actually added to that page)
  2600. $this->o_page($this->objects[$this->currentContents]['onPage'], 'content', $id);
  2601. }
  2602. break;
  2603. case 'even':
  2604. $this->addLooseObjects[$id] = 'even';
  2605. $pageObjectId = $this->objects[$this->currentContents]['onPage'];
  2606. if ($this->objects[$pageObjectId]['info']['pageNum']%2 == 0) {
  2607. $this->addObject($id);
  2608. // hacky huh :)
  2609. }
  2610. break;
  2611. case 'odd':
  2612. $this->addLooseObjects[$id] = 'odd';
  2613. $pageObjectId = $this->objects[$this->currentContents]['onPage'];
  2614. if ($this->objects[$pageObjectId]['info']['pageNum']%2 == 1) {
  2615. $this->addObject($id);
  2616. // hacky huh :)
  2617. }
  2618. break;
  2619. case 'next':
  2620. $this->addLooseObjects[$id] = 'all';
  2621. break;
  2622. case 'nexteven':
  2623. $this->addLooseObjects[$id] = 'even';
  2624. break;
  2625. case 'nextodd':
  2626. $this->addLooseObjects[$id] = 'odd';
  2627. break;
  2628. }
  2629. }
  2630. }
  2631. /**
  2632. * return a storable representation of a specific object
  2633. */
  2634. function serializeObject($id) {
  2635. if ( array_key_exists($id, $this->objects))
  2636. return var_export($this->objects[$id], true);
  2637. return null;
  2638. }
  2639. /**
  2640. * restore an object from its stored representation. returns its new object id.
  2641. */
  2642. function restoreSerializedObject($obj) {
  2643. $obj_id = $this->openObject();
  2644. eval('$this->objects[$obj_id] = ' . $obj . ';');
  2645. $this->closeObject();
  2646. return $obj_id;
  2647. }
  2648. /**
  2649. * add content to the documents info object
  2650. */
  2651. function addInfo($label, $value = 0) {
  2652. // this will only work if the label is one of the valid ones.
  2653. // modify this so that arrays can be passed as well.
  2654. // if $label is an array then assume that it is key => value pairs
  2655. // else assume that they are both scalar, anything else will probably error
  2656. if (is_array($label)) {
  2657. foreach ($label as $l => $v) {
  2658. $this->o_info($this->infoObject, $l, $v);
  2659. }
  2660. } else {
  2661. $this->o_info($this->infoObject, $label, $value);
  2662. }
  2663. }
  2664. /**
  2665. * set the viewer preferences of the document, it is up to the browser to obey these.
  2666. */
  2667. function setPreferences($label, $value = 0) {
  2668. // this will only work if the label is one of the valid ones.
  2669. if (is_array($label)) {
  2670. foreach ($label as $l => $v) {
  2671. $this->o_catalog($this->catalogId, 'viewerPreferences', array($l => $v));
  2672. }
  2673. } else {
  2674. $this->o_catalog($this->catalogId, 'viewerPreferences', array($label => $value));
  2675. }
  2676. }
  2677. /**
  2678. * extract an integer from a position in a byte stream
  2679. *
  2680. * @access private
  2681. */
  2682. function PRVT_getBytes(&$data, $pos, $num) {
  2683. // return the integer represented by $num bytes from $pos within $data
  2684. $ret = 0;
  2685. for ($i = 0;$i<$num;$i++) {
  2686. $ret = $ret*256;
  2687. $ret+= ord($data[$pos+$i]);
  2688. }
  2689. return $ret;
  2690. }
  2691. /**
  2692. * add a PNG image into the document, from a file
  2693. * this should work with remote files
  2694. */
  2695. function addPngFromFile($file, $x, $y, $w = 0, $h = 0) {
  2696. // read in a png file, interpret it, then add to the system
  2697. $error = 0;
  2698. $tmp = get_magic_quotes_runtime();
  2699. set_magic_quotes_runtime(0);
  2700. if ( ($data = file_get_contents($file)) === false) {
  2701. // $fp = @fopen($file,'rb');
  2702. // if ($fp){
  2703. // $data = '';
  2704. // while(!feof($fp)){
  2705. // $data .= fread($fp,1024);
  2706. // }
  2707. // fclose($fp);
  2708. $error = 1;
  2709. $errormsg = 'trouble opening file: '.$file;
  2710. }
  2711. set_magic_quotes_runtime($tmp);
  2712. if (!$error) {
  2713. $header = chr(137) .chr(80) .chr(78) .chr(71) .chr(13) .chr(10) .chr(26) .chr(10);
  2714. if (substr($data, 0, 8) != $header) {
  2715. $error = 1;
  2716. $errormsg = 'this file does not have a valid header';
  2717. }
  2718. }
  2719. if (!$error) {
  2720. // set pointer
  2721. $p = 8;
  2722. $len = strlen($data);
  2723. // cycle through the file, identifying chunks
  2724. $haveHeader = 0;
  2725. $info = array();
  2726. $idata = '';
  2727. $pdata = '';
  2728. while ($p < $len) {
  2729. $chunkLen = $this->PRVT_getBytes($data, $p, 4);
  2730. $chunkType = substr($data, $p+4, 4);
  2731. // echo $chunkType.' - '.$chunkLen.'<br>';
  2732. switch ($chunkType) {
  2733. case 'IHDR':
  2734. // this is where all the file information comes from
  2735. $info['width'] = $this->PRVT_getBytes($data, $p+8, 4);
  2736. $info['height'] = $this->PRVT_getBytes($data, $p+12, 4);
  2737. $info['bitDepth'] = ord($data[$p+16]);
  2738. $info['colorType'] = ord($data[$p+17]);
  2739. $info['compressionMethod'] = ord($data[$p+18]);
  2740. $info['filterMethod'] = ord($data[$p+19]);
  2741. $info['interlaceMethod'] = ord($data[$p+20]);
  2742. //print_r($info);
  2743. $haveHeader = 1;
  2744. if ($info['compressionMethod'] != 0) {
  2745. $error = 1;
  2746. $errormsg = 'unsupported compression method';
  2747. }
  2748. if ($info['filterMethod'] != 0) {
  2749. $error = 1;
  2750. $errormsg = 'unsupported filter method';
  2751. }
  2752. break;
  2753. case 'PLTE':
  2754. $pdata.= substr($data, $p+8, $chunkLen);
  2755. break;
  2756. case 'IDAT':
  2757. $idata.= substr($data, $p+8, $chunkLen);
  2758. break;
  2759. case 'tRNS':
  2760. //this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk
  2761. //print "tRNS found, color type = ".$info['colorType']."\n";
  2762. $transparency = array();
  2763. if ($info['colorType'] == 3) {
  2764. // indexed color, rbg
  2765. /* corresponding to entries in the plte chunk
  2766. Alpha for palette index 0: 1 byte
  2767. Alpha for palette index 1: 1 byte
  2768. ...etc...
  2769. */
  2770. // there will be one entry for each palette entry. up until the last non-opaque entry.
  2771. // set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent)
  2772. $transparency['type'] = 'indexed';
  2773. $numPalette = strlen($pdata) /3;
  2774. $trans = 0;
  2775. for ($i = $chunkLen;$i >= 0;$i--) {
  2776. if (ord($data[$p+8+$i]) == 0) {
  2777. $trans = $i;
  2778. }
  2779. }
  2780. $transparency['data'] = $trans;
  2781. } elseif ($info['colorType'] == 0) {
  2782. // grayscale
  2783. /* corresponding to entries in the plte chunk
  2784. Gray: 2 bytes, range 0 .. (2^bitdepth)-1
  2785. */
  2786. // $transparency['grayscale'] = $this->PRVT_getBytes($data,$p+8,2); // g = grayscale
  2787. $transparency['type'] = 'indexed';
  2788. $transparency['data'] = ord($data[$p+8+1]);
  2789. } elseif ($info['colorType'] == 2) {
  2790. // truecolor
  2791. /* corresponding to entries in the plte chunk
  2792. Red: 2 bytes, range 0 .. (2^bitdepth)-1
  2793. Green: 2 bytes, range 0 .. (2^bitdepth)-1
  2794. Blue: 2 bytes, range 0 .. (2^bitdepth)-1
  2795. */
  2796. $transparency['r'] = $this->PRVT_getBytes($data, $p+8, 2);
  2797. // r from truecolor
  2798. $transparency['g'] = $this->PRVT_getBytes($data, $p+10, 2);
  2799. // g from truecolor
  2800. $transparency['b'] = $this->PRVT_getBytes($data, $p+12, 2);
  2801. // b from truecolor
  2802. $transparency['type'] = 'color-key';
  2803. } else {
  2804. //unsupported transparency type
  2805. }
  2806. // KS End new code
  2807. break;
  2808. default:
  2809. break;
  2810. }
  2811. $p+= $chunkLen+12;
  2812. }
  2813. if (!$haveHeader) {
  2814. $error = 1;
  2815. $errormsg = 'information header is missing';
  2816. }
  2817. if (isset($info['interlaceMethod']) && $info['interlaceMethod']) {
  2818. $error = 1;
  2819. $errormsg = 'There appears to be no support for interlaced images in pdf.';
  2820. }
  2821. }
  2822. if (!$error && $info['bitDepth'] > 8) {
  2823. $error = 1;
  2824. $errormsg = 'only bit depth of 8 or less is supported';
  2825. }
  2826. if (!$error) {
  2827. if ($info['colorType'] != 2 && $info['colorType'] != 0 && $info['colorType'] != 3) {
  2828. $error = 1;
  2829. $errormsg = 'transparancey alpha channel not supported, transparency only supported for palette images.';
  2830. } else {
  2831. switch ($info['colorType']) {
  2832. case 3:
  2833. $color = 'DeviceRGB';
  2834. $ncolor = 1;
  2835. break;
  2836. case 2:
  2837. $color = 'DeviceRGB';
  2838. $ncolor = 3;
  2839. break;
  2840. case 0:
  2841. $color = 'DeviceGray';
  2842. $ncolor = 1;
  2843. break;
  2844. }
  2845. }
  2846. }
  2847. if ($error) {
  2848. $this->addMessage('PNG error - ('.$file.') '.$errormsg);
  2849. return;
  2850. }
  2851. if ($w == 0) {
  2852. $w = $h/$info['height']*$info['width'];
  2853. }
  2854. if ($h == 0) {
  2855. $h = $w*$info['height']/$info['width'];
  2856. }
  2857. //print_r($info);
  2858. // so this image is ok... add it in.
  2859. $this->numImages++;
  2860. $im = $this->numImages;
  2861. $label = 'I'.$im;
  2862. $this->numObj++;
  2863. // $this->o_image($this->numObj,'new',array('label' => $label,'data' => $idata,'iw' => $w,'ih' => $h,'type' => 'png','ic' => $info['width']));
  2864. $options = array('label' => $label, 'data' => $idata, 'bitsPerComponent' => $info['bitDepth'], 'pdata' => $pdata, 'iw' => $info['width'], 'ih' => $info['height'], 'type' => 'png', 'color' => $color, 'ncolor' => $ncolor);
  2865. if (isset($transparency)) {
  2866. $options['transparency'] = $transparency;
  2867. }
  2868. $this->o_image($this->numObj, 'new', $options);
  2869. $this->objects[$this->currentContents]['c'].= "\nq";
  2870. $this->objects[$this->currentContents]['c'].= "\n".sprintf('%.3f', $w) ." 0 0 ".sprintf('%.3f', $h) ." ".sprintf('%.3f', $x) ." ".sprintf('%.3f', $y) ." cm";
  2871. $this->objects[$this->currentContents]['c'].= "\n/".$label.' Do';
  2872. $this->objects[$this->currentContents]['c'].= "\nQ";
  2873. }
  2874. /**
  2875. * add a JPEG image into the document, from a file
  2876. */
  2877. function addJpegFromFile($img, $x, $y, $w = 0, $h = 0) {
  2878. // attempt to add a jpeg image straight from a file, using no GD commands
  2879. // note that this function is unable to operate on a remote file.
  2880. if (!file_exists($img)) {
  2881. return;
  2882. }
  2883. $tmp = getimagesize($img);
  2884. $imageWidth = $tmp[0];
  2885. $imageHeight = $tmp[1];
  2886. if (isset($tmp['channels'])) {
  2887. $channels = $tmp['channels'];
  2888. } else {
  2889. $channels = 3;
  2890. }
  2891. if ($w <= 0 && $h <= 0) {
  2892. $w = $imageWidth;
  2893. }
  2894. if ($w == 0) {
  2895. $w = $h/$imageHeight*$imageWidth;
  2896. }
  2897. if ($h == 0) {
  2898. $h = $w*$imageHeight/$imageWidth;
  2899. }
  2900. //$fp = fopen($img,'rb');
  2901. $tmp = get_magic_quotes_runtime();
  2902. set_magic_quotes_runtime(0);
  2903. $data = file_get_contents($img);
  2904. //fread($fp,filesize($img));
  2905. set_magic_quotes_runtime($tmp);
  2906. //fclose($fp);
  2907. $this->addJpegImage_common($data, $x, $y, $w, $h, $imageWidth, $imageHeight, $channels);
  2908. }
  2909. /**
  2910. * add an image into the document, from a GD object
  2911. * this function is not all that reliable, and I would probably encourage people to use
  2912. * the file based functions
  2913. */
  2914. function addImage(&$img, $x, $y, $w = 0, $h = 0, $quality = 75) {
  2915. // add a new image into the current location, as an external object
  2916. // add the image at $x,$y, and with width and height as defined by $w & $h
  2917. // note that this will only work with full colour images and makes them jpg images for display
  2918. // later versions could present lossless image formats if there is interest.
  2919. // there seems to be some problem here in that images that have quality set above 75 do not appear
  2920. // not too sure why this is, but in the meantime I have restricted this to 75.
  2921. if ($quality>75) {
  2922. $quality = 75;
  2923. }
  2924. // if the width or height are set to zero, then set the other one based on keeping the image
  2925. // height/width ratio the same, if they are both zero, then give up :)
  2926. $imageWidth = imagesx($img);
  2927. $imageHeight = imagesy($img);
  2928. if ($w <= 0 && $h <= 0) {
  2929. return;
  2930. }
  2931. if ($w == 0) {
  2932. $w = $h/$imageHeight*$imageWidth;
  2933. }
  2934. if ($h == 0) {
  2935. $h = $w*$imageHeight/$imageWidth;
  2936. }
  2937. // gotta get the data out of the img..
  2938. // so I write to a temp file, and then read it back.. soo ugly, my apologies.
  2939. $tmpDir = '/tmp';
  2940. $tmpName = tempnam($tmpDir, 'img');
  2941. imagejpeg($img, $tmpName, $quality);
  2942. //$fp = fopen($tmpName,'rb');
  2943. $tmp = get_magic_quotes_runtime();
  2944. set_magic_quotes_runtime(0);
  2945. if ( ($data = file_get_contents($tmpName)) === false) {
  2946. // $fp = @fopen($tmpName,'rb');
  2947. // if ($fp){
  2948. // $data = '';
  2949. // while(!feof($fp)){
  2950. // $data .= fread($fp,1024);
  2951. // }
  2952. // fclose($fp);
  2953. $error = 1;
  2954. $errormsg = 'trouble opening file';
  2955. }
  2956. // $data = fread($fp,filesize($tmpName));
  2957. set_magic_quotes_runtime($tmp);
  2958. // fclose($fp);
  2959. unlink($tmpName);
  2960. $this->addJpegImage_common($data, $x, $y, $w, $h, $imageWidth, $imageHeight);
  2961. }
  2962. /**
  2963. * common code used by the two JPEG adding functions
  2964. *
  2965. * @access private
  2966. */
  2967. function addJpegImage_common(&$data, $x, $y, $w = 0, $h = 0, $imageWidth, $imageHeight, $channels = 3) {
  2968. // note that this function is not to be called externally
  2969. // it is just the common code between the GD and the file options
  2970. $this->numImages++;
  2971. $im = $this->numImages;
  2972. $label = 'I'.$im;
  2973. $this->numObj++;
  2974. $this->o_image($this->numObj, 'new', array('label' => $label, 'data' => &$data, 'iw' => $imageWidth, 'ih' => $imageHeight, 'channels' => $channels));
  2975. $this->objects[$this->currentContents]['c'].= "\nq";
  2976. $this->objects[$this->currentContents]['c'].= "\n".sprintf('%.3f', $w) ." 0 0 ".sprintf('%.3f', $h) ." ".sprintf('%.3f', $x) ." ".sprintf('%.3f', $y) ." cm";
  2977. $this->objects[$this->currentContents]['c'].= "\n/".$label.' Do';
  2978. $this->objects[$this->currentContents]['c'].= "\nQ";
  2979. }
  2980. /**
  2981. * specify where the document should open when it first starts
  2982. */
  2983. function openHere($style, $a = 0, $b = 0, $c = 0) {
  2984. // this function will open the document at a specified page, in a specified style
  2985. // the values for style, and the required paramters are:
  2986. // 'XYZ' left, top, zoom
  2987. // 'Fit'
  2988. // 'FitH' top
  2989. // 'FitV' left
  2990. // 'FitR' left,bottom,right
  2991. // 'FitB'
  2992. // 'FitBH' top
  2993. // 'FitBV' left
  2994. $this->numObj++;
  2995. $this->o_destination($this->numObj, 'new', array('page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c));
  2996. $id = $this->catalogId;
  2997. $this->o_catalog($id, 'openHere', $this->numObj);
  2998. }
  2999. /**
  3000. * create a labelled destination within the document
  3001. */
  3002. function addDestination($label, $style, $a = 0, $b = 0, $c = 0) {
  3003. // associates the given label with the destination, it is done this way so that a destination can be specified after
  3004. // it has been linked to
  3005. // styles are the same as the 'openHere' function
  3006. $this->numObj++;
  3007. $this->o_destination($this->numObj, 'new', array('page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c));
  3008. $id = $this->numObj;
  3009. // store the label->idf relationship, note that this means that labels can be used only once
  3010. $this->destinations["$label"] = $id;
  3011. }
  3012. /**
  3013. * define font families, this is used to initialize the font families for the default fonts
  3014. * and for the user to add new ones for their fonts. The default bahavious can be overridden should
  3015. * that be desired.
  3016. */
  3017. function setFontFamily($family, $options = '') {
  3018. if (!is_array($options)) {
  3019. if ($family == 'init') {
  3020. // set the known family groups
  3021. // these font families will be used to enable bold and italic markers to be included
  3022. // within text streams. html forms will be used... <b></b> <i></i>
  3023. $this->fontFamilies['Helvetica.afm'] =
  3024. array('b' => 'Helvetica-Bold.afm',
  3025. 'i' => 'Helvetica-Oblique.afm',
  3026. 'bi' => 'Helvetica-BoldOblique.afm',
  3027. 'ib' => 'Helvetica-BoldOblique.afm');
  3028. $this->fontFamilies['Courier.afm'] =
  3029. array('b' => 'Courier-Bold.afm',
  3030. 'i' => 'Courier-Oblique.afm',
  3031. 'bi' => 'Courier-BoldOblique.afm',
  3032. 'ib' => 'Courier-BoldOblique.afm');
  3033. $this->fontFamilies['Times-Roman.afm'] =
  3034. array('b' => 'Times-Bold.afm',
  3035. 'i' => 'Times-Italic.afm',
  3036. 'bi' => 'Times-BoldItalic.afm',
  3037. 'ib' => 'Times-BoldItalic.afm');
  3038. }
  3039. } else {
  3040. // the user is trying to set a font family
  3041. // note that this can also be used to set the base ones to something else
  3042. if (strlen($family)) {
  3043. $this->fontFamilies[$family] = $options;
  3044. }
  3045. }
  3046. }
  3047. /**
  3048. * used to add messages for use in debugging
  3049. */
  3050. function addMessage($message) {
  3051. $this->messages.= $message."\n";
  3052. }
  3053. /**
  3054. * a few functions which should allow the document to be treated transactionally.
  3055. */
  3056. function transaction($action) {
  3057. switch ($action) {
  3058. case 'start':
  3059. // store all the data away into the checkpoint variable
  3060. $data = get_object_vars($this);
  3061. $this->checkpoint = $data;
  3062. unset($data);
  3063. break;
  3064. case 'commit':
  3065. if (is_array($this->checkpoint) && isset($this->checkpoint['checkpoint'])) {
  3066. $tmp = $this->checkpoint['checkpoint'];
  3067. $this->checkpoint = $tmp;
  3068. unset($tmp);
  3069. } else {
  3070. $this->checkpoint = '';
  3071. }
  3072. break;
  3073. case 'rewind':
  3074. // do not destroy the current checkpoint, but move us back to the state then, so that we can try again
  3075. if (is_array($this->checkpoint)) {
  3076. // can only abort if were inside a checkpoint
  3077. $tmp = $this->checkpoint;
  3078. foreach ($tmp as $k => $v) {
  3079. if ($k != 'checkpoint') {
  3080. $this->$k = $v;
  3081. }
  3082. }
  3083. unset($tmp);
  3084. }
  3085. break;
  3086. case 'abort':
  3087. if (is_array($this->checkpoint)) {
  3088. // can only abort if were inside a checkpoint
  3089. $tmp = $this->checkpoint;
  3090. foreach ($tmp as $k => $v) {
  3091. $this->$k = $v;
  3092. }
  3093. unset($tmp);
  3094. }
  3095. break;
  3096. }
  3097. }
  3098. }
  3099. // end of class
  3100. ?>