[ttLib.name] Add nameTable.findMultilingualName() method (#1921)

* add nameTable.findMultilingualName(), to find an existing multilingual name
* Make addMultilingualName() reuse nameIDs if possible when asking for a new nameID, by calling findMultilingualName()
This commit is contained in:
Just van Rossum 2020-05-07 11:06:51 +02:00 committed by GitHub
parent 37ff36bdd1
commit 4febf38be2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 99 additions and 1 deletions

View File

@ -184,6 +184,57 @@ 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):
"""Return the name ID of an existing multilingual name that
matches the 'names' dictionary, or None if not found.
'names' is a dictionary with the name in multiple languages,
such as {'en': 'Pale', 'de': 'Blaß', 'de-CH': 'Blass'}.
The keys can be arbitrary IETF BCP 47 language codes;
the values are Unicode strings.
If 'windows' is True, the returned name ID is guaranteed
exist for all requested languages for platformID=3 and
platEncID=1.
If 'mac' is True, the returned name ID is guaranteed to exist
for all requested languages for platformID=1 and platEncID=0.
"""
# Gather the set of requested
# (string, platformID, platEncID, langID)
# tuples
reqNameSet = set()
for lang, name in sorted(names.items()):
if windows:
windowsName = _makeWindowsName(name, None, lang)
if windowsName is not None:
reqNameSet.add((windowsName.string,
windowsName.platformID,
windowsName.platEncID,
windowsName.langID))
if mac:
macName = _makeMacName(name, None, lang)
if macName is not None:
reqNameSet.add((macName.string,
macName.platformID,
macName.platEncID,
macName.langID))
# Collect matching name IDs
matchingNames = dict()
for name in self.names:
key = (name.string, name.platformID,
name.platEncID, name.langID)
if key in reqNameSet:
nameSet = matchingNames.setdefault(name.nameID, set())
nameSet.add(key)
# Return the first name ID that defines all requested strings
for nameID, nameSet in sorted(matchingNames.items()):
if nameSet == reqNameSet:
return nameID
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):
"""Add a multilingual name, returning its name ID """Add a multilingual name, returning its name ID
@ -199,7 +250,8 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
names that otherwise cannot get encoded at all. names that otherwise cannot get encoded at all.
'nameID' is the name ID to be used, or None to let the library 'nameID' is the name ID to be used, or None to let the library
pick an unused name ID. find an existing set of name records that match, or pick an
unused name ID.
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.
@ -207,6 +259,10 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
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
nameID = self.findMultilingualName(names, windows=windows, mac=mac)
if nameID is not None:
return nameID
nameID = self._findUnusedNameID() nameID = self._findUnusedNameID()
# TODO: Should minimize BCP 47 language codes. # TODO: Should minimize BCP 47 language codes.
# https://github.com/fonttools/fonttools/issues/930 # https://github.com/fonttools/fonttools/issues/930

View File

@ -144,6 +144,48 @@ class NameTableTest(unittest.TestCase):
rec2 = table.getName(2, 1, 0, 0) rec2 = table.getName(2, 1, 0, 0)
self.assertEqual(str(rec2), "Regular") self.assertEqual(str(rec2), "Regular")
@staticmethod
def _get_test_names():
names = {
"en": "Width",
"de-CH": "Breite",
"gsw-LI": "Bräiti",
}
namesSubSet = names.copy()
del namesSubSet["gsw-LI"]
namesSuperSet = names.copy()
namesSuperSet["nl"] = "Breedte"
return names, namesSubSet, namesSuperSet
def test_findMultilingualName(self):
table = table__n_a_m_e()
names, namesSubSet, namesSuperSet = self._get_test_names()
nameID = table.addMultilingualName(names)
assert nameID is not None
self.assertEqual(nameID, table.findMultilingualName(names))
self.assertEqual(nameID, table.findMultilingualName(namesSubSet))
self.assertEqual(None, table.findMultilingualName(namesSuperSet))
def test_addMultilingualNameReuse(self):
table = table__n_a_m_e()
names, namesSubSet, namesSuperSet = self._get_test_names()
nameID = table.addMultilingualName(names)
assert nameID is not None
self.assertEqual(nameID, table.addMultilingualName(names))
self.assertEqual(nameID, table.addMultilingualName(namesSubSet))
self.assertNotEqual(None, table.addMultilingualName(namesSuperSet))
def test_findMultilingualNameNoMac(self):
table = table__n_a_m_e()
names, namesSubSet, namesSuperSet = self._get_test_names()
nameID = table.addMultilingualName(names, mac=False)
assert nameID is not None
self.assertEqual(nameID, table.findMultilingualName(names, mac=False))
self.assertEqual(None, table.findMultilingualName(names))
self.assertEqual(nameID, table.findMultilingualName(namesSubSet, mac=False))
self.assertEqual(None, table.findMultilingualName(namesSubSet))
self.assertEqual(None, table.findMultilingualName(namesSuperSet))
def test_addMultilingualName(self): def test_addMultilingualName(self):
# Microsoft Windows has language codes for “English” (en) # Microsoft Windows has language codes for “English” (en)
# and for “Standard German as used in Switzerland” (de-CH). # and for “Standard German as used in Switzerland” (de-CH).