4 # Copyright (C) 2002-2003 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2002-2003 André Wobst <wobsta@users.sourceforge.net>
6 # Copyright (C) 2003 Michael Schindler <m-schindler@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 import exceptions
, glob
, os
, threading
, Queue
, traceback
, re
, struct
, tempfile
, sys
, atexit
25 import helper
, unit
, bbox
, box
, base
, canvas
, color
, trafo
, path
, pykpathsea
28 def __init__(self
, word
):
34 self
.precomma
= abs(word
) >> 20
35 self
.postcomma
= abs(word
) & 0xFFFFF
38 return self
.sign
* (self
.precomma
+ 1.0*self
.postcomma
/0xFFFFF)
40 def __mul__(self
, other
):
44 result
.sign
= self
.sign
*other
.sign
45 c
= self
.postcomma
*other
.precomma
+ self
.precomma
*other
.postcomma
46 result
.precomma
= self
.precomma
*other
.precomma
+ (c
>> 20)
47 result
.postcomma
= c
& 0xFFFFF + ((self
.postcomma
*other
.postcomma
) >> 40)
53 def __init__(self
, filename
, mode
="r"):
54 self
.file = open(filename
, mode
)
57 return self
.file.tell()
59 def read(self
, bytes
):
60 return self
.file.read(bytes
)
62 def readint(self
, bytes
=4, signed
=0):
66 value
= ord(self
.file.read(1))
67 if first
and signed
and value
> 127:
70 result
= 256 * result
+ value
75 return struct
.unpack(">l", self
.file.read(4))[0]
78 return struct
.unpack(">L", self
.file.read(4))[0]
82 return struct
.unpack(">l", "\0"+self
.file.read(3))[0]
86 return struct
.unpack(">L", "\0"+self
.file.read(3))[0]
89 return struct
.unpack(">h", self
.file.read(2))[0]
92 return struct
.unpack(">H", self
.file.read(2))[0]
95 return struct
.unpack("b", self
.file.read(1))[0]
98 return struct
.unpack("B", self
.file.read(1))[0]
100 def readstring(self
, bytes
):
102 assert l
<= bytes
-1, "inconsistency in file: string too long"
103 return self
.file.read(bytes
-1)[:l
]
106 """ ascii file containing tokens separated by spaces.
108 Comments beginning with % are ignored. Strings containing spaces
109 are not handled correctly
112 def __init__(self
, filename
):
113 self
.file = open(filename
, "r")
117 """ return next token or None if EOF """
119 line
= self
.file.readline()
122 self
.line
= line
.split("%")[0].split()
124 self
.line
= self
.line
[1:]
131 class DVIError(exceptions
.Exception): pass
134 class TFMError(exceptions
.Exception): pass
137 class char_info_word
:
138 def __init__(self
, word
):
139 self
.width_index
= int((word
& 0xFF000000L
) >> 24) #make sign-safe
140 self
.height_index
= (word
& 0x00F00000) >> 20
141 self
.depth_index
= (word
& 0x000F0000) >> 16
142 self
.italic_index
= (word
& 0x0000FC00) >> 10
143 self
.tag
= (word
& 0x00000300) >> 8
144 self
.remainder
= (word
& 0x000000FF)
148 def __init__(self
, name
, debug
=0):
149 self
.file = binfile(name
, "rb")
156 self
.lf
= self
.file.readint16()
157 self
.lh
= self
.file.readint16()
158 self
.bc
= self
.file.readint16()
159 self
.ec
= self
.file.readint16()
160 self
.nw
= self
.file.readint16()
161 self
.nh
= self
.file.readint16()
162 self
.nd
= self
.file.readint16()
163 self
.ni
= self
.file.readint16()
164 self
.nl
= self
.file.readint16()
165 self
.nk
= self
.file.readint16()
166 self
.ne
= self
.file.readint16()
167 self
.np
= self
.file.readint16()
169 if not (self
.bc
-1 <= self
.ec
<= 255 and
171 self
.lf
== 6+self
.lh
+(self
.ec
-self
.bc
+1)+self
.nw
+self
.nh
+self
.nd
172 +self
.ni
+self
.nl
+self
.nk
+self
.ne
+self
.np
):
173 raise TFMError
, "error in TFM pre-header"
176 print "lh=%d" % self
.lh
182 self
.checksum
= self
.file.readint32()
183 self
.designsizeraw
= self
.file.readint32()
184 assert self
.designsizeraw
> 0, "invald design size"
185 self
.designsize
= fix_word(self
.designsizeraw
)
187 assert self
.lh
> 11, "inconsistency in TFM file: incomplete field"
188 self
.charcoding
= self
.file.readstring(40)
190 self
.charcoding
= None
193 assert self
.lh
> 16, "inconsistency in TFM file: incomplete field"
194 self
.fontfamily
= self
.file.readstring(20)
196 self
.fontfamily
= None
199 print "(FAMILY %s)" % self
.fontfamily
200 print "(CODINGSCHEME %s)" % self
.charcoding
201 print "(DESINGSIZE R %f)" % self
.designsize
204 self
.sevenbitsave
= self
.file.readuchar()
205 # ignore the following two bytes
206 self
.file.readint16()
207 facechar
= self
.file.readuchar()
208 # decode ugly face specification into the Knuth suggested string
220 self
.face
= "L" + self
.face
223 self
.face
= "B" + self
.face
226 self
.face
= "M" + self
.face
229 self
.face
= self
.face
[0] + "I" + self
.face
[1]
231 self
.face
= self
.face
[0] + "R" + self
.face
[1]
236 self
.sevenbitsave
= self
.face
= None
239 # just ignore the rest
240 print self
.file.read((self
.lh
-18)*4)
246 self
.char_info
= [None]*(self
.ec
+1)
248 for charcode
in range(self
.bc
, self
.ec
+1):
249 self
.char_info
[charcode
] = char_info_word(self
.file.readint32())
250 if self
.char_info
[charcode
].width_index
== 0:
251 # disable character if width_index is zero
252 self
.char_info
[charcode
] = None
258 self
.width
= [None for width_index
in range(self
.nw
)]
259 for width_index
in range(self
.nw
):
260 # self.width[width_index] = fix_word(self.file.readint32())
261 self
.width
[width_index
] = self
.file.readint32()
267 self
.height
= [None for height_index
in range(self
.nh
)]
268 for height_index
in range(self
.nh
):
269 # self.height[height_index] = fix_word(self.file.readint32())
270 self
.height
[height_index
] = self
.file.readint32()
276 self
.depth
= [None for depth_index
in range(self
.nd
)]
277 for depth_index
in range(self
.nd
):
278 # self.depth[depth_index] = fix_word(self.file.readint32())
279 self
.depth
[depth_index
] = self
.file.readint32()
285 self
.italic
= [None for italic_index
in range(self
.ni
)]
286 for italic_index
in range(self
.ni
):
287 # self.italic[italic_index] = fix_word(self.file.readint32())
288 self
.italic
[italic_index
] = self
.file.readint32()
294 # XXX decode to lig_kern_command
296 self
.lig_kern
= [None for lig_kern_index
in range(self
.nl
)]
297 for lig_kern_index
in range(self
.nl
):
298 self
.lig_kern
[lig_kern_index
] = self
.file.readint32()
304 self
.kern
= [None for kern_index
in range(self
.nk
)]
305 for kern_index
in range(self
.nk
):
306 # self.kern[kern_index] = fix_word(self.file.readint32())
307 self
.kern
[kern_index
] = self
.file.readint32()
313 # XXX decode to extensible_recipe
315 self
.exten
= [None for exten_index
in range(self
.ne
)]
316 for exten_index
in range(self
.ne
):
317 self
.exten
[exten_index
] = self
.file.readint32()
325 self
.param
= [None for param_index
in range(self
.np
)]
326 for param_index
in range(self
.np
):
327 self
.param
[param_index
] = self
.file.readint32()
329 self
.file.file.close()
334 def __init__(self
, filename
):
335 """ font encoding contained in filename """
336 encpath
= pykpathsea
.find_file(filename
, pykpathsea
.kpse_tex_ps_header_format
)
337 encfile
= tokenfile(encpath
)
340 self
.encname
= encfile
.gettoken()
341 token
= encfile
.gettoken()
343 raise RuntimeError("cannot parse encoding file '%s', expecting '[' got '%s'" % (filename
, token
))
346 token
= encfile
.gettoken()
347 if token
is None or token
=="]":
348 raise RuntimeError("not enough charcodes in encoding file '%s'" % filename
)
349 self
.encvector
.append(token
)
350 if encfile
.gettoken() != "]":
351 raise RuntimeError("too many charcodes in encoding file '%s'" % filename
)
352 token
= encfile
.gettoken()
354 raise RuntimeError("cannot parse encoding file '%s', expecting 'def' got '%s'" % (filename
, token
))
355 token
= encfile
.gettoken()
357 raise RuntimeError("encoding file '%s' too long" % filename
)
360 def encode(self
, charcode
):
361 return self
.encvector
[charcode
]
366 tokenpattern
= re
.compile(r
'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
368 def __init__(self
, s
):
369 """ construct font mapping from line s of dvips mapping file """
370 self
.texname
= self
.psname
= self
.fontfile
= self
.encoding
= None
372 # supported postscript fragments occuring in psfonts.map
373 self
.reencodefont
= self
.extendfont
= self
.slantfont
= None
377 match
= self
.tokenpattern
.match(s
)
379 if match
.groups()[0]:
380 tokens
.append('"%s"' % match
.groups()[0])
382 tokens
.append(match
.groups()[2])
385 raise RuntimeError("wrong syntax in font catalog file 'psfonts.map'")
388 if token
.startswith("<"):
389 if token
.startswith("<<"):
390 # XXX: support non-partial download here
391 self
.fontfile
= token
[2:]
392 elif token
.startswith("<["):
393 self
.encoding
= token
[2:]
394 elif token
.endswith(".pfa") or token
.endswith(".pfb"):
395 self
.fontfile
= token
[1:]
396 elif token
.endswith(".enc"):
397 self
.encoding
= token
[1:]
399 raise RuntimeError("wrong syntax in font catalog file 'psfonts.map'")
400 elif token
.startswith('"'):
401 pscode
= token
[1:-1].split()
402 # parse standard postscript code fragments
405 arg
, cmd
= pscode
[:2]
407 raise RuntimeError("Unsupported Postscript fragment '%s' in psfonts.map" % pscode
)
409 if cmd
== "ReEncodeFont":
410 self
.reencodefont
= arg
411 elif cmd
== "ExtendFont":
412 self
.extendfont
= arg
413 elif cmd
== "SlantFont":
416 raise RuntimeError("Unsupported Postscript fragment '%s %s' in psfonts.map" % (arg
, cmd
))
418 if self
.texname
is None:
422 if self
.psname
is None:
423 self
.psname
= self
.texname
426 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
427 (self
.texname
, self
.psname
, self
.fontfile
, repr(self
.encoding
)))
433 mappath
= pykpathsea
.find_file("psfonts.map", pykpathsea
.kpse_dvips_config_format
)
435 raise RuntimeError("cannot find dvips font catalog 'psfonts.map'")
436 mapfile
= open(mappath
, "r")
438 for line
in mapfile
.readlines():
440 if not (line
=="" or line
[0] in (" ", "%", "*", ";" , "#")):
441 fontmapping
= FontMapping(line
)
442 fontmap
[fontmapping
.texname
] = fontmapping
449 def __init__(self
, name
, c
, q
, d
, tfmconv
, debug
=0):
451 self
.tfmpath
= pykpathsea
.find_file("%s.tfm" % self
.name
, pykpathsea
.kpse_tfm_format
)
452 if self
.tfmpath
is None:
453 raise TFMError("cannot find %s.tfm" % self
.name
)
454 self
.tfmfile
= TFMFile(self
.tfmpath
, debug
)
455 self
.fontmapping
= fontmap
.get(name
, None)
456 print "found mapping %s for font %s" % (self
.fontmapping
, self
.name
)
458 if self
.tfmfile
.checksum
!= c
:
459 raise DVIError("check sums do not agree: %d vs. %d" %
460 (self
.tfmfile
.checksum
, c
))
462 self
.tfmdesignsize
= round(tfmconv
*self
.tfmfile
.designsizeraw
)
464 if abs(self
.tfmdesignsize
- d
) > 2:
465 raise DVIError("design sizes do not agree: %d vs. %d" %
466 (self
.tfmdesignsize
, d
))
467 if q
< 0 or q
> 134217728:
468 raise DVIError("font '%s' not loaded: bad scale" % self
.name
)
469 if d
< 0 or d
> 134217728:
470 raise DVIError("font '%s' not loaded: bad design size" % self
.name
)
474 self
.q
= self
.qorig
= q
475 while self
.q
>= 8388608:
479 self
.beta
= 256/self
.alpha
;
480 self
.alpha
= self
.alpha
*self
.q
;
482 # for bookkeeping of used characters
483 self
.usedchars
= [0] * 256
486 return "Font(%s, %d)" % (self
.name
, self
.tfmdesignsize
)
490 def convert(self
, width
):
492 return 16L*width
*self
.qorig
/16777216L
494 # original algorithm of Knuth (at the moment not used)
496 b1
= (width
>> 16) & 0xff
497 b2
= (width
>> 8 ) & 0xff
501 return (((((b3
*self
.q
)/256)+(b2
*self
.q
))/256)+(b1
*self
.q
))/self
.beta
503 return (((((b3
*self
.q
)/256)+(b2
*self
.q
))/256)+(b1
*self
.q
))/self
.beta
-self
.alpha
505 raise TFMError("error in font size")
507 def getwidth(self
, charcode
):
508 return self
.convert(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
510 def getheight(self
, charcode
):
511 return self
.convert(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
513 def getdepth(self
, charcode
):
514 return self
.convert(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
516 def getitalic(self
, charcode
):
517 return self
.convert(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
519 def markcharused(self
, charcode
):
520 self
.usedchars
[charcode
] = 1
522 def mergeusedchars(self
, otherfont
):
523 for i
in range(len(self
.usedchars
)):
524 self
.usedchars
[i
] = self
.usedchars
[i
] or otherfont
.usedchars
[i
]
528 return self
.fontmapping
.psname
530 return self
.name
.upper()
532 def getfontfile(self
):
534 return self
.fontmapping
.fontfile
536 return self
.name
+".pfb"
538 def getencoding(self
):
540 return self
.fontmapping
.encoding
545 _DVI_CHARMIN
= 0 # typeset a character and move right (range min)
546 _DVI_CHARMAX
= 127 # typeset a character and move right (range max)
547 _DVI_SET1234
= 128 # typeset a character and move right
548 _DVI_SETRULE
= 132 # typeset a rule and move right
549 _DVI_PUT1234
= 133 # typeset a character
550 _DVI_PUTRULE
= 137 # typeset a rule
551 _DVI_NOP
= 138 # no operation
552 _DVI_BOP
= 139 # beginning of page
553 _DVI_EOP
= 140 # ending of page
554 _DVI_PUSH
= 141 # save the current positions (h, v, w, x, y, z)
555 _DVI_POP
= 142 # restore positions (h, v, w, x, y, z)
556 _DVI_RIGHT1234
= 143 # move right
557 _DVI_W0
= 147 # move right by w
558 _DVI_W1234
= 148 # move right and set w
559 _DVI_X0
= 152 # move right by x
560 _DVI_X1234
= 153 # move right and set x
561 _DVI_DOWN1234
= 157 # move down
562 _DVI_Y0
= 161 # move down by y
563 _DVI_Y1234
= 162 # move down and set y
564 _DVI_Z0
= 166 # move down by z
565 _DVI_Z1234
= 167 # move down and set z
566 _DVI_FNTNUMMIN
= 171 # set current font (range min)
567 _DVI_FNTNUMMAX
= 234 # set current font (range max)
568 _DVI_FNT1234
= 235 # set current font
569 _DVI_SPECIAL1234
= 239 # special (dvi extention)
570 _DVI_FNTDEF1234
= 243 # define the meaning of a font number
571 _DVI_PRE
= 247 # preamble
572 _DVI_POST
= 248 # postamble beginning
573 _DVI_POSTPOST
= 249 # postamble ending
575 _DVI_VERSION
= 2 # dvi version
577 # position variable indices
594 # PostScript font selection and output primitives
597 class _selectfont(base
.PSOp
):
598 def __init__(self
, name
, size
):
601 def write(self
, file):
602 file.write("/%s %f selectfont\n" % (self
.name
, self
.size
))
604 # XXX: should we provide a prolog method for the font inclusion
605 # instead of using the coarser logic in DVIFile.prolog
608 class _show(base
.PSOp
):
609 def __init__(self
, x
, y
, s
):
614 def write(self
, file):
615 file.write("%f %f moveto (%s) show\n" % (self
.x
, self
.y
, self
.s
))
617 # save and restore colors
619 class _savecolor(base
.PSOp
):
620 def write(self
, file):
621 file.write("currentcolor currentcolorspace\n")
624 class _restorecolor(base
.PSOp
):
625 def write(self
, file):
626 file.write("setcolorspace setcolor\n")
628 class _savetrafo(base
.PSOp
):
629 def write(self
, file):
630 file.write("matrix currentmatrix\n")
633 class _restoretrafo(base
.PSOp
):
634 def write(self
, file):
635 file.write("setmatrix\n")
639 def __init__(self
, filename
, debug
=0):
640 self
.filename
= filename
647 """ flush currently active string """
649 x
= unit
.t_m(self
.actoutstart
[0] * self
.conv
* 0.0254 / self
.resolution
)
650 y
= -unit
.t_m(self
.actoutstart
[1] * self
.conv
* 0.0254 / self
.resolution
)
652 print "[%s]" % self
.actoutstring
653 self
.actpage
.insert(_show(unit
.topt(x
), unit
.topt(y
), self
.actoutstring
))
654 self
.actoutstart
= None
656 def putchar(self
, char
, inch
=1):
657 if self
.actoutstart
is None:
658 self
.actoutstart
= self
.pos
[_POS_H
], self
.pos
[_POS_V
]
659 self
.actoutstring
= ""
660 if char
> 32 and char
< 128 and chr(char
) not in "()[]<>":
661 ascii
= "%s" % chr(char
)
663 ascii
= "\\%03o" % char
664 self
.actoutstring
= self
.actoutstring
+ ascii
665 dx
= inch
and self
.fonts
[self
.activefont
].getwidth(char
) or 0
666 self
.fonts
[self
.activefont
].markcharused(char
)
668 print ("%d: %schar%d h:=%d+%d=%d, hh:=%d" %
670 inch
and "set" or "put",
672 self
.pos
[_POS_H
], dx
, self
.pos
[_POS_H
]+dx
,
674 self
.pos
[_POS_H
] += dx
679 def putrule(self
, height
, width
, inch
=1):
681 x1
= unit
.t_m(self
.pos
[_POS_H
] * self
.conv
* 0.0254 / self
.resolution
)
682 y1
= -unit
.t_m(self
.pos
[_POS_V
] * self
.conv
* 0.0254 / self
.resolution
)
683 w
= unit
.t_m(width
* self
.conv
* 0.0254 / self
.resolution
)
684 h
= unit
.t_m(height
* self
.conv
* 0.0254 / self
.resolution
)
686 if height
> 0 and width
> 0:
688 pixelw
= int(width
*self
.conv
)
689 if pixelw
< width
*self
.conv
: pixelw
+= 1
690 pixelh
= int(height
*self
.conv
)
691 if pixelh
< height
*self
.conv
: pixelh
+= 1
693 print ("%d: %srule height %d, width %d (%dx%d pixels)" %
694 (self
.filepos
, inch
and "set" or "put", height
, width
, pixelh
, pixelw
))
695 self
.actpage
.fill(path
.rect(x1
, y1
, w
, h
))
698 print ("%d: %srule height %d, width %d (invisible)" %
699 (self
.filepos
, inch
and "set" or "put", height
, width
))
703 print (" h:=%d+%d=%d, hh:=%d" %
704 (self
.pos
[_POS_H
], width
, self
.pos
[_POS_H
]+width
, 0))
705 self
.pos
[_POS_H
] += width
708 def usefont(self
, fontnum
):
710 self
.activefont
= fontnum
712 fontpsname
= self
.fonts
[self
.activefont
].getpsname()
713 fontscale
= self
.fonts
[self
.activefont
].scale
714 fontdesignsize
= float(self
.fonts
[self
.activefont
].tfmfile
.designsize
)
715 self
.actpage
.insert(_selectfont(fontpsname
,
716 fontscale
*fontdesignsize
*72/72.27))
719 print ("%d: fntnum%i current font is %s" %
721 self
.activefont
, self
.fonts
[fontnum
].name
))
723 def definefont(self
, cmdnr
, num
, c
, q
, d
, fontname
):
724 # cmdnr: type of fontdef command (only used for debugging output)
727 # Note that q is actually s in large parts of the documentation.
730 self
.fonts
[num
] = Font(fontname
, c
, q
, d
, self
.tfmconv
, self
.debug
> 1)
733 print "%d: fntdef%d %i: %s" % (self
.filepos
, cmdnr
, num
, fontname
)
735 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
737 # scalestring = scale!=1000 and " scaled %d" % scale or ""
738 # print ("Font %i: %s%s---loaded at size %d DVI units" %
739 # (num, fontname, scalestring, q))
741 # print " (this font is magnified %d%%)" % round(scale/10)
743 def special(self
, s
):
745 x
= unit
.t_m(self
.pos
[_POS_H
] * self
.conv
* 0.0254 / self
.resolution
)
746 y
= -unit
.t_m(self
.pos
[_POS_V
] * self
.conv
* 0.0254 / self
.resolution
)
748 print "%d: xxx '%s'" % (self
.filepos
, s
)
749 if not s
.startswith("PyX:"):
750 raise RuntimeError("the special '%s' cannot be handled by PyX, aborting" % s
)
751 command
, args
= s
[4:].split()[0], s
[4:].split()[1:]
752 if command
=="color_begin":
754 c
= color
.cmyk(float(args
[1]), float(args
[2]), float(args
[3]), float(args
[4]))
755 elif args
[0]=="gray":
756 c
= color
.gray(float(args
[1]))
758 c
= color
.hsb(float(args
[1]), float(args
[2]), float(args
[3]))
760 c
= color
.rgb(float(args
[1]), float(args
[2]), float(args
[3]))
762 c
= color
.rgb(int(args
[1])/255.0, int(args
[2])/255.0, int(args
[3])/255.0)
763 elif args
[0]=="texnamed":
765 c
= getattr(color
.cmyk
, args
[1])
766 except AttributeError:
767 raise RuntimeError("unknown TeX color '%s', aborting" % args
[1])
769 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args
[0])
770 self
.actpage
.insert(_savecolor())
771 self
.actpage
.insert(c
)
772 elif command
=="color_end":
773 self
.actpage
.insert(_restorecolor())
774 elif command
=="rotate_begin":
775 self
.actpage
.insert(_savetrafo())
776 self
.actpage
.insert(trafo
.rotate(float(args
[0]), x
, y
))
777 elif command
=="rotate_end":
778 self
.actpage
.insert(_restoretrafo())
779 elif command
=="scale_begin":
780 self
.actpage
.insert(_savetrafo())
781 self
.actpage
.insert(trafo
.scale(float(args
[0]), float(args
[1]), x
, y
))
782 elif command
=="scale_end":
783 self
.actpage
.insert(_restoretrafo())
784 elif command
=="epsinclude":
785 # XXX: we cannot include epsfile in the header because this would
786 # generate a cyclic import with the canvas and text modules
792 name
, value
= arg
.split("=")
793 argdict
[name
] = value
795 # construct kwargs for epsfile constructor
797 epskwargs
["filename"] = argdict
["file"]
798 epskwargs
["bbox"] = bbox
._bbox
(float(argdict
["llx"]), float(argdict
["lly"]),
799 float(argdict
["urx"]), float(argdict
["ury"]))
800 if argdict
.has_key("width"):
801 epskwargs
["width"] = unit
.t_pt(float(argdict
["width"]))
802 if argdict
.has_key("height"):
803 epskwargs
["height"] = unit
.t_pt(float(argdict
["height"]))
804 if argdict
.has_key("clip"):
805 epskwargs
["clip"] = int(argdict
["clip"])
806 self
.actpage
.insert(epsfile
.epsfile(x
, y
, **epskwargs
))
808 raise RuntimeError("unknown PyX special '%s', aborting" % command
)
810 # routines corresponding to the different reader states of the dvi maschine
815 self
.filepos
= file.tell()
816 cmd
= file.readuchar()
819 elif cmd
== _DVI_PRE
:
820 if self
.file.readuchar() != _DVI_VERSION
: raise DVIError
821 num
= file.readuint32()
822 den
= file.readuint32()
823 mag
= file.readuint32()
825 self
.tfmconv
= (25400000.0/num
)*(den
/473628672)/16.0;
827 self
.resolution
= 300.0
828 # self.trueconv = conv in DVIType docu
829 self
.trueconv
= (num
/254000.0)*(self
.resolution
/den
)
830 self
.conv
= self
.trueconv
*(mag
/1000.0)
832 comment
= file.read(file.readuchar())
837 def _read_nopage(self
):
840 self
.filepos
= file.tell()
841 cmd
= file.readuchar()
844 elif cmd
== _DVI_BOP
:
847 print "%d: beginning of page" % self
.filepos
,
848 print file.readuint32()
849 for i
in range(9): file.readuint32()
851 for i
in range(10): file.readuint32()
854 elif cmd
== _DVI_POST
:
855 return _READ_DONE
# we skip the rest
859 def _read_page(self
):
860 self
.pos
= [0, 0, 0, 0, 0, 0]
861 self
.pages
.append(canvas
.canvas())
862 self
.actpage
= self
.pages
[-1]
865 self
.filepos
= file.tell()
866 cmd
= file.readuchar()
869 if cmd
>= _DVI_CHARMIN
and cmd
<= _DVI_CHARMAX
:
871 elif cmd
>= _DVI_SET1234
and cmd
< _DVI_SET1234
+ 4:
872 self
.putchar(file.readint(cmd
- _DVI_SET1234
+ 1))
873 elif cmd
== _DVI_SETRULE
:
874 self
.putrule(file.readint32(), file.readint32())
875 elif cmd
>= _DVI_PUT1234
and cmd
< _DVI_PUT1234
+ 4:
876 self
.putchar(file.readint(cmd
- _DVI_PUT1234
+ 1), inch
=0)
877 elif cmd
== _DVI_PUTRULE
:
878 self
.putrule(file.readint32(), file.readint32(), 0)
879 elif cmd
== _DVI_EOP
:
882 print "%d: eop" % self
.filepos
885 elif cmd
== _DVI_PUSH
:
886 self
.stack
.append(tuple(self
.pos
))
888 print "%d: push" % self
.filepos
889 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
890 (( len(self
.stack
)-1,)+tuple(self
.pos
)))
891 elif cmd
== _DVI_POP
:
893 self
.pos
= list(self
.stack
[-1])
896 print "%d: pop" % self
.filepos
897 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
898 (( len(self
.stack
),)+tuple(self
.pos
)))
899 elif cmd
>= _DVI_RIGHT1234
and cmd
< _DVI_RIGHT1234
+ 4:
901 dh
= file.readint(cmd
- _DVI_RIGHT1234
+ 1, 1)
903 print ("%d: right%d %d h:=%d%+d=%d, hh:=" %
905 cmd
- _DVI_RIGHT1234
+ 1,
909 self
.pos
[_POS_H
]+dh
))
910 self
.pos
[_POS_H
] += dh
914 print ("%d: w0 %d h:=%d%+d=%d, hh:=" %
919 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
920 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
921 elif cmd
>= _DVI_W1234
and cmd
< _DVI_W1234
+ 4:
923 self
.pos
[_POS_W
] = file.readint(cmd
- _DVI_W1234
+ 1, 1)
925 print ("%d: w%d %d h:=%d%+d=%d, hh:=" %
927 cmd
- _DVI_W1234
+ 1,
931 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
932 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
935 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
936 elif cmd
>= _DVI_X1234
and cmd
< _DVI_X1234
+ 4:
938 self
.pos
[_POS_X
] = file.readint(cmd
- _DVI_X1234
+ 1, 1)
939 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
940 elif cmd
>= _DVI_DOWN1234
and cmd
< _DVI_DOWN1234
+ 4:
942 dv
= file.readint(cmd
- _DVI_DOWN1234
+ 1, 1)
944 print ("%d: down%d %d v:=%d%+d=%d, vv:=" %
946 cmd
- _DVI_DOWN1234
+ 1,
950 self
.pos
[_POS_V
]+dv
))
951 self
.pos
[_POS_V
] += dv
955 print ("%d: y0 %d v:=%d%+d=%d, vv:=" %
960 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
961 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
962 elif cmd
>= _DVI_Y1234
and cmd
< _DVI_Y1234
+ 4:
964 self
.pos
[_POS_Y
] = file.readint(cmd
- _DVI_Y1234
+ 1, 1)
966 print ("%d: y%d %d v:=%d%+d=%d, vv:=" %
968 cmd
- _DVI_Y1234
+ 1,
972 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
973 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
976 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
977 elif cmd
>= _DVI_Z1234
and cmd
< _DVI_Z1234
+ 4:
979 self
.pos
[_POS_Z
] = file.readint(cmd
- _DVI_Z1234
+ 1, 1)
980 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
981 elif cmd
>= _DVI_FNTNUMMIN
and cmd
<= _DVI_FNTNUMMAX
:
982 self
.usefont(cmd
- _DVI_FNTNUMMIN
)
983 elif cmd
>= _DVI_FNT1234
and cmd
< _DVI_FNT1234
+ 4:
984 self
.usefont(file.readint(cmd
- _DVI_FNT1234
+ 1, 1))
985 elif cmd
>= _DVI_SPECIAL1234
and cmd
< _DVI_SPECIAL1234
+ 4:
986 self
.special(file.read(file.readint(cmd
- _DVI_SPECIAL1234
+ 1)))
987 elif cmd
>= _DVI_FNTDEF1234
and cmd
< _DVI_FNTDEF1234
+ 4:
988 if cmd
== _DVI_FNTDEF1234
:
990 elif cmd
== _DVI_FNTDEF1234
+1:
991 num
=file.readuint16()
992 elif cmd
== _DVI_FNTDEF1234
+2:
993 num
=file.readuint24()
994 elif cmd
== _DVI_FNTDEF1234
+3:
995 # Cool, here we have according to docu a signed int. Why?
996 num
= file.readint32()
997 self
.definefont(cmd
-_DVI_FNTDEF1234
+1,
1002 file.read(file.readuchar()+file.readuchar()))
1003 else: raise DVIError
1006 """ reads and parses dvi file
1008 This routine reads the dvi file and generates a list
1009 of pages in self.pages. Each page consists itself of
1010 a list of PSCommands equivalent to the content of
1011 the dvi file. Furthermore, the list of used fonts
1012 can be extracted from the array self.fonts.
1015 # XXX max number of fonts
1016 self
.fonts
= [None for i
in range(64)]
1017 self
.activefont
= None
1021 # here goes the result, for each page one list.
1024 # pointer to currently active page
1027 # currently active output: position and content
1028 self
.actoutstart
= None
1029 self
.actoutstring
= ""
1031 self
.file = binfile(self
.filename
, "rb")
1033 # currently read byte in file (for debugging output)
1036 # start up reading process
1038 while state
!=_READ_DONE
:
1039 if state
== _READ_PRE
:
1040 state
= self
._read
_pre
()
1041 elif state
== _READ_NOPAGE
:
1042 state
= self
._read
_nopage
()
1043 elif state
== _READ_PAGE
:
1044 state
= self
._read
_page
()
1046 raise DVIError
# unexpected reader state, should not happen
1049 def prolog(self
, page
): # TODO: AW inserted this page argument -> should return the prolog needed for that page only!
1050 """ return prolog corresponding to contents of dvi file """
1051 # XXX replace this by prolog method in _selectfont
1053 for font
in self
.fonts
:
1054 if font
: result
.append(canvas
.fontdefinition(font
))
1055 result
.extend(self
.pages
[page
-1].prolog())
1058 def write(self
, file, page
):
1059 """write PostScript output for page into file"""
1060 # XXX: remove this method by return canvas to TexRunner
1062 print "dvifile(\"%s\").write() for page %s called" % (self
.filename
, page
)
1063 self
.pages
[page
-1].write(file)
1066 ###############################################################################
1068 # - please don't get confused:
1069 # - there is a texmessage (and a texmessageparsed) attribute within the
1070 # texrunner; it contains TeX/LaTeX response from the last command execution
1071 # - instances of classes derived from the class texmessage are used to
1072 # parse the TeX/LaTeX response as it is stored in the texmessageparsed
1073 # attribute of a texrunner instance
1074 # - the multiple usage of the name texmessage might be removed in the future
1075 # - texmessage instances should implement _Itexmessage
1076 ###############################################################################
1078 class TexResultError(Exception):
1079 """specialized texrunner exception class
1080 - it is raised by texmessage instances, when a texmessage indicates an error
1081 - it is raised by the texrunner itself, whenever there is a texmessage left
1082 after all parsing of this message (by texmessage instances)"""
1084 def __init__(self
, description
, texrunner
):
1085 self
.description
= description
1086 self
.texrunner
= texrunner
1089 """prints a detailed report about the problem
1090 - the verbose level is controlled by texrunner.errordebug"""
1091 if self
.texrunner
.errordebug
>= 2:
1092 return ("%s\n" % self
.description
+
1093 "The expression passed to TeX was:\n"
1094 " %s\n" % self
.texrunner
.expr
.replace("\n", "\n ").rstrip() +
1095 "The return message from TeX was:\n"
1096 " %s\n" % self
.texrunner
.texmessage
.replace("\n", "\n ").rstrip() +
1097 "After parsing this message, the following was left:\n"
1098 " %s" % self
.texrunner
.texmessageparsed
.replace("\n", "\n ").rstrip())
1099 elif self
.texrunner
.errordebug
== 1:
1100 firstlines
= self
.texrunner
.texmessageparsed
.split("\n")
1101 if len(firstlines
) > 5:
1102 firstlines
= firstlines
[:5] + ["(cut after 5 lines, increase errordebug for more output)"]
1103 return ("%s\n" % self
.description
+
1104 "The expression passed to TeX was:\n"
1105 " %s\n" % self
.texrunner
.expr
.replace("\n", "\n ").rstrip() +
1106 "After parsing the return message from TeX, the following was left:\n" +
1107 reduce(lambda x
, y
: "%s %s\n" % (x
,y
), firstlines
, "").rstrip())
1109 return self
.description
1112 class TexResultWarning(TexResultError
):
1113 """as above, but with different handling of the exception
1114 - when this exception is raised by a texmessage instance,
1115 the information just get reported and the execution continues"""
1120 """validates/invalidates TeX/LaTeX response"""
1122 def check(self
, texrunner
):
1123 """check a Tex/LaTeX response and respond appropriate
1124 - read the texrunners texmessageparsed attribute
1125 - if there is an problem found, raise an appropriate
1126 exception (TexResultError or TexResultWarning)
1127 - remove any valid and identified TeX/LaTeX response
1128 from the texrunners texmessageparsed attribute
1129 -> finally, there should be nothing left in there,
1130 otherwise it is interpreted as an error"""
1133 class texmessage
: pass
1136 class _texmessagestart(texmessage
):
1137 """validates TeX/LaTeX startup"""
1139 __implements__
= _Itexmessage
1141 startpattern
= re
.compile(r
"This is [0-9a-zA-Z\s_]*TeX")
1143 def check(self
, texrunner
):
1144 m
= self
.startpattern
.search(texrunner
.texmessageparsed
)
1146 raise TexResultError("TeX startup failed", texrunner
)
1147 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[m
.end():]
1149 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.split("%s.tex" % texrunner
.texfilename
, 1)[1]
1150 except (IndexError, ValueError):
1151 raise TexResultError("TeX running startup file failed", texrunner
)
1153 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.split("*! Undefined control sequence.\n<*> \\raiseerror\n %\n", 1)[1]
1154 except (IndexError, ValueError):
1155 raise TexResultError("TeX scrollmode check failed", texrunner
)
1158 class _texmessagenoaux(texmessage
):
1159 """allows for LaTeXs no-aux-file warning"""
1161 __implements__
= _Itexmessage
1163 def check(self
, texrunner
):
1165 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s.aux." % texrunner
.texfilename
, 1)
1166 texrunner
.texmessageparsed
= s1
+ s2
1167 except (IndexError, ValueError):
1169 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s%s%s.aux." % (os
.curdir
,
1171 texrunner
.texfilename
), 1)
1172 texrunner
.texmessageparsed
= s1
+ s2
1173 except (IndexError, ValueError):
1177 class _texmessageinputmarker(texmessage
):
1178 """validates the PyXInputMarker"""
1180 __implements__
= _Itexmessage
1182 def check(self
, texrunner
):
1184 s1
, s2
= texrunner
.texmessageparsed
.split("PyXInputMarker:executeid=%s:" % texrunner
.executeid
, 1)
1185 texrunner
.texmessageparsed
= s1
+ s2
1186 except (IndexError, ValueError):
1187 raise TexResultError("PyXInputMarker expected", texrunner
)
1190 class _texmessagepyxbox(texmessage
):
1191 """validates the PyXBox output"""
1193 __implements__
= _Itexmessage
1195 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:")
1197 def check(self
, texrunner
):
1198 m
= self
.pattern
.search(texrunner
.texmessageparsed
)
1199 if m
and m
.group("page") == str(texrunner
.page
):
1200 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
1202 raise TexResultError("PyXBox expected", texrunner
)
1205 class _texmessagepyxpageout(texmessage
):
1206 """validates the dvi shipout message (writing a page to the dvi file)"""
1208 __implements__
= _Itexmessage
1210 def check(self
, texrunner
):
1212 s1
, s2
= texrunner
.texmessageparsed
.split("[80.121.88.%s]" % texrunner
.page
, 1)
1213 texrunner
.texmessageparsed
= s1
+ s2
1214 except (IndexError, ValueError):
1215 raise TexResultError("PyXPageOutMarker expected", texrunner
)
1218 class _texmessagetexend(texmessage
):
1219 """validates TeX/LaTeX finish"""
1221 __implements__
= _Itexmessage
1223 def check(self
, texrunner
):
1225 s1
, s2
= texrunner
.texmessageparsed
.split("(%s.aux)" % texrunner
.texfilename
, 1)
1226 texrunner
.texmessageparsed
= s1
+ s2
1227 except (IndexError, ValueError):
1229 s1
, s2
= texrunner
.texmessageparsed
.split("(%s%s%s.aux)" % (os
.curdir
,
1231 texrunner
.texfilename
), 1)
1232 texrunner
.texmessageparsed
= s1
+ s2
1233 except (IndexError, ValueError):
1236 s1
, s2
= texrunner
.texmessageparsed
.split("(see the transcript file for additional information)", 1)
1237 texrunner
.texmessageparsed
= s1
+ s2
1238 except (IndexError, ValueError):
1240 dvipattern
= re
.compile(r
"Output written on %s\.dvi \((?P<page>\d+) pages?, \d+ bytes\)\." % texrunner
.texfilename
)
1241 m
= dvipattern
.search(texrunner
.texmessageparsed
)
1244 raise TexResultError("TeX dvifile messages expected", texrunner
)
1245 if m
.group("page") != str(texrunner
.page
):
1246 raise TexResultError("wrong number of pages reported", texrunner
)
1247 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
1250 s1
, s2
= texrunner
.texmessageparsed
.split("No pages of output.", 1)
1251 texrunner
.texmessageparsed
= s1
+ s2
1252 except (IndexError, ValueError):
1253 raise TexResultError("no dvifile expected")
1255 s1
, s2
= texrunner
.texmessageparsed
.split("Transcript written on %s.log." % texrunner
.texfilename
, 1)
1256 texrunner
.texmessageparsed
= s1
+ s2
1257 except (IndexError, ValueError):
1258 raise TexResultError("TeX logfile message expected")
1261 class _texmessageemptylines(texmessage
):
1262 """validates empty and "*-only" (TeX/LaTeX input marker in interactive mode) lines"""
1264 __implements__
= _Itexmessage
1266 pattern
= re
.compile(r
"^\*?\n", re
.M
)
1268 def check(self
, texrunner
):
1269 m
= self
.pattern
.search(texrunner
.texmessageparsed
)
1271 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
1272 m
= self
.pattern
.search(texrunner
.texmessageparsed
)
1275 class _texmessageload(texmessage
):
1276 """validates inclusion of arbitrary files
1277 - the matched pattern is "(<filename> <arbitrary other stuff>)", where
1278 <fielname> is a readable file and other stuff can be anything
1279 - "(" and ")" must be used consistent (otherwise this validator just does nothing)
1280 - this is not always wanted, but we just assume that file inclusion is fine"""
1282 __implements__
= _Itexmessage
1284 pattern
= re
.compile(r
"\((?P<filename>[^()\s\n]+)[^()]*\)")
1286 def baselevels(self
, s
, maxlevel
=1, brackets
="()"):
1287 """strip parts of a string above a given bracket level
1288 - return a modified (some parts might be removed) version of the string s
1289 where all parts inside brackets with level higher than maxlevel are
1291 - if brackets do not match (number of left and right brackets is wrong
1292 or at some points there were more right brackets than left brackets)
1293 just return the unmodified string"""
1298 if c
== brackets
[0]:
1300 if level
> highestlevel
:
1301 highestlevel
= level
1302 if level
<= maxlevel
:
1304 if c
== brackets
[1]:
1306 if level
== 0 and highestlevel
> 0:
1309 def check(self
, texrunner
):
1310 lowestbracketlevel
= self
.baselevels(texrunner
.texmessageparsed
)
1311 if lowestbracketlevel
is not None:
1312 m
= self
.pattern
.search(lowestbracketlevel
)
1314 if os
.access(m
.group("filename"), os
.R_OK
):
1315 lowestbracketlevel
= lowestbracketlevel
[:m
.start()] + lowestbracketlevel
[m
.end():]
1318 m
= self
.pattern
.search(lowestbracketlevel
)
1320 texrunner
.texmessageparsed
= lowestbracketlevel
1323 class _texmessageloadfd(_texmessageload
):
1324 """validates the inclusion of font description files (fd-files)
1325 - works like _texmessageload
1326 - filename must end with .fd and no further text is allowed"""
1328 pattern
= re
.compile(r
"\((?P<filename>[^)]+.fd)\)")
1331 class _texmessagegraphicsload(_texmessageload
):
1332 """validates the inclusion of files as the graphics packages writes it
1333 - works like _texmessageload, but using "<" and ">" as delimiters
1334 - filename must end with .eps and no further text is allowed"""
1336 pattern
= re
.compile(r
"<(?P<filename>[^>]+.eps)>")
1338 def baselevels(self
, s
, brackets
="<>", **args
):
1339 return _texmessageload
.baselevels(self
, s
, brackets
=brackets
, **args
)
1342 #class _texmessagepdfmapload(_texmessageload):
1343 # """validates the inclusion of files as the graphics packages writes it
1344 # - works like _texmessageload, but using "{" and "}" as delimiters
1345 # - filename must end with .map and no further text is allowed"""
1347 # pattern = re.compile(r"{(?P<filename>[^}]+.map)}")
1349 # def baselevels(self, s, brackets="{}", **args):
1350 # return _texmessageload.baselevels(self, s, brackets=brackets, **args)
1353 class _texmessageignore(_texmessageload
):
1354 """validates any TeX/LaTeX response
1355 - this might be used, when the expression is ok, but no suitable texmessage
1357 - PLEASE: - consider writing suitable tex message parsers
1358 - share your ideas/problems/solutions with others (use the PyX mailing lists)"""
1360 __implements__
= _Itexmessage
1362 def check(self
, texrunner
):
1363 texrunner
.texmessageparsed
= ""
1366 texmessage
.start
= _texmessagestart()
1367 texmessage
.noaux
= _texmessagenoaux()
1368 texmessage
.inputmarker
= _texmessageinputmarker()
1369 texmessage
.pyxbox
= _texmessagepyxbox()
1370 texmessage
.pyxpageout
= _texmessagepyxpageout()
1371 texmessage
.texend
= _texmessagetexend()
1372 texmessage
.emptylines
= _texmessageemptylines()
1373 texmessage
.load
= _texmessageload()
1374 texmessage
.loadfd
= _texmessageloadfd()
1375 texmessage
.graphicsload
= _texmessagegraphicsload()
1376 texmessage
.ignore
= _texmessageignore()
1379 ###############################################################################
1381 # - texsettings are used to modify a TeX/LaTeX expression
1382 # to fit the users need
1383 # - texsettings have an order attribute (id), because the order is usually
1384 # important (e.g. you can not change the fontsize in mathmode in LaTeX)
1385 # - lower id's get applied later (are more outside -> mathmode has a higher
1387 # - order attributes are used to exclude complementary settings (with the
1389 # - texsettings might (in rare cases) depend on each other (e.g. parbox and
1391 ###############################################################################
1395 - modifies a TeX/LaTeX expression"""
1398 """order attribute for TeX settings
1399 - higher id's will be applied first (most inside)"""
1402 """marks exclusive effect of the setting
1403 - when set, settings with this id exclude each other
1404 - when unset, settings with this id do not exclude each other"""
1406 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1407 """modifies the TeX/LaTeX expression
1408 - expr is the original expression
1409 - the return value is the modified expression
1410 - texsettings contains a list of all texsettings (in case a tex setting
1411 depends on another texsetting)
1412 - texrunner contains the texrunner in case the texsetting depends
1415 def __cmp__(self
, other
):
1416 """compare texsetting with other
1417 - other is a texsetting as well
1418 - performs an id comparison (NOTE: higher id's come first!!!)"""
1421 # preamble settings for texsetting macros
1422 _texsettingpreamble
= ""
1428 def __cmp__(self
, other
):
1429 return -cmp(self
.id, other
.id) # note the sign!!!
1432 class halign(_texsetting
):
1433 """horizontal alignment
1434 the left/right splitting is performed within the PyXBox routine"""
1436 __implements__
= _Itexsetting
1440 def __init__(self
, hratio
):
1441 self
.hratio
= hratio
1443 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1444 return r
"\gdef\PyXHAlign{%.5f}%s" % (self
.hratio
, expr
)
1446 halign
.left
= halign(0)
1447 halign
.center
= halign(0.5)
1448 halign
.right
= halign(1)
1451 _texsettingpreamble
+= "\\newbox\\PyXBoxVAlign%\n\\newdimen\PyXDimenVAlign%\n"
1453 class valign(_texsetting
):
1454 "vertical alignment"
1459 class _valigntop(valign
):
1461 __implements__
= _Itexsetting
1463 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1464 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\lower\ht\PyXBoxVAlign\box\PyXBoxVAlign" % expr
1467 class _valignmiddle(valign
):
1469 __implements__
= _Itexsetting
1471 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1472 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\PyXDimenVAlign=0.5\ht\PyXBoxVAlign\advance\PyXDimenVAlign by -0.5\dp\PyXBoxVAlign\lower\PyXDimenVAlign\box\PyXBoxVAlign" % expr
1475 class _valignbottom(valign
):
1477 __implements__
= _Itexsetting
1479 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1480 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\raise\dp\PyXBoxVAlign\box\PyXBoxVAlign" % expr
1483 class _valignbaseline(valign
):
1485 __implements__
= _Itexsetting
1487 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1488 for texsetting
in texsettings
:
1489 if isinstance(texsetting
, parbox
):
1490 raise RuntimeError("valign.baseline: specify top/middle/bottom baseline for parbox")
1494 class _valignxxxbaseline(valign
):
1496 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1497 for texsetting
in texsettings
:
1498 if isinstance(texsetting
, parbox
):
1501 raise RuntimeError(self
.noparboxmessage
)
1505 class _valigntopbaseline(_valignxxxbaseline
):
1507 __implements__
= _Itexsetting
1509 noparboxmessage
= "valign.topbaseline: no parbox defined"
1512 class _valignmiddlebaseline(_valignxxxbaseline
):
1514 __implements__
= _Itexsetting
1516 noparboxmessage
= "valign.middlebaseline: no parbox defined"
1519 class _valignbottombaseline(_valignxxxbaseline
):
1521 __implements__
= _Itexsetting
1523 noparboxmessage
= "valign.bottombaseline: no parbox defined"
1526 valign
.top
= _valigntop()
1527 valign
.middle
= _valignmiddle()
1528 valign
.center
= valign
.middle
1529 valign
.bottom
= _valignbottom()
1530 valign
.baseline
= _valignbaseline()
1531 valign
.topbaseline
= _valigntopbaseline()
1532 valign
.middlebaseline
= _valignmiddlebaseline()
1533 valign
.centerbaseline
= valign
.middlebaseline
1534 valign
.bottombaseline
= _valignbottombaseline()
1537 _texsettingpreamble
+= "\\newbox\\PyXBoxVBox%\n\\newdimen\PyXDimenVBox%\n"
1540 class _parbox(_texsetting
):
1541 "goes into the vertical mode"
1543 __implements__
= _Itexsetting
1547 def __init__(self
, width
):
1550 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1552 for texsetting
in texsettings
:
1553 if isinstance(texsetting
, valign
):
1554 if (not isinstance(texsetting
, _valigntop
) and
1555 not isinstance(texsetting
, _valignmiddle
) and
1556 not isinstance(texsetting
, _valignbottom
) and
1557 not isinstance(texsetting
, _valigntopbaseline
)):
1558 if isinstance(texsetting
, _valignmiddlebaseline
):
1560 elif isinstance(texsetting
, _valignbottombaseline
):
1563 raise RuntimeError("parbox couldn'd identify the valign instance")
1564 if boxkind
== "vcenter":
1565 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
)
1567 return r
"\linewidth%.5ftruept\%s{\hsize\linewidth{%s}}" % (self
.width
* 72.27 / 72, boxkind
, expr
)
1570 class parbox(_parbox
):
1572 def __init__(self
, width
):
1573 _parbox
.__init
__(self
, unit
.topt(width
))
1576 class vshift(_texsetting
):
1583 class _vshiftchar(vshift
):
1584 "vertical down shift by a fraction of a character height"
1586 def __init__(self
, lowerratio
, heightstr
="0"):
1587 self
.lowerratio
= lowerratio
1588 self
.heightstr
= heightstr
1590 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1591 return r
"\setbox0\hbox{{%s}}\lower%.5f\ht0\hbox{{%s}}" % (self
.heightstr
, self
.lowerratio
, expr
)
1594 class _vshiftmathaxis(vshift
):
1595 "vertical down shift by the height of the math axis"
1597 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1598 return r
"\setbox0\hbox{$\vcenter{\vrule width0pt}$}\lower\ht0\hbox{{%s}}" % expr
1601 vshift
.char
= _vshiftchar
1602 vshift
.bottomzero
= vshift
.char(0)
1603 vshift
.middlezero
= vshift
.char(0.5)
1604 vshift
.centerzero
= vshift
.middlezero
1605 vshift
.topzero
= vshift
.char(1)
1606 vshift
.mathaxis
= _vshiftmathaxis()
1609 class _mathmode(_texsetting
):
1612 __implements__
= _Itexsetting
1616 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1617 return r
"$\displaystyle{%s}$" % expr
1619 mathmode
= _mathmode()
1622 defaultsizelist
= ["normalsize", "large", "Large", "LARGE", "huge", "Huge", None, "tiny", "scriptsize", "footnotesize", "small"]
1624 class size(_texsetting
):
1627 __implements__
= _Itexsetting
1631 def __init__(self
, expr
, sizelist
=defaultsizelist
):
1632 if helper
.isinteger(expr
):
1633 if expr
>= 0 and expr
< sizelist
.index(None):
1634 self
.size
= sizelist
[expr
]
1635 elif expr
< 0 and expr
+ len(sizelist
) > sizelist
.index(None):
1636 self
.size
= sizelist
[expr
]
1638 raise IndexError("index out of sizelist range")
1642 def modifyexpr(self
, expr
, texsettings
, texrunner
):
1643 return r
"\%s{%s}" % (self
.size
, expr
)
1645 for s
in defaultsizelist
:
1647 size
.__dict
__[s
] = size(s
)
1650 ###############################################################################
1652 ###############################################################################
1655 class _readpipe(threading
.Thread
):
1656 """threaded reader of TeX/LaTeX output
1657 - sets an event, when a specific string in the programs output is found
1658 - sets an event, when the terminal ends"""
1660 def __init__(self
, pipe
, expectqueue
, gotevent
, gotqueue
, quitevent
):
1661 """initialize the reader
1662 - pipe: file to be read from
1663 - expectqueue: keeps the next InputMarker to be wait for
1664 - gotevent: the "got InputMarker" event
1665 - gotqueue: a queue containing the lines recieved from TeX/LaTeX
1666 - quitevent: the "end of terminal" event"""
1667 threading
.Thread
.__init
__(self
)
1668 self
.setDaemon(1) # don't care if the output might not be finished (nevertheless, it shouldn't happen)
1670 self
.expectqueue
= expectqueue
1671 self
.gotevent
= gotevent
1672 self
.gotqueue
= gotqueue
1673 self
.quitevent
= quitevent
1678 """thread routine"""
1679 read
= self
.pipe
.readline() # read, what comes in
1681 self
.expect
= self
.expectqueue
.get_nowait() # read, what should be expected
1685 # universal EOL handling (convert everything into unix like EOLs)
1686 read
.replace("\r", "")
1687 if not len(read
) or read
[-1] != "\n":
1689 self
.gotqueue
.put(read
) # report, whats readed
1690 if self
.expect
is not None and read
.find(self
.expect
) != -1:
1691 self
.gotevent
.set() # raise the got event, when the output was expected (XXX: within a single line)
1692 read
= self
.pipe
.readline() # read again
1694 self
.expect
= self
.expectqueue
.get_nowait()
1699 if self
.expect
is not None and self
.expect
.find("PyXInputMarker") != -1:
1700 raise RuntimeError("TeX/LaTeX finished unexpectedly")
1701 self
.quitevent
.set()
1705 class _textbox(box
._rect
, base
.PSCmd
):
1706 """basically a box.rect, but it contains a text created by the texrunner
1707 - texrunner._text and texrunner.text return such an object
1708 - _textbox instances can be inserted into a canvas
1709 - the output is contained in a page of the dvifile available thru the texrunner"""
1711 def __init__(self
, x
, y
, left
, right
, height
, depth
, texrunner
, dvinumber
, page
, *styles
):
1712 self
.texttrafo
= trafo
._translate
(x
, y
)
1713 box
._rect
.__init
__(self
, x
- left
, y
- depth
,
1714 left
+ right
, depth
+ height
,
1715 abscenter
= (left
, depth
))
1716 self
.texrunner
= texrunner
1717 self
.dvinumber
= dvinumber
1719 self
.styles
= styles
1721 def transform(self
, *trafos
):
1722 box
._rect
.transform(self
, *trafos
)
1723 for trafo
in trafos
:
1724 self
.texttrafo
= trafo
* self
.texttrafo
1728 for cmd
in self
.styles
:
1729 result
.extend(cmd
.prolog())
1730 return result
+ self
.texrunner
.prolog(self
.dvinumber
, self
.page
)
1732 def write(self
, file):
1733 canvas
._gsave
().write(file) # XXX: canvas?, constructor call needed?
1734 self
.texttrafo
.write(file)
1735 for style
in self
.styles
:
1737 self
.texrunner
.write(file, self
.dvinumber
, self
.page
)
1738 canvas
._grestore
().write(file)
1742 class textbox(_textbox
):
1744 def __init__(self
, x
, y
, left
, right
, height
, depth
, texrunner
, page
):
1745 _textbox
.__init
__(unit
.topt(x
), unit
.topt(y
), unit
.topt(left
), unit
.topt(right
),
1746 unit
.topt(height
), unit
.topt(depth
), texrunner
, page
)
1749 def _cleantmp(texrunner
):
1750 """get rid of temporary files
1751 - function to be registered by atexit
1752 - files contained in usefiles are kept"""
1753 if texrunner
.texruns
: # cleanup while TeX is still running?
1754 texrunner
.texruns
= 0
1755 texrunner
.texdone
= 1
1756 texrunner
.expectqueue
.put_nowait(None) # do not expect any output anymore
1757 texrunner
.texinput
.close() # close the input queue and
1758 texrunner
.quitevent
.wait(texrunner
.waitfortex
) # wait for finish of the output
1759 if not texrunner
.quitevent
.isSet(): return # didn't got a quit from TeX -> we can't do much more
1760 for usefile
in texrunner
.usefiles
:
1761 extpos
= usefile
.rfind(".")
1763 os
.rename(texrunner
.texfilename
+ usefile
[extpos
:], usefile
)
1766 for file in glob
.glob("%s.*" % texrunner
.texfilename
):
1773 # texrunner state exceptions
1774 class TexRunsError(Exception): pass
1775 class TexDoneError(Exception): pass
1776 class TexNotInPreambleModeError(Exception): pass
1780 """TeX/LaTeX interface
1781 - runs TeX/LaTeX expressions instantly
1782 - checks TeX/LaTeX response
1783 - the instance variable texmessage stores the last TeX
1784 response as a string
1785 - the instance variable texmessageparsed stores a parsed
1786 version of texmessage; it should be empty after
1787 texmessage.check was called, otherwise a TexResultError
1789 - the instance variable errordebug controls the verbose
1790 level of TexResultError"""
1792 def __init__(self
, mode
="tex",
1803 texmessagestart
=texmessage
.start
,
1804 texmessagedocclass
=texmessage
.load
,
1805 texmessagebegindoc
=(texmessage
.load
, texmessage
.noaux
),
1806 texmessageend
=texmessage
.texend
,
1807 texmessagedefaultpreamble
=texmessage
.load
,
1808 texmessagedefaultrun
=texmessage
.loadfd
):
1810 if mode
!= "tex" and mode
!= "latex":
1811 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
1814 self
.docclass
= docclass
1815 self
.docopt
= docopt
1816 self
.usefiles
= helper
.ensurelist(usefiles
)
1817 self
.waitfortex
= waitfortex
1818 self
.texdebug
= texdebug
1819 self
.dvidebug
= dvidebug
1820 self
.errordebug
= errordebug
1821 self
.dvicopy
= dvicopy
1822 self
.pyxgraphics
= pyxgraphics
1823 texmessagestart
= helper
.ensuresequence(texmessagestart
)
1824 helper
.checkattr(texmessagestart
, allowmulti
=(texmessage
,))
1825 self
.texmessagestart
= texmessagestart
1826 texmessagedocclass
= helper
.ensuresequence(texmessagedocclass
)
1827 helper
.checkattr(texmessagedocclass
, allowmulti
=(texmessage
,))
1828 self
.texmessagedocclass
= texmessagedocclass
1829 texmessagebegindoc
= helper
.ensuresequence(texmessagebegindoc
)
1830 helper
.checkattr(texmessagebegindoc
, allowmulti
=(texmessage
,))
1831 self
.texmessagebegindoc
= texmessagebegindoc
1832 texmessageend
= helper
.ensuresequence(texmessageend
)
1833 helper
.checkattr(texmessageend
, allowmulti
=(texmessage
,))
1834 self
.texmessageend
= texmessageend
1835 texmessagedefaultpreamble
= helper
.ensuresequence(texmessagedefaultpreamble
)
1836 helper
.checkattr(texmessagedefaultpreamble
, allowmulti
=(texmessage
,))
1837 self
.texmessagedefaultpreamble
= texmessagedefaultpreamble
1838 texmessagedefaultrun
= helper
.ensuresequence(texmessagedefaultrun
)
1839 helper
.checkattr(texmessagedefaultrun
, allowmulti
=(texmessage
,))
1840 self
.texmessagedefaultrun
= texmessagedefaultrun
1844 self
.preamblemode
= 1
1849 savetempdir
= tempfile
.tempdir
1850 tempfile
.tempdir
= os
.curdir
1851 self
.texfilename
= os
.path
.basename(tempfile
.mktemp())
1852 tempfile
.tempdir
= savetempdir
1854 def execute(self
, expr
, *checks
):
1855 """executes expr within TeX/LaTeX
1856 - if self.texruns is not yet set, TeX/LaTeX is initialized,
1857 self.texruns is set and self.preamblemode is set
1858 - the method must not be called, when self.texdone is already set
1859 - expr should be a string or None
1860 - when expr is None, TeX/LaTeX is stopped, self.texruns is unset and
1861 while self.texdone becomes set
1862 - when self.preamblemode is set, the expr is passed directly to TeX/LaTeX
1863 - when self.preamblemode is unset, the expr is passed to \ProcessPyXBox
1865 if not self
.texruns
:
1866 for usefile
in self
.usefiles
:
1867 extpos
= usefile
.rfind(".")
1869 os
.rename(usefile
, self
.texfilename
+ usefile
[extpos
:])
1872 texfile
= open("%s.tex" % self
.texfilename
, "w") # start with filename -> creates dvi file with that name
1873 texfile
.write("\\relax\n")
1876 self
.texinput
, self
.texoutput
= os
.popen4("%s %s" % (self
.mode
, self
.texfilename
), "t", 0)
1878 # XXX: workaround for MS Windows (bufsize = 0 makes trouble!?)
1879 self
.texinput
, self
.texoutput
= os
.popen4("%s %s" % (self
.mode
, self
.texfilename
), "t")
1880 atexit
.register(_cleantmp
, self
)
1881 self
.expectqueue
= Queue
.Queue(1) # allow for a single entry only -> keeps the next InputMarker to be wait for
1882 self
.gotevent
= threading
.Event() # keeps the got inputmarker event
1883 self
.gotqueue
= Queue
.Queue(0) # allow arbitrary number of entries
1884 self
.quitevent
= threading
.Event() # keeps for end of terminal event
1885 self
.readoutput
= _readpipe(self
.texoutput
, self
.expectqueue
, self
.gotevent
, self
.gotqueue
, self
.quitevent
)
1887 oldpreamblemode
= self
.preamblemode
1888 self
.preamblemode
= 1
1889 self
.execute("\\scrollmode\n\\raiseerror%\n" # switch to and check scrollmode
1890 "\\def\\PyX{P\\kern-.3em\\lower.5ex\hbox{Y}\kern-.18em X}%\n" # just the PyX Logo
1891 "\\gdef\\PyXHAlign{0}%\n" # global PyXHAlign (0.0-1.0) for the horizontal alignment, default to 0
1892 "\\newbox\\PyXBox%\n" # PyXBox will contain the output
1893 "\\newbox\\PyXBoxHAligned%\n" # PyXBox will contain the horizontal aligned output
1894 "\\newdimen\\PyXDimenHAlignLT%\n" # PyXDimenHAlignLT/RT will contain the left/right extent
1895 "\\newdimen\\PyXDimenHAlignRT%\n" +
1896 _texsettingpreamble
+ # insert preambles for texsetting macros
1897 "\\long\\def\\ProcessPyXBox#1#2{%\n" # the ProcessPyXBox definition (#1 is expr, #2 is page number)
1898 "\\setbox\\PyXBox=\\hbox{{#1}}%\n" # push expression into PyXBox
1899 "\\PyXDimenHAlignLT=\\PyXHAlign\\wd\\PyXBox%\n" # calculate the left/right extent
1900 "\\PyXDimenHAlignRT=\\wd\\PyXBox%\n"
1901 "\\advance\\PyXDimenHAlignRT by -\\PyXDimenHAlignLT%\n"
1902 "\\gdef\\PyXHAlign{0}%\n" # reset the PyXHAlign to the default 0
1903 "\\immediate\\write16{PyXBox:page=#2," # write page and extents of this box to stdout
1904 "lt=\\the\\PyXDimenHAlignLT,"
1905 "rt=\\the\\PyXDimenHAlignRT,"
1906 "ht=\\the\\ht\\PyXBox,"
1907 "dp=\\the\\dp\\PyXBox:}%\n"
1908 "\\setbox\\PyXBoxHAligned=\\hbox{\\kern-\\PyXDimenHAlignLT\\box\\PyXBox}%\n" # align horizontally
1909 "\\ht\\PyXBoxHAligned0pt%\n" # baseline alignment (hight to zero)
1910 "{\\count0=80\\count1=121\\count2=88\\count3=#2\\shipout\\box\\PyXBoxHAligned}}%\n" # shipout PyXBox to Page 80.121.88.<page number>
1911 "\\def\\PyXInput#1{\\immediate\\write16{PyXInputMarker:executeid=#1:}}", # write PyXInputMarker to stdout
1912 *self
.texmessagestart
)
1913 os
.remove("%s.tex" % self
.texfilename
)
1914 if self
.mode
== "tex":
1916 LocalLfsName
= str(self
.lfs
) + ".lfs"
1917 lfsdef
= open(LocalLfsName
, "r").read()
1921 SysLfsName
= os
.path
.join(sys
.prefix
, "share", "pyx", str(self
.lfs
) + ".lfs")
1922 lfsdef
= open(SysLfsName
, "r").read()
1924 SysLfsName
= os
.path
.join(os
.path
.dirname(__file__
), "lfs", str(self
.lfs
) + ".lfs")
1925 lfsdef
= open(SysLfsName
, "r").read()
1929 allfiles
+= os
.listdir(".")
1933 allfiles
+= os
.listdir(os
.path
.join(sys
.prefix
, "share", "pyx"))
1937 allfiles
+= os
.listdir(os
.path
.join(os
.path
.dirname(__file__
), "lfs"))
1940 files
= map(lambda x
: x
[:-4], filter(lambda x
: x
[-4:] == ".lfs", allfiles
))
1941 raise IOError("file '%s.lfs' not found. Available latex font sizes:\n%s" % (self
.lfs
, files
))
1942 self
.execute(lfsdef
)
1943 self
.execute("\\normalsize%\n")
1944 self
.execute("\\newdimen\\linewidth%\n")
1945 elif self
.mode
== "latex":
1946 if self
.pyxgraphics
:
1947 self
.execute("\\makeatletter%\n"
1948 "\\let\\saveProcessOptions=\\ProcessOptions%\n"
1949 "\\def\\ProcessOptions{%\n"
1950 "\\saveProcessOptions%\n"
1951 "\\def\\Gin@driver{../../contrib/pyx.def}%\n"
1952 "\\def\\c@lor@namefile{dvipsnam.def}}%\n"
1954 if self
.docopt
is not None:
1955 self
.execute("\\documentclass[%s]{%s}" % (self
.docopt
, self
.docclass
), *self
.texmessagedocclass
)
1957 self
.execute("\\documentclass{%s}" % self
.docclass
, *self
.texmessagedocclass
)
1958 self
.preamblemode
= oldpreamblemode
1960 if expr
is not None: # TeX/LaTeX should process expr
1961 self
.expectqueue
.put_nowait("PyXInputMarker:executeid=%i:" % self
.executeid
)
1962 if self
.preamblemode
:
1963 self
.expr
= ("%s%%\n" % expr
+
1964 "\\PyXInput{%i}%%\n" % self
.executeid
)
1967 self
.expr
= ("\\ProcessPyXBox{%s%%\n}{%i}%%\n" % (expr
, self
.page
) +
1968 "\\PyXInput{%i}%%\n" % self
.executeid
)
1969 else: # TeX/LaTeX should be finished
1970 self
.expectqueue
.put_nowait("Transcript written on %s.log" % self
.texfilename
)
1971 if self
.mode
== "latex":
1972 self
.expr
= "\\end{document}\n"
1974 self
.expr
= "\\end\n"
1976 print "pass the following expression to TeX/LaTeX:\n %s" % self
.expr
.replace("\n", "\n ").rstrip()
1977 self
.texinput
.write(self
.expr
)
1978 self
.gotevent
.wait(self
.waitfortex
) # wait for the expected output
1979 gotevent
= self
.gotevent
.isSet()
1980 self
.gotevent
.clear()
1981 if expr
is None and gotevent
: # TeX/LaTeX should have finished
1984 self
.texinput
.close() # close the input queue and
1985 self
.quitevent
.wait(self
.waitfortex
) # wait for finish of the output
1986 gotevent
= self
.quitevent
.isSet()
1988 self
.texmessage
= ""
1990 self
.texmessage
+= self
.gotqueue
.get_nowait()
1993 self
.texmessageparsed
= self
.texmessage
1995 if expr
is not None:
1996 texmessage
.inputmarker
.check(self
)
1997 if not self
.preamblemode
:
1998 texmessage
.pyxbox
.check(self
)
1999 texmessage
.pyxpageout
.check(self
)
2000 for check
in checks
:
2003 except TexResultWarning
:
2004 traceback
.print_exc()
2005 texmessage
.emptylines
.check(self
)
2006 if len(self
.texmessageparsed
):
2007 raise TexResultError("unhandled TeX response (might be an error)", self
)
2009 raise TexResultError("TeX didn't respond as expected within the timeout period (%i seconds)." % self
.waitfortex
, self
)
2012 "finish TeX/LaTeX and read the dvifile"
2013 self
.execute(None, *self
.texmessageend
)
2015 os
.system("dvicopy %s.dvi %s.dvicopy" % (self
.texfilename
, self
.texfilename
))
2016 dvifilename
= "%s.dvicopy" % self
.texfilename
2018 dvifilename
= "%s.dvi" % self
.texfilename
2019 self
.dvifiles
.append(DVIFile(dvifilename
, debug
=self
.dvidebug
))
2022 def prolog(self
, dvinumber
, page
):
2023 "return the dvifile prolog"
2024 if not self
.texdone
:
2026 return self
.dvifiles
[dvinumber
].prolog(page
)
2028 def write(self
, file, dvinumber
, page
):
2029 "write a page from the dvifile"
2030 if not self
.texdone
:
2032 return self
.dvifiles
[dvinumber
].write(file, page
)
2035 "resets the tex runner to its initial state (upto its record to old dvi file(s))"
2037 if not self
.texdone
:
2039 self
.preamblemode
= 1
2044 def settex(self
, mode
=None,
2052 texmessagestart
=None,
2053 texmessagedocclass
=None,
2054 texmessagebegindoc
=None,
2056 texmessagedefaultpreamble
=None,
2057 texmessagedefaultrun
=None):
2058 """provide a set command for TeX/LaTeX settings
2059 - TeX/LaTeX must not yet been started
2060 - especially needed for the defaultrunner, where no access to
2061 the constructor is available
2062 - do not call this method directly; better use the set method below"""
2065 if mode
is not None:
2067 if mode
!= "tex" and mode
!= "latex":
2068 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
2072 if docclass
is not None:
2073 self
.docclass
= docclass
2074 if docopt
is not None:
2075 self
.docopt
= docopt
2076 if self
.usefiles
is not None:
2077 self
.usefiles
= helper
.ensurelist(usefiles
)
2078 if waitfortex
is not None:
2079 self
.waitfortex
= waitfortex
2080 if dvicopy
is not None:
2081 self
.dvicopy
= dvicopy
2082 if dvicopy
is not None:
2083 self
.pyxgraphics
= pyxgraphics
2084 if texmessagestart
is not None:
2085 texmessagestart
= helper
.ensuresequence(texmessagestart
)
2086 helper
.checkattr(texmessagestart
, allowmulti
=(texmessage
,))
2087 self
.texmessagestart
= texmessagestart
2088 if texmessagedocclass
is not None:
2089 texmessagedocclass
= helper
.ensuresequence(texmessagedocclass
)
2090 helper
.checkattr(texmessagedocclass
, allowmulti
=(texmessage
,))
2091 self
.texmessagedocclass
= texmessagedocclass
2092 if texmessagebegindoc
is not None:
2093 texmessagebegindoc
= helper
.ensuresequence(texmessagebegindoc
)
2094 helper
.checkattr(texmessagebegindoc
, allowmulti
=(texmessage
,))
2095 self
.texmessagebegindoc
= texmessagebegindoc
2096 if texmessageend
is not None:
2097 texmessageend
= helper
.ensuresequence(texmessageend
)
2098 helper
.checkattr(texmessageend
, allowmulti
=(texmessage
,))
2099 self
.texmessageend
= texmessageend
2100 if texmessagedefaultpreamble
is not None:
2101 texmessagedefaultpreamble
= helper
.ensuresequence(texmessagedefaultpreamble
)
2102 helper
.checkattr(texmessagedefaultpreamble
, allowmulti
=(texmessage
,))
2103 self
.texmessagedefaultpreamble
= texmessagedefaultpreamble
2104 if texmessagedefaultrun
is not None:
2105 texmessagedefaultrun
= helper
.ensuresequence(texmessagedefaultrun
)
2106 helper
.checkattr(texmessagedefaultrun
, allowmulti
=(texmessage
,))
2107 self
.texmessagedefaultrun
= texmessagedefaultrun
2109 def set(self
, texdebug
=None, dvidebug
=None, errordebug
=None, **args
):
2110 """as above, but contains all settings
2111 - the debug level might be changed during TeX/LaTeX execution
2112 - dvidebug gets used only once, namely when TeX/LaTeX is being finished"""
2115 if texdebug
is not None:
2116 self
.texdebug
= texdebug
2117 if dvidebug
is not None:
2118 self
.dvidebug
= dvidebug
2119 if errordebug
is not None:
2120 self
.errordebug
= errordebug
2121 if len(args
.keys()):
2124 def bracketcheck(self
, expr
):
2125 """a helper method for consistant usage of "{" and "}"
2126 - Michael Schindler claims that this is not necessary"""
2129 # def bracketcheck(self, expr):
2130 # """a helper method for consistant usage of "{" and "}"
2131 # - prevent to pass unbalanced expressions to TeX
2132 # - raises an appropriate ValueError"""
2136 # if c == "{" and not esc:
2138 # if c == "}" and not esc:
2141 # raise ValueError("unmatched '}'")
2143 # esc = (esc + 1) % 2
2147 # raise ValueError("unmatched '{'")
2149 def preamble(self
, expr
, *args
):
2150 r
"""put something into the TeX/LaTeX preamble
2151 - in LaTeX, this is done before the \begin{document}
2152 (you might use \AtBeginDocument, when you're in need for)
2153 - it is not allowed to call preamble after calling the
2154 text method for the first time (for LaTeX this is needed
2155 due to \begin{document}; in TeX it is forced for compatibility
2156 (you should be able to switch from TeX to LaTeX, if you want,
2157 without breaking something
2158 - preamble expressions must not create any dvi output
2159 - args might contain texmessage instances
2160 - a bracketcheck is performed on the expression"""
2163 if not self
.preamblemode
:
2164 raise TexNotInPreambleModeError
2165 self
.bracketcheck(expr
)
2166 helper
.checkattr(args
, allowmulti
=(texmessage
,))
2167 self
.execute(expr
, *helper
.getattrs(args
, texmessage
, default
=self
.texmessagedefaultpreamble
))
2169 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:")
2171 def _text(self
, x
, y
, expr
, *args
):
2172 """create text by passing expr to TeX/LaTeX
2173 - returns a textbox containing the result from running expr thru TeX/LaTeX
2174 - the box center is set to x, y
2175 - *args may contain style parameters, namely:
2176 - an halign instance
2177 - _texsetting instances
2178 - texmessage instances
2179 - trafo._trafo instances
2180 - base.PathStyle instances
2181 - a bracketcheck is performed on the expression"""
2183 raise ValueError("None expression is invalid")
2186 if self
.preamblemode
:
2187 if self
.mode
== "latex":
2188 self
.execute("\\begin{document}", *self
.texmessagebegindoc
)
2189 self
.preamblemode
= 0
2190 helper
.checkattr(args
, allowmulti
=(_texsetting
, texmessage
, trafo
._trafo
, base
.PathStyle
))
2191 #XXX: should we distiguish between StrokeStyle and FillStyle?
2192 texsettings
= helper
.getattrs(args
, _texsetting
, default
=[])
2194 for texsetting
in texsettings
:
2195 if texsetting
.exclusive
:
2196 if texsetting
.id not in exclusive
:
2197 exclusive
.append(texsetting
.id)
2199 raise RuntimeError("multiple occurance of exclusive texsetting with id=%i" % texsetting
.id)
2201 for texsetting
in texsettings
:
2202 expr
= texsetting
.modifyexpr(expr
, texsettings
, self
)
2203 self
.bracketcheck(expr
)
2204 self
.execute(expr
, *helper
.getattrs(args
, texmessage
, default
=self
.texmessagedefaultrun
))
2205 match
= self
.PyXBoxPattern
.search(self
.texmessage
)
2206 if not match
or int(match
.group("page")) != self
.page
:
2207 raise TexResultError("box extents not found", self
)
2208 left
, right
, height
, depth
= map(lambda x
: float(x
) * 72.0 / 72.27, match
.group("lt", "rt", "ht", "dp"))
2209 box
= _textbox(x
, y
, left
, right
, height
, depth
, self
, self
.dvinumber
, self
.page
,
2210 *helper
.getattrs(args
, base
.PathStyle
, default
=[]))
2211 for t
in helper
.getattrs(args
, trafo
._trafo
, default
=()):
2215 def text(self
, x
, y
, expr
, *args
):
2216 return self
._text
(unit
.topt(x
), unit
.topt(y
), expr
, *args
)
2219 # the module provides an default texrunner and methods for direct access
2220 defaulttexrunner
= texrunner()
2221 reset
= defaulttexrunner
.reset
2222 set = defaulttexrunner
.set
2223 preamble
= defaulttexrunner
.preamble
2224 text
= defaulttexrunner
.text
2225 _text
= defaulttexrunner
._text