fix call signature
[PyX/mjg.git] / pyx / dvifile.py
blob85425c1abd18f9c94bf082d9af76bdf61c54f29e
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 import copy, cStringIO, exceptions, re, struct, string, sys, warnings
26 import unit, epsfile, bbox, canvas, color, trafo, path, pykpathsea, resource
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)
95 # class tokenfile:
96 # """ ascii file containing tokens separated by spaces.
98 # Comments beginning with % are ignored. Strings containing spaces
99 # are not handled correctly
100 # """
102 # def __init__(self, filename):
103 # self.file = open(filename, "r")
104 # self.line = None
106 # def gettoken(self):
107 # """ return next token or None if EOF """
108 # while not self.line:
109 # line = self.file.readline()
110 # if line == "":
111 # return None
112 # self.line = line.split("%")[0].split()
113 # token = self.line[0]
114 # self.line = self.line[1:]
115 # return token
117 # def close(self):
118 # self.file.close()
121 ##############################################################################
122 # TFM file handling
123 ##############################################################################
125 class TFMError(exceptions.Exception): pass
128 class char_info_word:
129 def __init__(self, word):
130 self.width_index = int((word & 0xFF000000L) >> 24) #make sign-safe
131 self.height_index = (word & 0x00F00000) >> 20
132 self.depth_index = (word & 0x000F0000) >> 16
133 self.italic_index = (word & 0x0000FC00) >> 10
134 self.tag = (word & 0x00000300) >> 8
135 self.remainder = (word & 0x000000FF)
138 class tfmfile:
139 def __init__(self, name, debug=0):
140 self.file = binfile(name, "rb")
143 # read pre header
146 self.lf = self.file.readint16()
147 self.lh = self.file.readint16()
148 self.bc = self.file.readint16()
149 self.ec = self.file.readint16()
150 self.nw = self.file.readint16()
151 self.nh = self.file.readint16()
152 self.nd = self.file.readint16()
153 self.ni = self.file.readint16()
154 self.nl = self.file.readint16()
155 self.nk = self.file.readint16()
156 self.ne = self.file.readint16()
157 self.np = self.file.readint16()
159 if not (self.bc-1 <= self.ec <= 255 and
160 self.ne <= 256 and
161 self.lf == 6+self.lh+(self.ec-self.bc+1)+self.nw+self.nh+self.nd
162 +self.ni+self.nl+self.nk+self.ne+self.np):
163 raise TFMError, "error in TFM pre-header"
165 if debug:
166 print "lh=%d" % self.lh
169 # read header
172 self.checksum = self.file.readint32()
173 self.designsize = self.file.readint32()
174 assert self.designsize > 0, "invald design size"
175 if self.lh > 2:
176 assert self.lh > 11, "inconsistency in TFM file: incomplete field"
177 self.charcoding = self.file.readstring(40)
178 else:
179 self.charcoding = None
181 if self.lh > 12:
182 assert self.lh > 16, "inconsistency in TFM file: incomplete field"
183 self.fontfamily = self.file.readstring(20)
184 else:
185 self.fontfamily = None
187 if debug:
188 print "(FAMILY %s)" % self.fontfamily
189 print "(CODINGSCHEME %s)" % self.charcoding
190 print "(DESINGSIZE R %f)" % 16.0*self.designsize/16777216L
192 if self.lh > 17:
193 self.sevenbitsave = self.file.readuchar()
194 # ignore the following two bytes
195 self.file.readint16()
196 facechar = self.file.readuchar()
197 # decode ugly face specification into the Knuth suggested string
198 if facechar < 18:
199 if facechar >= 12:
200 self.face = "E"
201 facechar -= 12
202 elif facechar >= 6:
203 self.face = "C"
204 facechar -= 6
205 else:
206 self.face = "R"
208 if facechar >= 4:
209 self.face = "L" + self.face
210 facechar -= 4
211 elif facechar >= 2:
212 self.face = "B" + self.face
213 facechar -= 2
214 else:
215 self.face = "M" + self.face
217 if facechar == 1:
218 self.face = self.face[0] + "I" + self.face[1]
219 else:
220 self.face = self.face[0] + "R" + self.face[1]
222 else:
223 self.face = None
224 else:
225 self.sevenbitsave = self.face = None
227 if self.lh > 18:
228 # just ignore the rest
229 print self.file.read((self.lh-18)*4)
232 # read char_info
235 self.char_info = [None]*(self.ec+1)
236 for charcode in range(self.bc, self.ec+1):
237 self.char_info[charcode] = char_info_word(self.file.readint32())
238 if self.char_info[charcode].width_index == 0:
239 # disable character if width_index is zero
240 self.char_info[charcode] = None
243 # read widths
246 self.width = [None for width_index in range(self.nw)]
247 for width_index in range(self.nw):
248 self.width[width_index] = self.file.readint32()
251 # read heights
254 self.height = [None for height_index in range(self.nh)]
255 for height_index in range(self.nh):
256 self.height[height_index] = self.file.readint32()
259 # read depths
262 self.depth = [None for depth_index in range(self.nd)]
263 for depth_index in range(self.nd):
264 self.depth[depth_index] = self.file.readint32()
267 # read italic
270 self.italic = [None for italic_index in range(self.ni)]
271 for italic_index in range(self.ni):
272 self.italic[italic_index] = self.file.readint32()
275 # read lig_kern
278 # XXX decode to lig_kern_command
280 self.lig_kern = [None for lig_kern_index in range(self.nl)]
281 for lig_kern_index in range(self.nl):
282 self.lig_kern[lig_kern_index] = self.file.readint32()
285 # read kern
288 self.kern = [None for kern_index in range(self.nk)]
289 for kern_index in range(self.nk):
290 self.kern[kern_index] = self.file.readint32()
293 # read exten
296 # XXX decode to extensible_recipe
298 self.exten = [None for exten_index in range(self.ne)]
299 for exten_index in range(self.ne):
300 self.exten[exten_index] = self.file.readint32()
303 # read param
306 # XXX decode
308 self.param = [None for param_index in range(self.np)]
309 for param_index in range(self.np):
310 self.param[param_index] = self.file.readint32()
312 self.file.close()
315 # class FontEncoding:
317 # def __init__(self, filename):
318 # """ font encoding contained in filename """
319 # encpath = pykpathsea.find_file(filename, pykpathsea.kpse_tex_ps_header_format)
320 # encfile = tokenfile(encpath)
322 # # name of encoding
323 # self.encname = encfile.gettoken()
324 # token = encfile.gettoken()
325 # if token != "[":
326 # raise RuntimeError("cannot parse encoding file '%s', expecting '[' got '%s'" % (filename, token))
327 # self.encvector = []
328 # for i in range(256):
329 # token = encfile.gettoken()
330 # if token is None or token=="]":
331 # raise RuntimeError("not enough charcodes in encoding file '%s'" % filename)
332 # self.encvector.append(token)
333 # if encfile.gettoken() != "]":
334 # raise RuntimeError("too many charcodes in encoding file '%s'" % filename)
335 # token = encfile.gettoken()
336 # if token != "def":
337 # raise RuntimeError("cannot parse encoding file '%s', expecting 'def' got '%s'" % (filename, token))
338 # token = encfile.gettoken()
339 # if token != None:
340 # raise RuntimeError("encoding file '%s' too long" % filename)
341 # encfile.close()
343 # def encode(self, charcode):
344 # return self.encvector[charcode]
346 ##############################################################################
347 # Font handling
348 ##############################################################################
351 # PostScript font selection and output primitives
354 class _begintextobject(canvas.canvasitem):
355 def outputPS(self, file):
356 pass
358 def outputPDF(self, file, writer, context):
359 file.write("BT\n")
362 class _endtextobject(canvas.canvasitem):
363 def outputPS(self, file):
364 pass
366 def outputPDF(self, file, writer, context):
367 file.write("ET\n")
370 class _selectfont(canvas.canvasitem):
371 # XXX this should go away and be merged with selectfont
372 def __init__(self, name, size):
373 self.name = name
374 self.size = size
376 def outputPS(self, file):
377 file.write("/%s %f selectfont\n" % (self.name, self.size))
379 def outputPDF(self, file, writer, context):
380 file.write("/%s %f Tf\n" % (self.name, self.size))
383 class selectfont(canvas.canvasitem):
385 def __init__(self, font):
386 # XXX maybe we should change the calling convention here and only pass the
387 # name, size, encoding, usedchars of the font
388 self.font = font
389 self.size = font.getsize_pt()
390 self.fontid = None
392 def resources(self):
393 fontresource = resource.font(self.font)
394 self.fontid = fontresource.id
395 return [fontresource]
397 def outputPS(self, file):
398 file.write("/%s %f selectfont\n" % (self.fontid, self.size))
400 def outputPDF(self, file, writer, context):
401 file.write("/%s %f Tf\n" % (self.fontid, self.size))
404 class _show(canvas.canvasitem):
406 def __init__(self, x, y):
407 self.x = x
408 self.y = y
409 self.width = 0
410 self.height = 0
411 self.depth = 0
412 self.chars = []
414 def addchar(self, width, height, depth, char):
415 self.width += width
416 if height > self.height:
417 self.height = height
418 if depth > self.depth:
419 self.depth = depth
420 self.chars.append(char)
422 def bbox(self):
423 return bbox.bbox_pt(self.x, self.y-self.depth, self.x+self.width, self.y+self.height)
425 def outputPS(self, file):
426 outstring = ""
427 for char in self.chars:
428 if char > 32 and char < 127 and chr(char) not in "()[]<>\\":
429 ascii = "%s" % chr(char)
430 else:
431 ascii = "\\%03o" % char
432 outstring += ascii
433 file.write("%g %g moveto (%s) show\n" % (self.x, self.y, outstring))
435 def outputPDF(self, file, writer, context):
436 outstring = ""
437 for char in self.chars:
438 if char > 32 and char < 127 and chr(char) not in "()[]<>\\":
439 ascii = "%s" % chr(char)
440 else:
441 ascii = "\\%03o" % char
442 outstring += ascii
443 # file.write("%f %f Td (%s) Tj\n" % (self.x, self.y, outstring))
444 file.write("1 0 0 1 %f %f Tm (%s) Tj\n" % (self.x, self.y, outstring))
447 class fontmapping:
449 tokenpattern = re.compile(r'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
451 def __init__(self, s):
452 """ construct font mapping from line s of font mapping file """
453 self.texname = self.basepsname = self.fontfile = None
455 # standard encoding
456 self.encodingfile = None
458 # supported postscript fragments occuring in psfonts.map
459 self.reencodefont = self.extendfont = self.slantfont = None
461 tokens = []
462 while len(s):
463 match = self.tokenpattern.match(s)
464 if match:
465 if match.groups()[0]:
466 tokens.append('"%s"' % match.groups()[0])
467 else:
468 tokens.append(match.groups()[2])
469 s = s[match.end():]
470 else:
471 raise RuntimeError("wrong syntax")
473 for token in tokens:
474 if token.startswith("<"):
475 if token.startswith("<<"):
476 # XXX: support non-partial download here
477 self.fontfile = token[2:]
478 elif token.startswith("<["):
479 self.encodingfile = token[2:]
480 elif token.endswith(".pfa") or token.endswith(".pfb"):
481 self.fontfile = token[1:]
482 elif token.endswith(".enc"):
483 self.encodingfile = token[1:]
484 else:
485 raise RuntimeError("wrong syntax")
486 elif token.startswith('"'):
487 pscode = token[1:-1].split()
488 # parse standard postscript code fragments
489 while pscode:
490 try:
491 arg, cmd = pscode[:2]
492 except:
493 raise RuntimeError("Unsupported Postscript fragment '%s'" % pscode)
494 pscode = pscode[2:]
495 if cmd == "ReEncodeFont":
496 self.reencodefont = arg
497 elif cmd == "ExtendFont":
498 self.extendfont = arg
499 elif cmd == "SlantFont":
500 self.slantfont = arg
501 else:
502 raise RuntimeError("Unsupported Postscript fragment '%s %s'" % (arg, cmd))
503 else:
504 if self.texname is None:
505 self.texname = token
506 else:
507 self.basepsname = token
508 if self.basepsname is None:
509 self.basepsname = self.texname
511 def __str__(self):
512 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
513 (self.texname, self.basepsname, self.fontfile, repr(self.encodingfile)))
515 # generate fontmap
517 def readfontmap(filenames):
518 """ read font map from filename (without path) """
519 fontmap = {}
520 for filename in filenames:
521 mappath = pykpathsea.find_file(filename, pykpathsea.kpse_fontmap_format)
522 # try also the oft-used registration as dvips config file
523 if not mappath:
524 mappath = pykpathsea.find_file(filename, pykpathsea.kpse_dvips_config_format)
525 if not mappath:
526 raise RuntimeError("cannot find font mapping file '%s'" % filename)
527 mapfile = open(mappath, "r")
528 lineno = 0
529 for line in mapfile.readlines():
530 lineno += 1
531 line = line.rstrip()
532 if not (line=="" or line[0] in (" ", "%", "*", ";" , "#")):
533 try:
534 fm = fontmapping(line)
535 except RuntimeError, e:
536 warnings.warn("Ignoring line %i in mapping file '%s': %s" % (lineno, filename, e))
537 else:
538 fontmap[fm.texname] = fm
539 mapfile.close()
540 return fontmap
543 class font:
544 def __init__(self, name, c, q, d, tfmconv, pyxconv, debug=0):
545 self.name = name
546 self.q = q # desired size of font (fix_word) in TeX points
547 self.d = d # design size of font (fix_word) in TeX points
548 self.tfmconv = tfmconv # conversion factor from tfm units to dvi units
549 self.pyxconv = pyxconv # conversion factor from dvi units to PostScript points
550 tfmpath = pykpathsea.find_file("%s.tfm" % self.name, pykpathsea.kpse_tfm_format)
551 if not tfmpath:
552 raise TFMError("cannot find %s.tfm" % self.name)
553 self.tfmfile = tfmfile(tfmpath, debug)
555 # We only check for equality of font checksums if none of them
556 # is zero. The case c == 0 happend in some VF files and
557 # according to the VFtoVP documentation, paragraph 40, a check
558 # is only performed if tfmfile.checksum > 0. Anyhow, being
559 # more generous here seems to be reasonable
560 if self.tfmfile.checksum != c and self.tfmfile.checksum != 0 and c != 0:
561 raise DVIError("check sums do not agree: %d vs. %d" %
562 (self.tfmfile.checksum, c))
564 # Check whether the given design size matches the one defined in the tfm file
565 if abs(self.tfmfile.designsize - d) > 2:
566 raise DVIError("design sizes do not agree: %d vs. %d" % (self.tfmfile.designsize, d))
567 #if q < 0 or q > 134217728:
568 # raise DVIError("font '%s' not loaded: bad scale" % self.name)
569 if d < 0 or d > 134217728:
570 raise DVIError("font '%s' not loaded: bad design size" % self.name)
572 self.scale = 1.0*q/d
574 # for bookkeeping of used characters
575 self.usedchars = [0] * 256
577 def __str__(self):
578 return "font %s designed at %g TeX pts used at %g TeX pts" % (self.name,
579 16.0*self.d/16777216L,
580 16.0*self.q/16777216L)
583 __repr__ = __str__
585 def getsize_pt(self):
586 """ return size of font in (PS) points """
587 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
588 # to the corresponding float. Furthermore, we have to convert from TeX
589 # points to points, hence the factor 72/72.27.
590 return 16L*self.q/16777216L*72/72.27
592 def _convert_tfm_to_dvi(self, length):
593 # doing the integer math with long integers will lead to different roundings
594 # return 16*length*int(round(self.q*self.tfmconv))/16777216
596 # Knuth instead suggests the following algorithm based on 4 byte integer logic only
597 # z = int(round(self.q*self.tfmconv))
598 # b0, b1, b2, b3 = [ord(c) for c in struct.pack(">L", length)]
599 # assert b0 == 0 or b0 == 255
600 # shift = 4
601 # while z >= 8388608:
602 # z >>= 1
603 # shift -= 1
604 # assert shift >= 0
605 # result = ( ( ( ( ( b3 * z ) >> 8 ) + ( b2 * z ) ) >> 8 ) + ( b1 * z ) ) >> shift
606 # if b0 == 255:
607 # result = result - (z << (8-shift))
609 # however, we can simplify this using a single long integer multiplication,
610 # but take into account the transformation of z
611 z = int(round(self.q*self.tfmconv))
612 assert -16777216 <= length < 16777216 # -(1 << 24) <= length < (1 << 24)
613 assert z < 134217728 # 1 << 27
614 shift = 20 # 1 << 20
615 while z >= 8388608: # 1 << 23
616 z >>= 1
617 shift -= 1
618 # length*z is a long integer, but the result will be a regular integer
619 return int(length*long(z) >> shift)
621 # we do not need that ...
622 def _convert_tfm_to_pt(self, length):
623 return (16*long(round(length*self.q*self.tfmconv))/16777216) * self.pyxconv
625 # routines returning lengths as integers in dvi units
627 def getwidth_dvi(self, charcode):
628 return self._convert_tfm_to_dvi(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
630 def getheight_dvi(self, charcode):
631 return self._convert_tfm_to_dvi(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
633 def getdepth_dvi(self, charcode):
634 return self._convert_tfm_to_dvi(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
636 def getitalic_dvi(self, charcode):
637 return self._convert_tfm_to_dvi(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
639 # routines returning lengths as floats in PostScript points
641 def getwidth_pt(self, charcode):
642 return self._convert_tfm_to_pt(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
644 # def getheight_pt(self, charcode):
645 # return self._convert_tfm_to_pt(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
647 # def getdepth_pt(self, charcode):
648 # return self._convert_tfm_to_pt(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
650 # def getitalic_pt(self, charcode):
651 # return self._convert_tfm_to_pt(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
653 def markcharused(self, charcode):
654 self.usedchars[charcode] = 1
656 def mergeusedchars(self, otherfont):
657 for i in range(len(self.usedchars)):
658 self.usedchars[i] = self.usedchars[i] or otherfont.usedchars[i]
660 def clearusedchars(self):
661 self.usedchars = [0] * 256
664 class type1font(font):
665 def __init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug=0):
666 font.__init__(self, name, c, q, d, tfmconv, pyxconv, debug)
667 self.fontmapping = fontmap.get(name)
668 if self.fontmapping is None:
669 raise RuntimeError("no information for font '%s' found in font mapping file, aborting" % name)
671 def getbasepsname(self):
672 return self.fontmapping.basepsname
674 def getpsname(self):
675 if self.fontmapping.reencodefont:
676 return "%s-%s" % (self.fontmapping.basepsname, self.fontmapping.reencodefont)
677 else:
678 return self.fontmapping.basepsname
680 def getfontfile(self):
681 return self.fontmapping.fontfile
683 def getencoding(self):
684 return self.fontmapping.reencodefont
686 def getencodingfile(self):
687 return self.fontmapping.encodingfile
690 class virtualfont(font):
691 def __init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug=0):
692 font.__init__(self, name, c, q, d, tfmconv, pyxconv, debug)
693 fontpath = pykpathsea.find_file(name, pykpathsea.kpse_vf_format)
694 if fontpath is None or not len(fontpath):
695 raise RuntimeError
696 self.vffile = vffile(fontpath, self.scale, tfmconv, pyxconv, fontmap, debug > 1)
698 def getfonts(self):
699 """ return fonts used in virtual font itself """
700 return self.vffile.getfonts()
702 def getchar(self, cc):
703 """ return dvi chunk corresponding to char code cc """
704 return self.vffile.getchar(cc)
707 ##############################################################################
708 # DVI file handling
709 ##############################################################################
711 _DVI_CHARMIN = 0 # typeset a character and move right (range min)
712 _DVI_CHARMAX = 127 # typeset a character and move right (range max)
713 _DVI_SET1234 = 128 # typeset a character and move right
714 _DVI_SETRULE = 132 # typeset a rule and move right
715 _DVI_PUT1234 = 133 # typeset a character
716 _DVI_PUTRULE = 137 # typeset a rule
717 _DVI_NOP = 138 # no operation
718 _DVI_BOP = 139 # beginning of page
719 _DVI_EOP = 140 # ending of page
720 _DVI_PUSH = 141 # save the current positions (h, v, w, x, y, z)
721 _DVI_POP = 142 # restore positions (h, v, w, x, y, z)
722 _DVI_RIGHT1234 = 143 # move right
723 _DVI_W0 = 147 # move right by w
724 _DVI_W1234 = 148 # move right and set w
725 _DVI_X0 = 152 # move right by x
726 _DVI_X1234 = 153 # move right and set x
727 _DVI_DOWN1234 = 157 # move down
728 _DVI_Y0 = 161 # move down by y
729 _DVI_Y1234 = 162 # move down and set y
730 _DVI_Z0 = 166 # move down by z
731 _DVI_Z1234 = 167 # move down and set z
732 _DVI_FNTNUMMIN = 171 # set current font (range min)
733 _DVI_FNTNUMMAX = 234 # set current font (range max)
734 _DVI_FNT1234 = 235 # set current font
735 _DVI_SPECIAL1234 = 239 # special (dvi extention)
736 _DVI_FNTDEF1234 = 243 # define the meaning of a font number
737 _DVI_PRE = 247 # preamble
738 _DVI_POST = 248 # postamble beginning
739 _DVI_POSTPOST = 249 # postamble ending
741 _DVI_VERSION = 2 # dvi version
743 # position variable indices
744 _POS_H = 0
745 _POS_V = 1
746 _POS_W = 2
747 _POS_X = 3
748 _POS_Y = 4
749 _POS_Z = 5
751 # reader states
752 _READ_PRE = 1
753 _READ_NOPAGE = 2
754 _READ_PAGE = 3
755 _READ_POST = 4 # XXX not used
756 _READ_POSTPOST = 5 # XXX not used
757 _READ_DONE = 6
760 class DVIError(exceptions.Exception): pass
762 # save and restore colors
764 class _savecolor(canvas.canvasitem):
765 def outputPS(self, file):
766 file.write("currentcolor currentcolorspace\n")
768 def outputPDF(self, file, writer, context):
769 file.write("q\n")
772 class _restorecolor(canvas.canvasitem):
773 def outputPS(self, file):
774 file.write("setcolorspace setcolor\n")
776 def outputPDF(self, file, writer, context):
777 file.write("Q\n")
779 class _savetrafo(canvas.canvasitem):
780 def outputPS(self, file):
781 file.write("matrix currentmatrix\n")
783 def outputPDF(self, file, writer, context):
784 file.write("q\n")
787 class _restoretrafo(canvas.canvasitem):
788 def outputPS(self, file):
789 file.write("setmatrix\n")
791 def outputPDF(self, file, writer, context):
792 file.write("Q\n")
795 class dvifile:
797 def __init__(self, filename, fontmap, debug=0, debugfile=sys.stdout):
798 """ opens the dvi file and reads the preamble """
799 self.filename = filename
800 self.fontmap = fontmap
801 self.debug = debug
802 self.debugfile = debugfile
803 self.debugstack = []
805 self.fonts = {}
806 self.activefont = None
808 # stack of fonts and fontscale currently used (used for VFs)
809 self.fontstack = []
810 self.stack = []
812 # pointer to currently active page
813 self.actpage = None
815 # currently active output: show instance currently used and
816 # the corresponding type 1 font
817 self.activeshow = None
818 self.activetype1font = None
820 # stack for self.file, self.fonts and self.stack, needed for VF inclusion
821 self.statestack = []
823 self.file = binfile(self.filename, "rb")
825 # currently read byte in file (for debugging output)
826 self.filepos = None
828 self._read_pre()
830 # helper routines
832 def flushout(self):
833 """ flush currently active string """
834 if self.activeshow is not None:
835 if self.debug:
836 self.debugfile.write("[%s]\n" % "".join([chr(char) for char in self.activeshow.chars]))
837 self.actpage.insert(self.activeshow)
838 self.activeshow = None
840 def begintext(self):
841 """ activate the font if is not yet active, closing a currently active
842 text object and flushing the output"""
843 if isinstance(self.activefont, type1font):
844 self.endtext()
845 if self.activetype1font != self.activefont and self.activefont:
846 self.actpage.insert(_begintextobject())
847 self.actpage.insert(selectfont(self.activefont))
848 self.activetype1font = self.activefont
850 def endtext(self):
851 self.flushout()
852 if self.activetype1font:
853 self.actpage.insert(_endtextobject())
854 self.activetype1font = None
856 def putrule(self, height, width, advancepos=1):
857 self.endtext()
858 x1 = self.pos[_POS_H] * self.pyxconv
859 y1 = -self.pos[_POS_V] * self.pyxconv
860 w = width * self.pyxconv
861 h = height * self.pyxconv
863 if height > 0 and width > 0:
864 if self.debug:
865 self.debugfile.write("%d: %srule height %d, width %d (???x??? pixels)\n" %
866 (self.filepos, advancepos and "set" or "put", height, width))
867 self.actpage.fill(path.rect_pt(x1, y1, w, h))
868 else:
869 if self.debug:
870 self.debugfile.write("%d: %srule height %d, width %d (invisible)\n" %
871 (self.filepos, advancepos and "set" or "put", height, width))
873 if advancepos:
874 if self.debug:
875 self.debugfile.write(" h:=%d+%d=%d, hh:=???\n" %
876 (self.pos[_POS_H], width, self.pos[_POS_H]+width))
877 self.pos[_POS_H] += width
879 def putchar(self, char, advancepos=1, id1234=0):
880 dx = advancepos and self.activefont.getwidth_dvi(char) or 0
882 if self.debug:
883 self.debugfile.write("%d: %s%s%d h:=%d+%d=%d, hh:=???\n" %
884 (self.filepos,
885 advancepos and "set" or "put",
886 id1234 and "%i " % id1234 or "char",
887 char,
888 self.pos[_POS_H], dx, self.pos[_POS_H]+dx))
890 if isinstance(self.activefont, type1font):
891 if self.activeshow is None:
892 # XXX: begintext would lead to massive number of selectfonts being issued
893 # OTOH is it save to remove begintext here? I think so ...
894 # self.begintext()
895 self.activeshow = _show(self.pos[_POS_H] * self.pyxconv, -self.pos[_POS_V] * self.pyxconv)
896 width = self.activefont.getwidth_dvi(char) * self.pyxconv
897 height = self.activefont.getheight_dvi(char) * self.pyxconv
898 depth = self.activefont.getdepth_dvi(char) * self.pyxconv
899 self.activeshow.addchar(width, height, depth, char)
901 self.activefont.markcharused(char)
902 self.pos[_POS_H] += dx
903 else:
904 # virtual font handling
905 afterpos = list(self.pos)
906 afterpos[_POS_H] += dx
907 self._push_dvistring(self.activefont.getchar(char), self.activefont.getfonts(), afterpos,
908 self.activefont.getsize_pt())
911 if not advancepos:
912 # XXX: correct !?
913 self.flushout()
915 def usefont(self, fontnum, id1234=0):
916 self.activefont = self.fonts[fontnum]
917 self.begintext()
918 if self.debug:
919 self.debugfile.write("%d: fnt%s%i current font is %s\n" %
920 (self.filepos,
921 id1234 and "%i " % id1234 or "num",
922 fontnum,
923 self.fonts[fontnum].name))
926 def definefont(self, cmdnr, num, c, q, d, fontname):
927 # cmdnr: type of fontdef command (only used for debugging output)
928 # c: checksum
929 # q: scaling factor (fix_word)
930 # Note that q is actually s in large parts of the documentation.
931 # d: design size (fix_word)
933 try:
934 font = virtualfont(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
935 except (TypeError, RuntimeError):
936 font = type1font(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
938 self.fonts[num] = font
940 if self.debug:
941 self.debugfile.write("%d: fntdef%d %i: %s\n" % (self.filepos, cmdnr, num, fontname))
943 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
944 # m = 1.0*q/d
945 # scalestring = scale!=1000 and " scaled %d" % scale or ""
946 # print ("Font %i: %s%s---loaded at size %d DVI units" %
947 # (num, fontname, scalestring, q))
948 # if scale!=1000:
949 # print " (this font is magnified %d%%)" % round(scale/10)
951 def special(self, s):
952 x = self.pos[_POS_H] * self.pyxconv
953 y = -self.pos[_POS_V] * self.pyxconv
954 if self.debug:
955 self.debugfile.write("%d: xxx '%s'\n" % (self.filepos, s))
956 if not s.startswith("PyX:"):
957 warnings.warn("ignoring special '%s'" % s)
958 return
960 # it is in general not safe to continue using the currently active font because
961 # the specials may involve some gsave/grestore operations
962 self.endtext()
964 command, args = s[4:].split()[0], s[4:].split()[1:]
965 if command=="color_begin":
966 if args[0]=="cmyk":
967 c = color.cmyk(float(args[1]), float(args[2]), float(args[3]), float(args[4]))
968 elif args[0]=="gray":
969 c = color.gray(float(args[1]))
970 elif args[0]=="hsb":
971 c = color.hsb(float(args[1]), float(args[2]), float(args[3]))
972 elif args[0]=="rgb":
973 c = color.rgb(float(args[1]), float(args[2]), float(args[3]))
974 elif args[0]=="RGB":
975 c = color.rgb(int(args[1])/255.0, int(args[2])/255.0, int(args[3])/255.0)
976 elif args[0]=="texnamed":
977 try:
978 c = getattr(color.cmyk, args[1])
979 except AttributeError:
980 raise RuntimeError("unknown TeX color '%s', aborting" % args[1])
981 else:
982 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args[0])
983 self.actpage.insert(_savecolor())
984 self.actpage.insert(c)
985 elif command=="color_end":
986 self.actpage.insert(_restorecolor())
987 elif command=="rotate_begin":
988 self.actpage.insert(_savetrafo())
989 self.actpage.insert(trafo.rotate_pt(float(args[0]), x, y))
990 elif command=="rotate_end":
991 self.actpage.insert(_restoretrafo())
992 elif command=="scale_begin":
993 self.actpage.insert(_savetrafo())
994 self.actpage.insert(trafo.scale_pt(float(args[0]), float(args[1]), x, y))
995 elif command=="scale_end":
996 self.actpage.insert(_restoretrafo())
997 elif command=="epsinclude":
998 # parse arguments
999 argdict = {}
1000 for arg in args:
1001 name, value = arg.split("=")
1002 argdict[name] = value
1004 # construct kwargs for epsfile constructor
1005 epskwargs = {}
1006 epskwargs["filename"] = argdict["file"]
1007 epskwargs["bbox"] = bbox.bbox_pt(float(argdict["llx"]), float(argdict["lly"]),
1008 float(argdict["urx"]), float(argdict["ury"]))
1009 if argdict.has_key("width"):
1010 epskwargs["width"] = float(argdict["width"]) * unit.t_pt
1011 if argdict.has_key("height"):
1012 epskwargs["height"] = float(argdict["height"]) * unit.t_pt
1013 if argdict.has_key("clip"):
1014 epskwargs["clip"] = int(argdict["clip"])
1015 self.actpage.insert(epsfile.epsfile(x * unit.t_pt, y * unit.t_pt, **epskwargs))
1016 elif command=="marker":
1017 if len(args) != 1:
1018 raise RuntimeError("marker contains spaces")
1019 for c in args[0]:
1020 if c not in string.digits + string.letters + "@":
1021 raise RuntimeError("marker contains invalid characters")
1022 if self.actpage.markers.has_key(args[0]):
1023 raise RuntimeError("marker name occurred several times")
1024 self.actpage.markers[args[0]] = x * unit.t_pt, y * unit.t_pt
1025 else:
1026 raise RuntimeError("unknown PyX special '%s', aborting" % command)
1027 self.begintext()
1029 # routines for pushing and popping different dvi chunks on the reader
1031 def _push_dvistring(self, dvi, fonts, afterpos, fontsize):
1032 """ push dvi string with defined fonts on top of reader
1033 stack. Every positions gets scaled relatively by the factor
1034 scale. After the interpreting of the dvi chunk has been finished,
1035 continue with self.pos=afterpos. The designsize of the virtual
1036 font is passed as a fix_word
1040 #if self.debug:
1041 # self.debugfile.write("executing new dvi chunk\n")
1042 self.debugstack.append(self.debug)
1043 self.debug = 0
1045 self.statestack.append((self.file, self.fonts, self.activefont, afterpos, self.stack, self.pyxconv, self.tfmconv))
1047 # units in vf files are relative to the size of the font and given as fix_words
1048 # which can be converted to floats by diving by 2**20
1049 oldpyxconv = self.pyxconv
1050 self.pyxconv = fontsize/2**20
1051 rescale = self.pyxconv/oldpyxconv
1053 self.file = stringbinfile(dvi)
1054 self.fonts = fonts
1055 self.stack = []
1056 self.filepos = 0
1058 # rescale self.pos in order to be consistent with the new scaling
1059 self.pos = map(lambda x, rescale=rescale:1.0*x/rescale, self.pos)
1061 # since tfmconv converts from tfm units to dvi units, rescale it as well
1062 self.tfmconv /= rescale
1064 self.usefont(0)
1066 def _pop_dvistring(self):
1067 self.flushout()
1068 #if self.debug:
1069 # self.debugfile.write("finished executing dvi chunk\n")
1070 self.debug = self.debugstack.pop()
1072 self.file.close()
1073 self.file, self.fonts, self.activefont, self.pos, self.stack, self.pyxconv, self.tfmconv = self.statestack.pop()
1075 # routines corresponding to the different reader states of the dvi maschine
1077 def _read_pre(self):
1078 afile = self.file
1079 while 1:
1080 self.filepos = afile.tell()
1081 cmd = afile.readuchar()
1082 if cmd == _DVI_NOP:
1083 pass
1084 elif cmd == _DVI_PRE:
1085 if afile.readuchar() != _DVI_VERSION: raise DVIError
1086 num = afile.readuint32()
1087 den = afile.readuint32()
1088 self.mag = afile.readuint32()
1090 # For the interpretation of the lengths in dvi and tfm files,
1091 # three conversion factors are relevant:
1092 # - self.tfmconv: tfm units -> dvi units
1093 # - self.pyxconv: dvi units -> (PostScript) points
1094 # - self.conv: dvi units -> pixels
1095 self.tfmconv = (25400000.0/num)*(den/473628672.0)/16.0
1097 # calculate conv as described in the DVIType docu using
1098 # a given resolution in dpi
1099 self.resolution = 300.0
1100 self.conv = (num/254000.0)*(self.resolution/den)
1102 # self.pyxconv is the conversion factor from the dvi units
1103 # to (PostScript) points. It consists of
1104 # - self.mag/1000.0: magstep scaling
1105 # - self.conv: conversion from dvi units to pixels
1106 # - 1/self.resolution: conversion from pixels to inch
1107 # - 72 : conversion from inch to points
1108 self.pyxconv = self.mag/1000.0*self.conv/self.resolution*72
1110 comment = afile.read(afile.readuchar())
1111 return
1112 else:
1113 raise DVIError
1115 def readpage(self, pageid=None):
1116 """ reads a page from the dvi file
1118 This routine reads a page from the dvi file which is
1119 returned as a canvas. When there is no page left in the
1120 dvifile, None is returned and the file is closed properly."""
1122 while 1:
1123 self.filepos = self.file.tell()
1124 cmd = self.file.readuchar()
1125 if cmd == _DVI_NOP:
1126 pass
1127 elif cmd == _DVI_BOP:
1128 # self.endtext()
1129 ispageid = [self.file.readuint32() for i in range(10)]
1130 if pageid is not None and ispageid != pageid:
1131 raise DVIError("invalid pageid")
1132 if self.debug:
1133 self.debugfile.write("%d: beginning of page %i\n" % (self.filepos, ispageid[0]))
1134 self.file.readuint32()
1135 break
1136 elif cmd == _DVI_POST:
1137 self.file.close()
1138 return None # nothing left
1139 else:
1140 raise DVIError
1142 actpage = canvas.canvas()
1143 self.actpage = actpage # XXX should be removed ...
1144 self.actpage.markers = {}
1145 self.pos = [0, 0, 0, 0, 0, 0]
1146 self.activetype1font = None
1148 # Since we do not know which dvi pages the actual PS file contains later on,
1149 # we have to keep track of used char informations separately for each dvi page.
1150 # In order to do so, the already defined fonts have to be copied and their
1151 # used char informations have to be reset
1152 for nr in self.fonts.keys():
1153 self.fonts[nr] = copy.copy(self.fonts[nr])
1154 self.fonts[nr].clearusedchars()
1156 while 1:
1157 afile = self.file
1158 self.filepos = afile.tell()
1159 try:
1160 cmd = afile.readuchar()
1161 except struct.error:
1162 # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
1163 # so we have to continue with the rest of the dvi file
1164 self._pop_dvistring()
1165 continue
1166 if cmd == _DVI_NOP:
1167 pass
1168 if cmd >= _DVI_CHARMIN and cmd <= _DVI_CHARMAX:
1169 self.putchar(cmd)
1170 elif cmd >= _DVI_SET1234 and cmd < _DVI_SET1234 + 4:
1171 self.putchar(afile.readint(cmd - _DVI_SET1234 + 1), id1234=cmd-_DVI_SET1234+1)
1172 elif cmd == _DVI_SETRULE:
1173 self.putrule(afile.readint32(), afile.readint32())
1174 elif cmd >= _DVI_PUT1234 and cmd < _DVI_PUT1234 + 4:
1175 self.putchar(afile.readint(cmd - _DVI_PUT1234 + 1), advancepos=0, id1234=cmd-_DVI_SET1234+1)
1176 elif cmd == _DVI_PUTRULE:
1177 self.putrule(afile.readint32(), afile.readint32(), 0)
1178 elif cmd == _DVI_EOP:
1179 self.endtext()
1180 if self.debug:
1181 self.debugfile.write("%d: eop\n \n" % self.filepos)
1182 return actpage
1183 elif cmd == _DVI_PUSH:
1184 self.stack.append(list(self.pos))
1185 if self.debug:
1186 self.debugfile.write("%s: push\n"
1187 "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1188 ((self.filepos, len(self.stack)-1) + tuple(self.pos)))
1189 elif cmd == _DVI_POP:
1190 self.flushout()
1191 self.pos = self.stack.pop()
1192 if self.debug:
1193 self.debugfile.write("%s: pop\n"
1194 "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1195 ((self.filepos, len(self.stack)) + tuple(self.pos)))
1196 elif cmd >= _DVI_RIGHT1234 and cmd < _DVI_RIGHT1234 + 4:
1197 self.flushout()
1198 dh = afile.readint(cmd - _DVI_RIGHT1234 + 1, 1)
1199 if self.debug:
1200 self.debugfile.write("%d: right%d %d h:=%d%+d=%d, hh:=???\n" %
1201 (self.filepos,
1202 cmd - _DVI_RIGHT1234 + 1,
1204 self.pos[_POS_H],
1206 self.pos[_POS_H]+dh))
1207 self.pos[_POS_H] += dh
1208 elif cmd == _DVI_W0:
1209 self.flushout()
1210 if self.debug:
1211 self.debugfile.write("%d: w0 %d h:=%d%+d=%d, hh:=???\n" %
1212 (self.filepos,
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_W1234 and cmd < _DVI_W1234 + 4:
1219 self.flushout()
1220 self.pos[_POS_W] = afile.readint(cmd - _DVI_W1234 + 1, 1)
1221 if self.debug:
1222 self.debugfile.write("%d: w%d %d h:=%d%+d=%d, hh:=???\n" %
1223 (self.filepos,
1224 cmd - _DVI_W1234 + 1,
1225 self.pos[_POS_W],
1226 self.pos[_POS_H],
1227 self.pos[_POS_W],
1228 self.pos[_POS_H]+self.pos[_POS_W]))
1229 self.pos[_POS_H] += self.pos[_POS_W]
1230 elif cmd == _DVI_X0:
1231 self.flushout()
1232 if self.debug:
1233 self.debugfile.write("%d: x0 %d h:=%d%+d=%d, hh:=???\n" %
1234 (self.filepos,
1235 self.pos[_POS_X],
1236 self.pos[_POS_H],
1237 self.pos[_POS_X],
1238 self.pos[_POS_H]+self.pos[_POS_X]))
1239 self.pos[_POS_H] += self.pos[_POS_X]
1240 elif cmd >= _DVI_X1234 and cmd < _DVI_X1234 + 4:
1241 self.flushout()
1242 self.pos[_POS_X] = afile.readint(cmd - _DVI_X1234 + 1, 1)
1243 if self.debug:
1244 self.debugfile.write("%d: x%d %d h:=%d%+d=%d, hh:=???\n" %
1245 (self.filepos,
1246 cmd - _DVI_X1234 + 1,
1247 self.pos[_POS_X],
1248 self.pos[_POS_H],
1249 self.pos[_POS_X],
1250 self.pos[_POS_H]+self.pos[_POS_X]))
1251 self.pos[_POS_H] += self.pos[_POS_X]
1252 elif cmd >= _DVI_DOWN1234 and cmd < _DVI_DOWN1234 + 4:
1253 self.flushout()
1254 dv = afile.readint(cmd - _DVI_DOWN1234 + 1, 1)
1255 if self.debug:
1256 self.debugfile.write("%d: down%d %d v:=%d%+d=%d, vv:=???\n" %
1257 (self.filepos,
1258 cmd - _DVI_DOWN1234 + 1,
1260 self.pos[_POS_V],
1262 self.pos[_POS_V]+dv))
1263 self.pos[_POS_V] += dv
1264 elif cmd == _DVI_Y0:
1265 self.flushout()
1266 if self.debug:
1267 self.debugfile.write("%d: y0 %d v:=%d%+d=%d, vv:=???\n" %
1268 (self.filepos,
1269 self.pos[_POS_Y],
1270 self.pos[_POS_V],
1271 self.pos[_POS_Y],
1272 self.pos[_POS_V]+self.pos[_POS_Y]))
1273 self.pos[_POS_V] += self.pos[_POS_Y]
1274 elif cmd >= _DVI_Y1234 and cmd < _DVI_Y1234 + 4:
1275 self.flushout()
1276 self.pos[_POS_Y] = afile.readint(cmd - _DVI_Y1234 + 1, 1)
1277 if self.debug:
1278 self.debugfile.write("%d: y%d %d v:=%d%+d=%d, vv:=???\n" %
1279 (self.filepos,
1280 cmd - _DVI_Y1234 + 1,
1281 self.pos[_POS_Y],
1282 self.pos[_POS_V],
1283 self.pos[_POS_Y],
1284 self.pos[_POS_V]+self.pos[_POS_Y]))
1285 self.pos[_POS_V] += self.pos[_POS_Y]
1286 elif cmd == _DVI_Z0:
1287 self.flushout()
1288 if self.debug:
1289 self.debugfile.write("%d: z0 %d v:=%d%+d=%d, vv:=???\n" %
1290 (self.filepos,
1291 self.pos[_POS_Z],
1292 self.pos[_POS_V],
1293 self.pos[_POS_Z],
1294 self.pos[_POS_V]+self.pos[_POS_Z]))
1295 self.pos[_POS_V] += self.pos[_POS_Z]
1296 elif cmd >= _DVI_Z1234 and cmd < _DVI_Z1234 + 4:
1297 self.flushout()
1298 self.pos[_POS_Z] = afile.readint(cmd - _DVI_Z1234 + 1, 1)
1299 if self.debug:
1300 self.debugfile.write("%d: z%d %d v:=%d%+d=%d, vv:=???\n" %
1301 (self.filepos,
1302 cmd - _DVI_Z1234 + 1,
1303 self.pos[_POS_Z],
1304 self.pos[_POS_V],
1305 self.pos[_POS_Z],
1306 self.pos[_POS_V]+self.pos[_POS_Z]))
1307 self.pos[_POS_V] += self.pos[_POS_Z]
1308 elif cmd >= _DVI_FNTNUMMIN and cmd <= _DVI_FNTNUMMAX:
1309 self.usefont(cmd - _DVI_FNTNUMMIN, 0)
1310 elif cmd >= _DVI_FNT1234 and cmd < _DVI_FNT1234 + 4:
1311 # note that according to the DVI docs, for four byte font numbers,
1312 # the font number is signed. Don't ask why!
1313 fntnum = afile.readint(cmd - _DVI_FNT1234 + 1, cmd == _DVI_FNT1234 + 3)
1314 self.usefont(fntnum, id1234=cmd-_DVI_FNT1234+1)
1315 elif cmd >= _DVI_SPECIAL1234 and cmd < _DVI_SPECIAL1234 + 4:
1316 self.special(afile.read(afile.readint(cmd - _DVI_SPECIAL1234 + 1)))
1317 elif cmd >= _DVI_FNTDEF1234 and cmd < _DVI_FNTDEF1234 + 4:
1318 if cmd == _DVI_FNTDEF1234:
1319 num = afile.readuchar()
1320 elif cmd == _DVI_FNTDEF1234+1:
1321 num = afile.readuint16()
1322 elif cmd == _DVI_FNTDEF1234+2:
1323 num = afile.readuint24()
1324 elif cmd == _DVI_FNTDEF1234+3:
1325 # Cool, here we have according to docu a signed int. Why?
1326 num = afile.readint32()
1327 self.definefont(cmd-_DVI_FNTDEF1234+1,
1328 num,
1329 afile.readint32(),
1330 afile.readint32(),
1331 afile.readint32(),
1332 afile.read(afile.readuchar()+afile.readuchar()))
1333 else:
1334 raise DVIError
1337 ##############################################################################
1338 # VF file handling
1339 ##############################################################################
1341 _VF_LONG_CHAR = 242 # character packet (long version)
1342 _VF_FNTDEF1234 = _DVI_FNTDEF1234 # font definition
1343 _VF_PRE = _DVI_PRE # preamble
1344 _VF_POST = _DVI_POST # postamble
1346 _VF_ID = 202 # VF id byte
1348 class VFError(exceptions.Exception): pass
1350 class vffile:
1351 def __init__(self, filename, scale, tfmconv, pyxconv, fontmap, debug=0):
1352 self.filename = filename
1353 self.scale = scale
1354 self.tfmconv = tfmconv
1355 self.pyxconv = pyxconv
1356 self.fontmap = fontmap
1357 self.debug = debug
1358 self.fonts = {} # used fonts
1359 self.widths = {} # widths of defined chars
1360 self.chardefs = {} # dvi chunks for defined chars
1362 afile = binfile(self.filename, "rb")
1364 cmd = afile.readuchar()
1365 if cmd == _VF_PRE:
1366 if afile.readuchar() != _VF_ID: raise VFError
1367 comment = afile.read(afile.readuchar())
1368 self.cs = afile.readuint32()
1369 self.ds = afile.readuint32()
1370 else:
1371 raise VFError
1373 while 1:
1374 cmd = afile.readuchar()
1375 if cmd >= _VF_FNTDEF1234 and cmd < _VF_FNTDEF1234 + 4:
1376 # font definition
1377 if cmd == _VF_FNTDEF1234:
1378 num = afile.readuchar()
1379 elif cmd == _VF_FNTDEF1234+1:
1380 num = afile.readuint16()
1381 elif cmd == _VF_FNTDEF1234+2:
1382 num = afile.readuint24()
1383 elif cmd == _VF_FNTDEF1234+3:
1384 num = afile.readint32()
1385 c = afile.readint32()
1386 s = afile.readint32() # relative scaling used for font (fix_word)
1387 d = afile.readint32() # design size of font
1388 fontname = afile.read(afile.readuchar()+afile.readuchar())
1390 # rescaled size of font: s is relative to the scaling
1391 # of the virtual font itself. Note that realscale has
1392 # to be a fix_word (like s)
1393 # XXX: check rounding
1394 reals = int(round(self.scale * (16*self.ds/16777216L) * s))
1396 # print ("defining font %s -- VF scale: %g, VF design size: %d, relative font size: %d => real size: %d" %
1397 # (fontname, self.scale, self.ds, s, reals)
1400 # XXX allow for virtual fonts here too
1401 self.fonts[num] = type1font(fontname, c, reals, d, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
1402 elif cmd == _VF_LONG_CHAR:
1403 # character packet (long form)
1404 pl = afile.readuint32() # packet length
1405 cc = afile.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1406 tfm = afile.readuint24() # character width
1407 dvi = afile.read(pl) # dvi code of character
1408 self.widths[cc] = tfm
1409 self.chardefs[cc] = dvi
1410 elif cmd < _VF_LONG_CHAR:
1411 # character packet (short form)
1412 cc = afile.readuchar() # char code
1413 tfm = afile.readuint24() # character width
1414 dvi = afile.read(cmd)
1415 self.widths[cc] = tfm
1416 self.chardefs[cc] = dvi
1417 elif cmd == _VF_POST:
1418 break
1419 else:
1420 raise VFError
1422 afile.close()
1424 def getfonts(self):
1425 return self.fonts
1427 def getchar(self, cc):
1428 return self.chardefs[cc]