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
.glyphs
= None # dictionary mapping glyph names to 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
)
233 return _READ_CHARMETRICS
, 0
234 elif key
== "StartKernData":
235 return _READ_KERNDATA
, None
236 elif key
== "StartComposites":
237 if self
.composites
is not None:
238 raise AFMError("Multiple composite character data sections")
239 self
.composites
= [None] * _parseint(args
)
240 return _READ_COMPOSITES
, 0
241 elif key
== "EndFontMetrics":
242 return _READ_END
, None
243 elif key
[0] in string
.lowercase
:
244 # ignoring private commands
246 return _READ_MAIN
, None
248 def _processline_direction(self
, line
, direction
):
250 key
, args
= line
.split(None, 1)
253 if key
== "UnderLinePosition":
254 self
.underlinepositions
[direction
] = _parseint(args
)
255 elif key
== "UnderlineThickness":
256 self
.underlinethicknesses
[direction
] = _parsefloat(args
)
257 elif key
== "ItalicAngle":
258 self
.italicangles
[direction
] = _parsefloat(args
)
259 elif key
== "Charwidth":
260 self
.charwidths
[direction
] = _parsefloats(args
, 2)
261 elif key
== "IsFixedPitch":
262 self
.isfixedpitchs
[direction
] = _parsebool(args
)
263 elif key
== "EndDirection":
264 return _READ_MAIN
, None
266 # we assume that we are implicitly leaving the direction section again,
267 # so try to reprocess the line in the header reader state
268 return self
._processline
_main
(line
)
269 return _READ_DIRECTION
, direction
271 def _processline_charmetrics(self
, line
, charno
):
272 if line
.rstrip() == "EndCharMetrics":
273 if charno
!= len(self
.charmetrics
):
274 raise AFMError("Fewer character metrics than expected")
275 return _READ_MAIN
, None
276 if charno
>= len(self
.charmetrics
):
277 raise AFMError("More character metrics than expected")
281 for s
in line
.split(";"):
285 key
, args
= s
.split(None, 1)
288 raise AFMError("Cannot define char code twice")
289 char
= AFMcharmetrics(_parseint(args
))
292 raise AFMError("Cannot define char code twice")
293 char
= AFMcharmetrics(_parsehex(args
))
294 elif key
== "WX" or key
== "W0X":
295 char
.widths
[0] = _parsefloat(args
), 0
297 char
.widths
[1] = _parsefloat(args
), 0
298 elif key
== "WY" or key
== "W0Y":
299 char
.widths
[0] = 0, _parsefloat(args
)
301 char
.widths
[1] = 0, _parsefloat(args
)
302 elif key
== "W" or key
== "W0":
303 char
.widths
[0] = _parsefloats(args
, 2)
305 char
.widths
[1] = _parsefloats(args
, 2)
307 char
.vvector
= _parsefloats(args
, 2)
309 # XXX: we should check that name is valid (no whitespcae, etc.)
311 char
.name
= _parsestr(args
)
313 char
.bbox
= _parsefloats(args
, 4)
315 successor
, ligature
= args
.split(None, 1)
316 char
.ligatures
.append((_parsestr(successor
), ligature
))
318 raise AFMError("Undefined command in character widths specification: '%s'", s
)
320 raise AFMError("Character metrics not defined")
322 self
.charmetrics
[charno
] = char
324 self
.glyphs
[char
.name
] = char
325 return _READ_CHARMETRICS
, charno
+1
327 def _processline_kerndata(self
, line
):
329 key
, args
= line
.split(None, 1)
333 return _READ_KERNDATA
, None
334 if key
== "StartTrackKern":
335 if self
.trackkerns
is not None:
336 raise AFMError("Multiple track kernings data sections")
337 self
.trackkerns
= [None] * _parseint(args
)
338 return _READ_TRACKKERN
, 0
339 elif key
== "StartKernPairs" or key
== "StartKernPairs0":
340 if self
.kernpairs
[0] is not None:
341 raise AFMError("Multiple kerning pairs data sections for direction 0")
342 self
.kernpairs
[0] = [None] * _parseint(args
)
343 return _READ_KERNPAIRS
, (0, 0)
344 elif key
== "StartKernPairs1":
345 if self
.kernpairs
[1] is not None:
346 raise AFMError("Multiple kerning pairs data sections for direction 0")
347 self
.kernpairs
[1] = [None] * _parseint(args
)
348 return _READ_KERNPAIRS
, (1, 0)
349 elif key
== "EndKernData":
350 return _READ_MAIN
, None
352 raise AFMError("Unsupported key %s in kerning data section" % key
)
354 def _processline_trackkern(self
, line
, i
):
356 key
, args
= line
.split(None, 1)
360 return _READ_TRACKKERN
, i
361 elif key
== "TrackKern":
362 if i
>= len(self
.trackkerns
):
363 raise AFMError("More track kerning data sets than expected")
364 degrees
, args
= args
.split(None, 1)
365 self
.trackkerns
[i
] = AFMtrackkern(int(degrees
), *_parsefloats(args
, 4))
366 return _READ_TRACKKERN
, i
+1
367 elif key
== "EndTrackKern":
368 if i
< len(self
.trackkerns
):
369 raise AFMError("Fewer track kerning data sets than expected")
370 return _READ_KERNDATA
, None
372 raise AFMError("Unsupported key %s in kerning data section" % key
)
374 def _processline_kernpairs(self
, line
, (direction
, i
)):
376 key
, args
= line
.split(None, 1)
380 return _READ_KERNPAIRS
, (direction
, i
)
381 elif key
== "EndKernPairs":
382 if i
< len(self
.kernpairs
[direction
]):
383 raise AFMError("Fewer kerning pairs than expected")
384 return _READ_KERNDATA
, None
386 if i
>= len(self
.kernpairs
[direction
]):
387 raise AFMError("More kerning pairs than expected")
390 name1
, name2
, x
, y
= args
.split()
392 raise AFMError("Expecting name1, name2, x, y, got '%s'" % args
)
393 self
.kernpairs
[direction
][i
] = AFMkernpair(name1
, name2
, _parsefloat(x
), _parsefloat(y
))
396 hex1
, hex2
, x
, y
= args
.split()
398 raise AFMError("Expecting <hex1>, <hex2>, x, y, got '%s'" % args
)
399 self
.kernpairs
[direction
][i
] = AFMkernpair(_parsehex(hex1
), _parsehex(hex2
),
400 _parsefloat(x
), _parsefloat(y
))
403 name1
, name2
, x
= args
.split()
405 raise AFMError("Expecting name1, name2, x, got '%s'" % args
)
406 self
.kernpairs
[direction
][i
] = AFMkernpair(name1
, name2
, _parsefloat(x
), 0)
409 name1
, name2
, y
= args
.split()
411 raise AFMError("Expecting name1, name2, x, got '%s'" % args
)
412 self
.kernpairs
[direction
][i
] = AFMkernpair(name1
, name2
, 0, _parsefloat(y
))
414 raise AFMError("Unknown key '%s' in kern pair section" % key
)
415 return _READ_KERNPAIRS
, (direction
, i
+1)
417 def _processline_composites(self
, line
, i
):
418 if line
.rstrip() == "EndComposites":
419 if i
< len(self
.composites
):
420 raise AFMError("Fewer composite character data sets than expected")
421 return _READ_MAIN
, None
422 if i
>= len(self
.composites
):
423 raise AFMError("More composite character data sets than expected")
428 for s
in line
.split(";"):
432 key
, args
= s
.split(None, 1)
435 name
, no
= args
.split()
437 raise AFMError("Expecting name number, got '%s'" % args
)
441 name1
, x
, y
= args
.split()
443 raise AFMError("Expecting name x y, got '%s'" % args
)
444 parts
.append((name1
, _parsefloat(x
), _parsefloat(y
)))
446 raise AFMError("Unknown key '%s' in composite character data section" % key
)
448 raise AFMError("Wrong number of composite characters")
449 self
.composites
[i
] = AFMcomposite(name
, parts
)
450 return _READ_COMPOSITES
, i
+1
453 f
= open(self
.filename
, "r")
455 # state of the reader, consisting of
456 # - the main state, i.e. the type of the section
457 # - a parameter sstate
458 state
= _READ_START
, None
459 # Note that we do a line by line processing here, since one
460 # of the states (_READ_DIRECTION) can be entered implicitly, i.e.
461 # without a corresponding StartDirection section and we thus
462 # may need to reprocess a line in the context of the new state
465 mstate
, sstate
= state
466 if mstate
== _READ_START
:
467 state
= self
._processline
_start
(line
)
469 # except for the first line, any empty will be ignored
472 if mstate
== _READ_MAIN
:
473 state
= self
._processline
_main
(line
)
474 elif mstate
== _READ_DIRECTION
:
475 state
= self
._processline
_direction
(line
, sstate
)
476 elif mstate
== _READ_CHARMETRICS
:
477 state
= self
._processline
_charmetrics
(line
, sstate
)
478 elif mstate
== _READ_KERNDATA
:
479 state
= self
._processline
_kerndata
(line
)
480 elif mstate
== _READ_TRACKKERN
:
481 state
= self
._processline
_trackkern
(line
, sstate
)
482 elif mstate
== _READ_KERNPAIRS
:
483 state
= self
._processline
_kernpairs
(line
, sstate
)
484 elif mstate
== _READ_COMPOSITES
:
485 state
= self
._processline
_composites
(line
, sstate
)
487 raise AFMError("Undefined state in AFM reader")
491 def width_ds(self
, glyphname
):
492 return self
.glyphs
[glyphname
].widths
[0][0]
494 def width_pt(self
, glyphnames
, size
):
495 return sum([self
.glyphs
[glyphname
].widths
[0][0] for glyphname
in glyphnames
])*size
/1000.0
497 def height_pt(self
, glyphnames
, size
):
498 return max([self
.glyphs
[glyphname
].bbox
[3] for glyphname
in glyphnames
])*size
/1000.0
500 def depth_pt(self
, glyphnames
, size
):
501 return min([self
.glyphs
[glyphname
].bbox
[1] for glyphname
in glyphnames
])*size
/1000.0
503 def writePDFfontinfo(self
, file):
504 file.write("/Flags 4\n") # any better???
505 if self
.fontbbox
is not None:
506 file.write("/FontBBox [%d %d %d %d]\n" % tuple(self
.fontbbox
))
507 if self
.italicangles
is not None:
508 file.write("/ItalicAngles %d\n" % self
.italicangles
[0])
509 if self
.fontbbox
is not None:
510 file.write("/Ascent %d\n" % self
.fontbbox
[3]) # TODO: any better???
511 if self
.fontbbox
is not None:
512 file.write("/Descent %d\n" % self
.fontbbox
[1]) # TODO: any better???
513 if self
.capheight
is not None:
514 file.write("/CapHeight %d\n" % self
.capheight
)
515 file.write("/StemV %d\n" % 100) # TODO: any better???
518 if __name__
== "__main__":
519 a
= AFMfile("/opt/local/share/texmf-dist/fonts/afm/yandy/lucida/lbc.afm")
520 print a
.charmetrics
[0].name
521 a
= AFMfile("/usr/share/enscript/hv.afm")
522 print a
.charmetrics
[32].name