Esempio n. 1
0
 /**
  * Convert and add the selected TrueType or Type1 font to the fonts folder (that must be writeable).
  * @param $fontfile (string) Font file (full path).
  * @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.
  * @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.
  * @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.
  * @param $outpath (string) Output path for generated font files (must be writeable by the web server). Leave empty for default font folder.
  * @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).
  * @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.
  * @param $addcbbox (boolean) If true includes the character bounding box information on the php font file.
  * @param $link (boolean) If true link to system font instead of copying the font data (not transportable) - Note: do not work with Type1 fonts.
  * @return (string) TCPDF font name or boolean false in case of error.
  * @author Nicola Asuni
  * @since 5.9.123 (2010-09-30)
  * @public static
  */
 public static function addTTFfont($fontfile, $fonttype = '', $enc = '', $flags = 32, $outpath = '', $platid = 3, $encid = 1, $addcbbox = false, $link = false)
 {
     if (!file_exists($fontfile)) {
         // Could not find file
         return false;
     }
     // font metrics
     $fmetric = array();
     // build new font name for TCPDF compatibility
     $font_path_parts = pathinfo($fontfile);
     if (!isset($font_path_parts['filename'])) {
         $font_path_parts['filename'] = substr($font_path_parts['basename'], 0, -(strlen($font_path_parts['extension']) + 1));
     }
     $font_name = strtolower($font_path_parts['filename']);
     $font_name = preg_replace('/[^a-z0-9_]/', '', $font_name);
     $search = array('bold', 'oblique', 'italic', 'regular');
     $replace = array('b', 'i', 'i', '');
     $font_name = str_replace($search, $replace, $font_name);
     if (empty($font_name)) {
         // set generic name
         $font_name = 'tcpdffont';
     }
     // set output path
     if (empty($outpath)) {
         $outpath = self::_getfontpath();
     }
     // check if this font already exist
     if (@file_exists($outpath . $font_name . '.php')) {
         // this font already exist (delete it from fonts folder to rebuild it)
         return $font_name;
     }
     $fmetric['file'] = $font_name;
     $fmetric['ctg'] = $font_name . '.ctg.z';
     // get font data
     $font = file_get_contents($fontfile);
     $fmetric['originalsize'] = strlen($font);
     // autodetect font type
     if (empty($fonttype)) {
         if (TcpdfStatic::_getULONG($font, 0) == 0x10000) {
             // True Type (Unicode or not)
             $fonttype = 'TrueTypeUnicode';
         } elseif (substr($font, 0, 4) == 'OTTO') {
             // Open Type (Unicode or not)
             //Unsupported font format: OpenType with CFF data
             return false;
         } else {
             // Type 1
             $fonttype = 'Type1';
         }
     }
     // set font type
     switch ($fonttype) {
         case 'CID0CT':
         case 'CID0CS':
         case 'CID0KR':
         case 'CID0JP':
             $fmetric['type'] = 'cidfont0';
             break;
         case 'Type1':
             $fmetric['type'] = 'Type1';
             if (empty($enc) and ($flags & 4) == 0) {
                 $enc = 'cp1252';
             }
             break;
         case 'TrueType':
             $fmetric['type'] = 'TrueType';
             break;
         case 'TrueTypeUnicode':
         default:
             $fmetric['type'] = 'TrueTypeUnicode';
             break;
     }
     // set encoding maps (if any)
     $fmetric['enc'] = preg_replace('/[^A-Za-z0-9_\\-]/', '', $enc);
     $fmetric['diff'] = '';
     if ($fmetric['type'] == 'TrueType' or $fmetric['type'] == 'Type1') {
         if (!empty($enc) and $enc != 'cp1252' and isset(TcpdfFontData::$encmap[$enc])) {
             // build differences from reference encoding
             $enc_ref = TcpdfFontData::$encmap['cp1252'];
             $enc_target = TcpdfFontData::$encmap[$enc];
             $last = 0;
             for ($i = 32; $i <= 255; ++$i) {
                 if ($enc_target != $enc_ref[$i]) {
                     if ($i != $last + 1) {
                         $fmetric['diff'] .= $i . ' ';
                     }
                     $last = $i;
                     $fmetric['diff'] .= '/' . $enc_target[$i] . ' ';
                 }
             }
         }
     }
     // parse the font by type
     if ($fmetric['type'] == 'Type1') {
         // ---------- TYPE 1 ----------
         // read first segment
         $a = unpack('Cmarker/Ctype/Vsize', substr($font, 0, 6));
         if ($a['marker'] != 128) {
             // Font file is not a valid binary Type1
             return false;
         }
         $fmetric['size1'] = $a['size'];
         $data = substr($font, 6, $fmetric['size1']);
         // read second segment
         $a = unpack('Cmarker/Ctype/Vsize', substr($font, 6 + $fmetric['size1'], 6));
         if ($a['marker'] != 128) {
             // Font file is not a valid binary Type1
             return false;
         }
         $fmetric['size2'] = $a['size'];
         $encrypted = substr($font, 12 + $fmetric['size1'], $fmetric['size2']);
         $data .= $encrypted;
         // store compressed font
         $fmetric['file'] .= '.z';
         $fp = fopen($outpath . $fmetric['file'], 'wb');
         fwrite($fp, gzcompress($data));
         fclose($fp);
         // get font info
         $fmetric['Flags'] = $flags;
         preg_match('#/FullName[\\s]*\\(([^\\)]*)#', $font, $matches);
         $fmetric['name'] = preg_replace('/[^a-zA-Z0-9_\\-]/', '', $matches[1]);
         preg_match('#/FontBBox[\\s]*{([^}]*)#', $font, $matches);
         $fmetric['bbox'] = trim($matches[1]);
         $bv = explode(' ', $fmetric['bbox']);
         $fmetric['Ascent'] = intval($bv[3]);
         $fmetric['Descent'] = intval($bv[1]);
         preg_match('#/ItalicAngle[\\s]*([0-9\\+\\-]*)#', $font, $matches);
         $fmetric['italicAngle'] = intval($matches[1]);
         if ($fmetric['italicAngle'] != 0) {
             $fmetric['Flags'] |= 64;
         }
         preg_match('#/UnderlinePosition[\\s]*([0-9\\+\\-]*)#', $font, $matches);
         $fmetric['underlinePosition'] = intval($matches[1]);
         preg_match('#/UnderlineThickness[\\s]*([0-9\\+\\-]*)#', $font, $matches);
         $fmetric['underlineThickness'] = intval($matches[1]);
         preg_match('#/isFixedPitch[\\s]*([^\\s]*)#', $font, $matches);
         if ($matches[1] == 'true') {
             $fmetric['Flags'] |= 1;
         }
         // get internal map
         $imap = array();
         if (preg_match_all('#dup[\\s]([0-9]+)[\\s]*/([^\\s]*)[\\s]put#sU', $font, $fmap, PREG_SET_ORDER) > 0) {
             foreach ($fmap as $v) {
                 $imap[$v[2]] = $v[1];
             }
         }
         // decrypt eexec encrypted part
         $r = 55665;
         // eexec encryption constant
         $c1 = 52845;
         $c2 = 22719;
         $elen = strlen($encrypted);
         $eplain = '';
         for ($i = 0; $i < $elen; ++$i) {
             $chr = ord($encrypted[$i]);
             $eplain .= chr($chr ^ $r >> 8);
             $r = (($chr + $r) * $c1 + $c2) % 65536;
         }
         if (preg_match('#/ForceBold[\\s]*([^\\s]*)#', $eplain, $matches) > 0) {
             if ($matches[1] == 'true') {
                 $fmetric['Flags'] |= 0x40000;
             }
         }
         if (preg_match('#/StdVW[\\s]*\\[([^\\]]*)#', $eplain, $matches) > 0) {
             $fmetric['StemV'] = intval($matches[1]);
         } else {
             $fmetric['StemV'] = 70;
         }
         if (preg_match('#/StdHW[\\s]*\\[([^\\]]*)#', $eplain, $matches) > 0) {
             $fmetric['StemH'] = intval($matches[1]);
         } else {
             $fmetric['StemH'] = 30;
         }
         if (preg_match('#/BlueValues[\\s]*\\[([^\\]]*)#', $eplain, $matches) > 0) {
             $bv = explode(' ', $matches[1]);
             if (count($bv) >= 6) {
                 $v1 = intval($bv[2]);
                 $v2 = intval($bv[4]);
                 if ($v1 <= $v2) {
                     $fmetric['XHeight'] = $v1;
                     $fmetric['CapHeight'] = $v2;
                 } else {
                     $fmetric['XHeight'] = $v2;
                     $fmetric['CapHeight'] = $v1;
                 }
             } else {
                 $fmetric['XHeight'] = 450;
                 $fmetric['CapHeight'] = 700;
             }
         } else {
             $fmetric['XHeight'] = 450;
             $fmetric['CapHeight'] = 700;
         }
         // get the number of random bytes at the beginning of charstrings
         if (preg_match('#/lenIV[\\s]*([0-9]*)#', $eplain, $matches) > 0) {
             $lenIV = intval($matches[1]);
         } else {
             $lenIV = 4;
         }
         $fmetric['Leading'] = 0;
         // get charstring data
         $eplain = substr($eplain, strpos($eplain, '/CharStrings') + 1);
         preg_match_all('#/([A-Za-z0-9\\.]*)[\\s][0-9]+[\\s]RD[\\s](.*)[\\s]ND#sU', $eplain, $matches, PREG_SET_ORDER);
         if (!empty($enc) and isset(TcpdfFontData::$encmap[$enc])) {
             $enc_map = TcpdfFontData::$encmap[$enc];
         } else {
             $enc_map = false;
         }
         $fmetric['cw'] = '';
         $fmetric['MaxWidth'] = 0;
         $cwidths = array();
         foreach ($matches as $k => $v) {
             $cid = 0;
             if (isset($imap[$v[1]])) {
                 $cid = $imap[$v[1]];
             } elseif ($enc_map !== false) {
                 $cid = array_search($v[1], $enc_map);
                 if ($cid === false) {
                     $cid = 0;
                 } elseif ($cid > 1000) {
                     $cid -= 1000;
                 }
             }
             // decrypt charstring encrypted part
             $r = 4330;
             // charstring encryption constant
             $c1 = 52845;
             $c2 = 22719;
             $cd = $v[2];
             $clen = strlen($cd);
             $ccom = array();
             for ($i = 0; $i < $clen; ++$i) {
                 $chr = ord($cd[$i]);
                 $ccom[] = $chr ^ $r >> 8;
                 $r = (($chr + $r) * $c1 + $c2) % 65536;
             }
             // decode numbers
             $cdec = array();
             $ck = 0;
             $i = $lenIV;
             while ($i < $clen) {
                 if ($ccom[$i] < 32) {
                     $cdec[$ck] = $ccom[$i];
                     if ($ck > 0 and $cdec[$ck] == 13) {
                         // hsbw command: update width
                         $cwidths[$cid] = $cdec[$ck - 1];
                     }
                     ++$i;
                 } elseif ($ccom[$i] >= 32 and $ccom[$i] <= 246) {
                     $cdec[$ck] = $ccom[$i] - 139;
                     ++$i;
                 } elseif ($ccom[$i] >= 247 and $ccom[$i] <= 250) {
                     $cdec[$ck] = ($ccom[$i] - 247) * 256 + $ccom[$i + 1] + 108;
                     $i += 2;
                 } elseif ($ccom[$i] >= 251 and $ccom[$i] <= 254) {
                     $cdec[$ck] = -($ccom[$i] - 251) * 256 - $ccom[$i + 1] - 108;
                     $i += 2;
                 } elseif ($ccom[$i] == 255) {
                     $sval = chr($ccom[$i + 1]) . chr($ccom[$i + 2]) . chr($ccom[$i + 3]) . chr($ccom[$i + 4]);
                     $vsval = unpack('li', $sval);
                     $cdec[$ck] = $vsval['i'];
                     $i += 5;
                 }
                 ++$ck;
             }
         }
         // end for each matches
         $fmetric['MissingWidth'] = $cwidths[0];
         $fmetric['MaxWidth'] = $fmetric['MissingWidth'];
         $fmetric['AvgWidth'] = 0;
         // set chars widths
         for ($cid = 0; $cid <= 255; ++$cid) {
             if (isset($cwidths[$cid])) {
                 if ($cwidths[$cid] > $fmetric['MaxWidth']) {
                     $fmetric['MaxWidth'] = $cwidths[$cid];
                 }
                 $fmetric['AvgWidth'] += $cwidths[$cid];
                 $fmetric['cw'] .= ',' . $cid . '=>' . $cwidths[$cid];
             } else {
                 $fmetric['cw'] .= ',' . $cid . '=>' . $fmetric['MissingWidth'];
             }
         }
         $fmetric['AvgWidth'] = round($fmetric['AvgWidth'] / count($cwidths));
     } else {
         // ---------- TRUE TYPE ----------
         if ($fmetric['type'] != 'cidfont0') {
             if ($link) {
                 // creates a symbolic link to the existing font
                 symlink($fontfile, $outpath . $fmetric['file']);
             } else {
                 // store compressed font
                 $fmetric['file'] .= '.z';
                 $fp = fopen($outpath . $fmetric['file'], 'wb');
                 fwrite($fp, gzcompress($font));
                 fclose($fp);
             }
         }
         $offset = 0;
         // offset position of the font data
         if (TcpdfStatic::_getULONG($font, $offset) != 0x10000) {
             // sfnt version must be 0x00010000 for TrueType version 1.0.
             return false;
         }
         $offset += 4;
         // get number of tables
         $numTables = TcpdfStatic::_getUSHORT($font, $offset);
         $offset += 2;
         // skip searchRange, entrySelector and rangeShift
         $offset += 6;
         // tables array
         $table = array();
         // ---------- get tables ----------
         for ($i = 0; $i < $numTables; ++$i) {
             // get table info
             $tag = substr($font, $offset, 4);
             $offset += 4;
             $table[$tag] = array();
             $table[$tag]['checkSum'] = TcpdfStatic::_getULONG($font, $offset);
             $offset += 4;
             $table[$tag]['offset'] = TcpdfStatic::_getULONG($font, $offset);
             $offset += 4;
             $table[$tag]['length'] = TcpdfStatic::_getULONG($font, $offset);
             $offset += 4;
         }
         // check magicNumber
         $offset = $table['head']['offset'] + 12;
         if (TcpdfStatic::_getULONG($font, $offset) != 0x5f0f3cf5) {
             // magicNumber must be 0x5F0F3CF5
             return false;
         }
         $offset += 4;
         $offset += 2;
         // skip flags
         // get FUnits
         $fmetric['unitsPerEm'] = TcpdfStatic::_getUSHORT($font, $offset);
         $offset += 2;
         // units ratio constant
         $urk = 1000 / $fmetric['unitsPerEm'];
         $offset += 16;
         // skip created, modified
         $xMin = round(TcpdfStatic::_getFWORD($font, $offset) * $urk);
         $offset += 2;
         $yMin = round(TcpdfStatic::_getFWORD($font, $offset) * $urk);
         $offset += 2;
         $xMax = round(TcpdfStatic::_getFWORD($font, $offset) * $urk);
         $offset += 2;
         $yMax = round(TcpdfStatic::_getFWORD($font, $offset) * $urk);
         $offset += 2;
         $fmetric['bbox'] = '' . $xMin . ' ' . $yMin . ' ' . $xMax . ' ' . $yMax . '';
         $macStyle = TcpdfStatic::_getUSHORT($font, $offset);
         $offset += 2;
         // PDF font flags
         $fmetric['Flags'] = $flags;
         if (($macStyle & 2) == 2) {
             // italic flag
             $fmetric['Flags'] |= 64;
         }
         // get offset mode (indexToLocFormat : 0 = short, 1 = long)
         $offset = $table['head']['offset'] + 50;
         $short_offset = TcpdfStatic::_getSHORT($font, $offset) == 0;
         $offset += 2;
         // get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
         $indexToLoc = array();
         $offset = $table['loca']['offset'];
         if ($short_offset) {
             // short version
             $tot_num_glyphs = floor($table['loca']['length'] / 2);
             // numGlyphs + 1
             for ($i = 0; $i < $tot_num_glyphs; ++$i) {
                 $indexToLoc[$i] = TcpdfStatic::_getUSHORT($font, $offset) * 2;
                 $offset += 2;
             }
         } else {
             // long version
             $tot_num_glyphs = floor($table['loca']['length'] / 4);
             // numGlyphs + 1
             for ($i = 0; $i < $tot_num_glyphs; ++$i) {
                 $indexToLoc[$i] = TcpdfStatic::_getULONG($font, $offset);
                 $offset += 4;
             }
         }
         // get glyphs indexes of chars from cmap table
         $offset = $table['cmap']['offset'] + 2;
         $numEncodingTables = TcpdfStatic::_getUSHORT($font, $offset);
         $offset += 2;
         $encodingTables = array();
         for ($i = 0; $i < $numEncodingTables; ++$i) {
             $encodingTables[$i]['platformID'] = TcpdfStatic::_getUSHORT($font, $offset);
             $offset += 2;
             $encodingTables[$i]['encodingID'] = TcpdfStatic::_getUSHORT($font, $offset);
             $offset += 2;
             $encodingTables[$i]['offset'] = TcpdfStatic::_getULONG($font, $offset);
             $offset += 4;
         }
         // ---------- get os/2 metrics ----------
         $offset = $table['OS/2']['offset'];
         $offset += 2;
         // skip version
         // xAvgCharWidth
         $fmetric['AvgWidth'] = round(TcpdfStatic::_getFWORD($font, $offset) * $urk);
         $offset += 2;
         // usWeightClass
         $usWeightClass = round(TcpdfStatic::_getUFWORD($font, $offset) * $urk);
         // estimate StemV and StemH (400 = usWeightClass for Normal - Regular font)
         $fmetric['StemV'] = round(70 * $usWeightClass / 400);
         $fmetric['StemH'] = round(30 * $usWeightClass / 400);
         $offset += 2;
         $offset += 2;
         // usWidthClass
         $fsType = TcpdfStatic::_getSHORT($font, $offset);
         $offset += 2;
         if ($fsType == 2) {
             // This Font cannot be modified, embedded or exchanged in any manner without first obtaining permission of the legal owner.
             return false;
         }
         // ---------- get font name ----------
         $fmetric['name'] = '';
         $offset = $table['name']['offset'];
         $offset += 2;
         // skip Format selector (=0).
         // Number of NameRecords that follow n.
         $numNameRecords = TcpdfStatic::_getUSHORT($font, $offset);
         $offset += 2;
         // Offset to start of string storage (from start of table).
         $stringStorageOffset = TcpdfStatic::_getUSHORT($font, $offset);
         $offset += 2;
         for ($i = 0; $i < $numNameRecords; ++$i) {
             $offset += 6;
             // skip Platform ID, Platform-specific encoding ID, Language ID.
             // Name ID.
             $nameID = TcpdfStatic::_getUSHORT($font, $offset);
             $offset += 2;
             if ($nameID == 6) {
                 // String length (in bytes).
                 $stringLength = TcpdfStatic::_getUSHORT($font, $offset);
                 $offset += 2;
                 // String offset from start of storage area (in bytes).
                 $stringOffset = TcpdfStatic::_getUSHORT($font, $offset);
                 $offset += 2;
                 $offset = $table['name']['offset'] + $stringStorageOffset + $stringOffset;
                 $fmetric['name'] = substr($font, $offset, $stringLength);
                 $fmetric['name'] = preg_replace('/[^a-zA-Z0-9_\\-]/', '', $fmetric['name']);
                 break;
             } else {
                 $offset += 4;
                 // skip String length, String offset
             }
         }
         if (empty($fmetric['name'])) {
             $fmetric['name'] = $font_name;
         }
         // ---------- get post data ----------
         $offset = $table['post']['offset'];
         $offset += 4;
         // skip Format Type
         $fmetric['italicAngle'] = TcpdfStatic::_getFIXED($font, $offset);
         $offset += 4;
         $fmetric['underlinePosition'] = round(TcpdfStatic::_getFWORD($font, $offset) * $urk);
         $offset += 2;
         $fmetric['underlineThickness'] = round(TcpdfStatic::_getFWORD($font, $offset) * $urk);
         $offset += 2;
         $isFixedPitch = TcpdfStatic::_getULONG($font, $offset) == 0 ? false : true;
         $offset += 2;
         if ($isFixedPitch) {
             $fmetric['Flags'] |= 1;
         }
         // ---------- get hhea data ----------
         $offset = $table['hhea']['offset'];
         $offset += 4;
         // skip Table version number
         // Ascender
         $fmetric['Ascent'] = round(TcpdfStatic::_getFWORD($font, $offset) * $urk);
         $offset += 2;
         // Descender
         $fmetric['Descent'] = round(TcpdfStatic::_getFWORD($font, $offset) * $urk);
         $offset += 2;
         // LineGap
         $fmetric['Leading'] = round(TcpdfStatic::_getFWORD($font, $offset) * $urk);
         $offset += 2;
         // advanceWidthMax
         $fmetric['MaxWidth'] = round(TcpdfStatic::_getUFWORD($font, $offset) * $urk);
         $offset += 2;
         $offset += 22;
         // skip some values
         // get the number of hMetric entries in hmtx table
         $numberOfHMetrics = TcpdfStatic::_getUSHORT($font, $offset);
         // ---------- get maxp data ----------
         $offset = $table['maxp']['offset'];
         $offset += 4;
         // skip Table version number
         // get the the number of glyphs in the font.
         $numGlyphs = TcpdfStatic::_getUSHORT($font, $offset);
         // ---------- get CIDToGIDMap ----------
         $ctg = array();
         foreach ($encodingTables as $enctable) {
             // get only specified Platform ID and Encoding ID
             if ($enctable['platformID'] == $platid and $enctable['encodingID'] == $encid) {
                 $offset = $table['cmap']['offset'] + $enctable['offset'];
                 $format = TcpdfStatic::_getUSHORT($font, $offset);
                 $offset += 2;
                 switch ($format) {
                     case 0:
                         // Format 0: Byte encoding table
                         $offset += 4;
                         // skip length and version/language
                         for ($c = 0; $c < 256; ++$c) {
                             $g = TcpdfStatic::_getBYTE($font, $offset);
                             $ctg[$c] = $g;
                             ++$offset;
                         }
                         break;
                     case 2:
                         // Format 2: High-byte mapping through table
                         $offset += 4;
                         // skip length and version/language
                         $numSubHeaders = 0;
                         for ($i = 0; $i < 256; ++$i) {
                             // Array that maps high bytes to subHeaders: value is subHeader index * 8.
                             $subHeaderKeys[$i] = TcpdfStatic::_getUSHORT($font, $offset) / 8;
                             $offset += 2;
                             if ($numSubHeaders < $subHeaderKeys[$i]) {
                                 $numSubHeaders = $subHeaderKeys[$i];
                             }
                         }
                         // the number of subHeaders is equal to the max of subHeaderKeys + 1
                         ++$numSubHeaders;
                         // read subHeader structures
                         $subHeaders = array();
                         $numGlyphIndexArray = 0;
                         for ($k = 0; $k < $numSubHeaders; ++$k) {
                             $subHeaders[$k]['firstCode'] = TcpdfStatic::_getUSHORT($font, $offset);
                             $offset += 2;
                             $subHeaders[$k]['entryCount'] = TcpdfStatic::_getUSHORT($font, $offset);
                             $offset += 2;
                             $subHeaders[$k]['idDelta'] = TcpdfStatic::_getUSHORT($font, $offset);
                             $offset += 2;
                             $subHeaders[$k]['idRangeOffset'] = TcpdfStatic::_getUSHORT($font, $offset);
                             $offset += 2;
                             $subHeaders[$k]['idRangeOffset'] -= 2 + ($numSubHeaders - $k - 1) * 8;
                             $subHeaders[$k]['idRangeOffset'] /= 2;
                             $numGlyphIndexArray += $subHeaders[$k]['entryCount'];
                         }
                         for ($k = 0; $k < $numGlyphIndexArray; ++$k) {
                             $glyphIndexArray[$k] = TcpdfStatic::_getUSHORT($font, $offset);
                             $offset += 2;
                         }
                         for ($i = 0; $i < 256; ++$i) {
                             $k = $subHeaderKeys[$i];
                             if ($k == 0) {
                                 // one byte code
                                 $c = $i;
                                 $g = $glyphIndexArray[0];
                                 $ctg[$c] = $g;
                             } else {
                                 // two bytes code
                                 $start_byte = $subHeaders[$k]['firstCode'];
                                 $end_byte = $start_byte + $subHeaders[$k]['entryCount'];
                                 for ($j = $start_byte; $j < $end_byte; ++$j) {
                                     // combine high and low bytes
                                     $c = ($i << 8) + $j;
                                     $idRangeOffset = $subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode'];
                                     $g = ($glyphIndexArray[$idRangeOffset] + $idDelta[$k]) % 65536;
                                     if ($g < 0) {
                                         $g = 0;
                                     }
                                     $ctg[$c] = $g;
                                 }
                             }
                         }
                         break;
                     case 4:
                         // Format 4: Segment mapping to delta values
                         $length = TcpdfStatic::_getUSHORT($font, $offset);
                         $offset += 2;
                         $offset += 2;
                         // skip version/language
                         $segCount = floor(TcpdfStatic::_getUSHORT($font, $offset) / 2);
                         $offset += 2;
                         $offset += 6;
                         // skip searchRange, entrySelector, rangeShift
                         $endCount = array();
                         // array of end character codes for each segment
                         for ($k = 0; $k < $segCount; ++$k) {
                             $endCount[$k] = TcpdfStatic::_getUSHORT($font, $offset);
                             $offset += 2;
                         }
                         $offset += 2;
                         // skip reservedPad
                         $startCount = array();
                         // array of start character codes for each segment
                         for ($k = 0; $k < $segCount; ++$k) {
                             $startCount[$k] = TcpdfStatic::_getUSHORT($font, $offset);
                             $offset += 2;
                         }
                         $idDelta = array();
                         // delta for all character codes in segment
                         for ($k = 0; $k < $segCount; ++$k) {
                             $idDelta[$k] = TcpdfStatic::_getUSHORT($font, $offset);
                             $offset += 2;
                         }
                         $idRangeOffset = array();
                         // Offsets into glyphIdArray or 0
                         for ($k = 0; $k < $segCount; ++$k) {
                             $idRangeOffset[$k] = TcpdfStatic::_getUSHORT($font, $offset);
                             $offset += 2;
                         }
                         $gidlen = floor($length / 2) - 8 - 4 * $segCount;
                         $glyphIdArray = array();
                         // glyph index array
                         for ($k = 0; $k < $gidlen; ++$k) {
                             $glyphIdArray[$k] = TcpdfStatic::_getUSHORT($font, $offset);
                             $offset += 2;
                         }
                         for ($k = 0; $k < $segCount; ++$k) {
                             for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
                                 if ($idRangeOffset[$k] == 0) {
                                     $g = ($idDelta[$k] + $c) % 65536;
                                 } else {
                                     $gid = floor($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k);
                                     $g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536;
                                 }
                                 if ($g < 0) {
                                     $g = 0;
                                 }
                                 $ctg[$c] = $g;
                             }
                         }
                         break;
                     case 6:
                         // Format 6: Trimmed table mapping
                         $offset += 4;
                         // skip length and version/language
                         $firstCode = TcpdfStatic::_getUSHORT($font, $offset);
                         $offset += 2;
                         $entryCount = TcpdfStatic::_getUSHORT($font, $offset);
                         $offset += 2;
                         for ($k = 0; $k < $entryCount; ++$k) {
                             $c = $k + $firstCode;
                             $g = TcpdfStatic::_getUSHORT($font, $offset);
                             $offset += 2;
                             $ctg[$c] = $g;
                         }
                         break;
                     case 8:
                         // Format 8: Mixed 16-bit and 32-bit coverage
                         $offset += 10;
                         // skip reserved, length and version/language
                         for ($k = 0; $k < 8192; ++$k) {
                             $is32[$k] = TcpdfStatic::_getBYTE($font, $offset);
                             ++$offset;
                         }
                         $nGroups = TcpdfStatic::_getULONG($font, $offset);
                         $offset += 4;
                         for ($i = 0; $i < $nGroups; ++$i) {
                             $startCharCode = TcpdfStatic::_getULONG($font, $offset);
                             $offset += 4;
                             $endCharCode = TcpdfStatic::_getULONG($font, $offset);
                             $offset += 4;
                             $startGlyphID = TcpdfStatic::_getULONG($font, $offset);
                             $offset += 4;
                             for ($k = $startCharCode; $k <= $endCharCode; ++$k) {
                                 $is32idx = floor($c / 8);
                                 if (isset($is32[$is32idx]) and ($is32[$is32idx] & 1 << 7 - $c % 8) == 0) {
                                     $c = $k;
                                 } else {
                                     // 32 bit format
                                     // convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4)
                                     //LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232
                                     //SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888
                                     $c = (55232 + ($k >> 10) << 10) + (0xdc00 + ($k & 0x3ff)) - 56613888;
                                 }
                                 $ctg[$c] = 0;
                                 ++$startGlyphID;
                             }
                         }
                         break;
                     case 10:
                         // Format 10: Trimmed array
                         $offset += 10;
                         // skip reserved, length and version/language
                         $startCharCode = TcpdfStatic::_getULONG($font, $offset);
                         $offset += 4;
                         $numChars = TcpdfStatic::_getULONG($font, $offset);
                         $offset += 4;
                         for ($k = 0; $k < $numChars; ++$k) {
                             $c = $k + $startCharCode;
                             $g = TcpdfStatic::_getUSHORT($font, $offset);
                             $ctg[$c] = $g;
                             $offset += 2;
                         }
                         break;
                     case 12:
                         // Format 12: Segmented coverage
                         $offset += 10;
                         // skip length and version/language
                         $nGroups = TcpdfStatic::_getULONG($font, $offset);
                         $offset += 4;
                         for ($k = 0; $k < $nGroups; ++$k) {
                             $startCharCode = TcpdfStatic::_getULONG($font, $offset);
                             $offset += 4;
                             $endCharCode = TcpdfStatic::_getULONG($font, $offset);
                             $offset += 4;
                             $startGlyphCode = TcpdfStatic::_getULONG($font, $offset);
                             $offset += 4;
                             for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
                                 $ctg[$c] = $startGlyphCode;
                                 ++$startGlyphCode;
                             }
                         }
                         break;
                     case 13:
                         // Format 13: Many-to-one range mappings
                         // to be implemented ...
                         break;
                     case 14:
                         // Format 14: Unicode Variation Sequences
                         // to be implemented ...
                         break;
                 }
             }
         }
         if (!isset($ctg[0])) {
             $ctg[0] = 0;
         }
         // get xHeight (height of x)
         $offset = $table['glyf']['offset'] + $indexToLoc[$ctg[120]] + 4;
         $yMin = TcpdfStatic::_getFWORD($font, $offset);
         $offset += 4;
         $yMax = TcpdfStatic::_getFWORD($font, $offset);
         $offset += 2;
         $fmetric['XHeight'] = round(($yMax - $yMin) * $urk);
         // get CapHeight (height of H)
         $offset = $table['glyf']['offset'] + $indexToLoc[$ctg[72]] + 4;
         $yMin = TcpdfStatic::_getFWORD($font, $offset);
         $offset += 4;
         $yMax = TcpdfStatic::_getFWORD($font, $offset);
         $offset += 2;
         $fmetric['CapHeight'] = round(($yMax - $yMin) * $urk);
         // ceate widths array
         $cw = array();
         $offset = $table['hmtx']['offset'];
         for ($i = 0; $i < $numberOfHMetrics; ++$i) {
             $cw[$i] = round(TcpdfStatic::_getUFWORD($font, $offset) * $urk);
             $offset += 4;
             // skip lsb
         }
         if ($numberOfHMetrics < $numGlyphs) {
             // fill missing widths with the last value
             $cw = array_pad($cw, $numGlyphs, $cw[$numberOfHMetrics - 1]);
         }
         $fmetric['MissingWidth'] = $cw[0];
         $fmetric['cw'] = '';
         for ($cid = 0; $cid <= 65535; ++$cid) {
             if (isset($ctg[$cid])) {
                 if (isset($cw[$ctg[$cid]])) {
                     $fmetric['cw'] .= ',' . $cid . '=>' . $cw[$ctg[$cid]];
                 }
                 if ($addcbbox and isset($indexToLoc[$ctg[$cid]])) {
                     $offset = $table['glyf']['offset'] + $indexToLoc[$ctg[$cid]];
                     $xMin = round(TcpdfStatic::_getFWORD($font, $offset + 2)) * $urk;
                     $yMin = round(TcpdfStatic::_getFWORD($font, $offset + 4)) * $urk;
                     $xMax = round(TcpdfStatic::_getFWORD($font, $offset + 6)) * $urk;
                     $yMax = round(TcpdfStatic::_getFWORD($font, $offset + 8)) * $urk;
                     $fmetric['cbbox'] .= ',' . $cid . '=>array(' . $xMin . ',' . $yMin . ',' . $xMax . ',' . $yMax . ')';
                 }
             }
         }
     }
     // end of true type
     if ($fmetric['type'] == 'TrueTypeUnicode' and count($ctg) == 256) {
         $fmetric['type'] == 'TrueType';
     }
     // ---------- create php font file ----------
     $pfile = '<' . '?' . 'php' . "\n";
     $pfile .= '// TCPDF FONT FILE DESCRIPTION' . "\n";
     $pfile .= '$type=\'' . $fmetric['type'] . '\';' . "\n";
     $pfile .= '$name=\'' . $fmetric['name'] . '\';' . "\n";
     $pfile .= '$up=' . $fmetric['underlinePosition'] . ';' . "\n";
     $pfile .= '$ut=' . $fmetric['underlineThickness'] . ';' . "\n";
     if ($fmetric['MissingWidth'] > 0) {
         $pfile .= '$dw=' . $fmetric['MissingWidth'] . ';' . "\n";
     } else {
         $pfile .= '$dw=' . $fmetric['AvgWidth'] . ';' . "\n";
     }
     $pfile .= '$diff=\'' . $fmetric['diff'] . '\';' . "\n";
     if ($fmetric['type'] == 'Type1') {
         // Type 1
         $pfile .= '$enc=\'' . $fmetric['enc'] . '\';' . "\n";
         $pfile .= '$file=\'' . $fmetric['file'] . '\';' . "\n";
         $pfile .= '$size1=' . $fmetric['size1'] . ';' . "\n";
         $pfile .= '$size2=' . $fmetric['size2'] . ';' . "\n";
     } else {
         $pfile .= '$originalsize=' . $fmetric['originalsize'] . ';' . "\n";
         if ($fmetric['type'] == 'cidfont0') {
             // CID-0
             switch ($fonttype) {
                 case 'CID0JP':
                     $pfile .= '// Japanese' . "\n";
                     $pfile .= '$enc=\'UniJIS-UTF16-H\';' . "\n";
                     $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Japan1\',\'Supplement\'=>5);' . "\n";
                     $pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');' . "\n";
                     break;
                 case 'CID0KR':
                     $pfile .= '// Korean' . "\n";
                     $pfile .= '$enc=\'UniKS-UTF16-H\';' . "\n";
                     $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Korea1\',\'Supplement\'=>0);' . "\n";
                     $pfile .= 'include(dirname(__FILE__).\'/uni2cid_ak12.php\');' . "\n";
                     break;
                 case 'CID0CS':
                     $pfile .= '// Chinese Simplified' . "\n";
                     $pfile .= '$enc=\'UniGB-UTF16-H\';' . "\n";
                     $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'GB1\',\'Supplement\'=>2);' . "\n";
                     $pfile .= 'include(dirname(__FILE__).\'/uni2cid_ag15.php\');' . "\n";
                     break;
                 case 'CID0CT':
                 default:
                     $pfile .= '// Chinese Traditional' . "\n";
                     $pfile .= '$enc=\'UniCNS-UTF16-H\';' . "\n";
                     $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'CNS1\',\'Supplement\'=>0);' . "\n";
                     $pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');' . "\n";
                     break;
             }
         } else {
             // TrueType
             $pfile .= '$enc=\'' . $fmetric['enc'] . '\';' . "\n";
             $pfile .= '$file=\'' . $fmetric['file'] . '\';' . "\n";
             $pfile .= '$ctg=\'' . $fmetric['ctg'] . '\';' . "\n";
             // create CIDToGIDMap
             $cidtogidmap = str_pad('', 131072, "");
             // (256 * 256 * 2) = 131072
             foreach ($ctg as $cid => $gid) {
                 $cidtogidmap = self::updateCIDtoGIDmap($cidtogidmap, $cid, $ctg[$cid]);
             }
             // store compressed CIDToGIDMap
             $fp = fopen($outpath . $fmetric['ctg'], 'wb');
             fwrite($fp, gzcompress($cidtogidmap));
             fclose($fp);
         }
     }
     $pfile .= '$desc=array(';
     $pfile .= '\'Flags\'=>' . $fmetric['Flags'] . ',';
     $pfile .= '\'FontBBox\'=>\'[' . $fmetric['bbox'] . ']\',';
     $pfile .= '\'ItalicAngle\'=>' . $fmetric['italicAngle'] . ',';
     $pfile .= '\'Ascent\'=>' . $fmetric['Ascent'] . ',';
     $pfile .= '\'Descent\'=>' . $fmetric['Descent'] . ',';
     $pfile .= '\'Leading\'=>' . $fmetric['Leading'] . ',';
     $pfile .= '\'CapHeight\'=>' . $fmetric['CapHeight'] . ',';
     $pfile .= '\'XHeight\'=>' . $fmetric['XHeight'] . ',';
     $pfile .= '\'StemV\'=>' . $fmetric['StemV'] . ',';
     $pfile .= '\'StemH\'=>' . $fmetric['StemH'] . ',';
     $pfile .= '\'AvgWidth\'=>' . $fmetric['AvgWidth'] . ',';
     $pfile .= '\'MaxWidth\'=>' . $fmetric['MaxWidth'] . ',';
     $pfile .= '\'MissingWidth\'=>' . $fmetric['MissingWidth'] . '';
     $pfile .= ');' . "\n";
     if (isset($fmetric['cbbox'])) {
         $pfile .= '$cbbox=array(' . substr($fmetric['cbbox'], 1) . ');' . "\n";
     }
     $pfile .= '$cw=array(' . substr($fmetric['cw'], 1) . ');' . "\n";
     $pfile .= '// --- EOF ---' . "\n";
     // store file
     $fp = fopen($outpath . $font_name . '.php', 'w');
     fwrite($fp, $pfile);
     fclose($fp);
     // return TCPDF font name
     return $font_name;
 }