add a share dir todo
[PyX/mjg.git] / pyx / dvifile.py
blobe8038d71ee40b23775d6f15bdc672fb6f13cd4bc
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2005 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
7 # Copyright (C) 2002-2005 André Wobst <wobsta@users.sourceforge.net>
9 # This file is part of PyX (http://pyx.sourceforge.net/).
11 # PyX is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # PyX is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with PyX; if not, write to the Free Software
23 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 import cStringIO, exceptions, re, struct, string, sys, warnings, math
26 import unit, epsfile, bbox, canvas, color, trafo, path, pykpathsea, type1font
29 class binfile:
31 def __init__(self, filename, mode="r"):
32 self.file = open(filename, mode)
34 def close(self):
35 self.file.close()
37 def tell(self):
38 return self.file.tell()
40 def eof(self):
41 return self.file.eof()
43 def read(self, bytes):
44 return self.file.read(bytes)
46 def readint(self, bytes=4, signed=0):
47 first = 1
48 result = 0
49 while bytes:
50 value = ord(self.file.read(1))
51 if first and signed and value > 127:
52 value -= 256
53 first = 0
54 result = 256 * result + value
55 bytes -= 1
56 return result
58 def readint32(self):
59 return struct.unpack(">l", self.file.read(4))[0]
61 def readuint32(self):
62 return struct.unpack(">L", self.file.read(4))[0]
64 def readint24(self):
65 # XXX: checkme
66 return struct.unpack(">l", "\0"+self.file.read(3))[0]
68 def readuint24(self):
69 # XXX: checkme
70 return struct.unpack(">L", "\0"+self.file.read(3))[0]
72 def readint16(self):
73 return struct.unpack(">h", self.file.read(2))[0]
75 def readuint16(self):
76 return struct.unpack(">H", self.file.read(2))[0]
78 def readchar(self):
79 return struct.unpack("b", self.file.read(1))[0]
81 def readuchar(self):
82 return struct.unpack("B", self.file.read(1))[0]
84 def readstring(self, bytes):
85 l = self.readuchar()
86 assert l <= bytes-1, "inconsistency in file: string too long"
87 return self.file.read(bytes-1)[:l]
89 class stringbinfile(binfile):
91 def __init__(self, s):
92 self.file = cStringIO.StringIO(s)
97 ##############################################################################
98 # TFM file handling
99 ##############################################################################
101 class TFMError(exceptions.Exception): pass
104 class char_info_word:
105 def __init__(self, word):
106 self.width_index = int((word & 0xFF000000L) >> 24) #make sign-safe
107 self.height_index = (word & 0x00F00000) >> 20
108 self.depth_index = (word & 0x000F0000) >> 16
109 self.italic_index = (word & 0x0000FC00) >> 10
110 self.tag = (word & 0x00000300) >> 8
111 self.remainder = (word & 0x000000FF)
114 class tfmfile:
115 def __init__(self, name, debug=0):
116 self.file = binfile(name, "rb")
119 # read pre header
122 self.lf = self.file.readint16()
123 self.lh = self.file.readint16()
124 self.bc = self.file.readint16()
125 self.ec = self.file.readint16()
126 self.nw = self.file.readint16()
127 self.nh = self.file.readint16()
128 self.nd = self.file.readint16()
129 self.ni = self.file.readint16()
130 self.nl = self.file.readint16()
131 self.nk = self.file.readint16()
132 self.ne = self.file.readint16()
133 self.np = self.file.readint16()
135 if not (self.bc-1 <= self.ec <= 255 and
136 self.ne <= 256 and
137 self.lf == 6+self.lh+(self.ec-self.bc+1)+self.nw+self.nh+self.nd
138 +self.ni+self.nl+self.nk+self.ne+self.np):
139 raise TFMError, "error in TFM pre-header"
141 if debug:
142 print "lh=%d" % self.lh
145 # read header
148 self.checksum = self.file.readint32()
149 self.designsize = self.file.readint32()
150 assert self.designsize > 0, "invald design size"
151 if self.lh > 2:
152 assert self.lh > 11, "inconsistency in TFM file: incomplete field"
153 self.charcoding = self.file.readstring(40)
154 else:
155 self.charcoding = None
157 if self.lh > 12:
158 assert self.lh > 16, "inconsistency in TFM file: incomplete field"
159 self.fontfamily = self.file.readstring(20)
160 else:
161 self.fontfamily = None
163 if debug:
164 print "(FAMILY %s)" % self.fontfamily
165 print "(CODINGSCHEME %s)" % self.charcoding
166 print "(DESINGSIZE R %f)" % 16.0*self.designsize/16777216L
168 if self.lh > 17:
169 self.sevenbitsave = self.file.readuchar()
170 # ignore the following two bytes
171 self.file.readint16()
172 facechar = self.file.readuchar()
173 # decode ugly face specification into the Knuth suggested string
174 if facechar < 18:
175 if facechar >= 12:
176 self.face = "E"
177 facechar -= 12
178 elif facechar >= 6:
179 self.face = "C"
180 facechar -= 6
181 else:
182 self.face = "R"
184 if facechar >= 4:
185 self.face = "L" + self.face
186 facechar -= 4
187 elif facechar >= 2:
188 self.face = "B" + self.face
189 facechar -= 2
190 else:
191 self.face = "M" + self.face
193 if facechar == 1:
194 self.face = self.face[0] + "I" + self.face[1]
195 else:
196 self.face = self.face[0] + "R" + self.face[1]
198 else:
199 self.face = None
200 else:
201 self.sevenbitsave = self.face = None
203 if self.lh > 18:
204 # just ignore the rest
205 print self.file.read((self.lh-18)*4)
208 # read char_info
211 self.char_info = [None]*(self.ec+1)
212 for charcode in range(self.bc, self.ec+1):
213 self.char_info[charcode] = char_info_word(self.file.readint32())
214 if self.char_info[charcode].width_index == 0:
215 # disable character if width_index is zero
216 self.char_info[charcode] = None
219 # read widths
222 self.width = [None for width_index in range(self.nw)]
223 for width_index in range(self.nw):
224 self.width[width_index] = self.file.readint32()
227 # read heights
230 self.height = [None for height_index in range(self.nh)]
231 for height_index in range(self.nh):
232 self.height[height_index] = self.file.readint32()
235 # read depths
238 self.depth = [None for depth_index in range(self.nd)]
239 for depth_index in range(self.nd):
240 self.depth[depth_index] = self.file.readint32()
243 # read italic
246 self.italic = [None for italic_index in range(self.ni)]
247 for italic_index in range(self.ni):
248 self.italic[italic_index] = self.file.readint32()
251 # read lig_kern
254 # XXX decode to lig_kern_command
256 self.lig_kern = [None for lig_kern_index in range(self.nl)]
257 for lig_kern_index in range(self.nl):
258 self.lig_kern[lig_kern_index] = self.file.readint32()
261 # read kern
264 self.kern = [None for kern_index in range(self.nk)]
265 for kern_index in range(self.nk):
266 self.kern[kern_index] = self.file.readint32()
269 # read exten
272 # XXX decode to extensible_recipe
274 self.exten = [None for exten_index in range(self.ne)]
275 for exten_index in range(self.ne):
276 self.exten[exten_index] = self.file.readint32()
279 # read param
282 # XXX decode
284 self.param = [None for param_index in range(self.np)]
285 for param_index in range(self.np):
286 self.param[param_index] = self.file.readint32()
288 self.file.close()
292 ##############################################################################
293 # Font handling
294 ##############################################################################
297 # PostScript font selection and output primitives
300 class UnsupportedFontFormat(Exception):
301 pass
303 class UnsupportedPSFragment(Exception):
304 pass
306 class fontmapping:
308 tokenpattern = re.compile(r'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
310 def __init__(self, s):
311 """ construct font mapping from line s of font mapping file """
312 self.texname = self.basepsname = self.fontfile = None
314 # standard encoding
315 self.encodingfile = None
317 # supported postscript fragments occuring in psfonts.map
318 self.reencodefont = self.extendfont = self.slantfont = None
320 tokens = []
321 while len(s):
322 match = self.tokenpattern.match(s)
323 if match:
324 if match.groups()[0] is not None:
325 tokens.append('"%s"' % match.groups()[0])
326 else:
327 tokens.append(match.groups()[2])
328 s = s[match.end():]
329 else:
330 raise RuntimeError("wrong syntax")
332 for token in tokens:
333 if token.startswith("<"):
334 if token.startswith("<<"):
335 # XXX: support non-partial download here
336 self.fontfile = token[2:]
337 elif token.startswith("<["):
338 self.encodingfile = token[2:]
339 elif token.endswith(".pfa") or token.endswith(".pfb"):
340 self.fontfile = token[1:]
341 elif token.endswith(".enc"):
342 self.encodingfile = token[1:]
343 elif token.endswith(".ttf"):
344 raise UnsupportedFontFormat("TrueType font")
345 else:
346 raise RuntimeError("wrong syntax")
347 elif token.startswith('"'):
348 pscode = token[1:-1].split()
349 # parse standard postscript code fragments
350 while pscode:
351 try:
352 arg, cmd = pscode[:2]
353 except:
354 raise UnsupportedPSFragment("Unsupported Postscript fragment '%s'" % pscode)
355 pscode = pscode[2:]
356 if cmd == "ReEncodeFont":
357 self.reencodefont = arg
358 elif cmd == "ExtendFont":
359 self.extendfont = arg
360 elif cmd == "SlantFont":
361 self.slantfont = arg
362 else:
363 raise UnsupportedPSFragment("Unsupported Postscript fragment '%s %s'" % (arg, cmd))
364 else:
365 if self.texname is None:
366 self.texname = token
367 else:
368 self.basepsname = token
369 if self.basepsname is None:
370 self.basepsname = self.texname
372 def __str__(self):
373 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
374 (self.texname, self.basepsname, self.fontfile, repr(self.encodingfile)))
376 # generate fontmap
378 def readfontmap(filenames):
379 """ read font map from filename (without path) """
380 fontmap = {}
381 for filename in filenames:
382 mappath = pykpathsea.find_file(filename, pykpathsea.kpse_fontmap_format)
383 # try also the oft-used registration as dvips config file
384 if not mappath:
385 mappath = pykpathsea.find_file(filename, pykpathsea.kpse_dvips_config_format)
386 if not mappath:
387 raise RuntimeError("cannot find font mapping file '%s'" % filename)
388 mapfile = open(mappath, "r")
389 lineno = 0
390 for line in mapfile.readlines():
391 lineno += 1
392 line = line.rstrip()
393 if not (line=="" or line[0] in (" ", "%", "*", ";" , "#")):
394 try:
395 fm = fontmapping(line)
396 except (RuntimeError, UnsupportedPSFragment), e:
397 warnings.warn("Ignoring line %i in mapping file '%s': %s" % (lineno, filename, e))
398 except UnsupportedFontFormat, e:
399 pass
400 else:
401 fontmap[fm.texname] = fm
402 mapfile.close()
403 return fontmap
406 class font:
408 def __init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug=0):
409 self.name = name
410 self.q = q # desired size of font (fix_word) in TeX points
411 self.d = d # design size of font (fix_word) in TeX points
412 self.tfmconv = tfmconv # conversion factor from tfm units to dvi units
413 self.pyxconv = pyxconv # conversion factor from dvi units to PostScript points
414 self.fontmap = fontmap
415 tfmpath = pykpathsea.find_file("%s.tfm" % self.name, pykpathsea.kpse_tfm_format)
416 if not tfmpath:
417 raise TFMError("cannot find %s.tfm" % self.name)
418 self.tfmfile = tfmfile(tfmpath, debug)
420 # We only check for equality of font checksums if none of them
421 # is zero. The case c == 0 happend in some VF files and
422 # according to the VFtoVP documentation, paragraph 40, a check
423 # is only performed if tfmfile.checksum > 0. Anyhow, being
424 # more generous here seems to be reasonable
425 if self.tfmfile.checksum != c and self.tfmfile.checksum != 0 and c != 0:
426 raise DVIError("check sums do not agree: %d vs. %d" %
427 (self.tfmfile.checksum, c))
429 # Check whether the given design size matches the one defined in the tfm file
430 if abs(self.tfmfile.designsize - d) > 2:
431 raise DVIError("design sizes do not agree: %d vs. %d" % (self.tfmfile.designsize, d))
432 #if q < 0 or q > 134217728:
433 # raise DVIError("font '%s' not loaded: bad scale" % self.name)
434 if d < 0 or d > 134217728:
435 raise DVIError("font '%s' not loaded: bad design size" % self.name)
437 self.scale = 1.0*q/d
439 def fontinfo(self):
440 class fontinfo:
441 pass
443 # The following code is a very crude way to obtain the information
444 # required for the PDF font descritor. (TODO: The correct way would
445 # be to read the information from the AFM file.)
446 fontinfo = fontinfo()
447 try:
448 fontinfo.fontbbox = (0,
449 -self.getdepth_ds(ord("y")),
450 self.getwidth_ds(ord("W")),
451 self.getheight_ds(ord("H")))
452 except:
453 fontinfo.fontbbox = (0, -10, 100, 100)
454 try:
455 fontinfo.italicangle = -180/math.pi*math.atan(self.tfmfile.param[0]/65536.0)
456 except IndexError:
457 fontinfo.italicangle = 0
458 fontinfo.ascent = fontinfo.fontbbox[3]
459 fontinfo.descent = fontinfo.fontbbox[1]
460 try:
461 fontinfo.capheight = self.getheight_ds(ord("h"))
462 except:
463 fontinfo.capheight = 100
464 try:
465 fontinfo.vstem = self.getwidth_ds(ord("."))/3
466 except:
467 fontinfo.vstem = 5
468 return fontinfo
470 def __str__(self):
471 return "font %s designed at %g TeX pts used at %g TeX pts" % (self.name,
472 16.0*self.d/16777216L,
473 16.0*self.q/16777216L)
474 __repr__ = __str__
476 def getsize_pt(self):
477 """ return size of font in (PS) points """
478 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
479 # to the corresponding float. Furthermore, we have to convert from TeX
480 # points to points, hence the factor 72/72.27.
481 return 16L*self.q/16777216L*72/72.27
483 def _convert_tfm_to_dvi(self, length):
484 # doing the integer math with long integers will lead to different roundings
485 # return 16*length*int(round(self.q*self.tfmconv))/16777216
487 # Knuth instead suggests the following algorithm based on 4 byte integer logic only
488 # z = int(round(self.q*self.tfmconv))
489 # b0, b1, b2, b3 = [ord(c) for c in struct.pack(">L", length)]
490 # assert b0 == 0 or b0 == 255
491 # shift = 4
492 # while z >= 8388608:
493 # z >>= 1
494 # shift -= 1
495 # assert shift >= 0
496 # result = ( ( ( ( ( b3 * z ) >> 8 ) + ( b2 * z ) ) >> 8 ) + ( b1 * z ) ) >> shift
497 # if b0 == 255:
498 # result = result - (z << (8-shift))
500 # however, we can simplify this using a single long integer multiplication,
501 # but take into account the transformation of z
502 z = int(round(self.q*self.tfmconv))
503 assert -16777216 <= length < 16777216 # -(1 << 24) <= length < (1 << 24)
504 assert z < 134217728 # 1 << 27
505 shift = 20 # 1 << 20
506 while z >= 8388608: # 1 << 23
507 z >>= 1
508 shift -= 1
509 # length*z is a long integer, but the result will be a regular integer
510 return int(length*long(z) >> shift)
512 def _convert_tfm_to_ds(self, length):
513 return (16*long(round(length*float(self.q)*self.tfmconv))/16777216) * self.pyxconv * 1000 / self.getsize_pt()
515 def _convert_tfm_to_pt(self, length):
516 return (16*long(round(length*float(self.q)*self.tfmconv))/16777216) * self.pyxconv
518 # routines returning lengths as integers in dvi units
520 def getwidth_dvi(self, charcode):
521 return self._convert_tfm_to_dvi(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
523 def getheight_dvi(self, charcode):
524 return self._convert_tfm_to_dvi(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
526 def getdepth_dvi(self, charcode):
527 return self._convert_tfm_to_dvi(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
529 def getitalic_dvi(self, charcode):
530 return self._convert_tfm_to_dvi(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
532 # routines returning lengths as integers in design size (AFM) units
534 def getwidth_ds(self, charcode):
535 return self._convert_tfm_to_ds(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
537 def getheight_ds(self, charcode):
538 return self._convert_tfm_to_ds(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
540 def getdepth_ds(self, charcode):
541 return self._convert_tfm_to_ds(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
543 def getitalic_ds(self, charcode):
544 return self._convert_tfm_to_ds(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
546 # routines returning lengths as floats in PostScript points
548 def getwidth_pt(self, charcode):
549 return self._convert_tfm_to_pt(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
551 def getheight_pt(self, charcode):
552 return self._convert_tfm_to_pt(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
554 def getdepth_pt(self, charcode):
555 return self._convert_tfm_to_pt(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
557 def getitalic_pt(self, charcode):
558 return self._convert_tfm_to_pt(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
561 class virtualfont(font):
562 def __init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug=0):
563 fontpath = pykpathsea.find_file(name, pykpathsea.kpse_vf_format)
564 if fontpath is None or not len(fontpath):
565 raise RuntimeError
566 font.__init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug)
567 self.vffile = vffile(fontpath, self.scale, tfmconv, pyxconv, fontmap, debug > 1)
569 def getfonts(self):
570 """ return fonts used in virtual font itself """
571 return self.vffile.getfonts()
573 def getchar(self, cc):
574 """ return dvi chunk corresponding to char code cc """
575 return self.vffile.getchar(cc)
578 ##############################################################################
579 # DVI file handling
580 ##############################################################################
582 _DVI_CHARMIN = 0 # typeset a character and move right (range min)
583 _DVI_CHARMAX = 127 # typeset a character and move right (range max)
584 _DVI_SET1234 = 128 # typeset a character and move right
585 _DVI_SETRULE = 132 # typeset a rule and move right
586 _DVI_PUT1234 = 133 # typeset a character
587 _DVI_PUTRULE = 137 # typeset a rule
588 _DVI_NOP = 138 # no operation
589 _DVI_BOP = 139 # beginning of page
590 _DVI_EOP = 140 # ending of page
591 _DVI_PUSH = 141 # save the current positions (h, v, w, x, y, z)
592 _DVI_POP = 142 # restore positions (h, v, w, x, y, z)
593 _DVI_RIGHT1234 = 143 # move right
594 _DVI_W0 = 147 # move right by w
595 _DVI_W1234 = 148 # move right and set w
596 _DVI_X0 = 152 # move right by x
597 _DVI_X1234 = 153 # move right and set x
598 _DVI_DOWN1234 = 157 # move down
599 _DVI_Y0 = 161 # move down by y
600 _DVI_Y1234 = 162 # move down and set y
601 _DVI_Z0 = 166 # move down by z
602 _DVI_Z1234 = 167 # move down and set z
603 _DVI_FNTNUMMIN = 171 # set current font (range min)
604 _DVI_FNTNUMMAX = 234 # set current font (range max)
605 _DVI_FNT1234 = 235 # set current font
606 _DVI_SPECIAL1234 = 239 # special (dvi extention)
607 _DVI_FNTDEF1234 = 243 # define the meaning of a font number
608 _DVI_PRE = 247 # preamble
609 _DVI_POST = 248 # postamble beginning
610 _DVI_POSTPOST = 249 # postamble ending
612 _DVI_VERSION = 2 # dvi version
614 # position variable indices
615 _POS_H = 0
616 _POS_V = 1
617 _POS_W = 2
618 _POS_X = 3
619 _POS_Y = 4
620 _POS_Z = 5
622 # reader states
623 _READ_PRE = 1
624 _READ_NOPAGE = 2
625 _READ_PAGE = 3
626 _READ_POST = 4 # XXX not used
627 _READ_POSTPOST = 5 # XXX not used
628 _READ_DONE = 6
631 class DVIError(exceptions.Exception): pass
633 # save and restore colors
635 class _savecolor(canvas.canvasitem):
636 def processPS(self, file, writer, context, registry, bbox):
637 file.write("currentcolor currentcolorspace\n")
639 def processPDF(self, file, writer, context, registry, bbox):
640 file.write("q\n")
643 class _restorecolor(canvas.canvasitem):
644 def processPS(self, file, writer, context, registry, bbox):
645 file.write("setcolorspace setcolor\n")
647 def processPDF(self, file, writer, context, registry, bbox):
648 file.write("Q\n")
650 class _savetrafo(canvas.canvasitem):
651 def processPS(self, file, writer, context, registry, bbox):
652 file.write("matrix currentmatrix\n")
654 def processPDF(self, file, writer, context, registry, bbox):
655 file.write("q\n")
658 class _restoretrafo(canvas.canvasitem):
659 def processPS(self, file, writer, context, registry, bbox):
660 file.write("setmatrix\n")
662 def processPDF(self, file, writer, context, registry, bbox):
663 file.write("Q\n")
666 class dvifile:
668 def __init__(self, filename, fontmap, debug=0, debugfile=sys.stdout):
669 """ opens the dvi file and reads the preamble """
670 self.filename = filename
671 self.fontmap = fontmap
672 self.debug = debug
673 self.debugfile = debugfile
674 self.debugstack = []
676 self.fonts = {}
677 self.activefont = None
679 # stack of fonts and fontscale currently used (used for VFs)
680 self.fontstack = []
681 self.stack = []
683 # pointer to currently active page
684 self.actpage = None
686 # stack for self.file, self.fonts and self.stack, needed for VF inclusion
687 self.statestack = []
689 self.file = binfile(self.filename, "rb")
691 # currently read byte in file (for debugging output)
692 self.filepos = None
694 self._read_pre()
696 # helper routines
698 def flushtext(self):
699 """ finish currently active text object """
700 if self.debug and self.activetext:
701 self.debugfile.write("[%s]\n" % "".join([chr(char) for char in self.activetext.chars]))
702 self.activetext = None
704 def putrule(self, height, width, advancepos=1):
705 self.flushtext()
706 x1 = self.pos[_POS_H] * self.pyxconv
707 y1 = -self.pos[_POS_V] * self.pyxconv
708 w = width * self.pyxconv
709 h = height * self.pyxconv
711 if height > 0 and width > 0:
712 if self.debug:
713 self.debugfile.write("%d: %srule height %d, width %d (???x??? pixels)\n" %
714 (self.filepos, advancepos and "set" or "put", height, width))
715 self.actpage.fill(path.rect_pt(x1, y1, w, h))
716 else:
717 if self.debug:
718 self.debugfile.write("%d: %srule height %d, width %d (invisible)\n" %
719 (self.filepos, advancepos and "set" or "put", height, width))
721 if advancepos:
722 if self.debug:
723 self.debugfile.write(" h:=%d+%d=%d, hh:=???\n" %
724 (self.pos[_POS_H], width, self.pos[_POS_H]+width))
725 self.pos[_POS_H] += width
727 def putchar(self, char, advancepos=1, id1234=0):
728 dx = advancepos and self.activefont.getwidth_dvi(char) or 0
730 if self.debug:
731 self.debugfile.write("%d: %s%s%d h:=%d+%d=%d, hh:=???\n" %
732 (self.filepos,
733 advancepos and "set" or "put",
734 id1234 and "%i " % id1234 or "char",
735 char,
736 self.pos[_POS_H], dx, self.pos[_POS_H]+dx))
738 if isinstance(self.activefont, virtualfont):
739 # virtual font handling
740 afterpos = list(self.pos)
741 afterpos[_POS_H] += dx
742 self._push_dvistring(self.activefont.getchar(char), self.activefont.getfonts(), afterpos,
743 self.activefont.getsize_pt())
744 else:
745 if self.activetext is None:
746 if not self.fontmap.has_key(self.activefont.name):
747 raise RuntimeError("missing font information for '%s'; check fontmapping file(s)" % self.activefont.name)
748 fontmapinfo = self.fontmap[self.activefont.name]
750 encodingname = fontmapinfo.reencodefont
751 if encodingname is not None:
752 encodingfilename = pykpathsea.find_file(fontmapinfo.encodingfile, pykpathsea.kpse_tex_ps_header_format)
753 if not encodingfilename:
754 raise RuntimeError("cannot find font encoding file %s" % fontmapinfo.encodingfile)
755 fontencoding = type1font.encoding(encodingname, encodingfilename)
756 else:
757 fontencoding = None
759 fontbasefontname = fontmapinfo.basepsname
760 if fontmapinfo.fontfile is not None:
761 fontfilename = pykpathsea.find_file(fontmapinfo.fontfile, pykpathsea.kpse_type1_format)
762 if not fontfilename:
763 raise RuntimeError("cannot find type 1 font %s" % fontmapinfo.fontfile)
764 else:
765 fontfilename = None
767 # XXX we currently misuse use self.activefont as metric
768 font = type1font.font(fontbasefontname, fontfilename, fontencoding, self.activefont)
770 self.activetext = type1font.text_pt(self.pos[_POS_H] * self.pyxconv, -self.pos[_POS_V] * self.pyxconv, font)
771 self.actpage.insert(self.activetext)
772 self.activetext.addchar(char)
773 self.pos[_POS_H] += dx
775 if not advancepos:
776 self.flushtext()
778 def usefont(self, fontnum, id1234=0):
779 self.flushtext()
780 self.activefont = self.fonts[fontnum]
781 if self.debug:
782 self.debugfile.write("%d: fnt%s%i current font is %s\n" %
783 (self.filepos,
784 id1234 and "%i " % id1234 or "num",
785 fontnum,
786 self.fonts[fontnum].name))
789 def definefont(self, cmdnr, num, c, q, d, fontname):
790 # cmdnr: type of fontdef command (only used for debugging output)
791 # c: checksum
792 # q: scaling factor (fix_word)
793 # Note that q is actually s in large parts of the documentation.
794 # d: design size (fix_word)
796 try:
797 afont = virtualfont(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
798 except (TypeError, RuntimeError):
799 afont = font(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
801 self.fonts[num] = afont
803 if self.debug:
804 self.debugfile.write("%d: fntdef%d %i: %s\n" % (self.filepos, cmdnr, num, fontname))
806 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
807 # m = 1.0*q/d
808 # scalestring = scale!=1000 and " scaled %d" % scale or ""
809 # print ("Font %i: %s%s---loaded at size %d DVI units" %
810 # (num, fontname, scalestring, q))
811 # if scale!=1000:
812 # print " (this font is magnified %d%%)" % round(scale/10)
814 def special(self, s):
815 x = self.pos[_POS_H] * self.pyxconv
816 y = -self.pos[_POS_V] * self.pyxconv
817 if self.debug:
818 self.debugfile.write("%d: xxx '%s'\n" % (self.filepos, s))
819 if not s.startswith("PyX:"):
820 warnings.warn("ignoring special '%s'" % s)
821 return
823 # it is in general not safe to continue using the currently active font because
824 # the specials may involve some gsave/grestore operations
825 self.flushtext()
827 command, args = s[4:].split()[0], s[4:].split()[1:]
828 if command == "color_begin":
829 if args[0] == "cmyk":
830 c = color.cmyk(float(args[1]), float(args[2]), float(args[3]), float(args[4]))
831 elif args[0] == "gray":
832 c = color.gray(float(args[1]))
833 elif args[0] == "hsb":
834 c = color.hsb(float(args[1]), float(args[2]), float(args[3]))
835 elif args[0] == "rgb":
836 c = color.rgb(float(args[1]), float(args[2]), float(args[3]))
837 elif args[0] == "RGB":
838 c = color.rgb(int(args[1])/255.0, int(args[2])/255.0, int(args[3])/255.0)
839 elif args[0] == "texnamed":
840 try:
841 c = getattr(color.cmyk, args[1])
842 except AttributeError:
843 raise RuntimeError("unknown TeX color '%s', aborting" % args[1])
844 elif args[0] == "pyxcolor":
845 try:
846 c = eval(args[1])
847 except NameError:
848 raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % args[1])
849 else:
850 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args[0])
851 self.actpage.insert(_savecolor())
852 self.actpage.insert(c)
853 elif command == "color_end":
854 self.actpage.insert(_restorecolor())
855 elif command == "rotate_begin":
856 self.actpage.insert(_savetrafo())
857 self.actpage.insert(trafo.rotate_pt(float(args[0]), x, y))
858 elif command == "rotate_end":
859 self.actpage.insert(_restoretrafo())
860 elif command == "scale_begin":
861 self.actpage.insert(_savetrafo())
862 self.actpage.insert(trafo.scale_pt(float(args[0]), float(args[1]), x, y))
863 elif command == "scale_end":
864 self.actpage.insert(_restoretrafo())
865 elif command == "epsinclude":
866 # parse arguments
867 argdict = {}
868 for arg in args:
869 name, value = arg.split("=")
870 argdict[name] = value
872 # construct kwargs for epsfile constructor
873 epskwargs = {}
874 epskwargs["filename"] = argdict["file"]
875 epskwargs["bbox"] = bbox.bbox_pt(float(argdict["llx"]), float(argdict["lly"]),
876 float(argdict["urx"]), float(argdict["ury"]))
877 if argdict.has_key("width"):
878 epskwargs["width"] = float(argdict["width"]) * unit.t_pt
879 if argdict.has_key("height"):
880 epskwargs["height"] = float(argdict["height"]) * unit.t_pt
881 if argdict.has_key("clip"):
882 epskwargs["clip"] = int(argdict["clip"])
883 self.actpage.insert(epsfile.epsfile(x * unit.t_pt, y * unit.t_pt, **epskwargs))
884 elif command == "marker":
885 if len(args) != 1:
886 raise RuntimeError("marker contains spaces")
887 for c in args[0]:
888 if c not in string.digits + string.letters + "@":
889 raise RuntimeError("marker contains invalid characters")
890 if self.actpage.markers.has_key(args[0]):
891 raise RuntimeError("marker name occurred several times")
892 self.actpage.markers[args[0]] = x * unit.t_pt, y * unit.t_pt
893 else:
894 raise RuntimeError("unknown PyX special '%s', aborting" % command)
896 # routines for pushing and popping different dvi chunks on the reader
898 def _push_dvistring(self, dvi, fonts, afterpos, fontsize):
899 """ push dvi string with defined fonts on top of reader
900 stack. Every positions gets scaled relatively by the factor
901 scale. After the interpreting of the dvi chunk has been finished,
902 continue with self.pos=afterpos. The designsize of the virtual
903 font is passed as a fix_word
907 #if self.debug:
908 # self.debugfile.write("executing new dvi chunk\n")
909 self.debugstack.append(self.debug)
910 self.debug = 0
912 self.statestack.append((self.file, self.fonts, self.activefont, afterpos, self.stack, self.pyxconv, self.tfmconv))
914 # units in vf files are relative to the size of the font and given as fix_words
915 # which can be converted to floats by diving by 2**20
916 oldpyxconv = self.pyxconv
917 self.pyxconv = fontsize/2**20
918 rescale = self.pyxconv/oldpyxconv
920 self.file = stringbinfile(dvi)
921 self.fonts = fonts
922 self.stack = []
923 self.filepos = 0
925 # rescale self.pos in order to be consistent with the new scaling
926 self.pos = map(lambda x, rescale=rescale:1.0*x/rescale, self.pos)
928 # since tfmconv converts from tfm units to dvi units, rescale it as well
929 self.tfmconv /= rescale
931 self.usefont(0)
933 def _pop_dvistring(self):
934 self.flushtext()
935 #if self.debug:
936 # self.debugfile.write("finished executing dvi chunk\n")
937 self.debug = self.debugstack.pop()
939 self.file.close()
940 self.file, self.fonts, self.activefont, self.pos, self.stack, self.pyxconv, self.tfmconv = self.statestack.pop()
942 # routines corresponding to the different reader states of the dvi maschine
944 def _read_pre(self):
945 afile = self.file
946 while 1:
947 self.filepos = afile.tell()
948 cmd = afile.readuchar()
949 if cmd == _DVI_NOP:
950 pass
951 elif cmd == _DVI_PRE:
952 if afile.readuchar() != _DVI_VERSION: raise DVIError
953 num = afile.readuint32()
954 den = afile.readuint32()
955 self.mag = afile.readuint32()
957 # For the interpretation of the lengths in dvi and tfm files,
958 # three conversion factors are relevant:
959 # - self.tfmconv: tfm units -> dvi units
960 # - self.pyxconv: dvi units -> (PostScript) points
961 # - self.conv: dvi units -> pixels
962 self.tfmconv = (25400000.0/num)*(den/473628672.0)/16.0
964 # calculate conv as described in the DVIType docu using
965 # a given resolution in dpi
966 self.resolution = 300.0
967 self.conv = (num/254000.0)*(self.resolution/den)
969 # self.pyxconv is the conversion factor from the dvi units
970 # to (PostScript) points. It consists of
971 # - self.mag/1000.0: magstep scaling
972 # - self.conv: conversion from dvi units to pixels
973 # - 1/self.resolution: conversion from pixels to inch
974 # - 72 : conversion from inch to points
975 self.pyxconv = self.mag/1000.0*self.conv/self.resolution*72
977 comment = afile.read(afile.readuchar())
978 return
979 else:
980 raise DVIError
982 def readpage(self, pageid=None):
983 """ reads a page from the dvi file
985 This routine reads a page from the dvi file which is
986 returned as a canvas. When there is no page left in the
987 dvifile, None is returned and the file is closed properly."""
989 while 1:
990 self.filepos = self.file.tell()
991 cmd = self.file.readuchar()
992 if cmd == _DVI_NOP:
993 pass
994 elif cmd == _DVI_BOP:
995 ispageid = [self.file.readuint32() for i in range(10)]
996 if pageid is not None and ispageid != pageid:
997 raise DVIError("invalid pageid")
998 if self.debug:
999 self.debugfile.write("%d: beginning of page %i\n" % (self.filepos, ispageid[0]))
1000 self.file.readuint32()
1001 break
1002 elif cmd == _DVI_POST:
1003 self.file.close()
1004 return None # nothing left
1005 else:
1006 raise DVIError
1008 self.actpage = canvas.canvas()
1009 self.actpage.markers = {}
1010 self.pos = [0, 0, 0, 0, 0, 0]
1012 # currently active output: text instance currently used
1013 self.activetext = None
1015 while 1:
1016 afile = self.file
1017 self.filepos = afile.tell()
1018 try:
1019 cmd = afile.readuchar()
1020 except struct.error:
1021 # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
1022 # so we have to continue with the rest of the dvi file
1023 self._pop_dvistring()
1024 continue
1025 if cmd == _DVI_NOP:
1026 pass
1027 if cmd >= _DVI_CHARMIN and cmd <= _DVI_CHARMAX:
1028 self.putchar(cmd)
1029 elif cmd >= _DVI_SET1234 and cmd < _DVI_SET1234 + 4:
1030 self.putchar(afile.readint(cmd - _DVI_SET1234 + 1), id1234=cmd-_DVI_SET1234+1)
1031 elif cmd == _DVI_SETRULE:
1032 self.putrule(afile.readint32(), afile.readint32())
1033 elif cmd >= _DVI_PUT1234 and cmd < _DVI_PUT1234 + 4:
1034 self.putchar(afile.readint(cmd - _DVI_PUT1234 + 1), advancepos=0, id1234=cmd-_DVI_SET1234+1)
1035 elif cmd == _DVI_PUTRULE:
1036 self.putrule(afile.readint32(), afile.readint32(), 0)
1037 elif cmd == _DVI_EOP:
1038 self.flushtext()
1039 if self.debug:
1040 self.debugfile.write("%d: eop\n \n" % self.filepos)
1041 return self.actpage
1042 elif cmd == _DVI_PUSH:
1043 self.stack.append(list(self.pos))
1044 if self.debug:
1045 self.debugfile.write("%s: push\n"
1046 "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1047 ((self.filepos, len(self.stack)-1) + tuple(self.pos)))
1048 elif cmd == _DVI_POP:
1049 self.flushtext()
1050 self.pos = self.stack.pop()
1051 if self.debug:
1052 self.debugfile.write("%s: pop\n"
1053 "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1054 ((self.filepos, len(self.stack)) + tuple(self.pos)))
1055 elif cmd >= _DVI_RIGHT1234 and cmd < _DVI_RIGHT1234 + 4:
1056 self.flushtext()
1057 dh = afile.readint(cmd - _DVI_RIGHT1234 + 1, 1)
1058 if self.debug:
1059 self.debugfile.write("%d: right%d %d h:=%d%+d=%d, hh:=???\n" %
1060 (self.filepos,
1061 cmd - _DVI_RIGHT1234 + 1,
1063 self.pos[_POS_H],
1065 self.pos[_POS_H]+dh))
1066 self.pos[_POS_H] += dh
1067 elif cmd == _DVI_W0:
1068 self.flushtext()
1069 if self.debug:
1070 self.debugfile.write("%d: w0 %d h:=%d%+d=%d, hh:=???\n" %
1071 (self.filepos,
1072 self.pos[_POS_W],
1073 self.pos[_POS_H],
1074 self.pos[_POS_W],
1075 self.pos[_POS_H]+self.pos[_POS_W]))
1076 self.pos[_POS_H] += self.pos[_POS_W]
1077 elif cmd >= _DVI_W1234 and cmd < _DVI_W1234 + 4:
1078 self.flushtext()
1079 self.pos[_POS_W] = afile.readint(cmd - _DVI_W1234 + 1, 1)
1080 if self.debug:
1081 self.debugfile.write("%d: w%d %d h:=%d%+d=%d, hh:=???\n" %
1082 (self.filepos,
1083 cmd - _DVI_W1234 + 1,
1084 self.pos[_POS_W],
1085 self.pos[_POS_H],
1086 self.pos[_POS_W],
1087 self.pos[_POS_H]+self.pos[_POS_W]))
1088 self.pos[_POS_H] += self.pos[_POS_W]
1089 elif cmd == _DVI_X0:
1090 self.flushtext()
1091 if self.debug:
1092 self.debugfile.write("%d: x0 %d h:=%d%+d=%d, hh:=???\n" %
1093 (self.filepos,
1094 self.pos[_POS_X],
1095 self.pos[_POS_H],
1096 self.pos[_POS_X],
1097 self.pos[_POS_H]+self.pos[_POS_X]))
1098 self.pos[_POS_H] += self.pos[_POS_X]
1099 elif cmd >= _DVI_X1234 and cmd < _DVI_X1234 + 4:
1100 self.flushtext()
1101 self.pos[_POS_X] = afile.readint(cmd - _DVI_X1234 + 1, 1)
1102 if self.debug:
1103 self.debugfile.write("%d: x%d %d h:=%d%+d=%d, hh:=???\n" %
1104 (self.filepos,
1105 cmd - _DVI_X1234 + 1,
1106 self.pos[_POS_X],
1107 self.pos[_POS_H],
1108 self.pos[_POS_X],
1109 self.pos[_POS_H]+self.pos[_POS_X]))
1110 self.pos[_POS_H] += self.pos[_POS_X]
1111 elif cmd >= _DVI_DOWN1234 and cmd < _DVI_DOWN1234 + 4:
1112 self.flushtext()
1113 dv = afile.readint(cmd - _DVI_DOWN1234 + 1, 1)
1114 if self.debug:
1115 self.debugfile.write("%d: down%d %d v:=%d%+d=%d, vv:=???\n" %
1116 (self.filepos,
1117 cmd - _DVI_DOWN1234 + 1,
1119 self.pos[_POS_V],
1121 self.pos[_POS_V]+dv))
1122 self.pos[_POS_V] += dv
1123 elif cmd == _DVI_Y0:
1124 self.flushtext()
1125 if self.debug:
1126 self.debugfile.write("%d: y0 %d v:=%d%+d=%d, vv:=???\n" %
1127 (self.filepos,
1128 self.pos[_POS_Y],
1129 self.pos[_POS_V],
1130 self.pos[_POS_Y],
1131 self.pos[_POS_V]+self.pos[_POS_Y]))
1132 self.pos[_POS_V] += self.pos[_POS_Y]
1133 elif cmd >= _DVI_Y1234 and cmd < _DVI_Y1234 + 4:
1134 self.flushtext()
1135 self.pos[_POS_Y] = afile.readint(cmd - _DVI_Y1234 + 1, 1)
1136 if self.debug:
1137 self.debugfile.write("%d: y%d %d v:=%d%+d=%d, vv:=???\n" %
1138 (self.filepos,
1139 cmd - _DVI_Y1234 + 1,
1140 self.pos[_POS_Y],
1141 self.pos[_POS_V],
1142 self.pos[_POS_Y],
1143 self.pos[_POS_V]+self.pos[_POS_Y]))
1144 self.pos[_POS_V] += self.pos[_POS_Y]
1145 elif cmd == _DVI_Z0:
1146 self.flushtext()
1147 if self.debug:
1148 self.debugfile.write("%d: z0 %d v:=%d%+d=%d, vv:=???\n" %
1149 (self.filepos,
1150 self.pos[_POS_Z],
1151 self.pos[_POS_V],
1152 self.pos[_POS_Z],
1153 self.pos[_POS_V]+self.pos[_POS_Z]))
1154 self.pos[_POS_V] += self.pos[_POS_Z]
1155 elif cmd >= _DVI_Z1234 and cmd < _DVI_Z1234 + 4:
1156 self.flushtext()
1157 self.pos[_POS_Z] = afile.readint(cmd - _DVI_Z1234 + 1, 1)
1158 if self.debug:
1159 self.debugfile.write("%d: z%d %d v:=%d%+d=%d, vv:=???\n" %
1160 (self.filepos,
1161 cmd - _DVI_Z1234 + 1,
1162 self.pos[_POS_Z],
1163 self.pos[_POS_V],
1164 self.pos[_POS_Z],
1165 self.pos[_POS_V]+self.pos[_POS_Z]))
1166 self.pos[_POS_V] += self.pos[_POS_Z]
1167 elif cmd >= _DVI_FNTNUMMIN and cmd <= _DVI_FNTNUMMAX:
1168 self.usefont(cmd - _DVI_FNTNUMMIN, 0)
1169 elif cmd >= _DVI_FNT1234 and cmd < _DVI_FNT1234 + 4:
1170 # note that according to the DVI docs, for four byte font numbers,
1171 # the font number is signed. Don't ask why!
1172 fntnum = afile.readint(cmd - _DVI_FNT1234 + 1, cmd == _DVI_FNT1234 + 3)
1173 self.usefont(fntnum, id1234=cmd-_DVI_FNT1234+1)
1174 elif cmd >= _DVI_SPECIAL1234 and cmd < _DVI_SPECIAL1234 + 4:
1175 self.special(afile.read(afile.readint(cmd - _DVI_SPECIAL1234 + 1)))
1176 elif cmd >= _DVI_FNTDEF1234 and cmd < _DVI_FNTDEF1234 + 4:
1177 if cmd == _DVI_FNTDEF1234:
1178 num = afile.readuchar()
1179 elif cmd == _DVI_FNTDEF1234+1:
1180 num = afile.readuint16()
1181 elif cmd == _DVI_FNTDEF1234+2:
1182 num = afile.readuint24()
1183 elif cmd == _DVI_FNTDEF1234+3:
1184 # Cool, here we have according to docu a signed int. Why?
1185 num = afile.readint32()
1186 self.definefont(cmd-_DVI_FNTDEF1234+1,
1187 num,
1188 afile.readint32(),
1189 afile.readint32(),
1190 afile.readint32(),
1191 afile.read(afile.readuchar()+afile.readuchar()))
1192 else:
1193 raise DVIError
1196 ##############################################################################
1197 # VF file handling
1198 ##############################################################################
1200 _VF_LONG_CHAR = 242 # character packet (long version)
1201 _VF_FNTDEF1234 = _DVI_FNTDEF1234 # font definition
1202 _VF_PRE = _DVI_PRE # preamble
1203 _VF_POST = _DVI_POST # postamble
1205 _VF_ID = 202 # VF id byte
1207 class VFError(exceptions.Exception): pass
1209 class vffile:
1210 def __init__(self, filename, scale, tfmconv, pyxconv, fontmap, debug=0):
1211 self.filename = filename
1212 self.scale = scale
1213 self.tfmconv = tfmconv
1214 self.pyxconv = pyxconv
1215 self.fontmap = fontmap
1216 self.debug = debug
1217 self.fonts = {} # used fonts
1218 self.widths = {} # widths of defined chars
1219 self.chardefs = {} # dvi chunks for defined chars
1221 afile = binfile(self.filename, "rb")
1223 cmd = afile.readuchar()
1224 if cmd == _VF_PRE:
1225 if afile.readuchar() != _VF_ID: raise VFError
1226 comment = afile.read(afile.readuchar())
1227 self.cs = afile.readuint32()
1228 self.ds = afile.readuint32()
1229 else:
1230 raise VFError
1232 while 1:
1233 cmd = afile.readuchar()
1234 if cmd >= _VF_FNTDEF1234 and cmd < _VF_FNTDEF1234 + 4:
1235 # font definition
1236 if cmd == _VF_FNTDEF1234:
1237 num = afile.readuchar()
1238 elif cmd == _VF_FNTDEF1234+1:
1239 num = afile.readuint16()
1240 elif cmd == _VF_FNTDEF1234+2:
1241 num = afile.readuint24()
1242 elif cmd == _VF_FNTDEF1234+3:
1243 num = afile.readint32()
1244 c = afile.readint32()
1245 s = afile.readint32() # relative scaling used for font (fix_word)
1246 d = afile.readint32() # design size of font
1247 fontname = afile.read(afile.readuchar()+afile.readuchar())
1249 # rescaled size of font: s is relative to the scaling
1250 # of the virtual font itself. Note that realscale has
1251 # to be a fix_word (like s)
1252 # XXX: check rounding
1253 reals = int(round(self.scale * (16*self.ds/16777216L) * s))
1255 # print ("defining font %s -- VF scale: %g, VF design size: %d, relative font size: %d => real size: %d" %
1256 # (fontname, self.scale, self.ds, s, reals)
1259 # XXX allow for virtual fonts here too
1260 self.fonts[num] = font(fontname, c, reals, d, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
1261 elif cmd == _VF_LONG_CHAR:
1262 # character packet (long form)
1263 pl = afile.readuint32() # packet length
1264 cc = afile.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1265 tfm = afile.readuint24() # character width
1266 dvi = afile.read(pl) # dvi code of character
1267 self.widths[cc] = tfm
1268 self.chardefs[cc] = dvi
1269 elif cmd < _VF_LONG_CHAR:
1270 # character packet (short form)
1271 cc = afile.readuchar() # char code
1272 tfm = afile.readuint24() # character width
1273 dvi = afile.read(cmd)
1274 self.widths[cc] = tfm
1275 self.chardefs[cc] = dvi
1276 elif cmd == _VF_POST:
1277 break
1278 else:
1279 raise VFError
1281 afile.close()
1283 def getfonts(self):
1284 return self.fonts
1286 def getchar(self, cc):
1287 return self.chardefs[cc]