├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── badge.go
├── badge_test.go
├── color.go
├── example
├── .gitignore
└── example.go
├── fonts
├── Bitstream Vera License.txt
├── vera.go
└── vera.ttf
├── go.mod
├── go.sum
├── style.go
├── utils.go
├── utils_test.go
└── version.go
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 | go:
3 | - 1.6
4 | - tip
5 | script: go test -v -bench=Render -benchmem
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Vladimir Varankin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # go-badge [](https://godoc.org/github.com/narqo/go-badge)
2 |
3 | go-badge is a library to render shield badges to SVG.
4 |
5 | ## Installation
6 |
7 | Using `go get`
8 |
9 | ```
10 | go get github.com/narqo/go-badge
11 | ```
12 |
13 | ## Usage
14 |
15 | ```go
16 | package main
17 |
18 | import (
19 | "fmt"
20 | "os"
21 |
22 | "github.com/narqo/go-badge"
23 | )
24 |
25 | func main() {
26 | err := badge.Render("godoc", "reference", "#5272B4", os.Stdout)
27 | if err != nil {
28 | panic(err)
29 | }
30 |
31 | badge, err := badge.RenderBytes("godoc", "reference", "#5272B4")
32 | if err != nil {
33 | panic(err)
34 | }
35 | fmt.Printf("%s", badge)
36 | }
37 |
38 | ```
39 |
40 | Hope `example/` directory will have more examples in future.
41 |
42 | ## Contribution and Feedback
43 |
44 | Contributing is more than welcome. Create an issue if you see any problem in the code or send a PR with fixes if you'd like.
45 |
46 | ## License
47 |
48 | MIT
49 |
50 | ---
51 |
52 | All the kudos should go to the great [Shields.io](https://github.com/badges/shields) specification project.
53 |
--------------------------------------------------------------------------------
/badge.go:
--------------------------------------------------------------------------------
1 | package badge
2 |
3 | import (
4 | "bytes"
5 | "html/template"
6 | "io"
7 | "sync"
8 |
9 | "github.com/golang/freetype/truetype"
10 | "github.com/narqo/go-badge/fonts"
11 | "golang.org/x/image/font"
12 | )
13 |
14 | type badge struct {
15 | Subject string
16 | Status string
17 | Color Color
18 | Bounds bounds
19 | }
20 |
21 | type bounds struct {
22 | // SubjectDx is the width of subject string of the badge.
23 | SubjectDx float64
24 | SubjectX float64
25 | // StatusDx is the width of status string of the badge.
26 | StatusDx float64
27 | StatusX float64
28 | }
29 |
30 | func (b bounds) Dx() float64 {
31 | return b.SubjectDx + b.StatusDx
32 | }
33 |
34 | type badgeDrawer struct {
35 | fd *font.Drawer
36 | tmpl *template.Template
37 | mutex *sync.Mutex
38 | }
39 |
40 | func (d *badgeDrawer) Render(subject, status string, color Color, w io.Writer) error {
41 | d.mutex.Lock()
42 | subjectDx := d.measureString(subject)
43 | statusDx := d.measureString(status)
44 | d.mutex.Unlock()
45 |
46 | bdg := badge{
47 | Subject: subject,
48 | Status: status,
49 | Color: color,
50 | Bounds: bounds{
51 | SubjectDx: subjectDx,
52 | SubjectX: subjectDx/2.0 + 1,
53 | StatusDx: statusDx,
54 | StatusX: subjectDx + statusDx/2.0 - 1,
55 | },
56 | }
57 | return d.tmpl.Execute(w, bdg)
58 | }
59 |
60 | func (d *badgeDrawer) RenderBytes(subject, status string, color Color) ([]byte, error) {
61 | buf := &bytes.Buffer{}
62 | err := d.Render(subject, status, color, buf)
63 | return buf.Bytes(), err
64 | }
65 |
66 | // shield.io uses Verdana.ttf to measure text width with an extra 10px.
67 | // As we use Vera.ttf, we have to tune this value a little.
68 | const extraDx = 13
69 |
70 | func (d *badgeDrawer) measureString(s string) float64 {
71 | return float64(d.fd.MeasureString(s)>>6) + extraDx
72 | }
73 |
74 | // Render renders a badge of the given color, with given subject and status to w.
75 | func Render(subject, status string, color Color, w io.Writer) error {
76 | return drawer.Render(subject, status, color, w)
77 | }
78 |
79 | // RenderBytes renders a badge of the given color, with given subject and status to bytes.
80 | func RenderBytes(subject, status string, color Color) ([]byte, error) {
81 | return drawer.RenderBytes(subject, status, color)
82 | }
83 |
84 | const (
85 | dpi = 72
86 | fontsize = 11
87 | )
88 |
89 | var drawer *badgeDrawer
90 |
91 | func init() {
92 | drawer = &badgeDrawer{
93 | fd: mustNewFontDrawer(fontsize, dpi),
94 | tmpl: template.Must(template.New("flat-template").Parse(flatTemplate)),
95 | mutex: &sync.Mutex{},
96 | }
97 | }
98 |
99 | func mustNewFontDrawer(size, dpi float64) *font.Drawer {
100 | ttf, err := truetype.Parse(fonts.VeraSans)
101 | if err != nil {
102 | panic(err)
103 | }
104 | return &font.Drawer{
105 | Face: truetype.NewFace(ttf, &truetype.Options{
106 | Size: size,
107 | DPI: dpi,
108 | Hinting: font.HintingFull,
109 | }),
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/badge_test.go:
--------------------------------------------------------------------------------
1 | package badge
2 |
3 | import (
4 | "bytes"
5 | "html/template"
6 | "io/ioutil"
7 | "strings"
8 | "sync"
9 | "testing"
10 | )
11 |
12 | func TestBadgeDrawerRender(t *testing.T) {
13 | mockTemplate := strings.TrimSpace(`
14 | {{.Subject}},{{.Status}},{{.Color}},{{with .Bounds}}{{.SubjectX}},{{.SubjectDx}},{{.StatusX}},{{.StatusDx}},{{.Dx}}{{end}}
15 | `)
16 | mockFontSize := 11.0
17 | mockDPI := 72.0
18 |
19 | d := &badgeDrawer{
20 | fd: mustNewFontDrawer(mockFontSize, mockDPI),
21 | tmpl: template.Must(template.New("mock-template").Parse(mockTemplate)),
22 | mutex: &sync.Mutex{},
23 | }
24 |
25 | output := "XXX,YYY,#c0c0c0,18,34,50,34,68"
26 |
27 | var buf bytes.Buffer
28 | err := d.Render("XXX", "YYY", "#c0c0c0", &buf)
29 | if err != nil {
30 | t.Errorf("unexpected error: %s", err)
31 | }
32 | result := buf.String()
33 | if result != output {
34 | t.Errorf("expect %q got %q", output, result)
35 | }
36 | }
37 |
38 | func TestBadgeDrawerRenderBytes(t *testing.T) {
39 | mockTemplate := strings.TrimSpace(`
40 | {{.Subject}},{{.Status}},{{.Color}},{{with .Bounds}}{{.SubjectX}},{{.SubjectDx}},{{.StatusX}},{{.StatusDx}},{{.Dx}}{{end}}
41 | `)
42 | mockFontSize := 11.0
43 | mockDPI := 72.0
44 |
45 | d := &badgeDrawer{
46 | fd: mustNewFontDrawer(mockFontSize, mockDPI),
47 | tmpl: template.Must(template.New("mock-template").Parse(mockTemplate)),
48 | mutex: &sync.Mutex{},
49 | }
50 |
51 | output := "XXX,YYY,#c0c0c0,18,34,50,34,68"
52 |
53 | bytes, err := d.RenderBytes("XXX", "YYY", "#c0c0c0")
54 | if err != nil {
55 | t.Errorf("unexpected error: %s", err)
56 | }
57 | if string(bytes) != output {
58 | t.Errorf("expect %q got %q", output, string(bytes))
59 | }
60 | }
61 |
62 | func BenchmarkRender(b *testing.B) {
63 | // warm up
64 | Render("XXX", "YYY", ColorBlue, ioutil.Discard)
65 | b.ResetTimer()
66 | for i := 0; i < b.N; i++ {
67 | err := Render("XXX", "YYY", ColorBlue, ioutil.Discard)
68 | if err != nil {
69 | b.Fatal(err)
70 | }
71 | }
72 | }
73 |
74 | func BenchmarkRenderParallel(b *testing.B) {
75 | b.RunParallel(func(pb *testing.PB) {
76 | for pb.Next() {
77 | err := Render("XXX", "YYY", ColorBlue, ioutil.Discard)
78 | if err != nil {
79 | b.Fatal(err)
80 | }
81 | }
82 | })
83 | }
84 |
--------------------------------------------------------------------------------
/color.go:
--------------------------------------------------------------------------------
1 | package badge
2 |
3 | // Color represents color of the badge.
4 | type Color string
5 |
6 | // ColorScheme contains named colors that could be used to render the badge.
7 | var ColorScheme = map[string]string{
8 | "brightgreen": "#4c1",
9 | "green": "#97ca00",
10 | "yellow": "#dfb317",
11 | "yellowgreen": "#a4a61d",
12 | "orange": "#fe7d37",
13 | "red": "#e05d44",
14 | "blue": "#007ec6",
15 | "grey": "#555",
16 | "gray": "#555",
17 | "lightgrey": "#9f9f9f",
18 | "lightgray": "#9f9f9f",
19 | }
20 |
21 | // Standard colors.
22 | const (
23 | ColorBrightgreen = Color("brightgreen")
24 | ColorGreen = Color("green")
25 | ColorYellow = Color("yellow")
26 | ColorYellowgreen = Color("yellowgreen")
27 | ColorOrange = Color("orange")
28 | ColorRed = Color("red")
29 | ColorBlue = Color("blue")
30 | ColorGrey = Color("grey")
31 | ColorGray = Color("gray")
32 | ColorLightgrey = Color("lightgrey")
33 | ColorLightgray = Color("lightgray")
34 | )
35 |
36 | func (c Color) String() string {
37 | color, ok := ColorScheme[string(c)]
38 | if ok {
39 | return color
40 | } else {
41 | return string(c)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | /example
2 | /example.svg
3 |
--------------------------------------------------------------------------------
/example/example.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "log"
7 | "os"
8 |
9 | "github.com/narqo/go-badge"
10 | )
11 |
12 | var (
13 | subject = flag.String("subject", "", "Badge subject")
14 | status = flag.String("status", "", "Badge status")
15 | color = flag.String("color", "blue", "Badge color")
16 | )
17 |
18 | func main() {
19 | flag.Parse()
20 | err := badge.Render(*subject, *status, badge.Color(*color), os.Stdout)
21 | if err != nil {
22 | log.Fatal(err)
23 | }
24 |
25 | badge, err := badge.RenderBytes(*subject, *status, badge.Color(*color))
26 | if err != nil {
27 | panic(err)
28 | }
29 | fmt.Printf("%s", badge)
30 | }
31 |
--------------------------------------------------------------------------------
/fonts/Bitstream Vera License.txt:
--------------------------------------------------------------------------------
1 | Bitstream Vera Fonts Copyright
2 |
3 | The fonts have a generous copyright, allowing derivative works (as
4 | long as "Bitstream" or "Vera" are not in the names), and full
5 | redistribution (so long as they are not *sold* by themselves). They
6 | can be be bundled, redistributed and sold with any software.
7 |
8 | The fonts are distributed under the following copyright:
9 |
10 | Copyright
11 | =========
12 |
13 | Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream
14 | Vera is a trademark of Bitstream, Inc.
15 |
16 | Permission is hereby granted, free of charge, to any person obtaining
17 | a copy of the fonts accompanying this license ("Fonts") and associated
18 | documentation files (the "Font Software"), to reproduce and distribute
19 | the Font Software, including without limitation the rights to use,
20 | copy, merge, publish, distribute, and/or sell copies of the Font
21 | Software, and to permit persons to whom the Font Software is furnished
22 | to do so, subject to the following conditions:
23 |
24 | The above copyright and trademark notices and this permission notice
25 | shall be included in all copies of one or more of the Font Software
26 | typefaces.
27 |
28 | The Font Software may be modified, altered, or added to, and in
29 | particular the designs of glyphs or characters in the Fonts may be
30 | modified and additional glyphs or characters may be added to the
31 | Fonts, only if the fonts are renamed to names not containing either
32 | the words "Bitstream" or the word "Vera".
33 |
34 | This License becomes null and void to the extent applicable to Fonts
35 | or Font Software that has been modified and is distributed under the
36 | "Bitstream Vera" names.
37 |
38 | The Font Software may be sold as part of a larger software package but
39 | no copy of one or more of the Font Software typefaces may be sold by
40 | itself.
41 |
42 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
43 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
44 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
45 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
46 | BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR
47 | OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL,
48 | OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR
49 | OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT
50 | SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
51 |
52 | Except as contained in this notice, the names of Gnome, the Gnome
53 | Foundation, and Bitstream Inc., shall not be used in advertising or
54 | otherwise to promote the sale, use or other dealings in this Font
55 | Software without prior written authorization from the Gnome Foundation
56 | or Bitstream Inc., respectively. For further information, contact:
57 | fonts at gnome dot org.
58 |
59 | Copyright FAQ
60 | =============
61 |
62 | 1. I don't understand the resale restriction... What gives?
63 |
64 | Bitstream is giving away these fonts, but wishes to ensure its
65 | competitors can't just drop the fonts as is into a font sale system
66 | and sell them as is. It seems fair that if Bitstream can't make money
67 | from the Bitstream Vera fonts, their competitors should not be able to
68 | do so either. You can sell the fonts as part of any software package,
69 | however.
70 |
71 | 2. I want to package these fonts separately for distribution and
72 | sale as part of a larger software package or system. Can I do so?
73 |
74 | Yes. A RPM or Debian package is a "larger software package" to begin
75 | with, and you aren't selling them independently by themselves.
76 | See 1. above.
77 |
78 | 3. Are derivative works allowed?
79 | Yes!
80 |
81 | 4. Can I change or add to the font(s)?
82 | Yes, but you must change the name(s) of the font(s).
83 |
84 | 5. Under what terms are derivative works allowed?
85 |
86 | You must change the name(s) of the fonts. This is to ensure the
87 | quality of the fonts, both to protect Bitstream and Gnome. We want to
88 | ensure that if an application has opened a font specifically of these
89 | names, it gets what it expects (though of course, using fontconfig,
90 | substitutions could still could have occurred during font
91 | opening). You must include the Bitstream copyright. Additional
92 | copyrights can be added, as per copyright law. Happy Font Hacking!
93 |
94 | 6. If I have improvements for Bitstream Vera, is it possible they might get
95 | adopted in future versions?
96 |
97 | Yes. The contract between the Gnome Foundation and Bitstream has
98 | provisions for working with Bitstream to ensure quality additions to
99 | the Bitstream Vera font family. Please contact us if you have such
100 | additions. Note, that in general, we will want such additions for the
101 | entire family, not just a single font, and that you'll have to keep
102 | both Gnome and Jim Lyles, Vera's designer, happy! To make sense to add
103 | glyphs to the font, they must be stylistically in keeping with Vera's
104 | design. Vera cannot become a "ransom note" font. Jim Lyles will be
105 | providing a document describing the design elements used in Vera, as a
106 | guide and aid for people interested in contributing to Vera.
107 |
108 | 7. I want to sell a software package that uses these fonts: Can I do so?
109 |
110 | Sure. Bundle the fonts with your software and sell your software
111 | with the fonts. That is the intent of the copyright.
112 |
113 | 8. If applications have built the names "Bitstream Vera" into them,
114 | can I override this somehow to use fonts of my choosing?
115 |
116 | This depends on exact details of the software. Most open source
117 | systems and software (e.g., Gnome, KDE, etc.) are now converting to
118 | use fontconfig (see www.fontconfig.org) to handle font configuration,
119 | selection and substitution; it has provisions for overriding font
120 | names and subsituting alternatives. An example is provided by the
121 | supplied local.conf file, which chooses the family Bitstream Vera for
122 | "sans", "serif" and "monospace". Other software (e.g., the XFree86
123 | core server) has other mechanisms for font substitution.
--------------------------------------------------------------------------------
/fonts/vera.go:
--------------------------------------------------------------------------------
1 | package fonts
2 |
3 | import (
4 | _ "embed"
5 | )
6 |
7 | // VeraSans is vera.ttf font inlined to the bytes slice.
8 | //
9 | //go:embed vera.ttf
10 | var VeraSans []byte
11 |
--------------------------------------------------------------------------------
/fonts/vera.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/narqo/go-badge/c9a75c019a59408f64f8ebcfa2419bb4ea707231/fonts/vera.ttf
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/narqo/go-badge
2 |
3 | go 1.17
4 |
5 | require (
6 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
7 | golang.org/x/image v0.0.0-20211028202545-6944b10bf410
8 | )
9 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/golang/freetype v0.0.0-20160221112527-9ce4eec92a4b h1:g/ACZ897P/JC5EnPl0nas+CTm7BAXv6QA/5sBxm2Umw=
2 | github.com/golang/freetype v0.0.0-20160221112527-9ce4eec92a4b/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
3 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
4 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
5 | golang.org/x/image v0.0.0-20160107224602-7c492694a644 h1:gDX87IHVAWzhS+GuswKlPHa5jnZxbtVTojgccecF6Pw=
6 | golang.org/x/image v0.0.0-20160107224602-7c492694a644/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
7 | golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
8 | golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
9 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
10 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
11 |
--------------------------------------------------------------------------------
/style.go:
--------------------------------------------------------------------------------
1 | package badge
2 |
3 | var flatTemplate = stripXmlWhitespace(`
4 |
27 | `)
28 |
--------------------------------------------------------------------------------
/utils.go:
--------------------------------------------------------------------------------
1 | package badge
2 |
3 | import (
4 | "regexp"
5 | "strings"
6 | )
7 |
8 | // https://github.com/badges/shields/blob/b6be37d277b64a90bde98ca10446c9d433a56681/badge-maker/lib/xml.js#L7
9 | func stripXmlWhitespace(xml string) string {
10 | return strings.TrimSpace(regexp.MustCompile(`<\s+`).ReplaceAllString(regexp.MustCompile(`>\s+`).ReplaceAllString(xml, ">"), "<"))
11 | }
12 |
--------------------------------------------------------------------------------
/utils_test.go:
--------------------------------------------------------------------------------
1 | package badge
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | // Keep newlines, but clean up whitespace
8 | func TestStripXmlWhitespace(t *testing.T) {
9 | const mock = `
10 |
11 |
12 | aaa
13 |
14 | aaaa
15 |
16 | `
17 | const expected = `aaa
18 | aaaa
19 | `
20 |
21 | if stripXmlWhitespace(mock) != expected {
22 | t.Errorf("stripXmlWhitespace failed")
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/version.go:
--------------------------------------------------------------------------------
1 | package badge
2 |
3 | const VersionString = "v0.3"
4 |
--------------------------------------------------------------------------------