[swfmill] Inkscape SVG
Gerrit Karius
g99k at hotmail.com
Wed Sep 20 08:24:07 EDT 2006
hi everyone. in july, i submitted a changed "simple-svg.xslt" stylesheet,
where i omitted the swf wrapper transforms for svg:g nodes. it turned out
that this was a mistake. deepest apologies. :(
my question was: should outer transforms of svg nodes apply to referencing
svg:use nodes as well? the svg spec defines svg:use nodes as deep-cloning
the original (implicitly including the transform). i thought that inkscape
ignored the transforms, since the position of clones does not change when
the transform of the original is altered. however, inkscape actually
silently adjusts the matrices of all clones to the new transform instead.
i wanted to omit the wrappers for a direct node hierarchy with better
actionscript accessability. my compromise for the xslt is now that it checks
whether an original node actually has a transform or is simply placed at the
origin. if it's placed at the origin, the wrapper can safely be omitted.
that way, everyone should be happy. :)
here's a list of changes:
* the svg:g template now checks for a transform and applies a wrapper, which
gets the id of the g node. for nonexisting, empty or identity transforms,
the wrapper is omitted, and the id is applied to the g node itself.
* a named transform template catches empty transform atributes
(transform=""). inkscape regularly produces them, and they caused swfmill to
crash.
* svg:flowRoot elements are now included again in the stylesheet. the
definition template simply extracts the text content of the flowRegion and
flowPara child nodes via the "svg-text" mode. the styling is not parsed, but
at least the text is now visible in the swf movie.
* the svg:text template has been changed. before, the text was invisible,
because it was all white color. it's now visible, without styling, just like
flowRoot.
for testing swfmill with non-inkscape files, i also included the following
lines at the start of parse_css_simple in swft_css.cpp. it checks for a
missing style attribute and applies a black stroke.
if( strlen(style_str) == 0 ) {
style->no_fill = true;
style->no_stroke = false;
style->width = 20;
style->stroke.a = 0xff;
return;
}
inkscape uses the style attribute exclusively for colors and styles, and
swfmill checks only this attribute. this means that svgs using regular
atributes like "fill" and "stroke" become all white color and fully
transparent, turning them invisible. now they at least get a black outline,
and can thus prove their existence. :)
i used this to run an swfmill test over the w3c svg testsuite, but the
results looked rather discouraging, even with outlines. however, since
almost all of the testcases represent features that inkscape doesn't use, i
wouldn't bother to implement them.
it might also be more efficient to have inkscape directly export basic
swfml, rather than parsing the svg output in swfmill. the stuff gets very
complicated in xslt, even with external functions. i suppose that in
inkscape, you'd have the data as binary variables instead of xml strings,
and would have global access to the entire tree.
dan, what does your inkscape experience suggest?
anyway, here's the "simple-svg.xslt" again:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:swft="http://subsignal.org/swfml/swft"
xmlns:str="http://exslt.org/strings"
xmlns:xlink="http://www.w3.org/1999/xlink"
extension-element-prefixes="swft"
version='1.0'>
<!-- named template for redundant transforms -->
<xsl:template name="transform">
<transform>
<xsl:choose>
<!-- catch empty transform attributes. -->
<xsl:when test="not(@transform) or @transform=''">
<Transform transX="0" transY="0"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="swft:transform(@transform)"/>
</xsl:otherwise>
</xsl:choose>
</transform>
</xsl:template>
<!-- named template for redundant placing -->
<xsl:template name="placeObject">
<!-- place the element, or the referenced element (if it's a reference).
-->
<xsl:variable name="id">
<xsl:choose>
<xsl:when test="name()='use'">
<xsl:value-of select="swft:map-id(substring(@xlink:href,2))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="swft:map-id(@id)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- use inkscape label as an instance name instead of id, allowing
multiple instances with the same name. -->
<xsl:variable name="name">
<xsl:choose>
<xsl:when test="@inkscape:label">
<xsl:choose>
<xsl:when test="substring(@inkscape:label,1,1)='#'">
<xsl:value-of select="substring(@inkscape:label,2)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@inkscape:label"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@id"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- place the object. -->
<PlaceObject2 replace="0" depth="{swft:next-depth()}" name="{$name}"
objectID="{$id}">
<!-- svg:use elements add an instance transform, all others have it
included in their definition. -->
<xsl:choose>
<xsl:when test="name()='use'">
<xsl:call-template name="transform" />
</xsl:when>
<xsl:otherwise>
<transform>
<Transform transX="0" transY="0"/>
</transform>
</xsl:otherwise>
</xsl:choose>
</PlaceObject2>
</xsl:template>
<!-- named template for redundant wrappers -->
<xsl:template name="wrapElement">
<xsl:param name="innerid" />
<xsl:variable name="id" select="swft:map-id(@id)" />
<DefineSprite objectID="{$id}" frames="1">
<tags>
<PlaceObject2 replace="0" depth="{swft:next-depth()}"
objectID="{$innerid}">
<xsl:call-template name="transform" />
</PlaceObject2>
<ShowFrame/>
<End/>
</tags>
</DefineSprite>
</xsl:template>
<!-- named template for redundant exports -->
<xsl:template name="exportElement">
<xsl:variable name="id" select="swft:map-id(@id)" />
<xsl:variable name="name" select="@id" />
<!-- export the element. -->
<xsl:if test="@id">
<Export>
<symbols>
<Symbol objectID="{$id}" name="{$name}"/>
</symbols>
</Export>
</xsl:if>
<!-- define a class, if applicable. -->
<xsl:variable name="class" select="@class"/>
<xsl:if test="string-length($class) > 0">
<xsl:call-template name="register-class">
<xsl:with-param name="class" select="$class"/>
<xsl:with-param name="linkage-id" select="$name"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<!-- entry point: starts 2 passes, one for queuing up the definitions, one
for placing the elements. -->
<xsl:template match="svg:svg" mode="svg">
<xsl:param name="id"/>
<!-- initiate the definition pass. -->
<xsl:apply-templates mode="queue" />
<!-- define svg root as sprite. -->
<DefineSprite objectID="{$id}" frames="1">
<tags>
<!-- initiate the placement pass. -->
<xsl:apply-templates mode="placement" />
<ShowFrame/>
<End/>
</tags>
</DefineSprite>
</xsl:template>
<xsl:template match="svg:g|svg:path|svg:rect|svg:use|svg:text|svg:flowRoot"
mode="queue">
<xsl:variable name="id"><xsl:value-of
select="swft:map-id(@id)"/></xsl:variable>
<xsl:variable name="name" select="@id"/>
<!-- first define the subparts, so that we get the innermost ones queued
first. -->
<xsl:apply-templates mode="queue" />
<!-- now define this element, which is based on the subparts. -->
<xsl:apply-templates select="." mode="definition">
<xsl:with-param name="id" select="$id"/>
<xsl:with-param name="name" select="$name"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="svg:g|svg:path|svg:rect|svg:use|svg:text|svg:flowRoot"
mode="placement">
<!-- no definition. just place this element. -->
<xsl:call-template name="placeObject" />
</xsl:template>
<xsl:template match="svg:g" mode="definition" priority="-1">
<xsl:param name="id"/>
<xsl:param name="name"/>
<!-- test if a wrapper is needed for a group transform -->
<xsl:choose>
<xsl:when test="not(@transform) or @transform='' or
transform='translate(0,0)'">
<!-- no transform, define the group and place the subparts -->
<DefineSprite objectID="{$id}" frames="1">
<tags>
<xsl:apply-templates mode="placement" />
<ShowFrame/>
<End/>
</tags>
</DefineSprite>
</xsl:when>
<xsl:otherwise>
<!-- define an inner group and wrap it with the group transform -->
<xsl:variable name="innerid"><xsl:value-of
select="swft:next-id()"/></xsl:variable>
<DefineSprite objectID="{$innerid}" frames="1">
<tags>
<xsl:apply-templates mode="placement" />
<ShowFrame/>
<End/>
</tags>
</DefineSprite>
<xsl:call-template name="wrapElement">
<xsl:with-param name="innerid" select="$innerid" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
<!-- export -->
<xsl:call-template name="exportElement" />
</xsl:template>
<xsl:template match="svg:path" mode="definition">
<xsl:param name="id"/>
<xsl:variable name="shapeid"><xsl:value-of
select="swft:next-id()"/></xsl:variable>
<!-- define the path -->
<xsl:copy-of select="swft:path( @d, $shapeid, @style )"/>
<!-- wrap in sprite -->
<xsl:call-template name="wrapElement">
<xsl:with-param name="innerid" select="$shapeid" />
</xsl:call-template>
<!-- export -->
<xsl:call-template name="exportElement" />
</xsl:template>
<xsl:template match="svg:rect" mode="definition">
<xsl:param name="id"/>
<xsl:param name="name"/>
<xsl:variable name="shapeid"><xsl:value-of
select="swft:next-id()"/></xsl:variable>
<!-- define the element -->
<DefineShape3 objectID="{$shapeid}">
<bounds>
<Rectangle left="{@x}" right="{(@x+ at width)*20}" top="{@y}"
bottom="{(@y+ at height)*20}"/>
</bounds>
<styles>
<StyleList>
<xsl:copy-of select="swft:css(@style)/tmp/*"/>
</StyleList>
</styles>
<shapes>
<Shape>
<edges>
<ShapeSetup x="{(@x+ at width)*20}" y="{(@y+ at height)*20}" fillStyle0="1"
lineStyle="1"/>
<LineTo x="-{(@width)*20}" y="0"/>
<LineTo x="0" y="-{(@height)*20}"/>
<LineTo x="{(@width)*20}" y="0"/>
<LineTo x="0" y="{(@height)*20}"/>
<ShapeSetup/>
</edges>
</Shape>
</shapes>
</DefineShape3>
<!-- wrap in sprite -->
<xsl:call-template name="wrapElement">
<xsl:with-param name="innerid" select="$shapeid" />
</xsl:call-template>
<!-- export -->
<xsl:call-template name="exportElement" />
</xsl:template>
<xsl:template match="svg:flowRoot" mode="definition">
<xsl:param name="id"/>
<xsl:param name="name"/>
<xsl:variable name="shapeid"><xsl:value-of
select="swft:next-id()"/></xsl:variable>
<!-- define the element -->
<DefineEditText objectID="{$shapeid}" wordWrap="1" multiLine="1"
password="0" readOnly="0" autoSize="0" hasLayout="1" notSelectable="0"
hasBorder="0" isHTML="0" useOutlines="0" fontRef="{swft:map-id('vera')}"
fontHeight="240" align="0" leftMargin="0" rightMargin="0" indent="0"
leading="40" variableName="{@name}">
<xsl:attribute name="initialText">
<xsl:apply-templates mode="svg-text"/>
</xsl:attribute>
<size>
<Rectangle left="{svg:flowRegion/svg:rect/@x * 20}"
right="{(svg:flowRegion/svg:rect/@x + svg:flowRegion/svg:rect/@width)* 20}"
top="{svg:flowRegion/svg:rect/@y * 20}" bottom="{(svg:flowRegion/svg:rect/@y
+ svg:flowRegion/svg:rect/@height)* 20}"/>
</size>
<color>
<Color red="0" green="0" blue="0" alpha="255"/>
</color>
</DefineEditText>
<!-- wrap in sprite -->
<xsl:call-template name="wrapElement">
<xsl:with-param name="innerid" select="$shapeid" />
</xsl:call-template>
<!-- export -->
<xsl:call-template name="exportElement" />
</xsl:template>
<xsl:template match="svg:text" mode="definition">
<xsl:param name="id"/>
<xsl:param name="name"/>
<xsl:variable name="shapeid"><xsl:value-of
select="swft:next-id()"/></xsl:variable>
<!-- define the element -->
<DefineEditText objectID="{$shapeid}" wordWrap="0" multiLine="1"
password="0" readOnly="1" autoSize="1" hasLayout="1" notSelectable="1"
hasBorder="0" isHTML="0" useOutlines="0" fontRef="{swft:map-id('vera')}"
fontHeight="240" align="0" leftMargin="0" rightMargin="0" indent="0"
leading="40" variableName="{@name}">
<xsl:attribute name="initialText">
<xsl:apply-templates mode="svg-text"/>
</xsl:attribute>
<size>
<Rectangle left="{@x * 20}" right="{@x * 30}" top="{@y * 20}" bottom="{@y
* 30}"/>
</size>
<color>
<Color red="0" green="0" blue="0" alpha="255"/>
</color>
</DefineEditText>
<!-- wrap in sprite -->
<xsl:call-template name="wrapElement">
<xsl:with-param name="innerid" select="$shapeid" />
</xsl:call-template>
<!-- export -->
<xsl:call-template name="exportElement" />
</xsl:template>
<xsl:template match="svg:flowRegion" mode="svg-text">
<xsl:apply-templates mode="svg-text"/>
</xsl:template>
<xsl:template match="svg:flowPara" mode="svg-text">
<xsl:apply-templates mode="svg-text"/>
</xsl:template>
<xsl:template match="svg:tspan[position()=1]" mode="svg-text">
<xsl:apply-templates mode="svg-text"/>
</xsl:template>
<xsl:template match="svg:tspan" mode="svg-text" priority="-1">
<xsl:text>
</xsl:text>
<xsl:apply-templates mode="svg-text"/>
</xsl:template>
<xsl:template match="text()" mode="svg-text">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="*|@*|text()" mode="svg" priority="-1"/>
<xsl:template match="ShapeSetup" mode="shape">
<ShapeSetup fillStyle0="1" fillStyle1="2" lineStyle="1">
<xsl:apply-templates select="*|@*" mode="shape"/>
</ShapeSetup>
</xsl:template>
<xsl:template match="*|@*|text()" mode="shape" priority="-1">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
More information about the swfmill
mailing list