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

Local font files adjust fallbacks #41180

Merged
merged 11 commits into from Oct 10, 2022
Merged
Show file tree
Hide file tree
Changes from 7 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
6 changes: 6 additions & 0 deletions packages/font/package.json
Expand Up @@ -16,5 +16,11 @@
"prepublishOnly": "cd ../../ && turbo run build",
"dev": "tsc -d -w -p tsconfig.json",
"typescript": "tsec --noEmit -p tsconfig.json"
},
"dependencies": {
"fontkit": "2.0.2"
hanneslund marked this conversation as resolved.
Show resolved Hide resolved
},
"devDependencies": {
"@types/fontkit": "2.0.0"
}
}
16 changes: 8 additions & 8 deletions packages/font/src/google/loader.ts
Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down
6 changes: 3 additions & 3 deletions 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
Expand All @@ -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 {
Expand Down
89 changes: 61 additions & 28 deletions packages/font/src/local/loader.ts
@@ -1,4 +1,7 @@
import type { FontLoader } from 'next/font'
// @ts-ignore
import { calculateSizeAdjustValues } from 'next/dist/server/font-utils'
import fontkit from 'fontkit'
import type { AdjustFontFallback, FontLoader } from 'next/font'

import { promisify } from 'util'
import { validateData } from './utils'
Expand All @@ -12,7 +15,9 @@ const fetchFonts: FontLoader = async ({
}) => {
const {
family,
files,
src,
ext,
format,
display,
weight,
style,
Expand All @@ -28,45 +33,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: fontkit.Font | undefined
try {
fontMetadata = fontkit.create(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,
}
}

Expand Down
50 changes: 14 additions & 36 deletions 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[]) =>
Expand All @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down
19 changes: 9 additions & 10 deletions packages/next/server/font-utils.ts
Expand Up @@ -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)
Expand All @@ -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))
Expand All @@ -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 `
Expand All @@ -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 {
Expand Down