• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.misc import sstruct
2from fontTools.misc.loggingTools import CapturingLogHandler
3from fontTools.misc.testTools import FakeFont
4from fontTools.misc.textTools import bytesjoin, tostr
5from fontTools.misc.xmlWriter import XMLWriter
6from io import BytesIO
7import struct
8import unittest
9from fontTools.ttLib import TTFont, newTable
10from fontTools.ttLib.tables._n_a_m_e import (
11    table__n_a_m_e,
12    NameRecord,
13    nameRecordFormat,
14    nameRecordSize,
15    makeName,
16    log,
17)
18
19
20def names(nameTable):
21    result = [
22        (n.nameID, n.platformID, n.platEncID, n.langID, n.string)
23        for n in nameTable.names
24    ]
25    result.sort()
26    return result
27
28
29class NameTableTest(unittest.TestCase):
30    def test_getDebugName(self):
31        table = table__n_a_m_e()
32        table.names = [
33            makeName("Bold", 258, 1, 0, 0),  # Mac, MacRoman, English
34            makeName("Gras", 258, 1, 0, 1),  # Mac, MacRoman, French
35            makeName("Fett", 258, 1, 0, 2),  # Mac, MacRoman, German
36            makeName("Sem Fracções", 292, 1, 0, 8),  # Mac, MacRoman, Portuguese
37        ]
38        self.assertEqual("Bold", table.getDebugName(258))
39        self.assertEqual("Sem Fracções", table.getDebugName(292))
40        self.assertEqual(None, table.getDebugName(999))
41
42    def test_setName(self):
43        table = table__n_a_m_e()
44        table.setName("Regular", 2, 1, 0, 0)
45        table.setName("Version 1.000", 5, 3, 1, 0x409)
46        table.setName("寬鬆", 276, 1, 2, 0x13)
47        self.assertEqual("Regular", table.getName(2, 1, 0, 0).toUnicode())
48        self.assertEqual("Version 1.000", table.getName(5, 3, 1, 0x409).toUnicode())
49        self.assertEqual("寬鬆", table.getName(276, 1, 2, 0x13).toUnicode())
50        self.assertTrue(len(table.names) == 3)
51        table.setName("緊縮", 276, 1, 2, 0x13)
52        self.assertEqual("緊縮", table.getName(276, 1, 2, 0x13).toUnicode())
53        self.assertTrue(len(table.names) == 3)
54        # passing bytes issues a warning
55        with CapturingLogHandler(log, "WARNING") as captor:
56            table.setName(b"abc", 0, 1, 0, 0)
57        self.assertTrue(
58            len([r for r in captor.records if "string is bytes" in r.msg]) == 1
59        )
60        # anything other than unicode or bytes raises an error
61        with self.assertRaises(TypeError):
62            table.setName(1.000, 5, 1, 0, 0)
63
64    def test_names_sort_bytes_str(self):
65        # Corner case: If a user appends a name record directly to `names`, the
66        # `__lt__` method on NameRecord may run into duplicate name records where
67        # one `string` is a str and the other one bytes, leading to an exception.
68        table = table__n_a_m_e()
69        table.names = [
70            makeName("Test", 25, 3, 1, 0x409),
71            makeName("Test".encode("utf-16be"), 25, 3, 1, 0x409),
72        ]
73        table.compile(None)
74
75    def test_names_sort_attributes(self):
76        table = table__n_a_m_e()
77        # Create an actual invalid NameRecord object
78        broken = makeName("Test", 25, 3, 1, 0x409)
79        delattr(broken, "platformID")
80        table.names = [
81            makeName("Test", 25, 3, 1, 0x409),
82            broken,
83        ]
84        # Sorting these two is impossible, expect an error to be raised
85        with self.assertRaises(TypeError):
86            table.names.sort()
87
88    def test_names_sort_encoding(self):
89        """
90        Confirm that encoding errors in name table strings do not prevent at
91        least sorting by other IDs
92        """
93        table = table__n_a_m_e()
94        table.names = [
95            makeName("Mac Unicode 寬 encodes ok", 25, 3, 0, 0x409),
96            makeName("Win Latin 寬 fails to encode", 25, 1, 0, 0),
97        ]
98        table.names.sort()
99        # Encoding errors or not, sort based on other IDs nonetheless
100        self.assertEqual(table.names[0].platformID, 1)
101        self.assertEqual(table.names[1].platformID, 3)
102
103    def test_addName(self):
104        table = table__n_a_m_e()
105        nameIDs = []
106        for string in ("Width", "Weight", "Custom"):
107            nameIDs.append(table.addName(string))
108
109        self.assertEqual(nameIDs[0], 256)
110        self.assertEqual(nameIDs[1], 257)
111        self.assertEqual(nameIDs[2], 258)
112        self.assertEqual(len(table.names), 6)
113        self.assertEqual(table.names[0].string, "Width")
114        self.assertEqual(table.names[1].string, "Width")
115        self.assertEqual(table.names[2].string, "Weight")
116        self.assertEqual(table.names[3].string, "Weight")
117        self.assertEqual(table.names[4].string, "Custom")
118        self.assertEqual(table.names[5].string, "Custom")
119
120        with self.assertRaises(ValueError):
121            table.addName("Invalid nameID", minNameID=32767)
122        with self.assertRaises(TypeError):
123            table.addName(b"abc")  # must be unicode string
124
125    def test_removeNames(self):
126        table = table__n_a_m_e()
127        table.setName("Regular", 2, 1, 0, 0)
128        table.setName("Regular", 2, 3, 1, 0x409)
129        table.removeNames(nameID=2)
130        self.assertEqual(table.names, [])
131
132        table = table__n_a_m_e()
133        table.setName("FamilyName", 1, 1, 0, 0)
134        table.setName("Regular", 2, 1, 0, 0)
135        table.setName("FamilyName", 1, 3, 1, 0x409)
136        table.setName("Regular", 2, 3, 1, 0x409)
137        table.removeNames(platformID=1)
138        self.assertEqual(len(table.names), 2)
139        self.assertIsNone(table.getName(1, 1, 0, 0))
140        self.assertIsNone(table.getName(2, 1, 0, 0))
141        rec1 = table.getName(1, 3, 1, 0x409)
142        self.assertEqual(str(rec1), "FamilyName")
143        rec2 = table.getName(2, 3, 1, 0x409)
144        self.assertEqual(str(rec2), "Regular")
145
146        table = table__n_a_m_e()
147        table.setName("FamilyName", 1, 1, 0, 0)
148        table.setName("Regular", 2, 1, 0, 0)
149        table.removeNames(nameID=1)
150        self.assertEqual(len(table.names), 1)
151        self.assertIsNone(table.getName(1, 1, 0, 0))
152        rec = table.getName(2, 1, 0, 0)
153        self.assertEqual(str(rec), "Regular")
154
155        table = table__n_a_m_e()
156        table.setName("FamilyName", 1, 1, 0, 0)
157        table.setName("Regular", 2, 1, 0, 0)
158        table.removeNames(2, 1, 0, 0)
159        self.assertEqual(len(table.names), 1)
160        self.assertIsNone(table.getName(2, 1, 0, 0))
161        rec = table.getName(1, 1, 0, 0)
162        self.assertEqual(str(rec), "FamilyName")
163
164        table = table__n_a_m_e()
165        table.setName("FamilyName", 1, 1, 0, 0)
166        table.setName("Regular", 2, 1, 0, 0)
167        table.removeNames()
168        self.assertEqual(len(table.names), 2)
169        rec1 = table.getName(1, 1, 0, 0)
170        self.assertEqual(str(rec1), "FamilyName")
171        rec2 = table.getName(2, 1, 0, 0)
172        self.assertEqual(str(rec2), "Regular")
173
174    @staticmethod
175    def _get_test_names():
176        names = {
177            "en": "Width",
178            "de-CH": "Breite",
179            "gsw-LI": "Bräiti",
180        }
181        namesSubSet = names.copy()
182        del namesSubSet["gsw-LI"]
183        namesSuperSet = names.copy()
184        namesSuperSet["nl"] = "Breedte"
185        return names, namesSubSet, namesSuperSet
186
187    def test_findMultilingualName(self):
188        table = table__n_a_m_e()
189        names, namesSubSet, namesSuperSet = self._get_test_names()
190        nameID = table.addMultilingualName(names)
191        assert nameID is not None
192        self.assertEqual(nameID, table.findMultilingualName(names))
193        self.assertEqual(nameID, table.findMultilingualName(namesSubSet))
194        self.assertEqual(None, table.findMultilingualName(namesSuperSet))
195
196    def test_findMultilingualName_compiled(self):
197        table = table__n_a_m_e()
198        names, namesSubSet, namesSuperSet = self._get_test_names()
199        nameID = table.addMultilingualName(names)
200        assert nameID is not None
201        # After compile/decompile, name.string is a bytes sequence, which
202        # findMultilingualName() should also handle
203        data = table.compile(None)
204        table = table__n_a_m_e()
205        table.decompile(data, None)
206        self.assertEqual(nameID, table.findMultilingualName(names))
207        self.assertEqual(nameID, table.findMultilingualName(namesSubSet))
208        self.assertEqual(None, table.findMultilingualName(namesSuperSet))
209
210    def test_addMultilingualNameReuse(self):
211        table = table__n_a_m_e()
212        names, namesSubSet, namesSuperSet = self._get_test_names()
213        nameID = table.addMultilingualName(names)
214        assert nameID is not None
215        self.assertEqual(nameID, table.addMultilingualName(names))
216        self.assertEqual(nameID, table.addMultilingualName(namesSubSet))
217        self.assertNotEqual(None, table.addMultilingualName(namesSuperSet))
218
219    def test_findMultilingualNameNoMac(self):
220        table = table__n_a_m_e()
221        names, namesSubSet, namesSuperSet = self._get_test_names()
222        nameID = table.addMultilingualName(names, mac=False)
223        assert nameID is not None
224        self.assertEqual(nameID, table.findMultilingualName(names, mac=False))
225        self.assertEqual(None, table.findMultilingualName(names))
226        self.assertEqual(nameID, table.findMultilingualName(namesSubSet, mac=False))
227        self.assertEqual(None, table.findMultilingualName(namesSubSet))
228        self.assertEqual(None, table.findMultilingualName(namesSuperSet))
229
230    def test_addMultilingualName(self):
231        # Microsoft Windows has language codes for “English” (en)
232        # and for “Standard German as used in Switzerland” (de-CH).
233        # In this case, we expect that the implementation just
234        # encodes the name for the Windows platform; Apple platforms
235        # have been able to decode Windows names since the early days
236        # of OSX (~2001). However, Windows has no language code for
237        # “Swiss German as used in Liechtenstein” (gsw-LI), so we
238        # expect that the implementation populates the 'ltag' table
239        # to represent that particular, rather exotic BCP47 code.
240        font = FakeFont(glyphs=[".notdef", "A"])
241        nameTable = font.tables["name"] = newTable("name")
242        with CapturingLogHandler(log, "WARNING") as captor:
243            widthID = nameTable.addMultilingualName(
244                {
245                    "en": "Width",
246                    "de-CH": "Breite",
247                    "gsw-LI": "Bräiti",
248                },
249                ttFont=font,
250                mac=False,
251            )
252            self.assertEqual(widthID, 256)
253            xHeightID = nameTable.addMultilingualName(
254                {"en": "X-Height", "gsw-LI": "X-Hööchi"}, ttFont=font, mac=False
255            )
256            self.assertEqual(xHeightID, 257)
257        captor.assertRegex("cannot add Windows name in language gsw-LI")
258        self.assertEqual(
259            names(nameTable),
260            [
261                (256, 0, 4, 0, "Bräiti"),
262                (256, 3, 1, 0x0409, "Width"),
263                (256, 3, 1, 0x0807, "Breite"),
264                (257, 0, 4, 0, "X-Hööchi"),
265                (257, 3, 1, 0x0409, "X-Height"),
266            ],
267        )
268        self.assertEqual(set(font.tables.keys()), {"ltag", "name"})
269        self.assertEqual(font["ltag"].tags, ["gsw-LI"])
270
271    def test_addMultilingualName_legacyMacEncoding(self):
272        # Windows has no language code for Latin; MacOS has a code;
273        # and we actually can convert the name to the legacy MacRoman
274        # encoding. In this case, we expect that the name gets encoded
275        # as Macintosh name (platformID 1) with the corresponding Mac
276        # language code (133); the 'ltag' table should not be used.
277        font = FakeFont(glyphs=[".notdef", "A"])
278        nameTable = font.tables["name"] = newTable("name")
279        with CapturingLogHandler(log, "WARNING") as captor:
280            nameTable.addMultilingualName({"la": "SPQR"}, ttFont=font)
281        captor.assertRegex("cannot add Windows name in language la")
282        self.assertEqual(names(nameTable), [(256, 1, 0, 131, "SPQR")])
283        self.assertNotIn("ltag", font.tables.keys())
284
285    def test_addMultilingualName_legacyMacEncodingButUnencodableName(self):
286        # Windows has no language code for Latin; MacOS has a code;
287        # but we cannot encode the name into this encoding because
288        # it contains characters that are not representable.
289        # In this case, we expect that the name gets encoded as
290        # Unicode name (platformID 0) with the language tag being
291        # added to the 'ltag' table.
292        font = FakeFont(glyphs=[".notdef", "A"])
293        nameTable = font.tables["name"] = newTable("name")
294        with CapturingLogHandler(log, "WARNING") as captor:
295            nameTable.addMultilingualName({"la": "ⱾƤℚⱤ"}, ttFont=font)
296        captor.assertRegex("cannot add Windows name in language la")
297        self.assertEqual(names(nameTable), [(256, 0, 4, 0, "ⱾƤℚⱤ")])
298        self.assertIn("ltag", font.tables)
299        self.assertEqual(font["ltag"].tags, ["la"])
300
301    def test_addMultilingualName_legacyMacEncodingButNoCodec(self):
302        # Windows has no language code for “Azeri written in the
303        # Arabic script” (az-Arab); MacOS would have a code (50);
304        # but we cannot encode the name into the legacy encoding
305        # because we have no codec for MacArabic in fonttools.
306        # In this case, we expect that the name gets encoded as
307        # Unicode name (platformID 0) with the language tag being
308        # added to the 'ltag' table.
309        font = FakeFont(glyphs=[".notdef", "A"])
310        nameTable = font.tables["name"] = newTable("name")
311        with CapturingLogHandler(log, "WARNING") as captor:
312            nameTable.addMultilingualName({"az-Arab": "آذربايجان ديلی"}, ttFont=font)
313        captor.assertRegex("cannot add Windows name in language az-Arab")
314        self.assertEqual(names(nameTable), [(256, 0, 4, 0, "آذربايجان ديلی")])
315        self.assertIn("ltag", font.tables)
316        self.assertEqual(font["ltag"].tags, ["az-Arab"])
317
318    def test_addMultilingualName_noTTFont(self):
319        # If the ttFont argument is not passed, the implementation
320        # should add whatever names it can, but it should not crash
321        # just because it cannot build an ltag table.
322        nameTable = newTable("name")
323        with CapturingLogHandler(log, "WARNING") as captor:
324            nameTable.addMultilingualName({"en": "A", "la": "ⱾƤℚⱤ"})
325        captor.assertRegex("cannot store language la into 'ltag' table")
326
327    def test_addMultilingualName_TTFont(self):
328        # if ttFont argument is passed, it should not WARN about not being able
329        # to create ltag table.
330        font = FakeFont(glyphs=[".notdef", "A"])
331        nameTable = newTable("name")
332        with CapturingLogHandler(log, "WARNING") as captor:
333            nameTable.addMultilingualName({"en": "A", "ar": "ع"}, ttFont=font)
334        self.assertFalse(captor.records)
335
336    def test_addMultilingualName_minNameID(self):
337        table = table__n_a_m_e()
338        names, namesSubSet, namesSuperSet = self._get_test_names()
339        nameID = table.addMultilingualName(names, nameID=2)
340        self.assertEqual(nameID, 2)
341        nameID = table.addMultilingualName(names)
342        self.assertEqual(nameID, 2)
343        nameID = table.addMultilingualName(names, minNameID=256)
344        self.assertGreaterEqual(nameID, 256)
345        self.assertEqual(nameID, table.findMultilingualName(names, minNameID=256))
346
347    def test_addMultilingualName_name_inconsistencies(self):
348        # Check what happens, when there are
349        # inconsistencies in the name table
350        table = table__n_a_m_e()
351        table.setName("Weight", 270, 3, 1, 0x409)
352        names = {
353            "en": "Weight",
354        }
355        nameID = table.addMultilingualName(names, minNameID=256)
356        # Because there is an inconsistency in the names,
357        # addMultilingualName adds a new name ID
358        self.assertEqual(271, nameID)
359
360    def test_decompile_badOffset(self):
361        # https://github.com/fonttools/fonttools/issues/525
362        table = table__n_a_m_e()
363        badRecord = {
364            "platformID": 1,
365            "platEncID": 3,
366            "langID": 7,
367            "nameID": 1,
368            "length": 3,
369            "offset": 8765,  # out of range
370        }
371        data = bytesjoin(
372            [
373                struct.pack(tostr(">HHH"), 1, 1, 6 + nameRecordSize),
374                sstruct.pack(nameRecordFormat, badRecord),
375            ]
376        )
377        table.decompile(data, ttFont=None)
378        self.assertEqual(table.names, [])
379
380
381class NameRecordTest(unittest.TestCase):
382    def test_toUnicode_utf16be(self):
383        name = makeName("Foo Bold", 111, 0, 2, 7)
384        self.assertEqual("utf_16_be", name.getEncoding())
385        self.assertEqual("Foo Bold", name.toUnicode())
386
387    def test_toUnicode_macroman(self):
388        name = makeName("Foo Italic", 222, 1, 0, 7)  # MacRoman
389        self.assertEqual("mac_roman", name.getEncoding())
390        self.assertEqual("Foo Italic", name.toUnicode())
391
392    def test_toUnicode_macromanian(self):
393        name = makeName(b"Foo Italic\xfb", 222, 1, 0, 37)  # Mac Romanian
394        self.assertEqual("mac_romanian", name.getEncoding())
395        self.assertEqual("Foo Italic" + chr(0x02DA), name.toUnicode())
396
397    def test_toUnicode_UnicodeDecodeError(self):
398        name = makeName(b"\1", 111, 0, 2, 7)
399        self.assertEqual("utf_16_be", name.getEncoding())
400        self.assertRaises(UnicodeDecodeError, name.toUnicode)
401
402    def test_toUnicode_singleChar(self):
403        # https://github.com/fonttools/fonttools/issues/1997
404        name = makeName("A", 256, 3, 1, 0x409)
405        self.assertEqual(name.toUnicode(), "A")
406
407    def toXML(self, name):
408        writer = XMLWriter(BytesIO())
409        name.toXML(writer, ttFont=None)
410        xml = writer.file.getvalue().decode("utf_8").strip()
411        return xml.split(writer.newlinestr.decode("utf_8"))[1:]
412
413    def test_toXML_utf16be(self):
414        name = makeName("Foo Bold", 111, 0, 2, 7)
415        self.assertEqual(
416            [
417                '<namerecord nameID="111" platformID="0" platEncID="2" langID="0x7">',
418                "  Foo Bold",
419                "</namerecord>",
420            ],
421            self.toXML(name),
422        )
423
424    def test_toXML_utf16be_odd_length1(self):
425        name = makeName(b"\0F\0o\0o\0", 111, 0, 2, 7)
426        self.assertEqual(
427            [
428                '<namerecord nameID="111" platformID="0" platEncID="2" langID="0x7">',
429                "  Foo",
430                "</namerecord>",
431            ],
432            self.toXML(name),
433        )
434
435    def test_toXML_utf16be_odd_length2(self):
436        name = makeName(b"\0Fooz", 111, 0, 2, 7)
437        self.assertEqual(
438            [
439                '<namerecord nameID="111" platformID="0" platEncID="2" langID="0x7">',
440                "  Fooz",
441                "</namerecord>",
442            ],
443            self.toXML(name),
444        )
445
446    def test_toXML_utf16be_double_encoded(self):
447        name = makeName(b"\0\0\0F\0\0\0o", 111, 0, 2, 7)
448        self.assertEqual(
449            [
450                '<namerecord nameID="111" platformID="0" platEncID="2" langID="0x7">',
451                "  Fo",
452                "</namerecord>",
453            ],
454            self.toXML(name),
455        )
456
457    def test_toXML_macroman(self):
458        name = makeName("Foo Italic", 222, 1, 0, 7)  # MacRoman
459        self.assertEqual(
460            [
461                '<namerecord nameID="222" platformID="1" platEncID="0" langID="0x7" unicode="True">',
462                "  Foo Italic",
463                "</namerecord>",
464            ],
465            self.toXML(name),
466        )
467
468    def test_toXML_macroman_actual_utf16be(self):
469        name = makeName("\0F\0o\0o", 222, 1, 0, 7)
470        self.assertEqual(
471            [
472                '<namerecord nameID="222" platformID="1" platEncID="0" langID="0x7" unicode="True">',
473                "  Foo",
474                "</namerecord>",
475            ],
476            self.toXML(name),
477        )
478
479    def test_toXML_unknownPlatEncID_nonASCII(self):
480        name = makeName(b"B\x8arli", 333, 1, 9876, 7)  # Unknown Mac encodingID
481        self.assertEqual(
482            [
483                '<namerecord nameID="333" platformID="1" platEncID="9876" langID="0x7" unicode="False">',
484                "  B&#138;rli",
485                "</namerecord>",
486            ],
487            self.toXML(name),
488        )
489
490    def test_toXML_unknownPlatEncID_ASCII(self):
491        name = makeName(b"Barli", 333, 1, 9876, 7)  # Unknown Mac encodingID
492        self.assertEqual(
493            [
494                '<namerecord nameID="333" platformID="1" platEncID="9876" langID="0x7" unicode="True">',
495                "  Barli",
496                "</namerecord>",
497            ],
498            self.toXML(name),
499        )
500
501    def test_encoding_macroman_misc(self):
502        name = makeName("", 123, 1, 0, 17)  # Mac Turkish
503        self.assertEqual(name.getEncoding(), "mac_turkish")
504        name.langID = 37
505        self.assertEqual(name.getEncoding(), "mac_romanian")
506        name.langID = 45  # Other
507        self.assertEqual(name.getEncoding(), "mac_roman")
508
509    def test_extended_mac_encodings(self):
510        name = makeName(b"\xfe", 123, 1, 1, 0)  # Mac Japanese
511        self.assertEqual(name.toUnicode(), chr(0x2122))
512
513    def test_extended_mac_encodings_errors(self):
514        s = "汉仪彩云体简"
515        name = makeName(s.encode("x_mac_simp_chinese_ttx"), 123, 1, 25, 0)
516        # first check we round-trip with 'strict'
517        self.assertEqual(name.toUnicode(errors="strict"), s)
518
519        # append an incomplete invalid sequence and check that we handle
520        # errors with the requested error handler
521        name.string += b"\xba"
522        self.assertEqual(name.toUnicode(errors="backslashreplace"), s + "\\xba")
523        self.assertEqual(name.toUnicode(errors="replace"), s + "�")
524
525    def test_extended_unknown(self):
526        name = makeName(b"\xfe", 123, 10, 11, 12)
527        self.assertEqual(name.getEncoding(), "ascii")
528        self.assertEqual(name.getEncoding(None), None)
529        self.assertEqual(name.getEncoding(default=None), None)
530
531    def test_get_family_name(self):
532        name = table__n_a_m_e()
533        name.names = [
534            makeName("Copyright", 0, 1, 0, 0),
535            makeName("Family Name ID 1", 1, 1, 0, 0),
536            makeName("SubFamily Name ID 2", 2, 1, 0, 0),
537            makeName("Unique Name ID 3", 3, 1, 0, 0),
538            makeName("Full Name ID 4", 4, 1, 0, 0),
539            makeName("PS Name ID 6", 6, 1, 0, 0),
540            makeName("Version Name ID 5", 5, 1, 0, 0),
541            makeName("Trademark Name ID 7", 7, 1, 0, 0),
542        ]
543
544        result_value = name.getBestFamilyName()
545        self.assertEqual("Family Name ID 1", result_value)
546
547        expected_value = "Family Name ID 16"
548        name.setName(expected_value, 16, 1, 0, 0)
549        result_value = name.getBestFamilyName()
550        self.assertEqual(expected_value, result_value)
551
552        expected_value = "Family Name ID 21"
553        name.setName(expected_value, 21, 1, 0, 0)
554        result_value = name.getBestFamilyName()
555        self.assertEqual(expected_value, result_value)
556
557    def test_get_subfamily_name(self):
558        name = table__n_a_m_e()
559        name.names = [
560            makeName("Copyright", 0, 1, 0, 0),
561            makeName("Family Name ID 1", 1, 1, 0, 0),
562            makeName("SubFamily Name ID 2", 2, 1, 0, 0),
563            makeName("Unique Name ID 3", 3, 1, 0, 0),
564            makeName("Full Name ID 4", 4, 1, 0, 0),
565            makeName("PS Name ID 6", 6, 1, 0, 0),
566            makeName("Version Name ID 5", 5, 1, 0, 0),
567            makeName("Trademark Name ID 7", 7, 1, 0, 0),
568        ]
569
570        result_value = name.getBestSubFamilyName()
571        self.assertEqual("SubFamily Name ID 2", result_value)
572
573        expected_value = "Family Name ID 17"
574        name.setName(expected_value, 17, 1, 0, 0)
575        result_value = name.getBestSubFamilyName()
576        self.assertEqual(expected_value, result_value)
577
578        expected_value = "Family Name ID 22"
579        name.setName(expected_value, 22, 1, 0, 0)
580        result_value = name.getBestSubFamilyName()
581        self.assertEqual(expected_value, result_value)
582
583    def test_get_nice_full_name(self):
584        name = table__n_a_m_e()
585        name.names = [
586            makeName("NID 1", 1, 1, 0, 0),
587            makeName("NID 2", 2, 1, 0, 0),
588            makeName("NID 4", 4, 1, 0, 0),
589            makeName("NID 6", 6, 1, 0, 0),
590        ]
591
592        result_value = name.getBestFullName()
593        self.assertEqual("NID 1 NID 2", result_value)
594
595        expected_value = "NID 1 NID 2"
596        # expection is still NID 1 NID 2,
597        # because name ID 17 is missing
598        name.setName("NID 16", 16, 1, 0, 0)
599        result_value = name.getBestFullName()
600        self.assertEqual(expected_value, result_value)
601
602        name.setName("NID 17", 17, 1, 0, 0)
603        result_value = name.getBestFullName()
604        self.assertEqual("NID 16 NID 17", result_value)
605
606        expected_value = "NID 16 NID 17"
607        # expection is still NID 16 NID 17,
608        # because name ID 21 is missing
609        name.setName("NID 21", 21, 1, 0, 0)
610        result_value = name.getBestFullName()
611        self.assertEqual(expected_value, result_value)
612
613        name.setName("NID 22", 22, 1, 0, 0)
614        result_value = name.getBestFullName()
615        self.assertEqual("NID 21 NID 22", result_value)
616
617        for NID in [2, 16, 17, 21, 22]:
618            name.removeNames(NID)
619
620        result_value = name.getBestFullName()
621        self.assertEqual("NID 4", result_value)
622
623        name.setName("Regular", 2, 1, 0, 0)
624        result_value = name.getBestFullName()
625        self.assertEqual("NID 1", result_value)
626
627
628if __name__ == "__main__":
629    import sys
630
631    sys.exit(unittest.main())
632