diff --git a/scour.py b/scour.py
index c1a1ace..6ad1c58 100755
--- a/scour.py
+++ b/scour.py
@@ -2335,123 +2335,164 @@ def optimizeTransform(transform):
The transformation list is modified in-place.
"""
+ # FIXME: reordering these would optimize even more cases:
+ # first: Fold consecutive runs of the same transformation
+ # extra: Attempt to cast between types to create sameness:
+ # "matrix(0 1 -1 0 0 0) rotate(180) scale(-1)" all
+ # are rotations (90, 180, 180) -- thus "rotate(90)"
+ # second: Simplify transforms where numbers are optional.
+ # third: Attempt to simplify any single remaining matrix()
+ #
# if there's only one transformation and it's a matrix,
# try to make it a shorter non-matrix transformation
+ # NOTE: as matrix(a b c d e f) in SVG means the matrix:
+ # |¯ a c e ¯| make constants |¯ A1 A2 A3 ¯|
+ # | b d f | translating them | B1 B2 B3 |
+ # |_ 0 0 1 _| to more readable |_ 0 0 1 _|
if len(transform) == 1 and transform[0][0] == 'matrix':
+ matrix = A1, B1, A2, B2, A3, B3 = transform[0][1]
# |¯ 1 0 0 ¯|
# | 0 1 0 | Identity matrix (no transformation)
# |_ 0 0 1 _|
- if transform[0][1] == [1, 0, 0, 0, 1, 0]:
+ if matrix == [1, 0, 0, 1, 0, 0]:
del transform[0]
# |¯ 1 0 X ¯|
# | 0 1 Y | Translation by (X, Y).
# |_ 0 0 1 _|
- if (transform[0][1][0] == 1 and
- transform[0][1][1] == 0 and
- transform[0][1][3] == 0 and
- transform[0][1][4] == 1):
- transform[0] = ('translate', [
- transform[0][1][2], transform[0][1][5]
- ])
+ elif (A1 == 1 and A2 == 0
+ and B1 == 0 and B2 == 1):
+ transform[0] = ('translate', [A3, B3])
# |¯ X 0 0 ¯|
# | 0 Y 0 | Scaling by (X, Y).
# |_ 0 0 1 _|
- elif (transform[0][1][1] == 0 and
- transform[0][1][2] == 0 and
- transform[0][1][3] == 0 and
- transform[0][1][5] == 0):
- transform[0] = ('scale', [
- transform[0][1][0], transform[0][1][4]
- ])
+ elif ( A2 == 0 and A3 == 0
+ and B1 == 0 and B3 == 0):
+ transform[0] = ('scale', [A1, B2])
# |¯ cos(A) -sin(A) 0 ¯| Rotation by angle A,
# | sin(A) cos(A) 0 | clockwise, about the origin.
- # |_ 0 0 1 _| A is in degrees.
- elif (transform[0][1][0] == transform[0][1][4] and
- transform[0][1][1] == -transform[0][1][3] and
- transform[0][1][0] >= 0 and
- transform[0][1][0] <= 1 and
- transform[0][1][3] == sqrt(1 - transform[0][1][0] ** 2) and
- transform[0][1][2] == 0 and
- transform[0][1][5] == 0):
- transform[0] = ('rotate', [
- # What allows us to get the angle from the matrix
- # is the inverse sine of sin(A), which is the 4th
- # matrix component, or the inverse cosine of cos(A),
- # which is the 1st matrix component. I chose sin(A).
- # math.asin returns radians, so convert.
- # SVG demands degrees.
- Decimal(math.degrees(math.asin(transform[0][1][4])))
- ])
+ # |_ 0 0 1 _| A is in degrees, [-180...180].
+ elif (A1 == B2 and -1 <= A1 <= 1 and A3 == 0
+ and -B1 == A2 and -1 <= B1 <= 1 and B3 == 0
+ # as cos² A + sin² A == 1 and as decimal trig is approximate:
+ # FIXME: the "epsilon" term here should really be some function
+ # of the precision of the (sin|cos)_A terms, not 1e-15:
+ and abs((B1 ** 2) + (A1 ** 2) - 1) < Decimal("1e-15")):
+ sin_A, cos_A = B1, A1
+ # while asin(A) and acos(A) both only have an 180° range
+ # the sign of sin(A) and cos(A) varies across quadrants,
+ # letting us hone in on the angle the matrix represents:
+ # -- => < -90 | -+ => -90..0 | ++ => 0..90 | +- => >= 90
+ #
+ # http://en.wikipedia.org/wiki/File:Sine_cosine_plot.svg
+ # shows asin has the correct angle the middle quadrants:
+ A = Decimal(str(math.degrees(math.asin(float(sin_A)))))
+ if cos_A < 0: # otherwise needs adjusting from the edges
+ if sin_A < 0:
+ A = -180 - A
+ else:
+ A = 180 - A
+ transform[0] = ('rotate', [A])
# Simplify transformations where numbers are optional.
- for singleTransform in transform:
- if singleTransform[0] == 'translate':
+ for type, args in transform:
+ if type == 'translate':
# Only the X coordinate is required for translations.
# If the Y coordinate is unspecified, it's 0.
- if (len(singleTransform[1]) == 2 and
- singleTransform[1][1] == 0):
- del singleTransform[1][1]
- elif singleTransform[0] == 'rotate':
+ if len(args) == 2 and args[1] == 0:
+ del args[1]
+ elif type == 'rotate':
# Only the angle is required for rotations.
# If the coordinates are unspecified, it's the origin (0, 0).
- if (len(singleTransform[1]) == 3 and
- singleTransform[1][1] == 0 and
- singleTransform[1][2] == 0):
- del singleTransform[1][1:]
- elif singleTransform[0] == 'scale':
+ if len(args) == 3 and args[1] == args[2] == 0:
+ del args[1:]
+ elif type == 'scale':
# Only the X scaling factor is required.
# If the Y factor is unspecified, it's the same as X.
- if (len(singleTransform[1]) == 2 and
- singleTransform[1][0] == singleTransform[1][1]):
- del singleTransform[1][1]
-
+ if len(args) == 2 and args[0] == args[1]:
+ del args[1]
+
# Attempt to coalesce runs of the same transformation.
# Translations followed immediately by other translations,
# rotations followed immediately by other rotations,
# scaling followed immediately by other scaling,
# are safe to add.
- # A matrix followed immediately by another matrix
- # would be safe to multiply together, too.
+ # Identity skewX/skewY are safe to remove, but how do they accrete?
+ # |¯ 1 0 0 ¯|
+ # | tan(A) 1 0 | skews X coordinates by angle A
+ # |_ 0 0 1 _|
+ #
+ # |¯ 1 tan(A) 0 ¯|
+ # | 0 1 0 | skews Y coordinates by angle A
+ # |_ 0 0 1 _|
+ #
+ # FIXME: A matrix followed immediately by another matrix
+ # would be safe to multiply together, too.
i = 1
while i < len(transform):
- if transform[i][0] == transform[i - 1][0] == 'translate':
- transform[i - 1][1][0] += transform[i][1][0] # x
+ currType, currArgs = transform[i]
+ prevType, prevArgs = transform[i - 1]
+ if currType == prevType == 'translate':
+ prevArgs[0] += currArgs[0] # x
# for y, only add if the second translation has an explicit y
- if len(transform[i][1]) == 2:
- if len(transform[i - 1][1]) == 2:
- transform[i - 1][1][1] += transform[i][1][1] # y
- elif len(transform[i - 1][1]) == 1:
- transform[i - 1][1].append(transform[i][1][1]) # y
+ if len(currArgs) == 2:
+ if len(prevArgs) == 2:
+ prevArgs[1] += currArgs[1] # y
+ elif len(prevArgs) == 1:
+ prevArgs.append(currArgs[1]) # y
del transform[i]
- if transform[i - 1][1][0] == transform[i - 1][1][1] == 0:
+ if prevArgs[0] == prevArgs[1] == 0:
# Identity translation!
i -= 1
del transform[i]
- elif (transform[i][0] == transform[i - 1][0] == 'rotate'
- and len(transform[i - 1][1]) == len(transform[i][1]) == 1):
+ elif (currType == prevType == 'rotate'
+ and len(prevArgs) == len(currArgs) == 1):
# Only coalesce if both rotations are from the origin.
- transform[i - 1][1][0] += transform[i][1][0] # angle
+ prevArgs[0] += currArgs[0] # angle
+ # 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]
- elif transform[i][0] == transform[i - 1][0] == 'scale':
- transform[i - 1][1][0] *= transform[i][1][0] # x
+ elif currType == prevType == 'scale':
+ prevArgs[0] *= currArgs[0] # x
# handle an implicit y
- if len(transform[i - 1][1]) == 2 and len(transform[i][1]) == 2:
+ if len(prevArgs) == 2 and len(currArgs) == 2:
# y1 * y2
- transform[i - 1][1][1] *= transform[i][1][1]
- elif len(transform[i - 1][1]) == 1 and len(transform[i][1]) == 2:
+ prevArgs[1] *= currArgs[1]
+ elif len(prevArgs) == 1 and len(currArgs) == 2:
# create y2 = uniformscalefactor1 * y2
- transform[i - 1][1].append(transform[i - 1][1][0] * transform[i][1][1])
- elif len(transform[i - 1][1]) == 2 and len(transform[i][1]) == 1:
+ prevArgs.append(prevArgs[0] * currArgs[1])
+ elif len(prevArgs) == 2 and len(currArgs) == 1:
# y1 * uniformscalefactor2
- transform[i - 1][1][1] *= transform[i][1][0]
+ prevArgs[1] *= currArgs[0]
del transform[i]
- if transform[i - 1][1][0] == transform[i - 1][1][1] == 1:
+ if prevArgs[0] == prevArgs[1] == 1:
# Identity scale!
i -= 1
del transform[i]
else:
i += 1
+ # Some fixups are needed for single-element transformation lists, since
+ # the loop above was to coalesce elements with their predecessors in the
+ # list, and thus it required 2 elements.
+ i = 0
+ while i < len(transform):
+ currType, currArgs = transform[i]
+ if ((currType == 'skewX' or currType == 'skewY')
+ and len(currArgs) == 1 and currArgs[0] == 0):
+ # Identity skew!
+ del transform[i]
+ elif ((currType == 'rotate')
+ and len(currArgs) == 1 and currArgs[0] == 0):
+ # Identity rotation!
+ del transform[i]
+ else:
+ i += 1
+
def optimizeTransforms(element, options) :
"""
Attempts to optimise transform specifications on the given node and its children.
@@ -2470,7 +2511,10 @@ def optimizeTransforms(element, options) :
newVal = serializeTransform(transform)
if len(newVal) < len(val):
- element.setAttribute(transformAttr, newVal)
+ if len(newVal):
+ element.setAttribute(transformAttr, newVal)
+ else:
+ element.removeAttribute(transformAttr)
num += len(val) - len(newVal)
for child in element.childNodes:
diff --git a/testscour.py b/testscour.py
index 82f1ab3..f5084d8 100755
--- a/testscour.py
+++ b/testscour.py
@@ -1217,6 +1217,96 @@ class RemoveDefsWithWhitespace(unittest.TestCase):
self.assertEquals(doc.getElementsByTagName('defs').length, 0,
'Kept defs, although it contains only whitespace or is ')
+class TransformIdentityMatrix(unittest.TestCase):
+ def runTest(self):
+ doc = scour.scourXmlFile('unittests/transform-matrix-is-identity.svg')
+ self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), '',
+ 'Transform containing identity matrix not removed')
+
+class TransformRotate135(unittest.TestCase):
+ def runTest(self):
+ doc = scour.scourXmlFile('unittests/transform-matrix-is-rotate-135.svg')
+ self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), 'rotate(135)',
+ 'Rotation matrix not converted to rotate(135)')
+
+class TransformRotate45(unittest.TestCase):
+ def runTest(self):
+ doc = scour.scourXmlFile('unittests/transform-matrix-is-rotate-45.svg')
+ self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), 'rotate(45)',
+ 'Rotation matrix not converted to rotate(45)')
+
+class TransformRotate90(unittest.TestCase):
+ def runTest(self):
+ doc = scour.scourXmlFile('unittests/transform-matrix-is-rotate-90.svg')
+ self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), 'rotate(90)',
+ 'Rotation matrix not converted to rotate(90)')
+
+class TransformRotateCCW135(unittest.TestCase):
+ def runTest(self):
+ doc = scour.scourXmlFile('unittests/transform-matrix-is-rotate-neg-135.svg')
+ self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), 'rotate(-135)',
+ 'Counter-clockwise rotation matrix not converted to rotate(-135)')
+
+class TransformRotateCCW45(unittest.TestCase):
+ def runTest(self):
+ doc = scour.scourXmlFile('unittests/transform-matrix-is-rotate-neg-45.svg')
+ self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), 'rotate(-45)',
+ 'Counter-clockwise rotation matrix not converted to rotate(-45)')
+
+class TransformRotateCCW90(unittest.TestCase):
+ def runTest(self):
+ doc = scour.scourXmlFile('unittests/transform-matrix-is-rotate-neg-90.svg')
+ self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), 'rotate(-90)',
+ 'Counter-clockwise rotation matrix not converted to rotate(-90)')
+
+class TransformScale2by3(unittest.TestCase):
+ def runTest(self):
+ doc = scour.scourXmlFile('unittests/transform-matrix-is-scale-2-3.svg')
+ self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), 'scale(2 3)',
+ 'Scaling matrix not converted to scale(2 3)')
+
+class TransformScaleMinus1(unittest.TestCase):
+ def runTest(self):
+ doc = scour.scourXmlFile('unittests/transform-matrix-is-scale-neg-1.svg')
+ self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), 'scale(-1)',
+ 'Scaling matrix not converted to scale(-1)')
+
+class TransformTranslate(unittest.TestCase):
+ def runTest(self):
+ doc = scour.scourXmlFile('unittests/transform-matrix-is-translate.svg')
+ self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), 'translate(2 3)',
+ 'Translation matrix not converted to translate(2 3)')
+
+class TransformRotation3Args(unittest.TestCase):
+ def runTest(self):
+ doc = scour.scourXmlFile('unittests/transform-rotate-fold-3args.svg')
+ self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), 'rotate(90)',
+ 'Optional zeroes in rotate(angle 0 0) not removed')
+
+class TransformIdentityRotation(unittest.TestCase):
+ def runTest(self):
+ doc = scour.scourXmlFile('unittests/transform-rotate-is-identity.svg')
+ self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), '',
+ 'Transform containing identity rotation not removed')
+
+class TransformIdentitySkewX(unittest.TestCase):
+ def runTest(self):
+ doc = scour.scourXmlFile('unittests/transform-skewX-is-identity.svg')
+ self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), '',
+ 'Transform containing identity X-axis skew not removed')
+
+class TransformIdentitySkewY(unittest.TestCase):
+ def runTest(self):
+ doc = scour.scourXmlFile('unittests/transform-skewY-is-identity.svg')
+ self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), '',
+ 'Transform containing identity Y-axis skew not removed')
+
+class TransformIdentityTranslate(unittest.TestCase):
+ def runTest(self):
+ doc = scour.scourXmlFile('unittests/transform-translate-is-identity.svg')
+ self.assertEquals(doc.getElementsByTagName('line')[0].getAttribute('transform'), '',
+ 'Transform containing identity translation not removed')
+
class DuplicateGradientsUpdateStyle(unittest.TestCase):
def runTest(self):
doc = scour.scourXmlFile('unittests/duplicate-gradients-update-style.svg',
@@ -1233,7 +1323,6 @@ class DuplicateGradientsUpdateStyle(unittest.TestCase):
# TODO; write a test for embedding rasters
# TODO: write a test for --disable-embed-rasters
# TODO: write tests for --keep-editor-data
-# TODO: write tests for scouring transformations
if __name__ == '__main__':
testcss = __import__('testcss')
diff --git a/unittests/adobe.svg b/unittests/adobe.svg
index 04b5e11..7dd7e73 100644
--- a/unittests/adobe.svg
+++ b/unittests/adobe.svg
@@ -42,4 +42,4 @@
xmlns:sfw="http://ns.adobe.com/SaveForWeb/1.0/"
sfw:baz="1"
ok:baz="1" />
-
\ No newline at end of file
+
diff --git a/unittests/cascading-default-attribute-removal.svg b/unittests/cascading-default-attribute-removal.svg
index d43a666..dbc3698 100644
--- a/unittests/cascading-default-attribute-removal.svg
+++ b/unittests/cascading-default-attribute-removal.svg
@@ -20,4 +20,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/cdata.svg b/unittests/cdata.svg
index f750f4a..8ecb680 100644
--- a/unittests/cdata.svg
+++ b/unittests/cdata.svg
@@ -3,4 +3,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/collapse-gradients-gradientUnits.svg b/unittests/collapse-gradients-gradientUnits.svg
index 7f79503..76f6169 100644
--- a/unittests/collapse-gradients-gradientUnits.svg
+++ b/unittests/collapse-gradients-gradientUnits.svg
@@ -8,4 +8,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/collapse-gradients.svg b/unittests/collapse-gradients.svg
index ef85006..a45f962 100644
--- a/unittests/collapse-gradients.svg
+++ b/unittests/collapse-gradients.svg
@@ -8,4 +8,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/collapse-same-path-points.svg b/unittests/collapse-same-path-points.svg
index 345bdd6..bda0fff 100644
--- a/unittests/collapse-same-path-points.svg
+++ b/unittests/collapse-same-path-points.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
diff --git a/unittests/comment-beside-xml-decl.svg b/unittests/comment-beside-xml-decl.svg
index 416c770..86e6413 100644
--- a/unittests/comment-beside-xml-decl.svg
+++ b/unittests/comment-beside-xml-decl.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
diff --git a/unittests/comments.svg b/unittests/comments.svg
index a588593..06a75f2 100644
--- a/unittests/comments.svg
+++ b/unittests/comments.svg
@@ -3,4 +3,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/commonized-referenced-elements.svg b/unittests/commonized-referenced-elements.svg
index 3a84239..3a152fb 100644
--- a/unittests/commonized-referenced-elements.svg
+++ b/unittests/commonized-referenced-elements.svg
@@ -6,4 +6,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/consecutive-hlines.svg b/unittests/consecutive-hlines.svg
index c8480e0..caae623 100644
--- a/unittests/consecutive-hlines.svg
+++ b/unittests/consecutive-hlines.svg
@@ -3,4 +3,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/css-reference.svg b/unittests/css-reference.svg
index 4accbb1..6330c60 100644
--- a/unittests/css-reference.svg
+++ b/unittests/css-reference.svg
@@ -24,4 +24,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/dont-collapse-gradients.svg b/unittests/dont-collapse-gradients.svg
index 4bbab49..00b58f5 100644
--- a/unittests/dont-collapse-gradients.svg
+++ b/unittests/dont-collapse-gradients.svg
@@ -10,4 +10,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/dont-convert-short-color-names.svg b/unittests/dont-convert-short-color-names.svg
index a350ead..cbcece7 100644
--- a/unittests/dont-convert-short-color-names.svg
+++ b/unittests/dont-convert-short-color-names.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
diff --git a/unittests/duplicate-gradient-stops-pct.svg b/unittests/duplicate-gradient-stops-pct.svg
index 8aecd1b..43c99c4 100644
--- a/unittests/duplicate-gradient-stops-pct.svg
+++ b/unittests/duplicate-gradient-stops-pct.svg
@@ -9,4 +9,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/empty-g.svg b/unittests/empty-g.svg
index b754a03..ccb7355 100644
--- a/unittests/empty-g.svg
+++ b/unittests/empty-g.svg
@@ -4,4 +4,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/empty-metadata.svg b/unittests/empty-metadata.svg
index 1d373d3..ca3c31f 100644
--- a/unittests/empty-metadata.svg
+++ b/unittests/empty-metadata.svg
@@ -1,3 +1,3 @@
\ No newline at end of file
+
diff --git a/unittests/empty-style.svg b/unittests/empty-style.svg
index 34bab4c..a2d2afd 100644
--- a/unittests/empty-style.svg
+++ b/unittests/empty-style.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
diff --git a/unittests/fill-none.svg b/unittests/fill-none.svg
index c8892a9..6442c90 100644
--- a/unittests/fill-none.svg
+++ b/unittests/fill-none.svg
@@ -2,4 +2,4 @@
\ No newline at end of file
+
diff --git a/unittests/font-styles.svg b/unittests/font-styles.svg
index a575ab2..e4120df 100644
--- a/unittests/font-styles.svg
+++ b/unittests/font-styles.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
diff --git a/unittests/full-metadata.svg b/unittests/full-metadata.svg
index c5e023c..f67e01d 100644
--- a/unittests/full-metadata.svg
+++ b/unittests/full-metadata.svg
@@ -19,4 +19,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/gradient-default-attrs.svg b/unittests/gradient-default-attrs.svg
index 9eb569c..36fd0e7 100644
--- a/unittests/gradient-default-attrs.svg
+++ b/unittests/gradient-default-attrs.svg
@@ -7,4 +7,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/groups-in-switch-with-id.svg b/unittests/groups-in-switch-with-id.svg
index dd4f90a..317cfcc 100644
--- a/unittests/groups-in-switch-with-id.svg
+++ b/unittests/groups-in-switch-with-id.svg
@@ -15,4 +15,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/groups-in-switch.svg b/unittests/groups-in-switch.svg
index 0c1fd06..96394fd 100644
--- a/unittests/groups-in-switch.svg
+++ b/unittests/groups-in-switch.svg
@@ -15,4 +15,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/groups-with-title-desc.svg b/unittests/groups-with-title-desc.svg
index 2ca55b5..7983dc0 100644
--- a/unittests/groups-with-title-desc.svg
+++ b/unittests/groups-with-title-desc.svg
@@ -10,4 +10,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/ids-to-strip.svg b/unittests/ids-to-strip.svg
index 8e25f91..1ac59bc 100644
--- a/unittests/ids-to-strip.svg
+++ b/unittests/ids-to-strip.svg
@@ -8,4 +8,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/important-groups-in-defs.svg b/unittests/important-groups-in-defs.svg
index 795e9fd..18ba1df 100644
--- a/unittests/important-groups-in-defs.svg
+++ b/unittests/important-groups-in-defs.svg
@@ -9,4 +9,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/inkscape.svg b/unittests/inkscape.svg
index 0cec626..a51ad49 100644
--- a/unittests/inkscape.svg
+++ b/unittests/inkscape.svg
@@ -4,4 +4,4 @@ xmlns:foo="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="0.46" version="1.0">
-
\ No newline at end of file
+
diff --git a/unittests/metadata-with-text.svg b/unittests/metadata-with-text.svg
index b20371d..6149b68 100644
--- a/unittests/metadata-with-text.svg
+++ b/unittests/metadata-with-text.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
diff --git a/unittests/move-common-attributes-to-grandparent.svg b/unittests/move-common-attributes-to-grandparent.svg
index 27b4867..4e202bd 100644
--- a/unittests/move-common-attributes-to-grandparent.svg
+++ b/unittests/move-common-attributes-to-grandparent.svg
@@ -7,4 +7,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/move-common-attributes-to-parent.svg b/unittests/move-common-attributes-to-parent.svg
index 8833347..f390c89 100644
--- a/unittests/move-common-attributes-to-parent.svg
+++ b/unittests/move-common-attributes-to-parent.svg
@@ -10,4 +10,4 @@
Goodbye
Cruel World!
-
\ No newline at end of file
+
diff --git a/unittests/nested-useless-groups.svg b/unittests/nested-useless-groups.svg
index 168a886..73b5f88 100644
--- a/unittests/nested-useless-groups.svg
+++ b/unittests/nested-useless-groups.svg
@@ -6,4 +6,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/path-abs-to-rel.svg b/unittests/path-abs-to-rel.svg
index 0171c57..c9cc803 100644
--- a/unittests/path-abs-to-rel.svg
+++ b/unittests/path-abs-to-rel.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
diff --git a/unittests/path-bez-optimize.svg b/unittests/path-bez-optimize.svg
index 8d3cc93..97bfdd1 100644
--- a/unittests/path-bez-optimize.svg
+++ b/unittests/path-bez-optimize.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
diff --git a/unittests/path-empty-move.svg b/unittests/path-empty-move.svg
index f5df077..d3b63d7 100644
--- a/unittests/path-empty-move.svg
+++ b/unittests/path-empty-move.svg
@@ -2,4 +2,4 @@
\ No newline at end of file
+
diff --git a/unittests/path-implicit-line.svg b/unittests/path-implicit-line.svg
index ecbbe9a..a42848e 100644
--- a/unittests/path-implicit-line.svg
+++ b/unittests/path-implicit-line.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
diff --git a/unittests/path-quad-optimize.svg b/unittests/path-quad-optimize.svg
index 097a631..bbe3bc9 100644
--- a/unittests/path-quad-optimize.svg
+++ b/unittests/path-quad-optimize.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
diff --git a/unittests/path-sn.svg b/unittests/path-sn.svg
index 87247e1..0b9f7d2 100644
--- a/unittests/path-sn.svg
+++ b/unittests/path-sn.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
diff --git a/unittests/path-with-closepath.svg b/unittests/path-with-closepath.svg
index 41f8403..80858ca 100644
--- a/unittests/path-with-closepath.svg
+++ b/unittests/path-with-closepath.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
diff --git a/unittests/polygon-coord.svg b/unittests/polygon-coord.svg
index 25406b3..15940d4 100644
--- a/unittests/polygon-coord.svg
+++ b/unittests/polygon-coord.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
diff --git a/unittests/polygon.svg b/unittests/polygon.svg
index f756050..d927a00 100644
--- a/unittests/polygon.svg
+++ b/unittests/polygon.svg
@@ -2,4 +2,4 @@
\ No newline at end of file
+
diff --git a/unittests/polyline-coord.svg b/unittests/polyline-coord.svg
index 94c0e2f..fc209ed 100644
--- a/unittests/polyline-coord.svg
+++ b/unittests/polyline-coord.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
diff --git a/unittests/quot-in-url.svg b/unittests/quot-in-url.svg
index b27a56d..6d82567 100644
--- a/unittests/quot-in-url.svg
+++ b/unittests/quot-in-url.svg
@@ -7,4 +7,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/redundant-svg-namespace.svg b/unittests/redundant-svg-namespace.svg
index 66eb773..5022693 100644
--- a/unittests/redundant-svg-namespace.svg
+++ b/unittests/redundant-svg-namespace.svg
@@ -5,4 +5,4 @@
Test
-
\ No newline at end of file
+
diff --git a/unittests/referenced-elements-1.svg b/unittests/referenced-elements-1.svg
index 4de5b00..e779080 100644
--- a/unittests/referenced-elements-1.svg
+++ b/unittests/referenced-elements-1.svg
@@ -8,4 +8,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/refs-in-defs.svg b/unittests/refs-in-defs.svg
index 11ca780..8636c5a 100644
--- a/unittests/refs-in-defs.svg
+++ b/unittests/refs-in-defs.svg
@@ -5,4 +5,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/remove-unused-attributes-on-parent.svg b/unittests/remove-unused-attributes-on-parent.svg
index 971a555..7f68d15 100644
--- a/unittests/remove-unused-attributes-on-parent.svg
+++ b/unittests/remove-unused-attributes-on-parent.svg
@@ -5,4 +5,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/scour-lengths.svg b/unittests/scour-lengths.svg
index 5b1d0dc..f5c0d5c 100644
--- a/unittests/scour-lengths.svg
+++ b/unittests/scour-lengths.svg
@@ -2,4 +2,4 @@
\ No newline at end of file
+
diff --git a/unittests/shorten-ids.svg b/unittests/shorten-ids.svg
index 5e0fdf6..7852c57 100644
--- a/unittests/shorten-ids.svg
+++ b/unittests/shorten-ids.svg
@@ -7,4 +7,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/straight-curve.svg b/unittests/straight-curve.svg
index 48498d6..95cd862 100644
--- a/unittests/straight-curve.svg
+++ b/unittests/straight-curve.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
diff --git a/unittests/stroke-none.svg b/unittests/stroke-none.svg
index 5adf741..4582a85 100644
--- a/unittests/stroke-none.svg
+++ b/unittests/stroke-none.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
diff --git a/unittests/stroke-nowidth.svg b/unittests/stroke-nowidth.svg
index b84c60a..2ca5809 100644
--- a/unittests/stroke-nowidth.svg
+++ b/unittests/stroke-nowidth.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
diff --git a/unittests/stroke-transparent.svg b/unittests/stroke-transparent.svg
index bdeec74..4ff39a2 100644
--- a/unittests/stroke-transparent.svg
+++ b/unittests/stroke-transparent.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
diff --git a/unittests/style-to-attr.svg b/unittests/style-to-attr.svg
index 570a802..3bbe3a0 100644
--- a/unittests/style-to-attr.svg
+++ b/unittests/style-to-attr.svg
@@ -6,4 +6,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/transform-matrix-is-identity.svg b/unittests/transform-matrix-is-identity.svg
new file mode 100644
index 0000000..9764b28
--- /dev/null
+++ b/unittests/transform-matrix-is-identity.svg
@@ -0,0 +1,3 @@
+
diff --git a/unittests/transform-matrix-is-rotate-135.svg b/unittests/transform-matrix-is-rotate-135.svg
new file mode 100644
index 0000000..a0583bc
--- /dev/null
+++ b/unittests/transform-matrix-is-rotate-135.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/unittests/transform-matrix-is-rotate-45.svg b/unittests/transform-matrix-is-rotate-45.svg
new file mode 100644
index 0000000..1749d98
--- /dev/null
+++ b/unittests/transform-matrix-is-rotate-45.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/unittests/transform-matrix-is-rotate-90.svg b/unittests/transform-matrix-is-rotate-90.svg
new file mode 100644
index 0000000..269d526
--- /dev/null
+++ b/unittests/transform-matrix-is-rotate-90.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/unittests/transform-matrix-is-rotate-neg-135.svg b/unittests/transform-matrix-is-rotate-neg-135.svg
new file mode 100644
index 0000000..1aa21ef
--- /dev/null
+++ b/unittests/transform-matrix-is-rotate-neg-135.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/unittests/transform-matrix-is-rotate-neg-45.svg b/unittests/transform-matrix-is-rotate-neg-45.svg
new file mode 100644
index 0000000..37b46e8
--- /dev/null
+++ b/unittests/transform-matrix-is-rotate-neg-45.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/unittests/transform-matrix-is-rotate-neg-90.svg b/unittests/transform-matrix-is-rotate-neg-90.svg
new file mode 100644
index 0000000..8fbbd4f
--- /dev/null
+++ b/unittests/transform-matrix-is-rotate-neg-90.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/unittests/transform-matrix-is-scale-2-3.svg b/unittests/transform-matrix-is-scale-2-3.svg
new file mode 100644
index 0000000..7a04ce5
--- /dev/null
+++ b/unittests/transform-matrix-is-scale-2-3.svg
@@ -0,0 +1,3 @@
+
diff --git a/unittests/transform-matrix-is-scale-neg-1.svg b/unittests/transform-matrix-is-scale-neg-1.svg
new file mode 100644
index 0000000..d402058
--- /dev/null
+++ b/unittests/transform-matrix-is-scale-neg-1.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/unittests/transform-matrix-is-translate.svg b/unittests/transform-matrix-is-translate.svg
new file mode 100644
index 0000000..0dfcd9d
--- /dev/null
+++ b/unittests/transform-matrix-is-translate.svg
@@ -0,0 +1,3 @@
+
diff --git a/unittests/transform-rotate-fold-3args.svg b/unittests/transform-rotate-fold-3args.svg
new file mode 100644
index 0000000..0139610
--- /dev/null
+++ b/unittests/transform-rotate-fold-3args.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/unittests/transform-rotate-is-identity.svg b/unittests/transform-rotate-is-identity.svg
new file mode 100644
index 0000000..198ba11
--- /dev/null
+++ b/unittests/transform-rotate-is-identity.svg
@@ -0,0 +1,3 @@
+
diff --git a/unittests/transform-skewX-is-identity.svg b/unittests/transform-skewX-is-identity.svg
new file mode 100644
index 0000000..b038c6e
--- /dev/null
+++ b/unittests/transform-skewX-is-identity.svg
@@ -0,0 +1,4 @@
+
diff --git a/unittests/transform-skewY-is-identity.svg b/unittests/transform-skewY-is-identity.svg
new file mode 100644
index 0000000..27da015
--- /dev/null
+++ b/unittests/transform-skewY-is-identity.svg
@@ -0,0 +1,4 @@
+
diff --git a/unittests/transform-translate-is-identity.svg b/unittests/transform-translate-is-identity.svg
new file mode 100644
index 0000000..6c62d23
--- /dev/null
+++ b/unittests/transform-translate-is-identity.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/unittests/unreferenced-linearGradient.svg b/unittests/unreferenced-linearGradient.svg
index 00807b0..f588eac 100644
--- a/unittests/unreferenced-linearGradient.svg
+++ b/unittests/unreferenced-linearGradient.svg
@@ -3,4 +3,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/unreferenced-pattern.svg b/unittests/unreferenced-pattern.svg
index 9e52d0e..7bcff58 100644
--- a/unittests/unreferenced-pattern.svg
+++ b/unittests/unreferenced-pattern.svg
@@ -3,4 +3,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/unreferenced-radialGradient.svg b/unittests/unreferenced-radialGradient.svg
index 671bdcb..bfa35c8 100644
--- a/unittests/unreferenced-radialGradient.svg
+++ b/unittests/unreferenced-radialGradient.svg
@@ -3,4 +3,4 @@
-
\ No newline at end of file
+
diff --git a/unittests/utf8.svg b/unittests/utf8.svg
index 57961e7..6c77d7a 100644
--- a/unittests/utf8.svg
+++ b/unittests/utf8.svg
@@ -2,4 +2,4 @@
\ No newline at end of file
+
diff --git a/unittests/whitespace-nested.svg b/unittests/whitespace-nested.svg
index 82d5930..3b99356 100644
--- a/unittests/whitespace-nested.svg
+++ b/unittests/whitespace-nested.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
diff --git a/unittests/xml-namespace-attrs.svg b/unittests/xml-namespace-attrs.svg
index b1c5b11..81c5fb4 100644
--- a/unittests/xml-namespace-attrs.svg
+++ b/unittests/xml-namespace-attrs.svg
@@ -21,4 +21,4 @@
-
\ No newline at end of file
+