Skip to content

Commit ea71a32

Browse files
committed
Update action badge font spacing
1 parent 1318783 commit ea71a32

File tree

3 files changed

+5759
-42
lines changed

3 files changed

+5759
-42
lines changed

build/generate-font-width.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
//go:build ignore
5+
6+
package main
7+
8+
import (
9+
"archive/zip"
10+
"bytes"
11+
"flag"
12+
"fmt"
13+
"go/format"
14+
"io"
15+
"log"
16+
"net/http"
17+
"os"
18+
"strings"
19+
"unicode"
20+
21+
"golang.org/x/image/font"
22+
"golang.org/x/image/font/sfnt"
23+
)
24+
25+
const dejavuZip = "https://github.com/dejavu-fonts/dejavu-fonts/releases/download/version_2_37/dejavu-sans-ttf-2.37.zip"
26+
27+
var flagOut = flag.String("o", "modules/badge/badge_font_width.go", "out")
28+
29+
func main() {
30+
flag.Parse()
31+
32+
ttfFont := extractFont()
33+
34+
str := "map[rune]int32{\n"
35+
for r := rune(0); r <= unicode.MaxRune; r++ {
36+
if !unicode.IsPrint(r) {
37+
continue
38+
}
39+
40+
glyphIndex, err := ttfFont.GlyphIndex(nil, r)
41+
if err != nil {
42+
log.Fatalf("Failed to find glyph for rune %s: %v", string(r), err)
43+
} else if glyphIndex == 0 {
44+
continue
45+
}
46+
47+
advancedWidth, err := ttfFont.GlyphAdvance(nil, glyphIndex, 11, font.HintingNone)
48+
if err != nil {
49+
log.Fatalf("Failed to find font width for rune %s: %v", string(r), err)
50+
} else if advancedWidth == 0 {
51+
continue
52+
}
53+
54+
str += fmt.Sprintf("\t%d: %d,\n", r, advancedWidth)
55+
}
56+
str += "}"
57+
58+
data, err := format.Source([]byte(str))
59+
if err != nil {
60+
log.Fatalf("Failed to format generated code: %v", err)
61+
}
62+
63+
err = os.WriteFile(*flagOut, []byte(fmt.Sprintf(hdr, dejavuZip, data)), 0o644)
64+
if err != nil {
65+
log.Fatalf("Failed to write file: %v", err)
66+
}
67+
}
68+
69+
func extractFont() *sfnt.Font {
70+
resp, err := http.Get(dejavuZip)
71+
if err != nil {
72+
log.Fatalf("Failed to download archive: %v", err)
73+
}
74+
defer resp.Body.Close()
75+
76+
buf, err := io.ReadAll(resp.Body)
77+
if err != nil {
78+
log.Fatalf("Failed to read download archive: %v", err)
79+
}
80+
81+
bufReader := io.NewSectionReader(bytes.NewReader(buf), 0, int64(len(buf)))
82+
83+
archive, err := zip.NewReader(bufReader, int64(len(buf)))
84+
if err != nil {
85+
log.Fatalf("Failed to unzip archive: %v", err)
86+
}
87+
88+
var fontBytes []byte
89+
for _, file := range archive.File {
90+
if strings.HasSuffix(file.Name, ".ttf") {
91+
ttfFile, err := file.Open()
92+
if err != nil {
93+
log.Fatalf("Failed to extract font from archive: %v", err)
94+
}
95+
96+
if fontBytes, err = io.ReadAll(ttfFile); err != nil {
97+
log.Fatalf("Failed to extract font from archive: %v", err)
98+
}
99+
100+
break
101+
}
102+
}
103+
if fontBytes == nil {
104+
log.Fatalf("Failed to find font in archive")
105+
}
106+
107+
font, err := sfnt.Parse(fontBytes)
108+
if err != nil {
109+
log.Fatalf("Failed to parse font: %v", err)
110+
}
111+
112+
return font
113+
}
114+
115+
const hdr = `// Copyright 2025 The Gitea Authors. All rights reserved.
116+
// SPDX-License-Identifier: MIT
117+
118+
package badge
119+
120+
// Code generated by build/generate-font-width.go. DO NOT EDIT.
121+
// Sourced from %s
122+
var DejaVuFontWidthData = %s
123+
`

modules/badge/badge.go

Lines changed: 37 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -11,65 +11,43 @@ import (
1111
// We use 10x scale to calculate more precisely
1212
// Then scale down to normal size in tmpl file
1313

14-
type Label struct {
15-
text string
16-
width int
17-
}
18-
19-
func (l Label) Text() string {
20-
return l.text
21-
}
22-
23-
func (l Label) Width() int {
24-
return l.width
25-
}
26-
27-
func (l Label) TextLength() int {
28-
return int(float64(l.width-defaultOffset) * 9.5)
29-
}
30-
31-
func (l Label) X() int {
32-
return l.width*5 + 10
33-
}
34-
35-
type Message struct {
14+
type Text struct {
3615
text string
3716
width int
3817
x int
3918
}
4019

41-
func (m Message) Text() string {
42-
return m.text
20+
func (t Text) Text() string {
21+
return t.text
4322
}
4423

45-
func (m Message) Width() int {
46-
return m.width
24+
func (t Text) Width() int {
25+
return t.width
4726
}
4827

49-
func (m Message) X() int {
50-
return m.x
28+
func (t Text) X() int {
29+
return t.x
5130
}
5231

53-
func (m Message) TextLength() int {
54-
return int(float64(m.width-defaultOffset) * 9.5)
32+
func (t Text) TextLength() int {
33+
return int(float64(t.width-defaultOffset) * 10)
5534
}
5635

5736
type Badge struct {
5837
Color string
5938
FontSize int
60-
Label Label
61-
Message Message
39+
Label Text
40+
Message Text
6241
}
6342

6443
func (b Badge) Width() int {
6544
return b.Label.width + b.Message.width
6645
}
6746

6847
const (
69-
defaultOffset = 9
70-
defaultFontSize = 11
71-
DefaultColor = "#9f9f9f" // Grey
72-
defaultFontWidth = 7 // approximate speculation
48+
defaultOffset = 10
49+
defaultFontSize = 11
50+
DefaultColor = "#9f9f9f" // Grey
7351
)
7452

7553
var StatusColorMap = map[actions_model.Status]string{
@@ -85,20 +63,37 @@ var StatusColorMap = map[actions_model.Status]string{
8563

8664
// GenerateBadge generates badge with given template
8765
func GenerateBadge(label, message, color string) Badge {
88-
lw := defaultFontWidth*len(label) + defaultOffset
89-
mw := defaultFontWidth*len(message) + defaultOffset
90-
x := lw*10 + mw*5 - 10
66+
lw := calculateTextWidth(label) + defaultOffset
67+
mw := calculateTextWidth(message) + defaultOffset
68+
69+
lx := lw * 5
70+
mx := lw*10 + mw*5 - 10
9171
return Badge{
92-
Label: Label{
72+
Label: Text{
9373
text: label,
9474
width: lw,
75+
x: lx,
9576
},
96-
Message: Message{
77+
Message: Text{
9778
text: message,
9879
width: mw,
99-
x: x,
80+
x: mx,
10081
},
10182
FontSize: defaultFontSize * 10,
10283
Color: color,
10384
}
10485
}
86+
87+
func calculateTextWidth(text string) int {
88+
width := 0
89+
90+
for _, char := range text {
91+
charWidth, ok := DejaVuFontWidthData[char]
92+
if !ok {
93+
charWidth = 0
94+
}
95+
width += int(charWidth)
96+
}
97+
98+
return width
99+
}

0 commit comments

Comments
 (0)