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)
173 lines
5.2 KiB
Python
173 lines
5.2 KiB
Python
__all__ = ["AbstractPointPen", "BasePointToSegmentPen", "PrintingPointPen",
|
|
"PrintingSegmentPen", "SegmentPrintingPointPen"]
|
|
|
|
|
|
class AbstractPointPen:
|
|
|
|
def beginPath(self):
|
|
"""Start a new sub path."""
|
|
raise NotImplementedError
|
|
|
|
def endPath(self):
|
|
"""End the current sub path."""
|
|
raise NotImplementedError
|
|
|
|
def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
|
|
"""Add a point to the current sub path."""
|
|
raise NotImplementedError
|
|
|
|
def addComponent(self, baseGlyphName, transformation):
|
|
"""Add a sub glyph."""
|
|
raise NotImplementedError
|
|
|
|
|
|
class BasePointToSegmentPen(AbstractPointPen):
|
|
|
|
"""Base class for retrieving the outline in a segment-oriented
|
|
way. The PointPen protocol is simple yet also a little tricky,
|
|
so when you need an outline presented as segments but you have
|
|
as points, do use this base implementation as it properly takes
|
|
care of all the edge cases.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.currentPath = None
|
|
|
|
def beginPath(self):
|
|
assert self.currentPath is None
|
|
self.currentPath = []
|
|
|
|
def _flushContour(self, segments):
|
|
"""Override this method.
|
|
|
|
It will be called for each non-empty sub path with a list
|
|
of segments: the 'segments' argument.
|
|
|
|
The segments list contains tuples of length 2:
|
|
(segmentType, points)
|
|
|
|
segmentType is one of "move", "line", "curve" or "qcurve".
|
|
"move" may only occur as the first segment, and it signifies
|
|
an OPEN path. A CLOSED path does NOT start with a "move", in
|
|
fact it will not contain a "move" at ALL.
|
|
|
|
The 'points' field in the 2-tuple is a list of point info
|
|
tuples. The list has 1 or more items, a point tuple has
|
|
four items:
|
|
(point, smooth, name, kwargs)
|
|
'point' is an (x, y) coordinate pair.
|
|
|
|
For a closed path, the initial moveTo point is defined as
|
|
the last point of the last segment.
|
|
|
|
The 'points' list of "move" and "line" segments always contains
|
|
exactly one point tuple.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def endPath(self):
|
|
assert self.currentPath is not None
|
|
points = self.currentPath
|
|
self.currentPath = None
|
|
if not points:
|
|
return
|
|
if len(points) == 1:
|
|
# Not much more we can do than output a single move segment.
|
|
pt, segmentType, smooth, name, kwargs = points[0]
|
|
segments = [("move", [(pt, smooth, name, kwargs)])]
|
|
self._flushContour(segments)
|
|
return
|
|
segments = []
|
|
if points[0][1] == "move":
|
|
# It's an open contour, insert a "move" segment for the first
|
|
# point and remove that first point from the point list.
|
|
pt, segmentType, smooth, name, kwargs = points[0]
|
|
segments.append(("move", [(pt, smooth, name, kwargs)]))
|
|
points.pop(0)
|
|
else:
|
|
# It's a closed contour. Locate the first on-curve point, and
|
|
# rotate the point list so that it _ends_ with an on-curve
|
|
# point.
|
|
firstOnCurve = None
|
|
for i in range(len(points)):
|
|
segmentType = points[i][1]
|
|
if segmentType is not None:
|
|
firstOnCurve = i
|
|
break
|
|
if firstOnCurve is None:
|
|
# Special case for quadratics: a contour with no on-curve
|
|
# points. Add a "None" point. (See also the Pen protocol's
|
|
# qCurveTo() method and fontTools.pens.basePen.py.)
|
|
points.append((None, "qcurve", None, None, None))
|
|
else:
|
|
points = points[firstOnCurve+1:] + points[:firstOnCurve+1]
|
|
|
|
currentSegment = []
|
|
for pt, segmentType, smooth, name, kwargs in points:
|
|
currentSegment.append((pt, smooth, name, kwargs))
|
|
if segmentType is None:
|
|
continue
|
|
segments.append((segmentType, currentSegment))
|
|
currentSegment = []
|
|
|
|
self._flushContour(segments)
|
|
|
|
def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
|
|
self.currentPath.append((pt, segmentType, smooth, name, kwargs))
|
|
|
|
|
|
class PrintingPointPen(AbstractPointPen):
|
|
def __init__(self):
|
|
self.havePath = False
|
|
def beginPath(self):
|
|
self.havePath = True
|
|
print "pen.beginPath()"
|
|
def endPath(self):
|
|
self.havePath = False
|
|
print "pen.endPath()"
|
|
def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
|
|
assert self.havePath
|
|
args = ["(%s, %s)" % (pt[0], pt[1])]
|
|
if segmentType is not None:
|
|
args.append("segmentType=%r" % segmentType)
|
|
if smooth:
|
|
args.append("smooth=True")
|
|
if name is not None:
|
|
args.append("name=%r" % name)
|
|
if kwargs:
|
|
args.append("**%s" % kwargs)
|
|
print "pen.addPoint(%s)" % ", ".join(args)
|
|
def addComponent(self, baseGlyphName, transformation):
|
|
assert not self.havePath
|
|
print "pen.addComponent(%r, %s)" % (baseGlyphName, tuple(transformation))
|
|
|
|
|
|
from fontTools.pens.basePen import AbstractPen
|
|
|
|
class PrintingSegmentPen(AbstractPen):
|
|
def moveTo(self, pt):
|
|
print "pen.moveTo(%s)" % (pt,)
|
|
def lineTo(self, pt):
|
|
print "pen.lineTo(%s)" % (pt,)
|
|
def curveTo(self, *pts):
|
|
print "pen.curveTo%s" % (pts,)
|
|
def qCurveTo(self, *pts):
|
|
print "pen.qCurveTo%s" % (pts,)
|
|
def closePath(self):
|
|
print "pen.closePath()"
|
|
def endPath(self):
|
|
print "pen.endPath()"
|
|
def addComponent(self, baseGlyphName, transformation):
|
|
print "pen.addComponent(%r, %s)" % (baseGlyphName, tuple(transformation))
|
|
|
|
|
|
class SegmentPrintingPointPen(BasePointToSegmentPen):
|
|
def _flushContour(self, segments):
|
|
from pprint import pprint
|
|
pprint(segments)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
p = SegmentPrintingPointPen()
|
|
from robofab.test.test_pens import TestShapes
|
|
TestShapes.onCurveLessQuadShape(p)
|