1<?php 2/******************************************************************************* 3* Utility to generate font definition files * 4* * 5* Version: 1.3 * 6* Date: 2015-11-29 * 7* Author: Olivier PLATHEY * 8*******************************************************************************/ 9 10require('ttfparser.php'); 11 12function 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 28function Notice($txt) 29{ 30 Message($txt, 'Notice'); 31} 32 33function Warning($txt) 34{ 35 Message($txt, 'Warning'); 36} 37 38function Error($txt) 39{ 40 Message($txt, 'Error'); 41 exit; 42} 43 44function 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 62function 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 124function 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 218function 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 255function 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 278function 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 297function 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 342function 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 351function 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 384function MakeFont($fontfile, $enc='cp1252', $embed=true, $subset=true) 385{ 386 // Generate a font definition file 387 if(get_magic_quotes_runtime()) 388 @set_magic_quotes_runtime(false); 389 ini_set('auto_detect_line_endings', '1'); 390 391 if(!file_exists($fontfile)) 392 Error('Font file not found: '.$fontfile); 393 $ext = strtolower(substr($fontfile,-3)); 394 if($ext=='ttf' || $ext=='otf') 395 $type = 'TrueType'; 396 elseif($ext=='pfb') 397 $type = 'Type1'; 398 else 399 Error('Unrecognized font file extension: '.$ext); 400 401 $map = LoadMap($enc); 402 403 if($type=='TrueType') 404 $info = GetInfoFromTrueType($fontfile, $embed, $subset, $map); 405 else 406 $info = GetInfoFromType1($fontfile, $embed, $map); 407 408 $basename = substr(basename($fontfile), 0, -4); 409 if($embed) 410 { 411 if(function_exists('gzcompress')) 412 { 413 $file = $basename.'.z'; 414 SaveToFile($file, gzcompress($info['Data']), 'b'); 415 $info['File'] = $file; 416 Message('Font file compressed: '.$file); 417 } 418 else 419 { 420 $info['File'] = basename($fontfile); 421 $subset = false; 422 Notice('Font file could not be compressed (zlib extension not available)'); 423 } 424 } 425 426 MakeDefinitionFile($basename.'.php', $type, $enc, $embed, $subset, $map, $info); 427 Message('Font definition file generated: '.$basename.'.php'); 428} 429 430if(PHP_SAPI=='cli') 431{ 432 // Command-line interface 433 ini_set('log_errors', '0'); 434 if($argc==1) 435 die("Usage: php makefont.php fontfile [encoding] [embed] [subset]\n"); 436 $fontfile = $argv[1]; 437 if($argc>=3) 438 $enc = $argv[2]; 439 else 440 $enc = 'cp1252'; 441 if($argc>=4) 442 $embed = ($argv[3]=='true' || $argv[3]=='1'); 443 else 444 $embed = true; 445 if($argc>=5) 446 $subset = ($argv[4]=='true' || $argv[4]=='1'); 447 else 448 $subset = true; 449 MakeFont($fontfile, $enc, $embed, $subset); 450} 451?> 452