Projet

Général

Profil

Paste
Télécharger (92,3 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / libraries / tcpdf-version / include / tcpdf_fonts.php @ 5a7e6170

1
<?php
2
//============================================================+
3
// File name   : tcpdf_fonts.php
4
// Version     : 1.0.009
5
// Begin       : 2008-01-01
6
// Last Update : 2013-09-04
7
// Author      : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
8
// License     : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
9
// -------------------------------------------------------------------
10
// Copyright (C) 2008-2013 Nicola Asuni - Tecnick.com LTD
11
//
12
// This file is part of TCPDF software library.
13
//
14
// TCPDF is free software: you can redistribute it and/or modify it
15
// under the terms of the GNU Lesser General Public License as
16
// published by the Free Software Foundation, either version 3 of the
17
// License, or (at your option) any later version.
18
//
19
// TCPDF is distributed in the hope that it will be useful, but
20
// WITHOUT ANY WARRANTY; without even the implied warranty of
21
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
22
// See the GNU Lesser General Public License for more details.
23
//
24
// You should have received a copy of the GNU Lesser General Public License
25
// along with TCPDF.  If not, see <http://www.gnu.org/licenses/>.
26
//
27
// See LICENSE.TXT file for more information.
28
// -------------------------------------------------------------------
29
//
30
// Description :Font methods for TCPDF library.
31
//
32
//============================================================+
33

    
34
/**
35
 * @file
36
 * Unicode data and font methods for TCPDF library.
37
 * @author Nicola Asuni
38
 * @package com.tecnick.tcpdf
39
 */
40

    
41
/**
42
 * @class TCPDF_FONTS
43
 * Font methods for TCPDF library.
44
 * @package com.tecnick.tcpdf
45
 * @version 1.0.009
46
 * @author Nicola Asuni - info@tecnick.com
47
 */
48
class TCPDF_FONTS {
49

    
50
        /**
51
         * Convert and add the selected TrueType or Type1 font to the fonts folder (that must be writeable).
52
         * @param $fontfile (string) Font file (full path).
53
         * @param $fonttype (string) 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.
54
         * @param $enc (string) 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.
55
         * @param $flags (int) 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.
56
         * @param $outpath (string) Output path for generated font files (must be writeable by the web server). Leave empty for default font folder.
57
         * @param $platid (int) Platform ID for CMAP table to extract (when building a Unicode font for Windows this value should be 3, for Macintosh should be 1).
58
         * @param $encid (int) 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.
59
         * @param $addcbbox (boolean) If true includes the character bounding box information on the php font file.
60
         * @param $link (boolean) If true link to system font instead of copying the font data (not transportable) - Note: do not work with Type1 fonts.
61
         * @return (string) TCPDF font name or boolean false in case of error.
62
         * @author Nicola Asuni
63
         * @since 5.9.123 (2010-09-30)
64
         * @public static
65
         */
66
        public static function addTTFfont($fontfile, $fonttype='', $enc='', $flags=32, $outpath='', $platid=3, $encid=1, $addcbbox=false, $link=false) {
67
                if (!file_exists($fontfile)) {
68
                        // Could not find file
69
                        return false;
70
                }
71
                // font metrics
72
                $fmetric = array();
73
                // build new font name for TCPDF compatibility
74
                $font_path_parts = pathinfo($fontfile);
75
                if (!isset($font_path_parts['filename'])) {
76
                        $font_path_parts['filename'] = substr($font_path_parts['basename'], 0, -(strlen($font_path_parts['extension']) + 1));
77
                }
78
                $font_name = strtolower($font_path_parts['filename']);
79
                $font_name = preg_replace('/[^a-z0-9_]/', '', $font_name);
80
                $search  = array('bold', 'oblique', 'italic', 'regular');
81
                $replace = array('b', 'i', 'i', '');
82
                $font_name = str_replace($search, $replace, $font_name);
83
                if (empty($font_name)) {
84
                        // set generic name
85
                        $font_name = 'tcpdffont';
86
                }
87
                // set output path
88
                if (empty($outpath)) {
89
                        $outpath = self::_getfontpath();
90
                }
91
                // check if this font already exist
92
                if (@file_exists($outpath.$font_name.'.php')) {
93
                        // this font already exist (delete it from fonts folder to rebuild it)
94
                        return $font_name;
95
                }
96
                $fmetric['file'] = $font_name;
97
                $fmetric['ctg'] = $font_name.'.ctg.z';
98
                // get font data
99
                $font = file_get_contents($fontfile);
100
                $fmetric['originalsize'] = strlen($font);
101
                // autodetect font type
102
                if (empty($fonttype)) {
103
                        if (TCPDF_STATIC::_getULONG($font, 0) == 0x10000) {
104
                                // True Type (Unicode or not)
105
                                $fonttype = 'TrueTypeUnicode';
106
                        } elseif (substr($font, 0, 4) == 'OTTO') {
107
                                // Open Type (Unicode or not)
108
                                //Unsupported font format: OpenType with CFF data
109
                                return false;
110
                        } else {
111
                                // Type 1
112
                                $fonttype = 'Type1';
113
                        }
114
                }
115
                // set font type
116
                switch ($fonttype) {
117
                        case 'CID0CT':
118
                        case 'CID0CS':
119
                        case 'CID0KR':
120
                        case 'CID0JP': {
121
                                $fmetric['type'] = 'cidfont0';
122
                                break;
123
                        }
124
                        case 'Type1': {
125
                                $fmetric['type'] = 'Type1';
126
                                if (empty($enc) AND (($flags & 4) == 0)) {
127
                                        $enc = 'cp1252';
128
                                }
129
                                break;
130
                        }
131
                        case 'TrueType': {
132
                                $fmetric['type'] = 'TrueType';
133
                                break;
134
                        }
135
                        case 'TrueTypeUnicode':
136
                        default: {
137
                                $fmetric['type'] = 'TrueTypeUnicode';
138
                                break;
139
                        }
140
                }
141
                // set encoding maps (if any)
142
                $fmetric['enc'] = preg_replace('/[^A-Za-z0-9_\-]/', '', $enc);
143
                $fmetric['diff'] = '';
144
                if (($fmetric['type'] == 'TrueType') OR ($fmetric['type'] == 'Type1')) {
145
                        if (!empty($enc) AND ($enc != 'cp1252') AND isset(TCPDF_FONT_DATA::$encmap[$enc])) {
146
                                // build differences from reference encoding
147
                                $enc_ref = TCPDF_FONT_DATA::$encmap['cp1252'];
148
                                $enc_target = TCPDF_FONT_DATA::$encmap[$enc];
149
                                $last = 0;
150
                                for ($i = 32; $i <= 255; ++$i) {
151
                                        if ($enc_target != $enc_ref[$i]) {
152
                                                if ($i != ($last + 1)) {
153
                                                        $fmetric['diff'] .= $i.' ';
154
                                                }
155
                                                $last = $i;
156
                                                $fmetric['diff'] .= '/'.$enc_target[$i].' ';
157
                                        }
158
                                }
159
                        }
160
                }
161
                // parse the font by type
162
                if ($fmetric['type'] == 'Type1') {
163
                        // ---------- TYPE 1 ----------
164
                        // read first segment
165
                        $a = unpack('Cmarker/Ctype/Vsize', substr($font, 0, 6));
166
                        if ($a['marker'] != 128) {
167
                                // Font file is not a valid binary Type1
168
                                return false;
169
                        }
170
                        $fmetric['size1'] = $a['size'];
171
                        $data = substr($font, 6, $fmetric['size1']);
172
                        // read second segment
173
                        $a = unpack('Cmarker/Ctype/Vsize', substr($font, (6 + $fmetric['size1']), 6));
174
                        if ($a['marker'] != 128) {
175
                                // Font file is not a valid binary Type1
176
                                return false;
177
                        }
178
                        $fmetric['size2'] = $a['size'];
179
                        $encrypted = substr($font, (12 + $fmetric['size1']), $fmetric['size2']);
180
                        $data .= $encrypted;
181
                        // store compressed font
182
                        $fmetric['file'] .= '.z';
183
                        $fp = fopen($outpath.$fmetric['file'], 'wb');
184
                        fwrite($fp, gzcompress($data));
185
                        fclose($fp);
186
                        // get font info
187
                        $fmetric['Flags'] = $flags;
188
                        preg_match ('#/FullName[\s]*\(([^\)]*)#', $font, $matches);
189
                        $fmetric['name'] = preg_replace('/[^a-zA-Z0-9_\-]/', '', $matches[1]);
190
                        preg_match('#/FontBBox[\s]*{([^}]*)#', $font, $matches);
191
                        $fmetric['bbox'] = trim($matches[1]);
192
                        $bv = explode(' ', $fmetric['bbox']);
193
                        $fmetric['Ascent'] = intval($bv[3]);
194
                        $fmetric['Descent'] = intval($bv[1]);
195
                        preg_match('#/ItalicAngle[\s]*([0-9\+\-]*)#', $font, $matches);
196
                        $fmetric['italicAngle'] = intval($matches[1]);
197
                        if ($fmetric['italicAngle'] != 0) {
198
                                $fmetric['Flags'] |= 64;
199
                        }
200
                        preg_match('#/UnderlinePosition[\s]*([0-9\+\-]*)#', $font, $matches);
201
                        $fmetric['underlinePosition'] = intval($matches[1]);
202
                        preg_match('#/UnderlineThickness[\s]*([0-9\+\-]*)#', $font, $matches);
203
                        $fmetric['underlineThickness'] = intval($matches[1]);
204
                        preg_match('#/isFixedPitch[\s]*([^\s]*)#', $font, $matches);
205
                        if ($matches[1] == 'true') {
206
                                $fmetric['Flags'] |= 1;
207
                        }
208
                        // get internal map
209
                        $imap = array();
210
                        if (preg_match_all('#dup[\s]([0-9]+)[\s]*/([^\s]*)[\s]put#sU', $font, $fmap, PREG_SET_ORDER) > 0) {
211
                                foreach ($fmap as $v) {
212
                                        $imap[$v[2]] = $v[1];
213
                                }
214
                        }
215
                        // decrypt eexec encrypted part
216
                        $r = 55665; // eexec encryption constant
217
                        $c1 = 52845;
218
                        $c2 = 22719;
219
                        $elen = strlen($encrypted);
220
                        $eplain = '';
221
                        for ($i = 0; $i < $elen; ++$i) {
222
                                $chr = ord($encrypted[$i]);
223
                                $eplain .= chr($chr ^ ($r >> 8));
224
                                $r = ((($chr + $r) * $c1 + $c2) % 65536);
225
                        }
226
                        if (preg_match('#/ForceBold[\s]*([^\s]*)#', $eplain, $matches) > 0) {
227
                                if ($matches[1] == 'true') {
228
                                        $fmetric['Flags'] |= 0x40000;
229
                                }
230
                        }
231
                        if (preg_match('#/StdVW[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
232
                                $fmetric['StemV'] = intval($matches[1]);
233
                        } else {
234
                                $fmetric['StemV'] = 70;
235
                        }
236
                        if (preg_match('#/StdHW[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
237
                                $fmetric['StemH'] = intval($matches[1]);
238
                        } else {
239
                                $fmetric['StemH'] = 30;
240
                        }
241
                        if (preg_match('#/BlueValues[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
242
                                $bv = explode(' ', $matches[1]);
243
                                if (count($bv) >= 6) {
244
                                        $v1 = intval($bv[2]);
245
                                        $v2 = intval($bv[4]);
246
                                        if ($v1 <= $v2) {
247
                                                $fmetric['XHeight'] = $v1;
248
                                                $fmetric['CapHeight'] = $v2;
249
                                        } else {
250
                                                $fmetric['XHeight'] = $v2;
251
                                                $fmetric['CapHeight'] = $v1;
252
                                        }
253
                                } else {
254
                                        $fmetric['XHeight'] = 450;
255
                                        $fmetric['CapHeight'] = 700;
256
                                }
257
                        } else {
258
                                $fmetric['XHeight'] = 450;
259
                                $fmetric['CapHeight'] = 700;
260
                        }
261
                        // get the number of random bytes at the beginning of charstrings
262
                        if (preg_match('#/lenIV[\s]*([0-9]*)#', $eplain, $matches) > 0) {
263
                                $lenIV = intval($matches[1]);
264
                        } else {
265
                                $lenIV = 4;
266
                        }
267
                        $fmetric['Leading'] = 0;
268
                        // get charstring data
269
                        $eplain = substr($eplain, (strpos($eplain, '/CharStrings') + 1));
270
                        preg_match_all('#/([A-Za-z0-9\.]*)[\s][0-9]+[\s]RD[\s](.*)[\s]ND#sU', $eplain, $matches, PREG_SET_ORDER);
271
                        if (!empty($enc) AND isset(TCPDF_FONT_DATA::$encmap[$enc])) {
272
                                $enc_map = TCPDF_FONT_DATA::$encmap[$enc];
273
                        } else {
274
                                $enc_map = false;
275
                        }
276
                        $fmetric['cw'] = '';
277
                        $fmetric['MaxWidth'] = 0;
278
                        $cwidths = array();
279
                        foreach ($matches as $k => $v) {
280
                                $cid = 0;
281
                                if (isset($imap[$v[1]])) {
282
                                        $cid = $imap[$v[1]];
283
                                } elseif ($enc_map !== false) {
284
                                        $cid = array_search($v[1], $enc_map);
285
                                        if ($cid === false) {
286
                                                $cid = 0;
287
                                        } elseif ($cid > 1000) {
288
                                                $cid -= 1000;
289
                                        }
290
                                }
291
                                // decrypt charstring encrypted part
292
                                $r = 4330; // charstring encryption constant
293
                                $c1 = 52845;
294
                                $c2 = 22719;
295
                                $cd = $v[2];
296
                                $clen = strlen($cd);
297
                                $ccom = array();
298
                                for ($i = 0; $i < $clen; ++$i) {
299
                                        $chr = ord($cd[$i]);
300
                                        $ccom[] = ($chr ^ ($r >> 8));
301
                                        $r = ((($chr + $r) * $c1 + $c2) % 65536);
302
                                }
303
                                // decode numbers
304
                                $cdec = array();
305
                                $ck = 0;
306
                                $i = $lenIV;
307
                                while ($i < $clen) {
308
                                        if ($ccom[$i] < 32) {
309
                                                $cdec[$ck] = $ccom[$i];
310
                                                if (($ck > 0) AND ($cdec[$ck] == 13)) {
311
                                                        // hsbw command: update width
312
                                                        $cwidths[$cid] = $cdec[($ck - 1)];
313
                                                }
314
                                                ++$i;
315
                                        } elseif (($ccom[$i] >= 32) AND ($ccom[$i] <= 246)) {
316
                                                $cdec[$ck] = ($ccom[$i] - 139);
317
                                                ++$i;
318
                                        } elseif (($ccom[$i] >= 247) AND ($ccom[$i] <= 250)) {
319
                                                $cdec[$ck] = ((($ccom[$i] - 247) * 256) + $ccom[($i + 1)] + 108);
320
                                                $i += 2;
321
                                        } elseif (($ccom[$i] >= 251) AND ($ccom[$i] <= 254)) {
322
                                                $cdec[$ck] = ((-($ccom[$i] - 251) * 256) - $ccom[($i + 1)] - 108);
323
                                                $i += 2;
324
                                        } elseif ($ccom[$i] == 255) {
325
                                                $sval = chr($ccom[($i + 1)]).chr($ccom[($i + 2)]).chr($ccom[($i + 3)]).chr($ccom[($i + 4)]);
326
                                                $vsval = unpack('li', $sval);
327
                                                $cdec[$ck] = $vsval['i'];
328
                                                $i += 5;
329
                                        }
330
                                        ++$ck;
331
                                }
332
                        } // end for each matches
333
                        $fmetric['MissingWidth'] = $cwidths[0];
334
                        $fmetric['MaxWidth'] = $fmetric['MissingWidth'];
335
                        $fmetric['AvgWidth'] = 0;
336
                        // set chars widths
337
                        for ($cid = 0; $cid <= 255; ++$cid) {
338
                                if (isset($cwidths[$cid])) {
339
                                        if ($cwidths[$cid] > $fmetric['MaxWidth']) {
340
                                                $fmetric['MaxWidth'] = $cwidths[$cid];
341
                                        }
342
                                        $fmetric['AvgWidth'] += $cwidths[$cid];
343
                                        $fmetric['cw'] .= ','.$cid.'=>'.$cwidths[$cid];
344
                                } else {
345
                                        $fmetric['cw'] .= ','.$cid.'=>'.$fmetric['MissingWidth'];
346
                                }
347
                        }
348
                        $fmetric['AvgWidth'] = round($fmetric['AvgWidth'] / count($cwidths));
349
                } else {
350
                        // ---------- TRUE TYPE ----------
351
                        if ($fmetric['type'] != 'cidfont0') {
352
                                if ($link) {
353
                                        // creates a symbolic link to the existing font
354
                                        symlink($fontfile, $outpath.$fmetric['file']);
355
                                } else {
356
                                        // store compressed font
357
                                        $fmetric['file'] .= '.z';
358
                                        $fp = fopen($outpath.$fmetric['file'], 'wb');
359
                                        fwrite($fp, gzcompress($font));
360
                                        fclose($fp);
361
                                }
362
                        }
363
                        $offset = 0; // offset position of the font data
364
                        if (TCPDF_STATIC::_getULONG($font, $offset) != 0x10000) {
365
                                // sfnt version must be 0x00010000 for TrueType version 1.0.
366
                                return false;
367
                        }
368
                        $offset += 4;
369
                        // get number of tables
370
                        $numTables = TCPDF_STATIC::_getUSHORT($font, $offset);
371
                        $offset += 2;
372
                        // skip searchRange, entrySelector and rangeShift
373
                        $offset += 6;
374
                        // tables array
375
                        $table = array();
376
                        // ---------- get tables ----------
377
                        for ($i = 0; $i < $numTables; ++$i) {
378
                                // get table info
379
                                $tag = substr($font, $offset, 4);
380
                                $offset += 4;
381
                                $table[$tag] = array();
382
                                $table[$tag]['checkSum'] = TCPDF_STATIC::_getULONG($font, $offset);
383
                                $offset += 4;
384
                                $table[$tag]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
385
                                $offset += 4;
386
                                $table[$tag]['length'] = TCPDF_STATIC::_getULONG($font, $offset);
387
                                $offset += 4;
388
                        }
389
                        // check magicNumber
390
                        $offset = $table['head']['offset'] + 12;
391
                        if (TCPDF_STATIC::_getULONG($font, $offset) != 0x5F0F3CF5) {
392
                                // magicNumber must be 0x5F0F3CF5
393
                                return false;
394
                        }
395
                        $offset += 4;
396
                        $offset += 2; // skip flags
397
                        // get FUnits
398
                        $fmetric['unitsPerEm'] = TCPDF_STATIC::_getUSHORT($font, $offset);
399
                        $offset += 2;
400
                        // units ratio constant
401
                        $urk = (1000 / $fmetric['unitsPerEm']);
402
                        $offset += 16; // skip created, modified
403
                        $xMin = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
404
                        $offset += 2;
405
                        $yMin = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
406
                        $offset += 2;
407
                        $xMax = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
408
                        $offset += 2;
409
                        $yMax = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
410
                        $offset += 2;
411
                        $fmetric['bbox'] = ''.$xMin.' '.$yMin.' '.$xMax.' '.$yMax.'';
412
                        $macStyle = TCPDF_STATIC::_getUSHORT($font, $offset);
413
                        $offset += 2;
414
                        // PDF font flags
415
                        $fmetric['Flags'] = $flags;
416
                        if (($macStyle & 2) == 2) {
417
                                // italic flag
418
                                $fmetric['Flags'] |= 64;
419
                        }
420
                        // get offset mode (indexToLocFormat : 0 = short, 1 = long)
421
                        $offset = $table['head']['offset'] + 50;
422
                        $short_offset = (TCPDF_STATIC::_getSHORT($font, $offset) == 0);
423
                        $offset += 2;
424
                        // get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
425
                        $indexToLoc = array();
426
                        $offset = $table['loca']['offset'];
427
                        if ($short_offset) {
428
                                // short version
429
                                $tot_num_glyphs = floor($table['loca']['length'] / 2); // numGlyphs + 1
430
                                for ($i = 0; $i < $tot_num_glyphs; ++$i) {
431
                                        $indexToLoc[$i] = TCPDF_STATIC::_getUSHORT($font, $offset) * 2;
432
                                        $offset += 2;
433
                                }
434
                        } else {
435
                                // long version
436
                                $tot_num_glyphs = floor($table['loca']['length'] / 4); // numGlyphs + 1
437
                                for ($i = 0; $i < $tot_num_glyphs; ++$i) {
438
                                        $indexToLoc[$i] = TCPDF_STATIC::_getULONG($font, $offset);
439
                                        $offset += 4;
440
                                }
441
                        }
442
                        // get glyphs indexes of chars from cmap table
443
                        $offset = $table['cmap']['offset'] + 2;
444
                        $numEncodingTables = TCPDF_STATIC::_getUSHORT($font, $offset);
445
                        $offset += 2;
446
                        $encodingTables = array();
447
                        for ($i = 0; $i < $numEncodingTables; ++$i) {
448
                                $encodingTables[$i]['platformID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
449
                                $offset += 2;
450
                                $encodingTables[$i]['encodingID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
451
                                $offset += 2;
452
                                $encodingTables[$i]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
453
                                $offset += 4;
454
                        }
455
                        // ---------- get os/2 metrics ----------
456
                        $offset = $table['OS/2']['offset'];
457
                        $offset += 2; // skip version
458
                        // xAvgCharWidth
459
                        $fmetric['AvgWidth'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
460
                        $offset += 2;
461
                        // usWeightClass
462
                        $usWeightClass = round(TCPDF_STATIC::_getUFWORD($font, $offset) * $urk);
463
                        // estimate StemV and StemH (400 = usWeightClass for Normal - Regular font)
464
                        $fmetric['StemV'] = round((70 * $usWeightClass) / 400);
465
                        $fmetric['StemH'] = round((30 * $usWeightClass) / 400);
466
                        $offset += 2;
467
                        $offset += 2; // usWidthClass
468
                        $fsType = TCPDF_STATIC::_getSHORT($font, $offset);
469
                        $offset += 2;
470
                        if ($fsType == 2) {
471
                                // This Font cannot be modified, embedded or exchanged in any manner without first obtaining permission of the legal owner.
472
                                return false;
473
                        }
474
                        // ---------- get font name ----------
475
                        $fmetric['name'] = '';
476
                        $offset = $table['name']['offset'];
477
                        $offset += 2; // skip Format selector (=0).
478
                        // Number of NameRecords that follow n.
479
                        $numNameRecords = TCPDF_STATIC::_getUSHORT($font, $offset);
480
                        $offset += 2;
481
                        // Offset to start of string storage (from start of table).
482
                        $stringStorageOffset = TCPDF_STATIC::_getUSHORT($font, $offset);
483
                        $offset += 2;
484
                        for ($i = 0; $i < $numNameRecords; ++$i) {
485
                                $offset += 6; // skip Platform ID, Platform-specific encoding ID, Language ID.
486
                                // Name ID.
487
                                $nameID = TCPDF_STATIC::_getUSHORT($font, $offset);
488
                                $offset += 2;
489
                                if ($nameID == 6) {
490
                                        // String length (in bytes).
491
                                        $stringLength = TCPDF_STATIC::_getUSHORT($font, $offset);
492
                                        $offset += 2;
493
                                        // String offset from start of storage area (in bytes).
494
                                        $stringOffset = TCPDF_STATIC::_getUSHORT($font, $offset);
495
                                        $offset += 2;
496
                                        $offset = ($table['name']['offset'] + $stringStorageOffset + $stringOffset);
497
                                        $fmetric['name'] = substr($font, $offset, $stringLength);
498
                                        $fmetric['name'] = preg_replace('/[^a-zA-Z0-9_\-]/', '', $fmetric['name']);
499
                                        break;
500
                                } else {
501
                                        $offset += 4; // skip String length, String offset
502
                                }
503
                        }
504
                        if (empty($fmetric['name'])) {
505
                                $fmetric['name'] = $font_name;
506
                        }
507
                        // ---------- get post data ----------
508
                        $offset = $table['post']['offset'];
509
                        $offset += 4; // skip Format Type
510
                        $fmetric['italicAngle'] = TCPDF_STATIC::_getFIXED($font, $offset);
511
                        $offset += 4;
512
                        $fmetric['underlinePosition'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
513
                        $offset += 2;
514
                        $fmetric['underlineThickness'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
515
                        $offset += 2;
516
                        $isFixedPitch = (TCPDF_STATIC::_getULONG($font, $offset) == 0) ? false : true;
517
                        $offset += 2;
518
                        if ($isFixedPitch) {
519
                                $fmetric['Flags'] |= 1;
520
                        }
521
                        // ---------- get hhea data ----------
522
                        $offset = $table['hhea']['offset'];
523
                        $offset += 4; // skip Table version number
524
                        // Ascender
525
                        $fmetric['Ascent'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
526
                        $offset += 2;
527
                        // Descender
528
                        $fmetric['Descent'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
529
                        $offset += 2;
530
                        // LineGap
531
                        $fmetric['Leading'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
532
                        $offset += 2;
533
                        // advanceWidthMax
534
                        $fmetric['MaxWidth'] = round(TCPDF_STATIC::_getUFWORD($font, $offset) * $urk);
535
                        $offset += 2;
536
                        $offset += 22; // skip some values
537
                        // get the number of hMetric entries in hmtx table
538
                        $numberOfHMetrics = TCPDF_STATIC::_getUSHORT($font, $offset);
539
                        // ---------- get maxp data ----------
540
                        $offset = $table['maxp']['offset'];
541
                        $offset += 4; // skip Table version number
542
                        // get the the number of glyphs in the font.
543
                        $numGlyphs = TCPDF_STATIC::_getUSHORT($font, $offset);
544
                        // ---------- get CIDToGIDMap ----------
545
                        $ctg = array();
546
                        foreach ($encodingTables as $enctable) {
547
                                // get only specified Platform ID and Encoding ID
548
                                if (($enctable['platformID'] == $platid) AND ($enctable['encodingID'] == $encid)) {
549
                                        $offset = $table['cmap']['offset'] + $enctable['offset'];
550
                                        $format = TCPDF_STATIC::_getUSHORT($font, $offset);
551
                                        $offset += 2;
552
                                        switch ($format) {
553
                                                case 0: { // Format 0: Byte encoding table
554
                                                        $offset += 4; // skip length and version/language
555
                                                        for ($c = 0; $c < 256; ++$c) {
556
                                                                $g = TCPDF_STATIC::_getBYTE($font, $offset);
557
                                                                $ctg[$c] = $g;
558
                                                                ++$offset;
559
                                                        }
560
                                                        break;
561
                                                }
562
                                                case 2: { // Format 2: High-byte mapping through table
563
                                                        $offset += 4; // skip length and version/language
564
                                                        $numSubHeaders = 0;
565
                                                        for ($i = 0; $i < 256; ++$i) {
566
                                                                // Array that maps high bytes to subHeaders: value is subHeader index * 8.
567
                                                                $subHeaderKeys[$i] = (TCPDF_STATIC::_getUSHORT($font, $offset) / 8);
568
                                                                $offset += 2;
569
                                                                if ($numSubHeaders < $subHeaderKeys[$i]) {
570
                                                                        $numSubHeaders = $subHeaderKeys[$i];
571
                                                                }
572
                                                        }
573
                                                        // the number of subHeaders is equal to the max of subHeaderKeys + 1
574
                                                        ++$numSubHeaders;
575
                                                        // read subHeader structures
576
                                                        $subHeaders = array();
577
                                                        $numGlyphIndexArray = 0;
578
                                                        for ($k = 0; $k < $numSubHeaders; ++$k) {
579
                                                                $subHeaders[$k]['firstCode'] = TCPDF_STATIC::_getUSHORT($font, $offset);
580
                                                                $offset += 2;
581
                                                                $subHeaders[$k]['entryCount'] = TCPDF_STATIC::_getUSHORT($font, $offset);
582
                                                                $offset += 2;
583
                                                                $subHeaders[$k]['idDelta'] = TCPDF_STATIC::_getUSHORT($font, $offset);
584
                                                                $offset += 2;
585
                                                                $subHeaders[$k]['idRangeOffset'] = TCPDF_STATIC::_getUSHORT($font, $offset);
586
                                                                $offset += 2;
587
                                                                $subHeaders[$k]['idRangeOffset'] -= (2 + (($numSubHeaders - $k - 1) * 8));
588
                                                                $subHeaders[$k]['idRangeOffset'] /= 2;
589
                                                                $numGlyphIndexArray += $subHeaders[$k]['entryCount'];
590
                                                        }
591
                                                        for ($k = 0; $k < $numGlyphIndexArray; ++$k) {
592
                                                                $glyphIndexArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
593
                                                                $offset += 2;
594
                                                        }
595
                                                        for ($i = 0; $i < 256; ++$i) {
596
                                                                $k = $subHeaderKeys[$i];
597
                                                                if ($k == 0) {
598
                                                                        // one byte code
599
                                                                        $c = $i;
600
                                                                        $g = $glyphIndexArray[0];
601
                                                                        $ctg[$c] = $g;
602
                                                                } else {
603
                                                                        // two bytes code
604
                                                                        $start_byte = $subHeaders[$k]['firstCode'];
605
                                                                        $end_byte = $start_byte + $subHeaders[$k]['entryCount'];
606
                                                                        for ($j = $start_byte; $j < $end_byte; ++$j) {
607
                                                                                // combine high and low bytes
608
                                                                                $c = (($i << 8) + $j);
609
                                                                                $idRangeOffset = ($subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode']);
610
                                                                                $g = ($glyphIndexArray[$idRangeOffset] + $idDelta[$k]) % 65536;
611
                                                                                if ($g < 0) {
612
                                                                                        $g = 0;
613
                                                                                }
614
                                                                                $ctg[$c] = $g;
615
                                                                        }
616
                                                                }
617
                                                        }
618
                                                        break;
619
                                                }
620
                                                case 4: { // Format 4: Segment mapping to delta values
621
                                                        $length = TCPDF_STATIC::_getUSHORT($font, $offset);
622
                                                        $offset += 2;
623
                                                        $offset += 2; // skip version/language
624
                                                        $segCount = floor(TCPDF_STATIC::_getUSHORT($font, $offset) / 2);
625
                                                        $offset += 2;
626
                                                        $offset += 6; // skip searchRange, entrySelector, rangeShift
627
                                                        $endCount = array(); // array of end character codes for each segment
628
                                                        for ($k = 0; $k < $segCount; ++$k) {
629
                                                                $endCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
630
                                                                $offset += 2;
631
                                                        }
632
                                                        $offset += 2; // skip reservedPad
633
                                                        $startCount = array(); // array of start character codes for each segment
634
                                                        for ($k = 0; $k < $segCount; ++$k) {
635
                                                                $startCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
636
                                                                $offset += 2;
637
                                                        }
638
                                                        $idDelta = array(); // delta for all character codes in segment
639
                                                        for ($k = 0; $k < $segCount; ++$k) {
640
                                                                $idDelta[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
641
                                                                $offset += 2;
642
                                                        }
643
                                                        $idRangeOffset = array(); // Offsets into glyphIdArray or 0
644
                                                        for ($k = 0; $k < $segCount; ++$k) {
645
                                                                $idRangeOffset[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
646
                                                                $offset += 2;
647
                                                        }
648
                                                        $gidlen = (floor($length / 2) - 8 - (4 * $segCount));
649
                                                        $glyphIdArray = array(); // glyph index array
650
                                                        for ($k = 0; $k < $gidlen; ++$k) {
651
                                                                $glyphIdArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
652
                                                                $offset += 2;
653
                                                        }
654
                                                        for ($k = 0; $k < $segCount; ++$k) {
655
                                                                for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
656
                                                                        if ($idRangeOffset[$k] == 0) {
657
                                                                                $g = ($idDelta[$k] + $c) % 65536;
658
                                                                        } else {
659
                                                                                $gid = (floor($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k));
660
                                                                                $g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536;
661
                                                                        }
662
                                                                        if ($g < 0) {
663
                                                                                $g = 0;
664
                                                                        }
665
                                                                        $ctg[$c] = $g;
666
                                                                }
667
                                                        }
668
                                                        break;
669
                                                }
670
                                                case 6: { // Format 6: Trimmed table mapping
671
                                                        $offset += 4; // skip length and version/language
672
                                                        $firstCode = TCPDF_STATIC::_getUSHORT($font, $offset);
673
                                                        $offset += 2;
674
                                                        $entryCount = TCPDF_STATIC::_getUSHORT($font, $offset);
675
                                                        $offset += 2;
676
                                                        for ($k = 0; $k < $entryCount; ++$k) {
677
                                                                $c = ($k + $firstCode);
678
                                                                $g = TCPDF_STATIC::_getUSHORT($font, $offset);
679
                                                                $offset += 2;
680
                                                                $ctg[$c] = $g;
681
                                                        }
682
                                                        break;
683
                                                }
684
                                                case 8: { // Format 8: Mixed 16-bit and 32-bit coverage
685
                                                        $offset += 10; // skip reserved, length and version/language
686
                                                        for ($k = 0; $k < 8192; ++$k) {
687
                                                                $is32[$k] = TCPDF_STATIC::_getBYTE($font, $offset);
688
                                                                ++$offset;
689
                                                        }
690
                                                        $nGroups = TCPDF_STATIC::_getULONG($font, $offset);
691
                                                        $offset += 4;
692
                                                        for ($i = 0; $i < $nGroups; ++$i) {
693
                                                                $startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
694
                                                                $offset += 4;
695
                                                                $endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
696
                                                                $offset += 4;
697
                                                                $startGlyphID = TCPDF_STATIC::_getULONG($font, $offset);
698
                                                                $offset += 4;
699
                                                                for ($k = $startCharCode; $k <= $endCharCode; ++$k) {
700
                                                                        $is32idx = floor($c / 8);
701
                                                                        if ((isset($is32[$is32idx])) AND (($is32[$is32idx] & (1 << (7 - ($c % 8)))) == 0)) {
702
                                                                                $c = $k;
703
                                                                        } else {
704
                                                                                // 32 bit format
705
                                                                                // convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4)
706
                                                                                //LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232
707
                                                                                //SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888
708
                                                                                $c = ((55232 + ($k >> 10)) << 10) + (0xDC00 + ($k & 0x3FF)) -56613888;
709
                                                                        }
710
                                                                        $ctg[$c] = 0;
711
                                                                        ++$startGlyphID;
712
                                                                }
713
                                                        }
714
                                                        break;
715
                                                }
716
                                                case 10: { // Format 10: Trimmed array
717
                                                        $offset += 10; // skip reserved, length and version/language
718
                                                        $startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
719
                                                        $offset += 4;
720
                                                        $numChars = TCPDF_STATIC::_getULONG($font, $offset);
721
                                                        $offset += 4;
722
                                                        for ($k = 0; $k < $numChars; ++$k) {
723
                                                                $c = ($k + $startCharCode);
724
                                                                $g = TCPDF_STATIC::_getUSHORT($font, $offset);
725
                                                                $ctg[$c] = $g;
726
                                                                $offset += 2;
727
                                                        }
728
                                                        break;
729
                                                }
730
                                                case 12: { // Format 12: Segmented coverage
731
                                                        $offset += 10; // skip length and version/language
732
                                                        $nGroups = TCPDF_STATIC::_getULONG($font, $offset);
733
                                                        $offset += 4;
734
                                                        for ($k = 0; $k < $nGroups; ++$k) {
735
                                                                $startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
736
                                                                $offset += 4;
737
                                                                $endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
738
                                                                $offset += 4;
739
                                                                $startGlyphCode = TCPDF_STATIC::_getULONG($font, $offset);
740
                                                                $offset += 4;
741
                                                                for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
742
                                                                        $ctg[$c] = $startGlyphCode;
743
                                                                        ++$startGlyphCode;
744
                                                                }
745
                                                        }
746
                                                        break;
747
                                                }
748
                                                case 13: { // Format 13: Many-to-one range mappings
749
                                                        // to be implemented ...
750
                                                        break;
751
                                                }
752
                                                case 14: { // Format 14: Unicode Variation Sequences
753
                                                        // to be implemented ...
754
                                                        break;
755
                                                }
756
                                        }
757
                                }
758
                        }
759
                        if (!isset($ctg[0])) {
760
                                $ctg[0] = 0;
761
                        }
762
                        // get xHeight (height of x)
763
                        $offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[120]] + 4);
764
                        $yMin = TCPDF_STATIC::_getFWORD($font, $offset);
765
                        $offset += 4;
766
                        $yMax = TCPDF_STATIC::_getFWORD($font, $offset);
767
                        $offset += 2;
768
                        $fmetric['XHeight'] = round(($yMax - $yMin) * $urk);
769
                        // get CapHeight (height of H)
770
                        $offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[72]] + 4);
771
                        $yMin = TCPDF_STATIC::_getFWORD($font, $offset);
772
                        $offset += 4;
773
                        $yMax = TCPDF_STATIC::_getFWORD($font, $offset);
774
                        $offset += 2;
775
                        $fmetric['CapHeight'] = round(($yMax - $yMin) * $urk);
776
                        // ceate widths array
777
                        $cw = array();
778
                        $offset = $table['hmtx']['offset'];
779
                        for ($i = 0 ; $i < $numberOfHMetrics; ++$i) {
780
                                $cw[$i] = round(TCPDF_STATIC::_getUFWORD($font, $offset) * $urk);
781
                                $offset += 4; // skip lsb
782
                        }
783
                        if ($numberOfHMetrics < $numGlyphs) {
784
                                // fill missing widths with the last value
785
                                $cw = array_pad($cw, $numGlyphs, $cw[($numberOfHMetrics - 1)]);
786
                        }
787
                        $fmetric['MissingWidth'] = $cw[0];
788
                        $fmetric['cw'] = '';
789
                        for ($cid = 0; $cid <= 65535; ++$cid) {
790
                                if (isset($ctg[$cid])) {
791
                                        if (isset($cw[$ctg[$cid]])) {
792
                                                $fmetric['cw'] .= ','.$cid.'=>'.$cw[$ctg[$cid]];
793
                                        }
794
                                        if ($addcbbox AND isset($indexToLoc[$ctg[$cid]])) {
795
                                                $offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[$cid]]);
796
                                                $xMin = round(TCPDF_STATIC::_getFWORD($font, $offset + 2)) * $urk;
797
                                                $yMin = round(TCPDF_STATIC::_getFWORD($font, $offset + 4)) * $urk;
798
                                                $xMax = round(TCPDF_STATIC::_getFWORD($font, $offset + 6)) * $urk;
799
                                                $yMax = round(TCPDF_STATIC::_getFWORD($font, $offset + 8)) * $urk;
800
                                                $fmetric['cbbox'] .= ','.$cid.'=>array('.$xMin.','.$yMin.','.$xMax.','.$yMax.')';
801
                                        }
802
                                }
803
                        }
804
                } // end of true type
805
                if (($fmetric['type'] == 'TrueTypeUnicode') AND (count($ctg) == 256)) {
806
                        $fmetric['type'] == 'TrueType';
807
                }
808
                // ---------- create php font file ----------
809
                $pfile = '<'.'?'.'php'."\n";
810
                $pfile .= '// TCPDF FONT FILE DESCRIPTION'."\n";
811
                $pfile .= '$type=\''.$fmetric['type'].'\';'."\n";
812
                $pfile .= '$name=\''.$fmetric['name'].'\';'."\n";
813
                $pfile .= '$up='.$fmetric['underlinePosition'].';'."\n";
814
                $pfile .= '$ut='.$fmetric['underlineThickness'].';'."\n";
815
                if ($fmetric['MissingWidth'] > 0) {
816
                        $pfile .= '$dw='.$fmetric['MissingWidth'].';'."\n";
817
                } else {
818
                        $pfile .= '$dw='.$fmetric['AvgWidth'].';'."\n";
819
                }
820
                $pfile .= '$diff=\''.$fmetric['diff'].'\';'."\n";
821
                if ($fmetric['type'] == 'Type1') {
822
                        // Type 1
823
                        $pfile .= '$enc=\''.$fmetric['enc'].'\';'."\n";
824
                        $pfile .= '$file=\''.$fmetric['file'].'\';'."\n";
825
                        $pfile .= '$size1='.$fmetric['size1'].';'."\n";
826
                        $pfile .= '$size2='.$fmetric['size2'].';'."\n";
827
                } else {
828
                        $pfile .= '$originalsize='.$fmetric['originalsize'].';'."\n";
829
                        if ($fmetric['type'] == 'cidfont0') {
830
                                // CID-0
831
                                switch ($fonttype) {
832
                                        case 'CID0JP': {
833
                                                $pfile .= '// Japanese'."\n";
834
                                                $pfile .= '$enc=\'UniJIS-UTF16-H\';'."\n";
835
                                                $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Japan1\',\'Supplement\'=>5);'."\n";
836
                                                $pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');'."\n";
837
                                                break;
838
                                        }
839
                                        case 'CID0KR': {
840
                                                $pfile .= '// Korean'."\n";
841
                                                $pfile .= '$enc=\'UniKS-UTF16-H\';'."\n";
842
                                                $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Korea1\',\'Supplement\'=>0);'."\n";
843
                                                $pfile .= 'include(dirname(__FILE__).\'/uni2cid_ak12.php\');'."\n";
844
                                                break;
845
                                        }
846
                                        case 'CID0CS': {
847
                                                $pfile .= '// Chinese Simplified'."\n";
848
                                                $pfile .= '$enc=\'UniGB-UTF16-H\';'."\n";
849
                                                $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'GB1\',\'Supplement\'=>2);'."\n";
850
                                                $pfile .= 'include(dirname(__FILE__).\'/uni2cid_ag15.php\');'."\n";
851
                                                break;
852
                                        }
853
                                        case 'CID0CT':
854
                                        default: {
855
                                                $pfile .= '// Chinese Traditional'."\n";
856
                                                $pfile .= '$enc=\'UniCNS-UTF16-H\';'."\n";
857
                                                $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'CNS1\',\'Supplement\'=>0);'."\n";
858
                                                $pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');'."\n";
859
                                                break;
860
                                        }
861
                                }
862
                        } else {
863
                                // TrueType
864
                                $pfile .= '$enc=\''.$fmetric['enc'].'\';'."\n";
865
                                $pfile .= '$file=\''.$fmetric['file'].'\';'."\n";
866
                                $pfile .= '$ctg=\''.$fmetric['ctg'].'\';'."\n";
867
                                // create CIDToGIDMap
868
                                $cidtogidmap = str_pad('', 131072, "\x00"); // (256 * 256 * 2) = 131072
869
                                foreach ($ctg as $cid => $gid) {
870
                                        $cidtogidmap = self::updateCIDtoGIDmap($cidtogidmap, $cid, $ctg[$cid]);
871
                                }
872
                                // store compressed CIDToGIDMap
873
                                $fp = fopen($outpath.$fmetric['ctg'], 'wb');
874
                                fwrite($fp, gzcompress($cidtogidmap));
875
                                fclose($fp);
876
                        }
877
                }
878
                $pfile .= '$desc=array(';
879
                $pfile .= '\'Flags\'=>'.$fmetric['Flags'].',';
880
                $pfile .= '\'FontBBox\'=>\'['.$fmetric['bbox'].']\',';
881
                $pfile .= '\'ItalicAngle\'=>'.$fmetric['italicAngle'].',';
882
                $pfile .= '\'Ascent\'=>'.$fmetric['Ascent'].',';
883
                $pfile .= '\'Descent\'=>'.$fmetric['Descent'].',';
884
                $pfile .= '\'Leading\'=>'.$fmetric['Leading'].',';
885
                $pfile .= '\'CapHeight\'=>'.$fmetric['CapHeight'].',';
886
                $pfile .= '\'XHeight\'=>'.$fmetric['XHeight'].',';
887
                $pfile .= '\'StemV\'=>'.$fmetric['StemV'].',';
888
                $pfile .= '\'StemH\'=>'.$fmetric['StemH'].',';
889
                $pfile .= '\'AvgWidth\'=>'.$fmetric['AvgWidth'].',';
890
                $pfile .= '\'MaxWidth\'=>'.$fmetric['MaxWidth'].',';
891
                $pfile .= '\'MissingWidth\'=>'.$fmetric['MissingWidth'].'';
892
                $pfile .= ');'."\n";
893
                if (isset($fmetric['cbbox'])) {
894
                        $pfile .= '$cbbox=array('.substr($fmetric['cbbox'], 1).');'."\n";
895
                }
896
                $pfile .= '$cw=array('.substr($fmetric['cw'], 1).');'."\n";
897
                $pfile .= '// --- EOF ---'."\n";
898
                // store file
899
                $fp = fopen($outpath.$font_name.'.php', 'w');
900
                fwrite($fp, $pfile);
901
                fclose($fp);
902
                // return TCPDF font name
903
                return $font_name;
904
        }
905

    
906
        /**
907
         * Returs the checksum of a TTF table.
908
         * @param $table (string) table to check
909
         * @param $length (int) length of table in bytes
910
         * @return int checksum
911
         * @author Nicola Asuni
912
         * @since 5.2.000 (2010-06-02)
913
         * @public static
914
         */
915
        public static function _getTTFtableChecksum($table, $length) {
916
                $sum = 0;
917
                $tlen = ($length / 4);
918
                $offset = 0;
919
                for ($i = 0; $i < $tlen; ++$i) {
920
                        $v = unpack('Ni', substr($table, $offset, 4));
921
                        $sum += $v['i'];
922
                        $offset += 4;
923
                }
924
                $sum = unpack('Ni', pack('N', $sum));
925
                return $sum['i'];
926
        }
927

    
928
        /**
929
         * Returns a subset of the TrueType font data without the unused glyphs.
930
         * @param $font (string) TrueType font data.
931
         * @param $subsetchars (array) Array of used characters (the glyphs to keep).
932
         * @return (string) A subset of TrueType font data without the unused glyphs.
933
         * @author Nicola Asuni
934
         * @since 5.2.000 (2010-06-02)
935
         * @public static
936
         */
937
        public static function _getTrueTypeFontSubset($font, $subsetchars) {
938
                ksort($subsetchars);
939
                $offset = 0; // offset position of the font data
940
                if (TCPDF_STATIC::_getULONG($font, $offset) != 0x10000) {
941
                        // sfnt version must be 0x00010000 for TrueType version 1.0.
942
                        return $font;
943
                }
944
                $offset += 4;
945
                // get number of tables
946
                $numTables = TCPDF_STATIC::_getUSHORT($font, $offset);
947
                $offset += 2;
948
                // skip searchRange, entrySelector and rangeShift
949
                $offset += 6;
950
                // tables array
951
                $table = array();
952
                // for each table
953
                for ($i = 0; $i < $numTables; ++$i) {
954
                        // get table info
955
                        $tag = substr($font, $offset, 4);
956
                        $offset += 4;
957
                        $table[$tag] = array();
958
                        $table[$tag]['checkSum'] = TCPDF_STATIC::_getULONG($font, $offset);
959
                        $offset += 4;
960
                        $table[$tag]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
961
                        $offset += 4;
962
                        $table[$tag]['length'] = TCPDF_STATIC::_getULONG($font, $offset);
963
                        $offset += 4;
964
                }
965
                // check magicNumber
966
                $offset = $table['head']['offset'] + 12;
967
                if (TCPDF_STATIC::_getULONG($font, $offset) != 0x5F0F3CF5) {
968
                        // magicNumber must be 0x5F0F3CF5
969
                        return $font;
970
                }
971
                $offset += 4;
972
                // get offset mode (indexToLocFormat : 0 = short, 1 = long)
973
                $offset = $table['head']['offset'] + 50;
974
                $short_offset = (TCPDF_STATIC::_getSHORT($font, $offset) == 0);
975
                $offset += 2;
976
                // get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
977
                $indexToLoc = array();
978
                $offset = $table['loca']['offset'];
979
                if ($short_offset) {
980
                        // short version
981
                        $tot_num_glyphs = floor($table['loca']['length'] / 2); // numGlyphs + 1
982
                        for ($i = 0; $i < $tot_num_glyphs; ++$i) {
983
                                $indexToLoc[$i] = TCPDF_STATIC::_getUSHORT($font, $offset) * 2;
984
                                $offset += 2;
985
                        }
986
                } else {
987
                        // long version
988
                        $tot_num_glyphs = ($table['loca']['length'] / 4); // numGlyphs + 1
989
                        for ($i = 0; $i < $tot_num_glyphs; ++$i) {
990
                                $indexToLoc[$i] = TCPDF_STATIC::_getULONG($font, $offset);
991
                                $offset += 4;
992
                        }
993
                }
994
                // get glyphs indexes of chars from cmap table
995
                $subsetglyphs = array(); // glyph IDs on key
996
                $subsetglyphs[0] = true; // character codes that do not correspond to any glyph in the font should be mapped to glyph index 0
997
                $offset = $table['cmap']['offset'] + 2;
998
                $numEncodingTables = TCPDF_STATIC::_getUSHORT($font, $offset);
999
                $offset += 2;
1000
                $encodingTables = array();
1001
                for ($i = 0; $i < $numEncodingTables; ++$i) {
1002
                        $encodingTables[$i]['platformID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
1003
                        $offset += 2;
1004
                        $encodingTables[$i]['encodingID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
1005
                        $offset += 2;
1006
                        $encodingTables[$i]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
1007
                        $offset += 4;
1008
                }
1009
                foreach ($encodingTables as $enctable) {
1010
                        // get all platforms and encodings
1011
                        $offset = $table['cmap']['offset'] + $enctable['offset'];
1012
                        $format = TCPDF_STATIC::_getUSHORT($font, $offset);
1013
                        $offset += 2;
1014
                        switch ($format) {
1015
                                case 0: { // Format 0: Byte encoding table
1016
                                        $offset += 4; // skip length and version/language
1017
                                        for ($c = 0; $c < 256; ++$c) {
1018
                                                if (isset($subsetchars[$c])) {
1019
                                                        $g = TCPDF_STATIC::_getBYTE($font, $offset);
1020
                                                        $subsetglyphs[$g] = true;
1021
                                                }
1022
                                                ++$offset;
1023
                                        }
1024
                                        break;
1025
                                }
1026
                                case 2: { // Format 2: High-byte mapping through table
1027
                                        $offset += 4; // skip length and version/language
1028
                                        $numSubHeaders = 0;
1029
                                        for ($i = 0; $i < 256; ++$i) {
1030
                                                // Array that maps high bytes to subHeaders: value is subHeader index * 8.
1031
                                                $subHeaderKeys[$i] = (TCPDF_STATIC::_getUSHORT($font, $offset) / 8);
1032
                                                $offset += 2;
1033
                                                if ($numSubHeaders < $subHeaderKeys[$i]) {
1034
                                                        $numSubHeaders = $subHeaderKeys[$i];
1035
                                                }
1036
                                        }
1037
                                        // the number of subHeaders is equal to the max of subHeaderKeys + 1
1038
                                        ++$numSubHeaders;
1039
                                        // read subHeader structures
1040
                                        $subHeaders = array();
1041
                                        $numGlyphIndexArray = 0;
1042
                                        for ($k = 0; $k < $numSubHeaders; ++$k) {
1043
                                                $subHeaders[$k]['firstCode'] = TCPDF_STATIC::_getUSHORT($font, $offset);
1044
                                                $offset += 2;
1045
                                                $subHeaders[$k]['entryCount'] = TCPDF_STATIC::_getUSHORT($font, $offset);
1046
                                                $offset += 2;
1047
                                                $subHeaders[$k]['idDelta'] = TCPDF_STATIC::_getUSHORT($font, $offset);
1048
                                                $offset += 2;
1049
                                                $subHeaders[$k]['idRangeOffset'] = TCPDF_STATIC::_getUSHORT($font, $offset);
1050
                                                $offset += 2;
1051
                                                $subHeaders[$k]['idRangeOffset'] -= (2 + (($numSubHeaders - $k - 1) * 8));
1052
                                                $subHeaders[$k]['idRangeOffset'] /= 2;
1053
                                                $numGlyphIndexArray += $subHeaders[$k]['entryCount'];
1054
                                        }
1055
                                        for ($k = 0; $k < $numGlyphIndexArray; ++$k) {
1056
                                                $glyphIndexArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
1057
                                                $offset += 2;
1058
                                        }
1059
                                        for ($i = 0; $i < 256; ++$i) {
1060
                                                $k = $subHeaderKeys[$i];
1061
                                                if ($k == 0) {
1062
                                                        // one byte code
1063
                                                        $c = $i;
1064
                                                        if (isset($subsetchars[$c])) {
1065
                                                                $g = $glyphIndexArray[0];
1066
                                                                $subsetglyphs[$g] = true;
1067
                                                        }
1068
                                                } else {
1069
                                                        // two bytes code
1070
                                                        $start_byte = $subHeaders[$k]['firstCode'];
1071
                                                        $end_byte = $start_byte + $subHeaders[$k]['entryCount'];
1072
                                                        for ($j = $start_byte; $j < $end_byte; ++$j) {
1073
                                                                // combine high and low bytes
1074
                                                                $c = (($i << 8) + $j);
1075
                                                                if (isset($subsetchars[$c])) {
1076
                                                                        $idRangeOffset = ($subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode']);
1077
                                                                        $g = ($glyphIndexArray[$idRangeOffset] + $idDelta[$k]) % 65536;
1078
                                                                        if ($g < 0) {
1079
                                                                                $g = 0;
1080
                                                                        }
1081
                                                                        $subsetglyphs[$g] = true;
1082
                                                                }
1083
                                                        }
1084
                                                }
1085
                                        }
1086
                                        break;
1087
                                }
1088
                                case 4: { // Format 4: Segment mapping to delta values
1089
                                        $length = TCPDF_STATIC::_getUSHORT($font, $offset);
1090
                                        $offset += 2;
1091
                                        $offset += 2; // skip version/language
1092
                                        $segCount = floor(TCPDF_STATIC::_getUSHORT($font, $offset) / 2);
1093
                                        $offset += 2;
1094
                                        $offset += 6; // skip searchRange, entrySelector, rangeShift
1095
                                        $endCount = array(); // array of end character codes for each segment
1096
                                        for ($k = 0; $k < $segCount; ++$k) {
1097
                                                $endCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
1098
                                                $offset += 2;
1099
                                        }
1100
                                        $offset += 2; // skip reservedPad
1101
                                        $startCount = array(); // array of start character codes for each segment
1102
                                        for ($k = 0; $k < $segCount; ++$k) {
1103
                                                $startCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
1104
                                                $offset += 2;
1105
                                        }
1106
                                        $idDelta = array(); // delta for all character codes in segment
1107
                                        for ($k = 0; $k < $segCount; ++$k) {
1108
                                                $idDelta[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
1109
                                                $offset += 2;
1110
                                        }
1111
                                        $idRangeOffset = array(); // Offsets into glyphIdArray or 0
1112
                                        for ($k = 0; $k < $segCount; ++$k) {
1113
                                                $idRangeOffset[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
1114
                                                $offset += 2;
1115
                                        }
1116
                                        $gidlen = (floor($length / 2) - 8 - (4 * $segCount));
1117
                                        $glyphIdArray = array(); // glyph index array
1118
                                        for ($k = 0; $k < $gidlen; ++$k) {
1119
                                                $glyphIdArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
1120
                                                $offset += 2;
1121
                                        }
1122
                                        for ($k = 0; $k < $segCount; ++$k) {
1123
                                                for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
1124
                                                        if (isset($subsetchars[$c])) {
1125
                                                                if ($idRangeOffset[$k] == 0) {
1126
                                                                        $g = ($idDelta[$k] + $c) % 65536;
1127
                                                                } else {
1128
                                                                        $gid = (floor($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k));
1129
                                                                        $g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536;
1130
                                                                }
1131
                                                                if ($g < 0) {
1132
                                                                        $g = 0;
1133
                                                                }
1134
                                                                $subsetglyphs[$g] = true;
1135
                                                        }
1136
                                                }
1137
                                        }
1138
                                        break;
1139
                                }
1140
                                case 6: { // Format 6: Trimmed table mapping
1141
                                        $offset += 4; // skip length and version/language
1142
                                        $firstCode = TCPDF_STATIC::_getUSHORT($font, $offset);
1143
                                        $offset += 2;
1144
                                        $entryCount = TCPDF_STATIC::_getUSHORT($font, $offset);
1145
                                        $offset += 2;
1146
                                        for ($k = 0; $k < $entryCount; ++$k) {
1147
                                                $c = ($k + $firstCode);
1148
                                                if (isset($subsetchars[$c])) {
1149
                                                        $g = TCPDF_STATIC::_getUSHORT($font, $offset);
1150
                                                        $subsetglyphs[$g] = true;
1151
                                                }
1152
                                                $offset += 2;
1153
                                        }
1154
                                        break;
1155
                                }
1156
                                case 8: { // Format 8: Mixed 16-bit and 32-bit coverage
1157
                                        $offset += 10; // skip reserved, length and version/language
1158
                                        for ($k = 0; $k < 8192; ++$k) {
1159
                                                $is32[$k] = TCPDF_STATIC::_getBYTE($font, $offset);
1160
                                                ++$offset;
1161
                                        }
1162
                                        $nGroups = TCPDF_STATIC::_getULONG($font, $offset);
1163
                                        $offset += 4;
1164
                                        for ($i = 0; $i < $nGroups; ++$i) {
1165
                                                $startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
1166
                                                $offset += 4;
1167
                                                $endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
1168
                                                $offset += 4;
1169
                                                $startGlyphID = TCPDF_STATIC::_getULONG($font, $offset);
1170
                                                $offset += 4;
1171
                                                for ($k = $startCharCode; $k <= $endCharCode; ++$k) {
1172
                                                        $is32idx = floor($c / 8);
1173
                                                        if ((isset($is32[$is32idx])) AND (($is32[$is32idx] & (1 << (7 - ($c % 8)))) == 0)) {
1174
                                                                $c = $k;
1175
                                                        } else {
1176
                                                                // 32 bit format
1177
                                                                // convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4)
1178
                                                                //LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232
1179
                                                                //SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888
1180
                                                                $c = ((55232 + ($k >> 10)) << 10) + (0xDC00 + ($k & 0x3FF)) -56613888;
1181
                                                        }
1182
                                                        if (isset($subsetchars[$c])) {
1183
                                                                $subsetglyphs[$startGlyphID] = true;
1184
                                                        }
1185
                                                        ++$startGlyphID;
1186
                                                }
1187
                                        }
1188
                                        break;
1189
                                }
1190
                                case 10: { // Format 10: Trimmed array
1191
                                        $offset += 10; // skip reserved, length and version/language
1192
                                        $startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
1193
                                        $offset += 4;
1194
                                        $numChars = TCPDF_STATIC::_getULONG($font, $offset);
1195
                                        $offset += 4;
1196
                                        for ($k = 0; $k < $numChars; ++$k) {
1197
                                                $c = ($k + $startCharCode);
1198
                                                if (isset($subsetchars[$c])) {
1199
                                                        $g = TCPDF_STATIC::_getUSHORT($font, $offset);
1200
                                                        $subsetglyphs[$g] = true;
1201
                                                }
1202
                                                $offset += 2;
1203
                                        }
1204
                                        break;
1205
                                }
1206
                                case 12: { // Format 12: Segmented coverage
1207
                                        $offset += 10; // skip length and version/language
1208
                                        $nGroups = TCPDF_STATIC::_getULONG($font, $offset);
1209
                                        $offset += 4;
1210
                                        for ($k = 0; $k < $nGroups; ++$k) {
1211
                                                $startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
1212
                                                $offset += 4;
1213
                                                $endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
1214
                                                $offset += 4;
1215
                                                $startGlyphCode = TCPDF_STATIC::_getULONG($font, $offset);
1216
                                                $offset += 4;
1217
                                                for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
1218
                                                        if (isset($subsetchars[$c])) {
1219
                                                                $subsetglyphs[$startGlyphCode] = true;
1220
                                                        }
1221
                                                        ++$startGlyphCode;
1222
                                                }
1223
                                        }
1224
                                        break;
1225
                                }
1226
                                case 13: { // Format 13: Many-to-one range mappings
1227
                                        // to be implemented ...
1228
                                        break;
1229
                                }
1230
                                case 14: { // Format 14: Unicode Variation Sequences
1231
                                        // to be implemented ...
1232
                                        break;
1233
                                }
1234
                        }
1235
                }
1236
                // include all parts of composite glyphs
1237
                $new_sga = $subsetglyphs;
1238
                while (!empty($new_sga)) {
1239
                        $sga = $new_sga;
1240
                        $new_sga = array();
1241
                        foreach ($sga as $key => $val) {
1242
                                if (isset($indexToLoc[$key])) {
1243
                                        $offset = ($table['glyf']['offset'] + $indexToLoc[$key]);
1244
                                        $numberOfContours = TCPDF_STATIC::_getSHORT($font, $offset);
1245
                                        $offset += 2;
1246
                                        if ($numberOfContours < 0) { // composite glyph
1247
                                                $offset += 8; // skip xMin, yMin, xMax, yMax
1248
                                                do {
1249
                                                        $flags = TCPDF_STATIC::_getUSHORT($font, $offset);
1250
                                                        $offset += 2;
1251
                                                        $glyphIndex = TCPDF_STATIC::_getUSHORT($font, $offset);
1252
                                                        $offset += 2;
1253
                                                        if (!isset($subsetglyphs[$glyphIndex])) {
1254
                                                                // add missing glyphs
1255
                                                                $new_sga[$glyphIndex] = true;
1256
                                                        }
1257
                                                        // skip some bytes by case
1258
                                                        if ($flags & 1) {
1259
                                                                $offset += 4;
1260
                                                        } else {
1261
                                                                $offset += 2;
1262
                                                        }
1263
                                                        if ($flags & 8) {
1264
                                                                $offset += 2;
1265
                                                        } elseif ($flags & 64) {
1266
                                                                $offset += 4;
1267
                                                        } elseif ($flags & 128) {
1268
                                                                $offset += 8;
1269
                                                        }
1270
                                                } while ($flags & 32);
1271
                                        }
1272
                                }
1273
                        }
1274
                        $subsetglyphs += $new_sga;
1275
                }
1276
                // sort glyphs by key (and remove duplicates)
1277
                ksort($subsetglyphs);
1278
                // build new glyf and loca tables
1279
                $glyf = '';
1280
                $loca = '';
1281
                $offset = 0;
1282
                $glyf_offset = $table['glyf']['offset'];
1283
                for ($i = 0; $i < $tot_num_glyphs; ++$i) {
1284
                        if (isset($subsetglyphs[$i])) {
1285
                                $length = ($indexToLoc[($i + 1)] - $indexToLoc[$i]);
1286
                                $glyf .= substr($font, ($glyf_offset + $indexToLoc[$i]), $length);
1287
                        } else {
1288
                                $length = 0;
1289
                        }
1290
                        if ($short_offset) {
1291
                                $loca .= pack('n', floor($offset / 2));
1292
                        } else {
1293
                                $loca .= pack('N', $offset);
1294
                        }
1295
                        $offset += $length;
1296
                }
1297
                // array of table names to preserve (loca and glyf tables will be added later)
1298
                // the cmap table is not needed and shall not be present, since the mapping from character codes to glyph descriptions is provided separately
1299
                $table_names = array ('head', 'hhea', 'hmtx', 'maxp', 'cvt ', 'fpgm', 'prep'); // minimum required table names
1300
                // get the tables to preserve
1301
                $offset = 12;
1302
                foreach ($table as $tag => $val) {
1303
                        if (in_array($tag, $table_names)) {
1304
                                $table[$tag]['data'] = substr($font, $table[$tag]['offset'], $table[$tag]['length']);
1305
                                if ($tag == 'head') {
1306
                                        // set the checkSumAdjustment to 0
1307
                                        $table[$tag]['data'] = substr($table[$tag]['data'], 0, 8)."\x0\x0\x0\x0".substr($table[$tag]['data'], 12);
1308
                                }
1309
                                $pad = 4 - ($table[$tag]['length'] % 4);
1310
                                if ($pad != 4) {
1311
                                        // the length of a table must be a multiple of four bytes
1312
                                        $table[$tag]['length'] += $pad;
1313
                                        $table[$tag]['data'] .= str_repeat("\x0", $pad);
1314
                                }
1315
                                $table[$tag]['offset'] = $offset;
1316
                                $offset += $table[$tag]['length'];
1317
                                // check sum is not changed (so keep the following line commented)
1318
                                //$table[$tag]['checkSum'] = self::_getTTFtableChecksum($table[$tag]['data'], $table[$tag]['length']);
1319
                        } else {
1320
                                unset($table[$tag]);
1321
                        }
1322
                }
1323
                // add loca
1324
                $table['loca']['data'] = $loca;
1325
                $table['loca']['length'] = strlen($loca);
1326
                $pad = 4 - ($table['loca']['length'] % 4);
1327
                if ($pad != 4) {
1328
                        // the length of a table must be a multiple of four bytes
1329
                        $table['loca']['length'] += $pad;
1330
                        $table['loca']['data'] .= str_repeat("\x0", $pad);
1331
                }
1332
                $table['loca']['offset'] = $offset;
1333
                $table['loca']['checkSum'] = self::_getTTFtableChecksum($table['loca']['data'], $table['loca']['length']);
1334
                $offset += $table['loca']['length'];
1335
                // add glyf
1336
                $table['glyf']['data'] = $glyf;
1337
                $table['glyf']['length'] = strlen($glyf);
1338
                $pad = 4 - ($table['glyf']['length'] % 4);
1339
                if ($pad != 4) {
1340
                        // the length of a table must be a multiple of four bytes
1341
                        $table['glyf']['length'] += $pad;
1342
                        $table['glyf']['data'] .= str_repeat("\x0", $pad);
1343
                }
1344
                $table['glyf']['offset'] = $offset;
1345
                $table['glyf']['checkSum'] = self::_getTTFtableChecksum($table['glyf']['data'], $table['glyf']['length']);
1346
                // rebuild font
1347
                $font = '';
1348
                $font .= pack('N', 0x10000); // sfnt version
1349
                $numTables = count($table);
1350
                $font .= pack('n', $numTables); // numTables
1351
                $entrySelector = floor(log($numTables, 2));
1352
                $searchRange = pow(2, $entrySelector) * 16;
1353
                $rangeShift = ($numTables * 16) - $searchRange;
1354
                $font .= pack('n', $searchRange); // searchRange
1355
                $font .= pack('n', $entrySelector); // entrySelector
1356
                $font .= pack('n', $rangeShift); // rangeShift
1357
                $offset = ($numTables * 16);
1358
                foreach ($table as $tag => $data) {
1359
                        $font .= $tag; // tag
1360
                        $font .= pack('N', $data['checkSum']); // checkSum
1361
                        $font .= pack('N', ($data['offset'] + $offset)); // offset
1362
                        $font .= pack('N', $data['length']); // length
1363
                }
1364
                foreach ($table as $data) {
1365
                        $font .= $data['data'];
1366
                }
1367
                // set checkSumAdjustment on head table
1368
                $checkSumAdjustment = 0xB1B0AFBA - self::_getTTFtableChecksum($font, strlen($font));
1369
                $font = substr($font, 0, $table['head']['offset'] + 8).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + 12);
1370
                return $font;
1371
        }
1372

    
1373
        /**
1374
         * Outputs font widths
1375
         * @param $font (array) font data
1376
         * @param $cidoffset (int) offset for CID values
1377
         * @return PDF command string for font widths
1378
         * @author Nicola Asuni
1379
         * @since 4.4.000 (2008-12-07)
1380
         * @public static
1381
         */
1382
        public static function _putfontwidths($font, $cidoffset=0) {
1383
                ksort($font['cw']);
1384
                $rangeid = 0;
1385
                $range = array();
1386
                $prevcid = -2;
1387
                $prevwidth = -1;
1388
                $interval = false;
1389
                // for each character
1390
                foreach ($font['cw'] as $cid => $width) {
1391
                        $cid -= $cidoffset;
1392
                        if ($font['subset'] AND (!isset($font['subsetchars'][$cid]))) {
1393
                                // ignore the unused characters (font subsetting)
1394
                                continue;
1395
                        }
1396
                        if ($width != $font['dw']) {
1397
                                if ($cid == ($prevcid + 1)) {
1398
                                        // consecutive CID
1399
                                        if ($width == $prevwidth) {
1400
                                                if ($width == $range[$rangeid][0]) {
1401
                                                        $range[$rangeid][] = $width;
1402
                                                } else {
1403
                                                        array_pop($range[$rangeid]);
1404
                                                        // new range
1405
                                                        $rangeid = $prevcid;
1406
                                                        $range[$rangeid] = array();
1407
                                                        $range[$rangeid][] = $prevwidth;
1408
                                                        $range[$rangeid][] = $width;
1409
                                                }
1410
                                                $interval = true;
1411
                                                $range[$rangeid]['interval'] = true;
1412
                                        } else {
1413
                                                if ($interval) {
1414
                                                        // new range
1415
                                                        $rangeid = $cid;
1416
                                                        $range[$rangeid] = array();
1417
                                                        $range[$rangeid][] = $width;
1418
                                                } else {
1419
                                                        $range[$rangeid][] = $width;
1420
                                                }
1421
                                                $interval = false;
1422
                                        }
1423
                                } else {
1424
                                        // new range
1425
                                        $rangeid = $cid;
1426
                                        $range[$rangeid] = array();
1427
                                        $range[$rangeid][] = $width;
1428
                                        $interval = false;
1429
                                }
1430
                                $prevcid = $cid;
1431
                                $prevwidth = $width;
1432
                        }
1433
                }
1434
                // optimize ranges
1435
                $prevk = -1;
1436
                $nextk = -1;
1437
                $prevint = false;
1438
                foreach ($range as $k => $ws) {
1439
                        $cws = count($ws);
1440
                        if (($k == $nextk) AND (!$prevint) AND ((!isset($ws['interval'])) OR ($cws < 4))) {
1441
                                if (isset($range[$k]['interval'])) {
1442
                                        unset($range[$k]['interval']);
1443
                                }
1444
                                $range[$prevk] = array_merge($range[$prevk], $range[$k]);
1445
                                unset($range[$k]);
1446
                        } else {
1447
                                $prevk = $k;
1448
                        }
1449
                        $nextk = $k + $cws;
1450
                        if (isset($ws['interval'])) {
1451
                                if ($cws > 3) {
1452
                                        $prevint = true;
1453
                                } else {
1454
                                        $prevint = false;
1455
                                }
1456
                                if (isset($range[$k]['interval'])) {
1457
                                        unset($range[$k]['interval']);
1458
                                }
1459
                                --$nextk;
1460
                        } else {
1461
                                $prevint = false;
1462
                        }
1463
                }
1464
                // output data
1465
                $w = '';
1466
                foreach ($range as $k => $ws) {
1467
                        if (count(array_count_values($ws)) == 1) {
1468
                                // interval mode is more compact
1469
                                $w .= ' '.$k.' '.($k + count($ws) - 1).' '.$ws[0];
1470
                        } else {
1471
                                // range mode
1472
                                $w .= ' '.$k.' [ '.implode(' ', $ws).' ]';
1473
                        }
1474
                }
1475
                return '/W ['.$w.' ]';
1476
        }
1477

    
1478
        /**
1479
         * Returns the unicode caracter specified by the value
1480
         * @param $c (int) UTF-8 value
1481
         * @param $unicode (boolean) True if we are in unicode mode, false otherwise.
1482
         * @return Returns the specified character.
1483
         * @since 2.3.000 (2008-03-05)
1484
         * @public static
1485
         */
1486
        public static function unichr($c, $unicode=true) {
1487
                if (!$unicode) {
1488
                        return chr($c);
1489
                } elseif ($c <= 0x7F) {
1490
                        // one byte
1491
                        return chr($c);
1492
                } elseif ($c <= 0x7FF) {
1493
                        // two bytes
1494
                        return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F);
1495
                } elseif ($c <= 0xFFFF) {
1496
                        // three bytes
1497
                        return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
1498
                } elseif ($c <= 0x10FFFF) {
1499
                        // four bytes
1500
                        return chr(0xF0 | $c >> 18).chr(0x80 | $c >> 12 & 0x3F).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
1501
                } else {
1502
                        return '';
1503
                }
1504
        }
1505

    
1506
        /**
1507
         * Returns the unicode caracter specified by UTF-8 value
1508
         * @param $c (int) UTF-8 value
1509
         * @return Returns the specified character.
1510
         * @public static
1511
         */
1512
        public static function unichrUnicode($c) {
1513
                return self::unichr($c, true);
1514
        }
1515

    
1516
        /**
1517
         * Returns the unicode caracter specified by ASCII value
1518
         * @param $c (int) UTF-8 value
1519
         * @return Returns the specified character.
1520
         * @public static
1521
         */
1522
        public static function unichrASCII($c) {
1523
                return self::unichr($c, false);
1524
        }
1525

    
1526
        /**
1527
         * Converts array of UTF-8 characters to UTF16-BE string.<br>
1528
         * Based on: http://www.faqs.org/rfcs/rfc2781.html
1529
         * <pre>
1530
         *   Encoding UTF-16:
1531
         *
1532
         *   Encoding of a single character from an ISO 10646 character value to
1533
         *    UTF-16 proceeds as follows. Let U be the character number, no greater
1534
         *    than 0x10FFFF.
1535
         *
1536
         *    1) If U < 0x10000, encode U as a 16-bit unsigned integer and
1537
         *       terminate.
1538
         *
1539
         *    2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
1540
         *       U' must be less than or equal to 0xFFFFF. That is, U' can be
1541
         *       represented in 20 bits.
1542
         *
1543
         *    3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
1544
         *       0xDC00, respectively. These integers each have 10 bits free to
1545
         *       encode the character value, for a total of 20 bits.
1546
         *
1547
         *    4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order
1548
         *       bits of W1 and the 10 low-order bits of U' to the 10 low-order
1549
         *       bits of W2. Terminate.
1550
         *
1551
         *    Graphically, steps 2 through 4 look like:
1552
         *    U' = yyyyyyyyyyxxxxxxxxxx
1553
         *    W1 = 110110yyyyyyyyyy
1554
         *    W2 = 110111xxxxxxxxxx
1555
         * </pre>
1556
         * @param $unicode (array) array containing UTF-8 unicode values
1557
         * @param $setbom (boolean) if true set the Byte Order Mark (BOM = 0xFEFF)
1558
         * @return string
1559
         * @protected
1560
         * @author Nicola Asuni
1561
         * @since 2.1.000 (2008-01-08)
1562
         * @public static
1563
         */
1564
        public static function arrUTF8ToUTF16BE($unicode, $setbom=false) {
1565
                $outstr = ''; // string to be returned
1566
                if ($setbom) {
1567
                        $outstr .= "\xFE\xFF"; // Byte Order Mark (BOM)
1568
                }
1569
                foreach ($unicode as $char) {
1570
                        if ($char == 0x200b) {
1571
                                // skip Unicode Character 'ZERO WIDTH SPACE' (DEC:8203, U+200B)
1572
                        } elseif ($char == 0xFFFD) {
1573
                                $outstr .= "\xFF\xFD"; // replacement character
1574
                        } elseif ($char < 0x10000) {
1575
                                $outstr .= chr($char >> 0x08);
1576
                                $outstr .= chr($char & 0xFF);
1577
                        } else {
1578
                                $char -= 0x10000;
1579
                                $w1 = 0xD800 | ($char >> 0x0a);
1580
                                $w2 = 0xDC00 | ($char & 0x3FF);
1581
                                $outstr .= chr($w1 >> 0x08);
1582
                                $outstr .= chr($w1 & 0xFF);
1583
                                $outstr .= chr($w2 >> 0x08);
1584
                                $outstr .= chr($w2 & 0xFF);
1585
                        }
1586
                }
1587
                return $outstr;
1588
        }
1589

    
1590
        /**
1591
         * Convert an array of UTF8 values to array of unicode characters
1592
         * @param $ta (array) The input array of UTF8 values.
1593
         * @param $isunicode (boolean) True for Unicode mode, false otherwise.
1594
         * @return Return array of unicode characters
1595
         * @since 4.5.037 (2009-04-07)
1596
         * @public static
1597
         */
1598
        public static function UTF8ArrayToUniArray($ta, $isunicode=true) {
1599
                if ($isunicode) {
1600
                        return array_map(array('self', 'unichrUnicode'), $ta);
1601
                }
1602
                return array_map(array('self', 'unichrASCII'), $ta);
1603
        }
1604

    
1605
        /**
1606
         * Extract a slice of the $strarr array and return it as string.
1607
         * @param $strarr (string) The input array of characters.
1608
         * @param $start (int) the starting element of $strarr.
1609
         * @param $end (int) first element that will not be returned.
1610
         * @param $unicode (boolean) True if we are in unicode mode, false otherwise.
1611
         * @return Return part of a string
1612
         * @public static
1613
         */
1614
        public static function UTF8ArrSubString($strarr, $start='', $end='', $unicode=true) {
1615
                if (strlen($start) == 0) {
1616
                        $start = 0;
1617
                }
1618
                if (strlen($end) == 0) {
1619
                        $end = count($strarr);
1620
                }
1621
                $string = '';
1622
                for ($i = $start; $i < $end; ++$i) {
1623
                        $string .= self::unichr($strarr[$i], $unicode);
1624
                }
1625
                return $string;
1626
        }
1627

    
1628
        /**
1629
         * Extract a slice of the $uniarr array and return it as string.
1630
         * @param $uniarr (string) The input array of characters.
1631
         * @param $start (int) the starting element of $strarr.
1632
         * @param $end (int) first element that will not be returned.
1633
         * @return Return part of a string
1634
         * @since 4.5.037 (2009-04-07)
1635
         * @public static
1636
         */
1637
        public static function UniArrSubString($uniarr, $start='', $end='') {
1638
                if (strlen($start) == 0) {
1639
                        $start = 0;
1640
                }
1641
                if (strlen($end) == 0) {
1642
                        $end = count($uniarr);
1643
                }
1644
                $string = '';
1645
                for ($i=$start; $i < $end; ++$i) {
1646
                        $string .= $uniarr[$i];
1647
                }
1648
                return $string;
1649
        }
1650

    
1651
        /**
1652
         * Update the CIDToGIDMap string with a new value.
1653
         * @param $map (string) CIDToGIDMap.
1654
         * @param $cid (int) CID value.
1655
         * @param $gid (int) GID value.
1656
         * @return (string) CIDToGIDMap.
1657
         * @author Nicola Asuni
1658
         * @since 5.9.123 (2011-09-29)
1659
         * @public static
1660
         */
1661
        public static function updateCIDtoGIDmap($map, $cid, $gid) {
1662
                if (($cid >= 0) AND ($cid <= 0xFFFF) AND ($gid >= 0)) {
1663
                        if ($gid > 0xFFFF) {
1664
                                $gid -= 0x10000;
1665
                        }
1666
                        $map[($cid * 2)] = chr($gid >> 8);
1667
                        $map[(($cid * 2) + 1)] = chr($gid & 0xFF);
1668
                }
1669
                return $map;
1670
        }
1671

    
1672
        /**
1673
         * Return fonts path
1674
         * @return string
1675
         * @public static
1676
         */
1677
        public static function _getfontpath() {
1678
                if (!defined('K_PATH_FONTS') AND is_dir($fdir = realpath(dirname(__FILE__).'/../fonts'))) {
1679
                        if (substr($fdir, -1) != '/') {
1680
                                $fdir .= '/';
1681
                        }
1682
                        define('K_PATH_FONTS', $fdir);
1683
                }
1684
                return defined('K_PATH_FONTS') ? K_PATH_FONTS : '';
1685
        }
1686

    
1687
        /**
1688
         * Return font full path
1689
         * @param $file (string) Font file name.
1690
         * @param $fontdir (string) Font directory (set to false fto search on default directories)
1691
         * @return string Font full path or empty string
1692
         * @author Nicola Asuni
1693
         * @since 6.0.025
1694
         * @public static
1695
         */
1696
        public static function getFontFullPath($file, $fontdir=false) {
1697
                $fontfile = '';
1698
                // search files on various directories
1699
                if (($fontdir !== false) AND @file_exists($fontdir.$file)) {
1700
                        $fontfile = $fontdir.$file;
1701
                } elseif (@file_exists(self::_getfontpath().$file)) {
1702
                        $fontfile = self::_getfontpath().$file;
1703
                } elseif (@file_exists($file)) {
1704
                        $fontfile = $file;
1705
                }
1706
                return $fontfile;
1707
        }
1708

    
1709
        /**
1710
         * Converts UTF-8 characters array to array of Latin1 characters array<br>
1711
         * @param $unicode (array) array containing UTF-8 unicode values
1712
         * @return array
1713
         * @author Nicola Asuni
1714
         * @since 4.8.023 (2010-01-15)
1715
         * @public static
1716
         */
1717
        public static function UTF8ArrToLatin1Arr($unicode) {
1718
                $outarr = array(); // array to be returned
1719
                foreach ($unicode as $char) {
1720
                        if ($char < 256) {
1721
                                $outarr[] = $char;
1722
                        } elseif (array_key_exists($char, TCPDF_FONT_DATA::$uni_utf8tolatin)) {
1723
                                // map from UTF-8
1724
                                $outarr[] = TCPDF_FONT_DATA::$uni_utf8tolatin[$char];
1725
                        } elseif ($char == 0xFFFD) {
1726
                                // skip
1727
                        } else {
1728
                                $outarr[] = 63; // '?' character
1729
                        }
1730
                }
1731
                return $outarr;
1732
        }
1733

    
1734
        /**
1735
         * Converts UTF-8 characters array to array of Latin1 string<br>
1736
         * @param $unicode (array) array containing UTF-8 unicode values
1737
         * @return array
1738
         * @author Nicola Asuni
1739
         * @since 4.8.023 (2010-01-15)
1740
         * @public static
1741
         */
1742
        public static function UTF8ArrToLatin1($unicode) {
1743
                $outstr = ''; // string to be returned
1744
                foreach ($unicode as $char) {
1745
                        if ($char < 256) {
1746
                                $outstr .= chr($char);
1747
                        } elseif (array_key_exists($char, TCPDF_FONT_DATA::$uni_utf8tolatin)) {
1748
                                // map from UTF-8
1749
                                $outstr .= chr(TCPDF_FONT_DATA::$uni_utf8tolatin[$char]);
1750
                        } elseif ($char == 0xFFFD) {
1751
                                // skip
1752
                        } else {
1753
                                $outstr .= '?';
1754
                        }
1755
                }
1756
                return $outstr;
1757
        }
1758

    
1759
        /**
1760
         * Converts UTF-8 character to integer value.<br>
1761
         * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
1762
         * Based on: http://www.faqs.org/rfcs/rfc3629.html
1763
         * <pre>
1764
         *    Char. number range  |        UTF-8 octet sequence
1765
         *       (hexadecimal)    |              (binary)
1766
         *    --------------------+-----------------------------------------------
1767
         *    0000 0000-0000 007F | 0xxxxxxx
1768
         *    0000 0080-0000 07FF | 110xxxxx 10xxxxxx
1769
         *    0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
1770
         *    0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
1771
         *    ---------------------------------------------------------------------
1772
         *
1773
         *   ABFN notation:
1774
         *   ---------------------------------------------------------------------
1775
         *   UTF8-octets = *( UTF8-char )
1776
         *   UTF8-char   = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
1777
         *   UTF8-1      = %x00-7F
1778
         *   UTF8-2      = %xC2-DF UTF8-tail
1779
         *
1780
         *   UTF8-3      = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
1781
         *                 %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
1782
         *   UTF8-4      = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
1783
         *                 %xF4 %x80-8F 2( UTF8-tail )
1784
         *   UTF8-tail   = %x80-BF
1785
         *   ---------------------------------------------------------------------
1786
         * </pre>
1787
         * @param $uch (string) character string to process.
1788
         * @return integer Unicode value
1789
         * @author Nicola Asuni
1790
         * @public static
1791
         */
1792
        public static function uniord($uch) {
1793
                if (function_exists('mb_convert_encoding')) {
1794
                        list(, $char) = @unpack('N', mb_convert_encoding($uch, 'UCS-4BE', 'UTF-8'));
1795
                        if ($char >= 0) {
1796
                                return $char;
1797
                        }
1798
                }
1799
                $bytes = array(); // array containing single character byte sequences
1800
                $countbytes = 0;
1801
                $numbytes = 1; // number of octetc needed to represent the UTF-8 character
1802
                $length = strlen($uch);
1803
                for ($i = 0; $i < $length; ++$i) {
1804
                        $char = ord($uch[$i]); // get one string character at time
1805
                        if ($countbytes == 0) { // get starting octect
1806
                                if ($char <= 0x7F) {
1807
                                        return $char; // use the character "as is" because is ASCII
1808
                                } elseif (($char >> 0x05) == 0x06) { // 2 bytes character (0x06 = 110 BIN)
1809
                                        $bytes[] = ($char - 0xC0) << 0x06;
1810
                                        ++$countbytes;
1811
                                        $numbytes = 2;
1812
                                } elseif (($char >> 0x04) == 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
1813
                                        $bytes[] = ($char - 0xE0) << 0x0C;
1814
                                        ++$countbytes;
1815
                                        $numbytes = 3;
1816
                                } elseif (($char >> 0x03) == 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
1817
                                        $bytes[] = ($char - 0xF0) << 0x12;
1818
                                        ++$countbytes;
1819
                                        $numbytes = 4;
1820
                                } else {
1821
                                        // use replacement character for other invalid sequences
1822
                                        return 0xFFFD;
1823
                                }
1824
                        } elseif (($char >> 0x06) == 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
1825
                                $bytes[] = $char - 0x80;
1826
                                ++$countbytes;
1827
                                if ($countbytes == $numbytes) {
1828
                                        // compose UTF-8 bytes to a single unicode value
1829
                                        $char = $bytes[0];
1830
                                        for ($j = 1; $j < $numbytes; ++$j) {
1831
                                                $char += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
1832
                                        }
1833
                                        if ((($char >= 0xD800) AND ($char <= 0xDFFF)) OR ($char >= 0x10FFFF)) {
1834
                                                // The definition of UTF-8 prohibits encoding character numbers between
1835
                                                // U+D800 and U+DFFF, which are reserved for use with the UTF-16
1836
                                                // encoding form (as surrogate pairs) and do not directly represent
1837
                                                // characters.
1838
                                                return 0xFFFD; // use replacement character
1839
                                        } else {
1840
                                                return $char;
1841
                                        }
1842
                                }
1843
                        } else {
1844
                                // use replacement character for other invalid sequences
1845
                                return 0xFFFD;
1846
                        }
1847
                }
1848
                return 0xFFFD;
1849
        }
1850

    
1851
        /**
1852
         * Converts UTF-8 strings to codepoints array.<br>
1853
         * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
1854
         * @param $str (string) string to process.
1855
         * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise.
1856
         * @param $currentfont (array) Reference to current font array.
1857
         * @return array containing codepoints (UTF-8 characters values)
1858
         * @author Nicola Asuni
1859
         * @public static
1860
         */
1861
        public static function UTF8StringToArray($str, $isunicode=true, &$currentfont) {
1862
                if ($isunicode) {
1863
                        // requires PCRE unicode support turned on
1864
                        $chars = TCPDF_STATIC::pregSplit('//','u', $str, -1, PREG_SPLIT_NO_EMPTY);
1865
                        $carr = array_map(array('self', 'uniord'), $chars);
1866
                } else {
1867
                        $chars = str_split($str);
1868
                        $carr = array_map('ord', $chars);
1869
                }
1870
                $currentfont['subsetchars'] += array_fill_keys($carr, true);
1871
                return $carr;
1872
        }
1873

    
1874
        /**
1875
         * Converts UTF-8 strings to Latin1 when using the standard 14 core fonts.<br>
1876
         * @param $str (string) string to process.
1877
         * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise.
1878
         * @param $currentfont (array) Reference to current font array.
1879
         * @return string
1880
         * @since 3.2.000 (2008-06-23)
1881
         * @public static
1882
         */
1883
        public static function UTF8ToLatin1($str, $isunicode=true, &$currentfont) {
1884
                $unicode = self::UTF8StringToArray($str, $isunicode, $currentfont); // array containing UTF-8 unicode values
1885
                return self::UTF8ArrToLatin1($unicode);
1886
        }
1887

    
1888
        /**
1889
         * Converts UTF-8 strings to UTF16-BE.<br>
1890
         * @param $str (string) string to process.
1891
         * @param $setbom (boolean) if true set the Byte Order Mark (BOM = 0xFEFF)
1892
         * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise.
1893
         * @param $currentfont (array) Reference to current font array.
1894
         * @return string
1895
         * @author Nicola Asuni
1896
         * @since 1.53.0.TC005 (2005-01-05)
1897
         * @public static
1898
         */
1899
        public static function UTF8ToUTF16BE($str, $setbom=false, $isunicode=true, &$currentfont) {
1900
                if (!$isunicode) {
1901
                        return $str; // string is not in unicode
1902
                }
1903
                $unicode = self::UTF8StringToArray($str, $isunicode, $currentfont); // array containing UTF-8 unicode values
1904
                return self::arrUTF8ToUTF16BE($unicode, $setbom);
1905
        }
1906

    
1907
        /**
1908
         * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
1909
         * @param $str (string) string to manipulate.
1910
         * @param $setbom (bool) if true set the Byte Order Mark (BOM = 0xFEFF)
1911
         * @param $forcertl (bool) if true forces RTL text direction
1912
         * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
1913
         * @param $currentfont (array) Reference to current font array.
1914
         * @return string
1915
         * @author Nicola Asuni
1916
         * @since 2.1.000 (2008-01-08)
1917
         * @public static
1918
         */
1919
        public static function utf8StrRev($str, $setbom=false, $forcertl=false, $isunicode=true, &$currentfont) {
1920
                return self::utf8StrArrRev(self::UTF8StringToArray($str, $isunicode, $currentfont), $str, $setbom, $forcertl, $isunicode, $currentfont);
1921
        }
1922

    
1923
        /**
1924
         * Reverse the RLT substrings array using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
1925
         * @param $arr (array) array of unicode values.
1926
         * @param $str (string) string to manipulate (or empty value).
1927
         * @param $setbom (bool) if true set the Byte Order Mark (BOM = 0xFEFF)
1928
         * @param $forcertl (bool) if true forces RTL text direction
1929
         * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
1930
         * @param $currentfont (array) Reference to current font array.
1931
         * @return string
1932
         * @author Nicola Asuni
1933
         * @since 4.9.000 (2010-03-27)
1934
         * @public static
1935
         */
1936
        public static function utf8StrArrRev($arr, $str='', $setbom=false, $forcertl=false, $isunicode=true, &$currentfont) {
1937
                return self::arrUTF8ToUTF16BE(self::utf8Bidi($arr, $str, $forcertl, $isunicode, $currentfont), $setbom);
1938
        }
1939

    
1940
        /**
1941
         * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
1942
         * @param $ta (array) array of characters composing the string.
1943
         * @param $str (string) string to process
1944
         * @param $forcertl (bool) if 'R' forces RTL, if 'L' forces LTR
1945
         * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
1946
         * @param $currentfont (array) Reference to current font array.
1947
         * @return array of unicode chars
1948
         * @author Nicola Asuni
1949
         * @since 2.4.000 (2008-03-06)
1950
         * @public static
1951
         */
1952
        public static function utf8Bidi($ta, $str='', $forcertl=false, $isunicode=true, &$currentfont) {
1953
                // paragraph embedding level
1954
                $pel = 0;
1955
                // max level
1956
                $maxlevel = 0;
1957
                if (TCPDF_STATIC::empty_string($str)) {
1958
                        // create string from array
1959
                        $str = self::UTF8ArrSubString($ta, '', '', $isunicode);
1960
                }
1961
                // check if string contains arabic text
1962
                if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $str)) {
1963
                        $arabic = true;
1964
                } else {
1965
                        $arabic = false;
1966
                }
1967
                // check if string contains RTL text
1968
                if (!($forcertl OR $arabic OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $str))) {
1969
                        return $ta;
1970
                }
1971

    
1972
                // get number of chars
1973
                $numchars = count($ta);
1974

    
1975
                if ($forcertl == 'R') {
1976
                        $pel = 1;
1977
                } elseif ($forcertl == 'L') {
1978
                        $pel = 0;
1979
                } else {
1980
                        // P2. In each paragraph, find the first character of type L, AL, or R.
1981
                        // 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.
1982
                        for ($i=0; $i < $numchars; ++$i) {
1983
                                $type = TCPDF_FONT_DATA::$uni_type[$ta[$i]];
1984
                                if ($type == 'L') {
1985
                                        $pel = 0;
1986
                                        break;
1987
                                } elseif (($type == 'AL') OR ($type == 'R')) {
1988
                                        $pel = 1;
1989
                                        break;
1990
                                }
1991
                        }
1992
                }
1993

    
1994
                // Current Embedding Level
1995
                $cel = $pel;
1996
                // directional override status
1997
                $dos = 'N';
1998
                $remember = array();
1999
                // start-of-level-run
2000
                $sor = $pel % 2 ? 'R' : 'L';
2001
                $eor = $sor;
2002

    
2003
                // Array of characters data
2004
                $chardata = Array();
2005

    
2006
                // 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.
2007
                // In the resolution of levels in rules I1 and I2, the maximum embedding level of 62 can be reached.
2008
                for ($i=0; $i < $numchars; ++$i) {
2009
                        if ($ta[$i] == TCPDF_FONT_DATA::$uni_RLE) {
2010
                                // X2. With each RLE, compute the least greater odd embedding level.
2011
                                //        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.
2012
                                //        b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2013
                                $next_level = $cel + ($cel % 2) + 1;
2014
                                if ($next_level < 62) {
2015
                                        $remember[] = array('num' => TCPDF_FONT_DATA::$uni_RLE, 'cel' => $cel, 'dos' => $dos);
2016
                                        $cel = $next_level;
2017
                                        $dos = 'N';
2018
                                        $sor = $eor;
2019
                                        $eor = $cel % 2 ? 'R' : 'L';
2020
                                }
2021
                        } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_LRE) {
2022
                                // X3. With each LRE, compute the least greater even embedding level.
2023
                                //        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.
2024
                                //        b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2025
                                $next_level = $cel + 2 - ($cel % 2);
2026
                                if ( $next_level < 62 ) {
2027
                                        $remember[] = array('num' => TCPDF_FONT_DATA::$uni_LRE, 'cel' => $cel, 'dos' => $dos);
2028
                                        $cel = $next_level;
2029
                                        $dos = 'N';
2030
                                        $sor = $eor;
2031
                                        $eor = $cel % 2 ? 'R' : 'L';
2032
                                }
2033
                        } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_RLO) {
2034
                                // X4. With each RLO, compute the least greater odd embedding level.
2035
                                //        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.
2036
                                //        b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2037
                                $next_level = $cel + ($cel % 2) + 1;
2038
                                if ($next_level < 62) {
2039
                                        $remember[] = array('num' => TCPDF_FONT_DATA::$uni_RLO, 'cel' => $cel, 'dos' => $dos);
2040
                                        $cel = $next_level;
2041
                                        $dos = 'R';
2042
                                        $sor = $eor;
2043
                                        $eor = $cel % 2 ? 'R' : 'L';
2044
                                }
2045
                        } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_LRO) {
2046
                                // X5. With each LRO, compute the least greater even embedding level.
2047
                                //        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.
2048
                                //        b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2049
                                $next_level = $cel + 2 - ($cel % 2);
2050
                                if ( $next_level < 62 ) {
2051
                                        $remember[] = array('num' => TCPDF_FONT_DATA::$uni_LRO, 'cel' => $cel, 'dos' => $dos);
2052
                                        $cel = $next_level;
2053
                                        $dos = 'L';
2054
                                        $sor = $eor;
2055
                                        $eor = $cel % 2 ? 'R' : 'L';
2056
                                }
2057
                        } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_PDF) {
2058
                                // 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.
2059
                                if (count($remember)) {
2060
                                        $last = count($remember ) - 1;
2061
                                        if (($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_RLE) OR
2062
                                                ($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_LRE) OR
2063
                                                ($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_RLO) OR
2064
                                                ($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_LRO)) {
2065
                                                $match = array_pop($remember);
2066
                                                $cel = $match['cel'];
2067
                                                $dos = $match['dos'];
2068
                                                $sor = $eor;
2069
                                                $eor = ($cel > $match['cel'] ? $cel : $match['cel']) % 2 ? 'R' : 'L';
2070
                                        }
2071
                                }
2072
                        } elseif (($ta[$i] != TCPDF_FONT_DATA::$uni_RLE) AND
2073
                                                         ($ta[$i] != TCPDF_FONT_DATA::$uni_LRE) AND
2074
                                                         ($ta[$i] != TCPDF_FONT_DATA::$uni_RLO) AND
2075
                                                         ($ta[$i] != TCPDF_FONT_DATA::$uni_LRO) AND
2076
                                                         ($ta[$i] != TCPDF_FONT_DATA::$uni_PDF)) {
2077
                                // X6. For all types besides RLE, LRE, RLO, LRO, and PDF:
2078
                                //        a. Set the level of the current character to the current embedding level.
2079
                                //        b. Whenever the directional override status is not neutral, reset the current character type to the directional override status.
2080
                                if ($dos != 'N') {
2081
                                        $chardir = $dos;
2082
                                } else {
2083
                                        if (isset(TCPDF_FONT_DATA::$uni_type[$ta[$i]])) {
2084
                                                $chardir = TCPDF_FONT_DATA::$uni_type[$ta[$i]];
2085
                                        } else {
2086
                                                $chardir = 'L';
2087
                                        }
2088
                                }
2089
                                // stores string characters and other information
2090
                                $chardata[] = array('char' => $ta[$i], 'level' => $cel, 'type' => $chardir, 'sor' => $sor, 'eor' => $eor);
2091
                        }
2092
                } // end for each char
2093

    
2094
                // X8. All explicit directional embeddings and overrides are completely terminated at the end of each paragraph. Paragraph separators are not included in the embedding.
2095
                // X9. Remove all RLE, LRE, RLO, LRO, PDF, and BN codes.
2096
                // 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.
2097

    
2098
                // 3.3.3 Resolving Weak Types
2099
                // 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.
2100
                // Nonspacing marks are now resolved based on the previous characters.
2101
                $numchars = count($chardata);
2102

    
2103
                // 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.
2104
                $prevlevel = -1; // track level changes
2105
                $levcount = 0; // counts consecutive chars at the same level
2106
                for ($i=0; $i < $numchars; ++$i) {
2107
                        if ($chardata[$i]['type'] == 'NSM') {
2108
                                if ($levcount) {
2109
                                        $chardata[$i]['type'] = $chardata[$i]['sor'];
2110
                                } elseif ($i > 0) {
2111
                                        $chardata[$i]['type'] = $chardata[($i-1)]['type'];
2112
                                }
2113
                        }
2114
                        if ($chardata[$i]['level'] != $prevlevel) {
2115
                                $levcount = 0;
2116
                        } else {
2117
                                ++$levcount;
2118
                        }
2119
                        $prevlevel = $chardata[$i]['level'];
2120
                }
2121

    
2122
                // 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.
2123
                $prevlevel = -1;
2124
                $levcount = 0;
2125
                for ($i=0; $i < $numchars; ++$i) {
2126
                        if ($chardata[$i]['char'] == 'EN') {
2127
                                for ($j=$levcount; $j >= 0; $j--) {
2128
                                        if ($chardata[$j]['type'] == 'AL') {
2129
                                                $chardata[$i]['type'] = 'AN';
2130
                                        } elseif (($chardata[$j]['type'] == 'L') OR ($chardata[$j]['type'] == 'R')) {
2131
                                                break;
2132
                                        }
2133
                                }
2134
                        }
2135
                        if ($chardata[$i]['level'] != $prevlevel) {
2136
                                $levcount = 0;
2137
                        } else {
2138
                                ++$levcount;
2139
                        }
2140
                        $prevlevel = $chardata[$i]['level'];
2141
                }
2142

    
2143
                // W3. Change all ALs to R.
2144
                for ($i=0; $i < $numchars; ++$i) {
2145
                        if ($chardata[$i]['type'] == 'AL') {
2146
                                $chardata[$i]['type'] = 'R';
2147
                        }
2148
                }
2149

    
2150
                // 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.
2151
                $prevlevel = -1;
2152
                $levcount = 0;
2153
                for ($i=0; $i < $numchars; ++$i) {
2154
                        if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
2155
                                if (($chardata[$i]['type'] == 'ES') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
2156
                                        $chardata[$i]['type'] = 'EN';
2157
                                } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
2158
                                        $chardata[$i]['type'] = 'EN';
2159
                                } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'AN') AND ($chardata[($i+1)]['type'] == 'AN')) {
2160
                                        $chardata[$i]['type'] = 'AN';
2161
                                }
2162
                        }
2163
                        if ($chardata[$i]['level'] != $prevlevel) {
2164
                                $levcount = 0;
2165
                        } else {
2166
                                ++$levcount;
2167
                        }
2168
                        $prevlevel = $chardata[$i]['level'];
2169
                }
2170

    
2171
                // W5. A sequence of European terminators adjacent to European numbers changes to all European numbers.
2172
                $prevlevel = -1;
2173
                $levcount = 0;
2174
                for ($i=0; $i < $numchars; ++$i) {
2175
                        if ($chardata[$i]['type'] == 'ET') {
2176
                                if (($levcount > 0) AND ($chardata[($i-1)]['type'] == 'EN')) {
2177
                                        $chardata[$i]['type'] = 'EN';
2178
                                } else {
2179
                                        $j = $i+1;
2180
                                        while (($j < $numchars) AND ($chardata[$j]['level'] == $prevlevel)) {
2181
                                                if ($chardata[$j]['type'] == 'EN') {
2182
                                                        $chardata[$i]['type'] = 'EN';
2183
                                                        break;
2184
                                                } elseif ($chardata[$j]['type'] != 'ET') {
2185
                                                        break;
2186
                                                }
2187
                                                ++$j;
2188
                                        }
2189
                                }
2190
                        }
2191
                        if ($chardata[$i]['level'] != $prevlevel) {
2192
                                $levcount = 0;
2193
                        } else {
2194
                                ++$levcount;
2195
                        }
2196
                        $prevlevel = $chardata[$i]['level'];
2197
                }
2198

    
2199
                // W6. Otherwise, separators and terminators change to Other Neutral.
2200
                $prevlevel = -1;
2201
                $levcount = 0;
2202
                for ($i=0; $i < $numchars; ++$i) {
2203
                        if (($chardata[$i]['type'] == 'ET') OR ($chardata[$i]['type'] == 'ES') OR ($chardata[$i]['type'] == 'CS')) {
2204
                                $chardata[$i]['type'] = 'ON';
2205
                        }
2206
                        if ($chardata[$i]['level'] != $prevlevel) {
2207
                                $levcount = 0;
2208
                        } else {
2209
                                ++$levcount;
2210
                        }
2211
                        $prevlevel = $chardata[$i]['level'];
2212
                }
2213

    
2214
                //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.
2215
                $prevlevel = -1;
2216
                $levcount = 0;
2217
                for ($i=0; $i < $numchars; ++$i) {
2218
                        if ($chardata[$i]['char'] == 'EN') {
2219
                                for ($j=$levcount; $j >= 0; $j--) {
2220
                                        if ($chardata[$j]['type'] == 'L') {
2221
                                                $chardata[$i]['type'] = 'L';
2222
                                        } elseif ($chardata[$j]['type'] == 'R') {
2223
                                                break;
2224
                                        }
2225
                                }
2226
                        }
2227
                        if ($chardata[$i]['level'] != $prevlevel) {
2228
                                $levcount = 0;
2229
                        } else {
2230
                                ++$levcount;
2231
                        }
2232
                        $prevlevel = $chardata[$i]['level'];
2233
                }
2234

    
2235
                // 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.
2236
                $prevlevel = -1;
2237
                $levcount = 0;
2238
                for ($i=0; $i < $numchars; ++$i) {
2239
                        if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
2240
                                if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
2241
                                        $chardata[$i]['type'] = 'L';
2242
                                } elseif (($chardata[$i]['type'] == 'N') AND
2243
                                 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
2244
                                 (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
2245
                                        $chardata[$i]['type'] = 'R';
2246
                                } elseif ($chardata[$i]['type'] == 'N') {
2247
                                        // N2. Any remaining neutrals take the embedding direction
2248
                                        $chardata[$i]['type'] = $chardata[$i]['sor'];
2249
                                }
2250
                        } elseif (($levcount == 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
2251
                                // first char
2252
                                if (($chardata[$i]['type'] == 'N') AND ($chardata[$i]['sor'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
2253
                                        $chardata[$i]['type'] = 'L';
2254
                                } elseif (($chardata[$i]['type'] == 'N') AND
2255
                                 (($chardata[$i]['sor'] == 'R') OR ($chardata[$i]['sor'] == 'EN') OR ($chardata[$i]['sor'] == 'AN')) AND
2256
                                 (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
2257
                                        $chardata[$i]['type'] = 'R';
2258
                                } elseif ($chardata[$i]['type'] == 'N') {
2259
                                        // N2. Any remaining neutrals take the embedding direction
2260
                                        $chardata[$i]['type'] = $chardata[$i]['sor'];
2261
                                }
2262
                        } elseif (($levcount > 0) AND ((($i+1) == $numchars) OR (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] != $prevlevel))) {
2263
                                //last char
2264
                                if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[$i]['eor'] == 'L')) {
2265
                                        $chardata[$i]['type'] = 'L';
2266
                                } elseif (($chardata[$i]['type'] == 'N') AND
2267
                                 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
2268
                                 (($chardata[$i]['eor'] == 'R') OR ($chardata[$i]['eor'] == 'EN') OR ($chardata[$i]['eor'] == 'AN'))) {
2269
                                        $chardata[$i]['type'] = 'R';
2270
                                } elseif ($chardata[$i]['type'] == 'N') {
2271
                                        // N2. Any remaining neutrals take the embedding direction
2272
                                        $chardata[$i]['type'] = $chardata[$i]['sor'];
2273
                                }
2274
                        } elseif ($chardata[$i]['type'] == 'N') {
2275
                                // N2. Any remaining neutrals take the embedding direction
2276
                                $chardata[$i]['type'] = $chardata[$i]['sor'];
2277
                        }
2278
                        if ($chardata[$i]['level'] != $prevlevel) {
2279
                                $levcount = 0;
2280
                        } else {
2281
                                ++$levcount;
2282
                        }
2283
                        $prevlevel = $chardata[$i]['level'];
2284
                }
2285

    
2286
                // 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.
2287
                // I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level.
2288
                for ($i=0; $i < $numchars; ++$i) {
2289
                        $odd = $chardata[$i]['level'] % 2;
2290
                        if ($odd) {
2291
                                if (($chardata[$i]['type'] == 'L') OR ($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
2292
                                        $chardata[$i]['level'] += 1;
2293
                                }
2294
                        } else {
2295
                                if ($chardata[$i]['type'] == 'R') {
2296
                                        $chardata[$i]['level'] += 1;
2297
                                } elseif (($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
2298
                                        $chardata[$i]['level'] += 2;
2299
                                }
2300
                        }
2301
                        $maxlevel = max($chardata[$i]['level'],$maxlevel);
2302
                }
2303

    
2304
                // L1. On each line, reset the embedding level of the following characters to the paragraph embedding level:
2305
                //        1. Segment separators,
2306
                //        2. Paragraph separators,
2307
                //        3. Any sequence of whitespace characters preceding a segment separator or paragraph separator, and
2308
                //        4. Any sequence of white space characters at the end of the line.
2309
                for ($i=0; $i < $numchars; ++$i) {
2310
                        if (($chardata[$i]['type'] == 'B') OR ($chardata[$i]['type'] == 'S')) {
2311
                                $chardata[$i]['level'] = $pel;
2312
                        } elseif ($chardata[$i]['type'] == 'WS') {
2313
                                $j = $i+1;
2314
                                while ($j < $numchars) {
2315
                                        if ((($chardata[$j]['type'] == 'B') OR ($chardata[$j]['type'] == 'S')) OR
2316
                                                (($j == ($numchars-1)) AND ($chardata[$j]['type'] == 'WS'))) {
2317
                                                $chardata[$i]['level'] = $pel;
2318
                                                break;
2319
                                        } elseif ($chardata[$j]['type'] != 'WS') {
2320
                                                break;
2321
                                        }
2322
                                        ++$j;
2323
                                }
2324
                        }
2325
                }
2326

    
2327
                // Arabic Shaping
2328
                // 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.
2329
                if ($arabic) {
2330
                        $endedletter = array(1569,1570,1571,1572,1573,1575,1577,1583,1584,1585,1586,1608,1688);
2331
                        $alfletter = array(1570,1571,1573,1575);
2332
                        $chardata2 = $chardata;
2333
                        $laaletter = false;
2334
                        $charAL = array();
2335
                        $x = 0;
2336
                        for ($i=0; $i < $numchars; ++$i) {
2337
                                if ((TCPDF_FONT_DATA::$uni_type[$chardata[$i]['char']] == 'AL') OR ($chardata[$i]['char'] == 32) OR ($chardata[$i]['char'] == 8204)) {
2338
                                        $charAL[$x] = $chardata[$i];
2339
                                        $charAL[$x]['i'] = $i;
2340
                                        $chardata[$i]['x'] = $x;
2341
                                        ++$x;
2342
                                }
2343
                        }
2344
                        $numAL = $x;
2345
                        for ($i=0; $i < $numchars; ++$i) {
2346
                                $thischar = $chardata[$i];
2347
                                if ($i > 0) {
2348
                                        $prevchar = $chardata[($i-1)];
2349
                                } else {
2350
                                        $prevchar = false;
2351
                                }
2352
                                if (($i+1) < $numchars) {
2353
                                        $nextchar = $chardata[($i+1)];
2354
                                } else {
2355
                                        $nextchar = false;
2356
                                }
2357
                                if (TCPDF_FONT_DATA::$uni_type[$thischar['char']] == 'AL') {
2358
                                        $x = $thischar['x'];
2359
                                        if ($x > 0) {
2360
                                                $prevchar = $charAL[($x-1)];
2361
                                        } else {
2362
                                                $prevchar = false;
2363
                                        }
2364
                                        if (($x+1) < $numAL) {
2365
                                                $nextchar = $charAL[($x+1)];
2366
                                        } else {
2367
                                                $nextchar = false;
2368
                                        }
2369
                                        // if laa letter
2370
                                        if (($prevchar !== false) AND ($prevchar['char'] == 1604) AND (in_array($thischar['char'], $alfletter))) {
2371
                                                $arabicarr = TCPDF_FONT_DATA::$uni_laa_array;
2372
                                                $laaletter = true;
2373
                                                if ($x > 1) {
2374
                                                        $prevchar = $charAL[($x-2)];
2375
                                                } else {
2376
                                                        $prevchar = false;
2377
                                                }
2378
                                        } else {
2379
                                                $arabicarr = TCPDF_FONT_DATA::$uni_arabicsubst;
2380
                                                $laaletter = false;
2381
                                        }
2382
                                        if (($prevchar !== false) AND ($nextchar !== false) AND
2383
                                                ((TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'NSM')) AND
2384
                                                ((TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'NSM')) AND
2385
                                                ($prevchar['type'] == $thischar['type']) AND
2386
                                                ($nextchar['type'] == $thischar['type']) AND
2387
                                                ($nextchar['char'] != 1567)) {
2388
                                                if (in_array($prevchar['char'], $endedletter)) {
2389
                                                        if (isset($arabicarr[$thischar['char']][2])) {
2390
                                                                // initial
2391
                                                                $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
2392
                                                        }
2393
                                                } else {
2394
                                                        if (isset($arabicarr[$thischar['char']][3])) {
2395
                                                                // medial
2396
                                                                $chardata2[$i]['char'] = $arabicarr[$thischar['char']][3];
2397
                                                        }
2398
                                                }
2399
                                        } elseif (($nextchar !== false) AND
2400
                                                ((TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'NSM')) AND
2401
                                                ($nextchar['type'] == $thischar['type']) AND
2402
                                                ($nextchar['char'] != 1567)) {
2403
                                                if (isset($arabicarr[$chardata[$i]['char']][2])) {
2404
                                                        // initial
2405
                                                        $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
2406
                                                }
2407
                                        } elseif ((($prevchar !== false) AND
2408
                                                ((TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'NSM')) AND
2409
                                                ($prevchar['type'] == $thischar['type'])) OR
2410
                                                (($nextchar !== false) AND ($nextchar['char'] == 1567))) {
2411
                                                // final
2412
                                                if (($i > 1) AND ($thischar['char'] == 1607) AND
2413
                                                        ($chardata[$i-1]['char'] == 1604) AND
2414
                                                        ($chardata[$i-2]['char'] == 1604)) {
2415
                                                        //Allah Word
2416
                                                        // mark characters to delete with false
2417
                                                        $chardata2[$i-2]['char'] = false;
2418
                                                        $chardata2[$i-1]['char'] = false;
2419
                                                        $chardata2[$i]['char'] = 65010;
2420
                                                } else {
2421
                                                        if (($prevchar !== false) AND in_array($prevchar['char'], $endedletter)) {
2422
                                                                if (isset($arabicarr[$thischar['char']][0])) {
2423
                                                                        // isolated
2424
                                                                        $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
2425
                                                                }
2426
                                                        } else {
2427
                                                                if (isset($arabicarr[$thischar['char']][1])) {
2428
                                                                        // final
2429
                                                                        $chardata2[$i]['char'] = $arabicarr[$thischar['char']][1];
2430
                                                                }
2431
                                                        }
2432
                                                }
2433
                                        } elseif (isset($arabicarr[$thischar['char']][0])) {
2434
                                                // isolated
2435
                                                $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
2436
                                        }
2437
                                        // if laa letter
2438
                                        if ($laaletter) {
2439
                                                // mark characters to delete with false
2440
                                                $chardata2[($charAL[($x-1)]['i'])]['char'] = false;
2441
                                        }
2442
                                } // end if AL (Arabic Letter)
2443
                        } // end for each char
2444
                        /*
2445
                         * Combining characters that can occur with Arabic Shadda (0651 HEX, 1617 DEC) are replaced.
2446
                         * Putting the combining mark and shadda in the same glyph allows us to avoid the two marks overlapping each other in an illegible manner.
2447
                         */
2448
                        for ($i = 0; $i < ($numchars-1); ++$i) {
2449
                                if (($chardata2[$i]['char'] == 1617) AND (isset(TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])]))) {
2450
                                        // check if the subtitution font is defined on current font
2451
                                        if (isset($currentfont['cw'][(TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])])])) {
2452
                                                $chardata2[$i]['char'] = false;
2453
                                                $chardata2[$i+1]['char'] = TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])];
2454
                                        }
2455
                                }
2456
                        }
2457
                        // remove marked characters
2458
                        foreach ($chardata2 as $key => $value) {
2459
                                if ($value['char'] === false) {
2460
                                        unset($chardata2[$key]);
2461
                                }
2462
                        }
2463
                        $chardata = array_values($chardata2);
2464
                        $numchars = count($chardata);
2465
                        unset($chardata2);
2466
                        unset($arabicarr);
2467
                        unset($laaletter);
2468
                        unset($charAL);
2469
                }
2470

    
2471
                // 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.
2472
                for ($j=$maxlevel; $j > 0; $j--) {
2473
                        $ordarray = Array();
2474
                        $revarr = Array();
2475
                        $onlevel = false;
2476
                        for ($i=0; $i < $numchars; ++$i) {
2477
                                if ($chardata[$i]['level'] >= $j) {
2478
                                        $onlevel = true;
2479
                                        if (isset(TCPDF_FONT_DATA::$uni_mirror[$chardata[$i]['char']])) {
2480
                                                // 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.
2481
                                                $chardata[$i]['char'] = TCPDF_FONT_DATA::$uni_mirror[$chardata[$i]['char']];
2482
                                        }
2483
                                        $revarr[] = $chardata[$i];
2484
                                } else {
2485
                                        if ($onlevel) {
2486
                                                $revarr = array_reverse($revarr);
2487
                                                $ordarray = array_merge($ordarray, $revarr);
2488
                                                $revarr = Array();
2489
                                                $onlevel = false;
2490
                                        }
2491
                                        $ordarray[] = $chardata[$i];
2492
                                }
2493
                        }
2494
                        if ($onlevel) {
2495
                                $revarr = array_reverse($revarr);
2496
                                $ordarray = array_merge($ordarray, $revarr);
2497
                        }
2498
                        $chardata = $ordarray;
2499
                }
2500
                $ordarray = array();
2501
                foreach ($chardata as $cd) {
2502
                        $ordarray[] = $cd['char'];
2503
                        // store char values for subsetting
2504
                        $currentfont['subsetchars'][$cd['char']] = true;
2505
                }
2506
                return $ordarray;
2507
        }
2508

    
2509
        /**
2510
         * Get a reference font size.
2511
         * @param $size (string) String containing font size value.
2512
         * @param $refsize (float) Reference font size in points.
2513
         * @return float value in points
2514
         * @public static
2515
         */
2516
        public static function getFontRefSize($size, $refsize=12) {
2517
                switch ($size) {
2518
                        case 'xx-small': {
2519
                                $size = ($refsize - 4);
2520
                                break;
2521
                        }
2522
                        case 'x-small': {
2523
                                $size = ($refsize - 3);
2524
                                break;
2525
                        }
2526
                        case 'small': {
2527
                                $size = ($refsize - 2);
2528
                                break;
2529
                        }
2530
                        case 'medium': {
2531
                                $size = $refsize;
2532
                                break;
2533
                        }
2534
                        case 'large': {
2535
                                $size = ($refsize + 2);
2536
                                break;
2537
                        }
2538
                        case 'x-large': {
2539
                                $size = ($refsize + 4);
2540
                                break;
2541
                        }
2542
                        case 'xx-large': {
2543
                                $size = ($refsize + 6);
2544
                                break;
2545
                        }
2546
                        case 'smaller': {
2547
                                $size = ($refsize - 3);
2548
                                break;
2549
                        }
2550
                        case 'larger': {
2551
                                $size = ($refsize + 3);
2552
                                break;
2553
                        }
2554
                }
2555
                return $size;
2556
        }
2557

    
2558
} // END OF TCPDF_FONTS CLASS
2559

    
2560
//============================================================+
2561
// END OF FILE
2562
//============================================================+