Skip to content

Commit

Permalink
@next/font/google variable fonts without weight range (#43036)
Browse files Browse the repository at this point in the history
A few variable fonts don't have a weight range, in those cases a `wght`
axis shouldn't be set at all. Just add optional additional axes.

Some fonts set a CSS variable on a `body` tag, ignore it to keep the CSS
module pure.

Fixes #43040

## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have a helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the
feature request has been accepted for implementation before opening a
PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have a helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `pnpm build && pnpm lint`
- [ ] The "examples guidelines" are followed from [our contributing
doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
  • Loading branch information
Hannes Bornö committed Nov 17, 2022
1 parent 8e7c45b commit 397894b
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 25 deletions.
5 changes: 4 additions & 1 deletion packages/font/src/google/loader.ts
Expand Up @@ -95,7 +95,7 @@ const downloadGoogleFonts: FontLoader = async ({

try {
const hasCachedCSS = cssCache.has(url)
const fontFaceDeclarations = hasCachedCSS
let fontFaceDeclarations = hasCachedCSS
? cssCache.get(url)
: await fetchCSSFromGoogleFonts(url, fontFamily).catch(() => null)
if (!hasCachedCSS) {
Expand All @@ -107,6 +107,9 @@ const downloadGoogleFonts: FontLoader = async ({
throw new Error(`Failed to fetch \`${fontFamily}\` from Google Fonts.`)
}

// CSS Variables may be set on a body tag, ignore them to keep the CSS module pure
fontFaceDeclarations = fontFaceDeclarations.split('body {')[0]

// Find font files to download
const fontFiles: Array<{
googleFontFileUrl: string
Expand Down
62 changes: 39 additions & 23 deletions packages/font/src/google/utils.ts
Expand Up @@ -127,26 +127,31 @@ export function validateData(functionName: string, data: any): FontOptions {
export function getUrl(
fontFamily: string,
axes: {
wght: string[]
ital: string[]
wght?: string[]
ital?: string[]
variableAxes?: [string, string][]
},
display: string
) {
// Variants are all combinations of weight and style, each variant will result in a separate font file
const variants: Array<[string, string][]> = []
for (const wgth of axes.wght) {
if (axes.ital.length === 0) {
variants.push([['wght', wgth], ...(axes.variableAxes ?? [])])
} else {
for (const ital of axes.ital) {
variants.push([
['ital', ital],
['wght', wgth],
...(axes.variableAxes ?? []),
])
if (axes.wght) {
for (const wgth of axes.wght) {
if (!axes.ital) {
variants.push([['wght', wgth], ...(axes.variableAxes ?? [])])
} else {
for (const ital of axes.ital) {
variants.push([
['ital', ital],
['wght', wgth],
...(axes.variableAxes ?? []),
])
}
}
}
} else if (axes.variableAxes) {
// Variable fonts might not have a range of weights, just add optional variable axes in that case
variants.push([...axes.variableAxes])
}

// Google api requires the axes to be sorted, starting with lowercase words
Expand All @@ -163,13 +168,21 @@ export function getUrl(
})
}

return `https://fonts.googleapis.com/css2?family=${fontFamily.replace(
let url = `https://fonts.googleapis.com/css2?family=${fontFamily.replace(
/ /g,
'+'
)}:${variants[0].map(([key]) => key).join(',')}@${variants
.map((variant) => variant.map(([, val]) => val).join(','))
.sort()
.join(';')}&display=${display}`
)}`

if (variants.length > 0) {
url = `${url}:${variants[0].map(([key]) => key).join(',')}@${variants
.map((variant) => variant.map(([, val]) => val).join(','))
.sort()
.join(';')}`
}

url = `${url}&display=${display}`

return url
}

export async function fetchCSSFromGoogleFonts(url: string, fontFamily: string) {
Expand Down Expand Up @@ -221,16 +234,16 @@ export function getFontAxes(
styles: string[],
selectedVariableAxes?: string[]
): {
wght: string[]
ital: string[]
wght?: string[]
ital?: string[]
variableAxes?: [string, string][]
} {
const allAxes: Array<{ tag: string; min: number; max: number }> = (
fontData as any
)[fontFamily].axes
const hasItalic = styles.includes('italic')
const hasNormal = styles.includes('normal')
const ital = hasItalic ? [...(hasNormal ? ['0'] : []), '1'] : []
const ital = hasItalic ? [...(hasNormal ? ['0'] : []), '1'] : undefined

// Weights will always contain one element if it's a variable font
if (weights[0] === 'variable') {
Expand Down Expand Up @@ -259,18 +272,21 @@ export function getFontAxes(
})
}

let weightAxis: string
const variableAxes: [string, string][] = []
let weightAxis: string | undefined
let variableAxes: [string, string][] | undefined
for (const { tag, min, max } of allAxes) {
if (tag === 'wght') {
weightAxis = `${min}..${max}`
} else if (selectedVariableAxes?.includes(tag)) {
if (!variableAxes) {
variableAxes = []
}
variableAxes.push([tag, `${min}..${max}`])
}
}

return {
wght: [weightAxis!],
wght: weightAxis ? [weightAxis] : undefined,
ital,
variableAxes,
}
Expand Down
11 changes: 11 additions & 0 deletions test/e2e/next-font/app/pages/variable-font-without-weight-range.js
@@ -0,0 +1,11 @@
import { Nabla } from '@next/font/google'

const nabla = Nabla()

export default function VariableFontWithoutWeightRange() {
return (
<p id="nabla" className={nabla.className}>
{JSON.stringify(nabla)}
</p>
)
}
50 changes: 50 additions & 0 deletions test/e2e/next-font/google-font-mocked-responses.js
Expand Up @@ -938,4 +938,54 @@ module.exports = {
`,
'https://fonts.googleapis.com/css2?family=Inter:wght@400&display=optional':
null,
'https://fonts.googleapis.com/css2?family=Nabla&display=optional': `
/* cyrillic-ext */
@font-face {
font-family: 'Nabla';
font-style: normal;
font-weight: 400;
font-display: optional;
src: url(https://fonts.gstatic.com/s/nabla/v9/j8_D6-LI0Lvpe7Makz5UhJt9C3uqg_X_75gyGS4jAxsNIjrRBRpeFXZ6x96OvGloBgM.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* math */
@font-face {
font-family: 'Nabla';
font-style: normal;
font-weight: 400;
font-display: optional;
src: url(https://fonts.gstatic.com/s/nabla/v9/j8_D6-LI0Lvpe7Makz5UhJt9C3uqg_X_75gyGS4jAxsNIjrRBWteFXZ6x96OvGloBgM.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0330, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2034-2037, U+2057, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2102, U+210A-210E, U+2110-2112, U+2115, U+2119-211D, U+2124, U+2128, U+212C-212D, U+212F-2131, U+2133-2138, U+213C-2140, U+2145-2149, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B6, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* vietnamese */
@font-face {
font-family: 'Nabla';
font-style: normal;
font-weight: 400;
font-display: optional;
src: url(https://fonts.gstatic.com/s/nabla/v9/j8_D6-LI0Lvpe7Makz5UhJt9C3uqg_X_75gyGS4jAxsNIjrRBRheFXZ6x96OvGloBgM.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: 'Nabla';
font-style: normal;
font-weight: 400;
font-display: optional;
src: url(https://fonts.gstatic.com/s/nabla/v9/j8_D6-LI0Lvpe7Makz5UhJt9C3uqg_X_75gyGS4jAxsNIjrRBRleFXZ6x96OvGloBgM.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: 'Nabla';
font-style: normal;
font-weight: 400;
font-display: optional;
src: url(https://fonts.gstatic.com/s/nabla/v9/j8_D6-LI0Lvpe7Makz5UhJt9C3uqg_X_75gyGS4jAxsNIjrRBRdeFXZ6x96OvAFr.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;
}
body {
--google-font-color-nabla:colrv1;
}
`,
}
16 changes: 16 additions & 0 deletions test/e2e/next-font/index.test.ts
Expand Up @@ -124,6 +124,22 @@ describe('@next/font/google', () => {
},
})
})

test('Variable font without weight range', async () => {
const html = await renderViaHTTP(
next.url,
'/variable-font-without-weight-range'
)
const $ = cheerio.load(html)

expect(JSON.parse($('#nabla').text())).toEqual({
className: expect.stringMatching(/__className_.{6}/),
style: {
fontFamily: expect.stringMatching(/^'__Nabla_.{6}'$/),
fontStyle: 'normal',
},
})
})
})

describe('computed styles', () => {
Expand Down
16 changes: 15 additions & 1 deletion test/unit/google-font-loader.test.ts
Expand Up @@ -91,12 +91,26 @@ describe('@next/font/google loader', () => {
},
'https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght,SOFT,WONK@0,9..144,100..900,0..100,0..1;1,9..144,100..900,0..100,0..1&display=optional',
],

[
'Poppins',
{ weight: ['900', '400', '100'] },
'https://fonts.googleapis.com/css2?family=Poppins:wght@100;400;900&display=optional',
],
[
'Nabla',
{},
'https://fonts.googleapis.com/css2?family=Nabla&display=optional',
],
[
'Nabla',
{ axes: ['EDPT', 'EHLT'] },
'https://fonts.googleapis.com/css2?family=Nabla:EDPT,EHLT@0..200,0..24&display=optional',
],
[
'Ballet',
{},
'https://fonts.googleapis.com/css2?family=Ballet&display=optional',
],
])('%s', async (functionName: string, data: any, url: string) => {
fetch.mockResolvedValue({
ok: true,
Expand Down

0 comments on commit 397894b

Please sign in to comment.