more PDF work: beginnings of text support
[PyX/mjg.git] / pyx / dvifile.py
blobef8d9a150ff35cfe5e4354553af0941d6b830f17
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
7 # Copyright (C) 2002-2004 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 import copy, cStringIO, exceptions, re, struct, string, sys
26 import unit, epsfile, bbox, base, canvas, color, trafo, path, prolog, pykpathsea
28 class fix_word:
29 def __init__(self, word):
30 if word >= 0:
31 self.sign = 1
32 else:
33 self.sign = -1
35 self.precomma = abs(word) >> 20
36 self.postcomma = abs(word) & 0xFFFFF
38 def __float__(self):
39 return self.sign * (self.precomma + 1.0*self.postcomma/0xFFFFF)
41 def __mul__(self, other):
42 # hey, it's Q&D
43 result = fix_word(0)
45 result.sign = self.sign*other.sign
46 c = self.postcomma*other.precomma + self.precomma*other.postcomma
47 result.precomma = self.precomma*other.precomma + (c >> 20)
48 result.postcomma = c & 0xFFFFF + ((self.postcomma*other.postcomma) >> 40)
49 return result
52 class binfile:
54 def __init__(self, filename, mode="r"):
55 self.file = open(filename, mode)
57 def close(self):
58 self.file.close()
60 def tell(self):
61 return self.file.tell()
63 def eof(self):
64 return self.file.eof()
66 def read(self, bytes):
67 return self.file.read(bytes)
69 def readint(self, bytes=4, signed=0):
70 first = 1
71 result = 0
72 while bytes:
73 value = ord(self.file.read(1))
74 if first and signed and value > 127:
75 value -= 256
76 first = 0
77 result = 256 * result + value
78 bytes -= 1
79 return result
81 def readint32(self):
82 return struct.unpack(">l", self.file.read(4))[0]
84 def readuint32(self):
85 return struct.unpack(">L", self.file.read(4))[0]
87 def readint24(self):
88 # XXX: checkme
89 return struct.unpack(">l", "\0"+self.file.read(3))[0]
91 def readuint24(self):
92 # XXX: checkme
93 return struct.unpack(">L", "\0"+self.file.read(3))[0]
95 def readint16(self):
96 return struct.unpack(">h", self.file.read(2))[0]
98 def readuint16(self):
99 return struct.unpack(">H", self.file.read(2))[0]
101 def readchar(self):
102 return struct.unpack("b", self.file.read(1))[0]
104 def readuchar(self):
105 return struct.unpack("B", self.file.read(1))[0]
107 def readstring(self, bytes):
108 l = self.readuchar()
109 assert l <= bytes-1, "inconsistency in file: string too long"
110 return self.file.read(bytes-1)[:l]
112 class stringbinfile(binfile):
114 def __init__(self, s):
115 self.file = cStringIO.StringIO(s)
118 # class tokenfile:
119 # """ ascii file containing tokens separated by spaces.
121 # Comments beginning with % are ignored. Strings containing spaces
122 # are not handled correctly
123 # """
125 # def __init__(self, filename):
126 # self.file = open(filename, "r")
127 # self.line = None
129 # def gettoken(self):
130 # """ return next token or None if EOF """
131 # while not self.line:
132 # line = self.file.readline()
133 # if line == "":
134 # return None
135 # self.line = line.split("%")[0].split()
136 # token = self.line[0]
137 # self.line = self.line[1:]
138 # return token
140 # def close(self):
141 # self.file.close()
144 ##############################################################################
145 # TFM file handling
146 ##############################################################################
148 class TFMError(exceptions.Exception): pass
151 class char_info_word:
152 def __init__(self, word):
153 self.width_index = int((word & 0xFF000000L) >> 24) #make sign-safe
154 self.height_index = (word & 0x00F00000) >> 20
155 self.depth_index = (word & 0x000F0000) >> 16
156 self.italic_index = (word & 0x0000FC00) >> 10
157 self.tag = (word & 0x00000300) >> 8
158 self.remainder = (word & 0x000000FF)
161 class tfmfile:
162 def __init__(self, name, debug=0):
163 self.file = binfile(name, "rb")
164 self.debug = debug
167 # read pre header
170 self.lf = self.file.readint16()
171 self.lh = self.file.readint16()
172 self.bc = self.file.readint16()
173 self.ec = self.file.readint16()
174 self.nw = self.file.readint16()
175 self.nh = self.file.readint16()
176 self.nd = self.file.readint16()
177 self.ni = self.file.readint16()
178 self.nl = self.file.readint16()
179 self.nk = self.file.readint16()
180 self.ne = self.file.readint16()
181 self.np = self.file.readint16()
183 if not (self.bc-1 <= self.ec <= 255 and
184 self.ne <= 256 and
185 self.lf == 6+self.lh+(self.ec-self.bc+1)+self.nw+self.nh+self.nd
186 +self.ni+self.nl+self.nk+self.ne+self.np):
187 raise TFMError, "error in TFM pre-header"
189 if debug:
190 print "lh=%d" % self.lh
193 # read header
196 self.checksum = self.file.readint32()
197 self.designsizeraw = self.file.readint32()
198 assert self.designsizeraw > 0, "invald design size"
199 self.designsize = fix_word(self.designsizeraw)
200 if self.lh > 2:
201 assert self.lh > 11, "inconsistency in TFM file: incomplete field"
202 self.charcoding = self.file.readstring(40)
203 else:
204 self.charcoding = None
206 if self.lh > 12:
207 assert self.lh > 16, "inconsistency in TFM file: incomplete field"
208 self.fontfamily = self.file.readstring(20)
209 else:
210 self.fontfamily = None
212 if self.debug:
213 print "(FAMILY %s)" % self.fontfamily
214 print "(CODINGSCHEME %s)" % self.charcoding
215 print "(DESINGSIZE R %f)" % self.designsize
217 if self.lh > 17:
218 self.sevenbitsave = self.file.readuchar()
219 # ignore the following two bytes
220 self.file.readint16()
221 facechar = self.file.readuchar()
222 # decode ugly face specification into the Knuth suggested string
223 if facechar < 18:
224 if facechar >= 12:
225 self.face = "E"
226 facechar -= 12
227 elif facechar >= 6:
228 self.face = "C"
229 facechar -= 6
230 else:
231 self.face = "R"
233 if facechar >= 4:
234 self.face = "L" + self.face
235 facechar -= 4
236 elif facechar >= 2:
237 self.face = "B" + self.face
238 facechar -= 2
239 else:
240 self.face = "M" + self.face
242 if facechar == 1:
243 self.face = self.face[0] + "I" + self.face[1]
244 else:
245 self.face = self.face[0] + "R" + self.face[1]
247 else:
248 self.face = None
249 else:
250 self.sevenbitsave = self.face = None
252 if self.lh > 18:
253 # just ignore the rest
254 print self.file.read((self.lh-18)*4)
257 # read char_info
260 self.char_info = [None]*(self.ec+1)
261 for charcode in range(self.bc, self.ec+1):
262 self.char_info[charcode] = char_info_word(self.file.readint32())
263 if self.char_info[charcode].width_index == 0:
264 # disable character if width_index is zero
265 self.char_info[charcode] = None
268 # read widths
271 self.width = [None for width_index in range(self.nw)]
272 for width_index in range(self.nw):
273 # self.width[width_index] = fix_word(self.file.readint32())
274 self.width[width_index] = self.file.readint32()
277 # read heights
280 self.height = [None for height_index in range(self.nh)]
281 for height_index in range(self.nh):
282 # self.height[height_index] = fix_word(self.file.readint32())
283 self.height[height_index] = self.file.readint32()
286 # read depths
289 self.depth = [None for depth_index in range(self.nd)]
290 for depth_index in range(self.nd):
291 # self.depth[depth_index] = fix_word(self.file.readint32())
292 self.depth[depth_index] = self.file.readint32()
295 # read italic
298 self.italic = [None for italic_index in range(self.ni)]
299 for italic_index in range(self.ni):
300 # self.italic[italic_index] = fix_word(self.file.readint32())
301 self.italic[italic_index] = self.file.readint32()
304 # read lig_kern
307 # XXX decode to lig_kern_command
309 self.lig_kern = [None for lig_kern_index in range(self.nl)]
310 for lig_kern_index in range(self.nl):
311 self.lig_kern[lig_kern_index] = self.file.readint32()
314 # read kern
317 self.kern = [None for kern_index in range(self.nk)]
318 for kern_index in range(self.nk):
319 # self.kern[kern_index] = fix_word(self.file.readint32())
320 self.kern[kern_index] = self.file.readint32()
323 # read exten
326 # XXX decode to extensible_recipe
328 self.exten = [None for exten_index in range(self.ne)]
329 for exten_index in range(self.ne):
330 self.exten[exten_index] = self.file.readint32()
333 # read param
336 # XXX decode
338 self.param = [None for param_index in range(self.np)]
339 for param_index in range(self.np):
340 self.param[param_index] = self.file.readint32()
342 self.file.close()
345 # class FontEncoding:
347 # def __init__(self, filename):
348 # """ font encoding contained in filename """
349 # encpath = pykpathsea.find_file(filename, pykpathsea.kpse_tex_ps_header_format)
350 # encfile = tokenfile(encpath)
352 # # name of encoding
353 # self.encname = encfile.gettoken()
354 # token = encfile.gettoken()
355 # if token != "[":
356 # raise RuntimeError("cannot parse encoding file '%s', expecting '[' got '%s'" % (filename, token))
357 # self.encvector = []
358 # for i in range(256):
359 # token = encfile.gettoken()
360 # if token is None or token=="]":
361 # raise RuntimeError("not enough charcodes in encoding file '%s'" % filename)
362 # self.encvector.append(token)
363 # if encfile.gettoken() != "]":
364 # raise RuntimeError("too many charcodes in encoding file '%s'" % filename)
365 # token = encfile.gettoken()
366 # if token != "def":
367 # raise RuntimeError("cannot parse encoding file '%s', expecting 'def' got '%s'" % (filename, token))
368 # token = encfile.gettoken()
369 # if token != None:
370 # raise RuntimeError("encoding file '%s' too long" % filename)
371 # encfile.close()
373 # def encode(self, charcode):
374 # return self.encvector[charcode]
376 ##############################################################################
377 # Font handling
378 ##############################################################################
380 _ReEncodeFont = prolog.definition("ReEncodeFont", """{
381 5 dict
382 begin
383 /newencoding exch def
384 /newfontname exch def
385 /basefontname exch def
386 /basefontdict basefontname findfont def
387 /newfontdict basefontdict maxlength dict def
388 basefontdict {
389 exch dup dup /FID ne exch /Encoding ne and
390 { exch newfontdict 3 1 roll put }
391 { pop pop }
392 ifelse
393 } forall
394 newfontdict /FontName newfontname put
395 newfontdict /Encoding newencoding put
396 newfontname newfontdict definefont pop
398 }""")
401 # PostScript font selection and output primitives
404 class _begintextobject(base.PSOp):
405 def outputPS(self, file):
406 pass
408 def outputPDF(self, file):
409 file.write("BT\n")
412 class _endtextobject(base.PSOp):
413 def outputPS(self, file):
414 pass
416 def outputPDF(self, file):
417 file.write("ET\n")
420 class _selectfont(base.PSOp):
421 def __init__(self, name, size):
422 self.name = name
423 self.size = size
425 def outputPS(self, file):
426 file.write("/%s %f selectfont\n" % (self.name, self.size))
428 def outputPDF(self, file):
429 file.write("/%s %f Tf\n" % (self.name, self.size))
432 class selectfont(_selectfont):
433 def __init__(self, font):
434 _selectfont.__init__(self, font.getpsname(), font.getsize())
435 self.font = font
437 def prolog(self):
438 result = [prolog.fontdefinition(self.font.getbasepsname(),
439 self.font.getfontfile(),
440 self.font.getencodingfile(),
441 self.font.usedchars)]
442 if self.font.getencoding():
443 result.append(_ReEncodeFont)
444 result.append(prolog.fontencoding(self.font.getencoding(), self.font.getencodingfile()))
445 result.append(prolog.fontreencoding(self.font.getpsname(), self.font.getbasepsname(), self.font.getencoding()))
446 return result
449 class _show(base.PSCmd):
451 def __init__(self, x, y):
452 self.x = x
453 self.y = y
454 self.width = 0
455 self.height = 0
456 self.depth = 0
457 self.chars = []
459 def addchar(self, width, height, depth, char):
460 self.width += width
461 if height > self.height:
462 self.height = height
463 if depth > self.depth:
464 self.depth = depth
465 self.chars.append(char)
467 def bbox(self):
468 return bbox.bbox_pt(self.x, self.y-self.depth, self.x+self.width, self.y+self.height)
470 def outputPS(self, file):
471 outstring = ""
472 for char in self.chars:
473 if char > 32 and char < 127 and chr(char) not in "()[]<>\\":
474 ascii = "%s" % chr(char)
475 else:
476 ascii = "\\%03o" % char
477 outstring += ascii
478 file.write("%g %g moveto (%s) show\n" % (self.x, self.y, outstring))
480 def outputPDF(self, file):
481 outstring = ""
482 for char in self.chars:
483 if char > 32 and char < 127 and chr(char) not in "()[]<>\\":
484 ascii = "%s" % chr(char)
485 else:
486 ascii = "\\%03o" % char
487 outstring += ascii
488 # file.write("%f %f Td (%s) Tj\n" % (self.x, self.y, outstring))
489 file.write("1 0 0 1 %f %f Tm (%s) Tj\n" % (self.x, self.y, outstring))
492 class fontmapping:
494 tokenpattern = re.compile(r'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
496 def __init__(self, s):
497 """ construct font mapping from line s of font mapping file """
498 self.texname = self.basepsname = self.fontfile = None
500 # standard encoding
501 self.encodingfile = None
503 # supported postscript fragments occuring in psfonts.map
504 self.reencodefont = self.extendfont = self.slantfont = None
506 tokens = []
507 while len(s):
508 match = self.tokenpattern.match(s)
509 if match:
510 if match.groups()[0]:
511 tokens.append('"%s"' % match.groups()[0])
512 else:
513 tokens.append(match.groups()[2])
514 s = s[match.end():]
515 else:
516 raise RuntimeError("wrong syntax")
518 for token in tokens:
519 if token.startswith("<"):
520 if token.startswith("<<"):
521 # XXX: support non-partial download here
522 self.fontfile = token[2:]
523 elif token.startswith("<["):
524 self.encodingfile = token[2:]
525 elif token.endswith(".pfa") or token.endswith(".pfb"):
526 self.fontfile = token[1:]
527 elif token.endswith(".enc"):
528 self.encodingfile = token[1:]
529 else:
530 raise RuntimeError("wrong syntax")
531 elif token.startswith('"'):
532 pscode = token[1:-1].split()
533 # parse standard postscript code fragments
534 while pscode:
535 try:
536 arg, cmd = pscode[:2]
537 except:
538 raise RuntimeError("Unsupported Postscript fragment '%s'" % pscode)
539 pscode = pscode[2:]
540 if cmd == "ReEncodeFont":
541 self.reencodefont = arg
542 elif cmd == "ExtendFont":
543 self.extendfont = arg
544 elif cmd == "SlantFont":
545 self.slantfont = arg
546 else:
547 raise RuntimeError("Unsupported Postscript fragment '%s %s'" % (arg, cmd))
548 else:
549 if self.texname is None:
550 self.texname = token
551 else:
552 self.basepsname = token
553 if self.basepsname is None:
554 self.basepsname = self.texname
556 def __str__(self):
557 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
558 (self.texname, self.basepsname, self.fontfile, repr(self.encodingfile)))
560 # generate fontmap
562 def readfontmap(filenames):
563 """ read font map from filename (without path) """
564 fontmap = {}
565 for filename in filenames:
566 mappath = pykpathsea.find_file(filename, pykpathsea.kpse_fontmap_format)
567 # try also the oft-used registration as dvips config file
568 if not mappath:
569 mappath = pykpathsea.find_file(filename, pykpathsea.kpse_dvips_config_format)
570 if not mappath:
571 raise RuntimeError("cannot find font mapping file '%s'" % filename)
572 mapfile = open(mappath, "r")
573 lineno = 0
574 for line in mapfile.readlines():
575 lineno += 1
576 line = line.rstrip()
577 if not (line=="" or line[0] in (" ", "%", "*", ";" , "#")):
578 try:
579 fm = fontmapping(line)
580 except RuntimeError, e:
581 sys.stderr.write("*** PyX Warning: Ignoring line %i in mapping file '%s': %s\n" % (lineno, filename, e))
582 else:
583 fontmap[fm.texname] = fm
584 mapfile.close()
585 return fontmap
588 class font:
589 def __init__(self, name, c, q, d, debug=0):
590 self.name = name
591 self.q = q # desired size of font (fix_word) in tex points
592 self.d = d # design size of font (fix_word) in tex points
593 tfmpath = pykpathsea.find_file("%s.tfm" % self.name, pykpathsea.kpse_tfm_format)
594 if not tfmpath:
595 raise TFMError("cannot find %s.tfm" % self.name)
596 self.tfmfile = tfmfile(tfmpath, debug)
598 # We only check for equality of font checksums if none of them
599 # is zero. The case c == 0 happend in some VF files and
600 # according to the VFtoVP documentation, paragraph 40, a check
601 # is only performed if tfmfile.checksum > 0. Anyhow, being
602 # more generous here seems to be reasonable
603 if self.tfmfile.checksum != c and self.tfmfile.checksum != 0 and c !=0:
604 raise DVIError("check sums do not agree: %d vs. %d" %
605 (self.tfmfile.checksum, c))
607 # tfmfile.designsizeraw is the design size of the font as a fix_word
608 if abs(self.tfmfile.designsizeraw - d) > 2:
609 raise DVIError("design sizes do not agree: %d vs. %d" % (self.tfmfile.designsizeraw, d))
610 if q < 0 or q > 134217728:
611 raise DVIError("font '%s' not loaded: bad scale" % self.name)
612 if d < 0 or d > 134217728:
613 raise DVIError("font '%s' not loaded: bad design size" % self.name)
615 self.scale = 1.0*q/d
617 # for bookkeeping of used characters
618 self.usedchars = [0] * 256
620 def __str__(self):
621 return "font %s designed at %g tex pts used at %g tex pts" % (self.name,
622 16.0*self.d/16777216L,
623 16.0*self.q/16777216L)
626 __repr__ = __str__
628 def getsize(self):
629 """ return size of font in (PS) points """
630 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
631 # to the corresponding float. Furthermore, we have to convert from TeX
632 # points to points, hence the factor 72/72.27.
633 return 16L*self.q/16777216L*72/72.27
635 def _convert(self, width):
636 return 16L*width*self.q/16777216L
638 def getwidth(self, charcode):
639 return self._convert(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
641 def getheight(self, charcode):
642 return self._convert(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
644 def getdepth(self, charcode):
645 return self._convert(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
647 def getitalic(self, charcode):
648 return self._convert(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
650 def markcharused(self, charcode):
651 self.usedchars[charcode] = 1
653 def mergeusedchars(self, otherfont):
654 for i in range(len(self.usedchars)):
655 self.usedchars[i] = self.usedchars[i] or otherfont.usedchars[i]
657 def clearusedchars(self):
658 self.usedchars = [0] * 256
661 class type1font(font):
662 def __init__(self, name, c, q, d, fontmap, debug=0):
663 font.__init__(self, name, c, q, d, debug)
664 self.fontmapping = fontmap.get(name)
665 if self.fontmapping is None:
666 raise RuntimeError("no information for font '%s' found in font mapping file, aborting" % name)
668 def getbasepsname(self):
669 return self.fontmapping.basepsname
671 def getpsname(self):
672 if self.fontmapping.reencodefont:
673 return "%s-%s" % (self.fontmapping.basepsname, self.fontmapping.reencodefont)
674 else:
675 return self.fontmapping.basepsname
677 def getfontfile(self):
678 return self.fontmapping.fontfile
680 def getencoding(self):
681 return self.fontmapping.reencodefont
683 def getencodingfile(self):
684 return self.fontmapping.encodingfile
687 class virtualfont(font):
688 def __init__(self, name, c, q, d, fontmap, debug=0):
689 font.__init__(self, name, c, q, d, debug)
690 fontpath = pykpathsea.find_file(name, pykpathsea.kpse_vf_format)
691 if fontpath is None or not len(fontpath):
692 raise RuntimeError
693 self.vffile = vffile(fontpath, self.scale, fontmap, debug > 1)
695 def getfonts(self):
696 """ return fonts used in virtual font itself """
697 return self.vffile.getfonts()
699 def getchar(self, cc):
700 """ return dvi chunk corresponding to char code cc """
701 return self.vffile.getchar(cc)
704 ##############################################################################
705 # DVI file handling
706 ##############################################################################
708 _DVI_CHARMIN = 0 # typeset a character and move right (range min)
709 _DVI_CHARMAX = 127 # typeset a character and move right (range max)
710 _DVI_SET1234 = 128 # typeset a character and move right
711 _DVI_SETRULE = 132 # typeset a rule and move right
712 _DVI_PUT1234 = 133 # typeset a character
713 _DVI_PUTRULE = 137 # typeset a rule
714 _DVI_NOP = 138 # no operation
715 _DVI_BOP = 139 # beginning of page
716 _DVI_EOP = 140 # ending of page
717 _DVI_PUSH = 141 # save the current positions (h, v, w, x, y, z)
718 _DVI_POP = 142 # restore positions (h, v, w, x, y, z)
719 _DVI_RIGHT1234 = 143 # move right
720 _DVI_W0 = 147 # move right by w
721 _DVI_W1234 = 148 # move right and set w
722 _DVI_X0 = 152 # move right by x
723 _DVI_X1234 = 153 # move right and set x
724 _DVI_DOWN1234 = 157 # move down
725 _DVI_Y0 = 161 # move down by y
726 _DVI_Y1234 = 162 # move down and set y
727 _DVI_Z0 = 166 # move down by z
728 _DVI_Z1234 = 167 # move down and set z
729 _DVI_FNTNUMMIN = 171 # set current font (range min)
730 _DVI_FNTNUMMAX = 234 # set current font (range max)
731 _DVI_FNT1234 = 235 # set current font
732 _DVI_SPECIAL1234 = 239 # special (dvi extention)
733 _DVI_FNTDEF1234 = 243 # define the meaning of a font number
734 _DVI_PRE = 247 # preamble
735 _DVI_POST = 248 # postamble beginning
736 _DVI_POSTPOST = 249 # postamble ending
738 _DVI_VERSION = 2 # dvi version
740 # position variable indices
741 _POS_H = 0
742 _POS_V = 1
743 _POS_W = 2
744 _POS_X = 3
745 _POS_Y = 4
746 _POS_Z = 5
748 # reader states
749 _READ_PRE = 1
750 _READ_NOPAGE = 2
751 _READ_PAGE = 3
752 _READ_POST = 4 # XXX not used
753 _READ_POSTPOST = 5 # XXX not used
754 _READ_DONE = 6
757 class DVIError(exceptions.Exception): pass
759 # save and restore colors
761 class _savecolor(base.PSOp):
762 def outputPS(self, file):
763 file.write("currentcolor currentcolorspace\n")
766 class _restorecolor(base.PSOp):
767 def outputPS(self, file):
768 file.write("setcolorspace setcolor\n")
770 class _savetrafo(base.PSOp):
771 def outputPS(self, file):
772 file.write("matrix currentmatrix\n")
775 class _restoretrafo(base.PSOp):
776 def outputPS(self, file):
777 file.write("setmatrix\n")
780 class dvifile:
782 def __init__(self, filename, fontmap, debug=0):
783 """ opens the dvi file and reads the preamble """
784 self.filename = filename
785 self.fontmap = fontmap
786 self.debug = debug
788 self.fonts = {}
789 self.activefont = None
791 # stack of fonts and fontscale currently used (used for VFs)
792 self.fontstack = []
793 self.stack = []
795 # pointer to currently active page
796 self.actpage = None
798 # currently active output: show instance currently used and
799 # the corresponding type 1 font
800 self.activeshow = None
801 self.activetype1font = None
803 # stack for self.file, self.fonts and self.stack, needed for VF inclusion
804 self.statestack = []
806 self.file = binfile(self.filename, "rb")
808 # currently read byte in file (for debugging output)
809 self.filepos = None
811 self._read_pre()
813 # helper routines
815 def flushout(self):
816 """ flush currently active string """
817 if self.activeshow is not None:
818 if self.debug:
819 print "[%s]" % "".join([chr(char) for char in self.activeshow.chars])
820 self.actpage.insert(self.activeshow)
821 self.activeshow = None
823 def begintext(self):
824 """ activate the font if is not yet active, closing a currently active
825 text object and flushing the output"""
826 if isinstance(self.activefont, type1font):
827 self.endtext()
828 if self.activetype1font != self.activefont and self.activefont:
829 self.actpage.insert(_begintextobject())
830 self.actpage.insert(selectfont(self.activefont))
831 self.activetype1font = self.activefont
833 def endtext(self):
834 self.flushout()
835 if self.activetype1font:
836 self.actpage.insert(_endtextobject())
837 self.activetype1font = None
839 def putrule(self, height, width, advancepos=1):
840 self.endtext()
841 x1 = self.pos[_POS_H] * self.conv
842 y1 = -self.pos[_POS_V] * self.conv
843 w = width * self.conv
844 h = height * self.conv
846 if height > 0 and width > 0:
847 if self.debug:
848 pixelw = int(width*self.trueconv*self.mag/1000.0)
849 if pixelw < width*self.conv: pixelw += 1
850 pixelh = int(height*self.trueconv*self.mag/1000.0)
851 if pixelh < height*self.conv: pixelh += 1
853 print ("%d: %srule height %d, width %d (%dx%d pixels)" %
854 (self.filepos, advancepos and "set" or "put", height, width, pixelh, pixelw))
855 self.actpage.fill(path.rect_pt(x1, y1, w, h))
856 else:
857 if self.debug:
858 print ("%d: %srule height %d, width %d (invisible)" %
859 (self.filepos, advancepos and "set" or "put", height, width))
861 if advancepos:
862 if self.debug:
863 print (" h:=%d+%d=%d, hh:=%d" %
864 (self.pos[_POS_H], width, self.pos[_POS_H]+width, 0))
865 self.pos[_POS_H] += width
867 def putchar(self, char, advancepos=1):
868 dx = advancepos and int(round(self.activefont.getwidth(char)*self.tfmconv)) or 0
870 if self.debug:
871 print ("%d: %schar%d h:=%d+%d=%d, hh:=%d" %
872 (self.filepos,
873 advancepos and "set" or "put",
874 char,
875 self.pos[_POS_H], dx, self.pos[_POS_H]+dx,
878 if isinstance(self.activefont, type1font):
879 if self.activeshow is None:
880 self.activeshow = _show(self.pos[_POS_H] * self.conv, -self.pos[_POS_V] * self.conv)
881 width = self.activefont.getwidth(char) * self.tfmconv * self.conv
882 height = self.activefont.getheight(char) * self.tfmconv * self.conv
883 depth = self.activefont.getdepth(char) * self.tfmconv * self.conv
884 self.activeshow.addchar(width, height, depth, char)
886 self.activefont.markcharused(char)
887 self.pos[_POS_H] += dx
888 else:
889 # virtual font handling
890 afterpos = list(self.pos)
891 afterpos[_POS_H] += dx
892 self._push_dvistring(self.activefont.getchar(char), self.activefont.getfonts(), afterpos,
893 self.activefont.getsize())
896 if not advancepos:
897 # XXX: correct !?
898 self.flushout()
900 def usefont(self, fontnum):
901 if self.debug:
902 print ("%d: fntnum%i current font is %s" %
903 (self.filepos,
904 fontnum, self.fonts[fontnum].name))
906 self.activefont = self.fonts[fontnum]
907 self.begintext()
909 def definefont(self, cmdnr, num, c, q, d, fontname):
910 # cmdnr: type of fontdef command (only used for debugging output)
911 # c: checksum
912 # q: scaling factor (fix_word)
913 # Note that q is actually s in large parts of the documentation.
914 # d: design size (fix_word)
916 try:
917 font = virtualfont(fontname, c, q/self.tfmconv, d/self.tfmconv, self.fontmap, self.debug > 1)
918 except (TypeError, RuntimeError):
919 font = type1font(fontname, c, q/self.tfmconv, d/self.tfmconv, self.fontmap, self.debug > 1)
921 self.fonts[num] = font
923 if self.debug:
924 print "%d: fntdef%d %i: %s" % (self.filepos, cmdnr, num, fontname)
926 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
927 # m = 1.0*q/d
928 # scalestring = scale!=1000 and " scaled %d" % scale or ""
929 # print ("Font %i: %s%s---loaded at size %d DVI units" %
930 # (num, fontname, scalestring, q))
931 # if scale!=1000:
932 # print " (this font is magnified %d%%)" % round(scale/10)
934 def special(self, s):
935 x = self.pos[_POS_H] * self.conv
936 y = -self.pos[_POS_V] * self.conv
937 if self.debug:
938 print "%d: xxx '%s'" % (self.filepos, s)
939 if not s.startswith("PyX:"):
940 if s.startswith("Warning:"):
941 sys.stderr.write("*** PyX Warning: ignoring special '%s'\n" % s)
942 return
943 else:
944 raise RuntimeError("the special '%s' cannot be handled by PyX, aborting" % s)
946 # it is in general not safe to continue using the currently active font because
947 # the specials may involve some gsave/grestore operations
948 self.endtext()
950 command, args = s[4:].split()[0], s[4:].split()[1:]
951 if command=="color_begin":
952 if args[0]=="cmyk":
953 c = color.cmyk(float(args[1]), float(args[2]), float(args[3]), float(args[4]))
954 elif args[0]=="gray":
955 c = color.gray(float(args[1]))
956 elif args[0]=="hsb":
957 c = color.hsb(float(args[1]), float(args[2]), float(args[3]))
958 elif args[0]=="rgb":
959 c = color.rgb(float(args[1]), float(args[2]), float(args[3]))
960 elif args[0]=="RGB":
961 c = color.rgb(int(args[1])/255.0, int(args[2])/255.0, int(args[3])/255.0)
962 elif args[0]=="texnamed":
963 try:
964 c = getattr(color.cmyk, args[1])
965 except AttributeError:
966 raise RuntimeError("unknown TeX color '%s', aborting" % args[1])
967 else:
968 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args[0])
969 self.actpage.insert(_savecolor())
970 self.actpage.insert(c)
971 elif command=="color_end":
972 self.actpage.insert(_restorecolor())
973 elif command=="rotate_begin":
974 self.actpage.insert(_savetrafo())
975 self.actpage.insert(trafo.rotate_pt(float(args[0]), x, y))
976 elif command=="rotate_end":
977 self.actpage.insert(_restoretrafo())
978 elif command=="scale_begin":
979 self.actpage.insert(_savetrafo())
980 self.actpage.insert(trafo.scale_pt(float(args[0]), float(args[1]), x, y))
981 elif command=="scale_end":
982 self.actpage.insert(_restoretrafo())
983 elif command=="epsinclude":
984 # parse arguments
985 argdict = {}
986 for arg in args:
987 name, value = arg.split("=")
988 argdict[name] = value
990 # construct kwargs for epsfile constructor
991 epskwargs = {}
992 epskwargs["filename"] = argdict["file"]
993 epskwargs["bbox"] = bbox.bbox_pt(float(argdict["llx"]), float(argdict["lly"]),
994 float(argdict["urx"]), float(argdict["ury"]))
995 if argdict.has_key("width"):
996 epskwargs["width"] = float(argdict["width"]) * unit.t_pt
997 if argdict.has_key("height"):
998 epskwargs["height"] = float(argdict["height"]) * unit.t_pt
999 if argdict.has_key("clip"):
1000 epskwargs["clip"] = int(argdict["clip"])
1001 self.actpage.insert(epsfile.epsfile(x * unit.t_pt, y * unit.t_pt, **epskwargs))
1002 elif command=="marker":
1003 if len(args) != 1:
1004 raise RuntimeError("marker contains spaces")
1005 for c in args[0]:
1006 if c not in string.digits + string.letters + "@":
1007 raise RuntimeError("marker contains invalid characters")
1008 if self.actpage.markers.has_key(args[0]):
1009 raise RuntimeError("marker name occurred several times")
1010 self.actpage.markers[args[0]] = x * unit.t_pt, y * unit.t_pt
1011 else:
1012 raise RuntimeError("unknown PyX special '%s', aborting" % command)
1013 self.begintext()
1015 # routines for pushing and popping different dvi chunks on the reader
1017 def _push_dvistring(self, dvi, fonts, afterpos, fontsize):
1018 """ push dvi string with defined fonts on top of reader
1019 stack. Every positions gets scaled relatively by the factor
1020 scale. After the interpreting of the dvi chunk has been finished,
1021 continue with self.pos=afterpos. The designsize of the virtual
1022 font is passed as a fix_word
1026 if self.debug:
1027 print "executing new dvi chunk"
1028 self.statestack.append((self.file, self.fonts, self.activefont, afterpos, self.stack, self.conv, self.tfmconv))
1030 # units in vf files are relative to the size of the font and given as fix_words
1031 # which can be converted to floats by diving by 2**20
1032 oldconv = self.conv
1033 self.conv = fontsize/2**20
1034 rescale = self.conv/oldconv
1036 self.file = stringbinfile(dvi)
1037 self.fonts = fonts
1038 self.stack = []
1039 self.filepos = 0
1041 # rescale self.pos in order to be consistent with the new scaling
1042 self.pos = map(lambda x, rescale=rescale:1.0*x/rescale, self.pos)
1044 # since tfmconv converts from tfm units to dvi units, rescale it as well
1045 self.tfmconv /= rescale
1047 self.usefont(0)
1049 def _pop_dvistring(self):
1050 self.flushout()
1051 if self.debug:
1052 print "finished executing dvi chunk"
1053 self.file.close()
1054 self.file, self.fonts, self.activefont, self.pos, self.stack, self.conv, self.tfmconv = self.statestack.pop()
1056 # routines corresponding to the different reader states of the dvi maschine
1058 def _read_pre(self):
1059 afile = self.file
1060 while 1:
1061 self.filepos = afile.tell()
1062 cmd = afile.readuchar()
1063 if cmd == _DVI_NOP:
1064 pass
1065 elif cmd == _DVI_PRE:
1066 if afile.readuchar() != _DVI_VERSION: raise DVIError
1067 num = afile.readuint32()
1068 den = afile.readuint32()
1069 self.mag = afile.readuint32()
1071 # for the interpretation of all quantities, two conversion factors
1072 # are relevant:
1073 # - self.tfmconv (tfm units->dvi units)
1074 # - self.conv (dvi units-> (PostScript) points)
1076 self.tfmconv = (25400000.0/num)*(den/473628672.0)/16.0
1078 # calculate self.conv as described in the DVIType docu
1080 # resolution in dpi
1081 self.resolution = 300.0
1082 # self.trueconv = conv in DVIType docu
1083 self.trueconv = (num/254000.0)*(self.resolution/den)
1085 # self.conv is the conversion factor from the dvi units
1086 # to (PostScript) points. It consists of
1087 # - self.mag/1000.0: magstep scaling
1088 # - self.trueconv: conversion from dvi units to pixels
1089 # - 1/self.resolution: conversion from pixels to inch
1090 # - 72 : conversion from inch to points
1091 self.conv = self.mag/1000.0*self.trueconv/self.resolution*72
1093 comment = afile.read(afile.readuchar())
1094 return
1095 else:
1096 raise DVIError
1098 def readpage(self, pageid=None):
1099 """ reads a page from the dvi file
1101 This routine reads a page from the dvi file which is
1102 returned as a canvas. When there is no page left in the
1103 dvifile, None is returned and the file is closed properly."""
1106 while 1:
1107 self.filepos = self.file.tell()
1108 cmd = self.file.readuchar()
1109 if cmd == _DVI_NOP:
1110 pass
1111 elif cmd == _DVI_BOP:
1112 # self.endtext()
1113 ispageid = [self.file.readuint32() for i in range(10)]
1114 #if ispageid[:3] != [ord("P"), ord("y"), ord("X")] or ispageid[4:] != [0, 0, 0, 0, 0, 0]:
1115 if pageid is not None and ispageid != pageid:
1116 raise DVIError("invalid pageid")
1117 if self.debug:
1118 print "%d: beginning of page %i" % (self.filepos, ispageid[0])
1119 self.file.readuint32()
1120 break
1121 elif cmd == _DVI_POST:
1122 self.file.close()
1123 return None # nothing left
1124 else:
1125 raise DVIError
1127 actpage = canvas.canvas()
1128 self.actpage = actpage # XXX should be removed ...
1129 self.actpage.markers = {}
1130 self.pos = [0, 0, 0, 0, 0, 0]
1131 self.activetype1font = None
1133 # Since we do not know which dvi pages the actual PS file contains later on,
1134 # we have to keep track of used char informations separately for each dvi page.
1135 # In order to do so, the already defined fonts have to be copied and their
1136 # used char informations have to be reset
1137 for nr in self.fonts.keys():
1138 self.fonts[nr] = copy.copy(self.fonts[nr])
1139 self.fonts[nr].clearusedchars()
1141 while 1:
1142 afile = self.file
1143 self.filepos = afile.tell()
1144 try:
1145 cmd = afile.readuchar()
1146 except struct.error:
1147 # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
1148 # so we have to continue with the rest of the dvi file
1149 self._pop_dvistring()
1150 continue
1151 if cmd == _DVI_NOP:
1152 pass
1153 if cmd >= _DVI_CHARMIN and cmd <= _DVI_CHARMAX:
1154 self.putchar(cmd)
1155 elif cmd >= _DVI_SET1234 and cmd < _DVI_SET1234 + 4:
1156 self.putchar(afile.readint(cmd - _DVI_SET1234 + 1))
1157 elif cmd == _DVI_SETRULE:
1158 self.putrule(afile.readint32(), afile.readint32())
1159 elif cmd >= _DVI_PUT1234 and cmd < _DVI_PUT1234 + 4:
1160 self.putchar(afile.readint(cmd - _DVI_PUT1234 + 1), advancepos=0)
1161 elif cmd == _DVI_PUTRULE:
1162 self.putrule(afile.readint32(), afile.readint32(), 0)
1163 elif cmd == _DVI_EOP:
1164 self.endtext()
1165 if self.debug:
1166 print "%d: eop" % self.filepos
1167 print
1168 return actpage
1169 elif cmd == _DVI_PUSH:
1170 self.stack.append(list(self.pos))
1171 if self.debug:
1172 print "%d: push" % self.filepos
1173 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
1174 (( len(self.stack)-1,)+tuple(self.pos)))
1175 elif cmd == _DVI_POP:
1176 self.flushout()
1177 self.pos = self.stack.pop()
1178 if self.debug:
1179 print "%d: pop" % self.filepos
1180 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
1181 (( len(self.stack),)+tuple(self.pos)))
1182 elif cmd >= _DVI_RIGHT1234 and cmd < _DVI_RIGHT1234 + 4:
1183 self.flushout()
1184 dh = afile.readint(cmd - _DVI_RIGHT1234 + 1, 1)
1185 if self.debug:
1186 print ("%d: right%d %d h:=%d%+d=%d, hh:=" %
1187 (self.filepos,
1188 cmd - _DVI_RIGHT1234 + 1,
1190 self.pos[_POS_H],
1192 self.pos[_POS_H]+dh))
1193 self.pos[_POS_H] += dh
1194 elif cmd == _DVI_W0:
1195 self.flushout()
1196 if self.debug:
1197 print ("%d: w0 %d h:=%d%+d=%d, hh:=" %
1198 (self.filepos,
1199 self.pos[_POS_W],
1200 self.pos[_POS_H],
1201 self.pos[_POS_W],
1202 self.pos[_POS_H]+self.pos[_POS_W]))
1203 self.pos[_POS_H] += self.pos[_POS_W]
1204 elif cmd >= _DVI_W1234 and cmd < _DVI_W1234 + 4:
1205 self.flushout()
1206 self.pos[_POS_W] = afile.readint(cmd - _DVI_W1234 + 1, 1)
1207 if self.debug:
1208 print ("%d: w%d %d h:=%d%+d=%d, hh:=" %
1209 (self.filepos,
1210 cmd - _DVI_W1234 + 1,
1211 self.pos[_POS_W],
1212 self.pos[_POS_H],
1213 self.pos[_POS_W],
1214 self.pos[_POS_H]+self.pos[_POS_W]))
1215 self.pos[_POS_H] += self.pos[_POS_W]
1216 elif cmd == _DVI_X0:
1217 self.flushout()
1218 self.pos[_POS_H] += self.pos[_POS_X]
1219 elif cmd >= _DVI_X1234 and cmd < _DVI_X1234 + 4:
1220 self.flushout()
1221 self.pos[_POS_X] = afile.readint(cmd - _DVI_X1234 + 1, 1)
1222 self.pos[_POS_H] += self.pos[_POS_X]
1223 elif cmd >= _DVI_DOWN1234 and cmd < _DVI_DOWN1234 + 4:
1224 self.flushout()
1225 dv = afile.readint(cmd - _DVI_DOWN1234 + 1, 1)
1226 if self.debug:
1227 print ("%d: down%d %d v:=%d%+d=%d, vv:=" %
1228 (self.filepos,
1229 cmd - _DVI_DOWN1234 + 1,
1231 self.pos[_POS_V],
1233 self.pos[_POS_V]+dv))
1234 self.pos[_POS_V] += dv
1235 elif cmd == _DVI_Y0:
1236 self.flushout()
1237 if self.debug:
1238 print ("%d: y0 %d v:=%d%+d=%d, vv:=" %
1239 (self.filepos,
1240 self.pos[_POS_Y],
1241 self.pos[_POS_V],
1242 self.pos[_POS_Y],
1243 self.pos[_POS_V]+self.pos[_POS_Y]))
1244 self.pos[_POS_V] += self.pos[_POS_Y]
1245 elif cmd >= _DVI_Y1234 and cmd < _DVI_Y1234 + 4:
1246 self.flushout()
1247 self.pos[_POS_Y] = afile.readint(cmd - _DVI_Y1234 + 1, 1)
1248 if self.debug:
1249 print ("%d: y%d %d v:=%d%+d=%d, vv:=" %
1250 (self.filepos,
1251 cmd - _DVI_Y1234 + 1,
1252 self.pos[_POS_Y],
1253 self.pos[_POS_V],
1254 self.pos[_POS_Y],
1255 self.pos[_POS_V]+self.pos[_POS_Y]))
1256 self.pos[_POS_V] += self.pos[_POS_Y]
1257 elif cmd == _DVI_Z0:
1258 self.flushout()
1259 self.pos[_POS_V] += self.pos[_POS_Z]
1260 elif cmd >= _DVI_Z1234 and cmd < _DVI_Z1234 + 4:
1261 self.flushout()
1262 self.pos[_POS_Z] = afile.readint(cmd - _DVI_Z1234 + 1, 1)
1263 self.pos[_POS_V] += self.pos[_POS_Z]
1264 elif cmd >= _DVI_FNTNUMMIN and cmd <= _DVI_FNTNUMMAX:
1265 self.usefont(cmd - _DVI_FNTNUMMIN)
1266 elif cmd >= _DVI_FNT1234 and cmd < _DVI_FNT1234 + 4:
1267 self.usefont(afile.readint(cmd - _DVI_FNT1234 + 1, 1))
1268 elif cmd >= _DVI_SPECIAL1234 and cmd < _DVI_SPECIAL1234 + 4:
1269 self.special(afile.read(afile.readint(cmd - _DVI_SPECIAL1234 + 1)))
1270 elif cmd >= _DVI_FNTDEF1234 and cmd < _DVI_FNTDEF1234 + 4:
1271 if cmd == _DVI_FNTDEF1234:
1272 num = afile.readuchar()
1273 elif cmd == _DVI_FNTDEF1234+1:
1274 num = afile.readuint16()
1275 elif cmd == _DVI_FNTDEF1234+2:
1276 num = afile.readuint24()
1277 elif cmd == _DVI_FNTDEF1234+3:
1278 # Cool, here we have according to docu a signed int. Why?
1279 num = afile.readint32()
1280 self.definefont(cmd-_DVI_FNTDEF1234+1,
1281 num,
1282 afile.readint32(),
1283 afile.readint32(),
1284 afile.readint32(),
1285 afile.read(afile.readuchar()+afile.readuchar()))
1286 else:
1287 raise DVIError
1290 ##############################################################################
1291 # VF file handling
1292 ##############################################################################
1294 _VF_LONG_CHAR = 242 # character packet (long version)
1295 _VF_FNTDEF1234 = _DVI_FNTDEF1234 # font definition
1296 _VF_PRE = _DVI_PRE # preamble
1297 _VF_POST = _DVI_POST # postamble
1299 _VF_ID = 202 # VF id byte
1301 class VFError(exceptions.Exception): pass
1303 class vffile:
1304 def __init__(self, filename, scale, fontmap, debug=0):
1305 self.filename = filename
1306 self.scale = scale
1307 self.fontmap = fontmap
1308 self.debug = debug
1309 self.fonts = {} # used fonts
1310 self.widths = {} # widths of defined chars
1311 self.chardefs = {} # dvi chunks for defined chars
1313 afile = binfile(self.filename, "rb")
1315 cmd = afile.readuchar()
1316 if cmd == _VF_PRE:
1317 if afile.readuchar() != _VF_ID: raise VFError
1318 comment = afile.read(afile.readuchar())
1319 self.cs = afile.readuint32()
1320 self.ds = afile.readuint32()
1321 else:
1322 raise VFError
1324 while 1:
1325 cmd = afile.readuchar()
1326 if cmd >= _VF_FNTDEF1234 and cmd < _VF_FNTDEF1234 + 4:
1327 # font definition
1328 if cmd == _VF_FNTDEF1234:
1329 num = afile.readuchar()
1330 elif cmd == _VF_FNTDEF1234+1:
1331 num = afile.readuint16()
1332 elif cmd == _VF_FNTDEF1234+2:
1333 num = afile.readuint24()
1334 elif cmd == _VF_FNTDEF1234+3:
1335 num = afile.readint32()
1336 c = afile.readint32()
1337 s = afile.readint32() # relative scaling used for font (fix_word)
1338 d = afile.readint32() # design size of font
1339 fontname = afile.read(afile.readuchar()+afile.readuchar())
1341 # rescaled size of font: s is relative to the scaling
1342 # of the virtual font itself. Note that realscale has
1343 # to be a fix_word (like s)
1344 # Furthermore we have to correct for self.tfmconv
1346 reals = int(self.scale * float(fix_word(self.ds))*s)
1347 # print ("defining font %s -- VF scale: %g, VF design size: %g, relative font size: %g => real size: %g" %
1348 # (fontname, self.scale, fix_word(self.ds), fix_word(s), fix_word(reals))
1350 # reald = int(d)
1352 # XXX allow for virtual fonts here too
1353 self.fonts[num] = type1font(fontname, c, reals, d, self.fontmap, self.debug > 1)
1354 elif cmd == _VF_LONG_CHAR:
1355 # character packet (long form)
1356 pl = afile.readuint32() # packet length
1357 cc = afile.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1358 tfm = afile.readuint24() # character width
1359 dvi = afile.read(pl) # dvi code of character
1360 self.widths[cc] = tfm
1361 self.chardefs[cc] = dvi
1362 elif cmd < _VF_LONG_CHAR:
1363 # character packet (short form)
1364 cc = afile.readuchar() # char code
1365 tfm = afile.readuint24() # character width
1366 dvi = afile.read(cmd)
1367 self.widths[cc] = tfm
1368 self.chardefs[cc] = dvi
1369 elif cmd == _VF_POST:
1370 break
1371 else:
1372 raise VFError
1374 afile.close()
1376 def getfonts(self):
1377 return self.fonts
1379 def getchar(self, cc):
1380 return self.chardefs[cc]