makefont.php (10873B)
1 <?php 2 /******************************************************************************* 3 * Utility to generate font definition files * 4 * * 5 * Version: 1.31 * 6 * Date: 2019-12-07 * 7 * Author: Olivier PLATHEY * 8 *******************************************************************************/ 9 10 require('ttfparser.php'); 11 12 function Message($txt, $severity='') 13 { 14 if(PHP_SAPI=='cli') 15 { 16 if($severity) 17 echo "$severity: "; 18 echo "$txt\n"; 19 } 20 else 21 { 22 if($severity) 23 echo "<b>$severity</b>: "; 24 echo "$txt<br>"; 25 } 26 } 27 28 function Notice($txt) 29 { 30 Message($txt, 'Notice'); 31 } 32 33 function Warning($txt) 34 { 35 Message($txt, 'Warning'); 36 } 37 38 function Error($txt) 39 { 40 Message($txt, 'Error'); 41 exit; 42 } 43 44 function LoadMap($enc) 45 { 46 $file = dirname(__FILE__).'/'.strtolower($enc).'.map'; 47 $a = file($file); 48 if(empty($a)) 49 Error('Encoding not found: '.$enc); 50 $map = array_fill(0, 256, array('uv'=>-1, 'name'=>'.notdef')); 51 foreach($a as $line) 52 { 53 $e = explode(' ', rtrim($line)); 54 $c = hexdec(substr($e[0],1)); 55 $uv = hexdec(substr($e[1],2)); 56 $name = $e[2]; 57 $map[$c] = array('uv'=>$uv, 'name'=>$name); 58 } 59 return $map; 60 } 61 62 function GetInfoFromTrueType($file, $embed, $subset, $map) 63 { 64 // Return information from a TrueType font 65 try 66 { 67 $ttf = new TTFParser($file); 68 $ttf->Parse(); 69 } 70 catch(Exception $e) 71 { 72 Error($e->getMessage()); 73 } 74 if($embed) 75 { 76 if(!$ttf->embeddable) 77 Error('Font license does not allow embedding'); 78 if($subset) 79 { 80 $chars = array(); 81 foreach($map as $v) 82 { 83 if($v['name']!='.notdef') 84 $chars[] = $v['uv']; 85 } 86 $ttf->Subset($chars); 87 $info['Data'] = $ttf->Build(); 88 } 89 else 90 $info['Data'] = file_get_contents($file); 91 $info['OriginalSize'] = strlen($info['Data']); 92 } 93 $k = 1000/$ttf->unitsPerEm; 94 $info['FontName'] = $ttf->postScriptName; 95 $info['Bold'] = $ttf->bold; 96 $info['ItalicAngle'] = $ttf->italicAngle; 97 $info['IsFixedPitch'] = $ttf->isFixedPitch; 98 $info['Ascender'] = round($k*$ttf->typoAscender); 99 $info['Descender'] = round($k*$ttf->typoDescender); 100 $info['UnderlineThickness'] = round($k*$ttf->underlineThickness); 101 $info['UnderlinePosition'] = round($k*$ttf->underlinePosition); 102 $info['FontBBox'] = array(round($k*$ttf->xMin), round($k*$ttf->yMin), round($k*$ttf->xMax), round($k*$ttf->yMax)); 103 $info['CapHeight'] = round($k*$ttf->capHeight); 104 $info['MissingWidth'] = round($k*$ttf->glyphs[0]['w']); 105 $widths = array_fill(0, 256, $info['MissingWidth']); 106 foreach($map as $c=>$v) 107 { 108 if($v['name']!='.notdef') 109 { 110 if(isset($ttf->chars[$v['uv']])) 111 { 112 $id = $ttf->chars[$v['uv']]; 113 $w = $ttf->glyphs[$id]['w']; 114 $widths[$c] = round($k*$w); 115 } 116 else 117 Warning('Character '.$v['name'].' is missing'); 118 } 119 } 120 $info['Widths'] = $widths; 121 return $info; 122 } 123 124 function GetInfoFromType1($file, $embed, $map) 125 { 126 // Return information from a Type1 font 127 if($embed) 128 { 129 $f = fopen($file, 'rb'); 130 if(!$f) 131 Error('Can\'t open font file'); 132 // Read first segment 133 $a = unpack('Cmarker/Ctype/Vsize', fread($f,6)); 134 if($a['marker']!=128) 135 Error('Font file is not a valid binary Type1'); 136 $size1 = $a['size']; 137 $data = fread($f, $size1); 138 // Read second segment 139 $a = unpack('Cmarker/Ctype/Vsize', fread($f,6)); 140 if($a['marker']!=128) 141 Error('Font file is not a valid binary Type1'); 142 $size2 = $a['size']; 143 $data .= fread($f, $size2); 144 fclose($f); 145 $info['Data'] = $data; 146 $info['Size1'] = $size1; 147 $info['Size2'] = $size2; 148 } 149 150 $afm = substr($file, 0, -3).'afm'; 151 if(!file_exists($afm)) 152 Error('AFM font file not found: '.$afm); 153 $a = file($afm); 154 if(empty($a)) 155 Error('AFM file empty or not readable'); 156 foreach($a as $line) 157 { 158 $e = explode(' ', rtrim($line)); 159 if(count($e)<2) 160 continue; 161 $entry = $e[0]; 162 if($entry=='C') 163 { 164 $w = $e[4]; 165 $name = $e[7]; 166 $cw[$name] = $w; 167 } 168 elseif($entry=='FontName') 169 $info['FontName'] = $e[1]; 170 elseif($entry=='Weight') 171 $info['Weight'] = $e[1]; 172 elseif($entry=='ItalicAngle') 173 $info['ItalicAngle'] = (int)$e[1]; 174 elseif($entry=='Ascender') 175 $info['Ascender'] = (int)$e[1]; 176 elseif($entry=='Descender') 177 $info['Descender'] = (int)$e[1]; 178 elseif($entry=='UnderlineThickness') 179 $info['UnderlineThickness'] = (int)$e[1]; 180 elseif($entry=='UnderlinePosition') 181 $info['UnderlinePosition'] = (int)$e[1]; 182 elseif($entry=='IsFixedPitch') 183 $info['IsFixedPitch'] = ($e[1]=='true'); 184 elseif($entry=='FontBBox') 185 $info['FontBBox'] = array((int)$e[1], (int)$e[2], (int)$e[3], (int)$e[4]); 186 elseif($entry=='CapHeight') 187 $info['CapHeight'] = (int)$e[1]; 188 elseif($entry=='StdVW') 189 $info['StdVW'] = (int)$e[1]; 190 } 191 192 if(!isset($info['FontName'])) 193 Error('FontName missing in AFM file'); 194 if(!isset($info['Ascender'])) 195 $info['Ascender'] = $info['FontBBox'][3]; 196 if(!isset($info['Descender'])) 197 $info['Descender'] = $info['FontBBox'][1]; 198 $info['Bold'] = isset($info['Weight']) && preg_match('/bold|black/i', $info['Weight']); 199 if(isset($cw['.notdef'])) 200 $info['MissingWidth'] = $cw['.notdef']; 201 else 202 $info['MissingWidth'] = 0; 203 $widths = array_fill(0, 256, $info['MissingWidth']); 204 foreach($map as $c=>$v) 205 { 206 if($v['name']!='.notdef') 207 { 208 if(isset($cw[$v['name']])) 209 $widths[$c] = $cw[$v['name']]; 210 else 211 Warning('Character '.$v['name'].' is missing'); 212 } 213 } 214 $info['Widths'] = $widths; 215 return $info; 216 } 217 218 function MakeFontDescriptor($info) 219 { 220 // Ascent 221 $fd = "array('Ascent'=>".$info['Ascender']; 222 // Descent 223 $fd .= ",'Descent'=>".$info['Descender']; 224 // CapHeight 225 if(!empty($info['CapHeight'])) 226 $fd .= ",'CapHeight'=>".$info['CapHeight']; 227 else 228 $fd .= ",'CapHeight'=>".$info['Ascender']; 229 // Flags 230 $flags = 0; 231 if($info['IsFixedPitch']) 232 $flags += 1<<0; 233 $flags += 1<<5; 234 if($info['ItalicAngle']!=0) 235 $flags += 1<<6; 236 $fd .= ",'Flags'=>".$flags; 237 // FontBBox 238 $fbb = $info['FontBBox']; 239 $fd .= ",'FontBBox'=>'[".$fbb[0].' '.$fbb[1].' '.$fbb[2].' '.$fbb[3]."]'"; 240 // ItalicAngle 241 $fd .= ",'ItalicAngle'=>".$info['ItalicAngle']; 242 // StemV 243 if(isset($info['StdVW'])) 244 $stemv = $info['StdVW']; 245 elseif($info['Bold']) 246 $stemv = 120; 247 else 248 $stemv = 70; 249 $fd .= ",'StemV'=>".$stemv; 250 // MissingWidth 251 $fd .= ",'MissingWidth'=>".$info['MissingWidth'].')'; 252 return $fd; 253 } 254 255 function MakeWidthArray($widths) 256 { 257 $s = "array(\n\t"; 258 for($c=0;$c<=255;$c++) 259 { 260 if(chr($c)=="'") 261 $s .= "'\\''"; 262 elseif(chr($c)=="\\") 263 $s .= "'\\\\'"; 264 elseif($c>=32 && $c<=126) 265 $s .= "'".chr($c)."'"; 266 else 267 $s .= "chr($c)"; 268 $s .= '=>'.$widths[$c]; 269 if($c<255) 270 $s .= ','; 271 if(($c+1)%22==0) 272 $s .= "\n\t"; 273 } 274 $s .= ')'; 275 return $s; 276 } 277 278 function MakeFontEncoding($map) 279 { 280 // Build differences from reference encoding 281 $ref = LoadMap('cp1252'); 282 $s = ''; 283 $last = 0; 284 for($c=32;$c<=255;$c++) 285 { 286 if($map[$c]['name']!=$ref[$c]['name']) 287 { 288 if($c!=$last+1) 289 $s .= $c.' '; 290 $last = $c; 291 $s .= '/'.$map[$c]['name'].' '; 292 } 293 } 294 return rtrim($s); 295 } 296 297 function MakeUnicodeArray($map) 298 { 299 // Build mapping to Unicode values 300 $ranges = array(); 301 foreach($map as $c=>$v) 302 { 303 $uv = $v['uv']; 304 if($uv!=-1) 305 { 306 if(isset($range)) 307 { 308 if($c==$range[1]+1 && $uv==$range[3]+1) 309 { 310 $range[1]++; 311 $range[3]++; 312 } 313 else 314 { 315 $ranges[] = $range; 316 $range = array($c, $c, $uv, $uv); 317 } 318 } 319 else 320 $range = array($c, $c, $uv, $uv); 321 } 322 } 323 $ranges[] = $range; 324 325 foreach($ranges as $range) 326 { 327 if(isset($s)) 328 $s .= ','; 329 else 330 $s = 'array('; 331 $s .= $range[0].'=>'; 332 $nb = $range[1]-$range[0]+1; 333 if($nb>1) 334 $s .= 'array('.$range[2].','.$nb.')'; 335 else 336 $s .= $range[2]; 337 } 338 $s .= ')'; 339 return $s; 340 } 341 342 function SaveToFile($file, $s, $mode) 343 { 344 $f = fopen($file, 'w'.$mode); 345 if(!$f) 346 Error('Can\'t write to file '.$file); 347 fwrite($f, $s); 348 fclose($f); 349 } 350 351 function MakeDefinitionFile($file, $type, $enc, $embed, $subset, $map, $info) 352 { 353 $s = "<?php\n"; 354 $s .= '$type = \''.$type."';\n"; 355 $s .= '$name = \''.$info['FontName']."';\n"; 356 $s .= '$desc = '.MakeFontDescriptor($info).";\n"; 357 $s .= '$up = '.$info['UnderlinePosition'].";\n"; 358 $s .= '$ut = '.$info['UnderlineThickness'].";\n"; 359 $s .= '$cw = '.MakeWidthArray($info['Widths']).";\n"; 360 $s .= '$enc = \''.$enc."';\n"; 361 $diff = MakeFontEncoding($map); 362 if($diff) 363 $s .= '$diff = \''.$diff."';\n"; 364 $s .= '$uv = '.MakeUnicodeArray($map).";\n"; 365 if($embed) 366 { 367 $s .= '$file = \''.$info['File']."';\n"; 368 if($type=='Type1') 369 { 370 $s .= '$size1 = '.$info['Size1'].";\n"; 371 $s .= '$size2 = '.$info['Size2'].";\n"; 372 } 373 else 374 { 375 $s .= '$originalsize = '.$info['OriginalSize'].";\n"; 376 if($subset) 377 $s .= "\$subsetted = true;\n"; 378 } 379 } 380 $s .= "?>\n"; 381 SaveToFile($file, $s, 't'); 382 } 383 384 function MakeFont($fontfile, $enc='cp1252', $embed=true, $subset=true) 385 { 386 // Generate a font definition file 387 if(!file_exists($fontfile)) 388 Error('Font file not found: '.$fontfile); 389 $ext = strtolower(substr($fontfile,-3)); 390 if($ext=='ttf' || $ext=='otf') 391 $type = 'TrueType'; 392 elseif($ext=='pfb') 393 $type = 'Type1'; 394 else 395 Error('Unrecognized font file extension: '.$ext); 396 397 $map = LoadMap($enc); 398 399 if($type=='TrueType') 400 $info = GetInfoFromTrueType($fontfile, $embed, $subset, $map); 401 else 402 $info = GetInfoFromType1($fontfile, $embed, $map); 403 404 $basename = substr(basename($fontfile), 0, -4); 405 if($embed) 406 { 407 if(function_exists('gzcompress')) 408 { 409 $file = $basename.'.z'; 410 SaveToFile($file, gzcompress($info['Data']), 'b'); 411 $info['File'] = $file; 412 Message('Font file compressed: '.$file); 413 } 414 else 415 { 416 $info['File'] = basename($fontfile); 417 $subset = false; 418 Notice('Font file could not be compressed (zlib extension not available)'); 419 } 420 } 421 422 MakeDefinitionFile($basename.'.php', $type, $enc, $embed, $subset, $map, $info); 423 Message('Font definition file generated: '.$basename.'.php'); 424 } 425 426 if(PHP_SAPI=='cli') 427 { 428 // Command-line interface 429 ini_set('log_errors', '0'); 430 if($argc==1) 431 die("Usage: php makefont.php fontfile [encoding] [embed] [subset]\n"); 432 $fontfile = $argv[1]; 433 if($argc>=3) 434 $enc = $argv[2]; 435 else 436 $enc = 'cp1252'; 437 if($argc>=4) 438 $embed = ($argv[3]=='true' || $argv[3]=='1'); 439 else 440 $embed = true; 441 if($argc>=5) 442 $subset = ($argv[4]=='true' || $argv[4]=='1'); 443 else 444 $subset = true; 445 MakeFont($fontfile, $enc, $embed, $subset); 446 } 447 ?>