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
, string
26 import config
, 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
)
64 return self
.file.tell()
66 def read(self
, bytes
):
67 return self
.file.read(bytes
)
69 def readint(self
, bytes
=4, signed
=0):
73 value
= ord(self
.file.read(1))
74 if first
and signed
and value
> 127:
77 result
= 256 * result
+ value
82 return struct
.unpack(">l", self
.file.read(4))[0]
85 return struct
.unpack(">L", self
.file.read(4))[0]
89 return struct
.unpack(">l", "\0"+self
.file.read(3))[0]
93 return struct
.unpack(">L", "\0"+self
.file.read(3))[0]
96 return struct
.unpack(">h", self
.file.read(2))[0]
99 return struct
.unpack(">H", self
.file.read(2))[0]
102 return struct
.unpack("b", self
.file.read(1))[0]
105 return struct
.unpack("B", self
.file.read(1))[0]
107 def readstring(self
, bytes
):
109 assert l
<= bytes
-1, "inconsistency in file: string too long"
110 return self
.file.read(bytes
-1)[:l
]
114 # """ ascii file containing tokens separated by spaces.
116 # Comments beginning with % are ignored. Strings containing spaces
117 # are not handled correctly
120 # def __init__(self, filename):
121 # self.file = open(filename, "r")
124 # def gettoken(self):
125 # """ return next token or None if EOF """
126 # while not self.line:
127 # line = self.file.readline()
130 # self.line = line.split("%")[0].split()
131 # token = self.line[0]
132 # self.line = self.line[1:]
139 ##############################################################################
141 ##############################################################################
143 class TFMError(exceptions
.Exception): pass
146 class char_info_word
:
147 def __init__(self
, word
):
148 self
.width_index
= int((word
& 0xFF000000L
) >> 24) #make sign-safe
149 self
.height_index
= (word
& 0x00F00000) >> 20
150 self
.depth_index
= (word
& 0x000F0000) >> 16
151 self
.italic_index
= (word
& 0x0000FC00) >> 10
152 self
.tag
= (word
& 0x00000300) >> 8
153 self
.remainder
= (word
& 0x000000FF)
157 def __init__(self
, name
, debug
=0):
158 self
.file = binfile(name
, "rb")
165 self
.lf
= self
.file.readint16()
166 self
.lh
= self
.file.readint16()
167 self
.bc
= self
.file.readint16()
168 self
.ec
= self
.file.readint16()
169 self
.nw
= self
.file.readint16()
170 self
.nh
= self
.file.readint16()
171 self
.nd
= self
.file.readint16()
172 self
.ni
= self
.file.readint16()
173 self
.nl
= self
.file.readint16()
174 self
.nk
= self
.file.readint16()
175 self
.ne
= self
.file.readint16()
176 self
.np
= self
.file.readint16()
178 if not (self
.bc
-1 <= self
.ec
<= 255 and
180 self
.lf
== 6+self
.lh
+(self
.ec
-self
.bc
+1)+self
.nw
+self
.nh
+self
.nd
181 +self
.ni
+self
.nl
+self
.nk
+self
.ne
+self
.np
):
182 raise TFMError
, "error in TFM pre-header"
185 print "lh=%d" % self
.lh
191 self
.checksum
= self
.file.readint32()
192 self
.designsizeraw
= self
.file.readint32()
193 assert self
.designsizeraw
> 0, "invald design size"
194 self
.designsize
= fix_word(self
.designsizeraw
)
196 assert self
.lh
> 11, "inconsistency in TFM file: incomplete field"
197 self
.charcoding
= self
.file.readstring(40)
199 self
.charcoding
= None
202 assert self
.lh
> 16, "inconsistency in TFM file: incomplete field"
203 self
.fontfamily
= self
.file.readstring(20)
205 self
.fontfamily
= None
208 print "(FAMILY %s)" % self
.fontfamily
209 print "(CODINGSCHEME %s)" % self
.charcoding
210 print "(DESINGSIZE R %f)" % self
.designsize
213 self
.sevenbitsave
= self
.file.readuchar()
214 # ignore the following two bytes
215 self
.file.readint16()
216 facechar
= self
.file.readuchar()
217 # decode ugly face specification into the Knuth suggested string
229 self
.face
= "L" + self
.face
232 self
.face
= "B" + self
.face
235 self
.face
= "M" + self
.face
238 self
.face
= self
.face
[0] + "I" + self
.face
[1]
240 self
.face
= self
.face
[0] + "R" + self
.face
[1]
245 self
.sevenbitsave
= self
.face
= None
248 # just ignore the rest
249 print self
.file.read((self
.lh
-18)*4)
255 self
.char_info
= [None]*(self
.ec
+1)
257 for charcode
in range(self
.bc
, self
.ec
+1):
258 self
.char_info
[charcode
] = char_info_word(self
.file.readint32())
259 if self
.char_info
[charcode
].width_index
== 0:
260 # disable character if width_index is zero
261 self
.char_info
[charcode
] = None
267 self
.width
= [None for width_index
in range(self
.nw
)]
268 for width_index
in range(self
.nw
):
269 # self.width[width_index] = fix_word(self.file.readint32())
270 self
.width
[width_index
] = self
.file.readint32()
276 self
.height
= [None for height_index
in range(self
.nh
)]
277 for height_index
in range(self
.nh
):
278 # self.height[height_index] = fix_word(self.file.readint32())
279 self
.height
[height_index
] = self
.file.readint32()
285 self
.depth
= [None for depth_index
in range(self
.nd
)]
286 for depth_index
in range(self
.nd
):
287 # self.depth[depth_index] = fix_word(self.file.readint32())
288 self
.depth
[depth_index
] = self
.file.readint32()
294 self
.italic
= [None for italic_index
in range(self
.ni
)]
295 for italic_index
in range(self
.ni
):
296 # self.italic[italic_index] = fix_word(self.file.readint32())
297 self
.italic
[italic_index
] = self
.file.readint32()
303 # XXX decode to lig_kern_command
305 self
.lig_kern
= [None for lig_kern_index
in range(self
.nl
)]
306 for lig_kern_index
in range(self
.nl
):
307 self
.lig_kern
[lig_kern_index
] = self
.file.readint32()
313 self
.kern
= [None for kern_index
in range(self
.nk
)]
314 for kern_index
in range(self
.nk
):
315 # self.kern[kern_index] = fix_word(self.file.readint32())
316 self
.kern
[kern_index
] = self
.file.readint32()
322 # XXX decode to extensible_recipe
324 self
.exten
= [None for exten_index
in range(self
.ne
)]
325 for exten_index
in range(self
.ne
):
326 self
.exten
[exten_index
] = self
.file.readint32()
334 self
.param
= [None for param_index
in range(self
.np
)]
335 for param_index
in range(self
.np
):
336 self
.param
[param_index
] = self
.file.readint32()
341 # class FontEncoding:
343 # def __init__(self, filename):
344 # """ font encoding contained in filename """
345 # encpath = pykpathsea.find_file(filename, pykpathsea.kpse_tex_ps_header_format)
346 # encfile = tokenfile(encpath)
349 # self.encname = encfile.gettoken()
350 # token = encfile.gettoken()
352 # raise RuntimeError("cannot parse encoding file '%s', expecting '[' got '%s'" % (filename, token))
353 # self.encvector = []
354 # for i in range(256):
355 # token = encfile.gettoken()
356 # if token is None or token=="]":
357 # raise RuntimeError("not enough charcodes in encoding file '%s'" % filename)
358 # self.encvector.append(token)
359 # if encfile.gettoken() != "]":
360 # raise RuntimeError("too many charcodes in encoding file '%s'" % filename)
361 # token = encfile.gettoken()
363 # raise RuntimeError("cannot parse encoding file '%s', expecting 'def' got '%s'" % (filename, token))
364 # token = encfile.gettoken()
366 # raise RuntimeError("encoding file '%s' too long" % filename)
369 # def encode(self, charcode):
370 # return self.encvector[charcode]
372 ##############################################################################
374 ##############################################################################
376 _ReEncodeFont
= prolog
.definition("ReEncodeFont", """{
379 /newencoding exch def
380 /newfontname exch def
381 /basefontname exch def
382 /basefontdict basefontname findfont def
383 /newfontdict basefontdict maxlength dict def
385 exch dup dup /FID ne exch /Encoding ne and
386 { exch newfontdict 3 1 roll put }
390 newfontdict /FontName newfontname put
391 newfontdict /Encoding newencoding put
392 newfontname newfontdict definefont pop
397 # PostScript font selection and output primitives
400 class _selectfont(base
.PSOp
):
401 def __init__(self
, name
, size
):
405 def write(self
, file):
406 file.write("/%s %f selectfont\n" % (self
.name
, self
.size
))
408 # XXX: should we provide a prolog method for the font inclusion
409 # instead of using the coarser logic in DVIFile.prolog
412 class _show(base
.PSOp
):
413 def __init__(self
, x
, y
, s
):
418 def write(self
, file):
419 file.write("%f %f moveto (%s) show\n" % (self
.x
, self
.y
, self
.s
))
424 tokenpattern
= re
.compile(r
'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
426 def __init__(self
, s
):
427 """ construct font mapping from line s of font mapping file """
428 self
.texname
= self
.basepsname
= self
.fontfile
= None
431 self
.encodingfile
= None
433 # supported postscript fragments occuring in psfonts.map
434 self
.reencodefont
= self
.extendfont
= self
.slantfont
= None
438 match
= self
.tokenpattern
.match(s
)
440 if match
.groups()[0]:
441 tokens
.append('"%s"' % match
.groups()[0])
443 tokens
.append(match
.groups()[2])
446 raise RuntimeError("wrong syntax")
449 if token
.startswith("<"):
450 if token
.startswith("<<"):
451 # XXX: support non-partial download here
452 self
.fontfile
= token
[2:]
453 elif token
.startswith("<["):
454 self
.encodingfile
= token
[2:]
455 elif token
.endswith(".pfa") or token
.endswith(".pfb"):
456 self
.fontfile
= token
[1:]
457 elif token
.endswith(".enc"):
458 self
.encodingfile
= token
[1:]
460 raise RuntimeError("wrong syntax")
461 elif token
.startswith('"'):
462 pscode
= token
[1:-1].split()
463 # parse standard postscript code fragments
466 arg
, cmd
= pscode
[:2]
468 raise RuntimeError("Unsupported Postscript fragment '%s'" % pscode
)
470 if cmd
== "ReEncodeFont":
471 self
.reencodefont
= arg
472 elif cmd
== "ExtendFont":
473 self
.extendfont
= arg
474 elif cmd
== "SlantFont":
477 raise RuntimeError("Unsupported Postscript fragment '%s %s'" % (arg
, cmd
))
479 if self
.texname
is None:
482 self
.basepsname
= token
483 if self
.basepsname
is None:
484 self
.basepsname
= self
.texname
487 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
488 (self
.texname
, self
.basepsname
, self
.fontfile
, repr(self
.encodingfile
)))
492 def readfontmap(filenames
):
493 """ read font map from filename (without path) """
495 for filename
in filenames
:
496 mappath
= pykpathsea
.find_file(filename
, pykpathsea
.kpse_dvips_config_format
)
498 raise RuntimeError("cannot find font mapping file '%s'" % filename
)
499 mapfile
= open(mappath
, "r")
501 for line
in mapfile
.readlines():
504 if not (line
=="" or line
[0] in (" ", "%", "*", ";" , "#")):
506 fontmapping
= FontMapping(line
)
507 except RuntimeError, e
:
508 sys
.stderr
.write("*** PyX Warning: Ignoring line %i in mapping file '%s': %s\n" % (lineno
, filename
, e
))
510 fontmap
[fontmapping
.texname
] = fontmapping
516 def __init__(self
, name
, c
, q
, d
, tfmconv
, fontmap
, debug
=0):
518 self
.tfmpath
= pykpathsea
.find_file("%s.tfm" % self
.name
, pykpathsea
.kpse_tfm_format
)
520 raise TFMError("cannot find %s.tfm" % self
.name
)
521 self
.tfmfile
= TFMFile(self
.tfmpath
, debug
)
522 self
.fontmapping
= fontmap
.get(name
)
523 if self
.fontmapping
is None:
524 raise RuntimeError("no information for font '%s' found in font mapping file, aborting" % name
)
525 # print "found mapping %s for font %s" % (self.fontmapping, self.name)
527 # We only check for equality of font checksums if none of them is zero
528 # c == 0 appeared in VF files and according to the VFtoVP 40. a check
529 # is only performed if tfmfile.checksum > 0. Anyhow, begin more generous
530 # here seems to be reasonable
531 if self
.tfmfile
.checksum
!= c
and self
.tfmfile
.checksum
!= 0 and c
!=0:
532 raise DVIError("check sums do not agree: %d vs. %d" %
533 (self
.tfmfile
.checksum
, c
))
535 self
.tfmdesignsize
= round(tfmconv
*self
.tfmfile
.designsizeraw
)
537 if abs(self
.tfmdesignsize
- d
) > 2:
538 raise DVIError("design sizes do not agree: %d vs. %d" %
539 (self
.tfmdesignsize
, d
))
540 if q
< 0 or q
> 134217728:
541 raise DVIError("font '%s' not loaded: bad scale" % self
.name
)
542 if d
< 0 or d
> 134217728:
543 raise DVIError("font '%s' not loaded: bad design size" % self
.name
)
547 self
.q
= self
.qorig
= q
548 while self
.q
>= 8388608:
552 self
.beta
= 256/self
.alpha
;
553 self
.alpha
= self
.alpha
*self
.q
;
555 # for bookkeeping of used characters
556 self
.usedchars
= [0] * 256
559 return "Font(%s, %d)" % (self
.name
, self
.tfmdesignsize
)
563 def convert(self
, width
):
565 return 16L*width
*self
.qorig
/16777216L
567 # original algorithm of Knuth (at the moment not used)
569 b1
= (width
>> 16) & 0xff
570 b2
= (width
>> 8 ) & 0xff
574 return (((((b3
*self
.q
)/256)+(b2
*self
.q
))/256)+(b1
*self
.q
))/self
.beta
576 return (((((b3
*self
.q
)/256)+(b2
*self
.q
))/256)+(b1
*self
.q
))/self
.beta
-self
.alpha
578 raise TFMError("error in font size")
580 def getwidth(self
, charcode
):
581 return self
.convert(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
583 def getheight(self
, charcode
):
584 return self
.convert(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
586 def getdepth(self
, charcode
):
587 return self
.convert(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
589 def getitalic(self
, charcode
):
590 return self
.convert(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
592 def markcharused(self
, charcode
):
593 self
.usedchars
[charcode
] = 1
595 def mergeusedchars(self
, otherfont
):
596 for i
in range(len(self
.usedchars
)):
597 self
.usedchars
[i
] = self
.usedchars
[i
] or otherfont
.usedchars
[i
]
599 def getbasepsname(self
):
600 return self
.fontmapping
.basepsname
603 if self
.fontmapping
.reencodefont
:
604 return "%s-%s" % (self
.fontmapping
.basepsname
, self
.fontmapping
.reencodefont
)
606 return self
.fontmapping
.basepsname
608 def getfontfile(self
):
609 return self
.fontmapping
.fontfile
611 def getencoding(self
):
612 return self
.fontmapping
.reencodefont
614 def getencodingfile(self
):
615 return self
.fontmapping
.encodingfile
617 ##############################################################################
619 ##############################################################################
621 _DVI_CHARMIN
= 0 # typeset a character and move right (range min)
622 _DVI_CHARMAX
= 127 # typeset a character and move right (range max)
623 _DVI_SET1234
= 128 # typeset a character and move right
624 _DVI_SETRULE
= 132 # typeset a rule and move right
625 _DVI_PUT1234
= 133 # typeset a character
626 _DVI_PUTRULE
= 137 # typeset a rule
627 _DVI_NOP
= 138 # no operation
628 _DVI_BOP
= 139 # beginning of page
629 _DVI_EOP
= 140 # ending of page
630 _DVI_PUSH
= 141 # save the current positions (h, v, w, x, y, z)
631 _DVI_POP
= 142 # restore positions (h, v, w, x, y, z)
632 _DVI_RIGHT1234
= 143 # move right
633 _DVI_W0
= 147 # move right by w
634 _DVI_W1234
= 148 # move right and set w
635 _DVI_X0
= 152 # move right by x
636 _DVI_X1234
= 153 # move right and set x
637 _DVI_DOWN1234
= 157 # move down
638 _DVI_Y0
= 161 # move down by y
639 _DVI_Y1234
= 162 # move down and set y
640 _DVI_Z0
= 166 # move down by z
641 _DVI_Z1234
= 167 # move down and set z
642 _DVI_FNTNUMMIN
= 171 # set current font (range min)
643 _DVI_FNTNUMMAX
= 234 # set current font (range max)
644 _DVI_FNT1234
= 235 # set current font
645 _DVI_SPECIAL1234
= 239 # special (dvi extention)
646 _DVI_FNTDEF1234
= 243 # define the meaning of a font number
647 _DVI_PRE
= 247 # preamble
648 _DVI_POST
= 248 # postamble beginning
649 _DVI_POSTPOST
= 249 # postamble ending
651 _DVI_VERSION
= 2 # dvi version
653 # position variable indices
665 _READ_POST
= 4 # XXX not used
666 _READ_POSTPOST
= 5 # XXX not used
670 class DVIError(exceptions
.Exception): pass
672 # save and restore colors
674 class _savecolor(base
.PSOp
):
675 def write(self
, file):
676 file.write("currentcolor currentcolorspace\n")
679 class _restorecolor(base
.PSOp
):
680 def write(self
, file):
681 file.write("setcolorspace setcolor\n")
683 class _savetrafo(base
.PSOp
):
684 def write(self
, file):
685 file.write("matrix currentmatrix\n")
688 class _restoretrafo(base
.PSOp
):
689 def write(self
, file):
690 file.write("setmatrix\n")
694 def __init__(self
, filename
, fontmap
, debug
=0, ipcmode
=0):
695 """ initializes the instance
697 Usually, the readfile method should be called once
698 immediately after this constructor. However, if you
699 set ipcmode, you need to call readpages as often as
700 there are pages in the dvi-file plus 1 (for properly
701 closing the dvi-file).
704 self
.filename
= filename
705 self
.fontmap
= fontmap
708 self
.ipcmode
= ipcmode
713 """ flush currently active string """
715 x
= unit
.t_m(self
.actoutstart
[0] * self
.conv
* 0.0254 / self
.resolution
)
716 y
= -unit
.t_m(self
.actoutstart
[1] * self
.conv
* 0.0254 / self
.resolution
)
718 print "[%s]" % self
.actoutstring
719 self
.actpage
.insert(_show(unit
.topt(x
), unit
.topt(y
), self
.actoutstring
))
720 self
.actoutstart
= None
722 def putchar(self
, char
, inch
=1):
723 if self
.actoutstart
is None:
724 self
.actoutstart
= self
.pos
[_POS_H
], self
.pos
[_POS_V
]
725 self
.actoutstring
= ""
726 if char
> 32 and char
< 127 and chr(char
) not in "()[]<>":
727 ascii
= "%s" % chr(char
)
729 ascii
= "\\%03o" % char
730 self
.actoutstring
= self
.actoutstring
+ ascii
731 dx
= inch
and self
.fonts
[self
.activefont
].getwidth(char
) or 0
732 self
.fonts
[self
.activefont
].markcharused(char
)
734 print ("%d: %schar%d h:=%d+%d=%d, hh:=%d" %
736 inch
and "set" or "put",
738 self
.pos
[_POS_H
], dx
, self
.pos
[_POS_H
]+dx
,
740 self
.pos
[_POS_H
] += dx
745 def putrule(self
, height
, width
, inch
=1):
747 x1
= unit
.t_m(self
.pos
[_POS_H
] * self
.conv
* 0.0254 / self
.resolution
)
748 y1
= -unit
.t_m(self
.pos
[_POS_V
] * self
.conv
* 0.0254 / self
.resolution
)
749 w
= unit
.t_m(width
* self
.conv
* 0.0254 / self
.resolution
)
750 h
= unit
.t_m(height
* self
.conv
* 0.0254 / self
.resolution
)
752 if height
> 0 and width
> 0:
754 pixelw
= int(width
*self
.conv
)
755 if pixelw
< width
*self
.conv
: pixelw
+= 1
756 pixelh
= int(height
*self
.conv
)
757 if pixelh
< height
*self
.conv
: pixelh
+= 1
759 print ("%d: %srule height %d, width %d (%dx%d pixels)" %
760 (self
.filepos
, inch
and "set" or "put", height
, width
, pixelh
, pixelw
))
761 self
.actpage
.fill(path
.rect(x1
, y1
, w
, h
))
764 print ("%d: %srule height %d, width %d (invisible)" %
765 (self
.filepos
, inch
and "set" or "put", height
, width
))
769 print (" h:=%d+%d=%d, hh:=%d" %
770 (self
.pos
[_POS_H
], width
, self
.pos
[_POS_H
]+width
, 0))
771 self
.pos
[_POS_H
] += width
774 def usefont(self
, fontnum
):
776 self
.activefont
= fontnum
778 fontpsname
= self
.fonts
[self
.activefont
].getpsname()
779 fontscale
= self
.fonts
[self
.activefont
].scale
780 fontdesignsize
= float(self
.fonts
[self
.activefont
].tfmfile
.designsize
)
781 self
.actpage
.insert(_selectfont(fontpsname
,
782 fontscale
*fontdesignsize
*72/72.27))
785 print ("%d: fntnum%i current font is %s" %
787 self
.activefont
, self
.fonts
[fontnum
].name
))
789 def definefont(self
, cmdnr
, num
, c
, q
, d
, fontname
):
790 # cmdnr: type of fontdef command (only used for debugging output)
793 # Note that q is actually s in large parts of the documentation.
796 self
.fonts
[num
] = Font(fontname
, c
, q
, d
, self
.tfmconv
, self
.fontmap
, self
.debug
> 1)
799 print "%d: fntdef%d %i: %s" % (self
.filepos
, cmdnr
, num
, fontname
)
801 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
803 # scalestring = scale!=1000 and " scaled %d" % scale or ""
804 # print ("Font %i: %s%s---loaded at size %d DVI units" %
805 # (num, fontname, scalestring, q))
807 # print " (this font is magnified %d%%)" % round(scale/10)
809 def special(self
, s
):
811 x
= unit
.t_m(self
.pos
[_POS_H
] * self
.conv
* 0.0254 / self
.resolution
)
812 y
= -unit
.t_m(self
.pos
[_POS_V
] * self
.conv
* 0.0254 / self
.resolution
)
814 print "%d: xxx '%s'" % (self
.filepos
, s
)
815 if not s
.startswith("PyX:"):
816 raise RuntimeError("the special '%s' cannot be handled by PyX, aborting" % s
)
817 command
, args
= s
[4:].split()[0], s
[4:].split()[1:]
818 if command
=="color_begin":
820 c
= color
.cmyk(float(args
[1]), float(args
[2]), float(args
[3]), float(args
[4]))
821 elif args
[0]=="gray":
822 c
= color
.gray(float(args
[1]))
824 c
= color
.hsb(float(args
[1]), float(args
[2]), float(args
[3]))
826 c
= color
.rgb(float(args
[1]), float(args
[2]), float(args
[3]))
828 c
= color
.rgb(int(args
[1])/255.0, int(args
[2])/255.0, int(args
[3])/255.0)
829 elif args
[0]=="texnamed":
831 c
= getattr(color
.cmyk
, args
[1])
832 except AttributeError:
833 raise RuntimeError("unknown TeX color '%s', aborting" % args
[1])
835 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args
[0])
836 self
.actpage
.insert(_savecolor())
837 self
.actpage
.insert(c
)
838 elif command
=="color_end":
839 self
.actpage
.insert(_restorecolor())
840 elif command
=="rotate_begin":
841 self
.actpage
.insert(_savetrafo())
842 self
.actpage
.insert(trafo
.rotate(float(args
[0]), x
, y
))
843 elif command
=="rotate_end":
844 self
.actpage
.insert(_restoretrafo())
845 elif command
=="scale_begin":
846 self
.actpage
.insert(_savetrafo())
847 self
.actpage
.insert(trafo
.scale(float(args
[0]), float(args
[1]), x
, y
))
848 elif command
=="scale_end":
849 self
.actpage
.insert(_restoretrafo())
850 elif command
=="epsinclude":
851 # XXX: we cannot include epsfile in the header because this would
852 # generate a cyclic import with the canvas and text modules
858 name
, value
= arg
.split("=")
859 argdict
[name
] = value
861 # construct kwargs for epsfile constructor
863 epskwargs
["filename"] = argdict
["file"]
864 epskwargs
["bbox"] = bbox
._bbox
(float(argdict
["llx"]), float(argdict
["lly"]),
865 float(argdict
["urx"]), float(argdict
["ury"]))
866 if argdict
.has_key("width"):
867 epskwargs
["width"] = unit
.t_pt(float(argdict
["width"]))
868 if argdict
.has_key("height"):
869 epskwargs
["height"] = unit
.t_pt(float(argdict
["height"]))
870 if argdict
.has_key("clip"):
871 epskwargs
["clip"] = int(argdict
["clip"])
872 self
.actpage
.insert(epsfile
.epsfile(x
, y
, **epskwargs
))
873 elif command
=="marker":
875 raise RuntimeError("marker contains spaces")
877 if c
not in string
.digits
+ string
.letters
+ "@":
878 raise RuntimeError("marker contains invalid characters")
879 if self
.actpage
.markers
.has_key(args
[0]):
880 raise RuntimeError("marker name occurred several times")
881 self
.actpage
.markers
[args
[0]] = x
, y
883 raise RuntimeError("unknown PyX special '%s', aborting" % command
)
885 # routines corresponding to the different reader states of the dvi maschine
890 self
.filepos
= file.tell()
891 cmd
= file.readuchar()
894 elif cmd
== _DVI_PRE
:
895 if self
.file.readuchar() != _DVI_VERSION
: raise DVIError
896 num
= file.readuint32()
897 den
= file.readuint32()
898 mag
= file.readuint32()
900 self
.tfmconv
= (25400000.0/num
)*(den
/473628672)/16.0;
902 self
.resolution
= 300.0
903 # self.trueconv = conv in DVIType docu
904 self
.trueconv
= (num
/254000.0)*(self
.resolution
/den
)
905 self
.conv
= self
.trueconv
*(mag
/1000.0)
907 comment
= file.read(file.readuchar())
912 def _read_nopage(self
):
915 self
.filepos
= file.tell()
916 cmd
= file.readuchar()
919 elif cmd
== _DVI_BOP
:
921 pagenos
= [file.readuint32() for i
in range(10)]
922 if pagenos
[:3] != [ord("P"), ord("y"), ord("X")] or pagenos
[4:] != [0, 0, 0, 0, 0, 0]:
923 raise DVIError("Page in dvi file is not a PyX page.")
925 print "%d: beginning of page %i" % (self
.filepos
, pagenos
[0])
928 elif cmd
== _DVI_POST
:
929 return _READ_DONE
# we skip the rest
933 def _read_page(self
):
934 self
.pages
.append(canvas
.canvas())
935 self
.actpage
= self
.pages
[-1]
936 self
.actpage
.markers
= {}
937 self
.pos
= [0, 0, 0, 0, 0, 0]
940 self
.filepos
= file.tell()
941 cmd
= file.readuchar()
944 if cmd
>= _DVI_CHARMIN
and cmd
<= _DVI_CHARMAX
:
946 elif cmd
>= _DVI_SET1234
and cmd
< _DVI_SET1234
+ 4:
947 self
.putchar(file.readint(cmd
- _DVI_SET1234
+ 1))
948 elif cmd
== _DVI_SETRULE
:
949 self
.putrule(file.readint32(), file.readint32())
950 elif cmd
>= _DVI_PUT1234
and cmd
< _DVI_PUT1234
+ 4:
951 self
.putchar(file.readint(cmd
- _DVI_PUT1234
+ 1), inch
=0)
952 elif cmd
== _DVI_PUTRULE
:
953 self
.putrule(file.readint32(), file.readint32(), 0)
954 elif cmd
== _DVI_EOP
:
957 print "%d: eop" % self
.filepos
960 elif cmd
== _DVI_PUSH
:
961 self
.stack
.append(tuple(self
.pos
))
963 print "%d: push" % self
.filepos
964 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
965 (( len(self
.stack
)-1,)+tuple(self
.pos
)))
966 elif cmd
== _DVI_POP
:
968 self
.pos
= list(self
.stack
[-1])
971 print "%d: pop" % self
.filepos
972 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
973 (( len(self
.stack
),)+tuple(self
.pos
)))
974 elif cmd
>= _DVI_RIGHT1234
and cmd
< _DVI_RIGHT1234
+ 4:
976 dh
= file.readint(cmd
- _DVI_RIGHT1234
+ 1, 1)
978 print ("%d: right%d %d h:=%d%+d=%d, hh:=" %
980 cmd
- _DVI_RIGHT1234
+ 1,
984 self
.pos
[_POS_H
]+dh
))
985 self
.pos
[_POS_H
] += dh
989 print ("%d: w0 %d h:=%d%+d=%d, hh:=" %
994 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
995 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
996 elif cmd
>= _DVI_W1234
and cmd
< _DVI_W1234
+ 4:
998 self
.pos
[_POS_W
] = file.readint(cmd
- _DVI_W1234
+ 1, 1)
1000 print ("%d: w%d %d h:=%d%+d=%d, hh:=" %
1002 cmd
- _DVI_W1234
+ 1,
1006 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
1007 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
1008 elif cmd
== _DVI_X0
:
1010 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
1011 elif cmd
>= _DVI_X1234
and cmd
< _DVI_X1234
+ 4:
1013 self
.pos
[_POS_X
] = file.readint(cmd
- _DVI_X1234
+ 1, 1)
1014 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
1015 elif cmd
>= _DVI_DOWN1234
and cmd
< _DVI_DOWN1234
+ 4:
1017 dv
= file.readint(cmd
- _DVI_DOWN1234
+ 1, 1)
1019 print ("%d: down%d %d v:=%d%+d=%d, vv:=" %
1021 cmd
- _DVI_DOWN1234
+ 1,
1025 self
.pos
[_POS_V
]+dv
))
1026 self
.pos
[_POS_V
] += dv
1027 elif cmd
== _DVI_Y0
:
1030 print ("%d: y0 %d v:=%d%+d=%d, vv:=" %
1035 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1036 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1037 elif cmd
>= _DVI_Y1234
and cmd
< _DVI_Y1234
+ 4:
1039 self
.pos
[_POS_Y
] = file.readint(cmd
- _DVI_Y1234
+ 1, 1)
1041 print ("%d: y%d %d v:=%d%+d=%d, vv:=" %
1043 cmd
- _DVI_Y1234
+ 1,
1047 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1048 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1049 elif cmd
== _DVI_Z0
:
1051 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1052 elif cmd
>= _DVI_Z1234
and cmd
< _DVI_Z1234
+ 4:
1054 self
.pos
[_POS_Z
] = file.readint(cmd
- _DVI_Z1234
+ 1, 1)
1055 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1056 elif cmd
>= _DVI_FNTNUMMIN
and cmd
<= _DVI_FNTNUMMAX
:
1057 self
.usefont(cmd
- _DVI_FNTNUMMIN
)
1058 elif cmd
>= _DVI_FNT1234
and cmd
< _DVI_FNT1234
+ 4:
1059 self
.usefont(file.readint(cmd
- _DVI_FNT1234
+ 1, 1))
1060 elif cmd
>= _DVI_SPECIAL1234
and cmd
< _DVI_SPECIAL1234
+ 4:
1061 self
.special(file.read(file.readint(cmd
- _DVI_SPECIAL1234
+ 1)))
1062 elif cmd
>= _DVI_FNTDEF1234
and cmd
< _DVI_FNTDEF1234
+ 4:
1063 if cmd
== _DVI_FNTDEF1234
:
1064 num
= file.readuchar()
1065 elif cmd
== _DVI_FNTDEF1234
+1:
1066 num
= file.readuint16()
1067 elif cmd
== _DVI_FNTDEF1234
+2:
1068 num
= file.readuint24()
1069 elif cmd
== _DVI_FNTDEF1234
+3:
1070 # Cool, here we have according to docu a signed int. Why?
1071 num
= file.readint32()
1072 self
.definefont(cmd
-_DVI_FNTDEF1234
+1,
1077 file.read(file.readuchar()+file.readuchar()))
1078 else: raise DVIError
1081 """ reads and parses dvi file
1083 This routine reads the dvi file and generates a list
1084 of pages in self.pages. Each page consists itself of
1085 a list of PSCommands equivalent to the content of
1086 the dvi file. Furthermore, the list of used fonts
1087 can be extracted from the array self.fonts.
1089 Usually, the readfile method should be called once
1090 immediately after constructing the instance. However,
1091 if you set ipcmode, you need to call readpages as
1092 often as there are pages in the dvi-file plus 1 (for
1093 properly closing the dvi-file).
1096 if self
.file is None:
1098 self
.activefont
= None
1102 # here goes the result, for each page one list.
1105 # pointer to currently active page
1108 # currently active output: position and content
1109 self
.actoutstart
= None
1110 self
.actoutstring
= ""
1112 self
.file = binfile(self
.filename
, "rb")
1114 # currently read byte in file (for debugging output)
1117 if self
._read
_pre
() != _READ_NOPAGE
:
1120 # start up reading process
1122 state
= self
._read
_nopage
()
1123 if state
== _READ_PAGE
:
1124 if self
._read
_page
() != _READ_NOPAGE
:
1126 elif state
!= _READ_DONE
:
1129 state
= _READ_NOPAGE
1130 while state
!= _READ_DONE
:
1131 if state
== _READ_NOPAGE
:
1132 state
= self
._read
_nopage
()
1133 elif state
== _READ_PAGE
:
1134 state
= self
._read
_page
()
1138 if state
== _READ_DONE
:
1141 def marker(self
, page
, marker
):
1142 """return marker from page"""
1143 return self
.pages
[page
-1].markers
[marker
]
1145 def prolog(self
, page
): # TODO: AW inserted this page argument -> should return the prolog needed for that page only!
1146 """ return prolog corresponding to contents of dvi file """
1147 # XXX replace this by prolog method in _selectfont
1148 result
= [_ReEncodeFont
]
1149 for font
in self
.fonts
.values():
1150 result
.append(prolog
.fontdefinition(font
.getbasepsname(),
1152 font
.getencodingfile(),
1154 if font
.getencoding():
1155 result
.append(prolog
.fontencoding(font
.getencoding(), font
.getencodingfile()))
1156 result
.append(prolog
.fontreencoding(font
.getpsname(), font
.getbasepsname(), font
.getencoding()))
1157 result
.extend(self
.pages
[page
-1].prolog())
1160 def write(self
, file, page
):
1161 """write PostScript output for page into file"""
1162 # XXX: remove this method by return canvas to TexRunner
1164 print "dvifile(\"%s\").write() for page %s called" % (self
.filename
, page
)
1165 self
.pages
[page
-1].write(file)
1170 _VF_FNTDEF1234
= _DVI_FNTDEF1234
1172 _VF_POST
= _DVI_POST
1174 class VFError(exceptions
.Exception): pass
1177 def __init__(self
, filename
, fontmap
, debug
=0):
1178 self
.filename
= filename
1179 self
.fontmap
= fontmap
1182 self
.fonts
= {} # used fonts
1183 self
.chars
= {} # defined chars
1185 file = binfile(self
.filename
, "rb")
1187 cmd
= file.readuchar()
1189 if file.readuchar() != _VF_ID
: raise VFError
1190 comment
= file.read(file.readuchar())
1191 cs
= file.readuint32()
1192 ds
= file.readuint32()
1197 cmd
= file.readuchar()
1198 if cmd
>= _VF_FNTDEF1234
and cmd
< _VF_FNTDEF1234
+ 4:
1200 if cmd
== _VF_FNTDEF1234
:
1201 num
= file.readuchar()
1202 elif cmd
== _VF_FNTDEF1234
+1:
1203 num
= file.readuint16()
1204 elif cmd
== _VF_FNTDEF1234
+2:
1205 num
= file.readuint24()
1206 elif cmd
== _VF_FNTDEF1234
+3:
1207 num
= file.readint32()
1208 c
= file.readint32()
1209 s
= file.readint32() # scaling used for font (fix_word)
1210 d
= file.readint32() # design size of font
1211 fontname
= file.read(file.readuchar()+file.readuchar())
1212 self
.fonts
[num
] = Font(fontname
, c
, s
, d
, self
.tfmconv
, self
.fontmap
, self
.debug
> 1)
1213 elif cmd
== _VF_LONG_CHAR
:
1214 # character packet (long form)
1215 pl
= file.readuint32() # packet length
1216 cc
= file.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1217 tfm
= file.readuint24() # character width
1218 dvi
= file.read(pl
) # dvi code of character
1219 self
.chars
[cc
] = (tfm
, dvi
)
1220 elif cmd
< _VF_LONG_CHAR
:
1221 # character packet (short form)
1222 cc
= file.readuchar() # char code
1223 tfm
= file.readuint24() # character width
1224 dvi
= file.read(cmd
)
1225 self
.chars
[cc
] = (tfm
, dvi
)
1226 elif cmd
== _VF_POST
:
1234 ###############################################################################
1236 # - please don't get confused:
1237 # - there is a texmessage (and a texmessageparsed) attribute within the
1238 # texrunner; it contains TeX/LaTeX response from the last command execution
1239 # - instances of classes derived from the class texmessage are used to
1240 # parse the TeX/LaTeX response as it is stored in the texmessageparsed
1241 # attribute of a texrunner instance
1242 # - the multiple usage of the name texmessage might be removed in the future
1243 # - texmessage instances should implement _Itexmessage
1244 ###############################################################################
1246 class TexResultError(Exception):
1247 """specialized texrunner exception class
1248 - it is raised by texmessage instances, when a texmessage indicates an error
1249 - it is raised by the texrunner itself, whenever there is a texmessage left
1250 after all parsing of this message (by texmessage instances)"""
1252 def __init__(self
, description
, texrunner
):
1253 self
.description
= description
1254 self
.texrunner
= texrunner
1257 """prints a detailed report about the problem
1258 - the verbose level is controlled by texrunner.errordebug"""
1259 if self
.texrunner
.errordebug
>= 2:
1260 return ("%s\n" % self
.description
+
1261 "The expression passed to TeX was:\n"
1262 " %s\n" % self
.texrunner
.expr
.replace("\n", "\n ").rstrip() +
1263 "The return message from TeX was:\n"
1264 " %s\n" % self
.texrunner
.texmessage
.replace("\n", "\n ").rstrip() +
1265 "After parsing this message, the following was left:\n"
1266 " %s" % self
.texrunner
.texmessageparsed
.replace("\n", "\n ").rstrip())
1267 elif self
.texrunner
.errordebug
== 1:
1268 firstlines
= self
.texrunner
.texmessageparsed
.split("\n")
1269 if len(firstlines
) > 5:
1270 firstlines
= firstlines
[:5] + ["(cut after 5 lines, increase errordebug for more output)"]
1271 return ("%s\n" % self
.description
+
1272 "The expression passed to TeX was:\n"
1273 " %s\n" % self
.texrunner
.expr
.replace("\n", "\n ").rstrip() +
1274 "After parsing the return message from TeX, the following was left:\n" +
1275 reduce(lambda x
, y
: "%s %s\n" % (x
,y
), firstlines
, "").rstrip())
1277 return self
.description
1280 class TexResultWarning(TexResultError
):
1281 """as above, but with different handling of the exception
1282 - when this exception is raised by a texmessage instance,
1283 the information just get reported and the execution continues"""
1288 """validates/invalidates TeX/LaTeX response"""
1290 def check(self
, texrunner
):
1291 """check a Tex/LaTeX response and respond appropriate
1292 - read the texrunners texmessageparsed attribute
1293 - if there is an problem found, raise an appropriate
1294 exception (TexResultError or TexResultWarning)
1295 - remove any valid and identified TeX/LaTeX response
1296 from the texrunners texmessageparsed attribute
1297 -> finally, there should be nothing left in there,
1298 otherwise it is interpreted as an error"""
1301 class texmessage
: pass
1304 class _texmessagestart(texmessage
):
1305 """validates TeX/LaTeX startup"""
1307 __implements__
= _Itexmessage
1309 startpattern
= re
.compile(r
"This is [-0-9a-zA-Z\s_]*TeX")
1311 def check(self
, texrunner
):
1312 m
= self
.startpattern
.search(texrunner
.texmessageparsed
)
1314 raise TexResultError("TeX startup failed", texrunner
)
1315 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[m
.end():]
1317 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.split("%s.tex" % texrunner
.texfilename
, 1)[1]
1318 except (IndexError, ValueError):
1319 raise TexResultError("TeX running startup file failed", texrunner
)
1321 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.split("*! Undefined control sequence.\n<*> \\raiseerror\n %\n", 1)[1]
1322 except (IndexError, ValueError):
1323 raise TexResultError("TeX scrollmode check failed", texrunner
)
1326 class _texmessagenoaux(texmessage
):
1327 """allows for LaTeXs no-aux-file warning"""
1329 __implements__
= _Itexmessage
1331 def check(self
, texrunner
):
1333 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s.aux." % texrunner
.texfilename
, 1)
1334 texrunner
.texmessageparsed
= s1
+ s2
1335 except (IndexError, ValueError):
1337 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s%s%s.aux." % (os
.curdir
,
1339 texrunner
.texfilename
), 1)
1340 texrunner
.texmessageparsed
= s1
+ s2
1341 except (IndexError, ValueError):
1345 class _texmessageinputmarker(texmessage
):
1346 """validates the PyXInputMarker"""
1348 __implements__
= _Itexmessage
1350 def check(self
, texrunner
):
1352 s1
, s2
= texrunner
.texmessageparsed
.split("PyXInputMarker:executeid=%s:" % texrunner
.executeid
, 1)
1353 texrunner
.texmessageparsed
= s1
+ s2
1354 except (IndexError, ValueError):
1355 raise TexResultError("PyXInputMarker expected", texrunner
)
1358 class _texmessagepyxbox(texmessage
):
1359 """validates the PyXBox output"""
1361 __implements__
= _Itexmessage
1363 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:")
1365 def check(self
, texrunner
):
1366 m
= self
.pattern
.search(texrunner
.texmessageparsed
)
1367 if m
and m
.group("page") == str(texrunner
.page
):
1368 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
1370 raise TexResultError("PyXBox expected", texrunner
)
1373 class _texmessagepyxpageout(texmessage
):
1374 """validates the dvi shipout message (writing a page to the dvi file)"""
1376 __implements__
= _Itexmessage
1378 def check(self
, texrunner
):
1380 s1
, s2
= texrunner
.texmessageparsed
.split("[80.121.88.%s]" % texrunner
.page
, 1)
1381 texrunner
.texmessageparsed
= s1
+ s2
1382 except (IndexError, ValueError):
1383 raise TexResultError("PyXPageOutMarker expected", texrunner
)
1386 class _texmessagetexend(texmessage
):
1387 """validates TeX/LaTeX finish"""
1389 __implements__
= _Itexmessage
1391 def check(self
, texrunner
):
1393 s1
, s2
= texrunner
.texmessageparsed
.split("(%s.aux)" % texrunner
.texfilename
, 1)
1394 texrunner
.texmessageparsed
= s1
+ s2
1395 except (IndexError, ValueError):
1397 s1
, s2
= texrunner
.texmessageparsed
.split("(%s%s%s.aux)" % (os
.curdir
,
1399 texrunner
.texfilename
), 1)
1400 texrunner
.texmessageparsed
= s1
+ s2
1401 except (IndexError, ValueError):
1404 s1
, s2
= texrunner
.texmessageparsed
.split("(see the transcript file for additional information)", 1)
1405 texrunner
.texmessageparsed
= s1
+ s2
1406 except (IndexError, ValueError):
1408 dvipattern
= re
.compile(r
"Output written on %s\.dvi \((?P<page>\d+) pages?, \d+ bytes\)\." % texrunner
.texfilename
)
1409 m
= dvipattern
.search(texrunner
.texmessageparsed
)
1412 raise TexResultError("TeX dvifile messages expected", texrunner
)
1413 if m
.group("page") != str(texrunner
.page
):
1414 raise TexResultError("wrong number of pages reported", texrunner
)
1415 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
1418 s1
, s2
= texrunner
.texmessageparsed
.split("No pages of output.", 1)
1419 texrunner
.texmessageparsed
= s1
+ s2
1420 except (IndexError, ValueError):
1421 raise TexResultError("no dvifile expected")
1423 s1
, s2
= texrunner
.texmessageparsed
.split("Transcript written on %s.log." % texrunner
.texfilename
, 1)
1424 texrunner
.texmessageparsed
= s1
+ s2
1425 except (IndexError, ValueError):
1426 raise TexResultError("TeX logfile message expected")
1429 class _texmessageemptylines(texmessage
):
1430 """validates empty and "*-only" (TeX/LaTeX input marker in interactive mode) lines"""
1432 __implements__
= _Itexmessage
1434 pattern
= re
.compile(r
"^\*?\n", re
.M
)
1436 def check(self
, texrunner
):
1437 m
= self
.pattern
.search(texrunner
.texmessageparsed
)
1439 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
1440 m
= self
.pattern
.search(texrunner
.texmessageparsed
)
1443 class _texmessageload(texmessage
):
1444 """validates inclusion of arbitrary files
1445 - the matched pattern is "(<filename> <arbitrary other stuff>)", where
1446 <fielname> is a readable file and other stuff can be anything
1447 - "(" and ")" must be used consistent (otherwise this validator just does nothing)
1448 - this is not always wanted, but we just assume that file inclusion is fine"""
1450 __implements__
= _Itexmessage
1452 pattern
= re
.compile(r
"\((?P<filename>[^()\s\n]+)[^()]*\)")
1454 def baselevels(self
, s
, maxlevel
=1, brackets
="()"):
1455 """strip parts of a string above a given bracket level
1456 - return a modified (some parts might be removed) version of the string s
1457 where all parts inside brackets with level higher than maxlevel are
1459 - if brackets do not match (number of left and right brackets is wrong
1460 or at some points there were more right brackets than left brackets)
1461 just return the unmodified string"""
1466 if c
== brackets
[0]:
1468 if level
> highestlevel
:
1469 highestlevel
= level
1470 if level
<= maxlevel
:
1472 if c
== brackets
[1]:
1474 if level
== 0 and highestlevel
> 0:
1477 def check(self
, texrunner
):
1478 lowestbracketlevel
= self
.baselevels(texrunner
.texmessageparsed
)
1479 if lowestbracketlevel
is not None:
1480 m
= self
.pattern
.search(lowestbracketlevel
)
1482 if os
.access(m
.group("filename"), os
.R_OK
):
1483 lowestbracketlevel
= lowestbracketlevel
[:m
.start()] + lowestbracketlevel
[m
.end():]
1486 m
= self
.pattern
.search(lowestbracketlevel
)
1488 texrunner
.texmessageparsed
= lowestbracketlevel
1491 class _texmessageloadfd(_texmessageload
):
1492 """validates the inclusion of font description files (fd-files)
1493 - works like _texmessageload
1494 - filename must end with .fd and no further text is allowed"""
1496 pattern
= re
.compile(r
"\((?P<filename>[^)]+.fd)\)")
1499 class _texmessagegraphicsload(_texmessageload
):
1500 """validates the inclusion of files as the graphics packages writes it
1501 - works like _texmessageload, but using "<" and ">" as delimiters
1502 - filename must end with .eps and no further text is allowed"""
1504 pattern
= re
.compile(r
"<(?P<filename>[^>]+.eps)>")
1506 def baselevels(self
, s
, brackets
="<>", **args
):
1507 return _texmessageload
.baselevels(self
, s
, brackets
=brackets
, **args
)
1510 #class _texmessagepdfmapload(_texmessageload):
1511 # """validates the inclusion of files as the graphics packages writes it
1512 # - works like _texmessageload, but using "{" and "}" as delimiters
1513 # - filename must end with .map and no further text is allowed"""
1515 # pattern = re.compile(r"{(?P<filename>[^}]+.map)}")
1517 # def baselevels(self, s, brackets="{}", **args):
1518 # return _texmessageload.baselevels(self, s, brackets=brackets, **args)
1521 class _texmessageignore(_texmessageload
):
1522 """validates any TeX/LaTeX response
1523 - this might be used, when the expression is ok, but no suitable texmessage
1525 - PLEASE: - consider writing suitable tex message parsers
1526 - share your ideas/problems/solutions with others (use the PyX mailing lists)"""
1528 __implements__
= _Itexmessage
1530 def check(self
, texrunner
):
1531 texrunner
.texmessageparsed
= ""
1534 texmessage
.start
= _texmessagestart()
1535 texmessage
.noaux
= _texmessagenoaux()
1536 texmessage
.inputmarker
= _texmessageinputmarker()
1537 texmessage
.pyxbox
= _texmessagepyxbox()
1538 texmessage
.pyxpageout
= _texmessagepyxpageout()
1539 texmessage
.texend
= _texmessagetexend()
1540 texmessage
.emptylines
= _texmessageemptylines()
1541 texmessage
.load
= _texmessageload()
1542 texmessage
.loadfd
= _texmessageloadfd()
1543 texmessage
.graphicsload
= _texmessagegraphicsload()
1544 texmessage
.ignore
= _texmessageignore()
1547 ###############################################################################
1549 # - texsettings are used to modify a TeX/LaTeX expression
1550 # to fit the users need
1551 # - texsettings have an order attribute (id), because the order is usually
1552 # important (e.g. you can not change the fontsize in mathmode in LaTeX)
1553 # - lower id's get applied later (are more outside -> mathmode has a higher
1555 # - order attributes are used to exclude complementary settings (with the
1557 # - texsettings might (in rare cases) depend on each other (e.g. parbox and
1559 ###############################################################################
1563 - modifies a TeX/LaTeX expression"""
1566 """order attribute for TeX settings
1567 - higher id's will be applied first (most inside)"""
1570 """marks exclusive effect of the setting
1571 - when set, settings with this id exclude each other
1572 - when unset, settings with this id do not exclude each other"""
1574 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1575 """modifies the TeX/LaTeX expression
1576 - expr is the original expression
1577 - the return value is the modified expression
1578 - texsettings contains a list of all texsettings (in case a tex setting
1579 depends on another texsetting)
1580 - texrunner contains the texrunner in case the texsetting depends
1583 def __cmp__(self
, other
):
1584 """compare texsetting with other
1585 - other is a texsetting as well
1586 - performs an id comparison (NOTE: higher id's come first!!!)"""
1589 # preamble settings for texsetting macros
1590 _texsettingpreamble
= ""
1596 def __cmp__(self
, other
):
1597 return -cmp(self
.id, other
.id) # note the sign!!!
1600 class halign(_texsetting
):
1601 """horizontal alignment
1602 the left/right splitting is performed within the PyXBox routine"""
1604 __implements__
= _Itexsetting
1608 def __init__(self
, hratio
):
1609 self
.hratio
= hratio
1611 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1612 return r
"\gdef\PyXHAlign{%.5f}%s" % (self
.hratio
, expr
)
1614 halign
.left
= halign(0)
1615 halign
.center
= halign(0.5)
1616 halign
.right
= halign(1)
1619 _texsettingpreamble
+= "\\newbox\\PyXBoxVAlign%\n\\newdimen\PyXDimenVAlign%\n"
1621 class valign(_texsetting
):
1622 "vertical alignment"
1627 class _valigntop(valign
):
1629 __implements__
= _Itexsetting
1631 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1632 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\lower\ht\PyXBoxVAlign\box\PyXBoxVAlign" % expr
1635 class _valignmiddle(valign
):
1637 __implements__
= _Itexsetting
1639 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1640 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\PyXDimenVAlign=0.5\ht\PyXBoxVAlign\advance\PyXDimenVAlign by -0.5\dp\PyXBoxVAlign\lower\PyXDimenVAlign\box\PyXBoxVAlign" % expr
1643 class _valignbottom(valign
):
1645 __implements__
= _Itexsetting
1647 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1648 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\raise\dp\PyXBoxVAlign\box\PyXBoxVAlign" % expr
1651 class _valignbaseline(valign
):
1653 __implements__
= _Itexsetting
1655 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1656 for texsetting
in texsettings
:
1657 if isinstance(texsetting
, parbox
):
1658 raise RuntimeError("valign.baseline: specify top/middle/bottom baseline for parbox")
1662 class _valignxxxbaseline(valign
):
1664 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1665 for texsetting
in texsettings
:
1666 if isinstance(texsetting
, parbox
):
1669 raise RuntimeError(self
.noparboxmessage
)
1673 class _valigntopbaseline(_valignxxxbaseline
):
1675 __implements__
= _Itexsetting
1677 noparboxmessage
= "valign.topbaseline: no parbox defined"
1680 class _valignmiddlebaseline(_valignxxxbaseline
):
1682 __implements__
= _Itexsetting
1684 noparboxmessage
= "valign.middlebaseline: no parbox defined"
1687 class _valignbottombaseline(_valignxxxbaseline
):
1689 __implements__
= _Itexsetting
1691 noparboxmessage
= "valign.bottombaseline: no parbox defined"
1694 valign
.top
= _valigntop()
1695 valign
.middle
= _valignmiddle()
1696 valign
.center
= valign
.middle
1697 valign
.bottom
= _valignbottom()
1698 valign
.baseline
= _valignbaseline()
1699 valign
.topbaseline
= _valigntopbaseline()
1700 valign
.middlebaseline
= _valignmiddlebaseline()
1701 valign
.centerbaseline
= valign
.middlebaseline
1702 valign
.bottombaseline
= _valignbottombaseline()
1705 _texsettingpreamble
+= "\\newbox\\PyXBoxVBox%\n\\newdimen\PyXDimenVBox%\n"
1708 class _parbox(_texsetting
):
1709 "goes into the vertical mode"
1711 __implements__
= _Itexsetting
1715 def __init__(self
, width
):
1718 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1720 for texsetting
in texsettings
:
1721 if isinstance(texsetting
, valign
):
1722 if (not isinstance(texsetting
, _valigntop
) and
1723 not isinstance(texsetting
, _valignmiddle
) and
1724 not isinstance(texsetting
, _valignbottom
) and
1725 not isinstance(texsetting
, _valigntopbaseline
)):
1726 if isinstance(texsetting
, _valignmiddlebaseline
):
1728 elif isinstance(texsetting
, _valignbottombaseline
):
1731 raise RuntimeError("parbox couldn'd identify the valign instance")
1732 if boxkind
== "vcenter":
1733 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
)
1735 return r
"\linewidth%.5ftruept\%s{\hsize\linewidth{%s}}" % (self
.width
* 72.27 / 72, boxkind
, expr
)
1738 class parbox(_parbox
):
1740 def __init__(self
, width
):
1741 _parbox
.__init
__(self
, unit
.topt(width
))
1744 class vshift(_texsetting
):
1751 class _vshiftchar(vshift
):
1752 "vertical down shift by a fraction of a character height"
1754 def __init__(self
, lowerratio
, heightstr
="0"):
1755 self
.lowerratio
= lowerratio
1756 self
.heightstr
= heightstr
1758 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1759 return r
"\setbox0\hbox{{%s}}\lower%.5f\ht0\hbox{{%s}}" % (self
.heightstr
, self
.lowerratio
, expr
)
1762 class _vshiftmathaxis(vshift
):
1763 "vertical down shift by the height of the math axis"
1765 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1766 return r
"\setbox0\hbox{$\vcenter{\vrule width0pt}$}\lower\ht0\hbox{{%s}}" % expr
1769 vshift
.char
= _vshiftchar
1770 vshift
.bottomzero
= vshift
.char(0)
1771 vshift
.middlezero
= vshift
.char(0.5)
1772 vshift
.centerzero
= vshift
.middlezero
1773 vshift
.topzero
= vshift
.char(1)
1774 vshift
.mathaxis
= _vshiftmathaxis()
1777 class _mathmode(_texsetting
):
1780 __implements__
= _Itexsetting
1784 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1785 return r
"$\displaystyle{%s}$" % expr
1787 mathmode
= _mathmode()
1790 defaultsizelist
= ["normalsize", "large", "Large", "LARGE", "huge", "Huge", None, "tiny", "scriptsize", "footnotesize", "small"]
1792 class size(_texsetting
):
1795 __implements__
= _Itexsetting
1799 def __init__(self
, expr
, sizelist
=defaultsizelist
):
1800 if helper
.isinteger(expr
):
1801 if expr
>= 0 and expr
< sizelist
.index(None):
1802 self
.size
= sizelist
[expr
]
1803 elif expr
< 0 and expr
+ len(sizelist
) > sizelist
.index(None):
1804 self
.size
= sizelist
[expr
]
1806 raise IndexError("index out of sizelist range")
1810 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1811 return r
"\%s{%s}" % (self
.size
, expr
)
1813 for s
in defaultsizelist
:
1815 size
.__dict
__[s
] = size(s
)
1818 ###############################################################################
1820 ###############################################################################
1823 class _readpipe(threading
.Thread
):
1824 """threaded reader of TeX/LaTeX output
1825 - sets an event, when a specific string in the programs output is found
1826 - sets an event, when the terminal ends"""
1828 def __init__(self
, pipe
, expectqueue
, gotevent
, gotqueue
, quitevent
):
1829 """initialize the reader
1830 - pipe: file to be read from
1831 - expectqueue: keeps the next InputMarker to be wait for
1832 - gotevent: the "got InputMarker" event
1833 - gotqueue: a queue containing the lines recieved from TeX/LaTeX
1834 - quitevent: the "end of terminal" event"""
1835 threading
.Thread
.__init
__(self
)
1836 self
.setDaemon(1) # don't care if the output might not be finished (nevertheless, it shouldn't happen)
1838 self
.expectqueue
= expectqueue
1839 self
.gotevent
= gotevent
1840 self
.gotqueue
= gotqueue
1841 self
.quitevent
= quitevent
1846 """thread routine"""
1847 read
= self
.pipe
.readline() # read, what comes in
1849 self
.expect
= self
.expectqueue
.get_nowait() # read, what should be expected
1853 # universal EOL handling (convert everything into unix like EOLs)
1854 read
.replace("\r", "")
1855 if not len(read
) or read
[-1] != "\n":
1857 self
.gotqueue
.put(read
) # report, whats readed
1858 if self
.expect
is not None and read
.find(self
.expect
) != -1:
1859 self
.gotevent
.set() # raise the got event, when the output was expected (XXX: within a single line)
1860 read
= self
.pipe
.readline() # read again
1862 self
.expect
= self
.expectqueue
.get_nowait()
1867 if self
.expect
is not None and self
.expect
.find("PyXInputMarker") != -1:
1868 raise RuntimeError("TeX/LaTeX finished unexpectedly")
1869 self
.quitevent
.set()
1873 class _textbox(box
._rect
, base
.PSCmd
):
1874 """basically a box.rect, but it contains a text created by the texrunner
1875 - texrunner._text and texrunner.text return such an object
1876 - _textbox instances can be inserted into a canvas
1877 - the output is contained in a page of the dvifile available thru the texrunner"""
1879 def __init__(self
, x
, y
, left
, right
, height
, depth
, texrunner
, dvinumber
, page
, *styles
):
1880 self
.texttrafo
= trafo
._translate
(x
, y
)
1881 box
._rect
.__init
__(self
, x
- left
, y
- depth
,
1882 left
+ right
, depth
+ height
,
1883 abscenter
= (left
, depth
))
1884 self
.texrunner
= texrunner
1885 self
.dvinumber
= dvinumber
1887 self
.styles
= styles
1889 def transform(self
, *trafos
):
1890 box
._rect
.transform(self
, *trafos
)
1891 for trafo
in trafos
:
1892 self
.texttrafo
= trafo
* self
.texttrafo
1894 def marker(self
, marker
):
1895 return self
.texttrafo
.apply(*self
.texrunner
.marker(self
.dvinumber
, self
.page
, marker
))
1899 for cmd
in self
.styles
:
1900 result
.extend(cmd
.prolog())
1901 return result
+ self
.texrunner
.prolog(self
.dvinumber
, self
.page
)
1903 def write(self
, file):
1904 canvas
._gsave
().write(file) # XXX: canvas?, constructor call needed?
1905 self
.texttrafo
.write(file)
1906 for style
in self
.styles
:
1908 self
.texrunner
.write(file, self
.dvinumber
, self
.page
)
1909 canvas
._grestore
().write(file)
1913 class textbox(_textbox
):
1915 def __init__(self
, x
, y
, left
, right
, height
, depth
, texrunner
, dvinumber
, page
, *styles
):
1916 _textbox
.__init
__(self
, unit
.topt(x
), unit
.topt(y
), unit
.topt(left
), unit
.topt(right
),
1917 unit
.topt(height
), unit
.topt(depth
), texrunner
, dvinumber
, page
, *styles
)
1920 def _cleantmp(texrunner
):
1921 """get rid of temporary files
1922 - function to be registered by atexit
1923 - files contained in usefiles are kept"""
1924 if texrunner
.texruns
: # cleanup while TeX is still running?
1925 texrunner
.texruns
= 0
1926 texrunner
.texdone
= 1
1927 texrunner
.expectqueue
.put_nowait(None) # do not expect any output anymore
1928 if texrunner
.mode
== "latex": # try to immediately quit from TeX or LaTeX
1929 texrunner
.texinput
.write("\n\\catcode`\\@11\\relax\\@@end\n")
1931 texrunner
.texinput
.write("\n\\end\n")
1932 texrunner
.texinput
.close() # close the input queue and
1933 if not texrunner
.waitforevent(texrunner
.quitevent
): # wait for finish of the output
1934 return # didn't got a quit from TeX -> we can't do much more
1935 for usefile
in texrunner
.usefiles
:
1936 extpos
= usefile
.rfind(".")
1938 os
.rename(texrunner
.texfilename
+ usefile
[extpos
:], usefile
)
1941 for file in glob
.glob("%s.*" % texrunner
.texfilename
):
1946 if texrunner
.texdebug
is not None:
1948 texrunner
.texdebug
.close()
1949 texrunner
.texdebug
= None
1954 # texrunner state exceptions
1955 class TexRunsError(Exception): pass
1956 class TexDoneError(Exception): pass
1957 class TexNotInPreambleModeError(Exception): pass
1961 """TeX/LaTeX interface
1962 - runs TeX/LaTeX expressions instantly
1963 - checks TeX/LaTeX response
1964 - the instance variable texmessage stores the last TeX
1965 response as a string
1966 - the instance variable texmessageparsed stores a parsed
1967 version of texmessage; it should be empty after
1968 texmessage.check was called, otherwise a TexResultError
1970 - the instance variable errordebug controls the verbose
1971 level of TexResultError"""
1973 def __init__(self
, mode
="tex",
1978 fontmaps
=config
.get("text", "fontmaps", "psfonts.map"),
1979 waitfortex
=config
.getint("text", "waitfortex", 60),
1980 showwaitfortex
=config
.getint("text", "showwaitfortex", 5),
1981 texipc
=config
.getboolean("text", "texipc", 0),
1987 texmessagestart
=texmessage
.start
,
1988 texmessagedocclass
=texmessage
.load
,
1989 texmessagebegindoc
=(texmessage
.load
, texmessage
.noaux
),
1990 texmessageend
=texmessage
.texend
,
1991 texmessagedefaultpreamble
=texmessage
.load
,
1992 texmessagedefaultrun
=texmessage
.loadfd
):
1994 if mode
!= "tex" and mode
!= "latex":
1995 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
1998 self
.docclass
= docclass
1999 self
.docopt
= docopt
2000 self
.usefiles
= helper
.ensurelist(usefiles
)
2001 self
.fontmap
= readfontmap(fontmaps
.split())
2002 self
.waitfortex
= waitfortex
2003 self
.showwaitfortex
= showwaitfortex
2004 self
.texipc
= texipc
2005 if texdebug
is not None:
2006 if texdebug
[-4:] == ".tex":
2007 self
.texdebug
= open(texdebug
, "w")
2009 self
.texdebug
= open("%s.tex" % texdebug
, "w")
2011 self
.texdebug
= None
2012 self
.dvidebug
= dvidebug
2013 self
.errordebug
= errordebug
2014 self
.dvicopy
= dvicopy
2015 self
.pyxgraphics
= pyxgraphics
2016 texmessagestart
= helper
.ensuresequence(texmessagestart
)
2017 helper
.checkattr(texmessagestart
, allowmulti
=(texmessage
,))
2018 self
.texmessagestart
= texmessagestart
2019 texmessagedocclass
= helper
.ensuresequence(texmessagedocclass
)
2020 helper
.checkattr(texmessagedocclass
, allowmulti
=(texmessage
,))
2021 self
.texmessagedocclass
= texmessagedocclass
2022 texmessagebegindoc
= helper
.ensuresequence(texmessagebegindoc
)
2023 helper
.checkattr(texmessagebegindoc
, allowmulti
=(texmessage
,))
2024 self
.texmessagebegindoc
= texmessagebegindoc
2025 texmessageend
= helper
.ensuresequence(texmessageend
)
2026 helper
.checkattr(texmessageend
, allowmulti
=(texmessage
,))
2027 self
.texmessageend
= texmessageend
2028 texmessagedefaultpreamble
= helper
.ensuresequence(texmessagedefaultpreamble
)
2029 helper
.checkattr(texmessagedefaultpreamble
, allowmulti
=(texmessage
,))
2030 self
.texmessagedefaultpreamble
= texmessagedefaultpreamble
2031 texmessagedefaultrun
= helper
.ensuresequence(texmessagedefaultrun
)
2032 helper
.checkattr(texmessagedefaultrun
, allowmulti
=(texmessage
,))
2033 self
.texmessagedefaultrun
= texmessagedefaultrun
2037 self
.preamblemode
= 1
2043 savetempdir
= tempfile
.tempdir
2044 tempfile
.tempdir
= os
.curdir
2045 self
.texfilename
= os
.path
.basename(tempfile
.mktemp())
2046 tempfile
.tempdir
= savetempdir
2048 def waitforevent(self
, event
):
2049 """waits verbosely with an timeout for an event
2050 - observes an event while periodly while printing messages
2051 - returns the status of the event (isSet)
2052 - does not clear the event"""
2053 if self
.showwaitfortex
:
2056 while waited
< self
.waitfortex
and not hasevent
:
2057 if self
.waitfortex
- waited
> self
.showwaitfortex
:
2058 event
.wait(self
.showwaitfortex
)
2059 waited
+= self
.showwaitfortex
2061 event
.wait(self
.waitfortex
- waited
)
2062 waited
+= self
.waitfortex
- waited
2063 hasevent
= event
.isSet()
2065 if waited
< self
.waitfortex
:
2066 sys
.stderr
.write("*** PyX INFO: still waiting for %s after %i seconds...\n" % (self
.mode
, waited
))
2068 sys
.stderr
.write("*** PyX ERROR: the timeout of %i seconds expired and %s did not respond.\n" % (waited
, self
.mode
))
2071 event
.wait(self
.waitfortex
)
2072 return event
.isSet()
2074 def execute(self
, expr
, *checks
):
2075 """executes expr within TeX/LaTeX
2076 - if self.texruns is not yet set, TeX/LaTeX is initialized,
2077 self.texruns is set and self.preamblemode is set
2078 - the method must not be called, when self.texdone is already set
2079 - expr should be a string or None
2080 - when expr is None, TeX/LaTeX is stopped, self.texruns is unset and
2081 while self.texdone becomes set
2082 - when self.preamblemode is set, the expr is passed directly to TeX/LaTeX
2083 - when self.preamblemode is unset, the expr is passed to \ProcessPyXBox
2085 if not self
.texruns
:
2086 if self
.texdebug
is not None:
2087 self
.texdebug
.write("%% PyX %s texdebug file\n" % version
.version
)
2088 self
.texdebug
.write("%% mode: %s\n" % self
.mode
)
2089 self
.texdebug
.write("%% date: %s\n" % time
.asctime(time
.localtime(time
.time())))
2090 for usefile
in self
.usefiles
:
2091 extpos
= usefile
.rfind(".")
2093 os
.rename(usefile
, self
.texfilename
+ usefile
[extpos
:])
2096 texfile
= open("%s.tex" % self
.texfilename
, "w") # start with filename -> creates dvi file with that name
2097 texfile
.write("\\relax%\n")
2104 self
.texinput
, self
.texoutput
= os
.popen4("%s%s %s" % (self
.mode
, ipcflag
, self
.texfilename
), "t", 0)
2106 # XXX: workaround for MS Windows (bufsize = 0 makes trouble!?)
2107 self
.texinput
, self
.texoutput
= os
.popen4("%s%s %s" % (self
.mode
, ipcflag
, self
.texfilename
), "t")
2108 atexit
.register(_cleantmp
, self
)
2109 self
.expectqueue
= Queue
.Queue(1) # allow for a single entry only -> keeps the next InputMarker to be wait for
2110 self
.gotevent
= threading
.Event() # keeps the got inputmarker event
2111 self
.gotqueue
= Queue
.Queue(0) # allow arbitrary number of entries
2112 self
.quitevent
= threading
.Event() # keeps for end of terminal event
2113 self
.readoutput
= _readpipe(self
.texoutput
, self
.expectqueue
, self
.gotevent
, self
.gotqueue
, self
.quitevent
)
2115 oldpreamblemode
= self
.preamblemode
2116 self
.preamblemode
= 1
2117 self
.execute("\\scrollmode\n\\raiseerror%\n" # switch to and check scrollmode
2118 "\\def\\PyX{P\\kern-.3em\\lower.5ex\hbox{Y}\kern-.18em X}%\n" # just the PyX Logo
2119 "\\gdef\\PyXHAlign{0}%\n" # global PyXHAlign (0.0-1.0) for the horizontal alignment, default to 0
2120 "\\newbox\\PyXBox%\n" # PyXBox will contain the output
2121 "\\newbox\\PyXBoxHAligned%\n" # PyXBox will contain the horizontal aligned output
2122 "\\newdimen\\PyXDimenHAlignLT%\n" # PyXDimenHAlignLT/RT will contain the left/right extent
2123 "\\newdimen\\PyXDimenHAlignRT%\n" +
2124 _texsettingpreamble
+ # insert preambles for texsetting macros
2125 "\\long\\def\\ProcessPyXBox#1#2{%\n" # the ProcessPyXBox definition (#1 is expr, #2 is page number)
2126 "\\setbox\\PyXBox=\\hbox{{#1}}%\n" # push expression into PyXBox
2127 "\\PyXDimenHAlignLT=\\PyXHAlign\\wd\\PyXBox%\n" # calculate the left/right extent
2128 "\\PyXDimenHAlignRT=\\wd\\PyXBox%\n"
2129 "\\advance\\PyXDimenHAlignRT by -\\PyXDimenHAlignLT%\n"
2130 "\\gdef\\PyXHAlign{0}%\n" # reset the PyXHAlign to the default 0
2131 "\\immediate\\write16{PyXBox:page=#2," # write page and extents of this box to stdout
2132 "lt=\\the\\PyXDimenHAlignLT,"
2133 "rt=\\the\\PyXDimenHAlignRT,"
2134 "ht=\\the\\ht\\PyXBox,"
2135 "dp=\\the\\dp\\PyXBox:}%\n"
2136 "\\setbox\\PyXBoxHAligned=\\hbox{\\kern-\\PyXDimenHAlignLT\\box\\PyXBox}%\n" # align horizontally
2137 "\\ht\\PyXBoxHAligned0pt%\n" # baseline alignment (hight to zero)
2138 "{\\count0=80\\count1=121\\count2=88\\count3=#2\\shipout\\box\\PyXBoxHAligned}}%\n" # shipout PyXBox to Page 80.121.88.<page number>
2139 "\\def\\PyXInput#1{\\immediate\\write16{PyXInputMarker:executeid=#1:}}%\n" # write PyXInputMarker to stdout
2140 "\\def\\PyXMarker#1{\\special{PyX:marker #1}}%\n", # write PyXMarker special into the dvi-file
2141 *self
.texmessagestart
)
2142 os
.remove("%s.tex" % self
.texfilename
)
2143 if self
.mode
== "tex":
2144 if len(self
.lfs
) > 4 and self
.lfs
[-4:] == ".lfs":
2147 lfsname
= "%s.lfs" % self
.lfs
2148 for fulllfsname
in [lfsname
,
2149 os
.path
.join(sys
.prefix
, "share", "pyx", lfsname
),
2150 os
.path
.join(os
.path
.dirname(__file__
), "lfs", lfsname
)]:
2152 lfsfile
= open(fulllfsname
, "r")
2153 lfsdef
= lfsfile
.read()
2159 allfiles
= (glob
.glob("*.lfs") +
2160 glob
.glob(os
.path
.join(sys
.prefix
, "share", "pyx", "*.lfs")) +
2161 glob
.glob(os
.path
.join(os
.path
.dirname(__file__
), "lfs", "*.lfs")))
2165 open(f
, "r").close()
2166 lfsnames
.append(os
.path
.basename(f
)[:-4])
2171 raise IOError("file '%s' is not available or not readable. Available LaTeX font size files (*.lfs): %s" % (lfsname
, lfsnames
))
2173 raise IOError("file '%s' is not available or not readable. No LaTeX font size files (*.lfs) available. Check your installation." % lfsname
)
2174 self
.execute(lfsdef
)
2175 self
.execute("\\normalsize%\n")
2176 self
.execute("\\newdimen\\linewidth%\n")
2177 elif self
.mode
== "latex":
2178 if self
.pyxgraphics
:
2179 for pyxdef
in ["pyx.def",
2180 os
.path
.join(sys
.prefix
, "share", "pyx", "pyx.def"),
2181 os
.path
.join(os
.path
.dirname(__file__
), "..", "contrib", "pyx.def")]:
2183 open(pyxdef
, "r").close()
2188 IOError("file 'pyx.def' is not available or not readable. Check your installation or turn off the pyxgraphics option.")
2189 pyxdef
= os
.path
.abspath(pyxdef
).replace(os
.sep
, "/")
2190 self
.execute("\\makeatletter%\n"
2191 "\\let\\saveProcessOptions=\\ProcessOptions%\n"
2192 "\\def\\ProcessOptions{%\n"
2193 "\\def\\Gin@driver{" + pyxdef
+ "}%\n"
2194 "\\def\\c@lor@namefile{dvipsnam.def}%\n"
2195 "\\saveProcessOptions}%\n"
2197 if self
.docopt
is not None:
2198 self
.execute("\\documentclass[%s]{%s}" % (self
.docopt
, self
.docclass
), *self
.texmessagedocclass
)
2200 self
.execute("\\documentclass{%s}" % self
.docclass
, *self
.texmessagedocclass
)
2201 self
.preamblemode
= oldpreamblemode
2203 if expr
is not None: # TeX/LaTeX should process expr
2204 self
.expectqueue
.put_nowait("PyXInputMarker:executeid=%i:" % self
.executeid
)
2205 if self
.preamblemode
:
2206 self
.expr
= ("%s%%\n" % expr
+
2207 "\\PyXInput{%i}%%\n" % self
.executeid
)
2210 self
.expr
= ("\\ProcessPyXBox{%s%%\n}{%i}%%\n" % (expr
, self
.page
) +
2211 "\\PyXInput{%i}%%\n" % self
.executeid
)
2212 else: # TeX/LaTeX should be finished
2213 self
.expectqueue
.put_nowait("Transcript written on %s.log" % self
.texfilename
)
2214 if self
.mode
== "latex":
2215 self
.expr
= "\\end{document}%\n"
2217 self
.expr
= "\\end%\n"
2218 if self
.texdebug
is not None:
2219 self
.texdebug
.write(self
.expr
)
2220 self
.texinput
.write(self
.expr
)
2221 gotevent
= self
.waitforevent(self
.gotevent
)
2222 self
.gotevent
.clear()
2223 if expr
is None and gotevent
: # TeX/LaTeX should have finished
2226 self
.texinput
.close() # close the input queue and
2227 gotevent
= self
.waitforevent(self
.quitevent
) # wait for finish of the output
2229 self
.texmessage
= ""
2231 self
.texmessage
+= self
.gotqueue
.get_nowait()
2234 self
.texmessageparsed
= self
.texmessage
2236 if expr
is not None:
2237 texmessage
.inputmarker
.check(self
)
2238 if not self
.preamblemode
:
2239 texmessage
.pyxbox
.check(self
)
2240 texmessage
.pyxpageout
.check(self
)
2241 for check
in checks
:
2244 except TexResultWarning
:
2245 traceback
.print_exc()
2246 texmessage
.emptylines
.check(self
)
2247 if len(self
.texmessageparsed
):
2248 raise TexResultError("unhandled TeX response (might be an error)", self
)
2250 raise TexResultError("TeX didn't respond as expected within the timeout period (%i seconds)." % self
.waitfortex
, self
)
2252 def finishdvi(self
):
2253 "finish TeX/LaTeX and read the dvifile"
2254 self
.execute(None, *self
.texmessageend
)
2256 os
.system("dvicopy %(t)s.dvi %(t)s.dvicopy > %(t)s.dvicopyout 2> %(t)s.dvicopyerr" % {"t": self
.texfilename
})
2257 dvifilename
= "%s.dvicopy" % self
.texfilename
2259 dvifilename
= "%s.dvi" % self
.texfilename
2261 dvifile
= DVIFile(dvifilename
, self
.fontmap
, debug
=self
.dvidebug
)
2262 self
.dvifiles
.append(dvifile
)
2263 self
.dvifiles
[-1].readfile()
2266 def marker(self
, dvinumber
, page
, marker
):
2267 "return the marker position"
2268 if not self
.texipc
and not self
.texdone
:
2270 return self
.dvifiles
[dvinumber
].marker(page
, marker
)
2272 def prolog(self
, dvinumber
, page
):
2273 "return the dvifile prolog"
2274 if not self
.texipc
and not self
.texdone
:
2276 return self
.dvifiles
[dvinumber
].prolog(page
)
2278 def write(self
, file, dvinumber
, page
):
2279 "write a page from the dvifile"
2280 if not self
.texipc
and not self
.texdone
:
2282 return self
.dvifiles
[dvinumber
].write(file, page
)
2284 def reset(self
, reinit
=0):
2285 "resets the tex runner to its initial state (upto its record to old dvi file(s))"
2288 if self
.texdebug
is not None:
2289 self
.texdebug
.write("%s\n%% preparing restart of %s\n" % ("%"*80, self
.mode
))
2294 self
.preamblemode
= 1
2295 for expr
, args
in self
.preambles
:
2296 self
.execute(expr
, *args
)
2297 if self
.mode
== "latex":
2298 self
.execute("\\begin{document}", *self
.texmessagebegindoc
)
2299 self
.preamblemode
= 0
2302 self
.preamblemode
= 1
2304 def set(self
, mode
=None,
2311 showwaitfortex
=None,
2318 texmessagestart
=None,
2319 texmessagedocclass
=None,
2320 texmessagebegindoc
=None,
2322 texmessagedefaultpreamble
=None,
2323 texmessagedefaultrun
=None):
2324 """provide a set command for TeX/LaTeX settings
2325 - TeX/LaTeX must not yet been started
2326 - especially needed for the defaultrunner, where no access to
2327 the constructor is available"""
2330 if mode
is not None:
2332 if mode
!= "tex" and mode
!= "latex":
2333 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
2337 if docclass
is not None:
2338 self
.docclass
= docclass
2339 if docopt
is not None:
2340 self
.docopt
= docopt
2341 if usefiles
is not None:
2342 self
.usefiles
= helper
.ensurelist(usefiles
)
2343 if fontmaps
is not None:
2344 self
.fontmap
= readfontmap(fontmaps
.split())
2345 if waitfortex
is not None:
2346 self
.waitfortex
= waitfortex
2347 if showwaitfortex
is not None:
2348 self
.showwaitfortex
= showwaitfortex
2349 if texipc
is not None:
2350 self
.texipc
= texipc
2351 if texdebug
is not None:
2352 if self
.texdebug
is not None:
2353 self
.texdebug
.close()
2354 if texdebug
[-4:] == ".tex":
2355 self
.texdebug
= open(texdebug
, "w")
2357 self
.texdebug
= open("%s.tex" % texdebug
, "w")
2358 if dvidebug
is not None:
2359 self
.dvidebug
= dvidebug
2360 if errordebug
is not None:
2361 self
.errordebug
= errordebug
2362 if dvicopy
is not None:
2363 self
.dvicopy
= dvicopy
2364 if pyxgraphics
is not None:
2365 self
.pyxgraphics
= pyxgraphics
2366 if errordebug
is not None:
2367 self
.errordebug
= errordebug
2368 if texmessagestart
is not None:
2369 texmessagestart
= helper
.ensuresequence(texmessagestart
)
2370 helper
.checkattr(texmessagestart
, allowmulti
=(texmessage
,))
2371 self
.texmessagestart
= texmessagestart
2372 if texmessagedocclass
is not None:
2373 texmessagedocclass
= helper
.ensuresequence(texmessagedocclass
)
2374 helper
.checkattr(texmessagedocclass
, allowmulti
=(texmessage
,))
2375 self
.texmessagedocclass
= texmessagedocclass
2376 if texmessagebegindoc
is not None:
2377 texmessagebegindoc
= helper
.ensuresequence(texmessagebegindoc
)
2378 helper
.checkattr(texmessagebegindoc
, allowmulti
=(texmessage
,))
2379 self
.texmessagebegindoc
= texmessagebegindoc
2380 if texmessageend
is not None:
2381 texmessageend
= helper
.ensuresequence(texmessageend
)
2382 helper
.checkattr(texmessageend
, allowmulti
=(texmessage
,))
2383 self
.texmessageend
= texmessageend
2384 if texmessagedefaultpreamble
is not None:
2385 texmessagedefaultpreamble
= helper
.ensuresequence(texmessagedefaultpreamble
)
2386 helper
.checkattr(texmessagedefaultpreamble
, allowmulti
=(texmessage
,))
2387 self
.texmessagedefaultpreamble
= texmessagedefaultpreamble
2388 if texmessagedefaultrun
is not None:
2389 texmessagedefaultrun
= helper
.ensuresequence(texmessagedefaultrun
)
2390 helper
.checkattr(texmessagedefaultrun
, allowmulti
=(texmessage
,))
2391 self
.texmessagedefaultrun
= texmessagedefaultrun
2393 def preamble(self
, expr
, *args
):
2394 r
"""put something into the TeX/LaTeX preamble
2395 - in LaTeX, this is done before the \begin{document}
2396 (you might use \AtBeginDocument, when you're in need for)
2397 - it is not allowed to call preamble after calling the
2398 text method for the first time (for LaTeX this is needed
2399 due to \begin{document}; in TeX it is forced for compatibility
2400 (you should be able to switch from TeX to LaTeX, if you want,
2401 without breaking something
2402 - preamble expressions must not create any dvi output
2403 - args might contain texmessage instances"""
2404 if self
.texdone
or not self
.preamblemode
:
2405 raise TexNotInPreambleModeError
2406 helper
.checkattr(args
, allowmulti
=(texmessage
,))
2407 args
= helper
.getattrs(args
, texmessage
, default
=self
.texmessagedefaultpreamble
)
2408 self
.execute(expr
, *args
)
2409 self
.preambles
.append((expr
, args
))
2411 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:")
2413 def _text(self
, x
, y
, expr
, *args
):
2414 """create text by passing expr to TeX/LaTeX
2415 - returns a textbox containing the result from running expr thru TeX/LaTeX
2416 - the box center is set to x, y
2417 - *args may contain style parameters, namely:
2418 - an halign instance
2419 - _texsetting instances
2420 - texmessage instances
2421 - trafo._trafo instances
2422 - base.PathStyle instances"""
2424 raise ValueError("None expression is invalid")
2426 self
.reset(reinit
=1)
2427 if self
.preamblemode
:
2428 if self
.mode
== "latex":
2429 self
.execute("\\begin{document}", *self
.texmessagebegindoc
)
2430 self
.preamblemode
= 0
2433 raise RuntimeError("texipc and dvicopy can't be mixed up")
2434 self
.dvifiles
.append(DVIFile("%s.dvi" % self
.texfilename
, self
.fontmap
, debug
=self
.dvidebug
, ipcmode
=1))
2435 helper
.checkattr(args
, allowmulti
=(_texsetting
, texmessage
, trafo
._trafo
, base
.PathStyle
))
2436 #XXX: should we distiguish between StrokeStyle and FillStyle?
2437 texsettings
= helper
.getattrs(args
, _texsetting
, default
=[])
2439 for texsetting
in texsettings
:
2440 if texsetting
.exclusive
:
2441 if texsetting
.id not in exclusive
:
2442 exclusive
.append(texsetting
.id)
2444 raise RuntimeError("multiple occurance of exclusive texsetting with id=%i" % texsetting
.id)
2446 for texsetting
in texsettings
:
2447 expr
= texsetting
.modifyexpr(expr
, texsettings
, self
)
2448 self
.execute(expr
, *helper
.getattrs(args
, texmessage
, default
=self
.texmessagedefaultrun
))
2450 self
.dvifiles
[-1].readfile()
2451 match
= self
.PyXBoxPattern
.search(self
.texmessage
)
2452 if not match
or int(match
.group("page")) != self
.page
:
2453 raise TexResultError("box extents not found", self
)
2454 left
, right
, height
, depth
= map(lambda x
: float(x
) * 72.0 / 72.27, match
.group("lt", "rt", "ht", "dp"))
2455 box
= _textbox(x
, y
, left
, right
, height
, depth
, self
, self
.dvinumber
, self
.page
,
2456 *helper
.getattrs(args
, base
.PathStyle
, default
=[]))
2457 for t
in helper
.getattrs(args
, trafo
._trafo
, default
=()):
2461 def text(self
, x
, y
, expr
, *args
):
2462 return self
._text
(unit
.topt(x
), unit
.topt(y
), expr
, *args
)
2465 # the module provides an default texrunner and methods for direct access
2466 defaulttexrunner
= texrunner()
2467 reset
= defaulttexrunner
.reset
2468 set = defaulttexrunner
.set
2469 preamble
= defaulttexrunner
.preamble
2470 text
= defaulttexrunner
.text
2471 _text
= defaulttexrunner
._text