Skip to content

Instantly share code, notes, and snippets.

@typoman
Last active October 23, 2025 11:38
Show Gist options
  • Select an option

  • Save typoman/66040ea9cd636505f027e32282367773 to your computer and use it in GitHub Desktop.

Select an option

Save typoman/66040ea9cd636505f027e32282367773 to your computer and use it in GitHub Desktop.
set start point on a glyph contour in python
from fontTools.pens.pointPen import AbstractPointPen
import defcon
class SetStartPointPen(AbstractPointPen):
"""
A point pen that changes the starting point of a contour.
This pen acts as a filter. It takes another point pen to draw to.
It buffers the points of the specified contour, reorders them
so that the point at `pointIndex` becomes the new starting point,
and then draws the reordered contour to the output pen.
All other contours and all components are passed through unchanged.
Note:
This pen only modifies closed contours. If the specified contour is open,
it will raise an error.
"""
def __init__(self, outputPen, contourIndex=0, pointIndex=0):
self.pen = outputPen
self.contourIndex = contourIndex
self.pointIndex = pointIndex
self._currentContourIndex = -1
self._buffered_points = None
self._buffered_path_info = None
def beginPath(self, identifier=None, **kwargs):
self._currentContourIndex += 1
if self._currentContourIndex == self.contourIndex:
if self._buffered_points is not None:
raise PenError("Path already begun.")
self._buffered_points = []
self._buffered_path_info = {"identifier": identifier, "kwargs": kwargs}
else:
self.pen.beginPath(identifier=identifier, **kwargs)
def endPath(self):
if self._currentContourIndex == self.contourIndex:
if self._buffered_points is None:
raise PenError("Path not begun.")
points = self._buffered_points
info = self._buffered_path_info
self._buffered_points = None
self._buffered_path_info = None
if points:
is_closed = points[0][1] != "move"
if is_closed:
if not (0 <= self.pointIndex < len(points)):
raise PenError(f"pointIndex is out of range.")
if points[self.pointIndex][1] is None:
raise PenError(f"The starting point should be an on curve point.")
points = points[self.pointIndex :] + points[: self.pointIndex]
else:
raise PenError("Can't set start point on an open path.")
self.pen.beginPath(identifier=info["identifier"], **info["kwargs"])
for pt, segmentType, smooth, name, kwargs in points:
self.pen.addPoint(pt, segmentType, smooth, name, **kwargs)
self.pen.endPath()
else:
self.pen.endPath()
def addPoint(
self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs
):
if self._currentContourIndex == self.contourIndex:
if self._buffered_points is None:
raise PenError("Path not begun")
if identifier is not None:
kwargs["identifier"] = identifier
self._buffered_points.append((pt, segmentType, smooth, name, kwargs))
else:
self.pen.addPoint(pt, segmentType, smooth, name, identifier, **kwargs)
# defcon implementation
def setStartPoint(contour, point_index):
new_contour = defcon.Contour(pointClass=contour.pointClass)
pen = SetStartPointPen(new_contour, contourIndex=0, pointIndex=point_index)
contour.drawPoints(pen)
contour._clear(postNotification=False)
contour._points = new_contour._points
contour.postNotification("Contour.PointsChanged")
contour.dirty = True
if __name__ == '__main__':
# RoboFont, select an on curve point
g = CurrentGlyph()
for c in g.contours:
for s in c.segments:
for p in s:
if p.selected:
setStartPoint(c.naked(), p.index)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment