git-svn-id: http://svn.robofab.com/branches/ufo3k@447 b5fa9d6c-a76f-4ffd-b3cb-f825fc41095c
294 lines
8.1 KiB
Python
294 lines
8.1 KiB
Python
import math
|
|
from fontTools.pens.basePen import AbstractPen
|
|
from robofab.pens.pointPen import AbstractPointPen, BasePointToSegmentPen
|
|
|
|
|
|
class FabToFontToolsPenAdapter:
|
|
|
|
"""Class that covers up the subtle differences between RoboFab
|
|
Pens and FontTools Pens. 'Fab should eventually move to FontTools
|
|
Pens, this class may help to make the transition smoother.
|
|
"""
|
|
|
|
# XXX The change to FontTools pens has almost been completed. Any
|
|
# usage of this class should be highly suspect.
|
|
|
|
def __init__(self, fontToolsPen):
|
|
self.fontToolsPen = fontToolsPen
|
|
|
|
def moveTo(self, pt, **kargs):
|
|
self.fontToolsPen.moveTo(pt)
|
|
|
|
def lineTo(self, pt, **kargs):
|
|
self.fontToolsPen.lineTo(pt)
|
|
|
|
def curveTo(self, *pts, **kargs):
|
|
self.fontToolsPen.curveTo(*pts)
|
|
|
|
def qCurveTo(self, *pts, **kargs):
|
|
self.fontToolsPen.qCurveTo(*pts)
|
|
|
|
def closePath(self):
|
|
self.fontToolsPen.closePath()
|
|
|
|
def endPath(self):
|
|
self.fontToolsPen.endPath()
|
|
|
|
def addComponent(self, glyphName, offset=(0, 0), scale=(1, 1)):
|
|
self.fontToolsPen.addComponent(glyphName,
|
|
(scale[0], 0, 0, scale[1], offset[0], offset[1]))
|
|
|
|
def setWidth(self, width):
|
|
self.width = width
|
|
|
|
def setNote(self, note):
|
|
pass
|
|
|
|
def addAnchor(self, name, pt):
|
|
self.fontToolsPen.moveTo(pt)
|
|
self.fontToolsPen.endPath()
|
|
|
|
def doneDrawing(self):
|
|
pass
|
|
|
|
|
|
class PointToSegmentPen(BasePointToSegmentPen):
|
|
|
|
"""Adapter class that converts the PointPen protocol to the
|
|
(Segment)Pen protocol.
|
|
"""
|
|
|
|
def __init__(self, segmentPen, outputImpliedClosingLine=False):
|
|
BasePointToSegmentPen.__init__(self)
|
|
self.pen = segmentPen
|
|
self.outputImpliedClosingLine = outputImpliedClosingLine
|
|
|
|
def _flushContour(self, segments):
|
|
assert len(segments) >= 1
|
|
pen = self.pen
|
|
if segments[0][0] == "move":
|
|
# It's an open path.
|
|
closed = False
|
|
points = segments[0][1]
|
|
assert len(points) == 1
|
|
movePt, smooth, name, kwargs = points[0]
|
|
del segments[0]
|
|
else:
|
|
# It's a closed path, do a moveTo to the last
|
|
# point of the last segment.
|
|
closed = True
|
|
segmentType, points = segments[-1]
|
|
movePt, smooth, name, kwargs = points[-1]
|
|
if movePt is None:
|
|
# quad special case: a contour with no on-curve points contains
|
|
# one "qcurve" segment that ends with a point that's None. We
|
|
# must not output a moveTo() in that case.
|
|
pass
|
|
else:
|
|
pen.moveTo(movePt)
|
|
outputImpliedClosingLine = self.outputImpliedClosingLine
|
|
nSegments = len(segments)
|
|
for i in range(nSegments):
|
|
segmentType, points = segments[i]
|
|
points = [pt for pt, smooth, name, kwargs in points]
|
|
if segmentType == "line":
|
|
assert len(points) == 1
|
|
pt = points[0]
|
|
if i + 1 != nSegments or outputImpliedClosingLine or not closed:
|
|
pen.lineTo(pt)
|
|
elif segmentType == "curve":
|
|
pen.curveTo(*points)
|
|
elif segmentType == "qcurve":
|
|
pen.qCurveTo(*points)
|
|
else:
|
|
assert 0, "illegal segmentType: %s" % segmentType
|
|
if closed:
|
|
pen.closePath()
|
|
else:
|
|
pen.endPath()
|
|
|
|
def addComponent(self, glyphName, transform):
|
|
self.pen.addComponent(glyphName, transform)
|
|
|
|
|
|
class SegmentToPointPen(AbstractPen):
|
|
|
|
"""Adapter class that converts the (Segment)Pen protocol to the
|
|
PointPen protocol.
|
|
"""
|
|
|
|
def __init__(self, pointPen, guessSmooth=True):
|
|
if guessSmooth:
|
|
self.pen = GuessSmoothPointPen(pointPen)
|
|
else:
|
|
self.pen = pointPen
|
|
self.contour = None
|
|
|
|
def _flushContour(self):
|
|
pen = self.pen
|
|
pen.beginPath()
|
|
for pt, segmentType in self.contour:
|
|
pen.addPoint(pt, segmentType=segmentType)
|
|
pen.endPath()
|
|
|
|
def moveTo(self, pt):
|
|
self.contour = []
|
|
self.contour.append((pt, "move"))
|
|
|
|
def lineTo(self, pt):
|
|
self.contour.append((pt, "line"))
|
|
|
|
def curveTo(self, *pts):
|
|
for pt in pts[:-1]:
|
|
self.contour.append((pt, None))
|
|
self.contour.append((pts[-1], "curve"))
|
|
|
|
def qCurveTo(self, *pts):
|
|
if pts[-1] is None:
|
|
self.contour = []
|
|
for pt in pts[:-1]:
|
|
self.contour.append((pt, None))
|
|
if pts[-1] is not None:
|
|
self.contour.append((pts[-1], "qcurve"))
|
|
|
|
def closePath(self):
|
|
if len(self.contour) > 1 and self.contour[0][0] == self.contour[-1][0]:
|
|
self.contour[0] = self.contour[-1]
|
|
del self.contour[-1]
|
|
else:
|
|
# There's an implied line at the end, replace "move" with "line"
|
|
# for the first point
|
|
pt, tp = self.contour[0]
|
|
if tp == "move":
|
|
self.contour[0] = pt, "line"
|
|
self._flushContour()
|
|
self.contour = None
|
|
|
|
def endPath(self):
|
|
self._flushContour()
|
|
self.contour = None
|
|
|
|
def addComponent(self, glyphName, transform):
|
|
assert self.contour is None
|
|
self.pen.addComponent(glyphName, transform)
|
|
|
|
|
|
class TransformPointPen(AbstractPointPen):
|
|
|
|
"""PointPen that transforms all coordinates, and passes them to another
|
|
PointPen. It also transforms the transformation given to addComponent().
|
|
"""
|
|
|
|
def __init__(self, outPen, transformation):
|
|
if not hasattr(transformation, "transformPoint"):
|
|
from fontTools.misc.transform import Transform
|
|
transformation = Transform(*transformation)
|
|
self._transformation = transformation
|
|
self._transformPoint = transformation.transformPoint
|
|
self._outPen = outPen
|
|
self._stack = []
|
|
|
|
def beginPath(self):
|
|
self._outPen.beginPath()
|
|
|
|
def endPath(self):
|
|
self._outPen.endPath()
|
|
|
|
def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
|
|
pt = self._transformPoint(pt)
|
|
self._outPen.addPoint(pt, segmentType, smooth, name, **kwargs)
|
|
|
|
def addComponent(self, glyphName, transformation):
|
|
transformation = self._transformation.transform(transformation)
|
|
self._outPen.addComponent(glyphName, transformation)
|
|
|
|
|
|
class GuessSmoothPointPen(AbstractPointPen):
|
|
|
|
"""Filtering PointPen that tries to determine whether an on-curve point
|
|
should be "smooth", ie. that it's a "tangent" point or a "curve" point.
|
|
"""
|
|
|
|
def __init__(self, outPen):
|
|
self._outPen = outPen
|
|
self._points = None
|
|
|
|
def _flushContour(self):
|
|
points = self._points
|
|
nPoints = len(points)
|
|
if not nPoints:
|
|
return
|
|
if points[0][1] == "move":
|
|
# Open path.
|
|
indices = range(1, nPoints - 1)
|
|
elif nPoints > 1:
|
|
# Closed path. To avoid having to mod the contour index, we
|
|
# simply abuse Python's negative index feature, and start at -1
|
|
indices = range(-1, nPoints - 1)
|
|
else:
|
|
# closed path containing 1 point (!), ignore.
|
|
indices = []
|
|
for i in indices:
|
|
pt, segmentType, dummy, name, kwargs = points[i]
|
|
if segmentType is None:
|
|
continue
|
|
prev = i - 1
|
|
next = i + 1
|
|
if points[prev][1] is not None and points[next][1] is not None:
|
|
continue
|
|
# At least one of our neighbors is an off-curve point
|
|
pt = points[i][0]
|
|
prevPt = points[prev][0]
|
|
nextPt = points[next][0]
|
|
if pt != prevPt and pt != nextPt:
|
|
dx1, dy1 = pt[0] - prevPt[0], pt[1] - prevPt[1]
|
|
dx2, dy2 = nextPt[0] - pt[0], nextPt[1] - pt[1]
|
|
a1 = math.atan2(dx1, dy1)
|
|
a2 = math.atan2(dx2, dy2)
|
|
if abs(a1 - a2) < 0.05:
|
|
points[i] = pt, segmentType, True, name, kwargs
|
|
|
|
for pt, segmentType, smooth, name, kwargs in points:
|
|
self._outPen.addPoint(pt, segmentType, smooth, name, **kwargs)
|
|
|
|
def beginPath(self):
|
|
assert self._points is None
|
|
self._points = []
|
|
self._outPen.beginPath()
|
|
|
|
def endPath(self):
|
|
self._flushContour()
|
|
self._outPen.endPath()
|
|
self._points = None
|
|
|
|
def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
|
|
self._points.append((pt, segmentType, False, name, kwargs))
|
|
|
|
def addComponent(self, glyphName, transformation):
|
|
assert self._points is None
|
|
self._outPen.addComponent(glyphName, transformation)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
from fontTools.pens.basePen import _TestPen as PSPen
|
|
from robofab.pens.printingPens import PrintingPointPen
|
|
segmentPen = PSPen(None)
|
|
# pen = PointToSegmentPen(SegmentToPointPen(PointToSegmentPen(PSPen(None))))
|
|
pen = PointToSegmentPen(SegmentToPointPen(PrintingPointPen()))
|
|
# pen = PrintingPointPen()
|
|
pen = PointToSegmentPen(PSPen(None), outputImpliedClosingLine=False)
|
|
# pen.beginPath()
|
|
# pen.addPoint((50, 50), name="an anchor")
|
|
# pen.endPath()
|
|
pen.beginPath()
|
|
pen.addPoint((-100, 0), segmentType="line")
|
|
pen.addPoint((0, 0), segmentType="line")
|
|
pen.addPoint((0, 100), segmentType="line")
|
|
pen.addPoint((30, 200))
|
|
pen.addPoint((50, 100), name="superbezcontrolpoint!")
|
|
pen.addPoint((70, 200))
|
|
pen.addPoint((100, 100), segmentType="curve")
|
|
pen.addPoint((100, 0), segmentType="line")
|
|
pen.endPath()
|
|
# pen.addComponent("a", (1, 0, 0, 1, 100, 200))
|