From: Michael J G. <mic...@us...> - 2011-10-09 15:26:02
|
Implement a decorator curvedtext for setting text along a given path. curvedtext switches to singlecharmode, but only while it is needed. The basic idea has been discussed on the list and goes back to the PyX grand masters. Signed-off-by: Michael J Gruber <mic...@us...> --- v2 has singlecharmode as a parameter to text() rather than the texrunner. The drawback is that ensuredvicanvas() and finishdvi() need that parameter also, or else the last few characters come out wrong. --- examples/text/INDEX | 1 + examples/text/textalongpath.py | 15 ++++++++++ examples/text/textalongpath.txt | 5 +++ pyx/deco.py | 56 +++++++++++++++++++++++++++++++++++++++ pyx/dvi/dvifile.py | 5 ++- pyx/text.py | 12 ++++---- 6 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 examples/text/textalongpath.py create mode 100644 examples/text/textalongpath.txt diff --git a/examples/text/INDEX b/examples/text/INDEX index 04692ae..c982f89 100644 --- a/examples/text/INDEX +++ b/examples/text/INDEX @@ -5,3 +5,4 @@ marker color texrunner textbox +textalongpath diff --git a/examples/text/textalongpath.py b/examples/text/textalongpath.py new file mode 100644 index 0000000..f4aad4e --- /dev/null +++ b/examples/text/textalongpath.py @@ -0,0 +1,15 @@ +from pyx import * + +c = canvas.canvas() + +R = 1.3 +p = path.path(path.arc(0,0, R, 0,270)) + path.line(0,-R, R,-R) +label = (r"\PyX{} is fun. " * 4)[:-1] # chop off last space + +c.draw(p, [deco.stroked([color.rgb.blue]), deco.curvedtext(label)]) +c.draw(p, [trafo.translate(2.5*R,0), deco.stroked([color.rgb.blue]), deco.curvedtext(label,textattrs=[text.halign.right],relarclenpos=1)]) +c.draw(p.reversed(), [trafo.translate(0, -2.5*R), deco.stroked([color.rgb.blue]), deco.curvedtext(label,textattrs=[text.halign.right],relarclenpos=1)]) +c.draw(p.reversed(), [trafo.translate(2.5*R, -2.5*R), deco.stroked([color.rgb.blue]), deco.curvedtext(label)]) + +c.writeEPSfile("textalongpath") +c.writePDFfile("textalongpath") diff --git a/examples/text/textalongpath.txt b/examples/text/textalongpath.txt new file mode 100644 index 0000000..1737993 --- /dev/null +++ b/examples/text/textalongpath.txt @@ -0,0 +1,5 @@ +Text along path + +! In order to set text along a given path, you can use the `curvedtext()` +decorator. The examples show how you can position the text relative to the +path. diff --git a/pyx/deco.py b/pyx/deco.py index 656845f..ceb6712 100644 --- a/pyx/deco.py +++ b/pyx/deco.py @@ -564,6 +564,62 @@ class text(deco, attr.attr): t.linealign(self.textdist, math.cos(angle), math.sin(angle)) dp.ornaments.insert(t) +class curvedtext(deco, attr.attr): + """a text decorator for curved text + + - text: is typeset along the path to which this decorator is applied + - relarclenpos: position for the base point of the text (default: 0) + - arlenfrombegin, arclenfromend: alternative ways of specifying the position of the base point; + use of relarclenpos, arclenfrombegin and arclenfromend is mutually exclusive + - textattrs, texrunner: standard text arguments (defaults: [] resp None) + + """ + + def __init__(self, text, textattrs=[], + relarclenpos=0, arclenfrombegin=None, arclenfromend=None, + texrunner=None): + if arclenfrombegin is not None and arclenfromend is not None: + raise ValueError("either set arclenfrombegin or arclenfromend") + self.text = text + self.textattrs = textattrs + self.relarclenpos = relarclenpos + self.arclenfrombegin = arclenfrombegin + self.arclenfromend = arclenfromend + self.texrunner = texrunner + + def decorate(self, dp, texrunner): + if self.texrunner: + texrunner = self.texrunner + import text as textmodule + + dp.ensurenormpath() + if self.arclenfrombegin is not None: + textpos = dp.path.begin() + self.arclenfrombegin + elif self.arclenfromend is not None: + textpos = dp.path.end() - self.arclenfromend + else: + # relarcpos is used if neither arcfrombegin nor arcfromend is given + textpos = self.relarclenpos * dp.path.arclen() + + c = canvas.canvas() + + t = texrunner.text(0, 0, self.text, self.textattrs, singlecharmode=1) + + # copy over attr ops (colour...) + # isinstance(op, canvas._canvas) should not occur before ensuredvicanvas; should we even care to check? + [ c.insert(op) for op in t.items if not isinstance(op, canvas._canvas)] + + t.ensuredvicanvas(singlecharmode=1) + + items = t.dvicanvas.items + xs = [item.bbox().center()[0] for item in items] + trafos = dp.path.trafo([textpos +x for x in xs]) + for x, op, atrafo in zip(xs, items, trafos): + c.insert(op, [trafo.translate(-x, 0), atrafo]) # reversed trafos: fix for change in canvas.py from r2728 to 2730 + + dp.ornaments.insert(c) + + class shownormpath(deco, attr.attr): diff --git a/pyx/dvi/dvifile.py b/pyx/dvi/dvifile.py index b1e8123..d65e952 100644 --- a/pyx/dvi/dvifile.py +++ b/pyx/dvi/dvifile.py @@ -112,12 +112,13 @@ class _restoretrafo(canvasitem.canvasitem): class DVIfile: - def __init__(self, filename, debug=0, debugfile=sys.stdout): + def __init__(self, filename, debug=0, debugfile=sys.stdout, singlecharmode=0): """ opens the dvi file and reads the preamble """ self.filename = filename self.debug = debug self.debugfile = debugfile self.debugstack = [] + self.singlecharmode = singlecharmode self.fonts = {} self.activefont = None @@ -197,7 +198,7 @@ class DVIfile: self.activetext[2].append(char) self.pos[_POS_H] += dx - if not advancepos: + if (not advancepos) or self.singlecharmode: self.flushtext(fontmap) def usefont(self, fontnum, id1234, fontmap): diff --git a/pyx/text.py b/pyx/text.py index 242a4b9..b55667d 100644 --- a/pyx/text.py +++ b/pyx/text.py @@ -700,9 +700,9 @@ class textbox(box.rect, canvas._canvas): raise RuntimeError("multiple call to setdvicanvas") self.dvicanvas = dvicanvas - def ensuredvicanvas(self): + def ensuredvicanvas(self, singlecharmode=0): if self.dvicanvas is None: - self.finishdvi() + self.finishdvi(singlecharmode=singlecharmode) assert self.dvicanvas is not None, "finishdvi is broken" if not self.insertdvicanvas: self.insert(self.dvicanvas, [self.texttrafo]) @@ -1024,14 +1024,14 @@ class texrunner: else: raise TexResultError("TeX didn't respond as expected within the timeout period (%i seconds)." % self.waitfortex, self) - def finishdvi(self, ignoretail=0): + def finishdvi(self, ignoretail=0, singlecharmode=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, debug=self.dvidebug) + self.dvifile = dvifile.DVIfile(dvifilename, debug=self.dvidebug, singlecharmode=singlecharmode) 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], fontmap=box.fontmap)) @@ -1151,7 +1151,7 @@ class texrunner: 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=[], fontmap=None): + def text(self, x, y, expr, textattrs=[], texmessages=[], fontmap=None, singlecharmode=0): """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 @@ -1190,7 +1190,7 @@ class texrunner: raise e if self.texipc: if first: - self.dvifile = dvifile.DVIfile("%s.dvi" % self.texfilename, debug=self.dvidebug) + self.dvifile = dvifile.DVIfile("%s.dvi" % self.texfilename, debug=self.dvidebug, singlecharmode=singlecharmode) match = self.PyXBoxPattern.search(self.texmessage) if not match or int(match.group("page")) != self.page: raise TexResultError("box extents not found", self) -- 1.7.7.338.g0156b |