from __future__ import annotations
import re
from functools import cache
from itertools import chain, count
from typing import Dict, Iterable, Iterator, List, Optional, Set, Tuple
try:
from lxml import etree
except ImportError:
# lxml is required for subsetting SVG, but we prefer to delay the import error
# until subset_glyphs() is called (i.e. if font to subset has an 'SVG ' table)
etree = None
from fontTools import ttLib
from fontTools.subset.util import _add_method
from fontTools.ttLib.tables.S_V_G_ import SVGDocument
__all__ = ["subset_glyphs"]
GID_RE = re.compile(r"^glyph(\d+)$")
NAMESPACES = {
"svg": "http://www.w3.org/2000/svg",
"xlink": "http://www.w3.org/1999/xlink",
}
XLINK_HREF = f'{{{NAMESPACES["xlink"]}}}href'
@cache
def xpath(path):
# compile XPath upfront, caching result to reuse on multiple elements
return etree.XPath(path, namespaces=NAMESPACES)
def group_elements_by_id(tree: etree.Element) -> Dict[str, etree.Element]:
# select all svg elements with 'id' attribute no matter where they are
# including the root element itself:
# https://github.com/fonttools/fonttools/issues/2548
return {el.attrib["id"]: el for el in xpath("//svg:*[@id]")(tree)}
def parse_css_declarations(style_attr: str) -> Dict[str, str]:
# https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/style
# https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax#css_declarations
result = {}
for declaration in style_attr.split(";"):
if declaration.count(":") == 1:
property_name, value = declaration.split(":")
property_name = property_name.strip()
result[property_name] = value.strip()
elif declaration.strip():
raise ValueError(f"Invalid CSS declaration syntax: {declaration}")
return result
def iter_referenced_ids(tree: etree.Element) -> Iterator[str]:
# Yield all the ids that can be reached via references from this element tree.
# We currently support xlink:href (as used by