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
24 # this code will be part of PyX 0.3
26 import os
, threading
, Queue
, traceback
, re
, struct
, tempfile
27 import helper
, attrlist
, bbox
, unit
, box
, base
, trafo
, canvas
29 ###############################################################################
30 # joergl would mainly work here ...
32 ###############################################################################
33 # this is the old stuff
34 _DVI_CHARMIN
= 0 # typeset a character and move right (range min)
35 _DVI_CHARMAX
= 127 # typeset a character and move right (range max)
36 _DVI_SET1234
= 128 # typeset a character and move right
37 _DVI_SETRULE
= 132 # typeset a rule and move right
38 _DVI_PUT1234
= 133 # typeset a character
39 _DVI_PUTRULE
= 137 # typeset a rule
40 _DVI_NOP
= 138 # no operation
41 _DVI_BOP
= 139 # beginning of page
42 _DVI_EOP
= 140 # ending of page
43 _DVI_PUSH
= 141 # save the current positions (h, v, w, x, y, z)
44 _DVI_POP
= 142 # restore positions (h, v, w, x, y, z)
45 _DVI_RIGHT1234
= 143 # move right
46 _DVI_W0
= 147 # move right by w
47 _DVI_W1234
= 148 # move right and set w
48 _DVI_X0
= 152 # move right by x
49 _DVI_X1234
= 153 # move right and set x
50 _DVI_DOWN1234
= 157 # move down
51 _DVI_Y0
= 161 # move down by y
52 _DVI_Y1234
= 162 # move down and set y
53 _DVI_Z0
= 166 # move down by z
54 _DVI_Z1234
= 167 # move down and set z
55 _DVI_FNTNUMMIN
= 171 # set current font (range min)
56 _DVI_FNTNUMMAX
= 234 # set current font (range max)
57 _DVI_FNT1234
= 235 # set current font
58 _DVI_SPECIAL1234
= 239 # special (dvi extention)
59 _DVI_FNTDEF1234
= 243 # define the meaning of a font number
60 _DVI_PRE
= 247 # preamble
61 _DVI_POST
= 248 # postamble beginning
62 _DVI_POSTPOST
= 249 # postamble ending
64 _DVI_VERSION
= 2 # dvi version
66 # position variable indices
83 def __init__(self
, word
):
89 self
.precomma
= abs(word
) >> 20
90 self
.postcomma
= abs(word
) & 0xFFFFF
93 return self
.sign
* (self
.precomma
+ 1.0*self
.postcomma
/0xFFFFF)
95 def __mul__(self
, other
):
99 result
.sign
= self
.sign
*other
.sign
100 c
= self
.postcomma
*other
.precomma
+ self
.precomma
*other
.postcomma
101 result
.precomma
= self
.precomma
*other
.precomma
+ (c
>> 20)
102 result
.postcomma
= c
& 0xFFFFF + ((self
.postcomma
*other
.postcomma
) >> 40)
106 class char_info_word
:
107 def __init__(self
, word
):
108 self
.width_index
= (word
& 0xFF000000) >> 24
109 self
.height_index
= (word
& 0x00F00000) >> 20
110 self
.depth_index
= (word
& 0x000F0000) >> 16
111 self
.italic_index
= (word
& 0x0000FC00) >> 10
112 self
.tag
= (word
& 0x00000300) >> 8
113 self
.remainder
= (word
& 0x000000FF)
115 if self
.width_index
==0:
116 raise TFMError
, "width_index should not be zero"
120 def __init__(self
, filename
, mode
="r"):
121 self
.file = open(filename
, mode
)
124 return self
.file.tell()
126 def read(self
, bytes
):
127 return self
.file.read(bytes
)
129 def readint(self
, bytes
= 4, signed
= 0):
133 value
= ord(self
.file.read(1))
134 if first
and signed
and value
> 127:
137 result
= 256 * result
+ value
142 return struct
.unpack(">l", self
.file.read(4))[0]
144 def readuint32(self
):
145 return struct
.unpack(">L", self
.file.read(4))[0]
149 return struct
.unpack(">l", "\0"+self
.file.read(3))[0]
151 def readuint24(self
):
153 return struct
.unpack(">L", "\0"+self
.file.read(3))[0]
156 return struct
.unpack(">h", self
.file.read(2))[0]
158 def readuint16(self
):
159 return struct
.unpack(">H", self
.file.read(2))[0]
162 return struct
.unpack("b", self
.file.read(1))[0]
165 return struct
.unpack("B", self
.file.read(1))[0]
167 def readstring(self
, bytes
):
169 assert l
<bytes
-1, "inconsistency in file: string too long"
170 return self
.file.read(bytes
-1)[:l
]
172 class DVIError(Exception): pass
174 class TFMError(Exception): pass
177 def __init__(self
, name
):
178 self
.file = binfile(name
, "rb")
184 self
.lf
= self
.file.readint16()
185 self
.lh
= self
.file.readint16()
186 self
.bc
= self
.file.readint16()
187 self
.ec
= self
.file.readint16()
188 self
.nw
= self
.file.readint16()
189 self
.nh
= self
.file.readint16()
190 self
.nd
= self
.file.readint16()
191 self
.ni
= self
.file.readint16()
192 self
.nl
= self
.file.readint16()
193 self
.nk
= self
.file.readint16()
194 self
.ne
= self
.file.readint16()
195 self
.np
= self
.file.readint16()
197 if not (self
.bc
-1<=self
.ec
<=255 and
199 self
.lf
==6+self
.lh
+(self
.ec
-self
.bc
+1)+self
.nw
+self
.nh
+self
.nd
200 +self
.ni
+self
.nl
+self
.nk
+self
.ne
+self
.np
):
201 raise TFMError
, "error in TFM pre-header"
207 self
.checksum
= self
.file.readint32()
208 self
.designsizeraw
= self
.file.readint32()
209 assert self
.designsizeraw
>0, "invald design size"
210 self
.designsize
= fix_word(self
.designsizeraw
)
212 self
.charcoding
= self
.file.readstring(40)
214 self
.charcoding
= None
217 self
.fontfamily
= self
.file.readstring(20)
219 self
.fontfamily
= None
222 self
.sevenbitsave
= self
.file.readuchar()
223 # ignore the following two bytes
224 self
.file.readint16()
225 facechar
= self
.file.readuchar()
226 # decode ugly face specification into the Knuth suggested string
238 self
.face
= "L" + self
.face
241 self
.face
= "B" + self
.face
244 self
.face
= "M" + self
.face
247 self
.face
= self
.face
[0] + "I" + self
.face
[1]
249 self
.face
= self
.face
[0] + "R" + self
.face
[1]
254 self
.sevenbitsave
= self
.face
= None
257 # just ignore the rest
258 self
.file.read((self
.lh
-18)*4)
264 self
.char_info
= [None for charcode
in range(self
.ec
+1)]
266 for charcode
in range(self
.bc
, self
.ec
+1):
267 self
.char_info
[charcode
] = char_info_word(self
.file.readint32())
273 self
.width
= [None for width_index
in range(self
.nw
)]
274 for width_index
in range(self
.nw
):
275 # self.width[width_index] = fix_word(self.file.readint32())
276 self
.width
[width_index
] = self
.file.readint32()
282 self
.height
= [None for height_index
in range(self
.nh
)]
283 for height_index
in range(self
.nh
):
284 # self.height[height_index] = fix_word(self.file.readint32())
285 self
.height
[height_index
] = self
.file.readint32()
291 self
.depth
= [None for depth_index
in range(self
.nd
)]
292 for depth_index
in range(self
.nd
):
293 # self.depth[depth_index] = fix_word(self.file.readint32())
294 self
.depth
[depth_index
] = self
.file.readint32()
300 self
.italic
= [None for italic_index
in range(self
.ni
)]
301 for italic_index
in range(self
.ni
):
302 # self.italic[italic_index] = fix_word(self.file.readint32())
303 self
.italic
[italic_index
] = self
.file.readint32()
309 # XXX decode to lig_kern_command
311 self
.lig_kern
= [None for lig_kern_index
in range(self
.nl
)]
312 for lig_kern_index
in range(self
.nl
):
313 self
.lig_kern
[lig_kern_index
] = self
.file.readint32()
319 self
.kern
= [None for kern_index
in range(self
.nk
)]
320 for kern_index
in range(self
.nk
):
321 # self.kern[kern_index] = fix_word(self.file.readint32())
322 self
.kern
[kern_index
] = self
.file.readint32()
328 # XXX decode to extensible_recipe
330 self
.exten
= [None for exten_index
in range(self
.ne
)]
331 for exten_index
in range(self
.ne
):
332 self
.exten
[exten_index
] = self
.file.readint32()
340 self
.param
= [None for param_index
in range(self
.np
)]
341 for param_index
in range(self
.np
):
342 self
.param
[param_index
] = self
.file.readint32()
346 def __init__(self
, name
, c
, q
, d
, tfmconv
):
348 path
= os
.popen("kpsewhich %s.tfm" % self
.name
, "r").readline()[:-1]
349 self
.tfmfile
= TFMFile(path
)
351 if self
.tfmfile
.checksum
!=c
:
352 raise DVIError("check sums do not agree: %d vs. %d" %
355 self
.tfmdesignsize
= round(tfmconv
*self
.tfmfile
.designsizeraw
)
357 if abs(self
.tfmdesignsize
- d
)>2:
358 raise DVIError("design sizes do not agree: %d vs. %d" %
359 (self
.tfmdesignsize
, d
))
362 if q
<0 or q
>134217728:
363 raise DVIError("font '%s' not loaded: bad scale" % fontname
)
365 if d
<0 or d
>134217728:
366 raise DVIError("font '%s' not loaded: bad design size" % fontname
)
370 self
.q
= self
.qorig
= q
371 while self
.q
>=8388608:
375 self
.beta
= 256/self
.alpha
;
376 self
.alpha
= self
.alpha
*self
.q
;
379 return "Font(%s, %d)" % (self
.name
, self
.tfmdesignsize
)
383 def convert(self
, width
):
385 b1
= (width
>> 16) & 0xff
386 b2
= (width
>> 8 ) & 0xff
388 # print width*self.qorig*16/ 16777216, (((((b3*self.q)/256)+(b2*self.q))/256)+(b1*self.q))/self.beta
391 return (((((b3
*self
.q
)/256)+(b2
*self
.q
))/256)+(b1
*self
.q
))/self
.beta
393 return (((((b3
*self
.q
)/256)+(b2
*self
.q
))/256)+(b1
*self
.q
))/self
.beta
-self
.alpha
395 raise TFMError("error in font size")
397 def __getattr__(self
, attr
):
398 return self
.tfmfile
.__dict
__[attr
]
400 def getwidth(self
, charcode
):
401 return self
.convert(self
.tfmfile
.width
[self
.char_info
[charcode
].width_index
])
403 def getheight(self
, charcode
):
404 return self
.convert(self
.tfmfile
.height
[self
.char_info
[charcode
].height_index
])
406 def getdepth(self
, charcode
):
407 return self
.convert(self
.tfmfile
.depth
[self
.char_info
[charcode
].depth_index
])
409 def getitalic(self
, charcode
):
410 return self
.convert(self
.tfmfile
.italic
[self
.char_info
[charcode
].italic_index
])
418 print "[%s]" % self
.actoutstring
419 self
.actpage
.append(("c",
420 self
.actoutstart
[0], self
.actoutstart
[1],
422 self
.actoutstart
= None
424 def putchar(self
, char
, inch
=1):
425 x
= self
.pos
[_POS_H
] * self
.conv
/ 100000
426 y
= -self
.pos
[_POS_V
] * self
.conv
/ 100000
427 dx
= inch
and self
.fonts
[self
.activefont
].getwidth(char
) or 0
430 print ("%d: %schar%d h:=%d+%d=%d, hh:=%d" %
432 inch
and "set" or "put",
434 self
.pos
[_POS_H
], dx
, self
.pos
[_POS_H
]+dx
,
437 self
.pos
[_POS_H
] += dx
439 ascii
= (char
> 32 and char
< 128) and "%s" % chr(char
) or "\\%03o" % char
441 if self
.actoutstart
is None:
442 self
.actoutstart
= unit
.t_cm(x
), unit
.t_cm(y
)
443 self
.actoutstring
= ""
444 self
.actoutstring
= self
.actoutstring
+ ascii
450 def putrule(self
, height
, width
, inch
=1):
452 if height
> 0 and width
> 0:
453 x1
= self
.pos
[_POS_H
] * self
.conv
* 1e-5
454 y1
= -self
.pos
[_POS_V
] * self
.conv
* 1e-5
455 w
= width
* self
.conv
* 1e-5
456 h
= height
* self
.conv
* 1e-5
458 print "%d: putrule height %d, width %d" % (self
.filepos
, height
, width
)
459 self
.actpage
.append(("r",
460 unit
.t_cm(x1
), unit
.t_cm(y1
),
461 unit
.t_cm(w
), unit
.t_cm(h
)))
463 pass # TODO: increment h
465 def usefont(self
, fontnum
):
467 self
.activefont
= fontnum
468 self
.actpage
.append(("f", self
.fonts
[fontnum
]))
470 print ("%d: fntnum%i current font is %s" %
472 self
.activefont
, self
.fonts
[fontnum
].name
))
474 def definefont(self
, num
, c
, q
, d
, fontname
):
477 # Note that q is actually s in large parts of the documentation.
480 self
.fonts
[num
] = Font(fontname
, c
, q
, d
, self
.tfmconv
)
482 scale
= round((1000.0*self
.conv
*q
)/(self
.trueconv
*d
))
486 print "%d: fntdefx %i: %s" % (self
.filepos
, num
, fontname
)
488 # scalestring = scale!=1000 and " scaled %d" % scale or ""
489 # print ("Font %i: %s%s---loaded at size %d DVI units" %
490 # (num, fontname, scalestring, q))
492 # print " (this font is magnified %d%%)" % round(scale/10)
494 def __init__(self
, filename
, debug
=0):
496 self
.filename
= filename
498 file = binfile(self
.filename
, "rb")
502 # XXX max number of fonts
503 self
.fonts
= [None for i
in range(64)]
504 self
.activefont
= None
506 # here goes the result, for each page one list.
509 # pointer to currently active page
512 # currently active output: position and content
513 self
.actoutstart
= None
514 self
.actoutstring
= ""
516 while state
!= _READ_DONE
:
517 self
.filepos
= file.tell()
518 cmd
= file.readuchar()
519 if cmd
== _DVI_NOP
: pass
521 elif state
== _READ_PRE
:
523 if file.readuchar() != _DVI_VERSION
: raise DVIError
524 num
= file.readuint32()
525 den
= file.readuint32()
526 mag
= file.readuint32()
528 # self.trueconv = conv in DVIType docu
529 # if resolution = 254000.0
531 self
.tfmconv
= (25400000.0/num
)*(den
/473628672)/16.0;
532 self
.trueconv
= 1.0*num
/den
533 self
.conv
= self
.trueconv
*(mag
/1000.0)
535 comment
= file.read(file.readuchar())
538 elif state
== _READ_NOPAGE
:
542 print "%d: beginning of page" % self
.filepos
,
543 print file.readuint32()
544 for i
in range(9): file.readuint32(),
546 for i
in range(10): file.readuint32(),
549 self
.pos
= [0, 0, 0, 0, 0, 0]
550 self
.pages
.append([])
551 self
.actpage
= self
.pages
[-1]
553 elif cmd
== _DVI_POST
:
554 state
= _READ_DONE
# we skip the rest
556 elif state
== _READ_PAGE
:
557 if cmd
>= _DVI_CHARMIN
and cmd
<= _DVI_CHARMAX
:
559 elif cmd
>= _DVI_SET1234
and cmd
< _DVI_SET1234
+ 4:
560 self
.putchar(file.readint(cmd
- _DVI_SET1234
+ 1))
561 elif cmd
== _DVI_SETRULE
:
562 self
.putrule(file.readint32(), file.readint32())
563 elif cmd
>= _DVI_PUT1234
and cmd
< _DVI_PUT1234
+ 4:
564 self
.putchar(file.readint(cmd
- _DVI_PUT1234
+ 1), inch
=0)
565 elif cmd
== _DVI_PUTRULE
:
566 self
.putrule(file.readint32(), file.readint32(), 0)
567 elif cmd
== _DVI_EOP
:
571 print "%d: eop" % self
.filepos
573 elif cmd
== _DVI_PUSH
:
574 stack
.append(tuple(self
.pos
))
576 print "%d: push" % self
.filepos
577 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
578 (( len(stack
)-1,)+tuple(self
.pos
)))
579 elif cmd
== _DVI_POP
:
581 self
.pos
= list(stack
[-1])
584 print "%d: pop" % self
.filepos
585 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
586 (( len(stack
),)+tuple(self
.pos
)))
588 elif cmd
>= _DVI_RIGHT1234
and cmd
< _DVI_RIGHT1234
+ 4:
590 dh
= file.readint(cmd
- _DVI_RIGHT1234
+ 1, 1)
592 print ("%d: right%d %d h:=%d%+d=%d, hh:=" %
594 cmd
- _DVI_RIGHT1234
+ 1,
598 self
.pos
[_POS_H
]+dh
))
599 self
.pos
[_POS_H
] += dh
603 print ("%d: w0 %d h:=%d%+d=%d, hh:=" %
608 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
609 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
610 elif cmd
>= _DVI_W1234
and cmd
< _DVI_W1234
+ 4:
612 self
.pos
[_POS_W
] = file.readint(cmd
- _DVI_W1234
+ 1, 1)
614 print ("%d: w%d %d h:=%d%+d=%d, hh:=" %
616 cmd
- _DVI_W1234
+ 1,
620 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
621 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
624 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
625 elif cmd
>= _DVI_X1234
and cmd
< _DVI_X1234
+ 4:
627 self
.pos
[_POS_X
] = file.readint(cmd
- _DVI_X1234
+ 1, 1)
628 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
629 elif cmd
>= _DVI_DOWN1234
and cmd
< _DVI_DOWN1234
+ 4:
631 dv
= file.readint(cmd
- _DVI_DOWN1234
+ 1, 1)
633 print ("%d: down%d %d v:=%d%+d=%d, vv:=" %
635 cmd
- _DVI_DOWN1234
+ 1,
639 self
.pos
[_POS_V
]+dv
))
640 self
.pos
[_POS_V
] += dv
643 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
644 elif cmd
>= _DVI_Y1234
and cmd
< _DVI_Y1234
+ 4:
646 self
.pos
[_POS_Y
] = file.readint(cmd
- _DVI_Y1234
+ 1, 1)
647 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
650 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
651 elif cmd
>= _DVI_Z1234
and cmd
< _DVI_Z1234
+ 4:
653 self
.pos
[_POS_Z
] = file.readint(cmd
- _DVI_Z1234
+ 1, 1)
654 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
655 elif cmd
>= _DVI_FNTNUMMIN
and cmd
<= _DVI_FNTNUMMAX
:
656 self
.usefont(cmd
- _DVI_FNTNUMMIN
)
657 elif cmd
>= _DVI_FNT1234
and cmd
< _DVI_FNT1234
+ 4:
658 self
.usefont(file.readint(cmd
- _DVI_FNT1234
+ 1, 1))
659 elif cmd
>= _DVI_SPECIAL1234
and cmd
< _DVI_SPECIAL1234
+ 4:
660 print "special %s" % file.read(file.readint(cmd
- _DVI_SPECIAL1234
+ 1))
661 raise RuntimeError("specials are not yet handled, abort")
662 elif cmd
>= _DVI_FNTDEF1234
and cmd
< _DVI_FNTDEF1234
+ 4:
663 if cmd
==_DVI_FNTDEF1234
:
665 elif cmd
==_DVI_FNTDEF1234
+1:
666 num
=file.readuint16()
667 elif cmd
==_DVI_FNTDEF1234
+2:
668 num
=file.readuint24()
669 elif cmd
==_DVI_FNTDEF1234
+3:
670 # Cool, here we have according to docu a signed int. Why?
677 file.read(file.readuchar()+file.readuchar()))
680 else: raise DVIError
# unexpected reader state
683 def writeheader(self
, file):
684 """write PostScript font header"""
685 for font
in self
.fonts
:
687 file.write("%%%%BeginFont: %s\n" % font
.name
.upper())
689 tmpfilename
= tempfile
.mktemp(suffix
=".pfa")
690 pfbfilename
= os
.popen("kpsewhich %s.pfb" % font
.name
).readlines()[-1][:-1]
691 os
.system("pfb2pfa %s %s" % (pfbfilename
, tmpfilename
))
693 pfa
= open(tmpfilename
, "r")
694 file.write(pfa
.read())
696 os
.unlink(tmpfilename
)
698 file.write("%%EndFont\n")
700 FontSizePattern
= re
.compile(r
"([0-9]+)$")
702 def write(self
, file, page
):
703 """write PostScript code for page into file"""
705 print "dvifile(\"%s\").write() for page %s called" % (self
.filename
, page
)
706 for el
in self
.pages
[page
-1]:
707 command
, arg
= el
[0], el
[1:]
710 file.write("%f %f moveto (%s) show\n" % (unit
.topt(x
), unit
.topt(y
), c
))
713 file.write("%f %f moveto %f 0 rlineto 0 %f rlineto %f 0 rlineto closepath fill\n" %
714 (unit
.topt(x1
), unit
.topt(y1
),
719 fontname
= arg
[0].name
720 fontscale
= arg
[0].scale
721 match
= self
.FontSizePattern
.search(fontname
)
723 file.write("/%s %f selectfont\n" % (fontname
.upper(),
724 int(match
.group(1))*fontscale
))
726 raise RuntimeError("cannot determine font size from name '%s'" % fontname
)
729 #if __name__=="__main__":
730 # cmr10 = Font("cmr10")
731 # print cmr10.charcoding
732 # print cmr10.fontfamily
734 # for charcode in range(cmr10.bc, cmr10.ec+1):
735 # print "%d\th=%f\tw=%f\td=%f\ti=%f" % (
737 # cmr10.getwidth(charcode),
738 # cmr10.getheight(charcode),
739 # cmr10.getdepth(charcode),
740 # cmr10.getitalic(charcode))
742 # dvifile = DVIFile("test.dvi")
743 # print [font for font in dvifile.fonts if font]
746 ###############################################################################
748 ###############################################################################
749 # wobsta would mainly work here ...
751 class TexResultError(Exception):
753 def __init__(self
, description
, texrunner
):
754 self
.description
= description
755 self
.texrunner
= texrunner
758 return ("%s\n" % self
.description
+
759 "The expression passed to TeX was:\n" +
760 " %s\n" % self
.texrunner
.expr
.replace("\n", "\n ").rstrip() +
761 "The return message from TeX was:\n" +
762 " %s\n" % self
.texrunner
.texmsg
.replace("\n", "\n ").rstrip() +
763 "After parsing this message, the following was left:\n" +
764 " %s" % self
.texrunner
.texmsgparsed
.replace("\n", "\n ").rstrip())
767 class TexResultWarning(TexResultError
): pass
770 ###############################################################################
772 ############################################################################{{{
778 class _checkmsgstart(checkmsg
):
780 startpattern
= re
.compile(r
"This is [0-9a-zA-Z\s_]*TeX")
782 def check(self
, texrunner
):
783 m
= self
.startpattern
.search(texrunner
.texmsgparsed
)
785 raise TexResultError("TeX startup failed", texrunner
)
786 texrunner
.texmsgparsed
= texrunner
.texmsgparsed
[m
.end():]
788 texrunner
.texmsgparsed
= texrunner
.texmsgparsed
.split("%s.tex" % texrunner
.texfilename
, 1)[1]
790 raise TexResultError("TeX running startup file failed", texrunner
)
792 texrunner
.texmsgparsed
= texrunner
.texmsgparsed
.split("*! Undefined control sequence.\n<*> \\raiseerror\n %\n", 1)[1]
794 raise TexResultError("TeX switch to scrollmode failed", texrunner
)
797 class _checkmsgnoaux(checkmsg
):
799 def check(self
, texrunner
):
801 texrunner
.texmsgparsed
= texrunner
.texmsgparsed
.split("No file %s.aux." % texrunner
.texfilename
, 1)[1]
806 class _checkmsginputmarker(checkmsg
):
808 def check(self
, texrunner
):
810 s1
, s2
= texrunner
.texmsgparsed
.split("PyXInputMarker(%s)" % texrunner
.executeid
, 1)
811 texrunner
.texmsgparsed
= s1
+ s2
813 raise TexResultError("PyXInputMarker expected", texrunner
)
816 class _checkmsgpyxbox(checkmsg
):
818 pattern
= re
.compile(r
"PyXBox\(page=(?P<page>\d+),wd=-?\d*((\d\.?)|(\.?\d))\d*pt,ht=-?\d*((\d\.?)|(\.?\d))\d*pt,dp=-?\d*((\d\.?)|(\.?\d))\d*pt\)")
820 def check(self
, texrunner
):
821 m
= self
.pattern
.search(texrunner
.texmsgparsed
)
822 if m
and m
.group("page") == str(texrunner
.page
):
823 texrunner
.texmsgparsed
= texrunner
.texmsgparsed
[:m
.start()] + texrunner
.texmsgparsed
[m
.end():]
825 raise TexResultError("PyXBox expected", texrunner
)
828 class _checkmsgpyxpageout(checkmsg
):
830 def check(self
, texrunner
):
832 s1
, s2
= texrunner
.texmsgparsed
.split("[80.121.88.%s]" % texrunner
.page
, 1)
833 texrunner
.texmsgparsed
= s1
+ s2
835 raise TexResultError("PyXPageOutMarker expected", texrunner
)
838 class _checkmsgtexend(checkmsg
):
840 auxpattern
= re
.compile(r
"\([^()]*\.aux\)")
841 dvipattern
= re
.compile(r
"Output written on (?P<texfilename>[a-z]+).dvi \((?P<page>\d+) pages?, \d+ bytes\)\.")
843 def check(self
, texrunner
):
844 m
= self
.auxpattern
.search(texrunner
.texmsgparsed
)
846 texrunner
.texmsgparsed
= texrunner
.texmsgparsed
[:m
.start()] + texrunner
.texmsgparsed
[m
.end():]
848 s1
, s2
= texrunner
.texmsgparsed
.split("(see the transcript file for additional information)", 1)
849 texrunner
.texmsgparsed
= s1
+ s2
852 m
= self
.dvipattern
.search(texrunner
.texmsgparsed
)
855 raise TexResultError("TeX dvifile messages expected", texrunner
)
856 if m
.group("page") != str(texrunner
.page
):
857 raise TexResultError("wrong number of pages reported", texrunner
)
858 if m
.group("texfilename") != texrunner
.texfilename
:
859 raise TexResultError("wrong filename of the dvifile reported", texrunner
)
860 texrunner
.texmsgparsed
= texrunner
.texmsgparsed
[:m
.start()] + texrunner
.texmsgparsed
[m
.end():]
863 s1
, s2
= texrunner
.texmsgparsed
.split("No pages of output.", 1)
864 texrunner
.texmsgparsed
= s1
+ s2
866 raise TexResultError("no dvifile expected")
868 s1
, s2
= texrunner
.texmsgparsed
.split("Transcript written on %s.log." % texrunner
.texfilename
, 1)
869 texrunner
.texmsgparsed
= s1
+ s2
871 raise TexResultError("TeX logfile message expected")
874 class _checkmsgemptylines(checkmsg
):
876 pattern
= re
.compile(r
"^\*?\n", re
.M
)
878 def check(self
, texrunner
):
879 m
= self
.pattern
.search(texrunner
.texmsgparsed
)
881 texrunner
.texmsgparsed
= texrunner
.texmsgparsed
[:m
.start()] + texrunner
.texmsgparsed
[m
.end():]
882 m
= self
.pattern
.search(texrunner
.texmsgparsed
)
885 class _checkmsgload(checkmsg
):
887 pattern
= re
.compile(r
"\((?P<filename>[^()\s\n]+)[^()]*\)")
889 def baselevels(self
, s
, maxlevel
=1, brackets
="()"):
896 if level
> highestlevel
:
898 if level
<= maxlevel
:
902 if not level
and highestlevel
> 0:
905 def check(self
, texrunner
):
906 lowestbracketlevel
= self
.baselevels(texrunner
.texmsgparsed
)
907 if lowestbracketlevel
is not None:
908 m
= self
.pattern
.search(lowestbracketlevel
)
910 if os
.access(m
.group("filename"), os
.R_OK
):
911 lowestbracketlevel
= lowestbracketlevel
[:m
.start()] + lowestbracketlevel
[m
.end():]
914 m
= self
.pattern
.match(lowestbracketlevel
)
916 texrunner
.texmsgparsed
= lowestbracketlevel
919 class _checkmsggraphicsload(_checkmsgload
):
921 def baselevels(self
, s
, brackets
="<>", **args
):
922 _checkmsgload
.baselevels(self
, s
, brackets
=brackets
, **args
)
926 class _checkmsgignore(_checkmsgload
):
928 def check(self
, texrunner
):
929 texrunner
.texmsgparsed
= ""
932 checkmsg
.start
= _checkmsgstart()
933 checkmsg
.noaux
= _checkmsgnoaux()
934 checkmsg
.inputmarker
= _checkmsginputmarker()
935 checkmsg
.pyxbox
= _checkmsgpyxbox()
936 checkmsg
.pyxpageout
= _checkmsgpyxpageout()
937 checkmsg
.texend
= _checkmsgtexend()
938 checkmsg
.emptylines
= _checkmsgemptylines()
939 checkmsg
.load
= _checkmsgload()
940 checkmsg
.graphicsload
= _checkmsggraphicsload()
941 checkmsg
.ignore
= _checkmsgignore()
946 ###############################################################################
948 ############################################################################{{{
951 class halign
: # horizontal alignment
953 def __init__(self
, hratio
):
957 halign
.left
= halign(0)
958 halign
.right
= halign(1)
959 halign
.center
= halign(0.5)
962 class _texsetting
: # generic tex settings (modifications of the tex expression)
964 def modifyexpr(self
, expr
):
968 class valign(_texsetting
):
970 def __init__(self
, width
):
971 self
.width_str
= width
973 def modifyexpr(self
, expr
):
974 return r
"\%s{\hsize%.5fpt{%s}}" % (self
.vkind
, unit
.topt(self
.width_str
)*72.27/72.0, expr
)
977 class _valigntopline(valign
):
982 class _valignbottomline(valign
):
987 class _valigncenterline(valign
):
989 def __init__(self
, heightstr
="0", lowerratio
=0.5):
990 self
.heightstr
= heightstr
991 self
.lowerratio
= lowerratio
993 def modifyexpr(self
, expr
):
994 return r
"\setbox0\hbox{%s}\lower%.5f\ht0\hbox{%s}" % (self
.heightstr
, self
.lowerratio
, expr
)
997 valign
.topline
= _valigntopline
998 valign
.bottomline
= _valignbottomline
999 valign
.centerline
= _valigncenterline
1002 class _mathmode(_texsetting
):
1004 def modifyexpr(self
, expr
):
1005 return r
"\hbox{$\displaystyle{%s}$}" % expr
1007 mathmode
= _mathmode()
1010 defaultsizelist
= ["normalsize", "large", "Large", "LARGE", "huge", "Huge", None, "tiny", "scriptsize", "footnotesize", "small"]
1012 class size(_texsetting
):
1014 def __init__(self
, expr
, sizelist
=defaultsizelist
):
1015 if helper
.isinteger(expr
):
1016 if expr
>= 0 and expr
< sizelist
.index(None):
1017 self
.size
= sizelist
[expr
]
1018 elif expr
< 0 and expr
+ len(sizelist
) > sizelist
.index(None):
1019 self
.size
= sizelist
[expr
]
1021 raise IndexError("index out of sizelist range")
1025 def modifyexpr(self
, expr
):
1026 return r
"\%s{%s}" % (self
.size
, expr
)
1028 for s
in defaultsizelist
:
1030 size
.__dict
__[s
] = size(s
)
1035 class _readpipe(threading
.Thread
):
1037 def __init__(self
, pipe
, expectqueue
, gotevent
, gotqueue
):
1038 threading
.Thread
.__init
__(self
)
1041 self
.expectqueue
= expectqueue
1042 self
.gotevent
= gotevent
1043 self
.gotqueue
= gotqueue
1047 read
= self
.pipe
.readline()
1049 read
.replace("\r", "")
1050 if not len(read
) or read
[-1] != "\n":
1052 self
.gotqueue
.put(read
)
1054 self
.expect
= self
.expectqueue
.get_nowait()
1057 if read
.find(self
.expect
) != -1:
1059 read
= self
.pipe
.readline()
1060 if self
.expect
.find("PyXInputMarker") != -1:
1061 raise RuntimeError("TeX finished unexpectedly")
1065 class _textbox(box
._rect
, base
.PSText
):
1067 def __init__(self
, x
, y
, left
, right
, height
, depth
, texrunner
, page
):
1068 self
.texttrafo
= trafo
._translate
(-left
, 0)
1069 box
._rect
.__init
__(self
, -left
, -depth
, left
+ right
, depth
+ height
, abscenter
= (left
, depth
), trafo
=trafo
._translate
(x
, y
))
1070 self
.texrunner
= texrunner
1073 def transform(self
, trafo
):
1074 box
._rect
.transform(self
, trafo
)
1075 self
.texttrafo
= trafo
* self
.texttrafo
1077 def writefontheader(self
, file, containsfonts
):
1078 self
.texrunner
.writefontheader(file, containsfonts
)
1080 def write(self
, file):
1081 canvas
._gsave
().write(file) # XXX: canvas?, constructor call needed?
1082 self
.texttrafo
.write(file)
1083 self
.texrunner
.write(file, self
.page
)
1084 canvas
._grestore
().write(file)
1088 class textbox(_textbox
):
1090 def __init__(self
, x
, y
, left
, right
, height
, depth
, texrunner
, page
):
1091 _textbox
.__init
__(unit
.topt(x
), unit
.topt(y
), unit
.topt(left
), unit
.topt(right
),
1092 unit
.topt(height
), unit
.topt(depth
), texrunner
, page
)
1096 class TexRunsError(Exception): pass
1097 class TexDoneError(Exception): pass
1098 class TexNotInDefineModeError(Exception): pass
1101 class texrunner(attrlist
.attrlist
):
1103 def __init__(self
, mode
="tex",
1110 checkmsgstart
=checkmsg
.start
,
1111 checkmsgdocclass
=checkmsg
.load
,
1112 checkmsgbegindoc
=(checkmsg
.load
, checkmsg
.noaux
),
1113 checkmsgend
=checkmsg
.texend
,
1114 checkmsgdefaultdefine
=(),
1115 checkmsgdefaultrun
=()):
1117 self
.docclass
= docclass
1118 self
.docopt
= docopt
1119 self
.usefiles
= usefiles
1120 self
.waitfortex
= waitfortex
1121 self
.texdebug
= texdebug
1122 self
.dvidebug
= dvidebug
1123 self
.checkmsgstart
= helper
.ensuresequence(checkmsgstart
)
1124 self
.checkmsgdocclass
= helper
.ensuresequence(checkmsgdocclass
)
1125 self
.checkmsgbegindoc
= helper
.ensuresequence(checkmsgbegindoc
)
1126 self
.checkmsgend
= helper
.ensuresequence(checkmsgend
)
1127 self
.checkmsgdefaultdefine
= helper
.ensuresequence(checkmsgdefaultdefine
)
1128 self
.checkmsgdefaultrun
= helper
.ensuresequence(checkmsgdefaultrun
)
1135 self
.texfilename
= "text"
1137 def execute(self
, expr
, *checks
):
1138 if not self
.texruns
:
1139 texfile
= open("%s.tex" % self
.texfilename
, "w") # start with filename -> creates dvi file with that name
1140 texfile
.write("\\relax\n")
1143 self
.texinput
, self
.texoutput
= os
.popen4("%s %s" % (self
.mode
, self
.texfilename
), "t", 0)
1145 # workaround for MS Windows
1146 self
.texinput
, self
.texoutput
= os
.popen4("%s %s" % (self
.mode
, self
.texfilename
), "t")
1147 self
.expectqueue
= Queue
.Queue(1) # allow for a single entry only
1148 self
.gotevent
= threading
.Event()
1149 self
.gotqueue
= Queue
.Queue(0) # allow arbitrary number of entries
1150 self
.readoutput
= _readpipe(self
.texoutput
, self
.expectqueue
, self
.gotevent
, self
.gotqueue
)
1152 olddefinemode
= self
.definemode
1154 self
.execute("\\scrollmode\n\\raiseerror%\n" + # switch to and check scrollmode
1155 "\\def\\PyX{P\\kern-.3em\\lower.5ex\hbox{Y}\kern-.18em X}%\n" +
1156 "\\newbox\\PyXBox%\n" +
1157 "\\def\\ProcessPyXBox#1#2{%\n" +
1158 "\\setbox\\PyXBox=\\hbox{{#1}}%\n" +
1159 "\\immediate\\write16{PyXBox(page=#2," +
1160 "wd=\\the\\wd\\PyXBox," +
1161 "ht=\\the\\ht\\PyXBox," +
1162 "dp=\\the\\dp\\PyXBox)}%\n" +
1163 "\\ht\\PyXBox0pt%\n" +
1164 "{\\count0=80\\count1=121\\count2=88\\count3=#2\\shipout\\copy\\PyXBox}}%\n" +
1165 "\\def\\PyXInput#1{\\immediate\\write16{PyXInputMarker(#1)}}",
1166 *self
.checkmsgstart
)
1167 os
.remove("%s.tex" % self
.texfilename
)
1168 if self
.mode
== "latex":
1169 if self
.docopt
is not None:
1170 self
.execute("\\documentclass[%s]{%s}" % (self
.docopt
, self
.docclass
), *self
.checkmsgdocclass
)
1172 self
.execute("\\documentclass{%s}" % self
.docclass
, *self
.checkmsgdocclass
)
1173 self
.definemode
= olddefinemode
1175 if expr
is not None:
1176 self
.expectqueue
.put_nowait("PyXInputMarker(%i)" % self
.executeid
)
1178 self
.expr
= ("%s%%\n" % expr
+
1179 "\\PyXInput{%i}%%\n" % self
.executeid
)
1182 self
.expr
= ("\\ProcessPyXBox{%s}{%i}%%\n" % (expr
, self
.page
) +
1183 "\\PyXInput{%i}%%\n" % self
.executeid
)
1185 self
.expectqueue
.put_nowait("Transcript written on %s.log" % self
.texfilename
)
1186 if self
.mode
== "latex":
1187 self
.expr
= "\\end{document}\n"
1189 self
.expr
= "\\end\n"
1191 print "pass the following expression to (La)TeX:\n %s" % self
.expr
.replace("\n", "\n ").rstrip()
1192 self
.texinput
.write(self
.expr
)
1193 self
.gotevent
.wait(self
.waitfortex
)
1194 nogotevent
= not self
.gotevent
.isSet()
1195 self
.gotevent
.clear()
1199 self
.texmsg
+= self
.gotqueue
.get_nowait()
1202 self
.texmsgparsed
= self
.texmsg
1204 raise TexResultError("TeX didn't respond as expected within the timeout period (%i seconds)." % self
.waitfortex
, self
)
1206 if expr
is not None:
1207 checkmsg
.inputmarker
.check(self
)
1208 if not self
.definemode
:
1209 checkmsg
.pyxbox
.check(self
)
1210 checkmsg
.pyxpageout
.check(self
)
1211 for check
in checks
:
1214 except TexResultWarning
:
1215 traceback
.print_exc()
1216 checkmsg
.emptylines
.check(self
)
1217 if len(self
.texmsgparsed
):
1218 raise TexResultError("unhandled TeX response (might be an error)", self
)
1223 def writefontheader(self
, file, containsfonts
):
1224 if not self
.texdone
:
1225 _default
.execute(None, *self
.checkmsgend
)
1226 self
.dvifile
= DVIFile("%s.dvi" % self
.texfilename
, debug
=self
.dvidebug
)
1227 if self
not in containsfonts
:
1228 self
.dvifile
.writeheader(file)
1229 containsfonts
.append(self
)
1230 # TODO: - containfonts should contain font/glyph information instead of texrunner references
1232 def write(self
, file, page
):
1233 if not self
.texdone
:
1234 _default
.execute(None, *self
.checkmsgend
)
1235 self
.dvifile
= DVIFile("%s.dvi" % self
.texfilename
)
1236 return self
.dvifile
.write(file, page
)
1238 def settex(self
, mode
=None, waitfortex
=None):
1241 if mode
is not None:
1243 if mode
!= "tex" and mode
!= "latex":
1244 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
1246 if waitfortex
is not None:
1247 self
.waitfortex
= waitfortex
1249 def set(self
, texdebug
=None, dvidebug
=None, **args
):
1252 if texdebug
is not None:
1253 self
.texdebug
= texdebug
1254 if dvidebug
is not None:
1255 self
.dvidebug
= dvidebug
1256 if len(args
.keys()):
1259 def bracketcheck(self
, expr
):
1263 if c
== "{" and not esc
:
1265 if c
== "}" and not esc
:
1268 raise ValueError("unmatched '}'")
1274 raise ValueError("unmatched '{'")
1276 def define(self
, expr
, *args
):
1279 if not self
.definemode
:
1280 raise TexNotInDefineModeError
1281 self
.bracketcheck(expr
)
1282 self
.execute(expr
, *self
.attrgetall(args
, checkmsg
, default
=self
.checkmsgdefaultdefine
))
1284 PyXBoxPattern
= re
.compile(r
"PyXBox\(page=(?P<page>\d+),wd=(?P<wd>-?\d*((\d\.?)|(\.?\d))\d*)pt,ht=(?P<ht>-?\d*((\d\.?)|(\.?\d))\d*)pt,dp=(?P<dp>-?\d*((\d\.?)|(\.?\d))\d*)pt\)")
1286 def text(self
, x
, y
, expr
, *args
):
1288 raise ValueError("None is invalid")
1292 if self
.mode
== "latex":
1293 self
.execute("\\begin{document}", *self
.checkmsgbegindoc
)
1295 self
.attrcheck(args
, allowmulti
=(halign
, _texsetting
, checkmsg
, trafo
._trafo
))
1296 for texsetting
in self
.attrgetall(args
, _texsetting
, default
=()):
1297 expr
= texsetting
.modifyexpr(expr
)
1298 self
.bracketcheck(expr
)
1299 self
.execute(expr
, *self
.attrgetall(args
, checkmsg
, default
=self
.checkmsgdefaultrun
))
1300 match
= self
.PyXBoxPattern
.search(self
.texmsg
)
1301 if not match
or int(match
.group("page")) != self
.page
:
1302 raise TexResultError("box extents not found", self
)
1303 width
, height
, depth
= map(lambda x
: float(x
) * 72.0 / 72.27, match
.group("wd", "ht", "dp"))
1304 hratio
= self
.attrgetall(args
, halign
, default
=(halign
.left
,))[0].hratio
1305 textbox
= _textbox(unit
.topt(x
), unit
.topt(y
), hratio
* width
, (1 - hratio
) * width
, height
, depth
, self
, self
.page
)
1306 for t
in self
.attrgetall(args
, trafo
._trafo
, default
=()):
1307 textbox
.transform(t
)
1311 _default
= texrunner()
1313 define
= _default
.define
1314 text
= _default
.text