diff --git a/app.go b/app.go new file mode 100644 index 0000000..2a9702c --- /dev/null +++ b/app.go @@ -0,0 +1,132 @@ +package main + +import ( + "bytes" + "crypto/sha1" + "errors" + "fmt" + "net/http" + "strings" + "text/template" + + "github.com/Luzifer/rconfig" + "github.com/gorilla/mux" + "github.com/tdewolff/minify" + "github.com/tdewolff/minify/svg" +) + +const ( + xSpacing = 8 + defaultColor = "4c1" +) + +var ( + cfg = struct { + Port int64 `env:"PORT"` + Listen string `flag:"listen" default:":3000" description:"Port/IP to listen on"` + }{} + serviceHandlers = map[string]serviceHandlerFunction{} +) + +type serviceHandlerFunction func(params []string) (title, text, color string, err error) + +func registerServiceHandler(service string, f serviceHandlerFunction) error { + if _, ok := serviceHandlers[service]; ok { + return errors.New("Duplicate service handler") + } + serviceHandlers[service] = f + return nil +} + +func main() { + rconfig.Parse(&cfg) + if cfg.Port != 0 { + cfg.Listen = fmt.Sprintf(":%d", cfg.Port) + } + + r := mux.NewRouter() + r.HandleFunc("/v1/badge", generateBadge).Methods("GET") + r.HandleFunc("/{service}/{parameters:.*}", generateServiceBadge).Methods("GET") + + http.ListenAndServe(cfg.Listen, r) +} + +func generateServiceBadge(res http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + service := vars["service"] + params := strings.Split(vars["parameters"], "/") + + handler, ok := serviceHandlers[service] + if !ok { + http.Error(res, "Service not found: "+service, http.StatusNotFound) + return + } + + title, text, color, err := handler(params) + if err != nil { + http.Error(res, "Error while executing service: "+err.Error(), http.StatusInternalServerError) + return + } + + renderBadgeToResponse(res, title, text, color) +} + +func generateBadge(res http.ResponseWriter, r *http.Request) { + title := r.URL.Query().Get("title") + text := r.URL.Query().Get("text") + color := r.URL.Query().Get("color") + + if title == "" || text == "" { + http.Error(res, "You must specify parameters 'title' and 'text'.", http.StatusInternalServerError) + return + } + + if color == "" { + color = defaultColor + } + + renderBadgeToResponse(res, title, text, color) +} + +func renderBadgeToResponse(res http.ResponseWriter, title, text, color string) { + badge, eTag := createBadge(title, text, color) + + res.Header().Add("Content-Type", "image/svg+xml") + res.Header().Add("Cache-Control", "public, max-age=31536000") + res.Header().Add("ETag", eTag) + + m := minify.New() + m.AddFunc("image/svg+xml", svg.Minify) + + badge, _ = m.Bytes("image/svg+xml", badge) + + res.Write(badge) +} + +func createBadge(title, text, color string) ([]byte, string) { + var buf bytes.Buffer + + titleW, _ := calculateTextWidth(title) + textW, _ := calculateTextWidth(text) + + width := titleW + textW + 4*xSpacing + + t, _ := Asset("assets/badgeTemplate.tpl") + tpl, _ := template.New("svg").Parse(string(t)) + + tpl.Execute(&buf, map[string]interface{}{ + "Width": width, + "TitleWidth": titleW + 2*xSpacing, + "Title": title, + "Text": text, + "TitleAnchor": titleW/2 + xSpacing, + "TextAnchor": titleW + textW/2 + 3*xSpacing, + "Color": color, + }) + + return buf.Bytes(), generateETag(buf.Bytes()) +} + +func generateETag(in []byte) string { + return fmt.Sprintf("%x", sha1.Sum(in)) +} diff --git a/font.go b/font.go index ad1dd88..0097ea1 100644 --- a/font.go +++ b/font.go @@ -5,7 +5,10 @@ // Copyright 2010 The Freetype-Go Authors. All rights reserved. package main -import "code.google.com/p/freetype-go/freetype/truetype" +import ( + "github.com/golang/freetype/truetype" + "golang.org/x/image/math/fixed" +) const ( fontSize = 11 @@ -23,11 +26,12 @@ func calculateTextWidth(text string) (int, error) { width := 0 prev, hasPrev := truetype.Index(0), false for _, rune := range text { + fUnitsPerEm := fixed.Int26_6(font.FUnitsPerEm()) index := font.Index(rune) if hasPrev { - width += int(font.Kerning(font.FUnitsPerEm(), prev, index)) + width += int(font.Kern(fUnitsPerEm, prev, index)) } - width += int(font.HMetric(font.FUnitsPerEm(), index).AdvanceWidth) + width += int(font.HMetric(fUnitsPerEm, index).AdvanceWidth) prev, hasPrev = index, true } diff --git a/main.go b/main.go deleted file mode 100644 index 8f2f525..0000000 --- a/main.go +++ /dev/null @@ -1,88 +0,0 @@ -package main - -import ( - "bufio" - "bytes" - "fmt" - "net/http" - "os" - - "github.com/alecthomas/template" - "github.com/gorilla/mux" - "github.com/tdewolff/minify" - "github.com/tdewolff/minify/svg" -) - -const ( - xSpacing = 8 -) - -func main() { - port := fmt.Sprintf(":%s", os.Getenv("PORT")) - if port == ":" { - port = ":3000" - } - - http.Handle("/", generateMux()) - http.ListenAndServe(port, nil) -} - -func generateMux() *mux.Router { - r := mux.NewRouter() - r.HandleFunc("/v1/badge", generateBadge).Methods("GET") - - return r -} - -func generateBadge(res http.ResponseWriter, r *http.Request) { - title := r.URL.Query().Get("title") - text := r.URL.Query().Get("text") - color := r.URL.Query().Get("color") - - if title == "" || text == "" { - http.Error(res, "You must specify parameters 'title' and 'text'.", http.StatusInternalServerError) - return - } - - if color == "" { - color = "4c1" - } - - badge := createBadge(title, text, color) - - res.Header().Add("Content-Type", "image/svg+xml") - res.Header().Add("Cache-Control", "public, max-age=31536000") - - m := minify.New() - m.AddFunc("image/svg+xml", svg.Minify) - - badge, _ = minify.Bytes(m, "image/svg+xml", badge) - - res.Write(badge) -} - -func createBadge(title, text, color string) []byte { - var buf bytes.Buffer - bufw := bufio.NewWriter(&buf) - - titleW, _ := calculateTextWidth(title) - textW, _ := calculateTextWidth(text) - - width := titleW + textW + 4*xSpacing - - t, _ := Asset("assets/badgeTemplate.tpl") - tpl, _ := template.New("svg").Parse(string(t)) - - tpl.Execute(bufw, map[string]interface{}{ - "Width": width, - "TitleWidth": titleW + 2*xSpacing, - "Title": title, - "Text": text, - "TitleAnchor": titleW/2 + xSpacing, - "TextAnchor": titleW + textW/2 + 3*xSpacing, - "Color": color, - }) - - bufw.Flush() - return buf.Bytes() -}