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