remove limitation on number of fonts in dvi file
[PyX/mjg.git] / pyx / tex.py
blob9e0aa84a5233fa09d1c41ae81d7a820b1f788796
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, unit, epsfile, color, attrlist
39 ################################################################################
40 # TeX attributes
41 ################################################################################
43 class _texattr:
45 """base class for all TeX attributes"""
47 pass
50 class fontsize(_texattr):
52 """fontsize TeX attribute"""
54 def __init__(self, value):
55 self.value = value
57 def __str__(self):
58 return self.value
61 fontsize.tiny = fontsize("tiny")
62 fontsize.scriptsize = fontsize("scriptsize")
63 fontsize.footnotesize = fontsize("footnotesize")
64 fontsize.small = fontsize("small")
65 fontsize.normalsize = fontsize("normalsize")
66 fontsize.large = fontsize("large")
67 fontsize.Large = fontsize("Large")
68 fontsize.LARGE = fontsize("LARGE")
69 fontsize.huge = fontsize("huge")
70 fontsize.Huge = fontsize("Huge")
73 class halign(_texattr):
75 """tex horizontal align attribute"""
77 def __init__(self, value):
78 self.value = value
80 def __cmp__(self, other):
81 if other is None: return 1
82 return cmp(self.value, other.value)
84 __rcmp__ = __cmp__
87 halign.left = halign("left")
88 halign.center = halign("center")
89 halign.right = halign("right")
92 class valign(_texattr):
94 """abstract tex vertical align attribute"""
96 def __init__(self, hsize):
97 self.hsize = hsize
100 class _valignvtop(valign):
102 """tex top vertical align attribute"""
104 pass
107 valign.top = _valignvtop
110 class _valignvbox(valign):
112 """tex bottom vertical align attribute"""
114 pass
117 valign.bottom = _valignvbox
120 class direction(_texattr):
122 """tex output direction attribute"""
124 def __init__(self, value):
125 self.value = value
127 def __str__(self):
128 return "%.5f" % self.value
132 direction.horizontal = direction(0)
133 direction.vertical = direction(90)
134 direction.upsidedown = direction(180)
135 direction.rvertical = direction(270)
138 class style(_texattr):
140 """tex style modification attribute"""
142 def __init__(self, praefix, suffix):
143 self.praefix = praefix
144 self.suffix = suffix
146 def ModifyCmd(self, cmd):
147 return self.praefix + cmd + self.suffix
150 style.text = style("", "")
151 style.math = style("$\displaystyle{}", "$")
154 ################################################################################
155 # TeX message handlers
156 ################################################################################
158 class msghandler(_texattr):
160 """abstract base class for tex message handlers
162 A message handler has to provide a parsemsg method. It gets a string and
163 returns a string. Within the parsemsg method the handler may remove any
164 part of the message it is familiar with."""
166 def removeemptylines(self, msg):
167 """any message parser may use this method to remove empty lines"""
169 msg = re.sub("^(\n)*", "", msg)
170 msg = re.sub("(\n){3,}", "\n\n", msg)
171 msg = re.sub("(\n)+$", "\n", msg)
172 return msg
175 class _msghandlershowall(msghandler):
177 """a message handler, which shows all messages"""
179 def parsemsg(self, msg):
180 return msg
183 msghandler.showall = _msghandlershowall()
185 class _msghandlerhideload(msghandler):
187 """a message handler, which hides all messages inside proper '(filename' and ')'
188 the string filename has to be a readable file"""
190 def parsemsg(self, msg):
191 depth = 0
192 newstr = ""
193 newlevel = 0
194 for c in msg:
195 if newlevel and (c in (list(string.whitespace) + ["(", ")"])):
196 if filestr not in ("c", "C"):
197 if not len(filestr):
198 break
199 if not os.access(filestr,os.R_OK):
200 break
201 newlevel = 0
202 if c == "(":
203 depth += 1
204 filestr = ""
205 newlevel = 1
206 elif c == ")":
207 depth -= 1
208 if depth < 0:
209 break
210 elif depth == 0:
211 newstr += c
212 else:
213 filestr += c
214 else:
215 # replace msg only if loop was completed and no ")" is missing
216 if depth == 0:
217 msg = self.removeemptylines(newstr)
218 return msg
221 msghandler.hideload = _msghandlerhideload()
224 class _msghandlerhidegraphicsload(msghandler):
226 """a message handler, which hides all messages like '<filename>'
227 the string filename has to be a readable file"""
229 def parsemsg(self, msg):
230 depth = 0
231 newstr = ""
232 for c in msg:
233 if c == "<":
234 depth += 1
235 if depth > 1:
236 break
237 filestr = ""
238 elif c == ">":
239 depth -= 1
240 if depth < 0:
241 break
242 if not os.access(filestr,os.R_OK):
243 newstr += "<" + filestr + ">"
244 elif depth == 0:
245 newstr += c
246 else:
247 filestr += c
248 else:
249 # replace msg only if loop was completed and no ">" missing
250 if depth == 0:
251 msg = self.removeemptylines(newstr)
252 return msg
255 msghandler.hidegraphicsload = _msghandlerhidegraphicsload()
258 class _msghandlerhidefontwarning(msghandler):
260 """a message handler, which hides LaTeX font warnings, e.g.
261 Messages starting with 'LaTeX Font Warning: ' which might be
262 continued on following lines by '(Font) '"""
264 def parsemsg(self, msg):
265 msglines = string.split(msg, "\n")
266 newmsglines = []
267 fontwarning = 0
268 for line in msglines:
269 if fontwarning and line[:20] != "(Font) ":
270 fontwarning = 0
271 if not fontwarning and line[:20] == "LaTeX Font Warning: ":
272 fontwarning = 1
273 if not fontwarning:
274 newmsglines.append(line)
275 newmsg = reduce(lambda x, y: x + y + "\n", newmsglines, "")
276 return self.removeemptylines(newmsg)
279 msghandler.hidefontwarning = _msghandlerhidefontwarning()
282 class _msghandlerhidebuterror(msghandler):
284 """a message handler, hides all messages whenever they do
285 not contain a line starting with '! '"""
287 def parsemsg(self, msg):
288 # the "\n" + msg instead of msg itself is needed, if the message starts with "! "
289 if string.find("\n" + msg, "\n! ") != -1:
290 return msg
291 else:
292 return ""
295 msghandler.hidebuterror = _msghandlerhidebuterror()
298 class _msghandlerhideall(msghandler):
300 """a message handler, which hides all messages"""
302 def parsemsg(self, msg):
303 return ""
306 msghandler.hideall = _msghandlerhideall()
309 ################################################################################
310 # extent handlers
311 ################################################################################
313 class missextents(_texattr):
315 """abstract base class for handling missing extents
317 A miss extent class has to provide a misshandler method."""
320 _missextentsreturnzero_report = 0
321 def _missextentsreturnzero_printreport():
322 sys.stderr.write("""
323 pyx.tex: Some requested extents were missing and have been replaced by zero.
324 Please run the file again to get correct extents.\n""")
326 class _missextentsreturnzero(missextents):
328 def misshandler(self, texinstance):
329 global _missextentsreturnzero_report
330 if not _missextentsreturnzero_report:
331 atexit.register(_missextentsreturnzero_printreport)
332 _missextentsreturnzero_report = 1
333 return map(lambda x: unit.t_pt(0), texinstance.BoxCmds[0].CmdExtents)
336 missextents.returnzero = _missextentsreturnzero()
339 class _missextentsreturnzeroquiet(missextents):
341 def misshandler(self, texinstance):
342 return map(lambda x: unit.t_pt(0), texinstance.BoxCmds[0].CmdExtents)
345 missextents.returnzeroquiet = _missextentsreturnzeroquiet()
348 class _missextentsraiseerror(missextents):
350 def misshandler(self, texinstance):
351 raise TexMissExtentError
354 missextents.raiseerror = _missextentsraiseerror()
357 class _missextentscreateextents(missextents):
359 def misshandler(self, texinstance):
360 if isinstance(texinstance, latex):
361 storeauxfilename = texinstance.auxfilename
362 texinstance.auxfilename = None
363 texinstance.DoneRunTex = 0
364 texinstance._run()
365 texinstance.DoneRunTex = 0
366 if isinstance(texinstance, latex):
367 texinstance.auxfilename = storeauxfilename
368 return texinstance.BoxCmds[0].Extents(texinstance.BoxCmds[0].CmdExtents,
369 missextents.returnzero, texinstance)
372 missextents.createextents = _missextentscreateextents()
375 class _missextentscreateallextents(missextents):
377 def misshandler(self, texinstance):
378 if isinstance(texinstance, latex):
379 storeauxfilename = texinstance.auxfilename
380 texinstance.auxfilename = None
381 texinstance.DoneRunTex = 0
382 storeextents = texinstance.BoxCmds[0].CmdExtents[0]
383 texinstance.BoxCmds[0].CmdExtents = [_extent.wd, _extent.ht, _extent.dp]
384 texinstance._run()
385 texinstance.BoxCmds[0].CmdExtents[0] = storeextents
386 texinstance.DoneRunTex = 0
387 if isinstance(texinstance, latex):
388 texinstance.auxfilename = storeauxfilename
389 return texinstance.BoxCmds[0].Extents(texinstance.BoxCmds[0].CmdExtents,
390 missextents.returnzero, texinstance)
393 missextents.createallextents = _missextentscreateallextents()
396 ################################################################################
397 # TeX exceptions
398 ################################################################################
400 class TexExcept(base.PyXExcept):
402 pass
405 class TexLeftParenthesisError(TexExcept):
407 def __str__(self):
408 return "no matching parenthesis for '{' found"
411 class TexRightParenthesisError(TexExcept):
413 def __str__(self):
414 return "no matching parenthesis for '}' found"
417 class TexHalignError(TexExcept):
419 def __str__(self):
420 return "unkown halign"
423 class TexValignError(TexExcept):
425 def __str__(self):
426 return "unkown valign"
429 class TexDefAfterBoxError(TexExcept):
431 def __str__(self):
432 return "definition commands not allowed after output commands"
435 class TexMissExtentError(TexExcept):
437 def __str__(self):
438 return "requested tex extent not available"
441 ################################################################################
442 # modules internal stuff
443 ################################################################################
445 class _extent:
447 def __init__(self, value):
448 self.value = value
450 def __str__(self):
451 return self.value
454 _extent.wd = _extent("wd")
455 _extent.ht = _extent("ht")
456 _extent.dp = _extent("dp")
459 class _TexCmd:
461 """class for all user supplied commands"""
463 PyxMarker = "PyxMarker"
464 BeginPyxMarker = "Begin" + PyxMarker
465 EndPyxMarker = "End" + PyxMarker
467 def __init__(self, Marker, Stack, msghandlers):
468 self.Marker = Marker
469 self.Stack = Stack
470 self.msghandlers = msghandlers
472 def TexParenthesisCheck(self, Cmd):
473 """check for proper usage of "{" and "}" in Cmd"""
475 depth = 0
476 esc = 0
477 for c in Cmd:
478 if c == "{" and not esc:
479 depth = depth + 1
480 if c == "}" and not esc:
481 depth = depth - 1
482 if depth < 0:
483 raise TexRightParenthesisError
484 if c == "\\":
485 esc = (esc + 1) % 2
486 else:
487 esc = 0
488 if depth > 0:
489 raise TexLeftParenthesisError
491 def BeginMarkerStr(self):
492 return "%s[%s]" % (self.BeginPyxMarker, self.Marker, )
494 def WriteBeginMarker(self, file):
495 file.write("\\immediate\\write16{%s}%%\n" % self.BeginMarkerStr())
497 def EndMarkerStr(self):
498 return "%s[%s]" % (self.EndPyxMarker, self.Marker, )
500 def WriteEndMarker(self, file):
501 file.write("\\immediate\\write16{%s}%%\n" % self.EndMarkerStr())
503 def WriteError(self, msg):
504 sys.stderr.write("Traceback (innermost last):\n")
505 traceback.print_list(self.Stack)
506 sys.stderr.write("(La)TeX Message:\n" + msg + "\n")
508 def CheckMarkerError(self, file):
509 """read markers and identify the message"""
511 line = file.readline()
512 while (line != "") and (line[:-1] != self.BeginMarkerStr()):
513 line = file.readline()
514 msg = ""
515 line = file.readline()
516 while (line != "") and (line[:-1] != self.EndMarkerStr()):
517 msg = msg + line
518 line = file.readline()
519 if line == "":
520 self.WriteError(msg)
521 raise IOError
522 else:
523 # check if message can be ignored
524 doprint = 0
525 parsedmsg = msg
526 for msghandler in self.msghandlers:
527 parsedmsg = msghandler.parsemsg(parsedmsg)
528 for c in parsedmsg:
529 if c not in string.whitespace:
530 self.WriteError(parsedmsg)
531 break
534 class _DefCmd(_TexCmd):
536 """definition commands"""
538 def __init__(self, DefCmd, Marker, Stack, msghandlers):
539 _TexCmd.__init__(self, Marker, Stack, msghandlers)
540 self.TexParenthesisCheck(DefCmd)
541 self.DefCmd = "%s%%\n" % DefCmd
543 def write(self, file):
544 self.WriteBeginMarker(file)
545 file.write(self.DefCmd)
546 self.WriteEndMarker(file)
549 class _CmdPut:
551 """print parameters for a BoxCmd (data structure)"""
553 def __init__(self, x, y, halign, direction, color):
554 self.x = x
555 self.y = y
556 self.halign = halign
557 self.direction = direction
558 self.color = color
561 class _BoxCmd(_TexCmd):
563 """BoxCmd (for printing text and getting extents)"""
565 def __init__(self, DefCmdsStr, BoxCmd, style, fontsize, valign, Marker, Stack, msghandlers):
566 _TexCmd.__init__(self, Marker, Stack, msghandlers)
567 self.TexParenthesisCheck(BoxCmd)
568 self.DefCmdsStr = DefCmdsStr
569 self.BoxCmd = "{%s}%%\n" % BoxCmd # add another "{" to ensure, that everything goes into the Box
570 self.CmdPuts = [] # list, where to put the command
571 self.CmdExtents = [] # list, which extents are requested
573 self.BoxCmd = style.ModifyCmd(self.BoxCmd)
574 if valign is not None:
575 if isinstance(valign, _valignvtop):
576 self.BoxCmd = "\\linewidth%.5ftruept\\vtop{\\hsize\\linewidth{%s}}" % \
577 (unit.topt(valign.hsize) * 72.27/72, self.BoxCmd, )
578 elif isinstance(valign, _valignvbox):
579 self.BoxCmd = "\\linewidth%.5ftruept\\vbox{\\hsize\\linewidth{%s}}" % \
580 (unit.topt(valign.hsize) * 72.27/72, self.BoxCmd, )
581 else:
582 raise TexValignError
583 self.BoxCmd = "\\setbox\\localbox=\\hbox{\\%s%s}%%\n" % (fontsize, self.BoxCmd, )
585 def __cmp__(self, other):
586 if other is None: return 1
587 return cmp(self.BoxCmd, other.BoxCmd)
589 __rcmp__ = __cmp__
591 def write(self, file):
592 self.WriteBeginMarker(file)
593 file.write(self.BoxCmd)
594 self.WriteEndMarker(file)
595 for CmdExtent in self.CmdExtents:
596 file.write("\\immediate\\write\\sizefile{%s:%s:%s:\\the\\%s\\localbox}%%\n" %
597 (self.MD5(), CmdExtent, time.time(), CmdExtent, ))
598 for CmdPut in self.CmdPuts:
600 file.write("{\\vbox to0pt{\\kern%.5ftruept\\hbox{\\kern%.5ftruept\\ht\\localbox0pt" %
601 (-CmdPut.y, CmdPut.x))
603 if CmdPut.direction != direction.horizontal:
604 file.write("\\special{ps: gsave currentpoint currentpoint translate " +
605 str(CmdPut.direction) + " neg rotate neg exch neg exch translate }")
606 if CmdPut.color != color.gray.black:
607 file.write("\\special{ps: ")
608 CmdPut.color.write(file)
609 file.write(" }")
610 if CmdPut.halign == halign.left:
611 pass
612 elif CmdPut.halign == halign.center:
613 file.write("\kern-.5\wd\localbox")
614 elif CmdPut.halign == halign.right:
615 file.write("\kern-\wd\localbox")
616 else:
617 raise TexHalignError
618 file.write("\\copy\\localbox")
620 if CmdPut.color != color.gray.black:
621 file.write("\\special{ps: ")
622 color.gray.black.write(file)
623 file.write(" }")
624 if CmdPut.direction != direction.horizontal:
625 file.write("\\special{ps: currentpoint grestore moveto }")
626 file.write("}\\vss}\\nointerlineskip}%\n")
628 def MD5(self):
629 """creates an MD5 hex string for texinit + Cmd"""
631 h = string.hexdigits
632 r = ''
633 s = md5.md5(self.DefCmdsStr + self.BoxCmd).digest()
634 for c in s:
635 i = ord(c)
636 r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
637 return r
639 def Put(self, x, y, halign, direction, color):
640 self.CmdPuts.append(_CmdPut(x, y, halign, direction, color))
642 def Extents(self, extents, missextents, texinstance):
643 """get sizes from previous LaTeX run"""
645 for extent in extents:
646 if extent not in self.CmdExtents:
647 self.CmdExtents.append(extent)
649 result = []
650 for extent in extents:
651 s = self.MD5() + ":" + str(extent)
652 for size in texinstance.Sizes:
653 if size[:len(s)] == s:
654 texpt = float(string.rstrip(size.split(":")[3][:-3]))
655 result.append(unit.t_pt(texpt * 72.0 / 72.27))
656 break
657 else:
658 break
659 else:
660 return result
662 # extent was not found --- temporarily remove all other commands in
663 # order to allow the misshandler to access everything it ever wants
664 storeboxcmds = texinstance.BoxCmds
665 storecmdputs = self.CmdPuts
666 storecmdextents = self.CmdExtents
667 texinstance.BoxCmds = [self, ]
668 self.CmdPuts = []
669 self.CmdExtents = extents
670 try:
671 result = missextents.misshandler(texinstance)
672 finally:
673 texinstance.BoxCmds = storeboxcmds
674 self.CmdPuts = storecmdputs
675 self.CmdExtents = storecmdextents
676 return result
679 ################################################################################
680 # tex, latex class
681 ################################################################################
683 class _tex(base.PSCmd, attrlist.attrlist):
685 """major parts are of tex and latex class are shared and implemented here"""
687 def __init__(self, defaultmsghandlers=msghandler.hideload,
688 defaultmissextents=missextents.returnzero,
689 texfilename=None):
690 if isinstance(defaultmsghandlers, msghandler):
691 self.defaultmsghandlers = (defaultmsghandlers,)
692 else:
693 self.defaultmsghandlers = defaultmsghandlers
694 self.defaultmissextents = defaultmissextents
695 self.texfilename = texfilename
696 self.DefCmds = []
697 self.DefCmdsStr = None
698 self.BoxCmds = []
699 self.DoneRunTex = 0
701 if len(os.path.basename(sys.argv[0])):
702 basename = os.path.basename(sys.argv[0])
703 if basename[-3:] == ".py":
704 basename = basename[:-3]
705 self.SizeFileName = os.path.join(os.getcwd(), basename + ".size")
706 else:
707 self.SizeFileName = os.path.join(os.getcwd(), "pyxput.size")
708 try:
709 file = open(self.SizeFileName, "r")
710 self.Sizes = file.readlines()
711 file.close()
712 except IOError:
713 self.Sizes = [ ]
715 def _getstack(self):
716 return traceback.extract_stack(sys._getframe())
718 def _execute(self, command):
719 if os.system(command):
720 sys.stderr.write("The exit code of the following command was non-zero:\n" + command +
721 """\nUsually, additional information causing this trouble appears closeby.
722 However, you may check the origin by keeping all temporary files.
723 In order to achieve this, you have to specify a texfilename in the
724 constructor of the class pyx.(la)tex. You can then try to run the
725 command by yourself.\n""")
727 def _createaddfiles(self, tempname):
728 pass
730 def _removeaddfiles(self, tempname):
731 pass
733 def _executetex(self, tempname):
734 pass
736 def _executedvips(self, tempname):
737 self._execute("dvips -O0in,11in -E -o %(t)s.eps %(t)s.dvi > %(t)s.dvipsout 2> %(t)s.dvipserr" % {"t": tempname})
739 def _run(self):
740 """run (La)TeX & dvips, report errors, fill self.abbox & self.epsdata"""
742 if self.DoneRunTex:
743 return
745 if self.texfilename:
746 mktemp = str(self.texfilename)
747 else:
748 storetempdir = tempfile.tempdir
749 tempfile.tempdir = os.curdir
750 mktemp = tempfile.mktemp()
751 tempfile.tempdir = storetempdir
752 tempname = os.path.basename(mktemp)
754 self._createaddfiles(tempname)
756 texfile = open(tempname + ".tex", "w")
758 texfile.write("\\nonstopmode%\n")
759 texfile.write("\\def\PyX{P\\kern-.3em\\lower.5ex\\hbox{Y}\\kern-.18em X}%\n")
760 texfile.write("\\newwrite\\sizefile%\n\\newbox\\localbox%\n\\newbox\\pagebox%\n")
761 texfile.write("{\\catcode`\\~=12\\immediate\\openout\\sizefile=%s.size\\relax}%%\n" % tempname)
763 for Cmd in self.DefCmds:
764 Cmd.write(texfile)
766 texfile.write("\\setbox\\pagebox=\\vbox{%\n")
768 for Cmd in self.BoxCmds:
769 Cmd.write(texfile)
771 texfile.write("}\n\\immediate\\closeout\\sizefile\n\\shipout\\copy\\pagebox\n")
772 texfile.write(self._endcmd())
773 texfile.close()
775 self._executetex(tempname)
777 try:
778 outfile = open(tempname + ".texout", "r")
779 for Cmd in self.DefCmds + self.BoxCmds:
780 Cmd.CheckMarkerError(outfile)
781 outfile.close()
782 except IOError:
783 sys.stderr.write("""An unexpected error occured while reading the (La)TeX output.
784 May be, you just have no disk space available. Or something badly
785 in your commands caused (La)TeX to give up completely. Or your
786 (La)TeX installation might be broken at all.
787 You may try to check the origin by keeping all temporary files.
788 In order to achieve this, you have to specify a texfilename in the
789 constructor of the class pyx.tex. You can then try to run (La)TeX
790 by yourself.\n""")
792 if not os.access(tempname + ".dvi", 0):
793 sys.stderr.write("""Can't find the dvi file which should be produced by (La)TeX.
794 May be, you just have no disk space available. Or something badly
795 in your commands caused (La)TeX to give up completely. Or your
796 (La)TeX installation might be broken at all.
797 You may try to check the origin by keeping all temporary files.
798 In order to achieve this, you have to specify a texfilename in the
799 constructor of the class pyx.tex. You can then try to run (La)TeX
800 by yourself.\n""")
802 else:
803 self._executedvips(tempname)
804 if not os.access(tempname + ".eps", 0):
805 sys.stderr.write("""Error reading the eps file which should be produced by dvips.
806 May be, you just have no disk space available. Or something badly
807 in your commands caused dvips to give up completely. Or your
808 (La)TeX installation might be broken at all.
809 You may try to check the origin by keeping all temporary files.
810 In order to achieve this, you have to specify a texfilename in the
811 constructor of the class pyx.tex. You can then try to run dvips
812 by yourself.\n""")
813 else:
814 aepsfile = epsfile.epsfile(0, 0, tempname + ".eps", translatebbox=0, clip=0)
815 self.abbox = aepsfile.bbox()
816 self.aprolog = aepsfile.prolog()
817 epsdatafile = StringIO.StringIO()
818 aepsfile.write(epsdatafile)
819 self.epsdata = epsdatafile.getvalue()
821 # merge new sizes
823 OldSizes = self.Sizes
825 try:
826 NewSizeFile = open(tempname + ".size", "r")
827 NewSizes = NewSizeFile.readlines()
828 NewSizeFile.close()
829 except IOError:
830 NewSizes = []
832 if (len(NewSizes) != 0) or (len(OldSizes) != 0):
833 SizeFile = open(self.SizeFileName, "w")
834 SizeFile.writelines(NewSizes)
835 self.Sizes = NewSizes
836 for OldSize in OldSizes:
837 OldSizeSplit = OldSize.split(":")
838 for NewSize in NewSizes:
839 if NewSize.split(":")[0:2] == OldSizeSplit[0:2]:
840 break
841 else:
842 if time.time() < float(OldSizeSplit[2]) + 60*60*24: # we keep size results for one day
843 SizeFile.write(OldSize)
844 self.Sizes.append(OldSize)
846 if not self.texfilename:
847 for suffix in ("tex", "log", "size", "dvi", "eps", "texout", "texerr", "dvipsout", "dvipserr", ):
848 try:
849 os.unlink(tempname + "." + suffix)
850 except:
851 pass
853 self._removeaddfiles(tempname)
854 self.DoneRunTex = 1
856 def prolog(self):
857 self._run()
858 return self.aprolog
860 def bbox(self):
861 self._run()
862 return self.abbox
864 def write(self, file):
865 self._run()
866 file.writelines(self.epsdata)
868 def define(self, Cmd, *attrs):
869 if len(self.BoxCmds):
870 raise TexDefAfterBoxError
871 self.DoneRunTex = 0
872 self.attrcheck(attrs, (), (msghandler,))
873 self.DefCmds.append(_DefCmd(Cmd,
874 len(self.DefCmds)+ len(self.BoxCmds),
875 self._getstack(),
876 self.attrgetall(attrs, msghandler, self.defaultmsghandlers)))
878 def _insertcmd(self, Cmd, *attrs):
879 if not len(self.BoxCmds):
880 self._beginboxcmds()
881 self.DefCmdsStr = reduce(lambda x,y: x + y.DefCmd, self.DefCmds, "")
882 mystyle = self.attrget(attrs, style, style.text)
883 myfontsize = self.attrget(attrs, fontsize, fontsize.normalsize)
884 myvalign = self.attrget(attrs, valign, None)
885 mymsghandlers = self.attrgetall(attrs, msghandler, self.defaultmsghandlers)
886 MyCmd = _BoxCmd(self.DefCmdsStr, Cmd, mystyle, myfontsize, myvalign,
887 len(self.DefCmds) + len(self.BoxCmds), self._getstack(), mymsghandlers)
888 if MyCmd not in self.BoxCmds:
889 self.BoxCmds.append(MyCmd)
890 for Cmd in self.BoxCmds:
891 if Cmd == MyCmd:
892 UseCmd = Cmd # we could use MyCmd directly if we have just inserted it before
893 # (that's due to the side effect, that append doesn't make a copy of the element,
894 # but we ignore this here -- we don't want to depend on this side effect)
895 return UseCmd
897 def _text(self, x, y, Cmd, *attrs):
898 """print Cmd at (x, y) --- position parameters in postscipt points"""
900 self.DoneRunTex = 0
901 self.attrcheck(attrs, (style, fontsize, halign, valign, direction, color.color), (msghandler,))
902 myhalign = self.attrget(attrs, halign, halign.left)
903 mydirection = self.attrget(attrs, direction, direction.horizontal)
904 mycolor = self.attrget(attrs, color.color, color.gray.black)
905 self._insertcmd(Cmd, *attrs).Put(x * 72.27 / 72.0, y * 72.27 / 72.0, myhalign, mydirection, mycolor)
907 def text(self, x, y, Cmd, *attrs):
908 """print Cmd at (x, y)"""
910 self._text(unit.topt(x), unit.topt(y), Cmd, *attrs)
912 def textwd(self, Cmd, *attrs):
913 """get width of Cmd"""
915 self.DoneRunTex = 0
916 self.attrcheck(attrs, (style, fontsize, missextents), (msghandler,))
917 mymissextents = self.attrget(attrs, missextents, self.defaultmissextents)
918 return self._insertcmd(Cmd, *attrs).Extents((_extent.wd, ), mymissextents, self)[0]
920 def textht(self, Cmd, *attrs):
921 """get height of Cmd"""
923 self.DoneRunTex = 0
924 self.attrcheck(attrs, (style, fontsize, valign, missextents), (msghandler,))
925 mymissextents = self.attrget(attrs, missextents, self.defaultmissextents)
926 return self._insertcmd(Cmd, *attrs).Extents((_extent.ht, ), mymissextents, self)[0]
929 def textdp(self, Cmd, *attrs):
930 """get depth of Cmd"""
932 self.DoneRunTex = 0
933 self.attrcheck(attrs, (style, fontsize, valign, missextents), (msghandler,))
934 mymissextents = self.attrget(attrs, missextents, self.defaultmissextents)
935 return self._insertcmd(Cmd, *attrs).Extents((_extent.dp, ), mymissextents, self)[0]
938 class tex(_tex):
940 """tex class adds the specializations to _tex needed for tex"""
942 def __init__(self, lfs="10pt", **addargs):
943 _tex.__init__(self, **addargs)
944 # XXX other ways for creating font sizes?
945 try:
946 LocalLfsName = str(lfs) + ".lfs"
947 lfsdef = open(LocalLfsName, "r").read()
948 except IOError:
949 try:
950 try:
951 SysLfsName = os.path.join(sys.prefix, "share", "pyx", str(lfs) + ".lfs")
952 lfsdef = open(SysLfsName, "r").read()
953 except IOError:
954 SysLfsName = os.path.join(os.path.dirname(__file__), "lfs", str(lfs) + ".lfs")
955 lfsdef = open(SysLfsName, "r").read()
956 except IOError:
957 files = map(lambda x: x[:-4],
958 filter(lambda x: x[-4:] == ".lfs",
959 os.listdir(".") +
960 os.listdir(os.path.join(sys.prefix, "share", "pyx")),
961 os.listdir(os.path.join(os.path.dirname(__file__), "lfs"))))
962 raise IOError("file '%s.lfs' not found. Available latex font sizes:\n%s" % (lfs, files))
963 self.define(lfsdef)
964 self.define("\\newdimen\\linewidth%\n\\hsize0truein%\n\\vsize0truein%\n\\hoffset-1truein%\n\\voffset-1truein")
966 def _beginboxcmds(self):
967 pass
969 def _endcmd(self):
970 return "\\end\n"
972 def _executetex(self, tempname):
973 self._execute("tex %(t)s.tex > %(t)s.texout 2> %(t)s.texerr" % {"t": tempname})
976 class latex(_tex):
978 """latex class adds the specializations to _tex needed for latex"""
980 def __init__(self, docclass="article", docopt=None, auxfilename=None, **addargs):
981 _tex.__init__(self, **addargs)
982 self.auxfilename = auxfilename
983 if docopt:
984 self.define("\\documentclass[" + str(docopt) + "]{" + str(docclass) + "}")
985 else:
986 self.define("\\documentclass{" + str(docclass) + "}")
987 self.define("\\hsize0truein%\n\\vsize0truein%\n\\hoffset-1truein%\n\\voffset-1truein")
989 def _beginboxcmds(self):
990 self.define("\\begin{document}")
992 def _endcmd(self):
993 return "\\end{document}\n"
995 def _createaddfiles(self, tempname):
996 if self.auxfilename is not None:
997 writenew = 0
998 try:
999 os.rename(self.auxfilename + ".aux", tempname + ".aux")
1000 except OSError:
1001 writenew = 1
1002 else:
1003 writenew = 1
1004 if writenew:
1005 auxfile = open(tempname + ".aux", "w")
1006 auxfile.write("\\relax\n")
1007 auxfile.close()
1009 def _executetex(self, tempname):
1010 self._execute("latex %(t)s.tex > %(t)s.texout 2> %(t)s.texerr" % {"t": tempname})
1012 def _removeaddfiles(self, tempname):
1013 if self.auxfilename is not None:
1014 os.rename(tempname + ".aux", self.auxfilename + ".aux")
1015 else:
1016 os.unlink(tempname + ".aux")