1 # -*- coding: ISO-8859-1 -*-
4 # Copyright (C) 2006 Jörg Lehmann <joergl@users.sourceforge.net>
6 # This file is part of PyX (http://pyx.sourceforge.net/).
8 # PyX is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # PyX is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with PyX; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 class AFMError(Exception):
38 # various parsing functions
43 raise AFMError("Expecting int, got '%s'" % s
)
47 if s
[0] != "<" or s
[-1] != ">":
49 return int(s
[1:-1], 16)
51 raise AFMError("Expecting hexadecimal int, got '%s'" % s
)
57 raise AFMError("Expecting float, got '%s'" % s
)
59 def _parsefloats(s
, nos
):
62 result
= map(float, numbers
)
63 if len(result
) != nos
:
66 raise AFMError("Expecting list of %d numbers, got '%s'" % (s
, nos
))
70 # XXX: check for invalid characters in s
80 raise AFMError("Expecting boolean, got '%s'" % s
)
84 def __init__(self
, code
, widths
=None, vvector
=None, name
=None, bbox
=None, ligatures
=None):
87 self
.widths
= [None] * 2
90 self
.vvector
= vvector
96 self
.ligatures
= ligatures
100 def __init__(self
, degree
, min_ptsize
, min_kern
, max_ptsize
, max_kern
):
102 self
.min_ptsize
= min_ptsize
103 self
.min_kern
= min_kern
104 self
.max_ptsize
= max_ptsize
105 self
.max_kern
= max_kern
109 def __init__(self
, name1
, name2
, x
, y
):
117 def __init__(self
, name
, parts
):
124 def __init__(self
, filename
):
125 self
.filename
= filename
126 self
.metricssets
= 0 # int, optional
127 self
.fontname
= None # str, required
128 self
.fullname
= None # str, optional
129 self
.familyname
= None # str, optional
130 self
.weight
= None # str, optional
131 self
.fontbbox
= None # 4 floats, required
132 self
.version
= None # str, optional
133 self
.notice
= None # str, optional
134 self
.encodingscheme
= None # str, optional
135 self
.mappingscheme
= None # int, optional (not present in base font programs)
136 self
.escchar
= None # int, required if mappingscheme == 3
137 self
.characterset
= None # str, optional
138 self
.characters
= None # int, optional
139 self
.isbasefont
= 1 # bool, optional
140 self
.vvector
= None # 2 floats, required if metricssets == 2
141 self
.isfixedv
= None # bool, default: true if vvector present, false otherwise
142 self
.capheight
= None # float, optional
143 self
.xheight
= None # float, optional
144 self
.ascender
= None # float, optional
145 self
.descender
= None # float, optional
146 self
.underlinepositions
= [None] * 2 # int, optional (for each direction)
147 self
.underlinethicknesses
= [None] * 2 # float, optional (for each direction)
148 self
.italicangles
= [None] * 2 # float, optional (for each direction)
149 self
.charwidths
= [None] * 2 # 2 floats, optional (for each direction)
150 self
.isfixedpitchs
= [None] * 2 # bool, optional (for each direction)
151 self
.charmetrics
= None # list of character metrics information, optional
152 self
.trackkerns
= None # list of track kernings, optional
153 self
.kernpairs
= [None] * 2 # list of list of kerning pairs (for each direction), optional
154 self
.composites
= None # list of composite character data sets, optional
156 if self
.isfixedv
is None:
157 self
.isfixedv
= self
.vvector
is not None
158 # XXX we should check the constraints on some parameters
160 # the following methods process a line when the reader is in the corresponding
161 # state and return the new state
162 def _processline_start(self
, line
):
163 key
, args
= line
.split(None, 1)
164 if key
!= "StartFontMetrics":
165 raise AFMError("Expecting StartFontMetrics, no found")
166 return _READ_MAIN
, None
168 def _processline_main(self
, line
):
170 key
, args
= line
.split(None, 1)
174 return _READ_MAIN
, None
175 elif key
== "MetricsSets":
176 self
.metricssets
= _parseint(args
)
177 if direction
is not None:
178 raise AFMError("MetricsSets not allowed after first (implicit) StartDirection")
179 elif key
== "FontName":
180 self
.fontname
= _parsestr(args
)
181 elif key
== "FullName":
182 self
.fullname
= _parsestr(args
)
183 elif key
== "FamilyName":
184 self
.familyname
= _parsestr(args
)
185 elif key
== "Weight":
186 self
.weight
= _parsestr(args
)
187 elif key
== "FontBBox":
188 self
.fontbbox
= _parsefloats(args
, 4)
189 elif key
== "Version":
190 self
.version
= _parsestr(args
)
191 elif key
== "Notice":
192 self
.notice
= _parsestr(args
)
193 elif key
== "EncodingScheme":
194 self
.encodingscheme
= _parsestr(args
)
195 elif key
== "MappingScheme":
196 self
.mappingscheme
= _parseint(args
)
197 elif key
== "EscChar":
198 self
.escchar
= _parseint(args
)
199 elif key
== "CharacterSet":
200 self
.characterset
= _parsestr(args
)
201 elif key
== "Characters":
202 self
.characters
= _parseint(args
)
203 elif key
== "IsBaseFont":
204 self
.isbasefont
= _parsebool(args
)
205 elif key
== "VVector":
206 self
.vvector
= _parsefloats(args
, 2)
207 elif key
== "IsFixedV":
208 self
.isfixedv
= _parsebool(args
)
209 elif key
== "CapHeight":
210 self
.capheight
= _parsefloat(args
)
211 elif key
== "XHeight":
212 self
.xheight
= _parsefloat(args
)
213 elif key
== "Ascender":
214 self
.ascender
= _parsefloat(args
)
215 elif key
== "Descender":
216 self
.descender
= _parsefloat(args
)
217 elif key
== "StartDirection":
218 direction
= _parseint(args
)
219 if 0 <= direction
<= 2:
220 return _READ_DIRECTION
, direction
222 raise AFMError("Wrong direction number %d" % direction
)
223 elif (key
== "UnderLinePosition" or key
== "UnderlineThickness" or key
== "ItalicAngle" or
224 key
== "Charwidth" or key
== "IsFixedPitch"):
225 # we implicitly entered a direction section, so we should process the line again
226 return self
._processline
_direction
(line
, 0)
227 elif key
== "StartCharMetrics":
228 if self
.charmetrics
is not None:
229 raise AFMError("Multiple character metrics sections")
230 self
.charmetrics
= [None] * _parseint(args
)
231 return _READ_CHARMETRICS
, 0
232 elif key
== "StartKernData":
233 return _READ_KERNDATA
, None
234 elif key
== "StartComposites":
235 if self
.composites
is not None:
236 raise AFMError("Multiple composite character data sections")
237 self
.composites
= [None] * _parseint(args
)
238 return _READ_COMPOSITES
, 0
239 elif key
== "EndFontMetrics":
240 return _READ_END
, None
241 elif key
[0] in string
.lowercase
:
242 # ignoring private commands
244 return _READ_MAIN
, None
246 def _processline_direction(self
, line
, direction
):
248 key
, args
= line
.split(None, 1)
251 if key
== "UnderLinePosition":
252 self
.underlinepositions
[direction
] = _parseint(args
)
253 elif key
== "UnderlineThickness":
254 self
.underlinethicknesses
[direction
] = _parsefloat(args
)
255 elif key
== "ItalicAngle":
256 self
.italicangles
[direction
] = _parsefloat(args
)
257 elif key
== "Charwidth":
258 self
.charwidths
[direction
] = _parsefloats(args
, 2)
259 elif key
== "IsFixedPitch":
260 self
.isfixedpitchs
[direction
] = _parsebool(args
)
261 elif key
== "EndDirection":
262 return _READ_MAIN
, None
264 # we assume that we are implicitly leaving the direction section again,
265 # so try to reprocess the line in the header reader state
266 return self
._processline
_main
(line
)
267 return _READ_DIRECTION
, direction
269 def _processline_charmetrics(self
, line
, charno
):
270 if line
.rstrip() == "EndCharMetrics":
271 if charno
!= len(self
.charmetrics
):
272 raise AFMError("Fewer character metrics than expected")
273 return _READ_MAIN
, None
274 if charno
>= len(self
.charmetrics
):
275 raise AFMError("More character metrics than expected")
278 for s
in line
.split(";"):
282 key
, args
= s
.split(None, 1)
285 raise AFMError("Cannot define char code twice")
286 char
= AFMcharmetrics(_parseint(args
))
289 raise AFMError("Cannot define char code twice")
290 char
= AFMcharmetrics(_parsehex(args
))
291 elif key
== "WX" or key
== "W0X":
292 char
.widths
[0] = _parsefloat(args
), 0
294 char
.widths
[1] = _parsefloat(args
), 0
295 elif key
== "WY" or key
== "W0Y":
296 char
.widths
[0] = 0, _parsefloat(args
)
298 char
.widths
[1] = 0, _parsefloat(args
)
299 elif key
== "W" or key
== "W0":
300 char
.widths
[0] = _parsefloats(args
, 2)
302 char
.widths
[1] = _parsefloats(args
, 2)
304 char
.vvector
= _parsefloats(args
, 2)
306 # XXX: we should check that name is valid (no whitespcae, etc.)
307 char
.name
= _parsestr(args
)
309 char
.bbox
= _parsefloats(args
, 4)
311 successor
, ligature
= args
.split(None, 1)
312 char
.ligatures
.append((_parsestr(successor
), ligature
))
314 raise AFMError("Undefined command in character widths specification: '%s'", s
)
316 raise AFMError("Character metrics not defined")
318 self
.charmetrics
[charno
] = char
319 return _READ_CHARMETRICS
, charno
+1
321 def _processline_kerndata(self
, line
):
323 key
, args
= line
.split(None, 1)
327 return _READ_KERNDATA
, None
328 if key
== "StartTrackKern":
329 if self
.trackkerns
is not None:
330 raise AFMError("Multiple track kernings data sections")
331 self
.trackkerns
= [None] * _parseint(args
)
332 return _READ_TRACKKERN
, 0
333 elif key
== "StartKernPairs" or key
== "StartKernPairs0":
334 if self
.kernpairs
[0] is not None:
335 raise AFMError("Multiple kerning pairs data sections for direction 0")
336 self
.kernpairs
[0] = [None] * _parseint(args
)
337 return _READ_KERNPAIRS
, (0, 0)
338 elif key
== "StartKernPairs1":
339 if self
.kernpairs
[1] is not None:
340 raise AFMError("Multiple kerning pairs data sections for direction 0")
341 self
.kernpairs
[1] = [None] * _parseint(args
)
342 return _READ_KERNPAIRS
, (1, 0)
343 elif key
== "EndKernData":
344 return _READ_MAIN
, None
346 raise AFMError("Unsupported key %s in kerning data section" % key
)
348 def _processline_trackkern(self
, line
, i
):
350 key
, args
= line
.split(None, 1)
354 return _READ_TRACKKERN
, i
355 elif key
== "TrackKern":
356 if i
>= len(self
.trackkerns
):
357 raise AFMError("More track kerning data sets than expected")
358 degrees
, args
= args
.split(None, 1)
359 self
.trackkerns
[i
] = AFMtrackkern(int(degrees
), *_parsefloats(args
, 4))
360 return _READ_TRACKKERN
, i
+1
361 elif key
== "EndTrackKern":
362 if i
< len(self
.trackkerns
):
363 raise AFMError("Fewer track kerning data sets than expected")
364 return _READ_KERNDATA
, None
366 raise AFMError("Unsupported key %s in kerning data section" % key
)
368 def _processline_kernpairs(self
, line
, (direction
, i
)):
370 key
, args
= line
.split(None, 1)
374 return _READ_KERNPAIRS
, (direction
, i
)
375 elif key
== "EndKernPairs":
376 if i
< len(self
.kernpairs
[direction
]):
377 raise AFMError("Fewer kerning pairs than expected")
378 return _READ_KERNDATA
, None
380 if i
>= len(self
.kernpairs
[direction
]):
381 raise AFMError("More kerning pairs than expected")
384 name1
, name2
, x
, y
= args
.split()
386 raise AFMError("Expecting name1, name2, x, y, got '%s'" % args
)
387 self
.kernpairs
[direction
][i
] = AFMkernpair(name1
, name2
, _parsefloat(x
), _parsefloat(y
))
390 hex1
, hex2
, x
, y
= args
.split()
392 raise AFMError("Expecting <hex1>, <hex2>, x, y, got '%s'" % args
)
393 self
.kernpairs
[direction
][i
] = AFMkernpair(_parsehex(hex1
), _parsehex(hex2
),
394 _parsefloat(x
), _parsefloat(y
))
397 name1
, name2
, x
= args
.split()
399 raise AFMError("Expecting name1, name2, x, got '%s'" % args
)
400 self
.kernpairs
[direction
][i
] = AFMkernpair(name1
, name2
, _parsefloat(x
), 0)
403 name1
, name2
, y
= args
.split()
405 raise AFMError("Expecting name1, name2, x, got '%s'" % args
)
406 self
.kernpairs
[direction
][i
] = AFMkernpair(name1
, name2
, 0, _parsefloat(y
))
408 raise AFMError("Unknown key '%s' in kern pair section" % key
)
409 return _READ_KERNPAIRS
, (direction
, i
+1)
411 def _processline_composites(self
, line
, i
):
412 if line
.rstrip() == "EndComposites":
413 if i
< len(self
.composites
):
414 raise AFMError("Fewer composite character data sets than expected")
415 return _READ_MAIN
, None
416 if i
>= len(self
.composites
):
417 raise AFMError("More composite character data sets than expected")
422 for s
in line
.split(";"):
426 key
, args
= s
.split(None, 1)
429 name
, no
= args
.split()
431 raise AFMError("Expecting name number, got '%s'" % args
)
435 name1
, x
, y
= args
.split()
437 raise AFMError("Expecting name x y, got '%s'" % args
)
438 parts
.append((name1
, _parsefloat(x
), _parsefloat(y
)))
440 raise AFMError("Unknown key '%s' in composite character data section" % key
)
442 raise AFMError("Wrong number of composite characters")
443 self
.composites
[i
] = AFMcomposite(name
, parts
)
444 return _READ_COMPOSITES
, i
+1
447 f
= open(self
.filename
, "r")
449 # state of the reader, consisting of
450 # - the main state, i.e. the type of the section
451 # - a parameter sstate
452 state
= _READ_START
, None
453 # Note that we do a line by line processing here, since one
454 # of the states (_READ_DIRECTION) can be entered implicitly, i.e.
455 # without a corresponding StartDirection section and we thus
456 # may need to reprocess a line in the context of the new state
459 mstate
, sstate
= state
460 if mstate
== _READ_START
:
461 state
= self
._processline
_start
(line
)
463 # except for the first line, any empty will be ignored
466 if mstate
== _READ_MAIN
:
467 state
= self
._processline
_main
(line
)
468 elif mstate
== _READ_DIRECTION
:
469 state
= self
._processline
_direction
(line
, sstate
)
470 elif mstate
== _READ_CHARMETRICS
:
471 state
= self
._processline
_charmetrics
(line
, sstate
)
472 elif mstate
== _READ_KERNDATA
:
473 state
= self
._processline
_kerndata
(line
)
474 elif mstate
== _READ_TRACKKERN
:
475 state
= self
._processline
_trackkern
(line
, sstate
)
476 elif mstate
== _READ_KERNPAIRS
:
477 state
= self
._processline
_kernpairs
(line
, sstate
)
478 elif mstate
== _READ_COMPOSITES
:
479 state
= self
._processline
_composites
(line
, sstate
)
481 raise RuntimeError("Undefined state in AFM reader")
485 if __name__
== "__main__":
486 a
= AFMfile("/opt/local/share/texmf-dist/fonts/afm/yandy/lucida/lbc.afm")
487 print a
.charmetrics
[0].name
488 a
= AFMfile("/usr/share/enscript/hv.afm")
489 print a
.charmetrics
[32].name