Projet

Général

Profil

Paste
Télécharger (93,4 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / libraries / tcpdf-6.2.9 / include / tcpdf_fonts.php @ a1cafe7e

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

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

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

    
50
        /**
51
         * Static cache used for speed up uniord performances
52
         * @protected
53
         */
54
        protected static $cache_uniord = array();
55

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

    
921
        /**
922
         * Returs the checksum of a TTF table.
923
         * @param $table (string) table to check
924
         * @param $length (int) length of table in bytes
925
         * @return int checksum
926
         * @author Nicola Asuni
927
         * @since 5.2.000 (2010-06-02)
928
         * @public static
929
         */
930
        public static function _getTTFtableChecksum($table, $length) {
931
                $sum = 0;
932
                $tlen = ($length / 4);
933
                $offset = 0;
934
                for ($i = 0; $i < $tlen; ++$i) {
935
                        $v = unpack('Ni', substr($table, $offset, 4));
936
                        $sum += $v['i'];
937
                        $offset += 4;
938
                }
939
                $sum = unpack('Ni', pack('N', $sum));
940
                return $sum['i'];
941
        }
942

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

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

    
1493
        /**
1494
         * Returns the unicode caracter specified by the value
1495
         * @param $c (int) UTF-8 value
1496
         * @param $unicode (boolean) True if we are in unicode mode, false otherwise.
1497
         * @return Returns the specified character.
1498
         * @since 2.3.000 (2008-03-05)
1499
         * @public static
1500
         */
1501
        public static function unichr($c, $unicode=true) {
1502
                if (!$unicode) {
1503
                        return chr($c);
1504
                } elseif ($c <= 0x7F) {
1505
                        // one byte
1506
                        return chr($c);
1507
                } elseif ($c <= 0x7FF) {
1508
                        // two bytes
1509
                        return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F);
1510
                } elseif ($c <= 0xFFFF) {
1511
                        // three bytes
1512
                        return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
1513
                } elseif ($c <= 0x10FFFF) {
1514
                        // four bytes
1515
                        return chr(0xF0 | $c >> 18).chr(0x80 | $c >> 12 & 0x3F).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
1516
                } else {
1517
                        return '';
1518
                }
1519
        }
1520

    
1521
        /**
1522
         * Returns the unicode caracter specified by UTF-8 value
1523
         * @param $c (int) UTF-8 value
1524
         * @return Returns the specified character.
1525
         * @public static
1526
         */
1527
        public static function unichrUnicode($c) {
1528
                return self::unichr($c, true);
1529
        }
1530

    
1531
        /**
1532
         * Returns the unicode caracter specified by ASCII value
1533
         * @param $c (int) UTF-8 value
1534
         * @return Returns the specified character.
1535
         * @public static
1536
         */
1537
        public static function unichrASCII($c) {
1538
                return self::unichr($c, false);
1539
        }
1540

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

    
1605
        /**
1606
         * Convert an array of UTF8 values to array of unicode characters
1607
         * @param $ta (array) The input array of UTF8 values.
1608
         * @param $isunicode (boolean) True for Unicode mode, false otherwise.
1609
         * @return Return array of unicode characters
1610
         * @since 4.5.037 (2009-04-07)
1611
         * @public static
1612
         */
1613
        public static function UTF8ArrayToUniArray($ta, $isunicode=true) {
1614
                if ($isunicode) {
1615
                        return array_map(array('TCPDF_FONTS', 'unichrUnicode'), $ta);
1616
                }
1617
                return array_map(array('TCPDF_FONTS', 'unichrASCII'), $ta);
1618
        }
1619

    
1620
        /**
1621
         * Extract a slice of the $strarr array and return it as string.
1622
         * @param $strarr (string) The input array of characters.
1623
         * @param $start (int) the starting element of $strarr.
1624
         * @param $end (int) first element that will not be returned.
1625
         * @param $unicode (boolean) True if we are in unicode mode, false otherwise.
1626
         * @return Return part of a string
1627
         * @public static
1628
         */
1629
        public static function UTF8ArrSubString($strarr, $start='', $end='', $unicode=true) {
1630
                if (strlen($start) == 0) {
1631
                        $start = 0;
1632
                }
1633
                if (strlen($end) == 0) {
1634
                        $end = count($strarr);
1635
                }
1636
                $string = '';
1637
                for ($i = $start; $i < $end; ++$i) {
1638
                        $string .= self::unichr($strarr[$i], $unicode);
1639
                }
1640
                return $string;
1641
        }
1642

    
1643
        /**
1644
         * Extract a slice of the $uniarr array and return it as string.
1645
         * @param $uniarr (string) The input array of characters.
1646
         * @param $start (int) the starting element of $strarr.
1647
         * @param $end (int) first element that will not be returned.
1648
         * @return Return part of a string
1649
         * @since 4.5.037 (2009-04-07)
1650
         * @public static
1651
         */
1652
        public static function UniArrSubString($uniarr, $start='', $end='') {
1653
                if (strlen($start) == 0) {
1654
                        $start = 0;
1655
                }
1656
                if (strlen($end) == 0) {
1657
                        $end = count($uniarr);
1658
                }
1659
                $string = '';
1660
                for ($i=$start; $i < $end; ++$i) {
1661
                        $string .= $uniarr[$i];
1662
                }
1663
                return $string;
1664
        }
1665

    
1666
        /**
1667
         * Update the CIDToGIDMap string with a new value.
1668
         * @param $map (string) CIDToGIDMap.
1669
         * @param $cid (int) CID value.
1670
         * @param $gid (int) GID value.
1671
         * @return (string) CIDToGIDMap.
1672
         * @author Nicola Asuni
1673
         * @since 5.9.123 (2011-09-29)
1674
         * @public static
1675
         */
1676
        public static function updateCIDtoGIDmap($map, $cid, $gid) {
1677
                if (($cid >= 0) AND ($cid <= 0xFFFF) AND ($gid >= 0)) {
1678
                        if ($gid > 0xFFFF) {
1679
                                $gid -= 0x10000;
1680
                        }
1681
                        $map[($cid * 2)] = chr($gid >> 8);
1682
                        $map[(($cid * 2) + 1)] = chr($gid & 0xFF);
1683
                }
1684
                return $map;
1685
        }
1686

    
1687
        /**
1688
         * Return fonts path
1689
         * @return string
1690
         * @public static
1691
         */
1692
        public static function _getfontpath() {
1693
                if (!defined('K_PATH_FONTS') AND is_dir($fdir = realpath(dirname(__FILE__).'/../fonts'))) {
1694
                        if (substr($fdir, -1) != '/') {
1695
                                $fdir .= '/';
1696
                        }
1697
                        define('K_PATH_FONTS', $fdir);
1698
                }
1699
                return defined('K_PATH_FONTS') ? K_PATH_FONTS : '';
1700
        }
1701

    
1702
        /**
1703
         * Return font full path
1704
         * @param $file (string) Font file name.
1705
         * @param $fontdir (string) Font directory (set to false fto search on default directories)
1706
         * @return string Font full path or empty string
1707
         * @author Nicola Asuni
1708
         * @since 6.0.025
1709
         * @public static
1710
         */
1711
        public static function getFontFullPath($file, $fontdir=false) {
1712
                $fontfile = '';
1713
                // search files on various directories
1714
                if (($fontdir !== false) AND @file_exists($fontdir.$file)) {
1715
                        $fontfile = $fontdir.$file;
1716
                } elseif (@file_exists(self::_getfontpath().$file)) {
1717
                        $fontfile = self::_getfontpath().$file;
1718
                } elseif (@file_exists($file)) {
1719
                        $fontfile = $file;
1720
                }
1721
                return $fontfile;
1722
        }
1723

    
1724
        /**
1725
         * Converts UTF-8 characters array to array of Latin1 characters array<br>
1726
         * @param $unicode (array) array containing UTF-8 unicode values
1727
         * @return array
1728
         * @author Nicola Asuni
1729
         * @since 4.8.023 (2010-01-15)
1730
         * @public static
1731
         */
1732
        public static function UTF8ArrToLatin1Arr($unicode) {
1733
                $outarr = array(); // array to be returned
1734
                foreach ($unicode as $char) {
1735
                        if ($char < 256) {
1736
                                $outarr[] = $char;
1737
                        } elseif (array_key_exists($char, TCPDF_FONT_DATA::$uni_utf8tolatin)) {
1738
                                // map from UTF-8
1739
                                $outarr[] = TCPDF_FONT_DATA::$uni_utf8tolatin[$char];
1740
                        } elseif ($char == 0xFFFD) {
1741
                                // skip
1742
                        } else {
1743
                                $outarr[] = 63; // '?' character
1744
                        }
1745
                }
1746
                return $outarr;
1747
        }
1748

    
1749
        /**
1750
         * Converts UTF-8 characters array to array of Latin1 string<br>
1751
         * @param $unicode (array) array containing UTF-8 unicode values
1752
         * @return array
1753
         * @author Nicola Asuni
1754
         * @since 4.8.023 (2010-01-15)
1755
         * @public static
1756
         */
1757
        public static function UTF8ArrToLatin1($unicode) {
1758
                $outstr = ''; // string to be returned
1759
                foreach ($unicode as $char) {
1760
                        if ($char < 256) {
1761
                                $outstr .= chr($char);
1762
                        } elseif (array_key_exists($char, TCPDF_FONT_DATA::$uni_utf8tolatin)) {
1763
                                // map from UTF-8
1764
                                $outstr .= chr(TCPDF_FONT_DATA::$uni_utf8tolatin[$char]);
1765
                        } elseif ($char == 0xFFFD) {
1766
                                // skip
1767
                        } else {
1768
                                $outstr .= '?';
1769
                        }
1770
                }
1771
                return $outstr;
1772
        }
1773

    
1774
        /**
1775
         * Converts UTF-8 character to integer value.<br>
1776
         * Uses the getUniord() method if the value is not cached.
1777
         * @param $uch (string) character string to process.
1778
         * @return integer Unicode value
1779
         * @public static
1780
         */
1781
        public static function uniord($uch) {
1782
                if (!isset(self::$cache_uniord[$uch])) {
1783
                        self::$cache_uniord[$uch] = self::getUniord($uch);
1784
                }
1785
                return self::$cache_uniord[$uch];
1786
        }
1787

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

    
1880
        /**
1881
         * Converts UTF-8 strings to codepoints array.<br>
1882
         * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
1883
         * @param $str (string) string to process.
1884
         * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise.
1885
         * @param $currentfont (array) Reference to current font array.
1886
         * @return array containing codepoints (UTF-8 characters values)
1887
         * @author Nicola Asuni
1888
         * @public static
1889
         */
1890
        public static function UTF8StringToArray($str, $isunicode=true, &$currentfont) {
1891
                if ($isunicode) {
1892
                        // requires PCRE unicode support turned on
1893
                        $chars = TCPDF_STATIC::pregSplit('//','u', $str, -1, PREG_SPLIT_NO_EMPTY);
1894
                        $carr = array_map(array('TCPDF_FONTS', 'uniord'), $chars);
1895
                } else {
1896
                        $chars = str_split($str);
1897
                        $carr = array_map('ord', $chars);
1898
                }
1899
                $currentfont['subsetchars'] += array_fill_keys($carr, true);
1900
                return $carr;
1901
        }
1902

    
1903
        /**
1904
         * Converts UTF-8 strings to Latin1 when using the standard 14 core fonts.<br>
1905
         * @param $str (string) string to process.
1906
         * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise.
1907
         * @param $currentfont (array) Reference to current font array.
1908
         * @return string
1909
         * @since 3.2.000 (2008-06-23)
1910
         * @public static
1911
         */
1912
        public static function UTF8ToLatin1($str, $isunicode=true, &$currentfont) {
1913
                $unicode = self::UTF8StringToArray($str, $isunicode, $currentfont); // array containing UTF-8 unicode values
1914
                return self::UTF8ArrToLatin1($unicode);
1915
        }
1916

    
1917
        /**
1918
         * Converts UTF-8 strings to UTF16-BE.<br>
1919
         * @param $str (string) string to process.
1920
         * @param $setbom (boolean) if true set the Byte Order Mark (BOM = 0xFEFF)
1921
         * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise.
1922
         * @param $currentfont (array) Reference to current font array.
1923
         * @return string
1924
         * @author Nicola Asuni
1925
         * @since 1.53.0.TC005 (2005-01-05)
1926
         * @public static
1927
         */
1928
        public static function UTF8ToUTF16BE($str, $setbom=false, $isunicode=true, &$currentfont) {
1929
                if (!$isunicode) {
1930
                        return $str; // string is not in unicode
1931
                }
1932
                $unicode = self::UTF8StringToArray($str, $isunicode, $currentfont); // array containing UTF-8 unicode values
1933
                return self::arrUTF8ToUTF16BE($unicode, $setbom);
1934
        }
1935

    
1936
        /**
1937
         * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
1938
         * @param $str (string) string to manipulate.
1939
         * @param $setbom (bool) if true set the Byte Order Mark (BOM = 0xFEFF)
1940
         * @param $forcertl (bool) if true forces RTL text direction
1941
         * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
1942
         * @param $currentfont (array) Reference to current font array.
1943
         * @return string
1944
         * @author Nicola Asuni
1945
         * @since 2.1.000 (2008-01-08)
1946
         * @public static
1947
         */
1948
        public static function utf8StrRev($str, $setbom=false, $forcertl=false, $isunicode=true, &$currentfont) {
1949
                return self::utf8StrArrRev(self::UTF8StringToArray($str, $isunicode, $currentfont), $str, $setbom, $forcertl, $isunicode, $currentfont);
1950
        }
1951

    
1952
        /**
1953
         * Reverse the RLT substrings array using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
1954
         * @param $arr (array) array of unicode values.
1955
         * @param $str (string) string to manipulate (or empty value).
1956
         * @param $setbom (bool) if true set the Byte Order Mark (BOM = 0xFEFF)
1957
         * @param $forcertl (bool) if true forces RTL text direction
1958
         * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
1959
         * @param $currentfont (array) Reference to current font array.
1960
         * @return string
1961
         * @author Nicola Asuni
1962
         * @since 4.9.000 (2010-03-27)
1963
         * @public static
1964
         */
1965
        public static function utf8StrArrRev($arr, $str='', $setbom=false, $forcertl=false, $isunicode=true, &$currentfont) {
1966
                return self::arrUTF8ToUTF16BE(self::utf8Bidi($arr, $str, $forcertl, $isunicode, $currentfont), $setbom);
1967
        }
1968

    
1969
        /**
1970
         * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
1971
         * @param $ta (array) array of characters composing the string.
1972
         * @param $str (string) string to process
1973
         * @param $forcertl (bool) if 'R' forces RTL, if 'L' forces LTR
1974
         * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
1975
         * @param $currentfont (array) Reference to current font array.
1976
         * @return array of unicode chars
1977
         * @author Nicola Asuni
1978
         * @since 2.4.000 (2008-03-06)
1979
         * @public static
1980
         */
1981
        public static function utf8Bidi($ta, $str='', $forcertl=false, $isunicode=true, &$currentfont) {
1982
                // paragraph embedding level
1983
                $pel = 0;
1984
                // max level
1985
                $maxlevel = 0;
1986
                if (TCPDF_STATIC::empty_string($str)) {
1987
                        // create string from array
1988
                        $str = self::UTF8ArrSubString($ta, '', '', $isunicode);
1989
                }
1990
                // check if string contains arabic text
1991
                if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $str)) {
1992
                        $arabic = true;
1993
                } else {
1994
                        $arabic = false;
1995
                }
1996
                // check if string contains RTL text
1997
                if (!($forcertl OR $arabic OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $str))) {
1998
                        return $ta;
1999
                }
2000

    
2001
                // get number of chars
2002
                $numchars = count($ta);
2003

    
2004
                if ($forcertl == 'R') {
2005
                        $pel = 1;
2006
                } elseif ($forcertl == 'L') {
2007
                        $pel = 0;
2008
                } else {
2009
                        // P2. In each paragraph, find the first character of type L, AL, or R.
2010
                        // 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.
2011
                        for ($i=0; $i < $numchars; ++$i) {
2012
                                $type = TCPDF_FONT_DATA::$uni_type[$ta[$i]];
2013
                                if ($type == 'L') {
2014
                                        $pel = 0;
2015
                                        break;
2016
                                } elseif (($type == 'AL') OR ($type == 'R')) {
2017
                                        $pel = 1;
2018
                                        break;
2019
                                }
2020
                        }
2021
                }
2022

    
2023
                // Current Embedding Level
2024
                $cel = $pel;
2025
                // directional override status
2026
                $dos = 'N';
2027
                $remember = array();
2028
                // start-of-level-run
2029
                $sor = $pel % 2 ? 'R' : 'L';
2030
                $eor = $sor;
2031

    
2032
                // Array of characters data
2033
                $chardata = Array();
2034

    
2035
                // 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.
2036
                // In the resolution of levels in rules I1 and I2, the maximum embedding level of 62 can be reached.
2037
                for ($i=0; $i < $numchars; ++$i) {
2038
                        if ($ta[$i] == TCPDF_FONT_DATA::$uni_RLE) {
2039
                                // X2. With each RLE, compute the least greater odd embedding level.
2040
                                //        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.
2041
                                //        b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2042
                                $next_level = $cel + ($cel % 2) + 1;
2043
                                if ($next_level < 62) {
2044
                                        $remember[] = array('num' => TCPDF_FONT_DATA::$uni_RLE, 'cel' => $cel, 'dos' => $dos);
2045
                                        $cel = $next_level;
2046
                                        $dos = 'N';
2047
                                        $sor = $eor;
2048
                                        $eor = $cel % 2 ? 'R' : 'L';
2049
                                }
2050
                        } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_LRE) {
2051
                                // X3. With each LRE, compute the least greater even embedding level.
2052
                                //        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.
2053
                                //        b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2054
                                $next_level = $cel + 2 - ($cel % 2);
2055
                                if ( $next_level < 62 ) {
2056
                                        $remember[] = array('num' => TCPDF_FONT_DATA::$uni_LRE, 'cel' => $cel, 'dos' => $dos);
2057
                                        $cel = $next_level;
2058
                                        $dos = 'N';
2059
                                        $sor = $eor;
2060
                                        $eor = $cel % 2 ? 'R' : 'L';
2061
                                }
2062
                        } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_RLO) {
2063
                                // X4. With each RLO, compute the least greater odd embedding level.
2064
                                //        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.
2065
                                //        b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2066
                                $next_level = $cel + ($cel % 2) + 1;
2067
                                if ($next_level < 62) {
2068
                                        $remember[] = array('num' => TCPDF_FONT_DATA::$uni_RLO, 'cel' => $cel, 'dos' => $dos);
2069
                                        $cel = $next_level;
2070
                                        $dos = 'R';
2071
                                        $sor = $eor;
2072
                                        $eor = $cel % 2 ? 'R' : 'L';
2073
                                }
2074
                        } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_LRO) {
2075
                                // X5. With each LRO, compute the least greater even embedding level.
2076
                                //        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.
2077
                                //        b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2078
                                $next_level = $cel + 2 - ($cel % 2);
2079
                                if ( $next_level < 62 ) {
2080
                                        $remember[] = array('num' => TCPDF_FONT_DATA::$uni_LRO, 'cel' => $cel, 'dos' => $dos);
2081
                                        $cel = $next_level;
2082
                                        $dos = 'L';
2083
                                        $sor = $eor;
2084
                                        $eor = $cel % 2 ? 'R' : 'L';
2085
                                }
2086
                        } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_PDF) {
2087
                                // 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.
2088
                                if (count($remember)) {
2089
                                        $last = count($remember ) - 1;
2090
                                        if (($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_RLE) OR
2091
                                                ($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_LRE) OR
2092
                                                ($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_RLO) OR
2093
                                                ($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_LRO)) {
2094
                                                $match = array_pop($remember);
2095
                                                $cel = $match['cel'];
2096
                                                $dos = $match['dos'];
2097
                                                $sor = $eor;
2098
                                                $eor = ($cel > $match['cel'] ? $cel : $match['cel']) % 2 ? 'R' : 'L';
2099
                                        }
2100
                                }
2101
                        } elseif (($ta[$i] != TCPDF_FONT_DATA::$uni_RLE) AND
2102
                                                         ($ta[$i] != TCPDF_FONT_DATA::$uni_LRE) AND
2103
                                                         ($ta[$i] != TCPDF_FONT_DATA::$uni_RLO) AND
2104
                                                         ($ta[$i] != TCPDF_FONT_DATA::$uni_LRO) AND
2105
                                                         ($ta[$i] != TCPDF_FONT_DATA::$uni_PDF)) {
2106
                                // X6. For all types besides RLE, LRE, RLO, LRO, and PDF:
2107
                                //        a. Set the level of the current character to the current embedding level.
2108
                                //        b. Whenever the directional override status is not neutral, reset the current character type to the directional override status.
2109
                                if ($dos != 'N') {
2110
                                        $chardir = $dos;
2111
                                } else {
2112
                                        if (isset(TCPDF_FONT_DATA::$uni_type[$ta[$i]])) {
2113
                                                $chardir = TCPDF_FONT_DATA::$uni_type[$ta[$i]];
2114
                                        } else {
2115
                                                $chardir = 'L';
2116
                                        }
2117
                                }
2118
                                // stores string characters and other information
2119
                                $chardata[] = array('char' => $ta[$i], 'level' => $cel, 'type' => $chardir, 'sor' => $sor, 'eor' => $eor);
2120
                        }
2121
                } // end for each char
2122

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

    
2127
                // 3.3.3 Resolving Weak Types
2128
                // 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.
2129
                // Nonspacing marks are now resolved based on the previous characters.
2130
                $numchars = count($chardata);
2131

    
2132
                // 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.
2133
                $prevlevel = -1; // track level changes
2134
                $levcount = 0; // counts consecutive chars at the same level
2135
                for ($i=0; $i < $numchars; ++$i) {
2136
                        if ($chardata[$i]['type'] == 'NSM') {
2137
                                if ($levcount) {
2138
                                        $chardata[$i]['type'] = $chardata[$i]['sor'];
2139
                                } elseif ($i > 0) {
2140
                                        $chardata[$i]['type'] = $chardata[($i-1)]['type'];
2141
                                }
2142
                        }
2143
                        if ($chardata[$i]['level'] != $prevlevel) {
2144
                                $levcount = 0;
2145
                        } else {
2146
                                ++$levcount;
2147
                        }
2148
                        $prevlevel = $chardata[$i]['level'];
2149
                }
2150

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

    
2172
                // W3. Change all ALs to R.
2173
                for ($i=0; $i < $numchars; ++$i) {
2174
                        if ($chardata[$i]['type'] == 'AL') {
2175
                                $chardata[$i]['type'] = 'R';
2176
                        }
2177
                }
2178

    
2179
                // 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.
2180
                $prevlevel = -1;
2181
                $levcount = 0;
2182
                for ($i=0; $i < $numchars; ++$i) {
2183
                        if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
2184
                                if (($chardata[$i]['type'] == 'ES') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
2185
                                        $chardata[$i]['type'] = 'EN';
2186
                                } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
2187
                                        $chardata[$i]['type'] = 'EN';
2188
                                } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'AN') AND ($chardata[($i+1)]['type'] == 'AN')) {
2189
                                        $chardata[$i]['type'] = 'AN';
2190
                                }
2191
                        }
2192
                        if ($chardata[$i]['level'] != $prevlevel) {
2193
                                $levcount = 0;
2194
                        } else {
2195
                                ++$levcount;
2196
                        }
2197
                        $prevlevel = $chardata[$i]['level'];
2198
                }
2199

    
2200
                // W5. A sequence of European terminators adjacent to European numbers changes to all European numbers.
2201
                $prevlevel = -1;
2202
                $levcount = 0;
2203
                for ($i=0; $i < $numchars; ++$i) {
2204
                        if ($chardata[$i]['type'] == 'ET') {
2205
                                if (($levcount > 0) AND ($chardata[($i-1)]['type'] == 'EN')) {
2206
                                        $chardata[$i]['type'] = 'EN';
2207
                                } else {
2208
                                        $j = $i+1;
2209
                                        while (($j < $numchars) AND ($chardata[$j]['level'] == $prevlevel)) {
2210
                                                if ($chardata[$j]['type'] == 'EN') {
2211
                                                        $chardata[$i]['type'] = 'EN';
2212
                                                        break;
2213
                                                } elseif ($chardata[$j]['type'] != 'ET') {
2214
                                                        break;
2215
                                                }
2216
                                                ++$j;
2217
                                        }
2218
                                }
2219
                        }
2220
                        if ($chardata[$i]['level'] != $prevlevel) {
2221
                                $levcount = 0;
2222
                        } else {
2223
                                ++$levcount;
2224
                        }
2225
                        $prevlevel = $chardata[$i]['level'];
2226
                }
2227

    
2228
                // W6. Otherwise, separators and terminators change to Other Neutral.
2229
                $prevlevel = -1;
2230
                $levcount = 0;
2231
                for ($i=0; $i < $numchars; ++$i) {
2232
                        if (($chardata[$i]['type'] == 'ET') OR ($chardata[$i]['type'] == 'ES') OR ($chardata[$i]['type'] == 'CS')) {
2233
                                $chardata[$i]['type'] = 'ON';
2234
                        }
2235
                        if ($chardata[$i]['level'] != $prevlevel) {
2236
                                $levcount = 0;
2237
                        } else {
2238
                                ++$levcount;
2239
                        }
2240
                        $prevlevel = $chardata[$i]['level'];
2241
                }
2242

    
2243
                //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.
2244
                $prevlevel = -1;
2245
                $levcount = 0;
2246
                for ($i=0; $i < $numchars; ++$i) {
2247
                        if ($chardata[$i]['char'] == 'EN') {
2248
                                for ($j=$levcount; $j >= 0; $j--) {
2249
                                        if ($chardata[$j]['type'] == 'L') {
2250
                                                $chardata[$i]['type'] = 'L';
2251
                                        } elseif ($chardata[$j]['type'] == 'R') {
2252
                                                break;
2253
                                        }
2254
                                }
2255
                        }
2256
                        if ($chardata[$i]['level'] != $prevlevel) {
2257
                                $levcount = 0;
2258
                        } else {
2259
                                ++$levcount;
2260
                        }
2261
                        $prevlevel = $chardata[$i]['level'];
2262
                }
2263

    
2264
                // 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.
2265
                $prevlevel = -1;
2266
                $levcount = 0;
2267
                for ($i=0; $i < $numchars; ++$i) {
2268
                        if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
2269
                                if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
2270
                                        $chardata[$i]['type'] = 'L';
2271
                                } elseif (($chardata[$i]['type'] == 'N') AND
2272
                                 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
2273
                                 (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
2274
                                        $chardata[$i]['type'] = 'R';
2275
                                } elseif ($chardata[$i]['type'] == 'N') {
2276
                                        // N2. Any remaining neutrals take the embedding direction
2277
                                        $chardata[$i]['type'] = $chardata[$i]['sor'];
2278
                                }
2279
                        } elseif (($levcount == 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
2280
                                // first char
2281
                                if (($chardata[$i]['type'] == 'N') AND ($chardata[$i]['sor'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
2282
                                        $chardata[$i]['type'] = 'L';
2283
                                } elseif (($chardata[$i]['type'] == 'N') AND
2284
                                 (($chardata[$i]['sor'] == 'R') OR ($chardata[$i]['sor'] == 'EN') OR ($chardata[$i]['sor'] == 'AN')) AND
2285
                                 (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
2286
                                        $chardata[$i]['type'] = 'R';
2287
                                } elseif ($chardata[$i]['type'] == 'N') {
2288
                                        // N2. Any remaining neutrals take the embedding direction
2289
                                        $chardata[$i]['type'] = $chardata[$i]['sor'];
2290
                                }
2291
                        } elseif (($levcount > 0) AND ((($i+1) == $numchars) OR (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] != $prevlevel))) {
2292
                                //last char
2293
                                if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[$i]['eor'] == 'L')) {
2294
                                        $chardata[$i]['type'] = 'L';
2295
                                } elseif (($chardata[$i]['type'] == 'N') AND
2296
                                 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
2297
                                 (($chardata[$i]['eor'] == 'R') OR ($chardata[$i]['eor'] == 'EN') OR ($chardata[$i]['eor'] == 'AN'))) {
2298
                                        $chardata[$i]['type'] = 'R';
2299
                                } elseif ($chardata[$i]['type'] == 'N') {
2300
                                        // N2. Any remaining neutrals take the embedding direction
2301
                                        $chardata[$i]['type'] = $chardata[$i]['sor'];
2302
                                }
2303
                        } elseif ($chardata[$i]['type'] == 'N') {
2304
                                // N2. Any remaining neutrals take the embedding direction
2305
                                $chardata[$i]['type'] = $chardata[$i]['sor'];
2306
                        }
2307
                        if ($chardata[$i]['level'] != $prevlevel) {
2308
                                $levcount = 0;
2309
                        } else {
2310
                                ++$levcount;
2311
                        }
2312
                        $prevlevel = $chardata[$i]['level'];
2313
                }
2314

    
2315
                // 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.
2316
                // I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level.
2317
                for ($i=0; $i < $numchars; ++$i) {
2318
                        $odd = $chardata[$i]['level'] % 2;
2319
                        if ($odd) {
2320
                                if (($chardata[$i]['type'] == 'L') OR ($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
2321
                                        $chardata[$i]['level'] += 1;
2322
                                }
2323
                        } else {
2324
                                if ($chardata[$i]['type'] == 'R') {
2325
                                        $chardata[$i]['level'] += 1;
2326
                                } elseif (($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
2327
                                        $chardata[$i]['level'] += 2;
2328
                                }
2329
                        }
2330
                        $maxlevel = max($chardata[$i]['level'],$maxlevel);
2331
                }
2332

    
2333
                // L1. On each line, reset the embedding level of the following characters to the paragraph embedding level:
2334
                //        1. Segment separators,
2335
                //        2. Paragraph separators,
2336
                //        3. Any sequence of whitespace characters preceding a segment separator or paragraph separator, and
2337
                //        4. Any sequence of white space characters at the end of the line.
2338
                for ($i=0; $i < $numchars; ++$i) {
2339
                        if (($chardata[$i]['type'] == 'B') OR ($chardata[$i]['type'] == 'S')) {
2340
                                $chardata[$i]['level'] = $pel;
2341
                        } elseif ($chardata[$i]['type'] == 'WS') {
2342
                                $j = $i+1;
2343
                                while ($j < $numchars) {
2344
                                        if ((($chardata[$j]['type'] == 'B') OR ($chardata[$j]['type'] == 'S')) OR
2345
                                                (($j == ($numchars-1)) AND ($chardata[$j]['type'] == 'WS'))) {
2346
                                                $chardata[$i]['level'] = $pel;
2347
                                                break;
2348
                                        } elseif ($chardata[$j]['type'] != 'WS') {
2349
                                                break;
2350
                                        }
2351
                                        ++$j;
2352
                                }
2353
                        }
2354
                }
2355

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

    
2500
                // 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.
2501
                for ($j=$maxlevel; $j > 0; $j--) {
2502
                        $ordarray = Array();
2503
                        $revarr = Array();
2504
                        $onlevel = false;
2505
                        for ($i=0; $i < $numchars; ++$i) {
2506
                                if ($chardata[$i]['level'] >= $j) {
2507
                                        $onlevel = true;
2508
                                        if (isset(TCPDF_FONT_DATA::$uni_mirror[$chardata[$i]['char']])) {
2509
                                                // 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.
2510
                                                $chardata[$i]['char'] = TCPDF_FONT_DATA::$uni_mirror[$chardata[$i]['char']];
2511
                                        }
2512
                                        $revarr[] = $chardata[$i];
2513
                                } else {
2514
                                        if ($onlevel) {
2515
                                                $revarr = array_reverse($revarr);
2516
                                                $ordarray = array_merge($ordarray, $revarr);
2517
                                                $revarr = Array();
2518
                                                $onlevel = false;
2519
                                        }
2520
                                        $ordarray[] = $chardata[$i];
2521
                                }
2522
                        }
2523
                        if ($onlevel) {
2524
                                $revarr = array_reverse($revarr);
2525
                                $ordarray = array_merge($ordarray, $revarr);
2526
                        }
2527
                        $chardata = $ordarray;
2528
                }
2529
                $ordarray = array();
2530
                foreach ($chardata as $cd) {
2531
                        $ordarray[] = $cd['char'];
2532
                        // store char values for subsetting
2533
                        $currentfont['subsetchars'][$cd['char']] = true;
2534
                }
2535
                return $ordarray;
2536
        }
2537

    
2538
        /**
2539
         * Get a reference font size.
2540
         * @param $size (string) String containing font size value.
2541
         * @param $refsize (float) Reference font size in points.
2542
         * @return float value in points
2543
         * @public static
2544
         */
2545
        public static function getFontRefSize($size, $refsize=12) {
2546
                switch ($size) {
2547
                        case 'xx-small': {
2548
                                $size = ($refsize - 4);
2549
                                break;
2550
                        }
2551
                        case 'x-small': {
2552
                                $size = ($refsize - 3);
2553
                                break;
2554
                        }
2555
                        case 'small': {
2556
                                $size = ($refsize - 2);
2557
                                break;
2558
                        }
2559
                        case 'medium': {
2560
                                $size = $refsize;
2561
                                break;
2562
                        }
2563
                        case 'large': {
2564
                                $size = ($refsize + 2);
2565
                                break;
2566
                        }
2567
                        case 'x-large': {
2568
                                $size = ($refsize + 4);
2569
                                break;
2570
                        }
2571
                        case 'xx-large': {
2572
                                $size = ($refsize + 6);
2573
                                break;
2574
                        }
2575
                        case 'smaller': {
2576
                                $size = ($refsize - 3);
2577
                                break;
2578
                        }
2579
                        case 'larger': {
2580
                                $size = ($refsize + 3);
2581
                                break;
2582
                        }
2583
                }
2584
                return $size;
2585
        }
2586

    
2587
} // END OF TCPDF_FONTS CLASS
2588

    
2589
//============================================================+
2590
// END OF FILE
2591
//============================================================+