import gzip import io import struct from fontTools.misc import etree from fontTools.misc.testTools import getXML, parseXML from fontTools.ttLib import TTFont from fontTools.ttLib.tables.S_V_G_ import table_S_V_G_ import pytest def dump(table, ttFont=None): print("\n".join(getXML(table.toXML, ttFont))) def compress(data: bytes) -> bytes: buf = io.BytesIO() with gzip.GzipFile(None, "w", fileobj=buf, mtime=0) as gz: gz.write(data) return buf.getvalue() def strip_xml_whitespace(xml_string): def strip_or_none(text): text = text.strip() if text else None return text if text else None tree = etree.fromstring(xml_string) for e in tree.iter("*"): e.text = strip_or_none(e.text) e.tail = strip_or_none(e.tail) return etree.tostring(tree, encoding="utf-8") SVG_DOCS = [ strip_xml_whitespace(svg) for svg in ( b"""\ """, b"""\ """, ) ] OTSVG_DATA = b"".join( [ # SVG table header b"\x00\x00" # version (0) b"\x00\x00\x00\x0a" # offset to SVGDocumentList (10) b"\x00\x00\x00\x00" # reserved (0) # SVGDocumentList b"\x00\x03" # number of SVGDocumentRecords (3) # SVGDocumentRecord[0] b"\x00\x01" # startGlyphID (1) b"\x00\x02" # endGlyphID (2) b"\x00\x00\x00\x26" # svgDocOffset (2 + 12*3 == 38 == 0x26) + struct.pack(">L", len(SVG_DOCS[0])) # svgDocLength # SVGDocumentRecord[1] (compressed) + b"\x00\x03" # startGlyphID (3) b"\x00\x03" # endGlyphID (3) + struct.pack(">L", 0x26 + len(SVG_DOCS[0])) # svgDocOffset + struct.pack(">L", len(compress(SVG_DOCS[1]))) # svgDocLength # SVGDocumentRecord[2] + b"\x00\x04" # startGlyphID (4) b"\x00\x04" # endGlyphID (4) b"\x00\x00\x00\x26" # svgDocOffset (38); records 0 and 2 point to same SVG doc + struct.pack(">L", len(SVG_DOCS[0])) # svgDocLength ] + [SVG_DOCS[0], compress(SVG_DOCS[1])] ) OTSVG_TTX = [ '', f" ", "", '', f" ", "", '', f" ", "", ] @pytest.fixture def font(): font = TTFont() font.setGlyphOrder([".notdef"] + ["glyph%05d" % i for i in range(1, 30)]) return font def test_decompile_and_compile(font): table = table_S_V_G_() table.decompile(OTSVG_DATA, font) assert table.compile(font) == OTSVG_DATA def test_decompile_and_dump_ttx(font): table = table_S_V_G_() table.decompile(OTSVG_DATA, font) dump(table, font) assert getXML(table.toXML, font) == OTSVG_TTX def test_load_from_ttx_and_compile(font): table = table_S_V_G_() for name, attrs, content in parseXML(OTSVG_TTX): table.fromXML(name, attrs, content, font) assert table.compile(font) == OTSVG_DATA def test_round_trip_ttx(font): table = table_S_V_G_() for name, attrs, content in parseXML(OTSVG_TTX): table.fromXML(name, attrs, content, font) compiled = table.compile(font) table = table_S_V_G_() table.decompile(compiled, font) assert getXML(table.toXML, font) == OTSVG_TTX def test_unpack_svg_doc_as_3_tuple(): # test that the legacy docList as list of 3-tuples interface still works # even after the new SVGDocument class with extra `compressed` attribute # was added table = table_S_V_G_() table.decompile(OTSVG_DATA, font) for doc, compressed in zip(table.docList, (False, True, False)): assert len(doc) == 3 data, startGID, endGID = doc assert doc.data == data assert doc.startGlyphID == startGID assert doc.endGlyphID == endGID assert doc.compressed == compressed