svg_generator.gno
6.53 Kb · 225 lines
1package gnft
2
3import (
4 b64 "encoding/base64"
5 "errors"
6 "math/rand"
7 "strconv"
8 "strings"
9
10 ufmt "gno.land/p/nt/ufmt/v0"
11)
12
13// MUST BE IMMUTABLE, DO NOT MODIFY.
14// SVG template structure:
15// The template is split at variable insertion points for efficient string concatenation.
16// Full template with placeholders (for reference):
17//
18// <svg width="135" height="135" viewBox="0 0 135 135" fill="none" xmlns="...">
19// <g clip-path="url(#clip0_7698_56846)">
20// <circle cx="67.5" cy="67.5" r="67.5" fill="url(#paint0_linear_7698_56846)"/>
21// ... (path elements) ...
22// </g>
23// <defs>
24// <linearGradient id="paint0_linear_7698_56846"
25// x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" <-- variables
26// gradientUnits="userSpaceOnUse">
27// <stop stop-color="{color1}"/> <-- variable
28// <stop offset="1" stop-color="{color2}"/> <-- variable
29// </linearGradient>
30// ...
31// </defs>
32// </svg>
33
34// svgTemplate holds pre-split template parts for efficient concatenation.
35// Usage: svgTemplate[0] + x1 + svgTemplate[1] + y1 + ... + color2 + svgTemplate[6]
36var svgTemplate = [7]string{
37 // [0] SVG header and body (before x1)
38 `<svg width="135" height="135" viewBox="0 0 135 135" fill="none" xmlns="http://www.w3.org/2000/svg">
39<g clip-path="url(#clip0_7698_56846)">
40<circle cx="67.5" cy="67.5" r="67.5" fill="url(#paint0_linear_7698_56846)"/>
41<path d="M51.2905 42.9449L66.4895 33L97 52.8061L81.8241 62.7425L51.2905 42.9449Z" fill="white"/>
42<path d="M51.6055 67.5059L66.8044 57.561L97 77.0657L82.1046 87.1793L51.6055 67.5059Z" fill="white" fill-opacity="0.4"/>
43<path d="M36.0464 81.7559L51.2905 71.811L81.7336 91.6547L66.4895 101.508L36.0464 81.7559Z" fill="white" fill-opacity="0.6"/>
44<path d="M36.001 52.8055L51.2884 42.9177L51.2884 71.8145L36.001 81.779L36.001 52.8055Z" fill="white"/>
45<path d="M82.1051 87.1797L97.0016 77.0662L97.0016 81.7029L81.7896 91.629L82.1051 87.1797Z" fill="white" fill-opacity="0.5"/>
46</g>
47<defs>
48<linearGradient id="paint0_linear_7698_56846" x1="`,
49 // [1] between x1 and y1
50 `" y1="`,
51 // [2] between y1 and x2
52 `" x2="`,
53 // [3] between x2 and y2
54 `" y2="`,
55 // [4] between y2 and color1
56 `" gradientUnits="userSpaceOnUse">
57<stop stop-color="`,
58 // [5] between color1 and color2
59 `"/>
60<stop offset="1" stop-color="`,
61 // [6] SVG footer (after color2)
62 `"/>
63</linearGradient>
64<clipPath id="clip0_7698_56846">
65<rect width="135" height="135" fill="white"/>
66</clipPath>
67</defs>
68</svg>
69`,
70}
71
72// charset contains valid hex digits for color generation.
73const charset = "0123456789ABCDEF"
74
75// Parameter range constants for gradient coordinates.
76const (
77 x1Min = 7
78 x1Max = 13
79 y1Min = 7
80 y1Max = 13
81 x2Min = 121
82 x2Max = 126
83 y2Min = 121
84 y2Max = 126
85
86 x1Range = x1Max - x1Min + 1
87 y1Range = y1Max - y1Min + 1
88 x2Range = x2Max - x2Min + 1
89 y2Range = y2Max - y2Min + 1
90)
91
92// genImageParamsString generates random gradient parameters and returns them as a compact string.
93// Format: "x1,y1,x2,y2,color1,color2" (e.g., "10,12,125,123,#AABBCC,#DDEEFF")
94func genImageParamsString(r *rand.Rand) string {
95 x1 := x1Min + r.Uint64N(x1Range)
96 y1 := y1Min + r.Uint64N(y1Range)
97 x2 := x2Min + r.Uint64N(x2Range)
98 y2 := y2Min + r.Uint64N(y2Range)
99
100 var buf1 [7]byte
101 var buf2 [7]byte
102 buf1[0] = '#'
103 buf2[0] = '#'
104 for i := 1; i < 7; i++ {
105 buf1[i] = charset[r.IntN(16)]
106 buf2[i] = charset[r.IntN(16)]
107 }
108 color1 := string(buf1[:])
109 color2 := string(buf2[:])
110
111 return strconv.Itoa(int(x1)) + "," + strconv.Itoa(int(y1)) + "," +
112 strconv.Itoa(int(x2)) + "," + strconv.Itoa(int(y2)) + "," +
113 color1 + "," + color2
114}
115
116// ImageParams holds parsed and validated image parameters.
117type ImageParams struct {
118 x1, y1, x2, y2 int
119 color1, color2 string
120}
121
122// validateCoordinate checks if a coordinate value is within valid range.
123// Returns error with details about which coordinate failed if out of range.
124func validateCoordinate(value int, min int, max int, name string) error {
125 if value < min || value > max {
126 details := ufmt.Sprintf("%s=%d (expected range: [%d, %d])", name, value, min, max)
127 return makeErrorWithDetails(errInvalidTokenParamsRange, details)
128 }
129 return nil
130}
131
132// parseImageParams parses and validates parameters in one step.
133// Returns parsed ImageParams pointer or error.
134// Returns nil on error to avoid allocating zero-value struct.
135// Expected format: "x1,y1,x2,y2,color1,color2"
136func parseImageParams(s string) (*ImageParams, error) {
137 parts := strings.Split(s, ",")
138 if len(parts) != 6 {
139 return nil, errors.New(errInvalidTokenParams)
140 }
141
142 x1, err := strconv.Atoi(parts[0])
143 if err != nil {
144 return nil, errors.New(errInvalidTokenParams)
145 }
146
147 y1, err := strconv.Atoi(parts[1])
148 if err != nil {
149 return nil, errors.New(errInvalidTokenParams)
150 }
151
152 x2, err := strconv.Atoi(parts[2])
153 if err != nil {
154 return nil, errors.New(errInvalidTokenParams)
155 }
156
157 y2, err := strconv.Atoi(parts[3])
158 if err != nil {
159 return nil, errors.New(errInvalidTokenParams)
160 }
161
162 // Validate coordinate ranges with detailed error messages
163 if err := validateCoordinate(x1, x1Min, x1Max, "x1"); err != nil {
164 return nil, err
165 }
166 if err := validateCoordinate(y1, y1Min, y1Max, "y1"); err != nil {
167 return nil, err
168 }
169 if err := validateCoordinate(x2, x2Min, x2Max, "x2"); err != nil {
170 return nil, err
171 }
172 if err := validateCoordinate(y2, y2Min, y2Max, "y2"); err != nil {
173 return nil, err
174 }
175
176 color1 := parts[4]
177 color2 := parts[5]
178
179 // Validate color format (#XXXXXX)
180 if !isValidHexColor(color1) || !isValidHexColor(color2) {
181 return nil, errors.New(errInvalidColorFormat)
182 }
183
184 return &ImageParams{
185 x1: x1,
186 y1: y1,
187 x2: x2,
188 y2: y2,
189 color1: color1,
190 color2: color2,
191 }, nil
192}
193
194// generateImageURI converts parsed image parameters to a base64-encoded SVG image URI.
195func (params ImageParams) generateImageURI() string {
196 svg := params.generateSVG()
197 sEnc := b64.StdEncoding.EncodeToString([]byte(svg))
198
199 return "data:image/svg+xml;base64," + sEnc
200}
201
202// generateSVG generates SVG image from parsed and validated parameters.
203func (params ImageParams) generateSVG() string {
204 return svgTemplate[0] + strconv.Itoa(params.x1) + svgTemplate[1] + strconv.Itoa(params.y1) +
205 svgTemplate[2] + strconv.Itoa(params.x2) + svgTemplate[3] + strconv.Itoa(params.y2) +
206 svgTemplate[4] + params.color1 + svgTemplate[5] + params.color2 + svgTemplate[6]
207}
208
209// isValidHexColor checks if a string is a valid hex color in #XXXXXX format.
210func isValidHexColor(color string) bool {
211 if len(color) != 7 || color[0] != '#' {
212 return false
213 }
214
215 for i := 1; i < 7; i++ {
216 c := color[i]
217
218 isHex := (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')
219 if !isHex {
220 return false
221 }
222 }
223
224 return true
225}