svg2ivg

package module
v0.0.0-...-49e18aa Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 10, 2026 License: MIT Imports: 18 Imported by: 0

README

svg2ivg

Convert SVG files to IconVG, the compact binary vector format (FFV0).

assets/arrow-left.svg  ──svg2ivg──▶  arrow-left.iconvg

svg2ivg handles a subset of shapes, groups, transforms, CSS, gradients, strokes and text. It rejects known unsupported constructs instead of deliberately dropping them, but conversion can still be geometrically lossy. Preview every unfamiliar icon set. See SVG support and Looking at what came out.

Beyond converting files, there are three things this repository is, each with its own document:

  • Generating Go-format go writes the icons into a Go source file as constants, so the binary carries its artwork instead of shipping files beside it.
  • As a library — the same conversion from your own Go program, at build time or at run time.
  • Using the icons with Gio — svg2ivg was created for Gio's widget.Icon; this is what to do with the output once you have it.

Install

svg2ivg requires Go 1.26.4 or newer.

go install github.com/Randomblock1/svg2ivg/cmd/svg2ivg@latest

Use

Each operand is an .svg file, or a directory whose .svg files are converted. Converted files are named after the artwork and land beside it, or in -o:

svg2ivg icon.svg                    # writes icon.iconvg
svg2ivg assets/ -o icons/           # writes icons/*.iconvg
svg2ivg a.svg b.svg -o out/

By default only the .svg files directly inside a directory are converted, but you can use -r to convert all .svg files below it recursively:

svg2ivg assets/ -r -o icons/

A recursive run reproduces under -o the layout it found, so assets/nav/arrow.svg becomes icons/nav/arrow.iconvg. Flattening it would collide on every set that organizes icons into subdirectories reusing one leaf name. As an example, Material Design ships each of its icons as a 24px.svg. Without -o each file is written where its source lives, which preserves the layout by construction.

A file you name explicitly is converted whatever its extension. Directory symlinks discovered below an input directory are not followed, so a recursive walk cannot loop. In non-recursive mode an explicitly named directory symlink is read like its target; with -r, name the real directory instead.

Nothing is written until everything has converted, so one broken asset fails the run rather than leaving a half-converted directory behind.

Flags
Flag Default Meaning
-o (beside each source) Directory to convert into
-r false Descend into subdirectories, reproducing the layout under -o
-size 48 IconVG coordinate size of the longer side
-keep-colors false Emit literal colors instead of making single-color icons themeable
-fonts (system) Comma-separated directories to resolve <text> fonts from
-no-text false Reject SVGs containing <text> instead of converting it
-notice (none) File carried along with the artwork, e.g. its LICENSE: copied into -o
-preview (none) Also write a PNG contact sheet of the converted icons
-prefix (none) (-preview) Prefix for every preview label
-format iconvg iconvg files, or a go source file of constants — see Generating Go

Flags may appear before, after, or between operands. A flag is rejected rather than ignored when nothing it describes is being produced: -notice needs an -o to copy into, and -prefix names preview labels, so without -preview it is refused. -format go adds -pkg and -bytes, which are documented in Generating Go.

IconVG is resolution independent and renderers scale icons to the size they are laid out at, so -size sets coordinate precision, not a rendered size.

Looking at what came out

svg2ivg ./assets -o ./icons -preview icons.png

-preview writes a PNG contact sheet: every icon rasterized back out of the IconVG it was just encoded to, labelled with a CamelCase form of the file name (arrow-left.svg becomes ArrowLeft), which -prefix prepends to.

This is worth doing at least once per icon set. An exit code only says the conversion produced well-formed IconVG — it cannot say the result still looks like the artwork. Strokes are flattened into filled outlines and <text> into glyph geometry, and both are lossy geometric transforms that fail quietly: a hairline stroke or a tiny dot can vanish into a graphic that decodes perfectly. Rendering it is the only way to find out.

Icons are drawn over a checkerboard, so a white icon and a missing one do not look alike, and in the colors they carry — a themeable icon shows the color it was drawn in, not the color it will be painted in.

Provenance and licensing

For a tool whose job is vendoring third-party artwork, the converted output should say where the artwork came from. -notice names a file to carry along with it — usually the icon set's LICENSE.

A .iconvg file has no header to record it in, so the notice travels as a file of its own: it is copied into -o, keeping the name it already had, so the converted directory is one you can vendor whole.

svg2ivg ./assets -o ./icons -notice ./assets/LICENSE   # writes icons/*.iconvg and icons/LICENSE

This needs somewhere to copy to, so -notice requires -o and is refused without it: a run with no -o converts each icon beside its source, where the license is already sitting. Under -format go the notice is copied into the generated header instead.

SVG files carry no license metadata in practice — the <metadata> element exists, but Lucide, Feather, Heroicons, Bootstrap Icons and Material Symbols all ship a plain LICENSE file next to the artwork instead, which is also what REUSE and SPDX assume. So svg2ivg looks for one (LICENSE, LICENCE, COPYING, with or without a .txt/.md suffix) alongside the artwork and, if it finds one you did not pass to -notice, prints a one-line reminder to stderr.

The reminder appears wherever the conversion is about to land somewhere the license does not reach: an -o directory, or a generated Go file. Converting .iconvg files in place, without -o, leaves the two together and says nothing.

Colors and theming

IconVG graphics can refer to a palette instead of naming a color, which lets the decoder recolor them. svg2ivg uses that:

  • An icon drawn in a single color — every Material Design icon, and most icon sets — refers to palette entry 0 instead of naming its color, so a decoder that overrides entry 0 recolors it. Opacity does not count as a second color: one hue used at several opacities stays themeable, and each shape keeps its own alpha.
  • A multi-color icon, or one containing a gradient, keeps its own colors and ignores the palette.

Pass -keep-colors to opt out and always emit literal colors.

The suggested palette in the output is seeded with the icon's own color, so decoders that do not override entry 0 still render the artwork as drawn.

Gio overrides entry 0 with the color passed to Layout — see Recoloring.

Text and fonts

<text> is converted to glyph outlines, so it needs a font to convert against. By default families resolve through the system font directories, which means the same SVG can produce different bytes on a machine with different fonts installed.

To pin it, commit the fonts alongside your assets and point -fonts at them:

svg2ivg ./assets -o ./icons -fonts ./assets/fonts

With committed fonts, conversion depends only on what is in the repository. If you would rather have no font dependency at all, convert text to outlines in your editor and pass -no-text, which turns any remaining <text> into an error.

A family that cannot be found is an error naming the family, not a fallback to some other face.

Note that <tspan> is rejected: the SVG reader draws nothing inside one, so allowing it would silently drop the text. Put the text directly in <text>.

SVG support

SVG is read with github.com/tdewolff/canvas, which resolves groups, transforms, units, CSS and shape geometry before svg2ivg sees anything. The supported subset is:

  • All shape elements: <path>, <rect> (including one radius or equal non-percentage rx/ry), <circle>, <ellipse>, <polygon>, <polyline>, <line>
  • <g> and transform at any nesting depth
  • Every path command, arcs included
  • Fills, fill-opacity, stroke-opacity, opacity, currentColor, and CSS colors through presentation attributes and style=
  • Strokes, converted to filled outlines, honoring stroke-width, stroke-linecap, stroke-linejoin and stroke-dasharray
  • Linear and radial gradients, including gradients under a transform
  • fill-rule="evenodd", from an attribute, a style= declaration, or inherited from an ancestor
  • viewBox with any origin, and non-square aspect ratios
  • <text>, converted to outlines — see Text and fonts

The following unsupported constructs are detected and rejected:

Rejected Why
<image> IconVG is vector-only
filter, <filter> no filter, blur or drop-shadow primitives
mask, <mask> no soft masks
clip-path, <clipPath> no clipping — flatten the clip into the path geometry
<pattern> no pattern fills
<use> the SVG reader ignores it, which would drop artwork
<tspan>, <textPath>, <tref> the SVG reader would drop some or all text
Unequal or dual-percentage rx and ry on <rect> the SVG reader uses one resolved radius; use one radius or equal absolute values
Text styling beyond family, size, and anchor; dx, dy, rotate, or textLength the SVG reader ignores these controls; convert styled text to outlines
<symbol>, <switch>, <foreignObject> their rendering semantics are not preserved
Foreign-namespace artwork elements only SVG elements can contribute artwork; non-rendering <metadata> is removed
Namespace-prefixed SVG elements such as <s:path> the SVG reader recognizes only unprefixed SVG tag names
display="none", visibility="hidden" or visibility="collapse" the SVG reader would paint hidden artwork
<style> selector stylesheets the SVG reader does not implement CSS specificity or cascade precedence; use presentation attributes or style=
CSS !important, comments, or escapes in style= the SVG reader and inline-style analyzer do not preserve them consistently
Non-local, missing, non-canonical, or fallback paint-server references the requested fill or stroke cannot be resolved faithfully; use url(#id)
Gradient templates through href or xlink:href the SVG reader does not inherit referenced gradient definitions
Radial object-bounding-box gradients, implicit/unitless object-bounding-box coordinates, implicit/percentage coordinates with userSpaceOnUse, or gradientTransform the SVG reader interprets or ignores them incorrectly
Opacity on gradient fills or strokes the SVG reader would discard it
Inline styling or currentColor on gradient stops the SVG reader would not preserve it

filter, mask and clip-path are rejected as presentation attributes or in style=. Selector stylesheets are rejected as a whole because their cascade cannot be preserved reliably. Set to none, they change nothing and are left alone.

Definitions that are declared but never referenced (an unused <clipPath> in <defs>, say) are ignored rather than rejected — exporters emit those routinely.

A file that fails for one of these reasons says so. An error reported as an internal error means the converter hit a case it does not handle instead, and is a bug worth reporting with the SVG; the detail is under Errors.

Known limitations
  • Group opacity is applied per shape, not to the composited group. IconVG has no group compositing, so <g opacity="0.5"> fades each descendant individually. The two agree unless shapes inside the group overlap each other, where the overlap will read darker than a browser would draw it.
  • Focal radial gradients (fx/fy away from cx/cy) lose their focus: IconVG radial gradients are concentric.
  • spreadMethod is always pad; the SVG reader does not record it.
  • 64 color registers cap how many gradient stops one gradient can hold (58).

Documentation

Overview

Package svg2ivg converts SVG documents into IconVG, the compact vector format that gioui.org/widget.Icon renders:

data, err := svg2ivg.ConvertFile("arrow-left.svg", svg2ivg.Options{})
icon, err := widget.NewIcon(data)

Convert is the whole of the conversion API. Everything else here is batch plumbing on top of it: SVGFiles expands a set of paths, and Generate and Preview run the results through code generation and a contact sheet.

Generate exists because a Go program usually wants its artwork inside the binary rather than beside it. It writes a source file declaring one IconVG string constant per icon, so the program embeds the icon data as ordinary string literals and never parses XML — or opens a file — at run time. Converting artwork that arrives while the program is running works just as well, at the cost of linking the SVG reader into the binary; see CacheKey.

SVG is read with github.com/tdewolff/canvas. The supported subset includes shapes, groups, transforms, units, CSS colors, gradients, strokes, dashes and text. Known unsupported constructs are reported as errors rather than deliberately dropped. See the package README for details and limitations.

Conversion is reproducible except for <text>, whose fonts resolve against the machine's own font directories unless Options.FontDirs says otherwise.

Index

Constants

View Source
const DefaultSize = 48

DefaultSize is the IconVG output size used when Options.Size is zero. It matches the Material Design icon set.

Variables

This section is empty.

Functions

func CacheKey

func CacheKey(src []byte, opts Options) string

CacheKey returns a stable key for src and the options that affect Convert. Repeating the same request shares a key, so it suits a program converting the same artwork more than once — which build-time generation never does, and a program converting SVG that arrived at run time usually does. Different SVG source bytes may have different keys even when they produce identical IconVG.

The key covers only the four options Convert reads, so varying the ones that configure code generation does not cause a spurious miss. It does not cover the converter itself: upgrading svg2ivg can change the bytes a key maps to, which makes this safe for a cache held in memory and unsafe for one that outlives the process.

Cache the decoded gioui.org/widget.Icon rather than these bytes. Conversion costs microseconds, but an Icon caches its most recent rasterisation, so building a fresh one each frame throws that away and decodes, rasterises and re-uploads a texture every frame — far and away the larger cost:

key := svg2ivg.CacheKey(src, opts)
icon, ok := cache[key]
if !ok {
	data, err := svg2ivg.Convert(bytes.NewReader(src), opts)
	if err != nil {
		return err
	}
	if icon, err = widget.NewIcon(data); err != nil {
		return err
	}
	cache[key] = icon
}

func Convert

func Convert(r io.Reader, opts Options) ([]byte, error)

Convert reads one SVG document and returns it encoded as IconVG, in the FFV0 encoding that gioui.org/widget.NewIcon decodes.

Only Options.Size, Options.KeepColors, Options.FontDirs and Options.NoText apply; the rest configure code generation.

Known unsupported constructs such as raster images, filters, soft masks and clip paths are reported as errors rather than deliberately dropped. Other documented SVG-to-IconVG limitations can still be lossy; see the README.

<text> is converted to outlines. Font families resolve against Options.FontDirs, or against the system fonts when that is empty — in which case the same SVG can convert differently on a machine with different fonts installed. Options.NoText rejects text outright.

Every panic the SVG reader and the geometry beneath it can throw is returned as an error instead, so converting artwork that arrived at run time cannot take the program down. See InternalError.

func ConvertFile

func ConvertFile(path string, opts Options) ([]byte, error)

ConvertFile is Convert reading from the named file.

func Generate

func Generate(w io.Writer, files []string, opts Options) error

Generate converts each named SVG file and writes the generated Go source to w. The output is gofmt'd. An unconvertible file fails the whole run rather than being skipped, so a broken asset cannot silently vanish from the generated package.

func GenerateDir

func GenerateDir(w io.Writer, dir string, opts Options) error

GenerateDir converts every .svg file directly inside dir and writes the generated Go source to w. Files are processed in lexical order so output is stable across runs.

func GenerateFile

func GenerateFile(out, dir string, opts Options) error

GenerateFile is GenerateDir writing to the file named out, which is created or truncated, along with any directories leading to it.

func GenerateIcons

func GenerateIcons(w io.Writer, icons []Icon, opts Options) error

GenerateIcons is Generate over icons that have already been converted. Only Options.Package, Options.Notice and Options.ByteSlices apply; the rest were read when the icons were converted.

func Ident

func Ident(path string) string

Ident derives an exported Go identifier from an SVG file path: "arrow-left.svg" becomes "ArrowLeft". A name that would otherwise start with a digit is prefixed with "X" — "2x-zoom.svg" becomes "X2xZoom" — since Go identifiers cannot.

Generate applies Options.Prefix before that patch-up, so with a prefix of "Icon" the same file becomes Icon2xZoom rather than IconX2xZoom.

func Preview

func Preview(w io.Writer, files []string, opts Options) error

Preview converts each named SVG file and writes a PNG contact sheet of the results to w: every icon rasterized from its own IconVG encoding, labelled with the identifier Generate would give it.

Rasterizing what was encoded, rather than the SVG that went in, is the point. Strokes become filled outlines and text becomes glyph geometry during conversion, and neither transformation reports its own inaccuracy — the output is well-formed IconVG whether or not it still looks like the artwork. A contact sheet is the only way to see that.

Icons are drawn in the colours they carry. A themeable icon shows the colour its suggested palette was seeded with, which is the colour it was drawn in, not the colour gioui.org/widget.Icon will paint it.

func PreviewDir

func PreviewDir(w io.Writer, dir string, opts Options) error

PreviewDir is Preview over every .svg file directly inside dir.

func PreviewFile

func PreviewFile(out, dir string, opts Options) error

PreviewFile is PreviewDir writing to the file named out, which is created or truncated, along with any directories leading to it.

func PreviewIcons

func PreviewIcons(w io.Writer, icons []Icon) error

PreviewIcons is Preview over icons that have already been converted. No option applies: everything the sheet shows was decided when they were.

func SVGFiles

func SVGFiles(paths ...string) ([]string, error)

SVGFiles expands a mix of file and directory paths into the list of SVG files they name. A path to a file is taken as given, whatever its extension, since naming it is explicit enough. A directory contributes the .svg files directly inside it; the walk stops there, because a nested directory is as likely to be somebody else's icon set as more of this one. SVGFilesRecursive descends.

A directory holding no .svg files is an error rather than an empty contribution: naming one is a typo far more often than it is a request to convert nothing.

The result is sorted and deduplicated, so passing both a directory and a file inside it converts that file once, and the output does not depend on the order the paths were given in.

func SVGFilesRecursive

func SVGFilesRecursive(paths ...string) ([]string, error)

SVGFilesRecursive is SVGFiles with every directory walked all the way down rather than one level deep. A directory whose tree holds no .svg file at any depth is the error case.

Symbolic links to directories are not followed, so an icon set cannot be walked twice or forever.

Types

type Icon

type Icon struct {
	// Source is the path the artwork was read from.
	Source string
	// Name is the Go identifier, Options.Prefix already applied.
	Name string
	// Data is the IconVG encoding, as Convert returns it.
	Data []byte
}

Icon is one converted file: where the artwork came from, the identifier Generate declares it under, and its IconVG encoding. ConvertFiles produces them, and GenerateIcons and PreviewIcons consume them.

func ConvertFiles

func ConvertFiles(files []string, opts Options) ([]Icon, error)

ConvertFiles converts each named SVG file, in lexical order so that whatever is written from the result is stable however the caller listed them. An unconvertible file fails the whole run rather than being skipped, so a broken asset cannot silently vanish from the output.

Reach for this when the same icons are written more than once — a generated source file and a contact sheet, say. Generate and Preview each convert what they are given, so calling both on one file list converts everything twice.

type InternalError

type InternalError struct {
	// Value is what was passed to panic.
	Value any
	// Stack is the stack trace as of the panic.
	Stack []byte
}

InternalError reports a panic that escaped the SVG reader or the geometry code beneath it. It means the converter hit a case it does not handle, not that the SVG is invalid: an SVG that fails this way is worth reporting at https://github.com/Randomblock1/svg2ivg/issues, with Stack attached.

func (*InternalError) Error

func (e *InternalError) Error() string

type Options

type Options struct {
	// Package is the package clause of the generated file. Required by
	// Generate, ignored by Convert.
	Package string
	// Size is the length, in IconVG units, of the longer side of the output
	// graphic. Zero means DefaultSize. IconVG is resolution independent, so
	// this sets coordinate precision rather than a rendered size.
	Size float32
	// Prefix is prepended to every generated identifier, e.g. "Icon" turns
	// star.svg into IconStar. It must begin with an upper-case letter or digit.
	Prefix string
	// KeepColors emits every fill as a literal colour. By default an icon
	// drawn in a single colour instead refers to palette entry 0, which
	// gioui.org/widget.Icon replaces with the colour passed to Layout, so the
	// icon follows the surrounding theme.
	KeepColors bool
	// FontDirs are the directories <text> font families resolve against. When
	// empty the system font directories are used, which makes conversion
	// depend on what is installed on the machine running it; pointing this at
	// fonts committed alongside the assets makes the output reproducible.
	FontDirs []string
	// NoText rejects any SVG containing <text> instead of converting it to
	// outlines. Use it to guarantee that no output can depend on locally
	// installed fonts.
	NoText bool
	// Notice is emitted as a comment block below the generated-code header,
	// before the package clause. Vendoring third-party artwork usually means
	// carrying its copyright notice and license along with it; this is where
	// they go. Typically the contents of the LICENSE file shipped with the
	// icon set.
	Notice string
	// ByteSlices emits each icon as a mutable []byte variable rather than a
	// string constant, matching what gioui.org/widget.NewIcon takes and saving
	// the []byte(...) conversion at the call site. It costs the two things that
	// make constants the default: a string literal is one token to the compiler
	// where a list of bytes is one per byte, and only a constant can live in the
	// binary's read-only segment. Being unwritable is a third benefit rather
	// than the reason.
	ByteSlices bool
}

Options configures conversion and code generation.

type Unsupported

type Unsupported struct {
	// Feature names the offending element or attribute, e.g. "<image>".
	Feature string
	// Reason explains why it cannot be converted.
	Reason string
}

Unsupported reports an SVG feature that has no IconVG representation, or that the converter would otherwise have dropped without saying so. Conversion fails rather than emitting an icon that is quietly missing artwork.

func (*Unsupported) Error

func (e *Unsupported) Error() string

Directories

Path Synopsis
cmd
svg2ivg command
Command svg2ivg converts SVG files into IconVG, the compact vector format that gioui.org/widget.Icon renders.
Command svg2ivg converts SVG files into IconVG, the compact vector format that gioui.org/widget.Icon renders.
internal
convert
Package convert turns one SVG document into an IconVG graphic, in the FFV0 encoding that gioui.org/widget.NewIcon decodes.
Package convert turns one SVG document into an IconVG graphic, in the FFV0 encoding that gioui.org/widget.NewIcon decodes.
gogen
Package gogen writes the Go source file that declares the converted icons.
Package gogen writes the Go source file that declares the converted icons.
ivgerr
Package ivgerr is the vocabulary for reporting SVG features that have no IconVG representation.
Package ivgerr is the vocabulary for reporting SVG features that have no IconVG representation.
outfile
Package outfile writes what a generator produced to a file.
Package outfile writes what a generator produced to a file.
prepass
Package prepass rewrites an SVG document into an equivalent one that github.com/tdewolff/canvas's SVG reader renders faithfully, and reports an error for anything it would render wrongly or drop.
Package prepass rewrites an SVG document into an equivalent one that github.com/tdewolff/canvas's SVG reader renders faithfully, and reports an error for anything it would render wrongly or drop.
sheet
Package sheet lays out rasterized IconVG graphics as a labelled contact sheet.
Package sheet lays out rasterized IconVG graphics as a labelled contact sheet.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL