Skip to content

Commit bf9500b

Browse files
Update action status badge layout (#34018)
The current action status badge are looking different from most other badges renders, which is especially noticeable when using them along with other badges. This PR updates the action badges to match the commonly used badges from other providers. --------- Co-authored-by: wxiaoguang <[email protected]>
1 parent 0d2607a commit bf9500b

File tree

7 files changed

+413
-136
lines changed

7 files changed

+413
-136
lines changed

modules/badge/badge.go

+51-44
Original file line numberDiff line numberDiff line change
@@ -4,72 +4,56 @@
44
package badge
55

66
import (
7+
"strings"
8+
"unicode"
9+
710
actions_model "code.gitea.io/gitea/models/actions"
811
)
912

1013
// The Badge layout: |offset|label|message|
1114
// We use 10x scale to calculate more precisely
1215
// Then scale down to normal size in tmpl file
1316

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 {
17+
type Text struct {
3618
text string
3719
width int
3820
x int
3921
}
4022

41-
func (m Message) Text() string {
42-
return m.text
23+
func (t Text) Text() string {
24+
return t.text
4325
}
4426

45-
func (m Message) Width() int {
46-
return m.width
27+
func (t Text) Width() int {
28+
return t.width
4729
}
4830

49-
func (m Message) X() int {
50-
return m.x
31+
func (t Text) X() int {
32+
return t.x
5133
}
5234

53-
func (m Message) TextLength() int {
54-
return int(float64(m.width-defaultOffset) * 9.5)
35+
func (t Text) TextLength() int {
36+
return int(float64(t.width-defaultOffset) * 10)
5537
}
5638

5739
type Badge struct {
58-
Color string
59-
FontSize int
60-
Label Label
61-
Message Message
40+
IDPrefix string
41+
FontFamily string
42+
Color string
43+
FontSize int
44+
Label Text
45+
Message Text
6246
}
6347

6448
func (b Badge) Width() int {
6549
return b.Label.width + b.Message.width
6650
}
6751

6852
const (
69-
defaultOffset = 9
70-
defaultFontSize = 11
71-
DefaultColor = "#9f9f9f" // Grey
72-
defaultFontWidth = 7 // approximate speculation
53+
defaultOffset = 10
54+
defaultFontSize = 11
55+
DefaultColor = "#9f9f9f" // Grey
56+
DefaultFontFamily = "DejaVu Sans,Verdana,Geneva,sans-serif"
7357
)
7458

7559
var StatusColorMap = map[actions_model.Status]string{
@@ -85,20 +69,43 @@ var StatusColorMap = map[actions_model.Status]string{
8569

8670
// GenerateBadge generates badge with given template
8771
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
72+
lw := calculateTextWidth(label) + defaultOffset
73+
mw := calculateTextWidth(message) + defaultOffset
74+
75+
lx := lw * 5
76+
mx := lw*10 + mw*5 - 10
9177
return Badge{
92-
Label: Label{
78+
FontFamily: DefaultFontFamily,
79+
Label: Text{
9380
text: label,
9481
width: lw,
82+
x: lx,
9583
},
96-
Message: Message{
84+
Message: Text{
9785
text: message,
9886
width: mw,
99-
x: x,
87+
x: mx,
10088
},
10189
FontSize: defaultFontSize * 10,
10290
Color: color,
10391
}
10492
}
93+
94+
func calculateTextWidth(text string) int {
95+
width := 0
96+
widthData := DejaVuGlyphWidthData()
97+
for _, char := range strings.TrimSpace(text) {
98+
charWidth, ok := widthData[char]
99+
if !ok {
100+
// use the width of 'm' in case of missing glyph width data for a printable character
101+
if unicode.IsPrint(char) {
102+
charWidth = widthData['m']
103+
} else {
104+
charWidth = 0
105+
}
106+
}
107+
width += int(charWidth)
108+
}
109+
110+
return width
111+
}

modules/badge/badge_glyph_width.go

+208
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package badge
5+
6+
import "sync"
7+
8+
// DejaVuGlyphWidthData is generated by `sfnt.Face.GlyphAdvance(nil, <rune>, 11, font.HintingNone)` with DejaVu Sans
9+
// v2.37 (https://github.com/dejavu-fonts/dejavu-fonts/releases/download/version_2_37/dejavu-sans-ttf-2.37.zip).
10+
//
11+
// Fonts defined in "DefaultFontFamily" all have similar widths (including "DejaVu Sans"),
12+
// and these widths are fixed and don't seem to change.
13+
//
14+
// A devtest page "/devtest/badge-actions-svg" could be used to check the rendered images.
15+
16+
var DejaVuGlyphWidthData = sync.OnceValue(func() map[rune]uint8 {
17+
return map[rune]uint8{
18+
32: 3,
19+
33: 4,
20+
34: 5,
21+
35: 9,
22+
36: 7,
23+
37: 10,
24+
38: 9,
25+
39: 3,
26+
40: 4,
27+
41: 4,
28+
42: 6,
29+
43: 9,
30+
44: 3,
31+
45: 4,
32+
46: 3,
33+
47: 4,
34+
48: 7,
35+
49: 7,
36+
50: 7,
37+
51: 7,
38+
52: 7,
39+
53: 7,
40+
54: 7,
41+
55: 7,
42+
56: 7,
43+
57: 7,
44+
58: 4,
45+
59: 4,
46+
60: 9,
47+
61: 9,
48+
62: 9,
49+
63: 6,
50+
64: 11,
51+
65: 8,
52+
66: 8,
53+
67: 8,
54+
68: 8,
55+
69: 7,
56+
70: 6,
57+
71: 9,
58+
72: 8,
59+
73: 3,
60+
74: 3,
61+
75: 7,
62+
76: 6,
63+
77: 9,
64+
78: 8,
65+
79: 9,
66+
80: 7,
67+
81: 9,
68+
82: 8,
69+
83: 7,
70+
84: 7,
71+
85: 8,
72+
86: 8,
73+
87: 11,
74+
88: 8,
75+
89: 7,
76+
90: 8,
77+
91: 4,
78+
92: 4,
79+
93: 4,
80+
94: 9,
81+
95: 6,
82+
96: 6,
83+
97: 7,
84+
98: 7,
85+
99: 6,
86+
100: 7,
87+
101: 7,
88+
102: 4,
89+
103: 7,
90+
104: 7,
91+
105: 3,
92+
106: 3,
93+
107: 6,
94+
108: 3,
95+
109: 11,
96+
110: 7,
97+
111: 7,
98+
112: 7,
99+
113: 7,
100+
114: 5,
101+
115: 6,
102+
116: 4,
103+
117: 7,
104+
118: 7,
105+
119: 9,
106+
120: 7,
107+
121: 7,
108+
122: 6,
109+
123: 7,
110+
124: 4,
111+
125: 7,
112+
126: 9,
113+
161: 4,
114+
162: 7,
115+
163: 7,
116+
164: 7,
117+
165: 7,
118+
166: 4,
119+
167: 6,
120+
168: 6,
121+
169: 11,
122+
170: 5,
123+
171: 7,
124+
172: 9,
125+
174: 11,
126+
175: 6,
127+
176: 6,
128+
177: 9,
129+
178: 4,
130+
179: 4,
131+
180: 6,
132+
181: 7,
133+
182: 7,
134+
183: 3,
135+
184: 6,
136+
185: 4,
137+
186: 5,
138+
187: 7,
139+
188: 11,
140+
189: 11,
141+
190: 11,
142+
191: 6,
143+
192: 8,
144+
193: 8,
145+
194: 8,
146+
195: 8,
147+
196: 8,
148+
197: 8,
149+
198: 11,
150+
199: 8,
151+
200: 7,
152+
201: 7,
153+
202: 7,
154+
203: 7,
155+
204: 3,
156+
205: 3,
157+
206: 3,
158+
207: 3,
159+
208: 9,
160+
209: 8,
161+
210: 9,
162+
211: 9,
163+
212: 9,
164+
213: 9,
165+
214: 9,
166+
215: 9,
167+
216: 9,
168+
217: 8,
169+
218: 8,
170+
219: 8,
171+
220: 8,
172+
221: 7,
173+
222: 7,
174+
223: 7,
175+
224: 7,
176+
225: 7,
177+
226: 7,
178+
227: 7,
179+
228: 7,
180+
229: 7,
181+
230: 11,
182+
231: 6,
183+
232: 7,
184+
233: 7,
185+
234: 7,
186+
235: 7,
187+
236: 3,
188+
237: 3,
189+
238: 3,
190+
239: 3,
191+
240: 7,
192+
241: 7,
193+
242: 7,
194+
243: 7,
195+
244: 7,
196+
245: 7,
197+
246: 7,
198+
247: 9,
199+
248: 7,
200+
249: 7,
201+
250: 7,
202+
251: 7,
203+
252: 7,
204+
253: 7,
205+
254: 7,
206+
255: 7,
207+
}
208+
})

0 commit comments

Comments
 (0)