a few rounding updates and comments
[PyX/mjg.git] / pyx / dvifile.py
blob09ca04bedd2f3e7f8150d6bbbdd9d57662129ac6
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
26 import unit, epsfile, bbox, base, canvas, color, trafo, path, prolog, pykpathsea
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")
141 self.debug = debug
144 # read pre header
147 self.lf = self.file.readint16()
148 self.lh = self.file.readint16()
149 self.bc = self.file.readint16()
150 self.ec = self.file.readint16()
151 self.nw = self.file.readint16()
152 self.nh = self.file.readint16()
153 self.nd = self.file.readint16()
154 self.ni = self.file.readint16()
155 self.nl = self.file.readint16()
156 self.nk = self.file.readint16()
157 self.ne = self.file.readint16()
158 self.np = self.file.readint16()
160 if not (self.bc-1 <= self.ec <= 255 and
161 self.ne <= 256 and
162 self.lf == 6+self.lh+(self.ec-self.bc+1)+self.nw+self.nh+self.nd
163 +self.ni+self.nl+self.nk+self.ne+self.np):
164 raise TFMError, "error in TFM pre-header"
166 if debug:
167 print "lh=%d" % self.lh
170 # read header
173 self.checksum = self.file.readint32()
174 self.designsize = self.file.readint32()
175 assert self.designsize > 0, "invald design size"
176 if self.lh > 2:
177 assert self.lh > 11, "inconsistency in TFM file: incomplete field"
178 self.charcoding = self.file.readstring(40)
179 else:
180 self.charcoding = None
182 if self.lh > 12:
183 assert self.lh > 16, "inconsistency in TFM file: incomplete field"
184 self.fontfamily = self.file.readstring(20)
185 else:
186 self.fontfamily = None
188 if self.debug:
189 print "(FAMILY %s)" % self.fontfamily
190 print "(CODINGSCHEME %s)" % self.charcoding
191 print "(DESINGSIZE R %f)" % 16.0*self.designsize/16777216L
193 if self.lh > 17:
194 self.sevenbitsave = self.file.readuchar()
195 # ignore the following two bytes
196 self.file.readint16()
197 facechar = self.file.readuchar()
198 # decode ugly face specification into the Knuth suggested string
199 if facechar < 18:
200 if facechar >= 12:
201 self.face = "E"
202 facechar -= 12
203 elif facechar >= 6:
204 self.face = "C"
205 facechar -= 6
206 else:
207 self.face = "R"
209 if facechar >= 4:
210 self.face = "L" + self.face
211 facechar -= 4
212 elif facechar >= 2:
213 self.face = "B" + self.face
214 facechar -= 2
215 else:
216 self.face = "M" + self.face
218 if facechar == 1:
219 self.face = self.face[0] + "I" + self.face[1]
220 else:
221 self.face = self.face[0] + "R" + self.face[1]
223 else:
224 self.face = None
225 else:
226 self.sevenbitsave = self.face = None
228 if self.lh > 18:
229 # just ignore the rest
230 print self.file.read((self.lh-18)*4)
233 # read char_info
236 self.char_info = [None]*(self.ec+1)
237 for charcode in range(self.bc, self.ec+1):
238 self.char_info[charcode] = char_info_word(self.file.readint32())
239 if self.char_info[charcode].width_index == 0:
240 # disable character if width_index is zero
241 self.char_info[charcode] = None
244 # read widths
247 self.width = [None for width_index in range(self.nw)]
248 for width_index in range(self.nw):
249 self.width[width_index] = self.file.readint32()
252 # read heights
255 self.height = [None for height_index in range(self.nh)]
256 for height_index in range(self.nh):
257 self.height[height_index] = self.file.readint32()
260 # read depths
263 self.depth = [None for depth_index in range(self.nd)]
264 for depth_index in range(self.nd):
265 self.depth[depth_index] = self.file.readint32()
268 # read italic
271 self.italic = [None for italic_index in range(self.ni)]
272 for italic_index in range(self.ni):
273 self.italic[italic_index] = self.file.readint32()
276 # read lig_kern
279 # XXX decode to lig_kern_command
281 self.lig_kern = [None for lig_kern_index in range(self.nl)]
282 for lig_kern_index in range(self.nl):
283 self.lig_kern[lig_kern_index] = self.file.readint32()
286 # read kern
289 self.kern = [None for kern_index in range(self.nk)]
290 for kern_index in range(self.nk):
291 self.kern[kern_index] = self.file.readint32()
294 # read exten
297 # XXX decode to extensible_recipe
299 self.exten = [None for exten_index in range(self.ne)]
300 for exten_index in range(self.ne):
301 self.exten[exten_index] = self.file.readint32()
304 # read param
307 # XXX decode
309 self.param = [None for param_index in range(self.np)]
310 for param_index in range(self.np):
311 self.param[param_index] = self.file.readint32()
313 self.file.close()
316 # class FontEncoding:
318 # def __init__(self, filename):
319 # """ font encoding contained in filename """
320 # encpath = pykpathsea.find_file(filename, pykpathsea.kpse_tex_ps_header_format)
321 # encfile = tokenfile(encpath)
323 # # name of encoding
324 # self.encname = encfile.gettoken()
325 # token = encfile.gettoken()
326 # if token != "[":
327 # raise RuntimeError("cannot parse encoding file '%s', expecting '[' got '%s'" % (filename, token))
328 # self.encvector = []
329 # for i in range(256):
330 # token = encfile.gettoken()
331 # if token is None or token=="]":
332 # raise RuntimeError("not enough charcodes in encoding file '%s'" % filename)
333 # self.encvector.append(token)
334 # if encfile.gettoken() != "]":
335 # raise RuntimeError("too many charcodes in encoding file '%s'" % filename)
336 # token = encfile.gettoken()
337 # if token != "def":
338 # raise RuntimeError("cannot parse encoding file '%s', expecting 'def' got '%s'" % (filename, token))
339 # token = encfile.gettoken()
340 # if token != None:
341 # raise RuntimeError("encoding file '%s' too long" % filename)
342 # encfile.close()
344 # def encode(self, charcode):
345 # return self.encvector[charcode]
347 ##############################################################################
348 # Font handling
349 ##############################################################################
351 _ReEncodeFont = prolog.definition("ReEncodeFont", """{
352 5 dict
353 begin
354 /newencoding exch def
355 /newfontname exch def
356 /basefontname exch def
357 /basefontdict basefontname findfont def
358 /newfontdict basefontdict maxlength dict def
359 basefontdict {
360 exch dup dup /FID ne exch /Encoding ne and
361 { exch newfontdict 3 1 roll put }
362 { pop pop }
363 ifelse
364 } forall
365 newfontdict /FontName newfontname put
366 newfontdict /Encoding newencoding put
367 newfontname newfontdict definefont pop
369 }""")
372 # PostScript font selection and output primitives
375 class _begintextobject(base.canvasitem):
376 def outputPS(self, file):
377 pass
379 def outputPDF(self, file):
380 file.write("BT\n")
383 class _endtextobject(base.canvasitem):
384 def outputPS(self, file):
385 pass
387 def outputPDF(self, file):
388 file.write("ET\n")
391 class _selectfont(base.canvasitem):
392 # XXX this should go away and be merged with selectfont
393 def __init__(self, name, size):
394 self.name = name
395 self.size = size
397 def outputPS(self, file):
398 file.write("/%s %f selectfont\n" % (self.name, self.size))
400 def outputPDF(self, file):
401 file.write("/%s %f Tf\n" % (self.name, self.size))
404 class selectfont(base.canvasitem):
405 def __init__(self, font):
406 # XXX maybe we should change the calling convention here and only pass the
407 # name, size, encoding, usedchars of the font
408 self.font = font
409 self.size = font.getsize_pt()
410 # self.fontid = None
411 self.fontid = font.getpsname()
413 def prolog(self):
414 result = [prolog.fontdefinition(self.font,
415 self.font.getbasepsname(),
416 self.font.getfontfile(),
417 self.font.getencodingfile(),
418 self.font.usedchars)]
419 if self.font.getencoding():
420 result.append(_ReEncodeFont)
421 result.append(prolog.fontencoding(self.font.getencoding(), self.font.getencodingfile()))
422 result.append(prolog.fontreencoding(self.font.getpsname(), self.font.getbasepsname(), self.font.getencoding()))
423 return result
425 def outputPS(self, file):
426 file.write("/%s %f selectfont\n" % (self.fontid, self.size))
428 def outputPDF(self, file):
429 file.write("/%s %f Tf\n" % (self.fontid, self.size))
432 class _show(base.canvasitem):
434 def __init__(self, x, y):
435 self.x = x
436 self.y = y
437 self.width = 0
438 self.height = 0
439 self.depth = 0
440 self.chars = []
442 def addchar(self, width, height, depth, char):
443 self.width += width
444 if height > self.height:
445 self.height = height
446 if depth > self.depth:
447 self.depth = depth
448 self.chars.append(char)
450 def bbox(self):
451 return bbox.bbox_pt(self.x, self.y-self.depth, self.x+self.width, self.y+self.height)
453 def outputPS(self, file):
454 outstring = ""
455 for char in self.chars:
456 if char > 32 and char < 127 and chr(char) not in "()[]<>\\":
457 ascii = "%s" % chr(char)
458 else:
459 ascii = "\\%03o" % char
460 outstring += ascii
461 file.write("%g %g moveto (%s) show\n" % (self.x, self.y, outstring))
463 def outputPDF(self, file):
464 outstring = ""
465 for char in self.chars:
466 if char > 32 and char < 127 and chr(char) not in "()[]<>\\":
467 ascii = "%s" % chr(char)
468 else:
469 ascii = "\\%03o" % char
470 outstring += ascii
471 # file.write("%f %f Td (%s) Tj\n" % (self.x, self.y, outstring))
472 file.write("1 0 0 1 %f %f Tm (%s) Tj\n" % (self.x, self.y, outstring))
475 class fontmapping:
477 tokenpattern = re.compile(r'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
479 def __init__(self, s):
480 """ construct font mapping from line s of font mapping file """
481 self.texname = self.basepsname = self.fontfile = None
483 # standard encoding
484 self.encodingfile = None
486 # supported postscript fragments occuring in psfonts.map
487 self.reencodefont = self.extendfont = self.slantfont = None
489 tokens = []
490 while len(s):
491 match = self.tokenpattern.match(s)
492 if match:
493 if match.groups()[0]:
494 tokens.append('"%s"' % match.groups()[0])
495 else:
496 tokens.append(match.groups()[2])
497 s = s[match.end():]
498 else:
499 raise RuntimeError("wrong syntax")
501 for token in tokens:
502 if token.startswith("<"):
503 if token.startswith("<<"):
504 # XXX: support non-partial download here
505 self.fontfile = token[2:]
506 elif token.startswith("<["):
507 self.encodingfile = token[2:]
508 elif token.endswith(".pfa") or token.endswith(".pfb"):
509 self.fontfile = token[1:]
510 elif token.endswith(".enc"):
511 self.encodingfile = token[1:]
512 else:
513 raise RuntimeError("wrong syntax")
514 elif token.startswith('"'):
515 pscode = token[1:-1].split()
516 # parse standard postscript code fragments
517 while pscode:
518 try:
519 arg, cmd = pscode[:2]
520 except:
521 raise RuntimeError("Unsupported Postscript fragment '%s'" % pscode)
522 pscode = pscode[2:]
523 if cmd == "ReEncodeFont":
524 self.reencodefont = arg
525 elif cmd == "ExtendFont":
526 self.extendfont = arg
527 elif cmd == "SlantFont":
528 self.slantfont = arg
529 else:
530 raise RuntimeError("Unsupported Postscript fragment '%s %s'" % (arg, cmd))
531 else:
532 if self.texname is None:
533 self.texname = token
534 else:
535 self.basepsname = token
536 if self.basepsname is None:
537 self.basepsname = self.texname
539 def __str__(self):
540 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
541 (self.texname, self.basepsname, self.fontfile, repr(self.encodingfile)))
543 # generate fontmap
545 def readfontmap(filenames):
546 """ read font map from filename (without path) """
547 fontmap = {}
548 for filename in filenames:
549 mappath = pykpathsea.find_file(filename, pykpathsea.kpse_fontmap_format)
550 # try also the oft-used registration as dvips config file
551 if not mappath:
552 mappath = pykpathsea.find_file(filename, pykpathsea.kpse_dvips_config_format)
553 if not mappath:
554 raise RuntimeError("cannot find font mapping file '%s'" % filename)
555 mapfile = open(mappath, "r")
556 lineno = 0
557 for line in mapfile.readlines():
558 lineno += 1
559 line = line.rstrip()
560 if not (line=="" or line[0] in (" ", "%", "*", ";" , "#")):
561 try:
562 fm = fontmapping(line)
563 except RuntimeError, e:
564 sys.stderr.write("*** PyX Warning: Ignoring line %i in mapping file '%s': %s\n" % (lineno, filename, e))
565 else:
566 fontmap[fm.texname] = fm
567 mapfile.close()
568 return fontmap
571 class font:
572 def __init__(self, name, c, q, d, tfmconv, pyxconv, debug=0):
573 self.name = name
574 self.q = q # desired size of font (fix_word) in TeX points
575 self.d = d # design size of font (fix_word) in TeX points
576 self.tfmconv = tfmconv # conversion factor from tfm units to dvi units
577 self.pyxconv = pyxconv # conversion factor from dvi units to PostScript points
578 tfmpath = pykpathsea.find_file("%s.tfm" % self.name, pykpathsea.kpse_tfm_format)
579 if not tfmpath:
580 raise TFMError("cannot find %s.tfm" % self.name)
581 self.tfmfile = tfmfile(tfmpath, debug)
583 # We only check for equality of font checksums if none of them
584 # is zero. The case c == 0 happend in some VF files and
585 # according to the VFtoVP documentation, paragraph 40, a check
586 # is only performed if tfmfile.checksum > 0. Anyhow, being
587 # more generous here seems to be reasonable
588 if self.tfmfile.checksum != c and self.tfmfile.checksum != 0 and c != 0:
589 raise DVIError("check sums do not agree: %d vs. %d" %
590 (self.tfmfile.checksum, c))
592 # Check whether the given design size matches the one defined in the tfm file
593 if abs(self.tfmfile.designsize - d) > 2:
594 raise DVIError("design sizes do not agree: %d vs. %d" % (self.tfmfile.designsize, d))
595 if q < 0 or q > 134217728:
596 raise DVIError("font '%s' not loaded: bad scale" % self.name)
597 if d < 0 or d > 134217728:
598 raise DVIError("font '%s' not loaded: bad design size" % self.name)
600 self.scale = 1.0*q/d
602 # for bookkeeping of used characters
603 self.usedchars = [0] * 256
605 def __str__(self):
606 return "font %s designed at %g TeX pts used at %g TeX pts" % (self.name,
607 16.0*self.d/16777216L,
608 16.0*self.q/16777216L)
611 __repr__ = __str__
613 def getsize_pt(self):
614 """ return size of font in (PS) points """
615 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
616 # to the corresponding float. Furthermore, we have to convert from TeX
617 # points to points, hence the factor 72/72.27.
618 return 16L*self.q/16777216L*72/72.27
620 def _convert_tfm_to_dvi(self, length):
621 return 16*long(round(length*self.q*self.tfmconv))/16777216
623 # Knuth instead suggests the following algorithm based on integer logic only
624 # For z < 8388608 the result was checked and seemed to be equal.
625 # z = int(round(self.q*self.tfmconv))
626 # b0, b1, b2, b3 = [ord(c) for c in struct.pack(">L", length)]
627 # assert b0 == 0 or b0 == 255
628 # beta = 4
629 # while z >= 8388608:
630 # z /= 2
631 # beta -= 1
632 # result = ( ( ( ( ( b3 * z ) >> 8 ) + ( b2 * z ) ) >> 8 ) + ( b1 * z ) ) >> beta
633 # if b0 == 255:
634 # result = result - (z << beta)
635 # assert result == 16*int(round(length*self.q*self.tfmconv))/16777216
637 # we do not need that ...
638 # def _convert_tfm_to_pt(self, length):
639 # return (16*long(round(length*self.q*self.tfmconv))/16777216) * self.pyxconv
641 # routines returning lengths as integers in dvi units
643 def getwidth_dvi(self, charcode):
644 return self._convert_tfm_to_dvi(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
646 def getheight_dvi(self, charcode):
647 return self._convert_tfm_to_dvi(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
649 def getdepth_dvi(self, charcode):
650 return self._convert_tfm_to_dvi(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
652 def getitalic_dvi(self, charcode):
653 return self._convert_tfm_to_dvi(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
655 # routines returning lengths as floats in PostScript points
657 # def getwidth_pt(self, charcode):
658 # return self._convert_tfm_to_pt(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
660 # def getheight_pt(self, charcode):
661 # return self._convert_tfm_to_pt(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
663 # def getdepth_pt(self, charcode):
664 # return self._convert_tfm_to_pt(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
666 # def getitalic_pt(self, charcode):
667 # return self._convert_tfm_to_pt(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
669 def markcharused(self, charcode):
670 self.usedchars[charcode] = 1
672 def mergeusedchars(self, otherfont):
673 for i in range(len(self.usedchars)):
674 self.usedchars[i] = self.usedchars[i] or otherfont.usedchars[i]
676 def clearusedchars(self):
677 self.usedchars = [0] * 256
680 class type1font(font):
681 def __init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug=0):
682 font.__init__(self, name, c, q, d, tfmconv, pyxconv, debug)
683 self.fontmapping = fontmap.get(name)
684 if self.fontmapping is None:
685 raise RuntimeError("no information for font '%s' found in font mapping file, aborting" % name)
687 def getbasepsname(self):
688 return self.fontmapping.basepsname
690 def getpsname(self):
691 if self.fontmapping.reencodefont:
692 return "%s-%s" % (self.fontmapping.basepsname, self.fontmapping.reencodefont)
693 else:
694 return self.fontmapping.basepsname
696 def getfontfile(self):
697 return self.fontmapping.fontfile
699 def getencoding(self):
700 return self.fontmapping.reencodefont
702 def getencodingfile(self):
703 return self.fontmapping.encodingfile
706 class virtualfont(font):
707 def __init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug=0):
708 font.__init__(self, name, c, q, d, tfmconv, pyxconv, debug)
709 fontpath = pykpathsea.find_file(name, pykpathsea.kpse_vf_format)
710 if fontpath is None or not len(fontpath):
711 raise RuntimeError
712 self.vffile = vffile(fontpath, self.scale, tfmconv, pyxconv, fontmap, debug > 1)
714 def getfonts(self):
715 """ return fonts used in virtual font itself """
716 return self.vffile.getfonts()
718 def getchar(self, cc):
719 """ return dvi chunk corresponding to char code cc """
720 return self.vffile.getchar(cc)
723 ##############################################################################
724 # DVI file handling
725 ##############################################################################
727 _DVI_CHARMIN = 0 # typeset a character and move right (range min)
728 _DVI_CHARMAX = 127 # typeset a character and move right (range max)
729 _DVI_SET1234 = 128 # typeset a character and move right
730 _DVI_SETRULE = 132 # typeset a rule and move right
731 _DVI_PUT1234 = 133 # typeset a character
732 _DVI_PUTRULE = 137 # typeset a rule
733 _DVI_NOP = 138 # no operation
734 _DVI_BOP = 139 # beginning of page
735 _DVI_EOP = 140 # ending of page
736 _DVI_PUSH = 141 # save the current positions (h, v, w, x, y, z)
737 _DVI_POP = 142 # restore positions (h, v, w, x, y, z)
738 _DVI_RIGHT1234 = 143 # move right
739 _DVI_W0 = 147 # move right by w
740 _DVI_W1234 = 148 # move right and set w
741 _DVI_X0 = 152 # move right by x
742 _DVI_X1234 = 153 # move right and set x
743 _DVI_DOWN1234 = 157 # move down
744 _DVI_Y0 = 161 # move down by y
745 _DVI_Y1234 = 162 # move down and set y
746 _DVI_Z0 = 166 # move down by z
747 _DVI_Z1234 = 167 # move down and set z
748 _DVI_FNTNUMMIN = 171 # set current font (range min)
749 _DVI_FNTNUMMAX = 234 # set current font (range max)
750 _DVI_FNT1234 = 235 # set current font
751 _DVI_SPECIAL1234 = 239 # special (dvi extention)
752 _DVI_FNTDEF1234 = 243 # define the meaning of a font number
753 _DVI_PRE = 247 # preamble
754 _DVI_POST = 248 # postamble beginning
755 _DVI_POSTPOST = 249 # postamble ending
757 _DVI_VERSION = 2 # dvi version
759 # position variable indices
760 _POS_H = 0
761 _POS_V = 1
762 _POS_W = 2
763 _POS_X = 3
764 _POS_Y = 4
765 _POS_Z = 5
767 # reader states
768 _READ_PRE = 1
769 _READ_NOPAGE = 2
770 _READ_PAGE = 3
771 _READ_POST = 4 # XXX not used
772 _READ_POSTPOST = 5 # XXX not used
773 _READ_DONE = 6
776 class DVIError(exceptions.Exception): pass
778 # save and restore colors
780 class _savecolor(base.canvasitem):
781 def outputPS(self, file):
782 file.write("currentcolor currentcolorspace\n")
784 def outputPDF(self, file):
785 file.write("q\n")
788 class _restorecolor(base.canvasitem):
789 def outputPS(self, file):
790 file.write("setcolorspace setcolor\n")
792 def outputPDF(self, file):
793 file.write("Q\n")
795 class _savetrafo(base.canvasitem):
796 def outputPS(self, file):
797 file.write("matrix currentmatrix\n")
799 def outputPDF(self, file):
800 file.write("q\n")
803 class _restoretrafo(base.canvasitem):
804 def outputPS(self, file):
805 file.write("setmatrix\n")
807 def outputPDF(self, file):
808 file.write("Q\n")
811 class dvifile:
813 def __init__(self, filename, fontmap, debug=0):
814 """ opens the dvi file and reads the preamble """
815 self.filename = filename
816 self.fontmap = fontmap
817 self.debug = debug
819 self.fonts = {}
820 self.activefont = None
822 # stack of fonts and fontscale currently used (used for VFs)
823 self.fontstack = []
824 self.stack = []
826 # pointer to currently active page
827 self.actpage = None
829 # currently active output: show instance currently used and
830 # the corresponding type 1 font
831 self.activeshow = None
832 self.activetype1font = None
834 # stack for self.file, self.fonts and self.stack, needed for VF inclusion
835 self.statestack = []
837 self.file = binfile(self.filename, "rb")
839 # currently read byte in file (for debugging output)
840 self.filepos = None
842 self._read_pre()
844 # helper routines
846 def flushout(self):
847 """ flush currently active string """
848 if self.activeshow is not None:
849 if self.debug:
850 print "[%s]" % "".join([chr(char) for char in self.activeshow.chars])
851 self.actpage.insert(self.activeshow)
852 self.activeshow = None
854 def begintext(self):
855 """ activate the font if is not yet active, closing a currently active
856 text object and flushing the output"""
857 if isinstance(self.activefont, type1font):
858 self.endtext()
859 if self.activetype1font != self.activefont and self.activefont:
860 self.actpage.insert(_begintextobject())
861 self.actpage.insert(selectfont(self.activefont))
862 self.activetype1font = self.activefont
864 def endtext(self):
865 self.flushout()
866 if self.activetype1font:
867 self.actpage.insert(_endtextobject())
868 self.activetype1font = None
870 def putrule(self, height, width, advancepos=1):
871 self.endtext()
872 x1 = self.pos[_POS_H] * self.pyxconv
873 y1 = -self.pos[_POS_V] * self.pyxconv
874 w = width * self.pyxconv
875 h = height * self.pyxconv
877 if height > 0 and width > 0:
878 if self.debug:
879 print ("%d: %srule height %d, width %d (???x??? pixels)" %
880 (self.filepos, advancepos and "set" or "put", height, width))
881 self.actpage.fill(path.rect_pt(x1, y1, w, h))
882 else:
883 if self.debug:
884 print ("%d: %srule height %d, width %d (invisible)" %
885 (self.filepos, advancepos and "set" or "put", height, width))
887 if advancepos:
888 if self.debug:
889 print (" h:=%d+%d=%d, hh:=???" %
890 (self.pos[_POS_H], width, self.pos[_POS_H]+width))
891 self.pos[_POS_H] += width
893 def putchar(self, char, advancepos=1):
894 dx = advancepos and self.activefont.getwidth_dvi(char) or 0
896 if self.debug:
897 print ("%d: %schar%d h:=%d+%d=%d, hh:=???" %
898 (self.filepos,
899 advancepos and "set" or "put",
900 char,
901 self.pos[_POS_H], dx, self.pos[_POS_H]+dx))
903 if isinstance(self.activefont, type1font):
904 if self.activeshow is None:
905 # XXX: begintext would lead to massive number of selectfonts being issued
906 # OTOH is it save to remove begintext here? I think so ...
907 # self.begintext()
908 self.activeshow = _show(self.pos[_POS_H] * self.pyxconv, -self.pos[_POS_V] * self.pyxconv)
909 width = self.activefont.getwidth_dvi(char) * self.pyxconv
910 height = self.activefont.getheight_dvi(char) * self.pyxconv
911 depth = self.activefont.getdepth_dvi(char) * self.pyxconv
912 self.activeshow.addchar(width, height, depth, char)
914 self.activefont.markcharused(char)
915 self.pos[_POS_H] += dx
916 else:
917 # virtual font handling
918 afterpos = list(self.pos)
919 afterpos[_POS_H] += dx
920 self._push_dvistring(self.activefont.getchar(char), self.activefont.getfonts(), afterpos,
921 self.activefont.getsize_pt())
924 if not advancepos:
925 # XXX: correct !?
926 self.flushout()
928 def usefont(self, fontnum):
929 self.activefont = self.fonts[fontnum]
930 self.begintext()
931 if self.debug:
932 print ("%d: fntnum%i current font is %s" %
933 (self.filepos,
934 fontnum, self.fonts[fontnum].name))
937 def definefont(self, cmdnr, num, c, q, d, fontname):
938 # cmdnr: type of fontdef command (only used for debugging output)
939 # c: checksum
940 # q: scaling factor (fix_word)
941 # Note that q is actually s in large parts of the documentation.
942 # d: design size (fix_word)
944 try:
945 font = virtualfont(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
946 except (TypeError, RuntimeError):
947 font = type1font(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
949 self.fonts[num] = font
951 if self.debug:
952 print "%d: fntdef%d %i: %s" % (self.filepos, cmdnr, num, fontname)
954 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
955 # m = 1.0*q/d
956 # scalestring = scale!=1000 and " scaled %d" % scale or ""
957 # print ("Font %i: %s%s---loaded at size %d DVI units" %
958 # (num, fontname, scalestring, q))
959 # if scale!=1000:
960 # print " (this font is magnified %d%%)" % round(scale/10)
962 def special(self, s):
963 x = self.pos[_POS_H] * self.pyxconv
964 y = -self.pos[_POS_V] * self.pyxconv
965 if self.debug:
966 print "%d: xxx '%s'" % (self.filepos, s)
967 if not s.startswith("PyX:"):
968 #if s.startswith("Warning:"):
969 sys.stderr.write("*** PyX Warning: ignoring special '%s'\n" % s)
970 return
971 #else:
972 # raise RuntimeError("the special '%s' cannot be handled by PyX, aborting" % s)
974 # it is in general not safe to continue using the currently active font because
975 # the specials may involve some gsave/grestore operations
976 self.endtext()
978 command, args = s[4:].split()[0], s[4:].split()[1:]
979 if command=="color_begin":
980 if args[0]=="cmyk":
981 c = color.cmyk(float(args[1]), float(args[2]), float(args[3]), float(args[4]))
982 elif args[0]=="gray":
983 c = color.gray(float(args[1]))
984 elif args[0]=="hsb":
985 c = color.hsb(float(args[1]), float(args[2]), float(args[3]))
986 elif args[0]=="rgb":
987 c = color.rgb(float(args[1]), float(args[2]), float(args[3]))
988 elif args[0]=="RGB":
989 c = color.rgb(int(args[1])/255.0, int(args[2])/255.0, int(args[3])/255.0)
990 elif args[0]=="texnamed":
991 try:
992 c = getattr(color.cmyk, args[1])
993 except AttributeError:
994 raise RuntimeError("unknown TeX color '%s', aborting" % args[1])
995 else:
996 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args[0])
997 self.actpage.insert(_savecolor())
998 self.actpage.insert(c)
999 elif command=="color_end":
1000 self.actpage.insert(_restorecolor())
1001 elif command=="rotate_begin":
1002 self.actpage.insert(_savetrafo())
1003 self.actpage.insert(trafo.rotate_pt(float(args[0]), x, y))
1004 elif command=="rotate_end":
1005 self.actpage.insert(_restoretrafo())
1006 elif command=="scale_begin":
1007 self.actpage.insert(_savetrafo())
1008 self.actpage.insert(trafo.scale_pt(float(args[0]), float(args[1]), x, y))
1009 elif command=="scale_end":
1010 self.actpage.insert(_restoretrafo())
1011 elif command=="epsinclude":
1012 # parse arguments
1013 argdict = {}
1014 for arg in args:
1015 name, value = arg.split("=")
1016 argdict[name] = value
1018 # construct kwargs for epsfile constructor
1019 epskwargs = {}
1020 epskwargs["filename"] = argdict["file"]
1021 epskwargs["bbox"] = bbox.bbox_pt(float(argdict["llx"]), float(argdict["lly"]),
1022 float(argdict["urx"]), float(argdict["ury"]))
1023 if argdict.has_key("width"):
1024 epskwargs["width"] = float(argdict["width"]) * unit.t_pt
1025 if argdict.has_key("height"):
1026 epskwargs["height"] = float(argdict["height"]) * unit.t_pt
1027 if argdict.has_key("clip"):
1028 epskwargs["clip"] = int(argdict["clip"])
1029 self.actpage.insert(epsfile.epsfile(x * unit.t_pt, y * unit.t_pt, **epskwargs))
1030 elif command=="marker":
1031 if len(args) != 1:
1032 raise RuntimeError("marker contains spaces")
1033 for c in args[0]:
1034 if c not in string.digits + string.letters + "@":
1035 raise RuntimeError("marker contains invalid characters")
1036 if self.actpage.markers.has_key(args[0]):
1037 raise RuntimeError("marker name occurred several times")
1038 self.actpage.markers[args[0]] = x * unit.t_pt, y * unit.t_pt
1039 else:
1040 raise RuntimeError("unknown PyX special '%s', aborting" % command)
1041 self.begintext()
1043 # routines for pushing and popping different dvi chunks on the reader
1045 def _push_dvistring(self, dvi, fonts, afterpos, fontsize):
1046 """ push dvi string with defined fonts on top of reader
1047 stack. Every positions gets scaled relatively by the factor
1048 scale. After the interpreting of the dvi chunk has been finished,
1049 continue with self.pos=afterpos. The designsize of the virtual
1050 font is passed as a fix_word
1054 if self.debug:
1055 print "executing new dvi chunk"
1056 self.statestack.append((self.file, self.fonts, self.activefont, afterpos, self.stack, self.pyxconv, self.tfmconv))
1058 # units in vf files are relative to the size of the font and given as fix_words
1059 # which can be converted to floats by diving by 2**20
1060 oldpyxconv = self.pyxconv
1061 self.pyxconv = fontsize/2**20
1062 rescale = self.pyxconv/oldpyxconv
1064 self.file = stringbinfile(dvi)
1065 self.fonts = fonts
1066 self.stack = []
1067 self.filepos = 0
1069 # rescale self.pos in order to be consistent with the new scaling
1070 self.pos = map(lambda x, rescale=rescale:1.0*x/rescale, self.pos)
1072 # since tfmconv converts from tfm units to dvi units, rescale it as well
1073 self.tfmconv /= rescale
1075 self.usefont(0)
1077 def _pop_dvistring(self):
1078 self.flushout()
1079 if self.debug:
1080 print "finished executing dvi chunk"
1081 self.file.close()
1082 self.file, self.fonts, self.activefont, self.pos, self.stack, self.pyxconv, self.tfmconv = self.statestack.pop()
1084 # routines corresponding to the different reader states of the dvi maschine
1086 def _read_pre(self):
1087 afile = self.file
1088 while 1:
1089 self.filepos = afile.tell()
1090 cmd = afile.readuchar()
1091 if cmd == _DVI_NOP:
1092 pass
1093 elif cmd == _DVI_PRE:
1094 if afile.readuchar() != _DVI_VERSION: raise DVIError
1095 num = afile.readuint32()
1096 den = afile.readuint32()
1097 self.mag = afile.readuint32()
1099 # For the interpretation of the lengths in dvi and tfm files,
1100 # three conversion factors are relevant:
1101 # - self.tfmconv: tfm units -> dvi units
1102 # - self.pyxconv: dvi units -> (PostScript) points
1103 # - self.conv: dvi units -> pixels
1104 self.tfmconv = (25400000.0/num)*(den/473628672.0)/16.0
1106 # calculate conv as described in the DVIType docu using
1107 # a given resolution in dpi
1108 self.resolution = 300.0
1109 self.conv = (num/254000.0)*(self.resolution/den)
1111 # self.pyxconv is the conversion factor from the dvi units
1112 # to (PostScript) points. It consists of
1113 # - self.mag/1000.0: magstep scaling
1114 # - self.conv: conversion from dvi units to pixels
1115 # - 1/self.resolution: conversion from pixels to inch
1116 # - 72 : conversion from inch to points
1117 self.pyxconv = self.mag/1000.0*self.conv/self.resolution*72
1119 comment = afile.read(afile.readuchar())
1120 return
1121 else:
1122 raise DVIError
1124 def readpage(self, pageid=None):
1125 """ reads a page from the dvi file
1127 This routine reads a page from the dvi file which is
1128 returned as a canvas. When there is no page left in the
1129 dvifile, None is returned and the file is closed properly."""
1131 while 1:
1132 self.filepos = self.file.tell()
1133 cmd = self.file.readuchar()
1134 if cmd == _DVI_NOP:
1135 pass
1136 elif cmd == _DVI_BOP:
1137 # self.endtext()
1138 ispageid = [self.file.readuint32() for i in range(10)]
1139 if pageid is not None and ispageid != pageid:
1140 raise DVIError("invalid pageid")
1141 if self.debug:
1142 print "%d: beginning of page %i" % (self.filepos, ispageid[0])
1143 self.file.readuint32()
1144 break
1145 elif cmd == _DVI_POST:
1146 self.file.close()
1147 return None # nothing left
1148 else:
1149 raise DVIError
1151 actpage = canvas.canvas()
1152 self.actpage = actpage # XXX should be removed ...
1153 self.actpage.markers = {}
1154 self.pos = [0, 0, 0, 0, 0, 0]
1155 self.activetype1font = None
1157 # Since we do not know which dvi pages the actual PS file contains later on,
1158 # we have to keep track of used char informations separately for each dvi page.
1159 # In order to do so, the already defined fonts have to be copied and their
1160 # used char informations have to be reset
1161 for nr in self.fonts.keys():
1162 self.fonts[nr] = copy.copy(self.fonts[nr])
1163 self.fonts[nr].clearusedchars()
1165 while 1:
1166 afile = self.file
1167 self.filepos = afile.tell()
1168 try:
1169 cmd = afile.readuchar()
1170 except struct.error:
1171 # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
1172 # so we have to continue with the rest of the dvi file
1173 self._pop_dvistring()
1174 continue
1175 if cmd == _DVI_NOP:
1176 pass
1177 if cmd >= _DVI_CHARMIN and cmd <= _DVI_CHARMAX:
1178 self.putchar(cmd)
1179 elif cmd >= _DVI_SET1234 and cmd < _DVI_SET1234 + 4:
1180 self.putchar(afile.readint(cmd - _DVI_SET1234 + 1))
1181 elif cmd == _DVI_SETRULE:
1182 self.putrule(afile.readint32(), afile.readint32())
1183 elif cmd >= _DVI_PUT1234 and cmd < _DVI_PUT1234 + 4:
1184 self.putchar(afile.readint(cmd - _DVI_PUT1234 + 1), advancepos=0)
1185 elif cmd == _DVI_PUTRULE:
1186 self.putrule(afile.readint32(), afile.readint32(), 0)
1187 elif cmd == _DVI_EOP:
1188 self.endtext()
1189 if self.debug:
1190 print "%d: eop" % self.filepos
1191 print
1192 return actpage
1193 elif cmd == _DVI_PUSH:
1194 self.stack.append(list(self.pos))
1195 if self.debug:
1196 print "%d: push" % self.filepos
1197 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)" %
1198 (( len(self.stack)-1,)+tuple(self.pos)))
1199 elif cmd == _DVI_POP:
1200 self.flushout()
1201 self.pos = self.stack.pop()
1202 if self.debug:
1203 print "%d: pop" % self.filepos
1204 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)" %
1205 (( len(self.stack),)+tuple(self.pos)))
1206 elif cmd >= _DVI_RIGHT1234 and cmd < _DVI_RIGHT1234 + 4:
1207 self.flushout()
1208 dh = afile.readint(cmd - _DVI_RIGHT1234 + 1, 1)
1209 if self.debug:
1210 print ("%d: right%d %d h:=%d%+d=%d, hh:=???" %
1211 (self.filepos,
1212 cmd - _DVI_RIGHT1234 + 1,
1214 self.pos[_POS_H],
1216 self.pos[_POS_H]+dh))
1217 self.pos[_POS_H] += dh
1218 elif cmd == _DVI_W0:
1219 self.flushout()
1220 if self.debug:
1221 print ("%d: w0 %d h:=%d%+d=%d, hh:=???" %
1222 (self.filepos,
1223 self.pos[_POS_W],
1224 self.pos[_POS_H],
1225 self.pos[_POS_W],
1226 self.pos[_POS_H]+self.pos[_POS_W]))
1227 self.pos[_POS_H] += self.pos[_POS_W]
1228 elif cmd >= _DVI_W1234 and cmd < _DVI_W1234 + 4:
1229 self.flushout()
1230 self.pos[_POS_W] = afile.readint(cmd - _DVI_W1234 + 1, 1)
1231 if self.debug:
1232 print ("%d: w%d %d h:=%d%+d=%d, hh:=???" %
1233 (self.filepos,
1234 cmd - _DVI_W1234 + 1,
1235 self.pos[_POS_W],
1236 self.pos[_POS_H],
1237 self.pos[_POS_W],
1238 self.pos[_POS_H]+self.pos[_POS_W]))
1239 self.pos[_POS_H] += self.pos[_POS_W]
1240 elif cmd == _DVI_X0:
1241 self.flushout()
1242 if self.debug:
1243 print ("%d: x0 %d h:=%d%+d=%d, hh:=???" %
1244 (self.filepos,
1245 self.pos[_POS_X],
1246 self.pos[_POS_H],
1247 self.pos[_POS_X],
1248 self.pos[_POS_H]+self.pos[_POS_X]))
1249 self.pos[_POS_H] += self.pos[_POS_X]
1250 elif cmd >= _DVI_X1234 and cmd < _DVI_X1234 + 4:
1251 self.flushout()
1252 self.pos[_POS_X] = afile.readint(cmd - _DVI_X1234 + 1, 1)
1253 if self.debug:
1254 print ("%d: x%d %d h:=%d%+d=%d, hh:=???" %
1255 (self.filepos,
1256 cmd - _DVI_X1234 + 1,
1257 self.pos[_POS_X],
1258 self.pos[_POS_H],
1259 self.pos[_POS_X],
1260 self.pos[_POS_H]+self.pos[_POS_X]))
1261 self.pos[_POS_H] += self.pos[_POS_X]
1262 elif cmd >= _DVI_DOWN1234 and cmd < _DVI_DOWN1234 + 4:
1263 self.flushout()
1264 dv = afile.readint(cmd - _DVI_DOWN1234 + 1, 1)
1265 if self.debug:
1266 print ("%d: down%d %d v:=%d%+d=%d, vv:=???" %
1267 (self.filepos,
1268 cmd - _DVI_DOWN1234 + 1,
1270 self.pos[_POS_V],
1272 self.pos[_POS_V]+dv))
1273 self.pos[_POS_V] += dv
1274 elif cmd == _DVI_Y0:
1275 self.flushout()
1276 if self.debug:
1277 print ("%d: y0 %d v:=%d%+d=%d, vv:=???" %
1278 (self.filepos,
1279 self.pos[_POS_Y],
1280 self.pos[_POS_V],
1281 self.pos[_POS_Y],
1282 self.pos[_POS_V]+self.pos[_POS_Y]))
1283 self.pos[_POS_V] += self.pos[_POS_Y]
1284 elif cmd >= _DVI_Y1234 and cmd < _DVI_Y1234 + 4:
1285 self.flushout()
1286 self.pos[_POS_Y] = afile.readint(cmd - _DVI_Y1234 + 1, 1)
1287 if self.debug:
1288 print ("%d: y%d %d v:=%d%+d=%d, vv:=???" %
1289 (self.filepos,
1290 cmd - _DVI_Y1234 + 1,
1291 self.pos[_POS_Y],
1292 self.pos[_POS_V],
1293 self.pos[_POS_Y],
1294 self.pos[_POS_V]+self.pos[_POS_Y]))
1295 self.pos[_POS_V] += self.pos[_POS_Y]
1296 elif cmd == _DVI_Z0:
1297 self.flushout()
1298 self.pos[_POS_V] += self.pos[_POS_Z]
1299 elif cmd >= _DVI_Z1234 and cmd < _DVI_Z1234 + 4:
1300 self.flushout()
1301 self.pos[_POS_Z] = afile.readint(cmd - _DVI_Z1234 + 1, 1)
1302 self.pos[_POS_V] += self.pos[_POS_Z]
1303 elif cmd >= _DVI_FNTNUMMIN and cmd <= _DVI_FNTNUMMAX:
1304 self.usefont(cmd - _DVI_FNTNUMMIN)
1305 elif cmd >= _DVI_FNT1234 and cmd < _DVI_FNT1234 + 4:
1306 # note that according to the DVI docs, for four byte font numbers,
1307 # the font number is signed. Don't ask why!
1308 self.usefont(afile.readint(cmd - _DVI_FNT1234 + 1, cmd == _DVI_FNT1234 + 3))
1309 elif cmd >= _DVI_SPECIAL1234 and cmd < _DVI_SPECIAL1234 + 4:
1310 self.special(afile.read(afile.readint(cmd - _DVI_SPECIAL1234 + 1)))
1311 elif cmd >= _DVI_FNTDEF1234 and cmd < _DVI_FNTDEF1234 + 4:
1312 if cmd == _DVI_FNTDEF1234:
1313 num = afile.readuchar()
1314 elif cmd == _DVI_FNTDEF1234+1:
1315 num = afile.readuint16()
1316 elif cmd == _DVI_FNTDEF1234+2:
1317 num = afile.readuint24()
1318 elif cmd == _DVI_FNTDEF1234+3:
1319 # Cool, here we have according to docu a signed int. Why?
1320 num = afile.readint32()
1321 self.definefont(cmd-_DVI_FNTDEF1234+1,
1322 num,
1323 afile.readint32(),
1324 afile.readint32(),
1325 afile.readint32(),
1326 afile.read(afile.readuchar()+afile.readuchar()))
1327 else:
1328 raise DVIError
1331 ##############################################################################
1332 # VF file handling
1333 ##############################################################################
1335 _VF_LONG_CHAR = 242 # character packet (long version)
1336 _VF_FNTDEF1234 = _DVI_FNTDEF1234 # font definition
1337 _VF_PRE = _DVI_PRE # preamble
1338 _VF_POST = _DVI_POST # postamble
1340 _VF_ID = 202 # VF id byte
1342 class VFError(exceptions.Exception): pass
1344 class vffile:
1345 def __init__(self, filename, scale, tfmconv, pyxconv, fontmap, debug=0):
1346 self.filename = filename
1347 self.scale = scale
1348 self.tfmconv = tfmconv
1349 self.pyxconv = pyxconv
1350 self.fontmap = fontmap
1351 self.debug = debug
1352 self.fonts = {} # used fonts
1353 self.widths = {} # widths of defined chars
1354 self.chardefs = {} # dvi chunks for defined chars
1356 afile = binfile(self.filename, "rb")
1358 cmd = afile.readuchar()
1359 if cmd == _VF_PRE:
1360 if afile.readuchar() != _VF_ID: raise VFError
1361 comment = afile.read(afile.readuchar())
1362 self.cs = afile.readuint32()
1363 self.ds = afile.readuint32()
1364 else:
1365 raise VFError
1367 while 1:
1368 cmd = afile.readuchar()
1369 if cmd >= _VF_FNTDEF1234 and cmd < _VF_FNTDEF1234 + 4:
1370 # font definition
1371 if cmd == _VF_FNTDEF1234:
1372 num = afile.readuchar()
1373 elif cmd == _VF_FNTDEF1234+1:
1374 num = afile.readuint16()
1375 elif cmd == _VF_FNTDEF1234+2:
1376 num = afile.readuint24()
1377 elif cmd == _VF_FNTDEF1234+3:
1378 num = afile.readint32()
1379 c = afile.readint32()
1380 s = afile.readint32() # relative scaling used for font (fix_word)
1381 d = afile.readint32() # design size of font
1382 fontname = afile.read(afile.readuchar()+afile.readuchar())
1384 # rescaled size of font: s is relative to the scaling
1385 # of the virtual font itself. Note that realscale has
1386 # to be a fix_word (like s)
1387 # XXX: check rounding
1388 reals = int(round(self.scale * (16*self.ds/16777216L) * s))
1390 # print ("defining font %s -- VF scale: %g, VF design size: %d, relative font size: %d => real size: %d" %
1391 # (fontname, self.scale, self.ds, s, reals)
1394 # XXX allow for virtual fonts here too
1395 self.fonts[num] = type1font(fontname, c, reals, d, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
1396 elif cmd == _VF_LONG_CHAR:
1397 # character packet (long form)
1398 pl = afile.readuint32() # packet length
1399 cc = afile.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1400 tfm = afile.readuint24() # character width
1401 dvi = afile.read(pl) # dvi code of character
1402 self.widths[cc] = tfm
1403 self.chardefs[cc] = dvi
1404 elif cmd < _VF_LONG_CHAR:
1405 # character packet (short form)
1406 cc = afile.readuchar() # char code
1407 tfm = afile.readuint24() # character width
1408 dvi = afile.read(cmd)
1409 self.widths[cc] = tfm
1410 self.chardefs[cc] = dvi
1411 elif cmd == _VF_POST:
1412 break
1413 else:
1414 raise VFError
1416 afile.close()
1418 def getfonts(self):
1419 return self.fonts
1421 def getchar(self, cc):
1422 return self.chardefs[cc]