Cython is used to compile some hot paths into native Python extensions. These hot paths were identified through running ufocompile with the hotshot profiler and then converting file by file to Cython, starting with the "hottest" paths and continuing until returns were deminishing. This means that only a few Python files were converted to Cython. Closes #23 Closes #20 (really this time)
293 lines
8.1 KiB
Python
293 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.pointPen 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))
|