Last active
October 23, 2025 11:38
-
-
Save typoman/66040ea9cd636505f027e32282367773 to your computer and use it in GitHub Desktop.
set start point on a glyph contour in python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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