svg.gno

6.17 Kb ยท 316 lines
  1package svg
  2
  3import (
  4	"encoding/base64"
  5	"strings"
  6
  7	"gno.land/p/demo/avl"
  8	"gno.land/p/demo/ufmt"
  9)
 10
 11type Canvas struct {
 12	Width, Height int
 13	ViewBox       string
 14	Elems         []Elem
 15	Style         *avl.Tree
 16}
 17
 18type Elem interface{ String() string }
 19
 20func NewCanvas(width, height int) *Canvas {
 21	return &Canvas{
 22		Width:  width,
 23		Height: height,
 24		Style:  nil,
 25	}
 26}
 27
 28func (c *Canvas) AddStyle(key, value string) *Canvas {
 29	if c.Style == nil {
 30		c.Style = avl.NewTree()
 31	}
 32	c.Style.Set(key, value)
 33	return c
 34}
 35
 36func (c *Canvas) WithViewBox(x, y, width, height int) *Canvas {
 37	c.ViewBox = ufmt.Sprintf("%d %d %d %d", x, y, width, height)
 38	return c
 39}
 40
 41// Render renders your canvas
 42func (c Canvas) Render(alt string) string {
 43	base64SVG := base64.StdEncoding.EncodeToString([]byte(c.String()))
 44	return ufmt.Sprintf("![%s](data:image/svg+xml;base64,%s)", alt, base64SVG)
 45}
 46
 47func (c Canvas) String() string {
 48	out := ""
 49	out += ufmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" viewBox="%s">`, c.Width, c.Height, c.ViewBox)
 50	if c.Style != nil {
 51		out += "<style>"
 52		c.Style.Iterate("", "", func(k string, val interface{}) bool {
 53			v := val.(string)
 54			out += ufmt.Sprintf("%s{%s}", k, v)
 55			return false
 56		})
 57		out += "</style>"
 58	}
 59	for _, elem := range c.Elems {
 60		out += elem.String()
 61	}
 62	out += "</svg>"
 63	return out
 64}
 65
 66func (c Canvas) Base64() string {
 67	out := c.String()
 68	return base64.StdEncoding.EncodeToString([]byte(out))
 69}
 70
 71func (c *Canvas) Append(elem ...Elem) {
 72	c.Elems = append(c.Elems, elem...)
 73}
 74
 75type BaseAttrs struct {
 76	ID          string
 77	Class       string
 78	Style       string
 79	Stroke      string
 80	StrokeWidth string
 81	Opacity     string
 82	Transform   string
 83	Visibility  string
 84}
 85
 86func (b BaseAttrs) String() string {
 87	var elems []string
 88
 89	if b.ID != "" {
 90		elems = append(elems, `id="`+b.ID+`"`)
 91	}
 92	if b.Class != "" {
 93		elems = append(elems, `class="`+b.Class+`"`)
 94	}
 95	if b.Style != "" {
 96		elems = append(elems, `style="`+b.Style+`"`)
 97	}
 98	if b.Stroke != "" {
 99		elems = append(elems, `stroke="`+b.Stroke+`"`)
100	}
101	if b.StrokeWidth != "" {
102		elems = append(elems, `stroke-width="`+b.StrokeWidth+`"`)
103	}
104	if b.Opacity != "" {
105		elems = append(elems, `opacity="`+b.Opacity+`"`)
106	}
107	if b.Transform != "" {
108		elems = append(elems, `transform="`+b.Transform+`"`)
109	}
110	if b.Visibility != "" {
111		elems = append(elems, `visibility="`+b.Visibility+`"`)
112	}
113	if len(elems) == 0 {
114		return ""
115	}
116	return strings.Join(elems, " ")
117}
118
119type Circle struct {
120	CX   int // center X
121	CY   int // center Y
122	R    int // radius
123	Fill string
124	Attr BaseAttrs
125}
126
127func (c Circle) String() string {
128	return ufmt.Sprintf(`<circle cx="%d" cy="%d" r="%d" fill="%s" %s/>`, c.CX, c.CY, c.R, c.Fill, c.Attr.String())
129}
130
131func NewCircle(cx, cy, r int, fill string) *Circle {
132	return &Circle{
133		CX:   cx,
134		CY:   cy,
135		R:    r,
136		Fill: fill,
137	}
138}
139
140func (c *Circle) WithClass(class string) *Circle {
141	c.Attr.Class = class
142	return c
143}
144
145type Ellipse struct {
146	CX   int // center X
147	CY   int // center Y
148	RX   int // radius X
149	RY   int // radius Y
150	Fill string
151	Attr BaseAttrs
152}
153
154func (e Ellipse) String() string {
155	return ufmt.Sprintf(`<ellipse cx="%d" cy="%d" rx="%d" ry="%d" fill="%s" %s/>`, e.CX, e.CY, e.RX, e.RY, e.Fill, e.Attr.String())
156}
157
158func NewEllipse(cx, cy int, fill string) *Ellipse {
159	return &Ellipse{
160		CX:   cx,
161		CY:   cy,
162		Fill: fill,
163	}
164}
165
166func (e *Ellipse) WithClass(class string) *Ellipse {
167	e.Attr.Class = class
168	return e
169}
170
171type Rectangle struct {
172	X, Y, Width, Height int
173	RX, RY              int // corner radiuses
174	Fill                string
175	Attr                BaseAttrs
176}
177
178func (r Rectangle) String() string {
179	return ufmt.Sprintf(`<rect x="%d" y="%d" width="%d" height="%d" rx="%d" ry="%d" fill="%s" %s/>`, r.X, r.Y, r.Width, r.Height, r.RX, r.RY, r.Fill, r.Attr.String())
180}
181
182func NewRectangle(x, y, width, height int, fill string) *Rectangle {
183	return &Rectangle{
184		X:      x,
185		Y:      y,
186		Width:  width,
187		Height: height,
188		Fill:   fill,
189	}
190}
191
192func (r *Rectangle) WithClass(class string) *Rectangle {
193	r.Attr.Class = class
194	return r
195}
196
197type Path struct {
198	D    string
199	Fill string
200	Attr BaseAttrs
201}
202
203func (p Path) String() string {
204	return ufmt.Sprintf(`<path d="%s" fill="%s" %s/>`, p.D, p.Fill, p.Attr.String())
205}
206
207func NewPath(d, fill string) *Path {
208	return &Path{
209		D:    d,
210		Fill: fill,
211	}
212}
213
214func (p *Path) WithClass(class string) *Path {
215	p.Attr.Class = class
216	return p
217}
218
219type Polygon struct { // closed shape
220	Points string
221	Fill   string
222	Attr   BaseAttrs
223}
224
225func (p Polygon) String() string {
226	return ufmt.Sprintf(`<polygon points="%s" fill="%s" %s/>`, p.Points, p.Fill, p.Attr.String())
227}
228
229func NewPolygon(points, fill string) *Polygon {
230	return &Polygon{
231		Points: points,
232		Fill:   fill,
233	}
234}
235
236func (p *Polygon) WithClass(class string) *Polygon {
237	p.Attr.Class = class
238	return p
239}
240
241type Polyline struct { // polygon but not necessarily closed
242	Points string
243	Fill   string
244	Attr   BaseAttrs
245}
246
247func (p Polyline) String() string {
248	return ufmt.Sprintf(`<polyline points="%s" fill="%s" %s/>`, p.Points, p.Fill, p.Attr.String())
249}
250
251func NewPolyline(points, fill string) *Polyline {
252	return &Polyline{
253		Points: points,
254		Fill:   fill,
255	}
256}
257
258func (p *Polyline) WithClass(class string) *Polyline {
259	p.Attr.Class = class
260	return p
261}
262
263type Text struct {
264	X, Y       int
265	DX, DY     int // shift text pos horizontally/ vertically
266	Rotate     string
267	Text, Fill string
268	Attr       BaseAttrs
269}
270
271func (c Text) String() string {
272	return ufmt.Sprintf(`<text x="%d" y="%d" dx="%d" dy="%d" rotate="%s" fill="%s" %s>%s</text>`, c.X, c.Y, c.DX, c.DY, c.Rotate, c.Fill, c.Attr.String(), c.Text)
273}
274
275func NewText(x, y int, text, fill string) *Text {
276	return &Text{
277		X:    x,
278		Y:    y,
279		Text: text,
280		Fill: fill,
281	}
282}
283
284func (c *Text) WithClass(class string) *Text {
285	c.Attr.Class = class
286	return c
287}
288
289type Group struct {
290	Elems []Elem
291	Fill  string
292	Attr  BaseAttrs
293}
294
295func (g Group) String() string {
296	out := ""
297	for _, e := range g.Elems {
298		out += e.String()
299	}
300	return ufmt.Sprintf(`<g fill="%s" %s>%s</g>`, g.Fill, g.Attr.String(), out)
301}
302
303func NewGroup(fill string) *Group {
304	return &Group{
305		Fill: fill,
306	}
307}
308
309func (g *Group) Append(elem ...Elem) {
310	g.Elems = append(g.Elems, elem...)
311}
312
313func (g *Group) WithClass(class string) *Group {
314	g.Attr.Class = class
315	return g
316}