SVG passthrough precision

If an SVG is imported into a design tool, then immediately exported as another SVG, how much precision is kept? What’s added, removed, or altered?

To test rounding precision, an SVG can be created with elements covering the range of decimal places to check. In the example below, the X and Y positions of rectangles hold values with 1 to 10 decimal places. Passing it through a design tool reveals the tool’s precision.

<rect x="10.1" y="10.1" width="10" height="10"/>
<rect x="10.01" y="10.01" width="10" height="10"/>
<rect x="10.001" y="10.001" width="10" height="10"/>
<rect x="10.0001" y="10.0001" width="10" height="10"/>
<rect x="10.00001" y="10.00001" width="10" height="10"/>
<rect x="10.000001" y="10.000001" width="10" height="10"/>
<rect x="10.0000001" y="10.0000001" width="10" height="10"/>
<rect x="10.00000001" y="10.00000001" width="10" height="10"/>
<rect x="10.000000001" y="10.000000001" width="10" height="10"/>
<rect x="10.0000000001" y="10.0000000001" width="10" height="10"/>

For ideal SVG exporting, design tools should have settings for rounding precision — lower precision can result in smaller files that are good for production assets, and higher precision can be good when transporting artwork between tools, where file size doesn’t matter.

This is an important test, because post processing SVGs with a tool like SVGO can not recover lost data. Once it’s gone, it’s gone.

Affinity Designer #

Here’s the resulting SVG exported from Affinity Designer. The markup is nice and clean, but it stops at 3 decimal place of precision, which I don’t consider to be enough for many uses. There’s no settings for controlling rounding.

<rect x="10.1" y="10.1" width="10" height="10"/>
<rect x="10.01" y="10.01" width="10" height="10"/>
<rect x="10.001" y="10.001" width="10" height="10"/>
<rect x="10" y="10" width="10" height="10"/>
<rect x="10" y="10" width="10" height="10"/>
<rect x="10" y="10" width="10" height="10"/>
<rect x="10" y="10" width="10" height="10"/>
<rect x="10" y="10" width="10" height="10"/>
<rect x="10" y="10" width="10" height="10"/>
<rect x="10" y="10" width="10" height="10"/>

Figma #

Figma’s SVG makes it to 4 decimal places. Rectangles are converted to paths, which results in a larger and messier SVG that would be harder to hand edit. It’s also interesting how the fill is a named colour (“black”). Black is the default shape colour for SVGs, so the fill attribute can be omitted entirely, as per the source SVG.

<path d="M20.1 10.1H10.1V20.1H20.1V10.1Z" fill="black"/>
<path d="M20.01 10.01H10.01V20.01H20.01V10.01Z" fill="black"/>
<path d="M20.001 10.001H10.001V20.001H20.001V10.001Z" fill="black"/>
<path d="M20.0001 10.0001H10.0001V20.0001H20.0001V10.0001Z" fill="black"/>
<path d="M20 10H10V20H20V10Z" fill="black"/>
<path d="M20 10H10V20H20V10Z" fill="black"/>
<path d="M20 10H10V20H20V10Z" fill="black"/>
<path d="M20 10H10V20H20V10Z" fill="black"/>
<path d="M20 10H10V20H20V10Z" fill="black"/>
<path d="M20 10H10V20H20V10Z" fill="black"/>

Illustrator #

Using Illustrator’s Export As export method gets to 5 decimal places. Export As does have some settings, and 5 decimal places is the maximum precision that can be set. Interestingly, this also sets the rounding when copying objects to the clipboard and pasting them as SVGs into other tools or a text editor.

<rect x="10.1" y="10.1" width="10" height="10"/>
<rect x="10.01" y="10.01" width="10" height="10"/>
<rect x="10.001" y="10.001" width="10" height="10"/>
<rect x="10.0001" y="10.0001" width="10" height="10"/>
<rect x="10.00001" y="10.00001" width="10" height="10"/>
<rect x="10" y="10" width="10" height="10"/>
<rect x="10" y="10" width="10" height="10"/>
<rect x="10" y="10" width="10" height="10"/>
<rect x="10" y="10" width="10" height="10"/>
<rect x="10" y="10" width="10" height="10"/>

Being a large app with multiple ways to do almost everything, Illustrator also has multiple methods to export SVGs. Exporting an SVG via the Save As menu command results in something that’s far worse than Export As, and is frankly a bit of a mess.

It’s difficult to assess the precision of Save As’ SVG. The values contained in the SVG would visually look almost identical to Export As, but the results don’t match the input. 10.1000004 is not 10.1. What’s up with the width and height changing? I know float to string conversions are difficult, but Export As gets this right, as do other design tools. I consider numerically incorrect values to be a failure, so Save As doesn’t successfully meet the criteria for 1 decimal place of precision. Thankfully, Export As can be used instead of Save As.

<rect x="10.1000004" y="10.1000004" width="10" height="10"/>
<rect x="10.0100002" y="10.0100002" width="10" height="10"/>
<rect x="10.0010004" y="10.0010004" width="9.999999" height="9.999999"/>
<rect x="10.0001001" y="10.0001001" width="9.999999" height="9.999999"/>
<rect x="10.0000095" y="10.0000095" width="10" height="10"/>
<rect x="10.000001" y="10.000001" width="9.999999" height="9.999999"/>
<rect x="10" y="10" width="10" height="10"/>
<rect x="10" y="10" width="10" height="10"/>
<rect x="10" y="10" width="10" height="10"/>
<rect x="10" y="10" width="10" height="10"/>

This is pretty interesting, because the settings for Save As go up to 7 decimal places. You might think it’s more precise than Export As, but it’s not. The image below also contains the settings I used for Export As and Save As.

Sketch #

Precision wise, Sketch was the best tool tested, with 7 decimal places of precision successfully passed through. The fill attribute isn’t needed, and it would be good to be able to optionally remove the IDs, but precision wise, this is good.

<rect id="Rectangle" fill="#000000" x="10.1" y="10.1" width="10" height="10"></rect>
<rect id="Rectangle" fill="#000000" x="10.01" y="10.01" width="10" height="10"></rect>
<rect id="Rectangle" fill="#000000" x="10.001" y="10.001" width="10" height="10"></rect>
<rect id="Rectangle" fill="#000000" x="10.0001" y="10.0001" width="10" height="10"></rect>
<rect id="Rectangle" fill="#000000" x="10.00001" y="10.00001" width="10" height="10"></rect>
<rect id="Rectangle" fill="#000000" x="10.000001" y="10.000001" width="10" height="10"></rect>
<rect id="Rectangle" fill="#000000" x="10.0000001" y="10.0000001" width="10" height="10"></rect>
<rect id="Rectangle" fill="#000000" x="10" y="10" width="10" height="10"></rect>
<rect id="Rectangle" fill="#000000" x="10" y="10" width="10" height="10"></rect>
<rect id="Rectangle" fill="#000000" x="10" y="10" width="10" height="10"></rect>

XD #

XD only makes it to 3 decimal places before giving up. It also uses the transform attribute for position and sprays layer names everywhere. I’m not a fan of this at all.

<rect id="Rectangle_2" data-name="Rectangle 2" width="10" height="10" transform="translate(10.1 10.1)"/>
<rect id="Rectangle_3" data-name="Rectangle 3" width="10" height="10" transform="translate(10.01 10.01)"/>
<rect id="Rectangle_4" data-name="Rectangle 4" width="10" height="10" transform="translate(10.001 10.001)"/>
<rect id="Rectangle_5" data-name="Rectangle 5" width="10" height="10" transform="translate(10 10)"/>
<rect id="Rectangle_6" data-name="Rectangle 6" width="10" height="10" transform="translate(10 10)"/>
<rect id="Rectangle_7" data-name="Rectangle 7" width="10" height="10" transform="translate(10 10)"/>
<rect id="Rectangle_8" data-name="Rectangle 8" width="10" height="10" transform="translate(10 10)"/>
<rect id="Rectangle_9" data-name="Rectangle 9" width="10" height="10" transform="translate(10 10)"/>
<rect id="Rectangle_10" data-name="Rectangle 10" width="10" height="10" transform="translate(10 10)"/>
<rect id="Rectangle_11" data-name="Rectangle 11" width="10" height="10" transform="translate(10 10)"/>

Other tests #

So far, only the X and Y position attribute precision have been discussed. I ran similar tests for rectangle width and height, rectangle corner radius, stroke size, polygon anchor point position, bézier control handle position, and object opacity. That’s not a complete set of every single attribute, but it should cover a lot of the common places rounding precision matters.

<rect x="10" y="10" width="10" height="10" style="opacity: 0.1"/>
<rect x="10" y="10" width="10" height="10" style="opacity: 0.01"/>
<rect x="10" y="10" width="10" height="10" style="opacity: 0.001"/>
<rect x="10" y="10" width="10" height="10" style="opacity: 0.0001"/>
<rect x="10" y="10" width="10" height="10" style="opacity: 0.00001"/>
<rect x="10" y="10" width="10" height="10" style="opacity: 0.000001"/>
<rect x="10" y="10" width="10" height="10" style="opacity: 0.0000001"/>
<rect x="10" y="10" width="10" height="10" style="opacity: 0.00000001"/>
<rect x="10" y="10" width="10" height="10" style="opacity: 0.000000001"/>
<rect x="10" y="10" width="10" height="10" style="opacity: 0.0000000001"/>

Surprisingly, every single tool had inconsistent results, with different rounding across different attributes. This was true, even when a setting for the precision existed.

Published 18 April 2022.