Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

svg lettericons #55

Merged
merged 6 commits into from
Feb 28, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
svg lettericon paths and tests
  • Loading branch information
gurgeous committed Feb 27, 2021
commit 4bfbf94cacfcdec39c13c836ca4e502c118ce7fa
30 changes: 25 additions & 5 deletions besticon/iconserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,12 @@ func iconHandler(w http.ResponseWriter, r *http.Request) {
}
}

redirectPath := lettericon.IconPath(letter, fmt.Sprintf("%d", sizeRange.Perfect), iconColor)
// We support both PNG and SVG fallback. Only return SVG if requested.
format := "png"
if includesString(finder.FormatsAllowed, "svg") {
format = "svg"
}
redirectPath := lettericon.IconPath(letter, fmt.Sprintf("%d", sizeRange.Perfect), iconColor, format)
redirectWithCacheControl(w, r, redirectPath)
}

Expand Down Expand Up @@ -155,15 +160,20 @@ func alliconsHandler(w http.ResponseWriter, r *http.Request) {
}

func lettericonHandler(w http.ResponseWriter, r *http.Request) {
charParam, col, size := lettericon.ParseIconPath(r.URL.Path)
if charParam == "" || col == nil || size <= 0 {
charParam, col, size, format := lettericon.ParseIconPath(r.URL.Path)
if charParam == "" || col == nil || size <= 0 || format == "" {
writeAPIError(w, 400, errors.New("wrong format for lettericons/ path, must look like lettericons/M-144-EFC25D.png"))
return
}

w.Header().Add(contentType, imagePNG)
if format == "svg" {
w.Header().Add(contentType, imageSVG)
lettericon.RenderSVG(charParam, col, w)
} else {
w.Header().Add(contentType, imagePNG)
lettericon.RenderPNG(charParam, col, size, w)
}
addCacheControl(w, oneYear)
lettericon.Render(charParam, col, size, w)
}

func writeAPIError(w http.ResponseWriter, httpStatus int, e error) {
Expand Down Expand Up @@ -198,6 +208,7 @@ const (
contentType = "Content-Type"
applicationJSON = "application/json"
imagePNG = "image/png"
imageSVG = "image/svg+xml"
)

func renderJSONResponse(w http.ResponseWriter, httpStatus int, data interface{}) {
Expand Down Expand Up @@ -431,3 +442,12 @@ func getenvOrFallback(key string, fallbackValue string) string {
}
return fallbackValue
}

func includesString(arr []string, str string) bool {
for _, e := range arr {
if e == str {
return true
}
}
return false
}
30 changes: 29 additions & 1 deletion besticon/iconserver/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,19 @@ func TestGet404IconWithInvalidFallbackColor(t *testing.T) {
assertStringEquals(t, "/lettericons/H-32.png", w.Header().Get("Location"))
}

func TestGetIconWithSVG(t *testing.T) {
req, err := http.NewRequest("GET", "/icons?size=32&url=httpbin.org/status/404&formats=svg", nil)
if err != nil {
log.Fatal(err)
}

w := httptest.NewRecorder()
iconHandler(w, req)

assertStringEquals(t, "302", fmt.Sprintf("%d", w.Code))
assertStringEquals(t, "/lettericons/H-32.svg", w.Header().Get("Location"))
}

func TestGetAllIcons(t *testing.T) {
req, err := http.NewRequest("GET", "/allicons.json?url=apple.com", nil)
if err != nil {
Expand Down Expand Up @@ -173,7 +186,7 @@ func TestGetPopular(t *testing.T) {
assertStringContains(t, w.Body.String(), `github.com`)
}

func TestGetLetterIcon(t *testing.T) {
func TestGetLetterIconPNG(t *testing.T) {
req, err := http.NewRequest("GET", "/lettericons/M-144-EFC25D.png", nil)
if err != nil {
t.Fatal(err)
Expand All @@ -188,6 +201,21 @@ func TestGetLetterIcon(t *testing.T) {
assertIntegerInInterval(t, 1500, 1800, w.Body.Len())
}

func TestGetLetterIconSVG(t *testing.T) {
req, err := http.NewRequest("GET", "/lettericons/M-144-EFC25D.svg", nil)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we even want to allow for the size to be in the path for .svg? I'm thinking the fact the we allow it is kind of ok by Postel's law on the one hand but could lead to misunderstanding one the other (we're not using this).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, let's turn off support for size entirely. We're not generating those with SVG so no need to parse them. Give me a sec here

if err != nil {
t.Fatal(err)
}

w := httptest.NewRecorder()
lettericonHandler(w, req)

assertStringEquals(t, "200", fmt.Sprintf("%d", w.Code))
assertStringEquals(t, "image/svg+xml", w.Header().Get("Content-Type"))
assertStringEquals(t, "max-age=31536000", w.Header().Get("Cache-Control"))
assertStringContains(t, w.Body.String(), `<svg`)
}

func TestGetBadLetterIconPath(t *testing.T) {
req, err := http.NewRequest("GET", "/lettericons/--120.png", nil)
if err != nil {
Expand Down
23 changes: 16 additions & 7 deletions lettericon/lettericon.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"math"
"net/url"
"path"
"path/filepath"
"strconv"
"strings"

Expand Down Expand Up @@ -181,17 +182,17 @@ func ColorFromHex(hex string) (*color.RGBA, error) {
return &col, nil
}

func IconPath(letter string, size string, colr *color.RGBA) string {
func IconPath(letter string, size string, colr *color.RGBA, format string) string {
if letter == "" {
letter = " "
} else {
letter = strings.ToUpper(letter)
}

if colr != nil {
return fmt.Sprintf("/lettericons/%s-%s-%s.png", letter, size, colorfinder.ColorToHex(*colr))
return fmt.Sprintf("/lettericons/%s-%s-%s.%s", letter, size, colorfinder.ColorToHex(*colr), format)
}
return fmt.Sprintf("/lettericons/%s-%s.png", letter, size)
return fmt.Sprintf("/lettericons/%s-%s.%s", letter, size, format)
}

const defaultIconSize = 144
Expand All @@ -200,14 +201,22 @@ const defaultIconSize = 144
const maxIconSize = 256

// path is like: lettericons/M-144-EFC25D.png
func ParseIconPath(fullpath string) (string, *color.RGBA, int) {
func ParseIconPath(fullpath string) (string, *color.RGBA, int, string) {
fullpath = percentDecode(fullpath)

_, filename := path.Split(fullpath)
filename = strings.TrimSuffix(filename, ".png")

// what is the format?
format := filepath.Ext(filename)
if !(format == ".png" || format == ".svg") {
return "", nil, -1, ""
}
filename = strings.TrimSuffix(filename, format)
format = format[1:] // remove period

params := strings.Split(filename, "-")
if len(params) < 1 || len(params[0]) < 1 {
return "", nil, -1
return "", nil, -1, ""
}

charParam := firstRune(params[0])
Expand All @@ -233,7 +242,7 @@ func ParseIconPath(fullpath string) (string, *color.RGBA, int) {
col = DefaultBackgroundColor
}

return charParam, col, size
return charParam, col, size, format
}

func MainLetterFromURL(URL string) string {
Expand Down
2 changes: 1 addition & 1 deletion lettericon/lettericon/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func main() {
os.Exit(1)
}

err = lettericon.Render(*letter, col, *width, f)
err = lettericon.RenderPNG(*letter, col, *width, f)
if err != nil {
os.Exit(1)
}
Expand Down
30 changes: 17 additions & 13 deletions lettericon/lettericon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func renderPNGBytes(letter string, bgColor color.Color, width int) ([]byte, erro
return b.Bytes(), nil
}

func BenchmarkRender(b *testing.B) {
func BenchmarkRenderPNG(b *testing.B) {
RenderPNG("X", DefaultBackgroundColor, 144, ioutil.Discard) // warmup
b.ResetTimer()

Expand All @@ -156,46 +156,50 @@ func TestMainLetterFromURL(t *testing.T) {
}

func TestIconPath(t *testing.T) {
assertEquals(t, "/lettericons/A-120-000000.png", IconPath("a", "120", &color.RGBA{0, 0, 0, 0}))
assertEquals(t, "/lettericons/Z-100-640ac8.png", IconPath("z", "100", &color.RGBA{100, 10, 200, 0}))
assertEquals(t, "/lettericons/A-120-000000.png", IconPath("a", "120", &color.RGBA{0, 0, 0, 0}, "png"))
assertEquals(t, "/lettericons/Z-100-640ac8.svg", IconPath("z", "100", &color.RGBA{100, 10, 200, 0}, "svg"))
}

func TestParseIconPath(t *testing.T) {
var char string
var col *color.RGBA
var size int
var format string

char, _, _ = ParseIconPath("lettericons/")
char, _, _, _ = ParseIconPath("lettericons/")
assertEquals(t, "", char)

char, _, _ = ParseIconPath("lettericons/A")
assertEquals(t, "A", char)

char, _, _ = ParseIconPath("lettericons/B.png")
char, _, _, _ = ParseIconPath("lettericons/B.png")
assertEquals(t, "B", char)

char, _, size = ParseIconPath("lettericons/C-120.png")
char, _, size, _ = ParseIconPath("lettericons/C-120.png")
assertEquals(t, "C", char)
assertEquals(t, 120, size)

char, _, size = ParseIconPath("lettericons/%D1%84-120.png") //ф-120.png
char, _, size, _ = ParseIconPath("lettericons/%D1%84-120.png") //ф-120.png
assertEquals(t, `ф`, char)
assertEquals(t, 120, size)

char, col, size = ParseIconPath("lettericons/D-150-ababab.png")
char, col, size, _ = ParseIconPath("lettericons/D-150-ababab.png")
assertEquals(t, "D", char)
assertEquals(t, 150, size)
assertEquals(t, &color.RGBA{171, 171, 171, 0xff}, col)

char, col, size = ParseIconPath("lettericons/D-256-ababab.png")
char, col, size, _ = ParseIconPath("lettericons/D-256-ababab.png")
assertEquals(t, "D", char)
assertEquals(t, 256, size)
assertEquals(t, &color.RGBA{171, 171, 171, 0xff}, col)

char, col, size = ParseIconPath("lettericons/D-1024-ababab.png")
char, col, size, _ = ParseIconPath("lettericons/D-1024-ababab.png")
assertEquals(t, "D", char)
assertEquals(t, 256, size)
assertEquals(t, &color.RGBA{171, 171, 171, 0xff}, col)

// test format
_, _, _, format = ParseIconPath("lettericons/B.png")
assertEquals(t, "png", format)
_, _, _, format = ParseIconPath("lettericons/B.svg")
assertEquals(t, "svg", format)
}

func assertColor(t *testing.T, hexColor string, expectedColor color.Color) {
Expand Down