173 lines
4.2 KiB
Go
173 lines
4.2 KiB
Go
// Copyright 2017 The oksvg Authors. All rights reserved.
|
|
// created: 2/12/2017 by S.R.Wiley
|
|
//
|
|
// utils.go implements translation of an SVG2.0 path into a rasterx Path.
|
|
|
|
package oksvg
|
|
|
|
import (
|
|
"errors"
|
|
"image/color"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/srwiley/rasterx"
|
|
"golang.org/x/image/colornames"
|
|
)
|
|
|
|
// unitSuffixes are suffixes sometimes applied to the width and height attributes
|
|
// of the svg element.
|
|
var unitSuffixes = []string{"cm", "mm", "px", "pt"}
|
|
|
|
func parseColorValue(v string) (uint8, error) {
|
|
if v[len(v)-1] == '%' {
|
|
n, err := strconv.Atoi(strings.TrimSpace(v[:len(v)-1]))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return uint8(n * 0xFF / 100), nil
|
|
}
|
|
n, err := strconv.Atoi(strings.TrimSpace(v))
|
|
if n > 255 {
|
|
n = 255
|
|
}
|
|
return uint8(n), err
|
|
}
|
|
|
|
// trimSuffixes removes unitSuffixes from any number that is not just numeric
|
|
func trimSuffixes(a string) (b string) {
|
|
if a == "" || (a[len(a)-1] >= '0' && a[len(a)-1] <= '9') {
|
|
return a
|
|
}
|
|
b = a
|
|
for _, v := range unitSuffixes {
|
|
b = strings.TrimSuffix(b, v)
|
|
}
|
|
return
|
|
}
|
|
|
|
// parseFloat is a helper function that strips suffixes before passing to strconv.ParseFloat
|
|
func parseFloat(s string, bitSize int) (float64, error) {
|
|
val := trimSuffixes(s)
|
|
return strconv.ParseFloat(val, bitSize)
|
|
}
|
|
|
|
// splitOnCommaOrSpace returns a list of strings after splitting the input on comma and space delimiters
|
|
func splitOnCommaOrSpace(s string) []string {
|
|
return strings.FieldsFunc(s,
|
|
func(r rune) bool {
|
|
return r == ',' || r == ' '
|
|
})
|
|
}
|
|
|
|
func parseClasses(data string) (map[string]styleAttribute, error) {
|
|
res := map[string]styleAttribute{}
|
|
arr := strings.Split(data, "}")
|
|
for _, v := range arr {
|
|
v = strings.TrimSpace(v)
|
|
if v == "" {
|
|
continue
|
|
}
|
|
valueIndex := strings.Index(v, "{")
|
|
if valueIndex == -1 || valueIndex == len(v)-1 {
|
|
return res, errors.New(v + "}: invalid map format in class definitions")
|
|
}
|
|
classesStr := v[:valueIndex]
|
|
attrStr := v[valueIndex+1:]
|
|
attrMap, err := parseAttrs(attrStr)
|
|
if err != nil {
|
|
return res, err
|
|
}
|
|
classes := strings.Split(classesStr, ",")
|
|
for _, class := range classes {
|
|
class = strings.TrimSpace(class)
|
|
if len(class) > 0 && class[0] == '.' {
|
|
class = class[1:]
|
|
}
|
|
for attrKey, attrVal := range attrMap {
|
|
if res[class] == nil {
|
|
res[class] = make(styleAttribute, len(attrMap))
|
|
}
|
|
res[class][attrKey] = attrVal
|
|
}
|
|
}
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func parseAttrs(attrStr string) (styleAttribute, error) {
|
|
arr := strings.Split(attrStr, ";")
|
|
res := make(styleAttribute, len(arr))
|
|
for _, kv := range arr {
|
|
kv = strings.TrimSpace(kv)
|
|
if kv == "" {
|
|
continue
|
|
}
|
|
tmp := strings.SplitN(kv, ":", 2)
|
|
if len(tmp) != 2 {
|
|
return res, errors.New(kv + ": invalid attribute format")
|
|
}
|
|
k := strings.TrimSpace(tmp[0])
|
|
v := strings.TrimSpace(tmp[1])
|
|
res[k] = v
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func readFraction(v string) (f float64, err error) {
|
|
v = strings.TrimSpace(v)
|
|
d := 1.0
|
|
if strings.HasSuffix(v, "%") {
|
|
d = 100
|
|
v = strings.TrimSuffix(v, "%")
|
|
}
|
|
f, err = parseFloat(v, 64)
|
|
f /= d
|
|
// Is this is an unnecessary restriction? For now fractions can be all values not just in the range [0,1]
|
|
// if f > 1 {
|
|
// f = 1
|
|
// } else if f < 0 {
|
|
// f = 0
|
|
// }
|
|
return
|
|
}
|
|
|
|
// getColor is a helper function to get the background color
|
|
// if ReadGradUrl needs it.
|
|
func getColor(clr interface{}) color.Color {
|
|
switch c := clr.(type) {
|
|
case rasterx.Gradient: // This is a bit lazy but oh well
|
|
for _, s := range c.Stops {
|
|
if s.StopColor != nil {
|
|
return s.StopColor
|
|
}
|
|
}
|
|
case color.NRGBA:
|
|
return c
|
|
}
|
|
return colornames.Black
|
|
}
|
|
|
|
func localizeGradIfStopClrNil(g *rasterx.Gradient, defaultColor interface{}) (grad rasterx.Gradient) {
|
|
grad = *g
|
|
for _, s := range grad.Stops {
|
|
if s.StopColor == nil { // This means we need copy the gradient's Stop slice
|
|
// and fill in the default color
|
|
|
|
// Copy the stops
|
|
stops := make([]rasterx.GradStop, len(grad.Stops))
|
|
copy(stops, grad.Stops)
|
|
grad.Stops = stops
|
|
// Use the background color when a stop color is nil
|
|
clr := getColor(defaultColor)
|
|
for i, s := range stops {
|
|
if s.StopColor == nil {
|
|
grad.Stops[i].StopColor = clr
|
|
}
|
|
}
|
|
break // Only need to do this once
|
|
}
|
|
}
|
|
return
|
|
}
|