utils.go

  1// Copyright 2017 The oksvg Authors. All rights reserved.
  2// created: 2/12/2017 by S.R.Wiley
  3//
  4// utils.go implements translation of an SVG2.0 path into a rasterx Path.
  5
  6package oksvg
  7
  8import (
  9	"errors"
 10	"image/color"
 11	"strconv"
 12	"strings"
 13
 14	"github.com/srwiley/rasterx"
 15	"golang.org/x/image/colornames"
 16)
 17
 18// unitSuffixes are suffixes sometimes applied to the width and height attributes
 19// of the svg element.
 20var unitSuffixes = []string{"cm", "mm", "px", "pt"}
 21
 22func parseColorValue(v string) (uint8, error) {
 23	if v[len(v)-1] == '%' {
 24		n, err := strconv.Atoi(strings.TrimSpace(v[:len(v)-1]))
 25		if err != nil {
 26			return 0, err
 27		}
 28		return uint8(n * 0xFF / 100), nil
 29	}
 30	n, err := strconv.Atoi(strings.TrimSpace(v))
 31	if n > 255 {
 32		n = 255
 33	}
 34	return uint8(n), err
 35}
 36
 37// trimSuffixes removes unitSuffixes from any number that is not just numeric
 38func trimSuffixes(a string) (b string) {
 39	if a == "" || (a[len(a)-1] >= '0' && a[len(a)-1] <= '9') {
 40		return a
 41	}
 42	b = a
 43	for _, v := range unitSuffixes {
 44		b = strings.TrimSuffix(b, v)
 45	}
 46	return
 47}
 48
 49// parseFloat is a helper function that strips suffixes before passing to strconv.ParseFloat
 50func parseFloat(s string, bitSize int) (float64, error) {
 51	val := trimSuffixes(s)
 52	return strconv.ParseFloat(val, bitSize)
 53}
 54
 55// splitOnCommaOrSpace returns a list of strings after splitting the input on comma and space delimiters
 56func splitOnCommaOrSpace(s string) []string {
 57	return strings.FieldsFunc(s,
 58		func(r rune) bool {
 59			return r == ',' || r == ' '
 60		})
 61}
 62
 63func parseClasses(data string) (map[string]styleAttribute, error) {
 64	res := map[string]styleAttribute{}
 65	arr := strings.Split(data, "}")
 66	for _, v := range arr {
 67		v = strings.TrimSpace(v)
 68		if v == "" {
 69			continue
 70		}
 71		valueIndex := strings.Index(v, "{")
 72		if valueIndex == -1 || valueIndex == len(v)-1 {
 73			return res, errors.New(v + "}: invalid map format in class definitions")
 74		}
 75		classesStr := v[:valueIndex]
 76		attrStr := v[valueIndex+1:]
 77		attrMap, err := parseAttrs(attrStr)
 78		if err != nil {
 79			return res, err
 80		}
 81		classes := strings.Split(classesStr, ",")
 82		for _, class := range classes {
 83			class = strings.TrimSpace(class)
 84			if len(class) > 0 && class[0] == '.' {
 85				class = class[1:]
 86			}
 87			for attrKey, attrVal := range attrMap {
 88				if res[class] == nil {
 89					res[class] = make(styleAttribute, len(attrMap))
 90				}
 91				res[class][attrKey] = attrVal
 92			}
 93		}
 94	}
 95	return res, nil
 96}
 97
 98func parseAttrs(attrStr string) (styleAttribute, error) {
 99	arr := strings.Split(attrStr, ";")
100	res := make(styleAttribute, len(arr))
101	for _, kv := range arr {
102		kv = strings.TrimSpace(kv)
103		if kv == "" {
104			continue
105		}
106		tmp := strings.SplitN(kv, ":", 2)
107		if len(tmp) != 2 {
108			return res, errors.New(kv + ": invalid attribute format")
109		}
110		k := strings.TrimSpace(tmp[0])
111		v := strings.TrimSpace(tmp[1])
112		res[k] = v
113	}
114	return res, nil
115}
116
117func readFraction(v string) (f float64, err error) {
118	v = strings.TrimSpace(v)
119	d := 1.0
120	if strings.HasSuffix(v, "%") {
121		d = 100
122		v = strings.TrimSuffix(v, "%")
123	}
124	f, err = parseFloat(v, 64)
125	f /= d
126	// Is this is an unnecessary restriction? For now fractions can be all values not just in the range [0,1]
127	// if f > 1 {
128	// 	f = 1
129	// } else if f < 0 {
130	// 	f = 0
131	// }
132	return
133}
134
135// getColor is a helper function to get the background color
136// if ReadGradUrl needs it.
137func getColor(clr interface{}) color.Color {
138	switch c := clr.(type) {
139	case rasterx.Gradient: // This is a bit lazy but oh well
140		for _, s := range c.Stops {
141			if s.StopColor != nil {
142				return s.StopColor
143			}
144		}
145	case color.NRGBA:
146		return c
147	}
148	return colornames.Black
149}
150
151func localizeGradIfStopClrNil(g *rasterx.Gradient, defaultColor interface{}) (grad rasterx.Gradient) {
152	grad = *g
153	for _, s := range grad.Stops {
154		if s.StopColor == nil { // This means we need copy the gradient's Stop slice
155			// and fill in the default color
156
157			// Copy the stops
158			stops := make([]rasterx.GradStop, len(grad.Stops))
159			copy(stops, grad.Stops)
160			grad.Stops = stops
161			// Use the background color when a stop color is nil
162			clr := getColor(defaultColor)
163			for i, s := range stops {
164				if s.StopColor == nil {
165					grad.Stops[i].StopColor = clr
166				}
167			}
168			break // Only need to do this once
169		}
170	}
171	return
172}