prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  [ttGlyphPen] decompose components if transform overflows F2Dot14
  https://github.com/googlei18n/ufo2ft/issues/217
  
  The UFO GLIF spec allows any numbers for xScale, xyScale, yxScale, yScale, xOffset, yOffset, however the OpenType glyf spec uses F2Dot14 numbers, which are encoded as a signed 16-bit integer and therefore can only contain values from -32768 (-0x8000, or -2.0) to +32767 included (0x7FFF, or +1.99993896484375...).
  
  We can't let the `struct.error` propagate.
  
  I think we have to handle the case of +2.0 specially, and treat it as if it were 1.99993896484375. By doing that we can support truetype component transforms in the range -2.0 to +2.0 (inclusive), for the sake of simplicity.
  
  Then, we also need to have the ttGlyphPen decompose the components if their transform values are less than -2.0 or they are greater than +2.0 (not greater and equal), as these can't fit in the TrueType glyf table.
  
  
  
  --- 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 __future__ import print_function, division, absolute_import
  2 from fontTools.misc.py23 import *
  3 from array import array
  4 from fontTools.pens.basePen import AbstractPen
  5 from fontTools.pens.transformPen import TransformPen
  6 from fontTools.ttLib.tables import ttProgram
  7 from fontTools.ttLib.tables._g_l_y_f import Glyph
  8 from fontTools.ttLib.tables._g_l_y_f import GlyphComponent
  9 from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
  10 
  11 
  12 __all__ = ["TTGlyphPen"]
  13 
  14 
  15 class TTGlyphPen(AbstractPen):
  16     """Pen used for drawing to a TrueType glyph."""
  17 
  18     def __init__(self, glyphSet):
  19         self.glyphSet = glyphSet
  20         self.init()
  21 
  22     def init(self):
  23         self.points = []
  24         self.endPts = []
  25         self.types = []
  26         self.components = []
  27 
  28     def _addPoint(self, pt, onCurve):
  29         self.points.append(pt)
  30         self.types.append(onCurve)
  31 
  32     def _popPoint(self):
  33         self.points.pop()
  34         self.types.pop()
  35 
  36     def _isClosed(self):
  37         return (
  38             (not self.points) or
  39             (self.endPts and self.endPts[-1] == len(self.points) - 1))
  40 
  41     def lineTo(self, pt):
  42         self._addPoint(pt, 1)
  43 
  44     def moveTo(self, pt):
  45         assert self._isClosed(), '"move"-type point must begin a new contour.'
  46         self._addPoint(pt, 1)
  47 
  48     def qCurveTo(self, *points):
  49         assert len(points) >= 1
  50         for pt in points[:-1]:
  51             self._addPoint(pt, 0)
  52 
  53         # last point is None if there are no on-curve points
  54         if points[-1] is not None:
  55             self._addPoint(points[-1], 1)
  56 
  57     def closePath(self):
  58         endPt = len(self.points) - 1
  59 
  60         # ignore anchors (one-point paths)
  61         if endPt == 0 or (self.endPts and endPt == self.endPts[-1] + 1):
  62             self._popPoint()
  63             return
  64 
  65         # if first and last point on this path are the same, remove last
  66         startPt = 0
  67         if self.endPts:
  68             startPt = self.endPts[-1] + 1
  69         if self.points[startPt] == self.points[endPt]:
  70             self._popPoint()
  71             endPt -= 1
  72 
  73         self.endPts.append(endPt)
  74 
  75     def endPath(self):
  76         # TrueType contours are always "closed"
  77         self.closePath()
  78 
  79     def addComponent(self, glyphName, transformation):
  80         self.components.append((glyphName, transformation))
  81 
  82     def glyph(self, componentFlags=0x4):
  83         assert self._isClosed(), "Didn't close last contour."
  84 
  85         components = []
  86         for glyphName, transformation in self.components:
  87             if self.points:
  88                 # can't have both, so decompose the glyph
  89                 tpen = TransformPen(self, transformation)
  90                 self.glyphSet[glyphName].draw(tpen)
  91                 continue
  92 
  93             component = GlyphComponent()
  94             component.glyphName = glyphName
  95             if transformation[:4] != (1, 0, 0, 1):
  96                 component.transform = (transformation[:2], transformation[2:4])
  97             component.x, component.y = transformation[4:]
  98             component.flags = componentFlags
  99             components.append(component)
  100 
  101         glyph = Glyph()
  102         glyph.coordinates = GlyphCoordinates(self.points)
  103         glyph.endPtsOfContours = self.endPts
  104         glyph.flags = array("B", self.types)
  105         self.init()
  106 
  107         if components:
  108             glyph.components = components
  109             glyph.numberOfContours = -1
  110         else:
  111             glyph.numberOfContours = len(glyph.endPtsOfContours)
  112             glyph.program = ttProgram.Program()
  113             glyph.program.fromBytecode(b"")
  114 
  115         return glyph
  116
  ```
  --- 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()
  
  ```
  
golden_diff: 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
  @@ -12,11 +12,32 @@
   __all__ = ["TTGlyphPen"]
   
   
  -class TTGlyphPen(AbstractPen):
  -    """Pen used for drawing to a TrueType glyph."""
  +# the max value that can still fit in an F2Dot14:
  +# 1.99993896484375
  +MAX_F2DOT14 = 0x7FFF / (1 << 14)
  +
   
  -    def __init__(self, glyphSet):
  +class TTGlyphPen(AbstractPen):
  +    """Pen used for drawing to a TrueType glyph.
  +
  +    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.
  +    """
  +
  +    def __init__(self, glyphSet, handleOverflowingTransforms=True):
           self.glyphSet = glyphSet
  +        self.handleOverflowingTransforms = handleOverflowingTransforms
           self.init()
   
       def init(self):
  @@ -82,19 +103,33 @@
       def glyph(self, componentFlags=0x4):
           assert self._isClosed(), "Didn't close last contour."
   
  +        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 self.points:
  -                # can't have both, so decompose the glyph
  +            if (self.points or
  +                    (self.handleOverflowingTransforms and overflowing)):
  +                # can't have both coordinates and components, so decompose
                   tpen = TransformPen(self, transformation)
                   self.glyphSet[glyphName].draw(tpen)
                   continue
   
               component = GlyphComponent()
               component.glyphName = glyphName
  -            if transformation[:4] != (1, 0, 0, 1):
  -                component.transform = (transformation[:2], transformation[2:4])
               component.x, component.y = transformation[4:]
  +            transformation = 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)
  
verification_info: {"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@@ -12,11 +12,32 @@\n __all__ = [\"TTGlyphPen\"]\n \n \n-class TTGlyphPen(AbstractPen):\n-    \"\"\"Pen used for drawing to a TrueType glyph.\"\"\"\n+# the max value that can still fit in an F2Dot14:\n+# 1.99993896484375\n+MAX_F2DOT14 = 0x7FFF / (1 << 14)\n+\n \n-    def __init__(self, glyphSet):\n+class TTGlyphPen(AbstractPen):\n+    \"\"\"Pen used for drawing to a TrueType glyph.\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+    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+    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+\n+    def __init__(self, glyphSet, handleOverflowingTransforms=True):\n         self.glyphSet = glyphSet\n+        self.handleOverflowingTransforms = handleOverflowingTransforms\n         self.init()\n \n     def init(self):\n@@ -82,19 +103,33 @@\n     def glyph(self, componentFlags=0x4):\n         assert self._isClosed(), \"Didn't close last contour.\"\n \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(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 self.points:\n-                # can't have both, so decompose the glyph\n+            if (self.points or\n+                    (self.handleOverflowingTransforms and overflowing)):\n+                # can't have both coordinates and components, so decompose\n                 tpen = TransformPen(self, transformation)\n                 self.glyphSet[glyphName].draw(tpen)\n                 continue\n \n             component = GlyphComponent()\n             component.glyphName = glyphName\n-            if transformation[:4] != (1, 0, 0, 1):\n-                component.transform = (transformation[:2], transformation[2:4])\n             component.x, component.y = transformation[4:]\n+            transformation = transformation[:4]\n+            if transformation != (1, 0, 0, 1):\n+                if (self.handleOverflowingTransforms and\n+                        any(MAX_F2DOT14 < s <= 2 for s in transformation)):\n+                    # clamp values ~= +2.0 so we can keep the component\n+                    transformation = tuple(MAX_F2DOT14 if MAX_F2DOT14 < s <= 2\n+                                           else s for s in transformation)\n+                component.transform = (transformation[:2], transformation[2:])\n             component.flags = componentFlags\n             components.append(component)\n", "issue": "[ttGlyphPen] decompose components if transform overflows F2Dot14\nhttps://github.com/googlei18n/ufo2ft/issues/217\r\n\r\nThe UFO GLIF spec allows any numbers for xScale, xyScale, yxScale, yScale, xOffset, yOffset, however the OpenType glyf spec uses F2Dot14 numbers, which are encoded as a signed 16-bit integer and therefore can only contain values from -32768 (-0x8000, or -2.0) to +32767 included (0x7FFF, or +1.99993896484375...).\r\n\r\nWe can't let the `struct.error` propagate.\r\n\r\nI think we have to handle the case of +2.0 specially, and treat it as if it were 1.99993896484375. By doing that we can support truetype component transforms in the range -2.0 to +2.0 (inclusive), for the sake of simplicity.\r\n\r\nThen, we also need to have the ttGlyphPen decompose the components if their transform values are less than -2.0 or they are greater than +2.0 (not greater and equal), as these can't fit in the TrueType glyf table.\r\n\r\n\n", "before_files": [{"content": "from __future__ import print_function, division, absolute_import\nfrom fontTools.misc.py23 import *\nfrom array import array\nfrom fontTools.pens.basePen import AbstractPen\nfrom fontTools.pens.transformPen import TransformPen\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\"]\n\n\nclass TTGlyphPen(AbstractPen):\n    \"\"\"Pen used for drawing to a TrueType glyph.\"\"\"\n\n    def __init__(self, glyphSet):\n        self.glyphSet = glyphSet\n        self.init()\n\n    def init(self):\n        self.points = []\n        self.endPts = []\n        self.types = []\n        self.components = []\n\n    def _addPoint(self, pt, onCurve):\n        self.points.append(pt)\n        self.types.append(onCurve)\n\n    def _popPoint(self):\n        self.points.pop()\n        self.types.pop()\n\n    def _isClosed(self):\n        return (\n            (not self.points) or\n            (self.endPts and self.endPts[-1] == len(self.points) - 1))\n\n    def lineTo(self, pt):\n        self._addPoint(pt, 1)\n\n    def moveTo(self, pt):\n        assert self._isClosed(), '\"move\"-type point must begin a new contour.'\n        self._addPoint(pt, 1)\n\n    def qCurveTo(self, *points):\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):\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):\n        # TrueType contours are always \"closed\"\n        self.closePath()\n\n    def addComponent(self, glyphName, transformation):\n        self.components.append((glyphName, transformation))\n\n    def glyph(self, componentFlags=0x4):\n        assert self._isClosed(), \"Didn't close last contour.\"\n\n        components = []\n        for glyphName, transformation in self.components:\n            if self.points:\n                # can't have both, so decompose the glyph\n                tpen = TransformPen(self, transformation)\n                self.glyphSet[glyphName].draw(tpen)\n                continue\n\n            component = GlyphComponent()\n            component.glyphName = glyphName\n            if transformation[:4] != (1, 0, 0, 1):\n                component.transform = (transformation[:2], transformation[2:4])\n            component.x, component.y = transformation[4:]\n            component.flags = componentFlags\n            components.append(component)\n\n        glyph = Glyph()\n        glyph.coordinates = GlyphCoordinates(self.points)\n        glyph.endPtsOfContours = self.endPts\n        glyph.flags = array(\"B\", self.types)\n        self.init()\n\n        if components:\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", "path": "Lib/fontTools/pens/ttGlyphPen.py"}], "after_files": [{"content": "from __future__ import print_function, division, absolute_import\nfrom fontTools.misc.py23 import *\nfrom array import array\nfrom fontTools.pens.basePen import AbstractPen\nfrom fontTools.pens.transformPen import TransformPen\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\"]\n\n\n# the max value that can still fit in an F2Dot14:\n# 1.99993896484375\nMAX_F2DOT14 = 0x7FFF / (1 << 14)\n\n\nclass TTGlyphPen(AbstractPen):\n    \"\"\"Pen used for drawing to a TrueType glyph.\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    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    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\n    def __init__(self, glyphSet, handleOverflowingTransforms=True):\n        self.glyphSet = glyphSet\n        self.handleOverflowingTransforms = handleOverflowingTransforms\n        self.init()\n\n    def init(self):\n        self.points = []\n        self.endPts = []\n        self.types = []\n        self.components = []\n\n    def _addPoint(self, pt, onCurve):\n        self.points.append(pt)\n        self.types.append(onCurve)\n\n    def _popPoint(self):\n        self.points.pop()\n        self.types.pop()\n\n    def _isClosed(self):\n        return (\n            (not self.points) or\n            (self.endPts and self.endPts[-1] == len(self.points) - 1))\n\n    def lineTo(self, pt):\n        self._addPoint(pt, 1)\n\n    def moveTo(self, pt):\n        assert self._isClosed(), '\"move\"-type point must begin a new contour.'\n        self._addPoint(pt, 1)\n\n    def qCurveTo(self, *points):\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):\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):\n        # TrueType contours are always \"closed\"\n        self.closePath()\n\n    def addComponent(self, glyphName, transformation):\n        self.components.append((glyphName, transformation))\n\n    def glyph(self, componentFlags=0x4):\n        assert self._isClosed(), \"Didn't close last contour.\"\n\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(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 (self.points or\n                    (self.handleOverflowingTransforms and overflowing)):\n                # can't have both coordinates and components, so decompose\n                tpen = TransformPen(self, transformation)\n                self.glyphSet[glyphName].draw(tpen)\n                continue\n\n            component = GlyphComponent()\n            component.glyphName = glyphName\n            component.x, component.y = transformation[4:]\n            transformation = transformation[:4]\n            if transformation != (1, 0, 0, 1):\n                if (self.handleOverflowingTransforms and\n                        any(MAX_F2DOT14 < s <= 2 for s in transformation)):\n                    # clamp values ~= +2.0 so we can keep the component\n                    transformation = tuple(MAX_F2DOT14 if MAX_F2DOT14 < s <= 2\n                                           else s for s in transformation)\n                component.transform = (transformation[:2], transformation[2:])\n            component.flags = componentFlags\n            components.append(component)\n\n        glyph = Glyph()\n        glyph.coordinates = GlyphCoordinates(self.points)\n        glyph.endPtsOfContours = self.endPts\n        glyph.flags = array(\"B\", self.types)\n        self.init()\n\n        if components:\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", "path": "Lib/fontTools/pens/ttGlyphPen.py"}]}