PageRenderTime 52ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/library/classes/class.pdf.php

https://bitbucket.org/astawiarski/openemr
PHP | 3088 lines | 2179 code | 170 blank | 739 comment | 394 complexity | 4d3201419558d62a7a758d609f573604 MD5 | raw file
Possible License(s): LGPL-2.1, AGPL-1.0, GPL-2.0, MPL-2.0

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

  1. <?php
  2. /**
  3. * Cpdf
  4. *
  5. * http://www.ros.co.nz/pdf
  6. *
  7. * A PHP class to provide the basic functionality to create a pdf document without
  8. * any requirement for additional modules.
  9. *
  10. * Note that 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. var $numObj=0;
  28. /**
  29. * this array contains all of the pdf objects, ready for final assembly
  30. */
  31. var $objects = array();
  32. /**
  33. * the objectId (number within the objects array) of the document catalog
  34. */
  35. var $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. var $fonts=array();
  41. /**
  42. * a record of the current font
  43. */
  44. var $currentFont='';
  45. /**
  46. * the current base font
  47. */
  48. var $currentBaseFont='';
  49. /**
  50. * the number of the current font within the font array
  51. */
  52. var $currentFontNum=0;
  53. /**
  54. *
  55. */
  56. var $currentNode;
  57. /**
  58. * object number of the current page
  59. */
  60. var $currentPage;
  61. /**
  62. * object number of the currently active contents block
  63. */
  64. var $currentContents;
  65. /**
  66. * number of fonts within the system
  67. */
  68. var $numFonts=0;
  69. /**
  70. * current colour for fill operations, defaults to inactive value, all three components should be between 0 and 1 inclusive when active
  71. */
  72. var $currentColour=array('r'=>-1,'g'=>-1,'b'=>-1);
  73. /**
  74. * current colour for stroke operations (lines etc.)
  75. */
  76. var $currentStrokeColour=array('r'=>-1,'g'=>-1,'b'=>-1);
  77. /**
  78. * current style that lines are drawn in
  79. */
  80. var $currentLineStyle='';
  81. /**
  82. * an array which is used to save the state of the document, mainly the colours and styles
  83. * it is used to temporarily change to another state, the change back to what it was before
  84. */
  85. var $stateStack = array();
  86. /**
  87. * number of elements within the state stack
  88. */
  89. var $nStateStack = 0;
  90. /**
  91. * number of page objects within the document
  92. */
  93. var $numPages=0;
  94. /**
  95. * object Id storage stack
  96. */
  97. var $stack=array();
  98. /**
  99. * number of elements within the object Id storage stack
  100. */
  101. var $nStack=0;
  102. /**
  103. * an array which contains information about the objects which are not firmly attached to pages
  104. * these have been added with the addObject function
  105. */
  106. var $looseObjects=array();
  107. /**
  108. * array contains infomation about how the loose objects are to be added to the document
  109. */
  110. var $addLooseObjects=array();
  111. /**
  112. * the objectId of the information object for the document
  113. * this contains authorship, title etc.
  114. */
  115. var $infoObject=0;
  116. /**
  117. * number of images being tracked within the document
  118. */
  119. var $numImages=0;
  120. /**
  121. * an array containing options about the document
  122. * it defaults to turning on the compression of the objects
  123. */
  124. var $options=array('compression'=>1);
  125. /**
  126. * the objectId of the first page of the document
  127. */
  128. var $firstPageId;
  129. /**
  130. * used to track the last used value of the inter-word spacing, this is so that it is known
  131. * when the spacing is changed.
  132. */
  133. var $wordSpaceAdjust=0;
  134. /**
  135. * the object Id of the procset object
  136. */
  137. var $procsetObjectId;
  138. /**
  139. * store the information about the relationship between font families
  140. * this used so that the code knows which font is the bold version of another font, etc.
  141. * the value of this array is initialised in the constuctor function.
  142. */
  143. var $fontFamilies = array();
  144. /**
  145. * track if the current font is bolded or italicised
  146. */
  147. var $currentTextState = '';
  148. /**
  149. * messages are stored here during processing, these can be selected afterwards to give some useful debug information
  150. */
  151. var $messages='';
  152. /**
  153. * the ancryption array for the document encryption is stored here
  154. */
  155. var $arc4='';
  156. /**
  157. * the object Id of the encryption information
  158. */
  159. var $arc4_objnum=0;
  160. /**
  161. * the file identifier, used to uniquely identify a pdf document
  162. */
  163. var $fileIdentifier='';
  164. /**
  165. * a flag to say if a document is to be encrypted or not
  166. */
  167. var $encrypted=0;
  168. /**
  169. * the ancryption key for the encryption of all the document content (structure is not encrypted)
  170. */
  171. var $encryptionKey='';
  172. /**
  173. * array which forms a stack to keep track of nested callback functions
  174. */
  175. var $callback = array();
  176. /**
  177. * the number of callback functions in the callback array
  178. */
  179. var $nCallback = 0;
  180. /**
  181. * store label->id pairs for named destinations, these will be used to replace internal links
  182. * done this way so that destinations can be defined after the location that links to them
  183. */
  184. var $destinations = array();
  185. /**
  186. * store the stack for the transaction commands, each item in here is a record of the values of all the
  187. * variables within the class, so that the user can rollback at will (from each 'start' command)
  188. * note that this includes the objects array, so these can be large.
  189. */
  190. var $checkpoint = '';
  191. /**
  192. * class constructor
  193. * this will start a new document
  194. * @var array array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
  195. */
  196. function Cpdf ($pageSize=array(0,0,612,792)){
  197. $this->newDocument($pageSize);
  198. // also initialize the font families that are known about already
  199. $this->setFontFamily('init');
  200. // $this->fileIdentifier = md5('xxxxxxxx'.time());
  201. }
  202. /**
  203. * Document object methods (internal use only)
  204. *
  205. * There is about one object method for each type of object in the pdf document
  206. * Each function has the same call list ($id,$action,$options).
  207. * $id = the object ID of the object, or what it is to be if it is being created
  208. * $action = a string specifying the action to be performed, though ALL must support:
  209. * 'new' - create the object with the id $id
  210. * 'out' - produce the output for the pdf object
  211. * $options = optional, a string or array containing the various parameters for the object
  212. *
  213. * These, in conjunction with the output function are the ONLY way for output to be produced
  214. * within the pdf 'file'.
  215. */
  216. /**
  217. *destination object, used to specify the location for the user to jump to, presently on opening
  218. */
  219. function o_destination($id,$action,$options=''){
  220. if ($action!='new'){
  221. $o =& $this->objects[$id];
  222. }
  223. switch($action){
  224. case 'new':
  225. $this->objects[$id]=array('t'=>'destination','info'=>array());
  226. $tmp = '';
  227. switch ($options['type']){
  228. case 'XYZ':
  229. case 'FitR':
  230. $tmp = ' '.$options['p3'].$tmp;
  231. case 'FitH':
  232. case 'FitV':
  233. case 'FitBH':
  234. case 'FitBV':
  235. $tmp = ' '.$options['p1'].' '.$options['p2'].$tmp;
  236. case 'Fit':
  237. case 'FitB':
  238. $tmp = $options['type'].$tmp;
  239. $this->objects[$id]['info']['string']=$tmp;
  240. $this->objects[$id]['info']['page']=$options['page'];
  241. }
  242. break;
  243. case 'out':
  244. $tmp = $o['info'];
  245. $res="\n".$id." 0 obj\n".'['.$tmp['page'].' 0 R /'.$tmp['string']."]\nendobj\n";
  246. return $res;
  247. break;
  248. }
  249. }
  250. /**
  251. * set the viewer preferences
  252. */
  253. function o_viewerPreferences($id,$action,$options=''){
  254. if ($action!='new'){
  255. $o =& $this->objects[$id];
  256. }
  257. switch ($action){
  258. case 'new':
  259. $this->objects[$id]=array('t'=>'viewerPreferences','info'=>array());
  260. break;
  261. case 'add':
  262. foreach($options as $k=>$v){
  263. switch ($k){
  264. case 'HideToolbar':
  265. case 'HideMenubar':
  266. case 'HideWindowUI':
  267. case 'FitWindow':
  268. case 'CenterWindow':
  269. case 'NonFullScreenPageMode':
  270. case 'Direction':
  271. $o['info'][$k]=$v;
  272. break;
  273. }
  274. }
  275. break;
  276. case 'out':
  277. $res="\n".$id." 0 obj\n".'<< ';
  278. foreach($o['info'] as $k=>$v){
  279. $res.="\n/".$k.' '.$v;
  280. }
  281. $res.="\n>>\n";
  282. return $res;
  283. break;
  284. }
  285. }
  286. /**
  287. * define the document catalog, the overall controller for the document
  288. */
  289. function o_catalog($id,$action,$options=''){
  290. if ($action!='new'){
  291. $o =& $this->objects[$id];
  292. }
  293. switch ($action){
  294. case 'new':
  295. $this->objects[$id]=array('t'=>'catalog','info'=>array());
  296. $this->catalogId=$id;
  297. break;
  298. case 'outlines':
  299. case 'pages':
  300. case 'openHere':
  301. $o['info'][$action]=$options;
  302. break;
  303. case 'viewerPreferences':
  304. if (!isset($o['info']['viewerPreferences'])){
  305. $this->numObj++;
  306. $this->o_viewerPreferences($this->numObj,'new');
  307. $o['info']['viewerPreferences']=$this->numObj;
  308. }
  309. $vp = $o['info']['viewerPreferences'];
  310. $this->o_viewerPreferences($vp,'add',$options);
  311. break;
  312. case 'out':
  313. $res="\n".$id." 0 obj\n".'<< /Type /Catalog';
  314. foreach($o['info'] as $k=>$v){
  315. switch($k){
  316. case 'outlines':
  317. $res.="\n".'/Outlines '.$v.' 0 R';
  318. break;
  319. case 'pages':
  320. $res.="\n".'/Pages '.$v.' 0 R';
  321. break;
  322. case 'viewerPreferences':
  323. $res.="\n".'/ViewerPreferences '.$o['info']['viewerPreferences'].' 0 R';
  324. break;
  325. case 'openHere':
  326. $res.="\n".'/OpenAction '.$o['info']['openHere'].' 0 R';
  327. break;
  328. }
  329. }
  330. $res.=" >>\nendobj";
  331. return $res;
  332. break;
  333. }
  334. }
  335. /**
  336. * object which is a parent to the pages in the document
  337. */
  338. function o_pages($id,$action,$options=''){
  339. if ($action!='new'){
  340. $o =& $this->objects[$id];
  341. }
  342. switch ($action){
  343. case 'new':
  344. $this->objects[$id]=array('t'=>'pages','info'=>array());
  345. $this->o_catalog($this->catalogId,'pages',$id);
  346. break;
  347. case 'page':
  348. if (!is_array($options)){
  349. // then it will just be the id of the new page
  350. $o['info']['pages'][]=$options;
  351. } else {
  352. // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
  353. // and pos is either 'before' or 'after', saying where this page will fit.
  354. if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])){
  355. $i = array_search($options['rid'],$o['info']['pages']);
  356. if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i]==$options['rid']){
  357. // then there is a match
  358. // make a space
  359. switch ($options['pos']){
  360. case 'before':
  361. $k = $i;
  362. break;
  363. case 'after':
  364. $k=$i+1;
  365. break;
  366. default:
  367. $k=-1;
  368. break;
  369. }
  370. if ($k>=0){
  371. for ($j=count($o['info']['pages'])-1;$j>=$k;$j--){
  372. $o['info']['pages'][$j+1]=$o['info']['pages'][$j];
  373. }
  374. $o['info']['pages'][$k]=$options['id'];
  375. }
  376. }
  377. }
  378. }
  379. break;
  380. case 'procset':
  381. $o['info']['procset']=$options;
  382. break;
  383. case 'mediaBox':
  384. $o['info']['mediaBox']=$options; // which should be an array of 4 numbers
  385. break;
  386. case 'font':
  387. $o['info']['fonts'][]=array('objNum'=>$options['objNum'],'fontNum'=>$options['fontNum']);
  388. break;
  389. case 'xObject':
  390. $o['info']['xObjects'][]=array('objNum'=>$options['objNum'],'label'=>$options['label']);
  391. break;
  392. case 'out':
  393. if (count($o['info']['pages'])){
  394. $res="\n".$id." 0 obj\n<< /Type /Pages\n/Kids [";
  395. foreach($o['info']['pages'] as $k=>$v){
  396. $res.=$v." 0 R\n";
  397. }
  398. $res.="]\n/Count ".count($this->objects[$id]['info']['pages']);
  399. if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) || isset($o['info']['procset'])){
  400. $res.="\n/Resources <<";
  401. if (isset($o['info']['procset'])){
  402. $res.="\n/ProcSet ".$o['info']['procset']." 0 R";
  403. }
  404. if (isset($o['info']['fonts']) && count($o['info']['fonts'])){
  405. $res.="\n/Font << ";
  406. foreach($o['info']['fonts'] as $finfo){
  407. $res.="\n/F".$finfo['fontNum']." ".$finfo['objNum']." 0 R";
  408. }
  409. $res.=" >>";
  410. }
  411. if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])){
  412. $res.="\n/XObject << ";
  413. foreach($o['info']['xObjects'] as $finfo){
  414. $res.="\n/".$finfo['label']." ".$finfo['objNum']." 0 R";
  415. }
  416. $res.=" >>";
  417. }
  418. $res.="\n>>";
  419. if (isset($o['info']['mediaBox'])){
  420. $tmp=$o['info']['mediaBox'];
  421. $res.="\n/MediaBox [".sprintf('%.3f',$tmp[0]).' '.sprintf('%.3f',$tmp[1]).' '.sprintf('%.3f',$tmp[2]).' '.sprintf('%.3f',$tmp[3]).']';
  422. }
  423. }
  424. $res.="\n >>\nendobj";
  425. } else {
  426. $res="\n".$id." 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
  427. }
  428. return $res;
  429. break;
  430. }
  431. }
  432. /**
  433. * define the outlines in the doc, empty for now
  434. */
  435. function o_outlines($id,$action,$options=''){
  436. if ($action!='new'){
  437. $o =& $this->objects[$id];
  438. }
  439. switch ($action){
  440. case 'new':
  441. $this->objects[$id]=array('t'=>'outlines','info'=>array('outlines'=>array()));
  442. $this->o_catalog($this->catalogId,'outlines',$id);
  443. break;
  444. case 'outline':
  445. $o['info']['outlines'][]=$options;
  446. break;
  447. case 'out':
  448. if (count($o['info']['outlines'])){
  449. $res="\n".$id." 0 obj\n<< /Type /Outlines /Kids [";
  450. foreach($o['info']['outlines'] as $k=>$v){
  451. $res.=$v." 0 R ";
  452. }
  453. $res.="] /Count ".count($o['info']['outlines'])." >>\nendobj";
  454. } else {
  455. $res="\n".$id." 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
  456. }
  457. return $res;
  458. break;
  459. }
  460. }
  461. /**
  462. * an object to hold the font description
  463. */
  464. function o_font($id,$action,$options=''){
  465. if ($action!='new'){
  466. $o =& $this->objects[$id];
  467. }
  468. switch ($action){
  469. case 'new':
  470. $this->objects[$id]=array('t'=>'font','info'=>array('name'=>$options['name'],'SubType'=>'Type1'));
  471. $fontNum=$this->numFonts;
  472. $this->objects[$id]['info']['fontNum']=$fontNum;
  473. // deal with the encoding and the differences
  474. if (isset($options['differences'])){
  475. // then we'll need an encoding dictionary
  476. $this->numObj++;
  477. $this->o_fontEncoding($this->numObj,'new',$options);
  478. $this->objects[$id]['info']['encodingDictionary']=$this->numObj;
  479. } else if (isset($options['encoding'])){
  480. // we can specify encoding here
  481. switch($options['encoding']){
  482. case 'WinAnsiEncoding':
  483. case 'MacRomanEncoding':
  484. case 'MacExpertEncoding':
  485. $this->objects[$id]['info']['encoding']=$options['encoding'];
  486. break;
  487. case 'none':
  488. break;
  489. default:
  490. $this->objects[$id]['info']['encoding']='WinAnsiEncoding';
  491. break;
  492. }
  493. } else {
  494. $this->objects[$id]['info']['encoding']='WinAnsiEncoding';
  495. }
  496. // also tell the pages node about the new font
  497. $this->o_pages($this->currentNode,'font',array('fontNum'=>$fontNum,'objNum'=>$id));
  498. break;
  499. case 'add':
  500. foreach ($options as $k=>$v){
  501. switch ($k){
  502. case 'BaseFont':
  503. $o['info']['name'] = $v;
  504. break;
  505. case 'FirstChar':
  506. case 'LastChar':
  507. case 'Widths':
  508. case 'FontDescriptor':
  509. case 'SubType':
  510. $this->addMessage('o_font '.$k." : ".$v);
  511. $o['info'][$k] = $v;
  512. break;
  513. }
  514. }
  515. break;
  516. case 'out':
  517. $res="\n".$id." 0 obj\n<< /Type /Font\n/Subtype /".$o['info']['SubType']."\n";
  518. $res.="/Name /F".$o['info']['fontNum']."\n";
  519. $res.="/BaseFont /".$o['info']['name']."\n";
  520. if (isset($o['info']['encodingDictionary'])){
  521. // then place a reference to the dictionary
  522. $res.="/Encoding ".$o['info']['encodingDictionary']." 0 R\n";
  523. } else if (isset($o['info']['encoding'])){
  524. // use the specified encoding
  525. $res.="/Encoding /".$o['info']['encoding']."\n";
  526. }
  527. if (isset($o['info']['FirstChar'])){
  528. $res.="/FirstChar ".$o['info']['FirstChar']."\n";
  529. }
  530. if (isset($o['info']['LastChar'])){
  531. $res.="/LastChar ".$o['info']['LastChar']."\n";
  532. }
  533. if (isset($o['info']['Widths'])){
  534. $res.="/Widths ".$o['info']['Widths']." 0 R\n";
  535. }
  536. if (isset($o['info']['FontDescriptor'])){
  537. $res.="/FontDescriptor ".$o['info']['FontDescriptor']." 0 R\n";
  538. }
  539. $res.=">>\nendobj";
  540. return $res;
  541. break;
  542. }
  543. }
  544. /**
  545. * a font descriptor, needed for including additional fonts
  546. */
  547. function o_fontDescriptor($id,$action,$options=''){
  548. if ($action!='new'){
  549. $o =& $this->objects[$id];
  550. }
  551. switch ($action){
  552. case 'new':
  553. $this->objects[$id]=array('t'=>'fontDescriptor','info'=>$options);
  554. break;
  555. case 'out':
  556. $res="\n".$id." 0 obj\n<< /Type /FontDescriptor\n";
  557. foreach ($o['info'] as $label => $value){
  558. switch ($label){
  559. case 'Ascent':
  560. case 'CapHeight':
  561. case 'Descent':
  562. case 'Flags':
  563. case 'ItalicAngle':
  564. case 'StemV':
  565. case 'AvgWidth':
  566. case 'Leading':
  567. case 'MaxWidth':
  568. case 'MissingWidth':
  569. case 'StemH':
  570. case 'XHeight':
  571. case 'CharSet':
  572. if (strlen($value)){
  573. $res.='/'.$label.' '.$value."\n";
  574. }
  575. break;
  576. case 'FontFile':
  577. case 'FontFile2':
  578. case 'FontFile3':
  579. $res.='/'.$label.' '.$value." 0 R\n";
  580. break;
  581. case 'FontBBox':
  582. $res.='/'.$label.' ['.$value[0].' '.$value[1].' '.$value[2].' '.$value[3]."]\n";
  583. break;
  584. case 'FontName':
  585. $res.='/'.$label.' /'.$value."\n";
  586. break;
  587. }
  588. }
  589. $res.=">>\nendobj";
  590. return $res;
  591. break;
  592. }
  593. }
  594. /**
  595. * the font encoding
  596. */
  597. function o_fontEncoding($id,$action,$options=''){
  598. if ($action!='new'){
  599. $o =& $this->objects[$id];
  600. }
  601. switch ($action){
  602. case 'new':
  603. // the options array should contain 'differences' and maybe 'encoding'
  604. $this->objects[$id]=array('t'=>'fontEncoding','info'=>$options);
  605. break;
  606. case 'out':
  607. $res="\n".$id." 0 obj\n<< /Type /Encoding\n";
  608. if (!isset($o['info']['encoding'])){
  609. $o['info']['encoding']='WinAnsiEncoding';
  610. }
  611. if ($o['info']['encoding']!='none'){
  612. $res.="/BaseEncoding /".$o['info']['encoding']."\n";
  613. }
  614. $res.="/Differences \n[";
  615. $onum=-100;
  616. foreach($o['info']['differences'] as $num=>$label){
  617. if ($num!=$onum+1){
  618. // we cannot make use of consecutive numbering
  619. $res.= "\n".$num." /".$label;
  620. } else {
  621. $res.= " /".$label;
  622. }
  623. $onum=$num;
  624. }
  625. $res.="\n]\n>>\nendobj";
  626. return $res;
  627. break;
  628. }
  629. }
  630. /**
  631. * the document procset, solves some problems with printing to old PS printers
  632. */
  633. function o_procset($id,$action,$options=''){
  634. if ($action!='new'){
  635. $o =& $this->objects[$id];
  636. }
  637. switch ($action){
  638. case 'new':
  639. $this->objects[$id]=array('t'=>'procset','info'=>array('PDF'=>1,'Text'=>1));
  640. $this->o_pages($this->currentNode,'procset',$id);
  641. $this->procsetObjectId=$id;
  642. break;
  643. case 'add':
  644. // this is to add new items to the procset list, despite the fact that this is considered
  645. // obselete, the items are required for printing to some postscript printers
  646. switch ($options) {
  647. case 'ImageB':
  648. case 'ImageC':
  649. case 'ImageI':
  650. $o['info'][$options]=1;
  651. break;
  652. }
  653. break;
  654. case 'out':
  655. $res="\n".$id." 0 obj\n[";
  656. foreach ($o['info'] as $label=>$val){
  657. $res.='/'.$label.' ';
  658. }
  659. $res.="]\nendobj";
  660. return $res;
  661. break;
  662. }
  663. }
  664. /**
  665. * define the document information
  666. */
  667. function o_info($id,$action,$options=''){
  668. if ($action!='new'){
  669. $o =& $this->objects[$id];
  670. }
  671. switch ($action){
  672. case 'new':
  673. $this->infoObject=$id;
  674. $date='D:'.date('Ymd');
  675. $this->objects[$id]=array('t'=>'info','info'=>array('Creator'=>'R and OS php pdf writer, http://www.ros.co.nz','CreationDate'=>$date));
  676. break;
  677. case 'Title':
  678. case 'Author':
  679. case 'Subject':
  680. case 'Keywords':
  681. case 'Creator':
  682. case 'Producer':
  683. case 'CreationDate':
  684. case 'ModDate':
  685. case 'Trapped':
  686. $o['info'][$action]=$options;
  687. break;
  688. case 'out':
  689. if ($this->encrypted){
  690. $this->encryptInit($id);
  691. }
  692. $res="\n".$id." 0 obj\n<<\n";
  693. foreach ($o['info'] as $k=>$v){
  694. $res.='/'.$k.' (';
  695. if ($this->encrypted){
  696. $res.=$this->filterText($this->ARC4($v));
  697. } else {
  698. $res.=$this->filterText($v);
  699. }
  700. $res.=")\n";
  701. }
  702. $res.=">>\nendobj";
  703. return $res;
  704. break;
  705. }
  706. }
  707. /**
  708. * an action object, used to link to URLS initially
  709. */
  710. function o_action($id,$action,$options=''){
  711. if ($action!='new'){
  712. $o =& $this->objects[$id];
  713. }
  714. switch ($action){
  715. case 'new':
  716. if (is_array($options)){
  717. $this->objects[$id]=array('t'=>'action','info'=>$options,'type'=>$options['type']);
  718. } else {
  719. // then assume a URI action
  720. $this->objects[$id]=array('t'=>'action','info'=>$options,'type'=>'URI');
  721. }
  722. break;
  723. case 'out':
  724. if ($this->encrypted){
  725. $this->encryptInit($id);
  726. }
  727. $res="\n".$id." 0 obj\n<< /Type /Action";
  728. switch($o['type']){
  729. case 'ilink':
  730. // there will be an 'label' setting, this is the name of the destination
  731. $res.="\n/S /GoTo\n/D ".$this->destinations[(string)$o['info']['label']]." 0 R";
  732. break;
  733. case 'URI':
  734. $res.="\n/S /URI\n/URI (";
  735. if ($this->encrypted){
  736. $res.=$this->filterText($this->ARC4($o['info']));
  737. } else {
  738. $res.=$this->filterText($o['info']);
  739. }
  740. $res.=")";
  741. break;
  742. }
  743. $res.="\n>>\nendobj";
  744. return $res;
  745. break;
  746. }
  747. }
  748. /**
  749. * an annotation object, this will add an annotation to the current page.
  750. * initially will support just link annotations
  751. */
  752. function o_annotation($id,$action,$options=''){
  753. if ($action!='new'){
  754. $o =& $this->objects[$id];
  755. }
  756. switch ($action){
  757. case 'new':
  758. // add the annotation to the current page
  759. $pageId = $this->currentPage;
  760. $this->o_page($pageId,'annot',$id);
  761. // and add the action object which is going to be required
  762. switch($options['type']){
  763. case 'link':
  764. $this->objects[$id]=array('t'=>'annotation','info'=>$options);
  765. $this->numObj++;
  766. $this->o_action($this->numObj,'new',$options['url']);
  767. $this->objects[$id]['info']['actionId']=$this->numObj;
  768. break;
  769. case 'ilink':
  770. // this is to a named internal link
  771. $label = $options['label'];
  772. $this->objects[$id]=array('t'=>'annotation','info'=>$options);
  773. $this->numObj++;
  774. $this->o_action($this->numObj,'new',array('type'=>'ilink','label'=>$label));
  775. $this->objects[$id]['info']['actionId']=$this->numObj;
  776. break;
  777. }
  778. break;
  779. case 'out':
  780. $res="\n".$id." 0 obj\n<< /Type /Annot";
  781. switch($o['info']['type']){
  782. case 'link':
  783. case 'ilink':
  784. $res.= "\n/Subtype /Link";
  785. break;
  786. }
  787. $res.="\n/A ".$o['info']['actionId']." 0 R";
  788. $res.="\n/Border [0 0 0]";
  789. $res.="\n/H /I";
  790. $res.="\n/Rect [ ";
  791. foreach($o['info']['rect'] as $v){
  792. $res.= sprintf("%.4f ",$v);
  793. }
  794. $res.="]";
  795. $res.="\n>>\nendobj";
  796. return $res;
  797. break;
  798. }
  799. }
  800. /**
  801. * a page object, it also creates a contents object to hold its contents
  802. */
  803. function o_page($id,$action,$options=''){
  804. if ($action!='new'){
  805. $o =& $this->objects[$id];
  806. }
  807. switch ($action){
  808. case 'new':
  809. $this->numPages++;
  810. $this->objects[$id]=array('t'=>'page','info'=>array('parent'=>$this->currentNode,'pageNum'=>$this->numPages));
  811. if (is_array($options)){
  812. // then this must be a page insertion, array shoudl contain 'rid','pos'=[before|after]
  813. $options['id']=$id;
  814. $this->o_pages($this->currentNode,'page',$options);
  815. } else {
  816. $this->o_pages($this->currentNode,'page',$id);
  817. }
  818. $this->currentPage=$id;
  819. //make a contents object to go with this page
  820. $this->numObj++;
  821. $this->o_contents($this->numObj,'new',$id);
  822. $this->currentContents=$this->numObj;
  823. $this->objects[$id]['info']['contents']=array();
  824. $this->objects[$id]['info']['contents'][]=$this->numObj;
  825. $match = ($this->numPages%2 ? 'odd' : 'even');
  826. foreach($this->addLooseObjects as $oId=>$target){
  827. if ($target=='all' || $match==$target){
  828. $this->objects[$id]['info']['contents'][]=$oId;
  829. }
  830. }
  831. break;
  832. case 'content':
  833. $o['info']['contents'][]=$options;
  834. break;
  835. case 'annot':
  836. // add an annotation to this page
  837. if (!isset($o['info']['annot'])){
  838. $o['info']['annot']=array();
  839. }
  840. // $options should contain the id of the annotation dictionary
  841. $o['info']['annot'][]=$options;
  842. break;
  843. case 'out':
  844. $res="\n".$id." 0 obj\n<< /Type /Page";
  845. $res.="\n/Parent ".$o['info']['parent']." 0 R";
  846. if (isset($o['info']['annot'])){
  847. $res.="\n/Annots [";
  848. foreach($o['info']['annot'] as $aId){
  849. $res.=" ".$aId." 0 R";
  850. }
  851. $res.=" ]";
  852. }
  853. $count = count($o['info']['contents']);
  854. if ($count==1){
  855. $res.="\n/Contents ".$o['info']['contents'][0]." 0 R";
  856. } else if ($count>1){
  857. $res.="\n/Contents [\n";
  858. foreach ($o['info']['contents'] as $cId){
  859. $res.=$cId." 0 R\n";
  860. }
  861. $res.="]";
  862. }
  863. $res.="\n>>\nendobj";
  864. return $res;
  865. break;
  866. }
  867. }
  868. /**
  869. * the contents objects hold all of the content which appears on pages
  870. */
  871. function o_contents($id,$action,$options=''){
  872. if ($action!='new'){
  873. $o =& $this->objects[$id];
  874. }
  875. switch ($action){
  876. case 'new':
  877. $this->objects[$id]=array('t'=>'contents','c'=>'','info'=>array());
  878. if (strlen($options) && intval($options)){
  879. // then this contents is the primary for a page
  880. $this->objects[$id]['onPage']=$options;
  881. } else if ($options=='raw'){
  882. // then this page contains some other type of system object
  883. $this->objects[$id]['raw']=1;
  884. }
  885. break;
  886. case 'add':
  887. // add more options to the decleration
  888. foreach ($options as $k=>$v){
  889. $o['info'][$k]=$v;
  890. }
  891. case 'out':
  892. $tmp=$o['c'];
  893. $res= "\n".$id." 0 obj\n";
  894. if (isset($this->objects[$id]['raw'])){
  895. $res.=$tmp;
  896. } else {
  897. $res.= "<<";
  898. if (function_exists('gzcompress') && $this->options['compression']){
  899. // then implement ZLIB based compression on this content stream
  900. $res.=" /Filter /FlateDecode";
  901. $tmp = gzcompress($tmp);
  902. }
  903. if ($this->encrypted){
  904. $this->encryptInit($id);
  905. $tmp = $this->ARC4($tmp);
  906. }
  907. foreach($o['info'] as $k=>$v){
  908. $res .= "\n/".$k.' '.$v;
  909. }
  910. $res.="\n/Length ".strlen($tmp)." >>\nstream\n".$tmp."\nendstream";
  911. }
  912. $res.="\nendobj\n";
  913. return $res;
  914. break;
  915. }
  916. }
  917. /**
  918. * an image object, will be an XObject in the document, includes description and data
  919. */
  920. function o_image($id,$action,$options=''){
  921. if ($action!='new'){
  922. $o =& $this->objects[$id];
  923. }
  924. switch($action){
  925. case 'new':
  926. // make the new object
  927. $this->objects[$id]=array('t'=>'image','data'=>$options['data'],'info'=>array());
  928. $this->objects[$id]['info']['Type']='/XObject';
  929. $this->objects[$id]['info']['Subtype']='/Image';
  930. $this->objects[$id]['info']['Width']=$options['iw'];
  931. $this->objects[$id]['info']['Height']=$options['ih'];
  932. if (!isset($options['type']) || $options['type']=='jpg'){
  933. if (!isset($options['channels'])){
  934. $options['channels']=3;
  935. }
  936. switch($options['channels']){
  937. case 1:
  938. $this->objects[$id]['info']['ColorSpace']='/DeviceGray';
  939. break;
  940. default:
  941. $this->objects[$id]['info']['ColorSpace']='/DeviceRGB';
  942. break;
  943. }
  944. $this->objects[$id]['info']['Filter']='/DCTDecode';
  945. $this->objects[$id]['info']['BitsPerComponent']=8;
  946. } else if ($options['type']=='png'){
  947. $this->objects[$id]['info']['Filter']='/FlateDecode';
  948. $this->objects[$id]['info']['DecodeParms']='<< /Predictor 15 /Colors '.$options['ncolor'].' /Columns '.$options['iw'].' /BitsPerComponent '.$options['bitsPerComponent'].'>>';
  949. if (strlen($options['pdata'])){
  950. $tmp = ' [ /Indexed /DeviceRGB '.(strlen($options['pdata'])/3-1).' ';
  951. $this->numObj++;
  952. $this->o_contents($this->numObj,'new');
  953. $this->objects[$this->numObj]['c']=$options['pdata'];
  954. $tmp.=$this->numObj.' 0 R';
  955. $tmp .=' ]';
  956. $this->objects[$id]['info']['ColorSpace'] = $tmp;
  957. if (isset($options['transparency'])){
  958. switch($options['transparency']['type']){
  959. case 'indexed':
  960. $tmp=' [ '.$options['transparency']['data'].' '.$options['transparency']['data'].'] ';
  961. $this->objects[$id]['info']['Mask'] = $tmp;
  962. break;
  963. }
  964. }
  965. } else {
  966. $this->objects[$id]['info']['ColorSpace']='/'.$options['color'];
  967. }
  968. $this->objects[$id]['info']['BitsPerComponent']=$options['bitsPerComponent'];
  969. }
  970. // assign it a place in the named resource dictionary as an external object, according to
  971. // the label passed in with it.
  972. $this->o_pages($this->currentNode,'xObject',array('label'=>$options['label'],'objNum'=>$id));
  973. // also make sure that we have the right procset object for it.
  974. $this->o_procset($this->procsetObjectId,'add','ImageC');
  975. break;
  976. case 'out':
  977. $tmp=$o['data'];
  978. $res= "\n".$id." 0 obj\n<<";
  979. foreach($o['info'] as $k=>$v){
  980. $res.="\n/".$k.' '.$v;
  981. }
  982. if ($this->encrypted){
  983. $this->encryptInit($id);
  984. $tmp = $this->ARC4($tmp);
  985. }
  986. $res.="\n/Length ".strlen($tmp)." >>\nstream\n".$tmp."\nendstream\nendobj\n";
  987. return $res;
  988. break;
  989. }
  990. }
  991. /**
  992. * encryption object.
  993. */
  994. function o_encryption($id,$action,$options=''){
  995. if ($action!='new'){
  996. $o =& $this->objects[$id];
  997. }
  998. switch($action){
  999. case 'new':
  1000. // make the new object
  1001. $this->objects[$id]=array('t'=>'encryption','info'=>$options);
  1002. $this->arc4_objnum=$id;
  1003. // figure out the additional paramaters required
  1004. $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);
  1005. $len = strlen($options['owner']);
  1006. if ($len>32){
  1007. $owner = substr($options['owner'],0,32);
  1008. } else if ($len<32){
  1009. $owner = $options['owner'].substr($pad,0,32-$len);
  1010. } else {
  1011. $owner = $options['owner'];
  1012. }
  1013. $len = strlen($options['user']);
  1014. if ($len>32){
  1015. $user = substr($options['user'],0,32);
  1016. } else if ($len<32){
  1017. $user = $options['user'].substr($pad,0,32-$len);
  1018. } else {
  1019. $user = $options['user'];
  1020. }
  1021. $tmp = $this->md5_16($owner);
  1022. $okey = substr($tmp,0,5);
  1023. $this->ARC4_init($okey);
  1024. $ovalue=$this->ARC4($user);
  1025. $this->objects[$id]['info']['O']=$ovalue;
  1026. // now make the u value, phew.
  1027. $tmp = $this->md5_16($user.$ovalue.chr($options['p']).chr(255).chr(255).chr(255).$this->fileIdentifier);
  1028. $ukey = substr($tmp,0,5);
  1029. $this->ARC4_init($ukey);
  1030. $this->encryptionKey = $ukey;
  1031. $this->encrypted=1;
  1032. $uvalue=$this->ARC4($pad);
  1033. $this->objects[$id]['info']['U']=$uvalue;
  1034. $this->encryptionKey=$ukey;
  1035. // initialize the arc4 array
  1036. break;
  1037. case 'out':
  1038. $res= "\n".$id." 0 obj\n<<";
  1039. $res.="\n/Filter /Standard";
  1040. $res.="\n/V 1";
  1041. $res.="\n/R 2";
  1042. $res.="\n/O (".$this->filterText($o['info']['O']).')';
  1043. $res.="\n/U (".$this->filterText($o['info']['U']).')';
  1044. // and the p-value needs to be converted to account for the twos-complement approach
  1045. $o['info']['p'] = (($o['info']['p']^255)+1)*-1;
  1046. $res.="\n/P ".($o['info']['p']);
  1047. $res.="\n>>\nendobj\n";
  1048. return $res;
  1049. break;
  1050. }
  1051. }
  1052. /**
  1053. * ARC4 functions
  1054. * A series of function to implement ARC4 encoding in PHP
  1055. */
  1056. /**
  1057. * calculate the 16 byte version of the 128 bit md5 digest of the string
  1058. */
  1059. function md5_16($string){
  1060. $tmp = md5($string);
  1061. $out='';
  1062. for ($i=0;$i<=30;$i=$i+2){
  1063. $out.=chr(hexdec(substr($tmp,$i,2)));
  1064. }
  1065. return $out;
  1066. }
  1067. /**
  1068. * initialize the encryption for processing a particular object
  1069. */
  1070. function encryptInit($id){
  1071. $tmp = $this->encryptionKey;
  1072. $hex = dechex($id);
  1073. if (strlen($hex)<6){
  1074. $hex = substr('000000',0,6-strlen($hex)).$hex;
  1075. }
  1076. $tmp.= chr(hexdec(substr($hex,4,2))).chr(hexdec(substr($hex,2,2))).chr(hexdec(substr($hex,0,2))).chr(0).chr(0);
  1077. $key = $this->md5_16($tmp);
  1078. $this->ARC4_init(substr($key,0,10));
  1079. }
  1080. /**
  1081. * initialize the ARC4 encryption
  1082. */
  1083. function ARC4_init($key=''){
  1084. $this->arc4 = '';
  1085. // setup the control array
  1086. if (strlen($key)==0){
  1087. return;
  1088. }
  1089. $k = '';
  1090. while(strlen($k)<256){
  1091. $k.=$key;
  1092. }
  1093. $k=substr($k,0,256);
  1094. for ($i=0;$i<256;$i++){
  1095. $this->arc4 .= chr($i);
  1096. }
  1097. $j=0;
  1098. for ($i=0;$i<256;$i++){
  1099. $t = $this->arc4[$i];
  1100. $j = ($j + ord($t) + ord($k[$i]))%256;
  1101. $this->arc4[$i]=$this->arc4[$j];
  1102. $this->arc4[$j]=$t;
  1103. }
  1104. }
  1105. /**
  1106. * ARC4 encrypt a text string
  1107. */
  1108. function ARC4($text){
  1109. $len=strlen($text);
  1110. $a=0;
  1111. $b=0;
  1112. $c = $this->arc4;
  1113. $out='';
  1114. for ($i=0;$i<$len;$i++){
  1115. $a = ($a+1)%256;
  1116. $t= $c[$a];
  1117. $b = ($b+ord($t))%256;
  1118. $c[$a]=$c[$b];
  1119. $c[$b]=$t;
  1120. $k = ord($c[(ord($c[$a])+ord($c[$b]))%256]);
  1121. $out.=chr(ord($text[$i]) ^ $k);
  1122. }
  1123. return $out;
  1124. }
  1125. /**
  1126. * functions which can be called to adjust or add to the document
  1127. */
  1128. /**
  1129. * add a link in the document to an external URL
  1130. */
  1131. function addLink($url,$x0,$y0,$x1,$y1){
  1132. $this->numObj++;
  1133. $info = array('type'=>'link','url'=>$url,'rect'=>array($x0,$y0,$x1,$y1));
  1134. $this->o_annotation($this->numObj,'new',$info);
  1135. }
  1136. /**
  1137. * add a link in the document to an internal destination (ie. within the document)
  1138. */
  1139. function addInternalLink($label,$x0,$y0,$x1,$y1){
  1140. $this->numObj++;
  1141. $info = array('type'=>'ilink','label'=>$label,'rect'=>array($x0,$y0,$x1,$y1));
  1142. $this->o_annotation($this->numObj,'new',$info);
  1143. }
  1144. /**
  1145. * set the encryption of the document
  1146. * can be used to turn it on and/or set the passwords which it will have.
  1147. * also the functions that the user will have are set here, such as print, modify, add
  1148. */
  1149. function setEncryption($userPass='',$ownerPass='',$pc=array()){
  1150. $p=bindec(11000000);
  1151. $options = array(
  1152. 'print'=>4
  1153. ,'modify'=>8
  1154. ,'copy'=>16
  1155. ,'add'=>32
  1156. );
  1157. foreach($pc as $k=>$v){
  1158. if ($v && isset($options[$k])){
  1159. $p+=$options[$k];
  1160. } else if (isset($options[$v])){
  1161. $p+=$options[$v];
  1162. }
  1163. }
  1164. // implement encryption on the document
  1165. if ($this->arc4_objnum == 0){
  1166. // then the block does not exist already, add it.
  1167. $this->numObj++;
  1168. if (strlen($ownerPass)==0){
  1169. $ownerPass=$userPass;
  1170. }
  1171. $this->o_encryption($this->numObj,'new',array('user'=>$userPass,'owner'=>$ownerPass,'p'=>$p));
  1172. }
  1173. }
  1174. /**
  1175. * should be used for internal checks, not implemented as yet
  1176. */
  1177. function checkAllHere(){
  1178. }
  1179. /**
  1180. * return the pdf stream as a string returned from the function
  1181. */
  1182. function output($debug=0){
  1183. if ($debug){
  1184. // turn compression off
  1185. $this->options['compression']=0;
  1186. }
  1187. if ($this->arc4_objnum){
  1188. $this->ARC4_init($this->encryptionKey);
  1189. }
  1190. $this->checkAllHere();
  1191. $xref=array();
  1192. $content="%PDF-1.3\n%��\n";
  1193. // $content="%PDF-1.3\n";
  1194. $pos=strlen($content);
  1195. foreach($this->objects as $k=>$v){
  1196. $tmp='o_'.$v['t'];
  1197. $cont=$this->$tmp($k,'out');
  1198. $content.=$cont;
  1199. $xref[]=$pos;
  1200. $pos+=strlen($cont);
  1201. }
  1202. $content.="\nxref\n0 ".(count($xref)+1)."\n0000000000 65535 f \n";
  1203. foreach($xref as $p){
  1204. $content.=substr('0000000000',0,10-strlen($p)).$p." 00000 n \n";
  1205. }
  1206. $content.="\ntrailer\n << /Size ".(count($xref)+1)."\n /Root 1 0 R\n /Info ".$this->infoObject." 0 R\n";
  1207. // if encryption has been applied to this document then add the marker for this dictionary
  1208. if ($this->arc4_objnum > 0){
  1209. $content .= "/Encrypt ".$this->arc4_objnum." 0 R\n";
  1210. }
  1211. if (strlen($this->fileIdentifier)){
  1212. $content .= "/ID[<".$this->fileIdentifier."><".$this->fileIdentifier.">]\n";
  1213. }
  1214. $content .= " >>\nstartxref\n".$pos."\n%%EOF\n";
  1215. return $content;
  1216. }
  1217. /**
  1218. * intialize a new document
  1219. * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum
  1220. * this function is called automatically by the constructor function
  1221. *
  1222. * @access private
  1223. */
  1224. function newDocument($pageSize=array(0,0,612,792)){
  1225. $this->numObj=0;
  1226. $this->objects = array();
  1227. $this->numObj++;
  1228. $this->o_catalog($this->numObj,'new');
  1229. $this->numObj++;
  1230. $this->o_outlines($this->numObj,'new');
  1231. $this->numObj++;
  1232. $this->o_pages($this->numObj,'new');
  1233. $this->o_pages($this->numObj,'mediaBox',$pageSize);
  1234. $this->currentNode = 3;
  1235. $this->numObj++;
  1236. $this->o_procset($this->numObj,'new');
  1237. $this->numObj++;
  1238. $this->o_info($this->numObj,'new');
  1239. $this->numObj++;
  1240. $this->o_page($this->numObj,'new');
  1241. // need to store the first page id as there is no way to get it to the user during
  1242. // startup
  1243. $this->firstPageId = $this->currentContents;
  1244. }
  1245. /**
  1246. * open the font file and return a php structure containing it.
  1247. * first check if this one has been done before and saved in a form more suited to php
  1248. * note that if a php serialized version does not exist it will try and make one, but will
  1249. * require write access to the directory to do it... it is MUCH faster to have these serialized
  1250. * files.
  1251. *
  1252. * @access private
  1253. */
  1254. function openFont($font){
  1255. // assume that $font contains both the path and perhaps the extension to the file, split them
  1256. $pos=strrpos($font,'/');
  1257. if ($pos===false){
  1258. $dir = './';
  1259. $name = $font;
  1260. } else {
  1261. $dir=substr($font,0,$pos+1);
  1262. $name=substr($font,$pos+1);
  1263. }
  1264. if (substr($name,-4)=='.afm'){
  1265. $name=substr($name,0,strlen($name)-4);
  1266. }
  1267. $this->addMessage('openFont: '.$font.' - '.$name);
  1268. if (file_exists($dir.'php_'.$name.'.afm')){
  1269. $this->addMessage('openFont: php file exists '.$dir.'php_'.$name.'.afm');
  1270. $tmp = file($dir.'php_'.$name.'.afm');
  1271. $this->fonts[$font]=unserialize($tmp[0]);
  1272. if (!isset($this->fonts[$font]['_version_']) || $this->fonts[$font]['_version_']<1){
  1273. // if the font file is old, then clear it out and prepare for re-creation
  1274. $this->addMessage('openFont: clear out, make way for new version.');
  1275. unset($this->fonts[$font]);
  1276. }
  1277. }
  1278. if (!isset($this->fonts[$font]) && file_exists($dir.$name.'.afm')){
  1279. // then rebuild the php_<font>.afm file from the <font>.afm file
  1280. $this->addMessage('openFont: build php file from '.$dir.$name.'.afm');
  1281. $data = array();
  1282. $file = file($dir.$name.'.afm');
  1283. foreach ($file as $rowA){
  1284. $row=trim($rowA);
  1285. $pos=strpos($row,' ');
  1286. if ($pos){
  1287. // then there must be some keyword
  1288. $key = substr($row,0,$pos);
  1289. switch ($key){
  1290. case 'FontName':
  1291. case 'FullName':
  1292. case 'FamilyName':
  1293. case 'Weight':
  1294. case 'ItalicAngle':
  1295. case 'IsFixedPitch':
  1296. case 'CharacterSet':
  1297. case 'UnderlinePosition':
  1298. case 'UnderlineThickness':
  1299. case 'Version':
  1300. case 'EncodingScheme':
  1301. case 'CapHeight':
  1302. case 'XHeight':
  1303. case 'Ascender':
  1304. case 'Descender':
  1305. case 'StdHW':
  1306. case 'StdVW':
  1307. case 'StartCharMetrics':
  1308. $data[$key]=trim(substr($row,$pos));
  1309. break;
  1310. case 'FontBBox':
  1311. $data[$key]=explode(' ',trim(substr($row,$pos)));
  1312. break;
  1313. case 'C':
  1314. //C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
  1315. $bits=explode(';',trim($row));
  1316. $dtmp=array();
  1317. foreach($bits as $bit){
  1318. $bits2 = explode(' ',trim($bit));
  1319. if (strlen($bits2[0])){
  1320. if (count($bits2)>2){
  1321. $dtmp[$bits2[0]]=array();
  1322. for ($i=1;$i<count($bits2);$i++){
  1323. $dtmp[$bits2[0]][]=$bits2[$i];
  1324. }
  1325. } else if (count($bits2)==2){
  1326. $dtmp[$bits2[0]]=$bits2[1];
  1327. }
  1328. }
  1329. }
  1330. if ($dtmp['C']>=0){
  1331. $data['C'][$dtmp['C']]=$dtmp;
  1332. $data['C'][$dtmp['N']]=$dtmp;
  1333. } else {
  1334. $data['C'][$dtmp['N']]=$dtmp;
  1335. }
  1336. break;
  1337. case 'KPX':
  1338. //KPX Adieresis yacute -40
  1339. $bits=explode(' ',trim($row));
  1340. $data['KPX'][$bits[1]][$bits[2]]=$bits[3];
  1341. break;
  1342. }
  1343. }
  1344. }
  1345. $data['_version_']=1;
  1346. $this->fonts[$font]=$data;
  1347. touch($dir.'php_'.$name.'.afm'); // php bug
  1348. $fp = fopen($dir.'php_'.$name.'.afm','w');
  1349. fwrite($fp,serialize($data));
  1350. fclose($fp);
  1351. } else if (!isset($this->fonts[$font])){
  1352. $this->addMessage('openFont: no font file found');
  1353. // echo 'Font not Found '.$font;
  1354. }
  1355. }
  1356. /**
  1357. * if the font is not loaded then load it and make the required object
  1358. * else just make it the current font
  1359. * the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding'
  1360. * note that encoding='none' will need to be used for symbolic fonts
  1361. * and 'differences' => an array of mappings between numbers 0->255 and character names.
  1362. *
  1363. */
  1364. function selectFont($fontName,$encoding='',$set=1){
  1365. if (!isset($this->fonts[$fontName])){
  1366. // load the file
  1367. $this->openFont($fontName);
  1368. if (isset($this->fonts[$fontName])){
  1369. $this->numObj++;
  1370. $this->numFonts++;
  1371. $pos=strrpos($fontName,'/');
  1372. // $dir=substr($fontName,0,$pos+1);
  1373. $name=substr($fontName,$pos+1);
  1374. if (substr($name,-4)=='.afm'){
  1375. $name=substr($name,0,strlen($name)-4);
  1376. }
  1377. $options=array('name'=>$name);
  1378. if (is_array($encoding)){
  1379. // then encoding and differences might be set
  1380. if (isset($encoding['encoding'])){
  1381. $options['encoding']=$encoding['encoding'];
  1382. }
  1383. if (isset($encoding['differences'])){
  1384. $options['differences']=$encoding['differences'];
  1385. }
  1386. } else if (strlen($encoding)){
  1387. // then perhaps only the encoding has been set
  1388. $options['encoding']=$encoding;
  1389. }
  1390. $fontObj = $this->numObj;
  1391. $this->o_font($this->numObj,'new',$options);
  1392. $this->fonts[$fontName]['fontNum']=$this->numFonts;
  1393. // if this is a '.afm' font, and there is a '.pfa' file to go with it ( as there
  1394. // should be for all non-basic fonts), then load it into an object and put the
  1395. // references into the font object
  1396. $basefile = substr($fontName,0,strlen($fontName)-4);
  1397. if (file_exists($basefile.'.pfb')){
  1398. $fbtype = 'pfb';
  1399. } else if (file_exists($basefile.'.ttf')){
  1400. $fbtype = 'ttf';
  1401. } else {
  1402. $fbtype='';
  1403. }
  1404. $fbfile = $basefile.'.'.$fbtype;
  1405. // $pfbfile = substr($fontName,0,strlen($fontName)-4).'.pfb';
  1406. // $ttffile = substr($fontName,0,strlen($fontName)-4).'.ttf';
  1407. $this->addMessage('selectFont: checking for - '.$fbfile);
  1408. if (substr($fontName,-4)=='.afm' && strlen($fbtype) ){
  1409. $adobeFontName = $this->fonts[$fontName]['FontName'];
  1410. // $fontObj = $this->numObj;
  1411. $this->addMessage('selectFont: adding font file - '.$fbfile.' - '.$adobeFontName);
  1412. // find the array of fond widths, and put that into an object.
  1413. $firstChar = -1;
  1414. $lastChar = 0;
  1415. $widths = array();
  1416. foreach ($this->fonts[$fontName]['C'] as $num=>$d){
  1417. if (intval($num)>0 || $num=='0'){
  1418. if ($lastChar>0 && $num>$lastChar+1){
  1419. for($i=$lastChar+1;$i<$num;$i++){
  1420. $widths[] = 0;
  1421. }
  1422. }
  1423. $widths[] = $d['WX'];
  1424. if ($firstChar==-1){
  1425. $firstChar = $num;
  1426. }
  1427. $lastChar = $num;
  1428. }
  1429. }
  1430. // also need to adjust the widths for the differences array
  1431. if (isset($options['differences'])){
  1432. foreach($options['differences'] as $charNum=>$charName){
  1433. if ($charNum>$lastChar){
  1434. for($i=$lastChar+1;$i<=$charNum;$i++){
  1435. $widths[]=0;
  1436. }
  1437. $lastChar=$charNum;
  1438. }
  1439. if (isset($this->fonts[$fontName]['C'][$charName])){
  1440. $widths[$charNum-$firstChar]=$this->fonts[$fontName]['C'][$charName]['WX'];
  1441. }
  1442. }
  1443. }
  1444. $this->addMessage('selectFont: FirstChar='.$firstChar);
  1445. $this->addMessage('selectFont: LastChar='.$lastChar);
  1446. $this->numObj++;
  1447. $this->o_contents($this->numObj,'new','raw');
  1448. $this->objects[$this->numObj]['c'].='[';
  1449. foreach($widths as $width){
  1450. $this->objects[$this->numObj]['c'].=' '.$width;
  1451. }
  1452. $this->objects[$this->numObj]['c'].=' ]';
  1453. $widthid = $this->numObj;
  1454. // load the pfb file, and put that into an object too.
  1455. // note that pdf supports only binary format type 1 font files, though there is a
  1456. // simple utility to convert them from pfa to pfb.
  1457. $fp = fopen($fbfile,'rb');
  1458. $tmp = get_magic_quotes_runtime();
  1459. set_magic_quotes_runtime(0);
  1460. $data = fread($fp,filesize($fbfile));
  1461. set_magic_quotes_runtime($tmp);
  1462. fclose($fp);
  1463. // create the font descriptor
  1464. $this->numObj++;
  1465. $fontDescriptorId = $this->numObj;
  1466. $this->numObj++;
  1467. $pfbid = $this->numObj;
  1468. // determine flags (more than a little flakey, hopefully will not matter much)
  1469. $flags=0;
  1470. if ($this->fonts[$fontName]['ItalicAngle']!=0){ $flags+=pow(2,6); }
  1471. if ($this->fonts[$fontName]['IsFixedPitch']=='true'){ $flags+=1; }
  1472. $flags+=pow(2,5); // assume non-sybolic
  1473. $list = array('Ascent'=>'Ascender','CapHeight'=>'CapHeight','Descent'=>'Descender','FontBBox'=>'FontBBox','ItalicAngle'=>'ItalicAngle');
  1474. $fdopt = array(
  1475. 'Flags'=>$flags
  1476. ,'FontName'=>$adobeFontName
  1477. ,'StemV'=>100 // don't know what the value for this should be!
  1478. );
  1479. foreach($list as $k=>$v){
  1480. if (isset($this->fonts[$fontName][$v])){
  1481. $fdopt[$k]=$this->fonts[$fontName][$v];
  1482. }
  1483. }
  1484. if ($fbtype=='pfb'){
  1485. $fdopt['FontFile']=$pfbid;
  1486. } else if ($fbtype=='ttf'){
  1487. $fdopt['FontFile2']=$pfbid;
  1488. }
  1489. $this->o_fontDescriptor($fontDescriptorId,'new',$fdopt);
  1490. // embed the font program
  1491. $this->o_contents($this->numObj,'new');
  1492. $this->objects[$pfbid]['c'].=$data;
  1493. // determine the cruicial lengths within this file
  1494. if ($fbtype=='pfb'){
  1495. $l1 = strpos($data,'eexec')+6;
  1496. $l2 = strpos($data,'00000000')-$l1;
  1497. $l3 = strlen($data)-$l2-$l1;
  1498. $this->o_contents($this->numObj,'add',array('Length1'=>$l1,'Length2'=>$l2,'Length3'=>$l3));
  1499. } else if ($fbtype=='ttf'){
  1500. $l1 = strlen($data);
  1501. $this->o_contents($this->numObj,'add',array('Length1'=>$l1));
  1502. }
  1503. // tell the font object about all this new stuff
  1504. $tmp = array('BaseFont'=>$adobeFontName,'Widths'=>$widthid
  1505. ,'FirstChar'=>$firstChar,'LastChar'=>$lastChar
  1506. ,'FontDescriptor'=>$fontDescriptorId);
  1507. if ($fbtype=='ttf'){
  1508. $tmp['SubType']='TrueType';
  1509. }
  1510. $this->addMessage('adding extra info to font.('.$fontObj.')');
  1511. foreach($tmp as $fk=>$fv){
  1512. $this->addMessage($fk." : ".$fv);
  1513. }
  1514. $this->o_font($fontObj,'add',$tmp);
  1515. } else {
  1516. $this->addMessage('selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts');
  1517. }
  1518. // also set the differences here, note that this means that these will take effect only the
  1519. //first time that a font is selected, else they are ignored
  1520. if (isset($options['differences'])){
  1521. $this->fonts[$fontName]['differences']=$options['differences'];
  1522. }
  1523. }
  1524. }
  1525. if ($set && isset($this->fonts[$fontName])){
  1526. // so if for some reason the font was not set in the last one then it will not be selected
  1527. $this->currentBaseFont=$fontName;
  1528. // the next line means that if a new font is selected, then the current text state will be
  1529. // applied to it as well.
  1530. $this->setCurrentFont();
  1531. }
  1532. return $this->currentFontNum;
  1533. }
  1534. /**
  1535. * sets up the current font, based on the font families, and the current text state
  1536. * note that this system is quite flexible, a <b><i> font can be completely different to a
  1537. * <i><b> font, and even <b><b> will have to be defined within the family to have meaning
  1538. * This function is to be called whenever the currentTextState is changed, it will update
  1539. * the currentFont setting to whatever the appropriatte family one is.
  1540. * If the user calls selectFont themselves then that will reset the cur…

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