Skip to content

Update action status badge layout #34018

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

Merged
merged 15 commits into from
Mar 28, 2025
Merged
95 changes: 51 additions & 44 deletions modules/badge/badge.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,72 +4,56 @@
package badge

import (
"strings"
"unicode"

actions_model "code.gitea.io/gitea/models/actions"
)

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

type Label struct {
text string
width int
}

func (l Label) Text() string {
return l.text
}

func (l Label) Width() int {
return l.width
}

func (l Label) TextLength() int {
return int(float64(l.width-defaultOffset) * 9.5)
}

func (l Label) X() int {
return l.width*5 + 10
}

type Message struct {
type Text struct {
text string
width int
x int
}

func (m Message) Text() string {
return m.text
func (t Text) Text() string {
return t.text
}

func (m Message) Width() int {
return m.width
func (t Text) Width() int {
return t.width
}

func (m Message) X() int {
return m.x
func (t Text) X() int {
return t.x
}

func (m Message) TextLength() int {
return int(float64(m.width-defaultOffset) * 9.5)
func (t Text) TextLength() int {
return int(float64(t.width-defaultOffset) * 10)
}

type Badge struct {
Color string
FontSize int
Label Label
Message Message
IDPrefix string
FontFamily string
Color string
FontSize int
Label Text
Message Text
}

func (b Badge) Width() int {
return b.Label.width + b.Message.width
}

const (
defaultOffset = 9
defaultFontSize = 11
DefaultColor = "#9f9f9f" // Grey
defaultFontWidth = 7 // approximate speculation
defaultOffset = 10
defaultFontSize = 11
DefaultColor = "#9f9f9f" // Grey
DefaultFontFamily = "DejaVu Sans,Verdana,Geneva,sans-serif"
)

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

// GenerateBadge generates badge with given template
func GenerateBadge(label, message, color string) Badge {
lw := defaultFontWidth*len(label) + defaultOffset
mw := defaultFontWidth*len(message) + defaultOffset
x := lw*10 + mw*5 - 10
lw := calculateTextWidth(label) + defaultOffset
mw := calculateTextWidth(message) + defaultOffset

lx := lw * 5
mx := lw*10 + mw*5 - 10
return Badge{
Label: Label{
FontFamily: DefaultFontFamily,
Label: Text{
text: label,
width: lw,
x: lx,
},
Message: Message{
Message: Text{
text: message,
width: mw,
x: x,
x: mx,
},
FontSize: defaultFontSize * 10,
Color: color,
}
}

func calculateTextWidth(text string) int {
width := 0
widthData := DejaVuGlyphWidthData()
for _, char := range strings.TrimSpace(text) {
charWidth, ok := widthData[char]
if !ok {
// use the width of 'm' in case of missing glyph width data for a printable character
if unicode.IsPrint(char) {
charWidth = widthData['m']
} else {
charWidth = 0
}
}
width += int(charWidth)
}

return width
}
208 changes: 208 additions & 0 deletions modules/badge/badge_glyph_width.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package badge

import "sync"

// DejaVuGlyphWidthData is generated by `sfnt.Face.GlyphAdvance(nil, <rune>, 11, font.HintingNone)` with DejaVu Sans
// v2.37 (https://github.com/dejavu-fonts/dejavu-fonts/releases/download/version_2_37/dejavu-sans-ttf-2.37.zip).
//
// Fonts defined in "DefaultFontFamily" all have similar widths (including "DejaVu Sans"),
// and these widths are fixed and don't seem to change.
//
// A devtest page "/devtest/badge-actions-svg" could be used to check the rendered images.

var DejaVuGlyphWidthData = sync.OnceValue(func() map[rune]uint8 {
return map[rune]uint8{
32: 3,
33: 4,
34: 5,
35: 9,
36: 7,
37: 10,
38: 9,
39: 3,
40: 4,
41: 4,
42: 6,
43: 9,
44: 3,
45: 4,
46: 3,
47: 4,
48: 7,
49: 7,
50: 7,
51: 7,
52: 7,
53: 7,
54: 7,
55: 7,
56: 7,
57: 7,
58: 4,
59: 4,
60: 9,
61: 9,
62: 9,
63: 6,
64: 11,
65: 8,
66: 8,
67: 8,
68: 8,
69: 7,
70: 6,
71: 9,
72: 8,
73: 3,
74: 3,
75: 7,
76: 6,
77: 9,
78: 8,
79: 9,
80: 7,
81: 9,
82: 8,
83: 7,
84: 7,
85: 8,
86: 8,
87: 11,
88: 8,
89: 7,
90: 8,
91: 4,
92: 4,
93: 4,
94: 9,
95: 6,
96: 6,
97: 7,
98: 7,
99: 6,
100: 7,
101: 7,
102: 4,
103: 7,
104: 7,
105: 3,
106: 3,
107: 6,
108: 3,
109: 11,
110: 7,
111: 7,
112: 7,
113: 7,
114: 5,
115: 6,
116: 4,
117: 7,
118: 7,
119: 9,
120: 7,
121: 7,
122: 6,
123: 7,
124: 4,
125: 7,
126: 9,
161: 4,
162: 7,
163: 7,
164: 7,
165: 7,
166: 4,
167: 6,
168: 6,
169: 11,
170: 5,
171: 7,
172: 9,
174: 11,
175: 6,
176: 6,
177: 9,
178: 4,
179: 4,
180: 6,
181: 7,
182: 7,
183: 3,
184: 6,
185: 4,
186: 5,
187: 7,
188: 11,
189: 11,
190: 11,
191: 6,
192: 8,
193: 8,
194: 8,
195: 8,
196: 8,
197: 8,
198: 11,
199: 8,
200: 7,
201: 7,
202: 7,
203: 7,
204: 3,
205: 3,
206: 3,
207: 3,
208: 9,
209: 8,
210: 9,
211: 9,
212: 9,
213: 9,
214: 9,
215: 9,
216: 9,
217: 8,
218: 8,
219: 8,
220: 8,
221: 7,
222: 7,
223: 7,
224: 7,
225: 7,
226: 7,
227: 7,
228: 7,
229: 7,
230: 11,
231: 6,
232: 7,
233: 7,
234: 7,
235: 7,
236: 3,
237: 3,
238: 3,
239: 3,
240: 7,
241: 7,
242: 7,
243: 7,
244: 7,
245: 7,
246: 7,
247: 9,
248: 7,
249: 7,
250: 7,
251: 7,
252: 7,
253: 7,
254: 7,
255: 7,
}
})
Loading