makefont.php 11 KB

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