1 # -*- coding: ISO-8859-1 -*-
4 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2003-2011 Michael Schindler <m-schindler@users.sourceforge.net>
6 # Copyright (C) 2002-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 import errno
, glob
, os
, threading
, Queue
, re
, tempfile
, atexit
, time
, warnings
25 import config
, siteconfig
, unit
, box
, canvas
, trafo
, version
, attr
, style
, filelocator
26 from pyx
.dvi
import dvifile
27 import bbox
as bboxmodule
32 have_subprocess
= False
34 have_subprocess
= True
36 class PyXTeXWarning(UserWarning): pass
37 warnings
.filterwarnings('always', category
=PyXTeXWarning
)
39 ###############################################################################
41 # - please don't get confused:
42 # - there is a texmessage (and a texmessageparsed) attribute within the
43 # texrunner; it contains TeX/LaTeX response from the last command execution
44 # - instances of classes derived from the class texmessage are used to
45 # parse the TeX/LaTeX response as it is stored in the texmessageparsed
46 # attribute of a texrunner instance
47 # - the multiple usage of the name texmessage might be removed in the future
48 # - texmessage instances should implement _Itexmessage
49 ###############################################################################
51 class TexResultError(RuntimeError):
52 """specialized texrunner exception class
53 - it is raised by texmessage instances, when a texmessage indicates an error
54 - it is raised by the texrunner itself, whenever there is a texmessage left
55 after all parsing of this message (by texmessage instances)
56 prints a detailed report about the problem
57 - the verbose level is controlled by texrunner.errordebug"""
59 def __init__(self
, description
, texrunner
):
60 if texrunner
.errordebug
>= 2:
61 self
.description
= ("%s\n" % description
+
62 "The expression passed to TeX was:\n"
63 " %s\n" % texrunner
.expr
.replace("\n", "\n ").rstrip() +
64 "The return message from TeX was:\n"
65 " %s\n" % texrunner
.texmessage
.replace("\n", "\n ").rstrip() +
66 "After parsing this message, the following was left:\n"
67 " %s" % texrunner
.texmessageparsed
.replace("\n", "\n ").rstrip())
68 elif texrunner
.errordebug
== 1:
69 firstlines
= texrunner
.texmessageparsed
.split("\n")
70 if len(firstlines
) > 5:
71 firstlines
= firstlines
[:5] + ["(cut after 5 lines, increase errordebug for more output)"]
72 self
.description
= ("%s\n" % description
+
73 "The expression passed to TeX was:\n"
74 " %s\n" % texrunner
.expr
.replace("\n", "\n ").rstrip() +
75 "After parsing the return message from TeX, the following was left:\n" +
76 reduce(lambda x
, y
: "%s %s\n" % (x
,y
), firstlines
, "").rstrip())
78 self
.description
= description
81 return self
.description
85 """validates/invalidates TeX/LaTeX response"""
87 def check(self
, texrunner
):
88 """check a Tex/LaTeX response and respond appropriate
89 - read the texrunners texmessageparsed attribute
90 - if there is an problem found, raise TexResultError
91 - remove any valid and identified TeX/LaTeX response
92 from the texrunners texmessageparsed attribute
93 -> finally, there should be nothing left in there,
94 otherwise it is interpreted as an error"""
97 class texmessage(attr
.attr
): pass
100 class _texmessagestart(texmessage
):
101 """validates TeX/LaTeX startup"""
103 __implements__
= _Itexmessage
105 startpattern
= re
.compile(r
"This is [-0-9a-zA-Z\s_]*TeX")
107 def check(self
, texrunner
):
108 # check for "This is e-TeX"
109 m
= self
.startpattern
.search(texrunner
.texmessageparsed
)
111 raise TexResultError("TeX startup failed", texrunner
)
112 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[m
.end():]
114 # check for \raiseerror -- just to be sure that communication works
116 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.split("*! Undefined control sequence.\n<*> \\raiseerror\n %\n", 1)[1]
117 except (IndexError, ValueError):
118 raise TexResultError("TeX scrollmode check failed", texrunner
)
121 class _texmessagenofile(texmessage
):
122 """allows for LaTeXs no-file warning"""
124 __implements__
= _Itexmessage
126 def __init__(self
, fileending
):
127 self
.fileending
= fileending
129 def check(self
, texrunner
):
131 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s.%s." % (texrunner
.texfilename
, self
.fileending
), 1)
132 texrunner
.texmessageparsed
= s1
+ s2
133 except (IndexError, ValueError):
135 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s%s%s.%s." % (os
.curdir
,
137 texrunner
.texfilename
,
139 texrunner
.texmessageparsed
= s1
+ s2
140 except (IndexError, ValueError):
144 class _texmessageinputmarker(texmessage
):
145 """validates the PyXInputMarker"""
147 __implements__
= _Itexmessage
149 def check(self
, texrunner
):
151 s1
, s2
= texrunner
.texmessageparsed
.split("PyXInputMarker:executeid=%s:" % texrunner
.executeid
, 1)
152 texrunner
.texmessageparsed
= s1
+ s2
153 except (IndexError, ValueError):
154 raise TexResultError("PyXInputMarker expected", texrunner
)
157 class _texmessagepyxbox(texmessage
):
158 """validates the PyXBox output"""
160 __implements__
= _Itexmessage
162 pattern
= re
.compile(r
"PyXBox:page=(?P<page>\d+),lt=-?\d*((\d\.?)|(\.?\d))\d*pt,rt=-?\d*((\d\.?)|(\.?\d))\d*pt,ht=-?\d*((\d\.?)|(\.?\d))\d*pt,dp=-?\d*((\d\.?)|(\.?\d))\d*pt:")
164 def check(self
, texrunner
):
165 m
= self
.pattern
.search(texrunner
.texmessageparsed
)
166 if m
and m
.group("page") == str(texrunner
.page
):
167 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
169 raise TexResultError("PyXBox expected", texrunner
)
172 class _texmessagepyxpageout(texmessage
):
173 """validates the dvi shipout message (writing a page to the dvi file)"""
175 __implements__
= _Itexmessage
177 def check(self
, texrunner
):
179 s1
, s2
= texrunner
.texmessageparsed
.split("[80.121.88.%s]" % texrunner
.page
, 1)
180 texrunner
.texmessageparsed
= s1
+ s2
181 except (IndexError, ValueError):
182 raise TexResultError("PyXPageOutMarker expected", texrunner
)
185 class _texmessageend(texmessage
):
186 """validates TeX/LaTeX finish"""
188 __implements__
= _Itexmessage
190 auxPattern
= re
.compile(r
"\(([^()]+\.aux|\"[^
\"]+\
.aux
\")\
)")
192 def check(self, texrunner):
193 m = self.auxPattern.search(texrunner.texmessageparsed)
195 texrunner.texmessageparsed = (texrunner.texmessageparsed[:m.start()] + texrunner.texmessageparsed[m.end():]).strip()
197 # check for "(see the transcript
file for additional information
)"
199 s1, s2 = texrunner.texmessageparsed.split("(see the transcript
file for additional information
)", 1)
200 texrunner.texmessageparsed = (s1 + s2).strip()
201 except (IndexError, ValueError):
204 # check for "Output written on
...dvi (1 page
, 220 bytes
)."
205 dvipattern = re.compile(r"Output written on
%s\
.dvi \
((?P
<page
>\d
+) pages?
, \d
+ bytes\
)\
." % texrunner.texfilename)
206 m = dvipattern.search(texrunner.texmessageparsed)
209 raise TexResultError("TeX dvifile messages expected
", texrunner)
210 if m.group("page
") != str(texrunner.page):
211 raise TexResultError("wrong number of pages reported
", texrunner)
212 texrunner.texmessageparsed = texrunner.texmessageparsed[:m.start()] + texrunner.texmessageparsed[m.end():]
215 s1, s2 = texrunner.texmessageparsed.split("No pages of output
.", 1)
216 texrunner.texmessageparsed = s1 + s2
217 except (IndexError, ValueError):
218 raise TexResultError("no dvifile expected
", texrunner)
220 # check for "Transcript written on
...log
."
222 s1, s2 = texrunner.texmessageparsed.split("Transcript written on
%s.log
." % texrunner.texfilename, 1)
223 texrunner.texmessageparsed = s1 + s2
224 except (IndexError, ValueError):
225 raise TexResultError("TeX logfile message expected
", texrunner)
228 class _texmessageemptylines(texmessage):
229 """validates "*-only
" (TeX/LaTeX input marker in interactive mode) and empty lines
230 also clear TeX interactive mode warning (Please type a command or say `\\end')
233 __implements__ = _Itexmessage
235 def check(self, texrunner):
236 texrunner.texmessageparsed = texrunner.texmessageparsed.replace(r"(Please
type a command
or say `\end
')", "")
237 texrunner.texmessageparsed = texrunner.texmessageparsed.replace(" ", "")
238 texrunner.texmessageparsed = texrunner.texmessageparsed.replace("*\n", "")
239 texrunner.texmessageparsed = texrunner.texmessageparsed.replace("\n", "")
242 class _texmessageload(texmessage):
243 """validates inclusion of arbitrary files
244 - the matched pattern is "(<filename> <arbitrary other stuff>)", where
245 <filename> is a readable file and other stuff can be anything
246 - If the filename is enclosed in double quotes, it may contain blank space.
247 - "(" and ")" must be used consistent (otherwise this validator just does nothing)
248 - this is not always wanted, but we just assume that file inclusion is fine"""
250 __implements__ = _Itexmessage
252 pattern = re.compile(r"\([\"]?(?P<filename>(?:(?<!\")[^()\s\n]+(?!\"))|[^\"\n]+)[\"]?(?P<additional>[^()]*)\)")
254 def baselevels(self, s, maxlevel=1, brackets="()", quotes='""'):
255 """strip parts of a string above a given bracket level
256 - return a modified (some parts might be removed) version of the string s
257 where all parts inside brackets with level higher than maxlevel are
259 - if brackets do not match (number of left and right brackets is wrong
260 or at some points there were more right brackets than left brackets)
261 just return the unmodified string
262 - a quoted string immediately followed after a bracket is left untouched
263 even if it contains quotes itself"""
268 for i, c in enumerate(s):
269 if quotes and level <= maxlevel:
270 if not inquote and c == quotes[0] and i and s[i-1] == brackets[0]:
272 elif inquote and c == quotes[1]:
279 if level > highestlevel:
281 if level <= maxlevel:
285 if level == 0 and highestlevel > 0:
288 def check(self, texrunner):
289 search = self.baselevels(texrunner.texmessageparsed)
291 if search is not None:
292 m = self.pattern.search(search)
294 filename = m.group("filename").replace("\n", "")
296 additional = m.group("additional")
299 if (os.access(filename, os.R_OK) or
300 len(additional) and additional[0] == "\n" and os.access(filename+additional.split()[0], os.R_OK)):
301 res.append(search[:m.start()])
303 res.append(search[:m.end()])
304 search = search[m.end():]
305 m = self.pattern.search(search)
308 texrunner.texmessageparsed = "".join(res)
311 class _texmessageloaddef(_texmessageload):
312 """validates the inclusion of font description files (fd-files)
313 - works like _texmessageload
314 - filename must end with .def or .fd
315 - further text is allowed"""
317 pattern = re.compile(r"\([\"]?(?P<filename>(?:(?:(?<!\")[^\(\)\s\n\"]+)|(?:(?<=\")[^\(\)\"]+))(\.fd|\.def))[\"]?[\s\n]*(?P<additional>[\(]?[^\(\)]*[\)]?)[\s\n]*\)")
319 def baselevels(self, s, **kwargs):
323 class _texmessagegraphicsload(_texmessageload):
324 """validates the inclusion of files as the graphics packages writes it
325 - works like _texmessageload, but using "<" and ">" as delimiters
326 - filename must end with .eps and no further text is allowed"""
328 pattern = re.compile(r"<(?P<filename>[^>]+.eps)>")
330 def baselevels(self, s, **kwargs):
334 class _texmessageignore(_texmessageload):
335 """validates any TeX/LaTeX response
336 - this might be used, when the expression is ok, but no suitable texmessage
338 - PLEASE: - consider writing suitable tex message parsers
339 - share your ideas/problems/solutions with others (use the PyX mailing lists)"""
341 __implements__ = _Itexmessage
343 def check(self, texrunner):
344 texrunner.texmessageparsed = ""
347 texmessage.start = _texmessagestart()
348 texmessage.noaux = _texmessagenofile("aux")
349 texmessage.nonav = _texmessagenofile("nav")
350 texmessage.end = _texmessageend()
351 texmessage.load = _texmessageload()
352 texmessage.loaddef = _texmessageloaddef()
353 texmessage.graphicsload = _texmessagegraphicsload()
354 texmessage.ignore = _texmessageignore()
357 texmessage.inputmarker = _texmessageinputmarker()
358 texmessage.pyxbox = _texmessagepyxbox()
359 texmessage.pyxpageout = _texmessagepyxpageout()
360 texmessage.emptylines = _texmessageemptylines()
363 class _texmessageallwarning(texmessage):
364 """validates a given pattern 'pattern
' as a warning 'warning
'"""
366 def check(self, texrunner):
367 if texrunner.texmessageparsed:
368 warnings.warn("ignoring all warnings:\n%s" % texrunner.texmessageparsed)
369 texrunner.texmessageparsed = ""
371 texmessage.allwarning = _texmessageallwarning()
374 class texmessagepattern(texmessage):
375 """validates a given pattern and issue a warning (when set)"""
377 def __init__(self, pattern, warning=None):
378 self.pattern = pattern
379 self.warning = warning
381 def check(self, texrunner):
382 m = self.pattern.search(texrunner.texmessageparsed)
384 texrunner.texmessageparsed = texrunner.texmessageparsed[:m.start()] + texrunner.texmessageparsed[m.end():]
386 warnings.warn("%s:\n%s" % (self.warning, m.string[m.start(): m.end()].rstrip()))
387 m = self.pattern.search(texrunner.texmessageparsed)
389 texmessage.fontwarning = texmessagepattern(re.compile(r"^LaTeX Font Warning: .*$(\n^\(Font\).*$)*", re.MULTILINE), "ignoring font warning")
390 texmessage.boxwarning = texmessagepattern(re.compile(r"^(Overfull|Underfull) \\[hv]box.*$(\n^..*$)*\n^$\n", re.MULTILINE), "ignoring overfull/underfull box warning")
391 texmessage.rerunwarning = texmessagepattern(re.compile(r"^(LaTeX Warning: Label\(s\) may have changed\. Rerun to get cross-references right\s*\.)$", re.MULTILINE), "ignoring rerun warning")
392 texmessage.packagewarning = texmessagepattern(re.compile(r"^package\s+(?P<packagename>\S+)\s+warning\s*:[^\n]+(?:\n\(?(?P=packagename)\)?[^\n]*)*", re.MULTILINE | re.IGNORECASE), "ignoring generic package warning")
393 texmessage.nobblwarning = texmessagepattern(re.compile(r"^[\s\*]*(No file .*\.bbl.)\s*", re.MULTILINE), "ignoring no-bbl warning")
397 ###############################################################################
399 ###############################################################################
401 _textattrspreamble = ""
404 "a textattr defines a apply method, which modifies a (La)TeX expression"
406 class _localattr: pass
408 _textattrspreamble += r"""\gdef\PyXFlushHAlign{0}%
410 \leftskip=0pt plus \PyXFlushHAlign fil%
411 \rightskip=0pt plus 1fil%
412 \advance\rightskip0pt plus -\PyXFlushHAlign fil%
418 \exhyphenpenalty=9999}%
421 class boxhalign(attr.exclusiveattr, textattr, _localattr):
423 def __init__(self, aboxhalign):
424 self.boxhalign = aboxhalign
425 attr.exclusiveattr.__init__(self, boxhalign)
427 def apply(self, expr):
428 return r"\gdef\PyXBoxHAlign{%.5f}%s" % (self.boxhalign, expr)
430 boxhalign.left = boxhalign(0)
431 boxhalign.center = boxhalign(0.5)
432 boxhalign.right = boxhalign(1)
433 # boxhalign.clear = attr.clearclass(boxhalign) # we can't defined a clearclass
for boxhalign since it can
't clear a halign's boxhalign
436 class flushhalign(attr
.exclusiveattr
, textattr
, _localattr
):
438 def __init__(self
, aflushhalign
):
439 self
.flushhalign
= aflushhalign
440 attr
.exclusiveattr
.__init
__(self
, flushhalign
)
442 def apply(self
, expr
):
443 return r
"\gdef\PyXFlushHAlign{%.5f}\PyXragged{}%s" % (self
.flushhalign
, expr
)
445 flushhalign
.left
= flushhalign(0)
446 flushhalign
.center
= flushhalign(0.5)
447 flushhalign
.right
= flushhalign(1)
448 # flushhalign.clear = attr.clearclass(flushhalign) # we can't defined a clearclass for flushhalign since it couldn't clear a halign's flushhalign
451 class halign(attr
.exclusiveattr
, textattr
, boxhalign
, flushhalign
, _localattr
):
453 def __init__(self
, aboxhalign
, aflushhalign
):
454 self
.boxhalign
= aboxhalign
455 self
.flushhalign
= aflushhalign
456 attr
.exclusiveattr
.__init
__(self
, halign
)
458 def apply(self
, expr
):
459 return r
"\gdef\PyXBoxHAlign{%.5f}\gdef\PyXFlushHAlign{%.5f}\PyXragged{}%s" % (self
.boxhalign
, self
.flushhalign
, expr
)
461 halign
.left
= halign(0, 0)
462 halign
.center
= halign(0.5, 0.5)
463 halign
.right
= halign(1, 1)
464 halign
.clear
= attr
.clearclass(halign
)
465 halign
.boxleft
= boxhalign
.left
466 halign
.boxcenter
= boxhalign
.center
467 halign
.boxright
= boxhalign
.right
468 halign
.flushleft
= halign
.raggedright
= flushhalign
.left
469 halign
.flushcenter
= halign
.raggedcenter
= flushhalign
.center
470 halign
.flushright
= halign
.raggedleft
= flushhalign
.right
473 class _mathmode(attr
.attr
, textattr
, _localattr
):
476 def apply(self
, expr
):
477 return r
"$\displaystyle{%s}$" % expr
479 mathmode
= _mathmode()
480 clearmathmode
= attr
.clearclass(_mathmode
)
483 class _phantom(attr
.attr
, textattr
, _localattr
):
486 def apply(self
, expr
):
487 return r
"\phantom{%s}" % expr
490 clearphantom
= attr
.clearclass(_phantom
)
493 _textattrspreamble
+= "\\newbox\\PyXBoxVBox%\n\\newdimen\\PyXDimenVBox%\n"
495 class parbox_pt(attr
.sortbeforeexclusiveattr
, textattr
):
501 def __init__(self
, width
, baseline
=top
):
502 self
.width
= width
* 72.27 / (unit
.scale
["x"] * 72)
503 self
.baseline
= baseline
504 attr
.sortbeforeexclusiveattr
.__init
__(self
, parbox_pt
, [_localattr
])
506 def apply(self
, expr
):
507 if self
.baseline
== self
.top
:
508 return r
"\linewidth=%.5ftruept\vtop{\hsize=\linewidth\textwidth=\linewidth{}%s}" % (self
.width
, expr
)
509 elif self
.baseline
== self
.middle
:
510 return r
"\linewidth=%.5ftruept\setbox\PyXBoxVBox=\hbox{{\vtop{\hsize=\linewidth\textwidth=\linewidth{}%s}}}\PyXDimenVBox=0.5\dp\PyXBoxVBox\setbox\PyXBoxVBox=\hbox{{\vbox{\hsize=\linewidth\textwidth=\linewidth{}%s}}}\advance\PyXDimenVBox by -0.5\dp\PyXBoxVBox\lower\PyXDimenVBox\box\PyXBoxVBox" % (self
.width
, expr
, expr
)
511 elif self
.baseline
== self
.bottom
:
512 return r
"\linewidth=%.5ftruept\vbox{\hsize=\linewidth\textwidth=\linewidth{}%s}" % (self
.width
, expr
)
514 RuntimeError("invalid baseline argument")
516 parbox_pt
.clear
= attr
.clearclass(parbox_pt
)
518 class parbox(parbox_pt
):
520 def __init__(self
, width
, **kwargs
):
521 parbox_pt
.__init
__(self
, unit
.topt(width
), **kwargs
)
523 parbox
.clear
= parbox_pt
.clear
526 _textattrspreamble
+= "\\newbox\\PyXBoxVAlign%\n\\newdimen\\PyXDimenVAlign%\n"
528 class valign(attr
.sortbeforeexclusiveattr
, textattr
):
530 def __init__(self
, avalign
):
531 self
.valign
= avalign
532 attr
.sortbeforeexclusiveattr
.__init
__(self
, valign
, [parbox_pt
, _localattr
])
534 def apply(self
, expr
):
535 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\PyXDimenVAlign=%.5f\ht\PyXBoxVAlign\advance\PyXDimenVAlign by -%.5f\dp\PyXBoxVAlign\lower\PyXDimenVAlign\box\PyXBoxVAlign" % (expr
, 1-self
.valign
, self
.valign
)
537 valign
.top
= valign(0)
538 valign
.middle
= valign(0.5)
539 valign
.bottom
= valign(1)
540 valign
.clear
= valign
.baseline
= attr
.clearclass(valign
)
543 _textattrspreamble
+= "\\newdimen\\PyXDimenVShift%\n"
545 class _vshift(attr
.sortbeforeattr
, textattr
):
548 attr
.sortbeforeattr
.__init
__(self
, [valign
, parbox_pt
, _localattr
])
550 def apply(self
, expr
):
551 return r
"%s\setbox0\hbox{{%s}}\lower\PyXDimenVShift\box0" % (self
.setheightexpr(), expr
)
553 class vshift(_vshift
):
554 "vertical down shift by a fraction of a character height"
556 def __init__(self
, lowerratio
, heightstr
="0"):
557 _vshift
.__init
__(self
)
558 self
.lowerratio
= lowerratio
559 self
.heightstr
= heightstr
561 def setheightexpr(self
):
562 return r
"\setbox0\hbox{{%s}}\PyXDimenVShift=%.5f\ht0" % (self
.heightstr
, self
.lowerratio
)
564 class _vshiftmathaxis(_vshift
):
565 "vertical down shift by the height of the math axis"
567 def setheightexpr(self
):
568 return r
"\setbox0\hbox{$\vcenter{\vrule width0pt}$}\PyXDimenVShift=\ht0"
571 vshift
.bottomzero
= vshift(0)
572 vshift
.middlezero
= vshift(0.5)
573 vshift
.topzero
= vshift(1)
574 vshift
.mathaxis
= _vshiftmathaxis()
575 vshift
.clear
= attr
.clearclass(_vshift
)
578 defaultsizelist
= ["normalsize", "large", "Large", "LARGE", "huge", "Huge",
579 None, "tiny", "scriptsize", "footnotesize", "small"]
581 class size(attr
.sortbeforeattr
, textattr
):
584 def __init__(self
, sizeindex
=None, sizename
=None, sizelist
=defaultsizelist
):
585 if (sizeindex
is None and sizename
is None) or (sizeindex
is not None and sizename
is not None):
586 raise RuntimeError("either specify sizeindex or sizename")
587 attr
.sortbeforeattr
.__init
__(self
, [_mathmode
, _vshift
])
588 if sizeindex
is not None:
589 if sizeindex
>= 0 and sizeindex
< sizelist
.index(None):
590 self
.size
= sizelist
[sizeindex
]
591 elif sizeindex
< 0 and sizeindex
+ len(sizelist
) > sizelist
.index(None):
592 self
.size
= sizelist
[sizeindex
]
594 raise IndexError("index out of sizelist range")
598 def apply(self
, expr
):
599 return r
"\%s{}%s" % (self
.size
, expr
)
602 size
.scriptsize
= size
.script
= size(-3)
603 size
.footnotesize
= size
.footnote
= size(-2)
604 size
.small
= size(-1)
605 size
.normalsize
= size
.normal
= size(0)
611 size
.clear
= attr
.clearclass(size
)
614 ###############################################################################
616 ###############################################################################
619 class _readpipe(threading
.Thread
):
620 """threaded reader of TeX/LaTeX output
621 - sets an event, when a specific string in the programs output is found
622 - sets an event, when the terminal ends"""
624 def __init__(self
, pipe
, expectqueue
, gotevent
, gotqueue
, quitevent
):
625 """initialize the reader
626 - pipe: file to be read from
627 - expectqueue: keeps the next InputMarker to be wait for
628 - gotevent: the "got InputMarker" event
629 - gotqueue: a queue containing the lines recieved from TeX/LaTeX
630 - quitevent: the "end of terminal" event"""
631 threading
.Thread
.__init
__(self
)
632 self
.setDaemon(1) # don't care if the output might not be finished (nevertheless, it shouldn't happen)
634 self
.expectqueue
= expectqueue
635 self
.gotevent
= gotevent
636 self
.gotqueue
= gotqueue
637 self
.quitevent
= quitevent
643 # catch interupted system call errors while reading
646 return self
.pipe
.readline()
648 if e
.errno
!= errno
.EINTR
:
650 read
= _read() # read, what comes in
652 self
.expect
= self
.expectqueue
.get_nowait() # read, what should be expected
656 # universal EOL handling (convert everything into unix like EOLs)
657 # XXX is this necessary on pipes?
658 read
= read
.replace("\r", "").replace("\n", "") + "\n"
659 self
.gotqueue
.put(read
) # report, whats read
660 if self
.expect
is not None and read
.find(self
.expect
) != -1:
661 self
.gotevent
.set() # raise the got event, when the output was expected (XXX: within a single line)
662 read
= _read() # read again
664 self
.expect
= self
.expectqueue
.get_nowait()
669 if self
.expect
is not None and self
.expect
.find("PyXInputMarker") != -1:
670 raise RuntimeError("TeX/LaTeX finished unexpectedly")
674 class textbox(box
.rect
, canvas
._canvas
):
675 """basically a box.rect, but it contains a text created by the texrunner
676 - texrunner._text and texrunner.text return such an object
677 - _textbox instances can be inserted into a canvas
678 - the output is contained in a page of the dvifile available thru the texrunner"""
679 # TODO: shouldn't all boxes become canvases? how about inserts then?
681 def __init__(self
, x
, y
, left
, right
, height
, depth
, finishdvi
, attrs
):
683 - finishdvi is a method to be called to get the dvicanvas
684 (e.g. the finishdvi calls the setdvicanvas method)
685 - attrs are fillstyles"""
688 self
.width
= left
+ right
691 self
.texttrafo
= trafo
.scale(unit
.scale
["x"]).translated(x
, y
)
692 box
.rect
.__init
__(self
, x
- left
, y
- depth
, left
+ right
, depth
+ height
, abscenter
= (left
, depth
))
693 canvas
._canvas
.__init
__(self
, attrs
)
694 self
.finishdvi
= finishdvi
695 self
.dvicanvas
= None
696 self
.insertdvicanvas
= 0
698 def transform(self
, *trafos
):
699 if self
.insertdvicanvas
:
700 raise RuntimeError("can't apply transformation after dvicanvas was inserted")
701 box
.rect
.transform(self
, *trafos
)
703 self
.texttrafo
= trafo
* self
.texttrafo
705 def setdvicanvas(self
, dvicanvas
):
706 if self
.dvicanvas
is not None:
707 raise RuntimeError("multiple call to setdvicanvas")
708 self
.dvicanvas
= dvicanvas
710 def ensuredvicanvas(self
):
711 if self
.dvicanvas
is None:
713 assert self
.dvicanvas
is not None, "finishdvi is broken"
714 if not self
.insertdvicanvas
:
715 self
.insert(self
.dvicanvas
, [self
.texttrafo
])
716 self
.insertdvicanvas
= 1
718 def marker(self
, marker
):
719 self
.ensuredvicanvas()
720 return self
.texttrafo
.apply(*self
.dvicanvas
.markers
[marker
])
722 def processPS(self
, file, writer
, context
, registry
, bbox
):
723 self
.ensuredvicanvas()
724 abbox
= bboxmodule
.empty()
725 canvas
._canvas
.processPS(self
, file, writer
, context
, registry
, abbox
)
726 bbox
+= box
.rect
.bbox(self
)
728 def processPDF(self
, file, writer
, context
, registry
, bbox
):
729 self
.ensuredvicanvas()
730 abbox
= bboxmodule
.empty()
731 canvas
._canvas
.processPDF(self
, file, writer
, context
, registry
, abbox
)
732 bbox
+= box
.rect
.bbox(self
)
735 def _cleantmp(texrunner
):
736 """get rid of temporary files
737 - function to be registered by atexit
738 - files contained in usefiles are kept"""
739 if texrunner
.texruns
: # cleanup while TeX is still running?
740 texrunner
.expectqueue
.put_nowait(None) # do not expect any output anymore
741 if texrunner
.mode
== "latex": # try to immediately quit from TeX or LaTeX
742 texrunner
.texinput
.write("\n\\catcode`\\@11\\relax\\@@end\n")
744 texrunner
.texinput
.write("\n\\end\n")
745 texrunner
.texinput
.close() # close the input queue and
746 if not texrunner
.waitforevent(texrunner
.quitevent
): # wait for finish of the output
747 return # didn't got a quit from TeX -> we can't do much more
748 texrunner
.texruns
= 0
749 texrunner
.texdone
= 1
750 for usefile
in texrunner
.usefiles
:
751 extpos
= usefile
.rfind(".")
753 os
.rename(texrunner
.texfilename
+ usefile
[extpos
:], usefile
)
756 for file in glob
.glob("%s.*" % texrunner
.texfilename
) + ["%sNotes.bib" % texrunner
.texfilename
]:
761 if texrunner
.texdebug
is not None:
763 texrunner
.texdebug
.close()
764 texrunner
.texdebug
= None
773 """TeX/LaTeX interface
774 - runs TeX/LaTeX expressions instantly
775 - checks TeX/LaTeX response
776 - the instance variable texmessage stores the last TeX
778 - the instance variable texmessageparsed stores a parsed
779 version of texmessage; it should be empty after
780 texmessage.check was called, otherwise a TexResultError
782 - the instance variable errordebug controls the verbose
783 level of TexResultError"""
785 defaulttexmessagesstart
= [texmessage
.start
]
786 defaulttexmessagesdocclass
= [texmessage
.load
]
787 defaulttexmessagesbegindoc
= [texmessage
.load
, texmessage
.noaux
]
788 defaulttexmessagesend
= [texmessage
.end
, texmessage
.fontwarning
, texmessage
.rerunwarning
, texmessage
.nobblwarning
]
789 defaulttexmessagesdefaultpreamble
= [texmessage
.load
]
790 defaulttexmessagesdefaultrun
= [texmessage
.loaddef
, texmessage
.graphicsload
,
791 texmessage
.fontwarning
, texmessage
.boxwarning
, texmessage
.packagewarning
]
793 def __init__(self
, mode
="tex",
798 waitfortex
=config
.getint("text", "waitfortex", 60),
799 showwaitfortex
=config
.getint("text", "showwaitfortex", 5),
800 texipc
=config
.getboolean("text", "texipc", 0),
806 texmessagesdocclass
=[],
807 texmessagesbegindoc
=[],
809 texmessagesdefaultpreamble
=[],
810 texmessagesdefaultrun
=[]):
812 if mode
!= "tex" and mode
!= "latex":
813 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
816 self
.docclass
= docclass
818 self
.usefiles
= usefiles
[:]
819 self
.waitfortex
= waitfortex
820 self
.showwaitfortex
= showwaitfortex
822 if texdebug
is not None:
823 if texdebug
[-4:] == ".tex":
824 self
.texdebug
= open(texdebug
, "w")
826 self
.texdebug
= open("%s.tex" % texdebug
, "w")
829 self
.dvidebug
= dvidebug
830 self
.errordebug
= errordebug
831 self
.pyxgraphics
= pyxgraphics
832 self
.texmessagesstart
= texmessagesstart
[:]
833 self
.texmessagesdocclass
= texmessagesdocclass
[:]
834 self
.texmessagesbegindoc
= texmessagesbegindoc
[:]
835 self
.texmessagesend
= texmessagesend
[:]
836 self
.texmessagesdefaultpreamble
= texmessagesdefaultpreamble
[:]
837 self
.texmessagesdefaultrun
= texmessagesdefaultrun
[:]
841 self
.preamblemode
= 1
845 self
.needdvitextboxes
= [] # when texipc-mode off
847 self
.textboxesincluded
= 0
848 savetempdir
= tempfile
.tempdir
849 tempfile
.tempdir
= os
.curdir
850 self
.texfilename
= os
.path
.basename(tempfile
.mktemp())
851 tempfile
.tempdir
= savetempdir
853 def waitforevent(self
, event
):
854 """waits verbosely with an timeout for an event
855 - observes an event while periodly while printing messages
856 - returns the status of the event (isSet)
857 - does not clear the event"""
858 if self
.showwaitfortex
:
861 while waited
< self
.waitfortex
and not hasevent
:
862 if self
.waitfortex
- waited
> self
.showwaitfortex
:
863 event
.wait(self
.showwaitfortex
)
864 waited
+= self
.showwaitfortex
866 event
.wait(self
.waitfortex
- waited
)
867 waited
+= self
.waitfortex
- waited
868 hasevent
= event
.isSet()
870 if waited
< self
.waitfortex
:
871 warnings
.warn("still waiting for %s after %i (of %i) seconds..." % (self
.mode
, waited
, self
.waitfortex
), PyXTeXWarning
)
873 warnings
.warn("the timeout of %i seconds expired and %s did not respond." % (waited
, self
.mode
), PyXTeXWarning
)
876 event
.wait(self
.waitfortex
)
879 def execute(self
, expr
, texmessages
):
880 """executes expr within TeX/LaTeX
881 - if self.texruns is not yet set, TeX/LaTeX is initialized,
882 self.texruns is set and self.preamblemode is set
883 - the method must not be called, when self.texdone is already set
884 - expr should be a string or None
885 - when expr is None, TeX/LaTeX is stopped, self.texruns is unset and
886 self.texdone becomes set
887 - when self.preamblemode is set, the expr is passed directly to TeX/LaTeX
888 - when self.preamblemode is unset, the expr is passed to \ProcessPyXBox
889 - texmessages is a list of texmessage instances"""
891 if self
.texdebug
is not None:
892 self
.texdebug
.write("%% PyX %s texdebug file\n" % version
.version
)
893 self
.texdebug
.write("%% mode: %s\n" % self
.mode
)
894 self
.texdebug
.write("%% date: %s\n" % time
.asctime(time
.localtime(time
.time())))
895 for usefile
in self
.usefiles
:
896 extpos
= usefile
.rfind(".")
898 os
.rename(usefile
, self
.texfilename
+ usefile
[extpos
:])
901 texfile
= open("%s.tex" % self
.texfilename
, "w") # start with filename -> creates dvi file with that name
902 texfile
.write("\\relax%\n")
909 p
= subprocess
.Popen("%s%s %s" % (self
.mode
, ipcflag
, self
.texfilename
), shell
=True, bufsize
=0,
910 stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.STDOUT
, close_fds
=True)
911 self
.texinput
, self
.texoutput
= p
.stdin
, p
.stdout
914 self
.texinput
, self
.texoutput
= os
.popen4("%s%s %s" % (self
.mode
, ipcflag
, self
.texfilename
), "t", 0)
916 # XXX: workaround for MS Windows (bufsize = 0 makes trouble!?)
917 self
.texinput
, self
.texoutput
= os
.popen4("%s%s %s" % (self
.mode
, ipcflag
, self
.texfilename
), "t")
918 atexit
.register(_cleantmp
, self
)
919 self
.expectqueue
= Queue
.Queue(1) # allow for a single entry only -> keeps the next InputMarker to be wait for
920 self
.gotevent
= threading
.Event() # keeps the got inputmarker event
921 self
.gotqueue
= Queue
.Queue(0) # allow arbitrary number of entries
922 self
.quitevent
= threading
.Event() # keeps for end of terminal event
923 self
.readoutput
= _readpipe(self
.texoutput
, self
.expectqueue
, self
.gotevent
, self
.gotqueue
, self
.quitevent
)
925 oldpreamblemode
= self
.preamblemode
926 self
.preamblemode
= 1
927 self
.readoutput
.start()
928 self
.execute("\\scrollmode\n\\raiseerror%\n" # switch to and check scrollmode
929 "\\def\\PyX{P\\kern-.3em\\lower.5ex\hbox{Y}\kern-.18em X}%\n" # just the PyX Logo
930 "\\gdef\\PyXBoxHAlign{0}%\n" # global PyXBoxHAlign (0.0-1.0) for the horizontal alignment, default to 0
931 "\\newbox\\PyXBox%\n" # PyXBox will contain the output
932 "\\newbox\\PyXBoxHAligned%\n" # PyXBox will contain the horizontal aligned output
933 "\\newdimen\\PyXDimenHAlignLT%\n" # PyXDimenHAlignLT/RT will contain the left/right extent
934 "\\newdimen\\PyXDimenHAlignRT%\n" +
935 _textattrspreamble
+ # insert preambles for textattrs macros
936 "\\long\\def\\ProcessPyXBox#1#2{%\n" # the ProcessPyXBox definition (#1 is expr, #2 is page number)
937 "\\setbox\\PyXBox=\\hbox{{#1}}%\n" # push expression into PyXBox
938 "\\PyXDimenHAlignLT=\\PyXBoxHAlign\\wd\\PyXBox%\n" # calculate the left/right extent
939 "\\PyXDimenHAlignRT=\\wd\\PyXBox%\n"
940 "\\advance\\PyXDimenHAlignRT by -\\PyXDimenHAlignLT%\n"
941 "\\gdef\\PyXBoxHAlign{0}%\n" # reset the PyXBoxHAlign to the default 0
942 "\\immediate\\write16{PyXBox:page=#2," # write page and extents of this box to stdout
943 "lt=\\the\\PyXDimenHAlignLT,"
944 "rt=\\the\\PyXDimenHAlignRT,"
945 "ht=\\the\\ht\\PyXBox,"
946 "dp=\\the\\dp\\PyXBox:}%\n"
947 "\\setbox\\PyXBoxHAligned=\\hbox{\\kern-\\PyXDimenHAlignLT\\box\\PyXBox}%\n" # align horizontally
948 "\\ht\\PyXBoxHAligned0pt%\n" # baseline alignment (hight to zero)
949 "{\\count0=80\\count1=121\\count2=88\\count3=#2\\shipout\\box\\PyXBoxHAligned}}%\n" # shipout PyXBox to Page 80.121.88.<page number>
950 "\\def\\PyXInput#1{\\immediate\\write16{PyXInputMarker:executeid=#1:}}%\n" # write PyXInputMarker to stdout
951 "\\def\\PyXMarker#1{\\hskip0pt\\special{PyX:marker #1}}%", # write PyXMarker special into the dvi-file
952 self
.defaulttexmessagesstart
+ self
.texmessagesstart
)
953 os
.remove("%s.tex" % self
.texfilename
)
954 if self
.mode
== "tex":
956 if not self
.lfs
.endswith(".lfs"):
957 self
.lfs
= "%s.lfs" % self
.lfs
959 lfsfile
= filelocator
.open(self
.lfs
, "lfs", "r")
960 lfsdef
= lfsfile
.read()
962 self
.execute(lfsdef
, [])
963 self
.execute("\\normalsize%\n", [])
964 self
.execute("\\newdimen\\linewidth\\newdimen\\textwidth%\n", [])
965 elif self
.mode
== "latex":
967 pyxdef
= os
.path
.join(siteconfig
.sharedir
, "pyx.def")
969 open(pyxdef
, "r").close()
971 IOError("file 'pyx.def' is not available or not readable. Check your installation or turn off the pyxgraphics option.")
972 pyxdef
= os
.path
.abspath(pyxdef
).replace(os
.sep
, "/")
973 self
.execute("\\makeatletter%\n"
974 "\\let\\saveProcessOptions=\\ProcessOptions%\n"
975 "\\def\\ProcessOptions{%\n"
976 "\\def\\Gin@driver{" + pyxdef
+ "}%\n"
977 "\\def\\c@lor@namefile{dvipsnam.def}%\n"
978 "\\saveProcessOptions}%\n"
981 if self
.docopt
is not None:
982 self
.execute("\\documentclass[%s]{%s}" % (self
.docopt
, self
.docclass
),
983 self
.defaulttexmessagesdocclass
+ self
.texmessagesdocclass
)
985 self
.execute("\\documentclass{%s}" % self
.docclass
,
986 self
.defaulttexmessagesdocclass
+ self
.texmessagesdocclass
)
987 self
.preamblemode
= oldpreamblemode
989 if expr
is not None: # TeX/LaTeX should process expr
990 self
.expectqueue
.put_nowait("PyXInputMarker:executeid=%i:" % self
.executeid
)
991 if self
.preamblemode
:
992 self
.expr
= ("%s%%\n" % expr
+
993 "\\PyXInput{%i}%%\n" % self
.executeid
)
996 self
.expr
= ("\\ProcessPyXBox{%s%%\n}{%i}%%\n" % (expr
, self
.page
) +
997 "\\PyXInput{%i}%%\n" % self
.executeid
)
998 else: # TeX/LaTeX should be finished
999 self
.expectqueue
.put_nowait("Transcript written on %s.log" % self
.texfilename
)
1000 if self
.mode
== "latex":
1001 self
.expr
= "\\end{document}%\n"
1003 self
.expr
= "\\end%\n"
1004 if self
.texdebug
is not None:
1005 self
.texdebug
.write(self
.expr
)
1006 self
.texinput
.write(self
.expr
)
1007 gotevent
= self
.waitforevent(self
.gotevent
)
1008 self
.gotevent
.clear()
1009 if expr
is None and gotevent
: # TeX/LaTeX should have finished
1012 self
.texinput
.close() # close the input queue and
1013 gotevent
= self
.waitforevent(self
.quitevent
) # wait for finish of the output
1015 self
.texmessage
= ""
1017 self
.texmessage
+= self
.gotqueue
.get_nowait()
1020 self
.texmessage
= self
.texmessage
.replace("\r\n", "\n").replace("\r", "\n")
1021 self
.texmessageparsed
= self
.texmessage
1023 if expr
is not None:
1024 texmessage
.inputmarker
.check(self
)
1025 if not self
.preamblemode
:
1026 texmessage
.pyxbox
.check(self
)
1027 texmessage
.pyxpageout
.check(self
)
1028 texmessages
= attr
.mergeattrs(texmessages
)
1029 for t
in texmessages
:
1031 keeptexmessageparsed
= self
.texmessageparsed
1032 texmessage
.emptylines
.check(self
)
1033 if len(self
.texmessageparsed
):
1034 self
.texmessageparsed
= keeptexmessageparsed
1035 raise TexResultError("unhandled TeX response (might be an error)", self
)
1037 raise TexResultError("TeX didn't respond as expected within the timeout period (%i seconds)." % self
.waitfortex
, self
)
1039 def finishdvi(self
, ignoretail
=0):
1040 """finish TeX/LaTeX and read the dvifile
1041 - this method ensures that all textboxes can access their
1043 self
.execute(None, self
.defaulttexmessagesend
+ self
.texmessagesend
)
1044 dvifilename
= "%s.dvi" % self
.texfilename
1046 self
.dvifile
= dvifile
.DVIfile(dvifilename
, debug
=self
.dvidebug
)
1048 for box
in self
.needdvitextboxes
:
1049 box
.setdvicanvas(self
.dvifile
.readpage([ord("P"), ord("y"), ord("X"), page
, 0, 0, 0, 0, 0, 0], fontmap
=box
.fontmap
))
1051 if not ignoretail
and self
.dvifile
.readpage(None) is not None:
1052 raise RuntimeError("end of dvifile expected")
1054 self
.needdvitextboxes
= []
1056 def reset(self
, reinit
=0):
1057 "resets the tex runner to its initial state (upto its record to old dvi file(s))"
1060 if self
.texdebug
is not None:
1061 self
.texdebug
.write("%s\n%% preparing restart of %s\n" % ("%"*80, self
.mode
))
1066 self
.preamblemode
= 1
1067 for expr
, texmessages
in self
.preambles
:
1068 self
.execute(expr
, texmessages
)
1069 if self
.mode
== "latex":
1070 self
.execute("\\begin{document}", self
.defaulttexmessagesbegindoc
+ self
.texmessagesbegindoc
)
1071 self
.preamblemode
= 0
1074 self
.preamblemode
= 1
1076 def set(self
, mode
=_unset
,
1082 showwaitfortex
=_unset
,
1088 texmessagesstart
=_unset
,
1089 texmessagesdocclass
=_unset
,
1090 texmessagesbegindoc
=_unset
,
1091 texmessagesend
=_unset
,
1092 texmessagesdefaultpreamble
=_unset
,
1093 texmessagesdefaultrun
=_unset
):
1094 """provide a set command for TeX/LaTeX settings
1095 - TeX/LaTeX must not yet been started
1096 - especially needed for the defaultrunner, where no access to
1097 the constructor is available"""
1099 raise RuntimeError("set not allowed -- TeX/LaTeX already started")
1100 if mode
is not _unset
:
1102 if mode
!= "tex" and mode
!= "latex":
1103 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
1105 if lfs
is not _unset
:
1107 if docclass
is not _unset
:
1108 self
.docclass
= docclass
1109 if docopt
is not _unset
:
1110 self
.docopt
= docopt
1111 if usefiles
is not _unset
:
1112 self
.usefiles
= usefiles
1113 if waitfortex
is not _unset
:
1114 self
.waitfortex
= waitfortex
1115 if showwaitfortex
is not _unset
:
1116 self
.showwaitfortex
= showwaitfortex
1117 if texipc
is not _unset
:
1118 self
.texipc
= texipc
1119 if texdebug
is not _unset
:
1120 if self
.texdebug
is not None:
1121 self
.texdebug
.close()
1122 if texdebug
[-4:] == ".tex":
1123 self
.texdebug
= open(texdebug
, "w")
1125 self
.texdebug
= open("%s.tex" % texdebug
, "w")
1126 if dvidebug
is not _unset
:
1127 self
.dvidebug
= dvidebug
1128 if errordebug
is not _unset
:
1129 self
.errordebug
= errordebug
1130 if pyxgraphics
is not _unset
:
1131 self
.pyxgraphics
= pyxgraphics
1132 if errordebug
is not _unset
:
1133 self
.errordebug
= errordebug
1134 if texmessagesstart
is not _unset
:
1135 self
.texmessagesstart
= texmessagesstart
1136 if texmessagesdocclass
is not _unset
:
1137 self
.texmessagesdocclass
= texmessagesdocclass
1138 if texmessagesbegindoc
is not _unset
:
1139 self
.texmessagesbegindoc
= texmessagesbegindoc
1140 if texmessagesend
is not _unset
:
1141 self
.texmessagesend
= texmessagesend
1142 if texmessagesdefaultpreamble
is not _unset
:
1143 self
.texmessagesdefaultpreamble
= texmessagesdefaultpreamble
1144 if texmessagesdefaultrun
is not _unset
:
1145 self
.texmessagesdefaultrun
= texmessagesdefaultrun
1147 def preamble(self
, expr
, texmessages
=[]):
1148 r
"""put something into the TeX/LaTeX preamble
1149 - in LaTeX, this is done before the \begin{document}
1150 (you might use \AtBeginDocument, when you're in need for)
1151 - it is not allowed to call preamble after calling the
1152 text method for the first time (for LaTeX this is needed
1153 due to \begin{document}; in TeX it is forced for compatibility
1154 (you should be able to switch from TeX to LaTeX, if you want,
1155 without breaking something)
1156 - preamble expressions must not create any dvi output
1157 - args might contain texmessage instances"""
1158 if self
.texdone
or not self
.preamblemode
:
1159 raise RuntimeError("preamble calls disabled due to previous text calls")
1160 texmessages
= self
.defaulttexmessagesdefaultpreamble
+ self
.texmessagesdefaultpreamble
+ texmessages
1161 self
.execute(expr
, texmessages
)
1162 self
.preambles
.append((expr
, texmessages
))
1164 PyXBoxPattern
= re
.compile(r
"PyXBox:page=(?P<page>\d+),lt=(?P<lt>-?\d*((\d\.?)|(\.?\d))\d*)pt,rt=(?P<rt>-?\d*((\d\.?)|(\.?\d))\d*)pt,ht=(?P<ht>-?\d*((\d\.?)|(\.?\d))\d*)pt,dp=(?P<dp>-?\d*((\d\.?)|(\.?\d))\d*)pt:")
1166 def text(self
, x
, y
, expr
, textattrs
=[], texmessages
=[], fontmap
=None):
1167 """create text by passing expr to TeX/LaTeX
1168 - returns a textbox containing the result from running expr thru TeX/LaTeX
1169 - the box center is set to x, y
1170 - *args may contain attr parameters, namely:
1171 - textattr instances
1172 - texmessage instances
1173 - trafo._trafo instances
1174 - style.fillstyle instances"""
1176 raise ValueError("None expression is invalid")
1178 self
.reset(reinit
=1)
1180 if self
.preamblemode
:
1181 if self
.mode
== "latex":
1182 self
.execute("\\begin{document}", self
.defaulttexmessagesbegindoc
+ self
.texmessagesbegindoc
)
1183 self
.preamblemode
= 0
1185 textattrs
= attr
.mergeattrs(textattrs
) # perform cleans
1186 attr
.checkattrs(textattrs
, [textattr
, trafo
.trafo_pt
, style
.fillstyle
])
1187 trafos
= attr
.getattrs(textattrs
, [trafo
.trafo_pt
])
1188 fillstyles
= attr
.getattrs(textattrs
, [style
.fillstyle
])
1189 textattrs
= attr
.getattrs(textattrs
, [textattr
])
1190 # reverse loop over the merged textattrs (last is applied first)
1191 lentextattrs
= len(textattrs
)
1192 for i
in range(lentextattrs
):
1193 expr
= textattrs
[lentextattrs
-1-i
].apply(expr
)
1195 self
.execute(expr
, self
.defaulttexmessagesdefaultrun
+ self
.texmessagesdefaultrun
+ texmessages
)
1196 except TexResultError
, e
:
1197 warnings
.warn("We try to finish the dvi due to an unhandled tex error", PyXTeXWarning
)
1199 self
.finishdvi(ignoretail
=1)
1200 except TexResultError
:
1205 self
.dvifile
= dvifile
.DVIfile("%s.dvi" % self
.texfilename
, debug
=self
.dvidebug
)
1206 match
= self
.PyXBoxPattern
.search(self
.texmessage
)
1207 if not match
or int(match
.group("page")) != self
.page
:
1208 raise TexResultError("box extents not found", self
)
1209 left
, right
, height
, depth
= [float(xxx
)*72/72.27*unit
.x_pt
for xxx
in match
.group("lt", "rt", "ht", "dp")]
1210 box
= textbox(x
, y
, left
, right
, height
, depth
, self
.finishdvi
, fillstyles
)
1212 box
.reltransform(t
) # TODO: should trafos really use reltransform???
1213 # this is quite different from what we do elsewhere!!!
1214 # see https://sourceforge.net/mailarchive/forum.php?thread_id=9137692&forum_id=23700
1216 box
.setdvicanvas(self
.dvifile
.readpage([ord("P"), ord("y"), ord("X"), self
.page
, 0, 0, 0, 0, 0, 0], fontmap
=fontmap
))
1218 box
.fontmap
= fontmap
1219 self
.needdvitextboxes
.append(box
)
1222 def text_pt(self
, x
, y
, expr
, *args
, **kwargs
):
1223 return self
.text(x
* unit
.t_pt
, y
* unit
.t_pt
, expr
, *args
, **kwargs
)
1225 PyXVariableBoxPattern
= re
.compile(r
"PyXVariableBox:page=(?P<page>\d+),par=(?P<par>\d+),prevgraf=(?P<prevgraf>\d+):")
1227 def textboxes(self
, text
, pageshapes
):
1228 # this is some experimental code to put text into several boxes
1229 # while the bounding shape changes from box to box (rectangles only)
1230 # first we load sev.tex
1231 if not self
.textboxesincluded
:
1232 self
.execute(r
"\input textboxes.tex", [texmessage
.load
])
1233 self
.textboxesincluded
= 1
1234 # define page shapes
1235 pageshapes_str
= "\\hsize=%.5ftruept%%\n\\vsize=%.5ftruept%%\n" % (72.27/72*unit
.topt(pageshapes
[0][0]), 72.27/72*unit
.topt(pageshapes
[0][1]))
1236 pageshapes_str
+= "\\lohsizes={%\n"
1237 for hsize
, vsize
in pageshapes
[1:]:
1238 pageshapes_str
+= "{\\global\\hsize=%.5ftruept}%%\n" % (72.27/72*unit
.topt(hsize
))
1239 pageshapes_str
+= "{\\relax}%\n}%\n"
1240 pageshapes_str
+= "\\lovsizes={%\n"
1241 for hsize
, vsize
in pageshapes
[1:]:
1242 pageshapes_str
+= "{\\global\\vsize=%.5ftruept}%%\n" % (72.27/72*unit
.topt(vsize
))
1243 pageshapes_str
+= "{\\relax}%\n}%\n"
1249 self
.execute(pageshapes_str
, [])
1250 parnos_str
= "}{".join(parnos
)
1252 parnos_str
= "{%s}" % parnos_str
1253 parnos_str
= "\\parnos={%s{\\relax}}%%\n" % parnos_str
1254 self
.execute(parnos_str
, [])
1255 parshapes_str
= "\\parshapes={%%\n%s%%\n{\\relax}%%\n}%%\n" % "%\n".join(parshapes
)
1256 self
.execute(parshapes_str
, [])
1257 self
.execute("\\global\\count0=1%%\n"
1258 "\\global\\parno=0%%\n"
1259 "\\global\\myprevgraf=0%%\n"
1260 "\\global\\showprevgraf=0%%\n"
1261 "\\global\\outputtype=0%%\n"
1262 "\\global\\leastcost=10000000%%\n"
1264 "\\vfill\\supereject%%\n" % text
, [texmessage
.ignore
])
1266 if self
.dvifile
is None:
1267 self
.dvifile
= dvifile
.DVIfile("%s.dvi" % self
.texfilename
, debug
=self
.dvidebug
)
1269 raise RuntimeError("textboxes currently needs texipc")
1272 lastparshapes
= parshapes
1275 lastpar
= prevgraf
= -1
1276 m
= self
.PyXVariableBoxPattern
.search(self
.texmessage
)
1279 page
= int(m
.group("page"))
1280 assert page
== pages
1281 par
= int(m
.group("par"))
1282 prevgraf
= int(m
.group("prevgraf"))
1283 if page
<= len(pageshapes
):
1284 width
= 72.27/72*unit
.topt(pageshapes
[page
-1][0])
1286 width
= 72.27/72*unit
.topt(pageshapes
[-1][0])
1287 if page
< len(pageshapes
):
1288 nextwidth
= 72.27/72*unit
.topt(pageshapes
[page
][0])
1290 nextwidth
= 72.27/72*unit
.topt(pageshapes
[-1][0])
1293 # a new paragraph is to be broken
1294 parnos
.append(str(par
))
1295 parshape
= " 0pt ".join(["%.5ftruept" % width
for i
in range(prevgraf
)])
1297 parshape
= " 0pt " + parshape
1298 parshapes
.append("{\\parshape %i%s 0pt %.5ftruept}" % (prevgraf
+ 1, parshape
, nextwidth
))
1299 elif prevgraf
== lastprevgraf
:
1302 # we have to append the breaking of the previous paragraph
1303 oldparshape
= " ".join(parshapes
[-1].split(' ')[2:2+2*lastprevgraf
])
1304 oldparshape
= oldparshape
.split('}')[0]
1306 oldparshape
= " " + oldparshape
1307 parshape
= " 0pt ".join(["%.5ftruept" % width
for i
in range(prevgraf
- lastprevgraf
)])
1309 parshape
= " 0pt " + parshape
1312 parshapes
[-1] = "{\\parshape %i%s%s 0pt %.5ftruept}" % (prevgraf
+ 1, oldparshape
, parshape
, nextwidth
)
1314 lastprevgraf
= prevgraf
1316 m
= self
.PyXVariableBoxPattern
.search(self
.texmessage
, nextpos
)
1318 for i
in range(pages
):
1319 result
.append(self
.dvifile
.readpage([i
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]))
1320 if parnos
== lastparnos
and parshapes
== lastparshapes
:
1324 raise TexResultError("Too many loops in textboxes ", texrunner
)
1327 # the module provides an default texrunner and methods for direct access
1328 defaulttexrunner
= texrunner()
1329 reset
= defaulttexrunner
.reset
1330 set = defaulttexrunner
.set
1331 preamble
= defaulttexrunner
.preamble
1332 text
= defaulttexrunner
.text
1333 text_pt
= defaulttexrunner
.text_pt
1335 def escapestring(s
, replace
={" ": "~",
1347 "\\": "{$\setminus$}",
1349 "escape all ascii characters such that they are printable by TeX/LaTeX"
1352 if not 32 <= ord(s
[i
]) < 127:
1353 raise ValueError("escapestring function handles ascii strings only")
1360 s
= s
[:i
] + r
+ s
[i
+1:]