PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / trunk
Matomo Analytics – Powerful, Privacy-First Insights for WordPress vtrunk
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / vendor / tecnickcom / tcpdf / include / tcpdf_fonts.php
matomo / app / vendor / tecnickcom / tcpdf / include Last commit date
barcodes 2 months ago sRGB.icc 6 years ago tcpdf_colors.php 2 months ago tcpdf_filters.php 2 months ago tcpdf_font_data.php 2 months ago tcpdf_fonts.php 2 months ago tcpdf_images.php 2 months ago tcpdf_static.php 2 months ago
tcpdf_fonts.php
2630 lines
1 <?php
2
3 namespace {
4 //============================================================+
5 // File name : tcpdf_fonts.php
6 // Version : 1.1.1
7 // Begin : 2008-01-01
8 // Last Update : 2024-12-23
9 // Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
10 // License : GNU-LGPL v3 (https://www.gnu.org/copyleft/lesser.html)
11 // -------------------------------------------------------------------
12 // Copyright (C) 2008-2026 Nicola Asuni - Tecnick.com LTD
13 //
14 // This file is part of TCPDF software library.
15 //
16 // TCPDF is free software: you can redistribute it and/or modify it
17 // under the terms of the GNU Lesser General Public License as
18 // published by the Free Software Foundation, either version 3 of the
19 // License, or (at your option) any later version.
20 //
21 // TCPDF is distributed in the hope that it will be useful, but
22 // WITHOUT ANY WARRANTY; without even the implied warranty of
23 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
24 // See the GNU Lesser General Public License for more details.
25 //
26 // You should have received a copy of the GNU Lesser General Public License
27 // along with TCPDF. If not, see <https://www.gnu.org/licenses/>.
28 //
29 // See LICENSE.TXT file for more information.
30 // -------------------------------------------------------------------
31 //
32 // Description :Font methods for TCPDF library.
33 //
34 //============================================================+
35 /**
36 * @file
37 * Unicode data and font methods for TCPDF library.
38 * @author Nicola Asuni
39 * @package com.tecnick.tcpdf
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 - info@tecnick.com
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 * Convert and add the selected TrueType or Type1 font to the fonts folder (that must be writeable).
57 * @param string $fontfile Font file (full path).
58 * @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.
59 * @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.
60 * @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.
61 * @param string $outpath Output path for generated font files (must be writeable by the web server). Leave empty for default font folder.
62 * @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).
63 * @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.
64 * @param boolean $addcbbox If true includes the character bounding box information on the php font file.
65 * @param boolean $link If true link to system font instead of copying the font data (not transportable) - Note: do not work with Type1 fonts.
66 * @return string|false TCPDF font name or boolean false in case of error.
67 * @author Nicola Asuni
68 * @since 5.9.123 (2010-09-30)
69 * @public static
70 */
71 public static function addTTFfont($fontfile, $fonttype = '', $enc = '', $flags = 32, $outpath = '', $platid = 3, $encid = 1, $addcbbox = \false, $link = \false)
72 {
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 case 'Type1':
130 $fmetric['type'] = 'Type1';
131 if (empty($enc) and ($flags & 4) == 0) {
132 $enc = 'cp1252';
133 }
134 break;
135 case 'TrueType':
136 $fmetric['type'] = 'TrueType';
137 break;
138 case 'TrueTypeUnicode':
139 default:
140 $fmetric['type'] = 'TrueTypeUnicode';
141 break;
142 }
143 // set encoding maps (if any)
144 $fmetric['enc'] = \preg_replace('/[^A-Za-z0-9_\\-]/', '', $enc);
145 $fmetric['diff'] = '';
146 if ($fmetric['type'] == 'TrueType' or $fmetric['type'] == 'Type1') {
147 if (!empty($enc) and $enc != 'cp1252' and isset(\TCPDF_FONT_DATA::$encmap[$enc])) {
148 // build differences from reference encoding
149 $enc_ref = \TCPDF_FONT_DATA::$encmap['cp1252'];
150 $enc_target = \TCPDF_FONT_DATA::$encmap[$enc];
151 $last = 0;
152 for ($i = 32; $i <= 255; ++$i) {
153 if ($enc_target[$i] != $enc_ref[$i]) {
154 if ($i != $last + 1) {
155 $fmetric['diff'] .= $i . ' ';
156 }
157 $last = $i;
158 $fmetric['diff'] .= '/' . $enc_target[$i] . ' ';
159 }
160 }
161 }
162 }
163 // parse the font by type
164 if ($fmetric['type'] == 'Type1') {
165 // ---------- TYPE 1 ----------
166 // read first segment
167 $a = \unpack('Cmarker/Ctype/Vsize', \substr($font, 0, 6));
168 if ($a['marker'] != 128) {
169 // Font file is not a valid binary Type1
170 return \false;
171 }
172 $fmetric['size1'] = $a['size'];
173 $data = \substr($font, 6, $fmetric['size1']);
174 // read second segment
175 $a = \unpack('Cmarker/Ctype/Vsize', \substr($font, 6 + $fmetric['size1'], 6));
176 if ($a['marker'] != 128) {
177 // Font file is not a valid binary Type1
178 return \false;
179 }
180 $fmetric['size2'] = $a['size'];
181 $encrypted = \substr($font, 12 + $fmetric['size1'], $fmetric['size2']);
182 $data .= $encrypted;
183 // store compressed font
184 $fmetric['file'] .= '.z';
185 $fp = \TCPDF_STATIC::fopenLocal($outpath . $fmetric['file'], 'wb');
186 \fwrite($fp, \gzcompress($data));
187 \fclose($fp);
188 // get font info
189 $fmetric['Flags'] = $flags;
190 \preg_match('#/FullName[\\s]*+\\(([^\\)]*+)#', $font, $matches);
191 $fmetric['name'] = \preg_replace('/[^a-zA-Z0-9_\\-]/', '', $matches[1]);
192 \preg_match('#/FontBBox[\\s]*+{([^}]*+)#', $font, $matches);
193 $rawbvl = \explode(' ', \trim($matches[1]));
194 $bvl = [(int) $rawbvl[0], (int) $rawbvl[1], (int) $rawbvl[2], (int) $rawbvl[3]];
195 $fmetric['bbox'] = \implode(' ', $bvl);
196 $fmetric['Ascent'] = $bvl[3];
197 $fmetric['Descent'] = $bvl[1];
198 \preg_match('#/ItalicAngle[\\s]*+([0-9\\+\\-]*+)#', $font, $matches);
199 $fmetric['italicAngle'] = \intval($matches[1]);
200 if ($fmetric['italicAngle'] != 0) {
201 $fmetric['Flags'] |= 64;
202 }
203 \preg_match('#/UnderlinePosition[\\s]*+([0-9\\+\\-]*+)#', $font, $matches);
204 $fmetric['underlinePosition'] = \intval($matches[1]);
205 \preg_match('#/UnderlineThickness[\\s]*+([0-9\\+\\-]*+)#', $font, $matches);
206 $fmetric['underlineThickness'] = \intval($matches[1]);
207 \preg_match('#/isFixedPitch[\\s]*+([^\\s]*+)#', $font, $matches);
208 if ($matches[1] == 'true') {
209 $fmetric['Flags'] |= 1;
210 }
211 // get internal map
212 $imap = array();
213 if (\preg_match_all('#dup[\\s]([0-9]+)[\\s]*+/([^\\s]*+)[\\s]put#sU', $font, $fmap, \PREG_SET_ORDER) > 0) {
214 foreach ($fmap as $v) {
215 $imap[$v[2]] = $v[1];
216 }
217 }
218 // decrypt eexec encrypted part
219 $r = 55665;
220 // eexec encryption constant
221 $c1 = 52845;
222 $c2 = 22719;
223 $elen = \strlen($encrypted);
224 $eplain = '';
225 for ($i = 0; $i < $elen; ++$i) {
226 $chr = \ord($encrypted[$i]);
227 $eplain .= \chr($chr ^ $r >> 8);
228 $r = (($chr + $r) * $c1 + $c2) % 65536;
229 }
230 if (\preg_match('#/ForceBold[\\s]*+([^\\s]*+)#', $eplain, $matches) > 0) {
231 if ($matches[1] == 'true') {
232 $fmetric['Flags'] |= 0x40000;
233 }
234 }
235 if (\preg_match('#/StdVW[\\s]*+\\[([^\\]]*+)#', $eplain, $matches) > 0) {
236 $fmetric['StemV'] = \intval($matches[1]);
237 } else {
238 $fmetric['StemV'] = 70;
239 }
240 if (\preg_match('#/StdHW[\\s]*+\\[([^\\]]*+)#', $eplain, $matches) > 0) {
241 $fmetric['StemH'] = \intval($matches[1]);
242 } else {
243 $fmetric['StemH'] = 30;
244 }
245 if (\preg_match('#/BlueValues[\\s]*+\\[([^\\]]*+)#', $eplain, $matches) > 0) {
246 $bv = \explode(' ', $matches[1]);
247 if (\count($bv) >= 6) {
248 $v1 = \intval($bv[2]);
249 $v2 = \intval($bv[4]);
250 if ($v1 <= $v2) {
251 $fmetric['XHeight'] = $v1;
252 $fmetric['CapHeight'] = $v2;
253 } else {
254 $fmetric['XHeight'] = $v2;
255 $fmetric['CapHeight'] = $v1;
256 }
257 } else {
258 $fmetric['XHeight'] = 450;
259 $fmetric['CapHeight'] = 700;
260 }
261 } else {
262 $fmetric['XHeight'] = 450;
263 $fmetric['CapHeight'] = 700;
264 }
265 // get the number of random bytes at the beginning of charstrings
266 if (\preg_match('#/lenIV[\\s]*+([\\d]*+)#', $eplain, $matches) > 0) {
267 $lenIV = \intval($matches[1]);
268 } else {
269 $lenIV = 4;
270 }
271 $fmetric['Leading'] = 0;
272 // get charstring data
273 $eplain = \substr($eplain, \strpos($eplain, '/CharStrings') + 1);
274 \preg_match_all('#/([A-Za-z0-9\\.]*+)[\\s][0-9]+[\\s]RD[\\s](.*)[\\s]ND#sU', $eplain, $matches, \PREG_SET_ORDER);
275 if (!empty($enc) and isset(\TCPDF_FONT_DATA::$encmap[$enc])) {
276 $enc_map = \TCPDF_FONT_DATA::$encmap[$enc];
277 } else {
278 $enc_map = \false;
279 }
280 $fmetric['cw'] = '';
281 $fmetric['MaxWidth'] = 0;
282 $cwidths = array();
283 foreach ($matches as $k => $v) {
284 $cid = 0;
285 if (isset($imap[$v[1]])) {
286 $cid = $imap[$v[1]];
287 } elseif ($enc_map !== \false) {
288 $cid = \array_search($v[1], $enc_map);
289 if ($cid === \false) {
290 $cid = 0;
291 } elseif ($cid > 1000) {
292 $cid -= 1000;
293 }
294 }
295 // decrypt charstring encrypted part
296 $r = 4330;
297 // charstring encryption constant
298 $c1 = 52845;
299 $c2 = 22719;
300 $cd = $v[2];
301 $clen = \strlen($cd);
302 $ccom = array();
303 for ($i = 0; $i < $clen; ++$i) {
304 $chr = \ord($cd[$i]);
305 $ccom[] = $chr ^ $r >> 8;
306 $r = (($chr + $r) * $c1 + $c2) % 65536;
307 }
308 // decode numbers
309 $cdec = array();
310 $ck = 0;
311 $i = $lenIV;
312 while ($i < $clen) {
313 if ($ccom[$i] < 32) {
314 $cdec[$ck] = $ccom[$i];
315 if ($ck > 0 and $cdec[$ck] == 13) {
316 // hsbw command: update width
317 $cwidths[$cid] = $cdec[$ck - 1];
318 }
319 ++$i;
320 } elseif ($ccom[$i] >= 32 and $ccom[$i] <= 246) {
321 $cdec[$ck] = $ccom[$i] - 139;
322 ++$i;
323 } elseif ($ccom[$i] >= 247 and $ccom[$i] <= 250) {
324 $cdec[$ck] = ($ccom[$i] - 247) * 256 + $ccom[$i + 1] + 108;
325 $i += 2;
326 } elseif ($ccom[$i] >= 251 and $ccom[$i] <= 254) {
327 $cdec[$ck] = -($ccom[$i] - 251) * 256 - $ccom[$i + 1] - 108;
328 $i += 2;
329 } elseif ($ccom[$i] == 255) {
330 $sval = \chr($ccom[$i + 1]) . \chr($ccom[$i + 2]) . \chr($ccom[$i + 3]) . \chr($ccom[$i + 4]);
331 $vsval = \unpack('li', $sval);
332 $cdec[$ck] = $vsval['i'];
333 $i += 5;
334 }
335 ++$ck;
336 }
337 }
338 // end for each matches
339 $fmetric['MissingWidth'] = $cwidths[0];
340 $fmetric['MaxWidth'] = $fmetric['MissingWidth'];
341 $fmetric['AvgWidth'] = 0;
342 // set chars widths
343 for ($cid = 0; $cid <= 255; ++$cid) {
344 if (isset($cwidths[$cid])) {
345 if ($cwidths[$cid] > $fmetric['MaxWidth']) {
346 $fmetric['MaxWidth'] = $cwidths[$cid];
347 }
348 $fmetric['AvgWidth'] += $cwidths[$cid];
349 $fmetric['cw'] .= ',' . $cid . '=>' . $cwidths[$cid];
350 } else {
351 $fmetric['cw'] .= ',' . $cid . '=>' . $fmetric['MissingWidth'];
352 }
353 }
354 $fmetric['AvgWidth'] = \round($fmetric['AvgWidth'] / \count($cwidths));
355 } else {
356 // ---------- TRUE TYPE ----------
357 $offset = 0;
358 // 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;
404 // skip flags
405 // get FUnits
406 $fmetric['unitsPerEm'] = \TCPDF_STATIC::_getUSHORT($font, $offset);
407 $offset += 2;
408 // units ratio constant
409 $urk = 1000 / $fmetric['unitsPerEm'];
410 $offset += 16;
411 // skip created, modified
412 $xMin = \round(\TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
413 $offset += 2;
414 $yMin = \round(\TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
415 $offset += 2;
416 $xMax = \round(\TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
417 $offset += 2;
418 $yMax = \round(\TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
419 $offset += 2;
420 $fmetric['bbox'] = '' . $xMin . ' ' . $yMin . ' ' . $xMax . ' ' . $yMax . '';
421 $macStyle = \TCPDF_STATIC::_getUSHORT($font, $offset);
422 $offset += 2;
423 // PDF font flags
424 $fmetric['Flags'] = $flags;
425 if (($macStyle & 2) == 2) {
426 // italic flag
427 $fmetric['Flags'] |= 64;
428 }
429 // get offset mode (indexToLocFormat : 0 = short, 1 = long)
430 $offset = $table['head']['offset'] + 50;
431 $short_offset = \TCPDF_STATIC::_getSHORT($font, $offset) == 0;
432 $offset += 2;
433 // get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
434 $indexToLoc = array();
435 $offset = $table['loca']['offset'];
436 if ($short_offset) {
437 // short version
438 $tot_num_glyphs = \floor($table['loca']['length'] / 2);
439 // numGlyphs + 1
440 for ($i = 0; $i < $tot_num_glyphs; ++$i) {
441 $indexToLoc[$i] = \TCPDF_STATIC::_getUSHORT($font, $offset) * 2;
442 if (isset($indexToLoc[$i - 1]) && $indexToLoc[$i] == $indexToLoc[$i - 1]) {
443 // the last glyph didn't have an outline
444 unset($indexToLoc[$i - 1]);
445 }
446 $offset += 2;
447 }
448 } else {
449 // long version
450 $tot_num_glyphs = \floor($table['loca']['length'] / 4);
451 // numGlyphs + 1
452 for ($i = 0; $i < $tot_num_glyphs; ++$i) {
453 $indexToLoc[$i] = \TCPDF_STATIC::_getULONG($font, $offset);
454 if (isset($indexToLoc[$i - 1]) && $indexToLoc[$i] == $indexToLoc[$i - 1]) {
455 // the last glyph didn't have an outline
456 unset($indexToLoc[$i - 1]);
457 }
458 $offset += 4;
459 }
460 }
461 // get glyphs indexes of chars from cmap table
462 $offset = $table['cmap']['offset'] + 2;
463 $numEncodingTables = \TCPDF_STATIC::_getUSHORT($font, $offset);
464 $offset += 2;
465 $encodingTables = array();
466 for ($i = 0; $i < $numEncodingTables; ++$i) {
467 $encodingTables[$i]['platformID'] = \TCPDF_STATIC::_getUSHORT($font, $offset);
468 $offset += 2;
469 $encodingTables[$i]['encodingID'] = \TCPDF_STATIC::_getUSHORT($font, $offset);
470 $offset += 2;
471 $encodingTables[$i]['offset'] = \TCPDF_STATIC::_getULONG($font, $offset);
472 $offset += 4;
473 }
474 // ---------- get os/2 metrics ----------
475 $offset = $table['OS/2']['offset'];
476 $offset += 2;
477 // skip version
478 // xAvgCharWidth
479 $fmetric['AvgWidth'] = \round(\TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
480 $offset += 2;
481 // usWeightClass
482 $usWeightClass = \round(\TCPDF_STATIC::_getUFWORD($font, $offset) * $urk);
483 // estimate StemV and StemH (400 = usWeightClass for Normal - Regular font)
484 $fmetric['StemV'] = \round(70 * $usWeightClass / 400);
485 $fmetric['StemH'] = \round(30 * $usWeightClass / 400);
486 $offset += 2;
487 $offset += 2;
488 // usWidthClass
489 $fsType = \TCPDF_STATIC::_getSHORT($font, $offset);
490 $offset += 2;
491 if ($fsType == 2) {
492 // This Font cannot be modified, embedded or exchanged in any manner without first obtaining permission of the legal owner.
493 return \false;
494 }
495 // ---------- get font name ----------
496 $fmetric['name'] = '';
497 $offset = $table['name']['offset'];
498 $offset += 2;
499 // skip Format selector (=0).
500 // Number of NameRecords that follow n.
501 $numNameRecords = \TCPDF_STATIC::_getUSHORT($font, $offset);
502 $offset += 2;
503 // Offset to start of string storage (from start of table).
504 $stringStorageOffset = \TCPDF_STATIC::_getUSHORT($font, $offset);
505 $offset += 2;
506 for ($i = 0; $i < $numNameRecords; ++$i) {
507 $offset += 6;
508 // skip Platform ID, Platform-specific encoding ID, Language ID.
509 // Name ID.
510 $nameID = \TCPDF_STATIC::_getUSHORT($font, $offset);
511 $offset += 2;
512 if ($nameID == 6) {
513 // String length (in bytes).
514 $stringLength = \TCPDF_STATIC::_getUSHORT($font, $offset);
515 $offset += 2;
516 // String offset from start of storage area (in bytes).
517 $stringOffset = \TCPDF_STATIC::_getUSHORT($font, $offset);
518 $offset += 2;
519 $offset = $table['name']['offset'] + $stringStorageOffset + $stringOffset;
520 $fmetric['name'] = \substr($font, $offset, $stringLength);
521 $fmetric['name'] = \preg_replace('/[^a-zA-Z0-9_\\-]/', '', $fmetric['name']);
522 break;
523 } else {
524 $offset += 4;
525 // skip String length, String offset
526 }
527 }
528 if (empty($fmetric['name'])) {
529 $fmetric['name'] = $font_name;
530 }
531 // ---------- get post data ----------
532 $offset = $table['post']['offset'];
533 $offset += 4;
534 // skip Format Type
535 $fmetric['italicAngle'] = \TCPDF_STATIC::_getFIXED($font, $offset);
536 $offset += 4;
537 $fmetric['underlinePosition'] = \round(\TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
538 $offset += 2;
539 $fmetric['underlineThickness'] = \round(\TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
540 $offset += 2;
541 $isFixedPitch = \TCPDF_STATIC::_getULONG($font, $offset) == 0 ? \false : \true;
542 $offset += 2;
543 if ($isFixedPitch) {
544 $fmetric['Flags'] |= 1;
545 }
546 // ---------- get hhea data ----------
547 $offset = $table['hhea']['offset'];
548 $offset += 4;
549 // skip Table version number
550 // Ascender
551 $fmetric['Ascent'] = \round(\TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
552 $offset += 2;
553 // Descender
554 $fmetric['Descent'] = \round(\TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
555 $offset += 2;
556 // LineGap
557 $fmetric['Leading'] = \round(\TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
558 $offset += 2;
559 // advanceWidthMax
560 $fmetric['MaxWidth'] = \round(\TCPDF_STATIC::_getUFWORD($font, $offset) * $urk);
561 $offset += 2;
562 $offset += 22;
563 // skip some values
564 // get the number of hMetric entries in hmtx table
565 $numberOfHMetrics = \TCPDF_STATIC::_getUSHORT($font, $offset);
566 // ---------- get maxp data ----------
567 $offset = $table['maxp']['offset'];
568 $offset += 4;
569 // skip Table version number
570 // get the the number of glyphs in the font.
571 $numGlyphs = \TCPDF_STATIC::_getUSHORT($font, $offset);
572 // ---------- get CIDToGIDMap ----------
573 $ctg = array();
574 $c = 0;
575 foreach ($encodingTables as $enctable) {
576 // get only specified Platform ID and Encoding ID
577 if ($enctable['platformID'] == $platid and $enctable['encodingID'] == $encid) {
578 $offset = $table['cmap']['offset'] + $enctable['offset'];
579 $format = \TCPDF_STATIC::_getUSHORT($font, $offset);
580 $offset += 2;
581 switch ($format) {
582 case 0:
583 // Format 0: Byte encoding table
584 $offset += 4;
585 // skip length and version/language
586 for ($c = 0; $c < 256; ++$c) {
587 $g = \TCPDF_STATIC::_getBYTE($font, $offset);
588 $ctg[$c] = $g;
589 ++$offset;
590 }
591 break;
592 case 2:
593 // Format 2: High-byte mapping through table
594 $offset += 4;
595 // skip length and version/language
596 $numSubHeaders = 0;
597 for ($i = 0; $i < 256; ++$i) {
598 // Array that maps high bytes to subHeaders: value is subHeader index * 8.
599 $subHeaderKeys[$i] = \TCPDF_STATIC::_getUSHORT($font, $offset) / 8;
600 $offset += 2;
601 if ($numSubHeaders < $subHeaderKeys[$i]) {
602 $numSubHeaders = $subHeaderKeys[$i];
603 }
604 }
605 // the number of subHeaders is equal to the max of subHeaderKeys + 1
606 ++$numSubHeaders;
607 // read subHeader structures
608 $subHeaders = array();
609 $numGlyphIndexArray = 0;
610 for ($k = 0; $k < $numSubHeaders; ++$k) {
611 $subHeaders[$k]['firstCode'] = \TCPDF_STATIC::_getUSHORT($font, $offset);
612 $offset += 2;
613 $subHeaders[$k]['entryCount'] = \TCPDF_STATIC::_getUSHORT($font, $offset);
614 $offset += 2;
615 $subHeaders[$k]['idDelta'] = \TCPDF_STATIC::_getUSHORT($font, $offset);
616 $offset += 2;
617 $subHeaders[$k]['idRangeOffset'] = \TCPDF_STATIC::_getUSHORT($font, $offset);
618 $offset += 2;
619 $subHeaders[$k]['idRangeOffset'] -= 2 + ($numSubHeaders - $k - 1) * 8;
620 $subHeaders[$k]['idRangeOffset'] /= 2;
621 $numGlyphIndexArray += $subHeaders[$k]['entryCount'];
622 }
623 for ($k = 0; $k < $numGlyphIndexArray; ++$k) {
624 $glyphIndexArray[$k] = \TCPDF_STATIC::_getUSHORT($font, $offset);
625 $offset += 2;
626 }
627 for ($i = 0; $i < 256; ++$i) {
628 $k = $subHeaderKeys[$i];
629 if ($k == 0) {
630 // one byte code
631 $c = $i;
632 $g = $glyphIndexArray[0];
633 $ctg[$c] = $g;
634 } else {
635 // two bytes code
636 $start_byte = $subHeaders[$k]['firstCode'];
637 $end_byte = $start_byte + $subHeaders[$k]['entryCount'];
638 for ($j = $start_byte; $j < $end_byte; ++$j) {
639 // combine high and low bytes
640 $c = ($i << 8) + $j;
641 $idRangeOffset = $subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode'];
642 $g = ($glyphIndexArray[$idRangeOffset] + $subHeaders[$k]['idDelta']) % 65536;
643 if ($g < 0) {
644 $g = 0;
645 }
646 $ctg[$c] = $g;
647 }
648 }
649 }
650 break;
651 case 4:
652 // Format 4: Segment mapping to delta values
653 $length = \TCPDF_STATIC::_getUSHORT($font, $offset);
654 $offset += 2;
655 $offset += 2;
656 // skip version/language
657 $segCount = \floor(\TCPDF_STATIC::_getUSHORT($font, $offset) / 2);
658 $offset += 2;
659 $offset += 6;
660 // skip searchRange, entrySelector, rangeShift
661 $endCount = array();
662 // array of end character codes for each segment
663 for ($k = 0; $k < $segCount; ++$k) {
664 $endCount[$k] = \TCPDF_STATIC::_getUSHORT($font, $offset);
665 $offset += 2;
666 }
667 $offset += 2;
668 // skip reservedPad
669 $startCount = array();
670 // array of start character codes for each segment
671 for ($k = 0; $k < $segCount; ++$k) {
672 $startCount[$k] = \TCPDF_STATIC::_getUSHORT($font, $offset);
673 $offset += 2;
674 }
675 $idDelta = array();
676 // delta for all character codes in segment
677 for ($k = 0; $k < $segCount; ++$k) {
678 $idDelta[$k] = \TCPDF_STATIC::_getUSHORT($font, $offset);
679 $offset += 2;
680 }
681 $idRangeOffset = array();
682 // Offsets into glyphIdArray or 0
683 for ($k = 0; $k < $segCount; ++$k) {
684 $idRangeOffset[$k] = \TCPDF_STATIC::_getUSHORT($font, $offset);
685 $offset += 2;
686 }
687 $gidlen = \floor($length / 2) - 8 - 4 * $segCount;
688 $glyphIdArray = array();
689 // glyph index array
690 for ($k = 0; $k < $gidlen; ++$k) {
691 $glyphIdArray[$k] = \TCPDF_STATIC::_getUSHORT($font, $offset);
692 $offset += 2;
693 }
694 for ($k = 0; $k < $segCount - 1; ++$k) {
695 for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
696 if ($idRangeOffset[$k] == 0) {
697 $g = ($idDelta[$k] + $c) % 65536;
698 } else {
699 $gid = \floor($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k);
700 $g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536;
701 }
702 if ($g < 0) {
703 $g = 0;
704 }
705 $ctg[$c] = $g;
706 }
707 }
708 break;
709 case 6:
710 // Format 6: Trimmed table mapping
711 $offset += 4;
712 // skip length and version/language
713 $firstCode = \TCPDF_STATIC::_getUSHORT($font, $offset);
714 $offset += 2;
715 $entryCount = \TCPDF_STATIC::_getUSHORT($font, $offset);
716 $offset += 2;
717 for ($k = 0; $k < $entryCount; ++$k) {
718 $c = $k + $firstCode;
719 $g = \TCPDF_STATIC::_getUSHORT($font, $offset);
720 $offset += 2;
721 $ctg[$c] = $g;
722 }
723 break;
724 case 8:
725 // Format 8: Mixed 16-bit and 32-bit coverage
726 $offset += 10;
727 // skip reserved, length and version/language
728 for ($k = 0; $k < 8192; ++$k) {
729 $is32[$k] = \TCPDF_STATIC::_getBYTE($font, $offset);
730 ++$offset;
731 }
732 $nGroups = \TCPDF_STATIC::_getULONG($font, $offset);
733 $offset += 4;
734 for ($i = 0; $i < $nGroups; ++$i) {
735 $startCharCode = \TCPDF_STATIC::_getULONG($font, $offset);
736 $offset += 4;
737 $endCharCode = \TCPDF_STATIC::_getULONG($font, $offset);
738 $offset += 4;
739 $startGlyphID = \TCPDF_STATIC::_getULONG($font, $offset);
740 $offset += 4;
741 for ($k = $startCharCode; $k <= $endCharCode; ++$k) {
742 $is32idx = \floor($c / 8);
743 if (isset($is32[$is32idx]) and ($is32[$is32idx] & 1 << 7 - $c % 8) == 0) {
744 $c = $k;
745 } else {
746 // 32 bit format
747 // convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4)
748 //LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232
749 //SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888
750 $c = (55232 + ($k >> 10) << 10) + (0xdc00 + ($k & 0x3ff)) - 56613888;
751 }
752 $ctg[$c] = 0;
753 ++$startGlyphID;
754 }
755 }
756 break;
757 case 10:
758 // Format 10: Trimmed array
759 $offset += 10;
760 // skip reserved, length and version/language
761 $startCharCode = \TCPDF_STATIC::_getULONG($font, $offset);
762 $offset += 4;
763 $numChars = \TCPDF_STATIC::_getULONG($font, $offset);
764 $offset += 4;
765 for ($k = 0; $k < $numChars; ++$k) {
766 $c = $k + $startCharCode;
767 $g = \TCPDF_STATIC::_getUSHORT($font, $offset);
768 $ctg[$c] = $g;
769 $offset += 2;
770 }
771 break;
772 case 12:
773 // Format 12: Segmented coverage
774 $offset += 10;
775 // skip length and version/language
776 $nGroups = \TCPDF_STATIC::_getULONG($font, $offset);
777 $offset += 4;
778 for ($k = 0; $k < $nGroups; ++$k) {
779 $startCharCode = \TCPDF_STATIC::_getULONG($font, $offset);
780 $offset += 4;
781 $endCharCode = \TCPDF_STATIC::_getULONG($font, $offset);
782 $offset += 4;
783 $startGlyphCode = \TCPDF_STATIC::_getULONG($font, $offset);
784 $offset += 4;
785 for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
786 $ctg[$c] = $startGlyphCode;
787 ++$startGlyphCode;
788 }
789 }
790 break;
791 case 13:
792 // Format 13: Many-to-one range mappings
793 // to be implemented ...
794 break;
795 case 14:
796 // Format 14: Unicode Variation Sequences
797 // to be implemented ...
798 break;
799 }
800 }
801 }
802 if (!isset($ctg[0])) {
803 $ctg[0] = 0;
804 }
805 // get xHeight (height of x)
806 $offset = $table['glyf']['offset'] + $indexToLoc[$ctg[120]] + 4;
807 $yMin = \TCPDF_STATIC::_getFWORD($font, $offset);
808 $offset += 4;
809 $yMax = \TCPDF_STATIC::_getFWORD($font, $offset);
810 $offset += 2;
811 $fmetric['XHeight'] = \round(($yMax - $yMin) * $urk);
812 // get CapHeight (height of H)
813 $offset = $table['glyf']['offset'] + $indexToLoc[$ctg[72]] + 4;
814 $yMin = \TCPDF_STATIC::_getFWORD($font, $offset);
815 $offset += 4;
816 $yMax = \TCPDF_STATIC::_getFWORD($font, $offset);
817 $offset += 2;
818 $fmetric['CapHeight'] = \round(($yMax - $yMin) * $urk);
819 // ceate widths array
820 $cw = array();
821 $offset = $table['hmtx']['offset'];
822 for ($i = 0; $i < $numberOfHMetrics; ++$i) {
823 $cw[$i] = \round(\TCPDF_STATIC::_getUFWORD($font, $offset) * $urk);
824 $offset += 4;
825 // skip lsb
826 }
827 if ($numberOfHMetrics < $numGlyphs) {
828 // fill missing widths with the last value
829 $cw = \array_pad($cw, $numGlyphs, $cw[$numberOfHMetrics - 1]);
830 }
831 $fmetric['MissingWidth'] = $cw[0];
832 $fmetric['cw'] = '';
833 $fmetric['cbbox'] = '';
834 for ($cid = 0; $cid <= 65535; ++$cid) {
835 if (isset($ctg[$cid])) {
836 if (isset($cw[$ctg[$cid]])) {
837 $fmetric['cw'] .= ',' . $cid . '=>' . $cw[$ctg[$cid]];
838 }
839 if ($addcbbox and isset($indexToLoc[$ctg[$cid]])) {
840 $offset = $table['glyf']['offset'] + $indexToLoc[$ctg[$cid]];
841 $xMin = \round(\TCPDF_STATIC::_getFWORD($font, $offset + 2) * $urk);
842 $yMin = \round(\TCPDF_STATIC::_getFWORD($font, $offset + 4) * $urk);
843 $xMax = \round(\TCPDF_STATIC::_getFWORD($font, $offset + 6) * $urk);
844 $yMax = \round(\TCPDF_STATIC::_getFWORD($font, $offset + 8) * $urk);
845 $fmetric['cbbox'] .= ',' . $cid . '=>array(' . $xMin . ',' . $yMin . ',' . $xMax . ',' . $yMax . ')';
846 }
847 }
848 }
849 }
850 // end of true type
851 if ($fmetric['type'] == 'TrueTypeUnicode' and \count($ctg) == 256) {
852 $fmetric['type'] = 'TrueType';
853 }
854 // ---------- create php font file ----------
855 $pfile = '<' . '?' . 'php' . "\n";
856 $pfile .= '// TCPDF FONT FILE DESCRIPTION' . "\n";
857 $pfile .= '$type=\'' . $fmetric['type'] . '\';' . "\n";
858 $pfile .= '$name=\'' . $fmetric['name'] . '\';' . "\n";
859 $pfile .= '$up=' . $fmetric['underlinePosition'] . ';' . "\n";
860 $pfile .= '$ut=' . $fmetric['underlineThickness'] . ';' . "\n";
861 if ($fmetric['MissingWidth'] > 0) {
862 $pfile .= '$dw=' . $fmetric['MissingWidth'] . ';' . "\n";
863 } else {
864 $pfile .= '$dw=' . $fmetric['AvgWidth'] . ';' . "\n";
865 }
866 $pfile .= '$diff=\'' . $fmetric['diff'] . '\';' . "\n";
867 if ($fmetric['type'] == 'Type1') {
868 // Type 1
869 $pfile .= '$enc=\'' . $fmetric['enc'] . '\';' . "\n";
870 $pfile .= '$file=\'' . $fmetric['file'] . '\';' . "\n";
871 $pfile .= '$size1=' . $fmetric['size1'] . ';' . "\n";
872 $pfile .= '$size2=' . $fmetric['size2'] . ';' . "\n";
873 } else {
874 $pfile .= '$originalsize=' . $fmetric['originalsize'] . ';' . "\n";
875 if ($fmetric['type'] == 'cidfont0') {
876 // CID-0
877 switch ($fonttype) {
878 case 'CID0JP':
879 $pfile .= '// Japanese' . "\n";
880 $pfile .= '$enc=\'UniJIS-UTF16-H\';' . "\n";
881 $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Japan1\',\'Supplement\'=>5);' . "\n";
882 $pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');' . "\n";
883 break;
884 case 'CID0KR':
885 $pfile .= '// Korean' . "\n";
886 $pfile .= '$enc=\'UniKS-UTF16-H\';' . "\n";
887 $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Korea1\',\'Supplement\'=>0);' . "\n";
888 $pfile .= 'include(dirname(__FILE__).\'/uni2cid_ak12.php\');' . "\n";
889 break;
890 case 'CID0CS':
891 $pfile .= '// Chinese Simplified' . "\n";
892 $pfile .= '$enc=\'UniGB-UTF16-H\';' . "\n";
893 $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'GB1\',\'Supplement\'=>2);' . "\n";
894 $pfile .= 'include(dirname(__FILE__).\'/uni2cid_ag15.php\');' . "\n";
895 break;
896 case 'CID0CT':
897 default:
898 $pfile .= '// Chinese Traditional' . "\n";
899 $pfile .= '$enc=\'UniCNS-UTF16-H\';' . "\n";
900 $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'CNS1\',\'Supplement\'=>0);' . "\n";
901 $pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');' . "\n";
902 break;
903 }
904 } else {
905 // TrueType
906 $pfile .= '$enc=\'' . $fmetric['enc'] . '\';' . "\n";
907 $pfile .= '$file=\'' . $fmetric['file'] . '\';' . "\n";
908 $pfile .= '$ctg=\'' . $fmetric['ctg'] . '\';' . "\n";
909 // create CIDToGIDMap
910 $cidtogidmap = \str_pad('', 131072, "\x00");
911 // (256 * 256 * 2) = 131072
912 foreach ($ctg as $cid => $gid) {
913 $cidtogidmap = self::updateCIDtoGIDmap($cidtogidmap, $cid, $ctg[$cid]);
914 }
915 // store compressed CIDToGIDMap
916 $fp = \TCPDF_STATIC::fopenLocal($outpath . $fmetric['ctg'], 'wb');
917 \fwrite($fp, \gzcompress($cidtogidmap));
918 \fclose($fp);
919 }
920 }
921 $pfile .= '$desc=array(';
922 $pfile .= '\'Flags\'=>' . $fmetric['Flags'] . ',';
923 $pfile .= '\'FontBBox\'=>\'[' . $fmetric['bbox'] . ']\',';
924 $pfile .= '\'ItalicAngle\'=>' . $fmetric['italicAngle'] . ',';
925 $pfile .= '\'Ascent\'=>' . $fmetric['Ascent'] . ',';
926 $pfile .= '\'Descent\'=>' . $fmetric['Descent'] . ',';
927 $pfile .= '\'Leading\'=>' . $fmetric['Leading'] . ',';
928 $pfile .= '\'CapHeight\'=>' . $fmetric['CapHeight'] . ',';
929 $pfile .= '\'XHeight\'=>' . $fmetric['XHeight'] . ',';
930 $pfile .= '\'StemV\'=>' . $fmetric['StemV'] . ',';
931 $pfile .= '\'StemH\'=>' . $fmetric['StemH'] . ',';
932 $pfile .= '\'AvgWidth\'=>' . $fmetric['AvgWidth'] . ',';
933 $pfile .= '\'MaxWidth\'=>' . $fmetric['MaxWidth'] . ',';
934 $pfile .= '\'MissingWidth\'=>' . $fmetric['MissingWidth'] . '';
935 $pfile .= ');' . "\n";
936 if (!empty($fmetric['cbbox'])) {
937 $pfile .= '$cbbox=array(' . \substr($fmetric['cbbox'], 1) . ');' . "\n";
938 }
939 $pfile .= '$cw=array(' . \substr($fmetric['cw'], 1) . ');' . "\n";
940 $pfile .= '// --- EOF ---' . "\n";
941 // store file
942 $fp = \TCPDF_STATIC::fopenLocal($outpath . $font_name . '.php', 'w');
943 \fwrite($fp, $pfile);
944 \fclose($fp);
945 // return TCPDF font name
946 return $font_name;
947 }
948 /**
949 * Returs the checksum of a TTF table.
950 * @param string $table table to check
951 * @param int $length length of table in bytes
952 * @return int checksum
953 * @author Nicola Asuni
954 * @since 5.2.000 (2010-06-02)
955 * @public static
956 */
957 public static function _getTTFtableChecksum($table, $length)
958 {
959 $sum = 0;
960 $tlen = $length / 4;
961 $offset = 0;
962 for ($i = 0; $i < $tlen; ++$i) {
963 $v = \unpack('Ni', \substr($table, $offset, 4));
964 $sum += $v['i'];
965 $offset += 4;
966 }
967 $sum = \unpack('Ni', \pack('N', $sum));
968 return $sum['i'];
969 }
970 /**
971 * Returns a subset of the TrueType font data without the unused glyphs.
972 * @param string $font TrueType font data.
973 * @param array $subsetchars Array of used characters (the glyphs to keep).
974 * @return string A subset of TrueType font data without the unused glyphs.
975 * @author Nicola Asuni
976 * @since 5.2.000 (2010-06-02)
977 * @public static
978 */
979 public static function _getTrueTypeFontSubset($font, $subsetchars)
980 {
981 \ksort($subsetchars);
982 $offset = 0;
983 // offset position of the font data
984 if (\TCPDF_STATIC::_getULONG($font, $offset) != 0x10000) {
985 // sfnt version must be 0x00010000 for TrueType version 1.0.
986 return $font;
987 }
988 $c = 0;
989 $offset += 4;
990 // get number of tables
991 $numTables = \TCPDF_STATIC::_getUSHORT($font, $offset);
992 $offset += 2;
993 // skip searchRange, entrySelector and rangeShift
994 $offset += 6;
995 // tables array
996 $table = array();
997 // for each table
998 for ($i = 0; $i < $numTables; ++$i) {
999 // get table info
1000 $tag = \substr($font, $offset, 4);
1001 $offset += 4;
1002 $table[$tag] = array();
1003 $table[$tag]['checkSum'] = \TCPDF_STATIC::_getULONG($font, $offset);
1004 $offset += 4;
1005 $table[$tag]['offset'] = \TCPDF_STATIC::_getULONG($font, $offset);
1006 $offset += 4;
1007 $table[$tag]['length'] = \TCPDF_STATIC::_getULONG($font, $offset);
1008 $offset += 4;
1009 }
1010 // check magicNumber
1011 $offset = $table['head']['offset'] + 12;
1012 if (\TCPDF_STATIC::_getULONG($font, $offset) != 0x5f0f3cf5) {
1013 // magicNumber must be 0x5F0F3CF5
1014 return $font;
1015 }
1016 $offset += 4;
1017 // get offset mode (indexToLocFormat : 0 = short, 1 = long)
1018 $offset = $table['head']['offset'] + 50;
1019 $short_offset = \TCPDF_STATIC::_getSHORT($font, $offset) == 0;
1020 $offset += 2;
1021 // get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
1022 $indexToLoc = array();
1023 $offset = $table['loca']['offset'];
1024 if ($short_offset) {
1025 // short version
1026 $tot_num_glyphs = \floor($table['loca']['length'] / 2);
1027 // numGlyphs + 1
1028 for ($i = 0; $i < $tot_num_glyphs; ++$i) {
1029 $indexToLoc[$i] = \TCPDF_STATIC::_getUSHORT($font, $offset) * 2;
1030 $offset += 2;
1031 }
1032 } else {
1033 // long version
1034 $tot_num_glyphs = $table['loca']['length'] / 4;
1035 // numGlyphs + 1
1036 for ($i = 0; $i < $tot_num_glyphs; ++$i) {
1037 $indexToLoc[$i] = \TCPDF_STATIC::_getULONG($font, $offset);
1038 $offset += 4;
1039 }
1040 }
1041 // get glyphs indexes of chars from cmap table
1042 $subsetglyphs = array();
1043 // glyph IDs on key
1044 $subsetglyphs[0] = \true;
1045 // character codes that do not correspond to any glyph in the font should be mapped to glyph index 0
1046 $offset = $table['cmap']['offset'] + 2;
1047 $numEncodingTables = \TCPDF_STATIC::_getUSHORT($font, $offset);
1048 $offset += 2;
1049 $encodingTables = array();
1050 for ($i = 0; $i < $numEncodingTables; ++$i) {
1051 $encodingTables[$i]['platformID'] = \TCPDF_STATIC::_getUSHORT($font, $offset);
1052 $offset += 2;
1053 $encodingTables[$i]['encodingID'] = \TCPDF_STATIC::_getUSHORT($font, $offset);
1054 $offset += 2;
1055 $encodingTables[$i]['offset'] = \TCPDF_STATIC::_getULONG($font, $offset);
1056 $offset += 4;
1057 }
1058 foreach ($encodingTables as $enctable) {
1059 // get all platforms and encodings
1060 $offset = $table['cmap']['offset'] + $enctable['offset'];
1061 $format = \TCPDF_STATIC::_getUSHORT($font, $offset);
1062 $offset += 2;
1063 switch ($format) {
1064 case 0:
1065 // Format 0: Byte encoding table
1066 $offset += 4;
1067 // skip length and version/language
1068 for ($c = 0; $c < 256; ++$c) {
1069 if (isset($subsetchars[$c])) {
1070 $g = \TCPDF_STATIC::_getBYTE($font, $offset);
1071 $subsetglyphs[$g] = \true;
1072 }
1073 ++$offset;
1074 }
1075 break;
1076 case 2:
1077 // Format 2: High-byte mapping through table
1078 $offset += 4;
1079 // skip length and version/language
1080 $numSubHeaders = 0;
1081 for ($i = 0; $i < 256; ++$i) {
1082 // Array that maps high bytes to subHeaders: value is subHeader index * 8.
1083 $subHeaderKeys[$i] = \TCPDF_STATIC::_getUSHORT($font, $offset) / 8;
1084 $offset += 2;
1085 if ($numSubHeaders < $subHeaderKeys[$i]) {
1086 $numSubHeaders = $subHeaderKeys[$i];
1087 }
1088 }
1089 // the number of subHeaders is equal to the max of subHeaderKeys + 1
1090 ++$numSubHeaders;
1091 // read subHeader structures
1092 $subHeaders = array();
1093 $numGlyphIndexArray = 0;
1094 for ($k = 0; $k < $numSubHeaders; ++$k) {
1095 $subHeaders[$k]['firstCode'] = \TCPDF_STATIC::_getUSHORT($font, $offset);
1096 $offset += 2;
1097 $subHeaders[$k]['entryCount'] = \TCPDF_STATIC::_getUSHORT($font, $offset);
1098 $offset += 2;
1099 $subHeaders[$k]['idDelta'] = \TCPDF_STATIC::_getUSHORT($font, $offset);
1100 $offset += 2;
1101 $subHeaders[$k]['idRangeOffset'] = \TCPDF_STATIC::_getUSHORT($font, $offset);
1102 $offset += 2;
1103 $subHeaders[$k]['idRangeOffset'] -= 2 + ($numSubHeaders - $k - 1) * 8;
1104 $subHeaders[$k]['idRangeOffset'] /= 2;
1105 $numGlyphIndexArray += $subHeaders[$k]['entryCount'];
1106 }
1107 for ($k = 0; $k < $numGlyphIndexArray; ++$k) {
1108 $glyphIndexArray[$k] = \TCPDF_STATIC::_getUSHORT($font, $offset);
1109 $offset += 2;
1110 }
1111 for ($i = 0; $i < 256; ++$i) {
1112 $k = $subHeaderKeys[$i];
1113 if ($k == 0) {
1114 // one byte code
1115 $c = $i;
1116 if (isset($subsetchars[$c])) {
1117 $g = $glyphIndexArray[0];
1118 $subsetglyphs[$g] = \true;
1119 }
1120 } else {
1121 // two bytes code
1122 $start_byte = $subHeaders[$k]['firstCode'];
1123 $end_byte = $start_byte + $subHeaders[$k]['entryCount'];
1124 for ($j = $start_byte; $j < $end_byte; ++$j) {
1125 // combine high and low bytes
1126 $c = ($i << 8) + $j;
1127 if (isset($subsetchars[$c])) {
1128 $idRangeOffset = $subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode'];
1129 $g = ($glyphIndexArray[$idRangeOffset] + $subHeaders[$k]['idDelta']) % 65536;
1130 if ($g < 0) {
1131 $g = 0;
1132 }
1133 $subsetglyphs[$g] = \true;
1134 }
1135 }
1136 }
1137 }
1138 break;
1139 case 4:
1140 // Format 4: Segment mapping to delta values
1141 $length = \TCPDF_STATIC::_getUSHORT($font, $offset);
1142 $offset += 2;
1143 $offset += 2;
1144 // skip version/language
1145 $segCount = \floor(\TCPDF_STATIC::_getUSHORT($font, $offset) / 2);
1146 $offset += 2;
1147 $offset += 6;
1148 // skip searchRange, entrySelector, rangeShift
1149 $endCount = array();
1150 // array of end character codes for each segment
1151 for ($k = 0; $k < $segCount; ++$k) {
1152 $endCount[$k] = \TCPDF_STATIC::_getUSHORT($font, $offset);
1153 $offset += 2;
1154 }
1155 $offset += 2;
1156 // skip reservedPad
1157 $startCount = array();
1158 // array of start character codes for each segment
1159 for ($k = 0; $k < $segCount; ++$k) {
1160 $startCount[$k] = \TCPDF_STATIC::_getUSHORT($font, $offset);
1161 $offset += 2;
1162 }
1163 $idDelta = array();
1164 // delta for all character codes in segment
1165 for ($k = 0; $k < $segCount; ++$k) {
1166 $idDelta[$k] = \TCPDF_STATIC::_getUSHORT($font, $offset);
1167 $offset += 2;
1168 }
1169 $idRangeOffset = array();
1170 // Offsets into glyphIdArray or 0
1171 for ($k = 0; $k < $segCount; ++$k) {
1172 $idRangeOffset[$k] = \TCPDF_STATIC::_getUSHORT($font, $offset);
1173 $offset += 2;
1174 }
1175 $gidlen = \floor($length / 2) - 8 - 4 * $segCount;
1176 $glyphIdArray = array();
1177 // glyph index array
1178 for ($k = 0; $k < $gidlen; ++$k) {
1179 $glyphIdArray[$k] = \TCPDF_STATIC::_getUSHORT($font, $offset);
1180 $offset += 2;
1181 }
1182 for ($k = 0; $k < $segCount; ++$k) {
1183 for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
1184 if (isset($subsetchars[$c])) {
1185 if ($idRangeOffset[$k] == 0) {
1186 $g = ($idDelta[$k] + $c) % 65536;
1187 } else {
1188 $gid = \floor($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k);
1189 $g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536;
1190 }
1191 if ($g < 0) {
1192 $g = 0;
1193 }
1194 $subsetglyphs[$g] = \true;
1195 }
1196 }
1197 }
1198 break;
1199 case 6:
1200 // Format 6: Trimmed table mapping
1201 $offset += 4;
1202 // skip length and version/language
1203 $firstCode = \TCPDF_STATIC::_getUSHORT($font, $offset);
1204 $offset += 2;
1205 $entryCount = \TCPDF_STATIC::_getUSHORT($font, $offset);
1206 $offset += 2;
1207 for ($k = 0; $k < $entryCount; ++$k) {
1208 $c = $k + $firstCode;
1209 if (isset($subsetchars[$c])) {
1210 $g = \TCPDF_STATIC::_getUSHORT($font, $offset);
1211 $subsetglyphs[$g] = \true;
1212 }
1213 $offset += 2;
1214 }
1215 break;
1216 case 8:
1217 // Format 8: Mixed 16-bit and 32-bit coverage
1218 $offset += 10;
1219 // skip reserved, length and version/language
1220 for ($k = 0; $k < 8192; ++$k) {
1221 $is32[$k] = \TCPDF_STATIC::_getBYTE($font, $offset);
1222 ++$offset;
1223 }
1224 $nGroups = \TCPDF_STATIC::_getULONG($font, $offset);
1225 $offset += 4;
1226 for ($i = 0; $i < $nGroups; ++$i) {
1227 $startCharCode = \TCPDF_STATIC::_getULONG($font, $offset);
1228 $offset += 4;
1229 $endCharCode = \TCPDF_STATIC::_getULONG($font, $offset);
1230 $offset += 4;
1231 $startGlyphID = \TCPDF_STATIC::_getULONG($font, $offset);
1232 $offset += 4;
1233 for ($k = $startCharCode; $k <= $endCharCode; ++$k) {
1234 $is32idx = \floor($c / 8);
1235 if (isset($is32[$is32idx]) and ($is32[$is32idx] & 1 << 7 - $c % 8) == 0) {
1236 $c = $k;
1237 } else {
1238 // 32 bit format
1239 // convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4)
1240 //LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232
1241 //SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888
1242 $c = (55232 + ($k >> 10) << 10) + (0xdc00 + ($k & 0x3ff)) - 56613888;
1243 }
1244 if (isset($subsetchars[$c])) {
1245 $subsetglyphs[$startGlyphID] = \true;
1246 }
1247 ++$startGlyphID;
1248 }
1249 }
1250 break;
1251 case 10:
1252 // Format 10: Trimmed array
1253 $offset += 10;
1254 // skip reserved, length and version/language
1255 $startCharCode = \TCPDF_STATIC::_getULONG($font, $offset);
1256 $offset += 4;
1257 $numChars = \TCPDF_STATIC::_getULONG($font, $offset);
1258 $offset += 4;
1259 for ($k = 0; $k < $numChars; ++$k) {
1260 $c = $k + $startCharCode;
1261 if (isset($subsetchars[$c])) {
1262 $g = \TCPDF_STATIC::_getUSHORT($font, $offset);
1263 $subsetglyphs[$g] = \true;
1264 }
1265 $offset += 2;
1266 }
1267 break;
1268 case 12:
1269 // Format 12: Segmented coverage
1270 $offset += 10;
1271 // skip length and version/language
1272 $nGroups = \TCPDF_STATIC::_getULONG($font, $offset);
1273 $offset += 4;
1274 for ($k = 0; $k < $nGroups; ++$k) {
1275 $startCharCode = \TCPDF_STATIC::_getULONG($font, $offset);
1276 $offset += 4;
1277 $endCharCode = \TCPDF_STATIC::_getULONG($font, $offset);
1278 $offset += 4;
1279 $startGlyphCode = \TCPDF_STATIC::_getULONG($font, $offset);
1280 $offset += 4;
1281 for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
1282 if (isset($subsetchars[$c])) {
1283 $subsetglyphs[$startGlyphCode] = \true;
1284 }
1285 ++$startGlyphCode;
1286 }
1287 }
1288 break;
1289 case 13:
1290 // Format 13: Many-to-one range mappings
1291 // to be implemented ...
1292 break;
1293 case 14:
1294 // Format 14: Unicode Variation Sequences
1295 // to be implemented ...
1296 break;
1297 }
1298 }
1299 // include all parts of composite glyphs
1300 $new_sga = $subsetglyphs;
1301 while (!empty($new_sga)) {
1302 $sga = $new_sga;
1303 $new_sga = array();
1304 foreach ($sga as $key => $val) {
1305 if (isset($indexToLoc[$key])) {
1306 $offset = $table['glyf']['offset'] + $indexToLoc[$key];
1307 $numberOfContours = \TCPDF_STATIC::_getSHORT($font, $offset);
1308 $offset += 2;
1309 if ($numberOfContours < 0) {
1310 // composite glyph
1311 $offset += 8;
1312 // skip xMin, yMin, xMax, yMax
1313 do {
1314 $flags = \TCPDF_STATIC::_getUSHORT($font, $offset);
1315 $offset += 2;
1316 $glyphIndex = \TCPDF_STATIC::_getUSHORT($font, $offset);
1317 $offset += 2;
1318 if (!isset($subsetglyphs[$glyphIndex])) {
1319 // add missing glyphs
1320 $new_sga[$glyphIndex] = \true;
1321 }
1322 // skip some bytes by case
1323 if ($flags & 1) {
1324 $offset += 4;
1325 } else {
1326 $offset += 2;
1327 }
1328 if ($flags & 8) {
1329 $offset += 2;
1330 } elseif ($flags & 64) {
1331 $offset += 4;
1332 } elseif ($flags & 128) {
1333 $offset += 8;
1334 }
1335 } while ($flags & 32);
1336 }
1337 }
1338 }
1339 $subsetglyphs += $new_sga;
1340 }
1341 // sort glyphs by key (and remove duplicates)
1342 \ksort($subsetglyphs);
1343 // build new glyf and loca tables
1344 $glyf = '';
1345 $loca = '';
1346 $offset = 0;
1347 $glyf_offset = $table['glyf']['offset'];
1348 for ($i = 0; $i < $tot_num_glyphs; ++$i) {
1349 if (isset($subsetglyphs[$i])) {
1350 $length = $indexToLoc[$i + 1] - $indexToLoc[$i];
1351 $glyf .= \substr($font, $glyf_offset + $indexToLoc[$i], $length);
1352 } else {
1353 $length = 0;
1354 }
1355 if ($short_offset) {
1356 $loca .= \pack('n', \floor($offset / 2));
1357 } else {
1358 $loca .= \pack('N', $offset);
1359 }
1360 $offset += $length;
1361 }
1362 // array of table names to preserve (loca and glyf tables will be added later)
1363 // the cmap table is not needed and shall not be present, since the mapping from character codes to glyph descriptions is provided separately
1364 $table_names = array('head', 'hhea', 'hmtx', 'maxp', 'cvt ', 'fpgm', 'prep');
1365 // minimum required table names
1366 // get the tables to preserve
1367 $offset = 12;
1368 foreach ($table as $tag => $val) {
1369 if (\in_array($tag, $table_names)) {
1370 $table[$tag]['data'] = \substr($font, $table[$tag]['offset'], $table[$tag]['length']);
1371 if ($tag == 'head') {
1372 // set the checkSumAdjustment to 0
1373 $table[$tag]['data'] = \substr($table[$tag]['data'], 0, 8) . "\x00\x00\x00\x00" . \substr($table[$tag]['data'], 12);
1374 }
1375 $table[$tag]['offset'] = $offset;
1376 $offset += $table[$tag]['length'];
1377 $numPad = ($offset + 3 & ~3) - $offset;
1378 if ($numPad > 0) {
1379 $table[$tag]['data'] .= \str_repeat("\x00", $numPad);
1380 $offset += $numPad;
1381 }
1382 // check sum is not changed (so keep the following line commented)
1383 //$table[$tag]['checkSum'] = self::_getTTFtableChecksum($table[$tag]['data'], $table[$tag]['length'] + $numPad);
1384 } else {
1385 unset($table[$tag]);
1386 }
1387 }
1388 // add loca
1389 $table['loca'] = array();
1390 $table['loca']['data'] = $loca;
1391 $table['loca']['length'] = \strlen($loca);
1392 $table['loca']['offset'] = $offset;
1393 $offset += $table['loca']['length'];
1394 $numPad = ($offset + 3 & ~3) - $offset;
1395 if ($numPad > 0) {
1396 $table['loca']['data'] .= \str_repeat("\x00", $numPad);
1397 $offset += $numPad;
1398 }
1399 $table['loca']['checkSum'] = self::_getTTFtableChecksum($table['loca']['data'], $table['loca']['length'] + $numPad);
1400 // add glyf
1401 $table['glyf'] = array();
1402 $table['glyf']['data'] = $glyf;
1403 $table['glyf']['length'] = \strlen($glyf);
1404 $table['glyf']['offset'] = $offset;
1405 $offset += $table['glyf']['length'];
1406 $numPad = ($offset + 3 & ~3) - $offset;
1407 if ($numPad > 0) {
1408 $table['glyf']['data'] .= \str_repeat("\x00", $numPad);
1409 $offset += $numPad;
1410 }
1411 $table['glyf']['checkSum'] = self::_getTTFtableChecksum($table['glyf']['data'], $table['glyf']['length'] + $numPad);
1412 // rebuild font
1413 $font = '';
1414 $font .= \pack('N', 0x10000);
1415 // sfnt version
1416 $numTables = \count($table);
1417 $font .= \pack('n', $numTables);
1418 // numTables
1419 $entrySelector = \floor(\log($numTables, 2));
1420 $searchRange = \pow(2, $entrySelector) * 16;
1421 $rangeShift = $numTables * 16 - $searchRange;
1422 $font .= \pack('n', $searchRange);
1423 // searchRange
1424 $font .= \pack('n', $entrySelector);
1425 // entrySelector
1426 $font .= \pack('n', $rangeShift);
1427 // rangeShift
1428 $offset = $numTables * 16;
1429 foreach ($table as $tag => $data) {
1430 $font .= $tag;
1431 // tag
1432 $font .= \pack('N', $data['checkSum']);
1433 // checkSum
1434 $font .= \pack('N', $data['offset'] + $offset);
1435 // offset
1436 $font .= \pack('N', $data['length']);
1437 // length
1438 }
1439 foreach ($table as $data) {
1440 $font .= $data['data'];
1441 }
1442 // set checkSumAdjustment on head table
1443 $checkSumAdjustment = 0xb1b0afba - self::_getTTFtableChecksum($font, \strlen($font));
1444 $font = \substr($font, 0, $table['head']['offset'] + $offset + 4) . \pack('N', $checkSumAdjustment) . \substr($font, $table['head']['offset'] + $offset + 8);
1445 return $font;
1446 }
1447 /**
1448 * Outputs font widths
1449 * @param array $font font data
1450 * @param int $cidoffset offset for CID values
1451 * @return string PDF command string for font widths
1452 * @author Nicola Asuni
1453 * @since 4.4.000 (2008-12-07)
1454 * @public static
1455 */
1456 public static function _putfontwidths($font, $cidoffset = 0)
1457 {
1458 \ksort($font['cw']);
1459 $rangeid = 0;
1460 $range = array();
1461 $prevcid = -2;
1462 $prevwidth = -1;
1463 $interval = \false;
1464 // for each character
1465 foreach ($font['cw'] as $cid => $width) {
1466 $cid -= $cidoffset;
1467 if ($font['subset'] and !isset($font['subsetchars'][$cid])) {
1468 // ignore the unused characters (font subsetting)
1469 continue;
1470 }
1471 if ($width != $font['dw']) {
1472 if ($cid == $prevcid + 1) {
1473 // consecutive CID
1474 if ($width == $prevwidth) {
1475 if ($width == $range[$rangeid][0]) {
1476 $range[$rangeid][] = $width;
1477 } else {
1478 \array_pop($range[$rangeid]);
1479 // new range
1480 $rangeid = $prevcid;
1481 $range[$rangeid] = array();
1482 $range[$rangeid][] = $prevwidth;
1483 $range[$rangeid][] = $width;
1484 }
1485 $interval = \true;
1486 $range[$rangeid]['interval'] = \true;
1487 } else {
1488 if ($interval) {
1489 // new range
1490 $rangeid = $cid;
1491 $range[$rangeid] = array();
1492 $range[$rangeid][] = $width;
1493 } else {
1494 $range[$rangeid][] = $width;
1495 }
1496 $interval = \false;
1497 }
1498 } else {
1499 // new range
1500 $rangeid = $cid;
1501 $range[$rangeid] = array();
1502 $range[$rangeid][] = $width;
1503 $interval = \false;
1504 }
1505 $prevcid = $cid;
1506 $prevwidth = $width;
1507 }
1508 }
1509 // optimize ranges
1510 $prevk = -1;
1511 $nextk = -1;
1512 $prevint = \false;
1513 foreach ($range as $k => $ws) {
1514 $cws = \count($ws);
1515 if ($k == $nextk and !$prevint and (!isset($ws['interval']) or $cws < 4)) {
1516 if (isset($range[$k]['interval'])) {
1517 unset($range[$k]['interval']);
1518 }
1519 $range[$prevk] = \array_merge($range[$prevk], $range[$k]);
1520 unset($range[$k]);
1521 } else {
1522 $prevk = $k;
1523 }
1524 $nextk = $k + $cws;
1525 if (isset($ws['interval'])) {
1526 if ($cws > 3) {
1527 $prevint = \true;
1528 } else {
1529 $prevint = \false;
1530 }
1531 if (isset($range[$k]['interval'])) {
1532 unset($range[$k]['interval']);
1533 }
1534 --$nextk;
1535 } else {
1536 $prevint = \false;
1537 }
1538 }
1539 // output data
1540 $w = '';
1541 foreach ($range as $k => $ws) {
1542 if (\count(\array_count_values($ws)) == 1) {
1543 // interval mode is more compact
1544 $w .= ' ' . $k . ' ' . ($k + \count($ws) - 1) . ' ' . $ws[0];
1545 } else {
1546 // range mode
1547 $w .= ' ' . $k . ' [ ' . \implode(' ', $ws) . ' ]';
1548 }
1549 }
1550 return '/W [' . $w . ' ]';
1551 }
1552 /**
1553 * Update the CIDToGIDMap string with a new value.
1554 * @param string $map CIDToGIDMap.
1555 * @param int $cid CID value.
1556 * @param int $gid GID value.
1557 * @return string CIDToGIDMap.
1558 * @author Nicola Asuni
1559 * @since 5.9.123 (2011-09-29)
1560 * @public static
1561 */
1562 public static function updateCIDtoGIDmap($map, $cid, $gid)
1563 {
1564 if ($cid >= 0 and $cid <= 0xffff and $gid >= 0) {
1565 if ($gid > 0xffff) {
1566 $gid -= 0x10000;
1567 }
1568 $map[$cid * 2] = \chr($gid >> 8);
1569 $map[$cid * 2 + 1] = \chr($gid & 0xff);
1570 }
1571 return $map;
1572 }
1573 /**
1574 * Return fonts path
1575 * @return string
1576 * @public static
1577 */
1578 public static function _getfontpath()
1579 {
1580 if (!\defined('K_PATH_FONTS') and \is_dir($fdir = \realpath(\dirname(__FILE__) . '/../fonts'))) {
1581 if (\substr($fdir, -1) != '/') {
1582 $fdir .= '/';
1583 }
1584 \define('K_PATH_FONTS', $fdir);
1585 }
1586 return \defined('K_PATH_FONTS') ? \K_PATH_FONTS : '';
1587 }
1588 /**
1589 * Return font full path
1590 * @param string $file Font file name.
1591 * @param string $fontdir Font directory (set to false fto search on default directories)
1592 * @return string Font full path or empty string
1593 * @author Nicola Asuni
1594 * @since 6.0.025
1595 * @public static
1596 */
1597 public static function getFontFullPath($file, $fontdir = \false)
1598 {
1599 $fontfile = '';
1600 // search files on various directories
1601 if ($fontdir !== \false and @\TCPDF_STATIC::file_exists($fontdir . $file)) {
1602 $fontfile = $fontdir . $file;
1603 } elseif (@\TCPDF_STATIC::file_exists(self::_getfontpath() . $file)) {
1604 $fontfile = self::_getfontpath() . $file;
1605 } elseif (@\TCPDF_STATIC::file_exists($file)) {
1606 $fontfile = $file;
1607 }
1608 return $fontfile;
1609 }
1610 /**
1611 * Get a reference font size.
1612 * @param string $size String containing font size value.
1613 * @param float $refsize Reference font size in points.
1614 * @return float value in points
1615 * @public static
1616 */
1617 public static function getFontRefSize($size, $refsize = 12)
1618 {
1619 switch ($size) {
1620 case 'xx-small':
1621 $size = $refsize - 4;
1622 break;
1623 case 'x-small':
1624 $size = $refsize - 3;
1625 break;
1626 case 'small':
1627 $size = $refsize - 2;
1628 break;
1629 case 'medium':
1630 $size = $refsize;
1631 break;
1632 case 'large':
1633 $size = $refsize + 2;
1634 break;
1635 case 'x-large':
1636 $size = $refsize + 4;
1637 break;
1638 case 'xx-large':
1639 $size = $refsize + 6;
1640 break;
1641 case 'smaller':
1642 $size = $refsize - 3;
1643 break;
1644 case 'larger':
1645 $size = $refsize + 3;
1646 break;
1647 }
1648 return $size;
1649 }
1650 // ====================================================================================================================
1651 // REIMPLEMENTED
1652 // ====================================================================================================================
1653 /**
1654 * Returns the unicode caracter specified by the value
1655 * @param int $c UTF-8 value
1656 * @param boolean $unicode True if we are in unicode mode, false otherwise.
1657 * @return string Returns the specified character.
1658 * @since 2.3.000 (2008-03-05)
1659 * @public static
1660 */
1661 public static function unichr($c, $unicode = \true)
1662 {
1663 $c = \intval($c);
1664 if (!$unicode) {
1665 return \chr($c);
1666 } elseif ($c <= 0x7f) {
1667 // one byte
1668 return \chr($c);
1669 } elseif ($c <= 0x7ff) {
1670 // two bytes
1671 return \chr(0xc0 | $c >> 6) . \chr(0x80 | $c & 0x3f);
1672 } elseif ($c <= 0xffff) {
1673 // three bytes
1674 return \chr(0xe0 | $c >> 12) . \chr(0x80 | $c >> 6 & 0x3f) . \chr(0x80 | $c & 0x3f);
1675 } elseif ($c <= 0x10ffff) {
1676 // four bytes
1677 return \chr(0xf0 | $c >> 18) . \chr(0x80 | $c >> 12 & 0x3f) . \chr(0x80 | $c >> 6 & 0x3f) . \chr(0x80 | $c & 0x3f);
1678 } else {
1679 return '';
1680 }
1681 }
1682 /**
1683 * Returns the unicode caracter specified by UTF-8 value
1684 * @param int $c UTF-8 value
1685 * @return string Returns the specified character.
1686 * @public static
1687 */
1688 public static function unichrUnicode($c)
1689 {
1690 return self::unichr($c, \true);
1691 }
1692 /**
1693 * Returns the unicode caracter specified by ASCII value
1694 * @param int $c UTF-8 value
1695 * @return string Returns the specified character.
1696 * @public static
1697 */
1698 public static function unichrASCII($c)
1699 {
1700 return self::unichr($c, \false);
1701 }
1702 /**
1703 * Converts array of UTF-8 characters to UTF16-BE string.<br>
1704 * Based on: http://www.faqs.org/rfcs/rfc2781.html
1705 * <pre>
1706 * Encoding UTF-16:
1707 *
1708 * Encoding of a single character from an ISO 10646 character value to
1709 * UTF-16 proceeds as follows. Let U be the character number, no greater
1710 * than 0x10FFFF.
1711 *
1712 * 1) If U < 0x10000, encode U as a 16-bit unsigned integer and
1713 * terminate.
1714 *
1715 * 2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
1716 * U' must be less than or equal to 0xFFFFF. That is, U' can be
1717 * represented in 20 bits.
1718 *
1719 * 3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
1720 * 0xDC00, respectively. These integers each have 10 bits free to
1721 * encode the character value, for a total of 20 bits.
1722 *
1723 * 4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order
1724 * bits of W1 and the 10 low-order bits of U' to the 10 low-order
1725 * bits of W2. Terminate.
1726 *
1727 * Graphically, steps 2 through 4 look like:
1728 * U' = yyyyyyyyyyxxxxxxxxxx
1729 * W1 = 110110yyyyyyyyyy
1730 * W2 = 110111xxxxxxxxxx
1731 * </pre>
1732 * @param array $unicode array containing UTF-8 unicode values
1733 * @param boolean $setbom if true set the Byte Order Mark (BOM = 0xFEFF)
1734 * @return string
1735 * @protected
1736 * @author Nicola Asuni
1737 * @since 2.1.000 (2008-01-08)
1738 * @public static
1739 */
1740 public static function arrUTF8ToUTF16BE($unicode, $setbom = \false)
1741 {
1742 $outstr = '';
1743 // string to be returned
1744 if ($setbom) {
1745 $outstr .= "\xfe\xff";
1746 // Byte Order Mark (BOM)
1747 }
1748 foreach ($unicode as $char) {
1749 if ($char == 0x200b) {
1750 // skip Unicode Character 'ZERO WIDTH SPACE' (DEC:8203, U+200B)
1751 } elseif ($char == 0xfffd) {
1752 $outstr .= "\xff\xfd";
1753 // replacement character
1754 } elseif ($char < 0x10000) {
1755 $outstr .= \chr($char >> 0x8);
1756 $outstr .= \chr($char & 0xff);
1757 } else {
1758 $char -= 0x10000;
1759 $w1 = 0xd800 | $char >> 0xa;
1760 $w2 = 0xdc00 | $char & 0x3ff;
1761 $outstr .= \chr($w1 >> 0x8);
1762 $outstr .= \chr($w1 & 0xff);
1763 $outstr .= \chr($w2 >> 0x8);
1764 $outstr .= \chr($w2 & 0xff);
1765 }
1766 }
1767 return $outstr;
1768 }
1769 /**
1770 * Convert an array of UTF8 values to array of unicode characters
1771 * @param array $ta The input array of UTF8 values.
1772 * @param boolean $isunicode True for Unicode mode, false otherwise.
1773 * @return array Return array of unicode characters
1774 * @since 4.5.037 (2009-04-07)
1775 * @public static
1776 */
1777 public static function UTF8ArrayToUniArray($ta, $isunicode = \true)
1778 {
1779 if ($isunicode) {
1780 return \array_map(static::class . '::unichrUnicode', $ta);
1781 }
1782 return \array_map(static::class . '::unichrASCII', $ta);
1783 }
1784 /**
1785 * Extract a slice of the $strarr array and return it as string.
1786 * @param string[] $strarr The input array of characters.
1787 * @param int $start the starting element of $strarr.
1788 * @param int $end first element that will not be returned.
1789 * @param boolean $unicode True if we are in unicode mode, false otherwise.
1790 * @return string Return part of a string
1791 * @public static
1792 */
1793 public static function UTF8ArrSubString($strarr, $start = '', $end = '', $unicode = \true)
1794 {
1795 if (\strlen($start) == 0) {
1796 $start = 0;
1797 }
1798 if (\strlen($end) == 0) {
1799 $end = \count($strarr);
1800 }
1801 $string = '';
1802 for ($i = $start; $i < $end; ++$i) {
1803 $string .= self::unichr($strarr[$i], $unicode);
1804 }
1805 return $string;
1806 }
1807 /**
1808 * Extract a slice of the $uniarr array and return it as string.
1809 * @param string[] $uniarr The input array of characters.
1810 * @param int $start the starting element of $strarr.
1811 * @param int $end first element that will not be returned.
1812 * @return string Return part of a string
1813 * @since 4.5.037 (2009-04-07)
1814 * @public static
1815 */
1816 public static function UniArrSubString($uniarr, $start = '', $end = '')
1817 {
1818 if (\strlen($start) == 0) {
1819 $start = 0;
1820 }
1821 if (\strlen($end) == 0) {
1822 $end = \count($uniarr);
1823 }
1824 $string = '';
1825 for ($i = $start; $i < $end; ++$i) {
1826 $string .= $uniarr[$i];
1827 }
1828 return $string;
1829 }
1830 /**
1831 * Converts UTF-8 characters array to array of Latin1 characters array<br>
1832 * @param array $unicode array containing UTF-8 unicode values
1833 * @return array
1834 * @author Nicola Asuni
1835 * @since 4.8.023 (2010-01-15)
1836 * @public static
1837 */
1838 public static function UTF8ArrToLatin1Arr($unicode)
1839 {
1840 $outarr = array();
1841 // array to be returned
1842 foreach ($unicode as $char) {
1843 if ($char < 256) {
1844 $outarr[] = $char;
1845 } elseif (\array_key_exists($char, \TCPDF_FONT_DATA::$uni_utf8tolatin)) {
1846 // map from UTF-8
1847 $outarr[] = \TCPDF_FONT_DATA::$uni_utf8tolatin[$char];
1848 } elseif ($char == 0xfffd) {
1849 // skip
1850 } else {
1851 $outarr[] = 63;
1852 // '?' character
1853 }
1854 }
1855 return $outarr;
1856 }
1857 /**
1858 * Converts UTF-8 characters array to Latin1 string<br>
1859 * @param array $unicode array containing UTF-8 unicode values
1860 * @return string
1861 * @author Nicola Asuni
1862 * @since 4.8.023 (2010-01-15)
1863 * @public static
1864 */
1865 public static function UTF8ArrToLatin1($unicode)
1866 {
1867 $outstr = '';
1868 // string to be returned
1869 foreach ($unicode as $char) {
1870 if ($char < 256) {
1871 $outstr .= \chr($char);
1872 } elseif (\array_key_exists($char, \TCPDF_FONT_DATA::$uni_utf8tolatin)) {
1873 // map from UTF-8
1874 $outstr .= \chr(\TCPDF_FONT_DATA::$uni_utf8tolatin[$char]);
1875 } elseif ($char == 0xfffd) {
1876 // skip
1877 } else {
1878 $outstr .= '?';
1879 }
1880 }
1881 return $outstr;
1882 }
1883 /**
1884 * Converts UTF-8 character to integer value.<br>
1885 * Uses the getUniord() method if the value is not cached.
1886 * @param string $uch character string to process.
1887 * @return int Unicode value
1888 * @public static
1889 */
1890 public static function uniord($uch)
1891 {
1892 if (!isset(self::$cache_uniord[$uch])) {
1893 self::$cache_uniord[$uch] = self::getUniord($uch);
1894 }
1895 return self::$cache_uniord[$uch];
1896 }
1897 /**
1898 * Converts UTF-8 character to integer value.<br>
1899 * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
1900 * Based on: http://www.faqs.org/rfcs/rfc3629.html
1901 * <pre>
1902 * Char. number range | UTF-8 octet sequence
1903 * (hexadecimal) | (binary)
1904 * --------------------+-----------------------------------------------
1905 * 0000 0000-0000 007F | 0xxxxxxx
1906 * 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
1907 * 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
1908 * 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
1909 * ---------------------------------------------------------------------
1910 *
1911 * ABFN notation:
1912 * ---------------------------------------------------------------------
1913 * UTF8-octets = *( UTF8-char )
1914 * UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
1915 * UTF8-1 = %x00-7F
1916 * UTF8-2 = %xC2-DF UTF8-tail
1917 *
1918 * UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
1919 * %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
1920 * UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
1921 * %xF4 %x80-8F 2( UTF8-tail )
1922 * UTF8-tail = %x80-BF
1923 * ---------------------------------------------------------------------
1924 * </pre>
1925 * @param string $uch character string to process.
1926 * @return int Unicode value
1927 * @author Nicola Asuni
1928 * @public static
1929 */
1930 public static function getUniord($uch)
1931 {
1932 if (\function_exists('mb_convert_encoding')) {
1933 list(, $char) = @\unpack('N', \mb_convert_encoding($uch, 'UCS-4BE', 'UTF-8'));
1934 if ($char >= 0) {
1935 return $char;
1936 }
1937 }
1938 $bytes = array();
1939 // array containing single character byte sequences
1940 $countbytes = 0;
1941 $numbytes = 1;
1942 // number of octetc needed to represent the UTF-8 character
1943 $length = \strlen($uch);
1944 for ($i = 0; $i < $length; ++$i) {
1945 $char = \ord($uch[$i]);
1946 // get one string character at time
1947 if ($countbytes == 0) {
1948 // get starting octect
1949 if ($char <= 0x7f) {
1950 return $char;
1951 // use the character "as is" because is ASCII
1952 } elseif ($char >> 0x5 == 0x6) {
1953 // 2 bytes character (0x06 = 110 BIN)
1954 $bytes[] = $char - 0xc0 << 0x6;
1955 ++$countbytes;
1956 $numbytes = 2;
1957 } elseif ($char >> 0x4 == 0xe) {
1958 // 3 bytes character (0x0E = 1110 BIN)
1959 $bytes[] = $char - 0xe0 << 0xc;
1960 ++$countbytes;
1961 $numbytes = 3;
1962 } elseif ($char >> 0x3 == 0x1e) {
1963 // 4 bytes character (0x1E = 11110 BIN)
1964 $bytes[] = $char - 0xf0 << 0x12;
1965 ++$countbytes;
1966 $numbytes = 4;
1967 } else {
1968 // use replacement character for other invalid sequences
1969 return 0xfffd;
1970 }
1971 } elseif ($char >> 0x6 == 0x2) {
1972 // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
1973 $bytes[] = $char - 0x80;
1974 ++$countbytes;
1975 if ($countbytes == $numbytes) {
1976 // compose UTF-8 bytes to a single unicode value
1977 $char = $bytes[0];
1978 for ($j = 1; $j < $numbytes; ++$j) {
1979 $char += $bytes[$j] << ($numbytes - $j - 1) * 0x6;
1980 }
1981 if ($char >= 0xd800 and $char <= 0xdfff or $char >= 0x10ffff) {
1982 // The definition of UTF-8 prohibits encoding character numbers between
1983 // U+D800 and U+DFFF, which are reserved for use with the UTF-16
1984 // encoding form (as surrogate pairs) and do not directly represent
1985 // characters.
1986 return 0xfffd;
1987 // use replacement character
1988 } else {
1989 return $char;
1990 }
1991 }
1992 } else {
1993 // use replacement character for other invalid sequences
1994 return 0xfffd;
1995 }
1996 }
1997 return 0xfffd;
1998 }
1999 /**
2000 * Converts UTF-8 strings to codepoints array.<br>
2001 * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
2002 * @param string $str string to process.
2003 * @param boolean $isunicode True when the documetn is in Unicode mode, false otherwise.
2004 * @param array $currentfont Reference to current font array.
2005 * @return array containing codepoints (UTF-8 characters values)
2006 * @author Nicola Asuni
2007 * @public static
2008 */
2009 public static function UTF8StringToArray($str, $isunicode, &$currentfont)
2010 {
2011 $str = \is_null($str) ? '' : $str;
2012 if ($isunicode) {
2013 // requires PCRE unicode support turned on
2014 $chars = \TCPDF_STATIC::pregSplit('//', 'u', $str, -1, \PREG_SPLIT_NO_EMPTY);
2015 $carr = \array_map(static::class . '::uniord', $chars);
2016 } else {
2017 $chars = \str_split($str);
2018 $carr = \array_map('ord', $chars);
2019 }
2020 if (\is_array($currentfont['subsetchars']) && \is_array($carr)) {
2021 $currentfont['subsetchars'] += \array_fill_keys($carr, \true);
2022 } else {
2023 $currentfont['subsetchars'] = \array_merge($currentfont['subsetchars'], $carr);
2024 }
2025 return $carr;
2026 }
2027 /**
2028 * Converts UTF-8 strings to Latin1 when using the standard 14 core fonts.<br>
2029 * @param string $str string to process.
2030 * @param boolean $isunicode True when the documetn is in Unicode mode, false otherwise.
2031 * @param array $currentfont Reference to current font array.
2032 * @return string
2033 * @since 3.2.000 (2008-06-23)
2034 * @public static
2035 */
2036 public static function UTF8ToLatin1($str, $isunicode, &$currentfont)
2037 {
2038 $unicode = self::UTF8StringToArray($str, $isunicode, $currentfont);
2039 // array containing UTF-8 unicode values
2040 return self::UTF8ArrToLatin1($unicode);
2041 }
2042 /**
2043 * Converts UTF-8 strings to UTF16-BE.<br>
2044 * @param string $str string to process.
2045 * @param boolean $setbom if true set the Byte Order Mark (BOM = 0xFEFF)
2046 * @param boolean $isunicode True when the documetn is in Unicode mode, false otherwise.
2047 * @param array $currentfont Reference to current font array.
2048 * @return string
2049 * @author Nicola Asuni
2050 * @since 1.53.0.TC005 (2005-01-05)
2051 * @public static
2052 */
2053 public static function UTF8ToUTF16BE($str, $setbom, $isunicode, &$currentfont)
2054 {
2055 if (!$isunicode) {
2056 return $str;
2057 // string is not in unicode
2058 }
2059 $unicode = self::UTF8StringToArray($str, $isunicode, $currentfont);
2060 // array containing UTF-8 unicode values
2061 return self::arrUTF8ToUTF16BE($unicode, $setbom);
2062 }
2063 /**
2064 * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
2065 * @param string $str string to manipulate.
2066 * @param bool $setbom if true set the Byte Order Mark (BOM = 0xFEFF)
2067 * @param bool $forcertl if true forces RTL text direction
2068 * @param boolean $isunicode True if the document is in Unicode mode, false otherwise.
2069 * @param array $currentfont Reference to current font array.
2070 * @return string
2071 * @author Nicola Asuni
2072 * @since 2.1.000 (2008-01-08)
2073 * @public static
2074 */
2075 public static function utf8StrRev($str, $setbom, $forcertl, $isunicode, &$currentfont)
2076 {
2077 return self::utf8StrArrRev(self::UTF8StringToArray($str, $isunicode, $currentfont), $str, $setbom, $forcertl, $isunicode, $currentfont);
2078 }
2079 /**
2080 * Reverse the RLT substrings array using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
2081 * @param array $arr array of unicode values.
2082 * @param string $str string to manipulate (or empty value).
2083 * @param bool $setbom if true set the Byte Order Mark (BOM = 0xFEFF)
2084 * @param bool $forcertl if true forces RTL text direction
2085 * @param boolean $isunicode True if the document is in Unicode mode, false otherwise.
2086 * @param array $currentfont Reference to current font array.
2087 * @return string
2088 * @author Nicola Asuni
2089 * @since 4.9.000 (2010-03-27)
2090 * @public static
2091 */
2092 public static function utf8StrArrRev($arr, $str, $setbom, $forcertl, $isunicode, &$currentfont)
2093 {
2094 return self::arrUTF8ToUTF16BE(self::utf8Bidi($arr, $str, $forcertl, $isunicode, $currentfont), $setbom);
2095 }
2096 /**
2097 * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
2098 * @param array $ta array of characters composing the string.
2099 * @param string $str string to process
2100 * @param bool $forcertl if 'R' forces RTL, if 'L' forces LTR
2101 * @param boolean $isunicode True if the document is in Unicode mode, false otherwise.
2102 * @param array $currentfont Reference to current font array.
2103 * @return array of unicode chars
2104 * @author Nicola Asuni
2105 * @since 2.4.000 (2008-03-06)
2106 * @public static
2107 */
2108 public static function utf8Bidi($ta, $str, $forcertl, $isunicode, &$currentfont)
2109 {
2110 // paragraph embedding level
2111 $pel = 0;
2112 // max level
2113 $maxlevel = 0;
2114 if (\TCPDF_STATIC::empty_string($str)) {
2115 // create string from array
2116 $str = self::UTF8ArrSubString($ta, '', '', $isunicode);
2117 }
2118 // check if string contains arabic text
2119 if (\preg_match(\TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $str)) {
2120 $arabic = \true;
2121 } else {
2122 $arabic = \false;
2123 }
2124 // check if string contains RTL text
2125 if (!($forcertl or $arabic or \preg_match(\TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $str))) {
2126 return $ta;
2127 }
2128 // get number of chars
2129 $numchars = \count($ta);
2130 if ($forcertl == 'R') {
2131 $pel = 1;
2132 } elseif ($forcertl == 'L') {
2133 $pel = 0;
2134 } else {
2135 // P2. In each paragraph, find the first character of type L, AL, or R.
2136 // 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.
2137 for ($i = 0; $i < $numchars; ++$i) {
2138 $type = \TCPDF_FONT_DATA::$uni_type[$ta[$i]];
2139 if ($type == 'L') {
2140 $pel = 0;
2141 break;
2142 } elseif ($type == 'AL' or $type == 'R') {
2143 $pel = 1;
2144 break;
2145 }
2146 }
2147 }
2148 // Current Embedding Level
2149 $cel = $pel;
2150 // directional override status
2151 $dos = 'N';
2152 $remember = array();
2153 // start-of-level-run
2154 $sor = $pel % 2 ? 'R' : 'L';
2155 $eor = $sor;
2156 // Array of characters data
2157 $chardata = array();
2158 // 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.
2159 // In the resolution of levels in rules I1 and I2, the maximum embedding level of 62 can be reached.
2160 for ($i = 0; $i < $numchars; ++$i) {
2161 if ($ta[$i] == \TCPDF_FONT_DATA::$uni_RLE) {
2162 // X2. With each RLE, compute the least greater odd embedding level.
2163 // 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.
2164 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2165 $next_level = $cel + $cel % 2 + 1;
2166 if ($next_level < 62) {
2167 $remember[] = array('num' => \TCPDF_FONT_DATA::$uni_RLE, 'cel' => $cel, 'dos' => $dos);
2168 $cel = $next_level;
2169 $dos = 'N';
2170 $sor = $eor;
2171 $eor = $cel % 2 ? 'R' : 'L';
2172 }
2173 } elseif ($ta[$i] == \TCPDF_FONT_DATA::$uni_LRE) {
2174 // X3. With each LRE, compute the least greater even embedding level.
2175 // 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.
2176 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2177 $next_level = $cel + 2 - $cel % 2;
2178 if ($next_level < 62) {
2179 $remember[] = array('num' => \TCPDF_FONT_DATA::$uni_LRE, 'cel' => $cel, 'dos' => $dos);
2180 $cel = $next_level;
2181 $dos = 'N';
2182 $sor = $eor;
2183 $eor = $cel % 2 ? 'R' : 'L';
2184 }
2185 } elseif ($ta[$i] == \TCPDF_FONT_DATA::$uni_RLO) {
2186 // X4. With each RLO, compute the least greater odd embedding level.
2187 // 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.
2188 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2189 $next_level = $cel + $cel % 2 + 1;
2190 if ($next_level < 62) {
2191 $remember[] = array('num' => \TCPDF_FONT_DATA::$uni_RLO, 'cel' => $cel, 'dos' => $dos);
2192 $cel = $next_level;
2193 $dos = 'R';
2194 $sor = $eor;
2195 $eor = $cel % 2 ? 'R' : 'L';
2196 }
2197 } elseif ($ta[$i] == \TCPDF_FONT_DATA::$uni_LRO) {
2198 // X5. With each LRO, compute the least greater even embedding level.
2199 // 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.
2200 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2201 $next_level = $cel + 2 - $cel % 2;
2202 if ($next_level < 62) {
2203 $remember[] = array('num' => \TCPDF_FONT_DATA::$uni_LRO, 'cel' => $cel, 'dos' => $dos);
2204 $cel = $next_level;
2205 $dos = 'L';
2206 $sor = $eor;
2207 $eor = $cel % 2 ? 'R' : 'L';
2208 }
2209 } elseif ($ta[$i] == \TCPDF_FONT_DATA::$uni_PDF) {
2210 // 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.
2211 if (\count($remember)) {
2212 $last = \count($remember) - 1;
2213 if ($remember[$last]['num'] == \TCPDF_FONT_DATA::$uni_RLE or $remember[$last]['num'] == \TCPDF_FONT_DATA::$uni_LRE or $remember[$last]['num'] == \TCPDF_FONT_DATA::$uni_RLO or $remember[$last]['num'] == \TCPDF_FONT_DATA::$uni_LRO) {
2214 $match = \array_pop($remember);
2215 $cel = $match['cel'];
2216 $dos = $match['dos'];
2217 $sor = $eor;
2218 $eor = ($cel > $match['cel'] ? $cel : $match['cel']) % 2 ? 'R' : 'L';
2219 }
2220 }
2221 } elseif ($ta[$i] != \TCPDF_FONT_DATA::$uni_RLE and $ta[$i] != \TCPDF_FONT_DATA::$uni_LRE and $ta[$i] != \TCPDF_FONT_DATA::$uni_RLO and $ta[$i] != \TCPDF_FONT_DATA::$uni_LRO and $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 }
2238 // end for each char
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 // 3.3.3 Resolving Weak Types
2243 // 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.
2244 // Nonspacing marks are now resolved based on the previous characters.
2245 $numchars = \count($chardata);
2246 // 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.
2247 $prevlevel = -1;
2248 // track level changes
2249 $levcount = 0;
2250 // 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 // 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.
2267 $prevlevel = -1;
2268 $levcount = 0;
2269 for ($i = 0; $i < $numchars; ++$i) {
2270 if ($chardata[$i]['char'] == 'EN') {
2271 for ($j = $levcount; $j >= 0; $j--) {
2272 if ($chardata[$j]['type'] == 'AL') {
2273 $chardata[$i]['type'] = 'AN';
2274 } elseif ($chardata[$j]['type'] == 'L' or $chardata[$j]['type'] == 'R') {
2275 break;
2276 }
2277 }
2278 }
2279 if ($chardata[$i]['level'] != $prevlevel) {
2280 $levcount = 0;
2281 } else {
2282 ++$levcount;
2283 }
2284 $prevlevel = $chardata[$i]['level'];
2285 }
2286 // W3. Change all ALs to R.
2287 for ($i = 0; $i < $numchars; ++$i) {
2288 if ($chardata[$i]['type'] == 'AL') {
2289 $chardata[$i]['type'] = 'R';
2290 }
2291 }
2292 // 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.
2293 $prevlevel = -1;
2294 $levcount = 0;
2295 for ($i = 0; $i < $numchars; ++$i) {
2296 if ($levcount > 0 and $i + 1 < $numchars and $chardata[$i + 1]['level'] == $prevlevel) {
2297 if ($chardata[$i]['type'] == 'ES' and $chardata[$i - 1]['type'] == 'EN' and $chardata[$i + 1]['type'] == 'EN') {
2298 $chardata[$i]['type'] = 'EN';
2299 } elseif ($chardata[$i]['type'] == 'CS' and $chardata[$i - 1]['type'] == 'EN' and $chardata[$i + 1]['type'] == 'EN') {
2300 $chardata[$i]['type'] = 'EN';
2301 } elseif ($chardata[$i]['type'] == 'CS' and $chardata[$i - 1]['type'] == 'AN' and $chardata[$i + 1]['type'] == 'AN') {
2302 $chardata[$i]['type'] = 'AN';
2303 }
2304 }
2305 if ($chardata[$i]['level'] != $prevlevel) {
2306 $levcount = 0;
2307 } else {
2308 ++$levcount;
2309 }
2310 $prevlevel = $chardata[$i]['level'];
2311 }
2312 // W5. A sequence of European terminators adjacent to European numbers changes to all European numbers.
2313 $prevlevel = -1;
2314 $levcount = 0;
2315 for ($i = 0; $i < $numchars; ++$i) {
2316 if ($chardata[$i]['type'] == 'ET') {
2317 if ($levcount > 0 and $chardata[$i - 1]['type'] == 'EN') {
2318 $chardata[$i]['type'] = 'EN';
2319 } else {
2320 $j = $i + 1;
2321 while ($j < $numchars and $chardata[$j]['level'] == $prevlevel) {
2322 if ($chardata[$j]['type'] == 'EN') {
2323 $chardata[$i]['type'] = 'EN';
2324 break;
2325 } elseif ($chardata[$j]['type'] != 'ET') {
2326 break;
2327 }
2328 ++$j;
2329 }
2330 }
2331 }
2332 if ($chardata[$i]['level'] != $prevlevel) {
2333 $levcount = 0;
2334 } else {
2335 ++$levcount;
2336 }
2337 $prevlevel = $chardata[$i]['level'];
2338 }
2339 // W6. Otherwise, separators and terminators change to Other Neutral.
2340 $prevlevel = -1;
2341 $levcount = 0;
2342 for ($i = 0; $i < $numchars; ++$i) {
2343 if ($chardata[$i]['type'] == 'ET' or $chardata[$i]['type'] == 'ES' or $chardata[$i]['type'] == 'CS') {
2344 $chardata[$i]['type'] = 'ON';
2345 }
2346 if ($chardata[$i]['level'] != $prevlevel) {
2347 $levcount = 0;
2348 } else {
2349 ++$levcount;
2350 }
2351 $prevlevel = $chardata[$i]['level'];
2352 }
2353 //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.
2354 $prevlevel = -1;
2355 $levcount = 0;
2356 for ($i = 0; $i < $numchars; ++$i) {
2357 if ($chardata[$i]['char'] == 'EN') {
2358 for ($j = $levcount; $j >= 0; $j--) {
2359 if ($chardata[$j]['type'] == 'L') {
2360 $chardata[$i]['type'] = 'L';
2361 } elseif ($chardata[$j]['type'] == 'R') {
2362 break;
2363 }
2364 }
2365 }
2366 if ($chardata[$i]['level'] != $prevlevel) {
2367 $levcount = 0;
2368 } else {
2369 ++$levcount;
2370 }
2371 $prevlevel = $chardata[$i]['level'];
2372 }
2373 // 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.
2374 $prevlevel = -1;
2375 $levcount = 0;
2376 for ($i = 0; $i < $numchars; ++$i) {
2377 if ($levcount > 0 and $i + 1 < $numchars and $chardata[$i + 1]['level'] == $prevlevel) {
2378 if ($chardata[$i]['type'] == 'N' and $chardata[$i - 1]['type'] == 'L' and $chardata[$i + 1]['type'] == 'L') {
2379 $chardata[$i]['type'] = 'L';
2380 } elseif ($chardata[$i]['type'] == 'N' and ($chardata[$i - 1]['type'] == 'R' or $chardata[$i - 1]['type'] == 'EN' or $chardata[$i - 1]['type'] == 'AN') and ($chardata[$i + 1]['type'] == 'R' or $chardata[$i + 1]['type'] == 'EN' or $chardata[$i + 1]['type'] == 'AN')) {
2381 $chardata[$i]['type'] = 'R';
2382 } elseif ($chardata[$i]['type'] == 'N') {
2383 // N2. Any remaining neutrals take the embedding direction
2384 $chardata[$i]['type'] = $chardata[$i]['sor'];
2385 }
2386 } elseif ($levcount == 0 and $i + 1 < $numchars and $chardata[$i + 1]['level'] == $prevlevel) {
2387 // first char
2388 if ($chardata[$i]['type'] == 'N' and $chardata[$i]['sor'] == 'L' and $chardata[$i + 1]['type'] == 'L') {
2389 $chardata[$i]['type'] = 'L';
2390 } elseif ($chardata[$i]['type'] == 'N' and ($chardata[$i]['sor'] == 'R' or $chardata[$i]['sor'] == 'EN' or $chardata[$i]['sor'] == 'AN') and ($chardata[$i + 1]['type'] == 'R' or $chardata[$i + 1]['type'] == 'EN' or $chardata[$i + 1]['type'] == 'AN')) {
2391 $chardata[$i]['type'] = 'R';
2392 } elseif ($chardata[$i]['type'] == 'N') {
2393 // N2. Any remaining neutrals take the embedding direction
2394 $chardata[$i]['type'] = $chardata[$i]['sor'];
2395 }
2396 } elseif ($levcount > 0 and ($i + 1 == $numchars or $i + 1 < $numchars and $chardata[$i + 1]['level'] != $prevlevel)) {
2397 //last char
2398 if ($chardata[$i]['type'] == 'N' and $chardata[$i - 1]['type'] == 'L' and $chardata[$i]['eor'] == 'L') {
2399 $chardata[$i]['type'] = 'L';
2400 } elseif ($chardata[$i]['type'] == 'N' and ($chardata[$i - 1]['type'] == 'R' or $chardata[$i - 1]['type'] == 'EN' or $chardata[$i - 1]['type'] == 'AN') and ($chardata[$i]['eor'] == 'R' or $chardata[$i]['eor'] == 'EN' or $chardata[$i]['eor'] == 'AN')) {
2401 $chardata[$i]['type'] = 'R';
2402 } elseif ($chardata[$i]['type'] == 'N') {
2403 // N2. Any remaining neutrals take the embedding direction
2404 $chardata[$i]['type'] = $chardata[$i]['sor'];
2405 }
2406 } elseif ($chardata[$i]['type'] == 'N') {
2407 // N2. Any remaining neutrals take the embedding direction
2408 $chardata[$i]['type'] = $chardata[$i]['sor'];
2409 }
2410 if ($chardata[$i]['level'] != $prevlevel) {
2411 $levcount = 0;
2412 } else {
2413 ++$levcount;
2414 }
2415 $prevlevel = $chardata[$i]['level'];
2416 }
2417 // 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.
2418 // I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level.
2419 for ($i = 0; $i < $numchars; ++$i) {
2420 $odd = $chardata[$i]['level'] % 2;
2421 if ($odd) {
2422 if ($chardata[$i]['type'] == 'L' or $chardata[$i]['type'] == 'AN' or $chardata[$i]['type'] == 'EN') {
2423 $chardata[$i]['level'] += 1;
2424 }
2425 } else {
2426 if ($chardata[$i]['type'] == 'R') {
2427 $chardata[$i]['level'] += 1;
2428 } elseif ($chardata[$i]['type'] == 'AN' or $chardata[$i]['type'] == 'EN') {
2429 $chardata[$i]['level'] += 2;
2430 }
2431 }
2432 $maxlevel = \max($chardata[$i]['level'], $maxlevel);
2433 }
2434 // L1. On each line, reset the embedding level of the following characters to the paragraph embedding level:
2435 // 1. Segment separators,
2436 // 2. Paragraph separators,
2437 // 3. Any sequence of whitespace characters preceding a segment separator or paragraph separator, and
2438 // 4. Any sequence of white space characters at the end of the line.
2439 for ($i = 0; $i < $numchars; ++$i) {
2440 if ($chardata[$i]['type'] == 'B' or $chardata[$i]['type'] == 'S') {
2441 $chardata[$i]['level'] = $pel;
2442 } elseif ($chardata[$i]['type'] == 'WS') {
2443 $j = $i + 1;
2444 while ($j < $numchars) {
2445 if ($chardata[$j]['type'] == 'B' or $chardata[$j]['type'] == 'S' or $j == $numchars - 1 and $chardata[$j]['type'] == 'WS') {
2446 $chardata[$i]['level'] = $pel;
2447 break;
2448 } elseif ($chardata[$j]['type'] != 'WS') {
2449 break;
2450 }
2451 ++$j;
2452 }
2453 }
2454 }
2455 // Arabic Shaping
2456 // 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.
2457 if ($arabic) {
2458 $endedletter = array(1569, 1570, 1571, 1572, 1573, 1575, 1577, 1583, 1584, 1585, 1586, 1608, 1688);
2459 $alfletter = array(1570, 1571, 1573, 1575);
2460 $chardata2 = $chardata;
2461 $laaletter = \false;
2462 $charAL = array();
2463 $x = 0;
2464 for ($i = 0; $i < $numchars; ++$i) {
2465 if (\TCPDF_FONT_DATA::$uni_type[$chardata[$i]['char']] == 'AL' or $chardata[$i]['char'] == 32 or $chardata[$i]['char'] == 8204) {
2466 $charAL[$x] = $chardata[$i];
2467 $charAL[$x]['i'] = $i;
2468 $chardata[$i]['x'] = $x;
2469 ++$x;
2470 }
2471 }
2472 $numAL = $x;
2473 for ($i = 0; $i < $numchars; ++$i) {
2474 $thischar = $chardata[$i];
2475 if ($i > 0) {
2476 $prevchar = $chardata[$i - 1];
2477 } else {
2478 $prevchar = \false;
2479 }
2480 if ($i + 1 < $numchars) {
2481 $nextchar = $chardata[$i + 1];
2482 } else {
2483 $nextchar = \false;
2484 }
2485 if (\TCPDF_FONT_DATA::$uni_type[$thischar['char']] == 'AL') {
2486 $x = $thischar['x'];
2487 if ($x > 0) {
2488 $prevchar = $charAL[$x - 1];
2489 } else {
2490 $prevchar = \false;
2491 }
2492 if ($x + 1 < $numAL) {
2493 $nextchar = $charAL[$x + 1];
2494 } else {
2495 $nextchar = \false;
2496 }
2497 // if laa letter
2498 if ($prevchar !== \false and $prevchar['char'] == 1604 and \in_array($thischar['char'], $alfletter)) {
2499 $arabicarr = \TCPDF_FONT_DATA::$uni_laa_array;
2500 $laaletter = \true;
2501 if ($x > 1) {
2502 $prevchar = $charAL[$x - 2];
2503 } else {
2504 $prevchar = \false;
2505 }
2506 } else {
2507 $arabicarr = \TCPDF_FONT_DATA::$uni_arabicsubst;
2508 $laaletter = \false;
2509 }
2510 if ($prevchar !== \false and $nextchar !== \false and (\TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'AL' or \TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'NSM') and (\TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'AL' or \TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'NSM') and $prevchar['type'] == $thischar['type'] and $nextchar['type'] == $thischar['type'] and $nextchar['char'] != 1567) {
2511 if (\in_array($prevchar['char'], $endedletter)) {
2512 if (isset($arabicarr[$thischar['char']][2])) {
2513 // initial
2514 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
2515 }
2516 } else {
2517 if (isset($arabicarr[$thischar['char']][3])) {
2518 // medial
2519 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][3];
2520 }
2521 }
2522 } elseif ($nextchar !== \false and (\TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'AL' or \TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'NSM') and $nextchar['type'] == $thischar['type'] and $nextchar['char'] != 1567) {
2523 if (isset($arabicarr[$chardata[$i]['char']][2])) {
2524 // initial
2525 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
2526 }
2527 } elseif ($prevchar !== \false and (\TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'AL' or \TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'NSM') and $prevchar['type'] == $thischar['type'] or $nextchar !== \false and $nextchar['char'] == 1567) {
2528 // final
2529 if ($i > 1 and $thischar['char'] == 1607 and $chardata[$i - 1]['char'] == 1604 and $chardata[$i - 2]['char'] == 1604) {
2530 //Allah Word
2531 // mark characters to delete with false
2532 $chardata2[$i - 2]['char'] = \false;
2533 $chardata2[$i - 1]['char'] = \false;
2534 $chardata2[$i]['char'] = 65010;
2535 } else {
2536 if ($prevchar !== \false and \in_array($prevchar['char'], $endedletter)) {
2537 if (isset($arabicarr[$thischar['char']][0])) {
2538 // isolated
2539 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
2540 }
2541 } else {
2542 if (isset($arabicarr[$thischar['char']][1])) {
2543 // final
2544 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][1];
2545 }
2546 }
2547 }
2548 } elseif (isset($arabicarr[$thischar['char']][0])) {
2549 // isolated
2550 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
2551 }
2552 // if laa letter
2553 if ($laaletter) {
2554 // mark characters to delete with false
2555 $chardata2[$charAL[$x - 1]['i']]['char'] = \false;
2556 }
2557 }
2558 // end if AL (Arabic Letter)
2559 }
2560 // end for each char
2561 /*
2562 * Combining characters that can occur with Arabic Shadda (0651 HEX, 1617 DEC) are replaced.
2563 * Putting the combining mark and shadda in the same glyph allows us to avoid the two marks overlapping each other in an illegible manner.
2564 */
2565 for ($i = 0; $i < $numchars - 1; ++$i) {
2566 if ($chardata2[$i]['char'] == 1617 and isset(\TCPDF_FONT_DATA::$uni_diacritics[$chardata2[$i + 1]['char']])) {
2567 // check if the subtitution font is defined on current font
2568 if (isset($currentfont['cw'][\TCPDF_FONT_DATA::$uni_diacritics[$chardata2[$i + 1]['char']]])) {
2569 $chardata2[$i]['char'] = \false;
2570 $chardata2[$i + 1]['char'] = \TCPDF_FONT_DATA::$uni_diacritics[$chardata2[$i + 1]['char']];
2571 }
2572 }
2573 }
2574 // remove marked characters
2575 foreach ($chardata2 as $key => $value) {
2576 if ($value['char'] === \false) {
2577 unset($chardata2[$key]);
2578 }
2579 }
2580 $chardata = \array_values($chardata2);
2581 $numchars = \count($chardata);
2582 unset($chardata2);
2583 unset($arabicarr);
2584 unset($laaletter);
2585 unset($charAL);
2586 }
2587 // 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.
2588 for ($j = $maxlevel; $j > 0; $j--) {
2589 $ordarray = array();
2590 $revarr = array();
2591 $onlevel = \false;
2592 for ($i = 0; $i < $numchars; ++$i) {
2593 if ($chardata[$i]['level'] >= $j) {
2594 $onlevel = \true;
2595 if (isset(\TCPDF_FONT_DATA::$uni_mirror[$chardata[$i]['char']])) {
2596 // 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.
2597 $chardata[$i]['char'] = \TCPDF_FONT_DATA::$uni_mirror[$chardata[$i]['char']];
2598 }
2599 $revarr[] = $chardata[$i];
2600 } else {
2601 if ($onlevel) {
2602 $revarr = \array_reverse($revarr);
2603 $ordarray = \array_merge($ordarray, $revarr);
2604 $revarr = array();
2605 $onlevel = \false;
2606 }
2607 $ordarray[] = $chardata[$i];
2608 }
2609 }
2610 if ($onlevel) {
2611 $revarr = \array_reverse($revarr);
2612 $ordarray = \array_merge($ordarray, $revarr);
2613 }
2614 $chardata = $ordarray;
2615 }
2616 $ordarray = array();
2617 foreach ($chardata as $cd) {
2618 $ordarray[] = $cd['char'];
2619 // store char values for subsetting
2620 $currentfont['subsetchars'][$cd['char']] = \true;
2621 }
2622 return $ordarray;
2623 }
2624 }
2625 // END OF TCPDF_FONTS CLASS
2626 //============================================================+
2627 // END OF FILE
2628 //============================================================+
2629 }
2630