-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathpatterns.go
More file actions
296 lines (264 loc) · 7.08 KB
/
Copy pathpatterns.go
File metadata and controls
296 lines (264 loc) · 7.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
package svg
import (
"encoding/xml"
"image/color"
"strconv"
"strings"
"golang.org/x/image/colornames"
"golang.org/x/image/math/fixed"
)
// This file defines colors and gradients used in SVG
// Pattern groups a basic color and a gradient pattern
// A nil value may by used to indicated that the function (fill or stroke) is off
type Pattern interface {
isPattern()
}
type PlainColor struct {
color.NRGBA
}
func NewPlainColor(r, g, b, a uint8) PlainColor {
return PlainColor{NRGBA: color.NRGBA{r, g, b, a}}
}
func (PlainColor) isPattern() {}
func (Gradient) isPattern() {}
// enables to differentiate between black and nil color
type optionnalColor struct {
valid bool
color PlainColor
}
func toOptColor(p PlainColor) optionnalColor {
return optionnalColor{valid: true, color: p}
}
func (o optionnalColor) asColor() color.Color {
if o.valid {
return o.color
}
return nil
}
func (o optionnalColor) asPattern() Pattern {
if o.valid {
return o.color
}
return nil
}
// parseSVGColor parses an SVG color string in all forms
// including all SVG1.1 names, obtained from the colornames package
func parseSVGColor(colorStr string) (optionnalColor, error) {
v := strings.ToLower(colorStr)
if strings.HasPrefix(v, "url") { // We are not handling urls
// and gradients and stuff at this point
return toOptColor(NewPlainColor(0, 0, 0, 255)), nil
}
switch v {
case "none":
// nil signals that the function (fill or stroke) is off;
// not the same as black
return optionnalColor{}, nil
default:
cn, ok := colornames.Map[v]
if ok {
r, g, b, a := cn.RGBA()
return toOptColor(NewPlainColor(uint8(r), uint8(g), uint8(b), uint8(a))), nil
}
}
cStr := strings.TrimPrefix(colorStr, "rgb(")
if cStr != colorStr {
cStr := strings.TrimSuffix(cStr, ")")
vals := strings.Split(cStr, ",")
if len(vals) != 3 {
return toOptColor(PlainColor{}), errParamMismatch
}
var cvals [3]uint8
var err error
for i := range cvals {
cvals[i], err = parseColorValue(vals[i])
if err != nil {
return optionnalColor{}, err
}
}
return toOptColor(NewPlainColor(cvals[0], cvals[1], cvals[2], 0xFF)), nil
}
if colorStr[0] == '#' {
r, g, b, err := parseSVGColorNum(colorStr)
if err != nil {
return optionnalColor{}, err
}
return toOptColor(NewPlainColor(r, g, b, 0xFF)), nil
}
return optionnalColor{}, errParamMismatch
}
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
}
// parseSVGColorNum reads the SFG color string e.g. #FBD9BD
func parseSVGColorNum(colorStr string) (r, g, b uint8, err error) {
colorStr = strings.TrimPrefix(colorStr, "#")
var t uint64
if len(colorStr) != 6 {
// SVG specs say duplicate characters in case of 3 digit hex number
colorStr = string([]byte{
colorStr[0], colorStr[0],
colorStr[1], colorStr[1], colorStr[2], colorStr[2],
})
}
for _, v := range []struct {
c *uint8
s string
}{
{&r, colorStr[0:2]},
{&g, colorStr[2:4]},
{&b, colorStr[4:6]},
} {
t, err = strconv.ParseUint(v.s, 16, 8)
if err != nil {
return
}
*v.c = uint8(t)
}
return
}
// GradientUnits is the type for gradient units
type GradientUnits byte
// SVG bounds paremater constants
const (
ObjectBoundingBox GradientUnits = iota
UserSpaceOnUse
)
// SpreadMethod is the type for spread parameters
type SpreadMethod byte
// SVG spread parameter constants
const (
PadSpread SpreadMethod = iota
ReflectSpread
RepeatSpread
)
// GradStop represents a stop in the SVG 2.0 gradient specification
type GradStop struct {
StopColor color.Color
Offset float64
Opacity float64
}
// Gradient holds a description of an SVG 2.0 gradient
type Gradient struct {
Direction gradientDirecter
Stops []GradStop
Bounds Bounds
Matrix Matrix2D
Spread SpreadMethod
Units GradientUnits
}
// ApplyPathExtent use the given path extent to adjust the bounding box,
// if required by `Units`.
// The `Direction` field is not modified, but a matrix accounting for both the bouding box and
// the gradient matrix is returned
func (g *Gradient) ApplyPathExtent(extent fixed.Rectangle26_6) Matrix2D {
if g.Units == ObjectBoundingBox {
mnx, mny := float64(extent.Min.X)/64, float64(extent.Min.Y)/64
mxx, mxy := float64(extent.Max.X)/64, float64(extent.Max.Y)/64
g.Bounds.X, g.Bounds.Y = mnx, mny
g.Bounds.W, g.Bounds.H = mxx-mnx, mxy-mny
// units in Direction are fraction, so
// we apply bounds
return Identity.Scale(g.Bounds.W, g.Bounds.H).Mult(g.Matrix)
}
// units in Direction are already scaled to the view box
// just return the gradient matrix
return g.Matrix
}
// radial or linear
type gradientDirecter interface {
isRadial() bool
}
// x1, y1, x2, y2
type Linear [4]float64
func (Linear) isRadial() bool { return false }
// cx, cy, fx, fy, r, fr
type Radial [6]float64
func (Radial) isRadial() bool { return true }
// GetColor is a helper function to get the background color
// if ReadGradUrl needs it.
func GetColor(clr Pattern) color.Color {
switch c := clr.(type) {
case Gradient: // This is a bit lazy but oh well
for _, s := range c.Stops {
if s.StopColor != nil {
return s.StopColor
}
}
case PlainColor:
return c
}
return colornames.Black
}
func localizeGradIfStopClrNil(g *Gradient, defaultColor Pattern) 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 := append([]GradStop{}, 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 grad
}
// readGradURL reads an SVG format gradient url
// Since the context of the gradient can affect the colors
// the current fill or line color is passed in and used in
// the case of a nil stopClor value
func (c *svgCursor) readGradURL(v string, defaultColor Pattern) (grad Gradient, ok bool) {
if strings.HasPrefix(v, "url(") && strings.HasSuffix(v, ")") {
urlStr := strings.TrimSpace(v[4 : len(v)-1])
if strings.HasPrefix(urlStr, "#") {
var g *Gradient
g, ok = c.svg.grads[urlStr[1:]]
if ok {
grad = localizeGradIfStopClrNil(g, defaultColor)
}
}
}
return
}
// readGradAttr reads an SVG gradient attribute
func (c *svgCursor) readGradAttr(attr xml.Attr) (err error) {
switch attr.Name.Local {
case "gradientTransform":
c.grad.Matrix, err = c.parseTransform(attr.Value)
case "gradientUnits":
switch strings.TrimSpace(attr.Value) {
case "userSpaceOnUse":
c.grad.Units = UserSpaceOnUse
case "objectBoundingBox":
c.grad.Units = ObjectBoundingBox
}
case "spreadMethod":
switch strings.TrimSpace(attr.Value) {
case "pad":
c.grad.Spread = PadSpread
case "reflect":
c.grad.Spread = ReflectSpread
case "repeat":
c.grad.Spread = RepeatSpread
}
}
return
}