# -*- coding: ISO-8859-1 -*- # # # Copyright (C) 2002-2004 Jörg Lehmann # Copyright (C) 2003-2007 Michael Schindler # Copyright (C) 2002-2006 André Wobst # # This file is part of PyX (http://pyx.sourceforge.net/). # # PyX is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # PyX is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with PyX; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA import glob, os, threading, Queue, re, tempfile, atexit, time, warnings import config, siteconfig, unit, box, canvas, trafo, version, attr, style, dvifile import bbox as bboxmodule ############################################################################### # texmessages # - please don't get confused: # - there is a texmessage (and a texmessageparsed) attribute within the # texrunner; it contains TeX/LaTeX response from the last command execution # - instances of classes derived from the class texmessage are used to # parse the TeX/LaTeX response as it is stored in the texmessageparsed # attribute of a texrunner instance # - the multiple usage of the name texmessage might be removed in the future # - texmessage instances should implement _Itexmessage ############################################################################### class TexResultError(RuntimeError): """specialized texrunner exception class - it is raised by texmessage instances, when a texmessage indicates an error - it is raised by the texrunner itself, whenever there is a texmessage left after all parsing of this message (by texmessage instances) prints a detailed report about the problem - the verbose level is controlled by texrunner.errordebug""" def __init__(self, description, texrunner): if texrunner.errordebug >= 2: self.description = ("%s\n" % description + "The expression passed to TeX was:\n" " %s\n" % texrunner.expr.replace("\n", "\n ").rstrip() + "The return message from TeX was:\n" " %s\n" % texrunner.texmessage.replace("\n", "\n ").rstrip() + "After parsing this message, the following was left:\n" " %s" % texrunner.texmessageparsed.replace("\n", "\n ").rstrip()) elif texrunner.errordebug == 1: firstlines = texrunner.texmessageparsed.split("\n") if len(firstlines) > 5: firstlines = firstlines[:5] + ["(cut after 5 lines, increase errordebug for more output)"] self.description = ("%s\n" % description + "The expression passed to TeX was:\n" " %s\n" % texrunner.expr.replace("\n", "\n ").rstrip() + "After parsing the return message from TeX, the following was left:\n" + reduce(lambda x, y: "%s %s\n" % (x,y), firstlines, "").rstrip()) else: self.description = description def __str__(self): return self.description class _Itexmessage: """validates/invalidates TeX/LaTeX response""" def check(self, texrunner): """check a Tex/LaTeX response and respond appropriate - read the texrunners texmessageparsed attribute - if there is an problem found, raise TexResultError - remove any valid and identified TeX/LaTeX response from the texrunners texmessageparsed attribute -> finally, there should be nothing left in there, otherwise it is interpreted as an error""" class texmessage(attr.attr): pass class _texmessagestart(texmessage): """validates TeX/LaTeX startup""" __implements__ = _Itexmessage startpattern = re.compile(r"This is [-0-9a-zA-Z\s_]*TeX") def check(self, texrunner): # check for "This is e-TeX" m = self.startpattern.search(texrunner.texmessageparsed) if not m: raise TexResultError("TeX startup failed", texrunner) texrunner.texmessageparsed = texrunner.texmessageparsed[m.end():] # check for filename to be processed try: texrunner.texmessageparsed = texrunner.texmessageparsed.split("%s.tex" % texrunner.texfilename, 1)[1] except (IndexError, ValueError): raise TexResultError("TeX running startup file failed", texrunner) # check for \raiseerror -- just to be sure that communication works try: texrunner.texmessageparsed = texrunner.texmessageparsed.split("*! Undefined control sequence.\n<*> \\raiseerror\n %\n", 1)[1] except (IndexError, ValueError): raise TexResultError("TeX scrollmode check failed", texrunner) class _texmessagenofile(texmessage): """allows for LaTeXs no-file warning""" __implements__ = _Itexmessage def __init__(self, fileending): self.fileending = fileending def check(self, texrunner): try: s1, s2 = texrunner.texmessageparsed.split("No file %s.%s." % (texrunner.texfilename, self.fileending), 1) texrunner.texmessageparsed = s1 + s2 except (IndexError, ValueError): try: s1, s2 = texrunner.texmessageparsed.split("No file %s%s%s.%s." % (os.curdir, os.sep, texrunner.texfilename, self.fileending), 1) texrunner.texmessageparsed = s1 + s2 except (IndexError, ValueError): pass class _texmessageinputmarker(texmessage): """validates the PyXInputMarker""" __implements__ = _Itexmessage def check(self, texrunner): try: s1, s2 = texrunner.texmessageparsed.split("PyXInputMarker:executeid=%s:" % texrunner.executeid, 1) texrunner.texmessageparsed = s1 + s2 except (IndexError, ValueError): raise TexResultError("PyXInputMarker expected", texrunner) class _texmessagepyxbox(texmessage): """validates the PyXBox output""" __implements__ = _Itexmessage pattern = re.compile(r"PyXBox:page=(?P\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:") def check(self, texrunner): m = self.pattern.search(texrunner.texmessageparsed) if m and m.group("page") == str(texrunner.page): texrunner.texmessageparsed = texrunner.texmessageparsed[:m.start()] + texrunner.texmessageparsed[m.end():] else: raise TexResultError("PyXBox expected", texrunner) class _texmessagepyxpageout(texmessage): """validates the dvi shipout message (writing a page to the dvi file)""" __implements__ = _Itexmessage def check(self, texrunner): try: s1, s2 = texrunner.texmessageparsed.split("[80.121.88.%s]" % texrunner.page, 1) texrunner.texmessageparsed = s1 + s2 except (IndexError, ValueError): raise TexResultError("PyXPageOutMarker expected", texrunner) class _texmessageend(texmessage): """validates TeX/LaTeX finish""" __implements__ = _Itexmessage def check(self, texrunner): try: s1, s2 = texrunner.texmessageparsed.split("(%s.aux)" % texrunner.texfilename, 1) texrunner.texmessageparsed = s1 + s2 except (IndexError, ValueError): try: s1, s2 = texrunner.texmessageparsed.split("(%s%s%s.aux)" % (os.curdir, os.sep, texrunner.texfilename), 1) texrunner.texmessageparsed = s1 + s2 except (IndexError, ValueError): pass # check for "(see the transcript file for additional information)" try: s1, s2 = texrunner.texmessageparsed.split("(see the transcript file for additional information)", 1) texrunner.texmessageparsed = s1 + s2 except (IndexError, ValueError): pass # check for "Output written on ...dvi (1 page, 220 bytes)." dvipattern = re.compile(r"Output written on %s\.dvi \((?P\d+) pages?, \d+ bytes\)\." % texrunner.texfilename) m = dvipattern.search(texrunner.texmessageparsed) if texrunner.page: if not m: raise TexResultError("TeX dvifile messages expected", texrunner) if m.group("page") != str(texrunner.page): raise TexResultError("wrong number of pages reported", texrunner) texrunner.texmessageparsed = texrunner.texmessageparsed[:m.start()] + texrunner.texmessageparsed[m.end():] else: try: s1, s2 = texrunner.texmessageparsed.split("No pages of output.", 1) texrunner.texmessageparsed = s1 + s2 except (IndexError, ValueError): raise TexResultError("no dvifile expected", texrunner) # check for "Transcript written on ...log." try: s1, s2 = texrunner.texmessageparsed.split("Transcript written on %s.log." % texrunner.texfilename, 1) texrunner.texmessageparsed = s1 + s2 except (IndexError, ValueError): raise TexResultError("TeX logfile message expected", texrunner) class _texmessageemptylines(texmessage): """validates "*-only" (TeX/LaTeX input marker in interactive mode) and empty lines also clear TeX interactive mode warning (Please type a command or say `\\end') """ __implements__ = _Itexmessage def check(self, texrunner): texrunner.texmessageparsed = texrunner.texmessageparsed.replace(r"(Please type a command or say `\end')", "") texrunner.texmessageparsed = texrunner.texmessageparsed.replace(" ", "") texrunner.texmessageparsed = texrunner.texmessageparsed.replace("*\n", "") texrunner.texmessageparsed = texrunner.texmessageparsed.replace("\n", "") class _texmessageload(texmessage): """validates inclusion of arbitrary files - the matched pattern is "( )", where is a readable file and other stuff can be anything - If the filename is enclosed in double quotes, it may contain blank space. - "(" and ")" must be used consistent (otherwise this validator just does nothing) - this is not always wanted, but we just assume that file inclusion is fine""" __implements__ = _Itexmessage pattern = re.compile(r"\([\"]?(?P(?:(?[^()]*)\)") def baselevels(self, s, maxlevel=1, brackets="()"): """strip parts of a string above a given bracket level - return a modified (some parts might be removed) version of the string s where all parts inside brackets with level higher than maxlevel are removed - if brackets do not match (number of left and right brackets is wrong or at some points there were more right brackets than left brackets) just return the unmodified string""" level = 0 highestlevel = 0 res = "" for c in s: if c == brackets[0]: level += 1 if level > highestlevel: highestlevel = level if level <= maxlevel: res += c if c == brackets[1]: level -= 1 if level == 0 and highestlevel > 0: return res def check(self, texrunner): lowestbracketlevel = self.baselevels(texrunner.texmessageparsed) if lowestbracketlevel is not None: m = self.pattern.search(lowestbracketlevel) while m: filename = m.group("filename").replace("\n", "") try: additional = m.group("additional") except IndexError: additional = "" if (os.access(filename, os.R_OK) or len(additional) and additional[0] == "\n" and os.access(filename+additional.split()[0], os.R_OK)): lowestbracketlevel = lowestbracketlevel[:m.start()] + lowestbracketlevel[m.end():] else: break m = self.pattern.search(lowestbracketlevel) else: texrunner.texmessageparsed = lowestbracketlevel class _texmessageloaddef(_texmessageload): """validates the inclusion of font description files (fd-files) - works like _texmessageload - filename must end with .def or .fd and no further text is allowed""" pattern = re.compile(r"\((?P[^)]+(\.fd|\.def))\)") def baselevels(self, s, **kwargs): return s class _texmessagegraphicsload(_texmessageload): """validates the inclusion of files as the graphics packages writes it - works like _texmessageload, but using "<" and ">" as delimiters - filename must end with .eps and no further text is allowed""" pattern = re.compile(r"<(?P[^>]+.eps)>") def baselevels(self, s, **kwargs): return s class _texmessageignore(_texmessageload): """validates any TeX/LaTeX response - this might be used, when the expression is ok, but no suitable texmessage parser is available - PLEASE: - consider writing suitable tex message parsers - share your ideas/problems/solutions with others (use the PyX mailing lists)""" __implements__ = _Itexmessage def check(self, texrunner): texrunner.texmessageparsed = "" texmessage.start = _texmessagestart() texmessage.noaux = _texmessagenofile("aux") texmessage.nonav = _texmessagenofile("nav") texmessage.end = _texmessageend() texmessage.load = _texmessageload() texmessage.loaddef = _texmessageloaddef() texmessage.graphicsload = _texmessagegraphicsload() texmessage.ignore = _texmessageignore() # for internal use: texmessage.inputmarker = _texmessageinputmarker() texmessage.pyxbox = _texmessagepyxbox() texmessage.pyxpageout = _texmessagepyxpageout() texmessage.emptylines = _texmessageemptylines() class _texmessageallwarning(texmessage): """validates a given pattern 'pattern' as a warning 'warning'""" def check(self, texrunner): if texrunner.texmessageparsed: warnings.warn("ignoring all warnings:\n%s" % texrunner.texmessageparsed) texrunner.texmessageparsed = "" texmessage.allwarning = _texmessageallwarning() class texmessagepattern(texmessage): """validates a given pattern and issue a warning (when set)""" def __init__(self, pattern, warning=None): self.pattern = pattern self.warning = warning def check(self, texrunner): m = self.pattern.search(texrunner.texmessageparsed) while m: texrunner.texmessageparsed = texrunner.texmessageparsed[:m.start()] + texrunner.texmessageparsed[m.end():] if self.warning: warnings.warn("%s:\n%s" % (self.warning, m.string[m.start(): m.end()].rstrip())) m = self.pattern.search(texrunner.texmessageparsed) texmessage.fontwarning = texmessagepattern(re.compile(r"^LaTeX Font Warning: .*$(\n^\(Font\).*$)*", re.MULTILINE), "ignoring font warning") texmessage.boxwarning = texmessagepattern(re.compile(r"^(Overfull|Underfull) \\[hv]box.*$(\n^..*$)*\n^$\n", re.MULTILINE), "ignoring overfull/underfull box warning") texmessage.rerunwarning = texmessagepattern(re.compile(r"^(LaTeX Warning: Label\(s\) may have changed\. Rerun to get cross-references right\s*\.)$", re.MULTILINE), "ignoring rerun warning") ############################################################################### # textattrs ############################################################################### _textattrspreamble = "" class textattr: "a textattr defines a apply method, which modifies a (La)TeX expression" class _localattr: pass _textattrspreamble += r"""\gdef\PyXFlushHAlign{0}% \def\PyXragged{% \leftskip=0pt plus \PyXFlushHAlign fil% \rightskip=0pt plus 1fil% \advance\rightskip0pt plus -\PyXFlushHAlign fil% \parfillskip=0pt% \pretolerance=9999% \tolerance=9999% \parindent=0pt% \hyphenpenalty=9999% \exhyphenpenalty=9999}% """ class boxhalign(attr.exclusiveattr, textattr, _localattr): def __init__(self, aboxhalign): self.boxhalign = aboxhalign attr.exclusiveattr.__init__(self, boxhalign) def apply(self, expr): return r"\gdef\PyXBoxHAlign{%.5f}%s" % (self.boxhalign, expr) boxhalign.left = boxhalign(0) boxhalign.center = boxhalign(0.5) boxhalign.right = boxhalign(1) # boxhalign.clear = attr.clearclass(boxhalign) # we can't defined a clearclass for boxhalign since it can't clear a halign's boxhalign class flushhalign(attr.exclusiveattr, textattr, _localattr): def __init__(self, aflushhalign): self.flushhalign = aflushhalign attr.exclusiveattr.__init__(self, flushhalign) def apply(self, expr): return r"\gdef\PyXFlushHAlign{%.5f}\PyXragged{}%s" % (self.flushhalign, expr) flushhalign.left = flushhalign(0) flushhalign.center = flushhalign(0.5) flushhalign.right = flushhalign(1) # flushhalign.clear = attr.clearclass(flushhalign) # we can't defined a clearclass for flushhalign since it couldn't clear a halign's flushhalign class halign(attr.exclusiveattr, textattr, boxhalign, flushhalign, _localattr): def __init__(self, aboxhalign, aflushhalign): self.boxhalign = aboxhalign self.flushhalign = aflushhalign attr.exclusiveattr.__init__(self, halign) def apply(self, expr): return r"\gdef\PyXBoxHAlign{%.5f}\gdef\PyXFlushHAlign{%.5f}\PyXragged{}%s" % (self.boxhalign, self.flushhalign, expr) halign.left = halign(0, 0) halign.center = halign(0.5, 0.5) halign.right = halign(1, 1) halign.clear = attr.clearclass(halign) halign.boxleft = boxhalign.left halign.boxcenter = boxhalign.center halign.boxright = boxhalign.right halign.flushleft = halign.raggedright = flushhalign.left halign.flushcenter = halign.raggedcenter = flushhalign.center halign.flushright = halign.raggedleft = flushhalign.right class _mathmode(attr.attr, textattr, _localattr): "math mode" def apply(self, expr): return r"$\displaystyle{%s}$" % expr mathmode = _mathmode() clearmathmode = attr.clearclass(_mathmode) class _phantom(attr.attr, textattr, _localattr): "phantom text" def apply(self, expr): return r"\phantom{%s}" % expr phantom = _phantom() clearphantom = attr.clearclass(_phantom) _textattrspreamble += "\\newbox\\PyXBoxVBox%\n\\newdimen\\PyXDimenVBox%\n" class parbox_pt(attr.sortbeforeexclusiveattr, textattr): top = 1 middle = 2 bottom = 3 def __init__(self, width, baseline=top): self.width = width * 72.27 / (unit.scale["x"] * 72) self.baseline = baseline attr.sortbeforeexclusiveattr.__init__(self, parbox_pt, [_localattr]) def apply(self, expr): if self.baseline == self.top: return r"\linewidth=%.5ftruept\vtop{\hsize=\linewidth\textwidth=\linewidth{}%s}" % (self.width, expr) elif self.baseline == self.middle: return r"\linewidth=%.5ftruept\setbox\PyXBoxVBox=\hbox{{\vtop{\hsize=\linewidth\textwidth=\linewidth{}%s}}}\PyXDimenVBox=0.5\dp\PyXBoxVBox\setbox\PyXBoxVBox=\hbox{{\vbox{\hsize=\linewidth\textwidth=\linewidth{}%s}}}\advance\PyXDimenVBox by -0.5\dp\PyXBoxVBox\lower\PyXDimenVBox\box\PyXBoxVBox" % (self.width, expr, expr) elif self.baseline == self.bottom: return r"\linewidth=%.5ftruept\vbox{\hsize=\linewidth\textwidth=\linewidth{}%s}" % (self.width, expr) else: RuntimeError("invalid baseline argument") parbox_pt.clear = attr.clearclass(parbox_pt) class parbox(parbox_pt): def __init__(self, width, **kwargs): parbox_pt.__init__(self, unit.topt(width), **kwargs) parbox.clear = parbox_pt.clear _textattrspreamble += "\\newbox\\PyXBoxVAlign%\n\\newdimen\\PyXDimenVAlign%\n" class valign(attr.sortbeforeexclusiveattr, textattr): def __init__(self, avalign): self.valign = avalign attr.sortbeforeexclusiveattr.__init__(self, valign, [parbox_pt, _localattr]) def apply(self, expr): return r"\setbox\PyXBoxVAlign=\hbox{{%s}}\PyXDimenVAlign=%.5f\ht\PyXBoxVAlign\advance\PyXDimenVAlign by -%.5f\dp\PyXBoxVAlign\lower\PyXDimenVAlign\box\PyXBoxVAlign" % (expr, 1-self.valign, self.valign) valign.top = valign(0) valign.middle = valign(0.5) valign.bottom = valign(1) valign.clear = valign.baseline = attr.clearclass(valign) _textattrspreamble += "\\newdimen\\PyXDimenVShift%\n" class _vshift(attr.sortbeforeattr, textattr): def __init__(self): attr.sortbeforeattr.__init__(self, [valign, parbox_pt, _localattr]) def apply(self, expr): return r"%s\setbox0\hbox{{%s}}\lower\PyXDimenVShift\box0" % (self.setheightexpr(), expr) class vshift(_vshift): "vertical down shift by a fraction of a character height" def __init__(self, lowerratio, heightstr="0"): _vshift.__init__(self) self.lowerratio = lowerratio self.heightstr = heightstr def setheightexpr(self): return r"\setbox0\hbox{{%s}}\PyXDimenVShift=%.5f\ht0" % (self.heightstr, self.lowerratio) class _vshiftmathaxis(_vshift): "vertical down shift by the height of the math axis" def setheightexpr(self): return r"\setbox0\hbox{$\vcenter{\vrule width0pt}$}\PyXDimenVShift=\ht0" vshift.bottomzero = vshift(0) vshift.middlezero = vshift(0.5) vshift.topzero = vshift(1) vshift.mathaxis = _vshiftmathaxis() vshift.clear = attr.clearclass(_vshift) defaultsizelist = ["normalsize", "large", "Large", "LARGE", "huge", "Huge", None, "tiny", "scriptsize", "footnotesize", "small"] class size(attr.sortbeforeattr, textattr): "font size" def __init__(self, sizeindex=None, sizename=None, sizelist=defaultsizelist): if (sizeindex is None and sizename is None) or (sizeindex is not None and sizename is not None): raise RuntimeError("either specify sizeindex or sizename") attr.sortbeforeattr.__init__(self, [_mathmode, _vshift]) if sizeindex is not None: if sizeindex >= 0 and sizeindex < sizelist.index(None): self.size = sizelist[sizeindex] elif sizeindex < 0 and sizeindex + len(sizelist) > sizelist.index(None): self.size = sizelist[sizeindex] else: raise IndexError("index out of sizelist range") else: self.size = sizename def apply(self, expr): return r"\%s{}%s" % (self.size, expr) size.tiny = size(-4) size.scriptsize = size.script = size(-3) size.footnotesize = size.footnote = size(-2) size.small = size(-1) size.normalsize = size.normal = size(0) size.large = size(1) size.Large = size(2) size.LARGE = size(3) size.huge = size(4) size.Huge = size(5) size.clear = attr.clearclass(size) ############################################################################### # texrunner ############################################################################### class _readpipe(threading.Thread): """threaded reader of TeX/LaTeX output - sets an event, when a specific string in the programs output is found - sets an event, when the terminal ends""" def __init__(self, pipe, expectqueue, gotevent, gotqueue, quitevent): """initialize the reader - pipe: file to be read from - expectqueue: keeps the next InputMarker to be wait for - gotevent: the "got InputMarker" event - gotqueue: a queue containing the lines recieved from TeX/LaTeX - quitevent: the "end of terminal" event""" threading.Thread.__init__(self) self.setDaemon(1) # don't care if the output might not be finished (nevertheless, it shouldn't happen) self.pipe = pipe self.expectqueue = expectqueue self.gotevent = gotevent self.gotqueue = gotqueue self.quitevent = quitevent self.expect = None self.start() def run(self): """thread routine""" read = self.pipe.readline() # read, what comes in try: self.expect = self.expectqueue.get_nowait() # read, what should be expected except Queue.Empty: pass while len(read): # universal EOL handling (convert everything into unix like EOLs) # XXX is this necessary on pipes? read = read.replace("\r", "").replace("\n", "") + "\n" self.gotqueue.put(read) # report, whats read if self.expect is not None and read.find(self.expect) != -1: self.gotevent.set() # raise the got event, when the output was expected (XXX: within a single line) read = self.pipe.readline() # read again try: self.expect = self.expectqueue.get_nowait() except Queue.Empty: pass # EOF reached self.pipe.close() if self.expect is not None and self.expect.find("PyXInputMarker") != -1: raise RuntimeError("TeX/LaTeX finished unexpectedly") self.quitevent.set() class textbox(box.rect, canvas._canvas): """basically a box.rect, but it contains a text created by the texrunner - texrunner._text and texrunner.text return such an object - _textbox instances can be inserted into a canvas - the output is contained in a page of the dvifile available thru the texrunner""" # TODO: shouldn't all boxes become canvases? how about inserts then? def __init__(self, x, y, left, right, height, depth, finishdvi, attrs): """ - finishdvi is a method to be called to get the dvicanvas (e.g. the finishdvi calls the setdvicanvas method) - attrs are fillstyles""" self.left = left self.right = right self.width = left + right self.height = height self.depth = depth self.texttrafo = trafo.scale(unit.scale["x"]).translated(x, y) box.rect.__init__(self, x - left, y - depth, left + right, depth + height, abscenter = (left, depth)) canvas._canvas.__init__(self, attrs) self.finishdvi = finishdvi self.dvicanvas = None self.insertdvicanvas = 0 def transform(self, *trafos): if self.insertdvicanvas: raise RuntimeError("can't apply transformation after dvicanvas was inserted") box.rect.transform(self, *trafos) for trafo in trafos: self.texttrafo = trafo * self.texttrafo def setdvicanvas(self, dvicanvas): if self.dvicanvas is not None: raise RuntimeError("multiple call to setdvicanvas") self.dvicanvas = dvicanvas def ensuredvicanvas(self): if self.dvicanvas is None: self.finishdvi() assert self.dvicanvas is not None, "finishdvi is broken" if not self.insertdvicanvas: self.insert(self.dvicanvas, [self.texttrafo]) self.insertdvicanvas = 1 def marker(self, marker): self.ensuredvicanvas() return self.texttrafo.apply(*self.dvicanvas.markers[marker]) def processPS(self, file, writer, context, registry, bbox): self.ensuredvicanvas() abbox = bboxmodule.empty() canvas._canvas.processPS(self, file, writer, context, registry, abbox) bbox += box.rect.bbox(self) def processPDF(self, file, writer, context, registry, bbox): self.ensuredvicanvas() abbox = bboxmodule.empty() canvas._canvas.processPDF(self, file, writer, context, registry, abbox) bbox += box.rect.bbox(self) def _cleantmp(texrunner): """get rid of temporary files - function to be registered by atexit - files contained in usefiles are kept""" if texrunner.texruns: # cleanup while TeX is still running? texrunner.expectqueue.put_nowait(None) # do not expect any output anymore if texrunner.mode == "latex": # try to immediately quit from TeX or LaTeX texrunner.texinput.write("\n\\catcode`\\@11\\relax\\@@end\n") else: texrunner.texinput.write("\n\\end\n") texrunner.texinput.close() # close the input queue and if not texrunner.waitforevent(texrunner.quitevent): # wait for finish of the output return # didn't got a quit from TeX -> we can't do much more texrunner.texruns = 0 texrunner.texdone = 1 for usefile in texrunner.usefiles: extpos = usefile.rfind(".") try: os.rename(texrunner.texfilename + usefile[extpos:], usefile) except OSError: pass for file in glob.glob("%s.*" % texrunner.texfilename): try: os.unlink(file) except OSError: pass if texrunner.texdebug is not None: try: texrunner.texdebug.close() texrunner.texdebug = None except IOError: pass class _unset: pass class texrunner: """TeX/LaTeX interface - runs TeX/LaTeX expressions instantly - checks TeX/LaTeX response - the instance variable texmessage stores the last TeX response as a string - the instance variable texmessageparsed stores a parsed version of texmessage; it should be empty after texmessage.check was called, otherwise a TexResultError is raised - the instance variable errordebug controls the verbose level of TexResultError""" defaulttexmessagesstart = [texmessage.start] defaulttexmessagesdocclass = [texmessage.load] defaulttexmessagesbegindoc = [texmessage.load, texmessage.noaux] defaulttexmessagesend = [texmessage.end, texmessage.fontwarning, texmessage.rerunwarning] defaulttexmessagesdefaultpreamble = [texmessage.load] defaulttexmessagesdefaultrun = [texmessage.loaddef, texmessage.graphicsload, texmessage.fontwarning, texmessage.boxwarning] def __init__(self, mode="tex", lfs="10pt", docclass="article", docopt=None, usefiles=[], fontmaps=config.get("text", "fontmaps", "psfonts.map"), waitfortex=config.getint("text", "waitfortex", 60), showwaitfortex=config.getint("text", "showwaitfortex", 5), texipc=config.getboolean("text", "texipc", 0), texdebug=None, dvidebug=0, errordebug=1, pyxgraphics=1, texmessagesstart=[], texmessagesdocclass=[], texmessagesbegindoc=[], texmessagesend=[], texmessagesdefaultpreamble=[], texmessagesdefaultrun=[]): mode = mode.lower() if mode != "tex" and mode != "latex": raise ValueError("mode \"TeX\" or \"LaTeX\" expected") self.mode = mode self.lfs = lfs self.docclass = docclass self.docopt = docopt self.usefiles = usefiles[:] self.fontmaps = fontmaps self.waitfortex = waitfortex self.showwaitfortex = showwaitfortex self.texipc = texipc if texdebug is not None: if texdebug[-4:] == ".tex": self.texdebug = open(texdebug, "w") else: self.texdebug = open("%s.tex" % texdebug, "w") else: self.texdebug = None self.dvidebug = dvidebug self.errordebug = errordebug self.pyxgraphics = pyxgraphics self.texmessagesstart = texmessagesstart[:] self.texmessagesdocclass = texmessagesdocclass[:] self.texmessagesbegindoc = texmessagesbegindoc[:] self.texmessagesend = texmessagesend[:] self.texmessagesdefaultpreamble = texmessagesdefaultpreamble[:] self.texmessagesdefaultrun = texmessagesdefaultrun[:] self.texruns = 0 self.texdone = 0 self.preamblemode = 1 self.executeid = 0 self.page = 0 self.preambles = [] self.needdvitextboxes = [] # when texipc-mode off self.dvifile = None self.textboxesincluded = 0 savetempdir = tempfile.tempdir tempfile.tempdir = os.curdir self.texfilename = os.path.basename(tempfile.mktemp()) tempfile.tempdir = savetempdir def waitforevent(self, event): """waits verbosely with an timeout for an event - observes an event while periodly while printing messages - returns the status of the event (isSet) - does not clear the event""" if self.showwaitfortex: waited = 0 hasevent = 0 while waited < self.waitfortex and not hasevent: if self.waitfortex - waited > self.showwaitfortex: event.wait(self.showwaitfortex) waited += self.showwaitfortex else: event.wait(self.waitfortex - waited) waited += self.waitfortex - waited hasevent = event.isSet() if not hasevent: if waited < self.waitfortex: warnings.warn("still waiting for %s after %i (of %i) seconds..." % (self.mode, waited, self.waitfortex)) else: warnings.warn("the timeout of %i seconds expired and %s did not respond." % (waited, self.mode)) return hasevent else: event.wait(self.waitfortex) return event.isSet() def execute(self, expr, texmessages): """executes expr within TeX/LaTeX - if self.texruns is not yet set, TeX/LaTeX is initialized, self.texruns is set and self.preamblemode is set - the method must not be called, when self.texdone is already set - expr should be a string or None - when expr is None, TeX/LaTeX is stopped, self.texruns is unset and self.texdone becomes set - when self.preamblemode is set, the expr is passed directly to TeX/LaTeX - when self.preamblemode is unset, the expr is passed to \ProcessPyXBox - texmessages is a list of texmessage instances""" if not self.texruns: if self.texdebug is not None: self.texdebug.write("%% PyX %s texdebug file\n" % version.version) self.texdebug.write("%% mode: %s\n" % self.mode) self.texdebug.write("%% date: %s\n" % time.asctime(time.localtime(time.time()))) for usefile in self.usefiles: extpos = usefile.rfind(".") try: os.rename(usefile, self.texfilename + usefile[extpos:]) except OSError: pass texfile = open("%s.tex" % self.texfilename, "w") # start with filename -> creates dvi file with that name texfile.write("\\relax%\n") texfile.close() if self.texipc: ipcflag = " --ipc" else: ipcflag = "" try: self.texinput, self.texoutput = os.popen4("%s%s %s" % (self.mode, ipcflag, self.texfilename), "t", 0) except ValueError: # XXX: workaround for MS Windows (bufsize = 0 makes trouble!?) self.texinput, self.texoutput = os.popen4("%s%s %s" % (self.mode, ipcflag, self.texfilename), "t") atexit.register(_cleantmp, self) self.expectqueue = Queue.Queue(1) # allow for a single entry only -> keeps the next InputMarker to be wait for self.gotevent = threading.Event() # keeps the got inputmarker event self.gotqueue = Queue.Queue(0) # allow arbitrary number of entries self.quitevent = threading.Event() # keeps for end of terminal event self.readoutput = _readpipe(self.texoutput, self.expectqueue, self.gotevent, self.gotqueue, self.quitevent) self.texruns = 1 self.fontmap = dvifile.readfontmap(self.fontmaps.split()) oldpreamblemode = self.preamblemode self.preamblemode = 1 self.execute("\\scrollmode\n\\raiseerror%\n" # switch to and check scrollmode "\\def\\PyX{P\\kern-.3em\\lower.5ex\hbox{Y}\kern-.18em X}%\n" # just the PyX Logo "\\gdef\\PyXBoxHAlign{0}%\n" # global PyXBoxHAlign (0.0-1.0) for the horizontal alignment, default to 0 "\\newbox\\PyXBox%\n" # PyXBox will contain the output "\\newbox\\PyXBoxHAligned%\n" # PyXBox will contain the horizontal aligned output "\\newdimen\\PyXDimenHAlignLT%\n" # PyXDimenHAlignLT/RT will contain the left/right extent "\\newdimen\\PyXDimenHAlignRT%\n" + _textattrspreamble + # insert preambles for textattrs macros "\\long\\def\\ProcessPyXBox#1#2{%\n" # the ProcessPyXBox definition (#1 is expr, #2 is page number) "\\setbox\\PyXBox=\\hbox{{#1}}%\n" # push expression into PyXBox "\\PyXDimenHAlignLT=\\PyXBoxHAlign\\wd\\PyXBox%\n" # calculate the left/right extent "\\PyXDimenHAlignRT=\\wd\\PyXBox%\n" "\\advance\\PyXDimenHAlignRT by -\\PyXDimenHAlignLT%\n" "\\gdef\\PyXBoxHAlign{0}%\n" # reset the PyXBoxHAlign to the default 0 "\\immediate\\write16{PyXBox:page=#2," # write page and extents of this box to stdout "lt=\\the\\PyXDimenHAlignLT," "rt=\\the\\PyXDimenHAlignRT," "ht=\\the\\ht\\PyXBox," "dp=\\the\\dp\\PyXBox:}%\n" "\\setbox\\PyXBoxHAligned=\\hbox{\\kern-\\PyXDimenHAlignLT\\box\\PyXBox}%\n" # align horizontally "\\ht\\PyXBoxHAligned0pt%\n" # baseline alignment (hight to zero) "{\\count0=80\\count1=121\\count2=88\\count3=#2\\shipout\\box\\PyXBoxHAligned}}%\n" # shipout PyXBox to Page 80.121.88. "\\def\\PyXInput#1{\\immediate\\write16{PyXInputMarker:executeid=#1:}}%\n" # write PyXInputMarker to stdout "\\def\\PyXMarker#1{\\hskip0pt\\special{PyX:marker #1}}%", # write PyXMarker special into the dvi-file self.defaulttexmessagesstart + self.texmessagesstart) os.remove("%s.tex" % self.texfilename) if self.mode == "tex": if self.lfs: lfserror = None if len(self.lfs) > 4 and self.lfs[-4:] == ".lfs": lfsname = self.lfs else: lfsname = "%s.lfs" % self.lfs for fulllfsname in [lfsname, os.path.join(siteconfig.lfsdir, lfsname)]: try: lfsfile = open(fulllfsname, "r") lfsdef = lfsfile.read() lfsfile.close() break except IOError: pass else: lfserror = "File '%s' is not available or not readable. " % lfsname else: lfserror = "" if lfserror is not None: allfiles = (glob.glob("*.lfs") + glob.glob(os.path.join(siteconfig.lfsdir, "*.lfs"))) lfsnames = [] for f in allfiles: try: open(f, "r").close() lfsnames.append(os.path.basename(f)[:-4]) except IOError: pass lfsnames.sort() if len(lfsnames): raise IOError("%sAvailable LaTeX font size files (*.lfs): %s" % (lfserror, lfsnames)) else: raise IOError("%sNo LaTeX font size files (*.lfs) available. Check your installation." % lfserror) self.execute(lfsdef, []) self.execute("\\normalsize%\n", []) self.execute("\\newdimen\\linewidth\\newdimen\\textwidth%\n", []) elif self.mode == "latex": if self.pyxgraphics: pyxdef = os.path.join(siteconfig.sharedir, "pyx.def") try: open(pyxdef, "r").close() except IOError: IOError("file 'pyx.def' is not available or not readable. Check your installation or turn off the pyxgraphics option.") pyxdef = os.path.abspath(pyxdef).replace(os.sep, "/") self.execute("\\makeatletter%\n" "\\let\\saveProcessOptions=\\ProcessOptions%\n" "\\def\\ProcessOptions{%\n" "\\def\\Gin@driver{" + pyxdef + "}%\n" "\\def\\c@lor@namefile{dvipsnam.def}%\n" "\\saveProcessOptions}%\n" "\\makeatother", []) if self.docopt is not None: self.execute("\\documentclass[%s]{%s}" % (self.docopt, self.docclass), self.defaulttexmessagesdocclass + self.texmessagesdocclass) else: self.execute("\\documentclass{%s}" % self.docclass, self.defaulttexmessagesdocclass + self.texmessagesdocclass) self.preamblemode = oldpreamblemode self.executeid += 1 if expr is not None: # TeX/LaTeX should process expr self.expectqueue.put_nowait("PyXInputMarker:executeid=%i:" % self.executeid) if self.preamblemode: self.expr = ("%s%%\n" % expr + "\\PyXInput{%i}%%\n" % self.executeid) else: self.page += 1 self.expr = ("\\ProcessPyXBox{%s%%\n}{%i}%%\n" % (expr, self.page) + "\\PyXInput{%i}%%\n" % self.executeid) else: # TeX/LaTeX should be finished self.expectqueue.put_nowait("Transcript written on %s.log" % self.texfilename) if self.mode == "latex": self.expr = "\\end{document}%\n" else: self.expr = "\\end%\n" if self.texdebug is not None: self.texdebug.write(self.expr) self.texinput.write(self.expr) gotevent = self.waitforevent(self.gotevent) self.gotevent.clear() if expr is None and gotevent: # TeX/LaTeX should have finished self.texruns = 0 self.texdone = 1 self.texinput.close() # close the input queue and gotevent = self.waitforevent(self.quitevent) # wait for finish of the output try: self.texmessage = "" while 1: self.texmessage += self.gotqueue.get_nowait() except Queue.Empty: pass self.texmessage = self.texmessage.replace("\r\n", "\n").replace("\r", "\n") self.texmessageparsed = self.texmessage if gotevent: if expr is not None: texmessage.inputmarker.check(self) if not self.preamblemode: texmessage.pyxbox.check(self) texmessage.pyxpageout.check(self) texmessages = attr.mergeattrs(texmessages) for t in texmessages: t.check(self) keeptexmessageparsed = self.texmessageparsed texmessage.emptylines.check(self) if len(self.texmessageparsed): self.texmessageparsed = keeptexmessageparsed raise TexResultError("unhandled TeX response (might be an error)", self) else: raise TexResultError("TeX didn't respond as expected within the timeout period (%i seconds)." % self.waitfortex, self) def finishdvi(self, ignoretail=0): """finish TeX/LaTeX and read the dvifile - this method ensures that all textboxes can access their dvicanvas""" self.execute(None, self.defaulttexmessagesend + self.texmessagesend) dvifilename = "%s.dvi" % self.texfilename if not self.texipc: self.dvifile = dvifile.dvifile(dvifilename, self.fontmap, debug=self.dvidebug) page = 1 for box in self.needdvitextboxes: box.setdvicanvas(self.dvifile.readpage([ord("P"), ord("y"), ord("X"), page, 0, 0, 0, 0, 0, 0])) page += 1 if not ignoretail and self.dvifile.readpage(None) is not None: raise RuntimeError("end of dvifile expected") self.dvifile = None self.needdvitextboxes = [] def reset(self, reinit=0): "resets the tex runner to its initial state (upto its record to old dvi file(s))" if self.texruns: self.finishdvi() if self.texdebug is not None: self.texdebug.write("%s\n%% preparing restart of %s\n" % ("%"*80, self.mode)) self.executeid = 0 self.page = 0 self.texdone = 0 if reinit: self.preamblemode = 1 for expr, texmessages in self.preambles: self.execute(expr, texmessages) if self.mode == "latex": self.execute("\\begin{document}", self.defaulttexmessagesbegindoc + self.texmessagesbegindoc) self.preamblemode = 0 else: self.preambles = [] self.preamblemode = 1 def set(self, mode=_unset, lfs=_unset, docclass=_unset, docopt=_unset, usefiles=_unset, fontmaps=_unset, waitfortex=_unset, showwaitfortex=_unset, texipc=_unset, texdebug=_unset, dvidebug=_unset, errordebug=_unset, pyxgraphics=_unset, texmessagesstart=_unset, texmessagesdocclass=_unset, texmessagesbegindoc=_unset, texmessagesend=_unset, texmessagesdefaultpreamble=_unset, texmessagesdefaultrun=_unset): """provide a set command for TeX/LaTeX settings - TeX/LaTeX must not yet been started - especially needed for the defaultrunner, where no access to the constructor is available""" if self.texruns: raise RuntimeError("set not allowed -- TeX/LaTeX already started") if mode is not _unset: mode = mode.lower() if mode != "tex" and mode != "latex": raise ValueError("mode \"TeX\" or \"LaTeX\" expected") self.mode = mode if lfs is not _unset: self.lfs = lfs if docclass is not _unset: self.docclass = docclass if docopt is not _unset: self.docopt = docopt if usefiles is not _unset: self.usefiles = usefiles if fontmaps is not _unset: self.fontmaps = fontmaps if waitfortex is not _unset: self.waitfortex = waitfortex if showwaitfortex is not _unset: self.showwaitfortex = showwaitfortex if texipc is not _unset: self.texipc = texipc if texdebug is not _unset: if self.texdebug is not None: self.texdebug.close() if texdebug[-4:] == ".tex": self.texdebug = open(texdebug, "w") else: self.texdebug = open("%s.tex" % texdebug, "w") if dvidebug is not _unset: self.dvidebug = dvidebug if errordebug is not _unset: self.errordebug = errordebug if pyxgraphics is not _unset: self.pyxgraphics = pyxgraphics if errordebug is not _unset: self.errordebug = errordebug if texmessagesstart is not _unset: self.texmessagesstart = texmessagesstart if texmessagesdocclass is not _unset: self.texmessagesdocclass = texmessagesdocclass if texmessagesbegindoc is not _unset: self.texmessagesbegindoc = texmessagesbegindoc if texmessagesend is not _unset: self.texmessagesend = texmessagesend if texmessagesdefaultpreamble is not _unset: self.texmessagesdefaultpreamble = texmessagesdefaultpreamble if texmessagesdefaultrun is not _unset: self.texmessagesdefaultrun = texmessagesdefaultrun def preamble(self, expr, texmessages=[]): r"""put something into the TeX/LaTeX preamble - in LaTeX, this is done before the \begin{document} (you might use \AtBeginDocument, when you're in need for) - it is not allowed to call preamble after calling the text method for the first time (for LaTeX this is needed due to \begin{document}; in TeX it is forced for compatibility (you should be able to switch from TeX to LaTeX, if you want, without breaking something) - preamble expressions must not create any dvi output - args might contain texmessage instances""" if self.texdone or not self.preamblemode: raise RuntimeError("preamble calls disabled due to previous text calls") texmessages = self.defaulttexmessagesdefaultpreamble + self.texmessagesdefaultpreamble + texmessages self.execute(expr, texmessages) self.preambles.append((expr, texmessages)) PyXBoxPattern = re.compile(r"PyXBox:page=(?P\d+),lt=(?P-?\d*((\d\.?)|(\.?\d))\d*)pt,rt=(?P-?\d*((\d\.?)|(\.?\d))\d*)pt,ht=(?P-?\d*((\d\.?)|(\.?\d))\d*)pt,dp=(?P-?\d*((\d\.?)|(\.?\d))\d*)pt:") def text(self, x, y, expr, textattrs=[], texmessages=[]): """create text by passing expr to TeX/LaTeX - returns a textbox containing the result from running expr thru TeX/LaTeX - the box center is set to x, y - *args may contain attr parameters, namely: - textattr instances - texmessage instances - trafo._trafo instances - style.fillstyle instances""" if expr is None: raise ValueError("None expression is invalid") if self.texdone: self.reset(reinit=1) first = 0 if self.preamblemode: if self.mode == "latex": self.execute("\\begin{document}", self.defaulttexmessagesbegindoc + self.texmessagesbegindoc) self.preamblemode = 0 first = 1 textattrs = attr.mergeattrs(textattrs) # perform cleans attr.checkattrs(textattrs, [textattr, trafo.trafo_pt, style.fillstyle]) trafos = attr.getattrs(textattrs, [trafo.trafo_pt]) fillstyles = attr.getattrs(textattrs, [style.fillstyle]) textattrs = attr.getattrs(textattrs, [textattr]) # reverse loop over the merged textattrs (last is applied first) lentextattrs = len(textattrs) for i in range(lentextattrs): expr = textattrs[lentextattrs-1-i].apply(expr) try: self.execute(expr, self.defaulttexmessagesdefaultrun + self.texmessagesdefaultrun + texmessages) except TexResultError: self.finishdvi(ignoretail=1) raise if self.texipc: if first: self.dvifile = dvifile.dvifile("%s.dvi" % self.texfilename, self.fontmap, debug=self.dvidebug) match = self.PyXBoxPattern.search(self.texmessage) if not match or int(match.group("page")) != self.page: raise TexResultError("box extents not found", self) left, right, height, depth = [float(xxx)*72/72.27*unit.x_pt for xxx in match.group("lt", "rt", "ht", "dp")] box = textbox(x, y, left, right, height, depth, self.finishdvi, fillstyles) for t in trafos: box.reltransform(t) # TODO: should trafos really use reltransform??? # this is quite different from what we do elsewhere!!! # see https://sourceforge.net/mailarchive/forum.php?thread_id=9137692&forum_id=23700 if self.texipc: box.setdvicanvas(self.dvifile.readpage([ord("P"), ord("y"), ord("X"), self.page, 0, 0, 0, 0, 0, 0])) else: self.needdvitextboxes.append(box) return box def text_pt(self, x, y, expr, *args, **kwargs): return self.text(x * unit.t_pt, y * unit.t_pt, expr, *args, **kwargs) PyXVariableBoxPattern = re.compile(r"PyXVariableBox:page=(?P\d+),par=(?P\d+),prevgraf=(?P\d+):") def textboxes(self, text, pageshapes): # this is some experimental code to put text into several boxes # while the bounding shape changes from box to box (rectangles only) # first we load sev.tex if not self.textboxesincluded: self.execute(r"\input textboxes.tex", [texmessage.load]) self.textboxesincluded = 1 # define page shapes pageshapes_str = "\\hsize=%.5ftruept%%\n\\vsize=%.5ftruept%%\n" % (72.27/72*unit.topt(pageshapes[0][0]), 72.27/72*unit.topt(pageshapes[0][1])) pageshapes_str += "\\lohsizes={%\n" for hsize, vsize in pageshapes[1:]: pageshapes_str += "{\\global\\hsize=%.5ftruept}%%\n" % (72.27/72*unit.topt(hsize)) pageshapes_str += "{\\relax}%\n}%\n" pageshapes_str += "\\lovsizes={%\n" for hsize, vsize in pageshapes[1:]: pageshapes_str += "{\\global\\vsize=%.5ftruept}%%\n" % (72.27/72*unit.topt(vsize)) pageshapes_str += "{\\relax}%\n}%\n" page = 0 parnos = [] parshapes = [] loop = 0 while 1: self.execute(pageshapes_str, []) parnos_str = "}{".join(parnos) if parnos_str: parnos_str = "{%s}" % parnos_str parnos_str = "\\parnos={%s{\\relax}}%%\n" % parnos_str self.execute(parnos_str, []) parshapes_str = "\\parshapes={%%\n%s%%\n{\\relax}%%\n}%%\n" % "%\n".join(parshapes) self.execute(parshapes_str, []) self.execute("\\global\\count0=1%%\n" "\\global\\parno=0%%\n" "\\global\\myprevgraf=0%%\n" "\\global\\showprevgraf=0%%\n" "\\global\\outputtype=0%%\n" "\\global\\leastcost=10000000%%\n" "%s%%\n" "\\vfill\\supereject%%\n" % text, [texmessage.ignore]) if self.texipc: if self.dvifile is None: self.dvifile = dvifile.dvifile("%s.dvi" % self.texfilename, self.fontmap, debug=self.dvidebug) else: raise RuntimeError("textboxes currently needs texipc") lastparnos = parnos parnos = [] lastparshapes = parshapes parshapes = [] pages = 0 lastpar = prevgraf = -1 m = self.PyXVariableBoxPattern.search(self.texmessage) while m: pages += 1 page = int(m.group("page")) assert page == pages par = int(m.group("par")) prevgraf = int(m.group("prevgraf")) if page <= len(pageshapes): width = 72.27/72*unit.topt(pageshapes[page-1][0]) else: width = 72.27/72*unit.topt(pageshapes[-1][0]) if page < len(pageshapes): nextwidth = 72.27/72*unit.topt(pageshapes[page][0]) else: nextwidth = 72.27/72*unit.topt(pageshapes[-1][0]) if par != lastpar: # a new paragraph is to be broken parnos.append(str(par)) parshape = " 0pt ".join(["%.5ftruept" % width for i in range(prevgraf)]) if len(parshape): parshape = " 0pt " + parshape parshapes.append("{\\parshape %i%s 0pt %.5ftruept}" % (prevgraf + 1, parshape, nextwidth)) elif prevgraf == lastprevgraf: pass else: # we have to append the breaking of the previous paragraph oldparshape = " ".join(parshapes[-1].split(' ')[2:2+2*lastprevgraf]) oldparshape = oldparshape.split('}')[0] if len(parshape): oldparshape = " " + oldparshape parshape = " 0pt ".join(["%.5ftruept" % width for i in range(prevgraf - lastprevgraf)]) if len(parshape): parshape = " 0pt " + parshape else: parshape = " " parshapes[-1] = "{\\parshape %i%s%s 0pt %.5ftruept}" % (prevgraf + 1, oldparshape, parshape, nextwidth) lastpar = par lastprevgraf = prevgraf nextpos = m.end() m = self.PyXVariableBoxPattern.search(self.texmessage, nextpos) result = [] for i in range(pages): result.append(self.dvifile.readpage([i + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0])) if parnos == lastparnos and parshapes == lastparshapes: return result loop += 1 if loop > 100: raise TexResultError("Too many loops in textboxes ", texrunner) # the module provides an default texrunner and methods for direct access defaulttexrunner = texrunner() reset = defaulttexrunner.reset set = defaulttexrunner.set preamble = defaulttexrunner.preamble text = defaulttexrunner.text text_pt = defaulttexrunner.text_pt def escapestring(s, replace={" ": "~", "$": "\\$", "&": "\\&", "#": "\\#", "_": "\\_", "%": "\\%", "^": "\\string^", "~": "\\string~", "<": "{$<$}", ">": "{$>$}", "{": "{$\{$}", "}": "{$\}$}", "\\": "{$\setminus$}", "|": "{$\mid$}"}): "escape all ascii characters such that they are printable by TeX/LaTeX" i = 0 while i < len(s): if not 32 <= ord(s[i]) < 127: raise ValueError("escapestring function handles ascii strings only") c = s[i] try: r = replace[c] except KeyError: i += 1 else: s = s[:i] + r + s[i+1:] i += len(r) return s