mathandy / svgpathtools

A collection of tools for manipulating and analyzing SVG Path objects and Bezier curves.
MIT License
556 stars 139 forks source link

Arcs with rotation transforms are not properly interpreted #126

Open abey79 opened 4 years ago

abey79 commented 4 years ago

Setup

python 3.8.6 svgpathtools master (45dc873f82fba28ae905eb4262446cc54908a8a8)

Expected

Arc with rotation transform are interpreted correctly:

E.g.:

[...]
    <g>
        <g transform="translate(40,40)">
            <path style="fill:none;stroke:#666" d="M12 -12 A12 12 0 0 1 -12 -12"/>
        </g>
        <g transform="translate(80,40) rotate(36)">
            <path style="fill:none;stroke:#666" d="M12 -12 A12 12 0 0 1 -12 -12"/>
        </g>
    </g>
    <g transform="translate(40,80)">
        <path style="fill:none;stroke:#666" d="M12 -12 A12 12 0 0 1 -12 -12"/>
    </g>
    <g transform="translate(80,80)">
        <g transform="rotate(36)">
            <path style="fill:none;stroke:#666" d="M12 -12 A12 12 0 0 1 -12 -12"/>
        </g>
    </g>
[...]

Full test file: arc_with_rotate.svg.zip

Displayed by macOS:

image

Actual

Arcs with a rotation transform (the ones on the right in this example) are deformed when loaded with svgpathtools.

import math

import matplotlib.pyplot as plt
import numpy as np
import svgpathtools as svg

doc = svg.Document("arc_with_rotate.svg")
paths = doc.paths()

quantization = 1

for elem in paths:
    step = int(math.ceil(elem.length() / quantization))
    coords = np.empty(step + 1, dtype=complex)
    coords[0] = elem.point(0)
    for i in range(step):
        coords[i + 1] = elem.point((i + 1) / step)

    plt.plot(np.real(coords), np.imag(coords), "-")

plt.axis("equal")
plt.gca().invert_yaxis()
plt.show()
image
abey79 commented 4 years ago

This simple test case is fixed by the following modification:

--- a/svgpathtools/path.py
+++ b/svgpathtools/path.py
@@ -288,7 +288,7 @@ def transform(curve, tf):
     elif isinstance(curve, Arc):
         new_start = to_complex(tf.dot(to_point(curve.start)))
         new_end = to_complex(tf.dot(to_point(curve.end)))
-        new_radius = to_complex(tf.dot(to_vector(curve.radius)))
+        new_radius = curve.radius
         if tf[0][0] * tf[1][1] >= 0.0:
             new_sweep = curve.sweep
         else:

However, I am unsure if this induces other regressions.