Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

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