more axispos work; all test graphs in the functional testsuite are running again
[PyX.git] / pyx / tex.py
blobeeb87b2d097fe67ed01f77f3b75dc6e493f75b4b
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2002 André Wobst <wobsta@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
25 """
26 (La)TeX interface of PyX
28 This module provides the classes tex and latex, which can be inserted into a
29 PyX canvas. The method (la)tex.text prints text, while (la)tex.textwd,
30 (la)tex.textht, and (la)tex.textdp appraise the width, height, and depth of a
31 text, respectively. The method (la)tex.define can be used to define macros in
32 (La)TeX.
33 """
35 import os, string, tempfile, sys, md5, traceback, time, StringIO, re, atexit
36 import base, helper, unit, epsfile, color
39 # Code snippets from former attrlist module (which has been removed from the
40 # CVS tree). We keep them here, until they are finally removed together with
41 # the tex module
43 class AttrlistError(base.PyXExcept):
44 pass
47 class attrlist:
48 def attrcheck(self, attrs, allowonce=(), allowmulti=()):
49 hadonce = []
50 for attr in attrs:
51 for once in allowonce:
52 if isinstance(attr, once):
53 if once in hadonce:
54 raise AttrlistError
55 else:
56 hadonce += [once]
57 break
58 else:
59 for multi in allowmulti:
60 if isinstance(attr, multi):
61 break
62 else:
63 raise AttrlistError
65 def attrgetall(self, attrs, get, default=helper.nodefault):
66 first = 1
67 for attr in attrs:
68 if isinstance(attr, get):
69 if first:
70 result = [attr]
71 first = 0
72 else:
73 result.append(attr)
74 if first:
75 if default is helper.nodefault:
76 raise AttrlistError
77 else:
78 return default
79 return result
81 def attrcount(self, attrs, check):
82 return len(self.attrgetall(attrs, check, ()))
84 def attrget(self, attrs, get, default=helper.nodefault):
85 try:
86 result = self.attrgetall(attrs, get)
87 except AttrlistError:
88 if default is helper.nodefault:
89 raise AttrlistError
90 else:
91 return default
92 if len(result) > 1:
93 raise AttrlistError
94 return result[0]
96 def attrgetfirst(self, attrs, get, default=helper.nodefault):
97 try:
98 result = self.attrgetall(attrs, get)
99 except AttrlistError:
100 if default is helper.nodefault:
101 raise AttrlistError
102 else:
103 return default
104 return result[0]
106 def attrgetlast(self, attrs, get, default=helper.nodefault):
107 try:
108 result = self.attrgetall(attrs, get)
109 except AttrlistError:
110 if default is helper.nodefault:
111 raise AttrlistError
112 else:
113 return default
114 return result[-1]
116 def attrdel(self, attrs, remove):
117 result = []
118 for attr in attrs:
119 if not isinstance(attr, remove):
120 result.append(attr)
121 return result
124 ################################################################################
125 # TeX attributes
126 ################################################################################
128 class _texattr:
130 """base class for all TeX attributes"""
132 pass
135 class fontsize(_texattr):
137 """fontsize TeX attribute"""
139 def __init__(self, value):
140 self.value = value
142 def __str__(self):
143 return self.value
146 fontsize.tiny = fontsize("tiny")
147 fontsize.scriptsize = fontsize("scriptsize")
148 fontsize.footnotesize = fontsize("footnotesize")
149 fontsize.small = fontsize("small")
150 fontsize.normalsize = fontsize("normalsize")
151 fontsize.large = fontsize("large")
152 fontsize.Large = fontsize("Large")
153 fontsize.LARGE = fontsize("LARGE")
154 fontsize.huge = fontsize("huge")
155 fontsize.Huge = fontsize("Huge")
158 class halign(_texattr):
160 """tex horizontal align attribute"""
162 def __init__(self, value):
163 self.value = value
165 def __cmp__(self, other):
166 if other is None: return 1
167 return cmp(self.value, other.value)
169 __rcmp__ = __cmp__
172 halign.left = halign("left")
173 halign.center = halign("center")
174 halign.right = halign("right")
177 class valign(_texattr):
179 """abstract tex vertical align attribute"""
181 def __init__(self, hsize):
182 self.hsize = hsize
185 class _valignvtop(valign):
187 """tex top vertical align attribute"""
189 pass
192 valign.top = _valignvtop
195 class _valignvbox(valign):
197 """tex bottom vertical align attribute"""
199 pass
202 valign.bottom = _valignvbox
205 class direction(_texattr):
207 """tex output direction attribute"""
209 def __init__(self, value):
210 self.value = value
212 def __str__(self):
213 return "%.5f" % self.value
217 direction.horizontal = direction(0)
218 direction.vertical = direction(90)
219 direction.upsidedown = direction(180)
220 direction.rvertical = direction(270)
223 class style(_texattr):
225 """tex style modification attribute"""
227 def __init__(self, praefix, suffix):
228 self.praefix = praefix
229 self.suffix = suffix
231 def ModifyCmd(self, cmd):
232 return self.praefix + cmd + self.suffix
235 style.text = style("", "")
236 style.math = style("$\displaystyle{}", "$")
239 ################################################################################
240 # TeX message handlers
241 ################################################################################
243 class msghandler(_texattr):
245 """abstract base class for tex message handlers
247 A message handler has to provide a parsemsg method. It gets a string and
248 returns a string. Within the parsemsg method the handler may remove any
249 part of the message it is familiar with."""
251 def removeemptylines(self, msg):
252 """any message parser may use this method to remove empty lines"""
254 msg = re.sub("^(\n)*", "", msg)
255 msg = re.sub("(\n){3,}", "\n\n", msg)
256 msg = re.sub("(\n)+$", "\n", msg)
257 return msg
260 class _msghandlershowall(msghandler):
262 """a message handler, which shows all messages"""
264 def parsemsg(self, msg):
265 return msg
268 msghandler.showall = _msghandlershowall()
270 class _msghandlerhideload(msghandler):
272 """a message handler, which hides all messages inside proper '(filename' and ')'
273 the string filename has to be a readable file"""
275 def parsemsg(self, msg):
276 depth = 0
277 newstr = ""
278 newlevel = 0
279 for c in msg:
280 if newlevel and (c in (list(string.whitespace) + ["(", ")"])):
281 if filestr not in ("c", "C"):
282 if not len(filestr):
283 break
284 if not os.access(filestr,os.R_OK):
285 break
286 newlevel = 0
287 if c == "(":
288 depth += 1
289 filestr = ""
290 newlevel = 1
291 elif c == ")":
292 depth -= 1
293 if depth < 0:
294 break
295 elif depth == 0:
296 newstr += c
297 else:
298 filestr += c
299 else:
300 # replace msg only if loop was completed and no ")" is missing
301 if depth == 0:
302 msg = self.removeemptylines(newstr)
303 return msg
306 msghandler.hideload = _msghandlerhideload()
309 class _msghandlerhidegraphicsload(msghandler):
311 """a message handler, which hides all messages like '<filename>'
312 the string filename has to be a readable file"""
314 def parsemsg(self, msg):
315 depth = 0
316 newstr = ""
317 for c in msg:
318 if c == "<":
319 depth += 1
320 if depth > 1:
321 break
322 filestr = ""
323 elif c == ">":
324 depth -= 1
325 if depth < 0:
326 break
327 if not os.access(filestr,os.R_OK):
328 newstr += "<" + filestr + ">"
329 elif depth == 0:
330 newstr += c
331 else:
332 filestr += c
333 else:
334 # replace msg only if loop was completed and no ">" missing
335 if depth == 0:
336 msg = self.removeemptylines(newstr)
337 return msg
340 msghandler.hidegraphicsload = _msghandlerhidegraphicsload()
343 class _msghandlerhidefontwarning(msghandler):
345 """a message handler, which hides LaTeX font warnings, e.g.
346 Messages starting with 'LaTeX Font Warning: ' which might be
347 continued on following lines by '(Font) '"""
349 def parsemsg(self, msg):
350 msglines = string.split(msg, "\n")
351 newmsglines = []
352 fontwarning = 0
353 for line in msglines:
354 if fontwarning and line[:20] != "(Font) ":
355 fontwarning = 0
356 if not fontwarning and line[:20] == "LaTeX Font Warning: ":
357 fontwarning = 1
358 if not fontwarning:
359 newmsglines.append(line)
360 newmsg = reduce(lambda x, y: x + y + "\n", newmsglines, "")
361 return self.removeemptylines(newmsg)
364 msghandler.hidefontwarning = _msghandlerhidefontwarning()
367 class _msghandlerhidebuterror(msghandler):
369 """a message handler, hides all messages whenever they do
370 not contain a line starting with '! '"""
372 def parsemsg(self, msg):
373 # the "\n" + msg instead of msg itself is needed, if the message starts with "! "
374 if string.find("\n" + msg, "\n! ") != -1:
375 return msg
376 else:
377 return ""
380 msghandler.hidebuterror = _msghandlerhidebuterror()
383 class _msghandlerhideall(msghandler):
385 """a message handler, which hides all messages"""
387 def parsemsg(self, msg):
388 return ""
391 msghandler.hideall = _msghandlerhideall()
394 ################################################################################
395 # extent handlers
396 ################################################################################
398 class missextents(_texattr):
400 """abstract base class for handling missing extents
402 A miss extent class has to provide a misshandler method."""
405 _missextentsreturnzero_report = 0
406 def _missextentsreturnzero_printreport():
407 sys.stderr.write("""
408 pyx.tex: Some requested extents were missing and have been replaced by zero.
409 Please run the file again to get correct extents.\n""")
411 class _missextentsreturnzero(missextents):
413 def misshandler(self, texinstance):
414 global _missextentsreturnzero_report
415 if not _missextentsreturnzero_report:
416 atexit.register(_missextentsreturnzero_printreport)
417 _missextentsreturnzero_report = 1
418 return map(lambda x: unit.t_pt(0), texinstance.BoxCmds[0].CmdExtents)
421 missextents.returnzero = _missextentsreturnzero()
424 class _missextentsreturnzeroquiet(missextents):
426 def misshandler(self, texinstance):
427 return map(lambda x: unit.t_pt(0), texinstance.BoxCmds[0].CmdExtents)
430 missextents.returnzeroquiet = _missextentsreturnzeroquiet()
433 class _missextentsraiseerror(missextents):
435 def misshandler(self, texinstance):
436 raise TexMissExtentError
439 missextents.raiseerror = _missextentsraiseerror()
442 class _missextentscreateextents(missextents):
444 def misshandler(self, texinstance):
445 if isinstance(texinstance, latex):
446 storeauxfilename = texinstance.auxfilename
447 texinstance.auxfilename = None
448 texinstance.DoneRunTex = 0
449 texinstance._run()
450 texinstance.DoneRunTex = 0
451 if isinstance(texinstance, latex):
452 texinstance.auxfilename = storeauxfilename
453 return texinstance.BoxCmds[0].Extents(texinstance.BoxCmds[0].CmdExtents,
454 missextents.returnzero, texinstance)
457 missextents.createextents = _missextentscreateextents()
460 class _missextentscreateallextents(missextents):
462 def misshandler(self, texinstance):
463 if isinstance(texinstance, latex):
464 storeauxfilename = texinstance.auxfilename
465 texinstance.auxfilename = None
466 texinstance.DoneRunTex = 0
467 storeextents = texinstance.BoxCmds[0].CmdExtents[0]
468 texinstance.BoxCmds[0].CmdExtents = [_extent.wd, _extent.ht, _extent.dp]
469 texinstance._run()
470 texinstance.BoxCmds[0].CmdExtents[0] = storeextents
471 texinstance.DoneRunTex = 0
472 if isinstance(texinstance, latex):
473 texinstance.auxfilename = storeauxfilename
474 return texinstance.BoxCmds[0].Extents(texinstance.BoxCmds[0].CmdExtents,
475 missextents.returnzero, texinstance)
478 missextents.createallextents = _missextentscreateallextents()
481 ################################################################################
482 # TeX exceptions
483 ################################################################################
485 class TexExcept(base.PyXExcept):
487 pass
490 class TexLeftParenthesisError(TexExcept):
492 def __str__(self):
493 return "no matching parenthesis for '{' found"
496 class TexRightParenthesisError(TexExcept):
498 def __str__(self):
499 return "no matching parenthesis for '}' found"
502 class TexHalignError(TexExcept):
504 def __str__(self):
505 return "unkown halign"
508 class TexValignError(TexExcept):
510 def __str__(self):
511 return "unkown valign"
514 class TexDefAfterBoxError(TexExcept):
516 def __str__(self):
517 return "definition commands not allowed after output commands"
520 class TexMissExtentError(TexExcept):
522 def __str__(self):
523 return "requested tex extent not available"
526 ################################################################################
527 # modules internal stuff
528 ################################################################################
530 class _extent:
532 def __init__(self, value):
533 self.value = value
535 def __str__(self):
536 return self.value
539 _extent.wd = _extent("wd")
540 _extent.ht = _extent("ht")
541 _extent.dp = _extent("dp")
544 class _TexCmd:
546 """class for all user supplied commands"""
548 PyxMarker = "PyxMarker"
549 BeginPyxMarker = "Begin" + PyxMarker
550 EndPyxMarker = "End" + PyxMarker
552 def __init__(self, Marker, Stack, msghandlers):
553 self.Marker = Marker
554 self.Stack = Stack
555 self.msghandlers = msghandlers
557 def TexParenthesisCheck(self, Cmd):
558 """check for proper usage of "{" and "}" in Cmd"""
560 depth = 0
561 esc = 0
562 for c in Cmd:
563 if c == "{" and not esc:
564 depth = depth + 1
565 if c == "}" and not esc:
566 depth = depth - 1
567 if depth < 0:
568 raise TexRightParenthesisError
569 if c == "\\":
570 esc = (esc + 1) % 2
571 else:
572 esc = 0
573 if depth > 0:
574 raise TexLeftParenthesisError
576 def BeginMarkerStr(self):
577 return "%s[%s]" % (self.BeginPyxMarker, self.Marker, )
579 def WriteBeginMarker(self, file):
580 file.write("\\immediate\\write16{%s}%%\n" % self.BeginMarkerStr())
582 def EndMarkerStr(self):
583 return "%s[%s]" % (self.EndPyxMarker, self.Marker, )
585 def WriteEndMarker(self, file):
586 file.write("\\immediate\\write16{%s}%%\n" % self.EndMarkerStr())
588 def WriteError(self, msg):
589 sys.stderr.write("Traceback (innermost last):\n")
590 traceback.print_list(self.Stack)
591 sys.stderr.write("(La)TeX Message:\n" + msg + "\n")
593 def CheckMarkerError(self, file):
594 """read markers and identify the message"""
596 line = file.readline()
597 while (line != "") and (line[:-1] != self.BeginMarkerStr()):
598 line = file.readline()
599 msg = ""
600 line = file.readline()
601 while (line != "") and (line[:-1] != self.EndMarkerStr()):
602 msg = msg + line
603 line = file.readline()
604 if line == "":
605 self.WriteError(msg)
606 raise IOError
607 else:
608 # check if message can be ignored
609 doprint = 0
610 parsedmsg = msg
611 for msghandler in self.msghandlers:
612 parsedmsg = msghandler.parsemsg(parsedmsg)
613 for c in parsedmsg:
614 if c not in string.whitespace:
615 self.WriteError(parsedmsg)
616 break
619 class _DefCmd(_TexCmd):
621 """definition commands"""
623 def __init__(self, DefCmd, Marker, Stack, msghandlers):
624 _TexCmd.__init__(self, Marker, Stack, msghandlers)
625 self.TexParenthesisCheck(DefCmd)
626 self.DefCmd = "%s%%\n" % DefCmd
628 def write(self, file):
629 self.WriteBeginMarker(file)
630 file.write(self.DefCmd)
631 self.WriteEndMarker(file)
634 class _CmdPut:
636 """print parameters for a BoxCmd (data structure)"""
638 def __init__(self, x, y, halign, direction, color):
639 self.x = x
640 self.y = y
641 self.halign = halign
642 self.direction = direction
643 self.color = color
646 class _BoxCmd(_TexCmd):
648 """BoxCmd (for printing text and getting extents)"""
650 def __init__(self, DefCmdsStr, BoxCmd, style, fontsize, valign, Marker, Stack, msghandlers):
651 _TexCmd.__init__(self, Marker, Stack, msghandlers)
652 self.TexParenthesisCheck(BoxCmd)
653 self.DefCmdsStr = DefCmdsStr
654 self.BoxCmd = "{%s}%%\n" % BoxCmd # add another "{" to ensure, that everything goes into the Box
655 self.CmdPuts = [] # list, where to put the command
656 self.CmdExtents = [] # list, which extents are requested
658 self.BoxCmd = style.ModifyCmd(self.BoxCmd)
659 if valign is not None:
660 if isinstance(valign, _valignvtop):
661 self.BoxCmd = "\\linewidth%.5ftruept\\vtop{\\hsize\\linewidth{%s}}" % \
662 (unit.topt(valign.hsize) * 72.27/72, self.BoxCmd, )
663 elif isinstance(valign, _valignvbox):
664 self.BoxCmd = "\\linewidth%.5ftruept\\vbox{\\hsize\\linewidth{%s}}" % \
665 (unit.topt(valign.hsize) * 72.27/72, self.BoxCmd, )
666 else:
667 raise TexValignError
668 self.BoxCmd = "\\setbox\\localbox=\\hbox{\\%s%s}%%\n" % (fontsize, self.BoxCmd, )
670 def __cmp__(self, other):
671 if other is None: return 1
672 return cmp(self.BoxCmd, other.BoxCmd)
674 __rcmp__ = __cmp__
676 def write(self, file):
677 self.WriteBeginMarker(file)
678 file.write(self.BoxCmd)
679 self.WriteEndMarker(file)
680 for CmdExtent in self.CmdExtents:
681 file.write("\\immediate\\write\\sizefile{%s:%s:%s:\\the\\%s\\localbox}%%\n" %
682 (self.MD5(), CmdExtent, time.time(), CmdExtent, ))
683 for CmdPut in self.CmdPuts:
685 file.write("{\\vbox to0pt{\\kern%.5ftruept\\hbox{\\kern%.5ftruept\\ht\\localbox0pt" %
686 (-CmdPut.y, CmdPut.x))
688 if CmdPut.direction != direction.horizontal:
689 file.write("\\special{ps: gsave currentpoint currentpoint translate " +
690 str(CmdPut.direction) + " neg rotate neg exch neg exch translate }")
691 if CmdPut.color != color.gray.black:
692 file.write("\\special{ps: ")
693 CmdPut.color.write(file)
694 file.write(" }")
695 if CmdPut.halign == halign.left:
696 pass
697 elif CmdPut.halign == halign.center:
698 file.write("\kern-.5\wd\localbox")
699 elif CmdPut.halign == halign.right:
700 file.write("\kern-\wd\localbox")
701 else:
702 raise TexHalignError
703 file.write("\\copy\\localbox")
705 if CmdPut.color != color.gray.black:
706 file.write("\\special{ps: ")
707 color.gray.black.write(file)
708 file.write(" }")
709 if CmdPut.direction != direction.horizontal:
710 file.write("\\special{ps: currentpoint grestore moveto }")
711 file.write("}\\vss}\\nointerlineskip}%\n")
713 def MD5(self):
714 """creates an MD5 hex string for texinit + Cmd"""
716 h = string.hexdigits
717 r = ''
718 s = md5.md5(self.DefCmdsStr + self.BoxCmd).digest()
719 for c in s:
720 i = ord(c)
721 r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
722 return r
724 def Put(self, x, y, halign, direction, color):
725 self.CmdPuts.append(_CmdPut(x, y, halign, direction, color))
727 def Extents(self, extents, missextents, texinstance):
728 """get sizes from previous LaTeX run"""
730 for extent in extents:
731 if extent not in self.CmdExtents:
732 self.CmdExtents.append(extent)
734 result = []
735 for extent in extents:
736 s = self.MD5() + ":" + str(extent)
737 for size in texinstance.Sizes:
738 if size[:len(s)] == s:
739 texpt = float(string.rstrip(size.split(":")[3][:-3]))
740 result.append(unit.t_pt(texpt * 72.0 / 72.27))
741 break
742 else:
743 break
744 else:
745 return result
747 # extent was not found --- temporarily remove all other commands in
748 # order to allow the misshandler to access everything it ever wants
749 storeboxcmds = texinstance.BoxCmds
750 storecmdputs = self.CmdPuts
751 storecmdextents = self.CmdExtents
752 texinstance.BoxCmds = [self, ]
753 self.CmdPuts = []
754 self.CmdExtents = extents
755 try:
756 result = missextents.misshandler(texinstance)
757 finally:
758 texinstance.BoxCmds = storeboxcmds
759 self.CmdPuts = storecmdputs
760 self.CmdExtents = storecmdextents
761 return result
764 ################################################################################
765 # tex, latex class
766 ################################################################################
768 class _tex(base.PSCmd, attrlist):
770 """major parts are of tex and latex class are shared and implemented here"""
772 def __init__(self, defaultmsghandlers=msghandler.hideload,
773 defaultmissextents=missextents.returnzero,
774 texfilename=None):
775 if isinstance(defaultmsghandlers, msghandler):
776 self.defaultmsghandlers = (defaultmsghandlers,)
777 else:
778 self.defaultmsghandlers = defaultmsghandlers
779 self.defaultmissextents = defaultmissextents
780 self.texfilename = texfilename
781 self.DefCmds = []
782 self.DefCmdsStr = None
783 self.BoxCmds = []
784 self.DoneRunTex = 0
786 if len(os.path.basename(sys.argv[0])):
787 basename = os.path.basename(sys.argv[0])
788 if basename[-3:] == ".py":
789 basename = basename[:-3]
790 self.SizeFileName = os.path.join(os.getcwd(), basename + ".size")
791 else:
792 self.SizeFileName = os.path.join(os.getcwd(), "pyxput.size")
793 try:
794 file = open(self.SizeFileName, "r")
795 self.Sizes = file.readlines()
796 file.close()
797 except IOError:
798 self.Sizes = [ ]
800 def _getstack(self):
801 return traceback.extract_stack(sys._getframe())
803 def _execute(self, command):
804 if os.system(command):
805 sys.stderr.write("The exit code of the following command was non-zero:\n" + command +
806 """\nUsually, additional information causing this trouble appears closeby.
807 However, you may check the origin by keeping all temporary files.
808 In order to achieve this, you have to specify a texfilename in the
809 constructor of the class pyx.(la)tex. You can then try to run the
810 command by yourself.\n""")
812 def _createaddfiles(self, tempname):
813 pass
815 def _removeaddfiles(self, tempname):
816 pass
818 def _executetex(self, tempname):
819 pass
821 def _executedvips(self, tempname):
822 self._execute("dvips -O0in,11in -E -o %(t)s.eps %(t)s.dvi > %(t)s.dvipsout 2> %(t)s.dvipserr" % {"t": tempname})
824 def _run(self):
825 """run (La)TeX & dvips, report errors, fill self.abbox & self.epsdata"""
827 if self.DoneRunTex:
828 return
830 if self.texfilename:
831 mktemp = str(self.texfilename)
832 else:
833 storetempdir = tempfile.tempdir
834 tempfile.tempdir = os.curdir
835 mktemp = tempfile.mktemp()
836 tempfile.tempdir = storetempdir
837 tempname = os.path.basename(mktemp)
839 self._createaddfiles(tempname)
841 texfile = open(tempname + ".tex", "w")
843 texfile.write("\\nonstopmode%\n")
844 texfile.write("\\def\PyX{P\\kern-.3em\\lower.5ex\\hbox{Y}\\kern-.18em X}%\n")
845 texfile.write("\\newwrite\\sizefile%\n\\newbox\\localbox%\n\\newbox\\pagebox%\n")
846 texfile.write("{\\catcode`\\~=12\\immediate\\openout\\sizefile=%s.size\\relax}%%\n" % tempname)
848 for Cmd in self.DefCmds:
849 Cmd.write(texfile)
851 texfile.write("\\setbox\\pagebox=\\vbox{%\n")
853 for Cmd in self.BoxCmds:
854 Cmd.write(texfile)
856 texfile.write("}\n\\immediate\\closeout\\sizefile\n\\shipout\\copy\\pagebox\n")
857 texfile.write(self._endcmd())
858 texfile.close()
860 self._executetex(tempname)
862 try:
863 outfile = open(tempname + ".texout", "r")
864 for Cmd in self.DefCmds + self.BoxCmds:
865 Cmd.CheckMarkerError(outfile)
866 outfile.close()
867 except IOError:
868 sys.stderr.write("""An unexpected error occured while reading the (La)TeX output.
869 May be, you just have no disk space available. Or something badly
870 in your commands caused (La)TeX to give up completely. Or your
871 (La)TeX installation might be broken at all.
872 You may try to check the origin by keeping all temporary files.
873 In order to achieve this, you have to specify a texfilename in the
874 constructor of the class pyx.tex. You can then try to run (La)TeX
875 by yourself.\n""")
877 if not os.access(tempname + ".dvi", 0):
878 sys.stderr.write("""Can't find the dvi file which should be produced by (La)TeX.
879 May be, you just have no disk space available. Or something badly
880 in your commands caused (La)TeX to give up completely. Or your
881 (La)TeX installation might be broken at all.
882 You may try to check the origin by keeping all temporary files.
883 In order to achieve this, you have to specify a texfilename in the
884 constructor of the class pyx.tex. You can then try to run (La)TeX
885 by yourself.\n""")
887 else:
888 self._executedvips(tempname)
889 if not os.access(tempname + ".eps", 0):
890 sys.stderr.write("""Error reading the eps file which should be produced by dvips.
891 May be, you just have no disk space available. Or something badly
892 in your commands caused dvips to give up completely. Or your
893 (La)TeX installation might be broken at all.
894 You may try to check the origin by keeping all temporary files.
895 In order to achieve this, you have to specify a texfilename in the
896 constructor of the class pyx.tex. You can then try to run dvips
897 by yourself.\n""")
898 else:
899 aepsfile = epsfile.epsfile(0, 0, tempname + ".eps", translatebbox=0, clip=0)
900 self.abbox = aepsfile.bbox()
901 self.aprolog = aepsfile.prolog()
902 epsdatafile = StringIO.StringIO()
903 aepsfile.write(epsdatafile)
904 self.epsdata = epsdatafile.getvalue()
906 # merge new sizes
908 OldSizes = self.Sizes
910 try:
911 NewSizeFile = open(tempname + ".size", "r")
912 NewSizes = NewSizeFile.readlines()
913 NewSizeFile.close()
914 except IOError:
915 NewSizes = []
917 if (len(NewSizes) != 0) or (len(OldSizes) != 0):
918 SizeFile = open(self.SizeFileName, "w")
919 SizeFile.writelines(NewSizes)
920 self.Sizes = NewSizes
921 for OldSize in OldSizes:
922 OldSizeSplit = OldSize.split(":")
923 for NewSize in NewSizes:
924 if NewSize.split(":")[0:2] == OldSizeSplit[0:2]:
925 break
926 else:
927 if time.time() < float(OldSizeSplit[2]) + 60*60*24: # we keep size results for one day
928 SizeFile.write(OldSize)
929 self.Sizes.append(OldSize)
931 if not self.texfilename:
932 for suffix in ("tex", "log", "size", "dvi", "eps", "texout", "texerr", "dvipsout", "dvipserr", ):
933 try:
934 os.unlink(tempname + "." + suffix)
935 except:
936 pass
938 self._removeaddfiles(tempname)
939 self.DoneRunTex = 1
941 def prolog(self):
942 self._run()
943 return self.aprolog
945 def bbox(self):
946 self._run()
947 return self.abbox
949 def write(self, file):
950 self._run()
951 file.writelines(self.epsdata)
953 def define(self, Cmd, *attrs):
954 if len(self.BoxCmds):
955 raise TexDefAfterBoxError
956 self.DoneRunTex = 0
957 self.attrcheck(attrs, (), (msghandler,))
958 self.DefCmds.append(_DefCmd(Cmd,
959 len(self.DefCmds)+ len(self.BoxCmds),
960 self._getstack(),
961 self.attrgetall(attrs, msghandler, self.defaultmsghandlers)))
963 def _insertcmd(self, Cmd, *attrs):
964 if not len(self.BoxCmds):
965 self._beginboxcmds()
966 self.DefCmdsStr = reduce(lambda x,y: x + y.DefCmd, self.DefCmds, "")
967 mystyle = self.attrget(attrs, style, style.text)
968 myfontsize = self.attrget(attrs, fontsize, fontsize.normalsize)
969 myvalign = self.attrget(attrs, valign, None)
970 mymsghandlers = self.attrgetall(attrs, msghandler, self.defaultmsghandlers)
971 MyCmd = _BoxCmd(self.DefCmdsStr, Cmd, mystyle, myfontsize, myvalign,
972 len(self.DefCmds) + len(self.BoxCmds), self._getstack(), mymsghandlers)
973 if MyCmd not in self.BoxCmds:
974 self.BoxCmds.append(MyCmd)
975 for Cmd in self.BoxCmds:
976 if Cmd == MyCmd:
977 UseCmd = Cmd # we could use MyCmd directly if we have just inserted it before
978 # (that's due to the side effect, that append doesn't make a copy of the element,
979 # but we ignore this here -- we don't want to depend on this side effect)
980 return UseCmd
982 def _text(self, x, y, Cmd, *attrs):
983 """print Cmd at (x, y) --- position parameters in postscipt points"""
985 self.DoneRunTex = 0
986 self.attrcheck(attrs, (style, fontsize, halign, valign, direction, color.color), (msghandler,))
987 myhalign = self.attrget(attrs, halign, halign.left)
988 mydirection = self.attrget(attrs, direction, direction.horizontal)
989 mycolor = self.attrget(attrs, color.color, color.gray.black)
990 self._insertcmd(Cmd, *attrs).Put(x * 72.27 / 72.0, y * 72.27 / 72.0, myhalign, mydirection, mycolor)
992 def text(self, x, y, Cmd, *attrs):
993 """print Cmd at (x, y)"""
995 self._text(unit.topt(x), unit.topt(y), Cmd, *attrs)
997 def textwd(self, Cmd, *attrs):
998 """get width of Cmd"""
1000 self.DoneRunTex = 0
1001 self.attrcheck(attrs, (style, fontsize, missextents), (msghandler,))
1002 mymissextents = self.attrget(attrs, missextents, self.defaultmissextents)
1003 return self._insertcmd(Cmd, *attrs).Extents((_extent.wd, ), mymissextents, self)[0]
1005 def textht(self, Cmd, *attrs):
1006 """get height of Cmd"""
1008 self.DoneRunTex = 0
1009 self.attrcheck(attrs, (style, fontsize, valign, missextents), (msghandler,))
1010 mymissextents = self.attrget(attrs, missextents, self.defaultmissextents)
1011 return self._insertcmd(Cmd, *attrs).Extents((_extent.ht, ), mymissextents, self)[0]
1014 def textdp(self, Cmd, *attrs):
1015 """get depth of Cmd"""
1017 self.DoneRunTex = 0
1018 self.attrcheck(attrs, (style, fontsize, valign, missextents), (msghandler,))
1019 mymissextents = self.attrget(attrs, missextents, self.defaultmissextents)
1020 return self._insertcmd(Cmd, *attrs).Extents((_extent.dp, ), mymissextents, self)[0]
1023 class tex(_tex):
1025 """tex class adds the specializations to _tex needed for tex"""
1027 def __init__(self, lfs="10pt", **addargs):
1028 _tex.__init__(self, **addargs)
1029 # XXX other ways for creating font sizes?
1030 try:
1031 LocalLfsName = str(lfs) + ".lfs"
1032 lfsdef = open(LocalLfsName, "r").read()
1033 except IOError:
1034 try:
1035 try:
1036 SysLfsName = os.path.join(sys.prefix, "share", "pyx", str(lfs) + ".lfs")
1037 lfsdef = open(SysLfsName, "r").read()
1038 except IOError:
1039 SysLfsName = os.path.join(os.path.dirname(__file__), "lfs", str(lfs) + ".lfs")
1040 lfsdef = open(SysLfsName, "r").read()
1041 except IOError:
1042 files = map(lambda x: x[:-4],
1043 filter(lambda x: x[-4:] == ".lfs",
1044 os.listdir(".") +
1045 os.listdir(os.path.join(sys.prefix, "share", "pyx")),
1046 os.listdir(os.path.join(os.path.dirname(__file__), "lfs"))))
1047 raise IOError("file '%s.lfs' not found. Available latex font sizes:\n%s" % (lfs, files))
1048 self.define(lfsdef)
1049 self.define("\\newdimen\\linewidth%\n\\hsize0truein%\n\\vsize0truein%\n\\hoffset-1truein%\n\\voffset-1truein")
1051 def _beginboxcmds(self):
1052 pass
1054 def _endcmd(self):
1055 return "\\end\n"
1057 def _executetex(self, tempname):
1058 self._execute("tex %(t)s.tex > %(t)s.texout 2> %(t)s.texerr" % {"t": tempname})
1061 class latex(_tex):
1063 """latex class adds the specializations to _tex needed for latex"""
1065 def __init__(self, docclass="article", docopt=None, auxfilename=None, **addargs):
1066 _tex.__init__(self, **addargs)
1067 self.auxfilename = auxfilename
1068 if docopt:
1069 self.define("\\documentclass[" + str(docopt) + "]{" + str(docclass) + "}")
1070 else:
1071 self.define("\\documentclass{" + str(docclass) + "}")
1072 self.define("\\hsize0truein%\n\\vsize0truein%\n\\hoffset-1truein%\n\\voffset-1truein")
1074 def _beginboxcmds(self):
1075 self.define("\\begin{document}")
1077 def _endcmd(self):
1078 return "\\end{document}\n"
1080 def _createaddfiles(self, tempname):
1081 if self.auxfilename is not None:
1082 writenew = 0
1083 try:
1084 os.rename(self.auxfilename + ".aux", tempname + ".aux")
1085 except OSError:
1086 writenew = 1
1087 else:
1088 writenew = 1
1089 if writenew:
1090 auxfile = open(tempname + ".aux", "w")
1091 auxfile.write("\\relax\n")
1092 auxfile.close()
1094 def _executetex(self, tempname):
1095 self._execute("latex %(t)s.tex > %(t)s.texout 2> %(t)s.texerr" % {"t": tempname})
1097 def _removeaddfiles(self, tempname):
1098 if self.auxfilename is not None:
1099 os.rename(tempname + ".aux", self.auxfilename + ".aux")
1100 else:
1101 os.unlink(tempname + ".aux")