User:Quibik/SvgGradientSimplify.py

# SVG Gradient Simplifier
#
# This script cleans up the linear gradients in an SVG file by applying the matrix transforms that may be present in the gradients.
# For example:
# <linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="-320.3149" y1="694.9482" x2="-319.3149" y2="694.9482" gradientTransform="matrix(-46.5269 252.9134 252.9134 46.5269 -190547.0313 48709.5078)">
# is changed into
# <linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="117.940" y1="31.363" x2="71.413" y2="284.276">
# The rest of the file is not modified in any way.
#
# Author: Quibik <http://commons.wikimedia.org/wiki/User:Quibik>
# Last modified: 2010-11-25
# Licensing: released into the public domain (attribution is still welcome, of course :)

from sys import argv
import re

def usage():
    print("This script cleans up the linear gradients in an SVG file by applying the matrix transforms that may be present in the gradients.\n\n"
          "Parameters:\n"
          "\t"+argv[0].split('\\')[-1]+" <input file> [<output file>]\n"
          "If no output file is specified, the input file will be overwritten.")
    return

def simplify_gradient(p1, p2, matrix):
    # apply an affine transformation to a point in 2D space
    def transform(pos, matrix):
        return [matrix[4] + matrix[0]*pos[0] + matrix[2]*pos[1], matrix[5] + matrix[1]*pos[0] + matrix[3]*pos[1]]

    return [transform(p1, matrix), transform(p2, matrix)]

def main():
    # get the input and output filenames from the command line
    if len(argv) == 2:
        input_file_name = output_file_name = argv[1]
    elif len(argv) == 3:
        input_file_name = argv[1]
        output_file_name = argv[2]
    else:
        usage()
        exit()

    # read in the whole input file
    data = ""
    with open(input_file_name, 'r') as f:
        data = f.read()

    # an iterator of all linearGradient nodes
    matchiter = re.finditer(r'<linearGradient[^<>]+>', data)
    # output file content will be stored here
    newdata = ""
    # the end position of the previous match in the input file
    lastend = 0

    modifycount = 0

    for m in matchiter:
        # add data outside the linearGradient node to output
        newdata += data[lastend:m.start()]
        lastend = m.end()

        node = m.group(0)

        # read in data fraom the attributes inside the node
        matrix = re.search('gradientTransform\s*=\s*["\']\s*matrix\(([^)]+)\)\s*["\']', node)
        x1 = re.search('x1\s*=\s*["\']([^"\']+)["\']', node)
        y1 = re.search('y1\s*=\s*["\']([^"\']+)["\']', node)
        x2 = re.search('x2\s*=\s*["\']([^"\']+)["\']', node)
        y2 = re.search('y2\s*=\s*["\']([^"\']+)["\']', node)

        if not (matrix and x1 and y1 and x2 and y2):
            newdata += node
            continue

        # convert strings to numbers
        matrix = re.split('[,\s]+', matrix.group(1))
        matrix = [float(x) for x in matrix]
        x1 = float(x1.group(1).replace('px', ''))
        y1 = float(y1.group(1).replace('px', ''))
        x2 = float(x2.group(1).replace('px', ''))
        y2 = float(y2.group(1).replace('px', ''))

        # the important part: applying the transformation matrix to the coordinates
        newp1, newp2 = simplify_gradient([x1, y1], [x2, y2], matrix)

        # update the attributes
        node = re.sub('\s+gradientTransform\s*=\s*["\']\s*matrix\([^)]+\)\s*["\']', '', node)
        node = re.sub('x1\s*=\s*["\'][^"\']+["\']', 'x1="' + "{0:0.3f}".format(newp1[0])+'"', node)
        node = re.sub('y1\s*=\s*["\'][^"\']+["\']', 'y1="' + "{0:0.3f}".format(newp1[1])+'"', node)
        node = re.sub('x2\s*=\s*["\'][^"\']+["\']', 'x2="' + "{0:0.3f}".format(newp2[0])+'"', node)
        node = re.sub('y2\s*=\s*["\'][^"\']+["\']', 'y2="' + "{0:0.3f}".format(newp2[1])+'"', node)

        # write the node to output
        newdata += node
        modifycount += 1

    newdata += data[lastend:]

    # write the new data to the output file
    with open(output_file_name, 'w') as f:
        f.write(str(newdata))

    print("Done! Modified "+str(modifycount)+" gradient definitions.")


if __name__ == "__main__":
    main()