Skip to content

Commit 7190baf

Browse files
howwohmmclaude
andcommitted
fix(badge): register Geist font with canvas to fix width measurement
The Geist font was not registered with @napi-rs/canvas, causing text measurement to use a wider system fallback font in production. This produced badges with excessive whitespace, especially for longer text. Register the Geist font from server assets so canvas measures text with the same font the SVG specifies for rendering. Closes #2187 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7f2fc1a commit 7190baf

File tree

2 files changed

+28
-1
lines changed

2 files changed

+28
-1
lines changed

nuxt.config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,12 @@ export default defineNuxtConfig({
253253
replace: {
254254
'import.meta.test': isTest,
255255
},
256+
serverAssets: [
257+
{
258+
baseName: 'fonts',
259+
dir: './public/fonts',
260+
},
261+
],
256262
},
257263

258264
fonts: {

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as v from 'valibot'
2-
import { createCanvas, type SKRSContext2D } from '@napi-rs/canvas'
2+
import { createCanvas, GlobalFonts, type SKRSContext2D } from '@napi-rs/canvas'
33
import { hash } from 'ohash'
44
import { createError, getRouterParam, getQuery, setHeader } from 'h3'
55
import { PackageRouteParamsSchema } from '#shared/schemas/package'
@@ -52,6 +52,25 @@ const BADGE_FONT_SHORTHAND = 'normal normal 400 11px Geist, system-ui, -apple-sy
5252
const SHIELDS_FONT_SHORTHAND = 'normal normal 400 11px Verdana, Geneva, DejaVu Sans, sans-serif'
5353

5454
let cachedCanvasContext: SKRSContext2D | null | undefined
55+
let fontRegistered = false
56+
57+
async function registerGeistFont(): Promise<void> {
58+
if (fontRegistered) return
59+
60+
try {
61+
const fontStorage = useStorage('assets:fonts')
62+
const fontData = await fontStorage.getItemRaw('Geist-Regular.ttf')
63+
64+
if (fontData) {
65+
const buffer = fontData instanceof Buffer ? fontData : Buffer.from(fontData as ArrayBuffer)
66+
GlobalFonts.register(buffer, 'Geist')
67+
}
68+
} catch {
69+
// font registration is best-effort; fallback measurement will be used
70+
}
71+
72+
fontRegistered = true
73+
}
5574

5675
function getCanvasContext(): SKRSContext2D | null {
5776
if (cachedCanvasContext !== undefined) {
@@ -431,6 +450,8 @@ const BadgeStyleSchema = v.picklist(['default', 'shieldsio'])
431450

432451
export default defineCachedEventHandler(
433452
async event => {
453+
await registerGeistFont()
454+
434455
const query = getQuery(event)
435456
const typeParam = getRouterParam(event, 'type')
436457
const pkgParamSegments = getRouterParam(event, 'pkg')?.split('/') ?? []

0 commit comments

Comments
 (0)