Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 79 additions & 7 deletions server/api/registry/badge/[type]/[...pkg].get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,87 @@ const COLORS = {
white: '#ffffff',
}

const CHAR_WIDTH = 7
const SHIELDS_CHAR_WIDTH = 6

const BADGE_PADDING_X = 8
const MIN_BADGE_TEXT_WIDTH = 40
const FALLBACK_VALUE_EXTRA_PADDING_X = 8
const SHIELDS_LABEL_PADDING_X = 5

const BADGE_FONT_SHORTHAND = 'normal normal 400 11px Geist, system-ui, -apple-system, sans-serif'
const SHIELDS_FONT_SHORTHAND = 'normal normal 400 11px Verdana, Geneva, DejaVu Sans, sans-serif'

let cachedCanvasContext: SKRSContext2D | null | undefined

const NARROW_CHARS = new Set([' ', '!', '"', "'", '(', ')', '*', ',', '-', '.', ':', ';', '|'])
const MEDIUM_CHARS = new Set([
'#',
'$',
'+',
'/',
'<',
'=',
'>',
'?',
'@',
'[',
'\\',
']',
'^',
'_',
'`',
'{',
'}',
'~',
])

const FALLBACK_WIDTHS = {
default: {
narrow: 3,
medium: 5,
digit: 6,
uppercase: 7,
other: 6,
},
shieldsio: {
narrow: 3,
medium: 5,
digit: 6,
uppercase: 7,
other: 5.5,
},
} as const

function estimateTextWidth(text: string, fallbackFont: 'default' | 'shieldsio'): number {
// Heuristic coefficients tuned to keep fallback rendering close to canvas metrics.
const widths = FALLBACK_WIDTHS[fallbackFont]
let totalWidth = 0

for (const character of text) {
if (NARROW_CHARS.has(character)) {
totalWidth += widths.narrow
continue
}

if (MEDIUM_CHARS.has(character)) {
totalWidth += widths.medium
continue
}

if (/\d/.test(character)) {
totalWidth += widths.digit
continue
}

if (/[A-Z]/.test(character)) {
totalWidth += widths.uppercase
continue
}

totalWidth += widths.other
}

return Math.max(1, Math.round(totalWidth))
}

function getCanvasContext(): SKRSContext2D | null {
if (cachedCanvasContext !== undefined) {
return cachedCanvasContext
Expand Down Expand Up @@ -83,14 +152,17 @@ function measureTextWidth(text: string, font: string): number | null {
return null
}

function measureDefaultTextWidth(text: string): number {
function measureDefaultTextWidth(text: string, fallbackExtraPadding = 0): number {
const measuredWidth = measureTextWidth(text, BADGE_FONT_SHORTHAND)

if (measuredWidth !== null) {
return Math.max(MIN_BADGE_TEXT_WIDTH, measuredWidth + BADGE_PADDING_X * 2)
}

return Math.max(MIN_BADGE_TEXT_WIDTH, Math.round(text.length * CHAR_WIDTH) + BADGE_PADDING_X * 2)
return Math.max(
MIN_BADGE_TEXT_WIDTH,
estimateTextWidth(text, 'default') + BADGE_PADDING_X * 2 + fallbackExtraPadding,
)
}

function escapeXML(str: string): string {
Expand Down Expand Up @@ -125,7 +197,7 @@ function measureShieldsTextLength(text: string): number {
return Math.max(1, measuredWidth)
}

return Math.max(1, Math.round(text.length * SHIELDS_CHAR_WIDTH))
return estimateTextWidth(text, 'shieldsio')
}

function renderDefaultBadgeSvg(params: {
Expand All @@ -139,7 +211,7 @@ function renderDefaultBadgeSvg(params: {
const { finalColor, finalLabel, finalLabelColor, finalValue, labelTextColor, valueTextColor } =
params
const leftWidth = finalLabel.trim().length === 0 ? 0 : measureDefaultTextWidth(finalLabel)
const rightWidth = measureDefaultTextWidth(finalValue)
const rightWidth = measureDefaultTextWidth(finalValue, FALLBACK_VALUE_EXTRA_PADDING_X)
const totalWidth = leftWidth + rightWidth
const height = 20
const escapedLabel = escapeXML(finalLabel)
Expand Down
Loading