# Source code for fontTools.pens.ttGlyphPen

    from array import array
    from typing import Any, Dict, Optional, Tuple
    from fontTools.misc.fixedTools import MAX_F2DOT14, floatToFixedToFloat
    from fontTools.misc.loggingTools import LogMixin
    from fontTools.pens.pointPen import AbstractPointPen
    from fontTools.misc.roundTools import otRound
    from fontTools.pens.basePen import LoggingPen, PenError
    from fontTools.pens.transformPen import TransformPen, TransformPointPen
    from fontTools.ttLib.tables import ttProgram
    from fontTools.ttLib.tables._g_l_y_f import Glyph
    from fontTools.ttLib.tables._g_l_y_f import GlyphComponent
    from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates

    __all__ = ["TTGlyphPen", "TTGlyphPointPen"]

    class _TTGlyphBasePen:
        def __init__(
            self,
            glyphSet: Optional[Dict[str, Any]],
            handleOverflowingTransforms: bool = True,
        ) -> None:
            """
            Construct a new pen.

            Args:
                glyphSet (Dict[str, Any]): A glyphset object, used to resolve components.
                handleOverflowingTransforms (bool): See below.

            If ``handleOverflowingTransforms`` is True, the components' transform values
            are checked that they don't overflow the limits of a F2Dot14 number:
            -2.0 <= v < +2.0. If any transform value exceeds these, the composite
            glyph is decomposed.

            An exception to this rule is done for values that are very close to +2.0
            (both for consistency with the -2.0 case, and for the relative frequency
            these occur in real fonts). When almost +2.0 values occur (and all other
            values are within the range -2.0 <= x <= +2.0), they are clamped to the
            maximum positive value that can still be encoded as an F2Dot14: i.e.
            1.99993896484375.

            If False, no check is done and all components are translated unmodified
            into the glyf table, followed by an inevitable ``struct.error`` once an
            attempt is made to compile them.

            If both contours and components are present in a glyph, the components
            are decomposed.
            """
            self.glyphSet = glyphSet
            self.handleOverflowingTransforms = handleOverflowingTransforms
            self.init()

        def _decompose(
            self,
            glyphName: str,
            transformation: Tuple[float, float, float, float, float, float],
        ):
            tpen = self.transformPen(self, transformation)
            getattr(self.glyphSet[glyphName], self.drawMethod)(tpen)

        def _isClosed(self):
            """
            Check if the current path is closed.
            """
            raise NotImplementedError

        def init(self) -> None:
            self.points = []
            self.endPts = []
            self.types = []
            self.components = []

        def addComponent(
            self,
            baseGlyphName: str,
            transformation: Tuple[float, float, float, float, float, float],
            identifier: Optional[str] = None,
            **kwargs: Any,
        ) -> None:
            """
            Add a sub glyph.
            """
            self.components.append((baseGlyphName, transformation))

        def _buildComponents(self, componentFlags):
            if self.handleOverflowingTransforms:
                # we can't encode transform values > 2 or < -2 in F2Dot14,
                # so we must decompose the glyph if any transform exceeds these
                overflowing = any(
                    s > 2 or s < -2
                    for (glyphName, transformation) in self.components
                    for s in transformation[:4]
                )
            components = []
            for glyphName, transformation in self.components:
                if glyphName not in self.glyphSet:
                    self.log.warning(f"skipped non-existing component '{glyphName}'")
                    continue
                if self.points or (self.handleOverflowingTransforms and overflowing):
                    # can't have both coordinates and components, so decompose
                    self._decompose(glyphName, transformation)
                    continue

                component = GlyphComponent()
                component.glyphName = glyphName
                component.x, component.y = (otRound(v) for v in transformation[4:])
                # quantize floats to F2Dot14 so we get same values as when decompiled
                # from a binary glyf table
                transformation = tuple(
                    floatToFixedToFloat(v, 14) for v in transformation[:4]
                )
                if transformation != (1, 0, 0, 1):
                    if self.handleOverflowingTransforms and any(
                        MAX_F2DOT14 < s <= 2 for s in transformation
                    ):
                        # clamp values ~= +2.0 so we can keep the component
                        transformation = tuple(
                            MAX_F2DOT14 if MAX_F2DOT14 < s <= 2 else s
                            for s in transformation
                        )
                    component.transform = (transformation[:2], transformation[2:])
                component.flags = componentFlags
                components.append(component)
            return components

        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

    class TTGlyphPen(_TTGlyphBasePen, LoggingPen):
        """
        Pen used for drawing to a TrueType glyph.

        This pen can be used to construct or modify glyphs in a TrueType format
        font. After using the pen to draw, use the ``.glyph()`` method to retrieve
        a :py:class:`~._g_l_y_f.Glyph` object representing the glyph.
        """

        drawMethod = "draw"
        transformPen = TransformPen

        def _addPoint(self, pt: Tuple[float, float], onCurve: int) -> None:
            self.points.append(pt)
            self.types.append(onCurve)

        def _popPoint(self) -> None:
            self.points.pop()
            self.types.pop()

        def _isClosed(self) -> bool:
            return (not self.points) or (
                self.endPts and self.endPts[-1] == len(self.points) - 1
            )

        def lineTo(self, pt: Tuple[float, float]) -> None:
            self._addPoint(pt, 1)

        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 curveTo(self, *points) -> None:
            raise NotImplementedError

        def qCurveTo(self, *points) -> None:
            assert len(points) >= 1
            for pt in points[:-1]:
                self._addPoint(pt, 0)

            # last point is None if there are no on-curve points
            if points[-1] is not None:
                self._addPoint(points[-1], 1)

        def closePath(self) -> None:
            endPt = len(self.points) - 1

            # ignore anchors (one-point paths)
            if endPt == 0 or (self.endPts and endPt == self.endPts[-1] + 1):
                self._popPoint()
                return

            # if first and last point on this path are the same, remove last
            startPt = 0
            if self.endPts:
                startPt = self.endPts[-1] + 1
            if self.points[startPt] == self.points[endPt]:
                self._popPoint()
                endPt -= 1

            self.endPts.append(endPt)

        def endPath(self) -> None:
            # TrueType contours are always "closed"
            self.closePath()

    class TTGlyphPointPen(_TTGlyphBasePen, LogMixin, AbstractPointPen):
        """
        Point pen used for drawing to a TrueType glyph.

        This pen can be used to construct or modify glyphs in a TrueType format
        font. After using the pen to draw, use the ``.glyph()`` method to retrieve
        a :py:class:`~._g_l_y_f.Glyph` object representing the glyph.
        """

        drawMethod = "drawPoints"
        transformPen = TransformPointPen

        def init(self) -> None:
            super().init()
            self._currentContourStartIndex = None

        def _isClosed(self) -> bool:
            return self._currentContourStartIndex is None

        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 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 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", "move"):
                self.types.append(1)  # oncurve
            elif segmentType == "curve":
                raise NotImplementedError("cubic curves are not supported")
            else:
                raise AssertionError(segmentType)

            self.points.append(pt)