at master 195 lines 4.7 kB view raw
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}