xref: /aosp_15_r20/external/fonttools/Tests/ttLib/tables/S_V_G__test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1import gzip
2import io
3import struct
4
5from fontTools.misc import etree
6from fontTools.misc.testTools import getXML, parseXML
7from fontTools.ttLib import TTFont
8from fontTools.ttLib.tables.S_V_G_ import table_S_V_G_
9
10import pytest
11
12
13def dump(table, ttFont=None):
14    print("\n".join(getXML(table.toXML, ttFont)))
15
16
17def compress(data: bytes) -> bytes:
18    buf = io.BytesIO()
19    with gzip.GzipFile(None, "w", fileobj=buf, mtime=0) as gz:
20        gz.write(data)
21    return buf.getvalue()
22
23
24def strip_xml_whitespace(xml_string):
25    def strip_or_none(text):
26        text = text.strip() if text else None
27        return text if text else None
28
29    tree = etree.fromstring(xml_string)
30    for e in tree.iter("*"):
31        e.text = strip_or_none(e.text)
32        e.tail = strip_or_none(e.tail)
33    return etree.tostring(tree, encoding="utf-8")
34
35
36SVG_DOCS = [
37    strip_xml_whitespace(svg)
38    for svg in (
39        b"""\
40        <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
41          <defs>
42            <rect x="100" y="-200" width="300" height="400" id="p1"/>
43          </defs>
44          <g id="glyph1">
45            <use xlink:href="#p1" fill="#red"/>
46          </g>
47          <g id="glyph2">
48            <use xlink:href="#p1" fill="#blue"/>
49          </g>
50          <g id="glyph4">
51            <use xlink:href="#p1" fill="#green"/>
52          </g>
53        </svg>""",
54        b"""\
55        <svg xmlns="http://www.w3.org/2000/svg" version="1.1">
56          <g id="glyph3">
57            <path d="M0,0 L100,0 L50,100 Z" fill="#red"/>
58            <path d="M10,10 L110,10 L60,110 Z" fill="#blue"/>
59            <path d="M20,20 L120,20 L70,120 Z" fill="#green"/>
60          </g>
61        </svg>""",
62    )
63]
64
65
66OTSVG_DATA = b"".join(
67    [
68        # SVG table header
69        b"\x00\x00"  # version (0)
70        b"\x00\x00\x00\x0a"  # offset to SVGDocumentList (10)
71        b"\x00\x00\x00\x00"  # reserved (0)
72        #  SVGDocumentList
73        b"\x00\x03"  # number of SVGDocumentRecords (3)
74        # SVGDocumentRecord[0]
75        b"\x00\x01"  # startGlyphID (1)
76        b"\x00\x02"  # endGlyphID (2)
77        b"\x00\x00\x00\x26"  # svgDocOffset (2 + 12*3 == 38 == 0x26)
78        + struct.pack(">L", len(SVG_DOCS[0]))  # svgDocLength
79        # SVGDocumentRecord[1] (compressed)
80        + b"\x00\x03"  # startGlyphID (3)
81        b"\x00\x03"  # endGlyphID (3)
82        + struct.pack(">L", 0x26 + len(SVG_DOCS[0]))  # svgDocOffset
83        + struct.pack(">L", len(compress(SVG_DOCS[1])))  # svgDocLength
84        # SVGDocumentRecord[2]
85        + b"\x00\x04"  # startGlyphID (4)
86        b"\x00\x04"  # endGlyphID (4)
87        b"\x00\x00\x00\x26"  # svgDocOffset (38); records 0 and 2 point to same SVG doc
88        + struct.pack(">L", len(SVG_DOCS[0]))  # svgDocLength
89    ]
90    + [SVG_DOCS[0], compress(SVG_DOCS[1])]
91)
92
93OTSVG_TTX = [
94    '<svgDoc endGlyphID="2" startGlyphID="1">',
95    f"  <![CDATA[{SVG_DOCS[0].decode()}]]>",
96    "</svgDoc>",
97    '<svgDoc compressed="1" endGlyphID="3" startGlyphID="3">',
98    f"  <![CDATA[{SVG_DOCS[1].decode()}]]>",
99    "</svgDoc>",
100    '<svgDoc endGlyphID="4" startGlyphID="4">',
101    f"  <![CDATA[{SVG_DOCS[0].decode()}]]>",
102    "</svgDoc>",
103]
104
105
106@pytest.fixture
107def font():
108    font = TTFont()
109    font.setGlyphOrder([".notdef"] + ["glyph%05d" % i for i in range(1, 30)])
110    return font
111
112
113def test_decompile_and_compile(font):
114    table = table_S_V_G_()
115    table.decompile(OTSVG_DATA, font)
116    assert table.compile(font) == OTSVG_DATA
117
118
119def test_decompile_and_dump_ttx(font):
120    table = table_S_V_G_()
121    table.decompile(OTSVG_DATA, font)
122
123    dump(table, font)
124    assert getXML(table.toXML, font) == OTSVG_TTX
125
126
127def test_load_from_ttx_and_compile(font):
128    table = table_S_V_G_()
129    for name, attrs, content in parseXML(OTSVG_TTX):
130        table.fromXML(name, attrs, content, font)
131    assert table.compile(font) == OTSVG_DATA
132
133
134def test_round_trip_ttx(font):
135    table = table_S_V_G_()
136    for name, attrs, content in parseXML(OTSVG_TTX):
137        table.fromXML(name, attrs, content, font)
138    compiled = table.compile(font)
139
140    table = table_S_V_G_()
141    table.decompile(compiled, font)
142    assert getXML(table.toXML, font) == OTSVG_TTX
143
144
145def test_unpack_svg_doc_as_3_tuple():
146    # test that the legacy docList as list of 3-tuples interface still works
147    # even after the new SVGDocument class with extra `compressed` attribute
148    # was added
149    table = table_S_V_G_()
150    table.decompile(OTSVG_DATA, font)
151
152    for doc, compressed in zip(table.docList, (False, True, False)):
153        assert len(doc) == 3
154        data, startGID, endGID = doc
155        assert doc.data == data
156        assert doc.startGlyphID == startGID
157        assert doc.endGlyphID == endGID
158        assert doc.compressed == compressed
159