diff --git a/packages/core/src/createFontStack.test.ts b/packages/core/src/createFontStack.test.ts index 37a125e..b6dcfe4 100644 --- a/packages/core/src/createFontStack.test.ts +++ b/packages/core/src/createFontStack.test.ts @@ -503,4 +503,127 @@ describe('createFontStack', () => { `); }); }); + + describe('local sources', () => { + it('with a familyName only provided', () => { + const { fontFaces } = createFontStack( + [merriweatherSans, { ...arial, familyName: 'Arial Family Name' }], + { fontFaceFormat: 'styleObject' }, + ); + + expect(fontFaces[0]['@font-face'].src).toBe(`local('Arial Family Name')`); + }); + + it('with a fullName provided', () => { + const { fontFaces } = createFontStack( + [merriweatherSans, { ...arial, fullName: 'Arial Full Name' }], + { fontFaceFormat: 'styleObject' }, + ); + + expect(fontFaces[0]['@font-face'].src).toBe(`local('Arial Full Name')`); + }); + + it('with a fullName and familyName provided', () => { + const { fontFaces } = createFontStack( + [ + merriweatherSans, + { + ...arial, + familyName: 'Arial Family Name', + fullName: 'Arial Full Name', + }, + ], + { fontFaceFormat: 'styleObject' }, + ); + + expect(fontFaces[0]['@font-face'].src).toBe(`local('Arial Full Name')`); + }); + + it('with a postscriptName provided', () => { + const { fontFaces } = createFontStack( + [ + merriweatherSans, + { ...arial, postscriptName: 'Arial Postscript Name' }, + ], + { fontFaceFormat: 'styleObject' }, + ); + + expect(fontFaces[0]['@font-face'].src).toBe( + `local('Arial Postscript Name')`, + ); + }); + + it('with a postscriptName and familyName provided', () => { + const { fontFaces } = createFontStack( + [ + merriweatherSans, + { + ...arial, + familyName: 'Arial Family Name', + postscriptName: 'Arial Postscript Name', + }, + ], + { fontFaceFormat: 'styleObject' }, + ); + + expect(fontFaces[0]['@font-face'].src).toBe( + `local('Arial Postscript Name')`, + ); + }); + + it('with both fullName and postscriptName provided', () => { + const { fontFaces } = createFontStack( + [ + merriweatherSans, + { + ...arial, + fullName: 'Arial Full Name', + postscriptName: 'Arial Postscript Name', + }, + ], + { fontFaceFormat: 'styleObject' }, + ); + + expect(fontFaces[0]['@font-face'].src).toBe( + `local('Arial Full Name'), local('Arial Postscript Name')`, + ); + }); + + it('with both fullName and postscriptName provided', () => { + const { fontFaces } = createFontStack( + [ + merriweatherSans, + { + ...arial, + fullName: 'Arial Full Name', + postscriptName: 'Arial Postscript Name', + }, + ], + { fontFaceFormat: 'styleObject' }, + ); + + expect(fontFaces[0]['@font-face'].src).toBe( + `local('Arial Full Name'), local('Arial Postscript Name')`, + ); + }); + + it('with all names provided', () => { + const { fontFaces } = createFontStack( + [ + merriweatherSans, + { + ...arial, + familyName: 'Arial Family Name', + fullName: 'Arial Full Name', + postscriptName: 'Arial Postscript Name', + }, + ], + { fontFaceFormat: 'styleObject' }, + ); + + expect(fontFaces[0]['@font-face'].src).toBe( + `local('Arial Full Name'), local('Arial Postscript Name')`, + ); + }); + }); }); diff --git a/packages/core/src/createFontStack.ts b/packages/core/src/createFontStack.ts index eeb0d13..9c02ccc 100644 --- a/packages/core/src/createFontStack.ts +++ b/packages/core/src/createFontStack.ts @@ -8,6 +8,17 @@ export const toCssProperty = (property: string) => property.replace(/([A-Z])/g, (property) => `-${property.toLowerCase()}`); type Optional = Pick, K> & Omit; +/* +Making `fullName` and `postscriptName` optional for the `createFontStack` API. +MDN recommends using these when accessing local fonts for to ensure the best +matching across platforms. This also enables selecting a single font face +within a larger family, e.g. `Arial Bold` or `Arial-BoldMT` within `Arial`. + +See MDN for details: https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/src#localfont-face-name + +Falling back to `familyName` (original behaviour) if these are not available. +This works, but will default to the `regular` font face within the family. +*/ type FontStackMetrics = Optional< Pick< FontMetrics, @@ -24,6 +35,24 @@ type FontStackMetrics = Optional< 'fullName' | 'postscriptName' >; +const resolveLocalFallbackSource = (metrics: FontStackMetrics) => { + const sources: string[] = []; + + if (metrics.fullName) { + sources.push(`local('${metrics.fullName}')`); + } + + if (metrics.postscriptName) { + sources.push(`local('${metrics.postscriptName}')`); + } + + if (sources.length > 0) { + return sources.join(', '); + } + + return `local('${metrics.familyName}')`; +}; + // Support old metrics pre-`subsets` alongside the newer core package with `subset` support. const resolveXWidthAvg = ( metrics: FontStackMetrics, @@ -231,15 +260,7 @@ export function createFontStack( '@font-face': { ...fontFaceProperties, fontFamily, - src: [ - fallback.fullName ? `local('${fallback.fullName}')` : '', - fallback.postscriptName ? `local('${fallback.postscriptName}')` : '', - !fallback.fullName && !fallback.postscriptName - ? `local('${fallback.familyName}')` - : '', - ] - .filter(Boolean) - .join(', '), + src: resolveLocalFallbackSource(fallback), ...calculateOverrideValues({ metrics, fallbackMetrics: fallback,