1package svg23import (4 "encoding/base64"5 "strings"67 "gno.land/p/nt/bptree/v0"8 "gno.land/p/nt/ufmt/v0"9)1011type Canvas struct {12 Width, Height int13 ViewBox string14 Elems []Elem15 Style *bptree.BPTree16}1718type Elem interface{ String() string }1920func NewCanvas(width, height int) *Canvas {21 return &Canvas{22 Width: width,23 Height: height,24 Style: nil,25 }26}2728func (c *Canvas) AddStyle(key, value string) *Canvas {29 if c.Style == nil {30 c.Style = bptree.NewBPTree32()31 }32 c.Style.Set(key, value)33 return c34}3536func (c *Canvas) WithViewBox(x, y, width, height int) *Canvas {37 c.ViewBox = ufmt.Sprintf("%d %d %d %d", x, y, width, height)38 return c39}4041// Render renders your canvas42func (c Canvas) Render(alt string) string {43 base64SVG := base64.StdEncoding.EncodeToString([]byte(c.String()))44 return ufmt.Sprintf("", alt, base64SVG)45}4647func (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 false56 })57 out += "</style>"58 }59 for _, elem := range c.Elems {60 out += elem.String()61 }62 out += "</svg>"63 return out64}6566func (c Canvas) Base64() string {67 out := c.String()68 return base64.StdEncoding.EncodeToString([]byte(out))69}7071func (c *Canvas) Append(elem ...Elem) {72 c.Elems = append(c.Elems, elem...)73}7475type BaseAttrs struct {76 ID string77 Class string78 Style string79 Stroke string80 StrokeWidth string81 Opacity string82 Transform string83 Visibility string84}8586func (b BaseAttrs) String() string {87 var elems []string8889 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}118119type Circle struct {120 CX int // center X121 CY int // center Y122 R int // radius123 Fill string124 Attr BaseAttrs125}126127func (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}130131func 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}139140func (c *Circle) WithClass(class string) *Circle {141 c.Attr.Class = class142 return c143}144145type Ellipse struct {146 CX int // center X147 CY int // center Y148 RX int // radius X149 RY int // radius Y150 Fill string151 Attr BaseAttrs152}153154func (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}157158func NewEllipse(cx, cy int, fill string) *Ellipse {159 return &Ellipse{160 CX: cx,161 CY: cy,162 Fill: fill,163 }164}165166func (e *Ellipse) WithClass(class string) *Ellipse {167 e.Attr.Class = class168 return e169}170171type Rectangle struct {172 X, Y, Width, Height int173 RX, RY int // corner radiuses174 Fill string175 Attr BaseAttrs176}177178func (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}181182func 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}191192func (r *Rectangle) WithClass(class string) *Rectangle {193 r.Attr.Class = class194 return r195}196197type Path struct {198 D string199 Fill string200 Attr BaseAttrs201}202203func (p Path) String() string {204 return ufmt.Sprintf(`<path d="%s" fill="%s" %s/>`, p.D, p.Fill, p.Attr.String())205}206207func NewPath(d, fill string) *Path {208 return &Path{209 D: d,210 Fill: fill,211 }212}213214func (p *Path) WithClass(class string) *Path {215 p.Attr.Class = class216 return p217}218219type Polygon struct { // closed shape220 Points string221 Fill string222 Attr BaseAttrs223}224225func (p Polygon) String() string {226 return ufmt.Sprintf(`<polygon points="%s" fill="%s" %s/>`, p.Points, p.Fill, p.Attr.String())227}228229func NewPolygon(points, fill string) *Polygon {230 return &Polygon{231 Points: points,232 Fill: fill,233 }234}235236func (p *Polygon) WithClass(class string) *Polygon {237 p.Attr.Class = class238 return p239}240241type Polyline struct { // polygon but not necessarily closed242 Points string243 Fill string244 Attr BaseAttrs245}246247func (p Polyline) String() string {248 return ufmt.Sprintf(`<polyline points="%s" fill="%s" %s/>`, p.Points, p.Fill, p.Attr.String())249}250251func NewPolyline(points, fill string) *Polyline {252 return &Polyline{253 Points: points,254 Fill: fill,255 }256}257258func (p *Polyline) WithClass(class string) *Polyline {259 p.Attr.Class = class260 return p261}262263type Text struct {264 X, Y int265 DX, DY int // shift text pos horizontally/ vertically266 Rotate string267 Text, Fill string268 Attr BaseAttrs269}270271func (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}274275func 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}283284func (c *Text) WithClass(class string) *Text {285 c.Attr.Class = class286 return c287}288289type Group struct {290 Elems []Elem291 Fill string292 Attr BaseAttrs293}294295func (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}302303func NewGroup(fill string) *Group {304 return &Group{305 Fill: fill,306 }307}308309func (g *Group) Append(elem ...Elem) {310 g.Elems = append(g.Elems, elem...)311}312313func (g *Group) WithClass(class string) *Group {314 g.Attr.Class = class315 return g316}317Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.