GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — 2.8 ( 651d2c...3866ce )
by Thorsten
18:13
created

TCPDF_FONTS::utf8Bidi()   F

Complexity

Conditions 215
Paths > 20000

Size

Total Lines 556
Code Lines 363

Duplication

Lines 98
Ratio 17.63 %

Importance

Changes 0
Metric Value
cc 215
eloc 363
nc 4294967295
nop 5
dl 98
loc 556
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
//============================================================+
3
// File name   : tcpdf_fonts.php
4
// Version     : 1.0.012
5
// Begin       : 2008-01-01
6
// Last Update : 2014-05-12
7
// Author      : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - [email protected]
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.0.012
46
 * @author Nicola Asuni - [email protected]
47
 */
48
class TCPDF_FONTS {
49
50
	/**
51
	 * Static cache used for speed up uniord performances
52
	 * @protected
53
	 */
54
	protected static $cache_uniord = array();
55
56
	/**
57
	 * Convert and add the selected TrueType or Type1 font to the fonts folder (that must be writeable).
58
	 * @param $fontfile (string) Font file (full path).
59
	 * @param $fonttype (string) Font type. Leave empty for autodetect mode. Valid values are: TrueTypeUnicode, TrueType, Type1, CID0JP = CID-0 Japanese, CID0KR = CID-0 Korean, CID0CS = CID-0 Chinese Simplified, CID0CT = CID-0 Chinese Traditional.
60
	 * @param $enc (string) Name of the encoding table to use. Leave empty for default mode. Omit this parameter for TrueType Unicode and symbolic fonts like Symbol or ZapfDingBats.
61
	 * @param $flags (int) Unsigned 32-bit integer containing flags specifying various characteristics of the font (PDF32000:2008 - 9.8.2 Font Descriptor Flags): +1 for fixed font; +4 for symbol or +32 for non-symbol; +64 for italic. Fixed and Italic mode are generally autodetected so you have to set it to 32 = non-symbolic font (default) or 4 = symbolic font.
62
	 * @param $outpath (string) Output path for generated font files (must be writeable by the web server). Leave empty for default font folder.
63
	 * @param $platid (int) Platform ID for CMAP table to extract (when building a Unicode font for Windows this value should be 3, for Macintosh should be 1).
64
	 * @param $encid (int) Encoding ID for CMAP table to extract (when building a Unicode font for Windows this value should be 1, for Macintosh should be 0). When Platform ID is 3, legal values for Encoding ID are: 0=Symbol, 1=Unicode, 2=ShiftJIS, 3=PRC, 4=Big5, 5=Wansung, 6=Johab, 7=Reserved, 8=Reserved, 9=Reserved, 10=UCS-4.
65
	 * @param $addcbbox (boolean) If true includes the character bounding box information on the php font file.
66
	 * @param $link (boolean) If true link to system font instead of copying the font data (not transportable) - Note: do not work with Type1 fonts.
67
	 * @return (string) TCPDF font name or boolean false in case of error.
68
	 * @author Nicola Asuni
69
	 * @since 5.9.123 (2010-09-30)
70
	 * @public static
71
	 */
72
	public static function addTTFfont($fontfile, $fonttype='', $enc='', $flags=32, $outpath='', $platid=3, $encid=1, $addcbbox=false, $link=false) {
73
		if (!file_exists($fontfile)) {
74
			// Could not find file
75
			return false;
76
		}
77
		// font metrics
78
		$fmetric = array();
79
		// build new font name for TCPDF compatibility
80
		$font_path_parts = pathinfo($fontfile);
81
		if (!isset($font_path_parts['filename'])) {
82
			$font_path_parts['filename'] = substr($font_path_parts['basename'], 0, -(strlen($font_path_parts['extension']) + 1));
83
		}
84
		$font_name = strtolower($font_path_parts['filename']);
85
		$font_name = preg_replace('/[^a-z0-9_]/', '', $font_name);
86
		$search  = array('bold', 'oblique', 'italic', 'regular');
87
		$replace = array('b', 'i', 'i', '');
88
		$font_name = str_replace($search, $replace, $font_name);
89
		if (empty($font_name)) {
90
			// set generic name
91
			$font_name = 'tcpdffont';
92
		}
93
		// set output path
94
		if (empty($outpath)) {
95
			$outpath = self::_getfontpath();
96
		}
97
		// check if this font already exist
98
		if (@file_exists($outpath.$font_name.'.php')) {
99
			// this font already exist (delete it from fonts folder to rebuild it)
100
			return $font_name;
101
		}
102
		$fmetric['file'] = $font_name;
103
		$fmetric['ctg'] = $font_name.'.ctg.z';
104
		// get font data
105
		$font = file_get_contents($fontfile);
106
		$fmetric['originalsize'] = strlen($font);
107
		// autodetect font type
108
		if (empty($fonttype)) {
109
			if (TCPDF_STATIC::_getULONG($font, 0) == 0x10000) {
110
				// True Type (Unicode or not)
111
				$fonttype = 'TrueTypeUnicode';
112
			} elseif (substr($font, 0, 4) == 'OTTO') {
113
				// Open Type (Unicode or not)
114
				//Unsupported font format: OpenType with CFF data
115
				return false;
116
			} else {
117
				// Type 1
118
				$fonttype = 'Type1';
119
			}
120
		}
121
		// set font type
122
		switch ($fonttype) {
123
			case 'CID0CT':
124
			case 'CID0CS':
125
			case 'CID0KR':
126
			case 'CID0JP': {
127
				$fmetric['type'] = 'cidfont0';
128
				break;
129
			}
130
			case 'Type1': {
131
				$fmetric['type'] = 'Type1';
132
				if (empty($enc) AND (($flags & 4) == 0)) {
133
					$enc = 'cp1252';
134
				}
135
				break;
136
			}
137
			case 'TrueType': {
138
				$fmetric['type'] = 'TrueType';
139
				break;
140
			}
141
			case 'TrueTypeUnicode':
142
			default: {
143
				$fmetric['type'] = 'TrueTypeUnicode';
144
				break;
145
			}
146
		}
147
		// set encoding maps (if any)
148
		$fmetric['enc'] = preg_replace('/[^A-Za-z0-9_\-]/', '', $enc);
149
		$fmetric['diff'] = '';
150
		if (($fmetric['type'] == 'TrueType') OR ($fmetric['type'] == 'Type1')) {
151
			if (!empty($enc) AND ($enc != 'cp1252') AND isset(TCPDF_FONT_DATA::$encmap[$enc])) {
152
				// build differences from reference encoding
153
				$enc_ref = TCPDF_FONT_DATA::$encmap['cp1252'];
154
				$enc_target = TCPDF_FONT_DATA::$encmap[$enc];
155
				$last = 0;
156
				for ($i = 32; $i <= 255; ++$i) {
157
					if ($enc_target != $enc_ref[$i]) {
158
						if ($i != ($last + 1)) {
159
							$fmetric['diff'] .= $i.' ';
160
						}
161
						$last = $i;
162
						$fmetric['diff'] .= '/'.$enc_target[$i].' ';
163
					}
164
				}
165
			}
166
		}
167
		// parse the font by type
168
		if ($fmetric['type'] == 'Type1') {
169
			// ---------- TYPE 1 ----------
170
			// read first segment
171
			$a = unpack('Cmarker/Ctype/Vsize', substr($font, 0, 6));
172
			if ($a['marker'] != 128) {
173
				// Font file is not a valid binary Type1
174
				return false;
175
			}
176
			$fmetric['size1'] = $a['size'];
177
			$data = substr($font, 6, $fmetric['size1']);
178
			// read second segment
179
			$a = unpack('Cmarker/Ctype/Vsize', substr($font, (6 + $fmetric['size1']), 6));
180
			if ($a['marker'] != 128) {
181
				// Font file is not a valid binary Type1
182
				return false;
183
			}
184
			$fmetric['size2'] = $a['size'];
185
			$encrypted = substr($font, (12 + $fmetric['size1']), $fmetric['size2']);
186
			$data .= $encrypted;
187
			// store compressed font
188
			$fmetric['file'] .= '.z';
189
			$fp = fopen($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 View Code Duplication
			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 View Code Duplication
			if (preg_match('#/StdVW[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
238
				$fmetric['StemV'] = intval($matches[1]);
239
			} else {
240
				$fmetric['StemV'] = 70;
241
			}
242 View Code Duplication
			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 View Code Duplication
				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
			if ($fmetric['type'] != 'cidfont0') {
358
				if ($link) {
359
					// creates a symbolic link to the existing font
360
					symlink($fontfile, $outpath.$fmetric['file']);
361
				} else {
362
					// store compressed font
363
					$fmetric['file'] .= '.z';
364
					$fp = fopen($outpath.$fmetric['file'], 'wb');
365
					fwrite($fp, gzcompress($font));
366
					fclose($fp);
367
				}
368
			}
369
			$offset = 0; // offset position of the font data
370
			if (TCPDF_STATIC::_getULONG($font, $offset) != 0x10000) {
371
				// sfnt version must be 0x00010000 for TrueType version 1.0.
372
				return false;
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 View Code Duplication
			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 View Code Duplication
			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
					$offset += 2;
439
				}
440
			} else {
441
				// long version
442
				$tot_num_glyphs = floor($table['loca']['length'] / 4); // numGlyphs + 1
443
				for ($i = 0; $i < $tot_num_glyphs; ++$i) {
444
					$indexToLoc[$i] = TCPDF_STATIC::_getULONG($font, $offset);
445
					$offset += 4;
446
				}
447
			}
448
			// get glyphs indexes of chars from cmap table
449
			$offset = $table['cmap']['offset'] + 2;
450
			$numEncodingTables = TCPDF_STATIC::_getUSHORT($font, $offset);
451
			$offset += 2;
452
			$encodingTables = array();
453 View Code Duplication
			for ($i = 0; $i < $numEncodingTables; ++$i) {
454
				$encodingTables[$i]['platformID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
455
				$offset += 2;
456
				$encodingTables[$i]['encodingID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
457
				$offset += 2;
458
				$encodingTables[$i]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
459
				$offset += 4;
460
			}
461
			// ---------- get os/2 metrics ----------
462
			$offset = $table['OS/2']['offset'];
463
			$offset += 2; // skip version
464
			// xAvgCharWidth
465
			$fmetric['AvgWidth'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
466
			$offset += 2;
467
			// usWeightClass
468
			$usWeightClass = round(TCPDF_STATIC::_getUFWORD($font, $offset) * $urk);
469
			// estimate StemV and StemH (400 = usWeightClass for Normal - Regular font)
470
			$fmetric['StemV'] = round((70 * $usWeightClass) / 400);
471
			$fmetric['StemH'] = round((30 * $usWeightClass) / 400);
472
			$offset += 2;
473
			$offset += 2; // usWidthClass
474
			$fsType = TCPDF_STATIC::_getSHORT($font, $offset);
475
			$offset += 2;
476
			if ($fsType == 2) {
477
				// This Font cannot be modified, embedded or exchanged in any manner without first obtaining permission of the legal owner.
478
				return false;
479
			}
480
			// ---------- get font name ----------
481
			$fmetric['name'] = '';
482
			$offset = $table['name']['offset'];
483
			$offset += 2; // skip Format selector (=0).
484
			// Number of NameRecords that follow n.
485
			$numNameRecords = TCPDF_STATIC::_getUSHORT($font, $offset);
486
			$offset += 2;
487
			// Offset to start of string storage (from start of table).
488
			$stringStorageOffset = TCPDF_STATIC::_getUSHORT($font, $offset);
489
			$offset += 2;
490
			for ($i = 0; $i < $numNameRecords; ++$i) {
491
				$offset += 6; // skip Platform ID, Platform-specific encoding ID, Language ID.
492
				// Name ID.
493
				$nameID = TCPDF_STATIC::_getUSHORT($font, $offset);
494
				$offset += 2;
495
				if ($nameID == 6) {
496
					// String length (in bytes).
497
					$stringLength = TCPDF_STATIC::_getUSHORT($font, $offset);
498
					$offset += 2;
499
					// String offset from start of storage area (in bytes).
500
					$stringOffset = TCPDF_STATIC::_getUSHORT($font, $offset);
501
					$offset += 2;
502
					$offset = ($table['name']['offset'] + $stringStorageOffset + $stringOffset);
503
					$fmetric['name'] = substr($font, $offset, $stringLength);
504
					$fmetric['name'] = preg_replace('/[^a-zA-Z0-9_\-]/', '', $fmetric['name']);
505
					break;
506
				} else {
507
					$offset += 4; // skip String length, String offset
508
				}
509
			}
510
			if (empty($fmetric['name'])) {
511
				$fmetric['name'] = $font_name;
512
			}
513
			// ---------- get post data ----------
514
			$offset = $table['post']['offset'];
515
			$offset += 4; // skip Format Type
516
			$fmetric['italicAngle'] = TCPDF_STATIC::_getFIXED($font, $offset);
517
			$offset += 4;
518
			$fmetric['underlinePosition'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
519
			$offset += 2;
520
			$fmetric['underlineThickness'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
521
			$offset += 2;
522
			$isFixedPitch = (TCPDF_STATIC::_getULONG($font, $offset) == 0) ? false : true;
523
			$offset += 2;
524
			if ($isFixedPitch) {
525
				$fmetric['Flags'] |= 1;
526
			}
527
			// ---------- get hhea data ----------
528
			$offset = $table['hhea']['offset'];
529
			$offset += 4; // skip Table version number
530
			// Ascender
531
			$fmetric['Ascent'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
532
			$offset += 2;
533
			// Descender
534
			$fmetric['Descent'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
535
			$offset += 2;
536
			// LineGap
537
			$fmetric['Leading'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
538
			$offset += 2;
539
			// advanceWidthMax
540
			$fmetric['MaxWidth'] = round(TCPDF_STATIC::_getUFWORD($font, $offset) * $urk);
541
			$offset += 2;
542
			$offset += 22; // skip some values
543
			// get the number of hMetric entries in hmtx table
544
			$numberOfHMetrics = TCPDF_STATIC::_getUSHORT($font, $offset);
545
			// ---------- get maxp data ----------
546
			$offset = $table['maxp']['offset'];
547
			$offset += 4; // skip Table version number
548
			// get the the number of glyphs in the font.
549
			$numGlyphs = TCPDF_STATIC::_getUSHORT($font, $offset);
550
			// ---------- get CIDToGIDMap ----------
551
			$ctg = array();
552
			foreach ($encodingTables as $enctable) {
553
				// get only specified Platform ID and Encoding ID
554
				if (($enctable['platformID'] == $platid) AND ($enctable['encodingID'] == $encid)) {
555
					$offset = $table['cmap']['offset'] + $enctable['offset'];
556
					$format = TCPDF_STATIC::_getUSHORT($font, $offset);
557
					$offset += 2;
558
					switch ($format) {
559 View Code Duplication
						case 0: { // Format 0: Byte encoding table
560
							$offset += 4; // skip length and version/language
561
							for ($c = 0; $c < 256; ++$c) {
562
								$g = TCPDF_STATIC::_getBYTE($font, $offset);
563
								$ctg[$c] = $g;
564
								++$offset;
565
							}
566
							break;
567
						}
568
						case 2: { // Format 2: High-byte mapping through table
569
							$offset += 4; // skip length and version/language
570
							$numSubHeaders = 0;
571 View Code Duplication
							for ($i = 0; $i < 256; ++$i) {
572
								// Array that maps high bytes to subHeaders: value is subHeader index * 8.
573
								$subHeaderKeys[$i] = (TCPDF_STATIC::_getUSHORT($font, $offset) / 8);
574
								$offset += 2;
575
								if ($numSubHeaders < $subHeaderKeys[$i]) {
576
									$numSubHeaders = $subHeaderKeys[$i];
577
								}
578
							}
579
							// the number of subHeaders is equal to the max of subHeaderKeys + 1
580
							++$numSubHeaders;
581
							// read subHeader structures
582
							$subHeaders = array();
583
							$numGlyphIndexArray = 0;
584 View Code Duplication
							for ($k = 0; $k < $numSubHeaders; ++$k) {
585
								$subHeaders[$k]['firstCode'] = TCPDF_STATIC::_getUSHORT($font, $offset);
586
								$offset += 2;
587
								$subHeaders[$k]['entryCount'] = TCPDF_STATIC::_getUSHORT($font, $offset);
588
								$offset += 2;
589
								$subHeaders[$k]['idDelta'] = TCPDF_STATIC::_getUSHORT($font, $offset);
590
								$offset += 2;
591
								$subHeaders[$k]['idRangeOffset'] = TCPDF_STATIC::_getUSHORT($font, $offset);
592
								$offset += 2;
593
								$subHeaders[$k]['idRangeOffset'] -= (2 + (($numSubHeaders - $k - 1) * 8));
594
								$subHeaders[$k]['idRangeOffset'] /= 2;
595
								$numGlyphIndexArray += $subHeaders[$k]['entryCount'];
596
							}
597 View Code Duplication
							for ($k = 0; $k < $numGlyphIndexArray; ++$k) {
598
								$glyphIndexArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
599
								$offset += 2;
600
							}
601
							for ($i = 0; $i < 256; ++$i) {
602
								$k = $subHeaderKeys[$i];
603
								if ($k == 0) {
604
									// one byte code
605
									$c = $i;
606
									$g = $glyphIndexArray[0];
607
									$ctg[$c] = $g;
608 View Code Duplication
								} else {
609
									// two bytes code
610
									$start_byte = $subHeaders[$k]['firstCode'];
611
									$end_byte = $start_byte + $subHeaders[$k]['entryCount'];
612
									for ($j = $start_byte; $j < $end_byte; ++$j) {
613
										// combine high and low bytes
614
										$c = (($i << 8) + $j);
615
										$idRangeOffset = ($subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode']);
616
										$g = ($glyphIndexArray[$idRangeOffset] + $subHeaders[$k]['idDelta']) % 65536;
617
										if ($g < 0) {
618
											$g = 0;
619
										}
620
										$ctg[$c] = $g;
621
									}
622
								}
623
							}
624
							break;
625
						}
626 View Code Duplication
						case 4: { // Format 4: Segment mapping to delta values
627
							$length = TCPDF_STATIC::_getUSHORT($font, $offset);
628
							$offset += 2;
629
							$offset += 2; // skip version/language
630
							$segCount = floor(TCPDF_STATIC::_getUSHORT($font, $offset) / 2);
631
							$offset += 2;
632
							$offset += 6; // skip searchRange, entrySelector, rangeShift
633
							$endCount = array(); // array of end character codes for each segment
634
							for ($k = 0; $k < $segCount; ++$k) {
635
								$endCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
636
								$offset += 2;
637
							}
638
							$offset += 2; // skip reservedPad
639
							$startCount = array(); // array of start character codes for each segment
640
							for ($k = 0; $k < $segCount; ++$k) {
641
								$startCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
642
								$offset += 2;
643
							}
644
							$idDelta = array(); // delta for all character codes in segment
645
							for ($k = 0; $k < $segCount; ++$k) {
646
								$idDelta[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
647
								$offset += 2;
648
							}
649
							$idRangeOffset = array(); // Offsets into glyphIdArray or 0
650
							for ($k = 0; $k < $segCount; ++$k) {
651
								$idRangeOffset[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
652
								$offset += 2;
653
							}
654
							$gidlen = (floor($length / 2) - 8 - (4 * $segCount));
655
							$glyphIdArray = array(); // glyph index array
656
							for ($k = 0; $k < $gidlen; ++$k) {
657
								$glyphIdArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
658
								$offset += 2;
659
							}
660
							for ($k = 0; $k < $segCount; ++$k) {
661
								for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
662
									if ($idRangeOffset[$k] == 0) {
663
										$g = ($idDelta[$k] + $c) % 65536;
664
									} else {
665
										$gid = (floor($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k));
666
										$g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536;
667
									}
668
									if ($g < 0) {
669
										$g = 0;
670
									}
671
									$ctg[$c] = $g;
672
								}
673
							}
674
							break;
675
						}
676 View Code Duplication
						case 6: { // Format 6: Trimmed table mapping
677
							$offset += 4; // skip length and version/language
678
							$firstCode = TCPDF_STATIC::_getUSHORT($font, $offset);
679
							$offset += 2;
680
							$entryCount = TCPDF_STATIC::_getUSHORT($font, $offset);
681
							$offset += 2;
682
							for ($k = 0; $k < $entryCount; ++$k) {
683
								$c = ($k + $firstCode);
684
								$g = TCPDF_STATIC::_getUSHORT($font, $offset);
685
								$offset += 2;
686
								$ctg[$c] = $g;
687
							}
688
							break;
689
						}
690
						case 8: { // Format 8: Mixed 16-bit and 32-bit coverage
691
							$offset += 10; // skip reserved, length and version/language
692 View Code Duplication
							for ($k = 0; $k < 8192; ++$k) {
693
								$is32[$k] = TCPDF_STATIC::_getBYTE($font, $offset);
694
								++$offset;
695
							}
696
							$nGroups = TCPDF_STATIC::_getULONG($font, $offset);
697
							$offset += 4;
698
							for ($i = 0; $i < $nGroups; ++$i) {
699
								$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
700
								$offset += 4;
701
								$endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
702
								$offset += 4;
703
								$startGlyphID = TCPDF_STATIC::_getULONG($font, $offset);
704
								$offset += 4;
705
								for ($k = $startCharCode; $k <= $endCharCode; ++$k) {
706
									$is32idx = floor($c / 8);
707 View Code Duplication
									if ((isset($is32[$is32idx])) AND (($is32[$is32idx] & (1 << (7 - ($c % 8)))) == 0)) {
708
										$c = $k;
709
									} else {
710
										// 32 bit format
711
										// convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4)
712
										//LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
713
										//SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
714
										$c = ((55232 + ($k >> 10)) << 10) + (0xDC00 + ($k & 0x3FF)) -56613888;
715
									}
716
									$ctg[$c] = 0;
717
									++$startGlyphID;
718
								}
719
							}
720
							break;
721
						}
722 View Code Duplication
						case 10: { // Format 10: Trimmed array
723
							$offset += 10; // skip reserved, length and version/language
724
							$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
725
							$offset += 4;
726
							$numChars = TCPDF_STATIC::_getULONG($font, $offset);
727
							$offset += 4;
728
							for ($k = 0; $k < $numChars; ++$k) {
729
								$c = ($k + $startCharCode);
730
								$g = TCPDF_STATIC::_getUSHORT($font, $offset);
731
								$ctg[$c] = $g;
732
								$offset += 2;
733
							}
734
							break;
735
						}
736 View Code Duplication
						case 12: { // Format 12: Segmented coverage
737
							$offset += 10; // skip length and version/language
738
							$nGroups = TCPDF_STATIC::_getULONG($font, $offset);
739
							$offset += 4;
740
							for ($k = 0; $k < $nGroups; ++$k) {
741
								$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
742
								$offset += 4;
743
								$endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
744
								$offset += 4;
745
								$startGlyphCode = TCPDF_STATIC::_getULONG($font, $offset);
746
								$offset += 4;
747
								for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
748
									$ctg[$c] = $startGlyphCode;
749
									++$startGlyphCode;
750
								}
751
							}
752
							break;
753
						}
754
						case 13: { // Format 13: Many-to-one range mappings
755
							// to be implemented ...
756
							break;
757
						}
758
						case 14: { // Format 14: Unicode Variation Sequences
759
							// to be implemented ...
760
							break;
761
						}
762
					}
763
				}
764
			}
765
			if (!isset($ctg[0])) {
766
				$ctg[0] = 0;
767
			}
768
			// get xHeight (height of x)
769
			$offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[120]] + 4);
770
			$yMin = TCPDF_STATIC::_getFWORD($font, $offset);
771
			$offset += 4;
772
			$yMax = TCPDF_STATIC::_getFWORD($font, $offset);
773
			$offset += 2;
774
			$fmetric['XHeight'] = round(($yMax - $yMin) * $urk);
775
			// get CapHeight (height of H)
776
			$offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[72]] + 4);
777
			$yMin = TCPDF_STATIC::_getFWORD($font, $offset);
778
			$offset += 4;
779
			$yMax = TCPDF_STATIC::_getFWORD($font, $offset);
780
			$offset += 2;
781
			$fmetric['CapHeight'] = round(($yMax - $yMin) * $urk);
782
			// ceate widths array
783
			$cw = array();
784
			$offset = $table['hmtx']['offset'];
785
			for ($i = 0 ; $i < $numberOfHMetrics; ++$i) {
786
				$cw[$i] = round(TCPDF_STATIC::_getUFWORD($font, $offset) * $urk);
787
				$offset += 4; // skip lsb
788
			}
789
			if ($numberOfHMetrics < $numGlyphs) {
790
				// fill missing widths with the last value
791
				$cw = array_pad($cw, $numGlyphs, $cw[($numberOfHMetrics - 1)]);
792
			}
793
			$fmetric['MissingWidth'] = $cw[0];
794
			$fmetric['cw'] = '';
795
			$fmetric['cbbox'] = '';
796
			for ($cid = 0; $cid <= 65535; ++$cid) {
797
				if (isset($ctg[$cid])) {
798
					if (isset($cw[$ctg[$cid]])) {
799
						$fmetric['cw'] .= ','.$cid.'=>'.$cw[$ctg[$cid]];
800
					}
801
					if ($addcbbox AND isset($indexToLoc[$ctg[$cid]])) {
802
						$offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[$cid]]);
803
						$xMin = round(TCPDF_STATIC::_getFWORD($font, $offset + 2) * $urk);
804
						$yMin = round(TCPDF_STATIC::_getFWORD($font, $offset + 4) * $urk);
805
						$xMax = round(TCPDF_STATIC::_getFWORD($font, $offset + 6) * $urk);
806
						$yMax = round(TCPDF_STATIC::_getFWORD($font, $offset + 8) * $urk);
807
						$fmetric['cbbox'] .= ','.$cid.'=>array('.$xMin.','.$yMin.','.$xMax.','.$yMax.')';
808
					}
809
				}
810
			}
811
		} // end of true type
812
		if (($fmetric['type'] == 'TrueTypeUnicode') AND (count($ctg) == 256)) {
813
			$fmetric['type'] == 'TrueType';
814
		}
815
		// ---------- create php font file ----------
816
		$pfile = '<'.'?'.'php'."\n";
817
		$pfile .= '// TCPDF FONT FILE DESCRIPTION'."\n";
818
		$pfile .= '$type=\''.$fmetric['type'].'\';'."\n";
819
		$pfile .= '$name=\''.$fmetric['name'].'\';'."\n";
820
		$pfile .= '$up='.$fmetric['underlinePosition'].';'."\n";
821
		$pfile .= '$ut='.$fmetric['underlineThickness'].';'."\n";
822
		if ($fmetric['MissingWidth'] > 0) {
823
			$pfile .= '$dw='.$fmetric['MissingWidth'].';'."\n";
824
		} else {
825
			$pfile .= '$dw='.$fmetric['AvgWidth'].';'."\n";
826
		}
827
		$pfile .= '$diff=\''.$fmetric['diff'].'\';'."\n";
828
		if ($fmetric['type'] == 'Type1') {
829
			// Type 1
830
			$pfile .= '$enc=\''.$fmetric['enc'].'\';'."\n";
831
			$pfile .= '$file=\''.$fmetric['file'].'\';'."\n";
832
			$pfile .= '$size1='.$fmetric['size1'].';'."\n";
833
			$pfile .= '$size2='.$fmetric['size2'].';'."\n";
834
		} else {
835
			$pfile .= '$originalsize='.$fmetric['originalsize'].';'."\n";
836
			if ($fmetric['type'] == 'cidfont0') {
837
				// CID-0
838
				switch ($fonttype) {
839 View Code Duplication
					case 'CID0JP': {
840
						$pfile .= '// Japanese'."\n";
841
						$pfile .= '$enc=\'UniJIS-UTF16-H\';'."\n";
842
						$pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Japan1\',\'Supplement\'=>5);'."\n";
843
						$pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');'."\n";
844
						break;
845
					}
846 View Code Duplication
					case 'CID0KR': {
847
						$pfile .= '// Korean'."\n";
848
						$pfile .= '$enc=\'UniKS-UTF16-H\';'."\n";
849
						$pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Korea1\',\'Supplement\'=>0);'."\n";
850
						$pfile .= 'include(dirname(__FILE__).\'/uni2cid_ak12.php\');'."\n";
851
						break;
852
					}
853 View Code Duplication
					case 'CID0CS': {
854
						$pfile .= '// Chinese Simplified'."\n";
855
						$pfile .= '$enc=\'UniGB-UTF16-H\';'."\n";
856
						$pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'GB1\',\'Supplement\'=>2);'."\n";
857
						$pfile .= 'include(dirname(__FILE__).\'/uni2cid_ag15.php\');'."\n";
858
						break;
859
					}
860
					case 'CID0CT':
861 View Code Duplication
					default: {
862
						$pfile .= '// Chinese Traditional'."\n";
863
						$pfile .= '$enc=\'UniCNS-UTF16-H\';'."\n";
864
						$pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'CNS1\',\'Supplement\'=>0);'."\n";
865
						$pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');'."\n";
866
						break;
867
					}
868
				}
869
			} else {
870
				// TrueType
871
				$pfile .= '$enc=\''.$fmetric['enc'].'\';'."\n";
872
				$pfile .= '$file=\''.$fmetric['file'].'\';'."\n";
873
				$pfile .= '$ctg=\''.$fmetric['ctg'].'\';'."\n";
874
				// create CIDToGIDMap
875
				$cidtogidmap = str_pad('', 131072, "\x00"); // (256 * 256 * 2) = 131072
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
876
				foreach ($ctg as $cid => $gid) {
877
					$cidtogidmap = self::updateCIDtoGIDmap($cidtogidmap, $cid, $ctg[$cid]);
878
				}
879
				// store compressed CIDToGIDMap
880
				$fp = fopen($outpath.$fmetric['ctg'], 'wb');
881
				fwrite($fp, gzcompress($cidtogidmap));
882
				fclose($fp);
883
			}
884
		}
885
		$pfile .= '$desc=array(';
886
		$pfile .= '\'Flags\'=>'.$fmetric['Flags'].',';
887
		$pfile .= '\'FontBBox\'=>\'['.$fmetric['bbox'].']\',';
888
		$pfile .= '\'ItalicAngle\'=>'.$fmetric['italicAngle'].',';
889
		$pfile .= '\'Ascent\'=>'.$fmetric['Ascent'].',';
890
		$pfile .= '\'Descent\'=>'.$fmetric['Descent'].',';
891
		$pfile .= '\'Leading\'=>'.$fmetric['Leading'].',';
892
		$pfile .= '\'CapHeight\'=>'.$fmetric['CapHeight'].',';
893
		$pfile .= '\'XHeight\'=>'.$fmetric['XHeight'].',';
894
		$pfile .= '\'StemV\'=>'.$fmetric['StemV'].',';
895
		$pfile .= '\'StemH\'=>'.$fmetric['StemH'].',';
896
		$pfile .= '\'AvgWidth\'=>'.$fmetric['AvgWidth'].',';
897
		$pfile .= '\'MaxWidth\'=>'.$fmetric['MaxWidth'].',';
898
		$pfile .= '\'MissingWidth\'=>'.$fmetric['MissingWidth'].'';
899
		$pfile .= ');'."\n";
900
		if (!empty($fmetric['cbbox'])) {
901
			$pfile .= '$cbbox=array('.substr($fmetric['cbbox'], 1).');'."\n";
902
		}
903
		$pfile .= '$cw=array('.substr($fmetric['cw'], 1).');'."\n";
904
		$pfile .= '// --- EOF ---'."\n";
905
		// store file
906
		$fp = fopen($outpath.$font_name.'.php', 'w');
907
		fwrite($fp, $pfile);
908
		fclose($fp);
909
		// return TCPDF font name
910
		return $font_name;
911
	}
912
913
	/**
914
	 * Returs the checksum of a TTF table.
915
	 * @param $table (string) table to check
916
	 * @param $length (int) length of table in bytes
917
	 * @return int checksum
918
	 * @author Nicola Asuni
919
	 * @since 5.2.000 (2010-06-02)
920
	 * @public static
921
	 */
922
	public static function _getTTFtableChecksum($table, $length) {
923
		$sum = 0;
924
		$tlen = ($length / 4);
925
		$offset = 0;
926
		for ($i = 0; $i < $tlen; ++$i) {
927
			$v = unpack('Ni', substr($table, $offset, 4));
928
			$sum += $v['i'];
929
			$offset += 4;
930
		}
931
		$sum = unpack('Ni', pack('N', $sum));
932
		return $sum['i'];
933
	}
934
935
	/**
936
	 * Returns a subset of the TrueType font data without the unused glyphs.
937
	 * @param $font (string) TrueType font data.
938
	 * @param $subsetchars (array) Array of used characters (the glyphs to keep).
939
	 * @return (string) A subset of TrueType font data without the unused glyphs.
940
	 * @author Nicola Asuni
941
	 * @since 5.2.000 (2010-06-02)
942
	 * @public static
943
	 */
944
	public static function _getTrueTypeFontSubset($font, $subsetchars) {
945
		ksort($subsetchars);
946
		$offset = 0; // offset position of the font data
947
		if (TCPDF_STATIC::_getULONG($font, $offset) != 0x10000) {
948
			// sfnt version must be 0x00010000 for TrueType version 1.0.
949
			return $font;
950
		}
951
		$offset += 4;
952
		// get number of tables
953
		$numTables = TCPDF_STATIC::_getUSHORT($font, $offset);
954
		$offset += 2;
955
		// skip searchRange, entrySelector and rangeShift
956
		$offset += 6;
957
		// tables array
958
		$table = array();
959
		// for each table
960 View Code Duplication
		for ($i = 0; $i < $numTables; ++$i) {
961
			// get table info
962
			$tag = substr($font, $offset, 4);
963
			$offset += 4;
964
			$table[$tag] = array();
965
			$table[$tag]['checkSum'] = TCPDF_STATIC::_getULONG($font, $offset);
966
			$offset += 4;
967
			$table[$tag]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
968
			$offset += 4;
969
			$table[$tag]['length'] = TCPDF_STATIC::_getULONG($font, $offset);
970
			$offset += 4;
971
		}
972
		// check magicNumber
973
		$offset = $table['head']['offset'] + 12;
974
		if (TCPDF_STATIC::_getULONG($font, $offset) != 0x5F0F3CF5) {
975
			// magicNumber must be 0x5F0F3CF5
976
			return $font;
977
		}
978
		$offset += 4;
979
		// get offset mode (indexToLocFormat : 0 = short, 1 = long)
980
		$offset = $table['head']['offset'] + 50;
981
		$short_offset = (TCPDF_STATIC::_getSHORT($font, $offset) == 0);
982
		$offset += 2;
983
		// get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
984
		$indexToLoc = array();
985
		$offset = $table['loca']['offset'];
986 View Code Duplication
		if ($short_offset) {
987
			// short version
988
			$tot_num_glyphs = floor($table['loca']['length'] / 2); // numGlyphs + 1
989
			for ($i = 0; $i < $tot_num_glyphs; ++$i) {
990
				$indexToLoc[$i] = TCPDF_STATIC::_getUSHORT($font, $offset) * 2;
991
				$offset += 2;
992
			}
993
		} else {
994
			// long version
995
			$tot_num_glyphs = ($table['loca']['length'] / 4); // numGlyphs + 1
996
			for ($i = 0; $i < $tot_num_glyphs; ++$i) {
997
				$indexToLoc[$i] = TCPDF_STATIC::_getULONG($font, $offset);
998
				$offset += 4;
999
			}
1000
		}
1001
		// get glyphs indexes of chars from cmap table
1002
		$subsetglyphs = array(); // glyph IDs on key
1003
		$subsetglyphs[0] = true; // character codes that do not correspond to any glyph in the font should be mapped to glyph index 0
1004
		$offset = $table['cmap']['offset'] + 2;
1005
		$numEncodingTables = TCPDF_STATIC::_getUSHORT($font, $offset);
1006
		$offset += 2;
1007
		$encodingTables = array();
1008 View Code Duplication
		for ($i = 0; $i < $numEncodingTables; ++$i) {
1009
			$encodingTables[$i]['platformID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
1010
			$offset += 2;
1011
			$encodingTables[$i]['encodingID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
1012
			$offset += 2;
1013
			$encodingTables[$i]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
1014
			$offset += 4;
1015
		}
1016
		foreach ($encodingTables as $enctable) {
1017
			// get all platforms and encodings
1018
			$offset = $table['cmap']['offset'] + $enctable['offset'];
1019
			$format = TCPDF_STATIC::_getUSHORT($font, $offset);
1020
			$offset += 2;
1021
			switch ($format) {
1022 View Code Duplication
				case 0: { // Format 0: Byte encoding table
1023
					$offset += 4; // skip length and version/language
1024
					for ($c = 0; $c < 256; ++$c) {
1025
						if (isset($subsetchars[$c])) {
1026
							$g = TCPDF_STATIC::_getBYTE($font, $offset);
1027
							$subsetglyphs[$g] = true;
1028
						}
1029
						++$offset;
1030
					}
1031
					break;
1032
				}
1033
				case 2: { // Format 2: High-byte mapping through table
1034
					$offset += 4; // skip length and version/language
1035
					$numSubHeaders = 0;
1036 View Code Duplication
					for ($i = 0; $i < 256; ++$i) {
1037
						// Array that maps high bytes to subHeaders: value is subHeader index * 8.
1038
						$subHeaderKeys[$i] = (TCPDF_STATIC::_getUSHORT($font, $offset) / 8);
1039
						$offset += 2;
1040
						if ($numSubHeaders < $subHeaderKeys[$i]) {
1041
							$numSubHeaders = $subHeaderKeys[$i];
1042
						}
1043
					}
1044
					// the number of subHeaders is equal to the max of subHeaderKeys + 1
1045
					++$numSubHeaders;
1046
					// read subHeader structures
1047
					$subHeaders = array();
1048
					$numGlyphIndexArray = 0;
1049 View Code Duplication
					for ($k = 0; $k < $numSubHeaders; ++$k) {
1050
						$subHeaders[$k]['firstCode'] = TCPDF_STATIC::_getUSHORT($font, $offset);
1051
						$offset += 2;
1052
						$subHeaders[$k]['entryCount'] = TCPDF_STATIC::_getUSHORT($font, $offset);
1053
						$offset += 2;
1054
						$subHeaders[$k]['idDelta'] = TCPDF_STATIC::_getUSHORT($font, $offset);
1055
						$offset += 2;
1056
						$subHeaders[$k]['idRangeOffset'] = TCPDF_STATIC::_getUSHORT($font, $offset);
1057
						$offset += 2;
1058
						$subHeaders[$k]['idRangeOffset'] -= (2 + (($numSubHeaders - $k - 1) * 8));
1059
						$subHeaders[$k]['idRangeOffset'] /= 2;
1060
						$numGlyphIndexArray += $subHeaders[$k]['entryCount'];
1061
					}
1062 View Code Duplication
					for ($k = 0; $k < $numGlyphIndexArray; ++$k) {
1063
						$glyphIndexArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
1064
						$offset += 2;
1065
					}
1066
					for ($i = 0; $i < 256; ++$i) {
1067
						$k = $subHeaderKeys[$i];
1068
						if ($k == 0) {
1069
							// one byte code
1070
							$c = $i;
1071
							if (isset($subsetchars[$c])) {
1072
								$g = $glyphIndexArray[0];
1073
								$subsetglyphs[$g] = true;
1074
							}
1075 View Code Duplication
						} else {
1076
							// two bytes code
1077
							$start_byte = $subHeaders[$k]['firstCode'];
1078
							$end_byte = $start_byte + $subHeaders[$k]['entryCount'];
1079
							for ($j = $start_byte; $j < $end_byte; ++$j) {
1080
								// combine high and low bytes
1081
								$c = (($i << 8) + $j);
1082
								if (isset($subsetchars[$c])) {
1083
									$idRangeOffset = ($subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode']);
1084
									$g = ($glyphIndexArray[$idRangeOffset] + $subHeaders[$k]['idDelta']) % 65536;
1085
									if ($g < 0) {
1086
										$g = 0;
1087
									}
1088
									$subsetglyphs[$g] = true;
1089
								}
1090
							}
1091
						}
1092
					}
1093
					break;
1094
				}
1095 View Code Duplication
				case 4: { // Format 4: Segment mapping to delta values
1096
					$length = TCPDF_STATIC::_getUSHORT($font, $offset);
1097
					$offset += 2;
1098
					$offset += 2; // skip version/language
1099
					$segCount = floor(TCPDF_STATIC::_getUSHORT($font, $offset) / 2);
1100
					$offset += 2;
1101
					$offset += 6; // skip searchRange, entrySelector, rangeShift
1102
					$endCount = array(); // array of end character codes for each segment
1103
					for ($k = 0; $k < $segCount; ++$k) {
1104
						$endCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
1105
						$offset += 2;
1106
					}
1107
					$offset += 2; // skip reservedPad
1108
					$startCount = array(); // array of start character codes for each segment
1109
					for ($k = 0; $k < $segCount; ++$k) {
1110
						$startCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
1111
						$offset += 2;
1112
					}
1113
					$idDelta = array(); // delta for all character codes in segment
1114
					for ($k = 0; $k < $segCount; ++$k) {
1115
						$idDelta[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
1116
						$offset += 2;
1117
					}
1118
					$idRangeOffset = array(); // Offsets into glyphIdArray or 0
1119
					for ($k = 0; $k < $segCount; ++$k) {
1120
						$idRangeOffset[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
1121
						$offset += 2;
1122
					}
1123
					$gidlen = (floor($length / 2) - 8 - (4 * $segCount));
1124
					$glyphIdArray = array(); // glyph index array
1125
					for ($k = 0; $k < $gidlen; ++$k) {
1126
						$glyphIdArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
1127
						$offset += 2;
1128
					}
1129
					for ($k = 0; $k < $segCount; ++$k) {
1130
						for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
1131
							if (isset($subsetchars[$c])) {
1132
								if ($idRangeOffset[$k] == 0) {
1133
									$g = ($idDelta[$k] + $c) % 65536;
1134
								} else {
1135
									$gid = (floor($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k));
1136
									$g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536;
1137
								}
1138
								if ($g < 0) {
1139
									$g = 0;
1140
								}
1141
								$subsetglyphs[$g] = true;
1142
							}
1143
						}
1144
					}	
1145
					break;
1146
				}
1147 View Code Duplication
				case 6: { // Format 6: Trimmed table mapping
1148
					$offset += 4; // skip length and version/language
1149
					$firstCode = TCPDF_STATIC::_getUSHORT($font, $offset);
1150
					$offset += 2;
1151
					$entryCount = TCPDF_STATIC::_getUSHORT($font, $offset);
1152
					$offset += 2;
1153
					for ($k = 0; $k < $entryCount; ++$k) {
1154
						$c = ($k + $firstCode);
1155
						if (isset($subsetchars[$c])) {
1156
							$g = TCPDF_STATIC::_getUSHORT($font, $offset);
1157
							$subsetglyphs[$g] = true;
1158
						}
1159
						$offset += 2;
1160
					}
1161
					break;
1162
				}
1163
				case 8: { // Format 8: Mixed 16-bit and 32-bit coverage
1164
					$offset += 10; // skip reserved, length and version/language
1165 View Code Duplication
					for ($k = 0; $k < 8192; ++$k) {
1166
						$is32[$k] = TCPDF_STATIC::_getBYTE($font, $offset);
1167
						++$offset;
1168
					}
1169
					$nGroups = TCPDF_STATIC::_getULONG($font, $offset);
1170
					$offset += 4;
1171
					for ($i = 0; $i < $nGroups; ++$i) {
1172
						$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
1173
						$offset += 4;
1174
						$endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
1175
						$offset += 4;
1176
						$startGlyphID = TCPDF_STATIC::_getULONG($font, $offset);
1177
						$offset += 4;
1178
						for ($k = $startCharCode; $k <= $endCharCode; ++$k) {
1179
							$is32idx = floor($c / 8);
1180 View Code Duplication
							if ((isset($is32[$is32idx])) AND (($is32[$is32idx] & (1 << (7 - ($c % 8)))) == 0)) {
1181
								$c = $k;
1182
							} else {
1183
								// 32 bit format
1184
								// convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4)
1185
								//LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1186
								//SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1187
								$c = ((55232 + ($k >> 10)) << 10) + (0xDC00 + ($k & 0x3FF)) -56613888;
1188
							}
1189
							if (isset($subsetchars[$c])) {
1190
								$subsetglyphs[$startGlyphID] = true;
1191
							}
1192
							++$startGlyphID;
1193
						}
1194
					}
1195
					break;
1196
				}
1197 View Code Duplication
				case 10: { // Format 10: Trimmed array
1198
					$offset += 10; // skip reserved, length and version/language
1199
					$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
1200
					$offset += 4;
1201
					$numChars = TCPDF_STATIC::_getULONG($font, $offset);
1202
					$offset += 4;
1203
					for ($k = 0; $k < $numChars; ++$k) {
1204
						$c = ($k + $startCharCode);
1205
						if (isset($subsetchars[$c])) {
1206
							$g = TCPDF_STATIC::_getUSHORT($font, $offset);
1207
							$subsetglyphs[$g] = true;
1208
						}
1209
						$offset += 2;
1210
					}
1211
					break;
1212
				}
1213 View Code Duplication
				case 12: { // Format 12: Segmented coverage
1214
					$offset += 10; // skip length and version/language
1215
					$nGroups = TCPDF_STATIC::_getULONG($font, $offset);
1216
					$offset += 4;
1217
					for ($k = 0; $k < $nGroups; ++$k) {
1218
						$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
1219
						$offset += 4;
1220
						$endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
1221
						$offset += 4;
1222
						$startGlyphCode = TCPDF_STATIC::_getULONG($font, $offset);
1223
						$offset += 4;
1224
						for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
1225
							if (isset($subsetchars[$c])) {
1226
								$subsetglyphs[$startGlyphCode] = true;
1227
							}
1228
							++$startGlyphCode;
1229
						}
1230
					}
1231
					break;
1232
				}
1233
				case 13: { // Format 13: Many-to-one range mappings
1234
					// to be implemented ...
1235
					break;
1236
				}
1237
				case 14: { // Format 14: Unicode Variation Sequences
1238
					// to be implemented ...
1239
					break;
1240
				}
1241
			}
1242
		}
1243
		// include all parts of composite glyphs
1244
		$new_sga = $subsetglyphs;
1245
		while (!empty($new_sga)) {
1246
			$sga = $new_sga;
1247
			$new_sga = array();
1248
			foreach ($sga as $key => $val) {
1249
				if (isset($indexToLoc[$key])) {
1250
					$offset = ($table['glyf']['offset'] + $indexToLoc[$key]);
1251
					$numberOfContours = TCPDF_STATIC::_getSHORT($font, $offset);
1252
					$offset += 2;
1253
					if ($numberOfContours < 0) { // composite glyph
1254
						$offset += 8; // skip xMin, yMin, xMax, yMax
1255
						do {
1256
							$flags = TCPDF_STATIC::_getUSHORT($font, $offset);
1257
							$offset += 2;
1258
							$glyphIndex = TCPDF_STATIC::_getUSHORT($font, $offset);
1259
							$offset += 2;
1260
							if (!isset($subsetglyphs[$glyphIndex])) {
1261
								// add missing glyphs
1262
								$new_sga[$glyphIndex] = true;
1263
							}
1264
							// skip some bytes by case
1265
							if ($flags & 1) {
1266
								$offset += 4;
1267
							} else {
1268
								$offset += 2;
1269
							}
1270
							if ($flags & 8) {
1271
								$offset += 2;
1272
							} elseif ($flags & 64) {
1273
								$offset += 4;
1274
							} elseif ($flags & 128) {
1275
								$offset += 8;
1276
							}
1277
						} while ($flags & 32);
1278
					}
1279
				}
1280
			}
1281
			$subsetglyphs += $new_sga;
1282
		}
1283
		// sort glyphs by key (and remove duplicates)
1284
		ksort($subsetglyphs);
1285
		// build new glyf and loca tables
1286
		$glyf = '';
1287
		$loca = '';
1288
		$offset = 0;
1289
		$glyf_offset = $table['glyf']['offset'];
1290
		for ($i = 0; $i < $tot_num_glyphs; ++$i) {
1291
			if (isset($subsetglyphs[$i])) {
1292
				$length = ($indexToLoc[($i + 1)] - $indexToLoc[$i]);
1293
				$glyf .= substr($font, ($glyf_offset + $indexToLoc[$i]), $length);
1294
			} else {
1295
				$length = 0;
1296
			}
1297
			if ($short_offset) {
1298
				$loca .= pack('n', floor($offset / 2));
1299
			} else {
1300
				$loca .= pack('N', $offset);
1301
			}
1302
			$offset += $length;
1303
		}
1304
		// array of table names to preserve (loca and glyf tables will be added later)
1305
		// the cmap table is not needed and shall not be present, since the mapping from character codes to glyph descriptions is provided separately
1306
		$table_names = array ('head', 'hhea', 'hmtx', 'maxp', 'cvt ', 'fpgm', 'prep'); // minimum required table names
1307
		// get the tables to preserve
1308
		$offset = 12;
1309
		foreach ($table as $tag => $val) {
1310
			if (in_array($tag, $table_names)) {
1311
				$table[$tag]['data'] = substr($font, $table[$tag]['offset'], $table[$tag]['length']);
1312
				if ($tag == 'head') {
1313
					// set the checkSumAdjustment to 0
1314
					$table[$tag]['data'] = substr($table[$tag]['data'], 0, 8)."\x0\x0\x0\x0".substr($table[$tag]['data'], 12);
1315
				}
1316
				$pad = 4 - ($table[$tag]['length'] % 4);
1317 View Code Duplication
				if ($pad != 4) {
1318
					// the length of a table must be a multiple of four bytes
1319
					$table[$tag]['length'] += $pad;
1320
					$table[$tag]['data'] .= str_repeat("\x0", $pad);
1321
				}
1322
				$table[$tag]['offset'] = $offset;
1323
				$offset += $table[$tag]['length'];
1324
				// check sum is not changed (so keep the following line commented)
1325
				//$table[$tag]['checkSum'] = self::_getTTFtableChecksum($table[$tag]['data'], $table[$tag]['length']);
0 ignored issues
show
Unused Code Comprehensibility introduced by
85% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1326
			} else {
1327
				unset($table[$tag]);
1328
			}
1329
		}
1330
		// add loca
1331
		$table['loca']['data'] = $loca;
1332
		$table['loca']['length'] = strlen($loca);
1333
		$pad = 4 - ($table['loca']['length'] % 4);
1334 View Code Duplication
		if ($pad != 4) {
1335
			// the length of a table must be a multiple of four bytes
1336
			$table['loca']['length'] += $pad;
1337
			$table['loca']['data'] .= str_repeat("\x0", $pad);
1338
		}
1339
		$table['loca']['offset'] = $offset;
1340
		$table['loca']['checkSum'] = self::_getTTFtableChecksum($table['loca']['data'], $table['loca']['length']);
1341
		$offset += $table['loca']['length'];
1342
		// add glyf
1343
		$table['glyf']['data'] = $glyf;
1344
		$table['glyf']['length'] = strlen($glyf);
1345
		$pad = 4 - ($table['glyf']['length'] % 4);
1346 View Code Duplication
		if ($pad != 4) {
1347
			// the length of a table must be a multiple of four bytes
1348
			$table['glyf']['length'] += $pad;
1349
			$table['glyf']['data'] .= str_repeat("\x0", $pad);
1350
		}
1351
		$table['glyf']['offset'] = $offset;
1352
		$table['glyf']['checkSum'] = self::_getTTFtableChecksum($table['glyf']['data'], $table['glyf']['length']);
1353
		// rebuild font
1354
		$font = '';
1355
		$font .= pack('N', 0x10000); // sfnt version
1356
		$numTables = count($table);
1357
		$font .= pack('n', $numTables); // numTables
1358
		$entrySelector = floor(log($numTables, 2));
1359
		$searchRange = pow(2, $entrySelector) * 16;
1360
		$rangeShift = ($numTables * 16) - $searchRange;
1361
		$font .= pack('n', $searchRange); // searchRange
1362
		$font .= pack('n', $entrySelector); // entrySelector
1363
		$font .= pack('n', $rangeShift); // rangeShift
1364
		$offset = ($numTables * 16);
1365
		foreach ($table as $tag => $data) {
1366
			$font .= $tag; // tag
1367
			$font .= pack('N', $data['checkSum']); // checkSum
1368
			$font .= pack('N', ($data['offset'] + $offset)); // offset
1369
			$font .= pack('N', $data['length']); // length
1370
		}
1371
		foreach ($table as $data) {
1372
			$font .= $data['data'];
1373
		}
1374
		// set checkSumAdjustment on head table
1375
		$checkSumAdjustment = 0xB1B0AFBA - self::_getTTFtableChecksum($font, strlen($font));
1376
		$font = substr($font, 0, $table['head']['offset'] + 8).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + 12);
1377
		return $font;
1378
	}
1379
1380
	/**
1381
	 * Outputs font widths
1382
	 * @param $font (array) font data
1383
	 * @param $cidoffset (int) offset for CID values
1384
	 * @return PDF command string for font widths
1385
	 * @author Nicola Asuni
1386
	 * @since 4.4.000 (2008-12-07)
1387
	 * @public static
1388
	 */
1389
	public static function _putfontwidths($font, $cidoffset=0) {
1390
		ksort($font['cw']);
1391
		$rangeid = 0;
1392
		$range = array();
1393
		$prevcid = -2;
1394
		$prevwidth = -1;
1395
		$interval = false;
1396
		// for each character
1397
		foreach ($font['cw'] as $cid => $width) {
1398
			$cid -= $cidoffset;
1399
			if ($font['subset'] AND (!isset($font['subsetchars'][$cid]))) {
1400
				// ignore the unused characters (font subsetting)
1401
				continue;
1402
			}
1403
			if ($width != $font['dw']) {
1404
				if ($cid == ($prevcid + 1)) {
1405
					// consecutive CID
1406
					if ($width == $prevwidth) {
1407
						if ($width == $range[$rangeid][0]) {
1408
							$range[$rangeid][] = $width;
1409 View Code Duplication
						} else {
1410
							array_pop($range[$rangeid]);
1411
							// new range
1412
							$rangeid = $prevcid;
1413
							$range[$rangeid] = array();
1414
							$range[$rangeid][] = $prevwidth;
1415
							$range[$rangeid][] = $width;
1416
						}
1417
						$interval = true;
1418
						$range[$rangeid]['interval'] = true;
1419 View Code Duplication
					} else {
1420
						if ($interval) {
1421
							// new range
1422
							$rangeid = $cid;
1423
							$range[$rangeid] = array();
1424
							$range[$rangeid][] = $width;
1425
						} else {
1426
							$range[$rangeid][] = $width;
1427
						}
1428
						$interval = false;
1429
					}
1430
				} else {
1431
					// new range
1432
					$rangeid = $cid;
1433
					$range[$rangeid] = array();
1434
					$range[$rangeid][] = $width;
1435
					$interval = false;
1436
				}
1437
				$prevcid = $cid;
1438
				$prevwidth = $width;
1439
			}
1440
		}
1441
		// optimize ranges
1442
		$prevk = -1;
1443
		$nextk = -1;
1444
		$prevint = false;
1445
		foreach ($range as $k => $ws) {
1446
			$cws = count($ws);
1447
			if (($k == $nextk) AND (!$prevint) AND ((!isset($ws['interval'])) OR ($cws < 4))) {
1448
				if (isset($range[$k]['interval'])) {
1449
					unset($range[$k]['interval']);
1450
				}
1451
				$range[$prevk] = array_merge($range[$prevk], $range[$k]);
1452
				unset($range[$k]);
1453
			} else {
1454
				$prevk = $k;
1455
			}
1456
			$nextk = $k + $cws;
1457
			if (isset($ws['interval'])) {
1458
				if ($cws > 3) {
1459
					$prevint = true;
1460
				} else {
1461
					$prevint = false;
1462
				}
1463
				if (isset($range[$k]['interval'])) {
1464
					unset($range[$k]['interval']);
1465
				}
1466
				--$nextk;
1467
			} else {
1468
				$prevint = false;
1469
			}
1470
		}
1471
		// output data
1472
		$w = '';
1473
		foreach ($range as $k => $ws) {
1474
			if (count(array_count_values($ws)) == 1) {
1475
				// interval mode is more compact
1476
				$w .= ' '.$k.' '.($k + count($ws) - 1).' '.$ws[0];
1477
			} else {
1478
				// range mode
1479
				$w .= ' '.$k.' [ '.implode(' ', $ws).' ]';
1480
			}
1481
		}
1482
		return '/W ['.$w.' ]';
1483
	}
1484
1485
	/**
1486
	 * Returns the unicode caracter specified by the value
1487
	 * @param $c (int) UTF-8 value
1488
	 * @param $unicode (boolean) True if we are in unicode mode, false otherwise.
1489
	 * @return Returns the specified character.
1490
	 * @since 2.3.000 (2008-03-05)
1491
	 * @public static
1492
	 */
1493
	public static function unichr($c, $unicode=true) {
1494
		if (!$unicode) {
1495
			return chr($c);
1496
		} elseif ($c <= 0x7F) {
1497
			// one byte
1498
			return chr($c);
1499
		} elseif ($c <= 0x7FF) {
1500
			// two bytes
1501
			return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F);
1502
		} elseif ($c <= 0xFFFF) {
1503
			// three bytes
1504
			return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
1505
		} elseif ($c <= 0x10FFFF) {
1506
			// four bytes
1507
			return chr(0xF0 | $c >> 18).chr(0x80 | $c >> 12 & 0x3F).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
1508
		} else {
1509
			return '';
1510
		}
1511
	}
1512
1513
	/**
1514
	 * Returns the unicode caracter specified by UTF-8 value
1515
	 * @param $c (int) UTF-8 value
1516
	 * @return Returns the specified character.
1517
	 * @public static
1518
	 */
1519
	public static function unichrUnicode($c) {
1520
		return self::unichr($c, true);
1521
	}
1522
1523
	/**
1524
	 * Returns the unicode caracter specified by ASCII value
1525
	 * @param $c (int) UTF-8 value
1526
	 * @return Returns the specified character.
1527
	 * @public static
1528
	 */
1529
	public static function unichrASCII($c) {
1530
		return self::unichr($c, false);
1531
	}
1532
1533
	/**
1534
	 * Converts array of UTF-8 characters to UTF16-BE string.<br>
1535
	 * Based on: http://www.faqs.org/rfcs/rfc2781.html
1536
	 * <pre>
1537
	 *   Encoding UTF-16:
1538
	 *
1539
	 *   Encoding of a single character from an ISO 10646 character value to
1540
	 *    UTF-16 proceeds as follows. Let U be the character number, no greater
1541
	 *    than 0x10FFFF.
1542
	 *
1543
	 *    1) If U < 0x10000, encode U as a 16-bit unsigned integer and
1544
	 *       terminate.
1545
	 *
1546
	 *    2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
1547
	 *       U' must be less than or equal to 0xFFFFF. That is, U' can be
1548
	 *       represented in 20 bits.
1549
	 *
1550
	 *    3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
1551
	 *       0xDC00, respectively. These integers each have 10 bits free to
1552
	 *       encode the character value, for a total of 20 bits.
1553
	 *
1554
	 *    4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order
1555
	 *       bits of W1 and the 10 low-order bits of U' to the 10 low-order
1556
	 *       bits of W2. Terminate.
1557
	 *
1558
	 *    Graphically, steps 2 through 4 look like:
1559
	 *    U' = yyyyyyyyyyxxxxxxxxxx
1560
	 *    W1 = 110110yyyyyyyyyy
1561
	 *    W2 = 110111xxxxxxxxxx
1562
	 * </pre>
1563
	 * @param $unicode (array) array containing UTF-8 unicode values
1564
	 * @param $setbom (boolean) if true set the Byte Order Mark (BOM = 0xFEFF)
1565
	 * @return string
1566
	 * @protected
1567
	 * @author Nicola Asuni
1568
	 * @since 2.1.000 (2008-01-08)
1569
	 * @public static
1570
	 */
1571
	public static function arrUTF8ToUTF16BE($unicode, $setbom=false) {
1572
		$outstr = ''; // string to be returned
1573
		if ($setbom) {
1574
			$outstr .= "\xFE\xFF"; // Byte Order Mark (BOM)
1575
		}
1576
		foreach ($unicode as $char) {
1577
			if ($char == 0x200b) {
1578
				// skip Unicode Character 'ZERO WIDTH SPACE' (DEC:8203, U+200B)
1579
			} elseif ($char == 0xFFFD) {
1580
				$outstr .= "\xFF\xFD"; // replacement character
1581
			} elseif ($char < 0x10000) {
1582
				$outstr .= chr($char >> 0x08);
1583
				$outstr .= chr($char & 0xFF);
1584
			} else {
1585
				$char -= 0x10000;
1586
				$w1 = 0xD800 | ($char >> 0x0a);
1587
				$w2 = 0xDC00 | ($char & 0x3FF);
1588
				$outstr .= chr($w1 >> 0x08);
1589
				$outstr .= chr($w1 & 0xFF);
1590
				$outstr .= chr($w2 >> 0x08);
1591
				$outstr .= chr($w2 & 0xFF);
1592
			}
1593
		}
1594
		return $outstr;
1595
	}
1596
1597
	/**
1598
	 * Convert an array of UTF8 values to array of unicode characters
1599
	 * @param $ta (array) The input array of UTF8 values.
1600
	 * @param $isunicode (boolean) True for Unicode mode, false otherwise.
1601
	 * @return Return array of unicode characters
1602
	 * @since 4.5.037 (2009-04-07)
1603
	 * @public static
1604
	 */
1605
	public static function UTF8ArrayToUniArray($ta, $isunicode=true) {
1606
		if ($isunicode) {
1607
			return array_map(array('TCPDF_FONTS', 'unichrUnicode'), $ta);
1608
		}
1609
		return array_map(array('TCPDF_FONTS', 'unichrASCII'), $ta);
1610
	}
1611
1612
	/**
1613
	 * Extract a slice of the $strarr array and return it as string.
1614
	 * @param $strarr (string) The input array of characters.
1615
	 * @param $start (int) the starting element of $strarr.
1616
	 * @param $end (int) first element that will not be returned.
1617
	 * @param $unicode (boolean) True if we are in unicode mode, false otherwise.
1618
	 * @return Return part of a string
1619
	 * @public static
1620
	 */
1621
	public static function UTF8ArrSubString($strarr, $start='', $end='', $unicode=true) {
1622
		if (strlen($start) == 0) {
1623
			$start = 0;
1624
		}
1625
		if (strlen($end) == 0) {
1626
			$end = count($strarr);
1627
		}
1628
		$string = '';
1629
		for ($i = $start; $i < $end; ++$i) {
1630
			$string .= self::unichr($strarr[$i], $unicode);
1631
		}
1632
		return $string;
1633
	}
1634
1635
	/**
1636
	 * Extract a slice of the $uniarr array and return it as string.
1637
	 * @param $uniarr (string) The input array of characters.
1638
	 * @param $start (int) the starting element of $strarr.
1639
	 * @param $end (int) first element that will not be returned.
1640
	 * @return Return part of a string
1641
	 * @since 4.5.037 (2009-04-07)
1642
	 * @public static
1643
	 */
1644 View Code Duplication
	public static function UniArrSubString($uniarr, $start='', $end='') {
1645
		if (strlen($start) == 0) {
1646
			$start = 0;
1647
		}
1648
		if (strlen($end) == 0) {
1649
			$end = count($uniarr);
1650
		}
1651
		$string = '';
1652
		for ($i=$start; $i < $end; ++$i) {
1653
			$string .= $uniarr[$i];
1654
		}
1655
		return $string;
1656
	}
1657
1658
	/**
1659
	 * Update the CIDToGIDMap string with a new value.
1660
	 * @param $map (string) CIDToGIDMap.
1661
	 * @param $cid (int) CID value.
1662
	 * @param $gid (int) GID value.
1663
	 * @return (string) CIDToGIDMap.
1664
	 * @author Nicola Asuni
1665
	 * @since 5.9.123 (2011-09-29)
1666
	 * @public static
1667
	 */
1668
	public static function updateCIDtoGIDmap($map, $cid, $gid) {
1669
		if (($cid >= 0) AND ($cid <= 0xFFFF) AND ($gid >= 0)) {
1670
			if ($gid > 0xFFFF) {
1671
				$gid -= 0x10000;
1672
			}
1673
			$map[($cid * 2)] = chr($gid >> 8);
1674
			$map[(($cid * 2) + 1)] = chr($gid & 0xFF);
1675
		}
1676
		return $map;
1677
	}
1678
1679
	/**
1680
	 * Return fonts path
1681
	 * @return string
1682
	 * @public static
1683
	 */
1684
	public static function _getfontpath() {
1685 View Code Duplication
		if (!defined('K_PATH_FONTS') AND is_dir($fdir = realpath(dirname(__FILE__).'/../fonts'))) {
1686
			if (substr($fdir, -1) != '/') {
1687
				$fdir .= '/';
1688
			}
1689
			define('K_PATH_FONTS', $fdir);
1690
		}
1691
		return defined('K_PATH_FONTS') ? K_PATH_FONTS : '';
1692
	}
1693
1694
	/**
1695
	 * Return font full path
1696
	 * @param $file (string) Font file name.
1697
	 * @param $fontdir (string) Font directory (set to false fto search on default directories)
1698
	 * @return string Font full path or empty string
1699
	 * @author Nicola Asuni
1700
	 * @since 6.0.025
1701
	 * @public static
1702
	 */
1703
	public static function getFontFullPath($file, $fontdir=false) {
1704
		$fontfile = '';
1705
		// search files on various directories
1706
		if (($fontdir !== false) AND @file_exists($fontdir.$file)) {
1707
			$fontfile = $fontdir.$file;
1708
		} elseif (@file_exists(self::_getfontpath().$file)) {
1709
			$fontfile = self::_getfontpath().$file;
1710
		} elseif (@file_exists($file)) {
1711
			$fontfile = $file;
1712
		}
1713
		return $fontfile;
1714
	}
1715
1716
	/**
1717
	 * Converts UTF-8 characters array to array of Latin1 characters array<br>
1718
	 * @param $unicode (array) array containing UTF-8 unicode values
1719
	 * @return array
1720
	 * @author Nicola Asuni
1721
	 * @since 4.8.023 (2010-01-15)
1722
	 * @public static
1723
	 */
1724
	public static function UTF8ArrToLatin1Arr($unicode) {
1725
		$outarr = array(); // array to be returned
1726
		foreach ($unicode as $char) {
1727
			if ($char < 256) {
1728
				$outarr[] = $char;
1729
			} elseif (array_key_exists($char, TCPDF_FONT_DATA::$uni_utf8tolatin)) {
1730
				// map from UTF-8
1731
				$outarr[] = TCPDF_FONT_DATA::$uni_utf8tolatin[$char];
1732
			} elseif ($char == 0xFFFD) {
1733
				// skip
1734
			} else {
1735
				$outarr[] = 63; // '?' character
1736
			}
1737
		}
1738
		return $outarr;
1739
	}
1740
1741
	/**
1742
	 * Converts UTF-8 characters array to array of Latin1 string<br>
1743
	 * @param $unicode (array) array containing UTF-8 unicode values
1744
	 * @return array
1745
	 * @author Nicola Asuni
1746
	 * @since 4.8.023 (2010-01-15)
1747
	 * @public static
1748
	 */
1749
	public static function UTF8ArrToLatin1($unicode) {
1750
		$outstr = ''; // string to be returned
1751
		foreach ($unicode as $char) {
1752
			if ($char < 256) {
1753
				$outstr .= chr($char);
1754
			} elseif (array_key_exists($char, TCPDF_FONT_DATA::$uni_utf8tolatin)) {
1755
				// map from UTF-8
1756
				$outstr .= chr(TCPDF_FONT_DATA::$uni_utf8tolatin[$char]);
1757
			} elseif ($char == 0xFFFD) {
1758
				// skip
1759
			} else {
1760
				$outstr .= '?';
1761
			}
1762
		}
1763
		return $outstr;
1764
	}
1765
1766
	/**
1767
	 * Converts UTF-8 character to integer value.<br>
1768
	 * Uses the getUniord() method if the value is not cached.
1769
	 * @param $uch (string) character string to process.
1770
	 * @return integer Unicode value
1771
	 * @public static
1772
	 */
1773
	public static function uniord($uch) {
1774
		if (!isset(self::$cache_uniord[$uch])) {
1775
			self::$cache_uniord[$uch] = self::getUniord($uch);
1776
		}
1777
		return self::$cache_uniord[$uch];
1778
	}
1779
1780
	/**
1781
	 * Converts UTF-8 character to integer value.<br>
1782
	 * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
1783
	 * Based on: http://www.faqs.org/rfcs/rfc3629.html
1784
	 * <pre>
1785
	 *    Char. number range  |        UTF-8 octet sequence
1786
	 *       (hexadecimal)    |              (binary)
1787
	 *    --------------------+-----------------------------------------------
1788
	 *    0000 0000-0000 007F | 0xxxxxxx
1789
	 *    0000 0080-0000 07FF | 110xxxxx 10xxxxxx
1790
	 *    0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
1791
	 *    0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
1792
	 *    ---------------------------------------------------------------------
1793
	 *
1794
	 *   ABFN notation:
1795
	 *   ---------------------------------------------------------------------
1796
	 *   UTF8-octets = *( UTF8-char )
1797
	 *   UTF8-char   = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
1798
	 *   UTF8-1      = %x00-7F
1799
	 *   UTF8-2      = %xC2-DF UTF8-tail
1800
	 *
1801
	 *   UTF8-3      = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
1802
	 *                 %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
1803
	 *   UTF8-4      = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
1804
	 *                 %xF4 %x80-8F 2( UTF8-tail )
1805
	 *   UTF8-tail   = %x80-BF
1806
	 *   ---------------------------------------------------------------------
1807
	 * </pre>
1808
	 * @param $uch (string) character string to process.
1809
	 * @return integer Unicode value
1810
	 * @author Nicola Asuni
1811
	 * @public static
1812
	 */
1813
	public static function getUniord($uch) {
1814
		if (function_exists('mb_convert_encoding')) {
1815
			list(, $char) = @unpack('N', mb_convert_encoding($uch, 'UCS-4BE', 'UTF-8'));
1816
			if ($char >= 0) {
1817
				return $char;
1818
			}
1819
		}
1820
		$bytes = array(); // array containing single character byte sequences
1821
		$countbytes = 0;
1822
		$numbytes = 1; // number of octetc needed to represent the UTF-8 character
1823
		$length = strlen($uch);
1824
		for ($i = 0; $i < $length; ++$i) {
1825
			$char = ord($uch[$i]); // get one string character at time
1826
			if ($countbytes == 0) { // get starting octect
1827
				if ($char <= 0x7F) {
1828
					return $char; // use the character "as is" because is ASCII
1829 View Code Duplication
				} elseif (($char >> 0x05) == 0x06) { // 2 bytes character (0x06 = 110 BIN)
1830
					$bytes[] = ($char - 0xC0) << 0x06;
1831
					++$countbytes;
1832
					$numbytes = 2;
1833
				} elseif (($char >> 0x04) == 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
1834
					$bytes[] = ($char - 0xE0) << 0x0C;
1835
					++$countbytes;
1836
					$numbytes = 3;
1837 View Code Duplication
				} elseif (($char >> 0x03) == 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
1838
					$bytes[] = ($char - 0xF0) << 0x12;
1839
					++$countbytes;
1840
					$numbytes = 4;
1841
				} else {
1842
					// use replacement character for other invalid sequences
1843
					return 0xFFFD;
1844
				}
1845
			} elseif (($char >> 0x06) == 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
1846
				$bytes[] = $char - 0x80;
1847
				++$countbytes;
1848
				if ($countbytes == $numbytes) {
1849
					// compose UTF-8 bytes to a single unicode value
1850
					$char = $bytes[0];
1851
					for ($j = 1; $j < $numbytes; ++$j) {
1852
						$char += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
1853
					}
1854
					if ((($char >= 0xD800) AND ($char <= 0xDFFF)) OR ($char >= 0x10FFFF)) {
1855
						// The definition of UTF-8 prohibits encoding character numbers between
1856
						// U+D800 and U+DFFF, which are reserved for use with the UTF-16
1857
						// encoding form (as surrogate pairs) and do not directly represent
1858
						// characters.
1859
						return 0xFFFD; // use replacement character
1860
					} else {
1861
						return $char;
1862
					}
1863
				}
1864
			} else {
1865
				// use replacement character for other invalid sequences
1866
				return 0xFFFD;
1867
			}
1868
		}
1869
		return 0xFFFD;
1870
	}
1871
1872
	/**
1873
	 * Converts UTF-8 strings to codepoints array.<br>
1874
	 * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
1875
	 * @param $str (string) string to process.
1876
	 * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise.
1877
	 * @param $currentfont (array) Reference to current font array.
1878
	 * @return array containing codepoints (UTF-8 characters values)
1879
	 * @author Nicola Asuni
1880
	 * @public static
1881
	 */
1882
	public static function UTF8StringToArray($str, $isunicode=true, &$currentfont) {
1883
		if ($isunicode) {
1884
			// requires PCRE unicode support turned on
1885
			$chars = TCPDF_STATIC::pregSplit('//','u', $str, -1, PREG_SPLIT_NO_EMPTY);
1886
			$carr = array_map(array('TCPDF_FONTS', 'uniord'), $chars);
1887
		} else {
1888
			$chars = str_split($str);
1889
			$carr = array_map('ord', $chars);
1890
		}
1891
		$currentfont['subsetchars'] += array_fill_keys($carr, true);
1892
		return $carr;
1893
	}
1894
1895
	/**
1896
	 * Converts UTF-8 strings to Latin1 when using the standard 14 core fonts.<br>
1897
	 * @param $str (string) string to process.
1898
	 * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise.
1899
	 * @param $currentfont (array) Reference to current font array.
1900
	 * @return string
1901
	 * @since 3.2.000 (2008-06-23)
1902
	 * @public static
1903
	 */
1904
	public static function UTF8ToLatin1($str, $isunicode=true, &$currentfont) {
1905
		$unicode = self::UTF8StringToArray($str, $isunicode, $currentfont); // array containing UTF-8 unicode values
1906
		return self::UTF8ArrToLatin1($unicode);
1907
	}
1908
1909
	/**
1910
	 * Converts UTF-8 strings to UTF16-BE.<br>
1911
	 * @param $str (string) string to process.
1912
	 * @param $setbom (boolean) if true set the Byte Order Mark (BOM = 0xFEFF)
1913
	 * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise.
1914
	 * @param $currentfont (array) Reference to current font array.
1915
	 * @return string
1916
	 * @author Nicola Asuni
1917
	 * @since 1.53.0.TC005 (2005-01-05)
1918
	 * @public static
1919
	 */
1920
	public static function UTF8ToUTF16BE($str, $setbom=false, $isunicode=true, &$currentfont) {
1921
		if (!$isunicode) {
1922
			return $str; // string is not in unicode
1923
		}
1924
		$unicode = self::UTF8StringToArray($str, $isunicode, $currentfont); // array containing UTF-8 unicode values
1925
		return self::arrUTF8ToUTF16BE($unicode, $setbom);
1926
	}
1927
1928
	/**
1929
	 * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
1930
	 * @param $str (string) string to manipulate.
1931
	 * @param $setbom (bool) if true set the Byte Order Mark (BOM = 0xFEFF)
1932
	 * @param $forcertl (bool) if true forces RTL text direction
1933
	 * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
1934
	 * @param $currentfont (array) Reference to current font array.
1935
	 * @return string
1936
	 * @author Nicola Asuni
1937
	 * @since 2.1.000 (2008-01-08)
1938
	 * @public static
1939
	 */
1940
	public static function utf8StrRev($str, $setbom=false, $forcertl=false, $isunicode=true, &$currentfont) {
1941
		return self::utf8StrArrRev(self::UTF8StringToArray($str, $isunicode, $currentfont), $str, $setbom, $forcertl, $isunicode, $currentfont);
1942
	}
1943
1944
	/**
1945
	 * Reverse the RLT substrings array using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
1946
	 * @param $arr (array) array of unicode values.
1947
	 * @param $str (string) string to manipulate (or empty value).
1948
	 * @param $setbom (bool) if true set the Byte Order Mark (BOM = 0xFEFF)
1949
	 * @param $forcertl (bool) if true forces RTL text direction
1950
	 * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
1951
	 * @param $currentfont (array) Reference to current font array.
1952
	 * @return string
1953
	 * @author Nicola Asuni
1954
	 * @since 4.9.000 (2010-03-27)
1955
	 * @public static
1956
	 */
1957
	public static function utf8StrArrRev($arr, $str='', $setbom=false, $forcertl=false, $isunicode=true, &$currentfont) {
1958
		return self::arrUTF8ToUTF16BE(self::utf8Bidi($arr, $str, $forcertl, $isunicode, $currentfont), $setbom);
1959
	}
1960
1961
	/**
1962
	 * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
1963
	 * @param $ta (array) array of characters composing the string.
1964
	 * @param $str (string) string to process
1965
	 * @param $forcertl (bool) if 'R' forces RTL, if 'L' forces LTR
1966
	 * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
1967
	 * @param $currentfont (array) Reference to current font array.
1968
	 * @return array of unicode chars
1969
	 * @author Nicola Asuni
1970
	 * @since 2.4.000 (2008-03-06)
1971
	 * @public static
1972
	 */
1973
	public static function utf8Bidi($ta, $str='', $forcertl=false, $isunicode=true, &$currentfont) {
1974
		// paragraph embedding level
1975
		$pel = 0;
1976
		// max level
1977
		$maxlevel = 0;
1978
		if (TCPDF_STATIC::empty_string($str)) {
1979
			// create string from array
1980
			$str = self::UTF8ArrSubString($ta, '', '', $isunicode);
1981
		}
1982
		// check if string contains arabic text
1983
		if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $str)) {
1984
			$arabic = true;
1985
		} else {
1986
			$arabic = false;
1987
		}
1988
		// check if string contains RTL text
1989
		if (!($forcertl OR $arabic OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $str))) {
1990
			return $ta;
1991
		}
1992
1993
		// get number of chars
1994
		$numchars = count($ta);
1995
1996
		if ($forcertl == 'R') {
1997
			$pel = 1;
1998
		} elseif ($forcertl == 'L') {
1999
			$pel = 0;
2000
		} else {
2001
			// P2. In each paragraph, find the first character of type L, AL, or R.
2002
			// 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.
2003
			for ($i=0; $i < $numchars; ++$i) {
2004
				$type = TCPDF_FONT_DATA::$uni_type[$ta[$i]];
2005
				if ($type == 'L') {
2006
					$pel = 0;
2007
					break;
2008
				} elseif (($type == 'AL') OR ($type == 'R')) {
2009
					$pel = 1;
2010
					break;
2011
				}
2012
			}
2013
		}
2014
2015
		// Current Embedding Level
2016
		$cel = $pel;
2017
		// directional override status
2018
		$dos = 'N';
2019
		$remember = array();
2020
		// start-of-level-run
2021
		$sor = $pel % 2 ? 'R' : 'L';
2022
		$eor = $sor;
2023
2024
		// Array of characters data
2025
		$chardata = Array();
2026
2027
		// 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.
2028
		// In the resolution of levels in rules I1 and I2, the maximum embedding level of 62 can be reached.
2029
		for ($i=0; $i < $numchars; ++$i) {
2030
			if ($ta[$i] == TCPDF_FONT_DATA::$uni_RLE) {
2031
				// X2. With each RLE, compute the least greater odd embedding level.
2032
				//	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.
2033
				//	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2034
				$next_level = $cel + ($cel % 2) + 1;
2035
				if ($next_level < 62) {
2036
					$remember[] = array('num' => TCPDF_FONT_DATA::$uni_RLE, 'cel' => $cel, 'dos' => $dos);
2037
					$cel = $next_level;
2038
					$dos = 'N';
2039
					$sor = $eor;
2040
					$eor = $cel % 2 ? 'R' : 'L';
2041
				}
2042 View Code Duplication
			} elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_LRE) {
2043
				// X3. With each LRE, compute the least greater even embedding level.
2044
				//	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.
2045
				//	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2046
				$next_level = $cel + 2 - ($cel % 2);
2047
				if ( $next_level < 62 ) {
2048
					$remember[] = array('num' => TCPDF_FONT_DATA::$uni_LRE, 'cel' => $cel, 'dos' => $dos);
2049
					$cel = $next_level;
2050
					$dos = 'N';
2051
					$sor = $eor;
2052
					$eor = $cel % 2 ? 'R' : 'L';
2053
				}
2054
			} elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_RLO) {
2055
				// X4. With each RLO, compute the least greater odd embedding level.
2056
				//	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.
2057
				//	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2058
				$next_level = $cel + ($cel % 2) + 1;
2059
				if ($next_level < 62) {
2060
					$remember[] = array('num' => TCPDF_FONT_DATA::$uni_RLO, 'cel' => $cel, 'dos' => $dos);
2061
					$cel = $next_level;
2062
					$dos = 'R';
2063
					$sor = $eor;
2064
					$eor = $cel % 2 ? 'R' : 'L';
2065
				}
2066 View Code Duplication
			} elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_LRO) {
2067
				// X5. With each LRO, compute the least greater even embedding level.
2068
				//	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.
2069
				//	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2070
				$next_level = $cel + 2 - ($cel % 2);
2071
				if ( $next_level < 62 ) {
2072
					$remember[] = array('num' => TCPDF_FONT_DATA::$uni_LRO, 'cel' => $cel, 'dos' => $dos);
2073
					$cel = $next_level;
2074
					$dos = 'L';
2075
					$sor = $eor;
2076
					$eor = $cel % 2 ? 'R' : 'L';
2077
				}
2078
			} elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_PDF) {
2079
				// 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.
2080
				if (count($remember)) {
2081
					$last = count($remember ) - 1;
2082
					if (($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_RLE) OR
2083
						($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_LRE) OR
2084
						($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_RLO) OR
2085
						($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_LRO)) {
2086
						$match = array_pop($remember);
2087
						$cel = $match['cel'];
2088
						$dos = $match['dos'];
2089
						$sor = $eor;
2090
						$eor = ($cel > $match['cel'] ? $cel : $match['cel']) % 2 ? 'R' : 'L';
2091
					}
2092
				}
2093
			} elseif (($ta[$i] != TCPDF_FONT_DATA::$uni_RLE) AND
2094
							 ($ta[$i] != TCPDF_FONT_DATA::$uni_LRE) AND
2095
							 ($ta[$i] != TCPDF_FONT_DATA::$uni_RLO) AND
2096
							 ($ta[$i] != TCPDF_FONT_DATA::$uni_LRO) AND
2097
							 ($ta[$i] != TCPDF_FONT_DATA::$uni_PDF)) {
2098
				// X6. For all types besides RLE, LRE, RLO, LRO, and PDF:
2099
				//	a. Set the level of the current character to the current embedding level.
2100
				//	b. Whenever the directional override status is not neutral, reset the current character type to the directional override status.
2101
				if ($dos != 'N') {
2102
					$chardir = $dos;
2103
				} else {
2104
					if (isset(TCPDF_FONT_DATA::$uni_type[$ta[$i]])) {
2105
						$chardir = TCPDF_FONT_DATA::$uni_type[$ta[$i]];
2106
					} else {
2107
						$chardir = 'L';
2108
					}
2109
				}
2110
				// stores string characters and other information
2111
				$chardata[] = array('char' => $ta[$i], 'level' => $cel, 'type' => $chardir, 'sor' => $sor, 'eor' => $eor);
2112
			}
2113
		} // end for each char
2114
2115
		// X8. All explicit directional embeddings and overrides are completely terminated at the end of each paragraph. Paragraph separators are not included in the embedding.
2116
		// X9. Remove all RLE, LRE, RLO, LRO, PDF, and BN codes.
2117
		// 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.
2118
2119
		// 3.3.3 Resolving Weak Types
2120
		// 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.
2121
		// Nonspacing marks are now resolved based on the previous characters.
2122
		$numchars = count($chardata);
2123
2124
		// 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.
2125
		$prevlevel = -1; // track level changes
2126
		$levcount = 0; // counts consecutive chars at the same level
2127
		for ($i=0; $i < $numchars; ++$i) {
2128
			if ($chardata[$i]['type'] == 'NSM') {
2129
				if ($levcount) {
2130
					$chardata[$i]['type'] = $chardata[$i]['sor'];
2131 View Code Duplication
				} elseif ($i > 0) {
2132
					$chardata[$i]['type'] = $chardata[($i-1)]['type'];
2133
				}
2134
			}
2135
			if ($chardata[$i]['level'] != $prevlevel) {
2136
				$levcount = 0;
2137
			} else {
2138
				++$levcount;
2139
			}
2140
			$prevlevel = $chardata[$i]['level'];
2141
		}
2142
2143
		// 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.
2144
		$prevlevel = -1;
2145
		$levcount = 0;
2146
		for ($i=0; $i < $numchars; ++$i) {
2147
			if ($chardata[$i]['char'] == 'EN') {
2148
				for ($j=$levcount; $j >= 0; $j--) {
2149 View Code Duplication
					if ($chardata[$j]['type'] == 'AL') {
2150
						$chardata[$i]['type'] = 'AN';
2151
					} elseif (($chardata[$j]['type'] == 'L') OR ($chardata[$j]['type'] == 'R')) {
2152
						break;
2153
					}
2154
				}
2155
			}
2156
			if ($chardata[$i]['level'] != $prevlevel) {
2157
				$levcount = 0;
2158
			} else {
2159
				++$levcount;
2160
			}
2161
			$prevlevel = $chardata[$i]['level'];
2162
		}
2163
2164
		// W3. Change all ALs to R.
2165
		for ($i=0; $i < $numchars; ++$i) {
2166
			if ($chardata[$i]['type'] == 'AL') {
2167
				$chardata[$i]['type'] = 'R';
2168
			}
2169
		}
2170
2171
		// 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.
2172
		$prevlevel = -1;
2173
		$levcount = 0;
2174
		for ($i=0; $i < $numchars; ++$i) {
2175
			if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
2176
				if (($chardata[$i]['type'] == 'ES') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
2177
					$chardata[$i]['type'] = 'EN';
2178
				} elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
2179
					$chardata[$i]['type'] = 'EN';
2180
				} elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'AN') AND ($chardata[($i+1)]['type'] == 'AN')) {
2181
					$chardata[$i]['type'] = 'AN';
2182
				}
2183
			}
2184
			if ($chardata[$i]['level'] != $prevlevel) {
2185
				$levcount = 0;
2186
			} else {
2187
				++$levcount;
2188
			}
2189
			$prevlevel = $chardata[$i]['level'];
2190
		}
2191
2192
		// W5. A sequence of European terminators adjacent to European numbers changes to all European numbers.
2193
		$prevlevel = -1;
2194
		$levcount = 0;
2195
		for ($i=0; $i < $numchars; ++$i) {
2196
			if ($chardata[$i]['type'] == 'ET') {
2197
				if (($levcount > 0) AND ($chardata[($i-1)]['type'] == 'EN')) {
2198
					$chardata[$i]['type'] = 'EN';
2199
				} else {
2200
					$j = $i+1;
2201
					while (($j < $numchars) AND ($chardata[$j]['level'] == $prevlevel)) {
2202 View Code Duplication
						if ($chardata[$j]['type'] == 'EN') {
2203
							$chardata[$i]['type'] = 'EN';
2204
							break;
2205
						} elseif ($chardata[$j]['type'] != 'ET') {
2206
							break;
2207
						}
2208
						++$j;
2209
					}
2210
				}
2211
			}
2212
			if ($chardata[$i]['level'] != $prevlevel) {
2213
				$levcount = 0;
2214
			} else {
2215
				++$levcount;
2216
			}
2217
			$prevlevel = $chardata[$i]['level'];
2218
		}
2219
2220
		// W6. Otherwise, separators and terminators change to Other Neutral.
2221
		$prevlevel = -1;
2222
		$levcount = 0;
2223
		for ($i=0; $i < $numchars; ++$i) {
2224
			if (($chardata[$i]['type'] == 'ET') OR ($chardata[$i]['type'] == 'ES') OR ($chardata[$i]['type'] == 'CS')) {
2225
				$chardata[$i]['type'] = 'ON';
2226
			}
2227
			if ($chardata[$i]['level'] != $prevlevel) {
2228
				$levcount = 0;
2229
			} else {
2230
				++$levcount;
2231
			}
2232
			$prevlevel = $chardata[$i]['level'];
2233
		}
2234
2235
		//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.
2236
		$prevlevel = -1;
2237
		$levcount = 0;
2238
		for ($i=0; $i < $numchars; ++$i) {
2239
			if ($chardata[$i]['char'] == 'EN') {
2240
				for ($j=$levcount; $j >= 0; $j--) {
2241 View Code Duplication
					if ($chardata[$j]['type'] == 'L') {
2242
						$chardata[$i]['type'] = 'L';
2243
					} elseif ($chardata[$j]['type'] == 'R') {
2244
						break;
2245
					}
2246
				}
2247
			}
2248
			if ($chardata[$i]['level'] != $prevlevel) {
2249
				$levcount = 0;
2250
			} else {
2251
				++$levcount;
2252
			}
2253
			$prevlevel = $chardata[$i]['level'];
2254
		}
2255
2256
		// 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.
2257
		$prevlevel = -1;
2258
		$levcount = 0;
2259
		for ($i=0; $i < $numchars; ++$i) {
2260
			if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
2261
				if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
2262
					$chardata[$i]['type'] = 'L';
2263
				} elseif (($chardata[$i]['type'] == 'N') AND
2264
				 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
2265
				 (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
2266
					$chardata[$i]['type'] = 'R';
2267 View Code Duplication
				} elseif ($chardata[$i]['type'] == 'N') {
2268
					// N2. Any remaining neutrals take the embedding direction
2269
					$chardata[$i]['type'] = $chardata[$i]['sor'];
2270
				}
2271
			} elseif (($levcount == 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
2272
				// first char
2273
				if (($chardata[$i]['type'] == 'N') AND ($chardata[$i]['sor'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
2274
					$chardata[$i]['type'] = 'L';
2275
				} elseif (($chardata[$i]['type'] == 'N') AND
2276
				 (($chardata[$i]['sor'] == 'R') OR ($chardata[$i]['sor'] == 'EN') OR ($chardata[$i]['sor'] == 'AN')) AND
2277
				 (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
2278
					$chardata[$i]['type'] = 'R';
2279 View Code Duplication
				} elseif ($chardata[$i]['type'] == 'N') {
2280
					// N2. Any remaining neutrals take the embedding direction
2281
					$chardata[$i]['type'] = $chardata[$i]['sor'];
2282
				}
2283
			} elseif (($levcount > 0) AND ((($i+1) == $numchars) OR (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] != $prevlevel))) {
2284
				//last char
2285
				if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[$i]['eor'] == 'L')) {
2286
					$chardata[$i]['type'] = 'L';
2287
				} elseif (($chardata[$i]['type'] == 'N') AND
2288
				 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
2289
				 (($chardata[$i]['eor'] == 'R') OR ($chardata[$i]['eor'] == 'EN') OR ($chardata[$i]['eor'] == 'AN'))) {
2290
					$chardata[$i]['type'] = 'R';
2291 View Code Duplication
				} elseif ($chardata[$i]['type'] == 'N') {
2292
					// N2. Any remaining neutrals take the embedding direction
2293
					$chardata[$i]['type'] = $chardata[$i]['sor'];
2294
				}
2295 View Code Duplication
			} elseif ($chardata[$i]['type'] == 'N') {
2296
				// N2. Any remaining neutrals take the embedding direction
2297
				$chardata[$i]['type'] = $chardata[$i]['sor'];
2298
			}
2299
			if ($chardata[$i]['level'] != $prevlevel) {
2300
				$levcount = 0;
2301
			} else {
2302
				++$levcount;
2303
			}
2304
			$prevlevel = $chardata[$i]['level'];
2305
		}
2306
2307
		// 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.
2308
		// I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level.
2309
		for ($i=0; $i < $numchars; ++$i) {
2310
			$odd = $chardata[$i]['level'] % 2;
2311
			if ($odd) {
2312 View Code Duplication
				if (($chardata[$i]['type'] == 'L') OR ($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
2313
					$chardata[$i]['level'] += 1;
2314
				}
2315
			} else {
2316
				if ($chardata[$i]['type'] == 'R') {
2317
					$chardata[$i]['level'] += 1;
2318
				} elseif (($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
2319
					$chardata[$i]['level'] += 2;
2320
				}
2321
			}
2322
			$maxlevel = max($chardata[$i]['level'],$maxlevel);
2323
		}
2324
2325
		// L1. On each line, reset the embedding level of the following characters to the paragraph embedding level:
2326
		//	1. Segment separators,
2327
		//	2. Paragraph separators,
2328
		//	3. Any sequence of whitespace characters preceding a segment separator or paragraph separator, and
2329
		//	4. Any sequence of white space characters at the end of the line.
2330
		for ($i=0; $i < $numchars; ++$i) {
2331
			if (($chardata[$i]['type'] == 'B') OR ($chardata[$i]['type'] == 'S')) {
2332
				$chardata[$i]['level'] = $pel;
2333
			} elseif ($chardata[$i]['type'] == 'WS') {
2334
				$j = $i+1;
2335
				while ($j < $numchars) {
2336
					if ((($chardata[$j]['type'] == 'B') OR ($chardata[$j]['type'] == 'S')) OR
2337
						(($j == ($numchars-1)) AND ($chardata[$j]['type'] == 'WS'))) {
2338
						$chardata[$i]['level'] = $pel;
2339
						break;
2340
					} elseif ($chardata[$j]['type'] != 'WS') {
2341
						break;
2342
					}
2343
					++$j;
2344
				}
2345
			}
2346
		}
2347
2348
		// Arabic Shaping
2349
		// 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.
2350
		if ($arabic) {
2351
			$endedletter = array(1569,1570,1571,1572,1573,1575,1577,1583,1584,1585,1586,1608,1688);
2352
			$alfletter = array(1570,1571,1573,1575);
2353
			$chardata2 = $chardata;
2354
			$laaletter = false;
2355
			$charAL = array();
2356
			$x = 0;
2357
			for ($i=0; $i < $numchars; ++$i) {
2358
				if ((TCPDF_FONT_DATA::$uni_type[$chardata[$i]['char']] == 'AL') OR ($chardata[$i]['char'] == 32) OR ($chardata[$i]['char'] == 8204)) {
2359
					$charAL[$x] = $chardata[$i];
2360
					$charAL[$x]['i'] = $i;
2361
					$chardata[$i]['x'] = $x;
2362
					++$x;
2363
				}
2364
			}
2365
			$numAL = $x;
2366
			for ($i=0; $i < $numchars; ++$i) {
2367
				$thischar = $chardata[$i];
2368
				if ($i > 0) {
2369
					$prevchar = $chardata[($i-1)];
2370
				} else {
2371
					$prevchar = false;
2372
				}
2373
				if (($i+1) < $numchars) {
2374
					$nextchar = $chardata[($i+1)];
2375
				} else {
2376
					$nextchar = false;
2377
				}
2378
				if (TCPDF_FONT_DATA::$uni_type[$thischar['char']] == 'AL') {
2379
					$x = $thischar['x'];
2380 View Code Duplication
					if ($x > 0) {
2381
						$prevchar = $charAL[($x-1)];
2382
					} else {
2383
						$prevchar = false;
2384
					}
2385
					if (($x+1) < $numAL) {
2386
						$nextchar = $charAL[($x+1)];
2387
					} else {
2388
						$nextchar = false;
2389
					}
2390
					// if laa letter
2391
					if (($prevchar !== false) AND ($prevchar['char'] == 1604) AND (in_array($thischar['char'], $alfletter))) {
2392
						$arabicarr = TCPDF_FONT_DATA::$uni_laa_array;
2393
						$laaletter = true;
2394 View Code Duplication
						if ($x > 1) {
2395
							$prevchar = $charAL[($x-2)];
2396
						} else {
2397
							$prevchar = false;
2398
						}
2399
					} else {
2400
						$arabicarr = TCPDF_FONT_DATA::$uni_arabicsubst;
2401
						$laaletter = false;
2402
					}
2403
					if (($prevchar !== false) AND ($nextchar !== false) AND
2404
						((TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'NSM')) AND
2405
						((TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'NSM')) AND
2406
						($prevchar['type'] == $thischar['type']) AND
2407
						($nextchar['type'] == $thischar['type']) AND
2408
						($nextchar['char'] != 1567)) {
2409 View Code Duplication
						if (in_array($prevchar['char'], $endedletter)) {
2410
							if (isset($arabicarr[$thischar['char']][2])) {
2411
								// initial
2412
								$chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
2413
							}
2414
						} else {
2415
							if (isset($arabicarr[$thischar['char']][3])) {
2416
								// medial
2417
								$chardata2[$i]['char'] = $arabicarr[$thischar['char']][3];
2418
							}
2419
						}
2420
					} elseif (($nextchar !== false) AND
2421
						((TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'NSM')) AND
2422
						($nextchar['type'] == $thischar['type']) AND
2423
						($nextchar['char'] != 1567)) {
2424
						if (isset($arabicarr[$chardata[$i]['char']][2])) {
2425
							// initial
2426
							$chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
2427
						}
2428
					} elseif ((($prevchar !== false) AND
2429
						((TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'NSM')) AND
2430
						($prevchar['type'] == $thischar['type'])) OR
2431
						(($nextchar !== false) AND ($nextchar['char'] == 1567))) {
2432
						// final
2433
						if (($i > 1) AND ($thischar['char'] == 1607) AND
2434
							($chardata[$i-1]['char'] == 1604) AND
2435
							($chardata[$i-2]['char'] == 1604)) {
2436
							//Allah Word
2437
							// mark characters to delete with false
2438
							$chardata2[$i-2]['char'] = false;
2439
							$chardata2[$i-1]['char'] = false;
2440
							$chardata2[$i]['char'] = 65010;
2441 View Code Duplication
						} else {
2442
							if (($prevchar !== false) AND in_array($prevchar['char'], $endedletter)) {
2443
								if (isset($arabicarr[$thischar['char']][0])) {
2444
									// isolated
2445
									$chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
2446
								}
2447
							} else {
2448
								if (isset($arabicarr[$thischar['char']][1])) {
2449
									// final
2450
									$chardata2[$i]['char'] = $arabicarr[$thischar['char']][1];
2451
								}
2452
							}
2453
						}
2454
					} elseif (isset($arabicarr[$thischar['char']][0])) {
2455
						// isolated
2456
						$chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
2457
					}
2458
					// if laa letter
2459
					if ($laaletter) {
2460
						// mark characters to delete with false
2461
						$chardata2[($charAL[($x-1)]['i'])]['char'] = false;
2462
					}
2463
				} // end if AL (Arabic Letter)
2464
			} // end for each char
2465
			/*
2466
			 * Combining characters that can occur with Arabic Shadda (0651 HEX, 1617 DEC) are replaced.
2467
			 * Putting the combining mark and shadda in the same glyph allows us to avoid the two marks overlapping each other in an illegible manner.
2468
			 */
2469
			for ($i = 0; $i < ($numchars-1); ++$i) {
2470
				if (($chardata2[$i]['char'] == 1617) AND (isset(TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])]))) {
2471
					// check if the subtitution font is defined on current font
2472
					if (isset($currentfont['cw'][(TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])])])) {
2473
						$chardata2[$i]['char'] = false;
2474
						$chardata2[$i+1]['char'] = TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])];
2475
					}
2476
				}
2477
			}
2478
			// remove marked characters
2479
			foreach ($chardata2 as $key => $value) {
2480
				if ($value['char'] === false) {
2481
					unset($chardata2[$key]);
2482
				}
2483
			}
2484
			$chardata = array_values($chardata2);
2485
			$numchars = count($chardata);
2486
			unset($chardata2);
2487
			unset($arabicarr);
2488
			unset($laaletter);
2489
			unset($charAL);
2490
		}
2491
2492
		// 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.
2493
		for ($j=$maxlevel; $j > 0; $j--) {
2494
			$ordarray = Array();
2495
			$revarr = Array();
2496
			$onlevel = false;
2497
			for ($i=0; $i < $numchars; ++$i) {
2498
				if ($chardata[$i]['level'] >= $j) {
2499
					$onlevel = true;
2500
					if (isset(TCPDF_FONT_DATA::$uni_mirror[$chardata[$i]['char']])) {
2501
						// 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.
2502
						$chardata[$i]['char'] = TCPDF_FONT_DATA::$uni_mirror[$chardata[$i]['char']];
2503
					}
2504
					$revarr[] = $chardata[$i];
2505
				} else {
2506
					if ($onlevel) {
2507
						$revarr = array_reverse($revarr);
2508
						$ordarray = array_merge($ordarray, $revarr);
2509
						$revarr = Array();
2510
						$onlevel = false;
2511
					}
2512
					$ordarray[] = $chardata[$i];
2513
				}
2514
			}
2515
			if ($onlevel) {
2516
				$revarr = array_reverse($revarr);
2517
				$ordarray = array_merge($ordarray, $revarr);
2518
			}
2519
			$chardata = $ordarray;
2520
		}
2521
		$ordarray = array();
2522
		foreach ($chardata as $cd) {
2523
			$ordarray[] = $cd['char'];
2524
			// store char values for subsetting
2525
			$currentfont['subsetchars'][$cd['char']] = true;
2526
		}
2527
		return $ordarray;
2528
	}
2529
2530
	/**
2531
	 * Get a reference font size.
2532
	 * @param $size (string) String containing font size value.
2533
	 * @param $refsize (float) Reference font size in points.
2534
	 * @return float value in points
2535
	 * @public static
2536
	 */
2537
	public static function getFontRefSize($size, $refsize=12) {
2538
		switch ($size) {
2539
			case 'xx-small': {
2540
				$size = ($refsize - 4);
2541
				break;
2542
			}
2543
			case 'x-small': {
2544
				$size = ($refsize - 3);
2545
				break;
2546
			}
2547
			case 'small': {
2548
				$size = ($refsize - 2);
2549
				break;
2550
			}
2551
			case 'medium': {
2552
				$size = $refsize;
2553
				break;
2554
			}
2555
			case 'large': {
2556
				$size = ($refsize + 2);
2557
				break;
2558
			}
2559
			case 'x-large': {
2560
				$size = ($refsize + 4);
2561
				break;
2562
			}
2563
			case 'xx-large': {
2564
				$size = ($refsize + 6);
2565
				break;
2566
			}
2567
			case 'smaller': {
2568
				$size = ($refsize - 3);
2569
				break;
2570
			}
2571
			case 'larger': {
2572
				$size = ($refsize + 3);
2573
				break;
2574
			}
2575
		}
2576
		return $size;
2577
	}
2578
2579
} // END OF TCPDF_FONTS CLASS
2580
2581
//============================================================+
2582
// END OF FILE
2583
//============================================================+
2584