Skip to content

Commit 7f53a18

Browse files
committed
fix: improve badge text measurement accuracy
This PR improves badge appearance by using a more accurate method to measure text width. Instead of relying on a predefined character width, it utilizes the Canvas API to measure the actual width of the text, resulting in better alignment and spacing for badges with varying character widths. Speaking in code, this is how the old and new methods compare: { label: 'iiiiiiiiii', leftWidthOld: 86, leftWidth: 41 } { label: 'wwwwwwwwww', leftWidthOld: 86, leftWidth: 97 }
1 parent 7fe73b0 commit 7f53a18

File tree

3 files changed

+166
-9
lines changed

3 files changed

+166
-9
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: 40 additions & 9 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'
@@ -35,14 +36,47 @@ const COLORS = {
3536
}
3637

3738
const DEFAULT_CHAR_WIDTH = 7
38-
const CHARS_WIDTH = {
39-
engines: 5.5,
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 {
61+
function fallbackMeasureTextWidth(text: string, charWidth?: number): number {
4362
charWidth ??= DEFAULT_CHAR_WIDTH
44-
const paddingX = 8
45-
return Math.max(40, Math.round(text.length * charWidth) + paddingX * 2)
63+
return Math.max(MIN_BADGE_TEXT_WIDTH, Math.round(text.length * charWidth) + BADGE_PADDING_X * 2)
64+
}
65+
66+
function measureTextWidth(text: string): number {
67+
const context = getCanvasContext()
68+
69+
if (context) {
70+
context.font = BADGE_FONT_SHORTHAND
71+
72+
const measuredWidth = context.measureText(text).width
73+
74+
if (!Number.isNaN(measuredWidth)) {
75+
return Math.max(MIN_BADGE_TEXT_WIDTH, Math.ceil(measuredWidth) + BADGE_PADDING_X * 2)
76+
}
77+
}
78+
79+
return fallbackMeasureTextWidth(text)
4680
}
4781

4882
function formatBytes(bytes: number): string {
@@ -296,10 +330,7 @@ export default defineCachedEventHandler(
296330
const finalLabelColor = rawLabelColor?.startsWith('#') ? rawLabelColor : `#${rawLabelColor}`
297331

298332
const leftWidth = finalLabel.trim().length === 0 ? 0 : measureTextWidth(finalLabel)
299-
const rightWidth = measureTextWidth(
300-
finalValue,
301-
CHARS_WIDTH[strategyKey as keyof typeof CHARS_WIDTH],
302-
)
333+
const rightWidth = measureTextWidth(finalValue)
303334
const totalWidth = leftWidth + rightWidth
304335
const height = 20
305336

0 commit comments

Comments
 (0)