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
30 def __init__(self
, word
):
36 self
.precomma
= abs(word
) >> 20
37 self
.postcomma
= abs(word
) & 0xFFFFF
40 return self
.sign
* (self
.precomma
+ 1.0*self
.postcomma
/0xFFFFF)
42 def __mul__(self
, other
):
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)
55 def __init__(self
, filename
, mode
="r"):
56 self
.file = open(filename
, mode
)
59 return self
.file.tell()
61 def read(self
, bytes
):
62 return self
.file.read(bytes
)
64 def readint(self
, bytes
= 4, signed
= 0):
68 value
= ord(self
.file.read(1))
69 if first
and signed
and value
> 127:
72 result
= 256 * result
+ value
77 return struct
.unpack(">l", self
.file.read(4))[0]
80 return struct
.unpack(">L", self
.file.read(4))[0]
84 return struct
.unpack(">l", "\0"+self
.file.read(3))[0]
88 return struct
.unpack(">L", "\0"+self
.file.read(3))[0]
91 return struct
.unpack(">h", self
.file.read(2))[0]
94 return struct
.unpack(">H", self
.file.read(2))[0]
97 return struct
.unpack("b", self
.file.read(1))[0]
100 return struct
.unpack("B", self
.file.read(1))[0]
102 def readstring(self
, bytes
):
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")
125 def __init__(self
, name
, debug
=0):
126 self
.file = binfile(name
, "rb")
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
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"
153 print "lh=%d" % self
.lh
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
)
164 assert self
.lh
> 11, "inconsistency in TFM file: incomplete field"
165 self
.charcoding
= self
.file.readstring(40)
167 self
.charcoding
= None
170 assert self
.lh
> 16, "inconsistency in TFM file: incomplete field"
171 self
.fontfamily
= self
.file.readstring(20)
173 self
.fontfamily
= None
176 print "(FAMILY %s)" % self
.fontfamily
177 print "(CODINGSCHEME %s)" % self
.charcoding
178 print "(DESINGSIZE R %f)" % self
.designsize
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
197 self
.face
= "L" + self
.face
200 self
.face
= "B" + self
.face
203 self
.face
= "M" + self
.face
206 self
.face
= self
.face
[0] + "I" + self
.face
[1]
208 self
.face
= self
.face
[0] + "R" + self
.face
[1]
213 self
.sevenbitsave
= self
.face
= None
216 # just ignore the rest
217 print self
.file.read((self
.lh
-18)*4)
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())
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()
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()
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()
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()
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()
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()
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()
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()
305 def __init__(self
, name
, c
, q
, d
, tfmconv
, debug
=0):
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
)
328 self
.q
= self
.qorig
= q
329 while self
.q
>= 8388608:
333 self
.beta
= 256/self
.alpha
;
334 self
.alpha
= self
.alpha
*self
.q
;
336 # for bookkeeping of used characters
337 self
.usedchars
= [0] * 256
340 return "Font(%s, %d)" % (self
.name
, self
.tfmdesignsize
)
344 def convert(self
, width
):
346 return 16L*width
*self
.qorig
/16777216L
348 # original algorithm of Knuth (at the moment not used)
350 b1
= (width
>> 16) & 0xff
351 b2
= (width
>> 8 ) & 0xff
355 return (((((b3
*self
.q
)/256)+(b2
*self
.q
))/256)+(b1
*self
.q
))/self
.beta
357 return (((((b3
*self
.q
)/256)+(b2
*self
.q
))/256)+(b1
*self
.q
))/self
.beta
-self
.alpha
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
431 def __init__(self
, filename
, debug
=0):
432 self
.filename
= filename
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
)
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
)
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
)
460 print ("%d: %schar%d h:=%d+%d=%d, hh:=%d" %
462 inch
and "set" or "put",
464 self
.pos
[_POS_H
], dx
, self
.pos
[_POS_H
]+dx
,
466 self
.pos
[_POS_H
] += dx
471 def putrule(self
, height
, width
, inch
=1):
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:
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
)))
494 print ("%d: %srule height %d, width %d (invisible)" %
495 (self
.filepos
, inch
and "set" or "put", height
, width
))
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
):
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))
516 print ("%d: fntnum%i current font is %s" %
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)
524 # Note that q is actually s in large parts of the documentation.
527 self
.fonts
[num
] = Font(fontname
, c
, q
, d
, self
.tfmconv
, self
.debug
> 1)
530 print "%d: fntdef%d %i: %s" % (self
.filepos
, cmdnr
, num
, fontname
)
532 # scale = round((1000.0*self.conv*q)/(self.trueconv*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))
538 # print " (this font is magnified %d%%)" % round(scale/10)
540 # routines corresponding to the different reader states of the dvi maschine
545 self
.filepos
= file.tell()
546 cmd
= file.readuchar()
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;
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())
567 def _read_nopage(self
):
570 self
.filepos
= file.tell()
571 cmd
= file.readuchar()
574 elif cmd
== _DVI_BOP
:
577 print "%d: beginning of page" % self
.filepos
,
578 print file.readuint32()
579 for i
in range(9): file.readuint32()
581 for i
in range(10): file.readuint32()
584 self
.pos
= [0, 0, 0, 0, 0, 0]
585 self
.pages
.append([])
586 self
.actpage
= self
.pages
[-1]
588 elif cmd
== _DVI_POST
:
589 return _READ_DONE
# we skip the rest
593 def _read_page(self
):
596 self
.filepos
= file.tell()
597 cmd
= file.readuchar()
600 if cmd
>= _DVI_CHARMIN
and cmd
<= _DVI_CHARMAX
:
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
:
613 print "%d: eop" % self
.filepos
616 elif cmd
== _DVI_PUSH
:
617 self
.stack
.append(tuple(self
.pos
))
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
:
624 self
.pos
= list(self
.stack
[-1])
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:
632 dh
= file.readint(cmd
- _DVI_RIGHT1234
+ 1, 1)
634 print ("%d: right%d %d h:=%d%+d=%d, hh:=" %
636 cmd
- _DVI_RIGHT1234
+ 1,
640 self
.pos
[_POS_H
]+dh
))
641 self
.pos
[_POS_H
] += dh
645 print ("%d: w0 %d h:=%d%+d=%d, hh:=" %
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:
654 self
.pos
[_POS_W
] = file.readint(cmd
- _DVI_W1234
+ 1, 1)
656 print ("%d: w%d %d h:=%d%+d=%d, hh:=" %
658 cmd
- _DVI_W1234
+ 1,
662 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
663 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
666 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
667 elif cmd
>= _DVI_X1234
and cmd
< _DVI_X1234
+ 4:
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:
673 dv
= file.readint(cmd
- _DVI_DOWN1234
+ 1, 1)
675 print ("%d: down%d %d v:=%d%+d=%d, vv:=" %
677 cmd
- _DVI_DOWN1234
+ 1,
681 self
.pos
[_POS_V
]+dv
))
682 self
.pos
[_POS_V
] += dv
686 print ("%d: y0 %d v:=%d%+d=%d, vv:=" %
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:
695 self
.pos
[_POS_Y
] = file.readint(cmd
- _DVI_Y1234
+ 1, 1)
697 print ("%d: y%d %d v:=%d%+d=%d, vv:=" %
699 cmd
- _DVI_Y1234
+ 1,
703 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
704 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
707 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
708 elif cmd
>= _DVI_Z1234
and cmd
< _DVI_Z1234
+ 4:
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
:
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,
734 file.read(file.readuchar()+file.readuchar()))
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
753 # here goes the result, for each page one list.
756 # pointer to currently active page
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)
768 # start up reading process
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
()
778 raise DVIError
# unexpected reader state, should not happen
782 """ return prolog corresponding to contents of dvi file """
784 for font
in self
.fonts
:
785 if font
: result
.append(canvas
.fontdefinition(font
))
788 def write(self
, file, page
):
789 """write PostScript output for page into file"""
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 ###############################################################################
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
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"""
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
)
863 raise TexResultError("TeX startup failed", texrunner
)
864 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[m
.end():]
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
)
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
):
882 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s.aux." % texrunner
.texfilename
, 1)
883 texrunner
.texmessageparsed
= s1
+ s2
884 except (IndexError, ValueError):
886 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s%s%s.aux." % (os
.curdir
,
888 texrunner
.texfilename
), 1)
889 texrunner
.texmessageparsed
= s1
+ s2
890 except (IndexError, ValueError):
894 class _texmessageinputmarker(texmessage
):
895 """validates the PyXInputMarker"""
897 __implements__
= _Itexmessage
899 def check(self
, texrunner
):
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():]
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
):
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
):
942 s1
, s2
= texrunner
.texmessageparsed
.split("(%s.aux)" % texrunner
.texfilename
, 1)
943 texrunner
.texmessageparsed
= s1
+ s2
944 except (IndexError, ValueError):
946 s1
, s2
= texrunner
.texmessageparsed
.split("(%s%s%s.aux)" % (os
.curdir
,
948 texrunner
.texfilename
), 1)
949 texrunner
.texmessageparsed
= s1
+ s2
950 except (IndexError, ValueError):
953 s1
, s2
= texrunner
.texmessageparsed
.split("(see the transcript file for additional information)", 1)
954 texrunner
.texmessageparsed
= s1
+ s2
955 except (IndexError, ValueError):
957 dvipattern
= re
.compile(r
"Output written on %s\.dvi \((?P<page>\d+) pages?, \d+ bytes\)\." % texrunner
.texfilename
)
958 m
= dvipattern
.search(texrunner
.texmessageparsed
)
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():]
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")
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
)
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
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"""
1015 if c
== brackets
[0]:
1017 if level
> highestlevel
:
1018 highestlevel
= level
1019 if level
<= maxlevel
:
1021 if c
== brackets
[1]:
1023 if level
== 0 and highestlevel
> 0:
1026 def check(self
, texrunner
):
1027 lowestbracketlevel
= self
.baselevels(texrunner
.texmessageparsed
)
1028 if lowestbracketlevel
is not None:
1029 m
= self
.pattern
.search(lowestbracketlevel
)
1031 print m
.group("filename")
1032 if os
.access(m
.group("filename"), os
.R_OK
):
1033 lowestbracketlevel
= lowestbracketlevel
[:m
.start()] + lowestbracketlevel
[m
.end():]
1036 m
= self
.pattern
.search(lowestbracketlevel
)
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
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 ###############################################################################
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
1096 # - order attributes are used to exclude complementary settings (with the
1098 # - texsettings might (in rare cases) depend on each other (e.g. parbox and
1100 ###############################################################################
1104 - modifies a TeX/LaTeX expression"""
1107 """order attribute for TeX settings
1108 - higher id's will be applied first (most inside)"""
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
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
= ""
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
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"
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
):
1209 raise RuntimeError(self
.noparboxmessage
)
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
1255 def __init__(self
, width
):
1258 def modifyexpr(self
, expr
, texsettings
, texrunner
):
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
):
1268 elif isinstance(texsetting
, _valignbottombaseline
):
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
)
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
):
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
):
1320 __implements__
= _Itexsetting
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
):
1335 __implements__
= _Itexsetting
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
]
1346 raise IndexError("index out of sizelist range")
1350 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1351 return r
"\%s{%s}" % (self
.size
, expr
)
1353 for s
in defaultsizelist
:
1355 size
.__dict
__[s
] = size(s
)
1358 ###############################################################################
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)
1378 self
.expectqueue
= expectqueue
1379 self
.gotevent
= gotevent
1380 self
.gotqueue
= gotqueue
1381 self
.quitevent
= quitevent
1386 """thread routine"""
1387 read
= self
.pipe
.readline() # read, what comes in
1389 self
.expect
= self
.expectqueue
.get_nowait() # read, what should be expected
1393 # universal EOL handling (convert everything into unix like EOLs)
1394 read
.replace("\r", "")
1395 if not len(read
) or read
[-1] != "\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
1402 self
.expect
= self
.expectqueue
.get_nowait()
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
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
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
:
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(".")
1469 os
.rename(texrunner
.texfilename
+ usefile
[extpos
:], usefile
)
1472 for file in glob
.glob("%s.*" % texrunner
.texfilename
):
1479 # texrunner state exceptions
1480 class TexRunsError(Exception): pass
1481 class TexDoneError(Exception): pass
1482 class TexNotInPreambleModeError(Exception): pass
1486 """TeX/LaTeX interface
1487 - runs TeX/LaTeX expressions instantly
1488 - checks TeX/LaTeX response"""
1490 def __init__(self
, mode
="tex",
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):
1505 if mode
!= "tex" and mode
!= "latex" and mode
!= "pdftex" and mode
!= "pdflatex":
1506 raise ValueError("mode \"TeX\", \"LaTeX\", \"pdfTeX\", or \"pdfLaTeX\" expected")
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
1536 self
.preamblemode
= 1
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(".")
1559 os
.rename(usefile
, self
.texfilename
+ usefile
[extpos
:])
1562 texfile
= open("%s.tex" % self
.texfilename
, "w") # start with filename -> creates dvi file with that name
1563 texfile
.write("\\relax\n")
1566 self
.texinput
, self
.texoutput
= os
.popen4("%s %s" % (self
.mode
, self
.texfilename
), "t", 0)
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
)
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"
1608 "\\write16{PyXMarker:name=#1,"
1609 "xpos=\\the\\pdflastxpos,"
1610 "ypos=\\the\\pdflastypos:}%\n"
1612 if self
.mode
== "tex":
1614 LocalLfsName
= str(self
.lfs
) + ".lfs"
1615 lfsdef
= open(LocalLfsName
, "r").read()
1619 SysLfsName
= os
.path
.join(sys
.prefix
, "share", "pyx", str(self
.lfs
) + ".lfs")
1620 lfsdef
= open(SysLfsName
, "r").read()
1622 SysLfsName
= os
.path
.join(os
.path
.dirname(__file__
), "lfs", str(self
.lfs
) + ".lfs")
1623 lfsdef
= open(SysLfsName
, "r").read()
1627 allfiles
+= os
.listdir(".")
1631 allfiles
+= os
.listdir(os
.path
.join(sys
.prefix
, "share", "pyx"))
1635 allfiles
+= os
.listdir(os
.path
.join(os
.path
.dirname(__file__
), "lfs"))
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
)
1647 self
.execute("\\documentclass{%s}" % self
.docclass
, *self
.texmessagedocclass
)
1648 self
.preamblemode
= oldpreamblemode
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
)
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"
1664 self
.expr
= "\\end\n"
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
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()
1678 self
.texmessage
= ""
1680 self
.texmessage
+= self
.gotqueue
.get_nowait()
1683 self
.texmessageparsed
= self
.texmessage
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
:
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
)
1699 raise TexResultError("TeX didn't respond as expected within the timeout period (%i seconds)." % self
.waitfortex
, 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
)
1707 "return the dvifile prolog (assume, that everything in the dvifile will be written to postscript)"
1708 if not self
.texdone
:
1710 return self
.dvifile
.prolog()
1712 def write(self
, file, page
):
1713 "write a page from the dvifile"
1714 if not self
.texdone
:
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,
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"""
1732 if mode
is not None:
1734 if mode
!= "tex" and mode
!= "latex" and mode
!= "pdftex" and mode
!= "pdflatex":
1735 raise ValueError("mode \"TeX\", \"LaTeX\", \"pdfTeX\", or \"pdfLaTeX\" expected")
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"""
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()):
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"""
1793 if c
== "{" and not esc
:
1795 if c
== "}" and not esc
:
1798 raise ValueError("unmatched '}'")
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"""
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"""
1840 raise ValueError("None expression is invalid")
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
=[])
1851 for texsetting
in texsettings
:
1852 if texsetting
.exclusive
:
1853 if texsetting
.id not in exclusive
:
1854 exclusive
.append(texsetting
.id)
1856 raise RuntimeError("multiple occurance of exclusive texsetting with id=%i" % texsetting
.id)
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
=()):
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