forked from SimplesIP/pabx-app
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
447 lines
11 KiB
447 lines
11 KiB
<?php |
|
/******************************************************************************* |
|
* Utility to generate font definition files * |
|
* * |
|
* Version: 1.31 * |
|
* Date: 2019-12-07 * |
|
* Author: Olivier PLATHEY * |
|
*******************************************************************************/ |
|
|
|
require('ttfparser.php'); |
|
|
|
function Message($txt, $severity='') |
|
{ |
|
if(PHP_SAPI=='cli') |
|
{ |
|
if($severity) |
|
echo "$severity: "; |
|
echo "$txt\n"; |
|
} |
|
else |
|
{ |
|
if($severity) |
|
echo "<b>$severity</b>: "; |
|
echo "$txt<br>"; |
|
} |
|
} |
|
|
|
function Notice($txt) |
|
{ |
|
Message($txt, 'Notice'); |
|
} |
|
|
|
function Warning($txt) |
|
{ |
|
Message($txt, 'Warning'); |
|
} |
|
|
|
function Error($txt) |
|
{ |
|
Message($txt, 'Error'); |
|
exit; |
|
} |
|
|
|
function LoadMap($enc) |
|
{ |
|
$file = dirname(__FILE__).'/'.strtolower($enc).'.map'; |
|
$a = file($file); |
|
if(empty($a)) |
|
Error('Encoding not found: '.$enc); |
|
$map = array_fill(0, 256, array('uv'=>-1, 'name'=>'.notdef')); |
|
foreach($a as $line) |
|
{ |
|
$e = explode(' ', rtrim($line)); |
|
$c = hexdec(substr($e[0],1)); |
|
$uv = hexdec(substr($e[1],2)); |
|
$name = $e[2]; |
|
$map[$c] = array('uv'=>$uv, 'name'=>$name); |
|
} |
|
return $map; |
|
} |
|
|
|
function GetInfoFromTrueType($file, $embed, $subset, $map) |
|
{ |
|
// Return information from a TrueType font |
|
try |
|
{ |
|
$ttf = new TTFParser($file); |
|
$ttf->Parse(); |
|
} |
|
catch(Exception $e) |
|
{ |
|
Error($e->getMessage()); |
|
} |
|
if($embed) |
|
{ |
|
if(!$ttf->embeddable) |
|
Error('Font license does not allow embedding'); |
|
if($subset) |
|
{ |
|
$chars = array(); |
|
foreach($map as $v) |
|
{ |
|
if($v['name']!='.notdef') |
|
$chars[] = $v['uv']; |
|
} |
|
$ttf->Subset($chars); |
|
$info['Data'] = $ttf->Build(); |
|
} |
|
else |
|
$info['Data'] = file_get_contents($file); |
|
$info['OriginalSize'] = strlen($info['Data']); |
|
} |
|
$k = 1000/$ttf->unitsPerEm; |
|
$info['FontName'] = $ttf->postScriptName; |
|
$info['Bold'] = $ttf->bold; |
|
$info['ItalicAngle'] = $ttf->italicAngle; |
|
$info['IsFixedPitch'] = $ttf->isFixedPitch; |
|
$info['Ascender'] = round($k*$ttf->typoAscender); |
|
$info['Descender'] = round($k*$ttf->typoDescender); |
|
$info['UnderlineThickness'] = round($k*$ttf->underlineThickness); |
|
$info['UnderlinePosition'] = round($k*$ttf->underlinePosition); |
|
$info['FontBBox'] = array(round($k*$ttf->xMin), round($k*$ttf->yMin), round($k*$ttf->xMax), round($k*$ttf->yMax)); |
|
$info['CapHeight'] = round($k*$ttf->capHeight); |
|
$info['MissingWidth'] = round($k*$ttf->glyphs[0]['w']); |
|
$widths = array_fill(0, 256, $info['MissingWidth']); |
|
foreach($map as $c=>$v) |
|
{ |
|
if($v['name']!='.notdef') |
|
{ |
|
if(isset($ttf->chars[$v['uv']])) |
|
{ |
|
$id = $ttf->chars[$v['uv']]; |
|
$w = $ttf->glyphs[$id]['w']; |
|
$widths[$c] = round($k*$w); |
|
} |
|
else |
|
Warning('Character '.$v['name'].' is missing'); |
|
} |
|
} |
|
$info['Widths'] = $widths; |
|
return $info; |
|
} |
|
|
|
function GetInfoFromType1($file, $embed, $map) |
|
{ |
|
// Return information from a Type1 font |
|
if($embed) |
|
{ |
|
$f = fopen($file, 'rb'); |
|
if(!$f) |
|
Error('Can\'t open font file'); |
|
// Read first segment |
|
$a = unpack('Cmarker/Ctype/Vsize', fread($f,6)); |
|
if($a['marker']!=128) |
|
Error('Font file is not a valid binary Type1'); |
|
$size1 = $a['size']; |
|
$data = fread($f, $size1); |
|
// Read second segment |
|
$a = unpack('Cmarker/Ctype/Vsize', fread($f,6)); |
|
if($a['marker']!=128) |
|
Error('Font file is not a valid binary Type1'); |
|
$size2 = $a['size']; |
|
$data .= fread($f, $size2); |
|
fclose($f); |
|
$info['Data'] = $data; |
|
$info['Size1'] = $size1; |
|
$info['Size2'] = $size2; |
|
} |
|
|
|
$afm = substr($file, 0, -3).'afm'; |
|
if(!file_exists($afm)) |
|
Error('AFM font file not found: '.$afm); |
|
$a = file($afm); |
|
if(empty($a)) |
|
Error('AFM file empty or not readable'); |
|
foreach($a as $line) |
|
{ |
|
$e = explode(' ', rtrim($line)); |
|
if(count($e)<2) |
|
continue; |
|
$entry = $e[0]; |
|
if($entry=='C') |
|
{ |
|
$w = $e[4]; |
|
$name = $e[7]; |
|
$cw[$name] = $w; |
|
} |
|
elseif($entry=='FontName') |
|
$info['FontName'] = $e[1]; |
|
elseif($entry=='Weight') |
|
$info['Weight'] = $e[1]; |
|
elseif($entry=='ItalicAngle') |
|
$info['ItalicAngle'] = (int)$e[1]; |
|
elseif($entry=='Ascender') |
|
$info['Ascender'] = (int)$e[1]; |
|
elseif($entry=='Descender') |
|
$info['Descender'] = (int)$e[1]; |
|
elseif($entry=='UnderlineThickness') |
|
$info['UnderlineThickness'] = (int)$e[1]; |
|
elseif($entry=='UnderlinePosition') |
|
$info['UnderlinePosition'] = (int)$e[1]; |
|
elseif($entry=='IsFixedPitch') |
|
$info['IsFixedPitch'] = ($e[1]=='true'); |
|
elseif($entry=='FontBBox') |
|
$info['FontBBox'] = array((int)$e[1], (int)$e[2], (int)$e[3], (int)$e[4]); |
|
elseif($entry=='CapHeight') |
|
$info['CapHeight'] = (int)$e[1]; |
|
elseif($entry=='StdVW') |
|
$info['StdVW'] = (int)$e[1]; |
|
} |
|
|
|
if(!isset($info['FontName'])) |
|
Error('FontName missing in AFM file'); |
|
if(!isset($info['Ascender'])) |
|
$info['Ascender'] = $info['FontBBox'][3]; |
|
if(!isset($info['Descender'])) |
|
$info['Descender'] = $info['FontBBox'][1]; |
|
$info['Bold'] = isset($info['Weight']) && preg_match('/bold|black/i', $info['Weight']); |
|
if(isset($cw['.notdef'])) |
|
$info['MissingWidth'] = $cw['.notdef']; |
|
else |
|
$info['MissingWidth'] = 0; |
|
$widths = array_fill(0, 256, $info['MissingWidth']); |
|
foreach($map as $c=>$v) |
|
{ |
|
if($v['name']!='.notdef') |
|
{ |
|
if(isset($cw[$v['name']])) |
|
$widths[$c] = $cw[$v['name']]; |
|
else |
|
Warning('Character '.$v['name'].' is missing'); |
|
} |
|
} |
|
$info['Widths'] = $widths; |
|
return $info; |
|
} |
|
|
|
function MakeFontDescriptor($info) |
|
{ |
|
// Ascent |
|
$fd = "array('Ascent'=>".$info['Ascender']; |
|
// Descent |
|
$fd .= ",'Descent'=>".$info['Descender']; |
|
// CapHeight |
|
if(!empty($info['CapHeight'])) |
|
$fd .= ",'CapHeight'=>".$info['CapHeight']; |
|
else |
|
$fd .= ",'CapHeight'=>".$info['Ascender']; |
|
// Flags |
|
$flags = 0; |
|
if($info['IsFixedPitch']) |
|
$flags += 1<<0; |
|
$flags += 1<<5; |
|
if($info['ItalicAngle']!=0) |
|
$flags += 1<<6; |
|
$fd .= ",'Flags'=>".$flags; |
|
// FontBBox |
|
$fbb = $info['FontBBox']; |
|
$fd .= ",'FontBBox'=>'[".$fbb[0].' '.$fbb[1].' '.$fbb[2].' '.$fbb[3]."]'"; |
|
// ItalicAngle |
|
$fd .= ",'ItalicAngle'=>".$info['ItalicAngle']; |
|
// StemV |
|
if(isset($info['StdVW'])) |
|
$stemv = $info['StdVW']; |
|
elseif($info['Bold']) |
|
$stemv = 120; |
|
else |
|
$stemv = 70; |
|
$fd .= ",'StemV'=>".$stemv; |
|
// MissingWidth |
|
$fd .= ",'MissingWidth'=>".$info['MissingWidth'].')'; |
|
return $fd; |
|
} |
|
|
|
function MakeWidthArray($widths) |
|
{ |
|
$s = "array(\n\t"; |
|
for($c=0;$c<=255;$c++) |
|
{ |
|
if(chr($c)=="'") |
|
$s .= "'\\''"; |
|
elseif(chr($c)=="\\") |
|
$s .= "'\\\\'"; |
|
elseif($c>=32 && $c<=126) |
|
$s .= "'".chr($c)."'"; |
|
else |
|
$s .= "chr($c)"; |
|
$s .= '=>'.$widths[$c]; |
|
if($c<255) |
|
$s .= ','; |
|
if(($c+1)%22==0) |
|
$s .= "\n\t"; |
|
} |
|
$s .= ')'; |
|
return $s; |
|
} |
|
|
|
function MakeFontEncoding($map) |
|
{ |
|
// Build differences from reference encoding |
|
$ref = LoadMap('cp1252'); |
|
$s = ''; |
|
$last = 0; |
|
for($c=32;$c<=255;$c++) |
|
{ |
|
if($map[$c]['name']!=$ref[$c]['name']) |
|
{ |
|
if($c!=$last+1) |
|
$s .= $c.' '; |
|
$last = $c; |
|
$s .= '/'.$map[$c]['name'].' '; |
|
} |
|
} |
|
return rtrim($s); |
|
} |
|
|
|
function MakeUnicodeArray($map) |
|
{ |
|
// Build mapping to Unicode values |
|
$ranges = array(); |
|
foreach($map as $c=>$v) |
|
{ |
|
$uv = $v['uv']; |
|
if($uv!=-1) |
|
{ |
|
if(isset($range)) |
|
{ |
|
if($c==$range[1]+1 && $uv==$range[3]+1) |
|
{ |
|
$range[1]++; |
|
$range[3]++; |
|
} |
|
else |
|
{ |
|
$ranges[] = $range; |
|
$range = array($c, $c, $uv, $uv); |
|
} |
|
} |
|
else |
|
$range = array($c, $c, $uv, $uv); |
|
} |
|
} |
|
$ranges[] = $range; |
|
|
|
foreach($ranges as $range) |
|
{ |
|
if(isset($s)) |
|
$s .= ','; |
|
else |
|
$s = 'array('; |
|
$s .= $range[0].'=>'; |
|
$nb = $range[1]-$range[0]+1; |
|
if($nb>1) |
|
$s .= 'array('.$range[2].','.$nb.')'; |
|
else |
|
$s .= $range[2]; |
|
} |
|
$s .= ')'; |
|
return $s; |
|
} |
|
|
|
function SaveToFile($file, $s, $mode) |
|
{ |
|
$f = fopen($file, 'w'.$mode); |
|
if(!$f) |
|
Error('Can\'t write to file '.$file); |
|
fwrite($f, $s); |
|
fclose($f); |
|
} |
|
|
|
function MakeDefinitionFile($file, $type, $enc, $embed, $subset, $map, $info) |
|
{ |
|
$s = "<?php\n"; |
|
$s .= '$type = \''.$type."';\n"; |
|
$s .= '$name = \''.$info['FontName']."';\n"; |
|
$s .= '$desc = '.MakeFontDescriptor($info).";\n"; |
|
$s .= '$up = '.$info['UnderlinePosition'].";\n"; |
|
$s .= '$ut = '.$info['UnderlineThickness'].";\n"; |
|
$s .= '$cw = '.MakeWidthArray($info['Widths']).";\n"; |
|
$s .= '$enc = \''.$enc."';\n"; |
|
$diff = MakeFontEncoding($map); |
|
if($diff) |
|
$s .= '$diff = \''.$diff."';\n"; |
|
$s .= '$uv = '.MakeUnicodeArray($map).";\n"; |
|
if($embed) |
|
{ |
|
$s .= '$file = \''.$info['File']."';\n"; |
|
if($type=='Type1') |
|
{ |
|
$s .= '$size1 = '.$info['Size1'].";\n"; |
|
$s .= '$size2 = '.$info['Size2'].";\n"; |
|
} |
|
else |
|
{ |
|
$s .= '$originalsize = '.$info['OriginalSize'].";\n"; |
|
if($subset) |
|
$s .= "\$subsetted = true;\n"; |
|
} |
|
} |
|
$s .= "?>\n"; |
|
SaveToFile($file, $s, 't'); |
|
} |
|
|
|
function MakeFont($fontfile, $enc='cp1252', $embed=true, $subset=true) |
|
{ |
|
// Generate a font definition file |
|
if(!file_exists($fontfile)) |
|
Error('Font file not found: '.$fontfile); |
|
$ext = strtolower(substr($fontfile,-3)); |
|
if($ext=='ttf' || $ext=='otf') |
|
$type = 'TrueType'; |
|
elseif($ext=='pfb') |
|
$type = 'Type1'; |
|
else |
|
Error('Unrecognized font file extension: '.$ext); |
|
|
|
$map = LoadMap($enc); |
|
|
|
if($type=='TrueType') |
|
$info = GetInfoFromTrueType($fontfile, $embed, $subset, $map); |
|
else |
|
$info = GetInfoFromType1($fontfile, $embed, $map); |
|
|
|
$basename = substr(basename($fontfile), 0, -4); |
|
if($embed) |
|
{ |
|
if(function_exists('gzcompress')) |
|
{ |
|
$file = $basename.'.z'; |
|
SaveToFile($file, gzcompress($info['Data']), 'b'); |
|
$info['File'] = $file; |
|
Message('Font file compressed: '.$file); |
|
} |
|
else |
|
{ |
|
$info['File'] = basename($fontfile); |
|
$subset = false; |
|
Notice('Font file could not be compressed (zlib extension not available)'); |
|
} |
|
} |
|
|
|
MakeDefinitionFile($basename.'.php', $type, $enc, $embed, $subset, $map, $info); |
|
Message('Font definition file generated: '.$basename.'.php'); |
|
} |
|
|
|
if(PHP_SAPI=='cli') |
|
{ |
|
// Command-line interface |
|
ini_set('log_errors', '0'); |
|
if($argc==1) |
|
die("Usage: php makefont.php fontfile [encoding] [embed] [subset]\n"); |
|
$fontfile = $argv[1]; |
|
if($argc>=3) |
|
$enc = $argv[2]; |
|
else |
|
$enc = 'cp1252'; |
|
if($argc>=4) |
|
$embed = ($argv[3]=='true' || $argv[3]=='1'); |
|
else |
|
$embed = true; |
|
if($argc>=5) |
|
$subset = ($argv[4]=='true' || $argv[4]=='1'); |
|
else |
|
$subset = true; |
|
MakeFont($fontfile, $enc, $embed, $subset); |
|
} |
|
?>
|
|
|