diff --git a/dev/dolibarr_changes.txt b/dev/dolibarr_changes.txt
index 728ced44dca..bf68293d369 100644
--- a/dev/dolibarr_changes.txt
+++ b/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
diff --git a/htdocs/admin/pdf.php b/htdocs/admin/pdf.php
index 64a213467d4..1453f80eef2 100644
--- a/htdocs/admin/pdf.php
+++ b/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 '
| '.$langs->trans("ShowVATIntaInAddress").' | ';
@@ -385,14 +385,14 @@ else // Show
print '';
print '| '.$langs->trans("Parameter").' | '.$langs->trans("Value").' | ';
-
+
// Hide any PDF informations
$var=!$var;
print '| '.$langs->trans("HideAnyVATInformationOnPDF").' | ';
print yn($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT,1);
print ' | ';
-
+
// Encrypt and protect PDF
$var=!$var;
print "";
@@ -417,16 +417,16 @@ else // Show
print ''.$langs->trans("Disable").'';
}
print "";
-
+
print "";
print ' ';
-
+
// Hide Tva Intra on adress
$var=!$var;
print '| '.$langs->trans("ShowVATIntaInAddress").' | ';
print yn($conf->global->MAIN_TVAINTRA_NOT_IN_ADDRESS,1);
print ' | ';
-
+
//Desc
$var=!$var;
print '| '.$langs->trans("HideDescOnPDF").' | ';
@@ -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 '';
print ' | '."\n";
print ' '."\n";
diff --git a/htdocs/admin/system/constall.php b/htdocs/admin/system/constall.php
index 0183da7a258..b0dfc40281f 100644
--- a/htdocs/admin/system/constall.php
+++ b/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',
diff --git a/htdocs/admin/system/dolibarr.php b/htdocs/admin/system/dolibarr.php
index 641a3166c04..96700ba1e16 100644
--- a/htdocs/admin/system/dolibarr.php
+++ b/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));
diff --git a/htdocs/core/lib/pdf.lib.php b/htdocs/core/lib/pdf.lib.php
index 1c5ae62d014..390973bd681 100644
--- a/htdocs/core/lib/pdf.lib.php
+++ b/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);
}
diff --git a/htdocs/filefunc.inc.php b/htdocs/filefunc.inc.php
index ee269646c84..7343e65c854 100644
--- a/htdocs/filefunc.inc.php
+++ b/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.'/')); }
diff --git a/htdocs/includes/tcpdi/fpdf_tpl.php b/htdocs/includes/tcpdi/fpdf_tpl.php
new file mode 100644
index 00000000000..86e38b71641
--- /dev/null
+++ b/htdocs/includes/tcpdi/fpdf_tpl.php
@@ -0,0 +1,460 @@
+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('<_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);
+ }
+ }
+}
diff --git a/htdocs/includes/tcpdi/tcpdi.php b/htdocs/includes/tcpdi/tcpdi.php
new file mode 100644
index 00000000000..48fdd8198f5
--- /dev/null
+++ b/htdocs/includes/tcpdi/tcpdi.php
@@ -0,0 +1,701 @@
+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('<_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 <>');
+
+ $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));
+ }
+}
diff --git a/htdocs/includes/tcpdi/tcpdi_parser.php b/htdocs/includes/tcpdi/tcpdi_parser.php
new file mode 100644
index 00000000000..ce222769af9
--- /dev/null
+++ b/htdocs/includes/tcpdi/tcpdi_parser.php
@@ -0,0 +1,1397 @@
+.
+//
+// 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.
+ * @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.
+ * 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; $jobjstreamobjs[$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 ";
+ return $this->xref['xref'][$key[0]][$key[1]];
+ }
+ }
+ if (array_key_exists($objref, $this->objoffsets)) {
+ //echo "Offset found in internal reftable ";
+ 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('TCPDF_PARSER ERROR: '.$msg);
+ }
+
+} // END OF TCPDF_PARSER CLASS
+
+//============================================================+
+// END OF FILE
+//============================================================+
diff --git a/htdocs/install/etape1.php b/htdocs/install/etape1.php
index 50d5ee27b55..4f31a728a1d 100644
--- a/htdocs/install/etape1.php
+++ b/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");
|