Skip to content

Commit 1bc1027

Browse files
authored
feat(examples): add more to /p/demo/svg pure package for more elements (#4255)
elipse, polygons, paths, ... Added functionalities: - the addition of styles at the top of the svg - the option of include basic attributes to any element type - <g> grouping element New elements: - ellipses - polygons - poly lines - paths + small addition to already existent element structures Feel free to propose suggestions to improve or add more functionalities
1 parent c1219b1 commit 1bc1027

File tree

4 files changed

+285
-39
lines changed

4 files changed

+285
-39
lines changed

examples/gno.land/p/demo/svg/doc.gno

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,30 @@
11
/*
2-
Package svg is a minimalist SVG generation library for Gno.
2+
Package svg is a minimalist and extensible SVG generation library for Gno.
33

4-
The svg package provides a simple and lightweight solution for programmatically generating SVG (Scalable Vector Graphics) markup in Gno. It allows you to create basic shapes like rectangles and circles, and output the generated SVG to a
4+
It provides a structured way to create and compose SVG elements such as rectangles, circles, text, paths, and more. The package is designed to be modular and developer-friendly, enabling optional attributes and method chaining for ease of use.
5+
6+
Each SVG element embeds a BaseAttrs struct, which supports common SVG attributes like `id`, `class`, `style`, `fill`, `stroke`, and `transform`.
7+
8+
Canvas objects represent the root SVG container and support global dimensions, viewBox configuration, embedded styles, and element composition.
59

610
Example:
711

8-
import "gno.land/p/demo/svg""
12+
import "gno.land/p/demo/svg"
913

1014
func Foo() string {
11-
canvas := svg.Canvas{Width: 200, Height: 200}
12-
canvas.DrawRectangle(50, 50, 100, 100, "red")
13-
canvas.DrawCircle(100, 100, 50, "blue")
14-
return canvas.String()
15+
canvas := svg.NewCanvas(200, 200).WithViewBox(0, 0, 200, 200)
16+
canvas.AddStyle(".my-rect", "stroke:black;stroke-width:2")
17+
canvas.Append(
18+
svg.NewRectangle(60, 40, 100, 50, "red").WithClass("my-rect"),
19+
svg.NewCircle(50, 80, 40, "blue"),
20+
&svg.Path{D: `M 10,30
21+
A 20,20 0,0,1 50,30
22+
A 20,20 0,0,1 90,30
23+
Q 90,60 50,90
24+
Q 10,60 10,30 z`, Fill: "magenta"},
25+
svg.NewText(20, 50, "Hello SVG", "black"),
26+
)
27+
mysvg := canvas.Base64()
1528
}
1629
*/
1730
package svg // import "gno.land/p/demo/svg"

examples/gno.land/p/demo/svg/svg.gno

Lines changed: 252 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,310 @@
11
package svg
22

3-
import "gno.land/p/demo/ufmt"
3+
import (
4+
"encoding/base64"
5+
"strings"
6+
7+
"gno.land/p/demo/avl"
8+
"gno.land/p/demo/ufmt"
9+
)
410

511
type Canvas struct {
6-
Width int
7-
Height int
8-
Elems []Elem
12+
Width, Height int
13+
ViewBox string
14+
Elems []Elem
15+
Style *avl.Tree
916
}
1017

1118
type Elem interface{ String() string }
1219

20+
func NewCanvas(width, height int) *Canvas {
21+
return &Canvas{
22+
Width: width,
23+
Height: height,
24+
Style: nil,
25+
}
26+
}
27+
28+
func (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+
36+
func (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+
1341
func (c Canvas) String() string {
14-
output := ""
15-
output += ufmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d">`, c.Width, c.Height)
42+
out := ""
43+
out += ufmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" viewBox="%s">`, c.Width, c.Height, c.ViewBox)
44+
if c.Style != nil {
45+
out += "<style>"
46+
c.Style.Iterate("", "", func(k string, val interface{}) bool {
47+
v := val.(string)
48+
out += ufmt.Sprintf("%s{%s}", k, v)
49+
return false
50+
})
51+
out += "</style>"
52+
}
1653
for _, elem := range c.Elems {
17-
output += elem.String()
54+
out += elem.String()
1855
}
19-
output += "</svg>"
20-
return output
56+
out += "</svg>"
57+
return out
2158
}
2259

23-
func (c *Canvas) Append(elem Elem) {
24-
c.Elems = append(c.Elems, elem)
60+
func (c Canvas) Base64() string {
61+
out := c.String()
62+
return base64.StdEncoding.EncodeToString([]byte(out))
63+
}
64+
65+
func (c *Canvas) Append(elem ...Elem) {
66+
c.Elems = append(c.Elems, elem...)
67+
}
68+
69+
type BaseAttrs struct {
70+
ID string
71+
Class string
72+
Style string
73+
Stroke string
74+
StrokeWidth string
75+
Opacity string
76+
Transform string
77+
Visibility string
78+
}
79+
80+
func (b BaseAttrs) String() string {
81+
var elems []string
82+
83+
if b.ID != "" {
84+
elems = append(elems, `id="`+b.ID+`"`)
85+
}
86+
if b.Class != "" {
87+
elems = append(elems, `class="`+b.Class+`"`)
88+
}
89+
if b.Style != "" {
90+
elems = append(elems, `style="`+b.Style+`"`)
91+
}
92+
if b.Stroke != "" {
93+
elems = append(elems, `stroke="`+b.Stroke+`"`)
94+
}
95+
if b.StrokeWidth != "" {
96+
elems = append(elems, `stroke-width="`+b.StrokeWidth+`"`)
97+
}
98+
if b.Opacity != "" {
99+
elems = append(elems, `opacity="`+b.Opacity+`"`)
100+
}
101+
if b.Transform != "" {
102+
elems = append(elems, `transform="`+b.Transform+`"`)
103+
}
104+
if b.Visibility != "" {
105+
elems = append(elems, `visibility="`+b.Visibility+`"`)
106+
}
107+
if len(elems) == 0 {
108+
return ""
109+
}
110+
return strings.Join(elems, " ")
25111
}
26112

27113
type Circle struct {
28114
CX int // center X
29115
CY int // center Y
30116
R int // radius
31117
Fill string
118+
Attr BaseAttrs
32119
}
33120

34121
func (c Circle) String() string {
35-
return ufmt.Sprintf(`<circle cx="%d" cy="%d" r="%d" fill="%s" />`, c.CX, c.CY, c.R, c.Fill)
122+
return ufmt.Sprintf(`<circle cx="%d" cy="%d" r="%d" fill="%s" %s/>`, c.CX, c.CY, c.R, c.Fill, c.Attr.String())
36123
}
37124

38-
func (c *Canvas) DrawCircle(cx, cy, r int, fill string) {
39-
c.Append(Circle{
125+
func NewCircle(cx, cy, r int, fill string) *Circle {
126+
return &Circle{
40127
CX: cx,
41128
CY: cy,
42129
R: r,
43130
Fill: fill,
44-
})
131+
}
132+
}
133+
134+
func (c *Circle) WithClass(class string) *Circle {
135+
c.Attr.Class = class
136+
return c
137+
}
138+
139+
type Ellipse struct {
140+
CX int // center X
141+
CY int // center Y
142+
RX int // radius X
143+
RY int // radius Y
144+
Fill string
145+
Attr BaseAttrs
146+
}
147+
148+
func (e Ellipse) String() string {
149+
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())
150+
}
151+
152+
func NewEllipse(cx, cy int, fill string) *Ellipse {
153+
return &Ellipse{
154+
CX: cx,
155+
CY: cy,
156+
Fill: fill,
157+
}
158+
}
159+
160+
func (e *Ellipse) WithClass(class string) *Ellipse {
161+
e.Attr.Class = class
162+
return e
45163
}
46164

47165
type Rectangle struct {
48166
X, Y, Width, Height int
167+
RX, RY int // corner radiuses
49168
Fill string
169+
Attr BaseAttrs
50170
}
51171

52-
func (c Rectangle) String() string {
53-
return ufmt.Sprintf(`<rect x="%d" y="%d" width="%d" height="%d" fill="%s" />`, c.X, c.Y, c.Width, c.Height, c.Fill)
172+
func (r Rectangle) String() string {
173+
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())
54174
}
55175

56-
func (c *Canvas) DrawRectangle(x, y, width, height int, fill string) {
57-
c.Append(Rectangle{
176+
func NewRectangle(x, y, width, height int, fill string) *Rectangle {
177+
return &Rectangle{
58178
X: x,
59179
Y: y,
60180
Width: width,
61181
Height: height,
62182
Fill: fill,
63-
})
183+
}
184+
}
185+
186+
func (r *Rectangle) WithClass(class string) *Rectangle {
187+
r.Attr.Class = class
188+
return r
189+
}
190+
191+
type Path struct {
192+
D string
193+
Fill string
194+
Attr BaseAttrs
195+
}
196+
197+
func (p Path) String() string {
198+
return ufmt.Sprintf(`<path d="%s" fill="%s" %s/>`, p.D, p.Fill, p.Attr.String())
199+
}
200+
201+
func NewPath(d, fill string) *Path {
202+
return &Path{
203+
D: d,
204+
Fill: fill,
205+
}
206+
}
207+
208+
func (p *Path) WithClass(class string) *Path {
209+
p.Attr.Class = class
210+
return p
211+
}
212+
213+
type Polygon struct { // closed shape
214+
Points string
215+
Fill string
216+
Attr BaseAttrs
217+
}
218+
219+
func (p Polygon) String() string {
220+
return ufmt.Sprintf(`<polygon points="%s" fill="%s" %s/>`, p.Points, p.Fill, p.Attr.String())
221+
}
222+
223+
func NewPolygon(points, fill string) *Polygon {
224+
return &Polygon{
225+
Points: points,
226+
Fill: fill,
227+
}
228+
}
229+
230+
func (p *Polygon) WithClass(class string) *Polygon {
231+
p.Attr.Class = class
232+
return p
233+
}
234+
235+
type Polyline struct { // polygon but not necessarily closed
236+
Points string
237+
Fill string
238+
Attr BaseAttrs
239+
}
240+
241+
func (p Polyline) String() string {
242+
return ufmt.Sprintf(`<polyline points="%s" fill="%s" %s/>`, p.Points, p.Fill, p.Attr.String())
243+
}
244+
245+
func NewPolyline(points, fill string) *Polyline {
246+
return &Polyline{
247+
Points: points,
248+
Fill: fill,
249+
}
250+
}
251+
252+
func (p *Polyline) WithClass(class string) *Polyline {
253+
p.Attr.Class = class
254+
return p
64255
}
65256

66257
type Text struct {
67258
X, Y int
259+
DX, DY int // shift text pos horizontally/ vertically
260+
Rotate string
68261
Text, Fill string
262+
Attr BaseAttrs
69263
}
70264

71265
func (c Text) String() string {
72-
return ufmt.Sprintf(`<text x="%d" y="%d" fill="%s">%s</text>`, c.X, c.Y, c.Fill, c.Text)
266+
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)
73267
}
74268

75-
func (c *Canvas) DrawText(x, y int, text, fill string) {
76-
c.Append(Text{
269+
func NewText(x, y int, text, fill string) *Text {
270+
return &Text{
77271
X: x,
78272
Y: y,
79273
Text: text,
80274
Fill: fill,
81-
})
275+
}
276+
}
277+
278+
func (c *Text) WithClass(class string) *Text {
279+
c.Attr.Class = class
280+
return c
281+
}
282+
283+
type Group struct {
284+
Elems []Elem
285+
Fill string
286+
Attr BaseAttrs
287+
}
288+
289+
func (g Group) String() string {
290+
out := ""
291+
for _, e := range g.Elems {
292+
out += e.String()
293+
}
294+
return ufmt.Sprintf(`<g fill="%s" %s>%s</g>`, g.Fill, g.Attr.String(), out)
295+
}
296+
297+
func NewGroup(fill string) *Group {
298+
return &Group{
299+
Fill: fill,
300+
}
301+
}
302+
303+
func (g *Group) Append(elem ...Elem) {
304+
g.Elems = append(g.Elems, elem...)
305+
}
306+
307+
func (g *Group) WithClass(class string) *Group {
308+
g.Attr.Class = class
309+
return g
82310
}

examples/gno.land/p/demo/svg/z0_filetest.gno

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@ import "gno.land/p/demo/svg"
55

66
func main() {
77
canvas := svg.Canvas{Width: 500, Height: 500}
8-
canvas.DrawRectangle(50, 50, 100, 100, "red")
9-
canvas.DrawCircle(100, 100, 50, "blue")
10-
canvas.DrawText(100, 100, "hello world!", "magenta")
8+
canvas.Append(
9+
svg.Rectangle{X: 50, Y: 50, Width: 100, Height: 100, Fill: "red"},
10+
svg.Circle{CX: 100, CY: 100, R: 50, Fill: "blue"},
11+
svg.Text{X: 100, Y: 100, Text: "hello world!", Fill: "magenta"},
12+
)
13+
canvas.Append(
14+
svg.NewCircle(100, 100, 50, "blue").WithClass("toto"),
15+
)
1116
println(canvas)
1217
}
1318

1419
// Output:
15-
// <svg xmlns="http://www.w3.org/2000/svg" width="500" height="500"><rect x="50" y="50" width="100" height="100" fill="red" /><circle cx="100" cy="100" r="50" fill="blue" /><text x="100" y="100" fill="magenta">hello world!</text></svg>
20+
// <svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox=""><rect x="50" y="50" width="100" height="100" rx="0" ry="0" fill="red" /><circle cx="100" cy="100" r="50" fill="blue" /><text x="100" y="100" dx="0" dy="0" rotate="" fill="magenta" >hello world!</text><circle cx="100" cy="100" r="50" fill="blue" class="toto"/></svg>

0 commit comments

Comments
 (0)