From 863c9de57c0e3ee6856f5be4e58ba94e640ef5f5 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Mon, 15 Nov 2021 17:05:40 +0000 Subject: [PATCH] subset/svg_test: test more complex document with cross-references --- Tests/subset/svg_test.py | 342 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 342 insertions(+) diff --git a/Tests/subset/svg_test.py b/Tests/subset/svg_test.py index acaeb57db..bdfc4d68c 100644 --- a/Tests/subset/svg_test.py +++ b/Tests/subset/svg_test.py @@ -1,4 +1,5 @@ from string import ascii_letters +import textwrap from fontTools.misc.testTools import getXML from fontTools import subset @@ -111,3 +112,344 @@ def test_subset_svg_simple(empty_svg_font, tmp_path): ' ]]>', "", ] + + +# This contains a bunch of cross-references between glyphs, paths, gradients, etc. +# Note the path coordinates are completely made up and not meant to be rendered. +# We only care about the tree structure, not it's visual content. +COMPLEX_SVG = """\ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + + +def _lines(s): + return textwrap.dedent(s).splitlines() + + +@pytest.mark.parametrize( + "subset_gids, expected_xml", + [ + # we only keep gid=2, with 'glyph2' defined inside 'glyph1': 'glyph2' + # is renamed 'glyph1' to match the new subset indices, and the old 'glyph1' + # is kept (as it contains 'glyph2') but renamed '.glyph1' to avoid clash + ( + "2", + _lines( + """\ + + + + + + + + + ]]> + + """ + ), + ), + # we keep both gid 1 and 2: the glyph elements' ids stay as they are (only the + # range endGlyphID change); a gradient is kept since it's referenced by glyph1 + ( + "1,2", + _lines( + """\ + + + + + + + + + + + + + + + + + + + ]]> + + """ + ), + ), + ( + # both gid 3 and 6 refer (via ; the glyph ids and range start/end are renumbered. + "3,6", + _lines( + """\ + + + + + + + + + + + + + ]]> + + """ + ), + ), + ( + # 'glyph4' uses the whole 'glyph1' element (translated); we keep the latter + # renamed to avoid clashes with new gids + "3-4", + _lines( + """\ + + + + + + + + + + + + + + + + + + + + + + + + ]]> + + """ + ), + ), + ( + # 'glyph9' uses a path 'p2' defined inside 'glyph8', the latter is excluded + # from our subset, as such its id is renamed with a '.' prefix; but (edge + # case!) there already is an id=".glyph8" in the doc (for whatever reason, + # it's still a valid id), so we append a .{digit} suffix to disambiguate. + "7,9", + _lines( + """\ + + + + + + + + + + + + + + + + + ]]> + + """ + ), + ), + ( + # 'glyph11' uses gradient 'rg4' which inherits from 'rg3', which inherits + # from 'rg2', etc. + "11", + _lines( + """\ + + + + + + + + + + + + + + + ]]> + + """ + ), + ), + ( + # 'glyph12' contains a style attribute with inline CSS declarations that + # contains references to a gradient fill and a clipPath: we keep those + "12", + _lines( + """\ + + + + + + + + + + + + + + + + ]]> + + """ + ), + ), + ], +) +def test_subset_svg_with_references( + empty_svg_font, tmp_path, subset_gids, expected_xml +): + font = empty_svg_font + + font["SVG "].docList.append((COMPLEX_SVG, 1, 12)) + svg_font_path = tmp_path / "TestSVG.ttf" + font.save(svg_font_path) + subset_path = svg_font_path.with_suffix(".subset.ttf") + + subset.main( + [ + str(svg_font_path), + f"--output-file={subset_path}", + f"--gids={subset_gids}", + "--pretty-svg", + ] + ) + subset_font = TTFont(subset_path) + + if expected_xml is not None: + assert getXML(subset_font["SVG "].toXML, subset_font) == expected_xml + else: + assert "SVG " not in subset_font + + +def test_subset_svg_empty_table(empty_svg_font, tmp_path): + font = empty_svg_font + + svg = new_svg() + etree.SubElement(svg, "rect", {"id": "glyph1", "x": "1", "y": "2"}) + font["SVG "].docList.append((etree.tostring(svg).decode(), 1, 1)) + + svg_font_path = tmp_path / "TestSVG.ttf" + font.save(svg_font_path) + subset_path = svg_font_path.with_suffix(".subset.ttf") + + # there's no gid=2 in SVG table, drop the empty table + subset.main([str(svg_font_path), f"--output-file={subset_path}", f"--gids=2"]) + + assert "SVG " not in TTFont(subset_path) + + +def test_subset_svg_missing_glyph(empty_svg_font, tmp_path): + font = empty_svg_font + + svg = new_svg() + etree.SubElement(svg, "rect", {"id": "glyph1", "x": "1", "y": "2"}) + font["SVG "].docList.append( + ( + etree.tostring(svg).decode(), + 1, + # the range endGlyphID=2 declares two glyphs however our svg contains + # only one glyph element with id="glyph1", the "glyph2" one is absent. + # Techically this would be invalid according to the OT-SVG spec. + 2, + ) + ) + svg_font_path = tmp_path / "TestSVG.ttf" + font.save(svg_font_path) + subset_path = svg_font_path.with_suffix(".subset.ttf") + + # make sure we don't crash when we don't find the expected "glyph2" element + subset.main([str(svg_font_path), f"--output-file={subset_path}", f"--gids=1"]) + + subset_font = TTFont(subset_path) + assert getXML(subset_font["SVG "].toXML, subset_font) == [ + '', + ' ]]>', + "", + ] + + # ignore the missing gid even if included in the subset; in this test case we + # end up with an empty svg document--which is dropped, along with the empty table + subset.main([str(svg_font_path), f"--output-file={subset_path}", f"--gids=2"]) + + assert "SVG " not in TTFont(subset_path)