Font::render()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 10
c 1
b 0
f 0
dl 0
loc 12
rs 9.9332
cc 1
nc 1
nop 0
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();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->loadFontData() can also be of type FontLib\TrueType\File. However, the property $fontData is declared as type FontLib\Font. Maybe add an additional type check?

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 the id property of an instance of the Account 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.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
230
			$this->document->setFontData($this->family, $this->weight, $this->style, $this->fontData);
0 ignored issues
show
Bug introduced by
It seems like $this->fontData can also be of type null; however, parameter $font of YetiForcePDF\Document::setFontData() does only seem to accept FontLib\TrueType\File, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

230
			$this->document->setFontData($this->family, $this->weight, $this->style, /** @scrutinizer ignore-type */ $this->fontData);
Loading history...
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);
0 ignored issues
show
Bug introduced by
The method getClosestWithUnit() does not exist on YetiForcePDF\Objects\PdfObject. It seems like you code against a sub-type of YetiForcePDF\Objects\PdfObject such as YetiForcePDF\Objects\Font. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

410
		return $this->getParent()->/** @scrutinizer ignore-call */ getClosestWithUnit($unit);
Loading history...
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'));
0 ignored issues
show
Bug introduced by
It seems like mb_convert_encoding($string, 'UCS-4BE', 'UTF-8') can also be of type array; however, parameter $string of unpack() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

429
			$result = unpack('N', /** @scrutinizer ignore-type */ mb_convert_encoding($string, 'UCS-4BE', 'UTF-8'));
Loading history...
430
			if (true === \is_array($result)) {
0 ignored issues
show
introduced by
The condition true === is_array($result) is always true.
Loading history...
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
0 ignored issues
show
Unused Code introduced by
The parameter $text is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

468
	public function getTextHeight(/** @scrutinizer ignore-unused */ string $text = null): string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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)
0 ignored issues
show
Unused Code introduced by
The parameter $charMapUnicode is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

530
	protected function setUpUnicode(/** @scrutinizer ignore-unused */ $charMapUnicode)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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)) {
0 ignored issues
show
introduced by
The condition is_array($decoded) is always true.
Loading history...
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;
0 ignored issues
show
Bug introduced by
The constant ROOT_DIRECTORY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
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