2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2003 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2002-2003 André Wobst <wobsta@users.sourceforge.net>
7 # Copyright (C) 2003 Michael Schindler <m-schindler@users.sourceforge.net>
9 # This file is part of PyX (http://pyx.sourceforge.net/).
11 # PyX is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # PyX is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with PyX; if not, write to the Free Software
23 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 import exceptions
, glob
, os
, threading
, Queue
, traceback
, re
, struct
, tempfile
, sys
, atexit
, time
26 import helper
, unit
, bbox
, box
, base
, canvas
, color
, trafo
, path
, prolog
, pykpathsea
, version
29 def __init__(self
, word
):
35 self
.precomma
= abs(word
) >> 20
36 self
.postcomma
= abs(word
) & 0xFFFFF
39 return self
.sign
* (self
.precomma
+ 1.0*self
.postcomma
/0xFFFFF)
41 def __mul__(self
, other
):
45 result
.sign
= self
.sign
*other
.sign
46 c
= self
.postcomma
*other
.precomma
+ self
.precomma
*other
.postcomma
47 result
.precomma
= self
.precomma
*other
.precomma
+ (c
>> 20)
48 result
.postcomma
= c
& 0xFFFFF + ((self
.postcomma
*other
.postcomma
) >> 40)
54 def __init__(self
, filename
, mode
="r"):
55 self
.file = open(filename
, mode
)
58 return self
.file.tell()
60 def read(self
, bytes
):
61 return self
.file.read(bytes
)
63 def readint(self
, bytes
=4, signed
=0):
67 value
= ord(self
.file.read(1))
68 if first
and signed
and value
> 127:
71 result
= 256 * result
+ value
76 return struct
.unpack(">l", self
.file.read(4))[0]
79 return struct
.unpack(">L", self
.file.read(4))[0]
83 return struct
.unpack(">l", "\0"+self
.file.read(3))[0]
87 return struct
.unpack(">L", "\0"+self
.file.read(3))[0]
90 return struct
.unpack(">h", self
.file.read(2))[0]
93 return struct
.unpack(">H", self
.file.read(2))[0]
96 return struct
.unpack("b", self
.file.read(1))[0]
99 return struct
.unpack("B", self
.file.read(1))[0]
101 def readstring(self
, bytes
):
103 assert l
<= bytes
-1, "inconsistency in file: string too long"
104 return self
.file.read(bytes
-1)[:l
]
108 # """ ascii file containing tokens separated by spaces.
110 # Comments beginning with % are ignored. Strings containing spaces
111 # are not handled correctly
114 # def __init__(self, filename):
115 # self.file = open(filename, "r")
118 # def gettoken(self):
119 # """ return next token or None if EOF """
120 # while not self.line:
121 # line = self.file.readline()
124 # self.line = line.split("%")[0].split()
125 # token = self.line[0]
126 # self.line = self.line[1:]
133 ##############################################################################
135 ##############################################################################
137 class TFMError(exceptions
.Exception): pass
140 class char_info_word
:
141 def __init__(self
, word
):
142 self
.width_index
= int((word
& 0xFF000000L
) >> 24) #make sign-safe
143 self
.height_index
= (word
& 0x00F00000) >> 20
144 self
.depth_index
= (word
& 0x000F0000) >> 16
145 self
.italic_index
= (word
& 0x0000FC00) >> 10
146 self
.tag
= (word
& 0x00000300) >> 8
147 self
.remainder
= (word
& 0x000000FF)
151 def __init__(self
, name
, debug
=0):
152 self
.file = binfile(name
, "rb")
159 self
.lf
= self
.file.readint16()
160 self
.lh
= self
.file.readint16()
161 self
.bc
= self
.file.readint16()
162 self
.ec
= self
.file.readint16()
163 self
.nw
= self
.file.readint16()
164 self
.nh
= self
.file.readint16()
165 self
.nd
= self
.file.readint16()
166 self
.ni
= self
.file.readint16()
167 self
.nl
= self
.file.readint16()
168 self
.nk
= self
.file.readint16()
169 self
.ne
= self
.file.readint16()
170 self
.np
= self
.file.readint16()
172 if not (self
.bc
-1 <= self
.ec
<= 255 and
174 self
.lf
== 6+self
.lh
+(self
.ec
-self
.bc
+1)+self
.nw
+self
.nh
+self
.nd
175 +self
.ni
+self
.nl
+self
.nk
+self
.ne
+self
.np
):
176 raise TFMError
, "error in TFM pre-header"
179 print "lh=%d" % self
.lh
185 self
.checksum
= self
.file.readint32()
186 self
.designsizeraw
= self
.file.readint32()
187 assert self
.designsizeraw
> 0, "invald design size"
188 self
.designsize
= fix_word(self
.designsizeraw
)
190 assert self
.lh
> 11, "inconsistency in TFM file: incomplete field"
191 self
.charcoding
= self
.file.readstring(40)
193 self
.charcoding
= None
196 assert self
.lh
> 16, "inconsistency in TFM file: incomplete field"
197 self
.fontfamily
= self
.file.readstring(20)
199 self
.fontfamily
= None
202 print "(FAMILY %s)" % self
.fontfamily
203 print "(CODINGSCHEME %s)" % self
.charcoding
204 print "(DESINGSIZE R %f)" % self
.designsize
207 self
.sevenbitsave
= self
.file.readuchar()
208 # ignore the following two bytes
209 self
.file.readint16()
210 facechar
= self
.file.readuchar()
211 # decode ugly face specification into the Knuth suggested string
223 self
.face
= "L" + self
.face
226 self
.face
= "B" + self
.face
229 self
.face
= "M" + self
.face
232 self
.face
= self
.face
[0] + "I" + self
.face
[1]
234 self
.face
= self
.face
[0] + "R" + self
.face
[1]
239 self
.sevenbitsave
= self
.face
= None
242 # just ignore the rest
243 print self
.file.read((self
.lh
-18)*4)
249 self
.char_info
= [None]*(self
.ec
+1)
251 for charcode
in range(self
.bc
, self
.ec
+1):
252 self
.char_info
[charcode
] = char_info_word(self
.file.readint32())
253 if self
.char_info
[charcode
].width_index
== 0:
254 # disable character if width_index is zero
255 self
.char_info
[charcode
] = None
261 self
.width
= [None for width_index
in range(self
.nw
)]
262 for width_index
in range(self
.nw
):
263 # self.width[width_index] = fix_word(self.file.readint32())
264 self
.width
[width_index
] = self
.file.readint32()
270 self
.height
= [None for height_index
in range(self
.nh
)]
271 for height_index
in range(self
.nh
):
272 # self.height[height_index] = fix_word(self.file.readint32())
273 self
.height
[height_index
] = self
.file.readint32()
279 self
.depth
= [None for depth_index
in range(self
.nd
)]
280 for depth_index
in range(self
.nd
):
281 # self.depth[depth_index] = fix_word(self.file.readint32())
282 self
.depth
[depth_index
] = self
.file.readint32()
288 self
.italic
= [None for italic_index
in range(self
.ni
)]
289 for italic_index
in range(self
.ni
):
290 # self.italic[italic_index] = fix_word(self.file.readint32())
291 self
.italic
[italic_index
] = self
.file.readint32()
297 # XXX decode to lig_kern_command
299 self
.lig_kern
= [None for lig_kern_index
in range(self
.nl
)]
300 for lig_kern_index
in range(self
.nl
):
301 self
.lig_kern
[lig_kern_index
] = self
.file.readint32()
307 self
.kern
= [None for kern_index
in range(self
.nk
)]
308 for kern_index
in range(self
.nk
):
309 # self.kern[kern_index] = fix_word(self.file.readint32())
310 self
.kern
[kern_index
] = self
.file.readint32()
316 # XXX decode to extensible_recipe
318 self
.exten
= [None for exten_index
in range(self
.ne
)]
319 for exten_index
in range(self
.ne
):
320 self
.exten
[exten_index
] = self
.file.readint32()
328 self
.param
= [None for param_index
in range(self
.np
)]
329 for param_index
in range(self
.np
):
330 self
.param
[param_index
] = self
.file.readint32()
332 self
.file.file.close()
335 # class FontEncoding:
337 # def __init__(self, filename):
338 # """ font encoding contained in filename """
339 # encpath = pykpathsea.find_file(filename, pykpathsea.kpse_tex_ps_header_format)
340 # encfile = tokenfile(encpath)
343 # self.encname = encfile.gettoken()
344 # token = encfile.gettoken()
346 # raise RuntimeError("cannot parse encoding file '%s', expecting '[' got '%s'" % (filename, token))
347 # self.encvector = []
348 # for i in range(256):
349 # token = encfile.gettoken()
350 # if token is None or token=="]":
351 # raise RuntimeError("not enough charcodes in encoding file '%s'" % filename)
352 # self.encvector.append(token)
353 # if encfile.gettoken() != "]":
354 # raise RuntimeError("too many charcodes in encoding file '%s'" % filename)
355 # token = encfile.gettoken()
357 # raise RuntimeError("cannot parse encoding file '%s', expecting 'def' got '%s'" % (filename, token))
358 # token = encfile.gettoken()
360 # raise RuntimeError("encoding file '%s' too long" % filename)
363 # def encode(self, charcode):
364 # return self.encvector[charcode]
366 ##############################################################################
368 ##############################################################################
370 _ReEncodeFont
= prolog
.definition("ReEncodeFont", """{
373 /newencoding exch def
374 /newfontname exch def
375 /basefontname exch def
376 /basefontdict basefontname findfont def
377 /newfontdict basefontdict maxlength dict def
379 exch dup dup /FID ne exch /Encoding ne and
380 { exch newfontdict 3 1 roll put }
384 newfontdict /FontName newfontname put
385 newfontdict /Encoding newencoding put
386 newfontname newfontdict definefont pop
391 # PostScript font selection and output primitives
394 class _selectfont(base
.PSOp
):
395 def __init__(self
, name
, size
):
399 def write(self
, file):
400 file.write("/%s %f selectfont\n" % (self
.name
, self
.size
))
402 # XXX: should we provide a prolog method for the font inclusion
403 # instead of using the coarser logic in DVIFile.prolog
406 class _show(base
.PSOp
):
407 def __init__(self
, x
, y
, s
):
412 def write(self
, file):
413 file.write("%f %f moveto (%s) show\n" % (self
.x
, self
.y
, self
.s
))
418 tokenpattern
= re
.compile(r
'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
420 def __init__(self
, s
):
421 """ construct font mapping from line s of dvips mapping file """
422 self
.texname
= self
.basepsname
= self
.fontfile
= None
425 self
.encodingfile
= None
427 # supported postscript fragments occuring in psfonts.map
428 self
.reencodefont
= self
.extendfont
= self
.slantfont
= None
432 match
= self
.tokenpattern
.match(s
)
434 if match
.groups()[0]:
435 tokens
.append('"%s"' % match
.groups()[0])
437 tokens
.append(match
.groups()[2])
440 raise RuntimeError("wrong syntax in font catalog file 'psfonts.map'")
443 if token
.startswith("<"):
444 if token
.startswith("<<"):
445 # XXX: support non-partial download here
446 self
.fontfile
= token
[2:]
447 elif token
.startswith("<["):
448 self
.encodingfile
= token
[2:]
449 elif token
.endswith(".pfa") or token
.endswith(".pfb"):
450 self
.fontfile
= token
[1:]
451 elif token
.endswith(".enc"):
452 self
.encodingfile
= token
[1:]
454 raise RuntimeError("wrong syntax in font catalog file 'psfonts.map'")
455 elif token
.startswith('"'):
456 pscode
= token
[1:-1].split()
457 # parse standard postscript code fragments
460 arg
, cmd
= pscode
[:2]
462 raise RuntimeError("Unsupported Postscript fragment '%s' in psfonts.map" % pscode
)
464 if cmd
== "ReEncodeFont":
465 self
.reencodefont
= arg
466 elif cmd
== "ExtendFont":
467 self
.extendfont
= arg
468 elif cmd
== "SlantFont":
471 raise RuntimeError("Unsupported Postscript fragment '%s %s' in psfonts.map" % (arg
, cmd
))
473 if self
.texname
is None:
476 self
.basepsname
= token
477 if self
.basepsname
is None:
478 self
.basepsname
= self
.texname
481 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
482 (self
.texname
, self
.basepsname
, self
.fontfile
, repr(self
.encodingfile
)))
486 def readfontmap(filenames
):
487 """ read font map from filename (without path) """
489 for filename
in filenames
:
490 mappath
= pykpathsea
.find_file(filename
, pykpathsea
.kpse_dvips_config_format
)
492 raise RuntimeError("cannot find dvips font catalog '%s', aborting" % filename
)
493 mapfile
= open(mappath
, "r")
494 for line
in mapfile
.readlines():
496 if not (line
=="" or line
[0] in (" ", "%", "*", ";" , "#")):
497 fontmapping
= FontMapping(line
)
498 fontmap
[fontmapping
.texname
] = fontmapping
504 fontmap
= readfontmap(["psfonts.map"])
508 def __init__(self
, name
, c
, q
, d
, tfmconv
, debug
=0):
510 self
.tfmpath
= pykpathsea
.find_file("%s.tfm" % self
.name
, pykpathsea
.kpse_tfm_format
)
511 if self
.tfmpath
is None:
512 raise TFMError("cannot find %s.tfm" % self
.name
)
513 self
.tfmfile
= TFMFile(self
.tfmpath
, debug
)
514 self
.fontmapping
= fontmap
.get(name
)
515 if self
.fontmapping
is None:
516 raise RuntimeError("no information for font '%s' found in font mapping file, aborting" % name
)
517 # print "found mapping %s for font %s" % (self.fontmapping, self.name)
519 if self
.tfmfile
.checksum
!= c
:
520 raise DVIError("check sums do not agree: %d vs. %d" %
521 (self
.tfmfile
.checksum
, c
))
523 self
.tfmdesignsize
= round(tfmconv
*self
.tfmfile
.designsizeraw
)
525 if abs(self
.tfmdesignsize
- d
) > 2:
526 raise DVIError("design sizes do not agree: %d vs. %d" %
527 (self
.tfmdesignsize
, d
))
528 if q
< 0 or q
> 134217728:
529 raise DVIError("font '%s' not loaded: bad scale" % self
.name
)
530 if d
< 0 or d
> 134217728:
531 raise DVIError("font '%s' not loaded: bad design size" % self
.name
)
535 self
.q
= self
.qorig
= q
536 while self
.q
>= 8388608:
540 self
.beta
= 256/self
.alpha
;
541 self
.alpha
= self
.alpha
*self
.q
;
543 # for bookkeeping of used characters
544 self
.usedchars
= [0] * 256
547 return "Font(%s, %d)" % (self
.name
, self
.tfmdesignsize
)
551 def convert(self
, width
):
553 return 16L*width
*self
.qorig
/16777216L
555 # original algorithm of Knuth (at the moment not used)
557 b1
= (width
>> 16) & 0xff
558 b2
= (width
>> 8 ) & 0xff
562 return (((((b3
*self
.q
)/256)+(b2
*self
.q
))/256)+(b1
*self
.q
))/self
.beta
564 return (((((b3
*self
.q
)/256)+(b2
*self
.q
))/256)+(b1
*self
.q
))/self
.beta
-self
.alpha
566 raise TFMError("error in font size")
568 def getwidth(self
, charcode
):
569 return self
.convert(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
571 def getheight(self
, charcode
):
572 return self
.convert(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
574 def getdepth(self
, charcode
):
575 return self
.convert(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
577 def getitalic(self
, charcode
):
578 return self
.convert(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
580 def markcharused(self
, charcode
):
581 self
.usedchars
[charcode
] = 1
583 def mergeusedchars(self
, otherfont
):
584 for i
in range(len(self
.usedchars
)):
585 self
.usedchars
[i
] = self
.usedchars
[i
] or otherfont
.usedchars
[i
]
587 def getbasepsname(self
):
588 return self
.fontmapping
.basepsname
591 if self
.fontmapping
.reencodefont
:
592 return "%s-%s" % (self
.fontmapping
.basepsname
, self
.fontmapping
.reencodefont
)
594 return self
.fontmapping
.basepsname
596 def getfontfile(self
):
597 return self
.fontmapping
.fontfile
599 def getencoding(self
):
600 return self
.fontmapping
.reencodefont
602 def getencodingfile(self
):
603 return self
.fontmapping
.encodingfile
605 ##############################################################################
607 ##############################################################################
609 _DVI_CHARMIN
= 0 # typeset a character and move right (range min)
610 _DVI_CHARMAX
= 127 # typeset a character and move right (range max)
611 _DVI_SET1234
= 128 # typeset a character and move right
612 _DVI_SETRULE
= 132 # typeset a rule and move right
613 _DVI_PUT1234
= 133 # typeset a character
614 _DVI_PUTRULE
= 137 # typeset a rule
615 _DVI_NOP
= 138 # no operation
616 _DVI_BOP
= 139 # beginning of page
617 _DVI_EOP
= 140 # ending of page
618 _DVI_PUSH
= 141 # save the current positions (h, v, w, x, y, z)
619 _DVI_POP
= 142 # restore positions (h, v, w, x, y, z)
620 _DVI_RIGHT1234
= 143 # move right
621 _DVI_W0
= 147 # move right by w
622 _DVI_W1234
= 148 # move right and set w
623 _DVI_X0
= 152 # move right by x
624 _DVI_X1234
= 153 # move right and set x
625 _DVI_DOWN1234
= 157 # move down
626 _DVI_Y0
= 161 # move down by y
627 _DVI_Y1234
= 162 # move down and set y
628 _DVI_Z0
= 166 # move down by z
629 _DVI_Z1234
= 167 # move down and set z
630 _DVI_FNTNUMMIN
= 171 # set current font (range min)
631 _DVI_FNTNUMMAX
= 234 # set current font (range max)
632 _DVI_FNT1234
= 235 # set current font
633 _DVI_SPECIAL1234
= 239 # special (dvi extention)
634 _DVI_FNTDEF1234
= 243 # define the meaning of a font number
635 _DVI_PRE
= 247 # preamble
636 _DVI_POST
= 248 # postamble beginning
637 _DVI_POSTPOST
= 249 # postamble ending
639 _DVI_VERSION
= 2 # dvi version
641 # position variable indices
658 class DVIError(exceptions
.Exception): pass
660 # save and restore colors
662 class _savecolor(base
.PSOp
):
663 def write(self
, file):
664 file.write("currentcolor currentcolorspace\n")
667 class _restorecolor(base
.PSOp
):
668 def write(self
, file):
669 file.write("setcolorspace setcolor\n")
671 class _savetrafo(base
.PSOp
):
672 def write(self
, file):
673 file.write("matrix currentmatrix\n")
676 class _restoretrafo(base
.PSOp
):
677 def write(self
, file):
678 file.write("setmatrix\n")
682 def __init__(self
, filename
, debug
=0):
683 self
.filename
= filename
690 """ flush currently active string """
692 x
= unit
.t_m(self
.actoutstart
[0] * self
.conv
* 0.0254 / self
.resolution
)
693 y
= -unit
.t_m(self
.actoutstart
[1] * self
.conv
* 0.0254 / self
.resolution
)
695 print "[%s]" % self
.actoutstring
696 self
.actpage
.insert(_show(unit
.topt(x
), unit
.topt(y
), self
.actoutstring
))
697 self
.actoutstart
= None
699 def putchar(self
, char
, inch
=1):
700 if self
.actoutstart
is None:
701 self
.actoutstart
= self
.pos
[_POS_H
], self
.pos
[_POS_V
]
702 self
.actoutstring
= ""
703 if char
> 32 and char
< 127 and chr(char
) not in "()[]<>":
704 ascii
= "%s" % chr(char
)
706 ascii
= "\\%03o" % char
707 self
.actoutstring
= self
.actoutstring
+ ascii
708 dx
= inch
and self
.fonts
[self
.activefont
].getwidth(char
) or 0
709 self
.fonts
[self
.activefont
].markcharused(char
)
711 print ("%d: %schar%d h:=%d+%d=%d, hh:=%d" %
713 inch
and "set" or "put",
715 self
.pos
[_POS_H
], dx
, self
.pos
[_POS_H
]+dx
,
717 self
.pos
[_POS_H
] += dx
722 def putrule(self
, height
, width
, inch
=1):
724 x1
= unit
.t_m(self
.pos
[_POS_H
] * self
.conv
* 0.0254 / self
.resolution
)
725 y1
= -unit
.t_m(self
.pos
[_POS_V
] * self
.conv
* 0.0254 / self
.resolution
)
726 w
= unit
.t_m(width
* self
.conv
* 0.0254 / self
.resolution
)
727 h
= unit
.t_m(height
* self
.conv
* 0.0254 / self
.resolution
)
729 if height
> 0 and width
> 0:
731 pixelw
= int(width
*self
.conv
)
732 if pixelw
< width
*self
.conv
: pixelw
+= 1
733 pixelh
= int(height
*self
.conv
)
734 if pixelh
< height
*self
.conv
: pixelh
+= 1
736 print ("%d: %srule height %d, width %d (%dx%d pixels)" %
737 (self
.filepos
, inch
and "set" or "put", height
, width
, pixelh
, pixelw
))
738 self
.actpage
.fill(path
.rect(x1
, y1
, w
, h
))
741 print ("%d: %srule height %d, width %d (invisible)" %
742 (self
.filepos
, inch
and "set" or "put", height
, width
))
746 print (" h:=%d+%d=%d, hh:=%d" %
747 (self
.pos
[_POS_H
], width
, self
.pos
[_POS_H
]+width
, 0))
748 self
.pos
[_POS_H
] += width
751 def usefont(self
, fontnum
):
753 self
.activefont
= fontnum
755 fontpsname
= self
.fonts
[self
.activefont
].getpsname()
756 fontscale
= self
.fonts
[self
.activefont
].scale
757 fontdesignsize
= float(self
.fonts
[self
.activefont
].tfmfile
.designsize
)
758 self
.actpage
.insert(_selectfont(fontpsname
,
759 fontscale
*fontdesignsize
*72/72.27))
762 print ("%d: fntnum%i current font is %s" %
764 self
.activefont
, self
.fonts
[fontnum
].name
))
766 def definefont(self
, cmdnr
, num
, c
, q
, d
, fontname
):
767 # cmdnr: type of fontdef command (only used for debugging output)
770 # Note that q is actually s in large parts of the documentation.
773 self
.fonts
[num
] = Font(fontname
, c
, q
, d
, self
.tfmconv
, self
.debug
> 1)
776 print "%d: fntdef%d %i: %s" % (self
.filepos
, cmdnr
, num
, fontname
)
778 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
780 # scalestring = scale!=1000 and " scaled %d" % scale or ""
781 # print ("Font %i: %s%s---loaded at size %d DVI units" %
782 # (num, fontname, scalestring, q))
784 # print " (this font is magnified %d%%)" % round(scale/10)
786 def special(self
, s
):
788 x
= unit
.t_m(self
.pos
[_POS_H
] * self
.conv
* 0.0254 / self
.resolution
)
789 y
= -unit
.t_m(self
.pos
[_POS_V
] * self
.conv
* 0.0254 / self
.resolution
)
791 print "%d: xxx '%s'" % (self
.filepos
, s
)
792 if not s
.startswith("PyX:"):
793 raise RuntimeError("the special '%s' cannot be handled by PyX, aborting" % s
)
794 command
, args
= s
[4:].split()[0], s
[4:].split()[1:]
795 if command
=="color_begin":
797 c
= color
.cmyk(float(args
[1]), float(args
[2]), float(args
[3]), float(args
[4]))
798 elif args
[0]=="gray":
799 c
= color
.gray(float(args
[1]))
801 c
= color
.hsb(float(args
[1]), float(args
[2]), float(args
[3]))
803 c
= color
.rgb(float(args
[1]), float(args
[2]), float(args
[3]))
805 c
= color
.rgb(int(args
[1])/255.0, int(args
[2])/255.0, int(args
[3])/255.0)
806 elif args
[0]=="texnamed":
808 c
= getattr(color
.cmyk
, args
[1])
809 except AttributeError:
810 raise RuntimeError("unknown TeX color '%s', aborting" % args
[1])
812 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args
[0])
813 self
.actpage
.insert(_savecolor())
814 self
.actpage
.insert(c
)
815 elif command
=="color_end":
816 self
.actpage
.insert(_restorecolor())
817 elif command
=="rotate_begin":
818 self
.actpage
.insert(_savetrafo())
819 self
.actpage
.insert(trafo
.rotate(float(args
[0]), x
, y
))
820 elif command
=="rotate_end":
821 self
.actpage
.insert(_restoretrafo())
822 elif command
=="scale_begin":
823 self
.actpage
.insert(_savetrafo())
824 self
.actpage
.insert(trafo
.scale(float(args
[0]), float(args
[1]), x
, y
))
825 elif command
=="scale_end":
826 self
.actpage
.insert(_restoretrafo())
827 elif command
=="epsinclude":
828 # XXX: we cannot include epsfile in the header because this would
829 # generate a cyclic import with the canvas and text modules
835 name
, value
= arg
.split("=")
836 argdict
[name
] = value
838 # construct kwargs for epsfile constructor
840 epskwargs
["filename"] = argdict
["file"]
841 epskwargs
["bbox"] = bbox
._bbox
(float(argdict
["llx"]), float(argdict
["lly"]),
842 float(argdict
["urx"]), float(argdict
["ury"]))
843 if argdict
.has_key("width"):
844 epskwargs
["width"] = unit
.t_pt(float(argdict
["width"]))
845 if argdict
.has_key("height"):
846 epskwargs
["height"] = unit
.t_pt(float(argdict
["height"]))
847 if argdict
.has_key("clip"):
848 epskwargs
["clip"] = int(argdict
["clip"])
849 self
.actpage
.insert(epsfile
.epsfile(x
, y
, **epskwargs
))
851 raise RuntimeError("unknown PyX special '%s', aborting" % command
)
853 # routines corresponding to the different reader states of the dvi maschine
858 self
.filepos
= file.tell()
859 cmd
= file.readuchar()
862 elif cmd
== _DVI_PRE
:
863 if self
.file.readuchar() != _DVI_VERSION
: raise DVIError
864 num
= file.readuint32()
865 den
= file.readuint32()
866 mag
= file.readuint32()
868 self
.tfmconv
= (25400000.0/num
)*(den
/473628672)/16.0;
870 self
.resolution
= 300.0
871 # self.trueconv = conv in DVIType docu
872 self
.trueconv
= (num
/254000.0)*(self
.resolution
/den
)
873 self
.conv
= self
.trueconv
*(mag
/1000.0)
875 comment
= file.read(file.readuchar())
880 def _read_nopage(self
):
883 self
.filepos
= file.tell()
884 cmd
= file.readuchar()
887 elif cmd
== _DVI_BOP
:
890 print "%d: beginning of page" % self
.filepos
,
891 print file.readuint32()
892 for i
in range(9): file.readuint32()
894 for i
in range(10): file.readuint32()
897 elif cmd
== _DVI_POST
:
898 return _READ_DONE
# we skip the rest
902 def _read_page(self
):
903 self
.pos
= [0, 0, 0, 0, 0, 0]
904 self
.pages
.append(canvas
.canvas())
905 self
.actpage
= self
.pages
[-1]
908 self
.filepos
= file.tell()
909 cmd
= file.readuchar()
912 if cmd
>= _DVI_CHARMIN
and cmd
<= _DVI_CHARMAX
:
914 elif cmd
>= _DVI_SET1234
and cmd
< _DVI_SET1234
+ 4:
915 self
.putchar(file.readint(cmd
- _DVI_SET1234
+ 1))
916 elif cmd
== _DVI_SETRULE
:
917 self
.putrule(file.readint32(), file.readint32())
918 elif cmd
>= _DVI_PUT1234
and cmd
< _DVI_PUT1234
+ 4:
919 self
.putchar(file.readint(cmd
- _DVI_PUT1234
+ 1), inch
=0)
920 elif cmd
== _DVI_PUTRULE
:
921 self
.putrule(file.readint32(), file.readint32(), 0)
922 elif cmd
== _DVI_EOP
:
925 print "%d: eop" % self
.filepos
928 elif cmd
== _DVI_PUSH
:
929 self
.stack
.append(tuple(self
.pos
))
931 print "%d: push" % self
.filepos
932 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
933 (( len(self
.stack
)-1,)+tuple(self
.pos
)))
934 elif cmd
== _DVI_POP
:
936 self
.pos
= list(self
.stack
[-1])
939 print "%d: pop" % self
.filepos
940 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
941 (( len(self
.stack
),)+tuple(self
.pos
)))
942 elif cmd
>= _DVI_RIGHT1234
and cmd
< _DVI_RIGHT1234
+ 4:
944 dh
= file.readint(cmd
- _DVI_RIGHT1234
+ 1, 1)
946 print ("%d: right%d %d h:=%d%+d=%d, hh:=" %
948 cmd
- _DVI_RIGHT1234
+ 1,
952 self
.pos
[_POS_H
]+dh
))
953 self
.pos
[_POS_H
] += dh
957 print ("%d: w0 %d h:=%d%+d=%d, hh:=" %
962 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
963 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
964 elif cmd
>= _DVI_W1234
and cmd
< _DVI_W1234
+ 4:
966 self
.pos
[_POS_W
] = file.readint(cmd
- _DVI_W1234
+ 1, 1)
968 print ("%d: w%d %d h:=%d%+d=%d, hh:=" %
970 cmd
- _DVI_W1234
+ 1,
974 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
975 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
978 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
979 elif cmd
>= _DVI_X1234
and cmd
< _DVI_X1234
+ 4:
981 self
.pos
[_POS_X
] = file.readint(cmd
- _DVI_X1234
+ 1, 1)
982 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
983 elif cmd
>= _DVI_DOWN1234
and cmd
< _DVI_DOWN1234
+ 4:
985 dv
= file.readint(cmd
- _DVI_DOWN1234
+ 1, 1)
987 print ("%d: down%d %d v:=%d%+d=%d, vv:=" %
989 cmd
- _DVI_DOWN1234
+ 1,
993 self
.pos
[_POS_V
]+dv
))
994 self
.pos
[_POS_V
] += dv
998 print ("%d: y0 %d v:=%d%+d=%d, vv:=" %
1003 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1004 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1005 elif cmd
>= _DVI_Y1234
and cmd
< _DVI_Y1234
+ 4:
1007 self
.pos
[_POS_Y
] = file.readint(cmd
- _DVI_Y1234
+ 1, 1)
1009 print ("%d: y%d %d v:=%d%+d=%d, vv:=" %
1011 cmd
- _DVI_Y1234
+ 1,
1015 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1016 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1017 elif cmd
== _DVI_Z0
:
1019 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1020 elif cmd
>= _DVI_Z1234
and cmd
< _DVI_Z1234
+ 4:
1022 self
.pos
[_POS_Z
] = file.readint(cmd
- _DVI_Z1234
+ 1, 1)
1023 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1024 elif cmd
>= _DVI_FNTNUMMIN
and cmd
<= _DVI_FNTNUMMAX
:
1025 self
.usefont(cmd
- _DVI_FNTNUMMIN
)
1026 elif cmd
>= _DVI_FNT1234
and cmd
< _DVI_FNT1234
+ 4:
1027 self
.usefont(file.readint(cmd
- _DVI_FNT1234
+ 1, 1))
1028 elif cmd
>= _DVI_SPECIAL1234
and cmd
< _DVI_SPECIAL1234
+ 4:
1029 self
.special(file.read(file.readint(cmd
- _DVI_SPECIAL1234
+ 1)))
1030 elif cmd
>= _DVI_FNTDEF1234
and cmd
< _DVI_FNTDEF1234
+ 4:
1031 if cmd
== _DVI_FNTDEF1234
:
1032 num
=file.readuchar()
1033 elif cmd
== _DVI_FNTDEF1234
+1:
1034 num
=file.readuint16()
1035 elif cmd
== _DVI_FNTDEF1234
+2:
1036 num
=file.readuint24()
1037 elif cmd
== _DVI_FNTDEF1234
+3:
1038 # Cool, here we have according to docu a signed int. Why?
1039 num
= file.readint32()
1040 self
.definefont(cmd
-_DVI_FNTDEF1234
+1,
1045 file.read(file.readuchar()+file.readuchar()))
1046 else: raise DVIError
1049 """ reads and parses dvi file
1051 This routine reads the dvi file and generates a list
1052 of pages in self.pages. Each page consists itself of
1053 a list of PSCommands equivalent to the content of
1054 the dvi file. Furthermore, the list of used fonts
1055 can be extracted from the array self.fonts.
1058 # XXX max number of fonts
1059 self
.fonts
= [None for i
in range(64)]
1060 self
.activefont
= None
1064 # here goes the result, for each page one list.
1067 # pointer to currently active page
1070 # currently active output: position and content
1071 self
.actoutstart
= None
1072 self
.actoutstring
= ""
1074 self
.file = binfile(self
.filename
, "rb")
1076 # currently read byte in file (for debugging output)
1079 # start up reading process
1081 while state
!=_READ_DONE
:
1082 if state
== _READ_PRE
:
1083 state
= self
._read
_pre
()
1084 elif state
== _READ_NOPAGE
:
1085 state
= self
._read
_nopage
()
1086 elif state
== _READ_PAGE
:
1087 state
= self
._read
_page
()
1089 raise DVIError
# unexpected reader state, should not happen
1092 def prolog(self
, page
): # TODO: AW inserted this page argument -> should return the prolog needed for that page only!
1093 """ return prolog corresponding to contents of dvi file """
1094 # XXX replace this by prolog method in _selectfont
1095 result
= [_ReEncodeFont
]
1096 for font
in self
.fonts
:
1098 result
.append(prolog
.fontdefinition(font
.getbasepsname(),
1100 font
.getencodingfile(),
1102 if font
.getencoding():
1103 result
.append(prolog
.fontencoding(font
.getencoding(), font
.getencodingfile()))
1104 result
.append(prolog
.fontreencoding(font
.getpsname(), font
.getbasepsname(), font
.getencoding()))
1105 result
.extend(self
.pages
[page
-1].prolog())
1108 def write(self
, file, page
):
1109 """write PostScript output for page into file"""
1110 # XXX: remove this method by return canvas to TexRunner
1112 print "dvifile(\"%s\").write() for page %s called" % (self
.filename
, page
)
1113 self
.pages
[page
-1].write(file)
1116 ###############################################################################
1118 # - please don't get confused:
1119 # - there is a texmessage (and a texmessageparsed) attribute within the
1120 # texrunner; it contains TeX/LaTeX response from the last command execution
1121 # - instances of classes derived from the class texmessage are used to
1122 # parse the TeX/LaTeX response as it is stored in the texmessageparsed
1123 # attribute of a texrunner instance
1124 # - the multiple usage of the name texmessage might be removed in the future
1125 # - texmessage instances should implement _Itexmessage
1126 ###############################################################################
1128 class TexResultError(Exception):
1129 """specialized texrunner exception class
1130 - it is raised by texmessage instances, when a texmessage indicates an error
1131 - it is raised by the texrunner itself, whenever there is a texmessage left
1132 after all parsing of this message (by texmessage instances)"""
1134 def __init__(self
, description
, texrunner
):
1135 self
.description
= description
1136 self
.texrunner
= texrunner
1139 """prints a detailed report about the problem
1140 - the verbose level is controlled by texrunner.errordebug"""
1141 if self
.texrunner
.errordebug
>= 2:
1142 return ("%s\n" % self
.description
+
1143 "The expression passed to TeX was:\n"
1144 " %s\n" % self
.texrunner
.expr
.replace("\n", "\n ").rstrip() +
1145 "The return message from TeX was:\n"
1146 " %s\n" % self
.texrunner
.texmessage
.replace("\n", "\n ").rstrip() +
1147 "After parsing this message, the following was left:\n"
1148 " %s" % self
.texrunner
.texmessageparsed
.replace("\n", "\n ").rstrip())
1149 elif self
.texrunner
.errordebug
== 1:
1150 firstlines
= self
.texrunner
.texmessageparsed
.split("\n")
1151 if len(firstlines
) > 5:
1152 firstlines
= firstlines
[:5] + ["(cut after 5 lines, increase errordebug for more output)"]
1153 return ("%s\n" % self
.description
+
1154 "The expression passed to TeX was:\n"
1155 " %s\n" % self
.texrunner
.expr
.replace("\n", "\n ").rstrip() +
1156 "After parsing the return message from TeX, the following was left:\n" +
1157 reduce(lambda x
, y
: "%s %s\n" % (x
,y
), firstlines
, "").rstrip())
1159 return self
.description
1162 class TexResultWarning(TexResultError
):
1163 """as above, but with different handling of the exception
1164 - when this exception is raised by a texmessage instance,
1165 the information just get reported and the execution continues"""
1170 """validates/invalidates TeX/LaTeX response"""
1172 def check(self
, texrunner
):
1173 """check a Tex/LaTeX response and respond appropriate
1174 - read the texrunners texmessageparsed attribute
1175 - if there is an problem found, raise an appropriate
1176 exception (TexResultError or TexResultWarning)
1177 - remove any valid and identified TeX/LaTeX response
1178 from the texrunners texmessageparsed attribute
1179 -> finally, there should be nothing left in there,
1180 otherwise it is interpreted as an error"""
1183 class texmessage
: pass
1186 class _texmessagestart(texmessage
):
1187 """validates TeX/LaTeX startup"""
1189 __implements__
= _Itexmessage
1191 startpattern
= re
.compile(r
"This is [0-9a-zA-Z\s_]*TeX")
1193 def check(self
, texrunner
):
1194 m
= self
.startpattern
.search(texrunner
.texmessageparsed
)
1196 raise TexResultError("TeX startup failed", texrunner
)
1197 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[m
.end():]
1199 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.split("%s.tex" % texrunner
.texfilename
, 1)[1]
1200 except (IndexError, ValueError):
1201 raise TexResultError("TeX running startup file failed", texrunner
)
1203 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.split("*! Undefined control sequence.\n<*> \\raiseerror\n %\n", 1)[1]
1204 except (IndexError, ValueError):
1205 raise TexResultError("TeX scrollmode check failed", texrunner
)
1208 class _texmessagenoaux(texmessage
):
1209 """allows for LaTeXs no-aux-file warning"""
1211 __implements__
= _Itexmessage
1213 def check(self
, texrunner
):
1215 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s.aux." % texrunner
.texfilename
, 1)
1216 texrunner
.texmessageparsed
= s1
+ s2
1217 except (IndexError, ValueError):
1219 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s%s%s.aux." % (os
.curdir
,
1221 texrunner
.texfilename
), 1)
1222 texrunner
.texmessageparsed
= s1
+ s2
1223 except (IndexError, ValueError):
1227 class _texmessageinputmarker(texmessage
):
1228 """validates the PyXInputMarker"""
1230 __implements__
= _Itexmessage
1232 def check(self
, texrunner
):
1234 s1
, s2
= texrunner
.texmessageparsed
.split("PyXInputMarker:executeid=%s:" % texrunner
.executeid
, 1)
1235 texrunner
.texmessageparsed
= s1
+ s2
1236 except (IndexError, ValueError):
1237 raise TexResultError("PyXInputMarker expected", texrunner
)
1240 class _texmessagepyxbox(texmessage
):
1241 """validates the PyXBox output"""
1243 __implements__
= _Itexmessage
1245 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:")
1247 def check(self
, texrunner
):
1248 m
= self
.pattern
.search(texrunner
.texmessageparsed
)
1249 if m
and m
.group("page") == str(texrunner
.page
):
1250 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
1252 raise TexResultError("PyXBox expected", texrunner
)
1255 class _texmessagepyxpageout(texmessage
):
1256 """validates the dvi shipout message (writing a page to the dvi file)"""
1258 __implements__
= _Itexmessage
1260 def check(self
, texrunner
):
1262 s1
, s2
= texrunner
.texmessageparsed
.split("[80.121.88.%s]" % texrunner
.page
, 1)
1263 texrunner
.texmessageparsed
= s1
+ s2
1264 except (IndexError, ValueError):
1265 raise TexResultError("PyXPageOutMarker expected", texrunner
)
1268 class _texmessagetexend(texmessage
):
1269 """validates TeX/LaTeX finish"""
1271 __implements__
= _Itexmessage
1273 def check(self
, texrunner
):
1275 s1
, s2
= texrunner
.texmessageparsed
.split("(%s.aux)" % texrunner
.texfilename
, 1)
1276 texrunner
.texmessageparsed
= s1
+ s2
1277 except (IndexError, ValueError):
1279 s1
, s2
= texrunner
.texmessageparsed
.split("(%s%s%s.aux)" % (os
.curdir
,
1281 texrunner
.texfilename
), 1)
1282 texrunner
.texmessageparsed
= s1
+ s2
1283 except (IndexError, ValueError):
1286 s1
, s2
= texrunner
.texmessageparsed
.split("(see the transcript file for additional information)", 1)
1287 texrunner
.texmessageparsed
= s1
+ s2
1288 except (IndexError, ValueError):
1290 dvipattern
= re
.compile(r
"Output written on %s\.dvi \((?P<page>\d+) pages?, \d+ bytes\)\." % texrunner
.texfilename
)
1291 m
= dvipattern
.search(texrunner
.texmessageparsed
)
1294 raise TexResultError("TeX dvifile messages expected", texrunner
)
1295 if m
.group("page") != str(texrunner
.page
):
1296 raise TexResultError("wrong number of pages reported", texrunner
)
1297 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
1300 s1
, s2
= texrunner
.texmessageparsed
.split("No pages of output.", 1)
1301 texrunner
.texmessageparsed
= s1
+ s2
1302 except (IndexError, ValueError):
1303 raise TexResultError("no dvifile expected")
1305 s1
, s2
= texrunner
.texmessageparsed
.split("Transcript written on %s.log." % texrunner
.texfilename
, 1)
1306 texrunner
.texmessageparsed
= s1
+ s2
1307 except (IndexError, ValueError):
1308 raise TexResultError("TeX logfile message expected")
1311 class _texmessageemptylines(texmessage
):
1312 """validates empty and "*-only" (TeX/LaTeX input marker in interactive mode) lines"""
1314 __implements__
= _Itexmessage
1316 pattern
= re
.compile(r
"^\*?\n", re
.M
)
1318 def check(self
, texrunner
):
1319 m
= self
.pattern
.search(texrunner
.texmessageparsed
)
1321 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
1322 m
= self
.pattern
.search(texrunner
.texmessageparsed
)
1325 class _texmessageload(texmessage
):
1326 """validates inclusion of arbitrary files
1327 - the matched pattern is "(<filename> <arbitrary other stuff>)", where
1328 <fielname> is a readable file and other stuff can be anything
1329 - "(" and ")" must be used consistent (otherwise this validator just does nothing)
1330 - this is not always wanted, but we just assume that file inclusion is fine"""
1332 __implements__
= _Itexmessage
1334 pattern
= re
.compile(r
"\((?P<filename>[^()\s\n]+)[^()]*\)")
1336 def baselevels(self
, s
, maxlevel
=1, brackets
="()"):
1337 """strip parts of a string above a given bracket level
1338 - return a modified (some parts might be removed) version of the string s
1339 where all parts inside brackets with level higher than maxlevel are
1341 - if brackets do not match (number of left and right brackets is wrong
1342 or at some points there were more right brackets than left brackets)
1343 just return the unmodified string"""
1348 if c
== brackets
[0]:
1350 if level
> highestlevel
:
1351 highestlevel
= level
1352 if level
<= maxlevel
:
1354 if c
== brackets
[1]:
1356 if level
== 0 and highestlevel
> 0:
1359 def check(self
, texrunner
):
1360 lowestbracketlevel
= self
.baselevels(texrunner
.texmessageparsed
)
1361 if lowestbracketlevel
is not None:
1362 m
= self
.pattern
.search(lowestbracketlevel
)
1364 if os
.access(m
.group("filename"), os
.R_OK
):
1365 lowestbracketlevel
= lowestbracketlevel
[:m
.start()] + lowestbracketlevel
[m
.end():]
1368 m
= self
.pattern
.search(lowestbracketlevel
)
1370 texrunner
.texmessageparsed
= lowestbracketlevel
1373 class _texmessageloadfd(_texmessageload
):
1374 """validates the inclusion of font description files (fd-files)
1375 - works like _texmessageload
1376 - filename must end with .fd and no further text is allowed"""
1378 pattern
= re
.compile(r
"\((?P<filename>[^)]+.fd)\)")
1381 class _texmessagegraphicsload(_texmessageload
):
1382 """validates the inclusion of files as the graphics packages writes it
1383 - works like _texmessageload, but using "<" and ">" as delimiters
1384 - filename must end with .eps and no further text is allowed"""
1386 pattern
= re
.compile(r
"<(?P<filename>[^>]+.eps)>")
1388 def baselevels(self
, s
, brackets
="<>", **args
):
1389 return _texmessageload
.baselevels(self
, s
, brackets
=brackets
, **args
)
1392 #class _texmessagepdfmapload(_texmessageload):
1393 # """validates the inclusion of files as the graphics packages writes it
1394 # - works like _texmessageload, but using "{" and "}" as delimiters
1395 # - filename must end with .map and no further text is allowed"""
1397 # pattern = re.compile(r"{(?P<filename>[^}]+.map)}")
1399 # def baselevels(self, s, brackets="{}", **args):
1400 # return _texmessageload.baselevels(self, s, brackets=brackets, **args)
1403 class _texmessageignore(_texmessageload
):
1404 """validates any TeX/LaTeX response
1405 - this might be used, when the expression is ok, but no suitable texmessage
1407 - PLEASE: - consider writing suitable tex message parsers
1408 - share your ideas/problems/solutions with others (use the PyX mailing lists)"""
1410 __implements__
= _Itexmessage
1412 def check(self
, texrunner
):
1413 texrunner
.texmessageparsed
= ""
1416 texmessage
.start
= _texmessagestart()
1417 texmessage
.noaux
= _texmessagenoaux()
1418 texmessage
.inputmarker
= _texmessageinputmarker()
1419 texmessage
.pyxbox
= _texmessagepyxbox()
1420 texmessage
.pyxpageout
= _texmessagepyxpageout()
1421 texmessage
.texend
= _texmessagetexend()
1422 texmessage
.emptylines
= _texmessageemptylines()
1423 texmessage
.load
= _texmessageload()
1424 texmessage
.loadfd
= _texmessageloadfd()
1425 texmessage
.graphicsload
= _texmessagegraphicsload()
1426 texmessage
.ignore
= _texmessageignore()
1429 ###############################################################################
1431 # - texsettings are used to modify a TeX/LaTeX expression
1432 # to fit the users need
1433 # - texsettings have an order attribute (id), because the order is usually
1434 # important (e.g. you can not change the fontsize in mathmode in LaTeX)
1435 # - lower id's get applied later (are more outside -> mathmode has a higher
1437 # - order attributes are used to exclude complementary settings (with the
1439 # - texsettings might (in rare cases) depend on each other (e.g. parbox and
1441 ###############################################################################
1445 - modifies a TeX/LaTeX expression"""
1448 """order attribute for TeX settings
1449 - higher id's will be applied first (most inside)"""
1452 """marks exclusive effect of the setting
1453 - when set, settings with this id exclude each other
1454 - when unset, settings with this id do not exclude each other"""
1456 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1457 """modifies the TeX/LaTeX expression
1458 - expr is the original expression
1459 - the return value is the modified expression
1460 - texsettings contains a list of all texsettings (in case a tex setting
1461 depends on another texsetting)
1462 - texrunner contains the texrunner in case the texsetting depends
1465 def __cmp__(self
, other
):
1466 """compare texsetting with other
1467 - other is a texsetting as well
1468 - performs an id comparison (NOTE: higher id's come first!!!)"""
1471 # preamble settings for texsetting macros
1472 _texsettingpreamble
= ""
1478 def __cmp__(self
, other
):
1479 return -cmp(self
.id, other
.id) # note the sign!!!
1482 class halign(_texsetting
):
1483 """horizontal alignment
1484 the left/right splitting is performed within the PyXBox routine"""
1486 __implements__
= _Itexsetting
1490 def __init__(self
, hratio
):
1491 self
.hratio
= hratio
1493 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1494 return r
"\gdef\PyXHAlign{%.5f}%s" % (self
.hratio
, expr
)
1496 halign
.left
= halign(0)
1497 halign
.center
= halign(0.5)
1498 halign
.right
= halign(1)
1501 _texsettingpreamble
+= "\\newbox\\PyXBoxVAlign%\n\\newdimen\PyXDimenVAlign%\n"
1503 class valign(_texsetting
):
1504 "vertical alignment"
1509 class _valigntop(valign
):
1511 __implements__
= _Itexsetting
1513 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1514 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\lower\ht\PyXBoxVAlign\box\PyXBoxVAlign" % expr
1517 class _valignmiddle(valign
):
1519 __implements__
= _Itexsetting
1521 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1522 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\PyXDimenVAlign=0.5\ht\PyXBoxVAlign\advance\PyXDimenVAlign by -0.5\dp\PyXBoxVAlign\lower\PyXDimenVAlign\box\PyXBoxVAlign" % expr
1525 class _valignbottom(valign
):
1527 __implements__
= _Itexsetting
1529 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1530 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\raise\dp\PyXBoxVAlign\box\PyXBoxVAlign" % expr
1533 class _valignbaseline(valign
):
1535 __implements__
= _Itexsetting
1537 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1538 for texsetting
in texsettings
:
1539 if isinstance(texsetting
, parbox
):
1540 raise RuntimeError("valign.baseline: specify top/middle/bottom baseline for parbox")
1544 class _valignxxxbaseline(valign
):
1546 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1547 for texsetting
in texsettings
:
1548 if isinstance(texsetting
, parbox
):
1551 raise RuntimeError(self
.noparboxmessage
)
1555 class _valigntopbaseline(_valignxxxbaseline
):
1557 __implements__
= _Itexsetting
1559 noparboxmessage
= "valign.topbaseline: no parbox defined"
1562 class _valignmiddlebaseline(_valignxxxbaseline
):
1564 __implements__
= _Itexsetting
1566 noparboxmessage
= "valign.middlebaseline: no parbox defined"
1569 class _valignbottombaseline(_valignxxxbaseline
):
1571 __implements__
= _Itexsetting
1573 noparboxmessage
= "valign.bottombaseline: no parbox defined"
1576 valign
.top
= _valigntop()
1577 valign
.middle
= _valignmiddle()
1578 valign
.center
= valign
.middle
1579 valign
.bottom
= _valignbottom()
1580 valign
.baseline
= _valignbaseline()
1581 valign
.topbaseline
= _valigntopbaseline()
1582 valign
.middlebaseline
= _valignmiddlebaseline()
1583 valign
.centerbaseline
= valign
.middlebaseline
1584 valign
.bottombaseline
= _valignbottombaseline()
1587 _texsettingpreamble
+= "\\newbox\\PyXBoxVBox%\n\\newdimen\PyXDimenVBox%\n"
1590 class _parbox(_texsetting
):
1591 "goes into the vertical mode"
1593 __implements__
= _Itexsetting
1597 def __init__(self
, width
):
1600 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1602 for texsetting
in texsettings
:
1603 if isinstance(texsetting
, valign
):
1604 if (not isinstance(texsetting
, _valigntop
) and
1605 not isinstance(texsetting
, _valignmiddle
) and
1606 not isinstance(texsetting
, _valignbottom
) and
1607 not isinstance(texsetting
, _valigntopbaseline
)):
1608 if isinstance(texsetting
, _valignmiddlebaseline
):
1610 elif isinstance(texsetting
, _valignbottombaseline
):
1613 raise RuntimeError("parbox couldn'd identify the valign instance")
1614 if boxkind
== "vcenter":
1615 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
)
1617 return r
"\linewidth%.5ftruept\%s{\hsize\linewidth{%s}}" % (self
.width
* 72.27 / 72, boxkind
, expr
)
1620 class parbox(_parbox
):
1622 def __init__(self
, width
):
1623 _parbox
.__init
__(self
, unit
.topt(width
))
1626 class vshift(_texsetting
):
1633 class _vshiftchar(vshift
):
1634 "vertical down shift by a fraction of a character height"
1636 def __init__(self
, lowerratio
, heightstr
="0"):
1637 self
.lowerratio
= lowerratio
1638 self
.heightstr
= heightstr
1640 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1641 return r
"\setbox0\hbox{{%s}}\lower%.5f\ht0\hbox{{%s}}" % (self
.heightstr
, self
.lowerratio
, expr
)
1644 class _vshiftmathaxis(vshift
):
1645 "vertical down shift by the height of the math axis"
1647 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1648 return r
"\setbox0\hbox{$\vcenter{\vrule width0pt}$}\lower\ht0\hbox{{%s}}" % expr
1651 vshift
.char
= _vshiftchar
1652 vshift
.bottomzero
= vshift
.char(0)
1653 vshift
.middlezero
= vshift
.char(0.5)
1654 vshift
.centerzero
= vshift
.middlezero
1655 vshift
.topzero
= vshift
.char(1)
1656 vshift
.mathaxis
= _vshiftmathaxis()
1659 class _mathmode(_texsetting
):
1662 __implements__
= _Itexsetting
1666 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1667 return r
"$\displaystyle{%s}$" % expr
1669 mathmode
= _mathmode()
1672 defaultsizelist
= ["normalsize", "large", "Large", "LARGE", "huge", "Huge", None, "tiny", "scriptsize", "footnotesize", "small"]
1674 class size(_texsetting
):
1677 __implements__
= _Itexsetting
1681 def __init__(self
, expr
, sizelist
=defaultsizelist
):
1682 if helper
.isinteger(expr
):
1683 if expr
>= 0 and expr
< sizelist
.index(None):
1684 self
.size
= sizelist
[expr
]
1685 elif expr
< 0 and expr
+ len(sizelist
) > sizelist
.index(None):
1686 self
.size
= sizelist
[expr
]
1688 raise IndexError("index out of sizelist range")
1692 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1693 return r
"\%s{%s}" % (self
.size
, expr
)
1695 for s
in defaultsizelist
:
1697 size
.__dict
__[s
] = size(s
)
1700 ###############################################################################
1702 ###############################################################################
1705 class _readpipe(threading
.Thread
):
1706 """threaded reader of TeX/LaTeX output
1707 - sets an event, when a specific string in the programs output is found
1708 - sets an event, when the terminal ends"""
1710 def __init__(self
, pipe
, expectqueue
, gotevent
, gotqueue
, quitevent
):
1711 """initialize the reader
1712 - pipe: file to be read from
1713 - expectqueue: keeps the next InputMarker to be wait for
1714 - gotevent: the "got InputMarker" event
1715 - gotqueue: a queue containing the lines recieved from TeX/LaTeX
1716 - quitevent: the "end of terminal" event"""
1717 threading
.Thread
.__init
__(self
)
1718 self
.setDaemon(1) # don't care if the output might not be finished (nevertheless, it shouldn't happen)
1720 self
.expectqueue
= expectqueue
1721 self
.gotevent
= gotevent
1722 self
.gotqueue
= gotqueue
1723 self
.quitevent
= quitevent
1728 """thread routine"""
1729 read
= self
.pipe
.readline() # read, what comes in
1731 self
.expect
= self
.expectqueue
.get_nowait() # read, what should be expected
1735 # universal EOL handling (convert everything into unix like EOLs)
1736 read
.replace("\r", "")
1737 if not len(read
) or read
[-1] != "\n":
1739 self
.gotqueue
.put(read
) # report, whats readed
1740 if self
.expect
is not None and read
.find(self
.expect
) != -1:
1741 self
.gotevent
.set() # raise the got event, when the output was expected (XXX: within a single line)
1742 read
= self
.pipe
.readline() # read again
1744 self
.expect
= self
.expectqueue
.get_nowait()
1749 if self
.expect
is not None and self
.expect
.find("PyXInputMarker") != -1:
1750 raise RuntimeError("TeX/LaTeX finished unexpectedly")
1751 self
.quitevent
.set()
1755 class _textbox(box
._rect
, base
.PSCmd
):
1756 """basically a box.rect, but it contains a text created by the texrunner
1757 - texrunner._text and texrunner.text return such an object
1758 - _textbox instances can be inserted into a canvas
1759 - the output is contained in a page of the dvifile available thru the texrunner"""
1761 def __init__(self
, x
, y
, left
, right
, height
, depth
, texrunner
, dvinumber
, page
, *styles
):
1762 self
.texttrafo
= trafo
._translate
(x
, y
)
1763 box
._rect
.__init
__(self
, x
- left
, y
- depth
,
1764 left
+ right
, depth
+ height
,
1765 abscenter
= (left
, depth
))
1766 self
.texrunner
= texrunner
1767 self
.dvinumber
= dvinumber
1769 self
.styles
= styles
1771 def transform(self
, *trafos
):
1772 box
._rect
.transform(self
, *trafos
)
1773 for trafo
in trafos
:
1774 self
.texttrafo
= trafo
* self
.texttrafo
1778 for cmd
in self
.styles
:
1779 result
.extend(cmd
.prolog())
1780 return result
+ self
.texrunner
.prolog(self
.dvinumber
, self
.page
)
1782 def write(self
, file):
1783 canvas
._gsave
().write(file) # XXX: canvas?, constructor call needed?
1784 self
.texttrafo
.write(file)
1785 for style
in self
.styles
:
1787 self
.texrunner
.write(file, self
.dvinumber
, self
.page
)
1788 canvas
._grestore
().write(file)
1792 class textbox(_textbox
):
1794 def __init__(self
, x
, y
, left
, right
, height
, depth
, texrunner
, dvinumber
, page
, *styles
):
1795 _textbox
.__init
__(self
, unit
.topt(x
), unit
.topt(y
), unit
.topt(left
), unit
.topt(right
),
1796 unit
.topt(height
), unit
.topt(depth
), texrunner
, dvinumber
, page
, *styles
)
1799 def _cleantmp(texrunner
):
1800 """get rid of temporary files
1801 - function to be registered by atexit
1802 - files contained in usefiles are kept"""
1803 if texrunner
.texruns
: # cleanup while TeX is still running?
1804 texrunner
.texruns
= 0
1805 texrunner
.texdone
= 1
1806 texrunner
.expectqueue
.put_nowait(None) # do not expect any output anymore
1807 texrunner
.texinput
.close() # close the input queue and
1808 texrunner
.quitevent
.wait(texrunner
.waitfortex
) # wait for finish of the output
1809 if not texrunner
.quitevent
.isSet(): return # didn't got a quit from TeX -> we can't do much more
1810 for usefile
in texrunner
.usefiles
:
1811 extpos
= usefile
.rfind(".")
1813 os
.rename(texrunner
.texfilename
+ usefile
[extpos
:], usefile
)
1816 for file in glob
.glob("%s.*" % texrunner
.texfilename
):
1823 # texrunner state exceptions
1824 class TexRunsError(Exception): pass
1825 class TexDoneError(Exception): pass
1826 class TexNotInPreambleModeError(Exception): pass
1830 """TeX/LaTeX interface
1831 - runs TeX/LaTeX expressions instantly
1832 - checks TeX/LaTeX response
1833 - the instance variable texmessage stores the last TeX
1834 response as a string
1835 - the instance variable texmessageparsed stores a parsed
1836 version of texmessage; it should be empty after
1837 texmessage.check was called, otherwise a TexResultError
1839 - the instance variable errordebug controls the verbose
1840 level of TexResultError"""
1842 def __init__(self
, mode
="tex",
1853 texmessagestart
=texmessage
.start
,
1854 texmessagedocclass
=texmessage
.load
,
1855 texmessagebegindoc
=(texmessage
.load
, texmessage
.noaux
),
1856 texmessageend
=texmessage
.texend
,
1857 texmessagedefaultpreamble
=texmessage
.load
,
1858 texmessagedefaultrun
=texmessage
.loadfd
):
1860 if mode
!= "tex" and mode
!= "latex":
1861 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
1864 self
.docclass
= docclass
1865 self
.docopt
= docopt
1866 self
.usefiles
= helper
.ensurelist(usefiles
)
1867 self
.waitfortex
= waitfortex
1868 if texdebug
is not None:
1869 if texdebug
[-4:] == ".tex":
1870 self
.texdebug
= open(texdebug
, "w")
1872 self
.texdebug
= open("%s.tex" % texdebug
, "w")
1874 self
.texdebug
= None
1875 self
.dvidebug
= dvidebug
1876 self
.errordebug
= errordebug
1877 self
.dvicopy
= dvicopy
1878 self
.pyxgraphics
= pyxgraphics
1879 texmessagestart
= helper
.ensuresequence(texmessagestart
)
1880 helper
.checkattr(texmessagestart
, allowmulti
=(texmessage
,))
1881 self
.texmessagestart
= texmessagestart
1882 texmessagedocclass
= helper
.ensuresequence(texmessagedocclass
)
1883 helper
.checkattr(texmessagedocclass
, allowmulti
=(texmessage
,))
1884 self
.texmessagedocclass
= texmessagedocclass
1885 texmessagebegindoc
= helper
.ensuresequence(texmessagebegindoc
)
1886 helper
.checkattr(texmessagebegindoc
, allowmulti
=(texmessage
,))
1887 self
.texmessagebegindoc
= texmessagebegindoc
1888 texmessageend
= helper
.ensuresequence(texmessageend
)
1889 helper
.checkattr(texmessageend
, allowmulti
=(texmessage
,))
1890 self
.texmessageend
= texmessageend
1891 texmessagedefaultpreamble
= helper
.ensuresequence(texmessagedefaultpreamble
)
1892 helper
.checkattr(texmessagedefaultpreamble
, allowmulti
=(texmessage
,))
1893 self
.texmessagedefaultpreamble
= texmessagedefaultpreamble
1894 texmessagedefaultrun
= helper
.ensuresequence(texmessagedefaultrun
)
1895 helper
.checkattr(texmessagedefaultrun
, allowmulti
=(texmessage
,))
1896 self
.texmessagedefaultrun
= texmessagedefaultrun
1900 self
.preamblemode
= 1
1906 savetempdir
= tempfile
.tempdir
1907 tempfile
.tempdir
= os
.curdir
1908 self
.texfilename
= os
.path
.basename(tempfile
.mktemp())
1909 tempfile
.tempdir
= savetempdir
1911 def execute(self
, expr
, *checks
):
1912 """executes expr within TeX/LaTeX
1913 - if self.texruns is not yet set, TeX/LaTeX is initialized,
1914 self.texruns is set and self.preamblemode is set
1915 - the method must not be called, when self.texdone is already set
1916 - expr should be a string or None
1917 - when expr is None, TeX/LaTeX is stopped, self.texruns is unset and
1918 while self.texdone becomes set
1919 - when self.preamblemode is set, the expr is passed directly to TeX/LaTeX
1920 - when self.preamblemode is unset, the expr is passed to \ProcessPyXBox
1922 if not self
.texruns
:
1923 if self
.texdebug
is not None:
1924 self
.texdebug
.write("%% PyX %s texdebug file\n" % version
.version
)
1925 self
.texdebug
.write("%% mode: %s\n" % self
.mode
)
1926 self
.texdebug
.write("%% date: %s\n" % time
.asctime(time
.localtime(time
.time())))
1927 for usefile
in self
.usefiles
:
1928 extpos
= usefile
.rfind(".")
1930 os
.rename(usefile
, self
.texfilename
+ usefile
[extpos
:])
1933 texfile
= open("%s.tex" % self
.texfilename
, "w") # start with filename -> creates dvi file with that name
1934 texfile
.write("\\relax\n")
1937 self
.texinput
, self
.texoutput
= os
.popen4("%s %s" % (self
.mode
, self
.texfilename
), "t", 0)
1939 # XXX: workaround for MS Windows (bufsize = 0 makes trouble!?)
1940 self
.texinput
, self
.texoutput
= os
.popen4("%s %s" % (self
.mode
, self
.texfilename
), "t")
1941 atexit
.register(_cleantmp
, self
)
1942 self
.expectqueue
= Queue
.Queue(1) # allow for a single entry only -> keeps the next InputMarker to be wait for
1943 self
.gotevent
= threading
.Event() # keeps the got inputmarker event
1944 self
.gotqueue
= Queue
.Queue(0) # allow arbitrary number of entries
1945 self
.quitevent
= threading
.Event() # keeps for end of terminal event
1946 self
.readoutput
= _readpipe(self
.texoutput
, self
.expectqueue
, self
.gotevent
, self
.gotqueue
, self
.quitevent
)
1948 oldpreamblemode
= self
.preamblemode
1949 self
.preamblemode
= 1
1950 self
.execute("\\scrollmode\n\\raiseerror%\n" # switch to and check scrollmode
1951 "\\def\\PyX{P\\kern-.3em\\lower.5ex\hbox{Y}\kern-.18em X}%\n" # just the PyX Logo
1952 "\\gdef\\PyXHAlign{0}%\n" # global PyXHAlign (0.0-1.0) for the horizontal alignment, default to 0
1953 "\\newbox\\PyXBox%\n" # PyXBox will contain the output
1954 "\\newbox\\PyXBoxHAligned%\n" # PyXBox will contain the horizontal aligned output
1955 "\\newdimen\\PyXDimenHAlignLT%\n" # PyXDimenHAlignLT/RT will contain the left/right extent
1956 "\\newdimen\\PyXDimenHAlignRT%\n" +
1957 _texsettingpreamble
+ # insert preambles for texsetting macros
1958 "\\long\\def\\ProcessPyXBox#1#2{%\n" # the ProcessPyXBox definition (#1 is expr, #2 is page number)
1959 "\\setbox\\PyXBox=\\hbox{{#1}}%\n" # push expression into PyXBox
1960 "\\PyXDimenHAlignLT=\\PyXHAlign\\wd\\PyXBox%\n" # calculate the left/right extent
1961 "\\PyXDimenHAlignRT=\\wd\\PyXBox%\n"
1962 "\\advance\\PyXDimenHAlignRT by -\\PyXDimenHAlignLT%\n"
1963 "\\gdef\\PyXHAlign{0}%\n" # reset the PyXHAlign to the default 0
1964 "\\immediate\\write16{PyXBox:page=#2," # write page and extents of this box to stdout
1965 "lt=\\the\\PyXDimenHAlignLT,"
1966 "rt=\\the\\PyXDimenHAlignRT,"
1967 "ht=\\the\\ht\\PyXBox,"
1968 "dp=\\the\\dp\\PyXBox:}%\n"
1969 "\\setbox\\PyXBoxHAligned=\\hbox{\\kern-\\PyXDimenHAlignLT\\box\\PyXBox}%\n" # align horizontally
1970 "\\ht\\PyXBoxHAligned0pt%\n" # baseline alignment (hight to zero)
1971 "{\\count0=80\\count1=121\\count2=88\\count3=#2\\shipout\\box\\PyXBoxHAligned}}%\n" # shipout PyXBox to Page 80.121.88.<page number>
1972 "\\def\\PyXInput#1{\\immediate\\write16{PyXInputMarker:executeid=#1:}}", # write PyXInputMarker to stdout
1973 *self
.texmessagestart
)
1974 os
.remove("%s.tex" % self
.texfilename
)
1975 if self
.mode
== "tex":
1976 if len(self
.lfs
) > 4 and self
.lfs
[-4:] == ".lfs":
1979 lfsname
= "%s.lfs" % self
.lfs
1980 for fulllfsname
in [lfsname
,
1981 os
.path
.join(sys
.prefix
, "share", "pyx", lfsname
),
1982 os
.path
.join(os
.path
.dirname(__file__
), "lfs", lfsname
)]:
1984 lfsdef
= open(fulllfsname
, "r").read()
1989 allfiles
= (glob
.glob("*.lfs") +
1990 glob
.glob(os
.path
.join(sys
.prefix
, "share", "pyx", "*.lfs")) +
1991 glob
.glob(os
.path
.join(os
.path
.dirname(__file__
), "lfs", "*.lfs")))
1992 lfsnames
= [os
.path
.basename(x
)[:-4] for x
in allfiles
]
1994 raise IOError("file '%s' not found. Available latex font sizes: %s" % (lfsname
, lfsnames
))
1995 self
.execute(lfsdef
)
1996 self
.execute("\\normalsize%\n")
1997 self
.execute("\\newdimen\\linewidth%\n")
1998 elif self
.mode
== "latex":
1999 if self
.pyxgraphics
:
2000 for pyxdef
in ["pyx.def",
2001 os
.path
.join(sys
.prefix
, "share", "pyx", "pyx.def"),
2002 os
.path
.join(os
.path
.dirname(__file__
), "..", "contrib", "pyx.def")]:
2003 if os
.path
.isfile(pyxdef
):
2006 IOError("could not find 'pyx.def'")
2007 pyxdef
= os
.path
.abspath(pyxdef
).replace(os
.sep
, "/")
2008 self
.execute("\\makeatletter%\n"
2009 "\\let\\saveProcessOptions=\\ProcessOptions%\n"
2010 "\\def\\ProcessOptions{%\n"
2011 "\\saveProcessOptions%\n"
2012 "\\def\\Gin@driver{" + pyxdef
+ "}%\n"
2013 "\\def\\c@lor@namefile{dvipsnam.def}}%\n"
2015 if self
.docopt
is not None:
2016 self
.execute("\\documentclass[%s]{%s}" % (self
.docopt
, self
.docclass
), *self
.texmessagedocclass
)
2018 self
.execute("\\documentclass{%s}" % self
.docclass
, *self
.texmessagedocclass
)
2019 self
.preamblemode
= oldpreamblemode
2021 if expr
is not None: # TeX/LaTeX should process expr
2022 self
.expectqueue
.put_nowait("PyXInputMarker:executeid=%i:" % self
.executeid
)
2023 if self
.preamblemode
:
2024 self
.expr
= ("%s%%\n" % expr
+
2025 "\\PyXInput{%i}%%\n" % self
.executeid
)
2028 self
.expr
= ("\\ProcessPyXBox{%s%%\n}{%i}%%\n" % (expr
, self
.page
) +
2029 "\\PyXInput{%i}%%\n" % self
.executeid
)
2030 else: # TeX/LaTeX should be finished
2031 self
.expectqueue
.put_nowait("Transcript written on %s.log" % self
.texfilename
)
2032 if self
.mode
== "latex":
2033 self
.expr
= "\\end{document}\n"
2035 self
.expr
= "\\end\n"
2036 if self
.texdebug
is not None:
2037 self
.texdebug
.write(self
.expr
)
2038 self
.texinput
.write(self
.expr
)
2039 self
.gotevent
.wait(self
.waitfortex
) # wait for the expected output
2040 gotevent
= self
.gotevent
.isSet()
2041 self
.gotevent
.clear()
2042 if expr
is None and gotevent
: # TeX/LaTeX should have finished
2045 self
.texinput
.close() # close the input queue and
2046 self
.quitevent
.wait(self
.waitfortex
) # wait for finish of the output
2047 gotevent
= self
.quitevent
.isSet()
2049 self
.texmessage
= ""
2051 self
.texmessage
+= self
.gotqueue
.get_nowait()
2054 self
.texmessageparsed
= self
.texmessage
2056 if expr
is not None:
2057 texmessage
.inputmarker
.check(self
)
2058 if not self
.preamblemode
:
2059 texmessage
.pyxbox
.check(self
)
2060 texmessage
.pyxpageout
.check(self
)
2061 for check
in checks
:
2064 except TexResultWarning
:
2065 traceback
.print_exc()
2066 texmessage
.emptylines
.check(self
)
2067 if len(self
.texmessageparsed
):
2068 raise TexResultError("unhandled TeX response (might be an error)", self
)
2070 raise TexResultError("TeX didn't respond as expected within the timeout period (%i seconds)." % self
.waitfortex
, self
)
2073 "finish TeX/LaTeX and read the dvifile"
2074 self
.execute(None, *self
.texmessageend
)
2076 os
.system("dvicopy %s.dvi %s.dvicopy" % (self
.texfilename
, self
.texfilename
))
2077 dvifilename
= "%s.dvicopy" % self
.texfilename
2079 dvifilename
= "%s.dvi" % self
.texfilename
2080 self
.dvifiles
.append(DVIFile(dvifilename
, debug
=self
.dvidebug
))
2083 def prolog(self
, dvinumber
, page
):
2084 "return the dvifile prolog"
2085 if not self
.texdone
:
2087 return self
.dvifiles
[dvinumber
].prolog(page
)
2089 def write(self
, file, dvinumber
, page
):
2090 "write a page from the dvifile"
2091 if not self
.texdone
:
2093 return self
.dvifiles
[dvinumber
].write(file, page
)
2095 def reset(self
, reinit
=0):
2096 "resets the tex runner to its initial state (upto its record to old dvi file(s))"
2098 if not self
.texdone
:
2100 self
.preamblemode
= 1
2105 for expr
, args
in self
.preambles
:
2106 self
.execute(expr
, *args
)
2110 def set(self
, mode
=None,
2121 texmessagestart
=None,
2122 texmessagedocclass
=None,
2123 texmessagebegindoc
=None,
2125 texmessagedefaultpreamble
=None,
2126 texmessagedefaultrun
=None):
2127 """provide a set command for TeX/LaTeX settings
2128 - TeX/LaTeX must not yet been started
2129 - especially needed for the defaultrunner, where no access to
2130 the constructor is available"""
2133 if mode
is not None:
2135 if mode
!= "tex" and mode
!= "latex":
2136 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
2140 if docclass
is not None:
2141 self
.docclass
= docclass
2142 if docopt
is not None:
2143 self
.docopt
= docopt
2144 if self
.usefiles
is not None:
2145 self
.usefiles
= helper
.ensurelist(usefiles
)
2146 if waitfortex
is not None:
2147 self
.waitfortex
= waitfortex
2148 if texdebug
is not None:
2149 if texdebug
[-4:] == ".tex":
2150 self
.texdebug
= open(texdebug
, "w")
2152 self
.texdebug
= open("%s.tex" % texdebug
, "w")
2153 if dvidebug
is not None:
2154 self
.dvidebug
= dvidebug
2155 if errordebug
is not None:
2156 self
.errordebug
= errordebug
2157 if dvicopy
is not None:
2158 self
.dvicopy
= dvicopy
2159 if pyxgraphics
is not None:
2160 self
.pyxgraphics
= pyxgraphics
2161 if errordebug
is not None:
2162 self
.errordebug
= errordebug
2163 if texmessagestart
is not None:
2164 texmessagestart
= helper
.ensuresequence(texmessagestart
)
2165 helper
.checkattr(texmessagestart
, allowmulti
=(texmessage
,))
2166 self
.texmessagestart
= texmessagestart
2167 if texmessagedocclass
is not None:
2168 texmessagedocclass
= helper
.ensuresequence(texmessagedocclass
)
2169 helper
.checkattr(texmessagedocclass
, allowmulti
=(texmessage
,))
2170 self
.texmessagedocclass
= texmessagedocclass
2171 if texmessagebegindoc
is not None:
2172 texmessagebegindoc
= helper
.ensuresequence(texmessagebegindoc
)
2173 helper
.checkattr(texmessagebegindoc
, allowmulti
=(texmessage
,))
2174 self
.texmessagebegindoc
= texmessagebegindoc
2175 if texmessageend
is not None:
2176 texmessageend
= helper
.ensuresequence(texmessageend
)
2177 helper
.checkattr(texmessageend
, allowmulti
=(texmessage
,))
2178 self
.texmessageend
= texmessageend
2179 if texmessagedefaultpreamble
is not None:
2180 texmessagedefaultpreamble
= helper
.ensuresequence(texmessagedefaultpreamble
)
2181 helper
.checkattr(texmessagedefaultpreamble
, allowmulti
=(texmessage
,))
2182 self
.texmessagedefaultpreamble
= texmessagedefaultpreamble
2183 if texmessagedefaultrun
is not None:
2184 texmessagedefaultrun
= helper
.ensuresequence(texmessagedefaultrun
)
2185 helper
.checkattr(texmessagedefaultrun
, allowmulti
=(texmessage
,))
2186 self
.texmessagedefaultrun
= texmessagedefaultrun
2188 def bracketcheck(self
, expr
):
2189 """a helper method to check the usage of "{" and "}"
2190 - Michael Schindler claims that this is not necessary"""
2193 # def bracketcheck(self, expr):
2194 # """a helper method for consistant usage of "{" and "}"
2195 # - prevent to pass unbalanced expressions to TeX
2196 # - raises an appropriate ValueError"""
2200 # if c == "{" and not esc:
2202 # if c == "}" and not esc:
2205 # raise ValueError("unmatched '}'")
2207 # esc = (esc + 1) % 2
2211 # raise ValueError("unmatched '{'")
2213 def preamble(self
, expr
, *args
):
2214 r
"""put something into the TeX/LaTeX preamble
2215 - in LaTeX, this is done before the \begin{document}
2216 (you might use \AtBeginDocument, when you're in need for)
2217 - it is not allowed to call preamble after calling the
2218 text method for the first time (for LaTeX this is needed
2219 due to \begin{document}; in TeX it is forced for compatibility
2220 (you should be able to switch from TeX to LaTeX, if you want,
2221 without breaking something
2222 - preamble expressions must not create any dvi output
2223 - args might contain texmessage instances
2224 - a bracketcheck is performed on the expression"""
2225 if self
.texdone
or not self
.preamblemode
:
2226 raise TexNotInPreambleModeError
2227 self
.bracketcheck(expr
)
2228 helper
.checkattr(args
, allowmulti
=(texmessage
,))
2229 args
= helper
.getattrs(args
, texmessage
, default
=self
.texmessagedefaultpreamble
)
2230 self
.execute(expr
, *args
)
2231 self
.preambles
.append((expr
, args
))
2233 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:")
2235 def _text(self
, x
, y
, expr
, *args
):
2236 """create text by passing expr to TeX/LaTeX
2237 - returns a textbox containing the result from running expr thru TeX/LaTeX
2238 - the box center is set to x, y
2239 - *args may contain style parameters, namely:
2240 - an halign instance
2241 - _texsetting instances
2242 - texmessage instances
2243 - trafo._trafo instances
2244 - base.PathStyle instances
2245 - a bracketcheck is performed on the expression"""
2247 raise ValueError("None expression is invalid")
2249 if self
.texdebug
is not None:
2250 self
.texdebug
.write("%s\n" % reduce(lambda x
, y
: "%" + x
, range(80), ""))
2251 self
.texdebug
.write("%% a new instance of %s is started\n" % self
.mode
)
2252 self
.reset(reinit
=1)
2253 if self
.preamblemode
:
2254 if self
.mode
== "latex":
2255 self
.execute("\\begin{document}", *self
.texmessagebegindoc
)
2256 self
.preamblemode
= 0
2257 helper
.checkattr(args
, allowmulti
=(_texsetting
, texmessage
, trafo
._trafo
, base
.PathStyle
))
2258 #XXX: should we distiguish between StrokeStyle and FillStyle?
2259 texsettings
= helper
.getattrs(args
, _texsetting
, default
=[])
2261 for texsetting
in texsettings
:
2262 if texsetting
.exclusive
:
2263 if texsetting
.id not in exclusive
:
2264 exclusive
.append(texsetting
.id)
2266 raise RuntimeError("multiple occurance of exclusive texsetting with id=%i" % texsetting
.id)
2268 for texsetting
in texsettings
:
2269 expr
= texsetting
.modifyexpr(expr
, texsettings
, self
)
2270 self
.bracketcheck(expr
)
2271 self
.execute(expr
, *helper
.getattrs(args
, texmessage
, default
=self
.texmessagedefaultrun
))
2272 match
= self
.PyXBoxPattern
.search(self
.texmessage
)
2273 if not match
or int(match
.group("page")) != self
.page
:
2274 raise TexResultError("box extents not found", self
)
2275 left
, right
, height
, depth
= map(lambda x
: float(x
) * 72.0 / 72.27, match
.group("lt", "rt", "ht", "dp"))
2276 box
= _textbox(x
, y
, left
, right
, height
, depth
, self
, self
.dvinumber
, self
.page
,
2277 *helper
.getattrs(args
, base
.PathStyle
, default
=[]))
2278 for t
in helper
.getattrs(args
, trafo
._trafo
, default
=()):
2282 def text(self
, x
, y
, expr
, *args
):
2283 return self
._text
(unit
.topt(x
), unit
.topt(y
), expr
, *args
)
2286 # the module provides an default texrunner and methods for direct access
2287 defaulttexrunner
= texrunner()
2288 reset
= defaulttexrunner
.reset
2289 set = defaulttexrunner
.set
2290 preamble
= defaulttexrunner
.preamble
2291 text
= defaulttexrunner
.text
2292 _text
= defaulttexrunner
._text