|
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.