• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.ttLib import TTFont, newTable, getTableModule
2from fontTools.ttLib.tables.O_S_2f_2 import *
3import unittest
4
5
6class OS2TableTest(unittest.TestCase):
7    @staticmethod
8    def makeOS2_cmap(mapping):
9        font = TTFont()
10        font["OS/2"] = os2 = newTable("OS/2")
11        os2.version = 4
12        font["cmap"] = cmap = newTable("cmap")
13        st = getTableModule("cmap").CmapSubtable.newSubtable(4)
14        st.platformID, st.platEncID, st.language = 3, 1, 0
15        st.cmap = mapping
16        cmap.tables = []
17        cmap.tables.append(st)
18        return font, os2, cmap
19
20    def test_getUnicodeRanges(self):
21        table = table_O_S_2f_2()
22        table.ulUnicodeRange1 = 0xFFFFFFFF
23        table.ulUnicodeRange2 = 0xFFFFFFFF
24        table.ulUnicodeRange3 = 0xFFFFFFFF
25        table.ulUnicodeRange4 = 0xFFFFFFFF
26        bits = table.getUnicodeRanges()
27        for i in range(127):
28            self.assertIn(i, bits)
29
30    def test_setUnicodeRanges(self):
31        table = table_O_S_2f_2()
32        table.ulUnicodeRange1 = 0
33        table.ulUnicodeRange2 = 0
34        table.ulUnicodeRange3 = 0
35        table.ulUnicodeRange4 = 0
36        bits = set(range(123))
37        table.setUnicodeRanges(bits)
38        self.assertEqual(table.getUnicodeRanges(), bits)
39        with self.assertRaises(ValueError):
40            table.setUnicodeRanges([-1, 127, 255])
41
42    def test_recalcUnicodeRanges(self):
43        font, os2, cmap = self.makeOS2_cmap(
44            {0x0041: "A", 0x03B1: "alpha", 0x0410: "Acyr"}
45        )
46        os2.setUnicodeRanges({0, 1, 9})
47        # 'pruneOnly' will clear any bits for which there's no intersection:
48        # bit 1 ('Latin 1 Supplement'), in this case. However, it won't set
49        # bit 7 ('Greek and Coptic') despite the "alpha" character is present.
50        self.assertEqual(os2.recalcUnicodeRanges(font, pruneOnly=True), {0, 9})
51        # try again with pruneOnly=False: bit 7 is now set.
52        self.assertEqual(os2.recalcUnicodeRanges(font), {0, 7, 9})
53        # add a non-BMP char from 'Mahjong Tiles' block (bit 122)
54        cmap.tables[0].cmap[0x1F000] = "eastwindtile"
55        # the bit 122 and the special bit 57 ('Non Plane 0') are also enabled
56        self.assertEqual(os2.recalcUnicodeRanges(font), {0, 7, 9, 57, 122})
57
58    def test_intersectUnicodeRanges(self):
59        self.assertEqual(intersectUnicodeRanges([0x0410]), {9})
60        self.assertEqual(intersectUnicodeRanges([0x0410, 0x1F000]), {9, 57, 122})
61        self.assertEqual(
62            intersectUnicodeRanges([0x0410, 0x1F000], inverse=True),
63            (set(range(123)) - {9, 57, 122}),
64        )
65
66    def test_getCodePageRanges(self):
67        table = table_O_S_2f_2()
68        # version 0 doesn't define these fields so by definition defines no cp ranges
69        table.version = 0
70        self.assertEqual(table.getCodePageRanges(), set())
71        # version 1 and above do contain ulCodePageRange1 and 2 fields
72        table.version = 1
73        table.ulCodePageRange1 = 0xFFFFFFFF
74        table.ulCodePageRange2 = 0xFFFFFFFF
75        bits = table.getCodePageRanges()
76        for i in range(63):
77            self.assertIn(i, bits)
78
79    def test_setCodePageRanges(self):
80        table = table_O_S_2f_2()
81        table.version = 4
82        table.ulCodePageRange1 = 0
83        table.ulCodePageRange2 = 0
84        bits = set(range(64))
85        table.setCodePageRanges(bits)
86        self.assertEqual(table.getCodePageRanges(), bits)
87        with self.assertRaises(ValueError):
88            table.setCodePageRanges([-1])
89        with self.assertRaises(ValueError):
90            table.setCodePageRanges([64])
91        with self.assertRaises(ValueError):
92            table.setCodePageRanges([255])
93
94    def test_setCodePageRanges_bump_version(self):
95        # Setting codepage ranges on a OS/2 table version 0 automatically makes it
96        # a version 1 table
97        table = table_O_S_2f_2()
98        table.version = 0
99        self.assertEqual(table.getCodePageRanges(), set())
100        table.setCodePageRanges({0, 1, 2})
101        self.assertEqual(table.getCodePageRanges(), {0, 1, 2})
102        self.assertEqual(table.version, 1)
103
104    def test_recalcCodePageRanges(self):
105        font, os2, cmap = self.makeOS2_cmap(
106            {ord("A"): "A", ord("Ά"): "Alphatonos", ord("Б"): "Be"}
107        )
108        os2.setCodePageRanges({0, 2, 9})
109
110        # With pruneOnly=True, should clear any CodePage for which there are no
111        # characters in the cmap.
112        self.assertEqual(os2.recalcCodePageRanges(font, pruneOnly=True), {2})
113
114        # With pruneOnly=False, should also set CodePages not initially set.
115        self.assertEqual(os2.recalcCodePageRanges(font), {2, 3})
116
117        # Add a Korean character, should set CodePage 21 (Korean Johab)
118        cmap.tables[0].cmap[ord("곴")] = "goss"
119        self.assertEqual(os2.recalcCodePageRanges(font), {2, 3, 21})
120
121        # Remove all characters from cmap, should still set CodePage 0 (Latin 1)
122        cmap.tables[0].cmap = {}
123        self.assertEqual(os2.recalcCodePageRanges(font), {0})
124
125
126if __name__ == "__main__":
127    import sys
128
129    sys.exit(unittest.main())
130