1 # -*- encoding: utf-8 -*-
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-2011 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
, unit
, box
, canvas
, trafo
, version
, attr
, style
, filelocator
, pycompat
26 from pyx
.dvi
import dvifile
27 import bbox
as bboxmodule
29 class PyXTeXWarning(UserWarning): pass
30 warnings
.filterwarnings('always', category
=PyXTeXWarning
)
32 ###############################################################################
34 # - please don't get confused:
35 # - there is a texmessage (and a texmessageparsed) attribute within the
36 # texrunner; it contains TeX/LaTeX response from the last command execution
37 # - instances of classes derived from the class texmessage are used to
38 # parse the TeX/LaTeX response as it is stored in the texmessageparsed
39 # attribute of a texrunner instance
40 # - the multiple usage of the name texmessage might be removed in the future
41 # - texmessage instances should implement _Itexmessage
42 ###############################################################################
44 class TexResultError(RuntimeError):
45 """specialized texrunner exception class
46 - it is raised by texmessage instances, when a texmessage indicates an error
47 - it is raised by the texrunner itself, whenever there is a texmessage left
48 after all parsing of this message (by texmessage instances)
49 prints a detailed report about the problem
50 - the verbose level is controlled by texrunner.errordebug"""
52 def __init__(self
, description
, texrunner
):
53 if texrunner
.errordebug
>= 2:
54 self
.description
= ("%s\n" % description
+
55 "The expression passed to TeX was:\n"
56 " %s\n" % texrunner
.expr
.replace("\n", "\n ").rstrip() +
57 "The return message from TeX was:\n"
58 " %s\n" % texrunner
.texmessage
.replace("\n", "\n ").rstrip() +
59 "After parsing this message, the following was left:\n"
60 " %s" % texrunner
.texmessageparsed
.replace("\n", "\n ").rstrip())
61 elif texrunner
.errordebug
== 1:
62 firstlines
= texrunner
.texmessageparsed
.split("\n")
63 if len(firstlines
) > 5:
64 firstlines
= firstlines
[:5] + ["(cut after 5 lines, increase errordebug for more output)"]
65 self
.description
= ("%s\n" % description
+
66 "The expression passed to TeX was:\n"
67 " %s\n" % texrunner
.expr
.replace("\n", "\n ").rstrip() +
68 "After parsing the return message from TeX, the following was left:\n" +
69 reduce(lambda x
, y
: "%s %s\n" % (x
,y
), firstlines
, "").rstrip())
71 self
.description
= description
74 return self
.description
78 """validates/invalidates TeX/LaTeX response"""
80 def check(self
, texrunner
):
81 """check a Tex/LaTeX response and respond appropriate
82 - read the texrunners texmessageparsed attribute
83 - if there is an problem found, raise TexResultError
84 - remove any valid and identified TeX/LaTeX response
85 from the texrunners texmessageparsed attribute
86 -> finally, there should be nothing left in there,
87 otherwise it is interpreted as an error"""
90 class texmessage(attr
.attr
): pass
93 class _texmessagestart(texmessage
):
94 """validates TeX/LaTeX startup"""
96 __implements__
= _Itexmessage
98 startpattern
= re
.compile(r
"This is [-0-9a-zA-Z\s_]*TeX")
100 def check(self
, texrunner
):
101 # check for "This is e-TeX"
102 m
= self
.startpattern
.search(texrunner
.texmessageparsed
)
104 raise TexResultError("TeX startup failed", texrunner
)
105 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[m
.end():]
107 # check for \raiseerror -- just to be sure that communication works
109 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.split("*! Undefined control sequence.\n<*> \\raiseerror\n %\n", 1)[1]
110 except (IndexError, ValueError):
111 raise TexResultError("TeX scrollmode check failed", texrunner
)
114 class _texmessagenofile(texmessage
):
115 """allows for LaTeXs no-file warning"""
117 __implements__
= _Itexmessage
119 def __init__(self
, fileending
):
120 self
.fileending
= fileending
122 def check(self
, texrunner
):
124 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s.%s." % (texrunner
.texfilename
, self
.fileending
), 1)
125 texrunner
.texmessageparsed
= s1
+ s2
126 except (IndexError, ValueError):
128 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s%s%s.%s." % (os
.curdir
,
130 texrunner
.texfilename
,
132 texrunner
.texmessageparsed
= s1
+ s2
133 except (IndexError, ValueError):
137 class _texmessageinputmarker(texmessage
):
138 """validates the PyXInputMarker"""
140 __implements__
= _Itexmessage
142 def check(self
, texrunner
):
144 s1
, s2
= texrunner
.texmessageparsed
.split("PyXInputMarker:executeid=%s:" % texrunner
.executeid
, 1)
145 texrunner
.texmessageparsed
= s1
+ s2
146 except (IndexError, ValueError):
147 raise TexResultError("PyXInputMarker expected", texrunner
)
150 class _texmessagepyxbox(texmessage
):
151 """validates the PyXBox output"""
153 __implements__
= _Itexmessage
155 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:")
157 def check(self
, texrunner
):
158 m
= self
.pattern
.search(texrunner
.texmessageparsed
)
159 if m
and m
.group("page") == str(texrunner
.page
):
160 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
162 raise TexResultError("PyXBox expected", texrunner
)
165 class _texmessagepyxpageout(texmessage
):
166 """validates the dvi shipout message (writing a page to the dvi file)"""
168 __implements__
= _Itexmessage
170 def check(self
, texrunner
):
172 s1
, s2
= texrunner
.texmessageparsed
.split("[80.121.88.%s]" % texrunner
.page
, 1)
173 texrunner
.texmessageparsed
= s1
+ s2
174 except (IndexError, ValueError):
175 raise TexResultError("PyXPageOutMarker expected", texrunner
)
178 class _texmessageend(texmessage
):
179 """validates TeX/LaTeX finish"""
181 __implements__
= _Itexmessage
183 auxPattern
= re
.compile(r
"\(([^()]+\.aux|\"[^
\"]+\
.aux
\")\
)")
185 def check(self, texrunner):
186 m = self.auxPattern.search(texrunner.texmessageparsed)
188 texrunner.texmessageparsed = (texrunner.texmessageparsed[:m.start()] + texrunner.texmessageparsed[m.end():]).strip()
190 # check for "(see the transcript
file for additional information
)"
192 s1, s2 = texrunner.texmessageparsed.split("(see the transcript
file for additional information
)", 1)
193 texrunner.texmessageparsed = (s1 + s2).strip()
194 except (IndexError, ValueError):
197 # check for "Output written on
...dvi (1 page
, 220 bytes
)."
198 dvipattern = re.compile(r"Output written on
%s\
.dvi \
((?P
<page
>\d
+) pages?
, \d
+ bytes\
)\
." % texrunner.texfilename)
199 m = dvipattern.search(texrunner.texmessageparsed)
202 raise TexResultError("TeX dvifile messages expected
", texrunner)
203 if m.group("page
") != str(texrunner.page):
204 raise TexResultError("wrong number of pages reported
", texrunner)
205 texrunner.texmessageparsed = texrunner.texmessageparsed[:m.start()] + texrunner.texmessageparsed[m.end():]
208 s1, s2 = texrunner.texmessageparsed.split("No pages of output
.", 1)
209 texrunner.texmessageparsed = s1 + s2
210 except (IndexError, ValueError):
211 raise TexResultError("no dvifile expected
", texrunner)
213 # check for "Transcript written on
...log
."
215 s1, s2 = texrunner.texmessageparsed.split("Transcript written on
%s.log
." % texrunner.texfilename, 1)
216 texrunner.texmessageparsed = s1 + s2
217 except (IndexError, ValueError):
218 raise TexResultError("TeX logfile message expected
", texrunner)
221 class _texmessageemptylines(texmessage):
222 """validates "*-only
" (TeX/LaTeX input marker in interactive mode) and empty lines
223 also clear TeX interactive mode warning (Please type a command or say `\\end')
226 __implements__ = _Itexmessage
228 def check(self, texrunner):
229 texrunner.texmessageparsed = texrunner.texmessageparsed.replace(r"(Please
type a command
or say `\end
')", "")
230 texrunner.texmessageparsed = texrunner.texmessageparsed.replace(" ", "")
231 texrunner.texmessageparsed = texrunner.texmessageparsed.replace("*\n", "")
232 texrunner.texmessageparsed = texrunner.texmessageparsed.replace("\n", "")
235 class _texmessageload(texmessage):
236 """validates inclusion of arbitrary files
237 - the matched pattern is "(<filename> <arbitrary other stuff>)", where
238 <filename> is a readable file and other stuff can be anything
239 - If the filename is enclosed in double quotes, it may contain blank space.
240 - "(" and ")" must be used consistent (otherwise this validator just does nothing)
241 - this is not always wanted, but we just assume that file inclusion is fine"""
243 __implements__ = _Itexmessage
245 pattern = re.compile(r"\([\"]?(?P<filename>(?:(?<!\")[^()\s\n]+(?!\"))|[^\"\n]+)[\"]?(?P<additional>[^()]*)\)")
247 def baselevels(self, s, maxlevel=1, brackets="()", quotes='""'):
248 """strip parts of a string above a given bracket level
249 - return a modified (some parts might be removed) version of the string s
250 where all parts inside brackets with level higher than maxlevel are
252 - if brackets do not match (number of left and right brackets is wrong
253 or at some points there were more right brackets than left brackets)
254 just return the unmodified string
255 - a quoted string immediately followed after a bracket is left untouched
256 even if it contains quotes itself"""
261 for i, c in enumerate(s):
262 if quotes and level <= maxlevel:
263 if not inquote and c == quotes[0] and i and s[i-1] == brackets[0]:
265 elif inquote and c == quotes[1]:
272 if level > highestlevel:
274 if level <= maxlevel:
278 if level == 0 and highestlevel > 0:
281 def check(self, texrunner):
282 search = self.baselevels(texrunner.texmessageparsed)
284 if search is not None:
285 m = self.pattern.search(search)
287 filename = m.group("filename").replace("\n", "")
289 additional = m.group("additional")
292 if (os.access(filename, os.R_OK) or
293 len(additional) and additional[0] == "\n" and os.access(filename+additional.split()[0], os.R_OK)):
294 res.append(search[:m.start()])
296 res.append(search[:m.end()])
297 search = search[m.end():]
298 m = self.pattern.search(search)
301 texrunner.texmessageparsed = "".join(res)
304 class _texmessageloaddef(_texmessageload):
305 """validates the inclusion of font description files (fd-files)
306 - works like _texmessageload
307 - filename must end with .def or .fd
308 - further text is allowed"""
310 pattern = re.compile(r"\([\"]?(?P<filename>(?:(?:(?<!\")[^\(\)\s\n\"]+)|(?:(?<=\")[^\(\)\"]+))(\.fd|\.def))[\"]?[\s\n]*(?P<additional>[\(]?[^\(\)]*[\)]?)[\s\n]*\)")
312 def baselevels(self, s, **kwargs):
316 class _texmessagegraphicsload(_texmessageload):
317 """validates the inclusion of files as the graphics packages writes it
318 - works like _texmessageload, but using "<" and ">" as delimiters
319 - filename must end with .eps and no further text is allowed"""
321 pattern = re.compile(r"<(?P<filename>[^>]+.eps)>")
323 def baselevels(self, s, **kwargs):
327 class _texmessageignore(_texmessageload):
328 """validates any TeX/LaTeX response
329 - this might be used, when the expression is ok, but no suitable texmessage
331 - PLEASE: - consider writing suitable tex message parsers
332 - share your ideas/problems/solutions with others (use the PyX mailing lists)"""
334 __implements__ = _Itexmessage
336 def check(self, texrunner):
337 texrunner.texmessageparsed = ""
340 texmessage.start = _texmessagestart()
341 texmessage.noaux = _texmessagenofile("aux")
342 texmessage.nonav = _texmessagenofile("nav")
343 texmessage.end = _texmessageend()
344 texmessage.load = _texmessageload()
345 texmessage.loaddef = _texmessageloaddef()
346 texmessage.graphicsload = _texmessagegraphicsload()
347 texmessage.ignore = _texmessageignore()
350 texmessage.inputmarker = _texmessageinputmarker()
351 texmessage.pyxbox = _texmessagepyxbox()
352 texmessage.pyxpageout = _texmessagepyxpageout()
353 texmessage.emptylines = _texmessageemptylines()
356 class _texmessageallwarning(texmessage):
357 """validates a given pattern 'pattern
' as a warning 'warning
'"""
359 def check(self, texrunner):
360 if texrunner.texmessageparsed:
361 warnings.warn("ignoring all warnings:\n%s" % texrunner.texmessageparsed)
362 texrunner.texmessageparsed = ""
364 texmessage.allwarning = _texmessageallwarning()
367 class texmessagepattern(texmessage):
368 """validates a given pattern and issue a warning (when set)"""
370 def __init__(self, pattern, warning=None):
371 self.pattern = pattern
372 self.warning = warning
374 def check(self, texrunner):
375 m = self.pattern.search(texrunner.texmessageparsed)
377 texrunner.texmessageparsed = texrunner.texmessageparsed[:m.start()] + texrunner.texmessageparsed[m.end():]
379 warnings.warn("%s:\n%s" % (self.warning, m.string[m.start(): m.end()].rstrip()))
380 m = self.pattern.search(texrunner.texmessageparsed)
382 texmessage.fontwarning = texmessagepattern(re.compile(r"^LaTeX Font Warning: .*$(\n^\(Font\).*$)*", re.MULTILINE), "ignoring font warning")
383 texmessage.boxwarning = texmessagepattern(re.compile(r"^(Overfull|Underfull) \\[hv]box.*$(\n^..*$)*\n^$\n", re.MULTILINE), "ignoring overfull/underfull box warning")
384 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")
385 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")
386 texmessage.nobblwarning = texmessagepattern(re.compile(r"^[\s\*]*(No file .*\.bbl.)\s*", re.MULTILINE), "ignoring no-bbl warning")
390 ###############################################################################
392 ###############################################################################
394 _textattrspreamble = ""
397 "a textattr defines a apply method, which modifies a (La)TeX expression"
399 class _localattr: pass
401 _textattrspreamble += r"""\gdef\PyXFlushHAlign{0}%
403 \leftskip=0pt plus \PyXFlushHAlign fil%
404 \rightskip=0pt plus 1fil%
405 \advance\rightskip0pt plus -\PyXFlushHAlign fil%
411 \exhyphenpenalty=9999}%
414 class boxhalign(attr.exclusiveattr, textattr, _localattr):
416 def __init__(self, aboxhalign):
417 self.boxhalign = aboxhalign
418 attr.exclusiveattr.__init__(self, boxhalign)
420 def apply(self, expr):
421 return r"\gdef\PyXBoxHAlign{%.5f}%s" % (self.boxhalign, expr)
423 boxhalign.left = boxhalign(0)
424 boxhalign.center = boxhalign(0.5)
425 boxhalign.right = boxhalign(1)
426 # boxhalign.clear = attr.clearclass(boxhalign) # we can't defined a clearclass
for boxhalign since it can
't clear a halign's boxhalign
429 class flushhalign(attr
.exclusiveattr
, textattr
, _localattr
):
431 def __init__(self
, aflushhalign
):
432 self
.flushhalign
= aflushhalign
433 attr
.exclusiveattr
.__init
__(self
, flushhalign
)
435 def apply(self
, expr
):
436 return r
"\gdef\PyXFlushHAlign{%.5f}\PyXragged{}%s" % (self
.flushhalign
, expr
)
438 flushhalign
.left
= flushhalign(0)
439 flushhalign
.center
= flushhalign(0.5)
440 flushhalign
.right
= flushhalign(1)
441 # flushhalign.clear = attr.clearclass(flushhalign) # we can't defined a clearclass for flushhalign since it couldn't clear a halign's flushhalign
444 class halign(attr
.exclusiveattr
, textattr
, boxhalign
, flushhalign
, _localattr
):
446 def __init__(self
, aboxhalign
, aflushhalign
):
447 self
.boxhalign
= aboxhalign
448 self
.flushhalign
= aflushhalign
449 attr
.exclusiveattr
.__init
__(self
, halign
)
451 def apply(self
, expr
):
452 return r
"\gdef\PyXBoxHAlign{%.5f}\gdef\PyXFlushHAlign{%.5f}\PyXragged{}%s" % (self
.boxhalign
, self
.flushhalign
, expr
)
454 halign
.left
= halign(0, 0)
455 halign
.center
= halign(0.5, 0.5)
456 halign
.right
= halign(1, 1)
457 halign
.clear
= attr
.clearclass(halign
)
458 halign
.boxleft
= boxhalign
.left
459 halign
.boxcenter
= boxhalign
.center
460 halign
.boxright
= boxhalign
.right
461 halign
.flushleft
= halign
.raggedright
= flushhalign
.left
462 halign
.flushcenter
= halign
.raggedcenter
= flushhalign
.center
463 halign
.flushright
= halign
.raggedleft
= flushhalign
.right
466 class _mathmode(attr
.attr
, textattr
, _localattr
):
469 def apply(self
, expr
):
470 return r
"$\displaystyle{%s}$" % expr
472 mathmode
= _mathmode()
473 clearmathmode
= attr
.clearclass(_mathmode
)
476 class _phantom(attr
.attr
, textattr
, _localattr
):
479 def apply(self
, expr
):
480 return r
"\phantom{%s}" % expr
483 clearphantom
= attr
.clearclass(_phantom
)
486 _textattrspreamble
+= "\\newbox\\PyXBoxVBox%\n\\newdimen\\PyXDimenVBox%\n"
488 class parbox_pt(attr
.sortbeforeexclusiveattr
, textattr
):
494 def __init__(self
, width
, baseline
=top
):
495 self
.width
= width
* 72.27 / (unit
.scale
["x"] * 72)
496 self
.baseline
= baseline
497 attr
.sortbeforeexclusiveattr
.__init
__(self
, parbox_pt
, [_localattr
])
499 def apply(self
, expr
):
500 if self
.baseline
== self
.top
:
501 return r
"\linewidth=%.5ftruept\vtop{\hsize=\linewidth\textwidth=\linewidth{}%s}" % (self
.width
, expr
)
502 elif self
.baseline
== self
.middle
:
503 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
)
504 elif self
.baseline
== self
.bottom
:
505 return r
"\linewidth=%.5ftruept\vbox{\hsize=\linewidth\textwidth=\linewidth{}%s}" % (self
.width
, expr
)
507 RuntimeError("invalid baseline argument")
509 parbox_pt
.clear
= attr
.clearclass(parbox_pt
)
511 class parbox(parbox_pt
):
513 def __init__(self
, width
, **kwargs
):
514 parbox_pt
.__init
__(self
, unit
.topt(width
), **kwargs
)
516 parbox
.clear
= parbox_pt
.clear
519 _textattrspreamble
+= "\\newbox\\PyXBoxVAlign%\n\\newdimen\\PyXDimenVAlign%\n"
521 class valign(attr
.sortbeforeexclusiveattr
, textattr
):
523 def __init__(self
, avalign
):
524 self
.valign
= avalign
525 attr
.sortbeforeexclusiveattr
.__init
__(self
, valign
, [parbox_pt
, _localattr
])
527 def apply(self
, expr
):
528 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
)
530 valign
.top
= valign(0)
531 valign
.middle
= valign(0.5)
532 valign
.bottom
= valign(1)
533 valign
.clear
= valign
.baseline
= attr
.clearclass(valign
)
536 _textattrspreamble
+= "\\newdimen\\PyXDimenVShift%\n"
538 class _vshift(attr
.sortbeforeattr
, textattr
):
541 attr
.sortbeforeattr
.__init
__(self
, [valign
, parbox_pt
, _localattr
])
543 def apply(self
, expr
):
544 return r
"%s\setbox0\hbox{{%s}}\lower\PyXDimenVShift\box0" % (self
.setheightexpr(), expr
)
546 class vshift(_vshift
):
547 "vertical down shift by a fraction of a character height"
549 def __init__(self
, lowerratio
, heightstr
="0"):
550 _vshift
.__init
__(self
)
551 self
.lowerratio
= lowerratio
552 self
.heightstr
= heightstr
554 def setheightexpr(self
):
555 return r
"\setbox0\hbox{{%s}}\PyXDimenVShift=%.5f\ht0" % (self
.heightstr
, self
.lowerratio
)
557 class _vshiftmathaxis(_vshift
):
558 "vertical down shift by the height of the math axis"
560 def setheightexpr(self
):
561 return r
"\setbox0\hbox{$\vcenter{\vrule width0pt}$}\PyXDimenVShift=\ht0"
564 vshift
.bottomzero
= vshift(0)
565 vshift
.middlezero
= vshift(0.5)
566 vshift
.topzero
= vshift(1)
567 vshift
.mathaxis
= _vshiftmathaxis()
568 vshift
.clear
= attr
.clearclass(_vshift
)
571 defaultsizelist
= ["normalsize", "large", "Large", "LARGE", "huge", "Huge",
572 None, "tiny", "scriptsize", "footnotesize", "small"]
574 class size(attr
.sortbeforeattr
, textattr
):
577 def __init__(self
, sizeindex
=None, sizename
=None, sizelist
=defaultsizelist
):
578 if (sizeindex
is None and sizename
is None) or (sizeindex
is not None and sizename
is not None):
579 raise RuntimeError("either specify sizeindex or sizename")
580 attr
.sortbeforeattr
.__init
__(self
, [_mathmode
, _vshift
])
581 if sizeindex
is not None:
582 if sizeindex
>= 0 and sizeindex
< sizelist
.index(None):
583 self
.size
= sizelist
[sizeindex
]
584 elif sizeindex
< 0 and sizeindex
+ len(sizelist
) > sizelist
.index(None):
585 self
.size
= sizelist
[sizeindex
]
587 raise IndexError("index out of sizelist range")
591 def apply(self
, expr
):
592 return r
"\%s{}%s" % (self
.size
, expr
)
595 size
.scriptsize
= size
.script
= size(-3)
596 size
.footnotesize
= size
.footnote
= size(-2)
597 size
.small
= size(-1)
598 size
.normalsize
= size
.normal
= size(0)
604 size
.clear
= attr
.clearclass(size
)
607 ###############################################################################
609 ###############################################################################
612 class _readpipe(threading
.Thread
):
613 """threaded reader of TeX/LaTeX output
614 - sets an event, when a specific string in the programs output is found
615 - sets an event, when the terminal ends"""
617 def __init__(self
, pipe
, expectqueue
, gotevent
, gotqueue
, quitevent
):
618 """initialize the reader
619 - pipe: file to be read from
620 - expectqueue: keeps the next InputMarker to be wait for
621 - gotevent: the "got InputMarker" event
622 - gotqueue: a queue containing the lines recieved from TeX/LaTeX
623 - quitevent: the "end of terminal" event"""
624 threading
.Thread
.__init
__(self
)
625 self
.setDaemon(1) # don't care if the output might not be finished (nevertheless, it shouldn't happen)
627 self
.expectqueue
= expectqueue
628 self
.gotevent
= gotevent
629 self
.gotqueue
= gotqueue
630 self
.quitevent
= quitevent
636 # catch interupted system call errors while reading
639 return self
.pipe
.readline()
641 if e
.errno
!= errno
.EINTR
:
643 read
= _read() # read, what comes in
645 self
.expect
= self
.expectqueue
.get_nowait() # read, what should be expected
649 # universal EOL handling (convert everything into unix like EOLs)
650 # XXX is this necessary on pipes?
651 read
= read
.replace("\r", "").replace("\n", "") + "\n"
652 self
.gotqueue
.put(read
) # report, whats read
653 if self
.expect
is not None and read
.find(self
.expect
) != -1:
654 self
.gotevent
.set() # raise the got event, when the output was expected (XXX: within a single line)
655 read
= _read() # read again
657 self
.expect
= self
.expectqueue
.get_nowait()
662 if self
.expect
is not None and self
.expect
.find("PyXInputMarker") != -1:
663 raise RuntimeError("TeX/LaTeX finished unexpectedly")
667 class textbox(box
.rect
, canvas
._canvas
):
668 """basically a box.rect, but it contains a text created by the texrunner
669 - texrunner._text and texrunner.text return such an object
670 - _textbox instances can be inserted into a canvas
671 - the output is contained in a page of the dvifile available thru the texrunner"""
672 # TODO: shouldn't all boxes become canvases? how about inserts then?
674 def __init__(self
, x
, y
, left
, right
, height
, depth
, finishdvi
, attrs
):
676 - finishdvi is a method to be called to get the dvicanvas
677 (e.g. the finishdvi calls the setdvicanvas method)
678 - attrs are fillstyles"""
681 self
.width
= left
+ right
684 self
.texttrafo
= trafo
.scale(unit
.scale
["x"]).translated(x
, y
)
685 box
.rect
.__init
__(self
, x
- left
, y
- depth
, left
+ right
, depth
+ height
, abscenter
= (left
, depth
))
686 canvas
._canvas
.__init
__(self
, attrs
)
687 self
.finishdvi
= finishdvi
688 self
.dvicanvas
= None
689 self
.insertdvicanvas
= 0
691 def transform(self
, *trafos
):
692 if self
.insertdvicanvas
:
693 raise RuntimeError("can't apply transformation after dvicanvas was inserted")
694 box
.rect
.transform(self
, *trafos
)
696 self
.texttrafo
= trafo
* self
.texttrafo
698 def setdvicanvas(self
, dvicanvas
):
699 if self
.dvicanvas
is not None:
700 raise RuntimeError("multiple call to setdvicanvas")
701 self
.dvicanvas
= dvicanvas
703 def ensuredvicanvas(self
):
704 if self
.dvicanvas
is None:
706 assert self
.dvicanvas
is not None, "finishdvi is broken"
707 if not self
.insertdvicanvas
:
708 self
.insert(self
.dvicanvas
, [self
.texttrafo
])
709 self
.insertdvicanvas
= 1
711 def marker(self
, marker
):
712 self
.ensuredvicanvas()
713 return self
.texttrafo
.apply(*self
.dvicanvas
.markers
[marker
])
715 def processPS(self
, file, writer
, context
, registry
, bbox
):
716 self
.ensuredvicanvas()
717 abbox
= bboxmodule
.empty()
718 canvas
._canvas
.processPS(self
, file, writer
, context
, registry
, abbox
)
719 bbox
+= box
.rect
.bbox(self
)
721 def processPDF(self
, file, writer
, context
, registry
, bbox
):
722 self
.ensuredvicanvas()
723 abbox
= bboxmodule
.empty()
724 canvas
._canvas
.processPDF(self
, file, writer
, context
, registry
, abbox
)
725 bbox
+= box
.rect
.bbox(self
)
728 def _cleantmp(texrunner
):
729 """get rid of temporary files
730 - function to be registered by atexit
731 - files contained in usefiles are kept"""
732 if texrunner
.texruns
: # cleanup while TeX is still running?
733 texrunner
.expectqueue
.put_nowait(None) # do not expect any output anymore
734 if texrunner
.mode
== "latex": # try to immediately quit from TeX or LaTeX
735 texrunner
.texinput
.write("\n\\catcode`\\@11\\relax\\@@end\n")
737 texrunner
.texinput
.write("\n\\end\n")
738 texrunner
.texinput
.close() # close the input queue and
739 if not texrunner
.waitforevent(texrunner
.quitevent
): # wait for finish of the output
740 return # didn't got a quit from TeX -> we can't do much more
741 texrunner
.texruns
= 0
742 texrunner
.texdone
= 1
743 for usefile
in texrunner
.usefiles
:
744 extpos
= usefile
.rfind(".")
746 os
.rename(texrunner
.texfilename
+ usefile
[extpos
:], usefile
)
749 for file in glob
.glob("%s.*" % texrunner
.texfilename
) + ["%sNotes.bib" % texrunner
.texfilename
]:
754 if texrunner
.texdebug
is not None:
756 texrunner
.texdebug
.close()
757 texrunner
.texdebug
= None
766 """TeX/LaTeX interface
767 - runs TeX/LaTeX expressions instantly
768 - checks TeX/LaTeX response
769 - the instance variable texmessage stores the last TeX
771 - the instance variable texmessageparsed stores a parsed
772 version of texmessage; it should be empty after
773 texmessage.check was called, otherwise a TexResultError
775 - the instance variable errordebug controls the verbose
776 level of TexResultError"""
778 defaulttexmessagesstart
= [texmessage
.start
]
779 defaulttexmessagesdocclass
= [texmessage
.load
]
780 defaulttexmessagesbegindoc
= [texmessage
.load
, texmessage
.noaux
]
781 defaulttexmessagesend
= [texmessage
.end
, texmessage
.fontwarning
, texmessage
.rerunwarning
, texmessage
.nobblwarning
]
782 defaulttexmessagesdefaultpreamble
= [texmessage
.load
]
783 defaulttexmessagesdefaultrun
= [texmessage
.loaddef
, texmessage
.graphicsload
,
784 texmessage
.fontwarning
, texmessage
.boxwarning
, texmessage
.packagewarning
]
786 def __init__(self
, mode
="tex",
791 waitfortex
=config
.getint("text", "waitfortex", 60),
792 showwaitfortex
=config
.getint("text", "showwaitfortex", 5),
793 texipc
=config
.getboolean("text", "texipc", 0),
800 texmessagesdocclass
=[],
801 texmessagesbegindoc
=[],
803 texmessagesdefaultpreamble
=[],
804 texmessagesdefaultrun
=[]):
806 if mode
!= "tex" and mode
!= "latex":
807 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
810 self
.docclass
= docclass
812 self
.usefiles
= usefiles
[:]
813 self
.waitfortex
= waitfortex
814 self
.showwaitfortex
= showwaitfortex
816 self
.singlecharmode
= singlecharmode
817 if texdebug
is not None:
818 if texdebug
[-4:] == ".tex":
819 self
.texdebug
= open(texdebug
, "w")
821 self
.texdebug
= open("%s.tex" % texdebug
, "w")
824 self
.dvidebug
= dvidebug
825 self
.errordebug
= errordebug
826 self
.pyxgraphics
= pyxgraphics
827 self
.texmessagesstart
= texmessagesstart
[:]
828 self
.texmessagesdocclass
= texmessagesdocclass
[:]
829 self
.texmessagesbegindoc
= texmessagesbegindoc
[:]
830 self
.texmessagesend
= texmessagesend
[:]
831 self
.texmessagesdefaultpreamble
= texmessagesdefaultpreamble
[:]
832 self
.texmessagesdefaultrun
= texmessagesdefaultrun
[:]
836 self
.preamblemode
= 1
840 self
.needdvitextboxes
= [] # when texipc-mode off
842 self
.textboxesincluded
= 0
843 savetempdir
= tempfile
.tempdir
844 tempfile
.tempdir
= os
.curdir
845 self
.texfilename
= os
.path
.basename(tempfile
.mktemp())
846 tempfile
.tempdir
= savetempdir
848 def waitforevent(self
, event
):
849 """waits verbosely with an timeout for an event
850 - observes an event while periodly while printing messages
851 - returns the status of the event (isSet)
852 - does not clear the event"""
853 if self
.showwaitfortex
:
856 while waited
< self
.waitfortex
and not hasevent
:
857 if self
.waitfortex
- waited
> self
.showwaitfortex
:
858 event
.wait(self
.showwaitfortex
)
859 waited
+= self
.showwaitfortex
861 event
.wait(self
.waitfortex
- waited
)
862 waited
+= self
.waitfortex
- waited
863 hasevent
= event
.isSet()
865 if waited
< self
.waitfortex
:
866 warnings
.warn("still waiting for %s after %i (of %i) seconds..." % (self
.mode
, waited
, self
.waitfortex
), PyXTeXWarning
)
868 warnings
.warn("the timeout of %i seconds expired and %s did not respond." % (waited
, self
.mode
), PyXTeXWarning
)
871 event
.wait(self
.waitfortex
)
874 def execute(self
, expr
, texmessages
):
875 """executes expr within TeX/LaTeX
876 - if self.texruns is not yet set, TeX/LaTeX is initialized,
877 self.texruns is set and self.preamblemode is set
878 - the method must not be called, when self.texdone is already set
879 - expr should be a string or None
880 - when expr is None, TeX/LaTeX is stopped, self.texruns is unset and
881 self.texdone becomes set
882 - when self.preamblemode is set, the expr is passed directly to TeX/LaTeX
883 - when self.preamblemode is unset, the expr is passed to \ProcessPyXBox
884 - texmessages is a list of texmessage instances"""
886 if self
.texdebug
is not None:
887 self
.texdebug
.write("%% PyX %s texdebug file\n" % version
.version
)
888 self
.texdebug
.write("%% mode: %s\n" % self
.mode
)
889 self
.texdebug
.write("%% date: %s\n" % time
.asctime(time
.localtime(time
.time())))
890 for usefile
in self
.usefiles
:
891 extpos
= usefile
.rfind(".")
893 os
.rename(usefile
, self
.texfilename
+ usefile
[extpos
:])
896 texfile
= open("%s.tex" % self
.texfilename
, "w") # start with filename -> creates dvi file with that name
897 texfile
.write("\\relax%\n")
904 self
.texinput
, self
.texoutput
= pycompat
.popen4("%s%s %s" % (self
.mode
, ipcflag
, self
.texfilename
), "t", 0)
906 # workaround: bufsize = 0 is not supported on MS windows for os.open4 (Python 2.4 and below, i.e. where subprocess is not available)
907 self
.texinput
, self
.texoutput
= pycompat
.popen4("%s%s %s" % (self
.mode
, ipcflag
, self
.texfilename
), "t")
908 atexit
.register(_cleantmp
, self
)
909 self
.expectqueue
= Queue
.Queue(1) # allow for a single entry only -> keeps the next InputMarker to be wait for
910 self
.gotevent
= threading
.Event() # keeps the got inputmarker event
911 self
.gotqueue
= Queue
.Queue(0) # allow arbitrary number of entries
912 self
.quitevent
= threading
.Event() # keeps for end of terminal event
913 self
.readoutput
= _readpipe(self
.texoutput
, self
.expectqueue
, self
.gotevent
, self
.gotqueue
, self
.quitevent
)
915 oldpreamblemode
= self
.preamblemode
916 self
.preamblemode
= 1
917 self
.readoutput
.start()
918 self
.execute("\\scrollmode\n\\raiseerror%\n" # switch to and check scrollmode
919 "\\def\\PyX{P\\kern-.3em\\lower.5ex\hbox{Y}\kern-.18em X}%\n" # just the PyX Logo
920 "\\gdef\\PyXBoxHAlign{0}%\n" # global PyXBoxHAlign (0.0-1.0) for the horizontal alignment, default to 0
921 "\\newbox\\PyXBox%\n" # PyXBox will contain the output
922 "\\newbox\\PyXBoxHAligned%\n" # PyXBox will contain the horizontal aligned output
923 "\\newdimen\\PyXDimenHAlignLT%\n" # PyXDimenHAlignLT/RT will contain the left/right extent
924 "\\newdimen\\PyXDimenHAlignRT%\n" +
925 _textattrspreamble
+ # insert preambles for textattrs macros
926 "\\long\\def\\ProcessPyXBox#1#2{%\n" # the ProcessPyXBox definition (#1 is expr, #2 is page number)
927 "\\setbox\\PyXBox=\\hbox{{#1}}%\n" # push expression into PyXBox
928 "\\PyXDimenHAlignLT=\\PyXBoxHAlign\\wd\\PyXBox%\n" # calculate the left/right extent
929 "\\PyXDimenHAlignRT=\\wd\\PyXBox%\n"
930 "\\advance\\PyXDimenHAlignRT by -\\PyXDimenHAlignLT%\n"
931 "\\gdef\\PyXBoxHAlign{0}%\n" # reset the PyXBoxHAlign to the default 0
932 "\\immediate\\write16{PyXBox:page=#2," # write page and extents of this box to stdout
933 "lt=\\the\\PyXDimenHAlignLT,"
934 "rt=\\the\\PyXDimenHAlignRT,"
935 "ht=\\the\\ht\\PyXBox,"
936 "dp=\\the\\dp\\PyXBox:}%\n"
937 "\\setbox\\PyXBoxHAligned=\\hbox{\\kern-\\PyXDimenHAlignLT\\box\\PyXBox}%\n" # align horizontally
938 "\\ht\\PyXBoxHAligned0pt%\n" # baseline alignment (hight to zero)
939 "{\\count0=80\\count1=121\\count2=88\\count3=#2\\shipout\\box\\PyXBoxHAligned}}%\n" # shipout PyXBox to Page 80.121.88.<page number>
940 "\\def\\PyXInput#1{\\immediate\\write16{PyXInputMarker:executeid=#1:}}%\n" # write PyXInputMarker to stdout
941 "\\def\\PyXMarker#1{\\hskip0pt\\special{PyX:marker #1}}%", # write PyXMarker special into the dvi-file
942 self
.defaulttexmessagesstart
+ self
.texmessagesstart
)
943 os
.remove("%s.tex" % self
.texfilename
)
944 if self
.mode
== "tex":
946 if not self
.lfs
.endswith(".lfs"):
947 self
.lfs
= "%s.lfs" % self
.lfs
948 lfsfile
= filelocator
.open(self
.lfs
, [], "r")
949 lfsdef
= lfsfile
.read()
951 self
.execute(lfsdef
, [])
952 self
.execute("\\normalsize%\n", [])
953 self
.execute("\\newdimen\\linewidth\\newdimen\\textwidth%\n", [])
954 elif self
.mode
== "latex":
956 pyxdef
= filelocator
.open("pyx.def", [], "rb")
957 pyxdef_filename
= self
.texfilename
+ ".pyx.def"
958 pyxdef_file
= open(pyxdef_filename
, "wb")
959 pyxdef_file
.write(pyxdef
.read())
962 pyxdef_filename_tex
= os
.path
.abspath(pyxdef_filename
).replace(os
.sep
, "/")
963 self
.execute("\\makeatletter%\n"
964 "\\let\\saveProcessOptions=\\ProcessOptions%\n"
965 "\\def\\ProcessOptions{%\n"
966 "\\def\\Gin@driver{" + pyxdef_filename_tex
+ "}%\n"
967 "\\def\\c@lor@namefile{dvipsnam.def}%\n"
968 "\\saveProcessOptions}%\n"
971 if self
.docopt
is not None:
972 self
.execute("\\documentclass[%s]{%s}" % (self
.docopt
, self
.docclass
),
973 self
.defaulttexmessagesdocclass
+ self
.texmessagesdocclass
)
975 self
.execute("\\documentclass{%s}" % self
.docclass
,
976 self
.defaulttexmessagesdocclass
+ self
.texmessagesdocclass
)
977 self
.preamblemode
= oldpreamblemode
979 if expr
is not None: # TeX/LaTeX should process expr
980 self
.expectqueue
.put_nowait("PyXInputMarker:executeid=%i:" % self
.executeid
)
981 if self
.preamblemode
:
982 self
.expr
= ("%s%%\n" % expr
+
983 "\\PyXInput{%i}%%\n" % self
.executeid
)
986 self
.expr
= ("\\ProcessPyXBox{%s%%\n}{%i}%%\n" % (expr
, self
.page
) +
987 "\\PyXInput{%i}%%\n" % self
.executeid
)
988 else: # TeX/LaTeX should be finished
989 self
.expectqueue
.put_nowait("Transcript written on %s.log" % self
.texfilename
)
990 if self
.mode
== "latex":
991 self
.expr
= "\\end{document}%\n"
993 self
.expr
= "\\end%\n"
994 if self
.texdebug
is not None:
995 self
.texdebug
.write(self
.expr
)
996 self
.texinput
.write(self
.expr
)
997 gotevent
= self
.waitforevent(self
.gotevent
)
998 self
.gotevent
.clear()
999 if expr
is None and gotevent
: # TeX/LaTeX should have finished
1002 self
.texinput
.close() # close the input queue and
1003 gotevent
= self
.waitforevent(self
.quitevent
) # wait for finish of the output
1005 self
.texmessage
= ""
1007 self
.texmessage
+= self
.gotqueue
.get_nowait()
1010 self
.texmessage
= self
.texmessage
.replace("\r\n", "\n").replace("\r", "\n")
1011 self
.texmessageparsed
= self
.texmessage
1013 if expr
is not None:
1014 texmessage
.inputmarker
.check(self
)
1015 if not self
.preamblemode
:
1016 texmessage
.pyxbox
.check(self
)
1017 texmessage
.pyxpageout
.check(self
)
1018 texmessages
= attr
.mergeattrs(texmessages
)
1019 for t
in texmessages
:
1021 keeptexmessageparsed
= self
.texmessageparsed
1022 texmessage
.emptylines
.check(self
)
1023 if len(self
.texmessageparsed
):
1024 self
.texmessageparsed
= keeptexmessageparsed
1025 raise TexResultError("unhandled TeX response (might be an error)", self
)
1027 raise TexResultError("TeX didn't respond as expected within the timeout period (%i seconds)." % self
.waitfortex
, self
)
1029 def finishdvi(self
, ignoretail
=0):
1030 """finish TeX/LaTeX and read the dvifile
1031 - this method ensures that all textboxes can access their
1033 self
.execute(None, self
.defaulttexmessagesend
+ self
.texmessagesend
)
1034 dvifilename
= "%s.dvi" % self
.texfilename
1036 self
.dvifile
= dvifile
.DVIfile(dvifilename
, debug
=self
.dvidebug
, singlecharmode
=self
.singlecharmode
)
1038 for box
in self
.needdvitextboxes
:
1039 box
.setdvicanvas(self
.dvifile
.readpage([ord("P"), ord("y"), ord("X"), page
, 0, 0, 0, 0, 0, 0], fontmap
=box
.fontmap
))
1041 if not ignoretail
and self
.dvifile
.readpage(None) is not None:
1042 raise RuntimeError("end of dvifile expected")
1044 self
.needdvitextboxes
= []
1046 def reset(self
, reinit
=0):
1047 "resets the tex runner to its initial state (upto its record to old dvi file(s))"
1050 if self
.texdebug
is not None:
1051 self
.texdebug
.write("%s\n%% preparing restart of %s\n" % ("%"*80, self
.mode
))
1056 self
.preamblemode
= 1
1057 for expr
, texmessages
in self
.preambles
:
1058 self
.execute(expr
, texmessages
)
1059 if self
.mode
== "latex":
1060 self
.execute("\\begin{document}", self
.defaulttexmessagesbegindoc
+ self
.texmessagesbegindoc
)
1061 self
.preamblemode
= 0
1064 self
.preamblemode
= 1
1066 def set(self
, mode
=_unset
,
1072 showwaitfortex
=_unset
,
1078 texmessagesstart
=_unset
,
1079 texmessagesdocclass
=_unset
,
1080 texmessagesbegindoc
=_unset
,
1081 texmessagesend
=_unset
,
1082 texmessagesdefaultpreamble
=_unset
,
1083 texmessagesdefaultrun
=_unset
):
1084 """provide a set command for TeX/LaTeX settings
1085 - TeX/LaTeX must not yet been started
1086 - especially needed for the defaultrunner, where no access to
1087 the constructor is available"""
1089 raise RuntimeError("set not allowed -- TeX/LaTeX already started")
1090 if mode
is not _unset
:
1092 if mode
!= "tex" and mode
!= "latex":
1093 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
1095 if lfs
is not _unset
:
1097 if docclass
is not _unset
:
1098 self
.docclass
= docclass
1099 if docopt
is not _unset
:
1100 self
.docopt
= docopt
1101 if usefiles
is not _unset
:
1102 self
.usefiles
= usefiles
1103 if waitfortex
is not _unset
:
1104 self
.waitfortex
= waitfortex
1105 if showwaitfortex
is not _unset
:
1106 self
.showwaitfortex
= showwaitfortex
1107 if texipc
is not _unset
:
1108 self
.texipc
= texipc
1109 if texdebug
is not _unset
:
1110 if self
.texdebug
is not None:
1111 self
.texdebug
.close()
1112 if texdebug
[-4:] == ".tex":
1113 self
.texdebug
= open(texdebug
, "w")
1115 self
.texdebug
= open("%s.tex" % texdebug
, "w")
1116 if dvidebug
is not _unset
:
1117 self
.dvidebug
= dvidebug
1118 if errordebug
is not _unset
:
1119 self
.errordebug
= errordebug
1120 if pyxgraphics
is not _unset
:
1121 self
.pyxgraphics
= pyxgraphics
1122 if errordebug
is not _unset
:
1123 self
.errordebug
= errordebug
1124 if texmessagesstart
is not _unset
:
1125 self
.texmessagesstart
= texmessagesstart
1126 if texmessagesdocclass
is not _unset
:
1127 self
.texmessagesdocclass
= texmessagesdocclass
1128 if texmessagesbegindoc
is not _unset
:
1129 self
.texmessagesbegindoc
= texmessagesbegindoc
1130 if texmessagesend
is not _unset
:
1131 self
.texmessagesend
= texmessagesend
1132 if texmessagesdefaultpreamble
is not _unset
:
1133 self
.texmessagesdefaultpreamble
= texmessagesdefaultpreamble
1134 if texmessagesdefaultrun
is not _unset
:
1135 self
.texmessagesdefaultrun
= texmessagesdefaultrun
1137 def preamble(self
, expr
, texmessages
=[]):
1138 r
"""put something into the TeX/LaTeX preamble
1139 - in LaTeX, this is done before the \begin{document}
1140 (you might use \AtBeginDocument, when you're in need for)
1141 - it is not allowed to call preamble after calling the
1142 text method for the first time (for LaTeX this is needed
1143 due to \begin{document}; in TeX it is forced for compatibility
1144 (you should be able to switch from TeX to LaTeX, if you want,
1145 without breaking something)
1146 - preamble expressions must not create any dvi output
1147 - args might contain texmessage instances"""
1148 if self
.texdone
or not self
.preamblemode
:
1149 raise RuntimeError("preamble calls disabled due to previous text calls")
1150 texmessages
= self
.defaulttexmessagesdefaultpreamble
+ self
.texmessagesdefaultpreamble
+ texmessages
1151 self
.execute(expr
, texmessages
)
1152 self
.preambles
.append((expr
, texmessages
))
1154 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:")
1156 def text(self
, x
, y
, expr
, textattrs
=[], texmessages
=[], fontmap
=None):
1157 """create text by passing expr to TeX/LaTeX
1158 - returns a textbox containing the result from running expr thru TeX/LaTeX
1159 - the box center is set to x, y
1160 - *args may contain attr parameters, namely:
1161 - textattr instances
1162 - texmessage instances
1163 - trafo._trafo instances
1164 - style.fillstyle instances"""
1166 raise ValueError("None expression is invalid")
1168 self
.reset(reinit
=1)
1170 if self
.preamblemode
:
1171 if self
.mode
== "latex":
1172 self
.execute("\\begin{document}", self
.defaulttexmessagesbegindoc
+ self
.texmessagesbegindoc
)
1173 self
.preamblemode
= 0
1175 textattrs
= attr
.mergeattrs(textattrs
) # perform cleans
1176 attr
.checkattrs(textattrs
, [textattr
, trafo
.trafo_pt
, style
.fillstyle
])
1177 trafos
= attr
.getattrs(textattrs
, [trafo
.trafo_pt
])
1178 fillstyles
= attr
.getattrs(textattrs
, [style
.fillstyle
])
1179 textattrs
= attr
.getattrs(textattrs
, [textattr
])
1180 # reverse loop over the merged textattrs (last is applied first)
1181 lentextattrs
= len(textattrs
)
1182 for i
in range(lentextattrs
):
1183 expr
= textattrs
[lentextattrs
-1-i
].apply(expr
)
1185 self
.execute(expr
, self
.defaulttexmessagesdefaultrun
+ self
.texmessagesdefaultrun
+ texmessages
)
1186 except TexResultError
, e
:
1187 warnings
.warn("We try to finish the dvi due to an unhandled tex error", PyXTeXWarning
)
1189 self
.finishdvi(ignoretail
=1)
1190 except TexResultError
:
1195 self
.dvifile
= dvifile
.DVIfile("%s.dvi" % self
.texfilename
, debug
=self
.dvidebug
, singlecharmode
=self
.singlecharmode
)
1196 match
= self
.PyXBoxPattern
.search(self
.texmessage
)
1197 if not match
or int(match
.group("page")) != self
.page
:
1198 raise TexResultError("box extents not found", self
)
1199 left
, right
, height
, depth
= [float(xxx
)*72/72.27*unit
.x_pt
for xxx
in match
.group("lt", "rt", "ht", "dp")]
1200 box
= textbox(x
, y
, left
, right
, height
, depth
, self
.finishdvi
, fillstyles
)
1202 box
.reltransform(t
) # TODO: should trafos really use reltransform???
1203 # this is quite different from what we do elsewhere!!!
1204 # see https://sourceforge.net/mailarchive/forum.php?thread_id=9137692&forum_id=23700
1206 box
.setdvicanvas(self
.dvifile
.readpage([ord("P"), ord("y"), ord("X"), self
.page
, 0, 0, 0, 0, 0, 0], fontmap
=fontmap
))
1208 box
.fontmap
= fontmap
1209 self
.needdvitextboxes
.append(box
)
1212 def text_pt(self
, x
, y
, expr
, *args
, **kwargs
):
1213 return self
.text(x
* unit
.t_pt
, y
* unit
.t_pt
, expr
, *args
, **kwargs
)
1215 PyXVariableBoxPattern
= re
.compile(r
"PyXVariableBox:page=(?P<page>\d+),par=(?P<par>\d+),prevgraf=(?P<prevgraf>\d+):")
1217 def textboxes(self
, text
, pageshapes
):
1218 # this is some experimental code to put text into several boxes
1219 # while the bounding shape changes from box to box (rectangles only)
1220 # first we load sev.tex
1221 if not self
.textboxesincluded
:
1222 self
.execute(r
"\input textboxes.tex", [texmessage
.load
])
1223 self
.textboxesincluded
= 1
1224 # define page shapes
1225 pageshapes_str
= "\\hsize=%.5ftruept%%\n\\vsize=%.5ftruept%%\n" % (72.27/72*unit
.topt(pageshapes
[0][0]), 72.27/72*unit
.topt(pageshapes
[0][1]))
1226 pageshapes_str
+= "\\lohsizes={%\n"
1227 for hsize
, vsize
in pageshapes
[1:]:
1228 pageshapes_str
+= "{\\global\\hsize=%.5ftruept}%%\n" % (72.27/72*unit
.topt(hsize
))
1229 pageshapes_str
+= "{\\relax}%\n}%\n"
1230 pageshapes_str
+= "\\lovsizes={%\n"
1231 for hsize
, vsize
in pageshapes
[1:]:
1232 pageshapes_str
+= "{\\global\\vsize=%.5ftruept}%%\n" % (72.27/72*unit
.topt(vsize
))
1233 pageshapes_str
+= "{\\relax}%\n}%\n"
1239 self
.execute(pageshapes_str
, [])
1240 parnos_str
= "}{".join(parnos
)
1242 parnos_str
= "{%s}" % parnos_str
1243 parnos_str
= "\\parnos={%s{\\relax}}%%\n" % parnos_str
1244 self
.execute(parnos_str
, [])
1245 parshapes_str
= "\\parshapes={%%\n%s%%\n{\\relax}%%\n}%%\n" % "%\n".join(parshapes
)
1246 self
.execute(parshapes_str
, [])
1247 self
.execute("\\global\\count0=1%%\n"
1248 "\\global\\parno=0%%\n"
1249 "\\global\\myprevgraf=0%%\n"
1250 "\\global\\showprevgraf=0%%\n"
1251 "\\global\\outputtype=0%%\n"
1252 "\\global\\leastcost=10000000%%\n"
1254 "\\vfill\\supereject%%\n" % text
, [texmessage
.ignore
])
1256 if self
.dvifile
is None:
1257 self
.dvifile
= dvifile
.DVIfile("%s.dvi" % self
.texfilename
, debug
=self
.dvidebug
, singlecharmode
=self
.singlecharmode
)
1259 raise RuntimeError("textboxes currently needs texipc")
1262 lastparshapes
= parshapes
1265 lastpar
= prevgraf
= -1
1266 m
= self
.PyXVariableBoxPattern
.search(self
.texmessage
)
1269 page
= int(m
.group("page"))
1270 assert page
== pages
1271 par
= int(m
.group("par"))
1272 prevgraf
= int(m
.group("prevgraf"))
1273 if page
<= len(pageshapes
):
1274 width
= 72.27/72*unit
.topt(pageshapes
[page
-1][0])
1276 width
= 72.27/72*unit
.topt(pageshapes
[-1][0])
1277 if page
< len(pageshapes
):
1278 nextwidth
= 72.27/72*unit
.topt(pageshapes
[page
][0])
1280 nextwidth
= 72.27/72*unit
.topt(pageshapes
[-1][0])
1283 # a new paragraph is to be broken
1284 parnos
.append(str(par
))
1285 parshape
= " 0pt ".join(["%.5ftruept" % width
for i
in range(prevgraf
)])
1287 parshape
= " 0pt " + parshape
1288 parshapes
.append("{\\parshape %i%s 0pt %.5ftruept}" % (prevgraf
+ 1, parshape
, nextwidth
))
1289 elif prevgraf
== lastprevgraf
:
1292 # we have to append the breaking of the previous paragraph
1293 oldparshape
= " ".join(parshapes
[-1].split(' ')[2:2+2*lastprevgraf
])
1294 oldparshape
= oldparshape
.split('}')[0]
1296 oldparshape
= " " + oldparshape
1297 parshape
= " 0pt ".join(["%.5ftruept" % width
for i
in range(prevgraf
- lastprevgraf
)])
1299 parshape
= " 0pt " + parshape
1302 parshapes
[-1] = "{\\parshape %i%s%s 0pt %.5ftruept}" % (prevgraf
+ 1, oldparshape
, parshape
, nextwidth
)
1304 lastprevgraf
= prevgraf
1306 m
= self
.PyXVariableBoxPattern
.search(self
.texmessage
, nextpos
)
1308 for i
in range(pages
):
1309 result
.append(self
.dvifile
.readpage([i
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]))
1310 if parnos
== lastparnos
and parshapes
== lastparshapes
:
1314 raise TexResultError("Too many loops in textboxes ", texrunner
)
1317 # the module provides an default texrunner and methods for direct access
1318 defaulttexrunner
= texrunner()
1319 reset
= defaulttexrunner
.reset
1320 set = defaulttexrunner
.set
1321 preamble
= defaulttexrunner
.preamble
1322 text
= defaulttexrunner
.text
1323 text_pt
= defaulttexrunner
.text_pt
1325 def escapestring(s
, replace
={" ": "~",
1337 "\\": "{$\setminus$}",
1339 "escape all ascii characters such that they are printable by TeX/LaTeX"
1342 if not 32 <= ord(s
[i
]) < 127:
1343 raise ValueError("escapestring function handles ascii strings only")
1350 s
= s
[:i
] + r
+ s
[i
+1:]