diff --git a/Lib/fontTools/ttLib/tables/_n_a_m_e.py b/Lib/fontTools/ttLib/tables/_n_a_m_e.py index 2383f7953..e9ff2151d 100644 --- a/Lib/fontTools/ttLib/tables/_n_a_m_e.py +++ b/Lib/fontTools/ttLib/tables/_n_a_m_e.py @@ -459,22 +459,32 @@ class NameRecord(object): if type(self) != type(other): return NotImplemented - # implemented so that list.sort() sorts according to the spec. - selfTuple = ( - getattr(self, "platformID", None), - getattr(self, "platEncID", None), - getattr(self, "langID", None), - getattr(self, "nameID", None), - getattr(self, "string", None), - ) - otherTuple = ( - getattr(other, "platformID", None), - getattr(other, "platEncID", None), - getattr(other, "langID", None), - getattr(other, "nameID", None), - getattr(other, "string", None), - ) - return selfTuple < otherTuple + try: + # implemented so that list.sort() sorts according to the spec. + selfTuple = ( + self.platformID, + self.platEncID, + self.langID, + self.nameID, + self.toBytes(), + ) + otherTuple = ( + other.platformID, + other.platEncID, + other.langID, + other.nameID, + other.toBytes(), + ) + return selfTuple < otherTuple + except (UnicodeEncodeError, AttributeError): + # This can only happen for + # 1) an object that is not a NameRecord, or + # 2) an unlikely incomplete NameRecord object which has not been + # fully populated, or + # 3) when all IDs are identical but the strings can't be encoded + # for their platform encoding. + # In all cases it is best to return NotImplemented. + return NotImplemented def __repr__(self): return "" % ( diff --git a/Tests/ttLib/tables/_n_a_m_e_test.py b/Tests/ttLib/tables/_n_a_m_e_test.py index 98047e452..d770a5231 100644 --- a/Tests/ttLib/tables/_n_a_m_e_test.py +++ b/Tests/ttLib/tables/_n_a_m_e_test.py @@ -53,6 +53,26 @@ class NameTableTest(unittest.TestCase): with self.assertRaises(TypeError): table.setName(1.000, 5, 1, 0, 0) + def test_names_sort_bytes_str(self): + # Corner case: If a user appends a name record directly to `names`, the + # `__lt__` method on NameRecord may run into duplicate name records where + # one `string` is a str and the other one bytes, leading to an exception. + table = table__n_a_m_e() + table.names = [ + makeName("Test", 25, 3, 1, 0x409), + makeName("Test".encode("utf-16be"), 25, 3, 1, 0x409), + ] + table.compile(None) + + def test_names_sort_bytes_str_encoding_error(self): + table = table__n_a_m_e() + table.names = [ + makeName("Test寬", 25, 1, 0, 0), + makeName("Test鬆鬆", 25, 1, 0, 0), + ] + with self.assertRaises(TypeError): + table.names.sort() + def test_addName(self): table = table__n_a_m_e() nameIDs = []