[READ-ONLY] a fast, modern browser for the npm registry
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: improve badge text measurement accuracy (#1486)

authored by

Wojciech Maj and committed by
GitHub
b03fb886 83007a55

+167 -11
+1
package.json
··· 60 60 "@iconify-json/vscode-icons": "1.2.40", 61 61 "@intlify/shared": "11.2.8", 62 62 "@lunariajs/core": "https://pkg.pr.new/lunariajs/lunaria/@lunariajs/core@f07e1a3", 63 + "@napi-rs/canvas": "0.1.92", 63 64 "@nuxt/a11y": "1.0.0-alpha.1", 64 65 "@nuxt/fonts": "0.13.0", 65 66 "@nuxt/scripts": "0.13.2",
+125
pnpm-lock.yaml
··· 53 53 '@lunariajs/core': 54 54 specifier: https://pkg.pr.new/lunariajs/lunaria/@lunariajs/core@f07e1a3 55 55 version: https://pkg.pr.new/lunariajs/lunaria/@lunariajs/core@f07e1a3 56 + '@napi-rs/canvas': 57 + specifier: 0.1.92 58 + version: 0.1.92 56 59 '@nuxt/a11y': 57 60 specifier: 1.0.0-alpha.1 58 61 version: 1.0.0-alpha.1(magicast@0.5.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)) ··· 1916 1919 peerDependenciesMeta: 1917 1920 '@cfworker/json-schema': 1918 1921 optional: true 1922 + 1923 + '@napi-rs/canvas-android-arm64@0.1.92': 1924 + resolution: {integrity: sha512-rDOtq53ujfOuevD5taxAuIFALuf1QsQWZe1yS/N4MtT+tNiDBEdjufvQRPWZ11FubL2uwgP8ApYU3YOaNu1ZsQ==} 1925 + engines: {node: '>= 10'} 1926 + cpu: [arm64] 1927 + os: [android] 1928 + 1929 + '@napi-rs/canvas-darwin-arm64@0.1.92': 1930 + resolution: {integrity: sha512-4PT6GRGCr7yMRehp42x0LJb1V0IEy1cDZDDayv7eKbFUIGbPFkV7CRC9Bee5MPkjg1EB4ZPXXUyy3gjQm7mR8Q==} 1931 + engines: {node: '>= 10'} 1932 + cpu: [arm64] 1933 + os: [darwin] 1934 + 1935 + '@napi-rs/canvas-darwin-x64@0.1.92': 1936 + resolution: {integrity: sha512-5e/3ZapP7CqPtDcZPtmowCsjoyQwuNMMD7c0GKPtZQ8pgQhLkeq/3fmk0HqNSD1i227FyJN/9pDrhw/UMTkaWA==} 1937 + engines: {node: '>= 10'} 1938 + cpu: [x64] 1939 + os: [darwin] 1940 + 1941 + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.92': 1942 + resolution: {integrity: sha512-j6KaLL9iir68lwpzzY+aBGag1PZp3+gJE2mQ3ar4VJVmyLRVOh+1qsdNK1gfWoAVy5w6U7OEYFrLzN2vOFUSng==} 1943 + engines: {node: '>= 10'} 1944 + cpu: [arm] 1945 + os: [linux] 1946 + 1947 + '@napi-rs/canvas-linux-arm64-gnu@0.1.92': 1948 + resolution: {integrity: sha512-s3NlnJMHOSotUYVoTCoC1OcomaChFdKmZg0VsHFeIkeHbwX0uPHP4eCX1irjSfMykyvsGHTQDfBAtGYuqxCxhQ==} 1949 + engines: {node: '>= 10'} 1950 + cpu: [arm64] 1951 + os: [linux] 1952 + libc: [glibc] 1953 + 1954 + '@napi-rs/canvas-linux-arm64-musl@0.1.92': 1955 + resolution: {integrity: sha512-xV0GQnukYq5qY+ebkAwHjnP2OrSGBxS3vSi1zQNQj0bkXU6Ou+Tw7JjCM7pZcQ28MUyEBS1yKfo7rc7ip2IPFQ==} 1956 + engines: {node: '>= 10'} 1957 + cpu: [arm64] 1958 + os: [linux] 1959 + libc: [musl] 1960 + 1961 + '@napi-rs/canvas-linux-riscv64-gnu@0.1.92': 1962 + resolution: {integrity: sha512-+GKvIFbQ74eB/TopEdH6XIXcvOGcuKvCITLGXy7WLJAyNp3Kdn1ncjxg91ihatBaPR+t63QOE99yHuIWn3UQ9w==} 1963 + engines: {node: '>= 10'} 1964 + cpu: [riscv64] 1965 + os: [linux] 1966 + libc: [glibc] 1967 + 1968 + '@napi-rs/canvas-linux-x64-gnu@0.1.92': 1969 + resolution: {integrity: sha512-tFd6MwbEhZ1g64iVY2asV+dOJC+GT3Yd6UH4G3Hp0/VHQ6qikB+nvXEULskFYZ0+wFqlGPtXjG1Jmv7sJy+3Ww==} 1970 + engines: {node: '>= 10'} 1971 + cpu: [x64] 1972 + os: [linux] 1973 + libc: [glibc] 1974 + 1975 + '@napi-rs/canvas-linux-x64-musl@0.1.92': 1976 + resolution: {integrity: sha512-uSuqeSveB/ZGd72VfNbHCSXO9sArpZTvznMVsb42nqPP7gBGEH6NJQ0+hmF+w24unEmxBhPYakP/Wiosm16KkA==} 1977 + engines: {node: '>= 10'} 1978 + cpu: [x64] 1979 + os: [linux] 1980 + libc: [musl] 1981 + 1982 + '@napi-rs/canvas-win32-arm64-msvc@0.1.92': 1983 + resolution: {integrity: sha512-20SK5AU/OUNz9ZuoAPj5ekWai45EIBDh/XsdrVZ8le/pJVlhjFU3olbumSQUXRFn7lBRS+qwM8kA//uLaDx6iQ==} 1984 + engines: {node: '>= 10'} 1985 + cpu: [arm64] 1986 + os: [win32] 1987 + 1988 + '@napi-rs/canvas-win32-x64-msvc@0.1.92': 1989 + resolution: {integrity: sha512-KEhyZLzq1MXCNlXybz4k25MJmHFp+uK1SIb8yJB0xfrQjz5aogAMhyseSzewo+XxAq3OAOdyKvfHGNzT3w1RPg==} 1990 + engines: {node: '>= 10'} 1991 + cpu: [x64] 1992 + os: [win32] 1993 + 1994 + '@napi-rs/canvas@0.1.92': 1995 + resolution: {integrity: sha512-q7ZaUCJkEU5BeOdE7fBx1XWRd2T5Ady65nxq4brMf5L4cE1VV/ACq5w9Z5b/IVJs8CwSSIwc30nlthH0gFo4Ig==} 1996 + engines: {node: '>= 10'} 1919 1997 1920 1998 '@napi-rs/wasm-runtime@1.1.1': 1921 1999 resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} ··· 11500 11578 zod-to-json-schema: 3.25.1(zod@4.3.6) 11501 11579 transitivePeerDependencies: 11502 11580 - supports-color 11581 + 11582 + '@napi-rs/canvas-android-arm64@0.1.92': 11583 + optional: true 11584 + 11585 + '@napi-rs/canvas-darwin-arm64@0.1.92': 11586 + optional: true 11587 + 11588 + '@napi-rs/canvas-darwin-x64@0.1.92': 11589 + optional: true 11590 + 11591 + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.92': 11592 + optional: true 11593 + 11594 + '@napi-rs/canvas-linux-arm64-gnu@0.1.92': 11595 + optional: true 11596 + 11597 + '@napi-rs/canvas-linux-arm64-musl@0.1.92': 11598 + optional: true 11599 + 11600 + '@napi-rs/canvas-linux-riscv64-gnu@0.1.92': 11601 + optional: true 11602 + 11603 + '@napi-rs/canvas-linux-x64-gnu@0.1.92': 11604 + optional: true 11605 + 11606 + '@napi-rs/canvas-linux-x64-musl@0.1.92': 11607 + optional: true 11608 + 11609 + '@napi-rs/canvas-win32-arm64-msvc@0.1.92': 11610 + optional: true 11611 + 11612 + '@napi-rs/canvas-win32-x64-msvc@0.1.92': 11613 + optional: true 11614 + 11615 + '@napi-rs/canvas@0.1.92': 11616 + optionalDependencies: 11617 + '@napi-rs/canvas-android-arm64': 0.1.92 11618 + '@napi-rs/canvas-darwin-arm64': 0.1.92 11619 + '@napi-rs/canvas-darwin-x64': 0.1.92 11620 + '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.92 11621 + '@napi-rs/canvas-linux-arm64-gnu': 0.1.92 11622 + '@napi-rs/canvas-linux-arm64-musl': 0.1.92 11623 + '@napi-rs/canvas-linux-riscv64-gnu': 0.1.92 11624 + '@napi-rs/canvas-linux-x64-gnu': 0.1.92 11625 + '@napi-rs/canvas-linux-x64-musl': 0.1.92 11626 + '@napi-rs/canvas-win32-arm64-msvc': 0.1.92 11627 + '@napi-rs/canvas-win32-x64-msvc': 0.1.92 11503 11628 11504 11629 '@napi-rs/wasm-runtime@1.1.1': 11505 11630 dependencies:
+41 -11
server/api/registry/badge/[type]/[...pkg].get.ts
··· 1 1 import * as v from 'valibot' 2 + import { createCanvas, type SKRSContext2D } from '@napi-rs/canvas' 2 3 import { hash } from 'ohash' 3 4 import { createError, getRouterParam, getQuery, setHeader } from 'h3' 4 5 import { PackageRouteParamsSchema } from '#shared/schemas/package' ··· 34 35 white: '#ffffff', 35 36 } 36 37 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 59 + } 60 + 61 + function fallbackMeasureTextWidth(text: string): number { 62 + return Math.max(MIN_BADGE_TEXT_WIDTH, Math.round(text.length * CHAR_WIDTH) + BADGE_PADDING_X * 2) 40 63 } 41 64 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) 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) 46 79 } 47 80 48 81 function formatBytes(bytes: number): string { ··· 300 333 const finalLabelColor = rawLabelColor?.startsWith('#') ? rawLabelColor : `#${rawLabelColor}` 301 334 302 335 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) 307 337 const totalWidth = leftWidth + rightWidth 308 338 const height = 20 309 339