# -*- coding: ISO-8859-1 -*-
#
#
# Copyright (C) 2002-2004 Jrg Lehmann <joergl@users.sourceforge.net>
# Copyright (C) 2003-2007 Michael Schindler <m-schindler@users.sourceforge.net>
# Copyright (C) 2002-2006 Andr Wobst <wobsta@users.sourceforge.net>
#
# 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<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:")
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<page>\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 "(<filename> <arbitrary other stuff>)", where
<filename> 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<filename>(?:(?<!\")[^()\s\n]+(?!\"))|[^()\"\n]+)[\"]?(?P<additional>[^()]*)\)")
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<filename>[^)]+(\.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<filename>[^>]+.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.<page number>
"\\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<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:")
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<page>\d+),par=(?P<par>\d+),prevgraf=(?P<prevgraf>\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
|