Subversion Repositories eFlore/Applications.cel

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

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