.
//
// See LICENSE.TXT file for more information.
// -------------------------------------------------------------------
//
// Description :Font methods for TCPDF library.
//
//============================================================+
/**
* @file
* Unicode data and font methods for TCPDF library.
* @author Nicola Asuni
* @package com.tecnick.tcpdf
*/
/**
* @class TCPDF_FONTS
* Font methods for TCPDF library.
* @package com.tecnick.tcpdf
* @version 1.0.014
* @author Nicola Asuni - info@tecnick.com
*/
class TCPDF_FONTS {
/**
* Static cache used for speed up uniord performances
* @protected
*/
protected static $cache_uniord = array();
/**
* 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 (TCPDF_STATIC::_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(TCPDF_FONT_DATA::$encmap[$enc])) {
// build differences from reference encoding
$enc_ref = TCPDF_FONT_DATA::$encmap['cp1252'];
$enc_target = TCPDF_FONT_DATA::$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(TCPDF_FONT_DATA::$encmap[$enc])) {
$enc_map = TCPDF_FONT_DATA::$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 (TCPDF_STATIC::_getULONG($font, $offset) != 0x10000) {
// sfnt version must be 0x00010000 for TrueType version 1.0.
return false;
}
$offset += 4;
// get number of tables
$numTables = TCPDF_STATIC::_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'] = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
$table[$tag]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
$table[$tag]['length'] = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
}
// check magicNumber
$offset = $table['head']['offset'] + 12;
if (TCPDF_STATIC::_getULONG($font, $offset) != 0x5F0F3CF5) {
// magicNumber must be 0x5F0F3CF5
return false;
}
$offset += 4;
$offset += 2; // skip flags
// get FUnits
$fmetric['unitsPerEm'] = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
// units ratio constant
$urk = (1000 / $fmetric['unitsPerEm']);
$offset += 16; // skip created, modified
$xMin = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
$offset += 2;
$yMin = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
$offset += 2;
$xMax = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
$offset += 2;
$yMax = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
$offset += 2;
$fmetric['bbox'] = ''.$xMin.' '.$yMin.' '.$xMax.' '.$yMax.'';
$macStyle = TCPDF_STATIC::_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 = (TCPDF_STATIC::_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] = TCPDF_STATIC::_getUSHORT($font, $offset) * 2;
if (isset($indexToLoc[($i - 1)]) && ($indexToLoc[$i] == $indexToLoc[($i - 1)])) {
// the last glyph didn't have an outline
unset($indexToLoc[($i - 1)]);
}
$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] = TCPDF_STATIC::_getULONG($font, $offset);
if (isset($indexToLoc[($i - 1)]) && ($indexToLoc[$i] == $indexToLoc[($i - 1)])) {
// the last glyph didn't have an outline
unset($indexToLoc[($i - 1)]);
}
$offset += 4;
}
}
// get glyphs indexes of chars from cmap table
$offset = $table['cmap']['offset'] + 2;
$numEncodingTables = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
$encodingTables = array();
for ($i = 0; $i < $numEncodingTables; ++$i) {
$encodingTables[$i]['platformID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
$encodingTables[$i]['encodingID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
$encodingTables[$i]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
}
// ---------- get os/2 metrics ----------
$offset = $table['OS/2']['offset'];
$offset += 2; // skip version
// xAvgCharWidth
$fmetric['AvgWidth'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
$offset += 2;
// usWeightClass
$usWeightClass = round(TCPDF_STATIC::_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 = TCPDF_STATIC::_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 = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
// Offset to start of string storage (from start of table).
$stringStorageOffset = TCPDF_STATIC::_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 = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
if ($nameID == 6) {
// String length (in bytes).
$stringLength = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
// String offset from start of storage area (in bytes).
$stringOffset = TCPDF_STATIC::_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'] = TCPDF_STATIC::_getFIXED($font, $offset);
$offset += 4;
$fmetric['underlinePosition'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
$offset += 2;
$fmetric['underlineThickness'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
$offset += 2;
$isFixedPitch = (TCPDF_STATIC::_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(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
$offset += 2;
// Descender
$fmetric['Descent'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
$offset += 2;
// LineGap
$fmetric['Leading'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk);
$offset += 2;
// advanceWidthMax
$fmetric['MaxWidth'] = round(TCPDF_STATIC::_getUFWORD($font, $offset) * $urk);
$offset += 2;
$offset += 22; // skip some values
// get the number of hMetric entries in hmtx table
$numberOfHMetrics = TCPDF_STATIC::_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 = TCPDF_STATIC::_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 = TCPDF_STATIC::_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 = TCPDF_STATIC::_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] = (TCPDF_STATIC::_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'] = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
$subHeaders[$k]['entryCount'] = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
$subHeaders[$k]['idDelta'] = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
$subHeaders[$k]['idRangeOffset'] = TCPDF_STATIC::_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] = TCPDF_STATIC::_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] + $subHeaders[$k]['idDelta']) % 65536;
if ($g < 0) {
$g = 0;
}
$ctg[$c] = $g;
}
}
}
break;
}
case 4: { // Format 4: Segment mapping to delta values
$length = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
$offset += 2; // skip version/language
$segCount = floor(TCPDF_STATIC::_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] = TCPDF_STATIC::_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] = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
}
$idDelta = array(); // delta for all character codes in segment
for ($k = 0; $k < $segCount; ++$k) {
$idDelta[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
}
$idRangeOffset = array(); // Offsets into glyphIdArray or 0
for ($k = 0; $k < $segCount; ++$k) {
$idRangeOffset[$k] = TCPDF_STATIC::_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] = TCPDF_STATIC::_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 = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
$entryCount = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
for ($k = 0; $k < $entryCount; ++$k) {
$c = ($k + $firstCode);
$g = TCPDF_STATIC::_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] = TCPDF_STATIC::_getBYTE($font, $offset);
++$offset;
}
$nGroups = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
for ($i = 0; $i < $nGroups; ++$i) {
$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
$endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
$startGlyphID = TCPDF_STATIC::_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 = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
$numChars = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
for ($k = 0; $k < $numChars; ++$k) {
$c = ($k + $startCharCode);
$g = TCPDF_STATIC::_getUSHORT($font, $offset);
$ctg[$c] = $g;
$offset += 2;
}
break;
}
case 12: { // Format 12: Segmented coverage
$offset += 10; // skip length and version/language
$nGroups = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
for ($k = 0; $k < $nGroups; ++$k) {
$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
$endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
$startGlyphCode = TCPDF_STATIC::_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 = TCPDF_STATIC::_getFWORD($font, $offset);
$offset += 4;
$yMax = TCPDF_STATIC::_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 = TCPDF_STATIC::_getFWORD($font, $offset);
$offset += 4;
$yMax = TCPDF_STATIC::_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(TCPDF_STATIC::_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'] = '';
$fmetric['cbbox'] = '';
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(TCPDF_STATIC::_getFWORD($font, $offset + 2) * $urk);
$yMin = round(TCPDF_STATIC::_getFWORD($font, $offset + 4) * $urk);
$xMax = round(TCPDF_STATIC::_getFWORD($font, $offset + 6) * $urk);
$yMax = round(TCPDF_STATIC::_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, "\x00"); // (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 (!empty($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;
}
/**
* Returs the checksum of a TTF table.
* @param $table (string) table to check
* @param $length (int) length of table in bytes
* @return int checksum
* @author Nicola Asuni
* @since 5.2.000 (2010-06-02)
* @public static
*/
public static function _getTTFtableChecksum($table, $length) {
$sum = 0;
$tlen = ($length / 4);
$offset = 0;
for ($i = 0; $i < $tlen; ++$i) {
$v = unpack('Ni', substr($table, $offset, 4));
$sum += $v['i'];
$offset += 4;
}
$sum = unpack('Ni', pack('N', $sum));
return $sum['i'];
}
/**
* Returns a subset of the TrueType font data without the unused glyphs.
* @param $font (string) TrueType font data.
* @param $subsetchars (array) Array of used characters (the glyphs to keep).
* @return (string) A subset of TrueType font data without the unused glyphs.
* @author Nicola Asuni
* @since 5.2.000 (2010-06-02)
* @public static
*/
public static function _getTrueTypeFontSubset($font, $subsetchars) {
ksort($subsetchars);
$offset = 0; // offset position of the font data
if (TCPDF_STATIC::_getULONG($font, $offset) != 0x10000) {
// sfnt version must be 0x00010000 for TrueType version 1.0.
return $font;
}
$offset += 4;
// get number of tables
$numTables = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
// skip searchRange, entrySelector and rangeShift
$offset += 6;
// tables array
$table = array();
// for each table
for ($i = 0; $i < $numTables; ++$i) {
// get table info
$tag = substr($font, $offset, 4);
$offset += 4;
$table[$tag] = array();
$table[$tag]['checkSum'] = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
$table[$tag]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
$table[$tag]['length'] = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
}
// check magicNumber
$offset = $table['head']['offset'] + 12;
if (TCPDF_STATIC::_getULONG($font, $offset) != 0x5F0F3CF5) {
// magicNumber must be 0x5F0F3CF5
return $font;
}
$offset += 4;
// get offset mode (indexToLocFormat : 0 = short, 1 = long)
$offset = $table['head']['offset'] + 50;
$short_offset = (TCPDF_STATIC::_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] = TCPDF_STATIC::_getUSHORT($font, $offset) * 2;
$offset += 2;
}
} else {
// long version
$tot_num_glyphs = ($table['loca']['length'] / 4); // numGlyphs + 1
for ($i = 0; $i < $tot_num_glyphs; ++$i) {
$indexToLoc[$i] = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
}
}
// get glyphs indexes of chars from cmap table
$subsetglyphs = array(); // glyph IDs on key
$subsetglyphs[0] = true; // character codes that do not correspond to any glyph in the font should be mapped to glyph index 0
$offset = $table['cmap']['offset'] + 2;
$numEncodingTables = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
$encodingTables = array();
for ($i = 0; $i < $numEncodingTables; ++$i) {
$encodingTables[$i]['platformID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
$encodingTables[$i]['encodingID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
$encodingTables[$i]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
}
foreach ($encodingTables as $enctable) {
// get all platforms and encodings
$offset = $table['cmap']['offset'] + $enctable['offset'];
$format = TCPDF_STATIC::_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) {
if (isset($subsetchars[$c])) {
$g = TCPDF_STATIC::_getBYTE($font, $offset);
$subsetglyphs[$g] = true;
}
++$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] = (TCPDF_STATIC::_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'] = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
$subHeaders[$k]['entryCount'] = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
$subHeaders[$k]['idDelta'] = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
$subHeaders[$k]['idRangeOffset'] = TCPDF_STATIC::_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] = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
}
for ($i = 0; $i < 256; ++$i) {
$k = $subHeaderKeys[$i];
if ($k == 0) {
// one byte code
$c = $i;
if (isset($subsetchars[$c])) {
$g = $glyphIndexArray[0];
$subsetglyphs[$g] = true;
}
} 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);
if (isset($subsetchars[$c])) {
$idRangeOffset = ($subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode']);
$g = ($glyphIndexArray[$idRangeOffset] + $subHeaders[$k]['idDelta']) % 65536;
if ($g < 0) {
$g = 0;
}
$subsetglyphs[$g] = true;
}
}
}
}
break;
}
case 4: { // Format 4: Segment mapping to delta values
$length = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
$offset += 2; // skip version/language
$segCount = floor(TCPDF_STATIC::_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] = TCPDF_STATIC::_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] = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
}
$idDelta = array(); // delta for all character codes in segment
for ($k = 0; $k < $segCount; ++$k) {
$idDelta[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
}
$idRangeOffset = array(); // Offsets into glyphIdArray or 0
for ($k = 0; $k < $segCount; ++$k) {
$idRangeOffset[$k] = TCPDF_STATIC::_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] = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
}
for ($k = 0; $k < $segCount; ++$k) {
for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
if (isset($subsetchars[$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;
}
$subsetglyphs[$g] = true;
}
}
}
break;
}
case 6: { // Format 6: Trimmed table mapping
$offset += 4; // skip length and version/language
$firstCode = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
$entryCount = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
for ($k = 0; $k < $entryCount; ++$k) {
$c = ($k + $firstCode);
if (isset($subsetchars[$c])) {
$g = TCPDF_STATIC::_getUSHORT($font, $offset);
$subsetglyphs[$g] = true;
}
$offset += 2;
}
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] = TCPDF_STATIC::_getBYTE($font, $offset);
++$offset;
}
$nGroups = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
for ($i = 0; $i < $nGroups; ++$i) {
$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
$endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
$startGlyphID = TCPDF_STATIC::_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;
}
if (isset($subsetchars[$c])) {
$subsetglyphs[$startGlyphID] = true;
}
++$startGlyphID;
}
}
break;
}
case 10: { // Format 10: Trimmed array
$offset += 10; // skip reserved, length and version/language
$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
$numChars = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
for ($k = 0; $k < $numChars; ++$k) {
$c = ($k + $startCharCode);
if (isset($subsetchars[$c])) {
$g = TCPDF_STATIC::_getUSHORT($font, $offset);
$subsetglyphs[$g] = true;
}
$offset += 2;
}
break;
}
case 12: { // Format 12: Segmented coverage
$offset += 10; // skip length and version/language
$nGroups = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
for ($k = 0; $k < $nGroups; ++$k) {
$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
$endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
$startGlyphCode = TCPDF_STATIC::_getULONG($font, $offset);
$offset += 4;
for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
if (isset($subsetchars[$c])) {
$subsetglyphs[$startGlyphCode] = true;
}
++$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;
}
}
}
// include all parts of composite glyphs
$new_sga = $subsetglyphs;
while (!empty($new_sga)) {
$sga = $new_sga;
$new_sga = array();
foreach ($sga as $key => $val) {
if (isset($indexToLoc[$key])) {
$offset = ($table['glyf']['offset'] + $indexToLoc[$key]);
$numberOfContours = TCPDF_STATIC::_getSHORT($font, $offset);
$offset += 2;
if ($numberOfContours < 0) { // composite glyph
$offset += 8; // skip xMin, yMin, xMax, yMax
do {
$flags = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
$glyphIndex = TCPDF_STATIC::_getUSHORT($font, $offset);
$offset += 2;
if (!isset($subsetglyphs[$glyphIndex])) {
// add missing glyphs
$new_sga[$glyphIndex] = true;
}
// skip some bytes by case
if ($flags & 1) {
$offset += 4;
} else {
$offset += 2;
}
if ($flags & 8) {
$offset += 2;
} elseif ($flags & 64) {
$offset += 4;
} elseif ($flags & 128) {
$offset += 8;
}
} while ($flags & 32);
}
}
}
$subsetglyphs += $new_sga;
}
// sort glyphs by key (and remove duplicates)
ksort($subsetglyphs);
// build new glyf and loca tables
$glyf = '';
$loca = '';
$offset = 0;
$glyf_offset = $table['glyf']['offset'];
for ($i = 0; $i < $tot_num_glyphs; ++$i) {
if (isset($subsetglyphs[$i])) {
$length = ($indexToLoc[($i + 1)] - $indexToLoc[$i]);
$glyf .= substr($font, ($glyf_offset + $indexToLoc[$i]), $length);
} else {
$length = 0;
}
if ($short_offset) {
$loca .= pack('n', floor($offset / 2));
} else {
$loca .= pack('N', $offset);
}
$offset += $length;
}
// array of table names to preserve (loca and glyf tables will be added later)
// the cmap table is not needed and shall not be present, since the mapping from character codes to glyph descriptions is provided separately
$table_names = array ('head', 'hhea', 'hmtx', 'maxp', 'cvt ', 'fpgm', 'prep'); // minimum required table names
// get the tables to preserve
$offset = 12;
foreach ($table as $tag => $val) {
if (in_array($tag, $table_names)) {
$table[$tag]['data'] = substr($font, $table[$tag]['offset'], $table[$tag]['length']);
if ($tag == 'head') {
// set the checkSumAdjustment to 0
$table[$tag]['data'] = substr($table[$tag]['data'], 0, 8)."\x0\x0\x0\x0".substr($table[$tag]['data'], 12);
}
$pad = 4 - ($table[$tag]['length'] % 4);
if ($pad != 4) {
// the length of a table must be a multiple of four bytes
$table[$tag]['length'] += $pad;
$table[$tag]['data'] .= str_repeat("\x0", $pad);
}
$table[$tag]['offset'] = $offset;
$offset += $table[$tag]['length'];
// check sum is not changed (so keep the following line commented)
//$table[$tag]['checkSum'] = self::_getTTFtableChecksum($table[$tag]['data'], $table[$tag]['length']);
} else {
unset($table[$tag]);
}
}
// add loca
$table['loca']['data'] = $loca;
$table['loca']['length'] = strlen($loca);
$pad = 4 - ($table['loca']['length'] % 4);
if ($pad != 4) {
// the length of a table must be a multiple of four bytes
$table['loca']['length'] += $pad;
$table['loca']['data'] .= str_repeat("\x0", $pad);
}
$table['loca']['offset'] = $offset;
$table['loca']['checkSum'] = self::_getTTFtableChecksum($table['loca']['data'], $table['loca']['length']);
$offset += $table['loca']['length'];
// add glyf
$table['glyf']['data'] = $glyf;
$table['glyf']['length'] = strlen($glyf);
$pad = 4 - ($table['glyf']['length'] % 4);
if ($pad != 4) {
// the length of a table must be a multiple of four bytes
$table['glyf']['length'] += $pad;
$table['glyf']['data'] .= str_repeat("\x0", $pad);
}
$table['glyf']['offset'] = $offset;
$table['glyf']['checkSum'] = self::_getTTFtableChecksum($table['glyf']['data'], $table['glyf']['length']);
// rebuild font
$font = '';
$font .= pack('N', 0x10000); // sfnt version
$numTables = count($table);
$font .= pack('n', $numTables); // numTables
$entrySelector = floor(log($numTables, 2));
$searchRange = pow(2, $entrySelector) * 16;
$rangeShift = ($numTables * 16) - $searchRange;
$font .= pack('n', $searchRange); // searchRange
$font .= pack('n', $entrySelector); // entrySelector
$font .= pack('n', $rangeShift); // rangeShift
$offset = ($numTables * 16);
foreach ($table as $tag => $data) {
$font .= $tag; // tag
$font .= pack('N', $data['checkSum']); // checkSum
$font .= pack('N', ($data['offset'] + $offset)); // offset
$font .= pack('N', $data['length']); // length
}
foreach ($table as $data) {
$font .= $data['data'];
}
// set checkSumAdjustment on head table
$checkSumAdjustment = 0xB1B0AFBA - self::_getTTFtableChecksum($font, strlen($font));
$font = substr($font, 0, $table['head']['offset'] + 8).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + 12);
return $font;
}
/**
* Outputs font widths
* @param $font (array) font data
* @param $cidoffset (int) offset for CID values
* @return PDF command string for font widths
* @author Nicola Asuni
* @since 4.4.000 (2008-12-07)
* @public static
*/
public static function _putfontwidths($font, $cidoffset=0) {
ksort($font['cw']);
$rangeid = 0;
$range = array();
$prevcid = -2;
$prevwidth = -1;
$interval = false;
// for each character
foreach ($font['cw'] as $cid => $width) {
$cid -= $cidoffset;
if ($font['subset'] AND (!isset($font['subsetchars'][$cid]))) {
// ignore the unused characters (font subsetting)
continue;
}
if ($width != $font['dw']) {
if ($cid == ($prevcid + 1)) {
// consecutive CID
if ($width == $prevwidth) {
if ($width == $range[$rangeid][0]) {
$range[$rangeid][] = $width;
} else {
array_pop($range[$rangeid]);
// new range
$rangeid = $prevcid;
$range[$rangeid] = array();
$range[$rangeid][] = $prevwidth;
$range[$rangeid][] = $width;
}
$interval = true;
$range[$rangeid]['interval'] = true;
} else {
if ($interval) {
// new range
$rangeid = $cid;
$range[$rangeid] = array();
$range[$rangeid][] = $width;
} else {
$range[$rangeid][] = $width;
}
$interval = false;
}
} else {
// new range
$rangeid = $cid;
$range[$rangeid] = array();
$range[$rangeid][] = $width;
$interval = false;
}
$prevcid = $cid;
$prevwidth = $width;
}
}
// optimize ranges
$prevk = -1;
$nextk = -1;
$prevint = false;
foreach ($range as $k => $ws) {
$cws = count($ws);
if (($k == $nextk) AND (!$prevint) AND ((!isset($ws['interval'])) OR ($cws < 4))) {
if (isset($range[$k]['interval'])) {
unset($range[$k]['interval']);
}
$range[$prevk] = array_merge($range[$prevk], $range[$k]);
unset($range[$k]);
} else {
$prevk = $k;
}
$nextk = $k + $cws;
if (isset($ws['interval'])) {
if ($cws > 3) {
$prevint = true;
} else {
$prevint = false;
}
if (isset($range[$k]['interval'])) {
unset($range[$k]['interval']);
}
--$nextk;
} else {
$prevint = false;
}
}
// output data
$w = '';
foreach ($range as $k => $ws) {
if (count(array_count_values($ws)) == 1) {
// interval mode is more compact
$w .= ' '.$k.' '.($k + count($ws) - 1).' '.$ws[0];
} else {
// range mode
$w .= ' '.$k.' [ '.implode(' ', $ws).' ]';
}
}
return '/W ['.$w.' ]';
}
/**
* Returns the unicode caracter specified by the value
* @param $c (int) UTF-8 value
* @param $unicode (boolean) True if we are in unicode mode, false otherwise.
* @return Returns the specified character.
* @since 2.3.000 (2008-03-05)
* @public static
*/
public static function unichr($c, $unicode=true) {
if (!$unicode) {
return chr($c);
} elseif ($c <= 0x7F) {
// one byte
return chr($c);
} elseif ($c <= 0x7FF) {
// two bytes
return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F);
} elseif ($c <= 0xFFFF) {
// three bytes
return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
} elseif ($c <= 0x10FFFF) {
// four bytes
return chr(0xF0 | $c >> 18).chr(0x80 | $c >> 12 & 0x3F).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
} else {
return '';
}
}
/**
* Returns the unicode caracter specified by UTF-8 value
* @param $c (int) UTF-8 value
* @return Returns the specified character.
* @public static
*/
public static function unichrUnicode($c) {
return self::unichr($c, true);
}
/**
* Returns the unicode caracter specified by ASCII value
* @param $c (int) UTF-8 value
* @return Returns the specified character.
* @public static
*/
public static function unichrASCII($c) {
return self::unichr($c, false);
}
/**
* Converts array of UTF-8 characters to UTF16-BE string.
* Based on: http://www.faqs.org/rfcs/rfc2781.html
*
* Encoding UTF-16: * * Encoding of a single character from an ISO 10646 character value to * UTF-16 proceeds as follows. Let U be the character number, no greater * than 0x10FFFF. * * 1) If U < 0x10000, encode U as a 16-bit unsigned integer and * terminate. * * 2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF, * U' must be less than or equal to 0xFFFFF. That is, U' can be * represented in 20 bits. * * 3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and * 0xDC00, respectively. These integers each have 10 bits free to * encode the character value, for a total of 20 bits. * * 4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order * bits of W1 and the 10 low-order bits of U' to the 10 low-order * bits of W2. Terminate. * * Graphically, steps 2 through 4 look like: * U' = yyyyyyyyyyxxxxxxxxxx * W1 = 110110yyyyyyyyyy * W2 = 110111xxxxxxxxxx ** @param $unicode (array) array containing UTF-8 unicode values * @param $setbom (boolean) if true set the Byte Order Mark (BOM = 0xFEFF) * @return string * @protected * @author Nicola Asuni * @since 2.1.000 (2008-01-08) * @public static */ public static function arrUTF8ToUTF16BE($unicode, $setbom=false) { $outstr = ''; // string to be returned if ($setbom) { $outstr .= "\xFE\xFF"; // Byte Order Mark (BOM) } foreach ($unicode as $char) { if ($char == 0x200b) { // skip Unicode Character 'ZERO WIDTH SPACE' (DEC:8203, U+200B) } elseif ($char == 0xFFFD) { $outstr .= "\xFF\xFD"; // replacement character } elseif ($char < 0x10000) { $outstr .= chr($char >> 0x08); $outstr .= chr($char & 0xFF); } else { $char -= 0x10000; $w1 = 0xD800 | ($char >> 0x0a); $w2 = 0xDC00 | ($char & 0x3FF); $outstr .= chr($w1 >> 0x08); $outstr .= chr($w1 & 0xFF); $outstr .= chr($w2 >> 0x08); $outstr .= chr($w2 & 0xFF); } } return $outstr; } /** * Convert an array of UTF8 values to array of unicode characters * @param $ta (array) The input array of UTF8 values. * @param $isunicode (boolean) True for Unicode mode, false otherwise. * @return Return array of unicode characters * @since 4.5.037 (2009-04-07) * @public static */ public static function UTF8ArrayToUniArray($ta, $isunicode=true) { if ($isunicode) { return array_map(array('TCPDF_FONTS', 'unichrUnicode'), $ta); } return array_map(array('TCPDF_FONTS', 'unichrASCII'), $ta); } /** * Extract a slice of the $strarr array and return it as string. * @param $strarr (string) The input array of characters. * @param $start (int) the starting element of $strarr. * @param $end (int) first element that will not be returned. * @param $unicode (boolean) True if we are in unicode mode, false otherwise. * @return Return part of a string * @public static */ public static function UTF8ArrSubString($strarr, $start='', $end='', $unicode=true) { if (strlen($start) == 0) { $start = 0; } if (strlen($end) == 0) { $end = count($strarr); } $string = ''; for ($i = $start; $i < $end; ++$i) { $string .= self::unichr($strarr[$i], $unicode); } return $string; } /** * Extract a slice of the $uniarr array and return it as string. * @param $uniarr (string) The input array of characters. * @param $start (int) the starting element of $strarr. * @param $end (int) first element that will not be returned. * @return Return part of a string * @since 4.5.037 (2009-04-07) * @public static */ public static function UniArrSubString($uniarr, $start='', $end='') { if (strlen($start) == 0) { $start = 0; } if (strlen($end) == 0) { $end = count($uniarr); } $string = ''; for ($i=$start; $i < $end; ++$i) { $string .= $uniarr[$i]; } return $string; } /** * Update the CIDToGIDMap string with a new value. * @param $map (string) CIDToGIDMap. * @param $cid (int) CID value. * @param $gid (int) GID value. * @return (string) CIDToGIDMap. * @author Nicola Asuni * @since 5.9.123 (2011-09-29) * @public static */ public static function updateCIDtoGIDmap($map, $cid, $gid) { if (($cid >= 0) AND ($cid <= 0xFFFF) AND ($gid >= 0)) { if ($gid > 0xFFFF) { $gid -= 0x10000; } $map[($cid * 2)] = chr($gid >> 8); $map[(($cid * 2) + 1)] = chr($gid & 0xFF); } return $map; } /** * Return fonts path * @return string * @public static */ public static function _getfontpath() { if (!defined('K_PATH_FONTS') AND is_dir($fdir = realpath(dirname(__FILE__).'/../fonts'))) { if (substr($fdir, -1) != '/') { $fdir .= '/'; } define('K_PATH_FONTS', $fdir); } return defined('K_PATH_FONTS') ? K_PATH_FONTS : ''; } /** * Return font full path * @param $file (string) Font file name. * @param $fontdir (string) Font directory (set to false fto search on default directories) * @return string Font full path or empty string * @author Nicola Asuni * @since 6.0.025 * @public static */ public static function getFontFullPath($file, $fontdir=false) { $fontfile = ''; // search files on various directories if (($fontdir !== false) AND @file_exists($fontdir.$file)) { $fontfile = $fontdir.$file; } elseif (@file_exists(self::_getfontpath().$file)) { $fontfile = self::_getfontpath().$file; } elseif (@file_exists($file)) { $fontfile = $file; } return $fontfile; } /** * Converts UTF-8 characters array to array of Latin1 characters array
* Char. number range | UTF-8 octet sequence * (hexadecimal) | (binary) * --------------------+----------------------------------------------- * 0000 0000-0000 007F | 0xxxxxxx * 0000 0080-0000 07FF | 110xxxxx 10xxxxxx * 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx * 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx * --------------------------------------------------------------------- * * ABFN notation: * --------------------------------------------------------------------- * UTF8-octets = *( UTF8-char ) * UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4 * UTF8-1 = %x00-7F * UTF8-2 = %xC2-DF UTF8-tail * * UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / * %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail ) * UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) / * %xF4 %x80-8F 2( UTF8-tail ) * UTF8-tail = %x80-BF * --------------------------------------------------------------------- ** @param $uch (string) character string to process. * @return integer Unicode value * @author Nicola Asuni * @public static */ public static function getUniord($uch) { if (function_exists('mb_convert_encoding')) { list(, $char) = @unpack('N', mb_convert_encoding($uch, 'UCS-4BE', 'UTF-8')); if ($char >= 0) { return $char; } } $bytes = array(); // array containing single character byte sequences $countbytes = 0; $numbytes = 1; // number of octetc needed to represent the UTF-8 character $length = strlen($uch); for ($i = 0; $i < $length; ++$i) { $char = ord($uch[$i]); // get one string character at time if ($countbytes == 0) { // get starting octect if ($char <= 0x7F) { return $char; // use the character "as is" because is ASCII } elseif (($char >> 0x05) == 0x06) { // 2 bytes character (0x06 = 110 BIN) $bytes[] = ($char - 0xC0) << 0x06; ++$countbytes; $numbytes = 2; } elseif (($char >> 0x04) == 0x0E) { // 3 bytes character (0x0E = 1110 BIN) $bytes[] = ($char - 0xE0) << 0x0C; ++$countbytes; $numbytes = 3; } elseif (($char >> 0x03) == 0x1E) { // 4 bytes character (0x1E = 11110 BIN) $bytes[] = ($char - 0xF0) << 0x12; ++$countbytes; $numbytes = 4; } else { // use replacement character for other invalid sequences return 0xFFFD; } } elseif (($char >> 0x06) == 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN $bytes[] = $char - 0x80; ++$countbytes; if ($countbytes == $numbytes) { // compose UTF-8 bytes to a single unicode value $char = $bytes[0]; for ($j = 1; $j < $numbytes; ++$j) { $char += ($bytes[$j] << (($numbytes - $j - 1) * 0x06)); } if ((($char >= 0xD800) AND ($char <= 0xDFFF)) OR ($char >= 0x10FFFF)) { // The definition of UTF-8 prohibits encoding character numbers between // U+D800 and U+DFFF, which are reserved for use with the UTF-16 // encoding form (as surrogate pairs) and do not directly represent // characters. return 0xFFFD; // use replacement character } else { return $char; } } } else { // use replacement character for other invalid sequences return 0xFFFD; } } return 0xFFFD; } /** * Converts UTF-8 strings to codepoints array.