diff --git a/packages/font/fontkit.js b/packages/font/fontkit.js new file mode 100644 index 0000000000000..a5e696245d572 --- /dev/null +++ b/packages/font/fontkit.js @@ -0,0 +1,3 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { create } from 'fontkit' +export default create diff --git a/packages/font/package.json b/packages/font/package.json index f5c3b0a55ef3b..7c9f1fd917c4c 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -12,9 +12,15 @@ ], "license": "MIT", "scripts": { - "build": "rm -rf dist && tsc -d -p tsconfig.json", + "build": "rm -rf dist && pnpm ncc-fontkit && tsc -d -p tsconfig.json", "prepublishOnly": "cd ../../ && turbo run build", - "dev": "tsc -d -w -p tsconfig.json", - "typescript": "tsec --noEmit -p tsconfig.json" + "dev": "pnpm ncc-fontkit && tsc -d -w -p tsconfig.json", + "typescript": "tsec --noEmit -p tsconfig.json", + "ncc-fontkit": "ncc build ./fontkit.js -o dist/fontkit" + }, + "devDependencies": { + "@types/fontkit": "2.0.0", + "@vercel/ncc": "0.34.0", + "fontkit": "2.0.2" } } diff --git a/packages/font/src/google/loader.ts b/packages/font/src/google/loader.ts index a3583d19ccbc7..63c9359ad81a5 100644 --- a/packages/font/src/google/loader.ts +++ b/packages/font/src/google/loader.ts @@ -2,7 +2,7 @@ import type { AdjustFontFallback, FontLoader } from 'next/font' // @ts-ignore import fetch from 'next/dist/compiled/node-fetch' // @ts-ignore -import { calculateOverrideValues } from 'next/dist/server/font-utils' +import { calculateSizeAdjustValues } from 'next/dist/server/font-utils' import { fetchCSSFromGoogleFonts, getFontAxes, @@ -101,16 +101,16 @@ const downloadGoogleFonts: FontLoader = async ({ let adjustFontFallbackMetrics: AdjustFontFallback | undefined if (adjustFontFallback) { try { - const { ascent, descent, lineGap, fallbackFont } = - calculateOverrideValues( - fontFamily, - require('next/dist/server/google-font-metrics.json') + const { ascent, descent, lineGap, fallbackFont, sizeAdjust } = + calculateSizeAdjustValues( + require('next/dist/server/google-font-metrics.json')[fontFamily] ) adjustFontFallbackMetrics = { fallbackFont, - ascentOverride: ascent, - descentOverride: descent, - lineGapOverride: lineGap, + ascentOverride: `${ascent}%`, + descentOverride: `${descent}%`, + lineGapOverride: `${lineGap}%`, + sizeAdjust: `${sizeAdjust}%`, } } catch { console.error( diff --git a/packages/font/src/local/index.ts b/packages/font/src/local/index.ts index c029157ce069d..805e1c382cebe 100644 --- a/packages/font/src/local/index.ts +++ b/packages/font/src/local/index.ts @@ -1,9 +1,9 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import type { AdjustFontFallback, FontModule } from 'next/font' +import type { FontModule } from 'next/font' type Display = 'auto' | 'block' | 'swap' | 'fallback' | 'optional' type CssVariable = `--${string}` type LocalFont = { - src: string | Array<{ file: string; unicodeRange: string }> + src: string display?: Display weight?: number style?: string @@ -20,7 +20,7 @@ type LocalFont = { lineGapOverride?: string sizeAdjust?: string - adjustFontFallback?: AdjustFontFallback + adjustFontFallback?: 'Arial' | 'Times New Roman' | false } export default function localFont(options: LocalFont): FontModule { diff --git a/packages/font/src/local/loader.ts b/packages/font/src/local/loader.ts index 78fdd6e5af2ec..eb897aa082e89 100644 --- a/packages/font/src/local/loader.ts +++ b/packages/font/src/local/loader.ts @@ -1,4 +1,9 @@ -import type { FontLoader } from 'next/font' +// @ts-ignore +import { calculateSizeAdjustValues } from 'next/dist/server/font-utils' +// @ts-ignore +// eslint-disable-next-line import/no-extraneous-dependencies +import fontFromBuffer from '@next/font/dist/fontkit' +import type { AdjustFontFallback, FontLoader } from 'next/font' import { promisify } from 'util' import { validateData } from './utils' @@ -12,7 +17,9 @@ const fetchFonts: FontLoader = async ({ }) => { const { family, - files, + src, + ext, + format, display, weight, style, @@ -28,45 +35,73 @@ const fetchFonts: FontLoader = async ({ adjustFontFallback, } = validateData(functionName, data) - const fontFaces = await Promise.all( - files.map(async ({ file, ext, format, unicodeRange }) => { - const resolved = await resolve(file) - const fileBuffer = await promisify(fs.readFile)(resolved) + const resolved = await resolve(src) + const fileBuffer = await promisify(fs.readFile)(resolved) + const fontUrl = emitFontFile(fileBuffer, ext, preload) - const fontUrl = emitFontFile(fileBuffer, ext, preload) + let fontMetadata: any + try { + fontMetadata = fontFromBuffer(fileBuffer) + } catch (e) { + console.error(`Failed to load font file: ${resolved}\n${e}`) + } + + // Add fallback font + let adjustFontFallbackMetrics: AdjustFontFallback | undefined + if (fontMetadata && adjustFontFallback !== false) { + const { + ascent, + descent, + lineGap, + fallbackFont, + sizeAdjust: fallbackSizeAdjust, + } = calculateSizeAdjustValues({ + category: + adjustFontFallback === 'Times New Roman' ? 'serif' : 'sans-serif', + ascent: fontMetadata.ascent, + descent: fontMetadata.descent, + lineGap: fontMetadata.lineGap, + unitsPerEm: fontMetadata.unitsPerEm, + xAvgCharWidth: (fontMetadata as any)['OS/2']?.xAvgCharWidth, + }) + adjustFontFallbackMetrics = { + fallbackFont, + ascentOverride: `${ascent}%`, + descentOverride: `${descent}%`, + lineGapOverride: `${lineGap}%`, + sizeAdjust: `${fallbackSizeAdjust}%`, + } + } - const fontFaceProperties = [ - ['font-family', `'${family}'`], - ['src', `url(${fontUrl}) format('${format}')`], - ['font-display', display], - ...(weight ? [['font-weight', weight]] : []), - ...(style ? [['font-style', style]] : []), - ...(ascentOverride ? [['ascent-override', ascentOverride]] : []), - ...(descentOverride ? [['descent-override', descentOverride]] : []), - ...(lineGapOverride ? [['line-gap-override', lineGapOverride]] : []), - ...(fontStretch ? [['font-stretch', fontStretch]] : []), - ...(fontFeatureSettings - ? [['font-feature-settings', fontFeatureSettings]] - : []), - ...(sizeAdjust ? [['size-adjust', sizeAdjust]] : []), - ...(unicodeRange ? [['unicode-range', unicodeRange]] : ''), - ] + const fontFaceProperties = [ + ['font-family', `'${fontMetadata?.familyName ?? family}'`], + ['src', `url(${fontUrl}) format('${format}')`], + ['font-display', display], + ...(weight ? [['font-weight', weight]] : []), + ...(style ? [['font-style', style]] : []), + ...(ascentOverride ? [['ascent-override', ascentOverride]] : []), + ...(descentOverride ? [['descent-override', descentOverride]] : []), + ...(lineGapOverride ? [['line-gap-override', lineGapOverride]] : []), + ...(fontStretch ? [['font-stretch', fontStretch]] : []), + ...(fontFeatureSettings + ? [['font-feature-settings', fontFeatureSettings]] + : []), + ...(sizeAdjust ? [['size-adjust', sizeAdjust]] : []), + ] - return `@font-face { + const css = `@font-face { ${fontFaceProperties .map(([property, value]) => `${property}: ${value};`) .join('\n')} }` - }) - ) return { - css: fontFaces.join('\n'), + css, fallbackFonts: fallback, weight, style, variable, - adjustFontFallback, + adjustFontFallback: adjustFontFallbackMetrics, } } diff --git a/packages/font/src/local/utils.ts b/packages/font/src/local/utils.ts index 5741a40c296dd..1ca7edffd016b 100644 --- a/packages/font/src/local/utils.ts +++ b/packages/font/src/local/utils.ts @@ -1,5 +1,3 @@ -import { AdjustFontFallback } from 'next/font' - const allowedDisplayValues = ['auto', 'block', 'swap', 'fallback', 'optional'] const formatValues = (values: string[]) => @@ -15,12 +13,9 @@ const extToFormat = { type FontOptions = { family: string - files: Array<{ - file: string - ext: string - format: string - unicodeRange?: string - }> + src: string + ext: string + format: string display: string weight?: number style?: string @@ -35,7 +30,7 @@ type FontOptions = { fontVariationSettings?: string lineGapOverride?: string sizeAdjust?: string - adjustFontFallback?: AdjustFontFallback + adjustFontFallback?: string | false } export function validateData(functionName: string, data: any): FontOptions { if (functionName) { @@ -68,39 +63,22 @@ export function validateData(functionName: string, data: any): FontOptions { ) } - const srcArray = Array.isArray(src) ? src : [{ file: src }] - - if (srcArray.length === 0) { - throw new Error('Src must contain one or more files') + if (!src) { + throw new Error('Missing required `src` property') } - const files = srcArray.map(({ file, unicodeRange }) => { - if (!file) { - throw new Error('Src array objects must have a `file` property') - } - if (srcArray.length > 1 && !unicodeRange) { - throw new Error( - "Files must have a unicode-range if there's more than one" - ) - } - - const ext = /\.(woff|woff2|eot|ttf|otf)$/.exec(file)?.[1] - if (!ext) { - throw new Error(`Unexpected file \`${file}\``) - } - return { - file, - unicodeRange, - ext, - format: extToFormat[ext as 'woff' | 'woff2' | 'eot' | 'ttf' | 'otf'], - } - }) + const ext = /\.(woff|woff2|eot|ttf|otf)$/.exec(src)?.[1] + if (!ext) { + throw new Error(`Unexpected file \`${src}\``) + } - const family = /.+\/(.+?)\./.exec(files[0].file)![1] + const family = /.+\/(.+?)\./.exec(src)![1] return { family, - files, + src, + ext, + format: extToFormat[ext as 'woff' | 'woff2' | 'eot' | 'ttf' | 'otf'], display, weight, style, diff --git a/packages/next/server/font-utils.ts b/packages/next/server/font-utils.ts index f37978575670f..5d672e2e3c95f 100644 --- a/packages/next/server/font-utils.ts +++ b/packages/next/server/font-utils.ts @@ -102,9 +102,8 @@ function formatOverrideValue(val: number) { return Math.abs(val * 100).toFixed(2) } -export function calculateOverrideValues(font: string, fontMetrics: any) { - const fontKey = font.trim() - let { category, ascent, descent, lineGap, unitsPerEm } = fontMetrics[fontKey] +export function calculateOverrideValues(fontMetrics: any) { + let { category, ascent, descent, lineGap, unitsPerEm } = fontMetrics const fallbackFont = category === 'serif' ? DEFAULT_SERIF_FONT : DEFAULT_SANS_SERIF_FONT ascent = formatOverrideValue(ascent / unitsPerEm) @@ -119,14 +118,15 @@ export function calculateOverrideValues(font: string, fontMetrics: any) { } } -export function calculateSizeAdjustValues(font: string, fontMetrics: any) { - const fontKey = font.trim() +export function calculateSizeAdjustValues(fontMetrics: any) { let { category, ascent, descent, lineGap, unitsPerEm, xAvgCharWidth } = - fontMetrics[fontKey] + fontMetrics const fallbackFont = category === 'serif' ? DEFAULT_SERIF_FONT : DEFAULT_SANS_SERIF_FONT - let sizeAdjust = xAvgCharWidth / fallbackFont.xAvgCharWidth + let sizeAdjust = xAvgCharWidth + ? xAvgCharWidth / fallbackFont.xAvgCharWidth + : 1 ascent = formatOverrideValue(ascent / (unitsPerEm * sizeAdjust)) descent = formatOverrideValue(descent / (unitsPerEm * sizeAdjust)) @@ -145,8 +145,7 @@ function calculateOverrideCSS(font: string, fontMetrics: any) { const fontName = font.trim() const { ascent, descent, lineGap, fallbackFont } = calculateOverrideValues( - font, - fontMetrics + fontMetrics[fontName] ) return ` @@ -164,7 +163,7 @@ function calculateSizeAdjustCSS(font: string, fontMetrics: any) { const fontName = font.trim() const { ascent, descent, lineGap, fallbackFont, sizeAdjust } = - calculateSizeAdjustValues(font, fontMetrics) + calculateSizeAdjustValues(fontMetrics[fontName]) return ` @font-face { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1227a73f65db3..e1316251fe06b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -429,7 +429,14 @@ importers: eslint: 7.24.0 packages/font: - specifiers: {} + specifiers: + '@types/fontkit': 2.0.0 + '@vercel/ncc': 0.34.0 + fontkit: 2.0.2 + devDependencies: + '@types/fontkit': 2.0.0 + '@vercel/ncc': 0.34.0 + fontkit: 2.0.2 packages/next: specifiers: @@ -596,8 +603,8 @@ importers: source-map: 0.6.1 stream-browserify: 3.0.0 stream-http: 3.1.1 - string_decoder: 1.3.0 string-hash: 1.1.3 + string_decoder: 1.3.0 strip-ansi: 6.0.0 styled-jsx: 5.0.7 tar: 6.1.11 @@ -786,8 +793,8 @@ importers: source-map: 0.6.1 stream-browserify: 3.0.0 stream-http: 3.1.1 - string_decoder: 1.3.0 string-hash: 1.1.3 + string_decoder: 1.3.0 strip-ansi: 6.0.0 tar: 6.1.11 taskr: 1.1.0 @@ -7343,6 +7350,15 @@ packages: } dev: true + /@types/fontkit/2.0.0: + resolution: + { + integrity: sha512-Qe+6szpPLTNsqkDFs2MScJyB51d5Hpobyg/T0QoUWO53WuNOTNLsV8fkE4QQPOJbhOMN5wlwmNeDdsh/e6Uqdg==, + } + dependencies: + '@types/node': 17.0.21 + dev: true + /@types/fresh/0.5.0: resolution: { @@ -10029,6 +10045,15 @@ packages: duplexer: 0.1.1 dev: true + /brotli/1.3.3: + resolution: + { + integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==, + } + dependencies: + base64-js: 1.5.1 + dev: true + /browser-process-hrtime/1.0.0: resolution: { @@ -11089,6 +11114,14 @@ packages: engines: { node: '>=0.8' } dev: true + /clone/2.1.2: + resolution: + { + integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==, + } + engines: { node: '>=0.8' } + dev: true + /clor/5.2.0: resolution: { @@ -11620,8 +11653,8 @@ packages: engines: { node: '>=10' } hasBin: true dependencies: - is-text-path: 1.0.1 JSONStream: 1.3.5 + is-text-path: 1.0.1 lodash: 4.17.21 meow: 8.1.2 split2: 2.2.0 @@ -12998,6 +13031,13 @@ packages: wrappy: 1.0.2 dev: true + /dfa/1.2.0: + resolution: + { + integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==, + } + dev: true + /diagnostics_channel/1.1.0: resolution: { @@ -15211,6 +15251,23 @@ packages: - supports-color dev: true + /fontkit/2.0.2: + resolution: + { + integrity: sha512-jc4k5Yr8iov8QfS6u8w2CnHWVmbOGtdBtOXMze5Y+QD966Rx6PEVWXSEGwXlsDlKtu1G12cJjcsybnqhSk/+LA==, + } + dependencies: + '@swc/helpers': 0.4.11 + brotli: 1.3.3 + clone: 2.1.2 + dfa: 1.2.0 + fast-deep-equal: 3.1.3 + restructure: 3.0.0 + tiny-inflate: 1.0.3 + unicode-properties: 1.4.1 + unicode-trie: 2.0.0 + dev: true + /for-in/1.0.2: resolution: { @@ -22823,6 +22880,13 @@ packages: - supports-color dev: true + /pako/0.2.9: + resolution: + { + integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==, + } + dev: true + /pako/1.0.11: resolution: { @@ -26338,6 +26402,13 @@ packages: signal-exit: 3.0.3 dev: true + /restructure/3.0.0: + resolution: + { + integrity: sha512-Xj8/MEIhhfj9X2rmD9iJ4Gga9EFqVlpMj3vfLnV2r/Mh5jRMryNV+6lWh9GdJtDBcBSPIqzRdfBQ3wDtNFv/uw==, + } + dev: true + /ret/0.1.15: resolution: { @@ -28595,6 +28666,13 @@ packages: globrex: 0.1.2 dev: true + /tiny-inflate/1.0.3: + resolution: + { + integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==, + } + dev: true + /tinydate/1.2.0: resolution: { @@ -29278,6 +29356,16 @@ packages: } engines: { node: '>=4' } + /unicode-properties/1.4.1: + resolution: + { + integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==, + } + dependencies: + base64-js: 1.5.1 + unicode-trie: 2.0.0 + dev: true + /unicode-property-aliases-ecmascript/1.0.5: resolution: { @@ -29293,6 +29381,16 @@ packages: } engines: { node: '>=4' } + /unicode-trie/2.0.0: + resolution: + { + integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==, + } + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + dev: true + /unified-diff/3.1.0: resolution: { diff --git a/test/e2e/next-font/app/fonts/my-font.woff2 b/test/e2e/next-font/app/fonts/my-font.woff2 index 7f7c21b8917a6..a6b3c3a9d69fa 100644 Binary files a/test/e2e/next-font/app/fonts/my-font.woff2 and b/test/e2e/next-font/app/fonts/my-font.woff2 differ diff --git a/test/e2e/next-font/app/fonts/my-other-font.woff b/test/e2e/next-font/app/fonts/my-other-font.woff deleted file mode 100644 index bf8c513058c62..0000000000000 --- a/test/e2e/next-font/app/fonts/my-other-font.woff +++ /dev/null @@ -1 +0,0 @@ -my other fake font \ No newline at end of file diff --git a/test/e2e/next-font/app/fonts/my-other-font.woff2 b/test/e2e/next-font/app/fonts/my-other-font.woff2 new file mode 100644 index 0000000000000..ac8adf10ed942 Binary files /dev/null and b/test/e2e/next-font/app/fonts/my-other-font.woff2 differ diff --git a/test/e2e/next-font/app/pages/with-google-fonts.js b/test/e2e/next-font/app/pages/with-google-fonts.js new file mode 100644 index 0000000000000..67cd042eb1b5d --- /dev/null +++ b/test/e2e/next-font/app/pages/with-google-fonts.js @@ -0,0 +1,17 @@ +import { Fraunces, Indie_Flower } from '@next/font/google' + +const indieFlower = Indie_Flower({ variant: '400' }) +const fraunces = Fraunces({ variant: '400' }) + +export default function WithFonts() { + return ( + <> +
+ {JSON.stringify(indieFlower)} +
+
+ {JSON.stringify(fraunces)} +
+ + ) +} diff --git a/test/e2e/next-font/app/pages/with-local-fonts.js b/test/e2e/next-font/app/pages/with-local-fonts.js index eb7eee392f7a1..a6438d69e8140 100644 --- a/test/e2e/next-font/app/pages/with-local-fonts.js +++ b/test/e2e/next-font/app/pages/with-local-fonts.js @@ -5,15 +5,12 @@ const myFont1 = localFont({ style: 'italic', weight: 100, fallback: ['system-ui'], + adjustFontFallback: 'Times New Roman', }) const myFont2 = localFont({ - src: '../fonts/my-other-font.woff', + src: '../fonts/my-other-font.woff2', preload: false, variable: '--my-font', - adjustFontFallback: { - fallbackFont: 'Arial', - sizeAdjust: '120%', - }, }) export default function WithFonts() { diff --git a/test/e2e/next-font/google-font-mocked-responses.js b/test/e2e/next-font/google-font-mocked-responses.js index 732813f2401c2..ad932d5e13ea2 100644 --- a/test/e2e/next-font/google-font-mocked-responses.js +++ b/test/e2e/next-font/google-font-mocked-responses.js @@ -493,4 +493,40 @@ module.exports = { unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } `, + 'https://fonts.googleapis.com/css2?family=Fraunces:wght@400&display=optional': `/* vietnamese */ + @font-face { + font-family: 'Fraunces'; + font-style: normal; + font-weight: 400; + font-display: optional; + src: url(https://fonts.gstatic.com/s/fraunces/v24/6NUh8FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0K7iN7hzFUPJH58nib1603gg7S2nfgRYIctxuTBv7Tp05GNyXkb24.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; + } + /* latin-ext */ + @font-face { + font-family: 'Fraunces'; + font-style: normal; + font-weight: 400; + font-display: optional; + src: url(https://fonts.gstatic.com/s/fraunces/v24/6NUh8FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0K7iN7hzFUPJH58nib1603gg7S2nfgRYIctxuTB_7Tp05GNyXkb24.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; + } + /* latin */ + @font-face { + font-family: 'Fraunces'; + font-style: normal; + font-weight: 400; + font-display: optional; + src: url(https://fonts.gstatic.com/s/fraunces/v24/6NUh8FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0K7iN7hzFUPJH58nib1603gg7S2nfgRYIctxuTCf7Tp05GNyXk.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + }`, + 'https://fonts.googleapis.com/css2?family=Indie+Flower:wght@400&display=optional': `/* latin */ + @font-face { + font-family: 'Indie Flower'; + font-style: normal; + font-weight: 400; + font-display: optional; + src: url(https://fonts.gstatic.com/s/indieflower/v17/m8JVjfNVeKWVnh3QMuKkFcZVaUuH99GUDg.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + }`, } diff --git a/test/e2e/next-font/index.test.ts b/test/e2e/next-font/index.test.ts index d53509586672a..5cee69bc642e0 100644 --- a/test/e2e/next-font/index.test.ts +++ b/test/e2e/next-font/index.test.ts @@ -103,7 +103,9 @@ describe('@next/font/google', () => { expect(JSON.parse($('#first-local-font').text())).toEqual({ className: expect.stringMatching(/__className_.{6}/), style: { - fontFamily: expect.stringMatching(/^'__my-font_.{6}', system-ui$/), + fontFamily: expect.stringMatching( + /^'__Fraunces_.{6}', system-ui, '__Fraunces_Fallback_.{6}'$/ + ), fontStyle: 'italic', fontWeight: 100, }, @@ -113,7 +115,7 @@ describe('@next/font/google', () => { variable: expect.stringMatching(/^__variable_.{6}$/), style: { fontFamily: expect.stringMatching( - /^'__my-other-font_.{6}', '__my-other-font_Fallback_.{6}'$/ + /^'__Indie_Flower_.{6}', '__Indie_Flower_Fallback_.{6}'$/ ), }, }) @@ -232,7 +234,7 @@ describe('@next/font/google', () => { ).not.toMatch(roboto100ItalicRegex) // Local font - const localFontRegex = /^__my-font_.{6}$/ + const localFontRegex = /^__Fraunces_.{6}, __Fraunces_Fallback_.{6}$/ expect( await browser.eval( 'getComputedStyle(document.querySelector("#variables-local-font")).fontFamily' @@ -342,10 +344,112 @@ describe('@next/font/google', () => { expect($('link[as="font"]').get(1).attribs).toEqual({ as: 'font', crossorigin: 'anonymous', - href: '/_next/static/media/7be88d77534e80fd.p.woff2', + href: '/_next/static/media/ab6fdae82d1a8d92.p.woff2', rel: 'preload', type: 'font/woff2', }) }) }) + + describe('Fallback fontfaces', () => { + describe('local', () => { + test('Indie flower', async () => { + const browser = await webdriver(next.url, '/with-local-fonts') + + const ascentOverride = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Indie_Flower_Fallback")).ascentOverride' + ) + expect(ascentOverride).toBe('185.52%') + + const descentOverride = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Indie_Flower_Fallback")).descentOverride' + ) + expect(descentOverride).toBe('93.32%') + + const lineGapOverride = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Indie_Flower_Fallback")).lineGapOverride' + ) + expect(lineGapOverride).toBe('0%') + + const sizeAdjust = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Indie_Flower_Fallback")).sizeAdjust' + ) + expect(sizeAdjust).toBe('52.32%') + }) + + test('Fraunces', async () => { + const browser = await webdriver(next.url, '/with-local-fonts') + + const ascentOverride = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Fraunces_Fallback")).ascentOverride' + ) + expect(ascentOverride).toBe('63.47%') + + const descentOverride = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Fraunces_Fallback")).descentOverride' + ) + expect(descentOverride).toBe('16.55%') + + const lineGapOverride = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Fraunces_Fallback")).lineGapOverride' + ) + expect(lineGapOverride).toBe('0%') + + const sizeAdjust = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Fraunces_Fallback")).sizeAdjust' + ) + expect(sizeAdjust).toBe('154.08%') + }) + }) + + describe('google', () => { + test('Indie flower', async () => { + const browser = await webdriver(next.url, '/with-google-fonts') + + const ascentOverride = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Indie_Flower_Fallback")).ascentOverride' + ) + expect(ascentOverride).toBe('185.52%') + + const descentOverride = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Indie_Flower_Fallback")).descentOverride' + ) + expect(descentOverride).toBe('93.32%') + + const lineGapOverride = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Indie_Flower_Fallback")).lineGapOverride' + ) + expect(lineGapOverride).toBe('0%') + + const sizeAdjust = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Indie_Flower_Fallback")).sizeAdjust' + ) + expect(sizeAdjust).toBe('52.32%') + }) + + test('Fraunces', async () => { + const browser = await webdriver(next.url, '/with-google-fonts') + + const ascentOverride = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Fraunces_Fallback")).ascentOverride' + ) + expect(ascentOverride).toBe('63.47%') + + const descentOverride = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Fraunces_Fallback")).descentOverride' + ) + expect(descentOverride).toBe('16.55%') + + const lineGapOverride = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Fraunces_Fallback")).lineGapOverride' + ) + expect(lineGapOverride).toBe('0%') + + const sizeAdjust = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Fraunces_Fallback")).sizeAdjust' + ) + expect(sizeAdjust).toBe('154.08%') + }) + }) + }) }) diff --git a/test/unit/google-font-loader.test.ts b/test/unit/google-font-loader.test.ts index f51772c7ea446..3bb967e7342ac 100644 --- a/test/unit/google-font-loader.test.ts +++ b/test/unit/google-font-loader.test.ts @@ -102,10 +102,11 @@ describe('@next/font/google loader', () => { fs: {} as any, }) expect(adjustFontFallback).toEqual({ - ascentOverride: '96.88', - descentOverride: '24.15', + ascentOverride: '47.65%', + descentOverride: '11.88%', fallbackFont: 'Arial', - lineGapOverride: '0.00', + lineGapOverride: '0.00%', + sizeAdjust: '203.32%', }) expect(fallbackFonts).toBeUndefined() }) @@ -124,10 +125,11 @@ describe('@next/font/google loader', () => { fs: {} as any, }) expect(adjustFontFallback).toEqual({ - ascentOverride: '98.40', - descentOverride: '27.30', + ascentOverride: '148.26%', + descentOverride: '41.13%', fallbackFont: 'Arial', - lineGapOverride: '0.00', + lineGapOverride: '0.00%', + sizeAdjust: '66.37%', }) expect(fallbackFonts).toBeUndefined() }) @@ -146,10 +148,11 @@ describe('@next/font/google loader', () => { fs: {} as any, }) expect(adjustFontFallback).toEqual({ - ascentOverride: '97.80', - descentOverride: '25.50', + ascentOverride: '63.47%', + descentOverride: '16.55%', fallbackFont: 'Times New Roman', - lineGapOverride: '0.00', + lineGapOverride: '0.00%', + sizeAdjust: '154.08%', }) expect(fallbackFonts).toEqual(['Abc', 'Def']) }) diff --git a/test/unit/local-font-loader.test.ts b/test/unit/local-font-loader.test.ts index b314024d8de38..72096968818e7 100644 --- a/test/unit/local-font-loader.test.ts +++ b/test/unit/local-font-loader.test.ts @@ -23,51 +23,6 @@ font-display: optional; `) }) - test('Default CSS - src array with unicodeRange', async () => { - const { css } = await loader({ - functionName: '', - data: [ - { src: [{ file: './my-font.woff2', unicodeRange: 'unicode-range' }] }, - ], - config: {}, - emitFontFile: () => '/_next/static/media/my-font.woff2', - resolve: jest.fn(), - fs: { - readFile: (_, cb) => cb(null, 'fontdata'), - }, - }) - - expect(css).toMatchInlineSnapshot(` -"@font-face { -font-family: 'my-font'; -src: url(/_next/static/media/my-font.woff2) format('woff2'); -font-display: optional; -unicode-range: unicode-range; -}" -`) - }) - - test('Default CSS - src array without unicodeRange', async () => { - const { css } = await loader({ - functionName: '', - data: [{ src: [{ file: './my-font.woff2' }] }], - config: {}, - emitFontFile: () => '/_next/static/media/my-font.woff2', - resolve: jest.fn(), - fs: { - readFile: (_, cb) => cb(null, 'fontdata'), - }, - }) - - expect(css).toMatchInlineSnapshot(` -"@font-face { -font-family: 'my-font'; -src: url(/_next/static/media/my-font.woff2) format('woff2'); -font-display: optional; -}" -`) - }) - test('Weight and style', async () => { const { css } = await loader({ functionName: '', @@ -129,62 +84,6 @@ font-stretch: fontStretch; font-feature-settings: fontFeatureSettings; size-adjust: sizeAdjust; }" -`) - }) - - test('Multiple files', async () => { - const { css } = await loader({ - functionName: '', - data: [ - { - src: [ - { file: './my-font1.woff', unicodeRange: '1' }, - { file: './my-font2.woff2', unicodeRange: '2' }, - { file: './my-font3.eot', unicodeRange: '3' }, - { file: './my-font4.ttf', unicodeRange: '4' }, - { file: './my-font5.otf', unicodeRange: '5' }, - ], - }, - ], - config: {}, - emitFontFile: () => `/_next/static/media/font-file`, - resolve: jest.fn(), - fs: { - readFile: (_, cb) => cb(null, 'fontdata'), - }, - }) - - expect(css).toMatchInlineSnapshot(` -"@font-face { -font-family: 'my-font1'; -src: url(/_next/static/media/font-file) format('woff'); -font-display: optional; -unicode-range: 1; -} -@font-face { -font-family: 'my-font1'; -src: url(/_next/static/media/font-file) format('woff2'); -font-display: optional; -unicode-range: 2; -} -@font-face { -font-family: 'my-font1'; -src: url(/_next/static/media/font-file) format('embedded-opentype'); -font-display: optional; -unicode-range: 3; -} -@font-face { -font-family: 'my-font1'; -src: url(/_next/static/media/font-file) format('truetype'); -font-display: optional; -unicode-range: 4; -} -@font-face { -font-family: 'my-font1'; -src: url(/_next/static/media/font-file) format('opentype'); -font-display: optional; -unicode-range: 5; -}" `) }) }) @@ -235,81 +134,5 @@ unicode-range: 5; Available display values: \`auto\`, \`block\`, \`swap\`, \`fallback\`, \`optional\`" `) }) - - test('Empty src array', async () => { - await expect( - loader({ - functionName: '', - data: [{ src: [] }], - config: {}, - emitFontFile: jest.fn(), - resolve: jest.fn(), - fs: {}, - }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Src must contain one or more files"` - ) - }) - - test('Src array must have one or more elements', async () => { - await expect( - loader({ - functionName: '', - data: [{ src: [] }], - config: {}, - emitFontFile: jest.fn(), - resolve: jest.fn(), - fs: {}, - }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Src must contain one or more files"` - ) - }) - - test('Src array elements must have file property', async () => { - await expect( - loader({ - functionName: '', - data: [ - { - src: [ - { file: './my-font1.woff2', unicodeRange: '1' }, - { unicodeRange: '2' }, - ], - }, - ], - - config: {}, - emitFontFile: jest.fn(), - resolve: jest.fn(), - fs: {}, - }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Src array objects must have a \`file\` property"` - ) - }) - - test("Src array files must have unicodeRange if there's many files", async () => { - await expect( - loader({ - functionName: '', - data: [ - { - src: [ - { file: './my-font1.woff2', unicodeRange: '1' }, - { file: './my-font2.woff2' }, - ], - }, - ], - - config: {}, - emitFontFile: jest.fn(), - resolve: jest.fn(), - fs: {}, - }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Files must have a unicode-range if there's more than one"` - ) - }) }) })