Source for file class.pdf.php
Documentation is available at class.pdf.php
* http://www.ros.co.nz/pdf
* A PHP class to provide the basic functionality to create a pdf document without
* any requirement for additional modules.
* Note that they companion class CezPdf can be used to extend this class and dramatically
* simplify the creation of documents.
* there is no warranty, implied or otherwise with this software.
* This code has been placed in the Public Domain for all to enjoy.
* @author Wayne Munro <pdf@ros.co.nz>
* the current number of pdf objects in the document
* this array contains all of the pdf objects, ready for final assembly
* the objectId (number within the objects array) of the document catalog
* array carrying information about the fonts that the system currently knows about
* used to ensure that a font is not loaded twice, among other things
* a record of the current font
* the number of the current font within the font array
* object number of the current page
* object number of the currently active contents block
* number of fonts within the system
* current colour for fill operations, defaults to inactive value, all three components should be between 0 and 1 inclusive when active
* current colour for stroke operations (lines etc.)
* current style that lines are drawn in
* an array which is used to save the state of the document, mainly the colours and styles
* it is used to temporarily change to another state, the change back to what it was before
* number of elements within the state stack
* number of page objects within the document
* object Id storage stack
* number of elements within the object Id storage stack
* an array which contains information about the objects which are not firmly attached to pages
* these have been added with the addObject function
* array contains infomation about how the loose objects are to be added to the document
* the objectId of the information object for the document
* this contains authorship, title etc.
* number of images being tracked within the document
* an array containing options about the document
* it defaults to turning on the compression of the objects
* the objectId of the first page of the document
* used to track the last used value of the inter-word spacing, this is so that it is known
* when the spacing is changed.
* the object Id of the procset object
* store the information about the relationship between font families
* this used so that the code knows which font is the bold version of another font, etc.
* the value of this array is initialised in the constuctor function.
* track if the current font is bolded or italicised
* messages are stored here during processing, these can be selected afterwards to give some useful debug information
* the ancryption array for the document encryption is stored here
* the object Id of the encryption information
* the file identifier, used to uniquely identify a pdf document
* a flag to say if a document is to be encrypted or not
* the ancryption key for the encryption of all the document content (structure is not encrypted)
* array which forms a stack to keep track of nested callback functions
* the number of callback functions in the callback array
* store label->id pairs for named destinations, these will be used to replace internal links
* done this way so that destinations can be defined after the location that links to them
* store the stack for the transaction commands, each item in here is a record of the values of all the
* variables within the class, so that the user can rollback at will (from each 'start' command)
* note that this includes the objects array, so these can be large.
* this will start a new document
* @var array array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
function Cpdf ($pageSize=
array(0,0,612,792)){
$this->newDocument($pageSize);
// also initialize the font families that are known about already
// $this->fileIdentifier = md5('xxxxxxxx'.time());
* Document object methods (internal use only)
* There is about one object method for each type of object in the pdf document
* Each function has the same call list ($id,$action,$options).
* $id = the object ID of the object, or what it is to be if it is being created
* $action = a string specifying the action to be performed, though ALL must support:
* 'new' - create the object with the id $id
* 'out' - produce the output for the pdf object
* $options = optional, a string or array containing the various parameters for the object
* These, in conjunction with the output function are the ONLY way for output to be produced
*destination object, used to specify the location for the user to jump to, presently on opening
$this->objects[$id]=
array('t'=>
'destination','info'=>
array());
switch ($options['type']){
$tmp =
' '.
$options['p3'].
$tmp;
$tmp =
' '.
$options['p1'].
' '.
$options['p2'].
$tmp;
$tmp =
$options['type'].
$tmp;
$this->objects[$id]['info']['string']=
$tmp;
$this->objects[$id]['info']['page']=
$options['page'];
$res=
"\n".
$id.
" 0 obj\n".
'['.
$tmp['page'].
' 0 R /'.
$tmp['string'].
"]\nendobj\n";
* set the viewer preferences
$this->objects[$id]=
array('t'=>
'viewerPreferences','info'=>
array());
foreach($options as $k=>
$v){
case 'NonFullScreenPageMode':
$res=
"\n".
$id.
" 0 obj\n".
'<< ';
foreach($o['info'] as $k=>
$v){
* define the document catalog, the overall controller for the document
$this->objects[$id]=
array('t'=>
'catalog','info'=>
array());
$o['info'][$action]=
$options;
case 'viewerPreferences':
if (!isset
($o['info']['viewerPreferences'])){
$o['info']['viewerPreferences']=
$this->numObj;
$vp =
$o['info']['viewerPreferences'];
$res=
"\n".
$id.
" 0 obj\n".
'<< /Type /Catalog';
foreach($o['info'] as $k=>
$v){
$res.=
"\n".
'/Outlines '.
$v.
' 0 R';
$res.=
"\n".
'/Pages '.
$v.
' 0 R';
case 'viewerPreferences':
$res.=
"\n".
'/ViewerPreferences '.
$o['info']['viewerPreferences'].
' 0 R';
$res.=
"\n".
'/OpenAction '.
$o['info']['openHere'].
' 0 R';
* object which is a parent to the pages in the document
function o_pages($id,$action,$options=
''){
$this->objects[$id]=
array('t'=>
'pages','info'=>
array());
// then it will just be the id of the new page
$o['info']['pages'][]=
$options;
// then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
// and pos is either 'before' or 'after', saying where this page will fit.
if (isset
($options['id']) && isset
($options['rid']) && isset
($options['pos'])){
if (isset
($o['info']['pages'][$i]) &&
$o['info']['pages'][$i]==
$options['rid']){
switch ($options['pos']){
for ($j=
count($o['info']['pages'])-
1;$j>=
$k;$j--
){
$o['info']['pages'][$j+
1]=
$o['info']['pages'][$j];
$o['info']['pages'][$k]=
$options['id'];
$o['info']['procset']=
$options;
$o['info']['mediaBox']=
$options; // which should be an array of 4 numbers
$o['info']['fonts'][]=
array('objNum'=>
$options['objNum'],'fontNum'=>
$options['fontNum']);
$o['info']['xObjects'][]=
array('objNum'=>
$options['objNum'],'label'=>
$options['label']);
if (count($o['info']['pages'])){
$res=
"\n".
$id.
" 0 obj\n<< /Type /Pages\n/Kids [";
foreach($o['info']['pages'] as $k=>
$v){
$res.=
"]\n/Count ".
count($this->objects[$id]['info']['pages']);
if ((isset
($o['info']['fonts']) &&
count($o['info']['fonts'])) || isset
($o['info']['procset'])){
if (isset
($o['info']['procset'])){
$res.=
"\n/ProcSet ".
$o['info']['procset'].
" 0 R";
if (isset
($o['info']['fonts']) &&
count($o['info']['fonts'])){
foreach($o['info']['fonts'] as $finfo){
$res.=
"\n/F".
$finfo['fontNum'].
" ".
$finfo['objNum'].
" 0 R";
if (isset
($o['info']['xObjects']) &&
count($o['info']['xObjects'])){
foreach($o['info']['xObjects'] as $finfo){
$res.=
"\n/".
$finfo['label'].
" ".
$finfo['objNum'].
" 0 R";
if (isset
($o['info']['mediaBox'])){
$tmp=
$o['info']['mediaBox'];
$res=
"\n".
$id.
" 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
* define the outlines in the doc, empty for now
$this->objects[$id]=
array('t'=>
'outlines','info'=>
array('outlines'=>
array()));
$o['info']['outlines'][]=
$options;
if (count($o['info']['outlines'])){
$res=
"\n".
$id.
" 0 obj\n<< /Type /Outlines /Kids [";
foreach($o['info']['outlines'] as $k=>
$v){
$res.=
"] /Count ".
count($o['info']['outlines']).
" >>\nendobj";
$res=
"\n".
$id.
" 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
* an object to hold the font description
function o_font($id,$action,$options=
''){
$this->objects[$id]=
array('t'=>
'font','info'=>
array('name'=>
$options['name'],'SubType'=>
'Type1'));
$this->objects[$id]['info']['fontNum']=
$fontNum;
// deal with the encoding and the differences
if (isset
($options['differences'])){
// then we'll need an encoding dictionary
} else if (isset
($options['encoding'])){
// we can specify encoding here
switch($options['encoding']){
case 'MacExpertEncoding':
$this->objects[$id]['info']['encoding']=
$options['encoding'];
$this->objects[$id]['info']['encoding']=
'WinAnsiEncoding';
$this->objects[$id]['info']['encoding']=
'WinAnsiEncoding';
// also tell the pages node about the new font
foreach ($options as $k=>
$v){
$res=
"\n".
$id.
" 0 obj\n<< /Type /Font\n/Subtype /".
$o['info']['SubType'].
"\n";
$res.=
"/Name /F".
$o['info']['fontNum'].
"\n";
$res.=
"/BaseFont /".
$o['info']['name'].
"\n";
if (isset
($o['info']['encodingDictionary'])){
// then place a reference to the dictionary
$res.=
"/Encoding ".
$o['info']['encodingDictionary'].
" 0 R\n";
} else if (isset
($o['info']['encoding'])){
// use the specified encoding
$res.=
"/Encoding /".
$o['info']['encoding'].
"\n";
if (isset
($o['info']['FirstChar'])){
$res.=
"/FirstChar ".
$o['info']['FirstChar'].
"\n";
if (isset
($o['info']['LastChar'])){
$res.=
"/LastChar ".
$o['info']['LastChar'].
"\n";
if (isset
($o['info']['Widths'])){
$res.=
"/Widths ".
$o['info']['Widths'].
" 0 R\n";
if (isset
($o['info']['FontDescriptor'])){
$res.=
"/FontDescriptor ".
$o['info']['FontDescriptor'].
" 0 R\n";
* a font descriptor, needed for including additional fonts
$this->objects[$id]=
array('t'=>
'fontDescriptor','info'=>
$options);
$res=
"\n".
$id.
" 0 obj\n<< /Type /FontDescriptor\n";
foreach ($o['info'] as $label =>
$value){
$res.=
'/'.
$label.
' '.
$value.
"\n";
$res.=
'/'.
$label.
' '.
$value.
" 0 R\n";
$res.=
'/'.
$label.
' ['.
$value[0].
' '.
$value[1].
' '.
$value[2].
' '.
$value[3].
"]\n";
$res.=
'/'.
$label.
' /'.
$value.
"\n";
// the options array should contain 'differences' and maybe 'encoding'
$this->objects[$id]=
array('t'=>
'fontEncoding','info'=>
$options);
$res=
"\n".
$id.
" 0 obj\n<< /Type /Encoding\n";
if (!isset
($o['info']['encoding'])){
$o['info']['encoding']=
'WinAnsiEncoding';
if ($o['info']['encoding']!=
'none'){
$res.=
"/BaseEncoding /".
$o['info']['encoding'].
"\n";
$res.=
"/Differences \n[";
foreach($o['info']['differences'] as $num=>
$label){
// we cannot make use of consecutive numbering
$res.=
"\n".
$num.
" /".
$label;
* the document procset, solves some problems with printing to old PS printers
$this->objects[$id]=
array('t'=>
'procset','info'=>
array('PDF'=>
1,'Text'=>
1));
// this is to add new items to the procset list, despite the fact that this is considered
// obselete, the items are required for printing to some postscript printers
$res=
"\n".
$id.
" 0 obj\n[";
foreach ($o['info'] as $label=>
$val){
* define the document information
function o_info($id,$action,$options=
''){
$this->objects[$id]=
array('t'=>
'info','info'=>
array('Creator'=>
'R and OS php pdf writer, http://www.ros.co.nz','CreationDate'=>
$date));
$o['info'][$action]=
$options;
$res=
"\n".
$id.
" 0 obj\n<<\n";
foreach ($o['info'] as $k=>
$v){
$res.=
$this->filterText($this->ARC4($v));
$res.=
$this->filterText($v);
* an action object, used to link to URLS initially
function o_action($id,$action,$options=
''){
$this->objects[$id]=
array('t'=>
'action','info'=>
$options,'type'=>
$options['type']);
// then assume a URI action
$this->objects[$id]=
array('t'=>
'action','info'=>
$options,'type'=>
'URI');
$res=
"\n".
$id.
" 0 obj\n<< /Type /Action";
// there will be an 'label' setting, this is the name of the destination
$res.=
"\n/S /GoTo\n/D ".
$this->destinations[(string)
$o['info']['label']].
" 0 R";
$res.=
"\n/S /URI\n/URI (";
$res.=
$this->filterText($this->ARC4($o['info']));
$res.=
$this->filterText($o['info']);
* an annotation object, this will add an annotation to the current page.
* initially will support just link annotations
// add the annotation to the current page
$this->o_page($pageId,'annot',$id);
// and add the action object which is going to be required
switch($options['type']){
$this->objects[$id]=
array('t'=>
'annotation','info'=>
$options);
// this is to a named internal link
$label =
$options['label'];
$this->objects[$id]=
array('t'=>
'annotation','info'=>
$options);
$this->o_action($this->numObj,'new',array('type'=>
'ilink','label'=>
$label));
$res=
"\n".
$id.
" 0 obj\n<< /Type /Annot";
switch($o['info']['type']){
$res.=
"\n/Subtype /Link";
$res.=
"\n/A ".
$o['info']['actionId'].
" 0 R";
$res.=
"\n/Border [0 0 0]";
foreach($o['info']['rect'] as $v){
* a page object, it also creates a contents object to hold its contents
function o_page($id,$action,$options=
''){
// then this must be a page insertion, array shoudl contain 'rid','pos'=[before|after]
//make a contents object to go with this page
$this->objects[$id]['info']['contents']=
array();
$match =
($this->numPages%
2 ?
'odd' :
'even');
if ($target==
'all' ||
$match==
$target){
$this->objects[$id]['info']['contents'][]=
$oId;
$o['info']['contents'][]=
$options;
// add an annotation to this page
if (!isset
($o['info']['annot'])){
$o['info']['annot']=
array();
// $options should contain the id of the annotation dictionary
$o['info']['annot'][]=
$options;
$res=
"\n".
$id.
" 0 obj\n<< /Type /Page";
$res.=
"\n/Parent ".
$o['info']['parent'].
" 0 R";
if (isset
($o['info']['annot'])){
foreach($o['info']['annot'] as $aId){
$count =
count($o['info']['contents']);
$res.=
"\n/Contents ".
$o['info']['contents'][0].
" 0 R";
foreach ($o['info']['contents'] as $cId){
* the contents objects hold all of the content which appears on pages
$this->objects[$id]=
array('t'=>
'contents','c'=>
'','info'=>
array());
// then this contents is the primary for a page
$this->objects[$id]['onPage']=
$options;
} else if ($options==
'raw'){
// then this page contains some other type of system object
// add more options to the decleration
foreach ($options as $k=>
$v){
$res=
"\n".
$id.
" 0 obj\n";
if (isset
($this->objects[$id]['raw'])){
// then implement ZLIB based compression on this content stream
$res.=
" /Filter /FlateDecode";
$tmp =
$this->ARC4($tmp);
foreach($o['info'] as $k=>
$v){
$res.=
"\n/Length ".
strlen($tmp).
" >>\nstream\n".
$tmp.
"\nendstream";
* an image object, will be an XObject in the document, includes description and data
function o_image($id,$action,$options=
''){
$this->objects[$id]=
array('t'=>
'image','data'=>
$options['data'],'info'=>
array());
$this->objects[$id]['info']['Type']=
'/XObject';
$this->objects[$id]['info']['Subtype']=
'/Image';
$this->objects[$id]['info']['Width']=
$options['iw'];
$this->objects[$id]['info']['Height']=
$options['ih'];
if (!isset
($options['type']) ||
$options['type']==
'jpg'){
if (!isset
($options['channels'])){
switch($options['channels']){
$this->objects[$id]['info']['ColorSpace']=
'/DeviceGray';
$this->objects[$id]['info']['ColorSpace']=
'/DeviceRGB';
$this->objects[$id]['info']['Filter']=
'/DCTDecode';
$this->objects[$id]['info']['BitsPerComponent']=
8;
} else if ($options['type']==
'png'){
$this->objects[$id]['info']['Filter']=
'/FlateDecode';
$this->objects[$id]['info']['DecodeParms']=
'<< /Predictor 15 /Colors '.
$options['ncolor'].
' /Columns '.
$options['iw'].
' /BitsPerComponent '.
$options['bitsPerComponent'].
'>>';
if (strlen($options['pdata'])){
$tmp =
' [ /Indexed /DeviceRGB '.
(strlen($options['pdata'])/
3-
1).
' ';
$this->objects[$id]['info']['ColorSpace'] =
$tmp;
if (isset
($options['transparency'])){
switch($options['transparency']['type']){
$tmp=
' [ '.
$options['transparency']['data'].
' '.
$options['transparency']['data'].
'] ';
$this->objects[$id]['info']['Mask'] =
$tmp;
$this->objects[$id]['info']['ColorSpace']=
'/'.
$options['color'];
$this->objects[$id]['info']['BitsPerComponent']=
$options['bitsPerComponent'];
// assign it a place in the named resource dictionary as an external object, according to
// the label passed in with it.
$this->o_pages($this->currentNode,'xObject',array('label'=>
$options['label'],'objNum'=>
$id));
// also make sure that we have the right procset object for it.
$res=
"\n".
$id.
" 0 obj\n<<";
foreach($o['info'] as $k=>
$v){
$tmp =
$this->ARC4($tmp);
$res.=
"\n/Length ".
strlen($tmp).
" >>\nstream\n".
$tmp.
"\nendstream\nendobj\n";
$this->objects[$id]=
array('t'=>
'encryption','info'=>
$options);
// figure out the additional paramaters required
$len =
strlen($options['owner']);
$owner =
substr($options['owner'],0,32);
$owner =
$options['owner'].
substr($pad,0,32-
$len);
$owner =
$options['owner'];
$len =
strlen($options['user']);
$user =
substr($options['user'],0,32);
$user =
$options['user'].
substr($pad,0,32-
$len);
$user =
$options['user'];
$ovalue=
$this->ARC4($user);
$this->objects[$id]['info']['O']=
$ovalue;
// now make the u value, phew.
$uvalue=
$this->ARC4($pad);
$this->objects[$id]['info']['U']=
$uvalue;
// initialize the arc4 array
$res=
"\n".
$id.
" 0 obj\n<<";
$res.=
"\n/Filter /Standard";
$res.=
"\n/O (".
$this->filterText($o['info']['O']).
')';
$res.=
"\n/U (".
$this->filterText($o['info']['U']).
')';
// and the p-value needs to be converted to account for the twos-complement approach
$o['info']['p'] =
(($o['info']['p']^
255)+
1)*-
1;
$res.=
"\n/P ".
($o['info']['p']);
* A series of function to implement ARC4 encoding in PHP
* calculate the 16 byte version of the 128 bit md5 digest of the string
for ($i=
0;$i<=
30;$i=
$i+
2){
* initialize the encryption for processing a particular object
* initialize the ARC4 encryption
// setup the control array
$j =
($j +
ord($t) +
ord($k[$i]))%
256;
* ARC4 encrypt a text string
$out.=
chr(ord($text[$i]) ^
$k);
* functions which can be called to adjust or add to the document
* add a link in the document to an external URL
function addLink($url,$x0,$y0,$x1,$y1){
$info =
array('type'=>
'link','url'=>
$url,'rect'=>
array($x0,$y0,$x1,$y1));
* add a link in the document to an internal destination (ie. within the document)
$info =
array('type'=>
'ilink','label'=>
$label,'rect'=>
array($x0,$y0,$x1,$y1));
* set the encryption of the document
* can be used to turn it on and/or set the passwords which it will have.
* also the functions that the user will have are set here, such as print, modify, add
if ($v && isset
($options[$k])){
} else if (isset
($options[$v])){
// implement encryption on the document
// then the block does not exist already, add it.
$this->o_encryption($this->numObj,'new',array('user'=>
$userPass,'owner'=>
$ownerPass,'p'=>
$p));
* should be used for internal checks, not implemented as yet
* return the pdf stream as a string returned from the function
$content=
"%PDF-1.3\n%âãÏÓ\n";
// $content="%PDF-1.3\n";
$cont=
$this->$tmp($k,'out');
$content.=
"\nxref\n0 ".
(count($xref)+
1).
"\n0000000000 65535 f \n";
$content.=
substr('0000000000',0,10-
strlen($p)).
$p.
" 00000 n \n";
$content.=
"\ntrailer\n << /Size ".
(count($xref)+
1).
"\n /Root 1 0 R\n /Info ".
$this->infoObject.
" 0 R\n";
// if encryption has been applied to this document then add the marker for this dictionary
$content .=
" >>\nstartxref\n".
$pos.
"\n%%EOF\n";
* intialize a new document
* if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum
* this function is called automatically by the constructor function
function newDocument($pageSize=
array(0,0,612,792)){
// need to store the first page id as there is no way to get it to the user during
* open the font file and return a php structure containing it.
* first check if this one has been done before and saved in a form more suited to php
* note that if a php serialized version does not exist it will try and make one, but will
* require write access to the directory to do it... it is MUCH faster to have these serialized
function openFont($font){
// assume that $font contains both the path and perhaps the extension to the file, split them
//$dir=substr($font,0,$pos+1);
if (substr($name,-
4)==
'.afm'){
$this->addMessage('openFont: '.
$font.
' - '.
$name);
$this->addMessage('openFont: php file exists '.
$dir.
'php_'.
$name.
'.afm');
$tmp =
file($dir.
'php_'.
$name.
'.afm');
if (!isset
($this->fonts[$font]['_version_']) ||
$this->fonts[$font]['_version_']<
1){
// if the font file is old, then clear it out and prepare for re-creation
$this->addMessage('openFont: clear out, make way for new version.');
unset
($this->fonts[$font]);
// then rebuild the php_<font>.afm file from the <font>.afm file
$this->addMessage('openFont: build php file from '.
$dir.
$name.
'.afm');
$file =
file($dir.
$name.
'.afm');
foreach ($file as $rowA){
// then there must be some keyword
case 'UnderlinePosition':
case 'UnderlineThickness':
//C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
$dtmp[$bits2[0]]=
array();
for ($i=
1;$i<
count($bits2);$i++
){
$dtmp[$bits2[0]][]=
$bits2[$i];
} else if (count($bits2)==
2){
$dtmp[$bits2[0]]=
$bits2[1];
$data['C'][$dtmp['C']]=
$dtmp;
$data['C'][$dtmp['N']]=
$dtmp;
$data['C'][$dtmp['N']]=
$dtmp;
//KPX Adieresis yacute -40
$data['KPX'][$bits[1]][$bits[2]]=
$bits[3];
$this->fonts[$font]=
$data;
$fp =
@fopen($dir.
'php_'.
$name.
'.afm','w') or die ("Please make sure your \"media\" directory is writeable (CHMOD 777).");
} else if (!isset
($this->fonts[$font])){
$this->addMessage('openFont: no font file found');
// echo 'Font not Found '.$font;
* if the font is not loaded then load it and make the required object
* else just make it the current font
* the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding'
* note that encoding='none' will need to be used for symbolic fonts
* and 'differences' => an array of mappings between numbers 0->255 and character names.
function selectFont($fontName,$encoding=
'',$set=
1){
if (!isset
($this->fonts[$fontName])){
$this->openFont($fontName);
if (isset
($this->fonts[$fontName])){
// $dir=substr($fontName,0,$pos+1);
$name=
substr($fontName,$pos+
1);
if (substr($name,-
4)==
'.afm'){
$options=
array('name'=>
$name);
// then encoding and differences might be set
if (isset
($encoding['encoding'])){
$options['encoding']=
$encoding['encoding'];
if (isset
($encoding['differences'])){
$options['differences']=
$encoding['differences'];
} else if (strlen($encoding)){
// then perhaps only the encoding has been set
$options['encoding']=
$encoding;
// if this is a '.afm' font, and there is a '.pfa' file to go with it ( as there
// should be for all non-basic fonts), then load it into an object and put the
// references into the font object
$fbfile =
$basefile.
'.'.
$fbtype;
// $pfbfile = substr($fontName,0,strlen($fontName)-4).'.pfb';
// $ttffile = substr($fontName,0,strlen($fontName)-4).'.ttf';
$this->addMessage('selectFont: checking for - '.
$fbfile);
$adobeFontName =
$this->fonts[$fontName]['FontName'];
// $fontObj = $this->numObj;
$this->addMessage('selectFont: adding font file - '.
$fbfile.
' - '.
$adobeFontName);
// find the array of fond widths, and put that into an object.
foreach ($this->fonts[$fontName]['C'] as $num=>
$d){
if (intval($num)>
0 ||
$num==
'0'){
if ($lastChar>
0 &&
$num>
$lastChar+
1){
for($i=
$lastChar+
1;$i<
$num;$i++
){
// also need to adjust the widths for the differences array
if (isset
($options['differences'])){
foreach($options['differences'] as $charNum=>
$charName){
for($i=
$lastChar+
1;$i<=
$charNum;$i++
){
if (isset
($this->fonts[$fontName]['C'][$charName])){
$widths[$charNum-
$firstChar]=
$this->fonts[$fontName]['C'][$charName]['WX'];
$this->addMessage('selectFont: FirstChar='.
$firstChar);
$this->addMessage('selectFont: LastChar='.
$lastChar);
foreach($widths as $width){
// load the pfb file, and put that into an object too.
// note that pdf supports only binary format type 1 font files, though there is a
// simple utility to convert them from pfa to pfb.
$fp =
fopen($fbfile,'rb');
// create the font descriptor
$fontDescriptorId =
$this->numObj;
// determine flags (more than a little flakey, hopefully will not matter much)
if ($this->fonts[$fontName]['ItalicAngle']!=
0){ $flags+=
pow(2,6); }
if ($this->fonts[$fontName]['IsFixedPitch']==
'true'){ $flags+=
1; }
$flags+=
pow(2,5); // assume non-sybolic
$list =
array('Ascent'=>
'Ascender','CapHeight'=>
'CapHeight','Descent'=>
'Descender','FontBBox'=>
'FontBBox','ItalicAngle'=>
'ItalicAngle');
,'FontName'=>
$adobeFontName
,'StemV'=>
100 // don't know what the value for this should be!
foreach($list as $k=>
$v){
if (isset
($this->fonts[$fontName][$v])){
$fdopt[$k]=
$this->fonts[$fontName][$v];
$fdopt['FontFile']=
$pfbid;
} else if ($fbtype==
'ttf'){
$fdopt['FontFile2']=
$pfbid;
// embed the font program
$this->objects[$pfbid]['c'].=
$data;
// determine the cruicial lengths within this file
$l1 =
strpos($data,'eexec')+
6;
$l2 =
strpos($data,'00000000')-
$l1;
$this->o_contents($this->numObj,'add',array('Length1'=>
$l1,'Length2'=>
$l2,'Length3'=>
$l3));
} else if ($fbtype==
'ttf'){
// tell the font object about all this new stuff
$tmp =
array('BaseFont'=>
$adobeFontName,'Widths'=>
$widthid
,'FirstChar'=>
$firstChar,'LastChar'=>
$lastChar
,'FontDescriptor'=>
$fontDescriptorId);
$tmp['SubType']=
'TrueType';
$this->addMessage('adding extra info to font.('.
$fontObj.
')');
foreach($tmp as $fk=>
$fv){
$this->o_font($fontObj,'add',$tmp);
$this->addMessage('selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts');
// also set the differences here, note that this means that these will take effect only the
//first time that a font is selected, else they are ignored
if (isset
($options['differences'])){
$this->fonts[$fontName]['differences']=
$options['differences'];
if ($set && isset
($this->fonts[$fontName])){
// so if for some reason the font was not set in the last one then it will not be selected
// the next line means that if a new font is selected, then the current text state will be
// applied to it as well.
* sets up the current font, based on the font families, and the current text state
* note that this system is quite flexible, a <b><i> font can be completely different to a
* <i><b> font, and even <b><b> will have to be defined within the family to have meaning
* This function is to be called whenever the currentTextState is changed, it will update
* the currentFont setting to whatever the appropriatte family one is.
* If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont
* This function will change the currentFont to whatever it should be, but will not change the
function setCurrentFont(){
// then assume an initial font
// then we are in some state or another
// and this font has a family, and the current setting exists within it
// select the font, then return it
// the this font must not have the right family member for the current state
// simply assume the base font
* function for the user to find out what the ID is of the first page that was created during
* startup - useful if they wish to add something to it later.
* add content to the currently active object
function addContent($content){
* sets the colour for fill operations
* sets the colour for stroke operations
* draw a line from one set of coordinates to another
function line($x1,$y1,$x2,$y2){
* draw a bezier curve based on 4 control points
function curve($x0,$y0,$x1,$y1,$x2,$y2,$x3,$y3){
// in the current line style, draw a bezier curve from (x0,y0) to (x3,y3) using the other two points
// as the control points for the curve.
* draw a part of an ellipse
function partEllipse($x0,$y0,$astart,$afinish,$r1,$r2=
0,$angle=
0,$nSeg=
8){
$this->ellipse($x0,$y0,$r1,$r2,$angle,$nSeg,$astart,$afinish,0);
function filledEllipse($x0,$y0,$r1,$r2=
0,$angle=
0,$nSeg=
8,$astart=
0,$afinish=
360){
return $this->ellipse($x0,$y0,$r1,$r2=
0,$angle,$nSeg,$astart,$afinish,1,1);
* note that the part and filled ellipse are just special cases of this function
* draws an ellipse in the current line style
* centered at $x0,$y0, radii $r1,$r2
* if $r2 is not set, then a circle is drawn
* nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a
* pretty crappy shape at 2, as we are approximating with bezier curves.
function ellipse($x0,$y0,$r1,$r2=
0,$angle=
0,$nSeg=
8,$astart=
0,$afinish=
360,$close=
1,$fill=
0){
$afinish =
deg2rad((float)
$afinish);
$totalAngle =
$afinish-
$astart;
for ($i=
1;$i<=
$nSeg;$i++
){
// draw this bit of the total curve
* this sets the line drawing style.
* width, is the thickness of the line in user units
* cap is the type of cap to put on the line, values can be 'butt','round','square'
* where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the
* join can be 'miter', 'round', 'bevel'
* dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the
* (2) represents 2 on, 2 off, 2 on , 2 off ...
* (2,1) is 2 on, 1 off, 2 on, 1 off.. etc
* phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts.
function setLineStyle($width=
1,$cap=
'',$join=
'',$dash=
'',$phase=
0){
// this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day
$ca =
array('butt'=>
0,'round'=>
1,'square'=>
2);
$string.=
' '.
$ca[$cap].
' J';
$ja =
array('miter'=>
0,'round'=>
1,'bevel'=>
2);
$string.=
' '.
$ja[$join].
' j';
$string.=
' ] '.
$phase.
' d';
* draw a polygon, the syntax for this is similar to the GD polygon command
for ($i=
2;$i<
$np*
2;$i=
$i+
2){
* a filled rectangle, note that it is the width and height of the rectangle which are the secondary paramaters, not
* the coordinates of the upper-right corner
* draw a rectangle, note that it is the width and height of the rectangle which are the secondary paramaters, not
* the coordinates of the upper-right corner
* add a new page to the document
* this also makes the new page the current active object
function newPage($insert=
0,$id=
0,$pos=
'after'){
// if there is a state saved, then go up the stack closing them
// then on the new page, re-open them with the right setings
// the id from the ezPdf class is the od of the contents of the page, not the page object itself
// query that object to find the parent
$rid =
$this->objects[$id]['onPage'];
$opt=
array('rid'=>
$rid,'pos'=>
$pos);
// if there is a stack saved, then put that onto the page
// and if there has been a stroke or fill colour set, then transfer them
// if there is a line style set, then put this in too
// the call to the o_page object set currentContents to the present page, so this can be returned as the page id
* output the pdf code, streaming it to the browser
* the relevant headers are set so that hopefully the browser will recognise it
// setting the options allows the adjustment of the headers
// values at the moment are:
// 'Content-Disposition'=>'filename' - sets the filename, though not too sure how well this will
// work as in my trial the browser seems to use the filename of the php file with .pdf on the end
// 'Accept-Ranges'=>1 or 0 - if this is not set to 1, then this header is not included, off by default
// this header seems to have caused some problems despite tha fact that it is supposed to solve
// them, so I am leaving it off by default.
// 'compress'=> 1 or 0 - apply content stream compression, this is on (1) by default
if ( isset
($options['compress']) &&
$options['compress']==
0){
header("Content-type: application/pdf");
$fileName =
(isset
($options['Content-Disposition'])?
$options['Content-Disposition']:
'file.pdf');
header("Content-Disposition: inline; filename=".
$fileName);
if (isset
($options['Accept-Ranges']) &&
$options['Accept-Ranges']==
1){
* return the height in units of the current font in the given size
// for the current font, and the given size, what is the height of the font in user units
* return the font decender, this will normally return a negative number
* if you add this number to the baseline, you get the level of the bottom of the font
* it is in the pdf user units
// note that this will most likely return a negative value
* filter the text, this is applied to all text just before being inserted into the pdf document
* it escapes the various things that need to be escaped, and so on
function filterText($text){
* given a start position and information about how text is to be laid out, calculate where
* on the page the text will end
function PRVTgetTextPosition($x,$y,$angle,$size,$wa,$text){
// given this information return an array containing x and y for the end position as elements 0 and 1
// need to adjust for the number of spaces in this text
$nspaces=
count($words)-
1;
return array(cos($a)*
$w+
$x,-
sin($a)*
$w+
$y);
* wrapper function for PRVTcheckTextDirective1
function PRVTcheckTextDirective(&$text,$i,&$f){
return $this->PRVTcheckTextDirective1($text,$i,$f,0,$x,$y);
* checks if the text stream contains a control directive
* if so then makes some changes and returns the number of characters involved in the directive
* this has been re-worked to include everything neccesary to fins the current writing point, so that
* the location can be sent to the callback function if required
* if the directive does not require a font change, then $f should be set to 0
function PRVTcheckTextDirective1(&$text,$i,&$f,$final,&$x,&$y,$size=
0,$angle=
0,$wordSpaceAdjust=
0){
// then there is one to remove
// this this might be a callback function
if ($k!==
false &&
$text[$j]==
':'){
// then this will be treated as a callback directive
// split the remainder on colons to get the function name and the paramater
$tmp =
substr($text,$j+
1,$k-
$j-
1);
// only call the function if this is the final call
// need to assess the text position, calculate the text width to this point
// can use getTextWidth to find the text width I think
$tmp =
$this->PRVTgetTextPosition($x,$y,$angle,$size,$wordSpaceAdjust,substr($text,0,$i));
$info =
array('x'=>
$tmp[0],'y'=>
$tmp[1],'angle'=>
$angle,'status'=>
'end','p'=>
$parm,'nCallback'=>
$this->nCallback);
$ret =
$this->$func($info);
// then the return from the callback function could set the position, to start with, later will do font colour, and font
foreach($ret as $rk=>
$rv){
// also remove from to the stack
// for simplicity, just take from the end, fix this another day
// this this might be a callback function
if ($k!==
false &&
$text[$j]==
':'){
// then this will be treated as a callback directive
// split the remainder on colons to get the function name and the paramater
// $bits = explode(':',substr($text,$j+1,$k-$j-1));
$tmp =
substr($text,$j+
1,$k-
$j-
1);
// only call the function if this is the final call, ie, the one actually doing printing, not measurement
// need to assess the text position, calculate the text width to this point
// can use getTextWidth to find the text width I think
// also add the text height and decender
$tmp =
$this->PRVTgetTextPosition($x,$y,$angle,$size,$wordSpaceAdjust,substr($text,0,$i));
$info =
array('x'=>
$tmp[0],'y'=>
$tmp[1],'angle'=>
$angle,'status'=>
'start','p'=>
$parm,'f'=>
$func,'height'=>
$this->getFontHeight($size),'decender'=>
$this->getFontDecender($size));
if (!isset
($noClose) ||
!$noClose){
// only add to the stack if this is a small 'c', therefore is a start-stop pair
$ret =
$this->$func($info);
// then the return from the callback function could set the position, to start with, later will do font colour, and font
foreach($ret as $rk=>
$rv){
* add text to the document, at a specified location, size and angle on the page
function addText($x,$y,$size,$text,$angle=
0,$wordSpaceAdjust=
0){
// if there are any open callbacks, then they should be called, to show the start of the line
$info =
array('x'=>
$x,'y'=>
$y,'angle'=>
$angle,'status'=>
'sol','p'=>
$this->callback[$i]['p'],'nCallback'=>
$this->callback[$i]['nCallback'],'height'=>
$this->callback[$i]['height'],'decender'=>
$this->callback[$i]['decender']);
$directive =
$this->PRVTcheckTextDirective($text,$i,$f);
// then we should write what we need to
$part =
substr($text,$start,$i-
$start);
// then there was nothing drastic done here, restore the contents
$directive =
$this->PRVTcheckTextDirective1($text,$i,$f,1,$xp,$yp,$size,$angle,$wordSpaceAdjust);
// restart the text object
// and move the writing point to the next piece of text
// if there are any open callbacks, then they should be called, to show the end of the line
$tmp =
$this->PRVTgetTextPosition($x,$y,$angle,$size,$wordSpaceAdjust,$text);
$info =
array('x'=>
$tmp[0],'y'=>
$tmp[1],'angle'=>
$angle,'status'=>
'eol','p'=>
$this->callback[$i]['p'],'nCallback'=>
$this->callback[$i]['nCallback'],'height'=>
$this->callback[$i]['height'],'decender'=>
$this->callback[$i]['decender']);
* calculate how wide a given text string will be on a page, at a given size.
* this can be called externally, but is alse used by the other class functions
// this function should not change any of the settings, though it will need to
// track any directives which change during calculation, so copy them at the start
// and put them back at the end.
// converts a number or a float to a string so it can get the width
// hmm, this is where it all starts to get tricky - use the font information to
// calculate the width of each character, add them up and convert to user units
$directive =
$this->PRVTcheckTextDirective($text,$i,$f);
if (isset
($this->fonts[$cf]['differences'][$char])){
// then this character is being replaced by another
$name =
$this->fonts[$cf]['differences'][$char];
if (isset
($this->fonts[$cf]['C'][$name]['WX'])){
$w+=
$this->fonts[$cf]['C'][$name]['WX'];
} else if (isset
($this->fonts[$cf]['C'][$char]['WX'])){
$w+=
$this->fonts[$cf]['C'][$char]['WX'];
* do a part of the calculation for sorting out the justification of the text
function PRVTadjustWrapText($text,$actual,$width,&$x,&$adjust,$justification){
// count the number of words
$nspaces=
count($words)-
1;
$adjust =
($width-
$actual)/
$nspaces;
* add text to the page, but ensure that it fits within a certain width
* if it does not fit then put in as much as possible, splitting at word boundaries
* and return the remainder.
* justification and angle can also be specified for the text
function addTextWrap($x,$y,$width,$size,$text,$justification=
'left',$angle=
0,$test=
0){
// this will display the text, and if it goes beyond the width $width, will backtrack to the
// previous space or hyphen, and return the remainder of the text.
// $justification can be set to 'left','right','center','centre','full'
// need to store the initial text state, as this will change during the width calculation
// but will need to be re-set before printing, so that the chars work out right
// error, pretend it printed ok, otherwise risking a loop
$directive =
$this->PRVTcheckTextDirective($text,$i,$f);
if (isset
($this->fonts[$cf]['differences'][$cOrd])){
// then this character is being replaced by another
$cOrd2 =
$this->fonts[$cf]['differences'][$cOrd];
if (isset
($this->fonts[$cf]['C'][$cOrd2]['WX'])){
$w+=
$this->fonts[$cf]['C'][$cOrd2]['WX'];
// then we need to truncate this line
// then we have somewhere that we can split :)
$tmp =
substr($text,0,$break);
$tmp =
substr($text,0,$break+
1);
$this->PRVTadjustWrapText($tmp,$breakWidth,$width,$x,$adjust,$justification);
$this->addText($x,$y,$size,$tmp,$angle,$adjust);
return substr($text,$break+
1);
// just split before the current character
if (isset
($this->fonts[$cf]['differences'][$ctmp])){
$ctmp=
$this->fonts[$cf]['differences'][$ctmp];
$tmpw=
($w-
$this->fonts[$cf]['C'][$ctmp]['WX'])*
$size/
1000;
$this->PRVTadjustWrapText($tmp,$tmpw,$width,$x,$adjust,$justification);
$this->addText($x,$y,$size,$tmp,$angle,$adjust);
$breakWidth =
$w*
$size/
1000;
if (isset
($this->fonts[$cf]['differences'][$ctmp])){
$ctmp=
$this->fonts[$cf]['differences'][$ctmp];
$breakWidth =
($w-
$this->fonts[$cf]['C'][$ctmp]['WX'])*
$size/
1000;
// then there was no need to break this line
if ($justification==
'full'){
$this->PRVTadjustWrapText($text,$tmpw,$width,$x,$adjust,$justification);
$this->addText($x,$y,$size,$text,$angle,$adjust,$angle);
* this will be called at a new page to return the state to what it was on the
* end of the previous page, before the stack was closed down
* This is to get around not being able to have open 'q' across pages
// this will be called at a new page to return the state to what it was on the
// end of the previous page, before the stack was closed down
// This is to get around not being able to have open 'q' across pages
$opt =
$this->stateStack[$pageEnd]; // ok to use this as stack starts numbering at 1
$this->setColor($opt['col']['r'],$opt['col']['g'],$opt['col']['b'],1);
$this->setStrokeColor($opt['str']['r'],$opt['str']['g'],$opt['str']['b'],1);
// $this->currentLineStyle = $opt['lin'];
* restore a previously saved state
* make a loose object, the output will go into this object, until it is closed, then will revert to
* this object will not appear until it is included within a page.
* the function will return the object number
// add a new object of the content type, to hold the data flow
* open an existing object for editing
// also if this object is the primary contents for a page, then set the current page to its parent
if (isset
($this->objects[$id]['onPage'])){
// close the object, as long as there was one open in the first place, which will be indicated by
// an objectId on the stack.
// easier to probably not worry about removing the old entries, they will be overwritten
// if there are new ones.
* stop an object from appearing on pages from this point on
// if an object has been appearing on pages up to now, then stop it, this page will
// be the last one that could contian it.
* after an object has been created, it wil only show if it has been added, using this function.
// add the specified object to the page
// then it is a valid object, and it is not being added to itself
// then this object is to be added to this page (done in the next block) and
// then the destination contents is the primary for the page
// (though this object is actually added to that page)
if ($this->objects[$pageObjectId]['info']['pageNum']%
2==
0){
if ($this->objects[$pageObjectId]['info']['pageNum']%
2==
1){
* add content to the documents info object
// this will only work if the label is one of the valid ones.
// modify this so that arrays can be passed as well.
// if $label is an array then assume that it is key=>value pairs
// else assume that they are both scalar, anything else will probably error
foreach ($label as $l=>
$v){
* set the viewer preferences of the document, it is up to the browser to obey these.
// this will only work if the label is one of the valid ones.
foreach ($label as $l=>
$v){
* extract an integer from a position in a byte stream
function PRVT_getBytes(&$data,$pos,$num){
// return the integer represented by $num bytes from $pos within $data
$ret+=
ord($data[$pos+
$i]);
* add a PNG image into the document, from a file
* this should work with remote files
// read in a png file, interpret it, then add to the system
$fp =
@fopen($file,'rb');
$data .=
fread($fp,1024);
$errormsg =
'trouble opening file: '.
$file;
if (substr($data,0,8)!=
$header){
$errormsg =
'this file does not have a valid header';
// cycle through the file, identifying chunks
$chunkLen =
$this->PRVT_getBytes($data,$p,4);
$chunkType =
substr($data,$p+
4,4);
// echo $chunkType.' - '.$chunkLen.'<br>';
// this is where all the file information comes from
$info['width']=
$this->PRVT_getBytes($data,$p+
8,4);
$info['height']=
$this->PRVT_getBytes($data,$p+
12,4);
$info['bitDepth']=
ord($data[$p+
16]);
$info['colorType']=
ord($data[$p+
17]);
$info['compressionMethod']=
ord($data[$p+
18]);
$info['filterMethod']=
ord($data[$p+
19]);
$info['interlaceMethod']=
ord($data[$p+
20]);
if ($info['compressionMethod']!=
0){
$errormsg =
'unsupported compression method';
if ($info['filterMethod']!=
0){
$errormsg =
'unsupported filter method';
$pdata.=
substr($data,$p+
8,$chunkLen);
$idata.=
substr($data,$p+
8,$chunkLen);
//this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk
//print "tRNS found, color type = ".$info['colorType']."<BR>";
if ($info['colorType'] ==
3) { // indexed color, rbg
/* corresponding to entries in the plte chunk
Alpha for palette index 0: 1 byte
Alpha for palette index 1: 1 byte
// there will be one entry for each palette entry. up until the last non-opaque entry.
// set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent)
$transparency['type']=
'indexed';
$numPalette =
strlen($pdata)/
3;
for ($i=
$chunkLen;$i>=
0;$i--
){
if (ord($data[$p+
8+
$i])==
0){
$transparency['data'] =
$trans;
} elseif($info['colorType'] ==
0) { // grayscale
/* corresponding to entries in the plte chunk
Gray: 2 bytes, range 0 .. (2^bitdepth)-1
// $transparency['grayscale']=$this->PRVT_getBytes($data,$p+8,2); // g = grayscale
$transparency['type']=
'indexed';
$transparency['data'] =
ord($data[$p+
8+
1]);
} elseif($info['colorType'] ==
2) { // truecolor
/* corresponding to entries in the plte chunk
Red: 2 bytes, range 0 .. (2^bitdepth)-1
Green: 2 bytes, range 0 .. (2^bitdepth)-1
Blue: 2 bytes, range 0 .. (2^bitdepth)-1
$transparency['r']=
$this->PRVT_getBytes($data,$p+
8,2); // r from truecolor
$transparency['g']=
$this->PRVT_getBytes($data,$p+
10,2); // g from truecolor
$transparency['b']=
$this->PRVT_getBytes($data,$p+
12,2); // b from truecolor
//unsupported transparency type
$errormsg =
'information header is missing';
if (isset
($info['interlaceMethod']) &&
$info['interlaceMethod']){
$errormsg =
'There appears to be no support for interlaced images in pdf.';
if (!$error &&
$info['bitDepth'] >
8){
$errormsg =
'only bit depth of 8 or less is supported';
if ($info['colorType']!=
2 &&
$info['colorType']!=
0 &&
$info['colorType']!=
3){
$errormsg =
'transparancey alpha channel not supported, transparency only supported for palette images.';
switch ($info['colorType']){
$this->addMessage('PNG error - ('.
$file.
') '.
$errormsg);
$w=
$h/
$info['height']*
$info['width'];
$h=
$w*
$info['height']/
$info['width'];
// so this image is ok... add it in.
// $this->o_image($this->numObj,'new',array('label'=>$label,'data'=>$idata,'iw'=>$w,'ih'=>$h,'type'=>'png','ic'=>$info['width']));
$options =
array('label'=>
$label,'data'=>
$idata,'bitsPerComponent'=>
$info['bitDepth'],'pdata'=>
$pdata
,'iw'=>
$info['width'],'ih'=>
$info['height'],'type'=>
'png','color'=>
$color,'ncolor'=>
$ncolor);
if (isset
($transparency)){
$options['transparency']=
$transparency;
* add a JPEG image into the document, from a file
// attempt to add a jpeg image straight from a file, using no GD commands
// note that this function is unable to operate on a remote file.
if (isset
($tmp['channels'])){
$channels =
$tmp['channels'];
$w=
$h/
$imageHeight*
$imageWidth;
$h=
$w*
$imageHeight/
$imageWidth;
$this->addJpegImage_common($data,$x,$y,$w,$h,$imageWidth,$imageHeight,$channels);
* add an image into the document, from a GD object
* this function is not all that reliable, and I would probably encourage people to use
* the file based functions
function addImage(&$img,$x,$y,$w=
0,$h=
0,$quality=
75){
// add a new image into the current location, as an external object
// add the image at $x,$y, and with width and height as defined by $w & $h
// note that this will only work with full colour images and makes them jpg images for display
// later versions could present lossless image formats if there is interest.
// there seems to be some problem here in that images that have quality set above 75 do not appear
// not too sure why this is, but in the meantime I have restricted this to 75.
// if the width or height are set to zero, then set the other one based on keeping the image
// height/width ratio the same, if they are both zero, then give up :)
$imageWidth=
imagesx($img);
$imageHeight=
imagesy($img);
$w=
$h/
$imageHeight*
$imageWidth;
$h=
$w*
$imageHeight/
$imageWidth;
// gotta get the data out of the img..
// so I write to a temp file, and then read it back.. soo ugly, my apologies.
imagejpeg($img,$tmpName,$quality);
$fp=
fopen($tmpName,'rb');
$fp =
@fopen($tmpName,'rb');
$data .=
fread($fp,1024);
$errormsg =
'trouble opening file';
// $data = fread($fp,filesize($tmpName));
$this->addJpegImage_common($data,$x,$y,$w,$h,$imageWidth,$imageHeight);
* common code used by the two JPEG adding functions
function addJpegImage_common(&$data,$x,$y,$w=
0,$h=
0,$imageWidth,$imageHeight,$channels=
3){
// note that this function is not to be called externally
// it is just the common code between the GD and the file options
$this->o_image($this->numObj,'new',array('label'=>
$label,'data'=>
$data,'iw'=>
$imageWidth,'ih'=>
$imageHeight,'channels'=>
$channels));
* specify where the document should open when it first starts
function openHere($style,$a=
0,$b=
0,$c=
0){
// this function will open the document at a specified page, in a specified style
// the values for style, and the required paramters are:
// 'FitR' left,bottom,right
* create a labelled destination within the document
// associates the given label with the destination, it is done this way so that a destination can be specified after
// styles are the same as the 'openHere' function
// store the label->idf relationship, note that this means that labels can be used only once
* define font families, this is used to initialize the font families for the default fonts
* and for the user to add new ones for their fonts. The default bahavious can be overridden should
// set the known family groups
// these font families will be used to enable bold and italic markers to be included
// within text streams. html forms will be used... <b></b> <i></i>
'b'=>
'Helvetica-Bold.afm'
,'i'=>
'Helvetica-Oblique.afm'
,'bi'=>
'Helvetica-BoldOblique.afm'
,'ib'=>
'Helvetica-BoldOblique.afm'
,'i'=>
'Courier-Oblique.afm'
,'bi'=>
'Courier-BoldOblique.afm'
,'ib'=>
'Courier-BoldOblique.afm'
,'bi'=>
'Times-BoldItalic.afm'
,'ib'=>
'Times-BoldItalic.afm'
// the user is trying to set a font family
// note that this can also be used to set the base ones to something else
* used to add messages for use in debugging
* a few functions which should allow the document to be treated transactionally.
// store all the data away into the checkpoint variable
// do not destroy the current checkpoint, but move us back to the state then, so that we can try again
// can only abort if were inside a checkpoint
foreach ($tmp as $k=>
$v){
// can only abort if were inside a checkpoint
foreach ($tmp as $k=>
$v){
Documentation generated on Mon, 05 May 2008 16:17:26 +0400 by phpDocumentor 1.4.0