Copybara bot | be50d49 | 2023-11-30 00:16:42 +0100 | [diff] [blame] | 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 | ?>
|