aboutsummaryrefslogtreecommitdiffstats
path: root/modules/badge/badge.go
blob: d2e9bd9d1bcc675f20283c52f27a4b1c2e94bfc8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// 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
}