disable outlines (we do not yet have those)
[PyX/mjg.git] / pyx / dvifile.py
blob5f0f4c50175b684c63f11e91f7f7fb1a08df1f0b
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,
439 self.font.getbasepsname(),
440 self.font.getfontfile(),
441 self.font.getencodingfile(),
442 self.font.usedchars)]
443 if self.font.getencoding():
444 result.append(_ReEncodeFont)
445 result.append(prolog.fontencoding(self.font.getencoding(), self.font.getencodingfile()))
446 result.append(prolog.fontreencoding(self.font.getpsname(), self.font.getbasepsname(), self.font.getencoding()))
447 return result
450 class _show(base.PSCmd):
452 def __init__(self, x, y):
453 self.x = x
454 self.y = y
455 self.width = 0
456 self.height = 0
457 self.depth = 0
458 self.chars = []
460 def addchar(self, width, height, depth, char):
461 self.width += width
462 if height > self.height:
463 self.height = height
464 if depth > self.depth:
465 self.depth = depth
466 self.chars.append(char)
468 def bbox(self):
469 return bbox.bbox_pt(self.x, self.y-self.depth, self.x+self.width, self.y+self.height)
471 def outputPS(self, file):
472 outstring = ""
473 for char in self.chars:
474 if char > 32 and char < 127 and chr(char) not in "()[]<>\\":
475 ascii = "%s" % chr(char)
476 else:
477 ascii = "\\%03o" % char
478 outstring += ascii
479 file.write("%g %g moveto (%s) show\n" % (self.x, self.y, outstring))
481 def outputPDF(self, file):
482 outstring = ""
483 for char in self.chars:
484 if char > 32 and char < 127 and chr(char) not in "()[]<>\\":
485 ascii = "%s" % chr(char)
486 else:
487 ascii = "\\%03o" % char
488 outstring += ascii
489 # file.write("%f %f Td (%s) Tj\n" % (self.x, self.y, outstring))
490 file.write("1 0 0 1 %f %f Tm (%s) Tj\n" % (self.x, self.y, outstring))
493 class fontmapping:
495 tokenpattern = re.compile(r'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
497 def __init__(self, s):
498 """ construct font mapping from line s of font mapping file """
499 self.texname = self.basepsname = self.fontfile = None
501 # standard encoding
502 self.encodingfile = None
504 # supported postscript fragments occuring in psfonts.map
505 self.reencodefont = self.extendfont = self.slantfont = None
507 tokens = []
508 while len(s):
509 match = self.tokenpattern.match(s)
510 if match:
511 if match.groups()[0]:
512 tokens.append('"%s"' % match.groups()[0])
513 else:
514 tokens.append(match.groups()[2])
515 s = s[match.end():]
516 else:
517 raise RuntimeError("wrong syntax")
519 for token in tokens:
520 if token.startswith("<"):
521 if token.startswith("<<"):
522 # XXX: support non-partial download here
523 self.fontfile = token[2:]
524 elif token.startswith("<["):
525 self.encodingfile = token[2:]
526 elif token.endswith(".pfa") or token.endswith(".pfb"):
527 self.fontfile = token[1:]
528 elif token.endswith(".enc"):
529 self.encodingfile = token[1:]
530 else:
531 raise RuntimeError("wrong syntax")
532 elif token.startswith('"'):
533 pscode = token[1:-1].split()
534 # parse standard postscript code fragments
535 while pscode:
536 try:
537 arg, cmd = pscode[:2]
538 except:
539 raise RuntimeError("Unsupported Postscript fragment '%s'" % pscode)
540 pscode = pscode[2:]
541 if cmd == "ReEncodeFont":
542 self.reencodefont = arg
543 elif cmd == "ExtendFont":
544 self.extendfont = arg
545 elif cmd == "SlantFont":
546 self.slantfont = arg
547 else:
548 raise RuntimeError("Unsupported Postscript fragment '%s %s'" % (arg, cmd))
549 else:
550 if self.texname is None:
551 self.texname = token
552 else:
553 self.basepsname = token
554 if self.basepsname is None:
555 self.basepsname = self.texname
557 def __str__(self):
558 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
559 (self.texname, self.basepsname, self.fontfile, repr(self.encodingfile)))
561 # generate fontmap
563 def readfontmap(filenames):
564 """ read font map from filename (without path) """
565 fontmap = {}
566 for filename in filenames:
567 mappath = pykpathsea.find_file(filename, pykpathsea.kpse_fontmap_format)
568 # try also the oft-used registration as dvips config file
569 if not mappath:
570 mappath = pykpathsea.find_file(filename, pykpathsea.kpse_dvips_config_format)
571 if not mappath:
572 raise RuntimeError("cannot find font mapping file '%s'" % filename)
573 mapfile = open(mappath, "r")
574 lineno = 0
575 for line in mapfile.readlines():
576 lineno += 1
577 line = line.rstrip()
578 if not (line=="" or line[0] in (" ", "%", "*", ";" , "#")):
579 try:
580 fm = fontmapping(line)
581 except RuntimeError, e:
582 sys.stderr.write("*** PyX Warning: Ignoring line %i in mapping file '%s': %s\n" % (lineno, filename, e))
583 else:
584 fontmap[fm.texname] = fm
585 mapfile.close()
586 return fontmap
589 class font:
590 def __init__(self, name, c, q, d, debug=0):
591 self.name = name
592 self.q = q # desired size of font (fix_word) in tex points
593 self.d = d # design size of font (fix_word) in tex points
594 tfmpath = pykpathsea.find_file("%s.tfm" % self.name, pykpathsea.kpse_tfm_format)
595 if not tfmpath:
596 raise TFMError("cannot find %s.tfm" % self.name)
597 self.tfmfile = tfmfile(tfmpath, debug)
599 # We only check for equality of font checksums if none of them
600 # is zero. The case c == 0 happend in some VF files and
601 # according to the VFtoVP documentation, paragraph 40, a check
602 # is only performed if tfmfile.checksum > 0. Anyhow, being
603 # more generous here seems to be reasonable
604 if self.tfmfile.checksum != c and self.tfmfile.checksum != 0 and c !=0:
605 raise DVIError("check sums do not agree: %d vs. %d" %
606 (self.tfmfile.checksum, c))
608 # tfmfile.designsizeraw is the design size of the font as a fix_word
609 if abs(self.tfmfile.designsizeraw - d) > 2:
610 raise DVIError("design sizes do not agree: %d vs. %d" % (self.tfmfile.designsizeraw, d))
611 if q < 0 or q > 134217728:
612 raise DVIError("font '%s' not loaded: bad scale" % self.name)
613 if d < 0 or d > 134217728:
614 raise DVIError("font '%s' not loaded: bad design size" % self.name)
616 self.scale = 1.0*q/d
618 # for bookkeeping of used characters
619 self.usedchars = [0] * 256
621 def __str__(self):
622 return "font %s designed at %g tex pts used at %g tex pts" % (self.name,
623 16.0*self.d/16777216L,
624 16.0*self.q/16777216L)
627 __repr__ = __str__
629 def getsize(self):
630 """ return size of font in (PS) points """
631 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
632 # to the corresponding float. Furthermore, we have to convert from TeX
633 # points to points, hence the factor 72/72.27.
634 return 16L*self.q/16777216L*72/72.27
636 def _convert(self, width):
637 return 16L*width*self.q/16777216L
639 def getwidth(self, charcode):
640 return self._convert(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
642 def getheight(self, charcode):
643 return self._convert(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
645 def getdepth(self, charcode):
646 return self._convert(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
648 def getitalic(self, charcode):
649 return self._convert(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
651 def markcharused(self, charcode):
652 self.usedchars[charcode] = 1
654 def mergeusedchars(self, otherfont):
655 for i in range(len(self.usedchars)):
656 self.usedchars[i] = self.usedchars[i] or otherfont.usedchars[i]
658 def clearusedchars(self):
659 self.usedchars = [0] * 256
662 class type1font(font):
663 def __init__(self, name, c, q, d, fontmap, debug=0):
664 font.__init__(self, name, c, q, d, debug)
665 self.fontmapping = fontmap.get(name)
666 if self.fontmapping is None:
667 raise RuntimeError("no information for font '%s' found in font mapping file, aborting" % name)
669 def getbasepsname(self):
670 return self.fontmapping.basepsname
672 def getpsname(self):
673 if self.fontmapping.reencodefont:
674 return "%s-%s" % (self.fontmapping.basepsname, self.fontmapping.reencodefont)
675 else:
676 return self.fontmapping.basepsname
678 def getfontfile(self):
679 return self.fontmapping.fontfile
681 def getencoding(self):
682 return self.fontmapping.reencodefont
684 def getencodingfile(self):
685 return self.fontmapping.encodingfile
688 class virtualfont(font):
689 def __init__(self, name, c, q, d, fontmap, debug=0):
690 font.__init__(self, name, c, q, d, debug)
691 fontpath = pykpathsea.find_file(name, pykpathsea.kpse_vf_format)
692 if fontpath is None or not len(fontpath):
693 raise RuntimeError
694 self.vffile = vffile(fontpath, self.scale, fontmap, debug > 1)
696 def getfonts(self):
697 """ return fonts used in virtual font itself """
698 return self.vffile.getfonts()
700 def getchar(self, cc):
701 """ return dvi chunk corresponding to char code cc """
702 return self.vffile.getchar(cc)
705 ##############################################################################
706 # DVI file handling
707 ##############################################################################
709 _DVI_CHARMIN = 0 # typeset a character and move right (range min)
710 _DVI_CHARMAX = 127 # typeset a character and move right (range max)
711 _DVI_SET1234 = 128 # typeset a character and move right
712 _DVI_SETRULE = 132 # typeset a rule and move right
713 _DVI_PUT1234 = 133 # typeset a character
714 _DVI_PUTRULE = 137 # typeset a rule
715 _DVI_NOP = 138 # no operation
716 _DVI_BOP = 139 # beginning of page
717 _DVI_EOP = 140 # ending of page
718 _DVI_PUSH = 141 # save the current positions (h, v, w, x, y, z)
719 _DVI_POP = 142 # restore positions (h, v, w, x, y, z)
720 _DVI_RIGHT1234 = 143 # move right
721 _DVI_W0 = 147 # move right by w
722 _DVI_W1234 = 148 # move right and set w
723 _DVI_X0 = 152 # move right by x
724 _DVI_X1234 = 153 # move right and set x
725 _DVI_DOWN1234 = 157 # move down
726 _DVI_Y0 = 161 # move down by y
727 _DVI_Y1234 = 162 # move down and set y
728 _DVI_Z0 = 166 # move down by z
729 _DVI_Z1234 = 167 # move down and set z
730 _DVI_FNTNUMMIN = 171 # set current font (range min)
731 _DVI_FNTNUMMAX = 234 # set current font (range max)
732 _DVI_FNT1234 = 235 # set current font
733 _DVI_SPECIAL1234 = 239 # special (dvi extention)
734 _DVI_FNTDEF1234 = 243 # define the meaning of a font number
735 _DVI_PRE = 247 # preamble
736 _DVI_POST = 248 # postamble beginning
737 _DVI_POSTPOST = 249 # postamble ending
739 _DVI_VERSION = 2 # dvi version
741 # position variable indices
742 _POS_H = 0
743 _POS_V = 1
744 _POS_W = 2
745 _POS_X = 3
746 _POS_Y = 4
747 _POS_Z = 5
749 # reader states
750 _READ_PRE = 1
751 _READ_NOPAGE = 2
752 _READ_PAGE = 3
753 _READ_POST = 4 # XXX not used
754 _READ_POSTPOST = 5 # XXX not used
755 _READ_DONE = 6
758 class DVIError(exceptions.Exception): pass
760 # save and restore colors
762 class _savecolor(base.PSOp):
763 def outputPS(self, file):
764 file.write("currentcolor currentcolorspace\n")
767 class _restorecolor(base.PSOp):
768 def outputPS(self, file):
769 file.write("setcolorspace setcolor\n")
771 class _savetrafo(base.PSOp):
772 def outputPS(self, file):
773 file.write("matrix currentmatrix\n")
776 class _restoretrafo(base.PSOp):
777 def outputPS(self, file):
778 file.write("setmatrix\n")
781 class dvifile:
783 def __init__(self, filename, fontmap, debug=0):
784 """ opens the dvi file and reads the preamble """
785 self.filename = filename
786 self.fontmap = fontmap
787 self.debug = debug
789 self.fonts = {}
790 self.activefont = None
792 # stack of fonts and fontscale currently used (used for VFs)
793 self.fontstack = []
794 self.stack = []
796 # pointer to currently active page
797 self.actpage = None
799 # currently active output: show instance currently used and
800 # the corresponding type 1 font
801 self.activeshow = None
802 self.activetype1font = None
804 # stack for self.file, self.fonts and self.stack, needed for VF inclusion
805 self.statestack = []
807 self.file = binfile(self.filename, "rb")
809 # currently read byte in file (for debugging output)
810 self.filepos = None
812 self._read_pre()
814 # helper routines
816 def flushout(self):
817 """ flush currently active string """
818 if self.activeshow is not None:
819 if self.debug:
820 print "[%s]" % "".join([chr(char) for char in self.activeshow.chars])
821 self.actpage.insert(self.activeshow)
822 self.activeshow = None
824 def begintext(self):
825 """ activate the font if is not yet active, closing a currently active
826 text object and flushing the output"""
827 if isinstance(self.activefont, type1font):
828 self.endtext()
829 if self.activetype1font != self.activefont and self.activefont:
830 self.actpage.insert(_begintextobject())
831 self.actpage.insert(selectfont(self.activefont))
832 self.activetype1font = self.activefont
834 def endtext(self):
835 self.flushout()
836 if self.activetype1font:
837 self.actpage.insert(_endtextobject())
838 self.activetype1font = None
840 def putrule(self, height, width, advancepos=1):
841 self.endtext()
842 x1 = self.pos[_POS_H] * self.conv
843 y1 = -self.pos[_POS_V] * self.conv
844 w = width * self.conv
845 h = height * self.conv
847 if height > 0 and width > 0:
848 if self.debug:
849 pixelw = int(width*self.trueconv*self.mag/1000.0)
850 if pixelw < width*self.conv: pixelw += 1
851 pixelh = int(height*self.trueconv*self.mag/1000.0)
852 if pixelh < height*self.conv: pixelh += 1
854 print ("%d: %srule height %d, width %d (%dx%d pixels)" %
855 (self.filepos, advancepos and "set" or "put", height, width, pixelh, pixelw))
856 self.actpage.fill(path.rect_pt(x1, y1, w, h))
857 else:
858 if self.debug:
859 print ("%d: %srule height %d, width %d (invisible)" %
860 (self.filepos, advancepos and "set" or "put", height, width))
862 if advancepos:
863 if self.debug:
864 print (" h:=%d+%d=%d, hh:=%d" %
865 (self.pos[_POS_H], width, self.pos[_POS_H]+width, 0))
866 self.pos[_POS_H] += width
868 def putchar(self, char, advancepos=1):
869 dx = advancepos and int(round(self.activefont.getwidth(char)*self.tfmconv)) or 0
871 if self.debug:
872 print ("%d: %schar%d h:=%d+%d=%d, hh:=%d" %
873 (self.filepos,
874 advancepos and "set" or "put",
875 char,
876 self.pos[_POS_H], dx, self.pos[_POS_H]+dx,
879 if isinstance(self.activefont, type1font):
880 if self.activeshow is None:
881 self.begintext()
882 self.activeshow = _show(self.pos[_POS_H] * self.conv, -self.pos[_POS_V] * self.conv)
883 width = self.activefont.getwidth(char) * self.tfmconv * self.conv
884 height = self.activefont.getheight(char) * self.tfmconv * self.conv
885 depth = self.activefont.getdepth(char) * self.tfmconv * self.conv
886 self.activeshow.addchar(width, height, depth, char)
888 self.activefont.markcharused(char)
889 self.pos[_POS_H] += dx
890 else:
891 # virtual font handling
892 afterpos = list(self.pos)
893 afterpos[_POS_H] += dx
894 self._push_dvistring(self.activefont.getchar(char), self.activefont.getfonts(), afterpos,
895 self.activefont.getsize())
898 if not advancepos:
899 # XXX: correct !?
900 self.flushout()
902 def usefont(self, fontnum):
903 if self.debug:
904 print ("%d: fntnum%i current font is %s" %
905 (self.filepos,
906 fontnum, self.fonts[fontnum].name))
908 self.activefont = self.fonts[fontnum]
909 self.begintext()
911 def definefont(self, cmdnr, num, c, q, d, fontname):
912 # cmdnr: type of fontdef command (only used for debugging output)
913 # c: checksum
914 # q: scaling factor (fix_word)
915 # Note that q is actually s in large parts of the documentation.
916 # d: design size (fix_word)
918 try:
919 font = virtualfont(fontname, c, q/self.tfmconv, d/self.tfmconv, self.fontmap, self.debug > 1)
920 except (TypeError, RuntimeError):
921 font = type1font(fontname, c, q/self.tfmconv, d/self.tfmconv, self.fontmap, self.debug > 1)
923 self.fonts[num] = font
925 if self.debug:
926 print "%d: fntdef%d %i: %s" % (self.filepos, cmdnr, num, fontname)
928 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
929 # m = 1.0*q/d
930 # scalestring = scale!=1000 and " scaled %d" % scale or ""
931 # print ("Font %i: %s%s---loaded at size %d DVI units" %
932 # (num, fontname, scalestring, q))
933 # if scale!=1000:
934 # print " (this font is magnified %d%%)" % round(scale/10)
936 def special(self, s):
937 x = self.pos[_POS_H] * self.conv
938 y = -self.pos[_POS_V] * self.conv
939 if self.debug:
940 print "%d: xxx '%s'" % (self.filepos, s)
941 if not s.startswith("PyX:"):
942 if s.startswith("Warning:"):
943 sys.stderr.write("*** PyX Warning: ignoring special '%s'\n" % s)
944 return
945 else:
946 raise RuntimeError("the special '%s' cannot be handled by PyX, aborting" % s)
948 # it is in general not safe to continue using the currently active font because
949 # the specials may involve some gsave/grestore operations
950 self.endtext()
952 command, args = s[4:].split()[0], s[4:].split()[1:]
953 if command=="color_begin":
954 if args[0]=="cmyk":
955 c = color.cmyk(float(args[1]), float(args[2]), float(args[3]), float(args[4]))
956 elif args[0]=="gray":
957 c = color.gray(float(args[1]))
958 elif args[0]=="hsb":
959 c = color.hsb(float(args[1]), float(args[2]), float(args[3]))
960 elif args[0]=="rgb":
961 c = color.rgb(float(args[1]), float(args[2]), float(args[3]))
962 elif args[0]=="RGB":
963 c = color.rgb(int(args[1])/255.0, int(args[2])/255.0, int(args[3])/255.0)
964 elif args[0]=="texnamed":
965 try:
966 c = getattr(color.cmyk, args[1])
967 except AttributeError:
968 raise RuntimeError("unknown TeX color '%s', aborting" % args[1])
969 else:
970 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args[0])
971 self.actpage.insert(_savecolor())
972 self.actpage.insert(c)
973 elif command=="color_end":
974 self.actpage.insert(_restorecolor())
975 elif command=="rotate_begin":
976 self.actpage.insert(_savetrafo())
977 self.actpage.insert(trafo.rotate_pt(float(args[0]), x, y))
978 elif command=="rotate_end":
979 self.actpage.insert(_restoretrafo())
980 elif command=="scale_begin":
981 self.actpage.insert(_savetrafo())
982 self.actpage.insert(trafo.scale_pt(float(args[0]), float(args[1]), x, y))
983 elif command=="scale_end":
984 self.actpage.insert(_restoretrafo())
985 elif command=="epsinclude":
986 # parse arguments
987 argdict = {}
988 for arg in args:
989 name, value = arg.split("=")
990 argdict[name] = value
992 # construct kwargs for epsfile constructor
993 epskwargs = {}
994 epskwargs["filename"] = argdict["file"]
995 epskwargs["bbox"] = bbox.bbox_pt(float(argdict["llx"]), float(argdict["lly"]),
996 float(argdict["urx"]), float(argdict["ury"]))
997 if argdict.has_key("width"):
998 epskwargs["width"] = float(argdict["width"]) * unit.t_pt
999 if argdict.has_key("height"):
1000 epskwargs["height"] = float(argdict["height"]) * unit.t_pt
1001 if argdict.has_key("clip"):
1002 epskwargs["clip"] = int(argdict["clip"])
1003 self.actpage.insert(epsfile.epsfile(x * unit.t_pt, y * unit.t_pt, **epskwargs))
1004 elif command=="marker":
1005 if len(args) != 1:
1006 raise RuntimeError("marker contains spaces")
1007 for c in args[0]:
1008 if c not in string.digits + string.letters + "@":
1009 raise RuntimeError("marker contains invalid characters")
1010 if self.actpage.markers.has_key(args[0]):
1011 raise RuntimeError("marker name occurred several times")
1012 self.actpage.markers[args[0]] = x * unit.t_pt, y * unit.t_pt
1013 else:
1014 raise RuntimeError("unknown PyX special '%s', aborting" % command)
1015 self.begintext()
1017 # routines for pushing and popping different dvi chunks on the reader
1019 def _push_dvistring(self, dvi, fonts, afterpos, fontsize):
1020 """ push dvi string with defined fonts on top of reader
1021 stack. Every positions gets scaled relatively by the factor
1022 scale. After the interpreting of the dvi chunk has been finished,
1023 continue with self.pos=afterpos. The designsize of the virtual
1024 font is passed as a fix_word
1028 if self.debug:
1029 print "executing new dvi chunk"
1030 self.statestack.append((self.file, self.fonts, self.activefont, afterpos, self.stack, self.conv, self.tfmconv))
1032 # units in vf files are relative to the size of the font and given as fix_words
1033 # which can be converted to floats by diving by 2**20
1034 oldconv = self.conv
1035 self.conv = fontsize/2**20
1036 rescale = self.conv/oldconv
1038 self.file = stringbinfile(dvi)
1039 self.fonts = fonts
1040 self.stack = []
1041 self.filepos = 0
1043 # rescale self.pos in order to be consistent with the new scaling
1044 self.pos = map(lambda x, rescale=rescale:1.0*x/rescale, self.pos)
1046 # since tfmconv converts from tfm units to dvi units, rescale it as well
1047 self.tfmconv /= rescale
1049 self.usefont(0)
1051 def _pop_dvistring(self):
1052 self.flushout()
1053 if self.debug:
1054 print "finished executing dvi chunk"
1055 self.file.close()
1056 self.file, self.fonts, self.activefont, self.pos, self.stack, self.conv, self.tfmconv = self.statestack.pop()
1058 # routines corresponding to the different reader states of the dvi maschine
1060 def _read_pre(self):
1061 afile = self.file
1062 while 1:
1063 self.filepos = afile.tell()
1064 cmd = afile.readuchar()
1065 if cmd == _DVI_NOP:
1066 pass
1067 elif cmd == _DVI_PRE:
1068 if afile.readuchar() != _DVI_VERSION: raise DVIError
1069 num = afile.readuint32()
1070 den = afile.readuint32()
1071 self.mag = afile.readuint32()
1073 # for the interpretation of all quantities, two conversion factors
1074 # are relevant:
1075 # - self.tfmconv (tfm units->dvi units)
1076 # - self.conv (dvi units-> (PostScript) points)
1078 self.tfmconv = (25400000.0/num)*(den/473628672.0)/16.0
1080 # calculate self.conv as described in the DVIType docu
1082 # resolution in dpi
1083 self.resolution = 300.0
1084 # self.trueconv = conv in DVIType docu
1085 self.trueconv = (num/254000.0)*(self.resolution/den)
1087 # self.conv is the conversion factor from the dvi units
1088 # to (PostScript) points. It consists of
1089 # - self.mag/1000.0: magstep scaling
1090 # - self.trueconv: conversion from dvi units to pixels
1091 # - 1/self.resolution: conversion from pixels to inch
1092 # - 72 : conversion from inch to points
1093 self.conv = self.mag/1000.0*self.trueconv/self.resolution*72
1095 comment = afile.read(afile.readuchar())
1096 return
1097 else:
1098 raise DVIError
1100 def readpage(self, pageid=None):
1101 """ reads a page from the dvi file
1103 This routine reads a page from the dvi file which is
1104 returned as a canvas. When there is no page left in the
1105 dvifile, None is returned and the file is closed properly."""
1108 while 1:
1109 self.filepos = self.file.tell()
1110 cmd = self.file.readuchar()
1111 if cmd == _DVI_NOP:
1112 pass
1113 elif cmd == _DVI_BOP:
1114 # self.endtext()
1115 ispageid = [self.file.readuint32() for i in range(10)]
1116 #if ispageid[:3] != [ord("P"), ord("y"), ord("X")] or ispageid[4:] != [0, 0, 0, 0, 0, 0]:
1117 if pageid is not None and ispageid != pageid:
1118 raise DVIError("invalid pageid")
1119 if self.debug:
1120 print "%d: beginning of page %i" % (self.filepos, ispageid[0])
1121 self.file.readuint32()
1122 break
1123 elif cmd == _DVI_POST:
1124 self.file.close()
1125 return None # nothing left
1126 else:
1127 raise DVIError
1129 actpage = canvas.canvas()
1130 self.actpage = actpage # XXX should be removed ...
1131 self.actpage.markers = {}
1132 self.pos = [0, 0, 0, 0, 0, 0]
1133 self.activetype1font = None
1135 # Since we do not know which dvi pages the actual PS file contains later on,
1136 # we have to keep track of used char informations separately for each dvi page.
1137 # In order to do so, the already defined fonts have to be copied and their
1138 # used char informations have to be reset
1139 for nr in self.fonts.keys():
1140 self.fonts[nr] = copy.copy(self.fonts[nr])
1141 self.fonts[nr].clearusedchars()
1143 while 1:
1144 afile = self.file
1145 self.filepos = afile.tell()
1146 try:
1147 cmd = afile.readuchar()
1148 except struct.error:
1149 # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
1150 # so we have to continue with the rest of the dvi file
1151 self._pop_dvistring()
1152 continue
1153 if cmd == _DVI_NOP:
1154 pass
1155 if cmd >= _DVI_CHARMIN and cmd <= _DVI_CHARMAX:
1156 self.putchar(cmd)
1157 elif cmd >= _DVI_SET1234 and cmd < _DVI_SET1234 + 4:
1158 self.putchar(afile.readint(cmd - _DVI_SET1234 + 1))
1159 elif cmd == _DVI_SETRULE:
1160 self.putrule(afile.readint32(), afile.readint32())
1161 elif cmd >= _DVI_PUT1234 and cmd < _DVI_PUT1234 + 4:
1162 self.putchar(afile.readint(cmd - _DVI_PUT1234 + 1), advancepos=0)
1163 elif cmd == _DVI_PUTRULE:
1164 self.putrule(afile.readint32(), afile.readint32(), 0)
1165 elif cmd == _DVI_EOP:
1166 self.endtext()
1167 if self.debug:
1168 print "%d: eop" % self.filepos
1169 print
1170 return actpage
1171 elif cmd == _DVI_PUSH:
1172 self.stack.append(list(self.pos))
1173 if self.debug:
1174 print "%d: push" % self.filepos
1175 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
1176 (( len(self.stack)-1,)+tuple(self.pos)))
1177 elif cmd == _DVI_POP:
1178 self.flushout()
1179 self.pos = self.stack.pop()
1180 if self.debug:
1181 print "%d: pop" % self.filepos
1182 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
1183 (( len(self.stack),)+tuple(self.pos)))
1184 elif cmd >= _DVI_RIGHT1234 and cmd < _DVI_RIGHT1234 + 4:
1185 self.flushout()
1186 dh = afile.readint(cmd - _DVI_RIGHT1234 + 1, 1)
1187 if self.debug:
1188 print ("%d: right%d %d h:=%d%+d=%d, hh:=" %
1189 (self.filepos,
1190 cmd - _DVI_RIGHT1234 + 1,
1192 self.pos[_POS_H],
1194 self.pos[_POS_H]+dh))
1195 self.pos[_POS_H] += dh
1196 elif cmd == _DVI_W0:
1197 self.flushout()
1198 if self.debug:
1199 print ("%d: w0 %d h:=%d%+d=%d, hh:=" %
1200 (self.filepos,
1201 self.pos[_POS_W],
1202 self.pos[_POS_H],
1203 self.pos[_POS_W],
1204 self.pos[_POS_H]+self.pos[_POS_W]))
1205 self.pos[_POS_H] += self.pos[_POS_W]
1206 elif cmd >= _DVI_W1234 and cmd < _DVI_W1234 + 4:
1207 self.flushout()
1208 self.pos[_POS_W] = afile.readint(cmd - _DVI_W1234 + 1, 1)
1209 if self.debug:
1210 print ("%d: w%d %d h:=%d%+d=%d, hh:=" %
1211 (self.filepos,
1212 cmd - _DVI_W1234 + 1,
1213 self.pos[_POS_W],
1214 self.pos[_POS_H],
1215 self.pos[_POS_W],
1216 self.pos[_POS_H]+self.pos[_POS_W]))
1217 self.pos[_POS_H] += self.pos[_POS_W]
1218 elif cmd == _DVI_X0:
1219 self.flushout()
1220 self.pos[_POS_H] += self.pos[_POS_X]
1221 elif cmd >= _DVI_X1234 and cmd < _DVI_X1234 + 4:
1222 self.flushout()
1223 self.pos[_POS_X] = afile.readint(cmd - _DVI_X1234 + 1, 1)
1224 self.pos[_POS_H] += self.pos[_POS_X]
1225 elif cmd >= _DVI_DOWN1234 and cmd < _DVI_DOWN1234 + 4:
1226 self.flushout()
1227 dv = afile.readint(cmd - _DVI_DOWN1234 + 1, 1)
1228 if self.debug:
1229 print ("%d: down%d %d v:=%d%+d=%d, vv:=" %
1230 (self.filepos,
1231 cmd - _DVI_DOWN1234 + 1,
1233 self.pos[_POS_V],
1235 self.pos[_POS_V]+dv))
1236 self.pos[_POS_V] += dv
1237 elif cmd == _DVI_Y0:
1238 self.flushout()
1239 if self.debug:
1240 print ("%d: y0 %d v:=%d%+d=%d, vv:=" %
1241 (self.filepos,
1242 self.pos[_POS_Y],
1243 self.pos[_POS_V],
1244 self.pos[_POS_Y],
1245 self.pos[_POS_V]+self.pos[_POS_Y]))
1246 self.pos[_POS_V] += self.pos[_POS_Y]
1247 elif cmd >= _DVI_Y1234 and cmd < _DVI_Y1234 + 4:
1248 self.flushout()
1249 self.pos[_POS_Y] = afile.readint(cmd - _DVI_Y1234 + 1, 1)
1250 if self.debug:
1251 print ("%d: y%d %d v:=%d%+d=%d, vv:=" %
1252 (self.filepos,
1253 cmd - _DVI_Y1234 + 1,
1254 self.pos[_POS_Y],
1255 self.pos[_POS_V],
1256 self.pos[_POS_Y],
1257 self.pos[_POS_V]+self.pos[_POS_Y]))
1258 self.pos[_POS_V] += self.pos[_POS_Y]
1259 elif cmd == _DVI_Z0:
1260 self.flushout()
1261 self.pos[_POS_V] += self.pos[_POS_Z]
1262 elif cmd >= _DVI_Z1234 and cmd < _DVI_Z1234 + 4:
1263 self.flushout()
1264 self.pos[_POS_Z] = afile.readint(cmd - _DVI_Z1234 + 1, 1)
1265 self.pos[_POS_V] += self.pos[_POS_Z]
1266 elif cmd >= _DVI_FNTNUMMIN and cmd <= _DVI_FNTNUMMAX:
1267 self.usefont(cmd - _DVI_FNTNUMMIN)
1268 elif cmd >= _DVI_FNT1234 and cmd < _DVI_FNT1234 + 4:
1269 self.usefont(afile.readint(cmd - _DVI_FNT1234 + 1, 1))
1270 elif cmd >= _DVI_SPECIAL1234 and cmd < _DVI_SPECIAL1234 + 4:
1271 self.special(afile.read(afile.readint(cmd - _DVI_SPECIAL1234 + 1)))
1272 elif cmd >= _DVI_FNTDEF1234 and cmd < _DVI_FNTDEF1234 + 4:
1273 if cmd == _DVI_FNTDEF1234:
1274 num = afile.readuchar()
1275 elif cmd == _DVI_FNTDEF1234+1:
1276 num = afile.readuint16()
1277 elif cmd == _DVI_FNTDEF1234+2:
1278 num = afile.readuint24()
1279 elif cmd == _DVI_FNTDEF1234+3:
1280 # Cool, here we have according to docu a signed int. Why?
1281 num = afile.readint32()
1282 self.definefont(cmd-_DVI_FNTDEF1234+1,
1283 num,
1284 afile.readint32(),
1285 afile.readint32(),
1286 afile.readint32(),
1287 afile.read(afile.readuchar()+afile.readuchar()))
1288 else:
1289 raise DVIError
1292 ##############################################################################
1293 # VF file handling
1294 ##############################################################################
1296 _VF_LONG_CHAR = 242 # character packet (long version)
1297 _VF_FNTDEF1234 = _DVI_FNTDEF1234 # font definition
1298 _VF_PRE = _DVI_PRE # preamble
1299 _VF_POST = _DVI_POST # postamble
1301 _VF_ID = 202 # VF id byte
1303 class VFError(exceptions.Exception): pass
1305 class vffile:
1306 def __init__(self, filename, scale, fontmap, debug=0):
1307 self.filename = filename
1308 self.scale = scale
1309 self.fontmap = fontmap
1310 self.debug = debug
1311 self.fonts = {} # used fonts
1312 self.widths = {} # widths of defined chars
1313 self.chardefs = {} # dvi chunks for defined chars
1315 afile = binfile(self.filename, "rb")
1317 cmd = afile.readuchar()
1318 if cmd == _VF_PRE:
1319 if afile.readuchar() != _VF_ID: raise VFError
1320 comment = afile.read(afile.readuchar())
1321 self.cs = afile.readuint32()
1322 self.ds = afile.readuint32()
1323 else:
1324 raise VFError
1326 while 1:
1327 cmd = afile.readuchar()
1328 if cmd >= _VF_FNTDEF1234 and cmd < _VF_FNTDEF1234 + 4:
1329 # font definition
1330 if cmd == _VF_FNTDEF1234:
1331 num = afile.readuchar()
1332 elif cmd == _VF_FNTDEF1234+1:
1333 num = afile.readuint16()
1334 elif cmd == _VF_FNTDEF1234+2:
1335 num = afile.readuint24()
1336 elif cmd == _VF_FNTDEF1234+3:
1337 num = afile.readint32()
1338 c = afile.readint32()
1339 s = afile.readint32() # relative scaling used for font (fix_word)
1340 d = afile.readint32() # design size of font
1341 fontname = afile.read(afile.readuchar()+afile.readuchar())
1343 # rescaled size of font: s is relative to the scaling
1344 # of the virtual font itself. Note that realscale has
1345 # to be a fix_word (like s)
1346 # Furthermore we have to correct for self.tfmconv
1348 reals = int(self.scale * float(fix_word(self.ds))*s)
1349 # print ("defining font %s -- VF scale: %g, VF design size: %g, relative font size: %g => real size: %g" %
1350 # (fontname, self.scale, fix_word(self.ds), fix_word(s), fix_word(reals))
1352 # reald = int(d)
1354 # XXX allow for virtual fonts here too
1355 self.fonts[num] = type1font(fontname, c, reals, d, self.fontmap, self.debug > 1)
1356 elif cmd == _VF_LONG_CHAR:
1357 # character packet (long form)
1358 pl = afile.readuint32() # packet length
1359 cc = afile.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1360 tfm = afile.readuint24() # character width
1361 dvi = afile.read(pl) # dvi code of character
1362 self.widths[cc] = tfm
1363 self.chardefs[cc] = dvi
1364 elif cmd < _VF_LONG_CHAR:
1365 # character packet (short form)
1366 cc = afile.readuchar() # char code
1367 tfm = afile.readuint24() # character width
1368 dvi = afile.read(cmd)
1369 self.widths[cc] = tfm
1370 self.chardefs[cc] = dvi
1371 elif cmd == _VF_POST:
1372 break
1373 else:
1374 raise VFError
1376 afile.close()
1378 def getfonts(self):
1379 return self.fonts
1381 def getchar(self, cc):
1382 return self.chardefs[cc]