|
From: Michael J G. <mic...@us...> - 2011-10-07 16:14:52
|
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...>
---
examples/text/INDEX | 1 +
examples/text/textalongpath.py | 15 ++++++++++
examples/text/textalongpath.txt | 5 +++
pyx/deco.py | 59 +++++++++++++++++++++++++++++++++++++++
pyx/dvi/dvifile.py | 5 ++-
pyx/text.py | 8 +++--
6 files changed, 88 insertions(+), 5 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..c410aa4
--- /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..a6a3359 100644
--- a/pyx/deco.py
+++ b/pyx/deco.py
@@ -564,6 +564,65 @@ 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()
+
+ singlecharmode=texrunner.singlecharmode # usually 0
+ texrunner.singlecharmode=1
+ t = texrunner.text(0, 0, self.text, self.textattrs)
+
+ # 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()
+ texrunner.singlecharmode=singlecharmode
+
+ 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..a7e94ac 100644
--- a/pyx/text.py
+++ b/pyx/text.py
@@ -791,6 +791,7 @@ class texrunner:
waitfortex=config.getint("text", "waitfortex", 60),
showwaitfortex=config.getint("text", "showwaitfortex", 5),
texipc=config.getboolean("text", "texipc", 0),
+ singlecharmode=0,
texdebug=None,
dvidebug=0,
errordebug=1,
@@ -812,6 +813,7 @@ class texrunner:
self.waitfortex = waitfortex
self.showwaitfortex = showwaitfortex
self.texipc = texipc
+ self.singlecharmode = singlecharmode
if texdebug is not None:
if texdebug[-4:] == ".tex":
self.texdebug = open(texdebug, "w")
@@ -1031,7 +1033,7 @@ class texrunner:
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=self.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))
@@ -1190,7 +1192,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=self.singlecharmode)
match = self.PyXBoxPattern.search(self.texmessage)
if not match or int(match.group("page")) != self.page:
raise TexResultError("box extents not found", self)
@@ -1252,7 +1254,7 @@ class texrunner:
"\\vfill\\supereject%%\n" % text, [texmessage.ignore])
if self.texipc:
if self.dvifile is None:
- self.dvifile = dvifile.DVIfile("%s.dvi" % self.texfilename, debug=self.dvidebug)
+ self.dvifile = dvifile.DVIfile("%s.dvi" % self.texfilename, debug=self.dvidebug, singlecharmode=self.singlecharmode)
else:
raise RuntimeError("textboxes currently needs texipc")
lastparnos = parnos
--
1.7.7.602.g8c3f8
|