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