[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/tcpdf/include/ -> tcpdf_fonts.php (source)

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


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1