Transaction
4C593F3978C01E…D10A9E751E2B
Block 25,917 · index 0 · indexed
Summary
- Hash
- 4C593F3978C01EC05A7F9C77592D2CE58515A9BBD451290CCDABD10A9E751E2B
- Block
- 25,917
- Size
- 10143 bytes
- Gas used
- 16,605,779 / 19,926,994
- Fee
- 1000000ugnot
- Memo
- gnopublish
- Status
- success
Messages
- Package
- gno.land/p/moul/svg/v0
Arguments · 11
- #1svg
- #2README.md
- #3# `gno.land/p/moul/svg/v0` Minimalist, extensible SVG generation library for Gno. The canvas style map is backed by [`gno.land/p/nt/bptree/v0`](https://gno.land/p/nt/bptree/v0): a B+ tree packs many entries per persisted node, so styles cost less storage and inserts spend less gas than an AVL backing would. **Compatibility caveats** (from the in-place-mutating B+ tree backing): - do not call `AddStyle` from inside a `Canvas.String`/`Render` iteration; - do not copy a non-zero `Canvas` by value once styles have been added — the copies would share live tree nodes. <!-- BEGIN GNOCONTRACTS FOOTER (generated by `make readmes`; do not edit below) --> --- Part of **[moul/gno-contracts](https://github.com/moul/gno-contracts)** — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage. **Dependency graph:**  > ⚠️ **Disclaimer:** provided as-is, without warranty; not security-audited. Full disclaimer: [DISCLAIMER](https://github.com/moul/gno-contracts/blob/main/DISCLAIMER.md). <!-- END GNOCONTRACTS FOOTER -->
- #4doc.gno
- #5/* Package svg is a minimalist and extensible SVG generation library for Gno. It is the B+ tree successor to [gno.land/p/moul/svg/v0] (whose canvas styles are backed by an AVL tree): a bump to v2 because the backing data structure used for the canvas style map — and thus the on-chain storage layout — changed from [gno.land/p/nt/avl/v0] to [gno.land/p/nt/bptree/v0]. The exported API is otherwise identical to v1. A B+ tree packs many entries per persisted node, so a stored style entry costs less storage than the AVL backing (and inserts spend materially less gas). Prefer v2 when the canvas is part of persisted realm state. Two behavioral caveats inherited from the in-place-mutating B+ tree backing: - do NOT call AddStyle from inside a Canvas.String/Render iteration — the AVL backing tolerated mutation during iteration, the B+ tree one does not; - do NOT copy a non-zero Canvas by value once styles have been added — the copies would share live B+ tree nodes (v1's AVL copies were independent). 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. Each SVG element embeds a BaseAttrs struct, which supports common SVG attributes like `id`, `class`, `style`, `fill`, `stroke`, and `transform`. Canvas objects represent the root SVG container and support global dimensions, viewBox configuration, embedded styles, and element composition. Example: import "gno.land/p/moul/svg/v0" func Foo() string { canvas := svg.NewCanvas(200, 200).WithViewBox(0, 0, 200, 200) canvas.AddStyle(".my-rect", "stroke:black;stroke-width:2") canvas.Append( svg.NewRectangle(60, 40, 100, 50, "red").WithClass("my-rect"), svg.NewCircle(50, 80, 40, "blue"), &svg.Path{D: `M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30 Q 90,60 50,90 Q 10,60 10,30 z`, Fill: "magenta"}, svg.NewText(20, 50, "Hello SVG", "black"), ) mysvg := canvas.Base64() } */ package svg // import "gno.land/p/moul/svg/v0"
- #6gnomod.toml
- #7module = "gno.land/p/moul/svg/v0" gno = "0.9"
- #8svg.gno
- #9package svg import ( "encoding/base64" "strings" "gno.land/p/nt/bptree/v0" "gno.land/p/nt/ufmt/v0" ) type Canvas struct { Width, Height int ViewBox string Elems []Elem Style *bptree.BPTree } type Elem interface{ String() string } func NewCanvas(width, height int) *Canvas { return &Canvas{ Width: width, Height: height, Style: nil, } } func (c *Canvas) AddStyle(key, value string) *Canvas { if c.Style == nil { c.Style = bptree.NewBPTree32() } c.Style.Set(key, value) return c } func (c *Canvas) WithViewBox(x, y, width, height int) *Canvas { c.ViewBox = ufmt.Sprintf("%d %d %d %d", x, y, width, height) return c } // Render renders your canvas func (c Canvas) Render(alt string) string { base64SVG := base64.StdEncoding.EncodeToString([]byte(c.String())) return ufmt.Sprintf("", alt, base64SVG) } func (c Canvas) String() string { out := "" out += ufmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" viewBox="%s">`, c.Width, c.Height, c.ViewBox) if c.Style != nil { out += "<style>" c.Style.Iterate("", "", func(k string, val interface{}) bool { v := val.(string) out += ufmt.Sprintf("%s{%s}", k, v) return false }) out += "</style>" } for _, elem := range c.Elems { out += elem.String() } out += "</svg>" return out } func (c Canvas) Base64() string { out := c.String() return base64.StdEncoding.EncodeToString([]byte(out)) } func (c *Canvas) Append(elem ...Elem) { c.Elems = append(c.Elems, elem...) } type BaseAttrs struct { ID string Class string Style string Stroke string StrokeWidth string Opacity string Transform string Visibility string } func (b BaseAttrs) String() string { var elems []string if b.ID != "" { elems = append(elems, `id="`+b.ID+`"`) } if b.Class != "" { elems = append(elems, `class="`+b.Class+`"`) } if b.Style != "" { elems = append(elems, `style="`+b.Style+`"`) } if b.Stroke != "" { elems = append(elems, `stroke="`+b.Stroke+`"`) } if b.StrokeWidth != "" { elems = append(elems, `stroke-width="`+b.StrokeWidth+`"`) } if b.Opacity != "" { elems = append(elems, `opacity="`+b.Opacity+`"`) } if b.Transform != "" { elems = append(elems, `transform="`+b.Transform+`"`) } if b.Visibility != "" { elems = append(elems, `visibility="`+b.Visibility+`"`) } if len(elems) == 0 { return "" } return strings.Join(elems, " ") } type Circle struct { CX int // center X CY int // center Y R int // radius Fill string Attr BaseAttrs } func (c Circle) String() string { return ufmt.Sprintf(`<circle cx="%d" cy="%d" r="%d" fill="%s" %s/>`, c.CX, c.CY, c.R, c.Fill, c.Attr.String()) } func NewCircle(cx, cy, r int, fill string) *Circle { return &Circle{ CX: cx, CY: cy, R: r, Fill: fill, } } func (c *Circle) WithClass(class string) *Circle { c.Attr.Class = class return c } type Ellipse struct { CX int // center X CY int // center Y RX int // radius X RY int // radius Y Fill string Attr BaseAttrs } func (e Ellipse) String() string { 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()) } func NewEllipse(cx, cy int, fill string) *Ellipse { return &Ellipse{ CX: cx, CY: cy, Fill: fill, } } func (e *Ellipse) WithClass(class string) *Ellipse { e.Attr.Class = class return e } type Rectangle struct { X, Y, Width, Height int RX, RY int // corner radiuses Fill string Attr BaseAttrs } func (r Rectangle) String() string { 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()) } func NewRectangle(x, y, width, height int, fill string) *Rectangle { return &Rectangle{ X: x, Y: y, Width: width, Height: height, Fill: fill, } } func (r *Rectangle) WithClass(class string) *Rectangle { r.Attr.Class = class return r } type Path struct { D string Fill string Attr BaseAttrs } func (p Path) String() string { return ufmt.Sprintf(`<path d="%s" fill="%s" %s/>`, p.D, p.Fill, p.Attr.String()) } func NewPath(d, fill string) *Path { return &Path{ D: d, Fill: fill, } } func (p *Path) WithClass(class string) *Path { p.Attr.Class = class return p } type Polygon struct { // closed shape Points string Fill string Attr BaseAttrs } func (p Polygon) String() string { return ufmt.Sprintf(`<polygon points="%s" fill="%s" %s/>`, p.Points, p.Fill, p.Attr.String()) } func NewPolygon(points, fill string) *Polygon { return &Polygon{ Points: points, Fill: fill, } } func (p *Polygon) WithClass(class string) *Polygon { p.Attr.Class = class return p } type Polyline struct { // polygon but not necessarily closed Points string Fill string Attr BaseAttrs } func (p Polyline) String() string { return ufmt.Sprintf(`<polyline points="%s" fill="%s" %s/>`, p.Points, p.Fill, p.Attr.String()) } func NewPolyline(points, fill string) *Polyline { return &Polyline{ Points: points, Fill: fill, } } func (p *Polyline) WithClass(class string) *Polyline { p.Attr.Class = class return p } type Text struct { X, Y int DX, DY int // shift text pos horizontally/ vertically Rotate string Text, Fill string Attr BaseAttrs } func (c Text) String() string { 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) } func NewText(x, y int, text, fill string) *Text { return &Text{ X: x, Y: y, Text: text, Fill: fill, } } func (c *Text) WithClass(class string) *Text { c.Attr.Class = class return c } type Group struct { Elems []Elem Fill string Attr BaseAttrs } func (g Group) String() string { out := "" for _, e := range g.Elems { out += e.String() } return ufmt.Sprintf(`<g fill="%s" %s>%s</g>`, g.Fill, g.Attr.String(), out) } func NewGroup(fill string) *Group { return &Group{ Fill: fill, } } func (g *Group) Append(elem ...Elem) { g.Elems = append(g.Elems, elem...) } func (g *Group) WithClass(class string) *Group { g.Attr.Class = class return g }
- #10/gno.MemPackageType
- #11 MPUserProd
Result log
msg:0,success:true,log:,events:[]