From 90c7c7fae1e886f54a04306611f882b3db4c7509 Mon Sep 17 00:00:00 2001 From: Just van Rossum Date: Mon, 8 Jun 2020 19:39:28 +0200 Subject: [PATCH] Fix for #1985: ensure that the AxisNameID in the STAT table is > 255 (#1986) Fix for #1985: * ensure that the AxisNameID in the STAT table is not less than 256. This needed an additional argument to the addMultiLingualName() name table method. * fvar axis name IDs must also not be less than 256, just like STAT axis names. --- Lib/fontTools/otlLib/builder.py | 6 +++--- Lib/fontTools/ttLib/tables/_n_a_m_e.py | 15 +++++++++++---- Lib/fontTools/varLib/__init__.py | 2 +- Tests/otlLib/builder_test.py | 5 +++++ Tests/ttLib/tables/_n_a_m_e_test.py | 11 +++++++++++ 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/Lib/fontTools/otlLib/builder.py b/Lib/fontTools/otlLib/builder.py index 4d9d2bc02..e2e8e11ca 100644 --- a/Lib/fontTools/otlLib/builder.py +++ b/Lib/fontTools/otlLib/builder.py @@ -784,7 +784,7 @@ def _buildAxisRecords(axes, nameTable): for axisRecordIndex, axisDict in enumerate(axes): axis = ot.AxisRecord() axis.AxisTag = axisDict["tag"] - axis.AxisNameID = _addName(nameTable, axisDict["name"]) + axis.AxisNameID = _addName(nameTable, axisDict["name"], 256) axis.AxisOrdering = axisDict.get("ordering", axisRecordIndex) axisRecords.append(axis) @@ -837,7 +837,7 @@ def _buildAxisValuesFormat4(locations, axes, nameTable): return axisValues -def _addName(nameTable, value): +def _addName(nameTable, value, minNameID=0): if isinstance(value, int): # Already a nameID return value @@ -847,4 +847,4 @@ def _addName(nameTable, value): names = value else: raise TypeError("value must be int, str or dict") - return nameTable.addMultilingualName(names) + return nameTable.addMultilingualName(names, minNameID=minNameID) diff --git a/Lib/fontTools/ttLib/tables/_n_a_m_e.py b/Lib/fontTools/ttLib/tables/_n_a_m_e.py index 3f756c8f0..a973fe3c9 100644 --- a/Lib/fontTools/ttLib/tables/_n_a_m_e.py +++ b/Lib/fontTools/ttLib/tables/_n_a_m_e.py @@ -184,7 +184,7 @@ class table__n_a_m_e(DefaultTable.DefaultTable): raise ValueError("nameID must be less than 32768") return nameID - def findMultilingualName(self, names, windows=True, mac=True): + def findMultilingualName(self, names, windows=True, mac=True, minNameID=0): """Return the name ID of an existing multilingual name that matches the 'names' dictionary, or None if not found. @@ -198,6 +198,9 @@ class table__n_a_m_e(DefaultTable.DefaultTable): platEncID=1. If 'mac' is True, the returned name ID is guaranteed to exist for all requested languages for platformID=1 and platEncID=0. + + The returned name ID will not be less than the 'minNameID' + argument. """ # Gather the set of requested # (string, platformID, platEncID, langID) @@ -227,7 +230,7 @@ class table__n_a_m_e(DefaultTable.DefaultTable): name.platEncID, name.langID) except UnicodeDecodeError: continue - if key in reqNameSet: + if key in reqNameSet and name.nameID >= minNameID: nameSet = matchingNames.setdefault(name.nameID, set()) nameSet.add(key) @@ -239,7 +242,7 @@ class table__n_a_m_e(DefaultTable.DefaultTable): return None # not found def addMultilingualName(self, names, ttFont=None, nameID=None, - windows=True, mac=True): + windows=True, mac=True, minNameID=0): """Add a multilingual name, returning its name ID 'names' is a dictionary with the name in multiple languages, @@ -258,12 +261,16 @@ class table__n_a_m_e(DefaultTable.DefaultTable): If 'windows' is True, a platformID=3 name record will be added. If 'mac' is True, a platformID=1 name record will be added. + + If the 'nameID' argument is None, the created nameID will not + be less than the 'minNameID' argument. """ if not hasattr(self, 'names'): self.names = [] if nameID is None: # Reuse nameID if possible - nameID = self.findMultilingualName(names, windows=windows, mac=mac) + nameID = self.findMultilingualName( + names, windows=windows, mac=mac, minNameID=minNameID) if nameID is not None: return nameID nameID = self._findUnusedNameID() diff --git a/Lib/fontTools/varLib/__init__.py b/Lib/fontTools/varLib/__init__.py index 1bf586f70..e491ce239 100644 --- a/Lib/fontTools/varLib/__init__.py +++ b/Lib/fontTools/varLib/__init__.py @@ -71,7 +71,7 @@ def _add_fvar(font, axes, instances): axis.axisTag = Tag(a.tag) # TODO Skip axes that have no variation. axis.minValue, axis.defaultValue, axis.maxValue = a.minimum, a.default, a.maximum - axis.axisNameID = nameTable.addMultilingualName(a.labelNames, font) + axis.axisNameID = nameTable.addMultilingualName(a.labelNames, font, minNameID=256) axis.flags = int(a.hidden) fvar.axes.append(axis) diff --git a/Tests/otlLib/builder_test.py b/Tests/otlLib/builder_test.py index 727d685f3..e909a59b5 100644 --- a/Tests/otlLib/builder_test.py +++ b/Tests/otlLib/builder_test.py @@ -1371,6 +1371,11 @@ def test_buildStatTable(axes, axisValues, elidedFallbackName, expected_ttx): font = ttLib.TTFont() font["name"] = ttLib.newTable("name") font["name"].names = [] + # https://github.com/fonttools/fonttools/issues/1985 + # Add nameID < 256 that matches a test axis name, to test whether + # the nameID is not reused: AxisNameIDs must be > 255 according + # to the spec. + font["name"].addMultilingualName(dict(en="ABCDTest"), nameID=6) builder.buildStatTable(font, axes, axisValues, elidedFallbackName) f = io.StringIO() font.saveXML(f, tables=["STAT"]) diff --git a/Tests/ttLib/tables/_n_a_m_e_test.py b/Tests/ttLib/tables/_n_a_m_e_test.py index c0275e7b1..a12c1ade5 100644 --- a/Tests/ttLib/tables/_n_a_m_e_test.py +++ b/Tests/ttLib/tables/_n_a_m_e_test.py @@ -294,6 +294,17 @@ class NameTableTest(unittest.TestCase): nameTable.addMultilingualName({"en": "A", "la": "ⱾƤℚⱤ"}) captor.assertRegex("cannot store language la into 'ltag' table") + def test_addMultilingualName_minNameID(self): + table = table__n_a_m_e() + names, namesSubSet, namesSuperSet = self._get_test_names() + nameID = table.addMultilingualName(names, nameID=2) + self.assertEqual(nameID, 2) + nameID = table.addMultilingualName(names) + self.assertEqual(nameID, 2) + nameID = table.addMultilingualName(names, minNameID=256) + self.assertGreaterEqual(nameID, 256) + self.assertEqual(nameID, table.findMultilingualName(names, minNameID=256)) + def test_decompile_badOffset(self): # https://github.com/fonttools/fonttools/issues/525 table = table__n_a_m_e()