correct the self-intersection in parallel
[PyX/mjg.git] / pyx / dvifile.py
blob05aa4c3e53f1b0be9c49c85fc0e5934acda148a7
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 import cStringIO, exceptions, re, struct, string, sys, warnings, math
26 import unit, epsfile, bbox, canvas, color, trafo, path, pykpathsea, 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 def fontinfo(self):
432 class fontinfo:
433 pass
435 # The following code is a very crude way to obtain the information
436 # required for the PDF font descritor. (TODO: The correct way would
437 # be to read the information from the AFM file.)
438 fontinfo = fontinfo()
439 fontinfo.fontbbox = (0,
440 -self.getdepth_ds(ord("y")),
441 self.getwidth_ds(ord("W")),
442 self.getheight_ds(ord("H")))
443 fontinfo.italicangle = -180/math.pi*math.atan(self.tfmfile.param[0]/65536.0)
444 fontinfo.ascent = fontinfo.fontbbox[3]
445 fontinfo.descent = fontinfo.fontbbox[1]
446 fontinfo.capheight = self.getheight_ds(ord("h"))
447 fontinfo.vstem = self.getwidth_ds(ord("."))/3
448 return fontinfo
450 def __str__(self):
451 return "font %s designed at %g TeX pts used at %g TeX pts" % (self.name,
452 16.0*self.d/16777216L,
453 16.0*self.q/16777216L)
454 __repr__ = __str__
456 def getsize_pt(self):
457 """ return size of font in (PS) points """
458 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
459 # to the corresponding float. Furthermore, we have to convert from TeX
460 # points to points, hence the factor 72/72.27.
461 return 16L*self.q/16777216L*72/72.27
463 def _convert_tfm_to_dvi(self, length):
464 # doing the integer math with long integers will lead to different roundings
465 # return 16*length*int(round(self.q*self.tfmconv))/16777216
467 # Knuth instead suggests the following algorithm based on 4 byte integer logic only
468 # z = int(round(self.q*self.tfmconv))
469 # b0, b1, b2, b3 = [ord(c) for c in struct.pack(">L", length)]
470 # assert b0 == 0 or b0 == 255
471 # shift = 4
472 # while z >= 8388608:
473 # z >>= 1
474 # shift -= 1
475 # assert shift >= 0
476 # result = ( ( ( ( ( b3 * z ) >> 8 ) + ( b2 * z ) ) >> 8 ) + ( b1 * z ) ) >> shift
477 # if b0 == 255:
478 # result = result - (z << (8-shift))
480 # however, we can simplify this using a single long integer multiplication,
481 # but take into account the transformation of z
482 z = int(round(self.q*self.tfmconv))
483 assert -16777216 <= length < 16777216 # -(1 << 24) <= length < (1 << 24)
484 assert z < 134217728 # 1 << 27
485 shift = 20 # 1 << 20
486 while z >= 8388608: # 1 << 23
487 z >>= 1
488 shift -= 1
489 # length*z is a long integer, but the result will be a regular integer
490 return int(length*long(z) >> shift)
492 def _convert_tfm_to_ds(self, length):
493 return (16*long(round(length*float(self.q)*self.tfmconv))/16777216) * self.pyxconv * 1000 / self.getsize_pt()
495 def _convert_tfm_to_pt(self, length):
496 return (16*long(round(length*float(self.q)*self.tfmconv))/16777216) * self.pyxconv
498 # routines returning lengths as integers in dvi units
500 def getwidth_dvi(self, charcode):
501 return self._convert_tfm_to_dvi(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
503 def getheight_dvi(self, charcode):
504 return self._convert_tfm_to_dvi(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
506 def getdepth_dvi(self, charcode):
507 return self._convert_tfm_to_dvi(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
509 def getitalic_dvi(self, charcode):
510 return self._convert_tfm_to_dvi(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
512 # routines returning lengths as integers in design size (AFM) units
514 def getwidth_ds(self, charcode):
515 return self._convert_tfm_to_ds(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
517 def getheight_ds(self, charcode):
518 return self._convert_tfm_to_ds(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
520 def getdepth_ds(self, charcode):
521 return self._convert_tfm_to_ds(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
523 def getitalic_ds(self, charcode):
524 return self._convert_tfm_to_ds(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
526 # routines returning lengths as floats in PostScript points
528 def getwidth_pt(self, charcode):
529 return self._convert_tfm_to_pt(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
531 def getheight_pt(self, charcode):
532 return self._convert_tfm_to_pt(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
534 def getdepth_pt(self, charcode):
535 return self._convert_tfm_to_pt(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
537 def getitalic_pt(self, charcode):
538 return self._convert_tfm_to_pt(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
541 class virtualfont(font):
542 def __init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug=0):
543 fontpath = pykpathsea.find_file(name, pykpathsea.kpse_vf_format)
544 if fontpath is None or not len(fontpath):
545 raise RuntimeError
546 font.__init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug)
547 self.vffile = vffile(fontpath, self.scale, tfmconv, pyxconv, fontmap, debug > 1)
549 def getfonts(self):
550 """ return fonts used in virtual font itself """
551 return self.vffile.getfonts()
553 def getchar(self, cc):
554 """ return dvi chunk corresponding to char code cc """
555 return self.vffile.getchar(cc)
558 ##############################################################################
559 # DVI file handling
560 ##############################################################################
562 _DVI_CHARMIN = 0 # typeset a character and move right (range min)
563 _DVI_CHARMAX = 127 # typeset a character and move right (range max)
564 _DVI_SET1234 = 128 # typeset a character and move right
565 _DVI_SETRULE = 132 # typeset a rule and move right
566 _DVI_PUT1234 = 133 # typeset a character
567 _DVI_PUTRULE = 137 # typeset a rule
568 _DVI_NOP = 138 # no operation
569 _DVI_BOP = 139 # beginning of page
570 _DVI_EOP = 140 # ending of page
571 _DVI_PUSH = 141 # save the current positions (h, v, w, x, y, z)
572 _DVI_POP = 142 # restore positions (h, v, w, x, y, z)
573 _DVI_RIGHT1234 = 143 # move right
574 _DVI_W0 = 147 # move right by w
575 _DVI_W1234 = 148 # move right and set w
576 _DVI_X0 = 152 # move right by x
577 _DVI_X1234 = 153 # move right and set x
578 _DVI_DOWN1234 = 157 # move down
579 _DVI_Y0 = 161 # move down by y
580 _DVI_Y1234 = 162 # move down and set y
581 _DVI_Z0 = 166 # move down by z
582 _DVI_Z1234 = 167 # move down and set z
583 _DVI_FNTNUMMIN = 171 # set current font (range min)
584 _DVI_FNTNUMMAX = 234 # set current font (range max)
585 _DVI_FNT1234 = 235 # set current font
586 _DVI_SPECIAL1234 = 239 # special (dvi extention)
587 _DVI_FNTDEF1234 = 243 # define the meaning of a font number
588 _DVI_PRE = 247 # preamble
589 _DVI_POST = 248 # postamble beginning
590 _DVI_POSTPOST = 249 # postamble ending
592 _DVI_VERSION = 2 # dvi version
594 # position variable indices
595 _POS_H = 0
596 _POS_V = 1
597 _POS_W = 2
598 _POS_X = 3
599 _POS_Y = 4
600 _POS_Z = 5
602 # reader states
603 _READ_PRE = 1
604 _READ_NOPAGE = 2
605 _READ_PAGE = 3
606 _READ_POST = 4 # XXX not used
607 _READ_POSTPOST = 5 # XXX not used
608 _READ_DONE = 6
611 class DVIError(exceptions.Exception): pass
613 # save and restore colors
615 class _savecolor(canvas.canvasitem):
616 def outputPS(self, file, writer, context):
617 file.write("currentcolor currentcolorspace\n")
619 def outputPDF(self, file, writer, context):
620 file.write("q\n")
623 class _restorecolor(canvas.canvasitem):
624 def outputPS(self, file, writer, context):
625 file.write("setcolorspace setcolor\n")
627 def outputPDF(self, file, writer, context):
628 file.write("Q\n")
630 class _savetrafo(canvas.canvasitem):
631 def outputPS(self, file, writer, context):
632 file.write("matrix currentmatrix\n")
634 def outputPDF(self, file, writer, context):
635 file.write("q\n")
638 class _restoretrafo(canvas.canvasitem):
639 def outputPS(self, file, writer, context):
640 file.write("setmatrix\n")
642 def outputPDF(self, file, writer, context):
643 file.write("Q\n")
646 class dvifile:
648 def __init__(self, filename, fontmap, debug=0, debugfile=sys.stdout):
649 """ opens the dvi file and reads the preamble """
650 self.filename = filename
651 self.fontmap = fontmap
652 self.debug = debug
653 self.debugfile = debugfile
654 self.debugstack = []
656 self.fonts = {}
657 self.activefont = None
659 # stack of fonts and fontscale currently used (used for VFs)
660 self.fontstack = []
661 self.stack = []
663 # pointer to currently active page
664 self.actpage = None
666 # stack for self.file, self.fonts and self.stack, needed for VF inclusion
667 self.statestack = []
669 self.file = binfile(self.filename, "rb")
671 # currently read byte in file (for debugging output)
672 self.filepos = None
674 self._read_pre()
676 # helper routines
678 def flushtext(self):
679 """ finish currently active text object """
680 if self.debug and self.activetext:
681 self.debugfile.write("[%s]\n" % "".join([chr(char) for char in self.activetext.chars]))
682 self.activetext = None
684 def putrule(self, height, width, advancepos=1):
685 self.flushtext()
686 x1 = self.pos[_POS_H] * self.pyxconv
687 y1 = -self.pos[_POS_V] * self.pyxconv
688 w = width * self.pyxconv
689 h = height * self.pyxconv
691 if height > 0 and width > 0:
692 if self.debug:
693 self.debugfile.write("%d: %srule height %d, width %d (???x??? pixels)\n" %
694 (self.filepos, advancepos and "set" or "put", height, width))
695 self.actpage.fill(path.rect_pt(x1, y1, w, h))
696 else:
697 if self.debug:
698 self.debugfile.write("%d: %srule height %d, width %d (invisible)\n" %
699 (self.filepos, advancepos and "set" or "put", height, width))
701 if advancepos:
702 if self.debug:
703 self.debugfile.write(" h:=%d+%d=%d, hh:=???\n" %
704 (self.pos[_POS_H], width, self.pos[_POS_H]+width))
705 self.pos[_POS_H] += width
707 def putchar(self, char, advancepos=1, id1234=0):
708 dx = advancepos and self.activefont.getwidth_dvi(char) or 0
710 if self.debug:
711 self.debugfile.write("%d: %s%s%d h:=%d+%d=%d, hh:=???\n" %
712 (self.filepos,
713 advancepos and "set" or "put",
714 id1234 and "%i " % id1234 or "char",
715 char,
716 self.pos[_POS_H], dx, self.pos[_POS_H]+dx))
718 if isinstance(self.activefont, virtualfont):
719 # virtual font handling
720 afterpos = list(self.pos)
721 afterpos[_POS_H] += dx
722 self._push_dvistring(self.activefont.getchar(char), self.activefont.getfonts(), afterpos,
723 self.activefont.getsize_pt())
724 else:
725 if self.activetext is None:
726 if not self.fontmap.has_key(self.activefont.name):
727 raise RuntimeError("missing font information for '%s'; check fontmapping file(s)" % self.activefont.name)
728 fontmapinfo = self.fontmap[self.activefont.name]
730 encodingname = fontmapinfo.reencodefont
731 if encodingname is not None:
732 encodingfilename = pykpathsea.find_file(fontmapinfo.encodingfile, pykpathsea.kpse_tex_ps_header_format)
733 if not encodingfilename:
734 raise RuntimeError("cannot find font encoding file %s" % fontmapinfo.encodingfile)
735 fontencoding = type1font.encoding(encodingname, encodingfilename)
736 else:
737 fontencoding = None
739 fontbasefontname = fontmapinfo.basepsname
740 if fontmapinfo.fontfile is not None:
741 fontfilename = pykpathsea.find_file(fontmapinfo.fontfile, pykpathsea.kpse_type1_format)
742 if not fontfilename:
743 raise RuntimeError("cannot find type 1 font %s" % fontmapinfo.fontfile)
744 else:
745 fontfilename = None
747 # XXX we currently misuse use self.activefont as metric
748 font = type1font.font(fontbasefontname, fontfilename, fontencoding, self.activefont)
750 self.activetext = type1font.text_pt(self.pos[_POS_H] * self.pyxconv, -self.pos[_POS_V] * self.pyxconv, font)
751 self.actpage.insert(self.activetext)
752 self.activetext.addchar(char)
753 self.pos[_POS_H] += dx
755 if not advancepos:
756 self.flushtext()
758 def usefont(self, fontnum, id1234=0):
759 self.flushtext()
760 self.activefont = self.fonts[fontnum]
761 if self.debug:
762 self.debugfile.write("%d: fnt%s%i current font is %s\n" %
763 (self.filepos,
764 id1234 and "%i " % id1234 or "num",
765 fontnum,
766 self.fonts[fontnum].name))
769 def definefont(self, cmdnr, num, c, q, d, fontname):
770 # cmdnr: type of fontdef command (only used for debugging output)
771 # c: checksum
772 # q: scaling factor (fix_word)
773 # Note that q is actually s in large parts of the documentation.
774 # d: design size (fix_word)
776 try:
777 afont = virtualfont(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
778 except (TypeError, RuntimeError):
779 afont = font(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
781 self.fonts[num] = afont
783 if self.debug:
784 self.debugfile.write("%d: fntdef%d %i: %s\n" % (self.filepos, cmdnr, num, fontname))
786 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
787 # m = 1.0*q/d
788 # scalestring = scale!=1000 and " scaled %d" % scale or ""
789 # print ("Font %i: %s%s---loaded at size %d DVI units" %
790 # (num, fontname, scalestring, q))
791 # if scale!=1000:
792 # print " (this font is magnified %d%%)" % round(scale/10)
794 def special(self, s):
795 x = self.pos[_POS_H] * self.pyxconv
796 y = -self.pos[_POS_V] * self.pyxconv
797 if self.debug:
798 self.debugfile.write("%d: xxx '%s'\n" % (self.filepos, s))
799 if not s.startswith("PyX:"):
800 warnings.warn("ignoring special '%s'" % s)
801 return
803 # it is in general not safe to continue using the currently active font because
804 # the specials may involve some gsave/grestore operations
805 self.flushtext()
807 command, args = s[4:].split()[0], s[4:].split()[1:]
808 if command == "color_begin":
809 if args[0] == "cmyk":
810 c = color.cmyk(float(args[1]), float(args[2]), float(args[3]), float(args[4]))
811 elif args[0] == "gray":
812 c = color.gray(float(args[1]))
813 elif args[0] == "hsb":
814 c = color.hsb(float(args[1]), float(args[2]), float(args[3]))
815 elif args[0] == "rgb":
816 c = color.rgb(float(args[1]), float(args[2]), float(args[3]))
817 elif args[0] == "RGB":
818 c = color.rgb(int(args[1])/255.0, int(args[2])/255.0, int(args[3])/255.0)
819 elif args[0] == "texnamed":
820 try:
821 c = getattr(color.cmyk, args[1])
822 except AttributeError:
823 raise RuntimeError("unknown TeX color '%s', aborting" % args[1])
824 else:
825 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args[0])
826 self.actpage.insert(_savecolor())
827 self.actpage.insert(c)
828 elif command == "color_end":
829 self.actpage.insert(_restorecolor())
830 elif command == "rotate_begin":
831 self.actpage.insert(_savetrafo())
832 self.actpage.insert(trafo.rotate_pt(float(args[0]), x, y))
833 elif command == "rotate_end":
834 self.actpage.insert(_restoretrafo())
835 elif command == "scale_begin":
836 self.actpage.insert(_savetrafo())
837 self.actpage.insert(trafo.scale_pt(float(args[0]), float(args[1]), x, y))
838 elif command == "scale_end":
839 self.actpage.insert(_restoretrafo())
840 elif command == "epsinclude":
841 # parse arguments
842 argdict = {}
843 for arg in args:
844 name, value = arg.split("=")
845 argdict[name] = value
847 # construct kwargs for epsfile constructor
848 epskwargs = {}
849 epskwargs["filename"] = argdict["file"]
850 epskwargs["bbox"] = bbox.bbox_pt(float(argdict["llx"]), float(argdict["lly"]),
851 float(argdict["urx"]), float(argdict["ury"]))
852 if argdict.has_key("width"):
853 epskwargs["width"] = float(argdict["width"]) * unit.t_pt
854 if argdict.has_key("height"):
855 epskwargs["height"] = float(argdict["height"]) * unit.t_pt
856 if argdict.has_key("clip"):
857 epskwargs["clip"] = int(argdict["clip"])
858 self.actpage.insert(epsfile.epsfile(x * unit.t_pt, y * unit.t_pt, **epskwargs))
859 elif command == "marker":
860 if len(args) != 1:
861 raise RuntimeError("marker contains spaces")
862 for c in args[0]:
863 if c not in string.digits + string.letters + "@":
864 raise RuntimeError("marker contains invalid characters")
865 if self.actpage.markers.has_key(args[0]):
866 raise RuntimeError("marker name occurred several times")
867 self.actpage.markers[args[0]] = x * unit.t_pt, y * unit.t_pt
868 else:
869 raise RuntimeError("unknown PyX special '%s', aborting" % command)
871 # routines for pushing and popping different dvi chunks on the reader
873 def _push_dvistring(self, dvi, fonts, afterpos, fontsize):
874 """ push dvi string with defined fonts on top of reader
875 stack. Every positions gets scaled relatively by the factor
876 scale. After the interpreting of the dvi chunk has been finished,
877 continue with self.pos=afterpos. The designsize of the virtual
878 font is passed as a fix_word
882 #if self.debug:
883 # self.debugfile.write("executing new dvi chunk\n")
884 self.debugstack.append(self.debug)
885 self.debug = 0
887 self.statestack.append((self.file, self.fonts, self.activefont, afterpos, self.stack, self.pyxconv, self.tfmconv))
889 # units in vf files are relative to the size of the font and given as fix_words
890 # which can be converted to floats by diving by 2**20
891 oldpyxconv = self.pyxconv
892 self.pyxconv = fontsize/2**20
893 rescale = self.pyxconv/oldpyxconv
895 self.file = stringbinfile(dvi)
896 self.fonts = fonts
897 self.stack = []
898 self.filepos = 0
900 # rescale self.pos in order to be consistent with the new scaling
901 self.pos = map(lambda x, rescale=rescale:1.0*x/rescale, self.pos)
903 # since tfmconv converts from tfm units to dvi units, rescale it as well
904 self.tfmconv /= rescale
906 self.usefont(0)
908 def _pop_dvistring(self):
909 self.flushtext()
910 #if self.debug:
911 # self.debugfile.write("finished executing dvi chunk\n")
912 self.debug = self.debugstack.pop()
914 self.file.close()
915 self.file, self.fonts, self.activefont, self.pos, self.stack, self.pyxconv, self.tfmconv = self.statestack.pop()
917 # routines corresponding to the different reader states of the dvi maschine
919 def _read_pre(self):
920 afile = self.file
921 while 1:
922 self.filepos = afile.tell()
923 cmd = afile.readuchar()
924 if cmd == _DVI_NOP:
925 pass
926 elif cmd == _DVI_PRE:
927 if afile.readuchar() != _DVI_VERSION: raise DVIError
928 num = afile.readuint32()
929 den = afile.readuint32()
930 self.mag = afile.readuint32()
932 # For the interpretation of the lengths in dvi and tfm files,
933 # three conversion factors are relevant:
934 # - self.tfmconv: tfm units -> dvi units
935 # - self.pyxconv: dvi units -> (PostScript) points
936 # - self.conv: dvi units -> pixels
937 self.tfmconv = (25400000.0/num)*(den/473628672.0)/16.0
939 # calculate conv as described in the DVIType docu using
940 # a given resolution in dpi
941 self.resolution = 300.0
942 self.conv = (num/254000.0)*(self.resolution/den)
944 # self.pyxconv is the conversion factor from the dvi units
945 # to (PostScript) points. It consists of
946 # - self.mag/1000.0: magstep scaling
947 # - self.conv: conversion from dvi units to pixels
948 # - 1/self.resolution: conversion from pixels to inch
949 # - 72 : conversion from inch to points
950 self.pyxconv = self.mag/1000.0*self.conv/self.resolution*72
952 comment = afile.read(afile.readuchar())
953 return
954 else:
955 raise DVIError
957 def readpage(self, pageid=None):
958 """ reads a page from the dvi file
960 This routine reads a page from the dvi file which is
961 returned as a canvas. When there is no page left in the
962 dvifile, None is returned and the file is closed properly."""
964 while 1:
965 self.filepos = self.file.tell()
966 cmd = self.file.readuchar()
967 if cmd == _DVI_NOP:
968 pass
969 elif cmd == _DVI_BOP:
970 ispageid = [self.file.readuint32() for i in range(10)]
971 if pageid is not None and ispageid != pageid:
972 raise DVIError("invalid pageid")
973 if self.debug:
974 self.debugfile.write("%d: beginning of page %i\n" % (self.filepos, ispageid[0]))
975 self.file.readuint32()
976 break
977 elif cmd == _DVI_POST:
978 self.file.close()
979 return None # nothing left
980 else:
981 raise DVIError
983 self.actpage = canvas.canvas()
984 self.actpage.markers = {}
985 self.pos = [0, 0, 0, 0, 0, 0]
987 # currently active output: text instance currently used
988 self.activetext = None
990 while 1:
991 afile = self.file
992 self.filepos = afile.tell()
993 try:
994 cmd = afile.readuchar()
995 except struct.error:
996 # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
997 # so we have to continue with the rest of the dvi file
998 self._pop_dvistring()
999 continue
1000 if cmd == _DVI_NOP:
1001 pass
1002 if cmd >= _DVI_CHARMIN and cmd <= _DVI_CHARMAX:
1003 self.putchar(cmd)
1004 elif cmd >= _DVI_SET1234 and cmd < _DVI_SET1234 + 4:
1005 self.putchar(afile.readint(cmd - _DVI_SET1234 + 1), id1234=cmd-_DVI_SET1234+1)
1006 elif cmd == _DVI_SETRULE:
1007 self.putrule(afile.readint32(), afile.readint32())
1008 elif cmd >= _DVI_PUT1234 and cmd < _DVI_PUT1234 + 4:
1009 self.putchar(afile.readint(cmd - _DVI_PUT1234 + 1), advancepos=0, id1234=cmd-_DVI_SET1234+1)
1010 elif cmd == _DVI_PUTRULE:
1011 self.putrule(afile.readint32(), afile.readint32(), 0)
1012 elif cmd == _DVI_EOP:
1013 self.flushtext()
1014 if self.debug:
1015 self.debugfile.write("%d: eop\n \n" % self.filepos)
1016 return self.actpage
1017 elif cmd == _DVI_PUSH:
1018 self.stack.append(list(self.pos))
1019 if self.debug:
1020 self.debugfile.write("%s: push\n"
1021 "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1022 ((self.filepos, len(self.stack)-1) + tuple(self.pos)))
1023 elif cmd == _DVI_POP:
1024 self.flushtext()
1025 self.pos = self.stack.pop()
1026 if self.debug:
1027 self.debugfile.write("%s: pop\n"
1028 "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1029 ((self.filepos, len(self.stack)) + tuple(self.pos)))
1030 elif cmd >= _DVI_RIGHT1234 and cmd < _DVI_RIGHT1234 + 4:
1031 self.flushtext()
1032 dh = afile.readint(cmd - _DVI_RIGHT1234 + 1, 1)
1033 if self.debug:
1034 self.debugfile.write("%d: right%d %d h:=%d%+d=%d, hh:=???\n" %
1035 (self.filepos,
1036 cmd - _DVI_RIGHT1234 + 1,
1038 self.pos[_POS_H],
1040 self.pos[_POS_H]+dh))
1041 self.pos[_POS_H] += dh
1042 elif cmd == _DVI_W0:
1043 self.flushtext()
1044 if self.debug:
1045 self.debugfile.write("%d: w0 %d h:=%d%+d=%d, hh:=???\n" %
1046 (self.filepos,
1047 self.pos[_POS_W],
1048 self.pos[_POS_H],
1049 self.pos[_POS_W],
1050 self.pos[_POS_H]+self.pos[_POS_W]))
1051 self.pos[_POS_H] += self.pos[_POS_W]
1052 elif cmd >= _DVI_W1234 and cmd < _DVI_W1234 + 4:
1053 self.flushtext()
1054 self.pos[_POS_W] = afile.readint(cmd - _DVI_W1234 + 1, 1)
1055 if self.debug:
1056 self.debugfile.write("%d: w%d %d h:=%d%+d=%d, hh:=???\n" %
1057 (self.filepos,
1058 cmd - _DVI_W1234 + 1,
1059 self.pos[_POS_W],
1060 self.pos[_POS_H],
1061 self.pos[_POS_W],
1062 self.pos[_POS_H]+self.pos[_POS_W]))
1063 self.pos[_POS_H] += self.pos[_POS_W]
1064 elif cmd == _DVI_X0:
1065 self.flushtext()
1066 if self.debug:
1067 self.debugfile.write("%d: x0 %d h:=%d%+d=%d, hh:=???\n" %
1068 (self.filepos,
1069 self.pos[_POS_X],
1070 self.pos[_POS_H],
1071 self.pos[_POS_X],
1072 self.pos[_POS_H]+self.pos[_POS_X]))
1073 self.pos[_POS_H] += self.pos[_POS_X]
1074 elif cmd >= _DVI_X1234 and cmd < _DVI_X1234 + 4:
1075 self.flushtext()
1076 self.pos[_POS_X] = afile.readint(cmd - _DVI_X1234 + 1, 1)
1077 if self.debug:
1078 self.debugfile.write("%d: x%d %d h:=%d%+d=%d, hh:=???\n" %
1079 (self.filepos,
1080 cmd - _DVI_X1234 + 1,
1081 self.pos[_POS_X],
1082 self.pos[_POS_H],
1083 self.pos[_POS_X],
1084 self.pos[_POS_H]+self.pos[_POS_X]))
1085 self.pos[_POS_H] += self.pos[_POS_X]
1086 elif cmd >= _DVI_DOWN1234 and cmd < _DVI_DOWN1234 + 4:
1087 self.flushtext()
1088 dv = afile.readint(cmd - _DVI_DOWN1234 + 1, 1)
1089 if self.debug:
1090 self.debugfile.write("%d: down%d %d v:=%d%+d=%d, vv:=???\n" %
1091 (self.filepos,
1092 cmd - _DVI_DOWN1234 + 1,
1094 self.pos[_POS_V],
1096 self.pos[_POS_V]+dv))
1097 self.pos[_POS_V] += dv
1098 elif cmd == _DVI_Y0:
1099 self.flushtext()
1100 if self.debug:
1101 self.debugfile.write("%d: y0 %d v:=%d%+d=%d, vv:=???\n" %
1102 (self.filepos,
1103 self.pos[_POS_Y],
1104 self.pos[_POS_V],
1105 self.pos[_POS_Y],
1106 self.pos[_POS_V]+self.pos[_POS_Y]))
1107 self.pos[_POS_V] += self.pos[_POS_Y]
1108 elif cmd >= _DVI_Y1234 and cmd < _DVI_Y1234 + 4:
1109 self.flushtext()
1110 self.pos[_POS_Y] = afile.readint(cmd - _DVI_Y1234 + 1, 1)
1111 if self.debug:
1112 self.debugfile.write("%d: y%d %d v:=%d%+d=%d, vv:=???\n" %
1113 (self.filepos,
1114 cmd - _DVI_Y1234 + 1,
1115 self.pos[_POS_Y],
1116 self.pos[_POS_V],
1117 self.pos[_POS_Y],
1118 self.pos[_POS_V]+self.pos[_POS_Y]))
1119 self.pos[_POS_V] += self.pos[_POS_Y]
1120 elif cmd == _DVI_Z0:
1121 self.flushtext()
1122 if self.debug:
1123 self.debugfile.write("%d: z0 %d v:=%d%+d=%d, vv:=???\n" %
1124 (self.filepos,
1125 self.pos[_POS_Z],
1126 self.pos[_POS_V],
1127 self.pos[_POS_Z],
1128 self.pos[_POS_V]+self.pos[_POS_Z]))
1129 self.pos[_POS_V] += self.pos[_POS_Z]
1130 elif cmd >= _DVI_Z1234 and cmd < _DVI_Z1234 + 4:
1131 self.flushtext()
1132 self.pos[_POS_Z] = afile.readint(cmd - _DVI_Z1234 + 1, 1)
1133 if self.debug:
1134 self.debugfile.write("%d: z%d %d v:=%d%+d=%d, vv:=???\n" %
1135 (self.filepos,
1136 cmd - _DVI_Z1234 + 1,
1137 self.pos[_POS_Z],
1138 self.pos[_POS_V],
1139 self.pos[_POS_Z],
1140 self.pos[_POS_V]+self.pos[_POS_Z]))
1141 self.pos[_POS_V] += self.pos[_POS_Z]
1142 elif cmd >= _DVI_FNTNUMMIN and cmd <= _DVI_FNTNUMMAX:
1143 self.usefont(cmd - _DVI_FNTNUMMIN, 0)
1144 elif cmd >= _DVI_FNT1234 and cmd < _DVI_FNT1234 + 4:
1145 # note that according to the DVI docs, for four byte font numbers,
1146 # the font number is signed. Don't ask why!
1147 fntnum = afile.readint(cmd - _DVI_FNT1234 + 1, cmd == _DVI_FNT1234 + 3)
1148 self.usefont(fntnum, id1234=cmd-_DVI_FNT1234+1)
1149 elif cmd >= _DVI_SPECIAL1234 and cmd < _DVI_SPECIAL1234 + 4:
1150 self.special(afile.read(afile.readint(cmd - _DVI_SPECIAL1234 + 1)))
1151 elif cmd >= _DVI_FNTDEF1234 and cmd < _DVI_FNTDEF1234 + 4:
1152 if cmd == _DVI_FNTDEF1234:
1153 num = afile.readuchar()
1154 elif cmd == _DVI_FNTDEF1234+1:
1155 num = afile.readuint16()
1156 elif cmd == _DVI_FNTDEF1234+2:
1157 num = afile.readuint24()
1158 elif cmd == _DVI_FNTDEF1234+3:
1159 # Cool, here we have according to docu a signed int. Why?
1160 num = afile.readint32()
1161 self.definefont(cmd-_DVI_FNTDEF1234+1,
1162 num,
1163 afile.readint32(),
1164 afile.readint32(),
1165 afile.readint32(),
1166 afile.read(afile.readuchar()+afile.readuchar()))
1167 else:
1168 raise DVIError
1171 ##############################################################################
1172 # VF file handling
1173 ##############################################################################
1175 _VF_LONG_CHAR = 242 # character packet (long version)
1176 _VF_FNTDEF1234 = _DVI_FNTDEF1234 # font definition
1177 _VF_PRE = _DVI_PRE # preamble
1178 _VF_POST = _DVI_POST # postamble
1180 _VF_ID = 202 # VF id byte
1182 class VFError(exceptions.Exception): pass
1184 class vffile:
1185 def __init__(self, filename, scale, tfmconv, pyxconv, fontmap, debug=0):
1186 self.filename = filename
1187 self.scale = scale
1188 self.tfmconv = tfmconv
1189 self.pyxconv = pyxconv
1190 self.fontmap = fontmap
1191 self.debug = debug
1192 self.fonts = {} # used fonts
1193 self.widths = {} # widths of defined chars
1194 self.chardefs = {} # dvi chunks for defined chars
1196 afile = binfile(self.filename, "rb")
1198 cmd = afile.readuchar()
1199 if cmd == _VF_PRE:
1200 if afile.readuchar() != _VF_ID: raise VFError
1201 comment = afile.read(afile.readuchar())
1202 self.cs = afile.readuint32()
1203 self.ds = afile.readuint32()
1204 else:
1205 raise VFError
1207 while 1:
1208 cmd = afile.readuchar()
1209 if cmd >= _VF_FNTDEF1234 and cmd < _VF_FNTDEF1234 + 4:
1210 # font definition
1211 if cmd == _VF_FNTDEF1234:
1212 num = afile.readuchar()
1213 elif cmd == _VF_FNTDEF1234+1:
1214 num = afile.readuint16()
1215 elif cmd == _VF_FNTDEF1234+2:
1216 num = afile.readuint24()
1217 elif cmd == _VF_FNTDEF1234+3:
1218 num = afile.readint32()
1219 c = afile.readint32()
1220 s = afile.readint32() # relative scaling used for font (fix_word)
1221 d = afile.readint32() # design size of font
1222 fontname = afile.read(afile.readuchar()+afile.readuchar())
1224 # rescaled size of font: s is relative to the scaling
1225 # of the virtual font itself. Note that realscale has
1226 # to be a fix_word (like s)
1227 # XXX: check rounding
1228 reals = int(round(self.scale * (16*self.ds/16777216L) * s))
1230 # print ("defining font %s -- VF scale: %g, VF design size: %d, relative font size: %d => real size: %d" %
1231 # (fontname, self.scale, self.ds, s, reals)
1234 # XXX allow for virtual fonts here too
1235 self.fonts[num] = font(fontname, c, reals, d, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
1236 elif cmd == _VF_LONG_CHAR:
1237 # character packet (long form)
1238 pl = afile.readuint32() # packet length
1239 cc = afile.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1240 tfm = afile.readuint24() # character width
1241 dvi = afile.read(pl) # dvi code of character
1242 self.widths[cc] = tfm
1243 self.chardefs[cc] = dvi
1244 elif cmd < _VF_LONG_CHAR:
1245 # character packet (short form)
1246 cc = afile.readuchar() # char code
1247 tfm = afile.readuint24() # character width
1248 dvi = afile.read(cmd)
1249 self.widths[cc] = tfm
1250 self.chardefs[cc] = dvi
1251 elif cmd == _VF_POST:
1252 break
1253 else:
1254 raise VFError
1256 afile.close()
1258 def getfonts(self):
1259 return self.fonts
1261 def getchar(self, cc):
1262 return self.chardefs[cc]