pathaxis direction added
[PyX.git] / pyx / dvifile.py
blobb588e15ddfc15d82e68910e0aa448f807eee4c12
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2003 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2002-2003 André Wobst <wobsta@users.sourceforge.net>
7 # Copyright (C) 2003 Michael Schindler <m-schindler@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)
262 for charcode in range(self.bc, self.ec+1):
263 self.char_info[charcode] = char_info_word(self.file.readint32())
264 if self.char_info[charcode].width_index == 0:
265 # disable character if width_index is zero
266 self.char_info[charcode] = None
269 # read widths
272 self.width = [None for width_index in range(self.nw)]
273 for width_index in range(self.nw):
274 # self.width[width_index] = fix_word(self.file.readint32())
275 self.width[width_index] = self.file.readint32()
278 # read heights
281 self.height = [None for height_index in range(self.nh)]
282 for height_index in range(self.nh):
283 # self.height[height_index] = fix_word(self.file.readint32())
284 self.height[height_index] = self.file.readint32()
287 # read depths
290 self.depth = [None for depth_index in range(self.nd)]
291 for depth_index in range(self.nd):
292 # self.depth[depth_index] = fix_word(self.file.readint32())
293 self.depth[depth_index] = self.file.readint32()
296 # read italic
299 self.italic = [None for italic_index in range(self.ni)]
300 for italic_index in range(self.ni):
301 # self.italic[italic_index] = fix_word(self.file.readint32())
302 self.italic[italic_index] = self.file.readint32()
305 # read lig_kern
308 # XXX decode to lig_kern_command
310 self.lig_kern = [None for lig_kern_index in range(self.nl)]
311 for lig_kern_index in range(self.nl):
312 self.lig_kern[lig_kern_index] = self.file.readint32()
315 # read kern
318 self.kern = [None for kern_index in range(self.nk)]
319 for kern_index in range(self.nk):
320 # self.kern[kern_index] = fix_word(self.file.readint32())
321 self.kern[kern_index] = self.file.readint32()
324 # read exten
327 # XXX decode to extensible_recipe
329 self.exten = [None for exten_index in range(self.ne)]
330 for exten_index in range(self.ne):
331 self.exten[exten_index] = self.file.readint32()
334 # read param
337 # XXX decode
339 self.param = [None for param_index in range(self.np)]
340 for param_index in range(self.np):
341 self.param[param_index] = self.file.readint32()
343 self.file.close()
346 # class FontEncoding:
348 # def __init__(self, filename):
349 # """ font encoding contained in filename """
350 # encpath = pykpathsea.find_file(filename, pykpathsea.kpse_tex_ps_header_format)
351 # encfile = tokenfile(encpath)
353 # # name of encoding
354 # self.encname = encfile.gettoken()
355 # token = encfile.gettoken()
356 # if token != "[":
357 # raise RuntimeError("cannot parse encoding file '%s', expecting '[' got '%s'" % (filename, token))
358 # self.encvector = []
359 # for i in range(256):
360 # token = encfile.gettoken()
361 # if token is None or token=="]":
362 # raise RuntimeError("not enough charcodes in encoding file '%s'" % filename)
363 # self.encvector.append(token)
364 # if encfile.gettoken() != "]":
365 # raise RuntimeError("too many charcodes in encoding file '%s'" % filename)
366 # token = encfile.gettoken()
367 # if token != "def":
368 # raise RuntimeError("cannot parse encoding file '%s', expecting 'def' got '%s'" % (filename, token))
369 # token = encfile.gettoken()
370 # if token != None:
371 # raise RuntimeError("encoding file '%s' too long" % filename)
372 # encfile.close()
374 # def encode(self, charcode):
375 # return self.encvector[charcode]
377 ##############################################################################
378 # Font handling
379 ##############################################################################
381 _ReEncodeFont = prolog.definition("ReEncodeFont", """{
382 5 dict
383 begin
384 /newencoding exch def
385 /newfontname exch def
386 /basefontname exch def
387 /basefontdict basefontname findfont def
388 /newfontdict basefontdict maxlength dict def
389 basefontdict {
390 exch dup dup /FID ne exch /Encoding ne and
391 { exch newfontdict 3 1 roll put }
392 { pop pop }
393 ifelse
394 } forall
395 newfontdict /FontName newfontname put
396 newfontdict /Encoding newencoding put
397 newfontname newfontdict definefont pop
399 }""")
402 # PostScript font selection and output primitives
405 class _selectfont(base.PSOp):
406 def __init__(self, name, size):
407 self.name = name
408 self.size = size
410 def write(self, file):
411 file.write("/%s %f selectfont\n" % (self.name, self.size))
414 class selectfont(_selectfont):
415 def __init__(self, font):
416 _selectfont.__init__(self, font.getpsname(), font.getsize())
417 self.font = font
419 def prolog(self):
420 result = [prolog.fontdefinition(self.font.getbasepsname(),
421 self.font.getfontfile(),
422 self.font.getencodingfile(),
423 self.font.usedchars)]
424 if self.font.getencoding():
425 result.append(_ReEncodeFont)
426 result.append(prolog.fontencoding(self.font.getencoding(), self.font.getencodingfile()))
427 result.append(prolog.fontreencoding(self.font.getpsname(), self.font.getbasepsname(), self.font.getencoding()))
428 return result
431 class _show(base.PSOp):
432 def __init__(self, x, y, s):
433 self.x = x
434 self.y = y
435 self.s = s
437 def write(self, file):
438 file.write("%f %f moveto (%s) show\n" % (self.x, self.y, self.s))
441 class fontmapping:
443 tokenpattern = re.compile(r'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
445 def __init__(self, s):
446 """ construct font mapping from line s of font mapping file """
447 self.texname = self.basepsname = self.fontfile = None
449 # standard encoding
450 self.encodingfile = None
452 # supported postscript fragments occuring in psfonts.map
453 self.reencodefont = self.extendfont = self.slantfont = None
455 tokens = []
456 while len(s):
457 match = self.tokenpattern.match(s)
458 if match:
459 if match.groups()[0]:
460 tokens.append('"%s"' % match.groups()[0])
461 else:
462 tokens.append(match.groups()[2])
463 s = s[match.end():]
464 else:
465 raise RuntimeError("wrong syntax")
467 for token in tokens:
468 if token.startswith("<"):
469 if token.startswith("<<"):
470 # XXX: support non-partial download here
471 self.fontfile = token[2:]
472 elif token.startswith("<["):
473 self.encodingfile = token[2:]
474 elif token.endswith(".pfa") or token.endswith(".pfb"):
475 self.fontfile = token[1:]
476 elif token.endswith(".enc"):
477 self.encodingfile = token[1:]
478 else:
479 raise RuntimeError("wrong syntax")
480 elif token.startswith('"'):
481 pscode = token[1:-1].split()
482 # parse standard postscript code fragments
483 while pscode:
484 try:
485 arg, cmd = pscode[:2]
486 except:
487 raise RuntimeError("Unsupported Postscript fragment '%s'" % pscode)
488 pscode = pscode[2:]
489 if cmd == "ReEncodeFont":
490 self.reencodefont = arg
491 elif cmd == "ExtendFont":
492 self.extendfont = arg
493 elif cmd == "SlantFont":
494 self.slantfont = arg
495 else:
496 raise RuntimeError("Unsupported Postscript fragment '%s %s'" % (arg, cmd))
497 else:
498 if self.texname is None:
499 self.texname = token
500 else:
501 self.basepsname = token
502 if self.basepsname is None:
503 self.basepsname = self.texname
505 def __str__(self):
506 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
507 (self.texname, self.basepsname, self.fontfile, repr(self.encodingfile)))
509 # generate fontmap
511 def readfontmap(filenames):
512 """ read font map from filename (without path) """
513 fontmap = {}
514 for filename in filenames:
515 mappath = pykpathsea.find_file(filename, pykpathsea.kpse_dvips_config_format)
516 if not mappath:
517 raise RuntimeError("cannot find font mapping file '%s'" % filename)
518 mapfile = open(mappath, "r")
519 lineno = 0
520 for line in mapfile.readlines():
521 lineno += 1
522 line = line.rstrip()
523 if not (line=="" or line[0] in (" ", "%", "*", ";" , "#")):
524 try:
525 fm = fontmapping(line)
526 except RuntimeError, e:
527 sys.stderr.write("*** PyX Warning: Ignoring line %i in mapping file '%s': %s\n" % (lineno, filename, e))
528 else:
529 fontmap[fm.texname] = fm
530 mapfile.close()
531 return fontmap
534 class font:
535 def __init__(self, name, c, q, d, debug=0):
536 self.name = name
537 self.q = q # desired size of font (fix_word) in tex points
538 self.d = d # design size of font (fix_word) in tex points
539 tfmpath = pykpathsea.find_file("%s.tfm" % self.name, pykpathsea.kpse_tfm_format)
540 if not tfmpath:
541 raise TFMError("cannot find %s.tfm" % self.name)
542 self.tfmfile = tfmfile(tfmpath, debug)
544 # We only check for equality of font checksums if none of them
545 # is zero. The case c == 0 happend in some VF files and
546 # according to the VFtoVP documentation, paragraph 40, a check
547 # is only performed if tfmfile.checksum > 0. Anyhow, begin
548 # more generous here seems to be reasonable
549 if self.tfmfile.checksum != c and self.tfmfile.checksum != 0 and c !=0:
550 raise DVIError("check sums do not agree: %d vs. %d" %
551 (self.tfmfile.checksum, c))
553 # tfmfile.designsizeraw is the design size of the font as a fix_word
554 if abs(self.tfmfile.designsizeraw - d) > 2:
555 raise DVIError("design sizes do not agree: %d vs. %d" % (tfmdesignsize, d))
556 if q < 0 or q > 134217728:
557 raise DVIError("font '%s' not loaded: bad scale" % self.name)
558 if d < 0 or d > 134217728:
559 raise DVIError("font '%s' not loaded: bad design size" % self.name)
561 self.scale = 1.0*q/d
563 # for bookkeeping of used characters
564 self.usedchars = [0] * 256
566 def __str__(self):
567 return "font %s designed at %g tex pts used at %g tex pts" % (self.name,
568 16.0*self.d/16777216L,
569 16.0*self.q/16777216L)
572 __repr__ = __str__
574 def getsize(self):
575 """ return size of font in (PS) points """
576 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
577 # to the corresponding float. Furthermore, we have to convert from TeX
578 # points to points, hence the factor 72/72.27.
579 return 16L*self.q/16777216L*72/72.27
581 def _convert(self, width):
582 return 16L*width*self.q/16777216L
584 def getwidth(self, charcode):
585 return self._convert(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
587 def getheight(self, charcode):
588 return self._convert(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
590 def getdepth(self, charcode):
591 return self._convert(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
593 def getitalic(self, charcode):
594 return self._convert(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
596 def markcharused(self, charcode):
597 self.usedchars[charcode] = 1
599 def mergeusedchars(self, otherfont):
600 for i in range(len(self.usedchars)):
601 self.usedchars[i] = self.usedchars[i] or otherfont.usedchars[i]
603 def clearusedchars(self):
604 self.usedchars = [0] * 256
607 class type1font(font):
608 def __init__(self, name, c, q, d, fontmap, debug=0):
609 font.__init__(self, name, c, q, d, debug)
610 self.fontmapping = fontmap.get(name)
611 if self.fontmapping is None:
612 raise RuntimeError("no information for font '%s' found in font mapping file, aborting" % name)
614 def getbasepsname(self):
615 return self.fontmapping.basepsname
617 def getpsname(self):
618 if self.fontmapping.reencodefont:
619 return "%s-%s" % (self.fontmapping.basepsname, self.fontmapping.reencodefont)
620 else:
621 return self.fontmapping.basepsname
623 def getfontfile(self):
624 return self.fontmapping.fontfile
626 def getencoding(self):
627 return self.fontmapping.reencodefont
629 def getencodingfile(self):
630 return self.fontmapping.encodingfile
633 class virtualfont(font):
634 def __init__(self, name, c, q, d, fontmap, debug=0):
635 font.__init__(self, name, c, q, d, debug)
636 fontpath = pykpathsea.find_file(name, pykpathsea.kpse_vf_format)
637 if fontpath is None or not len(fontpath):
638 raise RuntimeError
639 self.vffile = vffile(fontpath, self.scale, fontmap, debug > 1)
641 def getfonts(self):
642 """ return fonts used in virtual font itself """
643 return self.vffile.getfonts()
645 def getchar(self, cc):
646 """ return dvi chunk corresponding to char code cc """
647 return self.vffile.getchar(cc)
650 ##############################################################################
651 # DVI file handling
652 ##############################################################################
654 _DVI_CHARMIN = 0 # typeset a character and move right (range min)
655 _DVI_CHARMAX = 127 # typeset a character and move right (range max)
656 _DVI_SET1234 = 128 # typeset a character and move right
657 _DVI_SETRULE = 132 # typeset a rule and move right
658 _DVI_PUT1234 = 133 # typeset a character
659 _DVI_PUTRULE = 137 # typeset a rule
660 _DVI_NOP = 138 # no operation
661 _DVI_BOP = 139 # beginning of page
662 _DVI_EOP = 140 # ending of page
663 _DVI_PUSH = 141 # save the current positions (h, v, w, x, y, z)
664 _DVI_POP = 142 # restore positions (h, v, w, x, y, z)
665 _DVI_RIGHT1234 = 143 # move right
666 _DVI_W0 = 147 # move right by w
667 _DVI_W1234 = 148 # move right and set w
668 _DVI_X0 = 152 # move right by x
669 _DVI_X1234 = 153 # move right and set x
670 _DVI_DOWN1234 = 157 # move down
671 _DVI_Y0 = 161 # move down by y
672 _DVI_Y1234 = 162 # move down and set y
673 _DVI_Z0 = 166 # move down by z
674 _DVI_Z1234 = 167 # move down and set z
675 _DVI_FNTNUMMIN = 171 # set current font (range min)
676 _DVI_FNTNUMMAX = 234 # set current font (range max)
677 _DVI_FNT1234 = 235 # set current font
678 _DVI_SPECIAL1234 = 239 # special (dvi extention)
679 _DVI_FNTDEF1234 = 243 # define the meaning of a font number
680 _DVI_PRE = 247 # preamble
681 _DVI_POST = 248 # postamble beginning
682 _DVI_POSTPOST = 249 # postamble ending
684 _DVI_VERSION = 2 # dvi version
686 # position variable indices
687 _POS_H = 0
688 _POS_V = 1
689 _POS_W = 2
690 _POS_X = 3
691 _POS_Y = 4
692 _POS_Z = 5
694 # reader states
695 _READ_PRE = 1
696 _READ_NOPAGE = 2
697 _READ_PAGE = 3
698 _READ_POST = 4 # XXX not used
699 _READ_POSTPOST = 5 # XXX not used
700 _READ_DONE = 6
703 class DVIError(exceptions.Exception): pass
705 # save and restore colors
707 class _savecolor(base.PSOp):
708 def write(self, file):
709 file.write("currentcolor currentcolorspace\n")
712 class _restorecolor(base.PSOp):
713 def write(self, file):
714 file.write("setcolorspace setcolor\n")
716 class _savetrafo(base.PSOp):
717 def write(self, file):
718 file.write("matrix currentmatrix\n")
721 class _restoretrafo(base.PSOp):
722 def write(self, file):
723 file.write("setmatrix\n")
726 class dvifile:
728 def __init__(self, filename, fontmap, debug=0):
729 """ opens the dvi file and reads the preamble """
730 self.filename = filename
731 self.fontmap = fontmap
732 self.debug = debug
734 self.fonts = {}
735 self.activefont = None
737 # stack of fonts and fontscale currently used (used for VFs)
738 self.fontstack = []
739 self.stack = []
741 # pointer to currently active page
742 self.actpage = None
744 # currently active output: position, content and type 1 font
745 self.actoutstart = None
746 self.actoutstring = ""
747 self.actoutfont = None
749 # stack for self.file, self.fonts and self.stack, needed for VF inclusion
750 self.statestack = []
752 self.file = binfile(self.filename, "rb")
754 # currently read byte in file (for debugging output)
755 self.filepos = None
757 self._read_pre()
759 # helper routines
761 def flushout(self):
762 """ flush currently active string """
763 if self.actoutstart:
764 x = self.actoutstart[0] * self.conv
765 y = -self.actoutstart[1] * self.conv
766 if self.debug:
767 print "[%s]" % self.actoutstring
768 self.actpage.insert(_show(x, y, self.actoutstring))
769 self.actoutstart = None
771 def putrule(self, height, width, inch=1):
772 self.flushout()
773 x1 = self.pos[_POS_H] * self.conv
774 y1 = -self.pos[_POS_V] * self.conv
775 w = width * self.conv
776 h = height * self.conv
778 if height > 0 and width > 0:
779 if self.debug:
780 pixelw = int(width*self.trueconv*self.mag/1000.0)
781 if pixelw < width*self.conv: pixelw += 1
782 pixelh = int(height*self.trueconv*self.mag/1000.0)
783 if pixelh < height*self.conv: pixelh += 1
785 print ("%d: %srule height %d, width %d (%dx%d pixels)" %
786 (self.filepos, inch and "set" or "put", height, width, pixelh, pixelw))
787 self.actpage.fill(path._rect(x1, y1, w, h))
788 else:
789 if self.debug:
790 print ("%d: %srule height %d, width %d (invisible)" %
791 (self.filepos, inch and "set" or "put", height, width))
793 if inch:
794 if self.debug:
795 print (" h:=%d+%d=%d, hh:=%d" %
796 (self.pos[_POS_H], width, self.pos[_POS_H]+width, 0))
797 self.pos[_POS_H] += width
799 def putchar(self, char, inch=1):
800 dx = inch and int(round(self.activefont.getwidth(char)*self.tfmconv)) or 0
802 if self.debug:
803 print ("%d: %schar%d h:=%d+%d=%d, hh:=%d" %
804 (self.filepos,
805 inch and "set" or "put",
806 char,
807 self.pos[_POS_H], dx, self.pos[_POS_H]+dx,
810 if isinstance(self.activefont, type1font):
811 if self.actoutstart is None:
812 self.actoutstart = self.pos[_POS_H], self.pos[_POS_V]
813 self.actoutstring = ""
814 if char > 32 and char < 127 and chr(char) not in "()[]<>":
815 ascii = "%s" % chr(char)
816 else:
817 ascii = "\\%03o" % char
818 self.actoutstring = self.actoutstring + ascii
820 self.activefont.markcharused(char)
821 self.pos[_POS_H] += dx
822 else:
823 # virtual font handling
824 afterpos = list(self.pos)
825 afterpos[_POS_H] += dx
826 self._push_dvistring(self.activefont.getchar(char), self.activefont.getfonts(), afterpos,
827 self.activefont.getsize())
830 if not inch:
831 # XXX: correct !?
832 self.flushout()
834 def usefont(self, fontnum):
835 if self.debug:
836 print ("%d: fntnum%i current font is %s" %
837 (self.filepos,
838 fontnum, self.fonts[fontnum].name))
840 self.activefont = self.fonts[fontnum]
842 # if the new font is a type 1 font and if it is not already the
843 # font being currently used for the output, we have to flush the
844 # output and insert a selectfont statement in the PS code
845 if isinstance(self.activefont, type1font) and self.actoutfont!=self.activefont:
846 self.flushout()
847 self.actpage.insert(selectfont(self.activefont))
848 self.actoutfont = self.activefont
850 def definefont(self, cmdnr, num, c, q, d, fontname):
851 # cmdnr: type of fontdef command (only used for debugging output)
852 # c: checksum
853 # q: scaling factor (fix_word)
854 # Note that q is actually s in large parts of the documentation.
855 # d: design size (fix_word)
857 try:
858 font = virtualfont(fontname, c, q/self.tfmconv, d/self.tfmconv, self.fontmap, self.debug > 1)
859 except (TypeError, RuntimeError):
860 font = type1font(fontname, c, q/self.tfmconv, d/self.tfmconv, self.fontmap, self.debug > 1)
862 self.fonts[num] = font
864 if self.debug:
865 print "%d: fntdef%d %i: %s" % (self.filepos, cmdnr, num, fontname)
867 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
868 # m = 1.0*q/d
869 # scalestring = scale!=1000 and " scaled %d" % scale or ""
870 # print ("Font %i: %s%s---loaded at size %d DVI units" %
871 # (num, fontname, scalestring, q))
872 # if scale!=1000:
873 # print " (this font is magnified %d%%)" % round(scale/10)
875 def special(self, s):
876 self.flushout()
878 # it is in general not safe to continue using the currently active font because
879 # the specials may involve some gsave/grestore operations
880 # XXX: reset actoutfont only where strictly needed
881 self.actoutfont = None
883 x = self.pos[_POS_H] * self.conv
884 y = -self.pos[_POS_V] * self.conv
885 if self.debug:
886 print "%d: xxx '%s'" % (self.filepos, s)
887 if not s.startswith("PyX:"):
888 if s.startswith("Warning:"):
889 sys.stderr.write("*** PyX Warning: ignoring special '%s'\n" % s)
890 return
891 else:
892 raise RuntimeError("the special '%s' cannot be handled by PyX, aborting" % s)
893 command, args = s[4:].split()[0], s[4:].split()[1:]
894 if command=="color_begin":
895 if args[0]=="cmyk":
896 c = color.cmyk(float(args[1]), float(args[2]), float(args[3]), float(args[4]))
897 elif args[0]=="gray":
898 c = color.gray(float(args[1]))
899 elif args[0]=="hsb":
900 c = color.hsb(float(args[1]), float(args[2]), float(args[3]))
901 elif args[0]=="rgb":
902 c = color.rgb(float(args[1]), float(args[2]), float(args[3]))
903 elif args[0]=="RGB":
904 c = color.rgb(int(args[1])/255.0, int(args[2])/255.0, int(args[3])/255.0)
905 elif args[0]=="texnamed":
906 try:
907 c = getattr(color.cmyk, args[1])
908 except AttributeError:
909 raise RuntimeError("unknown TeX color '%s', aborting" % args[1])
910 else:
911 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args[0])
912 self.actpage.insert(_savecolor())
913 self.actpage.insert(c)
914 elif command=="color_end":
915 self.actpage.insert(_restorecolor())
916 elif command=="rotate_begin":
917 self.actpage.insert(_savetrafo())
918 self.actpage.insert(trafo._rotate(float(args[0]), x, y))
919 elif command=="rotate_end":
920 self.actpage.insert(_restoretrafo())
921 elif command=="scale_begin":
922 self.actpage.insert(_savetrafo())
923 self.actpage.insert(trafo._scale(float(args[0]), float(args[1]), x, y))
924 elif command=="scale_end":
925 self.actpage.insert(_restoretrafo())
926 elif command=="epsinclude":
927 # parse arguments
928 argdict = {}
929 for arg in args:
930 name, value = arg.split("=")
931 argdict[name] = value
933 # construct kwargs for epsfile constructor
934 epskwargs = {}
935 epskwargs["filename"] = argdict["file"]
936 epskwargs["bbox"] = bbox._bbox(float(argdict["llx"]), float(argdict["lly"]),
937 float(argdict["urx"]), float(argdict["ury"]))
938 if argdict.has_key("width"):
939 epskwargs["width"] = unit.t_pt(float(argdict["width"]))
940 if argdict.has_key("height"):
941 epskwargs["height"] = unit.t_pt(float(argdict["height"]))
942 if argdict.has_key("clip"):
943 epskwargs["clip"] = int(argdict["clip"])
944 self.actpage.insert(epsfile.epsfile(unit.t_pt(x), unit.t_pt(y), **epskwargs))
945 elif command=="marker":
946 if len(args) != 1:
947 raise RuntimeError("marker contains spaces")
948 for c in args[0]:
949 if c not in string.digits + string.letters + "@":
950 raise RuntimeError("marker contains invalid characters")
951 if self.actpage.markers.has_key(args[0]):
952 raise RuntimeError("marker name occurred several times")
953 self.actpage.markers[args[0]] = unit.t_pt(x), unit.t_pt(y)
954 else:
955 raise RuntimeError("unknown PyX special '%s', aborting" % command)
957 # routines for pushing and popping different dvi chunks on the reader
959 def _push_dvistring(self, dvi, fonts, afterpos, fontsize):
960 """ push dvi string with defined fonts on top of reader
961 stack. Every positions gets scaled relatively by the factor
962 scale. When finished with interpreting the dvi chunk, we
963 continue with self.pos=afterpos. The designsize of the virtual
964 font is passed as a fix_word
968 if self.debug:
969 print "executing new dvi chunk"
970 self.statestack.append((self.file, self.fonts, self.activefont, afterpos, self.stack, self.conv, self.tfmconv))
972 # units in vf files are relative to the size of the font and given as fix_words
973 # which can be converted to floats by diving by 2**20
974 oldconv = self.conv
975 self.conv = fontsize/2**20
976 rescale = self.conv/oldconv
978 self.file = stringbinfile(dvi)
979 self.fonts = fonts
980 self.stack = []
981 self.filepos = 0
983 # we have to rescale self.pos in order to be consistent with the new scaling
984 self.pos = map(lambda x, rescale=rescale:1.0*x/rescale, self.pos)
986 # since tfmconv converts from tfm units to dvi units, we have to rescale it as well
987 self.tfmconv /= rescale
989 self.usefont(0)
991 def _pop_dvistring(self):
992 self.flushout()
993 if self.debug:
994 print "finished executing dvi chunk"
995 self.file.close()
996 self.file, self.fonts, self.activefont, self.pos, self.stack, self.conv, self.tfmconv = self.statestack.pop()
998 # routines corresponding to the different reader states of the dvi maschine
1000 def _read_pre(self):
1001 file = self.file
1002 while 1:
1003 self.filepos = file.tell()
1004 cmd = file.readuchar()
1005 if cmd == _DVI_NOP:
1006 pass
1007 elif cmd == _DVI_PRE:
1008 if self.file.readuchar() != _DVI_VERSION: raise DVIError
1009 num = file.readuint32()
1010 den = file.readuint32()
1011 self.mag = file.readuint32()
1013 # for the interpretation of all quantities, two conversion factors
1014 # are relevant:
1015 # - self.tfmconv (tfm units->dvi units)
1016 # - self.conv (dvi units-> (PostScript) points)
1018 self.tfmconv = (25400000.0/num)*(den/473628672.0)/16.0
1020 # calculate self.conv as described in the DVIType docu
1022 # resolution in dpi
1023 self.resolution = 300.0
1024 # self.trueconv = conv in DVIType docu
1025 self.trueconv = (num/254000.0)*(self.resolution/den)
1027 # self.conv is the conversion factor from the dvi units
1028 # to (PostScript) points. It consists of
1029 # - self.mag/1000.0: magstep scaling
1030 # - self.trueconv: conversion from dvi units to pixels
1031 # - 1/self.resolution: conversion from pixels to inch
1032 # - 72 : conversion from inch to points
1033 self.conv = self.mag/1000.0*self.trueconv/self.resolution*72
1035 comment = file.read(file.readuchar())
1036 return
1037 else:
1038 raise DVIError
1040 def readpage(self):
1041 """ reads a page from the dvi file
1043 This routine reads a page from the dvi file which is
1044 returned as a canvas. When there is no page left in the
1045 dvifile, None is returned and the file is closed properly."""
1048 while 1:
1049 self.filepos = self.file.tell()
1050 cmd = self.file.readuchar()
1051 if cmd == _DVI_NOP:
1052 pass
1053 elif cmd == _DVI_BOP:
1054 self.flushout()
1055 pagenos = [self.file.readuint32() for i in range(10)]
1056 if pagenos[:3] != [ord("P"), ord("y"), ord("X")] or pagenos[4:] != [0, 0, 0, 0, 0, 0]:
1057 raise DVIError("Page in dvi file is not a PyX page.")
1058 if self.debug:
1059 print "%d: beginning of page %i" % (self.filepos, pagenos[0])
1060 self.file.readuint32()
1061 break
1062 elif cmd == _DVI_POST:
1063 self.file.close()
1064 return None # nothing left
1065 else:
1066 raise DVIError
1068 actpage = canvas.canvas()
1069 self.actpage = actpage # XXX should be removed ...
1070 self.actpage.markers = {}
1071 self.pos = [0, 0, 0, 0, 0, 0]
1072 self.actoutfont = None
1074 # Since we do not know, which dvi pages the actual PS file contains later on,
1075 # we have to keep track of used char informations separately for each dvi page.
1076 # In order to do so, the already defined fonts have to be copied and their
1077 # used char informations have to be reset
1078 for nr in self.fonts.keys():
1079 self.fonts[nr] = copy.copy(self.fonts[nr])
1080 self.fonts[nr].clearusedchars()
1082 while 1:
1083 file = self.file
1084 self.filepos = file.tell()
1085 try:
1086 cmd = file.readuchar()
1087 except struct.error:
1088 # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
1089 # so we have to continue with the rest of the dvi file
1090 self._pop_dvistring()
1091 continue
1092 if cmd == _DVI_NOP:
1093 pass
1094 if cmd >= _DVI_CHARMIN and cmd <= _DVI_CHARMAX:
1095 self.putchar(cmd)
1096 elif cmd >= _DVI_SET1234 and cmd < _DVI_SET1234 + 4:
1097 self.putchar(file.readint(cmd - _DVI_SET1234 + 1))
1098 elif cmd == _DVI_SETRULE:
1099 self.putrule(file.readint32(), file.readint32())
1100 elif cmd >= _DVI_PUT1234 and cmd < _DVI_PUT1234 + 4:
1101 self.putchar(file.readint(cmd - _DVI_PUT1234 + 1), inch=0)
1102 elif cmd == _DVI_PUTRULE:
1103 self.putrule(file.readint32(), file.readint32(), 0)
1104 elif cmd == _DVI_EOP:
1105 self.flushout()
1106 if self.debug:
1107 print "%d: eop" % self.filepos
1108 print
1109 return actpage
1110 elif cmd == _DVI_PUSH:
1111 self.stack.append(list(self.pos))
1112 if self.debug:
1113 print "%d: push" % self.filepos
1114 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
1115 (( len(self.stack)-1,)+tuple(self.pos)))
1116 elif cmd == _DVI_POP:
1117 self.flushout()
1118 self.pos = self.stack.pop()
1119 if self.debug:
1120 print "%d: pop" % self.filepos
1121 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
1122 (( len(self.stack),)+tuple(self.pos)))
1123 elif cmd >= _DVI_RIGHT1234 and cmd < _DVI_RIGHT1234 + 4:
1124 self.flushout()
1125 dh = file.readint(cmd - _DVI_RIGHT1234 + 1, 1)
1126 if self.debug:
1127 print ("%d: right%d %d h:=%d%+d=%d, hh:=" %
1128 (self.filepos,
1129 cmd - _DVI_RIGHT1234 + 1,
1131 self.pos[_POS_H],
1133 self.pos[_POS_H]+dh))
1134 self.pos[_POS_H] += dh
1135 elif cmd == _DVI_W0:
1136 self.flushout()
1137 if self.debug:
1138 print ("%d: w0 %d h:=%d%+d=%d, hh:=" %
1139 (self.filepos,
1140 self.pos[_POS_W],
1141 self.pos[_POS_H],
1142 self.pos[_POS_W],
1143 self.pos[_POS_H]+self.pos[_POS_W]))
1144 self.pos[_POS_H] += self.pos[_POS_W]
1145 elif cmd >= _DVI_W1234 and cmd < _DVI_W1234 + 4:
1146 self.flushout()
1147 self.pos[_POS_W] = file.readint(cmd - _DVI_W1234 + 1, 1)
1148 if self.debug:
1149 print ("%d: w%d %d h:=%d%+d=%d, hh:=" %
1150 (self.filepos,
1151 cmd - _DVI_W1234 + 1,
1152 self.pos[_POS_W],
1153 self.pos[_POS_H],
1154 self.pos[_POS_W],
1155 self.pos[_POS_H]+self.pos[_POS_W]))
1156 self.pos[_POS_H] += self.pos[_POS_W]
1157 elif cmd == _DVI_X0:
1158 self.flushout()
1159 self.pos[_POS_H] += self.pos[_POS_X]
1160 elif cmd >= _DVI_X1234 and cmd < _DVI_X1234 + 4:
1161 self.flushout()
1162 self.pos[_POS_X] = file.readint(cmd - _DVI_X1234 + 1, 1)
1163 self.pos[_POS_H] += self.pos[_POS_X]
1164 elif cmd >= _DVI_DOWN1234 and cmd < _DVI_DOWN1234 + 4:
1165 self.flushout()
1166 dv = file.readint(cmd - _DVI_DOWN1234 + 1, 1)
1167 if self.debug:
1168 print ("%d: down%d %d v:=%d%+d=%d, vv:=" %
1169 (self.filepos,
1170 cmd - _DVI_DOWN1234 + 1,
1172 self.pos[_POS_V],
1174 self.pos[_POS_V]+dv))
1175 self.pos[_POS_V] += dv
1176 elif cmd == _DVI_Y0:
1177 self.flushout()
1178 if self.debug:
1179 print ("%d: y0 %d v:=%d%+d=%d, vv:=" %
1180 (self.filepos,
1181 self.pos[_POS_Y],
1182 self.pos[_POS_V],
1183 self.pos[_POS_Y],
1184 self.pos[_POS_V]+self.pos[_POS_Y]))
1185 self.pos[_POS_V] += self.pos[_POS_Y]
1186 elif cmd >= _DVI_Y1234 and cmd < _DVI_Y1234 + 4:
1187 self.flushout()
1188 self.pos[_POS_Y] = file.readint(cmd - _DVI_Y1234 + 1, 1)
1189 if self.debug:
1190 print ("%d: y%d %d v:=%d%+d=%d, vv:=" %
1191 (self.filepos,
1192 cmd - _DVI_Y1234 + 1,
1193 self.pos[_POS_Y],
1194 self.pos[_POS_V],
1195 self.pos[_POS_Y],
1196 self.pos[_POS_V]+self.pos[_POS_Y]))
1197 self.pos[_POS_V] += self.pos[_POS_Y]
1198 elif cmd == _DVI_Z0:
1199 self.flushout()
1200 self.pos[_POS_V] += self.pos[_POS_Z]
1201 elif cmd >= _DVI_Z1234 and cmd < _DVI_Z1234 + 4:
1202 self.flushout()
1203 self.pos[_POS_Z] = file.readint(cmd - _DVI_Z1234 + 1, 1)
1204 self.pos[_POS_V] += self.pos[_POS_Z]
1205 elif cmd >= _DVI_FNTNUMMIN and cmd <= _DVI_FNTNUMMAX:
1206 self.usefont(cmd - _DVI_FNTNUMMIN)
1207 elif cmd >= _DVI_FNT1234 and cmd < _DVI_FNT1234 + 4:
1208 self.usefont(file.readint(cmd - _DVI_FNT1234 + 1, 1))
1209 elif cmd >= _DVI_SPECIAL1234 and cmd < _DVI_SPECIAL1234 + 4:
1210 self.special(file.read(file.readint(cmd - _DVI_SPECIAL1234 + 1)))
1211 elif cmd >= _DVI_FNTDEF1234 and cmd < _DVI_FNTDEF1234 + 4:
1212 if cmd == _DVI_FNTDEF1234:
1213 num = file.readuchar()
1214 elif cmd == _DVI_FNTDEF1234+1:
1215 num = file.readuint16()
1216 elif cmd == _DVI_FNTDEF1234+2:
1217 num = file.readuint24()
1218 elif cmd == _DVI_FNTDEF1234+3:
1219 # Cool, here we have according to docu a signed int. Why?
1220 num = file.readint32()
1221 self.definefont(cmd-_DVI_FNTDEF1234+1,
1222 num,
1223 file.readint32(),
1224 file.readint32(),
1225 file.readint32(),
1226 file.read(file.readuchar()+file.readuchar()))
1227 else:
1228 raise DVIError
1231 ##############################################################################
1232 # VF file handling
1233 ##############################################################################
1235 _VF_LONG_CHAR = 242 # character packet (long version)
1236 _VF_FNTDEF1234 = _DVI_FNTDEF1234 # font definition
1237 _VF_PRE = _DVI_PRE # preamble
1238 _VF_POST = _DVI_POST # postamble
1240 _VF_ID = 202 # VF id byte
1242 class VFError(exceptions.Exception): pass
1244 class vffile:
1245 def __init__(self, filename, scale, fontmap, debug=0):
1246 self.filename = filename
1247 self.scale = scale
1248 self.fontmap = fontmap
1249 self.debug = debug
1250 self.fonts = {} # used fonts
1251 self.widths = {} # widths of defined chars
1252 self.chardefs = {} # dvi chunks for defined chars
1254 file = binfile(self.filename, "rb")
1256 cmd = file.readuchar()
1257 if cmd == _VF_PRE:
1258 if file.readuchar() != _VF_ID: raise VFError
1259 comment = file.read(file.readuchar())
1260 self.cs = file.readuint32()
1261 self.ds = file.readuint32()
1262 else:
1263 raise VFError
1265 while 1:
1266 cmd = file.readuchar()
1267 if cmd >= _VF_FNTDEF1234 and cmd < _VF_FNTDEF1234 + 4:
1268 # font definition
1269 if cmd == _VF_FNTDEF1234:
1270 num = file.readuchar()
1271 elif cmd == _VF_FNTDEF1234+1:
1272 num = file.readuint16()
1273 elif cmd == _VF_FNTDEF1234+2:
1274 num = file.readuint24()
1275 elif cmd == _VF_FNTDEF1234+3:
1276 num = file.readint32()
1277 c = file.readint32()
1278 s = file.readint32() # relative scaling used for font (fix_word)
1279 d = file.readint32() # design size of font
1280 fontname = file.read(file.readuchar()+file.readuchar())
1282 # rescaled size of font: s is relative to the scaling
1283 # of the virtual font itself. Note that realscale has
1284 # to be a fix_word (like s)
1285 # Furthermore we have to correct for self.tfmconv
1287 reals = int(self.scale * float(fix_word(self.ds))*s)
1288 # print ("defining font %s -- VF scale: %g, VF design size: %g, relative font size: %g => real size: %g" %
1289 # (fontname, self.scale, fix_word(self.ds), fix_word(s), fix_word(reals))
1291 # reald = int(d)
1293 # XXX allow for virtual fonts here too
1294 self.fonts[num] = type1font(fontname, c, reals, d, self.fontmap, self.debug > 1)
1295 elif cmd == _VF_LONG_CHAR:
1296 # character packet (long form)
1297 pl = file.readuint32() # packet length
1298 cc = file.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1299 tfm = file.readuint24() # character width
1300 dvi = file.read(pl) # dvi code of character
1301 self.widths[cc] = tfm
1302 self.chardefs[cc] = dvi
1303 elif cmd < _VF_LONG_CHAR:
1304 # character packet (short form)
1305 cc = file.readuchar() # char code
1306 tfm = file.readuint24() # character width
1307 dvi = file.read(cmd)
1308 self.widths[cc] = tfm
1309 self.chardefs[cc] = dvi
1310 elif cmd == _VF_POST:
1311 break
1312 else:
1313 raise VFError
1315 file.close()
1317 def getfonts(self):
1318 return self.fonts
1320 def getchar(self, cc):
1321 return self.chardefs[cc]