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
, sys
, atexit
, time
26 import config
, 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 m
= self
.startpattern
.search(texrunner
.texmessageparsed
)
108 raise TexResultError("TeX startup failed", texrunner
)
109 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[m
.end():]
111 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.split("%s.tex" % texrunner
.texfilename
, 1)[1]
112 except (IndexError, ValueError):
113 raise TexResultError("TeX running startup file failed", texrunner
)
115 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.split("*! Undefined control sequence.\n<*> \\raiseerror\n %\n", 1)[1]
116 except (IndexError, ValueError):
117 raise TexResultError("TeX scrollmode check failed", texrunner
)
120 class _texmessagenoaux(texmessage
):
121 """allows for LaTeXs no-aux-file warning"""
123 __implements__
= _Itexmessage
125 def check(self
, texrunner
):
127 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s.aux." % texrunner
.texfilename
, 1)
128 texrunner
.texmessageparsed
= s1
+ s2
129 except (IndexError, ValueError):
131 s1
, s2
= texrunner
.texmessageparsed
.split("No file %s%s%s.aux." % (os
.curdir
,
133 texrunner
.texfilename
), 1)
134 texrunner
.texmessageparsed
= s1
+ s2
135 except (IndexError, ValueError):
139 class _texmessageinputmarker(texmessage
):
140 """validates the PyXInputMarker"""
142 __implements__
= _Itexmessage
144 def check(self
, texrunner
):
146 s1
, s2
= texrunner
.texmessageparsed
.split("PyXInputMarker:executeid=%s:" % texrunner
.executeid
, 1)
147 texrunner
.texmessageparsed
= s1
+ s2
148 except (IndexError, ValueError):
149 raise TexResultError("PyXInputMarker expected", texrunner
)
152 class _texmessagepyxbox(texmessage
):
153 """validates the PyXBox output"""
155 __implements__
= _Itexmessage
157 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:")
159 def check(self
, texrunner
):
160 m
= self
.pattern
.search(texrunner
.texmessageparsed
)
161 if m
and m
.group("page") == str(texrunner
.page
):
162 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
[:m
.start()] + texrunner
.texmessageparsed
[m
.end():]
164 raise TexResultError("PyXBox expected", texrunner
)
167 class _texmessagepyxpageout(texmessage
):
168 """validates the dvi shipout message (writing a page to the dvi file)"""
170 __implements__
= _Itexmessage
172 def check(self
, texrunner
):
174 s1
, s2
= texrunner
.texmessageparsed
.split("[80.121.88.%s]" % texrunner
.page
, 1)
175 texrunner
.texmessageparsed
= s1
+ s2
176 except (IndexError, ValueError):
177 raise TexResultError("PyXPageOutMarker expected", texrunner
)
180 class _texmessagetexend(texmessage
):
181 """validates TeX/LaTeX finish"""
183 __implements__
= _Itexmessage
185 def check(self
, texrunner
):
187 s1
, s2
= texrunner
.texmessageparsed
.split("(%s.aux)" % texrunner
.texfilename
, 1)
188 texrunner
.texmessageparsed
= s1
+ s2
189 except (IndexError, ValueError):
191 s1
, s2
= texrunner
.texmessageparsed
.split("(%s%s%s.aux)" % (os
.curdir
,
193 texrunner
.texfilename
), 1)
194 texrunner
.texmessageparsed
= s1
+ s2
195 except (IndexError, ValueError):
198 s1
, s2
= texrunner
.texmessageparsed
.split("(see the transcript file for additional information)", 1)
199 texrunner
.texmessageparsed
= s1
+ s2
200 except (IndexError, ValueError):
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 s1
, s2
= texrunner
.texmessageparsed
.split("Transcript written on %s.log." % texrunner
.texfilename
, 1)
218 texrunner
.texmessageparsed
= s1
+ s2
219 except (IndexError, ValueError):
220 raise TexResultError("TeX logfile message expected", texrunner
)
223 class _texmessageemptylines(texmessage
):
224 """validates "*-only" (TeX/LaTeX input marker in interactive mode) and empty lines
225 also clear TeX interactive mode warning (Please type a command or say `\\end')
228 __implements__
= _Itexmessage
230 def check(self
, texrunner
):
231 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.replace(r
"(Please type a command or say `\end')", "")
232 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.replace("*\n", "")
233 texrunner
.texmessageparsed
= texrunner
.texmessageparsed
.replace("\n", "")
236 class _texmessageload(texmessage
):
237 """validates inclusion of arbitrary files
238 - the matched pattern is "(<filename> <arbitrary other stuff>)", where
239 <fielname> is a readable file and other stuff can be anything
240 - "(" and ")" must be used consistent (otherwise this validator just does nothing)
241 - this is not always wanted, but we just assume that file inclusion is fine"""
243 __implements__
= _Itexmessage
245 pattern
= re
.compile(r
" *\((?P<filename>[^()\s\n]+)[^()]*\) *")
247 def baselevels(self
, s
, maxlevel
=1, brackets
="()"):
248 """strip parts of a string above a given bracket level
249 - return a modified (some parts might be removed) version of the string s
250 where all parts inside brackets with level higher than maxlevel are
252 - if brackets do not match (number of left and right brackets is wrong
253 or at some points there were more right brackets than left brackets)
254 just return the unmodified string"""
261 if level
> highestlevel
:
263 if level
<= maxlevel
:
267 if level
== 0 and highestlevel
> 0:
270 def check(self
, texrunner
):
271 lowestbracketlevel
= self
.baselevels(texrunner
.texmessageparsed
)
272 if lowestbracketlevel
is not None:
273 m
= self
.pattern
.search(lowestbracketlevel
)
275 if os
.access(m
.group("filename"), os
.R_OK
):
276 lowestbracketlevel
= lowestbracketlevel
[:m
.start()] + lowestbracketlevel
[m
.end():]
279 m
= self
.pattern
.search(lowestbracketlevel
)
281 texrunner
.texmessageparsed
= lowestbracketlevel
284 class _texmessageloadfd(_texmessageload
):
285 """validates the inclusion of font description files (fd-files)
286 - works like _texmessageload
287 - filename must end with .fd and no further text is allowed"""
289 pattern
= re
.compile(r
" *\((?P<filename>[^)]+.fd)\) *")
292 class _texmessagegraphicsload(_texmessageload
):
293 """validates the inclusion of files as the graphics packages writes it
294 - works like _texmessageload, but using "<" and ">" as delimiters
295 - filename must end with .eps and no further text is allowed"""
297 pattern
= re
.compile(r
" *<(?P<filename>[^>]+.eps)> *")
299 def baselevels(self
, s
, brackets
="<>", **args
):
300 return _texmessageload
.baselevels(self
, s
, brackets
=brackets
, **args
)
303 class _texmessageignore(_texmessageload
):
304 """validates any TeX/LaTeX response
305 - this might be used, when the expression is ok, but no suitable texmessage
307 - PLEASE: - consider writing suitable tex message parsers
308 - share your ideas/problems/solutions with others (use the PyX mailing lists)"""
310 __implements__
= _Itexmessage
312 def check(self
, texrunner
):
313 texrunner
.texmessageparsed
= ""
316 class _texmessagewarning(_texmessageload
):
317 """validates any TeX/LaTeX response
318 - this might be used, when the expression is ok, but no suitable texmessage
320 - PLEASE: - consider writing suitable tex message parsers
321 - share your ideas/problems/solutions with others (use the PyX mailing lists)"""
323 __implements__
= _Itexmessage
325 def check(self
, texrunner
):
326 if len(texrunner
.texmessageparsed
):
327 texrunner
.texmessageparsed
= ""
328 raise TexResultWarning("TeX result is ignored", texrunner
)
331 texmessage
.start
= _texmessagestart()
332 texmessage
.noaux
= _texmessagenoaux()
333 texmessage
.inputmarker
= _texmessageinputmarker()
334 texmessage
.pyxbox
= _texmessagepyxbox()
335 texmessage
.pyxpageout
= _texmessagepyxpageout()
336 texmessage
.texend
= _texmessagetexend()
337 texmessage
.emptylines
= _texmessageemptylines()
338 texmessage
.load
= _texmessageload()
339 texmessage
.loadfd
= _texmessageloadfd()
340 texmessage
.graphicsload
= _texmessagegraphicsload()
341 texmessage
.ignore
= _texmessageignore()
342 texmessage
.warning
= _texmessagewarning()
345 ###############################################################################
347 ###############################################################################
349 _textattrspreamble
= ""
352 "a textattr defines a apply method, which modifies a (La)TeX expression"
354 class halign(attr
.exclusiveattr
, textattr
):
356 def __init__(self
, hratio
):
358 attr
.exclusiveattr
.__init
__(self
, halign
)
360 def apply(self
, expr
):
361 return r
"\gdef\PyXHAlign{%.5f}%s" % (self
.hratio
, expr
)
363 halign
.center
= halign(0.5)
364 halign
.right
= halign(1)
365 halign
.clear
= attr
.clearclass(halign
)
366 halign
.left
= halign
.clear
369 class _localattr
: pass
371 class _mathmode(attr
.attr
, textattr
, _localattr
):
374 def apply(self
, expr
):
375 return r
"$\displaystyle{%s}$" % expr
377 mathmode
= _mathmode()
378 nomathmode
= attr
.clearclass(_mathmode
)
381 defaultsizelist
= ["normalsize", "large", "Large", "LARGE", "huge", "Huge", None, "tiny", "scriptsize", "footnotesize", "small"]
383 class size(attr
.sortbeforeattr
, textattr
, _localattr
):
386 def __init__(self
, sizeindex
=None, sizename
=None, sizelist
=defaultsizelist
):
387 if (sizeindex
is None and sizename
is None) or (sizeindex
is not None and sizename
is not None):
388 raise RuntimeError("either specify sizeindex or sizename")
389 attr
.sortbeforeattr
.__init
__(self
, [_mathmode
])
390 if sizeindex
is not None:
391 if sizeindex
>= 0 and sizeindex
< sizelist
.index(None):
392 self
.size
= sizelist
[sizeindex
]
393 elif sizeindex
< 0 and sizeindex
+ len(sizelist
) > sizelist
.index(None):
394 self
.size
= sizelist
[sizeindex
]
396 raise IndexError("index out of sizelist range")
400 def apply(self
, expr
):
401 return r
"\%s{%s}" % (self
.size
, expr
)
404 size
.scriptsize
= size
.script
= size(-3)
405 size
.footnotesize
= size
.footnote
= size(-2)
406 size
.small
= size(-1)
407 size
.normalsize
= size
.normal
= size(0)
413 size
.clear
= attr
.clearclass(size
)
416 _textattrspreamble
+= "\\newbox\\PyXBoxVBox%\n\\newdimen\PyXDimenVBox%\n"
418 class parbox_pt(attr
.sortbeforeexclusiveattr
, textattr
):
424 def __init__(self
, width
, baseline
=top
):
426 self
.baseline
= baseline
427 attr
.sortbeforeexclusiveattr
.__init
__(self
, parbox_pt
, [_localattr
])
429 def apply(self
, expr
):
430 if self
.baseline
== self
.top
:
431 return r
"\linewidth%.5ftruept\vtop{\hsize\linewidth{}%s}" % (self
.width
* 72.27 / 72, expr
)
432 elif self
.baseline
== self
.middle
:
433 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
)
434 elif self
.baseline
== self
.bottom
:
435 return r
"\linewidth%.5ftruept\vbox{\hsize\linewidth{}%s}" % (self
.width
* 72.27 / 72, expr
)
437 RuntimeError("invalid baseline argument")
439 parbox_pt
.clear
= attr
.clearclass(parbox_pt
)
441 class parbox(parbox_pt
):
443 def __init__(self
, width
, **kwargs
):
444 parbox_pt
.__init
__(self
, unit
.topt(width
), **kwargs
)
446 parbox
.clear
= parbox_pt
.clear
449 _textattrspreamble
+= "\\newbox\\PyXBoxVAlign%\n\\newdimen\PyXDimenVAlign%\n"
451 class valign(attr
.sortbeforeexclusiveattr
, textattr
):
454 attr
.sortbeforeexclusiveattr
.__init
__(self
, valign
, [parbox_pt
, _localattr
])
456 class _valigntop(valign
):
458 def apply(self
, expr
):
459 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\lower\ht\PyXBoxVAlign\box\PyXBoxVAlign" % expr
461 class _valignmiddle(valign
):
463 def apply(self
, expr
):
464 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\PyXDimenVAlign=0.5\ht\PyXBoxVAlign\advance\PyXDimenVAlign by -0.5\dp\PyXBoxVAlign\lower\PyXDimenVAlign\box\PyXBoxVAlign" % expr
466 class _valignbottom(valign
):
468 def apply(self
, expr
):
469 return r
"\setbox\PyXBoxVAlign=\hbox{{%s}}\raise\dp\PyXBoxVAlign\box\PyXBoxVAlign" % expr
471 valign
.top
= _valigntop()
472 valign
.middle
= _valignmiddle()
473 valign
.bottom
= _valignbottom()
474 valign
.clear
= attr
.clearclass(valign
)
475 valign
.baseline
= valign
.clear
478 class _vshift(attr
.sortbeforeattr
, textattr
):
481 attr
.sortbeforeattr
.__init
__(self
, [valign
, parbox_pt
, _localattr
])
483 class vshift(_vshift
):
484 "vertical down shift by a fraction of a character height"
486 def __init__(self
, lowerratio
, heightstr
="0"):
487 _vshift
.__init
__(self
)
488 self
.lowerratio
= lowerratio
489 self
.heightstr
= heightstr
491 def apply(self
, expr
):
492 return r
"\setbox0\hbox{{%s}}\lower%.5f\ht0\hbox{{%s}}" % (self
.heightstr
, self
.lowerratio
, expr
)
494 class _vshiftmathaxis(_vshift
):
495 "vertical down shift by the height of the math axis"
497 def apply(self
, expr
):
498 return r
"\setbox0\hbox{$\vcenter{\vrule width0pt}$}\lower\ht0\hbox{{%s}}" % expr
501 vshift
.bottomzero
= vshift(0)
502 vshift
.middlezero
= vshift(0.5)
503 vshift
.topzero
= vshift(1)
504 vshift
.mathaxis
= _vshiftmathaxis()
505 vshift
.clear
= attr
.clearclass(_vshift
)
508 ###############################################################################
510 ###############################################################################
513 class _readpipe(threading
.Thread
):
514 """threaded reader of TeX/LaTeX output
515 - sets an event, when a specific string in the programs output is found
516 - sets an event, when the terminal ends"""
518 def __init__(self
, pipe
, expectqueue
, gotevent
, gotqueue
, quitevent
):
519 """initialize the reader
520 - pipe: file to be read from
521 - expectqueue: keeps the next InputMarker to be wait for
522 - gotevent: the "got InputMarker" event
523 - gotqueue: a queue containing the lines recieved from TeX/LaTeX
524 - quitevent: the "end of terminal" event"""
525 threading
.Thread
.__init
__(self
)
526 self
.setDaemon(1) # don't care if the output might not be finished (nevertheless, it shouldn't happen)
528 self
.expectqueue
= expectqueue
529 self
.gotevent
= gotevent
530 self
.gotqueue
= gotqueue
531 self
.quitevent
= quitevent
537 read
= self
.pipe
.readline() # read, what comes in
539 self
.expect
= self
.expectqueue
.get_nowait() # read, what should be expected
543 # universal EOL handling (convert everything into unix like EOLs)
544 read
.replace("\r", "")
545 if not len(read
) or read
[-1] != "\n":
547 self
.gotqueue
.put(read
) # report, whats readed
548 if self
.expect
is not None and read
.find(self
.expect
) != -1:
549 self
.gotevent
.set() # raise the got event, when the output was expected (XXX: within a single line)
550 read
= self
.pipe
.readline() # read again
552 self
.expect
= self
.expectqueue
.get_nowait()
557 if self
.expect
is not None and self
.expect
.find("PyXInputMarker") != -1:
558 raise RuntimeError("TeX/LaTeX finished unexpectedly")
562 class textbox(box
.rect
, canvas
._canvas
):
563 """basically a box.rect, but it contains a text created by the texrunner
564 - texrunner._text and texrunner.text return such an object
565 - _textbox instances can be inserted into a canvas
566 - the output is contained in a page of the dvifile available thru the texrunner"""
567 # TODO: shouldn't all boxes become canvases? how about inserts then?
569 def __init__(self
, x
, y
, left
, right
, height
, depth
, finishdvi
, attrs
):
571 - finishdvi is a method to be called to get the dvicanvas
572 (e.g. the finishdvi calls the setdvicanvas method)
573 - attrs are fillstyles"""
576 self
.width
= left
+ right
579 self
.texttrafo
= trafo
.scale(unit
.scale
["x"]).translated(x
, y
)
580 box
.rect
.__init
__(self
, x
- left
, y
- depth
, left
+ right
, depth
+ height
, abscenter
= (left
, depth
))
581 canvas
._canvas
.__init
__(self
)
582 self
.finishdvi
= finishdvi
583 self
.dvicanvas
= None
585 self
.insertdvicanvas
= 0
587 def transform(self
, *trafos
):
588 if self
.insertdvicanvas
:
589 raise RuntimeError("can't apply transformation after dvicanvas was inserted")
590 box
.rect
.transform(self
, *trafos
)
592 self
.texttrafo
= trafo
* self
.texttrafo
594 def setdvicanvas(self
, dvicanvas
):
595 if self
.dvicanvas
is not None:
596 raise RuntimeError("multiple call to setdvicanvas")
597 self
.dvicanvas
= dvicanvas
599 def ensuredvicanvas(self
):
600 if self
.dvicanvas
is None:
602 assert self
.dvicanvas
is not None, "finishdvi is broken"
603 if not self
.insertdvicanvas
:
604 self
.insert(self
.dvicanvas
, [self
.texttrafo
])
605 self
.insertdvicanvas
= 1
607 def marker(self
, marker
):
608 self
.ensuredvicanvas()
609 return self
.texttrafo
.apply(*self
.dvicanvas
.markers
[marker
])
612 self
.ensuredvicanvas()
613 return canvas
._canvas
.prolog(self
)
615 def outputPS(self
, file):
616 self
.ensuredvicanvas()
617 canvas
._canvas
.outputPS(self
, file)
620 def _cleantmp(texrunner
):
621 """get rid of temporary files
622 - function to be registered by atexit
623 - files contained in usefiles are kept"""
624 if texrunner
.texruns
: # cleanup while TeX is still running?
625 texrunner
.texruns
= 0
626 texrunner
.texdone
= 1
627 texrunner
.expectqueue
.put_nowait(None) # do not expect any output anymore
628 if texrunner
.mode
== "latex": # try to immediately quit from TeX or LaTeX
629 texrunner
.texinput
.write("\n\\catcode`\\@11\\relax\\@@end\n")
631 texrunner
.texinput
.write("\n\\end\n")
632 texrunner
.texinput
.close() # close the input queue and
633 if not texrunner
.waitforevent(texrunner
.quitevent
): # wait for finish of the output
634 return # didn't got a quit from TeX -> we can't do much more
635 for usefile
in texrunner
.usefiles
:
636 extpos
= usefile
.rfind(".")
638 os
.rename(texrunner
.texfilename
+ usefile
[extpos
:], usefile
)
641 for file in glob
.glob("%s.*" % texrunner
.texfilename
):
646 if texrunner
.texdebug
is not None:
648 texrunner
.texdebug
.close()
649 texrunner
.texdebug
= None
655 """TeX/LaTeX interface
656 - runs TeX/LaTeX expressions instantly
657 - checks TeX/LaTeX response
658 - the instance variable texmessage stores the last TeX
660 - the instance variable texmessageparsed stores a parsed
661 version of texmessage; it should be empty after
662 texmessage.check was called, otherwise a TexResultError
664 - the instance variable errordebug controls the verbose
665 level of TexResultError"""
667 defaulttexmessagesstart
= [texmessage
.start
]
668 defaulttexmessagesdocclass
= [texmessage
.load
]
669 defaulttexmessagesbegindoc
= [texmessage
.load
, texmessage
.noaux
]
670 defaulttexmessagesend
= [texmessage
.texend
]
671 defaulttexmessagesdefaultpreamble
= [texmessage
.load
]
672 defaulttexmessagesdefaultrun
= [texmessage
.loadfd
, texmessage
.graphicsload
]
674 def __init__(self
, mode
="tex",
679 fontmaps
=config
.get("text", "fontmaps", "psfonts.map"),
680 waitfortex
=config
.getint("text", "waitfortex", 60),
681 showwaitfortex
=config
.getint("text", "showwaitfortex", 5),
682 texipc
=config
.getboolean("text", "texipc", 0),
689 texmessagesdocclass
=[],
690 texmessagesbegindoc
=[],
692 texmessagesdefaultpreamble
=[],
693 texmessagesdefaultrun
=[]):
695 if mode
!= "tex" and mode
!= "latex":
696 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
699 self
.docclass
= docclass
701 self
.usefiles
= usefiles
702 self
.fontmap
= dvifile
.readfontmap(fontmaps
.split())
703 self
.waitfortex
= waitfortex
704 self
.showwaitfortex
= showwaitfortex
706 if texdebug
is not None:
707 if texdebug
[-4:] == ".tex":
708 self
.texdebug
= open(texdebug
, "w")
710 self
.texdebug
= open("%s.tex" % texdebug
, "w")
713 self
.dvidebug
= dvidebug
714 self
.errordebug
= errordebug
715 self
.dvicopy
= dvicopy
716 self
.pyxgraphics
= pyxgraphics
717 self
.texmessagesstart
= texmessagesstart
718 self
.texmessagesdocclass
= texmessagesdocclass
719 self
.texmessagesbegindoc
= texmessagesbegindoc
720 self
.texmessagesend
= texmessagesend
721 self
.texmessagesdefaultpreamble
= texmessagesdefaultpreamble
722 self
.texmessagesdefaultrun
= texmessagesdefaultrun
726 self
.preamblemode
= 1
730 self
.needdvitextboxes
= [] # when texipc-mode off
732 self
.textboxesincluded
= 0
733 savetempdir
= tempfile
.tempdir
734 tempfile
.tempdir
= os
.curdir
735 self
.texfilename
= os
.path
.basename(tempfile
.mktemp())
736 tempfile
.tempdir
= savetempdir
738 def waitforevent(self
, event
):
739 """waits verbosely with an timeout for an event
740 - observes an event while periodly while printing messages
741 - returns the status of the event (isSet)
742 - does not clear the event"""
743 if self
.showwaitfortex
:
746 while waited
< self
.waitfortex
and not hasevent
:
747 if self
.waitfortex
- waited
> self
.showwaitfortex
:
748 event
.wait(self
.showwaitfortex
)
749 waited
+= self
.showwaitfortex
751 event
.wait(self
.waitfortex
- waited
)
752 waited
+= self
.waitfortex
- waited
753 hasevent
= event
.isSet()
755 if waited
< self
.waitfortex
:
756 sys
.stderr
.write("*** PyX Info: still waiting for %s after %i (of %i) seconds...\n" % (self
.mode
, waited
, self
.waitfortex
))
758 sys
.stderr
.write("*** PyX Error: the timeout of %i seconds expired and %s did not respond.\n" % (waited
, self
.mode
))
761 event
.wait(self
.waitfortex
)
764 def execute(self
, expr
, texmessages
):
765 """executes expr within TeX/LaTeX
766 - if self.texruns is not yet set, TeX/LaTeX is initialized,
767 self.texruns is set and self.preamblemode is set
768 - the method must not be called, when self.texdone is already set
769 - expr should be a string or None
770 - when expr is None, TeX/LaTeX is stopped, self.texruns is unset and
771 while self.texdone becomes set
772 - when self.preamblemode is set, the expr is passed directly to TeX/LaTeX
773 - when self.preamblemode is unset, the expr is passed to \ProcessPyXBox
774 - texmessages is a list of texmessage instances"""
776 if self
.texdebug
is not None:
777 self
.texdebug
.write("%% PyX %s texdebug file\n" % version
.version
)
778 self
.texdebug
.write("%% mode: %s\n" % self
.mode
)
779 self
.texdebug
.write("%% date: %s\n" % time
.asctime(time
.localtime(time
.time())))
780 for usefile
in self
.usefiles
:
781 extpos
= usefile
.rfind(".")
783 os
.rename(usefile
, self
.texfilename
+ usefile
[extpos
:])
786 texfile
= open("%s.tex" % self
.texfilename
, "w") # start with filename -> creates dvi file with that name
787 texfile
.write("\\relax%\n")
794 self
.texinput
, self
.texoutput
= os
.popen4("%s%s %s" % (self
.mode
, ipcflag
, self
.texfilename
), "t", 0)
796 # XXX: workaround for MS Windows (bufsize = 0 makes trouble!?)
797 self
.texinput
, self
.texoutput
= os
.popen4("%s%s %s" % (self
.mode
, ipcflag
, self
.texfilename
), "t")
798 atexit
.register(_cleantmp
, self
)
799 self
.expectqueue
= Queue
.Queue(1) # allow for a single entry only -> keeps the next InputMarker to be wait for
800 self
.gotevent
= threading
.Event() # keeps the got inputmarker event
801 self
.gotqueue
= Queue
.Queue(0) # allow arbitrary number of entries
802 self
.quitevent
= threading
.Event() # keeps for end of terminal event
803 self
.readoutput
= _readpipe(self
.texoutput
, self
.expectqueue
, self
.gotevent
, self
.gotqueue
, self
.quitevent
)
805 oldpreamblemode
= self
.preamblemode
806 self
.preamblemode
= 1
807 self
.execute("\\scrollmode\n\\raiseerror%\n" # switch to and check scrollmode
808 "\\def\\PyX{P\\kern-.3em\\lower.5ex\hbox{Y}\kern-.18em X}%\n" # just the PyX Logo
809 "\\gdef\\PyXHAlign{0}%\n" # global PyXHAlign (0.0-1.0) for the horizontal alignment, default to 0
810 "\\newbox\\PyXBox%\n" # PyXBox will contain the output
811 "\\newbox\\PyXBoxHAligned%\n" # PyXBox will contain the horizontal aligned output
812 "\\newdimen\\PyXDimenHAlignLT%\n" # PyXDimenHAlignLT/RT will contain the left/right extent
813 "\\newdimen\\PyXDimenHAlignRT%\n" +
814 _textattrspreamble
+ # insert preambles for textattrs macros
815 "\\long\\def\\ProcessPyXBox#1#2{%\n" # the ProcessPyXBox definition (#1 is expr, #2 is page number)
816 "\\setbox\\PyXBox=\\hbox{{#1}}%\n" # push expression into PyXBox
817 "\\PyXDimenHAlignLT=\\PyXHAlign\\wd\\PyXBox%\n" # calculate the left/right extent
818 "\\PyXDimenHAlignRT=\\wd\\PyXBox%\n"
819 "\\advance\\PyXDimenHAlignRT by -\\PyXDimenHAlignLT%\n"
820 "\\gdef\\PyXHAlign{0}%\n" # reset the PyXHAlign to the default 0
821 "\\immediate\\write16{PyXBox:page=#2," # write page and extents of this box to stdout
822 "lt=\\the\\PyXDimenHAlignLT,"
823 "rt=\\the\\PyXDimenHAlignRT,"
824 "ht=\\the\\ht\\PyXBox,"
825 "dp=\\the\\dp\\PyXBox:}%\n"
826 "\\setbox\\PyXBoxHAligned=\\hbox{\\kern-\\PyXDimenHAlignLT\\box\\PyXBox}%\n" # align horizontally
827 "\\ht\\PyXBoxHAligned0pt%\n" # baseline alignment (hight to zero)
828 "{\\count0=80\\count1=121\\count2=88\\count3=#2\\shipout\\box\\PyXBoxHAligned}}%\n" # shipout PyXBox to Page 80.121.88.<page number>
829 "\\def\\PyXInput#1{\\immediate\\write16{PyXInputMarker:executeid=#1:}}%\n" # write PyXInputMarker to stdout
830 "\\def\\PyXMarker#1{\\hskip0pt\\special{PyX:marker #1}}%\n", # write PyXMarker special into the dvi-file
831 self
.defaulttexmessagesstart
+ self
.texmessagesstart
)
832 os
.remove("%s.tex" % self
.texfilename
)
833 if self
.mode
== "tex":
834 if len(self
.lfs
) > 4 and self
.lfs
[-4:] == ".lfs":
837 lfsname
= "%s.lfs" % self
.lfs
838 for fulllfsname
in [lfsname
,
839 os
.path
.join(sys
.prefix
, "share", "pyx", lfsname
),
840 os
.path
.join(os
.path
.dirname(__file__
), "lfs", lfsname
)]:
842 lfsfile
= open(fulllfsname
, "r")
843 lfsdef
= lfsfile
.read()
849 allfiles
= (glob
.glob("*.lfs") +
850 glob
.glob(os
.path
.join(sys
.prefix
, "share", "pyx", "*.lfs")) +
851 glob
.glob(os
.path
.join(os
.path
.dirname(__file__
), "lfs", "*.lfs")))
856 lfsnames
.append(os
.path
.basename(f
)[:-4])
861 raise IOError("file '%s' is not available or not readable. Available LaTeX font size files (*.lfs): %s" % (lfsname
, lfsnames
))
863 raise IOError("file '%s' is not available or not readable. No LaTeX font size files (*.lfs) available. Check your installation." % lfsname
)
864 self
.execute(lfsdef
, [])
865 self
.execute("\\normalsize%\n", [])
866 self
.execute("\\newdimen\\linewidth%\n", [])
867 elif self
.mode
== "latex":
869 for pyxdef
in ["pyx.def",
870 os
.path
.join(sys
.prefix
, "share", "pyx", "pyx.def"),
871 os
.path
.join(os
.path
.dirname(__file__
), "..", "contrib", "pyx.def")]:
873 open(pyxdef
, "r").close()
878 IOError("file 'pyx.def' is not available or not readable. Check your installation or turn off the pyxgraphics option.")
879 pyxdef
= os
.path
.abspath(pyxdef
).replace(os
.sep
, "/")
880 self
.execute("\\makeatletter%\n"
881 "\\let\\saveProcessOptions=\\ProcessOptions%\n"
882 "\\def\\ProcessOptions{%\n"
883 "\\def\\Gin@driver{" + pyxdef
+ "}%\n"
884 "\\def\\c@lor@namefile{dvipsnam.def}%\n"
885 "\\saveProcessOptions}%\n"
888 if self
.docopt
is not None:
889 self
.execute("\\documentclass[%s]{%s}" % (self
.docopt
, self
.docclass
),
890 self
.defaulttexmessagesdocclass
+ self
.texmessagesdocclass
)
892 self
.execute("\\documentclass{%s}" % self
.docclass
,
893 self
.defaulttexmessagesdocclass
+ self
.texmessagesdocclass
)
894 self
.preamblemode
= oldpreamblemode
896 if expr
is not None: # TeX/LaTeX should process expr
897 self
.expectqueue
.put_nowait("PyXInputMarker:executeid=%i:" % self
.executeid
)
898 if self
.preamblemode
:
899 self
.expr
= ("%s%%\n" % expr
+
900 "\\PyXInput{%i}%%\n" % self
.executeid
)
903 self
.expr
= ("\\ProcessPyXBox{%s%%\n}{%i}%%\n" % (expr
, self
.page
) +
904 "\\PyXInput{%i}%%\n" % self
.executeid
)
905 else: # TeX/LaTeX should be finished
906 self
.expectqueue
.put_nowait("Transcript written on %s.log" % self
.texfilename
)
907 if self
.mode
== "latex":
908 self
.expr
= "\\end{document}%\n"
910 self
.expr
= "\\end%\n"
911 if self
.texdebug
is not None:
912 self
.texdebug
.write(self
.expr
)
913 self
.texinput
.write(self
.expr
)
914 gotevent
= self
.waitforevent(self
.gotevent
)
915 self
.gotevent
.clear()
916 if expr
is None and gotevent
: # TeX/LaTeX should have finished
919 self
.texinput
.close() # close the input queue and
920 gotevent
= self
.waitforevent(self
.quitevent
) # wait for finish of the output
924 self
.texmessage
+= self
.gotqueue
.get_nowait()
927 self
.texmessageparsed
= self
.texmessage
930 texmessage
.inputmarker
.check(self
)
931 if not self
.preamblemode
:
932 texmessage
.pyxbox
.check(self
)
933 texmessage
.pyxpageout
.check(self
)
934 texmessages
= attr
.mergeattrs(texmessages
)
935 # reverse loop over the merged texmessages (last is applied first)
936 lentexmessages
= len(texmessages
)
937 for i
in range(lentexmessages
):
939 texmessages
[lentexmessages
-1-i
].check(self
)
940 except TexResultWarning
:
941 traceback
.print_exc()
942 texmessage
.emptylines
.check(self
)
943 if len(self
.texmessageparsed
):
944 raise TexResultError("unhandled TeX response (might be an error)", self
)
946 raise TexResultError("TeX didn't respond as expected within the timeout period (%i seconds)." % self
.waitfortex
, self
)
949 """finish TeX/LaTeX and read the dvifile
950 - this method ensures that all textboxes can access their
952 self
.execute(None, self
.defaulttexmessagesend
+ self
.texmessagesend
)
954 os
.system("dvicopy %(t)s.dvi %(t)s.dvicopy > %(t)s.dvicopyout 2> %(t)s.dvicopyerr" % {"t": self
.texfilename
})
955 dvifilename
= "%s.dvicopy" % self
.texfilename
957 dvifilename
= "%s.dvi" % self
.texfilename
959 self
.dvifile
= dvifile
.dvifile(dvifilename
, self
.fontmap
, debug
=self
.dvidebug
)
961 for box
in self
.needdvitextboxes
:
962 box
.setdvicanvas(self
.dvifile
.readpage([ord("P"), ord("y"), ord("X"), page
, 0, 0, 0, 0, 0, 0]))
964 if self
.dvifile
.readpage(None) is not None:
965 raise RuntimeError("end of dvifile expected")
967 self
.needdvitextboxes
= []
969 def reset(self
, reinit
=0):
970 "resets the tex runner to its initial state (upto its record to old dvi file(s))"
973 if self
.texdebug
is not None:
974 self
.texdebug
.write("%s\n%% preparing restart of %s\n" % ("%"*80, self
.mode
))
979 self
.preamblemode
= 1
980 for expr
, texmessages
in self
.preambles
:
981 self
.execute(expr
, texmessages
)
982 if self
.mode
== "latex":
983 self
.execute("\\begin{document}", self
.defaulttexmessagesbegindoc
+ self
.texmessagesbegindoc
)
984 self
.preamblemode
= 0
987 self
.preamblemode
= 1
989 def set(self
, mode
=None,
1003 texmessagesstart
=None,
1004 texmessagesdocclass
=None,
1005 texmessagesbegindoc
=None,
1006 texmessagesend
=None,
1007 texmessagesdefaultpreamble
=None,
1008 texmessagesdefaultrun
=None):
1009 """provide a set command for TeX/LaTeX settings
1010 - TeX/LaTeX must not yet been started
1011 - especially needed for the defaultrunner, where no access to
1012 the constructor is available"""
1014 raise RuntimeError("set not allowed -- TeX/LaTeX already started")
1015 if mode
is not None:
1017 if mode
!= "tex" and mode
!= "latex":
1018 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
1022 if docclass
is not None:
1023 self
.docclass
= docclass
1024 if docopt
is not None:
1025 self
.docopt
= docopt
1026 if usefiles
is not None:
1027 self
.usefiles
= usefiles
1028 if fontmaps
is not None:
1029 self
.fontmap
= dvifile
.readfontmap(fontmaps
.split())
1030 if waitfortex
is not None:
1031 self
.waitfortex
= waitfortex
1032 if showwaitfortex
is not None:
1033 self
.showwaitfortex
= showwaitfortex
1034 if texipc
is not None:
1035 self
.texipc
= texipc
1036 if texdebug
is not None:
1037 if self
.texdebug
is not None:
1038 self
.texdebug
.close()
1039 if texdebug
[-4:] == ".tex":
1040 self
.texdebug
= open(texdebug
, "w")
1042 self
.texdebug
= open("%s.tex" % texdebug
, "w")
1043 if dvidebug
is not None:
1044 self
.dvidebug
= dvidebug
1045 if errordebug
is not None:
1046 self
.errordebug
= errordebug
1047 if dvicopy
is not None:
1048 self
.dvicopy
= dvicopy
1049 if pyxgraphics
is not None:
1050 self
.pyxgraphics
= pyxgraphics
1051 if errordebug
is not None:
1052 self
.errordebug
= errordebug
1053 if texmessagesstart
is not None:
1054 self
.texmessagesstart
= texmessagesstart
1055 if texmessagesdocclass
is not None:
1056 self
.texmessagesdocclass
= texmessagesdocclass
1057 if texmessagesbegindoc
is not None:
1058 self
.texmessagesbegindoc
= texmessagesbegindoc
1059 if texmessagesend
is not None:
1060 self
.texmessagesend
= texmessagesend
1061 if texmessagesdefaultpreamble
is not None:
1062 self
.texmessagesdefaultpreamble
= texmessagesdefaultpreamble
1063 if texmessagesdefaultrun
is not None:
1064 self
.texmessagesdefaultrun
= texmessagesdefaultrun
1066 def preamble(self
, expr
, texmessages
=[]):
1067 r
"""put something into the TeX/LaTeX preamble
1068 - in LaTeX, this is done before the \begin{document}
1069 (you might use \AtBeginDocument, when you're in need for)
1070 - it is not allowed to call preamble after calling the
1071 text method for the first time (for LaTeX this is needed
1072 due to \begin{document}; in TeX it is forced for compatibility
1073 (you should be able to switch from TeX to LaTeX, if you want,
1074 without breaking something)
1075 - preamble expressions must not create any dvi output
1076 - args might contain texmessage instances"""
1077 if self
.texdone
or not self
.preamblemode
:
1078 raise RuntimeError("preamble calls disabled due to previous text calls")
1079 texmessages
= self
.defaulttexmessagesdefaultpreamble
+ self
.texmessagesdefaultpreamble
+ texmessages
1080 self
.execute(expr
, texmessages
)
1081 self
.preambles
.append((expr
, texmessages
))
1083 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:")
1085 def text(self
, x
, y
, expr
, textattrs
=[], texmessages
=[]):
1086 """create text by passing expr to TeX/LaTeX
1087 - returns a textbox containing the result from running expr thru TeX/LaTeX
1088 - the box center is set to x, y
1089 - *args may contain attr parameters, namely:
1090 - textattr instances
1091 - texmessage instances
1092 - trafo._trafo instances
1093 - style.fillstyle instances"""
1095 raise ValueError("None expression is invalid")
1097 self
.reset(reinit
=1)
1099 if self
.preamblemode
:
1100 if self
.mode
== "latex":
1101 self
.execute("\\begin{document}", self
.defaulttexmessagesbegindoc
+ self
.texmessagesbegindoc
)
1102 self
.preamblemode
= 0
1104 if self
.texipc
and self
.dvicopy
:
1105 raise RuntimeError("texipc and dvicopy can't be mixed up")
1106 textattrs
= attr
.mergeattrs(textattrs
) # perform cleans
1107 attr
.checkattrs(textattrs
, [textattr
, trafo
.trafo_pt
, style
.fillstyle
])
1108 trafos
= attr
.getattrs(textattrs
, [trafo
.trafo_pt
])
1109 fillstyles
= attr
.getattrs(textattrs
, [style
.fillstyle
])
1110 textattrs
= attr
.getattrs(textattrs
, [textattr
])
1111 # reverse loop over the merged textattrs (last is applied first)
1112 lentextattrs
= len(textattrs
)
1113 for i
in range(lentextattrs
):
1114 expr
= textattrs
[lentextattrs
-1-i
].apply(expr
)
1115 self
.execute(expr
, self
.defaulttexmessagesdefaultrun
+ self
.texmessagesdefaultrun
+ texmessages
)
1118 self
.dvifile
= dvifile
.dvifile("%s.dvi" % self
.texfilename
, self
.fontmap
, debug
=self
.dvidebug
)
1119 match
= self
.PyXBoxPattern
.search(self
.texmessage
)
1120 if not match
or int(match
.group("page")) != self
.page
:
1121 raise TexResultError("box extents not found", self
)
1122 left
, right
, height
, depth
= [unit
.x_pt(float(xxx
)*72/72.27) for xxx
in match
.group("lt", "rt", "ht", "dp")]
1123 box
= textbox(x
, y
, left
, right
, height
, depth
, self
.finishdvi
, fillstyles
)
1127 box
.setdvicanvas(self
.dvifile
.readpage([ord("P"), ord("y"), ord("X"), self
.page
, 0, 0, 0, 0, 0, 0]))
1129 self
.needdvitextboxes
.append(box
)
1132 def text_pt(self
, x
, y
, expr
, *args
, **kwargs
):
1133 return self
.text(unit
.t_pt(x
), unit
.t_pt(y
), expr
, *args
, **kwargs
)
1135 PyXVariableBoxPattern
= re
.compile(r
"PyXVariableBox:page=(?P<page>\d+),par=(?P<par>\d+),prevgraf=(?P<prevgraf>\d+):")
1137 def textboxes(self
, text
, pageshapes
):
1138 # this is some experimental code to put text into several boxes
1139 # while the bounding shape changes from box to box (rectangles only)
1140 # first we load sev.tex
1141 if not self
.textboxesincluded
:
1142 self
.execute(r
"\input textboxes.tex", [texmessage
.load
])
1143 self
.textboxesincluded
= 1
1144 # define page shapes
1145 pageshapes_str
= "\\hsize=%.5ftruept%%\n\\vsize=%.5ftruept%%\n" % (72.27/72*unit
.topt(pageshapes
[0][0]), 72.27/72*unit
.topt(pageshapes
[0][1]))
1146 pageshapes_str
+= "\\lohsizes={%\n"
1147 for hsize
, vsize
in pageshapes
[1:]:
1148 pageshapes_str
+= "{\\global\\hsize=%.5ftruept}%%\n" % (72.27/72*unit
.topt(hsize
))
1149 pageshapes_str
+= "{\\relax}%\n}%\n"
1150 pageshapes_str
+= "\\lovsizes={%\n"
1151 for hsize
, vsize
in pageshapes
[1:]:
1152 pageshapes_str
+= "{\\global\\vsize=%.5ftruept}%%\n" % (72.27/72*unit
.topt(vsize
))
1153 pageshapes_str
+= "{\\relax}%\n}%\n"
1159 self
.execute(pageshapes_str
, [])
1160 parnos_str
= "}{".join(parnos
)
1162 parnos_str
= "{%s}" % parnos_str
1163 parnos_str
= "\\parnos={%s{\\relax}}%%\n" % parnos_str
1164 self
.execute(parnos_str
, [])
1165 parshapes_str
= "\\parshapes={%%\n%s%%\n{\\relax}%%\n}%%\n" % "%\n".join(parshapes
)
1166 self
.execute(parshapes_str
, [])
1167 self
.execute("\\global\\count0=1%%\n"
1168 "\\global\\parno=0%%\n"
1169 "\\global\\myprevgraf=0%%\n"
1170 "\\global\\showprevgraf=0%%\n"
1171 "\\global\\outputtype=0%%\n"
1172 "\\global\\leastcost=10000000%%\n"
1174 "\\vfill\\supereject%%\n" % text
, [texmessage
.ignore
])
1176 if self
.dvifile
is None:
1177 self
.dvifile
= dvifile
.dvifile("%s.dvi" % self
.texfilename
, self
.fontmap
, debug
=self
.dvidebug
)
1179 raise RuntimeError("textboxes currently needs texipc")
1182 lastparshapes
= parshapes
1185 lastpar
= prevgraf
= -1
1186 m
= self
.PyXVariableBoxPattern
.search(self
.texmessage
)
1189 page
= int(m
.group("page"))
1190 assert page
== pages
1191 par
= int(m
.group("par"))
1192 prevgraf
= int(m
.group("prevgraf"))
1193 if page
<= len(pageshapes
):
1194 width
= 72.27/72*unit
.topt(pageshapes
[page
-1][0])
1196 width
= 72.27/72*unit
.topt(pageshapes
[-1][0])
1197 if page
< len(pageshapes
):
1198 nextwidth
= 72.27/72*unit
.topt(pageshapes
[page
][0])
1200 nextwidth
= 72.27/72*unit
.topt(pageshapes
[-1][0])
1203 # a new paragraph is to be broken
1204 parnos
.append(str(par
))
1205 parshape
= " 0pt ".join(["%.5ftruept" % width
for i
in range(prevgraf
)])
1207 parshape
= " 0pt " + parshape
1208 parshapes
.append("{\\parshape %i%s 0pt %.5ftruept}" % (prevgraf
+ 1, parshape
, nextwidth
))
1209 elif prevgraf
== lastprevgraf
:
1212 # we have to append the breaking of the previous paragraph
1213 oldparshape
= " ".join(parshapes
[-1].split(' ')[2:2+2*lastprevgraf
])
1214 oldparshape
= oldparshape
.split('}')[0]
1216 oldparshape
= " " + oldparshape
1217 parshape
= " 0pt ".join(["%.5ftruept" % width
for i
in range(prevgraf
- lastprevgraf
)])
1219 parshape
= " 0pt " + parshape
1222 parshapes
[-1] = "{\\parshape %i%s%s 0pt %.5ftruept}" % (prevgraf
+ 1, oldparshape
, parshape
, nextwidth
)
1224 lastprevgraf
= prevgraf
1226 m
= self
.PyXVariableBoxPattern
.search(self
.texmessage
, nextpos
)
1228 for i
in range(pages
):
1229 result
.append(self
.dvifile
.readpage([i
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]))
1230 if parnos
== lastparnos
and parshapes
== lastparshapes
:
1234 raise TexResultError("Too many loops in textboxes ", texrunner
)
1237 # the module provides an default texrunner and methods for direct access
1238 defaulttexrunner
= texrunner()
1239 reset
= defaulttexrunner
.reset
1240 set = defaulttexrunner
.set
1241 preamble
= defaulttexrunner
.preamble
1242 text
= defaulttexrunner
.text
1243 text_pt
= defaulttexrunner
.text_pt