Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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