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_pt(box
.rect_pt
, 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"""
574 self
.texttrafo
= trafo
.translate_pt(x
, y
)
575 box
.rect_pt
.__init
__(self
, x
- left
, y
- depth
,
576 left
+ right
, depth
+ height
,
577 abscenter
= (left
, depth
))
578 canvas
._canvas
.__init
__(self
)
579 self
.finishdvi
= finishdvi
580 self
.dvicanvas
= None
582 self
.insertdvicanvas
= 0
584 def transform(self
, *trafos
):
585 if self
.insertdvicanvas
:
586 raise RuntimeError("can't apply transformation after dvicanvas was inserted")
587 box
.rect_pt
.transform(self
, *trafos
)
589 self
.texttrafo
= trafo
* self
.texttrafo
591 def setdvicanvas(self
, dvicanvas
):
592 if self
.dvicanvas
is not None:
593 raise RuntimeError("multiple call to setdvicanvas")
594 self
.dvicanvas
= dvicanvas
596 def ensuredvicanvas(self
):
597 if self
.dvicanvas
is None:
599 assert self
.dvicanvas
is not None, "finishdvi is broken"
600 if not self
.insertdvicanvas
:
601 self
.insert(self
.dvicanvas
, [self
.texttrafo
])
602 self
.insertdvicanvas
= 1
604 def marker(self
, marker
):
605 self
.ensuredvicanvas()
606 return self
.texttrafo
.apply(*self
.dvicanvas
.markers
[marker
])
609 self
.ensuredvicanvas()
610 return canvas
._canvas
.prolog(self
)
612 def write(self
, file):
613 self
.ensuredvicanvas()
614 canvas
._canvas
.write(self
, file)
617 class textbox(textbox_pt
):
619 def __init__(self
, x
, y
, left
, right
, height
, depth
, texrunner
, attrs
):
620 textbox_pt
.__init
__(self
, unit
.topt(x
), unit
.topt(y
), unit
.topt(left
), unit
.topt(right
),
621 unit
.topt(height
), unit
.topt(depth
), texrunner
, attrs
)
624 def _cleantmp(texrunner
):
625 """get rid of temporary files
626 - function to be registered by atexit
627 - files contained in usefiles are kept"""
628 if texrunner
.texruns
: # cleanup while TeX is still running?
629 texrunner
.texruns
= 0
630 texrunner
.texdone
= 1
631 texrunner
.expectqueue
.put_nowait(None) # do not expect any output anymore
632 if texrunner
.mode
== "latex": # try to immediately quit from TeX or LaTeX
633 texrunner
.texinput
.write("\n\\catcode`\\@11\\relax\\@@end\n")
635 texrunner
.texinput
.write("\n\\end\n")
636 texrunner
.texinput
.close() # close the input queue and
637 if not texrunner
.waitforevent(texrunner
.quitevent
): # wait for finish of the output
638 return # didn't got a quit from TeX -> we can't do much more
639 for usefile
in texrunner
.usefiles
:
640 extpos
= usefile
.rfind(".")
642 os
.rename(texrunner
.texfilename
+ usefile
[extpos
:], usefile
)
645 for file in glob
.glob("%s.*" % texrunner
.texfilename
):
650 if texrunner
.texdebug
is not None:
652 texrunner
.texdebug
.close()
653 texrunner
.texdebug
= None
659 """TeX/LaTeX interface
660 - runs TeX/LaTeX expressions instantly
661 - checks TeX/LaTeX response
662 - the instance variable texmessage stores the last TeX
664 - the instance variable texmessageparsed stores a parsed
665 version of texmessage; it should be empty after
666 texmessage.check was called, otherwise a TexResultError
668 - the instance variable errordebug controls the verbose
669 level of TexResultError"""
671 defaulttexmessagesstart
= [texmessage
.start
]
672 defaulttexmessagesdocclass
= [texmessage
.load
]
673 defaulttexmessagesbegindoc
= [texmessage
.load
, texmessage
.noaux
]
674 defaulttexmessagesend
= [texmessage
.texend
]
675 defaulttexmessagesdefaultpreamble
= [texmessage
.load
]
676 defaulttexmessagesdefaultrun
= [texmessage
.loadfd
, texmessage
.graphicsload
]
678 def __init__(self
, mode
="tex",
683 fontmaps
=config
.get("text", "fontmaps", "psfonts.map"),
684 waitfortex
=config
.getint("text", "waitfortex", 60),
685 showwaitfortex
=config
.getint("text", "showwaitfortex", 5),
686 texipc
=config
.getboolean("text", "texipc", 0),
693 texmessagesdocclass
=[],
694 texmessagesbegindoc
=[],
696 texmessagesdefaultpreamble
=[],
697 texmessagesdefaultrun
=[]):
699 if mode
!= "tex" and mode
!= "latex":
700 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
703 self
.docclass
= docclass
705 self
.usefiles
= usefiles
706 self
.fontmap
= dvifile
.readfontmap(fontmaps
.split())
707 self
.waitfortex
= waitfortex
708 self
.showwaitfortex
= showwaitfortex
710 if texdebug
is not None:
711 if texdebug
[-4:] == ".tex":
712 self
.texdebug
= open(texdebug
, "w")
714 self
.texdebug
= open("%s.tex" % texdebug
, "w")
717 self
.dvidebug
= dvidebug
718 self
.errordebug
= errordebug
719 self
.dvicopy
= dvicopy
720 self
.pyxgraphics
= pyxgraphics
721 self
.texmessagesstart
= texmessagesstart
722 self
.texmessagesdocclass
= texmessagesdocclass
723 self
.texmessagesbegindoc
= texmessagesbegindoc
724 self
.texmessagesend
= texmessagesend
725 self
.texmessagesdefaultpreamble
= texmessagesdefaultpreamble
726 self
.texmessagesdefaultrun
= texmessagesdefaultrun
730 self
.preamblemode
= 1
734 self
.needdvitextboxes
= [] # when texipc-mode off
736 savetempdir
= tempfile
.tempdir
737 tempfile
.tempdir
= os
.curdir
738 self
.texfilename
= os
.path
.basename(tempfile
.mktemp())
739 tempfile
.tempdir
= savetempdir
741 def waitforevent(self
, event
):
742 """waits verbosely with an timeout for an event
743 - observes an event while periodly while printing messages
744 - returns the status of the event (isSet)
745 - does not clear the event"""
746 if self
.showwaitfortex
:
749 while waited
< self
.waitfortex
and not hasevent
:
750 if self
.waitfortex
- waited
> self
.showwaitfortex
:
751 event
.wait(self
.showwaitfortex
)
752 waited
+= self
.showwaitfortex
754 event
.wait(self
.waitfortex
- waited
)
755 waited
+= self
.waitfortex
- waited
756 hasevent
= event
.isSet()
758 if waited
< self
.waitfortex
:
759 sys
.stderr
.write("*** PyX INFO: still waiting for %s after %i seconds...\n" % (self
.mode
, waited
))
761 sys
.stderr
.write("*** PyX ERROR: the timeout of %i seconds expired and %s did not respond.\n" % (waited
, self
.mode
))
764 event
.wait(self
.waitfortex
)
767 def execute(self
, expr
, texmessages
):
768 """executes expr within TeX/LaTeX
769 - if self.texruns is not yet set, TeX/LaTeX is initialized,
770 self.texruns is set and self.preamblemode is set
771 - the method must not be called, when self.texdone is already set
772 - expr should be a string or None
773 - when expr is None, TeX/LaTeX is stopped, self.texruns is unset and
774 while self.texdone becomes set
775 - when self.preamblemode is set, the expr is passed directly to TeX/LaTeX
776 - when self.preamblemode is unset, the expr is passed to \ProcessPyXBox
777 - texmessages is a list of texmessage instances"""
779 if self
.texdebug
is not None:
780 self
.texdebug
.write("%% PyX %s texdebug file\n" % version
.version
)
781 self
.texdebug
.write("%% mode: %s\n" % self
.mode
)
782 self
.texdebug
.write("%% date: %s\n" % time
.asctime(time
.localtime(time
.time())))
783 for usefile
in self
.usefiles
:
784 extpos
= usefile
.rfind(".")
786 os
.rename(usefile
, self
.texfilename
+ usefile
[extpos
:])
789 texfile
= open("%s.tex" % self
.texfilename
, "w") # start with filename -> creates dvi file with that name
790 texfile
.write("\\relax%\n")
797 self
.texinput
, self
.texoutput
= os
.popen4("%s%s %s" % (self
.mode
, ipcflag
, self
.texfilename
), "t", 0)
799 # XXX: workaround for MS Windows (bufsize = 0 makes trouble!?)
800 self
.texinput
, self
.texoutput
= os
.popen4("%s%s %s" % (self
.mode
, ipcflag
, self
.texfilename
), "t")
801 atexit
.register(_cleantmp
, self
)
802 self
.expectqueue
= Queue
.Queue(1) # allow for a single entry only -> keeps the next InputMarker to be wait for
803 self
.gotevent
= threading
.Event() # keeps the got inputmarker event
804 self
.gotqueue
= Queue
.Queue(0) # allow arbitrary number of entries
805 self
.quitevent
= threading
.Event() # keeps for end of terminal event
806 self
.readoutput
= _readpipe(self
.texoutput
, self
.expectqueue
, self
.gotevent
, self
.gotqueue
, self
.quitevent
)
808 oldpreamblemode
= self
.preamblemode
809 self
.preamblemode
= 1
810 self
.execute("\\scrollmode\n\\raiseerror%\n" # switch to and check scrollmode
811 "\\def\\PyX{P\\kern-.3em\\lower.5ex\hbox{Y}\kern-.18em X}%\n" # just the PyX Logo
812 "\\gdef\\PyXHAlign{0}%\n" # global PyXHAlign (0.0-1.0) for the horizontal alignment, default to 0
813 "\\newbox\\PyXBox%\n" # PyXBox will contain the output
814 "\\newbox\\PyXBoxHAligned%\n" # PyXBox will contain the horizontal aligned output
815 "\\newdimen\\PyXDimenHAlignLT%\n" # PyXDimenHAlignLT/RT will contain the left/right extent
816 "\\newdimen\\PyXDimenHAlignRT%\n" +
817 _textattrspreamble
+ # insert preambles for textattrs macros
818 "\\long\\def\\ProcessPyXBox#1#2{%\n" # the ProcessPyXBox definition (#1 is expr, #2 is page number)
819 "\\setbox\\PyXBox=\\hbox{{#1}}%\n" # push expression into PyXBox
820 "\\PyXDimenHAlignLT=\\PyXHAlign\\wd\\PyXBox%\n" # calculate the left/right extent
821 "\\PyXDimenHAlignRT=\\wd\\PyXBox%\n"
822 "\\advance\\PyXDimenHAlignRT by -\\PyXDimenHAlignLT%\n"
823 "\\gdef\\PyXHAlign{0}%\n" # reset the PyXHAlign to the default 0
824 "\\immediate\\write16{PyXBox:page=#2," # write page and extents of this box to stdout
825 "lt=\\the\\PyXDimenHAlignLT,"
826 "rt=\\the\\PyXDimenHAlignRT,"
827 "ht=\\the\\ht\\PyXBox,"
828 "dp=\\the\\dp\\PyXBox:}%\n"
829 "\\setbox\\PyXBoxHAligned=\\hbox{\\kern-\\PyXDimenHAlignLT\\box\\PyXBox}%\n" # align horizontally
830 "\\ht\\PyXBoxHAligned0pt%\n" # baseline alignment (hight to zero)
831 "{\\count0=80\\count1=121\\count2=88\\count3=#2\\shipout\\box\\PyXBoxHAligned}}%\n" # shipout PyXBox to Page 80.121.88.<page number>
832 "\\def\\PyXInput#1{\\immediate\\write16{PyXInputMarker:executeid=#1:}}%\n" # write PyXInputMarker to stdout
833 "\\def\\PyXMarker#1{\\special{PyX:marker #1}}%\n", # write PyXMarker special into the dvi-file
834 self
.defaulttexmessagesstart
+ self
.texmessagesstart
)
835 os
.remove("%s.tex" % self
.texfilename
)
836 if self
.mode
== "tex":
837 if len(self
.lfs
) > 4 and self
.lfs
[-4:] == ".lfs":
840 lfsname
= "%s.lfs" % self
.lfs
841 for fulllfsname
in [lfsname
,
842 os
.path
.join(sys
.prefix
, "share", "pyx", lfsname
),
843 os
.path
.join(os
.path
.dirname(__file__
), "lfs", lfsname
)]:
845 lfsfile
= open(fulllfsname
, "r")
846 lfsdef
= lfsfile
.read()
852 allfiles
= (glob
.glob("*.lfs") +
853 glob
.glob(os
.path
.join(sys
.prefix
, "share", "pyx", "*.lfs")) +
854 glob
.glob(os
.path
.join(os
.path
.dirname(__file__
), "lfs", "*.lfs")))
859 lfsnames
.append(os
.path
.basename(f
)[:-4])
864 raise IOError("file '%s' is not available or not readable. Available LaTeX font size files (*.lfs): %s" % (lfsname
, lfsnames
))
866 raise IOError("file '%s' is not available or not readable. No LaTeX font size files (*.lfs) available. Check your installation." % lfsname
)
867 self
.execute(lfsdef
, [])
868 self
.execute("\\normalsize%\n", [])
869 self
.execute("\\newdimen\\linewidth%\n", [])
870 elif self
.mode
== "latex":
872 for pyxdef
in ["pyx.def",
873 os
.path
.join(sys
.prefix
, "share", "pyx", "pyx.def"),
874 os
.path
.join(os
.path
.dirname(__file__
), "..", "contrib", "pyx.def")]:
876 open(pyxdef
, "r").close()
881 IOError("file 'pyx.def' is not available or not readable. Check your installation or turn off the pyxgraphics option.")
882 pyxdef
= os
.path
.abspath(pyxdef
).replace(os
.sep
, "/")
883 self
.execute("\\makeatletter%\n"
884 "\\let\\saveProcessOptions=\\ProcessOptions%\n"
885 "\\def\\ProcessOptions{%\n"
886 "\\def\\Gin@driver{" + pyxdef
+ "}%\n"
887 "\\def\\c@lor@namefile{dvipsnam.def}%\n"
888 "\\saveProcessOptions}%\n"
891 if self
.docopt
is not None:
892 self
.execute("\\documentclass[%s]{%s}" % (self
.docopt
, self
.docclass
),
893 self
.defaulttexmessagesdocclass
+ self
.texmessagesdocclass
)
895 self
.execute("\\documentclass{%s}" % self
.docclass
,
896 self
.defaulttexmessagesdocclass
+ self
.texmessagesdocclass
)
897 self
.preamblemode
= oldpreamblemode
899 if expr
is not None: # TeX/LaTeX should process expr
900 self
.expectqueue
.put_nowait("PyXInputMarker:executeid=%i:" % self
.executeid
)
901 if self
.preamblemode
:
902 self
.expr
= ("%s%%\n" % expr
+
903 "\\PyXInput{%i}%%\n" % self
.executeid
)
906 self
.expr
= ("\\ProcessPyXBox{%s%%\n}{%i}%%\n" % (expr
, self
.page
) +
907 "\\PyXInput{%i}%%\n" % self
.executeid
)
908 else: # TeX/LaTeX should be finished
909 self
.expectqueue
.put_nowait("Transcript written on %s.log" % self
.texfilename
)
910 if self
.mode
== "latex":
911 self
.expr
= "\\end{document}%\n"
913 self
.expr
= "\\end%\n"
914 if self
.texdebug
is not None:
915 self
.texdebug
.write(self
.expr
)
916 self
.texinput
.write(self
.expr
)
917 gotevent
= self
.waitforevent(self
.gotevent
)
918 self
.gotevent
.clear()
919 if expr
is None and gotevent
: # TeX/LaTeX should have finished
922 self
.texinput
.close() # close the input queue and
923 gotevent
= self
.waitforevent(self
.quitevent
) # wait for finish of the output
927 self
.texmessage
+= self
.gotqueue
.get_nowait()
930 self
.texmessageparsed
= self
.texmessage
933 texmessage
.inputmarker
.check(self
)
934 if not self
.preamblemode
:
935 texmessage
.pyxbox
.check(self
)
936 texmessage
.pyxpageout
.check(self
)
937 texmessages
= attr
.mergeattrs(texmessages
)
938 # reverse loop over the merged texmessages (last is applied first)
939 lentexmessages
= len(texmessages
)
940 for i
in range(lentexmessages
):
942 texmessages
[lentexmessages
-1-i
].check(self
)
943 except TexResultWarning
:
944 traceback
.print_exc()
945 texmessage
.emptylines
.check(self
)
946 if len(self
.texmessageparsed
):
947 raise TexResultError("unhandled TeX response (might be an error)", self
)
949 raise TexResultError("TeX didn't respond as expected within the timeout period (%i seconds)." % self
.waitfortex
, self
)
952 """finish TeX/LaTeX and read the dvifile
953 - this method ensures that all textboxes can access their
955 self
.execute(None, self
.defaulttexmessagesend
+ self
.texmessagesend
)
957 os
.system("dvicopy %(t)s.dvi %(t)s.dvicopy > %(t)s.dvicopyout 2> %(t)s.dvicopyerr" % {"t": self
.texfilename
})
958 dvifilename
= "%s.dvicopy" % self
.texfilename
960 dvifilename
= "%s.dvi" % self
.texfilename
962 self
.dvifile
= dvifile
.dvifile(dvifilename
, self
.fontmap
, debug
=self
.dvidebug
)
964 for box
in self
.needdvitextboxes
:
965 box
.setdvicanvas(self
.dvifile
.readpage([ord("P"), ord("y"), ord("X"), page
, 0, 0, 0, 0, 0, 0]))
967 if self
.dvifile
.readpage(None) is not None:
968 raise RuntimeError("end of dvifile expected")
970 self
.needdvitextboxes
= []
972 def reset(self
, reinit
=0):
973 "resets the tex runner to its initial state (upto its record to old dvi file(s))"
976 if self
.texdebug
is not None:
977 self
.texdebug
.write("%s\n%% preparing restart of %s\n" % ("%"*80, self
.mode
))
982 self
.preamblemode
= 1
983 for expr
, texmessages
in self
.preambles
:
984 self
.execute(expr
, texmessages
)
985 if self
.mode
== "latex":
986 self
.execute("\\begin{document}", self
.defaulttexmessagesbegindoc
+ self
.texmessagesbegindoc
)
987 self
.preamblemode
= 0
990 self
.preamblemode
= 1
992 def set(self
, mode
=None,
1006 texmessagesstart
=None,
1007 texmessagesdocclass
=None,
1008 texmessagesbegindoc
=None,
1009 texmessagesend
=None,
1010 texmessagesdefaultpreamble
=None,
1011 texmessagesdefaultrun
=None):
1012 """provide a set command for TeX/LaTeX settings
1013 - TeX/LaTeX must not yet been started
1014 - especially needed for the defaultrunner, where no access to
1015 the constructor is available"""
1017 raise RuntimeError("set not allowed -- TeX/LaTeX already started")
1018 if mode
is not None:
1020 if mode
!= "tex" and mode
!= "latex":
1021 raise ValueError("mode \"TeX\" or \"LaTeX\" expected")
1025 if docclass
is not None:
1026 self
.docclass
= docclass
1027 if docopt
is not None:
1028 self
.docopt
= docopt
1029 if usefiles
is not None:
1030 self
.usefiles
= usefiles
1031 if fontmaps
is not None:
1032 self
.fontmap
= dvifile
.readfontmap(fontmaps
.split())
1033 if waitfortex
is not None:
1034 self
.waitfortex
= waitfortex
1035 if showwaitfortex
is not None:
1036 self
.showwaitfortex
= showwaitfortex
1037 if texipc
is not None:
1038 self
.texipc
= texipc
1039 if texdebug
is not None:
1040 if self
.texdebug
is not None:
1041 self
.texdebug
.close()
1042 if texdebug
[-4:] == ".tex":
1043 self
.texdebug
= open(texdebug
, "w")
1045 self
.texdebug
= open("%s.tex" % texdebug
, "w")
1046 if dvidebug
is not None:
1047 self
.dvidebug
= dvidebug
1048 if errordebug
is not None:
1049 self
.errordebug
= errordebug
1050 if dvicopy
is not None:
1051 self
.dvicopy
= dvicopy
1052 if pyxgraphics
is not None:
1053 self
.pyxgraphics
= pyxgraphics
1054 if errordebug
is not None:
1055 self
.errordebug
= errordebug
1056 if texmessagesstart
is not None:
1057 self
.texmessagesstart
= texmessagesstart
1058 if texmessagesdocclass
is not None:
1059 self
.texmessagesdocclass
= texmessagesdocclass
1060 if texmessagesbegindoc
is not None:
1061 self
.texmessagesbegindoc
= texmessagesbegindoc
1062 if texmessagesend
is not None:
1063 self
.texmessagesend
= texmessagesend
1064 if texmessagesdefaultpreamble
is not None:
1065 self
.texmessagesdefaultpreamble
= texmessagesdefaultpreamble
1066 if texmessagesdefaultrun
is not None:
1067 self
.texmessagesdefaultrun
= texmessagesdefaultrun
1069 def preamble(self
, expr
, texmessages
=[]):
1070 r
"""put something into the TeX/LaTeX preamble
1071 - in LaTeX, this is done before the \begin{document}
1072 (you might use \AtBeginDocument, when you're in need for)
1073 - it is not allowed to call preamble after calling the
1074 text method for the first time (for LaTeX this is needed
1075 due to \begin{document}; in TeX it is forced for compatibility
1076 (you should be able to switch from TeX to LaTeX, if you want,
1077 without breaking something)
1078 - preamble expressions must not create any dvi output
1079 - args might contain texmessage instances"""
1080 if self
.texdone
or not self
.preamblemode
:
1081 raise RuntimeError("preamble calls disabled due to previous text calls")
1082 texmessages
= self
.defaulttexmessagesdefaultpreamble
+ self
.texmessagesdefaultpreamble
+ texmessages
1083 self
.execute(expr
, texmessages
)
1084 self
.preambles
.append((expr
, texmessages
))
1086 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:")
1088 def text_pt(self
, x
, y
, expr
, textattrs
=[], texmessages
=[]):
1089 """create text by passing expr to TeX/LaTeX
1090 - returns a textbox containing the result from running expr thru TeX/LaTeX
1091 - the box center is set to x, y
1092 - *args may contain attr parameters, namely:
1093 - textattr instances
1094 - texmessage instances
1095 - trafo._trafo instances
1096 - style.fillstyle instances"""
1098 raise ValueError("None expression is invalid")
1100 self
.reset(reinit
=1)
1102 if self
.preamblemode
:
1103 if self
.mode
== "latex":
1104 self
.execute("\\begin{document}", self
.defaulttexmessagesbegindoc
+ self
.texmessagesbegindoc
)
1105 self
.preamblemode
= 0
1107 if self
.texipc
and self
.dvicopy
:
1108 raise RuntimeError("texipc and dvicopy can't be mixed up")
1109 textattrs
= attr
.mergeattrs(textattrs
) # perform cleans
1110 attr
.checkattrs(textattrs
, [textattr
, trafo
.trafo_pt
, style
.fillstyle
])
1111 trafos
= attr
.getattrs(textattrs
, [trafo
.trafo_pt
])
1112 fillstyles
= attr
.getattrs(textattrs
, [style
.fillstyle
])
1113 textattrs
= attr
.getattrs(textattrs
, [textattr
])
1114 # reverse loop over the merged textattrs (last is applied first)
1115 lentextattrs
= len(textattrs
)
1116 for i
in range(lentextattrs
):
1117 expr
= textattrs
[lentextattrs
-1-i
].apply(expr
)
1118 self
.execute(expr
, self
.defaulttexmessagesdefaultrun
+ self
.texmessagesdefaultrun
+ texmessages
)
1121 self
.dvifile
= dvifile
.dvifile("%s.dvi" % self
.texfilename
, self
.fontmap
, debug
=self
.dvidebug
)
1122 match
= self
.PyXBoxPattern
.search(self
.texmessage
)
1123 if not match
or int(match
.group("page")) != self
.page
:
1124 raise TexResultError("box extents not found", self
)
1125 left
, right
, height
, depth
= map(lambda x
: float(x
) * 72.0 / 72.27, match
.group("lt", "rt", "ht", "dp"))
1126 box
= textbox_pt(x
, y
, left
, right
, height
, depth
, self
.finishdvi
, fillstyles
)
1130 box
.setdvicanvas(self
.dvifile
.readpage([ord("P"), ord("y"), ord("X"), self
.page
, 0, 0, 0, 0, 0, 0]))
1132 self
.needdvitextboxes
.append(box
)
1135 def text(self
, x
, y
, expr
, *args
, **kwargs
):
1136 return self
.text_pt(unit
.topt(x
), unit
.topt(y
), expr
, *args
, **kwargs
)
1138 PyXVariableBoxPattern
= re
.compile(r
"PyXVariableBox:page=(?P<page>\d+),par=(?P<par>\d+),prevgraf=(?P<prevgraf>\d+):")
1140 def textboxes(self
, text
, pageshapes
):
1141 # this is some experimental code to put text into several boxes
1142 # while the bounding shape changes from box to box (rectangles only)
1143 # first we load sev.tex
1144 self
.execute(r
"\input textboxes.tex", [texmessage
.load
])
1145 # define page shapes
1146 pageshapes_str
= "\\hsize=%.5ftruept%%\n\\vsize=%.5ftruept%%\n" % (72.27/72*unit
.topt(pageshapes
[0][0]), 72.27/72*unit
.topt(pageshapes
[0][1]))
1147 pageshapes_str
+= "\\lohsizes={%\n"
1148 for hsize
, vsize
in pageshapes
[1:]:
1149 pageshapes_str
+= "{\\global\\hsize=%.5ftruept}%%\n" % (72.27/72*unit
.topt(hsize
))
1150 pageshapes_str
+= "{\\relax}%\n}%\n"
1151 pageshapes_str
+= "\\lovsizes={%\n"
1152 for hsize
, vsize
in pageshapes
[1:]:
1153 pageshapes_str
+= "{\\global\\vsize=%.5ftruept}%%\n" % (72.27/72*unit
.topt(vsize
))
1154 pageshapes_str
+= "{\\relax}%\n}%\n"
1160 self
.execute(pageshapes_str
, [])
1161 # print pageshapes_str
1162 parnos_str
= "\\parnos={%s 0}%%\n" % " ".join(parnos
)
1163 self
.execute(parnos_str
, [])
1165 parshapes_str
= "\\parshapes={%%\n%s%%\n{\\relax}%%\n}%%\n" % "%\n".join(parshapes
)
1166 self
.execute(parshapes_str
, [])
1168 self
.execute("\\global\\count0=1%%\n"
1169 "\\global\\parno=0%%\n"
1170 "\\global\\myprevgraf=0%%\n"
1171 "\\global\\showprevgraf=0%%\n"
1172 "\\global\\outputtype=0%%\n"
1173 "\\global\\leastcost=10000000%%\n"
1175 "\\vfill\\supereject%%\n" % text
, [texmessage
.ignore
])
1177 if self
.dvifile
is None:
1178 self
.dvifile
= dvifile
.dvifile("%s.dvi" % self
.texfilename
, self
.fontmap
, debug
=self
.dvidebug
)
1180 raise RuntimeError("textboxes currently needs texipc")
1183 lastparshapes
= parshapes
1186 lastpar
= prevgraf
= -1
1187 m
= self
.PyXVariableBoxPattern
.search(self
.texmessage
)
1189 print m
.string
[m
.start(): m
.end()]
1191 page
= int(m
.group("page"))
1192 assert page
== pages
1193 par
= int(m
.group("par"))
1194 prevgraf
= int(m
.group("prevgraf"))
1195 if page
<= len(pageshapes
):
1196 width
= 72.27/72*unit
.topt(pageshapes
[page
-1][0])
1198 width
= 72.27/72*unit
.topt(pageshapes
[-1][0])
1199 if page
< len(pageshapes
):
1200 nextwidth
= 72.27/72*unit
.topt(pageshapes
[page
][0])
1202 nextwidth
= 72.27/72*unit
.topt(pageshapes
[-1][0])
1203 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
))
1210 parshape
= " 0pt ".join(["%.5ftruept" % width
for i
in range(prevgraf
- 1)])
1212 parshape
= " 0pt " + parshape
1213 oldparshape
= parshapes
[-1].split(None, 2)[2][:-1]
1215 oldparshape
= " " + oldparshape
1216 prevgraf
= prevgraf
+ lastprevgraf
1217 parshapes
[-1] = "{\\parshape %i%s%s 0pt %.5ftruept}" % (prevgraf
+ 1, oldparshape
, parshape
, nextwidth
)
1219 lastprevgraf
= prevgraf
1221 m
= self
.PyXVariableBoxPattern
.search(self
.texmessage
, nextpos
)
1223 for i
in range(pages
):
1224 result
.append(self
.dvifile
.readpage([ord("P"), ord("y"), ord("X"), i
, 0, 0, 0, 0, 0, 0]))
1225 if parnos
== lastparnos
and parshapes
== lastparshapes
:
1230 # the module provides an default texrunner and methods for direct access
1231 defaulttexrunner
= texrunner()
1232 reset
= defaulttexrunner
.reset
1233 set = defaulttexrunner
.set
1234 preamble
= defaulttexrunner
.preamble
1235 text
= defaulttexrunner
.text
1236 text_pt
= defaulttexrunner
.text_pt