PDF font descriptors work (except for encoding and partial font downloading)
[PyX.git] / pyx / dvifile.py
blob16acc97ab51cfb0e3eb661377cc944373b0ada89
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, pswriter, pdfwriter
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 registerPS(self, registry):
393 # note that we don't register PSfont as it is just a helper resource
394 # which registers the needed components
395 pswriter.PSfont(self.font, registry)
397 def registerPDF(self, registry):
398 registry.add(pdfwriter.PDFfont(self.font, registry))
400 def outputPS(self, file):
401 file.write("/%s %f selectfont\n" % (self.font.getpsname(), self.size))
403 def outputPDF(self, file, writer, context):
404 file.write("/%s %f Tf\n" % (self.font.getpsname(), self.size))
407 class _show(canvas.canvasitem):
409 def __init__(self, x, y):
410 self.x = x
411 self.y = y
412 self.width = 0
413 self.height = 0
414 self.depth = 0
415 self.chars = []
417 def addchar(self, width, height, depth, char):
418 self.width += width
419 if height > self.height:
420 self.height = height
421 if depth > self.depth:
422 self.depth = depth
423 self.chars.append(char)
425 def bbox(self):
426 return bbox.bbox_pt(self.x, self.y-self.depth, self.x+self.width, self.y+self.height)
428 def outputPS(self, file):
429 outstring = ""
430 for char in self.chars:
431 if char > 32 and char < 127 and chr(char) not in "()[]<>\\":
432 ascii = "%s" % chr(char)
433 else:
434 ascii = "\\%03o" % char
435 outstring += ascii
436 file.write("%g %g moveto (%s) show\n" % (self.x, self.y, outstring))
438 def outputPDF(self, file, writer, context):
439 outstring = ""
440 for char in self.chars:
441 if char > 32 and char < 127 and chr(char) not in "()[]<>\\":
442 ascii = "%s" % chr(char)
443 else:
444 ascii = "\\%03o" % char
445 outstring += ascii
446 # file.write("%f %f Td (%s) Tj\n" % (self.x, self.y, outstring))
447 file.write("1 0 0 1 %f %f Tm (%s) Tj\n" % (self.x, self.y, outstring))
450 class fontmapping:
452 tokenpattern = re.compile(r'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
454 def __init__(self, s):
455 """ construct font mapping from line s of font mapping file """
456 self.texname = self.basepsname = self.fontfile = None
458 # standard encoding
459 self.encodingfile = None
461 # supported postscript fragments occuring in psfonts.map
462 self.reencodefont = self.extendfont = self.slantfont = None
464 tokens = []
465 while len(s):
466 match = self.tokenpattern.match(s)
467 if match:
468 if match.groups()[0] is not None:
469 tokens.append('"%s"' % match.groups()[0])
470 else:
471 tokens.append(match.groups()[2])
472 s = s[match.end():]
473 else:
474 raise RuntimeError("wrong syntax")
476 for token in tokens:
477 if token.startswith("<"):
478 if token.startswith("<<"):
479 # XXX: support non-partial download here
480 self.fontfile = token[2:]
481 elif token.startswith("<["):
482 self.encodingfile = token[2:]
483 elif token.endswith(".pfa") or token.endswith(".pfb"):
484 self.fontfile = token[1:]
485 elif token.endswith(".enc"):
486 self.encodingfile = token[1:]
487 else:
488 raise RuntimeError("wrong syntax")
489 elif token.startswith('"'):
490 pscode = token[1:-1].split()
491 # parse standard postscript code fragments
492 while pscode:
493 try:
494 arg, cmd = pscode[:2]
495 except:
496 raise RuntimeError("Unsupported Postscript fragment '%s'" % pscode)
497 pscode = pscode[2:]
498 if cmd == "ReEncodeFont":
499 self.reencodefont = arg
500 elif cmd == "ExtendFont":
501 self.extendfont = arg
502 elif cmd == "SlantFont":
503 self.slantfont = arg
504 else:
505 raise RuntimeError("Unsupported Postscript fragment '%s %s'" % (arg, cmd))
506 else:
507 if self.texname is None:
508 self.texname = token
509 else:
510 self.basepsname = token
511 if self.basepsname is None:
512 self.basepsname = self.texname
514 def __str__(self):
515 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
516 (self.texname, self.basepsname, self.fontfile, repr(self.encodingfile)))
518 # generate fontmap
520 def readfontmap(filenames):
521 """ read font map from filename (without path) """
522 fontmap = {}
523 for filename in filenames:
524 mappath = pykpathsea.find_file(filename, pykpathsea.kpse_fontmap_format)
525 # try also the oft-used registration as dvips config file
526 if not mappath:
527 mappath = pykpathsea.find_file(filename, pykpathsea.kpse_dvips_config_format)
528 if not mappath:
529 raise RuntimeError("cannot find font mapping file '%s'" % filename)
530 mapfile = open(mappath, "r")
531 lineno = 0
532 for line in mapfile.readlines():
533 lineno += 1
534 line = line.rstrip()
535 if not (line=="" or line[0] in (" ", "%", "*", ";" , "#")):
536 try:
537 fm = fontmapping(line)
538 except RuntimeError, e:
539 warnings.warn("Ignoring line %i in mapping file '%s': %s" % (lineno, filename, e))
540 else:
541 fontmap[fm.texname] = fm
542 mapfile.close()
543 return fontmap
546 class font:
547 def __init__(self, name, c, q, d, tfmconv, pyxconv, debug=0):
548 self.name = name
549 self.q = q # desired size of font (fix_word) in TeX points
550 self.d = d # design size of font (fix_word) in TeX points
551 self.tfmconv = tfmconv # conversion factor from tfm units to dvi units
552 self.pyxconv = pyxconv # conversion factor from dvi units to PostScript points
553 tfmpath = pykpathsea.find_file("%s.tfm" % self.name, pykpathsea.kpse_tfm_format)
554 if not tfmpath:
555 raise TFMError("cannot find %s.tfm" % self.name)
556 self.tfmfile = tfmfile(tfmpath, debug)
558 # We only check for equality of font checksums if none of them
559 # is zero. The case c == 0 happend in some VF files and
560 # according to the VFtoVP documentation, paragraph 40, a check
561 # is only performed if tfmfile.checksum > 0. Anyhow, being
562 # more generous here seems to be reasonable
563 if self.tfmfile.checksum != c and self.tfmfile.checksum != 0 and c != 0:
564 raise DVIError("check sums do not agree: %d vs. %d" %
565 (self.tfmfile.checksum, c))
567 # Check whether the given design size matches the one defined in the tfm file
568 if abs(self.tfmfile.designsize - d) > 2:
569 raise DVIError("design sizes do not agree: %d vs. %d" % (self.tfmfile.designsize, d))
570 #if q < 0 or q > 134217728:
571 # raise DVIError("font '%s' not loaded: bad scale" % self.name)
572 if d < 0 or d > 134217728:
573 raise DVIError("font '%s' not loaded: bad design size" % self.name)
575 self.scale = 1.0*q/d
577 # for bookkeeping of used characters
578 self.usedchars = [0] * 256
580 def __str__(self):
581 return "font %s designed at %g TeX pts used at %g TeX pts" % (self.name,
582 16.0*self.d/16777216L,
583 16.0*self.q/16777216L)
586 __repr__ = __str__
588 def getsize_pt(self):
589 """ return size of font in (PS) points """
590 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
591 # to the corresponding float. Furthermore, we have to convert from TeX
592 # points to points, hence the factor 72/72.27.
593 return 16L*self.q/16777216L*72/72.27
595 def _convert_tfm_to_dvi(self, length):
596 # doing the integer math with long integers will lead to different roundings
597 # return 16*length*int(round(self.q*self.tfmconv))/16777216
599 # Knuth instead suggests the following algorithm based on 4 byte integer logic only
600 # z = int(round(self.q*self.tfmconv))
601 # b0, b1, b2, b3 = [ord(c) for c in struct.pack(">L", length)]
602 # assert b0 == 0 or b0 == 255
603 # shift = 4
604 # while z >= 8388608:
605 # z >>= 1
606 # shift -= 1
607 # assert shift >= 0
608 # result = ( ( ( ( ( b3 * z ) >> 8 ) + ( b2 * z ) ) >> 8 ) + ( b1 * z ) ) >> shift
609 # if b0 == 255:
610 # result = result - (z << (8-shift))
612 # however, we can simplify this using a single long integer multiplication,
613 # but take into account the transformation of z
614 z = int(round(self.q*self.tfmconv))
615 assert -16777216 <= length < 16777216 # -(1 << 24) <= length < (1 << 24)
616 assert z < 134217728 # 1 << 27
617 shift = 20 # 1 << 20
618 while z >= 8388608: # 1 << 23
619 z >>= 1
620 shift -= 1
621 # length*z is a long integer, but the result will be a regular integer
622 return int(length*long(z) >> shift)
624 # we do not need that ...
625 def _convert_tfm_to_pt(self, length):
626 return (16*long(round(length*self.q*self.tfmconv))/16777216) * self.pyxconv
628 # routines returning lengths as integers in dvi units
630 def getwidth_dvi(self, charcode):
631 return self._convert_tfm_to_dvi(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
633 def getheight_dvi(self, charcode):
634 return self._convert_tfm_to_dvi(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
636 def getdepth_dvi(self, charcode):
637 return self._convert_tfm_to_dvi(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
639 def getitalic_dvi(self, charcode):
640 return self._convert_tfm_to_dvi(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
642 # routines returning lengths as floats in PostScript points
644 def getwidth_pt(self, charcode):
645 return self._convert_tfm_to_pt(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
647 def getheight_pt(self, charcode):
648 return self._convert_tfm_to_pt(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
650 def getdepth_pt(self, charcode):
651 return self._convert_tfm_to_pt(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
653 def getitalic_pt(self, charcode):
654 return self._convert_tfm_to_pt(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
656 def markcharused(self, charcode):
657 self.usedchars[charcode] = 1
659 def mergeusedchars(self, otherfont):
660 for i in range(len(self.usedchars)):
661 self.usedchars[i] = self.usedchars[i] or otherfont.usedchars[i]
663 def clearusedchars(self):
664 self.usedchars = [0] * 256
667 class type1font(font):
668 def __init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug=0):
669 font.__init__(self, name, c, q, d, tfmconv, pyxconv, debug)
670 self.fontmapping = fontmap.get(name)
671 if self.fontmapping is None:
672 raise RuntimeError("no information for font '%s' found in font mapping file, aborting" % name)
674 def getbasepsname(self):
675 return self.fontmapping.basepsname
677 def getpsname(self):
678 if self.fontmapping.reencodefont:
679 return "%s-%s" % (self.fontmapping.basepsname, self.fontmapping.reencodefont)
680 else:
681 return self.fontmapping.basepsname
683 def getfontfile(self):
684 if self.fontmapping.fontfile is None:
685 return None
686 else:
687 return pykpathsea.find_file(self.fontmapping.fontfile, pykpathsea.kpse_type1_format)
689 def getencoding(self):
690 return self.fontmapping.reencodefont
692 def getencodingfile(self):
693 return self.fontmapping.encodingfile
696 class virtualfont(font):
697 def __init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug=0):
698 font.__init__(self, name, c, q, d, tfmconv, pyxconv, debug)
699 fontpath = pykpathsea.find_file(name, pykpathsea.kpse_vf_format)
700 if fontpath is None or not len(fontpath):
701 raise RuntimeError
702 self.vffile = vffile(fontpath, self.scale, tfmconv, pyxconv, fontmap, debug > 1)
704 def getfonts(self):
705 """ return fonts used in virtual font itself """
706 return self.vffile.getfonts()
708 def getchar(self, cc):
709 """ return dvi chunk corresponding to char code cc """
710 return self.vffile.getchar(cc)
713 ##############################################################################
714 # DVI file handling
715 ##############################################################################
717 _DVI_CHARMIN = 0 # typeset a character and move right (range min)
718 _DVI_CHARMAX = 127 # typeset a character and move right (range max)
719 _DVI_SET1234 = 128 # typeset a character and move right
720 _DVI_SETRULE = 132 # typeset a rule and move right
721 _DVI_PUT1234 = 133 # typeset a character
722 _DVI_PUTRULE = 137 # typeset a rule
723 _DVI_NOP = 138 # no operation
724 _DVI_BOP = 139 # beginning of page
725 _DVI_EOP = 140 # ending of page
726 _DVI_PUSH = 141 # save the current positions (h, v, w, x, y, z)
727 _DVI_POP = 142 # restore positions (h, v, w, x, y, z)
728 _DVI_RIGHT1234 = 143 # move right
729 _DVI_W0 = 147 # move right by w
730 _DVI_W1234 = 148 # move right and set w
731 _DVI_X0 = 152 # move right by x
732 _DVI_X1234 = 153 # move right and set x
733 _DVI_DOWN1234 = 157 # move down
734 _DVI_Y0 = 161 # move down by y
735 _DVI_Y1234 = 162 # move down and set y
736 _DVI_Z0 = 166 # move down by z
737 _DVI_Z1234 = 167 # move down and set z
738 _DVI_FNTNUMMIN = 171 # set current font (range min)
739 _DVI_FNTNUMMAX = 234 # set current font (range max)
740 _DVI_FNT1234 = 235 # set current font
741 _DVI_SPECIAL1234 = 239 # special (dvi extention)
742 _DVI_FNTDEF1234 = 243 # define the meaning of a font number
743 _DVI_PRE = 247 # preamble
744 _DVI_POST = 248 # postamble beginning
745 _DVI_POSTPOST = 249 # postamble ending
747 _DVI_VERSION = 2 # dvi version
749 # position variable indices
750 _POS_H = 0
751 _POS_V = 1
752 _POS_W = 2
753 _POS_X = 3
754 _POS_Y = 4
755 _POS_Z = 5
757 # reader states
758 _READ_PRE = 1
759 _READ_NOPAGE = 2
760 _READ_PAGE = 3
761 _READ_POST = 4 # XXX not used
762 _READ_POSTPOST = 5 # XXX not used
763 _READ_DONE = 6
766 class DVIError(exceptions.Exception): pass
768 # save and restore colors
770 class _savecolor(canvas.canvasitem):
771 def outputPS(self, file):
772 file.write("currentcolor currentcolorspace\n")
774 def outputPDF(self, file, writer, context):
775 file.write("q\n")
778 class _restorecolor(canvas.canvasitem):
779 def outputPS(self, file):
780 file.write("setcolorspace setcolor\n")
782 def outputPDF(self, file, writer, context):
783 file.write("Q\n")
785 class _savetrafo(canvas.canvasitem):
786 def outputPS(self, file):
787 file.write("matrix currentmatrix\n")
789 def outputPDF(self, file, writer, context):
790 file.write("q\n")
793 class _restoretrafo(canvas.canvasitem):
794 def outputPS(self, file):
795 file.write("setmatrix\n")
797 def outputPDF(self, file, writer, context):
798 file.write("Q\n")
801 class dvifile:
803 def __init__(self, filename, fontmap, debug=0, debugfile=sys.stdout):
804 """ opens the dvi file and reads the preamble """
805 self.filename = filename
806 self.fontmap = fontmap
807 self.debug = debug
808 self.debugfile = debugfile
809 self.debugstack = []
811 self.fonts = {}
812 self.activefont = None
814 # stack of fonts and fontscale currently used (used for VFs)
815 self.fontstack = []
816 self.stack = []
818 # pointer to currently active page
819 self.actpage = None
821 # currently active output: show instance currently used and
822 # the corresponding type 1 font
823 self.activeshow = None
824 self.activetype1font = None
826 # stack for self.file, self.fonts and self.stack, needed for VF inclusion
827 self.statestack = []
829 self.file = binfile(self.filename, "rb")
831 # currently read byte in file (for debugging output)
832 self.filepos = None
834 self._read_pre()
836 # helper routines
838 def flushout(self):
839 """ flush currently active string """
840 if self.activeshow is not None:
841 if self.debug:
842 self.debugfile.write("[%s]\n" % "".join([chr(char) for char in self.activeshow.chars]))
843 self.actpage.insert(self.activeshow)
844 self.activeshow = None
846 def begintext(self):
847 """ activate the font if is not yet active, closing a currently active
848 text object and flushing the output"""
849 if isinstance(self.activefont, type1font):
850 self.endtext()
851 if self.activetype1font != self.activefont and self.activefont:
852 self.actpage.insert(_begintextobject())
853 self.actpage.insert(selectfont(self.activefont))
854 self.activetype1font = self.activefont
856 def endtext(self):
857 self.flushout()
858 if self.activetype1font:
859 self.actpage.insert(_endtextobject())
860 self.activetype1font = None
862 def putrule(self, height, width, advancepos=1):
863 self.endtext()
864 x1 = self.pos[_POS_H] * self.pyxconv
865 y1 = -self.pos[_POS_V] * self.pyxconv
866 w = width * self.pyxconv
867 h = height * self.pyxconv
869 if height > 0 and width > 0:
870 if self.debug:
871 self.debugfile.write("%d: %srule height %d, width %d (???x??? pixels)\n" %
872 (self.filepos, advancepos and "set" or "put", height, width))
873 self.actpage.fill(path.rect_pt(x1, y1, w, h))
874 else:
875 if self.debug:
876 self.debugfile.write("%d: %srule height %d, width %d (invisible)\n" %
877 (self.filepos, advancepos and "set" or "put", height, width))
879 if advancepos:
880 if self.debug:
881 self.debugfile.write(" h:=%d+%d=%d, hh:=???\n" %
882 (self.pos[_POS_H], width, self.pos[_POS_H]+width))
883 self.pos[_POS_H] += width
885 def putchar(self, char, advancepos=1, id1234=0):
886 dx = advancepos and self.activefont.getwidth_dvi(char) or 0
888 if self.debug:
889 self.debugfile.write("%d: %s%s%d h:=%d+%d=%d, hh:=???\n" %
890 (self.filepos,
891 advancepos and "set" or "put",
892 id1234 and "%i " % id1234 or "char",
893 char,
894 self.pos[_POS_H], dx, self.pos[_POS_H]+dx))
896 if isinstance(self.activefont, type1font):
897 if self.activeshow is None:
898 # XXX: begintext would lead to massive number of selectfonts being issued
899 # OTOH is it save to remove begintext here? I think so ...
900 # self.begintext()
901 self.activeshow = _show(self.pos[_POS_H] * self.pyxconv, -self.pos[_POS_V] * self.pyxconv)
902 width = self.activefont.getwidth_dvi(char) * self.pyxconv
903 height = self.activefont.getheight_dvi(char) * self.pyxconv
904 depth = self.activefont.getdepth_dvi(char) * self.pyxconv
905 self.activeshow.addchar(width, height, depth, char)
907 self.activefont.markcharused(char)
908 self.pos[_POS_H] += dx
909 else:
910 # virtual font handling
911 afterpos = list(self.pos)
912 afterpos[_POS_H] += dx
913 self._push_dvistring(self.activefont.getchar(char), self.activefont.getfonts(), afterpos,
914 self.activefont.getsize_pt())
917 if not advancepos:
918 # XXX: correct !?
919 self.flushout()
921 def usefont(self, fontnum, id1234=0):
922 self.activefont = self.fonts[fontnum]
923 self.begintext()
924 if self.debug:
925 self.debugfile.write("%d: fnt%s%i current font is %s\n" %
926 (self.filepos,
927 id1234 and "%i " % id1234 or "num",
928 fontnum,
929 self.fonts[fontnum].name))
932 def definefont(self, cmdnr, num, c, q, d, fontname):
933 # cmdnr: type of fontdef command (only used for debugging output)
934 # c: checksum
935 # q: scaling factor (fix_word)
936 # Note that q is actually s in large parts of the documentation.
937 # d: design size (fix_word)
939 try:
940 font = virtualfont(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
941 except (TypeError, RuntimeError):
942 font = type1font(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
944 self.fonts[num] = font
946 if self.debug:
947 self.debugfile.write("%d: fntdef%d %i: %s\n" % (self.filepos, cmdnr, num, fontname))
949 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
950 # m = 1.0*q/d
951 # scalestring = scale!=1000 and " scaled %d" % scale or ""
952 # print ("Font %i: %s%s---loaded at size %d DVI units" %
953 # (num, fontname, scalestring, q))
954 # if scale!=1000:
955 # print " (this font is magnified %d%%)" % round(scale/10)
957 def special(self, s):
958 x = self.pos[_POS_H] * self.pyxconv
959 y = -self.pos[_POS_V] * self.pyxconv
960 if self.debug:
961 self.debugfile.write("%d: xxx '%s'\n" % (self.filepos, s))
962 if not s.startswith("PyX:"):
963 warnings.warn("ignoring special '%s'" % s)
964 return
966 # it is in general not safe to continue using the currently active font because
967 # the specials may involve some gsave/grestore operations
968 self.endtext()
970 command, args = s[4:].split()[0], s[4:].split()[1:]
971 if command=="color_begin":
972 if args[0]=="cmyk":
973 c = color.cmyk(float(args[1]), float(args[2]), float(args[3]), float(args[4]))
974 elif args[0]=="gray":
975 c = color.gray(float(args[1]))
976 elif args[0]=="hsb":
977 c = color.hsb(float(args[1]), float(args[2]), float(args[3]))
978 elif args[0]=="rgb":
979 c = color.rgb(float(args[1]), float(args[2]), float(args[3]))
980 elif args[0]=="RGB":
981 c = color.rgb(int(args[1])/255.0, int(args[2])/255.0, int(args[3])/255.0)
982 elif args[0]=="texnamed":
983 try:
984 c = getattr(color.cmyk, args[1])
985 except AttributeError:
986 raise RuntimeError("unknown TeX color '%s', aborting" % args[1])
987 else:
988 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args[0])
989 self.actpage.insert(_savecolor())
990 self.actpage.insert(c)
991 elif command=="color_end":
992 self.actpage.insert(_restorecolor())
993 elif command=="rotate_begin":
994 self.actpage.insert(_savetrafo())
995 self.actpage.insert(trafo.rotate_pt(float(args[0]), x, y))
996 elif command=="rotate_end":
997 self.actpage.insert(_restoretrafo())
998 elif command=="scale_begin":
999 self.actpage.insert(_savetrafo())
1000 self.actpage.insert(trafo.scale_pt(float(args[0]), float(args[1]), x, y))
1001 elif command=="scale_end":
1002 self.actpage.insert(_restoretrafo())
1003 elif command=="epsinclude":
1004 # parse arguments
1005 argdict = {}
1006 for arg in args:
1007 name, value = arg.split("=")
1008 argdict[name] = value
1010 # construct kwargs for epsfile constructor
1011 epskwargs = {}
1012 epskwargs["filename"] = argdict["file"]
1013 epskwargs["bbox"] = bbox.bbox_pt(float(argdict["llx"]), float(argdict["lly"]),
1014 float(argdict["urx"]), float(argdict["ury"]))
1015 if argdict.has_key("width"):
1016 epskwargs["width"] = float(argdict["width"]) * unit.t_pt
1017 if argdict.has_key("height"):
1018 epskwargs["height"] = float(argdict["height"]) * unit.t_pt
1019 if argdict.has_key("clip"):
1020 epskwargs["clip"] = int(argdict["clip"])
1021 self.actpage.insert(epsfile.epsfile(x * unit.t_pt, y * unit.t_pt, **epskwargs))
1022 elif command=="marker":
1023 if len(args) != 1:
1024 raise RuntimeError("marker contains spaces")
1025 for c in args[0]:
1026 if c not in string.digits + string.letters + "@":
1027 raise RuntimeError("marker contains invalid characters")
1028 if self.actpage.markers.has_key(args[0]):
1029 raise RuntimeError("marker name occurred several times")
1030 self.actpage.markers[args[0]] = x * unit.t_pt, y * unit.t_pt
1031 else:
1032 raise RuntimeError("unknown PyX special '%s', aborting" % command)
1033 self.begintext()
1035 # routines for pushing and popping different dvi chunks on the reader
1037 def _push_dvistring(self, dvi, fonts, afterpos, fontsize):
1038 """ push dvi string with defined fonts on top of reader
1039 stack. Every positions gets scaled relatively by the factor
1040 scale. After the interpreting of the dvi chunk has been finished,
1041 continue with self.pos=afterpos. The designsize of the virtual
1042 font is passed as a fix_word
1046 #if self.debug:
1047 # self.debugfile.write("executing new dvi chunk\n")
1048 self.debugstack.append(self.debug)
1049 self.debug = 0
1051 self.statestack.append((self.file, self.fonts, self.activefont, afterpos, self.stack, self.pyxconv, self.tfmconv))
1053 # units in vf files are relative to the size of the font and given as fix_words
1054 # which can be converted to floats by diving by 2**20
1055 oldpyxconv = self.pyxconv
1056 self.pyxconv = fontsize/2**20
1057 rescale = self.pyxconv/oldpyxconv
1059 self.file = stringbinfile(dvi)
1060 self.fonts = fonts
1061 self.stack = []
1062 self.filepos = 0
1064 # rescale self.pos in order to be consistent with the new scaling
1065 self.pos = map(lambda x, rescale=rescale:1.0*x/rescale, self.pos)
1067 # since tfmconv converts from tfm units to dvi units, rescale it as well
1068 self.tfmconv /= rescale
1070 self.usefont(0)
1072 def _pop_dvistring(self):
1073 self.flushout()
1074 #if self.debug:
1075 # self.debugfile.write("finished executing dvi chunk\n")
1076 self.debug = self.debugstack.pop()
1078 self.file.close()
1079 self.file, self.fonts, self.activefont, self.pos, self.stack, self.pyxconv, self.tfmconv = self.statestack.pop()
1081 # routines corresponding to the different reader states of the dvi maschine
1083 def _read_pre(self):
1084 afile = self.file
1085 while 1:
1086 self.filepos = afile.tell()
1087 cmd = afile.readuchar()
1088 if cmd == _DVI_NOP:
1089 pass
1090 elif cmd == _DVI_PRE:
1091 if afile.readuchar() != _DVI_VERSION: raise DVIError
1092 num = afile.readuint32()
1093 den = afile.readuint32()
1094 self.mag = afile.readuint32()
1096 # For the interpretation of the lengths in dvi and tfm files,
1097 # three conversion factors are relevant:
1098 # - self.tfmconv: tfm units -> dvi units
1099 # - self.pyxconv: dvi units -> (PostScript) points
1100 # - self.conv: dvi units -> pixels
1101 self.tfmconv = (25400000.0/num)*(den/473628672.0)/16.0
1103 # calculate conv as described in the DVIType docu using
1104 # a given resolution in dpi
1105 self.resolution = 300.0
1106 self.conv = (num/254000.0)*(self.resolution/den)
1108 # self.pyxconv is the conversion factor from the dvi units
1109 # to (PostScript) points. It consists of
1110 # - self.mag/1000.0: magstep scaling
1111 # - self.conv: conversion from dvi units to pixels
1112 # - 1/self.resolution: conversion from pixels to inch
1113 # - 72 : conversion from inch to points
1114 self.pyxconv = self.mag/1000.0*self.conv/self.resolution*72
1116 comment = afile.read(afile.readuchar())
1117 return
1118 else:
1119 raise DVIError
1121 def readpage(self, pageid=None):
1122 """ reads a page from the dvi file
1124 This routine reads a page from the dvi file which is
1125 returned as a canvas. When there is no page left in the
1126 dvifile, None is returned and the file is closed properly."""
1128 while 1:
1129 self.filepos = self.file.tell()
1130 cmd = self.file.readuchar()
1131 if cmd == _DVI_NOP:
1132 pass
1133 elif cmd == _DVI_BOP:
1134 # self.endtext()
1135 ispageid = [self.file.readuint32() for i in range(10)]
1136 if pageid is not None and ispageid != pageid:
1137 raise DVIError("invalid pageid")
1138 if self.debug:
1139 self.debugfile.write("%d: beginning of page %i\n" % (self.filepos, ispageid[0]))
1140 self.file.readuint32()
1141 break
1142 elif cmd == _DVI_POST:
1143 self.file.close()
1144 return None # nothing left
1145 else:
1146 raise DVIError
1148 actpage = canvas.canvas()
1149 self.actpage = actpage # XXX should be removed ...
1150 self.actpage.markers = {}
1151 self.pos = [0, 0, 0, 0, 0, 0]
1152 self.activetype1font = None
1154 # Since we do not know which dvi pages the actual PS file contains later on,
1155 # we have to keep track of used char informations separately for each dvi page.
1156 # In order to do so, the already defined fonts have to be copied and their
1157 # used char informations have to be reset
1158 for nr in self.fonts.keys():
1159 self.fonts[nr] = copy.copy(self.fonts[nr])
1160 self.fonts[nr].clearusedchars()
1162 while 1:
1163 afile = self.file
1164 self.filepos = afile.tell()
1165 try:
1166 cmd = afile.readuchar()
1167 except struct.error:
1168 # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
1169 # so we have to continue with the rest of the dvi file
1170 self._pop_dvistring()
1171 continue
1172 if cmd == _DVI_NOP:
1173 pass
1174 if cmd >= _DVI_CHARMIN and cmd <= _DVI_CHARMAX:
1175 self.putchar(cmd)
1176 elif cmd >= _DVI_SET1234 and cmd < _DVI_SET1234 + 4:
1177 self.putchar(afile.readint(cmd - _DVI_SET1234 + 1), id1234=cmd-_DVI_SET1234+1)
1178 elif cmd == _DVI_SETRULE:
1179 self.putrule(afile.readint32(), afile.readint32())
1180 elif cmd >= _DVI_PUT1234 and cmd < _DVI_PUT1234 + 4:
1181 self.putchar(afile.readint(cmd - _DVI_PUT1234 + 1), advancepos=0, id1234=cmd-_DVI_SET1234+1)
1182 elif cmd == _DVI_PUTRULE:
1183 self.putrule(afile.readint32(), afile.readint32(), 0)
1184 elif cmd == _DVI_EOP:
1185 self.endtext()
1186 if self.debug:
1187 self.debugfile.write("%d: eop\n \n" % self.filepos)
1188 return actpage
1189 elif cmd == _DVI_PUSH:
1190 self.stack.append(list(self.pos))
1191 if self.debug:
1192 self.debugfile.write("%s: push\n"
1193 "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1194 ((self.filepos, len(self.stack)-1) + tuple(self.pos)))
1195 elif cmd == _DVI_POP:
1196 self.flushout()
1197 self.pos = self.stack.pop()
1198 if self.debug:
1199 self.debugfile.write("%s: pop\n"
1200 "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1201 ((self.filepos, len(self.stack)) + tuple(self.pos)))
1202 elif cmd >= _DVI_RIGHT1234 and cmd < _DVI_RIGHT1234 + 4:
1203 self.flushout()
1204 dh = afile.readint(cmd - _DVI_RIGHT1234 + 1, 1)
1205 if self.debug:
1206 self.debugfile.write("%d: right%d %d h:=%d%+d=%d, hh:=???\n" %
1207 (self.filepos,
1208 cmd - _DVI_RIGHT1234 + 1,
1210 self.pos[_POS_H],
1212 self.pos[_POS_H]+dh))
1213 self.pos[_POS_H] += dh
1214 elif cmd == _DVI_W0:
1215 self.flushout()
1216 if self.debug:
1217 self.debugfile.write("%d: w0 %d h:=%d%+d=%d, hh:=???\n" %
1218 (self.filepos,
1219 self.pos[_POS_W],
1220 self.pos[_POS_H],
1221 self.pos[_POS_W],
1222 self.pos[_POS_H]+self.pos[_POS_W]))
1223 self.pos[_POS_H] += self.pos[_POS_W]
1224 elif cmd >= _DVI_W1234 and cmd < _DVI_W1234 + 4:
1225 self.flushout()
1226 self.pos[_POS_W] = afile.readint(cmd - _DVI_W1234 + 1, 1)
1227 if self.debug:
1228 self.debugfile.write("%d: w%d %d h:=%d%+d=%d, hh:=???\n" %
1229 (self.filepos,
1230 cmd - _DVI_W1234 + 1,
1231 self.pos[_POS_W],
1232 self.pos[_POS_H],
1233 self.pos[_POS_W],
1234 self.pos[_POS_H]+self.pos[_POS_W]))
1235 self.pos[_POS_H] += self.pos[_POS_W]
1236 elif cmd == _DVI_X0:
1237 self.flushout()
1238 if self.debug:
1239 self.debugfile.write("%d: x0 %d h:=%d%+d=%d, hh:=???\n" %
1240 (self.filepos,
1241 self.pos[_POS_X],
1242 self.pos[_POS_H],
1243 self.pos[_POS_X],
1244 self.pos[_POS_H]+self.pos[_POS_X]))
1245 self.pos[_POS_H] += self.pos[_POS_X]
1246 elif cmd >= _DVI_X1234 and cmd < _DVI_X1234 + 4:
1247 self.flushout()
1248 self.pos[_POS_X] = afile.readint(cmd - _DVI_X1234 + 1, 1)
1249 if self.debug:
1250 self.debugfile.write("%d: x%d %d h:=%d%+d=%d, hh:=???\n" %
1251 (self.filepos,
1252 cmd - _DVI_X1234 + 1,
1253 self.pos[_POS_X],
1254 self.pos[_POS_H],
1255 self.pos[_POS_X],
1256 self.pos[_POS_H]+self.pos[_POS_X]))
1257 self.pos[_POS_H] += self.pos[_POS_X]
1258 elif cmd >= _DVI_DOWN1234 and cmd < _DVI_DOWN1234 + 4:
1259 self.flushout()
1260 dv = afile.readint(cmd - _DVI_DOWN1234 + 1, 1)
1261 if self.debug:
1262 self.debugfile.write("%d: down%d %d v:=%d%+d=%d, vv:=???\n" %
1263 (self.filepos,
1264 cmd - _DVI_DOWN1234 + 1,
1266 self.pos[_POS_V],
1268 self.pos[_POS_V]+dv))
1269 self.pos[_POS_V] += dv
1270 elif cmd == _DVI_Y0:
1271 self.flushout()
1272 if self.debug:
1273 self.debugfile.write("%d: y0 %d v:=%d%+d=%d, vv:=???\n" %
1274 (self.filepos,
1275 self.pos[_POS_Y],
1276 self.pos[_POS_V],
1277 self.pos[_POS_Y],
1278 self.pos[_POS_V]+self.pos[_POS_Y]))
1279 self.pos[_POS_V] += self.pos[_POS_Y]
1280 elif cmd >= _DVI_Y1234 and cmd < _DVI_Y1234 + 4:
1281 self.flushout()
1282 self.pos[_POS_Y] = afile.readint(cmd - _DVI_Y1234 + 1, 1)
1283 if self.debug:
1284 self.debugfile.write("%d: y%d %d v:=%d%+d=%d, vv:=???\n" %
1285 (self.filepos,
1286 cmd - _DVI_Y1234 + 1,
1287 self.pos[_POS_Y],
1288 self.pos[_POS_V],
1289 self.pos[_POS_Y],
1290 self.pos[_POS_V]+self.pos[_POS_Y]))
1291 self.pos[_POS_V] += self.pos[_POS_Y]
1292 elif cmd == _DVI_Z0:
1293 self.flushout()
1294 if self.debug:
1295 self.debugfile.write("%d: z0 %d v:=%d%+d=%d, vv:=???\n" %
1296 (self.filepos,
1297 self.pos[_POS_Z],
1298 self.pos[_POS_V],
1299 self.pos[_POS_Z],
1300 self.pos[_POS_V]+self.pos[_POS_Z]))
1301 self.pos[_POS_V] += self.pos[_POS_Z]
1302 elif cmd >= _DVI_Z1234 and cmd < _DVI_Z1234 + 4:
1303 self.flushout()
1304 self.pos[_POS_Z] = afile.readint(cmd - _DVI_Z1234 + 1, 1)
1305 if self.debug:
1306 self.debugfile.write("%d: z%d %d v:=%d%+d=%d, vv:=???\n" %
1307 (self.filepos,
1308 cmd - _DVI_Z1234 + 1,
1309 self.pos[_POS_Z],
1310 self.pos[_POS_V],
1311 self.pos[_POS_Z],
1312 self.pos[_POS_V]+self.pos[_POS_Z]))
1313 self.pos[_POS_V] += self.pos[_POS_Z]
1314 elif cmd >= _DVI_FNTNUMMIN and cmd <= _DVI_FNTNUMMAX:
1315 self.usefont(cmd - _DVI_FNTNUMMIN, 0)
1316 elif cmd >= _DVI_FNT1234 and cmd < _DVI_FNT1234 + 4:
1317 # note that according to the DVI docs, for four byte font numbers,
1318 # the font number is signed. Don't ask why!
1319 fntnum = afile.readint(cmd - _DVI_FNT1234 + 1, cmd == _DVI_FNT1234 + 3)
1320 self.usefont(fntnum, id1234=cmd-_DVI_FNT1234+1)
1321 elif cmd >= _DVI_SPECIAL1234 and cmd < _DVI_SPECIAL1234 + 4:
1322 self.special(afile.read(afile.readint(cmd - _DVI_SPECIAL1234 + 1)))
1323 elif cmd >= _DVI_FNTDEF1234 and cmd < _DVI_FNTDEF1234 + 4:
1324 if cmd == _DVI_FNTDEF1234:
1325 num = afile.readuchar()
1326 elif cmd == _DVI_FNTDEF1234+1:
1327 num = afile.readuint16()
1328 elif cmd == _DVI_FNTDEF1234+2:
1329 num = afile.readuint24()
1330 elif cmd == _DVI_FNTDEF1234+3:
1331 # Cool, here we have according to docu a signed int. Why?
1332 num = afile.readint32()
1333 self.definefont(cmd-_DVI_FNTDEF1234+1,
1334 num,
1335 afile.readint32(),
1336 afile.readint32(),
1337 afile.readint32(),
1338 afile.read(afile.readuchar()+afile.readuchar()))
1339 else:
1340 raise DVIError
1343 ##############################################################################
1344 # VF file handling
1345 ##############################################################################
1347 _VF_LONG_CHAR = 242 # character packet (long version)
1348 _VF_FNTDEF1234 = _DVI_FNTDEF1234 # font definition
1349 _VF_PRE = _DVI_PRE # preamble
1350 _VF_POST = _DVI_POST # postamble
1352 _VF_ID = 202 # VF id byte
1354 class VFError(exceptions.Exception): pass
1356 class vffile:
1357 def __init__(self, filename, scale, tfmconv, pyxconv, fontmap, debug=0):
1358 self.filename = filename
1359 self.scale = scale
1360 self.tfmconv = tfmconv
1361 self.pyxconv = pyxconv
1362 self.fontmap = fontmap
1363 self.debug = debug
1364 self.fonts = {} # used fonts
1365 self.widths = {} # widths of defined chars
1366 self.chardefs = {} # dvi chunks for defined chars
1368 afile = binfile(self.filename, "rb")
1370 cmd = afile.readuchar()
1371 if cmd == _VF_PRE:
1372 if afile.readuchar() != _VF_ID: raise VFError
1373 comment = afile.read(afile.readuchar())
1374 self.cs = afile.readuint32()
1375 self.ds = afile.readuint32()
1376 else:
1377 raise VFError
1379 while 1:
1380 cmd = afile.readuchar()
1381 if cmd >= _VF_FNTDEF1234 and cmd < _VF_FNTDEF1234 + 4:
1382 # font definition
1383 if cmd == _VF_FNTDEF1234:
1384 num = afile.readuchar()
1385 elif cmd == _VF_FNTDEF1234+1:
1386 num = afile.readuint16()
1387 elif cmd == _VF_FNTDEF1234+2:
1388 num = afile.readuint24()
1389 elif cmd == _VF_FNTDEF1234+3:
1390 num = afile.readint32()
1391 c = afile.readint32()
1392 s = afile.readint32() # relative scaling used for font (fix_word)
1393 d = afile.readint32() # design size of font
1394 fontname = afile.read(afile.readuchar()+afile.readuchar())
1396 # rescaled size of font: s is relative to the scaling
1397 # of the virtual font itself. Note that realscale has
1398 # to be a fix_word (like s)
1399 # XXX: check rounding
1400 reals = int(round(self.scale * (16*self.ds/16777216L) * s))
1402 # print ("defining font %s -- VF scale: %g, VF design size: %d, relative font size: %d => real size: %d" %
1403 # (fontname, self.scale, self.ds, s, reals)
1406 # XXX allow for virtual fonts here too
1407 self.fonts[num] = type1font(fontname, c, reals, d, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
1408 elif cmd == _VF_LONG_CHAR:
1409 # character packet (long form)
1410 pl = afile.readuint32() # packet length
1411 cc = afile.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1412 tfm = afile.readuint24() # character width
1413 dvi = afile.read(pl) # dvi code of character
1414 self.widths[cc] = tfm
1415 self.chardefs[cc] = dvi
1416 elif cmd < _VF_LONG_CHAR:
1417 # character packet (short form)
1418 cc = afile.readuchar() # char code
1419 tfm = afile.readuint24() # character width
1420 dvi = afile.read(cmd)
1421 self.widths[cc] = tfm
1422 self.chardefs[cc] = dvi
1423 elif cmd == _VF_POST:
1424 break
1425 else:
1426 raise VFError
1428 afile.close()
1430 def getfonts(self):
1431 return self.fonts
1433 def getchar(self, cc):
1434 return self.chardefs[cc]