-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgradient.go
346 lines (317 loc) · 9.34 KB
/
gradient.go
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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
// Copyright (c) 2018, The Goki Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package svg
import (
"log"
"strings"
"goki.dev/colors/gradient"
"goki.dev/ki/v2"
"goki.dev/mat32/v2"
)
/////////////////////////////////////////////////////////////////////////////
// Gradient
// Gradient is used for holding a specified color gradient.
// The name is the id for lookup in url
type Gradient struct {
NodeBase
// the color gradient
Grad gradient.Gradient
// name of another gradient to get stops from
StopsName string
}
func (gr *Gradient) CopyFieldsFrom(frm any) {
fr := frm.(*Gradient)
gr.NodeBase.CopyFieldsFrom(&fr.NodeBase)
gradient.CopyFrom(gr.Grad, fr.Grad)
gr.StopsName = fr.StopsName
}
// GradientTypeName returns the SVG-style type name of gradient: linearGradient or radialGradient
func (gr *Gradient) GradientTypeName() string {
if _, ok := gr.Grad.(*gradient.Radial); ok {
return "radialGradient"
}
return "linearGradient"
}
//////////////////////////////////////////////////////////////////////////////
// SVG gradient management
// GradientByName returns the gradient of given name, stored on SVG node
func (sv *SVG) GradientByName(n Node, grnm string) *Gradient {
gri := sv.NodeFindURL(n, grnm)
if gri == nil {
return nil
}
gr, ok := gri.(*Gradient)
if !ok {
log.Printf("SVG Found element named: %v but isn't a Gradient type, instead is: %T", grnm, gri)
return nil
}
return gr
}
// GradientApplyTransform applies the given transform to any gradients for this node,
// that are using specific coordinates (not bounding box which is automatic)
func (g *NodeBase) GradientApplyTransform(sv *SVG, xf mat32.Mat2) {
gi := g.This().(Node)
gnm := NodePropURL(gi, "fill")
if gnm != "" {
gr := sv.GradientByName(gi, gnm)
if gr != nil {
gr.Grad.AsBase().Transform.SetMul(xf)
}
}
gnm = NodePropURL(gi, "stroke")
if gnm != "" {
gr := sv.GradientByName(gi, gnm)
if gr != nil {
gr.Grad.AsBase().Transform.SetMul(xf)
}
}
}
// GradientApplyTransformPt applies the given transform with ctr point
// to any gradients for this node, that are using specific coordinates
// (not bounding box which is automatic)
func (g *NodeBase) GradientApplyTransformPt(sv *SVG, xf mat32.Mat2, pt mat32.Vec2) {
gi := g.This().(Node)
gnm := NodePropURL(gi, "fill")
if gnm != "" {
gr := sv.GradientByName(gi, gnm)
if gr != nil {
gr.Grad.AsBase().Transform.SetMulCtr(xf, pt)
}
}
gnm = NodePropURL(gi, "stroke")
if gnm != "" {
gr := sv.GradientByName(gi, gnm)
if gr != nil {
gr.Grad.AsBase().Transform.SetMulCtr(xf, pt)
}
}
}
// GradientWritePoints writes the gradient points to
// a slice of floating point numbers, appending to end of slice.
func GradientWritePts(gr gradient.Gradient, dat *[]float32) {
// TODO: do we want this, and is this the right way to structure it?
if gr == nil {
return
}
gb := gr.AsBase()
*dat = append(*dat, gb.Transform.XX)
*dat = append(*dat, gb.Transform.YX)
*dat = append(*dat, gb.Transform.XY)
*dat = append(*dat, gb.Transform.YY)
*dat = append(*dat, gb.Transform.X0)
*dat = append(*dat, gb.Transform.Y0)
*dat = append(*dat, gb.Box.Min.X)
*dat = append(*dat, gb.Box.Min.Y)
*dat = append(*dat, gb.Box.Max.X)
*dat = append(*dat, gb.Box.Max.Y)
}
// GradientWritePts writes the geometry of the gradients for this node
// to a slice of floating point numbers, appending to end of slice.
func (g *NodeBase) GradientWritePts(sv *SVG, dat *[]float32) {
gnm := NodePropURL(g, "fill")
if gnm != "" {
gr := sv.GradientByName(g, gnm)
if gr != nil {
GradientWritePts(gr.Grad, dat)
}
}
gnm = NodePropURL(g, "stroke")
if gnm != "" {
gr := sv.GradientByName(g, gnm)
if gr != nil {
GradientWritePts(gr.Grad, dat)
}
}
}
// GradientReadPoints reads the gradient points from
// a slice of floating point numbers, reading from the end.
func GradientReadPts(gr gradient.Gradient, dat []float32) {
if gr == nil {
return
}
gb := gr.AsBase()
sz := len(dat)
gb.Box.Min.X = dat[sz-4]
gb.Box.Min.Y = dat[sz-3]
gb.Box.Max.X = dat[sz-2]
gb.Box.Max.Y = dat[sz-1]
gb.Transform.XX = dat[sz-10]
gb.Transform.YX = dat[sz-9]
gb.Transform.XY = dat[sz-8]
gb.Transform.YY = dat[sz-7]
gb.Transform.X0 = dat[sz-6]
gb.Transform.Y0 = dat[sz-5]
}
// GradientReadPts reads the geometry of the gradients for this node
// from a slice of floating point numbers, reading from the end.
func (g *NodeBase) GradientReadPts(sv *SVG, dat []float32) {
gnm := NodePropURL(g, "fill")
if gnm != "" {
gr := sv.GradientByName(g, gnm)
if gr != nil {
GradientReadPts(gr.Grad, dat)
}
}
gnm = NodePropURL(g, "stroke")
if gnm != "" {
gr := sv.GradientByName(g, gnm)
if gr != nil {
GradientReadPts(gr.Grad, dat)
}
}
}
//////////////////////////////////////////////////////////////////////////////
// Gradient management utilities for creating element-specific grads
// GradientUpdateStops copies stops from StopsName gradient if it is set
func (sv *SVG) GradientUpdateStops(gr *Gradient) {
if gr.StopsName == "" {
return
}
sgr := sv.GradientByName(gr, gr.StopsName)
if sgr != nil {
gr.Grad.AsBase().CopyStopsFrom(sgr.Grad.AsBase())
}
}
// GradientDeleteForNode deletes the node-specific gradient on given node
// of given name, which can be a full url(# name or just the bare name.
// Returns true if deleted.
func (sv *SVG) GradientDeleteForNode(n Node, grnm string) bool {
gr := sv.GradientByName(n, grnm)
if gr == nil || gr.StopsName == "" {
return false
}
unm := NameFromURL(grnm)
sv.Defs.DeleteChildByName(unm, ki.DestroyKids)
return true
}
// GradientNewForNode adds a new gradient specific to given node
// that points to given stops name. returns the new gradient
// and the url that points to it (nil if parent svg cannot be found).
// Initializes gradient to use bounding box of object, but using userSpaceOnUse setting
func (sv *SVG) GradientNewForNode(n Node, radial bool, stops string) (*Gradient, string) {
gr, url := sv.GradientNew(radial)
gr.StopsName = stops
gr.Grad.AsBase().SetBox(n.LocalBBox())
sv.GradientUpdateStops(gr)
return gr, url
}
// GradientNew adds a new gradient, either linear or radial,
// with a new unique id
func (sv *SVG) GradientNew(radial bool) (*Gradient, string) {
gnm := ""
if radial {
gnm = "radialGradient"
} else {
gnm = "linearGradient"
}
id := sv.NewUniqueId()
gnm = NameId(gnm, id)
gr := sv.Defs.NewChild(GradientType, gnm).(*Gradient)
url := NameToURL(gnm)
if radial {
gr.Grad = gradient.NewRadial()
} else {
gr.Grad = gradient.NewLinear()
}
return gr, url
}
// GradientUpdateNodeProp ensures that node has a gradient property of given type
func (sv *SVG) GradientUpdateNodeProp(n Node, prop string, radial bool, stops string) (*Gradient, string) {
ps := n.Prop(prop)
if ps == nil {
gr, url := sv.GradientNewForNode(n, radial, stops)
n.SetProp(prop, url)
return gr, url
}
pstr := ps.(string)
trgst := ""
if radial {
trgst = "radialGradient"
} else {
trgst = "linearGradient"
}
url := "url(#" + trgst
if strings.HasPrefix(pstr, url) {
gr := sv.GradientByName(n, pstr)
gr.StopsName = stops
sv.GradientUpdateStops(gr)
return gr, NameToURL(gr.Nm)
}
if strings.HasPrefix(pstr, "url(#") { // wrong kind
sv.GradientDeleteForNode(n, pstr)
}
gr, url := sv.GradientNewForNode(n, radial, stops)
n.SetProp(prop, url)
return gr, url
}
// GradientUpdateNodePoints updates the points for node based on current bbox
func (sv *SVG) GradientUpdateNodePoints(n Node, prop string) {
ps := n.Prop(prop)
if ps == nil {
return
}
pstr := ps.(string)
url := "url(#"
if !strings.HasPrefix(pstr, url) {
return
}
gr := sv.GradientByName(n, pstr)
if gr == nil {
return
}
gb := gr.Grad.AsBase()
gb.SetBox(n.LocalBBox())
gb.SetTransform(mat32.Identity2())
}
// GradientCloneNodeProp creates a new clone of the existing gradient for node
// if set for given property key ("fill" or "stroke").
// returns new gradient.
func (sv *SVG) GradientCloneNodeProp(n Node, prop string) *Gradient {
ps := n.Prop(prop)
if ps == nil {
return nil
}
pstr := ps.(string)
radial := false
if strings.HasPrefix(pstr, "url(#radialGradient") {
radial = true
} else if !strings.HasPrefix(pstr, "url(#linearGradient") {
return nil
}
gr := sv.GradientByName(n, pstr)
if gr == nil {
return nil
}
ngr, url := sv.GradientNewForNode(n, radial, gr.StopsName)
n.SetProp(prop, url)
gradient.CopyFrom(ngr.Grad, gr.Grad)
// TODO(kai): should this return ngr or gr? (used to return gr but ngr seems correct)
return ngr
}
// GradientDeleteNodeProp deletes any existing gradient for node
// if set for given property key ("fill" or "stroke").
// Returns true if deleted.
func (sv *SVG) GradientDeleteNodeProp(n Node, prop string) bool {
ps := n.Prop(prop)
if ps == nil {
return false
}
pstr := ps.(string)
if !strings.HasPrefix(pstr, "url(#radialGradient") && !strings.HasPrefix(pstr, "url(#linearGradient") {
return false
}
return sv.GradientDeleteForNode(n, pstr)
}
// GradientUpdateAllStops removes any items from Defs that are not actually referred to
// by anything in the current SVG tree. Returns true if items were removed.
// Does not remove gradients with StopsName = "" with extant stops -- these
// should be removed manually, as they are not automatically generated.
func (sv *SVG) GradientUpdateAllStops() {
for _, k := range sv.Defs.Kids {
gr, ok := k.(*Gradient)
if ok {
sv.GradientUpdateStops(gr)
}
}
}