diff options
Diffstat (limited to 'modules/badge')
-rw-r--r-- | modules/badge/badge.go | 133 | ||||
-rw-r--r-- | modules/badge/badge_glyph_width.go | 206 |
2 files changed, 285 insertions, 54 deletions
diff --git a/modules/badge/badge.go b/modules/badge/badge.go index b30d0b4729..d2e9bd9d1b 100644 --- a/modules/badge/badge.go +++ b/modules/badge/badge.go @@ -4,6 +4,10 @@ package badge import ( + "strings" + "sync" + "unicode" + actions_model "code.gitea.io/gitea/models/actions" ) @@ -11,94 +15,115 @@ import ( // 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 } +// Style follows https://shields.io/badges const ( - defaultOffset = 9 - defaultFontSize = 11 - DefaultColor = "#9f9f9f" // Grey - defaultFontWidth = 7 // approximate speculation + StyleFlat = "flat" + StyleFlatSquare = "flat-square" ) -var StatusColorMap = map[actions_model.Status]string{ - actions_model.StatusSuccess: "#4c1", // Green - actions_model.StatusSkipped: "#dfb317", // Yellow - actions_model.StatusUnknown: "#97ca00", // Light Green - actions_model.StatusFailure: "#e05d44", // Red - actions_model.StatusCancelled: "#fe7d37", // Orange - actions_model.StatusWaiting: "#dfb317", // Yellow - actions_model.StatusRunning: "#dfb317", // Yellow - actions_model.StatusBlocked: "#dfb317", // Yellow -} +const ( + defaultOffset = 10 + defaultFontSize = 11 + DefaultColor = "#9f9f9f" // Grey + DefaultFontFamily = "DejaVu Sans,Verdana,Geneva,sans-serif" + DefaultStyle = StyleFlat +) + +var GlobalVars = sync.OnceValue(func() (ret struct { + StatusColorMap map[actions_model.Status]string + DejaVuGlyphWidthData map[rune]uint8 + AllStyles []string +}, +) { + ret.StatusColorMap = map[actions_model.Status]string{ + actions_model.StatusSuccess: "#4c1", // Green + actions_model.StatusSkipped: "#dfb317", // Yellow + actions_model.StatusUnknown: "#97ca00", // Light Green + actions_model.StatusFailure: "#e05d44", // Red + actions_model.StatusCancelled: "#fe7d37", // Orange + actions_model.StatusWaiting: "#dfb317", // Yellow + actions_model.StatusRunning: "#dfb317", // Yellow + actions_model.StatusBlocked: "#dfb317", // Yellow + } + ret.DejaVuGlyphWidthData = dejaVuGlyphWidthDataFunc() + ret.AllStyles = []string{StyleFlat, StyleFlatSquare} + return ret +}) // 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 := GlobalVars().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 +} diff --git a/modules/badge/badge_glyph_width.go b/modules/badge/badge_glyph_width.go new file mode 100644 index 0000000000..0d950c5a70 --- /dev/null +++ b/modules/badge/badge_glyph_width.go @@ -0,0 +1,206 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package badge + +// 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. + +func dejaVuGlyphWidthDataFunc() 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, + } +} |