|
eZ Publish
[trunk]
|
00001 <?php 00002 /** 00003 * File containing the Cpdf class. 00004 * 00005 * @copyright Copyright (C) 1999-2012 eZ Systems AS. All rights reserved. 00006 * @license http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 00007 * @version //autogentag// 00008 * @package lib 00009 */ 00010 00011 /*! 00012 \class Cpdf class.pdf.php 00013 \ingroup eZPDF 00014 \brief Cpdf provides 00015 */ 00016 00017 class Cpdf 00018 { 00019 00020 /** 00021 * the current number of pdf objects in the document 00022 */ 00023 public $numObj = 0; 00024 00025 /** 00026 * this array contains all of the pdf objects, ready for final assembly 00027 */ 00028 public $objects = array(); 00029 00030 /** 00031 * the objectId (number within the objects array) of the document catalog 00032 */ 00033 public $catalogId; 00034 00035 /** 00036 * array carrying information about the fonts that the system currently knows about 00037 * used to ensure that a font is not loaded twice, among other things 00038 */ 00039 public $fonts = array(); 00040 00041 /** 00042 * a record of the current font 00043 */ 00044 public $currentFont = ''; 00045 00046 /** 00047 * the current base font 00048 */ 00049 public $currentBaseFont = ''; 00050 00051 /** 00052 * the number of the current font within the font array 00053 */ 00054 public $currentFontNum = 0; 00055 00056 /** 00057 * 00058 */ 00059 public $currentNode; 00060 00061 /** 00062 * object number of the current page 00063 */ 00064 public $currentPage; 00065 00066 /** 00067 * object number of the currently active contents block 00068 */ 00069 public $currentContents; 00070 00071 /** 00072 * number of fonts within the system 00073 */ 00074 public $numFonts = 0; 00075 00076 /** 00077 * current colour for fill operations, defaults to inactive value, all three components should be between 0 and 1 inclusive when active 00078 */ 00079 public $currentColour; 00080 00081 /** 00082 * current colour for stroke operations (lines etc.) 00083 */ 00084 public $currentStrokeColour; 00085 00086 /** 00087 * current style that lines are drawn in 00088 */ 00089 public $currentLineStyle = ''; 00090 00091 /** 00092 * an array which is used to save the state of the document, mainly the colours and styles 00093 * it is used to temporarily change to another state, the change back to what it was before 00094 */ 00095 public $stateStack = array(); 00096 00097 /** 00098 * number of elements within the state stack 00099 */ 00100 public $nStateStack = 0; 00101 00102 /** 00103 * number of page objects within the document 00104 */ 00105 public $numPages = 0; 00106 00107 /** 00108 * object Id storage stack 00109 */ 00110 public $stack = array(); 00111 00112 /** 00113 * number of elements within the object Id storage stack 00114 */ 00115 public $nStack = 0; 00116 00117 /** 00118 * an array which contains information about the objects which are not firmly attached to pages 00119 * these have been added with the addObject function 00120 */ 00121 public $looseObjects = array(); 00122 00123 /** 00124 * array contains infomation about how the loose objects are to be added to the document 00125 */ 00126 public $addLooseObjects = array(); 00127 00128 /** 00129 * the objectId of the information object for the document 00130 * this contains authorship, title etc. 00131 */ 00132 public $infoObject = 0; 00133 00134 /** 00135 * number of images being tracked within the document 00136 */ 00137 public $numImages = 0; 00138 00139 /** 00140 * an array containing options about the document 00141 * it defaults to turning on the compression of the objects 00142 */ 00143 public $options = array( 'compression' => 1 ); 00144 00145 /** 00146 * the objectId of the first page of the document 00147 */ 00148 public $firstPageId; 00149 00150 /** 00151 * used to track the last used value of the inter-word spacing, this is so that it is known 00152 * when the spacing is changed. 00153 */ 00154 public $wordSpaceAdjust = 0; 00155 00156 /** 00157 * the object Id of the procset object 00158 */ 00159 public $procsetObjectId; 00160 00161 /** 00162 * store the information about the relationship between font families 00163 * this used so that the code knows which font is the bold version of another font, etc. 00164 * the value of this array is initialised in the constuctor function. 00165 */ 00166 public $fontFamilies = array(); 00167 00168 /** 00169 * track if the current font is bolded or italicised 00170 */ 00171 public $currentTextState = ''; 00172 00173 /** 00174 * messages are stored here during processing, these can be selected afterwards to give some useful debug information 00175 */ 00176 public $messages = ''; 00177 00178 /** 00179 * the ancryption array for the document encryption is stored here 00180 */ 00181 public $arc4 = ''; 00182 00183 /** 00184 * the object Id of the encryption information 00185 */ 00186 public $arc4_objnum = 0; 00187 00188 /** 00189 * the file identifier, used to uniquely identify a pdf document 00190 */ 00191 public $fileIdentifier = ''; 00192 00193 /** 00194 * a flag to say if a document is to be encrypted or not 00195 */ 00196 public $encrypted = 0; 00197 00198 /** 00199 * the ancryption key for the encryption of all the document content (structure is not encrypted) 00200 */ 00201 public $encryptionKey = ''; 00202 00203 /** 00204 * array which forms a stack to keep track of nested callback functions 00205 */ 00206 public $callback = array(); 00207 00208 /** 00209 * the number of callback functions in the callback array 00210 */ 00211 public $nCallback = 0; 00212 00213 /** 00214 * store label->id pairs for named destinations, these will be used to replace internal links 00215 * done this way so that destinations can be defined after the location that links to them 00216 */ 00217 public $destinations = array(); 00218 00219 /** 00220 * store the stack for the transaction commands, each item in here is a record of the values of all the 00221 * variables within the class, so that the user can rollback at will (from each 'start' command) 00222 * note that this includes the objects array, so these can be large. 00223 */ 00224 public $checkpoint = ''; 00225 00226 /** 00227 * class constructor 00228 * this will start a new document 00229 * @var array array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero. 00230 */ 00231 function Cpdf ( $pageSize = array( 0, 0, 612, 792 ) ) 00232 { 00233 $this->currentColour = eZMath::rgbToCMYK( array( 'r' => -1, 00234 'g' => -1, 00235 'b' => -1 ) ); 00236 $this->currentStrokeColour = eZMath::rgbToCMYK( array( 'r' => -1, 00237 'g' => -1, 00238 'b' => -1 ) ); 00239 00240 $this->newDocument( $pageSize ); 00241 00242 // also initialize the font families that are known about already 00243 $this->setFontFamily( 'init' ); 00244 // $this->fileIdentifier = md5('xxxxxxxx'.time()); 00245 00246 } 00247 00248 /** 00249 * Document object methods (internal use only) 00250 * 00251 * There is about one object method for each type of object in the pdf document 00252 * Each function has the same call list ($id,$action,$options). 00253 * $id = the object ID of the object, or what it is to be if it is being created 00254 * $action = a string specifying the action to be performed, though ALL must support: 00255 * 'new' - create the object with the id $id 00256 * 'out' - produce the output for the pdf object 00257 * $options = optional, a string or array containing the various parameters for the object 00258 * 00259 * These, in conjunction with the output function are the ONLY way for output to be produced 00260 * within the pdf 'file'. 00261 */ 00262 00263 /** 00264 *destination object, used to specify the location for the user to jump to, presently on opening 00265 */ 00266 function o_destination( $id, $action, $options = '' ) 00267 { 00268 if ( $action != 'new' ) 00269 { 00270 $o =& $this->objects[$id]; 00271 } 00272 switch ( $action ) 00273 { 00274 case 'new': 00275 $this->objects[$id] = array( 't' => 'destination', 'info' => array() ); 00276 $tmp = ''; 00277 switch ( $options['type'] ) 00278 { 00279 case 'XYZ': 00280 case 'FitR': 00281 $tmp = ' ' . sprintf( '%.3F', $options['p3'] ) . $tmp; 00282 case 'FitV': 00283 case 'FitBH': 00284 case 'FitBV': 00285 $tmp = ' ' . sprintf( '%.3F', $options['p2'] ) . $tmp; 00286 case 'Fit': 00287 case 'FitH': 00288 $tmp = ' ' . sprintf( '%.3F', $options['p1'] ) . $tmp; 00289 case 'FitB': 00290 $tmp = $options['type'] . $tmp; 00291 $this->objects[$id]['info']['string'] = $tmp; 00292 $this->objects[$id]['info']['page'] = $options['page']; 00293 }break; 00294 00295 case 'out': 00296 $tmp = $o['info']; 00297 $res = "\n" . $id . " 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj\n"; 00298 return $res; 00299 break; 00300 } 00301 } 00302 00303 /** 00304 * set the viewer preferences 00305 */ 00306 function o_viewerPreferences( $id, $action, $options = '' ) 00307 { 00308 if ( $action != 'new' ) 00309 { 00310 $o =& $this->objects[$id]; 00311 } 00312 switch ( $action ) 00313 { 00314 case 'new': 00315 $this->objects[$id] = array( 't' => 'viewerPreferences', 'info' => array() ); 00316 break; 00317 case 'add': 00318 foreach ( $options as $k => $v ) 00319 { 00320 switch ( $k ) 00321 { 00322 case 'HideToolbar': 00323 case 'HideMenubar': 00324 case 'HideWindowUI': 00325 case 'FitWindow': 00326 case 'CenterWindow': 00327 case 'NonFullScreenPageMode': 00328 case 'Direction': 00329 $o['info'][$k] = $v; 00330 break; 00331 } 00332 }break; 00333 case 'out': 00334 $res = "\n" . $id . " 0 obj\n" . '<< '; 00335 foreach($o['info'] as $k=>$v) 00336 { 00337 $res .= "\n/" . $k . ' ' . $v; 00338 } 00339 $res .= "\n>>\n"; 00340 return $res; 00341 break; 00342 } 00343 } 00344 00345 /** 00346 * define the document catalog, the overall controller for the document 00347 */ 00348 function o_catalog( $id, $action, $options = '' ) 00349 { 00350 if ( $action != 'new' ) 00351 { 00352 $o =& $this->objects[$id]; 00353 } 00354 switch ( $action ) 00355 { 00356 case 'new': 00357 $this->objects[$id] = array( 't' => 'catalog', 'info' => array() ); 00358 $this->catalogId = $id; 00359 break; 00360 case 'outlines': 00361 case 'pages': 00362 case 'openHere': 00363 $o['info'][$action] = $options; 00364 break; 00365 case 'viewerPreferences': 00366 if ( !isset( $o['info']['viewerPreferences'] ) ) 00367 { 00368 $this->numObj++; 00369 $this->o_viewerPreferences( $this->numObj, 'new' ); 00370 $o['info']['viewerPreferences'] = $this->numObj; 00371 } 00372 $vp = $o['info']['viewerPreferences']; 00373 $this->o_viewerPreferences( $vp, 'add', $options ); 00374 break; 00375 case 'out': 00376 $res= "\n" . $id . " 0 obj\n" . '<< /Type /Catalog'; 00377 foreach ( $o['info'] as $k => $v ) 00378 { 00379 switch ( $k ) 00380 { 00381 case 'outlines': 00382 $res .= "\n" . '/Outlines ' . $v . ' 0 R'; 00383 break; 00384 case 'pages': 00385 $res .= "\n" . '/Pages ' . $v . ' 0 R'; 00386 break; 00387 case 'viewerPreferences': 00388 $res .= "\n" . '/ViewerPreferences ' . $o['info']['viewerPreferences'] . ' 0 R'; 00389 break; 00390 case 'openHere': 00391 $res .= "\n" . '/OpenAction ' . $o['info']['openHere'] . ' 0 R'; 00392 break; 00393 } 00394 } 00395 $res .= " >>\nendobj"; 00396 return $res; 00397 break; 00398 } 00399 } 00400 00401 /** 00402 * object which is a parent to the pages in the document 00403 */ 00404 function o_pages( $id, $action, $options = '' ) 00405 { 00406 if ($action!='new') 00407 { 00408 $o =& $this->objects[$id]; 00409 } 00410 switch ( $action ) 00411 { 00412 case 'new': 00413 $this->objects[$id] = array( 't' => 'pages', 'info' => array() ); 00414 $this->o_catalog( $this->catalogId, 'pages', $id ); 00415 break; 00416 case 'page': 00417 if ( !is_array( $options ) ) 00418 { 00419 // then it will just be the id of the new page 00420 $o['info']['pages'][] = $options; 00421 } 00422 else 00423 { 00424 // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative 00425 // and pos is either 'before' or 'after', saying where this page will fit. 00426 if ( isset( $options['id'] ) && isset( $options['rid'] ) && isset( $options['pos'] ) ) 00427 { 00428 $i = array_search( $options['rid'], $o['info']['pages'] ); 00429 if ( isset( $o['info']['pages'][$i] ) && $o['info']['pages'][$i] == $options['rid']) 00430 { 00431 // then there is a match 00432 // make a space 00433 switch ( $options['pos'] ) 00434 { 00435 case 'before': 00436 $k = $i; 00437 break; 00438 case 'after': 00439 $k = $i + 1; 00440 break; 00441 default: 00442 $k = -1; 00443 break; 00444 } 00445 if ( $k >= 0 ) 00446 { 00447 for ( $j = count( $o['info']['pages'] ) - 1; $j >= $k; $j-- ) 00448 { 00449 $o['info']['pages'][$j+1] = $o['info']['pages'][$j]; 00450 } 00451 $o['info']['pages'][$k] = $options['id']; 00452 } 00453 } 00454 } 00455 }break; 00456 00457 case 'procset': 00458 $o['info']['procset'] = $options; 00459 break; 00460 case 'mediaBox': 00461 $o['info']['mediaBox'] = $options; // which should be an array of 4 numbers 00462 break; 00463 case 'font': 00464 $o['info']['fonts'][] = array( 'objNum' => $options['objNum'], 'fontNum' => $options['fontNum'] ); 00465 break; 00466 case 'shading': 00467 $o['info']['shading'][] = array( 'objNum' => $options['objNum'], 'label' => $options['label'] ); 00468 break; 00469 case 'xObject': 00470 $o['info']['xObjects'][] = array( 'objNum' => $options['objNum'], 'label' => $options['label'] ); 00471 break; 00472 case 'out': 00473 if ( count( $o['info']['pages'] ) ) 00474 { 00475 $res = "\n" . $id . " 0 obj\n<< /Type /Pages\n/Kids ["; 00476 foreach ( $o['info']['pages'] as $k => $v ) 00477 { 00478 $res .= $v . " 0 R\n"; 00479 } 00480 $res .= "]\n/Count " . count( $this->objects[$id]['info']['pages'] ); 00481 if ( ( isset( $o['info']['fonts'] ) && count( $o['info']['fonts'] ) ) || isset( $o['info']['procset'] ) ) 00482 { 00483 $res .= "\n/Resources <<"; 00484 if ( isset( $o['info']['procset'] ) ) 00485 { 00486 $res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R"; 00487 } 00488 if ( isset( $o['info']['fonts'] ) && count( $o['info']['fonts'] ) ) 00489 { 00490 $res .= "\n/Font << "; 00491 foreach ( $o['info']['fonts'] as $finfo ) 00492 { 00493 $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R"; 00494 } 00495 $res .= " >>"; 00496 } 00497 00498 if ( isset( $o['info']['shading'] ) && count( $o['info']['shading'] ) ) 00499 { 00500 $res .= "\n/Shading <<"; 00501 foreach ( $o['info']['shading'] as $finfo ) 00502 { 00503 $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R"; 00504 } 00505 $res .= " >>"; 00506 } 00507 00508 if ( isset( $o['info']['xObjects'] ) && count( $o['info']['xObjects'] ) ) 00509 { 00510 $res .= "\n/XObject << "; 00511 foreach ( $o['info']['xObjects'] as $finfo ) 00512 { 00513 $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R"; 00514 } 00515 $res .= " >>"; 00516 } 00517 $res .= "\n>>"; 00518 if ( isset( $o['info']['mediaBox'] ) ) 00519 { 00520 $tmp = $o['info']['mediaBox']; 00521 $res .= "\n/MediaBox [" . sprintf( '%.3F', $tmp[0] ) . ' ' . sprintf( '%.3F', $tmp[1] ) . ' ' . sprintf( '%.3F', $tmp[2] ) . ' ' . sprintf( '%.3F', $tmp[3] ) . ']'; 00522 } 00523 } 00524 $res.="\n >>\nendobj"; 00525 } 00526 else 00527 { 00528 $res = "\n" . $id . " 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj"; 00529 } 00530 return $res; 00531 break; 00532 } 00533 } 00534 00535 /** 00536 * define the outlines in the doc, empty for now 00537 */ 00538 function o_outlines( $id, $action, $options = '' ) 00539 { 00540 if ( $action != 'new' ) 00541 { 00542 $o =& $this->objects[$id]; 00543 } 00544 switch ( $action ) 00545 { 00546 case 'new': 00547 $this->objects[$id] = array( 't' => 'outlines', 'info' => array( 'outlines' => array() ) ); 00548 $this->o_catalog( $this->catalogId, 'outlines', $id ); 00549 break; 00550 case 'outline': 00551 $o['info']['outlines'][] = $options; 00552 break; 00553 case 'out': 00554 if ( count( $o['info']['outlines'] ) ) 00555 { 00556 $res = "\n" . $id . " 0 obj\n<< /Type /Outlines /Kids ["; 00557 foreach ( $o['info']['outlines'] as $k => $v ) 00558 { 00559 $res .= $v . " 0 R "; 00560 } 00561 $res .= "] /Count " . count( $o['info']['outlines'] ) . " >>\nendobj"; 00562 } 00563 else 00564 { 00565 $res = "\n" . $id . " 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj"; 00566 } 00567 return $res; 00568 break; 00569 } 00570 } 00571 00572 /** 00573 Add object to hold Function properties 00574 Only partial support for type 0, 3 and 4 00575 00576 \param object id. 00577 \param action 00578 \param options 00579 */ 00580 function o_function( $id, $action, $options = '' ) 00581 { 00582 if ( $action != 'new' ) 00583 { 00584 $o =& $this->objects[$id]; 00585 } 00586 00587 switch ( $action ) 00588 { 00589 case 'new': 00590 { 00591 $this->objects[$id] = array ( 't' => 'function', 00592 'info' => array() ); 00593 00594 if ( isset( $options['type'] ) ) // Set function type 00595 { 00596 $this->objects[$id]['info']['type'] = $options['type']; 00597 } 00598 else 00599 { 00600 $this->objects[$id]['info']['type'] = '2'; 00601 } 00602 00603 if ( isset( $options['Domain'] ) ) // Set function Domain 00604 { 00605 $this->objects[$id]['info']['Domain'] = $options['Domain']; 00606 } 00607 else 00608 { 00609 $this->objects[$id]['info']['Domain'] = '[0.0 1.0]'; 00610 } 00611 00612 if ( $this->objects[$id]['info']['type'] == '2' ) // set start and en values 00613 { 00614 if ( isset( $options['C0'] ) ) // Set values0 00615 { 00616 $this->objects[$id]['info']['C0'] = $options['C0']; 00617 } 00618 00619 if ( isset( $options['C1'] ) ) // Set values1 00620 { 00621 $this->objects[$id]['info']['C1'] = $options['C1']; 00622 } 00623 00624 if ( isset( $options['N'] ) ) // set exponent 00625 { 00626 $this->objects[$id]['info']['N'] = $options['N']; 00627 } 00628 else 00629 { 00630 $this->objects[$id]['info']['N'] = '1.0'; 00631 } 00632 } 00633 00634 } break; 00635 00636 case 'out': 00637 { 00638 $res = "\n" . $id . " 0 obj\n<< "; 00639 $res.= "/FunctionType " . $o['info']['type'] . "\n"; 00640 $res.= "/Domain " . $o['info']['Domain'] . "\n"; 00641 if ( isset ( $o['info']['C0'] ) ) 00642 $res.= "/C0 " . $o['info']['C0'] . "\n"; 00643 if ( isset ( $o['info']['C1'] ) ) 00644 $res.= "/C1 " . $o['info']['C1'] . "\n"; 00645 $res.= "/N " . $o['info']['N'] . "\n"; 00646 $res.= ">>\nendobject"; 00647 return $res; 00648 } break; 00649 } 00650 } 00651 00652 /** 00653 Add object to hold pattern properties 00654 00655 \param object id. 00656 \param action 00657 \param options 00658 00659 \return action 'new' - dictionary name 00660 'out' - pdf output 00661 */ 00662 function o_pattern( $id, $action, $options = '' ) 00663 { 00664 if ( $action != 'new' ) 00665 { 00666 $o =& $this->objects[$id]; 00667 } 00668 00669 switch ( $action ) 00670 { 00671 case 'new': 00672 { 00673 $this->objects[$id] = array( 't' => 'shading', 00674 'info' => array() ); 00675 00676 if ( isset( $options['type'] ) ) //Set pattern type 00677 { 00678 $this->objects[$id]['info']['PatternType'] = $options['type']; 00679 } 00680 else 00681 { 00682 $this->objects[$id]['info']['PatternType'] = '2'; 00683 } 00684 00685 if ( isset( $options['matrix'] ) ) 00686 { 00687 $this->objects[$id]['info']['Matrix'] = '[ '; 00688 foreach ( $options['matrix'] as $coord ) 00689 { 00690 $this->objects[$id]['info']['Matrix'] .= sprintf( ' %.3F ', $coord ); 00691 } 00692 $this->objects[$id]['info']['Matrix'] .= ' ]'; 00693 } 00694 00695 if ( $this->objects[$id]['info']['PatternType'] == 1 ) // Pattern type 1, tiling pattern 00696 { 00697 $this->objects[$id]['info']['PaintType'] = $options['paintType']; 00698 $this->objects[$id]['info']['TilingType'] = $options['tilingType']; 00699 00700 $this->objects[$id]['info']['BBox'] = '[ '; 00701 foreach ( $options['bbox'] as $coord ) 00702 { 00703 $this->objects[$id]['info']['BBox'] .= sprintf( ' %.3F ', $coord ); 00704 } 00705 $this->objects[$id]['info']['BBox'] .= ']'; 00706 00707 $this->objects[$id]['info']['XStep'] = $options['xstep']; 00708 $this->objects[$id]['info']['YStep'] = $options['ystep']; 00709 } 00710 else // Non tiling, smooth pattern. 00711 { 00712 if ( isset( $options['shading'] ) ) 00713 { 00714 $this->objects[$id]['info']['Shading'] = $options['shading'] . ' R'; 00715 } 00716 } 00717 } break; 00718 00719 case 'out': 00720 { 00721 $res = "\n" . $id . " 0 obj\n<< "; 00722 foreach ( $this->objects[$id]['info'] as $key => $info ) 00723 { 00724 $res .= '/' . $key . ' ' . $info[$key] . "\n"; 00725 } 00726 $res .= ">>\nendobject"; 00727 return $res; 00728 } break; 00729 } 00730 } //TODO: Add pattern too 00731 00732 00733 /** 00734 Add object to hold shading properties 00735 00736 \param object id. 00737 \param action 00738 \param options 00739 00740 \return action 'new' - dictionary name 00741 'out' - pdf output 00742 */ 00743 function o_shading( $id, $action, $options = '' ) 00744 { 00745 if ( $action != 'new' ) 00746 { 00747 $o =& $this->objects[$id]; 00748 } 00749 00750 switch ( $action ) 00751 { 00752 case 'new': 00753 { 00754 $this->objects[$id] = array( 't' => 'shading', 00755 'info' => array( 'type' => '2', 00756 'ColorSpace' => '/DeviceCMYK' ) ); 00757 00758 if ( !isset( $options['orientation'] ) ) 00759 { 00760 $options['orientation'] = 'vertical'; 00761 } 00762 00763 if ( $options['orientation'] == 'horizontal' ) 00764 { 00765 $angle = 0; 00766 } 00767 else if ( $options['orientation'] == 'vertical' ) 00768 { 00769 $angle = deg2rad( 90 ); 00770 } 00771 else // orientation is an angle 00772 { 00773 $angle = $options['orientation']; 00774 } 00775 00776 $this->objects[$id]['info']['Coords'] = '[ '. // compute angle of gradient chading 00777 sprintf( '%.3F', $options['size']['x1'] ) . ' ' . 00778 sprintf( '%.3F', $options['size']['y1'] + $options['size']['height'] ) . ' ' . 00779 sprintf( '%.3F', $options['size']['x1'] + $options['size']['width'] * cos( $angle ) ) . ' ' . 00780 sprintf( '%.3F', $options['size']['y1'] + $options['size']['height'] * ( 1 - sin( $angle ) ) ) . ']'; 00781 00782 //$this->objects[$id]['info']['Coords'] = '[0.0 0.0 50.0 50.0]'; 00783 00784 if ( is_array( $options['color0'] ) ) //Set chading colors 00785 { 00786 if ( count( $options['color0'] ) == 3 ) 00787 { 00788 $options['color0'] = eZMath::rgbToCMYK( $options['color0'] ); 00789 } 00790 $color0 = array ( 'c' => sprintf( '%.3F', $options['color0']['c'] ), 00791 'm' => sprintf( '%.3F', $options['color0']['m'] ), 00792 'y' => sprintf( '%.3F', $options['color0']['y'] ), 00793 'k' => sprintf( '%.3F', $options['color0']['k'] ) ); 00794 } 00795 else 00796 { 00797 $color0 = eZMath::rgbToCMYK2( 0, 0, 0 ); 00798 } 00799 if ( is_array( $options['color1'] ) ) 00800 { 00801 if ( count( $options['color1'] ) == 3 ) 00802 { 00803 $options['color1'] = eZMath::rgbToCMYK( $options['color1'] ); 00804 } 00805 $color1 = array ( 'c' => sprintf( '%.3F', $options['color1']['c'] ), 00806 'm' => sprintf( '%.3F', $options['color1']['m'] ), 00807 'y' => sprintf( '%.3F', $options['color1']['y'] ), 00808 'k' => sprintf( '%.3F', $options['color1']['k'] ) ); 00809 } 00810 else 00811 { 00812 $color1 = eZMath::rgbToCMYK2( 1, 1, 1 ); 00813 } 00814 00815 $this->numObj++; 00816 $this->objects[$id]['info']['Function'] = $this->numObj; 00817 $this->o_function( $this->numObj, 'new', array ( 'type' => '2', 00818 'Domain' => '[0.0 1.0]', 00819 'C0' => '['.$color0['c'].' '.$color0['m'].' '.$color0['y'].' '.$color0['k'].']', 00820 'C1' => '['.$color1['c'].' '.$color1['m'].' '.$color1['y'].' '.$color1['k'].']', 00821 'N' => '1.0' ) ); 00822 $this->objects[$id]['info']['Extend'] = '[true true]'; 00823 // also tell the pages node about the new shading 00824 $this->o_pages( $this->currentNode, 'shading', array( 'label' => 'Sh' . $id, 00825 'objNum'=> $id ) ); 00826 return 'Sh' . $id; 00827 } break; 00828 00829 case 'out': 00830 { 00831 $res = "\n" . $id . " 0 obj\n<< "; 00832 $res.= "/ShadingType " . $o['info']['type'] . "\n"; 00833 $res.= "/ColorSpace " . $o['info']['ColorSpace'] . "\n"; 00834 $res.= "/Coords " . $o['info']['Coords'] . "\n"; 00835 $res.= "/Function " . $o['info']['Function'] . " 0 R\n"; 00836 $res.= "/Extend " . $o['info']['Extend'] . "\n"; 00837 $res.= ">>\nendobject"; 00838 return $res; 00839 } break; 00840 } 00841 } 00842 00843 /** 00844 * an object to hold the font description 00845 */ 00846 function o_font( $id, $action, $options = '' ) 00847 { 00848 if ( $action != 'new' ) 00849 { 00850 $o =& $this->objects[$id]; 00851 } 00852 switch ( $action ) 00853 { 00854 case 'new': 00855 $this->objects[$id] = array( 't' => 'font', 00856 'info' => array( 'name' => $options['name'], 00857 'SubType' => 'Type1' ) ); 00858 $fontNum = $this->numFonts; 00859 $this->objects[$id]['info']['fontNum'] = $fontNum; 00860 // deal with the encoding and the differences 00861 if ( isset( $options['differences'] ) ) 00862 { 00863 // then we'll need an encoding dictionary 00864 $this->numObj++; 00865 $this->o_fontEncoding( $this->numObj, 'new', $options ); 00866 $this->objects[$id]['info']['encodingDictionary'] = $this->numObj; 00867 } 00868 else if ( isset( $options['encoding'] ) ) 00869 { 00870 // we can specify encoding here 00871 switch ( $options['encoding'] ) 00872 { 00873 case 'WinAnsiEncoding': 00874 case 'MacRomanEncoding': 00875 case 'MacExpertEncoding': 00876 $this->objects[$id]['info']['encoding'] = $options['encoding']; 00877 break; 00878 case 'none': 00879 break; 00880 default: 00881 $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding'; 00882 break; 00883 } 00884 } 00885 else 00886 { 00887 $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding'; 00888 } 00889 // also tell the pages node about the new font 00890 $this->o_pages( $this->currentNode, 'font', array( 'fontNum' => $fontNum, 'objNum' => $id ) ); 00891 break; 00892 00893 case 'add': 00894 foreach ( $options as $k => $v ) 00895 { 00896 switch ( $k ) 00897 { 00898 case 'BaseFont': 00899 $o['info']['name'] = $v; 00900 break; 00901 case 'FirstChar': 00902 case 'LastChar': 00903 case 'Widths': 00904 case 'FontDescriptor': 00905 case 'SubType': 00906 $this->addMessage( 'o_font ' . $k . " : " . $v ); 00907 $o['info'][$k] = $v; 00908 break; 00909 } 00910 } 00911 break; 00912 00913 case 'out': 00914 $res = "\n" . $id . " 0 obj\n<< /Type /Font\n/Subtype /" . $o['info']['SubType'] . "\n"; 00915 $res.= "/Name /F" . $o['info']['fontNum'] . "\n"; 00916 $res.= "/BaseFont /" . $o['info']['name'] . "\n"; 00917 if ( isset( $o['info']['encodingDictionary'] ) ) 00918 { 00919 // then place a reference to the dictionary 00920 $res.= "/Encoding " . $o['info']['encodingDictionary'] . " 0 R\n"; 00921 } 00922 else if ( isset( $o['info']['encoding'] ) ) 00923 { 00924 // use the specified encoding 00925 $res.= "/Encoding /" . $o['info']['encoding'] . "\n"; 00926 } 00927 if ( isset($o['info']['FirstChar'] ) ) 00928 { 00929 $res.= "/FirstChar " . $o['info']['FirstChar'] . "\n"; 00930 } 00931 if ( isset( $o['info']['LastChar'] ) ) 00932 { 00933 $res.= "/LastChar " . $o['info']['LastChar'] . "\n"; 00934 } 00935 if ( isset( $o['info']['Widths'] ) ) 00936 { 00937 $res.= "/Widths " . $o['info']['Widths'] . " 0 R\n"; 00938 } 00939 if ( isset( $o['info']['FontDescriptor'] ) ) 00940 { 00941 $res.= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n"; 00942 } 00943 $res.= ">>\nendobj"; 00944 return $res; 00945 break; 00946 } 00947 } 00948 00949 /** 00950 * a font descriptor, needed for including additional fonts 00951 */ 00952 function o_fontDescriptor( $id, $action, $options = '') 00953 { 00954 if ( $action != 'new' ) 00955 { 00956 $o =& $this->objects[$id]; 00957 } 00958 switch ( $action ) 00959 { 00960 case 'new': 00961 $this->objects[$id] = array( 't' => 'fontDescriptor', 'info' => $options ); 00962 break; 00963 case 'out': 00964 $res = "\n" . $id . " 0 obj\n<< /Type /FontDescriptor\n"; 00965 foreach ( $o['info'] as $label => $value ) 00966 { 00967 switch ( $label ) 00968 { 00969 case 'Ascent': 00970 case 'CapHeight': 00971 case 'Descent': 00972 case 'Flags': 00973 case 'ItalicAngle': 00974 case 'StemV': 00975 case 'AvgWidth': 00976 case 'Leading': 00977 case 'MaxWidth': 00978 case 'MissingWidth': 00979 case 'StemH': 00980 case 'XHeight': 00981 case 'CharSet': 00982 if ( strlen( $value ) ) 00983 { 00984 $res.= '/'.$label.' '.$value."\n"; 00985 } 00986 break; 00987 case 'FontFile': 00988 case 'FontFile2': 00989 case 'FontFile3': 00990 $res.= '/'.$label.' '.$value." 0 R\n"; 00991 break; 00992 case 'FontBBox': 00993 $res.= '/'.$label.' ['.$value[0].' '.$value[1].' '.$value[2].' '.$value[3]."]\n"; 00994 break; 00995 case 'FontName': 00996 $res.= '/'.$label.' /'.$value."\n"; 00997 break; 00998 } 00999 } 01000 $res .= ">>\nendobj"; 01001 return $res; 01002 break; 01003 } 01004 } 01005 01006 /** 01007 * the font encoding 01008 */ 01009 function o_fontEncoding( $id, $action, $options = '' ) 01010 { 01011 if ( $action != 'new' ) 01012 { 01013 $o =& $this->objects[$id]; 01014 } 01015 switch ( $action ) 01016 { 01017 case 'new': 01018 // the options array should contain 'differences' and maybe 'encoding' 01019 $this->objects[$id] = array( 't' => 'fontEncoding', 01020 'info' => $options ); 01021 break; 01022 case 'out': 01023 $res = "\n" . $id . " 0 obj\n<< /Type /Encoding\n"; 01024 if ( !isset( $o['info']['encoding'] ) ) 01025 { 01026 $o['info']['encoding'] = 'WinAnsiEncoding'; 01027 } 01028 if ( $o['info']['encoding'] != 'none' ) 01029 { 01030 $res.= "/BaseEncoding /" . $o['info']['encoding'] . "\n"; 01031 } 01032 $res.= "/Differences \n["; 01033 $onum = -100; 01034 foreach ( $o['info']['differences'] as $num => $label ) 01035 { 01036 if ($num!=$onum+1) 01037 { 01038 // we cannot make use of consecutive numbering 01039 $res.= "\n".$num." /".$label; 01040 } 01041 else 01042 { 01043 $res.= " /".$label; 01044 } 01045 $onum = $num; 01046 } 01047 $res .= "\n]\n>>\nendobj"; 01048 return $res; 01049 break; 01050 } 01051 } 01052 01053 /** 01054 * the document procset, solves some problems with printing to old PS printers 01055 */ 01056 function o_procset( $id, $action, $options = '' ) 01057 { 01058 if ( $action != 'new' ) 01059 { 01060 $o =& $this->objects[$id]; 01061 } 01062 switch ( $action ) 01063 { 01064 case 'new': 01065 $this->objects[$id] = array( 't' => 'procset', 01066 'info' => array( 'PDF' => 1, 'Text' => 1 ) ); 01067 $this->o_pages( $this->currentNode, 'procset', $id ); 01068 $this->procsetObjectId = $id; 01069 break; 01070 case 'add': 01071 // this is to add new items to the procset list, despite the fact that this is considered 01072 // obselete, the items are required for printing to some postscript printers 01073 switch ( $options ) 01074 { 01075 case 'ImageB': 01076 case 'ImageC': 01077 case 'ImageI': 01078 $o['info'][$options] = 1; 01079 break; 01080 } 01081 break; 01082 case 'out': 01083 $res = "\n" . $id . " 0 obj\n["; 01084 foreach ( $o['info'] as $label => $val ) 01085 { 01086 $res .= '/'.$label.' '; 01087 } 01088 $res .= "]\nendobj"; 01089 return $res; 01090 break; 01091 } 01092 } 01093 01094 /** 01095 * define the document information 01096 */ 01097 function o_info( $id, $action, $options = '' ) 01098 { 01099 if ( $action != 'new' ) 01100 { 01101 $o =& $this->objects[$id]; 01102 } 01103 switch ($action) 01104 { 01105 case 'new': 01106 $this->infoObject = $id; 01107 $date = 'D:' . date( 'Ymd' ); 01108 $this->objects[$id] = array( 't' => 'info', 01109 'info' => array( 'Creator' => 'eZ Publish CMS, http://ez.no', 01110 'CreationDate' => $date ) ); 01111 break; 01112 case 'Title': 01113 case 'Author': 01114 case 'Subject': 01115 case 'Keywords': 01116 case 'Creator': 01117 case 'Producer': 01118 case 'CreationDate': 01119 case 'ModDate': 01120 case 'Trapped': 01121 $o['info'][$action]=$options; 01122 break; 01123 case 'out': 01124 if ( $this->encrypted ) 01125 { 01126 $this->encryptInit( $id ); 01127 } 01128 $res = "\n" . $id . " 0 obj\n<<\n"; 01129 foreach ( $o['info'] as $k => $v ) 01130 { 01131 $res .= '/' . $k . ' ('; 01132 if ( $this->encrypted ) 01133 { 01134 $res .= $this->filterText( $this->ARC4( $v ) ); 01135 } 01136 else 01137 { 01138 $res .= $this->filterText( $v ); 01139 } 01140 $res .= ")\n"; 01141 } 01142 $res .= ">>\nendobj"; 01143 return $res; 01144 break; 01145 } 01146 } 01147 01148 /** 01149 * an action object, used to link to URLS initially 01150 */ 01151 function o_action( $id, $action, $options = '' ) 01152 { 01153 if ( $action != 'new' ) 01154 { 01155 $o =& $this->objects[$id]; 01156 } 01157 switch ( $action ) 01158 { 01159 case 'new': 01160 if ( is_array( $options ) ) 01161 { 01162 $this->objects[$id] = array( 't' => 'action', 01163 'info' => $options, 01164 'type' => $options['type'] ); 01165 } 01166 else 01167 { 01168 // then assume a URI action 01169 $this->objects[$id] = array( 't' => 'action', 01170 'info' => $options, 01171 'type' => 'URI' ); 01172 } 01173 break; 01174 case 'out': 01175 if ( $this->encrypted ) 01176 { 01177 $this->encryptInit( $id ); 01178 } 01179 $res = "\n" . $id . " 0 obj\n<< /Type /Action"; 01180 switch( $o['type'] ) 01181 { 01182 case 'ilink': 01183 // there will be an 'label' setting, this is the name of the destination 01184 //$res.="\n/S /GoTo\n/D [".$this->destinations[(string)$o['info']['label']]." 0 R /Fit]"; 01185 $res .= "\n/S /GoTo\n/D [" . $this->objects[ $this->destinations[ (string)$o['info']['label'] ] ]['info']['page'] . " 0 R /" . 01186 $this->objects[ $this->destinations[ (string)$o['info']['label'] ] ]['info']['string'] . ']'; 01187 break; 01188 case 'URI': 01189 $res .= "\n/S /URI\n/URI ("; 01190 if ( $this->encrypted ) 01191 { 01192 $res .= $this->filterText( $this->ARC4( $o['info'] ) ); 01193 } 01194 else 01195 { 01196 $res .= $this->filterText( $o['info'] ); 01197 } 01198 $res .= ")"; 01199 break; 01200 } 01201 $res .= "\n>>\nendobj"; 01202 return $res; 01203 break; 01204 } 01205 } 01206 01207 /** 01208 * an annotation object, this will add an annotation to the current page. 01209 * initially will support just link annotations 01210 */ 01211 function o_annotation( $id, $action, $options = '' ) 01212 { 01213 if ( $action != 'new' ) 01214 { 01215 $o =& $this->objects[$id]; 01216 } 01217 switch ( $action ) 01218 { 01219 case 'new': 01220 // add the annotation to the current page 01221 $pageId = $this->currentPage; 01222 $this->o_page( $pageId, 'annot', $id); 01223 // and add the action object which is going to be required 01224 switch ( $options['type'] ) 01225 { 01226 case 'link': 01227 $this->objects[$id] = array( 't' => 'annotation', 'info' => $options ); 01228 $this->numObj++; 01229 $this->o_action( $this->numObj, 'new', $options['url'] ); 01230 $this->objects[$id]['info']['actionId'] = $this->numObj; 01231 break; 01232 case 'ilink': 01233 // this is to a named internal link 01234 $label = $options['label']; 01235 $this->objects[$id] = array( 't' => 'annotation', 'info' => $options ); 01236 $this->numObj++; 01237 $this->o_action( $this->numObj, 'new', array( 'type' => 'ilink', 'label' => $label ) ); 01238 $this->objects[$id]['info']['actionId'] = $this->numObj; 01239 break; 01240 } 01241 break; 01242 01243 case 'out': 01244 $res = "\n" . $id . " 0 obj\n<< /Type /Annot"; 01245 switch ( $o['info']['type'] ) 01246 { 01247 case 'link': 01248 case 'ilink': 01249 $res.= "\n/Subtype /Link"; 01250 break; 01251 } 01252 $res.= "\n/A ".$o['info']['actionId']." 0 R"; 01253 $res.= "\n/Border [0 0 0]"; 01254 $res.= "\n/H /I"; 01255 $res.= "\n/Rect [ "; 01256 foreach ( $o['info']['rect'] as $v ) 01257 { 01258 $res.= sprintf( "%.4F ", $v ); 01259 } 01260 $res.= "]"; 01261 $res.= "\n>>\nendobj"; 01262 return $res; 01263 break; 01264 } 01265 } 01266 01267 /** 01268 * a page object, it also creates a contents object to hold its contents 01269 */ 01270 function o_page($id,$action,$options=''){ 01271 if ($action!='new'){ 01272 $o =& $this->objects[$id]; 01273 } 01274 switch ($action){ 01275 case 'new': 01276 $this->numPages++; 01277 $this->objects[$id]=array('t'=>'page','info'=>array('parent'=>$this->currentNode,'pageNum'=>$this->numPages)); 01278 if (is_array($options)){ 01279 // then this must be a page insertion, array shoudl contain 'rid','pos'=[before|after] 01280 $options['id']=$id; 01281 $this->o_pages($this->currentNode,'page',$options); 01282 } else { 01283 $this->o_pages($this->currentNode,'page',$id); 01284 } 01285 $this->currentPage=$id; 01286 //make a contents object to go with this page 01287 $this->numObj++; 01288 $this->o_contents($this->numObj,'new',$id); 01289 $this->currentContents=$this->numObj; 01290 $this->objects[$id]['info']['contents']=array(); 01291 $this->objects[$id]['info']['contents'][]=$this->numObj; 01292 $match = ($this->numPages%2 ? 'odd' : 'even'); 01293 foreach($this->addLooseObjects as $oId=>$target){ 01294 if ($target=='all' || $match==$target){ 01295 $this->objects[$id]['info']['contents'][]=$oId; 01296 } 01297 } 01298 break; 01299 case 'content': 01300 $o['info']['contents'][]=$options; 01301 break; 01302 case 'annot': 01303 // add an annotation to this page 01304 if (!isset($o['info']['annot'])){ 01305 $o['info']['annot']=array(); 01306 } 01307 // $options should contain the id of the annotation dictionary 01308 $o['info']['annot'][]=$options; 01309 break; 01310 case 'out': 01311 $res="\n".$id." 0 obj\n<< /Type /Page"; 01312 $res.="\n/Parent ".$o['info']['parent']." 0 R"; 01313 if (isset($o['info']['annot'])){ 01314 $res.="\n/Annots ["; 01315 foreach($o['info']['annot'] as $aId){ 01316 $res.=" ".$aId." 0 R"; 01317 } 01318 $res.=" ]"; 01319 } 01320 $count = count($o['info']['contents']); 01321 if ($count==1){ 01322 $res.="\n/Contents ".$o['info']['contents'][0]." 0 R"; 01323 } else if ($count>1){ 01324 $res.="\n/Contents [\n"; 01325 foreach ($o['info']['contents'] as $cId){ 01326 $res.=$cId." 0 R\n"; 01327 } 01328 $res.="]"; 01329 } 01330 $res.="\n>>\nendobj"; 01331 return $res; 01332 break; 01333 } 01334 } 01335 01336 /** 01337 * the contents objects hold all of the content which appears on pages 01338 */ 01339 function o_contents($id,$action,$options=''){ 01340 if ($action!='new'){ 01341 $o =& $this->objects[$id]; 01342 } 01343 switch ($action){ 01344 case 'new': 01345 $this->objects[$id]=array('t'=>'contents','c'=>'','info'=>array()); 01346 if (strlen($options) && intval($options)){ 01347 // then this contents is the primary for a page 01348 $this->objects[$id]['onPage']=$options; 01349 } else if ($options=='raw'){ 01350 // then this page contains some other type of system object 01351 $this->objects[$id]['raw']=1; 01352 } 01353 break; 01354 case 'add': 01355 // add more options to the decleration 01356 foreach ($options as $k=>$v){ 01357 $o['info'][$k]=$v; 01358 } 01359 case 'out': 01360 $tmp=$o['c']; 01361 $res= "\n".$id." 0 obj\n"; 01362 if (isset($this->objects[$id]['raw'])){ 01363 $res.=$tmp; 01364 } else { 01365 $res.= "<<"; 01366 if (function_exists('gzcompress') && $this->options['compression']){ 01367 // then implement ZLIB based compression on this content stream 01368 $res.=" /Filter /FlateDecode"; 01369 $tmp = gzcompress($tmp); 01370 } 01371 if ($this->encrypted){ 01372 $this->encryptInit($id); 01373 $tmp = $this->ARC4($tmp); 01374 } 01375 foreach($o['info'] as $k=>$v){ 01376 $res .= "\n/".$k.' '.$v; 01377 } 01378 $res.="\n/Length ".strlen($tmp)." >>\nstream\n".$tmp."\nendstream"; 01379 } 01380 $res.="\nendobj\n"; 01381 return $res; 01382 break; 01383 } 01384 } 01385 01386 /** 01387 * an image object, will be an XObject in the document, includes description and data 01388 */ 01389 function o_image($id,$action,$options=''){ 01390 if ($action!='new'){ 01391 $o =& $this->objects[$id]; 01392 } 01393 switch($action){ 01394 case 'new': 01395 // make the new object 01396 $this->objects[$id]=array('t'=>'image','data'=>$options['data'],'info'=>array()); 01397 $this->objects[$id]['info']['Type']='/XObject'; 01398 $this->objects[$id]['info']['Subtype']='/Image'; 01399 $this->objects[$id]['info']['Width']=$options['iw']; 01400 $this->objects[$id]['info']['Height']=$options['ih']; 01401 if (!isset($options['type']) || $options['type']=='jpg'){ 01402 if (!isset($options['channels'])){ 01403 $options['channels']=3; 01404 } 01405 switch($options['channels']){ 01406 case 1: 01407 $this->objects[$id]['info']['ColorSpace']='/DeviceGray'; 01408 break; 01409 default: 01410 $this->objects[$id]['info']['ColorSpace']='/DeviceRGB'; 01411 break; 01412 } 01413 $this->objects[$id]['info']['Filter']='/DCTDecode'; 01414 $this->objects[$id]['info']['BitsPerComponent']=8; 01415 } else if ($options['type']=='png'){ 01416 $this->objects[$id]['info']['Filter']='/FlateDecode'; 01417 $this->objects[$id]['info']['DecodeParms']='<< /Predictor 15 /Colors '.$options['ncolor'].' /Columns '.$options['iw'].' /BitsPerComponent '.$options['bitsPerComponent'].'>>'; 01418 if (strlen($options['pdata'])){ 01419 $tmp = ' [ /Indexed /DeviceCMYK '.(strlen($options['pdata'])/3-1).' '; 01420 $this->numObj++; 01421 $this->o_contents($this->numObj,'new'); 01422 $this->objects[$this->numObj]['c']=$options['pdata']; 01423 $tmp.=$this->numObj.' 0 R'; 01424 $tmp .=' ]'; 01425 $this->objects[$id]['info']['ColorSpace'] = $tmp; 01426 if (isset($options['transparency'])){ 01427 switch($options['transparency']['type']){ 01428 case 'indexed': 01429 $tmp=' [ '.$options['transparency']['data'].' '.$options['transparency']['data'].'] '; 01430 $this->objects[$id]['info']['Mask'] = $tmp; 01431 break; 01432 } 01433 } 01434 } else { 01435 $this->objects[$id]['info']['ColorSpace']='/'.$options['color']; 01436 } 01437 $this->objects[$id]['info']['BitsPerComponent']=$options['bitsPerComponent']; 01438 } 01439 // assign it a place in the named resource dictionary as an external object, according to 01440 // the label passed in with it. 01441 $this->o_pages($this->currentNode,'xObject',array('label'=>$options['label'],'objNum'=>$id)); 01442 // also make sure that we have the right procset object for it. 01443 $this->o_procset($this->procsetObjectId,'add','ImageC'); 01444 break; 01445 case 'out': 01446 $tmp=$o['data']; 01447 $res= "\n".$id." 0 obj\n<<"; 01448 foreach($o['info'] as $k=>$v){ 01449 $res.="\n/".$k.' '.$v; 01450 } 01451 if ($this->encrypted){ 01452 $this->encryptInit($id); 01453 $tmp = $this->ARC4($tmp); 01454 } 01455 $res.="\n/Length ".strlen($tmp)." >>\nstream\n".$tmp."\nendstream\nendobj\n"; 01456 return $res; 01457 break; 01458 } 01459 } 01460 01461 /** 01462 * encryption object. 01463 */ 01464 function o_encryption($id,$action,$options=''){ 01465 if ($action!='new'){ 01466 $o =& $this->objects[$id]; 01467 } 01468 switch($action){ 01469 case 'new': 01470 // make the new object 01471 $this->objects[$id]=array('t'=>'encryption','info'=>$options); 01472 $this->arc4_objnum=$id; 01473 // figure out the additional paramaters required 01474 $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); 01475 $len = strlen($options['owner']); 01476 if ($len>32){ 01477 $owner = substr($options['owner'],0,32); 01478 } else if ($len<32){ 01479 $owner = $options['owner'].substr($pad,0,32-$len); 01480 } else { 01481 $owner = $options['owner']; 01482 } 01483 $len = strlen($options['user']); 01484 if ($len>32){ 01485 $user = substr($options['user'],0,32); 01486 } else if ($len<32){ 01487 $user = $options['user'].substr($pad,0,32-$len); 01488 } else { 01489 $user = $options['user']; 01490 } 01491 $tmp = $this->md5_16($owner); 01492 $okey = substr($tmp,0,5); 01493 $this->ARC4_init($okey); 01494 $ovalue=$this->ARC4($user); 01495 $this->objects[$id]['info']['O']=$ovalue; 01496 // now make the u value, phew. 01497 $tmp = $this->md5_16($user.$ovalue.chr($options['p']).chr(255).chr(255).chr(255).$this->fileIdentifier); 01498 $ukey = substr($tmp,0,5); 01499 01500 $this->ARC4_init($ukey); 01501 $this->encryptionKey = $ukey; 01502 $this->encrypted=1; 01503 $uvalue=$this->ARC4($pad); 01504 01505 $this->objects[$id]['info']['U']=$uvalue; 01506 $this->encryptionKey=$ukey; 01507 01508 // initialize the arc4 array 01509 break; 01510 case 'out': 01511 $res= "\n".$id." 0 obj\n<<"; 01512 $res.="\n/Filter /Standard"; 01513 $res.="\n/V 1"; 01514 $res.="\n/R 2"; 01515 $res.="\n/O (".$this->filterText($o['info']['O']).')'; 01516 $res.="\n/U (".$this->filterText($o['info']['U']).')'; 01517 // and the p-value needs to be converted to account for the twos-complement approach 01518 $o['info']['p'] = (($o['info']['p']^255)+1)*-1; 01519 $res.="\n/P ".($o['info']['p']); 01520 $res.="\n>>\nendobj\n"; 01521 01522 return $res; 01523 break; 01524 } 01525 } 01526 01527 /** 01528 * ARC4 functions 01529 * A series of function to implement ARC4 encoding in PHP 01530 */ 01531 01532 /** 01533 * calculate the 16 byte version of the 128 bit md5 digest of the string 01534 */ 01535 function md5_16($string){ 01536 $tmp = md5($string); 01537 $out=''; 01538 for ($i=0;$i<=30;$i=$i+2){ 01539 $out.=chr(hexdec(substr($tmp,$i,2))); 01540 } 01541 return $out; 01542 } 01543 01544 /** 01545 * initialize the encryption for processing a particular object 01546 */ 01547 function encryptInit($id){ 01548 $tmp = $this->encryptionKey; 01549 $hex = dechex($id); 01550 if (strlen($hex)<6){ 01551 $hex = substr('000000',0,6-strlen($hex)).$hex; 01552 } 01553 $tmp.= chr(hexdec(substr($hex,4,2))).chr(hexdec(substr($hex,2,2))).chr(hexdec(substr($hex,0,2))).chr(0).chr(0); 01554 $key = $this->md5_16($tmp); 01555 $this->ARC4_init(substr($key,0,10)); 01556 } 01557 01558 /** 01559 * initialize the ARC4 encryption 01560 */ 01561 function ARC4_init($key=''){ 01562 $this->arc4 = ''; 01563 // setup the control array 01564 if (strlen($key)==0){ 01565 return; 01566 } 01567 $k = ''; 01568 while(strlen($k)<256){ 01569 $k.=$key; 01570 } 01571 $k=substr($k,0,256); 01572 for ($i=0;$i<256;$i++){ 01573 $this->arc4 .= chr($i); 01574 } 01575 $j=0; 01576 for ($i=0;$i<256;$i++){ 01577 $t = $this->arc4[$i]; 01578 $j = ($j + ord($t) + ord($k[$i]))%256; 01579 $this->arc4[$i]=$this->arc4[$j]; 01580 $this->arc4[$j]=$t; 01581 } 01582 } 01583 01584 /** 01585 * ARC4 encrypt a text string 01586 */ 01587 function ARC4($text){ 01588 $len=strlen($text); 01589 $a=0; 01590 $b=0; 01591 $c = $this->arc4; 01592 $out=''; 01593 for ($i=0;$i<$len;$i++){ 01594 $a = ($a+1)%256; 01595 $t= $c[$a]; 01596 $b = ($b+ord($t))%256; 01597 $c[$a]=$c[$b]; 01598 $c[$b]=$t; 01599 $k = ord($c[(ord($c[$a])+ord($c[$b]))%256]); 01600 $out.=chr(ord($text[$i]) ^ $k); 01601 } 01602 01603 return $out; 01604 } 01605 01606 /** 01607 * functions which can be called to adjust or add to the document 01608 */ 01609 01610 /** 01611 * add a link in the document to an external URL 01612 */ 01613 function addLink($url,$x0,$y0,$x1,$y1){ 01614 $this->numObj++; 01615 $info = array('type'=>'link','url'=>$url,'rect'=>array($x0,$y0,$x1,$y1)); 01616 $this->o_annotation($this->numObj,'new',$info); 01617 } 01618 01619 /** 01620 * add a link in the document to an internal destination (ie. within the document) 01621 */ 01622 function addInternalLink($label,$x0,$y0,$x1,$y1){ 01623 $this->numObj++; 01624 $info = array('type'=>'ilink','label'=>$label,'rect'=>array($x0,$y0,$x1,$y1)); 01625 $this->o_annotation($this->numObj,'new',$info); 01626 } 01627 01628 /** 01629 * set the encryption of the document 01630 * can be used to turn it on and/or set the passwords which it will have. 01631 * also the functions that the user will have are set here, such as print, modify, add 01632 */ 01633 function setEncryption($userPass='',$ownerPass='',$pc=array()){ 01634 $p=bindec(11000000); 01635 01636 $options = array( 01637 'print'=>4 01638 ,'modify'=>8 01639 ,'copy'=>16 01640 ,'add'=>32 01641 ); 01642 foreach($pc as $k=>$v){ 01643 if ($v && isset($options[$k])){ 01644 $p+=$options[$k]; 01645 } else if (isset($options[$v])){ 01646 $p+=$options[$v]; 01647 } 01648 } 01649 // implement encryption on the document 01650 if ($this->arc4_objnum == 0){ 01651 // then the block does not exist already, add it. 01652 $this->numObj++; 01653 if (strlen($ownerPass)==0){ 01654 $ownerPass=$userPass; 01655 } 01656 $this->o_encryption($this->numObj,'new',array('user'=>$userPass,'owner'=>$ownerPass,'p'=>$p)); 01657 } 01658 } 01659 01660 /** 01661 * should be used for internal checks, not implemented as yet 01662 */ 01663 function checkAllHere(){ 01664 } 01665 01666 /** 01667 * return the pdf stream as a string returned from the function 01668 */ 01669 function output($debug=0){ 01670 01671 if ($debug){ 01672 // turn compression off 01673 $this->options['compression']=0; 01674 } 01675 01676 if ($this->arc4_objnum){ 01677 $this->ARC4_init($this->encryptionKey); 01678 } 01679 01680 $this->checkAllHere(); 01681 01682 $xref=array(); 01683 $content="%PDF-1.3\n%����\n"; 01684 // $content="%PDF-1.3\n"; 01685 $pos=strlen($content); 01686 foreach($this->objects as $k=>$v){ 01687 $tmp='o_'.$v['t']; 01688 $cont=$this->$tmp($k,'out'); 01689 $content.=$cont; 01690 $xref[]=$pos; 01691 $pos+=strlen($cont); 01692 } 01693 $content.="\nxref\n0 ".(count($xref)+1)."\n0000000000 65535 f \n"; 01694 foreach($xref as $p){ 01695 ++$p; 01696 $content.=substr('0000000000',0,10-strlen($p)).$p." 00000 n \n"; 01697 } 01698 $content.="\ntrailer\n << /Size ".(count($xref)+1)."\n /Root 1 0 R\n /Info ".$this->infoObject." 0 R\n"; 01699 // if encryption has been applied to this document then add the marker for this dictionary 01700 if ($this->arc4_objnum > 0){ 01701 $content .= "/Encrypt ".$this->arc4_objnum." 0 R\n"; 01702 } 01703 if (strlen($this->fileIdentifier)){ 01704 $content .= "/ID[<".$this->fileIdentifier."><".$this->fileIdentifier.">]\n"; 01705 } 01706 $content .= " >>\nstartxref\n".($pos+1)."\n%%EOF\n"; 01707 01708 return $content; 01709 } 01710 01711 /** 01712 * intialize a new document 01713 * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum 01714 * this function is called automatically by the constructor function 01715 * 01716 * @access private 01717 */ 01718 function newDocument($pageSize=array(0,0,612,792)){ 01719 $this->numObj=0; 01720 $this->objects = array(); 01721 01722 $this->numObj++; 01723 $this->o_catalog($this->numObj,'new'); 01724 01725 $this->numObj++; 01726 $this->o_outlines($this->numObj,'new'); 01727 01728 $this->numObj++; 01729 $this->o_pages($this->numObj,'new'); 01730 01731 $this->o_pages($this->numObj,'mediaBox',$pageSize); 01732 $this->currentNode = 3; 01733 01734 $this->numObj++; 01735 $this->o_procset($this->numObj,'new'); 01736 01737 $this->numObj++; 01738 $this->o_info($this->numObj,'new'); 01739 01740 $this->numObj++; 01741 $this->o_page($this->numObj,'new'); 01742 01743 // need to store the first page id as there is no way to get it to the user during 01744 // startup 01745 $this->firstPageId = $this->currentContents; 01746 } 01747 01748 /** 01749 * open the font file and return a php structure containing it. 01750 * first check if this one has been done before and saved in a form more suited to php 01751 * note that if a php serialized version does not exist it will try and make one, but will 01752 * require write access to the directory to do it... it is MUCH faster to have these serialized 01753 * files. 01754 * 01755 * @access private 01756 */ 01757 function openFont( $font ) 01758 { 01759 // assume that $font contains both the path and perhaps the extension to the file, split them 01760 $pos = strrpos( $font, '/' ); 01761 if ( $pos === false ) 01762 { 01763 $dir = './'; 01764 $name = $font; 01765 } 01766 else 01767 { 01768 $dir = substr( $font, 0, $pos + 1 ); 01769 $name = substr( $font, $pos + 1 ); 01770 } 01771 01772 if ( substr( $name, -5 ) == '.font' ) 01773 { 01774 $name = substr( $name, 0, strlen( $name ) - 5 ); 01775 } 01776 01777 $this->addMessage( 'openFont: ' . $font . ' - ' . $name ); 01778 if ( file_exists( $dir.'php_'.$name.'.font' ) ) 01779 { 01780 $this->addMessage( 'openFont: php file exists ' . $dir . 'php_' . $name . '.font' ); 01781 $tmp = file( $dir . 'php_' . $name . '.font' ); 01782 $this->fonts[$font] = unserialize( $tmp[0] ); 01783 if ( !isset( $this->fonts[$font]['_version_'] ) || $this->fonts[$font]['_version_'] < 1 ) 01784 { 01785 // if the font file is old, then clear it out and prepare for re-creation 01786 $this->addMessage( 'openFont: clear out, make way for new version.' ); 01787 unset( $this->fonts[$font] ); 01788 } 01789 } 01790 if ( !isset( $this->fonts[$font] ) && file_exists( $dir.$name.'.afm' ) ) 01791 { 01792 // then rebuild the php_<font>.afm file from the <font>.afm file 01793 $this->addMessage( 'openFont: build php file from ' . $dir.$name.'.afm' ); 01794 $data = array(); 01795 $file = file( $dir.$name.'.afm' ); 01796 foreach ( $file as $rowA ) 01797 { 01798 $row = trim( $rowA ); 01799 $pos = strpos( $row, ' ' ); 01800 if ( $pos ) 01801 { 01802 // then there must be some keyword 01803 $key = substr( $row, 0, $pos ); 01804 switch ( $key ) 01805 { 01806 case 'FontName': 01807 case 'FullName': 01808 case 'FamilyName': 01809 case 'Weight': 01810 case 'ItalicAngle': 01811 case 'IsFixedPitch': 01812 case 'CharacterSet': 01813 case 'UnderlinePosition': 01814 case 'UnderlineThickness': 01815 case 'Version': 01816 case 'EncodingScheme': 01817 case 'CapHeight': 01818 case 'XHeight': 01819 case 'Ascender': 01820 case 'Descender': 01821 case 'StdHW': 01822 case 'StdVW': 01823 case 'StartCharMetrics': 01824 $data[$key] = trim( substr( $row, $pos ) ); 01825 break; 01826 case 'FontBBox': 01827 $data[$key] = explode(' ', trim( substr( $row, $pos ) ) ); 01828 break; 01829 case 'C': 01830 //C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ; 01831 $bits = explode( ';', trim( $row ) ); 01832 $dtmp = array(); 01833 foreach ( $bits as $bit ) 01834 { 01835 $bits2 = explode( ' ', trim( $bit ) ); 01836 if ( strlen( $bits2[0] ) ) 01837 { 01838 if ( count( $bits2 ) > 2 ) 01839 { 01840 $dtmp[$bits2[0]] = array(); 01841 for ( $i = 1; $i < count( $bits2 ); $i++ ) 01842 { 01843 $dtmp[$bits2[0]][] = $bits2[$i]; 01844 } 01845 } 01846 else if ( count( $bits2 ) == 2 ) 01847 { 01848 $dtmp[$bits2[0]] = $bits2[1]; 01849 } 01850 } 01851 } 01852 if ( $dtmp['C'] >= 0 ) 01853 { 01854 $data['C'][$dtmp['C']] = $dtmp; 01855 $data['C'][$dtmp['N']] = $dtmp; 01856 } 01857 else 01858 { 01859 $data['C'][$dtmp['N']] = $dtmp; 01860 } 01861 break; 01862 01863 case 'KPX': 01864 //KPX Adieresis yacute -40 01865 $bits = explode( ' ', trim( $row ) ); 01866 $data['KPX'][$bits[1]][$bits[2]] = $bits[3]; 01867 break; 01868 } 01869 } 01870 } 01871 $data['_version_'] = 1; 01872 $this->fonts[$font] = $data; 01873 $fp = fopen( $dir.'php_'.$name.'.font', 'w' ); 01874 fwrite( $fp, serialize( $data ) ); 01875 fclose( $fp ); 01876 } 01877 else if ( !isset( $this->fonts[$font] ) ) 01878 { 01879 $this->addMessage( 'openFont: no font file found' ); 01880 //echo 'Font not Found '.$font; 01881 } 01882 } 01883 01884 /** 01885 * if the font is not loaded then load it and make the required object 01886 * else just make it the current font 01887 * the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding' 01888 * note that encoding='none' will need to be used for symbolic fonts 01889 * and 'differences' => an array of mappings between numbers 0->255 and character names. 01890 * 01891 */ 01892 function selectFont( $fontName, $encoding = '', $set = 1 ) 01893 { 01894 if ( !isset( $this->fonts[$fontName] ) ) 01895 { 01896 // load the file 01897 $this->openFont( $fontName ); 01898 if ( isset( $this->fonts[$fontName] ) ) 01899 { 01900 $this->numObj++; 01901 $this->numFonts++; 01902 $pos = strrpos( $fontName, '/' ); 01903 $name = substr( $fontName, $pos + 1 ); 01904 if ( substr( $name, -5 ) == '.font' ) 01905 { 01906 $name = substr( $name, 0, strlen( $name ) - 5 ); 01907 } 01908 if ( !isset( $this->fontFamilies[$name] ) ) 01909 { 01910 $this->setFontFamily( $name, array( 'b' => $name . '-Bold', 01911 'i' => $name . '-Italic', 01912 'bi' => $name . '-BoldItalic', 01913 'ib' => $name . '-BoldItalic' ) ); 01914 } 01915 $options = array( 'name' => $name ); 01916 if ( is_array( $encoding ) ) 01917 { 01918 // then encoding and differences might be set 01919 if ( isset( $encoding['encoding'] ) ) 01920 { 01921 $options['encoding'] = $encoding['encoding']; 01922 } 01923 if ( isset( $encoding['differences'] ) ) 01924 { 01925 $options['differences'] = $encoding['differences']; 01926 } 01927 } 01928 else if ( strlen( $encoding ) ) 01929 { 01930 // then perhaps only the encoding has been set 01931 $options['encoding'] = $encoding; 01932 } 01933 $fontObj = $this->numObj; 01934 $this->o_font( $this->numObj, 'new', $options ); 01935 $this->fonts[$fontName]['fontNum'] = $this->numFonts; 01936 // if this is a '.afm' font, and there is a '.pfa' file to go with it ( as there 01937 // should be for all non-basic fonts), then load it into an object and put the 01938 // references into the font object 01939 $basefile = substr( $fontName, 0, strlen( $fontName ) ); 01940 if ( file_exists( $basefile.'.pfb' ) ) 01941 { 01942 $fbtype = 'pfb'; 01943 } 01944 else if ( file_exists( $basefile.'.ttf' ) ) 01945 { 01946 $fbtype = 'ttf'; 01947 } 01948 else 01949 { 01950 $fbtype = ''; 01951 } 01952 $fbfile = $basefile.'.'.$fbtype; 01953 01954 $this->addMessage( 'selectFont: checking for - ' . $fbfile ); 01955 if ( strlen( $fbtype ) ) 01956 { 01957 $adobeFontName = $this->fonts[$fontName]['FontName']; 01958 $this->addMessage( 'selectFont: adding font file - ' . $fbfile . ' - ' . $adobeFontName ); 01959 // find the array of fond widths, and put that into an object. 01960 $firstChar = -1; 01961 $lastChar = 0; 01962 $widths = array(); 01963 foreach ( $this->fonts[$fontName]['C'] as $num => $d ) 01964 { 01965 if ( intval($num) > 0 || $num == '0' ) 01966 { 01967 if ( $lastChar > 0 && $num > $lastChar + 1 ) 01968 { 01969 for ( $i = $lastChar + 1; $i < $num; $i++ ) 01970 { 01971 $widths[] = 0; 01972 } 01973 } 01974 $widths[] = $d['WX']; 01975 if ( $firstChar == -1 ) 01976 { 01977 $firstChar = $num; 01978 } 01979 $lastChar = $num; 01980 } 01981 } 01982 // also need to adjust the widths for the differences array 01983 if ( isset( $options['differences'] ) ) 01984 { 01985 foreach( $options['differences'] as $charNum => $charName ) 01986 { 01987 if ( $charNum > $lastChar ) 01988 { 01989 for( $i = $lastChar + 1; $i <= $charNum; $i++ ) 01990 { 01991 $widths[] = 0; 01992 } 01993 $lastChar = $charNum; 01994 } 01995 if ( isset( $this->fonts[$fontName]['C'][$charName] ) ) 01996 { 01997 $widths[$charNum-$firstChar] = $this->fonts[$fontName]['C'][$charName]['WX']; 01998 } 01999 } 02000 } 02001 $this->addMessage( 'selectFont: FirstChar=' . $firstChar ); 02002 $this->addMessage( 'selectFont: LastChar=' . $lastChar ); 02003 $this->numObj++; 02004 $this->o_contents( $this->numObj, 'new', 'raw' ); 02005 $this->objects[$this->numObj]['c'] .= '['; 02006 foreach ( $widths as $width ) 02007 { 02008 $this->objects[$this->numObj]['c'] .= ' ' . $width; 02009 } 02010 $this->objects[$this->numObj]['c'] .= ' ]'; 02011 $widthid = $this->numObj; 02012 02013 // load the pfb file, and put that into an object too. 02014 // note that pdf supports only binary format type 1 font files, though there is a 02015 // simple utility to convert them from pfa to pfb. 02016 $fp = fopen( $fbfile, 'rb' ); 02017 $tmp = get_magic_quotes_runtime(); 02018 set_magic_quotes_runtime( 0 ); 02019 $data = fread( $fp, filesize( $fbfile ) ); 02020 set_magic_quotes_runtime( $tmp ); 02021 fclose( $fp ); 02022 02023 // create the font descriptor 02024 $this->numObj++; 02025 $fontDescriptorId = $this->numObj; 02026 $this->numObj++; 02027 $pfbid = $this->numObj; 02028 // determine flags (more than a little flakey, hopefully will not matter much) 02029 $flags=0; 02030 if ( $this->fonts[$fontName]['ItalicAngle'] != 0 ) 02031 { 02032 $flags += pow( 2, 6 ); 02033 } 02034 if ( $this->fonts[$fontName]['IsFixedPitch'] == 'true' ) 02035 { 02036 $flags += 1; 02037 } 02038 $flags += pow( 2, 5 ); // assume non-sybolic 02039 02040 $list = array( 'Ascent' => 'Ascender', 02041 'CapHeight' => 'CapHeight', 02042 'Descent' => 'Descender', 02043 'FontBBox' => 'FontBBox', 02044 'ItalicAngle' => 'ItalicAngle' ); 02045 $fdopt = array( 'Flags' => $flags, 02046 'FontName' => $adobeFontName, 02047 'StemV' => 100 // don't know what the value for this should be! 02048 ); 02049 foreach ( $list as $k => $v ) 02050 { 02051 if ( isset( $this->fonts[$fontName][$v] ) ) 02052 { 02053 $fdopt[$k] = $this->fonts[$fontName][$v]; 02054 } 02055 } 02056 02057 if ( $fbtype == 'pfb' ) 02058 { 02059 $fdopt['FontFile'] = $pfbid; 02060 } 02061 else if ( $fbtype == 'ttf' ) 02062 { 02063 $fdopt['FontFile2'] = $pfbid; 02064 } 02065 $this->o_fontDescriptor( $fontDescriptorId, 'new', $fdopt ); 02066 02067 // embed the font program 02068 $this->o_contents( $this->numObj, 'new' ); 02069 $this->objects[$pfbid]['c'] .= $data; 02070 // determine the cruicial lengths within this file 02071 if ($fbtype=='pfb') 02072 { 02073 $l1 = strpos($data,'eexec') + 6; 02074 $l2 = strpos($data,'00000000') - $l1; 02075 $l3 = strlen($data) - $l2 - $l1; 02076 $this->o_contents( $this->numObj, 'add', array( 'Length1' => $l1, 02077 'Length2' => $l2, 02078 'Length3' => $l3 ) ); 02079 } 02080 else if ( $fbtype == 'ttf' ) 02081 { 02082 $l1 = strlen($data); 02083 $this->o_contents( $this->numObj, 'add', array( 'Length1' => $l1 ) ); 02084 } 02085 02086 // tell the font object about all this new stuff 02087 $tmp = array( 'BaseFont' => $adobeFontName, 02088 'Widths' => $widthid, 02089 'FirstChar' => $firstChar, 02090 'LastChar' => $lastChar, 02091 'FontDescriptor' => $fontDescriptorId ); 02092 if ( $fbtype == 'ttf' ) 02093 { 02094 $tmp['SubType'] = 'TrueType'; 02095 } 02096 $this->addMessage( 'adding extra info to font.('.$fontObj.')' ); 02097 foreach ( $tmp as $fk => $fv ) 02098 { 02099 $this->addMessage( $fk." : ".$fv ); 02100 } 02101 $this->o_font( $fontObj, 'add', $tmp ); 02102 02103 } 02104 else 02105 { 02106 $this->addMessage( 'selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts' ); 02107 } 02108 02109 02110 // also set the differences here, note that this means that these will take effect only the 02111 //first time that a font is selected, else they are ignored 02112 if ( isset( $options['differences'] ) ) 02113 { 02114 $this->fonts[$fontName]['differences'] = $options['differences']; 02115 } 02116 } 02117 } 02118 if ( $set && isset( $this->fonts[$fontName] ) ) 02119 { 02120 // so if for some reason the font was not set in the last one then it will not be selected 02121 $this->currentBaseFont = $fontName; 02122 // the next line means that if a new font is selected, then the current text state will be 02123 // applied to it as well. 02124 $this->setCurrentFont(); 02125 } 02126 return $this->currentFontNum; 02127 } 02128 02129 /** 02130 Get current font 02131 02132 \return current font name 02133 */ 02134 function currentFont() 02135 { 02136 return $this->currentBaseFont; 02137 } 02138 02139 /** 02140 * sets up the current font, based on the font families, and the current text state 02141 * note that this system is quite flexible, a <b><i> font can be completely different to a 02142 * <i><b> font, and even <b><b> will have to be defined within the family to have meaning 02143 * This function is to be called whenever the currentTextState is changed, it will update 02144 * the currentFont setting to whatever the appropriatte family one is. 02145 * If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont 02146 * This function will change the currentFont to whatever it should be, but will not change the 02147 * currentBaseFont. 02148 * 02149 * @access private 02150 */ 02151 function setCurrentFont() 02152 { 02153 if ( strlen( $this->currentBaseFont ) == 0 ) 02154 { 02155 // then assume an initial font 02156 $this->selectFont( 'lib/ezpdf/classes/fonts/Helvetica' ); 02157 } 02158 $cf = substr( $this->currentBaseFont, strrpos( $this->currentBaseFont, '/' ) + 1 ); 02159 if ( strlen( $this->currentTextState ) 02160 && isset( $this->fontFamilies[$cf] ) 02161 && isset( $this->fontFamilies[$cf][$this->currentTextState] ) ) 02162 { 02163 // then we are in some state or another 02164 // and this font has a family, and the current setting exists within it 02165 // select the font, then return it 02166 $nf = substr( $this->currentBaseFont, 0, strrpos( $this->currentBaseFont, '/' ) + 1 ) . 02167 $this->fontFamilies[$cf][$this->currentTextState]; 02168 $this->selectFont( $nf, '', 0 ); 02169 $this->currentFont = $nf; 02170 $this->currentFontNum = $this->fonts[$nf]['fontNum']; 02171 } 02172 else 02173 { 02174 // the this font must not have the right family member for the current state 02175 // simply assume the base font 02176 $this->currentFont = $this->currentBaseFont; 02177 $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum']; 02178 } 02179 } 02180 02181 /** 02182 * function for the user to find out what the ID is of the first page that was created during 02183 * startup - useful if they wish to add something to it later. 02184 */ 02185 function getFirstPageId() 02186 { 02187 return $this->firstPageId; 02188 } 02189 02190 /** 02191 * add content to the currently active object 02192 * 02193 * @access private 02194 */ 02195 function addContent( $content ) 02196 { 02197 $this->objects[$this->currentContents]['c'] .= $content; 02198 } 02199 02200 /** 02201 * sets the colour for fill operations 02202 */ 02203 function setColorRGB( $r, $g, $b, $force = 0 ) 02204 { 02205 $this->setColor( eZMath::rgbToCMYK2( $r, $g, $b ), $force ); 02206 } 02207 02208 /*! 02209 sets the colour for fill operations 02210 02211 \param CMYK array 02212 */ 02213 function setColor( $cmykArray, $force = 0 ) 02214 { 02215 if ( $force || !$this->compareCMYK( $cmykArray, $this->currentColour ) ) 02216 { 02217 $this->objects[$this->currentContents]['c'] .= "\n" . 02218 sprintf( '%.3F', $cmykArray['c'] ) . ' ' . sprintf( '%.3F', $cmykArray['m'] ) . ' ' . 02219 sprintf( '%.3F', $cmykArray['y'] ) . ' ' . sprintf( '%.3F', $cmykArray['k'] ) . ' k'; 02220 $this->currentColour = $cmykArray; 02221 } 02222 } 02223 02224 /** 02225 * sets the colour for stroke operations 02226 */ 02227 function setStrokeColorRGB( $r, $g, $b, $force = 0 ) 02228 { 02229 $this->setStrokeColor( eZMath::rgbToCMYK2( $r, $g, $b ), $force ); 02230 } 02231 02232 /*! 02233 sets the colour for stroke operations 02234 02235 \param cmyk array 02236 \param force color change 02237 */ 02238 function setStrokeColor( $cmykArray, $force = 0 ) 02239 { 02240 if ( $force || !$this->compareCMYK( $cmykArray, $this->currentStrokeColour ) ) 02241 { 02242 $this->objects[$this->currentContents]['c'] .= "\n" . 02243 sprintf( '%.3F', $cmykArray['c'] ) . ' ' . sprintf( '%.3F', $cmykArray['m'] ) . ' ' . 02244 sprintf( '%.3F', $cmykArray['y'] ) . ' ' . sprintf( '%.3F', $cmykArray['k'] ) . ' K'; 02245 $this->currentStrokeColour = $cmykArray; 02246 } 02247 } 02248 02249 /** 02250 * draw a line from one set of coordinates to another 02251 */ 02252 function line( $x1, $y1, $x2, $y2 ) 02253 { 02254 $this->objects[$this->currentContents]['c'] .= "\n".sprintf('%.3F',$x1).' '.sprintf('%.3F',$y1).' m '.sprintf('%.3F',$x2).' '.sprintf('%.3F',$y2).' l S'; 02255 } 02256 02257 /** 02258 * draw a bezier curve based on 4 control points 02259 */ 02260 function curve( $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3 ) 02261 { 02262 // in the current line style, draw a bezier curve from (x0,y0) to (x3,y3) using the other two points 02263 // as the control points for the curve. 02264 $this->objects[$this->currentContents]['c'].="\n".sprintf('%.3F',$x0).' '.sprintf('%.3F',$y0).' m '.sprintf('%.3F',$x1).' '.sprintf('%.3F',$y1); 02265 $this->objects[$this->currentContents]['c'].= ' '.sprintf('%.3F',$x2).' '.sprintf('%.3F',$y2).' '.sprintf('%.3F',$x3).' '.sprintf('%.3F',$y3).' c S'; 02266 } 02267 02268 /** 02269 * draw a part of an ellipse 02270 */ 02271 function partEllipse( $x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8 ) 02272 { 02273 $this->ellipse( $x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, 0 ); 02274 } 02275 02276 /** 02277 * draw a filled ellipse 02278 */ 02279 function filledEllipse( $x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360 ) 02280 { 02281 return $this->ellipse( $x0, $y0, $r1, $r2 = 0, $angle, $nSeg, $astart, $afinish, 1, 1 ); 02282 } 02283 02284 /** 02285 * draw an ellipse 02286 * note that the part and filled ellipse are just special cases of this function 02287 * 02288 * draws an ellipse in the current line style 02289 * centered at $x0,$y0, radii $r1,$r2 02290 * if $r2 is not set, then a circle is drawn 02291 * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a 02292 * pretty crappy shape at 2, as we are approximating with bezier curves. 02293 */ 02294 function ellipse( $x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360, $close = 1, $fill = 0 ) 02295 { 02296 if ( $r1 == 0 ) 02297 { 02298 return; 02299 } 02300 if ( $r2 == 0 ) 02301 { 02302 $r2 = $r1; 02303 } 02304 if ( $nSeg < 2 ) 02305 { 02306 $nSeg = 2; 02307 } 02308 02309 $astart = deg2rad( (float)$astart ); 02310 $afinish = deg2rad( (float)$afinish ); 02311 $totalAngle = $afinish - $astart; 02312 02313 $dt = $totalAngle / $nSeg; 02314 $dtm = $dt / 3; 02315 02316 if ( $angle != 0 ) 02317 { 02318 $a = -1*deg2rad((float)$angle); 02319 $tmp = "\n q "; 02320 $tmp .= sprintf('%.3F',cos($a)).' '.sprintf('%.3F',(-1.0*sin($a))).' '.sprintf('%.3F',sin($a)).' '.sprintf('%.3F',cos($a)).' '; 02321 $tmp .= sprintf('%.3F',$x0).' '.sprintf('%.3F',$y0).' cm'; 02322 $this->objects[$this->currentContents]['c'].= $tmp; 02323 $x0=0; 02324 $y0=0; 02325 } 02326 02327 $t1 = $astart; 02328 $a0 = $x0 + $r1*cos($t1); 02329 $b0 = $y0 + $r2*sin($t1); 02330 $c0 = -$r1 * sin($t1); 02331 $d0 = $r2 * cos($t1); 02332 02333 $this->objects[$this->currentContents]['c'] .= "\n".sprintf('%.3F',$a0).' '.sprintf('%.3F',$b0).' m '; 02334 for ($i=1;$i<=$nSeg;$i++) 02335 { 02336 // draw this bit of the total curve 02337 $t1 = $i*$dt + $astart; 02338 $a1 = $x0 + $r1*cos($t1); 02339 $b1 = $y0 + $r2*sin($t1); 02340 $c1 = -$r1 * sin($t1); 02341 $d1 = $r2 * cos($t1); 02342 $this->objects[$this->currentContents]['c'].= "\n".sprintf('%.3F',($a0+$c0*$dtm)).' '.sprintf('%.3F',($b0+$d0*$dtm)); 02343 $this->objects[$this->currentContents]['c'].= ' '.sprintf('%.3F',($a1-$c1*$dtm)).' '.sprintf('%.3F',($b1-$d1*$dtm)).' '.sprintf('%.3F',$a1).' '.sprintf('%.3F',$b1).' c'; 02344 $a0 = $a1; 02345 $b0 = $b1; 02346 $c0 = $c1; 02347 $d0 = $d1; 02348 } 02349 if ( $fill ) 02350 { 02351 $this->objects[$this->currentContents]['c'].= ' f'; 02352 } 02353 else 02354 { 02355 if ($close) 02356 { 02357 $this->objects[$this->currentContents]['c'].= ' s'; // small 's' signifies closing the path as well 02358 } 02359 else 02360 { 02361 $this->objects[$this->currentContents]['c'].= ' S'; 02362 } 02363 } 02364 if ( $angle != 0 ) 02365 { 02366 $this->objects[$this->currentContents]['c'].= ' Q'; 02367 } 02368 } 02369 02370 /** 02371 * this sets the line drawing style. 02372 * width, is the thickness of the line in user units 02373 * cap is the type of cap to put on the line, values can be 'butt','round','square' 02374 * where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the 02375 * end of the line. 02376 * join can be 'miter', 'round', 'bevel' 02377 * dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the 02378 * on and off dashes. 02379 * (2) represents 2 on, 2 off, 2 on , 2 off ... 02380 * (2,1) is 2 on, 1 off, 2 on, 1 off.. etc 02381 * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts. 02382 */ 02383 function setLineStyle( $width = 1, $cap = '', $join = '', $dash = '', $phase = 0 ) 02384 { 02385 02386 // this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day 02387 $string = ''; 02388 if ( $width > 0 ) 02389 { 02390 $string.= $width.' w'; 02391 } 02392 $ca = array( 'butt' => 0, 'round' => 1, 'square' => 2 ); 02393 if ( isset( $ca[$cap] ) ) 02394 { 02395 $string.= ' '.$ca[$cap].' J'; 02396 } 02397 $ja = array('miter'=>0,'round'=>1,'bevel'=>2); 02398 if ( isset( $ja[$join] ) ) 02399 { 02400 $string.= ' '.$ja[$join].' j'; 02401 } 02402 if ( is_array( $dash ) ) 02403 { 02404 $string.= ' ['; 02405 foreach ( $dash as $len ) 02406 { 02407 $string.=' '.$len; 02408 } 02409 $string.= ' ] '.$phase.' d'; 02410 } 02411 $this->currentLineStyle = $string; 02412 $this->objects[$this->currentContents]['c'].= "\n".$string; 02413 } 02414 02415 /** 02416 * draw a polygon, the syntax for this is similar to the GD polygon command 02417 */ 02418 function polygon( $p, $np, $f = 0 ) 02419 { 02420 $this->objects[$this->currentContents]['c'].= "\n"; 02421 $this->objects[$this->currentContents]['c'].= sprintf('%.3F',$p[0]).' '.sprintf('%.3F',$p[1]).' m '; 02422 for ( $i = 2; $i < $np*2; $i = $i + 2 ) 02423 { 02424 $this->objects[$this->currentContents]['c'].= sprintf('%.3F',$p[$i]).' '.sprintf('%.3F',$p[$i+1]).' l '; 02425 } 02426 if ( $f == 1 ) 02427 { 02428 $this->objects[$this->currentContents]['c'].= ' f'; 02429 } 02430 else 02431 { 02432 $this->objects[$this->currentContents]['c'].= ' S'; 02433 } 02434 } 02435 02436 /** 02437 Create shaded rectangle area 02438 02439 \param x1 02440 \param x2 02441 \param width 02442 \param height 02443 \param options 02444 array ( 'orientation' => <vertical|horizontal ( optianal )>, 02445 'color0' => <CMYK color array>, 02446 'color1' => <CMYK color array> ) 02447 */ 02448 function shadedRectangle( $x1, $y1, $width, $height, $options ) 02449 { 02450 $this->numObj++; 02451 $shadingLabel = $this->o_shading( $this->numObj, 'new', $options ); 02452 02453 $this->saveState(); 02454 02455 $this->objects[$this->currentContents]['c'].="\n".sprintf('%.3F',$x1).' '.sprintf('%.3F',$y1).' '.sprintf('%.3F',$width).' '.sprintf('%.3F',$height).' re'; 02456 02457 $this->objects[$this->currentContents]['c'].="\nW n"; 02458 02459 $this->objects[$this->currentContents]['c'].="\n/".$shadingLabel.' sh'; 02460 02461 $this->restoreState(); 02462 } 02463 02464 /** 02465 * a filled rectangle, note that it is the width and height of the rectangle which are the secondary paramaters, not 02466 * the coordinates of the upper-right corner 02467 */ 02468 function filledRectangle( $x1, $y1, $width, $height ) 02469 { 02470 $this->objects[$this->currentContents]['c'].="\n".sprintf('%.3F',$x1).' '.sprintf('%.3F',$y1).' '.sprintf('%.3F',$width).' '.sprintf('%.3F',$height).' re f'; 02471 } 02472 02473 /** 02474 * draw a rectangle, note that it is the width and height of the rectangle which are the secondary paramaters, not 02475 * the coordinates of the upper-right corner 02476 */ 02477 function rectangle( $x1, $y1, $width, $height ) 02478 { 02479 $this->objects[$this->currentContents]['c'].="\n".sprintf('%.3F',$x1).' '.sprintf('%.3F',$y1).' '.sprintf('%.3F',$width).' '.sprintf('%.3F',$height).' re S'; 02480 } 02481 02482 /** 02483 * add a new page to the document 02484 * this also makes the new page the current active object 02485 */ 02486 function newPage( $insert = 0, $id = 0, $pos = 'after' ) 02487 { 02488 // if there is a state saved, then go up the stack closing them 02489 // then on the new page, re-open them with the right setings 02490 02491 if ( $this->nStateStack ) 02492 { 02493 for ( $i = $this->nStateStack; $i >= 1; $i-- ) 02494 { 02495 $this->restoreState( $i ); 02496 } 02497 } 02498 02499 $this->numObj++; 02500 if ( $insert ) 02501 { 02502 // the id from the ezPdf class is the od of the contents of the page, not the page object itself 02503 // query that object to find the parent 02504 $rid = $this->objects[$id]['onPage']; 02505 $opt = array( 'rid' => $rid, 'pos' => $pos ); 02506 $this->o_page( $this->numObj, 'new', $opt); 02507 } 02508 else 02509 { 02510 $this->o_page( $this->numObj, 'new' ); 02511 } 02512 // if there is a stack saved, then put that onto the page 02513 if ( $this->nStateStack ) 02514 { 02515 for ( $i = 1; $i <= $this->nStateStack; $i++) 02516 { 02517 $this->saveState($i); 02518 } 02519 } 02520 // and if there has been a stroke or fill colour set, then transfer them 02521 if ( $this->currentColour['c'] >= 0 ) 02522 { 02523 $this->setColor( $this->currentColour, 1 ); 02524 } 02525 if ( $this->currentStrokeColour['c'] >= 0 ) 02526 { 02527 $this->setStrokeColor( $this->currentStrokeColour, 1 ); 02528 } 02529 02530 // if there is a line style set, then put this in too 02531 if ( strlen( $this->currentLineStyle ) ) 02532 { 02533 $this->objects[$this->currentContents]['c'].= "\n".$this->currentLineStyle; 02534 } 02535 02536 // the call to the o_page object set currentContents to the present page, so this can be returned as the page id 02537 return $this->currentContents; 02538 } 02539 02540 /** 02541 * output the pdf code, streaming it to the browser 02542 * the relevant headers are set so that hopefully the browser will recognise it 02543 */ 02544 function stream( $options = '' ) 02545 { 02546 // setting the options allows the adjustment of the headers 02547 // values at the moment are: 02548 // 'Content-Disposition'=>'filename' - sets the filename, though not too sure how well this will 02549 // work as in my trial the browser seems to use the filename of the php file with .pdf on the end 02550 // 'Accept-Ranges'=>1 or 0 - if this is not set to 1, then this header is not included, off by default 02551 // this header seems to have caused some problems despite tha fact that it is supposed to solve 02552 // them, so I am leaving it off by default. 02553 // 'compress'=> 1 or 0 - apply content stream compression, this is on (1) by default 02554 if ( !is_array( $options ) ) 02555 { 02556 $options = array(); 02557 } 02558 if ( isset( $options['compress'] ) && $options['compress'] == 0 ) 02559 { 02560 $tmp = $this->output( 1 ); 02561 } 02562 else 02563 { 02564 $tmp = $this->output(); 02565 } 02566 $tmp = ltrim( $tmp ); 02567 02568 ob_clean(); 02569 02570 header( 'Pragma: ' ); 02571 header( 'Cache-Control: ' ); 02572 /* Set cache time out to 10 seconds, this should be good enough to work around an IE bug */ 02573 header( "Expires: ". gmdate( 'D, d M Y H:i:s', time() + 10 ) . ' GMT' ); 02574 header( 'X-Powered-By: eZ Publish' ); 02575 02576 header( 'Content-Length: '.strlen( $tmp ) ); 02577 header( 'Content-Type: application/pdf' ); 02578 header( 'Content-Transfer-Encoding: binary' ); 02579 header( 'Accept-Ranges: bytes' ); 02580 02581 ob_end_clean(); 02582 02583 echo $tmp; 02584 02585 eZExecution::cleanExit(); 02586 } 02587 02588 /** 02589 * return the height in units of the current font in the given size 02590 */ 02591 function getFontHeight( $size = false ) 02592 { 02593 if ( !$this->numFonts ) 02594 { 02595 $this->selectFont( './fonts/Helvetica' ); 02596 } 02597 // for the current font, and the given size, what is the height of the font in user units 02598 $h = $this->fonts[$this->currentFont]['FontBBox'][3]-$this->fonts[$this->currentFont]['FontBBox'][1]; 02599 if ( $size === false ) 02600 { 02601 $size = $this->fontSize(); 02602 } 02603 return $this->ez['lineSpace'] * $size * $h / 1000; 02604 } 02605 02606 /** 02607 * return the font decender, this will normally return a negative number 02608 * if you add this number to the baseline, you get the level of the bottom of the font 02609 * it is in the pdf user units 02610 */ 02611 function getFontDecender( $size = false ) 02612 { 02613 // note that this will most likely return a negative value 02614 if ( !$this->numFonts ) 02615 { 02616 $this->selectFont( './fonts/Helvetica' ); 02617 } 02618 $h = $this->fonts[$this->currentFont]['FontBBox'][1]; 02619 if ( $size === false ) 02620 { 02621 $size = $this->fontSize(); 02622 } 02623 return $size*$h/1000; 02624 } 02625 02626 /** 02627 * filter the text, this is applied to all text just before being inserted into the pdf document 02628 * it escapes the various things that need to be escaped, and so on 02629 * 02630 * @access private 02631 */ 02632 function filterText( $text ) 02633 { 02634 $text = str_replace('\\','\\\\',$text); 02635 $text = str_replace('(','\(',$text); 02636 $text = str_replace(')','\)',$text); 02637 $text = str_replace('<','<',$text); 02638 $text = str_replace('>','>',$text); 02639 $text = str_replace(''','\'',$text); 02640 $text = str_replace('"','"',$text); 02641 $text = str_replace('&','&',$text); 02642 02643 return $text; 02644 } 02645 02646 /** 02647 * given a start position and information about how text is to be laid out, calculate where 02648 * on the page the text will end 02649 * 02650 * @access private 02651 */ 02652 function PRVTgetTextPosition( $x, $y, $angle, $size, $wa, $text ) 02653 { 02654 $tmp = false; 02655 // given this information return an array containing x and y for the end position as elements 0 and 1 02656 02657 $w = $this->getTextWidth($size,$text); 02658 // need to adjust for the number of spaces in this text 02659 $words = explode(' ',$text); 02660 $nspaces=count($words)-1; 02661 $w += $wa*$nspaces; 02662 $a = deg2rad((float)$angle); 02663 if ( $tmp ) 02664 { 02665 return array($this->xOffset(),-sin($a)*$w+$y); 02666 } 02667 return array(cos($a)*$w+$x,-sin($a)*$w+$y); 02668 } 02669 02670 /** 02671 * wrapper function for PRVTcheckTextDirective1 02672 * 02673 * @access private 02674 */ 02675 function PRVTcheckTextDirective( &$text, $i, &$f, $final = 0 ) 02676 { 02677 $x = 0; 02678 $y = 0; 02679 return $this->PRVTcheckTextDirective1( $text, $i, $f, $final, $x, $y ); 02680 } 02681 02682 /** 02683 * checks if the text stream contains a control directive 02684 * if so then makes some changes and returns the number of characters involved in the directive 02685 * this has been re-worked to include everything neccesary to fins the current writing point, so that 02686 * the location can be sent to the callback function if required 02687 * if the directive does not require a font change, then $f should be set to 0 02688 * 02689 * @access private 02690 */ 02691 function PRVTcheckTextDirective1( &$text, $i, &$f, $final, &$x, &$y, $size = 0, $angle = 0, $wordSpaceAdjust = 0 ) 02692 { 02693 $newTextState = $this->currentTextState; 02694 $noClose = 0; 02695 $directive = 0; 02696 $j = $i; 02697 if ( $text[$j] == '<' ) 02698 { 02699 $j++; 02700 switch ( $text[$j] ) 02701 { 02702 case '/': 02703 $j++; 02704 if (strlen($text) <= $j) 02705 { 02706 return $directive; 02707 } 02708 switch ( $text[$j] ) 02709 { 02710 case 'b': 02711 case 'i': 02712 ++$j; 02713 if ( $text[$j] == '>' ) 02714 { 02715 if ( $final === true ) 02716 $this->currentTextState = substr( $this->currentTextState, 0, $p).substr( $this->currentTextState, $p+1); 02717 $directive = $j-$i+1; 02718 } 02719 break; 02720 02721 case 'c': 02722 { 02723 // this this might be a callback function 02724 $j++; 02725 $k = strpos( $text, '>', $j ); 02726 if ( $k !== false && $text[$j] == ':' ) 02727 { 02728 // then this will be treated as a callback directive 02729 $directive = $k-$i+1; 02730 $f = 0; 02731 // split the remainder on colons to get the function name and the paramater 02732 $tmp = substr( $text, $j+1, $k-$j-1 ); 02733 $b1 = strpos( $tmp, ':' ); 02734 if ( $b1 !== false ) 02735 { 02736 $func = substr( $tmp, 0, $b1 ); 02737 $parm = substr( $tmp, $b1+1 ); 02738 } 02739 else 02740 { 02741 $func = $tmp; 02742 $parm = ''; 02743 } 02744 if ( !isset( $func ) || !strlen( trim( $func ) ) ) 02745 { 02746 $directive = 0; 02747 } 02748 else 02749 { 02750 // only call the function if this is the final call 02751 if ( $final === 1 ) 02752 { 02753 // need to assess the text position, calculate the text width to this point 02754 // can use getTextWidth to find the text width I think 02755 if ( $x < $this->leftMargin() ) 02756 { 02757 $x = $this->leftMargin(); 02758 } 02759 $startInfo = $this->callback[$this->nCallback]; 02760 02761 if ( $x < $this->xOffset() ) 02762 { 02763 $x = $this->xOffset(); 02764 } 02765 $tmp = $this->PRVTgetTextPosition( $x, $y, $angle, $size, $wordSpaceAdjust, substr( $text, $startInfo['i'], $i-$startInfo['i'] ) ); 02766 $info = array( 'x' => $tmp[0],'y' => $tmp[1], 'angle' => $angle, 'status' => 'end', 'p' => $parm, 'nCallback' => $this->nCallback ); 02767 02768 $x = $tmp[0]; 02769 $y = $tmp[1]; 02770 $ret = $this->$func( $info ); 02771 if ( is_array( $ret ) ) 02772 { 02773 // then the return from the callback function could set the position, to start with, later will do font colour, and font 02774 foreach ( $ret as $rk => $rv ) 02775 { 02776 switch ( $rk ) 02777 { 02778 case 'x': 02779 case 'y': 02780 $$rk = $rv; 02781 break; 02782 } 02783 } 02784 } 02785 // also remove from to the stack 02786 // for simplicity, just take from the end, fix this another day 02787 $this->nCallback--; 02788 if ( $this->nCallback < 0 ) 02789 { 02790 $this->nCallBack = 0; 02791 } 02792 } 02793 } 02794 } 02795 } break; 02796 } 02797 break; 02798 02799 case 'b': 02800 case 'i': 02801 ++$j; 02802 02803 if ( $text[$j] == '>' ) 02804 { 02805 if ( $final === true ) 02806 $this->currentTextState .= $text[$j-1]; 02807 $directive = $j-$i+1; 02808 } 02809 break; 02810 02811 case 'C': 02812 $noClose = 1; 02813 case 'c': 02814 // this this might be a callback function 02815 $j++; 02816 $k = strpos( $text, '>', $j ); 02817 if ( $k !== false && $text[$j] == ':' ) 02818 { 02819 // then this will be treated as a callback directive 02820 $directive = $k-$i+1; 02821 $f=0; 02822 // split the remainder on colons to get the function name and the paramater 02823 $tmp = substr( $text, $j+1, $k-$j-1 ); 02824 $b1 = strpos( $tmp, ':' ); 02825 if ( $b1 !== false ) 02826 { 02827 $func = substr( $tmp, 0, $b1 ); 02828 $parm = substr( $tmp, $b1+1 ); 02829 } 02830 else 02831 { 02832 $func = $tmp; 02833 $parm = ''; 02834 } 02835 if ( !isset( $func ) || !strlen( trim( $func ) ) ) 02836 { 02837 $directive=0; 02838 } 02839 else 02840 { 02841 // only call the function if this is the final call, ie, the one actually doing printing, not measurement 02842 if ($final === 1) 02843 { 02844 // need to assess the text position, calculate the text width to this point 02845 // can use getTextWidth to find the text width I think 02846 // also add the text height and decender 02847 $tmp = $this->PRVTgetTextPosition($x,$y,$angle,$size,$wordSpaceAdjust,substr($text,0,$i)); 02848 if ( $tmp[0] < $this->leftMargin() ) 02849 { 02850 $tmp[0] = $this->leftMargin(); 02851 } 02852 if ( $tmp[0] < $this->xOffset() ) 02853 { 02854 $tmp[0] = $this->xOffset(); 02855 } 02856 $info = array( 'x' => $tmp[0], 02857 'y' => $tmp[1], 02858 'angle' => $angle, 02859 'status' => 'start', 02860 'p' => $parm, 02861 'f' => $func, 02862 'height' => $this->getFontHeight( $size ), 02863 'decender' => $this->getFontDecender( $size ), 02864 'i' => $i ); 02865 02866 $x = $tmp[0]; 02867 $y = $tmp[1]; 02868 if (!isset($noClose) || !$noClose) 02869 { 02870 // only add to the stack if this is a small 'c', therefore is a start-stop pair 02871 $this->nCallback++; 02872 $info['nCallback']=$this->nCallback; 02873 $this->callback[$this->nCallback]=$info; 02874 } 02875 $ret = $this->$func( $info ); 02876 if ( is_array( $ret ) ) 02877 { 02878 // then the return from the callback function could set the position, to start with, later will do font colour, and font 02879 foreach ( $ret as $rk => $rv ) 02880 { 02881 switch ( $rk ) 02882 { 02883 case 'x': 02884 case 'y': 02885 $$rk=$rv; 02886 break; 02887 } 02888 } 02889 } 02890 } 02891 } 02892 } 02893 break; 02894 } 02895 } 02896 return array( 'directive' => $directive, 02897 'y' => (float)$y, 02898 'noClose' => $noClose, 02899 'run_final' => ( $f == 0 ) ); 02900 } 02901 02902 /** 02903 * add text to the document, at a specified location, size and angle on the page 02904 * 02905 * \return array( 'height' => <used height if more than normal text, -1 if not> ) 02906 */ 02907 function addText( $x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0 ) 02908 { 02909 $returnArray = array( 'height' => -1 ); 02910 02911 if (!$this->numFonts) 02912 { 02913 $this->selectFont('./fonts/Helvetica'); 02914 } 02915 02916 // if there are any open callbacks, then they should be called, to show the start of the line 02917 if ($this->nCallback>0) 02918 { 02919 for ( $i = $this->nCallback; $i > 0; $i-- ) 02920 { 02921 // call each function 02922 $info = array( 'x' => $x, 'y' => $y, 02923 'angle' => $angle, 02924 'status' => 'sol', 02925 'p' => $this->callback[$i]['p'], 02926 'nCallback' => $this->callback[$i]['nCallback'], 02927 'height' => $this->callback[$i]['height'], 02928 'decender' => $this->callback[$i]['decender'] ); 02929 $func = $this->callback[$i]['f']; 02930 $this->$func( $info ); 02931 } 02932 } 02933 if ( $angle == 0 ) 02934 { 02935 $this->objects[$this->currentContents]['c'] .= "\n".'BT '.sprintf('%.3F',$x).' '.sprintf('%.3F',$y).' Td'; 02936 } 02937 else 02938 { 02939 $a = deg2rad((float)$angle); 02940 $tmp = "\n".'BT '; 02941 $tmp .= sprintf('%.3F',cos($a)).' '.sprintf('%.3F',(-1.0*sin($a))).' '.sprintf('%.3F',sin($a)).' '.sprintf('%.3F',cos($a)).' '; 02942 $tmp .= sprintf('%.3F',$x).' '.sprintf('%.3F',$y).' Tm'; 02943 $this->objects[$this->currentContents]['c'] .= $tmp; 02944 } 02945 if ($wordSpaceAdjust!=0 || $wordSpaceAdjust != $this->wordSpaceAdjust) 02946 { 02947 $this->wordSpaceAdjust=$wordSpaceAdjust; 02948 $this->objects[$this->currentContents]['c'].=' '.sprintf('%.3F',$wordSpaceAdjust).' Tw'; 02949 } 02950 $len = strlen($text); 02951 $start = 0; 02952 for ( $i = 0; $i < $len; $i++ ) 02953 { 02954 $f = 1; 02955 $directiveArray = $this->PRVTcheckTextDirective( $text, $i, $f, true ); 02956 $directive = $directiveArray['directive']; 02957 if ($directive) 02958 { 02959 // then we should write what we need to 02960 if ( $i > $start ) 02961 { 02962 $part = substr($text,$start,$i-$start); 02963 $this->objects[$this->currentContents]['c'].=' /F'.$this->currentFontNum.' '.sprintf('%.1F',$size).' Tf '; 02964 $this->objects[$this->currentContents]['c'].=' ('.$this->filterText($part).') Tj'; 02965 } 02966 if ( !$directiveArray['run_final'] ) 02967 { 02968 // then there was nothing drastic done here, restore the contents 02969 $this->setCurrentFont(); 02970 } 02971 else 02972 { 02973 $this->objects[$this->currentContents]['c'] .= ' ET'; 02974 $f = 1; 02975 $xp = $x; 02976 $yp = $y; 02977 $directiveArray = $this->PRVTcheckTextDirective1( $text, $i, $f, 1, $xp, $yp, $size, $angle, $wordSpaceAdjust ); 02978 if ( $directiveArray['y'] != 0 ) 02979 { 02980 $returnArray['height'] = $y - $directiveArray['y']; 02981 } 02982 02983 $directive = $directiveArray['directive']; 02984 02985 // restart the text object 02986 if ( $angle == 0 ) 02987 { 02988 $this->objects[$this->currentContents]['c'] .= "\n".'BT '.sprintf('%.3F',$xp).' '.sprintf('%.3F',$yp).' Td'; 02989 } 02990 else 02991 { 02992 $a = deg2rad( (float)$angle ); 02993 $tmp = "\n".'BT '; 02994 $tmp .= sprintf('%.3F',cos($a)).' '.sprintf('%.3F',(-1.0*sin($a))).' '.sprintf('%.3F',sin($a)).' '.sprintf('%.3F',cos($a)).' '; 02995 $tmp .= sprintf('%.3F',$xp).' '.sprintf('%.3F',$yp).' Tm'; 02996 $this->objects[$this->currentContents]['c'] .= $tmp; 02997 } 02998 if ($wordSpaceAdjust!=0 || $wordSpaceAdjust != $this->wordSpaceAdjust) 02999 { 03000 $this->wordSpaceAdjust=$wordSpaceAdjust; 03001 $this->objects[$this->currentContents]['c'] .= ' '.sprintf('%.3F',$wordSpaceAdjust).' Tw'; 03002 } 03003 } 03004 // and move the writing point to the next piece of text 03005 $i = $i + $directive - 1; 03006 $start = $i + 1; 03007 } 03008 03009 } 03010 if ( $start < $len ) 03011 { 03012 $part = substr($text,$start); 03013 $this->objects[$this->currentContents]['c'].=' /F'.$this->currentFontNum.' '.sprintf('%.1F',$size).' Tf '; 03014 $this->objects[$this->currentContents]['c'].=' ('.$this->filterText($part).') Tj'; 03015 } 03016 $this->objects[$this->currentContents]['c'].=' ET'; 03017 03018 // if there are any open callbacks, then they should be called, to show the end of the line 03019 if ( $this->nCallback > 0 ) 03020 { 03021 for ( $i = $this->nCallback; $i > 0; $i-- ) 03022 { 03023 // call each function 03024 $tmp = $this->PRVTgetTextPosition( $x, $y, $angle, $size, $wordSpaceAdjust, $text ); 03025 $info = array('x' => $tmp[0], 'y' => $tmp[1], 03026 'angle' => $angle, 03027 'status' => 'eol', 03028 'p' => $this->callback[$i]['p'], 03029 'nCallback' => $this->callback[$i]['nCallback'], 03030 'height' => $this->callback[$i]['height'], 03031 'decender' => $this->callback[$i]['decender'] ); 03032 $func = $this->callback[$i]['f']; 03033 $this->$func( $info ); 03034 } 03035 } 03036 03037 return $returnArray; 03038 } 03039 03040 /** 03041 * calculate how wide a given text string will be on a page, at a given size. 03042 * this can be called externally, but is alse used by the other class functions 03043 */ 03044 function getTextWidth( $size, $text ) 03045 { 03046 // this function should not change any of the settings, though it will need to 03047 // track any directives which change during calculation, so copy them at the start 03048 // and put them back at the end. 03049 $this->pushTextState( $this->currentTextState ); 03050 03051 if (!$this->numFonts) 03052 { 03053 $this->selectFont('./fonts/Helvetica'); 03054 } 03055 03056 // converts a number or a float to a string so it can get the width 03057 $text = "$text"; 03058 // hmm, this is where it all starts to get tricky - use the font information to 03059 // calculate the width of each character, add them up and convert to user units 03060 $w = 0; 03061 $len = strlen( $text ); 03062 $cf = $this->currentFont; 03063 for ( $i = 0; $i < $len; $i++ ) 03064 { 03065 $f = 1; 03066 $directiveArray = $this->PRVTcheckTextDirective( $text, $i, $f ); 03067 $directive = $directiveArray['directive']; 03068 if ( $directive ) 03069 { 03070 if ($f) 03071 { 03072 $this->setCurrentFont(); 03073 $cf = $this->currentFont; 03074 } 03075 $i = $i + $directive-1; 03076 } 03077 else 03078 { 03079 $char = ord( $text[$i] ); 03080 if ( isset($this->fonts[$cf]['differences'][$char] ) ) 03081 { 03082 // then this character is being replaced by another 03083 $name = $this->fonts[$cf]['differences'][$char]; 03084 if ( isset($this->fonts[$cf]['C'][$name]['WX'] ) ) 03085 { 03086 $w += $this->fonts[$cf]['C'][$name]['WX']; 03087 } 03088 } 03089 else if ( isset($this->fonts[$cf]['C'][$char]['WX'] ) ) 03090 { 03091 $w += $this->fonts[$cf]['C'][$char]['WX']; 03092 } 03093 else 03094 { 03095 $w += 700; 03096 } 03097 } 03098 } 03099 03100 $this->popTextState(); 03101 $this->setCurrentFont(); 03102 03103 return $w*$size/1000; 03104 } 03105 03106 /** 03107 * do a part of the calculation for sorting out the justification of the text 03108 * 03109 * @access private 03110 */ 03111 function PRVTadjustWrapText( $text, $actual, $width, &$x, &$adjust, $justification ) 03112 { 03113 switch ( $justification ) 03114 { 03115 case 'left': 03116 return; 03117 break; 03118 case 'right': 03119 $x += $width - $actual; 03120 break; 03121 case 'center': 03122 case 'centre': 03123 $x += ($width - $actual) / 2; 03124 break; 03125 case 'full': 03126 // count the number of words 03127 $words = explode( ' ', $text ); 03128 $nspaces = count($words) - 1; 03129 if ( $nspaces > 0 ) 03130 { 03131 $adjust = ($width-$actual) / $nspaces; 03132 } 03133 else 03134 { 03135 $adjust = 0; 03136 } 03137 break; 03138 } 03139 } 03140 03141 /** 03142 * add text to the page, but ensure that it fits within a certain width 03143 * if it does not fit then put in as much as possible, splitting at word boundaries 03144 * and return the remainder. 03145 * justification and angle can also be specified for the text 03146 * 03147 * return array ('text' => <text for new line>, 03148 'width' => <width of added text, 0 i more text>, 03149 'height' => <height of added text, -1 if none/default> ) 03150 */ 03151 function addTextWrap( $x, $y, $width, $size, $text, $justification = 'left', $angle = 0, $test = 0 ) 03152 { 03153 // this will display the text, and if it goes beyond the width $width, will backtrack to the 03154 // previous space or hyphen, and return the remainder of the text. 03155 03156 // $justification can be set to 'left','right','center','centre','full' 03157 03158 // need to store the initial text state, as this will change during the width calculation 03159 // but will need to be re-set before printing, so that the chars work out right 03160 $store_currentTextState = $this->currentTextState; 03161 $returnArray = array ( 'text' => '', 03162 'width' => 0, 03163 'height' => 0 ); 03164 if ( $text != '' ) 03165 { 03166 $returnArray['height'] = $this->getFontHeight( $size ); 03167 } 03168 03169 if ( !$this->numFonts ) 03170 { 03171 $this->selectFont('./fonts/Helvetica'); 03172 } 03173 if ( $width <= 0 ) 03174 { 03175 // error, pretend it printed ok, otherwise risking a loop 03176 return $returnArray; 03177 } 03178 $w = 0; 03179 $break = 0; 03180 $breakWidth = 0; 03181 $len = strlen( $text ); 03182 $cf = $this->currentFont; 03183 $tw = $width/$size*1000; 03184 $directive = 0; 03185 for ( $i = 0; $i < $len; $i++ ) 03186 { 03187 $f = 1; 03188 $directiveArray = $this->PRVTcheckTextDirective( $text, $i, $f ); 03189 $deltaDirective = $directiveArray['directive']; 03190 $directive += $deltaDirective; 03191 if ( $directiveArray['y'] != 0 ) 03192 { 03193 $y -= $directiveArray['y']; 03194 $returnArray['height'] = $directiveArray['y']; 03195 } 03196 if ($deltaDirective) 03197 { 03198 if ($f) 03199 { 03200 $this->setCurrentFont(); 03201 $cf = $this->currentFont; 03202 } 03203 $i=$i+$deltaDirective-1; 03204 } 03205 else 03206 { 03207 $cOrd = ord($text[$i]); 03208 if ( isset( $this->fonts[$cf]['differences'][$cOrd] ) ) 03209 { 03210 // then this character is being replaced by another 03211 $cOrd2 = $this->fonts[$cf]['differences'][$cOrd]; 03212 } 03213 else 03214 { 03215 $cOrd2 = $cOrd; 03216 } 03217 03218 if ( isset( $this->fonts[$cf]['C'][$cOrd2]['WX'] ) ) 03219 { 03220 $w += $this->fonts[$cf]['C'][$cOrd2]['WX']; 03221 } 03222 else 03223 { 03224 $w += 700; 03225 } 03226 03227 if ( $w > $tw ) 03228 { 03229 // then we need to truncate this line 03230 if ( $break > 0 ) 03231 { 03232 // then we have somewhere that we can split :) 03233 if ($text[$break] == ' ') 03234 { 03235 $tmp = substr( $text, 0, $break ); 03236 } 03237 else 03238 { 03239 $tmp = substr( $text, 0, $break + 1 ); 03240 } 03241 $adjust = 0; 03242 $this->PRVTadjustWrapText( $tmp, $breakWidth, $width, $x, $adjust, $justification ); 03243 03244 // reset the text state 03245 $this->currentTextState = $store_currentTextState; 03246 $this->setCurrentFont(); 03247 if ( !$test ) 03248 { 03249 $addTextArray = $this->addText( $x, $y, $size, $tmp, $angle, $adjust ); 03250 if ( $addTextArray['height'] != -1 && 03251 $returnArray['height'] < $addTextArray['height'] ) 03252 { 03253 $returnArray['height'] = $addTextArray['height']; 03254 } 03255 } 03256 $returnArray['text'] = substr( $text, $break + 1 ); 03257 return $returnArray; 03258 } 03259 else 03260 { 03261 // just split before the current character 03262 $tmp = substr( $text, 0, $i ); 03263 $adjust = 0; 03264 $ctmp = ord( $text[$i] ); 03265 if ( isset($this->fonts[$cf]['differences'][$ctmp] ) ) 03266 { 03267 $ctmp = $this->fonts[$cf]['differences'][$ctmp]; 03268 } 03269 $tmpw = ( $w - $this->fonts[$cf]['C'][$ctmp]['WX'] ) * $size/1000; 03270 $this->PRVTadjustWrapText( $tmp, $tmpw, $width, $x, $adjust, $justification ); 03271 // reset the text state 03272 $this->currentTextState = $store_currentTextState; 03273 $this->setCurrentFont(); 03274 if ( !$test ) 03275 { 03276 $addTextArray = $this->addText( $x, $y, $size, $tmp, $angle, $adjust ); 03277 if ( $addTextArray['height'] != -1 && 03278 $returnArray['height'] < $addTextArray['height'] ) 03279 { 03280 $returnArray['height'] = $addTextArray['height']; 03281 } 03282 } 03283 $returnArray['text'] = substr( $text, $i ); 03284 return $returnArray; 03285 } 03286 } 03287 03288 if ( $text[$i] == '-' ) 03289 { 03290 $break = $i; 03291 $breakWidth = $w*$size/1000; 03292 } 03293 if ( $text[$i] == ' ' ) 03294 { 03295 $break = $i; 03296 $ctmp = ord( $text[$i] ); 03297 if ( isset($this->fonts[$cf]['differences'][$ctmp] ) ) 03298 { 03299 $ctmp = $this->fonts[$cf]['differences'][$ctmp]; 03300 } 03301 $breakWidth = ( $w - $this->fonts[$cf]['C'][$ctmp]['WX'] ) * $size/1000; 03302 } 03303 } 03304 } 03305 // then there was no need to break this line 03306 if ( $justification == 'full' ) 03307 { 03308 $justification = 'left'; 03309 } 03310 $adjust = 0; 03311 $tmpw = $w * $size/1000; //tmpw, text width 03312 $this->PRVTadjustWrapText( $text, $tmpw, $width, $x, $adjust, $justification ); 03313 // reset the text state 03314 $this->currentTextState = $store_currentTextState; 03315 $this->setCurrentFont(); 03316 if ( !$test ) 03317 { 03318 $addTextArray = $this->addText( $x, $y, $size, $text, $angle, $adjust ); 03319 if ( isset( $directive ) && $directive == $len ) 03320 { 03321 return array( 'only_directive' => true ); 03322 } 03323 03324 if ( $addTextArray['height'] != -1 && 03325 $returnArray['height'] < $addTextArray['height'] ) 03326 { 03327 $returnArray['height'] = $addTextArray['height']; 03328 } 03329 } 03330 $returnArray['width'] = $tmpw; 03331 return $returnArray; 03332 } 03333 03334 /** 03335 * this will be called at a new page to return the state to what it was on the 03336 * end of the previous page, before the stack was closed down 03337 * This is to get around not being able to have open 'q' across pages 03338 * 03339 */ 03340 function saveState( $pageEnd = 0 ) 03341 { 03342 if ( $pageEnd ) 03343 { 03344 // this will be called at a new page to return the state to what it was on the 03345 // end of the previous page, before the stack was closed down 03346 // This is to get around not being able to have open 'q' across pages 03347 $opt = $this->stateStack[$pageEnd]; // ok to use this as stack starts numbering at 1 03348 $this->setColor($opt['col'],1); 03349 $this->setStrokeColor($opt['str'],1); 03350 $this->objects[$this->currentContents]['c'].="\n".$opt['lin']; 03351 //$this->currentLineStyle = $opt['lin']; 03352 } 03353 else 03354 { 03355 $this->nStateStack++; 03356 $this->stateStack[$this->nStateStack]=array( 'col' => $this->currentColour, 03357 'str' => $this->currentStrokeColour, 03358 'lin' => $this->currentLineStyle ); 03359 } 03360 $this->objects[$this->currentContents]['c'] .= "\nq"; 03361 } 03362 03363 /** 03364 * restore a previously saved state 03365 */ 03366 function restoreState( $pageEnd = 0 ) 03367 { 03368 if ( !$pageEnd ) 03369 { 03370 $n = $this->nStateStack; 03371 $this->currentColour = $this->stateStack[$n]['col']; 03372 $this->currentStrokeColour = $this->stateStack[$n]['str']; 03373 $this->objects[$this->currentContents]['c'] .= "\n".$this->stateStack[$n]['lin']; 03374 $this->currentLineStyle = $this->stateStack[$n]['lin']; 03375 unset( $this->stateStack[$n] ); 03376 $this->nStateStack--; 03377 } 03378 $this->objects[$this->currentContents]['c'] .= "\nQ"; 03379 } 03380 03381 /** 03382 * make a loose object, the output will go into this object, until it is closed, then will revert to 03383 * the current one. 03384 * this object will not appear until it is included within a page. 03385 * the function will return the object number 03386 */ 03387 function openObject() 03388 { 03389 $this->nStack++; 03390 $this->stack[$this->nStack] = array( 'c' => $this->currentContents, 03391 'p' => $this->currentPage ); 03392 // add a new object of the content type, to hold the data flow 03393 $this->numObj++; 03394 $this->o_contents( $this->numObj, 'new' ); 03395 $this->currentContents = $this->numObj; 03396 $this->looseObjects[$this->numObj] = 1; 03397 03398 return $this->numObj; 03399 } 03400 03401 /** 03402 * open an existing object for editing 03403 */ 03404 function reopenObject( $id ) 03405 { 03406 $this->nStack++; 03407 $this->stack[$this->nStack] = array( 'c' => $this->currentContents, 03408 'p' => $this->currentPage ); 03409 $this->currentContents=$id; 03410 // also if this object is the primary contents for a page, then set the current page to its parent 03411 if ( isset( $this->objects[$id]['onPage'] ) ) 03412 { 03413 $this->currentPage = $this->objects[$id]['onPage']; 03414 } 03415 } 03416 03417 /** 03418 * close an object 03419 */ 03420 function closeObject() 03421 { 03422 // close the object, as long as there was one open in the first place, which will be indicated by 03423 // an objectId on the stack. 03424 if ( $this->nStack > 0 ) 03425 { 03426 $this->currentContents=$this->stack[$this->nStack]['c']; 03427 $this->currentPage=$this->stack[$this->nStack]['p']; 03428 $this->nStack--; 03429 // easier to probably not worry about removing the old entries, they will be overwritten 03430 // if there are new ones. 03431 } 03432 } 03433 03434 /** 03435 * stop an object from appearing on pages from this point on 03436 */ 03437 function stopObject( $id ) 03438 { 03439 // if an object has been appearing on pages up to now, then stop it, this page will 03440 // be the last one that could contian it. 03441 if ( isset( $this->addLooseObjects[$id] ) ) 03442 { 03443 $this->addLooseObjects[$id] = ''; 03444 } 03445 } 03446 03447 /** 03448 * after an object has been created, it wil only show if it has been added, using this function. 03449 */ 03450 function addObject( $id, $options = 'add' ) 03451 { 03452 // add the specified object to the page 03453 if ( isset( $this->looseObjects[$id] ) && $this->currentContents != $id ) 03454 { 03455 // then it is a valid object, and it is not being added to itself 03456 switch ( $options ) 03457 { 03458 case 'all': 03459 // then this object is to be added to this page (done in the next block) and 03460 // all future new pages. 03461 $this->addLooseObjects[$id] = 'all'; 03462 case 'add': 03463 if ( isset( $this->objects[$this->currentContents]['onPage'] ) ) 03464 { 03465 // then the destination contents is the primary for the page 03466 // (though this object is actually added to that page) 03467 $this->o_page( $this->objects[$this->currentContents]['onPage'], 'content', $id ); 03468 } 03469 break; 03470 case 'even': 03471 $this->addLooseObjects[$id] = 'even'; 03472 $pageObjectId = $this->objects[$this->currentContents]['onPage']; 03473 if ( $this->objects[$pageObjectId]['info']['pageNum'] % 2 == 0 ) 03474 { 03475 $this->addObject( $id ); // hacky huh :) 03476 } 03477 break; 03478 case 'odd': 03479 $this->addLooseObjects[$id] = 'odd'; 03480 $pageObjectId = $this->objects[$this->currentContents]['onPage']; 03481 if ( $this->objects[$pageObjectId]['info']['pageNum'] % 2 == 1 ) 03482 { 03483 $this->addObject( $id ); // hacky huh :) 03484 } 03485 break; 03486 case 'next': 03487 $this->addLooseObjects[$id] = 'all'; 03488 break; 03489 case 'nexteven': 03490 $this->addLooseObjects[$id] = 'even'; 03491 break; 03492 case 'nextodd': 03493 $this->addLooseObjects[$id] = 'odd'; 03494 break; 03495 } 03496 } 03497 } 03498 03499 /** 03500 * add content to the documents info object 03501 */ 03502 function addInfo( $label, $value = 0 ) 03503 { 03504 // this will only work if the label is one of the valid ones. 03505 // modify this so that arrays can be passed as well. 03506 // if $label is an array then assume that it is key=>value pairs 03507 // else assume that they are both scalar, anything else will probably error 03508 if ( is_array( $label ) ) 03509 { 03510 foreach ( $label as $l => $v ) 03511 { 03512 $this->o_info( $this->infoObject, $l, $v ); 03513 } 03514 } 03515 else 03516 { 03517 $this->o_info( $this->infoObject, $label, $value ); 03518 } 03519 } 03520 03521 /** 03522 * set the viewer preferences of the document, it is up to the browser to obey these. 03523 */ 03524 function setPreferences( $label, $value = 0 ) 03525 { 03526 // this will only work if the label is one of the valid ones. 03527 if ( is_array( $label ) ) 03528 { 03529 foreach ( $label as $l => $v ) 03530 { 03531 $this->o_catalog( $this->catalogId, 'viewerPreferences', array( $l => $v ) ); 03532 } 03533 } 03534 else 03535 { 03536 $this->o_catalog( $this->catalogId, 'viewerPreferences', array( $label => $value ) ); 03537 } 03538 } 03539 03540 /** 03541 * extract an integer from a position in a byte stream 03542 * 03543 * @access private 03544 */ 03545 function PRVT_getBytes( &$data, $pos, $num ) 03546 { 03547 // return the integer represented by $num bytes from $pos within $data 03548 $ret = 0; 03549 for ( $i = 0; $i < $num; $i++ ) 03550 { 03551 $ret = $ret * 256; 03552 $ret+= ord( $data[$pos+$i] ); 03553 } 03554 return $ret; 03555 } 03556 03557 /** 03558 * add a PNG image into the document, from a file 03559 * this should work with remote files 03560 * 03561 * \return true if adding image succeded 03562 */ 03563 function addPngFromFile( $file, $x, $y, $w = 0, $h = 0 ) 03564 { 03565 // read in a png file, interpret it, then add to the system 03566 $error = 0; 03567 $tmp = get_magic_quotes_runtime(); 03568 set_magic_quotes_runtime(0); 03569 $fp = @fopen( $file, 'rb' ); 03570 if ( $fp ) 03571 { 03572 $data = ''; 03573 while ( !feof( $fp ) ) 03574 { 03575 $data .= fread( $fp, 1024 ); 03576 } 03577 fclose( $fp ); 03578 } 03579 else 03580 { 03581 $error = 1; 03582 $errormsg = 'trouble opening file: '.$file; 03583 } 03584 set_magic_quotes_runtime( $tmp ); 03585 03586 if ( !$error ) 03587 { 03588 $header = chr(137).chr(80).chr(78).chr(71).chr(13).chr(10).chr(26).chr(10); 03589 if ( substr( $data, 0, 8 ) != $header ) 03590 { 03591 $error = 1; 03592 $errormsg = 'this file does not have a valid header'; 03593 } 03594 } 03595 03596 if ( !$error ) 03597 { 03598 // set pointer 03599 $p = 8; 03600 $len = strlen($data); 03601 // cycle through the file, identifying chunks 03602 $haveHeader = 0; 03603 $info = array(); 03604 $idata = ''; 03605 $pdata = ''; 03606 while ( $p < $len ) 03607 { 03608 $chunkLen = $this->PRVT_getBytes($data,$p,4); 03609 $chunkType = substr($data,$p+4,4); 03610 //echo $chunkType.' - '.$chunkLen.'<br>'; 03611 03612 switch ( $chunkType ) 03613 { 03614 case 'IHDR': 03615 // this is where all the file information comes from 03616 $info['width'] = $this->PRVT_getBytes( $data, $p+8, 4 ); 03617 $info['height'] = $this->PRVT_getBytes( $data, $p+12, 4); 03618 $info['bitDepth'] = ord( $data[$p+16] ); 03619 $info['colorType'] = ord( $data[$p+17] ); 03620 $info['compressionMethod'] = ord( $data[$p+18] ); 03621 $info['filterMethod'] = ord( $data[$p+19] ); 03622 $info['interlaceMethod'] = ord( $data[$p+20] ); 03623 //print_r($info); 03624 $haveHeader = 1; 03625 if ( $info['compressionMethod'] != 0 ) 03626 { 03627 $error = 1; 03628 $errormsg = 'unsupported compression method'; 03629 } 03630 if ( $info['filterMethod'] != 0 ) 03631 { 03632 $error = 1; 03633 $errormsg = 'unsupported filter method'; 03634 } 03635 break; 03636 03637 case 'PLTE': 03638 $pdata .= substr( $data, $p + 8, $chunkLen ); 03639 break; 03640 case 'IDAT': 03641 $idata .= substr( $data, $p + 8, $chunkLen ); 03642 break; 03643 case 'tRNS': 03644 //this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk 03645 //print "tRNS found, color type = ".$info['colorType']."<BR>"; 03646 $transparency = array(); 03647 if ( $info['colorType'] == 3 ) 03648 { 03649 // indexed color, rbg 03650 /* corresponding to entries in the plte chunk 03651 Alpha for palette index 0: 1 byte 03652 Alpha for palette index 1: 1 byte 03653 ...etc... 03654 */ 03655 // there will be one entry for each palette entry. up until the last non-opaque entry. 03656 // set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent) 03657 $transparency['type'] = 'indexed'; 03658 $numPalette = strlen($pdata)/3; 03659 $trans = 0; 03660 for ( $i = $chunkLen; $i >= 0; $i-- ) 03661 { 03662 if ( ord( $data[$p+8+$i] ) == 0 ) 03663 { 03664 $trans = $i; 03665 } 03666 } 03667 $transparency['data'] = $trans; 03668 03669 } 03670 elseif ( $info['colorType'] == 0 ) 03671 { 03672 // grayscale 03673 /* corresponding to entries in the plte chunk 03674 Gray: 2 bytes, range 0 .. (2^bitdepth)-1 03675 */ 03676 //$transparency['grayscale']=$this->PRVT_getBytes($data,$p+8,2); // g = grayscale 03677 $transparency['type'] = 'indexed'; 03678 $transparency['data'] = ord( $data[$p+8+1] ); 03679 03680 } 03681 elseif ( $info['colorType'] == 2) 03682 { 03683 // truecolor 03684 /* corresponding to entries in the plte chunk 03685 Red: 2 bytes, range 0 .. (2^bitdepth)-1 03686 Green: 2 bytes, range 0 .. (2^bitdepth)-1 03687 Blue: 2 bytes, range 0 .. (2^bitdepth)-1 03688 */ 03689 $transparency['r'] = $this->PRVT_getBytes( $data, $p+8, 2 ); // r from truecolor 03690 $transparency['g'] = $this->PRVT_getBytes( $data, $p+10 ,2 ); // g from truecolor 03691 $transparency['b'] = $this->PRVT_getBytes( $data, $p+12 ,2 ); // b from truecolor 03692 } 03693 else 03694 { 03695 //unsupported transparency type 03696 } 03697 // KS End new code 03698 break; 03699 default: 03700 break; 03701 } 03702 03703 $p += $chunkLen+12; 03704 } 03705 03706 if ( !$haveHeader ) 03707 { 03708 $error = 1; 03709 $errormsg = 'information header is missing'; 03710 } 03711 if ( isset( $info['interlaceMethod']) && $info['interlaceMethod'] ) 03712 { 03713 $error = 1; 03714 $errormsg = 'There appears to be no support for interlaced images in pdf.'; 03715 } 03716 } 03717 03718 if ( !$error && $info['bitDepth'] > 8 ) 03719 { 03720 $error = 1; 03721 $errormsg = 'only bit depth of 8 or less is supported'; 03722 } 03723 03724 if ( !$error ) 03725 { 03726 if ( $info['colorType'] != 2 && $info['colorType'] != 0 && $info['colorType'] != 3 ) 03727 { 03728 $error = 1; 03729 $errormsg = 'transparancey alpha channel not supported, transparency only supported for palette images.'; 03730 } 03731 else 03732 { 03733 switch ( $info['colorType'] ) 03734 { 03735 case 3: 03736 $color = 'DeviceRGB'; 03737 $ncolor = 1; 03738 break; 03739 case 2: 03740 $color = 'DeviceRGB'; 03741 $ncolor = 3; 03742 break; 03743 case 0: 03744 $color = 'DeviceGray'; 03745 $ncolor = 1; 03746 break; 03747 } 03748 } 03749 } 03750 if ( $error ) 03751 { 03752 eZDebug::writeError( 'Adding PNG file, '. $file .', to PDF failed: '.$errormsg, 'Cpdf::addPngFromFile' ); 03753 $this->addMessage('PNG error - ('.$file.') '.$errormsg); 03754 return false; 03755 } 03756 if ( $w == 0 ) 03757 { 03758 $w = $h/$info['height'] * $info['width']; 03759 } 03760 if ( $h == 0 ) 03761 { 03762 $h = $w * $info['height'] / $info['width']; 03763 } 03764 //print_r($info); 03765 // so this image is ok... add it in. 03766 $this->numImages++; 03767 $im = $this->numImages; 03768 $label = 'I'.$im; 03769 $this->numObj++; 03770 // $this->o_image($this->numObj,'new',array('label'=>$label,'data'=>$idata,'iw'=>$w,'ih'=>$h,'type'=>'png','ic'=>$info['width'])); 03771 $options = array( 'label' => $label, 03772 'data' => $idata, 03773 'bitsPerComponent' => $info['bitDepth'], 03774 'pdata' => $pdata, 03775 'iw' => $info['width'], 03776 'ih' => $info['height'], 03777 'type' => 'png', 03778 'color' => $color, 03779 'ncolor' => $ncolor ); 03780 03781 if ( isset( $transparency ) ) 03782 { 03783 $options['transparency'] = $transparency; 03784 } 03785 $this->o_image( $this->numObj, 'new', $options ); 03786 03787 $this->objects[$this->currentContents]['c'].="\nq"; 03788 $this->objects[$this->currentContents]['c'].="\n".sprintf('%.3F',$w)." 0 0 ".sprintf('%.3F',$h)." ".sprintf('%.3F',$x)." ".sprintf('%.3F',$y)." cm"; 03789 $this->objects[$this->currentContents]['c'].="\n/".$label.' Do'; 03790 $this->objects[$this->currentContents]['c'].="\nQ"; 03791 } 03792 03793 /** 03794 * add a JPEG image into the document, from a file 03795 */ 03796 function addJpegFromFile( $img, $x, $y, $w = 0, $h = 0 ) 03797 { 03798 // attempt to add a jpeg image straight from a file, using no GD commands 03799 // note that this function is unable to operate on a remote file. 03800 03801 if ( !file_exists( $img ) ) 03802 { 03803 //echo "FILE NOT EXISTS: $img"; 03804 return; 03805 } 03806 03807 $tmp = getimagesize( $img ); 03808 $imageWidth = $tmp[0]; 03809 $imageHeight = $tmp[1]; 03810 03811 if ( isset( $tmp['channels'] ) ) 03812 { 03813 $channels = $tmp['channels']; 03814 } 03815 else 03816 { 03817 $channels = 3; 03818 } 03819 03820 if ( $w <= 0 && $h <= 0 ) 03821 { 03822 $w = $imageWidth; 03823 } 03824 if ( $w == 0 ) 03825 { 03826 $w = $h / $imageHeight * $imageWidth; 03827 } 03828 if ( $h == 0 ) 03829 { 03830 $h = $w * $imageHeight / $imageWidth; 03831 } 03832 03833 $fp = fopen( $img, 'rb' ); 03834 03835 $tmp = get_magic_quotes_runtime(); 03836 set_magic_quotes_runtime( 0 ); 03837 $data = fread( $fp, filesize( $img ) ); 03838 set_magic_quotes_runtime( $tmp ); 03839 03840 fclose( $fp ); 03841 03842 $this->addJpegImage_common( $data, $x, $y, $w, $h, $imageWidth, $imageHeight, $channels ); 03843 } 03844 03845 /** 03846 * add an image into the document, from a GD object 03847 * this function is not all that reliable, and I would probably encourage people to use 03848 * the file based functions 03849 */ 03850 function addImage( &$img, $x, $y, $w = 0, $h = 0, $quality = 75 ) 03851 { 03852 // add a new image into the current location, as an external object 03853 // add the image at $x,$y, and with width and height as defined by $w & $h 03854 03855 // note that this will only work with full colour images and makes them jpg images for display 03856 // later versions could present lossless image formats if there is interest. 03857 03858 // there seems to be some problem here in that images that have quality set above 75 do not appear 03859 // not too sure why this is, but in the meantime I have restricted this to 75. 03860 if ( $quality > 75 ) 03861 { 03862 $quality = 75; 03863 } 03864 03865 // if the width or height are set to zero, then set the other one based on keeping the image 03866 // height/width ratio the same, if they are both zero, then give up :) 03867 $imageWidth = imagesx( $img ); 03868 $imageHeight = imagesy( $img ); 03869 03870 if ( $w <= 0 && $h <= 0 ) 03871 { 03872 return; 03873 } 03874 if ( $w == 0 ) 03875 { 03876 $w = $h / $imageHeight * $imageWidth; 03877 } 03878 if ( $h == 0 ) 03879 { 03880 $h = $w * $imageHeight / $imageWidth; 03881 } 03882 03883 // gotta get the data out of the img.. 03884 03885 // so I write to a temp file, and then read it back.. soo ugly, my apologies. 03886 $tmpDir = '/tmp'; 03887 $tmpName = tempnam( $tmpDir, 'img' ); 03888 imagejpeg( $img, $tmpName, $quality ); 03889 $fp = fopen( $tmpName, 'rb' ); 03890 03891 $tmp = get_magic_quotes_runtime(); 03892 set_magic_quotes_runtime( 0 ); 03893 $fp = @fopen( $tmpName, 'rb' ); 03894 if ( $fp ) 03895 { 03896 $data = ''; 03897 while ( !feof( $fp ) ) 03898 { 03899 $data .= fread( $fp, 1024 ); 03900 } 03901 fclose( $fp ); 03902 } 03903 else 03904 { 03905 $error = 1; 03906 $errormsg = 'trouble opening file'; 03907 } 03908 // $data = fread($fp,filesize($tmpName)); 03909 set_magic_quotes_runtime( $tmp ); 03910 // fclose( $fp ); 03911 unlink( $tmpName ); 03912 $this->addJpegImage_common( $data, $x, $y, $w, $h, $imageWidth, $imageHeight ); 03913 } 03914 03915 /** 03916 * common code used by the two JPEG adding functions 03917 * 03918 * @access private 03919 */ 03920 function addJpegImage_common( &$data, $x, $y, $w = 0, $h = 0, $imageWidth, $imageHeight, $channels = 3 ) 03921 { 03922 // note that this function is not to be called externally 03923 // it is just the common code between the GD and the file options 03924 $this->numImages++; 03925 $im = $this->numImages; 03926 $label = 'I'.$im; 03927 $this->numObj++; 03928 $this->o_image( $this->numObj, 'new', array( 'label' => $label, 03929 'data' => $data, 03930 'iw' => $imageWidth, 03931 'ih' => $imageHeight, 03932 'channels' => $channels ) ); 03933 03934 $this->objects[$this->currentContents]['c'] .= "\nq"; 03935 $this->objects[$this->currentContents]['c'] .= "\n".sprintf('%.3F',$w)." 0 0 ".sprintf('%.3F',$h)." ".sprintf('%.3F',$x)." ".sprintf('%.3F',$y)." cm"; 03936 $this->objects[$this->currentContents]['c'] .= "\n/".$label.' Do'; 03937 $this->objects[$this->currentContents]['c'] .= "\nQ"; 03938 } 03939 03940 /** 03941 * specify where the document should open when it first starts 03942 */ 03943 function openHere( $style, $a = 0, $b = 0, $c = 0 ) 03944 { 03945 // this function will open the document at a specified page, in a specified style 03946 // the values for style, and the required paramters are: 03947 // 'XYZ' left, top, zoom 03948 // 'Fit' 03949 // 'FitH' top 03950 // 'FitV' left 03951 // 'FitR' left,bottom,right 03952 // 'FitB' 03953 // 'FitBH' top 03954 // 'FitBV' left 03955 $this->numObj++; 03956 $this->o_destination( $this->numObj, 'new', array( 'page' => $this->currentPage, 03957 'type' => $style, 03958 'p1' => $a, 'p2' => $b, 'p3' => $c ) ); 03959 $id = $this->catalogId; 03960 $this->o_catalog( $id, 'openHere', $this->numObj ); 03961 } 03962 03963 /** 03964 * create a labelled destination within the document 03965 */ 03966 function addDestination($label,$style,$a=0,$b=0,$c=0) 03967 { 03968 // associates the given label with the destination, it is done this way so that a destination can be specified after 03969 // it has been linked to 03970 // styles are the same as the 'openHere' function 03971 $this->numObj++; 03972 $this->o_destination( $this->numObj, 'new', array( 'page' => $this->currentPage, 03973 'type' => $style, 03974 'p1' => $a, 'p2' => $b, 'p3' => $c ) ); 03975 $id = $this->numObj; 03976 // store the label->idf relationship, note that this means that labels can be used only once 03977 $this->destinations["$label"] = $id; 03978 } 03979 03980 /** 03981 * define font families, this is used to initialize the font families for the default fonts 03982 * and for the user to add new ones for their fonts. The default bahavious can be overridden should 03983 * that be desired. 03984 */ 03985 function setFontFamily($family,$options='') 03986 { 03987 if (!is_array($options)) 03988 { 03989 if ($family=='init') 03990 { 03991 // set the known family groups 03992 // these font families will be used to enable bold and italic markers to be included 03993 // within text streams. html forms will be used... <b></b> <i></i> 03994 $this->fontFamilies['Helvetica'] = array( 03995 'b'=>'Helvetica-Bold' 03996 ,'i'=>'Helvetica-Oblique' 03997 ,'bi'=>'Helvetica-BoldOblique' 03998 ,'ib'=>'Helvetica-BoldOblique' 03999 ); 04000 $this->fontFamilies['Courier'] = array( 04001 'b'=>'Courier-Bold' 04002 ,'i'=>'Courier-Oblique' 04003 ,'bi'=>'Courier-BoldOblique' 04004 ,'ib'=>'Courier-BoldOblique' 04005 ); 04006 $this->fontFamilies['Times-Roman'] = array( 04007 'b'=>'Times-Bold' 04008 ,'i'=>'Times-Italic' 04009 ,'bi'=>'Times-BoldItalic' 04010 ,'ib'=>'Times-BoldItalic' 04011 ); 04012 } 04013 } 04014 else 04015 { 04016 // the user is trying to set a font family 04017 // note that this can also be used to set the base ones to something else 04018 if (strlen($family)) 04019 { 04020 $this->fontFamilies[$family] = $options; 04021 } 04022 } 04023 } 04024 04025 /** 04026 * used to add messages for use in debugging 04027 */ 04028 function addMessage( $message ) 04029 { 04030 $this->messages .= $message."\n"; 04031 } 04032 04033 /** 04034 * a few functions which should allow the document to be treated transactionally. 04035 */ 04036 function transaction( $action ) 04037 { 04038 switch ( $action ) 04039 { 04040 case 'start': 04041 // store all the data away into the checkpoint variable 04042 $data = get_object_vars( $this ); 04043 $this->checkpoint = $data; 04044 unset( $data ); 04045 break; 04046 case 'commit': 04047 if ( is_array( $this->checkpoint ) && isset( $this->checkpoint['checkpoint'] ) ) 04048 { 04049 $tmp = $this->checkpoint['checkpoint']; 04050 $this->checkpoint = $tmp; 04051 unset( $tmp ); 04052 } 04053 else 04054 { 04055 $this->checkpoint = ''; 04056 } 04057 break; 04058 case 'rewind': 04059 // do not destroy the current checkpoint, but move us back to the state then, so that we can try again 04060 if ( is_array( $this->checkpoint ) ) 04061 { 04062 // can only abort if were inside a checkpoint 04063 $tmp = $this->checkpoint; 04064 foreach ( $tmp as $k => $v ) 04065 { 04066 if ( $k != 'checkpoint' ) 04067 { 04068 $this->$k = $v; 04069 } 04070 } 04071 unset($tmp); 04072 } 04073 break; 04074 case 'abort': 04075 if ( is_array( $this->checkpoint ) ) 04076 { 04077 // can only abort if were inside a checkpoint 04078 $tmp = $this->checkpoint; 04079 foreach ( $tmp as $k => $v ) 04080 { 04081 $this->$k = $v; 04082 } 04083 unset($tmp); 04084 } 04085 break; 04086 } 04087 04088 } 04089 04090 /*! 04091 \static 04092 04093 \param new cmyk array 1 04094 \param new cmyk array 2 04095 04096 \return true if equal, false if not 04097 */ 04098 function compareCMYK( $cmykArray1, $cmykArray2 ) 04099 { 04100 return ( $cmykArray1['c'] == $cmykArray2['c'] && 04101 $cmykArray1['m'] == $cmykArray2['m'] && 04102 $cmykArray1['y'] == $cmykArray2['y'] && 04103 $cmykArray1['k'] == $cmykArray2['k'] ); 04104 } 04105 04106 /*! 04107 Push current text state, and set specified to current 04108 04109 \param new text state 04110 */ 04111 function pushTextState( $newState ) 04112 { 04113 $this->textStateStack[] = $this->currentTextState; 04114 $this->currentTextState = $newState; 04115 $this->setCurrentFont(); 04116 } 04117 04118 /*! 04119 Pop text stack, and set to previous state. 04120 */ 04121 function popTextState( ) 04122 { 04123 $this->currentTextState = array_pop( $this->textStateStack ); 04124 $this->setCurrentFont(); 04125 } 04126 04127 public $textStateStack = array( '' ); 04128 } // end of class 04129 04130 ?>