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):
42 raise AFMError("Expecting int, got '%s'" % s
)
46 if s
[0] != "<" or s
[-1] != ">":
48 return int(s
[1:-1], 16)
50 raise AFMError("Expecting hexadecimal int, got '%s'" % s
)
56 raise AFMError("Expecting float, got '%s'" % s
)
58 def parsefloats(s
, nos
):
61 result
= map(float, numbers
)
62 if len(result
) != nos
:
65 raise AFMError("Expecting list of %d numbers, got '%s'" % (s
, nos
))
69 # XXX: check for invalid characters in s
79 raise AFMError("Expecting boolean, got '%s'" % s
)
82 def __init__(self
, code
, widths
=None, vvector
=None, name
=None, bbox
=None, ligatures
=None):
85 self
.widths
= [None] * 2
88 self
.vvector
= vvector
94 self
.ligatures
= ligatures
98 def __init__(self
, degree
, min_ptsize
, min_kern
, max_ptsize
, max_kern
):
100 self
.min_ptsize
= min_ptsize
101 self
.min_kern
= min_kern
102 self
.max_ptsize
= max_ptsize
103 self
.max_kern
= max_kern
107 def __init__(self
, name1
, name2
, x
, y
):
115 def __init__(self
, name
, parts
):
122 def __init__(self
, filename
):
123 self
.filename
= filename
124 self
.metricssets
= 0 # int, optional
125 self
.fontname
= None # str, required
126 self
.fullname
= None # str, optional
127 self
.familyname
= None # str, optional
128 self
.weight
= None # str, optional
129 self
.fontbbox
= None # 4 floats, required
130 self
.version
= None # str, optional
131 self
.notice
= None # str, optional
132 self
.encodingscheme
= None # str, optional
133 self
.mappingscheme
= None # int, optional (not present in base font programs)
134 self
.escchar
= None # int, required if mappingscheme == 3
135 self
.characterset
= None # str, optional
136 self
.characters
= None # int, optional
137 self
.isbasefont
= 1 # bool, optional
138 self
.vvector
= None # 2 floats, required if metricssets == 2
139 self
.isfixedv
= None # bool, default: true if vvector present, false otherwise
140 self
.capheight
= None # float, optional
141 self
.xheight
= None # float, optional
142 self
.ascender
= None # float, optional
143 self
.descender
= None # float, optional
144 self
.underlinepositions
= [None] * 2 # int, optional (for each direction)
145 self
.underlinethicknesses
= [None] * 2 # float, optional (for each direction)
146 self
.italicangles
= [None] * 2 # float, optional (for each direction)
147 self
.charwidths
= [None] * 2 # 2 floats, optional (for each direction)
148 self
.isfixedpitchs
= [None] * 2 # bool, optional (for each direction)
149 self
.charmetrics
= None # list of character metrics information, optional
150 self
.trackkerns
= None # list of track kernings, optional
151 self
.kernpairs
= [None] * 2 # list of list of kerning pairs (for each direction), optional
152 self
.composites
= None # list of composite character data sets, optional
155 # the following methods process a line when the reader is in the corresponding
156 # state and return the new state
157 def _processline_start(self
, line
):
158 key
, args
= line
.split(None, 1)
159 if key
!= "StartFontMetrics":
160 raise AFMError("Expecting StartFontMetrics, no found")
161 return _READ_HEADER
, None
163 def _processline_header(self
, line
):
165 key
, args
= line
.split(None, 1)
169 return _READ_HEADER
, None
170 elif key
== "MetricsSets":
171 self
.metricssets
= parseint(args
)
172 if direction
is not None:
173 raise AFMError("MetricsSets not allowed after first (implicit) StartDirection")
174 elif key
== "FontName":
175 self
.fontname
= parsestr(args
)
176 elif key
== "FullName":
177 self
.fullname
= parsestr(args
)
178 elif key
== "FamilyName":
179 self
.familyname
= parsestr(args
)
180 elif key
== "Weight":
181 self
.weight
= parsestr(args
)
182 elif key
== "FontBBox":
183 self
.fontbbox
= parsefloats(args
, 4)
184 elif key
== "Version":
185 self
.version
= parsestr(args
)
186 elif key
== "Notice":
187 self
.notice
= parsestr(args
)
188 elif key
== "EncodingScheme":
189 self
.encodingscheme
= parsestr(args
)
190 elif key
== "MappingScheme":
191 self
.mappingscheme
= parseint(args
)
192 elif key
== "EscChar":
193 self
.escchar
= parseint(args
)
194 elif key
== "CharacterSet":
195 self
.characterset
= parsestr(args
)
196 elif key
== "Characters":
197 self
.characters
= parseint(args
)
198 elif key
== "IsBaseFont":
199 self
.isbasefont
= parsebool(args
)
200 elif key
== "VVector":
201 self
.vvector
= parsefloats(args
, 2)
202 elif key
== "IsFixedV":
203 self
.isfixedv
= parsebool(args
)
204 elif key
== "CapHeight":
205 self
.capheight
= parsefloat(args
)
206 elif key
== "XHeight":
207 self
.xheight
= parsefloat(args
)
208 elif key
== "Ascender":
209 self
.ascender
= parsefloat(args
)
210 elif key
== "Descender":
211 self
.descender
= parsefloat(args
)
212 elif key
== "StartDirection":
213 direction
= parseint(args
)
214 if 0 <= direction
<= 2:
215 return _READ_DIRECTION
, direction
217 raise AFMError("Wrong direction number %d" % direction
)
218 elif (key
== "UnderLinePosition" or key
== "UnderlineThickness" or key
== "ItalicAngle" or
219 key
== "Charwidth" or key
== "IsFixedPitch"):
220 # we implicitly entered a direction section, so we should process the line again
221 return self
._processline
_direction
(line
, 0)
222 elif key
== "StartCharMetrics":
223 if self
.charmetrics
is not None:
224 raise AFMError("Multiple character metrics sections")
225 self
.charmetrics
= [None] * parseint(args
)
226 return _READ_CHARMETRICS
, 0
227 elif key
== "StartKernData":
228 return _READ_KERNDATA
, None
229 elif key
== "StartComposites":
230 if self
.composites
is not None:
231 raise AFMError("Multiple composite character data sections")
232 self
.composites
= [None] * parseint(args
)
233 return _READ_COMPOSITES
, 0
234 elif key
== "EndFontMetrics":
235 return _READ_END
, None
236 elif key
[0] in string
.lowercase
:
237 # ignoring private commands
239 return _READ_HEADER
, None
241 def _processline_direction(self
, line
, direction
):
243 key
, args
= line
.split(None, 1)
246 if key
== "UnderLinePosition":
247 self
.underlinepositions
[direction
] = parseint(args
)
248 elif key
== "UnderlineThickness":
249 self
.underlinethicknesses
[direction
] = parsefloat(args
)
250 elif key
== "ItalicAngle":
251 self
.italicangles
[direction
] = parsefloat(args
)
252 elif key
== "Charwidth":
253 self
.charwidths
[direction
] = parsefloats(args
, 2)
254 elif key
== "IsFixedPitch":
255 self
.isfixedpitchs
[direction
] = parsebool(args
)
256 elif key
== "EndDirection":
257 return _READ_HEADER
, None
259 # we assume that we are implicitly leaving the direction section again,
260 # so try to reprocess the line in the header reader state
261 return self
._processline
_header
(line
)
262 return _READ_DIRECTION
, direction
264 def _processline_charmetrics(self
, line
, charno
):
265 if line
.rstrip() == "EndCharMetrics":
266 if charno
!= len(self
.charmetrics
):
267 raise AFMError("Fewer character metrics than expected")
268 return _READ_HEADER
, None
269 if charno
>= len(self
.charmetrics
):
270 raise AFMError("More character metrics than expected")
273 for s
in line
.split(";"):
277 key
, args
= s
.split(None, 1)
280 raise AFMError("Cannot define char code twice")
281 char
= AFMcharmetrics(parseint(args
))
284 raise AFMError("Cannot define char code twice")
285 char
= AFMcharmetrics(parsehex(args
))
286 elif key
== "WX" or key
== "W0X":
287 char
.widths
[0] = parsefloat(args
), 0
289 char
.widths
[1] = parsefloat(args
), 0
290 elif key
== "WY" or key
== "W0Y":
291 char
.widths
[0] = 0, parsefloat(args
)
293 char
.widths
[1] = 0, parsefloat(args
)
294 elif key
== "W" or key
== "W0":
295 char
.widths
[0] = parsefloats(args
, 2)
297 char
.widths
[1] = parsefloats(args
, 2)
299 char
.vvector
= parsefloats(args
, 2)
301 # XXX: we should check that name is valid (no whitespcae, etc.)
302 char
.name
= parsestr(args
)
304 char
.bbox
= parsefloats(args
, 4)
306 successor
, ligature
= args
.split(None, 1)
307 char
.ligatures
.append((parsestr(successor
), ligature
))
309 raise AFMError("Undefined command in character widths specification: '%s'", s
)
311 raise AFMError("Character metrics not defined")
313 self
.charmetrics
[charno
] = char
314 return _READ_CHARMETRICS
, charno
+1
316 def _processline_kerndata(self
, line
):
318 key
, args
= line
.split(None, 1)
322 return _READ_KERNDATA
, None
323 if key
== "StartTrackKern":
324 if self
.trackkerns
is not None:
325 raise AFMError("Multiple track kernings data sections")
326 self
.trackkerns
= [None] * parseint(args
)
327 return _READ_TRACKKERN
, 0
328 elif key
== "StartKernPairs" or key
== "StartKernPairs0":
329 if self
.kernpairs
[0] is not None:
330 raise AFMError("Multiple kerning pairs data sections for direction 0")
331 self
.kernpairs
[0] = [None] * parseint(args
)
332 return _READ_KERNPAIRS
, 0
333 elif key
== "StartKernPairs1":
334 if self
.kernpairs
[1] is not None:
335 raise AFMError("Multiple kerning pairs data sections for direction 0")
336 self
.kernpairs
[1] = [None] * parseint(args
)
337 return _READ_KERNPAIRS
, 1
338 elif key
== "EndKernData":
339 return _READ_HEADER
, None
341 raise AFMError("Unsupported key %s in kerning data section" % key
)
343 def _processline_trackkern(self
, line
, i
):
345 key
, args
= line
.split(None, 1)
349 return _READ_TRACKKERN
, i
350 elif key
== "TrackKern":
351 if i
>= len(self
.trackkerns
):
352 raise AFMError("More track kerning data sets than expected")
353 degrees
, args
= args
.split(None, 1)
354 self
.trackkerns
[i
] = AFMtrackkern(int(degrees
), *parsefloats(args
, 4))
355 return _READ_TRACKKERN
, i
+1
356 elif key
== "EndTrackKern":
357 if i
< len(self
.trackkerns
):
358 raise AFMError("Fewer track kerning data sets than expected")
359 return _READ_KERNDATA
, None
361 raise AFMError("Unsupported key %s in kerning data section" % key
)
363 def _processline_kernpairs(self
, line
, (direction
, i
)):
365 key
, args
= line
.split(None, 1)
369 return _READ_KERNPAIRS
, (direction
, i
)
371 if i
>= len(self
.kernpairs
):
372 raise AFMError("More track kerning data sets than expected")
375 name1
, name2
, x
, y
= args
.split()
377 raise AFMError("Expecting name1, name2, x, y, got '%s'" % args
)
378 self
.kernpairs
[direction
][i
] = AFMkernpair(name1
, name2
, parsefloat(x
), parsefloat(y
))
381 hex1
, hex2
, x
, y
= args
.split()
383 raise AFMError("Expecting <hex1>, <hex2>, x, y, got '%s'" % args
)
384 self
.kernpairs
[direction
][i
] = AFMkernpair(parsehex(hex1
), parsehex(hex2
),
385 parsefloat(x
), parsefloat(y
))
388 name1
, name2
, x
= args
.split()
390 raise AFMError("Expecting name1, name2, x, got '%s'" % args
)
391 self
.kernpairs
[direction
][i
] = AFMkernpair(name1
, name2
, parsefloat(x
), 0)
394 name1
, name2
, y
= args
.split()
396 raise AFMError("Expecting name1, name2, x, got '%s'" % args
)
397 self
.kernpairs
[direction
][i
] = AFMkernpair(name1
, name2
, 0, parsefloat(y
))
398 elif key
== "EndKernPairs":
399 if i
< len(self
.kernpairs
[direction
]):
400 raise AFMError("Fewer kerning pairs than expected")
402 raise AFMError("Unknown key '%s' in kern pair section" % key
)
403 return _READ_KERNPAIRS
, (direction
, i
+1)
405 def _processline_composites(self
, line
, i
):
406 if line
.rstrip() == "EndComposites":
407 if i
< len(self
.composites
):
408 raise AFMError("Fewer composite character data sets than expected")
409 return _READ_HEADER
, None
410 if i
>= len(self
.composites
):
411 raise AFMError("More composite character data sets than expected")
416 for s
in line
.split(";"):
420 key
, args
= s
.split(None, 1)
423 name
, no
= args
.split()
425 raise AFMError("Expecting name number, got '%s'" % args
)
429 name1
, x
, y
= args
.split()
431 raise AFMError("Expecting name x y, got '%s'" % args
)
432 parts
.append((name1
, parsefloat(x
), parsefloat(y
)))
434 raise AFMError("Unknown key '%s' in composite character data section" % key
)
436 raise AFMError("Wrong number of composite characters")
437 self
.composites
[i
] = AFMcomposite(name
, parts
)
438 return _READ_COMPOSITES
, i
+1
441 f
= open(self
.filename
, "r")
443 # state of the reader, consisting of
444 # - the main state, i.e. the type of the section
445 # - optionally a substate, which defines the section, if it can occur more than once
446 state
= _READ_START
, None
449 mstate
, sstate
= state
450 if mstate
== _READ_START
:
451 state
= self
._processline
_start
(line
)
453 # except for the first line, any empty will be ignored
456 if mstate
== _READ_HEADER
:
457 state
= self
._processline
_header
(line
)
458 elif mstate
== _READ_DIRECTION
:
459 state
= self
._processline
_direction
(line
, sstate
)
460 elif mstate
== _READ_CHARMETRICS
:
461 state
= self
._processline
_charmetrics
(line
, sstate
)
462 elif mstate
== _READ_KERNDATA
:
463 state
= self
._processline
_kerndata
(line
, sstate
)
464 elif mstate
== _READ_TRACKKERN
:
465 state
= self
._processline
_trackkern
(line
, sstate
)
466 elif mstate
== _READ_KERNPAIRS
:
467 state
= self
._processline
_kernpairs
(line
, sstate
)
468 elif mstate
== _READ_COMPOSITES
:
469 state
= self
._processline
_composites
(line
, sstate
)
471 raise RuntimeError("Undefined state in AFM reader")
475 if __name__
== "__main__":
476 a
= AFMfile("/private/opt/local/share/texmf-dist/fonts/afm/yandy/lucida/lbc.afm")
477 print a
.charmetrics
[0].name