package main import ( "encoding/json" "flag" "fmt" "io/fs" "log" "net/http" "os" "path/filepath" "strconv" "strings" "code.ndumas.com/ndumas/badges" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" ) var FONT_PATH = "fonts" type BadgeRenderer struct { Fonts map[string]string } func NewBadgeRenderer(fontDir string) (*BadgeRenderer, error) { var br BadgeRenderer br.Fonts = make(map[string]string) root := os.DirFS(fontDir) err := fs.WalkDir(root, ".", func(path string, d fs.DirEntry, err error) error { if strings.HasSuffix(path, ".ttf") { absFont := filepath.Join(fontDir, path) br.Fonts[filepath.Base(path)] = absFont } return nil }) if err != nil { return &br, fmt.Errorf("error walking for font files: %w", err) } return &br, nil } func (br *BadgeRenderer) listFonts(w http.ResponseWriter, r *http.Request) { style := chi.URLParam(r, "format") switch style { case "json": w.Header().Add("Content-Type", "application/json") enc := json.NewEncoder(w) err := enc.Encode(br.Fonts) if err != nil { w.WriteHeader(400) fmt.Fprintf(w, "couldn't encode font list to json: %s\n", err) } } } func (br *BadgeRenderer) renderBadge(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() if len(q["label"]) < 1 { w.WriteHeader(400) fmt.Fprintf(w, "please provide a label") } label := q["label"][0] if len(q["message"]) < 1 { w.WriteHeader(400) fmt.Fprintf(w, "please provide a message") } message := q["message"][0] if len(q["color"]) < 1 { w.WriteHeader(400) fmt.Fprintf(w, "please provide a color") } color := q["color"][0] if len(q["size"]) < 1 { w.WriteHeader(400) fmt.Fprintf(w, "please provide a size") } size, err := strconv.ParseInt(q["size"][0], 10, 64) if err != nil { w.WriteHeader(400) fmt.Fprintf(w, "could not convert size to integer: %s\n", err) return } if len(q["font"]) < 1 { w.WriteHeader(400) fmt.Fprintf(w, "please provide a font name") } font, ok := br.Fonts[q["font"][0]] if !ok { w.WriteHeader(400) fmt.Fprintf(w, "unknown font selection\n") } bg, err := badge.NewGenerator(font, int(size)) if err != nil { w.WriteHeader(400) fmt.Fprintf(w, "error instantitating generator: %s\n", err) return } w.Header().Add("Content-Type", "image/svg+xml") switch q["style"][0] { case "flat": fmt.Fprintf(w, "%s", bg.GenerateFlat(label, message, color)) case "flatsimple": fmt.Fprintf(w, "%s", bg.GenerateFlatSimple(message, color)) case "flatsquare": fmt.Fprintf(w, "%s", bg.GenerateFlatSquare(label, message, color)) case "flatsquaresimple": fmt.Fprintf(w, "%s", bg.GenerateFlatSquareSimple(message, color)) case "plastic": fmt.Fprintf(w, "%s", bg.GeneratePlastic(label, message, color)) case "plasticsimple": fmt.Fprintf(w, "%s", bg.GeneratePlasticSimple(message, color)) default: w.WriteHeader(400) fmt.Fprintf(w, "no matching style found\n") } } func main() { var ( port int fontsDir string ) flag.IntVar(&port, "port", 8484, "http listening port") flag.StringVar(&fontsDir, "fonts", "fonts/", "directory containing ttf files") flag.Parse() absFontDir, err := filepath.Abs(fontsDir) if err != nil { log.Fatalf("error building absolute font dir path: %s\n", err) } log.Println("absFontDir:", absFontDir) br, err := NewBadgeRenderer(absFontDir) if err != nil { fmt.Println("couldn't create renderer", err) return } r := chi.NewRouter() r.Use(middleware.Logger) // r.Use(middleware.Recoverer) r.Use(middleware.Compress(5, "image/svg+xml")) r.Get("/badge", br.renderBadge) r.Get("/fonts/{format}", br.listFonts) http.ListenAndServe(fmt.Sprintf(":%d", port), r) }