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-2004 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 import glob
, os
, threading
, Queue
, traceback
, 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)"""
46 def __init__(self
, description
, texrunner
):
47 self
.description
= description
48 self
.texrunner
= texrunner
51 """prints a detailed report about the problem
52 - the verbose level is controlled by texrunner.errordebug"""
53 if self
.texrunner
.errordebug
>= 2:
54 return ("%s\n" % self
.description
+
55 "The expression passed to TeX was:\n"
56 " %s\n" % self
.texrunner
.expr
.replace("\n", "\n ").rstrip() +
57 "The return message from TeX was:\n"
58 " %s\n" % self
.texrunner
.texmessage
.replace("\n", "\n ").rstrip() +
59 "After parsing this message, the following was left:\n"
60 " %s" % self
.texrunner
.texmessageparsed
.replace("\n", "\n ").rstrip())
61 elif self
.texrunner
.errordebug
== 1:
62 firstlines
= self
.texrunner
.texmessageparsed
.split("\n")
63 if len(firstlines
) > 5:
64 firstlines
= firstlines
[:5] + ["(cut after 5 lines, increase errordebug for more output)"]
65 return ("%s\n" % self
.description
+
66 "The expression passed to TeX was:\n"
67 " %s\n" % self
.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 return self
.description
74 class TexResultWarning(TexResultError
):
75 """as above, but with different handling of the exception
76 - when this exception is raised by a texmessage instance,
77 the information just get reported and the execution continues"""
82 """validates/invalidates TeX/LaTeX response"""
84 def check(self
, texrunner
):
85 """check a Tex/LaTeX response and respond appropriate
86 - read the texrunners texmessageparsed attribute
87 - if there is an problem found, raise an appropriate
88 exception (TexResultError or TexResultWarning)
89 - remove any valid and identified TeX/LaTeX response
90 from the texrunners texmessageparsed attribute
91 -> finally, there should be nothing left in there,
92 otherwise it is interpreted as an error"""
95 class texmessage(attr
.attr
): pass
98 class _texmessagestart(texmessage
):
99 """validates TeX/LaTeX startup"""
101 __implements__
= _Itexmessage
103 startpattern
= re
.compile(r
"This is [-0-9a-zA-Z\s_]*TeX")
105 def check(self
, texrunner
):
106 # check for "This is e-TeX"
107 m
= self
.startpattern
.search(texrunner
.texmessageparsed
)
109 raise TexResultError("TeX startup failed", texrunner
)
110 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[m
.end():]
112 # check for filename to be processed
114 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.split("%s.tex" % texrunner
.texfilename
, 1)[1]
115 except (IndexError, ValueError):
116 raise TexResultError("TeX running startup file failed", texrunner
)
118 # check for \raiseerror -- just to be sure that communication works
120 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.split("*! Undefined control sequence.\n<*> \\raiseerror\n %\n", 1)[1]
121 except (IndexError, ValueError):
122 raise TexResultError("TeX scrollmode check failed", texrunner
)
125 class _texmessagenoaux(texmessage
):
126 """allows for LaTeXs no-aux-file warning"""
128 __implements__
= _Itexmessage
130 def check(self
, texrunner
):
132 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s.aux." % texrunner
.texfilename
, 1)
133 texrunner
.texmessageparsed
= s1
+ s2
134 except (IndexError, ValueError):
136 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s%s%s.aux." % (os
.curdir
,
138 texrunner
.texfilename
), 1)
139 texrunner
.texmessageparsed
= s1
+ s2
140 except (IndexError, ValueError):
144 class _texmessageinputmarker(texmessage
):
145 """validates the PyXInputMarker"""
147 __implements__
= _Itexmessage
149 def check(self
, texrunner
):
151 s1
, s2
= texrunner
.texmessageparsed
.split("PyXInputMarker:executeid=%s:" % texrunner
.executeid
, 1)
152 texrunner
.texmessageparsed
= s1
+ s2
153 except (IndexError, ValueError):
154 raise TexResultError("PyXInputMarker expected", texrunner
)
157 class _texmessagepyxbox(texmessage
):
158 """validates the PyXBox output"""
160 __implements__
= _Itexmessage
162 pattern
= re
.compile(r
"PyXBox:page=(?P<page>\d+),lt=-?\d*((\d\.?)|(\.?\d))\d*pt,rt=-?\d*((\d\.?)|(\.?\d))\d*pt,ht=-?\d*((\d\.?)|(\.?\d))\d*pt,dp=-?\d*((\d\.?)|(\.?\d))\d*pt:")
164 def check(self
, texrunner
):
165 m
= self
.pattern
.search(texrunner
.texmessageparsed
)
166 if m
and m
.group("page") == str(texrunner
.page
):
167 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
169 raise TexResultError("PyXBox expected", texrunner
)
172 class _texmessagepyxpageout(texmessage
):
173 """validates the dvi shipout message (writing a page to the dvi file)"""
175 __implements__
= _Itexmessage
177 def check(self
, texrunner
):
179 s1
, s2
= texrunner
.texmessageparsed
.split("[80.121.88.%s]" % texrunner
.page
, 1)
180 texrunner
.texmessageparsed
= s1
+ s2
181 except (IndexError, ValueError):
182 raise TexResultError("PyXPageOutMarker expected", texrunner
)
185 class _texmessagefontsubstitution(texmessage
):
186 """validates the font substituion Warning"""
188 __implements__
= _Itexmessage
190 pattern
= re
.compile("LaTeX Font Warning: Font shape (?P<font>.*) in size <(?P<orig>.*)> not available\s*\(Font\)(.*) size <(?P<subst>.*)> substituted on input line (?P<line>.*)\.")
192 def check(self
, texrunner
):
193 m
= self
.pattern
.search(texrunner
.texmessageparsed
)
195 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
196 raise TexResultWarning("LaTeX Font Warning on input line %s" % (m
.group('line')), texrunner
)
199 class _texmessagetexend(texmessage
):
200 """validates TeX/LaTeX finish"""
202 __implements__
= _Itexmessage
204 def check(self
, texrunner
):
206 s1
, s2
= texrunner
.texmessageparsed
.split("(%s.aux)" % texrunner
.texfilename
, 1)
207 texrunner
.texmessageparsed
= s1
+ s2
208 except (IndexError, ValueError):
210 s1
, s2
= texrunner
.texmessageparsed
.split("(%s%s%s.aux)" % (os
.curdir
,
212 texrunner
.texfilename
), 1)
213 texrunner
.texmessageparsed
= s1
+ s2
214 except (IndexError, ValueError):
217 # pass font size summary over to PyX user
218 fontpattern
= re
.compile(r
"LaTeX Font Warning: Size substitutions with differences\s*\(Font\).* have occurred.\s*")
219 m
= fontpattern
.search(texrunner
.texmessageparsed
)
221 warnings
.warn("LaTeX has detected Font Size substituion differences.")
222 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
224 # check for "(see the transcript file for additional information)"
226 s1
, s2
= texrunner
.texmessageparsed
.split("(see the transcript file for additional information)", 1)
227 texrunner
.texmessageparsed
= s1
+ s2
228 except (IndexError, ValueError):
231 # check for "Output written on ...dvi (1 page, 220 bytes)."
232 dvipattern
= re
.compile(r
"Output written on %s\.dvi \((?P<page>\d+) pages?, \d+ bytes\)\." % texrunner
.texfilename
)
233 m
= dvipattern
.search(texrunner
.texmessageparsed
)
236 raise TexResultError("TeX dvifile messages expected", texrunner
)
237 if m
.group("page") != str(texrunner
.page
):
238 raise TexResultError("wrong number of pages reported", texrunner
)
239 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
242 s1
, s2
= texrunner
.texmessageparsed
.split("No pages of output.", 1)
243 texrunner
.texmessageparsed
= s1
+ s2
244 except (IndexError, ValueError):
245 raise TexResultError("no dvifile expected", texrunner
)
247 # check for "Transcript written on ...log."
249 s1
, s2
= texrunner
.texmessageparsed
.split("Transcript written on %s.log." % texrunner
.texfilename
, 1)
250 texrunner
.texmessageparsed
= s1
+ s2
251 except (IndexError, ValueError):
252 raise TexResultError("TeX logfile message expected", texrunner
)
255 class _texmessageemptylines(texmessage
):
256 """validates "*-only" (TeX/LaTeX input marker in interactive mode) and empty lines
257 also clear TeX interactive mode warning (Please type a command or say `\\end')
260 __implements__
= _Itexmessage
262 def check(self
, texrunner
):
263 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.replace(r
"(Please type a command or say `\end')", "")
264 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.replace("*\n", "")
265 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.replace("\n", "")
268 class _texmessageload(texmessage
):
269 """validates inclusion of arbitrary files
270 - the matched pattern is "(<filename> <arbitrary other stuff>)", where
271 <fielname> is a readable file and other stuff can be anything
272 - "(" and ")" must be used consistent (otherwise this validator just does nothing)
273 - this is not always wanted, but we just assume that file inclusion is fine"""
275 __implements__
= _Itexmessage
277 pattern
= re
.compile(r
" *\((?P<filename>[^()\s\n]+)[^()]*\) *")
279 def baselevels(self
, s
, maxlevel
=1, brackets
="()"):
280 """strip parts of a string above a given bracket level
281 - return a modified (some parts might be removed) version of the string s
282 where all parts inside brackets with level higher than maxlevel are
284 - if brackets do not match (number of left and right brackets is wrong
285 or at some points there were more right brackets than left brackets)
286 just return the unmodified string"""
293 if level
> highestlevel
:
295 if level
<= maxlevel
:
299 if level
== 0 and highestlevel
> 0:
302 def check(self
, texrunner
):
303 lowestbracketlevel
= self
.baselevels(texrunner
.texmessageparsed
)
304 if lowestbracketlevel
is not None:
305 m
= self
.pattern
.search(lowestbracketlevel
)
307 if os
.access(m
.group("filename"), os
.R_OK
):
308 lowestbracketlevel
= lowestbracketlevel
[:m
.start()] + lowestbracketlevel
[m
.end():]
311 m
= self
.pattern
.search(lowestbracketlevel
)
313 texrunner
.texmessageparsed
= lowestbracketlevel
316 class _texmessageloadfd(_texmessageload
):
317 """validates the inclusion of font description files (fd-files)
318 - works like _texmessageload
319 - filename must end with .fd and no further text is allowed"""
321 pattern
= re
.compile(r
" *\((?P<filename>[^)]+.fd)\) *")
324 class _texmessagegraphicsload(_texmessageload
):
325 """validates the inclusion of files as the graphics packages writes it
326 - works like _texmessageload, but using "<" and ">" as delimiters
327 - filename must end with .eps and no further text is allowed"""
329 pattern
= re
.compile(r
" *<(?P<filename>[^>]+.eps)> *")
331 def baselevels(self
, s
, brackets
="<>", **args
):
332 return _texmessageload
.baselevels(self
, s
, brackets
=brackets
, **args
)
335 class _texmessageignore(_texmessageload
):
336 """validates any TeX/LaTeX response
337 - this might be used, when the expression is ok, but no suitable texmessage
339 - PLEASE: - consider writing suitable tex message parsers
340 - share your ideas/problems/solutions with others (use the PyX mailing lists)"""
342 __implements__
= _Itexmessage
344 def check(self
, texrunner
):
345 texrunner
.texmessageparsed
= ""
348 class _texmessagewarning(_texmessageload
):
349 """validates any TeX/LaTeX response
350 - this might be used, when the expression is ok, but no suitable texmessage
352 - PLEASE: - consider writing suitable tex message parsers
353 - share your ideas/problems/solutions with others (use the PyX mailing lists)"""
355 __implements__
= _Itexmessage
357 def check(self
, texrunner
):
358 if len(texrunner
.texmessageparsed
):
359 texrunner
.texmessageparsed
= ""
360 raise TexResultWarning("TeX result is ignored", texrunner
)
363 texmessage
.start
= _texmessagestart()
364 texmessage
.noaux
= _texmessagenoaux()
365 texmessage
.inputmarker
= _texmessageinputmarker()
366 texmessage
.pyxbox
= _texmessagepyxbox()
367 texmessage
.pyxpageout
= _texmessagepyxpageout()
368 texmessage
.texend
= _texmessagetexend()
369 texmessage
.emptylines
= _texmessageemptylines()
370 texmessage
.load
= _texmessageload()
371 texmessage
.loadfd
= _texmessageloadfd()
372 texmessage
.graphicsload
= _texmessagegraphicsload()
373 texmessage
.ignore
= _texmessageignore()
374 texmessage
.warning
= _texmessagewarning()
375 texmessage
.fontsubstitution
= _texmessagefontsubstitution()
378 ###############################################################################
380 ###############################################################################
382 _textattrspreamble
= ""
385 "a textattr defines a apply method, which modifies a (La)TeX expression"
387 class halign(attr
.exclusiveattr
, textattr
):
389 def __init__(self
, hratio
):
391 attr
.exclusiveattr
.__init
__(self
, halign
)
393 def apply(self
, expr
):
394 return r
"\gdef\PyXHAlign{%.5f}%s" % (self
.hratio
, expr
)
396 halign
.center
= halign(0.5)
397 halign
.right
= halign(1)
398 halign
.clear
= attr
.clearclass(halign
)
399 halign
.left
= halign
.clear
402 class _localattr
: pass
404 class _mathmode(attr
.attr
, textattr
, _localattr
):
407 def apply(self
, expr
):
408 return r
"$\displaystyle{%s}$" % expr
410 mathmode
= _mathmode()
411 nomathmode
= attr
.clearclass(_mathmode
)
414 class _phantom(attr
.attr
, textattr
, _localattr
):
417 def apply(self
, expr
):
418 return r
"\phantom{%s}" % expr
421 nophantom
= attr
.clearclass(_phantom
)
424 defaultsizelist
= ["normalsize", "large", "Large", "LARGE", "huge", "Huge", None, "tiny", "scriptsize", "footnotesize", "small"]
426 class size(attr
.sortbeforeattr
, textattr
, _localattr
):
429 def __init__(self
, sizeindex
=None, sizename
=None, sizelist
=defaultsizelist
):
430 if (sizeindex
is None and sizename
is None) or (sizeindex
is not None and sizename
is not None):
431 raise RuntimeError("either specify sizeindex or sizename")
432 attr
.sortbeforeattr
.__init
__(self
, [_mathmode
])
433 if sizeindex
is not None:
434 if sizeindex
>= 0 and sizeindex
< sizelist
.index(None):
435 self
.size
= sizelist
[sizeindex
]
436 elif sizeindex
< 0 and sizeindex
+ len(sizelist
) > sizelist
.index(None):
437 self
.size
= sizelist
[sizeindex
]
439 raise IndexError("index out of sizelist range")
443 def apply(self
, expr
):
444 return r
"\%s{%s}" % (self
.size
, expr
)
447 size
.scriptsize
= size
.script
= size(-3)
448 size
.footnotesize
= size
.footnote
= size(-2)
449 size
.small
= size(-1)
450 size
.normalsize
= size
.normal
= size(0)
456 size
.clear
= attr
.clearclass(size
)
459 _textattrspreamble
+= "\\newbox\\PyXBoxVBox%\n\\newdimen\PyXDimenVBox%\n"
461 class parbox_pt(attr
.sortbeforeexclusiveattr
, textattr
):
467 def __init__(self
, width
, baseline
=top
):
469 self
.baseline
= baseline
470 attr
.sortbeforeexclusiveattr
.__init
__(self
, parbox_pt
, [_localattr
])
472 def apply(self
, expr
):
473 if self
.baseline
== self
.top
:
474 return r
"\linewidth%.5ftruept\vtop{\hsize\linewidth{}%s}" % (self
.width
* 72.27 / 72, expr
)
475 elif self
.baseline
== self
.middle
:
476 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
)
477 elif self
.baseline
== self
.bottom
:
478 return r
"\linewidth%.5ftruept\vbox{\hsize\linewidth{}%s}" % (self
.width
* 72.27 / 72, expr
)
480 RuntimeError("invalid baseline argument")
482 parbox_pt
.clear
= attr
.clearclass(parbox_pt
)
484 class parbox(parbox_pt
):
486 def __init__(self
, width
, **kwargs
):
487 parbox_pt
.__init
__(self
, unit
.topt(width
), **kwargs
)
489 parbox
.clear
= parbox_pt
.clear
492 _textattrspreamble
+= "\\newbox\\PyXBoxVAlign%\n\\newdimen\PyXDimenVAlign%\n"
494 class valign(attr
.sortbeforeexclusiveattr
, textattr
):
497 attr
.sortbeforeexclusiveattr
.__init
__(self
, valign
, [parbox_pt
, _localattr
])
499 class _valigntop(valign
):
501 def apply(self
, expr
):
502 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\lower\ht\PyXBoxVAlign\box\PyXBoxVAlign" % expr
504 class _valignmiddle(valign
):
506 def apply(self
, expr
):
507 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\PyXDimenVAlign=0.5\ht\PyXBoxVAlign\advance\PyXDimenVAlign by -0.5\dp\PyXBoxVAlign\lower\PyXDimenVAlign\box\PyXBoxVAlign" % expr
509 class _valignbottom(valign
):
511 def apply(self
, expr
):
512 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\raise\dp\PyXBoxVAlign\box\PyXBoxVAlign" % expr
514 valign
.top
= _valigntop()
515 valign
.middle
= _valignmiddle()
516 valign
.bottom
= _valignbottom()
517 valign
.clear
= attr
.clearclass(valign
)
518 valign
.baseline
= valign
.clear
521 class _vshift(attr
.sortbeforeattr
, textattr
):
524 attr
.sortbeforeattr
.__init
__(self
, [valign
, parbox_pt
, _localattr
])
526 class vshift(_vshift
):
527 "vertical down shift by a fraction of a character height"
529 def __init__(self
, lowerratio
, heightstr
="0"):
530 _vshift
.__init
__(self
)
531 self
.lowerratio
= lowerratio
532 self
.heightstr
= heightstr
534 def apply(self
, expr
):
535 return r
"\setbox0\hbox{{%s}}\lower%.5f\ht0\hbox{{%s}}" % (self
.heightstr
, self
.lowerratio
, expr
)
537 class _vshiftmathaxis(_vshift
):
538 "vertical down shift by the height of the math axis"
540 def apply(self
, expr
):
541 return r
"\setbox0\hbox{$\vcenter{\vrule width0pt}$}\lower\ht0\hbox{{%s}}" % expr
544 vshift
.bottomzero
= vshift(0)
545 vshift
.middlezero
= vshift(0.5)
546 vshift
.topzero
= vshift(1)
547 vshift
.mathaxis
= _vshiftmathaxis()
548 vshift
.clear
= attr
.clearclass(_vshift
)
551 ###############################################################################
553 ###############################################################################
556 class _readpipe(threading
.Thread
):
557 """threaded reader of TeX/LaTeX output
558 - sets an event, when a specific string in the programs output is found
559 - sets an event, when the terminal ends"""
561 def __init__(self
, pipe
, expectqueue
, gotevent
, gotqueue
, quitevent
):
562 """initialize the reader
563 - pipe: file to be read from
564 - expectqueue: keeps the next InputMarker to be wait for
565 - gotevent: the "got InputMarker" event
566 - gotqueue: a queue containing the lines recieved from TeX/LaTeX
567 - quitevent: the "end of terminal" event"""
568 threading
.Thread
.__init
__(self
)
569 self
.setDaemon(1) # don't care if the output might not be finished (nevertheless, it shouldn't happen)
571 self
.expectqueue
= expectqueue
572 self
.gotevent
= gotevent
573 self
.gotqueue
= gotqueue
574 self
.quitevent
= quitevent
580 read
= self
.pipe
.readline() # read, what comes in
582 self
.expect
= self
.expectqueue
.get_nowait() # read, what should be expected
586 # universal EOL handling (convert everything into unix like EOLs)
587 # XXX is this necessary on pipes?
588 read
= read
.replace("\r", "").replace("\n", "") + "\n"
589 self
.gotqueue
.put(read
) # report, whats read
590 if self
.expect
is not None and read
.find(self
.expect
) != -1:
591 self
.gotevent
.set() # raise the got event, when the output was expected (XXX: within a single line)
592 read
= self
.pipe
.readline() # read again
594 self
.expect
= self
.expectqueue
.get_nowait()
599 if self
.expect
is not None and self
.expect
.find("PyXInputMarker") != -1:
600 raise RuntimeError("TeX/LaTeX finished unexpectedly")
604 class textbox(box
.rect
, canvas
._canvas
):
605 """basically a box.rect, but it contains a text created by the texrunner
606 - texrunner._text and texrunner.text return such an object
607 - _textbox instances can be inserted into a canvas
608 - the output is contained in a page of the dvifile available thru the texrunner"""
609 # TODO: shouldn't all boxes become canvases? how about inserts then?
611 def __init__(self
, x
, y
, left
, right
, height
, depth
, finishdvi
, attrs
):
613 - finishdvi is a method to be called to get the dvicanvas
614 (e.g. the finishdvi calls the setdvicanvas method)
615 - attrs are fillstyles"""
618 self
.width
= left
+ right
621 self
.texttrafo
= trafo
.scale(unit
.scale
["x"]).translated(x
, y
)
622 box
.rect
.__init
__(self
, x
- left
, y
- depth
, left
+ right
, depth
+ height
, abscenter
= (left
, depth
))
623 canvas
._canvas
.__init
__(self
)
624 self
.finishdvi
= finishdvi
625 self
.dvicanvas
= None
627 self
.insertdvicanvas
= 0
629 def transform(self
, *trafos
):
630 if self
.insertdvicanvas
:
631 raise RuntimeError("can't apply transformation after dvicanvas was inserted")
632 box
.rect
.transform(self
, *trafos
)
634 self
.texttrafo
= trafo
* self
.texttrafo
636 def setdvicanvas(self
, dvicanvas
):
637 if self
.dvicanvas
is not None:
638 raise RuntimeError("multiple call to setdvicanvas")
639 self
.dvicanvas
= dvicanvas
641 def ensuredvicanvas(self
):
642 if self
.dvicanvas
is None:
644 assert self
.dvicanvas
is not None, "finishdvi is broken"
645 if not self
.insertdvicanvas
:
646 self
.insert(self
.dvicanvas
, [self
.texttrafo
])
647 self
.insertdvicanvas
= 1
649 def marker(self
, marker
):
650 self
.ensuredvicanvas()
651 return self
.texttrafo
.apply(*self
.dvicanvas
.markers
[marker
])
653 def registerPS(self
, registry
):
654 self
.ensuredvicanvas()
655 canvas
._canvas
.registerPS(self
, registry
)
657 def registerPDF(self
, registry
):
658 self
.ensuredvicanvas()
659 canvas
._canvas
.registerPDF(self
, registry
)
661 def outputPS(self
, file, writer
, context
):
662 self
.ensuredvicanvas()
663 canvas
._canvas
.outputPS(self
, file, writer
, context
)
665 def outputPDF(self
, file, writer
, context
):
666 self
.ensuredvicanvas()
667 canvas
._canvas
.outputPDF(self
, file, writer
, context
)
670 def _cleantmp(texrunner
):
671 """get rid of temporary files
672 - function to be registered by atexit
673 - files contained in usefiles are kept"""
674 if texrunner
.texruns
: # cleanup while TeX is still running?
675 texrunner
.expectqueue
.put_nowait(None) # do not expect any output anymore
676 if texrunner
.mode
== "latex": # try to immediately quit from TeX or LaTeX
677 texrunner
.texinput
.write("\n\\catcode`\\@11\\relax\\@@end\n")
679 texrunner
.texinput
.write("\n\\end\n")
680 texrunner
.texinput
.close() # close the input queue and
681 if not texrunner
.waitforevent(texrunner
.quitevent
): # wait for finish of the output
682 return # didn't got a quit from TeX -> we can't do much more
683 texrunner
.texruns
= 0
684 texrunner
.texdone
= 1
685 for usefile
in texrunner
.usefiles
:
686 extpos
= usefile
.rfind(".")
688 os
.rename(texrunner
.texfilename
+ usefile
[extpos
:], usefile
)
691 for file in glob
.glob("%s.*" % texrunner
.texfilename
):
696 if texrunner
.texdebug
is not None:
698 texrunner
.texdebug
.close()
699 texrunner
.texdebug
= None
705 """TeX/LaTeX interface
706 - runs TeX/LaTeX expressions instantly
707 - checks TeX/LaTeX response
708 - the instance variable texmessage stores the last TeX
710 - the instance variable texmessageparsed stores a parsed
711 version of texmessage; it should be empty after
712 texmessage.check was called, otherwise a TexResultError
714 - the instance variable errordebug controls the verbose
715 level of TexResultError"""
717 defaulttexmessagesstart
= [texmessage
.start
]
718 defaulttexmessagesdocclass
= [texmessage
.load
]
719 defaulttexmessagesbegindoc
= [texmessage
.load
, texmessage
.noaux
]
720 defaulttexmessagesend
= [texmessage
.texend
]
721 defaulttexmessagesdefaultpreamble
= [texmessage
.load
]
722 defaulttexmessagesdefaultrun
= [texmessage
.loadfd
, texmessage
.graphicsload
]
724 def __init__(self
, mode
="tex",
729 fontmaps
=config
.get("text", "fontmaps", "psfonts.map"),
730 waitfortex
=config
.getint("text", "waitfortex", 60),
731 showwaitfortex
=config
.getint("text", "showwaitfortex", 5),
732 texipc
=config
.getboolean("text", "texipc", 0),
739 texmessagesdocclass
=[],
740 texmessagesbegindoc
=[],
742 texmessagesdefaultpreamble
=[],
743 texmessagesdefaultrun
=[]):
745 if mode
!= "tex" and mode
!= "latex":
746 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
749 self
.docclass
= docclass
751 self
.usefiles
= usefiles
752 self
.fontmaps
= fontmaps
753 self
.waitfortex
= waitfortex
754 self
.showwaitfortex
= showwaitfortex
756 if texdebug
is not None:
757 if texdebug
[-4:] == ".tex":
758 self
.texdebug
= open(texdebug
, "w")
760 self
.texdebug
= open("%s.tex" % texdebug
, "w")
763 self
.dvidebug
= dvidebug
764 self
.errordebug
= errordebug
765 self
.dvicopy
= dvicopy
766 self
.pyxgraphics
= pyxgraphics
767 self
.texmessagesstart
= texmessagesstart
768 self
.texmessagesdocclass
= texmessagesdocclass
769 self
.texmessagesbegindoc
= texmessagesbegindoc
770 self
.texmessagesend
= texmessagesend
771 self
.texmessagesdefaultpreamble
= texmessagesdefaultpreamble
772 self
.texmessagesdefaultrun
= texmessagesdefaultrun
776 self
.preamblemode
= 1
780 self
.needdvitextboxes
= [] # when texipc-mode off
782 self
.textboxesincluded
= 0
783 savetempdir
= tempfile
.tempdir
784 tempfile
.tempdir
= os
.curdir
785 self
.texfilename
= os
.path
.basename(tempfile
.mktemp())
786 tempfile
.tempdir
= savetempdir
788 def waitforevent(self
, event
):
789 """waits verbosely with an timeout for an event
790 - observes an event while periodly while printing messages
791 - returns the status of the event (isSet)
792 - does not clear the event"""
793 if self
.showwaitfortex
:
796 while waited
< self
.waitfortex
and not hasevent
:
797 if self
.waitfortex
- waited
> self
.showwaitfortex
:
798 event
.wait(self
.showwaitfortex
)
799 waited
+= self
.showwaitfortex
801 event
.wait(self
.waitfortex
- waited
)
802 waited
+= self
.waitfortex
- waited
803 hasevent
= event
.isSet()
805 if waited
< self
.waitfortex
:
806 warnings
.warn("still waiting for %s after %i (of %i) seconds..." % (self
.mode
, waited
, self
.waitfortex
))
808 warnings
.warn("the timeout of %i seconds expired and %s did not respond." % (waited
, self
.mode
))
811 event
.wait(self
.waitfortex
)
814 def execute(self
, expr
, texmessages
):
815 """executes expr within TeX/LaTeX
816 - if self.texruns is not yet set, TeX/LaTeX is initialized,
817 self.texruns is set and self.preamblemode is set
818 - the method must not be called, when self.texdone is already set
819 - expr should be a string or None
820 - when expr is None, TeX/LaTeX is stopped, self.texruns is unset and
821 self.texdone becomes set
822 - when self.preamblemode is set, the expr is passed directly to TeX/LaTeX
823 - when self.preamblemode is unset, the expr is passed to \ProcessPyXBox
824 - texmessages is a list of texmessage instances"""
826 if self
.texdebug
is not None:
827 self
.texdebug
.write("%% PyX %s texdebug file\n" % version
.version
)
828 self
.texdebug
.write("%% mode: %s\n" % self
.mode
)
829 self
.texdebug
.write("%% date: %s\n" % time
.asctime(time
.localtime(time
.time())))
830 for usefile
in self
.usefiles
:
831 extpos
= usefile
.rfind(".")
833 os
.rename(usefile
, self
.texfilename
+ usefile
[extpos
:])
836 texfile
= open("%s.tex" % self
.texfilename
, "w") # start with filename -> creates dvi file with that name
837 texfile
.write("\\relax%\n")
844 self
.texinput
, self
.texoutput
= os
.popen4("%s%s %s" % (self
.mode
, ipcflag
, self
.texfilename
), "t", 0)
846 # XXX: workaround for MS Windows (bufsize = 0 makes trouble!?)
847 self
.texinput
, self
.texoutput
= os
.popen4("%s%s %s" % (self
.mode
, ipcflag
, self
.texfilename
), "t")
848 atexit
.register(_cleantmp
, self
)
849 self
.expectqueue
= Queue
.Queue(1) # allow for a single entry only -> keeps the next InputMarker to be wait for
850 self
.gotevent
= threading
.Event() # keeps the got inputmarker event
851 self
.gotqueue
= Queue
.Queue(0) # allow arbitrary number of entries
852 self
.quitevent
= threading
.Event() # keeps for end of terminal event
853 self
.readoutput
= _readpipe(self
.texoutput
, self
.expectqueue
, self
.gotevent
, self
.gotqueue
, self
.quitevent
)
855 self
.fontmap
= dvifile
.readfontmap(self
.fontmaps
.split())
856 oldpreamblemode
= self
.preamblemode
857 self
.preamblemode
= 1
858 self
.execute("\\scrollmode\n\\raiseerror%\n" # switch to and check scrollmode
859 "\\def\\PyX{P\\kern-.3em\\lower.5ex\hbox{Y}\kern-.18em X}%\n" # just the PyX Logo
860 "\\gdef\\PyXHAlign{0}%\n" # global PyXHAlign (0.0-1.0) for the horizontal alignment, default to 0
861 "\\newbox\\PyXBox%\n" # PyXBox will contain the output
862 "\\newbox\\PyXBoxHAligned%\n" # PyXBox will contain the horizontal aligned output
863 "\\newdimen\\PyXDimenHAlignLT%\n" # PyXDimenHAlignLT/RT will contain the left/right extent
864 "\\newdimen\\PyXDimenHAlignRT%\n" +
865 _textattrspreamble
+ # insert preambles for textattrs macros
866 "\\long\\def\\ProcessPyXBox#1#2{%\n" # the ProcessPyXBox definition (#1 is expr, #2 is page number)
867 "\\setbox\\PyXBox=\\hbox{{#1}}%\n" # push expression into PyXBox
868 "\\PyXDimenHAlignLT=\\PyXHAlign\\wd\\PyXBox%\n" # calculate the left/right extent
869 "\\PyXDimenHAlignRT=\\wd\\PyXBox%\n"
870 "\\advance\\PyXDimenHAlignRT by -\\PyXDimenHAlignLT%\n"
871 "\\gdef\\PyXHAlign{0}%\n" # reset the PyXHAlign to the default 0
872 "\\immediate\\write16{PyXBox:page=#2," # write page and extents of this box to stdout
873 "lt=\\the\\PyXDimenHAlignLT,"
874 "rt=\\the\\PyXDimenHAlignRT,"
875 "ht=\\the\\ht\\PyXBox,"
876 "dp=\\the\\dp\\PyXBox:}%\n"
877 "\\setbox\\PyXBoxHAligned=\\hbox{\\kern-\\PyXDimenHAlignLT\\box\\PyXBox}%\n" # align horizontally
878 "\\ht\\PyXBoxHAligned0pt%\n" # baseline alignment (hight to zero)
879 "{\\count0=80\\count1=121\\count2=88\\count3=#2\\shipout\\box\\PyXBoxHAligned}}%\n" # shipout PyXBox to Page 80.121.88.<page number>
880 "\\def\\PyXInput#1{\\immediate\\write16{PyXInputMarker:executeid=#1:}}%\n" # write PyXInputMarker to stdout
881 "\\def\\PyXMarker#1{\\hskip0pt\\special{PyX:marker #1}}%\n", # write PyXMarker special into the dvi-file
882 self
.defaulttexmessagesstart
+ self
.texmessagesstart
)
883 os
.remove("%s.tex" % self
.texfilename
)
884 if self
.mode
== "tex":
885 if len(self
.lfs
) > 4 and self
.lfs
[-4:] == ".lfs":
888 lfsname
= "%s.lfs" % self
.lfs
889 for fulllfsname
in [lfsname
,
890 os
.path
.join(siteconfig
.lfsdir
, lfsname
)]:
892 lfsfile
= open(fulllfsname
, "r")
893 lfsdef
= lfsfile
.read()
899 allfiles
= (glob
.glob("*.lfs") +
900 glob
.glob(os
.path
.join(siteconfig
.lfsdir
, "*.lfs")))
905 lfsnames
.append(os
.path
.basename(f
)[:-4])
910 raise IOError("file '%s' is not available or not readable. Available LaTeX font size files (*.lfs): %s" % (lfsname
, lfsnames
))
912 raise IOError("file '%s' is not available or not readable. No LaTeX font size files (*.lfs) available. Check your installation." % lfsname
)
913 self
.execute(lfsdef
, [])
914 self
.execute("\\normalsize%\n", [])
915 self
.execute("\\newdimen\\linewidth%\n", [])
916 elif self
.mode
== "latex":
918 pyxdef
= os
.path
.join(siteconfig
.sharedir
, "pyx.def")
920 open(pyxdef
, "r").close()
922 IOError("file 'pyx.def' is not available or not readable. Check your installation or turn off the pyxgraphics option.")
923 pyxdef
= os
.path
.abspath(pyxdef
).replace(os
.sep
, "/")
924 self
.execute("\\makeatletter%\n"
925 "\\let\\saveProcessOptions=\\ProcessOptions%\n"
926 "\\def\\ProcessOptions{%\n"
927 "\\def\\Gin@driver{" + pyxdef
+ "}%\n"
928 "\\def\\c@lor@namefile{dvipsnam.def}%\n"
929 "\\saveProcessOptions}%\n"
932 if self
.docopt
is not None:
933 self
.execute("\\documentclass[%s]{%s}" % (self
.docopt
, self
.docclass
),
934 self
.defaulttexmessagesdocclass
+ self
.texmessagesdocclass
)
936 self
.execute("\\documentclass{%s}" % self
.docclass
,
937 self
.defaulttexmessagesdocclass
+ self
.texmessagesdocclass
)
938 self
.preamblemode
= oldpreamblemode
940 if expr
is not None: # TeX/LaTeX should process expr
941 self
.expectqueue
.put_nowait("PyXInputMarker:executeid=%i:" % self
.executeid
)
942 if self
.preamblemode
:
943 self
.expr
= ("%s%%\n" % expr
+
944 "\\PyXInput{%i}%%\n" % self
.executeid
)
947 self
.expr
= ("\\ProcessPyXBox{%s%%\n}{%i}%%\n" % (expr
, self
.page
) +
948 "\\PyXInput{%i}%%\n" % self
.executeid
)
949 else: # TeX/LaTeX should be finished
950 self
.expectqueue
.put_nowait("Transcript written on %s.log" % self
.texfilename
)
951 if self
.mode
== "latex":
952 self
.expr
= "\\end{document}%\n"
954 self
.expr
= "\\end%\n"
955 if self
.texdebug
is not None:
956 self
.texdebug
.write(self
.expr
)
957 self
.texinput
.write(self
.expr
)
958 gotevent
= self
.waitforevent(self
.gotevent
)
959 self
.gotevent
.clear()
960 if expr
is None and gotevent
: # TeX/LaTeX should have finished
963 self
.texinput
.close() # close the input queue and
964 gotevent
= self
.waitforevent(self
.quitevent
) # wait for finish of the output
968 self
.texmessage
+= self
.gotqueue
.get_nowait()
971 self
.texmessageparsed
= self
.texmessage
974 texmessage
.inputmarker
.check(self
)
975 if not self
.preamblemode
:
976 texmessage
.pyxbox
.check(self
)
977 texmessage
.pyxpageout
.check(self
)
978 texmessages
= attr
.mergeattrs(texmessages
)
979 for t
in texmessages
:
982 except TexResultWarning
:
983 traceback
.print_exc()
984 texmessage
.emptylines
.check(self
)
985 if len(self
.texmessageparsed
):
986 raise TexResultError("unhandled TeX response (might be an error)", self
)
988 raise TexResultError("TeX didn't respond as expected within the timeout period (%i seconds)." % self
.waitfortex
, self
)
991 """finish TeX/LaTeX and read the dvifile
992 - this method ensures that all textboxes can access their
994 self
.execute(None, self
.defaulttexmessagesend
+ self
.texmessagesend
)
996 os
.system("dvicopy %(t)s.dvi %(t)s.dvicopy > %(t)s.dvicopyout 2> %(t)s.dvicopyerr" % {"t": self
.texfilename
})
997 dvifilename
= "%s.dvicopy" % self
.texfilename
999 dvifilename
= "%s.dvi" % self
.texfilename
1001 self
.dvifile
= dvifile
.dvifile(dvifilename
, self
.fontmap
, debug
=self
.dvidebug
)
1003 for box
in self
.needdvitextboxes
:
1004 box
.setdvicanvas(self
.dvifile
.readpage([ord("P"), ord("y"), ord("X"), page
, 0, 0, 0, 0, 0, 0]))
1006 if self
.dvifile
.readpage(None) is not None:
1007 raise RuntimeError("end of dvifile expected")
1009 self
.needdvitextboxes
= []
1011 def reset(self
, reinit
=0):
1012 "resets the tex runner to its initial state (upto its record to old dvi file(s))"
1015 if self
.texdebug
is not None:
1016 self
.texdebug
.write("%s\n%% preparing restart of %s\n" % ("%"*80, self
.mode
))
1021 self
.preamblemode
= 1
1022 for expr
, texmessages
in self
.preambles
:
1023 self
.execute(expr
, texmessages
)
1024 if self
.mode
== "latex":
1025 self
.execute("\\begin{document}", self
.defaulttexmessagesbegindoc
+ self
.texmessagesbegindoc
)
1026 self
.preamblemode
= 0
1029 self
.preamblemode
= 1
1031 def set(self
, mode
=None,
1038 showwaitfortex
=None,
1045 texmessagesstart
=None,
1046 texmessagesdocclass
=None,
1047 texmessagesbegindoc
=None,
1048 texmessagesend
=None,
1049 texmessagesdefaultpreamble
=None,
1050 texmessagesdefaultrun
=None):
1051 """provide a set command for TeX/LaTeX settings
1052 - TeX/LaTeX must not yet been started
1053 - especially needed for the defaultrunner, where no access to
1054 the constructor is available"""
1056 raise RuntimeError("set not allowed -- TeX/LaTeX already started")
1057 if mode
is not None:
1059 if mode
!= "tex" and mode
!= "latex":
1060 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
1064 if docclass
is not None:
1065 self
.docclass
= docclass
1066 if docopt
is not None:
1067 self
.docopt
= docopt
1068 if usefiles
is not None:
1069 self
.usefiles
= usefiles
1070 if fontmaps
is not None:
1071 self
.fontmaps
= fontmaps
1072 if waitfortex
is not None:
1073 self
.waitfortex
= waitfortex
1074 if showwaitfortex
is not None:
1075 self
.showwaitfortex
= showwaitfortex
1076 if texipc
is not None:
1077 self
.texipc
= texipc
1078 if texdebug
is not None:
1079 if self
.texdebug
is not None:
1080 self
.texdebug
.close()
1081 if texdebug
[-4:] == ".tex":
1082 self
.texdebug
= open(texdebug
, "w")
1084 self
.texdebug
= open("%s.tex" % texdebug
, "w")
1085 if dvidebug
is not None:
1086 self
.dvidebug
= dvidebug
1087 if errordebug
is not None:
1088 self
.errordebug
= errordebug
1089 if dvicopy
is not None:
1090 self
.dvicopy
= dvicopy
1091 if pyxgraphics
is not None:
1092 self
.pyxgraphics
= pyxgraphics
1093 if errordebug
is not None:
1094 self
.errordebug
= errordebug
1095 if texmessagesstart
is not None:
1096 self
.texmessagesstart
= texmessagesstart
1097 if texmessagesdocclass
is not None:
1098 self
.texmessagesdocclass
= texmessagesdocclass
1099 if texmessagesbegindoc
is not None:
1100 self
.texmessagesbegindoc
= texmessagesbegindoc
1101 if texmessagesend
is not None:
1102 self
.texmessagesend
= texmessagesend
1103 if texmessagesdefaultpreamble
is not None:
1104 self
.texmessagesdefaultpreamble
= texmessagesdefaultpreamble
1105 if texmessagesdefaultrun
is not None:
1106 self
.texmessagesdefaultrun
= texmessagesdefaultrun
1108 def preamble(self
, expr
, texmessages
=[]):
1109 r
"""put something into the TeX/LaTeX preamble
1110 - in LaTeX, this is done before the \begin{document}
1111 (you might use \AtBeginDocument, when you're in need for)
1112 - it is not allowed to call preamble after calling the
1113 text method for the first time (for LaTeX this is needed
1114 due to \begin{document}; in TeX it is forced for compatibility
1115 (you should be able to switch from TeX to LaTeX, if you want,
1116 without breaking something)
1117 - preamble expressions must not create any dvi output
1118 - args might contain texmessage instances"""
1119 if self
.texdone
or not self
.preamblemode
:
1120 raise RuntimeError("preamble calls disabled due to previous text calls")
1121 texmessages
= self
.defaulttexmessagesdefaultpreamble
+ self
.texmessagesdefaultpreamble
+ texmessages
1122 self
.execute(expr
, texmessages
)
1123 self
.preambles
.append((expr
, texmessages
))
1125 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:")
1127 def text(self
, x
, y
, expr
, textattrs
=[], texmessages
=[]):
1128 """create text by passing expr to TeX/LaTeX
1129 - returns a textbox containing the result from running expr thru TeX/LaTeX
1130 - the box center is set to x, y
1131 - *args may contain attr parameters, namely:
1132 - textattr instances
1133 - texmessage instances
1134 - trafo._trafo instances
1135 - style.fillstyle instances"""
1137 raise ValueError("None expression is invalid")
1139 self
.reset(reinit
=1)
1141 if self
.preamblemode
:
1142 if self
.mode
== "latex":
1143 self
.execute("\\begin{document}", self
.defaulttexmessagesbegindoc
+ self
.texmessagesbegindoc
)
1144 self
.preamblemode
= 0
1146 if self
.texipc
and self
.dvicopy
:
1147 raise RuntimeError("texipc and dvicopy can't be mixed up")
1148 textattrs
= attr
.mergeattrs(textattrs
) # perform cleans
1149 attr
.checkattrs(textattrs
, [textattr
, trafo
.trafo_pt
, style
.fillstyle
])
1150 trafos
= attr
.getattrs(textattrs
, [trafo
.trafo_pt
])
1151 fillstyles
= attr
.getattrs(textattrs
, [style
.fillstyle
])
1152 textattrs
= attr
.getattrs(textattrs
, [textattr
])
1153 # reverse loop over the merged textattrs (last is applied first)
1154 lentextattrs
= len(textattrs
)
1155 for i
in range(lentextattrs
):
1156 expr
= textattrs
[lentextattrs
-1-i
].apply(expr
)
1157 self
.execute(expr
, self
.defaulttexmessagesdefaultrun
+ self
.texmessagesdefaultrun
+ texmessages
)
1160 self
.dvifile
= dvifile
.dvifile("%s.dvi" % self
.texfilename
, self
.fontmap
, debug
=self
.dvidebug
)
1161 match
= self
.PyXBoxPattern
.search(self
.texmessage
)
1162 if not match
or int(match
.group("page")) != self
.page
:
1163 raise TexResultError("box extents not found", self
)
1164 left
, right
, height
, depth
= [float(xxx
)*72/72.27*unit
.x_pt
for xxx
in match
.group("lt", "rt", "ht", "dp")]
1165 box
= textbox(x
, y
, left
, right
, height
, depth
, self
.finishdvi
, fillstyles
)
1169 box
.setdvicanvas(self
.dvifile
.readpage([ord("P"), ord("y"), ord("X"), self
.page
, 0, 0, 0, 0, 0, 0]))
1171 self
.needdvitextboxes
.append(box
)
1174 def text_pt(self
, x
, y
, expr
, *args
, **kwargs
):
1175 return self
.text(x
* unit
.t_pt
, y
* unit
.t_pt
, expr
, *args
, **kwargs
)
1177 PyXVariableBoxPattern
= re
.compile(r
"PyXVariableBox:page=(?P<page>\d+),par=(?P<par>\d+),prevgraf=(?P<prevgraf>\d+):")
1179 def textboxes(self
, text
, pageshapes
):
1180 # this is some experimental code to put text into several boxes
1181 # while the bounding shape changes from box to box (rectangles only)
1182 # first we load sev.tex
1183 if not self
.textboxesincluded
:
1184 self
.execute(r
"\input textboxes.tex", [texmessage
.load
])
1185 self
.textboxesincluded
= 1
1186 # define page shapes
1187 pageshapes_str
= "\\hsize=%.5ftruept%%\n\\vsize=%.5ftruept%%\n" % (72.27/72*unit
.topt(pageshapes
[0][0]), 72.27/72*unit
.topt(pageshapes
[0][1]))
1188 pageshapes_str
+= "\\lohsizes={%\n"
1189 for hsize
, vsize
in pageshapes
[1:]:
1190 pageshapes_str
+= "{\\global\\hsize=%.5ftruept}%%\n" % (72.27/72*unit
.topt(hsize
))
1191 pageshapes_str
+= "{\\relax}%\n}%\n"
1192 pageshapes_str
+= "\\lovsizes={%\n"
1193 for hsize
, vsize
in pageshapes
[1:]:
1194 pageshapes_str
+= "{\\global\\vsize=%.5ftruept}%%\n" % (72.27/72*unit
.topt(vsize
))
1195 pageshapes_str
+= "{\\relax}%\n}%\n"
1201 self
.execute(pageshapes_str
, [])
1202 parnos_str
= "}{".join(parnos
)
1204 parnos_str
= "{%s}" % parnos_str
1205 parnos_str
= "\\parnos={%s{\\relax}}%%\n" % parnos_str
1206 self
.execute(parnos_str
, [])
1207 parshapes_str
= "\\parshapes={%%\n%s%%\n{\\relax}%%\n}%%\n" % "%\n".join(parshapes
)
1208 self
.execute(parshapes_str
, [])
1209 self
.execute("\\global\\count0=1%%\n"
1210 "\\global\\parno=0%%\n"
1211 "\\global\\myprevgraf=0%%\n"
1212 "\\global\\showprevgraf=0%%\n"
1213 "\\global\\outputtype=0%%\n"
1214 "\\global\\leastcost=10000000%%\n"
1216 "\\vfill\\supereject%%\n" % text
, [texmessage
.ignore
])
1218 if self
.dvifile
is None:
1219 self
.dvifile
= dvifile
.dvifile("%s.dvi" % self
.texfilename
, self
.fontmap
, debug
=self
.dvidebug
)
1221 raise RuntimeError("textboxes currently needs texipc")
1224 lastparshapes
= parshapes
1227 lastpar
= prevgraf
= -1
1228 m
= self
.PyXVariableBoxPattern
.search(self
.texmessage
)
1231 page
= int(m
.group("page"))
1232 assert page
== pages
1233 par
= int(m
.group("par"))
1234 prevgraf
= int(m
.group("prevgraf"))
1235 if page
<= len(pageshapes
):
1236 width
= 72.27/72*unit
.topt(pageshapes
[page
-1][0])
1238 width
= 72.27/72*unit
.topt(pageshapes
[-1][0])
1239 if page
< len(pageshapes
):
1240 nextwidth
= 72.27/72*unit
.topt(pageshapes
[page
][0])
1242 nextwidth
= 72.27/72*unit
.topt(pageshapes
[-1][0])
1245 # a new paragraph is to be broken
1246 parnos
.append(str(par
))
1247 parshape
= " 0pt ".join(["%.5ftruept" % width
for i
in range(prevgraf
)])
1249 parshape
= " 0pt " + parshape
1250 parshapes
.append("{\\parshape %i%s 0pt %.5ftruept}" % (prevgraf
+ 1, parshape
, nextwidth
))
1251 elif prevgraf
== lastprevgraf
:
1254 # we have to append the breaking of the previous paragraph
1255 oldparshape
= " ".join(parshapes
[-1].split(' ')[2:2+2*lastprevgraf
])
1256 oldparshape
= oldparshape
.split('}')[0]
1258 oldparshape
= " " + oldparshape
1259 parshape
= " 0pt ".join(["%.5ftruept" % width
for i
in range(prevgraf
- lastprevgraf
)])
1261 parshape
= " 0pt " + parshape
1264 parshapes
[-1] = "{\\parshape %i%s%s 0pt %.5ftruept}" % (prevgraf
+ 1, oldparshape
, parshape
, nextwidth
)
1266 lastprevgraf
= prevgraf
1268 m
= self
.PyXVariableBoxPattern
.search(self
.texmessage
, nextpos
)
1270 for i
in range(pages
):
1271 result
.append(self
.dvifile
.readpage([i
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]))
1272 if parnos
== lastparnos
and parshapes
== lastparshapes
:
1276 raise TexResultError("Too many loops in textboxes ", texrunner
)
1279 # the module provides an default texrunner and methods for direct access
1280 defaulttexrunner
= texrunner()
1281 reset
= defaulttexrunner
.reset
1282 set = defaulttexrunner
.set
1283 preamble
= defaulttexrunner
.preamble
1284 text
= defaulttexrunner
.text
1285 text_pt
= defaulttexrunner
.text_pt
1287 def escapestring(s
):
1288 """escape special TeX/LaTeX characters
1290 Returns a string, where some special characters of standard
1291 TeX/LaTeX are replaced by appropriate escaped versions. Note
1292 that we cannot handle the three ASCII characters '{', '}',
1293 and '\' that way, since they do not occure in the TeX default
1294 encoding and thus are more likely to need some special handling.
1295 All other ASCII characters should usually (but not always)
1298 # ASCII strings only
1303 s
= s
[:i
] + "\\" + s
[i
:]
1306 s
= s
[:i
] + r
"\string" + s
[i
:]