def endPath(self) -> None: """ End the current sub path. """ # TrueType contours are always "closed" if self._isClosed(): raise PenError("Contour is already closed.") if self._currentContourStartIndex == len(self.points): raise PenError("Tried to end an empty contour.") self.endPts.append(len(self.points) - 1) self._currentContourStartIndex = None
def beginPath(self, identifier=None, **kwargs): if self._points is not None: raise PenError("Path already begun") self._points = [] if identifier is not None: kwargs["identifier"] = identifier self._outPen.beginPath(**kwargs)
def glyph(self, componentFlags: int = 0x4) -> Glyph: """ Returns a :py:class:`~._g_l_y_f.Glyph` object representing the glyph. """ if not self._isClosed(): raise PenError("Didn't close last contour.") components = self._buildComponents(componentFlags) glyph = Glyph() glyph.coordinates = GlyphCoordinates(self.points) glyph.coordinates.toInt() glyph.endPtsOfContours = self.endPts glyph.flags = array("B", self.types) self.init() if components: # If both components and contours were present, they have by now # been decomposed by _buildComponents. glyph.components = components glyph.numberOfContours = -1 else: glyph.numberOfContours = len(glyph.endPtsOfContours) glyph.program = ttProgram.Program() glyph.program.fromBytecode(b"") return glyph
def addComponent(self, glyphName, transform, identifier=None, **kwargs): if self.currentContour is not None: raise PenError("Components must be added before or after contours") self.pen.addComponent(glyphName, transform, identifier=identifier, **kwargs)
def addPoint(self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs): if self._points is None: raise PenError("Path not begun") if identifier is not None: kwargs["identifier"] = identifier self._points.append((pt, segmentType, False, name, kwargs))
def _qCurveToOne(self, p1, p2): if not (self.contours and len(self.contours[-1].points) > 0): raise PenError("Contour missing required initial moveTo") t1, t2 = FT_CURVE_TAG_CONIC, FT_CURVE_TAG_ON contour = self.contours[-1] for p, t in ((p1, t1), (p2, t2)): contour.points.append(p) contour.tags.append(t)
def curveTo(self, *pts): if not pts: raise TypeError("Must pass in at least one point") if self.contour is None: raise PenError("Contour missing required initial moveTo") for pt in pts[:-1]: self.contour.append((pt, None)) self.contour.append((pts[-1], "curve"))
def beginPath(self, identifier: Optional[str] = None, **kwargs: Any) -> None: """ Start a new sub path. """ if not self._isClosed(): raise PenError("Didn't close previous contour.") self._currentContourStartIndex = len(self.points)
def addComponent(self, glyphName, transformation, identifier=None, **kwargs): if self._points is not None: raise PenError("Components must be added before or after contours") if identifier is not None: kwargs["identifier"] = identifier self._outPen.addComponent(glyphName, transformation, **kwargs)
def addPoint(self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs): if self.currentPath is None: raise PenError("Path not begun") self.currentPath.append((pt, segmentType, smooth, name, kwargs))
def closePath(self): if self.contour is None: raise PenError("Contour missing required initial moveTo") 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): if self.currentPath is None: raise PenError("Path not begun.") 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 _flushContour(self): if self._points is None: raise PenError("Path not begun") 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, _, 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 addPoint( self, pt: Tuple[float, float], segmentType: Optional[str] = None, smooth: bool = False, name: Optional[str] = None, identifier: Optional[str] = None, **kwargs: Any, ) -> None: """ Add a point to the current sub path. """ if self._isClosed(): raise PenError("Can't add a point to a closed contour.") if segmentType is None: self.types.append(0) # offcurve elif segmentType in ("qcurve", "line"): self.types.append(1) # oncurve else: # cubic curves are not supported raise NotImplementedError self.points.append(pt)
def _flushContour(self, segments): if not segments: raise PenError("Must have at least one segment.") pen = self.pen if segments[0][0] == "move": # It's an open path. closed = False points = segments[0][1] if len(points) != 1: raise PenError( f"Illegal move segment point count: {len(points)}") movePt, _, _, _ = 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, _, _, _ = 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) lastPt = movePt for i in range(nSegments): segmentType, points = segments[i] points = [pt for pt, _, _, _ in points] if segmentType == "line": if len(points) != 1: raise PenError( f"Illegal line segment point count: {len(points)}") pt = points[0] # For closed contours, a 'lineTo' is always implied from the last oncurve # point to the starting point, thus we can omit it when the last and # starting point don't overlap. # However, when the last oncurve point is a "line" segment and has same # coordinates as the starting point of a closed contour, we need to output # the closing 'lineTo' explicitly (regardless of the value of the # 'outputImpliedClosingLine' option) in order to disambiguate this case from # the implied closing 'lineTo', otherwise the duplicate point would be lost. # See https://github.com/googlefonts/fontmake/issues/572. if (i + 1 != nSegments or outputImpliedClosingLine or not closed or pt == lastPt): pen.lineTo(pt) lastPt = pt elif segmentType == "curve": pen.curveTo(*points) lastPt = points[-1] elif segmentType == "qcurve": pen.qCurveTo(*points) lastPt = points[-1] else: raise PenError(f"Illegal segmentType: {segmentType}") if closed: pen.closePath() else: pen.endPath()
def moveTo(self, pt: Tuple[float, float]) -> None: if not self._isClosed(): raise PenError('"move"-type point must begin a new contour.') self._addPoint(pt, 1)
def lineTo(self, pt): if self.contour is None: raise PenError("Contour missing required initial moveTo") self.contour.append((pt, "line"))
def _lineTo(self, pt): if not (self.contours and len(self.contours[-1].points) > 0): raise PenError("Contour missing required initial moveTo") contour = self.contours[-1] contour.points.append(pt) contour.tags.append(FT_CURVE_TAG_ON)
def beginPath(self, identifier=None, **kwargs): if self.currentPath is not None: raise PenError("Path already begun.") self.currentPath = []
def endPath(self): if self.contour is None: raise PenError("Contour missing required initial moveTo") self._flushContour() self.contour = None
def endPath(self): if self.currentContour is None: raise PenError("Path not begun") self._flushContour() self.currentContour = None
def beginPath(self, identifier=None, **kwargs): if self.currentContour is not None: raise PenError("Path already begun") self.currentContour = [] self.currentContourIdentifier = identifier self.onCurve = []
def addComponent(self, glyphName, transform): if self.contour is not None: raise PenError("Components must be added before or after contours") self.pen.addComponent(glyphName, transform)