xref: /aosp_15_r20/external/fonttools/Tests/ufoLib/glifLib_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesimport logging
2*e1fe3e4aSElliott Hughesimport os
3*e1fe3e4aSElliott Hughesimport tempfile
4*e1fe3e4aSElliott Hughesimport shutil
5*e1fe3e4aSElliott Hughesimport unittest
6*e1fe3e4aSElliott Hughesfrom pathlib import Path
7*e1fe3e4aSElliott Hughesfrom io import open
8*e1fe3e4aSElliott Hughesfrom .testSupport import getDemoFontGlyphSetPath
9*e1fe3e4aSElliott Hughesfrom fontTools.ufoLib.glifLib import (
10*e1fe3e4aSElliott Hughes    GlyphSet,
11*e1fe3e4aSElliott Hughes    glyphNameToFileName,
12*e1fe3e4aSElliott Hughes    readGlyphFromString,
13*e1fe3e4aSElliott Hughes    writeGlyphToString,
14*e1fe3e4aSElliott Hughes)
15*e1fe3e4aSElliott Hughesfrom fontTools.ufoLib.errors import (
16*e1fe3e4aSElliott Hughes    GlifLibError,
17*e1fe3e4aSElliott Hughes    UnsupportedGLIFFormat,
18*e1fe3e4aSElliott Hughes    UnsupportedUFOFormat,
19*e1fe3e4aSElliott Hughes)
20*e1fe3e4aSElliott Hughesfrom fontTools.misc.etree import XML_DECLARATION
21*e1fe3e4aSElliott Hughesfrom fontTools.pens.recordingPen import RecordingPointPen
22*e1fe3e4aSElliott Hughesimport pytest
23*e1fe3e4aSElliott Hughes
24*e1fe3e4aSElliott HughesGLYPHSETDIR = getDemoFontGlyphSetPath()
25*e1fe3e4aSElliott Hughes
26*e1fe3e4aSElliott Hughes
27*e1fe3e4aSElliott Hughesclass GlyphSetTests(unittest.TestCase):
28*e1fe3e4aSElliott Hughes    def setUp(self):
29*e1fe3e4aSElliott Hughes        self.dstDir = tempfile.mktemp()
30*e1fe3e4aSElliott Hughes        os.mkdir(self.dstDir)
31*e1fe3e4aSElliott Hughes
32*e1fe3e4aSElliott Hughes    def tearDown(self):
33*e1fe3e4aSElliott Hughes        shutil.rmtree(self.dstDir)
34*e1fe3e4aSElliott Hughes
35*e1fe3e4aSElliott Hughes    def testRoundTrip(self):
36*e1fe3e4aSElliott Hughes        import difflib
37*e1fe3e4aSElliott Hughes
38*e1fe3e4aSElliott Hughes        srcDir = GLYPHSETDIR
39*e1fe3e4aSElliott Hughes        dstDir = self.dstDir
40*e1fe3e4aSElliott Hughes        src = GlyphSet(
41*e1fe3e4aSElliott Hughes            srcDir, ufoFormatVersion=2, validateRead=True, validateWrite=True
42*e1fe3e4aSElliott Hughes        )
43*e1fe3e4aSElliott Hughes        dst = GlyphSet(
44*e1fe3e4aSElliott Hughes            dstDir, ufoFormatVersion=2, validateRead=True, validateWrite=True
45*e1fe3e4aSElliott Hughes        )
46*e1fe3e4aSElliott Hughes        for glyphName in src.keys():
47*e1fe3e4aSElliott Hughes            g = src[glyphName]
48*e1fe3e4aSElliott Hughes            g.drawPoints(None)  # load attrs
49*e1fe3e4aSElliott Hughes            dst.writeGlyph(glyphName, g, g.drawPoints)
50*e1fe3e4aSElliott Hughes        # compare raw file data:
51*e1fe3e4aSElliott Hughes        for glyphName in sorted(src.keys()):
52*e1fe3e4aSElliott Hughes            fileName = src.contents[glyphName]
53*e1fe3e4aSElliott Hughes            with open(os.path.join(srcDir, fileName), "r") as f:
54*e1fe3e4aSElliott Hughes                org = f.read()
55*e1fe3e4aSElliott Hughes            with open(os.path.join(dstDir, fileName), "r") as f:
56*e1fe3e4aSElliott Hughes                new = f.read()
57*e1fe3e4aSElliott Hughes            added = []
58*e1fe3e4aSElliott Hughes            removed = []
59*e1fe3e4aSElliott Hughes            for line in difflib.unified_diff(org.split("\n"), new.split("\n")):
60*e1fe3e4aSElliott Hughes                if line.startswith("+ "):
61*e1fe3e4aSElliott Hughes                    added.append(line[1:])
62*e1fe3e4aSElliott Hughes                elif line.startswith("- "):
63*e1fe3e4aSElliott Hughes                    removed.append(line[1:])
64*e1fe3e4aSElliott Hughes            self.assertEqual(
65*e1fe3e4aSElliott Hughes                added, removed, "%s.glif file differs after round tripping" % glyphName
66*e1fe3e4aSElliott Hughes            )
67*e1fe3e4aSElliott Hughes
68*e1fe3e4aSElliott Hughes    def testContentsExist(self):
69*e1fe3e4aSElliott Hughes        with self.assertRaises(GlifLibError):
70*e1fe3e4aSElliott Hughes            GlyphSet(
71*e1fe3e4aSElliott Hughes                self.dstDir,
72*e1fe3e4aSElliott Hughes                ufoFormatVersion=2,
73*e1fe3e4aSElliott Hughes                validateRead=True,
74*e1fe3e4aSElliott Hughes                validateWrite=True,
75*e1fe3e4aSElliott Hughes                expectContentsFile=True,
76*e1fe3e4aSElliott Hughes            )
77*e1fe3e4aSElliott Hughes
78*e1fe3e4aSElliott Hughes    def testRebuildContents(self):
79*e1fe3e4aSElliott Hughes        gset = GlyphSet(GLYPHSETDIR, validateRead=True, validateWrite=True)
80*e1fe3e4aSElliott Hughes        contents = gset.contents
81*e1fe3e4aSElliott Hughes        gset.rebuildContents()
82*e1fe3e4aSElliott Hughes        self.assertEqual(contents, gset.contents)
83*e1fe3e4aSElliott Hughes
84*e1fe3e4aSElliott Hughes    def testReverseContents(self):
85*e1fe3e4aSElliott Hughes        gset = GlyphSet(GLYPHSETDIR, validateRead=True, validateWrite=True)
86*e1fe3e4aSElliott Hughes        d = {}
87*e1fe3e4aSElliott Hughes        for k, v in gset.getReverseContents().items():
88*e1fe3e4aSElliott Hughes            d[v] = k
89*e1fe3e4aSElliott Hughes        org = {}
90*e1fe3e4aSElliott Hughes        for k, v in gset.contents.items():
91*e1fe3e4aSElliott Hughes            org[k] = v.lower()
92*e1fe3e4aSElliott Hughes        self.assertEqual(d, org)
93*e1fe3e4aSElliott Hughes
94*e1fe3e4aSElliott Hughes    def testReverseContents2(self):
95*e1fe3e4aSElliott Hughes        src = GlyphSet(GLYPHSETDIR, validateRead=True, validateWrite=True)
96*e1fe3e4aSElliott Hughes        dst = GlyphSet(self.dstDir, validateRead=True, validateWrite=True)
97*e1fe3e4aSElliott Hughes        dstMap = dst.getReverseContents()
98*e1fe3e4aSElliott Hughes        self.assertEqual(dstMap, {})
99*e1fe3e4aSElliott Hughes        for glyphName in src.keys():
100*e1fe3e4aSElliott Hughes            g = src[glyphName]
101*e1fe3e4aSElliott Hughes            g.drawPoints(None)  # load attrs
102*e1fe3e4aSElliott Hughes            dst.writeGlyph(glyphName, g, g.drawPoints)
103*e1fe3e4aSElliott Hughes        self.assertNotEqual(dstMap, {})
104*e1fe3e4aSElliott Hughes        srcMap = dict(src.getReverseContents())  # copy
105*e1fe3e4aSElliott Hughes        self.assertEqual(dstMap, srcMap)
106*e1fe3e4aSElliott Hughes        del srcMap["a.glif"]
107*e1fe3e4aSElliott Hughes        dst.deleteGlyph("a")
108*e1fe3e4aSElliott Hughes        self.assertEqual(dstMap, srcMap)
109*e1fe3e4aSElliott Hughes
110*e1fe3e4aSElliott Hughes    def testCustomFileNamingScheme(self):
111*e1fe3e4aSElliott Hughes        def myGlyphNameToFileName(glyphName, glyphSet):
112*e1fe3e4aSElliott Hughes            return "prefix" + glyphNameToFileName(glyphName, glyphSet)
113*e1fe3e4aSElliott Hughes
114*e1fe3e4aSElliott Hughes        src = GlyphSet(GLYPHSETDIR, validateRead=True, validateWrite=True)
115*e1fe3e4aSElliott Hughes        dst = GlyphSet(
116*e1fe3e4aSElliott Hughes            self.dstDir, myGlyphNameToFileName, validateRead=True, validateWrite=True
117*e1fe3e4aSElliott Hughes        )
118*e1fe3e4aSElliott Hughes        for glyphName in src.keys():
119*e1fe3e4aSElliott Hughes            g = src[glyphName]
120*e1fe3e4aSElliott Hughes            g.drawPoints(None)  # load attrs
121*e1fe3e4aSElliott Hughes            dst.writeGlyph(glyphName, g, g.drawPoints)
122*e1fe3e4aSElliott Hughes        d = {}
123*e1fe3e4aSElliott Hughes        for k, v in src.contents.items():
124*e1fe3e4aSElliott Hughes            d[k] = "prefix" + v
125*e1fe3e4aSElliott Hughes        self.assertEqual(d, dst.contents)
126*e1fe3e4aSElliott Hughes
127*e1fe3e4aSElliott Hughes    def testGetUnicodes(self):
128*e1fe3e4aSElliott Hughes        src = GlyphSet(GLYPHSETDIR, validateRead=True, validateWrite=True)
129*e1fe3e4aSElliott Hughes        unicodes = src.getUnicodes()
130*e1fe3e4aSElliott Hughes        for glyphName in src.keys():
131*e1fe3e4aSElliott Hughes            g = src[glyphName]
132*e1fe3e4aSElliott Hughes            g.drawPoints(None)  # load attrs
133*e1fe3e4aSElliott Hughes            if not hasattr(g, "unicodes"):
134*e1fe3e4aSElliott Hughes                self.assertEqual(unicodes[glyphName], [])
135*e1fe3e4aSElliott Hughes            else:
136*e1fe3e4aSElliott Hughes                self.assertEqual(g.unicodes, unicodes[glyphName])
137*e1fe3e4aSElliott Hughes
138*e1fe3e4aSElliott Hughes    def testReadGlyphInvalidXml(self):
139*e1fe3e4aSElliott Hughes        """Test that calling readGlyph() to read a .glif with invalid XML raises
140*e1fe3e4aSElliott Hughes        a library error, instead of an exception from the XML dependency that is
141*e1fe3e4aSElliott Hughes        used internally. In addition, check that the raised exception describes
142*e1fe3e4aSElliott Hughes        the glyph by name and gives the location of the broken .glif file."""
143*e1fe3e4aSElliott Hughes
144*e1fe3e4aSElliott Hughes        # Create a glyph set with three empty glyphs.
145*e1fe3e4aSElliott Hughes        glyph_set = GlyphSet(self.dstDir)
146*e1fe3e4aSElliott Hughes        glyph_set.writeGlyph("a", _Glyph())
147*e1fe3e4aSElliott Hughes        glyph_set.writeGlyph("b", _Glyph())
148*e1fe3e4aSElliott Hughes        glyph_set.writeGlyph("c", _Glyph())
149*e1fe3e4aSElliott Hughes
150*e1fe3e4aSElliott Hughes        # Corrupt the XML of /c.
151*e1fe3e4aSElliott Hughes        invalid_xml = b"<abc></def>"
152*e1fe3e4aSElliott Hughes        Path(self.dstDir, glyph_set.contents["c"]).write_bytes(invalid_xml)
153*e1fe3e4aSElliott Hughes
154*e1fe3e4aSElliott Hughes        # Confirm that reading /a and /b is fine...
155*e1fe3e4aSElliott Hughes        glyph_set.readGlyph("a", _Glyph())
156*e1fe3e4aSElliott Hughes        glyph_set.readGlyph("b", _Glyph())
157*e1fe3e4aSElliott Hughes
158*e1fe3e4aSElliott Hughes        # ...but that reading /c raises a descriptive library error.
159*e1fe3e4aSElliott Hughes        expected_message = (
160*e1fe3e4aSElliott Hughes            r"GLIF contains invalid XML\.\n"
161*e1fe3e4aSElliott Hughes            r"The issue is in glyph 'c', located in '.*c\.glif.*\."
162*e1fe3e4aSElliott Hughes        )
163*e1fe3e4aSElliott Hughes        with pytest.raises(GlifLibError, match=expected_message):
164*e1fe3e4aSElliott Hughes            glyph_set.readGlyph("c", _Glyph())
165*e1fe3e4aSElliott Hughes
166*e1fe3e4aSElliott Hughes
167*e1fe3e4aSElliott Hughesclass FileNameTest:
168*e1fe3e4aSElliott Hughes    def test_default_file_name_scheme(self):
169*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("a", None) == "a.glif"
170*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("A", None) == "A_.glif"
171*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("Aring", None) == "A_ring.glif"
172*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("F_A_B", None) == "F__A__B_.glif"
173*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("A.alt", None) == "A_.alt.glif"
174*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("A.Alt", None) == "A_.A_lt.glif"
175*e1fe3e4aSElliott Hughes        assert glyphNameToFileName(".notdef", None) == "_notdef.glif"
176*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("T_H", None) == "T__H_.glif"
177*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("T_h", None) == "T__h.glif"
178*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("t_h", None) == "t_h.glif"
179*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("F_F_I", None) == "F__F__I_.glif"
180*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("f_f_i", None) == "f_f_i.glif"
181*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("AE", None) == "A_E_.glif"
182*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("Ae", None) == "A_e.glif"
183*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("ae", None) == "ae.glif"
184*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("aE", None) == "aE_.glif"
185*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("a.alt", None) == "a.alt.glif"
186*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("A.aLt", None) == "A_.aL_t.glif"
187*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("A.alT", None) == "A_.alT_.glif"
188*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("Aacute_V.swash", None) == "A_acute_V_.swash.glif"
189*e1fe3e4aSElliott Hughes        assert glyphNameToFileName(".notdef", None) == "_notdef.glif"
190*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("con", None) == "_con.glif"
191*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("CON", None) == "C_O_N_.glif"
192*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("con.alt", None) == "_con.alt.glif"
193*e1fe3e4aSElliott Hughes        assert glyphNameToFileName("alt.con", None) == "alt._con.glif"
194*e1fe3e4aSElliott Hughes
195*e1fe3e4aSElliott Hughes    def test_conflicting_case_insensitive_file_names(self, tmp_path):
196*e1fe3e4aSElliott Hughes        src = GlyphSet(GLYPHSETDIR)
197*e1fe3e4aSElliott Hughes        dst = GlyphSet(tmp_path)
198*e1fe3e4aSElliott Hughes        glyph = src["a"]
199*e1fe3e4aSElliott Hughes
200*e1fe3e4aSElliott Hughes        dst.writeGlyph("a", glyph)
201*e1fe3e4aSElliott Hughes        dst.writeGlyph("A", glyph)
202*e1fe3e4aSElliott Hughes        dst.writeGlyph("a_", glyph)
203*e1fe3e4aSElliott Hughes        dst.deleteGlyph("a_")
204*e1fe3e4aSElliott Hughes        dst.writeGlyph("a_", glyph)
205*e1fe3e4aSElliott Hughes        dst.writeGlyph("A_", glyph)
206*e1fe3e4aSElliott Hughes        dst.writeGlyph("i_j", glyph)
207*e1fe3e4aSElliott Hughes
208*e1fe3e4aSElliott Hughes        assert dst.contents == {
209*e1fe3e4aSElliott Hughes            "a": "a.glif",
210*e1fe3e4aSElliott Hughes            "A": "A_.glif",
211*e1fe3e4aSElliott Hughes            "a_": "a_000000000000001.glif",
212*e1fe3e4aSElliott Hughes            "A_": "A__.glif",
213*e1fe3e4aSElliott Hughes            "i_j": "i_j.glif",
214*e1fe3e4aSElliott Hughes        }
215*e1fe3e4aSElliott Hughes
216*e1fe3e4aSElliott Hughes        # make sure filenames are unique even on case-insensitive filesystems
217*e1fe3e4aSElliott Hughes        assert len({fileName.lower() for fileName in dst.contents.values()}) == 5
218*e1fe3e4aSElliott Hughes
219*e1fe3e4aSElliott Hughes
220*e1fe3e4aSElliott Hughesclass _Glyph:
221*e1fe3e4aSElliott Hughes    pass
222*e1fe3e4aSElliott Hughes
223*e1fe3e4aSElliott Hughes
224*e1fe3e4aSElliott Hughesclass ReadWriteFuncTest:
225*e1fe3e4aSElliott Hughes    def test_roundtrip(self):
226*e1fe3e4aSElliott Hughes        glyph = _Glyph()
227*e1fe3e4aSElliott Hughes        glyph.name = "a"
228*e1fe3e4aSElliott Hughes        glyph.unicodes = [0x0061]
229*e1fe3e4aSElliott Hughes
230*e1fe3e4aSElliott Hughes        s1 = writeGlyphToString(glyph.name, glyph)
231*e1fe3e4aSElliott Hughes
232*e1fe3e4aSElliott Hughes        glyph2 = _Glyph()
233*e1fe3e4aSElliott Hughes        readGlyphFromString(s1, glyph2)
234*e1fe3e4aSElliott Hughes        assert glyph.__dict__ == glyph2.__dict__
235*e1fe3e4aSElliott Hughes
236*e1fe3e4aSElliott Hughes        s2 = writeGlyphToString(glyph2.name, glyph2)
237*e1fe3e4aSElliott Hughes        assert s1 == s2
238*e1fe3e4aSElliott Hughes
239*e1fe3e4aSElliott Hughes    def test_xml_declaration(self):
240*e1fe3e4aSElliott Hughes        s = writeGlyphToString("a", _Glyph())
241*e1fe3e4aSElliott Hughes        assert s.startswith(XML_DECLARATION % "UTF-8")
242*e1fe3e4aSElliott Hughes
243*e1fe3e4aSElliott Hughes    def test_parse_xml_remove_comments(self):
244*e1fe3e4aSElliott Hughes        s = b"""<?xml version='1.0' encoding='UTF-8'?>
245*e1fe3e4aSElliott Hughes		<!-- a comment -->
246*e1fe3e4aSElliott Hughes		<glyph name="A" format="2">
247*e1fe3e4aSElliott Hughes			<advance width="1290"/>
248*e1fe3e4aSElliott Hughes			<unicode hex="0041"/>
249*e1fe3e4aSElliott Hughes			<!-- another comment -->
250*e1fe3e4aSElliott Hughes		</glyph>
251*e1fe3e4aSElliott Hughes		"""
252*e1fe3e4aSElliott Hughes
253*e1fe3e4aSElliott Hughes        g = _Glyph()
254*e1fe3e4aSElliott Hughes        readGlyphFromString(s, g)
255*e1fe3e4aSElliott Hughes
256*e1fe3e4aSElliott Hughes        assert g.name == "A"
257*e1fe3e4aSElliott Hughes        assert g.width == 1290
258*e1fe3e4aSElliott Hughes        assert g.unicodes == [0x0041]
259*e1fe3e4aSElliott Hughes
260*e1fe3e4aSElliott Hughes    def test_read_invalid_xml(self):
261*e1fe3e4aSElliott Hughes        """Test that calling readGlyphFromString() with invalid XML raises a
262*e1fe3e4aSElliott Hughes        library error, instead of an exception from the XML dependency that is
263*e1fe3e4aSElliott Hughes        used internally."""
264*e1fe3e4aSElliott Hughes
265*e1fe3e4aSElliott Hughes        invalid_xml = b"<abc></def>"
266*e1fe3e4aSElliott Hughes        empty_glyph = _Glyph()
267*e1fe3e4aSElliott Hughes
268*e1fe3e4aSElliott Hughes        with pytest.raises(GlifLibError, match="GLIF contains invalid XML"):
269*e1fe3e4aSElliott Hughes            readGlyphFromString(invalid_xml, empty_glyph)
270*e1fe3e4aSElliott Hughes
271*e1fe3e4aSElliott Hughes    def test_read_unsupported_format_version(self, caplog):
272*e1fe3e4aSElliott Hughes        s = """<?xml version='1.0' encoding='utf-8'?>
273*e1fe3e4aSElliott Hughes		<glyph name="A" format="0" formatMinor="0">
274*e1fe3e4aSElliott Hughes			<advance width="500"/>
275*e1fe3e4aSElliott Hughes			<unicode hex="0041"/>
276*e1fe3e4aSElliott Hughes		</glyph>
277*e1fe3e4aSElliott Hughes		"""
278*e1fe3e4aSElliott Hughes
279*e1fe3e4aSElliott Hughes        with pytest.raises(UnsupportedGLIFFormat):
280*e1fe3e4aSElliott Hughes            readGlyphFromString(s, _Glyph())  # validate=True by default
281*e1fe3e4aSElliott Hughes
282*e1fe3e4aSElliott Hughes        with pytest.raises(UnsupportedGLIFFormat):
283*e1fe3e4aSElliott Hughes            readGlyphFromString(s, _Glyph(), validate=True)
284*e1fe3e4aSElliott Hughes
285*e1fe3e4aSElliott Hughes        caplog.clear()
286*e1fe3e4aSElliott Hughes        with caplog.at_level(logging.WARNING, logger="fontTools.ufoLib.glifLib"):
287*e1fe3e4aSElliott Hughes            readGlyphFromString(s, _Glyph(), validate=False)
288*e1fe3e4aSElliott Hughes
289*e1fe3e4aSElliott Hughes        assert len(caplog.records) == 1
290*e1fe3e4aSElliott Hughes        assert "Unsupported GLIF format" in caplog.text
291*e1fe3e4aSElliott Hughes        assert "Assuming the latest supported version" in caplog.text
292*e1fe3e4aSElliott Hughes
293*e1fe3e4aSElliott Hughes    def test_read_allow_format_versions(self):
294*e1fe3e4aSElliott Hughes        s = """<?xml version='1.0' encoding='utf-8'?>
295*e1fe3e4aSElliott Hughes		<glyph name="A" format="2">
296*e1fe3e4aSElliott Hughes			<advance width="500"/>
297*e1fe3e4aSElliott Hughes			<unicode hex="0041"/>
298*e1fe3e4aSElliott Hughes		</glyph>
299*e1fe3e4aSElliott Hughes		"""
300*e1fe3e4aSElliott Hughes
301*e1fe3e4aSElliott Hughes        # these two calls are are equivalent
302*e1fe3e4aSElliott Hughes        readGlyphFromString(s, _Glyph(), formatVersions=[1, 2])
303*e1fe3e4aSElliott Hughes        readGlyphFromString(s, _Glyph(), formatVersions=[(1, 0), (2, 0)])
304*e1fe3e4aSElliott Hughes
305*e1fe3e4aSElliott Hughes        # if at least one supported formatVersion, unsupported ones are ignored
306*e1fe3e4aSElliott Hughes        readGlyphFromString(s, _Glyph(), formatVersions=[(2, 0), (123, 456)])
307*e1fe3e4aSElliott Hughes
308*e1fe3e4aSElliott Hughes        with pytest.raises(
309*e1fe3e4aSElliott Hughes            ValueError, match="None of the requested GLIF formatVersions are supported"
310*e1fe3e4aSElliott Hughes        ):
311*e1fe3e4aSElliott Hughes            readGlyphFromString(s, _Glyph(), formatVersions=[0, 2001])
312*e1fe3e4aSElliott Hughes
313*e1fe3e4aSElliott Hughes        with pytest.raises(GlifLibError, match="Forbidden GLIF format version"):
314*e1fe3e4aSElliott Hughes            readGlyphFromString(s, _Glyph(), formatVersions=[1])
315*e1fe3e4aSElliott Hughes
316*e1fe3e4aSElliott Hughes    def test_read_ensure_x_y(self):
317*e1fe3e4aSElliott Hughes        """Ensure that a proper GlifLibError is raised when point coordinates are
318*e1fe3e4aSElliott Hughes        missing, regardless of validation setting."""
319*e1fe3e4aSElliott Hughes
320*e1fe3e4aSElliott Hughes        s = """<?xml version='1.0' encoding='utf-8'?>
321*e1fe3e4aSElliott Hughes		<glyph name="A" format="2">
322*e1fe3e4aSElliott Hughes			<outline>
323*e1fe3e4aSElliott Hughes				<contour>
324*e1fe3e4aSElliott Hughes					<point x="545" y="0" type="line"/>
325*e1fe3e4aSElliott Hughes					<point x="638" type="line"/>
326*e1fe3e4aSElliott Hughes				</contour>
327*e1fe3e4aSElliott Hughes			</outline>
328*e1fe3e4aSElliott Hughes		</glyph>
329*e1fe3e4aSElliott Hughes		"""
330*e1fe3e4aSElliott Hughes        pen = RecordingPointPen()
331*e1fe3e4aSElliott Hughes
332*e1fe3e4aSElliott Hughes        with pytest.raises(GlifLibError, match="Required y attribute"):
333*e1fe3e4aSElliott Hughes            readGlyphFromString(s, _Glyph(), pen)
334*e1fe3e4aSElliott Hughes
335*e1fe3e4aSElliott Hughes        with pytest.raises(GlifLibError, match="Required y attribute"):
336*e1fe3e4aSElliott Hughes            readGlyphFromString(s, _Glyph(), pen, validate=False)
337*e1fe3e4aSElliott Hughes
338*e1fe3e4aSElliott Hughes
339*e1fe3e4aSElliott Hughesdef test_GlyphSet_unsupported_ufoFormatVersion(tmp_path, caplog):
340*e1fe3e4aSElliott Hughes    with pytest.raises(UnsupportedUFOFormat):
341*e1fe3e4aSElliott Hughes        GlyphSet(tmp_path, ufoFormatVersion=0)
342*e1fe3e4aSElliott Hughes    with pytest.raises(UnsupportedUFOFormat):
343*e1fe3e4aSElliott Hughes        GlyphSet(tmp_path, ufoFormatVersion=(0, 1))
344*e1fe3e4aSElliott Hughes
345*e1fe3e4aSElliott Hughes
346*e1fe3e4aSElliott Hughesdef test_GlyphSet_writeGlyph_formatVersion(tmp_path):
347*e1fe3e4aSElliott Hughes    src = GlyphSet(GLYPHSETDIR)
348*e1fe3e4aSElliott Hughes    dst = GlyphSet(tmp_path, ufoFormatVersion=(2, 0))
349*e1fe3e4aSElliott Hughes    glyph = src["A"]
350*e1fe3e4aSElliott Hughes
351*e1fe3e4aSElliott Hughes    # no explicit formatVersion passed: use the more recent GLIF formatVersion
352*e1fe3e4aSElliott Hughes    # that is supported by given ufoFormatVersion (GLIF 1 for UFO 2)
353*e1fe3e4aSElliott Hughes    dst.writeGlyph("A", glyph)
354*e1fe3e4aSElliott Hughes    glif = dst.getGLIF("A")
355*e1fe3e4aSElliott Hughes    assert b'format="1"' in glif
356*e1fe3e4aSElliott Hughes    assert b"formatMinor" not in glif  # omitted when 0
357*e1fe3e4aSElliott Hughes
358*e1fe3e4aSElliott Hughes    # explicit, unknown formatVersion
359*e1fe3e4aSElliott Hughes    with pytest.raises(UnsupportedGLIFFormat):
360*e1fe3e4aSElliott Hughes        dst.writeGlyph("A", glyph, formatVersion=(0, 0))
361*e1fe3e4aSElliott Hughes
362*e1fe3e4aSElliott Hughes    # explicit, known formatVersion but unsupported by given ufoFormatVersion
363*e1fe3e4aSElliott Hughes    with pytest.raises(
364*e1fe3e4aSElliott Hughes        UnsupportedGLIFFormat,
365*e1fe3e4aSElliott Hughes        match="Unsupported GLIF format version .*for UFO format version",
366*e1fe3e4aSElliott Hughes    ):
367*e1fe3e4aSElliott Hughes        dst.writeGlyph("A", glyph, formatVersion=(2, 0))
368