1<?php
2
3$GLOBALS['g_last_assigned_font_id'] = 0;
4
5class Font {
6  var $underline_position;
7  var $underline_thickness;
8  var $ascender;
9  var $descender;
10  var $char_widths;
11  var $bbox;
12
13  function ascender() {
14    return $this->ascender;
15  }
16
17  function descender() {
18    return $this->descender;
19  }
20
21  function error_message() {
22    return $this->error_message;
23  }
24
25  function Font() {}
26
27  function linethrough_position() {
28    return $this->bbox[3]*0.25;
29  }
30
31  function name() {
32    return $this->name;
33  }
34
35  function overline_position() {
36    return $this->bbox[3]*0.8;
37  }
38
39  function points($fontsize, $dimension) {
40    return $dimension * $fontsize / 1000;
41  }
42
43  function stringwidth($string) {
44    $width = 0;
45
46    $length = strlen($string);
47    for ($i=0; $i<$length; $i++) {
48      $width += $this->char_widths[$string{$i}];
49    };
50
51    return $width;
52  }
53
54  function underline_position() {
55    return $this->underline_position;
56  }
57
58  function underline_thickness() {
59    return $this->underline_thickness;
60  }
61}
62
63class FontTrueType extends Font {
64  function create($fontfile, $encoding) {
65    $font = new FontTrueType();
66    $font->_read(TTF_FONTS_REPOSITORY.$fontfile, $encoding);
67    return $font;
68  }
69
70  /**
71   * TODO: cache results; replace makefont with this utility
72   */
73  function _read($file, $encoding) {
74    error_log(sprintf("Parsing font file file %s for encoding %s", $file, $encoding));
75
76    $font = new OpenTypeFile();
77    $font->open($file);
78    $hhea = $font->getTable('hhea');
79    $head = $font->getTable('head');
80    $hmtx = $font->getTable('hmtx');
81    $post = $font->getTable('post');
82    $cmap = $font->getTable('cmap');
83    $subtable = $cmap->findSubtable(OT_CMAP_PLATFORM_WINDOWS,
84                                    OT_CMAP_PLATFORM_WINDOWS_UNICODE);
85
86    /**
87     * Read character widths for selected encoding
88     */
89    $widths = array();
90    $manager = ManagerEncoding::get();
91    $map = $manager->get_encoding_vector($encoding);
92    foreach ($map as $code => $ucs2) {
93      $glyphIndex = $subtable->lookup($ucs2);
94      if (!is_null($glyphIndex)) {
95        $widths[$code] = floor($hmtx->_hMetrics[$glyphIndex]['advanceWidth']*1000/$head->_unitsPerEm);
96      } else {
97        $widths[$code] = DEFAULT_CHAR_WIDTH;
98      };
99    };
100
101    // Fill unknown characters with the default char width
102    for ($i=0; $i<256; $i++) {
103      if (!isset($widths[chr($i)])) {
104        $widths[chr($i)] = DEFAULT_CHAR_WIDTH;
105      };
106    };
107
108    $this->ascender            = floor($hhea->_ascender*1000/$head->_unitsPerEm);
109    $this->descender           = floor($hhea->_descender*1000/$head->_unitsPerEm);
110    $this->bbox                = array($head->_xMin*1000/$head->_unitsPerEm,
111                                       $head->_yMin*1000/$head->_unitsPerEm,
112                                       $head->_xMax*1000/$head->_unitsPerEm,
113                                       $head->_yMax*1000/$head->_unitsPerEm);
114    $this->underline_position  = floor($post->_underlinePosition*1000/$head->_unitsPerEm);
115    $this->underline_thickness = floor($post->_underlineThickness*1000/$head->_unitsPerEm);
116    $this->char_widths         = $widths;
117
118    $font->close();
119  }
120}
121
122// Note that ALL font dimensions are measured in 1/1000 of font size units;
123//
124class FontType1 extends Font {
125  function &create($typeface, $encoding, $font_resolver, &$error_message) {
126    $font = new FontType1();
127
128    $font->underline_position = 0;
129    $font->underline_thickness = 0;
130    $font->ascender;
131    $font->descender;
132    $font->char_widths = array();
133    $font->bbox = array();
134
135    global $g_last_assigned_font_id;
136    $g_last_assigned_font_id++;
137
138    $font->name = "font".$g_last_assigned_font_id;
139
140    // Get and load the metrics file
141    $afm = $font_resolver->get_afm_mapping($typeface);
142
143    if (!$font->_parse_afm($afm, $typeface, $encoding)) {
144      $error_message = $font->error_message();
145      $dummy = null;
146      return $dummy;
147    };
148
149    return $font;
150  }
151
152  // Parse the AFM metric file; keep only sized of glyphs present in the chosen encoding
153  function _parse_afm($afm, $typeface, $encoding) {
154    global $g_manager_encodings;
155    $encoding_data = $g_manager_encodings->get_glyph_to_code_mapping($encoding);
156
157    $filename = TYPE1_FONTS_REPOSITORY.$afm.".afm";
158
159    $file = @fopen($filename, 'r');
160    if (!$file) {
161      $_filename = $filename;
162      $_typeface = $typeface;
163
164      ob_start();
165      include(HTML2PS_DIR.'templates/error._missing_afm.tpl');
166      $this->error_message = ob_get_contents();
167      ob_end_clean();
168
169      error_log(sprintf("Missing font metrics file: %s",$filename));
170      return false;
171    };
172
173    while ($line = fgets($file)) {
174      if (preg_match("/C\s-?\d+\s;\sWX\s(\d+)\s;\sN\s(\S+)\s;/",$line,$matches)) {
175        $glyph_width = $matches[1];
176        $glyph_name  = $matches[2];
177
178        // This line is a character width definition
179        if (isset($encoding_data[$glyph_name])) {
180          foreach ($encoding_data[$glyph_name] as $c) {
181            $this->char_widths[$c] = $glyph_width;
182          };
183        };
184
185      } elseif (preg_match("/UnderlinePosition ([\d-]+)/",$line,$matches)) {
186        // This line is an underline position line
187        $this->underline_position = $matches[1];
188
189      } elseif (preg_match("/UnderlineThickness ([\d-]+)/",$line,$matches)) {
190        // This line is an underline thickness line
191        $this->underline_thickness = $matches[1];
192
193      } elseif (preg_match("/Ascender ([\d-]+)/",$line,$matches)) {
194        // This line is an ascender line
195        $this->ascender = $matches[1];
196
197      } elseif (preg_match("/Descender ([\d-]+)/",$line,$matches)) {
198        // This line is an descender line
199        $this->descender = $matches[1];
200
201      } elseif (preg_match("/FontBBox ([\d-]+) ([\d-]+) ([\d-]+) ([\d-]+)/",$line,$matches)) {
202        // This line is an font BBox line
203        $this->bbox = array($matches[1], $matches[2], $matches[3], $matches[4]);
204      };
205    };
206
207    fclose($file);
208
209    // Fill unknown characters with the default char width
210    for ($i=0; $i<256; $i++) {
211      if (!isset($this->char_widths[chr($i)])) {
212        $this->char_widths[chr($i)] = DEFAULT_CHAR_WIDTH;
213      };
214    };
215
216    return true;
217  }
218}
219?>