Joerg Lehmann venit, vidit, dixit 2006-03-16 17:43:
> Hi Michael,
>
> On 16.03.06, Michael J Gruber wrote:
>> I'm kinda getting to like the deco concept... I remembered an old post
>> by Andre, showing a method (due to Jörg, I think) for laying out text
>> along a curve. I adjusted it to the current API and coded it as a
>> decorator dubbed deco.curvedtext(). I'm attaching a simple example (how
>> to achieve typical line ups) and the code. The code goes into deco.py
>> (current svn). I'm unsure about the following:
>
>> a) Is the patch to dvifile.dvifile.putchar appropriate for going into
>> the trunk, or are there side effects (I don't know that module)?
>
> No, in this form it's not appropriate because this means that we adjust
> the position after each character, which is rather inefficient.
> The correct way would be to either add an option to the texrunner or
> to allow switching of the single char mode on and off in a dynamical
> way, such that you can request this feature for only parts of the
> output.
I see. It can't be a "set" option either because TeX might be running
already, and it should still be able to change the option. I implemented
a workaround, but I don't know how you think about changing class
variables from the outside...
...
>> sys.path.insert(0, os.environ.get("HOME")+"/lib/python/PyX-svn")
>
> Just a side remark: There is os.path.expanduser which allows you to
> just write os.path.expanduser("~/lib/...")
Not much shorter, but nicer.
>> for op in t.dvicanvas.items:
>> if isinstance(op, type1font.text_pt):
>> x = textpos + unit.t_pt*(op.x_pt+op.width_pt/2) # Make sure
>> we rotate with respect to the middle of the character.
>> op.x_pt = -op.width_pt/2
>> c.insert(op, [dp.path.trafo(x)])
>> else:
>> c.insert(op)
>
> My latest version looked like:
>
> items = t.dvicanvas.items
> xs = [item.bbox().center()[0] for item in items]
> trafos = p.trafo(xs)
> for x, op, atrafo in zip(xs, items, trafos):
> c.insert(op, [atrafo, trafo.translate(-x, 0)])
>
>> dp.ornaments.insert(c)
Maybe not shorter, but definitely more pythonish! Also, it's good to
avoid isinstance(). But is this code really more efficient than having
one loop running through all op? It seems we have two list
comprehensions plus one loop now instead of the single loop before.
So, here comes the diff.
Cheers,
Michael
Index: dvifile.py
===================================================================
--- dvifile.py (Revision 2577)
+++ dvifile.py (Arbeitskopie)
@@ -665,13 +665,14 @@
class dvifile:
- def __init__(self, filename, fontmap, debug=0, debugfile=sys.stdout):
+ def __init__(self, filename, fontmap, debug=0, debugfile=sys.stdout, singlecharmode=0):
""" opens the dvi file and reads the preamble """
self.filename = filename
self.fontmap = fontmap
self.debug = debug
self.debugfile = debugfile
self.debugstack = []
+ self.singlecharmode = singlecharmode
self.fonts = {}
self.activefont = None
@@ -772,7 +773,7 @@
self.activetext.addchar(char)
self.pos[_POS_H] += dx
- if not advancepos:
+ if (not advancepos) or self.singlecharmode:
self.flushtext()
def usefont(self, fontnum, id1234=0):
Index: deco.py
===================================================================
--- deco.py (Revision 2576)
+++ deco.py (Arbeitskopie)
@@ -29,7 +29,7 @@
from __future__ import nested_scopes
import sys, math
-import attr, canvas, color, path, normpath, style, trafo, unit
+import attr, canvas, color, path, normpath, style, trafo, type1font, unit
try:
from math import radians
@@ -566,7 +566,58 @@
t.linealign(self.textdist, math.cos(self.angle*math.pi/180), math.sin(self.angle*math.pi/180))
dp.ornaments.insert(t)
+class curvedtext(deco, attr.attr):
+ """a text decorator for curved text"""
+ 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, [atrafo, trafo.translate(-x, 0)])
+
+ dp.ornaments.insert(c)
+
+
+
class shownormpath(deco, attr.attr):
def decorate(self, dp, texrunner):
Index: text.py
===================================================================
--- text.py (Revision 2576)
+++ text.py (Arbeitskopie)
@@ -778,6 +778,7 @@
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,
@@ -800,6 +801,7 @@
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")
@@ -1046,7 +1048,7 @@
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)
+ self.dvifile = dvifile.dvifile(dvifilename, self.fontmap, 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]))
@@ -1204,7 +1206,7 @@
raise
if self.texipc:
if first:
- self.dvifile = dvifile.dvifile("%s.dvi" % self.texfilename, self.fontmap, debug=self.dvidebug)
+ self.dvifile = dvifile.dvifile("%s.dvi" % self.texfilename, self.fontmap, 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)
@@ -1263,7 +1265,7 @@
"\\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)
+ self.dvifile = dvifile.dvifile("%s.dvi" % self.texfilename, self.fontmap, debug=self.dvidebug, singlecharmode=self.singlecharmode)
else:
raise RuntimeError("textboxes currently needs texipc")
lastparnos = parnos