Browse Source

NEW: Introduce TCPDI as replacement of FPDI.

Laurent Destailleur 10 years ago
parent
commit
2f24622a7c

+ 13 - 0
dev/dolibarr_changes.txt

@@ -62,6 +62,19 @@ In htdocs/includes/tcpdf/tcpdf.php
 * Renamed getmypid into dol_getmypid().
 
 
+TCPDI:
+------
+Add fpdf_tpl.php 1.2
+Add tcpdi.php
+Add tcpdi_parser.php and replace:
+require_once(dirname(__FILE__).'/include/tcpdf_filters.php');
+with:
+require_once(dirname(__FILE__).'/../tcpdf/include/tcpdf_filters.php');
+
+
+
+
+
 JSGANTT:
 --------
 * Replace in function JSGantt.taskLink

+ 14 - 7
htdocs/admin/pdf.php

@@ -51,7 +51,7 @@ if ($action == 'update')
 {
 	dolibarr_set_const($db, "MAIN_PDF_FORMAT",    $_POST["MAIN_PDF_FORMAT"],'chaine',0,'',$conf->entity);
 
-	
+
     dolibarr_set_const($db, "MAIN_PROFID1_IN_ADDRESS",    $_POST["MAIN_PROFID1_IN_ADDRESS"],'chaine',0,'',$conf->entity);
 	dolibarr_set_const($db, "MAIN_PROFID2_IN_ADDRESS",    $_POST["MAIN_PROFID2_IN_ADDRESS"],'chaine',0,'',$conf->entity);
 	dolibarr_set_const($db, "MAIN_PROFID3_IN_ADDRESS",    $_POST["MAIN_PROFID3_IN_ADDRESS"],'chaine',0,'',$conf->entity);
@@ -222,7 +222,7 @@ if ($action == 'edit')	// Edit
     print '<tr '.$bc[$var].'><td>'.$langs->trans("HideAnyVATInformationOnPDF").'</td><td>';
 	print $form->selectyesno('MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT',(! empty($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT))?$conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT:0,1);
     print '</td></tr>';
-    
+
     // Hide Tva Intra on adress
     $var=!$var;
     print '<tr '.$bc[$var].'><td>'.$langs->trans("ShowVATIntaInAddress").'</td><td>';
@@ -385,14 +385,14 @@ else	// Show
     print '<table summary="more" class="noborder" width="100%">';
     print '<tr class="liste_titre"><td>'.$langs->trans("Parameter").'</td><td width="200px" colspan="2">'.$langs->trans("Value").'</td></tr>';
 
-    
+
     // Hide any PDF informations
     $var=!$var;
     print '<tr '.$bc[$var].'><td>'.$langs->trans("HideAnyVATInformationOnPDF").'</td><td colspan="2">';
     print yn($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT,1);
     print '</td></tr>';
 
-    
+
 	// Encrypt and protect PDF
 	$var=!$var;
 	print "<tr ".$bc[$var].">";
@@ -417,16 +417,16 @@ else	// Show
 		print '<a href="'.$_SERVER["PHP_SELF"].'?action=disable_pdfsecurity">'.$langs->trans("Disable").'</a>';
 	}
 	print "</td>";
-	
+
 	print "</td>";
 	print '</tr>';
-	
+
 	// Hide Tva Intra on adress
 	$var=!$var;
 	print '<tr '.$bc[$var].'><td>'.$langs->trans("ShowVATIntaInAddress").'</td><td colspan="2">';
 	print yn($conf->global->MAIN_TVAINTRA_NOT_IN_ADDRESS,1);
 	print '</td></tr>';
-	
+
 	//Desc
 	$var=!$var;
 	print '<tr '.$bc[$var].'><td>'.$langs->trans("HideDescOnPDF").'</td><td colspan="2">';
@@ -501,6 +501,13 @@ else	// Show
 		print ' ('.@constant('FPDI_PATH').')';
 		$i++;
 	}
+	if (class_exists('TCPDI'))
+	{
+		if ($i) print ' + ';
+		print 'TCPDI';
+		print ' ('.@constant('TCPDI_PATH').')';
+		$i++;
+	}
 	print '<!-- $conf->global->MAIN_USE_FPDF = '.$conf->global->MAIN_USE_FPDF.' -->';
 	print '</td>'."\n";
 	print '</tr>'."\n";

+ 4 - 2
htdocs/admin/system/constall.php

@@ -73,8 +73,10 @@ $configfileparameters=array(
 							'?dolibarr_main_auth_ldap_debug',
                             'separator',
                             '?dolibarr_lib_ADODB_PATH',
-                            '?dolibarr_lib_TCPDF_PATH',
-                            '?dolibarr_lib_FPDI_PATH',
+							'?dolibarr_lib_FPDF_PATH',
+	                        '?dolibarr_lib_TCPDF_PATH',
+							'?dolibarr_lib_FPDI_PATH',
+                            '?dolibarr_lib_TCPDI_PATH',
 							'?dolibarr_lib_NUSOAP_PATH',
                             '?dolibarr_lib_PHPEXCEL_PATH',
                             '?dolibarr_lib_GEOIP_PATH',

+ 3 - 2
htdocs/admin/system/dolibarr.php

@@ -266,9 +266,10 @@ $configfileparameters=array(
 		'?dolibarr_main_auth_ldap_debug' => 'dolibarr_main_auth_ldap_debug',
 		'separator3' => '',
 		'?dolibarr_lib_ADODB_PATH' => 'dolibarr_lib_ADODB_PATH',
-		'?dolibarr_lib_TCPDF_PATH' => 'dolibarr_lib_TCPDF_PATH',
 		'?dolibarr_lib_FPDF_PATH' => 'dolibarr_lib_FPDF_PATH',
+		'?dolibarr_lib_TCPDF_PATH' => 'dolibarr_lib_TCPDF_PATH',
 		'?dolibarr_lib_FPDI_PATH' => 'dolibarr_lib_FPDI_PATH',
+		'?dolibarr_lib_TCPDI_PATH' => 'dolibarr_lib_TCPDI_PATH',
 		'?dolibarr_lib_NUSOAP_PATH' => 'dolibarr_lib_NUSOAP_PATH',
 		'?dolibarr_lib_PHPEXCEL_PATH' => 'dolibarr_lib_PHPEXCEL_PATH',
 		'?dolibarr_lib_GEOIP_PATH' => 'dolibarr_lib_GEOIP_PATH',
@@ -332,7 +333,7 @@ foreach($configfileparameters as $key => $value)
 				{
 					if ($i > 0) print ', ';
 					print $value2;
-					if (! is_readable($value2)) 
+					if (! is_readable($value2))
 					{
 						$langs->load("errors");
 						print ' '.img_warning($langs->trans("ErrorCantReadDir",$value2));

+ 7 - 4
htdocs/core/lib/pdf.lib.php

@@ -125,8 +125,9 @@ function pdf_getInstance($format='',$metric='mm',$pagetype='P')
 	if (empty($conf->global->MAIN_USE_FPDF)) require_once TCPDF_PATH.'tcpdf.php';
 	else require_once FPDF_PATH.'fpdf.php';
 
-	// We need to instantiate fpdi object (instead of tcpdf) to use merging features. But we can disable it.
-	if (empty($conf->global->MAIN_DISABLE_FPDI)) require_once FPDI_PATH.'fpdi.php';
+	// We need to instantiate tcpdi or fpdi object (instead of tcpdf) to use merging features. But we can disable it (this will break all merge features).
+    if (empty($conf->global->MAIN_DISABLE_TCPDI)) require_once TCPDI_PATH.'tcpdi.php';
+	else if (empty($conf->global->MAIN_DISABLE_FPDI)) require_once FPDI_PATH.'fpdi.php';
 
 	//$arrayformat=pdf_getFormat();
 	//$format=array($arrayformat['width'],$arrayformat['height']);
@@ -146,7 +147,8 @@ function pdf_getInstance($format='',$metric='mm',$pagetype='P')
 		- print-high : Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set, printing is limited to a low-level representation of the appearance, possibly of degraded quality.
 		- owner : (inverted logic - only for public-key) when set permits change of encryption and enables all other permissions.
 		*/
-		if (class_exists('FPDI')) $pdf = new FPDI($pagetype,$metric,$format);
+		if (class_exists('TCPDI')) $pdf = new TCPDI($pagetype,$metric,$format);
+		else if (class_exists('FPDI')) $pdf = new FPDI($pagetype,$metric,$format);
 		else $pdf = new TCPDF($pagetype,$metric,$format);
 		// For TCPDF, we specify permission we want to block
 		$pdfrights = array('modify','copy');
@@ -157,7 +159,8 @@ function pdf_getInstance($format='',$metric='mm',$pagetype='P')
 	}
 	else
 	{
-		if (class_exists('FPDI')) $pdf = new FPDI($pagetype,$metric,$format);
+		if (class_exists('TCPDI')) $pdf = new TCPDI($pagetype,$metric,$format);
+		else if (class_exists('FPDI')) $pdf = new FPDI($pagetype,$metric,$format);
 		else $pdf = new TCPDF($pagetype,$metric,$format);
 	}
 

+ 2 - 1
htdocs/filefunc.inc.php

@@ -191,9 +191,10 @@ define('MAIN_DB_PREFIX',$dolibarr_main_db_prefix);
  */
 // Path to root libraries
 if (! defined('ADODB_PATH'))           { define('ADODB_PATH',           (!isset($dolibarr_lib_ADODB_PATH))?DOL_DOCUMENT_ROOT.'/includes/adodbtime/':(empty($dolibarr_lib_ADODB_PATH)?'':$dolibarr_lib_ADODB_PATH.'/')); }
-if (! defined('TCPDF_PATH'))           { define('TCPDF_PATH',           (empty($dolibarr_lib_TCPDF_PATH))?DOL_DOCUMENT_ROOT.'/includes/tcpdf/':$dolibarr_lib_TCPDF_PATH.'/'); }
 if (! defined('FPDF_PATH'))            { define('FPDF_PATH',            (empty($dolibarr_lib_FPDF_PATH))?DOL_DOCUMENT_ROOT.'/includes/fpdf/':$dolibarr_lib_FPDF_PATH.'/'); }	// Used only for package that can't include tcpdf
+if (! defined('TCPDF_PATH'))           { define('TCPDF_PATH',           (empty($dolibarr_lib_TCPDF_PATH))?DOL_DOCUMENT_ROOT.'/includes/tcpdf/':$dolibarr_lib_TCPDF_PATH.'/'); }
 if (! defined('FPDI_PATH'))            { define('FPDI_PATH',            (empty($dolibarr_lib_FPDI_PATH))?DOL_DOCUMENT_ROOT.'/includes/fpdfi/':$dolibarr_lib_FPDI_PATH.'/'); }
+if (! defined('TCPDI_PATH'))           { define('TCPDI_PATH',           (empty($dolibarr_lib_TCPDI_PATH))?DOL_DOCUMENT_ROOT.'/includes/tcpdi/':$dolibarr_lib_TCPDI_PATH.'/'); }
 if (! defined('NUSOAP_PATH'))          { define('NUSOAP_PATH',          (!isset($dolibarr_lib_NUSOAP_PATH))?DOL_DOCUMENT_ROOT.'/includes/nusoap/lib/':(empty($dolibarr_lib_NUSOAP_PATH)?'':$dolibarr_lib_NUSOAP_PATH.'/')); }
 if (! defined('PHPEXCEL_PATH'))        { define('PHPEXCEL_PATH',        (!isset($dolibarr_lib_PHPEXCEL_PATH))?DOL_DOCUMENT_ROOT.'/includes/phpexcel/':(empty($dolibarr_lib_PHPEXCEL_PATH)?'':$dolibarr_lib_PHPEXCEL_PATH.'/')); }
 if (! defined('GEOIP_PATH'))           { define('GEOIP_PATH',           (!isset($dolibarr_lib_GEOIP_PATH))?DOL_DOCUMENT_ROOT.'/includes/geoip/':(empty($dolibarr_lib_GEOIP_PATH)?'':$dolibarr_lib_GEOIP_PATH.'/')); }

+ 460 - 0
htdocs/includes/tcpdi/fpdf_tpl.php

@@ -0,0 +1,460 @@
+<?php
+//
+//  FPDF_TPL - Version 1.2.3
+//
+//    Copyright 2004-2013 Setasign - Jan Slabon
+//
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  you may not use this file except in compliance with the License.
+//  You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+//
+
+class FPDF_TPL extends FPDF {
+    /**
+     * Array of Tpl-Data
+     * @var array
+     */
+    var $tpls = array();
+
+    /**
+     * Current Template-ID
+     * @var int
+     */
+    var $tpl = 0;
+    
+    /**
+     * "In Template"-Flag
+     * @var boolean
+     */
+    var $_intpl = false;
+    
+    /**
+     * Nameprefix of Templates used in Resources-Dictonary
+     * @var string A String defining the Prefix used as Template-Object-Names. Have to beginn with an /
+     */
+    var $tplprefix = "/TPL";
+
+    /**
+     * Resources used By Templates and Pages
+     * @var array
+     */
+    var $_res = array();
+    
+    /**
+     * Last used Template data
+     *
+     * @var array
+     */
+    var $lastUsedTemplateData = array();
+    
+    /**
+     * Start a Template
+     *
+     * This method starts a template. You can give own coordinates to build an own sized
+     * Template. Pay attention, that the margins are adapted to the new templatesize.
+     * If you want to write outside the template, for example to build a clipped Template,
+     * you have to set the Margins and "Cursor"-Position manual after beginTemplate-Call.
+     *
+     * If no parameter is given, the template uses the current page-size.
+     * The Method returns an ID of the current Template. This ID is used later for using this template.
+     * Warning: A created Template is used in PDF at all events. Still if you don't use it after creation!
+     *
+     * @param int $x The x-coordinate given in user-unit
+     * @param int $y The y-coordinate given in user-unit
+     * @param int $w The width given in user-unit
+     * @param int $h The height given in user-unit
+     * @return int The ID of new created Template
+     */
+    function beginTemplate($x = null, $y = null, $w = null, $h = null) {
+    	if (is_subclass_of($this, 'TCPDF')) {
+    		$this->Error('This method is only usable with FPDF. Use TCPDF methods startTemplate() instead.');
+    		return;
+    	}
+    	
+        if ($this->page <= 0)
+            $this->error("You have to add a page to fpdf first!");
+
+        if ($x == null)
+            $x = 0;
+        if ($y == null)
+            $y = 0;
+        if ($w == null)
+            $w = $this->w;
+        if ($h == null)
+            $h = $this->h;
+
+        // Save settings
+        $this->tpl++;
+        $tpl =& $this->tpls[$this->tpl];
+        $tpl = array(
+            'o_x' => $this->x,
+            'o_y' => $this->y,
+            'o_AutoPageBreak' => $this->AutoPageBreak,
+            'o_bMargin' => $this->bMargin,
+            'o_tMargin' => $this->tMargin,
+            'o_lMargin' => $this->lMargin,
+            'o_rMargin' => $this->rMargin,
+            'o_h' => $this->h,
+            'o_w' => $this->w,
+            'o_FontFamily' => $this->FontFamily,
+            'o_FontStyle' => $this->FontStyle,
+            'o_FontSizePt' => $this->FontSizePt,
+            'o_FontSize' => $this->FontSize,
+            'buffer' => '',
+            'x' => $x,
+            'y' => $y,
+            'w' => $w,
+            'h' => $h
+        );
+
+        $this->SetAutoPageBreak(false);
+        
+        // Define own high and width to calculate possitions correct
+        $this->h = $h;
+        $this->w = $w;
+
+        $this->_intpl = true;
+        $this->SetXY($x + $this->lMargin, $y + $this->tMargin);
+        $this->SetRightMargin($this->w - $w + $this->rMargin);
+
+        if ($this->CurrentFont) {
+            $fontkey = $this->FontFamily . $this->FontStyle;
+		    $this->_res['tpl'][$this->tpl]['fonts'][$fontkey] =& $this->fonts[$fontkey];
+            
+        	$this->_out(sprintf('BT /F%d %.2f Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
+        }
+        
+        return $this->tpl;
+    }
+    
+    /**
+     * End Template
+     *
+     * This method ends a template and reset initiated variables on beginTemplate.
+     *
+     * @return mixed If a template is opened, the ID is returned. If not a false is returned.
+     */
+    function endTemplate() {
+    	if (is_subclass_of($this, 'TCPDF')) {
+        	$args = func_get_args();
+        	return call_user_func_array(array($this, 'TCPDF::endTemplate'), $args);
+        }
+        
+        if ($this->_intpl) {
+            $this->_intpl = false; 
+            $tpl =& $this->tpls[$this->tpl];
+            $this->SetXY($tpl['o_x'], $tpl['o_y']);
+            $this->tMargin = $tpl['o_tMargin'];
+            $this->lMargin = $tpl['o_lMargin'];
+            $this->rMargin = $tpl['o_rMargin'];
+            $this->h = $tpl['o_h'];
+            $this->w = $tpl['o_w'];
+            $this->SetAutoPageBreak($tpl['o_AutoPageBreak'], $tpl['o_bMargin']);
+            
+            $this->FontFamily = $tpl['o_FontFamily'];
+			$this->FontStyle = $tpl['o_FontStyle'];
+			$this->FontSizePt = $tpl['o_FontSizePt'];
+			$this->FontSize = $tpl['o_FontSize'];
+        	
+			$fontkey = $this->FontFamily . $this->FontStyle;
+			if ($fontkey)
+            	$this->CurrentFont =& $this->fonts[$fontkey];
+            
+            return $this->tpl;
+        } else {
+            return false;
+        }
+    }
+    
+    /**
+     * Use a Template in current Page or other Template
+     *
+     * You can use a template in a page or in another template.
+     * You can give the used template a new size like you use the Image()-method.
+     * All parameters are optional. The width or height is calculated automaticaly
+     * if one is given. If no parameter is given the origin size as defined in
+     * beginTemplate() is used.
+     * The calculated or used width and height are returned as an array.
+     *
+     * @param int $tplidx A valid template-Id
+     * @param int $_x The x-position
+     * @param int $_y The y-position
+     * @param int $_w The new width of the template
+     * @param int $_h The new height of the template
+     * @retrun array The height and width of the template
+     */
+    function useTemplate($tplidx, $_x = null, $_y = null, $_w = 0, $_h = 0) {
+        if ($this->page <= 0)
+        	$this->error('You have to add a page first!');
+        
+        if (!isset($this->tpls[$tplidx]))
+            $this->error('Template does not exist!');
+            
+        if ($this->_intpl) {
+            $this->_res['tpl'][$this->tpl]['tpls'][$tplidx] =& $this->tpls[$tplidx];
+        }
+        
+        $tpl =& $this->tpls[$tplidx];
+        $w = $tpl['w'];
+        $h = $tpl['h'];
+        
+        if ($_x == null)
+            $_x = 0;
+        if ($_y == null)
+            $_y = 0;
+            
+        $_x += $tpl['x'];
+        $_y += $tpl['y'];
+        
+        $wh = $this->getTemplateSize($tplidx, $_w, $_h);
+        $_w = $wh['w'];
+        $_h = $wh['h'];
+    
+        $tData = array(
+            'x' => $this->x,
+            'y' => $this->y,
+            'w' => $_w,
+            'h' => $_h,
+            'scaleX' => ($_w / $w),
+            'scaleY' => ($_h / $h),
+            'tx' => $_x,
+            'ty' =>  ($this->h - $_y - $_h),
+            'lty' => ($this->h - $_y - $_h) - ($this->h - $h) * ($_h / $h)
+        );
+        
+        $this->_out(sprintf('q %.4F 0 0 %.4F %.4F %.4F cm', $tData['scaleX'], $tData['scaleY'], $tData['tx'] * $this->k, $tData['ty'] * $this->k)); // Translate 
+        $this->_out(sprintf('%s%d Do Q', $this->tplprefix, $tplidx));
+
+        $this->lastUsedTemplateData = $tData;
+        
+        return array('w' => $_w, 'h' => $_h);
+    }
+    
+    /**
+     * Get The calculated Size of a Template
+     *
+     * If one size is given, this method calculates the other one.
+     *
+     * @param int $tplidx A valid template-Id
+     * @param int $_w The width of the template
+     * @param int $_h The height of the template
+     * @return array The height and width of the template
+     */
+    function getTemplateSize($tplidx, $_w = 0, $_h = 0) {
+        if (!isset($this->tpls[$tplidx]))
+            return false;
+
+        $tpl =& $this->tpls[$tplidx];
+        $w = $tpl['w'];
+        $h = $tpl['h'];
+        
+        if ($_w == 0 and $_h == 0) {
+            $_w = $w;
+            $_h = $h;
+        }
+
+    	if($_w == 0)
+    		$_w = $_h * $w / $h;
+    	if($_h == 0)
+    		$_h = $_w * $h / $w;
+    		
+        return array("w" => $_w, "h" => $_h);
+    }
+    
+    /**
+     * See FPDF/TCPDF-Documentation ;-)
+     */
+    public function SetFont($family, $style = '', $size = 0) {
+        if (is_subclass_of($this, 'TCPDF')) {
+        	$args = func_get_args();
+        	return call_user_func_array(array($this, 'TCPDF::SetFont'), $args);
+        }
+        
+        parent::SetFont($family, $style, $size);
+       
+        $fontkey = $this->FontFamily . $this->FontStyle;
+        
+        if ($this->_intpl) {
+            $this->_res['tpl'][$this->tpl]['fonts'][$fontkey] =& $this->fonts[$fontkey];
+        } else {
+            $this->_res['page'][$this->page]['fonts'][$fontkey] =& $this->fonts[$fontkey];
+        }
+    }
+    
+    /**
+     * See FPDF/TCPDF-Documentation ;-)
+     */
+    function Image(
+		$file, $x = '', $y = '', $w = 0, $h = 0, $type = '', $link = '', $align = '', $resize = false,
+		$dpi = 300, $palign = '', $ismask = false, $imgmask = false, $border = 0, $fitbox = false,
+		$hidden = false, $fitonpage = false, $alt = false, $altimgs = array()
+    ) {
+        if (is_subclass_of($this, 'TCPDF')) {
+        	$args = func_get_args();
+			return call_user_func_array(array($this, 'TCPDF::Image'), $args);
+        }
+        
+        $ret = parent::Image($file, $x, $y, $w, $h, $type, $link);
+        if ($this->_intpl) {
+            $this->_res['tpl'][$this->tpl]['images'][$file] =& $this->images[$file];
+        } else {
+            $this->_res['page'][$this->page]['images'][$file] =& $this->images[$file];
+        }
+        
+        return $ret;
+    }
+    
+    /**
+     * See FPDF-Documentation ;-)
+     *
+     * AddPage is not available when you're "in" a template.
+     */
+    function AddPage($orientation = '', $format = '', $keepmargins = false, $tocpage = false) {
+    	if (is_subclass_of($this, 'TCPDF')) {
+        	$args = func_get_args();
+        	return call_user_func_array(array($this, 'TCPDF::AddPage'), $args);
+        }
+        
+        if ($this->_intpl)
+            $this->Error('Adding pages in templates isn\'t possible!');
+            
+        parent::AddPage($orientation, $format);
+    }
+
+    /**
+     * Preserve adding Links in Templates ...won't work
+     */
+    function Link($x, $y, $w, $h, $link, $spaces = 0) {
+        if (is_subclass_of($this, 'TCPDF')) {
+        	$args = func_get_args();
+			return call_user_func_array(array($this, 'TCPDF::Link'), $args);
+        }
+        
+        if ($this->_intpl)
+            $this->Error('Using links in templates aren\'t possible!');
+            
+        parent::Link($x, $y, $w, $h, $link);
+    }
+    
+    function AddLink() {
+    	if (is_subclass_of($this, 'TCPDF')) {
+        	$args = func_get_args();
+			return call_user_func_array(array($this, 'TCPDF::AddLink'), $args);
+        }
+        
+        if ($this->_intpl)
+            $this->Error('Adding links in templates aren\'t possible!');
+        return parent::AddLink();
+    }
+    
+    function SetLink($link, $y = 0, $page = -1) {
+    	if (is_subclass_of($this, 'TCPDF')) {
+        	$args = func_get_args();
+			return call_user_func_array(array($this, 'TCPDF::SetLink'), $args);
+        }
+        
+        if ($this->_intpl)
+            $this->Error('Setting links in templates aren\'t possible!');
+        parent::SetLink($link, $y, $page);
+    }
+    
+    /**
+     * Private Method that writes the form xobjects
+     */
+    function _putformxobjects() {
+        $filter=($this->compress) ? '/Filter /FlateDecode ' : '';
+	    reset($this->tpls);
+        foreach($this->tpls AS $tplidx => $tpl) {
+
+            $p=($this->compress) ? gzcompress($tpl['buffer']) : $tpl['buffer'];
+    		$this->_newobj();
+    		$this->tpls[$tplidx]['n'] = $this->n;
+    		$this->_out('<<'.$filter.'/Type /XObject');
+            $this->_out('/Subtype /Form');
+            $this->_out('/FormType 1');
+            $this->_out(sprintf('/BBox [%.2F %.2F %.2F %.2F]',
+                // llx
+                $tpl['x'] * $this->k,
+                // lly
+                -$tpl['y'] * $this->k,
+                // urx
+                ($tpl['w'] + $tpl['x']) * $this->k,
+                // ury
+                ($tpl['h'] - $tpl['y']) * $this->k
+            ));
+            
+            if ($tpl['x'] != 0 || $tpl['y'] != 0) {
+                $this->_out(sprintf('/Matrix [1 0 0 1 %.5F %.5F]',
+                     -$tpl['x'] * $this->k * 2, $tpl['y'] * $this->k * 2
+                ));
+            }
+            
+            $this->_out('/Resources ');
+
+            $this->_out('<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
+        	if (isset($this->_res['tpl'][$tplidx]['fonts']) && count($this->_res['tpl'][$tplidx]['fonts'])) {
+            	$this->_out('/Font <<');
+                foreach($this->_res['tpl'][$tplidx]['fonts'] as $font)
+            		$this->_out('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R');
+            	$this->_out('>>');
+            }
+        	if(isset($this->_res['tpl'][$tplidx]['images']) && count($this->_res['tpl'][$tplidx]['images']) || 
+        	   isset($this->_res['tpl'][$tplidx]['tpls']) && count($this->_res['tpl'][$tplidx]['tpls']))
+        	{
+                $this->_out('/XObject <<');
+                if (isset($this->_res['tpl'][$tplidx]['images']) && count($this->_res['tpl'][$tplidx]['images'])) {
+                    foreach($this->_res['tpl'][$tplidx]['images'] as $image)
+              			$this->_out('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R');
+                }
+                if (isset($this->_res['tpl'][$tplidx]['tpls']) && count($this->_res['tpl'][$tplidx]['tpls'])) {
+                    foreach($this->_res['tpl'][$tplidx]['tpls'] as $i => $tpl)
+                        $this->_out($this->tplprefix . $i . ' ' . $tpl['n'] . ' 0 R');
+                }
+                $this->_out('>>');
+        	}
+        	$this->_out('>>');
+        	
+        	$this->_out('/Length ' . strlen($p) . ' >>');
+    		$this->_putstream($p);
+    		$this->_out('endobj');
+        }
+    }
+    
+    /**
+     * Overwritten to add _putformxobjects() after _putimages()
+     *
+     */
+    function _putimages() {
+        parent::_putimages();
+        $this->_putformxobjects();
+    }
+    
+    function _putxobjectdict() {
+        parent::_putxobjectdict();
+        
+        if (count($this->tpls)) {
+            foreach($this->tpls as $tplidx => $tpl) {
+                $this->_out(sprintf('%s%d %d 0 R', $this->tplprefix, $tplidx, $tpl['n']));
+            }
+        }
+    }
+
+    /**
+     * Private Method
+     */
+    function _out($s) {
+        if ($this->state == 2 && $this->_intpl) {
+            $this->tpls[$this->tpl]['buffer'] .= $s . "\n";
+        } else {
+            parent::_out($s);
+        }
+    }
+}

+ 701 - 0
htdocs/includes/tcpdi/tcpdi.php

@@ -0,0 +1,701 @@
+<?php
+//
+//  TCPDI - Version 1.0
+//  Based on FPDI - Version 1.4.4
+//
+//    Copyright 2004-2013 Setasign - Jan Slabon
+//
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  you may not use this file except in compliance with the License.
+//  You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+//
+
+// Dummy shim to allow unmodified use of fpdf_tpl
+class FPDF extends TCPDF {}
+
+require_once('fpdf_tpl.php');
+
+require_once('tcpdi_parser.php');
+
+
+class TCPDI extends FPDF_TPL {
+    /**
+     * Actual filename
+     * @var string
+     */
+    var $current_filename;
+
+    /**
+     * Parser-Objects
+     * @var array
+     */
+    var $parsers;
+
+    /**
+     * Current parser
+     * @var object
+     */
+    var $current_parser;
+
+    /**
+     * object stack
+     * @var array
+     */
+    var $_obj_stack;
+
+    /**
+     * done object stack
+     * @var array
+     */
+    var $_don_obj_stack;
+
+    /**
+     * Current Object Id.
+     * @var integer
+     */
+    var $_current_obj_id;
+
+    /**
+     * The name of the last imported page box
+     * @var string
+     */
+    var $lastUsedPageBox;
+
+    /**
+     * Cache for imported pages/template ids
+     * @var array
+     */
+    var $_importedPages = array();
+
+    /**
+     * Set a source-file
+     *
+     * @param string $filename a valid filename
+     * @return int number of available pages
+     */
+    function setSourceFile($filename) {
+        $this->current_filename = $filename;
+
+        if (!isset($this->parsers[$filename]))
+            $this->parsers[$filename] = $this->_getPdfParser($filename);
+        $this->current_parser =& $this->parsers[$filename];
+        $this->setPDFVersion(max($this->getPDFVersion(), $this->current_parser->getPDFVersion()));
+
+        return $this->parsers[$filename]->getPageCount();
+    }
+
+    /**
+     * Set a source-file PDF data
+     *
+     * @param string $pdfdata The PDF file content
+     * @return int number of available pages
+     */
+    function setSourceData($pdfdata) {
+        $filename = uniqid('tcpdi-');
+        $this->current_filename = $filename;
+
+        if (!isset($this->parsers[$filename]))
+            $this->parsers[$filename] = new tcpdi_parser($pdfdata, $filename);
+        $this->current_parser =& $this->parsers[$filename];
+        $this->setPDFVersion(max($this->getPDFVersion(), $this->current_parser->getPDFVersion()));
+
+        return $this->parsers[$filename]->getPageCount();
+    }
+
+    /**
+     * Returns a PDF parser object
+     *
+     * @param string $filename
+     * @return fpdi_pdf_parser
+     */
+    function _getPdfParser($filename) {
+        $data = file_get_contents($filename);
+    	return new tcpdi_parser($data, $filename);
+    }
+
+    /**
+     * Get the current PDF version
+     *
+     * @return string
+     */
+    function getPDFVersion() {
+		return $this->PDFVersion;
+	}
+
+	/**
+     * Set the PDF version
+     *
+     * @return string
+     */
+	function setPDFVersion($version = '1.3') {
+		$this->PDFVersion = $version;
+	}
+
+    /**
+     * Import a page
+     *
+     * @param int $pageno pagenumber
+     * @return int Index of imported page - to use with fpdf_tpl::useTemplate()
+     */
+    function importPage($pageno, $boxName = '/CropBox') {
+        if ($this->_intpl) {
+            return $this->error('Please import the desired pages before creating a new template.');
+        }
+
+        $fn = $this->current_filename;
+
+        // check if page already imported
+        $pageKey = $fn . '-' . ((int)$pageno) . $boxName;
+        if (isset($this->_importedPages[$pageKey]))
+            return $this->_importedPages[$pageKey];
+
+        $parser =& $this->parsers[$fn];
+        $parser->setPageno($pageno);
+
+        if (!in_array($boxName, $parser->availableBoxes))
+            return $this->Error(sprintf('Unknown box: %s', $boxName));
+
+        $pageboxes = $parser->getPageBoxes($pageno, $this->k);
+
+        /**
+         * MediaBox
+         * CropBox: Default -> MediaBox
+         * BleedBox: Default -> CropBox
+         * TrimBox: Default -> CropBox
+         * ArtBox: Default -> CropBox
+         */
+        if (!isset($pageboxes[$boxName]) && ($boxName == '/BleedBox' || $boxName == '/TrimBox' || $boxName == '/ArtBox'))
+            $boxName = '/CropBox';
+        if (!isset($pageboxes[$boxName]) && $boxName == '/CropBox')
+            $boxName = '/MediaBox';
+
+        if (!isset($pageboxes[$boxName]))
+            return false;
+
+        $this->lastUsedPageBox = $boxName;
+
+        $box = $pageboxes[$boxName];
+
+        $this->tpl++;
+        $this->tpls[$this->tpl] = array();
+        $tpl =& $this->tpls[$this->tpl];
+        $tpl['parser'] =& $parser;
+        $tpl['resources'] = $parser->getPageResources();
+        $tpl['buffer'] = $parser->getContent();
+        $tpl['box'] = $box;
+
+        // To build an array that can be used by PDF_TPL::useTemplate()
+        $this->tpls[$this->tpl] = array_merge($this->tpls[$this->tpl], $box);
+
+        // An imported page will start at 0,0 everytime. Translation will be set in _putformxobjects()
+        $tpl['x'] = 0;
+        $tpl['y'] = 0;
+
+        // handle rotated pages
+        $rotation = $parser->getPageRotation($pageno);
+        $tpl['_rotationAngle'] = 0;
+        if (isset($rotation[1]) && ($angle = $rotation[1] % 360) != 0) {
+        	$steps = $angle / 90;
+
+            $_w = $tpl['w'];
+            $_h = $tpl['h'];
+            $tpl['w'] = $steps % 2 == 0 ? $_w : $_h;
+            $tpl['h'] = $steps % 2 == 0 ? $_h : $_w;
+
+            if ($angle < 0)
+            	$angle += 360;
+
+        	$tpl['_rotationAngle'] = $angle * -1;
+        }
+
+        $this->_importedPages[$pageKey] = $this->tpl;
+
+        return $this->tpl;
+    }
+
+    /**
+     * Returns the last used page box
+     *
+     * @return string
+     */
+    function getLastUsedPageBox() {
+        return $this->lastUsedPageBox;
+    }
+
+
+    function useTemplate($tplidx, $_x = null, $_y = null, $_w = 0, $_h = 0, $adjustPageSize = false) {
+        if ($adjustPageSize == true && is_null($_x) && is_null($_y)) {
+            $size = $this->getTemplateSize($tplidx, $_w, $_h);
+            $orientation = $size['w'] > $size['h'] ? 'L' : 'P';
+            $size = array($size['w'], $size['h']);
+
+            $this->setPageFormat($size, $orientation);
+        }
+
+        $this->_out('q 0 J 1 w 0 j 0 G 0 g'); // reset standard values
+        $s = parent::useTemplate($tplidx, $_x, $_y, $_w, $_h);
+        $this->_out('Q');
+
+        return $s;
+    }
+
+    /**
+     * Private method, that rebuilds all needed objects of source files
+     */
+    function _putimportedobjects() {
+        if (is_array($this->parsers) && count($this->parsers) > 0) {
+            foreach($this->parsers AS $filename => $p) {
+                $this->current_parser =& $this->parsers[$filename];
+                if (isset($this->_obj_stack[$filename]) && is_array($this->_obj_stack[$filename])) {
+                    while(($n = key($this->_obj_stack[$filename])) !== null) {
+                        $nObj = $this->current_parser->getObjectVal($this->_obj_stack[$filename][$n][1]);
+
+                        $this->_newobj($this->_obj_stack[$filename][$n][0]);
+
+                        if ($nObj[0] == PDF_TYPE_STREAM) {
+							$this->pdf_write_value($nObj);
+                        } else {
+                            $this->pdf_write_value($nObj[1]);
+                        }
+
+                        $this->_out('endobj');
+                        $this->_obj_stack[$filename][$n] = null; // free memory
+                        unset($this->_obj_stack[$filename][$n]);
+                        reset($this->_obj_stack[$filename]);
+                    }
+                }
+
+                // We're done with this parser.  Clean it up to free a bit of RAM.
+                $this->current_parser->cleanUp();
+                unset($this->parsers[$filename]);
+            }
+        }
+    }
+
+
+    /**
+     * Private Method that writes the form xobjects
+     */
+    function _putformxobjects() {
+        $filter=($this->compress) ? '/Filter /FlateDecode ' : '';
+	    reset($this->tpls);
+        foreach($this->tpls AS $tplidx => $tpl) {
+            $p=($this->compress) ? gzcompress($tpl['buffer']) : $tpl['buffer'];
+    		$this->_newobj();
+    		$cN = $this->n; // TCPDF/Protection: rem current "n"
+
+    		$this->tpls[$tplidx]['n'] = $this->n;
+    		$this->_out('<<' . $filter . '/Type /XObject');
+            $this->_out('/Subtype /Form');
+            $this->_out('/FormType 1');
+
+            $this->_out(sprintf('/BBox [%.2F %.2F %.2F %.2F]',
+                (isset($tpl['box']['llx']) ? $tpl['box']['llx'] : $tpl['x']) * $this->k,
+                (isset($tpl['box']['lly']) ? $tpl['box']['lly'] : -$tpl['y']) * $this->k,
+                (isset($tpl['box']['urx']) ? $tpl['box']['urx'] : $tpl['w'] + $tpl['x']) * $this->k,
+                (isset($tpl['box']['ury']) ? $tpl['box']['ury'] : $tpl['h'] - $tpl['y']) * $this->k
+            ));
+
+            $c = 1;
+            $s = 0;
+            $tx = 0;
+            $ty = 0;
+
+            if (isset($tpl['box'])) {
+                $tx = -$tpl['box']['llx'];
+                $ty = -$tpl['box']['lly'];
+
+                if ($tpl['_rotationAngle'] <> 0) {
+                    $angle = $tpl['_rotationAngle'] * M_PI/180;
+                    $c=cos($angle);
+                    $s=sin($angle);
+
+                    switch($tpl['_rotationAngle']) {
+                        case -90:
+                           $tx = -$tpl['box']['lly'];
+                           $ty = $tpl['box']['urx'];
+                           break;
+                        case -180:
+                            $tx = $tpl['box']['urx'];
+                            $ty = $tpl['box']['ury'];
+                            break;
+                        case -270:
+                        	$tx = $tpl['box']['ury'];
+                            $ty = -$tpl['box']['llx'];
+                            break;
+                    }
+                }
+            } elseif ($tpl['x'] != 0 || $tpl['y'] != 0) {
+                $tx = -$tpl['x'] * 2;
+                $ty = $tpl['y'] * 2;
+            }
+
+            $tx *= $this->k;
+            $ty *= $this->k;
+
+            if ($c != 1 || $s != 0 || $tx != 0 || $ty != 0) {
+                $this->_out(sprintf('/Matrix [%.5F %.5F %.5F %.5F %.5F %.5F]',
+                    $c, $s, -$s, $c, $tx, $ty
+                ));
+            }
+
+            $this->_out('/Resources ');
+
+            if (isset($tpl['resources'])) {
+                $this->current_parser =& $tpl['parser'];
+                $this->pdf_write_value($tpl['resources']); // "n" will be changed
+            } else {
+                $this->_out('<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
+            	if (isset($this->_res['tpl'][$tplidx]['fonts']) && count($this->_res['tpl'][$tplidx]['fonts'])) {
+                	$this->_out('/Font <<');
+                    foreach($this->_res['tpl'][$tplidx]['fonts'] as $font)
+                		$this->_out('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R');
+                	$this->_out('>>');
+                }
+            	if(isset($this->_res['tpl'][$tplidx]['images']) && count($this->_res['tpl'][$tplidx]['images']) ||
+            	   isset($this->_res['tpl'][$tplidx]['tpls']) && count($this->_res['tpl'][$tplidx]['tpls']))
+            	{
+                    $this->_out('/XObject <<');
+                    if (isset($this->_res['tpl'][$tplidx]['images']) && count($this->_res['tpl'][$tplidx]['images'])) {
+                        foreach($this->_res['tpl'][$tplidx]['images'] as $image)
+                  			$this->_out('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R');
+                    }
+                    if (isset($this->_res['tpl'][$tplidx]['tpls']) && count($this->_res['tpl'][$tplidx]['tpls'])) {
+                        foreach($this->_res['tpl'][$tplidx]['tpls'] as $i => $tpl)
+                            $this->_out($this->tplprefix . $i . ' ' . $tpl['n'] . ' 0 R');
+                    }
+                    $this->_out('>>');
+            	}
+            	$this->_out('>>');
+            }
+
+            $this->_out('/Group <</Type/Group/S/Transparency>>');
+
+            $nN = $this->n; // TCPDF: rem new "n"
+            $this->n = $cN; // TCPDF: reset to current "n"
+
+        	$p = $this->_getrawstream($p);
+        	$this->_out('/Length ' . strlen($p) . ' >>');
+        	$this->_out("stream\n" . $p . "\nendstream");
+
+    		$this->_out('endobj');
+    		$this->n = $nN; // TCPDF: reset to new "n"
+        }
+
+        $this->_putimportedobjects();
+    }
+
+    /**
+     * Rewritten to handle existing own defined objects
+     */
+    function _newobj($obj_id = false, $onlynewobj = false) {
+        if (!$obj_id) {
+            $obj_id = ++$this->n;
+        }
+
+        //Begin a new object
+        if (!$onlynewobj) {
+            $this->offsets[$obj_id] = $this->bufferlen;
+            $this->_out($obj_id . ' 0 obj');
+            $this->_current_obj_id = $obj_id; // for later use with encryption
+        }
+
+        return $obj_id;
+    }
+
+    /**
+     * Writes a value
+     * Needed to rebuild the source document
+     *
+     * @param mixed $value A PDF-Value. Structure of values see cases in this method
+     */
+    function pdf_write_value(&$value)
+    {
+        switch ($value[0]) {
+            case PDF_TYPE_STRING:
+                if ($this->encrypted) {
+                    $value[1] = $this->_unescape($value[1]);
+                    $value[1] = $this->_encrypt_data($this->_current_obj_id, $value[1]);
+                    $value[1] = TCPDF_STATIC::_escape($value[1]);
+                }
+                break;
+
+            case PDF_TYPE_STREAM:
+                if ($this->encrypted) {
+                    $value[2][1] = $this->_encrypt_data($this->_current_obj_id, $value[2][1]);
+                    $value[1][1]['/Length'] = array(
+                        PDF_TYPE_NUMERIC,
+                        strlen($value[2][1])
+                    );
+                }
+                break;
+
+            case PDF_TYPE_HEX:
+                if ($this->encrypted) {
+                    $value[1] = $this->hex2str($value[1]);
+                    $value[1] = $this->_encrypt_data($this->_current_obj_id, $value[1]);
+
+                    // remake hexstring of encrypted string
+                    $value[1] = $this->str2hex($value[1]);
+                }
+                break;
+        }
+
+        switch ($value[0]) {
+
+            case PDF_TYPE_TOKEN:
+                $this->_straightOut('/'.$value[1] . ' ');
+                break;
+            case PDF_TYPE_NUMERIC:
+            case PDF_TYPE_REAL:
+                if (is_float($value[1]) && $value[1] != 0) {
+                    $this->_straightOut(rtrim(rtrim(sprintf('%F', $value[1]), '0'), '.') . ' ');
+                } else {
+                    $this->_straightOut($value[1] . ' ');
+                }
+                break;
+
+            case PDF_TYPE_ARRAY:
+
+                // An array. Output the proper
+                // structure and move on.
+
+                $this->_straightOut('[');
+                for ($i = 0; $i < count($value[1]); $i++) {
+                    $this->pdf_write_value($value[1][$i]);
+                }
+
+                $this->_out(']');
+                break;
+
+            case PDF_TYPE_DICTIONARY:
+
+                // A dictionary.
+                $this->_straightOut('<<');
+
+                reset ($value[1]);
+
+                while (list($k, $v) = each($value[1])) {
+                    $this->_straightOut($k . ' ');
+                    $this->pdf_write_value($v);
+                }
+
+                $this->_straightOut('>>');
+                break;
+
+            case PDF_TYPE_OBJREF:
+
+                // An indirect object reference
+                // Fill the object stack if needed
+                $cpfn =& $this->current_parser->uniqueid;
+
+                if (!isset($this->_don_obj_stack[$cpfn][$value[1]])) {
+                    $this->_newobj(false, true);
+                    $this->_obj_stack[$cpfn][$value[1]] = array($this->n, $value);
+                    $this->_don_obj_stack[$cpfn][$value[1]] = array($this->n, $value); // Value is maybee obsolete!!!
+                }
+                $objid = $this->_don_obj_stack[$cpfn][$value[1]][0];
+
+                $this->_out($objid . ' 0 R');
+                break;
+
+            case PDF_TYPE_STRING:
+
+                // A string.
+                $this->_straightOut('(' . $value[1] . ')');
+
+                break;
+
+            case PDF_TYPE_STREAM:
+
+                // A stream. First, output the
+                // stream dictionary, then the
+                // stream data itself.
+                $this->pdf_write_value($value[1]);
+                $this->_out('stream');
+                $this->_out($value[2][1]);
+                $this->_out('endstream');
+                break;
+
+            case PDF_TYPE_HEX:
+                $this->_straightOut('<' . $value[1] . '>');
+                break;
+
+            case PDF_TYPE_BOOLEAN:
+                $this->_straightOut($value[1] ? 'true ' : 'false ');
+                break;
+
+            case PDF_TYPE_NULL:
+                // The null object.
+
+                $this->_straightOut('null ');
+                break;
+        }
+    }
+
+    /**
+     * Modified so not each call will add a newline to the output.
+     */
+    function _straightOut($s) {
+        if ($this->state == 2) {
+			if ($this->inxobj) {
+				// we are inside an XObject template
+				$this->xobjects[$this->xobjid]['outdata'] .= $s;
+			} elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
+				// puts data before page footer
+				$pagebuff = $this->getPageBuffer($this->page);
+				$page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
+				$footer = substr($pagebuff, -$this->footerlen[$this->page]);
+				$this->setPageBuffer($this->page, $page.$s.$footer);
+				// update footer position
+				$this->footerpos[$this->page] += strlen($s);
+			} else {
+				// set page data
+				$this->setPageBuffer($this->page, $s, true);
+			}
+		} elseif ($this->state > 0) {
+			// set general data
+			$this->setBuffer($s);
+		}
+    }
+
+    /**
+     * rewritten to close opened parsers
+     *
+     */
+    function _enddoc() {
+        parent::_enddoc();
+        $this->_closeParsers();
+    }
+
+    /**
+     * close all files opened by parsers
+     */
+    function _closeParsers() {
+        if ($this->state > 2 && count($this->parsers) > 0) {
+          	$this->cleanUp();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Removes cylced references and closes the file handles of the parser objects
+     */
+    function cleanUp() {
+    	foreach ($this->parsers as $k => $_){
+        	$this->parsers[$k]->cleanUp();
+        	$this->parsers[$k] = null;
+        	unset($this->parsers[$k]);
+        }
+    }
+
+    // Functions from here on are taken from FPDI's fpdi2tcpdf_bridge.php to remove dependence on it
+    function _putstream($s, $n=0) {
+        $this->_out($this->_getstream($s, $n));
+    }
+
+    function _getxobjectdict() {
+        $out = parent::_getxobjectdict();
+        if (count($this->tpls)) {
+            foreach($this->tpls as $tplidx => $tpl) {
+                $out .= sprintf('%s%d %d 0 R', $this->tplprefix, $tplidx, $tpl['n']);
+            }
+        }
+
+        return $out;
+    }
+
+    /**
+     * Unescapes a PDF string
+     *
+     * @param string $s
+     * @return string
+     */
+    function _unescape($s) {
+        $out = '';
+        for ($count = 0, $n = strlen($s); $count < $n; $count++) {
+            if ($s[$count] != '\\' || $count == $n-1) {
+                $out .= $s[$count];
+            } else {
+                switch ($s[++$count]) {
+                    case ')':
+                    case '(':
+                    case '\\':
+                        $out .= $s[$count];
+                        break;
+                    case 'f':
+                        $out .= chr(0x0C);
+                        break;
+                    case 'b':
+                        $out .= chr(0x08);
+                        break;
+                    case 't':
+                        $out .= chr(0x09);
+                        break;
+                    case 'r':
+                        $out .= chr(0x0D);
+                        break;
+                    case 'n':
+                        $out .= chr(0x0A);
+                        break;
+                    case "\r":
+                        if ($count != $n-1 && $s[$count+1] == "\n")
+                            $count++;
+                        break;
+                    case "\n":
+                        break;
+                    default:
+                        // Octal-Values
+                        if (ord($s[$count]) >= ord('0') &&
+                            ord($s[$count]) <= ord('9')) {
+                            $oct = ''. $s[$count];
+
+                            if (ord($s[$count+1]) >= ord('0') &&
+                                ord($s[$count+1]) <= ord('9')) {
+                                $oct .= $s[++$count];
+
+                                if (ord($s[$count+1]) >= ord('0') &&
+                                    ord($s[$count+1]) <= ord('9')) {
+                                    $oct .= $s[++$count];
+                                }
+                            }
+
+                            $out .= chr(octdec($oct));
+                        } else {
+                            $out .= $s[$count];
+                        }
+                }
+            }
+        }
+        return $out;
+    }
+
+    /**
+     * Hexadecimal to string
+     *
+     * @param string $hex
+     * @return string
+     */
+    function hex2str($hex) {
+        return pack('H*', str_replace(array("\r", "\n", ' '), '', $hex));
+    }
+
+    /**
+     * String to hexadecimal
+     *
+     * @param string $str
+     * @return string
+     */
+    function str2hex($str) {
+        return current(unpack('H*', $str));
+    }
+}

+ 1397 - 0
htdocs/includes/tcpdi/tcpdi_parser.php

@@ -0,0 +1,1397 @@
+<?php
+//============================================================+
+// File name   : tcpdi_parser.php
+// Version     : 1.0
+// Begin       : 2013-09-25
+// Last Update : 2013-09-25
+// Author      : Paul Nicholls - https://github.com/pauln
+// License     : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
+//
+// Based on    : tcpdf_parser.php
+// Version     : 1.0.003
+// Begin       : 2011-05-23
+// Last Update : 2013-03-17
+// Author      : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
+// License     : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
+// -------------------------------------------------------------------
+// Copyright (C) 2011-2013 Nicola Asuni - Tecnick.com LTD
+//
+// This file is for use with the TCPDF software library.
+//
+// tcpdi_parser is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// tcpdi_parser 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the License
+// along with tcpdi_parser. If not, see
+// <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
+//
+// See LICENSE file for more information.
+// -------------------------------------------------------------------
+//
+// Description : This is a PHP class for parsing PDF documents.
+//
+//============================================================+
+
+/**
+ * @file
+ * This is a PHP class for parsing PDF documents.<br>
+ * @author Paul Nicholls
+ * @author Nicola Asuni
+ * @version 1.0
+ */
+
+// include class for decoding filters
+require_once(dirname(__FILE__).'/../tcpdf/include/tcpdf_filters.php');
+
+if (!defined ('PDF_TYPE_NULL'))
+    define ('PDF_TYPE_NULL', 0);
+if (!defined ('PDF_TYPE_NUMERIC'))
+    define ('PDF_TYPE_NUMERIC', 1);
+if (!defined ('PDF_TYPE_TOKEN'))
+    define ('PDF_TYPE_TOKEN', 2);
+if (!defined ('PDF_TYPE_HEX'))
+    define ('PDF_TYPE_HEX', 3);
+if (!defined ('PDF_TYPE_STRING'))
+    define ('PDF_TYPE_STRING', 4);
+if (!defined ('PDF_TYPE_DICTIONARY'))
+    define ('PDF_TYPE_DICTIONARY', 5);
+if (!defined ('PDF_TYPE_ARRAY'))
+    define ('PDF_TYPE_ARRAY', 6);
+if (!defined ('PDF_TYPE_OBJDEC'))
+    define ('PDF_TYPE_OBJDEC', 7);
+if (!defined ('PDF_TYPE_OBJREF'))
+    define ('PDF_TYPE_OBJREF', 8);
+if (!defined ('PDF_TYPE_OBJECT'))
+    define ('PDF_TYPE_OBJECT', 9);
+if (!defined ('PDF_TYPE_STREAM'))
+    define ('PDF_TYPE_STREAM', 10);
+if (!defined ('PDF_TYPE_BOOLEAN'))
+    define ('PDF_TYPE_BOOLEAN', 11);
+if (!defined ('PDF_TYPE_REAL'))
+    define ('PDF_TYPE_REAL', 12);
+
+/**
+ * @class tcpdi_parser
+ * This is a PHP class for parsing PDF documents.<br>
+ * Based on TCPDF_PARSER, part of the TCPDF project by Nicola Asuni.
+ * @brief This is a PHP class for parsing PDF documents..
+ * @version 1.0
+ * @author Paul Nicholls - github.com/pauln
+ * @author Nicola Asuni - info@tecnick.com
+ */
+class tcpdi_parser {
+	/**
+	 * Unique parser ID
+	 * @public
+	 */
+	public $uniqueid = '';
+
+	/**
+	 * Raw content of the PDF document.
+	 * @private
+	 */
+	private $pdfdata = '';
+
+	/**
+	 * XREF data.
+	 * @protected
+	 */
+	protected $xref = array();
+
+	/**
+	 * Object streams.
+	 * @protected
+	 */
+	protected $objstreams = array();
+
+	/**
+	 * Objects in objstreams.
+	 * @protected
+	 */
+	protected $objstreamobjs = array();
+
+	/**
+	 * List of seen XREF data locations.
+	 * @protected
+	 */
+	protected $xref_seen_offsets = array();
+
+	/**
+	 * Array of PDF objects.
+	 * @protected
+	 */
+	protected $objects = array();
+
+	/**
+	 * Array of object offsets.
+	 * @private
+	 */
+	private $objoffsets = array();
+
+	/**
+	 * Class object for decoding filters.
+	 * @private
+	 */
+	private $FilterDecoders;
+
+    /**
+     * Pages
+     *
+     * @private array
+     */
+    private $pages;
+
+    /**
+     * Page count
+     * @private integer
+     */
+    private $page_count;
+
+    /**
+     * actual page number
+     * @private integer
+     */
+    private $pageno;
+
+    /**
+     * PDF version of the loaded document
+     * @private string
+     */
+    private $pdfVersion;
+
+    /**
+     * Available BoxTypes
+     *
+     * @public array
+     */
+    public $availableBoxes = array('/MediaBox', '/CropBox', '/BleedBox', '/TrimBox', '/ArtBox');
+
+// -----------------------------------------------------------------------------
+
+	/**
+	 * Parse a PDF document an return an array of objects.
+	 * @param $data (string) PDF data to parse.
+	 * @public
+	 * @since 1.0.000 (2011-05-24)
+	 */
+	public function __construct($data, $uniqueid) {
+		if (empty($data)) {
+			$this->Error('Empty PDF data.');
+		}
+		$this->uniqueid = $uniqueid;
+		$this->pdfdata = $data;
+		// get length
+		$pdflen = strlen($this->pdfdata);
+		// initialize class for decoding filters
+		$this->FilterDecoders = new TCPDF_FILTERS();
+		// get xref and trailer data
+		$this->xref = $this->getXrefData();
+		$this->findObjectOffsets();
+		// parse all document objects
+		$this->objects = array();
+		/*foreach ($this->xref['xref'] as $obj => $offset) {
+			if (!isset($this->objects[$obj]) AND ($offset > 0)) {
+				// decode only objects with positive offset
+				//$this->objects[$obj] = $this->getIndirectObject($obj, $offset, true);
+			}
+		}*/
+        $this->getPDFVersion();
+		$this->readPages();
+	}
+
+	/**
+	 * Clean up when done, to free memory etc
+	 */
+	public function cleanUp() {
+		unset($this->pdfdata);
+		$this->pdfdata = '';
+		unset($this->objstreams);
+		$this->objstreams = array();
+		unset($this->objects);
+		$this->objects = array();
+		unset($this->objstreamobjs);
+		$this->objstreamobjs = array();
+		unset($this->xref);
+		$this->xref = array();
+		unset($this->objoffsets);
+		$this->objoffsets = array();
+		unset($this->pages);
+		$this->pages = array();
+	}
+
+	/**
+	 * Return an array of parsed PDF document objects.
+	 * @return (array) Array of parsed PDF document objects.
+	 * @public
+	 * @since 1.0.000 (2011-06-26)
+	 */
+	public function getParsedData() {
+		return array($this->xref, $this->objects, $this->pages);
+	}
+
+    /**
+     * Get PDF-Version
+     *
+     * And reset the PDF Version used in FPDI if needed
+     * @public
+     */
+    public function getPDFVersion() {
+        preg_match('/\d\.\d/', substr($this->pdfdata, 0, 16), $m);
+        if (isset($m[0]))
+            $this->pdfVersion = $m[0];
+        return $this->pdfVersion;
+    }
+
+    /**
+     * Read all /Page(es)
+     *
+     */
+    function readPages() {
+    	$params = $this->getObjectVal($this->xref['trailer'][1]['/Root']);
+    	$objref = null;
+    	foreach ($params[1][1] as $k=>$v) {
+    		if ($k == '/Pages') {
+    			$objref = $v;
+    			break;
+    		}
+    	}
+    	if ($objref == null || $objref[0] !== PDF_TYPE_OBJREF) {
+    		// Offset not found.
+    		return;
+    	}
+
+		$dict = $this->getObjectVal($objref);
+		if ($dict[0] == PDF_TYPE_OBJECT && $dict[1][0] == PDF_TYPE_DICTIONARY) {
+			// Dict wrapped in an object
+			$dict = $dict[1];
+		}
+
+		if ($dict[0] !== PDF_TYPE_DICTIONARY) {
+			return;
+		}
+
+		$this->pages = array();
+		if (isset($dict[1]['/Kids'])) {
+			$v = $dict[1]['/Kids'];
+			if ($v[0] == PDF_TYPE_ARRAY) {
+				foreach ($v[1] as $ref) {
+					$page = $this->getObjectVal($ref);
+					$this->readPage($page);
+				}
+			}
+		}
+
+		$this->page_count = count($this->pages);
+    }
+
+    /**
+     * Read a single /Page element, recursing through /Kids if necessary
+     *
+     */
+    private function readPage($page) {
+		if (isset($page[1][1]['/Kids'])) {
+			// Nested pages!
+			foreach ($page[1][1]['/Kids'][1] as $subref) {
+				$subpage = $this->getObjectVal($subref);
+				$this->readPage($subpage);
+			}
+		} else {
+			$this->pages[] = $page;
+		}
+    }
+
+    /**
+     * Get pagecount from sourcefile
+     *
+     * @return int
+     */
+    function getPageCount() {
+        return $this->page_count;
+    }
+
+	/**
+	 * Get Cross-Reference (xref) table and trailer data from PDF document data.
+	 * @param $offset (int) xref offset (if know).
+	 * @param $xref (array) previous xref array (if any).
+	 * @return Array containing xref and trailer data.
+	 * @protected
+	 * @since 1.0.000 (2011-05-24)
+	 */
+	protected function getXrefData($offset=0, $xref=array()) {
+		if ($offset == 0) {
+			// find last startxref
+			if (preg_match('/.*[\r\n]startxref[\s]*[\r\n]+([0-9]+)[\s]*[\r\n]+%%EOF/is', $this->pdfdata, $matches) == 0) {
+				$this->Error('Unable to find startxref');
+			}
+			$startxref = $matches[1];
+		} else {
+			if (preg_match('/([0-9]+[\s][0-9]+[\s]obj)/i', $this->pdfdata, $matches, PREG_OFFSET_CAPTURE, $offset)) {
+				// Cross-Reference Stream object
+				$startxref = $offset;
+			} elseif (preg_match('/[\r\n]startxref[\s]*[\r\n]+([0-9]+)[\s]*[\r\n]+%%EOF/i', $this->pdfdata, $matches, PREG_OFFSET_CAPTURE, $offset)) {
+				// startxref found
+				$startxref = $matches[1][0];
+			} else {
+				$this->Error('Unable to find startxref');
+			}
+		}
+		unset($matches);
+
+		// DOMPDF gets the startxref wrong, giving us the linebreak before the xref starts.
+		$startxref += strspn($this->pdfdata, "\r\n", $startxref);
+
+		// check xref position
+		if (strpos($this->pdfdata, 'xref', $startxref) == $startxref) {
+			// Cross-Reference
+			$xref = $this->decodeXref($startxref, $xref);
+		} else {
+			// Cross-Reference Stream
+			$xref = $this->decodeXrefStream($startxref, $xref);
+		}
+		if (empty($xref)) {
+			$this->Error('Unable to find xref');
+		}
+
+		return $xref;
+	}
+
+	/**
+	 * Decode the Cross-Reference section
+	 * @param $startxref (int) Offset at which the xref section starts.
+	 * @param $xref (array) Previous xref array (if any).
+	 * @return Array containing xref and trailer data.
+	 * @protected
+	 * @since 1.0.000 (2011-06-20)
+	 */
+	protected function decodeXref($startxref, $xref=array()) {
+		$this->xref_seen_offsets[] = $startxref;
+        if (!isset($xref['xref_location'])) {
+            $xref['xref_location'] = $startxref;
+            $xref['max_object'] = 0;
+    	}
+		// extract xref data (object indexes and offsets)
+		$xoffset = $startxref + 5;
+		// initialize object number
+		$obj_num = 0;
+		$offset = $xoffset;
+		while (preg_match('/^([0-9]+)[\s]([0-9]+)[\s]?([nf]?)/im', $this->pdfdata, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) {
+			$offset = (strlen($matches[0][0]) + $matches[0][1]);
+			if ($matches[3][0] == 'n') {
+				// create unique object index: [object number]_[generation number]
+				$gen_num = intval($matches[2][0]);
+				$index = $obj_num.'_'.$gen_num;
+				// check if object already exist
+				if (!isset($xref['xref'][$obj_num][$gen_num])) {
+					// store object offset position
+					$xref['xref'][$obj_num][$gen_num] = intval($matches[1][0]);
+				}
+				++$obj_num;
+				$offset += 2;
+			} elseif ($matches[3][0] == 'f') {
+				++$obj_num;
+				$offset += 2;
+			} else {
+				// object number (index)
+				$obj_num = intval($matches[1][0]);
+			}
+		}
+		unset($matches);
+		$xref['max_object'] = max($xref['max_object'], $obj_num);
+		// get trailer data
+		if (preg_match('/trailer[\s]*<<(.*)>>[\s]*[\r\n]+startxref[\s]*[\r\n]+/isU', $this->pdfdata, $matches, PREG_OFFSET_CAPTURE, $xoffset) > 0) {
+			$trailer_data = $matches[1][0];
+			if (!isset($xref['trailer']) OR empty($xref['trailer'])) {
+				// get only the last updated version
+				$xref['trailer'] = array();
+				$xref['trailer'][0] = PDF_TYPE_DICTIONARY;
+				$xref['trailer'][1] = array();
+				// parse trailer_data
+				if (preg_match('/Size[\s]+([0-9]+)/i', $trailer_data, $matches) > 0) {
+					$xref['trailer'][1]['/Size'] = array(PDF_TYPE_NUMERIC, intval($matches[1]));
+				}
+				if (preg_match('/Root[\s]+([0-9]+)[\s]+([0-9]+)[\s]+R/i', $trailer_data, $matches) > 0) {
+					$xref['trailer'][1]['/Root'] = array(PDF_TYPE_OBJREF, intval($matches[1]), intval($matches[2]));
+				}
+				if (preg_match('/Encrypt[\s]+([0-9]+)[\s]+([0-9]+)[\s]+R/i', $trailer_data, $matches) > 0) {
+					$xref['trailer'][1]['/Encrypt'] = array(PDF_TYPE_OBJREF, intval($matches[1]), intval($matches[2]));
+				}
+				if (preg_match('/Info[\s]+([0-9]+)[\s]+([0-9]+)[\s]+R/i', $trailer_data, $matches) > 0) {
+					$xref['trailer'][1]['/Info'] = array(PDF_TYPE_OBJREF, intval($matches[1]), intval($matches[2]));
+				}
+				if (preg_match('/ID[\s]*[\[][\s]*[<]([^>]*)[>][\s]*[<]([^>]*)[>]/i', $trailer_data, $matches) > 0) {
+					$xref['trailer'][1]['/ID'] = array(PDF_TYPE_ARRAY, array());
+					$xref['trailer'][1]['/ID'][1][0] = array(PDF_TYPE_HEX, $matches[1]);
+					$xref['trailer'][1]['/ID'][1][1] = array(PDF_TYPE_HEX, $matches[2]);
+				}
+			}
+			if (preg_match('/Prev[\s]+([0-9]+)/i', $trailer_data, $matches) > 0) {
+				// get previous xref
+				$prevoffset = intval($matches[1]);
+				if (!in_array($prevoffset, $this->xref_seen_offsets)) {
+					$this->xref_seen_offsets[] = $prevoffset;
+					$xref = $this->getXrefData($prevoffset, $xref);
+				}
+			}
+			unset($matches);
+		} else {
+			$this->Error('Unable to find trailer');
+		}
+		return $xref;
+	}
+
+	/**
+	 * Decode the Cross-Reference Stream section
+	 * @param $startxref (int) Offset at which the xref section starts.
+	 * @param $xref (array) Previous xref array (if any).
+	 * @return Array containing xref and trailer data.
+	 * @protected
+	 * @since 1.0.003 (2013-03-16)
+	 */
+	protected function decodeXrefStream($startxref, $xref=array()) {
+		// try to read Cross-Reference Stream
+		list($xrefobj, $unused) = $this->getRawObject($startxref);
+		$xrefcrs = $this->getIndirectObject($xrefobj[1], $startxref, true);
+        if (!isset($xref['xref_location'])) {
+            $xref['xref_location'] = $startxref;
+            $xref['max_object'] = 0;
+    	}
+        if (!isset($xref['xref'])) {
+            $xref['xref'] = array();
+    	}
+		if (!isset($xref['trailer']) OR empty($xref['trailer'])) {
+			// get only the last updated version
+			$xref['trailer'] = array();
+			$xref['trailer'][0] = PDF_TYPE_DICTIONARY;
+			$xref['trailer'][1] = array();
+			$filltrailer = true;
+		} else {
+			$filltrailer = false;
+		}
+		$valid_crs = false;
+		$sarr = $xrefcrs[0][1];
+		$keys = array_keys($sarr);
+		$columns = 1; // Default as per PDF 32000-1:2008.
+		$predictor = 1; // Default as per PDF 32000-1:2008.
+		foreach ($keys as $k=>$key) {
+			$v = $sarr[$key];
+			if (($key == '/Type') AND ($v[0] == PDF_TYPE_TOKEN AND ($v[1] == 'XRef'))) {
+				$valid_crs = true;
+			} elseif (($key == '/Index') AND ($v[0] == PDF_TYPE_ARRAY AND count($v[1] >= 2))) {
+				// first object number in the subsection
+				$index_first = intval($v[1][0][1]);
+				// number of entries in the subsection
+				$index_entries = intval($v[1][1][1]);
+			} elseif (($key == '/Prev') AND ($v[0] == PDF_TYPE_NUMERIC)) {
+				// get previous xref offset
+				$prevxref = intval($v[1]);
+			} elseif (($key == '/W') AND ($v[0] == PDF_TYPE_ARRAY)) {
+				// number of bytes (in the decoded stream) of the corresponding field
+				$wb = array();
+				$wb[0] = intval($v[1][0][1]);
+				$wb[1] = intval($v[1][1][1]);
+				$wb[2] = intval($v[1][2][1]);
+			} elseif (($key == '/DecodeParms') AND ($v[0] == PDF_TYPE_DICTIONARY)) {
+				$decpar = $v[1];
+				foreach ($decpar as $kdc => $vdc) {
+					if (($kdc == '/Columns') AND ($vdc[0] == PDF_TYPE_NUMERIC)) {
+						$columns = intval($vdc[1]);
+					} elseif (($kdc == '/Predictor') AND ($vdc[0] == PDF_TYPE_NUMERIC)) {
+						$predictor = intval($vdc[1]);
+					}
+				}
+			} elseif ($filltrailer) {
+				switch($key) {
+					case '/Size':
+					case '/Root':
+					case '/Info':
+					case '/ID':
+						$xref['trailer'][1][$key] = $v;
+						break;
+					default:
+						break;
+				}
+			}
+		}
+		// decode data
+		$obj_num = 0;
+		if ($valid_crs AND isset($xrefcrs[1][3][0])) {
+			// number of bytes in a row
+			$rowlen = ($columns + 1);
+			// convert the stream into an array of integers
+			$sdata = unpack('C*', $xrefcrs[1][3][0]);
+			// split the rows
+			$sdata = array_chunk($sdata, $rowlen);
+			// initialize decoded array
+			$ddata = array();
+			// initialize first row with zeros
+			$prev_row = array_fill (0, $rowlen, 0);
+			// for each row apply PNG unpredictor
+			foreach ($sdata as $k => $row) {
+				// initialize new row
+				$ddata[$k] = array();
+				// get PNG predictor value
+				if (empty($predictor)) {
+					$predictor = (10 + $row[0]);
+				}
+				// for each byte on the row
+				for ($i=1; $i<=$columns; ++$i) {
+					// new index
+					$j = ($i - 1);
+					$row_up = $prev_row[$j];
+					if ($i == 1) {
+						$row_left = 0;
+						$row_upleft = 0;
+					} else {
+						$row_left = $row[($i - 1)];
+						$row_upleft = $prev_row[($j - 1)];
+					}
+					switch ($predictor) {
+						case 1: // No prediction (equivalent to PNG None)
+						case 10: { // PNG prediction (on encoding, PNG None on all rows)
+							$ddata[$k][$j] = $row[$i];
+							break;
+						}
+						case 11: { // PNG prediction (on encoding, PNG Sub on all rows)
+							$ddata[$k][$j] = (($row[$i] + $row_left) & 0xff);
+							break;
+						}
+						case 12: { // PNG prediction (on encoding, PNG Up on all rows)
+							$ddata[$k][$j] = (($row[$i] + $row_up) & 0xff);
+							break;
+						}
+						case 13: { // PNG prediction (on encoding, PNG Average on all rows)
+							$ddata[$k][$j] = (($row[$i] + (($row_left + $row_up) / 2)) & 0xff);
+							break;
+						}
+						case 14: { // PNG prediction (on encoding, PNG Paeth on all rows)
+							// initial estimate
+							$p = ($row_left + $row_up - $row_upleft);
+							// distances
+							$pa = abs($p - $row_left);
+							$pb = abs($p - $row_up);
+							$pc = abs($p - $row_upleft);
+							$pmin = min($pa, $pb, $pc);
+							// return minumum distance
+							switch ($pmin) {
+								case $pa: {
+									$ddata[$k][$j] = (($row[$i] + $row_left) & 0xff);
+									break;
+								}
+								case $pb: {
+									$ddata[$k][$j] = (($row[$i] + $row_up) & 0xff);
+									break;
+								}
+								case $pc: {
+									$ddata[$k][$j] = (($row[$i] + $row_upleft) & 0xff);
+									break;
+								}
+							}
+							break;
+						}
+						default: { // PNG prediction (on encoding, PNG optimum)
+							$this->Error("Unknown PNG predictor $predictor");
+							break;
+						}
+					}
+				}
+				$prev_row = $ddata[$k];
+			} // end for each row
+			// complete decoding
+			unset($sdata);
+			$sdata = array();
+			// for every row
+			foreach ($ddata as $k => $row) {
+				// initialize new row
+				$sdata[$k] = array(0, 0, 0);
+				if ($wb[0] == 0) {
+					// default type field
+					$sdata[$k][0] = 1;
+				}
+				$i = 0; // count bytes on the row
+				// for every column
+				for ($c = 0; $c < 3; ++$c) {
+					// for every byte on the column
+					for ($b = 0; $b < $wb[$c]; ++$b) {
+						$sdata[$k][$c] += ($row[$i] << (($wb[$c] - 1 - $b) * 8));
+						++$i;
+					}
+				}
+			}
+			unset($ddata);
+			// fill xref
+			if (isset($index_first)) {
+				$obj_num = $index_first;
+			} else {
+				$obj_num = 0;
+			}
+			foreach ($sdata as $k => $row) {
+				switch ($row[0]) {
+					case 0: { // (f) linked list of free objects
+						++$obj_num;
+						break;
+					}
+					case 1: { // (n) objects that are in use but are not compressed
+						// create unique object index: [object number]_[generation number]
+						$index = $obj_num.'_'.$row[2];
+						// check if object already exist
+						if (!isset($xref['xref'][$obj_num][$row[2]])) {
+							// store object offset position
+							$xref['xref'][$obj_num][$row[2]] = $row[1];
+						}
+						++$obj_num;
+						break;
+					}
+					case 2: { // compressed objects
+						// $row[1] = object number of the object stream in which this object is stored
+						// $row[2] = index of this object within the object stream
+						/*$index = $row[1].'_0_'.$row[2];
+						$xref['xref'][$row[1]][0][$row[2]] = -1;*/
+						break;
+					}
+					default: { // null objects
+						break;
+					}
+				}
+			}
+		} // end decoding data
+		$xref['max_object'] = max($xref['max_object'], $obj_num);
+		if (isset($prevxref)) {
+			// get previous xref
+			$xref = $this->getXrefData($prevxref, $xref);
+		}
+		return $xref;
+	}
+
+	/**
+	 * Get raw stream data
+	 * @param $offset (int) Stream offset.
+	 * @param $length (int) Stream length.
+	 * @return string Steam content
+	 * @protected
+	 */
+	protected function getRawStream($offset, $length) {
+		$offset += strspn($this->pdfdata, "\x00\x09\x0a\x0c\x0d\x20", $offset);
+		$offset += 6; // "stream"
+		$offset += strspn($this->pdfdata, "\r\n", $offset);
+
+		$obj = array();
+		$obj[] = PDF_TYPE_STREAM;
+		$obj[] = substr($this->pdfdata, $offset, $length);
+
+		return array($obj, $offset+$length);
+	}
+
+	/**
+	 * Get object type, raw value and offset to next object
+	 * @param $offset (int) Object offset.
+	 * @return array containing object type, raw value and offset to next object
+	 * @protected
+	 * @since 1.0.000 (2011-06-20)
+	 */
+	protected function getRawObject($offset=0, $data=null) {
+		if ($data == null) {
+			$data =& $this->pdfdata;
+		}
+		$objtype = ''; // object type to be returned
+		$objval = ''; // object value to be returned
+		// skip initial white space chars: \x00 null (NUL), \x09 horizontal tab (HT), \x0A line feed (LF), \x0C form feed (FF), \x0D carriage return (CR), \x20 space (SP)
+		while (strspn($data{$offset}, "\x00\x09\x0a\x0c\x0d\x20") == 1) {
+			$offset++;
+		}
+		// get first char
+		$char = $data{$offset};
+		// get object type
+		switch ($char) {
+			case '%': { // \x25 PERCENT SIGN
+				// skip comment and search for next token
+				$next = strcspn($data, "\r\n", $offset);
+				if ($next > 0) {
+					$offset += $next;
+					list($obj, $unused) = $this->getRawObject($offset, $data);
+					return $obj;
+				}
+				break;
+			}
+			case '/': { // \x2F SOLIDUS
+				// name object
+				$objtype = PDF_TYPE_TOKEN;
+				++$offset;
+				$length = strcspn($data, "\x00\x09\x0a\x0c\x0d\x20\x28\x29\x3c\x3e\x5b\x5d\x7b\x7d\x2f\x25", $offset);
+				$objval = substr($data, $offset, $length);
+				$offset += $length;
+				break;
+			}
+			case '(':   // \x28 LEFT PARENTHESIS
+			case ')': { // \x29 RIGHT PARENTHESIS
+				// literal string object
+				$objtype = PDF_TYPE_STRING;
+				++$offset;
+				$strpos = $offset;
+				if ($char == '(') {
+					$open_bracket = 1;
+					while ($open_bracket > 0) {
+						if (!isset($data{$strpos})) {
+							break;
+						}
+						$ch = $data{$strpos};
+						switch ($ch) {
+							case '\\': { // REVERSE SOLIDUS (5Ch) (Backslash)
+								// skip next character
+								++$strpos;
+								break;
+							}
+							case '(': { // LEFT PARENHESIS (28h)
+								++$open_bracket;
+								break;
+							}
+							case ')': { // RIGHT PARENTHESIS (29h)
+								--$open_bracket;
+								break;
+							}
+						}
+						++$strpos;
+					}
+					$objval = substr($data, $offset, ($strpos - $offset - 1));
+					$offset = $strpos;
+				}
+				break;
+			}
+			case '[':   // \x5B LEFT SQUARE BRACKET
+			case ']': { // \x5D RIGHT SQUARE BRACKET
+				// array object
+				$objtype = PDF_TYPE_ARRAY;
+				++$offset;
+				if ($char == '[') {
+					// get array content
+					$objval = array();
+					do {
+						// get element
+						list($element, $offset) = $this->getRawObject($offset, $data);
+						$objval[] = $element;
+					} while ($element[0] !== ']');
+					// remove closing delimiter
+					array_pop($objval);
+				} else {
+					$objtype = ']';
+				}
+				break;
+			}
+			case '<':   // \x3C LESS-THAN SIGN
+			case '>': { // \x3E GREATER-THAN SIGN
+				if (isset($data{($offset + 1)}) AND ($data{($offset + 1)} == $char)) {
+					// dictionary object
+					$objtype = PDF_TYPE_DICTIONARY;
+					if ($char == '<') {
+						list ($objval, $offset) = $this->getDictValue($offset, $data);
+					} else {
+						$objtype = '>>';
+						$offset += 2;
+					}
+				} else {
+					// hexadecimal string object
+					$objtype = PDF_TYPE_HEX;
+					++$offset;
+					// The "Panose" entry in the FontDescriptor Style dict seems to have hex bytes separated by spaces.
+					if (($char == '<') AND (preg_match('/^([0-9A-Fa-f ]+)[>]/iU', substr($data, $offset), $matches) == 1)) {
+						$objval = $matches[1];
+						$offset += strlen($matches[0]);
+						unset($matches);
+					}
+				}
+				break;
+			}
+			default: {
+				$frag = $data{$offset} . @$data{$offset+1} . @$data{$offset+2} . @$data{$offset+3};
+				switch ($frag) {
+					case 'endo':
+						// indirect object
+						$objtype = 'endobj';
+						$offset += 6;
+						break;
+					case 'stre':
+						// Streams should always be indirect objects, and thus processed by getRawStream().
+						// If we get here, treat it as a null object as something has gone wrong.
+					case 'null':
+						// null object
+						$objtype = PDF_TYPE_NULL;
+						$offset += 4;
+						$objval = 'null';
+						break;
+					case 'true':
+						// boolean true object
+						$objtype = PDF_TYPE_BOOLEAN;
+						$offset += 4;
+						$objval = true;
+						break;
+					case 'fals':
+						// boolean false object
+						$objtype = PDF_TYPE_BOOLEAN;
+						$offset += 5;
+						$objval = false;
+						break;
+					case 'ends':
+						// end stream object
+						$objtype = 'endstream';
+						$offset += 9;
+						break;
+					default:
+						if (preg_match('/^([0-9]+)[\s]+([0-9]+)[\s]+([Robj]{1,3})/i', substr($data, $offset, 33), $matches) == 1) {
+							if ($matches[3] == 'R') {
+								// indirect object reference
+								$objtype = PDF_TYPE_OBJREF;
+								$offset += strlen($matches[0]);
+								$objval = array(intval($matches[1]), intval($matches[2]));
+							} elseif ($matches[3] == 'obj') {
+								// object start
+								$objtype = PDF_TYPE_OBJECT;
+								$objval = intval($matches[1]).'_'.intval($matches[2]);
+								$offset += strlen ($matches[0]);
+							}
+						} elseif (($numlen = strspn($data, '+-.0123456789', $offset)) > 0) {
+							// numeric object
+							$objval = substr($data, $offset, $numlen);
+							$objtype = (intval($objval) != $objval) ? PDF_TYPE_REAL : PDF_TYPE_NUMERIC;
+							$offset += $numlen;
+						}
+						unset($matches);
+						break;
+				}
+				break;
+			}
+		}
+		$obj = array();
+		$obj[] = $objtype;
+		if ($objtype == PDF_TYPE_OBJREF && is_array($objval)) {
+			foreach ($objval as $val) {
+				$obj[] = $val;
+			}
+		} else {
+			$obj[] = $objval;
+		}
+		return array($obj, $offset);
+	}
+	private function getDictValue($offset, &$data) {
+		$objval = array();
+
+		// Extract dict from data.
+		$i=1;
+		$dict = '';
+		$offset += 2;
+		do {
+			if ($data{$offset} == '>' && $data{$offset+1} == '>') {
+				$i--;
+				$dict .= '>>';
+				$offset += 2;
+			} else if ($data{$offset} == '<' && $data{$offset+1} == '<') {
+				$i++;
+				$dict .= '<<';
+				$offset += 2;
+			} else {
+				$dict .= $data{$offset};
+				$offset++;
+			}
+		} while ($i>0);
+
+		// Now that we have just the dict, parse it.
+		$dictoffset = 0;
+		do {
+			// Get dict element.
+			list($key, $eloffset) = $this->getRawObject($dictoffset, $dict);
+			if ($key[0] == '>>') {
+				break;
+			}
+			list($element, $dictoffset) = $this->getRawObject($eloffset, $dict);
+			$objval['/'.$key[1]] = $element;
+			unset($key);
+			unset($element);
+		} while (true);
+
+		return array($objval, $offset);
+	}
+
+	/**
+	 * Get content of indirect object.
+	 * @param $obj_ref (string) Object number and generation number separated by underscore character.
+	 * @param $offset (int) Object offset.
+	 * @param $decoding (boolean) If true decode streams.
+	 * @return array containing object data.
+	 * @protected
+	 * @since 1.0.000 (2011-05-24)
+	 */
+	protected function getIndirectObject($obj_ref, $offset=0, $decoding=true) {
+		$obj = explode('_', $obj_ref);
+		if (($obj === false) OR (count($obj) != 2)) {
+			$this->Error('Invalid object reference: '.$obj);
+			return;
+		}
+		$objref = $obj[0].' '.$obj[1].' obj';
+
+		if (strpos($this->pdfdata, $objref, $offset) != $offset) {
+			// an indirect reference to an undefined object shall be considered a reference to the null object
+			return array('null', 'null', $offset);
+		}
+		// starting position of object content
+		$offset += strlen($objref);
+		// get array of object content
+		$objdata = array();
+		$i = 0; // object main index
+		do {
+			if (($i > 0) AND (isset($objdata[($i - 1)][0])) AND ($objdata[($i - 1)][0] == PDF_TYPE_DICTIONARY) AND array_key_exists('/Length', $objdata[($i - 1)][1])) {
+				// Stream - get using /Length in stream's dict
+				$lengthobj = $objdata[($i-1)][1]['/Length'];
+				if ($lengthobj[0] === PDF_TYPE_OBJREF) {
+					$lengthobj = $this->getObjectVal($lengthobj);
+					if ($lengthobj[0] === PDF_TYPE_OBJECT) {
+						$lengthobj = $lengthobj[1];
+					}
+				}
+				$streamlength = $lengthobj[1];
+				list($element, $offset) = $this->getRawStream($offset, $streamlength);
+			} else {
+				// get element
+				list($element, $offset) = $this->getRawObject($offset);
+			}
+			// decode stream using stream's dictionary information
+			if ($decoding AND ($element[0] == PDF_TYPE_STREAM) AND (isset($objdata[($i - 1)][0])) AND ($objdata[($i - 1)][0] == PDF_TYPE_DICTIONARY)) {
+				$element[3] = $this->decodeStream($objdata[($i - 1)][1], $element[1]);
+			}
+			$objdata[$i] = $element;
+			++$i;
+		} while ($element[0] != 'endobj');
+		// remove closing delimiter
+		array_pop($objdata);
+		// return raw object content
+		return $objdata;
+	}
+
+	/**
+	 * Get the content of object, resolving indect object reference if necessary.
+	 * @param $obj (string) Object value.
+	 * @return array containing object data.
+	 * @public
+	 * @since 1.0.000 (2011-06-26)
+	 */
+	public function getObjectVal($obj) {
+		if ($obj[0] == PDF_TYPE_OBJREF) {
+			if (strpos($obj[1], '_') !== false) {
+				$key = explode('_', $obj[1]);
+			} else {
+				$key = array($obj[1], $obj[2]);
+			}
+
+			$ret = array(0=>PDF_TYPE_OBJECT, 'obj'=>$key[0], 'gen'=>$key[1]);
+
+			// reference to indirect object
+			$object = null;
+			if (isset($this->objects[$key[0]][$key[1]])) {
+				// this object has been already parsed
+				$object = $this->objects[$key[0]][$key[1]];
+			} elseif (($offset = $this->findObjectOffset($key)) !== false) {
+				// parse new object
+				$this->objects[$key[0]][$key[1]] = $this->getIndirectObject($key[0].'_'.$key[1], $offset, false);
+				$object = $this->objects[$key[0]][$key[1]];
+			} elseif (($key[1] == 0) && isset($this->objstreamobjs[$key[0]])) {
+				// Object is in an object stream
+				$streaminfo = $this->objstreamobjs[$key[0]];
+				$objs = $streaminfo[0];
+				if (!isset($this->objstreams[$objs[0]][$objs[1]])) {
+					// Fetch and decode object stream
+        			$offset = $this->findObjectOffset($objs);;
+        			$objstream = $this->getObjectVal(array(PDF_TYPE_OBJREF, $objs[0], $objs[1]));
+					$decoded = $this->decodeStream($objstream[1][1], $objstream[2][1]);
+					$this->objstreams[$objs[0]][$objs[1]] = $decoded[0]; // Store just the data, in case we need more from this objstream
+					// Free memory
+					unset($objstream);
+					unset($decoded);
+				}
+				$this->objects[$key[0]][$key[1]] = $this->getRawObject($streaminfo[1], $this->objstreams[$objs[0]][$objs[1]]);
+				$object = $this->objects[$key[0]][$key[1]];
+			}
+			if (!is_null($object)) {
+				$ret[1] = $object[0];
+				if (isset($object[1][0]) && $object[1][0] == PDF_TYPE_STREAM) {
+					$ret[0] = PDF_TYPE_STREAM;
+					$ret[2] = $object[1];
+				}
+				return $ret;
+			}
+		}
+		return $obj;
+	}
+
+    /**
+     * Extract object stream to find out what it contains.
+     *
+     */
+    function extractObjectStream($key) {
+		$objref = array(PDF_TYPE_OBJREF, $key[0], $key[1]);
+    	$obj = $this->getObjectVal($objref);
+    	if ($obj[0] !== PDF_TYPE_STREAM || !isset($obj[1][1]['/First'][1])) {
+    		// Not a valid object stream dictionary - skip it.
+    		return;
+    	}
+    	$stream = $this->decodeStream($obj[1][1], $obj[2][1]);// Decode object stream, as we need the first bit
+    	$first = intval($obj[1][1]['/First'][1]);
+    	$ints = explode(' ', substr($stream[0], 0, $first)); // Get list of object / offset pairs
+    	for ($j=1; $j<count($ints); $j++) {
+    		if (($j % 2) == 1) {
+    			$this->objstreamobjs[$ints[$j-1]] = array($key, $ints[$j]+$first);
+    		}
+    	}
+
+    	// Free memory - we may not need this at all.
+    	unset($obj);
+    	unset($stream);
+    }
+
+	/**
+	 * Find all object offsets.  Saves having to scour the file multiple times.
+	 * @private
+	 */
+	private function findObjectOffsets() {
+		$this->objoffsets = array();
+		if (preg_match_all('/(*ANYCRLF)^[\s]*([0-9]+)[\s]+([0-9]+)[\s]+obj/im', $this->pdfdata, $matches, PREG_OFFSET_CAPTURE) >= 1) {
+			$i = 0;
+			foreach($matches[0] as $match) {
+				$offset = $match[1] + strspn($match[0], "\x00\x09\x0a\x0c\x0d\x20");
+				$this->objoffsets[trim($match[0])] = $offset;
+				$dictoffset = $match[1] + strlen($match[0]);
+				if (preg_match('|^\s+<<[^>]+/ObjStm|', substr($this->pdfdata, $dictoffset, 256), $objstm) == 1) {
+					$this->extractObjectStream(array($matches[1][$i][0], $matches[2][$i][0]));
+				}
+				$i++;
+			}
+		}
+		unset($matches);
+	}
+
+	/**
+	 * Get offset of an object.  Checks xref first, then offsets found by scouring the file.
+	 * @param $key (array) Object key to find (obj, gen).
+	 * @return int Offset of the object in $this->pdfdata.
+	 * @private
+	 */
+	private function findObjectOffset($key) {
+		$objref = $key[0].' '.$key[1].' obj';
+		if (isset($this->xref['xref'][$key[0]][$key[1]])) {
+			$offset = $this->xref['xref'][$key[0]][$key[1]];
+			if (strpos($this->pdfdata, $objref, $offset) === $offset) {
+				// Offset is in xref table and matches actual position in file
+				//echo "Offset in XREF is correct, returning<br>";
+				return $this->xref['xref'][$key[0]][$key[1]];
+			}
+		}
+		if (array_key_exists($objref, $this->objoffsets)) {
+			//echo "Offset found in internal reftable<br>";
+			return $this->objoffsets[$objref];
+		}
+		return false;
+	}
+
+	/**
+	 * Decode the specified stream.
+	 * @param $sdic (array) Stream's dictionary array.
+	 * @param $stream (string) Stream to decode.
+	 * @return array containing decoded stream data and remaining filters.
+	 * @protected
+	 * @since 1.0.000 (2011-06-22)
+	 */
+	protected function decodeStream($sdic, $stream) {
+		// get stream lenght and filters
+		$slength = strlen($stream);
+		if ($slength <= 0) {
+			return array('', array());
+		}
+		$filters = array();
+		foreach ($sdic as $k => $v) {
+			if ($v[0] == PDF_TYPE_TOKEN) {
+				if (($k == '/Length') AND ($v[0] == PDF_TYPE_NUMERIC)) {
+					// get declared stream lenght
+					$declength = intval($v[1]);
+					if ($declength < $slength) {
+						$stream = substr($stream, 0, $declength);
+						$slength = $declength;
+					}
+				} elseif ($k == '/Filter') {
+					if ($v[0] == PDF_TYPE_TOKEN) {
+						// single filter
+						$filters[] = $v[1];
+					} elseif ($v[0] == PDF_TYPE_ARRAY) {
+						// array of filters
+						foreach ($v[1] as $flt) {
+							if ($flt[0] == PDF_TYPE_TOKEN) {
+								$filters[] = $flt[1];
+							}
+						}
+					}
+				}
+			}
+		}
+		// decode the stream
+		$remaining_filters = array();
+		foreach ($filters as $filter) {
+			if (in_array($filter, $this->FilterDecoders->getAvailableFilters())) {
+				$stream = $this->FilterDecoders->decodeFilter($filter, $stream);
+			} else {
+				// add missing filter to array
+				$remaining_filters[] = $filter;
+			}
+		}
+		return array($stream, $remaining_filters);
+	}
+
+
+    /**
+     * Set pageno
+     *
+     * @param int $pageno Pagenumber to use
+     */
+    public function setPageno($pageno) {
+        $pageno = ((int) $pageno) - 1;
+
+        if ($pageno < 0 || $pageno >= $this->getPageCount()) {
+            $this->error("Pagenumber is wrong! (Requested $pageno, max ".$this->getPageCount().")");
+        }
+
+        $this->pageno = $pageno;
+    }
+
+    /**
+     * Get page-resources from current page
+     *
+     * @return array
+     */
+    public function getPageResources() {
+        return $this->_getPageResources($this->pages[$this->pageno]);
+    }
+
+    /**
+     * Get page-resources from /Page
+     *
+     * @param array $obj Array of pdf-data
+     */
+    private function _getPageResources ($obj) { // $obj = /Page
+    	$obj = $this->getObjectVal($obj);
+
+        // If the current object has a resources
+    	// dictionary associated with it, we use
+    	// it. Otherwise, we move back to its
+    	// parent object.
+        if (isset ($obj[1][1]['/Resources'])) {
+    		$res = $obj[1][1]['/Resources'];
+    		if ($res[0] == PDF_TYPE_OBJECT)
+                return $res[1];
+            return $res;
+    	} else {
+    		if (!isset ($obj[1][1]['/Parent'])) {
+    			return false;
+    		} else {
+                $res = $this->_getPageResources($obj[1][1]['/Parent']);
+                if ($res[0] == PDF_TYPE_OBJECT)
+                    return $res[1];
+                return $res;
+    		}
+    	}
+    }
+
+
+    /**
+     * Get content of current page
+     *
+     * If more /Contents is an array, the streams are concated
+     *
+     * @return string
+     */
+    public function getContent() {
+        $buffer = '';
+
+        if (isset($this->pages[$this->pageno][1][1]['/Contents'])) {
+            $contents = $this->_getPageContent($this->pages[$this->pageno][1][1]['/Contents']);
+            foreach($contents AS $tmp_content) {
+                $buffer .= $this->_rebuildContentStream($tmp_content) . ' ';
+            }
+        }
+
+        return $buffer;
+    }
+
+
+    /**
+     * Resolve all content-objects
+     *
+     * @param array $content_ref
+     * @return array
+     */
+    private function _getPageContent($content_ref) {
+        $contents = array();
+
+        if ($content_ref[0] == PDF_TYPE_OBJREF) {
+            $content = $this->getObjectVal($content_ref);
+            if ($content[1][0] == PDF_TYPE_ARRAY) {
+                $contents = $this->_getPageContent($content[1]);
+            } else {
+                $contents[] = $content;
+            }
+        } elseif ($content_ref[0] == PDF_TYPE_ARRAY) {
+            foreach ($content_ref[1] AS $tmp_content_ref) {
+                $contents = array_merge($contents,$this->_getPageContent($tmp_content_ref));
+            }
+        }
+
+        return $contents;
+    }
+
+
+    /**
+     * Rebuild content-streams
+     *
+     * @param array $obj
+     * @return string
+     */
+    private function _rebuildContentStream($obj) {
+        $filters = array();
+
+        if (isset($obj[1][1]['/Filter'])) {
+            $_filter = $obj[1][1]['/Filter'];
+
+            if ($_filter[0] == PDF_TYPE_OBJREF) {
+                $tmpFilter = $this->getObjectVal($_filter);
+                $_filter = $tmpFilter[1];
+            }
+
+            if ($_filter[0] == PDF_TYPE_TOKEN) {
+                $filters[] = $_filter;
+            } elseif ($_filter[0] == PDF_TYPE_ARRAY) {
+                $filters = $_filter[1];
+            }
+        }
+
+        $stream = $obj[2][1];
+
+        foreach ($filters AS $_filter) {
+        	$stream = $this->FilterDecoders->decodeFilter($_filter[1], $stream);
+        }
+
+        return $stream;
+    }
+
+
+    /**
+     * Get a Box from a page
+     * Arrayformat is same as used by fpdf_tpl
+     *
+     * @param array $page a /Page
+     * @param string $box_index Type of Box @see $availableBoxes
+     * @param float Scale factor from user space units to points
+     * @return array
+     */
+    public function getPageBox($page, $box_index, $k) {
+        $page = $this->getObjectVal($page);
+        $box = null;
+        if (isset($page[1][1][$box_index]))
+            $box =& $page[1][1][$box_index];
+
+        if (!is_null($box) && $box[0] == PDF_TYPE_OBJREF) {
+            $tmp_box = $this->getObjectVal($box);
+            $box = $tmp_box[1];
+        }
+
+        if (!is_null($box) && $box[0] == PDF_TYPE_ARRAY) {
+            $b =& $box[1];
+            return array('x' => $b[0][1] / $k,
+                         'y' => $b[1][1] / $k,
+                         'w' => abs($b[0][1] - $b[2][1]) / $k,
+                         'h' => abs($b[1][1] - $b[3][1]) / $k,
+                         'llx' => min($b[0][1], $b[2][1]) / $k,
+                         'lly' => min($b[1][1], $b[3][1]) / $k,
+                         'urx' => max($b[0][1], $b[2][1]) / $k,
+                         'ury' => max($b[1][1], $b[3][1]) / $k,
+                         );
+        } elseif (!isset ($page[1][1]['/Parent'])) {
+            return false;
+        } else {
+            return $this->getPageBox($this->getObjectVal($page[1][1]['/Parent']), $box_index, $k);
+        }
+    }
+
+    /**
+     * Get all page boxes by page no
+     *
+     * @param int The page number
+     * @param float Scale factor from user space units to points
+     * @return array
+     */
+    public function getPageBoxes($pageno, $k) {
+        return $this->_getPageBoxes($this->pages[$pageno - 1], $k);
+    }
+
+    /**
+     * Get all boxes from /Page
+     *
+     * @param array a /Page
+     * @return array
+     */
+    private function _getPageBoxes($page, $k) {
+        $boxes = array();
+
+        foreach($this->availableBoxes AS $box) {
+            if ($_box = $this->getPageBox($page, $box, $k)) {
+                $boxes[$box] = $_box;
+            }
+        }
+
+        return $boxes;
+    }
+
+    /**
+     * Get the page rotation by pageno
+     *
+     * @param integer $pageno
+     * @return array
+     */
+    public function getPageRotation($pageno) {
+        return $this->_getPageRotation($this->pages[$pageno - 1]);
+    }
+
+    private function _getPageRotation($obj) { // $obj = /Page
+    	$obj = $this->getObjectVal($obj);
+    	if (isset ($obj[1][1]['/Rotate'])) {
+    		$res = $this->getObjectVal($obj[1][1]['/Rotate']);
+    		if ($res[0] == PDF_TYPE_OBJECT)
+                return $res[1];
+            return $res;
+    	} else {
+    		if (!isset ($obj[1][1]['/Parent'])) {
+    			return false;
+    		} else {
+                $res = $this->_getPageRotation($obj[1][1]['/Parent']);
+                if ($res[0] == PDF_TYPE_OBJECT)
+                    return $res[1];
+                return $res;
+    		}
+    	}
+    }
+
+	/**
+	 * This method is automatically called in case of fatal error; it simply outputs the message and halts the execution.
+	 * @param $msg (string) The error message
+	 * @public
+	 * @since 1.0.000 (2011-05-23)
+	 */
+	public function Error($msg) {
+		// exit program and print error
+		die('<strong>TCPDF_PARSER ERROR: </strong>'.$msg);
+	}
+
+} // END OF TCPDF_PARSER CLASS
+
+//============================================================+
+// END OF FILE
+//============================================================+

+ 6 - 3
htdocs/install/etape1.php

@@ -873,15 +873,18 @@ function write_conf_file($conffile)
 
         // Write params to overwrites default lib path
         fputs($fp,"\n");
-        if (empty($force_dolibarr_lib_TCPDF_PATH)) { fputs($fp, '//'); $force_dolibarr_lib_TCPDF_PATH=''; }
-        fputs($fp, '$dolibarr_lib_TCPDF_PATH=\''.$force_dolibarr_lib_TCPDF_PATH.'\';');
-        fputs($fp,"\n");
         if (empty($force_dolibarr_lib_FPDF_PATH)) { fputs($fp, '//'); $force_dolibarr_lib_FPDF_PATH=''; }
         fputs($fp, '$dolibarr_lib_FPDF_PATH=\''.$force_dolibarr_lib_FPDF_PATH.'\';');
         fputs($fp,"\n");
+        if (empty($force_dolibarr_lib_TCPDF_PATH)) { fputs($fp, '//'); $force_dolibarr_lib_TCPDF_PATH=''; }
+        fputs($fp, '$dolibarr_lib_TCPDF_PATH=\''.$force_dolibarr_lib_TCPDF_PATH.'\';');
+        fputs($fp,"\n");
         if (empty($force_dolibarr_lib_FPDI_PATH)) { fputs($fp, '//'); $force_dolibarr_lib_FPDI_PATH=''; }
         fputs($fp, '$dolibarr_lib_FPDI_PATH=\''.$force_dolibarr_lib_FPDI_PATH.'\';');
         fputs($fp,"\n");
+        if (empty($force_dolibarr_lib_TCPDI_PATH)) { fputs($fp, '//'); $force_dolibarr_lib_TCPDI_PATH=''; }
+        fputs($fp, '$dolibarr_lib_TCPDI_PATH=\''.$force_dolibarr_lib_TCPDI_PATH.'\';');
+        fputs($fp,"\n");
         if (empty($force_dolibarr_lib_ADODB_PATH)) { fputs($fp, '//'); $force_dolibarr_lib_ADODB_PATH=''; }
         fputs($fp, '$dolibarr_lib_ADODB_PATH=\''.$force_dolibarr_lib_ADODB_PATH.'\';');
         fputs($fp,"\n");