Rotate transforms now optimize angles, and have test coverage.

This commit is contained in:
Johan Sundström 2011-03-12 03:35:13 -08:00
parent 45bc6c0fac
commit 5987a9a271
5 changed files with 52 additions and 14 deletions

View file

@ -2328,6 +2328,28 @@ def reducePrecision(element) :
return num return num
def optimizeAngle(angle):
"""
Because any rotation can be expressed within 360 degrees
of any given number, and since negative angles sometimes
are one character longer than corresponding positive angle,
we shorten the number to one in the range to [-90, 270[.
"""
# First, we put the new angle in the range ]-360, 360[.
# The modulo operator yields results with the sign of the
# divisor, so for negative dividends, we preserve the sign
# of the angle.
if angle < 0: angle %= -360
else: angle %= 360
# 720 degrees is unneccessary, as 360 covers all angles.
# As "-x" is shorter than "35x" and "-xxx" one character
# longer than positive angles <= 260, we constrain angle
# range to [-90, 270[ (or, equally valid: ]-100, 260]).
if angle >= 270: angle -= 360
elif angle < -90: angle += 360
return angle
def optimizeTransform(transform): def optimizeTransform(transform):
""" """
Optimises a series of transformations parsed from a single Optimises a series of transformations parsed from a single
@ -2401,6 +2423,7 @@ def optimizeTransform(transform):
if len(args) == 2 and args[1] == 0: if len(args) == 2 and args[1] == 0:
del args[1] del args[1]
elif type == 'rotate': elif type == 'rotate':
args[0] = optimizeAngle(args[0]) # angle
# Only the angle is required for rotations. # Only the angle is required for rotations.
# If the coordinates are unspecified, it's the origin (0, 0). # If the coordinates are unspecified, it's the origin (0, 0).
if len(args) == 3 and args[1] == args[2] == 0: if len(args) == 3 and args[1] == args[2] == 0:
@ -2447,14 +2470,7 @@ def optimizeTransform(transform):
elif (currType == prevType == 'rotate' elif (currType == prevType == 'rotate'
and len(prevArgs) == len(currArgs) == 1): and len(prevArgs) == len(currArgs) == 1):
# Only coalesce if both rotations are from the origin. # Only coalesce if both rotations are from the origin.
prevArgs[0] += currArgs[0] # angle prevArgs[0] = optimizeAngle(prevArgs[0] + currArgs[0])
# Put the new angle in the range ]-360, 360[.
# The modulo operator yields results with the sign of the
# divisor, so for negative dividends, we preserve the sign
# of the angle.
if prevArgs[0] < 0: prevArgs[0] = prevArgs[0] % -360
else: prevArgs[0] = prevArgs[0] % 360
del transform[i] del transform[i]
elif currType == prevType == 'scale': elif currType == prevType == 'scale':
prevArgs[0] *= currArgs[0] # x prevArgs[0] *= currArgs[0] # x

View file

@ -1243,9 +1243,9 @@ class TransformRotate90(unittest.TestCase):
class TransformRotateCCW135(unittest.TestCase): class TransformRotateCCW135(unittest.TestCase):
def runTest(self): def runTest(self):
doc = scour.scourXmlFile('unittests/transform-matrix-is-rotate-neg-135.svg') doc = scour.scourXmlFile('unittests/transform-matrix-is-rotate-225.svg')
self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), 'rotate(-135)', self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), 'rotate(225)',
'Counter-clockwise rotation matrix not converted to rotate(-135)') 'Counter-clockwise rotation matrix not converted to rotate(225)')
class TransformRotateCCW45(unittest.TestCase): class TransformRotateCCW45(unittest.TestCase):
def runTest(self): def runTest(self):
@ -1277,6 +1277,18 @@ class TransformTranslate(unittest.TestCase):
self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), 'translate(2 3)', self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), 'translate(2 3)',
'Translation matrix not converted to translate(2 3)') 'Translation matrix not converted to translate(2 3)')
class TransformRotationRange719_5(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/transform-rotate-trim-range-719.5.svg')
self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), 'rotate(-.5)',
'Transform containing rotate(719.5) not shortened to rotate(-.5)')
class TransformRotationRangeCCW540_0(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/transform-rotate-trim-range-neg-540.0.svg')
self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), 'rotate(180)',
'Transform containing rotate(-540.0) not shortened to rotate(180)')
class TransformRotation3Args(unittest.TestCase): class TransformRotation3Args(unittest.TestCase):
def runTest(self): def runTest(self):
doc = scour.scourXmlFile('unittests/transform-rotate-fold-3args.svg') doc = scour.scourXmlFile('unittests/transform-rotate-fold-3args.svg')

View file

Before

Width:  |  Height:  |  Size: 293 B

After

Width:  |  Height:  |  Size: 293 B

Before After
Before After

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="-9 0 9 9">
<line stroke="rgba(255,0,0,0.5)" y1="9" x1="9" transform="rotate(719.5)"/>
<!-- all rotation angles can be mapped to the shorter range [-90, 270[ -->
</svg>

After

Width:  |  Height:  |  Size: 289 B

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="-9 0 9 9">
<line stroke="rgba(255,0,0,0.5)" y1="9" x1="9" transform="rotate(-540.0) rotate(0)"/>
<!-- all rotation angles can be mapped to the shorter range [-90, 270[ -->
</svg>

After

Width:  |  Height:  |  Size: 300 B