Upgrade fonttools to 4.18.1 am: 13f9df0298

Original change: https://android-review.googlesource.com/c/platform/external/fonttools/+/1522558

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I17f93b1ae92a833f6ee31ffedc1e5f204bb3f7e6
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index d43c840..ec6abe2 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -7,7 +7,7 @@
   push:
     # Sequence of patterns matched against refs/tags
     tags:
-      - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
+      - '*.*.*' # e.g. 1.0.0 or 20.15.10
 
 jobs:
   deploy:
diff --git a/Lib/fontTools/__init__.py b/Lib/fontTools/__init__.py
index 8a8121d..00b962c 100644
--- a/Lib/fontTools/__init__.py
+++ b/Lib/fontTools/__init__.py
@@ -4,6 +4,6 @@
 
 log = logging.getLogger(__name__)
 
-version = __version__ = "4.18.0"
+version = __version__ = "4.18.1"
 
 __all__ = ["version", "log", "configLogger"]
diff --git a/Lib/fontTools/colorLib/builder.py b/Lib/fontTools/colorLib/builder.py
index d5084f4..74abb8a 100644
--- a/Lib/fontTools/colorLib/builder.py
+++ b/Lib/fontTools/colorLib/builder.py
@@ -436,20 +436,6 @@
     raise TypeError(obj)
 
 
-def _as_tuple(obj) -> Tuple[Any, ...]:
-    # start simple, who even cares about cyclic graphs or interesting field types
-    def _tuple_safe(value):
-        if isinstance(value, enum.Enum):
-            return value
-        elif hasattr(value, "__dict__"):
-            return tuple((k, _tuple_safe(v)) for k, v in value.__dict__.items())
-        elif isinstance(value, collections.abc.MutableSequence):
-            return tuple(_tuple_safe(e) for e in value)
-        return value
-
-    return tuple(_tuple_safe(obj))
-
-
 def _reuse_ranges(num_layers: int) -> Generator[Tuple[int, int], None, None]:
     # TODO feels like something itertools might have already
     for lbound in range(num_layers):
@@ -465,11 +451,40 @@
     slices: List[ot.Paint]
     layers: List[ot.Paint]
     reusePool: Mapping[Tuple[Any, ...], int]
+    tuples: Mapping[int, Tuple[Any, ...]]
+    keepAlive: List[ot.Paint]  # we need id to remain valid
 
     def __init__(self):
         self.slices = []
         self.layers = []
         self.reusePool = {}
+        self.tuples = {}
+        self.keepAlive = []
+
+    def _paint_tuple(self, paint: ot.Paint):
+        # start simple, who even cares about cyclic graphs or interesting field types
+        def _tuple_safe(value):
+            if isinstance(value, enum.Enum):
+                return value
+            elif hasattr(value, "__dict__"):
+                return tuple(
+                    (k, _tuple_safe(v)) for k, v in sorted(value.__dict__.items())
+                )
+            elif isinstance(value, collections.abc.MutableSequence):
+                return tuple(_tuple_safe(e) for e in value)
+            return value
+
+        # Cache the tuples for individual Paint instead of the whole sequence
+        # because the seq could be a transient slice
+        result = self.tuples.get(id(paint), None)
+        if result is None:
+            result = _tuple_safe(paint)
+            self.tuples[id(paint)] = result
+            self.keepAlive.append(paint)
+        return result
+
+    def _as_tuple(self, paints: Sequence[ot.Paint]) -> Tuple[Any, ...]:
+        return tuple(self._paint_tuple(p) for p in paints)
 
     def buildPaintSolid(
         self, paletteIndex: int, alpha: _ScalarInput = _DEFAULT_ALPHA
@@ -605,7 +620,9 @@
                 reverse=True,
             )
             for lbound, ubound in ranges:
-                reuse_lbound = self.reusePool.get(_as_tuple(paints[lbound:ubound]), -1)
+                reuse_lbound = self.reusePool.get(
+                    self._as_tuple(paints[lbound:ubound]), -1
+                )
                 if reuse_lbound == -1:
                     continue
                 new_slice = ot.Paint()
@@ -622,7 +639,7 @@
 
         # Register our parts for reuse
         for lbound, ubound in _reuse_ranges(len(paints)):
-            self.reusePool[_as_tuple(paints[lbound:ubound])] = (
+            self.reusePool[self._as_tuple(paints[lbound:ubound])] = (
                 lbound + ot_paint.FirstLayerIndex
             )
 
diff --git a/Lib/fontTools/varLib/mutator.py b/Lib/fontTools/varLib/mutator.py
index 1996578..ad76420 100644
--- a/Lib/fontTools/varLib/mutator.py
+++ b/Lib/fontTools/varLib/mutator.py
@@ -138,7 +138,7 @@
 			lsb_delta = 0
 		else:
 			lsb = boundsPen.bounds[0]
-		lsb_delta = entry[1] - lsb
+			lsb_delta = entry[1] - lsb
 
 		if lsb_delta or width_delta:
 			if width_delta:
diff --git a/METADATA b/METADATA
index 363f52d..697bd99 100644
--- a/METADATA
+++ b/METADATA
@@ -7,13 +7,13 @@
   }
   url {
     type: ARCHIVE
-    value: "https://github.com/fonttools/fonttools/archive/4.18.0.zip"
+    value: "https://github.com/fonttools/fonttools/archive/4.18.1.zip"
   }
-  version: "4.18.0"
+  version: "4.18.1"
   license_type: NOTICE
   last_upgrade_date {
     year: 2020
     month: 12
-    day: 4
+    day: 9
   }
 }
diff --git a/NEWS.rst b/NEWS.rst
index bf3a722..e10d006 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -1,3 +1,9 @@
+4.18.1 (released 2020-12-09)
+----------------------------
+
+- [colorLib] Speed optimization for ``LayerV1ListBuilder`` (#2119).
+- [mutator] Fixed missing tab in ``interpolate_cff2_metrics`` (0957dc7a).
+
 4.18.0 (released 2020-12-04)
 ----------------------------
 
diff --git a/setup.cfg b/setup.cfg
index db6ac55..d959ddb 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 4.18.0
+current_version = 4.18.1
 commit = True
 tag = False
 tag_name = {new_version}
diff --git a/setup.py b/setup.py
index df01004..be33a73 100755
--- a/setup.py
+++ b/setup.py
@@ -441,7 +441,7 @@
 
 setup_params = dict(
 	name="fonttools",
-	version="4.18.0",
+	version="4.18.1",
 	description="Tools to manipulate font files",
 	author="Just van Rossum",
 	author_email="just@letterror.com",