[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

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

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


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1