make all parts of the manual compile again; parts of the manual are still out of...
[PyX/mjg.git] / pyx / text.py
blob0e657fd0180acaf8d135f41f241779551be53aa3
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, glob, os, threading, Queue, traceback, re, struct, string, tempfile, sys, atexit, time
26 import config, helper, unit, bbox, box, base, canvas, color, trafo, path, prolog, pykpathsea, version, attr, style
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 self.filename = filename
730 self.fontmap = fontmap
731 self.debug = debug
733 # helper routines
735 def flushout(self):
736 """ flush currently active string """
737 if self.actoutstart:
738 x = self.actoutstart[0] * self.conv
739 y = -self.actoutstart[1] * self.conv
740 if self.debug:
741 print "[%s]" % self.actoutstring
742 self.actpage.insert(_show(x, y, self.actoutstring))
743 self.actoutstart = None
745 def putrule(self, height, width, inch=1):
746 self.flushout()
747 x1 = self.pos[_POS_H] * self.conv
748 y1 = -self.pos[_POS_V] * self.conv
749 w = width * self.conv
750 h = height * self.conv
752 if height > 0 and width > 0:
753 if self.debug:
754 pixelw = int(width*self.trueconv*self.mag/1000.0)
755 if pixelw < width*self.conv: pixelw += 1
756 pixelh = int(height*self.trueconv*self.mag/1000.0)
757 if pixelh < height*self.conv: pixelh += 1
759 print ("%d: %srule height %d, width %d (%dx%d pixels)" %
760 (self.filepos, inch and "set" or "put", height, width, pixelh, pixelw))
761 self.actpage.fill(path._rect(x1, y1, w, h))
762 else:
763 if self.debug:
764 print ("%d: %srule height %d, width %d (invisible)" %
765 (self.filepos, inch and "set" or "put", height, width))
767 if inch:
768 if self.debug:
769 print (" h:=%d+%d=%d, hh:=%d" %
770 (self.pos[_POS_H], width, self.pos[_POS_H]+width, 0))
771 self.pos[_POS_H] += width
773 def putchar(self, char, inch=1):
774 dx = inch and int(round(self.activefont.getwidth(char)*self.tfmconv)) or 0
776 if self.debug:
777 print ("%d: %schar%d h:=%d+%d=%d, hh:=%d" %
778 (self.filepos,
779 inch and "set" or "put",
780 char,
781 self.pos[_POS_H], dx, self.pos[_POS_H]+dx,
784 if isinstance(self.activefont, type1font):
785 if self.actoutstart is None:
786 self.actoutstart = self.pos[_POS_H], self.pos[_POS_V]
787 self.actoutstring = ""
788 if char > 32 and char < 127 and chr(char) not in "()[]<>":
789 ascii = "%s" % chr(char)
790 else:
791 ascii = "\\%03o" % char
792 self.actoutstring = self.actoutstring + ascii
794 self.activefont.markcharused(char)
795 self.pos[_POS_H] += dx
796 else:
797 # virtual font handling
798 afterpos = list(self.pos)
799 afterpos[_POS_H] += dx
800 self._push_dvistring(self.activefont.getchar(char), self.activefont.getfonts(), afterpos,
801 self.activefont.getsize())
804 if not inch:
805 # XXX: correct !?
806 self.flushout()
808 def usefont(self, fontnum):
809 if self.debug:
810 print ("%d: fntnum%i current font is %s" %
811 (self.filepos,
812 fontnum, self.fonts[fontnum].name))
814 self.activefont = self.fonts[fontnum]
816 # if the new font is a type 1 font and if it is not already the
817 # font being currently used for the output, we have to flush the
818 # output and insert a selectfont statement in the PS code
819 if isinstance(self.activefont, type1font) and self.actoutfont!=self.activefont:
820 self.flushout()
821 self.actpage.insert(selectfont(self.activefont))
822 self.actoutfont = self.activefont
824 def definefont(self, cmdnr, num, c, q, d, fontname):
825 # cmdnr: type of fontdef command (only used for debugging output)
826 # c: checksum
827 # q: scaling factor (fix_word)
828 # Note that q is actually s in large parts of the documentation.
829 # d: design size (fix_word)
831 try:
832 font = virtualfont(fontname, c, q/self.tfmconv, d/self.tfmconv, self.fontmap, self.debug > 1)
833 except (TypeError, RuntimeError):
834 font = type1font(fontname, c, q/self.tfmconv, d/self.tfmconv, self.fontmap, self.debug > 1)
836 self.fonts[num] = font
838 if self.debug:
839 print "%d: fntdef%d %i: %s" % (self.filepos, cmdnr, num, fontname)
841 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
842 # m = 1.0*q/d
843 # scalestring = scale!=1000 and " scaled %d" % scale or ""
844 # print ("Font %i: %s%s---loaded at size %d DVI units" %
845 # (num, fontname, scalestring, q))
846 # if scale!=1000:
847 # print " (this font is magnified %d%%)" % round(scale/10)
849 def special(self, s):
850 self.flushout()
852 # it is in general not safe to continue using the currently active font because
853 # the specials may involve some gsave/grestore operations
854 # XXX: reset actoutfont only where strictly needed
855 self.actoutfont = None
857 x = self.pos[_POS_H] * self.conv
858 y = -self.pos[_POS_V] * self.conv
859 if self.debug:
860 print "%d: xxx '%s'" % (self.filepos, s)
861 if not s.startswith("PyX:"):
862 if s.startswith("Warning:"):
863 sys.stderr.write("*** PyX Warning: ignoring special '%s'\n" % s)
864 return
865 else:
866 raise RuntimeError("the special '%s' cannot be handled by PyX, aborting" % s)
867 command, args = s[4:].split()[0], s[4:].split()[1:]
868 if command=="color_begin":
869 if args[0]=="cmyk":
870 c = color.cmyk(float(args[1]), float(args[2]), float(args[3]), float(args[4]))
871 elif args[0]=="gray":
872 c = color.gray(float(args[1]))
873 elif args[0]=="hsb":
874 c = color.hsb(float(args[1]), float(args[2]), float(args[3]))
875 elif args[0]=="rgb":
876 c = color.rgb(float(args[1]), float(args[2]), float(args[3]))
877 elif args[0]=="RGB":
878 c = color.rgb(int(args[1])/255.0, int(args[2])/255.0, int(args[3])/255.0)
879 elif args[0]=="texnamed":
880 try:
881 c = getattr(color.cmyk, args[1])
882 except AttributeError:
883 raise RuntimeError("unknown TeX color '%s', aborting" % args[1])
884 else:
885 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args[0])
886 self.actpage.insert(_savecolor())
887 self.actpage.insert(c)
888 elif command=="color_end":
889 self.actpage.insert(_restorecolor())
890 elif command=="rotate_begin":
891 self.actpage.insert(_savetrafo())
892 self.actpage.insert(trafo._rotate(float(args[0]), x, y))
893 elif command=="rotate_end":
894 self.actpage.insert(_restoretrafo())
895 elif command=="scale_begin":
896 self.actpage.insert(_savetrafo())
897 self.actpage.insert(trafo._scale(float(args[0]), float(args[1]), x, y))
898 elif command=="scale_end":
899 self.actpage.insert(_restoretrafo())
900 elif command=="epsinclude":
901 # XXX: we cannot include epsfile in the header because this would
902 # generate a cyclic import with the canvas and text modules
903 import epsfile
905 # parse arguments
906 argdict = {}
907 for arg in args:
908 name, value = arg.split("=")
909 argdict[name] = value
911 # construct kwargs for epsfile constructor
912 epskwargs = {}
913 epskwargs["filename"] = argdict["file"]
914 epskwargs["bbox"] = bbox._bbox(float(argdict["llx"]), float(argdict["lly"]),
915 float(argdict["urx"]), float(argdict["ury"]))
916 if argdict.has_key("width"):
917 epskwargs["width"] = unit.t_pt(float(argdict["width"]))
918 if argdict.has_key("height"):
919 epskwargs["height"] = unit.t_pt(float(argdict["height"]))
920 if argdict.has_key("clip"):
921 epskwargs["clip"] = int(argdict["clip"])
922 self.actpage.insert(epsfile.epsfile(unit.t_pt(x), unit.t_pt(y), **epskwargs))
923 elif command=="marker":
924 if len(args) != 1:
925 raise RuntimeError("marker contains spaces")
926 for c in args[0]:
927 if c not in string.digits + string.letters + "@":
928 raise RuntimeError("marker contains invalid characters")
929 if self.actpage.markers.has_key(args[0]):
930 raise RuntimeError("marker name occurred several times")
931 self.actpage.markers[args[0]] = unit.t_pt(x), unit.t_pt(y)
932 else:
933 raise RuntimeError("unknown PyX special '%s', aborting" % command)
935 # routines for pushing and popping different dvi chunks on the reader
937 def _push_dvistring(self, dvi, fonts, afterpos, fontsize):
938 """ push dvi string with defined fonts on top of reader
939 stack. Every positions gets scaled relatively by the factor
940 scale. When finished with interpreting the dvi chunk, we
941 continue with self.pos=afterpos. The designsize of the virtual
942 font is passed as a fix_word
946 if self.debug:
947 print "executing new dvi chunk"
948 self.statestack.append((self.file, self.fonts, self.activefont, afterpos, self.stack, self.conv, self.tfmconv))
950 # units in vf files are relative to the size of the font and given as fix_words
951 # which can be converted to floats by diving by 2**20
952 oldconv = self.conv
953 self.conv = fontsize/2**20
954 rescale = self.conv/oldconv
956 self.file = stringbinfile(dvi)
957 self.fonts = fonts
958 self.stack = []
959 self.filepos = 0
961 # we have to rescale self.pos in order to be consistent with the new scaling
962 self.pos = map(lambda x, rescale=rescale:1.0*x/rescale, self.pos)
964 # since tfmconv converts from tfm units to dvi units, we have to rescale it as well
965 self.tfmconv /= rescale
967 self.usefont(0)
969 def _pop_dvistring(self):
970 self.flushout()
971 if self.debug:
972 print "finished executing dvi chunk"
973 self.file.close()
974 self.file, self.fonts, self.activefont, self.pos, self.stack, self.conv, self.tfmconv = self.statestack.pop()
976 # routines corresponding to the different reader states of the dvi maschine
978 def _read_pre(self):
979 file = self.file
980 while 1:
981 self.filepos = file.tell()
982 cmd = file.readuchar()
983 if cmd == _DVI_NOP:
984 pass
985 elif cmd == _DVI_PRE:
986 if self.file.readuchar() != _DVI_VERSION: raise DVIError
987 num = file.readuint32()
988 den = file.readuint32()
989 self.mag = file.readuint32()
991 # for the interpretation of all quantities, two conversion factors
992 # are relevant:
993 # - self.tfmconv (tfm units->dvi units)
994 # - self.conv (dvi units-> (PostScript) points)
996 self.tfmconv = (25400000.0/num)*(den/473628672.0)/16.0
998 # calculate self.conv as described in the DVIType docu
1000 # resolution in dpi
1001 self.resolution = 300.0
1002 # self.trueconv = conv in DVIType docu
1003 self.trueconv = (num/254000.0)*(self.resolution/den)
1005 # self.conv is the conversion factor from the dvi units
1006 # to (PostScript) points. It consists of
1007 # - self.mag/1000.0: magstep scaling
1008 # - self.trueconv: conversion from dvi units to pixels
1009 # - 1/self.resolution: conversion from pixels to inch
1010 # - 72 : conversion from inch to points
1011 self.conv = self.mag/1000.0*self.trueconv/self.resolution*72
1013 comment = file.read(file.readuchar())
1014 return _READ_NOPAGE
1015 else:
1016 raise DVIError
1018 def _read_nopage(self):
1019 file = self.file
1020 while 1:
1021 self.filepos = file.tell()
1022 cmd = file.readuchar()
1023 if cmd == _DVI_NOP:
1024 pass
1025 elif cmd == _DVI_BOP:
1026 self.flushout()
1027 pagenos = [file.readuint32() for i in range(10)]
1028 if pagenos[:3] != [ord("P"), ord("y"), ord("X")] or pagenos[4:] != [0, 0, 0, 0, 0, 0]:
1029 raise DVIError("Page in dvi file is not a PyX page.")
1030 if self.debug:
1031 print "%d: beginning of page %i" % (self.filepos, pagenos[0])
1032 file.readuint32()
1033 return _READ_PAGE
1034 elif cmd == _DVI_POST:
1035 return _READ_DONE # we skip the rest
1036 else:
1037 raise DVIError
1039 def _read_page(self):
1040 self.pages.append(canvas.canvas())
1041 self.actpage = self.pages[-1]
1042 self.actpage.markers = {}
1043 self.pos = [0, 0, 0, 0, 0, 0]
1044 self.actoutfont = None
1046 # Since we do not know, which dvi pages the actual PS file contains later on,
1047 # we have to keep track of used char informations separately for each dvi page.
1048 # In order to do so, the already defined fonts have to be copied and their
1049 # used char informations have to be reset
1050 for nr in self.fonts.keys():
1051 self.fonts[nr] = copy.copy(self.fonts[nr])
1052 self.fonts[nr].clearusedchars()
1054 while 1:
1055 file = self.file
1056 self.filepos = file.tell()
1057 try:
1058 cmd = file.readuchar()
1059 except struct.error:
1060 # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
1061 # so we have to continue with the rest of the dvi file
1062 self._pop_dvistring()
1063 continue
1064 if cmd == _DVI_NOP:
1065 pass
1066 if cmd >= _DVI_CHARMIN and cmd <= _DVI_CHARMAX:
1067 self.putchar(cmd)
1068 elif cmd >= _DVI_SET1234 and cmd < _DVI_SET1234 + 4:
1069 self.putchar(file.readint(cmd - _DVI_SET1234 + 1))
1070 elif cmd == _DVI_SETRULE:
1071 self.putrule(file.readint32(), file.readint32())
1072 elif cmd >= _DVI_PUT1234 and cmd < _DVI_PUT1234 + 4:
1073 self.putchar(file.readint(cmd - _DVI_PUT1234 + 1), inch=0)
1074 elif cmd == _DVI_PUTRULE:
1075 self.putrule(file.readint32(), file.readint32(), 0)
1076 elif cmd == _DVI_EOP:
1077 self.flushout()
1078 if self.debug:
1079 print "%d: eop" % self.filepos
1080 print
1081 return _READ_NOPAGE
1082 elif cmd == _DVI_PUSH:
1083 self.stack.append(list(self.pos))
1084 if self.debug:
1085 print "%d: push" % self.filepos
1086 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
1087 (( len(self.stack)-1,)+tuple(self.pos)))
1088 elif cmd == _DVI_POP:
1089 self.flushout()
1090 self.pos = self.stack.pop()
1091 if self.debug:
1092 print "%d: pop" % self.filepos
1093 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
1094 (( len(self.stack),)+tuple(self.pos)))
1095 elif cmd >= _DVI_RIGHT1234 and cmd < _DVI_RIGHT1234 + 4:
1096 self.flushout()
1097 dh = file.readint(cmd - _DVI_RIGHT1234 + 1, 1)
1098 if self.debug:
1099 print ("%d: right%d %d h:=%d%+d=%d, hh:=" %
1100 (self.filepos,
1101 cmd - _DVI_RIGHT1234 + 1,
1103 self.pos[_POS_H],
1105 self.pos[_POS_H]+dh))
1106 self.pos[_POS_H] += dh
1107 elif cmd == _DVI_W0:
1108 self.flushout()
1109 if self.debug:
1110 print ("%d: w0 %d h:=%d%+d=%d, hh:=" %
1111 (self.filepos,
1112 self.pos[_POS_W],
1113 self.pos[_POS_H],
1114 self.pos[_POS_W],
1115 self.pos[_POS_H]+self.pos[_POS_W]))
1116 self.pos[_POS_H] += self.pos[_POS_W]
1117 elif cmd >= _DVI_W1234 and cmd < _DVI_W1234 + 4:
1118 self.flushout()
1119 self.pos[_POS_W] = file.readint(cmd - _DVI_W1234 + 1, 1)
1120 if self.debug:
1121 print ("%d: w%d %d h:=%d%+d=%d, hh:=" %
1122 (self.filepos,
1123 cmd - _DVI_W1234 + 1,
1124 self.pos[_POS_W],
1125 self.pos[_POS_H],
1126 self.pos[_POS_W],
1127 self.pos[_POS_H]+self.pos[_POS_W]))
1128 self.pos[_POS_H] += self.pos[_POS_W]
1129 elif cmd == _DVI_X0:
1130 self.flushout()
1131 self.pos[_POS_H] += self.pos[_POS_X]
1132 elif cmd >= _DVI_X1234 and cmd < _DVI_X1234 + 4:
1133 self.flushout()
1134 self.pos[_POS_X] = file.readint(cmd - _DVI_X1234 + 1, 1)
1135 self.pos[_POS_H] += self.pos[_POS_X]
1136 elif cmd >= _DVI_DOWN1234 and cmd < _DVI_DOWN1234 + 4:
1137 self.flushout()
1138 dv = file.readint(cmd - _DVI_DOWN1234 + 1, 1)
1139 if self.debug:
1140 print ("%d: down%d %d v:=%d%+d=%d, vv:=" %
1141 (self.filepos,
1142 cmd - _DVI_DOWN1234 + 1,
1144 self.pos[_POS_V],
1146 self.pos[_POS_V]+dv))
1147 self.pos[_POS_V] += dv
1148 elif cmd == _DVI_Y0:
1149 self.flushout()
1150 if self.debug:
1151 print ("%d: y0 %d v:=%d%+d=%d, vv:=" %
1152 (self.filepos,
1153 self.pos[_POS_Y],
1154 self.pos[_POS_V],
1155 self.pos[_POS_Y],
1156 self.pos[_POS_V]+self.pos[_POS_Y]))
1157 self.pos[_POS_V] += self.pos[_POS_Y]
1158 elif cmd >= _DVI_Y1234 and cmd < _DVI_Y1234 + 4:
1159 self.flushout()
1160 self.pos[_POS_Y] = file.readint(cmd - _DVI_Y1234 + 1, 1)
1161 if self.debug:
1162 print ("%d: y%d %d v:=%d%+d=%d, vv:=" %
1163 (self.filepos,
1164 cmd - _DVI_Y1234 + 1,
1165 self.pos[_POS_Y],
1166 self.pos[_POS_V],
1167 self.pos[_POS_Y],
1168 self.pos[_POS_V]+self.pos[_POS_Y]))
1169 self.pos[_POS_V] += self.pos[_POS_Y]
1170 elif cmd == _DVI_Z0:
1171 self.flushout()
1172 self.pos[_POS_V] += self.pos[_POS_Z]
1173 elif cmd >= _DVI_Z1234 and cmd < _DVI_Z1234 + 4:
1174 self.flushout()
1175 self.pos[_POS_Z] = file.readint(cmd - _DVI_Z1234 + 1, 1)
1176 self.pos[_POS_V] += self.pos[_POS_Z]
1177 elif cmd >= _DVI_FNTNUMMIN and cmd <= _DVI_FNTNUMMAX:
1178 self.usefont(cmd - _DVI_FNTNUMMIN)
1179 elif cmd >= _DVI_FNT1234 and cmd < _DVI_FNT1234 + 4:
1180 self.usefont(file.readint(cmd - _DVI_FNT1234 + 1, 1))
1181 elif cmd >= _DVI_SPECIAL1234 and cmd < _DVI_SPECIAL1234 + 4:
1182 self.special(file.read(file.readint(cmd - _DVI_SPECIAL1234 + 1)))
1183 elif cmd >= _DVI_FNTDEF1234 and cmd < _DVI_FNTDEF1234 + 4:
1184 if cmd == _DVI_FNTDEF1234:
1185 num = file.readuchar()
1186 elif cmd == _DVI_FNTDEF1234+1:
1187 num = file.readuint16()
1188 elif cmd == _DVI_FNTDEF1234+2:
1189 num = file.readuint24()
1190 elif cmd == _DVI_FNTDEF1234+3:
1191 # Cool, here we have according to docu a signed int. Why?
1192 num = file.readint32()
1193 self.definefont(cmd-_DVI_FNTDEF1234+1,
1194 num,
1195 file.readint32(),
1196 file.readint32(),
1197 file.readint32(),
1198 file.read(file.readuchar()+file.readuchar()))
1199 else: raise DVIError
1201 def readpreamble(self):
1202 """ opens the dvi file and reads the preamble """
1203 # XXX shouldn't we put this into the constructor?
1204 self.fonts = {}
1205 self.activefont = None
1207 # stack of fonts and fontscale currently used (used for VFs)
1208 self.fontstack = []
1210 self.stack = []
1212 # here goes the result, for each page one list.
1213 self.pages = []
1215 # pointer to currently active page
1216 self.actpage = None
1218 # currently active output: position, content and type 1 font
1219 self.actoutstart = None
1220 self.actoutstring = ""
1221 self.actoutfont = None
1223 # stack for self.file, self.fonts and self.stack, needed for VF inclusion
1224 self.statestack = []
1226 self.file = binfile(self.filename, "rb")
1228 # currently read byte in file (for debugging output)
1229 self.filepos = None
1231 if self._read_pre() != _READ_NOPAGE:
1232 raise DVIError
1234 def readpage(self):
1235 # XXX shouldn't return this the next page (insted of appending to self.pages)
1236 """ reads a page from the dvi file
1238 This routine reads a page from the dvi file. The page
1239 is appended to the list self.pages. Each page consists
1240 of a list of PSCommands equivalent to the content of
1241 the dvi file. Furthermore, the list of used fonts
1242 can be extracted from the array self.fonts. """
1244 if self._read_nopage() == _READ_PAGE:
1245 state = self._read_page()
1246 else:
1247 raise DVIError
1249 def readpostamble(self):
1250 """ finish reading of the dvi file """
1251 self.file.close()
1253 def readfile(self):
1254 """ reads and parses the hole dvi file """
1255 self.readpreamble()
1256 # XXX its unknown how often readpage should be called so we
1257 # XXX do it differently ... :-(
1258 # XXX
1259 # XXX do we realy need this method at all?
1260 # XXX why can't we just return the page on readpage? Isn't this all we need in the end?
1262 state = _READ_NOPAGE
1263 while state != _READ_DONE:
1264 if state == _READ_NOPAGE:
1265 state = self._read_nopage()
1266 elif state == _READ_PAGE:
1267 state = self._read_page()
1268 else:
1269 raise DVIError
1270 self.file.close()
1272 def marker(self, page, marker):
1273 """return marker from page"""
1274 return self.pages[page-1].markers[marker]
1276 def prolog(self, page):
1277 """ return prolog corresponding to contents of dvi file """
1278 # XXX: can nearly be remove (see write below)
1279 return self.pages[page-1].prolog()
1281 def write(self, file, page):
1282 """write PostScript output for page into file"""
1283 # XXX: remove this method by return canvas to TexRunner
1284 # XXX: we should do this by removing readfile and changing readpage to return
1285 # XXX: the canvas
1286 if self.debug:
1287 print "dvifile(\"%s\").write() for page %s called" % (self.filename, page)
1288 self.pages[page-1].write(file)
1290 ##############################################################################
1291 # VF file handling
1292 ##############################################################################
1294 _VF_LONG_CHAR = 242 # character packet (long version)
1295 _VF_FNTDEF1234 = _DVI_FNTDEF1234 # font definition
1296 _VF_PRE = _DVI_PRE # preamble
1297 _VF_POST = _DVI_POST # postamble
1299 _VF_ID = 202 # VF id byte
1301 class VFError(exceptions.Exception): pass
1303 class vffile:
1304 def __init__(self, filename, scale, fontmap, debug=0):
1305 self.filename = filename
1306 self.scale = scale
1307 self.fontmap = fontmap
1308 self.debug = debug
1309 self.fonts = {} # used fonts
1310 self.widths = {} # widths of defined chars
1311 self.chardefs = {} # dvi chunks for defined chars
1313 file = binfile(self.filename, "rb")
1315 cmd = file.readuchar()
1316 if cmd == _VF_PRE:
1317 if file.readuchar() != _VF_ID: raise VFError
1318 comment = file.read(file.readuchar())
1319 self.cs = file.readuint32()
1320 self.ds = file.readuint32()
1321 else:
1322 raise VFError
1324 while 1:
1325 cmd = file.readuchar()
1326 if cmd >= _VF_FNTDEF1234 and cmd < _VF_FNTDEF1234 + 4:
1327 # font definition
1328 if cmd == _VF_FNTDEF1234:
1329 num = file.readuchar()
1330 elif cmd == _VF_FNTDEF1234+1:
1331 num = file.readuint16()
1332 elif cmd == _VF_FNTDEF1234+2:
1333 num = file.readuint24()
1334 elif cmd == _VF_FNTDEF1234+3:
1335 num = file.readint32()
1336 c = file.readint32()
1337 s = file.readint32() # relative scaling used for font (fix_word)
1338 d = file.readint32() # design size of font
1339 fontname = file.read(file.readuchar()+file.readuchar())
1341 # rescaled size of font: s is relative to the scaling
1342 # of the virtual font itself. Note that realscale has
1343 # to be a fix_word (like s)
1344 # Furthermore we have to correct for self.tfmconv
1346 reals = int(self.scale * float(fix_word(self.ds))*s)
1347 # print ("defining font %s -- VF scale: %g, VF design size: %g, relative font size: %g => real size: %g" %
1348 # (fontname, self.scale, fix_word(self.ds), fix_word(s), fix_word(reals))
1350 # reald = int(d)
1352 # XXX allow for virtual fonts here too
1353 self.fonts[num] = type1font(fontname, c, reals, d, self.fontmap, self.debug > 1)
1354 elif cmd == _VF_LONG_CHAR:
1355 # character packet (long form)
1356 pl = file.readuint32() # packet length
1357 cc = file.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1358 tfm = file.readuint24() # character width
1359 dvi = file.read(pl) # dvi code of character
1360 self.widths[cc] = tfm
1361 self.chardefs[cc] = dvi
1362 elif cmd < _VF_LONG_CHAR:
1363 # character packet (short form)
1364 cc = file.readuchar() # char code
1365 tfm = file.readuint24() # character width
1366 dvi = file.read(cmd)
1367 self.widths[cc] = tfm
1368 self.chardefs[cc] = dvi
1369 elif cmd == _VF_POST:
1370 break
1371 else:
1372 raise VFError
1374 file.close()
1376 def getfonts(self):
1377 return self.fonts
1379 def getchar(self, cc):
1380 return self.chardefs[cc]
1383 ###############################################################################
1384 # texmessages
1385 # - please don't get confused:
1386 # - there is a texmessage (and a texmessageparsed) attribute within the
1387 # texrunner; it contains TeX/LaTeX response from the last command execution
1388 # - instances of classes derived from the class texmessage are used to
1389 # parse the TeX/LaTeX response as it is stored in the texmessageparsed
1390 # attribute of a texrunner instance
1391 # - the multiple usage of the name texmessage might be removed in the future
1392 # - texmessage instances should implement _Itexmessage
1393 ###############################################################################
1395 class TexResultError(Exception):
1396 """specialized texrunner exception class
1397 - it is raised by texmessage instances, when a texmessage indicates an error
1398 - it is raised by the texrunner itself, whenever there is a texmessage left
1399 after all parsing of this message (by texmessage instances)"""
1401 def __init__(self, description, texrunner):
1402 self.description = description
1403 self.texrunner = texrunner
1405 def __str__(self):
1406 """prints a detailed report about the problem
1407 - the verbose level is controlled by texrunner.errordebug"""
1408 if self.texrunner.errordebug >= 2:
1409 return ("%s\n" % self.description +
1410 "The expression passed to TeX was:\n"
1411 " %s\n" % self.texrunner.expr.replace("\n", "\n ").rstrip() +
1412 "The return message from TeX was:\n"
1413 " %s\n" % self.texrunner.texmessage.replace("\n", "\n ").rstrip() +
1414 "After parsing this message, the following was left:\n"
1415 " %s" % self.texrunner.texmessageparsed.replace("\n", "\n ").rstrip())
1416 elif self.texrunner.errordebug == 1:
1417 firstlines = self.texrunner.texmessageparsed.split("\n")
1418 if len(firstlines) > 5:
1419 firstlines = firstlines[:5] + ["(cut after 5 lines, increase errordebug for more output)"]
1420 return ("%s\n" % self.description +
1421 "The expression passed to TeX was:\n"
1422 " %s\n" % self.texrunner.expr.replace("\n", "\n ").rstrip() +
1423 "After parsing the return message from TeX, the following was left:\n" +
1424 reduce(lambda x, y: "%s %s\n" % (x,y), firstlines, "").rstrip())
1425 else:
1426 return self.description
1429 class TexResultWarning(TexResultError):
1430 """as above, but with different handling of the exception
1431 - when this exception is raised by a texmessage instance,
1432 the information just get reported and the execution continues"""
1433 pass
1436 class _Itexmessage:
1437 """validates/invalidates TeX/LaTeX response"""
1439 def check(self, texrunner):
1440 """check a Tex/LaTeX response and respond appropriate
1441 - read the texrunners texmessageparsed attribute
1442 - if there is an problem found, raise an appropriate
1443 exception (TexResultError or TexResultWarning)
1444 - remove any valid and identified TeX/LaTeX response
1445 from the texrunners texmessageparsed attribute
1446 -> finally, there should be nothing left in there,
1447 otherwise it is interpreted as an error"""
1450 class texmessage: pass
1453 class _texmessagestart(texmessage):
1454 """validates TeX/LaTeX startup"""
1456 __implements__ = _Itexmessage
1458 startpattern = re.compile(r"This is [-0-9a-zA-Z\s_]*TeX")
1460 def check(self, texrunner):
1461 m = self.startpattern.search(texrunner.texmessageparsed)
1462 if not m:
1463 raise TexResultError("TeX startup failed", texrunner)
1464 texrunner.texmessageparsed = texrunner.texmessageparsed[m.end():]
1465 try:
1466 texrunner.texmessageparsed = texrunner.texmessageparsed.split("%s.tex" % texrunner.texfilename, 1)[1]
1467 except (IndexError, ValueError):
1468 raise TexResultError("TeX running startup file failed", texrunner)
1469 try:
1470 texrunner.texmessageparsed = texrunner.texmessageparsed.split("*! Undefined control sequence.\n<*> \\raiseerror\n %\n", 1)[1]
1471 except (IndexError, ValueError):
1472 raise TexResultError("TeX scrollmode check failed", texrunner)
1475 class _texmessagenoaux(texmessage):
1476 """allows for LaTeXs no-aux-file warning"""
1478 __implements__ = _Itexmessage
1480 def check(self, texrunner):
1481 try:
1482 s1, s2 = texrunner.texmessageparsed.split("No file %s.aux." % texrunner.texfilename, 1)
1483 texrunner.texmessageparsed = s1 + s2
1484 except (IndexError, ValueError):
1485 try:
1486 s1, s2 = texrunner.texmessageparsed.split("No file %s%s%s.aux." % (os.curdir,
1487 os.sep,
1488 texrunner.texfilename), 1)
1489 texrunner.texmessageparsed = s1 + s2
1490 except (IndexError, ValueError):
1491 pass
1494 class _texmessageinputmarker(texmessage):
1495 """validates the PyXInputMarker"""
1497 __implements__ = _Itexmessage
1499 def check(self, texrunner):
1500 try:
1501 s1, s2 = texrunner.texmessageparsed.split("PyXInputMarker:executeid=%s:" % texrunner.executeid, 1)
1502 texrunner.texmessageparsed = s1 + s2
1503 except (IndexError, ValueError):
1504 raise TexResultError("PyXInputMarker expected", texrunner)
1507 class _texmessagepyxbox(texmessage):
1508 """validates the PyXBox output"""
1510 __implements__ = _Itexmessage
1512 pattern = re.compile(r"PyXBox:page=(?P<page>\d+),lt=-?\d*((\d\.?)|(\.?\d))\d*pt,rt=-?\d*((\d\.?)|(\.?\d))\d*pt,ht=-?\d*((\d\.?)|(\.?\d))\d*pt,dp=-?\d*((\d\.?)|(\.?\d))\d*pt:")
1514 def check(self, texrunner):
1515 m = self.pattern.search(texrunner.texmessageparsed)
1516 if m and m.group("page") == str(texrunner.page):
1517 texrunner.texmessageparsed = texrunner.texmessageparsed[:m.start()] + texrunner.texmessageparsed[m.end():]
1518 else:
1519 raise TexResultError("PyXBox expected", texrunner)
1522 class _texmessagepyxpageout(texmessage):
1523 """validates the dvi shipout message (writing a page to the dvi file)"""
1525 __implements__ = _Itexmessage
1527 def check(self, texrunner):
1528 try:
1529 s1, s2 = texrunner.texmessageparsed.split("[80.121.88.%s]" % texrunner.page, 1)
1530 texrunner.texmessageparsed = s1 + s2
1531 except (IndexError, ValueError):
1532 raise TexResultError("PyXPageOutMarker expected", texrunner)
1535 class _texmessagetexend(texmessage):
1536 """validates TeX/LaTeX finish"""
1538 __implements__ = _Itexmessage
1540 def check(self, texrunner):
1541 try:
1542 s1, s2 = texrunner.texmessageparsed.split("(%s.aux)" % texrunner.texfilename, 1)
1543 texrunner.texmessageparsed = s1 + s2
1544 except (IndexError, ValueError):
1545 try:
1546 s1, s2 = texrunner.texmessageparsed.split("(%s%s%s.aux)" % (os.curdir,
1547 os.sep,
1548 texrunner.texfilename), 1)
1549 texrunner.texmessageparsed = s1 + s2
1550 except (IndexError, ValueError):
1551 pass
1552 try:
1553 s1, s2 = texrunner.texmessageparsed.split("(see the transcript file for additional information)", 1)
1554 texrunner.texmessageparsed = s1 + s2
1555 except (IndexError, ValueError):
1556 pass
1557 dvipattern = re.compile(r"Output written on %s\.dvi \((?P<page>\d+) pages?, \d+ bytes\)\." % texrunner.texfilename)
1558 m = dvipattern.search(texrunner.texmessageparsed)
1559 if texrunner.page:
1560 if not m:
1561 raise TexResultError("TeX dvifile messages expected", texrunner)
1562 if m.group("page") != str(texrunner.page):
1563 raise TexResultError("wrong number of pages reported", texrunner)
1564 texrunner.texmessageparsed = texrunner.texmessageparsed[:m.start()] + texrunner.texmessageparsed[m.end():]
1565 else:
1566 try:
1567 s1, s2 = texrunner.texmessageparsed.split("No pages of output.", 1)
1568 texrunner.texmessageparsed = s1 + s2
1569 except (IndexError, ValueError):
1570 raise TexResultError("no dvifile expected")
1571 try:
1572 s1, s2 = texrunner.texmessageparsed.split("Transcript written on %s.log." % texrunner.texfilename, 1)
1573 texrunner.texmessageparsed = s1 + s2
1574 except (IndexError, ValueError):
1575 raise TexResultError("TeX logfile message expected")
1578 class _texmessageemptylines(texmessage):
1579 """validates "*-only" (TeX/LaTeX input marker in interactive mode) and empty lines"""
1581 __implements__ = _Itexmessage
1583 def check(self, texrunner):
1584 texrunner.texmessageparsed = texrunner.texmessageparsed.replace("*\n", "")
1585 texrunner.texmessageparsed = texrunner.texmessageparsed.replace("\n", "")
1588 class _texmessageload(texmessage):
1589 """validates inclusion of arbitrary files
1590 - the matched pattern is "(<filename> <arbitrary other stuff>)", where
1591 <fielname> is a readable file and other stuff can be anything
1592 - "(" and ")" must be used consistent (otherwise this validator just does nothing)
1593 - this is not always wanted, but we just assume that file inclusion is fine"""
1595 __implements__ = _Itexmessage
1597 pattern = re.compile(r" *\((?P<filename>[^()\s\n]+)[^()]*\) *")
1599 def baselevels(self, s, maxlevel=1, brackets="()"):
1600 """strip parts of a string above a given bracket level
1601 - return a modified (some parts might be removed) version of the string s
1602 where all parts inside brackets with level higher than maxlevel are
1603 removed
1604 - if brackets do not match (number of left and right brackets is wrong
1605 or at some points there were more right brackets than left brackets)
1606 just return the unmodified string"""
1607 level = 0
1608 highestlevel = 0
1609 res = ""
1610 for c in s:
1611 if c == brackets[0]:
1612 level += 1
1613 if level > highestlevel:
1614 highestlevel = level
1615 if level <= maxlevel:
1616 res += c
1617 if c == brackets[1]:
1618 level -= 1
1619 if level == 0 and highestlevel > 0:
1620 return res
1622 def check(self, texrunner):
1623 lowestbracketlevel = self.baselevels(texrunner.texmessageparsed)
1624 if lowestbracketlevel is not None:
1625 m = self.pattern.search(lowestbracketlevel)
1626 while m:
1627 if os.access(m.group("filename"), os.R_OK):
1628 lowestbracketlevel = lowestbracketlevel[:m.start()] + lowestbracketlevel[m.end():]
1629 else:
1630 break
1631 m = self.pattern.search(lowestbracketlevel)
1632 else:
1633 texrunner.texmessageparsed = lowestbracketlevel
1636 class _texmessageloadfd(_texmessageload):
1637 """validates the inclusion of font description files (fd-files)
1638 - works like _texmessageload
1639 - filename must end with .fd and no further text is allowed"""
1641 pattern = re.compile(r" *\((?P<filename>[^)]+.fd)\) *")
1644 class _texmessagegraphicsload(_texmessageload):
1645 """validates the inclusion of files as the graphics packages writes it
1646 - works like _texmessageload, but using "<" and ">" as delimiters
1647 - filename must end with .eps and no further text is allowed"""
1649 pattern = re.compile(r" *<(?P<filename>[^>]+.eps)> *")
1651 def baselevels(self, s, brackets="<>", **args):
1652 return _texmessageload.baselevels(self, s, brackets=brackets, **args)
1655 #class _texmessagepdfmapload(_texmessageload):
1656 # """validates the inclusion of files as the graphics packages writes it
1657 # - works like _texmessageload, but using "{" and "}" as delimiters
1658 # - filename must end with .map and no further text is allowed"""
1660 # pattern = re.compile(r"{(?P<filename>[^}]+.map)}")
1662 # def baselevels(self, s, brackets="{}", **args):
1663 # return _texmessageload.baselevels(self, s, brackets=brackets, **args)
1666 class _texmessageignore(_texmessageload):
1667 """validates any TeX/LaTeX response
1668 - this might be used, when the expression is ok, but no suitable texmessage
1669 parser is available
1670 - PLEASE: - consider writing suitable tex message parsers
1671 - share your ideas/problems/solutions with others (use the PyX mailing lists)"""
1673 __implements__ = _Itexmessage
1675 def check(self, texrunner):
1676 texrunner.texmessageparsed = ""
1679 texmessage.start = _texmessagestart()
1680 texmessage.noaux = _texmessagenoaux()
1681 texmessage.inputmarker = _texmessageinputmarker()
1682 texmessage.pyxbox = _texmessagepyxbox()
1683 texmessage.pyxpageout = _texmessagepyxpageout()
1684 texmessage.texend = _texmessagetexend()
1685 texmessage.emptylines = _texmessageemptylines()
1686 texmessage.load = _texmessageload()
1687 texmessage.loadfd = _texmessageloadfd()
1688 texmessage.graphicsload = _texmessagegraphicsload()
1689 texmessage.ignore = _texmessageignore()
1692 ###############################################################################
1693 # textattrs
1694 ###############################################################################
1696 _textattrspreamble = ""
1698 class textattr:
1699 "a textattr defines a apply method, which modifies a (La)TeX expression"
1701 class halign(attr.exclusiveattr, textattr):
1703 def __init__(self, hratio):
1704 self.hratio = hratio
1705 attr.exclusiveattr.__init__(self, halign)
1707 def apply(self, expr):
1708 return r"\gdef\PyXHAlign{%.5f}%s" % (self.hratio, expr)
1710 halign.center = halign(0.5)
1711 halign.right = halign(1)
1712 halign.clear = attr.clearclass(halign)
1713 halign.left = halign.clear
1716 class _localattr: pass
1718 class _mathmode(attr.attr, textattr, _localattr):
1719 "math mode"
1721 def apply(self, expr):
1722 return r"$\displaystyle{%s}$" % expr
1724 mathmode = _mathmode()
1725 nomathmode = attr.clearclass(_mathmode)
1728 defaultsizelist = ["normalsize", "large", "Large", "LARGE", "huge", "Huge", None, "tiny", "scriptsize", "footnotesize", "small"]
1730 class size(attr.sortbeforeattr, textattr, _localattr):
1731 "font size"
1733 def __init__(self, expr, sizelist=defaultsizelist):
1734 attr.sortbeforeattr.__init__(self, [_mathmode])
1735 if helper.isinteger(expr):
1736 if expr >= 0 and expr < sizelist.index(None):
1737 self.size = sizelist[expr]
1738 elif expr < 0 and expr + len(sizelist) > sizelist.index(None):
1739 self.size = sizelist[expr]
1740 else:
1741 raise IndexError("index out of sizelist range")
1742 else:
1743 self.size = expr
1745 def apply(self, expr):
1746 return r"\%s{%s}" % (self.size, expr)
1748 for s in defaultsizelist:
1749 if s is not None:
1750 setattr(size, s, size(s))
1753 _textattrspreamble += "\\newbox\\PyXBoxVBox%\n\\newdimen\PyXDimenVBox%\n"
1755 class parbox_pt(attr.exclusiveattr, attr.sortbeforeattr, textattr):
1757 top = 1
1758 middle = 2
1759 bottom = 3
1761 def __init__(self, width, baseline=middle):
1762 self.width = width
1763 self.baseline = baseline
1764 attr.sortbeforeattr.__init__(self, [_localattr])
1765 attr.exclusiveattr.__init__(self, parbox_pt)
1767 def apply(self, expr):
1768 if self.baseline == self.top:
1769 return r"\linewidth%.5ftruept\vtop{\hsize\linewidth{%s}}" % (self.width * 72.27 / 72, expr)
1770 elif self.baseline == self.middle:
1771 return r"\linewidth%.5ftruept\setbox\PyXBoxVBox=\hbox{{\vtop{\hsize\linewidth{%s}}}}\PyXDimenVBox=0.5\dp\PyXBoxVBox\setbox\PyXBoxVBox=\hbox{{\vbox{\hsize\linewidth{%s}}}}\advance\PyXDimenVBox by -0.5\dp\PyXBoxVBox\lower\PyXDimenVBox\box\PyXBoxVBox" % (self.width, expr, expr)
1772 elif self.baseline == self.bottom:
1773 return r"\linewidth%.5ftruept\vbox{\hsize\linewidth{%s}}" % (self.width * 72.27 / 72, expr)
1774 else:
1775 RuntimeError("invalid baseline argument")
1777 class parbox(parbox_pt):
1779 def __init__(self, width, **kwargs):
1780 parbox_pt.__init__(self, unit.topt(width), **kwargs)
1783 _textattrspreamble += "\\newbox\\PyXBoxVAlign%\n\\newdimen\PyXDimenVAlign%\n"
1785 class valign(attr.exclusiveattr, attr.sortbeforeattr, textattr):
1787 def __init__(self):
1788 attr.exclusiveattr.__init__(self, valign)
1789 attr.sortbeforeattr.__init__(self, [parbox_pt, _localattr])
1791 class _valigntop(valign):
1793 def apply(self, expr):
1794 return r"\setbox\PyXBoxVAlign=\hbox{{%s}}\lower\ht\PyXBoxVAlign\box\PyXBoxVAlign" % expr
1796 class _valignbottom(valign):
1798 def apply(self, expr):
1799 return r"\setbox\PyXBoxVAlign=\hbox{{%s}}\raise\dp\PyXBoxVAlign\box\PyXBoxVAlign" % expr
1802 valign.top = _valigntop()
1803 valign.bottom = _valignbottom()
1804 valign.clear = attr.clearclass(valign)
1805 valign.baseline = valign.clear
1808 class _vshift(attr.sortbeforeattr, textattr):
1810 def __init__(self):
1811 attr.sortbeforeattr.__init__(self, [valign, parbox_pt, _localattr])
1813 class vshift(_vshift):
1814 "vertical down shift by a fraction of a character height"
1816 def __init__(self, lowerratio, heightstr="0"):
1817 _vshift.__init__(self)
1818 self.lowerratio = lowerratio
1819 self.heightstr = heightstr
1821 def apply(self, expr):
1822 return r"\setbox0\hbox{{%s}}\lower%.5f\ht0\hbox{{%s}}" % (self.heightstr, self.lowerratio, expr)
1824 class _vshiftmathaxis(_vshift):
1825 "vertical down shift by the height of the math axis"
1827 def apply(self, expr):
1828 return r"\setbox0\hbox{$\vcenter{\vrule width0pt}$}\lower\ht0\hbox{{%s}}" % expr
1831 vshift.bottomzero = vshift(0)
1832 vshift.middlezero = vshift(0.5)
1833 vshift.topzero = vshift(1)
1834 vshift.mathaxis = _vshiftmathaxis()
1837 ###############################################################################
1838 # texrunner
1839 ###############################################################################
1842 class _readpipe(threading.Thread):
1843 """threaded reader of TeX/LaTeX output
1844 - sets an event, when a specific string in the programs output is found
1845 - sets an event, when the terminal ends"""
1847 def __init__(self, pipe, expectqueue, gotevent, gotqueue, quitevent):
1848 """initialize the reader
1849 - pipe: file to be read from
1850 - expectqueue: keeps the next InputMarker to be wait for
1851 - gotevent: the "got InputMarker" event
1852 - gotqueue: a queue containing the lines recieved from TeX/LaTeX
1853 - quitevent: the "end of terminal" event"""
1854 threading.Thread.__init__(self)
1855 self.setDaemon(1) # don't care if the output might not be finished (nevertheless, it shouldn't happen)
1856 self.pipe = pipe
1857 self.expectqueue = expectqueue
1858 self.gotevent = gotevent
1859 self.gotqueue = gotqueue
1860 self.quitevent = quitevent
1861 self.expect = None
1862 self.start()
1864 def run(self):
1865 """thread routine"""
1866 read = self.pipe.readline() # read, what comes in
1867 try:
1868 self.expect = self.expectqueue.get_nowait() # read, what should be expected
1869 except Queue.Empty:
1870 pass
1871 while len(read):
1872 # universal EOL handling (convert everything into unix like EOLs)
1873 read.replace("\r", "")
1874 if not len(read) or read[-1] != "\n":
1875 read += "\n"
1876 self.gotqueue.put(read) # report, whats readed
1877 if self.expect is not None and read.find(self.expect) != -1:
1878 self.gotevent.set() # raise the got event, when the output was expected (XXX: within a single line)
1879 read = self.pipe.readline() # read again
1880 try:
1881 self.expect = self.expectqueue.get_nowait()
1882 except Queue.Empty:
1883 pass
1884 # EOF reached
1885 self.pipe.close()
1886 if self.expect is not None and self.expect.find("PyXInputMarker") != -1:
1887 raise RuntimeError("TeX/LaTeX finished unexpectedly")
1888 self.quitevent.set()
1892 class _textbox(box._rect, base.PSCmd):
1893 """basically a box.rect, but it contains a text created by the texrunner
1894 - texrunner._text and texrunner.text return such an object
1895 - _textbox instances can be inserted into a canvas
1896 - the output is contained in a page of the dvifile available thru the texrunner"""
1898 def __init__(self, x, y, left, right, height, depth, texrunner, dvinumber, page, *attrs):
1899 self.texttrafo = trafo._translate(x, y)
1900 box._rect.__init__(self, x - left, y - depth,
1901 left + right, depth + height,
1902 abscenter = (left, depth))
1903 self.texrunner = texrunner
1904 self.dvinumber = dvinumber
1905 self.page = page
1906 self.attrs = attrs
1908 def transform(self, *trafos):
1909 box._rect.transform(self, *trafos)
1910 for trafo in trafos:
1911 self.texttrafo = trafo * self.texttrafo
1913 def marker(self, marker):
1914 return self.texttrafo.apply(*self.texrunner.marker(self.dvinumber, self.page, marker))
1916 def prolog(self):
1917 result = []
1918 for cmd in self.attrs:
1919 result.extend(cmd.prolog())
1920 return result + self.texrunner.prolog(self.dvinumber, self.page)
1922 def write(self, file):
1923 canvas._gsave().write(file) # XXX: canvas?, constructor call needed?
1924 self.texttrafo.write(file)
1925 for attr in self.attrs:
1926 attr.write(file)
1927 self.texrunner.write(file, self.dvinumber, self.page)
1928 canvas._grestore().write(file)
1932 class textbox(_textbox):
1934 def __init__(self, x, y, left, right, height, depth, texrunner, dvinumber, page, *attrs):
1935 _textbox.__init__(self, unit.topt(x), unit.topt(y), unit.topt(left), unit.topt(right),
1936 unit.topt(height), unit.topt(depth), texrunner, dvinumber, page, *attrs)
1939 def _cleantmp(texrunner):
1940 """get rid of temporary files
1941 - function to be registered by atexit
1942 - files contained in usefiles are kept"""
1943 if texrunner.texruns: # cleanup while TeX is still running?
1944 texrunner.texruns = 0
1945 texrunner.texdone = 1
1946 texrunner.expectqueue.put_nowait(None) # do not expect any output anymore
1947 if texrunner.mode == "latex": # try to immediately quit from TeX or LaTeX
1948 texrunner.texinput.write("\n\\catcode`\\@11\\relax\\@@end\n")
1949 else:
1950 texrunner.texinput.write("\n\\end\n")
1951 texrunner.texinput.close() # close the input queue and
1952 if not texrunner.waitforevent(texrunner.quitevent): # wait for finish of the output
1953 return # didn't got a quit from TeX -> we can't do much more
1954 for usefile in texrunner.usefiles:
1955 extpos = usefile.rfind(".")
1956 try:
1957 os.rename(texrunner.texfilename + usefile[extpos:], usefile)
1958 except OSError:
1959 pass
1960 for file in glob.glob("%s.*" % texrunner.texfilename):
1961 try:
1962 os.unlink(file)
1963 except OSError:
1964 pass
1965 if texrunner.texdebug is not None:
1966 try:
1967 texrunner.texdebug.close()
1968 texrunner.texdebug = None
1969 except IOError:
1970 pass
1973 # texrunner state exceptions
1974 class TexRunsError(Exception): pass
1975 class TexDoneError(Exception): pass
1976 class TexNotInPreambleModeError(Exception): pass
1979 class texrunner:
1980 """TeX/LaTeX interface
1981 - runs TeX/LaTeX expressions instantly
1982 - checks TeX/LaTeX response
1983 - the instance variable texmessage stores the last TeX
1984 response as a string
1985 - the instance variable texmessageparsed stores a parsed
1986 version of texmessage; it should be empty after
1987 texmessage.check was called, otherwise a TexResultError
1988 is raised
1989 - the instance variable errordebug controls the verbose
1990 level of TexResultError"""
1992 def __init__(self, mode="tex",
1993 lfs="10pt",
1994 docclass="article",
1995 docopt=None,
1996 usefiles=None,
1997 fontmaps=config.get("text", "fontmaps", "psfonts.map"),
1998 waitfortex=config.getint("text", "waitfortex", 60),
1999 showwaitfortex=config.getint("text", "showwaitfortex", 5),
2000 texipc=config.getboolean("text", "texipc", 0),
2001 texdebug=None,
2002 dvidebug=0,
2003 errordebug=1,
2004 dvicopy=0,
2005 pyxgraphics=1,
2006 texmessagestart=texmessage.start,
2007 texmessagedocclass=texmessage.load,
2008 texmessagebegindoc=(texmessage.load, texmessage.noaux),
2009 texmessageend=texmessage.texend,
2010 texmessagedefaultpreamble=texmessage.load,
2011 texmessagedefaultrun=texmessage.loadfd):
2012 mode = mode.lower()
2013 if mode != "tex" and mode != "latex":
2014 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
2015 self.mode = mode
2016 self.lfs = lfs
2017 self.docclass = docclass
2018 self.docopt = docopt
2019 self.usefiles = helper.ensurelist(usefiles)
2020 self.fontmap = readfontmap(fontmaps.split())
2021 self.waitfortex = waitfortex
2022 self.showwaitfortex = showwaitfortex
2023 self.texipc = texipc
2024 if texdebug is not None:
2025 if texdebug[-4:] == ".tex":
2026 self.texdebug = open(texdebug, "w")
2027 else:
2028 self.texdebug = open("%s.tex" % texdebug, "w")
2029 else:
2030 self.texdebug = None
2031 self.dvidebug = dvidebug
2032 self.errordebug = errordebug
2033 self.dvicopy = dvicopy
2034 self.pyxgraphics = pyxgraphics
2035 texmessagestart = helper.ensuresequence(texmessagestart)
2036 helper.checkattr(texmessagestart, allowmulti=(texmessage,))
2037 self.texmessagestart = texmessagestart
2038 texmessagedocclass = helper.ensuresequence(texmessagedocclass)
2039 helper.checkattr(texmessagedocclass, allowmulti=(texmessage,))
2040 self.texmessagedocclass = texmessagedocclass
2041 texmessagebegindoc = helper.ensuresequence(texmessagebegindoc)
2042 helper.checkattr(texmessagebegindoc, allowmulti=(texmessage,))
2043 self.texmessagebegindoc = texmessagebegindoc
2044 texmessageend = helper.ensuresequence(texmessageend)
2045 helper.checkattr(texmessageend, allowmulti=(texmessage,))
2046 self.texmessageend = texmessageend
2047 texmessagedefaultpreamble = helper.ensuresequence(texmessagedefaultpreamble)
2048 helper.checkattr(texmessagedefaultpreamble, allowmulti=(texmessage,))
2049 self.texmessagedefaultpreamble = texmessagedefaultpreamble
2050 texmessagedefaultrun = helper.ensuresequence(texmessagedefaultrun)
2051 helper.checkattr(texmessagedefaultrun, allowmulti=(texmessage,))
2052 self.texmessagedefaultrun = texmessagedefaultrun
2054 self.texruns = 0
2055 self.texdone = 0
2056 self.preamblemode = 1
2057 self.executeid = 0
2058 self.page = 0
2059 self.dvinumber = 0
2060 self.dvifiles = []
2061 self.preambles = []
2062 savetempdir = tempfile.tempdir
2063 tempfile.tempdir = os.curdir
2064 self.texfilename = os.path.basename(tempfile.mktemp())
2065 tempfile.tempdir = savetempdir
2067 def waitforevent(self, event):
2068 """waits verbosely with an timeout for an event
2069 - observes an event while periodly while printing messages
2070 - returns the status of the event (isSet)
2071 - does not clear the event"""
2072 if self.showwaitfortex:
2073 waited = 0
2074 hasevent = 0
2075 while waited < self.waitfortex and not hasevent:
2076 if self.waitfortex - waited > self.showwaitfortex:
2077 event.wait(self.showwaitfortex)
2078 waited += self.showwaitfortex
2079 else:
2080 event.wait(self.waitfortex - waited)
2081 waited += self.waitfortex - waited
2082 hasevent = event.isSet()
2083 if not hasevent:
2084 if waited < self.waitfortex:
2085 sys.stderr.write("*** PyX INFO: still waiting for %s after %i seconds...\n" % (self.mode, waited))
2086 else:
2087 sys.stderr.write("*** PyX ERROR: the timeout of %i seconds expired and %s did not respond.\n" % (waited, self.mode))
2088 return hasevent
2089 else:
2090 event.wait(self.waitfortex)
2091 return event.isSet()
2093 def execute(self, expr, *checks):
2094 """executes expr within TeX/LaTeX
2095 - if self.texruns is not yet set, TeX/LaTeX is initialized,
2096 self.texruns is set and self.preamblemode is set
2097 - the method must not be called, when self.texdone is already set
2098 - expr should be a string or None
2099 - when expr is None, TeX/LaTeX is stopped, self.texruns is unset and
2100 while self.texdone becomes set
2101 - when self.preamblemode is set, the expr is passed directly to TeX/LaTeX
2102 - when self.preamblemode is unset, the expr is passed to \ProcessPyXBox
2104 if not self.texruns:
2105 if self.texdebug is not None:
2106 self.texdebug.write("%% PyX %s texdebug file\n" % version.version)
2107 self.texdebug.write("%% mode: %s\n" % self.mode)
2108 self.texdebug.write("%% date: %s\n" % time.asctime(time.localtime(time.time())))
2109 for usefile in self.usefiles:
2110 extpos = usefile.rfind(".")
2111 try:
2112 os.rename(usefile, self.texfilename + usefile[extpos:])
2113 except OSError:
2114 pass
2115 texfile = open("%s.tex" % self.texfilename, "w") # start with filename -> creates dvi file with that name
2116 texfile.write("\\relax%\n")
2117 texfile.close()
2118 if self.texipc:
2119 ipcflag = " --ipc"
2120 else:
2121 ipcflag = ""
2122 try:
2123 self.texinput, self.texoutput = os.popen4("%s%s %s" % (self.mode, ipcflag, self.texfilename), "t", 0)
2124 except ValueError:
2125 # XXX: workaround for MS Windows (bufsize = 0 makes trouble!?)
2126 self.texinput, self.texoutput = os.popen4("%s%s %s" % (self.mode, ipcflag, self.texfilename), "t")
2127 atexit.register(_cleantmp, self)
2128 self.expectqueue = Queue.Queue(1) # allow for a single entry only -> keeps the next InputMarker to be wait for
2129 self.gotevent = threading.Event() # keeps the got inputmarker event
2130 self.gotqueue = Queue.Queue(0) # allow arbitrary number of entries
2131 self.quitevent = threading.Event() # keeps for end of terminal event
2132 self.readoutput = _readpipe(self.texoutput, self.expectqueue, self.gotevent, self.gotqueue, self.quitevent)
2133 self.texruns = 1
2134 oldpreamblemode = self.preamblemode
2135 self.preamblemode = 1
2136 self.execute("\\scrollmode\n\\raiseerror%\n" # switch to and check scrollmode
2137 "\\def\\PyX{P\\kern-.3em\\lower.5ex\hbox{Y}\kern-.18em X}%\n" # just the PyX Logo
2138 "\\gdef\\PyXHAlign{0}%\n" # global PyXHAlign (0.0-1.0) for the horizontal alignment, default to 0
2139 "\\newbox\\PyXBox%\n" # PyXBox will contain the output
2140 "\\newbox\\PyXBoxHAligned%\n" # PyXBox will contain the horizontal aligned output
2141 "\\newdimen\\PyXDimenHAlignLT%\n" # PyXDimenHAlignLT/RT will contain the left/right extent
2142 "\\newdimen\\PyXDimenHAlignRT%\n" +
2143 _textattrspreamble + # insert preambles for textattrs macros
2144 "\\long\\def\\ProcessPyXBox#1#2{%\n" # the ProcessPyXBox definition (#1 is expr, #2 is page number)
2145 "\\setbox\\PyXBox=\\hbox{{#1}}%\n" # push expression into PyXBox
2146 "\\PyXDimenHAlignLT=\\PyXHAlign\\wd\\PyXBox%\n" # calculate the left/right extent
2147 "\\PyXDimenHAlignRT=\\wd\\PyXBox%\n"
2148 "\\advance\\PyXDimenHAlignRT by -\\PyXDimenHAlignLT%\n"
2149 "\\gdef\\PyXHAlign{0}%\n" # reset the PyXHAlign to the default 0
2150 "\\immediate\\write16{PyXBox:page=#2," # write page and extents of this box to stdout
2151 "lt=\\the\\PyXDimenHAlignLT,"
2152 "rt=\\the\\PyXDimenHAlignRT,"
2153 "ht=\\the\\ht\\PyXBox,"
2154 "dp=\\the\\dp\\PyXBox:}%\n"
2155 "\\setbox\\PyXBoxHAligned=\\hbox{\\kern-\\PyXDimenHAlignLT\\box\\PyXBox}%\n" # align horizontally
2156 "\\ht\\PyXBoxHAligned0pt%\n" # baseline alignment (hight to zero)
2157 "{\\count0=80\\count1=121\\count2=88\\count3=#2\\shipout\\box\\PyXBoxHAligned}}%\n" # shipout PyXBox to Page 80.121.88.<page number>
2158 "\\def\\PyXInput#1{\\immediate\\write16{PyXInputMarker:executeid=#1:}}%\n" # write PyXInputMarker to stdout
2159 "\\def\\PyXMarker#1{\\special{PyX:marker #1}}%\n", # write PyXMarker special into the dvi-file
2160 *self.texmessagestart)
2161 os.remove("%s.tex" % self.texfilename)
2162 if self.mode == "tex":
2163 if len(self.lfs) > 4 and self.lfs[-4:] == ".lfs":
2164 lfsname = self.lfs
2165 else:
2166 lfsname = "%s.lfs" % self.lfs
2167 for fulllfsname in [lfsname,
2168 os.path.join(sys.prefix, "share", "pyx", lfsname),
2169 os.path.join(os.path.dirname(__file__), "lfs", lfsname)]:
2170 try:
2171 lfsfile = open(fulllfsname, "r")
2172 lfsdef = lfsfile.read()
2173 lfsfile.close()
2174 break
2175 except IOError:
2176 pass
2177 else:
2178 allfiles = (glob.glob("*.lfs") +
2179 glob.glob(os.path.join(sys.prefix, "share", "pyx", "*.lfs")) +
2180 glob.glob(os.path.join(os.path.dirname(__file__), "lfs", "*.lfs")))
2181 lfsnames = []
2182 for f in allfiles:
2183 try:
2184 open(f, "r").close()
2185 lfsnames.append(os.path.basename(f)[:-4])
2186 except IOError:
2187 pass
2188 lfsnames.sort()
2189 if len(lfsnames):
2190 raise IOError("file '%s' is not available or not readable. Available LaTeX font size files (*.lfs): %s" % (lfsname, lfsnames))
2191 else:
2192 raise IOError("file '%s' is not available or not readable. No LaTeX font size files (*.lfs) available. Check your installation." % lfsname)
2193 self.execute(lfsdef)
2194 self.execute("\\normalsize%\n")
2195 self.execute("\\newdimen\\linewidth%\n")
2196 elif self.mode == "latex":
2197 if self.pyxgraphics:
2198 for pyxdef in ["pyx.def",
2199 os.path.join(sys.prefix, "share", "pyx", "pyx.def"),
2200 os.path.join(os.path.dirname(__file__), "..", "contrib", "pyx.def")]:
2201 try:
2202 open(pyxdef, "r").close()
2203 break
2204 except IOError:
2205 pass
2206 else:
2207 IOError("file 'pyx.def' is not available or not readable. Check your installation or turn off the pyxgraphics option.")
2208 pyxdef = os.path.abspath(pyxdef).replace(os.sep, "/")
2209 self.execute("\\makeatletter%\n"
2210 "\\let\\saveProcessOptions=\\ProcessOptions%\n"
2211 "\\def\\ProcessOptions{%\n"
2212 "\\def\\Gin@driver{" + pyxdef + "}%\n"
2213 "\\def\\c@lor@namefile{dvipsnam.def}%\n"
2214 "\\saveProcessOptions}%\n"
2215 "\\makeatother")
2216 if self.docopt is not None:
2217 self.execute("\\documentclass[%s]{%s}" % (self.docopt, self.docclass), *self.texmessagedocclass)
2218 else:
2219 self.execute("\\documentclass{%s}" % self.docclass, *self.texmessagedocclass)
2220 self.preamblemode = oldpreamblemode
2221 self.executeid += 1
2222 if expr is not None: # TeX/LaTeX should process expr
2223 self.expectqueue.put_nowait("PyXInputMarker:executeid=%i:" % self.executeid)
2224 if self.preamblemode:
2225 self.expr = ("%s%%\n" % expr +
2226 "\\PyXInput{%i}%%\n" % self.executeid)
2227 else:
2228 self.page += 1
2229 self.expr = ("\\ProcessPyXBox{%s%%\n}{%i}%%\n" % (expr, self.page) +
2230 "\\PyXInput{%i}%%\n" % self.executeid)
2231 else: # TeX/LaTeX should be finished
2232 self.expectqueue.put_nowait("Transcript written on %s.log" % self.texfilename)
2233 if self.mode == "latex":
2234 self.expr = "\\end{document}%\n"
2235 else:
2236 self.expr = "\\end%\n"
2237 if self.texdebug is not None:
2238 self.texdebug.write(self.expr)
2239 self.texinput.write(self.expr)
2240 gotevent = self.waitforevent(self.gotevent)
2241 self.gotevent.clear()
2242 if expr is None and gotevent: # TeX/LaTeX should have finished
2243 self.texruns = 0
2244 self.texdone = 1
2245 self.texinput.close() # close the input queue and
2246 gotevent = self.waitforevent(self.quitevent) # wait for finish of the output
2247 try:
2248 self.texmessage = ""
2249 while 1:
2250 self.texmessage += self.gotqueue.get_nowait()
2251 except Queue.Empty:
2252 pass
2253 self.texmessageparsed = self.texmessage
2254 if gotevent:
2255 if expr is not None:
2256 texmessage.inputmarker.check(self)
2257 if not self.preamblemode:
2258 texmessage.pyxbox.check(self)
2259 texmessage.pyxpageout.check(self)
2260 for check in checks:
2261 try:
2262 check.check(self)
2263 except TexResultWarning:
2264 traceback.print_exc()
2265 texmessage.emptylines.check(self)
2266 if len(self.texmessageparsed):
2267 raise TexResultError("unhandled TeX response (might be an error)", self)
2268 else:
2269 raise TexResultError("TeX didn't respond as expected within the timeout period (%i seconds)." % self.waitfortex, self)
2271 def finishdvi(self):
2272 "finish TeX/LaTeX and read the dvifile"
2273 self.execute(None, *self.texmessageend)
2274 if self.dvicopy:
2275 os.system("dvicopy %(t)s.dvi %(t)s.dvicopy > %(t)s.dvicopyout 2> %(t)s.dvicopyerr" % {"t": self.texfilename})
2276 dvifilename = "%s.dvicopy" % self.texfilename
2277 else:
2278 dvifilename = "%s.dvi" % self.texfilename
2279 if self.texipc:
2280 self.dvifiles[-1].readpostamble()
2281 else:
2282 advifile = dvifile(dvifilename, self.fontmap, debug=self.dvidebug)
2283 self.dvifiles.append(advifile)
2284 self.dvifiles[-1].readfile()
2285 self.dvinumber += 1
2287 def marker(self, dvinumber, page, marker):
2288 "return the marker position"
2289 if not self.texipc and not self.texdone:
2290 self.finishdvi()
2291 return self.dvifiles[dvinumber].marker(page, marker)
2293 def prolog(self, dvinumber, page):
2294 "return the dvifile prolog"
2295 if not self.texipc and not self.texdone:
2296 self.finishdvi()
2297 return self.dvifiles[dvinumber].prolog(page)
2299 def write(self, file, dvinumber, page):
2300 "write a page from the dvifile"
2301 if not self.texipc and not self.texdone:
2302 self.finishdvi()
2303 return self.dvifiles[dvinumber].write(file, page)
2305 def reset(self, reinit=0):
2306 "resets the tex runner to its initial state (upto its record to old dvi file(s))"
2307 if self.texruns:
2308 self.finishdvi()
2309 if self.texdebug is not None:
2310 self.texdebug.write("%s\n%% preparing restart of %s\n" % ("%"*80, self.mode))
2311 self.executeid = 0
2312 self.page = 0
2313 self.texdone = 0
2314 if reinit:
2315 self.preamblemode = 1
2316 for expr, args in self.preambles:
2317 self.execute(expr, *args)
2318 if self.mode == "latex":
2319 self.execute("\\begin{document}", *self.texmessagebegindoc)
2320 self.preamblemode = 0
2321 else:
2322 self.preambles = []
2323 self.preamblemode = 1
2325 def set(self, mode=None,
2326 lfs=None,
2327 docclass=None,
2328 docopt=None,
2329 usefiles=None,
2330 fontmaps=None,
2331 waitfortex=None,
2332 showwaitfortex=None,
2333 texipc=None,
2334 texdebug=None,
2335 dvidebug=None,
2336 errordebug=None,
2337 dvicopy=None,
2338 pyxgraphics=None,
2339 texmessagestart=None,
2340 texmessagedocclass=None,
2341 texmessagebegindoc=None,
2342 texmessageend=None,
2343 texmessagedefaultpreamble=None,
2344 texmessagedefaultrun=None):
2345 """provide a set command for TeX/LaTeX settings
2346 - TeX/LaTeX must not yet been started
2347 - especially needed for the defaultrunner, where no access to
2348 the constructor is available"""
2349 if self.texruns:
2350 raise TexRunsError
2351 if mode is not None:
2352 mode = mode.lower()
2353 if mode != "tex" and mode != "latex":
2354 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
2355 self.mode = mode
2356 if lfs is not None:
2357 self.lfs = lfs
2358 if docclass is not None:
2359 self.docclass = docclass
2360 if docopt is not None:
2361 self.docopt = docopt
2362 if usefiles is not None:
2363 self.usefiles = helper.ensurelist(usefiles)
2364 if fontmaps is not None:
2365 self.fontmap = readfontmap(fontmaps.split())
2366 if waitfortex is not None:
2367 self.waitfortex = waitfortex
2368 if showwaitfortex is not None:
2369 self.showwaitfortex = showwaitfortex
2370 if texipc is not None:
2371 self.texipc = texipc
2372 if texdebug is not None:
2373 if self.texdebug is not None:
2374 self.texdebug.close()
2375 if texdebug[-4:] == ".tex":
2376 self.texdebug = open(texdebug, "w")
2377 else:
2378 self.texdebug = open("%s.tex" % texdebug, "w")
2379 if dvidebug is not None:
2380 self.dvidebug = dvidebug
2381 if errordebug is not None:
2382 self.errordebug = errordebug
2383 if dvicopy is not None:
2384 self.dvicopy = dvicopy
2385 if pyxgraphics is not None:
2386 self.pyxgraphics = pyxgraphics
2387 if errordebug is not None:
2388 self.errordebug = errordebug
2389 if texmessagestart is not None:
2390 texmessagestart = helper.ensuresequence(texmessagestart)
2391 helper.checkattr(texmessagestart, allowmulti=(texmessage,))
2392 self.texmessagestart = texmessagestart
2393 if texmessagedocclass is not None:
2394 texmessagedocclass = helper.ensuresequence(texmessagedocclass)
2395 helper.checkattr(texmessagedocclass, allowmulti=(texmessage,))
2396 self.texmessagedocclass = texmessagedocclass
2397 if texmessagebegindoc is not None:
2398 texmessagebegindoc = helper.ensuresequence(texmessagebegindoc)
2399 helper.checkattr(texmessagebegindoc, allowmulti=(texmessage,))
2400 self.texmessagebegindoc = texmessagebegindoc
2401 if texmessageend is not None:
2402 texmessageend = helper.ensuresequence(texmessageend)
2403 helper.checkattr(texmessageend, allowmulti=(texmessage,))
2404 self.texmessageend = texmessageend
2405 if texmessagedefaultpreamble is not None:
2406 texmessagedefaultpreamble = helper.ensuresequence(texmessagedefaultpreamble)
2407 helper.checkattr(texmessagedefaultpreamble, allowmulti=(texmessage,))
2408 self.texmessagedefaultpreamble = texmessagedefaultpreamble
2409 if texmessagedefaultrun is not None:
2410 texmessagedefaultrun = helper.ensuresequence(texmessagedefaultrun)
2411 helper.checkattr(texmessagedefaultrun, allowmulti=(texmessage,))
2412 self.texmessagedefaultrun = texmessagedefaultrun
2414 def preamble(self, expr, *args):
2415 r"""put something into the TeX/LaTeX preamble
2416 - in LaTeX, this is done before the \begin{document}
2417 (you might use \AtBeginDocument, when you're in need for)
2418 - it is not allowed to call preamble after calling the
2419 text method for the first time (for LaTeX this is needed
2420 due to \begin{document}; in TeX it is forced for compatibility
2421 (you should be able to switch from TeX to LaTeX, if you want,
2422 without breaking something
2423 - preamble expressions must not create any dvi output
2424 - args might contain texmessage instances"""
2425 if self.texdone or not self.preamblemode:
2426 raise TexNotInPreambleModeError
2427 helper.checkattr(args, allowmulti=(texmessage,))
2428 args = helper.getattrs(args, texmessage, default=self.texmessagedefaultpreamble)
2429 self.execute(expr, *args)
2430 self.preambles.append((expr, args))
2432 PyXBoxPattern = re.compile(r"PyXBox:page=(?P<page>\d+),lt=(?P<lt>-?\d*((\d\.?)|(\.?\d))\d*)pt,rt=(?P<rt>-?\d*((\d\.?)|(\.?\d))\d*)pt,ht=(?P<ht>-?\d*((\d\.?)|(\.?\d))\d*)pt,dp=(?P<dp>-?\d*((\d\.?)|(\.?\d))\d*)pt:")
2434 def _text(self, x, y, expr, *args):
2435 """create text by passing expr to TeX/LaTeX
2436 - returns a textbox containing the result from running expr thru TeX/LaTeX
2437 - the box center is set to x, y
2438 - *args may contain attr parameters, namely:
2439 - textattr instances
2440 - texmessage instances
2441 - trafo._trafo instances
2442 - style.fillstyle instances"""
2443 if expr is None:
2444 raise ValueError("None expression is invalid")
2445 if self.texdone:
2446 self.reset(reinit=1)
2447 first = 0
2448 if self.preamblemode:
2449 if self.mode == "latex":
2450 self.execute("\\begin{document}", *self.texmessagebegindoc)
2451 self.preamblemode = 0
2452 first = 1
2453 if self.texipc and self.dvicopy:
2454 raise RuntimeError("texipc and dvicopy can't be mixed up")
2455 helper.checkattr(args, allowmulti=(textattr, texmessage, trafo._trafo, style.fillstyle))
2456 textattrs = attr.getattrs(args, [textattr])
2457 textattrs = attr.mergeattrs(textattrs)
2458 lentextattrs = len(textattrs)
2459 for i in range(lentextattrs):
2460 expr = textattrs[lentextattrs-1-i].apply(expr)
2461 self.execute(expr, *helper.getattrs(args, texmessage, default=self.texmessagedefaultrun))
2462 if self.texipc:
2463 if first:
2464 self.dvifiles.append(dvifile("%s.dvi" % self.texfilename, self.fontmap, debug=self.dvidebug))
2465 self.dvifiles[-1].readpreamble()
2466 self.dvifiles[-1].readpage()
2467 match = self.PyXBoxPattern.search(self.texmessage)
2468 if not match or int(match.group("page")) != self.page:
2469 raise TexResultError("box extents not found", self)
2470 left, right, height, depth = map(lambda x: float(x) * 72.0 / 72.27, match.group("lt", "rt", "ht", "dp"))
2471 box = _textbox(x, y, left, right, height, depth, self, self.dvinumber, self.page,
2472 *helper.getattrs(args, style.fillstyle, default=[]))
2473 for t in helper.getattrs(args, trafo._trafo, default=()):
2474 box.reltransform(t)
2475 return box
2477 def text(self, x, y, expr, *args):
2478 return self._text(unit.topt(x), unit.topt(y), expr, *args)
2481 # the module provides an default texrunner and methods for direct access
2482 defaulttexrunner = texrunner()
2483 reset = defaulttexrunner.reset
2484 set = defaulttexrunner.set
2485 preamble = defaulttexrunner.preamble
2486 text = defaulttexrunner.text
2487 _text = defaulttexrunner._text