Rotation transform functions do not correctly apply X Y arguments

In an SVG document, a rotation transform for a simple rotation about the origin may be written as: <rect transform="rotate(90)" [...] />, for an angle of 90°.

Rotation about a different point may be written as: <rect transform="rotate(90 50 125)" [...] /> or <rect transform="rotate(90, 50, 125)" [...] />, for a rotation angle of 90° about the point (50, 125).

I've attempted to input a number of different rotations:

xform = Transform("rotate(20)")
xform = Transform("rotate(30 50 125)")
xform = Transform("rotate(40, 50, 125)")
xform = Transform(rotate=(50))
xform = Transform(rotate=(60, 50, 125))
vectorlike = (50,125)
xform = Transform(rotate=(70, vectorlike))
vectorlike_2 = ImmutableVector2d(50,125)
xform = Transform(rotate=(80, vectorlike_2))
xform = Transform('rotate(90 10 12)')  # From tests
xform = Transform("translate(150,10)") #sanity check
xform = Transform("scale(0.5,1.5)")    #sanity check

Adding inkex.errormsg(str(xform)) after each, the output from running this test is as follows:

rotate(20)
rotate(30)
rotate(40)
rotate(50)
rotate(60)
rotate(80)
rotate(80)
rotate(90)
translate(150, 10)
scale(0.5, 1.5)

This appears to indicate that the rotations are not being parsed correctly, almost regardless of the input grammar used. However we cannot be certain, because this could also be (for example) the output __str__ function showing an issue.

To test, I added sys.stderr.write('deg, x, y: {}, {}, {}\n'.format(deg,center_x,center_y)) to def add_rotate(self, deg, *args), right below center_x, center_y = Vector2d(*args).

This gives result:

deg, x, y: 20.0, 0.0, 0.0
deg, x, y: 30.0, 50.0, 125.0
deg, x, y: 40.0, 50.0, 125.0
deg, x, y: 50, 0.0, 0.0
deg, x, y: 60, 50.0, 125.0
deg, x, y: 70, 50.0, 125.0
deg, x, y: 80, 50.0, 125.0
deg, x, y: 90.0, 10.0, 12.0

These look correct, showing that all of the inputs are being parsed correctly, but they are either not being applied or read out correctly.

The definition of add_rotate is currently as follows:

    def add_rotate(self, deg, *args):
        """Add rotation to this transformation"""
        center_x, center_y = Vector2d(*args)
        _cos, _sin = cos(radians(deg)), sin(radians(deg))
        self.__imul__(((_cos, -_sin, center_x), (_sin, _cos, center_y)))
        self.__imul__(((1.0, 0.0, -center_x), (0.0, 1.0, -center_y)))

This is not correctly implemented.

Per w3c references, rotating about a point other than the origin is done by using transformation matrices according to the recipe translate(<cx>, <cy>) rotate(<rotate-angle>) translate(-<cx>, -<cy>) . (Possibly with a sign error on the translations.)

    def add_rotate(self, deg, *args):
        """Add rotation to this transformation"""
        center_x, center_y = Vector2d(*args)
        _cos, _sin = cos(radians(deg)), sin(radians(deg))
        self.__imul__(((1.0, 0.0, -center_x), (0.0, 1.0, -center_y)))
        self.__imul__(((_cos, -_sin, 0), (_sin, _cos, 0)))
        self.__imul__(((1.0, 0.0, center_x), (0.0, 1.0, center_y)))

This, now, works correctly1.

To verify proper operation, we can try the following:

xform = Transform('rotate(90 50 125)')
inkex.errormsg(str(xform))
item.transform.add_matrix(xform)

This reports a value of: matrix(6.12323e-17 1 -1 6.12323e-17 175 75).

Testing with an SVG file, I can confirm that <rect transform="rotate(90 50 125)" [...] /> overlaps perfectly with <rect transform=transform="matrix(6.12323e-17 1 -1 6.12323e-17 175 75)" [...] /> and that both overlap perfectly with the rectangle rotated by item.transform.add_matrix(xform).

Attachments: test SVG file transform_tests7.svg, and test extension, implemented as a drop-in replacement for docinfo: docinfo.py

  1. This above code is correct with the corrected order of __imul__ operations from issue #255 (closed). As implemented with the incorrect __imul__ code currently code in master, the two translate operations that I've written down would need to be swapped.

To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information