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 fm
= 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
[fm
.texname
] = fm
516 def __init__(self
, name
, c
, q
, d
, tfmconv
, debug
=0):
519 tfmpath
= pykpathsea
.find_file("%s.tfm" % self
.name
, pykpathsea
.kpse_tfm_format
)
521 raise TFMError("cannot find %s.tfm" % self
.name
)
522 self
.tfmfile
= tfmfile(tfmpath
, debug
)
524 # We only check for equality of font checksums if none of them
525 # is zero. The case c == 0 happend in some VF files and
526 # according to the VFtoVP documentation, paragraph 40, a check
527 # is only performed if tfmfile.checksum > 0. Anyhow, begin
528 # more generous here seems to be reasonable
529 if self
.tfmfile
.checksum
!= c
and self
.tfmfile
.checksum
!= 0 and c
!=0:
530 raise DVIError("check sums do not agree: %d vs. %d" %
531 (self
.tfmfile
.checksum
, c
))
533 tfmdesignsize
= round(self
.tfmfile
.designsizeraw
*tfmconv
)
534 if abs(tfmdesignsize
- d
) > 2:
535 raise DVIError("design sizes do not agree: %d vs. %d" % (tfmdesignsize
, d
))
536 if q
< 0 or q
> 134217728:
537 raise DVIError("font '%s' not loaded: bad scale" % self
.name
)
538 if d
< 0 or d
> 134217728:
539 raise DVIError("font '%s' not loaded: bad design size" % self
.name
)
543 # for bookkeeping of used characters
544 self
.usedchars
= [0] * 256
547 return "font(%s, %f)" % (self
.name
, self
.scale
)
551 def convert(self
, width
):
552 return 16L*width
*self
.q
/16777216L
554 def getwidth(self
, charcode
):
555 return self
.convert(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
557 def getheight(self
, charcode
):
558 return self
.convert(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
560 def getdepth(self
, charcode
):
561 return self
.convert(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
563 def getitalic(self
, charcode
):
564 return self
.convert(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
566 def markcharused(self
, charcode
):
567 self
.usedchars
[charcode
] = 1
569 def mergeusedchars(self
, otherfont
):
570 for i
in range(len(self
.usedchars
)):
571 self
.usedchars
[i
] = self
.usedchars
[i
] or otherfont
.usedchars
[i
]
574 class type1font(font
):
575 def __init__(self
, name
, c
, q
, d
, tfmconv
, fontmap
, debug
=0):
576 font
.__init
__(self
, name
, c
, q
, d
, tfmconv
, debug
)
577 self
.fontmapping
= fontmap
.get(name
)
578 if self
.fontmapping
is None:
579 raise RuntimeError("no information for font '%s' found in font mapping file, aborting" % name
)
581 def getbasepsname(self
):
582 return self
.fontmapping
.basepsname
585 if self
.fontmapping
.reencodefont
:
586 return "%s-%s" % (self
.fontmapping
.basepsname
, self
.fontmapping
.reencodefont
)
588 return self
.fontmapping
.basepsname
590 def getfontfile(self
):
591 return self
.fontmapping
.fontfile
593 def getencoding(self
):
594 return self
.fontmapping
.reencodefont
596 def getencodingfile(self
):
597 return self
.fontmapping
.encodingfile
601 ##############################################################################
603 ##############################################################################
605 _DVI_CHARMIN
= 0 # typeset a character and move right (range min)
606 _DVI_CHARMAX
= 127 # typeset a character and move right (range max)
607 _DVI_SET1234
= 128 # typeset a character and move right
608 _DVI_SETRULE
= 132 # typeset a rule and move right
609 _DVI_PUT1234
= 133 # typeset a character
610 _DVI_PUTRULE
= 137 # typeset a rule
611 _DVI_NOP
= 138 # no operation
612 _DVI_BOP
= 139 # beginning of page
613 _DVI_EOP
= 140 # ending of page
614 _DVI_PUSH
= 141 # save the current positions (h, v, w, x, y, z)
615 _DVI_POP
= 142 # restore positions (h, v, w, x, y, z)
616 _DVI_RIGHT1234
= 143 # move right
617 _DVI_W0
= 147 # move right by w
618 _DVI_W1234
= 148 # move right and set w
619 _DVI_X0
= 152 # move right by x
620 _DVI_X1234
= 153 # move right and set x
621 _DVI_DOWN1234
= 157 # move down
622 _DVI_Y0
= 161 # move down by y
623 _DVI_Y1234
= 162 # move down and set y
624 _DVI_Z0
= 166 # move down by z
625 _DVI_Z1234
= 167 # move down and set z
626 _DVI_FNTNUMMIN
= 171 # set current font (range min)
627 _DVI_FNTNUMMAX
= 234 # set current font (range max)
628 _DVI_FNT1234
= 235 # set current font
629 _DVI_SPECIAL1234
= 239 # special (dvi extention)
630 _DVI_FNTDEF1234
= 243 # define the meaning of a font number
631 _DVI_PRE
= 247 # preamble
632 _DVI_POST
= 248 # postamble beginning
633 _DVI_POSTPOST
= 249 # postamble ending
635 _DVI_VERSION
= 2 # dvi version
637 # position variable indices
649 _READ_POST
= 4 # XXX not used
650 _READ_POSTPOST
= 5 # XXX not used
654 class DVIError(exceptions
.Exception): pass
656 # save and restore colors
658 class _savecolor(base
.PSOp
):
659 def write(self
, file):
660 file.write("currentcolor currentcolorspace\n")
663 class _restorecolor(base
.PSOp
):
664 def write(self
, file):
665 file.write("setcolorspace setcolor\n")
667 class _savetrafo(base
.PSOp
):
668 def write(self
, file):
669 file.write("matrix currentmatrix\n")
672 class _restoretrafo(base
.PSOp
):
673 def write(self
, file):
674 file.write("setmatrix\n")
679 def __init__(self
, filename
, fontmap
, debug
=0, ipcmode
=0):
680 """ initializes the instance
682 Usually, the readfile method should be called once
683 immediately after this constructor. However, if you
684 set ipcmode, you need to call readpages as often as
685 there are pages in the dvi-file plus 1 (for properly
686 closing the dvi-file).
689 self
.filename
= filename
690 self
.fontmap
= fontmap
693 self
.ipcmode
= ipcmode
698 """ flush currently active string """
700 x
= unit
.t_m(self
.actoutstart
[0] * self
.conv
* 0.0254 / self
.resolution
)
701 y
= -unit
.t_m(self
.actoutstart
[1] * self
.conv
* 0.0254 / self
.resolution
)
703 print "[%s]" % self
.actoutstring
704 self
.actpage
.insert(_show(unit
.topt(x
), unit
.topt(y
), self
.actoutstring
))
705 self
.actoutstart
= None
707 def putchar(self
, char
, inch
=1):
708 if self
.actoutstart
is None:
709 self
.actoutstart
= self
.pos
[_POS_H
], self
.pos
[_POS_V
]
710 self
.actoutstring
= ""
711 if char
> 32 and char
< 127 and chr(char
) not in "()[]<>":
712 ascii
= "%s" % chr(char
)
714 ascii
= "\\%03o" % char
715 self
.actoutstring
= self
.actoutstring
+ ascii
716 dx
= inch
and self
.fonts
[self
.activefont
].getwidth(char
) or 0
717 self
.fonts
[self
.activefont
].markcharused(char
)
719 print ("%d: %schar%d h:=%d+%d=%d, hh:=%d" %
721 inch
and "set" or "put",
723 self
.pos
[_POS_H
], dx
, self
.pos
[_POS_H
]+dx
,
725 self
.pos
[_POS_H
] += dx
730 def putrule(self
, height
, width
, inch
=1):
732 x1
= unit
.t_m(self
.pos
[_POS_H
] * self
.conv
* 0.0254 / self
.resolution
)
733 y1
= -unit
.t_m(self
.pos
[_POS_V
] * self
.conv
* 0.0254 / self
.resolution
)
734 w
= unit
.t_m(width
* self
.conv
* 0.0254 / self
.resolution
)
735 h
= unit
.t_m(height
* self
.conv
* 0.0254 / self
.resolution
)
737 if height
> 0 and width
> 0:
739 pixelw
= int(width
*self
.conv
)
740 if pixelw
< width
*self
.conv
: pixelw
+= 1
741 pixelh
= int(height
*self
.conv
)
742 if pixelh
< height
*self
.conv
: pixelh
+= 1
744 print ("%d: %srule height %d, width %d (%dx%d pixels)" %
745 (self
.filepos
, inch
and "set" or "put", height
, width
, pixelh
, pixelw
))
746 self
.actpage
.fill(path
.rect(x1
, y1
, w
, h
))
749 print ("%d: %srule height %d, width %d (invisible)" %
750 (self
.filepos
, inch
and "set" or "put", height
, width
))
754 print (" h:=%d+%d=%d, hh:=%d" %
755 (self
.pos
[_POS_H
], width
, self
.pos
[_POS_H
]+width
, 0))
756 self
.pos
[_POS_H
] += width
759 def usefont(self
, fontnum
):
761 self
.activefont
= fontnum
763 fontpsname
= self
.fonts
[self
.activefont
].getpsname()
764 fontscale
= self
.fonts
[self
.activefont
].scale
765 fontdesignsize
= float(self
.fonts
[self
.activefont
].tfmfile
.designsize
)
766 self
.actpage
.insert(_selectfont(fontpsname
,
767 fontscale
*fontdesignsize
*72/72.27))
770 print ("%d: fntnum%i current font is %s" %
772 self
.activefont
, self
.fonts
[fontnum
].name
))
774 def definefont(self
, cmdnr
, num
, c
, q
, d
, fontname
):
775 # cmdnr: type of fontdef command (only used for debugging output)
778 # Note that q is actually s in large parts of the documentation.
781 self
.fonts
[num
] = type1font(fontname
, c
, q
, d
, self
.tfmconv
, self
.fontmap
, self
.debug
> 1)
784 print "%d: fntdef%d %i: %s" % (self
.filepos
, cmdnr
, num
, fontname
)
786 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
788 # scalestring = scale!=1000 and " scaled %d" % scale or ""
789 # print ("Font %i: %s%s---loaded at size %d DVI units" %
790 # (num, fontname, scalestring, q))
792 # print " (this font is magnified %d%%)" % round(scale/10)
794 def special(self
, s
):
796 x
= unit
.t_m(self
.pos
[_POS_H
] * self
.conv
* 0.0254 / self
.resolution
)
797 y
= -unit
.t_m(self
.pos
[_POS_V
] * self
.conv
* 0.0254 / self
.resolution
)
799 print "%d: xxx '%s'" % (self
.filepos
, s
)
800 if not s
.startswith("PyX:"):
801 raise RuntimeError("the special '%s' cannot be handled by PyX, aborting" % s
)
802 command
, args
= s
[4:].split()[0], s
[4:].split()[1:]
803 if command
=="color_begin":
805 c
= color
.cmyk(float(args
[1]), float(args
[2]), float(args
[3]), float(args
[4]))
806 elif args
[0]=="gray":
807 c
= color
.gray(float(args
[1]))
809 c
= color
.hsb(float(args
[1]), float(args
[2]), float(args
[3]))
811 c
= color
.rgb(float(args
[1]), float(args
[2]), float(args
[3]))
813 c
= color
.rgb(int(args
[1])/255.0, int(args
[2])/255.0, int(args
[3])/255.0)
814 elif args
[0]=="texnamed":
816 c
= getattr(color
.cmyk
, args
[1])
817 except AttributeError:
818 raise RuntimeError("unknown TeX color '%s', aborting" % args
[1])
820 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args
[0])
821 self
.actpage
.insert(_savecolor())
822 self
.actpage
.insert(c
)
823 elif command
=="color_end":
824 self
.actpage
.insert(_restorecolor())
825 elif command
=="rotate_begin":
826 self
.actpage
.insert(_savetrafo())
827 self
.actpage
.insert(trafo
.rotate(float(args
[0]), x
, y
))
828 elif command
=="rotate_end":
829 self
.actpage
.insert(_restoretrafo())
830 elif command
=="scale_begin":
831 self
.actpage
.insert(_savetrafo())
832 self
.actpage
.insert(trafo
.scale(float(args
[0]), float(args
[1]), x
, y
))
833 elif command
=="scale_end":
834 self
.actpage
.insert(_restoretrafo())
835 elif command
=="epsinclude":
836 # XXX: we cannot include epsfile in the header because this would
837 # generate a cyclic import with the canvas and text modules
843 name
, value
= arg
.split("=")
844 argdict
[name
] = value
846 # construct kwargs for epsfile constructor
848 epskwargs
["filename"] = argdict
["file"]
849 epskwargs
["bbox"] = bbox
._bbox
(float(argdict
["llx"]), float(argdict
["lly"]),
850 float(argdict
["urx"]), float(argdict
["ury"]))
851 if argdict
.has_key("width"):
852 epskwargs
["width"] = unit
.t_pt(float(argdict
["width"]))
853 if argdict
.has_key("height"):
854 epskwargs
["height"] = unit
.t_pt(float(argdict
["height"]))
855 if argdict
.has_key("clip"):
856 epskwargs
["clip"] = int(argdict
["clip"])
857 self
.actpage
.insert(epsfile
.epsfile(x
, y
, **epskwargs
))
858 elif command
=="marker":
860 raise RuntimeError("marker contains spaces")
862 if c
not in string
.digits
+ string
.letters
+ "@":
863 raise RuntimeError("marker contains invalid characters")
864 if self
.actpage
.markers
.has_key(args
[0]):
865 raise RuntimeError("marker name occurred several times")
866 self
.actpage
.markers
[args
[0]] = x
, y
868 raise RuntimeError("unknown PyX special '%s', aborting" % command
)
870 # routines corresponding to the different reader states of the dvi maschine
875 self
.filepos
= file.tell()
876 cmd
= file.readuchar()
879 elif cmd
== _DVI_PRE
:
880 if self
.file.readuchar() != _DVI_VERSION
: raise DVIError
881 num
= file.readuint32()
882 den
= file.readuint32()
883 mag
= file.readuint32()
885 self
.tfmconv
= (25400000.0/num
)*(den
/473628672.0)/16.0
887 self
.resolution
= 300.0
888 # self.trueconv = conv in DVIType docu
889 self
.trueconv
= (num
/254000.0)*(self
.resolution
/den
)
890 self
.conv
= self
.trueconv
*(mag
/1000.0)
892 comment
= file.read(file.readuchar())
897 def _read_nopage(self
):
900 self
.filepos
= file.tell()
901 cmd
= file.readuchar()
904 elif cmd
== _DVI_BOP
:
906 pagenos
= [file.readuint32() for i
in range(10)]
907 if pagenos
[:3] != [ord("P"), ord("y"), ord("X")] or pagenos
[4:] != [0, 0, 0, 0, 0, 0]:
908 raise DVIError("Page in dvi file is not a PyX page.")
910 print "%d: beginning of page %i" % (self
.filepos
, pagenos
[0])
913 elif cmd
== _DVI_POST
:
914 return _READ_DONE
# we skip the rest
918 def _read_page(self
):
919 self
.pages
.append(canvas
.canvas())
920 self
.actpage
= self
.pages
[-1]
921 self
.actpage
.markers
= {}
922 self
.pos
= [0, 0, 0, 0, 0, 0]
925 self
.filepos
= file.tell()
926 cmd
= file.readuchar()
929 if cmd
>= _DVI_CHARMIN
and cmd
<= _DVI_CHARMAX
:
931 elif cmd
>= _DVI_SET1234
and cmd
< _DVI_SET1234
+ 4:
932 self
.putchar(file.readint(cmd
- _DVI_SET1234
+ 1))
933 elif cmd
== _DVI_SETRULE
:
934 self
.putrule(file.readint32(), file.readint32())
935 elif cmd
>= _DVI_PUT1234
and cmd
< _DVI_PUT1234
+ 4:
936 self
.putchar(file.readint(cmd
- _DVI_PUT1234
+ 1), inch
=0)
937 elif cmd
== _DVI_PUTRULE
:
938 self
.putrule(file.readint32(), file.readint32(), 0)
939 elif cmd
== _DVI_EOP
:
942 print "%d: eop" % self
.filepos
945 elif cmd
== _DVI_PUSH
:
946 self
.stack
.append(tuple(self
.pos
))
948 print "%d: push" % self
.filepos
949 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
950 (( len(self
.stack
)-1,)+tuple(self
.pos
)))
951 elif cmd
== _DVI_POP
:
953 self
.pos
= list(self
.stack
[-1])
956 print "%d: pop" % self
.filepos
957 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
958 (( len(self
.stack
),)+tuple(self
.pos
)))
959 elif cmd
>= _DVI_RIGHT1234
and cmd
< _DVI_RIGHT1234
+ 4:
961 dh
= file.readint(cmd
- _DVI_RIGHT1234
+ 1, 1)
963 print ("%d: right%d %d h:=%d%+d=%d, hh:=" %
965 cmd
- _DVI_RIGHT1234
+ 1,
969 self
.pos
[_POS_H
]+dh
))
970 self
.pos
[_POS_H
] += dh
974 print ("%d: w0 %d h:=%d%+d=%d, hh:=" %
979 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
980 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
981 elif cmd
>= _DVI_W1234
and cmd
< _DVI_W1234
+ 4:
983 self
.pos
[_POS_W
] = file.readint(cmd
- _DVI_W1234
+ 1, 1)
985 print ("%d: w%d %d h:=%d%+d=%d, hh:=" %
987 cmd
- _DVI_W1234
+ 1,
991 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
992 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
995 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
996 elif cmd
>= _DVI_X1234
and cmd
< _DVI_X1234
+ 4:
998 self
.pos
[_POS_X
] = file.readint(cmd
- _DVI_X1234
+ 1, 1)
999 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
1000 elif cmd
>= _DVI_DOWN1234
and cmd
< _DVI_DOWN1234
+ 4:
1002 dv
= file.readint(cmd
- _DVI_DOWN1234
+ 1, 1)
1004 print ("%d: down%d %d v:=%d%+d=%d, vv:=" %
1006 cmd
- _DVI_DOWN1234
+ 1,
1010 self
.pos
[_POS_V
]+dv
))
1011 self
.pos
[_POS_V
] += dv
1012 elif cmd
== _DVI_Y0
:
1015 print ("%d: y0 %d v:=%d%+d=%d, vv:=" %
1020 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1021 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1022 elif cmd
>= _DVI_Y1234
and cmd
< _DVI_Y1234
+ 4:
1024 self
.pos
[_POS_Y
] = file.readint(cmd
- _DVI_Y1234
+ 1, 1)
1026 print ("%d: y%d %d v:=%d%+d=%d, vv:=" %
1028 cmd
- _DVI_Y1234
+ 1,
1032 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1033 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1034 elif cmd
== _DVI_Z0
:
1036 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1037 elif cmd
>= _DVI_Z1234
and cmd
< _DVI_Z1234
+ 4:
1039 self
.pos
[_POS_Z
] = file.readint(cmd
- _DVI_Z1234
+ 1, 1)
1040 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1041 elif cmd
>= _DVI_FNTNUMMIN
and cmd
<= _DVI_FNTNUMMAX
:
1042 self
.usefont(cmd
- _DVI_FNTNUMMIN
)
1043 elif cmd
>= _DVI_FNT1234
and cmd
< _DVI_FNT1234
+ 4:
1044 self
.usefont(file.readint(cmd
- _DVI_FNT1234
+ 1, 1))
1045 elif cmd
>= _DVI_SPECIAL1234
and cmd
< _DVI_SPECIAL1234
+ 4:
1046 self
.special(file.read(file.readint(cmd
- _DVI_SPECIAL1234
+ 1)))
1047 elif cmd
>= _DVI_FNTDEF1234
and cmd
< _DVI_FNTDEF1234
+ 4:
1048 if cmd
== _DVI_FNTDEF1234
:
1049 num
= file.readuchar()
1050 elif cmd
== _DVI_FNTDEF1234
+1:
1051 num
= file.readuint16()
1052 elif cmd
== _DVI_FNTDEF1234
+2:
1053 num
= file.readuint24()
1054 elif cmd
== _DVI_FNTDEF1234
+3:
1055 # Cool, here we have according to docu a signed int. Why?
1056 num
= file.readint32()
1057 self
.definefont(cmd
-_DVI_FNTDEF1234
+1,
1062 file.read(file.readuchar()+file.readuchar()))
1063 else: raise DVIError
1066 """ reads and parses dvi file
1068 This routine reads the dvi file and generates a list
1069 of pages in self.pages. Each page consists itself of
1070 a list of PSCommands equivalent to the content of
1071 the dvi file. Furthermore, the list of used fonts
1072 can be extracted from the array self.fonts.
1074 Usually, the readfile method should be called once
1075 immediately after constructing the instance. However,
1076 if you set ipcmode, you need to call readpages as
1077 often as there are pages in the dvi-file plus 1 (for
1078 properly closing the dvi-file).
1081 if self
.file is None:
1083 self
.activefont
= None
1087 # here goes the result, for each page one list.
1090 # pointer to currently active page
1093 # currently active output: position and content
1094 self
.actoutstart
= None
1095 self
.actoutstring
= ""
1097 self
.file = binfile(self
.filename
, "rb")
1099 # currently read byte in file (for debugging output)
1102 if self
._read
_pre
() != _READ_NOPAGE
:
1105 # start up reading process
1107 state
= self
._read
_nopage
()
1108 if state
== _READ_PAGE
:
1109 if self
._read
_page
() != _READ_NOPAGE
:
1111 elif state
!= _READ_DONE
:
1114 state
= _READ_NOPAGE
1115 while state
!= _READ_DONE
:
1116 if state
== _READ_NOPAGE
:
1117 state
= self
._read
_nopage
()
1118 elif state
== _READ_PAGE
:
1119 state
= self
._read
_page
()
1123 if state
== _READ_DONE
:
1126 def marker(self
, page
, marker
):
1127 """return marker from page"""
1128 return self
.pages
[page
-1].markers
[marker
]
1130 def prolog(self
, page
): # TODO: AW inserted this page argument -> should return the prolog needed for that page only!
1131 """ return prolog corresponding to contents of dvi file """
1132 # XXX replace this by prolog method in _selectfont
1133 result
= [_ReEncodeFont
]
1134 for font
in self
.fonts
.values():
1135 result
.append(prolog
.fontdefinition(font
.getbasepsname(),
1137 font
.getencodingfile(),
1139 if font
.getencoding():
1140 result
.append(prolog
.fontencoding(font
.getencoding(), font
.getencodingfile()))
1141 result
.append(prolog
.fontreencoding(font
.getpsname(), font
.getbasepsname(), font
.getencoding()))
1142 result
.extend(self
.pages
[page
-1].prolog())
1145 def write(self
, file, page
):
1146 """write PostScript output for page into file"""
1147 # XXX: remove this method by return canvas to TexRunner
1149 print "dvifile(\"%s\").write() for page %s called" % (self
.filename
, page
)
1150 self
.pages
[page
-1].write(file)
1153 _VF_LONG_CHAR
= 242 # character packet (long version)
1154 _VF_FNTDEF1234
= _DVI_FNTDEF1234
# font definition
1155 _VF_PRE
= _DVI_PRE
# preamble
1156 _VF_POST
= _DVI_POST
# postamble
1158 _VF_ID
= 202 # VF id byte
1160 class VFError(exceptions
.Exception): pass
1163 def __init__(self
, filename
, fontmap
, debug
=0):
1164 self
.filename
= filename
1165 self
.fontmap
= fontmap
1168 self
.fonts
= {} # used fonts
1169 self
.chars
= {} # defined chars
1171 file = binfile(self
.filename
, "rb")
1173 cmd
= file.readuchar()
1175 if file.readuchar() != _VF_ID
: raise VFError
1176 comment
= file.read(file.readuchar())
1177 cs
= file.readuint32()
1178 ds
= file.readuint32()
1183 cmd
= file.readuchar()
1184 if cmd
>= _VF_FNTDEF1234
and cmd
< _VF_FNTDEF1234
+ 4:
1186 if cmd
== _VF_FNTDEF1234
:
1187 num
= file.readuchar()
1188 elif cmd
== _VF_FNTDEF1234
+1:
1189 num
= file.readuint16()
1190 elif cmd
== _VF_FNTDEF1234
+2:
1191 num
= file.readuint24()
1192 elif cmd
== _VF_FNTDEF1234
+3:
1193 num
= file.readint32()
1194 c
= file.readint32()
1195 s
= file.readint32() # scaling used for font (fix_word)
1196 d
= file.readint32() # design size of font
1197 fontname
= file.read(file.readuchar()+file.readuchar())
1199 self
.fonts
[num
] = type1font(fontname
, c
, s
, d
, self
.tfmconv
, self
.fontmap
, self
.debug
> 1)
1200 elif cmd
== _VF_LONG_CHAR
:
1201 # character packet (long form)
1202 pl
= file.readuint32() # packet length
1203 cc
= file.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1204 tfm
= file.readuint24() # character width
1205 dvi
= file.read(pl
) # dvi code of character
1206 self
.chars
[cc
] = (tfm
, dvi
)
1207 elif cmd
< _VF_LONG_CHAR
:
1208 # character packet (short form)
1209 cc
= file.readuchar() # char code
1210 tfm
= file.readuint24() # character width
1211 dvi
= file.read(cmd
)
1212 self
.chars
[cc
] = (tfm
, dvi
)
1213 elif cmd
== _VF_POST
:
1221 ###############################################################################
1223 # - please don't get confused:
1224 # - there is a texmessage (and a texmessageparsed) attribute within the
1225 # texrunner; it contains TeX/LaTeX response from the last command execution
1226 # - instances of classes derived from the class texmessage are used to
1227 # parse the TeX/LaTeX response as it is stored in the texmessageparsed
1228 # attribute of a texrunner instance
1229 # - the multiple usage of the name texmessage might be removed in the future
1230 # - texmessage instances should implement _Itexmessage
1231 ###############################################################################
1233 class TexResultError(Exception):
1234 """specialized texrunner exception class
1235 - it is raised by texmessage instances, when a texmessage indicates an error
1236 - it is raised by the texrunner itself, whenever there is a texmessage left
1237 after all parsing of this message (by texmessage instances)"""
1239 def __init__(self
, description
, texrunner
):
1240 self
.description
= description
1241 self
.texrunner
= texrunner
1244 """prints a detailed report about the problem
1245 - the verbose level is controlled by texrunner.errordebug"""
1246 if self
.texrunner
.errordebug
>= 2:
1247 return ("%s\n" % self
.description
+
1248 "The expression passed to TeX was:\n"
1249 " %s\n" % self
.texrunner
.expr
.replace("\n", "\n ").rstrip() +
1250 "The return message from TeX was:\n"
1251 " %s\n" % self
.texrunner
.texmessage
.replace("\n", "\n ").rstrip() +
1252 "After parsing this message, the following was left:\n"
1253 " %s" % self
.texrunner
.texmessageparsed
.replace("\n", "\n ").rstrip())
1254 elif self
.texrunner
.errordebug
== 1:
1255 firstlines
= self
.texrunner
.texmessageparsed
.split("\n")
1256 if len(firstlines
) > 5:
1257 firstlines
= firstlines
[:5] + ["(cut after 5 lines, increase errordebug for more output)"]
1258 return ("%s\n" % self
.description
+
1259 "The expression passed to TeX was:\n"
1260 " %s\n" % self
.texrunner
.expr
.replace("\n", "\n ").rstrip() +
1261 "After parsing the return message from TeX, the following was left:\n" +
1262 reduce(lambda x
, y
: "%s %s\n" % (x
,y
), firstlines
, "").rstrip())
1264 return self
.description
1267 class TexResultWarning(TexResultError
):
1268 """as above, but with different handling of the exception
1269 - when this exception is raised by a texmessage instance,
1270 the information just get reported and the execution continues"""
1275 """validates/invalidates TeX/LaTeX response"""
1277 def check(self
, texrunner
):
1278 """check a Tex/LaTeX response and respond appropriate
1279 - read the texrunners texmessageparsed attribute
1280 - if there is an problem found, raise an appropriate
1281 exception (TexResultError or TexResultWarning)
1282 - remove any valid and identified TeX/LaTeX response
1283 from the texrunners texmessageparsed attribute
1284 -> finally, there should be nothing left in there,
1285 otherwise it is interpreted as an error"""
1288 class texmessage
: pass
1291 class _texmessagestart(texmessage
):
1292 """validates TeX/LaTeX startup"""
1294 __implements__
= _Itexmessage
1296 startpattern
= re
.compile(r
"This is [-0-9a-zA-Z\s_]*TeX")
1298 def check(self
, texrunner
):
1299 m
= self
.startpattern
.search(texrunner
.texmessageparsed
)
1301 raise TexResultError("TeX startup failed", texrunner
)
1302 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[m
.end():]
1304 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.split("%s.tex" % texrunner
.texfilename
, 1)[1]
1305 except (IndexError, ValueError):
1306 raise TexResultError("TeX running startup file failed", texrunner
)
1308 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.split("*! Undefined control sequence.\n<*> \\raiseerror\n %\n", 1)[1]
1309 except (IndexError, ValueError):
1310 raise TexResultError("TeX scrollmode check failed", texrunner
)
1313 class _texmessagenoaux(texmessage
):
1314 """allows for LaTeXs no-aux-file warning"""
1316 __implements__
= _Itexmessage
1318 def check(self
, texrunner
):
1320 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s.aux." % texrunner
.texfilename
, 1)
1321 texrunner
.texmessageparsed
= s1
+ s2
1322 except (IndexError, ValueError):
1324 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s%s%s.aux." % (os
.curdir
,
1326 texrunner
.texfilename
), 1)
1327 texrunner
.texmessageparsed
= s1
+ s2
1328 except (IndexError, ValueError):
1332 class _texmessageinputmarker(texmessage
):
1333 """validates the PyXInputMarker"""
1335 __implements__
= _Itexmessage
1337 def check(self
, texrunner
):
1339 s1
, s2
= texrunner
.texmessageparsed
.split("PyXInputMarker:executeid=%s:" % texrunner
.executeid
, 1)
1340 texrunner
.texmessageparsed
= s1
+ s2
1341 except (IndexError, ValueError):
1342 raise TexResultError("PyXInputMarker expected", texrunner
)
1345 class _texmessagepyxbox(texmessage
):
1346 """validates the PyXBox output"""
1348 __implements__
= _Itexmessage
1350 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:")
1352 def check(self
, texrunner
):
1353 m
= self
.pattern
.search(texrunner
.texmessageparsed
)
1354 if m
and m
.group("page") == str(texrunner
.page
):
1355 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
1357 raise TexResultError("PyXBox expected", texrunner
)
1360 class _texmessagepyxpageout(texmessage
):
1361 """validates the dvi shipout message (writing a page to the dvi file)"""
1363 __implements__
= _Itexmessage
1365 def check(self
, texrunner
):
1367 s1
, s2
= texrunner
.texmessageparsed
.split("[80.121.88.%s]" % texrunner
.page
, 1)
1368 texrunner
.texmessageparsed
= s1
+ s2
1369 except (IndexError, ValueError):
1370 raise TexResultError("PyXPageOutMarker expected", texrunner
)
1373 class _texmessagetexend(texmessage
):
1374 """validates TeX/LaTeX finish"""
1376 __implements__
= _Itexmessage
1378 def check(self
, texrunner
):
1380 s1
, s2
= texrunner
.texmessageparsed
.split("(%s.aux)" % texrunner
.texfilename
, 1)
1381 texrunner
.texmessageparsed
= s1
+ s2
1382 except (IndexError, ValueError):
1384 s1
, s2
= texrunner
.texmessageparsed
.split("(%s%s%s.aux)" % (os
.curdir
,
1386 texrunner
.texfilename
), 1)
1387 texrunner
.texmessageparsed
= s1
+ s2
1388 except (IndexError, ValueError):
1391 s1
, s2
= texrunner
.texmessageparsed
.split("(see the transcript file for additional information)", 1)
1392 texrunner
.texmessageparsed
= s1
+ s2
1393 except (IndexError, ValueError):
1395 dvipattern
= re
.compile(r
"Output written on %s\.dvi \((?P<page>\d+) pages?, \d+ bytes\)\." % texrunner
.texfilename
)
1396 m
= dvipattern
.search(texrunner
.texmessageparsed
)
1399 raise TexResultError("TeX dvifile messages expected", texrunner
)
1400 if m
.group("page") != str(texrunner
.page
):
1401 raise TexResultError("wrong number of pages reported", texrunner
)
1402 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
1405 s1
, s2
= texrunner
.texmessageparsed
.split("No pages of output.", 1)
1406 texrunner
.texmessageparsed
= s1
+ s2
1407 except (IndexError, ValueError):
1408 raise TexResultError("no dvifile expected")
1410 s1
, s2
= texrunner
.texmessageparsed
.split("Transcript written on %s.log." % texrunner
.texfilename
, 1)
1411 texrunner
.texmessageparsed
= s1
+ s2
1412 except (IndexError, ValueError):
1413 raise TexResultError("TeX logfile message expected")
1416 class _texmessageemptylines(texmessage
):
1417 """validates "*-only" (TeX/LaTeX input marker in interactive mode) and empty lines"""
1419 __implements__
= _Itexmessage
1421 def check(self
, texrunner
):
1422 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.replace("*\n", "")
1423 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.replace("\n", "")
1426 class _texmessageload(texmessage
):
1427 """validates inclusion of arbitrary files
1428 - the matched pattern is "(<filename> <arbitrary other stuff>)", where
1429 <fielname> is a readable file and other stuff can be anything
1430 - "(" and ")" must be used consistent (otherwise this validator just does nothing)
1431 - this is not always wanted, but we just assume that file inclusion is fine"""
1433 __implements__
= _Itexmessage
1435 pattern
= re
.compile(r
" *\((?P<filename>[^()\s\n]+)[^()]*\) *")
1437 def baselevels(self
, s
, maxlevel
=1, brackets
="()"):
1438 """strip parts of a string above a given bracket level
1439 - return a modified (some parts might be removed) version of the string s
1440 where all parts inside brackets with level higher than maxlevel are
1442 - if brackets do not match (number of left and right brackets is wrong
1443 or at some points there were more right brackets than left brackets)
1444 just return the unmodified string"""
1449 if c
== brackets
[0]:
1451 if level
> highestlevel
:
1452 highestlevel
= level
1453 if level
<= maxlevel
:
1455 if c
== brackets
[1]:
1457 if level
== 0 and highestlevel
> 0:
1460 def check(self
, texrunner
):
1461 lowestbracketlevel
= self
.baselevels(texrunner
.texmessageparsed
)
1462 if lowestbracketlevel
is not None:
1463 m
= self
.pattern
.search(lowestbracketlevel
)
1465 if os
.access(m
.group("filename"), os
.R_OK
):
1466 lowestbracketlevel
= lowestbracketlevel
[:m
.start()] + lowestbracketlevel
[m
.end():]
1469 m
= self
.pattern
.search(lowestbracketlevel
)
1471 texrunner
.texmessageparsed
= lowestbracketlevel
1474 class _texmessageloadfd(_texmessageload
):
1475 """validates the inclusion of font description files (fd-files)
1476 - works like _texmessageload
1477 - filename must end with .fd and no further text is allowed"""
1479 pattern
= re
.compile(r
" *\((?P<filename>[^)]+.fd)\) *")
1482 class _texmessagegraphicsload(_texmessageload
):
1483 """validates the inclusion of files as the graphics packages writes it
1484 - works like _texmessageload, but using "<" and ">" as delimiters
1485 - filename must end with .eps and no further text is allowed"""
1487 pattern
= re
.compile(r
" *<(?P<filename>[^>]+.eps)> *")
1489 def baselevels(self
, s
, brackets
="<>", **args
):
1490 return _texmessageload
.baselevels(self
, s
, brackets
=brackets
, **args
)
1493 #class _texmessagepdfmapload(_texmessageload):
1494 # """validates the inclusion of files as the graphics packages writes it
1495 # - works like _texmessageload, but using "{" and "}" as delimiters
1496 # - filename must end with .map and no further text is allowed"""
1498 # pattern = re.compile(r"{(?P<filename>[^}]+.map)}")
1500 # def baselevels(self, s, brackets="{}", **args):
1501 # return _texmessageload.baselevels(self, s, brackets=brackets, **args)
1504 class _texmessageignore(_texmessageload
):
1505 """validates any TeX/LaTeX response
1506 - this might be used, when the expression is ok, but no suitable texmessage
1508 - PLEASE: - consider writing suitable tex message parsers
1509 - share your ideas/problems/solutions with others (use the PyX mailing lists)"""
1511 __implements__
= _Itexmessage
1513 def check(self
, texrunner
):
1514 texrunner
.texmessageparsed
= ""
1517 texmessage
.start
= _texmessagestart()
1518 texmessage
.noaux
= _texmessagenoaux()
1519 texmessage
.inputmarker
= _texmessageinputmarker()
1520 texmessage
.pyxbox
= _texmessagepyxbox()
1521 texmessage
.pyxpageout
= _texmessagepyxpageout()
1522 texmessage
.texend
= _texmessagetexend()
1523 texmessage
.emptylines
= _texmessageemptylines()
1524 texmessage
.load
= _texmessageload()
1525 texmessage
.loadfd
= _texmessageloadfd()
1526 texmessage
.graphicsload
= _texmessagegraphicsload()
1527 texmessage
.ignore
= _texmessageignore()
1530 ###############################################################################
1532 # - texsettings are used to modify a TeX/LaTeX expression
1533 # to fit the users need
1534 # - texsettings have an order attribute (id), because the order is usually
1535 # important (e.g. you can not change the fontsize in mathmode in LaTeX)
1536 # - lower id's get applied later (are more outside -> mathmode has a higher
1538 # - order attributes are used to exclude complementary settings (with the
1540 # - texsettings might (in rare cases) depend on each other (e.g. parbox and
1542 ###############################################################################
1546 - modifies a TeX/LaTeX expression"""
1549 """order attribute for TeX settings
1550 - higher id's will be applied first (most inside)"""
1553 """marks exclusive effect of the setting
1554 - when set, settings with this id exclude each other
1555 - when unset, settings with this id do not exclude each other"""
1557 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1558 """modifies the TeX/LaTeX expression
1559 - expr is the original expression
1560 - the return value is the modified expression
1561 - texsettings contains a list of all texsettings (in case a tex setting
1562 depends on another texsetting)
1563 - texrunner contains the texrunner in case the texsetting depends
1566 def __cmp__(self
, other
):
1567 """compare texsetting with other
1568 - other is a texsetting as well
1569 - performs an id comparison (NOTE: higher id's come first!!!)"""
1572 # preamble settings for texsetting macros
1573 _texsettingpreamble
= ""
1579 def __cmp__(self
, other
):
1580 return -cmp(self
.id, other
.id) # note the sign!!!
1583 class halign(_texsetting
):
1584 """horizontal alignment
1585 the left/right splitting is performed within the PyXBox routine"""
1587 __implements__
= _Itexsetting
1591 def __init__(self
, hratio
):
1592 self
.hratio
= hratio
1594 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1595 return r
"\gdef\PyXHAlign{%.5f}%s" % (self
.hratio
, expr
)
1597 halign
.left
= halign(0)
1598 halign
.center
= halign(0.5)
1599 halign
.right
= halign(1)
1602 _texsettingpreamble
+= "\\newbox\\PyXBoxVAlign%\n\\newdimen\PyXDimenVAlign%\n"
1604 class valign(_texsetting
):
1605 "vertical alignment"
1610 class _valigntop(valign
):
1612 __implements__
= _Itexsetting
1614 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1615 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\lower\ht\PyXBoxVAlign\box\PyXBoxVAlign" % expr
1618 class _valignmiddle(valign
):
1620 __implements__
= _Itexsetting
1622 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1623 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\PyXDimenVAlign=0.5\ht\PyXBoxVAlign\advance\PyXDimenVAlign by -0.5\dp\PyXBoxVAlign\lower\PyXDimenVAlign\box\PyXBoxVAlign" % expr
1626 class _valignbottom(valign
):
1628 __implements__
= _Itexsetting
1630 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1631 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\raise\dp\PyXBoxVAlign\box\PyXBoxVAlign" % expr
1634 class _valignbaseline(valign
):
1636 __implements__
= _Itexsetting
1638 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1639 for texsetting
in texsettings
:
1640 if isinstance(texsetting
, parbox
):
1641 raise RuntimeError("valign.baseline: specify top/middle/bottom baseline for parbox")
1645 class _valignxxxbaseline(valign
):
1647 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1648 for texsetting
in texsettings
:
1649 if isinstance(texsetting
, parbox
):
1652 raise RuntimeError(self
.noparboxmessage
)
1656 class _valigntopbaseline(_valignxxxbaseline
):
1658 __implements__
= _Itexsetting
1660 noparboxmessage
= "valign.topbaseline: no parbox defined"
1663 class _valignmiddlebaseline(_valignxxxbaseline
):
1665 __implements__
= _Itexsetting
1667 noparboxmessage
= "valign.middlebaseline: no parbox defined"
1670 class _valignbottombaseline(_valignxxxbaseline
):
1672 __implements__
= _Itexsetting
1674 noparboxmessage
= "valign.bottombaseline: no parbox defined"
1677 valign
.top
= _valigntop()
1678 valign
.middle
= _valignmiddle()
1679 valign
.center
= valign
.middle
1680 valign
.bottom
= _valignbottom()
1681 valign
.baseline
= _valignbaseline()
1682 valign
.topbaseline
= _valigntopbaseline()
1683 valign
.middlebaseline
= _valignmiddlebaseline()
1684 valign
.centerbaseline
= valign
.middlebaseline
1685 valign
.bottombaseline
= _valignbottombaseline()
1688 _texsettingpreamble
+= "\\newbox\\PyXBoxVBox%\n\\newdimen\PyXDimenVBox%\n"
1691 class _parbox(_texsetting
):
1692 "goes into the vertical mode"
1694 __implements__
= _Itexsetting
1698 def __init__(self
, width
):
1701 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1703 for texsetting
in texsettings
:
1704 if isinstance(texsetting
, valign
):
1705 if (not isinstance(texsetting
, _valigntop
) and
1706 not isinstance(texsetting
, _valignmiddle
) and
1707 not isinstance(texsetting
, _valignbottom
) and
1708 not isinstance(texsetting
, _valigntopbaseline
)):
1709 if isinstance(texsetting
, _valignmiddlebaseline
):
1711 elif isinstance(texsetting
, _valignbottombaseline
):
1714 raise RuntimeError("parbox couldn'd identify the valign instance")
1715 if boxkind
== "vcenter":
1716 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
)
1718 return r
"\linewidth%.5ftruept\%s{\hsize\linewidth{%s}}" % (self
.width
* 72.27 / 72, boxkind
, expr
)
1721 class parbox(_parbox
):
1723 def __init__(self
, width
):
1724 _parbox
.__init
__(self
, unit
.topt(width
))
1727 class vshift(_texsetting
):
1734 class _vshiftchar(vshift
):
1735 "vertical down shift by a fraction of a character height"
1737 def __init__(self
, lowerratio
, heightstr
="0"):
1738 self
.lowerratio
= lowerratio
1739 self
.heightstr
= heightstr
1741 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1742 return r
"\setbox0\hbox{{%s}}\lower%.5f\ht0\hbox{{%s}}" % (self
.heightstr
, self
.lowerratio
, expr
)
1745 class _vshiftmathaxis(vshift
):
1746 "vertical down shift by the height of the math axis"
1748 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1749 return r
"\setbox0\hbox{$\vcenter{\vrule width0pt}$}\lower\ht0\hbox{{%s}}" % expr
1752 vshift
.char
= _vshiftchar
1753 vshift
.bottomzero
= vshift
.char(0)
1754 vshift
.middlezero
= vshift
.char(0.5)
1755 vshift
.centerzero
= vshift
.middlezero
1756 vshift
.topzero
= vshift
.char(1)
1757 vshift
.mathaxis
= _vshiftmathaxis()
1760 class _mathmode(_texsetting
):
1763 __implements__
= _Itexsetting
1767 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1768 return r
"$\displaystyle{%s}$" % expr
1770 mathmode
= _mathmode()
1773 defaultsizelist
= ["normalsize", "large", "Large", "LARGE", "huge", "Huge", None, "tiny", "scriptsize", "footnotesize", "small"]
1775 class size(_texsetting
):
1778 __implements__
= _Itexsetting
1782 def __init__(self
, expr
, sizelist
=defaultsizelist
):
1783 if helper
.isinteger(expr
):
1784 if expr
>= 0 and expr
< sizelist
.index(None):
1785 self
.size
= sizelist
[expr
]
1786 elif expr
< 0 and expr
+ len(sizelist
) > sizelist
.index(None):
1787 self
.size
= sizelist
[expr
]
1789 raise IndexError("index out of sizelist range")
1793 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1794 return r
"\%s{%s}" % (self
.size
, expr
)
1796 for s
in defaultsizelist
:
1798 size
.__dict
__[s
] = size(s
)
1801 ###############################################################################
1803 ###############################################################################
1806 class _readpipe(threading
.Thread
):
1807 """threaded reader of TeX/LaTeX output
1808 - sets an event, when a specific string in the programs output is found
1809 - sets an event, when the terminal ends"""
1811 def __init__(self
, pipe
, expectqueue
, gotevent
, gotqueue
, quitevent
):
1812 """initialize the reader
1813 - pipe: file to be read from
1814 - expectqueue: keeps the next InputMarker to be wait for
1815 - gotevent: the "got InputMarker" event
1816 - gotqueue: a queue containing the lines recieved from TeX/LaTeX
1817 - quitevent: the "end of terminal" event"""
1818 threading
.Thread
.__init
__(self
)
1819 self
.setDaemon(1) # don't care if the output might not be finished (nevertheless, it shouldn't happen)
1821 self
.expectqueue
= expectqueue
1822 self
.gotevent
= gotevent
1823 self
.gotqueue
= gotqueue
1824 self
.quitevent
= quitevent
1829 """thread routine"""
1830 read
= self
.pipe
.readline() # read, what comes in
1832 self
.expect
= self
.expectqueue
.get_nowait() # read, what should be expected
1836 # universal EOL handling (convert everything into unix like EOLs)
1837 read
.replace("\r", "")
1838 if not len(read
) or read
[-1] != "\n":
1840 self
.gotqueue
.put(read
) # report, whats readed
1841 if self
.expect
is not None and read
.find(self
.expect
) != -1:
1842 self
.gotevent
.set() # raise the got event, when the output was expected (XXX: within a single line)
1843 read
= self
.pipe
.readline() # read again
1845 self
.expect
= self
.expectqueue
.get_nowait()
1850 if self
.expect
is not None and self
.expect
.find("PyXInputMarker") != -1:
1851 raise RuntimeError("TeX/LaTeX finished unexpectedly")
1852 self
.quitevent
.set()
1856 class _textbox(box
._rect
, base
.PSCmd
):
1857 """basically a box.rect, but it contains a text created by the texrunner
1858 - texrunner._text and texrunner.text return such an object
1859 - _textbox instances can be inserted into a canvas
1860 - the output is contained in a page of the dvifile available thru the texrunner"""
1862 def __init__(self
, x
, y
, left
, right
, height
, depth
, texrunner
, dvinumber
, page
, *styles
):
1863 self
.texttrafo
= trafo
._translate
(x
, y
)
1864 box
._rect
.__init
__(self
, x
- left
, y
- depth
,
1865 left
+ right
, depth
+ height
,
1866 abscenter
= (left
, depth
))
1867 self
.texrunner
= texrunner
1868 self
.dvinumber
= dvinumber
1870 self
.styles
= styles
1872 def transform(self
, *trafos
):
1873 box
._rect
.transform(self
, *trafos
)
1874 for trafo
in trafos
:
1875 self
.texttrafo
= trafo
* self
.texttrafo
1877 def marker(self
, marker
):
1878 return self
.texttrafo
.apply(*self
.texrunner
.marker(self
.dvinumber
, self
.page
, marker
))
1882 for cmd
in self
.styles
:
1883 result
.extend(cmd
.prolog())
1884 return result
+ self
.texrunner
.prolog(self
.dvinumber
, self
.page
)
1886 def write(self
, file):
1887 canvas
._gsave
().write(file) # XXX: canvas?, constructor call needed?
1888 self
.texttrafo
.write(file)
1889 for style
in self
.styles
:
1891 self
.texrunner
.write(file, self
.dvinumber
, self
.page
)
1892 canvas
._grestore
().write(file)
1896 class textbox(_textbox
):
1898 def __init__(self
, x
, y
, left
, right
, height
, depth
, texrunner
, dvinumber
, page
, *styles
):
1899 _textbox
.__init
__(self
, unit
.topt(x
), unit
.topt(y
), unit
.topt(left
), unit
.topt(right
),
1900 unit
.topt(height
), unit
.topt(depth
), texrunner
, dvinumber
, page
, *styles
)
1903 def _cleantmp(texrunner
):
1904 """get rid of temporary files
1905 - function to be registered by atexit
1906 - files contained in usefiles are kept"""
1907 if texrunner
.texruns
: # cleanup while TeX is still running?
1908 texrunner
.texruns
= 0
1909 texrunner
.texdone
= 1
1910 texrunner
.expectqueue
.put_nowait(None) # do not expect any output anymore
1911 if texrunner
.mode
== "latex": # try to immediately quit from TeX or LaTeX
1912 texrunner
.texinput
.write("\n\\catcode`\\@11\\relax\\@@end\n")
1914 texrunner
.texinput
.write("\n\\end\n")
1915 texrunner
.texinput
.close() # close the input queue and
1916 if not texrunner
.waitforevent(texrunner
.quitevent
): # wait for finish of the output
1917 return # didn't got a quit from TeX -> we can't do much more
1918 for usefile
in texrunner
.usefiles
:
1919 extpos
= usefile
.rfind(".")
1921 os
.rename(texrunner
.texfilename
+ usefile
[extpos
:], usefile
)
1924 for file in glob
.glob("%s.*" % texrunner
.texfilename
):
1929 if texrunner
.texdebug
is not None:
1931 texrunner
.texdebug
.close()
1932 texrunner
.texdebug
= None
1937 # texrunner state exceptions
1938 class TexRunsError(Exception): pass
1939 class TexDoneError(Exception): pass
1940 class TexNotInPreambleModeError(Exception): pass
1944 """TeX/LaTeX interface
1945 - runs TeX/LaTeX expressions instantly
1946 - checks TeX/LaTeX response
1947 - the instance variable texmessage stores the last TeX
1948 response as a string
1949 - the instance variable texmessageparsed stores a parsed
1950 version of texmessage; it should be empty after
1951 texmessage.check was called, otherwise a TexResultError
1953 - the instance variable errordebug controls the verbose
1954 level of TexResultError"""
1956 def __init__(self
, mode
="tex",
1961 fontmaps
=config
.get("text", "fontmaps", "psfonts.map"),
1962 waitfortex
=config
.getint("text", "waitfortex", 60),
1963 showwaitfortex
=config
.getint("text", "showwaitfortex", 5),
1964 texipc
=config
.getboolean("text", "texipc", 0),
1970 texmessagestart
=texmessage
.start
,
1971 texmessagedocclass
=texmessage
.load
,
1972 texmessagebegindoc
=(texmessage
.load
, texmessage
.noaux
),
1973 texmessageend
=texmessage
.texend
,
1974 texmessagedefaultpreamble
=texmessage
.load
,
1975 texmessagedefaultrun
=texmessage
.loadfd
):
1977 if mode
!= "tex" and mode
!= "latex":
1978 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
1981 self
.docclass
= docclass
1982 self
.docopt
= docopt
1983 self
.usefiles
= helper
.ensurelist(usefiles
)
1984 self
.fontmap
= readfontmap(fontmaps
.split())
1985 self
.waitfortex
= waitfortex
1986 self
.showwaitfortex
= showwaitfortex
1987 self
.texipc
= texipc
1988 if texdebug
is not None:
1989 if texdebug
[-4:] == ".tex":
1990 self
.texdebug
= open(texdebug
, "w")
1992 self
.texdebug
= open("%s.tex" % texdebug
, "w")
1994 self
.texdebug
= None
1995 self
.dvidebug
= dvidebug
1996 self
.errordebug
= errordebug
1997 self
.dvicopy
= dvicopy
1998 self
.pyxgraphics
= pyxgraphics
1999 texmessagestart
= helper
.ensuresequence(texmessagestart
)
2000 helper
.checkattr(texmessagestart
, allowmulti
=(texmessage
,))
2001 self
.texmessagestart
= texmessagestart
2002 texmessagedocclass
= helper
.ensuresequence(texmessagedocclass
)
2003 helper
.checkattr(texmessagedocclass
, allowmulti
=(texmessage
,))
2004 self
.texmessagedocclass
= texmessagedocclass
2005 texmessagebegindoc
= helper
.ensuresequence(texmessagebegindoc
)
2006 helper
.checkattr(texmessagebegindoc
, allowmulti
=(texmessage
,))
2007 self
.texmessagebegindoc
= texmessagebegindoc
2008 texmessageend
= helper
.ensuresequence(texmessageend
)
2009 helper
.checkattr(texmessageend
, allowmulti
=(texmessage
,))
2010 self
.texmessageend
= texmessageend
2011 texmessagedefaultpreamble
= helper
.ensuresequence(texmessagedefaultpreamble
)
2012 helper
.checkattr(texmessagedefaultpreamble
, allowmulti
=(texmessage
,))
2013 self
.texmessagedefaultpreamble
= texmessagedefaultpreamble
2014 texmessagedefaultrun
= helper
.ensuresequence(texmessagedefaultrun
)
2015 helper
.checkattr(texmessagedefaultrun
, allowmulti
=(texmessage
,))
2016 self
.texmessagedefaultrun
= texmessagedefaultrun
2020 self
.preamblemode
= 1
2026 savetempdir
= tempfile
.tempdir
2027 tempfile
.tempdir
= os
.curdir
2028 self
.texfilename
= os
.path
.basename(tempfile
.mktemp())
2029 tempfile
.tempdir
= savetempdir
2031 def waitforevent(self
, event
):
2032 """waits verbosely with an timeout for an event
2033 - observes an event while periodly while printing messages
2034 - returns the status of the event (isSet)
2035 - does not clear the event"""
2036 if self
.showwaitfortex
:
2039 while waited
< self
.waitfortex
and not hasevent
:
2040 if self
.waitfortex
- waited
> self
.showwaitfortex
:
2041 event
.wait(self
.showwaitfortex
)
2042 waited
+= self
.showwaitfortex
2044 event
.wait(self
.waitfortex
- waited
)
2045 waited
+= self
.waitfortex
- waited
2046 hasevent
= event
.isSet()
2048 if waited
< self
.waitfortex
:
2049 sys
.stderr
.write("*** PyX INFO: still waiting for %s after %i seconds...\n" % (self
.mode
, waited
))
2051 sys
.stderr
.write("*** PyX ERROR: the timeout of %i seconds expired and %s did not respond.\n" % (waited
, self
.mode
))
2054 event
.wait(self
.waitfortex
)
2055 return event
.isSet()
2057 def execute(self
, expr
, *checks
):
2058 """executes expr within TeX/LaTeX
2059 - if self.texruns is not yet set, TeX/LaTeX is initialized,
2060 self.texruns is set and self.preamblemode is set
2061 - the method must not be called, when self.texdone is already set
2062 - expr should be a string or None
2063 - when expr is None, TeX/LaTeX is stopped, self.texruns is unset and
2064 while self.texdone becomes set
2065 - when self.preamblemode is set, the expr is passed directly to TeX/LaTeX
2066 - when self.preamblemode is unset, the expr is passed to \ProcessPyXBox
2068 if not self
.texruns
:
2069 if self
.texdebug
is not None:
2070 self
.texdebug
.write("%% PyX %s texdebug file\n" % version
.version
)
2071 self
.texdebug
.write("%% mode: %s\n" % self
.mode
)
2072 self
.texdebug
.write("%% date: %s\n" % time
.asctime(time
.localtime(time
.time())))
2073 for usefile
in self
.usefiles
:
2074 extpos
= usefile
.rfind(".")
2076 os
.rename(usefile
, self
.texfilename
+ usefile
[extpos
:])
2079 texfile
= open("%s.tex" % self
.texfilename
, "w") # start with filename -> creates dvi file with that name
2080 texfile
.write("\\relax%\n")
2087 self
.texinput
, self
.texoutput
= os
.popen4("%s%s %s" % (self
.mode
, ipcflag
, self
.texfilename
), "t", 0)
2089 # XXX: workaround for MS Windows (bufsize = 0 makes trouble!?)
2090 self
.texinput
, self
.texoutput
= os
.popen4("%s%s %s" % (self
.mode
, ipcflag
, self
.texfilename
), "t")
2091 atexit
.register(_cleantmp
, self
)
2092 self
.expectqueue
= Queue
.Queue(1) # allow for a single entry only -> keeps the next InputMarker to be wait for
2093 self
.gotevent
= threading
.Event() # keeps the got inputmarker event
2094 self
.gotqueue
= Queue
.Queue(0) # allow arbitrary number of entries
2095 self
.quitevent
= threading
.Event() # keeps for end of terminal event
2096 self
.readoutput
= _readpipe(self
.texoutput
, self
.expectqueue
, self
.gotevent
, self
.gotqueue
, self
.quitevent
)
2098 oldpreamblemode
= self
.preamblemode
2099 self
.preamblemode
= 1
2100 self
.execute("\\scrollmode\n\\raiseerror%\n" # switch to and check scrollmode
2101 "\\def\\PyX{P\\kern-.3em\\lower.5ex\hbox{Y}\kern-.18em X}%\n" # just the PyX Logo
2102 "\\gdef\\PyXHAlign{0}%\n" # global PyXHAlign (0.0-1.0) for the horizontal alignment, default to 0
2103 "\\newbox\\PyXBox%\n" # PyXBox will contain the output
2104 "\\newbox\\PyXBoxHAligned%\n" # PyXBox will contain the horizontal aligned output
2105 "\\newdimen\\PyXDimenHAlignLT%\n" # PyXDimenHAlignLT/RT will contain the left/right extent
2106 "\\newdimen\\PyXDimenHAlignRT%\n" +
2107 _texsettingpreamble
+ # insert preambles for texsetting macros
2108 "\\long\\def\\ProcessPyXBox#1#2{%\n" # the ProcessPyXBox definition (#1 is expr, #2 is page number)
2109 "\\setbox\\PyXBox=\\hbox{{#1}}%\n" # push expression into PyXBox
2110 "\\PyXDimenHAlignLT=\\PyXHAlign\\wd\\PyXBox%\n" # calculate the left/right extent
2111 "\\PyXDimenHAlignRT=\\wd\\PyXBox%\n"
2112 "\\advance\\PyXDimenHAlignRT by -\\PyXDimenHAlignLT%\n"
2113 "\\gdef\\PyXHAlign{0}%\n" # reset the PyXHAlign to the default 0
2114 "\\immediate\\write16{PyXBox:page=#2," # write page and extents of this box to stdout
2115 "lt=\\the\\PyXDimenHAlignLT,"
2116 "rt=\\the\\PyXDimenHAlignRT,"
2117 "ht=\\the\\ht\\PyXBox,"
2118 "dp=\\the\\dp\\PyXBox:}%\n"
2119 "\\setbox\\PyXBoxHAligned=\\hbox{\\kern-\\PyXDimenHAlignLT\\box\\PyXBox}%\n" # align horizontally
2120 "\\ht\\PyXBoxHAligned0pt%\n" # baseline alignment (hight to zero)
2121 "{\\count0=80\\count1=121\\count2=88\\count3=#2\\shipout\\box\\PyXBoxHAligned}}%\n" # shipout PyXBox to Page 80.121.88.<page number>
2122 "\\def\\PyXInput#1{\\immediate\\write16{PyXInputMarker:executeid=#1:}}%\n" # write PyXInputMarker to stdout
2123 "\\def\\PyXMarker#1{\\special{PyX:marker #1}}%\n", # write PyXMarker special into the dvi-file
2124 *self
.texmessagestart
)
2125 os
.remove("%s.tex" % self
.texfilename
)
2126 if self
.mode
== "tex":
2127 if len(self
.lfs
) > 4 and self
.lfs
[-4:] == ".lfs":
2130 lfsname
= "%s.lfs" % self
.lfs
2131 for fulllfsname
in [lfsname
,
2132 os
.path
.join(sys
.prefix
, "share", "pyx", lfsname
),
2133 os
.path
.join(os
.path
.dirname(__file__
), "lfs", lfsname
)]:
2135 lfsfile
= open(fulllfsname
, "r")
2136 lfsdef
= lfsfile
.read()
2142 allfiles
= (glob
.glob("*.lfs") +
2143 glob
.glob(os
.path
.join(sys
.prefix
, "share", "pyx", "*.lfs")) +
2144 glob
.glob(os
.path
.join(os
.path
.dirname(__file__
), "lfs", "*.lfs")))
2148 open(f
, "r").close()
2149 lfsnames
.append(os
.path
.basename(f
)[:-4])
2154 raise IOError("file '%s' is not available or not readable. Available LaTeX font size files (*.lfs): %s" % (lfsname
, lfsnames
))
2156 raise IOError("file '%s' is not available or not readable. No LaTeX font size files (*.lfs) available. Check your installation." % lfsname
)
2157 self
.execute(lfsdef
)
2158 self
.execute("\\normalsize%\n")
2159 self
.execute("\\newdimen\\linewidth%\n")
2160 elif self
.mode
== "latex":
2161 if self
.pyxgraphics
:
2162 for pyxdef
in ["pyx.def",
2163 os
.path
.join(sys
.prefix
, "share", "pyx", "pyx.def"),
2164 os
.path
.join(os
.path
.dirname(__file__
), "..", "contrib", "pyx.def")]:
2166 open(pyxdef
, "r").close()
2171 IOError("file 'pyx.def' is not available or not readable. Check your installation or turn off the pyxgraphics option.")
2172 pyxdef
= os
.path
.abspath(pyxdef
).replace(os
.sep
, "/")
2173 self
.execute("\\makeatletter%\n"
2174 "\\let\\saveProcessOptions=\\ProcessOptions%\n"
2175 "\\def\\ProcessOptions{%\n"
2176 "\\def\\Gin@driver{" + pyxdef
+ "}%\n"
2177 "\\def\\c@lor@namefile{dvipsnam.def}%\n"
2178 "\\saveProcessOptions}%\n"
2180 if self
.docopt
is not None:
2181 self
.execute("\\documentclass[%s]{%s}" % (self
.docopt
, self
.docclass
), *self
.texmessagedocclass
)
2183 self
.execute("\\documentclass{%s}" % self
.docclass
, *self
.texmessagedocclass
)
2184 self
.preamblemode
= oldpreamblemode
2186 if expr
is not None: # TeX/LaTeX should process expr
2187 self
.expectqueue
.put_nowait("PyXInputMarker:executeid=%i:" % self
.executeid
)
2188 if self
.preamblemode
:
2189 self
.expr
= ("%s%%\n" % expr
+
2190 "\\PyXInput{%i}%%\n" % self
.executeid
)
2193 self
.expr
= ("\\ProcessPyXBox{%s%%\n}{%i}%%\n" % (expr
, self
.page
) +
2194 "\\PyXInput{%i}%%\n" % self
.executeid
)
2195 else: # TeX/LaTeX should be finished
2196 self
.expectqueue
.put_nowait("Transcript written on %s.log" % self
.texfilename
)
2197 if self
.mode
== "latex":
2198 self
.expr
= "\\end{document}%\n"
2200 self
.expr
= "\\end%\n"
2201 if self
.texdebug
is not None:
2202 self
.texdebug
.write(self
.expr
)
2203 self
.texinput
.write(self
.expr
)
2204 gotevent
= self
.waitforevent(self
.gotevent
)
2205 self
.gotevent
.clear()
2206 if expr
is None and gotevent
: # TeX/LaTeX should have finished
2209 self
.texinput
.close() # close the input queue and
2210 gotevent
= self
.waitforevent(self
.quitevent
) # wait for finish of the output
2212 self
.texmessage
= ""
2214 self
.texmessage
+= self
.gotqueue
.get_nowait()
2217 self
.texmessageparsed
= self
.texmessage
2219 if expr
is not None:
2220 texmessage
.inputmarker
.check(self
)
2221 if not self
.preamblemode
:
2222 texmessage
.pyxbox
.check(self
)
2223 texmessage
.pyxpageout
.check(self
)
2224 for check
in checks
:
2227 except TexResultWarning
:
2228 traceback
.print_exc()
2229 print "expr>>>%s<<<" % self
.expr
2230 print "aaa>>>%s<<<" % self
.texmessageparsed
2231 texmessage
.emptylines
.check(self
)
2232 print "bbb>>>%s<<<" % self
.texmessageparsed
2233 if len(self
.texmessageparsed
):
2234 raise TexResultError("unhandled TeX response (might be an error)", self
)
2236 raise TexResultError("TeX didn't respond as expected within the timeout period (%i seconds)." % self
.waitfortex
, self
)
2238 def finishdvi(self
):
2239 "finish TeX/LaTeX and read the dvifile"
2240 self
.execute(None, *self
.texmessageend
)
2242 os
.system("dvicopy %(t)s.dvi %(t)s.dvicopy > %(t)s.dvicopyout 2> %(t)s.dvicopyerr" % {"t": self
.texfilename
})
2243 dvifilename
= "%s.dvicopy" % self
.texfilename
2245 dvifilename
= "%s.dvi" % self
.texfilename
2247 advifile
= dvifile(dvifilename
, self
.fontmap
, debug
=self
.dvidebug
)
2248 self
.dvifiles
.append(advifile
)
2249 self
.dvifiles
[-1].readfile()
2252 def marker(self
, dvinumber
, page
, marker
):
2253 "return the marker position"
2254 if not self
.texipc
and not self
.texdone
:
2256 return self
.dvifiles
[dvinumber
].marker(page
, marker
)
2258 def prolog(self
, dvinumber
, page
):
2259 "return the dvifile prolog"
2260 if not self
.texipc
and not self
.texdone
:
2262 return self
.dvifiles
[dvinumber
].prolog(page
)
2264 def write(self
, file, dvinumber
, page
):
2265 "write a page from the dvifile"
2266 if not self
.texipc
and not self
.texdone
:
2268 return self
.dvifiles
[dvinumber
].write(file, page
)
2270 def reset(self
, reinit
=0):
2271 "resets the tex runner to its initial state (upto its record to old dvi file(s))"
2274 if self
.texdebug
is not None:
2275 self
.texdebug
.write("%s\n%% preparing restart of %s\n" % ("%"*80, self
.mode
))
2280 self
.preamblemode
= 1
2281 for expr
, args
in self
.preambles
:
2282 self
.execute(expr
, *args
)
2283 if self
.mode
== "latex":
2284 self
.execute("\\begin{document}", *self
.texmessagebegindoc
)
2285 self
.preamblemode
= 0
2288 self
.preamblemode
= 1
2290 def set(self
, mode
=None,
2297 showwaitfortex
=None,
2304 texmessagestart
=None,
2305 texmessagedocclass
=None,
2306 texmessagebegindoc
=None,
2308 texmessagedefaultpreamble
=None,
2309 texmessagedefaultrun
=None):
2310 """provide a set command for TeX/LaTeX settings
2311 - TeX/LaTeX must not yet been started
2312 - especially needed for the defaultrunner, where no access to
2313 the constructor is available"""
2316 if mode
is not None:
2318 if mode
!= "tex" and mode
!= "latex":
2319 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
2323 if docclass
is not None:
2324 self
.docclass
= docclass
2325 if docopt
is not None:
2326 self
.docopt
= docopt
2327 if usefiles
is not None:
2328 self
.usefiles
= helper
.ensurelist(usefiles
)
2329 if fontmaps
is not None:
2330 self
.fontmap
= readfontmap(fontmaps
.split())
2331 if waitfortex
is not None:
2332 self
.waitfortex
= waitfortex
2333 if showwaitfortex
is not None:
2334 self
.showwaitfortex
= showwaitfortex
2335 if texipc
is not None:
2336 self
.texipc
= texipc
2337 if texdebug
is not None:
2338 if self
.texdebug
is not None:
2339 self
.texdebug
.close()
2340 if texdebug
[-4:] == ".tex":
2341 self
.texdebug
= open(texdebug
, "w")
2343 self
.texdebug
= open("%s.tex" % texdebug
, "w")
2344 if dvidebug
is not None:
2345 self
.dvidebug
= dvidebug
2346 if errordebug
is not None:
2347 self
.errordebug
= errordebug
2348 if dvicopy
is not None:
2349 self
.dvicopy
= dvicopy
2350 if pyxgraphics
is not None:
2351 self
.pyxgraphics
= pyxgraphics
2352 if errordebug
is not None:
2353 self
.errordebug
= errordebug
2354 if texmessagestart
is not None:
2355 texmessagestart
= helper
.ensuresequence(texmessagestart
)
2356 helper
.checkattr(texmessagestart
, allowmulti
=(texmessage
,))
2357 self
.texmessagestart
= texmessagestart
2358 if texmessagedocclass
is not None:
2359 texmessagedocclass
= helper
.ensuresequence(texmessagedocclass
)
2360 helper
.checkattr(texmessagedocclass
, allowmulti
=(texmessage
,))
2361 self
.texmessagedocclass
= texmessagedocclass
2362 if texmessagebegindoc
is not None:
2363 texmessagebegindoc
= helper
.ensuresequence(texmessagebegindoc
)
2364 helper
.checkattr(texmessagebegindoc
, allowmulti
=(texmessage
,))
2365 self
.texmessagebegindoc
= texmessagebegindoc
2366 if texmessageend
is not None:
2367 texmessageend
= helper
.ensuresequence(texmessageend
)
2368 helper
.checkattr(texmessageend
, allowmulti
=(texmessage
,))
2369 self
.texmessageend
= texmessageend
2370 if texmessagedefaultpreamble
is not None:
2371 texmessagedefaultpreamble
= helper
.ensuresequence(texmessagedefaultpreamble
)
2372 helper
.checkattr(texmessagedefaultpreamble
, allowmulti
=(texmessage
,))
2373 self
.texmessagedefaultpreamble
= texmessagedefaultpreamble
2374 if texmessagedefaultrun
is not None:
2375 texmessagedefaultrun
= helper
.ensuresequence(texmessagedefaultrun
)
2376 helper
.checkattr(texmessagedefaultrun
, allowmulti
=(texmessage
,))
2377 self
.texmessagedefaultrun
= texmessagedefaultrun
2379 def preamble(self
, expr
, *args
):
2380 r
"""put something into the TeX/LaTeX preamble
2381 - in LaTeX, this is done before the \begin{document}
2382 (you might use \AtBeginDocument, when you're in need for)
2383 - it is not allowed to call preamble after calling the
2384 text method for the first time (for LaTeX this is needed
2385 due to \begin{document}; in TeX it is forced for compatibility
2386 (you should be able to switch from TeX to LaTeX, if you want,
2387 without breaking something
2388 - preamble expressions must not create any dvi output
2389 - args might contain texmessage instances"""
2390 if self
.texdone
or not self
.preamblemode
:
2391 raise TexNotInPreambleModeError
2392 helper
.checkattr(args
, allowmulti
=(texmessage
,))
2393 args
= helper
.getattrs(args
, texmessage
, default
=self
.texmessagedefaultpreamble
)
2394 self
.execute(expr
, *args
)
2395 self
.preambles
.append((expr
, args
))
2397 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:")
2399 def _text(self
, x
, y
, expr
, *args
):
2400 """create text by passing expr to TeX/LaTeX
2401 - returns a textbox containing the result from running expr thru TeX/LaTeX
2402 - the box center is set to x, y
2403 - *args may contain style parameters, namely:
2404 - an halign instance
2405 - _texsetting instances
2406 - texmessage instances
2407 - trafo._trafo instances
2408 - base.PathStyle instances"""
2410 raise ValueError("None expression is invalid")
2412 self
.reset(reinit
=1)
2413 if self
.preamblemode
:
2414 if self
.mode
== "latex":
2415 self
.execute("\\begin{document}", *self
.texmessagebegindoc
)
2416 self
.preamblemode
= 0
2419 raise RuntimeError("texipc and dvicopy can't be mixed up")
2420 self
.dvifiles
.append(dvifile("%s.dvi" % self
.texfilename
, self
.fontmap
, debug
=self
.dvidebug
, ipcmode
=1))
2421 helper
.checkattr(args
, allowmulti
=(_texsetting
, texmessage
, trafo
._trafo
, base
.PathStyle
))
2422 #XXX: should we distiguish between StrokeStyle and FillStyle?
2423 texsettings
= helper
.getattrs(args
, _texsetting
, default
=[])
2425 for texsetting
in texsettings
:
2426 if texsetting
.exclusive
:
2427 if texsetting
.id not in exclusive
:
2428 exclusive
.append(texsetting
.id)
2430 raise RuntimeError("multiple occurance of exclusive texsetting with id=%i" % texsetting
.id)
2432 for texsetting
in texsettings
:
2433 expr
= texsetting
.modifyexpr(expr
, texsettings
, self
)
2434 self
.execute(expr
, *helper
.getattrs(args
, texmessage
, default
=self
.texmessagedefaultrun
))
2436 self
.dvifiles
[-1].readfile()
2437 match
= self
.PyXBoxPattern
.search(self
.texmessage
)
2438 if not match
or int(match
.group("page")) != self
.page
:
2439 raise TexResultError("box extents not found", self
)
2440 left
, right
, height
, depth
= map(lambda x
: float(x
) * 72.0 / 72.27, match
.group("lt", "rt", "ht", "dp"))
2441 box
= _textbox(x
, y
, left
, right
, height
, depth
, self
, self
.dvinumber
, self
.page
,
2442 *helper
.getattrs(args
, base
.PathStyle
, default
=[]))
2443 for t
in helper
.getattrs(args
, trafo
._trafo
, default
=()):
2447 def text(self
, x
, y
, expr
, *args
):
2448 return self
._text
(unit
.topt(x
), unit
.topt(y
), expr
, *args
)
2451 # the module provides an default texrunner and methods for direct access
2452 defaulttexrunner
= texrunner()
2453 reset
= defaulttexrunner
.reset
2454 set = defaulttexrunner
.set
2455 preamble
= defaulttexrunner
.preamble
2456 text
= defaulttexrunner
.text
2457 _text
= defaulttexrunner
._text