448 lines
10 KiB
PHP
448 lines
10 KiB
PHP
<?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);
|
|
}
|
|
?>
|