fix(text): prefer matching local font slant

This commit is contained in:
Danila Poyarkov 2026-05-17 20:33:13 +03:00
parent b1ad6d41e9
commit b46722a50b
3 changed files with 59 additions and 11 deletions

View file

@ -165,6 +165,7 @@ export {
export {
FONT_WEIGHT_NAMES,
FontManager,
chooseLocalFontMatch,
fontManager,
styleToWeight,
weightToFigmaStyle,

View file

@ -22,6 +22,44 @@ export interface DownloadedFontCache {
type FindLocalFontOptions = { allowVariable?: boolean }
type LocalFontMatch = Pick<FontInfo, 'family' | 'style'>
export function chooseLocalFontMatch<T extends LocalFontMatch>(
fonts: T[],
family: string,
style?: string
): T | undefined {
const families = [family]
const normalized = normalizeFontFamily(family)
if (normalized !== family) families.push(normalized)
const requested = parseFontStyle(style)
for (const f of families) {
const exact = style ? fonts.find((x) => x.family === f && x.style === style) : undefined
if (exact) return exact
const candidates = fonts.filter((x) => x.family === f)
const sameStyle = candidates.find((x) => {
const parsed = parseFontStyle(x.style)
return parsed.weight === requested.weight && parsed.italic === requested.italic
})
if (sameStyle) return sameStyle
const sameSlant = candidates.filter((x) => parseFontStyle(x.style).italic === requested.italic)
if (sameSlant.length > 0) {
return sameSlant.sort(
(a, b) =>
Math.abs(parseFontStyle(a.style).weight - requested.weight) -
Math.abs(parseFontStyle(b.style).weight - requested.weight)
)[0]
}
if (!style && candidates.length > 0) return candidates[0]
}
return undefined
}
const BUNDLED_FONTS: Record<string, string> = {
'Inter|Regular': '/Inter-Regular.ttf',
'Inter|Medium': '/Inter-Medium.ttf',
@ -448,17 +486,7 @@ export class FontManager {
if (!IS_BROWSER || !window.queryLocalFonts) return null
try {
const fonts = await window.queryLocalFonts()
const families = [family]
const normalized = normalizeFontFamily(family)
if (normalized !== family) families.push(normalized)
let match: (typeof fonts)[number] | undefined
for (const f of families) {
match = style ? fonts.find((x) => x.family === f && x.style === style) : undefined
match ??= fonts.find((x) => x.family === f)
if (match) break
}
const match = chooseLocalFontMatch(fonts, family, style)
if (!match) return null
const blob: Blob = await match.blob()
const buffer = await blob.arrayBuffer()

View file

@ -3,6 +3,7 @@ import { describe, test, expect } from 'bun:test'
import type { CanvasKit, TypefaceFontProvider } from 'canvaskit-wasm'
import {
chooseLocalFontMatch,
fontManager,
isVariableFont,
normalizeFontFamily,
@ -91,6 +92,24 @@ function createRecordingProvider() {
return { provider, registrations }
}
describe('chooseLocalFontMatch', () => {
const fonts = [
{ family: 'Inter', style: 'Italic' },
{ family: 'Inter', style: 'Regular' },
{ family: 'Inter', style: 'Semi Bold' },
{ family: 'Inter', style: 'Bold Italic' }
]
test('prefers parsed weight and slant over first family match', () => {
expect(chooseLocalFontMatch(fonts, 'Inter', 'SemiBold')?.style).toBe('Semi Bold')
expect(chooseLocalFontMatch(fonts, 'Inter', 'Regular')?.style).toBe('Regular')
})
test('does not fall back from upright requests to italic when upright faces exist', () => {
expect(chooseLocalFontMatch(fonts, 'Inter', 'Bold')?.style).toBe('Semi Bold')
})
})
describe('FontManager loaded font cache', () => {
test('marks and checks font', () => {
const family = `TestFont_${Date.now()}`