TCPDF_FONTS::getUniord()   C
last analyzed

Complexity

Conditions 15
Paths 25

Size

Total Lines 57
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 15
eloc 41
c 1
b 0
f 0
nc 25
nop 1
dl 0
loc 57
rs 5.9166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
//============================================================+
3
// File name   : tcpdf_fonts.php
4
// Version     : 1.1.1
5
// Begin       : 2008-01-01
6
// Last Update : 2024-12-23
7
// Author      : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - [email protected]
8
// License     : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
9
// -------------------------------------------------------------------
10
// Copyright (C) 2008-2025 Nicola Asuni - Tecnick.com LTD
11
//
12
// This file is part of TCPDF software library.
13
//
14
// TCPDF is free software: you can redistribute it and/or modify it
15
// under the terms of the GNU Lesser General Public License as
16
// published by the Free Software Foundation, either version 3 of the
17
// License, or (at your option) any later version.
18
//
19
// TCPDF is distributed in the hope that it will be useful, but
20
// WITHOUT ANY WARRANTY; without even the implied warranty of
21
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
22
// See the GNU Lesser General Public License for more details.
23
//
24
// You should have received a copy of the GNU Lesser General Public License
25
// along with TCPDF.  If not, see <http://www.gnu.org/licenses/>.
26
//
27
// See LICENSE.TXT file for more information.
28
// -------------------------------------------------------------------
29
//
30
// Description :Font methods for TCPDF library.
31
//
32
//============================================================+
33
34
/**
35
 * @file
36
 * Unicode data and font methods for TCPDF library.
37
 * @author Nicola Asuni
38
 * @package com.tecnick.tcpdf
39
 */
40
41
/**
42
 * @class TCPDF_FONTS
43
 * Font methods for TCPDF library.
44
 * @package com.tecnick.tcpdf
45
 * @version 1.1.1
46
 * @author Nicola Asuni - [email protected]
47
 */
48
class TCPDF_FONTS {
49
50
	/**
51
	 * Static cache used for speed up uniord performances
52
	 * @protected
53
	 */
54
	protected static $cache_uniord = array();
55
56
	/**
57
	 * Convert and add the selected TrueType or Type1 font to the fonts folder (that must be writeable).
58
	 * @param string $fontfile Font file (full path).
59
	 * @param string $fonttype Font type. Leave empty for autodetect mode. Valid values are: TrueTypeUnicode, TrueType, Type1, CID0JP = CID-0 Japanese, CID0KR = CID-0 Korean, CID0CS = CID-0 Chinese Simplified, CID0CT = CID-0 Chinese Traditional.
60
	 * @param string $enc Name of the encoding table to use. Leave empty for default mode. Omit this parameter for TrueType Unicode and symbolic fonts like Symbol or ZapfDingBats.
61
	 * @param int $flags Unsigned 32-bit integer containing flags specifying various characteristics of the font (PDF32000:2008 - 9.8.2 Font Descriptor Flags): +1 for fixed font; +4 for symbol or +32 for non-symbol; +64 for italic. Fixed and Italic mode are generally autodetected so you have to set it to 32 = non-symbolic font (default) or 4 = symbolic font.
62
	 * @param string $outpath Output path for generated font files (must be writeable by the web server). Leave empty for default font folder.
63
	 * @param int $platid Platform ID for CMAP table to extract (when building a Unicode font for Windows this value should be 3, for Macintosh should be 1).
64
	 * @param int $encid Encoding ID for CMAP table to extract (when building a Unicode font for Windows this value should be 1, for Macintosh should be 0). When Platform ID is 3, legal values for Encoding ID are: 0=Symbol, 1=Unicode, 2=ShiftJIS, 3=PRC, 4=Big5, 5=Wansung, 6=Johab, 7=Reserved, 8=Reserved, 9=Reserved, 10=UCS-4.
65
	 * @param boolean $addcbbox If true includes the character bounding box information on the php font file.
66
	 * @param boolean $link If true link to system font instead of copying the font data (not transportable) - Note: do not work with Type1 fonts.
67
	 * @return string|false TCPDF font name or boolean false in case of error.
68
	 * @author Nicola Asuni
69
	 * @since 5.9.123 (2010-09-30)
70
	 * @public static
71
	 */
72
	public static function addTTFfont($fontfile, $fonttype='', $enc='', $flags=32, $outpath='', $platid=3, $encid=1, $addcbbox=false, $link=false) {
73
		if (!TCPDF_STATIC::file_exists($fontfile)) {
74
			// Could not find file
75
			return false;
76
		}
77
		// font metrics
78
		$fmetric = array();
79
		// build new font name for TCPDF compatibility
80
		$font_path_parts = pathinfo($fontfile);
81
		if (!isset($font_path_parts['filename'])) {
82
			$font_path_parts['filename'] = substr($font_path_parts['basename'], 0, -(strlen($font_path_parts['extension']) + 1));
83
		}
84
		$font_name = strtolower($font_path_parts['filename']);
85
		$font_name = preg_replace('/[^a-z0-9_]/', '', $font_name);
86
		$search  = array('bold', 'oblique', 'italic', 'regular');
87
		$replace = array('b', 'i', 'i', '');
88
		$font_name = str_replace($search, $replace, $font_name);
89
		if (empty($font_name)) {
90
			// set generic name
91
			$font_name = 'tcpdffont';
92
		}
93
		// set output path
94
		if (empty($outpath)) {
95
			$outpath = self::_getfontpath();
96
		}
97
		// check if this font already exist
98
		if (@TCPDF_STATIC::file_exists($outpath.$font_name.'.php')) {
99
			// this font already exist (delete it from fonts folder to rebuild it)
100
			return $font_name;
101
		}
102
		$fmetric['file'] = $font_name;
103
		$fmetric['ctg'] = $font_name.'.ctg.z';
104
		// get font data
105
		$font = file_get_contents($fontfile);
106
		$fmetric['originalsize'] = strlen($font);
107
		// autodetect font type
108
		if (empty($fonttype)) {
109
			if (TCPDF_STATIC::_getULONG($font, 0) == 0x10000) {
110
				// True Type (Unicode or not)
111
				$fonttype = 'TrueTypeUnicode';
112
			} elseif (substr($font, 0, 4) == 'OTTO') {
113
				// Open Type (Unicode or not)
114
				//Unsupported font format: OpenType with CFF data
115
				return false;
116
			} else {
117
				// Type 1
118
				$fonttype = 'Type1';
119
			}
120
		}
121
		// set font type
122
		switch ($fonttype) {
123
			case 'CID0CT':
124
			case 'CID0CS':
125
			case 'CID0KR':
126
			case 'CID0JP': {
127
				$fmetric['type'] = 'cidfont0';
128
				break;
129
			}
130
			case 'Type1': {
131
				$fmetric['type'] = 'Type1';
132
				if (empty($enc) AND (($flags & 4) == 0)) {
133
					$enc = 'cp1252';
134
				}
135
				break;
136
			}
137
			case 'TrueType': {
138
				$fmetric['type'] = 'TrueType';
139
				break;
140
			}
141
			case 'TrueTypeUnicode':
142
			default: {
143
				$fmetric['type'] = 'TrueTypeUnicode';
144
				break;
145
			}
146
		}
147
		// set encoding maps (if any)
148
		$fmetric['enc'] = preg_replace('/[^A-Za-z0-9_\-]/', '', $enc);
149
		$fmetric['diff'] = '';
150
		if (($fmetric['type'] == 'TrueType') OR ($fmetric['type'] == 'Type1')) {
151
			if (!empty($enc) AND ($enc != 'cp1252') AND isset(TCPDF_FONT_DATA::$encmap[$enc])) {
152
				// build differences from reference encoding
153
				$enc_ref = TCPDF_FONT_DATA::$encmap['cp1252'];
154
				$enc_target = TCPDF_FONT_DATA::$encmap[$enc];
155
				$last = 0;
156
				for ($i = 32; $i <= 255; ++$i) {
157
					if ($enc_target[$i] != $enc_ref[$i]) {
158
						if ($i != ($last + 1)) {
159
							$fmetric['diff'] .= $i.' ';
160
						}
161
						$last = $i;
162
						$fmetric['diff'] .= '/'.$enc_target[$i].' ';
163
					}
164
				}
165
			}
166
		}
167
		// parse the font by type
168
		if ($fmetric['type'] == 'Type1') {
169
			// ---------- TYPE 1 ----------
170
			// read first segment
171
			$a = unpack('Cmarker/Ctype/Vsize', substr($font, 0, 6));
172
			if ($a['marker'] != 128) {
173
				// Font file is not a valid binary Type1
174
				return false;
175
			}
176
			$fmetric['size1'] = $a['size'];
177
			$data = substr($font, 6, $fmetric['size1']);
178
			// read second segment
179
			$a = unpack('Cmarker/Ctype/Vsize', substr($font, (6 + $fmetric['size1']), 6));
180
			if ($a['marker'] != 128) {
181
				// Font file is not a valid binary Type1
182
				return false;
183
			}
184
			$fmetric['size2'] = $a['size'];
185
			$encrypted = substr($font, (12 + $fmetric['size1']), $fmetric['size2']);
186
			$data .= $encrypted;
187
			// store compressed font
188
			$fmetric['file'] .= '.z';
189
			$fp = TCPDF_STATIC::fopenLocal($outpath.$fmetric['file'], 'wb');
190
			fwrite($fp, gzcompress($data));
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $stream of fwrite() does only seem to accept resource, 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

190
			fwrite(/** @scrutinizer ignore-type */ $fp, gzcompress($data));
Loading history...
191
			fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $stream of fclose() does only seem to accept resource, 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

191
			fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
192
			// get font info
193
			$fmetric['Flags'] = $flags;
194
			preg_match ('#/FullName[\s]*+\(([^\)]*+)#', $font, $matches);
195
			$fmetric['name'] = preg_replace('/[^a-zA-Z0-9_\-]/', '', $matches[1]);
196
			preg_match('#/FontBBox[\s]*+{([^}]*+)#', $font, $matches);
197
			$rawbvl = explode(' ', trim($matches[1]));
198
			$bvl = [(int) $rawbvl[0], (int) $rawbvl[1], (int) $rawbvl[2], (int) $rawbvl[3]];
199
			$fmetric['bbox'] = implode(' ', $bvl);
200
			$fmetric['Ascent'] = $bvl[3];
201
			$fmetric['Descent'] = $bvl[1];
202
			preg_match('#/ItalicAngle[\s]*+([0-9\+\-]*+)#', $font, $matches);
203
			$fmetric['italicAngle'] = intval($matches[1]);
204
			if ($fmetric['italicAngle'] != 0) {
205
				$fmetric['Flags'] |= 64;
206
			}
207
			preg_match('#/UnderlinePosition[\s]*+([0-9\+\-]*+)#', $font, $matches);
208
			$fmetric['underlinePosition'] = intval($matches[1]);
209
			preg_match('#/UnderlineThickness[\s]*+([0-9\+\-]*+)#', $font, $matches);
210
			$fmetric['underlineThickness'] = intval($matches[1]);
211
			preg_match('#/isFixedPitch[\s]*+([^\s]*+)#', $font, $matches);
212
			if ($matches[1] == 'true') {
213
				$fmetric['Flags'] |= 1;
214
			}
215
			// get internal map
216
			$imap = array();
217
			if (preg_match_all('#dup[\s]([0-9]+)[\s]*+/([^\s]*+)[\s]put#sU', $font, $fmap, PREG_SET_ORDER) > 0) {
218
				foreach ($fmap as $v) {
219
					$imap[$v[2]] = $v[1];
220
				}
221
			}
222
			// decrypt eexec encrypted part
223
			$r = 55665; // eexec encryption constant
224
			$c1 = 52845;
225
			$c2 = 22719;
226
			$elen = strlen($encrypted);
227
			$eplain = '';
228
			for ($i = 0; $i < $elen; ++$i) {
229
				$chr = ord($encrypted[$i]);
230
				$eplain .= chr($chr ^ ($r >> 8));
231
				$r = ((($chr + $r) * $c1 + $c2) % 65536);
232
			}
233
			if (preg_match('#/ForceBold[\s]*+([^\s]*+)#', $eplain, $matches) > 0) {
234
				if ($matches[1] == 'true') {
235
					$fmetric['Flags'] |= 0x40000;
236
				}
237
			}
238
			if (preg_match('#/StdVW[\s]*+\[([^\]]*+)#', $eplain, $matches) > 0) {
239
				$fmetric['StemV'] = intval($matches[1]);
240
			} else {
241
				$fmetric['StemV'] = 70;
242
			}
243
			if (preg_match('#/StdHW[\s]*+\[([^\]]*+)#', $eplain, $matches) > 0) {
244
				$fmetric['StemH'] = intval($matches[1]);
245
			} else {
246
				$fmetric['StemH'] = 30;
247
			}
248
			if (preg_match('#/BlueValues[\s]*+\[([^\]]*+)#', $eplain, $matches) > 0) {
249
				$bv = explode(' ', $matches[1]);
250
				if (count($bv) >= 6) {
251
					$v1 = intval($bv[2]);
252
					$v2 = intval($bv[4]);
253
					if ($v1 <= $v2) {
254
						$fmetric['XHeight'] = $v1;
255
						$fmetric['CapHeight'] = $v2;
256
					} else {
257
						$fmetric['XHeight'] = $v2;
258
						$fmetric['CapHeight'] = $v1;
259
					}
260
				} else {
261
					$fmetric['XHeight'] = 450;
262
					$fmetric['CapHeight'] = 700;
263
				}
264
			} else {
265
				$fmetric['XHeight'] = 450;
266
				$fmetric['CapHeight'] = 700;
267
			}
268
			// get the number of random bytes at the beginning of charstrings
269
			if (preg_match('#/lenIV[\s]*+([\d]*+)#', $eplain, $matches) > 0) {
270
				$lenIV = intval($matches[1]);
271
			} else {
272
				$lenIV = 4;
273
			}
274
			$fmetric['Leading'] = 0;
275
			// get charstring data
276
			$eplain = substr($eplain, (strpos($eplain, '/CharStrings') + 1));
277
			preg_match_all('#/([A-Za-z0-9\.]*+)[\s][0-9]+[\s]RD[\s](.*)[\s]ND#sU', $eplain, $matches, PREG_SET_ORDER);
278
			if (!empty($enc) AND isset(TCPDF_FONT_DATA::$encmap[$enc])) {
279
				$enc_map = TCPDF_FONT_DATA::$encmap[$enc];
280
			} else {
281
				$enc_map = false;
282
			}
283
			$fmetric['cw'] = '';
284
			$fmetric['MaxWidth'] = 0;
285
			$cwidths = array();
286
			foreach ($matches as $k => $v) {
287
				$cid = 0;
288
				if (isset($imap[$v[1]])) {
289
					$cid = $imap[$v[1]];
290
				} elseif ($enc_map !== false) {
291
					$cid = array_search($v[1], $enc_map);
292
					if ($cid === false) {
293
						$cid = 0;
294
					} elseif ($cid > 1000) {
295
						$cid -= 1000;
296
					}
297
				}
298
				// decrypt charstring encrypted part
299
				$r = 4330; // charstring encryption constant
300
				$c1 = 52845;
301
				$c2 = 22719;
302
				$cd = $v[2];
303
				$clen = strlen($cd);
304
				$ccom = array();
305
				for ($i = 0; $i < $clen; ++$i) {
306
					$chr = ord($cd[$i]);
307
					$ccom[] = ($chr ^ ($r >> 8));
308
					$r = ((($chr + $r) * $c1 + $c2) % 65536);
309
				}
310
				// decode numbers
311
				$cdec = array();
312
				$ck = 0;
313
				$i = $lenIV;
314
				while ($i < $clen) {
315
					if ($ccom[$i] < 32) {
316
						$cdec[$ck] = $ccom[$i];
317
						if (($ck > 0) AND ($cdec[$ck] == 13)) {
318
							// hsbw command: update width
319
							$cwidths[$cid] = $cdec[($ck - 1)];
320
						}
321
						++$i;
322
					} elseif (($ccom[$i] >= 32) AND ($ccom[$i] <= 246)) {
323
						$cdec[$ck] = ($ccom[$i] - 139);
324
						++$i;
325
					} elseif (($ccom[$i] >= 247) AND ($ccom[$i] <= 250)) {
326
						$cdec[$ck] = ((($ccom[$i] - 247) * 256) + $ccom[($i + 1)] + 108);
327
						$i += 2;
328
					} elseif (($ccom[$i] >= 251) AND ($ccom[$i] <= 254)) {
329
						$cdec[$ck] = ((-($ccom[$i] - 251) * 256) - $ccom[($i + 1)] - 108);
330
						$i += 2;
331
					} elseif ($ccom[$i] == 255) {
332
						$sval = chr($ccom[($i + 1)]).chr($ccom[($i + 2)]).chr($ccom[($i + 3)]).chr($ccom[($i + 4)]);
333
						$vsval = unpack('li', $sval);
334
						$cdec[$ck] = $vsval['i'];
335
						$i += 5;
336
					}
337
					++$ck;
338
				}
339
			} // end for each matches
340
			$fmetric['MissingWidth'] = $cwidths[0];
341
			$fmetric['MaxWidth'] = $fmetric['MissingWidth'];
342
			$fmetric['AvgWidth'] = 0;
343
			// set chars widths
344
			for ($cid = 0; $cid <= 255; ++$cid) {
345
				if (isset($cwidths[$cid])) {
346
					if ($cwidths[$cid] > $fmetric['MaxWidth']) {
347
						$fmetric['MaxWidth'] = $cwidths[$cid];
348
					}
349
					$fmetric['AvgWidth'] += $cwidths[$cid];
350
					$fmetric['cw'] .= ','.$cid.'=>'.$cwidths[$cid];
351
				} else {
352
					$fmetric['cw'] .= ','.$cid.'=>'.$fmetric['MissingWidth'];
353
				}
354
			}
355
			$fmetric['AvgWidth'] = round($fmetric['AvgWidth'] / count($cwidths));
356
		} else {
357
			// ---------- TRUE TYPE ----------
358
			$offset = 0; // offset position of the font data
359
			if (TCPDF_STATIC::_getULONG($font, $offset) != 0x10000) {
360
				// sfnt version must be 0x00010000 for TrueType version 1.0.
361
				return false;
362
			}
363
			if ($fmetric['type'] != 'cidfont0') {
364
				if ($link) {
365
					// creates a symbolic link to the existing font
366
					symlink($fontfile, $outpath.$fmetric['file']);
367
				} else {
368
					// store compressed font
369
					$fmetric['file'] .= '.z';
370
					$fp = TCPDF_STATIC::fopenLocal($outpath.$fmetric['file'], 'wb');
371
					fwrite($fp, gzcompress($font));
372
					fclose($fp);
373
				}
374
			}
375
			$offset += 4;
376
			// get number of tables
377
			$numTables = TCPDF_STATIC::_getUSHORT($font, $offset);
378
			$offset += 2;
379
			// skip searchRange, entrySelector and rangeShift
380
			$offset += 6;
381
			// tables array
382
			$table = array();
383
			// ---------- get tables ----------
384
			for ($i = 0; $i < $numTables; ++$i) {
385
				// get table info
386
				$tag = substr($font, $offset, 4);
387
				$offset += 4;
388
				$table[$tag] = array();
389
				$table[$tag]['checkSum'] = TCPDF_STATIC::_getULONG($font, $offset);
390
				$offset += 4;
391
				$table[$tag]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
392
				$offset += 4;
393
				$table[$tag]['length'] = TCPDF_STATIC::_getULONG($font, $offset);
394
				$offset += 4;
395
			}
396
			// check magicNumber
397
			$offset = $table['head']['offset'] + 12;
398
			if (TCPDF_STATIC::_getULONG($font, $offset) != 0x5F0F3CF5) {
399
				// magicNumber must be 0x5F0F3CF5
400
				return false;
401
			}
402
			$offset += 4;
403
			$offset += 2; // skip flags
404
			// get FUnits
405
			$fmetric['unitsPerEm'] = TCPDF_STATIC::_getUSHORT($font, $offset);
406
			$offset += 2;
407
			// units ratio constant
408
			$urk = (1000 / $fmetric['unitsPerEm']);
409
			$offset += 16; // skip created, modified
410
			$xMin = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
411
			$offset += 2;
412
			$yMin = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
413
			$offset += 2;
414
			$xMax = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
415
			$offset += 2;
416
			$yMax = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
417
			$offset += 2;
418
			$fmetric['bbox'] = ''.$xMin.' '.$yMin.' '.$xMax.' '.$yMax.'';
419
			$macStyle = TCPDF_STATIC::_getUSHORT($font, $offset);
420
			$offset += 2;
421
			// PDF font flags
422
			$fmetric['Flags'] = $flags;
423
			if (($macStyle & 2) == 2) {
424
				// italic flag
425
				$fmetric['Flags'] |= 64;
426
			}
427
			// get offset mode (indexToLocFormat : 0 = short, 1 = long)
428
			$offset = $table['head']['offset'] + 50;
429
			$short_offset = (TCPDF_STATIC::_getSHORT($font, $offset) == 0);
430
			$offset += 2;
431
			// get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
432
			$indexToLoc = array();
433
			$offset = $table['loca']['offset'];
434
			if ($short_offset) {
435
				// short version
436
				$tot_num_glyphs = floor($table['loca']['length'] / 2); // numGlyphs + 1
437
				for ($i = 0; $i < $tot_num_glyphs; ++$i) {
438
					$indexToLoc[$i] = TCPDF_STATIC::_getUSHORT($font, $offset) * 2;
439
					if (isset($indexToLoc[($i - 1)]) && ($indexToLoc[$i] == $indexToLoc[($i - 1)])) {
440
						// the last glyph didn't have an outline
441
						unset($indexToLoc[($i - 1)]);
442
					}
443
					$offset += 2;
444
				}
445
			} else {
446
				// long version
447
				$tot_num_glyphs = floor($table['loca']['length'] / 4); // numGlyphs + 1
448
				for ($i = 0; $i < $tot_num_glyphs; ++$i) {
449
					$indexToLoc[$i] = TCPDF_STATIC::_getULONG($font, $offset);
450
					if (isset($indexToLoc[($i - 1)]) && ($indexToLoc[$i] == $indexToLoc[($i - 1)])) {
451
						// the last glyph didn't have an outline
452
						unset($indexToLoc[($i - 1)]);
453
					}
454
					$offset += 4;
455
				}
456
			}
457
			// get glyphs indexes of chars from cmap table
458
			$offset = $table['cmap']['offset'] + 2;
459
			$numEncodingTables = TCPDF_STATIC::_getUSHORT($font, $offset);
460
			$offset += 2;
461
			$encodingTables = array();
462
			for ($i = 0; $i < $numEncodingTables; ++$i) {
463
				$encodingTables[$i]['platformID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
464
				$offset += 2;
465
				$encodingTables[$i]['encodingID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
466
				$offset += 2;
467
				$encodingTables[$i]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
468
				$offset += 4;
469
			}
470
			// ---------- get os/2 metrics ----------
471
			$offset = $table['OS/2']['offset'];
472
			$offset += 2; // skip version
473
			// xAvgCharWidth
474
			$fmetric['AvgWidth'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
475
			$offset += 2;
476
			// usWeightClass
477
			$usWeightClass = round(TCPDF_STATIC::_getUFWORD($font, $offset) * $urk);
478
			// estimate StemV and StemH (400 = usWeightClass for Normal - Regular font)
479
			$fmetric['StemV'] = round((70 * $usWeightClass) / 400);
480
			$fmetric['StemH'] = round((30 * $usWeightClass) / 400);
481
			$offset += 2;
482
			$offset += 2; // usWidthClass
483
			$fsType = TCPDF_STATIC::_getSHORT($font, $offset);
484
			$offset += 2;
485
			if ($fsType == 2) {
486
				// This Font cannot be modified, embedded or exchanged in any manner without first obtaining permission of the legal owner.
487
				return false;
488
			}
489
			// ---------- get font name ----------
490
			$fmetric['name'] = '';
491
			$offset = $table['name']['offset'];
492
			$offset += 2; // skip Format selector (=0).
493
			// Number of NameRecords that follow n.
494
			$numNameRecords = TCPDF_STATIC::_getUSHORT($font, $offset);
495
			$offset += 2;
496
			// Offset to start of string storage (from start of table).
497
			$stringStorageOffset = TCPDF_STATIC::_getUSHORT($font, $offset);
498
			$offset += 2;
499
			for ($i = 0; $i < $numNameRecords; ++$i) {
500
				$offset += 6; // skip Platform ID, Platform-specific encoding ID, Language ID.
501
				// Name ID.
502
				$nameID = TCPDF_STATIC::_getUSHORT($font, $offset);
503
				$offset += 2;
504
				if ($nameID == 6) {
505
					// String length (in bytes).
506
					$stringLength = TCPDF_STATIC::_getUSHORT($font, $offset);
507
					$offset += 2;
508
					// String offset from start of storage area (in bytes).
509
					$stringOffset = TCPDF_STATIC::_getUSHORT($font, $offset);
510
					$offset += 2;
511
					$offset = ($table['name']['offset'] + $stringStorageOffset + $stringOffset);
512
					$fmetric['name'] = substr($font, $offset, $stringLength);
513
					$fmetric['name'] = preg_replace('/[^a-zA-Z0-9_\-]/', '', $fmetric['name']);
514
					break;
515
				} else {
516
					$offset += 4; // skip String length, String offset
517
				}
518
			}
519
			if (empty($fmetric['name'])) {
520
				$fmetric['name'] = $font_name;
521
			}
522
			// ---------- get post data ----------
523
			$offset = $table['post']['offset'];
524
			$offset += 4; // skip Format Type
525
			$fmetric['italicAngle'] = TCPDF_STATIC::_getFIXED($font, $offset);
526
			$offset += 4;
527
			$fmetric['underlinePosition'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
528
			$offset += 2;
529
			$fmetric['underlineThickness'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
530
			$offset += 2;
531
			$isFixedPitch = (TCPDF_STATIC::_getULONG($font, $offset) == 0) ? false : true;
532
			$offset += 2;
533
			if ($isFixedPitch) {
534
				$fmetric['Flags'] |= 1;
535
			}
536
			// ---------- get hhea data ----------
537
			$offset = $table['hhea']['offset'];
538
			$offset += 4; // skip Table version number
539
			// Ascender
540
			$fmetric['Ascent'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
541
			$offset += 2;
542
			// Descender
543
			$fmetric['Descent'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
544
			$offset += 2;
545
			// LineGap
546
			$fmetric['Leading'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
547
			$offset += 2;
548
			// advanceWidthMax
549
			$fmetric['MaxWidth'] = round(TCPDF_STATIC::_getUFWORD($font, $offset) * $urk);
550
			$offset += 2;
551
			$offset += 22; // skip some values
552
			// get the number of hMetric entries in hmtx table
553
			$numberOfHMetrics = TCPDF_STATIC::_getUSHORT($font, $offset);
554
			// ---------- get maxp data ----------
555
			$offset = $table['maxp']['offset'];
556
			$offset += 4; // skip Table version number
557
			// get the the number of glyphs in the font.
558
			$numGlyphs = TCPDF_STATIC::_getUSHORT($font, $offset);
559
			// ---------- get CIDToGIDMap ----------
560
			$ctg = array();
561
			$c = 0;
562
			foreach ($encodingTables as $enctable) {
563
				// get only specified Platform ID and Encoding ID
564
				if (($enctable['platformID'] == $platid) AND ($enctable['encodingID'] == $encid)) {
565
					$offset = $table['cmap']['offset'] + $enctable['offset'];
566
					$format = TCPDF_STATIC::_getUSHORT($font, $offset);
567
					$offset += 2;
568
					switch ($format) {
569
						case 0: { // Format 0: Byte encoding table
570
							$offset += 4; // skip length and version/language
571
							for ($c = 0; $c < 256; ++$c) {
572
								$g = TCPDF_STATIC::_getBYTE($font, $offset);
573
								$ctg[$c] = $g;
574
								++$offset;
575
							}
576
							break;
577
						}
578
						case 2: { // Format 2: High-byte mapping through table
579
							$offset += 4; // skip length and version/language
580
							$numSubHeaders = 0;
581
							for ($i = 0; $i < 256; ++$i) {
582
								// Array that maps high bytes to subHeaders: value is subHeader index * 8.
583
								$subHeaderKeys[$i] = (TCPDF_STATIC::_getUSHORT($font, $offset) / 8);
584
								$offset += 2;
585
								if ($numSubHeaders < $subHeaderKeys[$i]) {
586
									$numSubHeaders = $subHeaderKeys[$i];
587
								}
588
							}
589
							// the number of subHeaders is equal to the max of subHeaderKeys + 1
590
							++$numSubHeaders;
591
							// read subHeader structures
592
							$subHeaders = array();
593
							$numGlyphIndexArray = 0;
594
							for ($k = 0; $k < $numSubHeaders; ++$k) {
595
								$subHeaders[$k]['firstCode'] = TCPDF_STATIC::_getUSHORT($font, $offset);
596
								$offset += 2;
597
								$subHeaders[$k]['entryCount'] = TCPDF_STATIC::_getUSHORT($font, $offset);
598
								$offset += 2;
599
								$subHeaders[$k]['idDelta'] = TCPDF_STATIC::_getUSHORT($font, $offset);
600
								$offset += 2;
601
								$subHeaders[$k]['idRangeOffset'] = TCPDF_STATIC::_getUSHORT($font, $offset);
602
								$offset += 2;
603
								$subHeaders[$k]['idRangeOffset'] -= (2 + (($numSubHeaders - $k - 1) * 8));
604
								$subHeaders[$k]['idRangeOffset'] /= 2;
605
								$numGlyphIndexArray += $subHeaders[$k]['entryCount'];
606
							}
607
							for ($k = 0; $k < $numGlyphIndexArray; ++$k) {
608
								$glyphIndexArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
609
								$offset += 2;
610
							}
611
							for ($i = 0; $i < 256; ++$i) {
612
								$k = $subHeaderKeys[$i];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $subHeaderKeys does not seem to be defined for all execution paths leading up to this point.
Loading history...
613
								if ($k == 0) {
614
									// one byte code
615
									$c = $i;
616
									$g = $glyphIndexArray[0];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $glyphIndexArray does not seem to be defined for all execution paths leading up to this point.
Loading history...
617
									$ctg[$c] = $g;
618
								} else {
619
									// two bytes code
620
									$start_byte = $subHeaders[$k]['firstCode'];
621
									$end_byte = $start_byte + $subHeaders[$k]['entryCount'];
622
									for ($j = $start_byte; $j < $end_byte; ++$j) {
623
										// combine high and low bytes
624
										$c = (($i << 8) + $j);
625
										$idRangeOffset = ($subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode']);
626
										$g = ($glyphIndexArray[$idRangeOffset] + $subHeaders[$k]['idDelta']) % 65536;
627
										if ($g < 0) {
628
											$g = 0;
629
										}
630
										$ctg[$c] = $g;
631
									}
632
								}
633
							}
634
							break;
635
						}
636
						case 4: { // Format 4: Segment mapping to delta values
637
							$length = TCPDF_STATIC::_getUSHORT($font, $offset);
638
							$offset += 2;
639
							$offset += 2; // skip version/language
640
							$segCount = floor(TCPDF_STATIC::_getUSHORT($font, $offset) / 2);
641
							$offset += 2;
642
							$offset += 6; // skip searchRange, entrySelector, rangeShift
643
							$endCount = array(); // array of end character codes for each segment
644
							for ($k = 0; $k < $segCount; ++$k) {
645
								$endCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
646
								$offset += 2;
647
							}
648
							$offset += 2; // skip reservedPad
649
							$startCount = array(); // array of start character codes for each segment
650
							for ($k = 0; $k < $segCount; ++$k) {
651
								$startCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
652
								$offset += 2;
653
							}
654
							$idDelta = array(); // delta for all character codes in segment
655
							for ($k = 0; $k < $segCount; ++$k) {
656
								$idDelta[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
657
								$offset += 2;
658
							}
659
							$idRangeOffset = array(); // Offsets into glyphIdArray or 0
660
							for ($k = 0; $k < $segCount; ++$k) {
661
								$idRangeOffset[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
662
								$offset += 2;
663
							}
664
							$gidlen = (floor($length / 2) - 8 - (4 * $segCount));
665
							$glyphIdArray = array(); // glyph index array
666
							for ($k = 0; $k < $gidlen; ++$k) {
667
								$glyphIdArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
668
								$offset += 2;
669
							}
670
							for ($k = 0; $k < $segCount - 1; ++$k) {
671
								for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
672
									if ($idRangeOffset[$k] == 0) {
673
										$g = ($idDelta[$k] + $c) % 65536;
674
									} else {
675
										$gid = (floor($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k));
676
										$g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536;
677
									}
678
									if ($g < 0) {
679
										$g = 0;
680
									}
681
									$ctg[$c] = $g;
682
								}
683
							}
684
							break;
685
						}
686
						case 6: { // Format 6: Trimmed table mapping
687
							$offset += 4; // skip length and version/language
688
							$firstCode = TCPDF_STATIC::_getUSHORT($font, $offset);
689
							$offset += 2;
690
							$entryCount = TCPDF_STATIC::_getUSHORT($font, $offset);
691
							$offset += 2;
692
							for ($k = 0; $k < $entryCount; ++$k) {
693
								$c = ($k + $firstCode);
694
								$g = TCPDF_STATIC::_getUSHORT($font, $offset);
695
								$offset += 2;
696
								$ctg[$c] = $g;
697
							}
698
							break;
699
						}
700
						case 8: { // Format 8: Mixed 16-bit and 32-bit coverage
701
							$offset += 10; // skip reserved, length and version/language
702
							for ($k = 0; $k < 8192; ++$k) {
703
								$is32[$k] = TCPDF_STATIC::_getBYTE($font, $offset);
704
								++$offset;
705
							}
706
							$nGroups = TCPDF_STATIC::_getULONG($font, $offset);
707
							$offset += 4;
708
							for ($i = 0; $i < $nGroups; ++$i) {
709
								$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
710
								$offset += 4;
711
								$endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
712
								$offset += 4;
713
								$startGlyphID = TCPDF_STATIC::_getULONG($font, $offset);
714
								$offset += 4;
715
								for ($k = $startCharCode; $k <= $endCharCode; ++$k) {
716
									$is32idx = floor($c / 8);
717
									if ((isset($is32[$is32idx])) AND (($is32[$is32idx] & (1 << (7 - ($c % 8)))) == 0)) {
718
										$c = $k;
719
									} else {
720
										// 32 bit format
721
										// convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4)
722
										//LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232
723
										//SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888
724
										$c = ((55232 + ($k >> 10)) << 10) + (0xDC00 + ($k & 0x3FF)) -56613888;
725
									}
726
									$ctg[$c] = 0;
727
									++$startGlyphID;
728
								}
729
							}
730
							break;
731
						}
732
						case 10: { // Format 10: Trimmed array
733
							$offset += 10; // skip reserved, length and version/language
734
							$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
735
							$offset += 4;
736
							$numChars = TCPDF_STATIC::_getULONG($font, $offset);
737
							$offset += 4;
738
							for ($k = 0; $k < $numChars; ++$k) {
739
								$c = ($k + $startCharCode);
740
								$g = TCPDF_STATIC::_getUSHORT($font, $offset);
741
								$ctg[$c] = $g;
742
								$offset += 2;
743
							}
744
							break;
745
						}
746
						case 12: { // Format 12: Segmented coverage
747
							$offset += 10; // skip length and version/language
748
							$nGroups = TCPDF_STATIC::_getULONG($font, $offset);
749
							$offset += 4;
750
							for ($k = 0; $k < $nGroups; ++$k) {
751
								$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
752
								$offset += 4;
753
								$endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
754
								$offset += 4;
755
								$startGlyphCode = TCPDF_STATIC::_getULONG($font, $offset);
756
								$offset += 4;
757
								for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
758
									$ctg[$c] = $startGlyphCode;
759
									++$startGlyphCode;
760
								}
761
							}
762
							break;
763
						}
764
						case 13: { // Format 13: Many-to-one range mappings
765
							// to be implemented ...
766
							break;
767
						}
768
						case 14: { // Format 14: Unicode Variation Sequences
769
							// to be implemented ...
770
							break;
771
						}
772
					}
773
				}
774
			}
775
			if (!isset($ctg[0])) {
776
				$ctg[0] = 0;
777
			}
778
			// get xHeight (height of x)
779
			$offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[120]] + 4);
780
			$yMin = TCPDF_STATIC::_getFWORD($font, $offset);
781
			$offset += 4;
782
			$yMax = TCPDF_STATIC::_getFWORD($font, $offset);
783
			$offset += 2;
784
			$fmetric['XHeight'] = round(($yMax - $yMin) * $urk);
785
			// get CapHeight (height of H)
786
			$offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[72]] + 4);
787
			$yMin = TCPDF_STATIC::_getFWORD($font, $offset);
788
			$offset += 4;
789
			$yMax = TCPDF_STATIC::_getFWORD($font, $offset);
790
			$offset += 2;
791
			$fmetric['CapHeight'] = round(($yMax - $yMin) * $urk);
792
			// ceate widths array
793
			$cw = array();
794
			$offset = $table['hmtx']['offset'];
795
			for ($i = 0 ; $i < $numberOfHMetrics; ++$i) {
796
				$cw[$i] = round(TCPDF_STATIC::_getUFWORD($font, $offset) * $urk);
797
				$offset += 4; // skip lsb
798
			}
799
			if ($numberOfHMetrics < $numGlyphs) {
800
				// fill missing widths with the last value
801
				$cw = array_pad($cw, $numGlyphs, $cw[($numberOfHMetrics - 1)]);
802
			}
803
			$fmetric['MissingWidth'] = $cw[0];
804
			$fmetric['cw'] = '';
805
			$fmetric['cbbox'] = '';
806
			for ($cid = 0; $cid <= 65535; ++$cid) {
807
				if (isset($ctg[$cid])) {
808
					if (isset($cw[$ctg[$cid]])) {
809
						$fmetric['cw'] .= ','.$cid.'=>'.$cw[$ctg[$cid]];
810
					}
811
					if ($addcbbox AND isset($indexToLoc[$ctg[$cid]])) {
812
						$offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[$cid]]);
813
						$xMin = round(TCPDF_STATIC::_getFWORD($font, $offset + 2) * $urk);
814
						$yMin = round(TCPDF_STATIC::_getFWORD($font, $offset + 4) * $urk);
815
						$xMax = round(TCPDF_STATIC::_getFWORD($font, $offset + 6) * $urk);
816
						$yMax = round(TCPDF_STATIC::_getFWORD($font, $offset + 8) * $urk);
817
						$fmetric['cbbox'] .= ','.$cid.'=>array('.$xMin.','.$yMin.','.$xMax.','.$yMax.')';
818
					}
819
				}
820
			}
821
		} // end of true type
822
		if (($fmetric['type'] == 'TrueTypeUnicode') AND (count($ctg) == 256)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ctg does not seem to be defined for all execution paths leading up to this point.
Loading history...
823
			$fmetric['type'] = 'TrueType';
824
		}
825
		// ---------- create php font file ----------
826
		$pfile = '<'.'?'.'php'."\n";
827
		$pfile .= '// TCPDF FONT FILE DESCRIPTION'."\n";
828
		$pfile .= '$type=\''.$fmetric['type'].'\';'."\n";
829
		$pfile .= '$name=\''.$fmetric['name'].'\';'."\n";
830
		$pfile .= '$up='.$fmetric['underlinePosition'].';'."\n";
831
		$pfile .= '$ut='.$fmetric['underlineThickness'].';'."\n";
832
		if ($fmetric['MissingWidth'] > 0) {
833
			$pfile .= '$dw='.$fmetric['MissingWidth'].';'."\n";
834
		} else {
835
			$pfile .= '$dw='.$fmetric['AvgWidth'].';'."\n";
836
		}
837
		$pfile .= '$diff=\''.$fmetric['diff'].'\';'."\n";
838
		if ($fmetric['type'] == 'Type1') {
839
			// Type 1
840
			$pfile .= '$enc=\''.$fmetric['enc'].'\';'."\n";
841
			$pfile .= '$file=\''.$fmetric['file'].'\';'."\n";
842
			$pfile .= '$size1='.$fmetric['size1'].';'."\n";
843
			$pfile .= '$size2='.$fmetric['size2'].';'."\n";
844
		} else {
845
			$pfile .= '$originalsize='.$fmetric['originalsize'].';'."\n";
846
			if ($fmetric['type'] == 'cidfont0') {
847
				// CID-0
848
				switch ($fonttype) {
849
					case 'CID0JP': {
850
						$pfile .= '// Japanese'."\n";
851
						$pfile .= '$enc=\'UniJIS-UTF16-H\';'."\n";
852
						$pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Japan1\',\'Supplement\'=>5);'."\n";
853
						$pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');'."\n";
854
						break;
855
					}
856
					case 'CID0KR': {
857
						$pfile .= '// Korean'."\n";
858
						$pfile .= '$enc=\'UniKS-UTF16-H\';'."\n";
859
						$pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Korea1\',\'Supplement\'=>0);'."\n";
860
						$pfile .= 'include(dirname(__FILE__).\'/uni2cid_ak12.php\');'."\n";
861
						break;
862
					}
863
					case 'CID0CS': {
864
						$pfile .= '// Chinese Simplified'."\n";
865
						$pfile .= '$enc=\'UniGB-UTF16-H\';'."\n";
866
						$pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'GB1\',\'Supplement\'=>2);'."\n";
867
						$pfile .= 'include(dirname(__FILE__).\'/uni2cid_ag15.php\');'."\n";
868
						break;
869
					}
870
					case 'CID0CT':
871
					default: {
872
						$pfile .= '// Chinese Traditional'."\n";
873
						$pfile .= '$enc=\'UniCNS-UTF16-H\';'."\n";
874
						$pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'CNS1\',\'Supplement\'=>0);'."\n";
875
						$pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');'."\n";
876
						break;
877
					}
878
				}
879
			} else {
880
				// TrueType
881
				$pfile .= '$enc=\''.$fmetric['enc'].'\';'."\n";
882
				$pfile .= '$file=\''.$fmetric['file'].'\';'."\n";
883
				$pfile .= '$ctg=\''.$fmetric['ctg'].'\';'."\n";
884
				// create CIDToGIDMap
885
				$cidtogidmap = str_pad('', 131072, "\x00"); // (256 * 256 * 2) = 131072
886
				foreach ($ctg as $cid => $gid) {
887
					$cidtogidmap = self::updateCIDtoGIDmap($cidtogidmap, $cid, $ctg[$cid]);
888
				}
889
				// store compressed CIDToGIDMap
890
				$fp = TCPDF_STATIC::fopenLocal($outpath.$fmetric['ctg'], 'wb');
891
				fwrite($fp, gzcompress($cidtogidmap));
892
				fclose($fp);
893
			}
894
		}
895
		$pfile .= '$desc=array(';
896
		$pfile .= '\'Flags\'=>'.$fmetric['Flags'].',';
897
		$pfile .= '\'FontBBox\'=>\'['.$fmetric['bbox'].']\',';
898
		$pfile .= '\'ItalicAngle\'=>'.$fmetric['italicAngle'].',';
899
		$pfile .= '\'Ascent\'=>'.$fmetric['Ascent'].',';
900
		$pfile .= '\'Descent\'=>'.$fmetric['Descent'].',';
901
		$pfile .= '\'Leading\'=>'.$fmetric['Leading'].',';
902
		$pfile .= '\'CapHeight\'=>'.$fmetric['CapHeight'].',';
903
		$pfile .= '\'XHeight\'=>'.$fmetric['XHeight'].',';
904
		$pfile .= '\'StemV\'=>'.$fmetric['StemV'].',';
905
		$pfile .= '\'StemH\'=>'.$fmetric['StemH'].',';
906
		$pfile .= '\'AvgWidth\'=>'.$fmetric['AvgWidth'].',';
907
		$pfile .= '\'MaxWidth\'=>'.$fmetric['MaxWidth'].',';
908
		$pfile .= '\'MissingWidth\'=>'.$fmetric['MissingWidth'].'';
909
		$pfile .= ');'."\n";
910
		if (!empty($fmetric['cbbox'])) {
911
			$pfile .= '$cbbox=array('.substr($fmetric['cbbox'], 1).');'."\n";
912
		}
913
		$pfile .= '$cw=array('.substr($fmetric['cw'], 1).');'."\n";
914
		$pfile .= '// --- EOF ---'."\n";
915
		// store file
916
		$fp = TCPDF_STATIC::fopenLocal($outpath.$font_name.'.php', 'w');
917
		fwrite($fp, $pfile);
918
		fclose($fp);
919
		// return TCPDF font name
920
		return $font_name;
921
	}
922
923
	/**
924
	 * Returs the checksum of a TTF table.
925
	 * @param string $table table to check
926
	 * @param int $length length of table in bytes
927
	 * @return int checksum
928
	 * @author Nicola Asuni
929
	 * @since 5.2.000 (2010-06-02)
930
	 * @public static
931
	 */
932
	public static function _getTTFtableChecksum($table, $length) {
933
		$sum = 0;
934
		$tlen = ($length / 4);
935
		$offset = 0;
936
		for ($i = 0; $i < $tlen; ++$i) {
937
			$v = unpack('Ni', substr($table, $offset, 4));
938
			$sum += $v['i'];
939
			$offset += 4;
940
		}
941
		$sum = unpack('Ni', pack('N', $sum));
942
		return $sum['i'];
943
	}
944
945
	/**
946
	 * Returns a subset of the TrueType font data without the unused glyphs.
947
	 * @param string $font TrueType font data.
948
	 * @param array $subsetchars Array of used characters (the glyphs to keep).
949
	 * @return string A subset of TrueType font data without the unused glyphs.
950
	 * @author Nicola Asuni
951
	 * @since 5.2.000 (2010-06-02)
952
	 * @public static
953
	 */
954
	public static function _getTrueTypeFontSubset($font, $subsetchars) {
955
		ksort($subsetchars);
956
		$offset = 0; // offset position of the font data
957
		if (TCPDF_STATIC::_getULONG($font, $offset) != 0x10000) {
958
			// sfnt version must be 0x00010000 for TrueType version 1.0.
959
			return $font;
960
		}
961
		$c = 0;
962
		$offset += 4;
963
		// get number of tables
964
		$numTables = TCPDF_STATIC::_getUSHORT($font, $offset);
965
		$offset += 2;
966
		// skip searchRange, entrySelector and rangeShift
967
		$offset += 6;
968
		// tables array
969
		$table = array();
970
		// for each table
971
		for ($i = 0; $i < $numTables; ++$i) {
972
			// get table info
973
			$tag = substr($font, $offset, 4);
974
			$offset += 4;
975
			$table[$tag] = array();
976
			$table[$tag]['checkSum'] = TCPDF_STATIC::_getULONG($font, $offset);
977
			$offset += 4;
978
			$table[$tag]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
979
			$offset += 4;
980
			$table[$tag]['length'] = TCPDF_STATIC::_getULONG($font, $offset);
981
			$offset += 4;
982
		}
983
		// check magicNumber
984
		$offset = $table['head']['offset'] + 12;
985
		if (TCPDF_STATIC::_getULONG($font, $offset) != 0x5F0F3CF5) {
986
			// magicNumber must be 0x5F0F3CF5
987
			return $font;
988
		}
989
		$offset += 4;
990
		// get offset mode (indexToLocFormat : 0 = short, 1 = long)
991
		$offset = $table['head']['offset'] + 50;
992
		$short_offset = (TCPDF_STATIC::_getSHORT($font, $offset) == 0);
993
		$offset += 2;
994
		// get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
995
		$indexToLoc = array();
996
		$offset = $table['loca']['offset'];
997
		if ($short_offset) {
998
			// short version
999
			$tot_num_glyphs = floor($table['loca']['length'] / 2); // numGlyphs + 1
1000
			for ($i = 0; $i < $tot_num_glyphs; ++$i) {
1001
				$indexToLoc[$i] = TCPDF_STATIC::_getUSHORT($font, $offset) * 2;
1002
				$offset += 2;
1003
			}
1004
		} else {
1005
			// long version
1006
			$tot_num_glyphs = ($table['loca']['length'] / 4); // numGlyphs + 1
1007
			for ($i = 0; $i < $tot_num_glyphs; ++$i) {
1008
				$indexToLoc[$i] = TCPDF_STATIC::_getULONG($font, $offset);
1009
				$offset += 4;
1010
			}
1011
		}
1012
		// get glyphs indexes of chars from cmap table
1013
		$subsetglyphs = array(); // glyph IDs on key
1014
		$subsetglyphs[0] = true; // character codes that do not correspond to any glyph in the font should be mapped to glyph index 0
1015
		$offset = $table['cmap']['offset'] + 2;
1016
		$numEncodingTables = TCPDF_STATIC::_getUSHORT($font, $offset);
1017
		$offset += 2;
1018
		$encodingTables = array();
1019
		for ($i = 0; $i < $numEncodingTables; ++$i) {
1020
			$encodingTables[$i]['platformID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
1021
			$offset += 2;
1022
			$encodingTables[$i]['encodingID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
1023
			$offset += 2;
1024
			$encodingTables[$i]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
1025
			$offset += 4;
1026
		}
1027
		foreach ($encodingTables as $enctable) {
1028
			// get all platforms and encodings
1029
			$offset = $table['cmap']['offset'] + $enctable['offset'];
1030
			$format = TCPDF_STATIC::_getUSHORT($font, $offset);
1031
			$offset += 2;
1032
			switch ($format) {
1033
				case 0: { // Format 0: Byte encoding table
1034
					$offset += 4; // skip length and version/language
1035
					for ($c = 0; $c < 256; ++$c) {
1036
						if (isset($subsetchars[$c])) {
1037
							$g = TCPDF_STATIC::_getBYTE($font, $offset);
1038
							$subsetglyphs[$g] = true;
1039
						}
1040
						++$offset;
1041
					}
1042
					break;
1043
				}
1044
				case 2: { // Format 2: High-byte mapping through table
1045
					$offset += 4; // skip length and version/language
1046
					$numSubHeaders = 0;
1047
					for ($i = 0; $i < 256; ++$i) {
1048
						// Array that maps high bytes to subHeaders: value is subHeader index * 8.
1049
						$subHeaderKeys[$i] = (TCPDF_STATIC::_getUSHORT($font, $offset) / 8);
1050
						$offset += 2;
1051
						if ($numSubHeaders < $subHeaderKeys[$i]) {
1052
							$numSubHeaders = $subHeaderKeys[$i];
1053
						}
1054
					}
1055
					// the number of subHeaders is equal to the max of subHeaderKeys + 1
1056
					++$numSubHeaders;
1057
					// read subHeader structures
1058
					$subHeaders = array();
1059
					$numGlyphIndexArray = 0;
1060
					for ($k = 0; $k < $numSubHeaders; ++$k) {
1061
						$subHeaders[$k]['firstCode'] = TCPDF_STATIC::_getUSHORT($font, $offset);
1062
						$offset += 2;
1063
						$subHeaders[$k]['entryCount'] = TCPDF_STATIC::_getUSHORT($font, $offset);
1064
						$offset += 2;
1065
						$subHeaders[$k]['idDelta'] = TCPDF_STATIC::_getUSHORT($font, $offset);
1066
						$offset += 2;
1067
						$subHeaders[$k]['idRangeOffset'] = TCPDF_STATIC::_getUSHORT($font, $offset);
1068
						$offset += 2;
1069
						$subHeaders[$k]['idRangeOffset'] -= (2 + (($numSubHeaders - $k - 1) * 8));
1070
						$subHeaders[$k]['idRangeOffset'] /= 2;
1071
						$numGlyphIndexArray += $subHeaders[$k]['entryCount'];
1072
					}
1073
					for ($k = 0; $k < $numGlyphIndexArray; ++$k) {
1074
						$glyphIndexArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
1075
						$offset += 2;
1076
					}
1077
					for ($i = 0; $i < 256; ++$i) {
1078
						$k = $subHeaderKeys[$i];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $subHeaderKeys does not seem to be defined for all execution paths leading up to this point.
Loading history...
1079
						if ($k == 0) {
1080
							// one byte code
1081
							$c = $i;
1082
							if (isset($subsetchars[$c])) {
1083
								$g = $glyphIndexArray[0];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $glyphIndexArray does not seem to be defined for all execution paths leading up to this point.
Loading history...
1084
								$subsetglyphs[$g] = true;
1085
							}
1086
						} else {
1087
							// two bytes code
1088
							$start_byte = $subHeaders[$k]['firstCode'];
1089
							$end_byte = $start_byte + $subHeaders[$k]['entryCount'];
1090
							for ($j = $start_byte; $j < $end_byte; ++$j) {
1091
								// combine high and low bytes
1092
								$c = (($i << 8) + $j);
1093
								if (isset($subsetchars[$c])) {
1094
									$idRangeOffset = ($subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode']);
1095
									$g = ($glyphIndexArray[$idRangeOffset] + $subHeaders[$k]['idDelta']) % 65536;
1096
									if ($g < 0) {
1097
										$g = 0;
1098
									}
1099
									$subsetglyphs[$g] = true;
1100
								}
1101
							}
1102
						}
1103
					}
1104
					break;
1105
				}
1106
				case 4: { // Format 4: Segment mapping to delta values
1107
					$length = TCPDF_STATIC::_getUSHORT($font, $offset);
1108
					$offset += 2;
1109
					$offset += 2; // skip version/language
1110
					$segCount = floor(TCPDF_STATIC::_getUSHORT($font, $offset) / 2);
1111
					$offset += 2;
1112
					$offset += 6; // skip searchRange, entrySelector, rangeShift
1113
					$endCount = array(); // array of end character codes for each segment
1114
					for ($k = 0; $k < $segCount; ++$k) {
1115
						$endCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
1116
						$offset += 2;
1117
					}
1118
					$offset += 2; // skip reservedPad
1119
					$startCount = array(); // array of start character codes for each segment
1120
					for ($k = 0; $k < $segCount; ++$k) {
1121
						$startCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
1122
						$offset += 2;
1123
					}
1124
					$idDelta = array(); // delta for all character codes in segment
1125
					for ($k = 0; $k < $segCount; ++$k) {
1126
						$idDelta[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
1127
						$offset += 2;
1128
					}
1129
					$idRangeOffset = array(); // Offsets into glyphIdArray or 0
1130
					for ($k = 0; $k < $segCount; ++$k) {
1131
						$idRangeOffset[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
1132
						$offset += 2;
1133
					}
1134
					$gidlen = (floor($length / 2) - 8 - (4 * $segCount));
1135
					$glyphIdArray = array(); // glyph index array
1136
					for ($k = 0; $k < $gidlen; ++$k) {
1137
						$glyphIdArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
1138
						$offset += 2;
1139
					}
1140
					for ($k = 0; $k < $segCount; ++$k) {
1141
						for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
1142
							if (isset($subsetchars[$c])) {
1143
								if ($idRangeOffset[$k] == 0) {
1144
									$g = ($idDelta[$k] + $c) % 65536;
1145
								} else {
1146
									$gid = (floor($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k));
1147
									$g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536;
1148
								}
1149
								if ($g < 0) {
1150
									$g = 0;
1151
								}
1152
								$subsetglyphs[$g] = true;
1153
							}
1154
						}
1155
					}
1156
					break;
1157
				}
1158
				case 6: { // Format 6: Trimmed table mapping
1159
					$offset += 4; // skip length and version/language
1160
					$firstCode = TCPDF_STATIC::_getUSHORT($font, $offset);
1161
					$offset += 2;
1162
					$entryCount = TCPDF_STATIC::_getUSHORT($font, $offset);
1163
					$offset += 2;
1164
					for ($k = 0; $k < $entryCount; ++$k) {
1165
						$c = ($k + $firstCode);
1166
						if (isset($subsetchars[$c])) {
1167
							$g = TCPDF_STATIC::_getUSHORT($font, $offset);
1168
							$subsetglyphs[$g] = true;
1169
						}
1170
						$offset += 2;
1171
					}
1172
					break;
1173
				}
1174
				case 8: { // Format 8: Mixed 16-bit and 32-bit coverage
1175
					$offset += 10; // skip reserved, length and version/language
1176
					for ($k = 0; $k < 8192; ++$k) {
1177
						$is32[$k] = TCPDF_STATIC::_getBYTE($font, $offset);
1178
						++$offset;
1179
					}
1180
					$nGroups = TCPDF_STATIC::_getULONG($font, $offset);
1181
					$offset += 4;
1182
					for ($i = 0; $i < $nGroups; ++$i) {
1183
						$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
1184
						$offset += 4;
1185
						$endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
1186
						$offset += 4;
1187
						$startGlyphID = TCPDF_STATIC::_getULONG($font, $offset);
1188
						$offset += 4;
1189
						for ($k = $startCharCode; $k <= $endCharCode; ++$k) {
1190
							$is32idx = floor($c / 8);
1191
							if ((isset($is32[$is32idx])) AND (($is32[$is32idx] & (1 << (7 - ($c % 8)))) == 0)) {
1192
								$c = $k;
1193
							} else {
1194
								// 32 bit format
1195
								// convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4)
1196
								//LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232
1197
								//SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888
1198
								$c = ((55232 + ($k >> 10)) << 10) + (0xDC00 + ($k & 0x3FF)) -56613888;
1199
							}
1200
							if (isset($subsetchars[$c])) {
1201
								$subsetglyphs[$startGlyphID] = true;
1202
							}
1203
							++$startGlyphID;
1204
						}
1205
					}
1206
					break;
1207
				}
1208
				case 10: { // Format 10: Trimmed array
1209
					$offset += 10; // skip reserved, length and version/language
1210
					$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
1211
					$offset += 4;
1212
					$numChars = TCPDF_STATIC::_getULONG($font, $offset);
1213
					$offset += 4;
1214
					for ($k = 0; $k < $numChars; ++$k) {
1215
						$c = ($k + $startCharCode);
1216
						if (isset($subsetchars[$c])) {
1217
							$g = TCPDF_STATIC::_getUSHORT($font, $offset);
1218
							$subsetglyphs[$g] = true;
1219
						}
1220
						$offset += 2;
1221
					}
1222
					break;
1223
				}
1224
				case 12: { // Format 12: Segmented coverage
1225
					$offset += 10; // skip length and version/language
1226
					$nGroups = TCPDF_STATIC::_getULONG($font, $offset);
1227
					$offset += 4;
1228
					for ($k = 0; $k < $nGroups; ++$k) {
1229
						$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
1230
						$offset += 4;
1231
						$endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
1232
						$offset += 4;
1233
						$startGlyphCode = TCPDF_STATIC::_getULONG($font, $offset);
1234
						$offset += 4;
1235
						for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
1236
							if (isset($subsetchars[$c])) {
1237
								$subsetglyphs[$startGlyphCode] = true;
1238
							}
1239
							++$startGlyphCode;
1240
						}
1241
					}
1242
					break;
1243
				}
1244
				case 13: { // Format 13: Many-to-one range mappings
1245
					// to be implemented ...
1246
					break;
1247
				}
1248
				case 14: { // Format 14: Unicode Variation Sequences
1249
					// to be implemented ...
1250
					break;
1251
				}
1252
			}
1253
		}
1254
		// include all parts of composite glyphs
1255
		$new_sga = $subsetglyphs;
1256
		while (!empty($new_sga)) {
1257
			$sga = $new_sga;
1258
			$new_sga = array();
1259
			foreach ($sga as $key => $val) {
1260
				if (isset($indexToLoc[$key])) {
1261
					$offset = ($table['glyf']['offset'] + $indexToLoc[$key]);
1262
					$numberOfContours = TCPDF_STATIC::_getSHORT($font, $offset);
1263
					$offset += 2;
1264
					if ($numberOfContours < 0) { // composite glyph
1265
						$offset += 8; // skip xMin, yMin, xMax, yMax
1266
						do {
1267
							$flags = TCPDF_STATIC::_getUSHORT($font, $offset);
1268
							$offset += 2;
1269
							$glyphIndex = TCPDF_STATIC::_getUSHORT($font, $offset);
1270
							$offset += 2;
1271
							if (!isset($subsetglyphs[$glyphIndex])) {
1272
								// add missing glyphs
1273
								$new_sga[$glyphIndex] = true;
1274
							}
1275
							// skip some bytes by case
1276
							if ($flags & 1) {
1277
								$offset += 4;
1278
							} else {
1279
								$offset += 2;
1280
							}
1281
							if ($flags & 8) {
1282
								$offset += 2;
1283
							} elseif ($flags & 64) {
1284
								$offset += 4;
1285
							} elseif ($flags & 128) {
1286
								$offset += 8;
1287
							}
1288
						} while ($flags & 32);
1289
					}
1290
				}
1291
			}
1292
			$subsetglyphs += $new_sga;
1293
		}
1294
		// sort glyphs by key (and remove duplicates)
1295
		ksort($subsetglyphs);
1296
		// build new glyf and loca tables
1297
		$glyf = '';
1298
		$loca = '';
1299
		$offset = 0;
1300
		$glyf_offset = $table['glyf']['offset'];
1301
		for ($i = 0; $i < $tot_num_glyphs; ++$i) {
1302
			if (isset($subsetglyphs[$i])) {
1303
				$length = ($indexToLoc[($i + 1)] - $indexToLoc[$i]);
1304
				$glyf .= substr($font, ($glyf_offset + $indexToLoc[$i]), $length);
1305
			} else {
1306
				$length = 0;
1307
			}
1308
			if ($short_offset) {
1309
				$loca .= pack('n', floor($offset / 2));
1310
			} else {
1311
				$loca .= pack('N', $offset);
1312
			}
1313
			$offset += $length;
1314
		}
1315
		// array of table names to preserve (loca and glyf tables will be added later)
1316
		// the cmap table is not needed and shall not be present, since the mapping from character codes to glyph descriptions is provided separately
1317
		$table_names = array ('head', 'hhea', 'hmtx', 'maxp', 'cvt ', 'fpgm', 'prep'); // minimum required table names
1318
		// get the tables to preserve
1319
		$offset = 12;
1320
		foreach ($table as $tag => $val) {
1321
			if (in_array($tag, $table_names)) {
1322
				$table[$tag]['data'] = substr($font, $table[$tag]['offset'], $table[$tag]['length']);
1323
				if ($tag == 'head') {
1324
					// set the checkSumAdjustment to 0
1325
					$table[$tag]['data'] = substr($table[$tag]['data'], 0, 8)."\x0\x0\x0\x0".substr($table[$tag]['data'], 12);
1326
				}
1327
				$table[$tag]['offset'] = $offset;
1328
				$offset += $table[$tag]['length'];
1329
				$numPad = ($offset + 3 & ~3) - $offset;
1330
				if($numPad > 0) {
1331
					$table[$tag]['data'] .= str_repeat("\x0", $numPad);
1332
					$offset += $numPad;
1333
				}
1334
				// check sum is not changed (so keep the following line commented)
1335
				//$table[$tag]['checkSum'] = self::_getTTFtableChecksum($table[$tag]['data'], $table[$tag]['length'] + $numPad);
1336
			} else {
1337
				unset($table[$tag]);
1338
			}
1339
		}
1340
		// add loca
1341
		$table['loca'] = array();
1342
		$table['loca']['data'] = $loca;
1343
		$table['loca']['length'] = strlen($loca);
1344
		$table['loca']['offset'] = $offset;
1345
		$offset += $table['loca']['length'];
1346
		$numPad = ($offset + 3 & ~3) - $offset;
1347
		if($numPad > 0) {
1348
			$table['loca']['data'] .= str_repeat("\x0", $numPad);
1349
			$offset += $numPad;
1350
		}
1351
		$table['loca']['checkSum'] = self::_getTTFtableChecksum($table['loca']['data'], $table['loca']['length'] + $numPad);
1352
		// add glyf
1353
		$table['glyf'] = array();
1354
		$table['glyf']['data'] = $glyf;
1355
		$table['glyf']['length'] = strlen($glyf);
1356
		$table['glyf']['offset'] = $offset;
1357
		$offset += $table['glyf']['length'];
1358
		$numPad = ($offset + 3 & ~3) - $offset;
1359
		if($numPad > 0) {
1360
			$table['glyf']['data'] .= str_repeat("\x0", $numPad);
1361
			$offset += $numPad;
1362
		}
1363
		$table['glyf']['checkSum'] = self::_getTTFtableChecksum($table['glyf']['data'], $table['glyf']['length'] + $numPad);
1364
		// rebuild font
1365
		$font = '';
1366
		$font .= pack('N', 0x10000); // sfnt version
1367
		$numTables = count($table);
1368
		$font .= pack('n', $numTables); // numTables
1369
		$entrySelector = floor(log($numTables, 2));
1370
		$searchRange = pow(2, $entrySelector) * 16;
1371
		$rangeShift = ($numTables * 16) - $searchRange;
1372
		$font .= pack('n', $searchRange); // searchRange
1373
		$font .= pack('n', $entrySelector); // entrySelector
1374
		$font .= pack('n', $rangeShift); // rangeShift
1375
		$offset = ($numTables * 16);
1376
		foreach ($table as $tag => $data) {
1377
			$font .= $tag; // tag
1378
			$font .= pack('N', $data['checkSum']); // checkSum
1379
			$font .= pack('N', ($data['offset'] + $offset)); // offset
1380
			$font .= pack('N', $data['length']); // length
1381
		}
1382
		foreach ($table as $data) {
1383
			$font .= $data['data'];
1384
		}
1385
		// set checkSumAdjustment on head table
1386
		$checkSumAdjustment = 0xB1B0AFBA - self::_getTTFtableChecksum($font, strlen($font));
1387
		$font = substr($font, 0, $table['head']['offset'] + $offset + 8).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + $offset + 12);
1388
		return $font;
1389
	}
1390
1391
	/**
1392
	 * Outputs font widths
1393
	 * @param array $font font data
1394
	 * @param int $cidoffset offset for CID values
1395
	 * @return string PDF command string for font widths
1396
	 * @author Nicola Asuni
1397
	 * @since 4.4.000 (2008-12-07)
1398
	 * @public static
1399
	 */
1400
	public static function _putfontwidths($font, $cidoffset=0) {
1401
		ksort($font['cw']);
1402
		$rangeid = 0;
1403
		$range = array();
1404
		$prevcid = -2;
1405
		$prevwidth = -1;
1406
		$interval = false;
1407
		// for each character
1408
		foreach ($font['cw'] as $cid => $width) {
1409
			$cid -= $cidoffset;
1410
			if ($font['subset'] AND (!isset($font['subsetchars'][$cid]))) {
1411
				// ignore the unused characters (font subsetting)
1412
				continue;
1413
			}
1414
			if ($width != $font['dw']) {
1415
				if ($cid == ($prevcid + 1)) {
1416
					// consecutive CID
1417
					if ($width == $prevwidth) {
1418
						if ($width == $range[$rangeid][0]) {
1419
							$range[$rangeid][] = $width;
1420
						} else {
1421
							array_pop($range[$rangeid]);
1422
							// new range
1423
							$rangeid = $prevcid;
1424
							$range[$rangeid] = array();
1425
							$range[$rangeid][] = $prevwidth;
1426
							$range[$rangeid][] = $width;
1427
						}
1428
						$interval = true;
1429
						$range[$rangeid]['interval'] = true;
1430
					} else {
1431
						if ($interval) {
1432
							// new range
1433
							$rangeid = $cid;
1434
							$range[$rangeid] = array();
1435
							$range[$rangeid][] = $width;
1436
						} else {
1437
							$range[$rangeid][] = $width;
1438
						}
1439
						$interval = false;
1440
					}
1441
				} else {
1442
					// new range
1443
					$rangeid = $cid;
1444
					$range[$rangeid] = array();
1445
					$range[$rangeid][] = $width;
1446
					$interval = false;
1447
				}
1448
				$prevcid = $cid;
1449
				$prevwidth = $width;
1450
			}
1451
		}
1452
		// optimize ranges
1453
		$prevk = -1;
1454
		$nextk = -1;
1455
		$prevint = false;
1456
		foreach ($range as $k => $ws) {
1457
			$cws = count($ws);
1458
			if (($k == $nextk) AND (!$prevint) AND ((!isset($ws['interval'])) OR ($cws < 4))) {
1459
				if (isset($range[$k]['interval'])) {
1460
					unset($range[$k]['interval']);
1461
				}
1462
				$range[$prevk] = array_merge($range[$prevk], $range[$k]);
1463
				unset($range[$k]);
1464
			} else {
1465
				$prevk = $k;
1466
			}
1467
			$nextk = $k + $cws;
1468
			if (isset($ws['interval'])) {
1469
				if ($cws > 3) {
1470
					$prevint = true;
1471
				} else {
1472
					$prevint = false;
1473
				}
1474
				if (isset($range[$k]['interval'])) {
1475
					unset($range[$k]['interval']);
1476
				}
1477
				--$nextk;
1478
			} else {
1479
				$prevint = false;
1480
			}
1481
		}
1482
		// output data
1483
		$w = '';
1484
		foreach ($range as $k => $ws) {
1485
			if (count(array_count_values($ws)) == 1) {
1486
				// interval mode is more compact
1487
				$w .= ' '.$k.' '.($k + count($ws) - 1).' '.$ws[0];
1488
			} else {
1489
				// range mode
1490
				$w .= ' '.$k.' [ '.implode(' ', $ws).' ]';
1491
			}
1492
		}
1493
		return '/W ['.$w.' ]';
1494
	}
1495
1496
1497
1498
1499
	/**
1500
	 * Update the CIDToGIDMap string with a new value.
1501
	 * @param string $map CIDToGIDMap.
1502
	 * @param int $cid CID value.
1503
	 * @param int $gid GID value.
1504
	 * @return string CIDToGIDMap.
1505
	 * @author Nicola Asuni
1506
	 * @since 5.9.123 (2011-09-29)
1507
	 * @public static
1508
	 */
1509
	public static function updateCIDtoGIDmap($map, $cid, $gid) {
1510
		if (($cid >= 0) AND ($cid <= 0xFFFF) AND ($gid >= 0)) {
1511
			if ($gid > 0xFFFF) {
1512
				$gid -= 0x10000;
1513
			}
1514
			$map[($cid * 2)] = chr($gid >> 8);
1515
			$map[(($cid * 2) + 1)] = chr($gid & 0xFF);
1516
		}
1517
		return $map;
1518
	}
1519
1520
	/**
1521
	 * Return fonts path
1522
	 * @return string
1523
	 * @public static
1524
	 */
1525
	public static function _getfontpath() {
1526
		if (!defined('K_PATH_FONTS') AND is_dir($fdir = realpath(dirname(__FILE__).'/../fonts'))) {
1527
			if (substr($fdir, -1) != '/') {
1528
				$fdir .= '/';
1529
			}
1530
			define('K_PATH_FONTS', $fdir);
1531
		}
1532
		return defined('K_PATH_FONTS') ? K_PATH_FONTS : '';
1533
	}
1534
1535
1536
1537
	/**
1538
	 * Return font full path
1539
	 * @param string $file Font file name.
1540
	 * @param string $fontdir Font directory (set to false fto search on default directories)
1541
	 * @return string Font full path or empty string
1542
	 * @author Nicola Asuni
1543
	 * @since 6.0.025
1544
	 * @public static
1545
	 */
1546
	public static function getFontFullPath($file, $fontdir=false) {
1547
		$fontfile = '';
1548
		// search files on various directories
1549
		if (($fontdir !== false) AND @TCPDF_STATIC::file_exists($fontdir.$file)) {
1550
			$fontfile = $fontdir.$file;
1551
		} elseif (@TCPDF_STATIC::file_exists(self::_getfontpath().$file)) {
1552
			$fontfile = self::_getfontpath().$file;
1553
		} elseif (@TCPDF_STATIC::file_exists($file)) {
1554
			$fontfile = $file;
1555
		}
1556
		return $fontfile;
1557
	}
1558
1559
1560
1561
1562
	/**
1563
	 * Get a reference font size.
1564
	 * @param string $size String containing font size value.
1565
	 * @param float $refsize Reference font size in points.
1566
	 * @return float value in points
1567
	 * @public static
1568
	 */
1569
	public static function getFontRefSize($size, $refsize=12) {
1570
		switch ($size) {
1571
			case 'xx-small': {
1572
				$size = ($refsize - 4);
1573
				break;
1574
			}
1575
			case 'x-small': {
1576
				$size = ($refsize - 3);
1577
				break;
1578
			}
1579
			case 'small': {
1580
				$size = ($refsize - 2);
1581
				break;
1582
			}
1583
			case 'medium': {
1584
				$size = $refsize;
1585
				break;
1586
			}
1587
			case 'large': {
1588
				$size = ($refsize + 2);
1589
				break;
1590
			}
1591
			case 'x-large': {
1592
				$size = ($refsize + 4);
1593
				break;
1594
			}
1595
			case 'xx-large': {
1596
				$size = ($refsize + 6);
1597
				break;
1598
			}
1599
			case 'smaller': {
1600
				$size = ($refsize - 3);
1601
				break;
1602
			}
1603
			case 'larger': {
1604
				$size = ($refsize + 3);
1605
				break;
1606
			}
1607
		}
1608
		return $size;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $size also could return the type string which is incompatible with the documented return type double.
Loading history...
1609
	}
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
// ====================================================================================================================
1651
// REIMPLEMENTED
1652
// ====================================================================================================================
1653
1654
1655
1656
1657
1658
1659
1660
1661
	/**
1662
	 * Returns the unicode caracter specified by the value
1663
	 * @param int $c UTF-8 value
1664
	 * @param boolean $unicode True if we are in unicode mode, false otherwise.
1665
	 * @return string Returns the specified character.
1666
	 * @since 2.3.000 (2008-03-05)
1667
	 * @public static
1668
	 */
1669
	public static function unichr($c, $unicode=true) {
1670
		$c = intval($c);
1671
		if (!$unicode) {
1672
			return chr($c);
1673
		} elseif ($c <= 0x7F) {
1674
			// one byte
1675
			return chr($c);
1676
		} elseif ($c <= 0x7FF) {
1677
			// two bytes
1678
			return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F);
1679
		} elseif ($c <= 0xFFFF) {
1680
			// three bytes
1681
			return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
1682
		} elseif ($c <= 0x10FFFF) {
1683
			// four bytes
1684
			return chr(0xF0 | $c >> 18).chr(0x80 | $c >> 12 & 0x3F).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
1685
		} else {
1686
			return '';
1687
		}
1688
	}
1689
1690
	/**
1691
	 * Returns the unicode caracter specified by UTF-8 value
1692
	 * @param int $c UTF-8 value
1693
	 * @return string Returns the specified character.
1694
	 * @public static
1695
	 */
1696
	public static function unichrUnicode($c) {
1697
		return self::unichr($c, true);
1698
	}
1699
1700
	/**
1701
	 * Returns the unicode caracter specified by ASCII value
1702
	 * @param int $c UTF-8 value
1703
	 * @return string Returns the specified character.
1704
	 * @public static
1705
	 */
1706
	public static function unichrASCII($c) {
1707
		return self::unichr($c, false);
1708
	}
1709
1710
	/**
1711
	 * Converts array of UTF-8 characters to UTF16-BE string.<br>
1712
	 * Based on: http://www.faqs.org/rfcs/rfc2781.html
1713
	 * <pre>
1714
	 *   Encoding UTF-16:
1715
	 *
1716
	 *   Encoding of a single character from an ISO 10646 character value to
1717
	 *    UTF-16 proceeds as follows. Let U be the character number, no greater
1718
	 *    than 0x10FFFF.
1719
	 *
1720
	 *    1) If U < 0x10000, encode U as a 16-bit unsigned integer and
1721
	 *       terminate.
1722
	 *
1723
	 *    2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
1724
	 *       U' must be less than or equal to 0xFFFFF. That is, U' can be
1725
	 *       represented in 20 bits.
1726
	 *
1727
	 *    3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
1728
	 *       0xDC00, respectively. These integers each have 10 bits free to
1729
	 *       encode the character value, for a total of 20 bits.
1730
	 *
1731
	 *    4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order
1732
	 *       bits of W1 and the 10 low-order bits of U' to the 10 low-order
1733
	 *       bits of W2. Terminate.
1734
	 *
1735
	 *    Graphically, steps 2 through 4 look like:
1736
	 *    U' = yyyyyyyyyyxxxxxxxxxx
1737
	 *    W1 = 110110yyyyyyyyyy
1738
	 *    W2 = 110111xxxxxxxxxx
1739
	 * </pre>
1740
	 * @param array $unicode array containing UTF-8 unicode values
1741
	 * @param boolean $setbom if true set the Byte Order Mark (BOM = 0xFEFF)
1742
	 * @return string
1743
	 * @protected
1744
	 * @author Nicola Asuni
1745
	 * @since 2.1.000 (2008-01-08)
1746
	 * @public static
1747
	 */
1748
	public static function arrUTF8ToUTF16BE($unicode, $setbom=false) {
1749
		$outstr = ''; // string to be returned
1750
		if ($setbom) {
1751
			$outstr .= "\xFE\xFF"; // Byte Order Mark (BOM)
1752
		}
1753
		foreach ($unicode as $char) {
1754
			if ($char == 0x200b) {
1755
				// skip Unicode Character 'ZERO WIDTH SPACE' (DEC:8203, U+200B)
1756
			} elseif ($char == 0xFFFD) {
1757
				$outstr .= "\xFF\xFD"; // replacement character
1758
			} elseif ($char < 0x10000) {
1759
				$outstr .= chr($char >> 0x08);
1760
				$outstr .= chr($char & 0xFF);
1761
			} else {
1762
				$char -= 0x10000;
1763
				$w1 = 0xD800 | ($char >> 0x0a);
1764
				$w2 = 0xDC00 | ($char & 0x3FF);
1765
				$outstr .= chr($w1 >> 0x08);
1766
				$outstr .= chr($w1 & 0xFF);
1767
				$outstr .= chr($w2 >> 0x08);
1768
				$outstr .= chr($w2 & 0xFF);
1769
			}
1770
		}
1771
		return $outstr;
1772
	}
1773
1774
	/**
1775
	 * Convert an array of UTF8 values to array of unicode characters
1776
	 * @param array $ta The input array of UTF8 values.
1777
	 * @param boolean $isunicode True for Unicode mode, false otherwise.
1778
	 * @return array Return array of unicode characters
1779
	 * @since 4.5.037 (2009-04-07)
1780
	 * @public static
1781
	 */
1782
	public static function UTF8ArrayToUniArray($ta, $isunicode=true) {
1783
		if ($isunicode) {
1784
			return array_map(static::class.'::unichrUnicode', $ta);
1785
		}
1786
		return array_map(static::class.'::unichrASCII', $ta);
1787
	}
1788
1789
	/**
1790
	 * Extract a slice of the $strarr array and return it as string.
1791
	 * @param string[] $strarr The input array of characters.
1792
	 * @param int $start the starting element of $strarr.
1793
	 * @param int $end first element that will not be returned.
1794
	 * @param boolean $unicode True if we are in unicode mode, false otherwise.
1795
	 * @return string Return part of a string
1796
	 * @public static
1797
	 */
1798
	public static function UTF8ArrSubString($strarr, $start='', $end='', $unicode=true) {
1799
		if (strlen($start) == 0) {
1800
			$start = 0;
1801
		}
1802
		if (strlen($end) == 0) {
1803
			$end = count($strarr);
1804
		}
1805
		$string = '';
1806
		for ($i = $start; $i < $end; ++$i) {
1807
			$string .= self::unichr($strarr[$i], $unicode);
0 ignored issues
show
Bug introduced by
$strarr[$i] of type string is incompatible with the type integer expected by parameter $c of TCPDF_FONTS::unichr(). ( Ignorable by Annotation )

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

1807
			$string .= self::unichr(/** @scrutinizer ignore-type */ $strarr[$i], $unicode);
Loading history...
1808
		}
1809
		return $string;
1810
	}
1811
1812
	/**
1813
	 * Extract a slice of the $uniarr array and return it as string.
1814
	 * @param string[] $uniarr The input array of characters.
1815
	 * @param int $start the starting element of $strarr.
1816
	 * @param int $end first element that will not be returned.
1817
	 * @return string Return part of a string
1818
	 * @since 4.5.037 (2009-04-07)
1819
	 * @public static
1820
	 */
1821
	public static function UniArrSubString($uniarr, $start='', $end='') {
1822
		if (strlen($start) == 0) {
1823
			$start = 0;
1824
		}
1825
		if (strlen($end) == 0) {
1826
			$end = count($uniarr);
1827
		}
1828
		$string = '';
1829
		for ($i=$start; $i < $end; ++$i) {
1830
			$string .= $uniarr[$i];
1831
		}
1832
		return $string;
1833
	}
1834
1835
	/**
1836
	 * Converts UTF-8 characters array to array of Latin1 characters array<br>
1837
	 * @param array $unicode array containing UTF-8 unicode values
1838
	 * @return array
1839
	 * @author Nicola Asuni
1840
	 * @since 4.8.023 (2010-01-15)
1841
	 * @public static
1842
	 */
1843
	public static function UTF8ArrToLatin1Arr($unicode) {
1844
		$outarr = array(); // array to be returned
1845
		foreach ($unicode as $char) {
1846
			if ($char < 256) {
1847
				$outarr[] = $char;
1848
			} elseif (array_key_exists($char, TCPDF_FONT_DATA::$uni_utf8tolatin)) {
1849
				// map from UTF-8
1850
				$outarr[] = TCPDF_FONT_DATA::$uni_utf8tolatin[$char];
1851
			} elseif ($char == 0xFFFD) {
1852
				// skip
1853
			} else {
1854
				$outarr[] = 63; // '?' character
1855
			}
1856
		}
1857
		return $outarr;
1858
	}
1859
1860
	/**
1861
	 * Converts UTF-8 characters array to Latin1 string<br>
1862
	 * @param array $unicode array containing UTF-8 unicode values
1863
	 * @return string
1864
	 * @author Nicola Asuni
1865
	 * @since 4.8.023 (2010-01-15)
1866
	 * @public static
1867
	 */
1868
	public static function UTF8ArrToLatin1($unicode) {
1869
		$outstr = ''; // string to be returned
1870
		foreach ($unicode as $char) {
1871
			if ($char < 256) {
1872
				$outstr .= chr($char);
1873
			} elseif (array_key_exists($char, TCPDF_FONT_DATA::$uni_utf8tolatin)) {
1874
				// map from UTF-8
1875
				$outstr .= chr(TCPDF_FONT_DATA::$uni_utf8tolatin[$char]);
1876
			} elseif ($char == 0xFFFD) {
1877
				// skip
1878
			} else {
1879
				$outstr .= '?';
1880
			}
1881
		}
1882
		return $outstr;
1883
	}
1884
1885
	/**
1886
	 * Converts UTF-8 character to integer value.<br>
1887
	 * Uses the getUniord() method if the value is not cached.
1888
	 * @param string $uch character string to process.
1889
	 * @return int Unicode value
1890
	 * @public static
1891
	 */
1892
	public static function uniord($uch) {
1893
		if (!isset(self::$cache_uniord[$uch])) {
1894
			self::$cache_uniord[$uch] = self::getUniord($uch);
1895
		}
1896
		return self::$cache_uniord[$uch];
1897
	}
1898
1899
	/**
1900
	 * Converts UTF-8 character to integer value.<br>
1901
	 * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
1902
	 * Based on: http://www.faqs.org/rfcs/rfc3629.html
1903
	 * <pre>
1904
	 *    Char. number range  |        UTF-8 octet sequence
1905
	 *       (hexadecimal)    |              (binary)
1906
	 *    --------------------+-----------------------------------------------
1907
	 *    0000 0000-0000 007F | 0xxxxxxx
1908
	 *    0000 0080-0000 07FF | 110xxxxx 10xxxxxx
1909
	 *    0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
1910
	 *    0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
1911
	 *    ---------------------------------------------------------------------
1912
	 *
1913
	 *   ABFN notation:
1914
	 *   ---------------------------------------------------------------------
1915
	 *   UTF8-octets = *( UTF8-char )
1916
	 *   UTF8-char   = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
1917
	 *   UTF8-1      = %x00-7F
1918
	 *   UTF8-2      = %xC2-DF UTF8-tail
1919
	 *
1920
	 *   UTF8-3      = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
1921
	 *                 %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
1922
	 *   UTF8-4      = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
1923
	 *                 %xF4 %x80-8F 2( UTF8-tail )
1924
	 *   UTF8-tail   = %x80-BF
1925
	 *   ---------------------------------------------------------------------
1926
	 * </pre>
1927
	 * @param string $uch character string to process.
1928
	 * @return int Unicode value
1929
	 * @author Nicola Asuni
1930
	 * @public static
1931
	 */
1932
	public static function getUniord($uch) {
1933
		if (function_exists('mb_convert_encoding')) {
1934
			list(, $char) = @unpack('N', mb_convert_encoding($uch, 'UCS-4BE', 'UTF-8'));
0 ignored issues
show
Bug introduced by
It seems like mb_convert_encoding($uch, '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

1934
			list(, $char) = @unpack('N', /** @scrutinizer ignore-type */ mb_convert_encoding($uch, 'UCS-4BE', 'UTF-8'));
Loading history...
1935
			if ($char >= 0) {
1936
				return $char;
1937
			}
1938
		}
1939
		$bytes = array(); // array containing single character byte sequences
1940
		$countbytes = 0;
1941
		$numbytes = 1; // number of octetc needed to represent the UTF-8 character
1942
		$length = strlen($uch);
1943
		for ($i = 0; $i < $length; ++$i) {
1944
			$char = ord($uch[$i]); // get one string character at time
1945
			if ($countbytes == 0) { // get starting octect
1946
				if ($char <= 0x7F) {
1947
					return $char; // use the character "as is" because is ASCII
1948
				} elseif (($char >> 0x05) == 0x06) { // 2 bytes character (0x06 = 110 BIN)
1949
					$bytes[] = ($char - 0xC0) << 0x06;
1950
					++$countbytes;
1951
					$numbytes = 2;
1952
				} elseif (($char >> 0x04) == 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
1953
					$bytes[] = ($char - 0xE0) << 0x0C;
1954
					++$countbytes;
1955
					$numbytes = 3;
1956
				} elseif (($char >> 0x03) == 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
1957
					$bytes[] = ($char - 0xF0) << 0x12;
1958
					++$countbytes;
1959
					$numbytes = 4;
1960
				} else {
1961
					// use replacement character for other invalid sequences
1962
					return 0xFFFD;
1963
				}
1964
			} elseif (($char >> 0x06) == 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
1965
				$bytes[] = $char - 0x80;
1966
				++$countbytes;
1967
				if ($countbytes == $numbytes) {
1968
					// compose UTF-8 bytes to a single unicode value
1969
					$char = $bytes[0];
1970
					for ($j = 1; $j < $numbytes; ++$j) {
1971
						$char += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
1972
					}
1973
					if ((($char >= 0xD800) AND ($char <= 0xDFFF)) OR ($char >= 0x10FFFF)) {
1974
						// The definition of UTF-8 prohibits encoding character numbers between
1975
						// U+D800 and U+DFFF, which are reserved for use with the UTF-16
1976
						// encoding form (as surrogate pairs) and do not directly represent
1977
						// characters.
1978
						return 0xFFFD; // use replacement character
1979
					} else {
1980
						return $char;
1981
					}
1982
				}
1983
			} else {
1984
				// use replacement character for other invalid sequences
1985
				return 0xFFFD;
1986
			}
1987
		}
1988
		return 0xFFFD;
1989
	}
1990
1991
	/**
1992
	 * Converts UTF-8 strings to codepoints array.<br>
1993
	 * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
1994
	 * @param string $str string to process.
1995
	 * @param boolean $isunicode True when the documetn is in Unicode mode, false otherwise.
1996
	 * @param array $currentfont Reference to current font array.
1997
	 * @return array containing codepoints (UTF-8 characters values)
1998
	 * @author Nicola Asuni
1999
	 * @public static
2000
	 */
2001
	public static function UTF8StringToArray($str, $isunicode, &$currentfont) {
2002
		$str = is_null($str) ? '' : $str;
0 ignored issues
show
introduced by
The condition is_null($str) is always false.
Loading history...
2003
		if ($isunicode) {
2004
			// requires PCRE unicode support turned on
2005
			$chars = TCPDF_STATIC::pregSplit('//','u', $str, -1, PREG_SPLIT_NO_EMPTY);
2006
			$carr = array_map(static::class.'::uniord', $chars);
2007
		} else {
2008
			$chars = str_split($str);
2009
			$carr = array_map('ord', $chars);
0 ignored issues
show
Bug introduced by
It seems like $chars can also be of type true; however, parameter $array of array_map() does only seem to accept array, 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

2009
			$carr = array_map('ord', /** @scrutinizer ignore-type */ $chars);
Loading history...
2010
		}
2011
		if (is_array($currentfont['subsetchars']) && is_array($carr)) {
2012
			$currentfont['subsetchars'] += array_fill_keys($carr, true);
2013
		} else {
2014
			$currentfont['subsetchars'] = array_merge($currentfont['subsetchars'], $carr);
2015
		}
2016
		return $carr;
2017
	}
2018
2019
	/**
2020
	 * Converts UTF-8 strings to Latin1 when using the standard 14 core fonts.<br>
2021
	 * @param string $str string to process.
2022
	 * @param boolean $isunicode True when the documetn is in Unicode mode, false otherwise.
2023
	 * @param array $currentfont Reference to current font array.
2024
	 * @return string
2025
	 * @since 3.2.000 (2008-06-23)
2026
	 * @public static
2027
	 */
2028
	public static function UTF8ToLatin1($str, $isunicode, &$currentfont) {
2029
		$unicode = self::UTF8StringToArray($str, $isunicode, $currentfont); // array containing UTF-8 unicode values
2030
		return self::UTF8ArrToLatin1($unicode);
2031
	}
2032
2033
	/**
2034
	 * Converts UTF-8 strings to UTF16-BE.<br>
2035
	 * @param string $str string to process.
2036
	 * @param boolean $setbom if true set the Byte Order Mark (BOM = 0xFEFF)
2037
	 * @param boolean $isunicode True when the documetn is in Unicode mode, false otherwise.
2038
	 * @param array $currentfont Reference to current font array.
2039
	 * @return string
2040
	 * @author Nicola Asuni
2041
	 * @since 1.53.0.TC005 (2005-01-05)
2042
	 * @public static
2043
	 */
2044
	public static function UTF8ToUTF16BE($str, $setbom, $isunicode, &$currentfont) {
2045
		if (!$isunicode) {
2046
			return $str; // string is not in unicode
2047
		}
2048
		$unicode = self::UTF8StringToArray($str, $isunicode, $currentfont); // array containing UTF-8 unicode values
2049
		return self::arrUTF8ToUTF16BE($unicode, $setbom);
2050
	}
2051
2052
	/**
2053
	 * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
2054
	 * @param string $str string to manipulate.
2055
	 * @param bool $setbom if true set the Byte Order Mark (BOM = 0xFEFF)
2056
	 * @param bool $forcertl if true forces RTL text direction
2057
	 * @param boolean $isunicode True if the document is in Unicode mode, false otherwise.
2058
	 * @param array $currentfont Reference to current font array.
2059
	 * @return string
2060
	 * @author Nicola Asuni
2061
	 * @since 2.1.000 (2008-01-08)
2062
	 * @public static
2063
	 */
2064
	public static function utf8StrRev($str, $setbom, $forcertl, $isunicode, &$currentfont) {
2065
		return self::utf8StrArrRev(self::UTF8StringToArray($str, $isunicode, $currentfont), $str, $setbom, $forcertl, $isunicode, $currentfont);
2066
	}
2067
2068
	/**
2069
	 * Reverse the RLT substrings array using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
2070
	 * @param array $arr array of unicode values.
2071
	 * @param string $str string to manipulate (or empty value).
2072
	 * @param bool $setbom if true set the Byte Order Mark (BOM = 0xFEFF)
2073
	 * @param bool $forcertl if true forces RTL text direction
2074
	 * @param boolean $isunicode True if the document is in Unicode mode, false otherwise.
2075
	 * @param array $currentfont Reference to current font array.
2076
	 * @return string
2077
	 * @author Nicola Asuni
2078
	 * @since 4.9.000 (2010-03-27)
2079
	 * @public static
2080
	 */
2081
	public static function utf8StrArrRev($arr, $str, $setbom, $forcertl, $isunicode, &$currentfont) {
2082
		return self::arrUTF8ToUTF16BE(self::utf8Bidi($arr, $str, $forcertl, $isunicode, $currentfont), $setbom);
2083
	}
2084
2085
	/**
2086
	 * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
2087
	 * @param array $ta array of characters composing the string.
2088
	 * @param string $str string to process
2089
	 * @param bool $forcertl if 'R' forces RTL, if 'L' forces LTR
2090
	 * @param boolean $isunicode True if the document is in Unicode mode, false otherwise.
2091
	 * @param array $currentfont Reference to current font array.
2092
	 * @return array of unicode chars
2093
	 * @author Nicola Asuni
2094
	 * @since 2.4.000 (2008-03-06)
2095
	 * @public static
2096
	 */
2097
	public static function utf8Bidi($ta, $str, $forcertl, $isunicode, &$currentfont) {
2098
		// paragraph embedding level
2099
		$pel = 0;
2100
		// max level
2101
		$maxlevel = 0;
2102
		if (TCPDF_STATIC::empty_string($str)) {
2103
			// create string from array
2104
			$str = self::UTF8ArrSubString($ta, '', '', $isunicode);
0 ignored issues
show
Bug introduced by
'' of type string is incompatible with the type integer expected by parameter $end of TCPDF_FONTS::UTF8ArrSubString(). ( Ignorable by Annotation )

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

2104
			$str = self::UTF8ArrSubString($ta, '', /** @scrutinizer ignore-type */ '', $isunicode);
Loading history...
Bug introduced by
'' of type string is incompatible with the type integer expected by parameter $start of TCPDF_FONTS::UTF8ArrSubString(). ( Ignorable by Annotation )

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

2104
			$str = self::UTF8ArrSubString($ta, /** @scrutinizer ignore-type */ '', '', $isunicode);
Loading history...
2105
		}
2106
		// check if string contains arabic text
2107
		if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $str)) {
2108
			$arabic = true;
2109
		} else {
2110
			$arabic = false;
2111
		}
2112
		// check if string contains RTL text
2113
		if (!($forcertl OR $arabic OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $str))) {
2114
			return $ta;
2115
		}
2116
2117
		// get number of chars
2118
		$numchars = count($ta);
2119
2120
		if ($forcertl == 'R') {
2121
			$pel = 1;
2122
		} elseif ($forcertl == 'L') {
2123
			$pel = 0;
2124
		} else {
2125
			// P2. In each paragraph, find the first character of type L, AL, or R.
2126
			// P3. If a character is found in P2 and it is of type AL or R, then set the paragraph embedding level to one; otherwise, set it to zero.
2127
			for ($i=0; $i < $numchars; ++$i) {
2128
				$type = TCPDF_FONT_DATA::$uni_type[$ta[$i]];
2129
				if ($type == 'L') {
2130
					$pel = 0;
2131
					break;
2132
				} elseif (($type == 'AL') OR ($type == 'R')) {
2133
					$pel = 1;
2134
					break;
2135
				}
2136
			}
2137
		}
2138
2139
		// Current Embedding Level
2140
		$cel = $pel;
2141
		// directional override status
2142
		$dos = 'N';
2143
		$remember = array();
2144
		// start-of-level-run
2145
		$sor = $pel % 2 ? 'R' : 'L';
2146
		$eor = $sor;
2147
2148
		// Array of characters data
2149
		$chardata = Array();
2150
2151
		// X1. Begin by setting the current embedding level to the paragraph embedding level. Set the directional override status to neutral. Process each character iteratively, applying rules X2 through X9. Only embedding levels from 0 to 61 are valid in this phase.
2152
		// In the resolution of levels in rules I1 and I2, the maximum embedding level of 62 can be reached.
2153
		for ($i=0; $i < $numchars; ++$i) {
2154
			if ($ta[$i] == TCPDF_FONT_DATA::$uni_RLE) {
2155
				// X2. With each RLE, compute the least greater odd embedding level.
2156
				//	a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
2157
				//	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2158
				$next_level = $cel + ($cel % 2) + 1;
2159
				if ($next_level < 62) {
2160
					$remember[] = array('num' => TCPDF_FONT_DATA::$uni_RLE, 'cel' => $cel, 'dos' => $dos);
2161
					$cel = $next_level;
2162
					$dos = 'N';
2163
					$sor = $eor;
2164
					$eor = $cel % 2 ? 'R' : 'L';
2165
				}
2166
			} elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_LRE) {
2167
				// X3. With each LRE, compute the least greater even embedding level.
2168
				//	a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
2169
				//	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2170
				$next_level = $cel + 2 - ($cel % 2);
2171
				if ( $next_level < 62 ) {
2172
					$remember[] = array('num' => TCPDF_FONT_DATA::$uni_LRE, 'cel' => $cel, 'dos' => $dos);
2173
					$cel = $next_level;
2174
					$dos = 'N';
2175
					$sor = $eor;
2176
					$eor = $cel % 2 ? 'R' : 'L';
2177
				}
2178
			} elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_RLO) {
2179
				// X4. With each RLO, compute the least greater odd embedding level.
2180
				//	a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to right-to-left.
2181
				//	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2182
				$next_level = $cel + ($cel % 2) + 1;
2183
				if ($next_level < 62) {
2184
					$remember[] = array('num' => TCPDF_FONT_DATA::$uni_RLO, 'cel' => $cel, 'dos' => $dos);
2185
					$cel = $next_level;
2186
					$dos = 'R';
2187
					$sor = $eor;
2188
					$eor = $cel % 2 ? 'R' : 'L';
2189
				}
2190
			} elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_LRO) {
2191
				// X5. With each LRO, compute the least greater even embedding level.
2192
				//	a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to left-to-right.
2193
				//	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2194
				$next_level = $cel + 2 - ($cel % 2);
2195
				if ( $next_level < 62 ) {
2196
					$remember[] = array('num' => TCPDF_FONT_DATA::$uni_LRO, 'cel' => $cel, 'dos' => $dos);
2197
					$cel = $next_level;
2198
					$dos = 'L';
2199
					$sor = $eor;
2200
					$eor = $cel % 2 ? 'R' : 'L';
2201
				}
2202
			} elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_PDF) {
2203
				// X7. With each PDF, determine the matching embedding or override code. If there was a valid matching code, restore (pop) the last remembered (pushed) embedding level and directional override.
2204
				if (count($remember)) {
2205
					$last = count($remember ) - 1;
2206
					if (($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_RLE) OR
2207
						($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_LRE) OR
2208
						($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_RLO) OR
2209
						($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_LRO)) {
2210
						$match = array_pop($remember);
2211
						$cel = $match['cel'];
2212
						$dos = $match['dos'];
2213
						$sor = $eor;
2214
						$eor = ($cel > $match['cel'] ? $cel : $match['cel']) % 2 ? 'R' : 'L';
2215
					}
2216
				}
2217
			} elseif (($ta[$i] != TCPDF_FONT_DATA::$uni_RLE) AND
2218
							 ($ta[$i] != TCPDF_FONT_DATA::$uni_LRE) AND
2219
							 ($ta[$i] != TCPDF_FONT_DATA::$uni_RLO) AND
2220
							 ($ta[$i] != TCPDF_FONT_DATA::$uni_LRO) AND
2221
							 ($ta[$i] != TCPDF_FONT_DATA::$uni_PDF)) {
2222
				// X6. For all types besides RLE, LRE, RLO, LRO, and PDF:
2223
				//	a. Set the level of the current character to the current embedding level.
2224
				//	b. Whenever the directional override status is not neutral, reset the current character type to the directional override status.
2225
				if ($dos != 'N') {
2226
					$chardir = $dos;
2227
				} else {
2228
					if (isset(TCPDF_FONT_DATA::$uni_type[$ta[$i]])) {
2229
						$chardir = TCPDF_FONT_DATA::$uni_type[$ta[$i]];
2230
					} else {
2231
						$chardir = 'L';
2232
					}
2233
				}
2234
				// stores string characters and other information
2235
				$chardata[] = array('char' => $ta[$i], 'level' => $cel, 'type' => $chardir, 'sor' => $sor, 'eor' => $eor);
2236
			}
2237
		} // end for each char
2238
2239
		// X8. All explicit directional embeddings and overrides are completely terminated at the end of each paragraph. Paragraph separators are not included in the embedding.
2240
		// X9. Remove all RLE, LRE, RLO, LRO, PDF, and BN codes.
2241
		// X10. The remaining rules are applied to each run of characters at the same level. For each run, determine the start-of-level-run (sor) and end-of-level-run (eor) type, either L or R. This depends on the higher of the two levels on either side of the boundary (at the start or end of the paragraph, the level of the 'other' run is the base embedding level). If the higher level is odd, the type is R; otherwise, it is L.
2242
2243
		// 3.3.3 Resolving Weak Types
2244
		// Weak types are now resolved one level run at a time. At level run boundaries where the type of the character on the other side of the boundary is required, the type assigned to sor or eor is used.
2245
		// Nonspacing marks are now resolved based on the previous characters.
2246
		$numchars = count($chardata);
2247
2248
		// W1. Examine each nonspacing mark (NSM) in the level run, and change the type of the NSM to the type of the previous character. If the NSM is at the start of the level run, it will get the type of sor.
2249
		$prevlevel = -1; // track level changes
2250
		$levcount = 0; // counts consecutive chars at the same level
2251
		for ($i=0; $i < $numchars; ++$i) {
2252
			if ($chardata[$i]['type'] == 'NSM') {
2253
				if ($levcount) {
2254
					$chardata[$i]['type'] = $chardata[$i]['sor'];
2255
				} elseif ($i > 0) {
2256
					$chardata[$i]['type'] = $chardata[($i-1)]['type'];
2257
				}
2258
			}
2259
			if ($chardata[$i]['level'] != $prevlevel) {
2260
				$levcount = 0;
2261
			} else {
2262
				++$levcount;
2263
			}
2264
			$prevlevel = $chardata[$i]['level'];
2265
		}
2266
2267
		// W2. Search backward from each instance of a European number until the first strong type (R, L, AL, or sor) is found. If an AL is found, change the type of the European number to Arabic number.
2268
		$prevlevel = -1;
2269
		$levcount = 0;
2270
		for ($i=0; $i < $numchars; ++$i) {
2271
			if ($chardata[$i]['char'] == 'EN') {
2272
				for ($j=$levcount; $j >= 0; $j--) {
2273
					if ($chardata[$j]['type'] == 'AL') {
2274
						$chardata[$i]['type'] = 'AN';
2275
					} elseif (($chardata[$j]['type'] == 'L') OR ($chardata[$j]['type'] == 'R')) {
2276
						break;
2277
					}
2278
				}
2279
			}
2280
			if ($chardata[$i]['level'] != $prevlevel) {
2281
				$levcount = 0;
2282
			} else {
2283
				++$levcount;
2284
			}
2285
			$prevlevel = $chardata[$i]['level'];
2286
		}
2287
2288
		// W3. Change all ALs to R.
2289
		for ($i=0; $i < $numchars; ++$i) {
2290
			if ($chardata[$i]['type'] == 'AL') {
2291
				$chardata[$i]['type'] = 'R';
2292
			}
2293
		}
2294
2295
		// W4. A single European separator between two European numbers changes to a European number. A single common separator between two numbers of the same type changes to that type.
2296
		$prevlevel = -1;
2297
		$levcount = 0;
2298
		for ($i=0; $i < $numchars; ++$i) {
2299
			if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
2300
				if (($chardata[$i]['type'] == 'ES') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
2301
					$chardata[$i]['type'] = 'EN';
2302
				} elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
2303
					$chardata[$i]['type'] = 'EN';
2304
				} elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'AN') AND ($chardata[($i+1)]['type'] == 'AN')) {
2305
					$chardata[$i]['type'] = 'AN';
2306
				}
2307
			}
2308
			if ($chardata[$i]['level'] != $prevlevel) {
2309
				$levcount = 0;
2310
			} else {
2311
				++$levcount;
2312
			}
2313
			$prevlevel = $chardata[$i]['level'];
2314
		}
2315
2316
		// W5. A sequence of European terminators adjacent to European numbers changes to all European numbers.
2317
		$prevlevel = -1;
2318
		$levcount = 0;
2319
		for ($i=0; $i < $numchars; ++$i) {
2320
			if ($chardata[$i]['type'] == 'ET') {
2321
				if (($levcount > 0) AND ($chardata[($i-1)]['type'] == 'EN')) {
2322
					$chardata[$i]['type'] = 'EN';
2323
				} else {
2324
					$j = $i+1;
2325
					while (($j < $numchars) AND ($chardata[$j]['level'] == $prevlevel)) {
2326
						if ($chardata[$j]['type'] == 'EN') {
2327
							$chardata[$i]['type'] = 'EN';
2328
							break;
2329
						} elseif ($chardata[$j]['type'] != 'ET') {
2330
							break;
2331
						}
2332
						++$j;
2333
					}
2334
				}
2335
			}
2336
			if ($chardata[$i]['level'] != $prevlevel) {
2337
				$levcount = 0;
2338
			} else {
2339
				++$levcount;
2340
			}
2341
			$prevlevel = $chardata[$i]['level'];
2342
		}
2343
2344
		// W6. Otherwise, separators and terminators change to Other Neutral.
2345
		$prevlevel = -1;
2346
		$levcount = 0;
2347
		for ($i=0; $i < $numchars; ++$i) {
2348
			if (($chardata[$i]['type'] == 'ET') OR ($chardata[$i]['type'] == 'ES') OR ($chardata[$i]['type'] == 'CS')) {
2349
				$chardata[$i]['type'] = 'ON';
2350
			}
2351
			if ($chardata[$i]['level'] != $prevlevel) {
2352
				$levcount = 0;
2353
			} else {
2354
				++$levcount;
2355
			}
2356
			$prevlevel = $chardata[$i]['level'];
2357
		}
2358
2359
		//W7. Search backward from each instance of a European number until the first strong type (R, L, or sor) is found. If an L is found, then change the type of the European number to L.
2360
		$prevlevel = -1;
2361
		$levcount = 0;
2362
		for ($i=0; $i < $numchars; ++$i) {
2363
			if ($chardata[$i]['char'] == 'EN') {
2364
				for ($j=$levcount; $j >= 0; $j--) {
2365
					if ($chardata[$j]['type'] == 'L') {
2366
						$chardata[$i]['type'] = 'L';
2367
					} elseif ($chardata[$j]['type'] == 'R') {
2368
						break;
2369
					}
2370
				}
2371
			}
2372
			if ($chardata[$i]['level'] != $prevlevel) {
2373
				$levcount = 0;
2374
			} else {
2375
				++$levcount;
2376
			}
2377
			$prevlevel = $chardata[$i]['level'];
2378
		}
2379
2380
		// N1. A sequence of neutrals takes the direction of the surrounding strong text if the text on both sides has the same direction. European and Arabic numbers act as if they were R in terms of their influence on neutrals. Start-of-level-run (sor) and end-of-level-run (eor) are used at level run boundaries.
2381
		$prevlevel = -1;
2382
		$levcount = 0;
2383
		for ($i=0; $i < $numchars; ++$i) {
2384
			if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
2385
				if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
2386
					$chardata[$i]['type'] = 'L';
2387
				} elseif (($chardata[$i]['type'] == 'N') AND
2388
				 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
2389
				 (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
2390
					$chardata[$i]['type'] = 'R';
2391
				} elseif ($chardata[$i]['type'] == 'N') {
2392
					// N2. Any remaining neutrals take the embedding direction
2393
					$chardata[$i]['type'] = $chardata[$i]['sor'];
2394
				}
2395
			} elseif (($levcount == 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
2396
				// first char
2397
				if (($chardata[$i]['type'] == 'N') AND ($chardata[$i]['sor'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
2398
					$chardata[$i]['type'] = 'L';
2399
				} elseif (($chardata[$i]['type'] == 'N') AND
2400
				 (($chardata[$i]['sor'] == 'R') OR ($chardata[$i]['sor'] == 'EN') OR ($chardata[$i]['sor'] == 'AN')) AND
2401
				 (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
2402
					$chardata[$i]['type'] = 'R';
2403
				} elseif ($chardata[$i]['type'] == 'N') {
2404
					// N2. Any remaining neutrals take the embedding direction
2405
					$chardata[$i]['type'] = $chardata[$i]['sor'];
2406
				}
2407
			} elseif (($levcount > 0) AND ((($i+1) == $numchars) OR (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] != $prevlevel))) {
2408
				//last char
2409
				if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[$i]['eor'] == 'L')) {
2410
					$chardata[$i]['type'] = 'L';
2411
				} elseif (($chardata[$i]['type'] == 'N') AND
2412
				 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
2413
				 (($chardata[$i]['eor'] == 'R') OR ($chardata[$i]['eor'] == 'EN') OR ($chardata[$i]['eor'] == 'AN'))) {
2414
					$chardata[$i]['type'] = 'R';
2415
				} elseif ($chardata[$i]['type'] == 'N') {
2416
					// N2. Any remaining neutrals take the embedding direction
2417
					$chardata[$i]['type'] = $chardata[$i]['sor'];
2418
				}
2419
			} elseif ($chardata[$i]['type'] == 'N') {
2420
				// N2. Any remaining neutrals take the embedding direction
2421
				$chardata[$i]['type'] = $chardata[$i]['sor'];
2422
			}
2423
			if ($chardata[$i]['level'] != $prevlevel) {
2424
				$levcount = 0;
2425
			} else {
2426
				++$levcount;
2427
			}
2428
			$prevlevel = $chardata[$i]['level'];
2429
		}
2430
2431
		// I1. For all characters with an even (left-to-right) embedding direction, those of type R go up one level and those of type AN or EN go up two levels.
2432
		// I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level.
2433
		for ($i=0; $i < $numchars; ++$i) {
2434
			$odd = $chardata[$i]['level'] % 2;
2435
			if ($odd) {
2436
				if (($chardata[$i]['type'] == 'L') OR ($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
2437
					$chardata[$i]['level'] += 1;
2438
				}
2439
			} else {
2440
				if ($chardata[$i]['type'] == 'R') {
2441
					$chardata[$i]['level'] += 1;
2442
				} elseif (($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
2443
					$chardata[$i]['level'] += 2;
2444
				}
2445
			}
2446
			$maxlevel = max($chardata[$i]['level'],$maxlevel);
2447
		}
2448
2449
		// L1. On each line, reset the embedding level of the following characters to the paragraph embedding level:
2450
		//	1. Segment separators,
2451
		//	2. Paragraph separators,
2452
		//	3. Any sequence of whitespace characters preceding a segment separator or paragraph separator, and
2453
		//	4. Any sequence of white space characters at the end of the line.
2454
		for ($i=0; $i < $numchars; ++$i) {
2455
			if (($chardata[$i]['type'] == 'B') OR ($chardata[$i]['type'] == 'S')) {
2456
				$chardata[$i]['level'] = $pel;
2457
			} elseif ($chardata[$i]['type'] == 'WS') {
2458
				$j = $i+1;
2459
				while ($j < $numchars) {
2460
					if ((($chardata[$j]['type'] == 'B') OR ($chardata[$j]['type'] == 'S')) OR
2461
						(($j == ($numchars-1)) AND ($chardata[$j]['type'] == 'WS'))) {
2462
						$chardata[$i]['level'] = $pel;
2463
						break;
2464
					} elseif ($chardata[$j]['type'] != 'WS') {
2465
						break;
2466
					}
2467
					++$j;
2468
				}
2469
			}
2470
		}
2471
2472
		// Arabic Shaping
2473
		// Cursively connected scripts, such as Arabic or Syriac, require the selection of positional character shapes that depend on adjacent characters. Shaping is logically applied after the Bidirectional Algorithm is used and is limited to characters within the same directional run.
2474
		if ($arabic) {
2475
			$endedletter = array(1569,1570,1571,1572,1573,1575,1577,1583,1584,1585,1586,1608,1688);
2476
			$alfletter = array(1570,1571,1573,1575);
2477
			$chardata2 = $chardata;
2478
			$laaletter = false;
2479
			$charAL = array();
2480
			$x = 0;
2481
			for ($i=0; $i < $numchars; ++$i) {
2482
				if ((TCPDF_FONT_DATA::$uni_type[$chardata[$i]['char']] == 'AL') OR ($chardata[$i]['char'] == 32) OR ($chardata[$i]['char'] == 8204)) {
2483
					$charAL[$x] = $chardata[$i];
2484
					$charAL[$x]['i'] = $i;
2485
					$chardata[$i]['x'] = $x;
2486
					++$x;
2487
				}
2488
			}
2489
			$numAL = $x;
2490
			for ($i=0; $i < $numchars; ++$i) {
2491
				$thischar = $chardata[$i];
2492
				if ($i > 0) {
2493
					$prevchar = $chardata[($i-1)];
0 ignored issues
show
Unused Code introduced by
The assignment to $prevchar is dead and can be removed.
Loading history...
2494
				} else {
2495
					$prevchar = false;
2496
				}
2497
				if (($i+1) < $numchars) {
2498
					$nextchar = $chardata[($i+1)];
0 ignored issues
show
Unused Code introduced by
The assignment to $nextchar is dead and can be removed.
Loading history...
2499
				} else {
2500
					$nextchar = false;
2501
				}
2502
				if (TCPDF_FONT_DATA::$uni_type[$thischar['char']] == 'AL') {
2503
					$x = $thischar['x'];
2504
					if ($x > 0) {
2505
						$prevchar = $charAL[($x-1)];
2506
					} else {
2507
						$prevchar = false;
2508
					}
2509
					if (($x+1) < $numAL) {
2510
						$nextchar = $charAL[($x+1)];
2511
					} else {
2512
						$nextchar = false;
2513
					}
2514
					// if laa letter
2515
					if (($prevchar !== false) AND ($prevchar['char'] == 1604) AND (in_array($thischar['char'], $alfletter))) {
2516
						$arabicarr = TCPDF_FONT_DATA::$uni_laa_array;
2517
						$laaletter = true;
2518
						if ($x > 1) {
2519
							$prevchar = $charAL[($x-2)];
2520
						} else {
2521
							$prevchar = false;
2522
						}
2523
					} else {
2524
						$arabicarr = TCPDF_FONT_DATA::$uni_arabicsubst;
2525
						$laaletter = false;
2526
					}
2527
					if (($prevchar !== false) AND ($nextchar !== false) AND
2528
						((TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'NSM')) AND
2529
						((TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'NSM')) AND
2530
						($prevchar['type'] == $thischar['type']) AND
2531
						($nextchar['type'] == $thischar['type']) AND
2532
						($nextchar['char'] != 1567)) {
2533
						if (in_array($prevchar['char'], $endedletter)) {
2534
							if (isset($arabicarr[$thischar['char']][2])) {
2535
								// initial
2536
								$chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
2537
							}
2538
						} else {
2539
							if (isset($arabicarr[$thischar['char']][3])) {
2540
								// medial
2541
								$chardata2[$i]['char'] = $arabicarr[$thischar['char']][3];
2542
							}
2543
						}
2544
					} elseif (($nextchar !== false) AND
2545
						((TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'NSM')) AND
2546
						($nextchar['type'] == $thischar['type']) AND
2547
						($nextchar['char'] != 1567)) {
2548
						if (isset($arabicarr[$chardata[$i]['char']][2])) {
2549
							// initial
2550
							$chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
2551
						}
2552
					} elseif ((($prevchar !== false) AND
2553
						((TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'NSM')) AND
2554
						($prevchar['type'] == $thischar['type'])) OR
2555
						(($nextchar !== false) AND ($nextchar['char'] == 1567))) {
2556
						// final
2557
						if (($i > 1) AND ($thischar['char'] == 1607) AND
2558
							($chardata[$i-1]['char'] == 1604) AND
2559
							($chardata[$i-2]['char'] == 1604)) {
2560
							//Allah Word
2561
							// mark characters to delete with false
2562
							$chardata2[$i-2]['char'] = false;
2563
							$chardata2[$i-1]['char'] = false;
2564
							$chardata2[$i]['char'] = 65010;
2565
						} else {
2566
							if (($prevchar !== false) AND in_array($prevchar['char'], $endedletter)) {
2567
								if (isset($arabicarr[$thischar['char']][0])) {
2568
									// isolated
2569
									$chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
2570
								}
2571
							} else {
2572
								if (isset($arabicarr[$thischar['char']][1])) {
2573
									// final
2574
									$chardata2[$i]['char'] = $arabicarr[$thischar['char']][1];
2575
								}
2576
							}
2577
						}
2578
					} elseif (isset($arabicarr[$thischar['char']][0])) {
2579
						// isolated
2580
						$chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
2581
					}
2582
					// if laa letter
2583
					if ($laaletter) {
2584
						// mark characters to delete with false
2585
						$chardata2[($charAL[($x-1)]['i'])]['char'] = false;
2586
					}
2587
				} // end if AL (Arabic Letter)
2588
			} // end for each char
2589
			/*
2590
			 * Combining characters that can occur with Arabic Shadda (0651 HEX, 1617 DEC) are replaced.
2591
			 * Putting the combining mark and shadda in the same glyph allows us to avoid the two marks overlapping each other in an illegible manner.
2592
			 */
2593
			for ($i = 0; $i < ($numchars-1); ++$i) {
2594
				if (($chardata2[$i]['char'] == 1617) AND (isset(TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])]))) {
2595
					// check if the subtitution font is defined on current font
2596
					if (isset($currentfont['cw'][(TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])])])) {
2597
						$chardata2[$i]['char'] = false;
2598
						$chardata2[$i+1]['char'] = TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])];
2599
					}
2600
				}
2601
			}
2602
			// remove marked characters
2603
			foreach ($chardata2 as $key => $value) {
2604
				if ($value['char'] === false) {
2605
					unset($chardata2[$key]);
2606
				}
2607
			}
2608
			$chardata = array_values($chardata2);
2609
			$numchars = count($chardata);
2610
			unset($chardata2);
2611
			unset($arabicarr);
2612
			unset($laaletter);
2613
			unset($charAL);
2614
		}
2615
2616
		// L2. From the highest level found in the text to the lowest odd level on each line, including intermediate levels not actually present in the text, reverse any contiguous sequence of characters that are at that level or higher.
2617
		for ($j=$maxlevel; $j > 0; $j--) {
2618
			$ordarray = Array();
2619
			$revarr = Array();
2620
			$onlevel = false;
2621
			for ($i=0; $i < $numchars; ++$i) {
2622
				if ($chardata[$i]['level'] >= $j) {
2623
					$onlevel = true;
2624
					if (isset(TCPDF_FONT_DATA::$uni_mirror[$chardata[$i]['char']])) {
2625
						// L4. A character is depicted by a mirrored glyph if and only if (a) the resolved directionality of that character is R, and (b) the Bidi_Mirrored property value of that character is true.
2626
						$chardata[$i]['char'] = TCPDF_FONT_DATA::$uni_mirror[$chardata[$i]['char']];
2627
					}
2628
					$revarr[] = $chardata[$i];
2629
				} else {
2630
					if ($onlevel) {
2631
						$revarr = array_reverse($revarr);
2632
						$ordarray = array_merge($ordarray, $revarr);
2633
						$revarr = Array();
2634
						$onlevel = false;
2635
					}
2636
					$ordarray[] = $chardata[$i];
2637
				}
2638
			}
2639
			if ($onlevel) {
2640
				$revarr = array_reverse($revarr);
2641
				$ordarray = array_merge($ordarray, $revarr);
2642
			}
2643
			$chardata = $ordarray;
2644
		}
2645
		$ordarray = array();
2646
		foreach ($chardata as $cd) {
2647
			$ordarray[] = $cd['char'];
2648
			// store char values for subsetting
2649
			$currentfont['subsetchars'][$cd['char']] = true;
2650
		}
2651
		return $ordarray;
2652
	}
2653
2654
} // END OF TCPDF_FONTS CLASS
2655
2656
//============================================================+
2657
// END OF FILE
2658
//============================================================+
2659