1<?php
2
3require_once(HTML2PS_DIR.'ot.class.php');
4
5/**
6 * @return Array font metrics hash or null of TTF file could not be parsed
7 */
8function ReadTTF($fontfile, $map) {
9  if (!is_readable($fontfile)) { return null; };
10
11  /**
12   * Open font file and read metrics information
13   */
14  $font = new OpenTypeFile();
15  $font->open($fontfile);
16
17  $head =& $font->getTable('head');
18  $name =& $font->getTable('name');
19  $cmap =& $font->getTable('cmap');
20  $hmtx =& $font->getTable('hmtx');
21  $hhea =& $font->getTable('hhea');
22  $post =& $font->getTable('post');
23  $subtable =& $cmap->findSubtable(OT_CMAP_PLATFORM_WINDOWS,
24                                   OT_CMAP_PLATFORM_WINDOWS_UNICODE);
25
26  /**
27   * Prepare initial data
28   */
29  $widths = array();
30
31  for ($i=0; $i<256; $i++) {
32    $code = chr($i);
33    if (!isset($map[$code])) {
34      $widths[] = 1000;
35      continue;
36    };
37    $ucs2 = $map[$code];
38
39    /**
40     * If the font is monospaced, only one entry need be in the array,
41     * but  that entry  is required.  The  last entry  applies to  all
42     * subsequent glyphs.
43     */
44    $glyphIndex = $subtable->lookup($ucs2);
45
46    if (!is_null($glyphIndex)) {
47      $realIndex = min($glyphIndex, $hhea->_numberOfHMetrics-1);
48      $widths[]  = floor($hmtx->_hMetrics[$realIndex]['advanceWidth']*1000/$head->_unitsPerEm);
49    } else {
50      $widths[] = 1000;
51    };
52  };
53
54  $font_info = array();
55
56  /**
57   * Here we use a hack; as, acording to OT specifications,
58   *
59   * When  translated  to  ASCII,  these  [...]  strings  must  be
60   * identical; no  longer than 63  characters; and restricted  to the
61   * printable ASCII subset,  codes 33 through 126, except  for the 10
62   * characters: '[', ']', '(', ')', '{', '}', '<', '>', '/', '%'.
63   *
64   * we can assume that UCS-2 encoded string we receive can be easily
65   * translated to ASCII by removing the high-byte of all two-byte characters
66   */
67  $ps_name_ucs2 = $name->lookup(OT_CMAP_PLATFORM_WINDOWS,
68                                OT_CMAP_PLATFORM_WINDOWS_UNICODE,
69                                OT_CMAP_LANGUAGE_WINDOWS_ENGLISH_AMERICAN,
70                                OT_NAME_ID_POSTSCRIPT_NAME);
71  $ps_name_ascii = "";
72  for ($i=0; $i<strlen($ps_name_ucs2); $i+=2) {
73    $ps_name_ascii .= $ps_name_ucs2{$i+1};
74  };
75
76  $font_info['FontName']           = $ps_name_ascii;
77
78  $font_info['Weight']             = $name->lookup(null, null, null, OT_NAME_ID_SUBFAMILY_NAME);
79  $font_info['ItalicAngle']        = $post->_italicAngle;
80  $font_info['IsFixedPitch']       = (bool)$post->_isFixedPitch;
81  // $font_info['CapHeight']
82  // $font_info['StdVW']
83  $font_info['Ascender']           = floor($hhea->_ascender*1000/$head->_unitsPerEm);
84  $font_info['Descender']          = floor($hhea->_descender*1000/$head->_unitsPerEm);
85  $font_info['UnderlineThickness'] = floor($post->_underlineThickness*1000/$head->_unitsPerEm);
86  $font_info['UnderlinePosition']  = floor($post->_underlinePosition*1000/$head->_unitsPerEm);
87  $font_info['FontBBox']           = array($head->_xMin*1000/$head->_unitsPerEm,
88                                           $head->_yMin*1000/$head->_unitsPerEm,
89                                           $head->_xMax*1000/$head->_unitsPerEm,
90                                           $head->_yMax*1000/$head->_unitsPerEm);
91  $font_info['Widths']             = $widths;
92
93  $font->_delete();
94  unset($font);
95
96  return $font_info;
97}
98
99/**
100 * @return Array font metrics hash or null of AFM file is missing
101 */
102function ReadAFM($file, $map) {
103  if (!is_readable($file)) { return null; };
104
105  $afm_lines = file($file);
106  $widths=array();
107  $fm=array();
108
109  foreach ($afm_lines as $l) {
110    $e=explode(' ',rtrim($l));
111
112    if (count($e)<2) {
113      continue;
114    };
115
116    $code=$e[0];
117    $param=$e[1];
118
119    if ($code=='C') {
120      //Character metrics
121      $cc=(int)$e[1];
122      $w=$e[4];
123      $gn=$e[7];
124      if (substr($gn,-4)=='20AC') {
125        $gn='Euro';
126      };
127
128      $widths[$gn]=$w;
129
130      if ($gn=='.notdef') {
131        $fm['MissingWidth']=$w;
132      };
133    }
134    elseif($code=='FontName')
135      $fm['FontName']=$param;
136    elseif($code=='Weight')
137      $fm['Weight']=$param;
138    elseif($code=='ItalicAngle')
139      $fm['ItalicAngle']=(double)$param;
140    elseif($code=='Ascender')
141      $fm['Ascender']=(int)$param;
142    elseif($code=='Descender')
143      $fm['Descender']=(int)$param;
144    elseif($code=='UnderlineThickness')
145      $fm['UnderlineThickness']=(int)$param;
146    elseif($code=='UnderlinePosition')
147      $fm['UnderlinePosition']=(int)$param;
148    elseif($code=='IsFixedPitch')
149      $fm['IsFixedPitch']=($param=='true');
150    elseif($code=='FontBBox')
151      $fm['FontBBox']=array($e[1],$e[2],$e[3],$e[4]);
152    elseif($code=='CapHeight')
153      $fm['CapHeight']=(int)$param;
154    elseif($code=='StdVW')
155      $fm['StdVW']=(int)$param;
156  }
157
158  if(!isset($fm['FontName'])) {
159    die('FontName not found');
160  };
161
162  if (!isset($widths['.notdef'])) {
163    $widths['.notdef']=600;
164  };
165
166  if (!isset($widths['Delta']) and isset($widths['increment'])) {
167    $widths['Delta']=$widths['increment'];
168  };
169
170  // Order widths according to map
171  for ($i=0; $i<=255; $i++) {
172    if(!isset($widths[$map[chr($i)]])) {
173      error_log('<B>Warning:</B> character '.$map[chr($i)].' is missing<BR>');
174      $widths[$i]=$widths['.notdef'];
175    } else {
176      $widths[$i]=$widths[$map[chr($i)]];
177    };
178  };
179
180  $fm['Widths']=$widths;
181  return $fm;
182}
183
184function MakeFontDescriptor($fm,$symbolic) {
185  //Ascent
186  $asc=(isset($fm['Ascender']) ? $fm['Ascender'] : 1000);
187  $fd="array('Ascent'=>".$asc;
188
189  //Descent
190  $desc=(isset($fm['Descender']) ? $fm['Descender'] : -200);
191  $fd.=",'Descent'=>".$desc;
192
193  //CapHeight
194  if (isset($fm['CapHeight'])) {
195    $ch=$fm['CapHeight'];
196  }  elseif(isset($fm['CapXHeight'])) {
197    $ch=$fm['CapXHeight'];
198  } else {
199    $ch=$asc;
200  };
201  $fd.=",'CapHeight'=>".$ch;
202
203  //Flags
204  $flags=0;
205  if (isset($fm['IsFixedPitch']) and $fm['IsFixedPitch']) {
206    $flags+=1<<0;
207  };
208
209  if ($symbolic) {
210    $flags+=1<<2;
211  };
212
213  if (!$symbolic) {
214    $flags+=1<<5;
215  };
216
217  if (isset($fm['ItalicAngle']) and $fm['ItalicAngle']!=0) {
218    $flags+=1<<6;
219  };
220
221  $fd.=",'Flags'=>".$flags;
222
223  //FontBBox
224  if (isset($fm['FontBBox'])) {
225    $fbb=$fm['FontBBox'];
226  } else {
227    $fbb=array(0,$des-100,1000,$asc+100);
228  };
229
230  $fd.=",'FontBBox'=>'[".$fbb[0].' '.$fbb[1].' '.$fbb[2].' '.$fbb[3]."]'";
231
232  //ItalicAngle
233  $ia=(isset($fm['ItalicAngle']) ? $fm['ItalicAngle'] : 0);
234  $fd.=",'ItalicAngle'=>".$ia;
235
236  //StemV
237  if (isset($fm['StdVW'])) {
238    $stemv=$fm['StdVW'];
239  } elseif(isset($fm['Weight']) and eregi('(bold|black)',$fm['Weight'])) {
240    $stemv=120;
241  } else {
242    $stemv=70;
243  };
244  $fd.=",'StemV'=>".$stemv;
245
246  //MissingWidth
247  if (isset($fm['MissingWidth'])) {
248    $fd.=",'MissingWidth'=>".$fm['MissingWidth'];
249  };
250  $fd.=')';
251
252  return $fd;
253}
254
255function MakeWidthArray($fm) {
256  //Make character width array
257  $s="array(\n\t";
258  $cw=$fm['Widths'];
259  for ($i=0; $i<=255; $i++) {
260    if (chr($i)=="'") {
261      $s.="'\\''";
262    } elseif (chr($i)=="\\") {
263      $s.="'\\\\'";
264    } elseif($i>=32 and $i<=126) {
265      $s.="'".chr($i)."'";
266    } else {
267      $s.="chr($i)";
268    };
269    $s.='=>'.$fm['Widths'][$i];
270    if ($i<255) {
271      $s.=',';
272    };
273
274    if(($i+1)%22==0) {
275      $s.="\n\t";
276    };
277  }
278  $s.=')';
279  return $s;
280}
281
282function MakeFontEncoding($map) {
283  //Build differences from reference encoding
284  $manager = ManagerEncoding::get();
285  $ref = $manager->get_encoding_glyphs('windows-1252');
286
287  $s='';
288  $last=0;
289  for($i=32;$i<=255;$i++) {
290    if ($map[chr($i)]!=$ref[chr($i)]) {
291      if ($i!=$last+1) {
292        $s.=$i.' ';
293      };
294      $last=$i;
295      $s.='/'.$map[chr($i)].' ';
296    };
297  }
298
299  return rtrim($s);
300}
301
302function MakeFontCMap($encoding) {
303  //Build differences from reference encoding
304  $manager = ManagerEncoding::get();
305  $ref = $manager->get_encoding_vector($encoding);
306
307  $s  = "array(\n";
308  foreach ($ref as $char => $ucs) {
309    $s .= sprintf("0x%02X => 0x%04X,\n", ord($char), $ucs);
310  };
311  $s .= ")";
312
313  return trim($s);
314}
315
316function SaveToFile($file,$s,$mode='t')
317{
318  $f=fopen($file,'w'.$mode);
319  if(!$f)
320    die('Can\'t write to file '.$file);
321  fwrite($f,$s,strlen($s));
322  fclose($f);
323}
324
325function ReadShort($f)
326{
327  $a=unpack('n1n',fread($f,2));
328  return $a['n'];
329}
330
331function ReadLong($f)
332{
333  $a=unpack('N1N',fread($f,4));
334  return $a['N'];
335}
336
337function CheckTTF($file)
338{
339  //Check if font license allows embedding
340  $f=fopen($file,'rb');
341  if(!$f)
342    die('<B>Error:</B> Can\'t open '.$file);
343  //Extract number of tables
344  fseek($f,4,SEEK_CUR);
345  $nb=ReadShort($f);
346  fseek($f,6,SEEK_CUR);
347  //Seek OS/2 table
348  $found=false;
349
350  for ($i=0;$i<$nb;$i++) {
351    if (fread($f,4)=='OS/2') {
352      $found=true;
353      break;
354    }
355    fseek($f,12,SEEK_CUR);
356  };
357
358  if (!$found) {
359    fclose($f);
360    return;
361  };
362
363  fseek($f,4,SEEK_CUR);
364  $offset=ReadLong($f);
365  fseek($f,$offset,SEEK_SET);
366
367  //Extract fsType flags
368  fseek($f,8,SEEK_CUR);
369  $fsType=ReadShort($f);
370  $rl=($fsType & 0x02)!=0;
371  $pp=($fsType & 0x04)!=0;
372  $e=($fsType & 0x08)!=0;
373  fclose($f);
374  if ($rl and !$pp and !$e) {
375    echo '<B>Warning:</B> font license does not allow embedding';
376  };
377}
378
379/*******************************************************************************
380 * $fontfile : chemin du fichier TTF (ou cha�ne vide si pas d'incorporation)    *
381 * $afmfile :  chemin du fichier AFM                                            *
382 * $enc :      encodage (ou cha�ne vide si la police est symbolique)            *
383 * $patch :    patch optionnel pour l'encodage                                  *
384 * $type :     type de la police si $fontfile est vide                          *
385 *******************************************************************************/
386function MakeFont($fontfile, $afmfile, $destdir, $destfile, $enc) {
387  // Generate a font definition file
388  set_magic_quotes_runtime(0);
389  ini_set('auto_detect_line_endings','1');
390
391  $manager = ManagerEncoding::get();
392  $map     = $manager->get_encoding_glyphs($enc);
393
394  $fm = ReadAFM($afmfile, $map);
395
396  if (is_null($fm)) {
397    error_log(sprintf("Notice: Missing AFM file '%s'; attempting to parse font file '%s' directly",
398                      $afmfile,
399                      $fontfile));
400
401    $fm = ReadTTF($fontfile, $manager->get_encoding_vector($enc));
402
403    if (is_null($fm)) {
404      die(sprintf("Cannot get font metrics for '%s'", $fontfile));
405    };
406  }
407
408  $diff = MakeFontEncoding($map);
409  $cmap = MakeFontCMap($enc);
410  $fd   = MakeFontDescriptor($fm,empty($map));
411
412  //Find font type
413  if ($fontfile) {
414    $ext=strtolower(substr($fontfile,-3));
415    if ($ext=='ttf') {
416      $type='TrueType';
417    }  elseif($ext=='pfb') {
418      $type='Type1';
419    } else {
420      die('<B>Error:</B> unrecognized font file extension: '.$ext);
421    };
422  } else {
423    if ($type!='TrueType' and $type!='Type1') {
424      die('<B>Error:</B> incorrect font type: '.$type);
425    };
426  }
427
428  //Start generation
429  $s='<?php'."\n";
430  $s.='$type=\''.$type."';\n";
431  $s.='$name=\''.$fm['FontName']."';\n";
432  $s.='$desc='.$fd.";\n";
433  if (!isset($fm['UnderlinePosition'])) {
434    $fm['UnderlinePosition']=-100;
435  };
436  if (!isset($fm['UnderlineThickness'])) {
437    $fm['UnderlineThickness']=50;
438  };
439  $s.='$up='.$fm['UnderlinePosition'].";\n";
440  $s.='$ut='.$fm['UnderlineThickness'].";\n";
441  $w=MakeWidthArray($fm);
442  $s.='$cw='.$w.";\n";
443  $s.='$enc=\''.$enc."';\n";
444  $s.='$diff=\''.$diff."';\n";
445  $s.='$cmap='.$cmap.";\n";
446
447  $basename=substr(basename($afmfile),0,-4);
448
449  if ($fontfile) {
450    //Embedded font
451    if (!file_exists($fontfile)) {
452      die('<B>Error:</B> font file not found: '.$fontfile);
453    };
454
455    if ($type=='TrueType') {
456      CheckTTF($fontfile);
457    };
458
459    $f=fopen($fontfile,'rb');
460    if (!$f) {
461      die('<B>Error:</B> Can\'t open '.$fontfile);
462    };
463
464    $file=fread($f,filesize($fontfile));
465    fclose($f);
466    if ($type=='Type1') {
467      //Find first two sections and discard third one
468      $header=(ord($file{0})==128);
469      if ($header) {
470        //Strip first binary header
471        $file=substr($file,6);
472      }
473      $pos=strpos($file,'eexec');
474      if(!$pos) {
475        die('<B>Error:</B> font file does not seem to be valid Type1');
476      };
477      $size1=$pos+6;
478      if($header and ord($file{$size1})==128) {
479        //Strip second binary header
480        $file=substr($file,0,$size1).substr($file,$size1+6);
481      }
482      $pos=strpos($file,'00000000');
483      if (!$pos) {
484        die('<B>Error:</B> font file does not seem to be valid Type1');
485      };
486
487      $size2=$pos-$size1;
488      $file=substr($file,0,$size1+$size2);
489    }
490
491    $gzcompress_exists = function_exists('gzcompress');
492    if ($gzcompress_exists) {
493      $cmp = $basename.'.z';
494      SaveToFile($destdir.$cmp, gzcompress($file), 'b');
495
496      $s.='$file=\''.$cmp."';\n";
497    } else {
498      $cmp = $basename.'.ttf';
499      SaveToFile($destdir.$cmp, $file, 'b');
500
501      $s.='$file=\''.basename($fontfile)."';\n";
502      error_log('Notice: font file could not be compressed (zlib extension not available)');
503    }
504
505    if ($type=='Type1') {
506      $s.='$size1='.$size1.";\n";
507      $s.='$size2='.$size2.";\n";
508    } else {
509      $s.='$originalsize='.filesize($fontfile).";\n";
510    }
511  } else {
512    //Not embedded font
513    $s.='$file='."'';\n";
514  }
515
516  $s.="?>\n";
517  SaveToFile($destdir.$destfile,$s);
518}
519?>
520