svg2synfig using XSLT


#1

This is the first working version of my SVG to Synfig converter.
Very basic, only curved paths are converted, styles etc. are lost.
Tested with saxon (http://prdownloads.sourceforge.net/saxon/saxonb9-0-0-2j.zip) and Inkscape generated SVGs.

svg2synfig.xsl:

<!--
	basic svg curves into synfig bline fills transformation
	example usage (using saxon): java -jar saxon9.jar -xsl:svg2synfig.xsl your_svg_file.svg > output_file.sif
-->
<xsl:stylesheet version="2.0"
		xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
		xmlns:svg="http://www.w3.org/2000/svg"
		xmlns:xs="http://www.w3.org/2001/XMLSchema"
		xmlns:math="http://exslt.org/math">
	<xsl:output method="xml" indent="yes" encoding="UTF-8"/>

	<xsl:template match="/">
		<canvas version="0.1">
			<xsl:apply-templates select="//svg:path"/>
			<layer type="zoom" active="true" version="0.1">
				<param name="amount">
					<real value="-6"/>
				</param>
			</layer>
		</canvas>
	</xsl:template>

	<xsl:template match="svg:path">
		<xsl:analyze-string select="@d" regex="M[^z]+z">
			<xsl:matching-substring>
				<layer type="region" active="true" version="0.1">
					<param name="bline">
						<bline type="bline_point" loop="true">
							<xsl:call-template name="curve-path-to-bline">
								<xsl:with-param name="path" select="."/>
							</xsl:call-template>
						</bline>
					</param>
				</layer>
			</xsl:matching-substring>
		</xsl:analyze-string>
	</xsl:template>

	<xsl:template name="curve-path-to-bline">
		<xsl:param name="path"/>
		<xsl:analyze-string select="$path" regex="\s([0-9.]+[,\s][0-9.]+)\s[0-9.]+[,\s][0-9.]+\sz">
			<xsl:matching-substring>
				<xsl:analyze-string select="concat(regex-group(1), $path)" regex="([0-9.]+)[,\s]([0-9.]+)M?\s([0-9.]+)[,\s]([0-9.]+)\sC\s([0-9.]+)[,\s]([0-9.]+)">
					<xsl:matching-substring>
						<xsl:call-template name="node-to-bline-point">
							<xsl:with-param name="c1_x" select="regex-group(1)"/>
							<xsl:with-param name="c1_y" select="regex-group(2)"/>
							<xsl:with-param name="x" select="regex-group(3)"/>
							<xsl:with-param name="y" select="regex-group(4)"/>
							<xsl:with-param name="c2_x" select="regex-group(5)"/>
							<xsl:with-param name="c2_y" select="regex-group(6)"/>
						</xsl:call-template>
					</xsl:matching-substring>
				</xsl:analyze-string>
			</xsl:matching-substring>
		</xsl:analyze-string>
	</xsl:template>

	<xsl:template name="node-to-bline-point">
		<xsl:param name="x"/>
		<xsl:param name="y"/>
		<xsl:param name="c1_x"/>
		<xsl:param name="c1_y"/>
		<xsl:param name="c2_x"/>
		<xsl:param name="c2_y"/>
		<xsl:variable name="mirror_y" select="- xs:float($y)"/>
		<xsl:variable name="mirror_c1_y" select="- xs:float($c1_y)"/>
		<xsl:variable name="mirror_c2_y" select="- xs:float($c2_y)"/>
		<entry>
			<composite type="bline_point">
				<c1>
					<vector>
						<x><xsl:value-of select="$x"/></x>
						<y><xsl:value-of select="$mirror_y"/></y>
					</vector>
				</c1>
				<c2>
					<real value="1"/>
				</c2>
				<c3>
					<real value="0.5"/>
				</c3>
				<c4>
					<bool value="true"/>
				</c4>
				<c5>
					<xsl:call-template name="vectory-pair-to-radial">
						<xsl:with-param name="origin-x" select="$c1_x"/>
						<xsl:with-param name="origin-y" select="$mirror_c1_y"/>
						<xsl:with-param name="x" select="$x"/>
						<xsl:with-param name="y" select="$mirror_y"/>
					</xsl:call-template>
				</c5>
				<c6>
					<xsl:call-template name="vectory-pair-to-radial">
						<xsl:with-param name="origin-x" select="$x"/>
						<xsl:with-param name="origin-y" select="$mirror_y"/>
						<xsl:with-param name="x" select="$c2_x"/>
						<xsl:with-param name="y" select="$mirror_c2_y"/>
					</xsl:call-template>
				</c6>
			</composite>
		</entry>
	</xsl:template>

	<xsl:template name="vectory-pair-to-radial">
		<xsl:param name="x"/>
		<xsl:param name="y"/>
		<xsl:param name="origin-x"/>
		<xsl:param name="origin-y"/>
		<radial_composite type="vector">
			<xsl:variable name="dx" select="xs:float($x) - xs:float($origin-x)"/>
			<xsl:variable name="dy" select="xs:float($y) - xs:float($origin-y)"/>
			<c0>
				<xsl:variable name="d_sqared" select="$dx * $dx + $dy * $dy"/>
				<xsl:variable name="d">
					<xsl:call-template name="math:sqrt">
						<xsl:with-param name="number" select="$d_sqared"/>
					</xsl:call-template>
				</xsl:variable>
				<real value="{$d * 3}"/>
			</c0>
			<c1>
				<atan2 type="angle">
					<x>
						<real value="{$dx}"/>
					</x>
					<y>
						<real value="{$dy}"/>
					</y>
				</atan2>
			</c1>
		</radial_composite>
	</xsl:template>

	<xsl:template name="math:sqrt">
		<xsl:param name="number" select="0"/>
		<xsl:param name="try" select="1"/>
		<xsl:param name="iter" select="1"/>
		<xsl:param name="maxiter" select="10"/>
		<!-- This template was written by Nate Austin using Sir Isaac Newton's
				method of finding roots -->
			<xsl:choose>
				<xsl:when test="$try * $try = $number or $iter > $maxiter">
					<xsl:value-of select="$try"/>
				</xsl:when>
				<xsl:otherwise>
					<xsl:call-template name="math:sqrt">
					<xsl:with-param name="number" select="$number"/>
					<xsl:with-param name="try" select="$try - (($try * $try - $number) div (2 * $try))"/>
					<xsl:with-param name="iter" select="$iter + 1"/>
					<xsl:with-param name="maxiter" select="$maxiter"/>
				</xsl:call-template>
			</xsl:otherwise>
		</xsl:choose>
	</xsl:template>
</xsl:stylesheet>

#2

Awesome!

Not sure if you are working with the format produced by the latest release (0.61.07), or with the format produced by the SVN version, but the SVN version has some new layers and added parameters to some layers.


#3

Do you have a test svg that it works with? The svg file I tried from Inkscape gave me blank region layers in Synfig.

Any reason why you chose to render the tangents as radial composite rather than just composite? (i.e. theta and radius instead of using x & y?)

(Hats off to you though for doing something I only dreamt of)


#4

pabs:
i tested on the latest windows version i found in the downloads section (probably SVN 1320)

pxegeek:
i succeded with newly created svgs, filled using the calligraphy tool. (that’s what i wanted to import into synfig).
i’d like to attach a file but the forum doesn’t allow this (see viewtopic.php?t=28&highlight=ability+attach).
but basically every simple svg should work if you:

  1. convert each object into a path
  2. turn all path segments into curves
  3. zoom and translate the result according to the original svg’s units (the default zoom added should do for a start for svgs with inkscape’s default size)

you can try these steps with inkscape/share/icons/inkscape.svg, the result will appear very tiny in the lower right.

i didn’t use theta since i didn’t find a generic xsl implementation of atan2. (the svg input uses cartesian coordinates.)


#5

This version should work with more SVGs.
Added support for:

  • color
  • simple transformations
  • line path segments
  • outlines for non-filled paths
  • theta in tangents
    Tested with r1398, saxon and inkscape/share/examples/tiger.svg (unzipped).

[code]
<xsl:stylesheet version=“2.0” exclude-result-prefixes="#all"
xmlns:xsl=“http://www.w3.org/1999/XSL/Transform
xmlns:svg=“http://www.w3.org/2000/svg
xmlns:xs=“http://www.w3.org/2001/XMLSchema
xmlns:math=“http://exslt.org/math”>
<xsl:output method=“xml” indent=“yes” encoding=“UTF-8”/>

<xsl:template match="/">
	<xsl:apply-templates/>
</xsl:template>

<xsl:template match="svg:svg">
	<xsl:variable name="width" select="math:units_to_px(@width)"/>
	<xsl:variable name="height" select="math:units_to_px(@height)"/>
	<xsl:variable name="has_view_box" select="matches(@viewBox, '(\d+\s){3}\d+')"/>
	<canvas version="0.2" id="{@id}"
			width="{if ($has_view_box) then replace(@viewBox, '(\d+)\s(\d+)\s(\d+)\s(\d+)', '$3') else $width}"
			height="{if ($has_view_box) then replace(@viewBox, '(\d+)\s(\d+)\s(\d+)\s(\d+)', '$4') else $height}"
			view-box="{if ($has_view_box) then @viewBox else concat('0 0 ', $width, ' ', $height)}">
		<xsl:apply-templates select="svg:g|svg:svg|svg:path"/>
	</canvas>
</xsl:template>

<xsl:template match="svg:g">
	<layer type="PasteCanvas" active="true" version="0.1" desc="{@id}">
		<param name="canvas">
			<canvas>
				<xsl:apply-templates/>
				<xsl:apply-templates select="@transform"/>
			</canvas>
		</param>
	</layer>
</xsl:template>

<xsl:template match="svg:path">
	<xsl:variable name="style">
		<xsl:for-each select="ancestor-or-self::*">
			<xsl:sort select="position()" data-type="number" order="descending"/>
			<xsl:value-of select="concat(@style, ';fill:', @fill, ';stroke:', @stroke, ';stroke-width:', @stroke-width, ';')"/>
		</xsl:for-each>
	</xsl:variable>
	<xsl:variable name="self" select="."/>
	<xsl:variable name="is_fill" select="not(matches(replace($style, 'fill:[^n;][^o].*', ''), 'fill:none'))"/>
	<xsl:analyze-string select="@d" regex="m[^z]+(z|$)" flags="i">
		<xsl:matching-substring>
			<layer type="{if ($is_fill) then 'region' else 'outline'}" version="0.1" desc="{$self/@id}">
				<xsl:call-template name="style-to-color">
					<xsl:with-param name="style" select="replace(replace($style, ':none.*', ''), if ($is_fill) then '.*fill:([^;]+).*' else '.*stroke:([^;]+).*', '$1')"/>
				</xsl:call-template>
				<xsl:if test="not ($is_fill)">
					<xsl:call-template name="style-to-width">
						<xsl:with-param name="style" select="replace($style, '.*stroke-width:([^;]+).*', '$1')"/>
					</xsl:call-template>
				</xsl:if>
				<param name="bline">
					<bline type="bline_point" loop="{matches(., 'z', 'i')}">
						<xsl:call-template name="path-to-bline">
							<xsl:with-param name="path" select="."/>
							<xsl:with-param name="node" select="$self" tunnel="yes"/>
						</xsl:call-template>
					</bline>
				</param>
			</layer>
		</xsl:matching-substring>
	</xsl:analyze-string>
</xsl:template>

<xsl:template match="@transform">
	<xsl:analyze-string select="." regex="translate[(]([-\d.]+).([-\d.]*)">
		<xsl:matching-substring>
			<layer type="translate" active="true" version="0.1">
				<param name="origin">
					<vector>
						<x><xsl:value-of select="xs:float(regex-group(1))"/></x>
						<y><xsl:value-of select="xs:float(regex-group(2))"/></y>
					</vector>
				</param>
			</layer>
		</xsl:matching-substring>
	</xsl:analyze-string>
</xsl:template>

<xsl:template name="path-to-bline">
	<xsl:param name="path"/>
	<xsl:variable name="stripped" select="replace(replace(translate($path, ',', ' '), '(\d)-', '$1 -'), '\s*([a-z]+)\s*', '$1', 'i')"/>
	<xsl:variable name="closed" select="if (matches($stripped, 'z', 'i')) then $stripped else replace($stripped, 'm([-\d.]+\s[-\d.]+).*$', '$0l$1z', 'i')"/>
	<xsl:variable name="tmp" select="replace($closed, '([-\d.]+\s[-\d.]+)l([-\d.]+\s[-\d.]+)', '$1c$1 $2 $2', 'i')"/>
	<xsl:variable name="curve" select="replace($tmp, '([-\d.]+\s[-\d.]+)l([-\d.]+\s[-\d.]+)', '$1c$1 $2 $2', 'i')"/>
	<xsl:analyze-string select="$curve" regex="\s([-\d.]+\s[-\d.]+)\s[-\d.]+\s[-\d.]+z" flags="i">
		<xsl:matching-substring>
			<xsl:analyze-string select="concat(regex-group(1), $curve)" regex="([-\d.]+)\s([-\d.]+)[m\s]([-\d.]+)\s([-\d.]+)c([-\d.]+)\s([-\d.]+)" flags="i">
				<xsl:matching-substring>
					<xsl:call-template name="node-to-bline-point">
						<xsl:with-param name="c1_x" select="regex-group(1)"/>
						<xsl:with-param name="c1_y" select="regex-group(2)"/>
						<xsl:with-param name="x" select="regex-group(3)"/>
						<xsl:with-param name="y" select="regex-group(4)"/>
						<xsl:with-param name="c2_x" select="regex-group(5)"/>
						<xsl:with-param name="c2_y" select="regex-group(6)"/>
					</xsl:call-template>
				</xsl:matching-substring>
			</xsl:analyze-string>
		</xsl:matching-substring>
	</xsl:analyze-string>
</xsl:template>

<xsl:template name="node-to-bline-point">
	<xsl:param name="x"/>
	<xsl:param name="y"/>
	<xsl:param name="c1_x"/>
	<xsl:param name="c1_y"/>
	<xsl:param name="c2_x"/>
	<xsl:param name="c2_y"/>
	<xsl:param name="node" tunnel="yes"/>
	<xsl:variable name="transform">
		<xsl:for-each select="$node/ancestor-or-self::*/@transform">
			<xsl:value-of select="."/>
		</xsl:for-each>
	</xsl:variable>
	<xsl:variable name="t_a" select="if (matches($transform, 'matrix')) then xs:float(replace($transform, '.*matrix\(([^,]+,){0}([^,]+)[,)].*', '$2')) else 1"/>
	<xsl:variable name="t_d" select="if (matches($transform, 'matrix')) then xs:float(replace($transform, '.*matrix\(([^,]+,){3}([^,]+)[,)].*', '$2')) else 1"/>
	<xsl:variable name="t_e" select="if (matches($transform, 'matrix')) then xs:float(replace($transform, '.*matrix\(([^,]+,){4}([^,]+)[,)].*', '$2')) else 0"/>
	<xsl:variable name="t_f" select="if (matches($transform, 'matrix')) then xs:float(replace($transform, '.*matrix\(([^,]+,){5}([^,]+)[,)].*', '$2')) else 0"/>
	<xsl:variable name="transformed_x" select="$t_e + $t_a * xs:float($x)"/>
	<xsl:variable name="transformed_y" select="$t_f + $t_d * xs:float($y)"/>
	<xsl:variable name="transformed_c1_x" select="$t_e + $t_a * xs:float($c1_x)"/>
	<xsl:variable name="transformed_c1_y" select="$t_f + $t_d * xs:float($c1_y)"/>
	<xsl:variable name="transformed_c2_x" select="$t_e + $t_a * xs:float($c2_x)"/>
	<xsl:variable name="transformed_c2_y" select="$t_f + $t_d * xs:float($c2_y)"/>
	<entry>
		<composite type="bline_point">
			<point>
				<vector>
					<x><xsl:value-of select="$transformed_x"/></x>
					<y><xsl:value-of select="$transformed_y"/></y>
				</vector>
			</point>
			<width>
				<real value="1"/>
			</width>
			<origin>
				<real value="0.5"/>
			</origin>
			<split>
				<bool value="true"/>
			</split>
			<t1>
				<xsl:call-template name="vector-pair-to-radial">
					<xsl:with-param name="origin-x" select="$transformed_c1_x"/>
					<xsl:with-param name="origin-y" select="$transformed_c1_y"/>
					<xsl:with-param name="x" select="$transformed_x"/>
					<xsl:with-param name="y" select="$transformed_y"/>
				</xsl:call-template>
			</t1>
			<t2>
				<xsl:call-template name="vector-pair-to-radial">
					<xsl:with-param name="origin-x" select="$transformed_x"/>
					<xsl:with-param name="origin-y" select="$transformed_y"/>
					<xsl:with-param name="x" select="$transformed_c2_x"/>
					<xsl:with-param name="y" select="$transformed_c2_y"/>
				</xsl:call-template>
			</t2>
		</composite>
	</entry>
</xsl:template>

<xsl:template name="vector-pair-to-radial">
	<xsl:param name="x"/>
	<xsl:param name="y"/>
	<xsl:param name="origin-x"/>
	<xsl:param name="origin-y"/>
	<xsl:variable name="dx" select="xs:float($x) - xs:float($origin-x)"/>
	<xsl:variable name="dy" select="xs:float($y) - xs:float($origin-y)"/>
	<xsl:variable name="d" select="math:sqrt($dx * $dx + $dy * $dy)"/>
	<xsl:variable name="angle" select="math:atan2($dy, $dx)"/>
	<radial_composite type="vector">
		<radius>
			<real value="{$d * 3}"/>
		</radius>
		<theta>
			<angle value="{$angle * 57.295779513082320876798154814105}"/>
		</theta>
	</radial_composite>
</xsl:template>

<xsl:template name="style-to-width">
	<xsl:param name="style"/>
	<xsl:if test="matches($style, '^\d')">
		<param name="width">
			<real value="{math:units_to_px($style)}"/>
		 </param>
	</xsl:if>
</xsl:template>

<xsl:template name="style-to-color">
	<xsl:param name="style"/>
	<xsl:if test="matches($style, '#')">
		<xsl:analyze-string select="concat($style, ';')" regex="#([\da-f]{{2}})([\da-f]{{2}})([\da-f]{{2}});">
			<xsl:matching-substring>
				<param name="color">
					<color>
						<r><xsl:value-of select="math:hex_to_color(regex-group(1))"/></r>
						<g><xsl:value-of select="math:hex_to_color(regex-group(2))"/></g>
						<b><xsl:value-of select="math:hex_to_color(regex-group(3))"/></b>
						<a><xsl:value-of select="if (matches($style, 'fill-opacity:')) then math:power(xs:float(replace($style, '.*fill-opacity:([-\d.]+).*', '$1')), 1 div 2.2) else 1"/></a>
					</color>
				</param>
			</xsl:matching-substring>
		</xsl:analyze-string>
	</xsl:if>
	<xsl:if test="matches($style, 'rgb')">
		<xsl:analyze-string select="concat($style, ';')" regex="rgb[(\s]+([-\d.]+)[,\s]+([-\d.]+)[,\s]+([-\d.]+)[\s)]+;">
			<xsl:matching-substring>
				<param name="color">
					<color>
						<r><xsl:value-of select="math:power(xs:float(regex-group(1)) div 255, 2.2)"/></r>
						<g><xsl:value-of select="math:power(xs:float(regex-group(2)) div 255, 2.2)"/></g>
						<b><xsl:value-of select="math:power(xs:float(regex-group(3)) div 255, 2.2)"/></b>
						<a>1</a>
					</color>
				</param>
			</xsl:matching-substring>
		</xsl:analyze-string>
	</xsl:if>
	<xsl:if test="matches($style, 'url')">
		<param name="color">
			<color><r>0.5</r><g>0.5</g><b>0.5</b><a>0.5</a>	</color>
		</param>
	</xsl:if>
</xsl:template>

<xsl:function name="math:hex_to_color" as="xs:float">
	<xsl:param name="hex"/>
	<xsl:value-of select="math:power(xs:float(string-length(substring-before('0123456789abcdef', substring($hex,1,1))) * 16 + string-length(substring-before('0123456789abcdef', substring($hex,2,1)))) div 255, 2.2)"/>
</xsl:function>

<xsl:function name="math:units_to_px" as="xs:float">
	<xsl:param name="size"/>
	<xsl:analyze-string select="$size" regex="^([-\d.]+)([a-z%]*)$">
		<xsl:matching-substring>
			<xsl:variable name="factor">
				<xsl:choose>
					<xsl:when test="regex-group(2) = 'pt'">1.25</xsl:when>
					<xsl:when test="regex-group(2) = 'em'">16</xsl:when>
					<xsl:when test="regex-group(2) = 'mm'">3.54</xsl:when>
					<xsl:when test="regex-group(2) = 'pc'">15</xsl:when>
					<xsl:when test="regex-group(2) = 'cm'">35.43</xsl:when>
					<xsl:when test="regex-group(2) = 'in'">90</xsl:when>
					<xsl:otherwise>1</xsl:otherwise>
				</xsl:choose>
			</xsl:variable>
			<xsl:value-of select="xs:float($factor) * xs:float(regex-group(1))"/>
		</xsl:matching-substring>
	</xsl:analyze-string>
</xsl:function>

</xsl:stylesheet>
[/code]


#6

hey dmd!
can you make wiki page for that? I have no idea how to use that saxon utility or what ever it is. And don’t know if it is for linux windows or mac. :confused:


#7

synfig.org/Svg2synfig


#8

Thanks! :smiley:
-G


#9

I’m using libsaxonb-java from Debian with this command-line:

saxonb-xslt -ext:on -xsl:svg2synfig.xsl tiger.svg > tiger.sif

This results in a tiger.sif, but none of the blines have any vertices in them (looked at tiger.sif in a text editor).


#10

So I realize that I didn’t update with the fact that I got it working under Windows. I had to install Saxon9.

Here’s a tiger.sif
home.comcast.net/~pxegeek/synfig/tiger.sif

pabs - can you post your sif file?


#11

Attached.
tiger.sifz (3.68 KB)


#12

hi, I also tried to use it with saxon via console (kubuntu) and got the attached file. I used a plain svg (from inkscape) with a shape without outline and all nodes set to split tangents. Does anybody got an idea?
out.sif (26.2 KB)


#13

Looks like a hand. What should it look like?

Chris


#14

It looks like a hand? Marvellous! That’s what it should look like. But how do I import it? If I do the following:

  • Right click on animation window
  • File - Import
  • choose the file
    I’ll get an error message: “Unable to open this composition – (…)/out.sif”

(Apologies in advance if I overlooked sth essential, I’m still new to this program)


#15

You should not have problems importing it or opening it. The problems is that the file dimensions are a little odd. It has the incredible value of a image span of 394 (the default one is 9.18) and a canvas size of 286x270 units (default values are 8x5 units) but the image size is 286x270 (default value is 480x270)

It should not crash Synfig but I’ve experimented one crash meanwhile importing your hand example. It seems to be a bug or a buggy formed file.

You have to zoom out an amount of -4.5 to see the hand when import it in a new fresh file. Set the Zoom parameter of the Paste Canvas layer after import.

-G


#16

Lappi - hit ‘Ctrl-O’ to open the file (or File-> Open)

If you use import, Synfig will try to import an image file like jpg, png, tiff, bmp, etc…

Rgds,
Chris


#17

I remember having tried File-Open with the same result. But thanks for your answer, I will try this (again) after having changed the OS.


#18

It is possible to use xsltproc?


#19

This only works for simple objects, and does not respect too well degrade them?


#20

You are digging in an very old an outdated thread. “Official” svg to sif is done using Inkscape the Sif save as Inkscape feature.

regardS.