fix various deprecation warnings
[PyX/mjg.git] / pyx / dvifile.py
blob11984acdd4d4886c4e18dc9c21a64a11331f8b60
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2005 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
7 # Copyright (C) 2002-2005 André Wobst <wobsta@users.sourceforge.net>
9 # This file is part of PyX (http://pyx.sourceforge.net/).
11 # PyX is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # PyX is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with PyX; if not, write to the Free Software
23 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 import copy, cStringIO, exceptions, re, struct, string, sys, warnings, math
26 import unit, epsfile, bbox, canvas, color, trafo, path, pykpathsea, pswriter, pdfwriter, type1font
29 class binfile:
31 def __init__(self, filename, mode="r"):
32 self.file = open(filename, mode)
34 def close(self):
35 self.file.close()
37 def tell(self):
38 return self.file.tell()
40 def eof(self):
41 return self.file.eof()
43 def read(self, bytes):
44 return self.file.read(bytes)
46 def readint(self, bytes=4, signed=0):
47 first = 1
48 result = 0
49 while bytes:
50 value = ord(self.file.read(1))
51 if first and signed and value > 127:
52 value -= 256
53 first = 0
54 result = 256 * result + value
55 bytes -= 1
56 return result
58 def readint32(self):
59 return struct.unpack(">l", self.file.read(4))[0]
61 def readuint32(self):
62 return struct.unpack(">L", self.file.read(4))[0]
64 def readint24(self):
65 # XXX: checkme
66 return struct.unpack(">l", "\0"+self.file.read(3))[0]
68 def readuint24(self):
69 # XXX: checkme
70 return struct.unpack(">L", "\0"+self.file.read(3))[0]
72 def readint16(self):
73 return struct.unpack(">h", self.file.read(2))[0]
75 def readuint16(self):
76 return struct.unpack(">H", self.file.read(2))[0]
78 def readchar(self):
79 return struct.unpack("b", self.file.read(1))[0]
81 def readuchar(self):
82 return struct.unpack("B", self.file.read(1))[0]
84 def readstring(self, bytes):
85 l = self.readuchar()
86 assert l <= bytes-1, "inconsistency in file: string too long"
87 return self.file.read(bytes-1)[:l]
89 class stringbinfile(binfile):
91 def __init__(self, s):
92 self.file = cStringIO.StringIO(s)
97 ##############################################################################
98 # TFM file handling
99 ##############################################################################
101 class TFMError(exceptions.Exception): pass
104 class char_info_word:
105 def __init__(self, word):
106 self.width_index = int((word & 0xFF000000L) >> 24) #make sign-safe
107 self.height_index = (word & 0x00F00000) >> 20
108 self.depth_index = (word & 0x000F0000) >> 16
109 self.italic_index = (word & 0x0000FC00) >> 10
110 self.tag = (word & 0x00000300) >> 8
111 self.remainder = (word & 0x000000FF)
114 class tfmfile:
115 def __init__(self, name, debug=0):
116 self.file = binfile(name, "rb")
119 # read pre header
122 self.lf = self.file.readint16()
123 self.lh = self.file.readint16()
124 self.bc = self.file.readint16()
125 self.ec = self.file.readint16()
126 self.nw = self.file.readint16()
127 self.nh = self.file.readint16()
128 self.nd = self.file.readint16()
129 self.ni = self.file.readint16()
130 self.nl = self.file.readint16()
131 self.nk = self.file.readint16()
132 self.ne = self.file.readint16()
133 self.np = self.file.readint16()
135 if not (self.bc-1 <= self.ec <= 255 and
136 self.ne <= 256 and
137 self.lf == 6+self.lh+(self.ec-self.bc+1)+self.nw+self.nh+self.nd
138 +self.ni+self.nl+self.nk+self.ne+self.np):
139 raise TFMError, "error in TFM pre-header"
141 if debug:
142 print "lh=%d" % self.lh
145 # read header
148 self.checksum = self.file.readint32()
149 self.designsize = self.file.readint32()
150 assert self.designsize > 0, "invald design size"
151 if self.lh > 2:
152 assert self.lh > 11, "inconsistency in TFM file: incomplete field"
153 self.charcoding = self.file.readstring(40)
154 else:
155 self.charcoding = None
157 if self.lh > 12:
158 assert self.lh > 16, "inconsistency in TFM file: incomplete field"
159 self.fontfamily = self.file.readstring(20)
160 else:
161 self.fontfamily = None
163 if debug:
164 print "(FAMILY %s)" % self.fontfamily
165 print "(CODINGSCHEME %s)" % self.charcoding
166 print "(DESINGSIZE R %f)" % 16.0*self.designsize/16777216L
168 if self.lh > 17:
169 self.sevenbitsave = self.file.readuchar()
170 # ignore the following two bytes
171 self.file.readint16()
172 facechar = self.file.readuchar()
173 # decode ugly face specification into the Knuth suggested string
174 if facechar < 18:
175 if facechar >= 12:
176 self.face = "E"
177 facechar -= 12
178 elif facechar >= 6:
179 self.face = "C"
180 facechar -= 6
181 else:
182 self.face = "R"
184 if facechar >= 4:
185 self.face = "L" + self.face
186 facechar -= 4
187 elif facechar >= 2:
188 self.face = "B" + self.face
189 facechar -= 2
190 else:
191 self.face = "M" + self.face
193 if facechar == 1:
194 self.face = self.face[0] + "I" + self.face[1]
195 else:
196 self.face = self.face[0] + "R" + self.face[1]
198 else:
199 self.face = None
200 else:
201 self.sevenbitsave = self.face = None
203 if self.lh > 18:
204 # just ignore the rest
205 print self.file.read((self.lh-18)*4)
208 # read char_info
211 self.char_info = [None]*(self.ec+1)
212 for charcode in range(self.bc, self.ec+1):
213 self.char_info[charcode] = char_info_word(self.file.readint32())
214 if self.char_info[charcode].width_index == 0:
215 # disable character if width_index is zero
216 self.char_info[charcode] = None
219 # read widths
222 self.width = [None for width_index in range(self.nw)]
223 for width_index in range(self.nw):
224 self.width[width_index] = self.file.readint32()
227 # read heights
230 self.height = [None for height_index in range(self.nh)]
231 for height_index in range(self.nh):
232 self.height[height_index] = self.file.readint32()
235 # read depths
238 self.depth = [None for depth_index in range(self.nd)]
239 for depth_index in range(self.nd):
240 self.depth[depth_index] = self.file.readint32()
243 # read italic
246 self.italic = [None for italic_index in range(self.ni)]
247 for italic_index in range(self.ni):
248 self.italic[italic_index] = self.file.readint32()
251 # read lig_kern
254 # XXX decode to lig_kern_command
256 self.lig_kern = [None for lig_kern_index in range(self.nl)]
257 for lig_kern_index in range(self.nl):
258 self.lig_kern[lig_kern_index] = self.file.readint32()
261 # read kern
264 self.kern = [None for kern_index in range(self.nk)]
265 for kern_index in range(self.nk):
266 self.kern[kern_index] = self.file.readint32()
269 # read exten
272 # XXX decode to extensible_recipe
274 self.exten = [None for exten_index in range(self.ne)]
275 for exten_index in range(self.ne):
276 self.exten[exten_index] = self.file.readint32()
279 # read param
282 # XXX decode
284 self.param = [None for param_index in range(self.np)]
285 for param_index in range(self.np):
286 self.param[param_index] = self.file.readint32()
288 self.file.close()
292 ##############################################################################
293 # Font handling
294 ##############################################################################
297 # PostScript font selection and output primitives
302 class fontmapping:
304 tokenpattern = re.compile(r'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
306 def __init__(self, s):
307 """ construct font mapping from line s of font mapping file """
308 self.texname = self.basepsname = self.fontfile = None
310 # standard encoding
311 self.encodingfile = None
313 # supported postscript fragments occuring in psfonts.map
314 self.reencodefont = self.extendfont = self.slantfont = None
316 tokens = []
317 while len(s):
318 match = self.tokenpattern.match(s)
319 if match:
320 if match.groups()[0] is not None:
321 tokens.append('"%s"' % match.groups()[0])
322 else:
323 tokens.append(match.groups()[2])
324 s = s[match.end():]
325 else:
326 raise RuntimeError("wrong syntax")
328 for token in tokens:
329 if token.startswith("<"):
330 if token.startswith("<<"):
331 # XXX: support non-partial download here
332 self.fontfile = token[2:]
333 elif token.startswith("<["):
334 self.encodingfile = token[2:]
335 elif token.endswith(".pfa") or token.endswith(".pfb"):
336 self.fontfile = token[1:]
337 elif token.endswith(".enc"):
338 self.encodingfile = token[1:]
339 else:
340 raise RuntimeError("wrong syntax")
341 elif token.startswith('"'):
342 pscode = token[1:-1].split()
343 # parse standard postscript code fragments
344 while pscode:
345 try:
346 arg, cmd = pscode[:2]
347 except:
348 raise RuntimeError("Unsupported Postscript fragment '%s'" % pscode)
349 pscode = pscode[2:]
350 if cmd == "ReEncodeFont":
351 self.reencodefont = arg
352 elif cmd == "ExtendFont":
353 self.extendfont = arg
354 elif cmd == "SlantFont":
355 self.slantfont = arg
356 else:
357 raise RuntimeError("Unsupported Postscript fragment '%s %s'" % (arg, cmd))
358 else:
359 if self.texname is None:
360 self.texname = token
361 else:
362 self.basepsname = token
363 if self.basepsname is None:
364 self.basepsname = self.texname
366 def __str__(self):
367 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
368 (self.texname, self.basepsname, self.fontfile, repr(self.encodingfile)))
370 # generate fontmap
372 def readfontmap(filenames):
373 """ read font map from filename (without path) """
374 fontmap = {}
375 for filename in filenames:
376 mappath = pykpathsea.find_file(filename, pykpathsea.kpse_fontmap_format)
377 # try also the oft-used registration as dvips config file
378 if not mappath:
379 mappath = pykpathsea.find_file(filename, pykpathsea.kpse_dvips_config_format)
380 if not mappath:
381 raise RuntimeError("cannot find font mapping file '%s'" % filename)
382 mapfile = open(mappath, "r")
383 lineno = 0
384 for line in mapfile.readlines():
385 lineno += 1
386 line = line.rstrip()
387 if not (line=="" or line[0] in (" ", "%", "*", ";" , "#")):
388 try:
389 fm = fontmapping(line)
390 except RuntimeError, e:
391 warnings.warn("Ignoring line %i in mapping file '%s': %s" % (lineno, filename, e))
392 else:
393 fontmap[fm.texname] = fm
394 mapfile.close()
395 return fontmap
398 class font:
400 def __init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug=0):
401 self.name = name
402 self.q = q # desired size of font (fix_word) in TeX points
403 self.d = d # design size of font (fix_word) in TeX points
404 self.tfmconv = tfmconv # conversion factor from tfm units to dvi units
405 self.pyxconv = pyxconv # conversion factor from dvi units to PostScript points
406 self.fontmap = fontmap
407 tfmpath = pykpathsea.find_file("%s.tfm" % self.name, pykpathsea.kpse_tfm_format)
408 if not tfmpath:
409 raise TFMError("cannot find %s.tfm" % self.name)
410 self.tfmfile = tfmfile(tfmpath, debug)
412 # We only check for equality of font checksums if none of them
413 # is zero. The case c == 0 happend in some VF files and
414 # according to the VFtoVP documentation, paragraph 40, a check
415 # is only performed if tfmfile.checksum > 0. Anyhow, being
416 # more generous here seems to be reasonable
417 if self.tfmfile.checksum != c and self.tfmfile.checksum != 0 and c != 0:
418 raise DVIError("check sums do not agree: %d vs. %d" %
419 (self.tfmfile.checksum, c))
421 # Check whether the given design size matches the one defined in the tfm file
422 if abs(self.tfmfile.designsize - d) > 2:
423 raise DVIError("design sizes do not agree: %d vs. %d" % (self.tfmfile.designsize, d))
424 #if q < 0 or q > 134217728:
425 # raise DVIError("font '%s' not loaded: bad scale" % self.name)
426 if d < 0 or d > 134217728:
427 raise DVIError("font '%s' not loaded: bad design size" % self.name)
429 self.scale = 1.0*q/d
431 # The following code is a very crude way to obtain the information
432 # required for the PDF font descritor. (TODO: The correct way would
433 # be to read the information from the AFM file.)
434 self.fontbbox = (0,
435 -self.getdepth_ds(ord("y")),
436 self.getwidth_ds(ord("W")),
437 self.getheight_ds(ord("H")))
438 self.italicangle = -180/math.pi*math.atan(self.tfmfile.param[0]/65536.0)
439 self.ascent = self.fontbbox[3]
440 self.descent = self.fontbbox[1]
441 self.capheight = self.getheight_ds(ord("h"))
442 self.vstem = self.getwidth_ds(ord("."))/3
444 def __str__(self):
445 return "font %s designed at %g TeX pts used at %g TeX pts" % (self.name,
446 16.0*self.d/16777216L,
447 16.0*self.q/16777216L)
448 __repr__ = __str__
450 def getsize_pt(self):
451 """ return size of font in (PS) points """
452 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
453 # to the corresponding float. Furthermore, we have to convert from TeX
454 # points to points, hence the factor 72/72.27.
455 return 16L*self.q/16777216L*72/72.27
457 def _convert_tfm_to_dvi(self, length):
458 # doing the integer math with long integers will lead to different roundings
459 # return 16*length*int(round(self.q*self.tfmconv))/16777216
461 # Knuth instead suggests the following algorithm based on 4 byte integer logic only
462 # z = int(round(self.q*self.tfmconv))
463 # b0, b1, b2, b3 = [ord(c) for c in struct.pack(">L", length)]
464 # assert b0 == 0 or b0 == 255
465 # shift = 4
466 # while z >= 8388608:
467 # z >>= 1
468 # shift -= 1
469 # assert shift >= 0
470 # result = ( ( ( ( ( b3 * z ) >> 8 ) + ( b2 * z ) ) >> 8 ) + ( b1 * z ) ) >> shift
471 # if b0 == 255:
472 # result = result - (z << (8-shift))
474 # however, we can simplify this using a single long integer multiplication,
475 # but take into account the transformation of z
476 z = int(round(self.q*self.tfmconv))
477 assert -16777216 <= length < 16777216 # -(1 << 24) <= length < (1 << 24)
478 assert z < 134217728 # 1 << 27
479 shift = 20 # 1 << 20
480 while z >= 8388608: # 1 << 23
481 z >>= 1
482 shift -= 1
483 # length*z is a long integer, but the result will be a regular integer
484 return int(length*long(z) >> shift)
486 def _convert_tfm_to_ds(self, length):
487 return (16*long(round(length*self.q*self.tfmconv))/16777216) * self.pyxconv * 1000 / self.getsize_pt()
489 def _convert_tfm_to_pt(self, length):
490 return (16*long(round(length*self.q*self.tfmconv))/16777216) * self.pyxconv
492 # routines returning lengths as integers in dvi units
494 def getwidth_dvi(self, charcode):
495 return self._convert_tfm_to_dvi(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
497 def getheight_dvi(self, charcode):
498 return self._convert_tfm_to_dvi(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
500 def getdepth_dvi(self, charcode):
501 return self._convert_tfm_to_dvi(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
503 def getitalic_dvi(self, charcode):
504 return self._convert_tfm_to_dvi(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
506 # routines returning lengths as integers in design size (AFM) units
508 def getwidth_ds(self, charcode):
509 return self._convert_tfm_to_ds(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
511 def getheight_ds(self, charcode):
512 return self._convert_tfm_to_ds(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
514 def getdepth_ds(self, charcode):
515 return self._convert_tfm_to_ds(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
517 def getitalic_ds(self, charcode):
518 return self._convert_tfm_to_ds(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
520 # routines returning lengths as floats in PostScript points
522 def getwidth_pt(self, charcode):
523 return self._convert_tfm_to_pt(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
525 def getheight_pt(self, charcode):
526 return self._convert_tfm_to_pt(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
528 def getdepth_pt(self, charcode):
529 return self._convert_tfm_to_pt(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
531 def getitalic_pt(self, charcode):
532 return self._convert_tfm_to_pt(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
535 class virtualfont(font):
536 def __init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug=0):
537 font.__init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug)
538 fontpath = pykpathsea.find_file(name, pykpathsea.kpse_vf_format)
539 if fontpath is None or not len(fontpath):
540 raise RuntimeError
541 self.vffile = vffile(fontpath, self.scale, tfmconv, pyxconv, fontmap, debug > 1)
543 def getfonts(self):
544 """ return fonts used in virtual font itself """
545 return self.vffile.getfonts()
547 def getchar(self, cc):
548 """ return dvi chunk corresponding to char code cc """
549 return self.vffile.getchar(cc)
552 ##############################################################################
553 # DVI file handling
554 ##############################################################################
556 _DVI_CHARMIN = 0 # typeset a character and move right (range min)
557 _DVI_CHARMAX = 127 # typeset a character and move right (range max)
558 _DVI_SET1234 = 128 # typeset a character and move right
559 _DVI_SETRULE = 132 # typeset a rule and move right
560 _DVI_PUT1234 = 133 # typeset a character
561 _DVI_PUTRULE = 137 # typeset a rule
562 _DVI_NOP = 138 # no operation
563 _DVI_BOP = 139 # beginning of page
564 _DVI_EOP = 140 # ending of page
565 _DVI_PUSH = 141 # save the current positions (h, v, w, x, y, z)
566 _DVI_POP = 142 # restore positions (h, v, w, x, y, z)
567 _DVI_RIGHT1234 = 143 # move right
568 _DVI_W0 = 147 # move right by w
569 _DVI_W1234 = 148 # move right and set w
570 _DVI_X0 = 152 # move right by x
571 _DVI_X1234 = 153 # move right and set x
572 _DVI_DOWN1234 = 157 # move down
573 _DVI_Y0 = 161 # move down by y
574 _DVI_Y1234 = 162 # move down and set y
575 _DVI_Z0 = 166 # move down by z
576 _DVI_Z1234 = 167 # move down and set z
577 _DVI_FNTNUMMIN = 171 # set current font (range min)
578 _DVI_FNTNUMMAX = 234 # set current font (range max)
579 _DVI_FNT1234 = 235 # set current font
580 _DVI_SPECIAL1234 = 239 # special (dvi extention)
581 _DVI_FNTDEF1234 = 243 # define the meaning of a font number
582 _DVI_PRE = 247 # preamble
583 _DVI_POST = 248 # postamble beginning
584 _DVI_POSTPOST = 249 # postamble ending
586 _DVI_VERSION = 2 # dvi version
588 # position variable indices
589 _POS_H = 0
590 _POS_V = 1
591 _POS_W = 2
592 _POS_X = 3
593 _POS_Y = 4
594 _POS_Z = 5
596 # reader states
597 _READ_PRE = 1
598 _READ_NOPAGE = 2
599 _READ_PAGE = 3
600 _READ_POST = 4 # XXX not used
601 _READ_POSTPOST = 5 # XXX not used
602 _READ_DONE = 6
605 class DVIError(exceptions.Exception): pass
607 # save and restore colors
609 class _savecolor(canvas.canvasitem):
610 def outputPS(self, file, writer, context):
611 file.write("currentcolor currentcolorspace\n")
613 def outputPDF(self, file, writer, context):
614 file.write("q\n")
617 class _restorecolor(canvas.canvasitem):
618 def outputPS(self, file, writer, context):
619 file.write("setcolorspace setcolor\n")
621 def outputPDF(self, file, writer, context):
622 file.write("Q\n")
624 class _savetrafo(canvas.canvasitem):
625 def outputPS(self, file, writer, context):
626 file.write("matrix currentmatrix\n")
628 def outputPDF(self, file, writer, context):
629 file.write("q\n")
632 class _restoretrafo(canvas.canvasitem):
633 def outputPS(self, file, writer, context):
634 file.write("setmatrix\n")
636 def outputPDF(self, file, writer, context):
637 file.write("Q\n")
640 class dvifile:
642 def __init__(self, filename, fontmap, debug=0, debugfile=sys.stdout):
643 """ opens the dvi file and reads the preamble """
644 self.filename = filename
645 self.fontmap = fontmap
646 self.debug = debug
647 self.debugfile = debugfile
648 self.debugstack = []
650 self.fonts = {}
651 self.activefont = None
653 # stack of fonts and fontscale currently used (used for VFs)
654 self.fontstack = []
655 self.stack = []
657 # pointer to currently active page
658 self.actpage = None
660 # stack for self.file, self.fonts and self.stack, needed for VF inclusion
661 self.statestack = []
663 self.file = binfile(self.filename, "rb")
665 # currently read byte in file (for debugging output)
666 self.filepos = None
668 self._read_pre()
670 # helper routines
672 def flushtext(self):
673 """ finish currently active text object """
674 if self.debug and self.activetext:
675 self.debugfile.write("[%s]\n" % "".join([chr(char) for char in self.activetext.chars]))
676 self.activetext = None
678 def putrule(self, height, width, advancepos=1):
679 self.flushtext()
680 x1 = self.pos[_POS_H] * self.pyxconv
681 y1 = -self.pos[_POS_V] * self.pyxconv
682 w = width * self.pyxconv
683 h = height * self.pyxconv
685 if height > 0 and width > 0:
686 if self.debug:
687 self.debugfile.write("%d: %srule height %d, width %d (???x??? pixels)\n" %
688 (self.filepos, advancepos and "set" or "put", height, width))
689 self.actpage.fill(path.rect_pt(x1, y1, w, h))
690 else:
691 if self.debug:
692 self.debugfile.write("%d: %srule height %d, width %d (invisible)\n" %
693 (self.filepos, advancepos and "set" or "put", height, width))
695 if advancepos:
696 if self.debug:
697 self.debugfile.write(" h:=%d+%d=%d, hh:=???\n" %
698 (self.pos[_POS_H], width, self.pos[_POS_H]+width))
699 self.pos[_POS_H] += width
701 def putchar(self, char, advancepos=1, id1234=0):
702 dx = advancepos and self.activefont.getwidth_dvi(char) or 0
704 if self.debug:
705 self.debugfile.write("%d: %s%s%d h:=%d+%d=%d, hh:=???\n" %
706 (self.filepos,
707 advancepos and "set" or "put",
708 id1234 and "%i " % id1234 or "char",
709 char,
710 self.pos[_POS_H], dx, self.pos[_POS_H]+dx))
712 if isinstance(self.activefont, virtualfont):
713 # virtual font handling
714 afterpos = list(self.pos)
715 afterpos[_POS_H] += dx
716 self._push_dvistring(self.activefont.getchar(char), self.activefont.getfonts(), afterpos,
717 self.activefont.getsize_pt())
718 else:
719 if self.activetext is None:
720 fontmapinfo = self.fontmap.get(self.activefont.name)
722 encodingname = fontmapinfo.reencodefont
723 if encodingname is not None:
724 encodingfilename = pykpathsea.find_file(fontmapinfo.encodingfile, pykpathsea.kpse_tex_ps_header_format)
725 if not encodingfilename:
726 raise RuntimeError("cannot find font encoding file %s" % fontmapinfo.encodingfile)
727 fontencoding = type1font.encoding(encodingname, encodingfilename)
728 else:
729 fontencoding = None
731 fontbasefontname = fontmapinfo.basepsname
732 if fontmapinfo.fontfile is not None:
733 fontfilename = pykpathsea.find_file(fontmapinfo.fontfile, pykpathsea.kpse_type1_format)
734 if not fontfilename:
735 raise RuntimeError("cannot find type 1 font %s" % fontmapinfo.fontfile)
736 else:
737 fontfilename = None
739 # XXX we currently misuse use self.activefont as metric
740 font = type1font.font(fontbasefontname, fontfilename, fontencoding, self.activefont)
742 self.activetext = type1font.text_pt(self.pos[_POS_H] * self.pyxconv, -self.pos[_POS_V] * self.pyxconv, font)
743 self.actpage.insert(self.activetext)
744 self.activetext.addchar(char)
745 self.pos[_POS_H] += dx
747 if not advancepos:
748 self.flushtext()
750 def usefont(self, fontnum, id1234=0):
751 self.flushtext()
752 self.activefont = self.fonts[fontnum]
753 if self.debug:
754 self.debugfile.write("%d: fnt%s%i current font is %s\n" %
755 (self.filepos,
756 id1234 and "%i " % id1234 or "num",
757 fontnum,
758 self.fonts[fontnum].name))
761 def definefont(self, cmdnr, num, c, q, d, fontname):
762 # cmdnr: type of fontdef command (only used for debugging output)
763 # c: checksum
764 # q: scaling factor (fix_word)
765 # Note that q is actually s in large parts of the documentation.
766 # d: design size (fix_word)
768 try:
769 afont = virtualfont(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
770 except (TypeError, RuntimeError):
771 afont = font(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
773 self.fonts[num] = afont
775 if self.debug:
776 self.debugfile.write("%d: fntdef%d %i: %s\n" % (self.filepos, cmdnr, num, fontname))
778 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
779 # m = 1.0*q/d
780 # scalestring = scale!=1000 and " scaled %d" % scale or ""
781 # print ("Font %i: %s%s---loaded at size %d DVI units" %
782 # (num, fontname, scalestring, q))
783 # if scale!=1000:
784 # print " (this font is magnified %d%%)" % round(scale/10)
786 def special(self, s):
787 x = self.pos[_POS_H] * self.pyxconv
788 y = -self.pos[_POS_V] * self.pyxconv
789 if self.debug:
790 self.debugfile.write("%d: xxx '%s'\n" % (self.filepos, s))
791 if not s.startswith("PyX:"):
792 warnings.warn("ignoring special '%s'" % s)
793 return
795 # it is in general not safe to continue using the currently active font because
796 # the specials may involve some gsave/grestore operations
797 self.flushtext()
799 command, args = s[4:].split()[0], s[4:].split()[1:]
800 if command == "color_begin":
801 if args[0] == "cmyk":
802 c = color.cmyk(float(args[1]), float(args[2]), float(args[3]), float(args[4]))
803 elif args[0] == "gray":
804 c = color.gray(float(args[1]))
805 elif args[0] == "hsb":
806 c = color.hsb(float(args[1]), float(args[2]), float(args[3]))
807 elif args[0] == "rgb":
808 c = color.rgb(float(args[1]), float(args[2]), float(args[3]))
809 elif args[0] == "RGB":
810 c = color.rgb(int(args[1])/255.0, int(args[2])/255.0, int(args[3])/255.0)
811 elif args[0] == "texnamed":
812 try:
813 c = getattr(color.cmyk, args[1])
814 except AttributeError:
815 raise RuntimeError("unknown TeX color '%s', aborting" % args[1])
816 else:
817 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args[0])
818 self.actpage.insert(_savecolor())
819 self.actpage.insert(c)
820 elif command == "color_end":
821 self.actpage.insert(_restorecolor())
822 elif command == "rotate_begin":
823 self.actpage.insert(_savetrafo())
824 self.actpage.insert(trafo.rotate_pt(float(args[0]), x, y))
825 elif command == "rotate_end":
826 self.actpage.insert(_restoretrafo())
827 elif command == "scale_begin":
828 self.actpage.insert(_savetrafo())
829 self.actpage.insert(trafo.scale_pt(float(args[0]), float(args[1]), x, y))
830 elif command == "scale_end":
831 self.actpage.insert(_restoretrafo())
832 elif command == "epsinclude":
833 # parse arguments
834 argdict = {}
835 for arg in args:
836 name, value = arg.split("=")
837 argdict[name] = value
839 # construct kwargs for epsfile constructor
840 epskwargs = {}
841 epskwargs["filename"] = argdict["file"]
842 epskwargs["bbox"] = bbox.bbox_pt(float(argdict["llx"]), float(argdict["lly"]),
843 float(argdict["urx"]), float(argdict["ury"]))
844 if argdict.has_key("width"):
845 epskwargs["width"] = float(argdict["width"]) * unit.t_pt
846 if argdict.has_key("height"):
847 epskwargs["height"] = float(argdict["height"]) * unit.t_pt
848 if argdict.has_key("clip"):
849 epskwargs["clip"] = int(argdict["clip"])
850 self.actpage.insert(epsfile.epsfile(x * unit.t_pt, y * unit.t_pt, **epskwargs))
851 elif command == "marker":
852 if len(args) != 1:
853 raise RuntimeError("marker contains spaces")
854 for c in args[0]:
855 if c not in string.digits + string.letters + "@":
856 raise RuntimeError("marker contains invalid characters")
857 if self.actpage.markers.has_key(args[0]):
858 raise RuntimeError("marker name occurred several times")
859 self.actpage.markers[args[0]] = x * unit.t_pt, y * unit.t_pt
860 else:
861 raise RuntimeError("unknown PyX special '%s', aborting" % command)
863 # routines for pushing and popping different dvi chunks on the reader
865 def _push_dvistring(self, dvi, fonts, afterpos, fontsize):
866 """ push dvi string with defined fonts on top of reader
867 stack. Every positions gets scaled relatively by the factor
868 scale. After the interpreting of the dvi chunk has been finished,
869 continue with self.pos=afterpos. The designsize of the virtual
870 font is passed as a fix_word
874 #if self.debug:
875 # self.debugfile.write("executing new dvi chunk\n")
876 self.debugstack.append(self.debug)
877 self.debug = 0
879 self.statestack.append((self.file, self.fonts, self.activefont, afterpos, self.stack, self.pyxconv, self.tfmconv))
881 # units in vf files are relative to the size of the font and given as fix_words
882 # which can be converted to floats by diving by 2**20
883 oldpyxconv = self.pyxconv
884 self.pyxconv = fontsize/2**20
885 rescale = self.pyxconv/oldpyxconv
887 self.file = stringbinfile(dvi)
888 self.fonts = fonts
889 self.stack = []
890 self.filepos = 0
892 # rescale self.pos in order to be consistent with the new scaling
893 self.pos = map(lambda x, rescale=rescale:1.0*x/rescale, self.pos)
895 # since tfmconv converts from tfm units to dvi units, rescale it as well
896 self.tfmconv /= rescale
898 self.usefont(0)
900 def _pop_dvistring(self):
901 self.flushtext()
902 #if self.debug:
903 # self.debugfile.write("finished executing dvi chunk\n")
904 self.debug = self.debugstack.pop()
906 self.file.close()
907 self.file, self.fonts, self.activefont, self.pos, self.stack, self.pyxconv, self.tfmconv = self.statestack.pop()
909 # routines corresponding to the different reader states of the dvi maschine
911 def _read_pre(self):
912 afile = self.file
913 while 1:
914 self.filepos = afile.tell()
915 cmd = afile.readuchar()
916 if cmd == _DVI_NOP:
917 pass
918 elif cmd == _DVI_PRE:
919 if afile.readuchar() != _DVI_VERSION: raise DVIError
920 num = afile.readuint32()
921 den = afile.readuint32()
922 self.mag = afile.readuint32()
924 # For the interpretation of the lengths in dvi and tfm files,
925 # three conversion factors are relevant:
926 # - self.tfmconv: tfm units -> dvi units
927 # - self.pyxconv: dvi units -> (PostScript) points
928 # - self.conv: dvi units -> pixels
929 self.tfmconv = (25400000.0/num)*(den/473628672.0)/16.0
931 # calculate conv as described in the DVIType docu using
932 # a given resolution in dpi
933 self.resolution = 300.0
934 self.conv = (num/254000.0)*(self.resolution/den)
936 # self.pyxconv is the conversion factor from the dvi units
937 # to (PostScript) points. It consists of
938 # - self.mag/1000.0: magstep scaling
939 # - self.conv: conversion from dvi units to pixels
940 # - 1/self.resolution: conversion from pixels to inch
941 # - 72 : conversion from inch to points
942 self.pyxconv = self.mag/1000.0*self.conv/self.resolution*72
944 comment = afile.read(afile.readuchar())
945 return
946 else:
947 raise DVIError
949 def readpage(self, pageid=None):
950 """ reads a page from the dvi file
952 This routine reads a page from the dvi file which is
953 returned as a canvas. When there is no page left in the
954 dvifile, None is returned and the file is closed properly."""
956 while 1:
957 self.filepos = self.file.tell()
958 cmd = self.file.readuchar()
959 if cmd == _DVI_NOP:
960 pass
961 elif cmd == _DVI_BOP:
962 ispageid = [self.file.readuint32() for i in range(10)]
963 if pageid is not None and ispageid != pageid:
964 raise DVIError("invalid pageid")
965 if self.debug:
966 self.debugfile.write("%d: beginning of page %i\n" % (self.filepos, ispageid[0]))
967 self.file.readuint32()
968 break
969 elif cmd == _DVI_POST:
970 self.file.close()
971 return None # nothing left
972 else:
973 raise DVIError
975 self.actpage = canvas.canvas()
976 self.actpage.markers = {}
977 self.pos = [0, 0, 0, 0, 0, 0]
979 # currently active output: text instance currently used
980 self.activetext = None
982 while 1:
983 afile = self.file
984 self.filepos = afile.tell()
985 try:
986 cmd = afile.readuchar()
987 except struct.error:
988 # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
989 # so we have to continue with the rest of the dvi file
990 self._pop_dvistring()
991 continue
992 if cmd == _DVI_NOP:
993 pass
994 if cmd >= _DVI_CHARMIN and cmd <= _DVI_CHARMAX:
995 self.putchar(cmd)
996 elif cmd >= _DVI_SET1234 and cmd < _DVI_SET1234 + 4:
997 self.putchar(afile.readint(cmd - _DVI_SET1234 + 1), id1234=cmd-_DVI_SET1234+1)
998 elif cmd == _DVI_SETRULE:
999 self.putrule(afile.readint32(), afile.readint32())
1000 elif cmd >= _DVI_PUT1234 and cmd < _DVI_PUT1234 + 4:
1001 self.putchar(afile.readint(cmd - _DVI_PUT1234 + 1), advancepos=0, id1234=cmd-_DVI_SET1234+1)
1002 elif cmd == _DVI_PUTRULE:
1003 self.putrule(afile.readint32(), afile.readint32(), 0)
1004 elif cmd == _DVI_EOP:
1005 self.flushtext()
1006 if self.debug:
1007 self.debugfile.write("%d: eop\n \n" % self.filepos)
1008 return self.actpage
1009 elif cmd == _DVI_PUSH:
1010 self.stack.append(list(self.pos))
1011 if self.debug:
1012 self.debugfile.write("%s: push\n"
1013 "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1014 ((self.filepos, len(self.stack)-1) + tuple(self.pos)))
1015 elif cmd == _DVI_POP:
1016 self.flushtext()
1017 self.pos = self.stack.pop()
1018 if self.debug:
1019 self.debugfile.write("%s: pop\n"
1020 "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1021 ((self.filepos, len(self.stack)) + tuple(self.pos)))
1022 elif cmd >= _DVI_RIGHT1234 and cmd < _DVI_RIGHT1234 + 4:
1023 self.flushtext()
1024 dh = afile.readint(cmd - _DVI_RIGHT1234 + 1, 1)
1025 if self.debug:
1026 self.debugfile.write("%d: right%d %d h:=%d%+d=%d, hh:=???\n" %
1027 (self.filepos,
1028 cmd - _DVI_RIGHT1234 + 1,
1030 self.pos[_POS_H],
1032 self.pos[_POS_H]+dh))
1033 self.pos[_POS_H] += dh
1034 elif cmd == _DVI_W0:
1035 self.flushtext()
1036 if self.debug:
1037 self.debugfile.write("%d: w0 %d h:=%d%+d=%d, hh:=???\n" %
1038 (self.filepos,
1039 self.pos[_POS_W],
1040 self.pos[_POS_H],
1041 self.pos[_POS_W],
1042 self.pos[_POS_H]+self.pos[_POS_W]))
1043 self.pos[_POS_H] += self.pos[_POS_W]
1044 elif cmd >= _DVI_W1234 and cmd < _DVI_W1234 + 4:
1045 self.flushtext()
1046 self.pos[_POS_W] = afile.readint(cmd - _DVI_W1234 + 1, 1)
1047 if self.debug:
1048 self.debugfile.write("%d: w%d %d h:=%d%+d=%d, hh:=???\n" %
1049 (self.filepos,
1050 cmd - _DVI_W1234 + 1,
1051 self.pos[_POS_W],
1052 self.pos[_POS_H],
1053 self.pos[_POS_W],
1054 self.pos[_POS_H]+self.pos[_POS_W]))
1055 self.pos[_POS_H] += self.pos[_POS_W]
1056 elif cmd == _DVI_X0:
1057 self.flushtext()
1058 if self.debug:
1059 self.debugfile.write("%d: x0 %d h:=%d%+d=%d, hh:=???\n" %
1060 (self.filepos,
1061 self.pos[_POS_X],
1062 self.pos[_POS_H],
1063 self.pos[_POS_X],
1064 self.pos[_POS_H]+self.pos[_POS_X]))
1065 self.pos[_POS_H] += self.pos[_POS_X]
1066 elif cmd >= _DVI_X1234 and cmd < _DVI_X1234 + 4:
1067 self.flushtext()
1068 self.pos[_POS_X] = afile.readint(cmd - _DVI_X1234 + 1, 1)
1069 if self.debug:
1070 self.debugfile.write("%d: x%d %d h:=%d%+d=%d, hh:=???\n" %
1071 (self.filepos,
1072 cmd - _DVI_X1234 + 1,
1073 self.pos[_POS_X],
1074 self.pos[_POS_H],
1075 self.pos[_POS_X],
1076 self.pos[_POS_H]+self.pos[_POS_X]))
1077 self.pos[_POS_H] += self.pos[_POS_X]
1078 elif cmd >= _DVI_DOWN1234 and cmd < _DVI_DOWN1234 + 4:
1079 self.flushtext()
1080 dv = afile.readint(cmd - _DVI_DOWN1234 + 1, 1)
1081 if self.debug:
1082 self.debugfile.write("%d: down%d %d v:=%d%+d=%d, vv:=???\n" %
1083 (self.filepos,
1084 cmd - _DVI_DOWN1234 + 1,
1086 self.pos[_POS_V],
1088 self.pos[_POS_V]+dv))
1089 self.pos[_POS_V] += dv
1090 elif cmd == _DVI_Y0:
1091 self.flushtext()
1092 if self.debug:
1093 self.debugfile.write("%d: y0 %d v:=%d%+d=%d, vv:=???\n" %
1094 (self.filepos,
1095 self.pos[_POS_Y],
1096 self.pos[_POS_V],
1097 self.pos[_POS_Y],
1098 self.pos[_POS_V]+self.pos[_POS_Y]))
1099 self.pos[_POS_V] += self.pos[_POS_Y]
1100 elif cmd >= _DVI_Y1234 and cmd < _DVI_Y1234 + 4:
1101 self.flushtext()
1102 self.pos[_POS_Y] = afile.readint(cmd - _DVI_Y1234 + 1, 1)
1103 if self.debug:
1104 self.debugfile.write("%d: y%d %d v:=%d%+d=%d, vv:=???\n" %
1105 (self.filepos,
1106 cmd - _DVI_Y1234 + 1,
1107 self.pos[_POS_Y],
1108 self.pos[_POS_V],
1109 self.pos[_POS_Y],
1110 self.pos[_POS_V]+self.pos[_POS_Y]))
1111 self.pos[_POS_V] += self.pos[_POS_Y]
1112 elif cmd == _DVI_Z0:
1113 self.flushtext()
1114 if self.debug:
1115 self.debugfile.write("%d: z0 %d v:=%d%+d=%d, vv:=???\n" %
1116 (self.filepos,
1117 self.pos[_POS_Z],
1118 self.pos[_POS_V],
1119 self.pos[_POS_Z],
1120 self.pos[_POS_V]+self.pos[_POS_Z]))
1121 self.pos[_POS_V] += self.pos[_POS_Z]
1122 elif cmd >= _DVI_Z1234 and cmd < _DVI_Z1234 + 4:
1123 self.flushtext()
1124 self.pos[_POS_Z] = afile.readint(cmd - _DVI_Z1234 + 1, 1)
1125 if self.debug:
1126 self.debugfile.write("%d: z%d %d v:=%d%+d=%d, vv:=???\n" %
1127 (self.filepos,
1128 cmd - _DVI_Z1234 + 1,
1129 self.pos[_POS_Z],
1130 self.pos[_POS_V],
1131 self.pos[_POS_Z],
1132 self.pos[_POS_V]+self.pos[_POS_Z]))
1133 self.pos[_POS_V] += self.pos[_POS_Z]
1134 elif cmd >= _DVI_FNTNUMMIN and cmd <= _DVI_FNTNUMMAX:
1135 self.usefont(cmd - _DVI_FNTNUMMIN, 0)
1136 elif cmd >= _DVI_FNT1234 and cmd < _DVI_FNT1234 + 4:
1137 # note that according to the DVI docs, for four byte font numbers,
1138 # the font number is signed. Don't ask why!
1139 fntnum = afile.readint(cmd - _DVI_FNT1234 + 1, cmd == _DVI_FNT1234 + 3)
1140 self.usefont(fntnum, id1234=cmd-_DVI_FNT1234+1)
1141 elif cmd >= _DVI_SPECIAL1234 and cmd < _DVI_SPECIAL1234 + 4:
1142 self.special(afile.read(afile.readint(cmd - _DVI_SPECIAL1234 + 1)))
1143 elif cmd >= _DVI_FNTDEF1234 and cmd < _DVI_FNTDEF1234 + 4:
1144 if cmd == _DVI_FNTDEF1234:
1145 num = afile.readuchar()
1146 elif cmd == _DVI_FNTDEF1234+1:
1147 num = afile.readuint16()
1148 elif cmd == _DVI_FNTDEF1234+2:
1149 num = afile.readuint24()
1150 elif cmd == _DVI_FNTDEF1234+3:
1151 # Cool, here we have according to docu a signed int. Why?
1152 num = afile.readint32()
1153 self.definefont(cmd-_DVI_FNTDEF1234+1,
1154 num,
1155 afile.readint32(),
1156 afile.readint32(),
1157 afile.readint32(),
1158 afile.read(afile.readuchar()+afile.readuchar()))
1159 else:
1160 raise DVIError
1163 ##############################################################################
1164 # VF file handling
1165 ##############################################################################
1167 _VF_LONG_CHAR = 242 # character packet (long version)
1168 _VF_FNTDEF1234 = _DVI_FNTDEF1234 # font definition
1169 _VF_PRE = _DVI_PRE # preamble
1170 _VF_POST = _DVI_POST # postamble
1172 _VF_ID = 202 # VF id byte
1174 class VFError(exceptions.Exception): pass
1176 class vffile:
1177 def __init__(self, filename, scale, tfmconv, pyxconv, fontmap, debug=0):
1178 self.filename = filename
1179 self.scale = scale
1180 self.tfmconv = tfmconv
1181 self.pyxconv = pyxconv
1182 self.fontmap = fontmap
1183 self.debug = debug
1184 self.fonts = {} # used fonts
1185 self.widths = {} # widths of defined chars
1186 self.chardefs = {} # dvi chunks for defined chars
1188 afile = binfile(self.filename, "rb")
1190 cmd = afile.readuchar()
1191 if cmd == _VF_PRE:
1192 if afile.readuchar() != _VF_ID: raise VFError
1193 comment = afile.read(afile.readuchar())
1194 self.cs = afile.readuint32()
1195 self.ds = afile.readuint32()
1196 else:
1197 raise VFError
1199 while 1:
1200 cmd = afile.readuchar()
1201 if cmd >= _VF_FNTDEF1234 and cmd < _VF_FNTDEF1234 + 4:
1202 # font definition
1203 if cmd == _VF_FNTDEF1234:
1204 num = afile.readuchar()
1205 elif cmd == _VF_FNTDEF1234+1:
1206 num = afile.readuint16()
1207 elif cmd == _VF_FNTDEF1234+2:
1208 num = afile.readuint24()
1209 elif cmd == _VF_FNTDEF1234+3:
1210 num = afile.readint32()
1211 c = afile.readint32()
1212 s = afile.readint32() # relative scaling used for font (fix_word)
1213 d = afile.readint32() # design size of font
1214 fontname = afile.read(afile.readuchar()+afile.readuchar())
1216 # rescaled size of font: s is relative to the scaling
1217 # of the virtual font itself. Note that realscale has
1218 # to be a fix_word (like s)
1219 # XXX: check rounding
1220 reals = int(round(self.scale * (16*self.ds/16777216L) * s))
1222 # print ("defining font %s -- VF scale: %g, VF design size: %d, relative font size: %d => real size: %d" %
1223 # (fontname, self.scale, self.ds, s, reals)
1226 # XXX allow for virtual fonts here too
1227 self.fonts[num] = font(fontname, c, reals, d, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
1228 elif cmd == _VF_LONG_CHAR:
1229 # character packet (long form)
1230 pl = afile.readuint32() # packet length
1231 cc = afile.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1232 tfm = afile.readuint24() # character width
1233 dvi = afile.read(pl) # dvi code of character
1234 self.widths[cc] = tfm
1235 self.chardefs[cc] = dvi
1236 elif cmd < _VF_LONG_CHAR:
1237 # character packet (short form)
1238 cc = afile.readuchar() # char code
1239 tfm = afile.readuint24() # character width
1240 dvi = afile.read(cmd)
1241 self.widths[cc] = tfm
1242 self.chardefs[cc] = dvi
1243 elif cmd == _VF_POST:
1244 break
1245 else:
1246 raise VFError
1248 afile.close()
1250 def getfonts(self):
1251 return self.fonts
1253 def getchar(self, cc):
1254 return self.chardefs[cc]