// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package badge import ( "strings" "sync" "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 Text struct { text string width int x int } func (t Text) Text() string { return t.text } func (t Text) Width() int { return t.width } func (t Text) X() int { return t.x } func (t Text) TextLength() int { return int(float64(t.width-defaultOffset) * 10) } type Badge struct { 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 ( StyleFlat = "flat" StyleFlatSquare = "flat-square" ) 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 := calculateTextWidth(label) + defaultOffset mw := calculateTextWidth(message) + defaultOffset lx := lw * 5 mx := lw*10 + mw*5 - 10 return Badge{ FontFamily: DefaultFontFamily, Label: Text{ text: label, width: lw, x: lx, }, Message: Text{ text: message, width: mw, 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 }