2235 lines
79 KiB
PHP
2235 lines
79 KiB
PHP
<?php
|
||
|
||
/*******************************************************************************
|
||
* FPDM *
|
||
* *
|
||
*@file fpdm.php *
|
||
*@name A free PDF form filling tool *
|
||
*@package fpdftk *
|
||
*@version 2.9 *
|
||
*@date 2017-05-11 *
|
||
*@author 0livier *
|
||
*@todo in the importance order, natively by fpdm *
|
||
* -stream inline support (content change,repack,offset/size calculations) *
|
||
* -pdf inline protection *
|
||
* -flatten support *
|
||
* -extends filling to another form fields types (checkboxes,combos..) *
|
||
*@note *
|
||
* V2.9 (11.05.2017) fixed an issue with some PDFs *
|
||
* V2.8 (31.12.2011) added UTF-8 support *
|
||
* V2.7 (29.12.2011) compatibility with PDFs generated by pdftk *
|
||
* V2.6 (25.12.2010) OpenOffice 3 compatibility issues for Florian *
|
||
* see tracking issue here: http://www.fpdf.org/?go=forum&i=53697&t=53697 *
|
||
* V2.5 (06.12.2010) pdftk support for flatten mode and more...special *
|
||
* christmas release to the fpdf fanclub even if the red guy is busy *
|
||
* V2.4 (01.12.2010) Hack for malformed stream definitions, new parsing and *
|
||
* stream core with advanced verbose output. Fix() bonus for corrupted pdfs. *
|
||
* V2.3 (28.11.2010) stream type was lost when /length defined after /Filter *
|
||
* V2.2 (27.11.2010) Stream filter improved:decode now handles multi filters! * *
|
||
* V2.1 (25.11.2010) Only filter support for streams, trailer detection was *
|
||
* too restrictive. fixes FDF error occuring when empty array data is given. *
|
||
* V2.0 (05.11.2010) Load support for inline text fields datas or FDF content *
|
||
* V1.1 (04.11.2010) Works now under php4 for backward compat. *
|
||
* V1.0 (03.11.2010) First working release *
|
||
*******************************************************************************/
|
||
|
||
global $FPDM_FILTERS, $FPDM_REGEXPS; //needs explicit global scope, otherwise autoloading will be incomplete.
|
||
$FPDM_FILTERS=array(); //holds all supported filters
|
||
$FPDM_REGEXPS= array(
|
||
//FIX: parse checkbox definition
|
||
"/AS"=>"/\/AS\s+\/(\w+)$/",
|
||
"name"=>"/\/(\w+)/",
|
||
// "/AP_D_SingleLine"=>"/\/D\s+\/(\w+)\s+\d+\s+\d+\s+R\s+\/(\w+)$/",
|
||
//ENDFIX
|
||
"/Type"=>"/\/Type\s+\/(\w+)$/",
|
||
"/Subtype" =>"/^\/Subtype\s+\/(\w+)$/"
|
||
);
|
||
|
||
//Major stream filters come from FPDI's stuff but I've added some :)
|
||
if (!defined('FPDM_DIRECT')) {
|
||
$FPDM_FILTERS = array("LZWDecode", "ASCIIHexDecode", "ASCII85Decode", "FlateDecode", "Standard" );
|
||
}
|
||
// require_once("filters/FilterASCIIHex.php");
|
||
// require_once("filters/FilterASCII85.php");
|
||
// require_once("filters/FilterFlate.php");
|
||
// require_once("filters/FilterLZW.php");
|
||
// require_once("filters/FilterStandard.php");
|
||
|
||
|
||
$__tmp = version_compare(phpversion(), "5") == -1 ? array('FPDM') : array('FPDM', false);
|
||
if (!call_user_func_array('class_exists', $__tmp)) {
|
||
|
||
|
||
define('FPDM_VERSION',2.9);
|
||
|
||
define('FPDM_INVALID',0);
|
||
define('FPDM_STATIC',1);
|
||
define('FPDM_COMMON',2);
|
||
define('FPDM_VERBOSE',3);
|
||
define('FPDM_CACHE',dirname(__FILE__).'/export/cache/'); //cache directory for fdf temporary files needed by pdftk.
|
||
define('FPDM_PASSWORD_MAX_LEN',15); //Security to prevent shell overflow.
|
||
|
||
class FPDM {
|
||
//@@@@@@@@@
|
||
var $useCheckboxParser = false; //boolean: allows activation of custom checkbox parser (not available in original FPDM source)
|
||
|
||
var $pdf_source = ''; //string: full pathname to the input pdf , a form file
|
||
var $fdf_source = ''; //string: full pathname to the input fdf , a form data file
|
||
var $pdf_output = ''; //string: full pathname to the resulting filled pdf
|
||
var $n = 0;
|
||
|
||
var $pdf_entries = array(); //array: Holds the content of the pdf file as array
|
||
var $fdf_content = ''; //string: holds the content of the fdf file
|
||
var $fdf_parse_needed = false;//boolean: false will use $fields data else extract data from fdf content
|
||
var $value_entries = array(); //array: a map of values to faliclitate access and changes
|
||
|
||
var $positions = array(); //array, stores what object id is at a given position n ($positions[n]=<obj_id>)
|
||
|
||
var $offsets = array(); //array of offsets for objects, index is the object's id, starting at 1
|
||
var $pointer = 0; //integer, Current line position in the pdf file during the parsing
|
||
|
||
var $shifts = array(); //array, Shifts of objects in the order positions they appear in the pdf, starting at 0.
|
||
var $shift = 0; //integer, Global shift file size due to object values size changes
|
||
|
||
var $streams = ''; //Holds streams configuration found during parsing
|
||
var $streams_filter = ''; //Regexp to decode filter streams
|
||
|
||
var $safe_mode = false; //boolean, if set, ignore previous offsets do no calculations for the new xref table, seek pos directly in file
|
||
var $check_mode = false; //boolean, Use this to track offset calculations errors in corrupteds pdfs files for sample
|
||
var $halt_mode = false; //if true, stops when offset error is encountered
|
||
|
||
var $info = array(); //array, holds the info properties
|
||
var $fields = array(); //array that holds fields-Data parsed from FDF
|
||
|
||
var $verbose = false; //boolean , a debug flag to decide whether or not to show internal process
|
||
var $verbose_level = 1; //integer default is 1 and if greater than 3, shows internal parsing as well
|
||
|
||
var $support = ''; //string set to 'native' for fpdm or 'pdftk' for pdf toolkit
|
||
var $flatten_mode = false; //if true, flatten field data as text and remove form fields (NOT YET SUPPORTED BY FPDM)
|
||
var $compress_mode = false; //boolean , pdftk feature only to compress streams
|
||
var $uncompress_mode = false; //boolean pdftk feature only to uncompress streams
|
||
var $security = array(); //Array holding securtity settings
|
||
//(password owner nad user, encrypt (set to 40 or 128 or 0), allow <permissions>] see pdfk help
|
||
|
||
var $needAppearancesTrue = false; //boolean, indicates if /NeedAppearances is already set to true
|
||
var $isUTF8 = false; //boolean (true for UTF-8, false for ISO-8859-1)
|
||
|
||
/**
|
||
* Constructor
|
||
*
|
||
*@example Common use:
|
||
*@param string $pdf_source Source-Filename
|
||
*@param string $fdf_source Source-Filename
|
||
*@param boolean $verbose , optional false per default
|
||
*/
|
||
function __construct() {
|
||
//==============
|
||
|
||
$args=func_get_args();
|
||
$num_args=func_num_args();
|
||
|
||
$FDF_FILE=($num_args>=FPDM_COMMON);
|
||
$VERBOSE_FLAG=($num_args>=FPDM_VERBOSE);
|
||
|
||
$verbose=false;
|
||
|
||
//We are not joking here, let's have a polymorphic constructor!
|
||
switch($num_args) {
|
||
case FPDM_INVALID:
|
||
$this->Error("Invalid instantiation of FPDM, requires at least one param");
|
||
break;
|
||
case FPDM_STATIC:
|
||
if($args[0] =='[_STATIC_]') break; //static use, caller is anonymous function defined in _set_field_value
|
||
//else this is the pdf_source then, fdf content is loaded using Load() function
|
||
default:
|
||
case FPDM_VERBOSE: //Use the verbose value provided
|
||
if($VERBOSE_FLAG) $verbose=$args[2];
|
||
case FPDM_COMMON: //Common use
|
||
$this->pdf_source = $args[0];//Blank pdf form
|
||
|
||
if($FDF_FILE) {
|
||
$this->fdf_source = $args[1];//Holds the data of the fields to fill the form
|
||
$this->fdf_parse_needed=true;
|
||
}
|
||
|
||
//calculation and map
|
||
$this->offsets=array();
|
||
$this->pointer=0;
|
||
$this->shift=0;
|
||
$this->shifts=array();
|
||
$this->n=0;
|
||
|
||
//Stream filters
|
||
$filters=$this->getFilters("|");
|
||
$this->streams_filter="/(\/($filters))+/";
|
||
//$this->dumpContent($this->streams_filter);
|
||
|
||
$this->info=array();
|
||
|
||
//Debug modes
|
||
$this->verbose=$verbose;
|
||
$this->verbose_level=($verbose&&is_int($verbose)) ? $verbose : 1;
|
||
$this->safe_mode=false;
|
||
$this->check_mode=false; //script will takes much more time if you do so
|
||
$this->halt_mode=true;
|
||
|
||
$this->support='native'; //may ne overriden
|
||
$this->security=array('password'=>array('owner'=>null,'user'=>null),'encrypt'=>0,'allow'=>array());
|
||
|
||
//echo "<br>filesize:".filesize($this->pdf_source);
|
||
$this->load_file('PDF');
|
||
|
||
if($FDF_FILE) $this->load_file('FDF');
|
||
|
||
}
|
||
}
|
||
|
||
/**
|
||
*Loads a form data to be merged
|
||
*
|
||
*@note this overrides fdf input source if it was previously defined
|
||
*@access public
|
||
*@param string|array $fdf_data a FDF file content or $pdf_data an array containing the values for the fields to change
|
||
**/
|
||
function Load($data,$isUTF8=false) {
|
||
//------------------------
|
||
$this->isUTF8 = $isUTF8;
|
||
$this->load_file('FDF',$data);
|
||
}
|
||
|
||
/**
|
||
*Loads a file according to its type
|
||
*
|
||
*@access private
|
||
*@param string type 'PDF' or 'FDF'
|
||
*@param String|array content the data content of FDF files only or directly the fields values as array
|
||
**/
|
||
function load_file($type,$content=NULL) {
|
||
//------------------------------------
|
||
switch($type) {
|
||
case "PDF" :
|
||
if($content)
|
||
$this->Error("load_file do not accept PDF content, only FDF content sorry");
|
||
else
|
||
$this->pdf_entries = $this->getEntries($this->pdf_source,'PDF');
|
||
break;
|
||
case "FDF" :
|
||
if(!is_null($content)) {
|
||
if(is_array($content)) {
|
||
$this->fields=$content;
|
||
$this->fdf_parse_needed=false;
|
||
//$this->dumpEntries($content,"PDF fields content");
|
||
} else if(is_string($content)){ //String
|
||
$this->fdf_content = $content; //TODO: check content
|
||
$this->fdf_parse_needed=true;
|
||
} else
|
||
$this->Error('Invalid content type for this FDF file!');
|
||
} else {
|
||
$this->fdf_content = $this->getContent($this->fdf_source,'FDF');
|
||
$this->fdf_parse_needed=true;
|
||
}
|
||
break;
|
||
default:
|
||
$this->Error("Invalid file type $type");
|
||
}
|
||
}
|
||
|
||
/**
|
||
*Set a mode and play with your power debug toys
|
||
*
|
||
*@access public
|
||
*@note for big boys only coz it may hurt
|
||
*@param string $mode a choice between 'safe','check','verbose','halt' or 'verbose_level'
|
||
*@param string|int $value an integer for verbose_level
|
||
**/
|
||
function set_modes($mode,$value) {
|
||
//-------------------------------
|
||
switch($mode) {
|
||
case 'safe':
|
||
$this->safe_mode=$value;
|
||
break;
|
||
case 'check':
|
||
$this->check_mode=$value;
|
||
break;
|
||
case 'flatten':
|
||
$this->flatten_mode=$value;
|
||
break;
|
||
case 'compress_mode':
|
||
$this->compress_mode=$value;
|
||
if($value) $this->uncompress_mode=false;
|
||
break;
|
||
case 'uncompress_mode':
|
||
$this->uncompress_mode=$value;
|
||
if($value) $this->compress_mode=false;
|
||
break;
|
||
case 'verbose':
|
||
$this->verbose=$value;
|
||
break;
|
||
case 'halt':
|
||
$this->halt_mode=$value;
|
||
break;
|
||
case 'verbose_level':
|
||
$this->verbose_level=$value;
|
||
break;
|
||
default:
|
||
$this->Error("set_modes error, Invalid mode '<i>$mode</i>'");
|
||
}
|
||
}
|
||
|
||
/**
|
||
*Retrieves informations of the pdf
|
||
*
|
||
*@access public
|
||
*@note To track PDF versions and so on...
|
||
*@param Boolean output
|
||
**/
|
||
function Info($asArray=false) {
|
||
//----------------------
|
||
$info=$this->info;
|
||
$info["Reader"]=($this->support == "native") ? 'FPDF-Merge '.FPDM_VERSION: $this->support;
|
||
$info["Fields"]=$this->fields;
|
||
$info["Modes"]=array(
|
||
'safe'=>($this->safe_mode)? 'Yes' :'No',
|
||
'check'=>($this->check_mode) ? 'Yes': 'No',
|
||
'flatten'=>($this->flatten_mode) ? 'Yes': 'No',
|
||
'compress_mode'=>($this->compress_mode) ? 'Yes': 'No',
|
||
'uncompress_mode'=>($this->uncompress_mode) ? 'Yes': 'No',
|
||
'verbose'=>$this->verbose,
|
||
'verbose_level'=>$this->verbose_level,
|
||
'halt'=>$this->halt_mode
|
||
);
|
||
if($asArray) {
|
||
return $info;
|
||
} else {
|
||
$this->dumpEntries($info);
|
||
}
|
||
}
|
||
|
||
/**
|
||
*Changes the support
|
||
*
|
||
*@access public
|
||
*@internal fixes xref table offsets
|
||
*@note special playskool toy for Christmas dedicated to my impatient fanclub (Grant, Kris, nejck,...)
|
||
*@param String support Allow to use external support that has more advanced features (ie 'pdftk')
|
||
**/
|
||
function Plays($cool) {
|
||
//----------------------
|
||
if($cool=='pdftk') //Use a coolest support as ..
|
||
$this->support='pdftk';//..Per DeFinition This is Kool!
|
||
else
|
||
$this->support='native';
|
||
}
|
||
|
||
/**
|
||
*Fixes a corrupted PDF file
|
||
*
|
||
*@access public
|
||
*@internal fixes xref table offsets
|
||
*@note Real work is not made here but by Merge that should be launched after to complete the work
|
||
**/
|
||
function Fix() {
|
||
//---------------
|
||
if(!$this->fields) $this->fields=array(); //Default: No field data
|
||
$this->set_modes('check',true); //Compare xref table offsets with objects offsets in the pdf file
|
||
$this->set_modes('halt',false); //Do no stop on errors so fix is applied during merge process
|
||
}
|
||
|
||
//######## pdftk's output configuration #######
|
||
|
||
/**
|
||
*Decides to use the compress filter to restore compression.
|
||
*@note This is only useful when you want to repack PDF that was previously edited in a text editor like vim or emacs.
|
||
**/
|
||
function Compress() {
|
||
//-------------------
|
||
$this->set_modes('compress',true);
|
||
$this->support="pdftk";
|
||
}
|
||
|
||
/**
|
||
*Decides to remove PDF page stream compression by applying the uncompress filter.
|
||
*@note This is only useful when you want to edit PDF code in a text editor like vim or emacs.
|
||
**/
|
||
function Uncompress() {
|
||
//---------------------
|
||
$this->set_modes('uncompress',true);
|
||
$this->support="pdftk";
|
||
}
|
||
/**
|
||
*Activates the flatten output to remove form from pdf file keeping field datas.
|
||
**/
|
||
function Flatten() {
|
||
//-----------------
|
||
$this->set_modes('flatten',true);
|
||
$this->support="pdftk";
|
||
}
|
||
|
||
/***
|
||
*Defines a password type
|
||
*@param String type , 'owner' or 'user'
|
||
**/
|
||
function Password($type,$code) {
|
||
//------------------------------
|
||
switch($type) {
|
||
case 'owner':
|
||
case 'user':
|
||
$this->security["password"]["$type"]=$code;
|
||
break;
|
||
default:
|
||
$this->Error("Unsupported password type ($type), specify 'owner' or 'user' instead.");
|
||
}
|
||
$this->support="pdftk";
|
||
}
|
||
|
||
|
||
/**
|
||
*Defines the encrytion to the given bits
|
||
*@param integer $bits 0, 40 or 128
|
||
**/
|
||
function Encrypt($bits) {
|
||
//-----------------------
|
||
switch($bits) {
|
||
case 0:
|
||
case 40:
|
||
case 128:
|
||
$this->security["encrypt"]=$bits;
|
||
break;
|
||
default:
|
||
$this->Error("Unsupported encrypt value of $bits, only 0, 40 and 128 are supported");
|
||
}
|
||
$this->support="pdftk";
|
||
}
|
||
|
||
/**
|
||
*Allow permissions
|
||
*
|
||
*@param Array permmissions If no arg is given, show help.
|
||
* Permissions are applied to the output PDF only if an encryption
|
||
* strength is specified or an owner or user password is given. If
|
||
* permissions are not specified, they default to 'none,' which
|
||
* means all of the following features are disabled.
|
||
*
|
||
* The permissions section may include one or more of the following
|
||
* features:
|
||
*
|
||
* Printing
|
||
* Top Quality Printing
|
||
*
|
||
* DegradedPrinting
|
||
* Lower Quality Printing
|
||
*
|
||
* ModifyContents
|
||
* Also allows Assembly
|
||
*
|
||
* Assembly
|
||
*
|
||
* CopyContents
|
||
* Also allows ScreenReaders
|
||
*
|
||
* ScreenReaders
|
||
*
|
||
* ModifyAnnotations
|
||
* Also allows FillIn
|
||
*
|
||
* FillIn
|
||
*
|
||
* AllFeatures
|
||
* Allows the user to perform all of the above, and top
|
||
* quality printing.
|
||
**/
|
||
function Allow($permissions=null) {
|
||
//--------------------------
|
||
$perms_help=array(
|
||
'Printing'=>'Top Quality Printing',
|
||
'DegradedPrinting'=>'Lower Quality Printing',
|
||
'ModifyContents' =>'Also allows Assembly',
|
||
'Assembly' => '',
|
||
'CopyContents' => 'Also allows ScreenReaders',
|
||
'ScreenReaders' => '',
|
||
'ModifyAnnotations'=>'Also allows FillIn',
|
||
'FillIn'=>'',
|
||
'AllFeatures'=> "All above"
|
||
);
|
||
if(is_null($permissions)) {
|
||
echo '<br>Info Allow permissions:<br>';
|
||
print_r($perms_help);
|
||
}else {
|
||
if(is_string($permissions)) $permissions=array($permissions);
|
||
$perms=array_keys($perms_help);
|
||
$this->security["allow"]=array_intersect($permissions, $perms);
|
||
$this->support="pdftk";
|
||
}
|
||
}
|
||
|
||
//#############################
|
||
|
||
/**
|
||
*Merge FDF file with a PDF file
|
||
*
|
||
*@access public
|
||
*@note files has been provided during the instantiation of this class
|
||
*@internal flatten mode is not yet supported
|
||
*@param Boolean flatten Optional, false by default, if true will use pdftk (requires a shell) to flatten the pdf form
|
||
**/
|
||
function Merge($flatten=false) {
|
||
//------------------------------
|
||
|
||
if($flatten) $this->Flatten();
|
||
|
||
|
||
if($this->support == "native") {
|
||
|
||
if($this->fdf_parse_needed) {
|
||
$fields=$this->parseFDFContent();
|
||
}else {
|
||
$fields=$this->fields;
|
||
}
|
||
|
||
$count_fields=count($fields);
|
||
|
||
if($this->verbose&&($count_fields==0))
|
||
$this->dumpContent("The FDF content has either no field data or parsing may failed","FDF parser: ");
|
||
|
||
$fields_value_definition_lines=array();
|
||
|
||
$count_entries=$this->parsePDFEntries($fields_value_definition_lines);
|
||
|
||
|
||
if($count_entries) {
|
||
|
||
$this->value_entries=$fields_value_definition_lines;
|
||
if($this->verbose) {
|
||
$this->dumpContent("$count_entries Field entry values found for $count_fields field values to fill","Merge info: ");
|
||
}
|
||
//==== Alterate work is made here: change values ============
|
||
if($count_fields) {
|
||
foreach($fields as $name => $value) {
|
||
$this->set_field_value("current",$name,$value);
|
||
// $value=''; //Strategy applies only to current value, clear others
|
||
// $this->set_field_value("default",$name,$value);
|
||
// $this->set_field_value("tooltip",$name,$value);
|
||
}
|
||
}
|
||
//===========================================================
|
||
|
||
//===== Cross refs/size fixes (offsets calculations for objects have been previously be done in set_field_value) =======
|
||
|
||
//Update cross reference table to match object size changes
|
||
$this->fix_xref_table();
|
||
|
||
//update the pointer to the cross reference table
|
||
$this->fix_xref_start();
|
||
|
||
}else
|
||
$this->Error("PDF file is empty!");
|
||
|
||
} //else pdftk's job is done in Output, not here.
|
||
}
|
||
|
||
/**
|
||
*Warns verbose/output conflicts
|
||
*
|
||
*@access private
|
||
*@param string $dest a output destination
|
||
**/
|
||
function Close($dest) {
|
||
//----------------
|
||
$this->Error("Output: Verbose mode should be desactivated, it is incompatible with this output mode $dest");
|
||
}
|
||
|
||
/**
|
||
*Get current pdf content (without any offset fixes)
|
||
*
|
||
*@access private
|
||
*@param String pdf_file, if given , use the content as buffer (note file will be deleted after!)
|
||
*@return string buffer the pdf content
|
||
**/
|
||
function get_buffer($pdf_file=''){
|
||
//---------------------
|
||
if($pdf_file == '') {
|
||
$buffer=implode("\n",$this->pdf_entries);
|
||
}else {
|
||
$buffer=$this->getContent($pdf_file,'PDF');
|
||
//@unlink($pdf_file);
|
||
}
|
||
return $buffer;
|
||
}
|
||
|
||
|
||
/**
|
||
*Output PDF to some destination
|
||
*
|
||
*@access public
|
||
*@note reproduces the fpdf's behavior
|
||
*@param string dest the destination
|
||
*@param string name the filename
|
||
**/
|
||
function Output($dest='', $name=''){
|
||
//-----------------------------------
|
||
|
||
$pdf_file='';
|
||
|
||
if($this->support == "pdftk") {
|
||
//As PDFTK can only merge FDF files not data directly,
|
||
require_once("lib/url.php"); //we will need a url support because relative urls for pdf inside fdf files are not supported by PDFTK...
|
||
require_once("export/fdf/fdf.php"); //...conjointly with my patched/bridged forge_fdf that provides fdf file generation support from array data.
|
||
require_once("export/pdf/pdftk.php");//Of course don't forget to bridge to PDFTK!
|
||
|
||
$tmp_file=false;
|
||
$pdf_file=resolve_path(fix_path(dirname(__FILE__).'/'.$this->pdf_source)); //string: full pathname to the input pdf , a form file
|
||
|
||
if($this->fdf_source) { //FDF file provided
|
||
$fdf_file=resolve_path(fix_path(dirname(__FILE__).'/'.$this->fdf_source));
|
||
}else {
|
||
|
||
$pdf_url=getUrlfromDir($pdf_file); //Normaly http scheme not local file
|
||
|
||
if($this->fdf_parse_needed) { //fdf source was provided
|
||
$pdf_data=$this->parseFDFContent();
|
||
}else { //fields data was provided as an array, we have to generate the fdf file
|
||
$pdf_data=$this->fields;
|
||
}
|
||
|
||
$fdf_file=fix_path(FPDM_CACHE)."fields".rnunid().".fdf";
|
||
$tmp_file=true;
|
||
$ret=output_fdf($pdf_url,$pdf_data,$fdf_file);
|
||
if(!$ret["success"])
|
||
$this->Error("Output failed as something goes wrong (Pdf was $pdf_url) <br> during internal FDF generation of file $fdf_file, <br>Reason is given by {$ret['return']}");
|
||
}
|
||
|
||
//Serializes security options (not deeply tested)
|
||
$security='';
|
||
if(!is_null($this->security["password"]["owner"])) $security.=' owner_pw "'.substr($this->security["password"]["owner"],0,FPDM_PASSWORD_MAX_LEN).'"';
|
||
if(!is_null($this->security["password"]["user"])) $security.=' user_pw "'.substr($this->security["password"]["user"],0,FPDM_PASSWORD_MAX_LEN).'"';
|
||
if($this->security["encrypt"]!=0) $security.=' encrypt_'.$this->security["encrypt"].'bit';
|
||
if(count($this->security["allow"])>0) {
|
||
$permissions=$this->security["allow"];
|
||
$security.=' allow ';
|
||
foreach($permissions as $permission)
|
||
$security.=' '.$permission;
|
||
}
|
||
|
||
//Serialize output modes
|
||
$output_modes='';
|
||
if($this->flatten_mode) $output_modes.=' flatten';
|
||
if($this->compress_mode) $output_modes.=' compress';
|
||
if($this->uncompress_mode) $output_modes.=' uncompress';
|
||
|
||
|
||
$ret=pdftk($pdf_file,$fdf_file,array("security"=>$security,"output_modes"=>$output_modes));
|
||
|
||
if($tmp_file) @unlink($fdf_file); //Clear cache
|
||
|
||
if($ret["success"]) {
|
||
$pdf_file=$ret["return"];
|
||
}else
|
||
$this->Error($ret["return"]);
|
||
}
|
||
|
||
//$this->buffer=$this->get_buffer($pdf_file);
|
||
|
||
|
||
$dest=strtoupper($dest);
|
||
if($dest=='')
|
||
{
|
||
if($name=='')
|
||
{
|
||
$name='doc.pdf';
|
||
$dest='I';
|
||
}
|
||
else
|
||
$dest='F';
|
||
}
|
||
|
||
//Abort to avoid to polluate output
|
||
if($this->verbose&&(($dest=='I')||($dest=='D'))) {
|
||
$this->Close($dest);
|
||
}
|
||
|
||
switch($dest)
|
||
{
|
||
case 'I':
|
||
//Send to standard output
|
||
if(ob_get_length())
|
||
$this->Error('Some data has already been output, can\'t send PDF file');
|
||
if(php_sapi_name()!='cli')
|
||
{
|
||
//We send to a browser
|
||
header('Content-Type: application/pdf');
|
||
if(headers_sent())
|
||
$this->Error('Some data has already been output, can\'t send PDF file');
|
||
header('Content-Length: '.strlen($this->get_buffer()));
|
||
header('Content-Disposition: inline; filename="'.$name.'"');
|
||
header('Cache-Control: private, max-age=0, must-revalidate');
|
||
header('Pragma: public');
|
||
ini_set('zlib.output_compression','0');
|
||
}
|
||
echo $this->get_buffer();
|
||
break;
|
||
case 'D':
|
||
//Download file
|
||
if(ob_get_length())
|
||
$this->Error('Some data has already been output, can\'t send PDF file');
|
||
header('Content-Type: application/x-download');
|
||
if(headers_sent())
|
||
$this->Error('Some data has already been output, can\'t send PDF file');
|
||
header('Content-Length: '.strlen($this->get_buffer()));
|
||
header('Content-Disposition: attachment; filename="'.$name.'"');
|
||
|
||
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past
|
||
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); // always modified
|
||
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); // HTTP/1.1
|
||
header("Cache-Control: post-check=0, pre-check=0", false);
|
||
//header("Pragma: "); // HTTP/1.0
|
||
|
||
header('Cache-Control: private, max-age=0, must-revalidate');
|
||
header('Pragma: public,no-cache');
|
||
ini_set('zlib.output_compression','0');
|
||
echo $this->get_buffer();
|
||
break;
|
||
case 'F':
|
||
//Save to local file
|
||
if($this->verbose) $this->dumpContent("Write file $name","Output");
|
||
$f=fopen($name,'wb');
|
||
if(!$f)
|
||
$this->Error('Unable to create output file: '.$name.' (currently opened under Acrobat Reader?)');
|
||
|
||
fwrite($f,$this->get_buffer(),strlen($this->get_buffer()));
|
||
fclose($f);
|
||
break;
|
||
case 'S':
|
||
//Return as a string
|
||
return $this->get_buffer();
|
||
default:
|
||
$this->Error('Incorrect output destination: '.$dest);
|
||
}
|
||
return '';
|
||
}
|
||
|
||
|
||
/**
|
||
*Decodes and returns the binary form of a field hexified value
|
||
*
|
||
*@note static method due to callback..
|
||
*@param string value the hexified string
|
||
*@return string call the binary string
|
||
**/
|
||
function pdf_decode_field_value($value) {
|
||
//----------------------------------------
|
||
$call=$this->static_method_call('_hex2bin',$value);
|
||
return $call;
|
||
}
|
||
|
||
/**
|
||
*Encodes and returns the headecimal form of a field binary value
|
||
*
|
||
*@note static method due to callback..
|
||
*@param string value the binary string
|
||
*@return string call the hexified string
|
||
**/
|
||
function pdf_encode_field_value($value) {
|
||
//---------------------------------------
|
||
$value=$this->static_method_call('_bin2hex',$value);
|
||
return $value;
|
||
}
|
||
|
||
|
||
/**
|
||
*Universal Php4/5 static call helper
|
||
*
|
||
*@param String $method a name of a method belonging to this class
|
||
*@return mixed the return value of the called method
|
||
**/
|
||
function static_method_call($method) {
|
||
//---------------------------------------------
|
||
|
||
$params_call=func_get_args();
|
||
array_shift($params_call);
|
||
//var_dump($params_call);
|
||
|
||
return call_user_func_array(array($this,$method),$params_call);
|
||
}
|
||
|
||
/**
|
||
*Changes a field value that can be in hex <> or binary form ()
|
||
*
|
||
*@param $matches the regexp matches of the line that contains the value to change
|
||
*@param String $value the new value for the field property
|
||
**/
|
||
function replace_value($matches,$value) {
|
||
//----------------------------------------------
|
||
|
||
array_shift($matches);
|
||
|
||
if(($value!='')&&($matches[1]=="<")) //Value must be hexified..
|
||
$value=$this->pdf_encode_field_value($value);
|
||
|
||
$matches[2]=$value;
|
||
$value_type_code=$matches[0]; //Should be V, DV or TU
|
||
$matches[0]="/".$value_type_code." ";
|
||
|
||
$value=implode("",$matches);
|
||
//echo(htmlentities($value));
|
||
return $value;
|
||
}
|
||
|
||
/**
|
||
*Core to change the value of a field property, inline.
|
||
*
|
||
*@access private
|
||
*@param int $line the lien where the field property value is defined in the pdf file
|
||
*@param string $value the new value to set
|
||
*@return int $shift the size change of the field property value
|
||
**/
|
||
function _set_field_value($line,$value) {
|
||
//----------------------------------------
|
||
|
||
$verbose_set=($this->verbose&&($this->verbose_level>1));
|
||
//get the line content
|
||
$CurLine =$this->pdf_entries[$line];
|
||
|
||
$OldLen=strlen($CurLine);
|
||
|
||
//My PHP4/5 static call hack, only to make the callback $this->replace_value($matches,"$value") possible!
|
||
$callback_code='$THIS=new FPDM("[_STATIC_]");return $THIS->replace_value($matches,"'.$value.'");';
|
||
|
||
$field_regexp='/^\/(\w+)\s?(\<|\()([^\)\>]*)(\)|\>)/';
|
||
|
||
if(preg_match($field_regexp,$CurLine)) {
|
||
//modify it according to the new value $value
|
||
$CurLine = preg_replace_callback(
|
||
$field_regexp,
|
||
create_function('$matches',$callback_code),
|
||
$CurLine
|
||
);
|
||
}else {
|
||
if($verbose_set) echo("<br>WARNING:".htmlentities("Can not access to the value: $CurLine using regexp $field_regexp"));
|
||
}
|
||
|
||
|
||
$NewLen=strlen($CurLine);
|
||
$Shift=$NewLen-$OldLen;
|
||
$this->shift=$this->shift+$Shift;
|
||
|
||
//Saves
|
||
$this->pdf_entries[$line]=$CurLine;
|
||
|
||
return $Shift;
|
||
}
|
||
|
||
function _encode_value($str) {
|
||
if($this->isUTF8)
|
||
$str="\xFE\xFF".iconv('UTF-8','UTF-16BE',$str);
|
||
return $this->_bin2hex($str);
|
||
}
|
||
|
||
function _set_field_value2($line,$value,$append) {
|
||
$CurLine=$this->pdf_entries[$line];
|
||
$OldLen=strlen($CurLine);
|
||
|
||
if($append)
|
||
{
|
||
$CurLine .= ' /V <'.$this->_encode_value($value).'>';
|
||
}
|
||
else
|
||
{
|
||
if(preg_match('#/V\s?[<(]([^>)]*)[>)]#', $CurLine, $a, PREG_OFFSET_CAPTURE))
|
||
{
|
||
$len=strlen($a[1][0]);
|
||
$pos1=$a[1][1];
|
||
$pos2=$pos1+$len;
|
||
$CurLine=substr($CurLine,0,$pos1-1).'<'.$this->_encode_value($value).'>'.substr($CurLine,$pos2+1);
|
||
}
|
||
else
|
||
$this->Error('/V not found');
|
||
}
|
||
|
||
$NewLen=strlen($CurLine);
|
||
$Shift=$NewLen-$OldLen;
|
||
$this->shift=$this->shift+$Shift;
|
||
$this->pdf_entries[$line]=$CurLine;
|
||
return $Shift;
|
||
}
|
||
|
||
|
||
/**
|
||
*Changes the value of a field property, inline.
|
||
*
|
||
*@param string $type supported values for type are 'default' , 'current' or 'tooltip'
|
||
*@param string $name name of the field annotation to change the value
|
||
*@param string $value the new value to set
|
||
**/
|
||
function set_field_value($type,$name,$value) {
|
||
//------------------------------------
|
||
$verbose_set=($this->verbose&&($this->verbose_level>1));
|
||
|
||
//Get the line(s) of the misc field values
|
||
if(isset($this->value_entries["$name"])) {
|
||
|
||
$object_id=$this->value_entries["$name"]["infos"]["object"];
|
||
|
||
if($type=="tooltip") {
|
||
|
||
$offset_shift=$this->set_field_tooltip($name,$value);
|
||
|
||
} elseif ($this->useCheckboxParser && isset($this->value_entries["$name"]['infos']['checkbox_state'])) { //FIX: set checkbox value
|
||
$offset_shift=$this->set_field_checkbox($name, $value);
|
||
//ENDFIX
|
||
} else {//if(isset($this->value_entries["$name"]["values"]["$type"])) {
|
||
// echo $this->value_entries["$name"]["values"]["$type"];
|
||
/* $field_value_line=$this->value_entries["$name"]["values"]["$type"];
|
||
$field_value_maxlen=$this->value_entries["$name"]["constraints"]["maxlen"];
|
||
|
||
if($field_value_maxlen) //Truncates the size if needed
|
||
$value=substr($value, 0, $field_value_maxlen);
|
||
|
||
if($verbose_set) echo "<br>Change $type value of the field $name at line $field_value_line to '<i>$value</i>'";
|
||
$offset_shift=$this->_set_field_value($field_value_line,$value);*/
|
||
if(isset($this->value_entries[$name]["values"]["current"]))
|
||
$offset_shift=$this->_set_field_value2($this->value_entries[$name]["values"]["current"],$value,false);
|
||
else
|
||
$offset_shift=$this->_set_field_value2($this->value_entries[$name]["infos"]["name_line"],$value,true);
|
||
}
|
||
// }else
|
||
// $this->Error("set_field_value failed as invalid valuetype $type for object $object_id");
|
||
|
||
|
||
//offset size shift will affect the next objects offsets taking into accound the order they appear in the file--
|
||
$this->apply_offset_shift_from_object($object_id,$offset_shift);
|
||
|
||
} else
|
||
$this->Error("field $name not found");
|
||
|
||
}
|
||
|
||
|
||
/**
|
||
*Changes the tooltip value of a field property, inline.
|
||
*
|
||
*@param string $name name of the field annotation to change the value
|
||
*@param string $value the new value to set
|
||
*@return int offset_shift the size variation
|
||
**/
|
||
function set_field_tooltip($name,$value) {
|
||
//------------------------------------
|
||
$offset_shift=0;
|
||
$verbose_set=($this->verbose&&($this->verbose_level>1));
|
||
|
||
//Get the line(s) of the misc field values
|
||
if(isset($this->value_entries["$name"])) {
|
||
$field_tooltip_line=$this->value_entries["$name"]["infos"]["tooltip"];
|
||
if($field_tooltip_line) {
|
||
if($verbose_set) echo "<br>Change tooltip of the field $name at line $field_tooltip_line to value [$value]";
|
||
$offset_shift=$this->_set_field_value($field_tooltip_line,$value);
|
||
}else {
|
||
if($verbose_set) echo "<br>Change toolpip value aborted, the field $name has no tooltip definition.";
|
||
}
|
||
} else
|
||
$this->Error("set_field_tooltip failed as the field $name does not exist");
|
||
return $offset_shift;
|
||
}
|
||
|
||
//FIX: parse checkbox definition
|
||
/**
|
||
*Changes the checkbox state.
|
||
*
|
||
*@param string $name name of the field to change the state
|
||
*@param string $value the new state to set
|
||
*@return int offset_shift the size variation
|
||
**/
|
||
public function set_field_checkbox($name, $value)
|
||
{
|
||
//------------------------------------
|
||
$offset_shift=0;
|
||
$verbose_set=($this->verbose&&($this->verbose_level>1));
|
||
//Get the line(s) of the misc field values
|
||
if (isset($this->value_entries["$name"])) {
|
||
if (isset($this->value_entries["$name"]["infos"]["checkbox_state_line"])
|
||
&& isset($this->value_entries["$name"]["infos"]["checkbox_no"])
|
||
&& isset($this->value_entries["$name"]["infos"]["checkbox_yes"])) {
|
||
$field_checkbox_line=$this->value_entries["$name"]["infos"]["checkbox_state_line"];
|
||
if ($field_checkbox_line) {
|
||
if ($verbose_set) {
|
||
echo "<br>Change checkbox of the field $name at line $field_checkbox_line to value [$value]";
|
||
}
|
||
$state = $this->value_entries["$name"]["infos"]["checkbox_no"];
|
||
if ($value) {
|
||
$state = $this->value_entries["$name"]["infos"]["checkbox_yes"];
|
||
}
|
||
$CurLine =$this->pdf_entries[$field_checkbox_line];
|
||
$OldLen=strlen($CurLine);
|
||
$CurLine = '/AS /'.$state;
|
||
$NewLen=strlen($CurLine);
|
||
$Shift=$NewLen-$OldLen;
|
||
$this->shift=$this->shift+$Shift;
|
||
//Saves
|
||
$this->pdf_entries[$field_checkbox_line]=$CurLine;
|
||
return $Shift;
|
||
// $offset_shift=$this->_set_field_value($field_checkbox_line, $state);
|
||
} else {
|
||
if ($verbose_set) {
|
||
echo "<br>Change checkbox value aborted, parsed checkbox definition incomplete.";
|
||
}
|
||
}
|
||
} else {
|
||
if ($verbose_set) {
|
||
echo "<br>Change checkbox value aborted, the field $name has no checkbox definition.";
|
||
}
|
||
}
|
||
} else {
|
||
$this->Error("set_field_checkbox failed as the field $name does not exist");
|
||
}
|
||
return $offset_shift;
|
||
}
|
||
//ENDFIX
|
||
|
||
/**
|
||
*Dumps the line entries
|
||
*
|
||
*@note for debug purposes
|
||
*@access private
|
||
*@param array entries the content to dump
|
||
*@param string tag an optional tag to highlight
|
||
*@param boolean halt decides to stop or not this script
|
||
**/
|
||
function dumpEntries($entries,$tag="",$halt=false) {
|
||
//------------------------------------------------------------
|
||
if($tag) echo "<br><h4>$tag</h4><hr>";
|
||
if($entries) {
|
||
echo "<pre>";
|
||
echo htmlentities(print_r($entries,true));
|
||
echo "</pre>";
|
||
}
|
||
if($halt) exit();
|
||
}
|
||
|
||
|
||
/**
|
||
*Dumps the string content
|
||
*
|
||
*@note for debug purposes
|
||
*@access private
|
||
*@param string content the content to dump
|
||
*@param string tag an optional tag to highlight
|
||
*@param boolean halt decides to stop or not this script
|
||
**/
|
||
function dumpContent($content,$tag="",$halt=false) {
|
||
//--------------------------------------------------
|
||
if($tag) echo "<h4>$tag</h4>";
|
||
if($content) {
|
||
echo "<pre>";
|
||
echo htmlentities($content);
|
||
echo "</pre>";
|
||
}
|
||
if($halt) exit();
|
||
}
|
||
|
||
/**
|
||
*Retrieves the content of a file as a string
|
||
*
|
||
*@access private
|
||
*@param string $filename the filename of the file
|
||
*@param string $filetype the type of file as info
|
||
*@return string $content
|
||
**/
|
||
function getContent($filename,$filetype) {
|
||
//----------------------------------------
|
||
//$content = file_get_contents($filename);
|
||
$handle=fopen($filename,'rb');
|
||
$content = fread($handle, filesize($filename));
|
||
fclose($handle);
|
||
|
||
if (!$content)
|
||
$this->Error(sprintf('Cannot open '.$filetype.' file %s !', $filename));
|
||
|
||
if($filetype=='PDF')
|
||
{
|
||
$start = substr($content, 0, 2048);
|
||
if(strpos($start, '/ObjStm')!==false)
|
||
$this->Error('Object streams are not supported');
|
||
if(strpos($start, '/Linearized')!==false)
|
||
$this->Error('Fast Web View mode is not supported');
|
||
$end = substr($content, -512);
|
||
if(strpos($end, '/Prev')!==false)
|
||
$this->Error('Incremental updates are not supported');
|
||
$this->needAppearancesTrue = (strpos($content, '/NeedAppearances true')!==false);
|
||
}
|
||
|
||
/* if($this->verbose) {
|
||
$this->dumpContent($content,"$filetype file content read");
|
||
}*/
|
||
return $content;
|
||
}
|
||
|
||
/**
|
||
*Retrieves the content of a file as an array of lines entries
|
||
*
|
||
*@access private
|
||
*@param string $filename the filename of the file
|
||
*@param string $filetype the type of file as info
|
||
*@return array $entries
|
||
**/
|
||
function getEntries($filename,$filetype) {
|
||
//----------------------------------------
|
||
$content=$this->getContent($filename,$filetype);
|
||
$entries=explode("\n",$content);
|
||
|
||
/* if($this->verbose) {
|
||
$this->dumpEntries($entries,"$filetype file entries");
|
||
}*/
|
||
return $entries;
|
||
}
|
||
|
||
|
||
/**
|
||
*Retrieves a binary string from its hexadecimal representation
|
||
*
|
||
*@access private
|
||
*@note Function was written because PHP has a bin2hex, but not a hex2bin!
|
||
*@internal note pack(“C”,hexdec(substr($data,$i,2))) DOES NOT WORK
|
||
*@param string $hexString the hexified string
|
||
*@return string $bin a binary string
|
||
**/
|
||
function _hex2bin ($hexString)
|
||
{
|
||
//echo "<br>_hex2bin($hexString)";
|
||
$BinStr = '';
|
||
|
||
$hexLength=strlen($hexString);
|
||
// only hex numbers is allowed
|
||
if ($hexLength % 2 != 0 || preg_match("/[^\da-fA-F]/",$hexString)) return FALSE;
|
||
|
||
|
||
//Loop through the input and convert it
|
||
for ($i = 0; $i < $hexLength; $i += 2)
|
||
$BinStr .= '%'.substr ($hexString, $i, 2);
|
||
|
||
|
||
// Raw url-decode and return the result
|
||
return rawurldecode ($BinStr);//chr(hexdec())
|
||
}
|
||
|
||
|
||
/**
|
||
*Encodes a binary string to its hexadecimal representation
|
||
*
|
||
*@access private
|
||
*@internal dechex(ord($str{$i})); is buggy because for hex value of 0-15 heading 0 is missing! Using sprintf() to get it right.
|
||
*@param string $str a binary string
|
||
*@return string $hex the hexified string
|
||
**/
|
||
function _bin2hex($str) {
|
||
//----------------------
|
||
$hex = "";
|
||
$i = 0;
|
||
do {
|
||
$hex .= sprintf("%02X", ord($str[$i]));
|
||
$i++;
|
||
} while ($i < strlen($str));
|
||
return $hex;
|
||
}
|
||
|
||
|
||
/**
|
||
* Extracts the map object for the xref table
|
||
* @note PDF lines should have been previouly been parsed to make this work
|
||
* @return array a map that holds the xrefstart infos and values
|
||
*/
|
||
function get_xref_table() {
|
||
//------------------------
|
||
return $this->value_entries['$_XREF_$'];
|
||
}
|
||
|
||
/**
|
||
* Extracts the offset of the xref table
|
||
* @note PDF lines should have been previouly been parsed to make this work
|
||
* @return int the xrefstart value
|
||
*/
|
||
function get_xref_start() {
|
||
//------------------------
|
||
return $this->value_entries['$_XREF_$']["infos"]["start"]["pointer"];
|
||
}
|
||
|
||
|
||
/**
|
||
* Extracts the line where the offset of the xref table is stored
|
||
* @note PDF lines should have been previouly been parsed to make this work
|
||
* @return int the wished line number
|
||
*/
|
||
function get_xref_start_line() {
|
||
//-------------------------------
|
||
return $this->value_entries['$_XREF_$']["infos"]["start"]["line"];
|
||
}
|
||
|
||
/**
|
||
* Calculates the offset of the xref table
|
||
*
|
||
* @return int the wished xrefstart offset value
|
||
*/
|
||
function get_xref_start_value() {
|
||
//-------------------------------
|
||
$size_shift=$this->shift;
|
||
$xref_start=$this->get_xref_start();
|
||
return $xref_start+$size_shift;
|
||
}
|
||
|
||
|
||
/**
|
||
* Read the offset of the xref table directly from file content
|
||
*
|
||
* @note content has been previously been defined in $this->get_buffer()
|
||
* @param int $object_id an object id, a integer value starting from 1
|
||
* @return int the wished xrefstart offset value
|
||
*/
|
||
function read_xref_start_value() {
|
||
//------------------------------
|
||
$buffer=$this->get_buffer();
|
||
$chunks = preg_split('/\bxref\b/', $buffer, -1, PREG_SPLIT_OFFSET_CAPTURE);
|
||
return intval($chunks[1][1])-4; //-4 , relative to end of xref
|
||
}
|
||
|
||
|
||
/**
|
||
* Calculates the new offset/xref for this object id by applying the offset_shift due to value changes
|
||
*
|
||
* @note uses internally precalculated $offsets,$positions and $shifts
|
||
* @param int $object_id an object id, a integer value starting from 1
|
||
* @return int the wished offset
|
||
*/
|
||
function get_offset_object_value($object_id) {
|
||
//--------------------------------------------
|
||
|
||
//Static is to keep forever...
|
||
static $offsets=null;
|
||
static $positions=null;
|
||
static $shifts=null;
|
||
|
||
//if(is_null($offsets)) { //...variables content set once. This is the beauty of php :)
|
||
|
||
//!NOTE: xref table is ordered by object id (position's object is not defined linearly in the pdf !)
|
||
$positions=$this->_get_positions_ordered();
|
||
//Makes it 0 indexed as object id starts from 1 and positions starts from 0
|
||
$offsets=$this->_get_offsets_starting_from_zero();
|
||
//Shifts are already 0 indexed, don't change.
|
||
$shifts=$this->shifts;
|
||
//}
|
||
|
||
$p=$positions[$object_id];
|
||
$offset=$offsets[$p];
|
||
$shift=$shifts[$p]; //size shift of the object due to value changes
|
||
return $offset+$shift;
|
||
}
|
||
|
||
|
||
/**
|
||
* Reads the offset of the xref table directly from file content
|
||
*
|
||
* @note content has been previously been defined in $this->get_buffer()
|
||
* @param int $object_id an object id, a integer value starting from 1
|
||
* @return int the wished offset
|
||
*/
|
||
function read_offset_object_value($object_id) {
|
||
//------------------------------
|
||
$buffer=$this->get_buffer();
|
||
$previous_object_footer='';//'endobj' or comment;
|
||
$object_header=$previous_object_footer.'\n'.$object_id.' 0 obj';
|
||
$chars = preg_split('/'.$object_header.'/', $buffer, -1, PREG_SPLIT_OFFSET_CAPTURE);
|
||
$offset=intval($chars[1][1])-strlen($object_header)+strlen($previous_object_footer)+2;
|
||
return $offset;
|
||
}
|
||
|
||
|
||
/**
|
||
* Fix the offset of the xref table
|
||
*
|
||
*/
|
||
function fix_xref_start() {
|
||
//-------------------------
|
||
|
||
$pdf_entries=&$this->pdf_entries;
|
||
$verbose_fix=($this->verbose&&($this->verbose_level>1));
|
||
$calculate_xrefstart_value=((!$this->safe_mode)||$this->check_mode);
|
||
$extract_xrefstart_value_from_file=($this->safe_mode||$this->check_mode);
|
||
|
||
if($calculate_xrefstart_value) {
|
||
$xref_start_value_calculated=$this->get_xref_start_value(); //get computed value from old one
|
||
if(!$this->safe_mode) $xref_start_value=$xref_start_value_calculated;
|
||
}
|
||
|
||
if($extract_xrefstart_value_from_file) {
|
||
$xref_start_value_safe=$this->read_xref_start_value();//read direct from new file content
|
||
if($this->safe_mode) $xref_start_value=$xref_start_value_safe;
|
||
}
|
||
|
||
if($this->check_mode) { //Compared calculated value with position value read direct from file
|
||
if($xref_start_value_calculated != $xref_start_value_safe) {
|
||
if($verbose_fix) echo "<br>xrefstart's value must be $xref_start_value_safe calculated is $xref_start_value_calculated.Don't worry, FPDFM-merge will fix it for you.<br>";
|
||
$xref_start_value=$xref_start_value_safe; //Overrides with the good value
|
||
if($this->halt_mode)
|
||
$this->Error("Halt on error mode enabled, aborting. Use \$pdf->set_modes('halt',false); to disable this mode and go further fixing corrupted pdf.");
|
||
} else {
|
||
if($verbose_fix) echo "<br>xrefstart's value for the file is correct and vaults <b>$xref_start_value</b>";
|
||
}
|
||
}
|
||
|
||
//updates xrefstart's value
|
||
$xref_start_line=$this->get_xref_start_line();
|
||
$pdf_entries[$xref_start_line]="$xref_start_value";
|
||
}
|
||
|
||
/**
|
||
* Get the offsets table 0 indexed
|
||
*
|
||
* @return array $offsets
|
||
*/
|
||
function _get_offsets_starting_from_zero() {
|
||
//-------------------------------------------
|
||
$offsets=$this->offsets;
|
||
return array_values($offsets);
|
||
}
|
||
|
||
/**
|
||
* Sorts the position array by key
|
||
*
|
||
* @return array $positions the ordered positions
|
||
*/
|
||
function _get_positions_ordered() {
|
||
//--------------------------------
|
||
$positions=$this->positions;
|
||
ksort($positions);
|
||
return $positions;
|
||
}
|
||
|
||
/**
|
||
* Fix the xref table by rebuilding its offsets entries
|
||
*
|
||
*/
|
||
function fix_xref_table() {
|
||
//------------------------
|
||
|
||
$xref_table=$this->get_xref_table();
|
||
$xLen=$xref_table["infos"]["count"];
|
||
$pdf_entries=&$this->pdf_entries;
|
||
|
||
//Do some checks
|
||
$offsets=$this->offsets;
|
||
//$offsets=array_values($offsets);
|
||
$oLen=count($offsets);
|
||
|
||
|
||
if($xLen == $oLen) { //...to rectify xref entries
|
||
|
||
//jump over len and header, this is the first entry with n
|
||
$first_xref_entry_line=$xref_table["infos"]["line"]+3;
|
||
|
||
//echo "xREF:{$pdf_entries[$first_xref_entry_line]}";
|
||
|
||
//!NOTE: xref table is ordered by object id (position's object is not defined linearly in the pdf !)
|
||
//$positions=$this->positions;
|
||
//ksort($positions);
|
||
$verbose_fix=($this->verbose&&($this->verbose>1));
|
||
$calculate_offset_value=((!$this->safe_mode)||$this->check_mode);
|
||
$extract_offset_value_from_file=($this->safe_mode||$this->check_mode);
|
||
|
||
//Get new file content (ie with values changed)
|
||
//$this->get_buffer()=$this->get_buffer();
|
||
|
||
for($i=0;$i<$xLen;$i++) {
|
||
|
||
$obj_id=$i+1;
|
||
|
||
//Try two way to retrieve xref offset value of an object of the given id
|
||
|
||
if($calculate_offset_value) {
|
||
$offset_value_calculated=$this->get_offset_object_value($obj_id);;
|
||
if(!$this->safe_mode) $offset_value=$offset_value_calculated;
|
||
}
|
||
|
||
if($extract_offset_value_from_file) {
|
||
$offset_value_read=$this->read_offset_object_value($obj_id);
|
||
if($this->safe_mode) $offset_value=$offset_value_read;
|
||
}
|
||
|
||
if($this->check_mode) {
|
||
if($offset_value_calculated != $offset_value_read) {
|
||
if($verbose_fix) echo "<br>Offset for object $obj_id read is <b>$offset_value_read</b>, calculated $offset_value_calculated";
|
||
$offset_value=$offset_value_read; //overrides to fix bad values
|
||
if($this->halt_mode) $this->Error("<br>Offset for object $obj_id read is <b>$offset_value_read</b>, calculated $offset_value_calculated");
|
||
}else {
|
||
if($verbose_fix) echo "<br>Offset for object $obj_id is correct and vaults <b>$offset_value</b>";
|
||
}
|
||
}
|
||
$pdf_entries[$first_xref_entry_line+$i]=sprintf('%010d 00000 n ',$offset_value);
|
||
}
|
||
|
||
}else {
|
||
//Congratulations you won the corrupted Error Prize
|
||
$this->Error("Number of objects ($oLen) differs with number of xrefs ($xLen), something , pdf xref table is corrupted :(");
|
||
}
|
||
|
||
|
||
}
|
||
|
||
|
||
/**
|
||
* Applies a shift offset $shift from the object whose id is given as param
|
||
*
|
||
* @note offset shift will affect the next objects taking into accound the order they appear in the file
|
||
* @access public
|
||
* @param int object_id the id whose size shift has changed
|
||
* @param int offset_shift the shift value to use
|
||
*/
|
||
function apply_offset_shift_from_object($object_id,$offset_shift) {
|
||
//---------------------------------------------------------
|
||
//get the position of object
|
||
$object_pos=$this->positions[$object_id];
|
||
//get the next object position
|
||
$next_object_pos=$object_pos+1;
|
||
//Applies offset change to next following objects
|
||
$this->_apply_offset_shift($next_object_pos,$offset_shift);
|
||
}
|
||
|
||
/**
|
||
* Applies a shift offset $shift starting at the index $from to the shifts array
|
||
*
|
||
* @access private
|
||
* @param int from the index to start apply the shift
|
||
* @param int shift the shift value to use
|
||
*/
|
||
function _apply_offset_shift($from,$shift) {
|
||
//------------------------------------------
|
||
$offsets=&$this->shifts;
|
||
$params=array($from,$shift);
|
||
|
||
foreach($offsets as $key=>$value) {
|
||
if($key>=$from) {
|
||
$offset=$offsets[$key]+$shift;
|
||
$offsets[$key]=$offset;
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
/**
|
||
* Decodes a PDF value according to the encoding
|
||
*
|
||
* @access public
|
||
* @param string $encoding the encoding to use for decoding the value, only 'hex' is supported
|
||
* @param string value a value to decode
|
||
* @return string the value decoded
|
||
*/
|
||
function decodeValue($encoding,$value) {
|
||
//----------------------------------------------
|
||
//echo "Decoding $encoding value($value)";
|
||
if($encoding=="hex")
|
||
$value=$this->pdf_decode_field_value($value);
|
||
return $value;
|
||
}
|
||
|
||
/**
|
||
*Retrieve the list of supported filters
|
||
*
|
||
*@note Uses $FPDM_FILTERS array built dynamically
|
||
*@param String $sep a separator to merge filter names, default is '|'
|
||
*@return String the suported filters
|
||
**/
|
||
function getFilters($sep="|") {
|
||
//---------------------
|
||
global $FPDM_FILTERS;
|
||
return implode($sep,$FPDM_FILTERS);
|
||
}
|
||
|
||
|
||
/**
|
||
*Get a filter by name
|
||
*
|
||
*@param name a string matching one of the supported default filters (marked with +) *
|
||
*Without parameters:
|
||
*+ ASCIIHexDecode : Decodes data encoded in an ASCII hexadecimal representation, reproducing the original binary data.
|
||
*+ ASCII85Decode : Decodes data encoded in an ASCII base-85 representation, reproducing the original binary data.
|
||
* RunLengthDecode : Decompresses data encoded using a byte-oriented run-length encoding algorithm, reproducing the original text or binary data (typically monochrome image data, or any data that contains frequent long runs of a single byte value).
|
||
* JPXDecode : (PDF 1.5) Decompresses data encoded using the wavelet-based JPEG2000 standard, reproducing the original image data.
|
||
*With parameter(s):
|
||
*+ LZWDecode : Decompresses data encoded using the LZW (Lempel-Ziv-Welch) adaptive compression method, reproducing the original text or binary data.
|
||
*+ FlateDecode (PDF 1.2): Decompresses data encoded using the zlib/deflate compression method, reproducing the original text or binary data.
|
||
* CCITTFaxDecode : Decompresses data encoded using the CCITT facsimile standard, reproducing the original data (typically monochrome image data at 1 bit per pixel).
|
||
* JBIG2Decode (PDF 1.4) :Decompresses data encoded using the JBIG2 standard, reproducing the original monochrome (1 bit per pixel) image data (or an approximation of that data).
|
||
* DCTDecode : Decompresses data encoded using a DCT (discrete cosine transform) technique based on the JPEG standard, reproducing image sample data that approximates the original data.
|
||
* Crypt (PDF 1.5) :Decrypts data encrypted by a security handler, reproducing the data as it was before encryption.
|
||
*@return the wished filter class to access the stream
|
||
**/
|
||
function getFilter($name) {
|
||
//---------------------
|
||
|
||
switch($name) {
|
||
case "LZWDecode":
|
||
$filter=new FilterLZW();
|
||
break;
|
||
case "ASCIIHexDecode":
|
||
$filter=new FilterASCIIHex();
|
||
break;
|
||
case "ASCII85Decode":
|
||
$filter=new FilterASCII85();
|
||
break;
|
||
case "FlateDecode":
|
||
$filter=new FilterFlate();
|
||
break;
|
||
case "Standard": //Raw
|
||
$filter=new FilterStandard();
|
||
break;
|
||
default:
|
||
$this->Error("getFilter cannot open stream of object because filter '{$name}' is not supported, sorry.");
|
||
}
|
||
|
||
|
||
return $filter;
|
||
}
|
||
|
||
|
||
//========= Stream manipulation stuff (alpha, not used by now!) ================
|
||
|
||
/**
|
||
* Detect if the stream has a textual content
|
||
*
|
||
* @access public
|
||
* @param string $stream the string content of the stream
|
||
* @return boolean
|
||
*/
|
||
function is_text_stream($stream_content) {
|
||
//--------------------------------------
|
||
return preg_match("/(\s*Td\s+[\<\(])([^\>\)]+)([\>\)]\s+Tj)/",$stream_content);
|
||
}
|
||
|
||
/**
|
||
* changes the text value of a text stream
|
||
*
|
||
* @access public
|
||
* @param array $stream the stream defintion retrieved during PDF parsing
|
||
* @param string $value the new text value
|
||
*/
|
||
function change_stream_value($stream,$value) {
|
||
//--------------------------------------------
|
||
|
||
$entries=&$this->pdf_entries;
|
||
|
||
$verbose_parsing=($this->verbose&&($this->verbose_level>3));
|
||
|
||
if($is_text_stream) {
|
||
|
||
$OldLen=$stream["length"]["value"];
|
||
$lMin=$stream["start"];
|
||
$lMax=$stream["end"];
|
||
|
||
$stream_content=$this->_set_text_value($stream_content,$value);
|
||
$NewLen=strlen($stream_content);
|
||
|
||
for($l=$lMin;$l<=$lMax;$l++) {
|
||
|
||
if($l==$lMin) {
|
||
$entries[$lMin]=$stream_content;
|
||
|
||
//Update the length
|
||
$stream_def_line=$stream["length"]["line"];
|
||
$stream_def=$entries[$stream_def_line];
|
||
|
||
$stream_def=preg_replace("/\/Length\s*(\d+)/",'/Length '.$NewLen,$stream_def);
|
||
|
||
$entries[$stream_def_line]=$stream_def;
|
||
|
||
//update the filter type...
|
||
$stream_def_line=$stream["filters"]["line"];
|
||
$stream_def=$entries[$stream_def_line];
|
||
if($verbose_parsing) {
|
||
echo "<pre>";
|
||
echo htmlentities(print_r($stream_def,true));
|
||
echo "</pre>";
|
||
}
|
||
|
||
//...to filter Standard
|
||
$stream_def=preg_replace($this->streams_filter,'/Standard ',$stream_def);
|
||
|
||
$entries[$stream_def_line]=$stream_def;
|
||
|
||
//Update the shift
|
||
$size_shift=$NewLen-$OldLen;
|
||
$this->apply_offset_shift_from_object($obj,$size_shift);
|
||
|
||
}else if($lmin!=$lMax) {
|
||
unset($entries[$l]);
|
||
}
|
||
}
|
||
|
||
if($verbose_parsing) {
|
||
var_dump($stream_content);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Overrides value between Td and TJ, ommiting <>
|
||
*
|
||
* @note core method
|
||
* @access private
|
||
* @param array $stream the stream defintion retrieved during PDF parsing
|
||
* @param string $value the new text value
|
||
*/
|
||
function _set_text_value($stream,$value) {
|
||
//---------------------------------------
|
||
$chunks=preg_split("/(\s*Td\s+[\<\(])([^\>\)]+)([\>\)]\s+Tj)/",$stream,0,PREG_SPLIT_DELIM_CAPTURE);
|
||
$chunks[2]=$value;
|
||
$stream=implode($chunks,'');
|
||
return $stream;
|
||
}
|
||
|
||
|
||
//================================
|
||
|
||
function _extract_pdf_definition_value($name,$line,&$match) {
|
||
//-----------------------------------------------------------
|
||
global $FPDM_REGEXPS;
|
||
$value=preg_match($FPDM_REGEXPS["$name"],$line,$match);
|
||
if(!$value) { //value is concatained with name: /name/value
|
||
$value=preg_match("/".preg_quote($name,'/')."\/(\w+)/",$line,$match);
|
||
}
|
||
return $value;
|
||
}
|
||
|
||
function extract_pdf_definition_value($name,$line,&$match) {
|
||
//-----------------------------------------------------------
|
||
global $FPDM_REGEXPS;
|
||
if(array_key_exists($name,$FPDM_REGEXPS)) {
|
||
$value=$this->_extract_pdf_definition_value($name,$line,$match);
|
||
}else
|
||
$this->Error("extract_pdf_definition_value() does not support definition '$name'");
|
||
|
||
/*if($name=="/Type") {
|
||
if(preg_match("/\//",$line,$foo)) {
|
||
var_dump($match);
|
||
die("Decoding $name value in line ".htmlentities($line));
|
||
}
|
||
}*/
|
||
return $value;
|
||
}
|
||
|
||
|
||
/**
|
||
* Parses the lines entries of a PDF
|
||
*
|
||
* @access public
|
||
* @param array $lines the FDF content as an array of lines
|
||
* @return integer the number of lines the PDF has
|
||
*/
|
||
function parsePDFEntries(&$lines){
|
||
//--------------------------------
|
||
|
||
$entries=&$this->pdf_entries;
|
||
|
||
$CountLines = count($entries);
|
||
|
||
$Counter=0;
|
||
$obj=0; //this is an invalid object id, we use it to know if we are into an object
|
||
//FIX: parse checkbox definition
|
||
$ap_d_yes='';
|
||
$ap_d_no='';
|
||
$ap_line=0;
|
||
$ap_d_line=0;
|
||
$as='';
|
||
//ENDFIX
|
||
$type='';
|
||
$subtype='';
|
||
$name='';
|
||
$value='';
|
||
$default_maxLen=0; //No limit
|
||
$default_tooltip_line=0; //Tooltip is optional as it may not be defined
|
||
$xref_table=0;
|
||
$trailer_table=0;
|
||
$n=0; //Position of an object, in the order it is declared in the pdf file
|
||
$stream=array();
|
||
$id_def=false; //true when parsing/decoding trailer ID
|
||
$id_single_line_def=false; //true when the two ID chunks are one the same line
|
||
$id_multi_line_def=false; //true or OpenOffice 3.2
|
||
$creator='';
|
||
$producer='';
|
||
$creationDate='';
|
||
|
||
$verbose_parsing=($this->verbose&&($this->verbose_level>3));
|
||
$verbose_decoding=($this->verbose&&($this->verbose_level>4));
|
||
|
||
if($this->verbose) $this->dumpContent("Starting to parse $CountLines entries","PDF parse");
|
||
|
||
while ( $Counter < $CountLines ){
|
||
|
||
$CurLine = $entries[$Counter];
|
||
|
||
if($verbose_parsing) $this->dumpContent($CurLine,"====Parsing Line($Counter)");
|
||
if(!$xref_table) {
|
||
|
||
//Header of an object?
|
||
if(preg_match("/^(\d+) (\d+) obj/",$CurLine,$match)) {
|
||
$obj=intval($match[1]);
|
||
$this->offsets[$obj]=$this->pointer;
|
||
$this->positions[$obj]=$n;
|
||
$this->shifts[$n]=0;
|
||
$n++;
|
||
if($verbose_parsing) $this->dumpContent($CurLine,"====Opening object($obj) at line $Counter");
|
||
$object=array();
|
||
$object["values"]=array();
|
||
$object["constraints"]=array();
|
||
$object["constraints"]["maxlen"]=$default_maxLen;
|
||
$object["infos"]=array();
|
||
$object["infos"]["object"]=intval($obj);
|
||
$object["infos"]["tooltip"]=$default_tooltip_line;
|
||
|
||
} else {
|
||
|
||
//Object has been opened
|
||
if($obj) {
|
||
|
||
//Footer of an object?
|
||
if(preg_match("/endobj/",$CurLine,$match)) {
|
||
if($verbose_parsing) $this->dumpContent("","====Closing object($obj) at line $Counter");
|
||
|
||
//We process fields here, save only Annotations texts that are supported by now
|
||
if($subtype=="Widget") {
|
||
|
||
if($name != '') {
|
||
$lines["$name"]=$object;
|
||
if($verbose_parsing) $this->dumpContent("$type $subtype (obj id=$obj) is a text annotation of name '$name', saves it.");
|
||
}//else
|
||
// $this->Error("$type $subtype (obj id=$obj) is a text annotation without a name, this cannot be.");
|
||
|
||
|
||
$values=$object["values"];
|
||
|
||
//Sanity values checks, watchdog.
|
||
// if(!array_key_exists("current",$values)) $this->Error("Cannot find value (/V) for field $name");
|
||
// if(!array_key_exists("default",$values)) $this->Error("Cannot find default value (/DV) for field $name");
|
||
|
||
}else
|
||
if($verbose_parsing) $this->dumpContent("Object $type $subtype (obj id=$obj) is not supported");
|
||
|
||
|
||
$object=null;
|
||
$obj=0;
|
||
//FIX: parse checkbox definition
|
||
$ap_d_yes='';
|
||
$ap_d_no='';
|
||
$ap_line=0;
|
||
$ap_d_line=0;
|
||
$as='';
|
||
//ENDFIX
|
||
$type='';
|
||
$subtype='';
|
||
$name='';
|
||
$value='';
|
||
$maxLen=0;
|
||
|
||
} else {
|
||
|
||
if(preg_match("/\/Length\s*(\d+)/",$CurLine,$match)) {
|
||
$stream["length"]=array("line"=>$Counter,"value"=>$match[1]);
|
||
$stream["start"]=0;
|
||
$stream["end"]=0;
|
||
$stream["content"]='';
|
||
if($verbose_parsing) $this->dumpContent($CurLine,"->Stream filter length definition(<font color=\"darkorange\">{$match[1]}</font>) for object($obj) at line $Counter");
|
||
}
|
||
|
||
//Handles single filter /Filter /filter_type as well as well as filter chains such as /Filter [/filter_type1 /filter_type2 .../filter_typeN]
|
||
if(preg_match_all($this->streams_filter,$CurLine,$matches)) {
|
||
|
||
//$this->dumpContent($this->streams_filter);
|
||
/*$stream_filter=$match[1];
|
||
$stream_filter=trim(preg_replace('/(<<|\/Length\s*\d+|>>)/', '', $stream_filter),' ');
|
||
$stream_filters=preg_split('/\s*\//',$stream_filter);
|
||
array_shift($stream_filters);*/
|
||
$stream_filters=$matches[2];
|
||
$stream["filters"]=array("line"=>$Counter, "type"=>$stream_filters);
|
||
if($verbose_parsing) {
|
||
//var_dump($stream_filters);
|
||
$stream_filter=implode(" ",$stream_filters);
|
||
$this->dumpContent($CurLine,"->Stream filter type definition(<font color=\"darkorange\">$stream_filter</font>) for object($obj) at line $Counter");
|
||
}
|
||
}
|
||
|
||
if(array_key_exists("length",$stream)) { //length is mandatory
|
||
|
||
if(preg_match("/\b(stream|endstream)\b/",$CurLine,$match)) {
|
||
|
||
if(!array_key_exists("filters",$stream)) {//filter type is optional, if none is given, its standard
|
||
|
||
$stream["filters"]=array("type"=>array("Standard"));
|
||
if($verbose_parsing) {
|
||
var_dump($stream);
|
||
$this->dumpContent($CurLine,"->No stream filter type definition for object($obj) was found, setting it to '<font color=\"darkorange\">Standard</font>'");
|
||
}
|
||
}
|
||
|
||
|
||
if($match[1] == "stream") {
|
||
if($verbose_parsing) $this->dumpContent($CurLine,"->Opening stream for object($obj) at line $Counter");
|
||
$stream["start"]=$Counter+1;
|
||
}else {
|
||
$stream["end"]=$Counter-1;
|
||
|
||
$stream["content"]=implode("\n",array_slice($entries,$stream["start"],$stream["end"]-$stream["start"]+1));
|
||
|
||
|
||
|
||
$filters=$stream["filters"]["type"];
|
||
$f=count($filters);
|
||
$stream_content=$stream["content"];
|
||
|
||
//var_dump($filters);
|
||
|
||
//$filters_type=$filters["type"];
|
||
|
||
//now process the stream, ie unpack it if needed
|
||
//by decoding in the reverse order the streams have been encoded
|
||
//This is done by applying decode using the filters in the order given by /Filter.
|
||
foreach($filters as $filter_name) {
|
||
|
||
$stream_filter=$this->getFilter($filter_name);
|
||
$stream_content=$stream_filter->decode($stream_content);
|
||
if($verbose_decoding) {
|
||
echo "<br><font color=\"blue\"><u>Stream decoded using filter '<font color=\"darkorange\">$filter_name</font>'</u>:[<pre>";
|
||
var_dump($stream_content); //todo : manipulate this content and adjust offsets.
|
||
echo "</pre>]</font>";
|
||
}
|
||
}
|
||
|
||
if($verbose_parsing) {
|
||
$this->dumpEntries($stream);
|
||
|
||
echo "<font color=\"blue\">";
|
||
if($this->is_text_stream($stream_content)) {
|
||
echo "<u>Stream text unfiltered</u>:[<pre>";
|
||
} else {
|
||
echo "<u>Stream unfiltered</u>:[<pre>";
|
||
}
|
||
var_dump($stream_content);
|
||
echo "</pre>]</font>";
|
||
$this->dumpContent($CurLine,"->Closing stream for object($obj) at line $Counter");
|
||
}
|
||
|
||
$stream=array();
|
||
}
|
||
}else if($stream["start"]>0){
|
||
//stream content line that will be processed on endstream...
|
||
}
|
||
|
||
} else {
|
||
|
||
/*
|
||
Producer<FEFF004F00700065006E004F00660066006900630065002E006F0072006700200033002E0032>
|
||
/CreationDate (D:20101225151810+01'00')>>
|
||
*/
|
||
if(($creator=='')&&preg_match("/\/Creator\<([^\>]+)\>/",$CurLine,$values)) {
|
||
$creator=$this->decodeValue("hex",$values[1]);
|
||
if($verbose_parsing) echo("Creator read ($creator)");
|
||
$this->info["Creator"]=$creator;
|
||
}
|
||
|
||
if(($producer=='')&&preg_match("/\/Producer\<([^\>]+)\>/",$CurLine,$values)) {
|
||
$producer=$this->decodeValue("hex",$values[1]);
|
||
if($verbose_parsing) echo("Producer read ($producer)");
|
||
$this->info["Producer"]=$producer;
|
||
}
|
||
|
||
if(($creationDate=='')&&preg_match("/\/CreationDate\(([^\)]+)\)/",$CurLine,$values)) {
|
||
$creationDate=$values[1];
|
||
if($verbose_parsing) echo("Creation date read ($creationDate)");
|
||
$this->info["CreationDate"]=$creationDate;
|
||
}
|
||
|
||
//=== DEFINITION ====
|
||
//preg_match("/^\/Type\s+\/(\w+)$/",$CurLine,$match)
|
||
$match=array();
|
||
//FIX: parse checkbox definition
|
||
if($this->useCheckboxParser && ('' == $ap_d_yes || '' == $ap_d_no || '' == $as)) {
|
||
if (!$ap_line && '/AP' == substr($CurLine, 0, 3)) {
|
||
if ($verbose_parsing) {
|
||
echo("<br>Found AP Line '<i>$Counter</i>'");
|
||
}
|
||
$ap_line = $Counter;
|
||
} elseif (!$ap_d_line && '/D' == substr($CurLine, 0, 2)) {
|
||
if ($verbose_parsing) {
|
||
echo("<br>Found D Line '<i>$Counter</i>'");
|
||
}
|
||
$ap_d_line = $Counter;
|
||
} elseif (($ap_line==$Counter-4)&&($ap_d_line==$Counter-2)&&($ap_d_yes=='')&&$this->extract_pdf_definition_value("name", $CurLine, $match)) {
|
||
$ap_d_yes=$match[1];
|
||
if ($verbose_parsing) {
|
||
echo("<br>Object's checkbox_yes is '<i>$ap_d_yes</i>'");
|
||
}
|
||
$object["infos"]["checkbox_yes"]=$ap_d_yes;
|
||
} elseif (($ap_line==$Counter-5)&&($ap_d_line==$Counter-3)&&($ap_d_no=='')&&$this->extract_pdf_definition_value("name", $CurLine, $match)) {
|
||
$ap_d_no=$match[1];
|
||
if ($verbose_parsing) {
|
||
echo("<br>Object's checkbox_no is '<i>$ap_d_no</i>'");
|
||
}
|
||
$object["infos"]["checkbox_no"]=$ap_d_no;
|
||
} elseif (($as=='')&&$this->extract_pdf_definition_value("/AS", $CurLine, $match)) {
|
||
$as=$match[1];
|
||
if ($verbose_parsing) {
|
||
echo("<br>Object's AS is '<i>$as</i>'");
|
||
}
|
||
$object["infos"]["checkbox_state"]=$as;
|
||
$object["infos"]["checkbox_state_line"]=$Counter;
|
||
}
|
||
}
|
||
//ENDFIX
|
||
if(($type=='')||($subtype=='')||($name=="")) {
|
||
|
||
if(($type=='')&&$this->extract_pdf_definition_value("/Type",$CurLine,$match)) {
|
||
|
||
if($match[1]!='Border') {
|
||
$type=$match[1];
|
||
if($verbose_parsing) echo("<br>Object's type is '<i>$type</i>'");
|
||
}
|
||
|
||
}
|
||
if(($subtype=='')&&$this->extract_pdf_definition_value("/Subtype",$CurLine,$match)) {
|
||
|
||
$subtype=$match[1];
|
||
if($verbose_parsing) echo("<br>Object's subType is '<i>$subtype</i>'");
|
||
|
||
}
|
||
if(($name=="")&&preg_match("/^\/T\s?\((.+)\)\s*$/",$this->_protectContentValues($CurLine),$match)) {
|
||
|
||
$name=$this->_unprotectContentValues($match[1]);
|
||
//FIX: convert ASCII object names to utf-8
|
||
// don't use utf8_encode($name) yet, it's core function since php 7.2
|
||
$name = mb_convert_encoding($name, 'UTF-8', 'ASCII');
|
||
//ENDFIX
|
||
if($verbose_parsing) echo ("Object's name is '<i>$name</i>'");
|
||
|
||
$object["infos"]["name"]=$name; //Keep a track
|
||
$object["infos"]["name_line"]=$Counter;
|
||
|
||
//$this->dumpContent(" Name [$name]");
|
||
}
|
||
|
||
}// else {
|
||
|
||
//=== CONTENT ====
|
||
|
||
//$this->dumpContent($CurLine);
|
||
//=== Now, start the serious work , read DV, V Values and eventually TU
|
||
//note if(preg_match_all("/^\/(V|DV)\s+(\<|\))([^\)\>]+)(\)|\>)/",$CurLine,$matches)) {
|
||
//do not work as all is encoded on the same line...
|
||
if(preg_match("/^\/(V|DV|TU)\s+([\<\(])/",$CurLine,$def)) {
|
||
|
||
//get an human readable format of value type and encoding
|
||
|
||
if($def[1] == "TU") {
|
||
$valuetype="info";
|
||
$object["infos"]["tooltip"]=$Counter;
|
||
} else {
|
||
$valuetype=($def[1] == "DV") ? "default" : "current";
|
||
$object["values"]["$valuetype"]=$Counter; //Set a marker to process lately
|
||
}
|
||
|
||
$encoding=($def[2]=="<") ? "hex" : "plain";
|
||
|
||
if(preg_match("/^\/(V|DV|TU)\s+(\<|\)|\()([^\)\>]*)(\)|\>\))/",$CurLine,$values)) {
|
||
$value=$values[3];
|
||
$value=$this->decodeValue($encoding,$value);
|
||
}else
|
||
$value='';
|
||
|
||
if($verbose_parsing)
|
||
$this->dumpContent("$type $subtype (obj id=$obj) has $encoding $valuetype value [$value] at line $Counter");
|
||
|
||
|
||
}else if(preg_match("/^\/MaxLen\s+(\d+)/",$CurLine,$values)) {
|
||
$maxLen=$values[1];
|
||
$object["constraints"]["maxlen"]=intval($maxLen);
|
||
} else
|
||
if($verbose_parsing) echo("WARNING: definition ignored");
|
||
|
||
if(substr($CurLine,0,7)=='/Fields' && !$this->needAppearancesTrue) {
|
||
$CurLine='/NeedAppearances true '.$CurLine;
|
||
$entries[$Counter]=$CurLine;
|
||
}
|
||
|
||
//TODO: Fetch the XObject..and change Td <> Tj
|
||
/* if(preg_match("/^\/AP/",$CurLine,$values)) {
|
||
//die("stop");
|
||
$CurLine=''; //clear link to Xobject
|
||
$entries[$Counter]=$CurLine;
|
||
}*/
|
||
|
||
// }
|
||
|
||
}
|
||
|
||
|
||
}
|
||
|
||
}
|
||
|
||
//~~~~~Xref table header? ~~~~~~
|
||
if(preg_match("/\bxref\b/",$CurLine,$match)) {
|
||
|
||
$xref_table=1;
|
||
if($verbose_parsing) $this->dumpContent("->Starting xref table at line $Counter:[$CurLine]");
|
||
$lines['$_XREF_$']=array();
|
||
$lines['$_XREF_$']["entries"]=array();
|
||
$lines['$_XREF_$']["infos"]=array();
|
||
$lines['$_XREF_$']["infos"]["line"]=$Counter;
|
||
$lines['$_XREF_$']["infos"]["start"]=array();
|
||
$start_pointer=$this->pointer+strpos($CurLine,"xref"); //HACK for PDFcreator 1.0.0
|
||
$lines['$_XREF_$']["infos"]["start"]["pointer"]=$start_pointer;
|
||
}
|
||
|
||
}
|
||
$obj_header=false;
|
||
} else {
|
||
//We are inside the xref table
|
||
//$this->dumpContent($CurLine,"");
|
||
$xref_table=$xref_table+1;
|
||
switch($xref_table) {
|
||
case 2:
|
||
if(preg_match("/^(\d+) (\d+)/",$CurLine,$match)) {
|
||
$refs_count=intval($match[2]);//xref_table length+1 (includes this line)
|
||
$lines['$_XREF_$']["infos"]["count"]=$refs_count-1;
|
||
if($verbose_parsing) $this->dumpContent("Xref table length is $refs_count");
|
||
}else
|
||
if($verbose_parsing) $this->dumpContent("WARNING: Xref table length ignored!");
|
||
break;
|
||
case 3:
|
||
//Should be 0000000000 65535 f
|
||
if($verbose_parsing) $this->dumpContent("this is Xref table header, should be 0000000000 65535 f ");
|
||
break;
|
||
default:
|
||
//xref entries
|
||
if($refs_count>0) {
|
||
$xref=$xref_table-3;
|
||
|
||
if($refs_count == 1) {//Last one , due to the shift, is the trailer
|
||
if(!preg_match("/^trailer/",$CurLine)) //if not, Houston we have a problem
|
||
$this->Error("xref_table length corrupted?: Trailer not found at expected!");
|
||
else
|
||
$trailer_table=1;
|
||
}else {
|
||
$lines['$_XREF_$']["entries"][$xref]=$CurLine;
|
||
if($verbose_parsing) $this->dumpContent("Xref table entry for object $xref found.");
|
||
}
|
||
$refs_count--;
|
||
} else { //We are inside the trailer
|
||
|
||
if($trailer_table==1) { //should be <<
|
||
|
||
if(trim($CurLine) != '') { //HACK: PDFCreator Version 1.0.0 has an extra CR after trailer
|
||
if(!preg_match("/<</",$CurLine,$match))
|
||
$this->Error("trailer_table corrupted?; missing start delimiter << ");
|
||
$trailer_table++;
|
||
}
|
||
|
||
|
||
}else if(($trailer_table>0)&&((!is_null($id_def))||preg_match("/^\/(Size|Root|Info|ID|DocChecksum)/",$CurLine,$match))) {
|
||
|
||
//Value can be extracted using (\d+|\[[^\]]+\])
|
||
if(preg_match("/\/Size (\d+)/",$CurLine,$match)) {
|
||
//Seems to match with xref entries count..
|
||
$size_read=$match[1];
|
||
$this->info["size"]=$size_read;
|
||
if($verbose_parsing) $this->dumpContent("Size read ($size_read) for pdf found.");
|
||
}
|
||
|
||
if(preg_match("/^\/ID\s*\[\s*<([\da-fA-F]+)/",$CurLine,$match)) {
|
||
$oid=$match[1];
|
||
$id_def=true;
|
||
if($verbose_parsing) $this->dumpContent("ID chunk one ($oid) for pdf found.");
|
||
|
||
//Determines if the ID definition is one line...
|
||
if(preg_match("/\>\s?\</",$CurLine,$match))
|
||
$id_single_line_def=true;
|
||
|
||
}
|
||
|
||
if($id_def) {//we are inside the ID definition
|
||
if($id_single_line_def||$id_multi_line_def) {
|
||
//decode the second ID chunk
|
||
if(preg_match("/([\da-fA-F]+)>.*$/",$CurLine,$match)) {
|
||
$tid=$match[1];
|
||
$this->info["ID"]=array($oid,$tid);
|
||
if($verbose_parsing) $this->dumpContent("ID chunk two ($tid) for pdf found.");
|
||
$id_def=false;
|
||
}else
|
||
$this->Error("trailer_table corrupted?; ID chunk two can not be decoded ");
|
||
} else
|
||
$id_multi_line_def=true;
|
||
}
|
||
|
||
if(preg_match("/^\/DocChecksum \/([\da-fA-F]+)/",$CurLine,$match)) {
|
||
$checksum=$match[1];
|
||
$this->info["checksum"]=$checksum;
|
||
if($verbose_parsing) $this->dumpContent("Checksum read ($checksum) for pdf found.");
|
||
}
|
||
|
||
if(preg_match("/>>/",$CurLine,$match))
|
||
$trailer_table=-1;//negative value: expects startxref to follow
|
||
|
||
|
||
} else {
|
||
|
||
switch($trailer_table) {
|
||
case -1://startxref
|
||
if(!preg_match("/^startxref/",$CurLine,$match))
|
||
$this->Error("startxref tag expected, read $CurLine");
|
||
break;
|
||
case -2://startxref's value
|
||
if(preg_match("/^(\d+)/",$CurLine,$match)) {
|
||
$lines['$_XREF_$']["infos"]["start"]["value"]=intval($match[1]);
|
||
$lines['$_XREF_$']["infos"]["start"]["line"]=$Counter;
|
||
}else
|
||
$this->Error("startxref value expected, read $CurLine");
|
||
break;
|
||
default://%%EOF
|
||
}
|
||
$trailer_table--;
|
||
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
$this->pointer=$this->pointer+strlen($CurLine)+1; //+1 due to \n
|
||
$Counter++;
|
||
}
|
||
|
||
if($this->verbose) {
|
||
|
||
$refs=(array_key_exists('$_XREF_$',$lines)) ? $lines['$_XREF_$']["infos"]["count"] : 0;
|
||
if($refs) {
|
||
$this->dumpContent("PDF parse retrieved $refs refs");
|
||
}else {
|
||
$this->dumpContent("PDF parse retrieved no refs, seems the xref table is broken or inacessible, this is bad!");
|
||
}
|
||
}
|
||
|
||
return count($lines);
|
||
}
|
||
|
||
/**
|
||
* Protect ( ) that may be in value or names
|
||
*
|
||
* @access protected
|
||
* @param string $content the FDF content to protect values
|
||
* @return string the content protected
|
||
*/
|
||
function _protectContentValues($content) {
|
||
//-------------------------------------------------
|
||
$content=str_replace("\\(","$@#",$content);
|
||
$content=str_replace("\\)","#@$",$content);
|
||
return $content;
|
||
}
|
||
|
||
/**
|
||
* Unprotect ( ) that may be in value or names
|
||
*
|
||
* @access protected
|
||
* @param string $content the FDF content with protected values
|
||
* @return string the content unprotected
|
||
*/
|
||
function _unprotectContentValues($content) {
|
||
//--------------------------------------------------
|
||
$content=str_replace("$@#","\\(",$content);
|
||
$content=str_replace("#@$","\\)",$content);
|
||
$content=stripcslashes($content);
|
||
return $content;
|
||
}
|
||
|
||
/**
|
||
* Parses the content of a FDF file and saved extracted field data
|
||
*
|
||
*@access public
|
||
*@return array $fields the data of the fields parsed
|
||
*/
|
||
function parseFDFContent(){
|
||
//-------------------------
|
||
|
||
$content=$this->fdf_content;
|
||
$content=$this->_protectContentValues($content);//protect ( ) that may be in value or names...
|
||
|
||
if($this->verbose) $this->dumpEntries($content,"FDF parse");
|
||
|
||
//..so that this regexp can do its job without annoyances
|
||
if(preg_match_all("/(T|V)\s*\(([^\)]+)\)\s*\/(T|V)\s*\(([^\)]+)\)/", $content,$matches, PREG_PATTERN_ORDER)) {
|
||
|
||
$fMax=count($matches[0]);
|
||
$fields=array();
|
||
for($f=0;$f<$fMax;$f++) {
|
||
$value='';
|
||
$name='';
|
||
if($matches[1][$f]=="V") {
|
||
$value=$matches[2][$f];
|
||
if($matches[3][$f]=="T")
|
||
$name=$matches[4][$f];
|
||
else
|
||
$this->Error("Field $f ignored , incomplete field declaration, name is expected");
|
||
} else {
|
||
if($matches[1][$f]=="T") {
|
||
$name=$matches[2][$f];
|
||
if($matches[3][$f]=="V")
|
||
$value=$matches[4][$f];
|
||
else
|
||
$this->Error("Field $f ignored , incomplete field declaration, value is expected");
|
||
} else
|
||
$this->Error("Field $f ignored , Invalid field keys ({$matches[0][$f]})");
|
||
}
|
||
if($name!='') {
|
||
if(array_key_exists($name,$fields))
|
||
$this->Error("Field $f ignored , already defined");
|
||
else {
|
||
$name=$this->_unprotectContentValues($name);
|
||
$value=$this->_unprotectContentValues($value);
|
||
if($this->verbose)
|
||
$this->dumpContent("FDF field [$name] has its value set to \"$value\"");
|
||
$fields[$name]=$value;
|
||
}
|
||
} else
|
||
$this->Error("Field $f ignored , no name");
|
||
|
||
}
|
||
} else
|
||
if($this->verbose) $this->dumpContent($fields,"FDF has no fields",false);
|
||
|
||
if($this->verbose) $this->dumpContent($fields,"FDF parsed",false);
|
||
|
||
return $fields;
|
||
}
|
||
|
||
|
||
/**
|
||
* Close the opened file
|
||
*/
|
||
function closeFile() {
|
||
//--------------------
|
||
if (isset($this->f) && is_resource($this->f)) {
|
||
fclose($this->f);
|
||
unset($this->f);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Print Error and die
|
||
*
|
||
* @param string $msg Error-Message
|
||
*/
|
||
function Error($msg) {
|
||
//--------------------
|
||
die('<b>FPDF-Merge Error:</b> '.$msg);
|
||
}
|
||
|
||
|
||
}
|
||
|
||
}
|
||
|
||
unset($__tmp);
|