postpone reading of fontmap files until TeX/LaTeX is started
[PyX/mjg.git] / pyx / tex.py
blob36851ee6a4d3b4a8e144002cb17c04ec2f4d636d
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2002-2004 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
38 sys.stderr.write("*** PyX Warning: the tex module is obsolete, consider the text module instead\n")
40 # Code snippets from former attrlist module (which has been removed from the
41 # CVS tree). We keep them here, until they are finally removed together with
42 # the tex module
44 class AttrlistError(base.PyXExcept):
45 pass
48 class attrlist:
49 def attrcheck(self, attrs, allowonce=(), allowmulti=()):
50 hadonce = []
51 for attr in attrs:
52 for once in allowonce:
53 if isinstance(attr, once):
54 if once in hadonce:
55 raise AttrlistError
56 else:
57 hadonce += [once]
58 break
59 else:
60 for multi in allowmulti:
61 if isinstance(attr, multi):
62 break
63 else:
64 raise AttrlistError
66 def attrgetall(self, attrs, get, default=helper.nodefault):
67 first = 1
68 for attr in attrs:
69 if isinstance(attr, get):
70 if first:
71 result = [attr]
72 first = 0
73 else:
74 result.append(attr)
75 if first:
76 if default is helper.nodefault:
77 raise AttrlistError
78 else:
79 return default
80 return result
82 def attrcount(self, attrs, check):
83 return len(self.attrgetall(attrs, check, ()))
85 def attrget(self, attrs, get, default=helper.nodefault):
86 try:
87 result = self.attrgetall(attrs, get)
88 except AttrlistError:
89 if default is helper.nodefault:
90 raise AttrlistError
91 else:
92 return default
93 if len(result) > 1:
94 raise AttrlistError
95 return result[0]
97 def attrgetfirst(self, attrs, get, default=helper.nodefault):
98 try:
99 result = self.attrgetall(attrs, get)
100 except AttrlistError:
101 if default is helper.nodefault:
102 raise AttrlistError
103 else:
104 return default
105 return result[0]
107 def attrgetlast(self, attrs, get, default=helper.nodefault):
108 try:
109 result = self.attrgetall(attrs, get)
110 except AttrlistError:
111 if default is helper.nodefault:
112 raise AttrlistError
113 else:
114 return default
115 return result[-1]
117 def attrdel(self, attrs, remove):
118 result = []
119 for attr in attrs:
120 if not isinstance(attr, remove):
121 result.append(attr)
122 return result
125 ################################################################################
126 # TeX attributes
127 ################################################################################
129 class _texattr:
131 """base class for all TeX attributes"""
133 pass
136 class fontsize(_texattr):
138 """fontsize TeX attribute"""
140 def __init__(self, value):
141 self.value = value
143 def __str__(self):
144 return self.value
147 fontsize.tiny = fontsize("tiny")
148 fontsize.scriptsize = fontsize("scriptsize")
149 fontsize.footnotesize = fontsize("footnotesize")
150 fontsize.small = fontsize("small")
151 fontsize.normalsize = fontsize("normalsize")
152 fontsize.large = fontsize("large")
153 fontsize.Large = fontsize("Large")
154 fontsize.LARGE = fontsize("LARGE")
155 fontsize.huge = fontsize("huge")
156 fontsize.Huge = fontsize("Huge")
159 class halign(_texattr):
161 """tex horizontal align attribute"""
163 def __init__(self, value):
164 self.value = value
166 def __cmp__(self, other):
167 if other is None: return 1
168 return cmp(self.value, other.value)
170 __rcmp__ = __cmp__
173 halign.left = halign("left")
174 halign.center = halign("center")
175 halign.right = halign("right")
178 class valign(_texattr):
180 """abstract tex vertical align attribute"""
182 def __init__(self, hsize):
183 self.hsize = hsize
186 class _valignvtop(valign):
188 """tex top vertical align attribute"""
190 pass
193 valign.top = _valignvtop
196 class _valignvbox(valign):
198 """tex bottom vertical align attribute"""
200 pass
203 valign.bottom = _valignvbox
206 class direction(_texattr):
208 """tex output direction attribute"""
210 def __init__(self, value):
211 self.value = value
213 def __str__(self):
214 return "%.5f" % self.value
218 direction.horizontal = direction(0)
219 direction.vertical = direction(90)
220 direction.upsidedown = direction(180)
221 direction.rvertical = direction(270)
224 class style(_texattr):
226 """tex style modification attribute"""
228 def __init__(self, praefix, suffix):
229 self.praefix = praefix
230 self.suffix = suffix
232 def ModifyCmd(self, cmd):
233 return self.praefix + cmd + self.suffix
236 style.text = style("", "")
237 style.math = style("$\displaystyle{}", "$")
240 ################################################################################
241 # TeX message handlers
242 ################################################################################
244 class msghandler(_texattr):
246 """abstract base class for tex message handlers
248 A message handler has to provide a parsemsg method. It gets a string and
249 returns a string. Within the parsemsg method the handler may remove any
250 part of the message it is familiar with."""
252 def removeemptylines(self, msg):
253 """any message parser may use this method to remove empty lines"""
255 msg = re.sub("^(\n)*", "", msg)
256 msg = re.sub("(\n){3,}", "\n\n", msg)
257 msg = re.sub("(\n)+$", "\n", msg)
258 return msg
261 class _msghandlershowall(msghandler):
263 """a message handler, which shows all messages"""
265 def parsemsg(self, msg):
266 return msg
269 msghandler.showall = _msghandlershowall()
271 class _msghandlerhideload(msghandler):
273 """a message handler, which hides all messages inside proper '(filename' and ')'
274 the string filename has to be a readable file"""
276 def parsemsg(self, msg):
277 depth = 0
278 newstr = ""
279 newlevel = 0
280 for c in msg:
281 if newlevel and (c in (list(string.whitespace) + ["(", ")"])):
282 if filestr not in ("c", "C"):
283 if not len(filestr):
284 break
285 if not os.access(filestr,os.R_OK):
286 break
287 newlevel = 0
288 if c == "(":
289 depth += 1
290 filestr = ""
291 newlevel = 1
292 elif c == ")":
293 depth -= 1
294 if depth < 0:
295 break
296 elif depth == 0:
297 newstr += c
298 else:
299 filestr += c
300 else:
301 # replace msg only if loop was completed and no ")" is missing
302 if depth == 0:
303 msg = self.removeemptylines(newstr)
304 return msg
307 msghandler.hideload = _msghandlerhideload()
310 class _msghandlerhidegraphicsload(msghandler):
312 """a message handler, which hides all messages like '<filename>'
313 the string filename has to be a readable file"""
315 def parsemsg(self, msg):
316 depth = 0
317 newstr = ""
318 for c in msg:
319 if c == "<":
320 depth += 1
321 if depth > 1:
322 break
323 filestr = ""
324 elif c == ">":
325 depth -= 1
326 if depth < 0:
327 break
328 if not os.access(filestr,os.R_OK):
329 newstr += "<" + filestr + ">"
330 elif depth == 0:
331 newstr += c
332 else:
333 filestr += c
334 else:
335 # replace msg only if loop was completed and no ">" missing
336 if depth == 0:
337 msg = self.removeemptylines(newstr)
338 return msg
341 msghandler.hidegraphicsload = _msghandlerhidegraphicsload()
344 class _msghandlerhidefontwarning(msghandler):
346 """a message handler, which hides LaTeX font warnings, e.g.
347 Messages starting with 'LaTeX Font Warning: ' which might be
348 continued on following lines by '(Font) '"""
350 def parsemsg(self, msg):
351 msglines = string.split(msg, "\n")
352 newmsglines = []
353 fontwarning = 0
354 for line in msglines:
355 if fontwarning and line[:20] != "(Font) ":
356 fontwarning = 0
357 if not fontwarning and line[:20] == "LaTeX Font Warning: ":
358 fontwarning = 1
359 if not fontwarning:
360 newmsglines.append(line)
361 newmsg = reduce(lambda x, y: x + y + "\n", newmsglines, "")
362 return self.removeemptylines(newmsg)
365 msghandler.hidefontwarning = _msghandlerhidefontwarning()
368 class _msghandlerhidebuterror(msghandler):
370 """a message handler, hides all messages whenever they do
371 not contain a line starting with '! '"""
373 def parsemsg(self, msg):
374 # the "\n" + msg instead of msg itself is needed, if the message starts with "! "
375 if string.find("\n" + msg, "\n! ") != -1:
376 return msg
377 else:
378 return ""
381 msghandler.hidebuterror = _msghandlerhidebuterror()
384 class _msghandlerhideall(msghandler):
386 """a message handler, which hides all messages"""
388 def parsemsg(self, msg):
389 return ""
392 msghandler.hideall = _msghandlerhideall()
395 ################################################################################
396 # extent handlers
397 ################################################################################
399 class missextents(_texattr):
401 """abstract base class for handling missing extents
403 A miss extent class has to provide a misshandler method."""
406 _missextentsreturnzero_report = 0
407 def _missextentsreturnzero_printreport():
408 sys.stderr.write("""
409 pyx.tex: Some requested extents were missing and have been replaced by zero.
410 Please run the file again to get correct extents.\n""")
412 class _missextentsreturnzero(missextents):
414 def misshandler(self, texinstance):
415 global _missextentsreturnzero_report
416 if not _missextentsreturnzero_report:
417 atexit.register(_missextentsreturnzero_printreport)
418 _missextentsreturnzero_report = 1
419 return map(lambda x: 0 * unit.t_pt, texinstance.BoxCmds[0].CmdExtents)
422 missextents.returnzero = _missextentsreturnzero()
425 class _missextentsreturnzeroquiet(missextents):
427 def misshandler(self, texinstance):
428 return map(lambda x: 0 * unit.t_pt, texinstance.BoxCmds[0].CmdExtents)
431 missextents.returnzeroquiet = _missextentsreturnzeroquiet()
434 class _missextentsraiseerror(missextents):
436 def misshandler(self, texinstance):
437 raise TexMissExtentError
440 missextents.raiseerror = _missextentsraiseerror()
443 class _missextentscreateextents(missextents):
445 def misshandler(self, texinstance):
446 if isinstance(texinstance, latex):
447 storeauxfilename = texinstance.auxfilename
448 texinstance.auxfilename = None
449 texinstance.DoneRunTex = 0
450 texinstance._run()
451 texinstance.DoneRunTex = 0
452 if isinstance(texinstance, latex):
453 texinstance.auxfilename = storeauxfilename
454 return texinstance.BoxCmds[0].Extents(texinstance.BoxCmds[0].CmdExtents,
455 missextents.returnzero, texinstance)
458 missextents.createextents = _missextentscreateextents()
461 class _missextentscreateallextents(missextents):
463 def misshandler(self, texinstance):
464 if isinstance(texinstance, latex):
465 storeauxfilename = texinstance.auxfilename
466 texinstance.auxfilename = None
467 texinstance.DoneRunTex = 0
468 storeextents = texinstance.BoxCmds[0].CmdExtents[0]
469 texinstance.BoxCmds[0].CmdExtents = [_extent.wd, _extent.ht, _extent.dp]
470 texinstance._run()
471 texinstance.BoxCmds[0].CmdExtents[0] = storeextents
472 texinstance.DoneRunTex = 0
473 if isinstance(texinstance, latex):
474 texinstance.auxfilename = storeauxfilename
475 return texinstance.BoxCmds[0].Extents(texinstance.BoxCmds[0].CmdExtents,
476 missextents.returnzero, texinstance)
479 missextents.createallextents = _missextentscreateallextents()
482 ################################################################################
483 # TeX exceptions
484 ################################################################################
486 class TexExcept(base.PyXExcept):
488 pass
491 class TexLeftParenthesisError(TexExcept):
493 def __str__(self):
494 return "no matching parenthesis for '{' found"
497 class TexRightParenthesisError(TexExcept):
499 def __str__(self):
500 return "no matching parenthesis for '}' found"
503 class TexHalignError(TexExcept):
505 def __str__(self):
506 return "unkown halign"
509 class TexValignError(TexExcept):
511 def __str__(self):
512 return "unkown valign"
515 class TexDefAfterBoxError(TexExcept):
517 def __str__(self):
518 return "definition commands not allowed after output commands"
521 class TexMissExtentError(TexExcept):
523 def __str__(self):
524 return "requested tex extent not available"
527 ################################################################################
528 # modules internal stuff
529 ################################################################################
531 class _extent:
533 def __init__(self, value):
534 self.value = value
536 def __str__(self):
537 return self.value
540 _extent.wd = _extent("wd")
541 _extent.ht = _extent("ht")
542 _extent.dp = _extent("dp")
545 class _TexCmd:
547 """class for all user supplied commands"""
549 PyxMarker = "PyxMarker"
550 BeginPyxMarker = "Begin" + PyxMarker
551 EndPyxMarker = "End" + PyxMarker
553 def __init__(self, Marker, Stack, msghandlers):
554 self.Marker = Marker
555 self.Stack = Stack
556 self.msghandlers = msghandlers
558 def TexParenthesisCheck(self, Cmd):
559 """check for proper usage of "{" and "}" in Cmd"""
561 depth = 0
562 esc = 0
563 for c in Cmd:
564 if c == "{" and not esc:
565 depth = depth + 1
566 if c == "}" and not esc:
567 depth = depth - 1
568 if depth < 0:
569 raise TexRightParenthesisError
570 if c == "\\":
571 esc = (esc + 1) % 2
572 else:
573 esc = 0
574 if depth > 0:
575 raise TexLeftParenthesisError
577 def BeginMarkerStr(self):
578 return "%s[%s]" % (self.BeginPyxMarker, self.Marker, )
580 def WriteBeginMarker(self, file):
581 file.write("\\immediate\\write16{%s}%%\n" % self.BeginMarkerStr())
583 def EndMarkerStr(self):
584 return "%s[%s]" % (self.EndPyxMarker, self.Marker, )
586 def WriteEndMarker(self, file):
587 file.write("\\immediate\\write16{%s}%%\n" % self.EndMarkerStr())
589 def WriteError(self, msg):
590 sys.stderr.write("Traceback (innermost last):\n")
591 traceback.print_list(self.Stack)
592 sys.stderr.write("(La)TeX Message:\n" + msg + "\n")
594 def CheckMarkerError(self, file):
595 """read markers and identify the message"""
597 line = file.readline()
598 while (line != "") and (line[:-1] != self.BeginMarkerStr()):
599 line = file.readline()
600 msg = ""
601 line = file.readline()
602 while (line != "") and (line[:-1] != self.EndMarkerStr()):
603 msg = msg + line
604 line = file.readline()
605 if line == "":
606 self.WriteError(msg)
607 raise IOError
608 else:
609 # check if message can be ignored
610 doprint = 0
611 parsedmsg = msg
612 for msghandler in self.msghandlers:
613 parsedmsg = msghandler.parsemsg(parsedmsg)
614 for c in parsedmsg:
615 if c not in string.whitespace:
616 self.WriteError(parsedmsg)
617 break
620 class _DefCmd(_TexCmd):
622 """definition commands"""
624 def __init__(self, DefCmd, Marker, Stack, msghandlers):
625 _TexCmd.__init__(self, Marker, Stack, msghandlers)
626 self.TexParenthesisCheck(DefCmd)
627 self.DefCmd = "%s%%\n" % DefCmd
629 def write(self, file):
630 self.WriteBeginMarker(file)
631 file.write(self.DefCmd)
632 self.WriteEndMarker(file)
635 class _CmdPut:
637 """print parameters for a BoxCmd (data structure)"""
639 def __init__(self, x, y, halign, direction, color):
640 self.x = x
641 self.y = y
642 self.halign = halign
643 self.direction = direction
644 self.color = color
647 class _BoxCmd(_TexCmd):
649 """BoxCmd (for printing text and getting extents)"""
651 def __init__(self, DefCmdsStr, BoxCmd, style, fontsize, valign, Marker, Stack, msghandlers):
652 _TexCmd.__init__(self, Marker, Stack, msghandlers)
653 self.TexParenthesisCheck(BoxCmd)
654 self.DefCmdsStr = DefCmdsStr
655 self.BoxCmd = "{%s}%%\n" % BoxCmd # add another "{" to ensure, that everything goes into the Box
656 self.CmdPuts = [] # list, where to put the command
657 self.CmdExtents = [] # list, which extents are requested
659 self.BoxCmd = style.ModifyCmd(self.BoxCmd)
660 if valign is not None:
661 if isinstance(valign, _valignvtop):
662 self.BoxCmd = "\\linewidth%.5ftruept\\vtop{\\hsize\\linewidth{%s}}" % \
663 (unit.topt(valign.hsize) * 72.27/72, self.BoxCmd, )
664 elif isinstance(valign, _valignvbox):
665 self.BoxCmd = "\\linewidth%.5ftruept\\vbox{\\hsize\\linewidth{%s}}" % \
666 (unit.topt(valign.hsize) * 72.27/72, self.BoxCmd, )
667 else:
668 raise TexValignError
669 self.BoxCmd = "\\setbox\\localbox=\\hbox{\\%s%s}%%\n" % (fontsize, self.BoxCmd, )
671 def __cmp__(self, other):
672 if other is None: return 1
673 return cmp(self.BoxCmd, other.BoxCmd)
675 __rcmp__ = __cmp__
677 def write(self, file):
678 self.WriteBeginMarker(file)
679 file.write(self.BoxCmd)
680 self.WriteEndMarker(file)
681 for CmdExtent in self.CmdExtents:
682 file.write("\\immediate\\write\\sizefile{%s:%s:%s:\\the\\%s\\localbox}%%\n" %
683 (self.MD5(), CmdExtent, time.time(), CmdExtent, ))
684 for CmdPut in self.CmdPuts:
686 file.write("{\\vbox to0pt{\\kern%.5ftruept\\hbox{\\kern%.5ftruept\\ht\\localbox0pt" %
687 (-CmdPut.y, CmdPut.x))
689 if CmdPut.direction != direction.horizontal:
690 file.write("\\special{ps: gsave currentpoint currentpoint translate " +
691 str(CmdPut.direction) + " neg rotate neg exch neg exch translate }")
692 if CmdPut.color != color.gray.black:
693 file.write("\\special{ps: ")
694 CmdPut.color.outputPS(file)
695 file.write(" }")
696 if CmdPut.halign == halign.left:
697 pass
698 elif CmdPut.halign == halign.center:
699 file.write("\kern-.5\wd\localbox")
700 elif CmdPut.halign == halign.right:
701 file.write("\kern-\wd\localbox")
702 else:
703 raise TexHalignError
704 file.write("\\copy\\localbox")
706 if CmdPut.color != color.gray.black:
707 file.write("\\special{ps: ")
708 color.gray.black.outputPS(file)
709 file.write(" }")
710 if CmdPut.direction != direction.horizontal:
711 file.write("\\special{ps: currentpoint grestore moveto }")
712 file.write("}\\vss}\\nointerlineskip}%\n")
714 def MD5(self):
715 """creates an MD5 hex string for texinit + Cmd"""
717 h = string.hexdigits
718 r = ''
719 s = md5.md5(self.DefCmdsStr + self.BoxCmd).digest()
720 for c in s:
721 i = ord(c)
722 r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
723 return r
725 def Put(self, x, y, halign, direction, color):
726 self.CmdPuts.append(_CmdPut(x, y, halign, direction, color))
728 def Extents(self, extents, missextents, texinstance):
729 """get sizes from previous LaTeX run"""
731 for extent in extents:
732 if extent not in self.CmdExtents:
733 self.CmdExtents.append(extent)
735 result = []
736 for extent in extents:
737 s = self.MD5() + ":" + str(extent)
738 for size in texinstance.Sizes:
739 if size[:len(s)] == s:
740 texpt = float(string.rstrip(size.split(":")[3][:-3]))
741 result.append(unit.t_pt * texpt * 72.0 / 72.27)
742 break
743 else:
744 break
745 else:
746 return result
748 # extent was not found --- temporarily remove all other commands in
749 # order to allow the misshandler to access everything it ever wants
750 storeboxcmds = texinstance.BoxCmds
751 storecmdputs = self.CmdPuts
752 storecmdextents = self.CmdExtents
753 texinstance.BoxCmds = [self, ]
754 self.CmdPuts = []
755 self.CmdExtents = extents
756 try:
757 result = missextents.misshandler(texinstance)
758 finally:
759 texinstance.BoxCmds = storeboxcmds
760 self.CmdPuts = storecmdputs
761 self.CmdExtents = storecmdextents
762 return result
765 ################################################################################
766 # tex, latex class
767 ################################################################################
769 class _tex(base.canvasitem, attrlist):
771 """major parts are of tex and latex class are shared and implemented here"""
773 def __init__(self, defaultmsghandlers=msghandler.hideload,
774 defaultmissextents=missextents.returnzero,
775 texfilename=None):
776 if isinstance(defaultmsghandlers, msghandler):
777 self.defaultmsghandlers = (defaultmsghandlers,)
778 else:
779 self.defaultmsghandlers = defaultmsghandlers
780 self.defaultmissextents = defaultmissextents
781 self.texfilename = texfilename
782 self.DefCmds = []
783 self.DefCmdsStr = None
784 self.BoxCmds = []
785 self.DoneRunTex = 0
787 if len(os.path.basename(sys.argv[0])):
788 basename = os.path.basename(sys.argv[0])
789 if basename[-3:] == ".py":
790 basename = basename[:-3]
791 self.SizeFileName = os.path.join(os.getcwd(), basename + ".size")
792 else:
793 self.SizeFileName = os.path.join(os.getcwd(), "pyxput.size")
794 try:
795 file = open(self.SizeFileName, "r")
796 self.Sizes = file.readlines()
797 file.close()
798 except IOError:
799 self.Sizes = [ ]
801 def _execute(self, command):
802 if os.system(command):
803 sys.stderr.write("The exit code of the following command was non-zero:\n" + command +
804 """\nUsually, additional information causing this trouble appears closeby.
805 However, you may check the origin by keeping all temporary files.
806 In order to achieve this, you have to specify a texfilename in the
807 constructor of the class pyx.(la)tex. You can then try to run the
808 command by yourself.\n""")
810 def _createaddfiles(self, tempname):
811 pass
813 def _removeaddfiles(self, tempname):
814 pass
816 def _executetex(self, tempname):
817 pass
819 def _executedvips(self, tempname):
820 self._execute("dvips -O0in,11in -E -o %(t)s.eps %(t)s.dvi > %(t)s.dvipsout 2> %(t)s.dvipserr" % {"t": tempname})
822 def _run(self):
823 """run (La)TeX & dvips, report errors, fill self.abbox & self.epsdata"""
825 if self.DoneRunTex:
826 return
828 if self.texfilename:
829 mktemp = str(self.texfilename)
830 else:
831 storetempdir = tempfile.tempdir
832 tempfile.tempdir = os.curdir
833 mktemp = tempfile.mktemp()
834 tempfile.tempdir = storetempdir
835 tempname = os.path.basename(mktemp)
837 self._createaddfiles(tempname)
839 texfile = open(tempname + ".tex", "w")
841 texfile.write("\\nonstopmode%\n")
842 texfile.write("\\def\PyX{P\\kern-.3em\\lower.5ex\\hbox{Y}\\kern-.18em X}%\n")
843 texfile.write("\\newwrite\\sizefile%\n\\newbox\\localbox%\n\\newbox\\pagebox%\n")
844 texfile.write("{\\catcode`\\~=12\\immediate\\openout\\sizefile=%s.size\\relax}%%\n" % tempname)
846 for Cmd in self.DefCmds:
847 Cmd.write(texfile)
849 texfile.write("\\setbox\\pagebox=\\vbox{%\n")
851 for Cmd in self.BoxCmds:
852 Cmd.write(texfile)
854 texfile.write("}\n\\immediate\\closeout\\sizefile\n\\shipout\\copy\\pagebox\n")
855 texfile.write(self._endcmd())
856 texfile.close()
858 self._executetex(tempname)
860 try:
861 outfile = open(tempname + ".texout", "r")
862 for Cmd in self.DefCmds + self.BoxCmds:
863 Cmd.CheckMarkerError(outfile)
864 outfile.close()
865 except IOError:
866 sys.stderr.write("""An unexpected error occured while reading the (La)TeX output.
867 May be, you just have no disk space available. Or something badly
868 in your commands caused (La)TeX to give up completely. Or your
869 (La)TeX installation might be broken at all.
870 You may try to check the origin by keeping all temporary files.
871 In order to achieve this, you have to specify a texfilename in the
872 constructor of the class pyx.tex. You can then try to run (La)TeX
873 by yourself.\n""")
875 if not os.access(tempname + ".dvi", 0):
876 sys.stderr.write("""Can't find the dvi file which should be produced by (La)TeX.
877 May be, you just have no disk space available. Or something badly
878 in your commands caused (La)TeX to give up completely. Or your
879 (La)TeX installation might be broken at all.
880 You may try to check the origin by keeping all temporary files.
881 In order to achieve this, you have to specify a texfilename in the
882 constructor of the class pyx.tex. You can then try to run (La)TeX
883 by yourself.\n""")
885 else:
886 self._executedvips(tempname)
887 if not os.access(tempname + ".eps", 0):
888 sys.stderr.write("""Error reading the eps file which should be produced by dvips.
889 May be, you just have no disk space available. Or something badly
890 in your commands caused dvips to give up completely. Or your
891 (La)TeX installation might be broken at all.
892 You may try to check the origin by keeping all temporary files.
893 In order to achieve this, you have to specify a texfilename in the
894 constructor of the class pyx.tex. You can then try to run dvips
895 by yourself.\n""")
896 else:
897 aepsfile = epsfile.epsfile(0, 0, tempname + ".eps", translatebbox=0, clip=0)
898 self.abbox = aepsfile.bbox()
899 self.aprolog = aepsfile.prolog()
900 epsdatafile = StringIO.StringIO()
901 aepsfile.outputPS(epsdatafile)
902 self.epsdata = epsdatafile.getvalue()
904 # merge new sizes
906 OldSizes = self.Sizes
908 try:
909 NewSizeFile = open(tempname + ".size", "r")
910 NewSizes = NewSizeFile.readlines()
911 NewSizeFile.close()
912 except IOError:
913 NewSizes = []
915 if (len(NewSizes) != 0) or (len(OldSizes) != 0):
916 SizeFile = open(self.SizeFileName, "w")
917 SizeFile.writelines(NewSizes)
918 self.Sizes = NewSizes
919 for OldSize in OldSizes:
920 OldSizeSplit = OldSize.split(":")
921 for NewSize in NewSizes:
922 if NewSize.split(":")[0:2] == OldSizeSplit[0:2]:
923 break
924 else:
925 if time.time() < float(OldSizeSplit[2]) + 60*60*24: # we keep size results for one day
926 SizeFile.write(OldSize)
927 self.Sizes.append(OldSize)
929 if not self.texfilename:
930 for suffix in ("tex", "log", "size", "dvi", "eps", "texout", "texerr", "dvipsout", "dvipserr", ):
931 try:
932 os.unlink(tempname + "." + suffix)
933 except:
934 pass
936 self._removeaddfiles(tempname)
937 self.DoneRunTex = 1
939 def prolog(self):
940 self._run()
941 return self.aprolog
943 def bbox(self):
944 self._run()
945 return self.abbox
947 def outputPS(self, file):
948 self._run()
949 file.writelines(self.epsdata)
951 def define(self, Cmd, *attrs):
952 if len(self.BoxCmds):
953 raise TexDefAfterBoxError
954 self.DoneRunTex = 0
955 self.attrcheck(attrs, (), (msghandler,))
956 self.DefCmds.append(_DefCmd(Cmd,
957 len(self.DefCmds)+ len(self.BoxCmds),
958 traceback.extract_stack(),
959 self.attrgetall(attrs, msghandler, self.defaultmsghandlers)))
961 def _insertcmd(self, Cmd, *attrs):
962 if not len(self.BoxCmds):
963 self._beginboxcmds()
964 self.DefCmdsStr = reduce(lambda x,y: x + y.DefCmd, self.DefCmds, "")
965 mystyle = self.attrget(attrs, style, style.text)
966 myfontsize = self.attrget(attrs, fontsize, fontsize.normalsize)
967 myvalign = self.attrget(attrs, valign, None)
968 mymsghandlers = self.attrgetall(attrs, msghandler, self.defaultmsghandlers)
969 MyCmd = _BoxCmd(self.DefCmdsStr, Cmd, mystyle, myfontsize, myvalign,
970 len(self.DefCmds) + len(self.BoxCmds), traceback.extract_stack(), mymsghandlers)
971 if MyCmd not in self.BoxCmds:
972 self.BoxCmds.append(MyCmd)
973 for Cmd in self.BoxCmds:
974 if Cmd == MyCmd:
975 UseCmd = Cmd # we could use MyCmd directly if we have just inserted it before
976 # (that's due to the side effect, that append doesn't make a copy of the element,
977 # but we ignore this here -- we don't want to depend on this side effect)
978 return UseCmd
980 def _text(self, x, y, Cmd, *attrs):
981 """print Cmd at (x, y) --- position parameters in postscipt points"""
983 self.DoneRunTex = 0
984 self.attrcheck(attrs, (style, fontsize, halign, valign, direction, color.color), (msghandler,))
985 myhalign = self.attrget(attrs, halign, halign.left)
986 mydirection = self.attrget(attrs, direction, direction.horizontal)
987 mycolor = self.attrget(attrs, color.color, color.gray.black)
988 self._insertcmd(Cmd, *attrs).Put(x * 72.27 / 72.0, y * 72.27 / 72.0, myhalign, mydirection, mycolor)
990 def text(self, x, y, Cmd, *attrs):
991 """print Cmd at (x, y)"""
993 self._text(unit.topt(x), unit.topt(y), Cmd, *attrs)
995 def textwd(self, Cmd, *attrs):
996 """get width of Cmd"""
998 self.DoneRunTex = 0
999 self.attrcheck(attrs, (style, fontsize, missextents), (msghandler,))
1000 mymissextents = self.attrget(attrs, missextents, self.defaultmissextents)
1001 return self._insertcmd(Cmd, *attrs).Extents((_extent.wd, ), mymissextents, self)[0]
1003 def textht(self, Cmd, *attrs):
1004 """get height of Cmd"""
1006 self.DoneRunTex = 0
1007 self.attrcheck(attrs, (style, fontsize, valign, missextents), (msghandler,))
1008 mymissextents = self.attrget(attrs, missextents, self.defaultmissextents)
1009 return self._insertcmd(Cmd, *attrs).Extents((_extent.ht, ), mymissextents, self)[0]
1012 def textdp(self, Cmd, *attrs):
1013 """get depth of Cmd"""
1015 self.DoneRunTex = 0
1016 self.attrcheck(attrs, (style, fontsize, valign, missextents), (msghandler,))
1017 mymissextents = self.attrget(attrs, missextents, self.defaultmissextents)
1018 return self._insertcmd(Cmd, *attrs).Extents((_extent.dp, ), mymissextents, self)[0]
1021 class tex(_tex):
1023 """tex class adds the specializations to _tex needed for tex"""
1025 def __init__(self, lfs="10pt", **addargs):
1026 _tex.__init__(self, **addargs)
1027 # XXX other ways for creating font sizes?
1028 try:
1029 LocalLfsName = str(lfs) + ".lfs"
1030 lfsdef = open(LocalLfsName, "r").read()
1031 except IOError:
1032 try:
1033 try:
1034 SysLfsName = os.path.join(sys.prefix, "share", "pyx", str(lfs) + ".lfs")
1035 lfsdef = open(SysLfsName, "r").read()
1036 except IOError:
1037 SysLfsName = os.path.join(os.path.dirname(__file__), "lfs", str(lfs) + ".lfs")
1038 lfsdef = open(SysLfsName, "r").read()
1039 except IOError:
1040 files = map(lambda x: x[:-4],
1041 filter(lambda x: x[-4:] == ".lfs",
1042 os.listdir(".") +
1043 os.listdir(os.path.join(sys.prefix, "share", "pyx")),
1044 os.listdir(os.path.join(os.path.dirname(__file__), "lfs"))))
1045 raise IOError("file '%s.lfs' not found. Available latex font sizes:\n%s" % (lfs, files))
1046 self.define(lfsdef)
1047 self.define("\\newdimen\\linewidth%\n\\hsize0truein%\n\\vsize0truein%\n\\hoffset-1truein%\n\\voffset-1truein")
1049 def _beginboxcmds(self):
1050 pass
1052 def _endcmd(self):
1053 return "\\end\n"
1055 def _executetex(self, tempname):
1056 self._execute("tex %(t)s.tex > %(t)s.texout 2> %(t)s.texerr" % {"t": tempname})
1059 class latex(_tex):
1061 """latex class adds the specializations to _tex needed for latex"""
1063 def __init__(self, docclass="article", docopt=None, auxfilename=None, **addargs):
1064 _tex.__init__(self, **addargs)
1065 self.auxfilename = auxfilename
1066 if docopt:
1067 self.define("\\documentclass[" + str(docopt) + "]{" + str(docclass) + "}")
1068 else:
1069 self.define("\\documentclass{" + str(docclass) + "}")
1070 self.define("\\hsize0truein%\n\\vsize0truein%\n\\hoffset-1truein%\n\\voffset-1truein")
1072 def _beginboxcmds(self):
1073 self.define("\\begin{document}")
1075 def _endcmd(self):
1076 return "\\end{document}\n"
1078 def _createaddfiles(self, tempname):
1079 if self.auxfilename is not None:
1080 writenew = 0
1081 try:
1082 os.rename(self.auxfilename + ".aux", tempname + ".aux")
1083 except OSError:
1084 writenew = 1
1085 else:
1086 writenew = 1
1087 if writenew:
1088 auxfile = open(tempname + ".aux", "w")
1089 auxfile.write("\\relax\n")
1090 auxfile.close()
1092 def _executetex(self, tempname):
1093 self._execute("latex %(t)s.tex > %(t)s.texout 2> %(t)s.texerr" % {"t": tempname})
1095 def _removeaddfiles(self, tempname):
1096 if self.auxfilename is not None:
1097 os.rename(tempname + ".aux", self.auxfilename + ".aux")
1098 else:
1099 os.unlink(tempname + ".aux")