Test Failed
Push — main ( c8394f...8477f1 )
by Rafael
66:21
created

TCPDF_FONTS   F

Complexity

Total Complexity 539

Size/Duplication

Total Lines 2648
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 1588
dl 0
loc 2648
rs 0.8
c 0
b 0
f 0
wmc 539

How to fix   Complexity   

Complex Class

Complex classes like TCPDF_FONTS often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TCPDF_FONTS, and based on these observations, apply Extract Interface, too.

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