<?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); | |
} | |
?> |