1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
/** |
5
|
|
|
* Font class. |
6
|
|
|
* |
7
|
|
|
* @package YetiForcePDF\Objects |
8
|
|
|
* |
9
|
|
|
* @copyright YetiForce Sp. z o.o |
10
|
|
|
* @license MIT |
11
|
|
|
* @author Rafal Pospiech <[email protected]> |
12
|
|
|
*/ |
13
|
|
|
|
14
|
|
|
namespace YetiForcePDF\Objects; |
15
|
|
|
|
16
|
|
|
use YetiForcePDF\Math; |
17
|
|
|
use YetiForcePDF\Style\NumericValue; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Class Font. |
21
|
|
|
*/ |
22
|
|
|
class Font extends \YetiForcePDF\Objects\Resource |
23
|
|
|
{ |
24
|
|
|
protected static $fontFiles = [ |
25
|
|
|
'DejaVu Sans' => [ |
26
|
|
|
'100' => [ |
27
|
|
|
'normal' => 'DejaVuSans-ExtraLight.ttf', |
28
|
|
|
'italic' => 'DejaVuSans-ExtraLight.ttf', |
29
|
|
|
], |
30
|
|
|
'200' => [ |
31
|
|
|
'normal' => 'DejaVuSans-ExtraLight.ttf', |
32
|
|
|
'italic' => 'DejaVuSans-ExtraLight.ttf', |
33
|
|
|
], |
34
|
|
|
'300' => [ |
35
|
|
|
'normal' => 'DejaVuSans-ExtraLight.ttf', |
36
|
|
|
'italic' => 'DejaVuSans-ExtraLight.ttf', |
37
|
|
|
], |
38
|
|
|
'400' => [ |
39
|
|
|
'normal' => 'DejaVuSans.ttf', |
40
|
|
|
'italic' => 'DejaVuSans-Oblique.ttf', |
41
|
|
|
], |
42
|
|
|
'500' => [ |
43
|
|
|
'normal' => 'DejaVuSans.ttf', |
44
|
|
|
'italic' => 'DejaVuSans-Oblique.ttf', |
45
|
|
|
], |
46
|
|
|
'600' => [ |
47
|
|
|
'normal' => 'DejaVuSans.ttf', |
48
|
|
|
'italic' => 'DejaVuSans-Oblique.ttf', |
49
|
|
|
], |
50
|
|
|
'700' => [ |
51
|
|
|
'normal' => 'DejaVuSans-Bold.ttf', |
52
|
|
|
'italic' => 'DejaVuSans-BoldOblique.ttf', |
53
|
|
|
], |
54
|
|
|
'800' => [ |
55
|
|
|
'normal' => 'DejaVuSans-Bold.ttf', |
56
|
|
|
'italic' => 'DejaVuSans-BoldOblique.ttf', |
57
|
|
|
], |
58
|
|
|
'900' => [ |
59
|
|
|
'normal' => 'DejaVuSans-Bold.ttf', |
60
|
|
|
'italic' => 'DejaVuSans-BoldOblique.ttf', |
61
|
|
|
], |
62
|
|
|
], |
63
|
|
|
]; |
64
|
|
|
/** |
65
|
|
|
* @var string |
66
|
|
|
*/ |
67
|
|
|
public static $defaultFontFamily = 'DejaVu Sans'; |
68
|
|
|
/** |
69
|
|
|
* @var array |
70
|
|
|
*/ |
71
|
|
|
protected static $customFontFiles = []; |
72
|
|
|
/** |
73
|
|
|
* @var string |
74
|
|
|
*/ |
75
|
|
|
protected $fontDir = ''; |
76
|
|
|
/** |
77
|
|
|
* Which type of dictionary (Page, Catalog, Font etc...). |
78
|
|
|
* |
79
|
|
|
* @var string |
80
|
|
|
*/ |
81
|
|
|
protected $resourceType = 'Font'; |
82
|
|
|
/** |
83
|
|
|
* Object name. |
84
|
|
|
* |
85
|
|
|
* @var string |
86
|
|
|
*/ |
87
|
|
|
protected $resourceName = 'Font'; |
88
|
|
|
/** |
89
|
|
|
* Base font type aka font family. |
90
|
|
|
* |
91
|
|
|
* @var string |
92
|
|
|
*/ |
93
|
|
|
protected $family = ''; |
94
|
|
|
/** |
95
|
|
|
* Font weight. |
96
|
|
|
* |
97
|
|
|
* @var string |
98
|
|
|
*/ |
99
|
|
|
protected $weight = 'normal'; |
100
|
|
|
/** |
101
|
|
|
* Font style. |
102
|
|
|
* |
103
|
|
|
* @var string |
104
|
|
|
*/ |
105
|
|
|
protected $style = 'normal'; |
106
|
|
|
/** |
107
|
|
|
* Font number. |
108
|
|
|
* |
109
|
|
|
* @var string |
110
|
|
|
*/ |
111
|
|
|
protected $fontNumber = 'F1'; |
112
|
|
|
/** |
113
|
|
|
* Font size. |
114
|
|
|
* |
115
|
|
|
* @var NumericValue |
116
|
|
|
*/ |
117
|
|
|
protected $size; |
118
|
|
|
/** |
119
|
|
|
* Font height. |
120
|
|
|
* |
121
|
|
|
* @var string |
122
|
|
|
*/ |
123
|
|
|
protected $height = '0'; |
124
|
|
|
/** |
125
|
|
|
* Text height with ascender and descender. |
126
|
|
|
* |
127
|
|
|
* @var string|null |
128
|
|
|
*/ |
129
|
|
|
protected $textHeight; |
130
|
|
|
/** |
131
|
|
|
* Font data. |
132
|
|
|
* |
133
|
|
|
* @var \FontLib\Font |
134
|
|
|
*/ |
135
|
|
|
protected $fontData; |
136
|
|
|
/** |
137
|
|
|
* @var |
138
|
|
|
*/ |
139
|
|
|
protected $fontDescriptor; |
140
|
|
|
/** |
141
|
|
|
* @var \YetiForcePDF\Objects\Basic\StreamObject |
142
|
|
|
*/ |
143
|
|
|
protected $dataStream; |
144
|
|
|
/** |
145
|
|
|
* Info needed to write in pdf. |
146
|
|
|
* |
147
|
|
|
* @var array |
148
|
|
|
*/ |
149
|
|
|
protected $outputInfo = []; |
150
|
|
|
/** |
151
|
|
|
* @var string |
152
|
|
|
*/ |
153
|
|
|
protected $unitsPerEm = '1000'; |
154
|
|
|
/** |
155
|
|
|
* Character widths. |
156
|
|
|
* |
157
|
|
|
* @var int[] |
158
|
|
|
*/ |
159
|
|
|
protected $widths = []; |
160
|
|
|
/** |
161
|
|
|
* Cid to Gid characters map. |
162
|
|
|
* |
163
|
|
|
* @var \YetiForcePDF\Objects\Basic\StreamObject |
164
|
|
|
*/ |
165
|
|
|
protected $cidToGid; |
166
|
|
|
/** |
167
|
|
|
* Cid system info. |
168
|
|
|
* |
169
|
|
|
* @var \YetiForcePDF\Objects\Basic\DictionaryObject |
170
|
|
|
*/ |
171
|
|
|
protected $cidSystemInfo; |
172
|
|
|
/** |
173
|
|
|
* Character map (unicode). |
174
|
|
|
* |
175
|
|
|
* @var array |
176
|
|
|
*/ |
177
|
|
|
protected $charMap = []; |
178
|
|
|
/** |
179
|
|
|
* Unicode char map stream. |
180
|
|
|
* |
181
|
|
|
* @var \YetiForcePDF\Objects\Basic\StreamObject |
182
|
|
|
*/ |
183
|
|
|
protected $toUnicode; |
184
|
|
|
/** |
185
|
|
|
* Main font that is used - first font - this file is just descendant font. |
186
|
|
|
* |
187
|
|
|
* @var \YetiForcePDF\Objects\Basic\DictionaryObject |
188
|
|
|
*/ |
189
|
|
|
protected $fontType0; |
190
|
|
|
/** |
191
|
|
|
* From baseline to top of the font. |
192
|
|
|
* |
193
|
|
|
* @var string |
194
|
|
|
*/ |
195
|
|
|
protected $ascender = '0'; |
196
|
|
|
/** |
197
|
|
|
* From baseline to bottom (with jyg chars that are bellow baseline). |
198
|
|
|
* |
199
|
|
|
* @var string |
200
|
|
|
*/ |
201
|
|
|
protected $descender = '0'; |
202
|
|
|
/** |
203
|
|
|
* @var string |
204
|
|
|
*/ |
205
|
|
|
protected $fontPostscriptName; |
206
|
|
|
/** |
207
|
|
|
* Text widths cache. |
208
|
|
|
* |
209
|
|
|
* @var array |
210
|
|
|
*/ |
211
|
|
|
protected $textWidths = []; |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* Initialization. |
215
|
|
|
* |
216
|
|
|
* @return $this |
217
|
|
|
*/ |
218
|
|
|
public function init() |
219
|
|
|
{ |
220
|
|
|
if (empty($this->family)) { |
221
|
|
|
$this->family = self::$defaultFontFamily; |
222
|
|
|
} |
223
|
|
|
$this->fontDir = realpath(__DIR__ . \DIRECTORY_SEPARATOR . '..' . \DIRECTORY_SEPARATOR . 'Fonts') . \DIRECTORY_SEPARATOR; |
224
|
|
|
$alreadyExists = $this->document->getFontInstance($this->family, $this->weight, $this->style); |
225
|
|
|
if (null === $alreadyExists) { |
226
|
|
|
parent::init(); |
227
|
|
|
$this->document->setFontInstance($this->family, $this->weight, $this->style, $this); |
228
|
|
|
$this->fontNumber = 'F' . $this->document->getActualFontId(); |
229
|
|
|
$this->fontData = $this->loadFontData(); |
|
|
|
|
230
|
|
|
$this->document->setFontData($this->family, $this->weight, $this->style, $this->fontData); |
|
|
|
|
231
|
|
|
$this->fontDescriptor = (new \YetiForcePDF\Objects\FontDescriptor()) |
232
|
|
|
->setDocument($this->document) |
233
|
|
|
->setFont($this) |
234
|
|
|
->init(); |
235
|
|
|
foreach ($this->document->getObjects('Page') as $page) { |
236
|
|
|
$page->synchronizeFonts(); |
237
|
|
|
} |
238
|
|
|
return $this; |
239
|
|
|
} |
240
|
|
|
$this->setAddToDocument(false); |
241
|
|
|
// do not init parent! we don't want to create resources etc. |
242
|
|
|
return clone $alreadyExists; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* Add custom font. |
247
|
|
|
* |
248
|
|
|
* @param string $family |
249
|
|
|
* @param string $weight |
250
|
|
|
* @param string $style |
251
|
|
|
* @param string $fileName |
252
|
|
|
*/ |
253
|
|
|
public static function addCustomFont(string $family, string $weight, string $style, string $fileName) |
254
|
|
|
{ |
255
|
|
|
$strWeight = (new \YetiForcePDF\Style\Normalizer\FontWeight())->normalize($weight)['font-weight']; |
256
|
|
|
static::$customFontFiles[$family][$strWeight][$style] = $fileName; |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* Set font number. |
261
|
|
|
* |
262
|
|
|
* @param string $number |
263
|
|
|
* |
264
|
|
|
* @return \YetiForcePDF\Objects\Font |
265
|
|
|
*/ |
266
|
|
|
public function setNumber(string $number): self |
267
|
|
|
{ |
268
|
|
|
$this->fontNumber = $number; |
269
|
|
|
|
270
|
|
|
return $this; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* Set font name. |
275
|
|
|
* |
276
|
|
|
* @param string $name |
277
|
|
|
* |
278
|
|
|
* @return $this |
279
|
|
|
*/ |
280
|
|
|
public function setFamily(string $name) |
281
|
|
|
{ |
282
|
|
|
$this->family = $name; |
283
|
|
|
|
284
|
|
|
return $this; |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
/** |
288
|
|
|
* Get font name. |
289
|
|
|
* |
290
|
|
|
* @return string |
291
|
|
|
*/ |
292
|
|
|
public function getFamily(): string |
293
|
|
|
{ |
294
|
|
|
return $this->family; |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* Set font weight. |
299
|
|
|
* |
300
|
|
|
* @param string $weight |
301
|
|
|
* |
302
|
|
|
* @return $this |
303
|
|
|
*/ |
304
|
|
|
public function setWeight(string $weight) |
305
|
|
|
{ |
306
|
|
|
$this->weight = $weight; |
307
|
|
|
|
308
|
|
|
return $this; |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
/** |
312
|
|
|
* Get font name. |
313
|
|
|
* |
314
|
|
|
* @return string |
315
|
|
|
*/ |
316
|
|
|
public function getWeight(): string |
317
|
|
|
{ |
318
|
|
|
return $this->weight; |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
/** |
322
|
|
|
* Set font style. |
323
|
|
|
* |
324
|
|
|
* @param string $style |
325
|
|
|
* |
326
|
|
|
* @return $this |
327
|
|
|
*/ |
328
|
|
|
public function setStyle(string $style) |
329
|
|
|
{ |
330
|
|
|
$this->style = $style; |
331
|
|
|
|
332
|
|
|
return $this; |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
/** |
336
|
|
|
* Get font style. |
337
|
|
|
* |
338
|
|
|
* @return string |
339
|
|
|
*/ |
340
|
|
|
public function getStyle(): string |
341
|
|
|
{ |
342
|
|
|
return $this->style; |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
/** |
346
|
|
|
* Get full font name. |
347
|
|
|
* |
348
|
|
|
* @return string |
349
|
|
|
*/ |
350
|
|
|
public function getFullName() |
351
|
|
|
{ |
352
|
|
|
return $this->fontPostscriptName; |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
/** |
356
|
|
|
* Get output info. |
357
|
|
|
* |
358
|
|
|
* @return array |
359
|
|
|
*/ |
360
|
|
|
public function getOutputInfo() |
361
|
|
|
{ |
362
|
|
|
return $this->outputInfo; |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
/** |
366
|
|
|
* Get font data. |
367
|
|
|
* |
368
|
|
|
* @return \FontLib\Font |
369
|
|
|
*/ |
370
|
|
|
public function getData() |
371
|
|
|
{ |
372
|
|
|
return $this->fontData; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
/** |
376
|
|
|
* Set Font size. |
377
|
|
|
* |
378
|
|
|
* @param NumericValue $size |
379
|
|
|
* |
380
|
|
|
* @return $this |
381
|
|
|
*/ |
382
|
|
|
public function setSize(NumericValue $size) |
383
|
|
|
{ |
384
|
|
|
$this->size = $size; |
385
|
|
|
return $this; |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
/** |
389
|
|
|
* Get font size. |
390
|
|
|
* |
391
|
|
|
* @return NumericValue |
392
|
|
|
*/ |
393
|
|
|
public function getSize(): NumericValue |
394
|
|
|
{ |
395
|
|
|
return $this->size; |
396
|
|
|
} |
397
|
|
|
|
398
|
|
|
/** |
399
|
|
|
* Get closest value with specified unit - not converted. |
400
|
|
|
* |
401
|
|
|
* @param string $unit |
402
|
|
|
* |
403
|
|
|
* @return Font |
404
|
|
|
*/ |
405
|
|
|
public function getClosestWithUnit(string $unit) |
406
|
|
|
{ |
407
|
|
|
if ($this->getSize()->getUnit() === $unit) { |
408
|
|
|
return $this; |
409
|
|
|
} |
410
|
|
|
return $this->getParent()->getClosestWithUnit($unit); |
|
|
|
|
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
/** |
414
|
|
|
* Convert character to int. |
415
|
|
|
* |
416
|
|
|
* @param $string |
417
|
|
|
* |
418
|
|
|
* @return int |
419
|
|
|
*/ |
420
|
|
|
public function mbOrd($string) |
421
|
|
|
{ |
422
|
|
|
if (isset($this->document->ordCache[$string])) { |
423
|
|
|
return $this->document->ordCache[$string]; |
424
|
|
|
} |
425
|
|
|
if (true === \extension_loaded('mbstring')) { |
426
|
|
|
mb_language('Neutral'); |
427
|
|
|
mb_internal_encoding('UTF-8'); |
428
|
|
|
mb_detect_order(['UTF-8', 'ISO-8859-15', 'ISO-8859-1', 'ASCII']); |
429
|
|
|
$result = unpack('N', mb_convert_encoding($string, 'UCS-4BE', 'UTF-8')); |
|
|
|
|
430
|
|
|
if (true === \is_array($result)) { |
|
|
|
|
431
|
|
|
return $result[1]; |
432
|
|
|
} |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
return $this->document->ordCache[$string] = \ord($string); |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
/** |
439
|
|
|
* Get text width. |
440
|
|
|
* |
441
|
|
|
* @param string $text |
442
|
|
|
* |
443
|
|
|
* @return string |
444
|
|
|
*/ |
445
|
|
|
public function getTextWidth(string $text): string |
446
|
|
|
{ |
447
|
|
|
if (isset($this->textWidths[$text])) { |
448
|
|
|
return $this->textWidths[$text]; |
449
|
|
|
} |
450
|
|
|
$width = '0'; |
451
|
|
|
for ($i = 0, $len = mb_strlen($text); $i < $len; ++$i) { |
452
|
|
|
$char = mb_substr($text, $i, 1); |
453
|
|
|
if (isset($this->widths[$this->mbOrd($char)])) { |
454
|
|
|
$width = Math::add($width, (string) $this->widths[$this->mbOrd($char)]); |
455
|
|
|
} |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
return $this->textWidths[$text] = Math::div(Math::mul($this->size->getConverted(), $width), '1000'); |
459
|
|
|
} |
460
|
|
|
|
461
|
|
|
/** |
462
|
|
|
* Get text height. |
463
|
|
|
* |
464
|
|
|
* @param string|null $text |
465
|
|
|
* |
466
|
|
|
* @return string |
467
|
|
|
*/ |
468
|
|
|
public function getTextHeight(string $text = null): string |
|
|
|
|
469
|
|
|
{ |
470
|
|
|
if (null === $this->textHeight) { |
471
|
|
|
$this->textHeight = Math::add($this->getAscender(), Math::mul($this->getDescender(), '-1')); |
472
|
|
|
} |
473
|
|
|
|
474
|
|
|
return $this->textHeight; |
475
|
|
|
} |
476
|
|
|
|
477
|
|
|
/** |
478
|
|
|
* Get ascender (from baseline to top of the bounding box). |
479
|
|
|
* |
480
|
|
|
* @return string |
481
|
|
|
*/ |
482
|
|
|
public function getAscender(): string |
483
|
|
|
{ |
484
|
|
|
return Math::div(Math::mul($this->size->getConverted(), $this->ascender), $this->unitsPerEm); |
485
|
|
|
} |
486
|
|
|
|
487
|
|
|
/** |
488
|
|
|
* Get descender (from baseline to bottom of the bounding box). |
489
|
|
|
* |
490
|
|
|
* @return string |
491
|
|
|
*/ |
492
|
|
|
public function getDescender(): string |
493
|
|
|
{ |
494
|
|
|
return Math::div(Math::mul($this->size->getConverted(), $this->descender), $this->unitsPerEm); |
495
|
|
|
} |
496
|
|
|
|
497
|
|
|
/** |
498
|
|
|
* Get font number. |
499
|
|
|
* |
500
|
|
|
* @return string |
501
|
|
|
*/ |
502
|
|
|
public function getNumber(): string |
503
|
|
|
{ |
504
|
|
|
return $this->fontNumber; |
505
|
|
|
} |
506
|
|
|
|
507
|
|
|
/** |
508
|
|
|
* {@inheritdoc} |
509
|
|
|
*/ |
510
|
|
|
public function getReference(): string |
511
|
|
|
{ |
512
|
|
|
return $this->getRawId() . ' R'; |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
/** |
516
|
|
|
* Get data stream. |
517
|
|
|
* |
518
|
|
|
* @return \YetiForcePDF\Objects\Basic\StreamObject |
519
|
|
|
*/ |
520
|
|
|
public function getDataStream() |
521
|
|
|
{ |
522
|
|
|
return $this->dataStream; |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
public function normalizeUnit(string $value, string $base = '1000') |
526
|
|
|
{ |
527
|
|
|
return bcmul($value, Math::div($base, $this->unitsPerEm), 0); |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
protected function setUpUnicode($charMapUnicode) |
|
|
|
|
531
|
|
|
{ |
532
|
|
|
$stream = implode("\n", [ |
533
|
|
|
'/CIDInit /ProcSet findresource begin', |
534
|
|
|
'12 dict begin', |
535
|
|
|
'begincmap', |
536
|
|
|
'/CIDSystemInfo', |
537
|
|
|
'<</Registry (Adobe)', |
538
|
|
|
'/Ordering (UCS)', |
539
|
|
|
'/Supplement 0', |
540
|
|
|
'>> def', |
541
|
|
|
'/CMapName /Adobe-Identity-UCS def', |
542
|
|
|
'/CMapType 2 def', |
543
|
|
|
'1 begincodespacerange', |
544
|
|
|
'<0000> <FFFF>', |
545
|
|
|
'endcodespacerange', |
546
|
|
|
'1 beginbfrange', |
547
|
|
|
'<0000> <FFFF> <0000>', |
548
|
|
|
'endbfrange', |
549
|
|
|
'endcmap', |
550
|
|
|
'CMapName currentdict /CMap defineresource pop', |
551
|
|
|
'end', |
552
|
|
|
'end', |
553
|
|
|
]); |
554
|
|
|
$this->toUnicode->addRawContent($stream); |
555
|
|
|
} |
556
|
|
|
|
557
|
|
|
/** |
558
|
|
|
* Get Type0 font - main one. |
559
|
|
|
* |
560
|
|
|
* @return Font |
561
|
|
|
*/ |
562
|
|
|
public function getType0Font() |
563
|
|
|
{ |
564
|
|
|
return $this->fontType0; |
565
|
|
|
} |
566
|
|
|
|
567
|
|
|
/** |
568
|
|
|
* Match font to weights and styles - try other weighs/styles if not present. |
569
|
|
|
* |
570
|
|
|
* @param bool $custom |
571
|
|
|
* |
572
|
|
|
* @return string |
573
|
|
|
*/ |
574
|
|
|
protected function matchFont(bool $custom = false) |
575
|
|
|
{ |
576
|
|
|
if (!$custom) { |
577
|
|
|
return static::$fontFiles[$this->family][$this->weight][$this->style]; |
578
|
|
|
} |
579
|
|
|
if (isset(static::$customFontFiles[$this->family][$this->weight][$this->style]) && file_exists(static::$customFontFiles[$this->family][$this->weight][$this->style])) { |
580
|
|
|
return static::$customFontFiles[$this->family][$this->weight][$this->style]; |
581
|
|
|
} |
582
|
|
|
$weight = ''; |
583
|
|
|
for ($currentWeight = (int) $this->weight; $currentWeight >= 0; $currentWeight -= 100) { |
584
|
|
|
if (isset(static::$customFontFiles[$this->family][(string) $currentWeight])) { |
585
|
|
|
$weight = (string) $currentWeight; |
586
|
|
|
|
587
|
|
|
break; |
588
|
|
|
} |
589
|
|
|
} |
590
|
|
|
if (!$weight) { |
591
|
|
|
for ($currentWeight = (int) $this->weight; $currentWeight <= 900; $currentWeight += 100) { |
592
|
|
|
if (isset(static::$customFontFiles[$this->family][(string) $currentWeight])) { |
593
|
|
|
$weight = (string) $currentWeight; |
594
|
|
|
|
595
|
|
|
break; |
596
|
|
|
} |
597
|
|
|
} |
598
|
|
|
} |
599
|
|
|
if (!$weight) { |
600
|
|
|
// font file not found return default one |
601
|
|
|
return $this->fontDir . static::$fontFiles[static::$defaultFontFamily][$this->weight][$this->style]; |
602
|
|
|
} |
603
|
|
|
if (isset(static::$customFontFiles[$this->family][$weight][$this->style]) && file_exists(static::$customFontFiles[$this->family][$weight][$this->style])) { |
604
|
|
|
return static::$customFontFiles[$this->family][$weight][$this->style]; |
605
|
|
|
} |
606
|
|
|
// inverse style |
607
|
|
|
$style = 'normal' === $this->style ? 'italic' : 'normal'; |
608
|
|
|
if (isset(static::$customFontFiles[$this->family][$weight][$style]) && file_exists(static::$customFontFiles[$this->family][$weight][$style])) { |
609
|
|
|
return static::$customFontFiles[$this->family][$weight][$style]; |
610
|
|
|
} |
611
|
|
|
// font file not found - get default one |
612
|
|
|
return $this->fontDir . static::$fontFiles[static::$defaultFontFamily][$this->weight][$this->style]; |
613
|
|
|
} |
614
|
|
|
|
615
|
|
|
/** |
616
|
|
|
* Load fonts from array. |
617
|
|
|
* |
618
|
|
|
* @param array $decoded |
619
|
|
|
* |
620
|
|
|
* @throws \ErrorException |
621
|
|
|
*/ |
622
|
|
|
public static function loadFromArray(array $decoded) |
623
|
|
|
{ |
624
|
|
|
if (!\is_array($decoded)) { |
|
|
|
|
625
|
|
|
throw new \ErrorException('Invalid fonts json structure.'); |
626
|
|
|
} |
627
|
|
|
foreach ($decoded as $font) { |
628
|
|
|
if (!\is_array($font)) { |
629
|
|
|
throw new \ErrorException('Invalid fonts json structure.'); |
630
|
|
|
} |
631
|
|
|
if (empty($font['family']) || empty($font['weight']) || empty($font['style']) || empty($font['file'])) { |
632
|
|
|
throw new \ErrorException('Invalid fonts json structure.'); |
633
|
|
|
} |
634
|
|
|
static::addCustomFont($font['family'], $font['weight'], $font['style'], $font['file']); |
635
|
|
|
} |
636
|
|
|
} |
637
|
|
|
|
638
|
|
|
/** |
639
|
|
|
* Get font file name without extension. |
640
|
|
|
* |
641
|
|
|
* @throws \ErrorException |
642
|
|
|
* |
643
|
|
|
* @return string |
644
|
|
|
*/ |
645
|
|
|
public function getFontFileName() |
646
|
|
|
{ |
647
|
|
|
if (isset(static::$fontFiles[$this->family])) { |
648
|
|
|
$match = $this->matchFont(); |
649
|
|
|
if (file_exists($this->fontDir . $match)) { |
650
|
|
|
return $this->fontDir . $match; |
651
|
|
|
} |
652
|
|
|
if (\defined('ROOT_DIRECTORY')) { |
653
|
|
|
$path = \ROOT_DIRECTORY; |
|
|
|
|
654
|
|
|
$path .= \DIRECTORY_SEPARATOR . 'public_html'; |
655
|
|
|
$path .= \DIRECTORY_SEPARATOR . 'vendor'; |
656
|
|
|
$path .= \DIRECTORY_SEPARATOR . 'yetiforce'; |
657
|
|
|
$path .= \DIRECTORY_SEPARATOR . 'yetiforcepdf'; |
658
|
|
|
$path .= \DIRECTORY_SEPARATOR . 'lib'; |
659
|
|
|
$path .= \DIRECTORY_SEPARATOR . 'Fonts'; |
660
|
|
|
$path .= \DIRECTORY_SEPARATOR . $match; |
661
|
|
|
if (file_exists($path)) { |
662
|
|
|
return $path; |
663
|
|
|
} |
664
|
|
|
} |
665
|
|
|
} |
666
|
|
|
|
667
|
|
|
return $this->matchFont(true); |
668
|
|
|
} |
669
|
|
|
|
670
|
|
|
/** |
671
|
|
|
* Load font. |
672
|
|
|
* |
673
|
|
|
* @throws \FontLib\Exception\FontNotFoundException |
674
|
|
|
* |
675
|
|
|
* @return \FontLib\TrueType\File|null |
676
|
|
|
*/ |
677
|
|
|
protected function loadFontData() |
678
|
|
|
{ |
679
|
|
|
$fileName = $this->getFontFileName(); |
680
|
|
|
$fileContent = file_get_contents($fileName); |
681
|
|
|
$font = \FontLib\Font::load($fileName); |
682
|
|
|
$font->parse(); |
683
|
|
|
$head = $font->getData('head'); |
684
|
|
|
$hhea = $font->getData('hhea'); |
685
|
|
|
$hmtx = $font->getData('hmtx'); |
686
|
|
|
$post = $font->getData('post'); |
687
|
|
|
$os2 = $font->getData('OS/2'); |
688
|
|
|
if (isset($head['unitsPerEm'])) { |
689
|
|
|
$this->unitsPerEm = (string) $head['unitsPerEm']; |
690
|
|
|
} |
691
|
|
|
$this->outputInfo['descriptor'] = []; |
692
|
|
|
$this->outputInfo['descriptor']['FontBBox'] = '[' . implode(' ', [ |
693
|
|
|
$this->normalizeUnit((string) $head['xMin']), |
694
|
|
|
$this->normalizeUnit((string) $head['yMin']), |
695
|
|
|
$this->normalizeUnit((string) $head['xMax']), |
696
|
|
|
$this->normalizeUnit((string) $head['yMax']), |
697
|
|
|
]) . ']'; |
698
|
|
|
$this->outputInfo['descriptor']['Ascent'] = (string) $hhea['ascent']; |
699
|
|
|
$this->outputInfo['descriptor']['Descent'] = (string) $hhea['descent']; |
700
|
|
|
$this->ascender = (string) $this->outputInfo['descriptor']['Ascent']; |
701
|
|
|
$this->descender = (string) $this->outputInfo['descriptor']['Descent']; |
702
|
|
|
$this->outputInfo['descriptor']['MissingWidth'] = '500'; |
703
|
|
|
$this->outputInfo['descriptor']['StemV'] = '80'; |
704
|
|
|
if (isset($post['usWeightClass']) && $post['usWeightClass'] > 400) { |
705
|
|
|
$this->outputInfo['descriptor']['StemV'] = '120'; |
706
|
|
|
} |
707
|
|
|
$this->outputInfo['descriptor']['ItalicAngle'] = (string) $post['italicAngle']; |
708
|
|
|
$flags = 0; |
709
|
|
|
if ('0' !== $this->outputInfo['descriptor']['ItalicAngle']) { |
710
|
|
|
$flags += 2 ** 6; |
711
|
|
|
} |
712
|
|
|
if (true === $post['isFixedPitch']) { |
713
|
|
|
++$flags; |
714
|
|
|
} |
715
|
|
|
$flags += 2 ** 5; |
716
|
|
|
$this->outputInfo['descriptor']['Flags'] = (string) $flags; |
717
|
|
|
$this->outputInfo['font'] = []; |
718
|
|
|
$widths = []; |
719
|
|
|
$this->widths = []; |
720
|
|
|
$this->charMap = $font->getUnicodeCharMap(); |
721
|
|
|
$charMapUnicode = []; |
722
|
|
|
$cidToGid = str_pad('', 256 * 256 * 2, "\x00"); |
723
|
|
|
foreach ($this->charMap as $c => $glyph) { |
724
|
|
|
// Set values in CID to GID map |
725
|
|
|
if ($c >= 0 && $c < 0xFFFF && $glyph) { |
726
|
|
|
$cidToGid[$c * 2] = \chr($glyph >> 8); |
727
|
|
|
$cidToGid[$c * 2 + 1] = \chr($glyph & 0xFF); |
728
|
|
|
} |
729
|
|
|
$width = $this->normalizeUnit(isset($hmtx[$glyph]) ? (string) $hmtx[$glyph][0] : (string) $hmtx[0][0]); |
730
|
|
|
$widths[] = $c . ' [' . $width . ']'; |
731
|
|
|
$this->widths[$c] = $width; |
732
|
|
|
} |
733
|
|
|
$this->cidToGid = (new \YetiForcePDF\Objects\Basic\StreamObject()) |
734
|
|
|
->setDocument($this->document) |
735
|
|
|
->init(); |
736
|
|
|
$this->cidToGid->addRawContent($cidToGid)->setFilter('FlateDecode'); |
737
|
|
|
$this->outputInfo['font']['Widths'] = $widths; |
738
|
|
|
$this->outputInfo['font']['FirstChar'] = 0; |
739
|
|
|
$this->outputInfo['font']['LastChar'] = \count($widths) - 1; |
740
|
|
|
$this->height = Math::sub((string) $hhea['ascent'], (string) $hhea['descent']); |
741
|
|
|
if (isset($os2['typoLineGap'])) { |
742
|
|
|
$this->height = Math::add($this->height, (string) $os2['typoLineGap']); |
743
|
|
|
} |
744
|
|
|
$this->fontType0 = (new \YetiForcePDF\Objects\Basic\DictionaryObject()) |
745
|
|
|
->setDocument($this->document) |
746
|
|
|
->init(); |
747
|
|
|
$this->cidSystemInfo = (new \YetiForcePDF\Objects\Basic\DictionaryObject()) |
748
|
|
|
->setDocument($this->document) |
749
|
|
|
->init(); |
750
|
|
|
$this->cidSystemInfo->addValue('Registry', '(Adobe)') |
751
|
|
|
->addValue('Ordering', '(UCS)') |
752
|
|
|
->addValue('Supplement', '0'); |
753
|
|
|
$this->dataStream = (new \YetiForcePDF\Objects\Basic\StreamObject()) |
754
|
|
|
->setDocument($this->document) |
755
|
|
|
->setFilter('FlateDecode') |
756
|
|
|
->init() |
757
|
|
|
->addRawContent($fileContent); |
758
|
|
|
$this->toUnicode = (new \YetiForcePDF\Objects\Basic\StreamObject()) |
759
|
|
|
->setDocument($this->document) |
760
|
|
|
->init(); |
761
|
|
|
$this->setUpUnicode($charMapUnicode); |
762
|
|
|
$this->fontPostscriptName = $font->getFontPostscriptName(); |
763
|
|
|
$this->fontType0->setDictionaryType('Font') |
764
|
|
|
->addValue('Subtype', '/Type0') |
765
|
|
|
->addValue('BaseFont', '/' . $this->fontPostscriptName) |
766
|
|
|
->addValue('Encoding', '/Identity-H') |
767
|
|
|
->addValue('DescendantFonts', '[' . $this->getReference() . ']') |
768
|
|
|
->addValue('ToUnicode', $this->toUnicode->getReference()); |
769
|
|
|
$font->close(); |
770
|
|
|
|
771
|
|
|
return $font; |
772
|
|
|
} |
773
|
|
|
|
774
|
|
|
/** |
775
|
|
|
* {@inheritdoc} |
776
|
|
|
*/ |
777
|
|
|
public function render(): string |
778
|
|
|
{ |
779
|
|
|
return implode("\n", [$this->getRawId() . ' obj', |
780
|
|
|
'<</Type /Font/Subtype /CIDFontType2', |
781
|
|
|
' /BaseFont /' . $this->getFullName(), |
782
|
|
|
' /FontDescriptor ' . $this->fontDescriptor->getReference(), |
783
|
|
|
' /DW 500', |
784
|
|
|
' /W [' . implode(' ', $this->outputInfo['font']['Widths']) . ' ]', |
785
|
|
|
' /CIDSystemInfo ' . $this->cidSystemInfo->getReference(), |
786
|
|
|
' /CIDToGIDMap ' . $this->cidToGid->getReference(), |
787
|
|
|
'>>', |
788
|
|
|
'endobj', ]); |
789
|
|
|
} |
790
|
|
|
} |
791
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.