SVG in GTK

GTK has been using SVG for symbolic icons since essentially forever. It hasn’t been a perfect relationship, though.

Pre-History

For the longest time (all through the GTK 3 era, and until recently), we’ve used librsvg indirectly, through gdk-pixbuf, to obtain rendered icons, and then we used some pixel tricks to recolor the resulting image according to the theme.

Symbolic icon, with success color

This works, but it gives up on the defining feature of SVG: its scalability.

Once you’ve rasterized your icon at a given size, all you’re left with is pixels. In the GTK 3 era, this wasn’t a problem, but in GTK 4, we have a scene graph and fractional scaling, so we could do *much* better if we don’t rasterize early.

Symbolic icon, pixellated

Unfortunately, librsvg’s API isn’t set up to let us easily translate SVG into our own render nodes. And its rust nature makes for an inconvenient dependency, so we held off on depending on it for a long time.

History

Early this year, I grew tired of this situation, and decided to improve our story for icons, and symbolic ones in particular.

So I set out to see how hard it would be to parse the very limited subset of SVG used in symbolic icons myself. It turned out to be relatively easy. I quickly managed to parse 99% of the Adwaita symbolic icons, so I decided to merge this work for GTK 4.20.

There were some detours and complications along the way. Since my simple parser couldn’t handle 100% of Adwaita (let alone all of the SVGs out there), a fallback to a proper SVG parser was needed. So we added a librsvg dependency after all. Since our new Android backend has an even more difficult time with rust than our other backends, we needed to arrange for a non-rust librsvg branch to be used when necessary.

One thing that this hand-rolled SVG parser improved upon is that it allows stroking, in addition to filling. I documented the format for symbolic icons here.

Starting over

A bit later, I was inspired by Apple’s SF Symbols work to look into how hard it would be to extend my SVG parser with a few custom attributes to enable dynamic strokes.

It turned out to be easy again. With a handful of attributes, I could create plausible-looking animations and transitions. And it was fun to play with. When I showed this work to Jakub and Lapo at GUADEC, they were intrigued, so I decided to keep pushing this forward, and it landed in early GTK 4.21, as GtkPathPaintable.

To make experimenting with this easier, I made a quick editor.  It was invaluable to have Jakub as an early adopter play with the editor while I was improving the implementation. Some very good ideas came out of this rapid feedback cycle, for example dynamic stroke width.

You can get some impression of the new stroke-based icons Jakub has been working on here.

Recent happenings

As summer was turning to fall, I felt that I should attempt to support SVG more completely, including grouping and animations. GTK’s rendering infrastructure has most of the pieces that are required for SVG after all: transforms, filters, clips, paths, gradients are all supported.

This was *not* easy.

But eventually, things started to fall into place. And this week, I’ve replaced  GtkPathPaintable with GtkSvg, which is a GdkPaintable that supports SVG. At least, the subset of SVG that is most relevant for icons. And that includes animations.

 

This is still a subset of full SVG, but converting a few random lottie files to SVG animations gave me a decent success rate for getting things to display mostly ok.

The details are spelled out here.

Summary

GTK 4.22 will natively support SVG, including SVG animations.

If you’d like to help improve this further, here are some some suggestions.

If you would like to support the GNOME foundation, who’s infrastructure and hosting GTK relies on, please donate.

❤️