painter off (to prohibit TeX running)
[PyX/mjg.git] / pyx / tex.py
blobff02d2198e63ff5ac6803f93f3d767791a48920b
1 #!/usr/bin/env python
4 # Copyright (C) 2002 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2002 André Wobst <wobsta@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 """
25 (La)TeX interface of PyX
27 This module provides the classes tex and latex, which can be inserted into a
28 PyX canvas. The method (la)tex.text prints text, while (la)tex.textwd,
29 (la)tex.textht, and (la)tex.textdp appraise the width, height, and depth of a
30 text, respectively. The method (la)tex.define can be used to define macros in
31 (La)TeX.
32 """
34 import os, string, tempfile, sys, md5, traceback, time, StringIO, re, atexit
35 import base, unit, epsfile, color, attrlist
38 ################################################################################
39 # TeX attributes
40 ################################################################################
42 class _texattr:
44 """base class for all TeX attributes"""
46 pass
49 class fontsize(_texattr):
51 """fontsize TeX attribute"""
53 def __init__(self, value):
54 self.value = value
56 def __str__(self):
57 return self.value
60 fontsize.tiny = fontsize("tiny")
61 fontsize.scriptsize = fontsize("scriptsize")
62 fontsize.footnotesize = fontsize("footnotesize")
63 fontsize.small = fontsize("small")
64 fontsize.normalsize = fontsize("normalsize")
65 fontsize.large = fontsize("large")
66 fontsize.Large = fontsize("Large")
67 fontsize.LARGE = fontsize("LARGE")
68 fontsize.huge = fontsize("huge")
69 fontsize.Huge = fontsize("Huge")
72 class halign(_texattr):
74 """tex horizontal align attribute"""
76 def __init__(self, value):
77 self.value = value
79 def __cmp__(self, other):
80 if other is None: return 1
81 return cmp(self.value, other.value)
83 __rcmp__ = __cmp__
86 halign.left = halign("left")
87 halign.center = halign("center")
88 halign.right = halign("right")
91 class valign(_texattr):
93 """abstract tex vertical align attribute"""
95 def __init__(self, hsize):
96 self.hsize = hsize
99 class _valignvtop(valign):
101 """tex top vertical align attribute"""
103 pass
106 valign.top = _valignvtop
109 class _valignvbox(valign):
111 """tex bottom vertical align attribute"""
113 pass
116 valign.bottom = _valignvbox
119 class direction(_texattr):
121 """tex output direction attribute"""
123 def __init__(self, value):
124 self.value = value
126 def __str__(self):
127 return "%.5f" % self.value
131 direction.horizontal = direction(0)
132 direction.vertical = direction(90)
133 direction.upsidedown = direction(180)
134 direction.rvertical = direction(270)
137 class style(_texattr):
139 """tex style modification attribute"""
141 def __init__(self, praefix, suffix):
142 self.praefix = praefix
143 self.suffix = suffix
145 def ModifyCmd(self, cmd):
146 return self.praefix + cmd + self.suffix
149 style.text = style("", "")
150 style.math = style("$\displaystyle{}", "$")
153 ################################################################################
154 # TeX message handlers
155 ################################################################################
157 class msghandler(_texattr):
159 """abstract base class for tex message handlers
161 A message handler has to provide a parsemsg method. It gets a string and
162 returns a string. Within the parsemsg method the handler may remove any
163 part of the message it is familiar with."""
165 def removeemptylines(self, msg):
166 """any message parser may use this method to remove empty lines"""
168 msg = re.sub("^(\n)*", "", msg)
169 msg = re.sub("(\n){3,}", "\n\n", msg)
170 msg = re.sub("(\n)+$", "\n", msg)
171 return msg
174 class _msghandlershowall(msghandler):
176 """a message handler, which shows all messages"""
178 def parsemsg(self, msg):
179 return msg
182 msghandler.showall = _msghandlershowall()
184 class _msghandlerhideload(msghandler):
186 """a message handler, which hides all messages inside proper '(filename' and ')'
187 the string filename has to be a readable file"""
189 def parsemsg(self, msg):
190 depth = 0
191 newstr = ""
192 newlevel = 0
193 for c in msg:
194 if newlevel and (c in (list(string.whitespace) + ["(", ")"])):
195 if filestr not in ("c", "C"):
196 if not len(filestr):
197 break
198 if not os.access(filestr,os.R_OK):
199 break
200 newlevel = 0
201 if c == "(":
202 depth += 1
203 filestr = ""
204 newlevel = 1
205 elif c == ")":
206 depth -= 1
207 if depth < 0:
208 break
209 elif depth == 0:
210 newstr += c
211 else:
212 filestr += c
213 else:
214 # replace msg only if loop was completed and no ")" is missing
215 if depth == 0:
216 msg = self.removeemptylines(newstr)
217 return msg
220 msghandler.hideload = _msghandlerhideload()
223 class _msghandlerhidegraphicsload(msghandler):
225 """a message handler, which hides all messages like '<filename>'
226 the string filename has to be a readable file"""
228 def parsemsg(self, msg):
229 depth = 0
230 newstr = ""
231 for c in msg:
232 if c == "<":
233 depth += 1
234 if depth > 1:
235 break
236 filestr = ""
237 elif c == ">":
238 depth -= 1
239 if depth < 0:
240 break
241 if not os.access(filestr,os.R_OK):
242 newstr += "<" + filestr + ">"
243 elif depth == 0:
244 newstr += c
245 else:
246 filestr += c
247 else:
248 # replace msg only if loop was completed and no ">" missing
249 if depth == 0:
250 msg = self.removeemptylines(newstr)
251 return msg
254 msghandler.hidegraphicsload = _msghandlerhidegraphicsload()
257 class _msghandlerhidefontwarning(msghandler):
259 """a message handler, which hides LaTeX font warnings, e.g.
260 Messages starting with 'LaTeX Font Warning: ' which might be
261 continued on following lines by '(Font) '"""
263 def parsemsg(self, msg):
264 msglines = string.split(msg, "\n")
265 newmsglines = []
266 fontwarning = 0
267 for line in msglines:
268 if fontwarning and line[:20] != "(Font) ":
269 fontwarning = 0
270 if not fontwarning and line[:20] == "LaTeX Font Warning: ":
271 fontwarning = 1
272 if not fontwarning:
273 newmsglines.append(line)
274 newmsg = reduce(lambda x, y: x + y + "\n", newmsglines, "")
275 return self.removeemptylines(newmsg)
278 msghandler.hidefontwarning = _msghandlerhidefontwarning()
281 class _msghandlerhidebuterror(msghandler):
283 """a message handler, hides all messages whenever they do
284 not contain a line starting with '! '"""
286 def parsemsg(self, msg):
287 # the "\n" + msg instead of msg itself is needed, if the message starts with "! "
288 if string.find("\n" + msg, "\n! ") != -1:
289 return msg
290 else:
291 return ""
294 msghandler.hidebuterror = _msghandlerhidebuterror()
297 class _msghandlerhideall(msghandler):
299 """a message handler, which hides all messages"""
301 def parsemsg(self, msg):
302 return ""
305 msghandler.hideall = _msghandlerhideall()
308 ################################################################################
309 # extent handlers
310 ################################################################################
312 class missextents(_texattr):
314 """abstract base class for handling missing extents
316 A miss extent class has to provide a misshandler method."""
319 _missextentsreturnzero_report = 0
320 def _missextentsreturnzero_printreport():
321 sys.stderr.write("""
322 pyx.tex: Some requested extents were missing and have been replaced by zero.
323 Please run the file again to get correct extents.\n""")
325 class _missextentsreturnzero(missextents):
327 def misshandler(self, texinstance):
328 global _missextentsreturnzero_report
329 if not _missextentsreturnzero_report:
330 atexit.register(_missextentsreturnzero_printreport)
331 _missextentsreturnzero_report = 1
332 return map(lambda x: unit.t_pt(0), texinstance.BoxCmds[0].CmdExtents)
335 missextents.returnzero = _missextentsreturnzero()
338 class _missextentsreturnzeroquiet(missextents):
340 def misshandler(self, texinstance):
341 return map(lambda x: unit.t_pt(0), texinstance.BoxCmds[0].CmdExtents)
344 missextents.returnzeroquiet = _missextentsreturnzeroquiet()
347 class _missextentsraiseerror(missextents):
349 def misshandler(self, texinstance):
350 raise TexMissExtentError
353 missextents.raiseerror = _missextentsraiseerror()
356 class _missextentscreateextents(missextents):
358 def misshandler(self, texinstance):
359 if isinstance(texinstance, latex):
360 storeauxfilename = texinstance.auxfilename
361 texinstance.auxfilename = None
362 texinstance.DoneRunTex = 0
363 texinstance._run()
364 texinstance.DoneRunTex = 0
365 if isinstance(texinstance, latex):
366 texinstance.auxfilename = storeauxfilename
367 return texinstance.BoxCmds[0].Extents(texinstance.BoxCmds[0].CmdExtents,
368 missextents.returnzero, texinstance)
371 missextents.createextents = _missextentscreateextents()
374 class _missextentscreateallextents(missextents):
376 def misshandler(self, texinstance):
377 if isinstance(texinstance, latex):
378 storeauxfilename = texinstance.auxfilename
379 texinstance.auxfilename = None
380 texinstance.DoneRunTex = 0
381 storeextents = texinstance.BoxCmds[0].CmdExtents[0]
382 texinstance.BoxCmds[0].CmdExtents = [_extent.wd, _extent.ht, _extent.dp]
383 texinstance._run()
384 texinstance.BoxCmds[0].CmdExtents[0] = storeextents
385 texinstance.DoneRunTex = 0
386 if isinstance(texinstance, latex):
387 texinstance.auxfilename = storeauxfilename
388 return texinstance.BoxCmds[0].Extents(texinstance.BoxCmds[0].CmdExtents,
389 missextents.returnzero, texinstance)
392 missextents.createallextents = _missextentscreateallextents()
395 ################################################################################
396 # TeX exceptions
397 ################################################################################
399 class TexExcept(base.PyXExcept):
401 pass
404 class TexLeftParenthesisError(TexExcept):
406 def __str__(self):
407 return "no matching parenthesis for '{' found"
410 class TexRightParenthesisError(TexExcept):
412 def __str__(self):
413 return "no matching parenthesis for '}' found"
416 class TexHalignError(TexExcept):
418 def __str__(self):
419 return "unkown halign"
422 class TexValignError(TexExcept):
424 def __str__(self):
425 return "unkown valign"
428 class TexDefAfterBoxError(TexExcept):
430 def __str__(self):
431 return "definition commands not allowed after output commands"
434 class TexMissExtentError(TexExcept):
436 def __str__(self):
437 return "requested tex extent not available"
440 ################################################################################
441 # modules internal stuff
442 ################################################################################
444 class _extent:
446 def __init__(self, value):
447 self.value = value
449 def __str__(self):
450 return self.value
453 _extent.wd = _extent("wd")
454 _extent.ht = _extent("ht")
455 _extent.dp = _extent("dp")
458 class _TexCmd:
460 """class for all user supplied commands"""
462 PyxMarker = "PyxMarker"
463 BeginPyxMarker = "Begin" + PyxMarker
464 EndPyxMarker = "End" + PyxMarker
466 def __init__(self, Marker, Stack, msghandlers):
467 self.Marker = Marker
468 self.Stack = Stack
469 self.msghandlers = msghandlers
471 def TexParenthesisCheck(self, Cmd):
472 """check for proper usage of "{" and "}" in Cmd"""
474 depth = 0
475 esc = 0
476 for c in Cmd:
477 if c == "{" and not esc:
478 depth = depth + 1
479 if c == "}" and not esc:
480 depth = depth - 1
481 if depth < 0:
482 raise TexRightParenthesisError
483 if c == "\\":
484 esc = (esc + 1) % 2
485 else:
486 esc = 0
487 if depth > 0:
488 raise TexLeftParenthesisError
490 def BeginMarkerStr(self):
491 return "%s[%s]" % (self.BeginPyxMarker, self.Marker, )
493 def WriteBeginMarker(self, file):
494 file.write("\\immediate\\write16{%s}%%\n" % self.BeginMarkerStr())
496 def EndMarkerStr(self):
497 return "%s[%s]" % (self.EndPyxMarker, self.Marker, )
499 def WriteEndMarker(self, file):
500 file.write("\\immediate\\write16{%s}%%\n" % self.EndMarkerStr())
502 def WriteError(self, msg):
503 sys.stderr.write("Traceback (innermost last):\n")
504 traceback.print_list(self.Stack)
505 sys.stderr.write("(La)TeX Message:\n" + msg + "\n")
507 def CheckMarkerError(self, file):
508 """read markers and identify the message"""
510 line = file.readline()
511 while (line != "") and (line[:-1] != self.BeginMarkerStr()):
512 line = file.readline()
513 msg = ""
514 line = file.readline()
515 while (line != "") and (line[:-1] != self.EndMarkerStr()):
516 msg = msg + line
517 line = file.readline()
518 if line == "":
519 self.WriteError(msg)
520 raise IOError
521 else:
522 # check if message can be ignored
523 doprint = 0
524 parsedmsg = msg
525 for msghandler in self.msghandlers:
526 parsedmsg = msghandler.parsemsg(parsedmsg)
527 for c in parsedmsg:
528 if c not in string.whitespace:
529 self.WriteError(parsedmsg)
530 break
533 class _DefCmd(_TexCmd):
535 """definition commands"""
537 def __init__(self, DefCmd, Marker, Stack, msghandlers):
538 _TexCmd.__init__(self, Marker, Stack, msghandlers)
539 self.TexParenthesisCheck(DefCmd)
540 self.DefCmd = "%s%%\n" % DefCmd
542 def write(self, file):
543 self.WriteBeginMarker(file)
544 file.write(self.DefCmd)
545 self.WriteEndMarker(file)
548 class _CmdPut:
550 """print parameters for a BoxCmd (data structure)"""
552 def __init__(self, x, y, halign, direction, color):
553 self.x = x
554 self.y = y
555 self.halign = halign
556 self.direction = direction
557 self.color = color
560 class _BoxCmd(_TexCmd):
562 """BoxCmd (for printing text and getting extents)"""
564 def __init__(self, DefCmdsStr, BoxCmd, style, fontsize, valign, Marker, Stack, msghandlers):
565 _TexCmd.__init__(self, Marker, Stack, msghandlers)
566 self.TexParenthesisCheck(BoxCmd)
567 self.DefCmdsStr = DefCmdsStr
568 self.BoxCmd = "{%s}%%\n" % BoxCmd # add another "{" to ensure, that everything goes into the Box
569 self.CmdPuts = [] # list, where to put the command
570 self.CmdExtents = [] # list, which extents are requested
572 self.BoxCmd = style.ModifyCmd(self.BoxCmd)
573 if valign is not None:
574 if isinstance(valign, _valignvtop):
575 self.BoxCmd = "\\linewidth%.5ftruept\\vtop{\\hsize\\linewidth{%s}}" % \
576 (unit.topt(valign.hsize) * 72.27/72, self.BoxCmd, )
577 elif isinstance(valign, _valignvbox):
578 self.BoxCmd = "\\linewidth%.5ftruept\\vbox{\\hsize\\linewidth{%s}}" % \
579 (unit.topt(valign.hsize) * 72.27/72, self.BoxCmd, )
580 else:
581 raise TexValignError
582 self.BoxCmd = "\\setbox\\localbox=\\hbox{\\%s%s}%%\n" % (fontsize, self.BoxCmd, )
584 def __cmp__(self, other):
585 if other is None: return 1
586 return cmp(self.BoxCmd, other.BoxCmd)
588 __rcmp__ = __cmp__
590 def write(self, file):
591 self.WriteBeginMarker(file)
592 file.write(self.BoxCmd)
593 self.WriteEndMarker(file)
594 for CmdExtent in self.CmdExtents:
595 file.write("\\immediate\\write\\sizefile{%s:%s:%s:\\the\\%s\\localbox}%%\n" %
596 (self.MD5(), CmdExtent, time.time(), CmdExtent, ))
597 for CmdPut in self.CmdPuts:
599 file.write("{\\vbox to0pt{\\kern%.5ftruept\\hbox{\\kern%.5ftruept\\ht\\localbox0pt" %
600 (-CmdPut.y, CmdPut.x))
602 if CmdPut.direction != direction.horizontal:
603 file.write("\\special{ps: gsave currentpoint currentpoint translate " +
604 str(CmdPut.direction) + " neg rotate neg exch neg exch translate }")
605 if CmdPut.color != color.gray.black:
606 file.write("\\special{ps: ")
607 CmdPut.color.write(file)
608 file.write(" }")
609 if CmdPut.halign == halign.left:
610 pass
611 elif CmdPut.halign == halign.center:
612 file.write("\kern-.5\wd\localbox")
613 elif CmdPut.halign == halign.right:
614 file.write("\kern-\wd\localbox")
615 else:
616 raise TexHalignError
617 file.write("\\copy\\localbox")
619 if CmdPut.color != color.gray.black:
620 file.write("\\special{ps: ")
621 color.gray.black.write(file)
622 file.write(" }")
623 if CmdPut.direction != direction.horizontal:
624 file.write("\\special{ps: currentpoint grestore moveto }")
625 file.write("}\\vss}\\nointerlineskip}%\n")
627 def MD5(self):
628 """creates an MD5 hex string for texinit + Cmd"""
630 h = string.hexdigits
631 r = ''
632 s = md5.md5(self.DefCmdsStr + self.BoxCmd).digest()
633 for c in s:
634 i = ord(c)
635 r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
636 return r
638 def Put(self, x, y, halign, direction, color):
639 self.CmdPuts.append(_CmdPut(x, y, halign, direction, color))
641 def Extents(self, extents, missextents, texinstance):
642 """get sizes from previous LaTeX run"""
644 for extent in extents:
645 if extent not in self.CmdExtents:
646 self.CmdExtents.append(extent)
648 result = []
649 for extent in extents:
650 s = self.MD5() + ":" + str(extent)
651 for size in texinstance.Sizes:
652 if size[:len(s)] == s:
653 texpt = float(string.rstrip(size.split(":")[3][:-3]))
654 result.append(unit.t_pt(texpt * 72.0 / 72.27))
655 break
656 else:
657 break
658 else:
659 return result
661 # extent was not found --- temporarily remove all other commands in
662 # order to allow the misshandler to access everything it ever wants
663 storeboxcmds = texinstance.BoxCmds
664 storecmdputs = self.CmdPuts
665 storecmdextents = self.CmdExtents
666 texinstance.BoxCmds = [self, ]
667 self.CmdPuts = []
668 self.CmdExtents = extents
669 try:
670 result = missextents.misshandler(texinstance)
671 finally:
672 texinstance.BoxCmds = storeboxcmds
673 self.CmdPuts = storecmdputs
674 self.CmdExtents = storecmdextents
675 return result
678 ################################################################################
679 # tex, latex class
680 ################################################################################
682 class _tex(base.PSCmd, attrlist.attrlist):
684 """major parts are of tex and latex class are shared and implemented here"""
686 def __init__(self, defaultmsghandlers=msghandler.hideload,
687 defaultmissextents=missextents.returnzero,
688 texfilename=None):
689 if isinstance(defaultmsghandlers, msghandler):
690 self.defaultmsghandlers = (defaultmsghandlers,)
691 else:
692 self.defaultmsghandlers = defaultmsghandlers
693 self.defaultmissextents = defaultmissextents
694 self.texfilename = texfilename
695 self.DefCmds = []
696 self.DefCmdsStr = None
697 self.BoxCmds = []
698 self.DoneRunTex = 0
700 if len(os.path.basename(sys.argv[0])):
701 basename = os.path.basename(sys.argv[0])
702 if basename[-3:] == ".py":
703 basename = basename[:-3]
704 self.SizeFileName = os.path.join(os.getcwd(), basename + ".size")
705 else:
706 self.SizeFileName = os.path.join(os.getcwd(), "pyxput.size")
707 try:
708 file = open(self.SizeFileName, "r")
709 self.Sizes = file.readlines()
710 file.close()
711 except IOError:
712 self.Sizes = [ ]
714 def _getstack(self):
715 return traceback.extract_stack(sys._getframe())
717 def _execute(self, command):
718 if os.system(command):
719 sys.stderr.write("The exit code of the following command was non-zero:\n" + command +
720 """\nUsually, additional information causing this trouble appears closeby.
721 However, you may check the origin by keeping all temporary files.
722 In order to achieve this, you have to specify a texfilename in the
723 constructor of the class pyx.(la)tex. You can then try to run the
724 command by yourself.\n""")
726 def _createaddfiles(self, tempname):
727 pass
729 def _removeaddfiles(self, tempname):
730 pass
732 def _executetex(self, tempname):
733 pass
735 def _executedvips(self, tempname):
736 self._execute("dvips -O0in,11in -E -o %(t)s.eps %(t)s.dvi > %(t)s.dvipsout 2> %(t)s.dvipserr" % {"t": tempname})
738 def _run(self):
739 """run (La)TeX & dvips, report errors, fill self.abbox & self.epsdata"""
741 if self.DoneRunTex:
742 return
744 if self.texfilename:
745 mktemp = str(self.texfilename)
746 else:
747 storetempdir = tempfile.tempdir
748 tempfile.tempdir = os.curdir
749 mktemp = tempfile.mktemp()
750 tempfile.tempdir = storetempdir
751 tempname = os.path.basename(mktemp)
753 self._createaddfiles(tempname)
755 texfile = open(tempname + ".tex", "w")
757 texfile.write("\\nonstopmode%\n")
758 texfile.write("\\def\PyX{P\\kern-.3em\\lower.5ex\\hbox{Y}\\kern-.18em X}%\n")
759 texfile.write("\\newwrite\\sizefile%\n\\newbox\\localbox%\n\\newbox\\pagebox%\n")
760 texfile.write("{\\catcode`\\~=12\\immediate\\openout\\sizefile=%s.size\\relax}%%\n" % tempname)
762 for Cmd in self.DefCmds:
763 Cmd.write(texfile)
765 texfile.write("\\setbox\\pagebox=\\vbox{%\n")
767 for Cmd in self.BoxCmds:
768 Cmd.write(texfile)
770 texfile.write("}\n\\immediate\\closeout\\sizefile\n\\shipout\\copy\\pagebox\n")
771 texfile.write(self._endcmd())
772 texfile.close()
774 self._executetex(tempname)
776 try:
777 outfile = open(tempname + ".texout", "r")
778 for Cmd in self.DefCmds + self.BoxCmds:
779 Cmd.CheckMarkerError(outfile)
780 outfile.close()
781 except IOError:
782 sys.stderr.write("""An unexpected error occured while reading the (La)TeX output.
783 May be, you just have no disk space available. Or something badly
784 in your commands caused (La)TeX to give up completely. Or your
785 (La)TeX installation might be broken at all.
786 You may try to check the origin by keeping all temporary files.
787 In order to achieve this, you have to specify a texfilename in the
788 constructor of the class pyx.tex. You can then try to run (La)TeX
789 by yourself.\n""")
791 if not os.access(tempname + ".dvi", 0):
792 sys.stderr.write("""Can't find the dvi file which should be produced by (La)TeX.
793 May be, you just have no disk space available. Or something badly
794 in your commands caused (La)TeX to give up completely. Or your
795 (La)TeX installation might be broken at all.
796 You may try to check the origin by keeping all temporary files.
797 In order to achieve this, you have to specify a texfilename in the
798 constructor of the class pyx.tex. You can then try to run (La)TeX
799 by yourself.\n""")
801 else:
802 self._executedvips(tempname)
803 if not os.access(tempname + ".eps", 0):
804 sys.stderr.write("""Error reading the eps file which should be produced by dvips.
805 May be, you just have no disk space available. Or something badly
806 in your commands caused dvips to give up completely. Or your
807 (La)TeX installation might be broken at all.
808 You may try to check the origin by keeping all temporary files.
809 In order to achieve this, you have to specify a texfilename in the
810 constructor of the class pyx.tex. You can then try to run dvips
811 by yourself.\n""")
812 else:
813 aepsfile = epsfile.epsfile(0, 0, tempname + ".eps", translatebbox=0, clip=0)
814 self.abbox = aepsfile.bbox()
815 self.aprolog = aepsfile.prolog()
816 epsdatafile = StringIO.StringIO()
817 aepsfile.write(epsdatafile)
818 self.epsdata = epsdatafile.getvalue()
820 # merge new sizes
822 OldSizes = self.Sizes
824 try:
825 NewSizeFile = open(tempname + ".size", "r")
826 NewSizes = NewSizeFile.readlines()
827 NewSizeFile.close()
828 except IOError:
829 NewSizes = []
831 if (len(NewSizes) != 0) or (len(OldSizes) != 0):
832 SizeFile = open(self.SizeFileName, "w")
833 SizeFile.writelines(NewSizes)
834 self.Sizes = NewSizes
835 for OldSize in OldSizes:
836 OldSizeSplit = OldSize.split(":")
837 for NewSize in NewSizes:
838 if NewSize.split(":")[0:2] == OldSizeSplit[0:2]:
839 break
840 else:
841 if time.time() < float(OldSizeSplit[2]) + 60*60*24: # we keep size results for one day
842 SizeFile.write(OldSize)
843 self.Sizes.append(OldSize)
845 if not self.texfilename:
846 for suffix in ("tex", "log", "size", "dvi", "eps", "texout", "texerr", "dvipsout", "dvipserr", ):
847 try:
848 os.unlink(tempname + "." + suffix)
849 except:
850 pass
852 self._removeaddfiles(tempname)
853 self.DoneRunTex = 1
855 def prolog(self):
856 self._run()
857 return self.aprolog
859 def bbox(self):
860 self._run()
861 return self.abbox
863 def write(self, file):
864 self._run()
865 file.writelines(self.epsdata)
867 def define(self, Cmd, *attrs):
868 if len(self.BoxCmds):
869 raise TexDefAfterBoxError
870 self.DoneRunTex = 0
871 self.attrcheck(attrs, (), (msghandler,))
872 self.DefCmds.append(_DefCmd(Cmd,
873 len(self.DefCmds)+ len(self.BoxCmds),
874 self._getstack(),
875 self.attrgetall(attrs, msghandler, self.defaultmsghandlers)))
877 def _insertcmd(self, Cmd, *attrs):
878 if not len(self.BoxCmds):
879 self._beginboxcmds()
880 self.DefCmdsStr = reduce(lambda x,y: x + y.DefCmd, self.DefCmds, "")
881 mystyle = self.attrget(attrs, style, style.text)
882 myfontsize = self.attrget(attrs, fontsize, fontsize.normalsize)
883 myvalign = self.attrget(attrs, valign, None)
884 mymsghandlers = self.attrgetall(attrs, msghandler, self.defaultmsghandlers)
885 MyCmd = _BoxCmd(self.DefCmdsStr, Cmd, mystyle, myfontsize, myvalign,
886 len(self.DefCmds) + len(self.BoxCmds), self._getstack(), mymsghandlers)
887 if MyCmd not in self.BoxCmds:
888 self.BoxCmds.append(MyCmd)
889 for Cmd in self.BoxCmds:
890 if Cmd == MyCmd:
891 UseCmd = Cmd # we could use MyCmd directly if we have just inserted it before
892 # (that's due to the side effect, that append doesn't make a copy of the element,
893 # but we ignore this here -- we don't want to depend on this side effect)
894 return UseCmd
896 def _text(self, x, y, Cmd, *attrs):
897 """print Cmd at (x, y) --- position parameters in postscipt points"""
899 self.DoneRunTex = 0
900 self.attrcheck(attrs, (style, fontsize, halign, valign, direction, color.color), (msghandler,))
901 myhalign = self.attrget(attrs, halign, halign.left)
902 mydirection = self.attrget(attrs, direction, direction.horizontal)
903 mycolor = self.attrget(attrs, color.color, color.gray.black)
904 self._insertcmd(Cmd, *attrs).Put(x * 72.27 / 72.0, y * 72.27 / 72.0, myhalign, mydirection, mycolor)
906 def text(self, x, y, Cmd, *attrs):
907 """print Cmd at (x, y)"""
909 self._text(unit.topt(x), unit.topt(y), Cmd, *attrs)
911 def textwd(self, Cmd, *attrs):
912 """get width of Cmd"""
914 self.DoneRunTex = 0
915 self.attrcheck(attrs, (style, fontsize, missextents), (msghandler,))
916 mymissextents = self.attrget(attrs, missextents, self.defaultmissextents)
917 return self._insertcmd(Cmd, *attrs).Extents((_extent.wd, ), mymissextents, self)[0]
919 def textht(self, Cmd, *attrs):
920 """get height of Cmd"""
922 self.DoneRunTex = 0
923 self.attrcheck(attrs, (style, fontsize, valign, missextents), (msghandler,))
924 mymissextents = self.attrget(attrs, missextents, self.defaultmissextents)
925 return self._insertcmd(Cmd, *attrs).Extents((_extent.ht, ), mymissextents, self)[0]
928 def textdp(self, Cmd, *attrs):
929 """get depth of Cmd"""
931 self.DoneRunTex = 0
932 self.attrcheck(attrs, (style, fontsize, valign, missextents), (msghandler,))
933 mymissextents = self.attrget(attrs, missextents, self.defaultmissextents)
934 return self._insertcmd(Cmd, *attrs).Extents((_extent.dp, ), mymissextents, self)[0]
937 class tex(_tex):
939 """tex class adds the specializations to _tex needed for tex"""
941 def __init__(self, lfs="10pt", **addargs):
942 _tex.__init__(self, **addargs)
943 # XXX other ways for creating font sizes?
944 try:
945 LocalLfsName = str(lfs) + ".lfs"
946 lfsdef = open(LocalLfsName, "r").read()
947 except IOError:
948 try:
949 try:
950 SysLfsName = os.path.join(sys.prefix, "share", "pyx", str(lfs) + ".lfs")
951 lfsdef = open(SysLfsName, "r").read()
952 except IOError:
953 SysLfsName = os.path.join(os.path.dirname(__file__), "lfs", str(lfs) + ".lfs")
954 lfsdef = open(SysLfsName, "r").read()
955 except IOError:
956 files = map(lambda x: x[:-4],
957 filter(lambda x: x[-4:] == ".lfs",
958 os.listdir(".") +
959 os.listdir(os.path.join(sys.prefix, "share", "pyx")),
960 os.listdir(os.path.join(os.path.dirname(__file__), "lfs"))))
961 raise IOError("file '%s.lfs' not found. Available latex font sizes:\n%s" % (lfs, files))
962 self.define(lfsdef)
963 self.define("\\newdimen\\linewidth%\n\\hsize0truein%\n\\vsize0truein%\n\\hoffset-1truein%\n\\voffset-1truein")
965 def _beginboxcmds(self):
966 pass
968 def _endcmd(self):
969 return "\\end\n"
971 def _executetex(self, tempname):
972 self._execute("tex %(t)s.tex > %(t)s.texout 2> %(t)s.texerr" % {"t": tempname})
975 class latex(_tex):
977 """latex class adds the specializations to _tex needed for latex"""
979 def __init__(self, docclass="article", docopt=None, auxfilename=None, **addargs):
980 _tex.__init__(self, **addargs)
981 self.auxfilename = auxfilename
982 if docopt:
983 self.define("\\documentclass[" + str(docopt) + "]{" + str(docclass) + "}")
984 else:
985 self.define("\\documentclass{" + str(docclass) + "}")
986 self.define("\\hsize0truein%\n\\vsize0truein%\n\\hoffset-1truein%\n\\voffset-1truein")
988 def _beginboxcmds(self):
989 self.define("\\begin{document}")
991 def _endcmd(self):
992 return "\\end{document}\n"
994 def _createaddfiles(self, tempname):
995 if self.auxfilename is not None:
996 writenew = 0
997 try:
998 os.rename(self.auxfilename + ".aux", tempname + ".aux")
999 except OSError:
1000 writenew = 1
1001 else:
1002 writenew = 1
1003 if writenew:
1004 auxfile = open(tempname + ".aux", "w")
1005 auxfile.write("\\relax\n")
1006 auxfile.close()
1008 def _executetex(self, tempname):
1009 self._execute("latex %(t)s.tex > %(t)s.texout 2> %(t)s.texerr" % {"t": tempname})
1011 def _removeaddfiles(self, tempname):
1012 if self.auxfilename is not None:
1013 os.rename(tempname + ".aux", self.auxfilename + ".aux")
1014 else:
1015 os.unlink(tempname + ".aux")