eZ Publish  [trunk]
class.pdf.php
Go to the documentation of this file.
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('&lt;','<',$text);
02638         $text = str_replace('&gt;','>',$text);
02639         $text = str_replace('&#039;','\'',$text);
02640         $text = str_replace('&quot;','"',$text);
02641         $text = str_replace('&amp;','&',$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 ?>