Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}