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