[svg] fix duplicate svg ids

This commit is contained in:
Jan Petykiewicz 2026-04-01 20:57:35 -07:00
commit a82365ec8c
2 changed files with 47 additions and 3 deletions

View file

@ -30,6 +30,21 @@ def _ref_to_svg_transform(ref) -> str:
return f'matrix({a:g} {b:g} {c:g} {d:g} {e:g} {f:g})'
def _make_svg_ids(names: Mapping[str, Pattern]) -> dict[str, str]:
svg_ids: dict[str, str] = {}
seen_ids: set[str] = set()
for name in names:
base_id = mangle_name(name)
svg_id = base_id
suffix = 1
while svg_id in seen_ids:
suffix += 1
svg_id = f'{base_id}_{suffix}'
seen_ids.add(svg_id)
svg_ids[name] = svg_id
return svg_ids
def writefile(
library: Mapping[str, Pattern],
top: str,
@ -81,10 +96,11 @@ def writefile(
# Create file
svg = svgwrite.Drawing(filename, profile='full', viewBox=viewbox_string,
debug=(not custom_attributes))
svg_ids = _make_svg_ids(library)
# Now create a group for each pattern and add in any Boundary and Use elements
for name, pat in library.items():
svg_group = svg.g(id=mangle_name(name), fill='blue', stroke='red')
svg_group = svg.g(id=svg_ids[name], fill='blue', stroke='red')
for layer, shapes in pat.shapes.items():
for shape in shapes:
@ -123,11 +139,11 @@ def writefile(
continue
for ref in refs:
transform = _ref_to_svg_transform(ref)
use = svg.use(href='#' + mangle_name(target), transform=transform)
use = svg.use(href='#' + svg_ids[target], transform=transform)
svg_group.add(use)
svg.defs.add(svg_group)
svg.add(svg.use(href='#' + mangle_name(top)))
svg.add(svg.use(href='#' + svg_ids[top]))
svg.save()

View file

@ -68,3 +68,31 @@ def test_svg_ref_mirroring_changes_affine_transform(tmp_path: Path) -> None:
assert_allclose(plain_transform, (0, 2, -2, 0, 3, 4), atol=1e-10)
assert_allclose(mirrored_transform, (0, 2, 2, 0, 3, 4), atol=1e-10)
def test_svg_uses_unique_ids_for_colliding_mangled_names(tmp_path: Path) -> None:
lib = Library()
first = Pattern()
first.polygon("1", vertices=[[0, 0], [1, 0], [0, 1]])
lib["a b"] = first
second = Pattern()
second.polygon("1", vertices=[[0, 0], [2, 0], [0, 2]])
lib["a-b"] = second
top = Pattern()
top.ref("a b")
top.ref("a-b", offset=(5, 0))
lib["top"] = top
svg_path = tmp_path / "colliding_ids.svg"
svg.writefile(lib, "top", str(svg_path))
root = ET.fromstring(svg_path.read_text())
ids = [group.attrib["id"] for group in root.iter(f"{SVG_NS}g")]
hrefs = [use.attrib[XLINK_HREF] for use in root.iter(f"{SVG_NS}use")]
assert ids.count("a_b") == 1
assert len(set(ids)) == len(ids)
assert "#a_b" in hrefs
assert "#a_b_2" in hrefs