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