| 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"""\ |
| <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"> |
| <defs> |
| <rect x="100" y="-200" width="300" height="400" id="p1"/> |
| </defs> |
| <g id="glyph1"> |
| <use xlink:href="#p1" fill="#red"/> |
| </g> |
| <g id="glyph2"> |
| <use xlink:href="#p1" fill="#blue"/> |
| </g> |
| <g id="glyph4"> |
| <use xlink:href="#p1" fill="#green"/> |
| </g> |
| </svg>""", |
| b"""\ |
| <svg xmlns="http://www.w3.org/2000/svg" version="1.1"> |
| <g id="glyph3"> |
| <path d="M0,0 L100,0 L50,100 Z" fill="#red"/> |
| <path d="M10,10 L110,10 L60,110 Z" fill="#blue"/> |
| <path d="M20,20 L120,20 L70,120 Z" fill="#green"/> |
| </g> |
| </svg>""", |
| ) |
| ] |
| |
| |
| 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 = [ |
| '<svgDoc endGlyphID="2" startGlyphID="1">', |
| f" <![CDATA[{SVG_DOCS[0].decode()}]]>", |
| "</svgDoc>", |
| '<svgDoc compressed="1" endGlyphID="3" startGlyphID="3">', |
| f" <![CDATA[{SVG_DOCS[1].decode()}]]>", |
| "</svgDoc>", |
| '<svgDoc endGlyphID="4" startGlyphID="4">', |
| f" <![CDATA[{SVG_DOCS[0].decode()}]]>", |
| "</svgDoc>", |
| ] |
| |
| |
| @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 |