We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
TTGlyphPointPen doesn't handle open contours (starting with 'move')
in the PointPen protocol used by UFO glyphs, open contours start with a point that has segmentType="move", see https://unifiedfontobject.org/versions/ufo3/glyphs/glif/#point-types

Open contours are valid in UFO, and the original TTGlyphPen treats them as closed for the sake of building TrueType glyphs (where all paths are implicilty closed and filled with black).

But if one tries to construct an open contour with the TTGlyphPointPen, one gets a NotImplementedError:

```python
>>> from fontTools.pens.ttGlyphPen import TTGlyphPointPen, TTGlyphPen
>>> pen = TTGlyphPointPen(None)
>>> pen.beginPath()
>>> pen.addPoint((0, 0), "move")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/clupo/Github/fonttools/Lib/fontTools/pens/ttGlyphPen.py", line 282, in addPoint
    raise NotImplementedError
NotImplementedError
```

Whereas this works fine using `TTGlyphPen`, where `endPath` (the method that in segment pens signals an open contour) is aliased to `closePath`, e.g.:

```python
pen = TTGlyphPen(None)
pen.moveTo((0, 0))
pen.lineTo((10, 10))
pen.lineTo((20, 0))
pen.endPath()  # or pen.closePath()
```

The current code in TTGlyphPointPen is incorrectly assuming that the only segment types for point pens are: None (for an off-curve point), "line", "qcurve" and "curve"; it's completely ignoring the explicit "move" case. Actually, an inline comment above the NotImplementedError signals that the author incorrectly assumed that when the segment type is neither None, "line" nor "qcurve" then it must be a (cubic) "curve" -- which is in fact not supported and should error with the NotImplementedError --, but "move" is a valid segment type and should not fall through the `else:` block.

https://github.com/fonttools/fonttools/blob/aae11e45c29f4d07770574ce8577e6e61bc2b15c/Lib/fontTools/pens/ttGlyphPen.py#L276-L282

"move" should be treated like "line" or "qcurve", i.e. like a on-curve point.
This way the TTGlyphPointPen will treat incoming open contours the same way as the TTGlyphPen, that is, as implicitly closed.






--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `Lib/fontTools/pens/ttGlyphPen.py`
Content:
```
1 from array import array
2 from typing import Any, Dict, Optional, Tuple
3 from fontTools.misc.fixedTools import MAX_F2DOT14, floatToFixedToFloat
4 from fontTools.misc.loggingTools import LogMixin
5 from fontTools.pens.pointPen import AbstractPointPen
6 from fontTools.misc.roundTools import otRound
7 from fontTools.pens.basePen import LoggingPen, PenError
8 from fontTools.pens.transformPen import TransformPen, TransformPointPen
9 from fontTools.ttLib.tables import ttProgram
10 from fontTools.ttLib.tables._g_l_y_f import Glyph
11 from fontTools.ttLib.tables._g_l_y_f import GlyphComponent
12 from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
13 
14 
15 __all__ = ["TTGlyphPen", "TTGlyphPointPen"]
16 
17 
18 class _TTGlyphBasePen:
19     def __init__(
20         self,
21         glyphSet: Optional[Dict[str, Any]],
22         handleOverflowingTransforms: bool = True,
23     ) -> None:
24         """
25         Construct a new pen.
26 
27         Args:
28             glyphSet (Dict[str, Any]): A glyphset object, used to resolve components.
29             handleOverflowingTransforms (bool): See below.
30 
31         If ``handleOverflowingTransforms`` is True, the components' transform values
32         are checked that they don't overflow the limits of a F2Dot14 number:
33         -2.0 <= v < +2.0. If any transform value exceeds these, the composite
34         glyph is decomposed.
35 
36         An exception to this rule is done for values that are very close to +2.0
37         (both for consistency with the -2.0 case, and for the relative frequency
38         these occur in real fonts). When almost +2.0 values occur (and all other
39         values are within the range -2.0 <= x <= +2.0), they are clamped to the
40         maximum positive value that can still be encoded as an F2Dot14: i.e.
41         1.99993896484375.
42 
43         If False, no check is done and all components are translated unmodified
44         into the glyf table, followed by an inevitable ``struct.error`` once an
45         attempt is made to compile them.
46 
47         If both contours and components are present in a glyph, the components
48         are decomposed.
49         """
50         self.glyphSet = glyphSet
51         self.handleOverflowingTransforms = handleOverflowingTransforms
52         self.init()
53 
54     def _decompose(
55         self,
56         glyphName: str,
57         transformation: Tuple[float, float, float, float, float, float],
58     ):
59         tpen = self.transformPen(self, transformation)
60         getattr(self.glyphSet[glyphName], self.drawMethod)(tpen)
61 
62     def _isClosed(self):
63         """
64         Check if the current path is closed.
65         """
66         raise NotImplementedError
67 
68     def init(self) -> None:
69         self.points = []
70         self.endPts = []
71         self.types = []
72         self.components = []
73 
74     def addComponent(
75         self,
76         baseGlyphName: str,
77         transformation: Tuple[float, float, float, float, float, float],
78         identifier: Optional[str] = None,
79         **kwargs: Any,
80     ) -> None:
81         """
82         Add a sub glyph.
83         """
84         self.components.append((baseGlyphName, transformation))
85 
86     def _buildComponents(self, componentFlags):
87         if self.handleOverflowingTransforms:
88             # we can't encode transform values > 2 or < -2 in F2Dot14,
89             # so we must decompose the glyph if any transform exceeds these
90             overflowing = any(
91                 s > 2 or s < -2
92                 for (glyphName, transformation) in self.components
93                 for s in transformation[:4]
94             )
95         components = []
96         for glyphName, transformation in self.components:
97             if glyphName not in self.glyphSet:
98                 self.log.warning(f"skipped non-existing component '{glyphName}'")
99                 continue
100             if self.points or (self.handleOverflowingTransforms and overflowing):
101                 # can't have both coordinates and components, so decompose
102                 self._decompose(glyphName, transformation)
103                 continue
104 
105             component = GlyphComponent()
106             component.glyphName = glyphName
107             component.x, component.y = (otRound(v) for v in transformation[4:])
108             # quantize floats to F2Dot14 so we get same values as when decompiled
109             # from a binary glyf table
110             transformation = tuple(
111                 floatToFixedToFloat(v, 14) for v in transformation[:4]
112             )
113             if transformation != (1, 0, 0, 1):
114                 if self.handleOverflowingTransforms and any(
115                     MAX_F2DOT14 < s <= 2 for s in transformation
116                 ):
117                     # clamp values ~= +2.0 so we can keep the component
118                     transformation = tuple(
119                         MAX_F2DOT14 if MAX_F2DOT14 < s <= 2 else s
120                         for s in transformation
121                     )
122                 component.transform = (transformation[:2], transformation[2:])
123             component.flags = componentFlags
124             components.append(component)
125         return components
126 
127     def glyph(self, componentFlags: int = 0x4) -> Glyph:
128         """
129         Returns a :py:class:`~._g_l_y_f.Glyph` object representing the glyph.
130         """
131         if not self._isClosed():
132             raise PenError("Didn't close last contour.")
133         components = self._buildComponents(componentFlags)
134 
135         glyph = Glyph()
136         glyph.coordinates = GlyphCoordinates(self.points)
137         glyph.coordinates.toInt()
138         glyph.endPtsOfContours = self.endPts
139         glyph.flags = array("B", self.types)
140         self.init()
141 
142         if components:
143             # If both components and contours were present, they have by now
144             # been decomposed by _buildComponents.
145             glyph.components = components
146             glyph.numberOfContours = -1
147         else:
148             glyph.numberOfContours = len(glyph.endPtsOfContours)
149             glyph.program = ttProgram.Program()
150             glyph.program.fromBytecode(b"")
151 
152         return glyph
153 
154 
155 class TTGlyphPen(_TTGlyphBasePen, LoggingPen):
156     """
157     Pen used for drawing to a TrueType glyph.
158 
159     This pen can be used to construct or modify glyphs in a TrueType format
160     font. After using the pen to draw, use the ``.glyph()`` method to retrieve
161     a :py:class:`~._g_l_y_f.Glyph` object representing the glyph.
162     """
163 
164     drawMethod = "draw"
165     transformPen = TransformPen
166 
167     def _addPoint(self, pt: Tuple[float, float], onCurve: int) -> None:
168         self.points.append(pt)
169         self.types.append(onCurve)
170 
171     def _popPoint(self) -> None:
172         self.points.pop()
173         self.types.pop()
174 
175     def _isClosed(self) -> bool:
176         return (not self.points) or (
177             self.endPts and self.endPts[-1] == len(self.points) - 1
178         )
179 
180     def lineTo(self, pt: Tuple[float, float]) -> None:
181         self._addPoint(pt, 1)
182 
183     def moveTo(self, pt: Tuple[float, float]) -> None:
184         if not self._isClosed():
185             raise PenError('"move"-type point must begin a new contour.')
186         self._addPoint(pt, 1)
187 
188     def curveTo(self, *points) -> None:
189         raise NotImplementedError
190 
191     def qCurveTo(self, *points) -> None:
192         assert len(points) >= 1
193         for pt in points[:-1]:
194             self._addPoint(pt, 0)
195 
196         # last point is None if there are no on-curve points
197         if points[-1] is not None:
198             self._addPoint(points[-1], 1)
199 
200     def closePath(self) -> None:
201         endPt = len(self.points) - 1
202 
203         # ignore anchors (one-point paths)
204         if endPt == 0 or (self.endPts and endPt == self.endPts[-1] + 1):
205             self._popPoint()
206             return
207 
208         # if first and last point on this path are the same, remove last
209         startPt = 0
210         if self.endPts:
211             startPt = self.endPts[-1] + 1
212         if self.points[startPt] == self.points[endPt]:
213             self._popPoint()
214             endPt -= 1
215 
216         self.endPts.append(endPt)
217 
218     def endPath(self) -> None:
219         # TrueType contours are always "closed"
220         self.closePath()
221 
222 
223 class TTGlyphPointPen(_TTGlyphBasePen, LogMixin, AbstractPointPen):
224     """
225     Point pen used for drawing to a TrueType glyph.
226 
227     This pen can be used to construct or modify glyphs in a TrueType format
228     font. After using the pen to draw, use the ``.glyph()`` method to retrieve
229     a :py:class:`~._g_l_y_f.Glyph` object representing the glyph.
230     """
231 
232     drawMethod = "drawPoints"
233     transformPen = TransformPointPen
234 
235     def init(self) -> None:
236         super().init()
237         self._currentContourStartIndex = None
238 
239     def _isClosed(self) -> bool:
240         return self._currentContourStartIndex is None
241 
242     def beginPath(self, identifier: Optional[str] = None, **kwargs: Any) -> None:
243         """
244         Start a new sub path.
245         """
246         if not self._isClosed():
247             raise PenError("Didn't close previous contour.")
248         self._currentContourStartIndex = len(self.points)
249 
250     def endPath(self) -> None:
251         """
252         End the current sub path.
253         """
254         # TrueType contours are always "closed"
255         if self._isClosed():
256             raise PenError("Contour is already closed.")
257         if self._currentContourStartIndex == len(self.points):
258             raise PenError("Tried to end an empty contour.")
259         self.endPts.append(len(self.points) - 1)
260         self._currentContourStartIndex = None
261 
262     def addPoint(
263         self,
264         pt: Tuple[float, float],
265         segmentType: Optional[str] = None,
266         smooth: bool = False,
267         name: Optional[str] = None,
268         identifier: Optional[str] = None,
269         **kwargs: Any,
270     ) -> None:
271         """
272         Add a point to the current sub path.
273         """
274         if self._isClosed():
275             raise PenError("Can't add a point to a closed contour.")
276         if segmentType is None:
277             self.types.append(0)  # offcurve
278         elif segmentType in ("qcurve", "line"):
279             self.types.append(1)  # oncurve
280         else:
281             # cubic curves are not supported
282             raise NotImplementedError
283 
284         self.points.append(pt)
285
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/Lib/fontTools/pens/ttGlyphPen.py b/Lib/fontTools/pens/ttGlyphPen.py
--- a/Lib/fontTools/pens/ttGlyphPen.py
+++ b/Lib/fontTools/pens/ttGlyphPen.py
@@ -275,10 +275,11 @@
             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"):
+        elif segmentType in ("qcurve", "line", "move"):
             self.types.append(1)  # oncurve
+        elif segmentType == "curve":
+            raise NotImplementedError("cubic curves are not supported")
         else:
-            # cubic curves are not supported
-            raise NotImplementedError
+            raise AssertionError(segmentType)
 
         self.points.append(pt)

{"golden_diff": "diff --git a/Lib/fontTools/pens/ttGlyphPen.py b/Lib/fontTools/pens/ttGlyphPen.py\n--- a/Lib/fontTools/pens/ttGlyphPen.py\n+++ b/Lib/fontTools/pens/ttGlyphPen.py\n@@ -275,10 +275,11 @@\n             raise PenError(\"Can't add a point to a closed contour.\")\n         if segmentType is None:\n             self.types.append(0)  # offcurve\n-        elif segmentType in (\"qcurve\", \"line\"):\n+        elif segmentType in (\"qcurve\", \"line\", \"move\"):\n             self.types.append(1)  # oncurve\n+        elif segmentType == \"curve\":\n+            raise NotImplementedError(\"cubic curves are not supported\")\n         else:\n-            # cubic curves are not supported\n-            raise NotImplementedError\n+            raise AssertionError(segmentType)\n \n         self.points.append(pt)\n", "issue": "TTGlyphPointPen doesn't handle open contours (starting with 'move')\nin the PointPen protocol used by UFO glyphs, open contours start with a point that has segmentType=\"move\", see https://unifiedfontobject.org/versions/ufo3/glyphs/glif/#point-types\r\n\r\nOpen contours are valid in UFO, and the original TTGlyphPen treats them as closed for the sake of building TrueType glyphs (where all paths are implicilty closed and filled with black).\r\n\r\nBut if one tries to construct an open contour with the TTGlyphPointPen, one gets a NotImplementedError:\r\n\r\n```python\r\n>>> from fontTools.pens.ttGlyphPen import TTGlyphPointPen, TTGlyphPen\r\n>>> pen = TTGlyphPointPen(None)\r\n>>> pen.beginPath()\r\n>>> pen.addPoint((0, 0), \"move\")\r\nTraceback (most recent call last):\r\n  File \"<stdin>\", line 1, in <module>\r\n  File \"/Users/clupo/Github/fonttools/Lib/fontTools/pens/ttGlyphPen.py\", line 282, in addPoint\r\n    raise NotImplementedError\r\nNotImplementedError\r\n```\r\n\r\nWhereas this works fine using `TTGlyphPen`, where `endPath` (the method that in segment pens signals an open contour) is aliased to `closePath`, e.g.:\r\n\r\n```python\r\npen = TTGlyphPen(None)\r\npen.moveTo((0, 0))\r\npen.lineTo((10, 10))\r\npen.lineTo((20, 0))\r\npen.endPath()  # or pen.closePath()\r\n```\r\n\r\nThe current code in TTGlyphPointPen is incorrectly assuming that the only segment types for point pens are: None (for an off-curve point), \"line\", \"qcurve\" and \"curve\"; it's completely ignoring the explicit \"move\" case. Actually, an inline comment above the NotImplementedError signals that the author incorrectly assumed that when the segment type is neither None, \"line\" nor \"qcurve\" then it must be a (cubic) \"curve\" -- which is in fact not supported and should error with the NotImplementedError --, but \"move\" is a valid segment type and should not fall through the `else:` block.\r\n\r\nhttps://github.com/fonttools/fonttools/blob/aae11e45c29f4d07770574ce8577e6e61bc2b15c/Lib/fontTools/pens/ttGlyphPen.py#L276-L282\r\n\r\n\"move\" should be treated like \"line\" or \"qcurve\", i.e. like a on-curve point.\r\nThis way the TTGlyphPointPen will treat incoming open contours the same way as the TTGlyphPen, that is, as implicitly closed.\r\n\r\n\r\n\r\n\r\n\n", "before_files": [{"content": "from array import array\nfrom typing import Any, Dict, Optional, Tuple\nfrom fontTools.misc.fixedTools import MAX_F2DOT14, floatToFixedToFloat\nfrom fontTools.misc.loggingTools import LogMixin\nfrom fontTools.pens.pointPen import AbstractPointPen\nfrom fontTools.misc.roundTools import otRound\nfrom fontTools.pens.basePen import LoggingPen, PenError\nfrom fontTools.pens.transformPen import TransformPen, TransformPointPen\nfrom fontTools.ttLib.tables import ttProgram\nfrom fontTools.ttLib.tables._g_l_y_f import Glyph\nfrom fontTools.ttLib.tables._g_l_y_f import GlyphComponent\nfrom fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates\n\n\n__all__ = [\"TTGlyphPen\", \"TTGlyphPointPen\"]\n\n\nclass _TTGlyphBasePen:\n    def __init__(\n        self,\n        glyphSet: Optional[Dict[str, Any]],\n        handleOverflowingTransforms: bool = True,\n    ) -> None:\n        \"\"\"\n        Construct a new pen.\n\n        Args:\n            glyphSet (Dict[str, Any]): A glyphset object, used to resolve components.\n            handleOverflowingTransforms (bool): See below.\n\n        If ``handleOverflowingTransforms`` is True, the components' transform values\n        are checked that they don't overflow the limits of a F2Dot14 number:\n        -2.0 <= v < +2.0. If any transform value exceeds these, the composite\n        glyph is decomposed.\n\n        An exception to this rule is done for values that are very close to +2.0\n        (both for consistency with the -2.0 case, and for the relative frequency\n        these occur in real fonts). When almost +2.0 values occur (and all other\n        values are within the range -2.0 <= x <= +2.0), they are clamped to the\n        maximum positive value that can still be encoded as an F2Dot14: i.e.\n        1.99993896484375.\n\n        If False, no check is done and all components are translated unmodified\n        into the glyf table, followed by an inevitable ``struct.error`` once an\n        attempt is made to compile them.\n\n        If both contours and components are present in a glyph, the components\n        are decomposed.\n        \"\"\"\n        self.glyphSet = glyphSet\n        self.handleOverflowingTransforms = handleOverflowingTransforms\n        self.init()\n\n    def _decompose(\n        self,\n        glyphName: str,\n        transformation: Tuple[float, float, float, float, float, float],\n    ):\n        tpen = self.transformPen(self, transformation)\n        getattr(self.glyphSet[glyphName], self.drawMethod)(tpen)\n\n    def _isClosed(self):\n        \"\"\"\n        Check if the current path is closed.\n        \"\"\"\n        raise NotImplementedError\n\n    def init(self) -> None:\n        self.points = []\n        self.endPts = []\n        self.types = []\n        self.components = []\n\n    def addComponent(\n        self,\n        baseGlyphName: str,\n        transformation: Tuple[float, float, float, float, float, float],\n        identifier: Optional[str] = None,\n        **kwargs: Any,\n    ) -> None:\n        \"\"\"\n        Add a sub glyph.\n        \"\"\"\n        self.components.append((baseGlyphName, transformation))\n\n    def _buildComponents(self, componentFlags):\n        if self.handleOverflowingTransforms:\n            # we can't encode transform values > 2 or < -2 in F2Dot14,\n            # so we must decompose the glyph if any transform exceeds these\n            overflowing = any(\n                s > 2 or s < -2\n                for (glyphName, transformation) in self.components\n                for s in transformation[:4]\n            )\n        components = []\n        for glyphName, transformation in self.components:\n            if glyphName not in self.glyphSet:\n                self.log.warning(f\"skipped non-existing component '{glyphName}'\")\n                continue\n            if self.points or (self.handleOverflowingTransforms and overflowing):\n                # can't have both coordinates and components, so decompose\n                self._decompose(glyphName, transformation)\n                continue\n\n            component = GlyphComponent()\n            component.glyphName = glyphName\n            component.x, component.y = (otRound(v) for v in transformation[4:])\n            # quantize floats to F2Dot14 so we get same values as when decompiled\n            # from a binary glyf table\n            transformation = tuple(\n                floatToFixedToFloat(v, 14) for v in transformation[:4]\n            )\n            if transformation != (1, 0, 0, 1):\n                if self.handleOverflowingTransforms and any(\n                    MAX_F2DOT14 < s <= 2 for s in transformation\n                ):\n                    # clamp values ~= +2.0 so we can keep the component\n                    transformation = tuple(\n                        MAX_F2DOT14 if MAX_F2DOT14 < s <= 2 else s\n                        for s in transformation\n                    )\n                component.transform = (transformation[:2], transformation[2:])\n            component.flags = componentFlags\n            components.append(component)\n        return components\n\n    def glyph(self, componentFlags: int = 0x4) -> Glyph:\n        \"\"\"\n        Returns a :py:class:`~._g_l_y_f.Glyph` object representing the glyph.\n        \"\"\"\n        if not self._isClosed():\n            raise PenError(\"Didn't close last contour.\")\n        components = self._buildComponents(componentFlags)\n\n        glyph = Glyph()\n        glyph.coordinates = GlyphCoordinates(self.points)\n        glyph.coordinates.toInt()\n        glyph.endPtsOfContours = self.endPts\n        glyph.flags = array(\"B\", self.types)\n        self.init()\n\n        if components:\n            # If both components and contours were present, they have by now\n            # been decomposed by _buildComponents.\n            glyph.components = components\n            glyph.numberOfContours = -1\n        else:\n            glyph.numberOfContours = len(glyph.endPtsOfContours)\n            glyph.program = ttProgram.Program()\n            glyph.program.fromBytecode(b\"\")\n\n        return glyph\n\n\nclass TTGlyphPen(_TTGlyphBasePen, LoggingPen):\n    \"\"\"\n    Pen used for drawing to a TrueType glyph.\n\n    This pen can be used to construct or modify glyphs in a TrueType format\n    font. After using the pen to draw, use the ``.glyph()`` method to retrieve\n    a :py:class:`~._g_l_y_f.Glyph` object representing the glyph.\n    \"\"\"\n\n    drawMethod = \"draw\"\n    transformPen = TransformPen\n\n    def _addPoint(self, pt: Tuple[float, float], onCurve: int) -> None:\n        self.points.append(pt)\n        self.types.append(onCurve)\n\n    def _popPoint(self) -> None:\n        self.points.pop()\n        self.types.pop()\n\n    def _isClosed(self) -> bool:\n        return (not self.points) or (\n            self.endPts and self.endPts[-1] == len(self.points) - 1\n        )\n\n    def lineTo(self, pt: Tuple[float, float]) -> None:\n        self._addPoint(pt, 1)\n\n    def moveTo(self, pt: Tuple[float, float]) -> None:\n        if not self._isClosed():\n            raise PenError('\"move\"-type point must begin a new contour.')\n        self._addPoint(pt, 1)\n\n    def curveTo(self, *points) -> None:\n        raise NotImplementedError\n\n    def qCurveTo(self, *points) -> None:\n        assert len(points) >= 1\n        for pt in points[:-1]:\n            self._addPoint(pt, 0)\n\n        # last point is None if there are no on-curve points\n        if points[-1] is not None:\n            self._addPoint(points[-1], 1)\n\n    def closePath(self) -> None:\n        endPt = len(self.points) - 1\n\n        # ignore anchors (one-point paths)\n        if endPt == 0 or (self.endPts and endPt == self.endPts[-1] + 1):\n            self._popPoint()\n            return\n\n        # if first and last point on this path are the same, remove last\n        startPt = 0\n        if self.endPts:\n            startPt = self.endPts[-1] + 1\n        if self.points[startPt] == self.points[endPt]:\n            self._popPoint()\n            endPt -= 1\n\n        self.endPts.append(endPt)\n\n    def endPath(self) -> None:\n        # TrueType contours are always \"closed\"\n        self.closePath()\n\n\nclass TTGlyphPointPen(_TTGlyphBasePen, LogMixin, AbstractPointPen):\n    \"\"\"\n    Point pen used for drawing to a TrueType glyph.\n\n    This pen can be used to construct or modify glyphs in a TrueType format\n    font. After using the pen to draw, use the ``.glyph()`` method to retrieve\n    a :py:class:`~._g_l_y_f.Glyph` object representing the glyph.\n    \"\"\"\n\n    drawMethod = \"drawPoints\"\n    transformPen = TransformPointPen\n\n    def init(self) -> None:\n        super().init()\n        self._currentContourStartIndex = None\n\n    def _isClosed(self) -> bool:\n        return self._currentContourStartIndex is None\n\n    def beginPath(self, identifier: Optional[str] = None, **kwargs: Any) -> None:\n        \"\"\"\n        Start a new sub path.\n        \"\"\"\n        if not self._isClosed():\n            raise PenError(\"Didn't close previous contour.\")\n        self._currentContourStartIndex = len(self.points)\n\n    def endPath(self) -> None:\n        \"\"\"\n        End the current sub path.\n        \"\"\"\n        # TrueType contours are always \"closed\"\n        if self._isClosed():\n            raise PenError(\"Contour is already closed.\")\n        if self._currentContourStartIndex == len(self.points):\n            raise PenError(\"Tried to end an empty contour.\")\n        self.endPts.append(len(self.points) - 1)\n        self._currentContourStartIndex = None\n\n    def addPoint(\n        self,\n        pt: Tuple[float, float],\n        segmentType: Optional[str] = None,\n        smooth: bool = False,\n        name: Optional[str] = None,\n        identifier: Optional[str] = None,\n        **kwargs: Any,\n    ) -> None:\n        \"\"\"\n        Add a point to the current sub path.\n        \"\"\"\n        if self._isClosed():\n            raise PenError(\"Can't add a point to a closed contour.\")\n        if segmentType is None:\n            self.types.append(0)  # offcurve\n        elif segmentType in (\"qcurve\", \"line\"):\n            self.types.append(1)  # oncurve\n        else:\n            # cubic curves are not supported\n            raise NotImplementedError\n\n        self.points.append(pt)\n", "path": "Lib/fontTools/pens/ttGlyphPen.py"}], "after_files": [{"content": "from array import array\nfrom typing import Any, Dict, Optional, Tuple\nfrom fontTools.misc.fixedTools import MAX_F2DOT14, floatToFixedToFloat\nfrom fontTools.misc.loggingTools import LogMixin\nfrom fontTools.pens.pointPen import AbstractPointPen\nfrom fontTools.misc.roundTools import otRound\nfrom fontTools.pens.basePen import LoggingPen, PenError\nfrom fontTools.pens.transformPen import TransformPen, TransformPointPen\nfrom fontTools.ttLib.tables import ttProgram\nfrom fontTools.ttLib.tables._g_l_y_f import Glyph\nfrom fontTools.ttLib.tables._g_l_y_f import GlyphComponent\nfrom fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates\n\n\n__all__ = [\"TTGlyphPen\", \"TTGlyphPointPen\"]\n\n\nclass _TTGlyphBasePen:\n    def __init__(\n        self,\n        glyphSet: Optional[Dict[str, Any]],\n        handleOverflowingTransforms: bool = True,\n    ) -> None:\n        \"\"\"\n        Construct a new pen.\n\n        Args:\n            glyphSet (Dict[str, Any]): A glyphset object, used to resolve components.\n            handleOverflowingTransforms (bool): See below.\n\n        If ``handleOverflowingTransforms`` is True, the components' transform values\n        are checked that they don't overflow the limits of a F2Dot14 number:\n        -2.0 <= v < +2.0. If any transform value exceeds these, the composite\n        glyph is decomposed.\n\n        An exception to this rule is done for values that are very close to +2.0\n        (both for consistency with the -2.0 case, and for the relative frequency\n        these occur in real fonts). When almost +2.0 values occur (and all other\n        values are within the range -2.0 <= x <= +2.0), they are clamped to the\n        maximum positive value that can still be encoded as an F2Dot14: i.e.\n        1.99993896484375.\n\n        If False, no check is done and all components are translated unmodified\n        into the glyf table, followed by an inevitable ``struct.error`` once an\n        attempt is made to compile them.\n\n        If both contours and components are present in a glyph, the components\n        are decomposed.\n        \"\"\"\n        self.glyphSet = glyphSet\n        self.handleOverflowingTransforms = handleOverflowingTransforms\n        self.init()\n\n    def _decompose(\n        self,\n        glyphName: str,\n        transformation: Tuple[float, float, float, float, float, float],\n    ):\n        tpen = self.transformPen(self, transformation)\n        getattr(self.glyphSet[glyphName], self.drawMethod)(tpen)\n\n    def _isClosed(self):\n        \"\"\"\n        Check if the current path is closed.\n        \"\"\"\n        raise NotImplementedError\n\n    def init(self) -> None:\n        self.points = []\n        self.endPts = []\n        self.types = []\n        self.components = []\n\n    def addComponent(\n        self,\n        baseGlyphName: str,\n        transformation: Tuple[float, float, float, float, float, float],\n        identifier: Optional[str] = None,\n        **kwargs: Any,\n    ) -> None:\n        \"\"\"\n        Add a sub glyph.\n        \"\"\"\n        self.components.append((baseGlyphName, transformation))\n\n    def _buildComponents(self, componentFlags):\n        if self.handleOverflowingTransforms:\n            # we can't encode transform values > 2 or < -2 in F2Dot14,\n            # so we must decompose the glyph if any transform exceeds these\n            overflowing = any(\n                s > 2 or s < -2\n                for (glyphName, transformation) in self.components\n                for s in transformation[:4]\n            )\n        components = []\n        for glyphName, transformation in self.components:\n            if glyphName not in self.glyphSet:\n                self.log.warning(f\"skipped non-existing component '{glyphName}'\")\n                continue\n            if self.points or (self.handleOverflowingTransforms and overflowing):\n                # can't have both coordinates and components, so decompose\n                self._decompose(glyphName, transformation)\n                continue\n\n            component = GlyphComponent()\n            component.glyphName = glyphName\n            component.x, component.y = (otRound(v) for v in transformation[4:])\n            # quantize floats to F2Dot14 so we get same values as when decompiled\n            # from a binary glyf table\n            transformation = tuple(\n                floatToFixedToFloat(v, 14) for v in transformation[:4]\n            )\n            if transformation != (1, 0, 0, 1):\n                if self.handleOverflowingTransforms and any(\n                    MAX_F2DOT14 < s <= 2 for s in transformation\n                ):\n                    # clamp values ~= +2.0 so we can keep the component\n                    transformation = tuple(\n                        MAX_F2DOT14 if MAX_F2DOT14 < s <= 2 else s\n                        for s in transformation\n                    )\n                component.transform = (transformation[:2], transformation[2:])\n            component.flags = componentFlags\n            components.append(component)\n        return components\n\n    def glyph(self, componentFlags: int = 0x4) -> Glyph:\n        \"\"\"\n        Returns a :py:class:`~._g_l_y_f.Glyph` object representing the glyph.\n        \"\"\"\n        if not self._isClosed():\n            raise PenError(\"Didn't close last contour.\")\n        components = self._buildComponents(componentFlags)\n\n        glyph = Glyph()\n        glyph.coordinates = GlyphCoordinates(self.points)\n        glyph.coordinates.toInt()\n        glyph.endPtsOfContours = self.endPts\n        glyph.flags = array(\"B\", self.types)\n        self.init()\n\n        if components:\n            # If both components and contours were present, they have by now\n            # been decomposed by _buildComponents.\n            glyph.components = components\n            glyph.numberOfContours = -1\n        else:\n            glyph.numberOfContours = len(glyph.endPtsOfContours)\n            glyph.program = ttProgram.Program()\n            glyph.program.fromBytecode(b\"\")\n\n        return glyph\n\n\nclass TTGlyphPen(_TTGlyphBasePen, LoggingPen):\n    \"\"\"\n    Pen used for drawing to a TrueType glyph.\n\n    This pen can be used to construct or modify glyphs in a TrueType format\n    font. After using the pen to draw, use the ``.glyph()`` method to retrieve\n    a :py:class:`~._g_l_y_f.Glyph` object representing the glyph.\n    \"\"\"\n\n    drawMethod = \"draw\"\n    transformPen = TransformPen\n\n    def _addPoint(self, pt: Tuple[float, float], onCurve: int) -> None:\n        self.points.append(pt)\n        self.types.append(onCurve)\n\n    def _popPoint(self) -> None:\n        self.points.pop()\n        self.types.pop()\n\n    def _isClosed(self) -> bool:\n        return (not self.points) or (\n            self.endPts and self.endPts[-1] == len(self.points) - 1\n        )\n\n    def lineTo(self, pt: Tuple[float, float]) -> None:\n        self._addPoint(pt, 1)\n\n    def moveTo(self, pt: Tuple[float, float]) -> None:\n        if not self._isClosed():\n            raise PenError('\"move\"-type point must begin a new contour.')\n        self._addPoint(pt, 1)\n\n    def curveTo(self, *points) -> None:\n        raise NotImplementedError\n\n    def qCurveTo(self, *points) -> None:\n        assert len(points) >= 1\n        for pt in points[:-1]:\n            self._addPoint(pt, 0)\n\n        # last point is None if there are no on-curve points\n        if points[-1] is not None:\n            self._addPoint(points[-1], 1)\n\n    def closePath(self) -> None:\n        endPt = len(self.points) - 1\n\n        # ignore anchors (one-point paths)\n        if endPt == 0 or (self.endPts and endPt == self.endPts[-1] + 1):\n            self._popPoint()\n            return\n\n        # if first and last point on this path are the same, remove last\n        startPt = 0\n        if self.endPts:\n            startPt = self.endPts[-1] + 1\n        if self.points[startPt] == self.points[endPt]:\n            self._popPoint()\n            endPt -= 1\n\n        self.endPts.append(endPt)\n\n    def endPath(self) -> None:\n        # TrueType contours are always \"closed\"\n        self.closePath()\n\n\nclass TTGlyphPointPen(_TTGlyphBasePen, LogMixin, AbstractPointPen):\n    \"\"\"\n    Point pen used for drawing to a TrueType glyph.\n\n    This pen can be used to construct or modify glyphs in a TrueType format\n    font. After using the pen to draw, use the ``.glyph()`` method to retrieve\n    a :py:class:`~._g_l_y_f.Glyph` object representing the glyph.\n    \"\"\"\n\n    drawMethod = \"drawPoints\"\n    transformPen = TransformPointPen\n\n    def init(self) -> None:\n        super().init()\n        self._currentContourStartIndex = None\n\n    def _isClosed(self) -> bool:\n        return self._currentContourStartIndex is None\n\n    def beginPath(self, identifier: Optional[str] = None, **kwargs: Any) -> None:\n        \"\"\"\n        Start a new sub path.\n        \"\"\"\n        if not self._isClosed():\n            raise PenError(\"Didn't close previous contour.\")\n        self._currentContourStartIndex = len(self.points)\n\n    def endPath(self) -> None:\n        \"\"\"\n        End the current sub path.\n        \"\"\"\n        # TrueType contours are always \"closed\"\n        if self._isClosed():\n            raise PenError(\"Contour is already closed.\")\n        if self._currentContourStartIndex == len(self.points):\n            raise PenError(\"Tried to end an empty contour.\")\n        self.endPts.append(len(self.points) - 1)\n        self._currentContourStartIndex = None\n\n    def addPoint(\n        self,\n        pt: Tuple[float, float],\n        segmentType: Optional[str] = None,\n        smooth: bool = False,\n        name: Optional[str] = None,\n        identifier: Optional[str] = None,\n        **kwargs: Any,\n    ) -> None:\n        \"\"\"\n        Add a point to the current sub path.\n        \"\"\"\n        if self._isClosed():\n            raise PenError(\"Can't add a point to a closed contour.\")\n        if segmentType is None:\n            self.types.append(0)  # offcurve\n        elif segmentType in (\"qcurve\", \"line\", \"move\"):\n            self.types.append(1)  # oncurve\n        elif segmentType == \"curve\":\n            raise NotImplementedError(\"cubic curves are not supported\")\n        else:\n            raise AssertionError(segmentType)\n\n        self.points.append(pt)\n", "path": "Lib/fontTools/pens/ttGlyphPen.py"}]}