summaryrefslogtreecommitdiff
path: root/misc/pylib/robofab/pens/pointPen.py
diff options
context:
space:
mode:
Diffstat (limited to 'misc/pylib/robofab/pens/pointPen.py')
-rw-r--r--misc/pylib/robofab/pens/pointPen.py173
1 files changed, 173 insertions, 0 deletions
diff --git a/misc/pylib/robofab/pens/pointPen.py b/misc/pylib/robofab/pens/pointPen.py
new file mode 100644
index 000000000..dc81df38e
--- /dev/null
+++ b/misc/pylib/robofab/pens/pointPen.py
@@ -0,0 +1,173 @@
+__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)