1
|
|
|
<?php
|
2
|
|
|
|
3
|
|
|
/*******************************************************************************
|
4
|
|
|
* FPDM *
|
5
|
|
|
* *
|
6
|
|
|
*@file fpdm.php *
|
7
|
|
|
*@name A free PDF form filling tool *
|
8
|
|
|
*@package fpdftk *
|
9
|
|
|
*@version 2.8 *
|
10
|
|
|
*@date 2011-12-31 *
|
11
|
|
|
*@author 0livier *
|
12
|
|
|
*@todo in the importance order, natively by fpdm *
|
13
|
|
|
* -stream inline support (content change,repack,offset/size calculations) *
|
14
|
|
|
* -pdf inline protection *
|
15
|
|
|
* -flatten support *
|
16
|
|
|
* -extends filling to another form fields types (checkboxes,combos..) *
|
17
|
|
|
*@note *
|
18
|
|
|
* V2.8 (31.12.2011) added UTF-8 support *
|
19
|
|
|
* V2.7 (29.12.2011) compatibility with PDFs generated by pdftk *
|
20
|
|
|
* V2.6 (25.12.2010) OpenOffice 3 compatibility issues for Florian *
|
21
|
|
|
* see tracking issue here: http://www.fpdf.org/?go=forum&i=53697&t=53697 *
|
22
|
|
|
* V2.5 (06.12.2010) pdftk support for flatten mode and more...special *
|
23
|
|
|
* christmas release to the fpdf fanclub even if the red guy is busy *
|
24
|
|
|
* V2.4 (01.12.2010) Hack for malformed stream definitions, new parsing and *
|
25
|
|
|
* stream core with advanced verbose output. Fix() bonus for corrupted pdfs. *
|
26
|
|
|
* V2.3 (28.11.2010) stream type was lost when /length defined after /Filter *
|
27
|
|
|
* V2.2 (27.11.2010) Stream filter improved:decode now handles multi filters! * *
|
28
|
|
|
* V2.1 (25.11.2010) Only filter support for streams, trailer detection was *
|
29
|
|
|
* too restrictive. fixes FDF error occuring when empty array data is given. *
|
30
|
|
|
* V2.0 (05.11.2010) Load support for inline text fields datas or FDF content *
|
31
|
|
|
* V1.1 (04.11.2010) Works now under php4 for backward compat. *
|
32
|
|
|
* V1.0 (03.11.2010) First working release *
|
33
|
|
|
*******************************************************************************/
|
34
|
|
|
|
35
|
|
|
$FPDM_FILTERS=array(); //holds all supported filters
|
36
|
|
|
$FPDM_REGEXPS= array(
|
37
|
|
|
"/Type"=>"/\/Type\s+\/(\w+)$/",
|
38
|
|
|
"/Subtype" =>"/^\/Subtype\s+\/(\w+)$/"
|
39
|
|
|
);
|
40
|
|
|
|
41
|
|
|
//Major stream filters come from FPDI's stuff but I've added some :)
|
42
|
|
|
require_once(APP_DIR . "/fpdm/filters/FilterASCIIHex.php");
|
43
|
|
|
require_once(APP_DIR . "/fpdm/filters/FilterASCII85.php");
|
44
|
|
|
require_once(APP_DIR . "/fpdm/filters/FilterFlate.php");
|
45
|
|
|
require_once(APP_DIR . "/fpdm/filters/FilterLZW.php");
|
46
|
|
|
require_once(APP_DIR . "/fpdm/filters/FilterStandard.php");
|
47
|
|
|
|
48
|
|
|
|
49
|
|
|
$__tmp = version_compare(phpversion(), "5") == -1 ? array('FPDM') : array('FPDM', false);
|
50
|
|
|
if (!call_user_func_array('class_exists', $__tmp)) {
|
51
|
|
|
|
52
|
|
|
|
53
|
|
|
define('FPDM_VERSION',2.6);
|
54
|
|
|
define('FPDM_RELEASE',"Snowstream forever dream(20101225)");
|
55
|
|
|
|
56
|
|
|
define('FPDM_INVALID',0);
|
57
|
|
|
define('FPDM_STATIC',1);
|
58
|
|
|
define('FPDM_COMMON',2);
|
59
|
|
|
define('FPDM_VERBOSE',3);
|
60
|
|
|
define('FPDM_CACHE',dirname(__FILE__).'/export/cache/'); //cache directory for fdf temporary files needed by pdftk.
|
61
|
|
|
define('FPDM_PASSWORD_MAX_LEN',15); //Security to prevent shell overflow.
|
62
|
|
|
|
63
|
|
|
class FPDM {
|
64
|
|
|
//@@@@@@@@@
|
65
|
|
|
|
66
|
|
|
private $FPDM_FILTERS = array();
|
67
|
|
|
private $FPDM_REGEXPS = array(
|
68
|
|
|
"/Type"=>"/\/Type\s+\/(\w+)$/",
|
69
|
|
|
"/Subtype" =>"/^\/Subtype\s+\/(\w+)$/"
|
70
|
|
|
);
|
71
|
|
|
var $pdf_source; //string: full pathname to the input pdf , a form file
|
72
|
|
|
var $fdf_source; //string: full pathname to the input fdf , a form data file
|
73
|
|
|
var $pdf_output; //string: full pathname to the resulting filled pdf
|
74
|
|
|
|
75
|
|
|
var $pdf_entries; //array: Holds the content of the pdf file as array
|
76
|
|
|
var $fdf_content; //string: holds the content of the fdf file
|
77
|
|
|
var $fdf_parse_needed;//boolean: false will use $fields data else extract data from fdf content
|
78
|
|
|
var $value_entries; //array: a map of values to faliclitate access and changes
|
79
|
|
|
|
80
|
|
|
var $positions; //array, stores what object id is at a given position n ($positions[n]=<obj_id>)
|
81
|
|
|
|
82
|
|
|
var $offsets; //array of offsets for objects, index is the object's id, starting at 1
|
83
|
|
|
var $pointer; //integer, Current line position in the pdf file during the parsing
|
84
|
|
|
|
85
|
|
|
var $shifts; //array, Shifts of objects in the order positions they appear in the pdf, starting at 0.
|
86
|
|
|
var $shift; //integer, Global shift file size due to object values size changes
|
87
|
|
|
|
88
|
|
|
var $streams; //Holds streams configuration found during parsing
|
89
|
|
|
var $streams_filter; //Regexp to decode filter streams
|
90
|
|
|
|
91
|
|
|
var $safe_mode; //boolean, if set, ignore previous offsets do no calculations for the new xref table, seek pos directly in file
|
92
|
|
|
var $check_mode; //boolean, Use this to track offset calculations errors in corrupteds pdfs files for sample
|
93
|
|
|
var $halt_mode; //if true, stops when offset error is encountered
|
94
|
|
|
|
95
|
|
|
var $info; //array, holds the info properties
|
96
|
|
|
var $fields; //array that holds fields-Data parsed from FDF
|
97
|
|
|
|
98
|
|
|
var $verbose; //boolean , a debug flag to decide whether or not to show internal process
|
99
|
|
|
var $verbose_level; //integer default is 1 and if greater than 3, shows internal parsing as well
|
100
|
|
|
|
101
|
|
|
var $support; //string set to 'native' for fpdm or 'pdftk' for pdf toolkit
|
102
|
|
|
var $flatten_mode; //if true, flatten field data as text and remove form fields (NOT YET SUPPORTED BY FPDM)
|
103
|
|
|
var $compress_mode; //boolean , pdftk feature only to compress streams
|
104
|
|
|
var $uncompress_mode; //boolean pdftk feature only to uncompress streams
|
105
|
|
|
var $security; //Array holding securtity settings
|
106
|
|
|
//(password owner nad user, encrypt (set to 40 or 128 or 0), allow <permissions>] see pdfk help
|
107
|
|
|
|
108
|
|
|
var $needAppearancesTrue; //boolean, indicates if /NeedAppearances is already set to true
|
109
|
|
|
var $isUTF8; //boolean (true for UTF-8, false for ISO-8859-1)
|
110
|
|
|
|
111
|
|
|
/**
|
112
|
|
|
* Constructor
|
113
|
|
|
*
|
114
|
|
|
*@example Common use:
|
115
|
|
|
*@param string $pdf_source Source-Filename
|
|
|
|
|
116
|
|
|
*@param string $fdf_source Source-Filename
|
|
|
|
|
117
|
|
|
*@param boolean $verbose , optional false per default
|
|
|
|
|
118
|
|
|
*/
|
119
|
|
|
function FPDM() {
|
|
|
|
|
120
|
|
|
//==============
|
121
|
|
|
|
122
|
|
|
$args=func_get_args();
|
123
|
|
|
$num_args=func_num_args();
|
124
|
|
|
|
125
|
|
|
$FDF_FILE=($num_args>=FPDM_COMMON);
|
126
|
|
|
$VERBOSE_FLAG=($num_args>=FPDM_VERBOSE);
|
127
|
|
|
|
128
|
|
|
$verbose=false;
|
129
|
|
|
|
130
|
|
|
//We are not joking here, let's have a polymorphic constructor!
|
131
|
|
|
switch($num_args) {
|
132
|
|
|
case FPDM_INVALID:
|
133
|
|
|
$this->Error("Invalid instantiation of FPDM, requires at least one param");
|
134
|
|
|
break;
|
135
|
|
|
case FPDM_STATIC:
|
136
|
|
|
if($args[0] =='[_STATIC_]') break; //static use, caller is anonymous function defined in _set_field_value
|
137
|
|
|
//else this is the pdf_source then, fdf content is loaded using Load() function
|
138
|
|
|
default:
|
139
|
|
|
case FPDM_VERBOSE: //Use the verbose value provided
|
|
|
|
|
140
|
|
|
if($VERBOSE_FLAG) $verbose=$args[2];
|
141
|
|
|
case FPDM_COMMON: //Common use
|
142
|
|
|
$this->pdf_source = $args[0];//Blank pdf form
|
143
|
|
|
|
144
|
|
|
if($FDF_FILE) {
|
145
|
|
|
$this->fdf_source = $args[1];//Holds the data of the fields to fill the form
|
146
|
|
|
$this->fdf_parse_needed=true;
|
147
|
|
|
}
|
148
|
|
|
|
149
|
|
|
//calculation and map
|
150
|
|
|
$this->offsets=array();
|
151
|
|
|
$this->pointer=0;
|
152
|
|
|
$this->shift=0;
|
153
|
|
|
$this->shifts=array();
|
154
|
|
|
$this->n=0;
|
|
|
|
|
155
|
|
|
|
156
|
|
|
//Stream filters
|
157
|
|
|
$filters=$this->getFilters("|");
|
158
|
|
|
$this->streams_filter="/(\/($filters))+/";
|
159
|
|
|
//$this->dumpContent($this->streams_filter);
|
160
|
|
|
|
161
|
|
|
$this->info=array();
|
162
|
|
|
|
163
|
|
|
//Debug modes
|
164
|
|
|
$this->verbose=$verbose;
|
165
|
|
|
$this->verbose_level=($verbose&&is_int($verbose)) ? $verbose : 1;
|
166
|
|
|
$this->safe_mode=false;
|
167
|
|
|
$this->check_mode=false; //script will takes much more time if you do so
|
168
|
|
|
$this->halt_mode=true;
|
169
|
|
|
|
170
|
|
|
$this->support='native'; //may ne overriden
|
171
|
|
|
$this->security=array('password'=>array('owner'=>null,'user'=>null),'encrypt'=>0,'allow'=>array());
|
172
|
|
|
|
173
|
|
|
//echo "<br>filesize:".filesize($this->pdf_source);
|
174
|
|
|
$this->load_file('PDF');
|
175
|
|
|
|
176
|
|
|
if($FDF_FILE) $this->load_file('FDF');
|
177
|
|
|
|
178
|
|
|
}
|
179
|
|
|
}
|
180
|
|
|
|
181
|
|
|
/**
|
182
|
|
|
*Loads a form data to be merged
|
183
|
|
|
*
|
184
|
|
|
*@note this overrides fdf input source if it was previously defined
|
185
|
|
|
*@access public
|
186
|
|
|
*@param string|array $fdf_data a FDF file content or $pdf_data an array containing the values for the fields to change
|
|
|
|
|
187
|
|
|
**/
|
188
|
|
|
function Load($data,$isUTF8=false) {
|
|
|
|
|
189
|
|
|
//------------------------
|
190
|
|
|
$this->isUTF8 = $isUTF8;
|
191
|
|
|
$this->load_file('FDF',$data);
|
192
|
|
|
}
|
193
|
|
|
|
194
|
|
|
/**
|
195
|
|
|
*Loads a file according to its type
|
196
|
|
|
*
|
197
|
|
|
*@access private
|
198
|
|
|
*@param string type 'PDF' or 'FDF'
|
199
|
|
|
*@param String|array content the data content of FDF files only or directly the fields values as array
|
200
|
|
|
**/
|
201
|
|
|
function load_file($type,$content=NULL) {
|
|
|
|
|
202
|
|
|
//------------------------------------
|
203
|
|
|
switch($type) {
|
204
|
|
|
case "PDF" :
|
205
|
|
|
if($content)
|
206
|
|
|
$this->Error("load_file do not accept PDF content, only FDF content sorry");
|
207
|
|
|
else
|
208
|
|
|
$this->pdf_entries = $this->getEntries($this->pdf_source,'PDF');
|
209
|
|
|
break;
|
210
|
|
|
case "FDF" :
|
211
|
|
|
if(!is_null($content)) {
|
212
|
|
|
if(is_array($content)) {
|
213
|
|
|
$this->fields=$content;
|
214
|
|
|
$this->fdf_parse_needed=false;
|
215
|
|
|
//$this->dumpEntries($content,"PDF fields content");
|
216
|
|
|
} else if(is_string($content)){ //String
|
217
|
|
|
$this->fdf_content = $content; //TODO: check content
|
218
|
|
|
$this->fdf_parse_needed=true;
|
219
|
|
|
} else
|
220
|
|
|
$this->Error('Invalid content type for this FDF file!');
|
221
|
|
|
} else {
|
222
|
|
|
$this->fdf_content = $this->getContent($this->fdf_source,'FDF');
|
223
|
|
|
$this->fdf_parse_needed=true;
|
224
|
|
|
}
|
225
|
|
|
break;
|
226
|
|
|
default:
|
227
|
|
|
$this->Error("Invalid file type $type");
|
228
|
|
|
}
|
229
|
|
|
}
|
230
|
|
|
|
231
|
|
|
/**
|
232
|
|
|
*Set a mode and play with your power debug toys
|
233
|
|
|
*
|
234
|
|
|
*@access public
|
235
|
|
|
*@note for big boys only coz it may hurt
|
236
|
|
|
*@param string $mode a choice between 'safe','check','verbose','halt' or 'verbose_level'
|
237
|
|
|
*@param string|int $value an integer for verbose_level
|
238
|
|
|
**/
|
239
|
|
|
function set_modes($mode,$value) {
|
|
|
|
|
240
|
|
|
//-------------------------------
|
241
|
|
|
switch($mode) {
|
242
|
|
|
case 'safe':
|
243
|
|
|
$this->safe_mode=$value;
|
244
|
|
|
break;
|
245
|
|
|
case 'check':
|
246
|
|
|
$this->check_mode=$value;
|
247
|
|
|
break;
|
248
|
|
|
case 'flatten':
|
249
|
|
|
$this->flatten_mode=$value;
|
250
|
|
|
break;
|
251
|
|
|
case 'compress_mode':
|
252
|
|
|
$this->compress_mode=$value;
|
253
|
|
|
if($value) $this->uncompress_mode=false;
|
254
|
|
|
break;
|
255
|
|
|
case 'uncompress_mode':
|
256
|
|
|
$this->uncompress_mode=$value;
|
257
|
|
|
if($value) $this->compress_mode=false;
|
258
|
|
|
break;
|
259
|
|
|
case 'verbose':
|
260
|
|
|
$this->verbose=$value;
|
261
|
|
|
break;
|
262
|
|
|
case 'halt':
|
263
|
|
|
$this->halt_mode=$value;
|
264
|
|
|
break;
|
265
|
|
|
case 'verbose_level':
|
266
|
|
|
$this->verbose_level=$value;
|
267
|
|
|
break;
|
268
|
|
|
default:
|
269
|
|
|
$this->Error("set_modes error, Invalid mode '<i>$mode</i>'");
|
270
|
|
|
}
|
271
|
|
|
}
|
272
|
|
|
|
273
|
|
|
/**
|
274
|
|
|
*Retrieves informations of the pdf
|
275
|
|
|
*
|
276
|
|
|
*@access public
|
277
|
|
|
*@note To track PDF versions and so on...
|
278
|
|
|
*@param Boolean output
|
279
|
|
|
**/
|
280
|
|
|
function Info($asArray=false) {
|
|
|
|
|
281
|
|
|
//----------------------
|
282
|
|
|
$info=$this->info;
|
283
|
|
|
$info["Reader"]=($this->support == "native") ? 'FPDF-Merge '.FPDM_VERSION: $this->support;
|
284
|
|
|
$info["Fields"]=$this->fields;
|
285
|
|
|
$info["Modes"]=array(
|
286
|
|
|
'safe'=>($this->safe_mode)? 'Yes' :'No',
|
287
|
|
|
'check'=>($this->check_mode) ? 'Yes': 'No',
|
288
|
|
|
'flatten'=>($this->flatten_mode) ? 'Yes': 'No',
|
289
|
|
|
'compress_mode'=>($this->compress_mode) ? 'Yes': 'No',
|
290
|
|
|
'uncompress_mode'=>($this->uncompress_mode) ? 'Yes': 'No',
|
291
|
|
|
'verbose'=>$this->verbose,
|
292
|
|
|
'verbose_level'=>$this->verbose_level,
|
293
|
|
|
'halt'=>$this->halt_mode
|
294
|
|
|
);
|
295
|
|
|
if($asArray) {
|
296
|
|
|
return $info;
|
297
|
|
|
} else {
|
298
|
|
|
$this->dumpEntries($info,"Welcome on FPDMerge flight to ".FPDM_RELEASE.", here is the pdf temperature:");
|
299
|
|
|
}
|
300
|
|
|
}
|
301
|
|
|
|
302
|
|
|
/**
|
303
|
|
|
*Changes the support
|
304
|
|
|
*
|
305
|
|
|
*@access public
|
306
|
|
|
*@internal fixes xref table offsets
|
307
|
|
|
*@note special playskool toy for Christmas dedicated to my impatient fanclub (Grant, Kris, nejck,...)
|
308
|
|
|
*@param String support Allow to use external support that has more advanced features (ie 'pdftk')
|
309
|
|
|
**/
|
310
|
|
|
function Plays($cool) {
|
|
|
|
|
311
|
|
|
//----------------------
|
312
|
|
|
if($cool=='pdftk') //Use a coolest support as ..
|
313
|
|
|
$this->support='pdftk';//..Per DeFinition This is Kool!
|
314
|
|
|
else
|
315
|
|
|
$this->support='native';
|
316
|
|
|
}
|
317
|
|
|
|
318
|
|
|
/**
|
319
|
|
|
*Fixes a corrupted PDF file
|
320
|
|
|
*
|
321
|
|
|
*@access public
|
322
|
|
|
*@internal fixes xref table offsets
|
323
|
|
|
*@note Real work is not made here but by Merge that should be launched after to complete the work
|
324
|
|
|
**/
|
325
|
|
|
function Fix() {
|
|
|
|
|
326
|
|
|
//---------------
|
327
|
|
|
if(!$this->fields) $this->fields=array(); //Default: No field data
|
|
|
|
|
328
|
|
|
$this->set_modes('check',true); //Compare xref table offsets with objects offsets in the pdf file
|
|
|
|
|
329
|
|
|
$this->set_modes('halt',false); //Do no stop on errors so fix is applied during merge process
|
|
|
|
|
330
|
|
|
}
|
331
|
|
|
|
332
|
|
|
//######## pdftk's output configuration #######
|
333
|
|
|
|
334
|
|
|
/**
|
335
|
|
|
*Decides to use the compress filter to restore compression.
|
336
|
|
|
*@note This is only useful when you want to repack PDF that was previously edited in a text editor like vim or emacs.
|
337
|
|
|
**/
|
338
|
|
|
function Compress() {
|
|
|
|
|
339
|
|
|
//-------------------
|
340
|
|
|
$this->set_modes('compress',true);
|
|
|
|
|
341
|
|
|
$this->support="pdftk";
|
342
|
|
|
}
|
343
|
|
|
|
344
|
|
|
/**
|
345
|
|
|
*Decides to remove PDF page stream compression by applying the uncompress filter.
|
346
|
|
|
*@note This is only useful when you want to edit PDF code in a text editor like vim or emacs.
|
347
|
|
|
**/
|
348
|
|
|
function Uncompress() {
|
|
|
|
|
349
|
|
|
//---------------------
|
350
|
|
|
$this->set_modes('uncompress',true);
|
|
|
|
|
351
|
|
|
$this->support="pdftk";
|
352
|
|
|
}
|
353
|
|
|
/**
|
354
|
|
|
*Activates the flatten output to remove form from pdf file keeping field datas.
|
355
|
|
|
**/
|
356
|
|
|
function Flatten() {
|
|
|
|
|
357
|
|
|
//-----------------
|
358
|
|
|
$this->set_modes('flatten',true);
|
|
|
|
|
359
|
|
|
$this->support="pdftk";
|
360
|
|
|
}
|
361
|
|
|
|
362
|
|
|
/***
|
363
|
|
|
*Defines a password type
|
364
|
|
|
*@param String type , 'owner' or 'user'
|
365
|
|
|
**/
|
366
|
|
View Code Duplication |
function Password($type,$code) {
|
|
|
|
|
367
|
|
|
//------------------------------
|
368
|
|
|
switch($type) {
|
369
|
|
|
case 'owner':
|
370
|
|
|
case 'user':
|
371
|
|
|
$this->security["password"]["$type"]=$code;
|
372
|
|
|
break;
|
373
|
|
|
default:
|
374
|
|
|
$this->Error("Unsupported password type ($type), specify 'owner' or 'user' instead.");
|
375
|
|
|
}
|
376
|
|
|
$this->support="pdftk";
|
377
|
|
|
}
|
378
|
|
|
|
379
|
|
|
|
380
|
|
|
/**
|
381
|
|
|
*Defines the encrytion to the given bits
|
382
|
|
|
*@param integer $bits 0, 40 or 128
|
383
|
|
|
**/
|
384
|
|
View Code Duplication |
function Encrypt($bits) {
|
|
|
|
|
385
|
|
|
//-----------------------
|
386
|
|
|
switch($bits) {
|
387
|
|
|
case 0:
|
388
|
|
|
case 40:
|
389
|
|
|
case 128:
|
390
|
|
|
$this->security["encrypt"]=$bits;
|
391
|
|
|
break;
|
392
|
|
|
default:
|
393
|
|
|
$this->Error("Unsupported encrypt value of $bits, only 0, 40 and 128 are supported");
|
394
|
|
|
}
|
395
|
|
|
$this->support="pdftk";
|
396
|
|
|
}
|
397
|
|
|
|
398
|
|
|
/**
|
399
|
|
|
*Allow permissions
|
400
|
|
|
*
|
401
|
|
|
*@param Array permmissions If no arg is given, show help.
|
402
|
|
|
* Permissions are applied to the output PDF only if an encryption
|
403
|
|
|
* strength is specified or an owner or user password is given. If
|
404
|
|
|
* permissions are not specified, they default to 'none,' which
|
405
|
|
|
* means all of the following features are disabled.
|
406
|
|
|
*
|
407
|
|
|
* The permissions section may include one or more of the following
|
408
|
|
|
* features:
|
409
|
|
|
*
|
410
|
|
|
* Printing
|
411
|
|
|
* Top Quality Printing
|
412
|
|
|
*
|
413
|
|
|
* DegradedPrinting
|
414
|
|
|
* Lower Quality Printing
|
415
|
|
|
*
|
416
|
|
|
* ModifyContents
|
417
|
|
|
* Also allows Assembly
|
418
|
|
|
*
|
419
|
|
|
* Assembly
|
420
|
|
|
*
|
421
|
|
|
* CopyContents
|
422
|
|
|
* Also allows ScreenReaders
|
423
|
|
|
*
|
424
|
|
|
* ScreenReaders
|
425
|
|
|
*
|
426
|
|
|
* ModifyAnnotations
|
427
|
|
|
* Also allows FillIn
|
428
|
|
|
*
|
429
|
|
|
* FillIn
|
430
|
|
|
*
|
431
|
|
|
* AllFeatures
|
432
|
|
|
* Allows the user to perform all of the above, and top
|
433
|
|
|
* quality printing.
|
434
|
|
|
**/
|
435
|
|
|
function Allow($permissions=null) {
|
|
|
|
|
436
|
|
|
//--------------------------
|
437
|
|
|
$perms_help=array(
|
438
|
|
|
'Printing'=>'Top Quality Printing',
|
439
|
|
|
'DegradedPrinting'=>'Lower Quality Printing',
|
440
|
|
|
'ModifyContents' =>'Also allows Assembly',
|
441
|
|
|
'Assembly' => '',
|
442
|
|
|
'CopyContents' => 'Also allows ScreenReaders',
|
443
|
|
|
'ScreenReaders' => '',
|
444
|
|
|
'ModifyAnnotations'=>'Also allows FillIn',
|
445
|
|
|
'FillIn'=>'',
|
446
|
|
|
'AllFeatures'=> "All above"
|
447
|
|
|
);
|
448
|
|
|
if(is_null($permissions)) {
|
449
|
|
|
echo '<br>Info Allow permissions:<br>';
|
450
|
|
|
print_r($perms_help);
|
451
|
|
|
}else {
|
452
|
|
|
if(is_string($permissions)) $permissions=array($permissions);
|
453
|
|
|
$perms=array_keys($perms_help);
|
454
|
|
|
$this->security["allow"]=array_intersect($permissions, $perms);
|
455
|
|
|
$this->support="pdftk";
|
456
|
|
|
}
|
457
|
|
|
}
|
458
|
|
|
|
459
|
|
|
//#############################
|
460
|
|
|
|
461
|
|
|
/**
|
462
|
|
|
*Merge FDF file with a PDF file
|
463
|
|
|
*
|
464
|
|
|
*@access public
|
465
|
|
|
*@note files has been provided during the instantiation of this class
|
466
|
|
|
*@internal flatten mode is not yet supported
|
467
|
|
|
*@param Boolean flatten Optional, false by default, if true will use pdftk (requires a shell) to flatten the pdf form
|
468
|
|
|
**/
|
469
|
|
|
function Merge($flatten=false) {
|
|
|
|
|
470
|
|
|
//------------------------------
|
471
|
|
|
|
472
|
|
|
if($flatten) $this->Flatten();
|
473
|
|
|
|
474
|
|
|
|
475
|
|
|
if($this->support == "native") {
|
476
|
|
|
|
477
|
|
|
if($this->fdf_parse_needed) {
|
478
|
|
|
$fields=$this->parseFDFContent();
|
479
|
|
|
}else {
|
480
|
|
|
$fields=$this->fields;
|
481
|
|
|
}
|
482
|
|
|
|
483
|
|
|
$count_fields=count($fields);
|
484
|
|
|
|
485
|
|
|
if($this->verbose&&($count_fields==0))
|
486
|
|
|
$this->dumpContent("The FDF content has either no field data or parsing may failed","FDF parser: ");
|
487
|
|
|
|
488
|
|
|
$fields_value_definition_lines=array();
|
489
|
|
|
|
490
|
|
|
$count_entries=$this->parsePDFEntries($fields_value_definition_lines);
|
491
|
|
|
|
492
|
|
|
|
493
|
|
|
if($count_entries) {
|
494
|
|
|
|
495
|
|
|
$this->value_entries=$fields_value_definition_lines;
|
496
|
|
|
if($this->verbose) {
|
497
|
|
|
$this->dumpContent("$count_entries Field entry values found for $count_fields field values to fill","Merge info: ");
|
498
|
|
|
}
|
499
|
|
|
//==== Alterate work is made here: change values ============
|
500
|
|
|
if($count_fields) {
|
501
|
|
|
foreach($fields as $name => $value) {
|
502
|
|
|
$this->set_field_value("current",$name,$value);
|
503
|
|
|
// $value=''; //Strategy applies only to current value, clear others
|
504
|
|
|
// $this->set_field_value("default",$name,$value);
|
505
|
|
|
// $this->set_field_value("tooltip",$name,$value);
|
506
|
|
|
}
|
507
|
|
|
}
|
508
|
|
|
//===========================================================
|
509
|
|
|
|
510
|
|
|
//===== Cross refs/size fixes (offsets calculations for objects have been previously be done in set_field_value) =======
|
511
|
|
|
|
512
|
|
|
//Update cross reference table to match object size changes
|
513
|
|
|
$this->fix_xref_table();
|
514
|
|
|
|
515
|
|
|
//update the pointer to the cross reference table
|
516
|
|
|
$this->fix_xref_start();
|
517
|
|
|
|
518
|
|
|
}else
|
519
|
|
|
$this->Error("PDF file is empty!");
|
520
|
|
|
|
521
|
|
|
} //else pdftk's job is done in Output, not here.
|
522
|
|
|
}
|
523
|
|
|
|
524
|
|
|
/**
|
525
|
|
|
*Warns verbose/output conflicts
|
526
|
|
|
*
|
527
|
|
|
*@access private
|
528
|
|
|
*@param string $dest a output destination
|
529
|
|
|
**/
|
530
|
|
|
function Close($dest) {
|
|
|
|
|
531
|
|
|
//----------------
|
532
|
|
|
$this->Error("Output: Verbose mode should be desactivated, it is incompatible with this output mode $dest");
|
533
|
|
|
}
|
534
|
|
|
|
535
|
|
|
/**
|
536
|
|
|
*Get current pdf content (without any offset fixes)
|
537
|
|
|
*
|
538
|
|
|
*@access private
|
539
|
|
|
*@param String pdf_file, if given , use the content as buffer (note file will be deleted after!)
|
540
|
|
|
*@return string buffer the pdf content
|
541
|
|
|
**/
|
542
|
|
|
function get_buffer($pdf_file=''){
|
|
|
|
|
543
|
|
|
//---------------------
|
544
|
|
|
if($pdf_file == '') {
|
545
|
|
|
$buffer=implode("\n",$this->pdf_entries);
|
546
|
|
|
}else {
|
547
|
|
|
$buffer=$this->getContent($pdf_file,'PDF');
|
548
|
|
|
//@unlink($pdf_file);
|
549
|
|
|
}
|
550
|
|
|
return $buffer;
|
551
|
|
|
}
|
552
|
|
|
|
553
|
|
|
|
554
|
|
|
/**
|
555
|
|
|
*Output PDF to some destination
|
556
|
|
|
*
|
557
|
|
|
*@access public
|
558
|
|
|
*@note reproduces the fpdf's behavior
|
559
|
|
|
*@param string name the filename
|
560
|
|
|
*@string dest the destination
|
561
|
|
|
* by default it's a file ('F')
|
562
|
|
|
* if 'D' , download
|
563
|
|
|
* and 'I' , Send to standard output
|
564
|
|
|
*
|
565
|
|
|
**/
|
566
|
|
|
function Output($name='', $dest=''){
|
|
|
|
|
567
|
|
|
//-----------------------------------
|
568
|
|
|
|
569
|
|
|
$pdf_file='';
|
570
|
|
|
|
571
|
|
|
if($this->support == "pdftk") {
|
572
|
|
|
//As PDFTK can only merge FDF files not data directly,
|
573
|
|
|
require_once("lib/url.php"); //we will need a url support because relative urls for pdf inside fdf files are not supported by PDFTK...
|
574
|
|
|
require_once("export/fdf/fdf.php"); //...conjointly with my patched/bridged forge_fdf that provides fdf file generation support from array data.
|
575
|
|
|
require_once("export/pdf/pdftk.php");//Of course don't forget to bridge to PDFTK!
|
576
|
|
|
|
577
|
|
|
$tmp_file=false;
|
578
|
|
|
$pdf_file=resolve_path(fix_path(dirname(__FILE__).'/'.$this->pdf_source)); //string: full pathname to the input pdf , a form file
|
579
|
|
|
|
580
|
|
|
if($this->fdf_source) { //FDF file provided
|
581
|
|
|
$fdf_file=resolve_path(fix_path(dirname(__FILE__).'/'.$this->fdf_source));
|
582
|
|
|
}else {
|
583
|
|
|
|
584
|
|
|
$pdf_url=getUrlfromDir($pdf_file); //Normaly http scheme not local file
|
585
|
|
|
|
586
|
|
|
if($this->fdf_parse_needed) { //fdf source was provided
|
587
|
|
|
$pdf_data=$this->parseFDFContent();
|
588
|
|
|
}else { //fields data was provided as an array, we have to generate the fdf file
|
589
|
|
|
$pdf_data=$this->fields;
|
590
|
|
|
}
|
591
|
|
|
|
592
|
|
|
$fdf_file=fix_path(FPDM_CACHE)."fields".rnunid().".fdf";
|
593
|
|
|
$tmp_file=true;
|
594
|
|
|
$ret=output_fdf($pdf_url,$pdf_data,$fdf_file);
|
595
|
|
|
if(!$ret["success"])
|
596
|
|
|
$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']}");
|
597
|
|
|
}
|
598
|
|
|
|
599
|
|
|
//Serializes security options (not deeply tested)
|
600
|
|
|
$security='';
|
601
|
|
View Code Duplication |
if(!is_null($this->security["password"]["owner"])) $security.=' owner_pw "'.substr($this->security["password"]["owner"],0,FPDM_PASSWORD_MAX_LEN).'"';
|
|
|
|
|
602
|
|
View Code Duplication |
if(!is_null($this->security["password"]["user"])) $security.=' user_pw "'.substr($this->security["password"]["user"],0,FPDM_PASSWORD_MAX_LEN).'"';
|
|
|
|
|
603
|
|
|
if($this->security["encrypt"]!=0) $security.=' encrypt_'.$this->security["encrypt"].'bit';
|
604
|
|
|
if(count($this->security["allow"])>0) {
|
605
|
|
|
$permissions=$this->security["allow"];
|
606
|
|
|
$security.=' allow ';
|
607
|
|
|
foreach($permissions as $permission)
|
608
|
|
|
$security.=' '.$permission;
|
609
|
|
|
}
|
610
|
|
|
|
611
|
|
|
//Serialize output modes
|
612
|
|
|
$output_modes='';
|
613
|
|
|
if($this->flatten_mode) $output_modes.=' flatten';
|
614
|
|
|
if($this->compress_mode) $output_modes.=' compress';
|
615
|
|
|
if($this->uncompress_mode) $output_modes.=' uncompress';
|
616
|
|
|
|
617
|
|
|
|
618
|
|
|
$ret=pdftk($pdf_file,$fdf_file,array("security"=>$security,"output_modes"=>$output_modes));
|
619
|
|
|
|
620
|
|
|
if($tmp_file) @unlink($fdf_file); //Clear cache
|
|
|
|
|
621
|
|
|
|
622
|
|
|
if($ret["success"]) {
|
623
|
|
|
$pdf_file=$ret["return"];
|
624
|
|
|
}else
|
625
|
|
|
$this->Error($ret["return"]);
|
626
|
|
|
}
|
627
|
|
|
|
628
|
|
|
$this->buffer=$this->get_buffer($pdf_file);
|
|
|
|
|
629
|
|
|
|
630
|
|
|
|
631
|
|
|
$dest=strtoupper($dest);
|
632
|
|
|
if($dest=='')
|
633
|
|
|
{
|
634
|
|
|
if($name=='')
|
635
|
|
|
{
|
636
|
|
|
$name='doc.pdf';
|
637
|
|
|
$dest='I';
|
638
|
|
|
}
|
639
|
|
|
else
|
640
|
|
|
$dest='F';
|
641
|
|
|
}
|
642
|
|
|
|
643
|
|
|
//Abort to avoid to polluate output
|
644
|
|
|
if($this->verbose&&(($dest=='I')||($dest=='D'))) {
|
645
|
|
|
$this->Close($dest);
|
646
|
|
|
}
|
647
|
|
|
|
648
|
|
|
switch($dest)
|
649
|
|
|
{
|
650
|
|
|
case 'I':
|
651
|
|
|
//Send to standard output
|
652
|
|
|
if(ob_get_length())
|
653
|
|
|
$this->Error('Some data has already been output, can\'t send PDF file');
|
654
|
|
|
if(php_sapi_name()!='cli')
|
655
|
|
|
{
|
656
|
|
|
//We send to a browser
|
657
|
|
|
header('Content-Type: application/pdf');
|
658
|
|
|
if(headers_sent())
|
659
|
|
|
$this->Error('Some data has already been output, can\'t send PDF file');
|
660
|
|
|
header('Content-Length: '.strlen($this->buffer));
|
661
|
|
|
header('Content-Disposition: inline; filename="'.$name.'"');
|
662
|
|
|
header('Cache-Control: private, max-age=0, must-revalidate');
|
663
|
|
|
header('Pragma: public');
|
664
|
|
|
ini_set('zlib.output_compression','0');
|
665
|
|
|
}
|
666
|
|
|
echo $this->buffer;
|
667
|
|
|
break;
|
668
|
|
|
case 'D':
|
669
|
|
|
//Download file
|
670
|
|
|
if(ob_get_length())
|
671
|
|
|
$this->Error('Some data has already been output, can\'t send PDF file');
|
672
|
|
|
header('Content-Type: application/x-download');
|
673
|
|
|
if(headers_sent())
|
674
|
|
|
$this->Error('Some data has already been output, can\'t send PDF file');
|
675
|
|
|
header('Content-Length: '.strlen($this->buffer));
|
676
|
|
|
header('Content-Disposition: attachment; filename="'.$name.'"');
|
677
|
|
|
|
678
|
|
|
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past
|
679
|
|
|
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); // always modified
|
680
|
|
|
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); // HTTP/1.1
|
681
|
|
|
header("Cache-Control: post-check=0, pre-check=0", false);
|
682
|
|
|
//header("Pragma: "); // HTTP/1.0
|
683
|
|
|
|
684
|
|
|
header('Cache-Control: private, max-age=0, must-revalidate');
|
685
|
|
|
header('Pragma: public,no-cache');
|
686
|
|
|
ini_set('zlib.output_compression','0');
|
687
|
|
|
echo $this->buffer;
|
688
|
|
|
break;
|
689
|
|
|
case 'F':
|
690
|
|
|
//Save to local file
|
691
|
|
|
if($this->verbose) $this->dumpContent("Write file $name","Output");
|
692
|
|
|
$f=fopen($name,'wb');
|
693
|
|
|
if(!$f)
|
694
|
|
|
$this->Error('Unable to create output file: '.$name.' (currently opened under Acrobat Reader?)');
|
695
|
|
|
|
696
|
|
|
fwrite($f,$this->buffer,strlen($this->buffer));
|
697
|
|
|
fclose($f);
|
698
|
|
|
break;
|
699
|
|
|
case 'S':
|
700
|
|
|
//Return as a string
|
701
|
|
|
return $this->buffer;
|
702
|
|
|
default:
|
703
|
|
|
$this->Error('Incorrect output destination: '.$dest);
|
704
|
|
|
}
|
705
|
|
|
return '';
|
706
|
|
|
}
|
707
|
|
|
|
708
|
|
|
|
709
|
|
|
/**
|
710
|
|
|
*Decodes and returns the binary form of a field hexified value
|
711
|
|
|
*
|
712
|
|
|
*@note static method due to callback..
|
713
|
|
|
*@param string value the hexified string
|
714
|
|
|
*@return string call the binary string
|
715
|
|
|
**/
|
716
|
|
|
function pdf_decode_field_value($value) {
|
|
|
|
|
717
|
|
|
//----------------------------------------
|
718
|
|
|
$call=$this->static_method_call('_hex2bin',$value);
|
719
|
|
|
return $call;
|
720
|
|
|
}
|
721
|
|
|
|
722
|
|
|
/**
|
723
|
|
|
*Encodes and returns the headecimal form of a field binary value
|
724
|
|
|
*
|
725
|
|
|
*@note static method due to callback..
|
726
|
|
|
*@param string value the binary string
|
727
|
|
|
*@return string call the hexified string
|
728
|
|
|
**/
|
729
|
|
|
function pdf_encode_field_value($value) {
|
|
|
|
|
730
|
|
|
//---------------------------------------
|
731
|
|
|
$value=$this->static_method_call('_bin2hex',$value);
|
732
|
|
|
return $value;
|
733
|
|
|
}
|
734
|
|
|
|
735
|
|
|
|
736
|
|
|
/**
|
737
|
|
|
*Universal Php4/5 static call helper
|
738
|
|
|
*
|
739
|
|
|
*@param String $method a name of a method belonging to this class
|
740
|
|
|
*@return mixed the return value of the called method
|
741
|
|
|
**/
|
742
|
|
|
function static_method_call($method) {
|
|
|
|
|
743
|
|
|
//---------------------------------------------
|
744
|
|
|
|
745
|
|
|
$params_call=func_get_args();
|
746
|
|
|
array_shift($params_call);
|
747
|
|
|
//var_dump($params_call);
|
748
|
|
|
|
749
|
|
|
return call_user_func_array(array($this,$method),$params_call);
|
750
|
|
|
}
|
751
|
|
|
|
752
|
|
|
/**
|
753
|
|
|
*Changes a field value that can be in hex <> or binary form ()
|
754
|
|
|
*
|
755
|
|
|
*@param $matches the regexp matches of the line that contains the value to change
|
756
|
|
|
*@param String $value the new value for the field property
|
757
|
|
|
**/
|
758
|
|
|
function replace_value($matches,$value) {
|
|
|
|
|
759
|
|
|
//----------------------------------------------
|
760
|
|
|
|
761
|
|
|
array_shift($matches);
|
762
|
|
|
|
763
|
|
|
if(($value!='')&&($matches[1]=="<")) //Value must be hexified..
|
764
|
|
|
$value=$this->pdf_encode_field_value($value);
|
765
|
|
|
|
766
|
|
|
$matches[2]=$value;
|
767
|
|
|
$value_type_code=$matches[0]; //Should be V, DV or TU
|
768
|
|
|
$matches[0]="/".$value_type_code." ";
|
769
|
|
|
|
770
|
|
|
$value=implode("",$matches);
|
771
|
|
|
//echo(htmlentities($value));
|
772
|
|
|
return $value;
|
773
|
|
|
}
|
774
|
|
|
|
775
|
|
|
/**
|
776
|
|
|
*Core to change the value of a field property, inline.
|
777
|
|
|
*
|
778
|
|
|
*@access private
|
779
|
|
|
*@param int $line the lien where the field property value is defined in the pdf file
|
780
|
|
|
*@param string $value the new value to set
|
781
|
|
|
*@return int $shift the size change of the field property value
|
782
|
|
|
**/
|
783
|
|
|
function _set_field_value($line,$value) {
|
|
|
|
|
784
|
|
|
//----------------------------------------
|
785
|
|
|
|
786
|
|
|
$verbose_set=($this->verbose&&($this->verbose_level>1));
|
787
|
|
|
//get the line content
|
788
|
|
|
$CurLine =$this->pdf_entries[$line];
|
789
|
|
|
|
790
|
|
|
$OldLen=strlen($CurLine);
|
791
|
|
|
|
792
|
|
|
//My PHP4/5 static call hack, only to make the callback $this->replace_value($matches,"$value") possible!
|
793
|
|
|
$callback_code='$THIS=new FPDM("[_STATIC_]");return $THIS->replace_value($matches,"'.$value.'");';
|
794
|
|
|
|
795
|
|
|
$field_regexp='/^\/(\w+)\s?(\<|\()([^\)\>]*)(\)|\>)/';
|
796
|
|
|
|
797
|
|
|
if(preg_match($field_regexp,$CurLine)) {
|
798
|
|
|
//modify it according to the new value $value
|
799
|
|
|
$CurLine = preg_replace_callback(
|
800
|
|
|
$field_regexp,
|
801
|
|
|
create_function('$matches',$callback_code),
|
802
|
|
|
$CurLine
|
803
|
|
|
);
|
804
|
|
|
}else {
|
805
|
|
|
if($verbose_set) echo("<br>WARNING:".htmlentities("Can not access to the value: $CurLine using regexp $field_regexp"));
|
806
|
|
|
}
|
807
|
|
|
|
808
|
|
|
|
809
|
|
|
$NewLen=strlen($CurLine);
|
810
|
|
|
$Shift=$NewLen-$OldLen;
|
811
|
|
|
$this->shift=$this->shift+$Shift;
|
812
|
|
|
|
813
|
|
|
//Saves
|
814
|
|
|
$this->pdf_entries[$line]=$CurLine;
|
815
|
|
|
|
816
|
|
|
return $Shift;
|
817
|
|
|
}
|
818
|
|
|
|
819
|
|
|
function _encode_value($str) {
|
|
|
|
|
820
|
|
|
if($this->isUTF8)
|
821
|
|
|
$str="\xFE\xFF".iconv('UTF-8','UTF-16BE',$str);
|
822
|
|
|
return $this->_bin2hex($str);
|
823
|
|
|
}
|
824
|
|
|
|
825
|
|
|
function _set_field_value2($line,$value,$append) {
|
|
|
|
|
826
|
|
|
$CurLine=$this->pdf_entries[$line];
|
827
|
|
|
$OldLen=strlen($CurLine);
|
828
|
|
|
|
829
|
|
|
if($append)
|
830
|
|
|
{
|
831
|
|
|
$CurLine .= ' /V <'.$this->_encode_value($value).'>';
|
832
|
|
|
}
|
833
|
|
|
else
|
834
|
|
|
{
|
835
|
|
|
if(preg_match('#/V\s?[<(]([^>)]*)[>)]#', $CurLine, $a, PREG_OFFSET_CAPTURE))
|
836
|
|
|
{
|
837
|
|
|
$len=strlen($a[1][0]);
|
838
|
|
|
$pos1=$a[1][1];
|
839
|
|
|
$pos2=$pos1+$len;
|
840
|
|
|
$CurLine=substr($CurLine,0,$pos1-1).'<'.$this->_encode_value($value).'>'.substr($CurLine,$pos2+1);
|
841
|
|
|
}
|
842
|
|
|
else
|
843
|
|
|
$this->Error('/V not found');
|
844
|
|
|
}
|
845
|
|
|
|
846
|
|
|
$NewLen=strlen($CurLine);
|
847
|
|
|
$Shift=$NewLen-$OldLen;
|
848
|
|
|
$this->shift=$this->shift+$Shift;
|
849
|
|
|
$this->pdf_entries[$line]=$CurLine;
|
850
|
|
|
return $Shift;
|
851
|
|
|
}
|
852
|
|
|
|
853
|
|
|
|
854
|
|
|
/**
|
855
|
|
|
*Changes the value of a field property, inline.
|
856
|
|
|
*
|
857
|
|
|
*@param string $type supported values for type are 'default' , 'current' or 'tooltip'
|
858
|
|
|
*@param string $name name of the field annotation to change the value
|
859
|
|
|
*@param string $value the new value to set
|
860
|
|
|
**/
|
861
|
|
|
function set_field_value($type,$name,$value) {
|
|
|
|
|
862
|
|
|
//------------------------------------
|
863
|
|
|
$verbose_set=($this->verbose&&($this->verbose_level>1));
|
|
|
|
|
864
|
|
|
|
865
|
|
|
//Get the line(s) of the misc field values
|
866
|
|
|
if(isset($this->value_entries["$name"])) {
|
867
|
|
|
|
868
|
|
|
$object_id=$this->value_entries["$name"]["infos"]["object"];
|
869
|
|
|
|
870
|
|
|
if($type=="tooltip") {
|
871
|
|
|
|
872
|
|
|
$offset_shift=$this->set_field_tooltip($name,$value);
|
873
|
|
|
|
874
|
|
|
} else {//if(isset($this->value_entries["$name"]["values"]["$type"])) {
|
875
|
|
|
// echo $this->value_entries["$name"]["values"]["$type"];
|
876
|
|
|
/* $field_value_line=$this->value_entries["$name"]["values"]["$type"];
|
877
|
|
|
$field_value_maxlen=$this->value_entries["$name"]["constraints"]["maxlen"];
|
878
|
|
|
|
879
|
|
|
if($field_value_maxlen) //Truncates the size if needed
|
880
|
|
|
$value=substr($value, 0, $field_value_maxlen);
|
881
|
|
|
|
882
|
|
|
if($verbose_set) echo "<br>Change $type value of the field $name at line $field_value_line to '<i>$value</i>'";
|
883
|
|
|
$offset_shift=$this->_set_field_value($field_value_line,$value);*/
|
884
|
|
|
if(isset($this->value_entries[$name]["values"]["current"]))
|
885
|
|
|
$offset_shift=$this->_set_field_value2($this->value_entries[$name]["values"]["current"],$value,false);
|
886
|
|
|
else
|
887
|
|
|
$offset_shift=$this->_set_field_value2($this->value_entries[$name]["infos"]["name_line"],$value,true);
|
888
|
|
|
}
|
889
|
|
|
// }else
|
890
|
|
|
// $this->Error("set_field_value failed as invalid valuetype $type for object $object_id");
|
891
|
|
|
|
892
|
|
|
|
893
|
|
|
//offset size shift will affect the next objects offsets taking into accound the order they appear in the file--
|
894
|
|
|
$this->apply_offset_shift_from_object($object_id,$offset_shift);
|
895
|
|
|
|
896
|
|
|
} else
|
897
|
|
|
$this->Error("field $name not found");
|
898
|
|
|
|
899
|
|
|
}
|
900
|
|
|
|
901
|
|
|
|
902
|
|
|
/**
|
903
|
|
|
*Changes the tooltip value of a field property, inline.
|
904
|
|
|
*
|
905
|
|
|
*@param string $name name of the field annotation to change the value
|
906
|
|
|
*@param string $value the new value to set
|
907
|
|
|
*@return int offset_shift the size variation
|
908
|
|
|
**/
|
909
|
|
|
function set_field_tooltip($name,$value) {
|
|
|
|
|
910
|
|
|
//------------------------------------
|
911
|
|
|
$offset_shift=0;
|
912
|
|
|
$verbose_set=($this->verbose&&($this->verbose_level>1));
|
913
|
|
|
|
914
|
|
|
//Get the line(s) of the misc field values
|
915
|
|
|
if(isset($this->value_entries["$name"])) {
|
916
|
|
|
$field_tooltip_line=$this->value_entries["$name"]["infos"]["tooltip"];
|
917
|
|
|
if($field_tooltip_line) {
|
918
|
|
|
if($verbose_set) echo "<br>Change tooltip of the field $name at line $field_tooltip_line to value [$value]";
|
919
|
|
|
$offset_shift=$this->_set_field_value($field_tooltip_line,$value);
|
920
|
|
|
}else {
|
921
|
|
|
if($verbose_set) echo "<br>Change toolpip value aborted, the field $name has no tooltip definition.";
|
922
|
|
|
}
|
923
|
|
|
} else
|
924
|
|
|
$this->Error("set_field_tooltip failed as the field $name does not exist");
|
925
|
|
|
return $offset_shift;
|
926
|
|
|
}
|
927
|
|
|
|
928
|
|
|
/**
|
929
|
|
|
*Dumps the line entries
|
930
|
|
|
*
|
931
|
|
|
*@note for debug purposes
|
932
|
|
|
*@access private
|
933
|
|
|
*@param array entries the content to dump
|
934
|
|
|
*@param string tag an optional tag to highlight
|
935
|
|
|
*@param boolean halt decides to stop or not this script
|
936
|
|
|
**/
|
937
|
|
|
function dumpEntries($entries,$tag="",$halt=false) {
|
|
|
|
|
938
|
|
|
//------------------------------------------------------------
|
939
|
|
|
if($tag) echo "<br><h4>$tag</h4><hr>";
|
940
|
|
|
if($entries) {
|
941
|
|
|
echo "<pre>";
|
942
|
|
|
echo htmlentities(print_r($entries,true));
|
943
|
|
|
echo "</pre>";
|
944
|
|
|
}
|
945
|
|
|
if($halt) exit();
|
946
|
|
|
}
|
947
|
|
|
|
948
|
|
|
|
949
|
|
|
/**
|
950
|
|
|
*Dumps the string content
|
951
|
|
|
*
|
952
|
|
|
*@note for debug purposes
|
953
|
|
|
*@access private
|
954
|
|
|
*@param string content the content to dump
|
955
|
|
|
*@param string tag an optional tag to highlight
|
956
|
|
|
*@param boolean halt decides to stop or not this script
|
957
|
|
|
**/
|
958
|
|
|
function dumpContent($content,$tag="",$halt=false) {
|
|
|
|
|
959
|
|
|
//--------------------------------------------------
|
960
|
|
|
if($tag) echo "<h4>$tag</h4>";
|
961
|
|
|
if($content) {
|
962
|
|
|
echo "<pre>";
|
963
|
|
|
echo htmlentities($content);
|
964
|
|
|
echo "</pre>";
|
965
|
|
|
}
|
966
|
|
|
if($halt) exit();
|
967
|
|
|
}
|
968
|
|
|
|
969
|
|
|
/**
|
970
|
|
|
*Retrieves the content of a file as a string
|
971
|
|
|
*
|
972
|
|
|
*@access private
|
973
|
|
|
*@param string $filename the filename of the file
|
974
|
|
|
*@param string $filetype the type of file as info
|
975
|
|
|
*@return string $content
|
976
|
|
|
**/
|
977
|
|
|
function getContent($filename,$filetype) {
|
|
|
|
|
978
|
|
|
//----------------------------------------
|
979
|
|
|
//$content = file_get_contents($filename);
|
980
|
|
|
$handle=fopen($filename,'rb');
|
981
|
|
|
$content = fread($handle, filesize($filename));
|
982
|
|
|
fclose($handle);
|
983
|
|
|
|
984
|
|
|
if (!$content)
|
985
|
|
|
$this->Error(sprintf('Cannot open '.$filetype.' file %s !', $filename));
|
986
|
|
|
|
987
|
|
|
if($filetype=='PDF')
|
988
|
|
|
{
|
989
|
|
|
$start = substr($content, 0, 2048);
|
990
|
|
|
if(strpos($start, '/ObjStm')!==false)
|
991
|
|
|
$this->Error('Object streams are not supported');
|
992
|
|
|
if(strpos($start, '/Linearized')!==false)
|
993
|
|
|
$this->Error('Fast Web View mode is not supported');
|
994
|
|
|
$end = substr($content, -512);
|
995
|
|
|
if(strpos($end, '/Prev')!==false)
|
996
|
|
|
$this->Error('Incremental updates are not supported');
|
997
|
|
|
$this->needAppearancesTrue = (strpos($content, '/NeedAppearances true')!==false);
|
998
|
|
|
}
|
999
|
|
|
|
1000
|
|
|
/* if($this->verbose) {
|
1001
|
|
|
$this->dumpContent($content,"$filetype file content read");
|
1002
|
|
|
}*/
|
1003
|
|
|
return $content;
|
1004
|
|
|
}
|
1005
|
|
|
|
1006
|
|
|
/**
|
1007
|
|
|
*Retrieves the content of a file as an array of lines entries
|
1008
|
|
|
*
|
1009
|
|
|
*@access private
|
1010
|
|
|
*@param string $filename the filename of the file
|
1011
|
|
|
*@param string $filetype the type of file as info
|
1012
|
|
|
*@return array $entries
|
1013
|
|
|
**/
|
1014
|
|
|
function getEntries($filename,$filetype) {
|
|
|
|
|
1015
|
|
|
//----------------------------------------
|
1016
|
|
|
$content=$this->getContent($filename,$filetype);
|
1017
|
|
|
$entries=explode("\n",$content);
|
1018
|
|
|
|
1019
|
|
|
/* if($this->verbose) {
|
1020
|
|
|
$this->dumpEntries($entries,"$filetype file entries");
|
1021
|
|
|
}*/
|
1022
|
|
|
return $entries;
|
1023
|
|
|
}
|
1024
|
|
|
|
1025
|
|
|
|
1026
|
|
|
/**
|
1027
|
|
|
*Retrieves a binary string from its hexadecimal representation
|
1028
|
|
|
*
|
1029
|
|
|
*@access private
|
1030
|
|
|
*@note Function was written because PHP has a bin2hex, but not a hex2bin!
|
1031
|
|
|
*@internal note pack(�C�,hexdec(substr($data,$i,2))) DOES NOT WORK
|
1032
|
|
|
*@param string $hexString the hexified string
|
1033
|
|
|
*@return string $bin a binary string
|
1034
|
|
|
**/
|
1035
|
|
View Code Duplication |
function _hex2bin ($hexString)
|
|
|
|
|
1036
|
|
|
{
|
1037
|
|
|
//echo "<br>_hex2bin($hexString)";
|
1038
|
|
|
$BinStr = '';
|
1039
|
|
|
|
1040
|
|
|
$hexLength=strlen($hexString);
|
1041
|
|
|
// only hex numbers is allowed
|
1042
|
|
|
if ($hexLength % 2 != 0 || preg_match("/[^\da-fA-F]/",$hexString)) return FALSE;
|
1043
|
|
|
|
1044
|
|
|
|
1045
|
|
|
//Loop through the input and convert it
|
1046
|
|
|
for ($i = 0; $i < $hexLength; $i += 2)
|
1047
|
|
|
$BinStr .= '%'.substr ($hexString, $i, 2);
|
1048
|
|
|
|
1049
|
|
|
|
1050
|
|
|
// Raw url-decode and return the result
|
1051
|
|
|
return rawurldecode ($BinStr);//chr(hexdec())
|
1052
|
|
|
}
|
1053
|
|
|
|
1054
|
|
|
|
1055
|
|
|
/**
|
1056
|
|
|
*Encodes a binary string to its hexadecimal representation
|
1057
|
|
|
*
|
1058
|
|
|
*@access private
|
1059
|
|
|
*@internal dechex(ord($str{$i})); is buggy because for hex value of 0-15 heading 0 is missing! Using sprintf() to get it right.
|
1060
|
|
|
*@param string $str a binary string
|
1061
|
|
|
*@return string $hex the hexified string
|
1062
|
|
|
**/
|
1063
|
|
View Code Duplication |
function _bin2hex($str) {
|
|
|
|
|
1064
|
|
|
//----------------------
|
1065
|
|
|
$hex = "";
|
1066
|
|
|
$i = 0;
|
1067
|
|
|
do {
|
1068
|
|
|
$hex .= sprintf("%02X", ord($str[$i]));
|
1069
|
|
|
$i++;
|
1070
|
|
|
} while ($i < strlen($str));
|
1071
|
|
|
return $hex;
|
1072
|
|
|
}
|
1073
|
|
|
|
1074
|
|
|
|
1075
|
|
|
/**
|
1076
|
|
|
* Extracts the map object for the xref table
|
1077
|
|
|
* @note PDF lines should have been previouly been parsed to make this work
|
1078
|
|
|
* @return array a map that holds the xrefstart infos and values
|
1079
|
|
|
*/
|
1080
|
|
|
function get_xref_table() {
|
|
|
|
|
1081
|
|
|
//------------------------
|
1082
|
|
|
return $this->value_entries['$_XREF_$'];
|
1083
|
|
|
}
|
1084
|
|
|
|
1085
|
|
|
/**
|
1086
|
|
|
* Extracts the offset of the xref table
|
1087
|
|
|
* @note PDF lines should have been previouly been parsed to make this work
|
1088
|
|
|
* @return int the xrefstart value
|
1089
|
|
|
*/
|
1090
|
|
|
function get_xref_start() {
|
|
|
|
|
1091
|
|
|
//------------------------
|
1092
|
|
|
return $this->value_entries['$_XREF_$']["infos"]["start"]["pointer"];
|
1093
|
|
|
}
|
1094
|
|
|
|
1095
|
|
|
|
1096
|
|
|
/**
|
1097
|
|
|
* Extracts the line where the offset of the xref table is stored
|
1098
|
|
|
* @note PDF lines should have been previouly been parsed to make this work
|
1099
|
|
|
* @return int the wished line number
|
1100
|
|
|
*/
|
1101
|
|
|
function get_xref_start_line() {
|
|
|
|
|
1102
|
|
|
//-------------------------------
|
1103
|
|
|
return $this->value_entries['$_XREF_$']["infos"]["start"]["line"];
|
1104
|
|
|
}
|
1105
|
|
|
|
1106
|
|
|
/**
|
1107
|
|
|
* Calculates the offset of the xref table
|
1108
|
|
|
*
|
1109
|
|
|
* @return int the wished xrefstart offset value
|
1110
|
|
|
*/
|
1111
|
|
|
function get_xref_start_value() {
|
|
|
|
|
1112
|
|
|
//-------------------------------
|
1113
|
|
|
$size_shift=$this->shift;
|
1114
|
|
|
$xref_start=$this->get_xref_start();
|
1115
|
|
|
return $xref_start+$size_shift;
|
1116
|
|
|
}
|
1117
|
|
|
|
1118
|
|
|
|
1119
|
|
|
/**
|
1120
|
|
|
* Read the offset of the xref table directly from file content
|
1121
|
|
|
*
|
1122
|
|
|
* @note content has been previously been defined in $this->buffer
|
1123
|
|
|
* @param int $object_id an object id, a integer value starting from 1
|
|
|
|
|
1124
|
|
|
* @return int the wished xrefstart offset value
|
1125
|
|
|
*/
|
1126
|
|
|
function read_xref_start_value() {
|
|
|
|
|
1127
|
|
|
//------------------------------
|
1128
|
|
|
$buffer=$this->get_buffer();
|
1129
|
|
|
$chunks = preg_split('/\bxref\b/', $buffer, -1, PREG_SPLIT_OFFSET_CAPTURE);
|
1130
|
|
|
return intval($chunks[1][1])-4; //-4 , relative to end of xref
|
1131
|
|
|
}
|
1132
|
|
|
|
1133
|
|
|
|
1134
|
|
|
/**
|
1135
|
|
|
* Calculates the new offset/xref for this object id by applying the offset_shift due to value changes
|
1136
|
|
|
*
|
1137
|
|
|
* @note uses internally precalculated $offsets,$positions and $shifts
|
1138
|
|
|
* @param int $object_id an object id, a integer value starting from 1
|
1139
|
|
|
* @return int the wished offset
|
1140
|
|
|
*/
|
1141
|
|
|
function get_offset_object_value($object_id) {
|
|
|
|
|
1142
|
|
|
//--------------------------------------------
|
1143
|
|
|
|
1144
|
|
|
//Static is to keep forever...
|
1145
|
|
|
static $offsets=null;
|
1146
|
|
|
static $positions=null;
|
1147
|
|
|
static $shifts=null;
|
1148
|
|
|
|
1149
|
|
|
if(is_null($offsets)) { //...variables content set once. This is the beauty of php :)
|
1150
|
|
|
|
1151
|
|
|
//!NOTE: xref table is ordered by object id (position's object is not defined linearly in the pdf !)
|
1152
|
|
|
$positions=$this->_get_positions_ordered();
|
1153
|
|
|
//Makes it 0 indexed as object id starts from 1 and positions starts from 0
|
1154
|
|
|
$offsets=$this->_get_offsets_starting_from_zero();
|
1155
|
|
|
//Shifts are already 0 indexed, don't change.
|
1156
|
|
|
$shifts=$this->shifts;
|
1157
|
|
|
}
|
1158
|
|
|
|
1159
|
|
|
$p=$positions[$object_id];
|
1160
|
|
|
$offset=$offsets[$p];
|
1161
|
|
|
$shift=$shifts[$p]; //size shift of the object due to value changes
|
1162
|
|
|
return $offset+$shift;
|
1163
|
|
|
}
|
1164
|
|
|
|
1165
|
|
|
|
1166
|
|
|
/**
|
1167
|
|
|
* Reads the offset of the xref table directly from file content
|
1168
|
|
|
*
|
1169
|
|
|
* @note content has been previously been defined in $this->buffer
|
1170
|
|
|
* @param int $object_id an object id, a integer value starting from 1
|
1171
|
|
|
* @return int the wished offset
|
1172
|
|
|
*/
|
1173
|
|
|
function read_offset_object_value($object_id) {
|
|
|
|
|
1174
|
|
|
//------------------------------
|
1175
|
|
|
$buffer=$this->buffer;
|
1176
|
|
|
$previous_object_footer='';//'endobj' or comment;
|
1177
|
|
|
$object_header=$previous_object_footer.'\n'.$object_id.' 0 obj';
|
1178
|
|
|
$chars = preg_split('/'.$object_header.'/', $buffer, -1, PREG_SPLIT_OFFSET_CAPTURE);
|
1179
|
|
|
$offset=intval($chars[1][1])-strlen($object_header)+strlen($previous_object_footer)+2;
|
1180
|
|
|
return $offset;
|
1181
|
|
|
}
|
1182
|
|
|
|
1183
|
|
|
|
1184
|
|
|
/**
|
1185
|
|
|
* Fix the offset of the xref table
|
1186
|
|
|
*
|
1187
|
|
|
*/
|
1188
|
|
|
function fix_xref_start() {
|
|
|
|
|
1189
|
|
|
//-------------------------
|
1190
|
|
|
|
1191
|
|
|
$pdf_entries=&$this->pdf_entries;
|
1192
|
|
|
$verbose_fix=($this->verbose&&($this->verbose_level>1));
|
1193
|
|
|
$calculate_xrefstart_value=((!$this->safe_mode)||$this->check_mode);
|
1194
|
|
|
$extract_xrefstart_value_from_file=($this->safe_mode||$this->check_mode);
|
1195
|
|
|
|
1196
|
|
|
if($calculate_xrefstart_value) {
|
1197
|
|
|
$xref_start_value_calculated=$this->get_xref_start_value(); //get computed value from old one
|
1198
|
|
|
if(!$this->safe_mode) $xref_start_value=$xref_start_value_calculated;
|
1199
|
|
|
}
|
1200
|
|
|
|
1201
|
|
|
if($extract_xrefstart_value_from_file) {
|
1202
|
|
|
$xref_start_value_safe=$this->read_xref_start_value();//read direct from new file content
|
1203
|
|
|
if($this->safe_mode) $xref_start_value=$xref_start_value_safe;
|
1204
|
|
|
}
|
1205
|
|
|
|
1206
|
|
|
if($this->check_mode) { //Compared calculated value with position value read direct from file
|
1207
|
|
|
if($xref_start_value_calculated != $xref_start_value_safe) {
|
1208
|
|
|
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>";
|
|
|
|
|
1209
|
|
|
$xref_start_value=$xref_start_value_safe; //Overrides with the good value
|
1210
|
|
|
if($this->halt_mode)
|
1211
|
|
|
$this->Error("Halt on error mode enabled, aborting. Use \$pdf->set_modes('halt',false); to disable this mode and go further fixing corrupted pdf.");
|
1212
|
|
|
} else {
|
1213
|
|
|
if($verbose_fix) echo "<br>xrefstart's value for the file is correct and vaults <b>$xref_start_value</b>";
|
|
|
|
|
1214
|
|
|
}
|
1215
|
|
|
}
|
1216
|
|
|
|
1217
|
|
|
//updates xrefstart's value
|
1218
|
|
|
$xref_start_line=$this->get_xref_start_line();
|
1219
|
|
|
$pdf_entries[$xref_start_line]="$xref_start_value";
|
1220
|
|
|
}
|
1221
|
|
|
|
1222
|
|
|
/**
|
1223
|
|
|
* Get the offsets table 0 indexed
|
1224
|
|
|
*
|
1225
|
|
|
* @return array $offsets
|
1226
|
|
|
*/
|
1227
|
|
|
function _get_offsets_starting_from_zero() {
|
|
|
|
|
1228
|
|
|
//-------------------------------------------
|
1229
|
|
|
$offsets=$this->offsets;
|
1230
|
|
|
return array_values($offsets);
|
1231
|
|
|
}
|
1232
|
|
|
|
1233
|
|
|
/**
|
1234
|
|
|
* Sorts the position array by key
|
1235
|
|
|
*
|
1236
|
|
|
* @return array $positions the ordered positions
|
1237
|
|
|
*/
|
1238
|
|
|
function _get_positions_ordered() {
|
|
|
|
|
1239
|
|
|
//--------------------------------
|
1240
|
|
|
$positions=$this->positions;
|
1241
|
|
|
ksort($positions);
|
1242
|
|
|
return $positions;
|
1243
|
|
|
}
|
1244
|
|
|
|
1245
|
|
|
/**
|
1246
|
|
|
* Fix the xref table by rebuilding its offsets entries
|
1247
|
|
|
*
|
1248
|
|
|
*/
|
1249
|
|
|
function fix_xref_table() {
|
|
|
|
|
1250
|
|
|
//------------------------
|
1251
|
|
|
|
1252
|
|
|
$xref_table=$this->get_xref_table();
|
1253
|
|
|
$xLen=$xref_table["infos"]["count"];
|
1254
|
|
|
$pdf_entries=&$this->pdf_entries;
|
1255
|
|
|
|
1256
|
|
|
//Do some checks
|
1257
|
|
|
$offsets=$this->offsets;
|
1258
|
|
|
//$offsets=array_values($offsets);
|
1259
|
|
|
$oLen=count($offsets);
|
1260
|
|
|
|
1261
|
|
|
|
1262
|
|
|
if($xLen == $oLen) { //...to rectify xref entries
|
1263
|
|
|
|
1264
|
|
|
//jump over len and header, this is the first entry with n
|
1265
|
|
|
$first_xref_entry_line=$xref_table["infos"]["line"]+3;
|
1266
|
|
|
|
1267
|
|
|
//echo "xREF:{$pdf_entries[$first_xref_entry_line]}";
|
1268
|
|
|
|
1269
|
|
|
//!NOTE: xref table is ordered by object id (position's object is not defined linearly in the pdf !)
|
1270
|
|
|
//$positions=$this->positions;
|
1271
|
|
|
//ksort($positions);
|
1272
|
|
|
$verbose_fix=($this->verbose&&($this->verbose>1));
|
1273
|
|
|
$calculate_offset_value=((!$this->safe_mode)||$this->check_mode);
|
1274
|
|
|
$extract_offset_value_from_file=($this->safe_mode||$this->check_mode);
|
1275
|
|
|
|
1276
|
|
|
//Get new file content (ie with values changed)
|
1277
|
|
|
$this->buffer=$this->get_buffer();
|
1278
|
|
|
|
1279
|
|
|
for($i=0;$i<$xLen;$i++) {
|
1280
|
|
|
|
1281
|
|
|
$obj_id=$i+1;
|
1282
|
|
|
|
1283
|
|
|
//Try two way to retrieve xref offset value of an object of the given id
|
1284
|
|
|
|
1285
|
|
|
if($calculate_offset_value) {
|
1286
|
|
|
$offset_value_calculated=$this->get_offset_object_value($obj_id);;
|
1287
|
|
|
if(!$this->safe_mode) $offset_value=$offset_value_calculated;
|
1288
|
|
|
}
|
1289
|
|
|
|
1290
|
|
|
if($extract_offset_value_from_file) {
|
1291
|
|
|
$offset_value_read=$this->read_offset_object_value($obj_id);
|
1292
|
|
|
if($this->safe_mode) $offset_value=$offset_value_read;
|
1293
|
|
|
}
|
1294
|
|
|
|
1295
|
|
|
if($this->check_mode) {
|
1296
|
|
|
if($offset_value_calculated != $offset_value_read) {
|
1297
|
|
|
if($verbose_fix) echo "<br>Offset for object $obj_id read is <b>$offset_value_read</b>, calculated $offset_value_calculated";
|
|
|
|
|
1298
|
|
|
$offset_value=$offset_value_read; //overrides to fix bad values
|
1299
|
|
|
if($this->halt_mode) $this->Error("<br>Offset for object $obj_id read is <b>$offset_value_read</b>, calculated $offset_value_calculated");
|
1300
|
|
|
}else {
|
1301
|
|
|
if($verbose_fix) echo "<br>Offset for object $obj_id is correct and vaults <b>$offset_value</b>";
|
|
|
|
|
1302
|
|
|
}
|
1303
|
|
|
}
|
1304
|
|
|
$pdf_entries[$first_xref_entry_line+$i]=sprintf('%010d 00000 n ',$offset_value);
|
1305
|
|
|
}
|
1306
|
|
|
|
1307
|
|
|
}else {
|
1308
|
|
|
//Congratulations you won the corrupted Error Prize
|
1309
|
|
|
$this->Error("Number of objects ($oLen) differs with number of xrefs ($xLen), something , pdf xref table is corrupted :(");
|
1310
|
|
|
}
|
1311
|
|
|
|
1312
|
|
|
|
1313
|
|
|
}
|
1314
|
|
|
|
1315
|
|
|
|
1316
|
|
|
/**
|
1317
|
|
|
* Applies a shift offset $shift from the object whose id is given as param
|
1318
|
|
|
*
|
1319
|
|
|
* @note offset shift will affect the next objects taking into accound the order they appear in the file
|
1320
|
|
|
* @access public
|
1321
|
|
|
* @param int object_id the id whose size shift has changed
|
1322
|
|
|
* @param int offset_shift the shift value to use
|
1323
|
|
|
*/
|
1324
|
|
|
function apply_offset_shift_from_object($object_id,$offset_shift) {
|
|
|
|
|
1325
|
|
|
//---------------------------------------------------------
|
1326
|
|
|
//get the position of object
|
1327
|
|
|
$object_pos=$this->positions[$object_id];
|
1328
|
|
|
//get the next object position
|
1329
|
|
|
$next_object_pos=$object_pos+1;
|
1330
|
|
|
//Applies offset change to next following objects
|
1331
|
|
|
$this->_apply_offset_shift($next_object_pos,$offset_shift);
|
1332
|
|
|
}
|
1333
|
|
|
|
1334
|
|
|
/**
|
1335
|
|
|
* Applies a shift offset $shift starting at the index $from to the shifts array
|
1336
|
|
|
*
|
1337
|
|
|
* @access private
|
1338
|
|
|
* @param int from the index to start apply the shift
|
1339
|
|
|
* @param int shift the shift value to use
|
1340
|
|
|
*/
|
1341
|
|
|
function _apply_offset_shift($from,$shift) {
|
|
|
|
|
1342
|
|
|
//------------------------------------------
|
1343
|
|
|
$offsets=&$this->shifts;
|
1344
|
|
|
$params=array($from,$shift);
|
|
|
|
|
1345
|
|
|
|
1346
|
|
|
foreach($offsets as $key=>$value) {
|
1347
|
|
|
if($key>=$from) {
|
1348
|
|
|
$offset=$offsets[$key]+$shift;
|
1349
|
|
|
$offsets[$key]=$offset;
|
1350
|
|
|
}
|
1351
|
|
|
}
|
1352
|
|
|
|
1353
|
|
|
}
|
1354
|
|
|
|
1355
|
|
|
/**
|
1356
|
|
|
* Decodes a PDF value according to the encoding
|
1357
|
|
|
*
|
1358
|
|
|
* @access public
|
1359
|
|
|
* @param string $encoding the encoding to use for decoding the value, only 'hex' is supported
|
1360
|
|
|
* @param string value a value to decode
|
1361
|
|
|
* @return string the value decoded
|
1362
|
|
|
*/
|
1363
|
|
|
function decodeValue($encoding,$value) {
|
|
|
|
|
1364
|
|
|
//----------------------------------------------
|
1365
|
|
|
//echo "Decoding $encoding value($value)";
|
1366
|
|
|
if($encoding=="hex")
|
1367
|
|
|
$value=$this->pdf_decode_field_value($value);
|
1368
|
|
|
return $value;
|
1369
|
|
|
}
|
1370
|
|
|
|
1371
|
|
|
/**
|
1372
|
|
|
*Retrieve the list of supported filters
|
1373
|
|
|
*
|
1374
|
|
|
*@note Uses $FPDM_FILTERS array built dynamically
|
1375
|
|
|
*@param String $sep a separator to merge filter names, default is '|'
|
1376
|
|
|
*@return String the suported filters
|
1377
|
|
|
**/
|
1378
|
|
|
function getFilters($sep="|") {
|
|
|
|
|
1379
|
|
|
//---------------------
|
1380
|
|
|
if ($this->FPDM_FILTERS) {
|
|
|
|
|
1381
|
|
|
return implode($sep,$this->FPDM_FILTERS);
|
1382
|
|
|
} else {
|
1383
|
|
|
return null;
|
1384
|
|
|
}
|
1385
|
|
|
}
|
1386
|
|
|
|
1387
|
|
|
|
1388
|
|
|
/**
|
1389
|
|
|
*Get a filter by name
|
1390
|
|
|
*
|
1391
|
|
|
*@param name a string matching one of the supported default filters (marked with +) *
|
1392
|
|
|
*Without parameters:
|
1393
|
|
|
*+ ASCIIHexDecode : Decodes data encoded in an ASCII hexadecimal representation, reproducing the original binary data.
|
1394
|
|
|
*+ ASCII85Decode : Decodes data encoded in an ASCII base-85 representation, reproducing the original binary data.
|
1395
|
|
|
* 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).
|
1396
|
|
|
* JPXDecode : (PDF 1.5) Decompresses data encoded using the wavelet-based JPEG2000 standard, reproducing the original image data.
|
1397
|
|
|
*With parameter(s):
|
1398
|
|
|
*+ LZWDecode : Decompresses data encoded using the LZW (Lempel-Ziv-Welch) adaptive compression method, reproducing the original text or binary data.
|
1399
|
|
|
*+ FlateDecode (PDF�1.2): Decompresses data encoded using the zlib/deflate compression method, reproducing the original text or binary data.
|
1400
|
|
|
* CCITTFaxDecode : Decompresses data encoded using the CCITT facsimile standard, reproducing the original data (typically monochrome image data at 1 bit per pixel).
|
1401
|
|
|
* 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).
|
1402
|
|
|
* 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.
|
1403
|
|
|
* Crypt (PDF 1.5) :Decrypts data encrypted by a security handler, reproducing the data as it was before encryption.
|
1404
|
|
|
*@return the wished filter class to access the stream
|
1405
|
|
|
**/
|
1406
|
|
|
function getFilter($name) {
|
|
|
|
|
1407
|
|
|
//---------------------
|
1408
|
|
|
|
1409
|
|
|
switch($name) {
|
1410
|
|
|
case "LZWDecode":
|
1411
|
|
|
$filter=new FilterLZW();
|
1412
|
|
|
break;
|
1413
|
|
|
case "ASCIIHexDecode":
|
1414
|
|
|
$filter=new FilterASCIIHex();
|
1415
|
|
|
break;
|
1416
|
|
|
case "ASCII85Decode":
|
1417
|
|
|
$filter=new FilterASCII85();
|
1418
|
|
|
break;
|
1419
|
|
|
case "FlateDecode":
|
1420
|
|
|
$filter=new FilterFlate();
|
1421
|
|
|
break;
|
1422
|
|
|
case "Standard": //Raw
|
1423
|
|
|
$filter=new FilterStandard();
|
1424
|
|
|
break;
|
1425
|
|
|
default:
|
1426
|
|
|
$filter=new FilterStandard();
|
1427
|
|
|
}
|
1428
|
|
|
|
1429
|
|
|
|
1430
|
|
|
return $filter;
|
1431
|
|
|
}
|
1432
|
|
|
|
1433
|
|
|
|
1434
|
|
|
//========= Stream manipulation stuff (alpha, not used by now!) ================
|
1435
|
|
|
|
1436
|
|
|
/**
|
1437
|
|
|
* Detect if the stream has a textual content
|
1438
|
|
|
*
|
1439
|
|
|
* @access public
|
1440
|
|
|
* @param string $stream the string content of the stream
|
|
|
|
|
1441
|
|
|
* @return boolean
|
1442
|
|
|
*/
|
1443
|
|
|
function is_text_stream($stream_content) {
|
|
|
|
|
1444
|
|
|
//--------------------------------------
|
1445
|
|
|
return preg_match("/(\s*Td\s+[\<\(])([^\>\)]+)([\>\)]\s+Tj)/",$stream_content);
|
1446
|
|
|
}
|
1447
|
|
|
|
1448
|
|
|
/**
|
1449
|
|
|
* changes the text value of a text stream
|
1450
|
|
|
*
|
1451
|
|
|
* @access public
|
1452
|
|
|
* @param array $stream the stream defintion retrieved during PDF parsing
|
1453
|
|
|
* @param string $value the new text value
|
1454
|
|
|
*/
|
1455
|
|
|
function change_stream_value($stream,$value) {
|
|
|
|
|
1456
|
|
|
//--------------------------------------------
|
1457
|
|
|
|
1458
|
|
|
$entries=&$this->pdf_entries;
|
1459
|
|
|
|
1460
|
|
|
$verbose_parsing=($this->verbose&&($this->verbose_level>3));
|
1461
|
|
|
|
1462
|
|
|
if($is_text_stream) {
|
|
|
|
|
1463
|
|
|
|
1464
|
|
|
$OldLen=$stream["length"]["value"];
|
1465
|
|
|
$lMin=$stream["start"];
|
1466
|
|
|
$lMax=$stream["end"];
|
1467
|
|
|
|
1468
|
|
|
$stream_content=$this->_set_text_value($stream_content,$value);
|
|
|
|
|
1469
|
|
|
$NewLen=strlen($stream_content);
|
1470
|
|
|
|
1471
|
|
|
for($l=$lMin;$l<=$lMax;$l++) {
|
1472
|
|
|
|
1473
|
|
|
if($l==$lMin) {
|
1474
|
|
|
$entries[$lMin]=$stream_content;
|
1475
|
|
|
|
1476
|
|
|
//Update the length
|
1477
|
|
|
$stream_def_line=$stream["length"]["line"];
|
1478
|
|
|
$stream_def=$entries[$stream_def_line];
|
1479
|
|
|
|
1480
|
|
|
$stream_def=preg_replace("/\/Length\s*(\d+)/",'/Length '.$NewLen,$stream_def);
|
1481
|
|
|
|
1482
|
|
|
$entries[$stream_def_line]=$stream_def;
|
1483
|
|
|
|
1484
|
|
|
//update the filter type...
|
1485
|
|
|
$stream_def_line=$stream["filters"]["line"];
|
1486
|
|
|
$stream_def=$entries[$stream_def_line];
|
1487
|
|
|
if($verbose_parsing) {
|
1488
|
|
|
echo "<pre>";
|
1489
|
|
|
echo htmlentities(print_r($stream_def,true));
|
1490
|
|
|
echo "</pre>";
|
1491
|
|
|
}
|
1492
|
|
|
|
1493
|
|
|
//...to filter Standard
|
1494
|
|
|
$stream_def=preg_replace($this->streams_filter,'/Standard ',$stream_def);
|
1495
|
|
|
|
1496
|
|
|
$entries[$stream_def_line]=$stream_def;
|
1497
|
|
|
|
1498
|
|
|
//Update the shift
|
1499
|
|
|
$size_shift=$NewLen-$OldLen;
|
1500
|
|
|
$this->apply_offset_shift_from_object($obj,$size_shift);
|
|
|
|
|
1501
|
|
|
|
1502
|
|
|
}else if($lmin!=$lMax) {
|
|
|
|
|
1503
|
|
|
unset($entries[$l]);
|
1504
|
|
|
}
|
1505
|
|
|
}
|
1506
|
|
|
|
1507
|
|
|
if($verbose_parsing) {
|
1508
|
|
|
var_dump($stream_content);
|
|
|
|
|
1509
|
|
|
}
|
1510
|
|
|
}
|
1511
|
|
|
}
|
1512
|
|
|
|
1513
|
|
|
/**
|
1514
|
|
|
* Overrides value between Td and TJ, ommiting <>
|
1515
|
|
|
*
|
1516
|
|
|
* @note core method
|
1517
|
|
|
* @access private
|
1518
|
|
|
* @param array $stream the stream defintion retrieved during PDF parsing
|
1519
|
|
|
* @param string $value the new text value
|
1520
|
|
|
*/
|
1521
|
|
|
function _set_text_value($stream,$value) {
|
|
|
|
|
1522
|
|
|
//---------------------------------------
|
1523
|
|
|
$chunks=preg_split("/(\s*Td\s+[\<\(])([^\>\)]+)([\>\)]\s+Tj)/",$stream,0,PREG_SPLIT_DELIM_CAPTURE);
|
1524
|
|
|
$chunks[2]=$value;
|
1525
|
|
|
$stream=implode($chunks,'');
|
1526
|
|
|
return $stream;
|
1527
|
|
|
}
|
1528
|
|
|
|
1529
|
|
|
|
1530
|
|
|
//================================
|
1531
|
|
|
|
1532
|
|
|
function _extract_pdf_definition_value($name,$line,&$match) {
|
|
|
|
|
1533
|
|
|
//-----------------------------------------------------------
|
1534
|
|
|
$value=preg_match($this->FPDM_REGEXPS["$name"],$line,$match);
|
1535
|
|
|
if(!$value) { //value is concatained with name: /name/value
|
1536
|
|
|
$value=preg_match("/".preg_quote($name,'/')."\/(\w+)/",$line,$match);
|
1537
|
|
|
}
|
1538
|
|
|
return $value;
|
1539
|
|
|
}
|
1540
|
|
|
|
1541
|
|
|
function extract_pdf_definition_value($name,$line,&$match) {
|
|
|
|
|
1542
|
|
|
//-----------------------------------------------------------
|
1543
|
|
|
|
1544
|
|
|
if(array_key_exists($name,$this->FPDM_REGEXPS)) {
|
1545
|
|
|
$value=$this->_extract_pdf_definition_value($name,$line,$match);
|
1546
|
|
|
}else
|
1547
|
|
|
$this->Error("extract_pdf_definition_value() does not support definition '$name'");
|
1548
|
|
|
|
1549
|
|
|
/*if($name=="/Type") {
|
1550
|
|
|
if(preg_match("/\//",$line,$foo)) {
|
1551
|
|
|
var_dump($match);
|
1552
|
|
|
die("Decoding $name value in line ".htmlentities($line));
|
1553
|
|
|
}
|
1554
|
|
|
}*/
|
1555
|
|
|
return $value;
|
|
|
|
|
1556
|
|
|
}
|
1557
|
|
|
|
1558
|
|
|
|
1559
|
|
|
/**
|
1560
|
|
|
* Parses the lines entries of a PDF
|
1561
|
|
|
*
|
1562
|
|
|
* @access public
|
1563
|
|
|
* @param array $lines the FDF content as an array of lines
|
1564
|
|
|
* @return integer the number of lines the PDF has
|
1565
|
|
|
*/
|
1566
|
|
|
function parsePDFEntries(&$lines){
|
|
|
|
|
1567
|
|
|
//--------------------------------
|
1568
|
|
|
|
1569
|
|
|
$entries=&$this->pdf_entries;
|
1570
|
|
|
|
1571
|
|
|
$CountLines = count($entries);
|
1572
|
|
|
|
1573
|
|
|
$Counter=0;
|
1574
|
|
|
$obj=0; //this is an invalid object id, we use it to know if we are into an object
|
1575
|
|
|
$type='';
|
1576
|
|
|
$subtype='';
|
1577
|
|
|
$name='';
|
1578
|
|
|
$value='';
|
|
|
|
|
1579
|
|
|
$default_maxLen=0; //No limit
|
1580
|
|
|
$default_tooltip_line=0; //Tooltip is optional as it may not be defined
|
1581
|
|
|
$xref_table=0;
|
1582
|
|
|
$trailer_table=0;
|
1583
|
|
|
$n=0; //Position of an object, in the order it is declared in the pdf file
|
1584
|
|
|
$stream=array();
|
1585
|
|
|
$id_def=false; //true when parsing/decoding trailer ID
|
1586
|
|
|
$id_single_line_def=false; //true when the two ID chunks are one the same line
|
1587
|
|
|
$id_multi_line_def=false; //true or OpenOffice 3.2
|
1588
|
|
|
$creator='';
|
1589
|
|
|
$producer='';
|
1590
|
|
|
$creationDate='';
|
1591
|
|
|
|
1592
|
|
|
$verbose_parsing=($this->verbose&&($this->verbose_level>3));
|
1593
|
|
|
$verbose_decoding=($this->verbose&&($this->verbose_level>4));
|
1594
|
|
|
|
1595
|
|
|
if($this->verbose) $this->dumpContent("Starting to parse $CountLines entries","PDF parse");
|
1596
|
|
|
|
1597
|
|
|
while ( $Counter < $CountLines ){
|
1598
|
|
|
|
1599
|
|
|
$CurLine = $entries[$Counter];
|
1600
|
|
|
|
1601
|
|
|
if($verbose_parsing) $this->dumpContent($CurLine,"====Parsing Line($Counter)");
|
1602
|
|
|
if(!$xref_table) {
|
1603
|
|
|
|
1604
|
|
|
//Header of an object?
|
1605
|
|
|
if(preg_match("/^(\d+) (\d+) obj/",$CurLine,$match)) {
|
1606
|
|
|
$obj=intval($match[1]);
|
1607
|
|
|
$this->offsets[$obj]=$this->pointer;
|
1608
|
|
|
$this->positions[$obj]=$n;
|
1609
|
|
|
$this->shifts[$n]=0;
|
1610
|
|
|
$n++;
|
1611
|
|
|
if($verbose_parsing) $this->dumpContent($CurLine,"====Opening object($obj) at line $Counter");
|
1612
|
|
|
$object=array();
|
1613
|
|
|
$object["values"]=array();
|
1614
|
|
|
$object["constraints"]=array();
|
1615
|
|
|
$object["constraints"]["maxlen"]=$default_maxLen;
|
1616
|
|
|
$object["infos"]=array();
|
1617
|
|
|
$object["infos"]["object"]=intval($obj);
|
1618
|
|
|
$object["infos"]["tooltip"]=$default_tooltip_line;
|
1619
|
|
|
|
1620
|
|
|
} else {
|
1621
|
|
|
|
1622
|
|
|
//Object has been opened
|
1623
|
|
|
if($obj) {
|
1624
|
|
|
|
1625
|
|
|
//Footer of an object?
|
1626
|
|
|
if(preg_match("/endobj/",$CurLine,$match)) {
|
1627
|
|
|
if($verbose_parsing) $this->dumpContent("","====Closing object($obj) at line $Counter");
|
1628
|
|
|
|
1629
|
|
|
//We process fields here, save only Annotations texts that are supported by now
|
1630
|
|
|
if(($type=='Annot')&&($subtype=="Widget")) {
|
1631
|
|
|
|
1632
|
|
|
if($name != '') {
|
1633
|
|
|
$lines["$name"]=$object;
|
|
|
|
|
1634
|
|
|
if($verbose_parsing) $this->dumpContent("$type $subtype (obj id=$obj) is a text annotation of name '$name', saves it.");
|
1635
|
|
|
}//else
|
1636
|
|
|
// $this->Error("$type $subtype (obj id=$obj) is a text annotation without a name, this cannot be.");
|
1637
|
|
|
|
1638
|
|
|
|
1639
|
|
|
$values=$object["values"];
|
1640
|
|
|
|
1641
|
|
|
//Sanity values checks, watchdog.
|
1642
|
|
|
// if(!array_key_exists("current",$values)) $this->Error("Cannot find value (/V) for field $name");
|
1643
|
|
|
// if(!array_key_exists("default",$values)) $this->Error("Cannot find default value (/DV) for field $name");
|
1644
|
|
|
|
1645
|
|
|
}else
|
1646
|
|
|
if($verbose_parsing) $this->dumpContent("Object $type $subtype (obj id=$obj) is not supported");
|
1647
|
|
|
|
1648
|
|
|
|
1649
|
|
|
$object=null;
|
1650
|
|
|
$obj=0;
|
1651
|
|
|
$type='';
|
1652
|
|
|
$subtype='';
|
1653
|
|
|
$name='';
|
1654
|
|
|
$value='';
|
|
|
|
|
1655
|
|
|
$maxLen=0;
|
|
|
|
|
1656
|
|
|
|
1657
|
|
|
} else {
|
1658
|
|
|
|
1659
|
|
|
if(preg_match("/\/Length\s*(\d+)/",$CurLine,$match)) {
|
1660
|
|
|
$stream["length"]=array("line"=>$Counter,"value"=>$match[1]);
|
1661
|
|
|
$stream["start"]=0;
|
1662
|
|
|
$stream["end"]=0;
|
1663
|
|
|
$stream["content"]='';
|
1664
|
|
|
if($verbose_parsing) $this->dumpContent($CurLine,"->Stream filter length definition(<font color=\"darkorange\">{$match[1]}</font>) for object($obj) at line $Counter");
|
1665
|
|
|
}
|
1666
|
|
|
|
1667
|
|
|
//Handles single filter /Filter /filter_type as well as well as filter chains such as /Filter [/filter_type1 /filter_type2 .../filter_typeN]
|
1668
|
|
|
if(preg_match_all($this->streams_filter,$CurLine,$matches)) {
|
1669
|
|
|
|
1670
|
|
|
//$this->dumpContent($this->streams_filter);
|
1671
|
|
|
/*$stream_filter=$match[1];
|
1672
|
|
|
$stream_filter=trim(preg_replace('/(<<|\/Length\s*\d+|>>)/', '', $stream_filter),' ');
|
1673
|
|
|
$stream_filters=preg_split('/\s*\//',$stream_filter);
|
1674
|
|
|
array_shift($stream_filters);*/
|
1675
|
|
|
$stream_filters=$matches[2];
|
1676
|
|
|
$stream["filters"]=array("line"=>$Counter, "type"=>$stream_filters);
|
1677
|
|
|
if($verbose_parsing) {
|
1678
|
|
|
//var_dump($stream_filters);
|
1679
|
|
|
$stream_filter=implode(" ",$stream_filters);
|
1680
|
|
|
$this->dumpContent($CurLine,"->Stream filter type definition(<font color=\"darkorange\">$stream_filter</font>) for object($obj) at line $Counter");
|
1681
|
|
|
}
|
1682
|
|
|
}
|
1683
|
|
|
|
1684
|
|
|
if(array_key_exists("length",$stream)) { //length is mandatory
|
1685
|
|
|
|
1686
|
|
|
if(preg_match("/\b(stream|endstream)\b/",$CurLine,$match)) {
|
1687
|
|
|
|
1688
|
|
|
if(!array_key_exists("filters",$stream)) {//filter type is optional, if none is given, its standard
|
1689
|
|
|
|
1690
|
|
|
$stream["filters"]=array("type"=>array("Standard"));
|
1691
|
|
|
if($verbose_parsing) {
|
1692
|
|
|
var_dump($stream);
|
|
|
|
|
1693
|
|
|
$this->dumpContent($CurLine,"->No stream filter type definition for object($obj) was found, setting it to '<font color=\"darkorange\">Standard</font>'");
|
1694
|
|
|
}
|
1695
|
|
|
}
|
1696
|
|
|
|
1697
|
|
|
|
1698
|
|
|
if($match[1] == "stream") {
|
1699
|
|
|
if($verbose_parsing) $this->dumpContent($CurLine,"->Opening stream for object($obj) at line $Counter");
|
1700
|
|
|
$stream["start"]=$Counter+1;
|
1701
|
|
|
}else {
|
1702
|
|
|
$stream["end"]=$Counter-1;
|
1703
|
|
|
|
1704
|
|
|
$stream["content"]=implode("\n",array_slice($entries,$stream["start"],$stream["end"]-$stream["start"]+1));
|
1705
|
|
|
|
1706
|
|
|
|
1707
|
|
|
|
1708
|
|
|
$filters=$stream["filters"]["type"];
|
1709
|
|
|
$f=count($filters);
|
|
|
|
|
1710
|
|
|
$stream_content=$stream["content"];
|
1711
|
|
|
|
1712
|
|
|
//var_dump($filters);
|
1713
|
|
|
|
1714
|
|
|
//$filters_type=$filters["type"];
|
1715
|
|
|
|
1716
|
|
|
//now process the stream, ie unpack it if needed
|
1717
|
|
|
//by decoding in the reverse order the streams have been encoded
|
1718
|
|
|
//This is done by applying decode using the filters in the order given by /Filter.
|
1719
|
|
|
foreach($filters as $filter_name) {
|
1720
|
|
|
|
1721
|
|
|
$stream_filter=$this->getFilter($filter_name);
|
1722
|
|
|
$stream_content=$stream_filter->decode($stream_content);
|
1723
|
|
|
if($verbose_decoding) {
|
1724
|
|
|
echo "<br><font color=\"blue\"><u>Stream decoded using filter '<font color=\"darkorange\">$filter_name</font>'</u>:[<pre>";
|
1725
|
|
|
var_dump($stream_content); //todo : manipulate this content and adjust offsets.
|
1726
|
|
|
echo "</pre>]</font>";
|
1727
|
|
|
}
|
1728
|
|
|
}
|
1729
|
|
|
|
1730
|
|
|
if($verbose_parsing) {
|
1731
|
|
|
$this->dumpEntries($stream);
|
1732
|
|
|
|
1733
|
|
|
echo "<font color=\"blue\">";
|
1734
|
|
|
if($this->is_text_stream($stream_content)) {
|
1735
|
|
|
echo "<u>Stream text unfiltered</u>:[<pre>";
|
1736
|
|
|
} else {
|
1737
|
|
|
echo "<u>Stream unfiltered</u>:[<pre>";
|
1738
|
|
|
}
|
1739
|
|
|
var_dump($stream_content);
|
1740
|
|
|
echo "</pre>]</font>";
|
1741
|
|
|
$this->dumpContent($CurLine,"->Closing stream for object($obj) at line $Counter");
|
1742
|
|
|
}
|
1743
|
|
|
|
1744
|
|
|
$stream=array();
|
1745
|
|
|
}
|
1746
|
|
|
}else if($stream["start"]>0){
|
1747
|
|
|
//stream content line that will be processed on endstream...
|
1748
|
|
|
}
|
1749
|
|
|
|
1750
|
|
|
} else {
|
1751
|
|
|
|
1752
|
|
|
/*
|
1753
|
|
|
Producer<FEFF004F00700065006E004F00660066006900630065002E006F0072006700200033002E0032>
|
1754
|
|
|
/CreationDate (D:20101225151810+01'00')>>
|
1755
|
|
|
*/
|
1756
|
|
View Code Duplication |
if(($creator=='')&&preg_match("/\/Creator\<([^\>]+)\>/",$CurLine,$values)) {
|
|
|
|
|
1757
|
|
|
$creator=$this->decodeValue("hex",$values[1]);
|
1758
|
|
|
if($verbose_parsing) echo("Creator read ($creator)");
|
1759
|
|
|
$this->info["Creator"]=$creator;
|
1760
|
|
|
}
|
1761
|
|
|
|
1762
|
|
View Code Duplication |
if(($producer=='')&&preg_match("/\/Producer\<([^\>]+)\>/",$CurLine,$values)) {
|
|
|
|
|
1763
|
|
|
$producer=$this->decodeValue("hex",$values[1]);
|
1764
|
|
|
if($verbose_parsing) echo("Producer read ($producer)");
|
1765
|
|
|
$this->info["Producer"]=$producer;
|
1766
|
|
|
}
|
1767
|
|
|
|
1768
|
|
View Code Duplication |
if(($creationDate=='')&&preg_match("/\/CreationDate\(([^\)]+)\)/",$CurLine,$values)) {
|
|
|
|
|
1769
|
|
|
$creationDate=$values[1];
|
1770
|
|
|
if($verbose_parsing) echo("Creation date read ($creationDate)");
|
1771
|
|
|
$this->info["CreationDate"]=$creationDate;
|
1772
|
|
|
}
|
1773
|
|
|
|
1774
|
|
|
//=== DEFINITION ====
|
1775
|
|
|
//preg_match("/^\/Type\s+\/(\w+)$/",$CurLine,$match)
|
1776
|
|
|
$match=array();
|
1777
|
|
|
if(($type=='')||($subtype=='')||($name=="")) {
|
1778
|
|
|
|
1779
|
|
View Code Duplication |
if(($type=='')&&$this->extract_pdf_definition_value("/Type",$CurLine,$match)) {
|
|
|
|
|
1780
|
|
|
|
1781
|
|
|
if($match[1]!='Border') {
|
1782
|
|
|
$type=$match[1];
|
1783
|
|
|
if($verbose_parsing) echo("<br>Object's type is '<i>$type</i>'");
|
1784
|
|
|
}
|
1785
|
|
|
|
1786
|
|
|
}
|
1787
|
|
View Code Duplication |
if(($subtype=='')&&$this->extract_pdf_definition_value("/Subtype",$CurLine,$match)) {
|
|
|
|
|
1788
|
|
|
|
1789
|
|
|
$subtype=$match[1];
|
1790
|
|
|
if($verbose_parsing) echo("<br>Object's subType is '<i>$subtype</i>'");
|
1791
|
|
|
|
1792
|
|
|
}
|
1793
|
|
|
if(($name=="")&&preg_match("/^\/T\s?\((.+)\)\s*$/",$this->_protectContentValues($CurLine),$match)) {
|
1794
|
|
|
|
1795
|
|
|
$name=$this->_unprotectContentValues($match[1]);
|
1796
|
|
|
if($verbose_parsing) echo ("Object's name is '<i>$name</i>'");
|
1797
|
|
|
|
1798
|
|
|
$object["infos"]["name"]=$name; //Keep a track
|
1799
|
|
|
$object["infos"]["name_line"]=$Counter;
|
1800
|
|
|
|
1801
|
|
|
//$this->dumpContent(" Name [$name]");
|
1802
|
|
|
}
|
1803
|
|
|
|
1804
|
|
|
}// else {
|
1805
|
|
|
|
1806
|
|
|
//=== CONTENT ====
|
1807
|
|
|
|
1808
|
|
|
//$this->dumpContent($CurLine);
|
1809
|
|
|
//=== Now, start the serious work , read DV, V Values and eventually TU
|
1810
|
|
|
//note if(preg_match_all("/^\/(V|DV)\s+(\<|\))([^\)\>]+)(\)|\>)/",$CurLine,$matches)) {
|
1811
|
|
|
//do not work as all is encoded on the same line...
|
1812
|
|
|
if(preg_match("/^\/(V|DV|TU)\s+([\<\(])/",$CurLine,$def)) {
|
1813
|
|
|
|
1814
|
|
|
//get an human readable format of value type and encoding
|
1815
|
|
|
|
1816
|
|
|
if($def[1] == "TU") {
|
1817
|
|
|
$valuetype="info";
|
1818
|
|
|
$object["infos"]["tooltip"]=$Counter;
|
1819
|
|
|
} else {
|
1820
|
|
|
$valuetype=($def[1] == "DV") ? "default" : "current";
|
1821
|
|
|
$object["values"]["$valuetype"]=$Counter; //Set a marker to process lately
|
1822
|
|
|
}
|
1823
|
|
|
|
1824
|
|
|
$encoding=($def[2]=="<") ? "hex" : "plain";
|
1825
|
|
|
|
1826
|
|
|
if(preg_match("/^\/(V|DV|TU)\s+(\<|\)|\()([^\)\>]*)(\)|\>\))/",$CurLine,$values)) {
|
1827
|
|
|
$value=$values[3];
|
1828
|
|
|
$value=$this->decodeValue($encoding,$value);
|
1829
|
|
|
}else
|
1830
|
|
|
$value='';
|
1831
|
|
|
|
1832
|
|
|
if($verbose_parsing)
|
1833
|
|
|
$this->dumpContent("$type $subtype (obj id=$obj) has $encoding $valuetype value [$value] at line $Counter");
|
1834
|
|
|
|
1835
|
|
|
|
1836
|
|
|
}else if(preg_match("/^\/MaxLen\s+(\d+)/",$CurLine,$values)) {
|
1837
|
|
|
$maxLen=$values[1];
|
1838
|
|
|
$object["constraints"]["maxlen"]=intval($maxLen);
|
1839
|
|
|
} else
|
1840
|
|
|
if($verbose_parsing) echo("WARNING: definition ignored");
|
1841
|
|
|
|
1842
|
|
|
if(substr($CurLine,0,7)=='/Fields' && !$this->needAppearancesTrue) {
|
1843
|
|
|
$CurLine='/NeedAppearances true '.$CurLine;
|
1844
|
|
|
$entries[$Counter]=$CurLine;
|
1845
|
|
|
}
|
1846
|
|
|
|
1847
|
|
|
//TODO: Fetch the XObject..and change Td <> Tj
|
1848
|
|
|
/* if(preg_match("/^\/AP/",$CurLine,$values)) {
|
1849
|
|
|
//die("stop");
|
1850
|
|
|
$CurLine=''; //clear link to Xobject
|
1851
|
|
|
$entries[$Counter]=$CurLine;
|
1852
|
|
|
}*/
|
1853
|
|
|
|
1854
|
|
|
// }
|
1855
|
|
|
|
1856
|
|
|
}
|
1857
|
|
|
|
1858
|
|
|
|
1859
|
|
|
}
|
1860
|
|
|
|
1861
|
|
|
}
|
1862
|
|
|
|
1863
|
|
|
//~~~~~Xref table header? ~~~~~~
|
1864
|
|
|
if(preg_match("/\bxref\b/",$CurLine,$match)) {
|
1865
|
|
|
|
1866
|
|
|
$xref_table=1;
|
1867
|
|
|
if($verbose_parsing) $this->dumpContent("->Starting xref table at line $Counter:[$CurLine]");
|
1868
|
|
|
$lines['$_XREF_$']=array();
|
1869
|
|
|
$lines['$_XREF_$']["entries"]=array();
|
1870
|
|
|
$lines['$_XREF_$']["infos"]=array();
|
1871
|
|
|
$lines['$_XREF_$']["infos"]["line"]=$Counter;
|
1872
|
|
|
$lines['$_XREF_$']["infos"]["start"]=array();
|
1873
|
|
|
$start_pointer=$this->pointer+strpos($CurLine,"xref"); //HACK for PDFcreator 1.0.0
|
1874
|
|
|
$lines['$_XREF_$']["infos"]["start"]["pointer"]=$start_pointer;
|
1875
|
|
|
}
|
1876
|
|
|
|
1877
|
|
|
}
|
1878
|
|
|
$obj_header=false;
|
|
|
|
|
1879
|
|
|
} else {
|
1880
|
|
|
//We are inside the xref table
|
1881
|
|
|
//$this->dumpContent($CurLine,"");
|
1882
|
|
|
$xref_table=$xref_table+1;
|
1883
|
|
|
switch($xref_table) {
|
1884
|
|
|
case 2:
|
1885
|
|
View Code Duplication |
if(preg_match("/^(\d+) (\d+)/",$CurLine,$match)) {
|
|
|
|
|
1886
|
|
|
$refs_count=intval($match[2]);//xref_table length+1 (includes this line)
|
1887
|
|
|
$lines['$_XREF_$']["infos"]["count"]=$refs_count-1;
|
1888
|
|
|
if($verbose_parsing) $this->dumpContent("Xref table length is $refs_count");
|
1889
|
|
|
}else
|
1890
|
|
|
if($verbose_parsing) $this->dumpContent("WARNING: Xref table length ignored!");
|
1891
|
|
|
break;
|
1892
|
|
|
case 3:
|
1893
|
|
|
//Should be 0000000000 65535 f
|
1894
|
|
|
if($verbose_parsing) $this->dumpContent("this is Xref table header, should be 0000000000 65535 f ");
|
1895
|
|
|
break;
|
1896
|
|
|
default:
|
1897
|
|
|
//xref entries
|
1898
|
|
|
if($refs_count>0) {
|
1899
|
|
|
$xref=$xref_table-3;
|
1900
|
|
|
|
1901
|
|
View Code Duplication |
if($refs_count == 1) {//Last one , due to the shift, is the trailer
|
|
|
|
|
1902
|
|
|
if(!preg_match("/^trailer/",$CurLine)) //if not, Houston we have a problem
|
1903
|
|
|
$this->Error("xref_table length corrupted?: Trailer not found at expected!");
|
1904
|
|
|
else
|
1905
|
|
|
$trailer_table=1;
|
1906
|
|
|
}else {
|
1907
|
|
|
$lines['$_XREF_$']["entries"][$xref]=$CurLine;
|
1908
|
|
|
if($verbose_parsing) $this->dumpContent("Xref table entry for object $xref found.");
|
1909
|
|
|
}
|
1910
|
|
|
$refs_count--;
|
1911
|
|
|
} else { //We are inside the trailer
|
1912
|
|
|
|
1913
|
|
|
if($trailer_table==1) { //should be <<
|
1914
|
|
|
|
1915
|
|
|
if(trim($CurLine) != '') { //HACK: PDFCreator Version 1.0.0 has an extra CR after trailer
|
1916
|
|
|
if(!preg_match("/<</",$CurLine,$match))
|
1917
|
|
|
$this->Error("trailer_table corrupted?; missing start delimiter << ");
|
1918
|
|
|
$trailer_table++;
|
1919
|
|
|
}
|
1920
|
|
|
|
1921
|
|
|
|
1922
|
|
|
}else if(($trailer_table>0)&&((!is_null($id_def))||preg_match("/^\/(Size|Root|Info|ID|DocChecksum)/",$CurLine,$match))) {
|
1923
|
|
|
|
1924
|
|
|
//Value can be extracted using (\d+|\[[^\]]+\])
|
1925
|
|
View Code Duplication |
if(preg_match("/\/Size (\d+)/",$CurLine,$match)) {
|
|
|
|
|
1926
|
|
|
//Seems to match with xref entries count..
|
1927
|
|
|
$size_read=$match[1];
|
1928
|
|
|
$this->info["size"]=$size_read;
|
1929
|
|
|
if($verbose_parsing) $this->dumpContent("Size read ($size_read) for pdf found.");
|
1930
|
|
|
}
|
1931
|
|
|
|
1932
|
|
|
if(preg_match("/^\/ID\s*\[\s*<([\da-fA-F]+)/",$CurLine,$match)) {
|
1933
|
|
|
$oid=$match[1];
|
1934
|
|
|
$id_def=true;
|
1935
|
|
|
if($verbose_parsing) $this->dumpContent("ID chunk one ($oid) for pdf found.");
|
1936
|
|
|
|
1937
|
|
|
//Determines if the ID definition is one line...
|
1938
|
|
|
if(preg_match("/\>\s?\</",$CurLine,$match))
|
1939
|
|
|
$id_single_line_def=true;
|
1940
|
|
|
|
1941
|
|
|
}
|
1942
|
|
|
|
1943
|
|
|
if($id_def) {//we are inside the ID definition
|
1944
|
|
|
if($id_single_line_def||$id_multi_line_def) {
|
1945
|
|
|
//decode the second ID chunk
|
1946
|
|
|
if(preg_match("/([\da-fA-F]+)>.*$/",$CurLine,$match)) {
|
1947
|
|
|
$tid=$match[1];
|
1948
|
|
|
$this->info["ID"]=array($oid,$tid);
|
|
|
|
|
1949
|
|
|
if($verbose_parsing) $this->dumpContent("ID chunk two ($tid) for pdf found.");
|
1950
|
|
|
$id_def=false;
|
1951
|
|
|
}else
|
1952
|
|
|
$this->Error("trailer_table corrupted?; ID chunk two can not be decoded ");
|
1953
|
|
|
} else
|
1954
|
|
|
$id_multi_line_def=true;
|
1955
|
|
|
}
|
1956
|
|
|
|
1957
|
|
View Code Duplication |
if(preg_match("/^\/DocChecksum \/([\da-fA-F]+)/",$CurLine,$match)) {
|
|
|
|
|
1958
|
|
|
$checksum=$match[1];
|
1959
|
|
|
$this->info["checksum"]=$checksum;
|
1960
|
|
|
if($verbose_parsing) $this->dumpContent("Checksum read ($checksum) for pdf found.");
|
1961
|
|
|
}
|
1962
|
|
|
|
1963
|
|
|
if(preg_match("/>>/",$CurLine,$match))
|
1964
|
|
|
$trailer_table=-1;//negative value: expects startxref to follow
|
1965
|
|
|
|
1966
|
|
|
|
1967
|
|
|
} else {
|
1968
|
|
|
|
1969
|
|
|
switch($trailer_table) {
|
1970
|
|
|
case -1://startxref
|
1971
|
|
|
if(!preg_match("/^startxref/",$CurLine,$match))
|
1972
|
|
|
$this->Error("startxref tag expected, read $CurLine");
|
1973
|
|
|
break;
|
1974
|
|
|
case -2://startxref's value
|
1975
|
|
|
if(preg_match("/^(\d+)/",$CurLine,$match)) {
|
1976
|
|
|
$lines['$_XREF_$']["infos"]["start"]["value"]=intval($match[1]);
|
1977
|
|
|
$lines['$_XREF_$']["infos"]["start"]["line"]=$Counter;
|
1978
|
|
|
}else
|
1979
|
|
|
$this->Error("startxref value expected, read $CurLine");
|
1980
|
|
|
break;
|
1981
|
|
|
default://%%EOF
|
1982
|
|
|
}
|
1983
|
|
|
$trailer_table--;
|
1984
|
|
|
|
1985
|
|
|
}
|
1986
|
|
|
|
1987
|
|
|
}
|
1988
|
|
|
}
|
1989
|
|
|
|
1990
|
|
|
}
|
1991
|
|
|
|
1992
|
|
|
$this->pointer=$this->pointer+strlen($CurLine)+1; //+1 due to \n
|
1993
|
|
|
$Counter++;
|
1994
|
|
|
}
|
1995
|
|
|
|
1996
|
|
|
if($this->verbose) {
|
1997
|
|
|
|
1998
|
|
|
$refs=(array_key_exists('$_XREF_$',$lines)) ? $lines['$_XREF_$']["infos"]["count"] : 0;
|
1999
|
|
|
if($refs) {
|
2000
|
|
|
$this->dumpContent("PDF parse retrieved $refs refs");
|
2001
|
|
|
}else {
|
2002
|
|
|
$this->dumpContent("PDF parse retrieved no refs, seems the xref table is broken or inacessible, this is bad!");
|
2003
|
|
|
}
|
2004
|
|
|
}
|
2005
|
|
|
|
2006
|
|
|
return count($lines);
|
2007
|
|
|
}
|
2008
|
|
|
|
2009
|
|
|
/**
|
2010
|
|
|
* Protect ( ) that may be in value or names
|
2011
|
|
|
*
|
2012
|
|
|
* @access protected
|
2013
|
|
|
* @param string $content the FDF content to protect values
|
2014
|
|
|
* @return string the content protected
|
2015
|
|
|
*/
|
2016
|
|
|
function _protectContentValues($content) {
|
|
|
|
|
2017
|
|
|
//-------------------------------------------------
|
2018
|
|
|
$content=str_replace("\\(","$@#",$content);
|
2019
|
|
|
$content=str_replace("\\)","#@$",$content);
|
2020
|
|
|
return $content;
|
2021
|
|
|
}
|
2022
|
|
|
|
2023
|
|
|
/**
|
2024
|
|
|
* Unprotect ( ) that may be in value or names
|
2025
|
|
|
*
|
2026
|
|
|
* @access protected
|
2027
|
|
|
* @param string $content the FDF content with protected values
|
2028
|
|
|
* @return string the content unprotected
|
2029
|
|
|
*/
|
2030
|
|
|
function _unprotectContentValues($content) {
|
|
|
|
|
2031
|
|
|
//--------------------------------------------------
|
2032
|
|
|
$content=str_replace("$@#","\\(",$content);
|
2033
|
|
|
$content=str_replace("#@$","\\)",$content);
|
2034
|
|
|
$content=stripcslashes($content);
|
2035
|
|
|
return $content;
|
2036
|
|
|
}
|
2037
|
|
|
|
2038
|
|
|
/**
|
2039
|
|
|
* Parses the content of a FDF file and saved extracted field data
|
2040
|
|
|
*
|
2041
|
|
|
*@access public
|
2042
|
|
|
*@return array $fields the data of the fields parsed
|
2043
|
|
|
*/
|
2044
|
|
|
function parseFDFContent(){
|
|
|
|
|
2045
|
|
|
//-------------------------
|
2046
|
|
|
|
2047
|
|
|
$content=$this->fdf_content;
|
2048
|
|
|
$content=$this->_protectContentValues($content);//protect ( ) that may be in value or names...
|
2049
|
|
|
|
2050
|
|
|
if($this->verbose) $this->dumpEntries($content,"FDF parse");
|
2051
|
|
|
|
2052
|
|
|
//..so that this regexp can do its job without annoyances
|
2053
|
|
|
if(preg_match_all("/(T|V)\s*\(([^\)]+)\)\s*\/(T|V)\s*\(([^\)]+)\)/", $content,$matches, PREG_PATTERN_ORDER)) {
|
2054
|
|
|
|
2055
|
|
|
$fMax=count($matches[0]);
|
2056
|
|
|
$fields=array();
|
2057
|
|
|
for($f=0;$f<$fMax;$f++) {
|
2058
|
|
|
$value='';
|
2059
|
|
|
$name='';
|
2060
|
|
|
if($matches[1][$f]=="V") {
|
2061
|
|
|
$value=$matches[2][$f];
|
2062
|
|
|
if($matches[3][$f]=="T")
|
2063
|
|
|
$name=$matches[4][$f];
|
2064
|
|
|
else
|
2065
|
|
|
$this->Error("Field $f ignored , incomplete field declaration, name is expected");
|
2066
|
|
|
} else {
|
2067
|
|
|
if($matches[1][$f]=="T") {
|
2068
|
|
|
$name=$matches[2][$f];
|
2069
|
|
|
if($matches[3][$f]=="V")
|
2070
|
|
|
$value=$matches[4][$f];
|
2071
|
|
|
else
|
2072
|
|
|
$this->Error("Field $f ignored , incomplete field declaration, value is expected");
|
2073
|
|
|
} else
|
2074
|
|
|
$this->Error("Field $f ignored , Invalid field keys ({$matches[0][$f]})");
|
2075
|
|
|
}
|
2076
|
|
|
if($name!='') {
|
2077
|
|
|
if(array_key_exists($name,$fields))
|
2078
|
|
|
$this->Error("Field $f ignored , already defined");
|
2079
|
|
|
else {
|
2080
|
|
|
$name=$this->_unprotectContentValues($name);
|
2081
|
|
|
$value=$this->_unprotectContentValues($value);
|
2082
|
|
|
if($this->verbose)
|
2083
|
|
|
$this->dumpContent("FDF field [$name] has its value set to \"$value\"");
|
2084
|
|
|
$fields[$name]=$value;
|
2085
|
|
|
}
|
2086
|
|
|
} else
|
2087
|
|
|
$this->Error("Field $f ignored , no name");
|
2088
|
|
|
|
2089
|
|
|
}
|
2090
|
|
|
} else
|
2091
|
|
|
if($this->verbose) $this->dumpContent($fields,"FDF has no fields",false);
|
|
|
|
|
2092
|
|
|
|
2093
|
|
|
if($this->verbose) $this->dumpContent($fields,"FDF parsed",false);
|
|
|
|
|
2094
|
|
|
|
2095
|
|
|
return $fields;
|
2096
|
|
|
}
|
2097
|
|
|
|
2098
|
|
|
|
2099
|
|
|
/**
|
2100
|
|
|
* Close the opened file
|
2101
|
|
|
*/
|
2102
|
|
|
function closeFile() {
|
|
|
|
|
2103
|
|
|
//--------------------
|
2104
|
|
|
if (isset($this->f) && is_resource($this->f)) {
|
2105
|
|
|
fclose($this->f);
|
|
|
|
|
2106
|
|
|
unset($this->f);
|
2107
|
|
|
}
|
2108
|
|
|
}
|
2109
|
|
|
|
2110
|
|
|
/**
|
2111
|
|
|
* Print Error and die
|
2112
|
|
|
*
|
2113
|
|
|
* @param string $msg Error-Message
|
2114
|
|
|
*/
|
2115
|
|
|
function Error($msg) {
|
|
|
|
|
2116
|
|
|
//--------------------
|
2117
|
|
|
die('<b>FPDF-Merge Error:</b> '.$msg);
|
2118
|
|
|
}
|
2119
|
|
|
|
2120
|
|
|
|
2121
|
|
|
}
|
2122
|
|
|
|
2123
|
|
|
}
|
2124
|
|
|
|
2125
|
|
|
unset($__tmp);
|
2126
|
|
|
?> |
|
|
|
|
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.
Consider the following example. The parameter
$italy
is not defined by the methodfinale(...)
.The most likely cause is that the parameter was removed, but the annotation was not.