xref: /aosp_15_r20/external/fonttools/Tests/subset/svg_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesfrom string import ascii_letters
2*e1fe3e4aSElliott Hughesimport textwrap
3*e1fe3e4aSElliott Hughes
4*e1fe3e4aSElliott Hughesfrom fontTools.misc.testTools import getXML
5*e1fe3e4aSElliott Hughesfrom fontTools import subset
6*e1fe3e4aSElliott Hughesfrom fontTools.fontBuilder import FontBuilder
7*e1fe3e4aSElliott Hughesfrom fontTools.pens.ttGlyphPen import TTGlyphPen
8*e1fe3e4aSElliott Hughesfrom fontTools.ttLib import TTFont, newTable
9*e1fe3e4aSElliott Hughesfrom fontTools.subset.svg import NAMESPACES, ranges
10*e1fe3e4aSElliott Hughes
11*e1fe3e4aSElliott Hughesimport pytest
12*e1fe3e4aSElliott Hughes
13*e1fe3e4aSElliott Hughesetree = pytest.importorskip("lxml.etree")
14*e1fe3e4aSElliott Hughes
15*e1fe3e4aSElliott Hughes
16*e1fe3e4aSElliott Hughes@pytest.fixture
17*e1fe3e4aSElliott Hughesdef empty_svg_font():
18*e1fe3e4aSElliott Hughes    glyph_order = [".notdef"] + list(ascii_letters)
19*e1fe3e4aSElliott Hughes
20*e1fe3e4aSElliott Hughes    pen = TTGlyphPen(glyphSet=None)
21*e1fe3e4aSElliott Hughes    pen.moveTo((0, 0))
22*e1fe3e4aSElliott Hughes    pen.lineTo((0, 500))
23*e1fe3e4aSElliott Hughes    pen.lineTo((500, 500))
24*e1fe3e4aSElliott Hughes    pen.lineTo((500, 0))
25*e1fe3e4aSElliott Hughes    pen.closePath()
26*e1fe3e4aSElliott Hughes    glyph = pen.glyph()
27*e1fe3e4aSElliott Hughes    glyphs = {g: glyph for g in glyph_order}
28*e1fe3e4aSElliott Hughes
29*e1fe3e4aSElliott Hughes    fb = FontBuilder(unitsPerEm=1024, isTTF=True)
30*e1fe3e4aSElliott Hughes    fb.setupGlyphOrder(glyph_order)
31*e1fe3e4aSElliott Hughes    fb.setupCharacterMap({ord(c): c for c in ascii_letters})
32*e1fe3e4aSElliott Hughes    fb.setupGlyf(glyphs)
33*e1fe3e4aSElliott Hughes    fb.setupHorizontalMetrics({g: (500, 0) for g in glyph_order})
34*e1fe3e4aSElliott Hughes    fb.setupHorizontalHeader()
35*e1fe3e4aSElliott Hughes    fb.setupOS2()
36*e1fe3e4aSElliott Hughes    fb.setupPost()
37*e1fe3e4aSElliott Hughes    fb.setupNameTable({"familyName": "TestSVG", "styleName": "Regular"})
38*e1fe3e4aSElliott Hughes
39*e1fe3e4aSElliott Hughes    svg_table = newTable("SVG ")
40*e1fe3e4aSElliott Hughes    svg_table.docList = []
41*e1fe3e4aSElliott Hughes    fb.font["SVG "] = svg_table
42*e1fe3e4aSElliott Hughes
43*e1fe3e4aSElliott Hughes    return fb.font
44*e1fe3e4aSElliott Hughes
45*e1fe3e4aSElliott Hughes
46*e1fe3e4aSElliott Hughes# 'simple' here means one svg document per glyph. The required 'id' attribute
47*e1fe3e4aSElliott Hughes# containing the 'glyphXXX' indices can be either on a child of the root <svg>
48*e1fe3e4aSElliott Hughes# or on the <svg> root itself, so we test with both.
49*e1fe3e4aSElliott Hughes# see https://github.com/fonttools/fonttools/issues/2548
50*e1fe3e4aSElliott Hughes
51*e1fe3e4aSElliott Hughes
52*e1fe3e4aSElliott Hughesdef simple_svg_table_glyph_ids_on_children(empty_svg_font):
53*e1fe3e4aSElliott Hughes    font = empty_svg_font
54*e1fe3e4aSElliott Hughes    svg_docs = font["SVG "].docList
55*e1fe3e4aSElliott Hughes    for i in range(1, 11):
56*e1fe3e4aSElliott Hughes        svg = new_svg()
57*e1fe3e4aSElliott Hughes        etree.SubElement(svg, "path", {"id": f"glyph{i}", "d": f"M{i},{i}"})
58*e1fe3e4aSElliott Hughes        svg_docs.append((etree.tostring(svg).decode(), i, i))
59*e1fe3e4aSElliott Hughes    return font
60*e1fe3e4aSElliott Hughes
61*e1fe3e4aSElliott Hughes
62*e1fe3e4aSElliott Hughesdef simple_svg_table_glyph_ids_on_roots(empty_svg_font):
63*e1fe3e4aSElliott Hughes    font = empty_svg_font
64*e1fe3e4aSElliott Hughes    svg_docs = font["SVG "].docList
65*e1fe3e4aSElliott Hughes    for i in range(1, 11):
66*e1fe3e4aSElliott Hughes        svg = new_svg(id=f"glyph{i}")
67*e1fe3e4aSElliott Hughes        etree.SubElement(svg, "path", {"d": f"M{i},{i}"})
68*e1fe3e4aSElliott Hughes        svg_docs.append((etree.tostring(svg).decode(), i, i))
69*e1fe3e4aSElliott Hughes    return font
70*e1fe3e4aSElliott Hughes
71*e1fe3e4aSElliott Hughes
72*e1fe3e4aSElliott Hughesdef new_svg(**attrs):
73*e1fe3e4aSElliott Hughes    return etree.Element("svg", {"xmlns": NAMESPACES["svg"], **attrs})
74*e1fe3e4aSElliott Hughes
75*e1fe3e4aSElliott Hughes
76*e1fe3e4aSElliott Hughesdef _lines(s):
77*e1fe3e4aSElliott Hughes    return textwrap.dedent(s).splitlines()
78*e1fe3e4aSElliott Hughes
79*e1fe3e4aSElliott Hughes
80*e1fe3e4aSElliott Hughes@pytest.mark.parametrize(
81*e1fe3e4aSElliott Hughes    "add_svg_table, gids, retain_gids, expected_xml",
82*e1fe3e4aSElliott Hughes    [
83*e1fe3e4aSElliott Hughes        # keep four glyphs in total, don't retain gids, which thus get remapped
84*e1fe3e4aSElliott Hughes        (
85*e1fe3e4aSElliott Hughes            simple_svg_table_glyph_ids_on_children,
86*e1fe3e4aSElliott Hughes            "2,4-6",
87*e1fe3e4aSElliott Hughes            False,
88*e1fe3e4aSElliott Hughes            _lines(
89*e1fe3e4aSElliott Hughes                """\
90*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="1" startGlyphID="1">
91*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg"><path id="glyph1" d="M2,2"/></svg>]]>
92*e1fe3e4aSElliott Hughes                </svgDoc>
93*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="2" startGlyphID="2">
94*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg"><path id="glyph2" d="M4,4"/></svg>]]>
95*e1fe3e4aSElliott Hughes                </svgDoc>
96*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="3" startGlyphID="3">
97*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg"><path id="glyph3" d="M5,5"/></svg>]]>
98*e1fe3e4aSElliott Hughes                </svgDoc>
99*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="4" startGlyphID="4">
100*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg"><path id="glyph4" d="M6,6"/></svg>]]>
101*e1fe3e4aSElliott Hughes                </svgDoc>
102*e1fe3e4aSElliott Hughes                """
103*e1fe3e4aSElliott Hughes            ),
104*e1fe3e4aSElliott Hughes        ),
105*e1fe3e4aSElliott Hughes        # same as above but with glyph id attribute in the root <svg> element itself
106*e1fe3e4aSElliott Hughes        # https://github.com/fonttools/fonttools/issues/2548
107*e1fe3e4aSElliott Hughes        (
108*e1fe3e4aSElliott Hughes            simple_svg_table_glyph_ids_on_roots,
109*e1fe3e4aSElliott Hughes            "2,4-6",
110*e1fe3e4aSElliott Hughes            False,
111*e1fe3e4aSElliott Hughes            _lines(
112*e1fe3e4aSElliott Hughes                """\
113*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="1" startGlyphID="1">
114*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg" id="glyph1"><path d="M2,2"/></svg>]]>
115*e1fe3e4aSElliott Hughes                </svgDoc>
116*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="2" startGlyphID="2">
117*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg" id="glyph2"><path d="M4,4"/></svg>]]>
118*e1fe3e4aSElliott Hughes                </svgDoc>
119*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="3" startGlyphID="3">
120*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg" id="glyph3"><path d="M5,5"/></svg>]]>
121*e1fe3e4aSElliott Hughes                </svgDoc>
122*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="4" startGlyphID="4">
123*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg" id="glyph4"><path d="M6,6"/></svg>]]>
124*e1fe3e4aSElliott Hughes                </svgDoc>
125*e1fe3e4aSElliott Hughes                """
126*e1fe3e4aSElliott Hughes            ),
127*e1fe3e4aSElliott Hughes        ),
128*e1fe3e4aSElliott Hughes        # same four glyphs, but we now retain gids
129*e1fe3e4aSElliott Hughes        (
130*e1fe3e4aSElliott Hughes            simple_svg_table_glyph_ids_on_children,
131*e1fe3e4aSElliott Hughes            "2,4-6",
132*e1fe3e4aSElliott Hughes            True,
133*e1fe3e4aSElliott Hughes            _lines(
134*e1fe3e4aSElliott Hughes                """\
135*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="2" startGlyphID="2">
136*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg"><path id="glyph2" d="M2,2"/></svg>]]>
137*e1fe3e4aSElliott Hughes                </svgDoc>
138*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="4" startGlyphID="4">
139*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg"><path id="glyph4" d="M4,4"/></svg>]]>
140*e1fe3e4aSElliott Hughes                </svgDoc>
141*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="5" startGlyphID="5">
142*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg"><path id="glyph5" d="M5,5"/></svg>]]>
143*e1fe3e4aSElliott Hughes                </svgDoc>
144*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="6" startGlyphID="6">
145*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg"><path id="glyph6" d="M6,6"/></svg>]]>
146*e1fe3e4aSElliott Hughes                </svgDoc>
147*e1fe3e4aSElliott Hughes                """
148*e1fe3e4aSElliott Hughes            ),
149*e1fe3e4aSElliott Hughes        ),
150*e1fe3e4aSElliott Hughes        # retain gids like above but with glyph id attribute in the root <svg> element itself
151*e1fe3e4aSElliott Hughes        # https://github.com/fonttools/fonttools/issues/2548
152*e1fe3e4aSElliott Hughes        (
153*e1fe3e4aSElliott Hughes            simple_svg_table_glyph_ids_on_roots,
154*e1fe3e4aSElliott Hughes            "2,4-6",
155*e1fe3e4aSElliott Hughes            True,
156*e1fe3e4aSElliott Hughes            _lines(
157*e1fe3e4aSElliott Hughes                """\
158*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="2" startGlyphID="2">
159*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg" id="glyph2"><path d="M2,2"/></svg>]]>
160*e1fe3e4aSElliott Hughes                </svgDoc>
161*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="4" startGlyphID="4">
162*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg" id="glyph4"><path d="M4,4"/></svg>]]>
163*e1fe3e4aSElliott Hughes                </svgDoc>
164*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="5" startGlyphID="5">
165*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg" id="glyph5"><path d="M5,5"/></svg>]]>
166*e1fe3e4aSElliott Hughes                </svgDoc>
167*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="6" startGlyphID="6">
168*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg" id="glyph6"><path d="M6,6"/></svg>]]>
169*e1fe3e4aSElliott Hughes                </svgDoc>
170*e1fe3e4aSElliott Hughes                """
171*e1fe3e4aSElliott Hughes            ),
172*e1fe3e4aSElliott Hughes        ),
173*e1fe3e4aSElliott Hughes    ],
174*e1fe3e4aSElliott Hughes)
175*e1fe3e4aSElliott Hughesdef test_subset_single_glyph_per_svg(
176*e1fe3e4aSElliott Hughes    empty_svg_font, add_svg_table, tmp_path, gids, retain_gids, expected_xml
177*e1fe3e4aSElliott Hughes):
178*e1fe3e4aSElliott Hughes    font = add_svg_table(empty_svg_font)
179*e1fe3e4aSElliott Hughes
180*e1fe3e4aSElliott Hughes    svg_font_path = tmp_path / "TestSVG.ttf"
181*e1fe3e4aSElliott Hughes    font.save(svg_font_path)
182*e1fe3e4aSElliott Hughes
183*e1fe3e4aSElliott Hughes    subset_path = svg_font_path.with_suffix(".subset.ttf")
184*e1fe3e4aSElliott Hughes
185*e1fe3e4aSElliott Hughes    subset.main(
186*e1fe3e4aSElliott Hughes        [
187*e1fe3e4aSElliott Hughes            str(svg_font_path),
188*e1fe3e4aSElliott Hughes            f"--output-file={subset_path}",
189*e1fe3e4aSElliott Hughes            f"--gids={gids}",
190*e1fe3e4aSElliott Hughes            "--retain_gids" if retain_gids else "--no-retain_gids",
191*e1fe3e4aSElliott Hughes        ]
192*e1fe3e4aSElliott Hughes    )
193*e1fe3e4aSElliott Hughes    subset_font = TTFont(subset_path)
194*e1fe3e4aSElliott Hughes
195*e1fe3e4aSElliott Hughes    assert getXML(subset_font["SVG "].toXML, subset_font) == expected_xml
196*e1fe3e4aSElliott Hughes
197*e1fe3e4aSElliott Hughes
198*e1fe3e4aSElliott Hughes# This contains a bunch of cross-references between glyphs, paths, gradients, etc.
199*e1fe3e4aSElliott Hughes# Note the path coordinates are completely made up and not meant to be rendered.
200*e1fe3e4aSElliott Hughes# We only care about the tree structure, not it's visual content.
201*e1fe3e4aSElliott HughesCOMPLEX_SVG = """\
202*e1fe3e4aSElliott Hughes<svg xmlns="http://www.w3.org/2000/svg"
203*e1fe3e4aSElliott Hughes     xmlns:xlink="http://www.w3.org/1999/xlink">
204*e1fe3e4aSElliott Hughes  <defs>
205*e1fe3e4aSElliott Hughes    <linearGradient id="lg1" x1="50" x2="50" y1="80" y2="80" gradientUnits="userSpaceOnUse">
206*e1fe3e4aSElliott Hughes      <stop stop-color="#A47B62" offset="0"/>
207*e1fe3e4aSElliott Hughes      <stop stop-color="#AD8264" offset="1.0"/>
208*e1fe3e4aSElliott Hughes    </linearGradient>
209*e1fe3e4aSElliott Hughes    <radialGradient id="rg2" cx="50" cy="50" r="10" gradientUnits="userSpaceOnUse">
210*e1fe3e4aSElliott Hughes      <stop stop-color="#A47B62" offset="0"/>
211*e1fe3e4aSElliott Hughes      <stop stop-color="#AD8264" offset="1.0"/>
212*e1fe3e4aSElliott Hughes    </radialGradient>
213*e1fe3e4aSElliott Hughes    <radialGradient id="rg3" xlink:href="#rg2" r="20"/>
214*e1fe3e4aSElliott Hughes    <radialGradient id="rg4" xlink:href="#rg3" cy="100"/>
215*e1fe3e4aSElliott Hughes    <path id="p1" d="M3,3"/>
216*e1fe3e4aSElliott Hughes    <clipPath id="c1">
217*e1fe3e4aSElliott Hughes      <circle cx="10" cy="10" r="1"/>
218*e1fe3e4aSElliott Hughes    </clipPath>
219*e1fe3e4aSElliott Hughes  </defs>
220*e1fe3e4aSElliott Hughes  <g id="glyph1">
221*e1fe3e4aSElliott Hughes    <g id="glyph2">
222*e1fe3e4aSElliott Hughes      <path d="M0,0"/>
223*e1fe3e4aSElliott Hughes    </g>
224*e1fe3e4aSElliott Hughes    <g>
225*e1fe3e4aSElliott Hughes      <path d="M1,1" fill="url(#lg1)"/>
226*e1fe3e4aSElliott Hughes      <path d="M2,2"/>
227*e1fe3e4aSElliott Hughes    </g>
228*e1fe3e4aSElliott Hughes  </g>
229*e1fe3e4aSElliott Hughes  <g id="glyph3">
230*e1fe3e4aSElliott Hughes    <use xlink:href="#p1"/>
231*e1fe3e4aSElliott Hughes  </g>
232*e1fe3e4aSElliott Hughes  <use id="glyph4" xlink:href="#glyph1" x="10"/>
233*e1fe3e4aSElliott Hughes  <use id="glyph5" xlink:href="#glyph2" y="-10"/>
234*e1fe3e4aSElliott Hughes  <g id="glyph6">
235*e1fe3e4aSElliott Hughes    <use xlink:href="#p1" transform="scale(2, 1)"/>
236*e1fe3e4aSElliott Hughes  </g>
237*e1fe3e4aSElliott Hughes  <g id="group1">
238*e1fe3e4aSElliott Hughes    <g id="glyph7">
239*e1fe3e4aSElliott Hughes      <path id="p2" d="M4,4"/>
240*e1fe3e4aSElliott Hughes    </g>
241*e1fe3e4aSElliott Hughes    <g id=".glyph7">
242*e1fe3e4aSElliott Hughes      <path d="M4,4"/>
243*e1fe3e4aSElliott Hughes    </g>
244*e1fe3e4aSElliott Hughes    <g id="glyph8">
245*e1fe3e4aSElliott Hughes      <g id=".glyph8">
246*e1fe3e4aSElliott Hughes        <path id="p3" d="M5,5"/>
247*e1fe3e4aSElliott Hughes        <path id="M6,6"/>
248*e1fe3e4aSElliott Hughes      </g>
249*e1fe3e4aSElliott Hughes      <path d="M7,7"/>
250*e1fe3e4aSElliott Hughes    </g>
251*e1fe3e4aSElliott Hughes    <g id="glyph9">
252*e1fe3e4aSElliott Hughes      <use xlink:href="#p2"/>
253*e1fe3e4aSElliott Hughes    </g>
254*e1fe3e4aSElliott Hughes    <g id="glyph10">
255*e1fe3e4aSElliott Hughes      <use xlink:href="#p3"/>
256*e1fe3e4aSElliott Hughes    </g>
257*e1fe3e4aSElliott Hughes  </g>
258*e1fe3e4aSElliott Hughes  <g id="glyph11">
259*e1fe3e4aSElliott Hughes    <path d="M7,7" fill="url(#rg4)"/>
260*e1fe3e4aSElliott Hughes  </g>
261*e1fe3e4aSElliott Hughes  <g id="glyph12">
262*e1fe3e4aSElliott Hughes    <path d="M7,7" style="fill:url(#lg1);stroke:red;clip-path:url(#c1)"/>
263*e1fe3e4aSElliott Hughes  </g>
264*e1fe3e4aSElliott Hughes</svg>
265*e1fe3e4aSElliott Hughes"""
266*e1fe3e4aSElliott Hughes
267*e1fe3e4aSElliott Hughes
268*e1fe3e4aSElliott Hughes@pytest.mark.parametrize(
269*e1fe3e4aSElliott Hughes    "subset_gids, expected_xml",
270*e1fe3e4aSElliott Hughes    [
271*e1fe3e4aSElliott Hughes        # we only keep gid=2, with 'glyph2' defined inside 'glyph1': 'glyph2'
272*e1fe3e4aSElliott Hughes        # is renamed 'glyph1' to match the new subset indices, and the old 'glyph1'
273*e1fe3e4aSElliott Hughes        # is kept (as it contains 'glyph2') but renamed '.glyph1' to avoid clash
274*e1fe3e4aSElliott Hughes        (
275*e1fe3e4aSElliott Hughes            "2",
276*e1fe3e4aSElliott Hughes            _lines(
277*e1fe3e4aSElliott Hughes                """\
278*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="1" startGlyphID="1">
279*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
280*e1fe3e4aSElliott Hughes                  <g id=".glyph1">
281*e1fe3e4aSElliott Hughes                    <g id="glyph1">
282*e1fe3e4aSElliott Hughes                      <path d="M0,0"/>
283*e1fe3e4aSElliott Hughes                    </g>
284*e1fe3e4aSElliott Hughes                  </g>
285*e1fe3e4aSElliott Hughes                </svg>
286*e1fe3e4aSElliott Hughes                ]]>
287*e1fe3e4aSElliott Hughes                </svgDoc>
288*e1fe3e4aSElliott Hughes                """
289*e1fe3e4aSElliott Hughes            ),
290*e1fe3e4aSElliott Hughes        ),
291*e1fe3e4aSElliott Hughes        # we keep both gid 1 and 2: the glyph elements' ids stay as they are (only the
292*e1fe3e4aSElliott Hughes        # range endGlyphID change); a gradient is kept since it's referenced by glyph1
293*e1fe3e4aSElliott Hughes        (
294*e1fe3e4aSElliott Hughes            "1,2",
295*e1fe3e4aSElliott Hughes            _lines(
296*e1fe3e4aSElliott Hughes                """\
297*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="2" startGlyphID="1">
298*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
299*e1fe3e4aSElliott Hughes                  <defs>
300*e1fe3e4aSElliott Hughes                    <linearGradient id="lg1" x1="50" x2="50" y1="80" y2="80" gradientUnits="userSpaceOnUse">
301*e1fe3e4aSElliott Hughes                      <stop stop-color="#A47B62" offset="0"/>
302*e1fe3e4aSElliott Hughes                      <stop stop-color="#AD8264" offset="1.0"/>
303*e1fe3e4aSElliott Hughes                    </linearGradient>
304*e1fe3e4aSElliott Hughes                  </defs>
305*e1fe3e4aSElliott Hughes                  <g id="glyph1">
306*e1fe3e4aSElliott Hughes                    <g id="glyph2">
307*e1fe3e4aSElliott Hughes                      <path d="M0,0"/>
308*e1fe3e4aSElliott Hughes                    </g>
309*e1fe3e4aSElliott Hughes                    <g>
310*e1fe3e4aSElliott Hughes                      <path d="M1,1" fill="url(#lg1)"/>
311*e1fe3e4aSElliott Hughes                      <path d="M2,2"/>
312*e1fe3e4aSElliott Hughes                    </g>
313*e1fe3e4aSElliott Hughes                  </g>
314*e1fe3e4aSElliott Hughes                </svg>
315*e1fe3e4aSElliott Hughes                ]]>
316*e1fe3e4aSElliott Hughes                </svgDoc>
317*e1fe3e4aSElliott Hughes                """
318*e1fe3e4aSElliott Hughes            ),
319*e1fe3e4aSElliott Hughes        ),
320*e1fe3e4aSElliott Hughes        (
321*e1fe3e4aSElliott Hughes            # both gid 3 and 6 refer (via <use xlink:href="#...") to path 'p1', which
322*e1fe3e4aSElliott Hughes            # is thus kept in <defs>; the glyph ids and range start/end are renumbered.
323*e1fe3e4aSElliott Hughes            "3,6",
324*e1fe3e4aSElliott Hughes            _lines(
325*e1fe3e4aSElliott Hughes                """\
326*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="2" startGlyphID="1">
327*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
328*e1fe3e4aSElliott Hughes                  <defs>
329*e1fe3e4aSElliott Hughes                    <path id="p1" d="M3,3"/>
330*e1fe3e4aSElliott Hughes                  </defs>
331*e1fe3e4aSElliott Hughes                  <g id="glyph1">
332*e1fe3e4aSElliott Hughes                    <use xlink:href="#p1"/>
333*e1fe3e4aSElliott Hughes                  </g>
334*e1fe3e4aSElliott Hughes                  <g id="glyph2">
335*e1fe3e4aSElliott Hughes                    <use xlink:href="#p1" transform="scale(2, 1)"/>
336*e1fe3e4aSElliott Hughes                  </g>
337*e1fe3e4aSElliott Hughes                </svg>
338*e1fe3e4aSElliott Hughes                ]]>
339*e1fe3e4aSElliott Hughes                </svgDoc>
340*e1fe3e4aSElliott Hughes                """
341*e1fe3e4aSElliott Hughes            ),
342*e1fe3e4aSElliott Hughes        ),
343*e1fe3e4aSElliott Hughes        (
344*e1fe3e4aSElliott Hughes            # 'glyph4' uses the whole 'glyph1' element (translated); we keep the latter
345*e1fe3e4aSElliott Hughes            # renamed to avoid clashes with new gids
346*e1fe3e4aSElliott Hughes            "3-4",
347*e1fe3e4aSElliott Hughes            _lines(
348*e1fe3e4aSElliott Hughes                """\
349*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="2" startGlyphID="1">
350*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
351*e1fe3e4aSElliott Hughes                  <defs>
352*e1fe3e4aSElliott Hughes                    <linearGradient id="lg1" x1="50" x2="50" y1="80" y2="80" gradientUnits="userSpaceOnUse">
353*e1fe3e4aSElliott Hughes                      <stop stop-color="#A47B62" offset="0"/>
354*e1fe3e4aSElliott Hughes                      <stop stop-color="#AD8264" offset="1.0"/>
355*e1fe3e4aSElliott Hughes                    </linearGradient>
356*e1fe3e4aSElliott Hughes                    <path id="p1" d="M3,3"/>
357*e1fe3e4aSElliott Hughes                  </defs>
358*e1fe3e4aSElliott Hughes                  <g id=".glyph1">
359*e1fe3e4aSElliott Hughes                    <g id=".glyph2">
360*e1fe3e4aSElliott Hughes                      <path d="M0,0"/>
361*e1fe3e4aSElliott Hughes                    </g>
362*e1fe3e4aSElliott Hughes                    <g>
363*e1fe3e4aSElliott Hughes                      <path d="M1,1" fill="url(#lg1)"/>
364*e1fe3e4aSElliott Hughes                      <path d="M2,2"/>
365*e1fe3e4aSElliott Hughes                    </g>
366*e1fe3e4aSElliott Hughes                  </g>
367*e1fe3e4aSElliott Hughes                  <g id="glyph1">
368*e1fe3e4aSElliott Hughes                    <use xlink:href="#p1"/>
369*e1fe3e4aSElliott Hughes                  </g>
370*e1fe3e4aSElliott Hughes                  <use id="glyph2" xlink:href="#.glyph1" x="10"/>
371*e1fe3e4aSElliott Hughes                </svg>
372*e1fe3e4aSElliott Hughes                ]]>
373*e1fe3e4aSElliott Hughes                </svgDoc>
374*e1fe3e4aSElliott Hughes                """
375*e1fe3e4aSElliott Hughes            ),
376*e1fe3e4aSElliott Hughes        ),
377*e1fe3e4aSElliott Hughes        (
378*e1fe3e4aSElliott Hughes            # 'glyph9' uses a path 'p2' defined inside 'glyph7', the latter is excluded
379*e1fe3e4aSElliott Hughes            # from our subset, thus gets renamed '.glyph7'; an unrelated element with
380*e1fe3e4aSElliott Hughes            # same id=".glyph7" doesn't clash because it was dropped.
381*e1fe3e4aSElliott Hughes            # Similarly 'glyph10' uses path 'p3' defined inside 'glyph8', also excluded
382*e1fe3e4aSElliott Hughes            # from subset and prefixed with '.'. But since an id=".glyph8" is already
383*e1fe3e4aSElliott Hughes            # used in the doc, we append a .{digit} suffix to disambiguate.
384*e1fe3e4aSElliott Hughes            "9,10",
385*e1fe3e4aSElliott Hughes            _lines(
386*e1fe3e4aSElliott Hughes                """\
387*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="2" startGlyphID="1">
388*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
389*e1fe3e4aSElliott Hughes                  <g id="group1">
390*e1fe3e4aSElliott Hughes                    <g id=".glyph7">
391*e1fe3e4aSElliott Hughes                      <path id="p2" d="M4,4"/>
392*e1fe3e4aSElliott Hughes                    </g>
393*e1fe3e4aSElliott Hughes                    <g id=".glyph8.1">
394*e1fe3e4aSElliott Hughes                      <g id=".glyph8">
395*e1fe3e4aSElliott Hughes                        <path id="p3" d="M5,5"/>
396*e1fe3e4aSElliott Hughes                      </g>
397*e1fe3e4aSElliott Hughes                    </g>
398*e1fe3e4aSElliott Hughes                    <g id="glyph1">
399*e1fe3e4aSElliott Hughes                      <use xlink:href="#p2"/>
400*e1fe3e4aSElliott Hughes                    </g>
401*e1fe3e4aSElliott Hughes                    <g id="glyph2">
402*e1fe3e4aSElliott Hughes                      <use xlink:href="#p3"/>
403*e1fe3e4aSElliott Hughes                    </g>
404*e1fe3e4aSElliott Hughes                  </g>
405*e1fe3e4aSElliott Hughes                </svg>
406*e1fe3e4aSElliott Hughes                ]]>
407*e1fe3e4aSElliott Hughes                </svgDoc>
408*e1fe3e4aSElliott Hughes                """
409*e1fe3e4aSElliott Hughes            ),
410*e1fe3e4aSElliott Hughes        ),
411*e1fe3e4aSElliott Hughes        (
412*e1fe3e4aSElliott Hughes            # 'glyph11' uses gradient 'rg4' which inherits from 'rg3', which inherits
413*e1fe3e4aSElliott Hughes            # from 'rg2', etc.
414*e1fe3e4aSElliott Hughes            "11",
415*e1fe3e4aSElliott Hughes            _lines(
416*e1fe3e4aSElliott Hughes                """\
417*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="1" startGlyphID="1">
418*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
419*e1fe3e4aSElliott Hughes                  <defs>
420*e1fe3e4aSElliott Hughes                    <radialGradient id="rg2" cx="50" cy="50" r="10" gradientUnits="userSpaceOnUse">
421*e1fe3e4aSElliott Hughes                      <stop stop-color="#A47B62" offset="0"/>
422*e1fe3e4aSElliott Hughes                      <stop stop-color="#AD8264" offset="1.0"/>
423*e1fe3e4aSElliott Hughes                    </radialGradient>
424*e1fe3e4aSElliott Hughes                    <radialGradient id="rg3" xlink:href="#rg2" r="20"/>
425*e1fe3e4aSElliott Hughes                    <radialGradient id="rg4" xlink:href="#rg3" cy="100"/>
426*e1fe3e4aSElliott Hughes                  </defs>
427*e1fe3e4aSElliott Hughes                  <g id="glyph1">
428*e1fe3e4aSElliott Hughes                    <path d="M7,7" fill="url(#rg4)"/>
429*e1fe3e4aSElliott Hughes                  </g>
430*e1fe3e4aSElliott Hughes                </svg>
431*e1fe3e4aSElliott Hughes                ]]>
432*e1fe3e4aSElliott Hughes                </svgDoc>
433*e1fe3e4aSElliott Hughes                """
434*e1fe3e4aSElliott Hughes            ),
435*e1fe3e4aSElliott Hughes        ),
436*e1fe3e4aSElliott Hughes        (
437*e1fe3e4aSElliott Hughes            # 'glyph12' contains a style attribute with inline CSS declarations that
438*e1fe3e4aSElliott Hughes            # contains references to a gradient fill and a clipPath: we keep those
439*e1fe3e4aSElliott Hughes            "12",
440*e1fe3e4aSElliott Hughes            _lines(
441*e1fe3e4aSElliott Hughes                """\
442*e1fe3e4aSElliott Hughes                <svgDoc endGlyphID="1" startGlyphID="1">
443*e1fe3e4aSElliott Hughes                  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
444*e1fe3e4aSElliott Hughes                  <defs>
445*e1fe3e4aSElliott Hughes                    <linearGradient id="lg1" x1="50" x2="50" y1="80" y2="80" gradientUnits="userSpaceOnUse">
446*e1fe3e4aSElliott Hughes                      <stop stop-color="#A47B62" offset="0"/>
447*e1fe3e4aSElliott Hughes                      <stop stop-color="#AD8264" offset="1.0"/>
448*e1fe3e4aSElliott Hughes                    </linearGradient>
449*e1fe3e4aSElliott Hughes                    <clipPath id="c1">
450*e1fe3e4aSElliott Hughes                      <circle cx="10" cy="10" r="1"/>
451*e1fe3e4aSElliott Hughes                    </clipPath>
452*e1fe3e4aSElliott Hughes                  </defs>
453*e1fe3e4aSElliott Hughes                  <g id="glyph1">
454*e1fe3e4aSElliott Hughes                    <path d="M7,7" style="fill:url(#lg1);stroke:red;clip-path:url(#c1)"/>
455*e1fe3e4aSElliott Hughes                  </g>
456*e1fe3e4aSElliott Hughes                </svg>
457*e1fe3e4aSElliott Hughes                ]]>
458*e1fe3e4aSElliott Hughes                </svgDoc>
459*e1fe3e4aSElliott Hughes                """
460*e1fe3e4aSElliott Hughes            ),
461*e1fe3e4aSElliott Hughes        ),
462*e1fe3e4aSElliott Hughes    ],
463*e1fe3e4aSElliott Hughes)
464*e1fe3e4aSElliott Hughesdef test_subset_svg_with_references(
465*e1fe3e4aSElliott Hughes    empty_svg_font, tmp_path, subset_gids, expected_xml
466*e1fe3e4aSElliott Hughes):
467*e1fe3e4aSElliott Hughes    font = empty_svg_font
468*e1fe3e4aSElliott Hughes
469*e1fe3e4aSElliott Hughes    font["SVG "].docList.append((COMPLEX_SVG, 1, 12))
470*e1fe3e4aSElliott Hughes    svg_font_path = tmp_path / "TestSVG.ttf"
471*e1fe3e4aSElliott Hughes    font.save(svg_font_path)
472*e1fe3e4aSElliott Hughes    subset_path = svg_font_path.with_suffix(".subset.ttf")
473*e1fe3e4aSElliott Hughes
474*e1fe3e4aSElliott Hughes    subset.main(
475*e1fe3e4aSElliott Hughes        [
476*e1fe3e4aSElliott Hughes            str(svg_font_path),
477*e1fe3e4aSElliott Hughes            f"--output-file={subset_path}",
478*e1fe3e4aSElliott Hughes            f"--gids={subset_gids}",
479*e1fe3e4aSElliott Hughes            "--pretty-svg",
480*e1fe3e4aSElliott Hughes        ]
481*e1fe3e4aSElliott Hughes    )
482*e1fe3e4aSElliott Hughes    subset_font = TTFont(subset_path)
483*e1fe3e4aSElliott Hughes
484*e1fe3e4aSElliott Hughes    if expected_xml is not None:
485*e1fe3e4aSElliott Hughes        assert getXML(subset_font["SVG "].toXML, subset_font) == expected_xml
486*e1fe3e4aSElliott Hughes    else:
487*e1fe3e4aSElliott Hughes        assert "SVG " not in subset_font
488*e1fe3e4aSElliott Hughes
489*e1fe3e4aSElliott Hughes
490*e1fe3e4aSElliott Hughesdef test_subset_svg_empty_table(empty_svg_font, tmp_path):
491*e1fe3e4aSElliott Hughes    font = empty_svg_font
492*e1fe3e4aSElliott Hughes
493*e1fe3e4aSElliott Hughes    svg = new_svg()
494*e1fe3e4aSElliott Hughes    etree.SubElement(svg, "rect", {"id": "glyph1", "x": "1", "y": "2"})
495*e1fe3e4aSElliott Hughes    font["SVG "].docList.append((etree.tostring(svg).decode(), 1, 1))
496*e1fe3e4aSElliott Hughes
497*e1fe3e4aSElliott Hughes    svg_font_path = tmp_path / "TestSVG.ttf"
498*e1fe3e4aSElliott Hughes    font.save(svg_font_path)
499*e1fe3e4aSElliott Hughes    subset_path = svg_font_path.with_suffix(".subset.ttf")
500*e1fe3e4aSElliott Hughes
501*e1fe3e4aSElliott Hughes    # there's no gid=2 in SVG table, drop the empty table
502*e1fe3e4aSElliott Hughes    subset.main([str(svg_font_path), f"--output-file={subset_path}", f"--gids=2"])
503*e1fe3e4aSElliott Hughes
504*e1fe3e4aSElliott Hughes    assert "SVG " not in TTFont(subset_path)
505*e1fe3e4aSElliott Hughes
506*e1fe3e4aSElliott Hughes
507*e1fe3e4aSElliott Hughesdef test_subset_svg_missing_glyph(empty_svg_font, tmp_path):
508*e1fe3e4aSElliott Hughes    font = empty_svg_font
509*e1fe3e4aSElliott Hughes
510*e1fe3e4aSElliott Hughes    svg = new_svg()
511*e1fe3e4aSElliott Hughes    etree.SubElement(svg, "rect", {"id": "glyph1", "x": "1", "y": "2"})
512*e1fe3e4aSElliott Hughes    font["SVG "].docList.append(
513*e1fe3e4aSElliott Hughes        (
514*e1fe3e4aSElliott Hughes            etree.tostring(svg).decode(),
515*e1fe3e4aSElliott Hughes            1,
516*e1fe3e4aSElliott Hughes            # the range endGlyphID=2 declares two glyphs however our svg contains
517*e1fe3e4aSElliott Hughes            # only one glyph element with id="glyph1", the "glyph2" one is absent.
518*e1fe3e4aSElliott Hughes            # Techically this would be invalid according to the OT-SVG spec.
519*e1fe3e4aSElliott Hughes            2,
520*e1fe3e4aSElliott Hughes        )
521*e1fe3e4aSElliott Hughes    )
522*e1fe3e4aSElliott Hughes    svg_font_path = tmp_path / "TestSVG.ttf"
523*e1fe3e4aSElliott Hughes    font.save(svg_font_path)
524*e1fe3e4aSElliott Hughes    subset_path = svg_font_path.with_suffix(".subset.ttf")
525*e1fe3e4aSElliott Hughes
526*e1fe3e4aSElliott Hughes    # make sure we don't crash when we don't find the expected "glyph2" element
527*e1fe3e4aSElliott Hughes    subset.main([str(svg_font_path), f"--output-file={subset_path}", f"--gids=1"])
528*e1fe3e4aSElliott Hughes
529*e1fe3e4aSElliott Hughes    subset_font = TTFont(subset_path)
530*e1fe3e4aSElliott Hughes    assert getXML(subset_font["SVG "].toXML, subset_font) == [
531*e1fe3e4aSElliott Hughes        '<svgDoc endGlyphID="1" startGlyphID="1">',
532*e1fe3e4aSElliott Hughes        '  <![CDATA[<svg xmlns="http://www.w3.org/2000/svg"><rect id="glyph1" x="1" y="2"/></svg>]]>',
533*e1fe3e4aSElliott Hughes        "</svgDoc>",
534*e1fe3e4aSElliott Hughes    ]
535*e1fe3e4aSElliott Hughes
536*e1fe3e4aSElliott Hughes    # ignore the missing gid even if included in the subset; in this test case we
537*e1fe3e4aSElliott Hughes    # end up with an empty svg document--which is dropped, along with the empty table
538*e1fe3e4aSElliott Hughes    subset.main([str(svg_font_path), f"--output-file={subset_path}", f"--gids=2"])
539*e1fe3e4aSElliott Hughes
540*e1fe3e4aSElliott Hughes    assert "SVG " not in TTFont(subset_path)
541*e1fe3e4aSElliott Hughes
542*e1fe3e4aSElliott Hughes
543*e1fe3e4aSElliott Hughes@pytest.mark.parametrize(
544*e1fe3e4aSElliott Hughes    "ints, expected_ranges",
545*e1fe3e4aSElliott Hughes    [
546*e1fe3e4aSElliott Hughes        ((), []),
547*e1fe3e4aSElliott Hughes        ((0,), [(0, 0)]),
548*e1fe3e4aSElliott Hughes        ((0, 1), [(0, 1)]),
549*e1fe3e4aSElliott Hughes        ((1, 1, 1, 1), [(1, 1)]),
550*e1fe3e4aSElliott Hughes        ((1, 3), [(1, 1), (3, 3)]),
551*e1fe3e4aSElliott Hughes        ((4, 2, 1, 3), [(1, 4)]),
552*e1fe3e4aSElliott Hughes        ((1, 2, 4, 5, 6, 9, 13, 14, 15), [(1, 2), (4, 6), (9, 9), (13, 15)]),
553*e1fe3e4aSElliott Hughes    ],
554*e1fe3e4aSElliott Hughes)
555*e1fe3e4aSElliott Hughesdef test_ranges(ints, expected_ranges):
556*e1fe3e4aSElliott Hughes    assert list(ranges(ints)) == expected_ranges
557