1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* A PHP class to provide the basic functionality to create a pdf document without |
4
|
|
|
* any requirement for additional modules. |
5
|
|
|
* |
6
|
|
|
* Extended by Orion Richardson to support Unicode / UTF-8 characters using |
7
|
|
|
* TCPDF and others as a guide. |
8
|
|
|
* |
9
|
|
|
* @author Wayne Munro <[email protected]> |
10
|
|
|
* @author Orion Richardson <[email protected]> |
11
|
|
|
* @author Helmut Tischer <[email protected]> |
12
|
|
|
* @author Ryan H. Masten <[email protected]> |
13
|
|
|
* @author Brian Sweeney <[email protected]> |
14
|
|
|
* @author Fabien Ménager <[email protected]> |
15
|
|
|
* @license Public Domain http://creativecommons.org/licenses/publicdomain/ |
16
|
|
|
* @package Cpdf |
17
|
|
|
*/ |
18
|
|
|
use FontLib\Font; |
19
|
|
|
use FontLib\BinaryStream; |
20
|
|
|
|
21
|
|
|
class Cpdf |
|
|
|
|
22
|
|
|
{ |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @var integer The current number of pdf objects in the document |
26
|
|
|
*/ |
27
|
|
|
public $numObj = 0; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var array This array contains all of the pdf objects, ready for final assembly |
31
|
|
|
*/ |
32
|
|
|
public $objects = array(); |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var integer The objectId (number within the objects array) of the document catalog |
36
|
|
|
*/ |
37
|
|
|
public $catalogId; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var array Array carrying information about the fonts that the system currently knows about |
41
|
|
|
* Used to ensure that a font is not loaded twice, among other things |
42
|
|
|
*/ |
43
|
|
|
public $fonts = array(); |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var string The default font metrics file to use if no other font has been loaded. |
47
|
|
|
* The path to the directory containing the font metrics should be included |
48
|
|
|
*/ |
49
|
|
|
public $defaultFont = './fonts/Helvetica.afm'; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* @string A record of the current font |
53
|
|
|
*/ |
54
|
|
|
public $currentFont = ''; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* @var string The current base font |
58
|
|
|
*/ |
59
|
|
|
public $currentBaseFont = ''; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* @var integer The number of the current font within the font array |
63
|
|
|
*/ |
64
|
|
|
public $currentFontNum = 0; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* @var integer |
68
|
|
|
*/ |
69
|
|
|
public $currentNode; |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* @var integer Object number of the current page |
73
|
|
|
*/ |
74
|
|
|
public $currentPage; |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @var integer Object number of the currently active contents block |
78
|
|
|
*/ |
79
|
|
|
public $currentContents; |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* @var integer Number of fonts within the system |
83
|
|
|
*/ |
84
|
|
|
public $numFonts = 0; |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* @var integer Number of graphic state resources used |
88
|
|
|
*/ |
89
|
|
|
private $numStates = 0; |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* @var array Number of graphic state resources used |
93
|
|
|
*/ |
94
|
|
|
private $gstates = array(); |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* @var array Current color for fill operations, defaults to inactive value, |
98
|
|
|
* all three components should be between 0 and 1 inclusive when active |
99
|
|
|
*/ |
100
|
|
|
public $currentColor = null; |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* @var array Current color for stroke operations (lines etc.) |
104
|
|
|
*/ |
105
|
|
|
public $currentStrokeColor = null; |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* @var string Fill rule (nonzero or evenodd) |
109
|
|
|
*/ |
110
|
|
|
public $fillRule = "nonzero"; |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* @var string Current style that lines are drawn in |
114
|
|
|
*/ |
115
|
|
|
public $currentLineStyle = ''; |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* @var array Current line transparency (partial graphics state) |
119
|
|
|
*/ |
120
|
|
|
public $currentLineTransparency = array("mode" => "Normal", "opacity" => 1.0); |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* array Current fill transparency (partial graphics state) |
124
|
|
|
*/ |
125
|
|
|
public $currentFillTransparency = array("mode" => "Normal", "opacity" => 1.0); |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* @var array An array which is used to save the state of the document, mainly the colors and styles |
129
|
|
|
* it is used to temporarily change to another state, then change back to what it was before |
130
|
|
|
*/ |
131
|
|
|
public $stateStack = array(); |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* @var integer Number of elements within the state stack |
135
|
|
|
*/ |
136
|
|
|
public $nStateStack = 0; |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* @var integer Number of page objects within the document |
140
|
|
|
*/ |
141
|
|
|
public $numPages = 0; |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* @var array Object Id storage stack |
145
|
|
|
*/ |
146
|
|
|
public $stack = array(); |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* @var integer Number of elements within the object Id storage stack |
150
|
|
|
*/ |
151
|
|
|
public $nStack = 0; |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* an array which contains information about the objects which are not firmly attached to pages |
155
|
|
|
* these have been added with the addObject function |
156
|
|
|
*/ |
157
|
|
|
public $looseObjects = array(); |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* array contains information about how the loose objects are to be added to the document |
161
|
|
|
*/ |
162
|
|
|
public $addLooseObjects = array(); |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* @var integer The objectId of the information object for the document |
166
|
|
|
* this contains authorship, title etc. |
167
|
|
|
*/ |
168
|
|
|
public $infoObject = 0; |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* @var integer Number of images being tracked within the document |
172
|
|
|
*/ |
173
|
|
|
public $numImages = 0; |
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* @var array An array containing options about the document |
177
|
|
|
* it defaults to turning on the compression of the objects |
178
|
|
|
*/ |
179
|
|
|
public $options = array('compression' => true); |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* @var integer The objectId of the first page of the document |
183
|
|
|
*/ |
184
|
|
|
public $firstPageId; |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* @var integer The object Id of the procset object |
188
|
|
|
*/ |
189
|
|
|
public $procsetObjectId; |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* @var array Store the information about the relationship between font families |
193
|
|
|
* this used so that the code knows which font is the bold version of another font, etc. |
194
|
|
|
* the value of this array is initialised in the constructor function. |
195
|
|
|
*/ |
196
|
|
|
public $fontFamilies = array(); |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* @var string Folder for php serialized formats of font metrics files. |
200
|
|
|
* If empty string, use same folder as original metrics files. |
201
|
|
|
* This can be passed in from class creator. |
202
|
|
|
* If this folder does not exist or is not writable, Cpdf will be **much** slower. |
203
|
|
|
* Because of potential trouble with php safe mode, folder cannot be created at runtime. |
204
|
|
|
*/ |
205
|
|
|
public $fontcache = ''; |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* @var integer The version of the font metrics cache file. |
209
|
|
|
* This value must be manually incremented whenever the internal font data structure is modified. |
210
|
|
|
*/ |
211
|
|
|
public $fontcacheVersion = 6; |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* @var string Temporary folder. |
215
|
|
|
* If empty string, will attempt system tmp folder. |
216
|
|
|
* This can be passed in from class creator. |
217
|
|
|
*/ |
218
|
|
|
public $tmp = ''; |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* @var string Track if the current font is bolded or italicised |
222
|
|
|
*/ |
223
|
|
|
public $currentTextState = ''; |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* @var string Messages are stored here during processing, these can be selected afterwards to give some useful debug information |
227
|
|
|
*/ |
228
|
|
|
public $messages = ''; |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* @var string The encryption array for the document encryption is stored here |
232
|
|
|
*/ |
233
|
|
|
public $arc4 = ''; |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* @var integer The object Id of the encryption information |
237
|
|
|
*/ |
238
|
|
|
public $arc4_objnum = 0; |
239
|
|
|
|
240
|
|
|
/** |
241
|
|
|
* @var string The file identifier, used to uniquely identify a pdf document |
242
|
|
|
*/ |
243
|
|
|
public $fileIdentifier = ''; |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* @var boolean A flag to say if a document is to be encrypted or not |
247
|
|
|
*/ |
248
|
|
|
public $encrypted = false; |
249
|
|
|
|
250
|
|
|
/** |
251
|
|
|
* @var string The encryption key for the encryption of all the document content (structure is not encrypted) |
252
|
|
|
*/ |
253
|
|
|
public $encryptionKey = ''; |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* @var array Array which forms a stack to keep track of nested callback functions |
257
|
|
|
*/ |
258
|
|
|
public $callback = array(); |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* @var integer The number of callback functions in the callback array |
262
|
|
|
*/ |
263
|
|
|
public $nCallback = 0; |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* @var array Store label->id pairs for named destinations, these will be used to replace internal links |
267
|
|
|
* done this way so that destinations can be defined after the location that links to them |
268
|
|
|
*/ |
269
|
|
|
public $destinations = array(); |
270
|
|
|
|
271
|
|
|
/** |
272
|
|
|
* @var array Store the stack for the transaction commands, each item in here is a record of the values of all the |
273
|
|
|
* publiciables within the class, so that the user can rollback at will (from each 'start' command) |
274
|
|
|
* note that this includes the objects array, so these can be large. |
275
|
|
|
*/ |
276
|
|
|
public $checkpoint = ''; |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* @var array Table of Image origin filenames and image labels which were already added with o_image(). |
280
|
|
|
* Allows to merge identical images |
281
|
|
|
*/ |
282
|
|
|
public $imagelist = array(); |
283
|
|
|
|
284
|
|
|
/** |
285
|
|
|
* @var boolean Whether the text passed in should be treated as Unicode or just local character set. |
286
|
|
|
*/ |
287
|
|
|
public $isUnicode = false; |
288
|
|
|
|
289
|
|
|
/** |
290
|
|
|
* @var string the JavaScript code of the document |
291
|
|
|
*/ |
292
|
|
|
public $javascript = ''; |
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
* @var boolean whether the compression is possible |
296
|
|
|
*/ |
297
|
|
|
protected $compressionReady = false; |
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* @var array Current page size |
301
|
|
|
*/ |
302
|
|
|
protected $currentPageSize = array("width" => 0, "height" => 0); |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* @var array All the chars that will be required in the font subsets |
306
|
|
|
*/ |
307
|
|
|
protected $stringSubsets = array(); |
308
|
|
|
|
309
|
|
|
/** |
310
|
|
|
* @var string The target internal encoding |
311
|
|
|
*/ |
312
|
|
|
static protected $targetEncoding = 'Windows-1252'; |
313
|
|
|
|
314
|
|
|
/** |
315
|
|
|
* @var array The list of the core fonts |
316
|
|
|
*/ |
317
|
|
|
static protected $coreFonts = array( |
318
|
|
|
'courier', |
319
|
|
|
'courier-bold', |
320
|
|
|
'courier-oblique', |
321
|
|
|
'courier-boldoblique', |
322
|
|
|
'helvetica', |
323
|
|
|
'helvetica-bold', |
324
|
|
|
'helvetica-oblique', |
325
|
|
|
'helvetica-boldoblique', |
326
|
|
|
'times-roman', |
327
|
|
|
'times-bold', |
328
|
|
|
'times-italic', |
329
|
|
|
'times-bolditalic', |
330
|
|
|
'symbol', |
331
|
|
|
'zapfdingbats' |
332
|
|
|
); |
333
|
|
|
|
334
|
|
|
/** |
335
|
|
|
* Class constructor |
336
|
|
|
* This will start a new document |
337
|
|
|
* |
338
|
|
|
* @param array $pageSize Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero. |
339
|
|
|
* @param boolean $isUnicode Whether text will be treated as Unicode or not. |
340
|
|
|
* @param string $fontcache The font cache folder |
341
|
|
|
* @param string $tmp The temporary folder |
342
|
|
|
*/ |
343
|
|
|
function __construct($pageSize = array(0, 0, 612, 792), $isUnicode = false, $fontcache = '', $tmp = '') |
|
|
|
|
344
|
|
|
{ |
345
|
|
|
$this->isUnicode = $isUnicode; |
346
|
|
|
$this->fontcache = rtrim($fontcache, DIRECTORY_SEPARATOR."/\\"); |
347
|
|
|
$this->tmp = ($tmp !== '' ? $tmp : sys_get_temp_dir()); |
348
|
|
|
$this->newDocument($pageSize); |
349
|
|
|
|
350
|
|
|
$this->compressionReady = function_exists('gzcompress'); |
351
|
|
|
|
352
|
|
|
if (in_array('Windows-1252', mb_list_encodings())) { |
353
|
|
|
self::$targetEncoding = 'Windows-1252'; |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
// also initialize the font families that are known about already |
357
|
|
|
$this->setFontFamily('init'); |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
/** |
361
|
|
|
* Document object methods (internal use only) |
362
|
|
|
* |
363
|
|
|
* There is about one object method for each type of object in the pdf document |
364
|
|
|
* Each function has the same call list ($id,$action,$options). |
365
|
|
|
* $id = the object ID of the object, or what it is to be if it is being created |
366
|
|
|
* $action = a string specifying the action to be performed, though ALL must support: |
367
|
|
|
* 'new' - create the object with the id $id |
368
|
|
|
* 'out' - produce the output for the pdf object |
369
|
|
|
* $options = optional, a string or array containing the various parameters for the object |
370
|
|
|
* |
371
|
|
|
* These, in conjunction with the output function are the ONLY way for output to be produced |
372
|
|
|
* within the pdf 'file'. |
373
|
|
|
*/ |
374
|
|
|
|
375
|
|
|
/** |
376
|
|
|
* Destination object, used to specify the location for the user to jump to, presently on opening |
377
|
|
|
* |
378
|
|
|
* @param $id |
379
|
|
|
* @param $action |
380
|
|
|
* @param string $options |
381
|
|
|
* @return string|null |
382
|
|
|
*/ |
383
|
|
|
protected function o_destination($id, $action, $options = '') |
384
|
|
|
{ |
385
|
|
|
switch ($action) { |
386
|
|
|
case 'new': |
387
|
|
|
$this->objects[$id] = array('t' => 'destination', 'info' => array()); |
388
|
|
|
$tmp = ''; |
389
|
|
|
switch ($options['type']) { |
390
|
|
|
case 'XYZ': |
|
|
|
|
391
|
|
|
/** @noinspection PhpMissingBreakStatementInspection */ |
392
|
|
|
case 'FitR': |
|
|
|
|
393
|
|
|
$tmp = ' ' . $options['p3'] . $tmp; |
394
|
|
|
case 'FitH': |
395
|
|
|
case 'FitV': |
396
|
|
|
case 'FitBH': |
|
|
|
|
397
|
|
|
/** @noinspection PhpMissingBreakStatementInspection */ |
398
|
|
|
case 'FitBV': |
|
|
|
|
399
|
|
|
$tmp = ' ' . $options['p1'] . ' ' . $options['p2'] . $tmp; |
400
|
|
|
case 'Fit': |
401
|
|
|
case 'FitB': |
402
|
|
|
$tmp = $options['type'] . $tmp; |
403
|
|
|
$this->objects[$id]['info']['string'] = $tmp; |
404
|
|
|
$this->objects[$id]['info']['page'] = $options['page']; |
405
|
|
|
} |
406
|
|
|
break; |
407
|
|
|
|
408
|
|
|
case 'out': |
409
|
|
|
$o = &$this->objects[$id]; |
410
|
|
|
|
411
|
|
|
$tmp = $o['info']; |
412
|
|
|
$res = "\n$id 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj"; |
413
|
|
|
|
414
|
|
|
return $res; |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
return null; |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* set the viewer preferences |
422
|
|
|
* |
423
|
|
|
* @param $id |
424
|
|
|
* @param $action |
425
|
|
|
* @param string|array $options |
426
|
|
|
* @return string|null |
427
|
|
|
*/ |
428
|
|
|
protected function o_viewerPreferences($id, $action, $options = '') |
429
|
|
|
{ |
430
|
|
|
switch ($action) { |
431
|
|
|
case 'new': |
432
|
|
|
$this->objects[$id] = array('t' => 'viewerPreferences', 'info' => array()); |
433
|
|
|
break; |
434
|
|
|
|
435
|
|
|
case 'add': |
436
|
|
|
$o = &$this->objects[$id]; |
437
|
|
|
|
438
|
|
|
foreach ($options as $k => $v) { |
|
|
|
|
439
|
|
|
switch ($k) { |
440
|
|
|
// Boolean keys |
441
|
|
|
case 'HideToolbar': |
442
|
|
|
case 'HideMenubar': |
443
|
|
|
case 'HideWindowUI': |
444
|
|
|
case 'FitWindow': |
445
|
|
|
case 'CenterWindow': |
446
|
|
|
case 'DisplayDocTitle': |
447
|
|
|
case 'PickTrayByPDFSize': |
448
|
|
|
$o['info'][$k] = (bool)$v; |
449
|
|
|
break; |
450
|
|
|
|
451
|
|
|
// Integer keys |
452
|
|
|
case 'NumCopies': |
453
|
|
|
$o['info'][$k] = (int)$v; |
454
|
|
|
break; |
455
|
|
|
|
456
|
|
|
// Name keys |
457
|
|
|
case 'ViewArea': |
458
|
|
|
case 'ViewClip': |
459
|
|
|
case 'PrintClip': |
460
|
|
|
case 'PrintArea': |
461
|
|
|
$o['info'][$k] = (string)$v; |
462
|
|
|
break; |
463
|
|
|
|
464
|
|
|
// Named with limited valid values |
465
|
|
|
case 'NonFullScreenPageMode': |
466
|
|
|
if (!in_array($v, array('UseNone', 'UseOutlines', 'UseThumbs', 'UseOC'))) { |
467
|
|
|
continue; |
468
|
|
|
} |
469
|
|
|
$o['info'][$k] = $v; |
470
|
|
|
break; |
471
|
|
|
|
472
|
|
View Code Duplication |
case 'Direction': |
|
|
|
|
473
|
|
|
if (!in_array($v, array('L2R', 'R2L'))) { |
474
|
|
|
continue; |
475
|
|
|
} |
476
|
|
|
$o['info'][$k] = $v; |
477
|
|
|
break; |
478
|
|
|
|
479
|
|
View Code Duplication |
case 'PrintScaling': |
|
|
|
|
480
|
|
|
if (!in_array($v, array('None', 'AppDefault'))) { |
481
|
|
|
continue; |
482
|
|
|
} |
483
|
|
|
$o['info'][$k] = $v; |
484
|
|
|
break; |
485
|
|
|
|
486
|
|
View Code Duplication |
case 'Duplex': |
|
|
|
|
487
|
|
|
if (!in_array($v, array('None', 'AppDefault'))) { |
488
|
|
|
continue; |
489
|
|
|
} |
490
|
|
|
$o['info'][$k] = $v; |
491
|
|
|
break; |
492
|
|
|
|
493
|
|
|
// Integer array |
494
|
|
|
case 'PrintPageRange': |
495
|
|
|
// Cast to integer array |
496
|
|
|
foreach ($v as $vK => $vV) { |
497
|
|
|
$v[$vK] = (int)$vV; |
498
|
|
|
} |
499
|
|
|
$o['info'][$k] = array_values($v); |
500
|
|
|
break; |
501
|
|
|
} |
502
|
|
|
} |
503
|
|
|
break; |
504
|
|
|
|
505
|
|
|
case 'out': |
506
|
|
|
$o = &$this->objects[$id]; |
507
|
|
|
$res = "\n$id 0 obj\n<< "; |
508
|
|
|
|
509
|
|
|
foreach ($o['info'] as $k => $v) { |
510
|
|
|
if (is_string($v)) { |
511
|
|
|
$v = '/' . $v; |
512
|
|
|
} elseif (is_int($v)) { |
513
|
|
|
$v = (string) $v; |
514
|
|
|
} elseif (is_bool($v)) { |
515
|
|
|
$v = ($v ? 'true' : 'false'); |
516
|
|
|
} elseif (is_array($v)) { |
517
|
|
|
$v = '[' . implode(' ', $v) . ']'; |
518
|
|
|
} |
519
|
|
|
$res .= "\n/$k $v"; |
520
|
|
|
} |
521
|
|
|
$res .= "\n>>\n"; |
522
|
|
|
|
523
|
|
|
return $res; |
524
|
|
|
} |
525
|
|
|
|
526
|
|
|
return null; |
527
|
|
|
} |
528
|
|
|
|
529
|
|
|
/** |
530
|
|
|
* define the document catalog, the overall controller for the document |
531
|
|
|
* |
532
|
|
|
* @param $id |
533
|
|
|
* @param $action |
534
|
|
|
* @param string|array $options |
535
|
|
|
* @return string|null |
536
|
|
|
*/ |
537
|
|
|
protected function o_catalog($id, $action, $options = '') |
538
|
|
|
{ |
539
|
|
|
if ($action !== 'new') { |
540
|
|
|
$o = &$this->objects[$id]; |
541
|
|
|
} |
542
|
|
|
|
543
|
|
|
switch ($action) { |
544
|
|
|
case 'new': |
545
|
|
|
$this->objects[$id] = array('t' => 'catalog', 'info' => array()); |
546
|
|
|
$this->catalogId = $id; |
547
|
|
|
break; |
548
|
|
|
|
549
|
|
|
case 'outlines': |
550
|
|
|
case 'pages': |
551
|
|
|
case 'openHere': |
552
|
|
|
case 'javascript': |
553
|
|
|
$o['info'][$action] = $options; |
|
|
|
|
554
|
|
|
break; |
555
|
|
|
|
556
|
|
|
case 'viewerPreferences': |
557
|
|
|
if (!isset($o['info']['viewerPreferences'])) { |
558
|
|
|
$this->numObj++; |
559
|
|
|
$this->o_viewerPreferences($this->numObj, 'new'); |
560
|
|
|
$o['info']['viewerPreferences'] = $this->numObj; |
561
|
|
|
} |
562
|
|
|
|
563
|
|
|
$vp = $o['info']['viewerPreferences']; |
564
|
|
|
$this->o_viewerPreferences($vp, 'add', $options); |
565
|
|
|
|
566
|
|
|
break; |
567
|
|
|
|
568
|
|
|
case 'out': |
569
|
|
|
$res = "\n$id 0 obj\n<< /Type /Catalog"; |
570
|
|
|
|
571
|
|
|
foreach ($o['info'] as $k => $v) { |
572
|
|
|
switch ($k) { |
573
|
|
|
case 'outlines': |
574
|
|
|
$res .= "\n/Outlines $v 0 R"; |
575
|
|
|
break; |
576
|
|
|
|
577
|
|
|
case 'pages': |
578
|
|
|
$res .= "\n/Pages $v 0 R"; |
579
|
|
|
break; |
580
|
|
|
|
581
|
|
|
case 'viewerPreferences': |
582
|
|
|
$res .= "\n/ViewerPreferences $v 0 R"; |
583
|
|
|
break; |
584
|
|
|
|
585
|
|
|
case 'openHere': |
586
|
|
|
$res .= "\n/OpenAction $v 0 R"; |
587
|
|
|
break; |
588
|
|
|
|
589
|
|
|
case 'javascript': |
590
|
|
|
$res .= "\n/Names <</JavaScript $v 0 R>>"; |
591
|
|
|
break; |
592
|
|
|
} |
593
|
|
|
} |
594
|
|
|
|
595
|
|
|
$res .= " >>\nendobj"; |
596
|
|
|
|
597
|
|
|
return $res; |
598
|
|
|
} |
599
|
|
|
|
600
|
|
|
return null; |
601
|
|
|
} |
602
|
|
|
|
603
|
|
|
/** |
604
|
|
|
* object which is a parent to the pages in the document |
605
|
|
|
* |
606
|
|
|
* @param $id |
607
|
|
|
* @param $action |
608
|
|
|
* @param string $options |
609
|
|
|
* @return string|null |
610
|
|
|
*/ |
611
|
|
|
protected function o_pages($id, $action, $options = '') |
612
|
|
|
{ |
613
|
|
|
if ($action !== 'new') { |
614
|
|
|
$o = &$this->objects[$id]; |
615
|
|
|
} |
616
|
|
|
|
617
|
|
|
switch ($action) { |
618
|
|
View Code Duplication |
case 'new': |
|
|
|
|
619
|
|
|
$this->objects[$id] = array('t' => 'pages', 'info' => array()); |
620
|
|
|
$this->o_catalog($this->catalogId, 'pages', $id); |
621
|
|
|
break; |
622
|
|
|
|
623
|
|
|
case 'page': |
624
|
|
|
if (!is_array($options)) { |
625
|
|
|
// then it will just be the id of the new page |
626
|
|
|
$o['info']['pages'][] = $options; |
|
|
|
|
627
|
|
|
} else { |
628
|
|
|
// then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative |
629
|
|
|
// and pos is either 'before' or 'after', saying where this page will fit. |
630
|
|
|
if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) { |
631
|
|
|
$i = array_search($options['rid'], $o['info']['pages']); |
632
|
|
|
if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i] == $options['rid']) { |
633
|
|
|
|
634
|
|
|
// then there is a match |
635
|
|
|
// make a space |
636
|
|
|
switch ($options['pos']) { |
637
|
|
|
case 'before': |
638
|
|
|
$k = $i; |
639
|
|
|
break; |
640
|
|
|
|
641
|
|
|
case 'after': |
642
|
|
|
$k = $i + 1; |
643
|
|
|
break; |
644
|
|
|
|
645
|
|
|
default: |
646
|
|
|
$k = -1; |
647
|
|
|
break; |
648
|
|
|
} |
649
|
|
|
|
650
|
|
|
if ($k >= 0) { |
651
|
|
|
for ($j = count($o['info']['pages']) - 1; $j >= $k; $j--) { |
652
|
|
|
$o['info']['pages'][$j + 1] = $o['info']['pages'][$j]; |
653
|
|
|
} |
654
|
|
|
|
655
|
|
|
$o['info']['pages'][$k] = $options['id']; |
656
|
|
|
} |
657
|
|
|
} |
658
|
|
|
} |
659
|
|
|
} |
660
|
|
|
break; |
661
|
|
|
|
662
|
|
|
case 'procset': |
663
|
|
|
$o['info']['procset'] = $options; |
664
|
|
|
break; |
665
|
|
|
|
666
|
|
|
case 'mediaBox': |
667
|
|
|
$o['info']['mediaBox'] = $options; |
668
|
|
|
// which should be an array of 4 numbers |
669
|
|
|
$this->currentPageSize = array('width' => $options[2], 'height' => $options[3]); |
670
|
|
|
break; |
671
|
|
|
|
672
|
|
View Code Duplication |
case 'font': |
|
|
|
|
673
|
|
|
$o['info']['fonts'][] = array('objNum' => $options['objNum'], 'fontNum' => $options['fontNum']); |
674
|
|
|
break; |
675
|
|
|
|
676
|
|
|
case 'extGState': |
677
|
|
|
$o['info']['extGStates'][] = array('objNum' => $options['objNum'], 'stateNum' => $options['stateNum']); |
678
|
|
|
break; |
679
|
|
|
|
680
|
|
View Code Duplication |
case 'xObject': |
|
|
|
|
681
|
|
|
$o['info']['xObjects'][] = array('objNum' => $options['objNum'], 'label' => $options['label']); |
682
|
|
|
break; |
683
|
|
|
|
684
|
|
|
case 'out': |
685
|
|
|
if (count($o['info']['pages'])) { |
686
|
|
|
$res = "\n$id 0 obj\n<< /Type /Pages\n/Kids ["; |
687
|
|
|
foreach ($o['info']['pages'] as $v) { |
688
|
|
|
$res .= "$v 0 R\n"; |
689
|
|
|
} |
690
|
|
|
|
691
|
|
|
$res .= "]\n/Count " . count($this->objects[$id]['info']['pages']); |
692
|
|
|
|
693
|
|
|
if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) || |
694
|
|
|
isset($o['info']['procset']) || |
695
|
|
|
(isset($o['info']['extGStates']) && count($o['info']['extGStates'])) |
696
|
|
|
) { |
697
|
|
|
$res .= "\n/Resources <<"; |
698
|
|
|
|
699
|
|
|
if (isset($o['info']['procset'])) { |
700
|
|
|
$res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R"; |
701
|
|
|
} |
702
|
|
|
|
703
|
|
View Code Duplication |
if (isset($o['info']['fonts']) && count($o['info']['fonts'])) { |
|
|
|
|
704
|
|
|
$res .= "\n/Font << "; |
705
|
|
|
foreach ($o['info']['fonts'] as $finfo) { |
706
|
|
|
$res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R"; |
707
|
|
|
} |
708
|
|
|
$res .= "\n>>"; |
709
|
|
|
} |
710
|
|
|
|
711
|
|
View Code Duplication |
if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) { |
|
|
|
|
712
|
|
|
$res .= "\n/XObject << "; |
713
|
|
|
foreach ($o['info']['xObjects'] as $finfo) { |
714
|
|
|
$res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R"; |
715
|
|
|
} |
716
|
|
|
$res .= "\n>>"; |
717
|
|
|
} |
718
|
|
|
|
719
|
|
View Code Duplication |
if (isset($o['info']['extGStates']) && count($o['info']['extGStates'])) { |
|
|
|
|
720
|
|
|
$res .= "\n/ExtGState << "; |
721
|
|
|
foreach ($o['info']['extGStates'] as $gstate) { |
722
|
|
|
$res .= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R"; |
723
|
|
|
} |
724
|
|
|
$res .= "\n>>"; |
725
|
|
|
} |
726
|
|
|
|
727
|
|
|
$res .= "\n>>"; |
728
|
|
View Code Duplication |
if (isset($o['info']['mediaBox'])) { |
|
|
|
|
729
|
|
|
$tmp = $o['info']['mediaBox']; |
730
|
|
|
$res .= "\n/MediaBox [" . sprintf( |
731
|
|
|
'%.3F %.3F %.3F %.3F', |
732
|
|
|
$tmp[0], |
733
|
|
|
$tmp[1], |
734
|
|
|
$tmp[2], |
735
|
|
|
$tmp[3] |
736
|
|
|
) . ']'; |
737
|
|
|
} |
738
|
|
|
} |
739
|
|
|
|
740
|
|
|
$res .= "\n >>\nendobj"; |
741
|
|
|
} else { |
742
|
|
|
$res = "\n$id 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj"; |
743
|
|
|
} |
744
|
|
|
|
745
|
|
|
return $res; |
746
|
|
|
} |
747
|
|
|
|
748
|
|
|
return null; |
749
|
|
|
} |
750
|
|
|
|
751
|
|
|
/** |
752
|
|
|
* define the outlines in the doc, empty for now |
753
|
|
|
* |
754
|
|
|
* @param $id |
755
|
|
|
* @param $action |
756
|
|
|
* @param string $options |
757
|
|
|
* @return string|null |
758
|
|
|
*/ |
759
|
|
|
protected function o_outlines($id, $action, $options = '') |
760
|
|
|
{ |
761
|
|
|
if ($action !== 'new') { |
762
|
|
|
$o = &$this->objects[$id]; |
763
|
|
|
} |
764
|
|
|
|
765
|
|
|
switch ($action) { |
766
|
|
View Code Duplication |
case 'new': |
|
|
|
|
767
|
|
|
$this->objects[$id] = array('t' => 'outlines', 'info' => array('outlines' => array())); |
768
|
|
|
$this->o_catalog($this->catalogId, 'outlines', $id); |
769
|
|
|
break; |
770
|
|
|
|
771
|
|
|
case 'outline': |
772
|
|
|
$o['info']['outlines'][] = $options; |
|
|
|
|
773
|
|
|
break; |
774
|
|
|
|
775
|
|
|
case 'out': |
776
|
|
|
if (count($o['info']['outlines'])) { |
777
|
|
|
$res = "\n$id 0 obj\n<< /Type /Outlines /Kids ["; |
778
|
|
|
foreach ($o['info']['outlines'] as $v) { |
779
|
|
|
$res .= "$v 0 R "; |
780
|
|
|
} |
781
|
|
|
|
782
|
|
|
$res .= "] /Count " . count($o['info']['outlines']) . " >>\nendobj"; |
783
|
|
|
} else { |
784
|
|
|
$res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj"; |
785
|
|
|
} |
786
|
|
|
|
787
|
|
|
return $res; |
788
|
|
|
} |
789
|
|
|
|
790
|
|
|
return null; |
791
|
|
|
} |
792
|
|
|
|
793
|
|
|
/** |
794
|
|
|
* an object to hold the font description |
795
|
|
|
* |
796
|
|
|
* @param $id |
797
|
|
|
* @param $action |
798
|
|
|
* @param string|array $options |
799
|
|
|
* @return string|null |
800
|
|
|
*/ |
801
|
|
|
protected function o_font($id, $action, $options = '') |
802
|
|
|
{ |
803
|
|
|
if ($action !== 'new') { |
804
|
|
|
$o = &$this->objects[$id]; |
805
|
|
|
} |
806
|
|
|
|
807
|
|
|
switch ($action) { |
808
|
|
|
case 'new': |
809
|
|
|
$this->objects[$id] = array( |
810
|
|
|
't' => 'font', |
811
|
|
|
'info' => array( |
812
|
|
|
'name' => $options['name'], |
813
|
|
|
'fontFileName' => $options['fontFileName'], |
814
|
|
|
'SubType' => 'Type1' |
815
|
|
|
) |
816
|
|
|
); |
817
|
|
|
$fontNum = $this->numFonts; |
818
|
|
|
$this->objects[$id]['info']['fontNum'] = $fontNum; |
819
|
|
|
|
820
|
|
|
// deal with the encoding and the differences |
821
|
|
|
if (isset($options['differences'])) { |
822
|
|
|
// then we'll need an encoding dictionary |
823
|
|
|
$this->numObj++; |
824
|
|
|
$this->o_fontEncoding($this->numObj, 'new', $options); |
|
|
|
|
825
|
|
|
$this->objects[$id]['info']['encodingDictionary'] = $this->numObj; |
826
|
|
|
} else { |
827
|
|
|
if (isset($options['encoding'])) { |
828
|
|
|
// we can specify encoding here |
829
|
|
|
switch ($options['encoding']) { |
830
|
|
|
case 'WinAnsiEncoding': |
831
|
|
|
case 'MacRomanEncoding': |
832
|
|
|
case 'MacExpertEncoding': |
833
|
|
|
$this->objects[$id]['info']['encoding'] = $options['encoding']; |
834
|
|
|
break; |
835
|
|
|
|
836
|
|
|
case 'none': |
837
|
|
|
break; |
838
|
|
|
|
839
|
|
|
default: |
840
|
|
|
$this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding'; |
841
|
|
|
break; |
842
|
|
|
} |
843
|
|
|
} else { |
844
|
|
|
$this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding'; |
845
|
|
|
} |
846
|
|
|
} |
847
|
|
|
|
848
|
|
|
if ($this->fonts[$options['fontFileName']]['isUnicode']) { |
849
|
|
|
// For Unicode fonts, we need to incorporate font data into |
850
|
|
|
// sub-sections that are linked from the primary font section. |
851
|
|
|
// Look at o_fontGIDtoCID and o_fontDescendentCID functions |
852
|
|
|
// for more information. |
853
|
|
|
// |
854
|
|
|
// All of this code is adapted from the excellent changes made to |
855
|
|
|
// transform FPDF to TCPDF (http://tcpdf.sourceforge.net/) |
856
|
|
|
|
857
|
|
|
$toUnicodeId = ++$this->numObj; |
858
|
|
|
$this->o_contents($toUnicodeId, 'new', 'raw'); |
859
|
|
|
$this->objects[$id]['info']['toUnicode'] = $toUnicodeId; |
860
|
|
|
|
861
|
|
|
$stream = <<<EOT |
862
|
|
|
/CIDInit /ProcSet findresource begin |
863
|
|
|
12 dict begin |
864
|
|
|
begincmap |
865
|
|
|
/CIDSystemInfo |
866
|
|
|
<</Registry (Adobe) |
867
|
|
|
/Ordering (UCS) |
868
|
|
|
/Supplement 0 |
869
|
|
|
>> def |
870
|
|
|
/CMapName /Adobe-Identity-UCS def |
871
|
|
|
/CMapType 2 def |
872
|
|
|
1 begincodespacerange |
873
|
|
|
<0000> <FFFF> |
874
|
|
|
endcodespacerange |
875
|
|
|
1 beginbfrange |
876
|
|
|
<0000> <FFFF> <0000> |
877
|
|
|
endbfrange |
878
|
|
|
endcmap |
879
|
|
|
CMapName currentdict /CMap defineresource pop |
880
|
|
|
end |
881
|
|
|
end |
882
|
|
|
EOT; |
883
|
|
|
|
884
|
|
|
$res = "<</Length " . mb_strlen($stream, '8bit') . " >>\n"; |
885
|
|
|
$res .= "stream\n" . $stream . "\nendstream"; |
886
|
|
|
|
887
|
|
|
$this->objects[$toUnicodeId]['c'] = $res; |
888
|
|
|
|
889
|
|
|
$cidFontId = ++$this->numObj; |
890
|
|
|
$this->o_fontDescendentCID($cidFontId, 'new', $options); |
891
|
|
|
$this->objects[$id]['info']['cidFont'] = $cidFontId; |
892
|
|
|
} |
893
|
|
|
|
894
|
|
|
// also tell the pages node about the new font |
895
|
|
|
$this->o_pages($this->currentNode, 'font', array('fontNum' => $fontNum, 'objNum' => $id)); |
|
|
|
|
896
|
|
|
break; |
897
|
|
|
|
898
|
|
|
case 'add': |
899
|
|
|
foreach ($options as $k => $v) { |
|
|
|
|
900
|
|
|
switch ($k) { |
901
|
|
|
case 'BaseFont': |
902
|
|
|
$o['info']['name'] = $v; |
|
|
|
|
903
|
|
|
break; |
904
|
|
|
case 'FirstChar': |
905
|
|
|
case 'LastChar': |
906
|
|
|
case 'Widths': |
907
|
|
|
case 'FontDescriptor': |
908
|
|
|
case 'SubType': |
909
|
|
|
$this->addMessage('o_font ' . $k . " : " . $v); |
910
|
|
|
$o['info'][$k] = $v; |
911
|
|
|
break; |
912
|
|
|
} |
913
|
|
|
} |
914
|
|
|
|
915
|
|
|
// pass values down to descendent font |
916
|
|
|
if (isset($o['info']['cidFont'])) { |
917
|
|
|
$this->o_fontDescendentCID($o['info']['cidFont'], 'add', $options); |
918
|
|
|
} |
919
|
|
|
break; |
920
|
|
|
|
921
|
|
|
case 'out': |
922
|
|
|
if ($this->fonts[$this->objects[$id]['info']['fontFileName']]['isUnicode']) { |
923
|
|
|
// For Unicode fonts, we need to incorporate font data into |
924
|
|
|
// sub-sections that are linked from the primary font section. |
925
|
|
|
// Look at o_fontGIDtoCID and o_fontDescendentCID functions |
926
|
|
|
// for more information. |
927
|
|
|
// |
928
|
|
|
// All of this code is adapted from the excellent changes made to |
929
|
|
|
// transform FPDF to TCPDF (http://tcpdf.sourceforge.net/) |
930
|
|
|
|
931
|
|
|
$res = "\n$id 0 obj\n<</Type /Font\n/Subtype /Type0\n"; |
932
|
|
|
$res .= "/BaseFont /" . $o['info']['name'] . "\n"; |
933
|
|
|
|
934
|
|
|
// The horizontal identity mapping for 2-byte CIDs; may be used |
935
|
|
|
// with CIDFonts using any Registry, Ordering, and Supplement values. |
936
|
|
|
$res .= "/Encoding /Identity-H\n"; |
937
|
|
|
$res .= "/DescendantFonts [" . $o['info']['cidFont'] . " 0 R]\n"; |
938
|
|
|
$res .= "/ToUnicode " . $o['info']['toUnicode'] . " 0 R\n"; |
939
|
|
|
$res .= ">>\n"; |
940
|
|
|
$res .= "endobj"; |
941
|
|
|
} else { |
942
|
|
|
$res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /" . $o['info']['SubType'] . "\n"; |
943
|
|
|
$res .= "/Name /F" . $o['info']['fontNum'] . "\n"; |
944
|
|
|
$res .= "/BaseFont /" . $o['info']['name'] . "\n"; |
945
|
|
|
|
946
|
|
|
if (isset($o['info']['encodingDictionary'])) { |
947
|
|
|
// then place a reference to the dictionary |
948
|
|
|
$res .= "/Encoding " . $o['info']['encodingDictionary'] . " 0 R\n"; |
949
|
|
View Code Duplication |
} else { |
|
|
|
|
950
|
|
|
if (isset($o['info']['encoding'])) { |
951
|
|
|
// use the specified encoding |
952
|
|
|
$res .= "/Encoding /" . $o['info']['encoding'] . "\n"; |
953
|
|
|
} |
954
|
|
|
} |
955
|
|
|
|
956
|
|
|
if (isset($o['info']['FirstChar'])) { |
957
|
|
|
$res .= "/FirstChar " . $o['info']['FirstChar'] . "\n"; |
958
|
|
|
} |
959
|
|
|
|
960
|
|
|
if (isset($o['info']['LastChar'])) { |
961
|
|
|
$res .= "/LastChar " . $o['info']['LastChar'] . "\n"; |
962
|
|
|
} |
963
|
|
|
|
964
|
|
|
if (isset($o['info']['Widths'])) { |
965
|
|
|
$res .= "/Widths " . $o['info']['Widths'] . " 0 R\n"; |
966
|
|
|
} |
967
|
|
|
|
968
|
|
View Code Duplication |
if (isset($o['info']['FontDescriptor'])) { |
|
|
|
|
969
|
|
|
$res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n"; |
970
|
|
|
} |
971
|
|
|
|
972
|
|
|
$res .= ">>\n"; |
973
|
|
|
$res .= "endobj"; |
974
|
|
|
} |
975
|
|
|
|
976
|
|
|
return $res; |
977
|
|
|
} |
978
|
|
|
|
979
|
|
|
return null; |
980
|
|
|
} |
981
|
|
|
|
982
|
|
|
/** |
983
|
|
|
* a font descriptor, needed for including additional fonts |
984
|
|
|
* |
985
|
|
|
* @param $id |
986
|
|
|
* @param $action |
987
|
|
|
* @param string $options |
988
|
|
|
* @return null|string |
989
|
|
|
*/ |
990
|
|
|
protected function o_fontDescriptor($id, $action, $options = '') |
991
|
|
|
{ |
992
|
|
|
if ($action !== 'new') { |
993
|
|
|
$o = &$this->objects[$id]; |
994
|
|
|
} |
995
|
|
|
|
996
|
|
|
switch ($action) { |
997
|
|
|
case 'new': |
998
|
|
|
$this->objects[$id] = array('t' => 'fontDescriptor', 'info' => $options); |
999
|
|
|
break; |
1000
|
|
|
|
1001
|
|
|
case 'out': |
1002
|
|
|
$res = "\n$id 0 obj\n<< /Type /FontDescriptor\n"; |
1003
|
|
|
foreach ($o['info'] as $label => $value) { |
|
|
|
|
1004
|
|
|
switch ($label) { |
1005
|
|
|
case 'Ascent': |
1006
|
|
|
case 'CapHeight': |
1007
|
|
|
case 'Descent': |
1008
|
|
|
case 'Flags': |
1009
|
|
|
case 'ItalicAngle': |
1010
|
|
|
case 'StemV': |
1011
|
|
|
case 'AvgWidth': |
1012
|
|
|
case 'Leading': |
1013
|
|
|
case 'MaxWidth': |
1014
|
|
|
case 'MissingWidth': |
1015
|
|
|
case 'StemH': |
1016
|
|
|
case 'XHeight': |
1017
|
|
|
case 'CharSet': |
1018
|
|
|
if (mb_strlen($value, '8bit')) { |
1019
|
|
|
$res .= "/$label $value\n"; |
1020
|
|
|
} |
1021
|
|
|
|
1022
|
|
|
break; |
1023
|
|
|
case 'FontFile': |
1024
|
|
|
case 'FontFile2': |
1025
|
|
|
case 'FontFile3': |
1026
|
|
|
$res .= "/$label $value 0 R\n"; |
1027
|
|
|
break; |
1028
|
|
|
|
1029
|
|
|
case 'FontBBox': |
1030
|
|
|
$res .= "/$label [$value[0] $value[1] $value[2] $value[3]]\n"; |
1031
|
|
|
break; |
1032
|
|
|
|
1033
|
|
|
case 'FontName': |
1034
|
|
|
$res .= "/$label /$value\n"; |
1035
|
|
|
break; |
1036
|
|
|
} |
1037
|
|
|
} |
1038
|
|
|
|
1039
|
|
|
$res .= ">>\nendobj"; |
1040
|
|
|
|
1041
|
|
|
return $res; |
1042
|
|
|
} |
1043
|
|
|
|
1044
|
|
|
return null; |
1045
|
|
|
} |
1046
|
|
|
|
1047
|
|
|
/** |
1048
|
|
|
* the font encoding |
1049
|
|
|
* |
1050
|
|
|
* @param $id |
1051
|
|
|
* @param $action |
1052
|
|
|
* @param string $options |
1053
|
|
|
* @return null|string |
1054
|
|
|
*/ |
1055
|
|
|
protected function o_fontEncoding($id, $action, $options = '') |
1056
|
|
|
{ |
1057
|
|
|
if ($action !== 'new') { |
1058
|
|
|
$o = &$this->objects[$id]; |
1059
|
|
|
} |
1060
|
|
|
|
1061
|
|
|
switch ($action) { |
1062
|
|
|
case 'new': |
1063
|
|
|
// the options array should contain 'differences' and maybe 'encoding' |
1064
|
|
|
$this->objects[$id] = array('t' => 'fontEncoding', 'info' => $options); |
1065
|
|
|
break; |
1066
|
|
|
|
1067
|
|
|
case 'out': |
1068
|
|
|
$res = "\n$id 0 obj\n<< /Type /Encoding\n"; |
1069
|
|
|
if (!isset($o['info']['encoding'])) { |
1070
|
|
|
$o['info']['encoding'] = 'WinAnsiEncoding'; |
|
|
|
|
1071
|
|
|
} |
1072
|
|
|
|
1073
|
|
View Code Duplication |
if ($o['info']['encoding'] !== 'none') { |
|
|
|
|
1074
|
|
|
$res .= "/BaseEncoding /" . $o['info']['encoding'] . "\n"; |
1075
|
|
|
} |
1076
|
|
|
|
1077
|
|
|
$res .= "/Differences \n["; |
1078
|
|
|
|
1079
|
|
|
$onum = -100; |
1080
|
|
|
|
1081
|
|
|
foreach ($o['info']['differences'] as $num => $label) { |
1082
|
|
|
if ($num != $onum + 1) { |
1083
|
|
|
// we cannot make use of consecutive numbering |
1084
|
|
|
$res .= "\n$num /$label"; |
1085
|
|
|
} else { |
1086
|
|
|
$res .= " /$label"; |
1087
|
|
|
} |
1088
|
|
|
|
1089
|
|
|
$onum = $num; |
1090
|
|
|
} |
1091
|
|
|
|
1092
|
|
|
$res .= "\n]\n>>\nendobj"; |
1093
|
|
|
|
1094
|
|
|
return $res; |
1095
|
|
|
} |
1096
|
|
|
|
1097
|
|
|
return null; |
1098
|
|
|
} |
1099
|
|
|
|
1100
|
|
|
/** |
1101
|
|
|
* a descendent cid font, needed for unicode fonts |
1102
|
|
|
* |
1103
|
|
|
* @param $id |
1104
|
|
|
* @param $action |
1105
|
|
|
* @param string|array $options |
1106
|
|
|
* @return null|string |
1107
|
|
|
*/ |
1108
|
|
|
protected function o_fontDescendentCID($id, $action, $options = '') |
1109
|
|
|
{ |
1110
|
|
|
if ($action !== 'new') { |
1111
|
|
|
$o = &$this->objects[$id]; |
1112
|
|
|
} |
1113
|
|
|
|
1114
|
|
|
switch ($action) { |
1115
|
|
|
case 'new': |
1116
|
|
|
$this->objects[$id] = array('t' => 'fontDescendentCID', 'info' => $options); |
1117
|
|
|
|
1118
|
|
|
// we need a CID system info section |
1119
|
|
|
$cidSystemInfoId = ++$this->numObj; |
1120
|
|
|
$this->o_contents($cidSystemInfoId, 'new', 'raw'); |
1121
|
|
|
$this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId; |
1122
|
|
|
$res = "<</Registry (Adobe)\n"; // A string identifying an issuer of character collections |
1123
|
|
|
$res .= "/Ordering (UCS)\n"; // A string that uniquely names a character collection issued by a specific registry |
1124
|
|
|
$res .= "/Supplement 0\n"; // The supplement number of the character collection. |
1125
|
|
|
$res .= ">>"; |
1126
|
|
|
$this->objects[$cidSystemInfoId]['c'] = $res; |
1127
|
|
|
|
1128
|
|
|
// and a CID to GID map |
1129
|
|
|
$cidToGidMapId = ++$this->numObj; |
1130
|
|
|
$this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options); |
|
|
|
|
1131
|
|
|
$this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId; |
1132
|
|
|
break; |
1133
|
|
|
|
1134
|
|
|
case 'add': |
1135
|
|
|
foreach ($options as $k => $v) { |
|
|
|
|
1136
|
|
|
switch ($k) { |
1137
|
|
|
case 'BaseFont': |
1138
|
|
|
$o['info']['name'] = $v; |
|
|
|
|
1139
|
|
|
break; |
1140
|
|
|
|
1141
|
|
|
case 'FirstChar': |
1142
|
|
|
case 'LastChar': |
1143
|
|
|
case 'MissingWidth': |
1144
|
|
|
case 'FontDescriptor': |
1145
|
|
|
case 'SubType': |
1146
|
|
|
$this->addMessage("o_fontDescendentCID $k : $v"); |
1147
|
|
|
$o['info'][$k] = $v; |
1148
|
|
|
break; |
1149
|
|
|
} |
1150
|
|
|
} |
1151
|
|
|
|
1152
|
|
|
// pass values down to cid to gid map |
1153
|
|
|
$this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options); |
|
|
|
|
1154
|
|
|
break; |
1155
|
|
|
|
1156
|
|
|
case 'out': |
1157
|
|
|
$res = "\n$id 0 obj\n"; |
1158
|
|
|
$res .= "<</Type /Font\n"; |
1159
|
|
|
$res .= "/Subtype /CIDFontType2\n"; |
1160
|
|
|
$res .= "/BaseFont /" . $o['info']['name'] . "\n"; |
1161
|
|
|
$res .= "/CIDSystemInfo " . $o['info']['cidSystemInfo'] . " 0 R\n"; |
1162
|
|
|
// if (isset($o['info']['FirstChar'])) { |
|
|
|
|
1163
|
|
|
// $res.= "/FirstChar ".$o['info']['FirstChar']."\n"; |
|
|
|
|
1164
|
|
|
// } |
1165
|
|
|
|
1166
|
|
|
// if (isset($o['info']['LastChar'])) { |
|
|
|
|
1167
|
|
|
// $res.= "/LastChar ".$o['info']['LastChar']."\n"; |
|
|
|
|
1168
|
|
|
// } |
1169
|
|
View Code Duplication |
if (isset($o['info']['FontDescriptor'])) { |
|
|
|
|
1170
|
|
|
$res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n"; |
1171
|
|
|
} |
1172
|
|
|
|
1173
|
|
|
if (isset($o['info']['MissingWidth'])) { |
1174
|
|
|
$res .= "/DW " . $o['info']['MissingWidth'] . "\n"; |
1175
|
|
|
} |
1176
|
|
|
|
1177
|
|
|
if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) { |
1178
|
|
|
$cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths']; |
1179
|
|
|
$w = ''; |
1180
|
|
|
foreach ($cid_widths as $cid => $width) { |
1181
|
|
|
$w .= "$cid [$width] "; |
1182
|
|
|
} |
1183
|
|
|
$res .= "/W [$w]\n"; |
1184
|
|
|
} |
1185
|
|
|
|
1186
|
|
|
$res .= "/CIDToGIDMap " . $o['info']['cidToGidMap'] . " 0 R\n"; |
1187
|
|
|
$res .= ">>\n"; |
1188
|
|
|
$res .= "endobj"; |
1189
|
|
|
|
1190
|
|
|
return $res; |
1191
|
|
|
} |
1192
|
|
|
|
1193
|
|
|
return null; |
1194
|
|
|
} |
1195
|
|
|
|
1196
|
|
|
/** |
1197
|
|
|
* a font glyph to character map, needed for unicode fonts |
1198
|
|
|
* |
1199
|
|
|
* @param $id |
1200
|
|
|
* @param $action |
1201
|
|
|
* @param string $options |
1202
|
|
|
* @return null|string |
1203
|
|
|
*/ |
1204
|
|
|
protected function o_fontGIDtoCIDMap($id, $action, $options = '') |
1205
|
|
|
{ |
1206
|
|
|
if ($action !== 'new') { |
1207
|
|
|
$o = &$this->objects[$id]; |
1208
|
|
|
} |
1209
|
|
|
|
1210
|
|
|
switch ($action) { |
1211
|
|
|
case 'new': |
1212
|
|
|
$this->objects[$id] = array('t' => 'fontGIDtoCIDMap', 'info' => $options); |
1213
|
|
|
break; |
1214
|
|
|
|
1215
|
|
|
case 'out': |
1216
|
|
|
$res = "\n$id 0 obj\n"; |
1217
|
|
|
$fontFileName = $o['info']['fontFileName']; |
|
|
|
|
1218
|
|
|
$tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']); |
1219
|
|
|
|
1220
|
|
|
$compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) && |
1221
|
|
|
$this->fonts[$fontFileName]['CIDtoGID_Compressed']; |
1222
|
|
|
|
1223
|
|
|
if (!$compressed && isset($o['raw'])) { |
1224
|
|
|
$res .= $tmp; |
1225
|
|
|
} else { |
1226
|
|
|
$res .= "<<"; |
1227
|
|
|
|
1228
|
|
View Code Duplication |
if (!$compressed && $this->compressionReady && $this->options['compression']) { |
|
|
|
|
1229
|
|
|
// then implement ZLIB based compression on this content stream |
1230
|
|
|
$compressed = true; |
1231
|
|
|
$tmp = gzcompress($tmp, 6); |
1232
|
|
|
} |
1233
|
|
|
if ($compressed) { |
1234
|
|
|
$res .= "\n/Filter /FlateDecode"; |
1235
|
|
|
} |
1236
|
|
|
|
1237
|
|
|
if ($this->encrypted) { |
1238
|
|
|
$this->encryptInit($id); |
1239
|
|
|
$tmp = $this->ARC4($tmp); |
1240
|
|
|
} |
1241
|
|
|
|
1242
|
|
|
$res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream"; |
1243
|
|
|
} |
1244
|
|
|
|
1245
|
|
|
$res .= "\nendobj"; |
1246
|
|
|
|
1247
|
|
|
return $res; |
1248
|
|
|
} |
1249
|
|
|
|
1250
|
|
|
return null; |
1251
|
|
|
} |
1252
|
|
|
|
1253
|
|
|
/** |
1254
|
|
|
* the document procset, solves some problems with printing to old PS printers |
1255
|
|
|
* |
1256
|
|
|
* @param $id |
1257
|
|
|
* @param $action |
1258
|
|
|
* @param string $options |
1259
|
|
|
* @return null|string |
1260
|
|
|
*/ |
1261
|
|
|
protected function o_procset($id, $action, $options = '') |
1262
|
|
|
{ |
1263
|
|
|
if ($action !== 'new') { |
1264
|
|
|
$o = &$this->objects[$id]; |
1265
|
|
|
} |
1266
|
|
|
|
1267
|
|
|
switch ($action) { |
1268
|
|
View Code Duplication |
case 'new': |
|
|
|
|
1269
|
|
|
$this->objects[$id] = array('t' => 'procset', 'info' => array('PDF' => 1, 'Text' => 1)); |
1270
|
|
|
$this->o_pages($this->currentNode, 'procset', $id); |
1271
|
|
|
$this->procsetObjectId = $id; |
1272
|
|
|
break; |
1273
|
|
|
|
1274
|
|
|
case 'add': |
1275
|
|
|
// this is to add new items to the procset list, despite the fact that this is considered |
1276
|
|
|
// obsolete, the items are required for printing to some postscript printers |
1277
|
|
|
switch ($options) { |
1278
|
|
|
case 'ImageB': |
1279
|
|
|
case 'ImageC': |
1280
|
|
|
case 'ImageI': |
1281
|
|
|
$o['info'][$options] = 1; |
|
|
|
|
1282
|
|
|
break; |
1283
|
|
|
} |
1284
|
|
|
break; |
1285
|
|
|
|
1286
|
|
|
case 'out': |
1287
|
|
|
$res = "\n$id 0 obj\n["; |
1288
|
|
|
foreach ($o['info'] as $label => $val) { |
1289
|
|
|
$res .= "/$label "; |
1290
|
|
|
} |
1291
|
|
|
$res .= "]\nendobj"; |
1292
|
|
|
|
1293
|
|
|
return $res; |
1294
|
|
|
} |
1295
|
|
|
|
1296
|
|
|
return null; |
1297
|
|
|
} |
1298
|
|
|
|
1299
|
|
|
/** |
1300
|
|
|
* define the document information |
1301
|
|
|
* |
1302
|
|
|
* @param $id |
1303
|
|
|
* @param $action |
1304
|
|
|
* @param string $options |
1305
|
|
|
* @return null|string |
1306
|
|
|
*/ |
1307
|
|
|
protected function o_info($id, $action, $options = '') |
1308
|
|
|
{ |
1309
|
|
|
switch ($action) { |
1310
|
|
|
case 'new': |
1311
|
|
|
$this->infoObject = $id; |
1312
|
|
|
$date = 'D:' . @date('Ymd'); |
1313
|
|
|
$this->objects[$id] = array( |
1314
|
|
|
't' => 'info', |
1315
|
|
|
'info' => array( |
1316
|
|
|
'Producer' => 'CPDF (dompdf)', |
1317
|
|
|
'CreationDate' => $date |
1318
|
|
|
) |
1319
|
|
|
); |
1320
|
|
|
break; |
1321
|
|
|
case 'Title': |
1322
|
|
|
case 'Author': |
1323
|
|
|
case 'Subject': |
1324
|
|
|
case 'Keywords': |
1325
|
|
|
case 'Creator': |
1326
|
|
|
case 'Producer': |
1327
|
|
|
case 'CreationDate': |
1328
|
|
|
case 'ModDate': |
1329
|
|
|
case 'Trapped': |
1330
|
|
|
$this->objects[$id]['info'][$action] = $options; |
1331
|
|
|
break; |
1332
|
|
|
|
1333
|
|
|
case 'out': |
1334
|
|
|
$encrypted = $this->encrypted; |
1335
|
|
|
if ($encrypted) { |
1336
|
|
|
$this->encryptInit($id); |
1337
|
|
|
} |
1338
|
|
|
|
1339
|
|
|
$res = "\n$id 0 obj\n<<\n"; |
1340
|
|
|
$o = &$this->objects[$id]; |
1341
|
|
|
foreach ($o['info'] as $k => $v) { |
1342
|
|
|
$res .= "/$k ("; |
1343
|
|
|
|
1344
|
|
|
// dates must be outputted as-is, without Unicode transformations |
1345
|
|
|
if ($k !== 'CreationDate' && $k !== 'ModDate') { |
1346
|
|
|
$v = $this->filterText($v, true, false); |
1347
|
|
|
} |
1348
|
|
|
|
1349
|
|
|
if ($encrypted) { |
1350
|
|
|
$v = $this->ARC4($v); |
1351
|
|
|
} |
1352
|
|
|
|
1353
|
|
|
$res .= $v; |
1354
|
|
|
$res .= ")\n"; |
1355
|
|
|
} |
1356
|
|
|
|
1357
|
|
|
$res .= ">>\nendobj"; |
1358
|
|
|
|
1359
|
|
|
return $res; |
1360
|
|
|
} |
1361
|
|
|
|
1362
|
|
|
return null; |
1363
|
|
|
} |
1364
|
|
|
|
1365
|
|
|
/** |
1366
|
|
|
* an action object, used to link to URLS initially |
1367
|
|
|
* |
1368
|
|
|
* @param $id |
1369
|
|
|
* @param $action |
1370
|
|
|
* @param string $options |
1371
|
|
|
* @return null|string |
1372
|
|
|
*/ |
1373
|
|
|
protected function o_action($id, $action, $options = '') |
1374
|
|
|
{ |
1375
|
|
|
if ($action !== 'new') { |
1376
|
|
|
$o = &$this->objects[$id]; |
1377
|
|
|
} |
1378
|
|
|
|
1379
|
|
|
switch ($action) { |
1380
|
|
|
case 'new': |
1381
|
|
|
if (is_array($options)) { |
1382
|
|
|
$this->objects[$id] = array('t' => 'action', 'info' => $options, 'type' => $options['type']); |
1383
|
|
|
} else { |
1384
|
|
|
// then assume a URI action |
1385
|
|
|
$this->objects[$id] = array('t' => 'action', 'info' => $options, 'type' => 'URI'); |
1386
|
|
|
} |
1387
|
|
|
break; |
1388
|
|
|
|
1389
|
|
|
case 'out': |
1390
|
|
|
if ($this->encrypted) { |
1391
|
|
|
$this->encryptInit($id); |
1392
|
|
|
} |
1393
|
|
|
|
1394
|
|
|
$res = "\n$id 0 obj\n<< /Type /Action"; |
1395
|
|
|
switch ($o['type']) { |
|
|
|
|
1396
|
|
|
case 'ilink': |
1397
|
|
|
if (!isset($this->destinations[(string)$o['info']['label']])) { |
1398
|
|
|
break; |
1399
|
|
|
} |
1400
|
|
|
|
1401
|
|
|
// there will be an 'label' setting, this is the name of the destination |
1402
|
|
|
$res .= "\n/S /GoTo\n/D " . $this->destinations[(string)$o['info']['label']] . " 0 R"; |
1403
|
|
|
break; |
1404
|
|
|
|
1405
|
|
|
case 'URI': |
1406
|
|
|
$res .= "\n/S /URI\n/URI ("; |
1407
|
|
|
if ($this->encrypted) { |
1408
|
|
|
$res .= $this->filterText($this->ARC4($o['info']), true, false); |
1409
|
|
|
} else { |
1410
|
|
|
$res .= $this->filterText($o['info'], true, false); |
1411
|
|
|
} |
1412
|
|
|
|
1413
|
|
|
$res .= ")"; |
1414
|
|
|
break; |
1415
|
|
|
} |
1416
|
|
|
|
1417
|
|
|
$res .= "\n>>\nendobj"; |
1418
|
|
|
|
1419
|
|
|
return $res; |
1420
|
|
|
} |
1421
|
|
|
|
1422
|
|
|
return null; |
1423
|
|
|
} |
1424
|
|
|
|
1425
|
|
|
/** |
1426
|
|
|
* an annotation object, this will add an annotation to the current page. |
1427
|
|
|
* initially will support just link annotations |
1428
|
|
|
* |
1429
|
|
|
* @param $id |
1430
|
|
|
* @param $action |
1431
|
|
|
* @param string $options |
1432
|
|
|
* @return null|string |
1433
|
|
|
*/ |
1434
|
|
|
protected function o_annotation($id, $action, $options = '') |
1435
|
|
|
{ |
1436
|
|
|
if ($action !== 'new') { |
1437
|
|
|
$o = &$this->objects[$id]; |
1438
|
|
|
} |
1439
|
|
|
|
1440
|
|
|
switch ($action) { |
1441
|
|
|
case 'new': |
1442
|
|
|
// add the annotation to the current page |
1443
|
|
|
$pageId = $this->currentPage; |
1444
|
|
|
$this->o_page($pageId, 'annot', $id); |
1445
|
|
|
|
1446
|
|
|
// and add the action object which is going to be required |
1447
|
|
|
switch ($options['type']) { |
1448
|
|
|
case 'link': |
1449
|
|
|
$this->objects[$id] = array('t' => 'annotation', 'info' => $options); |
1450
|
|
|
$this->numObj++; |
1451
|
|
|
$this->o_action($this->numObj, 'new', $options['url']); |
1452
|
|
|
$this->objects[$id]['info']['actionId'] = $this->numObj; |
1453
|
|
|
break; |
1454
|
|
|
|
1455
|
|
|
case 'ilink': |
1456
|
|
|
// this is to a named internal link |
1457
|
|
|
$label = $options['label']; |
1458
|
|
|
$this->objects[$id] = array('t' => 'annotation', 'info' => $options); |
1459
|
|
|
$this->numObj++; |
1460
|
|
|
$this->o_action($this->numObj, 'new', array('type' => 'ilink', 'label' => $label)); |
|
|
|
|
1461
|
|
|
$this->objects[$id]['info']['actionId'] = $this->numObj; |
1462
|
|
|
break; |
1463
|
|
|
} |
1464
|
|
|
break; |
1465
|
|
|
|
1466
|
|
|
case 'out': |
1467
|
|
|
$res = "\n$id 0 obj\n<< /Type /Annot"; |
1468
|
|
|
switch ($o['info']['type']) { |
|
|
|
|
1469
|
|
|
case 'link': |
1470
|
|
|
case 'ilink': |
1471
|
|
|
$res .= "\n/Subtype /Link"; |
1472
|
|
|
break; |
1473
|
|
|
} |
1474
|
|
|
$res .= "\n/A " . $o['info']['actionId'] . " 0 R"; |
1475
|
|
|
$res .= "\n/Border [0 0 0]"; |
1476
|
|
|
$res .= "\n/H /I"; |
1477
|
|
|
$res .= "\n/Rect [ "; |
1478
|
|
|
|
1479
|
|
|
foreach ($o['info']['rect'] as $v) { |
1480
|
|
|
$res .= sprintf("%.4F ", $v); |
1481
|
|
|
} |
1482
|
|
|
|
1483
|
|
|
$res .= "]"; |
1484
|
|
|
$res .= "\n>>\nendobj"; |
1485
|
|
|
|
1486
|
|
|
return $res; |
1487
|
|
|
} |
1488
|
|
|
|
1489
|
|
|
return null; |
1490
|
|
|
} |
1491
|
|
|
|
1492
|
|
|
/** |
1493
|
|
|
* a page object, it also creates a contents object to hold its contents |
1494
|
|
|
* |
1495
|
|
|
* @param $id |
1496
|
|
|
* @param $action |
1497
|
|
|
* @param string $options |
1498
|
|
|
* @return null|string |
1499
|
|
|
*/ |
1500
|
|
|
protected function o_page($id, $action, $options = '') |
1501
|
|
|
{ |
1502
|
|
|
if ($action !== 'new') { |
1503
|
|
|
$o = &$this->objects[$id]; |
1504
|
|
|
} |
1505
|
|
|
|
1506
|
|
|
switch ($action) { |
1507
|
|
|
case 'new': |
1508
|
|
|
$this->numPages++; |
1509
|
|
|
$this->objects[$id] = array( |
1510
|
|
|
't' => 'page', |
1511
|
|
|
'info' => array( |
1512
|
|
|
'parent' => $this->currentNode, |
1513
|
|
|
'pageNum' => $this->numPages, |
1514
|
|
|
'mediaBox' => $this->objects[$this->currentNode]['info']['mediaBox'] |
1515
|
|
|
) |
1516
|
|
|
); |
1517
|
|
|
|
1518
|
|
|
if (is_array($options)) { |
1519
|
|
|
// then this must be a page insertion, array should contain 'rid','pos'=[before|after] |
1520
|
|
|
$options['id'] = $id; |
1521
|
|
|
$this->o_pages($this->currentNode, 'page', $options); |
|
|
|
|
1522
|
|
|
} else { |
1523
|
|
|
$this->o_pages($this->currentNode, 'page', $id); |
1524
|
|
|
} |
1525
|
|
|
|
1526
|
|
|
$this->currentPage = $id; |
1527
|
|
|
//make a contents object to go with this page |
1528
|
|
|
$this->numObj++; |
1529
|
|
|
$this->o_contents($this->numObj, 'new', $id); |
1530
|
|
|
$this->currentContents = $this->numObj; |
1531
|
|
|
$this->objects[$id]['info']['contents'] = array(); |
1532
|
|
|
$this->objects[$id]['info']['contents'][] = $this->numObj; |
1533
|
|
|
|
1534
|
|
|
$match = ($this->numPages % 2 ? 'odd' : 'even'); |
1535
|
|
|
foreach ($this->addLooseObjects as $oId => $target) { |
1536
|
|
|
if ($target === 'all' || $match === $target) { |
1537
|
|
|
$this->objects[$id]['info']['contents'][] = $oId; |
1538
|
|
|
} |
1539
|
|
|
} |
1540
|
|
|
break; |
1541
|
|
|
|
1542
|
|
|
case 'content': |
1543
|
|
|
$o['info']['contents'][] = $options; |
|
|
|
|
1544
|
|
|
break; |
1545
|
|
|
|
1546
|
|
|
case 'annot': |
1547
|
|
|
// add an annotation to this page |
1548
|
|
|
if (!isset($o['info']['annot'])) { |
1549
|
|
|
$o['info']['annot'] = array(); |
1550
|
|
|
} |
1551
|
|
|
|
1552
|
|
|
// $options should contain the id of the annotation dictionary |
1553
|
|
|
$o['info']['annot'][] = $options; |
1554
|
|
|
break; |
1555
|
|
|
|
1556
|
|
|
case 'out': |
1557
|
|
|
$res = "\n$id 0 obj\n<< /Type /Page"; |
1558
|
|
View Code Duplication |
if (isset($o['info']['mediaBox'])) { |
|
|
|
|
1559
|
|
|
$tmp = $o['info']['mediaBox']; |
1560
|
|
|
$res .= "\n/MediaBox [" . sprintf( |
1561
|
|
|
'%.3F %.3F %.3F %.3F', |
1562
|
|
|
$tmp[0], |
1563
|
|
|
$tmp[1], |
1564
|
|
|
$tmp[2], |
1565
|
|
|
$tmp[3] |
1566
|
|
|
) . ']'; |
1567
|
|
|
} |
1568
|
|
|
$res .= "\n/Parent " . $o['info']['parent'] . " 0 R"; |
1569
|
|
|
|
1570
|
|
|
if (isset($o['info']['annot'])) { |
1571
|
|
|
$res .= "\n/Annots ["; |
1572
|
|
|
foreach ($o['info']['annot'] as $aId) { |
1573
|
|
|
$res .= " $aId 0 R"; |
1574
|
|
|
} |
1575
|
|
|
$res .= " ]"; |
1576
|
|
|
} |
1577
|
|
|
|
1578
|
|
|
$count = count($o['info']['contents']); |
1579
|
|
|
if ($count == 1) { |
1580
|
|
|
$res .= "\n/Contents " . $o['info']['contents'][0] . " 0 R"; |
1581
|
|
|
} else { |
1582
|
|
|
if ($count > 1) { |
1583
|
|
|
$res .= "\n/Contents [\n"; |
1584
|
|
|
|
1585
|
|
|
// reverse the page contents so added objects are below normal content |
1586
|
|
|
//foreach (array_reverse($o['info']['contents']) as $cId) { |
|
|
|
|
1587
|
|
|
// Back to normal now that I've got transparency working --Benj |
1588
|
|
|
foreach ($o['info']['contents'] as $cId) { |
1589
|
|
|
$res .= "$cId 0 R\n"; |
1590
|
|
|
} |
1591
|
|
|
$res .= "]"; |
1592
|
|
|
} |
1593
|
|
|
} |
1594
|
|
|
|
1595
|
|
|
$res .= "\n>>\nendobj"; |
1596
|
|
|
|
1597
|
|
|
return $res; |
1598
|
|
|
} |
1599
|
|
|
|
1600
|
|
|
return null; |
1601
|
|
|
} |
1602
|
|
|
|
1603
|
|
|
/** |
1604
|
|
|
* the contents objects hold all of the content which appears on pages |
1605
|
|
|
* |
1606
|
|
|
* @param $id |
1607
|
|
|
* @param $action |
1608
|
|
|
* @param string|array $options |
1609
|
|
|
* @return null|string |
1610
|
|
|
*/ |
1611
|
|
|
protected function o_contents($id, $action, $options = '') |
1612
|
|
|
{ |
1613
|
|
|
if ($action !== 'new') { |
1614
|
|
|
$o = &$this->objects[$id]; |
1615
|
|
|
} |
1616
|
|
|
|
1617
|
|
|
switch ($action) { |
1618
|
|
|
case 'new': |
1619
|
|
|
$this->objects[$id] = array('t' => 'contents', 'c' => '', 'info' => array()); |
1620
|
|
|
if (mb_strlen($options, '8bit') && intval($options)) { |
1621
|
|
|
// then this contents is the primary for a page |
1622
|
|
|
$this->objects[$id]['onPage'] = $options; |
1623
|
|
|
} else { |
1624
|
|
|
if ($options === 'raw') { |
1625
|
|
|
// then this page contains some other type of system object |
1626
|
|
|
$this->objects[$id]['raw'] = 1; |
1627
|
|
|
} |
1628
|
|
|
} |
1629
|
|
|
break; |
1630
|
|
|
|
1631
|
|
|
case 'add': |
|
|
|
|
1632
|
|
|
// add more options to the declaration |
1633
|
|
|
foreach ($options as $k => $v) { |
|
|
|
|
1634
|
|
|
$o['info'][$k] = $v; |
|
|
|
|
1635
|
|
|
} |
1636
|
|
|
|
1637
|
|
|
case 'out': |
1638
|
|
|
$tmp = $o['c']; |
1639
|
|
|
$res = "\n$id 0 obj\n"; |
1640
|
|
|
|
1641
|
|
|
if (isset($this->objects[$id]['raw'])) { |
1642
|
|
|
$res .= $tmp; |
1643
|
|
|
} else { |
1644
|
|
|
$res .= "<<"; |
1645
|
|
View Code Duplication |
if ($this->compressionReady && $this->options['compression']) { |
|
|
|
|
1646
|
|
|
// then implement ZLIB based compression on this content stream |
1647
|
|
|
$res .= " /Filter /FlateDecode"; |
1648
|
|
|
$tmp = gzcompress($tmp, 6); |
1649
|
|
|
} |
1650
|
|
|
|
1651
|
|
|
if ($this->encrypted) { |
1652
|
|
|
$this->encryptInit($id); |
1653
|
|
|
$tmp = $this->ARC4($tmp); |
1654
|
|
|
} |
1655
|
|
|
|
1656
|
|
|
foreach ($o['info'] as $k => $v) { |
1657
|
|
|
$res .= "\n/$k $v"; |
1658
|
|
|
} |
1659
|
|
|
|
1660
|
|
|
$res .= "\n/Length " . mb_strlen($tmp, '8bit') . " >>\nstream\n$tmp\nendstream"; |
1661
|
|
|
} |
1662
|
|
|
|
1663
|
|
|
$res .= "\nendobj"; |
1664
|
|
|
|
1665
|
|
|
return $res; |
1666
|
|
|
} |
1667
|
|
|
|
1668
|
|
|
return null; |
1669
|
|
|
} |
1670
|
|
|
|
1671
|
|
|
/** |
1672
|
|
|
* @param $id |
1673
|
|
|
* @param $action |
1674
|
|
|
* @return string|null |
1675
|
|
|
*/ |
1676
|
|
|
protected function o_embedjs($id, $action) |
1677
|
|
|
{ |
1678
|
|
|
switch ($action) { |
1679
|
|
|
case 'new': |
1680
|
|
|
$this->objects[$id] = array( |
1681
|
|
|
't' => 'embedjs', |
1682
|
|
|
'info' => array( |
1683
|
|
|
'Names' => '[(EmbeddedJS) ' . ($id + 1) . ' 0 R]' |
1684
|
|
|
) |
1685
|
|
|
); |
1686
|
|
|
break; |
1687
|
|
|
|
1688
|
|
View Code Duplication |
case 'out': |
|
|
|
|
1689
|
|
|
$o = &$this->objects[$id]; |
1690
|
|
|
$res = "\n$id 0 obj\n<< "; |
1691
|
|
|
foreach ($o['info'] as $k => $v) { |
1692
|
|
|
$res .= "\n/$k $v"; |
1693
|
|
|
} |
1694
|
|
|
$res .= "\n>>\nendobj"; |
1695
|
|
|
|
1696
|
|
|
return $res; |
1697
|
|
|
} |
1698
|
|
|
|
1699
|
|
|
return null; |
1700
|
|
|
} |
1701
|
|
|
|
1702
|
|
|
/** |
1703
|
|
|
* @param $id |
1704
|
|
|
* @param $action |
1705
|
|
|
* @param string $code |
1706
|
|
|
* @return null|string |
1707
|
|
|
*/ |
1708
|
|
|
protected function o_javascript($id, $action, $code = '') |
1709
|
|
|
{ |
1710
|
|
|
switch ($action) { |
1711
|
|
|
case 'new': |
1712
|
|
|
$this->objects[$id] = array( |
1713
|
|
|
't' => 'javascript', |
1714
|
|
|
'info' => array( |
1715
|
|
|
'S' => '/JavaScript', |
1716
|
|
|
'JS' => '(' . $this->filterText($code, true, false) . ')', |
1717
|
|
|
) |
1718
|
|
|
); |
1719
|
|
|
break; |
1720
|
|
|
|
1721
|
|
View Code Duplication |
case 'out': |
|
|
|
|
1722
|
|
|
$o = &$this->objects[$id]; |
1723
|
|
|
$res = "\n$id 0 obj\n<< "; |
1724
|
|
|
|
1725
|
|
|
foreach ($o['info'] as $k => $v) { |
1726
|
|
|
$res .= "\n/$k $v"; |
1727
|
|
|
} |
1728
|
|
|
$res .= "\n>>\nendobj"; |
1729
|
|
|
|
1730
|
|
|
return $res; |
1731
|
|
|
} |
1732
|
|
|
|
1733
|
|
|
return null; |
1734
|
|
|
} |
1735
|
|
|
|
1736
|
|
|
/** |
1737
|
|
|
* an image object, will be an XObject in the document, includes description and data |
1738
|
|
|
* |
1739
|
|
|
* @param $id |
1740
|
|
|
* @param $action |
1741
|
|
|
* @param string $options |
1742
|
|
|
* @return null|string |
1743
|
|
|
*/ |
1744
|
|
|
protected function o_image($id, $action, $options = '') |
1745
|
|
|
{ |
1746
|
|
|
switch ($action) { |
1747
|
|
|
case 'new': |
1748
|
|
|
// make the new object |
1749
|
|
|
$this->objects[$id] = array('t' => 'image', 'data' => &$options['data'], 'info' => array()); |
1750
|
|
|
|
1751
|
|
|
$info =& $this->objects[$id]['info']; |
1752
|
|
|
|
1753
|
|
|
$info['Type'] = '/XObject'; |
1754
|
|
|
$info['Subtype'] = '/Image'; |
1755
|
|
|
$info['Width'] = $options['iw']; |
1756
|
|
|
$info['Height'] = $options['ih']; |
1757
|
|
|
|
1758
|
|
|
if (isset($options['masked']) && $options['masked']) { |
1759
|
|
|
$info['SMask'] = ($this->numObj - 1) . ' 0 R'; |
1760
|
|
|
} |
1761
|
|
|
|
1762
|
|
|
if (!isset($options['type']) || $options['type'] === 'jpg') { |
1763
|
|
|
if (!isset($options['channels'])) { |
1764
|
|
|
$options['channels'] = 3; |
1765
|
|
|
} |
1766
|
|
|
|
1767
|
|
|
switch ($options['channels']) { |
1768
|
|
|
case 1: |
|
|
|
|
1769
|
|
|
$info['ColorSpace'] = '/DeviceGray'; |
1770
|
|
|
break; |
1771
|
|
|
case 4: |
|
|
|
|
1772
|
|
|
$info['ColorSpace'] = '/DeviceCMYK'; |
1773
|
|
|
break; |
1774
|
|
|
default: |
1775
|
|
|
$info['ColorSpace'] = '/DeviceRGB'; |
1776
|
|
|
break; |
1777
|
|
|
} |
1778
|
|
|
|
1779
|
|
|
if ($info['ColorSpace'] === '/DeviceCMYK') { |
1780
|
|
|
$info['Decode'] = '[1 0 1 0 1 0 1 0]'; |
1781
|
|
|
} |
1782
|
|
|
|
1783
|
|
|
$info['Filter'] = '/DCTDecode'; |
1784
|
|
|
$info['BitsPerComponent'] = 8; |
1785
|
|
|
} else { |
1786
|
|
|
if ($options['type'] === 'png') { |
1787
|
|
|
$info['Filter'] = '/FlateDecode'; |
1788
|
|
|
$info['DecodeParms'] = '<< /Predictor 15 /Colors ' . $options['ncolor'] . ' /Columns ' . $options['iw'] . ' /BitsPerComponent ' . $options['bitsPerComponent'] . '>>'; |
1789
|
|
|
|
1790
|
|
|
if ($options['isMask']) { |
1791
|
|
|
$info['ColorSpace'] = '/DeviceGray'; |
1792
|
|
|
} else { |
1793
|
|
|
if (mb_strlen($options['pdata'], '8bit')) { |
1794
|
|
|
$tmp = ' [ /Indexed /DeviceRGB ' . (mb_strlen($options['pdata'], '8bit') / 3 - 1) . ' '; |
1795
|
|
|
$this->numObj++; |
1796
|
|
|
$this->o_contents($this->numObj, 'new'); |
1797
|
|
|
$this->objects[$this->numObj]['c'] = $options['pdata']; |
1798
|
|
|
$tmp .= $this->numObj . ' 0 R'; |
1799
|
|
|
$tmp .= ' ]'; |
1800
|
|
|
$info['ColorSpace'] = $tmp; |
1801
|
|
|
|
1802
|
|
View Code Duplication |
if (isset($options['transparency'])) { |
|
|
|
|
1803
|
|
|
$transparency = $options['transparency']; |
1804
|
|
|
switch ($transparency['type']) { |
1805
|
|
|
case 'indexed': |
1806
|
|
|
$tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] '; |
1807
|
|
|
$info['Mask'] = $tmp; |
1808
|
|
|
break; |
1809
|
|
|
|
1810
|
|
|
case 'color-key': |
1811
|
|
|
$tmp = ' [ ' . |
1812
|
|
|
$transparency['r'] . ' ' . $transparency['r'] . |
1813
|
|
|
$transparency['g'] . ' ' . $transparency['g'] . |
1814
|
|
|
$transparency['b'] . ' ' . $transparency['b'] . |
1815
|
|
|
' ] '; |
1816
|
|
|
$info['Mask'] = $tmp; |
1817
|
|
|
break; |
1818
|
|
|
} |
1819
|
|
|
} |
1820
|
|
|
} else { |
1821
|
|
View Code Duplication |
if (isset($options['transparency'])) { |
|
|
|
|
1822
|
|
|
$transparency = $options['transparency']; |
1823
|
|
|
|
1824
|
|
|
switch ($transparency['type']) { |
1825
|
|
|
case 'indexed': |
1826
|
|
|
$tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] '; |
1827
|
|
|
$info['Mask'] = $tmp; |
1828
|
|
|
break; |
1829
|
|
|
|
1830
|
|
|
case 'color-key': |
1831
|
|
|
$tmp = ' [ ' . |
1832
|
|
|
$transparency['r'] . ' ' . $transparency['r'] . ' ' . |
1833
|
|
|
$transparency['g'] . ' ' . $transparency['g'] . ' ' . |
1834
|
|
|
$transparency['b'] . ' ' . $transparency['b'] . |
1835
|
|
|
' ] '; |
1836
|
|
|
$info['Mask'] = $tmp; |
1837
|
|
|
break; |
1838
|
|
|
} |
1839
|
|
|
} |
1840
|
|
|
$info['ColorSpace'] = '/' . $options['color']; |
1841
|
|
|
} |
1842
|
|
|
} |
1843
|
|
|
|
1844
|
|
|
$info['BitsPerComponent'] = $options['bitsPerComponent']; |
1845
|
|
|
} |
1846
|
|
|
} |
1847
|
|
|
|
1848
|
|
|
// assign it a place in the named resource dictionary as an external object, according to |
1849
|
|
|
// the label passed in with it. |
1850
|
|
|
$this->o_pages($this->currentNode, 'xObject', array('label' => $options['label'], 'objNum' => $id)); |
|
|
|
|
1851
|
|
|
|
1852
|
|
|
// also make sure that we have the right procset object for it. |
1853
|
|
|
$this->o_procset($this->procsetObjectId, 'add', 'ImageC'); |
1854
|
|
|
break; |
1855
|
|
|
|
1856
|
|
|
case 'out': |
1857
|
|
|
$o = &$this->objects[$id]; |
1858
|
|
|
$tmp = &$o['data']; |
1859
|
|
|
$res = "\n$id 0 obj\n<<"; |
1860
|
|
|
|
1861
|
|
|
foreach ($o['info'] as $k => $v) { |
1862
|
|
|
$res .= "\n/$k $v"; |
1863
|
|
|
} |
1864
|
|
|
|
1865
|
|
|
if ($this->encrypted) { |
1866
|
|
|
$this->encryptInit($id); |
1867
|
|
|
$tmp = $this->ARC4($tmp); |
1868
|
|
|
} |
1869
|
|
|
|
1870
|
|
|
$res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream\nendobj"; |
1871
|
|
|
|
1872
|
|
|
return $res; |
1873
|
|
|
} |
1874
|
|
|
|
1875
|
|
|
return null; |
1876
|
|
|
} |
1877
|
|
|
|
1878
|
|
|
/** |
1879
|
|
|
* graphics state object |
1880
|
|
|
* |
1881
|
|
|
* @param $id |
1882
|
|
|
* @param $action |
1883
|
|
|
* @param string $options |
1884
|
|
|
* @return null|string |
1885
|
|
|
*/ |
1886
|
|
|
protected function o_extGState($id, $action, $options = "") |
1887
|
|
|
{ |
1888
|
|
|
static $valid_params = array( |
1889
|
|
|
"LW", |
1890
|
|
|
"LC", |
1891
|
|
|
"LC", |
1892
|
|
|
"LJ", |
1893
|
|
|
"ML", |
1894
|
|
|
"D", |
1895
|
|
|
"RI", |
1896
|
|
|
"OP", |
1897
|
|
|
"op", |
1898
|
|
|
"OPM", |
1899
|
|
|
"Font", |
1900
|
|
|
"BG", |
1901
|
|
|
"BG2", |
1902
|
|
|
"UCR", |
1903
|
|
|
"TR", |
1904
|
|
|
"TR2", |
1905
|
|
|
"HT", |
1906
|
|
|
"FL", |
1907
|
|
|
"SM", |
1908
|
|
|
"SA", |
1909
|
|
|
"BM", |
1910
|
|
|
"SMask", |
1911
|
|
|
"CA", |
1912
|
|
|
"ca", |
1913
|
|
|
"AIS", |
1914
|
|
|
"TK" |
1915
|
|
|
); |
1916
|
|
|
|
1917
|
|
|
switch ($action) { |
1918
|
|
View Code Duplication |
case "new": |
|
|
|
|
1919
|
|
|
$this->objects[$id] = array('t' => 'extGState', 'info' => $options); |
1920
|
|
|
|
1921
|
|
|
// Tell the pages about the new resource |
1922
|
|
|
$this->numStates++; |
1923
|
|
|
$this->o_pages($this->currentNode, 'extGState', array("objNum" => $id, "stateNum" => $this->numStates)); |
|
|
|
|
1924
|
|
|
break; |
1925
|
|
|
|
1926
|
|
View Code Duplication |
case "out": |
|
|
|
|
1927
|
|
|
$o = &$this->objects[$id]; |
1928
|
|
|
$res = "\n$id 0 obj\n<< /Type /ExtGState\n"; |
1929
|
|
|
|
1930
|
|
|
foreach ($o["info"] as $k => $v) { |
1931
|
|
|
if (!in_array($k, $valid_params)) { |
1932
|
|
|
continue; |
1933
|
|
|
} |
1934
|
|
|
$res .= "/$k $v\n"; |
1935
|
|
|
} |
1936
|
|
|
|
1937
|
|
|
$res .= ">>\nendobj"; |
1938
|
|
|
|
1939
|
|
|
return $res; |
1940
|
|
|
} |
1941
|
|
|
|
1942
|
|
|
return null; |
1943
|
|
|
} |
1944
|
|
|
|
1945
|
|
|
/** |
1946
|
|
|
* encryption object. |
1947
|
|
|
* |
1948
|
|
|
* @param $id |
1949
|
|
|
* @param $action |
1950
|
|
|
* @param string $options |
1951
|
|
|
* @return string|null |
1952
|
|
|
*/ |
1953
|
|
|
protected function o_encryption($id, $action, $options = '') |
1954
|
|
|
{ |
1955
|
|
|
switch ($action) { |
1956
|
|
|
case 'new': |
1957
|
|
|
// make the new object |
1958
|
|
|
$this->objects[$id] = array('t' => 'encryption', 'info' => $options); |
1959
|
|
|
$this->arc4_objnum = $id; |
1960
|
|
|
break; |
1961
|
|
|
|
1962
|
|
|
case 'keys': |
1963
|
|
|
// figure out the additional parameters required |
1964
|
|
|
$pad = chr(0x28) . chr(0xBF) . chr(0x4E) . chr(0x5E) . chr(0x4E) . chr(0x75) . chr(0x8A) . chr(0x41) |
1965
|
|
|
. chr(0x64) . chr(0x00) . chr(0x4E) . chr(0x56) . chr(0xFF) . chr(0xFA) . chr(0x01) . chr(0x08) |
1966
|
|
|
. chr(0x2E) . chr(0x2E) . chr(0x00) . chr(0xB6) . chr(0xD0) . chr(0x68) . chr(0x3E) . chr(0x80) |
1967
|
|
|
. chr(0x2F) . chr(0x0C) . chr(0xA9) . chr(0xFE) . chr(0x64) . chr(0x53) . chr(0x69) . chr(0x7A); |
1968
|
|
|
|
1969
|
|
|
$info = $this->objects[$id]['info']; |
1970
|
|
|
|
1971
|
|
|
$len = mb_strlen($info['owner'], '8bit'); |
1972
|
|
|
|
1973
|
|
View Code Duplication |
if ($len > 32) { |
|
|
|
|
1974
|
|
|
$owner = substr($info['owner'], 0, 32); |
1975
|
|
|
} else { |
1976
|
|
|
if ($len < 32) { |
1977
|
|
|
$owner = $info['owner'] . substr($pad, 0, 32 - $len); |
1978
|
|
|
} else { |
1979
|
|
|
$owner = $info['owner']; |
1980
|
|
|
} |
1981
|
|
|
} |
1982
|
|
|
|
1983
|
|
|
$len = mb_strlen($info['user'], '8bit'); |
1984
|
|
View Code Duplication |
if ($len > 32) { |
|
|
|
|
1985
|
|
|
$user = substr($info['user'], 0, 32); |
1986
|
|
|
} else { |
1987
|
|
|
if ($len < 32) { |
1988
|
|
|
$user = $info['user'] . substr($pad, 0, 32 - $len); |
1989
|
|
|
} else { |
1990
|
|
|
$user = $info['user']; |
1991
|
|
|
} |
1992
|
|
|
} |
1993
|
|
|
|
1994
|
|
|
$tmp = $this->md5_16($owner); |
1995
|
|
|
$okey = substr($tmp, 0, 5); |
1996
|
|
|
$this->ARC4_init($okey); |
1997
|
|
|
$ovalue = $this->ARC4($user); |
1998
|
|
|
$this->objects[$id]['info']['O'] = $ovalue; |
1999
|
|
|
|
2000
|
|
|
// now make the u value, phew. |
2001
|
|
|
$tmp = $this->md5_16( |
2002
|
|
|
$user . $ovalue . chr($info['p']) . chr(255) . chr(255) . chr(255) . hex2bin($this->fileIdentifier) |
2003
|
|
|
); |
2004
|
|
|
|
2005
|
|
|
$ukey = substr($tmp, 0, 5); |
2006
|
|
|
$this->ARC4_init($ukey); |
2007
|
|
|
$this->encryptionKey = $ukey; |
2008
|
|
|
$this->encrypted = true; |
2009
|
|
|
$uvalue = $this->ARC4($pad); |
2010
|
|
|
$this->objects[$id]['info']['U'] = $uvalue; |
2011
|
|
|
// initialize the arc4 array |
2012
|
|
|
break; |
2013
|
|
|
|
2014
|
|
|
case 'out': |
2015
|
|
|
$o = &$this->objects[$id]; |
2016
|
|
|
|
2017
|
|
|
$res = "\n$id 0 obj\n<<"; |
2018
|
|
|
$res .= "\n/Filter /Standard"; |
2019
|
|
|
$res .= "\n/V 1"; |
2020
|
|
|
$res .= "\n/R 2"; |
2021
|
|
|
$res .= "\n/O (" . $this->filterText($o['info']['O'], false, false) . ')'; |
2022
|
|
|
$res .= "\n/U (" . $this->filterText($o['info']['U'], false, false) . ')'; |
2023
|
|
|
// and the p-value needs to be converted to account for the twos-complement approach |
2024
|
|
|
$o['info']['p'] = (($o['info']['p'] ^ 255) + 1) * -1; |
2025
|
|
|
$res .= "\n/P " . ($o['info']['p']); |
2026
|
|
|
$res .= "\n>>\nendobj"; |
2027
|
|
|
|
2028
|
|
|
return $res; |
2029
|
|
|
} |
2030
|
|
|
|
2031
|
|
|
return null; |
2032
|
|
|
} |
2033
|
|
|
|
2034
|
|
|
/** |
2035
|
|
|
* ARC4 functions |
2036
|
|
|
* A series of function to implement ARC4 encoding in PHP |
2037
|
|
|
*/ |
2038
|
|
|
|
2039
|
|
|
/** |
2040
|
|
|
* calculate the 16 byte version of the 128 bit md5 digest of the string |
2041
|
|
|
* |
2042
|
|
|
* @param $string |
2043
|
|
|
* @return string |
2044
|
|
|
*/ |
2045
|
|
|
function md5_16($string) |
|
|
|
|
2046
|
|
|
{ |
2047
|
|
|
$tmp = md5($string); |
2048
|
|
|
$out = ''; |
2049
|
|
|
for ($i = 0; $i <= 30; $i = $i + 2) { |
2050
|
|
|
$out .= chr(hexdec(substr($tmp, $i, 2))); |
2051
|
|
|
} |
2052
|
|
|
|
2053
|
|
|
return $out; |
2054
|
|
|
} |
2055
|
|
|
|
2056
|
|
|
/** |
2057
|
|
|
* initialize the encryption for processing a particular object |
2058
|
|
|
* |
2059
|
|
|
* @param $id |
2060
|
|
|
*/ |
2061
|
|
|
function encryptInit($id) |
|
|
|
|
2062
|
|
|
{ |
2063
|
|
|
$tmp = $this->encryptionKey; |
2064
|
|
|
$hex = dechex($id); |
2065
|
|
|
if (mb_strlen($hex, '8bit') < 6) { |
2066
|
|
|
$hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')) . $hex; |
2067
|
|
|
} |
2068
|
|
|
$tmp .= chr(hexdec(substr($hex, 4, 2))) |
2069
|
|
|
. chr(hexdec(substr($hex, 2, 2))) |
2070
|
|
|
. chr(hexdec(substr($hex, 0, 2))) |
2071
|
|
|
. chr(0) |
2072
|
|
|
. chr(0) |
2073
|
|
|
; |
2074
|
|
|
$key = $this->md5_16($tmp); |
2075
|
|
|
$this->ARC4_init(substr($key, 0, 10)); |
2076
|
|
|
} |
2077
|
|
|
|
2078
|
|
|
/** |
2079
|
|
|
* initialize the ARC4 encryption |
2080
|
|
|
* |
2081
|
|
|
* @param string $key |
2082
|
|
|
*/ |
2083
|
|
|
function ARC4_init($key = '') |
|
|
|
|
2084
|
|
|
{ |
2085
|
|
|
$this->arc4 = ''; |
2086
|
|
|
|
2087
|
|
|
// setup the control array |
2088
|
|
|
if (mb_strlen($key, '8bit') == 0) { |
2089
|
|
|
return; |
2090
|
|
|
} |
2091
|
|
|
|
2092
|
|
|
$k = ''; |
2093
|
|
|
while (mb_strlen($k, '8bit') < 256) { |
2094
|
|
|
$k .= $key; |
2095
|
|
|
} |
2096
|
|
|
|
2097
|
|
|
$k = substr($k, 0, 256); |
2098
|
|
|
for ($i = 0; $i < 256; $i++) { |
2099
|
|
|
$this->arc4 .= chr($i); |
2100
|
|
|
} |
2101
|
|
|
|
2102
|
|
|
$j = 0; |
2103
|
|
|
|
2104
|
|
|
for ($i = 0; $i < 256; $i++) { |
2105
|
|
|
$t = $this->arc4[$i]; |
2106
|
|
|
$j = ($j + ord($t) + ord($k[$i])) % 256; |
2107
|
|
|
$this->arc4[$i] = $this->arc4[$j]; |
2108
|
|
|
$this->arc4[$j] = $t; |
2109
|
|
|
} |
2110
|
|
|
} |
2111
|
|
|
|
2112
|
|
|
/** |
2113
|
|
|
* ARC4 encrypt a text string |
2114
|
|
|
* |
2115
|
|
|
* @param $text |
2116
|
|
|
* @return string |
2117
|
|
|
*/ |
2118
|
|
|
function ARC4($text) |
|
|
|
|
2119
|
|
|
{ |
2120
|
|
|
$len = mb_strlen($text, '8bit'); |
2121
|
|
|
$a = 0; |
2122
|
|
|
$b = 0; |
2123
|
|
|
$c = $this->arc4; |
2124
|
|
|
$out = ''; |
2125
|
|
|
for ($i = 0; $i < $len; $i++) { |
2126
|
|
|
$a = ($a + 1) % 256; |
2127
|
|
|
$t = $c[$a]; |
2128
|
|
|
$b = ($b + ord($t)) % 256; |
2129
|
|
|
$c[$a] = $c[$b]; |
2130
|
|
|
$c[$b] = $t; |
2131
|
|
|
$k = ord($c[(ord($c[$a]) + ord($c[$b])) % 256]); |
2132
|
|
|
$out .= chr(ord($text[$i]) ^ $k); |
2133
|
|
|
} |
2134
|
|
|
|
2135
|
|
|
return $out; |
2136
|
|
|
} |
2137
|
|
|
|
2138
|
|
|
/** |
2139
|
|
|
* functions which can be called to adjust or add to the document |
2140
|
|
|
*/ |
2141
|
|
|
|
2142
|
|
|
/** |
2143
|
|
|
* add a link in the document to an external URL |
2144
|
|
|
* |
2145
|
|
|
* @param $url |
2146
|
|
|
* @param $x0 |
2147
|
|
|
* @param $y0 |
2148
|
|
|
* @param $x1 |
2149
|
|
|
* @param $y1 |
2150
|
|
|
*/ |
2151
|
|
|
function addLink($url, $x0, $y0, $x1, $y1) |
|
|
|
|
2152
|
|
|
{ |
2153
|
|
|
$this->numObj++; |
2154
|
|
|
$info = array('type' => 'link', 'url' => $url, 'rect' => array($x0, $y0, $x1, $y1)); |
2155
|
|
|
$this->o_annotation($this->numObj, 'new', $info); |
|
|
|
|
2156
|
|
|
} |
2157
|
|
|
|
2158
|
|
|
/** |
2159
|
|
|
* add a link in the document to an internal destination (ie. within the document) |
2160
|
|
|
* |
2161
|
|
|
* @param $label |
2162
|
|
|
* @param $x0 |
2163
|
|
|
* @param $y0 |
2164
|
|
|
* @param $x1 |
2165
|
|
|
* @param $y1 |
2166
|
|
|
*/ |
2167
|
|
|
function addInternalLink($label, $x0, $y0, $x1, $y1) |
|
|
|
|
2168
|
|
|
{ |
2169
|
|
|
$this->numObj++; |
2170
|
|
|
$info = array('type' => 'ilink', 'label' => $label, 'rect' => array($x0, $y0, $x1, $y1)); |
2171
|
|
|
$this->o_annotation($this->numObj, 'new', $info); |
|
|
|
|
2172
|
|
|
} |
2173
|
|
|
|
2174
|
|
|
/** |
2175
|
|
|
* set the encryption of the document |
2176
|
|
|
* can be used to turn it on and/or set the passwords which it will have. |
2177
|
|
|
* also the functions that the user will have are set here, such as print, modify, add |
2178
|
|
|
* |
2179
|
|
|
* @param string $userPass |
2180
|
|
|
* @param string $ownerPass |
2181
|
|
|
* @param array $pc |
2182
|
|
|
*/ |
2183
|
|
|
function setEncryption($userPass = '', $ownerPass = '', $pc = array()) |
|
|
|
|
2184
|
|
|
{ |
2185
|
|
|
$p = bindec("11000000"); |
2186
|
|
|
|
2187
|
|
|
$options = array('print' => 4, 'modify' => 8, 'copy' => 16, 'add' => 32); |
2188
|
|
|
|
2189
|
|
|
foreach ($pc as $k => $v) { |
2190
|
|
|
if ($v && isset($options[$k])) { |
2191
|
|
|
$p += $options[$k]; |
2192
|
|
|
} else { |
2193
|
|
|
if (isset($options[$v])) { |
2194
|
|
|
$p += $options[$v]; |
2195
|
|
|
} |
2196
|
|
|
} |
2197
|
|
|
} |
2198
|
|
|
|
2199
|
|
|
// implement encryption on the document |
2200
|
|
|
if ($this->arc4_objnum == 0) { |
2201
|
|
|
// then the block does not exist already, add it. |
2202
|
|
|
$this->numObj++; |
2203
|
|
|
if (mb_strlen($ownerPass) == 0) { |
2204
|
|
|
$ownerPass = $userPass; |
2205
|
|
|
} |
2206
|
|
|
|
2207
|
|
|
$this->o_encryption($this->numObj, 'new', array('user' => $userPass, 'owner' => $ownerPass, 'p' => $p)); |
|
|
|
|
2208
|
|
|
} |
2209
|
|
|
} |
2210
|
|
|
|
2211
|
|
|
/** |
2212
|
|
|
* should be used for internal checks, not implemented as yet |
2213
|
|
|
*/ |
2214
|
|
|
function checkAllHere() |
|
|
|
|
2215
|
|
|
{ |
2216
|
|
|
} |
2217
|
|
|
|
2218
|
|
|
/** |
2219
|
|
|
* return the pdf stream as a string returned from the function |
2220
|
|
|
* |
2221
|
|
|
* @param bool $debug |
2222
|
|
|
* @return string |
2223
|
|
|
*/ |
2224
|
|
|
function output($debug = false) |
|
|
|
|
2225
|
|
|
{ |
2226
|
|
|
if ($debug) { |
2227
|
|
|
// turn compression off |
2228
|
|
|
$this->options['compression'] = false; |
2229
|
|
|
} |
2230
|
|
|
|
2231
|
|
|
if ($this->javascript) { |
2232
|
|
|
$this->numObj++; |
2233
|
|
|
|
2234
|
|
|
$js_id = $this->numObj; |
2235
|
|
|
$this->o_embedjs($js_id, 'new'); |
2236
|
|
|
$this->o_javascript(++$this->numObj, 'new', $this->javascript); |
2237
|
|
|
|
2238
|
|
|
$id = $this->catalogId; |
2239
|
|
|
|
2240
|
|
|
$this->o_catalog($id, 'javascript', $js_id); |
2241
|
|
|
} |
2242
|
|
|
|
2243
|
|
|
if ($this->fileIdentifier === '') { |
2244
|
|
|
$tmp = implode('', $this->objects[$this->infoObject]['info']); |
2245
|
|
|
$this->fileIdentifier = md5('DOMPDF' . __FILE__ . $tmp . microtime() . mt_rand()); |
2246
|
|
|
} |
2247
|
|
|
|
2248
|
|
|
if ($this->arc4_objnum) { |
2249
|
|
|
$this->o_encryption($this->arc4_objnum, 'keys'); |
2250
|
|
|
$this->ARC4_init($this->encryptionKey); |
2251
|
|
|
} |
2252
|
|
|
|
2253
|
|
|
$this->checkAllHere(); |
2254
|
|
|
|
2255
|
|
|
$xref = array(); |
2256
|
|
|
$content = '%PDF-1.3'; |
2257
|
|
|
$pos = mb_strlen($content, '8bit'); |
2258
|
|
|
|
2259
|
|
|
foreach ($this->objects as $k => $v) { |
2260
|
|
|
$tmp = 'o_' . $v['t']; |
2261
|
|
|
$cont = $this->$tmp($k, 'out'); |
2262
|
|
|
$content .= $cont; |
2263
|
|
|
$xref[] = $pos + 1; //+1 to account for \n at the start of each object |
2264
|
|
|
$pos += mb_strlen($cont, '8bit'); |
2265
|
|
|
} |
2266
|
|
|
|
2267
|
|
|
$content .= "\nxref\n0 " . (count($xref) + 1) . "\n0000000000 65535 f \n"; |
2268
|
|
|
|
2269
|
|
|
foreach ($xref as $p) { |
2270
|
|
|
$content .= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n"; |
2271
|
|
|
} |
2272
|
|
|
|
2273
|
|
|
$content .= "trailer\n<<\n" . |
2274
|
|
|
'/Size ' . (count($xref) + 1) . "\n" . |
2275
|
|
|
'/Root 1 0 R' . "\n" . |
2276
|
|
|
'/Info ' . $this->infoObject . " 0 R\n" |
2277
|
|
|
; |
2278
|
|
|
|
2279
|
|
|
// if encryption has been applied to this document then add the marker for this dictionary |
2280
|
|
|
if ($this->arc4_objnum > 0) { |
2281
|
|
|
$content .= '/Encrypt ' . $this->arc4_objnum . " 0 R\n"; |
2282
|
|
|
} |
2283
|
|
|
|
2284
|
|
|
$content .= '/ID[<' . $this->fileIdentifier . '><' . $this->fileIdentifier . ">]\n"; |
2285
|
|
|
|
2286
|
|
|
// account for \n added at start of xref table |
2287
|
|
|
$pos++; |
2288
|
|
|
|
2289
|
|
|
$content .= ">>\nstartxref\n$pos\n%%EOF\n"; |
2290
|
|
|
|
2291
|
|
|
return $content; |
2292
|
|
|
} |
2293
|
|
|
|
2294
|
|
|
/** |
2295
|
|
|
* initialize a new document |
2296
|
|
|
* if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum |
2297
|
|
|
* this function is called automatically by the constructor function |
2298
|
|
|
* |
2299
|
|
|
* @param array $pageSize |
2300
|
|
|
*/ |
2301
|
|
|
private function newDocument($pageSize = array(0, 0, 612, 792)) |
2302
|
|
|
{ |
2303
|
|
|
$this->numObj = 0; |
2304
|
|
|
$this->objects = array(); |
2305
|
|
|
|
2306
|
|
|
$this->numObj++; |
2307
|
|
|
$this->o_catalog($this->numObj, 'new'); |
2308
|
|
|
|
2309
|
|
|
$this->numObj++; |
2310
|
|
|
$this->o_outlines($this->numObj, 'new'); |
2311
|
|
|
|
2312
|
|
|
$this->numObj++; |
2313
|
|
|
$this->o_pages($this->numObj, 'new'); |
2314
|
|
|
|
2315
|
|
|
$this->o_pages($this->numObj, 'mediaBox', $pageSize); |
|
|
|
|
2316
|
|
|
$this->currentNode = 3; |
2317
|
|
|
|
2318
|
|
|
$this->numObj++; |
2319
|
|
|
$this->o_procset($this->numObj, 'new'); |
2320
|
|
|
|
2321
|
|
|
$this->numObj++; |
2322
|
|
|
$this->o_info($this->numObj, 'new'); |
2323
|
|
|
|
2324
|
|
|
$this->numObj++; |
2325
|
|
|
$this->o_page($this->numObj, 'new'); |
2326
|
|
|
|
2327
|
|
|
// need to store the first page id as there is no way to get it to the user during |
2328
|
|
|
// startup |
2329
|
|
|
$this->firstPageId = $this->currentContents; |
2330
|
|
|
} |
2331
|
|
|
|
2332
|
|
|
/** |
2333
|
|
|
* open the font file and return a php structure containing it. |
2334
|
|
|
* first check if this one has been done before and saved in a form more suited to php |
2335
|
|
|
* note that if a php serialized version does not exist it will try and make one, but will |
2336
|
|
|
* require write access to the directory to do it... it is MUCH faster to have these serialized |
2337
|
|
|
* files. |
2338
|
|
|
* |
2339
|
|
|
* @param $font |
2340
|
|
|
*/ |
2341
|
|
|
private function openFont($font) |
2342
|
|
|
{ |
2343
|
|
|
// assume that $font contains the path and file but not the extension |
2344
|
|
|
$name = basename($font); |
2345
|
|
|
$dir = dirname($font) . '/'; |
2346
|
|
|
|
2347
|
|
|
$fontcache = $this->fontcache; |
2348
|
|
|
if ($fontcache == '') { |
2349
|
|
|
$fontcache = rtrim($dir, DIRECTORY_SEPARATOR."/\\"); |
2350
|
|
|
} |
2351
|
|
|
|
2352
|
|
|
//$name filename without folder and extension of font metrics |
2353
|
|
|
//$dir folder of font metrics |
2354
|
|
|
//$fontcache folder of runtime created php serialized version of font metrics. |
2355
|
|
|
// If this is not given, the same folder as the font metrics will be used. |
2356
|
|
|
// Storing and reusing serialized versions improves speed much |
2357
|
|
|
|
2358
|
|
|
$this->addMessage("openFont: $font - $name"); |
2359
|
|
|
|
2360
|
|
|
if (!$this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) { |
2361
|
|
|
$metrics_name = "$name.afm"; |
2362
|
|
|
} else { |
2363
|
|
|
$metrics_name = "$name.ufm"; |
2364
|
|
|
} |
2365
|
|
|
|
2366
|
|
|
$cache_name = "$metrics_name.php"; |
2367
|
|
|
$this->addMessage("metrics: $metrics_name, cache: $cache_name"); |
2368
|
|
|
|
2369
|
|
|
if (file_exists($fontcache . '/' . $cache_name)) { |
2370
|
|
|
$this->addMessage("openFont: php file exists $fontcache/$cache_name"); |
2371
|
|
|
$this->fonts[$font] = require($fontcache . '/' . $cache_name); |
2372
|
|
|
|
2373
|
|
|
if (!isset($this->fonts[$font]['_version_']) || $this->fonts[$font]['_version_'] != $this->fontcacheVersion) { |
2374
|
|
|
// if the font file is old, then clear it out and prepare for re-creation |
2375
|
|
|
$this->addMessage('openFont: clear out, make way for new version.'); |
2376
|
|
|
$this->fonts[$font] = null; |
2377
|
|
|
unset($this->fonts[$font]); |
2378
|
|
|
} |
2379
|
|
|
} else { |
2380
|
|
|
$old_cache_name = "php_$metrics_name"; |
2381
|
|
|
if (file_exists($fontcache . '/' . $old_cache_name)) { |
2382
|
|
|
$this->addMessage( |
2383
|
|
|
"openFont: php file doesn't exist $fontcache/$cache_name, creating it from the old format" |
2384
|
|
|
); |
2385
|
|
|
$old_cache = file_get_contents($fontcache . '/' . $old_cache_name); |
2386
|
|
|
file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . $old_cache . ';'); |
2387
|
|
|
|
2388
|
|
|
$this->openFont($font); |
2389
|
|
|
return; |
2390
|
|
|
} |
2391
|
|
|
} |
2392
|
|
|
|
2393
|
|
|
if (!isset($this->fonts[$font]) && file_exists($dir . $metrics_name)) { |
2394
|
|
|
// then rebuild the php_<font>.afm file from the <font>.afm file |
2395
|
|
|
$this->addMessage("openFont: build php file from $dir$metrics_name"); |
2396
|
|
|
$data = array(); |
2397
|
|
|
|
2398
|
|
|
// 20 => 'space' |
|
|
|
|
2399
|
|
|
$data['codeToName'] = array(); |
2400
|
|
|
|
2401
|
|
|
// Since we're not going to enable Unicode for the core fonts we need to use a font-based |
2402
|
|
|
// setting for Unicode support rather than a global setting. |
2403
|
|
|
$data['isUnicode'] = (strtolower(substr($metrics_name, -3)) !== 'afm'); |
2404
|
|
|
|
2405
|
|
|
$cidtogid = ''; |
2406
|
|
|
if ($data['isUnicode']) { |
2407
|
|
|
$cidtogid = str_pad('', 256 * 256 * 2, "\x00"); |
2408
|
|
|
} |
2409
|
|
|
|
2410
|
|
|
$file = file($dir . $metrics_name); |
2411
|
|
|
|
2412
|
|
|
foreach ($file as $rowA) { |
2413
|
|
|
$row = trim($rowA); |
2414
|
|
|
$pos = strpos($row, ' '); |
2415
|
|
|
|
2416
|
|
|
if ($pos) { |
2417
|
|
|
// then there must be some keyword |
2418
|
|
|
$key = substr($row, 0, $pos); |
2419
|
|
|
switch ($key) { |
2420
|
|
|
case 'FontName': |
2421
|
|
|
case 'FullName': |
2422
|
|
|
case 'FamilyName': |
2423
|
|
|
case 'PostScriptName': |
2424
|
|
|
case 'Weight': |
2425
|
|
|
case 'ItalicAngle': |
2426
|
|
|
case 'IsFixedPitch': |
2427
|
|
|
case 'CharacterSet': |
2428
|
|
|
case 'UnderlinePosition': |
2429
|
|
|
case 'UnderlineThickness': |
2430
|
|
|
case 'Version': |
2431
|
|
|
case 'EncodingScheme': |
2432
|
|
|
case 'CapHeight': |
2433
|
|
|
case 'XHeight': |
2434
|
|
|
case 'Ascender': |
2435
|
|
|
case 'Descender': |
2436
|
|
|
case 'StdHW': |
2437
|
|
|
case 'StdVW': |
2438
|
|
|
case 'StartCharMetrics': |
2439
|
|
|
case 'FontHeightOffset': // OAR - Added so we can offset the height calculation of a Windows font. Otherwise it's too big. |
2440
|
|
|
$data[$key] = trim(substr($row, $pos)); |
2441
|
|
|
break; |
2442
|
|
|
|
2443
|
|
|
case 'FontBBox': |
2444
|
|
|
$data[$key] = explode(' ', trim(substr($row, $pos))); |
2445
|
|
|
break; |
2446
|
|
|
|
2447
|
|
|
//C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ; |
2448
|
|
|
case 'C': // Found in AFM files |
2449
|
|
|
$bits = explode(';', trim($row)); |
2450
|
|
|
$dtmp = array(); |
2451
|
|
|
|
2452
|
|
View Code Duplication |
foreach ($bits as $bit) { |
|
|
|
|
2453
|
|
|
$bits2 = explode(' ', trim($bit)); |
2454
|
|
|
if (mb_strlen($bits2[0], '8bit') == 0) { |
2455
|
|
|
continue; |
2456
|
|
|
} |
2457
|
|
|
|
2458
|
|
|
if (count($bits2) > 2) { |
2459
|
|
|
$dtmp[$bits2[0]] = array(); |
2460
|
|
|
for ($i = 1; $i < count($bits2); $i++) { |
|
|
|
|
2461
|
|
|
$dtmp[$bits2[0]][] = $bits2[$i]; |
2462
|
|
|
} |
2463
|
|
|
} else { |
2464
|
|
|
if (count($bits2) == 2) { |
2465
|
|
|
$dtmp[$bits2[0]] = $bits2[1]; |
2466
|
|
|
} |
2467
|
|
|
} |
2468
|
|
|
} |
2469
|
|
|
|
2470
|
|
|
$c = (int)$dtmp['C']; |
2471
|
|
|
$n = $dtmp['N']; |
2472
|
|
|
$width = floatval($dtmp['WX']); |
2473
|
|
|
|
2474
|
|
|
if ($c >= 0) { |
2475
|
|
|
if ($c != hexdec($n)) { |
2476
|
|
|
$data['codeToName'][$c] = $n; |
2477
|
|
|
} |
2478
|
|
|
$data['C'][$c] = $width; |
2479
|
|
|
} else { |
2480
|
|
|
$data['C'][$n] = $width; |
2481
|
|
|
} |
2482
|
|
|
|
2483
|
|
View Code Duplication |
if (!isset($data['MissingWidth']) && $c == -1 && $n === '.notdef') { |
|
|
|
|
2484
|
|
|
$data['MissingWidth'] = $width; |
2485
|
|
|
} |
2486
|
|
|
|
2487
|
|
|
break; |
2488
|
|
|
|
2489
|
|
|
// U 827 ; WX 0 ; N squaresubnosp ; G 675 ; |
2490
|
|
|
case 'U': // Found in UFM files |
2491
|
|
|
if (!$data['isUnicode']) { |
2492
|
|
|
break; |
2493
|
|
|
} |
2494
|
|
|
|
2495
|
|
|
$bits = explode(';', trim($row)); |
2496
|
|
|
$dtmp = array(); |
2497
|
|
|
|
2498
|
|
View Code Duplication |
foreach ($bits as $bit) { |
|
|
|
|
2499
|
|
|
$bits2 = explode(' ', trim($bit)); |
2500
|
|
|
if (mb_strlen($bits2[0], '8bit') === 0) { |
2501
|
|
|
continue; |
2502
|
|
|
} |
2503
|
|
|
|
2504
|
|
|
if (count($bits2) > 2) { |
2505
|
|
|
$dtmp[$bits2[0]] = array(); |
2506
|
|
|
for ($i = 1; $i < count($bits2); $i++) { |
|
|
|
|
2507
|
|
|
$dtmp[$bits2[0]][] = $bits2[$i]; |
2508
|
|
|
} |
2509
|
|
|
} else { |
2510
|
|
|
if (count($bits2) == 2) { |
2511
|
|
|
$dtmp[$bits2[0]] = $bits2[1]; |
2512
|
|
|
} |
2513
|
|
|
} |
2514
|
|
|
} |
2515
|
|
|
|
2516
|
|
|
$c = (int)$dtmp['U']; |
2517
|
|
|
$n = $dtmp['N']; |
2518
|
|
|
$glyph = $dtmp['G']; |
2519
|
|
|
$width = floatval($dtmp['WX']); |
2520
|
|
|
|
2521
|
|
|
if ($c >= 0) { |
2522
|
|
|
// Set values in CID to GID map |
2523
|
|
View Code Duplication |
if ($c >= 0 && $c < 0xFFFF && $glyph) { |
|
|
|
|
2524
|
|
|
$cidtogid[$c * 2] = chr($glyph >> 8); |
2525
|
|
|
$cidtogid[$c * 2 + 1] = chr($glyph & 0xFF); |
2526
|
|
|
} |
2527
|
|
|
|
2528
|
|
|
if ($c != hexdec($n)) { |
2529
|
|
|
$data['codeToName'][$c] = $n; |
2530
|
|
|
} |
2531
|
|
|
$data['C'][$c] = $width; |
2532
|
|
|
} else { |
2533
|
|
|
$data['C'][$n] = $width; |
2534
|
|
|
} |
2535
|
|
|
|
2536
|
|
View Code Duplication |
if (!isset($data['MissingWidth']) && $c == -1 && $n === '.notdef') { |
|
|
|
|
2537
|
|
|
$data['MissingWidth'] = $width; |
2538
|
|
|
} |
2539
|
|
|
|
2540
|
|
|
break; |
2541
|
|
|
|
2542
|
|
|
case 'KPX': |
2543
|
|
|
break; // don't include them as they are not used yet |
2544
|
|
|
//KPX Adieresis yacute -40 |
2545
|
|
|
/*$bits = explode(' ', trim($row)); |
|
|
|
|
2546
|
|
|
$data['KPX'][$bits[1]][$bits[2]] = $bits[3]; |
2547
|
|
|
break;*/ |
2548
|
|
|
} |
2549
|
|
|
} |
2550
|
|
|
} |
2551
|
|
|
|
2552
|
|
View Code Duplication |
if ($this->compressionReady && $this->options['compression']) { |
|
|
|
|
2553
|
|
|
// then implement ZLIB based compression on CIDtoGID string |
2554
|
|
|
$data['CIDtoGID_Compressed'] = true; |
2555
|
|
|
$cidtogid = gzcompress($cidtogid, 6); |
2556
|
|
|
} |
2557
|
|
|
$data['CIDtoGID'] = base64_encode($cidtogid); |
2558
|
|
|
$data['_version_'] = $this->fontcacheVersion; |
2559
|
|
|
$this->fonts[$font] = $data; |
2560
|
|
|
|
2561
|
|
|
//Because of potential trouble with php safe mode, expect that the folder already exists. |
2562
|
|
|
//If not existing, this will hit performance because of missing cached results. |
2563
|
|
|
if (is_dir($fontcache) && is_writable($fontcache)) { |
2564
|
|
|
file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . var_export($data, true) . ';'); |
2565
|
|
|
} |
2566
|
|
|
$data = null; |
|
|
|
|
2567
|
|
|
} |
2568
|
|
|
|
2569
|
|
|
if (!isset($this->fonts[$font])) { |
2570
|
|
|
$this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?"); |
2571
|
|
|
} |
2572
|
|
|
|
2573
|
|
|
//pre_r($this->messages); |
|
|
|
|
2574
|
|
|
} |
2575
|
|
|
|
2576
|
|
|
/** |
2577
|
|
|
* if the font is not loaded then load it and make the required object |
2578
|
|
|
* else just make it the current font |
2579
|
|
|
* the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding' |
2580
|
|
|
* note that encoding='none' will need to be used for symbolic fonts |
2581
|
|
|
* and 'differences' => an array of mappings between numbers 0->255 and character names. |
2582
|
|
|
* |
2583
|
|
|
* @param $fontName |
2584
|
|
|
* @param string $encoding |
2585
|
|
|
* @param bool $set |
2586
|
|
|
* @return int |
2587
|
|
|
*/ |
2588
|
|
|
function selectFont($fontName, $encoding = '', $set = true) |
|
|
|
|
2589
|
|
|
{ |
2590
|
|
|
$ext = substr($fontName, -4); |
2591
|
|
|
if ($ext === '.afm' || $ext === '.ufm') { |
2592
|
|
|
$fontName = substr($fontName, 0, mb_strlen($fontName) - 4); |
2593
|
|
|
} |
2594
|
|
|
|
2595
|
|
|
if (!isset($this->fonts[$fontName])) { |
2596
|
|
|
$this->addMessage("selectFont: selecting - $fontName - $encoding, $set"); |
2597
|
|
|
|
2598
|
|
|
// load the file |
2599
|
|
|
$this->openFont($fontName); |
2600
|
|
|
|
2601
|
|
|
if (isset($this->fonts[$fontName])) { |
2602
|
|
|
$this->numObj++; |
2603
|
|
|
$this->numFonts++; |
2604
|
|
|
|
2605
|
|
|
$font = &$this->fonts[$fontName]; |
2606
|
|
|
|
2607
|
|
|
$name = basename($fontName); |
2608
|
|
|
$dir = dirname($fontName) . '/'; |
|
|
|
|
2609
|
|
|
$options = array('name' => $name, 'fontFileName' => $fontName); |
2610
|
|
|
|
2611
|
|
|
if (is_array($encoding)) { |
2612
|
|
|
// then encoding and differences might be set |
2613
|
|
|
if (isset($encoding['encoding'])) { |
2614
|
|
|
$options['encoding'] = $encoding['encoding']; |
2615
|
|
|
} |
2616
|
|
|
|
2617
|
|
|
if (isset($encoding['differences'])) { |
2618
|
|
|
$options['differences'] = $encoding['differences']; |
2619
|
|
|
} |
2620
|
|
|
} else { |
2621
|
|
|
if (mb_strlen($encoding, '8bit')) { |
2622
|
|
|
// then perhaps only the encoding has been set |
2623
|
|
|
$options['encoding'] = $encoding; |
2624
|
|
|
} |
2625
|
|
|
} |
2626
|
|
|
|
2627
|
|
|
$fontObj = $this->numObj; |
2628
|
|
|
$this->o_font($this->numObj, 'new', $options); |
2629
|
|
|
$font['fontNum'] = $this->numFonts; |
2630
|
|
|
|
2631
|
|
|
// if this is a '.afm' font, and there is a '.pfa' file to go with it (as there |
2632
|
|
|
// should be for all non-basic fonts), then load it into an object and put the |
2633
|
|
|
// references into the font object |
2634
|
|
|
$basefile = $fontName; |
2635
|
|
|
|
2636
|
|
|
$fbtype = ''; |
2637
|
|
|
if (file_exists("$basefile.ttf")) { |
2638
|
|
|
$fbtype = 'ttf'; |
2639
|
|
|
} elseif (file_exists("$basefile.TTF")) { |
2640
|
|
|
$fbtype = 'TTF'; |
2641
|
|
|
} elseif (file_exists("$basefile.pfb")) { |
2642
|
|
|
$fbtype = 'pfb'; |
2643
|
|
|
} elseif (file_exists("$basefile.PFB")) { |
2644
|
|
|
$fbtype = 'PFB'; |
2645
|
|
|
} |
2646
|
|
|
|
2647
|
|
|
$fbfile = "$basefile.$fbtype"; |
2648
|
|
|
|
2649
|
|
|
// $pfbfile = substr($fontName,0,strlen($fontName)-4).'.pfb'; |
|
|
|
|
2650
|
|
|
// $ttffile = substr($fontName,0,strlen($fontName)-4).'.ttf'; |
|
|
|
|
2651
|
|
|
$this->addMessage('selectFont: checking for - ' . $fbfile); |
2652
|
|
|
|
2653
|
|
|
// OAR - I don't understand this old check |
2654
|
|
|
// if (substr($fontName, -4) === '.afm' && strlen($fbtype)) { |
|
|
|
|
2655
|
|
|
if ($fbtype) { |
2656
|
|
|
$adobeFontName = isset($font['PostScriptName']) ? $font['PostScriptName'] : $font['FontName']; |
2657
|
|
|
// $fontObj = $this->numObj; |
|
|
|
|
2658
|
|
|
$this->addMessage("selectFont: adding font file - $fbfile - $adobeFontName"); |
2659
|
|
|
|
2660
|
|
|
// find the array of font widths, and put that into an object. |
2661
|
|
|
$firstChar = -1; |
2662
|
|
|
$lastChar = 0; |
2663
|
|
|
$widths = array(); |
2664
|
|
|
$cid_widths = array(); |
2665
|
|
|
|
2666
|
|
|
foreach ($font['C'] as $num => $d) { |
2667
|
|
|
if (intval($num) > 0 || $num == '0') { |
2668
|
|
|
if (!$font['isUnicode']) { |
2669
|
|
|
// With Unicode, widths array isn't used |
2670
|
|
|
if ($lastChar > 0 && $num > $lastChar + 1) { |
2671
|
|
|
for ($i = $lastChar + 1; $i < $num; $i++) { |
2672
|
|
|
$widths[] = 0; |
2673
|
|
|
} |
2674
|
|
|
} |
2675
|
|
|
} |
2676
|
|
|
|
2677
|
|
|
$widths[] = $d; |
2678
|
|
|
|
2679
|
|
|
if ($font['isUnicode']) { |
2680
|
|
|
$cid_widths[$num] = $d; |
2681
|
|
|
} |
2682
|
|
|
|
2683
|
|
|
if ($firstChar == -1) { |
2684
|
|
|
$firstChar = $num; |
2685
|
|
|
} |
2686
|
|
|
|
2687
|
|
|
$lastChar = $num; |
2688
|
|
|
} |
2689
|
|
|
} |
2690
|
|
|
|
2691
|
|
|
// also need to adjust the widths for the differences array |
2692
|
|
|
if (isset($options['differences'])) { |
2693
|
|
|
foreach ($options['differences'] as $charNum => $charName) { |
2694
|
|
|
if ($charNum > $lastChar) { |
2695
|
|
|
if (!$font['isUnicode']) { |
2696
|
|
|
// With Unicode, widths array isn't used |
2697
|
|
|
for ($i = $lastChar + 1; $i <= $charNum; $i++) { |
2698
|
|
|
$widths[] = 0; |
2699
|
|
|
} |
2700
|
|
|
} |
2701
|
|
|
|
2702
|
|
|
$lastChar = $charNum; |
2703
|
|
|
} |
2704
|
|
|
|
2705
|
|
|
if (isset($font['C'][$charName])) { |
2706
|
|
|
$widths[$charNum - $firstChar] = $font['C'][$charName]; |
2707
|
|
|
if ($font['isUnicode']) { |
2708
|
|
|
$cid_widths[$charName] = $font['C'][$charName]; |
2709
|
|
|
} |
2710
|
|
|
} |
2711
|
|
|
} |
2712
|
|
|
} |
2713
|
|
|
|
2714
|
|
|
if ($font['isUnicode']) { |
2715
|
|
|
$font['CIDWidths'] = $cid_widths; |
2716
|
|
|
} |
2717
|
|
|
|
2718
|
|
|
$this->addMessage('selectFont: FirstChar = ' . $firstChar); |
2719
|
|
|
$this->addMessage('selectFont: LastChar = ' . $lastChar); |
2720
|
|
|
|
2721
|
|
|
$widthid = -1; |
2722
|
|
|
|
2723
|
|
|
if (!$font['isUnicode']) { |
2724
|
|
|
// With Unicode, widths array isn't used |
2725
|
|
|
|
2726
|
|
|
$this->numObj++; |
2727
|
|
|
$this->o_contents($this->numObj, 'new', 'raw'); |
2728
|
|
|
$this->objects[$this->numObj]['c'] .= '[' . implode(' ', $widths) . ']'; |
2729
|
|
|
$widthid = $this->numObj; |
2730
|
|
|
} |
2731
|
|
|
|
2732
|
|
|
$missing_width = 500; |
2733
|
|
|
$stemV = 70; |
2734
|
|
|
|
2735
|
|
|
if (isset($font['MissingWidth'])) { |
2736
|
|
|
$missing_width = $font['MissingWidth']; |
2737
|
|
|
} |
2738
|
|
|
if (isset($font['StdVW'])) { |
2739
|
|
|
$stemV = $font['StdVW']; |
2740
|
|
|
} else { |
2741
|
|
|
if (isset($font['Weight']) && preg_match('!(bold|black)!i', $font['Weight'])) { |
2742
|
|
|
$stemV = 120; |
2743
|
|
|
} |
2744
|
|
|
} |
2745
|
|
|
|
2746
|
|
|
// load the pfb file, and put that into an object too. |
2747
|
|
|
// note that pdf supports only binary format type 1 font files, though there is a |
2748
|
|
|
// simple utility to convert them from pfa to pfb. |
2749
|
|
|
// FIXME: should we move font subset creation to CPDF::output? See notes in issue #750. |
2750
|
|
|
if (!$this->isUnicode || strtolower($fbtype) !== 'ttf' || empty($this->stringSubsets)) { |
2751
|
|
|
$data = file_get_contents($fbfile); |
2752
|
|
|
} else { |
2753
|
|
|
$this->stringSubsets[$fontName][] = 32; // Force space if not in yet |
2754
|
|
|
|
2755
|
|
|
$subset = $this->stringSubsets[$fontName]; |
2756
|
|
|
sort($subset); |
2757
|
|
|
|
2758
|
|
|
// Load font |
2759
|
|
|
$font_obj = Font::load($fbfile); |
2760
|
|
|
$font_obj->parse(); |
2761
|
|
|
|
2762
|
|
|
// Define subset |
2763
|
|
|
$font_obj->setSubset($subset); |
2764
|
|
|
$font_obj->reduce(); |
2765
|
|
|
|
2766
|
|
|
// Write new font |
2767
|
|
|
$tmp_name = $this->tmp . "/" . basename($fbfile) . ".tmp." . uniqid(); |
2768
|
|
|
$font_obj->open($tmp_name, BinaryStream::modeWrite); |
2769
|
|
|
$font_obj->encode(array("OS/2")); |
2770
|
|
|
$font_obj->close(); |
2771
|
|
|
|
2772
|
|
|
// Parse the new font to get cid2gid and widths |
2773
|
|
|
$font_obj = Font::load($tmp_name); |
2774
|
|
|
|
2775
|
|
|
// Find Unicode char map table |
2776
|
|
|
$subtable = null; |
2777
|
|
|
foreach ($font_obj->getData("cmap", "subtables") as $_subtable) { |
2778
|
|
|
if ($_subtable["platformID"] == 0 || $_subtable["platformID"] == 3 && $_subtable["platformSpecificID"] == 1) { |
2779
|
|
|
$subtable = $_subtable; |
2780
|
|
|
break; |
2781
|
|
|
} |
2782
|
|
|
} |
2783
|
|
|
|
2784
|
|
|
if ($subtable) { |
2785
|
|
|
$glyphIndexArray = $subtable["glyphIndexArray"]; |
2786
|
|
|
$hmtx = $font_obj->getData("hmtx"); |
2787
|
|
|
|
2788
|
|
|
unset($glyphIndexArray[0xFFFF]); |
2789
|
|
|
|
2790
|
|
|
$cidtogid = str_pad('', max(array_keys($glyphIndexArray)) * 2 + 1, "\x00"); |
2791
|
|
|
$font['CIDWidths'] = array(); |
2792
|
|
|
foreach ($glyphIndexArray as $cid => $gid) { |
2793
|
|
View Code Duplication |
if ($cid >= 0 && $cid < 0xFFFF && $gid) { |
|
|
|
|
2794
|
|
|
$cidtogid[$cid * 2] = chr($gid >> 8); |
2795
|
|
|
$cidtogid[$cid * 2 + 1] = chr($gid & 0xFF); |
2796
|
|
|
} |
2797
|
|
|
|
2798
|
|
|
$width = $font_obj->normalizeFUnit(isset($hmtx[$gid]) ? $hmtx[$gid][0] : $hmtx[0][0]); |
2799
|
|
|
$font['CIDWidths'][$cid] = $width; |
2800
|
|
|
} |
2801
|
|
|
|
2802
|
|
|
$font['CIDtoGID'] = base64_encode(gzcompress($cidtogid)); |
2803
|
|
|
$font['CIDtoGID_Compressed'] = true; |
2804
|
|
|
|
2805
|
|
|
$data = file_get_contents($tmp_name); |
2806
|
|
|
} else { |
2807
|
|
|
$data = file_get_contents($fbfile); |
2808
|
|
|
} |
2809
|
|
|
|
2810
|
|
|
$font_obj->close(); |
2811
|
|
|
unlink($tmp_name); |
2812
|
|
|
} |
2813
|
|
|
|
2814
|
|
|
// create the font descriptor |
2815
|
|
|
$this->numObj++; |
2816
|
|
|
$fontDescriptorId = $this->numObj; |
2817
|
|
|
|
2818
|
|
|
$this->numObj++; |
2819
|
|
|
$pfbid = $this->numObj; |
2820
|
|
|
|
2821
|
|
|
// determine flags (more than a little flakey, hopefully will not matter much) |
2822
|
|
|
$flags = 0; |
2823
|
|
|
|
2824
|
|
|
if ($font['ItalicAngle'] != 0) { |
2825
|
|
|
$flags += pow(2, 6); |
2826
|
|
|
} |
2827
|
|
|
|
2828
|
|
|
if ($font['IsFixedPitch'] === 'true') { |
2829
|
|
|
$flags += 1; |
2830
|
|
|
} |
2831
|
|
|
|
2832
|
|
|
$flags += pow(2, 5); // assume non-sybolic |
2833
|
|
|
$list = array( |
2834
|
|
|
'Ascent' => 'Ascender', |
2835
|
|
|
'CapHeight' => 'Ascender', //FIXME: php-font-lib is not grabbing this value, so we'll fake it and use the Ascender value // 'CapHeight' |
2836
|
|
|
'MissingWidth' => 'MissingWidth', |
2837
|
|
|
'Descent' => 'Descender', |
2838
|
|
|
'FontBBox' => 'FontBBox', |
2839
|
|
|
'ItalicAngle' => 'ItalicAngle' |
2840
|
|
|
); |
2841
|
|
|
$fdopt = array( |
2842
|
|
|
'Flags' => $flags, |
2843
|
|
|
'FontName' => $adobeFontName, |
2844
|
|
|
'StemV' => $stemV |
2845
|
|
|
); |
2846
|
|
|
|
2847
|
|
|
foreach ($list as $k => $v) { |
2848
|
|
|
if (isset($font[$v])) { |
2849
|
|
|
$fdopt[$k] = $font[$v]; |
2850
|
|
|
} |
2851
|
|
|
} |
2852
|
|
|
|
2853
|
|
|
if (strtolower($fbtype) === 'pfb') { |
2854
|
|
|
$fdopt['FontFile'] = $pfbid; |
2855
|
|
|
} elseif (strtolower($fbtype) === 'ttf') { |
2856
|
|
|
$fdopt['FontFile2'] = $pfbid; |
2857
|
|
|
} |
2858
|
|
|
|
2859
|
|
|
$this->o_fontDescriptor($fontDescriptorId, 'new', $fdopt); |
|
|
|
|
2860
|
|
|
|
2861
|
|
|
// embed the font program |
2862
|
|
|
$this->o_contents($this->numObj, 'new'); |
2863
|
|
|
$this->objects[$pfbid]['c'] .= $data; |
2864
|
|
|
|
2865
|
|
|
// determine the cruicial lengths within this file |
2866
|
|
|
if (strtolower($fbtype) === 'pfb') { |
2867
|
|
|
$l1 = strpos($data, 'eexec') + 6; |
2868
|
|
|
$l2 = strpos($data, '00000000') - $l1; |
2869
|
|
|
$l3 = mb_strlen($data, '8bit') - $l2 - $l1; |
2870
|
|
|
$this->o_contents( |
2871
|
|
|
$this->numObj, |
2872
|
|
|
'add', |
2873
|
|
|
array('Length1' => $l1, 'Length2' => $l2, 'Length3' => $l3) |
2874
|
|
|
); |
2875
|
|
|
} elseif (strtolower($fbtype) == 'ttf') { |
2876
|
|
|
$l1 = mb_strlen($data, '8bit'); |
2877
|
|
|
$this->o_contents($this->numObj, 'add', array('Length1' => $l1)); |
2878
|
|
|
} |
2879
|
|
|
|
2880
|
|
|
// tell the font object about all this new stuff |
2881
|
|
|
$tmp = array( |
2882
|
|
|
'BaseFont' => $adobeFontName, |
2883
|
|
|
'MissingWidth' => $missing_width, |
2884
|
|
|
'Widths' => $widthid, |
2885
|
|
|
'FirstChar' => $firstChar, |
2886
|
|
|
'LastChar' => $lastChar, |
2887
|
|
|
'FontDescriptor' => $fontDescriptorId |
2888
|
|
|
); |
2889
|
|
|
|
2890
|
|
|
if (strtolower($fbtype) === 'ttf') { |
2891
|
|
|
$tmp['SubType'] = 'TrueType'; |
2892
|
|
|
} |
2893
|
|
|
|
2894
|
|
|
$this->addMessage("adding extra info to font.($fontObj)"); |
2895
|
|
|
|
2896
|
|
|
foreach ($tmp as $fk => $fv) { |
2897
|
|
|
$this->addMessage("$fk : $fv"); |
2898
|
|
|
} |
2899
|
|
|
|
2900
|
|
|
$this->o_font($fontObj, 'add', $tmp); |
2901
|
|
|
} else { |
2902
|
|
|
$this->addMessage( |
2903
|
|
|
'selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts' |
2904
|
|
|
); |
2905
|
|
|
} |
2906
|
|
|
|
2907
|
|
|
// also set the differences here, note that this means that these will take effect only the |
2908
|
|
|
//first time that a font is selected, else they are ignored |
2909
|
|
|
if (isset($options['differences'])) { |
2910
|
|
|
$font['differences'] = $options['differences']; |
2911
|
|
|
} |
2912
|
|
|
} |
2913
|
|
|
} |
2914
|
|
|
|
2915
|
|
|
if ($set && isset($this->fonts[$fontName])) { |
2916
|
|
|
// so if for some reason the font was not set in the last one then it will not be selected |
2917
|
|
|
$this->currentBaseFont = $fontName; |
2918
|
|
|
|
2919
|
|
|
// the next lines mean that if a new font is selected, then the current text state will be |
2920
|
|
|
// applied to it as well. |
2921
|
|
|
$this->currentFont = $this->currentBaseFont; |
2922
|
|
|
$this->currentFontNum = $this->fonts[$this->currentFont]['fontNum']; |
2923
|
|
|
|
2924
|
|
|
//$this->setCurrentFont(); |
|
|
|
|
2925
|
|
|
} |
2926
|
|
|
|
2927
|
|
|
return $this->currentFontNum; |
2928
|
|
|
//return $this->numObj; |
|
|
|
|
2929
|
|
|
} |
2930
|
|
|
|
2931
|
|
|
/** |
2932
|
|
|
* sets up the current font, based on the font families, and the current text state |
2933
|
|
|
* note that this system is quite flexible, a bold-italic font can be completely different to a |
2934
|
|
|
* italic-bold font, and even bold-bold will have to be defined within the family to have meaning |
2935
|
|
|
* This function is to be called whenever the currentTextState is changed, it will update |
2936
|
|
|
* the currentFont setting to whatever the appropriate family one is. |
2937
|
|
|
* If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont |
2938
|
|
|
* This function will change the currentFont to whatever it should be, but will not change the |
2939
|
|
|
* currentBaseFont. |
2940
|
|
|
*/ |
2941
|
|
|
private function setCurrentFont() |
2942
|
|
|
{ |
2943
|
|
|
// if (strlen($this->currentBaseFont) == 0){ |
|
|
|
|
2944
|
|
|
// // then assume an initial font |
2945
|
|
|
// $this->selectFont($this->defaultFont); |
|
|
|
|
2946
|
|
|
// } |
2947
|
|
|
// $cf = substr($this->currentBaseFont,strrpos($this->currentBaseFont,'/')+1); |
|
|
|
|
2948
|
|
|
// if (strlen($this->currentTextState) |
|
|
|
|
2949
|
|
|
// && isset($this->fontFamilies[$cf]) |
|
|
|
|
2950
|
|
|
// && isset($this->fontFamilies[$cf][$this->currentTextState])){ |
|
|
|
|
2951
|
|
|
// // then we are in some state or another |
2952
|
|
|
// // and this font has a family, and the current setting exists within it |
2953
|
|
|
// // select the font, then return it |
2954
|
|
|
// $nf = substr($this->currentBaseFont,0,strrpos($this->currentBaseFont,'/')+1).$this->fontFamilies[$cf][$this->currentTextState]; |
|
|
|
|
2955
|
|
|
// $this->selectFont($nf,'',0); |
|
|
|
|
2956
|
|
|
// $this->currentFont = $nf; |
|
|
|
|
2957
|
|
|
// $this->currentFontNum = $this->fonts[$nf]['fontNum']; |
|
|
|
|
2958
|
|
|
// } else { |
|
|
|
|
2959
|
|
|
// // the this font must not have the right family member for the current state |
2960
|
|
|
// // simply assume the base font |
2961
|
|
|
$this->currentFont = $this->currentBaseFont; |
2962
|
|
|
$this->currentFontNum = $this->fonts[$this->currentFont]['fontNum']; |
2963
|
|
|
// } |
2964
|
|
|
} |
2965
|
|
|
|
2966
|
|
|
/** |
2967
|
|
|
* function for the user to find out what the ID is of the first page that was created during |
2968
|
|
|
* startup - useful if they wish to add something to it later. |
2969
|
|
|
* |
2970
|
|
|
* @return int |
2971
|
|
|
*/ |
2972
|
|
|
function getFirstPageId() |
|
|
|
|
2973
|
|
|
{ |
2974
|
|
|
return $this->firstPageId; |
2975
|
|
|
} |
2976
|
|
|
|
2977
|
|
|
/** |
2978
|
|
|
* add content to the currently active object |
2979
|
|
|
* |
2980
|
|
|
* @param $content |
2981
|
|
|
*/ |
2982
|
|
|
private function addContent($content) |
2983
|
|
|
{ |
2984
|
|
|
$this->objects[$this->currentContents]['c'] .= $content; |
2985
|
|
|
} |
2986
|
|
|
|
2987
|
|
|
/** |
2988
|
|
|
* sets the color for fill operations |
2989
|
|
|
* |
2990
|
|
|
* @param $color |
2991
|
|
|
* @param bool $force |
2992
|
|
|
*/ |
2993
|
|
View Code Duplication |
function setColor($color, $force = false) |
|
|
|
|
2994
|
|
|
{ |
2995
|
|
|
$new_color = array($color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null); |
2996
|
|
|
|
2997
|
|
|
if (!$force && $this->currentColor == $new_color) { |
2998
|
|
|
return; |
2999
|
|
|
} |
3000
|
|
|
|
3001
|
|
|
if (isset($new_color[3])) { |
3002
|
|
|
$this->currentColor = $new_color; |
3003
|
|
|
$this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F k", $this->currentColor)); |
3004
|
|
|
} else { |
3005
|
|
|
if (isset($new_color[2])) { |
3006
|
|
|
$this->currentColor = $new_color; |
3007
|
|
|
$this->addContent(vsprintf("\n%.3F %.3F %.3F rg", $this->currentColor)); |
3008
|
|
|
} |
3009
|
|
|
} |
3010
|
|
|
} |
3011
|
|
|
|
3012
|
|
|
/** |
3013
|
|
|
* sets the color for fill operations |
3014
|
|
|
* |
3015
|
|
|
* @param $fillRule |
3016
|
|
|
*/ |
3017
|
|
|
function setFillRule($fillRule) |
|
|
|
|
3018
|
|
|
{ |
3019
|
|
|
if (!in_array($fillRule, array("nonzero", "evenodd"))) { |
3020
|
|
|
return; |
3021
|
|
|
} |
3022
|
|
|
|
3023
|
|
|
$this->fillRule = $fillRule; |
3024
|
|
|
} |
3025
|
|
|
|
3026
|
|
|
/** |
3027
|
|
|
* sets the color for stroke operations |
3028
|
|
|
* |
3029
|
|
|
* @param $color |
3030
|
|
|
* @param bool $force |
3031
|
|
|
*/ |
3032
|
|
View Code Duplication |
function setStrokeColor($color, $force = false) |
|
|
|
|
3033
|
|
|
{ |
3034
|
|
|
$new_color = array($color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null); |
3035
|
|
|
|
3036
|
|
|
if (!$force && $this->currentStrokeColor == $new_color) { |
3037
|
|
|
return; |
3038
|
|
|
} |
3039
|
|
|
|
3040
|
|
|
if (isset($new_color[3])) { |
3041
|
|
|
$this->currentStrokeColor = $new_color; |
3042
|
|
|
$this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F K", $this->currentStrokeColor)); |
3043
|
|
|
} else { |
3044
|
|
|
if (isset($new_color[2])) { |
3045
|
|
|
$this->currentStrokeColor = $new_color; |
3046
|
|
|
$this->addContent(vsprintf("\n%.3F %.3F %.3F RG", $this->currentStrokeColor)); |
3047
|
|
|
} |
3048
|
|
|
} |
3049
|
|
|
} |
3050
|
|
|
|
3051
|
|
|
/** |
3052
|
|
|
* Set the graphics state for compositions |
3053
|
|
|
* |
3054
|
|
|
* @param $parameters |
3055
|
|
|
*/ |
3056
|
|
|
function setGraphicsState($parameters) |
|
|
|
|
3057
|
|
|
{ |
3058
|
|
|
// Create a new graphics state object if necessary |
3059
|
|
|
if (($gstate = array_search($parameters, $this->gstates)) === false) { |
3060
|
|
|
$this->numObj++; |
3061
|
|
|
$this->o_extGState($this->numObj, 'new', $parameters); |
3062
|
|
|
$gstate = $this->numStates; |
3063
|
|
|
$this->gstates[$gstate] = $parameters; |
3064
|
|
|
} |
3065
|
|
|
$this->addContent("\n/GS$gstate gs"); |
3066
|
|
|
} |
3067
|
|
|
|
3068
|
|
|
/** |
3069
|
|
|
* Set current blend mode & opacity for lines. |
3070
|
|
|
* |
3071
|
|
|
* Valid blend modes are: |
3072
|
|
|
* |
3073
|
|
|
* Normal, Multiply, Screen, Overlay, Darken, Lighten, |
3074
|
|
|
* ColorDogde, ColorBurn, HardLight, SoftLight, Difference, |
3075
|
|
|
* Exclusion |
3076
|
|
|
* |
3077
|
|
|
* @param string $mode the blend mode to use |
3078
|
|
|
* @param float $opacity 0.0 fully transparent, 1.0 fully opaque |
3079
|
|
|
*/ |
3080
|
|
View Code Duplication |
function setLineTransparency($mode, $opacity) |
|
|
|
|
3081
|
|
|
{ |
3082
|
|
|
static $blend_modes = array( |
3083
|
|
|
"Normal", |
3084
|
|
|
"Multiply", |
3085
|
|
|
"Screen", |
3086
|
|
|
"Overlay", |
3087
|
|
|
"Darken", |
3088
|
|
|
"Lighten", |
3089
|
|
|
"ColorDogde", |
3090
|
|
|
"ColorBurn", |
3091
|
|
|
"HardLight", |
3092
|
|
|
"SoftLight", |
3093
|
|
|
"Difference", |
3094
|
|
|
"Exclusion" |
3095
|
|
|
); |
3096
|
|
|
|
3097
|
|
|
if (!in_array($mode, $blend_modes)) { |
3098
|
|
|
$mode = "Normal"; |
3099
|
|
|
} |
3100
|
|
|
|
3101
|
|
|
// Only create a new graphics state if required |
3102
|
|
|
if ($mode === $this->currentLineTransparency["mode"] && |
3103
|
|
|
$opacity == $this->currentLineTransparency["opacity"] |
3104
|
|
|
) { |
3105
|
|
|
return; |
3106
|
|
|
} |
3107
|
|
|
|
3108
|
|
|
$this->currentLineTransparency["mode"] = $mode; |
3109
|
|
|
$this->currentLineTransparency["opacity"] = $opacity; |
3110
|
|
|
|
3111
|
|
|
$options = array( |
3112
|
|
|
"BM" => "/$mode", |
3113
|
|
|
"CA" => (float)$opacity |
3114
|
|
|
); |
3115
|
|
|
|
3116
|
|
|
$this->setGraphicsState($options); |
3117
|
|
|
} |
3118
|
|
|
|
3119
|
|
|
/** |
3120
|
|
|
* Set current blend mode & opacity for filled objects. |
3121
|
|
|
* |
3122
|
|
|
* Valid blend modes are: |
3123
|
|
|
* |
3124
|
|
|
* Normal, Multiply, Screen, Overlay, Darken, Lighten, |
3125
|
|
|
* ColorDogde, ColorBurn, HardLight, SoftLight, Difference, |
3126
|
|
|
* Exclusion |
3127
|
|
|
* |
3128
|
|
|
* @param string $mode the blend mode to use |
3129
|
|
|
* @param float $opacity 0.0 fully transparent, 1.0 fully opaque |
3130
|
|
|
*/ |
3131
|
|
View Code Duplication |
function setFillTransparency($mode, $opacity) |
|
|
|
|
3132
|
|
|
{ |
3133
|
|
|
static $blend_modes = array( |
3134
|
|
|
"Normal", |
3135
|
|
|
"Multiply", |
3136
|
|
|
"Screen", |
3137
|
|
|
"Overlay", |
3138
|
|
|
"Darken", |
3139
|
|
|
"Lighten", |
3140
|
|
|
"ColorDogde", |
3141
|
|
|
"ColorBurn", |
3142
|
|
|
"HardLight", |
3143
|
|
|
"SoftLight", |
3144
|
|
|
"Difference", |
3145
|
|
|
"Exclusion" |
3146
|
|
|
); |
3147
|
|
|
|
3148
|
|
|
if (!in_array($mode, $blend_modes)) { |
3149
|
|
|
$mode = "Normal"; |
3150
|
|
|
} |
3151
|
|
|
|
3152
|
|
|
if ($mode === $this->currentFillTransparency["mode"] && |
3153
|
|
|
$opacity == $this->currentFillTransparency["opacity"] |
3154
|
|
|
) { |
3155
|
|
|
return; |
3156
|
|
|
} |
3157
|
|
|
|
3158
|
|
|
$this->currentFillTransparency["mode"] = $mode; |
3159
|
|
|
$this->currentFillTransparency["opacity"] = $opacity; |
3160
|
|
|
|
3161
|
|
|
$options = array( |
3162
|
|
|
"BM" => "/$mode", |
3163
|
|
|
"ca" => (float)$opacity, |
3164
|
|
|
); |
3165
|
|
|
|
3166
|
|
|
$this->setGraphicsState($options); |
3167
|
|
|
} |
3168
|
|
|
|
3169
|
|
|
/** |
3170
|
|
|
* draw a line from one set of coordinates to another |
3171
|
|
|
* |
3172
|
|
|
* @param $x1 |
3173
|
|
|
* @param $y1 |
3174
|
|
|
* @param $x2 |
3175
|
|
|
* @param $y2 |
3176
|
|
|
* @param bool $stroke |
3177
|
|
|
*/ |
3178
|
|
|
function line($x1, $y1, $x2, $y2, $stroke = true) |
|
|
|
|
3179
|
|
|
{ |
3180
|
|
|
$this->addContent(sprintf("\n%.3F %.3F m %.3F %.3F l", $x1, $y1, $x2, $y2)); |
3181
|
|
|
|
3182
|
|
|
if ($stroke) { |
3183
|
|
|
$this->addContent(' S'); |
3184
|
|
|
} |
3185
|
|
|
} |
3186
|
|
|
|
3187
|
|
|
/** |
3188
|
|
|
* draw a bezier curve based on 4 control points |
3189
|
|
|
* |
3190
|
|
|
* @param $x0 |
3191
|
|
|
* @param $y0 |
3192
|
|
|
* @param $x1 |
3193
|
|
|
* @param $y1 |
3194
|
|
|
* @param $x2 |
3195
|
|
|
* @param $y2 |
3196
|
|
|
* @param $x3 |
3197
|
|
|
* @param $y3 |
3198
|
|
|
*/ |
3199
|
|
|
function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3) |
|
|
|
|
3200
|
|
|
{ |
3201
|
|
|
// in the current line style, draw a bezier curve from (x0,y0) to (x3,y3) using the other two points |
3202
|
|
|
// as the control points for the curve. |
3203
|
|
|
$this->addContent( |
3204
|
|
|
sprintf("\n%.3F %.3F m %.3F %.3F %.3F %.3F %.3F %.3F c S", $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3) |
3205
|
|
|
); |
3206
|
|
|
} |
3207
|
|
|
|
3208
|
|
|
/** |
3209
|
|
|
* draw a part of an ellipse |
3210
|
|
|
* |
3211
|
|
|
* @param $x0 |
3212
|
|
|
* @param $y0 |
3213
|
|
|
* @param $astart |
3214
|
|
|
* @param $afinish |
3215
|
|
|
* @param $r1 |
3216
|
|
|
* @param int $r2 |
3217
|
|
|
* @param int $angle |
3218
|
|
|
* @param int $nSeg |
3219
|
|
|
*/ |
3220
|
|
|
function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8) |
|
|
|
|
3221
|
|
|
{ |
3222
|
|
|
$this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, false); |
3223
|
|
|
} |
3224
|
|
|
|
3225
|
|
|
/** |
3226
|
|
|
* draw a filled ellipse |
3227
|
|
|
* |
3228
|
|
|
* @param $x0 |
3229
|
|
|
* @param $y0 |
3230
|
|
|
* @param $r1 |
3231
|
|
|
* @param int $r2 |
3232
|
|
|
* @param int $angle |
3233
|
|
|
* @param int $nSeg |
3234
|
|
|
* @param int $astart |
3235
|
|
|
* @param int $afinish |
3236
|
|
|
*/ |
3237
|
|
|
function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360) |
|
|
|
|
3238
|
|
|
{ |
3239
|
|
|
$this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, true, true); |
3240
|
|
|
} |
3241
|
|
|
|
3242
|
|
|
/** |
3243
|
|
|
* @param $x |
3244
|
|
|
* @param $y |
3245
|
|
|
*/ |
3246
|
|
|
function lineTo($x, $y) |
|
|
|
|
3247
|
|
|
{ |
3248
|
|
|
$this->addContent(sprintf("\n%.3F %.3F l", $x, $y)); |
3249
|
|
|
} |
3250
|
|
|
|
3251
|
|
|
/** |
3252
|
|
|
* @param $x |
3253
|
|
|
* @param $y |
3254
|
|
|
*/ |
3255
|
|
|
function moveTo($x, $y) |
|
|
|
|
3256
|
|
|
{ |
3257
|
|
|
$this->addContent(sprintf("\n%.3F %.3F m", $x, $y)); |
3258
|
|
|
} |
3259
|
|
|
|
3260
|
|
|
/** |
3261
|
|
|
* draw a bezier curve based on 4 control points |
3262
|
|
|
* |
3263
|
|
|
* @param $x1 |
3264
|
|
|
* @param $y1 |
3265
|
|
|
* @param $x2 |
3266
|
|
|
* @param $y2 |
3267
|
|
|
* @param $x3 |
3268
|
|
|
* @param $y3 |
3269
|
|
|
*/ |
3270
|
|
|
function curveTo($x1, $y1, $x2, $y2, $x3, $y3) |
|
|
|
|
3271
|
|
|
{ |
3272
|
|
|
$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F %.3F %.3F c", $x1, $y1, $x2, $y2, $x3, $y3)); |
3273
|
|
|
} |
3274
|
|
|
|
3275
|
|
|
/** |
3276
|
|
|
* draw a bezier curve based on 4 control points |
3277
|
|
|
*/ function quadTo($cpx, $cpy, $x, $y) |
|
|
|
|
3278
|
|
|
{ |
3279
|
|
|
$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F v", $cpx, $cpy, $x, $y)); |
3280
|
|
|
} |
3281
|
|
|
|
3282
|
|
|
function closePath() |
|
|
|
|
3283
|
|
|
{ |
3284
|
|
|
$this->addContent(' h'); |
3285
|
|
|
} |
3286
|
|
|
|
3287
|
|
|
function endPath() |
|
|
|
|
3288
|
|
|
{ |
3289
|
|
|
$this->addContent(' n'); |
3290
|
|
|
} |
3291
|
|
|
|
3292
|
|
|
/** |
3293
|
|
|
* draw an ellipse |
3294
|
|
|
* note that the part and filled ellipse are just special cases of this function |
3295
|
|
|
* |
3296
|
|
|
* draws an ellipse in the current line style |
3297
|
|
|
* centered at $x0,$y0, radii $r1,$r2 |
3298
|
|
|
* if $r2 is not set, then a circle is drawn |
3299
|
|
|
* from $astart to $afinish, measured in degrees, running anti-clockwise from the right hand side of the ellipse. |
3300
|
|
|
* nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a |
3301
|
|
|
* pretty crappy shape at 2, as we are approximating with bezier curves. |
3302
|
|
|
* |
3303
|
|
|
* @param $x0 |
3304
|
|
|
* @param $y0 |
3305
|
|
|
* @param $r1 |
3306
|
|
|
* @param int $r2 |
3307
|
|
|
* @param int $angle |
3308
|
|
|
* @param int $nSeg |
3309
|
|
|
* @param int $astart |
3310
|
|
|
* @param int $afinish |
3311
|
|
|
* @param bool $close |
3312
|
|
|
* @param bool $fill |
3313
|
|
|
* @param bool $stroke |
3314
|
|
|
* @param bool $incomplete |
3315
|
|
|
*/ |
3316
|
|
|
function ellipse( |
|
|
|
|
3317
|
|
|
$x0, |
3318
|
|
|
$y0, |
3319
|
|
|
$r1, |
3320
|
|
|
$r2 = 0, |
3321
|
|
|
$angle = 0, |
3322
|
|
|
$nSeg = 8, |
3323
|
|
|
$astart = 0, |
3324
|
|
|
$afinish = 360, |
3325
|
|
|
$close = true, |
3326
|
|
|
$fill = false, |
3327
|
|
|
$stroke = true, |
3328
|
|
|
$incomplete = false |
3329
|
|
|
) { |
3330
|
|
|
if ($r1 == 0) { |
3331
|
|
|
return; |
3332
|
|
|
} |
3333
|
|
|
|
3334
|
|
|
if ($r2 == 0) { |
3335
|
|
|
$r2 = $r1; |
3336
|
|
|
} |
3337
|
|
|
|
3338
|
|
|
if ($nSeg < 2) { |
3339
|
|
|
$nSeg = 2; |
3340
|
|
|
} |
3341
|
|
|
|
3342
|
|
|
$astart = deg2rad((float)$astart); |
3343
|
|
|
$afinish = deg2rad((float)$afinish); |
3344
|
|
|
$totalAngle = $afinish - $astart; |
3345
|
|
|
|
3346
|
|
|
$dt = $totalAngle / $nSeg; |
3347
|
|
|
$dtm = $dt / 3; |
3348
|
|
|
|
3349
|
|
|
if ($angle != 0) { |
3350
|
|
|
$a = -1 * deg2rad((float)$angle); |
3351
|
|
|
|
3352
|
|
|
$this->addContent( |
3353
|
|
|
sprintf("\n q %.3F %.3F %.3F %.3F %.3F %.3F cm", cos($a), -sin($a), sin($a), cos($a), $x0, $y0) |
3354
|
|
|
); |
3355
|
|
|
|
3356
|
|
|
$x0 = 0; |
3357
|
|
|
$y0 = 0; |
3358
|
|
|
} |
3359
|
|
|
|
3360
|
|
|
$t1 = $astart; |
3361
|
|
|
$a0 = $x0 + $r1 * cos($t1); |
3362
|
|
|
$b0 = $y0 + $r2 * sin($t1); |
3363
|
|
|
$c0 = -$r1 * sin($t1); |
3364
|
|
|
$d0 = $r2 * cos($t1); |
3365
|
|
|
|
3366
|
|
|
if (!$incomplete) { |
3367
|
|
|
$this->addContent(sprintf("\n%.3F %.3F m ", $a0, $b0)); |
3368
|
|
|
} |
3369
|
|
|
|
3370
|
|
|
for ($i = 1; $i <= $nSeg; $i++) { |
3371
|
|
|
// draw this bit of the total curve |
3372
|
|
|
$t1 = $i * $dt + $astart; |
3373
|
|
|
$a1 = $x0 + $r1 * cos($t1); |
3374
|
|
|
$b1 = $y0 + $r2 * sin($t1); |
3375
|
|
|
$c1 = -$r1 * sin($t1); |
3376
|
|
|
$d1 = $r2 * cos($t1); |
3377
|
|
|
|
3378
|
|
|
$this->addContent( |
3379
|
|
|
sprintf( |
3380
|
|
|
"\n%.3F %.3F %.3F %.3F %.3F %.3F c", |
3381
|
|
|
($a0 + $c0 * $dtm), |
3382
|
|
|
($b0 + $d0 * $dtm), |
3383
|
|
|
($a1 - $c1 * $dtm), |
3384
|
|
|
($b1 - $d1 * $dtm), |
3385
|
|
|
$a1, |
3386
|
|
|
$b1 |
3387
|
|
|
) |
3388
|
|
|
); |
3389
|
|
|
|
3390
|
|
|
$a0 = $a1; |
3391
|
|
|
$b0 = $b1; |
3392
|
|
|
$c0 = $c1; |
3393
|
|
|
$d0 = $d1; |
3394
|
|
|
} |
3395
|
|
|
|
3396
|
|
|
if (!$incomplete) { |
3397
|
|
|
if ($fill) { |
3398
|
|
|
$this->addContent(' f'); |
3399
|
|
|
} |
3400
|
|
|
|
3401
|
|
|
if ($stroke) { |
3402
|
|
|
if ($close) { |
3403
|
|
|
$this->addContent(' s'); // small 's' signifies closing the path as well |
3404
|
|
|
} else { |
3405
|
|
|
$this->addContent(' S'); |
3406
|
|
|
} |
3407
|
|
|
} |
3408
|
|
|
} |
3409
|
|
|
|
3410
|
|
|
if ($angle != 0) { |
3411
|
|
|
$this->addContent(' Q'); |
3412
|
|
|
} |
3413
|
|
|
} |
3414
|
|
|
|
3415
|
|
|
/** |
3416
|
|
|
* this sets the line drawing style. |
3417
|
|
|
* width, is the thickness of the line in user units |
3418
|
|
|
* cap is the type of cap to put on the line, values can be 'butt','round','square' |
3419
|
|
|
* where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the |
3420
|
|
|
* end of the line. |
3421
|
|
|
* join can be 'miter', 'round', 'bevel' |
3422
|
|
|
* dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the |
3423
|
|
|
* on and off dashes. |
3424
|
|
|
* (2) represents 2 on, 2 off, 2 on , 2 off ... |
3425
|
|
|
* (2,1) is 2 on, 1 off, 2 on, 1 off.. etc |
3426
|
|
|
* phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts. |
3427
|
|
|
* |
3428
|
|
|
* @param int $width |
3429
|
|
|
* @param string $cap |
3430
|
|
|
* @param string $join |
3431
|
|
|
* @param string $dash |
3432
|
|
|
* @param int $phase |
3433
|
|
|
*/ |
3434
|
|
|
function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0) |
|
|
|
|
3435
|
|
|
{ |
3436
|
|
|
// this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day |
3437
|
|
|
$string = ''; |
3438
|
|
|
|
3439
|
|
|
if ($width > 0) { |
3440
|
|
|
$string .= "$width w"; |
3441
|
|
|
} |
3442
|
|
|
|
3443
|
|
|
$ca = array('butt' => 0, 'round' => 1, 'square' => 2); |
3444
|
|
|
|
3445
|
|
|
if (isset($ca[$cap])) { |
3446
|
|
|
$string .= " $ca[$cap] J"; |
3447
|
|
|
} |
3448
|
|
|
|
3449
|
|
|
$ja = array('miter' => 0, 'round' => 1, 'bevel' => 2); |
3450
|
|
|
|
3451
|
|
|
if (isset($ja[$join])) { |
3452
|
|
|
$string .= " $ja[$join] j"; |
3453
|
|
|
} |
3454
|
|
|
|
3455
|
|
|
if (is_array($dash)) { |
3456
|
|
|
$string .= ' [ ' . implode(' ', $dash) . " ] $phase d"; |
3457
|
|
|
} |
3458
|
|
|
|
3459
|
|
|
$this->currentLineStyle = $string; |
3460
|
|
|
$this->addContent("\n$string"); |
3461
|
|
|
} |
3462
|
|
|
|
3463
|
|
|
/** |
3464
|
|
|
* draw a polygon, the syntax for this is similar to the GD polygon command |
3465
|
|
|
* |
3466
|
|
|
* @param $p |
3467
|
|
|
* @param $np |
3468
|
|
|
* @param bool $f |
3469
|
|
|
*/ |
3470
|
|
|
function polygon($p, $np, $f = false) |
|
|
|
|
3471
|
|
|
{ |
3472
|
|
|
$this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1])); |
3473
|
|
|
|
3474
|
|
|
for ($i = 2; $i < $np * 2; $i = $i + 2) { |
3475
|
|
|
$this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1])); |
3476
|
|
|
} |
3477
|
|
|
|
3478
|
|
|
if ($f) { |
3479
|
|
|
$this->addContent(' f'); |
3480
|
|
|
} else { |
3481
|
|
|
$this->addContent(' S'); |
3482
|
|
|
} |
3483
|
|
|
} |
3484
|
|
|
|
3485
|
|
|
/** |
3486
|
|
|
* a filled rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not |
3487
|
|
|
* the coordinates of the upper-right corner |
3488
|
|
|
* |
3489
|
|
|
* @param $x1 |
3490
|
|
|
* @param $y1 |
3491
|
|
|
* @param $width |
3492
|
|
|
* @param $height |
3493
|
|
|
*/ |
3494
|
|
|
function filledRectangle($x1, $y1, $width, $height) |
|
|
|
|
3495
|
|
|
{ |
3496
|
|
|
$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re f", $x1, $y1, $width, $height)); |
3497
|
|
|
} |
3498
|
|
|
|
3499
|
|
|
/** |
3500
|
|
|
* draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not |
3501
|
|
|
* the coordinates of the upper-right corner |
3502
|
|
|
* |
3503
|
|
|
* @param $x1 |
3504
|
|
|
* @param $y1 |
3505
|
|
|
* @param $width |
3506
|
|
|
* @param $height |
3507
|
|
|
*/ |
3508
|
|
|
function rectangle($x1, $y1, $width, $height) |
|
|
|
|
3509
|
|
|
{ |
3510
|
|
|
$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re S", $x1, $y1, $width, $height)); |
3511
|
|
|
} |
3512
|
|
|
|
3513
|
|
|
/** |
3514
|
|
|
* draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not |
3515
|
|
|
* the coordinates of the upper-right corner |
3516
|
|
|
* |
3517
|
|
|
* @param $x1 |
3518
|
|
|
* @param $y1 |
3519
|
|
|
* @param $width |
3520
|
|
|
* @param $height |
3521
|
|
|
*/ |
3522
|
|
|
function rect($x1, $y1, $width, $height) |
|
|
|
|
3523
|
|
|
{ |
3524
|
|
|
$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re", $x1, $y1, $width, $height)); |
3525
|
|
|
} |
3526
|
|
|
|
3527
|
|
|
function stroke() |
|
|
|
|
3528
|
|
|
{ |
3529
|
|
|
$this->addContent("\nS"); |
3530
|
|
|
} |
3531
|
|
|
|
3532
|
|
|
function fill() |
|
|
|
|
3533
|
|
|
{ |
3534
|
|
|
$this->addContent("\nf" . ($this->fillRule === "evenodd" ? "*" : "")); |
3535
|
|
|
} |
3536
|
|
|
|
3537
|
|
|
function fillStroke() |
|
|
|
|
3538
|
|
|
{ |
3539
|
|
|
$this->addContent("\nb" . ($this->fillRule === "evenodd" ? "*" : "")); |
3540
|
|
|
} |
3541
|
|
|
|
3542
|
|
|
/** |
3543
|
|
|
* save the current graphic state |
3544
|
|
|
*/ |
3545
|
|
|
function save() |
|
|
|
|
3546
|
|
|
{ |
3547
|
|
|
// we must reset the color cache or it will keep bad colors after clipping |
3548
|
|
|
$this->currentColor = null; |
|
|
|
|
3549
|
|
|
$this->currentStrokeColor = null; |
|
|
|
|
3550
|
|
|
$this->addContent("\nq"); |
3551
|
|
|
} |
3552
|
|
|
|
3553
|
|
|
/** |
3554
|
|
|
* restore the last graphic state |
3555
|
|
|
*/ |
3556
|
|
|
function restore() |
|
|
|
|
3557
|
|
|
{ |
3558
|
|
|
// we must reset the color cache or it will keep bad colors after clipping |
3559
|
|
|
$this->currentColor = null; |
|
|
|
|
3560
|
|
|
$this->currentStrokeColor = null; |
|
|
|
|
3561
|
|
|
$this->addContent("\nQ"); |
3562
|
|
|
} |
3563
|
|
|
|
3564
|
|
|
/** |
3565
|
|
|
* draw a clipping rectangle, all the elements added after this will be clipped |
3566
|
|
|
* |
3567
|
|
|
* @param $x1 |
3568
|
|
|
* @param $y1 |
3569
|
|
|
* @param $width |
3570
|
|
|
* @param $height |
3571
|
|
|
*/ |
3572
|
|
|
function clippingRectangle($x1, $y1, $width, $height) |
|
|
|
|
3573
|
|
|
{ |
3574
|
|
|
$this->save(); |
3575
|
|
|
$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re W n", $x1, $y1, $width, $height)); |
3576
|
|
|
} |
3577
|
|
|
|
3578
|
|
|
/** |
3579
|
|
|
* draw a clipping rounded rectangle, all the elements added after this will be clipped |
3580
|
|
|
* |
3581
|
|
|
* @param $x1 |
3582
|
|
|
* @param $y1 |
3583
|
|
|
* @param $w |
3584
|
|
|
* @param $h |
3585
|
|
|
* @param $rTL |
3586
|
|
|
* @param $rTR |
3587
|
|
|
* @param $rBR |
3588
|
|
|
* @param $rBL |
3589
|
|
|
*/ |
3590
|
|
|
function clippingRectangleRounded($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL) |
|
|
|
|
3591
|
|
|
{ |
3592
|
|
|
$this->save(); |
3593
|
|
|
|
3594
|
|
|
// start: top edge, left end |
3595
|
|
|
$this->addContent(sprintf("\n%.3F %.3F m ", $x1, $y1 - $rTL + $h)); |
3596
|
|
|
|
3597
|
|
|
// line: bottom edge, left end |
3598
|
|
|
$this->addContent(sprintf("\n%.3F %.3F l ", $x1, $y1 + $rBL)); |
3599
|
|
|
|
3600
|
|
|
// curve: bottom-left corner |
3601
|
|
|
$this->ellipse($x1 + $rBL, $y1 + $rBL, $rBL, 0, 0, 8, 180, 270, false, false, false, true); |
3602
|
|
|
|
3603
|
|
|
// line: right edge, bottom end |
3604
|
|
|
$this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w - $rBR, $y1)); |
3605
|
|
|
|
3606
|
|
|
// curve: bottom-right corner |
3607
|
|
|
$this->ellipse($x1 + $w - $rBR, $y1 + $rBR, $rBR, 0, 0, 8, 270, 360, false, false, false, true); |
3608
|
|
|
|
3609
|
|
|
// line: right edge, top end |
3610
|
|
|
$this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w, $y1 + $h - $rTR)); |
3611
|
|
|
|
3612
|
|
|
// curve: bottom-right corner |
3613
|
|
|
$this->ellipse($x1 + $w - $rTR, $y1 + $h - $rTR, $rTR, 0, 0, 8, 0, 90, false, false, false, true); |
3614
|
|
|
|
3615
|
|
|
// line: bottom edge, right end |
3616
|
|
|
$this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rTL, $y1 + $h)); |
3617
|
|
|
|
3618
|
|
|
// curve: top-right corner |
3619
|
|
|
$this->ellipse($x1 + $rTL, $y1 + $h - $rTL, $rTL, 0, 0, 8, 90, 180, false, false, false, true); |
3620
|
|
|
|
3621
|
|
|
// line: top edge, left end |
3622
|
|
|
$this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rBL, $y1)); |
3623
|
|
|
|
3624
|
|
|
// Close & clip |
3625
|
|
|
$this->addContent(" W n"); |
3626
|
|
|
} |
3627
|
|
|
|
3628
|
|
|
/** |
3629
|
|
|
* ends the last clipping shape |
3630
|
|
|
*/ |
3631
|
|
|
function clippingEnd() |
|
|
|
|
3632
|
|
|
{ |
3633
|
|
|
$this->restore(); |
3634
|
|
|
} |
3635
|
|
|
|
3636
|
|
|
/** |
3637
|
|
|
* scale |
3638
|
|
|
* |
3639
|
|
|
* @param float $s_x scaling factor for width as percent |
3640
|
|
|
* @param float $s_y scaling factor for height as percent |
3641
|
|
|
* @param float $x Origin abscissa |
3642
|
|
|
* @param float $y Origin ordinate |
3643
|
|
|
*/ |
3644
|
|
|
function scale($s_x, $s_y, $x, $y) |
|
|
|
|
3645
|
|
|
{ |
3646
|
|
|
$y = $this->currentPageSize["height"] - $y; |
3647
|
|
|
|
3648
|
|
|
$tm = array( |
3649
|
|
|
$s_x, |
3650
|
|
|
0, |
3651
|
|
|
0, |
3652
|
|
|
$s_y, |
3653
|
|
|
$x * (1 - $s_x), |
3654
|
|
|
$y * (1 - $s_y) |
3655
|
|
|
); |
3656
|
|
|
|
3657
|
|
|
$this->transform($tm); |
3658
|
|
|
} |
3659
|
|
|
|
3660
|
|
|
/** |
3661
|
|
|
* translate |
3662
|
|
|
* |
3663
|
|
|
* @param float $t_x movement to the right |
3664
|
|
|
* @param float $t_y movement to the bottom |
3665
|
|
|
*/ |
3666
|
|
|
function translate($t_x, $t_y) |
|
|
|
|
3667
|
|
|
{ |
3668
|
|
|
$tm = array( |
3669
|
|
|
1, |
3670
|
|
|
0, |
3671
|
|
|
0, |
3672
|
|
|
1, |
3673
|
|
|
$t_x, |
3674
|
|
|
-$t_y |
3675
|
|
|
); |
3676
|
|
|
|
3677
|
|
|
$this->transform($tm); |
3678
|
|
|
} |
3679
|
|
|
|
3680
|
|
|
/** |
3681
|
|
|
* rotate |
3682
|
|
|
* |
3683
|
|
|
* @param float $angle angle in degrees for counter-clockwise rotation |
3684
|
|
|
* @param float $x Origin abscissa |
3685
|
|
|
* @param float $y Origin ordinate |
3686
|
|
|
*/ |
3687
|
|
|
function rotate($angle, $x, $y) |
|
|
|
|
3688
|
|
|
{ |
3689
|
|
|
$y = $this->currentPageSize["height"] - $y; |
3690
|
|
|
|
3691
|
|
|
$a = deg2rad($angle); |
3692
|
|
|
$cos_a = cos($a); |
3693
|
|
|
$sin_a = sin($a); |
3694
|
|
|
|
3695
|
|
|
$tm = array( |
3696
|
|
|
$cos_a, |
3697
|
|
|
-$sin_a, |
3698
|
|
|
$sin_a, |
3699
|
|
|
$cos_a, |
3700
|
|
|
$x - $sin_a * $y - $cos_a * $x, |
3701
|
|
|
$y - $cos_a * $y + $sin_a * $x, |
3702
|
|
|
); |
3703
|
|
|
|
3704
|
|
|
$this->transform($tm); |
3705
|
|
|
} |
3706
|
|
|
|
3707
|
|
|
/** |
3708
|
|
|
* skew |
3709
|
|
|
* |
3710
|
|
|
* @param float $angle_x |
3711
|
|
|
* @param float $angle_y |
3712
|
|
|
* @param float $x Origin abscissa |
3713
|
|
|
* @param float $y Origin ordinate |
3714
|
|
|
*/ |
3715
|
|
|
function skew($angle_x, $angle_y, $x, $y) |
|
|
|
|
3716
|
|
|
{ |
3717
|
|
|
$y = $this->currentPageSize["height"] - $y; |
3718
|
|
|
|
3719
|
|
|
$tan_x = tan(deg2rad($angle_x)); |
3720
|
|
|
$tan_y = tan(deg2rad($angle_y)); |
3721
|
|
|
|
3722
|
|
|
$tm = array( |
3723
|
|
|
1, |
3724
|
|
|
-$tan_y, |
3725
|
|
|
-$tan_x, |
3726
|
|
|
1, |
3727
|
|
|
$tan_x * $y, |
3728
|
|
|
$tan_y * $x, |
3729
|
|
|
); |
3730
|
|
|
|
3731
|
|
|
$this->transform($tm); |
3732
|
|
|
} |
3733
|
|
|
|
3734
|
|
|
/** |
3735
|
|
|
* apply graphic transformations |
3736
|
|
|
* |
3737
|
|
|
* @param array $tm transformation matrix |
3738
|
|
|
*/ |
3739
|
|
|
function transform($tm) |
|
|
|
|
3740
|
|
|
{ |
3741
|
|
|
$this->addContent(vsprintf("\n %.3F %.3F %.3F %.3F %.3F %.3F cm", $tm)); |
3742
|
|
|
} |
3743
|
|
|
|
3744
|
|
|
/** |
3745
|
|
|
* add a new page to the document |
3746
|
|
|
* this also makes the new page the current active object |
3747
|
|
|
* |
3748
|
|
|
* @param int $insert |
3749
|
|
|
* @param int $id |
3750
|
|
|
* @param string $pos |
3751
|
|
|
* @return int |
3752
|
|
|
*/ |
3753
|
|
|
function newPage($insert = 0, $id = 0, $pos = 'after') |
|
|
|
|
3754
|
|
|
{ |
3755
|
|
|
// if there is a state saved, then go up the stack closing them |
3756
|
|
|
// then on the new page, re-open them with the right setings |
3757
|
|
|
|
3758
|
|
|
if ($this->nStateStack) { |
3759
|
|
|
for ($i = $this->nStateStack; $i >= 1; $i--) { |
3760
|
|
|
$this->restoreState($i); |
3761
|
|
|
} |
3762
|
|
|
} |
3763
|
|
|
|
3764
|
|
|
$this->numObj++; |
3765
|
|
|
|
3766
|
|
|
if ($insert) { |
3767
|
|
|
// the id from the ezPdf class is the id of the contents of the page, not the page object itself |
3768
|
|
|
// query that object to find the parent |
3769
|
|
|
$rid = $this->objects[$id]['onPage']; |
3770
|
|
|
$opt = array('rid' => $rid, 'pos' => $pos); |
3771
|
|
|
$this->o_page($this->numObj, 'new', $opt); |
|
|
|
|
3772
|
|
|
} else { |
3773
|
|
|
$this->o_page($this->numObj, 'new'); |
3774
|
|
|
} |
3775
|
|
|
|
3776
|
|
|
// if there is a stack saved, then put that onto the page |
3777
|
|
|
if ($this->nStateStack) { |
3778
|
|
|
for ($i = 1; $i <= $this->nStateStack; $i++) { |
3779
|
|
|
$this->saveState($i); |
3780
|
|
|
} |
3781
|
|
|
} |
3782
|
|
|
|
3783
|
|
|
// and if there has been a stroke or fill color set, then transfer them |
3784
|
|
|
if (isset($this->currentColor)) { |
3785
|
|
|
$this->setColor($this->currentColor, true); |
3786
|
|
|
} |
3787
|
|
|
|
3788
|
|
|
if (isset($this->currentStrokeColor)) { |
3789
|
|
|
$this->setStrokeColor($this->currentStrokeColor, true); |
3790
|
|
|
} |
3791
|
|
|
|
3792
|
|
|
// if there is a line style set, then put this in too |
3793
|
|
|
if (mb_strlen($this->currentLineStyle, '8bit')) { |
3794
|
|
|
$this->addContent("\n$this->currentLineStyle"); |
3795
|
|
|
} |
3796
|
|
|
|
3797
|
|
|
// the call to the o_page object set currentContents to the present page, so this can be returned as the page id |
3798
|
|
|
return $this->currentContents; |
3799
|
|
|
} |
3800
|
|
|
|
3801
|
|
|
/** |
3802
|
|
|
* Streams the PDF to the client. |
3803
|
|
|
* |
3804
|
|
|
* @param string $filename The filename to present to the client. |
3805
|
|
|
* @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1). |
3806
|
|
|
*/ |
3807
|
|
|
function stream($filename = "document.pdf", $options = array()) |
|
|
|
|
3808
|
|
|
{ |
3809
|
|
|
if (headers_sent()) { |
3810
|
|
|
die("Unable to stream pdf: headers already sent"); |
|
|
|
|
3811
|
|
|
} |
3812
|
|
|
|
3813
|
|
|
if (!isset($options["compress"])) $options["compress"] = true; |
3814
|
|
|
if (!isset($options["Attachment"])) $options["Attachment"] = true; |
3815
|
|
|
|
3816
|
|
|
$debug = !$options['compress']; |
3817
|
|
|
$tmp = ltrim($this->output($debug)); |
3818
|
|
|
|
3819
|
|
|
header("Cache-Control: private"); |
3820
|
|
|
header("Content-Type: application/pdf"); |
3821
|
|
|
header("Content-Length: " . mb_strlen($tmp, "8bit")); |
3822
|
|
|
|
3823
|
|
|
$filename = str_replace(array("\n", "'"), "", basename($filename, ".pdf")) . ".pdf"; |
3824
|
|
|
$attachment = $options["Attachment"] ? "attachment" : "inline"; |
3825
|
|
|
|
3826
|
|
|
$encoding = mb_detect_encoding($filename); |
3827
|
|
|
$fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding); |
3828
|
|
|
$fallbackfilename = str_replace("\"", "", $fallbackfilename); |
3829
|
|
|
$encodedfilename = rawurlencode($filename); |
3830
|
|
|
|
3831
|
|
|
$contentDisposition = "Content-Disposition: $attachment; filename=\"$fallbackfilename\""; |
3832
|
|
|
if ($fallbackfilename !== $filename) { |
3833
|
|
|
$contentDisposition .= "; filename*=UTF-8''$encodedfilename"; |
3834
|
|
|
} |
3835
|
|
|
header($contentDisposition); |
3836
|
|
|
|
3837
|
|
|
echo $tmp; |
3838
|
|
|
flush(); |
3839
|
|
|
} |
3840
|
|
|
|
3841
|
|
|
/** |
3842
|
|
|
* return the height in units of the current font in the given size |
3843
|
|
|
* |
3844
|
|
|
* @param $size |
3845
|
|
|
* @return float|int |
3846
|
|
|
*/ |
3847
|
|
|
function getFontHeight($size) |
|
|
|
|
3848
|
|
|
{ |
3849
|
|
|
if (!$this->numFonts) { |
3850
|
|
|
$this->selectFont($this->defaultFont); |
3851
|
|
|
} |
3852
|
|
|
|
3853
|
|
|
$font = $this->fonts[$this->currentFont]; |
3854
|
|
|
|
3855
|
|
|
// for the current font, and the given size, what is the height of the font in user units |
3856
|
|
|
if (isset($font['Ascender']) && isset($font['Descender'])) { |
3857
|
|
|
$h = $font['Ascender'] - $font['Descender']; |
3858
|
|
|
} else { |
3859
|
|
|
$h = $font['FontBBox'][3] - $font['FontBBox'][1]; |
3860
|
|
|
} |
3861
|
|
|
|
3862
|
|
|
// have to adjust by a font offset for Windows fonts. unfortunately it looks like |
3863
|
|
|
// the bounding box calculations are wrong and I don't know why. |
3864
|
|
|
if (isset($font['FontHeightOffset'])) { |
3865
|
|
|
// For CourierNew from Windows this needs to be -646 to match the |
3866
|
|
|
// Adobe native Courier font. |
3867
|
|
|
// |
3868
|
|
|
// For FreeMono from GNU this needs to be -337 to match the |
3869
|
|
|
// Courier font. |
3870
|
|
|
// |
3871
|
|
|
// Both have been added manually to the .afm and .ufm files. |
3872
|
|
|
$h += (int)$font['FontHeightOffset']; |
3873
|
|
|
} |
3874
|
|
|
|
3875
|
|
|
return $size * $h / 1000; |
3876
|
|
|
} |
3877
|
|
|
|
3878
|
|
|
/** |
3879
|
|
|
* @param $size |
3880
|
|
|
* @return float|int |
3881
|
|
|
*/ |
3882
|
|
|
function getFontXHeight($size) |
|
|
|
|
3883
|
|
|
{ |
3884
|
|
|
if (!$this->numFonts) { |
3885
|
|
|
$this->selectFont($this->defaultFont); |
3886
|
|
|
} |
3887
|
|
|
|
3888
|
|
|
$font = $this->fonts[$this->currentFont]; |
3889
|
|
|
|
3890
|
|
|
// for the current font, and the given size, what is the height of the font in user units |
3891
|
|
|
if (isset($font['XHeight'])) { |
3892
|
|
|
$xh = $font['Ascender'] - $font['Descender']; |
3893
|
|
|
} else { |
3894
|
|
|
$xh = $this->getFontHeight($size) / 2; |
3895
|
|
|
} |
3896
|
|
|
|
3897
|
|
|
return $size * $xh / 1000; |
3898
|
|
|
} |
3899
|
|
|
|
3900
|
|
|
/** |
3901
|
|
|
* return the font descender, this will normally return a negative number |
3902
|
|
|
* if you add this number to the baseline, you get the level of the bottom of the font |
3903
|
|
|
* it is in the pdf user units |
3904
|
|
|
* |
3905
|
|
|
* @param $size |
3906
|
|
|
* @return float|int |
3907
|
|
|
*/ |
3908
|
|
|
function getFontDescender($size) |
|
|
|
|
3909
|
|
|
{ |
3910
|
|
|
// note that this will most likely return a negative value |
3911
|
|
|
if (!$this->numFonts) { |
3912
|
|
|
$this->selectFont($this->defaultFont); |
3913
|
|
|
} |
3914
|
|
|
|
3915
|
|
|
//$h = $this->fonts[$this->currentFont]['FontBBox'][1]; |
|
|
|
|
3916
|
|
|
$h = $this->fonts[$this->currentFont]['Descender']; |
3917
|
|
|
|
3918
|
|
|
return $size * $h / 1000; |
3919
|
|
|
} |
3920
|
|
|
|
3921
|
|
|
/** |
3922
|
|
|
* filter the text, this is applied to all text just before being inserted into the pdf document |
3923
|
|
|
* it escapes the various things that need to be escaped, and so on |
3924
|
|
|
* |
3925
|
|
|
* @access private |
3926
|
|
|
* |
3927
|
|
|
* @param $text |
3928
|
|
|
* @param bool $bom |
3929
|
|
|
* @param bool $convert_encoding |
3930
|
|
|
* @return string |
3931
|
|
|
*/ |
3932
|
|
|
function filterText($text, $bom = true, $convert_encoding = true) |
|
|
|
|
3933
|
|
|
{ |
3934
|
|
|
if (!$this->numFonts) { |
3935
|
|
|
$this->selectFont($this->defaultFont); |
3936
|
|
|
} |
3937
|
|
|
|
3938
|
|
|
if ($convert_encoding) { |
3939
|
|
|
$cf = $this->currentFont; |
3940
|
|
|
if (isset($this->fonts[$cf]) && $this->fonts[$cf]['isUnicode']) { |
3941
|
|
|
$text = $this->utf8toUtf16BE($text, $bom); |
3942
|
|
|
} else { |
3943
|
|
|
//$text = html_entity_decode($text, ENT_QUOTES); |
|
|
|
|
3944
|
|
|
$text = mb_convert_encoding($text, self::$targetEncoding, 'UTF-8'); |
3945
|
|
|
} |
3946
|
|
|
} else if ($bom) { |
3947
|
|
|
$text = $this->utf8toUtf16BE($text, $bom); |
3948
|
|
|
} |
3949
|
|
|
|
3950
|
|
|
// the chr(13) substitution fixes a bug seen in TCPDF (bug #1421290) |
3951
|
|
|
return strtr($text, array(')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r')); |
3952
|
|
|
} |
3953
|
|
|
|
3954
|
|
|
/** |
3955
|
|
|
* return array containing codepoints (UTF-8 character values) for the |
3956
|
|
|
* string passed in. |
3957
|
|
|
* |
3958
|
|
|
* based on the excellent TCPDF code by Nicola Asuni and the |
3959
|
|
|
* RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html |
3960
|
|
|
* |
3961
|
|
|
* @access private |
3962
|
|
|
* @author Orion Richardson |
3963
|
|
|
* @since January 5, 2008 |
3964
|
|
|
* |
3965
|
|
|
* @param string $text UTF-8 string to process |
3966
|
|
|
* |
3967
|
|
|
* @return array UTF-8 codepoints array for the string |
3968
|
|
|
*/ |
3969
|
|
|
function utf8toCodePointsArray(&$text) |
|
|
|
|
3970
|
|
|
{ |
3971
|
|
|
$length = mb_strlen($text, '8bit'); // http://www.php.net/manual/en/function.mb-strlen.php#77040 |
3972
|
|
|
$unicode = array(); // array containing unicode values |
3973
|
|
|
$bytes = array(); // array containing single character byte sequences |
3974
|
|
|
$numbytes = 1; // number of octets needed to represent the UTF-8 character |
3975
|
|
|
|
3976
|
|
|
for ($i = 0; $i < $length; $i++) { |
3977
|
|
|
$c = ord($text[$i]); // get one string character at time |
3978
|
|
|
if (count($bytes) === 0) { // get starting octect |
3979
|
|
|
if ($c <= 0x7F) { |
3980
|
|
|
$unicode[] = $c; // use the character "as is" because is ASCII |
3981
|
|
|
$numbytes = 1; |
3982
|
|
View Code Duplication |
} elseif (($c >> 0x05) === 0x06) { // 2 bytes character (0x06 = 110 BIN) |
|
|
|
|
3983
|
|
|
$bytes[] = ($c - 0xC0) << 0x06; |
3984
|
|
|
$numbytes = 2; |
3985
|
|
|
} elseif (($c >> 0x04) === 0x0E) { // 3 bytes character (0x0E = 1110 BIN) |
3986
|
|
|
$bytes[] = ($c - 0xE0) << 0x0C; |
3987
|
|
|
$numbytes = 3; |
3988
|
|
View Code Duplication |
} elseif (($c >> 0x03) === 0x1E) { // 4 bytes character (0x1E = 11110 BIN) |
|
|
|
|
3989
|
|
|
$bytes[] = ($c - 0xF0) << 0x12; |
3990
|
|
|
$numbytes = 4; |
3991
|
|
|
} else { |
3992
|
|
|
// use replacement character for other invalid sequences |
3993
|
|
|
$unicode[] = 0xFFFD; |
3994
|
|
|
$bytes = array(); |
3995
|
|
|
$numbytes = 1; |
3996
|
|
|
} |
3997
|
|
|
} elseif (($c >> 0x06) === 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN |
3998
|
|
|
$bytes[] = $c - 0x80; |
3999
|
|
|
if (count($bytes) === $numbytes) { |
4000
|
|
|
// compose UTF-8 bytes to a single unicode value |
4001
|
|
|
$c = $bytes[0]; |
4002
|
|
|
for ($j = 1; $j < $numbytes; $j++) { |
4003
|
|
|
$c += ($bytes[$j] << (($numbytes - $j - 1) * 0x06)); |
4004
|
|
|
} |
4005
|
|
|
if ((($c >= 0xD800) AND ($c <= 0xDFFF)) OR ($c >= 0x10FFFF)) { |
|
|
|
|
4006
|
|
|
// The definition of UTF-8 prohibits encoding character numbers between |
4007
|
|
|
// U+D800 and U+DFFF, which are reserved for use with the UTF-16 |
4008
|
|
|
// encoding form (as surrogate pairs) and do not directly represent |
4009
|
|
|
// characters. |
4010
|
|
|
$unicode[] = 0xFFFD; // use replacement character |
4011
|
|
|
} else { |
4012
|
|
|
$unicode[] = $c; // add char to array |
4013
|
|
|
} |
4014
|
|
|
// reset data for next char |
4015
|
|
|
$bytes = array(); |
4016
|
|
|
$numbytes = 1; |
4017
|
|
|
} |
4018
|
|
|
} else { |
4019
|
|
|
// use replacement character for other invalid sequences |
4020
|
|
|
$unicode[] = 0xFFFD; |
4021
|
|
|
$bytes = array(); |
4022
|
|
|
$numbytes = 1; |
4023
|
|
|
} |
4024
|
|
|
} |
4025
|
|
|
|
4026
|
|
|
return $unicode; |
4027
|
|
|
} |
4028
|
|
|
|
4029
|
|
|
/** |
4030
|
|
|
* convert UTF-8 to UTF-16 with an additional byte order marker |
4031
|
|
|
* at the front if required. |
4032
|
|
|
* |
4033
|
|
|
* based on the excellent TCPDF code by Nicola Asuni and the |
4034
|
|
|
* RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html |
4035
|
|
|
* |
4036
|
|
|
* @access private |
4037
|
|
|
* @author Orion Richardson |
4038
|
|
|
* @since January 5, 2008 |
4039
|
|
|
* |
4040
|
|
|
* @param string $text UTF-8 string to process |
4041
|
|
|
* @param boolean $bom whether to add the byte order marker |
4042
|
|
|
* |
4043
|
|
|
* @return string UTF-16 result string |
4044
|
|
|
*/ |
4045
|
|
|
function utf8toUtf16BE(&$text, $bom = true) |
|
|
|
|
4046
|
|
|
{ |
4047
|
|
|
$out = $bom ? "\xFE\xFF" : ''; |
4048
|
|
|
|
4049
|
|
|
$unicode = $this->utf8toCodePointsArray($text); |
4050
|
|
|
foreach ($unicode as $c) { |
4051
|
|
|
if ($c === 0xFFFD) { |
4052
|
|
|
$out .= "\xFF\xFD"; // replacement character |
4053
|
|
|
} elseif ($c < 0x10000) { |
4054
|
|
|
$out .= chr($c >> 0x08) . chr($c & 0xFF); |
4055
|
|
|
} else { |
4056
|
|
|
$c -= 0x10000; |
4057
|
|
|
$w1 = 0xD800 | ($c >> 0x10); |
4058
|
|
|
$w2 = 0xDC00 | ($c & 0x3FF); |
4059
|
|
|
$out .= chr($w1 >> 0x08) . chr($w1 & 0xFF) . chr($w2 >> 0x08) . chr($w2 & 0xFF); |
4060
|
|
|
} |
4061
|
|
|
} |
4062
|
|
|
|
4063
|
|
|
return $out; |
4064
|
|
|
} |
4065
|
|
|
|
4066
|
|
|
/** |
4067
|
|
|
* given a start position and information about how text is to be laid out, calculate where |
4068
|
|
|
* on the page the text will end |
4069
|
|
|
* |
4070
|
|
|
* @param $x |
4071
|
|
|
* @param $y |
4072
|
|
|
* @param $angle |
4073
|
|
|
* @param $size |
4074
|
|
|
* @param $wa |
4075
|
|
|
* @param $text |
4076
|
|
|
* @return array |
4077
|
|
|
*/ |
4078
|
|
|
private function getTextPosition($x, $y, $angle, $size, $wa, $text) |
4079
|
|
|
{ |
4080
|
|
|
// given this information return an array containing x and y for the end position as elements 0 and 1 |
4081
|
|
|
$w = $this->getTextWidth($size, $text); |
4082
|
|
|
|
4083
|
|
|
// need to adjust for the number of spaces in this text |
4084
|
|
|
$words = explode(' ', $text); |
4085
|
|
|
$nspaces = count($words) - 1; |
4086
|
|
|
$w += $wa * $nspaces; |
4087
|
|
|
$a = deg2rad((float)$angle); |
4088
|
|
|
|
4089
|
|
|
return array(cos($a) * $w + $x, -sin($a) * $w + $y); |
4090
|
|
|
} |
4091
|
|
|
|
4092
|
|
|
/** |
4093
|
|
|
* Callback method used by smallCaps |
4094
|
|
|
* |
4095
|
|
|
* @param array $matches |
4096
|
|
|
* |
4097
|
|
|
* @return string |
4098
|
|
|
*/ |
4099
|
|
|
function toUpper($matches) |
|
|
|
|
4100
|
|
|
{ |
4101
|
|
|
return mb_strtoupper($matches[0]); |
4102
|
|
|
} |
4103
|
|
|
|
4104
|
|
|
function concatMatches($matches) |
|
|
|
|
4105
|
|
|
{ |
4106
|
|
|
$str = ""; |
4107
|
|
|
foreach ($matches as $match) { |
4108
|
|
|
$str .= $match[0]; |
4109
|
|
|
} |
4110
|
|
|
|
4111
|
|
|
return $str; |
4112
|
|
|
} |
4113
|
|
|
|
4114
|
|
|
/** |
4115
|
|
|
* register text for font subsetting |
4116
|
|
|
* |
4117
|
|
|
* @param $font |
4118
|
|
|
* @param $text |
4119
|
|
|
*/ |
4120
|
|
|
function registerText($font, $text) |
|
|
|
|
4121
|
|
|
{ |
4122
|
|
|
if (!$this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) { |
4123
|
|
|
return; |
4124
|
|
|
} |
4125
|
|
|
|
4126
|
|
|
if (!isset($this->stringSubsets[$font])) { |
4127
|
|
|
$this->stringSubsets[$font] = array(); |
4128
|
|
|
} |
4129
|
|
|
|
4130
|
|
|
$this->stringSubsets[$font] = array_unique( |
4131
|
|
|
array_merge($this->stringSubsets[$font], $this->utf8toCodePointsArray($text)) |
4132
|
|
|
); |
4133
|
|
|
} |
4134
|
|
|
|
4135
|
|
|
/** |
4136
|
|
|
* add text to the document, at a specified location, size and angle on the page |
4137
|
|
|
* |
4138
|
|
|
* @param $x |
4139
|
|
|
* @param $y |
4140
|
|
|
* @param $size |
4141
|
|
|
* @param $text |
4142
|
|
|
* @param int $angle |
4143
|
|
|
* @param int $wordSpaceAdjust |
4144
|
|
|
* @param int $charSpaceAdjust |
4145
|
|
|
* @param bool $smallCaps |
4146
|
|
|
*/ |
4147
|
|
|
function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false) |
|
|
|
|
4148
|
|
|
{ |
4149
|
|
|
if (!$this->numFonts) { |
4150
|
|
|
$this->selectFont($this->defaultFont); |
4151
|
|
|
} |
4152
|
|
|
|
4153
|
|
|
$text = str_replace(array("\r", "\n"), "", $text); |
4154
|
|
|
|
4155
|
|
|
if ($smallCaps) { |
4156
|
|
|
preg_match_all("/(\P{Ll}+)/u", $text, $matches, PREG_SET_ORDER); |
4157
|
|
|
$lower = $this->concatMatches($matches); |
4158
|
|
|
d($lower); |
4159
|
|
|
|
4160
|
|
|
preg_match_all("/(\p{Ll}+)/u", $text, $matches, PREG_SET_ORDER); |
4161
|
|
|
$other = $this->concatMatches($matches); |
4162
|
|
|
d($other); |
4163
|
|
|
|
4164
|
|
|
//$text = preg_replace_callback("/\p{Ll}/u", array($this, "toUpper"), $text); |
|
|
|
|
4165
|
|
|
} |
4166
|
|
|
|
4167
|
|
|
// if there are any open callbacks, then they should be called, to show the start of the line |
4168
|
|
|
if ($this->nCallback > 0) { |
4169
|
|
|
for ($i = $this->nCallback; $i > 0; $i--) { |
4170
|
|
|
// call each function |
4171
|
|
|
$info = array( |
4172
|
|
|
'x' => $x, |
4173
|
|
|
'y' => $y, |
4174
|
|
|
'angle' => $angle, |
4175
|
|
|
'status' => 'sol', |
4176
|
|
|
'p' => $this->callback[$i]['p'], |
4177
|
|
|
'nCallback' => $this->callback[$i]['nCallback'], |
4178
|
|
|
'height' => $this->callback[$i]['height'], |
4179
|
|
|
'descender' => $this->callback[$i]['descender'] |
4180
|
|
|
); |
4181
|
|
|
|
4182
|
|
|
$func = $this->callback[$i]['f']; |
4183
|
|
|
$this->$func($info); |
4184
|
|
|
} |
4185
|
|
|
} |
4186
|
|
|
|
4187
|
|
|
if ($angle == 0) { |
4188
|
|
|
$this->addContent(sprintf("\nBT %.3F %.3F Td", $x, $y)); |
4189
|
|
|
} else { |
4190
|
|
|
$a = deg2rad((float)$angle); |
4191
|
|
|
$this->addContent( |
4192
|
|
|
sprintf("\nBT %.3F %.3F %.3F %.3F %.3F %.3F Tm", cos($a), -sin($a), sin($a), cos($a), $x, $y) |
4193
|
|
|
); |
4194
|
|
|
} |
4195
|
|
|
|
4196
|
|
|
if ($wordSpaceAdjust != 0) { |
4197
|
|
|
$this->addContent(sprintf(" %.3F Tw", $wordSpaceAdjust)); |
4198
|
|
|
} |
4199
|
|
|
|
4200
|
|
|
if ($charSpaceAdjust != 0) { |
4201
|
|
|
$this->addContent(sprintf(" %.3F Tc", $charSpaceAdjust)); |
4202
|
|
|
} |
4203
|
|
|
|
4204
|
|
|
$len = mb_strlen($text); |
4205
|
|
|
$start = 0; |
4206
|
|
|
|
4207
|
|
|
if ($start < $len) { |
4208
|
|
|
$part = $text; // OAR - Don't need this anymore, given that $start always equals zero. substr($text, $start); |
4209
|
|
|
$place_text = $this->filterText($part, false); |
4210
|
|
|
// modify unicode text so that extra word spacing is manually implemented (bug #) |
4211
|
|
|
$cf = $this->currentFont; |
4212
|
|
|
if ($this->fonts[$cf]['isUnicode'] && $wordSpaceAdjust != 0) { |
4213
|
|
|
$space_scale = 1000 / $size; |
4214
|
|
|
$place_text = str_replace("\x00\x20", "\x00\x20)\x00\x20" . (-round($space_scale * $wordSpaceAdjust)) . "\x00\x20(", $place_text); |
4215
|
|
|
} |
4216
|
|
|
$this->addContent(" /F$this->currentFontNum " . sprintf('%.1F Tf ', $size)); |
4217
|
|
|
$this->addContent(" [($place_text)] TJ"); |
4218
|
|
|
} |
4219
|
|
|
|
4220
|
|
|
if ($wordSpaceAdjust != 0) { |
4221
|
|
|
$this->addContent(sprintf(" %.3F Tw", 0)); |
4222
|
|
|
} |
4223
|
|
|
|
4224
|
|
|
if ($charSpaceAdjust != 0) { |
4225
|
|
|
$this->addContent(sprintf(" %.3F Tc", 0)); |
4226
|
|
|
} |
4227
|
|
|
|
4228
|
|
|
$this->addContent(' ET'); |
4229
|
|
|
|
4230
|
|
|
// if there are any open callbacks, then they should be called, to show the end of the line |
4231
|
|
|
if ($this->nCallback > 0) { |
4232
|
|
|
for ($i = $this->nCallback; $i > 0; $i--) { |
4233
|
|
|
// call each function |
4234
|
|
|
$tmp = $this->getTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, $text); |
4235
|
|
|
$info = array( |
4236
|
|
|
'x' => $tmp[0], |
4237
|
|
|
'y' => $tmp[1], |
4238
|
|
|
'angle' => $angle, |
4239
|
|
|
'status' => 'eol', |
4240
|
|
|
'p' => $this->callback[$i]['p'], |
4241
|
|
|
'nCallback' => $this->callback[$i]['nCallback'], |
4242
|
|
|
'height' => $this->callback[$i]['height'], |
4243
|
|
|
'descender' => $this->callback[$i]['descender'] |
4244
|
|
|
); |
4245
|
|
|
$func = $this->callback[$i]['f']; |
4246
|
|
|
$this->$func($info); |
4247
|
|
|
} |
4248
|
|
|
} |
4249
|
|
|
} |
4250
|
|
|
|
4251
|
|
|
/** |
4252
|
|
|
* calculate how wide a given text string will be on a page, at a given size. |
4253
|
|
|
* this can be called externally, but is also used by the other class functions |
4254
|
|
|
* |
4255
|
|
|
* @param $size |
4256
|
|
|
* @param $text |
4257
|
|
|
* @param int $word_spacing |
4258
|
|
|
* @param int $char_spacing |
4259
|
|
|
* @return float|int |
4260
|
|
|
*/ |
4261
|
|
|
function getTextWidth($size, $text, $word_spacing = 0, $char_spacing = 0) |
|
|
|
|
4262
|
|
|
{ |
4263
|
|
|
static $ord_cache = array(); |
4264
|
|
|
|
4265
|
|
|
// this function should not change any of the settings, though it will need to |
4266
|
|
|
// track any directives which change during calculation, so copy them at the start |
4267
|
|
|
// and put them back at the end. |
4268
|
|
|
$store_currentTextState = $this->currentTextState; |
4269
|
|
|
|
4270
|
|
|
if (!$this->numFonts) { |
4271
|
|
|
$this->selectFont($this->defaultFont); |
4272
|
|
|
} |
4273
|
|
|
|
4274
|
|
|
$text = str_replace(array("\r", "\n"), "", $text); |
4275
|
|
|
|
4276
|
|
|
// converts a number or a float to a string so it can get the width |
4277
|
|
|
$text = "$text"; |
4278
|
|
|
|
4279
|
|
|
// hmm, this is where it all starts to get tricky - use the font information to |
4280
|
|
|
// calculate the width of each character, add them up and convert to user units |
4281
|
|
|
$w = 0; |
4282
|
|
|
$cf = $this->currentFont; |
4283
|
|
|
$current_font = $this->fonts[$cf]; |
4284
|
|
|
$space_scale = 1000 / ($size > 0 ? $size : 1); |
4285
|
|
|
$n_spaces = 0; |
4286
|
|
|
|
4287
|
|
|
if ($current_font['isUnicode']) { |
4288
|
|
|
// for Unicode, use the code points array to calculate width rather |
4289
|
|
|
// than just the string itself |
4290
|
|
|
$unicode = $this->utf8toCodePointsArray($text); |
4291
|
|
|
|
4292
|
|
|
foreach ($unicode as $char) { |
4293
|
|
|
// check if we have to replace character |
4294
|
|
|
if (isset($current_font['differences'][$char])) { |
4295
|
|
|
$char = $current_font['differences'][$char]; |
4296
|
|
|
} |
4297
|
|
|
|
4298
|
|
View Code Duplication |
if (isset($current_font['C'][$char])) { |
|
|
|
|
4299
|
|
|
$char_width = $current_font['C'][$char]; |
4300
|
|
|
|
4301
|
|
|
// add the character width |
4302
|
|
|
$w += $char_width; |
4303
|
|
|
|
4304
|
|
|
// add additional padding for space |
4305
|
|
|
if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space |
4306
|
|
|
$w += $word_spacing * $space_scale; |
4307
|
|
|
$n_spaces++; |
4308
|
|
|
} |
4309
|
|
|
} |
4310
|
|
|
} |
4311
|
|
|
|
4312
|
|
|
// add additional char spacing |
4313
|
|
|
if ($char_spacing != 0) { |
4314
|
|
|
$w += $char_spacing * $space_scale * (count($unicode) + $n_spaces); |
4315
|
|
|
} |
4316
|
|
|
|
4317
|
|
|
} else { |
4318
|
|
|
// If CPDF is in Unicode mode but the current font does not support Unicode we need to convert the character set to Windows-1252 |
4319
|
|
|
if ($this->isUnicode) { |
4320
|
|
|
$text = mb_convert_encoding($text, 'Windows-1252', 'UTF-8'); |
4321
|
|
|
} |
4322
|
|
|
|
4323
|
|
|
$len = mb_strlen($text, 'Windows-1252'); |
4324
|
|
|
|
4325
|
|
|
for ($i = 0; $i < $len; $i++) { |
4326
|
|
|
$c = $text[$i]; |
4327
|
|
|
$char = isset($ord_cache[$c]) ? $ord_cache[$c] : ($ord_cache[$c] = ord($c)); |
4328
|
|
|
|
4329
|
|
|
// check if we have to replace character |
4330
|
|
|
if (isset($current_font['differences'][$char])) { |
4331
|
|
|
$char = $current_font['differences'][$char]; |
4332
|
|
|
} |
4333
|
|
|
|
4334
|
|
View Code Duplication |
if (isset($current_font['C'][$char])) { |
|
|
|
|
4335
|
|
|
$char_width = $current_font['C'][$char]; |
4336
|
|
|
|
4337
|
|
|
// add the character width |
4338
|
|
|
$w += $char_width; |
4339
|
|
|
|
4340
|
|
|
// add additional padding for space |
4341
|
|
|
if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space |
4342
|
|
|
$w += $word_spacing * $space_scale; |
4343
|
|
|
$n_spaces++; |
4344
|
|
|
} |
4345
|
|
|
} |
4346
|
|
|
} |
4347
|
|
|
|
4348
|
|
|
// add additional char spacing |
4349
|
|
|
if ($char_spacing != 0) { |
4350
|
|
|
$w += $char_spacing * $space_scale * ($len + $n_spaces); |
4351
|
|
|
} |
4352
|
|
|
} |
4353
|
|
|
|
4354
|
|
|
$this->currentTextState = $store_currentTextState; |
4355
|
|
|
$this->setCurrentFont(); |
4356
|
|
|
|
4357
|
|
|
return $w * $size / 1000; |
4358
|
|
|
} |
4359
|
|
|
|
4360
|
|
|
/** |
4361
|
|
|
* this will be called at a new page to return the state to what it was on the |
4362
|
|
|
* end of the previous page, before the stack was closed down |
4363
|
|
|
* This is to get around not being able to have open 'q' across pages |
4364
|
|
|
* |
4365
|
|
|
* @param int $pageEnd |
4366
|
|
|
*/ |
4367
|
|
|
function saveState($pageEnd = 0) |
|
|
|
|
4368
|
|
|
{ |
4369
|
|
|
if ($pageEnd) { |
4370
|
|
|
// this will be called at a new page to return the state to what it was on the |
4371
|
|
|
// end of the previous page, before the stack was closed down |
4372
|
|
|
// This is to get around not being able to have open 'q' across pages |
4373
|
|
|
$opt = $this->stateStack[$pageEnd]; |
4374
|
|
|
// ok to use this as stack starts numbering at 1 |
4375
|
|
|
$this->setColor($opt['col'], true); |
4376
|
|
|
$this->setStrokeColor($opt['str'], true); |
4377
|
|
|
$this->addContent("\n" . $opt['lin']); |
4378
|
|
|
// $this->currentLineStyle = $opt['lin']; |
|
|
|
|
4379
|
|
|
} else { |
4380
|
|
|
$this->nStateStack++; |
4381
|
|
|
$this->stateStack[$this->nStateStack] = array( |
4382
|
|
|
'col' => $this->currentColor, |
4383
|
|
|
'str' => $this->currentStrokeColor, |
4384
|
|
|
'lin' => $this->currentLineStyle |
4385
|
|
|
); |
4386
|
|
|
} |
4387
|
|
|
|
4388
|
|
|
$this->save(); |
4389
|
|
|
} |
4390
|
|
|
|
4391
|
|
|
/** |
4392
|
|
|
* restore a previously saved state |
4393
|
|
|
* |
4394
|
|
|
* @param int $pageEnd |
4395
|
|
|
*/ |
4396
|
|
|
function restoreState($pageEnd = 0) |
|
|
|
|
4397
|
|
|
{ |
4398
|
|
|
if (!$pageEnd) { |
4399
|
|
|
$n = $this->nStateStack; |
4400
|
|
|
$this->currentColor = $this->stateStack[$n]['col']; |
4401
|
|
|
$this->currentStrokeColor = $this->stateStack[$n]['str']; |
4402
|
|
|
$this->addContent("\n" . $this->stateStack[$n]['lin']); |
4403
|
|
|
$this->currentLineStyle = $this->stateStack[$n]['lin']; |
4404
|
|
|
$this->stateStack[$n] = null; |
4405
|
|
|
unset($this->stateStack[$n]); |
4406
|
|
|
$this->nStateStack--; |
4407
|
|
|
} |
4408
|
|
|
|
4409
|
|
|
$this->restore(); |
4410
|
|
|
} |
4411
|
|
|
|
4412
|
|
|
/** |
4413
|
|
|
* make a loose object, the output will go into this object, until it is closed, then will revert to |
4414
|
|
|
* the current one. |
4415
|
|
|
* this object will not appear until it is included within a page. |
4416
|
|
|
* the function will return the object number |
4417
|
|
|
* |
4418
|
|
|
* @return int |
4419
|
|
|
*/ |
4420
|
|
|
function openObject() |
|
|
|
|
4421
|
|
|
{ |
4422
|
|
|
$this->nStack++; |
4423
|
|
|
$this->stack[$this->nStack] = array('c' => $this->currentContents, 'p' => $this->currentPage); |
4424
|
|
|
// add a new object of the content type, to hold the data flow |
4425
|
|
|
$this->numObj++; |
4426
|
|
|
$this->o_contents($this->numObj, 'new'); |
4427
|
|
|
$this->currentContents = $this->numObj; |
4428
|
|
|
$this->looseObjects[$this->numObj] = 1; |
4429
|
|
|
|
4430
|
|
|
return $this->numObj; |
4431
|
|
|
} |
4432
|
|
|
|
4433
|
|
|
/** |
4434
|
|
|
* open an existing object for editing |
4435
|
|
|
* |
4436
|
|
|
* @param $id |
4437
|
|
|
*/ |
4438
|
|
|
function reopenObject($id) |
|
|
|
|
4439
|
|
|
{ |
4440
|
|
|
$this->nStack++; |
4441
|
|
|
$this->stack[$this->nStack] = array('c' => $this->currentContents, 'p' => $this->currentPage); |
4442
|
|
|
$this->currentContents = $id; |
4443
|
|
|
|
4444
|
|
|
// also if this object is the primary contents for a page, then set the current page to its parent |
4445
|
|
|
if (isset($this->objects[$id]['onPage'])) { |
4446
|
|
|
$this->currentPage = $this->objects[$id]['onPage']; |
4447
|
|
|
} |
4448
|
|
|
} |
4449
|
|
|
|
4450
|
|
|
/** |
4451
|
|
|
* close an object |
4452
|
|
|
*/ |
4453
|
|
|
function closeObject() |
|
|
|
|
4454
|
|
|
{ |
4455
|
|
|
// close the object, as long as there was one open in the first place, which will be indicated by |
4456
|
|
|
// an objectId on the stack. |
4457
|
|
|
if ($this->nStack > 0) { |
4458
|
|
|
$this->currentContents = $this->stack[$this->nStack]['c']; |
4459
|
|
|
$this->currentPage = $this->stack[$this->nStack]['p']; |
4460
|
|
|
$this->nStack--; |
4461
|
|
|
// easier to probably not worry about removing the old entries, they will be overwritten |
4462
|
|
|
// if there are new ones. |
4463
|
|
|
} |
4464
|
|
|
} |
4465
|
|
|
|
4466
|
|
|
/** |
4467
|
|
|
* stop an object from appearing on pages from this point on |
4468
|
|
|
* |
4469
|
|
|
* @param $id |
4470
|
|
|
*/ |
4471
|
|
|
function stopObject($id) |
|
|
|
|
4472
|
|
|
{ |
4473
|
|
|
// if an object has been appearing on pages up to now, then stop it, this page will |
4474
|
|
|
// be the last one that could contain it. |
4475
|
|
|
if (isset($this->addLooseObjects[$id])) { |
4476
|
|
|
$this->addLooseObjects[$id] = ''; |
4477
|
|
|
} |
4478
|
|
|
} |
4479
|
|
|
|
4480
|
|
|
/** |
4481
|
|
|
* after an object has been created, it wil only show if it has been added, using this function. |
4482
|
|
|
* |
4483
|
|
|
* @param $id |
4484
|
|
|
* @param string $options |
4485
|
|
|
*/ |
4486
|
|
|
function addObject($id, $options = 'add') |
|
|
|
|
4487
|
|
|
{ |
4488
|
|
|
// add the specified object to the page |
4489
|
|
|
if (isset($this->looseObjects[$id]) && $this->currentContents != $id) { |
4490
|
|
|
// then it is a valid object, and it is not being added to itself |
4491
|
|
|
switch ($options) { |
4492
|
|
|
case 'all': |
|
|
|
|
4493
|
|
|
// then this object is to be added to this page (done in the next block) and |
4494
|
|
|
// all future new pages. |
4495
|
|
|
$this->addLooseObjects[$id] = 'all'; |
4496
|
|
|
|
4497
|
|
|
case 'add': |
4498
|
|
|
if (isset($this->objects[$this->currentContents]['onPage'])) { |
4499
|
|
|
// then the destination contents is the primary for the page |
4500
|
|
|
// (though this object is actually added to that page) |
4501
|
|
|
$this->o_page($this->objects[$this->currentContents]['onPage'], 'content', $id); |
4502
|
|
|
} |
4503
|
|
|
break; |
4504
|
|
|
|
4505
|
|
View Code Duplication |
case 'even': |
|
|
|
|
4506
|
|
|
$this->addLooseObjects[$id] = 'even'; |
4507
|
|
|
$pageObjectId = $this->objects[$this->currentContents]['onPage']; |
4508
|
|
|
if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 0) { |
4509
|
|
|
$this->addObject($id); |
4510
|
|
|
// hacky huh :) |
4511
|
|
|
} |
4512
|
|
|
break; |
4513
|
|
|
|
4514
|
|
View Code Duplication |
case 'odd': |
|
|
|
|
4515
|
|
|
$this->addLooseObjects[$id] = 'odd'; |
4516
|
|
|
$pageObjectId = $this->objects[$this->currentContents]['onPage']; |
4517
|
|
|
if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 1) { |
4518
|
|
|
$this->addObject($id); |
4519
|
|
|
// hacky huh :) |
4520
|
|
|
} |
4521
|
|
|
break; |
4522
|
|
|
|
4523
|
|
|
case 'next': |
4524
|
|
|
$this->addLooseObjects[$id] = 'all'; |
4525
|
|
|
break; |
4526
|
|
|
|
4527
|
|
|
case 'nexteven': |
4528
|
|
|
$this->addLooseObjects[$id] = 'even'; |
4529
|
|
|
break; |
4530
|
|
|
|
4531
|
|
|
case 'nextodd': |
4532
|
|
|
$this->addLooseObjects[$id] = 'odd'; |
4533
|
|
|
break; |
4534
|
|
|
} |
4535
|
|
|
} |
4536
|
|
|
} |
4537
|
|
|
|
4538
|
|
|
/** |
4539
|
|
|
* return a storable representation of a specific object |
4540
|
|
|
* |
4541
|
|
|
* @param $id |
4542
|
|
|
* @return string|null |
4543
|
|
|
*/ |
4544
|
|
|
function serializeObject($id) |
|
|
|
|
4545
|
|
|
{ |
4546
|
|
|
if (array_key_exists($id, $this->objects)) { |
4547
|
|
|
return serialize($this->objects[$id]); |
4548
|
|
|
} |
4549
|
|
|
|
4550
|
|
|
return null; |
4551
|
|
|
} |
4552
|
|
|
|
4553
|
|
|
/** |
4554
|
|
|
* restore an object from its stored representation. returns its new object id. |
4555
|
|
|
* |
4556
|
|
|
* @param $obj |
4557
|
|
|
* @return int |
4558
|
|
|
*/ |
4559
|
|
|
function restoreSerializedObject($obj) |
|
|
|
|
4560
|
|
|
{ |
4561
|
|
|
$obj_id = $this->openObject(); |
4562
|
|
|
$this->objects[$obj_id] = unserialize($obj); |
4563
|
|
|
$this->closeObject(); |
4564
|
|
|
|
4565
|
|
|
return $obj_id; |
4566
|
|
|
} |
4567
|
|
|
|
4568
|
|
|
/** |
4569
|
|
|
* add content to the documents info object |
4570
|
|
|
* |
4571
|
|
|
* @param $label |
4572
|
|
|
* @param int $value |
4573
|
|
|
*/ |
4574
|
|
|
function addInfo($label, $value = 0) |
|
|
|
|
4575
|
|
|
{ |
4576
|
|
|
// this will only work if the label is one of the valid ones. |
4577
|
|
|
// modify this so that arrays can be passed as well. |
4578
|
|
|
// if $label is an array then assume that it is key => value pairs |
4579
|
|
|
// else assume that they are both scalar, anything else will probably error |
4580
|
|
|
if (is_array($label)) { |
4581
|
|
|
foreach ($label as $l => $v) { |
4582
|
|
|
$this->o_info($this->infoObject, $l, $v); |
4583
|
|
|
} |
4584
|
|
|
} else { |
4585
|
|
|
$this->o_info($this->infoObject, $label, $value); |
4586
|
|
|
} |
4587
|
|
|
} |
4588
|
|
|
|
4589
|
|
|
/** |
4590
|
|
|
* set the viewer preferences of the document, it is up to the browser to obey these. |
4591
|
|
|
* |
4592
|
|
|
* @param $label |
4593
|
|
|
* @param int $value |
4594
|
|
|
*/ |
4595
|
|
|
function setPreferences($label, $value = 0) |
|
|
|
|
4596
|
|
|
{ |
4597
|
|
|
// this will only work if the label is one of the valid ones. |
4598
|
|
|
if (is_array($label)) { |
4599
|
|
|
foreach ($label as $l => $v) { |
4600
|
|
|
$this->o_catalog($this->catalogId, 'viewerPreferences', array($l => $v)); |
4601
|
|
|
} |
4602
|
|
|
} else { |
4603
|
|
|
$this->o_catalog($this->catalogId, 'viewerPreferences', array($label => $value)); |
4604
|
|
|
} |
4605
|
|
|
} |
4606
|
|
|
|
4607
|
|
|
/** |
4608
|
|
|
* extract an integer from a position in a byte stream |
4609
|
|
|
* |
4610
|
|
|
* @param $data |
4611
|
|
|
* @param $pos |
4612
|
|
|
* @param $num |
4613
|
|
|
* @return int |
4614
|
|
|
*/ |
4615
|
|
|
private function getBytes(&$data, $pos, $num) |
4616
|
|
|
{ |
4617
|
|
|
// return the integer represented by $num bytes from $pos within $data |
4618
|
|
|
$ret = 0; |
4619
|
|
|
for ($i = 0; $i < $num; $i++) { |
4620
|
|
|
$ret *= 256; |
4621
|
|
|
$ret += ord($data[$pos + $i]); |
4622
|
|
|
} |
4623
|
|
|
|
4624
|
|
|
return $ret; |
4625
|
|
|
} |
4626
|
|
|
|
4627
|
|
|
/** |
4628
|
|
|
* Check if image already added to pdf image directory. |
4629
|
|
|
* If yes, need not to create again (pass empty data) |
4630
|
|
|
* |
4631
|
|
|
* @param $imgname |
4632
|
|
|
* @return bool |
4633
|
|
|
*/ |
4634
|
|
|
function image_iscached($imgname) |
|
|
|
|
4635
|
|
|
{ |
4636
|
|
|
return isset($this->imagelist[$imgname]); |
4637
|
|
|
} |
4638
|
|
|
|
4639
|
|
|
/** |
4640
|
|
|
* add a PNG image into the document, from a GD object |
4641
|
|
|
* this should work with remote files |
4642
|
|
|
* |
4643
|
|
|
* @param string $file The PNG file |
4644
|
|
|
* @param float $x X position |
4645
|
|
|
* @param float $y Y position |
4646
|
|
|
* @param float $w Width |
4647
|
|
|
* @param float $h Height |
4648
|
|
|
* @param resource $img A GD resource |
4649
|
|
|
* @param bool $is_mask true if the image is a mask |
4650
|
|
|
* @param bool $mask true if the image is masked |
4651
|
|
|
* @throws Exception |
4652
|
|
|
*/ |
4653
|
|
|
function addImagePng($file, $x, $y, $w = 0.0, $h = 0.0, &$img, $is_mask = false, $mask = null) |
|
|
|
|
4654
|
|
|
{ |
4655
|
|
|
if (!function_exists("imagepng")) { |
4656
|
|
|
throw new \Exception("The PHP GD extension is required, but is not installed."); |
4657
|
|
|
} |
4658
|
|
|
|
4659
|
|
|
//if already cached, need not to read again |
4660
|
|
|
if (isset($this->imagelist[$file])) { |
4661
|
|
|
$data = null; |
4662
|
|
|
} else { |
4663
|
|
|
// Example for transparency handling on new image. Retain for current image |
4664
|
|
|
// $tIndex = imagecolortransparent($img); |
|
|
|
|
4665
|
|
|
// if ($tIndex > 0) { |
|
|
|
|
4666
|
|
|
// $tColor = imagecolorsforindex($img, $tIndex); |
|
|
|
|
4667
|
|
|
// $new_tIndex = imagecolorallocate($new_img, $tColor['red'], $tColor['green'], $tColor['blue']); |
|
|
|
|
4668
|
|
|
// imagefill($new_img, 0, 0, $new_tIndex); |
|
|
|
|
4669
|
|
|
// imagecolortransparent($new_img, $new_tIndex); |
|
|
|
|
4670
|
|
|
// } |
4671
|
|
|
// blending mode (literal/blending) on drawing into current image. not relevant when not saved or not drawn |
4672
|
|
|
//imagealphablending($img, true); |
|
|
|
|
4673
|
|
|
|
4674
|
|
|
//default, but explicitely set to ensure pdf compatibility |
4675
|
|
|
imagesavealpha($img, false/*!$is_mask && !$mask*/); |
|
|
|
|
4676
|
|
|
|
4677
|
|
|
$error = 0; |
4678
|
|
|
//DEBUG_IMG_TEMP |
4679
|
|
|
//debugpng |
4680
|
|
|
if (defined("DEBUGPNG") && DEBUGPNG) { |
4681
|
|
|
print '[addImagePng ' . $file . ']'; |
4682
|
|
|
} |
4683
|
|
|
|
4684
|
|
|
ob_start(); |
4685
|
|
|
@imagepng($img); |
|
|
|
|
4686
|
|
|
$data = ob_get_clean(); |
4687
|
|
|
|
4688
|
|
View Code Duplication |
if ($data == '') { |
|
|
|
|
4689
|
|
|
$error = 1; |
4690
|
|
|
$errormsg = 'trouble writing file from GD'; |
4691
|
|
|
//DEBUG_IMG_TEMP |
4692
|
|
|
//debugpng |
4693
|
|
|
if (defined("DEBUGPNG") && DEBUGPNG) { |
4694
|
|
|
print 'trouble writing file from GD'; |
4695
|
|
|
} |
4696
|
|
|
} |
4697
|
|
|
|
4698
|
|
|
if ($error) { |
4699
|
|
|
$this->addMessage('PNG error - (' . $file . ') ' . $errormsg); |
|
|
|
|
4700
|
|
|
|
4701
|
|
|
return; |
4702
|
|
|
} |
4703
|
|
|
} //End isset($this->imagelist[$file]) (png Duplicate removal) |
|
|
|
|
4704
|
|
|
|
4705
|
|
|
$this->addPngFromBuf($file, $x, $y, $w, $h, $data, $is_mask, $mask); |
|
|
|
|
4706
|
|
|
} |
4707
|
|
|
|
4708
|
|
|
/** |
4709
|
|
|
* @param $file |
4710
|
|
|
* @param $x |
4711
|
|
|
* @param $y |
4712
|
|
|
* @param $w |
4713
|
|
|
* @param $h |
4714
|
|
|
* @param $byte |
4715
|
|
|
*/ |
4716
|
|
|
protected function addImagePngAlpha($file, $x, $y, $w, $h, $byte) |
4717
|
|
|
{ |
4718
|
|
|
// generate images |
4719
|
|
|
$img = imagecreatefrompng($file); |
4720
|
|
|
|
4721
|
|
|
if ($img === false) { |
4722
|
|
|
return; |
4723
|
|
|
} |
4724
|
|
|
|
4725
|
|
|
// FIXME The pixel transformation doesn't work well with 8bit PNGs |
4726
|
|
|
$eight_bit = ($byte & 4) !== 4; |
4727
|
|
|
|
4728
|
|
|
$wpx = imagesx($img); |
4729
|
|
|
$hpx = imagesy($img); |
4730
|
|
|
|
4731
|
|
|
imagesavealpha($img, false); |
4732
|
|
|
|
4733
|
|
|
// create temp alpha file |
4734
|
|
|
$tempfile_alpha = tempnam($this->tmp, "cpdf_img_"); |
4735
|
|
|
@unlink($tempfile_alpha); |
|
|
|
|
4736
|
|
|
$tempfile_alpha = "$tempfile_alpha.png"; |
4737
|
|
|
|
4738
|
|
|
// create temp plain file |
4739
|
|
|
$tempfile_plain = tempnam($this->tmp, "cpdf_img_"); |
4740
|
|
|
@unlink($tempfile_plain); |
|
|
|
|
4741
|
|
|
$tempfile_plain = "$tempfile_plain.png"; |
4742
|
|
|
|
4743
|
|
|
$imgalpha = imagecreate($wpx, $hpx); |
4744
|
|
|
imagesavealpha($imgalpha, false); |
4745
|
|
|
|
4746
|
|
|
// generate gray scale palette (0 -> 255) |
4747
|
|
|
for ($c = 0; $c < 256; ++$c) { |
4748
|
|
|
imagecolorallocate($imgalpha, $c, $c, $c); |
4749
|
|
|
} |
4750
|
|
|
|
4751
|
|
|
// Use PECL gmagick + Graphics Magic to process transparent PNG images |
4752
|
|
|
if (extension_loaded("gmagick")) { |
4753
|
|
|
$gmagick = new \Gmagick($file); |
4754
|
|
|
$gmagick->setimageformat('png'); |
4755
|
|
|
|
4756
|
|
|
// Get opacity channel (negative of alpha channel) |
4757
|
|
|
$alpha_channel_neg = clone $gmagick; |
4758
|
|
|
$alpha_channel_neg->separateimagechannel(\Gmagick::CHANNEL_OPACITY); |
4759
|
|
|
|
4760
|
|
|
// Negate opacity channel |
4761
|
|
|
$alpha_channel = new \Gmagick(); |
4762
|
|
|
$alpha_channel->newimage($wpx, $hpx, "#FFFFFF", "png"); |
4763
|
|
|
$alpha_channel->compositeimage($alpha_channel_neg, \Gmagick::COMPOSITE_DIFFERENCE, 0, 0); |
4764
|
|
|
$alpha_channel->separateimagechannel(\Gmagick::CHANNEL_RED); |
4765
|
|
|
$alpha_channel->writeimage($tempfile_alpha); |
4766
|
|
|
|
4767
|
|
|
// Cast to 8bit+palette |
4768
|
|
|
$imgalpha_ = imagecreatefrompng($tempfile_alpha); |
4769
|
|
|
imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx); |
4770
|
|
|
imagedestroy($imgalpha_); |
4771
|
|
|
imagepng($imgalpha, $tempfile_alpha); |
4772
|
|
|
|
4773
|
|
|
// Make opaque image |
4774
|
|
|
$color_channels = new \Gmagick(); |
4775
|
|
|
$color_channels->newimage($wpx, $hpx, "#FFFFFF", "png"); |
4776
|
|
|
$color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYRED, 0, 0); |
4777
|
|
|
$color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYGREEN, 0, 0); |
4778
|
|
|
$color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYBLUE, 0, 0); |
4779
|
|
|
$color_channels->writeimage($tempfile_plain); |
4780
|
|
|
|
4781
|
|
|
$imgplain = imagecreatefrompng($tempfile_plain); |
4782
|
|
|
} |
4783
|
|
|
// Use PECL imagick + ImageMagic to process transparent PNG images |
4784
|
|
|
elseif (extension_loaded("imagick")) { |
4785
|
|
|
// Native cloning was added to pecl-imagick in svn commit 263814 |
4786
|
|
|
// the first version containing it was 3.0.1RC1 |
4787
|
|
|
static $imagickClonable = null; |
4788
|
|
|
if ($imagickClonable === null) { |
4789
|
|
|
$imagickClonable = version_compare(Imagick::IMAGICK_EXTVER, '3.0.1rc1') > 0; |
4790
|
|
|
} |
4791
|
|
|
|
4792
|
|
|
$imagick = new \Imagick($file); |
4793
|
|
|
$imagick->setFormat('png'); |
4794
|
|
|
|
4795
|
|
|
// Get opacity channel (negative of alpha channel) |
4796
|
|
|
$alpha_channel = $imagickClonable ? clone $imagick : $imagick->clone(); |
4797
|
|
|
$alpha_channel->separateImageChannel(\Imagick::CHANNEL_ALPHA); |
4798
|
|
|
$alpha_channel->negateImage(true); |
4799
|
|
|
$alpha_channel->writeImage($tempfile_alpha); |
4800
|
|
|
|
4801
|
|
|
// Cast to 8bit+palette |
4802
|
|
|
$imgalpha_ = imagecreatefrompng($tempfile_alpha); |
4803
|
|
|
imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx); |
4804
|
|
|
imagedestroy($imgalpha_); |
4805
|
|
|
imagepng($imgalpha, $tempfile_alpha); |
4806
|
|
|
|
4807
|
|
|
// Make opaque image |
4808
|
|
|
$color_channels = new \Imagick(); |
4809
|
|
|
$color_channels->newImage($wpx, $hpx, "#FFFFFF", "png"); |
4810
|
|
|
$color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYRED, 0, 0); |
4811
|
|
|
$color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYGREEN, 0, 0); |
4812
|
|
|
$color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYBLUE, 0, 0); |
4813
|
|
|
$color_channels->writeImage($tempfile_plain); |
4814
|
|
|
|
4815
|
|
|
$imgplain = imagecreatefrompng($tempfile_plain); |
4816
|
|
|
} else { |
4817
|
|
|
// allocated colors cache |
4818
|
|
|
$allocated_colors = array(); |
4819
|
|
|
|
4820
|
|
|
// extract alpha channel |
4821
|
|
|
for ($xpx = 0; $xpx < $wpx; ++$xpx) { |
4822
|
|
|
for ($ypx = 0; $ypx < $hpx; ++$ypx) { |
4823
|
|
|
$color = imagecolorat($img, $xpx, $ypx); |
4824
|
|
|
$col = imagecolorsforindex($img, $color); |
4825
|
|
|
$alpha = $col['alpha']; |
4826
|
|
|
|
4827
|
|
|
if ($eight_bit) { |
4828
|
|
|
// with gamma correction |
4829
|
|
|
$gammacorr = 2.2; |
4830
|
|
|
$pixel = pow((((127 - $alpha) * 255 / 127) / 255), $gammacorr) * 255; |
4831
|
|
|
} else { |
4832
|
|
|
// without gamma correction |
4833
|
|
|
$pixel = (127 - $alpha) * 2; |
4834
|
|
|
|
4835
|
|
|
$key = $col['red'] . $col['green'] . $col['blue']; |
4836
|
|
|
|
4837
|
|
|
if (!isset($allocated_colors[$key])) { |
4838
|
|
|
$pixel_img = imagecolorallocate($img, $col['red'], $col['green'], $col['blue']); |
4839
|
|
|
$allocated_colors[$key] = $pixel_img; |
4840
|
|
|
} else { |
4841
|
|
|
$pixel_img = $allocated_colors[$key]; |
4842
|
|
|
} |
4843
|
|
|
|
4844
|
|
|
imagesetpixel($img, $xpx, $ypx, $pixel_img); |
4845
|
|
|
} |
4846
|
|
|
|
4847
|
|
|
imagesetpixel($imgalpha, $xpx, $ypx, $pixel); |
4848
|
|
|
} |
4849
|
|
|
} |
4850
|
|
|
|
4851
|
|
|
// extract image without alpha channel |
4852
|
|
|
$imgplain = imagecreatetruecolor($wpx, $hpx); |
4853
|
|
|
imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx); |
4854
|
|
|
imagedestroy($img); |
4855
|
|
|
|
4856
|
|
|
imagepng($imgalpha, $tempfile_alpha); |
4857
|
|
|
imagepng($imgplain, $tempfile_plain); |
4858
|
|
|
} |
4859
|
|
|
|
4860
|
|
|
// embed mask image |
4861
|
|
|
$this->addImagePng($tempfile_alpha, $x, $y, $w, $h, $imgalpha, true); |
4862
|
|
|
imagedestroy($imgalpha); |
4863
|
|
|
|
4864
|
|
|
// embed image, masked with previously embedded mask |
4865
|
|
|
$this->addImagePng($tempfile_plain, $x, $y, $w, $h, $imgplain, false, true); |
4866
|
|
|
imagedestroy($imgplain); |
4867
|
|
|
|
4868
|
|
|
// remove temp files |
4869
|
|
|
unlink($tempfile_alpha); |
4870
|
|
|
unlink($tempfile_plain); |
4871
|
|
|
} |
4872
|
|
|
|
4873
|
|
|
/** |
4874
|
|
|
* add a PNG image into the document, from a file |
4875
|
|
|
* this should work with remote files |
4876
|
|
|
* |
4877
|
|
|
* @param $file |
4878
|
|
|
* @param $x |
4879
|
|
|
* @param $y |
4880
|
|
|
* @param int $w |
4881
|
|
|
* @param int $h |
4882
|
|
|
* @throws Exception |
4883
|
|
|
*/ |
4884
|
|
|
function addPngFromFile($file, $x, $y, $w = 0, $h = 0) |
|
|
|
|
4885
|
|
|
{ |
4886
|
|
|
if (!function_exists("imagecreatefrompng")) { |
4887
|
|
|
throw new \Exception("The PHP GD extension is required, but is not installed."); |
4888
|
|
|
} |
4889
|
|
|
|
4890
|
|
|
//if already cached, need not to read again |
4891
|
|
|
if (isset($this->imagelist[$file])) { |
4892
|
|
|
$img = null; |
4893
|
|
|
} else { |
4894
|
|
|
$info = file_get_contents($file, false, null, 24, 5); |
4895
|
|
|
$meta = unpack("CbitDepth/CcolorType/CcompressionMethod/CfilterMethod/CinterlaceMethod", $info); |
4896
|
|
|
$bit_depth = $meta["bitDepth"]; |
4897
|
|
|
$color_type = $meta["colorType"]; |
4898
|
|
|
|
4899
|
|
|
// http://www.w3.org/TR/PNG/#11IHDR |
4900
|
|
|
// 3 => indexed |
4901
|
|
|
// 4 => greyscale with alpha |
4902
|
|
|
// 6 => fullcolor with alpha |
4903
|
|
|
$is_alpha = in_array($color_type, array(4, 6)) || ($color_type == 3 && $bit_depth != 4); |
4904
|
|
|
|
4905
|
|
|
if ($is_alpha) { // exclude grayscale alpha |
4906
|
|
|
$this->addImagePngAlpha($file, $x, $y, $w, $h, $color_type); |
4907
|
|
|
return; |
4908
|
|
|
} |
4909
|
|
|
|
4910
|
|
|
//png files typically contain an alpha channel. |
4911
|
|
|
//pdf file format or class.pdf does not support alpha blending. |
4912
|
|
|
//on alpha blended images, more transparent areas have a color near black. |
4913
|
|
|
//This appears in the result on not storing the alpha channel. |
4914
|
|
|
//Correct would be the box background image or its parent when transparent. |
4915
|
|
|
//But this would make the image dependent on the background. |
4916
|
|
|
//Therefore create an image with white background and copy in |
4917
|
|
|
//A more natural background than black is white. |
4918
|
|
|
//Therefore create an empty image with white background and merge the |
4919
|
|
|
//image in with alpha blending. |
4920
|
|
|
$imgtmp = @imagecreatefrompng($file); |
4921
|
|
|
if (!$imgtmp) { |
4922
|
|
|
return; |
4923
|
|
|
} |
4924
|
|
|
$sx = imagesx($imgtmp); |
4925
|
|
|
$sy = imagesy($imgtmp); |
4926
|
|
|
$img = imagecreatetruecolor($sx, $sy); |
4927
|
|
|
imagealphablending($img, true); |
4928
|
|
|
|
4929
|
|
|
// @todo is it still needed ?? |
4930
|
|
|
$ti = imagecolortransparent($imgtmp); |
4931
|
|
|
if ($ti >= 0) { |
4932
|
|
|
$tc = imagecolorsforindex($imgtmp, $ti); |
4933
|
|
|
$ti = imagecolorallocate($img, $tc['red'], $tc['green'], $tc['blue']); |
4934
|
|
|
imagefill($img, 0, 0, $ti); |
4935
|
|
|
imagecolortransparent($img, $ti); |
4936
|
|
|
} else { |
4937
|
|
|
imagefill($img, 1, 1, imagecolorallocate($img, 255, 255, 255)); |
4938
|
|
|
} |
4939
|
|
|
|
4940
|
|
|
imagecopy($img, $imgtmp, 0, 0, 0, 0, $sx, $sy); |
4941
|
|
|
imagedestroy($imgtmp); |
4942
|
|
|
} |
4943
|
|
|
$this->addImagePng($file, $x, $y, $w, $h, $img); |
|
|
|
|
4944
|
|
|
|
4945
|
|
|
if ($img) { |
4946
|
|
|
imagedestroy($img); |
4947
|
|
|
} |
4948
|
|
|
} |
4949
|
|
|
|
4950
|
|
|
/** |
4951
|
|
|
* add a PNG image into the document, from a file |
4952
|
|
|
* this should work with remote files |
4953
|
|
|
* |
4954
|
|
|
* @param $file |
4955
|
|
|
* @param $x |
4956
|
|
|
* @param $y |
4957
|
|
|
* @param int $w |
4958
|
|
|
* @param int $h |
4959
|
|
|
*/ |
4960
|
|
|
function addSvgFromFile($file, $x, $y, $w = 0, $h = 0) |
|
|
|
|
4961
|
|
|
{ |
4962
|
|
|
$doc = new \Svg\Document(); |
4963
|
|
|
$doc->loadFile($file); |
4964
|
|
|
$dimensions = $doc->getDimensions(); |
4965
|
|
|
|
4966
|
|
|
$this->save(); |
4967
|
|
|
|
4968
|
|
|
$this->transform(array($w / $dimensions["width"], 0, 0, $h / $dimensions["height"], $x, $y)); |
4969
|
|
|
|
4970
|
|
|
$surface = new \Svg\Surface\SurfaceCpdf($doc, $this); |
4971
|
|
|
$doc->render($surface); |
4972
|
|
|
|
4973
|
|
|
$this->restore(); |
4974
|
|
|
} |
4975
|
|
|
|
4976
|
|
|
/** |
4977
|
|
|
* add a PNG image into the document, from a memory buffer of the file |
4978
|
|
|
* |
4979
|
|
|
* @param $file |
4980
|
|
|
* @param $x |
4981
|
|
|
* @param $y |
4982
|
|
|
* @param float $w |
4983
|
|
|
* @param float $h |
4984
|
|
|
* @param $data |
4985
|
|
|
* @param bool $is_mask |
4986
|
|
|
* @param null $mask |
4987
|
|
|
*/ |
4988
|
|
|
function addPngFromBuf($file, $x, $y, $w = 0.0, $h = 0.0, &$data, $is_mask = false, $mask = null) |
|
|
|
|
4989
|
|
|
{ |
4990
|
|
|
if (isset($this->imagelist[$file])) { |
4991
|
|
|
$data = null; |
4992
|
|
|
$info['width'] = $this->imagelist[$file]['w']; |
|
|
|
|
4993
|
|
|
$info['height'] = $this->imagelist[$file]['h']; |
4994
|
|
|
$label = $this->imagelist[$file]['label']; |
4995
|
|
|
} else { |
4996
|
|
|
if ($data == null) { |
4997
|
|
|
$this->addMessage('addPngFromBuf error - data not present!'); |
4998
|
|
|
|
4999
|
|
|
return; |
5000
|
|
|
} |
5001
|
|
|
|
5002
|
|
|
$error = 0; |
5003
|
|
|
|
5004
|
|
|
if (!$error) { |
5005
|
|
|
$header = chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10); |
5006
|
|
|
|
5007
|
|
|
if (mb_substr($data, 0, 8, '8bit') != $header) { |
5008
|
|
|
$error = 1; |
5009
|
|
|
|
5010
|
|
|
if (defined("DEBUGPNG") && DEBUGPNG) { |
5011
|
|
|
print '[addPngFromFile this file does not have a valid header ' . $file . ']'; |
5012
|
|
|
} |
5013
|
|
|
|
5014
|
|
|
$errormsg = 'this file does not have a valid header'; |
5015
|
|
|
} |
5016
|
|
|
} |
5017
|
|
|
|
5018
|
|
|
if (!$error) { |
5019
|
|
|
// set pointer |
5020
|
|
|
$p = 8; |
5021
|
|
|
$len = mb_strlen($data, '8bit'); |
5022
|
|
|
|
5023
|
|
|
// cycle through the file, identifying chunks |
5024
|
|
|
$haveHeader = 0; |
5025
|
|
|
$info = array(); |
5026
|
|
|
$idata = ''; |
5027
|
|
|
$pdata = ''; |
5028
|
|
|
|
5029
|
|
|
while ($p < $len) { |
5030
|
|
|
$chunkLen = $this->getBytes($data, $p, 4); |
5031
|
|
|
$chunkType = mb_substr($data, $p + 4, 4, '8bit'); |
5032
|
|
|
|
5033
|
|
|
switch ($chunkType) { |
5034
|
|
|
case 'IHDR': |
5035
|
|
|
// this is where all the file information comes from |
5036
|
|
|
$info['width'] = $this->getBytes($data, $p + 8, 4); |
5037
|
|
|
$info['height'] = $this->getBytes($data, $p + 12, 4); |
5038
|
|
|
$info['bitDepth'] = ord($data[$p + 16]); |
5039
|
|
|
$info['colorType'] = ord($data[$p + 17]); |
5040
|
|
|
$info['compressionMethod'] = ord($data[$p + 18]); |
5041
|
|
|
$info['filterMethod'] = ord($data[$p + 19]); |
5042
|
|
|
$info['interlaceMethod'] = ord($data[$p + 20]); |
5043
|
|
|
|
5044
|
|
|
//print_r($info); |
5045
|
|
|
$haveHeader = 1; |
5046
|
|
View Code Duplication |
if ($info['compressionMethod'] != 0) { |
|
|
|
|
5047
|
|
|
$error = 1; |
5048
|
|
|
|
5049
|
|
|
//debugpng |
5050
|
|
|
if (defined("DEBUGPNG") && DEBUGPNG) { |
5051
|
|
|
print '[addPngFromFile unsupported compression method ' . $file . ']'; |
5052
|
|
|
} |
5053
|
|
|
|
5054
|
|
|
$errormsg = 'unsupported compression method'; |
5055
|
|
|
} |
5056
|
|
|
|
5057
|
|
View Code Duplication |
if ($info['filterMethod'] != 0) { |
|
|
|
|
5058
|
|
|
$error = 1; |
5059
|
|
|
|
5060
|
|
|
//debugpng |
5061
|
|
|
if (defined("DEBUGPNG") && DEBUGPNG) { |
5062
|
|
|
print '[addPngFromFile unsupported filter method ' . $file . ']'; |
5063
|
|
|
} |
5064
|
|
|
|
5065
|
|
|
$errormsg = 'unsupported filter method'; |
5066
|
|
|
} |
5067
|
|
|
break; |
5068
|
|
|
|
5069
|
|
|
case 'PLTE': |
5070
|
|
|
$pdata .= mb_substr($data, $p + 8, $chunkLen, '8bit'); |
5071
|
|
|
break; |
5072
|
|
|
|
5073
|
|
|
case 'IDAT': |
5074
|
|
|
$idata .= mb_substr($data, $p + 8, $chunkLen, '8bit'); |
5075
|
|
|
break; |
5076
|
|
|
|
5077
|
|
|
case 'tRNS': |
5078
|
|
|
//this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk |
5079
|
|
|
//print "tRNS found, color type = ".$info['colorType']."\n"; |
|
|
|
|
5080
|
|
|
$transparency = array(); |
5081
|
|
|
|
5082
|
|
|
switch ($info['colorType']) { |
5083
|
|
|
// indexed color, rbg |
5084
|
|
|
case 3: |
5085
|
|
|
/* corresponding to entries in the plte chunk |
5086
|
|
|
Alpha for palette index 0: 1 byte |
5087
|
|
|
Alpha for palette index 1: 1 byte |
5088
|
|
|
...etc... |
5089
|
|
|
*/ |
5090
|
|
|
// there will be one entry for each palette entry. up until the last non-opaque entry. |
5091
|
|
|
// set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent) |
5092
|
|
|
$transparency['type'] = 'indexed'; |
5093
|
|
|
$trans = 0; |
5094
|
|
|
|
5095
|
|
|
for ($i = $chunkLen; $i >= 0; $i--) { |
5096
|
|
|
if (ord($data[$p + 8 + $i]) == 0) { |
5097
|
|
|
$trans = $i; |
5098
|
|
|
} |
5099
|
|
|
} |
5100
|
|
|
|
5101
|
|
|
$transparency['data'] = $trans; |
5102
|
|
|
break; |
5103
|
|
|
|
5104
|
|
|
// grayscale |
5105
|
|
|
case 0: |
5106
|
|
|
/* corresponding to entries in the plte chunk |
5107
|
|
|
Gray: 2 bytes, range 0 .. (2^bitdepth)-1 |
5108
|
|
|
*/ |
5109
|
|
|
// $transparency['grayscale'] = $this->PRVT_getBytes($data,$p+8,2); // g = grayscale |
|
|
|
|
5110
|
|
|
$transparency['type'] = 'indexed'; |
5111
|
|
|
$transparency['data'] = ord($data[$p + 8 + 1]); |
5112
|
|
|
break; |
5113
|
|
|
|
5114
|
|
|
// truecolor |
5115
|
|
|
case 2: |
5116
|
|
|
/* corresponding to entries in the plte chunk |
5117
|
|
|
Red: 2 bytes, range 0 .. (2^bitdepth)-1 |
5118
|
|
|
Green: 2 bytes, range 0 .. (2^bitdepth)-1 |
5119
|
|
|
Blue: 2 bytes, range 0 .. (2^bitdepth)-1 |
5120
|
|
|
*/ |
5121
|
|
|
$transparency['r'] = $this->getBytes($data, $p + 8, 2); |
5122
|
|
|
// r from truecolor |
5123
|
|
|
$transparency['g'] = $this->getBytes($data, $p + 10, 2); |
5124
|
|
|
// g from truecolor |
5125
|
|
|
$transparency['b'] = $this->getBytes($data, $p + 12, 2); |
5126
|
|
|
// b from truecolor |
5127
|
|
|
|
5128
|
|
|
$transparency['type'] = 'color-key'; |
5129
|
|
|
break; |
5130
|
|
|
|
5131
|
|
|
//unsupported transparency type |
5132
|
|
|
default: |
5133
|
|
|
if (defined("DEBUGPNG") && DEBUGPNG) { |
5134
|
|
|
print '[addPngFromFile unsupported transparency type ' . $file . ']'; |
5135
|
|
|
} |
5136
|
|
|
break; |
5137
|
|
|
} |
5138
|
|
|
|
5139
|
|
|
// KS End new code |
5140
|
|
|
break; |
5141
|
|
|
|
5142
|
|
|
default: |
5143
|
|
|
break; |
5144
|
|
|
} |
5145
|
|
|
|
5146
|
|
|
$p += $chunkLen + 12; |
5147
|
|
|
} |
5148
|
|
|
|
5149
|
|
View Code Duplication |
if (!$haveHeader) { |
|
|
|
|
5150
|
|
|
$error = 1; |
5151
|
|
|
|
5152
|
|
|
//debugpng |
5153
|
|
|
if (defined("DEBUGPNG") && DEBUGPNG) { |
5154
|
|
|
print '[addPngFromFile information header is missing ' . $file . ']'; |
5155
|
|
|
} |
5156
|
|
|
|
5157
|
|
|
$errormsg = 'information header is missing'; |
5158
|
|
|
} |
5159
|
|
|
|
5160
|
|
View Code Duplication |
if (isset($info['interlaceMethod']) && $info['interlaceMethod']) { |
|
|
|
|
5161
|
|
|
$error = 1; |
5162
|
|
|
|
5163
|
|
|
//debugpng |
5164
|
|
|
if (defined("DEBUGPNG") && DEBUGPNG) { |
5165
|
|
|
print '[addPngFromFile no support for interlaced images in pdf ' . $file . ']'; |
5166
|
|
|
} |
5167
|
|
|
|
5168
|
|
|
$errormsg = 'There appears to be no support for interlaced images in pdf.'; |
5169
|
|
|
} |
5170
|
|
|
} |
5171
|
|
|
|
5172
|
|
View Code Duplication |
if (!$error && $info['bitDepth'] > 8) { |
|
|
|
|
5173
|
|
|
$error = 1; |
5174
|
|
|
|
5175
|
|
|
//debugpng |
5176
|
|
|
if (defined("DEBUGPNG") && DEBUGPNG) { |
5177
|
|
|
print '[addPngFromFile bit depth of 8 or less is supported ' . $file . ']'; |
5178
|
|
|
} |
5179
|
|
|
|
5180
|
|
|
$errormsg = 'only bit depth of 8 or less is supported'; |
5181
|
|
|
} |
5182
|
|
|
|
5183
|
|
|
if (!$error) { |
5184
|
|
|
switch ($info['colorType']) { |
5185
|
|
|
case 3: |
5186
|
|
|
$color = 'DeviceRGB'; |
5187
|
|
|
$ncolor = 1; |
5188
|
|
|
break; |
5189
|
|
|
|
5190
|
|
|
case 2: |
5191
|
|
|
$color = 'DeviceRGB'; |
5192
|
|
|
$ncolor = 3; |
5193
|
|
|
break; |
5194
|
|
|
|
5195
|
|
|
case 0: |
5196
|
|
|
$color = 'DeviceGray'; |
5197
|
|
|
$ncolor = 1; |
5198
|
|
|
break; |
5199
|
|
|
|
5200
|
|
|
default: |
5201
|
|
|
$error = 1; |
5202
|
|
|
|
5203
|
|
|
//debugpng |
5204
|
|
|
if (defined("DEBUGPNG") && DEBUGPNG) { |
5205
|
|
|
print '[addPngFromFile alpha channel not supported: ' . $info['colorType'] . ' ' . $file . ']'; |
5206
|
|
|
} |
5207
|
|
|
|
5208
|
|
|
$errormsg = 'transparency alpha channel not supported, transparency only supported for palette images.'; |
5209
|
|
|
} |
5210
|
|
|
} |
5211
|
|
|
|
5212
|
|
|
if ($error) { |
5213
|
|
|
$this->addMessage('PNG error - (' . $file . ') ' . $errormsg); |
|
|
|
|
5214
|
|
|
|
5215
|
|
|
return; |
5216
|
|
|
} |
5217
|
|
|
|
5218
|
|
|
//print_r($info); |
5219
|
|
|
// so this image is ok... add it in. |
5220
|
|
|
$this->numImages++; |
5221
|
|
|
$im = $this->numImages; |
5222
|
|
|
$label = "I$im"; |
5223
|
|
|
$this->numObj++; |
5224
|
|
|
|
5225
|
|
|
// $this->o_image($this->numObj,'new',array('label' => $label,'data' => $idata,'iw' => $w,'ih' => $h,'type' => 'png','ic' => $info['width'])); |
|
|
|
|
5226
|
|
|
$options = array( |
5227
|
|
|
'label' => $label, |
5228
|
|
|
'data' => $idata, |
|
|
|
|
5229
|
|
|
'bitsPerComponent' => $info['bitDepth'], |
5230
|
|
|
'pdata' => $pdata, |
|
|
|
|
5231
|
|
|
'iw' => $info['width'], |
5232
|
|
|
'ih' => $info['height'], |
5233
|
|
|
'type' => 'png', |
5234
|
|
|
'color' => $color, |
|
|
|
|
5235
|
|
|
'ncolor' => $ncolor, |
|
|
|
|
5236
|
|
|
'masked' => $mask, |
5237
|
|
|
'isMask' => $is_mask |
5238
|
|
|
); |
5239
|
|
|
|
5240
|
|
|
if (isset($transparency)) { |
5241
|
|
|
$options['transparency'] = $transparency; |
5242
|
|
|
} |
5243
|
|
|
|
5244
|
|
|
$this->o_image($this->numObj, 'new', $options); |
|
|
|
|
5245
|
|
|
$this->imagelist[$file] = array('label' => $label, 'w' => $info['width'], 'h' => $info['height']); |
5246
|
|
|
} |
5247
|
|
|
|
5248
|
|
|
if ($is_mask) { |
5249
|
|
|
return; |
5250
|
|
|
} |
5251
|
|
|
|
5252
|
|
|
if ($w <= 0 && $h <= 0) { |
5253
|
|
|
$w = $info['width']; |
5254
|
|
|
$h = $info['height']; |
5255
|
|
|
} |
5256
|
|
|
|
5257
|
|
|
if ($w <= 0) { |
5258
|
|
|
$w = $h / $info['height'] * $info['width']; |
5259
|
|
|
} |
5260
|
|
|
|
5261
|
|
|
if ($h <= 0) { |
5262
|
|
|
$h = $w * $info['height'] / $info['width']; |
5263
|
|
|
} |
5264
|
|
|
|
5265
|
|
|
$this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ", $w, $h, $x, $y, $label)); |
5266
|
|
|
} |
5267
|
|
|
|
5268
|
|
|
/** |
5269
|
|
|
* add a JPEG image into the document, from a file |
5270
|
|
|
* |
5271
|
|
|
* @param $img |
5272
|
|
|
* @param $x |
5273
|
|
|
* @param $y |
5274
|
|
|
* @param int $w |
5275
|
|
|
* @param int $h |
5276
|
|
|
*/ |
5277
|
|
|
function addJpegFromFile($img, $x, $y, $w = 0, $h = 0) |
|
|
|
|
5278
|
|
|
{ |
5279
|
|
|
// attempt to add a jpeg image straight from a file, using no GD commands |
5280
|
|
|
// note that this function is unable to operate on a remote file. |
5281
|
|
|
|
5282
|
|
|
if (!file_exists($img)) { |
5283
|
|
|
return; |
5284
|
|
|
} |
5285
|
|
|
|
5286
|
|
|
if ($this->image_iscached($img)) { |
5287
|
|
|
$data = null; |
5288
|
|
|
$imageWidth = $this->imagelist[$img]['w']; |
5289
|
|
|
$imageHeight = $this->imagelist[$img]['h']; |
5290
|
|
|
$channels = $this->imagelist[$img]['c']; |
5291
|
|
|
} else { |
5292
|
|
|
$tmp = getimagesize($img); |
5293
|
|
|
$imageWidth = $tmp[0]; |
5294
|
|
|
$imageHeight = $tmp[1]; |
5295
|
|
|
|
5296
|
|
|
if (isset($tmp['channels'])) { |
5297
|
|
|
$channels = $tmp['channels']; |
5298
|
|
|
} else { |
5299
|
|
|
$channels = 3; |
5300
|
|
|
} |
5301
|
|
|
|
5302
|
|
|
$data = file_get_contents($img); |
5303
|
|
|
} |
5304
|
|
|
|
5305
|
|
|
if ($w <= 0 && $h <= 0) { |
5306
|
|
|
$w = $imageWidth; |
5307
|
|
|
} |
5308
|
|
|
|
5309
|
|
|
if ($w == 0) { |
5310
|
|
|
$w = $h / $imageHeight * $imageWidth; |
5311
|
|
|
} |
5312
|
|
|
|
5313
|
|
|
if ($h == 0) { |
5314
|
|
|
$h = $w * $imageHeight / $imageWidth; |
5315
|
|
|
} |
5316
|
|
|
|
5317
|
|
|
$this->addJpegImage_common($data, $x, $y, $w, $h, $imageWidth, $imageHeight, $channels, $img); |
5318
|
|
|
} |
5319
|
|
|
|
5320
|
|
|
/** |
5321
|
|
|
* common code used by the two JPEG adding functions |
5322
|
|
|
* @param $data |
5323
|
|
|
* @param $x |
5324
|
|
|
* @param $y |
5325
|
|
|
* @param int $w |
5326
|
|
|
* @param int $h |
5327
|
|
|
* @param $imageWidth |
5328
|
|
|
* @param $imageHeight |
5329
|
|
|
* @param int $channels |
5330
|
|
|
* @param $imgname |
5331
|
|
|
*/ |
5332
|
|
|
private function addJpegImage_common( |
5333
|
|
|
&$data, |
5334
|
|
|
$x, |
5335
|
|
|
$y, |
5336
|
|
|
$w = 0, |
5337
|
|
|
$h = 0, |
5338
|
|
|
$imageWidth, |
5339
|
|
|
$imageHeight, |
5340
|
|
|
$channels = 3, |
5341
|
|
|
$imgname |
5342
|
|
|
) { |
5343
|
|
|
if ($this->image_iscached($imgname)) { |
5344
|
|
|
$label = $this->imagelist[$imgname]['label']; |
5345
|
|
|
//debugpng |
5346
|
|
|
//if (DEBUGPNG) print '[addJpegImage_common Duplicate '.$imgname.']'; |
|
|
|
|
5347
|
|
|
|
5348
|
|
|
} else { |
5349
|
|
|
if ($data == null) { |
5350
|
|
|
$this->addMessage('addJpegImage_common error - (' . $imgname . ') data not present!'); |
5351
|
|
|
|
5352
|
|
|
return; |
5353
|
|
|
} |
5354
|
|
|
|
5355
|
|
|
// note that this function is not to be called externally |
5356
|
|
|
// it is just the common code between the GD and the file options |
5357
|
|
|
$this->numImages++; |
5358
|
|
|
$im = $this->numImages; |
5359
|
|
|
$label = "I$im"; |
5360
|
|
|
$this->numObj++; |
5361
|
|
|
|
5362
|
|
|
$this->o_image( |
5363
|
|
|
$this->numObj, |
5364
|
|
|
'new', |
5365
|
|
|
array( |
|
|
|
|
5366
|
|
|
'label' => $label, |
5367
|
|
|
'data' => &$data, |
5368
|
|
|
'iw' => $imageWidth, |
5369
|
|
|
'ih' => $imageHeight, |
5370
|
|
|
'channels' => $channels |
5371
|
|
|
) |
5372
|
|
|
); |
5373
|
|
|
|
5374
|
|
|
$this->imagelist[$imgname] = array( |
5375
|
|
|
'label' => $label, |
5376
|
|
|
'w' => $imageWidth, |
5377
|
|
|
'h' => $imageHeight, |
5378
|
|
|
'c' => $channels |
5379
|
|
|
); |
5380
|
|
|
} |
5381
|
|
|
|
5382
|
|
|
$this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ ", $w, $h, $x, $y, $label)); |
5383
|
|
|
} |
5384
|
|
|
|
5385
|
|
|
/** |
5386
|
|
|
* specify where the document should open when it first starts |
5387
|
|
|
* |
5388
|
|
|
* @param $style |
5389
|
|
|
* @param int $a |
5390
|
|
|
* @param int $b |
5391
|
|
|
* @param int $c |
5392
|
|
|
*/ |
5393
|
|
View Code Duplication |
function openHere($style, $a = 0, $b = 0, $c = 0) |
|
|
|
|
5394
|
|
|
{ |
5395
|
|
|
// this function will open the document at a specified page, in a specified style |
5396
|
|
|
// the values for style, and the required parameters are: |
5397
|
|
|
// 'XYZ' left, top, zoom |
5398
|
|
|
// 'Fit' |
5399
|
|
|
// 'FitH' top |
5400
|
|
|
// 'FitV' left |
5401
|
|
|
// 'FitR' left,bottom,right |
|
|
|
|
5402
|
|
|
// 'FitB' |
5403
|
|
|
// 'FitBH' top |
5404
|
|
|
// 'FitBV' left |
5405
|
|
|
$this->numObj++; |
5406
|
|
|
$this->o_destination( |
5407
|
|
|
$this->numObj, |
5408
|
|
|
'new', |
5409
|
|
|
array('page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c) |
|
|
|
|
5410
|
|
|
); |
5411
|
|
|
$id = $this->catalogId; |
5412
|
|
|
$this->o_catalog($id, 'openHere', $this->numObj); |
5413
|
|
|
} |
5414
|
|
|
|
5415
|
|
|
/** |
5416
|
|
|
* Add JavaScript code to the PDF document |
5417
|
|
|
* |
5418
|
|
|
* @param string $code |
5419
|
|
|
*/ |
5420
|
|
|
function addJavascript($code) |
|
|
|
|
5421
|
|
|
{ |
5422
|
|
|
$this->javascript .= $code; |
5423
|
|
|
} |
5424
|
|
|
|
5425
|
|
|
/** |
5426
|
|
|
* create a labelled destination within the document |
5427
|
|
|
* |
5428
|
|
|
* @param $label |
5429
|
|
|
* @param $style |
5430
|
|
|
* @param int $a |
5431
|
|
|
* @param int $b |
5432
|
|
|
* @param int $c |
5433
|
|
|
*/ |
5434
|
|
View Code Duplication |
function addDestination($label, $style, $a = 0, $b = 0, $c = 0) |
|
|
|
|
5435
|
|
|
{ |
5436
|
|
|
// associates the given label with the destination, it is done this way so that a destination can be specified after |
5437
|
|
|
// it has been linked to |
5438
|
|
|
// styles are the same as the 'openHere' function |
5439
|
|
|
$this->numObj++; |
5440
|
|
|
$this->o_destination( |
5441
|
|
|
$this->numObj, |
5442
|
|
|
'new', |
5443
|
|
|
array('page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c) |
|
|
|
|
5444
|
|
|
); |
5445
|
|
|
$id = $this->numObj; |
5446
|
|
|
|
5447
|
|
|
// store the label->idf relationship, note that this means that labels can be used only once |
5448
|
|
|
$this->destinations["$label"] = $id; |
5449
|
|
|
} |
5450
|
|
|
|
5451
|
|
|
/** |
5452
|
|
|
* define font families, this is used to initialize the font families for the default fonts |
5453
|
|
|
* and for the user to add new ones for their fonts. The default bahavious can be overridden should |
5454
|
|
|
* that be desired. |
5455
|
|
|
* |
5456
|
|
|
* @param $family |
5457
|
|
|
* @param string $options |
5458
|
|
|
*/ |
5459
|
|
|
function setFontFamily($family, $options = '') |
|
|
|
|
5460
|
|
|
{ |
5461
|
|
|
if (!is_array($options)) { |
5462
|
|
|
if ($family === 'init') { |
5463
|
|
|
// set the known family groups |
5464
|
|
|
// these font families will be used to enable bold and italic markers to be included |
5465
|
|
|
// within text streams. html forms will be used... <b></b> <i></i> |
5466
|
|
|
$this->fontFamilies['Helvetica.afm'] = |
5467
|
|
|
array( |
5468
|
|
|
'b' => 'Helvetica-Bold.afm', |
5469
|
|
|
'i' => 'Helvetica-Oblique.afm', |
5470
|
|
|
'bi' => 'Helvetica-BoldOblique.afm', |
5471
|
|
|
'ib' => 'Helvetica-BoldOblique.afm' |
5472
|
|
|
); |
5473
|
|
|
|
5474
|
|
|
$this->fontFamilies['Courier.afm'] = |
5475
|
|
|
array( |
5476
|
|
|
'b' => 'Courier-Bold.afm', |
5477
|
|
|
'i' => 'Courier-Oblique.afm', |
5478
|
|
|
'bi' => 'Courier-BoldOblique.afm', |
5479
|
|
|
'ib' => 'Courier-BoldOblique.afm' |
5480
|
|
|
); |
5481
|
|
|
|
5482
|
|
|
$this->fontFamilies['Times-Roman.afm'] = |
5483
|
|
|
array( |
5484
|
|
|
'b' => 'Times-Bold.afm', |
5485
|
|
|
'i' => 'Times-Italic.afm', |
5486
|
|
|
'bi' => 'Times-BoldItalic.afm', |
5487
|
|
|
'ib' => 'Times-BoldItalic.afm' |
5488
|
|
|
); |
5489
|
|
|
} |
5490
|
|
|
} else { |
5491
|
|
|
|
5492
|
|
|
// the user is trying to set a font family |
5493
|
|
|
// note that this can also be used to set the base ones to something else |
5494
|
|
|
if (mb_strlen($family)) { |
5495
|
|
|
$this->fontFamilies[$family] = $options; |
5496
|
|
|
} |
5497
|
|
|
} |
5498
|
|
|
} |
5499
|
|
|
|
5500
|
|
|
/** |
5501
|
|
|
* used to add messages for use in debugging |
5502
|
|
|
* |
5503
|
|
|
* @param $message |
5504
|
|
|
*/ |
5505
|
|
|
function addMessage($message) |
|
|
|
|
5506
|
|
|
{ |
5507
|
|
|
$this->messages .= $message . "\n"; |
5508
|
|
|
} |
5509
|
|
|
|
5510
|
|
|
/** |
5511
|
|
|
* a few functions which should allow the document to be treated transactionally. |
5512
|
|
|
* |
5513
|
|
|
* @param $action |
5514
|
|
|
*/ |
5515
|
|
|
function transaction($action) |
|
|
|
|
5516
|
|
|
{ |
5517
|
|
|
switch ($action) { |
5518
|
|
|
case 'start': |
5519
|
|
|
// store all the data away into the checkpoint variable |
5520
|
|
|
$data = get_object_vars($this); |
5521
|
|
|
$this->checkpoint = $data; |
5522
|
|
|
unset($data); |
5523
|
|
|
break; |
5524
|
|
|
|
5525
|
|
|
case 'commit': |
5526
|
|
|
if (is_array($this->checkpoint) && isset($this->checkpoint['checkpoint'])) { |
5527
|
|
|
$tmp = $this->checkpoint['checkpoint']; |
5528
|
|
|
$this->checkpoint = $tmp; |
5529
|
|
|
unset($tmp); |
5530
|
|
|
} else { |
5531
|
|
|
$this->checkpoint = ''; |
|
|
|
|
5532
|
|
|
} |
5533
|
|
|
break; |
5534
|
|
|
|
5535
|
|
View Code Duplication |
case 'rewind': |
|
|
|
|
5536
|
|
|
// do not destroy the current checkpoint, but move us back to the state then, so that we can try again |
5537
|
|
|
if (is_array($this->checkpoint)) { |
5538
|
|
|
// can only abort if were inside a checkpoint |
5539
|
|
|
$tmp = $this->checkpoint; |
5540
|
|
|
|
5541
|
|
|
foreach ($tmp as $k => $v) { |
5542
|
|
|
if ($k !== 'checkpoint') { |
5543
|
|
|
$this->$k = $v; |
5544
|
|
|
} |
5545
|
|
|
} |
5546
|
|
|
unset($tmp); |
5547
|
|
|
} |
5548
|
|
|
break; |
5549
|
|
|
|
5550
|
|
View Code Duplication |
case 'abort': |
|
|
|
|
5551
|
|
|
if (is_array($this->checkpoint)) { |
5552
|
|
|
// can only abort if were inside a checkpoint |
5553
|
|
|
$tmp = $this->checkpoint; |
5554
|
|
|
foreach ($tmp as $k => $v) { |
5555
|
|
|
$this->$k = $v; |
5556
|
|
|
} |
5557
|
|
|
unset($tmp); |
5558
|
|
|
} |
5559
|
|
|
break; |
5560
|
|
|
} |
5561
|
|
|
} |
5562
|
|
|
} |
5563
|
|
|
|
You can fix this by adding a namespace to your class:
When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.