Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   1  <?php
   2  //============================================================+
   3  // File name   : tcpdf_fonts.php
   4  // Version     : 1.1.0
   5  // Begin       : 2008-01-01
   6  // Last Update : 2014-12-10
   7  // Author      : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
   8  // License     : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
   9  // -------------------------------------------------------------------
  10  // Copyright (C) 2008-2014 Nicola Asuni - Tecnick.com LTD
  11  //
  12  // This file is part of TCPDF software library.
  13  //
  14  // TCPDF is free software: you can redistribute it and/or modify it
  15  // under the terms of the GNU Lesser General Public License as
  16  // published by the Free Software Foundation, either version 3 of the
  17  // License, or (at your option) any later version.
  18  //
  19  // TCPDF is distributed in the hope that it will be useful, but
  20  // WITHOUT ANY WARRANTY; without even the implied warranty of
  21  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  22  // See the GNU Lesser General Public License for more details.
  23  //
  24  // You should have received a copy of the GNU Lesser General Public License
  25  // along with TCPDF.  If not, see <http://www.gnu.org/licenses/>.
  26  //
  27  // See LICENSE.TXT file for more information.
  28  // -------------------------------------------------------------------
  29  //
  30  // Description :Font methods for TCPDF library.
  31  //
  32  //============================================================+
  33  
  34  /**
  35   * @file
  36   * Unicode data and font methods for TCPDF library.
  37   * @author Nicola Asuni
  38   * @package com.tecnick.tcpdf
  39   */
  40  
  41  /**
  42   * @class TCPDF_FONTS
  43   * Font methods for TCPDF library.
  44   * @package com.tecnick.tcpdf
  45   * @version 1.1.0
  46   * @author Nicola Asuni - info@tecnick.com
  47   */
  48  class TCPDF_FONTS {
  49  
  50  	 /**
  51  	  * Static cache used for speed up uniord performances
  52  	  * @protected
  53  	  */
  54  	 protected static $cache_uniord = array();
  55  
  56  	 /**
  57  	  * Convert and add the selected TrueType or Type1 font to the fonts folder (that must be writeable).
  58  	  * @param $fontfile (string) Font file (full path).
  59  	  * @param $fonttype (string) Font type. Leave empty for autodetect mode. Valid values are: TrueTypeUnicode, TrueType, Type1, CID0JP = CID-0 Japanese, CID0KR = CID-0 Korean, CID0CS = CID-0 Chinese Simplified, CID0CT = CID-0 Chinese Traditional.
  60  	  * @param $enc (string) Name of the encoding table to use. Leave empty for default mode. Omit this parameter for TrueType Unicode and symbolic fonts like Symbol or ZapfDingBats.
  61  	  * @param $flags (int) Unsigned 32-bit integer containing flags specifying various characteristics of the font (PDF32000:2008 - 9.8.2 Font Descriptor Flags): +1 for fixed font; +4 for symbol or +32 for non-symbol; +64 for italic. Fixed and Italic mode are generally autodetected so you have to set it to 32 = non-symbolic font (default) or 4 = symbolic font.
  62  	  * @param $outpath (string) Output path for generated font files (must be writeable by the web server). Leave empty for default font folder.
  63  	  * @param $platid (int) Platform ID for CMAP table to extract (when building a Unicode font for Windows this value should be 3, for Macintosh should be 1).
  64  	  * @param $encid (int) Encoding ID for CMAP table to extract (when building a Unicode font for Windows this value should be 1, for Macintosh should be 0). When Platform ID is 3, legal values for Encoding ID are: 0=Symbol, 1=Unicode, 2=ShiftJIS, 3=PRC, 4=Big5, 5=Wansung, 6=Johab, 7=Reserved, 8=Reserved, 9=Reserved, 10=UCS-4.
  65  	  * @param $addcbbox (boolean) If true includes the character bounding box information on the php font file.
  66  	  * @param $link (boolean) If true link to system font instead of copying the font data (not transportable) - Note: do not work with Type1 fonts.
  67  	  * @return (string) TCPDF font name or boolean false in case of error.
  68  	  * @author Nicola Asuni
  69  	  * @since 5.9.123 (2010-09-30)
  70  	  * @public static
  71  	  */
  72  	public static function addTTFfont($fontfile, $fonttype='', $enc='', $flags=32, $outpath='', $platid=3, $encid=1, $addcbbox=false, $link=false) {
  73  	 	 if (!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  //============================================================+