blob: fbe8dcf6f217fee688ebd576af6452a8fb8b1e99 [file] [log] [blame]
Copybara botbe50d492023-11-30 00:16:42 +01001<?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
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(!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
426if(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?>