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