2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2006 Jörg Lehmann <joergl@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 class AFMError(Exception):
39 # various parsing functions
44 raise AFMError("Expecting int, got '%s'" % s
)
48 if s
[0] != "<" or s
[-1] != ">":
50 return int(s
[1:-1], 16)
52 raise AFMError("Expecting hexadecimal int, got '%s'" % s
)
58 raise AFMError("Expecting float, got '%s'" % s
)
60 def _parsefloats(s
, nos
):
63 result
= map(float, numbers
)
64 if len(result
) != nos
:
67 raise AFMError("Expecting list of %d numbers, got '%s'" % (s
, nos
))
71 # XXX: check for invalid characters in s
81 raise AFMError("Expecting boolean, got '%s'" % s
)
85 def __init__(self
, code
, widths
=None, vvector
=None, name
=None, bbox
=None, ligatures
=None):
88 self
.widths
= [None] * 2
91 self
.vvector
= vvector
97 self
.ligatures
= ligatures
101 def __init__(self
, degree
, min_ptsize
, min_kern
, max_ptsize
, max_kern
):
103 self
.min_ptsize
= min_ptsize
104 self
.min_kern
= min_kern
105 self
.max_ptsize
= max_ptsize
106 self
.max_kern
= max_kern
110 def __init__(self
, name1
, name2
, x
, y
):
118 def __init__(self
, name
, parts
):
125 def __init__(self
, filename
):
126 self
.filename
= filename
127 self
.metricssets
= 0 # int, optional
128 self
.fontname
= None # str, required
129 self
.fullname
= None # str, optional
130 self
.familyname
= None # str, optional
131 self
.weight
= None # str, optional
132 self
.fontbbox
= None # 4 floats, required
133 self
.version
= None # str, optional
134 self
.notice
= None # str, optional
135 self
.encodingscheme
= None # str, optional
136 self
.mappingscheme
= None # int, optional (not present in base font programs)
137 self
.escchar
= None # int, required if mappingscheme == 3
138 self
.characterset
= None # str, optional
139 self
.characters
= None # int, optional
140 self
.isbasefont
= 1 # bool, optional
141 self
.vvector
= None # 2 floats, required if metricssets == 2
142 self
.isfixedv
= None # bool, default: true if vvector present, false otherwise
143 self
.capheight
= None # float, optional
144 self
.xheight
= None # float, optional
145 self
.ascender
= None # float, optional
146 self
.descender
= None # float, optional
147 self
.underlinepositions
= [None] * 2 # int, optional (for each direction)
148 self
.underlinethicknesses
= [None] * 2 # float, optional (for each direction)
149 self
.italicangles
= [None] * 2 # float, optional (for each direction)
150 self
.charwidths
= [None] * 2 # 2 floats, optional (for each direction)
151 self
.isfixedpitchs
= [None] * 2 # bool, optional (for each direction)
152 self
.charmetrics
= None # list of character metrics information, optional
153 self
.trackkerns
= None # list of track kernings, optional
154 self
.kernpairs
= [None] * 2 # list of list of kerning pairs (for each direction), optional
155 self
.composites
= None # list of composite character data sets, optional
157 if self
.isfixedv
is None:
158 self
.isfixedv
= self
.vvector
is not None
159 # XXX we should check the constraints on some parameters
161 # the following methods process a line when the reader is in the corresponding
162 # state and return the new state
163 def _processline_start(self
, line
):
164 key
, args
= line
.split(None, 1)
165 if key
!= "StartFontMetrics":
166 raise AFMError("Expecting StartFontMetrics, no found")
167 return _READ_MAIN
, None
169 def _processline_main(self
, line
):
171 key
, args
= line
.split(None, 1)
175 return _READ_MAIN
, None
176 elif key
== "MetricsSets":
177 self
.metricssets
= _parseint(args
)
178 if direction
is not None:
179 raise AFMError("MetricsSets not allowed after first (implicit) StartDirection")
180 elif key
== "FontName":
181 self
.fontname
= _parsestr(args
)
182 elif key
== "FullName":
183 self
.fullname
= _parsestr(args
)
184 elif key
== "FamilyName":
185 self
.familyname
= _parsestr(args
)
186 elif key
== "Weight":
187 self
.weight
= _parsestr(args
)
188 elif key
== "FontBBox":
189 self
.fontbbox
= _parsefloats(args
, 4)
190 elif key
== "Version":
191 self
.version
= _parsestr(args
)
192 elif key
== "Notice":
193 self
.notice
= _parsestr(args
)
194 elif key
== "EncodingScheme":
195 self
.encodingscheme
= _parsestr(args
)
196 elif key
== "MappingScheme":
197 self
.mappingscheme
= _parseint(args
)
198 elif key
== "EscChar":
199 self
.escchar
= _parseint(args
)
200 elif key
== "CharacterSet":
201 self
.characterset
= _parsestr(args
)
202 elif key
== "Characters":
203 self
.characters
= _parseint(args
)
204 elif key
== "IsBaseFont":
205 self
.isbasefont
= _parsebool(args
)
206 elif key
== "VVector":
207 self
.vvector
= _parsefloats(args
, 2)
208 elif key
== "IsFixedV":
209 self
.isfixedv
= _parsebool(args
)
210 elif key
== "CapHeight":
211 self
.capheight
= _parsefloat(args
)
212 elif key
== "XHeight":
213 self
.xheight
= _parsefloat(args
)
214 elif key
== "Ascender":
215 self
.ascender
= _parsefloat(args
)
216 elif key
== "Descender":
217 self
.descender
= _parsefloat(args
)
218 elif key
== "StartDirection":
219 direction
= _parseint(args
)
220 if 0 <= direction
<= 2:
221 return _READ_DIRECTION
, direction
223 raise AFMError("Wrong direction number %d" % direction
)
224 elif (key
== "UnderLinePosition" or key
== "UnderlineThickness" or key
== "ItalicAngle" or
225 key
== "Charwidth" or key
== "IsFixedPitch"):
226 # we implicitly entered a direction section, so we should process the line again
227 return self
._processline
_direction
(line
, 0)
228 elif key
== "StartCharMetrics":
229 if self
.charmetrics
is not None:
230 raise AFMError("Multiple character metrics sections")
231 self
.charmetrics
= [None] * _parseint(args
)
232 return _READ_CHARMETRICS
, 0
233 elif key
== "StartKernData":
234 return _READ_KERNDATA
, None
235 elif key
== "StartComposites":
236 if self
.composites
is not None:
237 raise AFMError("Multiple composite character data sections")
238 self
.composites
= [None] * _parseint(args
)
239 return _READ_COMPOSITES
, 0
240 elif key
== "EndFontMetrics":
241 return _READ_END
, None
242 elif key
[0] in string
.lowercase
:
243 # ignoring private commands
245 return _READ_MAIN
, None
247 def _processline_direction(self
, line
, direction
):
249 key
, args
= line
.split(None, 1)
252 if key
== "UnderLinePosition":
253 self
.underlinepositions
[direction
] = _parseint(args
)
254 elif key
== "UnderlineThickness":
255 self
.underlinethicknesses
[direction
] = _parsefloat(args
)
256 elif key
== "ItalicAngle":
257 self
.italicangles
[direction
] = _parsefloat(args
)
258 elif key
== "Charwidth":
259 self
.charwidths
[direction
] = _parsefloats(args
, 2)
260 elif key
== "IsFixedPitch":
261 self
.isfixedpitchs
[direction
] = _parsebool(args
)
262 elif key
== "EndDirection":
263 return _READ_MAIN
, None
265 # we assume that we are implicitly leaving the direction section again,
266 # so try to reprocess the line in the header reader state
267 return self
._processline
_main
(line
)
268 return _READ_DIRECTION
, direction
270 def _processline_charmetrics(self
, line
, charno
):
271 if line
.rstrip() == "EndCharMetrics":
272 if charno
!= len(self
.charmetrics
):
273 raise AFMError("Fewer character metrics than expected")
274 return _READ_MAIN
, None
275 if charno
>= len(self
.charmetrics
):
276 raise AFMError("More character metrics than expected")
279 for s
in line
.split(";"):
283 key
, args
= s
.split(None, 1)
286 raise AFMError("Cannot define char code twice")
287 char
= AFMcharmetrics(_parseint(args
))
290 raise AFMError("Cannot define char code twice")
291 char
= AFMcharmetrics(_parsehex(args
))
292 elif key
== "WX" or key
== "W0X":
293 char
.widths
[0] = _parsefloat(args
), 0
295 char
.widths
[1] = _parsefloat(args
), 0
296 elif key
== "WY" or key
== "W0Y":
297 char
.widths
[0] = 0, _parsefloat(args
)
299 char
.widths
[1] = 0, _parsefloat(args
)
300 elif key
== "W" or key
== "W0":
301 char
.widths
[0] = _parsefloats(args
, 2)
303 char
.widths
[1] = _parsefloats(args
, 2)
305 char
.vvector
= _parsefloats(args
, 2)
307 # XXX: we should check that name is valid (no whitespcae, etc.)
308 char
.name
= _parsestr(args
)
310 char
.bbox
= _parsefloats(args
, 4)
312 successor
, ligature
= args
.split(None, 1)
313 char
.ligatures
.append((_parsestr(successor
), ligature
))
315 raise AFMError("Undefined command in character widths specification: '%s'", s
)
317 raise AFMError("Character metrics not defined")
319 self
.charmetrics
[charno
] = char
320 return _READ_CHARMETRICS
, charno
+1
322 def _processline_kerndata(self
, line
):
324 key
, args
= line
.split(None, 1)
328 return _READ_KERNDATA
, None
329 if key
== "StartTrackKern":
330 if self
.trackkerns
is not None:
331 raise AFMError("Multiple track kernings data sections")
332 self
.trackkerns
= [None] * _parseint(args
)
333 return _READ_TRACKKERN
, 0
334 elif key
== "StartKernPairs" or key
== "StartKernPairs0":
335 if self
.kernpairs
[0] is not None:
336 raise AFMError("Multiple kerning pairs data sections for direction 0")
337 self
.kernpairs
[0] = [None] * _parseint(args
)
338 return _READ_KERNPAIRS
, (0, 0)
339 elif key
== "StartKernPairs1":
340 if self
.kernpairs
[1] is not None:
341 raise AFMError("Multiple kerning pairs data sections for direction 0")
342 self
.kernpairs
[1] = [None] * _parseint(args
)
343 return _READ_KERNPAIRS
, (1, 0)
344 elif key
== "EndKernData":
345 return _READ_MAIN
, None
347 raise AFMError("Unsupported key %s in kerning data section" % key
)
349 def _processline_trackkern(self
, line
, i
):
351 key
, args
= line
.split(None, 1)
355 return _READ_TRACKKERN
, i
356 elif key
== "TrackKern":
357 if i
>= len(self
.trackkerns
):
358 raise AFMError("More track kerning data sets than expected")
359 degrees
, args
= args
.split(None, 1)
360 self
.trackkerns
[i
] = AFMtrackkern(int(degrees
), *_parsefloats(args
, 4))
361 return _READ_TRACKKERN
, i
+1
362 elif key
== "EndTrackKern":
363 if i
< len(self
.trackkerns
):
364 raise AFMError("Fewer track kerning data sets than expected")
365 return _READ_KERNDATA
, None
367 raise AFMError("Unsupported key %s in kerning data section" % key
)
369 def _processline_kernpairs(self
, line
, (direction
, i
)):
371 key
, args
= line
.split(None, 1)
375 return _READ_KERNPAIRS
, (direction
, i
)
376 elif key
== "EndKernPairs":
377 if i
< len(self
.kernpairs
[direction
]):
378 raise AFMError("Fewer kerning pairs than expected")
379 return _READ_KERNDATA
, None
381 if i
>= len(self
.kernpairs
[direction
]):
382 raise AFMError("More kerning pairs than expected")
385 name1
, name2
, x
, y
= args
.split()
387 raise AFMError("Expecting name1, name2, x, y, got '%s'" % args
)
388 self
.kernpairs
[direction
][i
] = AFMkernpair(name1
, name2
, _parsefloat(x
), _parsefloat(y
))
391 hex1
, hex2
, x
, y
= args
.split()
393 raise AFMError("Expecting <hex1>, <hex2>, x, y, got '%s'" % args
)
394 self
.kernpairs
[direction
][i
] = AFMkernpair(_parsehex(hex1
), _parsehex(hex2
),
395 _parsefloat(x
), _parsefloat(y
))
398 name1
, name2
, x
= args
.split()
400 raise AFMError("Expecting name1, name2, x, got '%s'" % args
)
401 self
.kernpairs
[direction
][i
] = AFMkernpair(name1
, name2
, _parsefloat(x
), 0)
404 name1
, name2
, y
= args
.split()
406 raise AFMError("Expecting name1, name2, x, got '%s'" % args
)
407 self
.kernpairs
[direction
][i
] = AFMkernpair(name1
, name2
, 0, _parsefloat(y
))
409 raise AFMError("Unknown key '%s' in kern pair section" % key
)
410 return _READ_KERNPAIRS
, (direction
, i
+1)
412 def _processline_composites(self
, line
, i
):
413 if line
.rstrip() == "EndComposites":
414 if i
< len(self
.composites
):
415 raise AFMError("Fewer composite character data sets than expected")
416 return _READ_MAIN
, None
417 if i
>= len(self
.composites
):
418 raise AFMError("More composite character data sets than expected")
423 for s
in line
.split(";"):
427 key
, args
= s
.split(None, 1)
430 name
, no
= args
.split()
432 raise AFMError("Expecting name number, got '%s'" % args
)
436 name1
, x
, y
= args
.split()
438 raise AFMError("Expecting name x y, got '%s'" % args
)
439 parts
.append((name1
, _parsefloat(x
), _parsefloat(y
)))
441 raise AFMError("Unknown key '%s' in composite character data section" % key
)
443 raise AFMError("Wrong number of composite characters")
444 self
.composites
[i
] = AFMcomposite(name
, parts
)
445 return _READ_COMPOSITES
, i
+1
448 f
= open(self
.filename
, "r")
450 # state of the reader, consisting of
451 # - the main state, i.e. the type of the section
452 # - a parameter sstate
453 state
= _READ_START
, None
454 # Note that we do a line by line processing here, since one
455 # of the states (_READ_DIRECTION) can be entered implicitly, i.e.
456 # without a corresponding StartDirection section and we thus
457 # may need to reprocess a line in the context of the new state
460 mstate
, sstate
= state
461 if mstate
== _READ_START
:
462 state
= self
._processline
_start
(line
)
464 # except for the first line, any empty will be ignored
467 if mstate
== _READ_MAIN
:
468 state
= self
._processline
_main
(line
)
469 elif mstate
== _READ_DIRECTION
:
470 state
= self
._processline
_direction
(line
, sstate
)
471 elif mstate
== _READ_CHARMETRICS
:
472 state
= self
._processline
_charmetrics
(line
, sstate
)
473 elif mstate
== _READ_KERNDATA
:
474 state
= self
._processline
_kerndata
(line
)
475 elif mstate
== _READ_TRACKKERN
:
476 state
= self
._processline
_trackkern
(line
, sstate
)
477 elif mstate
== _READ_KERNPAIRS
:
478 state
= self
._processline
_kernpairs
(line
, sstate
)
479 elif mstate
== _READ_COMPOSITES
:
480 state
= self
._processline
_composites
(line
, sstate
)
482 raise RuntimeError("Undefined state in AFM reader")
486 if __name__
== "__main__":
487 a
= AFMfile("/opt/local/share/texmf-dist/fonts/afm/yandy/lucida/lbc.afm")
488 print a
.charmetrics
[0].name
489 a
= AFMfile("/usr/share/enscript/hv.afm")
490 print a
.charmetrics
[32].name