Skip to content

Commit b03fb88

Browse files
authored
fix: improve badge text measurement accuracy (#1486)
1 parent 83007a5 commit b03fb88

File tree

3 files changed

+167
-11
lines changed

3 files changed

+167
-11
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"@iconify-json/vscode-icons": "1.2.40",
6161
"@intlify/shared": "11.2.8",
6262
"@lunariajs/core": "https://pkg.pr.new/lunariajs/lunaria/@lunariajs/core@f07e1a3",
63+
"@napi-rs/canvas": "0.1.92",
6364
"@nuxt/a11y": "1.0.0-alpha.1",
6465
"@nuxt/fonts": "0.13.0",
6566
"@nuxt/scripts": "0.13.2",

pnpm-lock.yaml

Lines changed: 125 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/api/registry/badge/[type]/[...pkg].get.ts

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as v from 'valibot'
2+
import { createCanvas, type SKRSContext2D } from '@napi-rs/canvas'
23
import { hash } from 'ohash'
34
import { createError, getRouterParam, getQuery, setHeader } from 'h3'
45
import { PackageRouteParamsSchema } from '#shared/schemas/package'
@@ -34,15 +35,47 @@ const COLORS = {
3435
white: '#ffffff',
3536
}
3637

37-
const DEFAULT_CHAR_WIDTH = 7
38-
const CHARS_WIDTH = {
39-
engines: 5.5,
38+
const CHAR_WIDTH = 7
39+
40+
const BADGE_PADDING_X = 8
41+
const MIN_BADGE_TEXT_WIDTH = 40
42+
43+
const BADGE_FONT_SHORTHAND = 'normal normal 400 11px Geist, system-ui, -apple-system, sans-serif'
44+
45+
let cachedCanvasContext: SKRSContext2D | null | undefined
46+
47+
function getCanvasContext(): SKRSContext2D | null {
48+
if (cachedCanvasContext !== undefined) {
49+
return cachedCanvasContext
50+
}
51+
52+
try {
53+
cachedCanvasContext = createCanvas(1, 1).getContext('2d')
54+
} catch {
55+
cachedCanvasContext = null
56+
}
57+
58+
return cachedCanvasContext
4059
}
4160

42-
function measureTextWidth(text: string, charWidth?: number): number {
43-
charWidth ??= DEFAULT_CHAR_WIDTH
44-
const paddingX = 8
45-
return Math.max(40, Math.round(text.length * charWidth) + paddingX * 2)
61+
function fallbackMeasureTextWidth(text: string): number {
62+
return Math.max(MIN_BADGE_TEXT_WIDTH, Math.round(text.length * CHAR_WIDTH) + BADGE_PADDING_X * 2)
63+
}
64+
65+
function measureTextWidth(text: string): number {
66+
const context = getCanvasContext()
67+
68+
if (context) {
69+
context.font = BADGE_FONT_SHORTHAND
70+
71+
const measuredWidth = context.measureText(text).width
72+
73+
if (!Number.isNaN(measuredWidth)) {
74+
return Math.max(MIN_BADGE_TEXT_WIDTH, Math.ceil(measuredWidth) + BADGE_PADDING_X * 2)
75+
}
76+
}
77+
78+
return fallbackMeasureTextWidth(text)
4679
}
4780

4881
function formatBytes(bytes: number): string {
@@ -300,10 +333,7 @@ export default defineCachedEventHandler(
300333
const finalLabelColor = rawLabelColor?.startsWith('#') ? rawLabelColor : `#${rawLabelColor}`
301334

302335
const leftWidth = finalLabel.trim().length === 0 ? 0 : measureTextWidth(finalLabel)
303-
const rightWidth = measureTextWidth(
304-
finalValue,
305-
CHARS_WIDTH[strategyKey as keyof typeof CHARS_WIDTH],
306-
)
336+
const rightWidth = measureTextWidth(finalValue)
307337
const totalWidth = leftWidth + rightWidth
308338
const height = 20
309339

0 commit comments

Comments
 (0)