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.
This commit is contained in:
Just van Rossum 2020-06-08 19:39:28 +02:00 committed by GitHub
parent df1b499dbc
commit 90c7c7fae1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 31 additions and 8 deletions

View File

@ -784,7 +784,7 @@ def _buildAxisRecords(axes, nameTable):
for axisRecordIndex, axisDict in enumerate(axes): for axisRecordIndex, axisDict in enumerate(axes):
axis = ot.AxisRecord() axis = ot.AxisRecord()
axis.AxisTag = axisDict["tag"] axis.AxisTag = axisDict["tag"]
axis.AxisNameID = _addName(nameTable, axisDict["name"]) axis.AxisNameID = _addName(nameTable, axisDict["name"], 256)
axis.AxisOrdering = axisDict.get("ordering", axisRecordIndex) axis.AxisOrdering = axisDict.get("ordering", axisRecordIndex)
axisRecords.append(axis) axisRecords.append(axis)
@ -837,7 +837,7 @@ def _buildAxisValuesFormat4(locations, axes, nameTable):
return axisValues return axisValues
def _addName(nameTable, value): def _addName(nameTable, value, minNameID=0):
if isinstance(value, int): if isinstance(value, int):
# Already a nameID # Already a nameID
return value return value
@ -847,4 +847,4 @@ def _addName(nameTable, value):
names = value names = value
else: else:
raise TypeError("value must be int, str or dict") raise TypeError("value must be int, str or dict")
return nameTable.addMultilingualName(names) return nameTable.addMultilingualName(names, minNameID=minNameID)

View File

@ -184,7 +184,7 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
raise ValueError("nameID must be less than 32768") raise ValueError("nameID must be less than 32768")
return nameID 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 """Return the name ID of an existing multilingual name that
matches the 'names' dictionary, or None if not found. matches the 'names' dictionary, or None if not found.
@ -198,6 +198,9 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
platEncID=1. platEncID=1.
If 'mac' is True, the returned name ID is guaranteed to exist If 'mac' is True, the returned name ID is guaranteed to exist
for all requested languages for platformID=1 and platEncID=0. 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 # Gather the set of requested
# (string, platformID, platEncID, langID) # (string, platformID, platEncID, langID)
@ -227,7 +230,7 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
name.platEncID, name.langID) name.platEncID, name.langID)
except UnicodeDecodeError: except UnicodeDecodeError:
continue continue
if key in reqNameSet: if key in reqNameSet and name.nameID >= minNameID:
nameSet = matchingNames.setdefault(name.nameID, set()) nameSet = matchingNames.setdefault(name.nameID, set())
nameSet.add(key) nameSet.add(key)
@ -239,7 +242,7 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
return None # not found return None # not found
def addMultilingualName(self, names, ttFont=None, nameID=None, 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 """Add a multilingual name, returning its name ID
'names' is a dictionary with the name in multiple languages, '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 'windows' is True, a platformID=3 name record will be added.
If 'mac' is True, a platformID=1 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'): if not hasattr(self, 'names'):
self.names = [] self.names = []
if nameID is None: if nameID is None:
# Reuse nameID if possible # 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: if nameID is not None:
return nameID return nameID
nameID = self._findUnusedNameID() nameID = self._findUnusedNameID()

View File

@ -71,7 +71,7 @@ def _add_fvar(font, axes, instances):
axis.axisTag = Tag(a.tag) axis.axisTag = Tag(a.tag)
# TODO Skip axes that have no variation. # TODO Skip axes that have no variation.
axis.minValue, axis.defaultValue, axis.maxValue = a.minimum, a.default, a.maximum 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) axis.flags = int(a.hidden)
fvar.axes.append(axis) fvar.axes.append(axis)

View File

@ -1371,6 +1371,11 @@ def test_buildStatTable(axes, axisValues, elidedFallbackName, expected_ttx):
font = ttLib.TTFont() font = ttLib.TTFont()
font["name"] = ttLib.newTable("name") font["name"] = ttLib.newTable("name")
font["name"].names = [] 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) builder.buildStatTable(font, axes, axisValues, elidedFallbackName)
f = io.StringIO() f = io.StringIO()
font.saveXML(f, tables=["STAT"]) font.saveXML(f, tables=["STAT"])

View File

@ -294,6 +294,17 @@ class NameTableTest(unittest.TestCase):
nameTable.addMultilingualName({"en": "A", "la": "ⱾƤℚⱤ"}) nameTable.addMultilingualName({"en": "A", "la": "ⱾƤℚⱤ"})
captor.assertRegex("cannot store language la into 'ltag' table") 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): def test_decompile_badOffset(self):
# https://github.com/fonttools/fonttools/issues/525 # https://github.com/fonttools/fonttools/issues/525
table = table__n_a_m_e() table = table__n_a_m_e()