pdftex; texter (graph is currently broken); data docstrings --- minor other stuff...
[PyX/mjg.git] / pyx / text.py
blob2c4dd005026b5ed94bc826b2cc786eed7c249aa1
1 #!/usr/bin/env python
4 # Copyright (C) 2002 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2002 André Wobst <wobsta@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 # (TODO: check whether Knuth's division can be simplified within Python)
24 # seems to be the case!
26 import exceptions, glob, os, threading, Queue, traceback, re, struct, tempfile, sys, atexit
27 import helper, unit, box, base, trafo, canvas, pykpathsea
29 class fix_word:
30 def __init__(self, word):
31 if word >= 0:
32 self.sign = 1
33 else:
34 self.sign = -1
36 self.precomma = abs(word) >> 20
37 self.postcomma = abs(word) & 0xFFFFF
39 def __float__(self):
40 return self.sign * (self.precomma + 1.0*self.postcomma/0xFFFFF)
42 def __mul__(self, other):
43 # hey, it's Q&D
44 result = fix_word(0)
46 result.sign = self.sign*other.sign
47 c = self.postcomma*other.precomma + self.precomma*other.postcomma
48 result.precomma = self.precomma*other.precomma + (c >> 20)
49 result.postcomma = c & 0xFFFFF + ((self.postcomma*other.postcomma) >> 40)
50 return result
53 class binfile:
55 def __init__(self, filename, mode="r"):
56 self.file = open(filename, mode)
58 def tell(self):
59 return self.file.tell()
61 def read(self, bytes):
62 return self.file.read(bytes)
64 def readint(self, bytes = 4, signed = 0):
65 first = 1
66 result = 0
67 while bytes:
68 value = ord(self.file.read(1))
69 if first and signed and value > 127:
70 value -= 256
71 first = 0
72 result = 256 * result + value
73 bytes -= 1
74 return result
76 def readint32(self):
77 return struct.unpack(">l", self.file.read(4))[0]
79 def readuint32(self):
80 return struct.unpack(">L", self.file.read(4))[0]
82 def readint24(self):
83 # XXX: checkme
84 return struct.unpack(">l", "\0"+self.file.read(3))[0]
86 def readuint24(self):
87 # XXX: checkme
88 return struct.unpack(">L", "\0"+self.file.read(3))[0]
90 def readint16(self):
91 return struct.unpack(">h", self.file.read(2))[0]
93 def readuint16(self):
94 return struct.unpack(">H", self.file.read(2))[0]
96 def readchar(self):
97 return struct.unpack("b", self.file.read(1))[0]
99 def readuchar(self):
100 return struct.unpack("B", self.file.read(1))[0]
102 def readstring(self, bytes):
103 l = self.readuchar()
104 assert l <= bytes-1, "inconsistency in file: string too long"
105 return self.file.read(bytes-1)[:l]
107 class DVIError(exceptions.Exception): pass
109 class TFMError(exceptions.Exception): pass
111 class char_info_word:
112 def __init__(self, word):
113 self.width_index = (word & 0xFF000000) >> 24
114 self.height_index = (word & 0x00F00000) >> 20
115 self.depth_index = (word & 0x000F0000) >> 16
116 self.italic_index = (word & 0x0000FC00) >> 10
117 self.tag = (word & 0x00000300) >> 8
118 self.remainder = (word & 0x000000FF)
120 if self.width_index == 0:
121 raise TFMError("width_index should not be zero")
124 class TFMFile:
125 def __init__(self, name, debug=0):
126 self.file = binfile(name, "rb")
127 self.debug = debug
130 # read pre header
133 self.lf = self.file.readint16()
134 self.lh = self.file.readint16()
135 self.bc = self.file.readint16()
136 self.ec = self.file.readint16()
137 self.nw = self.file.readint16()
138 self.nh = self.file.readint16()
139 self.nd = self.file.readint16()
140 self.ni = self.file.readint16()
141 self.nl = self.file.readint16()
142 self.nk = self.file.readint16()
143 self.ne = self.file.readint16()
144 self.np = self.file.readint16()
146 if not (self.bc-1 <= self.ec <= 255 and
147 self.ne <= 256 and
148 self.lf == 6+self.lh+(self.ec-self.bc+1)+self.nw+self.nh+self.nd
149 +self.ni+self.nl+self.nk+self.ne+self.np):
150 raise TFMError, "error in TFM pre-header"
152 if debug:
153 print "lh=%d" % self.lh
156 # read header
159 self.checksum = self.file.readint32()
160 self.designsizeraw = self.file.readint32()
161 assert self.designsizeraw > 0, "invald design size"
162 self.designsize = fix_word(self.designsizeraw)
163 if self.lh > 2:
164 assert self.lh > 11, "inconsistency in TFM file: incomplete field"
165 self.charcoding = self.file.readstring(40)
166 else:
167 self.charcoding = None
169 if self.lh > 12:
170 assert self.lh > 16, "inconsistency in TFM file: incomplete field"
171 self.fontfamily = self.file.readstring(20)
172 else:
173 self.fontfamily = None
175 if self.debug:
176 print "(FAMILY %s)" % self.fontfamily
177 print "(CODINGSCHEME %s)" % self.charcoding
178 print "(DESINGSIZE R %f)" % self.designsize
180 if self.lh > 17:
181 self.sevenbitsave = self.file.readuchar()
182 # ignore the following two bytes
183 self.file.readint16()
184 facechar = self.file.readuchar()
185 # decode ugly face specification into the Knuth suggested string
186 if facechar < 18:
187 if facechar >= 12:
188 self.face = "E"
189 facechar -= 12
190 elif facechar >= 6:
191 self.face = "C"
192 facechar -= 6
193 else:
194 self.face = "R"
196 if facechar >= 4:
197 self.face = "L" + self.face
198 facechar -= 4
199 elif facechar >= 2:
200 self.face = "B" + self.face
201 facechar -= 2
202 else:
203 self.face = "M" + self.face
205 if facechar == 1:
206 self.face = self.face[0] + "I" + self.face[1]
207 else:
208 self.face = self.face[0] + "R" + self.face[1]
210 else:
211 self.face = None
212 else:
213 self.sevenbitsave = self.face = None
215 if self.lh > 18:
216 # just ignore the rest
217 print self.file.read((self.lh-18)*4)
220 # read char_info
223 self.char_info = [None for charcode in range(self.ec+1)]
225 for charcode in range(self.bc, self.ec+1):
226 self.char_info[charcode] = char_info_word(self.file.readint32())
229 # read widths
232 self.width = [None for width_index in range(self.nw)]
233 for width_index in range(self.nw):
234 # self.width[width_index] = fix_word(self.file.readint32())
235 self.width[width_index] = self.file.readint32()
238 # read heights
241 self.height = [None for height_index in range(self.nh)]
242 for height_index in range(self.nh):
243 # self.height[height_index] = fix_word(self.file.readint32())
244 self.height[height_index] = self.file.readint32()
247 # read depths
250 self.depth = [None for depth_index in range(self.nd)]
251 for depth_index in range(self.nd):
252 # self.depth[depth_index] = fix_word(self.file.readint32())
253 self.depth[depth_index] = self.file.readint32()
256 # read italic
259 self.italic = [None for italic_index in range(self.ni)]
260 for italic_index in range(self.ni):
261 # self.italic[italic_index] = fix_word(self.file.readint32())
262 self.italic[italic_index] = self.file.readint32()
265 # read lig_kern
268 # XXX decode to lig_kern_command
270 self.lig_kern = [None for lig_kern_index in range(self.nl)]
271 for lig_kern_index in range(self.nl):
272 self.lig_kern[lig_kern_index] = self.file.readint32()
275 # read kern
278 self.kern = [None for kern_index in range(self.nk)]
279 for kern_index in range(self.nk):
280 # self.kern[kern_index] = fix_word(self.file.readint32())
281 self.kern[kern_index] = self.file.readint32()
284 # read exten
287 # XXX decode to extensible_recipe
289 self.exten = [None for exten_index in range(self.ne)]
290 for exten_index in range(self.ne):
291 self.exten[exten_index] = self.file.readint32()
294 # read param
297 # XXX decode
299 self.param = [None for param_index in range(self.np)]
300 for param_index in range(self.np):
301 self.param[param_index] = self.file.readint32()
304 class Font:
305 def __init__(self, name, c, q, d, tfmconv, debug=0):
306 self.name = name
307 self.path = pykpathsea.find_file("%s.tfm" % self.name, pykpathsea.kpse_tfm_format)
308 if self.path is None:
309 raise TFMError("cannot find %f.tfm" % self.name)
310 self.tfmfile = TFMFile(self.path, debug)
312 if self.tfmfile.checksum!=c:
313 raise DVIError("check sums do not agree: %d vs. %d" %
314 (self.tfmfile.checksum, c))
316 self.tfmdesignsize = round(tfmconv*self.tfmfile.designsizeraw)
318 if abs(self.tfmdesignsize - d) > 2:
319 raise DVIError("design sizes do not agree: %d vs. %d" %
320 (self.tfmdesignsize, d))
321 if q < 0 or q > 134217728:
322 raise DVIError("font '%s' not loaded: bad scale" % self.name)
323 if d < 0 or d > 134217728:
324 raise DVIError("font '%s' not loaded: bad design size" % self.name)
326 self.scale = 1.0*q/d
327 self.alpha = 16;
328 self.q = self.qorig = q
329 while self.q >= 8388608:
330 self.q = self.q/2
331 self.alpha *= 2
333 self.beta = 256/self.alpha;
334 self.alpha = self.alpha*self.q;
336 # for bookkeeping of used characters
337 self.usedchars = [0] * 256
339 def __str__(self):
340 return "Font(%s, %d)" % (self.name, self.tfmdesignsize)
342 __repr__ = __str__
344 def convert(self, width):
345 # simplified version
346 return 16L*width*self.qorig/16777216L
348 # original algorithm of Knuth (at the moment not used)
349 b0 = width >> 24
350 b1 = (width >> 16) & 0xff
351 b2 = (width >> 8 ) & 0xff
352 b3 = (width ) & 0xff
354 if b0 == 0:
355 return (((((b3*self.q)/256)+(b2*self.q))/256)+(b1*self.q))/self.beta
356 elif b0 == 255:
357 return (((((b3*self.q)/256)+(b2*self.q))/256)+(b1*self.q))/self.beta-self.alpha
358 else:
359 raise TFMError("error in font size")
361 def getwidth(self, charcode):
362 return self.convert(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
364 def getheight(self, charcode):
365 return self.convert(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
367 def getdepth(self, charcode):
368 return self.convert(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
370 def getitalic(self, charcode):
371 return self.convert(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
373 def markcharused(self, charcode):
374 self.usedchars[charcode] = 1
376 def mergeusedchars(self, otherfont):
377 for i in range(len(self.usedchars)):
378 self.usedchars[i] = self.usedchars[i] or otherfont.usedchars[i]
381 _DVI_CHARMIN = 0 # typeset a character and move right (range min)
382 _DVI_CHARMAX = 127 # typeset a character and move right (range max)
383 _DVI_SET1234 = 128 # typeset a character and move right
384 _DVI_SETRULE = 132 # typeset a rule and move right
385 _DVI_PUT1234 = 133 # typeset a character
386 _DVI_PUTRULE = 137 # typeset a rule
387 _DVI_NOP = 138 # no operation
388 _DVI_BOP = 139 # beginning of page
389 _DVI_EOP = 140 # ending of page
390 _DVI_PUSH = 141 # save the current positions (h, v, w, x, y, z)
391 _DVI_POP = 142 # restore positions (h, v, w, x, y, z)
392 _DVI_RIGHT1234 = 143 # move right
393 _DVI_W0 = 147 # move right by w
394 _DVI_W1234 = 148 # move right and set w
395 _DVI_X0 = 152 # move right by x
396 _DVI_X1234 = 153 # move right and set x
397 _DVI_DOWN1234 = 157 # move down
398 _DVI_Y0 = 161 # move down by y
399 _DVI_Y1234 = 162 # move down and set y
400 _DVI_Z0 = 166 # move down by z
401 _DVI_Z1234 = 167 # move down and set z
402 _DVI_FNTNUMMIN = 171 # set current font (range min)
403 _DVI_FNTNUMMAX = 234 # set current font (range max)
404 _DVI_FNT1234 = 235 # set current font
405 _DVI_SPECIAL1234 = 239 # special (dvi extention)
406 _DVI_FNTDEF1234 = 243 # define the meaning of a font number
407 _DVI_PRE = 247 # preamble
408 _DVI_POST = 248 # postamble beginning
409 _DVI_POSTPOST = 249 # postamble ending
411 _DVI_VERSION = 2 # dvi version
413 # position variable indices
414 _POS_H = 0
415 _POS_V = 1
416 _POS_W = 2
417 _POS_X = 3
418 _POS_Y = 4
419 _POS_Z = 5
421 # reader states
422 _READ_PRE = 1
423 _READ_NOPAGE = 2
424 _READ_PAGE = 3
425 _READ_POST = 4
426 _READ_POSTPOST = 5
427 _READ_DONE = 6
429 class DVIFile:
431 def __init__(self, filename, debug=0):
432 self.filename = filename
433 self.debug = debug
434 self.readfile()
436 # helper routines
438 def flushout(self):
439 if self.actoutstart:
440 x = unit.t_m(self.actoutstart[0] * self.conv * 0.0254 / self.resolution)
441 y = -unit.t_m(self.actoutstart[1] * self.conv * 0.0254 / self.resolution)
442 if self.debug:
443 print "[%s]" % self.actoutstring
444 self.actpage.append("%f %f moveto (%s) show\n" %
445 (unit.topt(x), unit.topt(y), self.actoutstring))
446 self.actoutstart = None
448 def putchar(self, char, inch=1):
449 if self.actoutstart is None:
450 self.actoutstart = self.pos[_POS_H], self.pos[_POS_V]
451 self.actoutstring = ""
452 if char > 32 and char < 128 and chr(char) not in "()[]<>":
453 ascii = "%s" % chr(char)
454 else:
455 ascii = "\\%03o" % char
456 self.actoutstring = self.actoutstring + ascii
457 dx = inch and self.fonts[self.activefont].getwidth(char) or 0
458 self.fonts[self.activefont].markcharused(char)
459 if self.debug:
460 print ("%d: %schar%d h:=%d+%d=%d, hh:=%d" %
461 (self.filepos,
462 inch and "set" or "put",
463 char,
464 self.pos[_POS_H], dx, self.pos[_POS_H]+dx,
466 self.pos[_POS_H] += dx
467 if not inch:
468 # XXX: correct !?
469 self.flushout()
471 def putrule(self, height, width, inch=1):
472 self.flushout()
473 x1 = unit.t_m(self.pos[_POS_H] * self.conv * 0.0254 / self.resolution)
474 y1 = -unit.t_m(self.pos[_POS_V] * self.conv * 0.0254 / self.resolution)
475 w = unit.t_m(width * self.conv * 0.0254 / self.resolution)
476 h = unit.t_m(height * self.conv * 0.0254 / self.resolution)
478 if height > 0 and width > 0:
479 if self.debug:
480 pixelw = int(width*self.conv)
481 if pixelw < width*self.conv: pixelw += 1
482 pixelh = int(height*self.conv)
483 if pixelh < height*self.conv: pixelh += 1
485 print ("%d: %srule height %d, width %d (%dx%d pixels)" %
486 (self.filepos, inch and "set" or "put", height, width, pixelh, pixelw))
488 self.actpage.append("%f %f moveto %f 0 rlineto 0 %f rlineto "
489 "%f 0 rlineto closepath fill\n" %
490 (unit.topt(x1), unit.topt(y1),
491 unit.topt(w), unit.topt(h), -unit.topt(w)))
492 else:
493 if self.debug:
494 print ("%d: %srule height %d, width %d (invisible)" %
495 (self.filepos, inch and "set" or "put", height, width))
497 if inch:
498 if self.debug:
499 print (" h:=%d+%d=%d, hh:=%d" %
500 (self.pos[_POS_H], width, self.pos[_POS_H]+width, 0))
501 self.pos[_POS_H] += width
504 def usefont(self, fontnum):
505 self.flushout()
506 self.activefont = fontnum
508 fontname = self.fonts[self.activefont].name
509 fontscale = self.fonts[self.activefont].scale
510 fontdesignsize = float(self.fonts[self.activefont].tfmfile.designsize)
511 self.actpage.append("/%s %f selectfont\n" %
512 (fontname.upper(), fontscale*fontdesignsize*72/72.27))
515 if self.debug:
516 print ("%d: fntnum%i current font is %s" %
517 (self.filepos,
518 self.activefont, self.fonts[fontnum].name))
520 def definefont(self, cmdnr, num, c, q, d, fontname):
521 # cmdnr: type of fontdef command (only used for debugging output)
522 # c: checksum
523 # q: scaling factor
524 # Note that q is actually s in large parts of the documentation.
525 # d: design size
527 self.fonts[num] = Font(fontname, c, q, d, self.tfmconv, self.debug > 1)
529 if self.debug:
530 print "%d: fntdef%d %i: %s" % (self.filepos, cmdnr, num, fontname)
532 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
533 # m = 1.0*q/d
534 # scalestring = scale!=1000 and " scaled %d" % scale or ""
535 # print ("Font %i: %s%s---loaded at size %d DVI units" %
536 # (num, fontname, scalestring, q))
537 # if scale!=1000:
538 # print " (this font is magnified %d%%)" % round(scale/10)
540 # routines corresponding to the different reader states of the dvi maschine
542 def _read_pre(self):
543 file = self.file
544 while 1:
545 self.filepos = file.tell()
546 cmd = file.readuchar()
547 if cmd == _DVI_NOP:
548 pass
549 elif cmd == _DVI_PRE:
550 if self.file.readuchar() != _DVI_VERSION: raise DVIError
551 num = file.readuint32()
552 den = file.readuint32()
553 mag = file.readuint32()
555 self.tfmconv = (25400000.0/num)*(den/473628672)/16.0;
556 # resolution in dpi
557 self.resolution = 300.0
558 # self.trueconv = conv in DVIType docu
559 self.trueconv = (num/254000.0)*(self.resolution/den)
560 self.conv = self.trueconv*(mag/1000.0)
562 comment = file.read(file.readuchar())
563 return _READ_NOPAGE
564 else:
565 raise DVIError
567 def _read_nopage(self):
568 file = self.file
569 while 1:
570 self.filepos = file.tell()
571 cmd = file.readuchar()
572 if cmd == _DVI_NOP:
573 pass
574 elif cmd == _DVI_BOP:
575 self.flushout()
576 if self.debug:
577 print "%d: beginning of page" % self.filepos,
578 print file.readuint32()
579 for i in range(9): file.readuint32()
580 else:
581 for i in range(10): file.readuint32()
582 file.readuint32()
584 self.pos = [0, 0, 0, 0, 0, 0]
585 self.pages.append([])
586 self.actpage = self.pages[-1]
587 return _READ_PAGE
588 elif cmd == _DVI_POST:
589 return _READ_DONE # we skip the rest
590 else:
591 raise DVIError
593 def _read_page(self):
594 file = self.file
595 while 1:
596 self.filepos = file.tell()
597 cmd = file.readuchar()
598 if cmd == _DVI_NOP:
599 pass
600 if cmd >= _DVI_CHARMIN and cmd <= _DVI_CHARMAX:
601 self.putchar(cmd)
602 elif cmd >= _DVI_SET1234 and cmd < _DVI_SET1234 + 4:
603 self.putchar(file.readint(cmd - _DVI_SET1234 + 1))
604 elif cmd == _DVI_SETRULE:
605 self.putrule(file.readint32(), file.readint32())
606 elif cmd >= _DVI_PUT1234 and cmd < _DVI_PUT1234 + 4:
607 self.putchar(file.readint(cmd - _DVI_PUT1234 + 1), inch=0)
608 elif cmd == _DVI_PUTRULE:
609 self.putrule(file.readint32(), file.readint32(), 0)
610 elif cmd == _DVI_EOP:
611 self.flushout()
612 if self.debug:
613 print "%d: eop" % self.filepos
614 print
615 return _READ_NOPAGE
616 elif cmd == _DVI_PUSH:
617 self.stack.append(tuple(self.pos))
618 if self.debug:
619 print "%d: push" % self.filepos
620 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
621 (( len(self.stack)-1,)+tuple(self.pos)))
622 elif cmd == _DVI_POP:
623 self.flushout()
624 self.pos = list(self.stack[-1])
625 del self.stack[-1]
626 if self.debug:
627 print "%d: pop" % self.filepos
628 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
629 (( len(self.stack),)+tuple(self.pos)))
630 elif cmd >= _DVI_RIGHT1234 and cmd < _DVI_RIGHT1234 + 4:
631 self.flushout()
632 dh = file.readint(cmd - _DVI_RIGHT1234 + 1, 1)
633 if self.debug:
634 print ("%d: right%d %d h:=%d%+d=%d, hh:=" %
635 (self.filepos,
636 cmd - _DVI_RIGHT1234 + 1,
638 self.pos[_POS_H],
640 self.pos[_POS_H]+dh))
641 self.pos[_POS_H] += dh
642 elif cmd == _DVI_W0:
643 self.flushout()
644 if self.debug:
645 print ("%d: w0 %d h:=%d%+d=%d, hh:=" %
646 (self.filepos,
647 self.pos[_POS_W],
648 self.pos[_POS_H],
649 self.pos[_POS_W],
650 self.pos[_POS_H]+self.pos[_POS_W]))
651 self.pos[_POS_H] += self.pos[_POS_W]
652 elif cmd >= _DVI_W1234 and cmd < _DVI_W1234 + 4:
653 self.flushout()
654 self.pos[_POS_W] = file.readint(cmd - _DVI_W1234 + 1, 1)
655 if self.debug:
656 print ("%d: w%d %d h:=%d%+d=%d, hh:=" %
657 (self.filepos,
658 cmd - _DVI_W1234 + 1,
659 self.pos[_POS_W],
660 self.pos[_POS_H],
661 self.pos[_POS_W],
662 self.pos[_POS_H]+self.pos[_POS_W]))
663 self.pos[_POS_H] += self.pos[_POS_W]
664 elif cmd == _DVI_X0:
665 self.flushout()
666 self.pos[_POS_H] += self.pos[_POS_X]
667 elif cmd >= _DVI_X1234 and cmd < _DVI_X1234 + 4:
668 self.flushout()
669 self.pos[_POS_X] = file.readint(cmd - _DVI_X1234 + 1, 1)
670 self.pos[_POS_H] += self.pos[_POS_X]
671 elif cmd >= _DVI_DOWN1234 and cmd < _DVI_DOWN1234 + 4:
672 self.flushout()
673 dv = file.readint(cmd - _DVI_DOWN1234 + 1, 1)
674 if self.debug:
675 print ("%d: down%d %d v:=%d%+d=%d, vv:=" %
676 (self.filepos,
677 cmd - _DVI_DOWN1234 + 1,
679 self.pos[_POS_V],
681 self.pos[_POS_V]+dv))
682 self.pos[_POS_V] += dv
683 elif cmd == _DVI_Y0:
684 self.flushout()
685 if self.debug:
686 print ("%d: y0 %d v:=%d%+d=%d, vv:=" %
687 (self.filepos,
688 self.pos[_POS_Y],
689 self.pos[_POS_V],
690 self.pos[_POS_Y],
691 self.pos[_POS_V]+self.pos[_POS_Y]))
692 self.pos[_POS_V] += self.pos[_POS_Y]
693 elif cmd >= _DVI_Y1234 and cmd < _DVI_Y1234 + 4:
694 self.flushout()
695 self.pos[_POS_Y] = file.readint(cmd - _DVI_Y1234 + 1, 1)
696 if self.debug:
697 print ("%d: y%d %d v:=%d%+d=%d, vv:=" %
698 (self.filepos,
699 cmd - _DVI_Y1234 + 1,
700 self.pos[_POS_Y],
701 self.pos[_POS_V],
702 self.pos[_POS_Y],
703 self.pos[_POS_V]+self.pos[_POS_Y]))
704 self.pos[_POS_V] += self.pos[_POS_Y]
705 elif cmd == _DVI_Z0:
706 self.flushout()
707 self.pos[_POS_V] += self.pos[_POS_Z]
708 elif cmd >= _DVI_Z1234 and cmd < _DVI_Z1234 + 4:
709 self.flushout()
710 self.pos[_POS_Z] = file.readint(cmd - _DVI_Z1234 + 1, 1)
711 self.pos[_POS_V] += self.pos[_POS_Z]
712 elif cmd >= _DVI_FNTNUMMIN and cmd <= _DVI_FNTNUMMAX:
713 self.usefont(cmd - _DVI_FNTNUMMIN)
714 elif cmd >= _DVI_FNT1234 and cmd < _DVI_FNT1234 + 4:
715 self.usefont(file.readint(cmd - _DVI_FNT1234 + 1, 1))
716 elif cmd >= _DVI_SPECIAL1234 and cmd < _DVI_SPECIAL1234 + 4:
717 print "special %s" % file.read(file.readint(cmd - _DVI_SPECIAL1234 + 1))
718 raise RuntimeError("specials are not yet handled, abort")
719 elif cmd >= _DVI_FNTDEF1234 and cmd < _DVI_FNTDEF1234 + 4:
720 if cmd == _DVI_FNTDEF1234:
721 num=file.readuchar()
722 elif cmd == _DVI_FNTDEF1234+1:
723 num=file.readuint16()
724 elif cmd == _DVI_FNTDEF1234+2:
725 num=file.readuint24()
726 elif cmd == _DVI_FNTDEF1234+3:
727 # Cool, here we have according to docu a signed int. Why?
728 num = file.readint32()
729 self.definefont(cmd-_DVI_FNTDEF1234+1,
730 num,
731 file.readint32(),
732 file.readint32(),
733 file.readint32(),
734 file.read(file.readuchar()+file.readuchar()))
735 else: raise DVIError
737 def readfile(self):
738 """ reads and parses dvi file
740 This routine reads the dvi file and generates a list
741 of pages in self.pages. Each page consists itself of
742 a list of PSCommands equivalent to the content of
743 the dvi file. Furthermore, the list of used fonts
744 can be extracted from the array self.fonts.
747 # XXX max number of fonts
748 self.fonts = [None for i in range(64)]
749 self.activefont = None
751 self.stack = []
753 # here goes the result, for each page one list.
754 self.pages = []
756 # pointer to currently active page
757 self.actpage = None
759 # currently active output: position and content
760 self.actoutstart = None
761 self.actoutstring = ""
763 self.file = binfile(self.filename, "rb")
765 # currently read byte in file (for debugging output)
766 self.filepos = None
768 # start up reading process
769 state = _READ_PRE
770 while state!=_READ_DONE:
771 if state == _READ_PRE:
772 state = self._read_pre()
773 elif state == _READ_NOPAGE:
774 state = self._read_nopage()
775 elif state == _READ_PAGE:
776 state = self._read_page()
777 else:
778 raise DVIError # unexpected reader state, should not happen
779 self.flushout()
781 def prolog(self):
782 """ return prolog corresponding to contents of dvi file """
783 result = []
784 for font in self.fonts:
785 if font: result.append(canvas.fontdefinition(font))
786 return result
788 def write(self, file, page):
789 """write PostScript output for page into file"""
790 if self.debug:
791 print "dvifile(\"%s\").write() for page %s called" % (self.filename, page)
792 for pscommand in self.pages[page-1]:
793 file.write(pscommand)
796 ###############################################################################
797 # texmessages
798 # - please don't get confused:
799 # - there is a texmessage (and a texmessageparsed) attribute within the
800 # texrunner; it contains TeX/LaTeX response from the last command execution
801 # - instances of classes derived from the class texmessage get used to
802 # parse the TeX/LaTeX response as it is storred in the texmessageparsed
803 # attribute of a texrunner instance
804 # - the multiple usage of the name texmessage might be removed in the future
805 # - texmessage instances should implement _Itexmessage
806 ###############################################################################
808 class TexResultError(Exception):
809 """specialized texrunner exception class
810 - it is raised by texmessage instances, when a texmessage indicates an error
811 - it is raised by the texrunner itself, whenever there is a texmessage left
812 after all parsing of this message (by texmessage instances)"""
814 def __init__(self, description, texrunner):
815 self.description = description
816 self.texrunner = texrunner
818 def __str__(self):
819 "prints a detailed report about the problem"
820 return ("%s\n" % self.description +
821 "The expression passed to TeX was:\n"
822 " %s\n" % self.texrunner.expr.replace("\n", "\n ").rstrip() +
823 "The return message from TeX was:\n"
824 " %s\n" % self.texrunner.texmessage.replace("\n", "\n ").rstrip() +
825 "After parsing this message, the following was left:\n"
826 " %s" % self.texrunner.texmessageparsed.replace("\n", "\n ").rstrip())
829 class TexResultWarning(TexResultError):
830 """as above, but with different handling of the exception
831 - when this exception is raised by a texmessage instance,
832 the information just get reported and the execution continues"""
833 pass
836 class _Itexmessage:
837 """validates/invalidates TeX/LaTeX response"""
839 def check(self, texrunner):
840 """check a Tex/LaTeX response and respond appropriate
841 - read the texrunners texmessageparsed attribute
842 - if there is an problem found, raise an appropriate
843 exception (TexResultError or TexResultWarning)
844 - remove any valid and identified TeX/LaTeX response
845 from the texrunners texmessageparsed attribute
846 -> finally, there should be nothing left in there,
847 otherwise it is interpreted as an error"""
850 class texmessage: pass
853 class _texmessagestart(texmessage):
854 """validates TeX/LaTeX startup"""
856 __implements__ = _Itexmessage
858 startpattern = re.compile(r"This is [0-9a-zA-Z\s_]*TeX")
860 def check(self, texrunner):
861 m = self.startpattern.search(texrunner.texmessageparsed)
862 if not m:
863 raise TexResultError("TeX startup failed", texrunner)
864 texrunner.texmessageparsed = texrunner.texmessageparsed[m.end():]
865 try:
866 texrunner.texmessageparsed = texrunner.texmessageparsed.split("%s.tex" % texrunner.texfilename, 1)[1]
867 except (IndexError, ValueError):
868 raise TexResultError("TeX running startup file failed", texrunner)
869 try:
870 texrunner.texmessageparsed = texrunner.texmessageparsed.split("*! Undefined control sequence.\n<*> \\raiseerror\n %\n", 1)[1]
871 except (IndexError, ValueError):
872 raise TexResultError("TeX scrollmode check failed", texrunner)
875 class _texmessagenoaux(texmessage):
876 """allows for LaTeXs no-aux-file warning"""
878 __implements__ = _Itexmessage
880 def check(self, texrunner):
881 try:
882 s1, s2 = texrunner.texmessageparsed.split("No file %s.aux." % texrunner.texfilename, 1)
883 texrunner.texmessageparsed = s1 + s2
884 except (IndexError, ValueError):
885 try:
886 s1, s2 = texrunner.texmessageparsed.split("No file %s%s%s.aux." % (os.curdir,
887 os.sep,
888 texrunner.texfilename), 1)
889 texrunner.texmessageparsed = s1 + s2
890 except (IndexError, ValueError):
891 pass
894 class _texmessageinputmarker(texmessage):
895 """validates the PyXInputMarker"""
897 __implements__ = _Itexmessage
899 def check(self, texrunner):
900 try:
901 s1, s2 = texrunner.texmessageparsed.split("PyXInputMarker:executeid=%s:" % texrunner.executeid, 1)
902 texrunner.texmessageparsed = s1 + s2
903 except (IndexError, ValueError):
904 raise TexResultError("PyXInputMarker expected", texrunner)
907 class _texmessagepyxbox(texmessage):
908 """validates the PyXBox output"""
910 __implements__ = _Itexmessage
912 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:")
914 def check(self, texrunner):
915 m = self.pattern.search(texrunner.texmessageparsed)
916 if m and m.group("page") == str(texrunner.page):
917 texrunner.texmessageparsed = texrunner.texmessageparsed[:m.start()] + texrunner.texmessageparsed[m.end():]
918 else:
919 raise TexResultError("PyXBox expected", texrunner)
922 class _texmessagepyxpageout(texmessage):
923 """validates the dvi shipout message (writing a page to the dvi file)"""
925 __implements__ = _Itexmessage
927 def check(self, texrunner):
928 try:
929 s1, s2 = texrunner.texmessageparsed.split("[80.121.88.%s]" % texrunner.page, 1)
930 texrunner.texmessageparsed = s1 + s2
931 except (IndexError, ValueError):
932 raise TexResultError("PyXPageOutMarker expected", texrunner)
935 class _texmessagetexend(texmessage):
936 """validates TeX/LaTeX finish"""
938 __implements__ = _Itexmessage
940 def check(self, texrunner):
941 try:
942 s1, s2 = texrunner.texmessageparsed.split("(%s.aux)" % texrunner.texfilename, 1)
943 texrunner.texmessageparsed = s1 + s2
944 except (IndexError, ValueError):
945 try:
946 s1, s2 = texrunner.texmessageparsed.split("(%s%s%s.aux)" % (os.curdir,
947 os.sep,
948 texrunner.texfilename), 1)
949 texrunner.texmessageparsed = s1 + s2
950 except (IndexError, ValueError):
951 pass
952 try:
953 s1, s2 = texrunner.texmessageparsed.split("(see the transcript file for additional information)", 1)
954 texrunner.texmessageparsed = s1 + s2
955 except (IndexError, ValueError):
956 pass
957 dvipattern = re.compile(r"Output written on %s\.dvi \((?P<page>\d+) pages?, \d+ bytes\)\." % texrunner.texfilename)
958 m = dvipattern.search(texrunner.texmessageparsed)
959 if texrunner.page:
960 if not m:
961 raise TexResultError("TeX dvifile messages expected", texrunner)
962 if m.group("page") != str(texrunner.page):
963 raise TexResultError("wrong number of pages reported", texrunner)
964 texrunner.texmessageparsed = texrunner.texmessageparsed[:m.start()] + texrunner.texmessageparsed[m.end():]
965 else:
966 try:
967 s1, s2 = texrunner.texmessageparsed.split("No pages of output.", 1)
968 texrunner.texmessageparsed = s1 + s2
969 except (IndexError, ValueError):
970 raise TexResultError("no dvifile expected")
971 try:
972 s1, s2 = texrunner.texmessageparsed.split("Transcript written on %s.log." % texrunner.texfilename, 1)
973 texrunner.texmessageparsed = s1 + s2
974 except (IndexError, ValueError):
975 raise TexResultError("TeX logfile message expected")
978 class _texmessageemptylines(texmessage):
979 """validates empty and "*-only" (TeX/LaTeX input marker in interactive mode) lines"""
981 __implements__ = _Itexmessage
983 pattern = re.compile(r"^\*?\n", re.M)
985 def check(self, texrunner):
986 m = self.pattern.search(texrunner.texmessageparsed)
987 while m:
988 texrunner.texmessageparsed = texrunner.texmessageparsed[:m.start()] + texrunner.texmessageparsed[m.end():]
989 m = self.pattern.search(texrunner.texmessageparsed)
992 class _texmessageload(texmessage):
993 """validates inclusion of arbitrary files
994 - the matched pattern is "(<filename> <arbitrary other stuff>)", where
995 <fielname> is a readable file and other stuff can be anything
996 - "(" and ")" must be used consistent (otherwise this validator just does nothing)
997 - this is not always wanted, but we just assume that file inclusion is fine"""
999 __implements__ = _Itexmessage
1001 pattern = re.compile(r"\((?P<filename>[^()\s\n]+)[^()]*\)")
1003 def baselevels(self, s, maxlevel=1, brackets="()"):
1004 """strip parts of a string above a given bracket level
1005 - return a modified (some parts might be removed) version of the string s
1006 where all parts inside brackets with level higher than maxlevel are
1007 removed
1008 - if brackets do not match (number of left and right brackets is wrong
1009 or at some points there were more right brackets than left brackets)
1010 just return the unmodified string"""
1011 level = 0
1012 highestlevel = 0
1013 res = ""
1014 for c in s:
1015 if c == brackets[0]:
1016 level += 1
1017 if level > highestlevel:
1018 highestlevel = level
1019 if level <= maxlevel:
1020 res += c
1021 if c == brackets[1]:
1022 level -= 1
1023 if level == 0 and highestlevel > 0:
1024 return res
1026 def check(self, texrunner):
1027 lowestbracketlevel = self.baselevels(texrunner.texmessageparsed)
1028 if lowestbracketlevel is not None:
1029 m = self.pattern.search(lowestbracketlevel)
1030 while m:
1031 print m.group("filename")
1032 if os.access(m.group("filename"), os.R_OK):
1033 lowestbracketlevel = lowestbracketlevel[:m.start()] + lowestbracketlevel[m.end():]
1034 else:
1035 break
1036 m = self.pattern.search(lowestbracketlevel)
1037 else:
1038 texrunner.texmessageparsed = lowestbracketlevel
1041 class _texmessagegraphicsload(_texmessageload):
1042 """validates the inclusion of files as the graphics packages writes it
1043 - works like _texmessageload, but using "<" and ">" as delimiters
1044 - filename must end with .eps and no further text is allowed"""
1046 pattern = re.compile(r"<(?P<filename>[^>]+.eps)>")
1048 def baselevels(self, s, brackets="<>", **args):
1049 return _texmessageload.baselevels(self, s, brackets=brackets, **args)
1052 #class _texmessagepdfmapload(_texmessageload):
1053 # """validates the inclusion of files as the graphics packages writes it
1054 # - works like _texmessageload, but using "{" and "}" as delimiters
1055 # - filename must end with .map and no further text is allowed"""
1057 # pattern = re.compile(r"{(?P<filename>[^}]+.map)}")
1059 # def baselevels(self, s, brackets="{}", **args):
1060 # return _texmessageload.baselevels(self, s, brackets=brackets, **args)
1063 class _texmessageignore(_texmessageload):
1064 """validates any TeX/LaTeX response
1065 - this might be used, when the expression is ok, but no suitable texmessage
1066 parser is available
1067 - PLEASE: - consider writing suitable tex message parsers
1068 - share your ideas/problems/solutions with others (use the PyX mailing lists)"""
1070 __implements__ = _Itexmessage
1072 def check(self, texrunner):
1073 texrunner.texmessageparsed = ""
1076 texmessage.start = _texmessagestart()
1077 texmessage.noaux = _texmessagenoaux()
1078 texmessage.inputmarker = _texmessageinputmarker()
1079 texmessage.pyxbox = _texmessagepyxbox()
1080 texmessage.pyxpageout = _texmessagepyxpageout()
1081 texmessage.texend = _texmessagetexend()
1082 texmessage.emptylines = _texmessageemptylines()
1083 texmessage.load = _texmessageload()
1084 texmessage.graphicsload = _texmessagegraphicsload()
1085 texmessage.ignore = _texmessageignore()
1088 ###############################################################################
1089 # texsettings
1090 # - texsettings are used to modify a TeX/LaTeX expression
1091 # to fit the users need
1092 # - texsettings have an order attribute (id), because the order is usually
1093 # important (e.g. you can not change the fontsize in mathmode in LaTeX)
1094 # - lower id's get applied later (are more outside -> mathmode has a higher
1095 # id than fontsize)
1096 # - order attributes are used to exclude complementary settings (with the
1097 # same id)
1098 # - texsettings might (in rare cases) depend on each other (e.g. parbox and
1099 # valign)
1100 ###############################################################################
1102 class _Itexsetting:
1103 """tex setting
1104 - modifies a TeX/LaTeX expression"""
1106 id = 0
1107 """order attribute for TeX settings
1108 - higher id's will be applied first (most inside)"""
1110 exclusive = 0
1111 """marks complementary effect of the setting
1112 - when set, settings with this id exclude each other
1113 - when unset, settings with this id do not exclude each other"""
1115 def modifyexpr(self, expr, texsettings, texrunner):
1116 """modifies the TeX/LaTeX expression
1117 - expr is the original expression
1118 - the return value is the modified expression
1119 - texsettings contains a list of all texsettings (in case a tex setting
1120 depends on another texsetting)
1121 - texrunner contains the texrunner in case the texsetting depends
1122 on it"""
1124 def __cmp__(self, other):
1125 """compare texsetting with other
1126 - other is a texsetting as well
1127 - performs an id comparison (NOTE: higher id's come first!!!)"""
1130 # preamble settings for texsetting macros
1131 _texsettingpreamble = ""
1133 class _texsetting:
1135 exclusive = 1
1137 def __cmp__(self, other):
1138 return -cmp(self.id, other.id) # note the sign!!!
1141 class halign(_texsetting):
1142 """horizontal alignment
1143 the left/right splitting is performed within the PyXBox routine"""
1145 __implements__ = _Itexsetting
1147 id = 1000
1149 def __init__(self, hratio):
1150 self.hratio = hratio
1152 def modifyexpr(self, expr, texsettings, texrunner):
1153 return r"\gdef\PyXHAlign{%.5f}%s" % (self.hratio, expr)
1155 halign.left = halign(0)
1156 halign.center = halign(0.5)
1157 halign.right = halign(1)
1160 _texsettingpreamble += "\\newbox\\PyXBoxVAlign%\n\\newdimen\PyXDimenVAlign%\n"
1162 class valign(_texsetting):
1163 "vertical alignment"
1165 id = 7000
1168 class _valigntop(valign):
1170 __implements__ = _Itexsetting
1172 def modifyexpr(self, expr, texsettings, texrunner):
1173 return r"\setbox\PyXBoxVAlign=\hbox{{%s}}\lower\ht\PyXBoxVAlign\box\PyXBoxVAlign" % expr
1176 class _valignmiddle(valign):
1178 __implements__ = _Itexsetting
1180 def modifyexpr(self, expr, texsettings, texrunner):
1181 return r"\setbox\PyXBoxVAlign=\hbox{{%s}}\PyXDimenVAlign=0.5\ht\PyXBoxVAlign\advance\PyXDimenVAlign by -0.5\dp\PyXBoxVAlign\lower\PyXDimenVAlign\box\PyXBoxVAlign" % expr
1184 class _valignbottom(valign):
1186 __implements__ = _Itexsetting
1188 def modifyexpr(self, expr, texsettings, texrunner):
1189 return r"\setbox\PyXBoxVAlign=\hbox{{%s}}\raise\dp\PyXBoxVAlign\box\PyXBoxVAlign" % expr
1192 class _valignbaseline(valign):
1194 __implements__ = _Itexsetting
1196 def modifyexpr(self, expr, texsettings, texrunner):
1197 for texsetting in texsettings:
1198 if isinstance(texsetting, parbox):
1199 raise RuntimeError("valign.baseline: specify top/middle/bottom baseline for parbox")
1202 class _valignxxxbaseline(valign):
1204 def modifyexpr(self, expr, texsettings, texrunner):
1205 for texsetting in texsettings:
1206 if isinstance(texsetting, parbox):
1207 break
1208 else:
1209 raise RuntimeError(self.noparboxmessage)
1210 return expr
1213 class _valigntopbaseline(_valignxxxbaseline):
1215 __implements__ = _Itexsetting
1217 noparboxmessage = "valign.topbaseline: no parbox defined"
1220 class _valignmiddlebaseline(_valignxxxbaseline):
1222 __implements__ = _Itexsetting
1224 noparboxmessage = "valign.middlebaseline: no parbox defined"
1227 class _valignbottombaseline(_valignxxxbaseline):
1229 __implements__ = _Itexsetting
1231 noparboxmessage = "valign.bottombaseline: no parbox defined"
1234 valign.top = _valigntop()
1235 valign.middle = _valignmiddle()
1236 valign.center = valign.middle
1237 valign.bottom = _valignbottom()
1238 valign.baseline = _valignbaseline()
1239 valign.topbaseline = _valigntopbaseline()
1240 valign.middlebaseline = _valignmiddlebaseline()
1241 valign.centerbaseline = valign.middlebaseline
1242 valign.bottombaseline = _valignbottombaseline()
1245 _texsettingpreamble += "\\newbox\\PyXBoxVBox%\n\\newdimen\PyXDimenVBox%\n"
1248 class _parbox(_texsetting):
1249 "goes into the vertical mode"
1251 __implements__ = _Itexsetting
1253 id = 7100
1255 def __init__(self, width):
1256 self.width = width
1258 def modifyexpr(self, expr, texsettings, texrunner):
1259 boxkind = "vtop"
1260 for texsetting in texsettings:
1261 if isinstance(texsetting, valign):
1262 if (not isinstance(texsetting, _valigntop) and
1263 not isinstance(texsetting, _valignmiddle) and
1264 not isinstance(texsetting, _valignbottom) and
1265 not isinstance(texsetting, _valigntopbaseline)):
1266 if isinstance(texsetting, _valignmiddlebaseline):
1267 boxkind = "vcenter"
1268 elif isinstance(texsetting, _valignbottombaseline):
1269 boxkind = "vbox"
1270 else:
1271 raise RuntimeError("parbox couldn'd identify the valign instance")
1272 if boxkind == "vcenter":
1273 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)
1274 else:
1275 return r"\linewidth%.5ftruept\%s{\hsize\linewidth{%s}}" % (self.width * 72.27 / 72, boxkind, expr)
1278 class parbox(_parbox):
1280 def __init__(self, width):
1281 _parbox.__init__(self, unit.topt(width))
1284 class vshift(_texsetting):
1286 exclusive = 0
1288 id = 5000
1291 class _vshiftchar(vshift):
1292 "vertical down shift by a fraction of a character height"
1294 def __init__(self, lowerratio, heightstr="0"):
1295 self.lowerratio = lowerratio
1296 self.heightstr = heightstr
1298 def modifyexpr(self, expr, texsettings, texrunner):
1299 return r"\setbox0\hbox{{%s}}\lower%.5f\ht0\hbox{{%s}}" % (self.heightstr, self.lowerratio, expr)
1302 class _vshiftmathaxis(vshift):
1303 "vertical down shift by the height of the math axis"
1305 def modifyexpr(self, expr, texsettings, texrunner):
1306 return r"\setbox0\hbox{$\vcenter{\vrule width0pt}$}\lower\ht0\hbox{{%s}}" % expr
1309 vshift.char = _vshiftchar
1310 vshift.bottomzero = vshift.char(0)
1311 vshift.middlezero = vshift.char(0.5)
1312 vshift.centerzero = vshift.middlezero
1313 vshift.topzero = vshift.char(1)
1314 vshift.mathaxis = _vshiftmathaxis()
1317 class _mathmode(_texsetting):
1318 "math mode"
1320 __implements__ = _Itexsetting
1322 id = 9000
1324 def modifyexpr(self, expr, texsettings, texrunner):
1325 return r"$\displaystyle{%s}$" % expr
1327 mathmode = _mathmode()
1330 defaultsizelist = ["normalsize", "large", "Large", "LARGE", "huge", "Huge", None, "tiny", "scriptsize", "footnotesize", "small"]
1332 class size(_texsetting):
1333 "font size"
1335 __implements__ = _Itexsetting
1337 id = 3000
1339 def __init__(self, expr, sizelist=defaultsizelist):
1340 if helper.isinteger(expr):
1341 if expr >= 0 and expr < sizelist.index(None):
1342 self.size = sizelist[expr]
1343 elif expr < 0 and expr + len(sizelist) > sizelist.index(None):
1344 self.size = sizelist[expr]
1345 else:
1346 raise IndexError("index out of sizelist range")
1347 else:
1348 self.size = expr
1350 def modifyexpr(self, expr, texsettings, texrunner):
1351 return r"\%s{%s}" % (self.size, expr)
1353 for s in defaultsizelist:
1354 if s is not None:
1355 size.__dict__[s] = size(s)
1358 ###############################################################################
1359 # texrunner
1360 ###############################################################################
1363 class _readpipe(threading.Thread):
1364 """threaded reader of TeX/LaTeX output
1365 - sets an event, when a specific string in the programs output is found
1366 - sets an event, when the terminal ends"""
1368 def __init__(self, pipe, expectqueue, gotevent, gotqueue, quitevent):
1369 """initialize the reader
1370 - pipe: file to be read from
1371 - expectqueue: keeps the next InputMarker to be wait for
1372 - gotevent: the "got InputMarker" event
1373 - gotqueue: a queue containing the lines recieved from TeX/LaTeX
1374 - quitevent: the "end of terminal" event"""
1375 threading.Thread.__init__(self)
1376 self.setDaemon(1) # don't care if the output might not be finished (nevertheless, it shouldn't happen)
1377 self.pipe = pipe
1378 self.expectqueue = expectqueue
1379 self.gotevent = gotevent
1380 self.gotqueue = gotqueue
1381 self.quitevent = quitevent
1382 self.expect = None
1383 self.start()
1385 def run(self):
1386 """thread routine"""
1387 read = self.pipe.readline() # read, what comes in
1388 try:
1389 self.expect = self.expectqueue.get_nowait() # read, what should be expected
1390 except Queue.Empty:
1391 pass
1392 while len(read):
1393 # universal EOL handling (convert everything into unix like EOLs)
1394 read.replace("\r", "")
1395 if not len(read) or read[-1] != "\n":
1396 read += "\n"
1397 self.gotqueue.put(read) # report, whats readed
1398 if self.expect is not None and read.find(self.expect) != -1:
1399 self.gotevent.set() # raise the got event, when the output was expected (XXX: within a single line)
1400 read = self.pipe.readline() # read again
1401 try:
1402 self.expect = self.expectqueue.get_nowait()
1403 except Queue.Empty:
1404 pass
1405 # EOF reached
1406 if self.expect is not None and self.expect.find("PyXInputMarker") != -1:
1407 raise RuntimeError("TeX/LaTeX finished unexpectedly")
1408 self.quitevent.set()
1412 class _textbox(box._rect, base.PSCmd):
1413 """basically a box.rect, but it contains a text created by the texrunner
1414 - texrunner._text and texrunner.text return such an object
1415 - _textbox instances can be inserted into a canvas
1416 - the output is contained in a page of the dvifile available thru the texrunner"""
1418 def __init__(self, x, y, left, right, height, depth, texrunner, page, *styles):
1419 self.texttrafo = trafo._translate(x, y)
1420 box._rect.__init__(self, x - left, y - depth,
1421 left + right, depth + height,
1422 abscenter = (left, depth))
1423 self.texrunner = texrunner
1424 self.page = page
1425 self.styles = styles
1427 def transform(self, *trafos):
1428 box._rect.transform(self, *trafos)
1429 for trafo in trafos:
1430 self.texttrafo = trafo * self.texttrafo
1432 def prolog(self):
1433 result = []
1434 for cmd in self.styles:
1435 result.extend(cmd.prolog())
1436 return result + self.texrunner.prolog()
1438 def write(self, file):
1439 canvas._gsave().write(file) # XXX: canvas?, constructor call needed?
1440 self.texttrafo.write(file)
1441 for style in self.styles:
1442 style.write(file)
1443 self.texrunner.write(file, self.page)
1444 canvas._grestore().write(file)
1448 class textbox(_textbox):
1450 def __init__(self, x, y, left, right, height, depth, texrunner, page):
1451 _textbox.__init__(unit.topt(x), unit.topt(y), unit.topt(left), unit.topt(right),
1452 unit.topt(height), unit.topt(depth), texrunner, page)
1455 def _cleantmp(texrunner):
1456 """get rid of temporary files
1457 - function to be registered by atexit
1458 - files contained in usefiles are kept"""
1459 if texrunner.texruns: # cleanup while TeX is still running?
1460 texrunner.texruns = 0
1461 texrunner.texdone = 1
1462 texrunner.expectqueue.put_nowait(None) # do not expect any output anymore
1463 texrunner.texinput.close() # close the input queue and
1464 texrunner.quitevent.wait(texrunner.waitfortex) # wait for finish of the output
1465 if not texrunner.quitevent.isSet(): return # didn't got a quit from TeX -> we can't do much more
1466 for usefile in texrunner.usefiles:
1467 extpos = usefile.rfind(".")
1468 try:
1469 os.rename(texrunner.texfilename + usefile[extpos:], usefile)
1470 except OSError:
1471 pass
1472 for file in glob.glob("%s.*" % texrunner.texfilename):
1473 try:
1474 os.unlink(file)
1475 except OSError:
1476 pass
1479 # texrunner state exceptions
1480 class TexRunsError(Exception): pass
1481 class TexDoneError(Exception): pass
1482 class TexNotInPreambleModeError(Exception): pass
1485 class texrunner:
1486 """TeX/LaTeX interface
1487 - runs TeX/LaTeX expressions instantly
1488 - checks TeX/LaTeX response"""
1490 def __init__(self, mode="tex",
1491 lfs="10pt",
1492 docclass="article",
1493 docopt=None,
1494 usefiles=None,
1495 waitfortex=5,
1496 texdebug=0,
1497 dvidebug=0,
1498 texmessagestart=texmessage.start,
1499 texmessagedocclass=texmessage.load,
1500 texmessagebegindoc=(texmessage.load, texmessage.noaux),
1501 texmessageend=texmessage.texend,
1502 texmessagedefaultpreamble=texmessage.load,
1503 texmessagedefaultrun=None):
1504 mode = mode.lower()
1505 if mode != "tex" and mode != "latex" and mode != "pdftex" and mode != "pdflatex":
1506 raise ValueError("mode \"TeX\", \"LaTeX\", \"pdfTeX\", or \"pdfLaTeX\" expected")
1507 self.mode = mode
1508 self.lfs = lfs
1509 self.docclass = docclass
1510 self.docopt = docopt
1511 self.usefiles = helper.ensurelist(usefiles)
1512 self.waitfortex = waitfortex
1513 self.texdebug = texdebug
1514 self.dvidebug = dvidebug
1515 texmessagestart = helper.ensuresequence(texmessagestart)
1516 helper.checkattr(texmessagestart, allowmulti=(texmessage,))
1517 self.texmessagestart = texmessagestart
1518 texmessagedocclass = helper.ensuresequence(texmessagedocclass)
1519 helper.checkattr(texmessagedocclass, allowmulti=(texmessage,))
1520 self.texmessagedocclass = texmessagedocclass
1521 texmessagebegindoc = helper.ensuresequence(texmessagebegindoc)
1522 helper.checkattr(texmessagebegindoc, allowmulti=(texmessage,))
1523 self.texmessagebegindoc = texmessagebegindoc
1524 texmessageend = helper.ensuresequence(texmessageend)
1525 helper.checkattr(texmessageend, allowmulti=(texmessage,))
1526 self.texmessageend = texmessageend
1527 texmessagedefaultpreamble = helper.ensuresequence(texmessagedefaultpreamble)
1528 helper.checkattr(texmessagedefaultpreamble, allowmulti=(texmessage,))
1529 self.texmessagedefaultpreamble = texmessagedefaultpreamble
1530 texmessagedefaultrun = helper.ensuresequence(texmessagedefaultrun)
1531 helper.checkattr(texmessagedefaultrun, allowmulti=(texmessage,))
1532 self.texmessagedefaultrun = texmessagedefaultrun
1534 self.texruns = 0
1535 self.texdone = 0
1536 self.preamblemode = 1
1537 self.executeid = 0
1538 self.page = 0
1539 savetempdir = tempfile.tempdir
1540 tempfile.tempdir = os.curdir
1541 self.texfilename = os.path.basename(tempfile.mktemp())
1542 tempfile.tempdir = savetempdir
1544 def execute(self, expr, *checks):
1545 """executes expr within TeX/LaTeX
1546 - if self.texruns is not yet set, TeX/LaTeX is initialized,
1547 self.texruns is set and self.preamblemode is set
1548 - the method must not be called, when self.texdone is already set
1549 - expr should be a string or None
1550 - when expr is None, TeX/LaTeX is stopped, self.texruns is unset and
1551 while self.texdone becomes set
1552 - when self.preamblemode is set, the expr is passed directly to TeX/LaTeX
1553 - when self.preamblemode is unset, the expr is passed to \ProcessPyXBox
1555 if not self.texruns:
1556 for usefile in self.usefiles:
1557 extpos = usefile.rfind(".")
1558 try:
1559 os.rename(usefile, self.texfilename + usefile[extpos:])
1560 except OSError:
1561 pass
1562 texfile = open("%s.tex" % self.texfilename, "w") # start with filename -> creates dvi file with that name
1563 texfile.write("\\relax\n")
1564 texfile.close()
1565 try:
1566 self.texinput, self.texoutput = os.popen4("%s %s" % (self.mode, self.texfilename), "t", 0)
1567 except ValueError:
1568 # XXX: workaround for MS Windows (bufsize = 0 makes trouble!?)
1569 self.texinput, self.texoutput = os.popen4("%s %s" % (self.mode, self.texfilename), "t")
1570 atexit.register(_cleantmp, self)
1571 self.expectqueue = Queue.Queue(1) # allow for a single entry only -> keeps the next InputMarker to be wait for
1572 self.gotevent = threading.Event() # keeps the got inputmarker event
1573 self.gotqueue = Queue.Queue(0) # allow arbitrary number of entries
1574 self.quitevent = threading.Event() # keeps for end of terminal event
1575 self.readoutput = _readpipe(self.texoutput, self.expectqueue, self.gotevent, self.gotqueue, self.quitevent)
1576 self.texruns = 1
1577 oldpreamblemode = self.preamblemode
1578 self.preamblemode = 1
1579 self.execute("\\scrollmode\n\\raiseerror%\n" # switch to and check scrollmode
1580 "\\def\\PyX{P\\kern-.3em\\lower.5ex\hbox{Y}\kern-.18em X}%\n" # just the PyX Logo
1581 "\\gdef\\PyXHAlign{0}%\n" # global PyXHAlign (0.0-1.0) for the horizontal alignment, default to 0
1582 "\\newbox\\PyXBox%\n" # PyXBox will contain the output
1583 "\\newbox\\PyXBoxHAligned%\n" # PyXBox will contain the horizontal aligned output
1584 "\\newdimen\\PyXDimenHAlignLT%\n" # PyXDimenHAlignLT/RT will contain the left/right extent
1585 "\\newdimen\\PyXDimenHAlignRT%\n" +
1586 _texsettingpreamble + # insert preambles for texsetting macros
1587 "\\def\\ProcessPyXBox#1#2{%\n" # the ProcessPyXBox definition (#1 is expr, #2 is page number)
1588 "\\setbox\\PyXBox=\\hbox{{#1}}%\n" # push expression into PyXBox
1589 "\\PyXDimenHAlignLT=\\PyXHAlign\\wd\\PyXBox%\n" # calculate the left/right extent
1590 "\\PyXDimenHAlignRT=\\wd\\PyXBox%\n"
1591 "\\advance\\PyXDimenHAlignRT by -\\PyXDimenHAlignLT%\n"
1592 "\\gdef\\PyXHAlign{0}%\n" # reset the PyXHAlign to the default 0
1593 "\\immediate\\write16{PyXBox:page=#2," # write page and extents of this box to stdout
1594 "lt=\\the\\PyXDimenHAlignLT,"
1595 "rt=\\the\\PyXDimenHAlignRT,"
1596 "ht=\\the\\ht\\PyXBox,"
1597 "dp=\\the\\dp\\PyXBox:}%\n"
1598 "\\setbox\\PyXBoxHAligned=\\hbox{\\kern-\\PyXDimenHAlignLT\\box\\PyXBox}%\n" # align horizontally
1599 "\\ht\\PyXBoxHAligned0pt%\n" # baseline alignment (hight to zero)
1600 "{\\count0=80\\count1=121\\count2=88\\count3=#2\\shipout\\box\\PyXBoxHAligned}}%\n" # shipout PyXBox to Page 80.121.88.<page number>
1601 "\\def\\PyXInput#1{\\immediate\\write16{PyXInputMarker:executeid=#1:}}", # write PyXInputMarker to stdout
1602 *self.texmessagestart)
1603 os.remove("%s.tex" % self.texfilename)
1604 if self.mode == "pdftex" or self.mode == "pdflatex":
1605 self.execute("\\pdfoutput=1%\n"
1606 "\\def\\marker#1{%\n"
1607 #"\\pdfsavepos%\n"
1608 "\\write16{PyXMarker:name=#1,"
1609 "xpos=\\the\\pdflastxpos,"
1610 "ypos=\\the\\pdflastypos:}%\n"
1611 "}%\n")
1612 if self.mode == "tex":
1613 try:
1614 LocalLfsName = str(self.lfs) + ".lfs"
1615 lfsdef = open(LocalLfsName, "r").read()
1616 except IOError:
1617 try:
1618 try:
1619 SysLfsName = os.path.join(sys.prefix, "share", "pyx", str(self.lfs) + ".lfs")
1620 lfsdef = open(SysLfsName, "r").read()
1621 except IOError:
1622 SysLfsName = os.path.join(os.path.dirname(__file__), "lfs", str(self.lfs) + ".lfs")
1623 lfsdef = open(SysLfsName, "r").read()
1624 except IOError:
1625 allfiles = []
1626 try:
1627 allfiles += os.listdir(".")
1628 except OSError:
1629 pass
1630 try:
1631 allfiles += os.listdir(os.path.join(sys.prefix, "share", "pyx"))
1632 except OSError:
1633 pass
1634 try:
1635 allfiles += os.listdir(os.path.join(os.path.dirname(__file__), "lfs"))
1636 except OSError:
1637 pass
1638 files = map(lambda x: x[:-4], filter(lambda x: x[-4:] == ".lfs", allfiles))
1639 raise IOError("file '%s.lfs' not found. Available latex font sizes:\n%s" % (self.lfs, files))
1640 self.execute(lfsdef)
1641 self.execute("\\normalsize%\n")
1642 self.execute("\\newdimen\\linewidth%\n")
1643 elif self.mode == "latex" or self.mode == "pdflatex":
1644 if self.docopt is not None:
1645 self.execute("\\documentclass[%s]{%s}" % (self.docopt, self.docclass), *self.texmessagedocclass)
1646 else:
1647 self.execute("\\documentclass{%s}" % self.docclass, *self.texmessagedocclass)
1648 self.preamblemode = oldpreamblemode
1649 self.executeid += 1
1650 if expr is not None: # TeX/LaTeX should process expr
1651 self.expectqueue.put_nowait("PyXInputMarker:executeid=%i:" % self.executeid)
1652 if self.preamblemode:
1653 self.expr = ("%s%%\n" % expr +
1654 "\\PyXInput{%i}%%\n" % self.executeid)
1655 else:
1656 self.page += 1
1657 self.expr = ("\\ProcessPyXBox{%s}{%i}%%\n" % (expr, self.page) +
1658 "\\PyXInput{%i}%%\n" % self.executeid)
1659 else: # TeX/LaTeX should be finished
1660 self.expectqueue.put_nowait("Transcript written on %s.log" % self.texfilename)
1661 if self.mode == "latex" or self.mode == "pdflatex":
1662 self.expr = "\\end{document}\n"
1663 else:
1664 self.expr = "\\end\n"
1665 if self.texdebug:
1666 print "pass the following expression to TeX/LaTeX:\n %s" % self.expr.replace("\n", "\n ").rstrip()
1667 self.texinput.write(self.expr)
1668 self.gotevent.wait(self.waitfortex) # wait for the expected output
1669 gotevent = self.gotevent.isSet()
1670 self.gotevent.clear()
1671 if expr is None and gotevent: # TeX/LaTeX should have finished
1672 self.texruns = 0
1673 self.texdone = 1
1674 self.texinput.close() # close the input queue and
1675 self.quitevent.wait(self.waitfortex) # wait for finish of the output
1676 gotevent = self.quitevent.isSet()
1677 try:
1678 self.texmessage = ""
1679 while 1:
1680 self.texmessage += self.gotqueue.get_nowait()
1681 except Queue.Empty:
1682 pass
1683 self.texmessageparsed = self.texmessage
1684 if gotevent:
1685 if expr is not None:
1686 texmessage.inputmarker.check(self)
1687 if not self.preamblemode:
1688 texmessage.pyxbox.check(self)
1689 texmessage.pyxpageout.check(self)
1690 for check in checks:
1691 try:
1692 check.check(self)
1693 except TexResultWarning:
1694 traceback.print_exc()
1695 texmessage.emptylines.check(self)
1696 if len(self.texmessageparsed):
1697 raise TexResultError("unhandled TeX response (might be an error)", self)
1698 else:
1699 raise TexResultError("TeX didn't respond as expected within the timeout period (%i seconds)." % self.waitfortex, self)
1701 def getdvi(self):
1702 "finish TeX/LaTeX and initialize dvifile"
1703 self.execute(None, *self.texmessageend)
1704 self.dvifile = DVIFile("%s.dvi" % self.texfilename, debug=self.dvidebug)
1706 def prolog(self):
1707 "return the dvifile prolog (assume, that everything in the dvifile will be written to postscript)"
1708 if not self.texdone:
1709 self.getdvi()
1710 return self.dvifile.prolog()
1712 def write(self, file, page):
1713 "write a page from the dvifile"
1714 if not self.texdone:
1715 self.getdvi()
1716 return self.dvifile.write(file, page)
1718 def settex(self, mode=None, lfs=None, docclass=None, docopt=None, usefiles=None, waitfortex=None,
1719 texmessagestart=None,
1720 texmessagedocclass=None,
1721 texmessagebegindoc=None,
1722 texmessageend=None,
1723 texmessagedefaultpreamble=None,
1724 texmessagedefaultrun=None):
1725 """provide a set command for TeX/LaTeX settings
1726 - TeX/LaTeX must not yet been started
1727 - especially needed for the defaultrunner, where no access to
1728 the constructor is available
1729 - do not call this method directly; better use the set method below"""
1730 if self.texruns:
1731 raise TexRunsError
1732 if mode is not None:
1733 mode = mode.lower()
1734 if mode != "tex" and mode != "latex" and mode != "pdftex" and mode != "pdflatex":
1735 raise ValueError("mode \"TeX\", \"LaTeX\", \"pdfTeX\", or \"pdfLaTeX\" expected")
1736 self.mode = mode
1737 if lfs is not None:
1738 self.lfs = lfs
1739 if docclass is not None:
1740 self.docclass = docclass
1741 if docopt is not None:
1742 self.docopt = docopt
1743 if self.usefiles is not None:
1744 self.usefiles = helper.ensurelist(usefiles)
1745 if waitfortex is not None:
1746 self.waitfortex = waitfortex
1747 if texmessagestart is not None:
1748 texmessagestart = helper.ensuresequence(texmessagestart)
1749 helper.checkattr(texmessagestart, allowmulti=(texmessage,))
1750 self.texmessagestart = texmessagestart
1751 if texmessagedocclass is not None:
1752 texmessagedocclass = helper.ensuresequence(texmessagedocclass)
1753 helper.checkattr(texmessagedocclass, allowmulti=(texmessage,))
1754 self.texmessagedocclass = texmessagedocclass
1755 if texmessagebegindoc is not None:
1756 texmessagebegindoc = helper.ensuresequence(texmessagebegindoc)
1757 helper.checkattr(texmessagebegindoc, allowmulti=(texmessage,))
1758 self.texmessagebegindoc = texmessagebegindoc
1759 if texmessageend is not None:
1760 texmessageend = helper.ensuresequence(texmessageend)
1761 helper.checkattr(texmessageend, allowmulti=(texmessage,))
1762 self.texmessageend = texmessageend
1763 if texmessagedefaultpreamble is not None:
1764 texmessagedefaultpreamble = helper.ensuresequence(texmessagedefaultpreamble)
1765 helper.checkattr(texmessagedefaultpreamble, allowmulti=(texmessage,))
1766 self.texmessagedefaultpreamble = texmessagedefaultpreamble
1767 if texmessagedefaultrun is not None:
1768 texmessagedefaultrun = helper.ensuresequence(texmessagedefaultrun)
1769 helper.checkattr(texmessagedefaultrun, allowmulti=(texmessage,))
1770 print texmessagedefaultrun
1771 self.texmessagedefaultrun = texmessagedefaultrun
1773 def set(self, texdebug=None, dvidebug=None, **args):
1774 """as above, but contains all settings
1775 - the debug level might be changed during TeX/LaTeX execution
1776 - dvidebug gets used only once, namely when TeX/LaTeX is being finished"""
1777 if self.texdone:
1778 raise TexDoneError
1779 if texdebug is not None:
1780 self.texdebug = texdebug
1781 if dvidebug is not None:
1782 self.dvidebug = dvidebug
1783 if len(args.keys()):
1784 self.settex(**args)
1786 def bracketcheck(self, expr):
1787 """a helper method for consistant usage of "{" and "}"
1788 - prevent to pass unbalanced expressions to TeX
1789 - raises an appropriate ValueError"""
1790 depth = 0
1791 esc = 0
1792 for c in expr:
1793 if c == "{" and not esc:
1794 depth = depth + 1
1795 if c == "}" and not esc:
1796 depth = depth - 1
1797 if depth < 0:
1798 raise ValueError("unmatched '}'")
1799 if c == "\\":
1800 esc = (esc + 1) % 2
1801 else:
1802 esc = 0
1803 if depth > 0:
1804 raise ValueError("unmatched '{'")
1806 def preamble(self, expr, *args):
1807 r"""put something into the TeX/LaTeX preamble
1808 - in LaTeX, this is done before the \begin{document}
1809 (you might use \AtBeginDocument, when you're in need for)
1810 - it is not allowed to call preamble after calling the
1811 text method for the first time (for LaTeX this is needed
1812 due to \begin{document}; in TeX it is forced for compatibility
1813 (you should be able to switch from TeX to LaTeX, if you want,
1814 without breaking something
1815 - preamble expressions must not create any dvi output
1816 - args might contain texmessage instances
1817 - a bracketcheck is performed on the expression"""
1818 if self.texdone:
1819 raise TexDoneError
1820 if not self.preamblemode:
1821 raise TexNotInPreambleModeError
1822 self.bracketcheck(expr)
1823 helper.checkattr(args, allowmulti=(texmessage,))
1824 self.execute(expr, *helper.getattrs(args, texmessage, default=self.texmessagedefaultpreamble))
1826 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:")
1828 def _text(self, x, y, expr, *args):
1829 """create text by passing expr to TeX/LaTeX
1830 - returns a textbox containing the result from running expr thru TeX/LaTeX
1831 - the box center is set to x, y
1832 - *args may contain style parameters, namely:
1833 - an halign instance
1834 - _texsetting instances
1835 - texmessage instances
1836 - trafo._trafo instances
1837 - base.PathStyle instances
1838 - a bracketcheck is performed on the expression"""
1839 if expr is None:
1840 raise ValueError("None expression is invalid")
1841 if self.texdone:
1842 raise TexDoneError
1843 if self.preamblemode:
1844 if self.mode == "latex" or self.mode == "pdflatex":
1845 self.execute("\\begin{document}", *self.texmessagebegindoc)
1846 self.preamblemode = 0
1847 helper.checkattr(args, allowmulti=(_texsetting, texmessage, trafo._trafo, base.PathStyle))
1848 #XXX: should we distiguish between StrokeStyle and FillStyle?
1849 texsettings = helper.getattrs(args, _texsetting, default=[])
1850 exclusive = []
1851 for texsetting in texsettings:
1852 if texsetting.exclusive:
1853 if texsetting.id not in exclusive:
1854 exclusive.append(texsetting.id)
1855 else:
1856 raise RuntimeError("multiple occurance of exclusive texsetting with id=%i" % texsetting.id)
1857 texsettings.sort()
1858 for texsetting in texsettings:
1859 expr = texsetting.modifyexpr(expr, texsettings, self)
1860 self.bracketcheck(expr)
1861 self.execute(expr, *helper.getattrs(args, texmessage, default=self.texmessagedefaultrun))
1862 match = self.PyXBoxPattern.search(self.texmessage)
1863 if not match or int(match.group("page")) != self.page:
1864 raise TexResultError("box extents not found", self)
1865 left, right, height, depth = map(lambda x: float(x) * 72.0 / 72.27, match.group("lt", "rt", "ht", "dp"))
1866 box = _textbox(x, y, left, right, height, depth, self, self.page,
1867 *helper.getattrs(args, base.PathStyle, default=[]))
1868 for t in helper.getattrs(args, trafo._trafo, default=()):
1869 box.reltransform(t)
1870 return box
1872 def text(self, x, y, expr, *args):
1873 return self._text(unit.topt(x), unit.topt(y), expr, *args)
1876 # the module provides an default texrunner and methods for direct access
1877 defaulttexrunner = texrunner()
1878 set = defaulttexrunner.set
1879 preamble = defaulttexrunner.preamble
1880 text = defaulttexrunner.text
1881 _text = defaulttexrunner._text