2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
7 # Copyright (C) 2002-2005 André Wobst <wobsta@users.sourceforge.net>
9 # This file is part of PyX (http://pyx.sourceforge.net/).
11 # PyX is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # PyX is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with PyX; if not, write to the Free Software
23 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 import glob
, os
, threading
, Queue
, re
, tempfile
, atexit
, time
, warnings
26 import config
, siteconfig
, unit
, box
, canvas
, trafo
, version
, attr
, style
, dvifile
28 ###############################################################################
30 # - please don't get confused:
31 # - there is a texmessage (and a texmessageparsed) attribute within the
32 # texrunner; it contains TeX/LaTeX response from the last command execution
33 # - instances of classes derived from the class texmessage are used to
34 # parse the TeX/LaTeX response as it is stored in the texmessageparsed
35 # attribute of a texrunner instance
36 # - the multiple usage of the name texmessage might be removed in the future
37 # - texmessage instances should implement _Itexmessage
38 ###############################################################################
40 class TexResultError(RuntimeError):
41 """specialized texrunner exception class
42 - it is raised by texmessage instances, when a texmessage indicates an error
43 - it is raised by the texrunner itself, whenever there is a texmessage left
44 after all parsing of this message (by texmessage instances)
45 prints a detailed report about the problem
46 - the verbose level is controlled by texrunner.errordebug"""
48 def __init__(self
, description
, texrunner
):
49 if texrunner
.errordebug
>= 2:
50 self
.description
= ("%s\n" % description
+
51 "The expression passed to TeX was:\n"
52 " %s\n" % texrunner
.expr
.replace("\n", "\n ").rstrip() +
53 "The return message from TeX was:\n"
54 " %s\n" % texrunner
.texmessage
.replace("\n", "\n ").rstrip() +
55 "After parsing this message, the following was left:\n"
56 " %s" % texrunner
.texmessageparsed
.replace("\n", "\n ").rstrip())
57 elif texrunner
.errordebug
== 1:
58 firstlines
= texrunner
.texmessageparsed
.split("\n")
59 if len(firstlines
) > 5:
60 firstlines
= firstlines
[:5] + ["(cut after 5 lines, increase errordebug for more output)"]
61 self
.description
= ("%s\n" % description
+
62 "The expression passed to TeX was:\n"
63 " %s\n" % texrunner
.expr
.replace("\n", "\n ").rstrip() +
64 "After parsing the return message from TeX, the following was left:\n" +
65 reduce(lambda x
, y
: "%s %s\n" % (x
,y
), firstlines
, "").rstrip())
67 self
.description
= description
70 return self
.description
74 """validates/invalidates TeX/LaTeX response"""
76 def check(self
, texrunner
):
77 """check a Tex/LaTeX response and respond appropriate
78 - read the texrunners texmessageparsed attribute
79 - if there is an problem found, raise TexResultError
80 - remove any valid and identified TeX/LaTeX response
81 from the texrunners texmessageparsed attribute
82 -> finally, there should be nothing left in there,
83 otherwise it is interpreted as an error"""
86 class texmessage(attr
.attr
): pass
89 class _texmessagestart(texmessage
):
90 """validates TeX/LaTeX startup"""
92 __implements__
= _Itexmessage
94 startpattern
= re
.compile(r
"This is [-0-9a-zA-Z\s_]*TeX")
96 def check(self
, texrunner
):
97 # check for "This is e-TeX"
98 m
= self
.startpattern
.search(texrunner
.texmessageparsed
)
100 raise TexResultError("TeX startup failed", texrunner
)
101 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[m
.end():]
103 # check for filename to be processed
105 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.split("%s.tex" % texrunner
.texfilename
, 1)[1]
106 except (IndexError, ValueError):
107 raise TexResultError("TeX running startup file failed", texrunner
)
109 # check for \raiseerror -- just to be sure that communication works
111 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.split("*! Undefined control sequence.\n<*> \\raiseerror\n %\n", 1)[1]
112 except (IndexError, ValueError):
113 raise TexResultError("TeX scrollmode check failed", texrunner
)
116 class _texmessagenoaux(texmessage
):
117 """allows for LaTeXs no-aux-file warning"""
119 __implements__
= _Itexmessage
121 def check(self
, texrunner
):
123 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s.aux." % texrunner
.texfilename
, 1)
124 texrunner
.texmessageparsed
= s1
+ s2
125 except (IndexError, ValueError):
127 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s%s%s.aux." % (os
.curdir
,
129 texrunner
.texfilename
), 1)
130 texrunner
.texmessageparsed
= s1
+ s2
131 except (IndexError, ValueError):
135 class _texmessageinputmarker(texmessage
):
136 """validates the PyXInputMarker"""
138 __implements__
= _Itexmessage
140 def check(self
, texrunner
):
142 s1
, s2
= texrunner
.texmessageparsed
.split("PyXInputMarker:executeid=%s:" % texrunner
.executeid
, 1)
143 texrunner
.texmessageparsed
= s1
+ s2
144 except (IndexError, ValueError):
145 raise TexResultError("PyXInputMarker expected", texrunner
)
148 class _texmessagepyxbox(texmessage
):
149 """validates the PyXBox output"""
151 __implements__
= _Itexmessage
153 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:")
155 def check(self
, texrunner
):
156 m
= self
.pattern
.search(texrunner
.texmessageparsed
)
157 if m
and m
.group("page") == str(texrunner
.page
):
158 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
160 raise TexResultError("PyXBox expected", texrunner
)
163 class _texmessagepyxpageout(texmessage
):
164 """validates the dvi shipout message (writing a page to the dvi file)"""
166 __implements__
= _Itexmessage
168 def check(self
, texrunner
):
170 s1
, s2
= texrunner
.texmessageparsed
.split("[80.121.88.%s]" % texrunner
.page
, 1)
171 texrunner
.texmessageparsed
= s1
+ s2
172 except (IndexError, ValueError):
173 raise TexResultError("PyXPageOutMarker expected", texrunner
)
176 class _texmessageend(texmessage
):
177 """validates TeX/LaTeX finish"""
179 __implements__
= _Itexmessage
181 def check(self
, texrunner
):
183 s1
, s2
= texrunner
.texmessageparsed
.split("(%s.aux)" % texrunner
.texfilename
, 1)
184 texrunner
.texmessageparsed
= s1
+ s2
185 except (IndexError, ValueError):
187 s1
, s2
= texrunner
.texmessageparsed
.split("(%s%s%s.aux)" % (os
.curdir
,
189 texrunner
.texfilename
), 1)
190 texrunner
.texmessageparsed
= s1
+ s2
191 except (IndexError, ValueError):
194 # check for "(see the transcript file for additional information)"
196 s1
, s2
= texrunner
.texmessageparsed
.split("(see the transcript file for additional information)", 1)
197 texrunner
.texmessageparsed
= s1
+ s2
198 except (IndexError, ValueError):
201 # check for "Output written on ...dvi (1 page, 220 bytes)."
202 dvipattern
= re
.compile(r
"Output written on %s\.dvi \((?P<page>\d+) pages?, \d+ bytes\)\." % texrunner
.texfilename
)
203 m
= dvipattern
.search(texrunner
.texmessageparsed
)
206 raise TexResultError("TeX dvifile messages expected", texrunner
)
207 if m
.group("page") != str(texrunner
.page
):
208 raise TexResultError("wrong number of pages reported", texrunner
)
209 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
212 s1
, s2
= texrunner
.texmessageparsed
.split("No pages of output.", 1)
213 texrunner
.texmessageparsed
= s1
+ s2
214 except (IndexError, ValueError):
215 raise TexResultError("no dvifile expected", texrunner
)
217 # check for "Transcript written on ...log."
219 s1
, s2
= texrunner
.texmessageparsed
.split("Transcript written on %s.log." % texrunner
.texfilename
, 1)
220 texrunner
.texmessageparsed
= s1
+ s2
221 except (IndexError, ValueError):
222 raise TexResultError("TeX logfile message expected", texrunner
)
225 class _texmessageemptylines(texmessage
):
226 """validates "*-only" (TeX/LaTeX input marker in interactive mode) and empty lines
227 also clear TeX interactive mode warning (Please type a command or say `\\end')
230 __implements__
= _Itexmessage
232 def check(self
, texrunner
):
233 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.replace(r
"(Please type a command or say `\end')", "")
234 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.replace(" ", "")
235 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.replace("*\n", "")
236 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.replace("\n", "")
239 class _texmessageload(texmessage
):
240 """validates inclusion of arbitrary files
241 - the matched pattern is "(<filename> <arbitrary other stuff>)", where
242 <fielname> is a readable file and other stuff can be anything
243 - "(" and ")" must be used consistent (otherwise this validator just does nothing)
244 - this is not always wanted, but we just assume that file inclusion is fine"""
246 __implements__
= _Itexmessage
248 pattern
= re
.compile(r
"\((?P<filename>[^()\s\n]+)(?P<additional>[^()]*)\)")
250 def baselevels(self
, s
, maxlevel
=1, brackets
="()"):
251 """strip parts of a string above a given bracket level
252 - return a modified (some parts might be removed) version of the string s
253 where all parts inside brackets with level higher than maxlevel are
255 - if brackets do not match (number of left and right brackets is wrong
256 or at some points there were more right brackets than left brackets)
257 just return the unmodified string"""
264 if level
> highestlevel
:
266 if level
<= maxlevel
:
270 if level
== 0 and highestlevel
> 0:
273 def check(self
, texrunner
):
274 lowestbracketlevel
= self
.baselevels(texrunner
.texmessageparsed
)
275 if lowestbracketlevel
is not None:
276 m
= self
.pattern
.search(lowestbracketlevel
)
278 filename
= m
.group("filename").replace("\n", "")
280 additional
= m
.group("additional")
283 if (os
.access(filename
, os
.R_OK
) or
284 len(additional
) and additional
[0] == "\n" and os
.access(filename
+additional
.split()[0], os
.R_OK
)):
285 lowestbracketlevel
= lowestbracketlevel
[:m
.start()] + lowestbracketlevel
[m
.end():]
288 m
= self
.pattern
.search(lowestbracketlevel
)
290 texrunner
.texmessageparsed
= lowestbracketlevel
293 class _texmessageloaddef(_texmessageload
):
294 """validates the inclusion of font description files (fd-files)
295 - works like _texmessageload
296 - filename must end with .def or .fd and no further text is allowed"""
298 pattern
= re
.compile(r
"\((?P<filename>[^)]+(\.fd|\.def))\)")
300 def baselevels(self
, s
, **kwargs
):
304 class _texmessagegraphicsload(_texmessageload
):
305 """validates the inclusion of files as the graphics packages writes it
306 - works like _texmessageload, but using "<" and ">" as delimiters
307 - filename must end with .eps and no further text is allowed"""
309 pattern
= re
.compile(r
"<(?P<filename>[^>]+.eps)>")
311 def baselevels(self
, s
, **kwargs
):
315 class _texmessageignore(_texmessageload
):
316 """validates any TeX/LaTeX response
317 - this might be used, when the expression is ok, but no suitable texmessage
319 - PLEASE: - consider writing suitable tex message parsers
320 - share your ideas/problems/solutions with others (use the PyX mailing lists)"""
322 __implements__
= _Itexmessage
324 def check(self
, texrunner
):
325 texrunner
.texmessageparsed
= ""
328 texmessage
.start
= _texmessagestart()
329 texmessage
.noaux
= _texmessagenoaux()
330 texmessage
.end
= _texmessageend()
331 texmessage
.load
= _texmessageload()
332 texmessage
.loaddef
= _texmessageloaddef()
333 texmessage
.graphicsload
= _texmessagegraphicsload()
334 texmessage
.ignore
= _texmessageignore()
337 texmessage
.inputmarker
= _texmessageinputmarker()
338 texmessage
.pyxbox
= _texmessagepyxbox()
339 texmessage
.pyxpageout
= _texmessagepyxpageout()
340 texmessage
.emptylines
= _texmessageemptylines()
343 class _texmessageallwarning(texmessage
):
344 """validates a given pattern 'pattern' as a warning 'warning'"""
346 def check(self
, texrunner
):
347 if texrunner
.texmessageparsed
:
348 warnings
.warn("ignoring all warnings:\n%s" % texrunner
.texmessageparsed
)
349 texrunner
.texmessageparsed
= ""
351 texmessage
.allwarning
= _texmessageallwarning()
354 class texmessagepattern(texmessage
):
355 """validates a given pattern and issue a warning (when set)"""
357 def __init__(self
, pattern
, warning
=None):
358 self
.pattern
= pattern
359 self
.warning
= warning
361 def check(self
, texrunner
):
362 m
= self
.pattern
.search(texrunner
.texmessageparsed
)
364 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
366 warnings
.warn("%s:\n%s" % (self
.warning
, m
.string
[m
.start(): m
.end()].rstrip()))
367 m
= self
.pattern
.search(texrunner
.texmessageparsed
)
369 texmessage
.fontwarning
= texmessagepattern(re
.compile(r
"^LaTeX Font Warning: .*$(\n^\(Font\).*$)*", re
.MULTILINE
), "ignoring font warning")
370 texmessage
.boxwarning
= texmessagepattern(re
.compile(r
"^(Overfull|Underfull) \\[hv]box.*$(\n^..*$)*\n^$\n", re
.MULTILINE
), "ignoring overfull/underfull box warning")
374 ###############################################################################
376 ###############################################################################
378 _textattrspreamble
= ""
381 "a textattr defines a apply method, which modifies a (La)TeX expression"
383 class _localattr
: pass
385 _textattrspreamble
+= r
"""\gdef\PyXFlushHAlign{0}%
386 \newdimen\PyXraggedskipplus%
387 \def\PyXragged{\PyXraggedskipplus=4em%
388 \leftskip=0pt plus \PyXFlushHAlign\PyXraggedskipplus%
389 \rightskip=0pt plus \PyXraggedskipplus%
390 \advance\rightskip0pt plus -\PyXFlushHAlign\PyXraggedskipplus%
398 \exhyphenpenalty=9999}%
401 class boxhalign(attr
.exclusiveattr
, textattr
, _localattr
):
403 def __init__(self
, aboxhalign
):
404 self
.boxhalign
= aboxhalign
405 attr
.exclusiveattr
.__init
__(self
, boxhalign
)
407 def apply(self
, expr
):
408 return r
"\gdef\PyXBoxHAlign{%.5f}%s" % (self
.boxhalign
, expr
)
410 boxhalign
.left
= boxhalign(0)
411 boxhalign
.center
= boxhalign(0.5)
412 boxhalign
.right
= boxhalign(1)
413 # boxhalign.clear = attr.clearclass(boxhalign) # we can't defined a clearclass for boxhalign since it can't clear a halign's boxhalign
416 class flushhalign(attr
.exclusiveattr
, textattr
, _localattr
):
418 def __init__(self
, aflushhalign
):
419 self
.flushhalign
= aflushhalign
420 attr
.exclusiveattr
.__init
__(self
, flushhalign
)
422 def apply(self
, expr
):
423 return r
"\gdef\PyXFlushHAlign{%.5f}\PyXragged{}%s" % (self
.flushhalign
, expr
)
425 flushhalign
.left
= flushhalign(0)
426 flushhalign
.center
= flushhalign(0.5)
427 flushhalign
.right
= flushhalign(1)
428 # flushhalign.clear = attr.clearclass(flushhalign) # we can't defined a clearclass for flushhalign since it couldn't clear a halign's flushhalign
431 class halign(attr
.exclusiveattr
, textattr
, boxhalign
, flushhalign
, _localattr
):
433 def __init__(self
, aboxhalign
, aflushhalign
):
434 self
.boxhalign
= aboxhalign
435 self
.flushhalign
= aflushhalign
436 attr
.exclusiveattr
.__init
__(self
, halign
)
438 def apply(self
, expr
):
439 return r
"\gdef\PyXBoxHAlign{%.5f}\gdef\PyXFlushHAlign{%.5f}\PyXragged{}%s" % (self
.boxhalign
, self
.flushhalign
, expr
)
441 halign
.left
= halign(0, 0)
442 halign
.center
= halign(0.5, 0.5)
443 halign
.right
= halign(1, 1)
444 halign
.clear
= attr
.clearclass(halign
)
445 halign
.boxleft
= boxhalign
.left
446 halign
.boxcenter
= boxhalign
.center
447 halign
.boxright
= boxhalign
.right
448 halign
.flushleft
= halign
.raggedright
= flushhalign
.left
449 halign
.flushcenter
= halign
.raggedcenter
= flushhalign
.center
450 halign
.flushright
= halign
.raggedleft
= flushhalign
.right
453 class _mathmode(attr
.attr
, textattr
, _localattr
):
456 def apply(self
, expr
):
457 return r
"$\displaystyle{%s}$" % expr
459 mathmode
= _mathmode()
460 clearmathmode
= attr
.clearclass(_mathmode
)
463 class _phantom(attr
.attr
, textattr
, _localattr
):
466 def apply(self
, expr
):
467 return r
"\phantom{%s}" % expr
470 clearphantom
= attr
.clearclass(_phantom
)
473 _textattrspreamble
+= "\\newbox\\PyXBoxVBox%\n\\newdimen\\PyXDimenVBox%\n"
475 class parbox_pt(attr
.sortbeforeexclusiveattr
, textattr
):
481 def __init__(self
, width
, baseline
=top
):
482 self
.width
= width
* 72.27 / (unit
.scale
["x"] * 72)
483 self
.baseline
= baseline
484 attr
.sortbeforeexclusiveattr
.__init
__(self
, parbox_pt
, [_localattr
])
486 def apply(self
, expr
):
487 if self
.baseline
== self
.top
:
488 return r
"\linewidth%.5ftruept\vtop{\hsize\linewidth{}%s}" % (self
.width
, expr
)
489 elif self
.baseline
== self
.middle
:
490 return r
"\linewidth%.5ftruept\setbox\PyXBoxVBox=\hbox{{\vtop{\hsize\linewidth{}%s}}}\PyXDimenVBox=0.5\dp\PyXBoxVBox\setbox\PyXBoxVBox=\hbox{{\vbox{\hsize\linewidth{}%s}}}\advance\PyXDimenVBox by -0.5\dp\PyXBoxVBox\lower\PyXDimenVBox\box\PyXBoxVBox" % (self
.width
, expr
, expr
)
491 elif self
.baseline
== self
.bottom
:
492 return r
"\linewidth%.5ftruept\vbox{\hsize\linewidth{}%s}" % (self
.width
, expr
)
494 RuntimeError("invalid baseline argument")
496 parbox_pt
.clear
= attr
.clearclass(parbox_pt
)
498 class parbox(parbox_pt
):
500 def __init__(self
, width
, **kwargs
):
501 parbox_pt
.__init
__(self
, unit
.topt(width
), **kwargs
)
503 parbox
.clear
= parbox_pt
.clear
506 _textattrspreamble
+= "\\newbox\\PyXBoxVAlign%\n\\newdimen\\PyXDimenVAlign%\n"
508 class valign(attr
.sortbeforeexclusiveattr
, textattr
):
510 def __init__(self
, avalign
):
511 self
.valign
= avalign
512 attr
.sortbeforeexclusiveattr
.__init
__(self
, valign
, [parbox_pt
, _localattr
])
514 def apply(self
, expr
):
515 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
)
517 valign
.top
= valign(0)
518 valign
.middle
= valign(0.5)
519 valign
.bottom
= valign(1)
520 valign
.clear
= valign
.baseline
= attr
.clearclass(valign
)
523 _textattrspreamble
+= "\\newdimen\\PyXDimenVShift%\n"
525 class _vshift(attr
.sortbeforeattr
, textattr
):
528 attr
.sortbeforeattr
.__init
__(self
, [valign
, parbox_pt
, _localattr
])
530 def apply(self
, expr
):
531 return r
"%s\setbox0\hbox{{%s}}\lower\PyXDimenVShift\box0" % (self
.setheightexpr(), expr
)
533 class vshift(_vshift
):
534 "vertical down shift by a fraction of a character height"
536 def __init__(self
, lowerratio
, heightstr
="0"):
537 _vshift
.__init
__(self
)
538 self
.lowerratio
= lowerratio
539 self
.heightstr
= heightstr
541 def setheightexpr(self
):
542 return r
"\setbox0\hbox{{%s}}\PyXDimenVShift=%.5f\ht0" % (self
.heightstr
, self
.lowerratio
)
544 class _vshiftmathaxis(_vshift
):
545 "vertical down shift by the height of the math axis"
547 def setheightexpr(self
):
548 return r
"\setbox0\hbox{$\vcenter{\vrule width0pt}$}\PyXDimenVShift=\ht0"
551 vshift
.bottomzero
= vshift(0)
552 vshift
.middlezero
= vshift(0.5)
553 vshift
.topzero
= vshift(1)
554 vshift
.mathaxis
= _vshiftmathaxis()
555 vshift
.clear
= attr
.clearclass(_vshift
)
558 defaultsizelist
= ["normalsize", "large", "Large", "LARGE", "huge", "Huge",
559 None, "tiny", "scriptsize", "footnotesize", "small"]
561 class size(attr
.sortbeforeattr
, textattr
):
564 def __init__(self
, sizeindex
=None, sizename
=None, sizelist
=defaultsizelist
):
565 if (sizeindex
is None and sizename
is None) or (sizeindex
is not None and sizename
is not None):
566 raise RuntimeError("either specify sizeindex or sizename")
567 attr
.sortbeforeattr
.__init
__(self
, [_mathmode
, _vshift
])
568 if sizeindex
is not None:
569 if sizeindex
>= 0 and sizeindex
< sizelist
.index(None):
570 self
.size
= sizelist
[sizeindex
]
571 elif sizeindex
< 0 and sizeindex
+ len(sizelist
) > sizelist
.index(None):
572 self
.size
= sizelist
[sizeindex
]
574 raise IndexError("index out of sizelist range")
578 def apply(self
, expr
):
579 return r
"\%s{}%s" % (self
.size
, expr
)
582 size
.scriptsize
= size
.script
= size(-3)
583 size
.footnotesize
= size
.footnote
= size(-2)
584 size
.small
= size(-1)
585 size
.normalsize
= size
.normal
= size(0)
591 size
.clear
= attr
.clearclass(size
)
594 ###############################################################################
596 ###############################################################################
599 class _readpipe(threading
.Thread
):
600 """threaded reader of TeX/LaTeX output
601 - sets an event, when a specific string in the programs output is found
602 - sets an event, when the terminal ends"""
604 def __init__(self
, pipe
, expectqueue
, gotevent
, gotqueue
, quitevent
):
605 """initialize the reader
606 - pipe: file to be read from
607 - expectqueue: keeps the next InputMarker to be wait for
608 - gotevent: the "got InputMarker" event
609 - gotqueue: a queue containing the lines recieved from TeX/LaTeX
610 - quitevent: the "end of terminal" event"""
611 threading
.Thread
.__init
__(self
)
612 self
.setDaemon(1) # don't care if the output might not be finished (nevertheless, it shouldn't happen)
614 self
.expectqueue
= expectqueue
615 self
.gotevent
= gotevent
616 self
.gotqueue
= gotqueue
617 self
.quitevent
= quitevent
623 read
= self
.pipe
.readline() # read, what comes in
625 self
.expect
= self
.expectqueue
.get_nowait() # read, what should be expected
629 # universal EOL handling (convert everything into unix like EOLs)
630 # XXX is this necessary on pipes?
631 read
= read
.replace("\r", "").replace("\n", "") + "\n"
632 self
.gotqueue
.put(read
) # report, whats read
633 if self
.expect
is not None and read
.find(self
.expect
) != -1:
634 self
.gotevent
.set() # raise the got event, when the output was expected (XXX: within a single line)
635 read
= self
.pipe
.readline() # read again
637 self
.expect
= self
.expectqueue
.get_nowait()
642 if self
.expect
is not None and self
.expect
.find("PyXInputMarker") != -1:
643 raise RuntimeError("TeX/LaTeX finished unexpectedly")
647 class textbox(box
.rect
, canvas
._canvas
):
648 """basically a box.rect, but it contains a text created by the texrunner
649 - texrunner._text and texrunner.text return such an object
650 - _textbox instances can be inserted into a canvas
651 - the output is contained in a page of the dvifile available thru the texrunner"""
652 # TODO: shouldn't all boxes become canvases? how about inserts then?
654 def __init__(self
, x
, y
, left
, right
, height
, depth
, finishdvi
, attrs
):
656 - finishdvi is a method to be called to get the dvicanvas
657 (e.g. the finishdvi calls the setdvicanvas method)
658 - attrs are fillstyles"""
661 self
.width
= left
+ right
664 self
.texttrafo
= trafo
.scale(unit
.scale
["x"]).translated(x
, y
)
665 box
.rect
.__init
__(self
, x
- left
, y
- depth
, left
+ right
, depth
+ height
, abscenter
= (left
, depth
))
666 canvas
._canvas
.__init
__(self
)
667 self
.finishdvi
= finishdvi
668 self
.dvicanvas
= None
670 self
.insertdvicanvas
= 0
672 def transform(self
, *trafos
):
673 if self
.insertdvicanvas
:
674 raise RuntimeError("can't apply transformation after dvicanvas was inserted")
675 box
.rect
.transform(self
, *trafos
)
677 self
.texttrafo
= trafo
* self
.texttrafo
679 def setdvicanvas(self
, dvicanvas
):
680 if self
.dvicanvas
is not None:
681 raise RuntimeError("multiple call to setdvicanvas")
682 self
.dvicanvas
= dvicanvas
684 def ensuredvicanvas(self
):
685 if self
.dvicanvas
is None:
687 assert self
.dvicanvas
is not None, "finishdvi is broken"
688 if not self
.insertdvicanvas
:
689 self
.insert(self
.dvicanvas
, [self
.texttrafo
])
690 self
.insertdvicanvas
= 1
692 def marker(self
, marker
):
693 self
.ensuredvicanvas()
694 return self
.texttrafo
.apply(*self
.dvicanvas
.markers
[marker
])
696 def processPS(self
, file, writer
, context
, registry
, bbox
):
697 self
.ensuredvicanvas()
698 canvas
._canvas
.processPS(self
, file, writer
, context
, registry
, bbox
)
700 def processPDF(self
, file, writer
, context
, registry
, bbox
):
701 self
.ensuredvicanvas()
702 canvas
._canvas
.processPDF(self
, file, writer
, context
, registry
, bbox
)
705 def _cleantmp(texrunner
):
706 """get rid of temporary files
707 - function to be registered by atexit
708 - files contained in usefiles are kept"""
709 if texrunner
.texruns
: # cleanup while TeX is still running?
710 texrunner
.expectqueue
.put_nowait(None) # do not expect any output anymore
711 if texrunner
.mode
== "latex": # try to immediately quit from TeX or LaTeX
712 texrunner
.texinput
.write("\n\\catcode`\\@11\\relax\\@@end\n")
714 texrunner
.texinput
.write("\n\\end\n")
715 texrunner
.texinput
.close() # close the input queue and
716 if not texrunner
.waitforevent(texrunner
.quitevent
): # wait for finish of the output
717 return # didn't got a quit from TeX -> we can't do much more
718 texrunner
.texruns
= 0
719 texrunner
.texdone
= 1
720 for usefile
in texrunner
.usefiles
:
721 extpos
= usefile
.rfind(".")
723 os
.rename(texrunner
.texfilename
+ usefile
[extpos
:], usefile
)
726 for file in glob
.glob("%s.*" % texrunner
.texfilename
):
731 if texrunner
.texdebug
is not None:
733 texrunner
.texdebug
.close()
734 texrunner
.texdebug
= None
743 """TeX/LaTeX interface
744 - runs TeX/LaTeX expressions instantly
745 - checks TeX/LaTeX response
746 - the instance variable texmessage stores the last TeX
748 - the instance variable texmessageparsed stores a parsed
749 version of texmessage; it should be empty after
750 texmessage.check was called, otherwise a TexResultError
752 - the instance variable errordebug controls the verbose
753 level of TexResultError"""
755 defaulttexmessagesstart
= [texmessage
.start
]
756 defaulttexmessagesdocclass
= [texmessage
.load
]
757 defaulttexmessagesbegindoc
= [texmessage
.load
, texmessage
.noaux
]
758 defaulttexmessagesend
= [texmessage
.end
, texmessage
.fontwarning
]
759 defaulttexmessagesdefaultpreamble
= [texmessage
.load
]
760 defaulttexmessagesdefaultrun
= [texmessage
.loaddef
, texmessage
.graphicsload
,
761 texmessage
.fontwarning
, texmessage
.boxwarning
]
763 def __init__(self
, mode
="tex",
768 fontmaps
=config
.get("text", "fontmaps", "psfonts.map"),
769 waitfortex
=config
.getint("text", "waitfortex", 60),
770 showwaitfortex
=config
.getint("text", "showwaitfortex", 5),
771 texipc
=config
.getboolean("text", "texipc", 0),
777 texmessagesdocclass
=[],
778 texmessagesbegindoc
=[],
780 texmessagesdefaultpreamble
=[],
781 texmessagesdefaultrun
=[]):
783 if mode
!= "tex" and mode
!= "latex":
784 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
787 self
.docclass
= docclass
789 self
.usefiles
= usefiles
790 self
.fontmaps
= fontmaps
791 self
.waitfortex
= waitfortex
792 self
.showwaitfortex
= showwaitfortex
794 if texdebug
is not None:
795 if texdebug
[-4:] == ".tex":
796 self
.texdebug
= open(texdebug
, "w")
798 self
.texdebug
= open("%s.tex" % texdebug
, "w")
801 self
.dvidebug
= dvidebug
802 self
.errordebug
= errordebug
803 self
.pyxgraphics
= pyxgraphics
804 self
.texmessagesstart
= texmessagesstart
805 self
.texmessagesdocclass
= texmessagesdocclass
806 self
.texmessagesbegindoc
= texmessagesbegindoc
807 self
.texmessagesend
= texmessagesend
808 self
.texmessagesdefaultpreamble
= texmessagesdefaultpreamble
809 self
.texmessagesdefaultrun
= texmessagesdefaultrun
813 self
.preamblemode
= 1
817 self
.needdvitextboxes
= [] # when texipc-mode off
819 self
.textboxesincluded
= 0
820 savetempdir
= tempfile
.tempdir
821 tempfile
.tempdir
= os
.curdir
822 self
.texfilename
= os
.path
.basename(tempfile
.mktemp())
823 tempfile
.tempdir
= savetempdir
825 def waitforevent(self
, event
):
826 """waits verbosely with an timeout for an event
827 - observes an event while periodly while printing messages
828 - returns the status of the event (isSet)
829 - does not clear the event"""
830 if self
.showwaitfortex
:
833 while waited
< self
.waitfortex
and not hasevent
:
834 if self
.waitfortex
- waited
> self
.showwaitfortex
:
835 event
.wait(self
.showwaitfortex
)
836 waited
+= self
.showwaitfortex
838 event
.wait(self
.waitfortex
- waited
)
839 waited
+= self
.waitfortex
- waited
840 hasevent
= event
.isSet()
842 if waited
< self
.waitfortex
:
843 warnings
.warn("still waiting for %s after %i (of %i) seconds..." % (self
.mode
, waited
, self
.waitfortex
))
845 warnings
.warn("the timeout of %i seconds expired and %s did not respond." % (waited
, self
.mode
))
848 event
.wait(self
.waitfortex
)
851 def execute(self
, expr
, texmessages
):
852 """executes expr within TeX/LaTeX
853 - if self.texruns is not yet set, TeX/LaTeX is initialized,
854 self.texruns is set and self.preamblemode is set
855 - the method must not be called, when self.texdone is already set
856 - expr should be a string or None
857 - when expr is None, TeX/LaTeX is stopped, self.texruns is unset and
858 self.texdone becomes set
859 - when self.preamblemode is set, the expr is passed directly to TeX/LaTeX
860 - when self.preamblemode is unset, the expr is passed to \ProcessPyXBox
861 - texmessages is a list of texmessage instances"""
863 if self
.texdebug
is not None:
864 self
.texdebug
.write("%% PyX %s texdebug file\n" % version
.version
)
865 self
.texdebug
.write("%% mode: %s\n" % self
.mode
)
866 self
.texdebug
.write("%% date: %s\n" % time
.asctime(time
.localtime(time
.time())))
867 for usefile
in self
.usefiles
:
868 extpos
= usefile
.rfind(".")
870 os
.rename(usefile
, self
.texfilename
+ usefile
[extpos
:])
873 texfile
= open("%s.tex" % self
.texfilename
, "w") # start with filename -> creates dvi file with that name
874 texfile
.write("\\relax%\n")
881 self
.texinput
, self
.texoutput
= os
.popen4("%s%s %s" % (self
.mode
, ipcflag
, self
.texfilename
), "t", 0)
883 # XXX: workaround for MS Windows (bufsize = 0 makes trouble!?)
884 self
.texinput
, self
.texoutput
= os
.popen4("%s%s %s" % (self
.mode
, ipcflag
, self
.texfilename
), "t")
885 atexit
.register(_cleantmp
, self
)
886 self
.expectqueue
= Queue
.Queue(1) # allow for a single entry only -> keeps the next InputMarker to be wait for
887 self
.gotevent
= threading
.Event() # keeps the got inputmarker event
888 self
.gotqueue
= Queue
.Queue(0) # allow arbitrary number of entries
889 self
.quitevent
= threading
.Event() # keeps for end of terminal event
890 self
.readoutput
= _readpipe(self
.texoutput
, self
.expectqueue
, self
.gotevent
, self
.gotqueue
, self
.quitevent
)
892 self
.fontmap
= dvifile
.readfontmap(self
.fontmaps
.split())
893 oldpreamblemode
= self
.preamblemode
894 self
.preamblemode
= 1
895 self
.execute("\\scrollmode\n\\raiseerror%\n" # switch to and check scrollmode
896 "\\def\\PyX{P\\kern-.3em\\lower.5ex\hbox{Y}\kern-.18em X}%\n" # just the PyX Logo
897 "\\gdef\\PyXBoxHAlign{0}%\n" # global PyXBoxHAlign (0.0-1.0) for the horizontal alignment, default to 0
898 "\\newbox\\PyXBox%\n" # PyXBox will contain the output
899 "\\newbox\\PyXBoxHAligned%\n" # PyXBox will contain the horizontal aligned output
900 "\\newdimen\\PyXDimenHAlignLT%\n" # PyXDimenHAlignLT/RT will contain the left/right extent
901 "\\newdimen\\PyXDimenHAlignRT%\n" +
902 _textattrspreamble
+ # insert preambles for textattrs macros
903 "\\long\\def\\ProcessPyXBox#1#2{%\n" # the ProcessPyXBox definition (#1 is expr, #2 is page number)
904 "\\setbox\\PyXBox=\\hbox{{#1}}%\n" # push expression into PyXBox
905 "\\PyXDimenHAlignLT=\\PyXBoxHAlign\\wd\\PyXBox%\n" # calculate the left/right extent
906 "\\PyXDimenHAlignRT=\\wd\\PyXBox%\n"
907 "\\advance\\PyXDimenHAlignRT by -\\PyXDimenHAlignLT%\n"
908 "\\gdef\\PyXBoxHAlign{0}%\n" # reset the PyXBoxHAlign to the default 0
909 "\\immediate\\write16{PyXBox:page=#2," # write page and extents of this box to stdout
910 "lt=\\the\\PyXDimenHAlignLT,"
911 "rt=\\the\\PyXDimenHAlignRT,"
912 "ht=\\the\\ht\\PyXBox,"
913 "dp=\\the\\dp\\PyXBox:}%\n"
914 "\\setbox\\PyXBoxHAligned=\\hbox{\\kern-\\PyXDimenHAlignLT\\box\\PyXBox}%\n" # align horizontally
915 "\\ht\\PyXBoxHAligned0pt%\n" # baseline alignment (hight to zero)
916 "{\\count0=80\\count1=121\\count2=88\\count3=#2\\shipout\\box\\PyXBoxHAligned}}%\n" # shipout PyXBox to Page 80.121.88.<page number>
917 "\\def\\PyXInput#1{\\immediate\\write16{PyXInputMarker:executeid=#1:}}%\n" # write PyXInputMarker to stdout
918 "\\def\\PyXMarker#1{\\hskip0pt\\special{PyX:marker #1}}%", # write PyXMarker special into the dvi-file
919 self
.defaulttexmessagesstart
+ self
.texmessagesstart
)
920 os
.remove("%s.tex" % self
.texfilename
)
921 if self
.mode
== "tex":
924 if len(self
.lfs
) > 4 and self
.lfs
[-4:] == ".lfs":
927 lfsname
= "%s.lfs" % self
.lfs
928 for fulllfsname
in [lfsname
,
929 os
.path
.join(siteconfig
.lfsdir
, lfsname
)]:
931 lfsfile
= open(fulllfsname
, "r")
932 lfsdef
= lfsfile
.read()
938 lfserror
= "File '%s' is not available or not readable. " % lfsname
941 if lfserror
is not None:
942 allfiles
= (glob
.glob("*.lfs") +
943 glob
.glob(os
.path
.join(siteconfig
.lfsdir
, "*.lfs")))
948 lfsnames
.append(os
.path
.basename(f
)[:-4])
953 raise IOError("%sAvailable LaTeX font size files (*.lfs): %s" % (lfserror
, lfsnames
))
955 raise IOError("%sNo LaTeX font size files (*.lfs) available. Check your installation." % lfserror
)
956 self
.execute(lfsdef
, [])
957 self
.execute("\\normalsize%\n", [])
958 self
.execute("\\newdimen\\linewidth%\n", [])
959 elif self
.mode
== "latex":
961 pyxdef
= os
.path
.join(siteconfig
.sharedir
, "pyx.def")
963 open(pyxdef
, "r").close()
965 IOError("file 'pyx.def' is not available or not readable. Check your installation or turn off the pyxgraphics option.")
966 pyxdef
= os
.path
.abspath(pyxdef
).replace(os
.sep
, "/")
967 self
.execute("\\makeatletter%\n"
968 "\\let\\saveProcessOptions=\\ProcessOptions%\n"
969 "\\def\\ProcessOptions{%\n"
970 "\\def\\Gin@driver{" + pyxdef
+ "}%\n"
971 "\\def\\c@lor@namefile{dvipsnam.def}%\n"
972 "\\saveProcessOptions}%\n"
975 if self
.docopt
is not None:
976 self
.execute("\\documentclass[%s]{%s}" % (self
.docopt
, self
.docclass
),
977 self
.defaulttexmessagesdocclass
+ self
.texmessagesdocclass
)
979 self
.execute("\\documentclass{%s}" % self
.docclass
,
980 self
.defaulttexmessagesdocclass
+ self
.texmessagesdocclass
)
981 self
.preamblemode
= oldpreamblemode
983 if expr
is not None: # TeX/LaTeX should process expr
984 self
.expectqueue
.put_nowait("PyXInputMarker:executeid=%i:" % self
.executeid
)
985 if self
.preamblemode
:
986 self
.expr
= ("%s%%\n" % expr
+
987 "\\PyXInput{%i}%%\n" % self
.executeid
)
990 self
.expr
= ("\\ProcessPyXBox{%s%%\n}{%i}%%\n" % (expr
, self
.page
) +
991 "\\PyXInput{%i}%%\n" % self
.executeid
)
992 else: # TeX/LaTeX should be finished
993 self
.expectqueue
.put_nowait("Transcript written on %s.log" % self
.texfilename
)
994 if self
.mode
== "latex":
995 self
.expr
= "\\end{document}%\n"
997 self
.expr
= "\\end%\n"
998 if self
.texdebug
is not None:
999 self
.texdebug
.write(self
.expr
)
1000 self
.texinput
.write(self
.expr
)
1001 gotevent
= self
.waitforevent(self
.gotevent
)
1002 self
.gotevent
.clear()
1003 if expr
is None and gotevent
: # TeX/LaTeX should have finished
1006 self
.texinput
.close() # close the input queue and
1007 gotevent
= self
.waitforevent(self
.quitevent
) # wait for finish of the output
1009 self
.texmessage
= ""
1011 self
.texmessage
+= self
.gotqueue
.get_nowait()
1014 self
.texmessage
= self
.texmessage
.replace("\r\n", "\n").replace("\r", "\n")
1015 self
.texmessageparsed
= self
.texmessage
1017 if expr
is not None:
1018 texmessage
.inputmarker
.check(self
)
1019 if not self
.preamblemode
:
1020 texmessage
.pyxbox
.check(self
)
1021 texmessage
.pyxpageout
.check(self
)
1022 texmessages
= attr
.mergeattrs(texmessages
)
1023 for t
in texmessages
:
1025 keeptexmessageparsed
= self
.texmessageparsed
1026 texmessage
.emptylines
.check(self
)
1027 if len(self
.texmessageparsed
):
1028 self
.texmessageparsed
= keeptexmessageparsed
1029 raise TexResultError("unhandled TeX response (might be an error)", self
)
1031 raise TexResultError("TeX didn't respond as expected within the timeout period (%i seconds)." % self
.waitfortex
, self
)
1033 def finishdvi(self
, ignoretail
=0):
1034 """finish TeX/LaTeX and read the dvifile
1035 - this method ensures that all textboxes can access their
1037 self
.execute(None, self
.defaulttexmessagesend
+ self
.texmessagesend
)
1038 dvifilename
= "%s.dvi" % self
.texfilename
1040 self
.dvifile
= dvifile
.dvifile(dvifilename
, self
.fontmap
, debug
=self
.dvidebug
)
1042 for box
in self
.needdvitextboxes
:
1043 box
.setdvicanvas(self
.dvifile
.readpage([ord("P"), ord("y"), ord("X"), page
, 0, 0, 0, 0, 0, 0]))
1045 if not ignoretail
and self
.dvifile
.readpage(None) is not None:
1046 raise RuntimeError("end of dvifile expected")
1048 self
.needdvitextboxes
= []
1050 def reset(self
, reinit
=0):
1051 "resets the tex runner to its initial state (upto its record to old dvi file(s))"
1054 if self
.texdebug
is not None:
1055 self
.texdebug
.write("%s\n%% preparing restart of %s\n" % ("%"*80, self
.mode
))
1060 self
.preamblemode
= 1
1061 for expr
, texmessages
in self
.preambles
:
1062 self
.execute(expr
, texmessages
)
1063 if self
.mode
== "latex":
1064 self
.execute("\\begin{document}", self
.defaulttexmessagesbegindoc
+ self
.texmessagesbegindoc
)
1065 self
.preamblemode
= 0
1068 self
.preamblemode
= 1
1070 def set(self
, mode
=_unset
,
1077 showwaitfortex
=_unset
,
1083 texmessagesstart
=_unset
,
1084 texmessagesdocclass
=_unset
,
1085 texmessagesbegindoc
=_unset
,
1086 texmessagesend
=_unset
,
1087 texmessagesdefaultpreamble
=_unset
,
1088 texmessagesdefaultrun
=_unset
):
1089 """provide a set command for TeX/LaTeX settings
1090 - TeX/LaTeX must not yet been started
1091 - especially needed for the defaultrunner, where no access to
1092 the constructor is available"""
1094 raise RuntimeError("set not allowed -- TeX/LaTeX already started")
1095 if mode
is not _unset
:
1097 if mode
!= "tex" and mode
!= "latex":
1098 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
1100 if lfs
is not _unset
:
1102 if docclass
is not _unset
:
1103 self
.docclass
= docclass
1104 if docopt
is not _unset
:
1105 self
.docopt
= docopt
1106 if usefiles
is not _unset
:
1107 self
.usefiles
= usefiles
1108 if fontmaps
is not _unset
:
1109 self
.fontmaps
= fontmaps
1110 if waitfortex
is not _unset
:
1111 self
.waitfortex
= waitfortex
1112 if showwaitfortex
is not _unset
:
1113 self
.showwaitfortex
= showwaitfortex
1114 if texipc
is not _unset
:
1115 self
.texipc
= texipc
1116 if texdebug
is not _unset
:
1117 if self
.texdebug
is not None:
1118 self
.texdebug
.close()
1119 if texdebug
[-4:] == ".tex":
1120 self
.texdebug
= open(texdebug
, "w")
1122 self
.texdebug
= open("%s.tex" % texdebug
, "w")
1123 if dvidebug
is not _unset
:
1124 self
.dvidebug
= dvidebug
1125 if errordebug
is not _unset
:
1126 self
.errordebug
= errordebug
1127 if pyxgraphics
is not _unset
:
1128 self
.pyxgraphics
= pyxgraphics
1129 if errordebug
is not _unset
:
1130 self
.errordebug
= errordebug
1131 if texmessagesstart
is not _unset
:
1132 self
.texmessagesstart
= texmessagesstart
1133 if texmessagesdocclass
is not _unset
:
1134 self
.texmessagesdocclass
= texmessagesdocclass
1135 if texmessagesbegindoc
is not _unset
:
1136 self
.texmessagesbegindoc
= texmessagesbegindoc
1137 if texmessagesend
is not _unset
:
1138 self
.texmessagesend
= texmessagesend
1139 if texmessagesdefaultpreamble
is not _unset
:
1140 self
.texmessagesdefaultpreamble
= texmessagesdefaultpreamble
1141 if texmessagesdefaultrun
is not _unset
:
1142 self
.texmessagesdefaultrun
= texmessagesdefaultrun
1144 def preamble(self
, expr
, texmessages
=[]):
1145 r
"""put something into the TeX/LaTeX preamble
1146 - in LaTeX, this is done before the \begin{document}
1147 (you might use \AtBeginDocument, when you're in need for)
1148 - it is not allowed to call preamble after calling the
1149 text method for the first time (for LaTeX this is needed
1150 due to \begin{document}; in TeX it is forced for compatibility
1151 (you should be able to switch from TeX to LaTeX, if you want,
1152 without breaking something)
1153 - preamble expressions must not create any dvi output
1154 - args might contain texmessage instances"""
1155 if self
.texdone
or not self
.preamblemode
:
1156 raise RuntimeError("preamble calls disabled due to previous text calls")
1157 texmessages
= self
.defaulttexmessagesdefaultpreamble
+ self
.texmessagesdefaultpreamble
+ texmessages
1158 self
.execute(expr
, texmessages
)
1159 self
.preambles
.append((expr
, texmessages
))
1161 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:")
1163 def text(self
, x
, y
, expr
, textattrs
=[], texmessages
=[]):
1164 """create text by passing expr to TeX/LaTeX
1165 - returns a textbox containing the result from running expr thru TeX/LaTeX
1166 - the box center is set to x, y
1167 - *args may contain attr parameters, namely:
1168 - textattr instances
1169 - texmessage instances
1170 - trafo._trafo instances
1171 - style.fillstyle instances"""
1173 raise ValueError("None expression is invalid")
1175 self
.reset(reinit
=1)
1177 if self
.preamblemode
:
1178 if self
.mode
== "latex":
1179 self
.execute("\\begin{document}", self
.defaulttexmessagesbegindoc
+ self
.texmessagesbegindoc
)
1180 self
.preamblemode
= 0
1182 textattrs
= attr
.mergeattrs(textattrs
) # perform cleans
1183 attr
.checkattrs(textattrs
, [textattr
, trafo
.trafo_pt
, style
.fillstyle
])
1184 trafos
= attr
.getattrs(textattrs
, [trafo
.trafo_pt
])
1185 fillstyles
= attr
.getattrs(textattrs
, [style
.fillstyle
])
1186 textattrs
= attr
.getattrs(textattrs
, [textattr
])
1187 # reverse loop over the merged textattrs (last is applied first)
1188 lentextattrs
= len(textattrs
)
1189 for i
in range(lentextattrs
):
1190 expr
= textattrs
[lentextattrs
-1-i
].apply(expr
)
1192 self
.execute(expr
, self
.defaulttexmessagesdefaultrun
+ self
.texmessagesdefaultrun
+ texmessages
)
1193 except TexResultError
:
1194 self
.finishdvi(ignoretail
=1)
1198 self
.dvifile
= dvifile
.dvifile("%s.dvi" % self
.texfilename
, self
.fontmap
, debug
=self
.dvidebug
)
1199 match
= self
.PyXBoxPattern
.search(self
.texmessage
)
1200 if not match
or int(match
.group("page")) != self
.page
:
1201 raise TexResultError("box extents not found", self
)
1202 left
, right
, height
, depth
= [float(xxx
)*72/72.27*unit
.x_pt
for xxx
in match
.group("lt", "rt", "ht", "dp")]
1203 box
= textbox(x
, y
, left
, right
, height
, depth
, self
.finishdvi
, fillstyles
)
1207 box
.setdvicanvas(self
.dvifile
.readpage([ord("P"), ord("y"), ord("X"), self
.page
, 0, 0, 0, 0, 0, 0]))
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
, self
.fontmap
, debug
=self
.dvidebug
)
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:]