1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Create pdf documents without additional modules |
4
|
|
|
* |
5
|
|
|
* Note that the companion class Document_CezPdf can be used to extend this class and |
6
|
|
|
* simplify the creation of documents. |
7
|
|
|
* |
8
|
|
|
* @category Documents |
9
|
|
|
* @package Document_Cpdf |
10
|
|
|
* @author Wayne Munro (inactive) <[email protected]> |
11
|
|
|
* @author Lars Olesen <[email protected]> |
12
|
|
|
* @author Sune Jensen <[email protected]> |
13
|
|
|
* @author Ole K <[email protected]> |
14
|
|
|
* @copyright 2007 - 2013 The authors |
15
|
|
|
* @license GPL http://www.opensource.org/licenses/gpl-license.php |
16
|
|
|
* @version 0.11.8 |
17
|
|
|
* @link http://pdf-php.sf.net |
18
|
|
|
*/ |
19
|
|
|
|
20
|
|
|
class Intraface_LegacyCpdf |
21
|
|
|
{ |
22
|
|
|
/** |
23
|
|
|
* allow the programmer to output debug messages on serveral places |
24
|
|
|
* 'none' = no debug output at all |
25
|
|
|
* 'error_log' = use error_log |
26
|
|
|
* 'variable' = store in a variable called $this->messages |
27
|
|
|
* |
28
|
|
|
* @default 'error_log' |
29
|
|
|
*/ |
30
|
|
|
public $DEBUG = 'error_log'; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Set the debug level |
34
|
|
|
* E_USER_ERROR = only errors |
35
|
|
|
* E_USER_WARNING = errors and warning |
36
|
|
|
* E_USER_NOTICE = nearly everything |
37
|
|
|
* |
38
|
|
|
* @default E_USER_WARNING |
39
|
|
|
*/ |
40
|
|
|
public $DEBUGLEVEL = E_USER_WARNING; |
41
|
|
|
/** |
42
|
|
|
* Reversed char string to allow arabic or Hebrew |
43
|
|
|
*/ |
44
|
|
|
public $rtl = false; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* flag to validate the output and if output method has be executed |
48
|
|
|
*/ |
49
|
|
|
protected $valid = false; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* global defined temporary path used on several places |
53
|
|
|
*/ |
54
|
|
|
public $tempPath = '/tmp'; |
55
|
|
|
/** |
56
|
|
|
* the current number of pdf objects in the document |
57
|
|
|
* |
58
|
|
|
* @var integer |
59
|
|
|
*/ |
60
|
|
|
private $numObj=0; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* this array contains all of the pdf objects, ready for final assembly |
64
|
|
|
* |
65
|
|
|
* @var array |
66
|
|
|
*/ |
67
|
|
|
private $objects = array(); |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* allows object being hashed (affect images only) |
71
|
|
|
*/ |
72
|
|
|
public $hashed = true; |
73
|
|
|
/** |
74
|
|
|
* Object hash used to free pdf from redundacies (primary images) |
75
|
|
|
*/ |
76
|
|
|
private $objectHashes = array(); |
|
|
|
|
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* the objectId (number within the objects array) of the document catalog |
80
|
|
|
* |
81
|
|
|
* @var integer |
82
|
|
|
*/ |
83
|
|
|
private $catalogId; |
84
|
|
|
|
85
|
|
|
|
86
|
|
|
public $targetEncoding = 'iso-8859-1'; |
87
|
|
|
/** |
88
|
|
|
* @var boolean Whether the text passed in should be treated as Unicode or just local character set. |
89
|
|
|
*/ |
90
|
|
|
public $isUnicode = false; |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* @var boolean used to either embed or not embed ttf/pfb fonts. |
94
|
|
|
*/ |
95
|
|
|
protected $embedFont = true; |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* store the information about the relationship between font families |
99
|
|
|
* this used so that the code knows which font is the bold version of another font, etc. |
100
|
|
|
* the value of this array is initialised in the constuctor function. |
101
|
|
|
* |
102
|
|
|
* @var array |
103
|
|
|
*/ |
104
|
|
|
private $fontFamilies = array( |
105
|
|
|
'Helvetica' => array( |
106
|
|
|
'b'=>'Helvetica-Bold', |
107
|
|
|
'i'=>'Helvetica-Oblique', |
108
|
|
|
'bi'=>'Helvetica-BoldOblique', |
109
|
|
|
'ib'=>'Helvetica-BoldOblique', |
110
|
|
|
), |
111
|
|
|
'Courier' => array( |
112
|
|
|
'b'=>'Courier-Bold', |
113
|
|
|
'i'=>'Courier-Oblique', |
114
|
|
|
'bi'=>'Courier-BoldOblique', |
115
|
|
|
'ib'=>'Courier-BoldOblique', |
116
|
|
|
), |
117
|
|
|
'Times-Roman' => array( |
118
|
|
|
'b'=>'Times-Bold', |
119
|
|
|
'i'=>'Times-Italic', |
120
|
|
|
'bi'=>'Times-BoldItalic', |
121
|
|
|
'ib'=>'Times-BoldItalic', |
122
|
|
|
) |
123
|
|
|
); |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* the core fonts to ignore them from unicode |
127
|
|
|
*/ |
128
|
|
|
private $coreFonts = array('courier', 'courier-bold', 'courier-oblique', 'courier-boldoblique', |
129
|
|
|
'helvetica', 'helvetica-bold', 'helvetica-oblique', 'helvetica-boldoblique', |
130
|
|
|
'times-roman', 'times-bold', 'times-italic', 'times-bolditalic', |
131
|
|
|
'symbol', 'zapfdingbats'); |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* array carrying information about the fonts that the system currently knows about |
135
|
|
|
* used to ensure that a font is not loaded twice, among other things |
136
|
|
|
* |
137
|
|
|
* @var array |
138
|
|
|
*/ |
139
|
|
|
private $fonts = array(); |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* a record of the current font |
143
|
|
|
* |
144
|
|
|
* @var string |
145
|
|
|
*/ |
146
|
|
|
private $currentFont=''; |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* the current base font |
150
|
|
|
* |
151
|
|
|
* @var string |
152
|
|
|
*/ |
153
|
|
|
private $currentBaseFont=''; |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* the number of the current font within the font array |
157
|
|
|
* |
158
|
|
|
* @var integer |
159
|
|
|
*/ |
160
|
|
|
private $currentFontNum=0; |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* @var integer |
164
|
|
|
*/ |
165
|
|
|
private $currentNode; |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* object number of the current page |
169
|
|
|
* |
170
|
|
|
* @var integer |
171
|
|
|
*/ |
172
|
|
|
private $currentPage; |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* object number of the currently active contents block |
176
|
|
|
*/ |
177
|
|
|
private $currentContents; |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* number of fonts within the system |
181
|
|
|
*/ |
182
|
|
|
private $numFonts = 0; |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* current colour for fill operations, defaults to inactive value, all three components should be between 0 and 1 inclusive when active |
186
|
|
|
*/ |
187
|
|
|
private $currentColour = array('r' => -1, 'g' => -1, 'b' => -1); |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* current colour for stroke operations (lines etc.) |
191
|
|
|
*/ |
192
|
|
|
private $currentStrokeColour = array('r' => -1, 'g' => -1, 'b' => -1); |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* current style that lines are drawn in |
196
|
|
|
*/ |
197
|
|
|
private $currentLineStyle=''; |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* an array which is used to save the state of the document, mainly the colours and styles |
201
|
|
|
* it is used to temporarily change to another state, the change back to what it was before |
202
|
|
|
*/ |
203
|
|
|
private $stateStack = array(); |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* number of elements within the state stack |
207
|
|
|
*/ |
208
|
|
|
private $nStateStack = 0; |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* number of page objects within the document |
212
|
|
|
*/ |
213
|
|
|
private $numPages=0; |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* object Id storage stack |
217
|
|
|
*/ |
218
|
|
|
private $stack=array(); |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* number of elements within the object Id storage stack |
222
|
|
|
*/ |
223
|
|
|
private $nStack=0; |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* an array which contains information about the objects which are not firmly attached to pages |
227
|
|
|
* these have been added with the addObject function |
228
|
|
|
*/ |
229
|
|
|
private $looseObjects=array(); |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* array contains infomation about how the loose objects are to be added to the document |
233
|
|
|
*/ |
234
|
|
|
private $addLooseObjects=array(); |
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* the objectId of the information object for the document |
238
|
|
|
* this contains authorship, title etc. |
239
|
|
|
*/ |
240
|
|
|
private $infoObject=0; |
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* number of images being tracked within the document |
244
|
|
|
*/ |
245
|
|
|
private $numImages=0; |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* some additional options while generation |
249
|
|
|
* currently used for compression only |
250
|
|
|
* Default: 'compression' => -1 which will set gzcompress to the default level of 6 |
251
|
|
|
*/ |
252
|
|
|
public $options=array('compression'=>-1); |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* the objectId of the first page of the document |
256
|
|
|
*/ |
257
|
|
|
private $firstPageId; |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* used to track the last used value of the inter-word spacing, this is so that it is known |
261
|
|
|
* when the spacing is changed. |
262
|
|
|
*/ |
263
|
|
|
private $wordSpaceAdjust=0; |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* track if the current font is bolded or italicised |
267
|
|
|
*/ |
268
|
|
|
private $currentTextState = ''; |
269
|
|
|
|
270
|
|
|
/** |
271
|
|
|
* messages are stored here during processing, these can be selected afterwards to give some useful debug information |
272
|
|
|
*/ |
273
|
|
|
public $messages=''; |
274
|
|
|
|
275
|
|
|
/** |
276
|
|
|
* the ancryption array for the document encryption is stored here |
277
|
|
|
*/ |
278
|
|
|
private $arc4=''; |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* the object Id of the encryption information |
282
|
|
|
*/ |
283
|
|
|
private $arc4_objnum=0; |
284
|
|
|
|
285
|
|
|
/** |
286
|
|
|
* the file identifier, used to uniquely identify a pdf document |
287
|
|
|
*/ |
288
|
|
|
public $fileIdentifier=''; |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* a flag to say if a document is to be encrypted or not |
292
|
|
|
* |
293
|
|
|
* @var boolean |
294
|
|
|
*/ |
295
|
|
|
private $encrypted=0; |
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* Set the encryption mode |
299
|
|
|
* 1 = RC40bit |
300
|
|
|
* 2 = RC128bit (since PDF Version 1.4) |
301
|
|
|
*/ |
302
|
|
|
private $encryptionMode = 1; |
303
|
|
|
/** |
304
|
|
|
* the encryption key for the encryption of all the document content (structure is not encrypted) |
305
|
|
|
* |
306
|
|
|
* @var string |
307
|
|
|
*/ |
308
|
|
|
private $encryptionKey=''; |
309
|
|
|
|
310
|
|
|
/* |
311
|
|
|
* encryption padding fetched from the Adobe PDF reference |
312
|
|
|
*/ |
313
|
|
|
private $encryptionPad; |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* array which forms a stack to keep track of nested callback functions |
317
|
|
|
* |
318
|
|
|
* @var array |
319
|
|
|
*/ |
320
|
|
|
private $callback = array(); |
321
|
|
|
|
322
|
|
|
/** |
323
|
|
|
* the number of callback functions in the callback array |
324
|
|
|
* |
325
|
|
|
* @var integer |
326
|
|
|
*/ |
327
|
|
|
private $nCallback = 0; |
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* store label->id pairs for named destinations, these will be used to replace internal links |
331
|
|
|
* done this way so that destinations can be defined after the location that links to them |
332
|
|
|
* |
333
|
|
|
* @var array |
334
|
|
|
*/ |
335
|
|
|
private $destinations = array(); |
336
|
|
|
|
337
|
|
|
/** |
338
|
|
|
* store the stack for the transaction commands, each item in here is a record of the values of all the |
339
|
|
|
* variables within the class, so that the user can rollback at will (from each 'start' command) |
340
|
|
|
* note that this includes the objects array, so these can be large. |
341
|
|
|
* |
342
|
|
|
* @var string |
343
|
|
|
*/ |
344
|
|
|
private $checkpoint = ''; |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* Constructor - starts a new document |
348
|
|
|
* |
349
|
|
|
* @param array $pageSize Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero. |
350
|
|
|
* |
351
|
|
|
* @return void |
|
|
|
|
352
|
|
|
*/ |
353
|
3 |
|
public function __construct($pageSize = array(0, 0, 612, 792), $isUnicode = false) |
354
|
|
|
{ |
355
|
3 |
|
$this->isUnicode = $isUnicode; |
356
|
|
|
// set the hardcoded encryption pad |
357
|
3 |
|
$this->encryptionPad = chr(0x28).chr(0xBF).chr(0x4E).chr(0x5E).chr(0x4E).chr(0x75).chr(0x8A).chr(0x41).chr(0x64).chr(0x00).chr(0x4E).chr(0x56).chr(0xFF).chr(0xFA).chr(0x01).chr(0x08).chr(0x2E).chr(0x2E).chr(0x00).chr(0xB6).chr(0xD0).chr(0x68).chr(0x3E).chr(0x80).chr(0x2F).chr(0x0C).chr(0xA9).chr(0xFE).chr(0x64).chr(0x53).chr(0x69).chr(0x7A); |
358
|
|
|
|
359
|
3 |
|
$this->newDocument($pageSize); |
360
|
|
|
|
361
|
3 |
|
if (in_array('Windows-1252', mb_list_encodings())) { |
362
|
3 |
|
$this->targetEncoding = 'Windows-1252'; |
363
|
3 |
|
} |
364
|
|
|
|
365
|
|
|
// font familys are already known in $this->fontFamilies |
366
|
3 |
|
$this->fileIdentifier = md5('ROSPDF'); |
367
|
3 |
|
} |
368
|
|
|
|
369
|
|
|
/** |
370
|
|
|
* Document object methods (internal use only) |
371
|
|
|
* |
372
|
|
|
* There is about one object method for each type of object in the pdf document |
373
|
|
|
* Each function has the same call list ($id,$action,$options). |
374
|
|
|
* $id = the object ID of the object, or what it is to be if it is being created |
375
|
|
|
* $action = a string specifying the action to be performed, though ALL must support: |
376
|
|
|
* 'new' - create the object with the id $id |
377
|
|
|
* 'out' - produce the output for the pdf object |
378
|
|
|
* $options = optional, a string or array containing the various parameters for the object |
379
|
|
|
* |
380
|
|
|
* These, in conjunction with the output function are the ONLY way for output to be produced |
381
|
|
|
* within the pdf 'file'. |
382
|
|
|
*/ |
383
|
|
|
|
384
|
|
|
/** |
385
|
|
|
* destination object, used to specify the location for the user to jump to, presently on opening |
386
|
|
|
* @access private |
387
|
|
|
*/ |
388
|
|
|
private function o_destination($id, $action, $options = '') |
389
|
|
|
{ |
390
|
|
|
if ($action!='new') { |
391
|
|
|
$o =& $this->objects[$id]; |
392
|
|
|
} |
393
|
|
|
switch ($action) { |
394
|
|
|
case 'new': |
395
|
|
|
$this->objects[$id]=array('t'=>'destination','info'=>array()); |
396
|
|
|
$tmp = ''; |
397
|
|
|
switch ($options['type']) { |
398
|
|
|
case 'XYZ': |
399
|
|
|
case 'FitR': |
400
|
|
|
$tmp = ' '.$options['p3'].$tmp; |
401
|
|
|
case 'FitH': |
402
|
|
|
case 'FitV': |
403
|
|
|
case 'FitBH': |
404
|
|
|
case 'FitBV': |
405
|
|
|
$tmp = ' '.$options['p1'].' '.$options['p2'].$tmp; |
406
|
|
|
case 'Fit': |
407
|
|
|
case 'FitB': |
408
|
|
|
$tmp = $options['type'].$tmp; |
409
|
|
|
$this->objects[$id]['info']['string']=$tmp; |
410
|
|
|
$this->objects[$id]['info']['page']=$options['page']; |
411
|
|
|
} |
412
|
|
|
break; |
413
|
|
|
case 'out': |
414
|
|
|
$tmp = $o['info']; |
|
|
|
|
415
|
|
|
$res="\n".$id." 0 obj\n".'['.$tmp['page'].' 0 R /'.$tmp['string']."]\nendobj"; |
416
|
|
|
return $res; |
417
|
|
|
break; |
|
|
|
|
418
|
|
|
} |
419
|
|
|
} |
420
|
|
|
|
421
|
|
|
/** |
422
|
|
|
* sets the viewer preferences |
423
|
|
|
* @access private |
424
|
|
|
*/ |
425
|
|
|
private function o_viewerPreferences($id, $action, $options = '') |
426
|
|
|
{ |
427
|
|
|
if ($action!='new') { |
428
|
|
|
$o =& $this->objects[$id]; |
429
|
|
|
} |
430
|
|
|
switch ($action) { |
431
|
|
|
case 'new': |
432
|
|
|
$this->objects[$id]=array('t'=>'viewerPreferences','info'=>array()); |
433
|
|
|
break; |
434
|
|
|
case 'add': |
435
|
|
|
foreach ($options as $k => $v) { |
|
|
|
|
436
|
|
|
switch ($k) { |
437
|
|
|
case 'HideToolbar': |
438
|
|
|
case 'HideMenubar': |
439
|
|
|
case 'HideWindowUI': |
440
|
|
|
case 'FitWindow': |
441
|
|
|
case 'CenterWindow': |
442
|
|
|
case 'DisplayDocTitle': |
443
|
|
|
case 'NonFullScreenPageMode': |
444
|
|
|
case 'Direction': |
445
|
|
|
$o['info'][$k]=$v; |
|
|
|
|
446
|
|
|
break; |
447
|
|
|
} |
448
|
|
|
} |
449
|
|
|
break; |
450
|
|
|
case 'out': |
451
|
|
|
$res="\n".$id." 0 obj\n".'<< '; |
452
|
|
|
foreach ($o['info'] as $k => $v) { |
453
|
|
|
$res.="\n/".$k.' '.$v; |
454
|
|
|
} |
455
|
|
|
$res.="\n>>\n"; |
456
|
|
|
return $res; |
457
|
|
|
break; |
|
|
|
|
458
|
|
|
} |
459
|
|
|
} |
460
|
|
|
|
461
|
|
|
/** |
462
|
|
|
* define the document catalog, the overall controller for the document |
463
|
|
|
* @access private |
464
|
|
|
*/ |
465
|
3 |
|
private function o_catalog($id, $action, $options = '') |
466
|
|
|
{ |
467
|
3 |
|
if ($action!='new') { |
468
|
3 |
|
$o =& $this->objects[$id]; |
469
|
3 |
|
} |
470
|
|
|
switch ($action) { |
471
|
3 |
|
case 'new': |
472
|
3 |
|
$this->objects[$id]=array('t'=>'catalog','info'=>array()); |
473
|
3 |
|
$this->catalogId=$id; |
474
|
3 |
|
break; |
475
|
3 |
|
case 'outlines': |
476
|
3 |
|
case 'pages': |
477
|
3 |
|
case 'openHere': |
478
|
3 |
|
$o['info'][$action]=$options; |
|
|
|
|
479
|
3 |
|
break; |
480
|
|
|
case 'viewerPreferences': |
481
|
|
|
if (!isset($o['info']['viewerPreferences'])) { |
482
|
|
|
$this->numObj++; |
483
|
|
|
$this->o_viewerPreferences($this->numObj, 'new'); |
484
|
|
|
$o['info']['viewerPreferences']=$this->numObj; |
485
|
|
|
} |
486
|
|
|
$vp = $o['info']['viewerPreferences']; |
487
|
|
|
$this->o_viewerPreferences($vp, 'add', $options); |
488
|
|
|
break; |
489
|
|
|
case 'out': |
490
|
|
|
$res="\n".$id." 0 obj\n".'<< /Type /Catalog'; |
491
|
|
|
foreach ($o['info'] as $k => $v) { |
492
|
|
|
switch ($k) { |
493
|
|
|
case 'outlines': |
494
|
|
|
$res.=' /Outlines '.$v.' 0 R'; |
495
|
|
|
break; |
496
|
|
|
case 'pages': |
497
|
|
|
$res.=' /Pages '.$v.' 0 R'; |
498
|
|
|
break; |
499
|
|
|
case 'viewerPreferences': |
500
|
|
|
$res.=' /ViewerPreferences '.$o['info']['viewerPreferences'].' 0 R'; |
501
|
|
|
break; |
502
|
|
|
case 'openHere': |
503
|
|
|
$res.=' /OpenAction '.$o['info']['openHere'].' 0 R'; |
504
|
|
|
break; |
505
|
|
|
} |
506
|
|
|
} |
507
|
|
|
$res.=" >>\nendobj"; |
508
|
|
|
return $res; |
509
|
|
|
break; |
|
|
|
|
510
|
|
|
} |
511
|
3 |
|
} |
512
|
|
|
|
513
|
|
|
/** |
514
|
|
|
* object which is a parent to the pages in the document |
515
|
|
|
* @access private |
516
|
|
|
*/ |
517
|
3 |
|
private function o_pages($id, $action, $options = '') |
518
|
|
|
{ |
519
|
3 |
|
if ($action!='new') { |
520
|
3 |
|
$o =& $this->objects[$id]; |
521
|
3 |
|
} |
522
|
|
|
switch ($action) { |
523
|
3 |
View Code Duplication |
case 'new': |
524
|
3 |
|
$this->objects[$id]=array('t'=>'pages','info'=>array()); |
525
|
3 |
|
$this->o_catalog($this->catalogId, 'pages', $id); |
526
|
3 |
|
break; |
527
|
3 |
|
case 'page': |
528
|
3 |
|
if (!is_array($options)) { |
529
|
|
|
// then it will just be the id of the new page |
530
|
3 |
|
$o['info']['pages'][]=$options; |
|
|
|
|
531
|
3 |
|
} else { |
532
|
|
|
// then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative |
533
|
|
|
// and pos is either 'before' or 'after', saying where this page will fit. |
534
|
|
|
if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) { |
535
|
|
|
$i = array_search($options['rid'], $o['info']['pages']); |
536
|
|
|
if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i]==$options['rid']) { |
537
|
|
|
// then there is a match make a space |
538
|
|
|
switch ($options['pos']) { |
539
|
|
|
case 'before': |
540
|
|
|
$k = $i; |
541
|
|
|
break; |
542
|
|
|
case 'after': |
543
|
|
|
$k=$i+1; |
544
|
|
|
break; |
545
|
|
|
default: |
546
|
|
|
$k=-1; |
547
|
|
|
break; |
548
|
|
|
} |
549
|
|
|
if ($k>=0) { |
550
|
|
|
for ($j=count($o['info']['pages'])-1; $j>=$k; $j--) { |
551
|
|
|
$o['info']['pages'][$j+1]=$o['info']['pages'][$j]; |
552
|
|
|
} |
553
|
|
|
$o['info']['pages'][$k]=$options['id']; |
554
|
|
|
} |
555
|
|
|
} |
556
|
|
|
} |
557
|
|
|
} |
558
|
3 |
|
break; |
559
|
3 |
|
case 'procset': |
560
|
3 |
|
$o['info']['procset']=$options; |
561
|
3 |
|
break; |
562
|
3 |
|
case 'mediaBox': |
563
|
3 |
|
$o['info']['mediaBox']=$options; // which should be an array of 4 numbers |
564
|
3 |
|
break; |
565
|
3 |
View Code Duplication |
case 'font': |
566
|
3 |
|
$o['info']['fonts'][]=array('objNum'=>$options['objNum'],'fontNum'=>$options['fontNum']); |
567
|
3 |
|
break; |
568
|
|
View Code Duplication |
case 'xObject': |
569
|
|
|
$o['info']['xObjects'][]=array('objNum'=>$options['objNum'],'label'=>$options['label']); |
570
|
|
|
break; |
571
|
|
|
case 'out': |
572
|
|
|
if (count($o['info']['pages'])) { |
573
|
|
|
$res="\n".$id." 0 obj\n<< /Type /Pages /Kids ["; |
574
|
|
|
foreach ($o['info']['pages'] as $k => $v) { |
575
|
|
|
$res.=$v." 0 R "; |
576
|
|
|
} |
577
|
|
|
$res.="] /Count ".count($this->objects[$id]['info']['pages']); |
578
|
|
|
if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) || isset($o['info']['procset'])) { |
579
|
|
|
$res.=" /Resources <<"; |
580
|
|
|
if (isset($o['info']['procset'])) { |
581
|
|
|
$res.=" /ProcSet ".$o['info']['procset']; |
582
|
|
|
} |
583
|
|
View Code Duplication |
if (isset($o['info']['fonts']) && count($o['info']['fonts'])) { |
584
|
|
|
$res.=" /Font << "; |
585
|
|
|
foreach ($o['info']['fonts'] as $finfo) { |
586
|
|
|
$res.=" /F".$finfo['fontNum']." ".$finfo['objNum']." 0 R"; |
587
|
|
|
} |
588
|
|
|
$res.=" >>"; |
589
|
|
|
} |
590
|
|
View Code Duplication |
if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) { |
591
|
|
|
$res.=" /XObject << "; |
592
|
|
|
foreach ($o['info']['xObjects'] as $finfo) { |
593
|
|
|
$res.=" /".$finfo['label']." ".$finfo['objNum']." 0 R"; |
594
|
|
|
} |
595
|
|
|
$res.=" >>"; |
596
|
|
|
} |
597
|
|
|
$res.=" >>"; |
598
|
|
|
if (isset($o['info']['mediaBox'])) { |
599
|
|
|
$tmp=$o['info']['mediaBox']; |
600
|
|
|
$res.=" /MediaBox [".sprintf('%.3F', $tmp[0]).' '.sprintf('%.3F', $tmp[1]).' '.sprintf('%.3F', $tmp[2]).' '.sprintf('%.3F', $tmp[3]).']'; |
601
|
|
|
} |
602
|
|
|
} |
603
|
|
|
$res.=" >>\nendobj"; |
604
|
|
|
} else { |
605
|
|
|
$res="\n".$id." 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj"; |
606
|
|
|
} |
607
|
|
|
return $res; |
608
|
|
|
break; |
|
|
|
|
609
|
|
|
} |
610
|
3 |
|
} |
611
|
|
|
|
612
|
|
|
/** |
613
|
|
|
* Beta Redirection function |
614
|
|
|
* @access private |
615
|
|
|
*/ |
616
|
|
|
private function o_redirect($id, $action, $options = '') |
617
|
|
|
{ |
618
|
|
|
switch ($action) { |
619
|
|
|
case 'new': |
620
|
|
|
$this->objects[$id]=array('t'=>'redirect','data'=>$options['data'],'info'=>array()); |
621
|
|
|
$this->o_pages($this->currentNode, 'xObject', array('label'=>$options['label'],'objNum'=>$id)); |
|
|
|
|
622
|
|
|
break; |
623
|
|
|
case 'out': |
624
|
|
|
$o =& $this->objects[$id]; |
625
|
|
|
$tmp=$o['data']; |
|
|
|
|
626
|
|
|
$res= "\n".$id." 0 obj\n<<"; |
627
|
|
|
$res.="/R".$o['data']." ".$o['data']." 0 R>>\nendobj"; |
628
|
|
|
return $res; |
629
|
|
|
break; |
|
|
|
|
630
|
|
|
} |
631
|
|
|
} |
632
|
|
|
|
633
|
|
|
/** |
634
|
|
|
* defines the outlines in the doc, empty for now |
635
|
|
|
* @access private |
636
|
|
|
*/ |
637
|
3 |
|
private function o_outlines($id, $action, $options = '') |
638
|
|
|
{ |
639
|
3 |
|
if ($action!='new') { |
640
|
|
|
$o =& $this->objects[$id]; |
641
|
|
|
} |
642
|
|
|
switch ($action) { |
643
|
3 |
View Code Duplication |
case 'new': |
644
|
3 |
|
$this->objects[$id]=array('t'=>'outlines','info'=>array('outlines'=>array())); |
645
|
3 |
|
$this->o_catalog($this->catalogId, 'outlines', $id); |
646
|
3 |
|
break; |
647
|
|
|
case 'outline': |
648
|
|
|
$o['info']['outlines'][]=$options; |
|
|
|
|
649
|
|
|
break; |
650
|
|
|
case 'out': |
651
|
|
|
if (count($o['info']['outlines'])) { |
652
|
|
|
$res="\n".$id." 0 obj\n<< /Type /Outlines /Kids ["; |
653
|
|
|
foreach ($o['info']['outlines'] as $k => $v) { |
654
|
|
|
$res.=$v." 0 R "; |
655
|
|
|
} |
656
|
|
|
$res.="] /Count ".count($o['info']['outlines'])." >>\nendobj"; |
657
|
|
|
} else { |
658
|
|
|
$res="\n".$id." 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj"; |
659
|
|
|
} |
660
|
|
|
return $res; |
661
|
|
|
break; |
|
|
|
|
662
|
|
|
} |
663
|
3 |
|
} |
664
|
|
|
|
665
|
|
|
/** |
666
|
|
|
* an object to hold the font description |
667
|
|
|
* @access private |
668
|
|
|
*/ |
669
|
3 |
|
private function o_font($id, $action, $options = '') |
670
|
|
|
{ |
671
|
3 |
|
if ($action!='new') { |
672
|
|
|
$o =& $this->objects[$id]; |
673
|
|
|
} |
674
|
|
|
switch ($action) { |
675
|
3 |
|
case 'new': |
676
|
3 |
|
$this->objects[$id]=array('t'=>'font','info'=>array('name'=>$options['name'], 'fontFileName' => $options['fontFileName'],'SubType'=>'Type1')); |
677
|
3 |
|
$fontNum=$this->numFonts; |
678
|
3 |
|
$this->objects[$id]['info']['fontNum']=$fontNum; |
679
|
|
|
// deal with the encoding and the differences |
680
|
3 |
|
if (isset($options['differences'])) { |
681
|
|
|
// then we'll need an encoding dictionary |
682
|
3 |
|
$this->numObj++; |
683
|
3 |
|
$this->o_fontEncoding($this->numObj, 'new', $options); |
684
|
3 |
|
$this->objects[$id]['info']['encodingDictionary']=$this->numObj; |
685
|
3 |
|
} elseif (isset($options['encoding'])) { |
686
|
|
|
// we can specify encoding here |
687
|
|
|
switch ($options['encoding']) { |
688
|
|
|
case 'WinAnsiEncoding': |
689
|
|
|
case 'MacRomanEncoding': |
690
|
|
|
case 'MacExpertEncoding': |
691
|
|
|
$this->objects[$id]['info']['encoding']=$options['encoding']; |
692
|
|
|
break; |
693
|
|
|
case 'none': |
694
|
|
|
break; |
695
|
|
|
default: |
696
|
|
|
$this->objects[$id]['info']['encoding']='WinAnsiEncoding'; |
697
|
|
|
break; |
698
|
|
|
} |
699
|
|
|
} else { |
700
|
|
|
$this->objects[$id]['info']['encoding']='WinAnsiEncoding'; |
701
|
|
|
} |
702
|
|
|
|
703
|
3 |
|
if ($this->fonts[$options['fontFileName']]['isUnicode']) { |
704
|
|
|
// For Unicode fonts, we need to incorporate font data into |
705
|
|
|
// sub-sections that are linked from the primary font section. |
706
|
|
|
// Look at o_fontGIDtoCID and o_fontDescendentCID functions |
707
|
|
|
// for more informaiton. |
708
|
|
|
// |
709
|
|
|
// All of this code is adapted from the excellent changes made to |
710
|
|
|
// transform FPDF to TCPDF (http://tcpdf.sourceforge.net/) |
711
|
|
|
$toUnicodeId = ++$this->numObj; |
712
|
|
|
$this->o_contents($toUnicodeId, 'new', 'raw'); |
713
|
|
|
$this->objects[$id]['info']['toUnicode'] = $toUnicodeId; |
714
|
|
|
|
715
|
|
|
$stream = "/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo <</Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n1 begincodespacerange\n<0000> <FFFF>\nendcodespacerange\n1 beginbfrange\n<0000> <FFFF> <0000>\nendbfrange\nendcmap\nCMapName currentdict /CMap defineresource pop\nend\nend\n"; |
716
|
|
|
|
717
|
|
|
$res = "<</Length " . mb_strlen($stream, '8bit') . " >>\n"; |
718
|
|
|
$res .= "stream\n" . $stream . "\nendstream"; |
719
|
|
|
|
720
|
|
|
$this->objects[$toUnicodeId]['c'] = $res; |
721
|
|
|
|
722
|
|
|
$cidFontId = ++$this->numObj; |
723
|
|
|
$this->o_fontDescendentCID($cidFontId, 'new', $options); |
724
|
|
|
$this->objects[$id]['info']['cidFont'] = $cidFontId; |
725
|
|
|
} |
726
|
|
|
// also tell the pages node about the new font |
727
|
3 |
|
$this->o_pages($this->currentNode, 'font', array('fontNum'=>$fontNum,'objNum'=>$id)); |
|
|
|
|
728
|
3 |
|
break; |
729
|
|
|
case 'add': |
730
|
|
|
foreach ($options as $k => $v) { |
|
|
|
|
731
|
|
|
switch ($k) { |
732
|
|
|
case 'BaseFont': |
733
|
|
|
$o['info']['name'] = $v; |
|
|
|
|
734
|
|
|
break; |
735
|
|
|
case 'FirstChar': |
736
|
|
|
case 'LastChar': |
737
|
|
|
case 'Widths': |
738
|
|
|
case 'FontDescriptor': |
739
|
|
|
case 'SubType': |
740
|
|
|
$this->debug('o_font '.$k." : ".$v, E_USER_NOTICE); |
741
|
|
|
$o['info'][$k] = $v; |
742
|
|
|
break; |
743
|
|
|
} |
744
|
|
|
} |
745
|
|
|
|
746
|
|
|
// pass values down to descendent font |
747
|
|
|
if (isset($o['info']['cidFont'])) { |
748
|
|
|
$this->o_fontDescendentCID($o['info']['cidFont'], 'add', $options); |
749
|
|
|
} |
750
|
|
|
break; |
751
|
|
|
case 'out': |
752
|
|
|
// when font program is embedded and its not a coreFont, attach the font either as subset or completely |
753
|
|
|
if ($this->embedFont && !in_array(strtolower($o['info']['name']), $this->coreFonts)) { |
754
|
|
|
// when TrueType font is used |
755
|
|
|
if (isset($this->objects[$o['info']['FontDescriptor']]['info']['FontFile2'])) { |
756
|
|
|
// find font program id for TTF fonts (FontFile2) |
757
|
|
|
$pfbid = $this->objects[$o['info']['FontDescriptor']]['info']['FontFile2']; |
758
|
|
|
// if subsetting is set |
759
|
|
|
if ($this->fonts[$o['info']['fontFileName']]['isSubset'] && !empty($this->fonts[$o['info']['fontFileName']]['subset'])) { |
760
|
|
|
$this->debug('subset font for ' . $o['info']['fontFileName'], E_USER_NOTICE); |
761
|
|
|
$subsetFontName = "AAAAAD+" . $o['info']['name']; |
762
|
|
|
$o['info']['name'] = $subsetFontName; |
763
|
|
|
// find descendant font |
764
|
|
|
$this->objects[$o['info']['cidFont']]['info']['name'] = $subsetFontName; |
765
|
|
|
// find font descriptor |
766
|
|
|
$this->objects[$o['info']['FontDescriptor']]['info']['FontName'] = $subsetFontName; |
767
|
|
|
|
768
|
|
|
// use TTF subset script from http://www.4real.gr/technical-documents-ttf-subset.html |
769
|
|
|
$t = new TTFsubset(); |
770
|
|
|
// combine all used characters as string |
771
|
|
|
$s = implode('', array_keys($this->fonts[$o['info']['fontFileName']]['subset'])); |
772
|
|
|
// submit the string to TTFsubset class to return the subset (as binary) |
773
|
|
|
$data = $t->doSubset($o['info']['fontFileName'] . '.ttf', $s, null); |
774
|
|
|
// $data is the new (subset) of the font font |
775
|
|
|
//file_put_contents($o['info']['name'] . '.ttf', $data); |
|
|
|
|
776
|
|
|
|
777
|
|
|
$newcidwidth = array(); |
778
|
|
|
$cidwidth = &$this->fonts[$o['info']['fontFileName']]['CIDWidths']; |
779
|
|
|
foreach ($t->TTFchars as $TTFchar) { |
780
|
|
|
if (!empty($TTFchar->charCode) && isset($cidwidth[$TTFchar->charCode])) { |
781
|
|
|
$newcidwidth[$TTFchar->charCode] = $cidwidth[$TTFchar->charCode]; |
782
|
|
|
} |
783
|
|
|
} |
784
|
|
|
$cidwidth = $newcidwidth; |
785
|
|
|
} else { |
786
|
|
|
$data = file_get_contents($o['info']['fontFileName']. '.ttf'); |
787
|
|
|
} |
788
|
|
|
|
789
|
|
|
// TODO: cache the subset |
790
|
|
|
|
791
|
|
|
$l1 = strlen($data); |
792
|
|
|
$this->objects[$pfbid]['c'].= $data; |
793
|
|
|
$this->o_contents($pfbid, 'add', array('Length1'=>$l1)); |
|
|
|
|
794
|
|
|
} elseif (isset($this->objects[$o['info']['FontDescriptor']]['info']['FontFile'])) { |
795
|
|
|
// find FontFile id - used for PFB fonts |
796
|
|
|
$pfbid = $this->objects[$o['info']['FontDescriptor']]['info']['FontFile']; |
797
|
|
|
$data = file_get_contents($o['info']['fontFileName']. '.pfb'); |
798
|
|
|
$l1 = strpos($data, 'eexec')+6; |
799
|
|
|
$l2 = strpos($data, '00000000')-$l1; |
800
|
|
|
$l3 = strlen($data)-$l2-$l1; |
801
|
|
|
$this->o_contents($pfbid, 'add', array('Length1'=>$l1,'Length2'=>$l2,'Length3'=>$l3)); |
|
|
|
|
802
|
|
|
} else { |
803
|
|
|
$this->debug("Failed to select the correct font program", E_USER_WARNING); |
804
|
|
|
} |
805
|
|
|
} |
806
|
|
|
|
807
|
|
|
if ($this->fonts[$o['info']['fontFileName']]['isUnicode']) { |
808
|
|
|
// For Unicode fonts, we need to incorporate font data into |
809
|
|
|
// sub-sections that are linked from the primary font section. |
810
|
|
|
// Look at o_fontGIDtoCID and o_fontDescendentCID functions |
811
|
|
|
// for more informaiton. |
812
|
|
|
// |
813
|
|
|
// All of this code is adapted from the excellent changes made to |
814
|
|
|
// transform FPDF to TCPDF (http://tcpdf.sourceforge.net/) |
815
|
|
|
|
816
|
|
|
$res = "\n$id 0 obj\n<</Type /Font /Subtype /Type0 /BaseFont /".$o['info']['name'].""; |
817
|
|
|
// The horizontal identity mapping for 2-byte CIDs; may be used |
818
|
|
|
// with CIDFonts using any Registry, Ordering, and Supplement values. |
819
|
|
|
$res.= " /Encoding /Identity-H /DescendantFonts [".$o['info']['cidFont']." 0 R] /ToUnicode ".$o['info']['toUnicode']." 0 R >>\n"; |
820
|
|
|
$res.= "endobj"; |
821
|
|
|
} else { |
822
|
|
|
$res="\n".$id." 0 obj\n<< /Type /Font /Subtype /".$o['info']['SubType']." "; |
823
|
|
|
$res.="/Name /F".$o['info']['fontNum']." "; |
824
|
|
|
$res.="/BaseFont /".$o['info']['name']." "; |
825
|
|
|
if (isset($o['info']['encodingDictionary'])) { |
826
|
|
|
// then place a reference to the dictionary |
827
|
|
|
$res.="/Encoding ".$o['info']['encodingDictionary']." 0 R "; |
828
|
|
View Code Duplication |
} elseif (isset($o['info']['encoding'])) { |
829
|
|
|
// use the specified encoding |
830
|
|
|
$res.="/Encoding /".$o['info']['encoding']." "; |
831
|
|
|
} |
832
|
|
|
if (isset($o['info']['FirstChar'])) { |
833
|
|
|
$res.="/FirstChar ".$o['info']['FirstChar']." "; |
834
|
|
|
} |
835
|
|
|
if (isset($o['info']['LastChar'])) { |
836
|
|
|
$res.="/LastChar ".$o['info']['LastChar']." "; |
837
|
|
|
} |
838
|
|
|
if (isset($o['info']['Widths'])) { |
839
|
|
|
$res.="/Widths ".$o['info']['Widths']." 0 R "; |
840
|
|
|
} |
841
|
|
View Code Duplication |
if (isset($o['info']['FontDescriptor'])) { |
842
|
|
|
$res.="/FontDescriptor ".$o['info']['FontDescriptor']." 0 R "; |
843
|
|
|
} |
844
|
|
|
$res.=">>\nendobj"; |
845
|
|
|
} |
846
|
|
|
return $res; |
847
|
|
|
break; |
|
|
|
|
848
|
|
|
} |
849
|
3 |
|
} |
850
|
|
|
|
851
|
|
|
/** |
852
|
|
|
* a font descriptor, needed for including additional fonts |
853
|
|
|
* @access private |
854
|
|
|
*/ |
855
|
|
|
private function o_fontDescriptor($id, $action, $options = '') |
856
|
|
|
{ |
857
|
|
|
if ($action!='new') { |
858
|
|
|
$o =& $this->objects[$id]; |
859
|
|
|
} |
860
|
|
|
switch ($action) { |
861
|
|
|
case 'new': |
862
|
|
|
$this->objects[$id]=array('t'=>'fontDescriptor','info'=>$options); |
863
|
|
|
break; |
864
|
|
|
case 'out': |
865
|
|
|
$res="\n".$id." 0 obj\n<< /Type /FontDescriptor "; |
866
|
|
|
foreach ($o['info'] as $label => $value) { |
|
|
|
|
867
|
|
|
switch ($label) { |
868
|
|
|
case 'Ascent': |
869
|
|
|
case 'CapHeight': |
870
|
|
|
case 'Descent': |
871
|
|
|
case 'Flags': |
872
|
|
|
case 'ItalicAngle': |
873
|
|
|
case 'StemV': |
874
|
|
|
case 'AvgWidth': |
875
|
|
|
case 'Leading': |
876
|
|
|
case 'MaxWidth': |
877
|
|
|
case 'MissingWidth': |
878
|
|
|
case 'StemH': |
879
|
|
|
case 'XHeight': |
880
|
|
|
case 'CharSet': |
881
|
|
|
if (strlen($value)) { |
882
|
|
|
$res.='/'.$label.' '.$value." "; |
883
|
|
|
} |
884
|
|
|
break; |
885
|
|
|
case 'FontFile': |
886
|
|
|
case 'FontFile2': |
887
|
|
|
case 'FontFile3': |
888
|
|
|
$res.='/'.$label.' '.$value." 0 R "; |
889
|
|
|
break; |
890
|
|
|
case 'FontBBox': |
891
|
|
|
$res.='/'.$label.' ['.$value[0].' '.$value[1].' '.$value[2].' '.$value[3]."] "; |
892
|
|
|
break; |
893
|
|
|
case 'FontName': |
894
|
|
|
$res.='/'.$label.' /'.$value." "; |
895
|
|
|
break; |
896
|
|
|
} |
897
|
|
|
} |
898
|
|
|
$res.=">>\nendobj"; |
899
|
|
|
return $res; |
900
|
|
|
break; |
|
|
|
|
901
|
|
|
} |
902
|
|
|
} |
903
|
|
|
|
904
|
|
|
/** |
905
|
|
|
* the font encoding |
906
|
|
|
* @access private |
907
|
|
|
*/ |
908
|
3 |
|
private function o_fontEncoding($id, $action, $options = '') |
909
|
|
|
{ |
910
|
3 |
|
if ($action!='new') { |
911
|
|
|
$o =& $this->objects[$id]; |
912
|
|
|
} |
913
|
|
|
switch ($action) { |
914
|
3 |
|
case 'new': |
915
|
|
|
// the options array should contain 'differences' and maybe 'encoding' |
916
|
3 |
|
$this->objects[$id]=array('t'=>'fontEncoding','info'=>$options); |
917
|
3 |
|
break; |
918
|
|
|
case 'out': |
919
|
|
|
$res="\n".$id." 0 obj\n<< /Type /Encoding "; |
920
|
|
|
if (!isset($o['info']['encoding'])) { |
921
|
|
|
$o['info']['encoding']='WinAnsiEncoding'; |
|
|
|
|
922
|
|
|
} |
923
|
|
View Code Duplication |
if ($o['info']['encoding']!='none') { |
924
|
|
|
$res.="/BaseEncoding /".$o['info']['encoding']." "; |
925
|
|
|
} |
926
|
|
|
$res.="/Differences ["; |
927
|
|
|
$onum=-100; |
928
|
|
|
foreach ($o['info']['differences'] as $num => $label) { |
929
|
|
|
if ($num!=$onum+1) { |
930
|
|
|
// we cannot make use of consecutive numbering |
931
|
|
|
$res.= " ".$num." /".$label; |
932
|
|
|
} else { |
933
|
|
|
$res.= " /".$label; |
934
|
|
|
} |
935
|
|
|
$onum=$num; |
936
|
|
|
} |
937
|
|
|
$res.="] >>\nendobj"; |
938
|
|
|
return $res; |
939
|
|
|
break; |
|
|
|
|
940
|
|
|
} |
941
|
3 |
|
} |
942
|
|
|
|
943
|
|
|
/** |
944
|
|
|
* a descendent cid font, needed for unicode fonts |
945
|
|
|
* @access private |
946
|
|
|
*/ |
947
|
|
|
private function o_fontDescendentCID($id, $action, $options = '') |
948
|
|
|
{ |
949
|
|
|
if ($action !== 'new') { |
950
|
|
|
$o = & $this->objects[$id]; |
951
|
|
|
} |
952
|
|
|
|
953
|
|
|
switch ($action) { |
954
|
|
|
case 'new': |
955
|
|
|
$this->objects[$id] = array('t' => 'fontDescendentCID', 'info' => $options); |
956
|
|
|
// and a CID to GID map |
957
|
|
|
if ($this->embedFont) { |
958
|
|
|
$cidToGidMapId = ++$this->numObj; |
959
|
|
|
$this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options); |
960
|
|
|
$this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId; |
961
|
|
|
} |
962
|
|
|
break; |
963
|
|
|
|
964
|
|
|
case 'add': |
965
|
|
|
foreach ($options as $k => $v) { |
|
|
|
|
966
|
|
|
switch ($k) { |
967
|
|
|
case 'BaseFont': |
968
|
|
|
$o['info']['name'] = $v; |
|
|
|
|
969
|
|
|
break; |
970
|
|
|
|
971
|
|
|
case 'FirstChar': |
972
|
|
|
case 'LastChar': |
973
|
|
|
case 'MissingWidth': |
974
|
|
|
case 'FontDescriptor': |
975
|
|
|
case 'SubType': |
976
|
|
|
$this->debug("o_fontDescendentCID $k : $v", E_USER_NOTICE); |
977
|
|
|
$o['info'][$k] = $v; |
978
|
|
|
break; |
979
|
|
|
} |
980
|
|
|
} |
981
|
|
|
|
982
|
|
|
// pass values down to cid to gid map |
983
|
|
|
if ($this->embedFont) { |
984
|
|
|
$this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options); |
985
|
|
|
} |
986
|
|
|
break; |
987
|
|
|
|
988
|
|
|
case 'out': |
989
|
|
|
$res = "\n$id 0 obj\n"; |
990
|
|
|
$res.= "<</Type /Font /Subtype /CIDFontType2 /BaseFont /".$o['info']['name']." /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >>"; |
991
|
|
View Code Duplication |
if (isset($o['info']['FontDescriptor'])) { |
992
|
|
|
$res.= " /FontDescriptor ".$o['info']['FontDescriptor']." 0 R"; |
993
|
|
|
} |
994
|
|
|
|
995
|
|
|
if (isset($o['info']['MissingWidth'])) { |
996
|
|
|
$res.= " /DW ".$o['info']['MissingWidth'].""; |
997
|
|
|
} |
998
|
|
|
|
999
|
|
|
if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) { |
1000
|
|
|
$cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths']; |
1001
|
|
|
$w = ''; |
1002
|
|
|
foreach ($cid_widths as $cid => $width) { |
1003
|
|
|
$w .= "$cid [$width] "; |
1004
|
|
|
} |
1005
|
|
|
$res.= " /W [$w]"; |
1006
|
|
|
} |
1007
|
|
|
|
1008
|
|
|
if ($this->embedFont) { |
1009
|
|
|
$res.= " /CIDToGIDMap ".$o['info']['cidToGidMap']." 0 R"; |
1010
|
|
|
} |
1011
|
|
|
$res.= " >>\n"; |
1012
|
|
|
$res.= "endobj"; |
1013
|
|
|
|
1014
|
|
|
return $res; |
1015
|
|
|
} |
1016
|
|
|
} |
1017
|
|
|
|
1018
|
|
|
/** |
1019
|
|
|
* a font glyph to character map, needed for unicode fonts |
1020
|
|
|
* @access private |
1021
|
|
|
*/ |
1022
|
|
|
private function o_fontGIDtoCIDMap($id, $action, $options = '') |
1023
|
|
|
{ |
1024
|
|
|
if ($action !== 'new') { |
1025
|
|
|
$o = & $this->objects[$id]; |
1026
|
|
|
} |
1027
|
|
|
|
1028
|
|
|
switch ($action) { |
1029
|
|
|
case 'new': |
1030
|
|
|
$this->objects[$id] = array('t' => 'fontGIDtoCIDMap', 'info' => $options); |
1031
|
|
|
break; |
1032
|
|
|
|
1033
|
|
|
case 'out': |
1034
|
|
|
$res = "\n$id 0 obj\n"; |
1035
|
|
|
$fontFileName = $o['info']['fontFileName']; |
|
|
|
|
1036
|
|
|
$tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']); |
1037
|
|
|
|
1038
|
|
|
if (isset($o['raw'])) { |
1039
|
|
|
$res.= $tmp; |
1040
|
|
|
} else { |
1041
|
|
|
$res.= "<<"; |
1042
|
|
View Code Duplication |
if (function_exists('gzcompress') && $this->options['compression']) { |
1043
|
|
|
// then implement ZLIB based compression on this content stream |
1044
|
|
|
$tmp = gzcompress($tmp, $this->options['compression']); |
1045
|
|
|
$res.= " /Filter /FlateDecode"; |
1046
|
|
|
} |
1047
|
|
|
|
1048
|
|
|
$res.= " /Length ".mb_strlen($tmp, '8bit') .">>\nstream\n$tmp\nendstream"; |
1049
|
|
|
} |
1050
|
|
|
|
1051
|
|
|
$res.= "\nendobj"; |
1052
|
|
|
return $res; |
1053
|
|
|
} |
1054
|
|
|
} |
1055
|
|
|
|
1056
|
|
|
/** |
1057
|
|
|
* define the document information |
1058
|
|
|
* @access private |
1059
|
|
|
*/ |
1060
|
3 |
|
private function o_info($id, $action, $options = '') |
1061
|
|
|
{ |
1062
|
3 |
|
if ($action!='new') { |
1063
|
|
|
$o =& $this->objects[$id]; |
1064
|
|
|
} |
1065
|
|
|
switch ($action) { |
1066
|
3 |
|
case 'new': |
1067
|
3 |
|
$this->infoObject=$id; |
1068
|
3 |
|
$date='D:'.date('Ymd'); |
1069
|
3 |
|
$this->objects[$id]=array('t'=>'info','info'=>array('Creator'=>'R and OS php pdf writer, http://www.ros.co.nz','CreationDate'=>$date)); |
1070
|
3 |
|
break; |
1071
|
|
|
case 'Title': |
1072
|
|
|
case 'Author': |
1073
|
|
|
case 'Subject': |
1074
|
|
|
case 'Keywords': |
1075
|
|
|
case 'Creator': |
1076
|
|
|
case 'Producer': |
1077
|
|
|
case 'CreationDate': |
1078
|
|
|
case 'ModDate': |
1079
|
|
|
case 'Trapped': |
1080
|
|
|
$o['info'][$action]=$options; |
|
|
|
|
1081
|
|
|
break; |
1082
|
|
|
case 'out': |
1083
|
|
|
if ($this->encrypted) { |
1084
|
|
|
$this->encryptInit($id); |
1085
|
|
|
} |
1086
|
|
|
$res="\n".$id." 0 obj\n<< "; |
1087
|
|
|
foreach ($o['info'] as $k => $v) { |
1088
|
|
|
$res.='/'.$k.' ('; |
1089
|
|
View Code Duplication |
if ($this->encrypted) { |
1090
|
|
|
$res.=$this->filterText($this->ARC4($v), true, false); |
1091
|
|
|
} else { |
1092
|
|
|
$res.=$this->filterText($v, true, false); |
1093
|
|
|
} |
1094
|
|
|
$res.=") "; |
1095
|
|
|
} |
1096
|
|
|
$res.=">>\nendobj"; |
1097
|
|
|
return $res; |
1098
|
|
|
break; |
|
|
|
|
1099
|
|
|
} |
1100
|
3 |
|
} |
1101
|
|
|
|
1102
|
|
|
/** |
1103
|
|
|
* an action object, used to link to URLS initially |
1104
|
|
|
* @access private |
1105
|
|
|
*/ |
1106
|
|
|
private function o_action($id, $action, $options = '') |
1107
|
|
|
{ |
1108
|
|
|
if ($action!='new') { |
1109
|
|
|
$o =& $this->objects[$id]; |
1110
|
|
|
} |
1111
|
|
|
switch ($action) { |
1112
|
|
|
case 'new': |
1113
|
|
|
if (is_array($options)) { |
1114
|
|
|
$this->objects[$id]=array('t'=>'action','info'=>$options,'type'=>$options['type']); |
1115
|
|
|
} else { |
1116
|
|
|
// then assume a URI action |
1117
|
|
|
$this->objects[$id]=array('t'=>'action','info'=>$options,'type'=>'URI'); |
1118
|
|
|
} |
1119
|
|
|
break; |
1120
|
|
|
case 'out': |
1121
|
|
|
if ($this->encrypted) { |
1122
|
|
|
$this->encryptInit($id); |
1123
|
|
|
} |
1124
|
|
|
$res="\n".$id." 0 obj\n<< /Type /Action"; |
1125
|
|
|
switch ($o['type']) { |
|
|
|
|
1126
|
|
|
case 'ilink': |
1127
|
|
|
// there will be an 'label' setting, this is the name of the destination |
1128
|
|
|
$res.=" /S /GoTo /D ".$this->destinations[(string)$o['info']['label']]." 0 R"; |
1129
|
|
|
break; |
1130
|
|
|
case 'URI': |
1131
|
|
|
$res.=" /S /URI /URI ("; |
1132
|
|
View Code Duplication |
if ($this->encrypted) { |
1133
|
|
|
$res.=$this->filterText($this->ARC4($o['info']), true, false); |
1134
|
|
|
} else { |
1135
|
|
|
$res.=$this->filterText($o['info'], true, false); |
1136
|
|
|
} |
1137
|
|
|
$res.=")"; |
1138
|
|
|
break; |
1139
|
|
|
} |
1140
|
|
|
$res.=" >>\nendobj"; |
1141
|
|
|
return $res; |
1142
|
|
|
break; |
|
|
|
|
1143
|
|
|
} |
1144
|
|
|
} |
1145
|
|
|
|
1146
|
|
|
/** |
1147
|
|
|
* an annotation object, this will add an annotation to the current page. |
1148
|
|
|
* initially will support just link annotations |
1149
|
|
|
* @access private |
1150
|
|
|
*/ |
1151
|
|
|
private function o_annotation($id, $action, $options = '') |
1152
|
|
|
{ |
1153
|
|
|
if ($action!='new') { |
1154
|
|
|
$o =& $this->objects[$id]; |
1155
|
|
|
} |
1156
|
|
|
switch ($action) { |
1157
|
|
|
case 'new': |
1158
|
|
|
// add the annotation to the current page |
1159
|
|
|
$pageId = $this->currentPage; |
1160
|
|
|
$this->o_page($pageId, 'annot', $id); |
1161
|
|
|
// and add the action object which is going to be required |
1162
|
|
|
switch ($options['type']) { |
1163
|
|
|
case 'link': |
1164
|
|
|
$this->objects[$id]=array('t'=>'annotation','info'=>$options); |
1165
|
|
|
$this->numObj++; |
1166
|
|
|
$this->o_action($this->numObj, 'new', $options['url']); |
1167
|
|
|
$this->objects[$id]['info']['actionId']=$this->numObj; |
1168
|
|
|
break; |
1169
|
|
|
case 'ilink': |
1170
|
|
|
// this is to a named internal link |
1171
|
|
|
$label = $options['label']; |
1172
|
|
|
$this->objects[$id]=array('t'=>'annotation','info'=>$options); |
1173
|
|
|
$this->numObj++; |
1174
|
|
|
$this->o_action($this->numObj, 'new', array('type'=>'ilink','label'=>$label)); |
|
|
|
|
1175
|
|
|
$this->objects[$id]['info']['actionId']=$this->numObj; |
1176
|
|
|
break; |
1177
|
|
|
} |
1178
|
|
|
break; |
1179
|
|
|
case 'out': |
1180
|
|
|
$res="\n".$id." 0 obj << /Type /Annot"; |
1181
|
|
|
switch ($o['info']['type']) { |
|
|
|
|
1182
|
|
|
case 'link': |
1183
|
|
|
case 'ilink': |
1184
|
|
|
$res.= " /Subtype /Link"; |
1185
|
|
|
break; |
1186
|
|
|
} |
1187
|
|
|
$res.=" /A ".$o['info']['actionId']." 0 R"; |
1188
|
|
|
$res.=" /Border [0 0 0]"; |
1189
|
|
|
$res.=" /H /I"; |
1190
|
|
|
$res.=" /Rect [ "; |
1191
|
|
|
foreach ($o['info']['rect'] as $v) { |
1192
|
|
|
$res.= sprintf("%.4f ", $v); |
1193
|
|
|
} |
1194
|
|
|
$res.="]"; |
1195
|
|
|
$res.=" >>\nendobj"; |
1196
|
|
|
return $res; |
1197
|
|
|
break; |
|
|
|
|
1198
|
|
|
} |
1199
|
|
|
} |
1200
|
|
|
|
1201
|
|
|
/** |
1202
|
|
|
* a page object, it also creates a contents object to hold its contents |
1203
|
|
|
* @access private |
1204
|
|
|
*/ |
1205
|
3 |
|
private function o_page($id, $action, $options = '') |
1206
|
|
|
{ |
1207
|
3 |
|
if ($action!='new') { |
1208
|
|
|
$o =& $this->objects[$id]; |
1209
|
|
|
} |
1210
|
|
|
switch ($action) { |
1211
|
3 |
|
case 'new': |
1212
|
3 |
|
$this->numPages++; |
1213
|
3 |
|
$this->objects[$id]=array('t'=>'page','info'=>array('parent'=>$this->currentNode,'pageNum'=>$this->numPages)); |
1214
|
3 |
|
if (is_array($options)) { |
1215
|
|
|
// then this must be a page insertion, array shoudl contain 'rid','pos'=[before|after] |
1216
|
|
|
$options['id']=$id; |
1217
|
|
|
$this->o_pages($this->currentNode, 'page', $options); |
|
|
|
|
1218
|
|
|
} else { |
1219
|
3 |
|
$this->o_pages($this->currentNode, 'page', $id); |
1220
|
|
|
} |
1221
|
3 |
|
$this->currentPage=$id; |
1222
|
|
|
// make a contents object to go with this page |
1223
|
3 |
|
$this->numObj++; |
1224
|
3 |
|
$this->o_contents($this->numObj, 'new', $id); |
1225
|
3 |
|
$this->currentContents=$this->numObj; |
1226
|
3 |
|
$this->objects[$id]['info']['contents']=array(); |
1227
|
3 |
|
$this->objects[$id]['info']['contents'][]=$this->numObj; |
1228
|
3 |
|
$match = ($this->numPages%2 ? 'odd' : 'even'); |
1229
|
3 |
|
foreach ($this->addLooseObjects as $oId => $target) { |
1230
|
|
|
if ($target=='all' || $match==$target) { |
1231
|
|
|
$this->objects[$id]['info']['contents'][]=$oId; |
1232
|
|
|
} |
1233
|
3 |
|
} |
1234
|
3 |
|
break; |
1235
|
|
|
case 'content': |
1236
|
|
|
$o['info']['contents'][]=$options; |
|
|
|
|
1237
|
|
|
break; |
1238
|
|
|
case 'annot': |
1239
|
|
|
// add an annotation to this page |
1240
|
|
|
if (!isset($o['info']['annot'])) { |
1241
|
|
|
$o['info']['annot']=array(); |
1242
|
|
|
} |
1243
|
|
|
// $options should contain the id of the annotation dictionary |
1244
|
|
|
$o['info']['annot'][]=$options; |
1245
|
|
|
break; |
1246
|
|
|
case 'out': |
1247
|
|
|
$res="\n".$id." 0 obj\n<< /Type /Page"; |
1248
|
|
|
$res.=" /Parent ".$o['info']['parent']." 0 R"; |
1249
|
|
|
if (isset($o['info']['annot'])) { |
1250
|
|
|
$res.=" /Annots ["; |
1251
|
|
|
foreach ($o['info']['annot'] as $aId) { |
1252
|
|
|
$res.=" ".$aId." 0 R"; |
1253
|
|
|
} |
1254
|
|
|
$res.=" ]"; |
1255
|
|
|
} |
1256
|
|
|
$count = count($o['info']['contents']); |
1257
|
|
|
if ($count==1) { |
1258
|
|
|
$res.=" /Contents ".$o['info']['contents'][0]." 0 R"; |
1259
|
|
|
} elseif ($count>1) { |
1260
|
|
|
$res.=" /Contents [ "; |
1261
|
|
|
foreach ($o['info']['contents'] as $cId) { |
1262
|
|
|
$res.=$cId." 0 R "; |
1263
|
|
|
} |
1264
|
|
|
$res.="]"; |
1265
|
|
|
} |
1266
|
|
|
$res.=" >>\nendobj"; |
1267
|
|
|
return $res; |
1268
|
|
|
break; |
|
|
|
|
1269
|
|
|
} |
1270
|
3 |
|
} |
1271
|
|
|
|
1272
|
|
|
/** |
1273
|
|
|
* the contents objects hold all of the content which appears on pages |
1274
|
|
|
* @access private |
1275
|
|
|
*/ |
1276
|
3 |
|
private function o_contents($id, $action, $options = '') |
1277
|
|
|
{ |
1278
|
3 |
|
if ($action!='new') { |
1279
|
|
|
$o =& $this->objects[$id]; |
1280
|
|
|
} |
1281
|
|
|
switch ($action) { |
1282
|
3 |
|
case 'new': |
1283
|
3 |
|
$this->objects[$id]=array('t'=>'contents','c'=>'','info'=>array()); |
1284
|
3 |
|
if (strlen($options) && intval($options)) { |
1285
|
|
|
// then this contents is the primary for a page |
1286
|
3 |
|
$this->objects[$id]['onPage']=$options; |
1287
|
3 |
|
} elseif ($options=='raw') { |
1288
|
|
|
// then this page contains some other type of system object |
1289
|
|
|
$this->objects[$id]['raw']=1; |
1290
|
|
|
} |
1291
|
3 |
|
break; |
1292
|
|
|
case 'add': |
1293
|
|
|
// add more options to the decleration |
1294
|
|
|
foreach ($options as $k => $v) { |
|
|
|
|
1295
|
|
|
$o['info'][$k]=$v; |
|
|
|
|
1296
|
|
|
} |
1297
|
|
|
case 'out': |
1298
|
|
|
$tmp=$o['c']; |
1299
|
|
|
$res= "\n".$id." 0 obj\n"; |
1300
|
|
|
if (isset($this->objects[$id]['raw'])) { |
1301
|
|
|
$res.=$tmp; |
1302
|
|
|
} else { |
1303
|
|
|
$res.= "<<"; |
1304
|
|
View Code Duplication |
if (function_exists('gzcompress') && $this->options['compression']) { |
1305
|
|
|
// then implement ZLIB based compression on this content stream |
1306
|
|
|
$res.=" /Filter /FlateDecode"; |
1307
|
|
|
$tmp = gzcompress($tmp, $this->options['compression']); |
1308
|
|
|
} |
1309
|
|
|
if ($this->encrypted) { |
1310
|
|
|
$this->encryptInit($id); |
1311
|
|
|
$tmp = $this->ARC4($tmp); |
1312
|
|
|
} |
1313
|
|
|
foreach ($o['info'] as $k => $v) { |
1314
|
|
|
$res .= " /".$k.' '.$v; |
1315
|
|
|
} |
1316
|
|
|
$res.=" /Length ".strlen($tmp)." >> stream\n".$tmp."\nendstream"; |
1317
|
|
|
} |
1318
|
|
|
$res.="\nendobj"; |
1319
|
|
|
return $res; |
1320
|
|
|
break; |
|
|
|
|
1321
|
|
|
} |
1322
|
3 |
|
} |
1323
|
|
|
|
1324
|
|
|
/** |
1325
|
|
|
* an image object, will be an XObject in the document, includes description and data |
1326
|
|
|
* @access private |
1327
|
|
|
*/ |
1328
|
|
|
private function o_image($id, $action, $options = '') |
1329
|
|
|
{ |
1330
|
|
|
if ($action!='new') { |
1331
|
|
|
$o =& $this->objects[$id]; |
1332
|
|
|
} |
1333
|
|
|
switch ($action) { |
1334
|
|
|
case 'new': |
1335
|
|
|
// make the new object |
1336
|
|
|
$this->objects[$id]=array('t'=>'image','data'=>$options['data'],'info'=>array()); |
1337
|
|
|
$this->objects[$id]['info']['Type']='/XObject'; |
1338
|
|
|
$this->objects[$id]['info']['Subtype']='/Image'; |
1339
|
|
|
$this->objects[$id]['info']['Width']=$options['iw']; |
1340
|
|
|
$this->objects[$id]['info']['Height']=$options['ih']; |
1341
|
|
|
if (!isset($options['type']) || $options['type']=='jpg') { |
1342
|
|
|
if (!isset($options['channels'])) { |
1343
|
|
|
$options['channels']=3; |
1344
|
|
|
} |
1345
|
|
|
switch ($options['channels']) { |
1346
|
|
|
case 1: |
1347
|
|
|
$this->objects[$id]['info']['ColorSpace']='/DeviceGray'; |
1348
|
|
|
break; |
1349
|
|
|
default: |
1350
|
|
|
$this->objects[$id]['info']['ColorSpace']='/DeviceRGB'; |
1351
|
|
|
break; |
1352
|
|
|
} |
1353
|
|
|
$this->objects[$id]['info']['Filter']='/DCTDecode'; |
1354
|
|
|
$this->objects[$id]['info']['BitsPerComponent']=8; |
1355
|
|
|
} elseif ($options['type']=='png') { |
1356
|
|
|
if (strlen($options['pdata'])) { |
1357
|
|
|
$this->numObj++; |
1358
|
|
|
$this->objects[$this->numObj]=array('t'=>'image','c'=>'','info'=>array()); |
1359
|
|
|
$this->objects[$this->numObj]['info'] = array('Type'=>'/XObject', 'Subtype'=>'/Image', 'Width'=> $options['iw'], 'Height'=> $options['ih'], 'Filter'=>'/FlateDecode', 'ColorSpace'=>'/DeviceGray', 'BitsPerComponent'=>'8', 'DecodeParms'=>'<< /Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns '.$options['iw'].' >>'); |
1360
|
|
|
$this->objects[$this->numObj]['data']=$options['pdata']; |
1361
|
|
|
if (isset($options['transparency'])) { |
1362
|
|
|
switch ($options['transparency']['type']) { |
1363
|
|
|
case 'indexed': |
1364
|
|
|
$tmp=' [ '.$options['transparency']['data'].' '.$options['transparency']['data'].'] '; |
1365
|
|
|
$this->objects[$id]['info']['Mask'] = $tmp; |
1366
|
|
|
$this->objects[$id]['info']['ColorSpace'] = ' [ /Indexed /DeviceRGB '.(strlen($options['pdata'])/3-1).' '.$this->numObj.' 0 R ]'; |
1367
|
|
|
break; |
1368
|
|
|
case 'alpha': |
1369
|
|
|
$this->objects[$id]['info']['SMask'] = $this->numObj.' 0 R'; |
1370
|
|
|
$this->objects[$id]['info']['ColorSpace'] = '/'.$options['color']; |
1371
|
|
|
break; |
1372
|
|
|
} |
1373
|
|
|
} |
1374
|
|
|
} else { |
1375
|
|
|
$this->objects[$id]['info']['ColorSpace']='/'.$options['color']; |
1376
|
|
|
} |
1377
|
|
|
$this->objects[$id]['info']['BitsPerComponent']=$options['bitsPerComponent']; |
1378
|
|
|
$this->objects[$id]['info']['Filter']='/FlateDecode'; |
1379
|
|
|
$this->objects[$id]['data'] = $options['data']; |
1380
|
|
|
$this->objects[$id]['info']['DecodeParms']='<< /Predictor 15 /Colors '.$options['ncolor'].' /Columns '.$options['iw'].' /BitsPerComponent '.$options['bitsPerComponent'].'>>'; |
1381
|
|
|
} |
1382
|
|
|
// assign it a place in the named resource dictionary as an external object, according to |
1383
|
|
|
// the label passed in with it. |
1384
|
|
|
$this->o_pages($this->currentNode, 'xObject', array('label'=>$options['label'],'objNum'=>$id)); |
|
|
|
|
1385
|
|
|
break; |
1386
|
|
|
case 'out': |
1387
|
|
|
$tmp=$o['data']; |
|
|
|
|
1388
|
|
|
$res= "\n".$id." 0 obj\n<<"; |
1389
|
|
|
foreach ($o['info'] as $k => $v) { |
1390
|
|
|
$res.=" /".$k.' '.$v; |
1391
|
|
|
} |
1392
|
|
|
if ($this->encrypted) { |
1393
|
|
|
$this->encryptInit($id); |
1394
|
|
|
$tmp = $this->ARC4($tmp); |
1395
|
|
|
} |
1396
|
|
|
$res.=" /Length ".strlen($tmp)." >> stream\n".$tmp."\nendstream\nendobj"; |
1397
|
|
|
return $res; |
1398
|
|
|
break; |
|
|
|
|
1399
|
|
|
} |
1400
|
|
|
} |
1401
|
|
|
|
1402
|
|
|
/** |
1403
|
|
|
* encryption object. |
1404
|
|
|
* @access private |
1405
|
|
|
*/ |
1406
|
|
|
private function o_encryption($id, $action, $options = '') |
1407
|
|
|
{ |
1408
|
|
|
if ($action!='new') { |
1409
|
|
|
$o =& $this->objects[$id]; |
1410
|
|
|
} |
1411
|
|
|
switch ($action) { |
1412
|
|
|
case 'new': |
1413
|
|
|
// make the new object |
1414
|
|
|
$this->objects[$id]=array('t'=>'encryption','info'=>$options); |
1415
|
|
|
$this->arc4_objnum=$id; |
1416
|
|
|
|
1417
|
|
|
// Pad or truncate the owner password |
1418
|
|
|
$owner = substr($options['owner'].$this->encryptionPad, 0, 32); |
1419
|
|
|
$user = substr($options['user'].$this->encryptionPad, 0, 32); |
1420
|
|
|
|
1421
|
|
|
$this->debug("o_encryption: user password (".$options['user'].") / owner password (".$options['owner'].")"); |
1422
|
|
|
|
1423
|
|
|
// convert permission set into binary string |
1424
|
|
|
$permissions = sprintf("%c%c%c%c", ($options['p'] & 255), (($options['p'] >> 8) & 255), (($options['p'] >> 16) & 255), (($options['p'] >> 24) & 255)); |
1425
|
|
|
|
1426
|
|
|
// Algo 3.3 Owner Password being set into /O Dictionary |
1427
|
|
|
$this->objects[$id]['info']['O'] = $this->encryptOwner($owner, $user); |
1428
|
|
|
|
1429
|
|
|
// Algo 3.5 User Password - START |
1430
|
|
|
$this->objects[$id]['info']['U'] = $this->encryptUser($user, $this->objects[$id]['info']['O'], $permissions); |
1431
|
|
|
// encryption key is set in encryptUser function |
1432
|
|
|
//$this->encryptionKey = $encryptionKey; |
|
|
|
|
1433
|
|
|
|
1434
|
|
|
$this->encrypted=1; |
|
|
|
|
1435
|
|
|
break; |
1436
|
|
|
case 'out': |
1437
|
|
|
$res= "\n".$id." 0 obj\n<<"; |
1438
|
|
|
$res.=' /Filter /Standard'; |
1439
|
|
|
if ($this->encryptionMode > 1) { // RC4 128bit encryption |
1440
|
|
|
$res.=' /V 2'; |
1441
|
|
|
$res.=' /R 3'; |
1442
|
|
|
$res.=' /Length 128'; |
1443
|
|
|
} else { // RC4 40bit encryption |
1444
|
|
|
$res.=' /V 1'; |
1445
|
|
|
$res.=' /R 2'; |
1446
|
|
|
} |
1447
|
|
|
// use hex string instead of char code - char codes can make troubles (E.g. CR or LF) |
1448
|
|
|
$res.=' /O <'.$this->strToHex($o['info']['O']).'>'; |
|
|
|
|
1449
|
|
|
$res.=' /U <'.$this->strToHex($o['info']['U']).'>'; |
1450
|
|
|
// and the p-value needs to be converted to account for the twos-complement approach |
1451
|
|
|
//$o['info']['p'] = (($o['info']['p'] ^ 0xFFFFFFFF)+1)*-1; |
|
|
|
|
1452
|
|
|
$res.=' /P '.($o['info']['p']); |
1453
|
|
|
$res.=" >>\nendobj"; |
1454
|
|
|
return $res; |
1455
|
|
|
break; |
|
|
|
|
1456
|
|
|
} |
1457
|
|
|
} |
1458
|
|
|
|
1459
|
|
|
/** |
1460
|
|
|
* owner part of the encryption |
1461
|
|
|
* @param $owner - owner password plus padding |
1462
|
|
|
* @param $user - user password plus padding |
1463
|
|
|
* @access private |
1464
|
|
|
*/ |
1465
|
|
|
private function encryptOwner($owner, $user) |
1466
|
|
|
{ |
1467
|
|
|
$keylength = 5; |
1468
|
|
|
if ($this->encryptionMode > 1) { |
1469
|
|
|
$keylength = 16; |
1470
|
|
|
} |
1471
|
|
|
|
1472
|
|
|
$ownerHash = $this->md5_16($owner); // PDF 1.4 - repeat this 50 times in revision 3 |
1473
|
|
|
if ($this->encryptionMode > 1) { // if it is the RC4 128bit encryption |
1474
|
|
|
for ($i = 0; $i < 50; $i++) { |
1475
|
|
|
$ownerHash = $this->md5_16($ownerHash); |
1476
|
|
|
} |
1477
|
|
|
} |
1478
|
|
|
|
1479
|
|
|
$ownerKey = substr($ownerHash, 0, $keylength); // PDF 1.4 - Create the encryption key (IMPORTANT: need to check Length) |
1480
|
|
|
|
1481
|
|
|
$this->ARC4_init($ownerKey); // 5 bytes of the encryption key (hashed 50 times) |
1482
|
|
|
$ovalue=$this->ARC4($user); // PDF 1.4 - Encrypt the padded user password using RC4 |
1483
|
|
|
|
1484
|
|
|
if ($this->encryptionMode > 1) { |
1485
|
|
|
$len = strlen($ownerKey); |
1486
|
|
View Code Duplication |
for ($i = 1; $i<=19; ++$i) { |
1487
|
|
|
$ek = ''; |
1488
|
|
|
for ($j=0; $j < $len; $j++) { |
1489
|
|
|
$ek .= chr(ord($ownerKey[$j]) ^ $i); |
1490
|
|
|
} |
1491
|
|
|
$this->ARC4_init($ek); |
1492
|
|
|
$ovalue = $this->ARC4($ovalue); |
1493
|
|
|
} |
1494
|
|
|
} |
1495
|
|
|
return $ovalue; |
1496
|
|
|
} |
1497
|
|
|
|
1498
|
|
|
/** |
1499
|
|
|
* |
1500
|
|
|
* user part of the encryption |
1501
|
|
|
* @param $user - user password plus padding |
1502
|
|
|
* @param $ownerDict - encrypted owner entry |
1503
|
|
|
* @param $permissions - permission set (print, copy, modify, ...) |
1504
|
|
|
*/ |
1505
|
|
|
function encryptUser($user, $ownerDict, $permissions) |
1506
|
|
|
{ |
1507
|
|
|
$keylength = 5; |
1508
|
|
|
if ($this->encryptionMode > 1) { |
1509
|
|
|
$keylength = 16; |
1510
|
|
|
} |
1511
|
|
|
// make hash with user, encrypted owner, permission set and fileIdentifier |
1512
|
|
|
$hash = $this->md5_16($user.$ownerDict.$permissions.$this->hexToStr($this->fileIdentifier)); |
1513
|
|
|
|
1514
|
|
|
// loop thru the hash process when it is revision 3 of encryption routine (usually RC4 128bit) |
1515
|
|
|
if ($this->encryptionMode > 1) { |
1516
|
|
|
for ($i = 0; $i < 50; ++$i) { |
1517
|
|
|
$hash = $this->md5_16(substr($hash, 0, $keylength)); // use only length of encryption key from the previous hash |
1518
|
|
|
} |
1519
|
|
|
} |
1520
|
|
|
|
1521
|
|
|
$this->encryptionKey = substr($hash, 0, $keylength); // PDF 1.4 - Create the encryption key (IMPORTANT: need to check Length) |
1522
|
|
|
|
1523
|
|
|
if ($this->encryptionMode > 1) { // if it is the RC4 128bit encryption |
1524
|
|
|
// make a md5 hash from padding string (hardcoded by Adobe) and the fileIdenfier |
1525
|
|
|
$userHash = $this->md5_16($this->encryptionPad.$this->hexToStr($this->fileIdentifier)); |
1526
|
|
|
|
1527
|
|
|
// encrypt the hash from the previous method by using the encryptionKey |
1528
|
|
|
$this->ARC4_init($this->encryptionKey); |
1529
|
|
|
$uvalue=$this->ARC4($userHash); |
1530
|
|
|
|
1531
|
|
|
$len = strlen($this->encryptionKey); |
1532
|
|
View Code Duplication |
for ($i = 1; $i<=19; ++$i) { |
1533
|
|
|
$ek = ''; |
1534
|
|
|
for ($j=0; $j< $len; $j++) { |
1535
|
|
|
$ek .= chr(ord($this->encryptionKey[$j]) ^ $i); |
1536
|
|
|
} |
1537
|
|
|
$this->ARC4_init($ek); |
1538
|
|
|
$uvalue = $this->ARC4($uvalue); |
1539
|
|
|
} |
1540
|
|
|
$uvalue .= substr($this->encryptionPad, 0, 16); |
1541
|
|
|
|
1542
|
|
|
//$this->encryptionKey = $encryptionKey; |
|
|
|
|
1543
|
|
|
} else { // if it is the RC4 40bit encryption |
1544
|
|
|
$this->ARC4_init($this->encryptionKey); |
1545
|
|
|
//$this->encryptionKey = $encryptionKey; |
|
|
|
|
1546
|
|
|
//$this->encrypted=1; |
|
|
|
|
1547
|
|
|
$uvalue=$this->ARC4($this->encryptionPad); |
1548
|
|
|
} |
1549
|
|
|
return $uvalue; |
1550
|
|
|
} |
1551
|
|
|
|
1552
|
|
|
/** |
1553
|
|
|
* internal method to convert string to hexstring (used for owner and user dictionary) |
1554
|
|
|
* @param $string - any string value |
1555
|
|
|
* @access protected |
1556
|
|
|
*/ |
1557
|
|
|
protected function strToHex($string) |
1558
|
|
|
{ |
1559
|
|
|
$hex = ''; |
1560
|
|
|
for ($i=0; $i < strlen($string); $i++) { |
1561
|
|
|
$hex .= sprintf("%02x", ord($string[$i])); |
1562
|
|
|
} |
1563
|
|
|
return $hex; |
1564
|
|
|
} |
1565
|
|
|
|
1566
|
|
|
protected function hexToStr($hex) |
1567
|
|
|
{ |
1568
|
|
|
$str = ''; |
1569
|
|
|
for ($i=0; $i<strlen($hex); $i+=2) { |
1570
|
|
|
$str .= chr(hexdec(substr($hex, $i, 2))); |
1571
|
|
|
} |
1572
|
|
|
return $str; |
1573
|
|
|
} |
1574
|
|
|
|
1575
|
|
|
/** |
1576
|
|
|
* calculate the 16 byte version of the 128 bit md5 digest of the string |
1577
|
|
|
* @access private |
1578
|
|
|
*/ |
1579
|
|
|
private function md5_16($string) |
1580
|
|
|
{ |
1581
|
|
|
$tmp = md5($string); |
1582
|
|
|
$out = pack("H*", $tmp); |
1583
|
|
|
return $out; |
1584
|
|
|
} |
1585
|
|
|
|
1586
|
|
|
/** |
1587
|
|
|
* initialize the encryption for processing a particular object |
1588
|
|
|
* @access private |
1589
|
|
|
*/ |
1590
|
|
|
private function encryptInit($id) |
1591
|
|
|
{ |
1592
|
|
|
$tmp = $this->encryptionKey; |
1593
|
|
|
$hex = dechex($id); |
1594
|
|
|
if (strlen($hex)<6) { |
1595
|
|
|
$hex = substr('000000', 0, 6-strlen($hex)).$hex; |
1596
|
|
|
} |
1597
|
|
|
$tmp.= chr(hexdec(substr($hex, 4, 2))).chr(hexdec(substr($hex, 2, 2))).chr(hexdec(substr($hex, 0, 2))).chr(0).chr(0); |
1598
|
|
|
$key = $this->md5_16($tmp); |
1599
|
|
|
if ($this->encryptionMode > 1) { |
1600
|
|
|
$this->ARC4_init(substr($key, 0, 16)); // use max 16 bytes for RC4 128bit encryption key |
1601
|
|
|
} else { |
1602
|
|
|
$this->ARC4_init(substr($key, 0, 10)); // use (n + 5 bytes) for RC4 40bit encryption key |
1603
|
|
|
} |
1604
|
|
|
} |
1605
|
|
|
|
1606
|
|
|
/** |
1607
|
|
|
* initialize the ARC4 encryption |
1608
|
|
|
* @access private |
1609
|
|
|
*/ |
1610
|
|
|
private function ARC4_init($key = '') |
1611
|
|
|
{ |
1612
|
|
|
$this->arc4 = ''; |
1613
|
|
|
// setup the control array |
1614
|
|
|
if (strlen($key)==0) { |
1615
|
|
|
return; |
1616
|
|
|
} |
1617
|
|
|
$k = ''; |
1618
|
|
|
while (strlen($k)<256) { |
1619
|
|
|
$k.=$key; |
1620
|
|
|
} |
1621
|
|
|
$k=substr($k, 0, 256); |
1622
|
|
|
for ($i=0; $i<256; $i++) { |
1623
|
|
|
$this->arc4 .= chr($i); |
1624
|
|
|
} |
1625
|
|
|
$j=0; |
1626
|
|
|
for ($i=0; $i<256; $i++) { |
1627
|
|
|
$t = $this->arc4[$i]; |
1628
|
|
|
$j = ($j + ord($t) + ord($k[$i]))%256; |
1629
|
|
|
$this->arc4[$i]=$this->arc4[$j]; |
1630
|
|
|
$this->arc4[$j]=$t; |
1631
|
|
|
} |
1632
|
|
|
} |
1633
|
|
|
|
1634
|
|
|
/** |
1635
|
|
|
* ARC4 encrypt a text string |
1636
|
|
|
* @access private |
1637
|
|
|
*/ |
1638
|
|
|
private function ARC4($text) |
1639
|
|
|
{ |
1640
|
|
|
$len=strlen($text); |
1641
|
|
|
$a=0; |
1642
|
|
|
$b=0; |
1643
|
|
|
$c = $this->arc4; |
1644
|
|
|
$out=''; |
1645
|
|
|
for ($i=0; $i<$len; $i++) { |
1646
|
|
|
$a = ($a+1)%256; |
1647
|
|
|
$t= $c[$a]; |
1648
|
|
|
$b = ($b+ord($t))%256; |
1649
|
|
|
$c[$a]=$c[$b]; |
1650
|
|
|
$c[$b]=$t; |
1651
|
|
|
$k = ord($c[(ord($c[$a])+ord($c[$b]))%256]); |
1652
|
|
|
$out.=chr(ord($text[$i]) ^ $k); |
1653
|
|
|
} |
1654
|
|
|
return $out; |
1655
|
|
|
} |
1656
|
|
|
|
1657
|
|
|
/** |
1658
|
|
|
* add a link in the document to an external URL |
1659
|
|
|
* @access public |
1660
|
|
|
*/ |
1661
|
|
|
public function addLink($url, $x0, $y0, $x1, $y1) |
1662
|
|
|
{ |
1663
|
|
|
$this->numObj++; |
1664
|
|
|
$info = array('type'=>'link','url'=>$url,'rect'=>array($x0,$y0,$x1,$y1)); |
1665
|
|
|
$this->o_annotation($this->numObj, 'new', $info); |
|
|
|
|
1666
|
|
|
} |
1667
|
|
|
|
1668
|
|
|
/** |
1669
|
|
|
* add a link in the document to an internal destination (ie. within the document) |
1670
|
|
|
* @access public |
1671
|
|
|
*/ |
1672
|
|
|
public function addInternalLink($label, $x0, $y0, $x1, $y1) |
1673
|
|
|
{ |
1674
|
|
|
$this->numObj++; |
1675
|
|
|
$info = array('type'=>'ilink','label'=>$label,'rect'=>array($x0,$y0,$x1,$y1)); |
1676
|
|
|
$this->o_annotation($this->numObj, 'new', $info); |
|
|
|
|
1677
|
|
|
} |
1678
|
|
|
|
1679
|
|
|
/** |
1680
|
|
|
* set the encryption of the document |
1681
|
|
|
* can be used to turn it on and/or set the passwords which it will have. |
1682
|
|
|
* also the functions that the user will have are set here, such as print, modify, add |
1683
|
|
|
* @access public |
1684
|
|
|
*/ |
1685
|
|
|
public function setEncryption($userPass = '', $ownerPass = '', $pc = array(), $mode = 1) |
1686
|
|
|
{ |
1687
|
|
|
if ($mode > 1) { |
1688
|
|
|
$p=bindec('01111111111111111111000011000000'); // revision 3 is using bit 3 - 6 AND 9 - 12 |
1689
|
|
|
} else { |
1690
|
|
|
$p=bindec('01111111111111111111111111000000'); // while revision 2 is using bit 3 - 6 only |
1691
|
|
|
} |
1692
|
|
|
|
1693
|
|
|
$options = array( |
1694
|
|
|
'print'=>4 |
1695
|
|
|
,'modify'=>8 |
1696
|
|
|
,'copy'=>16 |
1697
|
|
|
,'add'=>32 |
1698
|
|
|
,'fill'=>256 |
1699
|
|
|
,'extract'=>512 |
1700
|
|
|
,'assemble'=>1024 |
1701
|
|
|
,'represent'=>2048 |
1702
|
|
|
); |
1703
|
|
|
foreach ($pc as $k => $v) { |
1704
|
|
|
if ($v && isset($options[$k])) { |
1705
|
|
|
$p+=$options[$k]; |
1706
|
|
|
} elseif (isset($options[$v])) { |
1707
|
|
|
$p+=$options[$v]; |
1708
|
|
|
} |
1709
|
|
|
} |
1710
|
|
|
|
1711
|
|
|
// set the encryption mode to either RC4 40bit or RC4 128bit |
1712
|
|
|
$this->encryptionMode = $mode; |
1713
|
|
|
|
1714
|
|
|
// implement encryption on the document |
1715
|
|
|
if ($this->arc4_objnum == 0) { |
1716
|
|
|
// then the block does not exist already, add it. |
1717
|
|
|
$this->numObj++; |
1718
|
|
|
if (strlen($ownerPass)==0) { |
1719
|
|
|
$ownerPass=$userPass; |
1720
|
|
|
} |
1721
|
|
|
$this->o_encryption($this->numObj, 'new', array('user'=>$userPass,'owner'=>$ownerPass,'p'=>$p)); |
|
|
|
|
1722
|
|
|
} |
1723
|
|
|
} |
1724
|
|
|
|
1725
|
|
|
/** |
1726
|
|
|
* should be used for internal checks, not implemented as yet |
1727
|
|
|
* @access public |
1728
|
|
|
*/ |
1729
|
|
|
function checkAllHere() |
1730
|
|
|
{ |
1731
|
|
|
// set the validation flag to true when everything is ok. |
1732
|
|
|
// currently it only checks if output function has been called |
1733
|
|
|
$this->valid = true; |
1734
|
|
|
} |
1735
|
|
|
|
1736
|
|
|
/** |
1737
|
|
|
* return the pdf stream as a string returned from the function |
1738
|
|
|
* This method is protect to force user to use ezOutput from Cezpdf.php |
1739
|
|
|
* @access protected |
1740
|
|
|
*/ |
1741
|
|
|
function output($debug = 0) |
1742
|
|
|
{ |
1743
|
|
|
|
1744
|
|
|
if ($debug) { |
1745
|
|
|
// turn compression off |
1746
|
|
|
$this->options['compression']=0; |
1747
|
|
|
} |
1748
|
|
|
|
1749
|
|
|
if ($this->arc4_objnum) { |
1750
|
|
|
$this->ARC4_init($this->encryptionKey); |
1751
|
|
|
} |
1752
|
|
|
|
1753
|
|
|
if ($this->valid) { |
1754
|
|
|
$this->debug('The output method has been executed again', E_USER_WARNING); |
1755
|
|
|
} |
1756
|
|
|
|
1757
|
|
|
$this->checkAllHere(); |
1758
|
|
|
|
1759
|
|
|
$xref=array(); |
1760
|
|
|
$content="%PDF-1.4\n%����"; |
1761
|
|
|
// $content="%PDF-1.3\n"; |
1762
|
|
|
$pos=strlen($content); |
1763
|
|
|
foreach ($this->objects as $k => $v) { |
1764
|
|
|
$tmp='o_'.$v['t']; |
1765
|
|
|
$cont=$this->$tmp($k, 'out'); |
1766
|
|
|
$content.=$cont; |
1767
|
|
|
$xref[]=$pos; |
1768
|
|
|
$pos+=strlen($cont); |
1769
|
|
|
} |
1770
|
|
|
++$pos; |
1771
|
|
|
$content.="\nxref\n0 ".(count($xref)+1)."\n0000000000 65535 f \n"; |
1772
|
|
|
foreach ($xref as $p) { |
1773
|
|
|
$content.=substr('0000000000', 0, 10-strlen($p+1)).($p+1)." 00000 n \n"; |
1774
|
|
|
} |
1775
|
|
|
$content.="trailer\n<< /Size ".(count($xref)+1)." /Root 1 0 R /Info ".$this->infoObject." 0 R"; |
1776
|
|
|
// if encryption has been applied to this document then add the marker for this dictionary |
1777
|
|
|
if ($this->arc4_objnum > 0) { |
1778
|
|
|
$content .= " /Encrypt ".$this->arc4_objnum." 0 R"; |
1779
|
|
|
} |
1780
|
|
|
if (strlen($this->fileIdentifier)) { |
1781
|
|
|
$content .= " /ID[<".$this->fileIdentifier."><".$this->fileIdentifier.">]"; |
1782
|
|
|
} |
1783
|
|
|
$content .= " >>\nstartxref\n".$pos."\n%%EOF\n"; |
1784
|
|
|
return $content; |
1785
|
|
|
} |
1786
|
|
|
|
1787
|
|
|
/** |
1788
|
|
|
* intialize a new document |
1789
|
|
|
* if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum |
1790
|
|
|
* this function is called automatically by the constructor function |
1791
|
|
|
* |
1792
|
|
|
* @access protected |
1793
|
|
|
*/ |
1794
|
3 |
|
protected function newDocument($pageSize = array(0,0,612,792)) |
1795
|
|
|
{ |
1796
|
3 |
|
$this->numObj=0; |
1797
|
3 |
|
$this->objects = array(); |
1798
|
|
|
|
1799
|
3 |
|
$this->numObj++; |
1800
|
3 |
|
$this->o_catalog($this->numObj, 'new'); |
1801
|
|
|
|
1802
|
3 |
|
$this->numObj++; |
1803
|
3 |
|
$this->o_outlines($this->numObj, 'new'); |
1804
|
|
|
|
1805
|
3 |
|
$this->numObj++; |
1806
|
3 |
|
$this->o_pages($this->numObj, 'new'); |
1807
|
|
|
|
1808
|
3 |
|
$this->o_pages($this->numObj, 'mediaBox', $pageSize); |
|
|
|
|
1809
|
3 |
|
$this->currentNode = 3; |
1810
|
|
|
|
1811
|
3 |
|
$this->o_pages($this->numObj, 'procset', '[/PDF/TEXT/ImageB/ImageC/ImageI]'); |
1812
|
|
|
|
1813
|
3 |
|
$this->numObj++; |
1814
|
3 |
|
$this->o_info($this->numObj, 'new'); |
1815
|
|
|
|
1816
|
3 |
|
$this->numObj++; |
1817
|
3 |
|
$this->o_page($this->numObj, 'new'); |
1818
|
|
|
|
1819
|
|
|
// need to store the first page id as there is no way to get it to the user during |
1820
|
|
|
// startup |
1821
|
3 |
|
$this->firstPageId = $this->currentContents; |
1822
|
3 |
|
} |
1823
|
|
|
|
1824
|
|
|
/** |
1825
|
|
|
* open the font file and return a php structure containing it. |
1826
|
|
|
* first check if this one has been done before and saved in a form more suited to php |
1827
|
|
|
* note that if a php serialized version does not exist it will try and make one, but will |
1828
|
|
|
* require write access to the directory to do it... it is MUCH faster to have these serialized |
1829
|
|
|
* files. |
1830
|
|
|
* |
1831
|
|
|
* @param string $font Font name (can contain both path and extension) |
1832
|
|
|
* |
1833
|
|
|
* @return void |
1834
|
|
|
*/ |
1835
|
3 |
|
protected function openFont($font) |
1836
|
|
|
{ |
1837
|
|
|
// assume that $font contains both the path and perhaps the extension to the file, split them |
1838
|
3 |
|
$pos = strrpos($font, '/'); |
1839
|
3 |
|
if ($pos === false) { |
1840
|
|
|
// $dir = './'; |
|
|
|
|
1841
|
3 |
|
$dir = dirname(__FILE__) . '/fonts/'; |
1842
|
3 |
|
$name = $font; |
1843
|
3 |
|
} else { |
1844
|
|
|
$dir = substr($font, 0, $pos + 1); |
1845
|
|
|
$name = substr($font, $pos + 1); |
1846
|
|
|
} |
1847
|
|
|
|
1848
|
3 |
|
if (!$this->isUnicode) { |
1849
|
3 |
|
$metrics_name = "$name.afm"; |
1850
|
3 |
|
} else { |
1851
|
|
|
$metrics_name = "$name.ufm"; |
1852
|
|
|
} |
1853
|
|
|
|
1854
|
3 |
|
$this->debug('openFont executed: '.$font.' - '.$name.' / IsUnicode: '.$this->isUnicode); |
1855
|
3 |
|
$cachedFile = 'cached'.$metrics_name.'.php'; |
1856
|
|
|
|
1857
|
|
|
// use the temp folder to read/write cached font data |
1858
|
3 |
|
if (file_exists($this->tempPath.'/'.$cachedFile)) { |
1859
|
2 |
|
$cacheDate = filemtime($this->tempPath.'/'.$cachedFile); |
1860
|
|
|
|
1861
|
2 |
|
if (file_exists($dir.$metrics_name) && filemtime($dir.$metrics_name) > $cacheDate) { |
1862
|
|
|
unset($this->fonts[$font]); |
1863
|
|
|
$this->debug('openFont: update required for cached file '.$this->tempPath.'/'.$cachedFile); |
1864
|
|
|
} else { |
1865
|
2 |
|
$this->debug('openFont: cached file found in '.$this->tempPath.'/'.$cachedFile); |
1866
|
2 |
|
$this->fonts[$font] = require($this->tempPath.'/'.$cachedFile); |
1867
|
2 |
|
if (!isset($this->fonts[$font]['_version_']) || $this->fonts[$font]['_version_']<2) { |
1868
|
|
|
// if the font file is old, then clear it out and prepare for re-creation |
1869
|
|
|
$this->debug('openFont: version conflict with cached font file. Force recreation'); |
1870
|
|
|
unset($this->fonts[$font]); |
1871
|
|
|
} |
1872
|
|
|
} |
1873
|
2 |
|
} |
1874
|
3 |
|
if (!isset($this->fonts[$font]) && file_exists($dir.$metrics_name)) { |
1875
|
|
|
// (re)build the php_<font>.afm file from the <font>.afm file |
1876
|
1 |
|
$this->debug('openFont: cached file '.$cachedFile.' (re)created'); |
1877
|
1 |
|
$data = array(); |
1878
|
|
|
// set unicode to true ufm file is used |
1879
|
1 |
|
$data['isUnicode'] = (strtolower(substr($metrics_name, -3)) !== 'afm'); |
1880
|
|
|
|
1881
|
1 |
|
$cidtogid = ''; |
1882
|
1 |
|
if ($data['isUnicode']) { |
1883
|
|
|
$cidtogid = str_pad('', 256*256*2, "\x00"); |
1884
|
|
|
} |
1885
|
|
|
|
1886
|
1 |
|
$file = file($dir.$metrics_name); |
1887
|
1 |
|
foreach ($file as $row) { |
1888
|
1 |
|
$row=trim($row); |
1889
|
1 |
|
$pos=strpos($row, ' '); |
1890
|
1 |
|
if ($pos) { |
1891
|
|
|
// then there must be some keyword |
1892
|
1 |
|
$key = substr($row, 0, $pos); |
1893
|
|
|
switch ($key) { |
1894
|
1 |
|
case 'FontName': |
1895
|
1 |
|
case 'FullName': |
1896
|
1 |
|
case 'FamilyName': |
1897
|
1 |
|
case 'Weight': |
1898
|
1 |
|
case 'ItalicAngle': |
1899
|
1 |
|
case 'IsFixedPitch': |
1900
|
1 |
|
case 'CharacterSet': |
1901
|
1 |
|
case 'UnderlinePosition': |
1902
|
1 |
|
case 'UnderlineThickness': |
1903
|
1 |
|
case 'Version': |
1904
|
1 |
|
case 'EncodingScheme': |
1905
|
1 |
|
case 'CapHeight': |
1906
|
1 |
|
case 'XHeight': |
1907
|
1 |
|
case 'Ascender': |
1908
|
1 |
|
case 'Descender': |
1909
|
1 |
|
case 'StdHW': |
1910
|
1 |
|
case 'StdVW': |
1911
|
1 |
|
case 'StartCharMetrics': |
1912
|
1 |
|
$data[$key]=trim(substr($row, $pos)); |
1913
|
1 |
|
break; |
1914
|
1 |
|
case 'FontBBox': |
1915
|
1 |
|
$data[$key]=explode(' ', trim(substr($row, $pos))); |
1916
|
1 |
|
break; |
1917
|
1 |
|
case 'C': |
1918
|
|
|
// C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ; |
1919
|
|
|
// use preg_match instead to improve performace |
1920
|
|
|
// IMPORTANT: if "L i fi ; L l fl ;" is required preg_match must be amended |
1921
|
1 |
|
$r = preg_match('/C (-?\d+) ; WX (-?\d+) ; N (\w+) ; B (-?\d+) (-?\d+) (-?\d+) (-?\d+) ;/', $row, $m); |
1922
|
1 |
|
if ($r == 1) { |
1923
|
|
|
//$dtmp = array('C'=> $m[1],'WX'=> $m[2], 'N' => $m[3], 'B' => array($m[4], $m[5], $m[6], $m[7])); |
|
|
|
|
1924
|
1 |
|
$c = (int)$m[1]; |
1925
|
1 |
|
$n = $m[3]; |
1926
|
1 |
|
$width = floatval($m[2]); |
1927
|
|
|
|
1928
|
1 |
|
if ($c >= 0) { |
1929
|
1 |
|
if ($c != hexdec($n)) { |
1930
|
1 |
|
$data['codeToName'][$c] = $n; |
1931
|
1 |
|
} |
1932
|
1 |
|
$data['C'][$c] = $width; |
1933
|
1 |
|
$data['C'][$n] = $width; |
1934
|
1 |
|
} else { |
1935
|
1 |
|
$data['C'][$n] = $width; |
1936
|
|
|
} |
1937
|
|
|
|
1938
|
1 |
View Code Duplication |
if (!isset($data['MissingWidth']) && $c == -1 && $n === '.notdef') { |
1939
|
|
|
$data['MissingWidth'] = $width; |
1940
|
|
|
} |
1941
|
1 |
|
} |
1942
|
1 |
|
break; |
1943
|
|
|
// U 827 ; WX 0 ; N squaresubnosp ; G 675 ; |
1944
|
1 |
|
case 'U': // Found in UFM files |
1945
|
|
|
if (!$data['isUnicode']) { |
1946
|
|
|
break; |
1947
|
|
|
} |
1948
|
|
|
|
1949
|
|
|
$r = preg_match('/U (-?\d+) ; WX (-?\d+) ; N (\w+) ; G (-?\d+) ;/', $row, $m); |
1950
|
|
|
|
1951
|
|
|
if ($r == 1) { |
1952
|
|
|
//$dtmp = array('U'=> $m[1],'WX'=> $m[2], 'N' => $m[3], 'G' => $m[4]); |
|
|
|
|
1953
|
|
|
$c = (int)$m[1]; |
1954
|
|
|
$n = $m[3]; |
1955
|
|
|
$glyph = $m[4]; |
1956
|
|
|
$width = floatval($m[2]); |
1957
|
|
|
|
1958
|
|
|
if ($c >= 0) { |
1959
|
|
|
if ($c >= 0 && $c < 0xFFFF && $glyph) { |
1960
|
|
|
$cidtogid[$c*2] = chr($glyph >> 8); |
1961
|
|
|
$cidtogid[$c*2 + 1] = chr($glyph & 0xFF); |
1962
|
|
|
} |
1963
|
|
|
if ($c != hexdec($n)) { |
1964
|
|
|
$data['codeToName'][$c] = $n; |
1965
|
|
|
} |
1966
|
|
|
$data['C'][$c] = $width; |
1967
|
|
|
} else { |
1968
|
|
|
$data['C'][$n] = $width; |
1969
|
|
|
} |
1970
|
|
|
|
1971
|
|
View Code Duplication |
if (!isset($data['MissingWidth']) && $c == -1 && $n === '.notdef') { |
1972
|
|
|
$data['MissingWidth'] = $width; |
1973
|
|
|
} |
1974
|
|
|
} |
1975
|
|
|
break; |
1976
|
1 |
|
case 'KPX': |
1977
|
1 |
|
break; |
1978
|
|
|
// KPX Adieresis yacute -40 |
1979
|
|
|
$bits=explode(' ', $row); |
|
|
|
|
1980
|
|
|
$data['KPX'][$bits[1]][$bits[2]]=$bits[3]; |
1981
|
|
|
break; |
1982
|
|
|
} |
1983
|
1 |
|
} |
1984
|
1 |
|
} |
1985
|
1 |
|
$data['CIDtoGID'] = base64_encode($cidtogid); |
1986
|
1 |
|
$data['_version_']=2; |
1987
|
|
|
|
1988
|
1 |
|
$this->fonts[$font]=$data; |
1989
|
1 |
|
$fp = fopen($this->tempPath.'/'.$cachedFile, 'w'); // use the temp folder to write cached font data |
1990
|
1 |
|
fwrite($fp, '<?php /* R&OS php pdf class font cache file */ return '.var_export($data, true).'; ?>'); |
1991
|
1 |
|
fclose($fp); |
1992
|
3 |
|
} elseif (!isset($this->fonts[$font])) { |
1993
|
|
|
$this->debug(sprintf('openFont: no font file found for "'.$font.'" IsUnicode: %b', $font, $this->isUnicode), E_USER_WARNING); |
1994
|
|
|
} |
1995
|
3 |
|
} |
1996
|
|
|
|
1997
|
|
|
/** |
1998
|
|
|
* if the font is not loaded then load it and make the required object |
1999
|
|
|
* else just make it the current font |
2000
|
|
|
* the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding' |
2001
|
|
|
* note that encoding='none' will need to be used for symbolic fonts |
2002
|
|
|
* and 'differences' => an array of mappings between numbers 0->255 and character names. |
2003
|
|
|
* |
2004
|
|
|
* @param string $fontName Name of the font |
2005
|
|
|
* @param string $encoding Which encoding to use |
2006
|
|
|
* @param integer $set What is this |
2007
|
|
|
* |
2008
|
|
|
* @return void |
2009
|
|
|
* @access public |
2010
|
|
|
*/ |
2011
|
3 |
|
public function selectFont($fontName, $encoding = '', $set = 1, $subsetFont = false) |
2012
|
|
|
{ |
2013
|
3 |
|
if ($subsetFont && !class_exists('TTFsubset')) { |
2014
|
|
|
$this->debug("TTFsubset class not found. Falling back to complete font program", E_USER_WARNING); |
2015
|
|
|
$subsetFont = false; |
2016
|
|
|
} |
2017
|
|
|
|
2018
|
3 |
|
$ext = substr($fontName, -4); |
2019
|
3 |
|
if ($ext === '.afm' || $ext === '.ufm') { |
2020
|
3 |
|
$fontName = substr($fontName, 0, strlen($fontName)-4); |
2021
|
3 |
|
} |
2022
|
|
|
|
2023
|
3 |
|
$pos = strrpos($fontName, '/'); |
2024
|
3 |
|
if ($pos !== false) { |
2025
|
|
|
$name = substr($fontName, $pos + 1); |
2026
|
|
|
} else { |
2027
|
3 |
|
$name = $fontName; |
2028
|
|
|
} |
2029
|
|
|
|
2030
|
3 |
|
if (!isset($this->fonts[$fontName])) { |
2031
|
|
|
// load the file |
2032
|
3 |
|
$this->openFont($fontName); |
2033
|
3 |
|
if (isset($this->fonts[$fontName])) { |
2034
|
3 |
|
$this->numObj++; |
2035
|
3 |
|
$this->numFonts++; |
2036
|
|
|
|
2037
|
3 |
|
$font = &$this->fonts[$fontName]; |
2038
|
3 |
|
$options = array('name' => $name, 'fontFileName' => $fontName); |
2039
|
|
|
|
2040
|
3 |
|
if (is_array($encoding)) { |
2041
|
|
|
// then encoding and differences might be set |
2042
|
3 |
|
if (isset($encoding['encoding'])) { |
2043
|
|
|
$options['encoding'] = $encoding['encoding']; |
2044
|
|
|
} |
2045
|
3 |
|
if (isset($encoding['differences'])) { |
2046
|
3 |
|
$options['differences'] = $encoding['differences']; |
2047
|
3 |
|
} |
2048
|
3 |
|
} elseif (strlen($encoding)) { |
2049
|
|
|
// then perhaps only the encoding has been set |
2050
|
|
|
$options['encoding'] = $encoding; |
2051
|
|
|
} |
2052
|
3 |
|
$fontObj = $this->numObj; |
2053
|
3 |
|
$this->o_font($fontObj, 'new', $options); |
|
|
|
|
2054
|
3 |
|
$font['fontNum'] = $this->numFonts; |
2055
|
|
|
// if this is a '.afm' font, and there is a '.pfa' file to go with it (as there |
2056
|
|
|
// should be for all non-basic fonts), then load it into an object and put the |
2057
|
|
|
// references into the font object |
2058
|
|
|
|
2059
|
3 |
|
$fbtype = ''; |
2060
|
3 |
|
if (file_exists($fontName.'.pfb')) { |
2061
|
|
|
$fbtype = 'pfb'; |
2062
|
3 |
|
} elseif (file_exists($fontName.'.ttf')) { |
2063
|
|
|
$fbtype = 'ttf'; |
2064
|
|
|
} |
2065
|
|
|
|
2066
|
3 |
|
$fbfile = $fontName.'.'.$fbtype; |
2067
|
|
|
|
2068
|
3 |
|
if ($fbtype) { |
2069
|
|
|
$adobeFontName = $font['FontName']; |
2070
|
|
|
$this->debug('selectFont: adding font file "'.$fbfile.'" to pdf'); |
2071
|
|
|
// find the array of fond widths, and put that into an object. |
2072
|
|
|
$firstChar = -1; |
2073
|
|
|
$lastChar = 0; |
2074
|
|
|
$widths = array(); |
2075
|
|
|
$cid_widths = array(); |
2076
|
|
|
|
2077
|
|
|
foreach ($font['C'] as $num => $d) { |
2078
|
|
|
if (intval($num) > 0 || $num == '0') { |
2079
|
|
|
if (!$font['isUnicode']) { |
2080
|
|
|
if ($lastChar > 0 && $num > $lastChar + 1) { |
2081
|
|
|
for ($i = $lastChar + 1; $i < $num; $i++) { |
2082
|
|
|
$widths[] = 0; |
2083
|
|
|
} |
2084
|
|
|
} |
2085
|
|
|
} |
2086
|
|
|
$widths[] = $d; |
2087
|
|
|
|
2088
|
|
|
if ($font['isUnicode']) { |
2089
|
|
|
$cid_widths[$num] = $d; |
2090
|
|
|
} |
2091
|
|
|
|
2092
|
|
|
if ($firstChar == -1) { |
2093
|
|
|
$firstChar = $num; |
2094
|
|
|
} |
2095
|
|
|
$lastChar = $num; |
2096
|
|
|
} |
2097
|
|
|
} |
2098
|
|
|
// also need to adjust the widths for the differences array |
2099
|
|
|
if (isset($options['differences'])) { |
2100
|
|
|
foreach ($options['differences'] as $charNum => $charName) { |
2101
|
|
|
if ($charNum>$lastChar) { |
2102
|
|
|
for ($i = $lastChar + 1; $i <= $charNum; $i++) { |
2103
|
|
|
$widths[]=0; |
2104
|
|
|
} |
2105
|
|
|
$lastChar = $charNum; |
2106
|
|
|
} |
2107
|
|
|
if (isset($font['C'][$charName])) { |
2108
|
|
|
$widths[$charNum-$firstChar]=$font['C'][$charName]; |
2109
|
|
|
if ($font['isUnicode']) { |
2110
|
|
|
$cid_widths[$charName] = $font['C'][$charName]; |
2111
|
|
|
} |
2112
|
|
|
} |
2113
|
|
|
} |
2114
|
|
|
} |
2115
|
|
|
|
2116
|
|
|
if ($font['isUnicode']) { |
2117
|
|
|
$font['CIDWidths'] = $cid_widths; |
2118
|
|
|
} |
2119
|
|
|
$this->debug('selectFont: FirstChar='.$firstChar); |
2120
|
|
|
$this->debug('selectFont: LastChar='.$lastChar); |
2121
|
|
|
|
2122
|
|
|
$widthid = -1; |
2123
|
|
|
|
2124
|
|
|
if (!$font['isUnicode']) { |
2125
|
|
|
$this->numObj++; |
2126
|
|
|
$this->o_contents($this->numObj, 'new', 'raw'); |
2127
|
|
|
$this->objects[$this->numObj]['c'].='['.implode(' ', $widths).']'; |
2128
|
|
|
$widthid = $this->numObj; |
2129
|
|
|
} |
2130
|
|
|
|
2131
|
|
|
$missing_width = 500; |
|
|
|
|
2132
|
|
|
$stemV = 70; |
2133
|
|
|
|
2134
|
|
|
if (isset($font['MissingWidth'])) { |
2135
|
|
|
$missing_width = $font['MissingWidth']; |
|
|
|
|
2136
|
|
|
} |
2137
|
|
|
if (isset($font['StdVW'])) { |
2138
|
|
|
$stemV = $font['StdVW']; |
2139
|
|
|
} elseif (isset($font['Weight']) && preg_match('!(bold|black)!i', $font['Weight'])) { |
2140
|
|
|
$stemV = 120; |
2141
|
|
|
} |
2142
|
|
|
|
2143
|
|
|
// create the font descriptor |
2144
|
|
|
$fontDescriptorId = ++$this->numObj; |
2145
|
|
|
|
2146
|
|
|
// determine flags (more than a little flakey, hopefully will not matter much) |
2147
|
|
|
$flags=0; |
2148
|
|
|
if ($font['ItalicAngle']!=0) { |
|
|
|
|
2149
|
|
|
$flags+=pow(2, 6); |
2150
|
|
|
} |
2151
|
|
|
if ($font['IsFixedPitch']=='true') { |
2152
|
|
|
$flags+=1; |
2153
|
|
|
} |
2154
|
|
|
$flags+=pow(2, 5); // assume non-sybolic |
2155
|
|
|
|
2156
|
|
|
$list = array('Ascent'=>'Ascender','CapHeight'=>'CapHeight','Descent'=>'Descender','FontBBox'=>'FontBBox','ItalicAngle'=>'ItalicAngle'); |
2157
|
|
|
$fdopt = array( |
2158
|
|
|
'Flags' => $flags, |
2159
|
|
|
'FontName' => $adobeFontName, |
2160
|
|
|
'StemV' => $stemV |
2161
|
|
|
); |
2162
|
|
|
foreach ($list as $k => $v) { |
2163
|
|
|
if (isset($font[$v])) { |
2164
|
|
|
$fdopt[$k]=$font[$v]; |
2165
|
|
|
} |
2166
|
|
|
} |
2167
|
|
|
|
2168
|
|
|
// setup the basic properties for o_font output |
2169
|
|
|
$tmp = array('BaseFont'=>$adobeFontName,'Widths'=>$widthid |
2170
|
|
|
,'FirstChar'=>$firstChar,'LastChar'=>$lastChar |
2171
|
|
|
,'FontDescriptor'=>$fontDescriptorId); |
2172
|
|
|
|
2173
|
|
|
// binary content of pfb or ttf file |
2174
|
|
|
$pfbid = ++$this->numObj; |
2175
|
|
|
|
2176
|
|
|
// embed the font program |
2177
|
|
|
// to allow font subsets embedding fonts is proceed in o_font 'output' |
2178
|
|
|
if ($this->embedFont) { |
2179
|
|
|
if ($fbtype=='pfb') { |
2180
|
|
|
$fdopt['FontFile']=$pfbid; |
2181
|
|
|
} elseif ($fbtype=='ttf') { |
2182
|
|
|
$fdopt['FontFile2']=$pfbid; |
2183
|
|
|
$tmp['SubType']='TrueType'; // Declare basic font as TrueType |
2184
|
|
|
} |
2185
|
|
|
$this->o_fontDescriptor($fontDescriptorId, 'new', $fdopt); |
|
|
|
|
2186
|
|
|
$this->o_contents($pfbid, 'new'); |
2187
|
|
|
} |
2188
|
|
|
|
2189
|
|
|
$this->debug('selectFont: adding extra info to font.('.$fontObj.')'); |
2190
|
|
|
foreach ($tmp as $fk => $fv) { |
2191
|
|
|
$this->debug($fk." : ".$fv); |
2192
|
|
|
} |
2193
|
|
|
$this->o_font($fontObj, 'add', $tmp); |
|
|
|
|
2194
|
3 |
|
} elseif (!in_array(strtolower($name), $this->coreFonts)) { |
2195
|
|
|
$this->debug('selectFont: No pfb/ttf file found for "'.$name.'"', E_USER_WARNING); |
2196
|
|
|
} |
2197
|
|
|
|
2198
|
|
|
// also set the differences here, note that this means that these will take effect only the |
2199
|
|
|
// first time that a font is selected, else they are ignored |
2200
|
3 |
|
if (isset($options['differences'])) { |
2201
|
3 |
|
$font['differences']=$options['differences']; |
2202
|
3 |
|
} |
2203
|
3 |
|
} |
2204
|
3 |
|
} |
2205
|
|
|
|
2206
|
3 |
|
$this->fonts[$fontName]['isSubset'] = $subsetFont; |
2207
|
3 |
|
if (!isset($this->fonts[$fontName]['subset'])) { |
2208
|
3 |
|
$this->fonts[$fontName]['subset'] = array(); |
2209
|
3 |
|
} |
2210
|
|
|
|
2211
|
3 |
|
if ($set && isset($this->fonts[$fontName])) { |
2212
|
|
|
// so if for some reason the font was not set in the last one then it will not be selected |
2213
|
3 |
|
$this->currentBaseFont=$fontName; |
2214
|
|
|
// the next line means that if a new font is selected, then the current text state will be |
2215
|
|
|
// applied to it as well. |
2216
|
3 |
|
$this->setCurrentFont(); |
2217
|
3 |
|
} |
2218
|
3 |
|
return $this->currentFontNum; |
2219
|
|
|
} |
2220
|
|
|
|
2221
|
|
|
/** |
2222
|
|
|
* sets up the current font, based on the font families, and the current text state |
2223
|
|
|
* note that this system is quite flexible, a <b><i> font can be completely different to a |
2224
|
|
|
* <i><b> font, and even <b><b> will have to be defined within the family to have meaning |
2225
|
|
|
* This function is to be called whenever the currentTextState is changed, it will update |
2226
|
|
|
* the currentFont setting to whatever the appropriatte family one is. |
2227
|
|
|
* If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont |
2228
|
|
|
* This function will change the currentFont to whatever it should be, but will not change the |
2229
|
|
|
* currentBaseFont. |
2230
|
|
|
* |
2231
|
|
|
* @access protected |
2232
|
|
|
*/ |
2233
|
3 |
|
protected function setCurrentFont() |
2234
|
|
|
{ |
2235
|
3 |
|
if (strlen($this->currentBaseFont)==0) { |
2236
|
|
|
// then assume an initial font |
2237
|
|
|
$this->selectFont(dirname(__FILE__) . '/fonts/Helvetica'); |
2238
|
|
|
} |
2239
|
3 |
|
$pos = strrpos($this->currentBaseFont, '/'); |
2240
|
3 |
|
if ($pos !== false) { |
2241
|
|
|
$cf = substr($this->currentBaseFont, $pos + 1); |
2242
|
|
|
} else { |
2243
|
3 |
|
$cf = $this->currentBaseFont; |
2244
|
|
|
} |
2245
|
3 |
|
if (strlen($this->currentTextState) |
2246
|
3 |
|
&& isset($this->fontFamilies[$cf]) |
2247
|
3 |
|
&& isset($this->fontFamilies[$cf][$this->currentTextState])) { |
2248
|
|
|
// then we are in some state or another |
2249
|
|
|
// and this font has a family, and the current setting exists within it |
2250
|
|
|
// select the font, then return it |
2251
|
|
|
if ($pos !== false) { |
2252
|
|
|
$nf = substr($this->currentBaseFont, 0, strrpos($this->currentBaseFont, '/') + 1).$this->fontFamilies[$cf][$this->currentTextState]; |
2253
|
|
|
} else { |
2254
|
|
|
$nf = $this->fontFamilies[$cf][$this->currentTextState]; |
2255
|
|
|
} |
2256
|
|
|
$this->selectFont($nf, '', 0); |
2257
|
|
|
$this->currentFont = $nf; |
2258
|
|
|
$this->currentFontNum = $this->fonts[$nf]['fontNum']; |
2259
|
|
|
} else { |
2260
|
|
|
// the this font must not have the right family member for the current state |
2261
|
|
|
// simply assume the base font |
2262
|
3 |
|
$this->currentFont = $this->currentBaseFont; |
2263
|
3 |
|
$this->currentFontNum = $this->fonts[$this->currentFont]['fontNum']; |
2264
|
|
|
} |
2265
|
3 |
|
} |
2266
|
|
|
|
2267
|
|
|
/** |
2268
|
|
|
* function for the user to find out what the ID is of the first page that was created during |
2269
|
|
|
* startup - useful if they wish to add something to it later. |
2270
|
|
|
* @access protected |
2271
|
|
|
*/ |
2272
|
|
|
protected function getFirstPageId() |
2273
|
|
|
{ |
2274
|
|
|
return $this->firstPageId; |
2275
|
|
|
} |
2276
|
|
|
|
2277
|
|
|
/** |
2278
|
|
|
* add content to the currently active object |
2279
|
|
|
* @access protected |
2280
|
|
|
*/ |
2281
|
|
|
protected function addContent($content) |
2282
|
|
|
{ |
2283
|
|
|
$this->objects[$this->currentContents]['c'].=$content; |
2284
|
|
|
} |
2285
|
|
|
|
2286
|
|
|
/** |
2287
|
|
|
* sets the colour for fill operations |
2288
|
|
|
* @access public |
2289
|
|
|
*/ |
2290
|
|
View Code Duplication |
public function setColor($r, $g, $b, $force = 0) |
|
|
|
|
2291
|
|
|
{ |
2292
|
|
|
if ($r>=0 && ($force || $r!=$this->currentColour['r'] || $g!=$this->currentColour['g'] || $b!=$this->currentColour['b'])) { |
2293
|
|
|
$this->objects[$this->currentContents]['c'].="\n".sprintf('%.3F', $r).' '.sprintf('%.3F', $g).' '.sprintf('%.3F', $b).' rg'; |
2294
|
|
|
$this->currentColour=array('r'=>$r,'g'=>$g,'b'=>$b); |
2295
|
|
|
} |
2296
|
|
|
} |
2297
|
|
|
|
2298
|
|
|
/** |
2299
|
|
|
* sets the colour for stroke operations |
2300
|
|
|
* @access public |
2301
|
|
|
*/ |
2302
|
|
View Code Duplication |
public function setStrokeColor($r, $g, $b, $force = 0) |
|
|
|
|
2303
|
|
|
{ |
2304
|
|
|
if ($r>=0 && ($force || $r!=$this->currentStrokeColour['r'] || $g!=$this->currentStrokeColour['g'] || $b!=$this->currentStrokeColour['b'])) { |
2305
|
|
|
$this->objects[$this->currentContents]['c'].="\n".sprintf('%.3F', $r).' '.sprintf('%.3F', $g).' '.sprintf('%.3F', $b).' RG'; |
2306
|
|
|
$this->currentStrokeColour=array('r'=>$r,'g'=>$g,'b'=>$b); |
2307
|
|
|
} |
2308
|
|
|
} |
2309
|
|
|
|
2310
|
|
|
/** |
2311
|
|
|
* draw a line from one set of coordinates to another |
2312
|
|
|
* @access public |
2313
|
|
|
*/ |
2314
|
|
|
public function line($x1, $y1, $x2, $y2) |
2315
|
|
|
{ |
2316
|
|
|
$this->objects[$this->currentContents]['c'].="\n".sprintf('%.3F', $x1).' '.sprintf('%.3F', $y1).' m '.sprintf('%.3F', $x2).' '.sprintf('%.3F', $y2).' l S'; |
2317
|
|
|
} |
2318
|
|
|
|
2319
|
|
|
/** |
2320
|
|
|
* draw a bezier curve based on 4 control points |
2321
|
|
|
* @access public |
2322
|
|
|
*/ |
2323
|
|
|
public function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3) |
2324
|
|
|
{ |
2325
|
|
|
// in the current line style, draw a bezier curve from (x0,y0) to (x3,y3) using the other two points |
2326
|
|
|
// as the control points for the curve. |
2327
|
|
|
$this->objects[$this->currentContents]['c'].="\n".sprintf('%.3F', $x0).' '.sprintf('%.3F', $y0).' m '.sprintf('%.3F', $x1).' '.sprintf('%.3F', $y1); |
2328
|
|
|
$this->objects[$this->currentContents]['c'].= ' '.sprintf('%.3F', $x2).' '.sprintf('%.3F', $y2).' '.sprintf('%.3F', $x3).' '.sprintf('%.3F', $y3).' c S'; |
2329
|
|
|
} |
2330
|
|
|
|
2331
|
|
|
/** |
2332
|
|
|
* draw a part of an ellipse |
2333
|
|
|
* @access public |
2334
|
|
|
*/ |
2335
|
|
|
public function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8) |
2336
|
|
|
{ |
2337
|
|
|
$this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, 0); |
2338
|
|
|
} |
2339
|
|
|
|
2340
|
|
|
/** |
2341
|
|
|
* draw a filled ellipse |
2342
|
|
|
* @access public |
2343
|
|
|
*/ |
2344
|
|
|
public function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360) |
|
|
|
|
2345
|
|
|
{ |
2346
|
|
|
return $this->ellipse($x0, $y0, $r1, $r2 = 0, $angle, $nSeg, $astart, $afinish, 1, 1); |
2347
|
|
|
} |
2348
|
|
|
|
2349
|
|
|
/** |
2350
|
|
|
* draw an ellipse |
2351
|
|
|
* note that the part and filled ellipse are just special cases of this function |
2352
|
|
|
* |
2353
|
|
|
* draws an ellipse in the current line style |
2354
|
|
|
* centered at $x0,$y0, radii $r1,$r2 |
2355
|
|
|
* if $r2 is not set, then a circle is drawn |
2356
|
|
|
* nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a |
2357
|
|
|
* pretty crappy shape at 2, as we are approximating with bezier curves. |
2358
|
|
|
* @access public |
2359
|
|
|
*/ |
2360
|
|
|
public function ellipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360, $close = 1, $fill = 0) |
2361
|
|
|
{ |
2362
|
|
|
if ($r1==0) { |
2363
|
|
|
return; |
2364
|
|
|
} |
2365
|
|
|
if ($r2==0) { |
2366
|
|
|
$r2=$r1; |
2367
|
|
|
} |
2368
|
|
|
if ($nSeg<2) { |
2369
|
|
|
$nSeg=2; |
2370
|
|
|
} |
2371
|
|
|
|
2372
|
|
|
$astart = deg2rad((float)$astart); |
2373
|
|
|
$afinish = deg2rad((float)$afinish); |
2374
|
|
|
$totalAngle =$afinish-$astart; |
2375
|
|
|
|
2376
|
|
|
$dt = $totalAngle/$nSeg; |
2377
|
|
|
$dtm = $dt/3; |
2378
|
|
|
|
2379
|
|
|
if ($angle != 0) { |
2380
|
|
|
$a = -1*deg2rad((float)$angle); |
2381
|
|
|
$tmp = "\n q "; |
2382
|
|
|
$tmp .= sprintf('%.3F', cos($a)).' '.sprintf('%.3F', (-1.0*sin($a))).' '.sprintf('%.3F', sin($a)).' '.sprintf('%.3F', cos($a)).' '; |
2383
|
|
|
$tmp .= sprintf('%.3F', $x0).' '.sprintf('%.3F', $y0).' cm'; |
2384
|
|
|
$this->objects[$this->currentContents]['c'].= $tmp; |
2385
|
|
|
$x0=0; |
2386
|
|
|
$y0=0; |
2387
|
|
|
} |
2388
|
|
|
|
2389
|
|
|
$t1 = $astart; |
2390
|
|
|
$a0 = $x0+$r1*cos($t1); |
2391
|
|
|
$b0 = $y0+$r2*sin($t1); |
2392
|
|
|
$c0 = -$r1*sin($t1); |
2393
|
|
|
$d0 = $r2*cos($t1); |
2394
|
|
|
|
2395
|
|
|
$this->objects[$this->currentContents]['c'].="\n".sprintf('%.3F', $a0).' '.sprintf('%.3F', $b0).' m '; |
2396
|
|
|
for ($i=1; $i<=$nSeg; $i++) { |
2397
|
|
|
// draw this bit of the total curve |
2398
|
|
|
$t1 = $i*$dt+$astart; |
2399
|
|
|
$a1 = $x0+$r1*cos($t1); |
2400
|
|
|
$b1 = $y0+$r2*sin($t1); |
2401
|
|
|
$c1 = -$r1*sin($t1); |
2402
|
|
|
$d1 = $r2*cos($t1); |
2403
|
|
|
$this->objects[$this->currentContents]['c'].="\n".sprintf('%.3F', ($a0+$c0*$dtm)).' '.sprintf('%.3F', ($b0+$d0*$dtm)); |
2404
|
|
|
$this->objects[$this->currentContents]['c'].= ' '.sprintf('%.3F', ($a1-$c1*$dtm)).' '.sprintf('%.3F', ($b1-$d1*$dtm)).' '.sprintf('%.3F', $a1).' '.sprintf('%.3F', $b1).' c'; |
2405
|
|
|
$a0=$a1; |
2406
|
|
|
$b0=$b1; |
2407
|
|
|
$c0=$c1; |
2408
|
|
|
$d0=$d1; |
2409
|
|
|
} |
2410
|
|
|
if ($fill) { |
2411
|
|
|
$this->objects[$this->currentContents]['c'].=' f'; |
2412
|
|
View Code Duplication |
} else { |
2413
|
|
|
if ($close) { |
2414
|
|
|
$this->objects[$this->currentContents]['c'].=' s'; // small 's' signifies closing the path as well |
2415
|
|
|
} else { |
2416
|
|
|
$this->objects[$this->currentContents]['c'].=' S'; |
2417
|
|
|
} |
2418
|
|
|
} |
2419
|
|
|
if ($angle !=0) { |
2420
|
|
|
$this->objects[$this->currentContents]['c'].=' Q'; |
2421
|
|
|
} |
2422
|
|
|
} |
2423
|
|
|
|
2424
|
|
|
/** |
2425
|
|
|
* this sets the line drawing style. |
2426
|
|
|
* width, is the thickness of the line in user units |
2427
|
|
|
* cap is the type of cap to put on the line, values can be 'butt','round','square' |
2428
|
|
|
* where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the |
2429
|
|
|
* end of the line. |
2430
|
|
|
* join can be 'miter', 'round', 'bevel' |
2431
|
|
|
* dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the |
2432
|
|
|
* on and off dashes. |
2433
|
|
|
* (2) represents 2 on, 2 off, 2 on , 2 off ... |
2434
|
|
|
* (2,1) is 2 on, 1 off, 2 on, 1 off.. etc |
2435
|
|
|
* phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts. |
2436
|
|
|
* @access public |
2437
|
|
|
*/ |
2438
|
|
|
public function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0) |
2439
|
|
|
{ |
2440
|
|
|
|
2441
|
|
|
// this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day |
2442
|
|
|
$string = ''; |
2443
|
|
|
if ($width>0) { |
2444
|
|
|
$string.= $width.' w'; |
2445
|
|
|
} |
2446
|
|
|
$ca = array('butt'=>0,'round'=>1,'square'=>2); |
2447
|
|
|
if (isset($ca[$cap])) { |
2448
|
|
|
$string.= ' '.$ca[$cap].' J'; |
2449
|
|
|
} |
2450
|
|
|
$ja = array('miter'=>0,'round'=>1,'bevel'=>2); |
2451
|
|
|
if (isset($ja[$join])) { |
2452
|
|
|
$string.= ' '.$ja[$join].' j'; |
2453
|
|
|
} |
2454
|
|
|
if (is_array($dash)) { |
2455
|
|
|
$string.= ' ['; |
2456
|
|
|
foreach ($dash as $len) { |
2457
|
|
|
$string.=' '.$len; |
2458
|
|
|
} |
2459
|
|
|
$string.= ' ] '.$phase.' d'; |
2460
|
|
|
} |
2461
|
|
|
$this->currentLineStyle = $string; |
2462
|
|
|
$this->objects[$this->currentContents]['c'].="\n".$string; |
2463
|
|
|
} |
2464
|
|
|
|
2465
|
|
|
/** |
2466
|
|
|
* draw a polygon, the syntax for this is similar to the GD polygon command |
2467
|
|
|
* @access public |
2468
|
|
|
*/ |
2469
|
|
|
public function polygon($p, $np, $f = 0) |
2470
|
|
|
{ |
2471
|
|
|
$this->objects[$this->currentContents]['c'].="\n"; |
2472
|
|
|
$this->objects[$this->currentContents]['c'].=sprintf('%.3F', $p[0]).' '.sprintf('%.3F', $p[1]).' m '; |
2473
|
|
|
for ($i=2; $i<$np*2; $i=$i+2) { |
2474
|
|
|
$this->objects[$this->currentContents]['c'].= sprintf('%.3F', $p[$i]).' '.sprintf('%.3F', $p[$i+1]).' l '; |
2475
|
|
|
} |
2476
|
|
View Code Duplication |
if ($f==1) { |
2477
|
|
|
$this->objects[$this->currentContents]['c'].=' f'; |
2478
|
|
|
} else { |
2479
|
|
|
$this->objects[$this->currentContents]['c'].=' S'; |
2480
|
|
|
} |
2481
|
|
|
} |
2482
|
|
|
|
2483
|
|
|
/** |
2484
|
|
|
* a filled rectangle, note that it is the width and height of the rectangle which are the secondary paramaters, not |
2485
|
|
|
* the coordinates of the upper-right corner |
2486
|
|
|
* @access public |
2487
|
|
|
*/ |
2488
|
|
|
public function filledRectangle($x1, $y1, $width, $height) |
2489
|
|
|
{ |
2490
|
|
|
$this->objects[$this->currentContents]['c'].="\n".sprintf('%.3F', $x1).' '.sprintf('%.3F', $y1).' '.sprintf('%.3F', $width).' '.sprintf('%.3F', $height).' re f'; |
2491
|
|
|
} |
2492
|
|
|
|
2493
|
|
|
/** |
2494
|
|
|
* draw a rectangle, note that it is the width and height of the rectangle which are the secondary paramaters, not |
2495
|
|
|
* the coordinates of the upper-right corner |
2496
|
|
|
* @access public |
2497
|
|
|
*/ |
2498
|
|
|
public function rectangle($x1, $y1, $width, $height) |
2499
|
|
|
{ |
2500
|
|
|
$this->objects[$this->currentContents]['c'].="\n".sprintf('%.3F', $x1).' '.sprintf('%.3F', $y1).' '.sprintf('%.3F', $width).' '.sprintf('%.3F', $height).' re S'; |
2501
|
|
|
} |
2502
|
|
|
|
2503
|
|
|
/** |
2504
|
|
|
* add a new page to the document |
2505
|
|
|
* this also makes the new page the current active object |
2506
|
|
|
* @access public |
2507
|
|
|
*/ |
2508
|
|
|
public function newPage($insert = 0, $id = 0, $pos = 'after') |
2509
|
|
|
{ |
2510
|
|
|
|
2511
|
|
|
// if there is a state saved, then go up the stack closing them |
2512
|
|
|
// then on the new page, re-open them with the right setings |
2513
|
|
|
|
2514
|
|
|
if ($this->nStateStack) { |
2515
|
|
|
for ($i=$this->nStateStack; $i>=1; $i--) { |
2516
|
|
|
$this->restoreState($i); |
2517
|
|
|
} |
2518
|
|
|
} |
2519
|
|
|
|
2520
|
|
|
$this->numObj++; |
2521
|
|
|
if ($insert) { |
2522
|
|
|
// the id from the ezPdf class is the od of the contents of the page, not the page object itself |
2523
|
|
|
// query that object to find the parent |
2524
|
|
|
$rid = $this->objects[$id]['onPage']; |
2525
|
|
|
$opt= array('rid'=>$rid,'pos'=>$pos); |
2526
|
|
|
$this->o_page($this->numObj, 'new', $opt); |
|
|
|
|
2527
|
|
|
} else { |
2528
|
|
|
$this->o_page($this->numObj, 'new'); |
2529
|
|
|
} |
2530
|
|
|
// if there is a stack saved, then put that onto the page |
2531
|
|
|
if ($this->nStateStack) { |
2532
|
|
|
for ($i=1; $i<=$this->nStateStack; $i++) { |
2533
|
|
|
$this->saveState($i); |
2534
|
|
|
} |
2535
|
|
|
} |
2536
|
|
|
// and if there has been a stroke or fill colour set, then transfer them |
2537
|
|
View Code Duplication |
if ($this->currentColour['r']>=0) { |
2538
|
|
|
$this->setColor($this->currentColour['r'], $this->currentColour['g'], $this->currentColour['b'], 1); |
2539
|
|
|
} |
2540
|
|
View Code Duplication |
if ($this->currentStrokeColour['r']>=0) { |
2541
|
|
|
$this->setStrokeColor($this->currentStrokeColour['r'], $this->currentStrokeColour['g'], $this->currentStrokeColour['b'], 1); |
2542
|
|
|
} |
2543
|
|
|
|
2544
|
|
|
// if there is a line style set, then put this in too |
2545
|
|
|
if (strlen($this->currentLineStyle)) { |
2546
|
|
|
$this->objects[$this->currentContents]['c'].="\n".$this->currentLineStyle; |
2547
|
|
|
} |
2548
|
|
|
|
2549
|
|
|
// the call to the o_page object set currentContents to the present page, so this can be returned as the page id |
2550
|
|
|
return $this->currentContents; |
2551
|
|
|
} |
2552
|
|
|
|
2553
|
|
|
/** |
2554
|
|
|
* output the pdf code, streaming it to the browser |
2555
|
|
|
* the relevant headers are set so that hopefully the browser will recognise it |
2556
|
|
|
* this method is protected to force user to use ezStream method from Cezpdf.php |
2557
|
|
|
* @access protected |
2558
|
|
|
*/ |
2559
|
|
|
protected function stream($options = '') |
2560
|
|
|
{ |
2561
|
|
|
// setting the options allows the adjustment of the headers |
2562
|
|
|
// values at the moment are: |
2563
|
|
|
// 'Content-Disposition'=>'filename' - sets the filename, though not too sure how well this will |
2564
|
|
|
// work as in my trial the browser seems to use the filename of the php file with .pdf on the end |
2565
|
|
|
// 'Accept-Ranges'=>1 or 0 - if this is not set to 1, then this header is not included, off by default |
2566
|
|
|
// this header seems to have caused some problems despite tha fact that it is supposed to solve |
2567
|
|
|
// them, so I am leaving it off by default. |
2568
|
|
|
// 'compress'=> 1 or 0 - apply content stream compression, this is on (1) by default |
2569
|
|
|
// 'download'=> 1 or 0 - provide download dialog |
2570
|
|
|
if (!is_array($options)) { |
2571
|
|
|
$options=array(); |
2572
|
|
|
} |
2573
|
|
|
if (isset($options['compress']) && $options['compress']==0) { |
2574
|
|
|
$tmp = $this->output(1); |
2575
|
|
|
} else { |
2576
|
|
|
$tmp = $this->output(); |
2577
|
|
|
} |
2578
|
|
|
header("Content-Type: application/pdf"); |
2579
|
|
|
header("Content-Length: ".strlen(ltrim($tmp))); |
2580
|
|
|
$fileName = (isset($options['Content-Disposition'])?$options['Content-Disposition']:'file.pdf'); |
2581
|
|
|
if (isset($options['download']) && $options['download'] == 1) { |
2582
|
|
|
$attached = 'attachment'; |
2583
|
|
|
} else { |
2584
|
|
|
$attached = 'inline'; |
2585
|
|
|
} |
2586
|
|
|
header("Content-Disposition: $attached; filename=".$fileName); |
2587
|
|
|
if (isset($options['Accept-Ranges']) && $options['Accept-Ranges']==1) { |
2588
|
|
|
header("Accept-Ranges: ".strlen(ltrim($tmp))); |
2589
|
|
|
} |
2590
|
|
|
echo ltrim($tmp); |
2591
|
|
|
} |
2592
|
|
|
|
2593
|
|
|
/** |
2594
|
|
|
* return the height in units of the current font in the given size |
2595
|
|
|
* @access public |
2596
|
|
|
*/ |
2597
|
|
|
public function getFontHeight($size) |
2598
|
|
|
{ |
2599
|
|
|
if (!$this->numFonts) { |
2600
|
|
|
$this->selectFont('./fonts/Helvetica'); |
2601
|
|
|
} |
2602
|
|
|
|
2603
|
|
|
$font = &$this->fonts[$this->currentFont]; |
2604
|
|
|
// for the current font, and the given size, what is the height of the font in user units |
2605
|
|
|
$h = $font['FontBBox'][3] - $font['FontBBox'][1]; |
2606
|
|
|
return $size*$h/1000; |
2607
|
|
|
} |
2608
|
|
|
|
2609
|
|
|
/** |
2610
|
|
|
* return the font decender, this will normally return a negative number |
2611
|
|
|
* if you add this number to the baseline, you get the level of the bottom of the font |
2612
|
|
|
* it is in the pdf user units |
2613
|
|
|
* @access public |
2614
|
|
|
*/ |
2615
|
|
|
public function getFontDecender($size) |
2616
|
|
|
{ |
2617
|
|
|
// note that this will most likely return a negative value |
2618
|
|
|
if (!$this->numFonts) { |
2619
|
|
|
$this->selectFont('./fonts/Helvetica'); |
2620
|
|
|
} |
2621
|
|
|
$h = $this->fonts[$this->currentFont]['Descender']; |
2622
|
|
|
return $size*$h/1000; |
2623
|
|
|
} |
2624
|
|
|
|
2625
|
|
|
/** |
2626
|
|
|
* filter the text, this is applied to all text just before being inserted into the pdf document |
2627
|
|
|
* it escapes the various things that need to be escaped, and so on |
2628
|
|
|
* |
2629
|
|
|
* @access protected |
2630
|
|
|
*/ |
2631
|
|
|
protected function filterText($text, $bom = true, $convert_encoding = true) |
2632
|
|
|
{ |
2633
|
|
|
|
2634
|
|
|
if ($convert_encoding) { |
2635
|
|
|
$cf = $this->currentFont; |
2636
|
|
|
if (isset($this->fonts[$cf]) && $this->fonts[$cf]['isUnicode']) { |
2637
|
|
|
$text = $this->utf8toUtf16BE($text, $bom); // convert utf8 chars into utf16 |
2638
|
|
|
// store all used characters if subset font is set to true |
2639
|
|
View Code Duplication |
if ($this->fonts[$cf]['isSubset']) { |
2640
|
|
|
for ($i = 0; $i < mb_strlen($text, 'UTF-16BE'); $i++) { |
2641
|
|
|
$this->fonts[$cf]['subset'][mb_substr($text, $i, 1, 'UTF-16BE')] = true; |
2642
|
|
|
} |
2643
|
|
|
} |
2644
|
|
|
} else { |
2645
|
|
|
$text = mb_convert_encoding($text, $this->targetEncoding); |
2646
|
|
|
$hexStr = $this->strToHex($text); |
2647
|
|
|
// store all used characters if subset font is set to true |
2648
|
|
View Code Duplication |
if ($this->fonts[$cf]['isSubset']) { |
2649
|
|
|
for ($i = 0; $i < (strlen($hexStr) / 2); $i++) { |
2650
|
|
|
array_push($this->fonts[$cf]['subset'], substr($hexStr, ($i * 2), 2)); |
2651
|
|
|
} |
2652
|
|
|
} |
2653
|
|
|
} |
2654
|
|
|
} |
2655
|
|
|
$text = strtr($text, array(')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(8) => '\\b', chr(9) => '\\t', chr(10) => '\\n', chr(12) => '\\f' ,chr(13) => '\\r')); |
2656
|
|
|
|
2657
|
|
|
if ($this->rtl) { |
2658
|
|
|
$text = strrev($text); |
2659
|
|
|
} |
2660
|
|
|
|
2661
|
|
|
return $text; |
2662
|
|
|
} |
2663
|
|
|
|
2664
|
|
|
/** |
2665
|
|
|
* return array containing codepoints (UTF-8 character values) for the |
2666
|
|
|
* string passed in. |
2667
|
|
|
* |
2668
|
|
|
* based on the excellent TCPDF code by Nicola Asuni and the |
2669
|
|
|
* RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html |
2670
|
|
|
* |
2671
|
|
|
* @access private |
2672
|
|
|
* @author Orion Richardson |
2673
|
|
|
* @since January 5, 2008 |
2674
|
|
|
* @param string $text UTF-8 string to process |
2675
|
|
|
* @return array UTF-8 codepoints array for the string |
2676
|
|
|
*/ |
2677
|
|
|
private function utf8toCodePointsArray(&$text) |
2678
|
|
|
{ |
2679
|
|
|
$length = mb_strlen($text, '8bit'); // http://www.php.net/manual/en/function.mb-strlen.php#77040 |
2680
|
|
|
$unicode = array(); // array containing unicode values |
2681
|
|
|
$bytes = array(); // array containing single character byte sequences |
2682
|
|
|
$numbytes = 1; // number of octetc needed to represent the UTF-8 character |
2683
|
|
|
|
2684
|
|
|
for ($i = 0; $i < $length; $i++) { |
2685
|
|
|
$c = ord($text[$i]); // get one string character at time |
2686
|
|
|
if (count($bytes) === 0) { // get starting octect |
2687
|
|
|
if ($c <= 0x7F) { |
2688
|
|
|
$unicode[] = $c; // use the character "as is" because is ASCII |
2689
|
|
|
$numbytes = 1; |
2690
|
|
View Code Duplication |
} elseif (($c >> 0x05) === 0x06) { // 2 bytes character (0x06 = 110 BIN) |
2691
|
|
|
$bytes[] = ($c - 0xC0) << 0x06; |
2692
|
|
|
$numbytes = 2; |
2693
|
|
|
} elseif (($c >> 0x04) === 0x0E) { // 3 bytes character (0x0E = 1110 BIN) |
2694
|
|
|
$bytes[] = ($c - 0xE0) << 0x0C; |
2695
|
|
|
$numbytes = 3; |
2696
|
|
View Code Duplication |
} elseif (($c >> 0x03) === 0x1E) { // 4 bytes character (0x1E = 11110 BIN) |
2697
|
|
|
$bytes[] = ($c - 0xF0) << 0x12; |
2698
|
|
|
$numbytes = 4; |
2699
|
|
|
} else { |
2700
|
|
|
// use replacement character for other invalid sequences |
2701
|
|
|
$unicode[] = 0xFFFD; |
2702
|
|
|
$bytes = array(); |
2703
|
|
|
$numbytes = 1; |
2704
|
|
|
} |
2705
|
|
|
} elseif (($c >> 0x06) === 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN |
2706
|
|
|
$bytes[] = $c - 0x80; |
2707
|
|
|
if (count($bytes) === $numbytes) { |
2708
|
|
|
// compose UTF-8 bytes to a single unicode value |
2709
|
|
|
$c = $bytes[0]; |
2710
|
|
|
for ($j = 1; $j < $numbytes; $j++) { |
2711
|
|
|
$c += ($bytes[$j] << (($numbytes - $j - 1) * 0x06)); |
2712
|
|
|
} |
2713
|
|
|
if ((($c >= 0xD800) and ($c <= 0xDFFF)) or ($c >= 0x10FFFF)) { |
|
|
|
|
2714
|
|
|
// The definition of UTF-8 prohibits encoding character numbers between |
2715
|
|
|
// U+D800 and U+DFFF, which are reserved for use with the UTF-16 |
2716
|
|
|
// encoding form (as surrogate pairs) and do not directly represent |
2717
|
|
|
// characters. |
2718
|
|
|
$unicode[] = 0xFFFD; // use replacement character |
2719
|
|
|
} else { |
2720
|
|
|
$unicode[] = $c; // add char to array |
2721
|
|
|
} |
2722
|
|
|
// reset data for next char |
2723
|
|
|
$bytes = array(); |
2724
|
|
|
$numbytes = 1; |
2725
|
|
|
} |
2726
|
|
|
} else { |
2727
|
|
|
// use replacement character for other invalid sequences |
2728
|
|
|
$unicode[] = 0xFFFD; |
2729
|
|
|
$bytes = array(); |
2730
|
|
|
$numbytes = 1; |
2731
|
|
|
} |
2732
|
|
|
} |
2733
|
|
|
return $unicode; |
2734
|
|
|
} |
2735
|
|
|
|
2736
|
|
|
/** |
2737
|
|
|
* convert UTF-8 to UTF-16 with an additional byte order marker |
2738
|
|
|
* at the front if required. |
2739
|
|
|
* |
2740
|
|
|
* based on the excellent TCPDF code by Nicola Asuni and the |
2741
|
|
|
* RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html |
2742
|
|
|
* |
2743
|
|
|
* @access private |
2744
|
|
|
* @author Orion Richardson |
2745
|
|
|
* @since January 5, 2008 |
2746
|
|
|
* @param string $text UTF-8 string to process |
2747
|
|
|
* @param boolean $bom whether to add the byte order marker |
2748
|
|
|
* @return string UTF-16 result string |
2749
|
|
|
*/ |
2750
|
|
|
private function utf8toUtf16BE(&$text, $bom = true) |
2751
|
|
|
{ |
2752
|
|
|
$cf = $this->currentFont; |
2753
|
|
|
if (!$this->fonts[$cf]['isUnicode']) { |
2754
|
|
|
return $text; |
2755
|
|
|
} |
2756
|
|
|
$out = $bom ? "\xFE\xFF" : ''; |
2757
|
|
|
$unicode = $this->utf8toCodePointsArray($text); |
2758
|
|
|
foreach ($unicode as $c) { |
2759
|
|
|
if ($c === 0xFFFD) { |
2760
|
|
|
$out .= "\xFF\xFD"; // replacement character |
2761
|
|
|
} elseif ($c < 0x10000) { |
2762
|
|
|
$out .= chr($c >> 0x08) . chr($c & 0xFF); |
2763
|
|
|
} else { |
2764
|
|
|
$c -= 0x10000; |
2765
|
|
|
$w1 = 0xD800 | ($c >> 0x10); |
2766
|
|
|
$w2 = 0xDC00 | ($c & 0x3FF); |
2767
|
|
|
$out .= chr($w1 >> 0x08) . chr($w1 & 0xFF) . chr($w2 >> 0x08) . chr($w2 & 0xFF); |
2768
|
|
|
} |
2769
|
|
|
} |
2770
|
|
|
return $out; |
2771
|
|
|
} |
2772
|
|
|
|
2773
|
|
|
/** |
2774
|
|
|
* given a start position and information about how text is to be laid out, calculate where |
2775
|
|
|
* on the page the text will end |
2776
|
|
|
* |
2777
|
|
|
* @access protected |
2778
|
|
|
*/ |
2779
|
|
|
protected function getTextPosition($x, $y, $angle, $size, $wa, $text) |
2780
|
|
|
{ |
2781
|
|
|
// given this information return an array containing x and y for the end position as elements 0 and 1 |
2782
|
|
|
$w = $this->getTextWidth($size, $text); |
2783
|
|
|
// need to adjust for the number of spaces in this text |
2784
|
|
|
$words = explode(' ', $text); |
2785
|
|
|
$nspaces=count($words)-1; |
2786
|
|
|
$w += $wa*$nspaces; |
2787
|
|
|
$a = deg2rad((float)$angle); |
2788
|
|
|
return array(cos($a)*$w+$x,-sin($a)*$w+$y); |
2789
|
|
|
} |
2790
|
|
|
|
2791
|
|
|
/** |
2792
|
|
|
* wrapper function for checkTextDirective1 |
2793
|
|
|
* |
2794
|
|
|
* @access private |
2795
|
|
|
*/ |
2796
|
|
|
private function checkTextDirective(&$text, $i, &$f) |
2797
|
|
|
{ |
2798
|
|
|
$x=0; |
2799
|
|
|
$y=0; |
2800
|
|
|
return $this->checkTextDirective1($text, $i, $f, 0, $x, $y); |
2801
|
|
|
} |
2802
|
|
|
|
2803
|
|
|
/** |
2804
|
|
|
* checks if the text stream contains a control directive |
2805
|
|
|
* if so then makes some changes and returns the number of characters involved in the directive |
2806
|
|
|
* this has been re-worked to include everything neccesary to fins the current writing point, so that |
2807
|
|
|
* the location can be sent to the callback function if required |
2808
|
|
|
* if the directive does not require a font change, then $f should be set to 0 |
2809
|
|
|
* |
2810
|
|
|
* @access private |
2811
|
|
|
*/ |
2812
|
|
|
private function checkTextDirective1(&$text, $i, &$f, $final, &$x, &$y, $size = 0, $angle = 0, $wordSpaceAdjust = 0) |
2813
|
|
|
{ |
2814
|
|
|
$directive = 0; |
2815
|
|
|
$j=$i; |
2816
|
|
|
if ($text[$j]=='<') { |
2817
|
|
|
$j++; |
2818
|
|
|
switch ($text[$j]) { |
2819
|
|
|
case '/': |
2820
|
|
|
$j++; |
2821
|
|
|
if (strlen($text) <= $j) { |
2822
|
|
|
return $directive; |
2823
|
|
|
} |
2824
|
|
|
switch ($text[$j]) { |
2825
|
|
|
case 'b': |
2826
|
|
|
case 'i': |
2827
|
|
|
$j++; |
2828
|
|
|
if ($text[$j]=='>') { |
2829
|
|
|
$p = strrpos($this->currentTextState, $text[$j-1]); |
2830
|
|
|
if ($p !== false) { |
2831
|
|
|
// then there is one to remove |
2832
|
|
|
$this->currentTextState = substr($this->currentTextState, 0, $p).substr($this->currentTextState, $p+1); |
2833
|
|
|
} |
2834
|
|
|
$directive=$j-$i+1; |
2835
|
|
|
} |
2836
|
|
|
break; |
2837
|
|
|
case 'c': |
2838
|
|
|
// this this might be a callback function |
2839
|
|
|
$j++; |
2840
|
|
|
$k = strpos($text, '>', $j); |
2841
|
|
|
if ($k!==false && $text[$j]==':') { |
2842
|
|
|
// then this will be treated as a callback directive |
2843
|
|
|
$directive = $k-$i+1; |
2844
|
|
|
$f=0; |
2845
|
|
|
// split the remainder on colons to get the function name and the paramater |
2846
|
|
|
$tmp = substr($text, $j+1, $k-$j-1); |
2847
|
|
|
$b1 = strpos($tmp, ':'); |
2848
|
|
View Code Duplication |
if ($b1!==false) { |
2849
|
|
|
$func = substr($tmp, 0, $b1); |
2850
|
|
|
$parm = substr($tmp, $b1+1); |
2851
|
|
|
} else { |
2852
|
|
|
$func=$tmp; |
2853
|
|
|
$parm=''; |
2854
|
|
|
} |
2855
|
|
|
if (!isset($func) || !strlen(trim($func))) { |
2856
|
|
|
$directive=0; |
2857
|
|
|
} else { |
2858
|
|
|
// only call the function if this is the final call |
2859
|
|
|
if ($final) { |
2860
|
|
|
// need to assess the text position, calculate the text width to this point |
2861
|
|
|
// can use getTextWidth to find the text width I think |
2862
|
|
|
$tmp = $this->getTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, substr($text, 0, $i)); |
2863
|
|
|
$info = array('x'=>$tmp[0],'y'=>$tmp[1],'angle'=>$angle,'status'=>'end','p'=>$parm,'nCallback'=>$this->nCallback); |
2864
|
|
|
$x=$tmp[0]; |
2865
|
|
|
$y=$tmp[1]; |
2866
|
|
|
$ret = $this->$func($info); |
2867
|
|
View Code Duplication |
if (is_array($ret)) { |
2868
|
|
|
// then the return from the callback function could set the position, to start with, later will do font colour, and font |
2869
|
|
|
foreach ($ret as $rk => $rv) { |
2870
|
|
|
switch ($rk) { |
2871
|
|
|
case 'x': |
2872
|
|
|
case 'y': |
2873
|
|
|
$$rk=$rv; |
2874
|
|
|
break; |
2875
|
|
|
} |
2876
|
|
|
} |
2877
|
|
|
} |
2878
|
|
|
// also remove from to the stack |
2879
|
|
|
// for simplicity, just take from the end, fix this another day |
2880
|
|
|
$this->nCallback--; |
2881
|
|
|
if ($this->nCallback<0) { |
2882
|
|
|
$this->nCallBack=0; |
|
|
|
|
2883
|
|
|
} |
2884
|
|
|
} |
2885
|
|
|
} |
2886
|
|
|
} |
2887
|
|
|
break; |
2888
|
|
|
} |
2889
|
|
|
break; |
2890
|
|
|
case 'b': |
2891
|
|
|
case 'i': |
2892
|
|
|
$j++; |
2893
|
|
|
if ($text[$j]=='>') { |
2894
|
|
|
$this->currentTextState.=$text[$j-1]; |
2895
|
|
|
$directive=$j-$i+1; |
2896
|
|
|
} |
2897
|
|
|
break; |
2898
|
|
|
case 'C': |
2899
|
|
|
$noClose=1; |
2900
|
|
|
case 'c': |
2901
|
|
|
// this this might be a callback function |
2902
|
|
|
$j++; |
2903
|
|
|
$k = strpos($text, '>', $j); |
2904
|
|
|
if ($k!==false && $text[$j]==':') { |
2905
|
|
|
// then this will be treated as a callback directive |
2906
|
|
|
$directive = $k-$i+1; |
2907
|
|
|
$f=0; |
2908
|
|
|
// split the remainder on colons to get the function name and the paramater |
2909
|
|
|
// $bits = explode(':',substr($text,$j+1,$k-$j-1)); |
|
|
|
|
2910
|
|
|
$tmp = substr($text, $j+1, $k-$j-1); |
2911
|
|
|
$b1 = strpos($tmp, ':'); |
2912
|
|
View Code Duplication |
if ($b1!==false) { |
2913
|
|
|
$func = substr($tmp, 0, $b1); |
2914
|
|
|
$parm = substr($tmp, $b1+1); |
2915
|
|
|
} else { |
2916
|
|
|
$func=$tmp; |
2917
|
|
|
$parm=''; |
2918
|
|
|
} |
2919
|
|
|
if (!isset($func) || !strlen(trim($func))) { |
2920
|
|
|
$directive=0; |
2921
|
|
|
} else { |
2922
|
|
|
// only call the function if this is the final call, ie, the one actually doing printing, not measurement |
2923
|
|
|
if ($final) { |
2924
|
|
|
// need to assess the text position, calculate the text width to this point |
2925
|
|
|
// can use getTextWidth to find the text width I think |
2926
|
|
|
// also add the text height and decender |
2927
|
|
|
$tmp = $this->getTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, substr($text, 0, $i)); |
2928
|
|
|
$info = array('x'=>$tmp[0],'y'=>$tmp[1],'angle'=>$angle,'status'=>'start','p'=>$parm,'f'=>$func,'height'=>$this->getFontHeight($size),'decender'=>$this->getFontDecender($size)); |
2929
|
|
|
$x=$tmp[0]; |
2930
|
|
|
$y=$tmp[1]; |
2931
|
|
|
if (!isset($noClose) || !$noClose) { |
2932
|
|
|
// only add to the stack if this is a small 'c', therefore is a start-stop pair |
2933
|
|
|
$this->nCallback++; |
2934
|
|
|
$info['nCallback']=$this->nCallback; |
2935
|
|
|
$this->callback[$this->nCallback]=$info; |
2936
|
|
|
} |
2937
|
|
|
$ret = $this->$func($info); |
2938
|
|
View Code Duplication |
if (is_array($ret)) { |
2939
|
|
|
// then the return from the callback function could set the position, to start with, later will do font colour, and font |
2940
|
|
|
foreach ($ret as $rk => $rv) { |
2941
|
|
|
switch ($rk) { |
2942
|
|
|
case 'x': |
2943
|
|
|
case 'y': |
2944
|
|
|
$$rk=$rv; |
2945
|
|
|
break; |
2946
|
|
|
} |
2947
|
|
|
} |
2948
|
|
|
} |
2949
|
|
|
} |
2950
|
|
|
} |
2951
|
|
|
} |
2952
|
|
|
break; |
2953
|
|
|
} |
2954
|
|
|
} |
2955
|
|
|
return $directive; |
2956
|
|
|
} |
2957
|
|
|
|
2958
|
|
|
/** |
2959
|
|
|
* add text to the document, at a specified location, size and angle on the page |
2960
|
|
|
* @access public |
2961
|
|
|
*/ |
2962
|
|
|
public function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0) |
2963
|
|
|
{ |
2964
|
|
|
if (!$this->numFonts) { |
2965
|
|
|
$this->selectFont(dirname(__FILE__) . '/fonts/Helvetica'); |
2966
|
|
|
} |
2967
|
|
|
// if there are any open callbacks, then they should be called, to show the start of the line |
2968
|
|
|
if ($this->nCallback > 0) { |
2969
|
|
|
for ($i = $this->nCallback; $i > 0; $i--) { |
2970
|
|
|
// call each function |
2971
|
|
|
$info = array('x'=>$x,'y'=>$y,'angle'=>$angle,'status'=>'sol','p'=>$this->callback[$i]['p'],'nCallback'=>$this->callback[$i]['nCallback'],'height'=>$this->callback[$i]['height'],'decender'=>$this->callback[$i]['decender']); |
2972
|
|
|
$func = $this->callback[$i]['f']; |
2973
|
|
|
$this->$func($info); |
2974
|
|
|
} |
2975
|
|
|
} |
2976
|
|
|
if ($angle == 0) { |
2977
|
|
|
$this->addContent(sprintf("\nBT %.3F %.3F Td", $x, $y)); |
2978
|
|
|
} else { |
2979
|
|
|
$a = deg2rad((float)$angle); |
2980
|
|
|
$this->addContent(sprintf("\nBT %.3F %.3F %.3F %.3F %.3F %.3F Tm", cos($a), -sin($a), sin($a), cos($a), $x, $y)); |
2981
|
|
|
} |
2982
|
|
|
|
2983
|
|
View Code Duplication |
if ($wordSpaceAdjust != 0 || $wordSpaceAdjust != $this->wordSpaceAdjust) { |
2984
|
|
|
$this->wordSpaceAdjust = $wordSpaceAdjust; |
2985
|
|
|
$this->addContent(sprintf(" %.3F Tw", $wordSpaceAdjust)); |
2986
|
|
|
} |
2987
|
|
|
|
2988
|
|
|
$len = strlen($text); |
2989
|
|
|
$start=0; |
2990
|
|
|
for ($i=0; $i<$len; $i++) { |
2991
|
|
|
$f=1; |
2992
|
|
|
$directive = $this->checkTextDirective($text, $i, $f); |
2993
|
|
|
if ($directive) { |
2994
|
|
|
// then we should write what we need to |
2995
|
|
|
if ($i>$start) { |
2996
|
|
|
$part = substr($text, $start, $i-$start); |
2997
|
|
|
$this->addContent(' /F'.$this->currentFontNum.' '.sprintf('%.1f', $size).' Tf '); |
2998
|
|
|
$this->addContent(' ('.$this->filterText($part, false).') Tj'); |
2999
|
|
|
} |
3000
|
|
|
if ($f) { |
3001
|
|
|
// then there was nothing drastic done here, restore the contents |
3002
|
|
|
$this->setCurrentFont(); |
3003
|
|
|
} else { |
3004
|
|
|
$this->addContent(' ET'); |
3005
|
|
|
$f=1; |
3006
|
|
|
$xp=$x; |
3007
|
|
|
$yp=$y; |
3008
|
|
|
$directive = $this->checkTextDirective1($text, $i, $f, 1, $xp, $yp, $size, $angle, $wordSpaceAdjust); |
3009
|
|
|
|
3010
|
|
|
// restart the text object |
3011
|
|
|
if ($angle==0) { |
3012
|
|
|
$this->addContent("\n".'BT '.sprintf('%.3F', $xp).' '.sprintf('%.3F', $yp).' Td'); |
3013
|
|
|
} else { |
3014
|
|
|
$a = deg2rad((float)$angle); |
3015
|
|
|
$tmp = "\n".'BT '; |
3016
|
|
|
$tmp .= sprintf('%.3F', cos($a)).' '.sprintf('%.3F', (-1.0*sin($a))).' '.sprintf('%.3F', sin($a)).' '.sprintf('%.3F', cos($a)).' '; |
3017
|
|
|
$tmp .= sprintf('%.3F', $xp).' '.sprintf('%.3F', $yp).' Tm'; |
3018
|
|
|
$this->addContent($tmp); |
3019
|
|
|
} |
3020
|
|
View Code Duplication |
if ($wordSpaceAdjust!=0 || $wordSpaceAdjust != $this->wordSpaceAdjust) { |
3021
|
|
|
$this->wordSpaceAdjust=$wordSpaceAdjust; |
3022
|
|
|
$this->addContent(' '.sprintf('%.3F', $wordSpaceAdjust).' Tw'); |
3023
|
|
|
} |
3024
|
|
|
} |
3025
|
|
|
// and move the writing point to the next piece of text |
3026
|
|
|
$i=$i+$directive-1; |
3027
|
|
|
$start=$i+1; |
3028
|
|
|
} |
3029
|
|
|
} |
3030
|
|
|
|
3031
|
|
|
if ($start < $len) { |
3032
|
|
|
$part = substr($text, $start); |
3033
|
|
|
$place_text = $this->filterText($part, false); |
3034
|
|
|
// modify unicode text so that extra word spacing is manually implemented (bug #) |
3035
|
|
|
$cf = $this->currentFont; |
3036
|
|
|
if ($this->fonts[$cf]['isUnicode'] && $wordSpaceAdjust != 0) { |
3037
|
|
|
$space_scale = 1000 / $size; |
3038
|
|
|
$place_text = str_replace(' ', ' ) '.(-round($space_scale*$wordSpaceAdjust)).' (', $place_text); |
3039
|
|
|
} |
3040
|
|
|
$this->addContent(" /F$this->currentFontNum ".sprintf('%.1F Tf ', $size)); |
3041
|
|
|
$this->addContent(" [($place_text)] TJ"); |
3042
|
|
|
} |
3043
|
|
|
|
3044
|
|
|
$this->addContent(' ET'); |
3045
|
|
|
|
3046
|
|
|
// if there are any open callbacks, then they should be called, to show the end of the line |
3047
|
|
|
if ($this->nCallback > 0) { |
3048
|
|
|
for ($i = $this->nCallback; $i > 0; $i--) { |
3049
|
|
|
// call each function |
3050
|
|
|
$tmp = $this->getTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, $text); |
3051
|
|
|
$info = array( |
3052
|
|
|
'x' => $tmp[0], |
3053
|
|
|
'y' => $tmp[1], |
3054
|
|
|
'angle' => $angle, |
3055
|
|
|
'status' => 'eol', |
3056
|
|
|
'p' => $this->callback[$i]['p'], |
3057
|
|
|
'nCallback' => $this->callback[$i]['nCallback'], |
3058
|
|
|
'height' => $this->callback[$i]['height'], |
3059
|
|
|
'descender' => $this->callback[$i]['descender'] |
3060
|
|
|
); |
3061
|
|
|
$func = $this->callback[$i]['f']; |
3062
|
|
|
$this->$func($info); |
3063
|
|
|
} |
3064
|
|
|
} |
3065
|
|
|
} |
3066
|
|
|
|
3067
|
|
|
/** |
3068
|
|
|
* calculate how wide a given text string will be on a page, at a given size. |
3069
|
|
|
* this can be called externally, but is alse used by the other class functions |
3070
|
|
|
* @access public |
3071
|
|
|
*/ |
3072
|
|
|
public function getTextWidth($size, $text) |
3073
|
|
|
{ |
3074
|
|
|
// this function should not change any of the settings, though it will need to |
3075
|
|
|
// track any directives which change during calculation, so copy them at the start |
3076
|
|
|
// and put them back at the end. |
3077
|
|
|
$store_currentTextState = $this->currentTextState; |
3078
|
|
|
|
3079
|
|
|
if (!$this->numFonts) { |
3080
|
|
|
$this->selectFont('./fonts/Helvetica'); |
3081
|
|
|
} |
3082
|
|
|
|
3083
|
|
|
// converts a number or a float to a string so it can get the width |
3084
|
|
|
$text = "$text"; |
3085
|
|
|
|
3086
|
|
|
// hmm, this is where it all starts to get tricky - use the font information to |
3087
|
|
|
// calculate the width of each character, add them up and convert to user units |
3088
|
|
|
$w=0; |
3089
|
|
|
$len = strlen($text); |
|
|
|
|
3090
|
|
|
$cf = $this->currentFont; |
3091
|
|
|
// font is set to unicode mode |
3092
|
|
|
$unicode = $this->fonts[$cf]['isUnicode']; |
3093
|
|
|
|
3094
|
|
|
if ($unicode) { |
3095
|
|
|
$out = $this->utf8toUtf16BE($text); |
3096
|
|
|
} |
3097
|
|
|
|
3098
|
|
|
for ($i=0; $i< strlen($text); $i++) { |
3099
|
|
|
$f=1; |
3100
|
|
|
$directive = $this->checkTextDirective($text, $i, $f); |
3101
|
|
|
if ($directive) { |
3102
|
|
|
if ($f) { |
3103
|
|
|
$this->setCurrentFont(); |
3104
|
|
|
$cf = $this->currentFont; |
3105
|
|
|
} |
3106
|
|
|
$i=$i+$directive-1; |
3107
|
|
|
} else { |
3108
|
|
|
if ($unicode) { |
3109
|
|
|
// use the previous converted text (utf8toUtf16BE) and get the correct character index by using below workaround |
3110
|
|
|
$char = hexdec(bin2hex(mb_substr($out, $i + 1, 1, 'UTF-16BE'))); |
|
|
|
|
3111
|
|
|
} else { |
3112
|
|
|
// normal ANSI text characters |
3113
|
|
|
$char = ord($text[$i]); |
3114
|
|
|
} |
3115
|
|
|
if (isset($this->fonts[$cf]['differences'][$char])) { |
3116
|
|
|
// then this character is being replaced by another |
3117
|
|
|
$name = $this->fonts[$cf]['differences'][$char]; |
3118
|
|
View Code Duplication |
if (isset($this->fonts[$cf]['C'][$name])) { |
3119
|
|
|
$w+=$this->fonts[$cf]['C'][$name]; |
3120
|
|
|
} |
3121
|
|
View Code Duplication |
} elseif (isset($this->fonts[$cf]['C'][$char])) { |
3122
|
|
|
$w+=$this->fonts[$cf]['C'][$char]; |
3123
|
|
|
} |
3124
|
|
|
} |
3125
|
|
|
} |
3126
|
|
|
$this->currentTextState = $store_currentTextState; |
3127
|
|
|
$this->setCurrentFont(); |
3128
|
|
|
return $w*$size/1000; |
3129
|
|
|
} |
3130
|
|
|
|
3131
|
|
|
/** |
3132
|
|
|
* do a part of the calculation for sorting out the justification of the text |
3133
|
|
|
* |
3134
|
|
|
* @access private |
3135
|
|
|
*/ |
3136
|
|
|
private function adjustWrapText($text, $actual, $width, &$x, &$adjust, $justification) |
3137
|
|
|
{ |
3138
|
|
|
switch ($justification) { |
3139
|
|
|
case 'left': |
3140
|
|
|
return; |
3141
|
|
|
break; |
|
|
|
|
3142
|
|
|
case 'right': |
3143
|
|
|
$x+=$width-$actual; |
3144
|
|
|
break; |
3145
|
|
|
case 'center': |
3146
|
|
|
case 'centre': |
3147
|
|
|
$x+=($width-$actual)/2; |
3148
|
|
|
break; |
3149
|
|
|
case 'full': |
3150
|
|
|
// count the number of words |
3151
|
|
|
$words = explode(' ', $text); |
3152
|
|
|
$nspaces=count($words)-1; |
3153
|
|
|
if ($nspaces>0) { |
3154
|
|
|
$adjust = ($width-$actual)/$nspaces; |
3155
|
|
|
} else { |
3156
|
|
|
$adjust=0; |
3157
|
|
|
} |
3158
|
|
|
break; |
3159
|
|
|
} |
3160
|
|
|
} |
3161
|
|
|
|
3162
|
|
|
/** |
3163
|
|
|
* add text to the page, but ensure that it fits within a certain width |
3164
|
|
|
* if it does not fit then put in as much as possible, splitting at word boundaries |
3165
|
|
|
* and return the remainder. |
3166
|
|
|
* justification and angle can also be specified for the text |
3167
|
|
|
* @access public |
3168
|
|
|
*/ |
3169
|
|
|
public function addTextWrap($x, $y, $width, $size, $text, $justification = 'left', $angle = 0, $test = 0) |
3170
|
|
|
{ |
3171
|
|
|
// this will display the text, and if it goes beyond the width $width, will backtrack to the |
3172
|
|
|
// previous space or hyphen, and return the remainder of the text. |
3173
|
|
|
|
3174
|
|
|
// $justification can be set to 'left','right','center','centre','full' |
|
|
|
|
3175
|
|
|
|
3176
|
|
|
// need to store the initial text state, as this will change during the width calculation |
3177
|
|
|
// but will need to be re-set before printing, so that the chars work out right |
3178
|
|
|
$store_currentTextState = $this->currentTextState; |
3179
|
|
|
|
3180
|
|
|
if (!$this->numFonts) { |
3181
|
|
|
$this->selectFont(dirname(__FILE__) . '/fonts/Helvetica'); |
3182
|
|
|
} |
3183
|
|
|
if ($width<=0) { |
3184
|
|
|
// error, pretend it printed ok, otherwise risking a loop |
3185
|
|
|
return ''; |
3186
|
|
|
} |
3187
|
|
|
$w=0; |
3188
|
|
|
$break=0; |
3189
|
|
|
$breakWidth=0; |
3190
|
|
|
$len=strlen($text); |
3191
|
|
|
$cf = $this->currentFont; |
3192
|
|
|
$tw = $width/$size*1000; |
3193
|
|
|
for ($i=0; $i<$len; $i++) { |
3194
|
|
|
$f=1; |
3195
|
|
|
$directive = $this->checkTextDirective($text, $i, $f); |
3196
|
|
|
if ($directive) { |
3197
|
|
|
if ($f) { |
3198
|
|
|
$this->setCurrentFont(); |
3199
|
|
|
$cf = $this->currentFont; |
3200
|
|
|
} |
3201
|
|
|
$i=$i+$directive-1; |
3202
|
|
|
} else { |
3203
|
|
|
$cOrd = ord($text[$i]); |
3204
|
|
View Code Duplication |
if (isset($this->fonts[$cf]['differences'][$cOrd])) { |
3205
|
|
|
// then this character is being replaced by another |
3206
|
|
|
$cOrd2 = $this->fonts[$cf]['differences'][$cOrd]; |
3207
|
|
|
} else { |
3208
|
|
|
$cOrd2 = $cOrd; |
3209
|
|
|
} |
3210
|
|
|
|
3211
|
|
View Code Duplication |
if (isset($this->fonts[$cf]['C'][$cOrd2])) { |
3212
|
|
|
$w+=$this->fonts[$cf]['C'][$cOrd2]; |
3213
|
|
|
} |
3214
|
|
|
if ($w>$tw) { |
3215
|
|
|
// then we need to truncate this line |
3216
|
|
|
if ($break>0) { |
3217
|
|
|
// then we have somewhere that we can split :) |
3218
|
|
|
if ($text[$break]==' ') { |
3219
|
|
|
$tmp = substr($text, 0, $break); |
3220
|
|
|
} else { |
3221
|
|
|
$tmp = substr($text, 0, $break+1); |
3222
|
|
|
} |
3223
|
|
|
$adjust=0; |
3224
|
|
|
$this->adjustWrapText($tmp, $breakWidth, $width, $x, $adjust, $justification); |
3225
|
|
|
|
3226
|
|
|
// reset the text state |
3227
|
|
|
$this->currentTextState = $store_currentTextState; |
3228
|
|
|
$this->setCurrentFont(); |
3229
|
|
|
if (!$test) { |
3230
|
|
|
$this->addText($x, $y, $size, $tmp, $angle, $adjust); |
3231
|
|
|
} |
3232
|
|
|
return substr($text, $break+1); |
3233
|
|
|
} else { |
3234
|
|
|
// just split before the current character |
3235
|
|
|
$tmp = substr($text, 0, $i); |
3236
|
|
|
$adjust=0; |
3237
|
|
|
$ctmp=ord($text[$i]); |
3238
|
|
View Code Duplication |
if (isset($this->fonts[$cf]['differences'][$ctmp])) { |
3239
|
|
|
$ctmp=$this->fonts[$cf]['differences'][$ctmp]; |
3240
|
|
|
} |
3241
|
|
|
$tmpw=($w-$this->fonts[$cf]['C'][$ctmp])*$size/1000; |
3242
|
|
|
$this->adjustWrapText($tmp, $tmpw, $width, $x, $adjust, $justification); |
3243
|
|
|
// reset the text state |
3244
|
|
|
$this->currentTextState = $store_currentTextState; |
3245
|
|
|
$this->setCurrentFont(); |
3246
|
|
|
if (!$test) { |
3247
|
|
|
$this->addText($x, $y, $size, $tmp, $angle, $adjust); |
3248
|
|
|
} |
3249
|
|
|
return substr($text, $i); |
3250
|
|
|
} |
3251
|
|
|
} |
3252
|
|
|
if ($text[$i]=='-') { |
3253
|
|
|
$break=$i; |
3254
|
|
|
$breakWidth = $w*$size/1000; |
3255
|
|
|
} |
3256
|
|
|
if ($text[$i]==' ') { |
3257
|
|
|
$break=$i; |
3258
|
|
|
$ctmp=ord($text[$i]); |
3259
|
|
View Code Duplication |
if (isset($this->fonts[$cf]['differences'][$ctmp])) { |
3260
|
|
|
$ctmp=$this->fonts[$cf]['differences'][$ctmp]; |
3261
|
|
|
} |
3262
|
|
|
$breakWidth = ($w-$this->fonts[$cf]['C'][$ctmp])*$size/1000; |
3263
|
|
|
} |
3264
|
|
|
} |
3265
|
|
|
} |
3266
|
|
|
// then there was no need to break this line |
3267
|
|
|
if ($justification=='full') { |
3268
|
|
|
$justification='left'; |
3269
|
|
|
} |
3270
|
|
|
$adjust=0; |
3271
|
|
|
$tmpw=$w*$size/1000; |
3272
|
|
|
$this->adjustWrapText($text, $tmpw, $width, $x, $adjust, $justification); |
3273
|
|
|
// reset the text state |
3274
|
|
|
$this->currentTextState = $store_currentTextState; |
3275
|
|
|
$this->setCurrentFont(); |
3276
|
|
|
if (!$test) { |
3277
|
|
|
$this->addText($x, $y, $size, $text, $angle, $adjust, $angle); |
|
|
|
|
3278
|
|
|
} |
3279
|
|
|
return ''; |
3280
|
|
|
} |
3281
|
|
|
|
3282
|
|
|
/** |
3283
|
|
|
* this will be called at a new page to return the state to what it was on the |
3284
|
|
|
* end of the previous page, before the stack was closed down |
3285
|
|
|
* This is to get around not being able to have open 'q' across pages |
3286
|
|
|
* @access public |
3287
|
|
|
*/ |
3288
|
|
|
public function saveState($pageEnd = 0) |
3289
|
|
|
{ |
3290
|
|
|
if ($pageEnd) { |
3291
|
|
|
// this will be called at a new page to return the state to what it was on the |
3292
|
|
|
// end of the previous page, before the stack was closed down |
3293
|
|
|
// This is to get around not being able to have open 'q' across pages |
3294
|
|
|
$opt = $this->stateStack[$pageEnd]; // ok to use this as stack starts numbering at 1 |
3295
|
|
|
$this->setColor($opt['col']['r'], $opt['col']['g'], $opt['col']['b'], 1); |
3296
|
|
|
$this->setStrokeColor($opt['str']['r'], $opt['str']['g'], $opt['str']['b'], 1); |
3297
|
|
|
$this->objects[$this->currentContents]['c'].="\n".$opt['lin']; |
3298
|
|
|
// $this->currentLineStyle = $opt['lin']; |
|
|
|
|
3299
|
|
|
} else { |
3300
|
|
|
$this->nStateStack++; |
3301
|
|
|
$this->stateStack[$this->nStateStack]=array( |
3302
|
|
|
'col'=>$this->currentColour |
3303
|
|
|
,'str'=>$this->currentStrokeColour |
3304
|
|
|
,'lin'=>$this->currentLineStyle |
3305
|
|
|
); |
3306
|
|
|
} |
3307
|
|
|
$this->objects[$this->currentContents]['c'].="\nq"; |
3308
|
|
|
} |
3309
|
|
|
|
3310
|
|
|
/** |
3311
|
|
|
* restore a previously saved state |
3312
|
|
|
* @access public |
3313
|
|
|
*/ |
3314
|
|
|
public function restoreState($pageEnd = 0) |
3315
|
|
|
{ |
3316
|
|
|
if (!$pageEnd) { |
3317
|
|
|
$n = $this->nStateStack; |
3318
|
|
|
$this->currentColour = $this->stateStack[$n]['col']; |
3319
|
|
|
$this->currentStrokeColour = $this->stateStack[$n]['str']; |
3320
|
|
|
$this->objects[$this->currentContents]['c'].="\n".$this->stateStack[$n]['lin']; |
3321
|
|
|
$this->currentLineStyle = $this->stateStack[$n]['lin']; |
3322
|
|
|
unset($this->stateStack[$n]); |
3323
|
|
|
$this->nStateStack--; |
3324
|
|
|
} |
3325
|
|
|
$this->objects[$this->currentContents]['c'].="\nQ"; |
3326
|
|
|
} |
3327
|
|
|
|
3328
|
|
|
/** |
3329
|
|
|
* make a loose object, the output will go into this object, until it is closed, then will revert to |
3330
|
|
|
* the current one. |
3331
|
|
|
* this object will not appear until it is included within a page. |
3332
|
|
|
* the function will return the object number |
3333
|
|
|
* @access public |
3334
|
|
|
*/ |
3335
|
|
|
public function openObject() |
3336
|
|
|
{ |
3337
|
|
|
$this->nStack++; |
3338
|
|
|
$this->stack[$this->nStack]=array('c'=>$this->currentContents,'p'=>$this->currentPage); |
3339
|
|
|
// add a new object of the content type, to hold the data flow |
3340
|
|
|
$this->numObj++; |
3341
|
|
|
$this->o_contents($this->numObj, 'new'); |
3342
|
|
|
$this->currentContents=$this->numObj; |
3343
|
|
|
$this->looseObjects[$this->numObj]=1; |
3344
|
|
|
|
3345
|
|
|
return $this->numObj; |
3346
|
|
|
} |
3347
|
|
|
|
3348
|
|
|
/** |
3349
|
|
|
* open an existing object for editing |
3350
|
|
|
* @access public |
3351
|
|
|
*/ |
3352
|
|
|
public function reopenObject($id) |
3353
|
|
|
{ |
3354
|
|
|
$this->nStack++; |
3355
|
|
|
$this->stack[$this->nStack]=array('c'=>$this->currentContents,'p'=>$this->currentPage); |
3356
|
|
|
$this->currentContents=$id; |
3357
|
|
|
// also if this object is the primary contents for a page, then set the current page to its parent |
3358
|
|
|
if (isset($this->objects[$id]['onPage'])) { |
3359
|
|
|
$this->currentPage = $this->objects[$id]['onPage']; |
3360
|
|
|
} |
3361
|
|
|
} |
3362
|
|
|
|
3363
|
|
|
/** |
3364
|
|
|
* close an object |
3365
|
|
|
* @access public |
3366
|
|
|
*/ |
3367
|
|
|
public function closeObject() |
3368
|
|
|
{ |
3369
|
|
|
// close the object, as long as there was one open in the first place, which will be indicated by |
3370
|
|
|
// an objectId on the stack. |
3371
|
|
|
if ($this->nStack>0) { |
3372
|
|
|
$this->currentContents=$this->stack[$this->nStack]['c']; |
3373
|
|
|
$this->currentPage=$this->stack[$this->nStack]['p']; |
3374
|
|
|
$this->nStack--; |
3375
|
|
|
// easier to probably not worry about removing the old entries, they will be overwritten |
3376
|
|
|
// if there are new ones. |
3377
|
|
|
} |
3378
|
|
|
} |
3379
|
|
|
|
3380
|
|
|
/** |
3381
|
|
|
* stop an object from appearing on pages from this point on |
3382
|
|
|
* @access public |
3383
|
|
|
*/ |
3384
|
|
|
public function stopObject($id) |
3385
|
|
|
{ |
3386
|
|
|
// if an object has been appearing on pages up to now, then stop it, this page will |
3387
|
|
|
// be the last one that could contian it. |
3388
|
|
|
if (isset($this->addLooseObjects[$id])) { |
3389
|
|
|
$this->addLooseObjects[$id]=''; |
3390
|
|
|
} |
3391
|
|
|
} |
3392
|
|
|
|
3393
|
|
|
/** |
3394
|
|
|
* after an object has been created, it wil only show if it has been added, using this function. |
3395
|
|
|
* @access public |
3396
|
|
|
*/ |
3397
|
|
|
public function addObject($id, $options = 'add') |
3398
|
|
|
{ |
3399
|
|
|
// add the specified object to the page |
3400
|
|
|
if (isset($this->looseObjects[$id]) && $this->currentContents!=$id) { |
3401
|
|
|
// then it is a valid object, and it is not being added to itself |
3402
|
|
|
switch ($options) { |
3403
|
|
|
case 'all': |
3404
|
|
|
// then this object is to be added to this page (done in the next block) and |
3405
|
|
|
// all future new pages. |
3406
|
|
|
$this->addLooseObjects[$id]='all'; |
3407
|
|
|
case 'add': |
3408
|
|
|
if (isset($this->objects[$this->currentContents]['onPage'])) { |
3409
|
|
|
// then the destination contents is the primary for the page |
3410
|
|
|
// (though this object is actually added to that page) |
3411
|
|
|
$this->o_page($this->objects[$this->currentContents]['onPage'], 'content', $id); |
3412
|
|
|
} |
3413
|
|
|
break; |
3414
|
|
View Code Duplication |
case 'even': |
3415
|
|
|
$this->addLooseObjects[$id]='even'; |
3416
|
|
|
$pageObjectId=$this->objects[$this->currentContents]['onPage']; |
3417
|
|
|
if ($this->objects[$pageObjectId]['info']['pageNum']%2==0) { |
3418
|
|
|
$this->addObject($id); // hacky huh :) |
3419
|
|
|
} |
3420
|
|
|
break; |
3421
|
|
View Code Duplication |
case 'odd': |
3422
|
|
|
$this->addLooseObjects[$id]='odd'; |
3423
|
|
|
$pageObjectId=$this->objects[$this->currentContents]['onPage']; |
3424
|
|
|
if ($this->objects[$pageObjectId]['info']['pageNum']%2==1) { |
3425
|
|
|
$this->addObject($id); // hacky huh :) |
3426
|
|
|
} |
3427
|
|
|
break; |
3428
|
|
|
case 'next': |
3429
|
|
|
$this->addLooseObjects[$id]='all'; |
3430
|
|
|
break; |
3431
|
|
|
case 'nexteven': |
3432
|
|
|
$this->addLooseObjects[$id]='even'; |
3433
|
|
|
break; |
3434
|
|
|
case 'nextodd': |
3435
|
|
|
$this->addLooseObjects[$id]='odd'; |
3436
|
|
|
break; |
3437
|
|
|
} |
3438
|
|
|
} |
3439
|
|
|
} |
3440
|
|
|
|
3441
|
|
|
/** |
3442
|
|
|
* add content to the documents info object |
3443
|
|
|
* @access public |
3444
|
|
|
*/ |
3445
|
|
|
public function addInfo($label, $value = 0) |
3446
|
|
|
{ |
3447
|
|
|
// this will only work if the label is one of the valid ones. |
3448
|
|
|
// modify this so that arrays can be passed as well. |
3449
|
|
|
// if $label is an array then assume that it is key=>value pairs |
3450
|
|
|
// else assume that they are both scalar, anything else will probably error |
3451
|
|
|
if (is_array($label)) { |
3452
|
|
|
foreach ($label as $l => $v) { |
3453
|
|
|
$this->o_info($this->infoObject, $l, $v); |
3454
|
|
|
} |
3455
|
|
|
} else { |
3456
|
|
|
$this->o_info($this->infoObject, $label, $value); |
3457
|
|
|
} |
3458
|
|
|
} |
3459
|
|
|
|
3460
|
|
|
/** |
3461
|
|
|
* set the viewer preferences of the document, it is up to the browser to obey these. |
3462
|
|
|
* @access public |
3463
|
|
|
*/ |
3464
|
|
|
public function setPreferences($label, $value = 0) |
3465
|
|
|
{ |
3466
|
|
|
// this will only work if the label is one of the valid ones. |
3467
|
|
|
if (is_array($label)) { |
3468
|
|
|
foreach ($label as $l => $v) { |
3469
|
|
|
$this->o_catalog($this->catalogId, 'viewerPreferences', array($l=>$v)); |
|
|
|
|
3470
|
|
|
} |
3471
|
|
|
} else { |
3472
|
|
|
$this->o_catalog($this->catalogId, 'viewerPreferences', array($label=>$value)); |
|
|
|
|
3473
|
|
|
} |
3474
|
|
|
} |
3475
|
|
|
|
3476
|
|
|
/** |
3477
|
|
|
* extract an integer from a position in a byte stream |
3478
|
|
|
* |
3479
|
|
|
* @access private |
3480
|
|
|
*/ |
3481
|
|
|
private function getBytes(&$data, $pos, $num) |
3482
|
|
|
{ |
3483
|
|
|
// return the integer represented by $num bytes from $pos within $data |
3484
|
|
|
$ret=0; |
3485
|
|
|
for ($i=0; $i<$num; $i++) { |
3486
|
|
|
$ret=$ret*256; |
3487
|
|
|
$ret+=ord($data[$pos+$i]); |
3488
|
|
|
} |
3489
|
|
|
return $ret; |
3490
|
|
|
} |
3491
|
|
|
|
3492
|
|
|
/** |
3493
|
|
|
* reads the PNG chunk |
3494
|
|
|
* @param $data - binary part of the png image |
3495
|
|
|
* @access private |
3496
|
|
|
*/ |
3497
|
|
|
private function readPngChunks(&$data) |
3498
|
|
|
{ |
3499
|
|
|
$default = array('info'=> array(), 'transparency'=> null, 'idata'=> null, 'pdata'=> null, 'haveHeader'=> false); |
3500
|
|
|
// set pointer |
3501
|
|
|
$p = 8; |
3502
|
|
|
$len = strlen($data); |
3503
|
|
|
// cycle through the file, identifying chunks |
3504
|
|
|
while ($p<$len) { |
3505
|
|
|
$chunkLen = $this->getBytes($data, $p, 4); |
3506
|
|
|
$chunkType = substr($data, $p+4, 4); |
3507
|
|
|
//error_log($chunkType. ' - '.$chunkLen); |
|
|
|
|
3508
|
|
|
switch ($chunkType) { |
3509
|
|
|
case 'IHDR': |
3510
|
|
|
//this is where all the file information comes from |
3511
|
|
|
$default['info']['width']=$this->getBytes($data, $p+8, 4); |
3512
|
|
|
$default['info']['height']=$this->getBytes($data, $p+12, 4); |
3513
|
|
|
$default['info']['bitDepth']=ord($data[$p+16]); |
3514
|
|
|
$default['info']['colorType']=ord($data[$p+17]); |
3515
|
|
|
$default['info']['compressionMethod']=ord($data[$p+18]); |
3516
|
|
|
$default['info']['filterMethod']=ord($data[$p+19]); |
3517
|
|
|
$default['info']['interlaceMethod']=ord($data[$p+20]); |
3518
|
|
|
|
3519
|
|
|
$this->debug('readPngChunks: ColorType is' . $default['info']['colorType'], E_USER_NOTICE); |
3520
|
|
|
|
3521
|
|
|
$default['haveHeader'] = true; |
3522
|
|
|
|
3523
|
|
|
if ($default['info']['compressionMethod']!=0) { |
3524
|
|
|
$error = true; |
|
|
|
|
3525
|
|
|
$errormsg = "unsupported compression method"; |
|
|
|
|
3526
|
|
|
} |
3527
|
|
|
if ($default['info']['filterMethod']!=0) { |
3528
|
|
|
$error = true; |
|
|
|
|
3529
|
|
|
$errormsg = "unsupported filter method"; |
|
|
|
|
3530
|
|
|
} |
3531
|
|
|
|
3532
|
|
|
$default['transparency'] = array('type'=> null, 'data' => null); |
3533
|
|
|
|
3534
|
|
|
if ($default['info']['colorType'] == 3) { // indexed color, rbg |
3535
|
|
|
// corresponding to entries in the plte chunk |
3536
|
|
|
// Alpha for palette index 0: 1 byte |
3537
|
|
|
// Alpha for palette index 1: 1 byte |
3538
|
|
|
// ...etc... |
3539
|
|
|
|
3540
|
|
|
// there will be one entry for each palette entry. up until the last non-opaque entry. |
3541
|
|
|
// set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent) |
3542
|
|
|
$default['transparency']['type']='indexed'; |
3543
|
|
|
//$numPalette = strlen($default['pdata'])/3; |
|
|
|
|
3544
|
|
|
$trans=0; |
3545
|
|
|
for ($i=$chunkLen; $i>=0; $i--) { |
3546
|
|
|
if (ord($data[$p+8+$i])==0) { |
3547
|
|
|
$trans=$i; |
3548
|
|
|
} |
3549
|
|
|
} |
3550
|
|
|
$default['transparency']['data'] = $trans; |
3551
|
|
|
} elseif ($default['info']['colorType'] == 0) { // grayscale |
3552
|
|
|
// corresponding to entries in the plte chunk |
3553
|
|
|
// Gray: 2 bytes, range 0 .. (2^bitdepth)-1 |
|
|
|
|
3554
|
|
|
|
3555
|
|
|
// $transparency['grayscale']=$this->getBytes($data,$p+8,2); // g = grayscale |
|
|
|
|
3556
|
|
|
$default['transparency']['type']='indexed'; |
3557
|
|
|
$default['transparency']['data'] = ord($data[$p+8+1]); |
3558
|
|
|
} elseif ($default['info']['colorType'] == 2) { // truecolor |
3559
|
|
|
// corresponding to entries in the plte chunk |
3560
|
|
|
// Red: 2 bytes, range 0 .. (2^bitdepth)-1 |
|
|
|
|
3561
|
|
|
// Green: 2 bytes, range 0 .. (2^bitdepth)-1 |
|
|
|
|
3562
|
|
|
// Blue: 2 bytes, range 0 .. (2^bitdepth)-1 |
|
|
|
|
3563
|
|
|
$default['transparency']['r']=$this->getBytes($data, $p+8, 2); // r from truecolor |
3564
|
|
|
$default['transparency']['g']=$this->getBytes($data, $p+10, 2); // g from truecolor |
3565
|
|
|
$default['transparency']['b']=$this->getBytes($data, $p+12, 2); // b from truecolor |
3566
|
|
|
} elseif ($default['info']['colorType'] == 6 || $default['info']['colorType'] == 4) { |
3567
|
|
|
// set transparency type to "alpha" and proceed with it in $this->o_image later |
3568
|
|
|
$default['transparency']['type'] = 'alpha'; |
3569
|
|
|
|
3570
|
|
|
$img = imagecreatefromstring($data); |
3571
|
|
|
|
3572
|
|
|
$imgalpha = imagecreate($default['info']['width'], $default['info']['height']); |
3573
|
|
|
// generate gray scale palette (0 -> 255) |
3574
|
|
|
for ($c = 0; $c < 256; ++$c) { |
3575
|
|
|
ImageColorAllocate($imgalpha, $c, $c, $c); |
3576
|
|
|
} |
3577
|
|
|
// extract alpha channel |
3578
|
|
|
for ($xpx = 0; $xpx < $default['info']['width']; ++$xpx) { |
3579
|
|
|
for ($ypx = 0; $ypx < $default['info']['height']; ++$ypx) { |
3580
|
|
|
$colorBits = imagecolorat($img, $xpx, $ypx); |
3581
|
|
|
$color = imagecolorsforindex($img, $colorBits); |
3582
|
|
|
$color['alpha'] = (((127 - $color['alpha']) / 127) * 255); |
3583
|
|
|
imagesetpixel($imgalpha, $xpx, $ypx, $color['alpha']); |
3584
|
|
|
} |
3585
|
|
|
} |
3586
|
|
|
$tmpfile_alpha=tempnam($this->tempPath, 'ezImg'); |
3587
|
|
|
|
3588
|
|
|
imagepng($imgalpha, $tmpfile_alpha); |
3589
|
|
|
imagedestroy($imgalpha); |
3590
|
|
|
|
3591
|
|
|
$alphaData = file_get_contents($tmpfile_alpha); |
3592
|
|
|
// nested method call to receive info on alpha image |
3593
|
|
|
$alphaImg = $this->readPngChunks($alphaData); |
3594
|
|
|
// use 'pdate' to fill alpha image as "palette". But it s the alpha channel |
3595
|
|
|
$default['pdata'] = $alphaImg['idata']; |
3596
|
|
|
|
3597
|
|
|
// generate true color image with no alpha channel |
3598
|
|
|
$tmpfile_tt=tempnam($this->tempPath, 'ezImg'); |
3599
|
|
|
|
3600
|
|
|
$imgplain = imagecreatetruecolor($default['info']['width'], $default['info']['height']); |
3601
|
|
|
imagecopy($imgplain, $img, 0, 0, 0, 0, $default['info']['width'], $default['info']['height']); |
3602
|
|
|
imagepng($imgplain, $tmpfile_tt); |
3603
|
|
|
imagedestroy($imgplain); |
3604
|
|
|
|
3605
|
|
|
$ttData = file_get_contents($tmpfile_tt); |
3606
|
|
|
$ttImg = $this->readPngChunks($ttData); |
3607
|
|
|
|
3608
|
|
|
$default['idata'] = $ttImg['idata']; |
3609
|
|
|
|
3610
|
|
|
// remove temp files |
3611
|
|
|
unlink($tmpfile_alpha); |
3612
|
|
|
unlink($tmpfile_tt); |
3613
|
|
|
// return to addPngImage prematurely. IDAT has already been read and PLTE is not required |
3614
|
|
|
return $default; |
3615
|
|
|
} |
3616
|
|
|
break; |
3617
|
|
|
case 'PLTE': |
3618
|
|
|
$default['pdata'] = substr($data, $p+8, $chunkLen); |
3619
|
|
|
break; |
3620
|
|
|
case 'IDAT': |
3621
|
|
|
$default['idata'] .= substr($data, $p+8, $chunkLen); |
3622
|
|
|
break; |
3623
|
|
|
case 'tRNS': // this HEADER info is optional. More info: rfc2083 (http://tools.ietf.org/html/rfc2083) |
3624
|
|
|
// error_log('OPTIONAL HEADER -tRNS- exist:'); |
|
|
|
|
3625
|
|
|
// this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk |
3626
|
|
|
// KS End new code |
3627
|
|
|
break; |
3628
|
|
|
default: |
3629
|
|
|
break; |
3630
|
|
|
} |
3631
|
|
|
$p += $chunkLen+12; |
3632
|
|
|
} |
3633
|
|
|
return $default; |
3634
|
|
|
} |
3635
|
|
|
|
3636
|
|
|
/** |
3637
|
|
|
* add a PNG image into the document, from a file |
3638
|
|
|
* this should work with remote files |
3639
|
|
|
* @access public |
3640
|
|
|
*/ |
3641
|
|
|
public function addPngFromFile($file, $x, $y, $w = 0, $h = 0) |
3642
|
|
|
{ |
3643
|
|
|
// read in a png file, interpret it, then add to the system |
3644
|
|
|
$error = false; |
3645
|
|
|
$errormsg = ""; |
3646
|
|
|
|
3647
|
|
|
$this->debug('addPngFromFile: opening image ' . $file); |
3648
|
|
|
|
3649
|
|
|
$data = file_get_contents($file); |
3650
|
|
|
|
3651
|
|
|
if ($data === false) { |
3652
|
|
|
$this->debug('addPngFromFile: trouble opening file ' . $file, E_USER_WARNING); |
3653
|
|
|
return; |
3654
|
|
|
} |
3655
|
|
|
|
3656
|
|
|
$header = chr(137).chr(80).chr(78).chr(71).chr(13).chr(10).chr(26).chr(10); |
3657
|
|
|
if (substr($data, 0, 8)!=$header) { |
3658
|
|
|
$this->debug('addPngFromFile: Invalid PNG header for file: ' . $file, E_USER_WARNING); |
3659
|
|
|
return; |
3660
|
|
|
} |
3661
|
|
|
|
3662
|
|
|
$iChunk = $this->readPngChunks($data); |
3663
|
|
|
|
3664
|
|
|
if (!$iChunk['haveHeader']) { |
3665
|
|
|
$error = true; |
3666
|
|
|
$errormsg = "information header is missing."; |
3667
|
|
|
} |
3668
|
|
|
if (isset($iChunk['info']['interlaceMethod']) && $iChunk['info']['interlaceMethod']) { |
3669
|
|
|
$error = true; |
3670
|
|
|
$errormsg = "There appears to be no support for interlaced images in pdf."; |
3671
|
|
|
} |
3672
|
|
|
|
3673
|
|
|
if ($iChunk['info']['bitDepth'] > 8) { |
3674
|
|
|
$error = true; |
3675
|
|
|
$errormsg = "only bit depth of 8 or less is supported."; |
3676
|
|
|
} |
3677
|
|
|
|
3678
|
|
|
if ($iChunk['info']['colorType'] == 1 || $iChunk['info']['colorType'] == 5 || $iChunk['info']['colorType']== 7) { |
3679
|
|
|
$error = true; |
3680
|
|
|
$errormsg = 'Unsupported PNG color type: '.$iChunk['info']['colorType']; |
3681
|
|
|
} elseif (isset($iChunk['info'])) { |
3682
|
|
|
switch ($iChunk['info']['colorType']) { |
3683
|
|
|
case 3: |
3684
|
|
|
$color = 'DeviceRGB'; |
3685
|
|
|
$ncolor=1; |
3686
|
|
|
break; |
3687
|
|
|
case 6: |
3688
|
|
|
case 2: |
3689
|
|
|
$color = 'DeviceRGB'; |
3690
|
|
|
$ncolor=3; |
3691
|
|
|
break; |
3692
|
|
|
case 4: |
3693
|
|
|
case 0: |
3694
|
|
|
$color = 'DeviceGray'; |
3695
|
|
|
$ncolor=1; |
3696
|
|
|
break; |
3697
|
|
|
} |
3698
|
|
|
} |
3699
|
|
|
|
3700
|
|
|
if ($error) { |
3701
|
|
|
$this->debug('addPngFromFile: '.$errormsg, E_USER_WARNING); |
3702
|
|
|
return; |
3703
|
|
|
} |
3704
|
|
View Code Duplication |
if ($w==0) { |
3705
|
|
|
$w=$h/$iChunk['info']['height']*$iChunk['info']['width']; |
3706
|
|
|
} |
3707
|
|
View Code Duplication |
if ($h==0) { |
3708
|
|
|
$h=$w*$iChunk['info']['height']/$iChunk['info']['width']; |
3709
|
|
|
} |
3710
|
|
|
|
3711
|
|
|
if ($this->hashed) { |
3712
|
|
|
$oHash = md5($iChunk['idata']); |
3713
|
|
|
} |
3714
|
|
|
if (isset($oHash) && isset($this->objectHash[$oHash])) { |
|
|
|
|
3715
|
|
|
$label = $this->objectHash[$oHash]; |
|
|
|
|
3716
|
|
|
} else { |
3717
|
|
|
$this->numImages++; |
3718
|
|
|
$label='I'.$this->numImages; |
3719
|
|
|
$this->numObj++; |
3720
|
|
|
|
3721
|
|
|
if (isset($oHash)) { |
3722
|
|
|
$this->objectHash[$oHash] = $label; |
|
|
|
|
3723
|
|
|
} |
3724
|
|
|
|
3725
|
|
|
$options = array('label'=>$label,'data'=>$iChunk['idata'],'bitsPerComponent'=>$iChunk['info']['bitDepth'],'pdata'=>$iChunk['pdata'] |
3726
|
|
|
,'iw'=>$iChunk['info']['width'],'ih'=>$iChunk['info']['height'],'type'=>'png','color'=>$color,'ncolor'=>$ncolor); |
|
|
|
|
3727
|
|
|
if (isset($iChunk['transparency'])) { |
3728
|
|
|
$options['transparency']=$iChunk['transparency']; |
3729
|
|
|
} |
3730
|
|
|
$this->o_image($this->numObj, 'new', $options); |
|
|
|
|
3731
|
|
|
} |
3732
|
|
|
|
3733
|
|
|
$this->objects[$this->currentContents]['c'].="\nq ".sprintf('%.3F', $w)." 0 0 ".sprintf('%.3F', $h)." ".sprintf('%.3F', $x)." ".sprintf('%.3F', $y)." cm"; |
3734
|
|
|
$this->objects[$this->currentContents]['c'].=" /".$label.' Do'; |
3735
|
|
|
$this->objects[$this->currentContents]['c'].=" Q"; |
3736
|
|
|
} |
3737
|
|
|
|
3738
|
|
|
/** |
3739
|
|
|
* add a JPEG image into the document, from a file |
3740
|
|
|
* @access public |
3741
|
|
|
*/ |
3742
|
|
|
public function addJpegFromFile($img, $x, $y, $w = 0, $h = 0) |
3743
|
|
|
{ |
3744
|
|
|
// attempt to add a jpeg image straight from a file, using no GD commands |
3745
|
|
|
// note that this function is unable to operate on a remote file. |
3746
|
|
|
|
3747
|
|
|
if (!file_exists($img)) { |
3748
|
|
|
return; |
3749
|
|
|
} |
3750
|
|
|
|
3751
|
|
|
$tmp=getimagesize($img); |
3752
|
|
|
$imageWidth=$tmp[0]; |
3753
|
|
|
$imageHeight=$tmp[1]; |
3754
|
|
|
|
3755
|
|
|
if (isset($tmp['channels'])) { |
3756
|
|
|
$channels = $tmp['channels']; |
3757
|
|
|
} else { |
3758
|
|
|
$channels = 3; |
3759
|
|
|
} |
3760
|
|
|
|
3761
|
|
|
if ($w<=0 && $h<=0) { |
3762
|
|
|
$w=$imageWidth; |
3763
|
|
|
} |
3764
|
|
|
if ($w==0) { |
3765
|
|
|
$w=$h/$imageHeight*$imageWidth; |
3766
|
|
|
} |
3767
|
|
|
if ($h==0) { |
3768
|
|
|
$h=$w*$imageHeight/$imageWidth; |
3769
|
|
|
} |
3770
|
|
|
|
3771
|
|
|
$data = file_get_contents($img); |
3772
|
|
|
|
3773
|
|
|
$this->addJpegImage_common($data, $x, $y, $w, $h, $imageWidth, $imageHeight, $channels); |
3774
|
|
|
} |
3775
|
|
|
|
3776
|
|
|
/** |
3777
|
|
|
* read gif image from file, converts it into an JPEG (no transparancy) and display it |
3778
|
|
|
* @param $img - file path ti gif image |
3779
|
|
|
* @param $x - coord x |
3780
|
|
|
* @param $y - y cord |
3781
|
|
|
* @param $w - width |
3782
|
|
|
* @param $h - height |
3783
|
|
|
* @access public |
3784
|
|
|
*/ |
3785
|
|
|
public function addGifFromFile($img, $x, $y, $w = 0, $h = 0) |
3786
|
|
|
{ |
3787
|
|
|
if (!file_exists($img)) { |
3788
|
|
|
return; |
3789
|
|
|
} |
3790
|
|
|
|
3791
|
|
|
if (!function_exists("imagecreatefromgif")) { |
3792
|
|
|
$this->debug('addGifFromFile: Missing GD function imageCreateFromGif', E_USER_ERROR); |
3793
|
|
|
return; |
3794
|
|
|
} |
3795
|
|
|
|
3796
|
|
|
$tmp=getimagesize($img); |
3797
|
|
|
$imageWidth=$tmp[0]; |
3798
|
|
|
$imageHeight=$tmp[1]; |
3799
|
|
|
|
3800
|
|
|
|
3801
|
|
|
if ($w<=0 && $h<=0) { |
3802
|
|
|
$w=$imageWidth; |
3803
|
|
|
} |
3804
|
|
|
if ($w==0) { |
3805
|
|
|
$w=$h/$imageHeight*$imageWidth; |
3806
|
|
|
} |
3807
|
|
|
if ($h==0) { |
3808
|
|
|
$h=$w*$imageHeight/$imageWidth; |
3809
|
|
|
} |
3810
|
|
|
|
3811
|
|
|
$imgres = imagecreatefromgif($img); |
3812
|
|
|
$tmpName=tempnam($this->tempPath, 'img'); |
3813
|
|
|
imagejpeg($imgres, $tmpName, 90); |
3814
|
|
|
|
3815
|
|
|
$this->addJpegFromFile($tmpName, $x, $y, $w, $h); |
3816
|
|
|
} |
3817
|
|
|
|
3818
|
|
|
/** |
3819
|
|
|
* add an image into the document, from a GD object |
3820
|
|
|
* this function is not all that reliable, and I would probably encourage people to use |
3821
|
|
|
* the file based functions |
3822
|
|
|
* @param $img - gd image resource |
3823
|
|
|
* @param $x coord x |
3824
|
|
|
* @param $y coord y |
3825
|
|
|
* @param $w width |
3826
|
|
|
* @param $h height |
3827
|
|
|
* @param $quality image quality |
3828
|
|
|
* @access protected |
3829
|
|
|
*/ |
3830
|
|
|
protected function addImage(&$img, $x, $y, $w = 0, $h = 0, $quality = 75) |
3831
|
|
|
{ |
3832
|
|
|
// add a new image into the current location, as an external object |
3833
|
|
|
// add the image at $x,$y, and with width and height as defined by $w & $h |
3834
|
|
|
|
3835
|
|
|
// note that this will only work with full colour images and makes them jpg images for display |
3836
|
|
|
// later versions could present lossless image formats if there is interest. |
3837
|
|
|
|
3838
|
|
|
// there seems to be some problem here in that images that have quality set above 75 do not appear |
3839
|
|
|
// not too sure why this is, but in the meantime I have restricted this to 75. |
3840
|
|
|
if ($quality>75) { |
3841
|
|
|
$quality=75; |
3842
|
|
|
} |
3843
|
|
|
|
3844
|
|
|
// if the width or height are set to zero, then set the other one based on keeping the image |
3845
|
|
|
// height/width ratio the same, if they are both zero, then give up :) |
3846
|
|
|
$imageWidth=imagesx($img); |
3847
|
|
|
$imageHeight=imagesy($img); |
3848
|
|
|
|
3849
|
|
|
if ($w<=0 && $h<=0) { |
3850
|
|
|
return; |
3851
|
|
|
} |
3852
|
|
|
if ($w==0) { |
3853
|
|
|
$w=$h/$imageHeight*$imageWidth; |
3854
|
|
|
} |
3855
|
|
|
if ($h==0) { |
3856
|
|
|
$h=$w*$imageHeight/$imageWidth; |
3857
|
|
|
} |
3858
|
|
|
|
3859
|
|
|
$tmpName=tempnam($this->tempPath, 'img'); |
3860
|
|
|
imagejpeg($img, $tmpName, $quality); |
3861
|
|
|
|
3862
|
|
|
$data = file_get_contents($tmpName); |
3863
|
|
|
if ($data === false) { |
3864
|
|
|
$this->debug('addImage: trouble opening image resource', E_USER_WARNING); |
3865
|
|
|
} |
3866
|
|
|
unlink($tmpName); |
3867
|
|
|
$this->addJpegImage_common($data, $x, $y, $w, $h, $imageWidth, $imageHeight); |
3868
|
|
|
} |
3869
|
|
|
|
3870
|
|
|
/** |
3871
|
|
|
* common code used by the two JPEG adding functions |
3872
|
|
|
* @access private |
3873
|
|
|
*/ |
3874
|
|
|
private function addJpegImage_common(&$data, $x, $y, $w = 0, $h = 0, $imageWidth, $imageHeight, $channels = 3) |
3875
|
|
|
{ |
3876
|
|
|
// note that this function is not to be called externally |
3877
|
|
|
// it is just the common code between the GD and the file options |
3878
|
|
|
if ($this->hashed) { |
3879
|
|
|
$oHash = md5($data); |
3880
|
|
|
} |
3881
|
|
|
if (isset($oHash) && isset($this->objectHash[$oHash])) { |
|
|
|
|
3882
|
|
|
$label = $this->objectHash[$oHash]; |
|
|
|
|
3883
|
|
|
} else { |
3884
|
|
|
$this->numImages++; |
3885
|
|
|
$label='I'.$this->numImages; |
3886
|
|
|
$this->numObj++; |
3887
|
|
|
|
3888
|
|
|
if (isset($oHash)) { |
3889
|
|
|
$this->objectHash[$oHash] = $label; |
|
|
|
|
3890
|
|
|
} |
3891
|
|
|
|
3892
|
|
|
$this->o_image($this->numObj, 'new', array('label'=>$label,'data'=>$data,'iw'=>$imageWidth,'ih'=>$imageHeight,'channels'=>$channels)); |
|
|
|
|
3893
|
|
|
} |
3894
|
|
|
|
3895
|
|
|
$this->objects[$this->currentContents]['c'].="\nq ".sprintf('%.3F', $w)." 0 0 ".sprintf('%.3F', $h)." ".sprintf('%.3F', $x)." ".sprintf('%.3F', $y)." cm"; |
3896
|
|
|
$this->objects[$this->currentContents]['c'].=" /".$label.' Do'; |
3897
|
|
|
$this->objects[$this->currentContents]['c'].=" Q"; |
3898
|
|
|
} |
3899
|
|
|
|
3900
|
|
|
/** |
3901
|
|
|
* specify where the document should open when it first starts |
3902
|
|
|
* @access public |
3903
|
|
|
*/ |
3904
|
|
View Code Duplication |
public function openHere($style, $a = 0, $b = 0, $c = 0) |
|
|
|
|
3905
|
|
|
{ |
3906
|
|
|
// this function will open the document at a specified page, in a specified style |
3907
|
|
|
// the values for style, and the required paramters are: |
3908
|
|
|
// 'XYZ' left, top, zoom |
3909
|
|
|
// 'Fit' |
3910
|
|
|
// 'FitH' top |
3911
|
|
|
// 'FitV' left |
3912
|
|
|
// 'FitR' left,bottom,right |
|
|
|
|
3913
|
|
|
// 'FitB' |
3914
|
|
|
// 'FitBH' top |
3915
|
|
|
// 'FitBV' left |
3916
|
|
|
$this->numObj++; |
3917
|
|
|
$this->o_destination($this->numObj, 'new', array('page'=>$this->currentPage,'type'=>$style,'p1'=>$a,'p2'=>$b,'p3'=>$c)); |
|
|
|
|
3918
|
|
|
$id = $this->catalogId; |
3919
|
|
|
$this->o_catalog($id, 'openHere', $this->numObj); |
3920
|
|
|
} |
3921
|
|
|
|
3922
|
|
|
/** |
3923
|
|
|
* create a labelled destination within the document |
3924
|
|
|
* @access public |
3925
|
|
|
*/ |
3926
|
|
View Code Duplication |
public function addDestination($label, $style, $a = 0, $b = 0, $c = 0) |
|
|
|
|
3927
|
|
|
{ |
3928
|
|
|
// associates the given label with the destination, it is done this way so that a destination can be specified after |
3929
|
|
|
// it has been linked to |
3930
|
|
|
// styles are the same as the 'openHere' function |
3931
|
|
|
$this->numObj++; |
3932
|
|
|
$this->o_destination($this->numObj, 'new', array('page'=>$this->currentPage,'type'=>$style,'p1'=>$a,'p2'=>$b,'p3'=>$c)); |
|
|
|
|
3933
|
|
|
$id = $this->numObj; |
3934
|
|
|
// store the label->idf relationship, note that this means that labels can be used only once |
3935
|
|
|
$this->destinations["$label"]=$id; |
3936
|
|
|
} |
3937
|
|
|
|
3938
|
|
|
/** |
3939
|
|
|
* define font families, this is used to initialize the font families for the default fonts |
3940
|
|
|
* and for the user to add new ones for their fonts. The default bahavious can be overridden should |
3941
|
|
|
* that be desired. |
3942
|
|
|
* @access public |
3943
|
|
|
*/ |
3944
|
|
|
public function setFontFamily($family, $options = '') |
3945
|
|
|
{ |
3946
|
|
|
if (is_array($options)) { |
3947
|
|
|
// the user is trying to set a font family |
3948
|
|
|
// note that this can also be used to set the base ones to something else |
3949
|
|
|
if (strlen($family)) { |
3950
|
|
|
$this->fontFamilies[$family] = $options; |
3951
|
|
|
} |
3952
|
|
|
} |
3953
|
|
|
} |
3954
|
|
|
|
3955
|
|
|
/** |
3956
|
|
|
* used to add messages for use in debugging |
3957
|
|
|
* @access protected |
3958
|
|
|
*/ |
3959
|
3 |
|
protected function debug($message, $error_type = E_USER_NOTICE) |
3960
|
|
|
{ |
3961
|
3 |
|
if ($error_type <= $this->DEBUGLEVEL) { |
3962
|
|
|
switch (strtolower($this->DEBUG)) { |
3963
|
|
|
default: |
3964
|
|
|
case 'none': |
3965
|
|
|
break; |
3966
|
|
|
case 'error_log': |
3967
|
|
|
trigger_error($message, $error_type); |
3968
|
|
|
break; |
3969
|
|
|
case 'variable': |
3970
|
|
|
$this->messages.=$message."\n"; |
3971
|
|
|
break; |
3972
|
|
|
} |
3973
|
|
|
} |
3974
|
3 |
|
} |
3975
|
|
|
|
3976
|
|
|
/** |
3977
|
|
|
* a few functions which should allow the document to be treated transactionally. |
3978
|
|
|
* |
3979
|
|
|
* @param string $action WHAT IS THIS? |
3980
|
|
|
* @return void |
3981
|
|
|
* @access protected |
3982
|
|
|
*/ |
3983
|
|
|
public function transaction($action) |
3984
|
|
|
{ |
3985
|
|
|
switch ($action) { |
3986
|
|
|
case 'start': |
3987
|
|
|
// store all the data away into the checkpoint variable |
3988
|
|
|
$data = get_object_vars($this); |
3989
|
|
|
$this->checkpoint = $data; |
|
|
|
|
3990
|
|
|
unset($data); |
3991
|
|
|
break; |
3992
|
|
|
case 'commit': |
3993
|
|
|
if (is_array($this->checkpoint) && isset($this->checkpoint['checkpoint'])) { |
3994
|
|
|
$tmp = $this->checkpoint['checkpoint']; |
3995
|
|
|
$this->checkpoint = $tmp; |
3996
|
|
|
unset($tmp); |
3997
|
|
|
} else { |
3998
|
|
|
$this->checkpoint=''; |
3999
|
|
|
} |
4000
|
|
|
break; |
4001
|
|
View Code Duplication |
case 'rewind': |
4002
|
|
|
// do not destroy the current checkpoint, but move us back to the state then, so that we can try again |
4003
|
|
|
if (is_array($this->checkpoint)) { |
4004
|
|
|
// can only abort if were inside a checkpoint |
4005
|
|
|
$tmp = $this->checkpoint; |
4006
|
|
|
foreach ($tmp as $k => $v) { |
4007
|
|
|
if ($k != 'checkpoint') { |
4008
|
|
|
$this->$k=$v; |
4009
|
|
|
} |
4010
|
|
|
} |
4011
|
|
|
unset($tmp); |
4012
|
|
|
} |
4013
|
|
|
break; |
4014
|
|
View Code Duplication |
case 'abort': |
4015
|
|
|
if (is_array($this->checkpoint)) { |
4016
|
|
|
// can only abort if were inside a checkpoint |
4017
|
|
|
$tmp = $this->checkpoint; |
4018
|
|
|
foreach ($tmp as $k => $v) { |
4019
|
|
|
$this->$k=$v; |
4020
|
|
|
} |
4021
|
|
|
unset($tmp); |
4022
|
|
|
} |
4023
|
|
|
break; |
4024
|
|
|
} |
4025
|
|
|
} |
4026
|
|
|
} // end of class |
4027
|
|
|
|
This check marks private properties in classes that are never used. Those properties can be removed.