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