adam-gui/vendor/github.com/srwiley/oksvg/draw.go
2024-04-29 19:13:50 +02:00

393 lines
9.7 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 (
"encoding/xml"
"errors"
"log"
"strings"
"github.com/srwiley/rasterx"
"golang.org/x/image/math/fixed"
)
// svgFunc defines function interface to use as drawing implementation.
type svgFunc func(c *IconCursor, attrs []xml.Attr) error
var (
drawFuncs = map[string]svgFunc{
"svg": svgF,
"g": gF,
"line": lineF,
"stop": stopF,
"rect": rectF,
"circle": circleF,
"ellipse": circleF, //circleF handles ellipse also
"polyline": polylineF,
"polygon": polygonF,
"path": pathF,
"desc": descF,
"defs": defsF,
"style": styleF,
"title": titleF,
"linearGradient": linearGradientF,
"radialGradient": radialGradientF,
}
svgF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
c.icon.ViewBox.X = 0
c.icon.ViewBox.Y = 0
c.icon.ViewBox.W = 0
c.icon.ViewBox.H = 0
var width, height float64
var err error
for _, attr := range attrs {
switch attr.Name.Local {
case "viewBox":
err = c.GetPoints(attr.Value)
if len(c.points) != 4 {
return errParamMismatch
}
c.icon.ViewBox.X = c.points[0]
c.icon.ViewBox.Y = c.points[1]
c.icon.ViewBox.W = c.points[2]
c.icon.ViewBox.H = c.points[3]
case "width":
width, err = parseFloat(attr.Value, 64)
case "height":
height, err = parseFloat(attr.Value, 64)
}
if err != nil {
return err
}
}
if c.icon.ViewBox.W == 0 {
c.icon.ViewBox.W = width
}
if c.icon.ViewBox.H == 0 {
c.icon.ViewBox.H = height
}
return nil
}
gF svgFunc = func(*IconCursor, []xml.Attr) error { return nil } // g does nothing but push the style
rectF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
var x, y, w, h, rx, ry float64
var err error
for _, attr := range attrs {
switch attr.Name.Local {
case "x":
x, err = parseFloat(attr.Value, 64)
case "y":
y, err = parseFloat(attr.Value, 64)
case "width":
w, err = parseFloat(attr.Value, 64)
case "height":
h, err = parseFloat(attr.Value, 64)
case "rx":
rx, err = parseFloat(attr.Value, 64)
case "ry":
ry, err = parseFloat(attr.Value, 64)
}
if err != nil {
return err
}
}
if w == 0 || h == 0 {
return nil
}
rasterx.AddRoundRect(x, y, w+x, h+y, rx, ry, 0, rasterx.RoundGap, &c.Path)
return nil
}
circleF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
var cx, cy, rx, ry float64
var err error
for _, attr := range attrs {
switch attr.Name.Local {
case "cx":
cx, err = parseFloat(attr.Value, 64)
case "cy":
cy, err = parseFloat(attr.Value, 64)
case "r":
rx, err = parseFloat(attr.Value, 64)
ry = rx
case "rx":
rx, err = parseFloat(attr.Value, 64)
case "ry":
ry, err = parseFloat(attr.Value, 64)
}
if err != nil {
return err
}
}
if rx == 0 || ry == 0 { // not drawn, but not an error
return nil
}
c.EllipseAt(cx, cy, rx, ry)
return nil
}
lineF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
var x1, x2, y1, y2 float64
var err error
for _, attr := range attrs {
switch attr.Name.Local {
case "x1":
x1, err = parseFloat(attr.Value, 64)
case "x2":
x2, err = parseFloat(attr.Value, 64)
case "y1":
y1, err = parseFloat(attr.Value, 64)
case "y2":
y2, err = parseFloat(attr.Value, 64)
}
if err != nil {
return err
}
}
c.Path.Start(fixed.Point26_6{
X: fixed.Int26_6((x1) * 64),
Y: fixed.Int26_6((y1) * 64)})
c.Path.Line(fixed.Point26_6{
X: fixed.Int26_6((x2) * 64),
Y: fixed.Int26_6((y2) * 64)})
return nil
}
polylineF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
var err error
for _, attr := range attrs {
switch attr.Name.Local {
case "points":
err = c.GetPoints(attr.Value)
if len(c.points)%2 != 0 {
return errors.New("polygon has odd number of points")
}
}
if err != nil {
return err
}
}
if len(c.points) > 4 {
c.Path.Start(fixed.Point26_6{
X: fixed.Int26_6((c.points[0]) * 64),
Y: fixed.Int26_6((c.points[1]) * 64)})
for i := 2; i < len(c.points)-1; i += 2 {
c.Path.Line(fixed.Point26_6{
X: fixed.Int26_6((c.points[i]) * 64),
Y: fixed.Int26_6((c.points[i+1]) * 64)})
}
}
return nil
}
polygonF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
err := polylineF(c, attrs)
if len(c.points) > 4 {
c.Path.Stop(true)
}
return err
}
pathF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
var err error
for _, attr := range attrs {
switch attr.Name.Local {
case "d":
err = c.CompilePath(attr.Value)
}
if err != nil {
return err
}
}
return nil
}
descF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
c.inDescText = true
c.icon.Descriptions = append(c.icon.Descriptions, "")
return nil
}
titleF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
c.inTitleText = true
c.icon.Titles = append(c.icon.Titles, "")
return nil
}
defsF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
c.inDefs = true
return nil
}
styleF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
c.inDefsStyle = true
return nil
}
linearGradientF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
var err error
c.inGrad = true
c.grad = &rasterx.Gradient{Points: [5]float64{0, 0, 1, 0, 0},
IsRadial: false, Bounds: c.icon.ViewBox, Matrix: rasterx.Identity}
for _, attr := range attrs {
switch attr.Name.Local {
case "id":
id := attr.Value
if len(id) >= 0 {
c.icon.Grads[id] = c.grad
} else {
return errZeroLengthID
}
case "x1":
c.grad.Points[0], err = readFraction(attr.Value)
case "y1":
c.grad.Points[1], err = readFraction(attr.Value)
case "x2":
c.grad.Points[2], err = readFraction(attr.Value)
case "y2":
c.grad.Points[3], err = readFraction(attr.Value)
default:
err = c.ReadGradAttr(attr)
}
if err != nil {
return err
}
}
return nil
}
radialGradientF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
c.inGrad = true
c.grad = &rasterx.Gradient{Points: [5]float64{0.5, 0.5, 0.5, 0.5, 0.5},
IsRadial: true, Bounds: c.icon.ViewBox, Matrix: rasterx.Identity}
var setFx, setFy bool
var err error
for _, attr := range attrs {
switch attr.Name.Local {
case "id":
id := attr.Value
if len(id) >= 0 {
c.icon.Grads[id] = c.grad
} else {
return errZeroLengthID
}
case "r":
c.grad.Points[4], err = readFraction(attr.Value)
case "cx":
c.grad.Points[0], err = readFraction(attr.Value)
case "cy":
c.grad.Points[1], err = readFraction(attr.Value)
case "fx":
setFx = true
c.grad.Points[2], err = readFraction(attr.Value)
case "fy":
setFy = true
c.grad.Points[3], err = readFraction(attr.Value)
default:
err = c.ReadGradAttr(attr)
}
if err != nil {
return err
}
}
if !setFx { // set fx to cx by default
c.grad.Points[2] = c.grad.Points[0]
}
if !setFy { // set fy to cy by default
c.grad.Points[3] = c.grad.Points[1]
}
return nil
}
stopF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
var err error
if c.inGrad {
stop := rasterx.GradStop{Opacity: 1.0}
for _, attr := range attrs {
switch attr.Name.Local {
case "offset":
stop.Offset, err = readFraction(attr.Value)
case "stop-color":
//todo: add current color inherit
stop.StopColor, err = ParseSVGColor(attr.Value)
case "stop-opacity":
stop.Opacity, err = parseFloat(attr.Value, 64)
}
if err != nil {
return err
}
}
c.grad.Stops = append(c.grad.Stops, stop)
}
return nil
}
useF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
var (
href string
x, y float64
err error
)
for _, attr := range attrs {
switch attr.Name.Local {
case "href":
href = attr.Value
case "x":
x, err = parseFloat(attr.Value, 64)
case "y":
y, err = parseFloat(attr.Value, 64)
}
if err != nil {
return err
}
}
// Translate the Style adder matrix by use's x and y
c.StyleStack[len(c.StyleStack)-1].mAdder.M =
c.StyleStack[len(c.StyleStack)-1].mAdder.M.Translate(x, y)
if href == "" {
return errors.New("only use tags with href is supported")
}
if !strings.HasPrefix(href, "#") {
return errors.New("only the ID CSS selector is supported")
}
defs, ok := c.icon.Defs[href[1:]]
if !ok {
return errors.New("href ID in use statement was not found in saved defs")
}
for _, def := range defs {
if def.Tag == "endg" {
// pop style
c.StyleStack = c.StyleStack[:len(c.StyleStack)-1]
continue
}
if err = c.PushStyle(def.Attrs); err != nil {
return err
}
df, ok := drawFuncs[def.Tag]
if !ok {
errStr := "Cannot process svg element " + def.Tag
if c.ErrorMode == StrictErrorMode {
return errors.New(errStr)
} else if c.ErrorMode == WarnErrorMode {
log.Println(errStr)
}
return nil
}
if err := df(c, def.Attrs); err != nil {
return err
}
//Did c.Path get added to during the drawFunction call iteration?
if len(c.Path) > 0 {
//The cursor parsed a path from the xml element
pathCopy := make(rasterx.Path, len(c.Path))
copy(pathCopy, c.Path)
c.icon.SVGPaths = append(c.icon.SVGPaths, SvgPath{c.StyleStack[len(c.StyleStack)-1], pathCopy})
c.Path = c.Path[:0]
}
if def.Tag != "g" {
// pop style
c.StyleStack = c.StyleStack[:len(c.StyleStack)-1]
}
}
return nil
}
)
func init() {
// avoids cyclical static declaration
// called on package initialization
drawFuncs["use"] = useF
}