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 copy
, cStringIO
, exceptions
, glob
, os
, threading
, Queue
, traceback
, re
, struct
, string
, tempfile
, sys
, atexit
, time
26 import config
, helper
, unit
, bbox
, box
, base
, canvas
, color
, trafo
, path
, prolog
, pykpathsea
, version
, style
, attr
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
)
61 return self
.file.tell()
64 return self
.file.eof()
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
]
112 class stringbinfile(binfile
):
114 def __init__(self
, s
):
115 self
.file = cStringIO
.StringIO(s
)
119 # """ ascii file containing tokens separated by spaces.
121 # Comments beginning with % are ignored. Strings containing spaces
122 # are not handled correctly
125 # def __init__(self, filename):
126 # self.file = open(filename, "r")
129 # def gettoken(self):
130 # """ return next token or None if EOF """
131 # while not self.line:
132 # line = self.file.readline()
135 # self.line = line.split("%")[0].split()
136 # token = self.line[0]
137 # self.line = self.line[1:]
144 ##############################################################################
146 ##############################################################################
148 class TFMError(exceptions
.Exception): pass
151 class char_info_word
:
152 def __init__(self
, word
):
153 self
.width_index
= int((word
& 0xFF000000L
) >> 24) #make sign-safe
154 self
.height_index
= (word
& 0x00F00000) >> 20
155 self
.depth_index
= (word
& 0x000F0000) >> 16
156 self
.italic_index
= (word
& 0x0000FC00) >> 10
157 self
.tag
= (word
& 0x00000300) >> 8
158 self
.remainder
= (word
& 0x000000FF)
162 def __init__(self
, name
, debug
=0):
163 self
.file = binfile(name
, "rb")
170 self
.lf
= self
.file.readint16()
171 self
.lh
= self
.file.readint16()
172 self
.bc
= self
.file.readint16()
173 self
.ec
= self
.file.readint16()
174 self
.nw
= self
.file.readint16()
175 self
.nh
= self
.file.readint16()
176 self
.nd
= self
.file.readint16()
177 self
.ni
= self
.file.readint16()
178 self
.nl
= self
.file.readint16()
179 self
.nk
= self
.file.readint16()
180 self
.ne
= self
.file.readint16()
181 self
.np
= self
.file.readint16()
183 if not (self
.bc
-1 <= self
.ec
<= 255 and
185 self
.lf
== 6+self
.lh
+(self
.ec
-self
.bc
+1)+self
.nw
+self
.nh
+self
.nd
186 +self
.ni
+self
.nl
+self
.nk
+self
.ne
+self
.np
):
187 raise TFMError
, "error in TFM pre-header"
190 print "lh=%d" % self
.lh
196 self
.checksum
= self
.file.readint32()
197 self
.designsizeraw
= self
.file.readint32()
198 assert self
.designsizeraw
> 0, "invald design size"
199 self
.designsize
= fix_word(self
.designsizeraw
)
201 assert self
.lh
> 11, "inconsistency in TFM file: incomplete field"
202 self
.charcoding
= self
.file.readstring(40)
204 self
.charcoding
= None
207 assert self
.lh
> 16, "inconsistency in TFM file: incomplete field"
208 self
.fontfamily
= self
.file.readstring(20)
210 self
.fontfamily
= None
213 print "(FAMILY %s)" % self
.fontfamily
214 print "(CODINGSCHEME %s)" % self
.charcoding
215 print "(DESINGSIZE R %f)" % self
.designsize
218 self
.sevenbitsave
= self
.file.readuchar()
219 # ignore the following two bytes
220 self
.file.readint16()
221 facechar
= self
.file.readuchar()
222 # decode ugly face specification into the Knuth suggested string
234 self
.face
= "L" + self
.face
237 self
.face
= "B" + self
.face
240 self
.face
= "M" + self
.face
243 self
.face
= self
.face
[0] + "I" + self
.face
[1]
245 self
.face
= self
.face
[0] + "R" + self
.face
[1]
250 self
.sevenbitsave
= self
.face
= None
253 # just ignore the rest
254 print self
.file.read((self
.lh
-18)*4)
260 self
.char_info
= [None]*(self
.ec
+1)
262 for charcode
in range(self
.bc
, self
.ec
+1):
263 self
.char_info
[charcode
] = char_info_word(self
.file.readint32())
264 if self
.char_info
[charcode
].width_index
== 0:
265 # disable character if width_index is zero
266 self
.char_info
[charcode
] = None
272 self
.width
= [None for width_index
in range(self
.nw
)]
273 for width_index
in range(self
.nw
):
274 # self.width[width_index] = fix_word(self.file.readint32())
275 self
.width
[width_index
] = self
.file.readint32()
281 self
.height
= [None for height_index
in range(self
.nh
)]
282 for height_index
in range(self
.nh
):
283 # self.height[height_index] = fix_word(self.file.readint32())
284 self
.height
[height_index
] = self
.file.readint32()
290 self
.depth
= [None for depth_index
in range(self
.nd
)]
291 for depth_index
in range(self
.nd
):
292 # self.depth[depth_index] = fix_word(self.file.readint32())
293 self
.depth
[depth_index
] = self
.file.readint32()
299 self
.italic
= [None for italic_index
in range(self
.ni
)]
300 for italic_index
in range(self
.ni
):
301 # self.italic[italic_index] = fix_word(self.file.readint32())
302 self
.italic
[italic_index
] = self
.file.readint32()
308 # XXX decode to lig_kern_command
310 self
.lig_kern
= [None for lig_kern_index
in range(self
.nl
)]
311 for lig_kern_index
in range(self
.nl
):
312 self
.lig_kern
[lig_kern_index
] = self
.file.readint32()
318 self
.kern
= [None for kern_index
in range(self
.nk
)]
319 for kern_index
in range(self
.nk
):
320 # self.kern[kern_index] = fix_word(self.file.readint32())
321 self
.kern
[kern_index
] = self
.file.readint32()
327 # XXX decode to extensible_recipe
329 self
.exten
= [None for exten_index
in range(self
.ne
)]
330 for exten_index
in range(self
.ne
):
331 self
.exten
[exten_index
] = self
.file.readint32()
339 self
.param
= [None for param_index
in range(self
.np
)]
340 for param_index
in range(self
.np
):
341 self
.param
[param_index
] = self
.file.readint32()
346 # class FontEncoding:
348 # def __init__(self, filename):
349 # """ font encoding contained in filename """
350 # encpath = pykpathsea.find_file(filename, pykpathsea.kpse_tex_ps_header_format)
351 # encfile = tokenfile(encpath)
354 # self.encname = encfile.gettoken()
355 # token = encfile.gettoken()
357 # raise RuntimeError("cannot parse encoding file '%s', expecting '[' got '%s'" % (filename, token))
358 # self.encvector = []
359 # for i in range(256):
360 # token = encfile.gettoken()
361 # if token is None or token=="]":
362 # raise RuntimeError("not enough charcodes in encoding file '%s'" % filename)
363 # self.encvector.append(token)
364 # if encfile.gettoken() != "]":
365 # raise RuntimeError("too many charcodes in encoding file '%s'" % filename)
366 # token = encfile.gettoken()
368 # raise RuntimeError("cannot parse encoding file '%s', expecting 'def' got '%s'" % (filename, token))
369 # token = encfile.gettoken()
371 # raise RuntimeError("encoding file '%s' too long" % filename)
374 # def encode(self, charcode):
375 # return self.encvector[charcode]
377 ##############################################################################
379 ##############################################################################
381 _ReEncodeFont
= prolog
.definition("ReEncodeFont", """{
384 /newencoding exch def
385 /newfontname exch def
386 /basefontname exch def
387 /basefontdict basefontname findfont def
388 /newfontdict basefontdict maxlength dict def
390 exch dup dup /FID ne exch /Encoding ne and
391 { exch newfontdict 3 1 roll put }
395 newfontdict /FontName newfontname put
396 newfontdict /Encoding newencoding put
397 newfontname newfontdict definefont pop
402 # PostScript font selection and output primitives
405 class _selectfont(base
.PSOp
):
406 def __init__(self
, name
, size
):
410 def write(self
, file):
411 file.write("/%s %f selectfont\n" % (self
.name
, self
.size
))
414 class selectfont(_selectfont
):
415 def __init__(self
, font
):
416 _selectfont
.__init
__(self
, font
.getpsname(), font
.getsize())
420 result
= [prolog
.fontdefinition(self
.font
.getbasepsname(),
421 self
.font
.getfontfile(),
422 self
.font
.getencodingfile(),
423 self
.font
.usedchars
)]
424 if self
.font
.getencoding():
425 result
.append(_ReEncodeFont
)
426 result
.append(prolog
.fontencoding(self
.font
.getencoding(), self
.font
.getencodingfile()))
427 result
.append(prolog
.fontreencoding(self
.font
.getpsname(), self
.font
.getbasepsname(), self
.font
.getencoding()))
431 class _show(base
.PSOp
):
432 def __init__(self
, x
, y
, s
):
437 def write(self
, file):
438 file.write("%f %f moveto (%s) show\n" % (self
.x
, self
.y
, self
.s
))
443 tokenpattern
= re
.compile(r
'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
445 def __init__(self
, s
):
446 """ construct font mapping from line s of font mapping file """
447 self
.texname
= self
.basepsname
= self
.fontfile
= None
450 self
.encodingfile
= None
452 # supported postscript fragments occuring in psfonts.map
453 self
.reencodefont
= self
.extendfont
= self
.slantfont
= None
457 match
= self
.tokenpattern
.match(s
)
459 if match
.groups()[0]:
460 tokens
.append('"%s"' % match
.groups()[0])
462 tokens
.append(match
.groups()[2])
465 raise RuntimeError("wrong syntax")
468 if token
.startswith("<"):
469 if token
.startswith("<<"):
470 # XXX: support non-partial download here
471 self
.fontfile
= token
[2:]
472 elif token
.startswith("<["):
473 self
.encodingfile
= token
[2:]
474 elif token
.endswith(".pfa") or token
.endswith(".pfb"):
475 self
.fontfile
= token
[1:]
476 elif token
.endswith(".enc"):
477 self
.encodingfile
= token
[1:]
479 raise RuntimeError("wrong syntax")
480 elif token
.startswith('"'):
481 pscode
= token
[1:-1].split()
482 # parse standard postscript code fragments
485 arg
, cmd
= pscode
[:2]
487 raise RuntimeError("Unsupported Postscript fragment '%s'" % pscode
)
489 if cmd
== "ReEncodeFont":
490 self
.reencodefont
= arg
491 elif cmd
== "ExtendFont":
492 self
.extendfont
= arg
493 elif cmd
== "SlantFont":
496 raise RuntimeError("Unsupported Postscript fragment '%s %s'" % (arg
, cmd
))
498 if self
.texname
is None:
501 self
.basepsname
= token
502 if self
.basepsname
is None:
503 self
.basepsname
= self
.texname
506 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
507 (self
.texname
, self
.basepsname
, self
.fontfile
, repr(self
.encodingfile
)))
511 def readfontmap(filenames
):
512 """ read font map from filename (without path) """
514 for filename
in filenames
:
515 mappath
= pykpathsea
.find_file(filename
, pykpathsea
.kpse_dvips_config_format
)
517 raise RuntimeError("cannot find font mapping file '%s'" % filename
)
518 mapfile
= open(mappath
, "r")
520 for line
in mapfile
.readlines():
523 if not (line
=="" or line
[0] in (" ", "%", "*", ";" , "#")):
525 fm
= fontmapping(line
)
526 except RuntimeError, e
:
527 sys
.stderr
.write("*** PyX Warning: Ignoring line %i in mapping file '%s': %s\n" % (lineno
, filename
, e
))
529 fontmap
[fm
.texname
] = fm
535 def __init__(self
, name
, c
, q
, d
, debug
=0):
537 self
.q
= q
# desired size of font (fix_word) in tex points
538 self
.d
= d
# design size of font (fix_word) in tex points
539 tfmpath
= pykpathsea
.find_file("%s.tfm" % self
.name
, pykpathsea
.kpse_tfm_format
)
541 raise TFMError("cannot find %s.tfm" % self
.name
)
542 self
.tfmfile
= tfmfile(tfmpath
, debug
)
544 # We only check for equality of font checksums if none of them
545 # is zero. The case c == 0 happend in some VF files and
546 # according to the VFtoVP documentation, paragraph 40, a check
547 # is only performed if tfmfile.checksum > 0. Anyhow, begin
548 # more generous here seems to be reasonable
549 if self
.tfmfile
.checksum
!= c
and self
.tfmfile
.checksum
!= 0 and c
!=0:
550 raise DVIError("check sums do not agree: %d vs. %d" %
551 (self
.tfmfile
.checksum
, c
))
553 # tfmfile.designsizeraw is the design size of the font as a fix_word
554 if abs(self
.tfmfile
.designsizeraw
- d
) > 2:
555 raise DVIError("design sizes do not agree: %d vs. %d" % (tfmdesignsize
, d
))
556 if q
< 0 or q
> 134217728:
557 raise DVIError("font '%s' not loaded: bad scale" % self
.name
)
558 if d
< 0 or d
> 134217728:
559 raise DVIError("font '%s' not loaded: bad design size" % self
.name
)
563 # for bookkeeping of used characters
564 self
.usedchars
= [0] * 256
567 return "font %s designed at %g tex pts used at %g tex pts" % (self
.name
,
568 16.0*self
.d
/16777216L,
569 16.0*self
.q
/16777216L)
575 """ return size of font in (PS) points """
576 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
577 # to the corresponding float. Furthermore, we have to convert from TeX
578 # points to points, hence the factor 72/72.27.
579 return 16L*self
.q
/16777216L*72/72.27
581 def _convert(self
, width
):
582 return 16L*width
*self
.q
/16777216L
584 def getwidth(self
, charcode
):
585 return self
._convert
(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
587 def getheight(self
, charcode
):
588 return self
._convert
(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
590 def getdepth(self
, charcode
):
591 return self
._convert
(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
593 def getitalic(self
, charcode
):
594 return self
._convert
(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
596 def markcharused(self
, charcode
):
597 self
.usedchars
[charcode
] = 1
599 def mergeusedchars(self
, otherfont
):
600 for i
in range(len(self
.usedchars
)):
601 self
.usedchars
[i
] = self
.usedchars
[i
] or otherfont
.usedchars
[i
]
603 def clearusedchars(self
):
604 self
.usedchars
= [0] * 256
607 class type1font(font
):
608 def __init__(self
, name
, c
, q
, d
, fontmap
, debug
=0):
609 font
.__init
__(self
, name
, c
, q
, d
, debug
)
610 self
.fontmapping
= fontmap
.get(name
)
611 if self
.fontmapping
is None:
612 raise RuntimeError("no information for font '%s' found in font mapping file, aborting" % name
)
614 def getbasepsname(self
):
615 return self
.fontmapping
.basepsname
618 if self
.fontmapping
.reencodefont
:
619 return "%s-%s" % (self
.fontmapping
.basepsname
, self
.fontmapping
.reencodefont
)
621 return self
.fontmapping
.basepsname
623 def getfontfile(self
):
624 return self
.fontmapping
.fontfile
626 def getencoding(self
):
627 return self
.fontmapping
.reencodefont
629 def getencodingfile(self
):
630 return self
.fontmapping
.encodingfile
633 class virtualfont(font
):
634 def __init__(self
, name
, c
, q
, d
, fontmap
, debug
=0):
635 font
.__init
__(self
, name
, c
, q
, d
, debug
)
636 fontpath
= pykpathsea
.find_file(name
, pykpathsea
.kpse_vf_format
)
637 if fontpath
is None or not len(fontpath
):
639 self
.vffile
= vffile(fontpath
, self
.scale
, fontmap
, debug
> 1)
642 """ return fonts used in virtual font itself """
643 return self
.vffile
.getfonts()
645 def getchar(self
, cc
):
646 """ return dvi chunk corresponding to char code cc """
647 return self
.vffile
.getchar(cc
)
650 ##############################################################################
652 ##############################################################################
654 _DVI_CHARMIN
= 0 # typeset a character and move right (range min)
655 _DVI_CHARMAX
= 127 # typeset a character and move right (range max)
656 _DVI_SET1234
= 128 # typeset a character and move right
657 _DVI_SETRULE
= 132 # typeset a rule and move right
658 _DVI_PUT1234
= 133 # typeset a character
659 _DVI_PUTRULE
= 137 # typeset a rule
660 _DVI_NOP
= 138 # no operation
661 _DVI_BOP
= 139 # beginning of page
662 _DVI_EOP
= 140 # ending of page
663 _DVI_PUSH
= 141 # save the current positions (h, v, w, x, y, z)
664 _DVI_POP
= 142 # restore positions (h, v, w, x, y, z)
665 _DVI_RIGHT1234
= 143 # move right
666 _DVI_W0
= 147 # move right by w
667 _DVI_W1234
= 148 # move right and set w
668 _DVI_X0
= 152 # move right by x
669 _DVI_X1234
= 153 # move right and set x
670 _DVI_DOWN1234
= 157 # move down
671 _DVI_Y0
= 161 # move down by y
672 _DVI_Y1234
= 162 # move down and set y
673 _DVI_Z0
= 166 # move down by z
674 _DVI_Z1234
= 167 # move down and set z
675 _DVI_FNTNUMMIN
= 171 # set current font (range min)
676 _DVI_FNTNUMMAX
= 234 # set current font (range max)
677 _DVI_FNT1234
= 235 # set current font
678 _DVI_SPECIAL1234
= 239 # special (dvi extention)
679 _DVI_FNTDEF1234
= 243 # define the meaning of a font number
680 _DVI_PRE
= 247 # preamble
681 _DVI_POST
= 248 # postamble beginning
682 _DVI_POSTPOST
= 249 # postamble ending
684 _DVI_VERSION
= 2 # dvi version
686 # position variable indices
698 _READ_POST
= 4 # XXX not used
699 _READ_POSTPOST
= 5 # XXX not used
703 class DVIError(exceptions
.Exception): pass
705 # save and restore colors
707 class _savecolor(base
.PSOp
):
708 def write(self
, file):
709 file.write("currentcolor currentcolorspace\n")
712 class _restorecolor(base
.PSOp
):
713 def write(self
, file):
714 file.write("setcolorspace setcolor\n")
716 class _savetrafo(base
.PSOp
):
717 def write(self
, file):
718 file.write("matrix currentmatrix\n")
721 class _restoretrafo(base
.PSOp
):
722 def write(self
, file):
723 file.write("setmatrix\n")
728 def __init__(self
, filename
, fontmap
, debug
=0):
729 self
.filename
= filename
730 self
.fontmap
= fontmap
736 """ flush currently active string """
738 x
= self
.actoutstart
[0] * self
.conv
739 y
= -self
.actoutstart
[1] * self
.conv
741 print "[%s]" % self
.actoutstring
742 self
.actpage
.insert(_show(x
, y
, self
.actoutstring
))
743 self
.actoutstart
= None
745 def putrule(self
, height
, width
, inch
=1):
747 x1
= self
.pos
[_POS_H
] * self
.conv
748 y1
= -self
.pos
[_POS_V
] * self
.conv
749 w
= width
* self
.conv
750 h
= height
* self
.conv
752 if height
> 0 and width
> 0:
754 pixelw
= int(width
*self
.trueconv
*self
.mag
/1000.0)
755 if pixelw
< width
*self
.conv
: pixelw
+= 1
756 pixelh
= int(height
*self
.trueconv
*self
.mag
/1000.0)
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
773 def putchar(self
, char
, inch
=1):
774 dx
= inch
and int(round(self
.activefont
.getwidth(char
)*self
.tfmconv
)) or 0
777 print ("%d: %schar%d h:=%d+%d=%d, hh:=%d" %
779 inch
and "set" or "put",
781 self
.pos
[_POS_H
], dx
, self
.pos
[_POS_H
]+dx
,
784 if isinstance(self
.activefont
, type1font
):
785 if self
.actoutstart
is None:
786 self
.actoutstart
= self
.pos
[_POS_H
], self
.pos
[_POS_V
]
787 self
.actoutstring
= ""
788 if char
> 32 and char
< 127 and chr(char
) not in "()[]<>":
789 ascii
= "%s" % chr(char
)
791 ascii
= "\\%03o" % char
792 self
.actoutstring
= self
.actoutstring
+ ascii
794 self
.activefont
.markcharused(char
)
795 self
.pos
[_POS_H
] += dx
797 # virtual font handling
798 afterpos
= list(self
.pos
)
799 afterpos
[_POS_H
] += dx
800 self
._push
_dvistring
(self
.activefont
.getchar(char
), self
.activefont
.getfonts(), afterpos
,
801 self
.activefont
.getsize())
808 def usefont(self
, fontnum
):
810 print ("%d: fntnum%i current font is %s" %
812 fontnum
, self
.fonts
[fontnum
].name
))
814 self
.activefont
= self
.fonts
[fontnum
]
816 # if the new font is a type 1 font and if it is not already the
817 # font being currently used for the output, we have to flush the
818 # output and insert a selectfont statement in the PS code
819 if isinstance(self
.activefont
, type1font
) and self
.actoutfont
!=self
.activefont
:
821 self
.actpage
.insert(selectfont(self
.activefont
))
822 self
.actoutfont
= self
.activefont
824 def definefont(self
, cmdnr
, num
, c
, q
, d
, fontname
):
825 # cmdnr: type of fontdef command (only used for debugging output)
827 # q: scaling factor (fix_word)
828 # Note that q is actually s in large parts of the documentation.
829 # d: design size (fix_word)
832 font
= virtualfont(fontname
, c
, q
/self
.tfmconv
, d
/self
.tfmconv
, self
.fontmap
, self
.debug
> 1)
833 except (TypeError, RuntimeError):
834 font
= type1font(fontname
, c
, q
/self
.tfmconv
, d
/self
.tfmconv
, self
.fontmap
, self
.debug
> 1)
836 self
.fonts
[num
] = font
839 print "%d: fntdef%d %i: %s" % (self
.filepos
, cmdnr
, num
, fontname
)
841 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
843 # scalestring = scale!=1000 and " scaled %d" % scale or ""
844 # print ("Font %i: %s%s---loaded at size %d DVI units" %
845 # (num, fontname, scalestring, q))
847 # print " (this font is magnified %d%%)" % round(scale/10)
849 def special(self
, s
):
852 # it is in general not safe to continue using the currently active font because
853 # the specials may involve some gsave/grestore operations
854 # XXX: reset actoutfont only where strictly needed
855 self
.actoutfont
= None
857 x
= self
.pos
[_POS_H
] * self
.conv
858 y
= -self
.pos
[_POS_V
] * self
.conv
860 print "%d: xxx '%s'" % (self
.filepos
, s
)
861 if not s
.startswith("PyX:"):
862 if s
.startswith("Warning:"):
863 sys
.stderr
.write("*** PyX Warning: ignoring special '%s'\n" % s
)
866 raise RuntimeError("the special '%s' cannot be handled by PyX, aborting" % s
)
867 command
, args
= s
[4:].split()[0], s
[4:].split()[1:]
868 if command
=="color_begin":
870 c
= color
.cmyk(float(args
[1]), float(args
[2]), float(args
[3]), float(args
[4]))
871 elif args
[0]=="gray":
872 c
= color
.gray(float(args
[1]))
874 c
= color
.hsb(float(args
[1]), float(args
[2]), float(args
[3]))
876 c
= color
.rgb(float(args
[1]), float(args
[2]), float(args
[3]))
878 c
= color
.rgb(int(args
[1])/255.0, int(args
[2])/255.0, int(args
[3])/255.0)
879 elif args
[0]=="texnamed":
881 c
= getattr(color
.cmyk
, args
[1])
882 except AttributeError:
883 raise RuntimeError("unknown TeX color '%s', aborting" % args
[1])
885 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args
[0])
886 self
.actpage
.insert(_savecolor())
887 self
.actpage
.insert(c
)
888 elif command
=="color_end":
889 self
.actpage
.insert(_restorecolor())
890 elif command
=="rotate_begin":
891 self
.actpage
.insert(_savetrafo())
892 self
.actpage
.insert(trafo
._rotate
(float(args
[0]), x
, y
))
893 elif command
=="rotate_end":
894 self
.actpage
.insert(_restoretrafo())
895 elif command
=="scale_begin":
896 self
.actpage
.insert(_savetrafo())
897 self
.actpage
.insert(trafo
._scale
(float(args
[0]), float(args
[1]), x
, y
))
898 elif command
=="scale_end":
899 self
.actpage
.insert(_restoretrafo())
900 elif command
=="epsinclude":
901 # XXX: we cannot include epsfile in the header because this would
902 # generate a cyclic import with the canvas and text modules
908 name
, value
= arg
.split("=")
909 argdict
[name
] = value
911 # construct kwargs for epsfile constructor
913 epskwargs
["filename"] = argdict
["file"]
914 epskwargs
["bbox"] = bbox
._bbox
(float(argdict
["llx"]), float(argdict
["lly"]),
915 float(argdict
["urx"]), float(argdict
["ury"]))
916 if argdict
.has_key("width"):
917 epskwargs
["width"] = unit
.t_pt(float(argdict
["width"]))
918 if argdict
.has_key("height"):
919 epskwargs
["height"] = unit
.t_pt(float(argdict
["height"]))
920 if argdict
.has_key("clip"):
921 epskwargs
["clip"] = int(argdict
["clip"])
922 self
.actpage
.insert(epsfile
.epsfile(unit
.t_pt(x
), unit
.t_pt(y
), **epskwargs
))
923 elif command
=="marker":
925 raise RuntimeError("marker contains spaces")
927 if c
not in string
.digits
+ string
.letters
+ "@":
928 raise RuntimeError("marker contains invalid characters")
929 if self
.actpage
.markers
.has_key(args
[0]):
930 raise RuntimeError("marker name occurred several times")
931 self
.actpage
.markers
[args
[0]] = unit
.t_pt(x
), unit
.t_pt(y
)
933 raise RuntimeError("unknown PyX special '%s', aborting" % command
)
935 # routines for pushing and popping different dvi chunks on the reader
937 def _push_dvistring(self
, dvi
, fonts
, afterpos
, fontsize
):
938 """ push dvi string with defined fonts on top of reader
939 stack. Every positions gets scaled relatively by the factor
940 scale. When finished with interpreting the dvi chunk, we
941 continue with self.pos=afterpos. The designsize of the virtual
942 font is passed as a fix_word
947 print "executing new dvi chunk"
948 self
.statestack
.append((self
.file, self
.fonts
, self
.activefont
, afterpos
, self
.stack
, self
.conv
, self
.tfmconv
))
950 # units in vf files are relative to the size of the font and given as fix_words
951 # which can be converted to floats by diving by 2**20
953 self
.conv
= fontsize
/2**20
954 rescale
= self
.conv
/oldconv
956 self
.file = stringbinfile(dvi
)
961 # we have to rescale self.pos in order to be consistent with the new scaling
962 self
.pos
= map(lambda x
, rescale
=rescale
:1.0*x
/rescale
, self
.pos
)
964 # since tfmconv converts from tfm units to dvi units, we have to rescale it as well
965 self
.tfmconv
/= rescale
969 def _pop_dvistring(self
):
972 print "finished executing dvi chunk"
974 self
.file, self
.fonts
, self
.activefont
, self
.pos
, self
.stack
, self
.conv
, self
.tfmconv
= self
.statestack
.pop()
976 # routines corresponding to the different reader states of the dvi maschine
981 self
.filepos
= file.tell()
982 cmd
= file.readuchar()
985 elif cmd
== _DVI_PRE
:
986 if self
.file.readuchar() != _DVI_VERSION
: raise DVIError
987 num
= file.readuint32()
988 den
= file.readuint32()
989 self
.mag
= file.readuint32()
991 # for the interpretation of all quantities, two conversion factors
993 # - self.tfmconv (tfm units->dvi units)
994 # - self.conv (dvi units-> (PostScript) points)
996 self
.tfmconv
= (25400000.0/num
)*(den
/473628672.0)/16.0
998 # calculate self.conv as described in the DVIType docu
1001 self
.resolution
= 300.0
1002 # self.trueconv = conv in DVIType docu
1003 self
.trueconv
= (num
/254000.0)*(self
.resolution
/den
)
1005 # self.conv is the conversion factor from the dvi units
1006 # to (PostScript) points. It consists of
1007 # - self.mag/1000.0: magstep scaling
1008 # - self.trueconv: conversion from dvi units to pixels
1009 # - 1/self.resolution: conversion from pixels to inch
1010 # - 72 : conversion from inch to points
1011 self
.conv
= self
.mag
/1000.0*self
.trueconv
/self
.resolution
*72
1013 comment
= file.read(file.readuchar())
1018 def _read_nopage(self
):
1021 self
.filepos
= file.tell()
1022 cmd
= file.readuchar()
1025 elif cmd
== _DVI_BOP
:
1027 pagenos
= [file.readuint32() for i
in range(10)]
1028 if pagenos
[:3] != [ord("P"), ord("y"), ord("X")] or pagenos
[4:] != [0, 0, 0, 0, 0, 0]:
1029 raise DVIError("Page in dvi file is not a PyX page.")
1031 print "%d: beginning of page %i" % (self
.filepos
, pagenos
[0])
1034 elif cmd
== _DVI_POST
:
1035 return _READ_DONE
# we skip the rest
1039 def _read_page(self
):
1040 self
.pages
.append(canvas
.canvas())
1041 self
.actpage
= self
.pages
[-1]
1042 self
.actpage
.markers
= {}
1043 self
.pos
= [0, 0, 0, 0, 0, 0]
1044 self
.actoutfont
= None
1046 # Since we do not know, which dvi pages the actual PS file contains later on,
1047 # we have to keep track of used char informations separately for each dvi page.
1048 # In order to do so, the already defined fonts have to be copied and their
1049 # used char informations have to be reset
1050 for nr
in self
.fonts
.keys():
1051 self
.fonts
[nr
] = copy
.copy(self
.fonts
[nr
])
1052 self
.fonts
[nr
].clearusedchars()
1056 self
.filepos
= file.tell()
1058 cmd
= file.readuchar()
1059 except struct
.error
:
1060 # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
1061 # so we have to continue with the rest of the dvi file
1062 self
._pop
_dvistring
()
1066 if cmd
>= _DVI_CHARMIN
and cmd
<= _DVI_CHARMAX
:
1068 elif cmd
>= _DVI_SET1234
and cmd
< _DVI_SET1234
+ 4:
1069 self
.putchar(file.readint(cmd
- _DVI_SET1234
+ 1))
1070 elif cmd
== _DVI_SETRULE
:
1071 self
.putrule(file.readint32(), file.readint32())
1072 elif cmd
>= _DVI_PUT1234
and cmd
< _DVI_PUT1234
+ 4:
1073 self
.putchar(file.readint(cmd
- _DVI_PUT1234
+ 1), inch
=0)
1074 elif cmd
== _DVI_PUTRULE
:
1075 self
.putrule(file.readint32(), file.readint32(), 0)
1076 elif cmd
== _DVI_EOP
:
1079 print "%d: eop" % self
.filepos
1082 elif cmd
== _DVI_PUSH
:
1083 self
.stack
.append(list(self
.pos
))
1085 print "%d: push" % self
.filepos
1086 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
1087 (( len(self
.stack
)-1,)+tuple(self
.pos
)))
1088 elif cmd
== _DVI_POP
:
1090 self
.pos
= self
.stack
.pop()
1092 print "%d: pop" % self
.filepos
1093 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
1094 (( len(self
.stack
),)+tuple(self
.pos
)))
1095 elif cmd
>= _DVI_RIGHT1234
and cmd
< _DVI_RIGHT1234
+ 4:
1097 dh
= file.readint(cmd
- _DVI_RIGHT1234
+ 1, 1)
1099 print ("%d: right%d %d h:=%d%+d=%d, hh:=" %
1101 cmd
- _DVI_RIGHT1234
+ 1,
1105 self
.pos
[_POS_H
]+dh
))
1106 self
.pos
[_POS_H
] += dh
1107 elif cmd
== _DVI_W0
:
1110 print ("%d: w0 %d h:=%d%+d=%d, hh:=" %
1115 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
1116 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
1117 elif cmd
>= _DVI_W1234
and cmd
< _DVI_W1234
+ 4:
1119 self
.pos
[_POS_W
] = file.readint(cmd
- _DVI_W1234
+ 1, 1)
1121 print ("%d: w%d %d h:=%d%+d=%d, hh:=" %
1123 cmd
- _DVI_W1234
+ 1,
1127 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
1128 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
1129 elif cmd
== _DVI_X0
:
1131 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
1132 elif cmd
>= _DVI_X1234
and cmd
< _DVI_X1234
+ 4:
1134 self
.pos
[_POS_X
] = file.readint(cmd
- _DVI_X1234
+ 1, 1)
1135 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
1136 elif cmd
>= _DVI_DOWN1234
and cmd
< _DVI_DOWN1234
+ 4:
1138 dv
= file.readint(cmd
- _DVI_DOWN1234
+ 1, 1)
1140 print ("%d: down%d %d v:=%d%+d=%d, vv:=" %
1142 cmd
- _DVI_DOWN1234
+ 1,
1146 self
.pos
[_POS_V
]+dv
))
1147 self
.pos
[_POS_V
] += dv
1148 elif cmd
== _DVI_Y0
:
1151 print ("%d: y0 %d v:=%d%+d=%d, vv:=" %
1156 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1157 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1158 elif cmd
>= _DVI_Y1234
and cmd
< _DVI_Y1234
+ 4:
1160 self
.pos
[_POS_Y
] = file.readint(cmd
- _DVI_Y1234
+ 1, 1)
1162 print ("%d: y%d %d v:=%d%+d=%d, vv:=" %
1164 cmd
- _DVI_Y1234
+ 1,
1168 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1169 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1170 elif cmd
== _DVI_Z0
:
1172 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1173 elif cmd
>= _DVI_Z1234
and cmd
< _DVI_Z1234
+ 4:
1175 self
.pos
[_POS_Z
] = file.readint(cmd
- _DVI_Z1234
+ 1, 1)
1176 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1177 elif cmd
>= _DVI_FNTNUMMIN
and cmd
<= _DVI_FNTNUMMAX
:
1178 self
.usefont(cmd
- _DVI_FNTNUMMIN
)
1179 elif cmd
>= _DVI_FNT1234
and cmd
< _DVI_FNT1234
+ 4:
1180 self
.usefont(file.readint(cmd
- _DVI_FNT1234
+ 1, 1))
1181 elif cmd
>= _DVI_SPECIAL1234
and cmd
< _DVI_SPECIAL1234
+ 4:
1182 self
.special(file.read(file.readint(cmd
- _DVI_SPECIAL1234
+ 1)))
1183 elif cmd
>= _DVI_FNTDEF1234
and cmd
< _DVI_FNTDEF1234
+ 4:
1184 if cmd
== _DVI_FNTDEF1234
:
1185 num
= file.readuchar()
1186 elif cmd
== _DVI_FNTDEF1234
+1:
1187 num
= file.readuint16()
1188 elif cmd
== _DVI_FNTDEF1234
+2:
1189 num
= file.readuint24()
1190 elif cmd
== _DVI_FNTDEF1234
+3:
1191 # Cool, here we have according to docu a signed int. Why?
1192 num
= file.readint32()
1193 self
.definefont(cmd
-_DVI_FNTDEF1234
+1,
1198 file.read(file.readuchar()+file.readuchar()))
1199 else: raise DVIError
1201 def readpreamble(self
):
1202 """ opens the dvi file and reads the preamble """
1203 # XXX shouldn't we put this into the constructor?
1205 self
.activefont
= None
1207 # stack of fonts and fontscale currently used (used for VFs)
1212 # here goes the result, for each page one list.
1215 # pointer to currently active page
1218 # currently active output: position, content and type 1 font
1219 self
.actoutstart
= None
1220 self
.actoutstring
= ""
1221 self
.actoutfont
= None
1223 # stack for self.file, self.fonts and self.stack, needed for VF inclusion
1224 self
.statestack
= []
1226 self
.file = binfile(self
.filename
, "rb")
1228 # currently read byte in file (for debugging output)
1231 if self
._read
_pre
() != _READ_NOPAGE
:
1235 # XXX shouldn't return this the next page (insted of appending to self.pages)
1236 """ reads a page from the dvi file
1238 This routine reads a page from the dvi file. The page
1239 is appended to the list self.pages. Each page consists
1240 of a list of PSCommands equivalent to the content of
1241 the dvi file. Furthermore, the list of used fonts
1242 can be extracted from the array self.fonts. """
1244 if self
._read
_nopage
() == _READ_PAGE
:
1245 state
= self
._read
_page
()
1249 def readpostamble(self
):
1250 """ finish reading of the dvi file """
1254 """ reads and parses the hole dvi file """
1256 # XXX its unknown how often readpage should be called so we
1257 # XXX do it differently ... :-(
1259 # XXX do we realy need this method at all?
1260 # XXX why can't we just return the page on readpage? Isn't this all we need in the end?
1262 state
= _READ_NOPAGE
1263 while state
!= _READ_DONE
:
1264 if state
== _READ_NOPAGE
:
1265 state
= self
._read
_nopage
()
1266 elif state
== _READ_PAGE
:
1267 state
= self
._read
_page
()
1272 def marker(self
, page
, marker
):
1273 """return marker from page"""
1274 return self
.pages
[page
-1].markers
[marker
]
1276 def prolog(self
, page
):
1277 """ return prolog corresponding to contents of dvi file """
1278 # XXX: can nearly be remove (see write below) but:
1279 # XXX: we still have to separate the glyphs used for the different pages
1280 # XXX: maybe we just clear the markused arrays of the fonts after each page
1281 return self
.pages
[page
-1].prolog()
1283 def write(self
, file, page
):
1284 """write PostScript output for page into file"""
1285 # XXX: remove this method by return canvas to TexRunner
1286 # XXX: we should do this by removing readfile and changing readpage to return
1289 print "dvifile(\"%s\").write() for page %s called" % (self
.filename
, page
)
1290 self
.pages
[page
-1].write(file)
1292 ##############################################################################
1294 ##############################################################################
1296 _VF_LONG_CHAR
= 242 # character packet (long version)
1297 _VF_FNTDEF1234
= _DVI_FNTDEF1234
# font definition
1298 _VF_PRE
= _DVI_PRE
# preamble
1299 _VF_POST
= _DVI_POST
# postamble
1301 _VF_ID
= 202 # VF id byte
1303 class VFError(exceptions
.Exception): pass
1306 def __init__(self
, filename
, scale
, fontmap
, debug
=0):
1307 self
.filename
= filename
1309 self
.fontmap
= fontmap
1311 self
.fonts
= {} # used fonts
1312 self
.widths
= {} # widths of defined chars
1313 self
.chardefs
= {} # dvi chunks for defined chars
1315 file = binfile(self
.filename
, "rb")
1317 cmd
= file.readuchar()
1319 if file.readuchar() != _VF_ID
: raise VFError
1320 comment
= file.read(file.readuchar())
1321 self
.cs
= file.readuint32()
1322 self
.ds
= file.readuint32()
1327 cmd
= file.readuchar()
1328 if cmd
>= _VF_FNTDEF1234
and cmd
< _VF_FNTDEF1234
+ 4:
1330 if cmd
== _VF_FNTDEF1234
:
1331 num
= file.readuchar()
1332 elif cmd
== _VF_FNTDEF1234
+1:
1333 num
= file.readuint16()
1334 elif cmd
== _VF_FNTDEF1234
+2:
1335 num
= file.readuint24()
1336 elif cmd
== _VF_FNTDEF1234
+3:
1337 num
= file.readint32()
1338 c
= file.readint32()
1339 s
= file.readint32() # relative scaling used for font (fix_word)
1340 d
= file.readint32() # design size of font
1341 fontname
= file.read(file.readuchar()+file.readuchar())
1343 # rescaled size of font: s is relative to the scaling
1344 # of the virtual font itself. Note that realscale has
1345 # to be a fix_word (like s)
1346 # Furthermore we have to correct for self.tfmconv
1348 reals
= int(self
.scale
* float(fix_word(self
.ds
))*s
)
1349 # print ("defining font %s -- VF scale: %g, VF design size: %g, relative font size: %g => real size: %g" %
1350 # (fontname, self.scale, fix_word(self.ds), fix_word(s), fix_word(reals))
1354 # XXX allow for virtual fonts here too
1355 self
.fonts
[num
] = type1font(fontname
, c
, reals
, d
, self
.fontmap
, self
.debug
> 1)
1356 elif cmd
== _VF_LONG_CHAR
:
1357 # character packet (long form)
1358 pl
= file.readuint32() # packet length
1359 cc
= file.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1360 tfm
= file.readuint24() # character width
1361 dvi
= file.read(pl
) # dvi code of character
1362 self
.widths
[cc
] = tfm
1363 self
.chardefs
[cc
] = dvi
1364 elif cmd
< _VF_LONG_CHAR
:
1365 # character packet (short form)
1366 cc
= file.readuchar() # char code
1367 tfm
= file.readuint24() # character width
1368 dvi
= file.read(cmd
)
1369 self
.widths
[cc
] = tfm
1370 self
.chardefs
[cc
] = dvi
1371 elif cmd
== _VF_POST
:
1381 def getchar(self
, cc
):
1382 return self
.chardefs
[cc
]
1385 ###############################################################################
1387 # - please don't get confused:
1388 # - there is a texmessage (and a texmessageparsed) attribute within the
1389 # texrunner; it contains TeX/LaTeX response from the last command execution
1390 # - instances of classes derived from the class texmessage are used to
1391 # parse the TeX/LaTeX response as it is stored in the texmessageparsed
1392 # attribute of a texrunner instance
1393 # - the multiple usage of the name texmessage might be removed in the future
1394 # - texmessage instances should implement _Itexmessage
1395 ###############################################################################
1397 class TexResultError(Exception):
1398 """specialized texrunner exception class
1399 - it is raised by texmessage instances, when a texmessage indicates an error
1400 - it is raised by the texrunner itself, whenever there is a texmessage left
1401 after all parsing of this message (by texmessage instances)"""
1403 def __init__(self
, description
, texrunner
):
1404 self
.description
= description
1405 self
.texrunner
= texrunner
1408 """prints a detailed report about the problem
1409 - the verbose level is controlled by texrunner.errordebug"""
1410 if self
.texrunner
.errordebug
>= 2:
1411 return ("%s\n" % self
.description
+
1412 "The expression passed to TeX was:\n"
1413 " %s\n" % self
.texrunner
.expr
.replace("\n", "\n ").rstrip() +
1414 "The return message from TeX was:\n"
1415 " %s\n" % self
.texrunner
.texmessage
.replace("\n", "\n ").rstrip() +
1416 "After parsing this message, the following was left:\n"
1417 " %s" % self
.texrunner
.texmessageparsed
.replace("\n", "\n ").rstrip())
1418 elif self
.texrunner
.errordebug
== 1:
1419 firstlines
= self
.texrunner
.texmessageparsed
.split("\n")
1420 if len(firstlines
) > 5:
1421 firstlines
= firstlines
[:5] + ["(cut after 5 lines, increase errordebug for more output)"]
1422 return ("%s\n" % self
.description
+
1423 "The expression passed to TeX was:\n"
1424 " %s\n" % self
.texrunner
.expr
.replace("\n", "\n ").rstrip() +
1425 "After parsing the return message from TeX, the following was left:\n" +
1426 reduce(lambda x
, y
: "%s %s\n" % (x
,y
), firstlines
, "").rstrip())
1428 return self
.description
1431 class TexResultWarning(TexResultError
):
1432 """as above, but with different handling of the exception
1433 - when this exception is raised by a texmessage instance,
1434 the information just get reported and the execution continues"""
1439 """validates/invalidates TeX/LaTeX response"""
1441 def check(self
, texrunner
):
1442 """check a Tex/LaTeX response and respond appropriate
1443 - read the texrunners texmessageparsed attribute
1444 - if there is an problem found, raise an appropriate
1445 exception (TexResultError or TexResultWarning)
1446 - remove any valid and identified TeX/LaTeX response
1447 from the texrunners texmessageparsed attribute
1448 -> finally, there should be nothing left in there,
1449 otherwise it is interpreted as an error"""
1452 class texmessage
: pass
1455 class _texmessagestart(texmessage
):
1456 """validates TeX/LaTeX startup"""
1458 __implements__
= _Itexmessage
1460 startpattern
= re
.compile(r
"This is [-0-9a-zA-Z\s_]*TeX")
1462 def check(self
, texrunner
):
1463 m
= self
.startpattern
.search(texrunner
.texmessageparsed
)
1465 raise TexResultError("TeX startup failed", texrunner
)
1466 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[m
.end():]
1468 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.split("%s.tex" % texrunner
.texfilename
, 1)[1]
1469 except (IndexError, ValueError):
1470 raise TexResultError("TeX running startup file failed", texrunner
)
1472 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.split("*! Undefined control sequence.\n<*> \\raiseerror\n %\n", 1)[1]
1473 except (IndexError, ValueError):
1474 raise TexResultError("TeX scrollmode check failed", texrunner
)
1477 class _texmessagenoaux(texmessage
):
1478 """allows for LaTeXs no-aux-file warning"""
1480 __implements__
= _Itexmessage
1482 def check(self
, texrunner
):
1484 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s.aux." % texrunner
.texfilename
, 1)
1485 texrunner
.texmessageparsed
= s1
+ s2
1486 except (IndexError, ValueError):
1488 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s%s%s.aux." % (os
.curdir
,
1490 texrunner
.texfilename
), 1)
1491 texrunner
.texmessageparsed
= s1
+ s2
1492 except (IndexError, ValueError):
1496 class _texmessageinputmarker(texmessage
):
1497 """validates the PyXInputMarker"""
1499 __implements__
= _Itexmessage
1501 def check(self
, texrunner
):
1503 s1
, s2
= texrunner
.texmessageparsed
.split("PyXInputMarker:executeid=%s:" % texrunner
.executeid
, 1)
1504 texrunner
.texmessageparsed
= s1
+ s2
1505 except (IndexError, ValueError):
1506 raise TexResultError("PyXInputMarker expected", texrunner
)
1509 class _texmessagepyxbox(texmessage
):
1510 """validates the PyXBox output"""
1512 __implements__
= _Itexmessage
1514 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:")
1516 def check(self
, texrunner
):
1517 m
= self
.pattern
.search(texrunner
.texmessageparsed
)
1518 if m
and m
.group("page") == str(texrunner
.page
):
1519 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
1521 raise TexResultError("PyXBox expected", texrunner
)
1524 class _texmessagepyxpageout(texmessage
):
1525 """validates the dvi shipout message (writing a page to the dvi file)"""
1527 __implements__
= _Itexmessage
1529 def check(self
, texrunner
):
1531 s1
, s2
= texrunner
.texmessageparsed
.split("[80.121.88.%s]" % texrunner
.page
, 1)
1532 texrunner
.texmessageparsed
= s1
+ s2
1533 except (IndexError, ValueError):
1534 raise TexResultError("PyXPageOutMarker expected", texrunner
)
1537 class _texmessagetexend(texmessage
):
1538 """validates TeX/LaTeX finish"""
1540 __implements__
= _Itexmessage
1542 def check(self
, texrunner
):
1544 s1
, s2
= texrunner
.texmessageparsed
.split("(%s.aux)" % texrunner
.texfilename
, 1)
1545 texrunner
.texmessageparsed
= s1
+ s2
1546 except (IndexError, ValueError):
1548 s1
, s2
= texrunner
.texmessageparsed
.split("(%s%s%s.aux)" % (os
.curdir
,
1550 texrunner
.texfilename
), 1)
1551 texrunner
.texmessageparsed
= s1
+ s2
1552 except (IndexError, ValueError):
1555 s1
, s2
= texrunner
.texmessageparsed
.split("(see the transcript file for additional information)", 1)
1556 texrunner
.texmessageparsed
= s1
+ s2
1557 except (IndexError, ValueError):
1559 dvipattern
= re
.compile(r
"Output written on %s\.dvi \((?P<page>\d+) pages?, \d+ bytes\)\." % texrunner
.texfilename
)
1560 m
= dvipattern
.search(texrunner
.texmessageparsed
)
1563 raise TexResultError("TeX dvifile messages expected", texrunner
)
1564 if m
.group("page") != str(texrunner
.page
):
1565 raise TexResultError("wrong number of pages reported", texrunner
)
1566 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
1569 s1
, s2
= texrunner
.texmessageparsed
.split("No pages of output.", 1)
1570 texrunner
.texmessageparsed
= s1
+ s2
1571 except (IndexError, ValueError):
1572 raise TexResultError("no dvifile expected")
1574 s1
, s2
= texrunner
.texmessageparsed
.split("Transcript written on %s.log." % texrunner
.texfilename
, 1)
1575 texrunner
.texmessageparsed
= s1
+ s2
1576 except (IndexError, ValueError):
1577 raise TexResultError("TeX logfile message expected")
1580 class _texmessageemptylines(texmessage
):
1581 """validates "*-only" (TeX/LaTeX input marker in interactive mode) and empty lines"""
1583 __implements__
= _Itexmessage
1585 def check(self
, texrunner
):
1586 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.replace("*\n", "")
1587 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.replace("\n", "")
1590 class _texmessageload(texmessage
):
1591 """validates inclusion of arbitrary files
1592 - the matched pattern is "(<filename> <arbitrary other stuff>)", where
1593 <fielname> is a readable file and other stuff can be anything
1594 - "(" and ")" must be used consistent (otherwise this validator just does nothing)
1595 - this is not always wanted, but we just assume that file inclusion is fine"""
1597 __implements__
= _Itexmessage
1599 pattern
= re
.compile(r
" *\((?P<filename>[^()\s\n]+)[^()]*\) *")
1601 def baselevels(self
, s
, maxlevel
=1, brackets
="()"):
1602 """strip parts of a string above a given bracket level
1603 - return a modified (some parts might be removed) version of the string s
1604 where all parts inside brackets with level higher than maxlevel are
1606 - if brackets do not match (number of left and right brackets is wrong
1607 or at some points there were more right brackets than left brackets)
1608 just return the unmodified string"""
1613 if c
== brackets
[0]:
1615 if level
> highestlevel
:
1616 highestlevel
= level
1617 if level
<= maxlevel
:
1619 if c
== brackets
[1]:
1621 if level
== 0 and highestlevel
> 0:
1624 def check(self
, texrunner
):
1625 lowestbracketlevel
= self
.baselevels(texrunner
.texmessageparsed
)
1626 if lowestbracketlevel
is not None:
1627 m
= self
.pattern
.search(lowestbracketlevel
)
1629 if os
.access(m
.group("filename"), os
.R_OK
):
1630 lowestbracketlevel
= lowestbracketlevel
[:m
.start()] + lowestbracketlevel
[m
.end():]
1633 m
= self
.pattern
.search(lowestbracketlevel
)
1635 texrunner
.texmessageparsed
= lowestbracketlevel
1638 class _texmessageloadfd(_texmessageload
):
1639 """validates the inclusion of font description files (fd-files)
1640 - works like _texmessageload
1641 - filename must end with .fd and no further text is allowed"""
1643 pattern
= re
.compile(r
" *\((?P<filename>[^)]+.fd)\) *")
1646 class _texmessagegraphicsload(_texmessageload
):
1647 """validates the inclusion of files as the graphics packages writes it
1648 - works like _texmessageload, but using "<" and ">" as delimiters
1649 - filename must end with .eps and no further text is allowed"""
1651 pattern
= re
.compile(r
" *<(?P<filename>[^>]+.eps)> *")
1653 def baselevels(self
, s
, brackets
="<>", **args
):
1654 return _texmessageload
.baselevels(self
, s
, brackets
=brackets
, **args
)
1657 #class _texmessagepdfmapload(_texmessageload):
1658 # """validates the inclusion of files as the graphics packages writes it
1659 # - works like _texmessageload, but using "{" and "}" as delimiters
1660 # - filename must end with .map and no further text is allowed"""
1662 # pattern = re.compile(r"{(?P<filename>[^}]+.map)}")
1664 # def baselevels(self, s, brackets="{}", **args):
1665 # return _texmessageload.baselevels(self, s, brackets=brackets, **args)
1668 class _texmessageignore(_texmessageload
):
1669 """validates any TeX/LaTeX response
1670 - this might be used, when the expression is ok, but no suitable texmessage
1672 - PLEASE: - consider writing suitable tex message parsers
1673 - share your ideas/problems/solutions with others (use the PyX mailing lists)"""
1675 __implements__
= _Itexmessage
1677 def check(self
, texrunner
):
1678 texrunner
.texmessageparsed
= ""
1681 texmessage
.start
= _texmessagestart()
1682 texmessage
.noaux
= _texmessagenoaux()
1683 texmessage
.inputmarker
= _texmessageinputmarker()
1684 texmessage
.pyxbox
= _texmessagepyxbox()
1685 texmessage
.pyxpageout
= _texmessagepyxpageout()
1686 texmessage
.texend
= _texmessagetexend()
1687 texmessage
.emptylines
= _texmessageemptylines()
1688 texmessage
.load
= _texmessageload()
1689 texmessage
.loadfd
= _texmessageloadfd()
1690 texmessage
.graphicsload
= _texmessagegraphicsload()
1691 texmessage
.ignore
= _texmessageignore()
1694 ###############################################################################
1696 # - texsettings are used to modify a TeX/LaTeX expression
1697 # to fit the users need
1698 # - texsettings have an order attribute (id), because the order is usually
1699 # important (e.g. you can not change the fontsize in mathmode in LaTeX)
1700 # - lower id's get applied later (are more outside -> mathmode has a higher
1702 # - order attributes are used to exclude complementary settings (with the
1704 # - texsettings might (in rare cases) depend on each other (e.g. parbox and
1706 ###############################################################################
1710 - modifies a TeX/LaTeX expression"""
1713 """order attribute for TeX settings
1714 - higher id's will be applied first (most inside)"""
1717 """marks exclusive effect of the setting
1718 - when set, settings with this id exclude each other
1719 - when unset, settings with this id do not exclude each other"""
1721 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1722 """modifies the TeX/LaTeX expression
1723 - expr is the original expression
1724 - the return value is the modified expression
1725 - texsettings contains a list of all texsettings (in case a tex setting
1726 depends on another texsetting)
1727 - texrunner contains the texrunner in case the texsetting depends
1730 def __cmp__(self
, other
):
1731 """compare texsetting with other
1732 - other is a texsetting as well
1733 - performs an id comparison (NOTE: higher id's come first!!!)"""
1736 # preamble settings for texsetting macros
1737 _texsettingpreamble
= ""
1743 def __cmp__(self
, other
):
1744 return -cmp(self
.id, other
.id) # note the sign!!!
1747 class halign(_texsetting
):
1748 """horizontal alignment
1749 the left/right splitting is performed within the PyXBox routine"""
1751 __implements__
= _Itexsetting
1755 def __init__(self
, hratio
):
1756 self
.hratio
= hratio
1758 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1759 return r
"\gdef\PyXHAlign{%.5f}%s" % (self
.hratio
, expr
)
1761 halign
.left
= halign(0)
1762 halign
.center
= halign(0.5)
1763 halign
.right
= halign(1)
1766 _texsettingpreamble
+= "\\newbox\\PyXBoxVAlign%\n\\newdimen\PyXDimenVAlign%\n"
1768 class valign(_texsetting
):
1769 "vertical alignment"
1774 class _valigntop(valign
):
1776 __implements__
= _Itexsetting
1778 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1779 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\lower\ht\PyXBoxVAlign\box\PyXBoxVAlign" % expr
1782 class _valignmiddle(valign
):
1784 __implements__
= _Itexsetting
1786 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1787 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\PyXDimenVAlign=0.5\ht\PyXBoxVAlign\advance\PyXDimenVAlign by -0.5\dp\PyXBoxVAlign\lower\PyXDimenVAlign\box\PyXBoxVAlign" % expr
1790 class _valignbottom(valign
):
1792 __implements__
= _Itexsetting
1794 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1795 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\raise\dp\PyXBoxVAlign\box\PyXBoxVAlign" % expr
1798 class _valignbaseline(valign
):
1800 __implements__
= _Itexsetting
1802 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1803 for texsetting
in texsettings
:
1804 if isinstance(texsetting
, parbox
):
1805 raise RuntimeError("valign.baseline: specify top/middle/bottom baseline for parbox")
1809 class _valignxxxbaseline(valign
):
1811 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1812 for texsetting
in texsettings
:
1813 if isinstance(texsetting
, parbox
):
1816 raise RuntimeError(self
.noparboxmessage
)
1820 class _valigntopbaseline(_valignxxxbaseline
):
1822 __implements__
= _Itexsetting
1824 noparboxmessage
= "valign.topbaseline: no parbox defined"
1827 class _valignmiddlebaseline(_valignxxxbaseline
):
1829 __implements__
= _Itexsetting
1831 noparboxmessage
= "valign.middlebaseline: no parbox defined"
1834 class _valignbottombaseline(_valignxxxbaseline
):
1836 __implements__
= _Itexsetting
1838 noparboxmessage
= "valign.bottombaseline: no parbox defined"
1841 valign
.top
= _valigntop()
1842 valign
.middle
= _valignmiddle()
1843 valign
.center
= valign
.middle
1844 valign
.bottom
= _valignbottom()
1845 valign
.baseline
= _valignbaseline()
1846 valign
.topbaseline
= _valigntopbaseline()
1847 valign
.middlebaseline
= _valignmiddlebaseline()
1848 valign
.centerbaseline
= valign
.middlebaseline
1849 valign
.bottombaseline
= _valignbottombaseline()
1852 _texsettingpreamble
+= "\\newbox\\PyXBoxVBox%\n\\newdimen\PyXDimenVBox%\n"
1855 class _parbox(_texsetting
):
1856 "goes into the vertical mode"
1858 __implements__
= _Itexsetting
1862 def __init__(self
, width
):
1865 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1867 for texsetting
in texsettings
:
1868 if isinstance(texsetting
, valign
):
1869 if (not isinstance(texsetting
, _valigntop
) and
1870 not isinstance(texsetting
, _valignmiddle
) and
1871 not isinstance(texsetting
, _valignbottom
) and
1872 not isinstance(texsetting
, _valigntopbaseline
)):
1873 if isinstance(texsetting
, _valignmiddlebaseline
):
1875 elif isinstance(texsetting
, _valignbottombaseline
):
1878 raise RuntimeError("parbox couldn'd identify the valign instance")
1879 if boxkind
== "vcenter":
1880 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
)
1882 return r
"\linewidth%.5ftruept\%s{\hsize\linewidth{%s}}" % (self
.width
* 72.27 / 72, boxkind
, expr
)
1885 class parbox(_parbox
):
1887 def __init__(self
, width
):
1888 _parbox
.__init
__(self
, unit
.topt(width
))
1891 class vshift(_texsetting
):
1898 class _vshiftchar(vshift
):
1899 "vertical down shift by a fraction of a character height"
1901 def __init__(self
, lowerratio
, heightstr
="0"):
1902 self
.lowerratio
= lowerratio
1903 self
.heightstr
= heightstr
1905 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1906 return r
"\setbox0\hbox{{%s}}\lower%.5f\ht0\hbox{{%s}}" % (self
.heightstr
, self
.lowerratio
, expr
)
1909 class _vshiftmathaxis(vshift
):
1910 "vertical down shift by the height of the math axis"
1912 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1913 return r
"\setbox0\hbox{$\vcenter{\vrule width0pt}$}\lower\ht0\hbox{{%s}}" % expr
1916 vshift
.char
= _vshiftchar
1917 vshift
.bottomzero
= vshift
.char(0)
1918 vshift
.middlezero
= vshift
.char(0.5)
1919 vshift
.centerzero
= vshift
.middlezero
1920 vshift
.topzero
= vshift
.char(1)
1921 vshift
.mathaxis
= _vshiftmathaxis()
1924 class _mathmode(_texsetting
):
1927 __implements__
= _Itexsetting
1931 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1932 return r
"$\displaystyle{%s}$" % expr
1934 mathmode
= _mathmode()
1937 defaultsizelist
= ["normalsize", "large", "Large", "LARGE", "huge", "Huge", None, "tiny", "scriptsize", "footnotesize", "small"]
1939 class size(_texsetting
):
1942 __implements__
= _Itexsetting
1946 def __init__(self
, expr
, sizelist
=defaultsizelist
):
1947 if helper
.isinteger(expr
):
1948 if expr
>= 0 and expr
< sizelist
.index(None):
1949 self
.size
= sizelist
[expr
]
1950 elif expr
< 0 and expr
+ len(sizelist
) > sizelist
.index(None):
1951 self
.size
= sizelist
[expr
]
1953 raise IndexError("index out of sizelist range")
1957 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1958 return r
"\%s{%s}" % (self
.size
, expr
)
1960 for s
in defaultsizelist
:
1962 size
.__dict
__[s
] = size(s
)
1965 ###############################################################################
1967 ###############################################################################
1970 class _readpipe(threading
.Thread
):
1971 """threaded reader of TeX/LaTeX output
1972 - sets an event, when a specific string in the programs output is found
1973 - sets an event, when the terminal ends"""
1975 def __init__(self
, pipe
, expectqueue
, gotevent
, gotqueue
, quitevent
):
1976 """initialize the reader
1977 - pipe: file to be read from
1978 - expectqueue: keeps the next InputMarker to be wait for
1979 - gotevent: the "got InputMarker" event
1980 - gotqueue: a queue containing the lines recieved from TeX/LaTeX
1981 - quitevent: the "end of terminal" event"""
1982 threading
.Thread
.__init
__(self
)
1983 self
.setDaemon(1) # don't care if the output might not be finished (nevertheless, it shouldn't happen)
1985 self
.expectqueue
= expectqueue
1986 self
.gotevent
= gotevent
1987 self
.gotqueue
= gotqueue
1988 self
.quitevent
= quitevent
1993 """thread routine"""
1994 read
= self
.pipe
.readline() # read, what comes in
1996 self
.expect
= self
.expectqueue
.get_nowait() # read, what should be expected
2000 # universal EOL handling (convert everything into unix like EOLs)
2001 read
.replace("\r", "")
2002 if not len(read
) or read
[-1] != "\n":
2004 self
.gotqueue
.put(read
) # report, whats readed
2005 if self
.expect
is not None and read
.find(self
.expect
) != -1:
2006 self
.gotevent
.set() # raise the got event, when the output was expected (XXX: within a single line)
2007 read
= self
.pipe
.readline() # read again
2009 self
.expect
= self
.expectqueue
.get_nowait()
2014 if self
.expect
is not None and self
.expect
.find("PyXInputMarker") != -1:
2015 raise RuntimeError("TeX/LaTeX finished unexpectedly")
2016 self
.quitevent
.set()
2020 class _textbox(box
._rect
, base
.PSCmd
):
2021 """basically a box.rect, but it contains a text created by the texrunner
2022 - texrunner._text and texrunner.text return such an object
2023 - _textbox instances can be inserted into a canvas
2024 - the output is contained in a page of the dvifile available thru the texrunner"""
2026 def __init__(self
, x
, y
, left
, right
, height
, depth
, texrunner
, dvinumber
, page
, *styles
):
2027 self
.texttrafo
= trafo
._translate
(x
, y
)
2028 box
._rect
.__init
__(self
, x
- left
, y
- depth
,
2029 left
+ right
, depth
+ height
,
2030 abscenter
= (left
, depth
))
2031 self
.texrunner
= texrunner
2032 self
.dvinumber
= dvinumber
2034 self
.styles
= styles
2036 def transform(self
, *trafos
):
2037 box
._rect
.transform(self
, *trafos
)
2038 for trafo
in trafos
:
2039 self
.texttrafo
= trafo
* self
.texttrafo
2041 def marker(self
, marker
):
2042 return self
.texttrafo
.apply(*self
.texrunner
.marker(self
.dvinumber
, self
.page
, marker
))
2046 for cmd
in self
.styles
:
2047 result
.extend(cmd
.prolog())
2048 return result
+ self
.texrunner
.prolog(self
.dvinumber
, self
.page
)
2050 def write(self
, file):
2051 canvas
._gsave
().write(file) # XXX: canvas?, constructor call needed?
2052 self
.texttrafo
.write(file)
2053 for style
in self
.styles
:
2055 self
.texrunner
.write(file, self
.dvinumber
, self
.page
)
2056 canvas
._grestore
().write(file)
2060 class textbox(_textbox
):
2062 def __init__(self
, x
, y
, left
, right
, height
, depth
, texrunner
, dvinumber
, page
, *styles
):
2063 _textbox
.__init
__(self
, unit
.topt(x
), unit
.topt(y
), unit
.topt(left
), unit
.topt(right
),
2064 unit
.topt(height
), unit
.topt(depth
), texrunner
, dvinumber
, page
, *styles
)
2067 def _cleantmp(texrunner
):
2068 """get rid of temporary files
2069 - function to be registered by atexit
2070 - files contained in usefiles are kept"""
2071 if texrunner
.texruns
: # cleanup while TeX is still running?
2072 texrunner
.texruns
= 0
2073 texrunner
.texdone
= 1
2074 texrunner
.expectqueue
.put_nowait(None) # do not expect any output anymore
2075 if texrunner
.mode
== "latex": # try to immediately quit from TeX or LaTeX
2076 texrunner
.texinput
.write("\n\\catcode`\\@11\\relax\\@@end\n")
2078 texrunner
.texinput
.write("\n\\end\n")
2079 texrunner
.texinput
.close() # close the input queue and
2080 if not texrunner
.waitforevent(texrunner
.quitevent
): # wait for finish of the output
2081 return # didn't got a quit from TeX -> we can't do much more
2082 for usefile
in texrunner
.usefiles
:
2083 extpos
= usefile
.rfind(".")
2085 os
.rename(texrunner
.texfilename
+ usefile
[extpos
:], usefile
)
2088 for file in glob
.glob("%s.*" % texrunner
.texfilename
):
2093 if texrunner
.texdebug
is not None:
2095 texrunner
.texdebug
.close()
2096 texrunner
.texdebug
= None
2101 # texrunner state exceptions
2102 class TexRunsError(Exception): pass
2103 class TexDoneError(Exception): pass
2104 class TexNotInPreambleModeError(Exception): pass
2108 """TeX/LaTeX interface
2109 - runs TeX/LaTeX expressions instantly
2110 - checks TeX/LaTeX response
2111 - the instance variable texmessage stores the last TeX
2112 response as a string
2113 - the instance variable texmessageparsed stores a parsed
2114 version of texmessage; it should be empty after
2115 texmessage.check was called, otherwise a TexResultError
2117 - the instance variable errordebug controls the verbose
2118 level of TexResultError"""
2120 def __init__(self
, mode
="tex",
2125 fontmaps
=config
.get("text", "fontmaps", "psfonts.map"),
2126 waitfortex
=config
.getint("text", "waitfortex", 60),
2127 showwaitfortex
=config
.getint("text", "showwaitfortex", 5),
2128 texipc
=config
.getboolean("text", "texipc", 0),
2134 texmessagestart
=texmessage
.start
,
2135 texmessagedocclass
=texmessage
.load
,
2136 texmessagebegindoc
=(texmessage
.load
, texmessage
.noaux
),
2137 texmessageend
=texmessage
.texend
,
2138 texmessagedefaultpreamble
=texmessage
.load
,
2139 texmessagedefaultrun
=texmessage
.loadfd
):
2141 if mode
!= "tex" and mode
!= "latex":
2142 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
2145 self
.docclass
= docclass
2146 self
.docopt
= docopt
2147 self
.usefiles
= helper
.ensurelist(usefiles
)
2148 self
.fontmap
= readfontmap(fontmaps
.split())
2149 self
.waitfortex
= waitfortex
2150 self
.showwaitfortex
= showwaitfortex
2151 self
.texipc
= texipc
2152 if texdebug
is not None:
2153 if texdebug
[-4:] == ".tex":
2154 self
.texdebug
= open(texdebug
, "w")
2156 self
.texdebug
= open("%s.tex" % texdebug
, "w")
2158 self
.texdebug
= None
2159 self
.dvidebug
= dvidebug
2160 self
.errordebug
= errordebug
2161 self
.dvicopy
= dvicopy
2162 self
.pyxgraphics
= pyxgraphics
2163 texmessagestart
= helper
.ensuresequence(texmessagestart
)
2164 helper
.checkattr(texmessagestart
, allowmulti
=(texmessage
,))
2165 self
.texmessagestart
= texmessagestart
2166 texmessagedocclass
= helper
.ensuresequence(texmessagedocclass
)
2167 helper
.checkattr(texmessagedocclass
, allowmulti
=(texmessage
,))
2168 self
.texmessagedocclass
= texmessagedocclass
2169 texmessagebegindoc
= helper
.ensuresequence(texmessagebegindoc
)
2170 helper
.checkattr(texmessagebegindoc
, allowmulti
=(texmessage
,))
2171 self
.texmessagebegindoc
= texmessagebegindoc
2172 texmessageend
= helper
.ensuresequence(texmessageend
)
2173 helper
.checkattr(texmessageend
, allowmulti
=(texmessage
,))
2174 self
.texmessageend
= texmessageend
2175 texmessagedefaultpreamble
= helper
.ensuresequence(texmessagedefaultpreamble
)
2176 helper
.checkattr(texmessagedefaultpreamble
, allowmulti
=(texmessage
,))
2177 self
.texmessagedefaultpreamble
= texmessagedefaultpreamble
2178 texmessagedefaultrun
= helper
.ensuresequence(texmessagedefaultrun
)
2179 helper
.checkattr(texmessagedefaultrun
, allowmulti
=(texmessage
,))
2180 self
.texmessagedefaultrun
= texmessagedefaultrun
2184 self
.preamblemode
= 1
2190 savetempdir
= tempfile
.tempdir
2191 tempfile
.tempdir
= os
.curdir
2192 self
.texfilename
= os
.path
.basename(tempfile
.mktemp())
2193 tempfile
.tempdir
= savetempdir
2195 def waitforevent(self
, event
):
2196 """waits verbosely with an timeout for an event
2197 - observes an event while periodly while printing messages
2198 - returns the status of the event (isSet)
2199 - does not clear the event"""
2200 if self
.showwaitfortex
:
2203 while waited
< self
.waitfortex
and not hasevent
:
2204 if self
.waitfortex
- waited
> self
.showwaitfortex
:
2205 event
.wait(self
.showwaitfortex
)
2206 waited
+= self
.showwaitfortex
2208 event
.wait(self
.waitfortex
- waited
)
2209 waited
+= self
.waitfortex
- waited
2210 hasevent
= event
.isSet()
2212 if waited
< self
.waitfortex
:
2213 sys
.stderr
.write("*** PyX INFO: still waiting for %s after %i seconds...\n" % (self
.mode
, waited
))
2215 sys
.stderr
.write("*** PyX ERROR: the timeout of %i seconds expired and %s did not respond.\n" % (waited
, self
.mode
))
2218 event
.wait(self
.waitfortex
)
2219 return event
.isSet()
2221 def execute(self
, expr
, *checks
):
2222 """executes expr within TeX/LaTeX
2223 - if self.texruns is not yet set, TeX/LaTeX is initialized,
2224 self.texruns is set and self.preamblemode is set
2225 - the method must not be called, when self.texdone is already set
2226 - expr should be a string or None
2227 - when expr is None, TeX/LaTeX is stopped, self.texruns is unset and
2228 while self.texdone becomes set
2229 - when self.preamblemode is set, the expr is passed directly to TeX/LaTeX
2230 - when self.preamblemode is unset, the expr is passed to \ProcessPyXBox
2232 if not self
.texruns
:
2233 if self
.texdebug
is not None:
2234 self
.texdebug
.write("%% PyX %s texdebug file\n" % version
.version
)
2235 self
.texdebug
.write("%% mode: %s\n" % self
.mode
)
2236 self
.texdebug
.write("%% date: %s\n" % time
.asctime(time
.localtime(time
.time())))
2237 for usefile
in self
.usefiles
:
2238 extpos
= usefile
.rfind(".")
2240 os
.rename(usefile
, self
.texfilename
+ usefile
[extpos
:])
2243 texfile
= open("%s.tex" % self
.texfilename
, "w") # start with filename -> creates dvi file with that name
2244 texfile
.write("\\relax%\n")
2251 self
.texinput
, self
.texoutput
= os
.popen4("%s%s %s" % (self
.mode
, ipcflag
, self
.texfilename
), "t", 0)
2253 # XXX: workaround for MS Windows (bufsize = 0 makes trouble!?)
2254 self
.texinput
, self
.texoutput
= os
.popen4("%s%s %s" % (self
.mode
, ipcflag
, self
.texfilename
), "t")
2255 atexit
.register(_cleantmp
, self
)
2256 self
.expectqueue
= Queue
.Queue(1) # allow for a single entry only -> keeps the next InputMarker to be wait for
2257 self
.gotevent
= threading
.Event() # keeps the got inputmarker event
2258 self
.gotqueue
= Queue
.Queue(0) # allow arbitrary number of entries
2259 self
.quitevent
= threading
.Event() # keeps for end of terminal event
2260 self
.readoutput
= _readpipe(self
.texoutput
, self
.expectqueue
, self
.gotevent
, self
.gotqueue
, self
.quitevent
)
2262 oldpreamblemode
= self
.preamblemode
2263 self
.preamblemode
= 1
2264 self
.execute("\\scrollmode\n\\raiseerror%\n" # switch to and check scrollmode
2265 "\\def\\PyX{P\\kern-.3em\\lower.5ex\hbox{Y}\kern-.18em X}%\n" # just the PyX Logo
2266 "\\gdef\\PyXHAlign{0}%\n" # global PyXHAlign (0.0-1.0) for the horizontal alignment, default to 0
2267 "\\newbox\\PyXBox%\n" # PyXBox will contain the output
2268 "\\newbox\\PyXBoxHAligned%\n" # PyXBox will contain the horizontal aligned output
2269 "\\newdimen\\PyXDimenHAlignLT%\n" # PyXDimenHAlignLT/RT will contain the left/right extent
2270 "\\newdimen\\PyXDimenHAlignRT%\n" +
2271 _texsettingpreamble
+ # insert preambles for texsetting macros
2272 "\\long\\def\\ProcessPyXBox#1#2{%\n" # the ProcessPyXBox definition (#1 is expr, #2 is page number)
2273 "\\setbox\\PyXBox=\\hbox{{#1}}%\n" # push expression into PyXBox
2274 "\\PyXDimenHAlignLT=\\PyXHAlign\\wd\\PyXBox%\n" # calculate the left/right extent
2275 "\\PyXDimenHAlignRT=\\wd\\PyXBox%\n"
2276 "\\advance\\PyXDimenHAlignRT by -\\PyXDimenHAlignLT%\n"
2277 "\\gdef\\PyXHAlign{0}%\n" # reset the PyXHAlign to the default 0
2278 "\\immediate\\write16{PyXBox:page=#2," # write page and extents of this box to stdout
2279 "lt=\\the\\PyXDimenHAlignLT,"
2280 "rt=\\the\\PyXDimenHAlignRT,"
2281 "ht=\\the\\ht\\PyXBox,"
2282 "dp=\\the\\dp\\PyXBox:}%\n"
2283 "\\setbox\\PyXBoxHAligned=\\hbox{\\kern-\\PyXDimenHAlignLT\\box\\PyXBox}%\n" # align horizontally
2284 "\\ht\\PyXBoxHAligned0pt%\n" # baseline alignment (hight to zero)
2285 "{\\count0=80\\count1=121\\count2=88\\count3=#2\\shipout\\box\\PyXBoxHAligned}}%\n" # shipout PyXBox to Page 80.121.88.<page number>
2286 "\\def\\PyXInput#1{\\immediate\\write16{PyXInputMarker:executeid=#1:}}%\n" # write PyXInputMarker to stdout
2287 "\\def\\PyXMarker#1{\\special{PyX:marker #1}}%\n", # write PyXMarker special into the dvi-file
2288 *self
.texmessagestart
)
2289 os
.remove("%s.tex" % self
.texfilename
)
2290 if self
.mode
== "tex":
2291 if len(self
.lfs
) > 4 and self
.lfs
[-4:] == ".lfs":
2294 lfsname
= "%s.lfs" % self
.lfs
2295 for fulllfsname
in [lfsname
,
2296 os
.path
.join(sys
.prefix
, "share", "pyx", lfsname
),
2297 os
.path
.join(os
.path
.dirname(__file__
), "lfs", lfsname
)]:
2299 lfsfile
= open(fulllfsname
, "r")
2300 lfsdef
= lfsfile
.read()
2306 allfiles
= (glob
.glob("*.lfs") +
2307 glob
.glob(os
.path
.join(sys
.prefix
, "share", "pyx", "*.lfs")) +
2308 glob
.glob(os
.path
.join(os
.path
.dirname(__file__
), "lfs", "*.lfs")))
2312 open(f
, "r").close()
2313 lfsnames
.append(os
.path
.basename(f
)[:-4])
2318 raise IOError("file '%s' is not available or not readable. Available LaTeX font size files (*.lfs): %s" % (lfsname
, lfsnames
))
2320 raise IOError("file '%s' is not available or not readable. No LaTeX font size files (*.lfs) available. Check your installation." % lfsname
)
2321 self
.execute(lfsdef
)
2322 self
.execute("\\normalsize%\n")
2323 self
.execute("\\newdimen\\linewidth%\n")
2324 elif self
.mode
== "latex":
2325 if self
.pyxgraphics
:
2326 for pyxdef
in ["pyx.def",
2327 os
.path
.join(sys
.prefix
, "share", "pyx", "pyx.def"),
2328 os
.path
.join(os
.path
.dirname(__file__
), "..", "contrib", "pyx.def")]:
2330 open(pyxdef
, "r").close()
2335 IOError("file 'pyx.def' is not available or not readable. Check your installation or turn off the pyxgraphics option.")
2336 pyxdef
= os
.path
.abspath(pyxdef
).replace(os
.sep
, "/")
2337 self
.execute("\\makeatletter%\n"
2338 "\\let\\saveProcessOptions=\\ProcessOptions%\n"
2339 "\\def\\ProcessOptions{%\n"
2340 "\\def\\Gin@driver{" + pyxdef
+ "}%\n"
2341 "\\def\\c@lor@namefile{dvipsnam.def}%\n"
2342 "\\saveProcessOptions}%\n"
2344 if self
.docopt
is not None:
2345 self
.execute("\\documentclass[%s]{%s}" % (self
.docopt
, self
.docclass
), *self
.texmessagedocclass
)
2347 self
.execute("\\documentclass{%s}" % self
.docclass
, *self
.texmessagedocclass
)
2348 self
.preamblemode
= oldpreamblemode
2350 if expr
is not None: # TeX/LaTeX should process expr
2351 self
.expectqueue
.put_nowait("PyXInputMarker:executeid=%i:" % self
.executeid
)
2352 if self
.preamblemode
:
2353 self
.expr
= ("%s%%\n" % expr
+
2354 "\\PyXInput{%i}%%\n" % self
.executeid
)
2357 self
.expr
= ("\\ProcessPyXBox{%s%%\n}{%i}%%\n" % (expr
, self
.page
) +
2358 "\\PyXInput{%i}%%\n" % self
.executeid
)
2359 else: # TeX/LaTeX should be finished
2360 self
.expectqueue
.put_nowait("Transcript written on %s.log" % self
.texfilename
)
2361 if self
.mode
== "latex":
2362 self
.expr
= "\\end{document}%\n"
2364 self
.expr
= "\\end%\n"
2365 if self
.texdebug
is not None:
2366 self
.texdebug
.write(self
.expr
)
2367 self
.texinput
.write(self
.expr
)
2368 gotevent
= self
.waitforevent(self
.gotevent
)
2369 self
.gotevent
.clear()
2370 if expr
is None and gotevent
: # TeX/LaTeX should have finished
2373 self
.texinput
.close() # close the input queue and
2374 gotevent
= self
.waitforevent(self
.quitevent
) # wait for finish of the output
2376 self
.texmessage
= ""
2378 self
.texmessage
+= self
.gotqueue
.get_nowait()
2381 self
.texmessageparsed
= self
.texmessage
2383 if expr
is not None:
2384 texmessage
.inputmarker
.check(self
)
2385 if not self
.preamblemode
:
2386 texmessage
.pyxbox
.check(self
)
2387 texmessage
.pyxpageout
.check(self
)
2388 for check
in checks
:
2391 except TexResultWarning
:
2392 traceback
.print_exc()
2393 texmessage
.emptylines
.check(self
)
2394 if len(self
.texmessageparsed
):
2395 raise TexResultError("unhandled TeX response (might be an error)", self
)
2397 raise TexResultError("TeX didn't respond as expected within the timeout period (%i seconds)." % self
.waitfortex
, self
)
2399 def finishdvi(self
):
2400 "finish TeX/LaTeX and read the dvifile"
2401 self
.execute(None, *self
.texmessageend
)
2403 os
.system("dvicopy %(t)s.dvi %(t)s.dvicopy > %(t)s.dvicopyout 2> %(t)s.dvicopyerr" % {"t": self
.texfilename
})
2404 dvifilename
= "%s.dvicopy" % self
.texfilename
2406 dvifilename
= "%s.dvi" % self
.texfilename
2408 self
.dvifiles
[-1].readpostamble()
2410 advifile
= dvifile(dvifilename
, self
.fontmap
, debug
=self
.dvidebug
)
2411 self
.dvifiles
.append(advifile
)
2412 self
.dvifiles
[-1].readfile()
2415 def marker(self
, dvinumber
, page
, marker
):
2416 "return the marker position"
2417 if not self
.texipc
and not self
.texdone
:
2419 return self
.dvifiles
[dvinumber
].marker(page
, marker
)
2421 def prolog(self
, dvinumber
, page
):
2422 "return the dvifile prolog"
2423 if not self
.texipc
and not self
.texdone
:
2425 return self
.dvifiles
[dvinumber
].prolog(page
)
2427 def write(self
, file, dvinumber
, page
):
2428 "write a page from the dvifile"
2429 if not self
.texipc
and not self
.texdone
:
2431 return self
.dvifiles
[dvinumber
].write(file, page
)
2433 def reset(self
, reinit
=0):
2434 "resets the tex runner to its initial state (upto its record to old dvi file(s))"
2437 if self
.texdebug
is not None:
2438 self
.texdebug
.write("%s\n%% preparing restart of %s\n" % ("%"*80, self
.mode
))
2443 self
.preamblemode
= 1
2444 for expr
, args
in self
.preambles
:
2445 self
.execute(expr
, *args
)
2446 if self
.mode
== "latex":
2447 self
.execute("\\begin{document}", *self
.texmessagebegindoc
)
2448 self
.preamblemode
= 0
2451 self
.preamblemode
= 1
2453 def set(self
, mode
=None,
2460 showwaitfortex
=None,
2467 texmessagestart
=None,
2468 texmessagedocclass
=None,
2469 texmessagebegindoc
=None,
2471 texmessagedefaultpreamble
=None,
2472 texmessagedefaultrun
=None):
2473 """provide a set command for TeX/LaTeX settings
2474 - TeX/LaTeX must not yet been started
2475 - especially needed for the defaultrunner, where no access to
2476 the constructor is available"""
2479 if mode
is not None:
2481 if mode
!= "tex" and mode
!= "latex":
2482 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
2486 if docclass
is not None:
2487 self
.docclass
= docclass
2488 if docopt
is not None:
2489 self
.docopt
= docopt
2490 if usefiles
is not None:
2491 self
.usefiles
= helper
.ensurelist(usefiles
)
2492 if fontmaps
is not None:
2493 self
.fontmap
= readfontmap(fontmaps
.split())
2494 if waitfortex
is not None:
2495 self
.waitfortex
= waitfortex
2496 if showwaitfortex
is not None:
2497 self
.showwaitfortex
= showwaitfortex
2498 if texipc
is not None:
2499 self
.texipc
= texipc
2500 if texdebug
is not None:
2501 if self
.texdebug
is not None:
2502 self
.texdebug
.close()
2503 if texdebug
[-4:] == ".tex":
2504 self
.texdebug
= open(texdebug
, "w")
2506 self
.texdebug
= open("%s.tex" % texdebug
, "w")
2507 if dvidebug
is not None:
2508 self
.dvidebug
= dvidebug
2509 if errordebug
is not None:
2510 self
.errordebug
= errordebug
2511 if dvicopy
is not None:
2512 self
.dvicopy
= dvicopy
2513 if pyxgraphics
is not None:
2514 self
.pyxgraphics
= pyxgraphics
2515 if errordebug
is not None:
2516 self
.errordebug
= errordebug
2517 if texmessagestart
is not None:
2518 texmessagestart
= helper
.ensuresequence(texmessagestart
)
2519 helper
.checkattr(texmessagestart
, allowmulti
=(texmessage
,))
2520 self
.texmessagestart
= texmessagestart
2521 if texmessagedocclass
is not None:
2522 texmessagedocclass
= helper
.ensuresequence(texmessagedocclass
)
2523 helper
.checkattr(texmessagedocclass
, allowmulti
=(texmessage
,))
2524 self
.texmessagedocclass
= texmessagedocclass
2525 if texmessagebegindoc
is not None:
2526 texmessagebegindoc
= helper
.ensuresequence(texmessagebegindoc
)
2527 helper
.checkattr(texmessagebegindoc
, allowmulti
=(texmessage
,))
2528 self
.texmessagebegindoc
= texmessagebegindoc
2529 if texmessageend
is not None:
2530 texmessageend
= helper
.ensuresequence(texmessageend
)
2531 helper
.checkattr(texmessageend
, allowmulti
=(texmessage
,))
2532 self
.texmessageend
= texmessageend
2533 if texmessagedefaultpreamble
is not None:
2534 texmessagedefaultpreamble
= helper
.ensuresequence(texmessagedefaultpreamble
)
2535 helper
.checkattr(texmessagedefaultpreamble
, allowmulti
=(texmessage
,))
2536 self
.texmessagedefaultpreamble
= texmessagedefaultpreamble
2537 if texmessagedefaultrun
is not None:
2538 texmessagedefaultrun
= helper
.ensuresequence(texmessagedefaultrun
)
2539 helper
.checkattr(texmessagedefaultrun
, allowmulti
=(texmessage
,))
2540 self
.texmessagedefaultrun
= texmessagedefaultrun
2542 def preamble(self
, expr
, *args
):
2543 r
"""put something into the TeX/LaTeX preamble
2544 - in LaTeX, this is done before the \begin{document}
2545 (you might use \AtBeginDocument, when you're in need for)
2546 - it is not allowed to call preamble after calling the
2547 text method for the first time (for LaTeX this is needed
2548 due to \begin{document}; in TeX it is forced for compatibility
2549 (you should be able to switch from TeX to LaTeX, if you want,
2550 without breaking something
2551 - preamble expressions must not create any dvi output
2552 - args might contain texmessage instances"""
2553 if self
.texdone
or not self
.preamblemode
:
2554 raise TexNotInPreambleModeError
2555 helper
.checkattr(args
, allowmulti
=(texmessage
,))
2556 args
= helper
.getattrs(args
, texmessage
, default
=self
.texmessagedefaultpreamble
)
2557 self
.execute(expr
, *args
)
2558 self
.preambles
.append((expr
, args
))
2560 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:")
2562 def _text(self
, x
, y
, expr
, *args
):
2563 """create text by passing expr to TeX/LaTeX
2564 - returns a textbox containing the result from running expr thru TeX/LaTeX
2565 - the box center is set to x, y
2566 - *args may contain style parameters, namely:
2567 - an halign instance
2568 - _texsetting instances
2569 - texmessage instances
2570 - trafo._trafo instances
2571 - base.PathStyle instances"""
2573 raise ValueError("None expression is invalid")
2575 self
.reset(reinit
=1)
2577 if self
.preamblemode
:
2578 if self
.mode
== "latex":
2579 self
.execute("\\begin{document}", *self
.texmessagebegindoc
)
2580 self
.preamblemode
= 0
2582 if self
.texipc
and self
.dvicopy
:
2583 raise RuntimeError("texipc and dvicopy can't be mixed up")
2584 helper
.checkattr(args
, allowmulti
=(_texsetting
, texmessage
, trafo
._trafo
, base
.fillattr
))
2585 texsettings
= helper
.getattrs(args
, _texsetting
, default
=[])
2587 for texsetting
in texsettings
:
2588 if texsetting
.exclusive
:
2589 if texsetting
.id not in exclusive
:
2590 exclusive
.append(texsetting
.id)
2592 raise RuntimeError("multiple occurance of exclusive texsetting with id=%i" % texsetting
.id)
2594 for texsetting
in texsettings
:
2595 expr
= texsetting
.modifyexpr(expr
, texsettings
, self
)
2596 self
.execute(expr
, *helper
.getattrs(args
, texmessage
, default
=self
.texmessagedefaultrun
))
2599 self
.dvifiles
.append(dvifile("%s.dvi" % self
.texfilename
, self
.fontmap
, debug
=self
.dvidebug
))
2600 self
.dvifiles
[-1].readpreamble()
2601 self
.dvifiles
[-1].readpage()
2602 match
= self
.PyXBoxPattern
.search(self
.texmessage
)
2603 if not match
or int(match
.group("page")) != self
.page
:
2604 raise TexResultError("box extents not found", self
)
2605 left
, right
, height
, depth
= map(lambda x
: float(x
) * 72.0 / 72.27, match
.group("lt", "rt", "ht", "dp"))
2606 box
= _textbox(x
, y
, left
, right
, height
, depth
, self
, self
.dvinumber
, self
.page
,
2607 *helper
.getattrs(args
, base
.fillattr
, default
=[]))
2608 for t
in helper
.getattrs(args
, trafo
._trafo
, default
=()):
2612 def text(self
, x
, y
, expr
, *args
):
2613 return self
._text
(unit
.topt(x
), unit
.topt(y
), expr
, *args
)
2616 # the module provides an default texrunner and methods for direct access
2617 defaulttexrunner
= texrunner()
2618 reset
= defaulttexrunner
.reset
2619 set = defaulttexrunner
.set
2620 preamble
= defaulttexrunner
.preamble
2621 text
= defaulttexrunner
.text
2622 _text
= defaulttexrunner
._text