Skip to content

Commit

Permalink
Google fonts multiple weights & styles (#42008)
Browse files Browse the repository at this point in the history
Enable using multiple weights and styles in the same google font loader
call.

## 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 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 Oct 27, 2022
1 parent b9140ed commit 4b00495
Show file tree
Hide file tree
Showing 8 changed files with 5,318 additions and 2,850 deletions.
7,884 changes: 5,113 additions & 2,771 deletions packages/font/src/google/index.ts

Large diffs are not rendered by default.

73 changes: 42 additions & 31 deletions packages/font/src/google/loader.ts
@@ -1,19 +1,18 @@
import type { AdjustFontFallback, FontLoader } from 'next/font'
// @ts-ignore
import { calculateSizeAdjustValues } from 'next/dist/server/font-utils'
// @ts-ignore
import * as Log from 'next/dist/build/output/log'
// @ts-ignore
import chalk from 'next/dist/compiled/chalk'
// @ts-ignore
// eslint-disable-next-line import/no-extraneous-dependencies
import fontFromBuffer from '@next/font/dist/fontkit'
import {
fetchCSSFromGoogleFonts,
fetchFontFile,
getFontAxes,
getUrl,
validateData,
} from './utils'
import { calculateFallbackFontValues } from '../utils'

const cssCache = new Map<string, Promise<string>>()
const fontCache = new Map<string, any>()
Expand All @@ -29,8 +28,8 @@ const downloadGoogleFonts: FontLoader = async ({

const {
fontFamily,
weight,
style,
weights,
styles,
display,
preload,
selectedVariableAxes,
Expand All @@ -50,23 +49,33 @@ const downloadGoogleFonts: FontLoader = async ({
)
}

const fontAxes = getFontAxes(fontFamily, weight, style, selectedVariableAxes)
const url = getUrl(fontFamily, fontAxes, display)
let fontFaceDeclarations = ''
for (const weight of weights) {
for (const style of styles) {
const fontAxes = getFontAxes(
fontFamily,
weight,
style,
selectedVariableAxes
)
const url = getUrl(fontFamily, fontAxes, display)

let cachedCssRequest = cssCache.get(url)
const fontFaceDeclarations =
cachedCssRequest ?? (await fetchCSSFromGoogleFonts(url, fontFamily))
if (!cachedCssRequest) {
cssCache.set(url, fontFaceDeclarations)
} else {
cssCache.delete(url)
let cachedCssRequest = cssCache.get(url)
const fontFaceDeclaration =
cachedCssRequest ?? (await fetchCSSFromGoogleFonts(url, fontFamily))
if (!cachedCssRequest) {
cssCache.set(url, fontFaceDeclaration)
} else {
cssCache.delete(url)
}
fontFaceDeclarations += `${fontFaceDeclaration}\n`
}
}

// Find font files to download
const fontFiles: Array<{
googleFontFileUrl: string
preloadFontFile: boolean
isLatin: boolean
}> = []
let currentSubset = ''
for (const line of fontFaceDeclarations.split('\n')) {
Expand All @@ -81,24 +90,17 @@ const downloadGoogleFonts: FontLoader = async ({
googleFontFileUrl,
preloadFontFile:
!!preload && (callSubsets ?? subsets).includes(currentSubset),
isLatin: currentSubset === 'latin',
})
}
}
}

// Download font files
let latinFont: any
const downloadedFiles = await Promise.all(
fontFiles.map(async ({ googleFontFileUrl, preloadFontFile, isLatin }) => {
fontFiles.map(async ({ googleFontFileUrl, preloadFontFile }) => {
let cachedFontRequest = fontCache.get(googleFontFileUrl)
const fontFileBuffer =
cachedFontRequest ?? (await fetchFontFile(googleFontFileUrl))
if (isLatin) {
try {
latinFont = fontFromBuffer(fontFileBuffer)
} catch {}
}
if (!cachedFontRequest) {
fontCache.set(googleFontFileUrl, fontFileBuffer)
} else {
Expand Down Expand Up @@ -131,13 +133,19 @@ const downloadGoogleFonts: FontLoader = async ({

// Add fallback font
let adjustFontFallbackMetrics: AdjustFontFallback | undefined
if (adjustFontFallback && latinFont) {
if (adjustFontFallback) {
try {
adjustFontFallbackMetrics = calculateFallbackFontValues(
latinFont,
require('next/dist/server/google-font-metrics.json')[fontFamily]
.category
)
const { ascent, descent, lineGap, fallbackFont, sizeAdjust } =
calculateSizeAdjustValues(
require('next/dist/server/google-font-metrics.json')[fontFamily]
)
adjustFontFallbackMetrics = {
fallbackFont,
ascentOverride: `${ascent}%`,
descentOverride: `${descent}%`,
lineGapOverride: `${lineGap}%`,
sizeAdjust: `${sizeAdjust}%`,
}
} catch {
Log.error(
`Failed to find font override values for font \`${fontFamily}\``
Expand All @@ -148,8 +156,11 @@ const downloadGoogleFonts: FontLoader = async ({
return {
css: updatedCssResponse,
fallbackFonts: fallback,
weight: weight === 'variable' ? undefined : weight,
style,
weight:
weights.length === 1 && weights[0] !== 'variable'
? weights[0]
: undefined,
style: styles.length === 1 ? styles[0] : undefined,
variable,
adjustFontFallback: adjustFontFallbackMetrics,
}
Expand Down
63 changes: 41 additions & 22 deletions packages/font/src/google/utils.ts
Expand Up @@ -9,8 +9,8 @@ const formatValues = (values: string[]) =>

type FontOptions = {
fontFamily: string
weight: string
style: string
weights: string[]
styles: string[]
display: string
preload: boolean
selectedVariableAxes?: string[]
Expand Down Expand Up @@ -44,10 +44,17 @@ export function validateData(functionName: string, data: any): FontOptions {
}
const fontStyles = fontFamilyData.styles

// Set variable as default, throw if not available
if (!weight) {
const weights = !weight
? []
: [...new Set(Array.isArray(weight) ? weight : [weight])]
const styles = !style
? []
: [...new Set(Array.isArray(style) ? style : [style])]

if (weights.length === 0) {
// Set variable as default, throw if not available
if (fontWeights.includes('variable')) {
weight = 'variable'
weights.push('variable')
} else {
throw new Error(
`Missing weight for font \`${fontFamily}\`.\nAvailable weights: ${formatValues(
Expand All @@ -56,28 +63,40 @@ export function validateData(functionName: string, data: any): FontOptions {
)
}
}
if (!fontWeights.includes(weight)) {

if (weights.length > 1 && weights.includes('variable')) {
throw new Error(
`Unknown weight \`${weight}\` for font \`${fontFamily}\`.\nAvailable weights: ${formatValues(
fontWeights
)}`
`Unexpected \`variable\` in weight array for font \`${fontFamily}\`. You only need \`variable\`, it includes all available weights.`
)
}

if (!style) {
weights.forEach((selectedWeight) => {
if (!fontWeights.includes(selectedWeight)) {
throw new Error(
`Unknown weight \`${selectedWeight}\` for font \`${fontFamily}\`.\nAvailable weights: ${formatValues(
fontWeights
)}`
)
}
})

if (styles.length === 0) {
if (fontStyles.length === 1) {
style = fontStyles[0]
styles.push(fontStyles[0])
} else {
style = 'normal'
styles.push('normal')
}
}
if (!fontStyles.includes(style)) {
throw new Error(
`Unknown style \`${style}\` for font \`${fontFamily}\`.\nAvailable styles: ${formatValues(
fontStyles
)}`
)
}

styles.forEach((selectedStyle) => {
if (!fontStyles.includes(selectedStyle)) {
throw new Error(
`Unknown style \`${selectedStyle}\` for font \`${fontFamily}\`.\nAvailable styles: ${formatValues(
fontStyles
)}`
)
}
})

if (!allowedDisplayValues.includes(display)) {
throw new Error(
Expand All @@ -87,14 +106,14 @@ export function validateData(functionName: string, data: any): FontOptions {
)
}

if (weight !== 'variable' && axes) {
if (weights[0] !== 'variable' && axes) {
throw new Error('Axes can only be defined for variable fonts')
}

return {
fontFamily,
weight,
style,
weights,
styles,
display,
preload,
selectedVariableAxes: axes,
Expand Down
4 changes: 2 additions & 2 deletions packages/next/shared/lib/constants.ts
Expand Up @@ -100,13 +100,13 @@ export const OPTIMIZED_FONT_PROVIDERS = [
export const DEFAULT_SERIF_FONT = {
name: 'Times New Roman',
xAvgCharWidth: 821,
azAvgWidth: 940.6538,
azAvgWidth: 854.3953488372093,
unitsPerEm: 2048,
}
export const DEFAULT_SANS_SERIF_FONT = {
name: 'Arial',
xAvgCharWidth: 904,
azAvgWidth: 1002.5769,
azAvgWidth: 934.5116279069767,
unitsPerEm: 2048,
}
export const STATIC_STATUS_PAGES = ['/500']
Expand Down
21 changes: 15 additions & 6 deletions scripts/update-google-fonts.js
Expand Up @@ -49,23 +49,32 @@ const fetch = require('node-fetch')
axes: hasVariableFont ? axes : undefined,
}
const optionalIfVariableFont = hasVariableFont ? '?' : ''

const formatUnion = (values) =>
values.map((value) => `"${value}"`).join('|')

const weightTypes = [...weights]
const styleTypes = [...styles]

fontFunctions += `export declare function ${family.replaceAll(
' ',
'_'
)}(options${optionalIfVariableFont}: {
weight${optionalIfVariableFont}:${[...weights]
.map((weight) => `"${weight}"`)
.join('|')}
style?: ${[...styles].map((style) => `"${style}"`).join('|')}
weight${optionalIfVariableFont}:${formatUnion(
weightTypes
)} | Array<${formatUnion(
weightTypes.filter((weight) => weight !== 'variable')
)}>
style?: ${formatUnion(styleTypes)} | Array<${formatUnion(styleTypes)}>
display?:Display
variable?: CssVariable
preload?:boolean
fallback?: string[]
adjustFontFallback?: boolean
subsets?: Array<${subsets.map((subset) => `"${subset}"`).join('|')}>
subsets?: Array<${formatUnion(subsets)}>
${
optionalAxes
? `axes?:(${optionalAxes.map(({ tag }) => `'${tag}'`).join('|')})[]`
? `axes?:(${formatUnion(optionalAxes.map(({ tag }) => tag))})[]`
: ''
}
}):FontModule
Expand Down

0 comments on commit 4b00495

Please sign in to comment.