[svg] fix rotation in svg
This commit is contained in:
parent
9ede16df5d
commit
06f8611a90
2 changed files with 86 additions and 1 deletions
|
|
@ -10,11 +10,26 @@ import svgwrite # type: ignore
|
||||||
|
|
||||||
from .utils import mangle_name
|
from .utils import mangle_name
|
||||||
from .. import Pattern
|
from .. import Pattern
|
||||||
|
from ..utils import rotation_matrix_2d
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _ref_to_svg_transform(ref) -> str:
|
||||||
|
linear = rotation_matrix_2d(ref.rotation) * ref.scale
|
||||||
|
if ref.mirrored:
|
||||||
|
linear = linear @ numpy.diag((1.0, -1.0))
|
||||||
|
|
||||||
|
a = linear[0, 0]
|
||||||
|
b = linear[1, 0]
|
||||||
|
c = linear[0, 1]
|
||||||
|
d = linear[1, 1]
|
||||||
|
e = ref.offset[0]
|
||||||
|
f = ref.offset[1]
|
||||||
|
return f'matrix({a:g} {b:g} {c:g} {d:g} {e:g} {f:g})'
|
||||||
|
|
||||||
|
|
||||||
def writefile(
|
def writefile(
|
||||||
library: Mapping[str, Pattern],
|
library: Mapping[str, Pattern],
|
||||||
top: str,
|
top: str,
|
||||||
|
|
@ -107,7 +122,7 @@ def writefile(
|
||||||
if target is None:
|
if target is None:
|
||||||
continue
|
continue
|
||||||
for ref in refs:
|
for ref in refs:
|
||||||
transform = f'scale({ref.scale:g}) rotate({ref.rotation:g}) translate({ref.offset[0]:g},{ref.offset[1]:g})'
|
transform = _ref_to_svg_transform(ref)
|
||||||
use = svg.use(href='#' + mangle_name(target), transform=transform)
|
use = svg.use(href='#' + mangle_name(target), transform=transform)
|
||||||
svg_group.add(use)
|
svg_group.add(use)
|
||||||
|
|
||||||
|
|
|
||||||
70
masque/test/test_svg.py
Normal file
70
masque/test/test_svg.py
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
from pathlib import Path
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
import pytest
|
||||||
|
from numpy.testing import assert_allclose
|
||||||
|
|
||||||
|
pytest.importorskip("svgwrite")
|
||||||
|
|
||||||
|
from ..library import Library
|
||||||
|
from ..pattern import Pattern
|
||||||
|
from ..file import svg
|
||||||
|
|
||||||
|
|
||||||
|
SVG_NS = "{http://www.w3.org/2000/svg}"
|
||||||
|
XLINK_HREF = "{http://www.w3.org/1999/xlink}href"
|
||||||
|
|
||||||
|
|
||||||
|
def _child_transform(svg_path: Path) -> tuple[float, ...]:
|
||||||
|
root = ET.fromstring(svg_path.read_text())
|
||||||
|
for use in root.iter(f"{SVG_NS}use"):
|
||||||
|
if use.attrib.get(XLINK_HREF) == "#child":
|
||||||
|
raw = use.attrib["transform"]
|
||||||
|
assert raw.startswith("matrix(") and raw.endswith(")")
|
||||||
|
return tuple(float(value) for value in raw[7:-1].split())
|
||||||
|
raise AssertionError("No child reference found in SVG output")
|
||||||
|
|
||||||
|
|
||||||
|
def test_svg_ref_rotation_uses_correct_affine_transform(tmp_path: Path) -> None:
|
||||||
|
lib = Library()
|
||||||
|
child = Pattern()
|
||||||
|
child.polygon("1", vertices=[[0, 0], [1, 0], [0, 1]])
|
||||||
|
lib["child"] = child
|
||||||
|
|
||||||
|
top = Pattern()
|
||||||
|
top.ref("child", offset=(3, 4), rotation=numpy.pi / 2, scale=2)
|
||||||
|
lib["top"] = top
|
||||||
|
|
||||||
|
svg_path = tmp_path / "rotation.svg"
|
||||||
|
svg.writefile(lib, "top", str(svg_path))
|
||||||
|
|
||||||
|
assert_allclose(_child_transform(svg_path), (0, 2, -2, 0, 3, 4), atol=1e-10)
|
||||||
|
|
||||||
|
|
||||||
|
def test_svg_ref_mirroring_changes_affine_transform(tmp_path: Path) -> None:
|
||||||
|
base = Library()
|
||||||
|
child = Pattern()
|
||||||
|
child.polygon("1", vertices=[[0, 0], [1, 0], [0, 1]])
|
||||||
|
base["child"] = child
|
||||||
|
|
||||||
|
top_plain = Pattern()
|
||||||
|
top_plain.ref("child", offset=(3, 4), rotation=numpy.pi / 2, scale=2, mirrored=False)
|
||||||
|
base["plain"] = top_plain
|
||||||
|
|
||||||
|
plain_path = tmp_path / "plain.svg"
|
||||||
|
svg.writefile(base, "plain", str(plain_path))
|
||||||
|
plain_transform = _child_transform(plain_path)
|
||||||
|
|
||||||
|
mirrored = Library()
|
||||||
|
mirrored["child"] = child.deepcopy()
|
||||||
|
top_mirrored = Pattern()
|
||||||
|
top_mirrored.ref("child", offset=(3, 4), rotation=numpy.pi / 2, scale=2, mirrored=True)
|
||||||
|
mirrored["mirrored"] = top_mirrored
|
||||||
|
|
||||||
|
mirrored_path = tmp_path / "mirrored.svg"
|
||||||
|
svg.writefile(mirrored, "mirrored", str(mirrored_path))
|
||||||
|
mirrored_transform = _child_transform(mirrored_path)
|
||||||
|
|
||||||
|
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)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue