@@ -40,18 +40,87 @@ const COLORS = {
4040 white : '#ffffff' ,
4141}
4242
43- const CHAR_WIDTH = 7
44- const SHIELDS_CHAR_WIDTH = 6
45-
4643const BADGE_PADDING_X = 8
4744const MIN_BADGE_TEXT_WIDTH = 40
45+ const FALLBACK_VALUE_EXTRA_PADDING_X = 8
4846const SHIELDS_LABEL_PADDING_X = 5
4947
5048const BADGE_FONT_SHORTHAND = 'normal normal 400 11px Geist, system-ui, -apple-system, sans-serif'
5149const SHIELDS_FONT_SHORTHAND = 'normal normal 400 11px Verdana, Geneva, DejaVu Sans, sans-serif'
5250
5351let cachedCanvasContext : SKRSContext2D | null | undefined
5452
53+ const NARROW_CHARS = new Set ( [ ' ' , '!' , '"' , "'" , '(' , ')' , '*' , ',' , '-' , '.' , ':' , ';' , '|' ] )
54+ const MEDIUM_CHARS = new Set ( [
55+ '#' ,
56+ '$' ,
57+ '+' ,
58+ '/' ,
59+ '<' ,
60+ '=' ,
61+ '>' ,
62+ '?' ,
63+ '@' ,
64+ '[' ,
65+ '\\' ,
66+ ']' ,
67+ '^' ,
68+ '_' ,
69+ '`' ,
70+ '{' ,
71+ '}' ,
72+ '~' ,
73+ ] )
74+
75+ const FALLBACK_WIDTHS = {
76+ default : {
77+ narrow : 3 ,
78+ medium : 5 ,
79+ digit : 6 ,
80+ uppercase : 7 ,
81+ other : 6 ,
82+ } ,
83+ shieldsio : {
84+ narrow : 3 ,
85+ medium : 5 ,
86+ digit : 6 ,
87+ uppercase : 7 ,
88+ other : 5.5 ,
89+ } ,
90+ } as const
91+
92+ function estimateTextWidth ( text : string , fallbackFont : 'default' | 'shieldsio' ) : number {
93+ // Heuristic coefficients tuned to keep fallback rendering close to canvas metrics.
94+ const widths = FALLBACK_WIDTHS [ fallbackFont ]
95+ let totalWidth = 0
96+
97+ for ( const character of text ) {
98+ if ( NARROW_CHARS . has ( character ) ) {
99+ totalWidth += widths . narrow
100+ continue
101+ }
102+
103+ if ( MEDIUM_CHARS . has ( character ) ) {
104+ totalWidth += widths . medium
105+ continue
106+ }
107+
108+ if ( / \d / . test ( character ) ) {
109+ totalWidth += widths . digit
110+ continue
111+ }
112+
113+ if ( / [ A - Z ] / . test ( character ) ) {
114+ totalWidth += widths . uppercase
115+ continue
116+ }
117+
118+ totalWidth += widths . other
119+ }
120+
121+ return Math . max ( 1 , Math . round ( totalWidth ) )
122+ }
123+
55124function getCanvasContext ( ) : SKRSContext2D | null {
56125 if ( cachedCanvasContext !== undefined ) {
57126 return cachedCanvasContext
@@ -82,14 +151,17 @@ function measureTextWidth(text: string, font: string): number | null {
82151 return null
83152}
84153
85- function measureDefaultTextWidth ( text : string ) : number {
154+ function measureDefaultTextWidth ( text : string , fallbackExtraPadding = 0 ) : number {
86155 const measuredWidth = measureTextWidth ( text , BADGE_FONT_SHORTHAND )
87156
88157 if ( measuredWidth !== null ) {
89158 return Math . max ( MIN_BADGE_TEXT_WIDTH , measuredWidth + BADGE_PADDING_X * 2 )
90159 }
91160
92- return Math . max ( MIN_BADGE_TEXT_WIDTH , Math . round ( text . length * CHAR_WIDTH ) + BADGE_PADDING_X * 2 )
161+ return Math . max (
162+ MIN_BADGE_TEXT_WIDTH ,
163+ estimateTextWidth ( text , 'default' ) + BADGE_PADDING_X * 2 + fallbackExtraPadding ,
164+ )
93165}
94166
95167function escapeXML ( str : string ) : string {
@@ -124,7 +196,7 @@ function measureShieldsTextLength(text: string): number {
124196 return Math . max ( 1 , measuredWidth )
125197 }
126198
127- return Math . max ( 1 , Math . round ( text . length * SHIELDS_CHAR_WIDTH ) )
199+ return estimateTextWidth ( text , 'shieldsio' )
128200}
129201
130202function renderDefaultBadgeSvg ( params : {
@@ -138,7 +210,7 @@ function renderDefaultBadgeSvg(params: {
138210 const { finalColor, finalLabel, finalLabelColor, finalValue, labelTextColor, valueTextColor } =
139211 params
140212 const leftWidth = finalLabel . trim ( ) . length === 0 ? 0 : measureDefaultTextWidth ( finalLabel )
141- const rightWidth = measureDefaultTextWidth ( finalValue )
213+ const rightWidth = measureDefaultTextWidth ( finalValue , FALLBACK_VALUE_EXTRA_PADDING_X )
142214 const totalWidth = leftWidth + rightWidth
143215 const height = 20
144216 const escapedLabel = escapeXML ( finalLabel )
0 commit comments