SvgTidy

From W3C Wiki


SVG Tidy

It is common for SVG authoring tools to generate sub-optimal SVG markup that is difficult to post-process through custom applications, scripting, animation, or simply using a text editor to manipulate the SVG/XML document or are low quality. There should be an SVG Tidy tool similar to HTML Tidy that attempts to improve the quality of SVG documents.

SVG Quality Assurance Project

SvgTidy will likely be part of the new SVG Quality Assurance Project,

Implementation

Many of the symptoms require knowledge that's implemented in SVG implementations, for example, in order to replace CSS with SVG elements and attributes a CSS implementation is required and in order to convert a rectangular path to a rect element, an implementation of at least the SVG path syntax is required. It is thus reasonable to implement parts of SvgTidy on top of SVG implementations, and since those typically support ECMAScript, these parts could and probably should be written in ES. This will also allow people who are not familiar with other programming languages or environments to contribute new code.

The following script for example converts


  <rect style="fill:green;stroke:none" height="100" width="100" />


into


  <rect fill="green" stroke="none" height="100" width="100" />


/**
 * This script converts CSS in style attributes to attributes.
 *
 * @author Bjoern Hoehrmann
 * @version $Id$
 *
 */

// :-)
var svgns = "http://www.w3.org/2000/svg";
var nodes = document.getElementsByTagNameNS(svgns, "*");

for (var i = 0; i < nodes.length; ++i)
{
    try
    {
        var node = nodes.item(i);
        
        // skip this element if it does not have a style attribute
        if (!node.hasAttributeNS(null, "style"))
            continue;
        
        var style = node.style;
        
        if (!style)
            continue;
        
        // local cascade memory
        var importance = new Array();
        
        // This could be simpler, but some implementations consider
        // style="fill:x;fill:y" to have two declarations so we take
        // that into account, assuming they report the duplicates in
        // order.
        while (style.length)
        {
            var name = style.item(0);
            var valu = style.getPropertyValue(name);
            var prio = style.getPropertyPriority(name) ? true : false;
            
            // skip this declaration if previous has precedence
            if (importance[name] && !prio)
            {
                style.removeProperty(name);
                continue;
            }
            
            // remember precedence
            importance[name] = prio;
            
            // add as attribute, assuming valu is a proper value for
            // the attribute and name a proper attribute
            node.setAttributeNS(null, name, valu);
            style.removeProperty(name);
        }
        
        // drop the empty attribute
        node.removeAttributeNS(null, "style");
    }
    catch (e)
    {
        // ignore errors
    }
}


Testing

Testing needs to be sorted out before comitting code. One approach would be to specify for each "feature", "input document", and "configuration" a set of XPath expressions that must match for the test to pass, probably with some skip conditions (or not-skip conditions)...

Configuration

SvgTidy needs a configuration system...

Plugins

SvgTidy should be easily extensible...

Coding conventions

SvgTidy developers should agree on some basic coding conventions... Some proposed conventions:

  • Prefer namespace-aware methods over namespace-unaware methods
    • use setAttributeNS() rather than setAttribute()
  • 4 column indent
  • No tabs, use spaces for indentation
  • ...

See Also

Wiki

There should be a Wiki for SvgTidy... Well, we have one already but in the long term something should be hosted on svg-qa.sf.net...

Design

...

Symptoms

...

<rect x="318.1" y="-1306" height="6" width="6" fill="#505050" stroke="#505050" />
<rect x="318.1" y="-1314" height="6" width="6" fill="#505050" stroke="#505050" />
<rect x="318.1" y="-1323" height="6" width="6" fill="#505050" stroke="#505050" />
<rect x="318.1" y="-1332" height="6" width="6" fill="#505050" stroke="#505050" />
<rect x="318.1" y="-1340" height="6" width="6" fill="#505050" stroke="#505050" />
<rect x="318.1" y="-1349" height="6" width="6" fill="#505050" stroke="#505050" />
<rect x="318.1" y="-1357" height="6" width="6" fill="#505050" stroke="#505050" />
<rect x="318.1" y="-1366" height="6" width="6" fill="#505050" stroke="#505050" />
...


The constant attributes should be defined in one common place and only the variable attributes should be explicitly specified. It might also make sense to analyze the document further and replace the markup with a short sXBL fragment and proper bootstrap markup.

Path to Shape

<path d="M372.1 -723.6l-6.109 -5.087e-005l-0.0001017 6.109l6.109 0.0001526z" fill="#505050" stroke="#505050"/>
<path d="M372.1 -732.1l-6.109 -5.087e-005l-0.0001017 6.109l6.109 0.0001526z" fill="#505050" stroke="#505050"/>
<path d="M372.1 -740.7l-6.109 -5.087e-005l-0.0001017 6.109l6.109 0.0001526z" fill="#505050" stroke="#505050"/>
<path d="M372.1 -749.3l-6.109 -5.087e-005l-0.0001017 6.109l6.109 0.0001526z" fill="#505050" stroke="#505050"/>
<path d="M372.1 -757.8l-6.109 -5.087e-005l-0.0001017 6.109l6.109 0.0001526z" fill="#505050" stroke="#505050"/>
<path d="M372.1 -766.4l-6.109 -5.087e-005l-0.0001017 6.109l6.109 0.0001526z" fill="#505050" stroke="#505050"/>
...


The <rect> element should be used instead.

CSS vs attribute conflicts

<rect fill="red" style="fill:green" ... >


The conflict should be resolved.

Redundant style attributes

<g fill="blue" ...>
  <rect fill="red" ... />
  <rect fill="red" ... />
  <rect fill="red" ... />
</g>


The fill="blue" is never applied and should thus be removed.

Style Grouping and Cleanup

<g id="node2" class="node">
<polygon style="fill:none;stroke:black;" points="21,144 1621,144 1621,108 21,108 21,144"/>
<text text-anchor="middle" x="29" y="131">p</text>
<polyline style="fill:none;stroke:black;" points="37,144 37,108 "/>
<text text-anchor="middle" x="45" y="131">p</text>
<polyline style="fill:none;stroke:black;" points="53,144 53,108 "/>
<text text-anchor="middle" x="61" y="131">p</text>
<polyline style="fill:none;stroke:black;" points="69,144 69,108 "/>
<text text-anchor="middle" x="77" y="131">p</text>
<polyline style="fill:none;stroke:black;" points="85,144 85,108 "/>
<text text-anchor="middle" x="93" y="131">p</text>
...


  • The text-anchor="middle" should be on the <g> element only
  • The style="fill:none;stroke:black;" should be on the <g> element only

Namespace Cleanup I

<svg:path d="..." xmlns:svg="http://www.w3.org/2000/svg" />
<svg:path d="..." xmlns:svg="http://www.w3.org/2000/svg" />
<svg:path d="..." xmlns:svg="http://www.w3.org/2000/svg" />
<svg:path d="..." xmlns:svg="http://www.w3.org/2000/svg" />
<svg:path d="..." xmlns:svg="http://www.w3.org/2000/svg" />
<svg:path d="..." xmlns:svg="http://www.w3.org/2000/svg" />
...


The prefix should be declared for a common parent.

Namespace Cleanup II

<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
<svg:path d="..." />
<svg:path d="..." />
<svg:path d="..." />
<svg:path d="..." />
<svg:path d="..." />
...


The redundant prefix should be removed.

...

<g transform="translate(26,0)"><g transform="rotate(90)"><use xlink:href="#nodearc"/></g></g>
<g transform="translate(26,0)"><g transform="rotate(-90)"><use xlink:href="#nodearc"/></g></g>
<g transform="translate(0,-15)"><g transform="rotate(-30)"><use xlink:href="#nodearc"/></g></g>
<g transform="translate(0,-15)"><g transform="rotate(150)"><use xlink:href="#nodearc"/></g></g>
<g transform="translate(0,15)"><g transform="rotate(30)"><use xlink:href="#nodearc"/></g></g>
<g transform="translate(0,15)"><g transform="rotate(-150)"><use xlink:href="#nodearc"/></g></g>


This is basically equivalent to


<use transform="translate(26,  0) rotate(  90)" xlink:href="#nodearc"/>
<use transform="translate(26,  0) rotate( -90)" xlink:href="#nodearc"/>
<use transform="translate( 0,-15) rotate( -30)" xlink:href="#nodearc"/>
<use transform="translate( 0,-15) rotate( 150)" xlink:href="#nodearc"/>
<use transform="translate( 0, 15) rotate(  30)" xlink:href="#nodearc"/>
<use transform="translate( 0, 15) rotate(-150)" xlink:href="#nodearc"/>


...

@@ redundant glyphs in fonts

Attribute Value Normalization

<linearGradient
 xlink:href="#linearGradient593"
 id="linearGradient588"
 x1="1.000000"
 y1="2.000000"
 x2="3.000000"
 y2="4.000000"
 gradientUnits="userSpaceOnUse"
 spreadMethod="reflect"
/>


This is equivalent to


<linearGradient
 xlink:href="#linearGradient593"
 id="linearGradient588"
 x1="1"
 y1="2"
 x2="3"
 y2="4"
 gradientUnits="userSpaceOnUse"
 spreadMethod="reflect"
/>



<rect fill="#FFFFFF" ... />
<rect fill="#FFF" ... />
<rect fill="rgb(255,255,255)" ... />
<rect fill="white" ... />
<rect fill="WHITE" ... />


...


<rect fill="white" ... />
<rect fill="white" ... />
<rect fill="white" ... />
<rect fill="white" ... />
<rect fill="white" ... />


Default Attribute Values

<linearGradient
 x1="0%"
 y1="0%"
 x2="100%"
 y2="0%"
 gradientUnits="objectBoundingBox"
 spreadMethod="pad"
 xml:space="default"
 color-rendering="auto"
/>


This is equivalent to


<linearGradient/>


Proprietary Extensions

<path
 sodipodi:type="star"
 style="..."
 id="path1934"
 sodipodi:sides="13"
 sodipodi:cx="400.19958"
 sodipodi:cy="488.05197"
 sodipodi:r1="109.98328"
 sodipodi:r2="219.96657"
 sodipodi:arg1="0.92467742"
 sodipodi:arg2="1.1663384"
 inkscape:flatsided="false"
 inkscape:rounded="0.0000000"
 d="..."
/>


This is in most viewers equivalent to


<path
 style="..."
 id="path1934"
 d="..."
/>


xml:base, xml:lang cleanup

relative references

CSS

It is sometimes desirable to have no CSS in SVG documents, e.g. because the document should work in viewers that do not support CSS. SVG should provide XML syntax for all CSS syntax and there should be thus a complete mapping from CSS features to SVG, tools like e.g. http://lists.w3.org/Archives/Public/www-archive/2004Jun/0001.html can help here.

Complex cleanup

@@

Conformance cleanup

It is unfortunately common that authoring tools (and authors) create non-conforming SVG documents. SvgTidy should help correcting mistakes to ensure the images work on as many viewers as possible.

missing media type in data: urls

For example,


  ... xlink:href = 'data:;base64,...'


This is really equivalent to


  ... xlink:href = 'data:text/plain;charset=us-ascii;base64,...'


which is not intended most of the time. SvgTidy should analyze the reference and the content of the data: URL to find a proper media type and add it to the reference.

%xx escaped data: urls

SVG 1.1 only requires support for base64 encoded data in the data: URL scheme, and many viewers do not support simply %xx-escaped data, SvgTidy should be able to convert %xx-escaped data: URLs to base64 encoded data: URLs.

PNG compression

When PNG images are included in a SVG image using the data: URL scheme, SvgTidy should allow plugging external image tools to the processing that allow reducing the size of the image, e.g. pngcrush is such a tool.

animate vs animateColor

animate fill from red to blue should be animateColor fill from red to blue

Prior Art

See Also

Random Notes

  • There needs to be code that compares two presentation attribute values (e.g., their CSSValue)
  • ...