fork
Configure Feed
Select the types of activity you want to include in your feed.
Monorepo for Tangled
tangled.org
fork
Configure Feed
Select the types of activity you want to include in your feed.
1package main
2
3import (
4 "bytes"
5 _ "embed"
6 "flag"
7 "fmt"
8 "image"
9 "image/color"
10 "image/png"
11 "os"
12 "path/filepath"
13 "strconv"
14 "strings"
15 "text/template"
16
17 "github.com/srwiley/oksvg"
18 "github.com/srwiley/rasterx"
19 "golang.org/x/image/draw"
20 "tangled.org/core/ico"
21)
22
23func main() {
24 var (
25 size string
26 fillColor string
27 output string
28 templatePath string
29 )
30
31 flag.StringVar(&templatePath, "template", "", "Path to dolly go-html template")
32 flag.StringVar(&size, "size", "512x512", "Output size in format WIDTHxHEIGHT (e.g., 512x512)")
33 flag.StringVar(&fillColor, "color", "#000000", "Fill color in hex format (e.g., #FF5733)")
34 flag.StringVar(&output, "output", "dolly.svg", "Output file path (format detected from extension: .svg, .png, or .ico)")
35 flag.Parse()
36
37 if templatePath == "" {
38 fmt.Fprintf(os.Stderr, "Empty template path")
39 os.Exit(1)
40 }
41
42 width, height, err := parseSize(size)
43 if err != nil {
44 fmt.Fprintf(os.Stderr, "Error parsing size: %v\n", err)
45 os.Exit(1)
46 }
47
48 // Detect format from file extension
49 ext := strings.ToLower(filepath.Ext(output))
50 format := strings.TrimPrefix(ext, ".")
51
52 if format != "svg" && format != "png" && format != "ico" {
53 fmt.Fprintf(os.Stderr, "Invalid file extension: %s. Must be .svg, .png, or .ico\n", ext)
54 os.Exit(1)
55 }
56
57 if fillColor != "currentColor" && !isValidHexColor(fillColor) {
58 fmt.Fprintf(os.Stderr, "Invalid color format: %s. Use hex format like #FF5733\n", fillColor)
59 os.Exit(1)
60 }
61
62 tpl, err := os.ReadFile(templatePath)
63 if err != nil {
64 fmt.Fprintf(os.Stderr, "Failed to read template from path %s: %v\n", templatePath, err)
65 os.Exit(1)
66 }
67
68 svgData, err := dolly(string(tpl), fillColor)
69 if err != nil {
70 fmt.Fprintf(os.Stderr, "Error generating SVG: %v\n", err)
71 os.Exit(1)
72 }
73
74 // Create output directory if it doesn't exist
75 dir := filepath.Dir(output)
76 if dir != "" && dir != "." {
77 if err := os.MkdirAll(dir, 0755); err != nil {
78 fmt.Fprintf(os.Stderr, "Error creating output directory: %v\n", err)
79 os.Exit(1)
80 }
81 }
82
83 switch format {
84 case "svg":
85 err = saveSVG(svgData, output, width, height)
86 case "png":
87 err = savePNG(svgData, output, width, height)
88 case "ico":
89 err = saveICO(svgData, output, width, height)
90 }
91
92 if err != nil {
93 fmt.Fprintf(os.Stderr, "Error saving file: %v\n", err)
94 os.Exit(1)
95 }
96
97 fmt.Printf("Successfully generated %s (%dx%d)\n", output, width, height)
98}
99
100func dolly(tplString, hexColor string) ([]byte, error) {
101 tpl, err := template.New("dolly").Parse(tplString)
102 if err != nil {
103 return nil, err
104 }
105
106 var svgData bytes.Buffer
107 if err := tpl.ExecuteTemplate(&svgData, "fragments/dolly/logo", map[string]any{
108 "FillColor": hexColor,
109 "Classes": "",
110 }); err != nil {
111 return nil, err
112 }
113
114 return svgData.Bytes(), nil
115}
116
117func svgToImage(svgData []byte, w, h int) (image.Image, error) {
118 icon, err := oksvg.ReadIconStream(bytes.NewReader(svgData))
119 if err != nil {
120 return nil, fmt.Errorf("error parsing SVG: %v", err)
121 }
122
123 icon.SetTarget(0, 0, float64(w), float64(h))
124 rgba := image.NewRGBA(image.Rect(0, 0, w, h))
125 draw.Draw(rgba, rgba.Bounds(), &image.Uniform{color.Transparent}, image.Point{}, draw.Src)
126 scanner := rasterx.NewScannerGV(w, h, rgba, rgba.Bounds())
127 raster := rasterx.NewDasher(w, h, scanner)
128 icon.Draw(raster, 1.0)
129
130 return rgba, nil
131}
132
133func parseSize(size string) (int, int, error) {
134 parts := strings.Split(size, "x")
135 if len(parts) != 2 {
136 return 0, 0, fmt.Errorf("invalid size format, use WIDTHxHEIGHT")
137 }
138
139 width, err := strconv.Atoi(parts[0])
140 if err != nil {
141 return 0, 0, fmt.Errorf("invalid width: %v", err)
142 }
143
144 height, err := strconv.Atoi(parts[1])
145 if err != nil {
146 return 0, 0, fmt.Errorf("invalid height: %v", err)
147 }
148
149 if width <= 0 || height <= 0 {
150 return 0, 0, fmt.Errorf("width and height must be positive")
151 }
152
153 return width, height, nil
154}
155
156func isValidHexColor(hex string) bool {
157 if len(hex) != 7 || hex[0] != '#' {
158 return false
159 }
160 _, err := strconv.ParseUint(hex[1:], 16, 32)
161 return err == nil
162}
163
164func saveSVG(svgData []byte, filepath string, _, _ int) error {
165 return os.WriteFile(filepath, svgData, 0644)
166}
167
168func savePNG(svgData []byte, filepath string, width, height int) error {
169 img, err := svgToImage(svgData, width, height)
170 if err != nil {
171 return err
172 }
173
174 f, err := os.Create(filepath)
175 if err != nil {
176 return err
177 }
178 defer f.Close()
179
180 return png.Encode(f, img)
181}
182
183func saveICO(svgData []byte, filepath string, width, height int) error {
184 img, err := svgToImage(svgData, width, height)
185 if err != nil {
186 return err
187 }
188
189 icoData, err := ico.ImageToIco(img)
190 if err != nil {
191 return err
192 }
193
194 return os.WriteFile(filepath, icoData, 0644)
195}