/includes/cezpdf/Cpdf.php
PHP | 3899 lines | 2616 code | 358 blank | 925 comment | 573 complexity | e2a5849c82c34ff5b1d6951d0e7fc281 MD5 | raw file
Possible License(s): GPL-3.0
Large files files are truncated, but you can click here to view the full file
- <?php
- @include_once('include/TTFsubset.php');
- /**
- * Create pdf documents without additional modules
- * Note that the companion class Document_CezPdf can be used to extend this class and
- * simplify the creation of documents.
- * <pre>
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see http://www.gnu.org/licenses/
- * </pre>
- *
- * **Document object methods**
- *
- * There is about one object method for each type of object in the pdf document<br>
- * Each function has the same call list ($id,$action,$options).<br>
- * <pre>
- * $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
- * </pre>
- * These, in conjunction with the output function are the ONLY way for output to be produced
- * within the pdf 'file'.
- *
- * @category Documents
- * @package Cpdf
- * @version [0.12-rc12] $Id: Cpdf.php 207 2013-11-19 16:18:14Z ole1986 $
- * @author Wayne Munro (inactive) <pdf@ros.co.nz>
- * @author Lars Olesen <lars@legestue.net>
- * @author Sune Jensen <sj@sunet.dk>
- * @author Ole Koeckemann <ole1986@users.sourceforge.net>
- * @copyright 2007 - 2014 The authors
- * @license GNU General Public License v3
- * @link http://pdf-php.sf.net
- */
- class Cpdf
- {
- /**
- * PDF version
- * This value may vary dependent on which methods and/or features are used.
- * For instance setEncryption may cause the pdf version to increase to $this->pdfversion = 1.4
- *
- * Minimum 1.3
- * @var string $pdfversion default is 1.3
- */
- protected $pdfversion = 1.3;
- /**
- * allow the programmer to output debug messages on several places<br>
- * 'none' = no debug output at all
- * 'error_log' = use error_log
- * 'variable' = store in a variable called $this->messages
- * @var string $DEBUG Default is error_log
- */
- public $DEBUG = 'error_log';
-
- /**
- * Set the debug level
- * E_USER_ERROR = only errors
- * E_USER_WARNING = errors and warning
- * E_USER_NOTICE = nearly everything
- *
- * @var int $DEBUGLEVEL Default E_USER_WARNING
- */
- public $DEBUGLEVEL = E_USER_WARNING;
- /**
- * Reversed char string to allow arabic or Hebrew
- * @todo incomplete implementation
- */
- public $rtl = false;
-
- /**
- * flag to validate the output and if output method has be executed
- * This option is not really in use but is set to true in checkAllHere method
- * @var bool
- */
- protected $valid = false;
-
- /**
- * temporary path used for image and font caching.
- * Need to get changed when using XAMPP
- * @var string
- */
- public $tempPath = '/tmp';
- /**
- * the current number of pdf objects in the document
- * @var integer
- */
- protected $numObj=0;
- /**
- * contains pdf objects ready for the final assembly
- * @var array
- */
- protected $objects = array();
- /**
- * set to true allows object being hashed. Primary used for images
- * @var bool
- */
- public $hashed = true;
- /**
- * Object hash array used to free pdf from redundancies
- * @var array
- */
- private $objectHash = array();
-
- /**
- * the objectId (number within the objects array) of the document catalog
- * @var integer
- */
- private $catalogId;
-
- /**
- * default encoding for NON-UNICODE text
- @var string default encoding is IS0-8859-1
- */
- public $targetEncoding = 'ISO-8859-1';
- /**
- * set this to true allows TTF font being parsed as unicode in PDF output.
- * This also converts all text output into utf16_be
- * @var boolean default is false
- */
- public $isUnicode = false;
-
- /**
- * define the tags being allowed in any text input, like addText or addTextWrap (default: bold, italic and links)
- * @var string
- */
- public $allowedTags = 'b|strong|i|uline|alink:?.*?|ilink:?.*?|color:?.*?';
-
- /**
- * used to either embed or not embed the ttf/pfb font program
- * @var boolean default embed the font program
- */
- protected $embedFont = true;
-
- /**
- * font cache timeout in seconds
- * @var integer default is 86400 which is 1 day
- */
- public $cacheTimeout = 86400;
-
- /**
- * stores the font family information for either core fonts or any other TTF font program.
- * Once the font family is defined, directives like bold and italic
- * @var array
- */
- private $fontFamilies = array(
- 'Helvetica' => array(
- 'b'=>'Helvetica-Bold',
- 'i'=>'Helvetica-Oblique',
- 'bi'=>'Helvetica-BoldOblique',
- 'ib'=>'Helvetica-BoldOblique',
- ),
- 'Courier' => array(
- 'b'=>'Courier-Bold',
- 'i'=>'Courier-Oblique',
- 'bi'=>'Courier-BoldOblique',
- 'ib'=>'Courier-BoldOblique',
- ),
- 'Times-Roman' => array(
- 'b'=>'Times-Bold',
- 'i'=>'Times-Italic',
- 'bi'=>'Times-BoldItalic',
- 'ib'=>'Times-BoldItalic',
- )
- );
- /**
- * all CoreFonts available in PDF by default.
- * This array is used check if TTF font need to get attached and/or is unicode
- * @var array
- */
- private $coreFonts = array('courier', 'courier-bold', 'courier-oblique', 'courier-boldoblique',
- 'helvetica', 'helvetica-bold', 'helvetica-oblique', 'helvetica-boldoblique',
- 'times-roman', 'times-bold', 'times-italic', 'times-bolditalic',
- 'symbol', 'zapfdingbats');
- /**
- * 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
- * @var array
- */
- protected $fonts = array();
-
- /**
- * font path location
- * @since 0.12-rc8
- */
- public $fontPath = './';
-
- /**
- * a record of the current font
- * @var string
- */
- protected $currentFont='';
- /**
- * the current base font
- * @var string
- */
- protected $currentBaseFont='';
- /**
- * the number of the current font within the font array
- * @var integer
- */
- protected $currentFontNum=0;
- /**
- * no clue for what this is used
- * @var integer
- */
- private $currentNode;
- /**
- * object number of the current page
- * @var integer
- */
- protected $currentPage;
- /**
- * object number of the currently active contents block
- * @var integer
- */
- protected $currentContents;
- /**
- * number of fonts within the system
- * @var integer
- */
- protected $numFonts = 0;
- /**
- * current colour for fill operations, defaults to inactive value, all three components should be between 0 and 1 inclusive when active
- */
- protected $currentColour = array('r' => -1, 'g' => -1, 'b' => -1);
- /**
- * current colour for stroke operations (lines etc.)
- */
- protected $currentStrokeColour = array('r' => -1, 'g' => -1, 'b' => -1);
- /**
- * current style that lines are drawn in
- */
- protected $currentLineStyle='';
- /**
- * 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
- */
- private $stateStack = array();
- /**
- * number of elements within the state stack
- */
- private $nStateStack = 0;
- /**
- * number of page objects within the document
- */
- protected $numPages=0;
- /**
- * object Id storage stack
- */
- private $stack=array();
- /**
- * number of elements within the object Id storage stack
- */
- private $nStack=0;
- /**
- * an array which contains information about the objects which are not firmly attached to pages
- * these have been added with the addObject function
- */
- private $looseObjects=array();
- /**
- * array contains infomation about how the loose objects are to be added to the document
- */
- private $addLooseObjects=array();
- /**
- * the objectId of the information object for the document
- * this contains authorship, title etc.
- */
- private $infoObject=0;
- /**
- * number of images being tracked within the document
- */
- private $numImages=0;
- /**
- * some additional options while generation
- * currently used for compression only
- * Default: 'compression' => -1 which will set gzcompress to the default level of 6
- */
- public $options=array('compression'=>-1);
- /**
- * the objectId of the first page of the document
- */
- private $firstPageId;
- /**
- * used to track the last used value of the inter-word spacing, this is so that it is known
- * when the spacing is changed.
- */
- private $wordSpaceAdjust=0;
- /**
- * tracks the status of the current font style, like bold or italic
- */
- private $currentTextState = '';
- /**
- * messages are stored here during processing, these can be selected afterwards to give some useful debug information
- */
- public $messages='';
- /**
- * the encryption array for the document encryption is stored here
- */
- private $arc4='';
- /**
- * the object Id of the encryption information
- */
- private $arc4_objnum=0;
- /**
- * the file identifier, used to uniquely identify a pdf document
- */
- public $fileIdentifier='';
- /**
- * Set the encryption mode
- * 0 = no encryption
- * 1 = RC40bit
- * 2 = RC128bit (since PDF Version 1.4)
- */
- private $encryptionMode = 0;
- /**
- * the encryption key for the encryption of all the document content (structure is not encrypted)
- *
- * @var string
- */
- private $encryptionKey='';
-
- /**
- * encryption padding fetched from the Adobe PDF reference
- */
- private $encryptionPad;
-
- /**
- * array which forms a stack to keep track of nested callback functions
- *
- * @var array
- */
- private $callback = array();
- /**
- * the number of callback functions in the callback array
- *
- * @var integer
- */
- private $nCallback = 0;
- /**
- * 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
- *
- * @var array
- */
- private $destinations = array();
- /**
- * 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.
- *
- * @var string
- */
- protected $checkpoint = '';
- /**
- * Constructor - start with a new PDF document
- *
- * @param array $pageSize Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
- * @param bool $isUnicode
- * @return void
- */
- public function __construct($pageSize = array(0, 0, 612, 792), $isUnicode = false)
- {
- $this->isUnicode = $isUnicode;
- // set the hardcoded encryption pad
- $this->encryptionPad = 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);
-
- $this->newDocument($pageSize);
- if ( in_array('Windows-1252', mb_list_encodings()) ) {
- $this->targetEncoding = 'Windows-1252';
- }
- // use the md5 to have a unique identifier for all documents created with R&OS pdf class
- $this->fileIdentifier = md5('ROSPDF'.time());
-
- // set the default font path to [...]/src/fonts
- $this->fontPath = dirname(__FILE__).'/fonts';
- }
- /**
- * destination object, used to specify the location for the user to jump to, presently on opening
- */
- private function o_destination($id,$action,$options='')
- {
- if ($action!='new'){
- $o =& $this->objects[$id];
- }
- switch($action){
- case 'new':
- $this->objects[$id]=array('t'=>'destination','info'=>array());
- $tmp = '';
- switch ($options['type']){
- case 'XYZ':
- case 'FitR':
- $tmp = ' '.$options['p3'].$tmp;
- case 'FitH':
- case 'FitV':
- case 'FitBH':
- case 'FitBV':
- $tmp = ' '.$options['p1'].' '.$options['p2'].$tmp;
- case 'Fit':
- case 'FitB':
- $tmp = $options['type'].$tmp;
- $this->objects[$id]['info']['string']=$tmp;
- $this->objects[$id]['info']['page']=$options['page'];
- }
- break;
- case 'out':
- $tmp = $o['info'];
- $res="\n".$id." 0 obj\n".'['.$tmp['page'].' 0 R /'.$tmp['string']."]\nendobj";
- return $res;
- break;
- }
- }
- /**
- * sets the viewer preferences
- */
- private function o_viewerPreferences($id,$action,$options='')
- {
- if ($action!='new'){
- $o =& $this->objects[$id];
- }
- switch ($action){
- case 'new':
- $this->objects[$id]=array('t'=>'viewerPreferences','info'=>array());
- break;
- case 'add':
- foreach($options as $k=>$v){
- switch ($k){
- case 'HideToolbar':
- case 'HideMenubar':
- case 'HideWindowUI':
- case 'FitWindow':
- case 'CenterWindow':
- case 'DisplayDocTitle': // since PDF 1.4
- case 'NonFullScreenPageMode':
- case 'Direction':
- $o['info'][$k]=$v;
- break;
- }
- }
- break;
- case 'out':
- $res="\n".$id." 0 obj\n".'<< ';
- foreach($o['info'] as $k=>$v){
- $res.="\n/".$k.' '.$v;
- }
- $res.="\n>>\n";
- return $res;
- break;
- }
- }
- /**
- * define the document catalog, the overall controller for the document
- */
- private function o_catalog($id, $action, $options = '')
- {
- if ($action!='new'){
- $o =& $this->objects[$id];
- }
- switch ($action){
- case 'new':
- $this->objects[$id]=array('t'=>'catalog','info'=>array());
- $this->catalogId=$id;
- break;
- case 'outlines':
- case 'pages':
- case 'openHere':
- $o['info'][$action]=$options;
- break;
- case 'viewerPreferences':
- if (!isset($o['info']['viewerPreferences'])){
- $this->numObj++;
- $this->o_viewerPreferences($this->numObj,'new');
- $o['info']['viewerPreferences']=$this->numObj;
- }
- $vp = $o['info']['viewerPreferences'];
- $this->o_viewerPreferences($vp,'add',$options);
- break;
- case 'out':
- $res="\n".$id." 0 obj\n".'<< /Type /Catalog';
- foreach($o['info'] as $k=>$v){
- switch($k){
- case 'outlines':
- $res.=' /Outlines '.$v.' 0 R';
- break;
- case 'pages':
- $res.=' /Pages '.$v.' 0 R';
- break;
- case 'viewerPreferences':
- $res.=' /ViewerPreferences '.$o['info']['viewerPreferences'].' 0 R';
- break;
- case 'openHere':
- $res.=' /OpenAction '.$o['info']['openHere'].' 0 R';
- break;
- }
- }
- $res.=" >>\nendobj";
- return $res;
- break;
- }
- }
- /**
- * object which is a parent to the pages in the document
- */
- private function o_pages($id,$action,$options='')
- {
- if ($action!='new'){
- $o =& $this->objects[$id];
- }
- switch ($action){
- case 'new':
- $this->objects[$id]=array('t'=>'pages','info'=>array());
- $this->o_catalog($this->catalogId,'pages',$id);
- break;
- case 'page':
- if (!is_array($options)){
- // then it will just be the id of the new page
- $o['info']['pages'][]=$options;
- } else {
- // 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'])){
- $i = array_search($options['rid'],$o['info']['pages']);
- if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i]==$options['rid']){
- // then there is a match make a space
- switch ($options['pos']){
- case 'before':
- $k = $i;
- break;
- case 'after':
- $k=$i+1;
- break;
- default:
- $k=-1;
- break;
- }
- if ($k>=0){
- 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'];
- }
- }
- }
- }
- break;
- case 'procset':
- $o['info']['procset']=$options;
- break;
- case 'mediaBox':
- $o['info']['mediaBox']=$options; // which should be an array of 4 numbers
- break;
- case 'font':
- $o['info']['fonts'][]=array('objNum'=>$options['objNum'],'fontNum'=>$options['fontNum']);
- break;
- case 'xObject':
- $o['info']['xObjects'][]=array('objNum'=>$options['objNum'],'label'=>$options['label']);
- break;
- case 'out':
- if (count($o['info']['pages'])){
- $res="\n".$id." 0 obj\n<< /Type /Pages /Kids [";
- foreach($o['info']['pages'] as $k=>$v){
- $res.=$v." 0 R ";
- }
- $res.="] /Count ".count($this->objects[$id]['info']['pages']);
- if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) || isset($o['info']['procset'])){
- $res.=" /Resources <<";
- if (isset($o['info']['procset'])){
- $res.=" /ProcSet ".$o['info']['procset'];
- }
- if (isset($o['info']['fonts']) && count($o['info']['fonts'])){
- $res.=" /Font << ";
- foreach($o['info']['fonts'] as $finfo){
- $res.=" /F".$finfo['fontNum']." ".$finfo['objNum']." 0 R";
- }
- $res.=" >>";
- }
- if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])){
- $res.=" /XObject << ";
- foreach($o['info']['xObjects'] as $finfo){
- $res.=" /".$finfo['label']." ".$finfo['objNum']." 0 R";
- }
- $res.=" >>";
- }
- $res.=" >>";
- if (isset($o['info']['mediaBox'])){
- $tmp=$o['info']['mediaBox'];
- $res.=" /MediaBox [".sprintf('%.3F',$tmp[0]).' '.sprintf('%.3F',$tmp[1]).' '.sprintf('%.3F',$tmp[2]).' '.sprintf('%.3F',$tmp[3]).']';
- }
- }
- $res.=" >>\nendobj";
- } else {
- $res="\n".$id." 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
- }
- return $res;
- break;
- }
- }
-
- /**
- * defines the outlines in the doc, empty for now
- */
- private function o_outlines($id,$action,$options='')
- {
- if ($action!='new'){
- $o =& $this->objects[$id];
- }
- switch ($action){
- case 'new':
- $this->objects[$id]=array('t'=>'outlines','info'=>array('outlines'=>array()));
- $this->o_catalog($this->catalogId,'outlines',$id);
- break;
- case 'outline':
- $o['info']['outlines'][]=$options;
- break;
- case 'out':
- if (count($o['info']['outlines'])){
- $res="\n".$id." 0 obj\n<< /Type /Outlines /Kids [";
- foreach($o['info']['outlines'] as $k=>$v){
- $res.=$v." 0 R ";
- }
- $res.="] /Count ".count($o['info']['outlines'])." >>\nendobj";
- } else {
- $res="\n".$id." 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
- }
- return $res;
- break;
- }
- }
- /**
- * an object to hold the font description
- */
- private function o_font($id,$action,$options=''){
- if ($action!='new'){
- $o =& $this->objects[$id];
- }
- switch ($action){
- case 'new':
- $this->objects[$id]=array('t'=>'font','info'=>array('name'=>$options['name'], 'fontFileName'=> $options['fontFileName'], 'SubType'=>'Type1'));
-
- $fontFileName = &$options['fontFileName'];
-
- $fontNum=$this->numFonts;
- $this->objects[$id]['info']['fontNum']=$fontNum;
- // deal with the encoding and the differences
- if (isset($options['differences'])){
- // then we'll need an encoding dictionary
- $this->numObj++;
- $this->o_fontEncoding($this->numObj,'new',$options);
- $this->objects[$id]['info']['encodingDictionary']=$this->numObj;
- } else if (isset($options['encoding'])){
- // we can specify encoding here
- switch($options['encoding']){
- case 'WinAnsiEncoding':
- case 'MacRomanEncoding':
- case 'MacExpertEncoding':
- $this->objects[$id]['info']['encoding']=$options['encoding'];
- break;
- case 'none':
- break;
- default:
- $this->objects[$id]['info']['encoding']='WinAnsiEncoding';
- break;
- }
- } else {
- $this->objects[$id]['info']['encoding']='WinAnsiEncoding';
- }
-
- if ($this->fonts[$fontFileName]['isUnicode']) {
- // For Unicode fonts, we need to incorporate font data into
- // sub-sections that are linked from the primary font section.
- // Look at o_fontGIDtoCID and o_fontDescendentCID functions
- // for more informaiton.
- //
- // All of this code is adapted from the excellent changes made to
- // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
- $toUnicodeId = ++$this->numObj;
- $this->o_contents($toUnicodeId, 'new', 'raw');
- $this->objects[$id]['info']['toUnicode'] = $toUnicodeId;
- $stream = "/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo <</Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n1 begincodespacerange\n<0000> <FFFF>\nendcodespacerange\n1 beginbfrange\n<0000> <FFFF> <0000>\nendbfrange\nendcmap\nCMapName currentdict /CMap defineresource pop\nend\nend\n";
- $res = "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
- $res .= "stream\n" . $stream . "\nendstream";
- $this->objects[$toUnicodeId]['c'] = $res;
- $cidFontId = ++$this->numObj;
- $this->o_fontDescendentCID($cidFontId, 'new', $options);
- $this->objects[$id]['info']['cidFont'] = $cidFontId;
- }
- // also tell the pages node about the new font
- $this->o_pages($this->currentNode,'font',array('fontNum'=>$fontNum,'objNum'=>$id));
- break;
- case 'add':
- foreach ($options as $k=>$v){
- switch ($k){
- case 'BaseFont':
- $o['info']['name'] = $v;
- break;
- case 'FirstChar':
- case 'LastChar':
- case 'Widths':
- case 'FontDescriptor':
- case 'SubType':
- $this->debug('o_font '.$k." : ".$v, E_USER_NOTICE);
- $o['info'][$k] = $v;
- break;
- }
- }
-
- // pass values down to descendent font
- if (isset($o['info']['cidFont'])) {
- $this->o_fontDescendentCID($o['info']['cidFont'], 'add', $options);
- }
- break;
- case 'out':
- $fontFileName = &$o['info']['fontFileName'];
- // when font program is embedded and its not a coreFont, attach the font either as subset or completely
- if($this->embedFont && !in_array(strtolower($o['info']['name']), $this->coreFonts)){
- // when TrueType font is used
- if(isset($this->objects[$o['info']['FontDescriptor']]['info']['FontFile2'])){
- // find font program id for TTF fonts (FontFile2)
- $pfbid = $this->objects[$o['info']['FontDescriptor']]['info']['FontFile2'];
- // if subsetting is set
- if($this->fonts[$fontFileName]['isSubset'] && $this->fonts[$fontFileName]['isUnicode']){
- $this->debug('subset font for ' . $fontFileName, E_USER_NOTICE);
- $subsetFontName = "AAAAAD+" . $o['info']['name'];
- $o['info']['name'] = $subsetFontName;
- // find descendant font
- $this->objects[$o['info']['cidFont']]['info']['name'] = $subsetFontName;
- // find font descriptor
- $this->objects[$o['info']['FontDescriptor']]['info']['FontName'] = $subsetFontName;
-
- // use TTF subset script from http://www.4real.gr/technical-documents-ttf-subset.html
- $t = new TTFsubset();
- // combine all used characters as string
- $s = implode('',array_keys($this->fonts[$fontFileName]['subset']));
- // submit the string to TTFsubset class to return the subset (as binary)
- $data = $t->doSubset($this->fontPath.'/'.$fontFileName . '.ttf', $s, null);
- // $data is the new (subset) of the font font
- //file_put_contents('/tmp/'.$o['info']['name'] . '.ttf', $data);
-
- $newcidwidth = array();
- $cidwidth = &$this->fonts[$fontFileName]['CIDWidths'];
- foreach($t->TTFchars as $TTFchar){
- if(!empty($TTFchar->charCode) && isset($cidwidth[$TTFchar->charCode])){
- $newcidwidth[$TTFchar->charCode] = $cidwidth[$TTFchar->charCode];
- }
- }
- $cidwidth = $newcidwidth;
- } else {
- $data = file_get_contents($this->fontPath.'/'.$fontFileName. '.ttf');
- }
-
- // TODO: cache the subset
-
- $l1 = strlen($data);
- $this->objects[$pfbid]['c'].= $data;
- $this->o_contents($pfbid,'add',array('Length1'=>$l1));
- } else if(isset($this->objects[$o['info']['FontDescriptor']]['info']['FontFile'])) {
- // find FontFile id - used for PFB fonts
- $pfbid = $this->objects[$o['info']['FontDescriptor']]['info']['FontFile'];
- $data = file_get_contents($this->fontPath.'/'.$fontFileName. '.pfb');
- $l1 = strpos($data,'eexec')+6;
- $l2 = strpos($data,'00000000')-$l1;
- $l3 = strlen($data)-$l2-$l1;
- $this->o_contents($pfbid,'add',array('Length1'=>$l1,'Length2'=>$l2,'Length3'=>$l3));
- } else {
- $this->debug("Failed to select the correct font program", E_USER_WARNING);
- }
- }
-
- if ($this->fonts[$fontFileName]['isUnicode']) {
- // For Unicode fonts, we need to incorporate font data into
- // sub-sections that are linked from the primary font section.
- // Look at o_fontGIDtoCID and o_fontDescendentCID functions
- // for more informaiton.
- //
- // All of this code is adapted from the excellent changes made to
- // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
- $res = "\n$id 0 obj\n<</Type /Font /Subtype /Type0 /BaseFont /".$o['info']['name']."";
- // The horizontal identity mapping for 2-byte CIDs; may be used
- // with CIDFonts using any Registry, Ordering, and Supplement values.
- $res.= " /Encoding /Identity-H /DescendantFonts [".$o['info']['cidFont']." 0 R] /ToUnicode ".$o['info']['toUnicode']." 0 R >>\n";
- $res.= "endobj";
- } else {
- $res="\n".$id." 0 obj\n<< /Type /Font /Subtype /".$o['info']['SubType']." ";
- $res.="/Name /F".$o['info']['fontNum']." ";
- $res.="/BaseFont /".$o['info']['name']." ";
- if (isset($o['info']['encodingDictionary'])){
- // then place a reference to the dictionary
- $res.="/Encoding ".$o['info']['encodingDictionary']." 0 R ";
- } else if (isset($o['info']['encoding'])){
- // use the specified encoding
- $res.="/Encoding /".$o['info']['encoding']." ";
- }
- if (isset($o['info']['FirstChar'])){
- $res.="/FirstChar ".$o['info']['FirstChar']." ";
- }
- if (isset($o['info']['LastChar'])){
- $res.="/LastChar ".$o['info']['LastChar']." ";
- }
- if (isset($o['info']['Widths'])){
- $res.="/Widths ".$o['info']['Widths']." 0 R ";
- }
- if (isset($o['info']['FontDescriptor'])){
- $res.="/FontDescriptor ".$o['info']['FontDescriptor']." 0 R ";
- }
- $res.=">>\nendobj";
- }
- return $res;
- break;
- }
- }
- /**
- * a font descriptor, needed for including additional fonts
- */
- private function o_fontDescriptor($id, $action, $options = '')
- {
- if ($action!='new'){
- $o =& $this->objects[$id];
- }
- switch ($action){
- case 'new':
- $this->objects[$id]=array('t'=>'fontDescriptor','info'=>$options);
- break;
- case 'out':
- $res="\n".$id." 0 obj\n<< /Type /FontDescriptor ";
- foreach ($o['info'] as $label => $value){
- switch ($label){
- case 'Ascent':
- case 'CapHeight':
- case 'Descent':
- case 'Flags':
- case 'ItalicAngle':
- case 'StemV':
- case 'AvgWidth':
- case 'Leading':
- case 'MaxWidth':
- case 'MissingWidth':
- case 'StemH':
- case 'XHeight':
- case 'CharSet':
- if (strlen($value)){
- $res.='/'.$label.' '.$value." ";
- }
- break;
- case 'FontFile':
- case 'FontFile2':
- case 'FontFile3':
- $res.='/'.$label.' '.$value." 0 R ";
- break;
- case 'FontBBox':
- $res.='/'.$label.' ['.$value[0].' '.$value[1].' '.$value[2].' '.$value[3]."] ";
- break;
- case 'FontName':
- $res.='/'.$label.' /'.$value." ";
- break;
- }
- }
- $res.=">>\nendobj";
- return $res;
- break;
- }
- }
- /**
- * the font encoding
- */
- private function o_fontEncoding($id,$action,$options=''){
- if ($action!='new'){
- $o =& $this->objects[$id];
- }
- switch ($action){
- case 'new':
- // the options array should contain 'differences' and maybe 'encoding'
- $this->objects[$id]=array('t'=>'fontEncoding','info'=>$options);
- break;
- case 'out':
- $res="\n".$id." 0 obj\n<< /Type /Encoding ";
- if (!isset($o['info']['encoding'])){
- $o['info']['encoding']='WinAnsiEncoding';
- }
- if ($o['info']['encoding']!='none'){
- $res.="/BaseEncoding /".$o['info']['encoding']." ";
- }
- $res.="/Differences [";
- $onum=-100;
- foreach($o['info']['differences'] as $num=>$label){
- if ($num!=$onum+1){
- // we cannot make use of consecutive numbering
- $res.= " ".$num." /".$label;
- } else {
- $res.= " /".$label;
- }
- $onum=$num;
- }
- $res.="] >>\nendobj";
- return $res;
- break;
- }
- }
- /**
- * a descendent cid font, needed for unicode fonts
- */
- private function o_fontDescendentCID($id, $action, $options = '') {
- if ($action !== 'new') {
- $o = & $this->objects[$id];
- }
- switch ($action) {
- case 'new':
- $this->objects[$id] = array('t' => 'fontDescendentCID', 'info' => $options);
- // and a CID to GID map
- if($this->embedFont){
- $cidToGidMapId = ++$this->numObj;
- $this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options);
- $this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId;
- }
- break;
- case 'add':
- foreach ($options as $k => $v) {
- switch ($k) {
- case 'BaseFont':
- $o['info']['name'] = $v;
- break;
- case 'FirstChar':
- case 'LastChar':
- case 'MissingWidth':
- case 'FontDescriptor':
- case 'SubType':
- $this->debug("o_fontDescendentCID $k : $v", E_USER_NOTICE);
- $o['info'][$k] = $v;
- break;
- }
- }
- // pass values down to cid to gid map
- if($this->embedFont){
- $this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options);
- }
- break;
- case 'out':
- $fontFileName = &$o['info']['fontFileName'];
- $res = "\n$id 0 obj\n";
- $res.= "<</Type /Font /Subtype /CIDFontType2 /BaseFont /".$o['info']['name']." /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >>";
- if (isset($o['info']['FontDescriptor'])) {
- $res.= " /FontDescriptor ".$o['info']['FontDescriptor']." 0 R";
- }
- if (isset($o['info']['MissingWidth'])) {
- $res.= " /DW ".$o['info']['MissingWidth']."";
- }
- if (isset($fontFileName) && isset($this->fonts[$fontFileName]['CIDWidths'])) {
- $cid_widths = &$this->fonts[$fontFileName]['CIDWidths'];
- $res.= ' /W [';
- reset($cid_widths);
- $opened = false;
- while (list($k,$v) = each($cid_widths)) {
- list($nextk, $nextv) = each($cid_widths);
- //echo "\n$k ($v) == $nextk ($nextv)";
- if(($k + 1) == $nextk){
- if(!$opened){
- $res.= " $k [$v";
- $opened = true;
- } else if($opened) {
- $res.= ' '.$v;
- }
- prev($cid_widths);
- } else {
- if($opened){
- $res.=" $v]";
- } else {
- $res.= " $k [$v]";
- }
-
- $opened = false;
- prev($cid_widths);
- }
- }
-
- if(isset($nextk) && isset($nextv)){
- if($opened){
- $res.= "]";
- }
- $res.= " $nextk [$nextv]";
- }
-
- $res.= ' ]';
- }
-
- if($this->embedFont){
- $res.= " /CIDToGIDMap ".$o['info']['cidToGidMap']." 0 R";
- }
- $res.= " >>\n";
- $res.= "endobj";
- return $res;
- }
- }
- /**
- * a font glyph to character map, needed for unicode fonts
- */
- private function o_fontGIDtoCIDMap($id, $action, $options = '') {
- if ($action !== 'new') {
- $o = & $this->objects[$id];
- }
- switch ($action) {
- case 'new':
- $this->objects[$id] = array('t' => 'fontGIDtoCIDMap', 'info' => $options);
- break;
- case 'out':
- $res = "\n$id 0 obj\n";
- $fontFileName = &$o['info']['fontFileName'];
- $tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']);
-
- if (isset($o['raw'])) {
- $res.= $tmp;
- } else {
- $res.= "<<";
- if (function_exists('gzcompress') && $this->options['compression']) {
- // then implement ZLIB based compression on this content stream
- $tmp = gzcompress($tmp, $this->options['compression']);
- $res.= " /Filter /FlateDecode";
- }
-
- $res.= " /Length ".mb_strlen($tmp, '8bit') .">>\nstream\n$tmp\nendstream";
- }
- $res.= "\nendobj";
- return $res;
- }
- }
- /**
- * define the document information
- */
- private function o_info($id,$action,$options=''){
- if ($action!='new'){
- $o =& $this->objects[$id];
- }
- switch ($action){
- case 'new':
- $this->infoObject=$id;
- $date='D:'.date('Ymd');
- $this->objects[$id]=array('t'=>'info','info'=>array('Creator'=>'R&OS php pdf class, http://pdf-php.sf.net/','CreationDate'=>$date));
- break;
- case 'Title':
- case 'Author':
- case 'Subject':
- case 'Keywords':
- case 'Creator':
- case 'Producer':
- case 'CreationDate':
- case 'ModDate':
- case 'Trapped':
- $o['info'][$action]=$options;
- break;
- case 'out':
- if ($this->encryptionMode > 0){
- $this->encryptInit($id);
- }
- $res="\n".$id." 0 obj\n<< ";
- foreach ($o['info'] as $k=>$v){
- $res.='/'.$k.' (';
- if ($this->encryptionMode > 0){
- $res.=$this->filterText($this->ARC4($v), true, false);
- } else {
- $res.=$this->filterText($v, true, false);
- }
- $res.=") ";
- }
- $res.=">>\nendobj";
- return $res;
- break;
- }
- }
- /**
- * an action object, used to link to URLS initially
- * In version >= 0.12.2 internal and external links are handled in o_annotation directly
- * Additional actions, like SubmitForm, ResetForm, ImportData, Javascript will be part of
- * o_actions. Unless we also do not handle them similar to Links.
- *
- */
- private function o_action($id,$action,$options=''){
- if ($action!='new'){
- $o =& $this->objects[$id];
- }
- switch ($action){
- case 'new':
- if (is_array($options)){
- $this->objects[$id]=array('t'=>'action','info'=>$options,'type'=>$options['type']);
- } else {
- // then assume a URI action
- $this->objects[$id]=array('t'=>'action','info'=>$options,'type'=>'URI');
- }
- break;
- case 'out':
- if ($this->encryptionMode > 0){
- $this->encryptInit($id);
- }
- $res="\n".$id." 0 obj\n<< /Type /Action";
- switch($o['type']){
- case 'ilink':
- // there will be an 'label' setting, this is the name of the destination
- $res.=" /S /GoTo /D ".$this->destinations[(string)$o['info']['label']]." 0 R";
- break;
- case 'URI':
- $res.=" /S /URI /URI (";
- if ($this->encryptionMode > 0){
- $res.=$this->filterText($this->ARC4($o['info']), true, false);
- } else {
- $res.=$this->filterText($o['info'], true, false);
- }
- $res.=")";
- break;
- }
- $res.=" >>\nendobj";
- return $res;
- break;
- }
- }
- /**
- * an annotation object, this will add an annotation to the current page.
- * initially will support just link annotations
- */
- private function o_annotation($id,$action,$options=''){
- if ($action!='new'){
- $o =& $this->objects[$id];
- }
- switch ($action){
- case 'new':
- // add the annotation to the current page
- $pageId = $this->currentPage;
- $this->o_page($pageId,'annot',$id);
- // and add the action object which is going to be required
- switch($options['type']){
- case 'link':
- $this->objects[$id]=array('t'=>'annotation','info'=>$options);
- //$this->numObj++;
- //$this->o_action($this->numObj,'new',$options['url']);
- //$this->objects[$id]['info']['actionId']=$this->numObj;
- break;
- case 'ilink':
- // this is to a named internal link
- $label = $options['label'];
- $this->objects[$id]=array('t'=>'annotation','info'=>$options);
- //$this->numObj++;
- //$this->o_action($this->numObj,'new',array('type'=>'ilink','label'=>$label));
- //$this->objects[$id]['info']['actionId']=$this->numObj;
- break;
- case 'text':
- $this->objects[$id]=array('t'=>'annotation','info'=>$options);
- break;
- }
- break;
- case 'out':
- $res="\n".$id." 0 obj\n<< /Type /Annot";
- switch($o['info']['type']){
- case 'link':
- $res.= " /Subtype /Link";
- $res.=" /A << /S /URI /URI (".$o['info']['url'].") >>";
- $res.=" /Border [0 0 0]";
- $res.=" /H /I";
- break;
- case 'ilink':
- $res.= " /Subtype /Link";
- if(isset($this->destinations[(string)$o['info']['label']])){
- $res.=" /A << /S /GoTo /D ".$this->destinations[(string)$o['info']['label']]." 0 R >>";
- }
- $res.=" /Border [0 0 0]";
- $res.=" /H /I";
- break;
- case 'text':
- $res.= " /Subtype /Text";
- $res.= " /T (".$this->filterText($o['info']['title'], false, false).") /Contents (".$o['info']['content'].")";
- break;
- }
-
- $res.=" /Rect [ ";
- foreach($o['info']['rect'] as $v){
- $res.= sprintf("%.4F ",$v);
- }
- $res.="]";
- $res.=" >>\nendobj";
- return $res;
- break;
- }
- }
- /**
- * a page object, it also creates a contents object to hold its contents
- */
- private function o_page($id,$action,$options=''){
- if ($action!='new'){
- $o =& $this->objects[$id];
- }
- switch ($action){
- case 'new':
- $this->numPages++;
- $this->objects[$id]=array('t'=>'page','info'=>array('parent'=>$this->currentNode,'pageNum'=>$this->numPages));
- if (is_array($options)){
- // then this must be a page insertion, array shoudl contain 'rid','pos'=[before|after]
- $options['id']=$id;
- $this->o_pages($this->currentNode,'page',$options);
- } else {
- $this->o_pages($this->currentNode,'page',$id);
- }
- $this->currentPage=$id;
- // make a contents object to go with this page
- $this->numObj++;
- $this->o_contents($this->numObj,'new',$id);
- $this->currentContents=$this->numObj;
- $this->objects[$id]['info']['contents']=array();
- $this->objects[$id]['info']['contents'][]=$this->numObj;
- $match = ($this->numPages%2 ? 'odd' : 'even');
- foreach($this->addLooseObjects as $oId=>$target){
- if ($target=='all' || $match==$target){
- $this->objects[$id]['info']['contents'][]=$oId;
- }
- }
- break;
- case 'content':
- $o['info']['contents'][]=$options;
- break;
- case 'annot':
- // 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;
- break;
- case 'out':
- $res="\n".$id." 0 obj\n<< /Type /Page";
- $res.=" /Parent ".$o['info']['parent']." 0 R";
- if (isset($o['info']['annot'])){
- $res.=" /Annots [";
- foreach($o['info']['annot'] as $aId){
- $res.=" ".$aId." 0 R";
- }
- $res.=" ]";
- }
- $count = count($o['info']['contents']);
- if ($count==1){
- $res.=" /Contents ".$o['info']['contents'][0]." 0 R";
- } else if ($count>1){
- $res.=" /Contents [ ";
- foreach ($o['info']['contents'] as $cId){
- $res.=$cId." 0 R …
Large files files are truncated, but you can click here to view the full file