~eliasnaur/giologo

4d09dbaa61be8665e995221a9056dbd9b9644682 — Elias Naur 5 years ago
initial import

Signed-off-by: Elias Naur <mail@eliasnaur.com>
8 files changed, 644 insertions(+), 0 deletions(-)

A README.md
A data.go
A data_test.go
A gen.go
A genicons.go
A go.mod
A go.sum
A ic_gio_48px.svg
A  => README.md +21 -0
@@ 1,21 @@
[Egon Elbre's](https://egonelbre.com/) logo for
[Gio](https://gioui.org) in SVG and
[IconVG](https://godoc.org/golang.org/x/exp/shiny/iconvg) format.

# Use

	import "eliasnaur.com/giologo"
	import "golang.org/x/exp/shiny/iconvg"

	sz := 100 // pixels
	logo := giologo.Gio
	m, _ := iconvg.DecodeMetadata(logo)
	dx, dy := m.ViewBox.AspectRatio()
	img := image.NewRGBA(image.Rectangle{Max: image.Point{X: sz, Y: int(float32(sz) * dy / dx)}})
	var ico iconvg.Rasterizer
	ico.SetDstImage(img, img.Bounds(), draw.Src)
	// Use white for icons.
	m.Palette[0] = color.RGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff}
	iconvg.Decode(&ico, logo, &iconvg.DecodeOptions{
		Palette: &m.Palette,
	})

A  => data.go +54 -0
@@ 1,54 @@
// generated by go run gen.go; DO NOT EDIT

package giologo

var Gio = []byte{
	0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0x80, 0x80, 0xc0, 0x19, 0x88,
	0xe5, 0x69, 0xbf, 0x21, 0x80, 0x3d, 0x80, 0x31, 0x80, 0x91, 0x80, 0x29, 0x80, 0xc5, 0x80, 0xe1,
	0x7f, 0xb9, 0x80, 0x71, 0x7f, 0x05, 0x81, 0xd9, 0x7e, 0x35, 0x81, 0xc9, 0x7c, 0x0d, 0x81, 0xad,
	0x79, 0x71, 0x82, 0xc5, 0x76, 0x25, 0x84, 0x95, 0x7e, 0xd5, 0x80, 0x35, 0x7d, 0xc1, 0x81, 0xe9,
	0x7b, 0xc1, 0x82, 0xb1, 0x7c, 0x8d, 0x82, 0xcd, 0x79, 0xa5, 0x85, 0x15, 0x78, 0x6d, 0x89, 0x45,
	0x7f, 0xa1, 0x81, 0xc5, 0x7e, 0x5d, 0x83, 0x8d, 0x7e, 0x21, 0x85, 0xd9, 0x7f, 0x51, 0x81, 0xd1,
	0x7f, 0xa5, 0x82, 0xf9, 0x7f, 0xf9, 0x83, 0x2d, 0x80, 0x69, 0x81, 0x99, 0x80, 0xcd, 0x82, 0x51,
	0x81, 0x09, 0x84, 0x8d, 0x80, 0xf1, 0x80, 0x39, 0x81, 0xcd, 0x81, 0x0d, 0x82, 0x85, 0x82, 0x86,
	0x95, 0x82, 0x31, 0x87, 0xc9, 0x83, 0xdd, 0x8a, 0x39, 0x84, 0xd9, 0x82, 0x59, 0x80, 0x89, 0x85,
	0x35, 0x80, 0x1d, 0x88, 0xb1, 0x7f, 0x21, 0x83, 0x65, 0x7f, 0x99, 0x86, 0xd1, 0x7e, 0xa9, 0x88,
	0x69, 0x7c, 0xb1, 0x80, 0x35, 0x7f, 0x19, 0x81, 0x25, 0x7e, 0x82, 0x2d, 0x7d, 0xd9, 0x7f, 0x65,
	0x7e, 0xd1, 0x7e, 0x21, 0x7d, 0x95, 0x7d, 0x3d, 0x7c, 0x85, 0x7f, 0xa9, 0x7f, 0x7e, 0x5d, 0x7f,
	0x71, 0x7e, 0x31, 0x7f, 0xe5, 0x7f, 0xf9, 0x7f, 0xc9, 0x7f, 0xf1, 0x7f, 0xad, 0x7f, 0xe5, 0x7f,
	0xbf, 0x9d, 0x7f, 0xd5, 0x7f, 0x55, 0x7f, 0x71, 0x7f, 0x41, 0x7f, 0x09, 0x7f, 0xf1, 0x7f, 0xa1,
	0x7f, 0x05, 0x80, 0x45, 0x7f, 0x3d, 0x80, 0x05, 0x7f, 0x81, 0x80, 0x65, 0x7f, 0x21, 0x81, 0x69,
	0x7f, 0xc5, 0x81, 0x9d, 0x7f, 0xb9, 0x80, 0x3d, 0x80, 0x69, 0x81, 0x95, 0x80, 0x11, 0x82, 0xf9,
	0x80, 0x05, 0x81, 0x9d, 0x80, 0xfd, 0x81, 0x55, 0x81, 0xb9, 0x82, 0x41, 0x82, 0xd9, 0x80, 0x0d,
	0x81, 0x85, 0x81, 0x3d, 0x82, 0xd5, 0x81, 0x59, 0x83, 0x81, 0x80, 0xbd, 0x81, 0x49, 0x80, 0xa1,
	0x83, 0x81, 0x7f, 0x41, 0x85, 0x2d, 0x7f, 0xb1, 0x81, 0xb1, 0x7d, 0x45, 0x83, 0x61, 0x7c, 0x41,
	0x84, 0x6d, 0x7e, 0x31, 0x81, 0x95, 0x7c, 0x41, 0x82, 0xf9, 0x7a, 0xd5, 0x82, 0xf9, 0x7c, 0x19,
	0x81, 0xcd, 0x79, 0xb1, 0x81, 0x0d, 0x77, 0xa5, 0x81, 0xa9, 0x7b, 0xed, 0x7f, 0xb9, 0x76, 0x71,
	0x7e, 0x31, 0x73, 0x8d, 0x7c, 0xa9, 0x7d, 0xc1, 0x7e, 0x99, 0x7b, 0x15, 0x7d, 0x05, 0x7a, 0x09,
	0x7b, 0xcd, 0x7d, 0x2d, 0x7d, 0xcd, 0x7c, 0x29, 0x79, 0xe5, 0x7c, 0xfd, 0x75, 0x21, 0x80, 0x89,
	0x7b, 0x11, 0x82, 0xb1, 0x76, 0x61, 0x84, 0x79, 0x73, 0xf1, 0x81, 0x51, 0x7d, 0x69, 0x84, 0x76,
	0x15, 0x87, 0x09, 0x79, 0x51, 0x83, 0x91, 0x7d, 0x8e, 0x9d, 0x7b, 0xd5, 0x8a, 0x09, 0x7a, 0xb1,
	0x71, 0x81, 0x69, 0x7f, 0xe9, 0x82, 0xe1, 0x7e, 0x69, 0x84, 0x65, 0x7e, 0x9d, 0x80, 0xd1, 0x7f,
	0x49, 0x81, 0x31, 0x80, 0x8d, 0x81, 0x9d, 0x80, 0xe1, 0x80, 0x80, 0xc0, 0x45, 0x7b, 0x59, 0x82,
	0xbb, 0x75, 0x80, 0x25, 0x80, 0xb9, 0x80, 0x95, 0x80, 0xd5, 0x80, 0x05, 0x81, 0x5d, 0x80, 0x65,
	0x81, 0xed, 0x80, 0xb5, 0x82, 0x59, 0x81, 0x11, 0x84, 0x1d, 0x80, 0x59, 0x80, 0x39, 0x80, 0xb1,
	0x80, 0x3d, 0x80, 0x0d, 0x81, 0xe9, 0x7f, 0x91, 0x80, 0x7d, 0x7f, 0x82, 0x35, 0x7f, 0x7d, 0x81,
	0xc9, 0x7f, 0x61, 0x80, 0x91, 0x7f, 0xcd, 0x80, 0x35, 0x7f, 0x11, 0x81, 0xe1, 0x7f, 0x19, 0x80,
	0xbd, 0x7f, 0xe9, 0x7f, 0xa1, 0x7f, 0xd5, 0x7f, 0x75, 0x7f, 0x9d, 0x7f, 0x7e, 0x1d, 0x7f, 0x81,
	0x7e, 0xad, 0x7e, 0xc1, 0x7f, 0xc9, 0x7f, 0x8d, 0x7f, 0x7d, 0x7f, 0x95, 0x7f, 0x29, 0x7f, 0xf5,
	0x7f, 0x11, 0x7f, 0x19, 0x80, 0x21, 0x7e, 0x29, 0x80, 0x35, 0x7d, 0x0d, 0x80, 0x4d, 0x7f, 0x1d,
	0x80, 0x99, 0x7e, 0x1d, 0x80, 0xe5, 0x7d, 0x80, 0x99, 0x7f, 0x11, 0x80, 0x29, 0x7f, 0x5d, 0x80,
	0xdd, 0x7e, 0x3d, 0x80, 0xc5, 0x7f, 0x95, 0x80, 0xa9, 0x7f, 0xe5, 0x80, 0xc5, 0x7f, 0xe1, 0x80,
	0x80, 0xc0, 0x69, 0x84, 0x59, 0x84, 0xb3, 0x29, 0x7f, 0x75, 0x80, 0xc9, 0x7e, 0x89, 0x81, 0xf5,
	0x7e, 0x71, 0x82, 0x19, 0x80, 0x7d, 0x80, 0xad, 0x80, 0x91, 0x80, 0x19, 0x81, 0x95, 0x80, 0x5d,
	0x80, 0x05, 0x80, 0xc5, 0x80, 0xe5, 0x7f, 0xdd, 0x80, 0x81, 0x7f, 0x3d, 0x80, 0xf5, 0x7e, 0xd9,
	0x7f, 0x7c, 0x1d, 0x7f, 0x7d, 0x7d, 0xe3, 0xf9, 0x7f, 0xf5, 0x7d, 0xb7, 0x21, 0x81, 0xd9, 0x7f,
	0x51, 0x82, 0x05, 0x80, 0x49, 0x83, 0xa1, 0x80, 0xd9, 0x80, 0x89, 0x80, 0x79, 0x81, 0x69, 0x81,
	0x9d, 0x81, 0x65, 0x82, 0x31, 0x80, 0x81, 0x81, 0xad, 0x7f, 0x1d, 0x83, 0x95, 0x7e, 0x2d, 0x84,
	0x15, 0x7f, 0xe9, 0x80, 0xbd, 0x7d, 0x45, 0x81, 0x75, 0x7c, 0x11, 0x81, 0xbd, 0x7e, 0xcd, 0x7f,
	0x79, 0x7d, 0x29, 0x7f, 0xd9, 0x7c, 0x7c, 0x59, 0x7f, 0xcd, 0x7e, 0x85, 0x7f, 0x4d, 0x7d, 0x19,
	0x80, 0x1d, 0x7c, 0x8d, 0x80, 0xe5, 0x7e, 0x8d, 0x81, 0xe9, 0x7d, 0xcd, 0x82, 0xad, 0x7d, 0x21,
	0x80, 0xfd, 0x7f, 0x3d, 0x80, 0xf9, 0x7f, 0x5d, 0x80, 0xf9, 0x7f, 0xe1,
}

// In total, 2912 SVG bytes in 1 files converted to 732 IconVG bytes.

A  => data_test.go +10 -0
@@ 1,10 @@
// generated by go run genicons.go; DO NOT EDIT

package giologo

var list = []struct {
	name string
	data []byte
}{
	{"Gio", Gio},
}

A  => gen.go +3 -0
@@ 1,3 @@
package giologo

//go:generate go run genicons.go -pkg giologo .

A  => genicons.go +524 -0
@@ 1,524 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build ignore

package main

import (
	"bytes"
	"encoding/xml"
	"flag"
	"fmt"
	"go/format"
	"image/color"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"

	"golang.org/x/exp/shiny/iconvg"
	"golang.org/x/image/math/f32"
)

var outDir = flag.String("o", "", "output directory")
var pkgName = flag.String("pkg", "icons", "package name")

var (
	out      = new(bytes.Buffer)
	failures = []string{}
	varNames = []string{}

	totalFiles    int
	totalIVGBytes int
	totalSVGBytes int
)

func upperCase(s string) string {
	if c := s[0]; 'a' <= c && c <= 'z' {
		return string(c-0x20) + s[1:]
	}
	return s
}

func main() {
	flag.Parse()
	args := flag.Args()
	if len(args) < 1 {
		fmt.Fprintf(os.Stderr, "please provide a directory to convert\n")
		os.Exit(2)
	}
	iconsDir := args[0]

	out.WriteString("// generated by go run gen.go; DO NOT EDIT\n\npackage ")
	out.WriteString(*pkgName)
	out.WriteString("\n\n")

	if err := genDir(iconsDir); err != nil {
		log.Fatal(err)
	}

	fmt.Fprintf(out,
		"// In total, %d SVG bytes in %d files converted to %d IconVG bytes.\n",
		totalSVGBytes, totalFiles, totalIVGBytes)

	if len(failures) != 0 {
		out.WriteString("\n/*\nFAILURES:\n\n")
		for _, failure := range failures {
			out.WriteString(failure)
			out.WriteByte('\n')
		}
		out.WriteString("\n*/")
	}

	if *outDir != "" {
		if err := os.MkdirAll(*outDir, 0775); err != nil && !os.IsExist(err) {
			log.Fatal(err)
		}
	}
	raw := out.Bytes()
	formatted, err := format.Source(raw)
	if err != nil {
		log.Fatalf("gofmt failed: %v\n\nGenerated code:\n%s", err, raw)
	}
	if err := ioutil.WriteFile(filepath.Join(*outDir, "data.go"), formatted, 0644); err != nil {
		log.Fatalf("WriteFile failed: %s\n", err)
	}

	{
		b := new(bytes.Buffer)
		b.WriteString("// generated by go run genicons.go; DO NOT EDIT\n\npackage ")
		b.WriteString(*pkgName)
		b.WriteString("\n\n")
		b.WriteString("var list = []struct{ name string; data []byte } {\n")
		for _, v := range varNames {
			fmt.Fprintf(b, "{%q, %s},\n", v, v)
		}
		b.WriteString("}\n\n")
		raw := b.Bytes()
		formatted, err := format.Source(raw)
		if err != nil {
			log.Fatalf("gofmt failed: %v\n\nGenerated code:\n%s", err, raw)
		}
		if err := ioutil.WriteFile(filepath.Join(*outDir, "data_test.go"), formatted, 0644); err != nil {
			log.Fatalf("WriteFile failed: %s\n", err)
		}
	}
}

func genDir(dirName string) error {
	fqSVGDirName := filepath.FromSlash(dirName)
	f, err := os.Open(fqSVGDirName)
	if err != nil {
		return err
	}
	defer f.Close()

	infos, err := f.Readdir(-1)
	if err != nil {
		log.Fatal(err)
	}
	baseNames, fileNames, sizes := []string{}, map[string]string{}, map[string]int{}
	for _, info := range infos {
		name := info.Name()

		nameParts := strings.Split(name, "_")
		if len(nameParts) != 3 || nameParts[0] != "ic" {
			continue
		}
		baseName := nameParts[1]
		var size int
		if n, err := fmt.Sscanf(nameParts[2], "%dpx.svg", &size); err != nil || n != 1 {
			continue
		}
		if prevSize, ok := sizes[baseName]; ok {
			if size > prevSize {
				fileNames[baseName] = name
				sizes[baseName] = size
			}
		} else {
			fileNames[baseName] = name
			sizes[baseName] = size
			baseNames = append(baseNames, baseName)
		}
	}

	sort.Strings(baseNames)
	for _, baseName := range baseNames {
		fileName := fileNames[baseName]
		path := filepath.Join(dirName, fileName)
		f, err := ioutil.ReadFile(path)
		if err != nil {
			failures = append(failures, fmt.Sprintf("%s: %v", path, err))
			continue
		}
		if err = genFile(f, baseName, float32(sizes[baseName])); err != nil {
			failures = append(failures, fmt.Sprintf("%s: %v", path, err))
			continue
		}
	}
	return nil
}

type SVG struct {
	Width   string  `xml:"width,attr"`
	Height  string  `xml:"height,attr"`
	Fill    string  `xml:"fill,attr"`
	ViewBox string  `xml:"viewBox,attr"`
	Paths   []*Path `xml:"path"`
	// Some of the SVG files contain <circle> elements, not just <path>
	// elements. IconVG doesn't have circles per se. Instead, we convert such
	// circles to paired arcTo commands, tacked on to the first path.
	//
	// In general, this isn't correct if the circles and the path overlap, but
	// that doesn't happen in the specific case of the Material Design icons.
	Circles []Circle `xml:"circle"`
}

type Path struct {
	D           string   `xml:"d,attr"`
	Fill        string   `xml:"fill,attr"`
	FillOpacity *float32 `xml:"fill-opacity,attr"`
	Opacity     *float32 `xml:"opacity,attr"`

	creg uint8
}

type Circle struct {
	Cx float32 `xml:"cx,attr"`
	Cy float32 `xml:"cy,attr"`
	R  float32 `xml:"r,attr"`
}

func genFile(svgData []byte, baseName string, outSize float32) error {
	var varName string
	for _, s := range strings.Split(baseName, "_") {
		varName += upperCase(s)
	}
	fmt.Fprintf(out, "var %s = []byte{", varName)
	defer fmt.Fprintf(out, "\n}\n\n")
	varNames = append(varNames, varName)

	g := &SVG{}
	if err := xml.Unmarshal(svgData, g); err != nil {
		return err
	}

	var vbx, vby, vbx2, vby2 float32
	for i, v := range strings.Split(g.ViewBox, " ") {
		f, err := strconv.ParseFloat(v, 32)
		if err != nil {
			return fmt.Errorf("genFile: failed to parse ViewBox (%q):", g.ViewBox, err)
		}
		switch i {
		case 0:
			vbx = float32(f)
		case 1:
			vby = float32(f)
		case 2:
			vbx2 = float32(f)
		case 3:
			vby2 = float32(f)
		}
	}
	dx, dy := outSize, outSize
	var size float32
	if aspect := (vbx2 - vbx) / (vby2 - vby); aspect >= 1 {
		dy /= aspect
		size = vbx2 - vbx
	} else {
		dx /= aspect
		size = vby2 - vby
	}
	palette := iconvg.DefaultPalette
	pmap := make(map[color.RGBA]uint8)
	for _, p := range g.Paths {
		if p.Fill == "" {
			p.Fill = g.Fill
		}
		c, err := parseColor(p.Fill)
		if err != nil {
			return err
		}
		var ok bool
		if p.creg, ok = pmap[c]; !ok {
			if len(pmap) == 64 {
				panic("too many colors")
			}
			p.creg = uint8(len(pmap))
			palette[p.creg] = c
			pmap[c] = p.creg
		}
	}
	var enc iconvg.Encoder
	enc.Reset(iconvg.Metadata{
		ViewBox: iconvg.Rectangle{
			Min: f32.Vec2{-dx * .5, -dy * .5},
			Max: f32.Vec2{+dx * .5, +dy * .5},
		},
		Palette: palette,
	})

	offset := f32.Vec2{
		vbx * outSize / size,
		vby * outSize / size,
	}

	// adjs maps from opacity to a cReg adj value.
	adjs := map[float32]uint8{}

	for _, p := range g.Paths {
		if err := genPath(&enc, p, adjs, outSize, size, offset, g.Circles); err != nil {
			return err
		}
		g.Circles = nil
	}

	if len(g.Circles) != 0 {
		if err := genPath(&enc, &Path{}, adjs, outSize, size, offset, g.Circles); err != nil {
			return err
		}
		g.Circles = nil
	}

	ivgData, err := enc.Bytes()
	if err != nil {
		return fmt.Errorf("iconvg encoding failed: %v", err)
	}
	for i, x := range ivgData {
		if i&0x0f == 0x00 {
			out.WriteByte('\n')
		}
		fmt.Fprintf(out, "%#02x, ", x)
	}

	totalFiles++
	totalSVGBytes += len(svgData)
	totalIVGBytes += len(ivgData)
	return nil
}

func parseColor(col string) (color.RGBA, error) {
	if col == "none" {
		return color.RGBA{}, nil
	}
	if len(col) == 0 {
		return color.RGBA{A: 0xff}, nil
	}
	if len(col) == 0 || col[0] != '#' {
		return color.RGBA{}, fmt.Errorf("invalid color: %q", col)
	}
	col = col[1:]
	if len(col) != 6 {
		return color.RGBA{}, fmt.Errorf("invalid color length: %q", col)
	}
	elems := make([]byte, len(col)/2)
	for i := range elems {
		e, err := strconv.ParseUint(col[i*2:i*2+2], 16, 8)
		if err != nil {
			return color.RGBA{}, err
		}
		elems[i] = byte(e)
	}
	return color.RGBA{R: elems[0], G: elems[1], B: elems[2], A: 255}, nil
}

func genPath(enc *iconvg.Encoder, p *Path, adjs map[float32]uint8, outSize, size float32, offset f32.Vec2, circles []Circle) error {
	adj := uint8(0)
	opacity := float32(1)
	if p.Opacity != nil {
		opacity = *p.Opacity
	} else if p.FillOpacity != nil {
		opacity = *p.FillOpacity
	}
	if opacity != 1 {
		var ok bool
		if adj, ok = adjs[opacity]; !ok {
			adj = uint8(len(adjs) + 1)
			adjs[opacity] = adj
			// Set CREG[0-adj] to be a blend of transparent (0x7f) and the
			// first custom palette color (0x80).
			enc.SetCReg(adj, false, iconvg.BlendColor(uint8(opacity*0xff), 0x7f, 0x80+p.creg))
		}
	} else {
		enc.SetCReg(adj, false, iconvg.PaletteIndexColor(p.creg))
	}

	needStartPath := true
	if p.D != "" {
		needStartPath = false
		if err := genPathData(enc, adj, p.D, outSize, size, offset); err != nil {
			return err
		}
	}

	for _, c := range circles {
		// Normalize.
		cx := c.Cx * outSize / size
		cx -= outSize/2 + offset[0]
		cy := c.Cy * outSize / size
		cy -= outSize/2 + offset[1]
		r := c.R * outSize / size

		if needStartPath {
			needStartPath = false
			enc.StartPath(adj, cx-r, cy)
		} else {
			enc.ClosePathAbsMoveTo(cx-r, cy)
		}

		// Convert a circle to two relative arcTo ops, each of 180 degrees.
		// We can't use one 360 degree arcTo as the start and end point
		// would be coincident and the computation is degenerate.
		enc.RelArcTo(r, r, 0, false, true, +2*r, 0)
		enc.RelArcTo(r, r, 0, false, true, -2*r, 0)
	}

	enc.ClosePathEndPath()
	return nil
}

func genPathData(enc *iconvg.Encoder, adj uint8, pathData string, outSize, size float32, offset f32.Vec2) error {
	if strings.HasSuffix(pathData, "z") {
		pathData = pathData[:len(pathData)-1]
	}
	r := strings.NewReader(pathData)

	var args [7]float32
	op, relative, started := byte(0), false, false
	var count int
	for {
		b, err := r.ReadByte()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		count++

		switch {
		case b == ' ' || b == '\n' || b == '\t':
			continue
		case 'A' <= b && b <= 'Z':
			op, relative = b, false
		case 'a' <= b && b <= 'z':
			op, relative = b, true
		default:
			r.UnreadByte()
		}

		n := 0
		switch op {
		case 'A', 'a':
			n = 7
		case 'L', 'l', 'T', 't':
			n = 2
		case 'Q', 'q', 'S', 's':
			n = 4
		case 'C', 'c':
			n = 6
		case 'H', 'h', 'V', 'v':
			n = 1
		case 'M', 'm':
			n = 2
		case 'Z', 'z':
		default:
			return fmt.Errorf("unknown opcode %c\n", b)
		}

		scan(&args, r, n)
		normalize(&args, n, op, outSize, size, offset, relative)

		switch op {
		case 'A':
			enc.AbsArcTo(args[0], args[1], args[2], args[3] != 0, args[4] != 0, args[5], args[6])
		case 'a':
			enc.RelArcTo(args[0], args[1], args[2], args[3] != 0, args[4] != 0, args[5], args[6])
		case 'L':
			enc.AbsLineTo(args[0], args[1])
		case 'l':
			enc.RelLineTo(args[0], args[1])
		case 'T':
			enc.AbsSmoothQuadTo(args[0], args[1])
		case 't':
			enc.RelSmoothQuadTo(args[0], args[1])
		case 'Q':
			enc.AbsQuadTo(args[0], args[1], args[2], args[3])
		case 'q':
			enc.RelQuadTo(args[0], args[1], args[2], args[3])
		case 'S':
			enc.AbsSmoothCubeTo(args[0], args[1], args[2], args[3])
		case 's':
			enc.RelSmoothCubeTo(args[0], args[1], args[2], args[3])
		case 'C':
			enc.AbsCubeTo(args[0], args[1], args[2], args[3], args[4], args[5])
		case 'c':
			enc.RelCubeTo(args[0], args[1], args[2], args[3], args[4], args[5])
		case 'H':
			enc.AbsHLineTo(args[0])
		case 'h':
			enc.RelHLineTo(args[0])
		case 'V':
			enc.AbsVLineTo(args[0])
		case 'v':
			enc.RelVLineTo(args[0])
		case 'M':
			if !started {
				started = true
				enc.StartPath(adj, args[0], args[1])
			} else {
				enc.ClosePathAbsMoveTo(args[0], args[1])
			}
		case 'm':
			enc.ClosePathRelMoveTo(args[0], args[1])
		}
	}
	return nil
}

func scan(args *[7]float32, r *strings.Reader, n int) {
	for i := 0; i < n; i++ {
		for {
			if b, _ := r.ReadByte(); b != ' ' && b != ',' && b != '\n' && b != '\t' {
				r.UnreadByte()
				break
			}
		}
		fmt.Fscanf(r, "%f", &args[i])
	}
}

func normalize(args *[7]float32, n int, op byte, outSize, size float32, offset f32.Vec2, relative bool) {
	for i := 0; i < n; i++ {
		if (op == 'A' || op == 'a') && (i == 3 || i == 4) {
			continue
		}
		args[i] *= outSize / size
		if relative {
			continue
		}
		if (op == 'A' || op == 'a') && i < 5 {
			// For arcs, skip everything other than x, y.
			continue
		}
		args[i] -= outSize / 2
		switch {
		case op == 'A' && i == 5: // Arc x.
			args[i] -= offset[0]
		case op == 'A' && i == 6: // Arc y.
			args[i] -= offset[1]
		case n != 1:
			args[i] -= offset[i&0x01]
		case op == 'H':
			args[i] -= offset[0]
		case op == 'V':
			args[i] -= offset[1]
		}
	}
}

A  => go.mod +8 -0
@@ 1,8 @@
module eliasnaur.com/giologo

go 1.13

require (
	golang.org/x/exp v0.0.0-20190927203820-447a159532ef // indirect
	golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a // indirect
)

A  => go.sum +23 -0
@@ 1,23 @@
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190927203820-447a159532ef h1:0MEfU0Kh8iitbYr+L8WhnyAxLCVa5p0hV8tnPmdGDp0=
golang.org/x/exp v0.0.0-20190927203820-447a159532ef/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a h1:gHevYm0pO4QUbwy8Dmdr01R5r1BuKtfYqRqF0h/Cbh0=
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

A  => ic_gio_48px.svg +1 -0
@@ 1,1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><path id="path2" d="M171.134,10.115c0.706,1.277 1.031,3.018 0.85,4.053c-0.668,3.8 -3.02,5.433 -6.177,6.456c-17.173,5.565 -33.724,12.96 -49.238,22.051c-7.574,4.439 -14.895,9.297 -21.866,14.647c-17.701,13.585 -33.109,30.048 -42.288,50.274c-3.926,8.651 -6.584,17.9 -7.716,27.3c-0.84,6.98 -1.032,14.097 -0.165,21.183c0.922,7.526 3.168,14.914 6.982,21.473c2.919,5.02 6.541,9.614 10.953,13.397c16.009,13.726 38.348,20.202 57.903,22.535c15.197,1.813 29.539,1.052 43.283,-1.646c16.656,-3.271 35.205,-6.303 46.134,-19.15c3.637,-4.275 5.862,-9.958 5.366,-15.091c-0.832,-8.611 -6.318,-15.363 -12.911,-20.092c-2.547,-1.826 -5.3,-3.447 -8.319,-4.329c-0.596,-0.174 -1.199,-0.342 -1.759,-0.591c-2.067,-0.92 -3.61,-2.965 -3.963,-5.198c-0.309,-1.961 0.112,-3.916 1.237,-5.259c2.691,-3.211 6.011,-3.203 9.424,-2.112c3.849,1.23 7.512,3.078 10.996,5.136c5.441,3.214 10.553,7.091 14.52,11.986c4.536,5.6 8.044,11.92 9.72,17.837c2.628,9.28 1.529,19.312 -2.703,27.966c-4.406,9.012 -12.317,17.43 -19.292,22.706c-8.415,6.364 -18.268,11.999 -26.835,15.081c-16.145,5.809 -33.096,8.98 -47.79,8.718c-23.131,-0.413 -49.514,-8.324 -68.328,-18.39c-12.484,-6.679 -23.508,-15.565 -31.952,-26.468c-11.709,-15.117 -17.065,-36.463 -16.605,-53.416c0.646,-23.808 11.029,-49.666 23.321,-66.802c10.312,-14.375 23.526,-26.693 37.774,-37.166c17.675,-12.991 37.297,-23.386 57.737,-31.816c7.706,-3.178 15.527,-6.035 23.475,-8.547c3.247,-1.027 6.806,1.041 8.232,3.274Z"/><path id="path6" d="M102.759,140.491c2.404,0.748 3.816,3.067 4.414,5.416c1.88,7.39 4.876,14.435 7.161,21.7c0.566,1.799 1.157,3.648 1.228,5.543c-0.527,3.023 -2.755,5.323 -4.274,7.885c-1.198,2.019 -2.354,4.236 -4.251,5.633c-0.635,0.468 -1.429,-0.517 -2.002,-0.931c-2.904,-2.098 -5.352,-4.752 -8.033,-7.122c-1.298,-1.147 -2.392,-2.723 -2.273,-4.527c-0.252,-4.991 0.489,-9.962 0.814,-14.935c0.247,-3.761 0.564,-7.517 0.593,-11.286c0.017,-2.168 0.294,-4.463 1.912,-6.09c1.233,-1.241 3.045,-1.804 4.711,-1.286Z"/><path id="path10" d="M151.528,151.13c-4.499,2.422 -6.52,8.181 -5.586,13.021c0.502,2.602 3.583,2.978 5.813,3.046c1.941,0.058 4.078,-0.557 4.559,-2.689c1.254,-5.56 -0.872,-10.645 -4.786,-13.378Zm-0.135,-10.877c5.967,-0.866 12.309,0.095 17.472,3.322c4.482,2.802 7.871,7.466 8.557,12.786c1.036,8.038 -1.784,16.585 -7.578,22.26c-4.904,4.803 -12.117,6.715 -18.883,5.665c-6.77,-1.051 -13.52,-4.483 -16.857,-10.666c-3.479,-6.445 -2.568,-14.395 0.515,-20.779c2.878,-5.958 8.212,-11.169 14.881,-12.447c0.626,-0.098 1.26,-0.145 1.893,-0.141Z"/></svg>
\ No newline at end of file