monkey-patch for mathpazo bug
[PyX/mjg.git] / pyx / dvifile.py
blob9c67414a2e447335235717f3aa63fa3a4cae0954
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 _selectfont(base.PSOp):
405 def __init__(self, name, size):
406 self.name = name
407 self.size = size
409 def outputPS(self, file):
410 file.write("/%s %f selectfont\n" % (self.name, self.size))
413 class selectfont(_selectfont):
414 def __init__(self, font):
415 _selectfont.__init__(self, font.getpsname(), font.getsize())
416 self.font = font
418 def prolog(self):
419 result = [prolog.fontdefinition(self.font.getbasepsname(),
420 self.font.getfontfile(),
421 self.font.getencodingfile(),
422 self.font.usedchars)]
423 if self.font.getencoding():
424 result.append(_ReEncodeFont)
425 result.append(prolog.fontencoding(self.font.getencoding(), self.font.getencodingfile()))
426 result.append(prolog.fontreencoding(self.font.getpsname(), self.font.getbasepsname(), self.font.getencoding()))
427 return result
430 class _show(base.PSCmd):
432 def __init__(self, x, y):
433 self.x = x
434 self.y = y
435 self.width = 0
436 self.height = 0
437 self.depth = 0
438 self.chars = []
440 def addchar(self, width, height, depth, char):
441 self.width += width
442 if height > self.height:
443 self.height = height
444 if depth > self.depth:
445 self.depth = depth
446 self.chars.append(char)
448 def bbox(self):
449 return bbox._bbox(self.x, self.y-self.depth, self.x+self.width, self.y+self.height)
451 def outputPS(self, file):
452 outstring = ""
453 for char in self.chars:
454 if char > 32 and char < 127 and chr(char) not in "()[]<>\\":
455 ascii = "%s" % chr(char)
456 else:
457 ascii = "\\%03o" % char
458 outstring += ascii
459 file.write("%f %f moveto (%s) show\n" % (self.x, self.y, outstring))
462 class fontmapping:
464 tokenpattern = re.compile(r'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
466 def __init__(self, s):
467 """ construct font mapping from line s of font mapping file """
468 self.texname = self.basepsname = self.fontfile = None
470 # standard encoding
471 self.encodingfile = None
473 # supported postscript fragments occuring in psfonts.map
474 self.reencodefont = self.extendfont = self.slantfont = None
476 tokens = []
477 while len(s):
478 match = self.tokenpattern.match(s)
479 if match:
480 if match.groups()[0]:
481 tokens.append('"%s"' % match.groups()[0])
482 else:
483 tokens.append(match.groups()[2])
484 s = s[match.end():]
485 else:
486 raise RuntimeError("wrong syntax")
488 for token in tokens:
489 if token.startswith("<"):
490 if token.startswith("<<"):
491 # XXX: support non-partial download here
492 self.fontfile = token[2:]
493 elif token.startswith("<["):
494 self.encodingfile = token[2:]
495 elif token.endswith(".pfa") or token.endswith(".pfb"):
496 self.fontfile = token[1:]
497 elif token.endswith(".enc"):
498 self.encodingfile = token[1:]
499 else:
500 raise RuntimeError("wrong syntax")
501 elif token.startswith('"'):
502 pscode = token[1:-1].split()
503 # parse standard postscript code fragments
504 while pscode:
505 try:
506 arg, cmd = pscode[:2]
507 except:
508 raise RuntimeError("Unsupported Postscript fragment '%s'" % pscode)
509 pscode = pscode[2:]
510 if cmd == "ReEncodeFont":
511 self.reencodefont = arg
512 elif cmd == "ExtendFont":
513 self.extendfont = arg
514 elif cmd == "SlantFont":
515 self.slantfont = arg
516 else:
517 raise RuntimeError("Unsupported Postscript fragment '%s %s'" % (arg, cmd))
518 else:
519 if self.texname is None:
520 self.texname = token
521 else:
522 self.basepsname = token
523 if self.basepsname is None:
524 self.basepsname = self.texname
526 def __str__(self):
527 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
528 (self.texname, self.basepsname, self.fontfile, repr(self.encodingfile)))
530 # generate fontmap
532 def readfontmap(filenames):
533 """ read font map from filename (without path) """
534 fontmap = {}
535 for filename in filenames:
536 mappath = pykpathsea.find_file(filename, pykpathsea.kpse_fontmap_format)
537 # try also the oft-used registration as dvips config file
538 if not mappath:
539 mappath = pykpathsea.find_file(filename, pykpathsea.kpse_dvips_config_format)
540 if not mappath:
541 raise RuntimeError("cannot find font mapping file '%s'" % filename)
542 mapfile = open(mappath, "r")
543 lineno = 0
544 for line in mapfile.readlines():
545 lineno += 1
546 line = line.rstrip()
547 if not (line=="" or line[0] in (" ", "%", "*", ";" , "#")):
548 try:
549 fm = fontmapping(line)
550 except RuntimeError, e:
551 sys.stderr.write("*** PyX Warning: Ignoring line %i in mapping file '%s': %s\n" % (lineno, filename, e))
552 else:
553 fontmap[fm.texname] = fm
554 mapfile.close()
555 return fontmap
558 class font:
559 def __init__(self, name, c, q, d, debug=0):
560 self.name = name
561 self.q = q # desired size of font (fix_word) in tex points
562 self.d = d # design size of font (fix_word) in tex points
563 tfmpath = pykpathsea.find_file("%s.tfm" % self.name, pykpathsea.kpse_tfm_format)
564 if not tfmpath:
565 raise TFMError("cannot find %s.tfm" % self.name)
566 self.tfmfile = tfmfile(tfmpath, debug)
568 # We only check for equality of font checksums if none of them
569 # is zero. The case c == 0 happend in some VF files and
570 # according to the VFtoVP documentation, paragraph 40, a check
571 # is only performed if tfmfile.checksum > 0. Anyhow, being
572 # more generous here seems to be reasonable
573 if self.tfmfile.checksum != c and self.tfmfile.checksum != 0 and c !=0:
574 raise DVIError("check sums do not agree: %d vs. %d" %
575 (self.tfmfile.checksum, c))
577 # tfmfile.designsizeraw is the design size of the font as a fix_word
578 if abs(self.tfmfile.designsizeraw - d) > 2:
579 raise DVIError("design sizes do not agree: %d vs. %d" % (self.tfmfile.designsizeraw, d))
580 if q < 0 or q > 134217728:
581 raise DVIError("font '%s' not loaded: bad scale" % self.name)
582 if d < 0 or d > 134217728:
583 raise DVIError("font '%s' not loaded: bad design size" % self.name)
585 self.scale = 1.0*q/d
587 # for bookkeeping of used characters
588 self.usedchars = [0] * 256
590 def __str__(self):
591 return "font %s designed at %g tex pts used at %g tex pts" % (self.name,
592 16.0*self.d/16777216L,
593 16.0*self.q/16777216L)
596 __repr__ = __str__
598 def getsize(self):
599 """ return size of font in (PS) points """
600 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
601 # to the corresponding float. Furthermore, we have to convert from TeX
602 # points to points, hence the factor 72/72.27.
603 return 16L*self.q/16777216L*72/72.27
605 def _convert(self, width):
606 return 16L*width*self.q/16777216L
608 def getwidth(self, charcode):
609 return self._convert(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
611 def getheight(self, charcode):
612 return self._convert(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
614 def getdepth(self, charcode):
615 return self._convert(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
617 def getitalic(self, charcode):
618 return self._convert(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
620 def markcharused(self, charcode):
621 self.usedchars[charcode] = 1
623 def mergeusedchars(self, otherfont):
624 for i in range(len(self.usedchars)):
625 self.usedchars[i] = self.usedchars[i] or otherfont.usedchars[i]
627 def clearusedchars(self):
628 self.usedchars = [0] * 256
631 class type1font(font):
632 def __init__(self, name, c, q, d, fontmap, debug=0):
633 font.__init__(self, name, c, q, d, debug)
634 self.fontmapping = fontmap.get(name)
635 if self.fontmapping is None:
636 raise RuntimeError("no information for font '%s' found in font mapping file, aborting" % name)
638 def getbasepsname(self):
639 return self.fontmapping.basepsname
641 def getpsname(self):
642 if self.fontmapping.reencodefont:
643 return "%s-%s" % (self.fontmapping.basepsname, self.fontmapping.reencodefont)
644 else:
645 return self.fontmapping.basepsname
647 def getfontfile(self):
648 return self.fontmapping.fontfile
650 def getencoding(self):
651 return self.fontmapping.reencodefont
653 def getencodingfile(self):
654 return self.fontmapping.encodingfile
657 class virtualfont(font):
658 def __init__(self, name, c, q, d, fontmap, debug=0):
659 font.__init__(self, name, c, q, d, debug)
660 fontpath = pykpathsea.find_file(name, pykpathsea.kpse_vf_format)
661 if fontpath is None or not len(fontpath):
662 raise RuntimeError
663 self.vffile = vffile(fontpath, self.scale, fontmap, debug > 1)
665 def getfonts(self):
666 """ return fonts used in virtual font itself """
667 return self.vffile.getfonts()
669 def getchar(self, cc):
670 """ return dvi chunk corresponding to char code cc """
671 return self.vffile.getchar(cc)
674 ##############################################################################
675 # DVI file handling
676 ##############################################################################
678 _DVI_CHARMIN = 0 # typeset a character and move right (range min)
679 _DVI_CHARMAX = 127 # typeset a character and move right (range max)
680 _DVI_SET1234 = 128 # typeset a character and move right
681 _DVI_SETRULE = 132 # typeset a rule and move right
682 _DVI_PUT1234 = 133 # typeset a character
683 _DVI_PUTRULE = 137 # typeset a rule
684 _DVI_NOP = 138 # no operation
685 _DVI_BOP = 139 # beginning of page
686 _DVI_EOP = 140 # ending of page
687 _DVI_PUSH = 141 # save the current positions (h, v, w, x, y, z)
688 _DVI_POP = 142 # restore positions (h, v, w, x, y, z)
689 _DVI_RIGHT1234 = 143 # move right
690 _DVI_W0 = 147 # move right by w
691 _DVI_W1234 = 148 # move right and set w
692 _DVI_X0 = 152 # move right by x
693 _DVI_X1234 = 153 # move right and set x
694 _DVI_DOWN1234 = 157 # move down
695 _DVI_Y0 = 161 # move down by y
696 _DVI_Y1234 = 162 # move down and set y
697 _DVI_Z0 = 166 # move down by z
698 _DVI_Z1234 = 167 # move down and set z
699 _DVI_FNTNUMMIN = 171 # set current font (range min)
700 _DVI_FNTNUMMAX = 234 # set current font (range max)
701 _DVI_FNT1234 = 235 # set current font
702 _DVI_SPECIAL1234 = 239 # special (dvi extention)
703 _DVI_FNTDEF1234 = 243 # define the meaning of a font number
704 _DVI_PRE = 247 # preamble
705 _DVI_POST = 248 # postamble beginning
706 _DVI_POSTPOST = 249 # postamble ending
708 _DVI_VERSION = 2 # dvi version
710 # position variable indices
711 _POS_H = 0
712 _POS_V = 1
713 _POS_W = 2
714 _POS_X = 3
715 _POS_Y = 4
716 _POS_Z = 5
718 # reader states
719 _READ_PRE = 1
720 _READ_NOPAGE = 2
721 _READ_PAGE = 3
722 _READ_POST = 4 # XXX not used
723 _READ_POSTPOST = 5 # XXX not used
724 _READ_DONE = 6
727 class DVIError(exceptions.Exception): pass
729 # save and restore colors
731 class _savecolor(base.PSOp):
732 def outputPS(self, file):
733 file.write("currentcolor currentcolorspace\n")
736 class _restorecolor(base.PSOp):
737 def outputPS(self, file):
738 file.write("setcolorspace setcolor\n")
740 class _savetrafo(base.PSOp):
741 def outputPS(self, file):
742 file.write("matrix currentmatrix\n")
745 class _restoretrafo(base.PSOp):
746 def outputPS(self, file):
747 file.write("setmatrix\n")
750 class dvifile:
752 def __init__(self, filename, fontmap, debug=0):
753 """ opens the dvi file and reads the preamble """
754 self.filename = filename
755 self.fontmap = fontmap
756 self.debug = debug
758 self.fonts = {}
759 self.activefont = None
761 # stack of fonts and fontscale currently used (used for VFs)
762 self.fontstack = []
763 self.stack = []
765 # pointer to currently active page
766 self.actpage = None
768 # currently active output: show instance being filled and actoutfont
769 self.activeshow = None
770 self.actoutfont = None
772 # stack for self.file, self.fonts and self.stack, needed for VF inclusion
773 self.statestack = []
775 self.file = binfile(self.filename, "rb")
777 # currently read byte in file (for debugging output)
778 self.filepos = None
780 self._read_pre()
782 # helper routines
784 def flushout(self):
785 """ flush currently active string """
786 if self.activeshow is not None:
787 if self.debug:
788 print "[%s]" % "".join([chr(char) for char in self.activeshow.chars])
789 self.actpage.insert(self.activeshow)
790 self.activeshow = None
792 def putrule(self, height, width, advancepos=1):
793 self.flushout()
794 x1 = self.pos[_POS_H] * self.conv
795 y1 = -self.pos[_POS_V] * self.conv
796 w = width * self.conv
797 h = height * self.conv
799 if height > 0 and width > 0:
800 if self.debug:
801 pixelw = int(width*self.trueconv*self.mag/1000.0)
802 if pixelw < width*self.conv: pixelw += 1
803 pixelh = int(height*self.trueconv*self.mag/1000.0)
804 if pixelh < height*self.conv: pixelh += 1
806 print ("%d: %srule height %d, width %d (%dx%d pixels)" %
807 (self.filepos, advancepos and "set" or "put", height, width, pixelh, pixelw))
808 self.actpage.fill(path.rect_pt(x1, y1, w, h))
809 else:
810 if self.debug:
811 print ("%d: %srule height %d, width %d (invisible)" %
812 (self.filepos, advancepos and "set" or "put", height, width))
814 if advancepos:
815 if self.debug:
816 print (" h:=%d+%d=%d, hh:=%d" %
817 (self.pos[_POS_H], width, self.pos[_POS_H]+width, 0))
818 self.pos[_POS_H] += width
820 def putchar(self, char, advancepos=1):
821 dx = advancepos and int(round(self.activefont.getwidth(char)*self.tfmconv)) or 0
823 if self.debug:
824 print ("%d: %schar%d h:=%d+%d=%d, hh:=%d" %
825 (self.filepos,
826 advancepos and "set" or "put",
827 char,
828 self.pos[_POS_H], dx, self.pos[_POS_H]+dx,
831 if isinstance(self.activefont, type1font):
832 if self.activeshow is None:
833 self.activeshow = _show(self.pos[_POS_H] * self.conv, -self.pos[_POS_V] * self.conv)
834 width = self.activefont.getwidth(char) * self.tfmconv * self.conv
835 height = self.activefont.getheight(char) * self.tfmconv * self.conv
836 depth = self.activefont.getdepth(char) * self.tfmconv * self.conv
837 self.activeshow.addchar(width, height, depth, char)
839 self.activefont.markcharused(char)
840 self.pos[_POS_H] += dx
841 else:
842 # virtual font handling
843 afterpos = list(self.pos)
844 afterpos[_POS_H] += dx
845 self._push_dvistring(self.activefont.getchar(char), self.activefont.getfonts(), afterpos,
846 self.activefont.getsize())
849 if not advancepos:
850 # XXX: correct !?
851 self.flushout()
853 def usefont(self, fontnum):
854 if self.debug:
855 print ("%d: fntnum%i current font is %s" %
856 (self.filepos,
857 fontnum, self.fonts[fontnum].name))
859 self.activefont = self.fonts[fontnum]
861 # if the new font is a type 1 font and if it is not already the
862 # font being currently used for the output, we have to flush the
863 # output and insert a selectfont statement in the PS code
864 if isinstance(self.activefont, type1font) and self.actoutfont!=self.activefont:
865 self.flushout()
866 self.actpage.insert(selectfont(self.activefont))
867 self.actoutfont = self.activefont
869 def definefont(self, cmdnr, num, c, q, d, fontname):
870 # cmdnr: type of fontdef command (only used for debugging output)
871 # c: checksum
872 # q: scaling factor (fix_word)
873 # Note that q is actually s in large parts of the documentation.
874 # d: design size (fix_word)
876 try:
877 font = virtualfont(fontname, c, q/self.tfmconv, d/self.tfmconv, self.fontmap, self.debug > 1)
878 except (TypeError, RuntimeError):
879 font = type1font(fontname, c, q/self.tfmconv, d/self.tfmconv, self.fontmap, self.debug > 1)
881 self.fonts[num] = font
883 if self.debug:
884 print "%d: fntdef%d %i: %s" % (self.filepos, cmdnr, num, fontname)
886 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
887 # m = 1.0*q/d
888 # scalestring = scale!=1000 and " scaled %d" % scale or ""
889 # print ("Font %i: %s%s---loaded at size %d DVI units" %
890 # (num, fontname, scalestring, q))
891 # if scale!=1000:
892 # print " (this font is magnified %d%%)" % round(scale/10)
894 def special(self, s):
895 self.flushout()
897 # it is in general not safe to continue using the currently active font because
898 # the specials may involve some gsave/grestore operations
899 # XXX: reset actoutfont only where strictly needed
900 self.actoutfont = None
902 x = self.pos[_POS_H] * self.conv
903 y = -self.pos[_POS_V] * self.conv
904 if self.debug:
905 print "%d: xxx '%s'" % (self.filepos, s)
906 if not s.startswith("PyX:"):
907 if s.startswith("Warning:"):
908 sys.stderr.write("*** PyX Warning: ignoring special '%s'\n" % s)
909 return
910 else:
911 raise RuntimeError("the special '%s' cannot be handled by PyX, aborting" % s)
912 command, args = s[4:].split()[0], s[4:].split()[1:]
913 if command=="color_begin":
914 if args[0]=="cmyk":
915 c = color.cmyk(float(args[1]), float(args[2]), float(args[3]), float(args[4]))
916 elif args[0]=="gray":
917 c = color.gray(float(args[1]))
918 elif args[0]=="hsb":
919 c = color.hsb(float(args[1]), float(args[2]), float(args[3]))
920 elif args[0]=="rgb":
921 c = color.rgb(float(args[1]), float(args[2]), float(args[3]))
922 elif args[0]=="RGB":
923 c = color.rgb(int(args[1])/255.0, int(args[2])/255.0, int(args[3])/255.0)
924 elif args[0]=="texnamed":
925 try:
926 c = getattr(color.cmyk, args[1])
927 except AttributeError:
928 raise RuntimeError("unknown TeX color '%s', aborting" % args[1])
929 else:
930 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args[0])
931 self.actpage.insert(_savecolor())
932 self.actpage.insert(c)
933 elif command=="color_end":
934 self.actpage.insert(_restorecolor())
935 elif command=="rotate_begin":
936 self.actpage.insert(_savetrafo())
937 self.actpage.insert(trafo.rotate_pt(float(args[0]), x, y))
938 elif command=="rotate_end":
939 self.actpage.insert(_restoretrafo())
940 elif command=="scale_begin":
941 self.actpage.insert(_savetrafo())
942 self.actpage.insert(trafo.scale_pt(float(args[0]), float(args[1]), x, y))
943 elif command=="scale_end":
944 self.actpage.insert(_restoretrafo())
945 elif command=="epsinclude":
946 # parse arguments
947 argdict = {}
948 for arg in args:
949 name, value = arg.split("=")
950 argdict[name] = value
952 # construct kwargs for epsfile constructor
953 epskwargs = {}
954 epskwargs["filename"] = argdict["file"]
955 epskwargs["bbox"] = bbox._bbox(float(argdict["llx"]), float(argdict["lly"]),
956 float(argdict["urx"]), float(argdict["ury"]))
957 if argdict.has_key("width"):
958 epskwargs["width"] = unit.t_pt(float(argdict["width"]))
959 if argdict.has_key("height"):
960 epskwargs["height"] = unit.t_pt(float(argdict["height"]))
961 if argdict.has_key("clip"):
962 epskwargs["clip"] = int(argdict["clip"])
963 self.actpage.insert(epsfile.epsfile(unit.t_pt(x), unit.t_pt(y), **epskwargs))
964 elif command=="marker":
965 if len(args) != 1:
966 raise RuntimeError("marker contains spaces")
967 for c in args[0]:
968 if c not in string.digits + string.letters + "@":
969 raise RuntimeError("marker contains invalid characters")
970 if self.actpage.markers.has_key(args[0]):
971 raise RuntimeError("marker name occurred several times")
972 self.actpage.markers[args[0]] = unit.t_pt(x), unit.t_pt(y)
973 else:
974 raise RuntimeError("unknown PyX special '%s', aborting" % command)
976 # routines for pushing and popping different dvi chunks on the reader
978 def _push_dvistring(self, dvi, fonts, afterpos, fontsize):
979 """ push dvi string with defined fonts on top of reader
980 stack. Every positions gets scaled relatively by the factor
981 scale. After the interpreting of the dvi chunk has been finished,
982 continue with self.pos=afterpos. The designsize of the virtual
983 font is passed as a fix_word
987 if self.debug:
988 print "executing new dvi chunk"
989 self.statestack.append((self.file, self.fonts, self.activefont, afterpos, self.stack, self.conv, self.tfmconv))
991 # units in vf files are relative to the size of the font and given as fix_words
992 # which can be converted to floats by diving by 2**20
993 oldconv = self.conv
994 self.conv = fontsize/2**20
995 rescale = self.conv/oldconv
997 self.file = stringbinfile(dvi)
998 self.fonts = fonts
999 self.stack = []
1000 self.filepos = 0
1002 # rescale self.pos in order to be consistent with the new scaling
1003 self.pos = map(lambda x, rescale=rescale:1.0*x/rescale, self.pos)
1005 # since tfmconv converts from tfm units to dvi units, rescale it as well
1006 self.tfmconv /= rescale
1008 self.usefont(0)
1010 def _pop_dvistring(self):
1011 self.flushout()
1012 if self.debug:
1013 print "finished executing dvi chunk"
1014 self.file.close()
1015 self.file, self.fonts, self.activefont, self.pos, self.stack, self.conv, self.tfmconv = self.statestack.pop()
1017 # routines corresponding to the different reader states of the dvi maschine
1019 def _read_pre(self):
1020 afile = self.file
1021 while 1:
1022 self.filepos = afile.tell()
1023 cmd = afile.readuchar()
1024 if cmd == _DVI_NOP:
1025 pass
1026 elif cmd == _DVI_PRE:
1027 if afile.readuchar() != _DVI_VERSION: raise DVIError
1028 num = afile.readuint32()
1029 den = afile.readuint32()
1030 self.mag = afile.readuint32()
1032 # for the interpretation of all quantities, two conversion factors
1033 # are relevant:
1034 # - self.tfmconv (tfm units->dvi units)
1035 # - self.conv (dvi units-> (PostScript) points)
1037 self.tfmconv = (25400000.0/num)*(den/473628672.0)/16.0
1039 # calculate self.conv as described in the DVIType docu
1041 # resolution in dpi
1042 self.resolution = 300.0
1043 # self.trueconv = conv in DVIType docu
1044 self.trueconv = (num/254000.0)*(self.resolution/den)
1046 # self.conv is the conversion factor from the dvi units
1047 # to (PostScript) points. It consists of
1048 # - self.mag/1000.0: magstep scaling
1049 # - self.trueconv: conversion from dvi units to pixels
1050 # - 1/self.resolution: conversion from pixels to inch
1051 # - 72 : conversion from inch to points
1052 self.conv = self.mag/1000.0*self.trueconv/self.resolution*72
1054 comment = afile.read(afile.readuchar())
1055 return
1056 else:
1057 raise DVIError
1059 def readpage(self, pageid):
1060 """ reads a page from the dvi file
1062 This routine reads a page from the dvi file which is
1063 returned as a canvas. When there is no page left in the
1064 dvifile, None is returned and the file is closed properly."""
1067 while 1:
1068 self.filepos = self.file.tell()
1069 cmd = self.file.readuchar()
1070 if cmd == _DVI_NOP:
1071 pass
1072 elif cmd == _DVI_BOP:
1073 self.flushout()
1074 ispageid = [self.file.readuint32() for i in range(10)]
1075 #if ispageid[:3] != [ord("P"), ord("y"), ord("X")] or ispageid[4:] != [0, 0, 0, 0, 0, 0]:
1076 if pageid is not None and ispageid != pageid:
1077 raise DVIError("invalid pageid")
1078 if self.debug:
1079 print "%d: beginning of page %i" % (self.filepos, ispageid[0])
1080 self.file.readuint32()
1081 break
1082 elif cmd == _DVI_POST:
1083 self.file.close()
1084 return None # nothing left
1085 else:
1086 raise DVIError
1088 actpage = canvas.canvas()
1089 self.actpage = actpage # XXX should be removed ...
1090 self.actpage.markers = {}
1091 self.pos = [0, 0, 0, 0, 0, 0]
1092 self.actoutfont = None
1094 # Since we do not know which dvi pages the actual PS file contains later on,
1095 # we have to keep track of used char informations separately for each dvi page.
1096 # In order to do so, the already defined fonts have to be copied and their
1097 # used char informations have to be reset
1098 for nr in self.fonts.keys():
1099 self.fonts[nr] = copy.copy(self.fonts[nr])
1100 self.fonts[nr].clearusedchars()
1102 while 1:
1103 afile = self.file
1104 self.filepos = afile.tell()
1105 try:
1106 cmd = afile.readuchar()
1107 except struct.error:
1108 # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
1109 # so we have to continue with the rest of the dvi file
1110 self._pop_dvistring()
1111 continue
1112 if cmd == _DVI_NOP:
1113 pass
1114 if cmd >= _DVI_CHARMIN and cmd <= _DVI_CHARMAX:
1115 self.putchar(cmd)
1116 elif cmd >= _DVI_SET1234 and cmd < _DVI_SET1234 + 4:
1117 self.putchar(afile.readint(cmd - _DVI_SET1234 + 1))
1118 elif cmd == _DVI_SETRULE:
1119 self.putrule(afile.readint32(), afile.readint32())
1120 elif cmd >= _DVI_PUT1234 and cmd < _DVI_PUT1234 + 4:
1121 self.putchar(afile.readint(cmd - _DVI_PUT1234 + 1), advancepos=0)
1122 elif cmd == _DVI_PUTRULE:
1123 self.putrule(afile.readint32(), afile.readint32(), 0)
1124 elif cmd == _DVI_EOP:
1125 self.flushout()
1126 if self.debug:
1127 print "%d: eop" % self.filepos
1128 print
1129 return actpage
1130 elif cmd == _DVI_PUSH:
1131 self.stack.append(list(self.pos))
1132 if self.debug:
1133 print "%d: push" % self.filepos
1134 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
1135 (( len(self.stack)-1,)+tuple(self.pos)))
1136 elif cmd == _DVI_POP:
1137 self.flushout()
1138 self.pos = self.stack.pop()
1139 if self.debug:
1140 print "%d: pop" % self.filepos
1141 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
1142 (( len(self.stack),)+tuple(self.pos)))
1143 elif cmd >= _DVI_RIGHT1234 and cmd < _DVI_RIGHT1234 + 4:
1144 self.flushout()
1145 dh = afile.readint(cmd - _DVI_RIGHT1234 + 1, 1)
1146 if self.debug:
1147 print ("%d: right%d %d h:=%d%+d=%d, hh:=" %
1148 (self.filepos,
1149 cmd - _DVI_RIGHT1234 + 1,
1151 self.pos[_POS_H],
1153 self.pos[_POS_H]+dh))
1154 self.pos[_POS_H] += dh
1155 elif cmd == _DVI_W0:
1156 self.flushout()
1157 if self.debug:
1158 print ("%d: w0 %d h:=%d%+d=%d, hh:=" %
1159 (self.filepos,
1160 self.pos[_POS_W],
1161 self.pos[_POS_H],
1162 self.pos[_POS_W],
1163 self.pos[_POS_H]+self.pos[_POS_W]))
1164 self.pos[_POS_H] += self.pos[_POS_W]
1165 elif cmd >= _DVI_W1234 and cmd < _DVI_W1234 + 4:
1166 self.flushout()
1167 self.pos[_POS_W] = afile.readint(cmd - _DVI_W1234 + 1, 1)
1168 if self.debug:
1169 print ("%d: w%d %d h:=%d%+d=%d, hh:=" %
1170 (self.filepos,
1171 cmd - _DVI_W1234 + 1,
1172 self.pos[_POS_W],
1173 self.pos[_POS_H],
1174 self.pos[_POS_W],
1175 self.pos[_POS_H]+self.pos[_POS_W]))
1176 self.pos[_POS_H] += self.pos[_POS_W]
1177 elif cmd == _DVI_X0:
1178 self.flushout()
1179 self.pos[_POS_H] += self.pos[_POS_X]
1180 elif cmd >= _DVI_X1234 and cmd < _DVI_X1234 + 4:
1181 self.flushout()
1182 self.pos[_POS_X] = afile.readint(cmd - _DVI_X1234 + 1, 1)
1183 self.pos[_POS_H] += self.pos[_POS_X]
1184 elif cmd >= _DVI_DOWN1234 and cmd < _DVI_DOWN1234 + 4:
1185 self.flushout()
1186 dv = afile.readint(cmd - _DVI_DOWN1234 + 1, 1)
1187 if self.debug:
1188 print ("%d: down%d %d v:=%d%+d=%d, vv:=" %
1189 (self.filepos,
1190 cmd - _DVI_DOWN1234 + 1,
1192 self.pos[_POS_V],
1194 self.pos[_POS_V]+dv))
1195 self.pos[_POS_V] += dv
1196 elif cmd == _DVI_Y0:
1197 self.flushout()
1198 if self.debug:
1199 print ("%d: y0 %d v:=%d%+d=%d, vv:=" %
1200 (self.filepos,
1201 self.pos[_POS_Y],
1202 self.pos[_POS_V],
1203 self.pos[_POS_Y],
1204 self.pos[_POS_V]+self.pos[_POS_Y]))
1205 self.pos[_POS_V] += self.pos[_POS_Y]
1206 elif cmd >= _DVI_Y1234 and cmd < _DVI_Y1234 + 4:
1207 self.flushout()
1208 self.pos[_POS_Y] = afile.readint(cmd - _DVI_Y1234 + 1, 1)
1209 if self.debug:
1210 print ("%d: y%d %d v:=%d%+d=%d, vv:=" %
1211 (self.filepos,
1212 cmd - _DVI_Y1234 + 1,
1213 self.pos[_POS_Y],
1214 self.pos[_POS_V],
1215 self.pos[_POS_Y],
1216 self.pos[_POS_V]+self.pos[_POS_Y]))
1217 self.pos[_POS_V] += self.pos[_POS_Y]
1218 elif cmd == _DVI_Z0:
1219 self.flushout()
1220 self.pos[_POS_V] += self.pos[_POS_Z]
1221 elif cmd >= _DVI_Z1234 and cmd < _DVI_Z1234 + 4:
1222 self.flushout()
1223 self.pos[_POS_Z] = afile.readint(cmd - _DVI_Z1234 + 1, 1)
1224 self.pos[_POS_V] += self.pos[_POS_Z]
1225 elif cmd >= _DVI_FNTNUMMIN and cmd <= _DVI_FNTNUMMAX:
1226 self.usefont(cmd - _DVI_FNTNUMMIN)
1227 elif cmd >= _DVI_FNT1234 and cmd < _DVI_FNT1234 + 4:
1228 self.usefont(afile.readint(cmd - _DVI_FNT1234 + 1, 0))
1229 elif cmd >= _DVI_SPECIAL1234 and cmd < _DVI_SPECIAL1234 + 4:
1230 self.special(afile.read(afile.readint(cmd - _DVI_SPECIAL1234 + 1)))
1231 elif cmd >= _DVI_FNTDEF1234 and cmd < _DVI_FNTDEF1234 + 4:
1232 if cmd == _DVI_FNTDEF1234:
1233 num = afile.readuchar()
1234 elif cmd == _DVI_FNTDEF1234+1:
1235 num = afile.readuint16()
1236 elif cmd == _DVI_FNTDEF1234+2:
1237 num = afile.readuint24()
1238 elif cmd == _DVI_FNTDEF1234+3:
1239 # Cool, here we have according to docu a signed int. Why?
1240 num = afile.readint32()
1241 self.definefont(cmd-_DVI_FNTDEF1234+1,
1242 num,
1243 afile.readint32(),
1244 afile.readint32(),
1245 afile.readint32(),
1246 afile.read(afile.readuchar()+afile.readuchar()))
1247 else:
1248 raise DVIError
1251 ##############################################################################
1252 # VF file handling
1253 ##############################################################################
1255 _VF_LONG_CHAR = 242 # character packet (long version)
1256 _VF_FNTDEF1234 = _DVI_FNTDEF1234 # font definition
1257 _VF_PRE = _DVI_PRE # preamble
1258 _VF_POST = _DVI_POST # postamble
1260 _VF_ID = 202 # VF id byte
1262 class VFError(exceptions.Exception): pass
1264 class vffile:
1265 def __init__(self, filename, scale, fontmap, debug=0):
1266 self.filename = filename
1267 self.scale = scale
1268 self.fontmap = fontmap
1269 self.debug = debug
1270 self.fonts = {} # used fonts
1271 self.widths = {} # widths of defined chars
1272 self.chardefs = {} # dvi chunks for defined chars
1274 afile = binfile(self.filename, "rb")
1276 cmd = afile.readuchar()
1277 if cmd == _VF_PRE:
1278 if afile.readuchar() != _VF_ID: raise VFError
1279 comment = afile.read(afile.readuchar())
1280 self.cs = afile.readuint32()
1281 self.ds = afile.readuint32()
1282 else:
1283 raise VFError
1285 while 1:
1286 cmd = afile.readuchar()
1287 if cmd >= _VF_FNTDEF1234 and cmd < _VF_FNTDEF1234 + 4:
1288 # font definition
1289 if cmd == _VF_FNTDEF1234:
1290 num = afile.readuchar()
1291 elif cmd == _VF_FNTDEF1234+1:
1292 num = afile.readuint16()
1293 elif cmd == _VF_FNTDEF1234+2:
1294 num = afile.readuint24()
1295 elif cmd == _VF_FNTDEF1234+3:
1296 num = afile.readint32()
1297 c = afile.readint32()
1298 s = afile.readint32() # relative scaling used for font (fix_word)
1299 d = afile.readint32() # design size of font
1300 fontname = afile.read(afile.readuchar()+afile.readuchar())
1302 # rescaled size of font: s is relative to the scaling
1303 # of the virtual font itself. Note that realscale has
1304 # to be a fix_word (like s)
1305 # Furthermore we have to correct for self.tfmconv
1307 reals = int(self.scale * float(fix_word(self.ds))*s)
1308 # print ("defining font %s -- VF scale: %g, VF design size: %g, relative font size: %g => real size: %g" %
1309 # (fontname, self.scale, fix_word(self.ds), fix_word(s), fix_word(reals))
1311 # reald = int(d)
1313 # XXX allow for virtual fonts here too
1314 self.fonts[num] = type1font(fontname, c, reals, d, self.fontmap, self.debug > 1)
1315 elif cmd == _VF_LONG_CHAR:
1316 # character packet (long form)
1317 pl = afile.readuint32() # packet length
1318 cc = afile.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1319 tfm = afile.readuint24() # character width
1320 dvi = afile.read(pl) # dvi code of character
1321 self.widths[cc] = tfm
1322 self.chardefs[cc] = dvi
1323 elif cmd < _VF_LONG_CHAR:
1324 # character packet (short form)
1325 cc = afile.readuchar() # char code
1326 tfm = afile.readuint24() # character width
1327 dvi = afile.read(cmd)
1328 self.widths[cc] = tfm
1329 self.chardefs[cc] = dvi
1330 elif cmd == _VF_POST:
1331 break
1332 else:
1333 raise VFError
1335 afile.close()
1337 def getfonts(self):
1338 return self.fonts
1340 def getchar(self, cc):
1341 return self.chardefs[cc]