Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Google fonts multiple weights & styles #42008

Merged
merged 6 commits into from Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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