Skip to content

Commit

Permalink
Update font avg (#41734)
Browse files Browse the repository at this point in the history
Update the avg calculations.

## 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)

Co-authored-by: JJ Kasper <jj@jjsweb.site>
  • Loading branch information
Hannes Bornö and ijjk committed Oct 24, 2022
1 parent b177013 commit c5111f7
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 190 deletions.
34 changes: 19 additions & 15 deletions packages/font/src/google/loader.ts
@@ -1,17 +1,19 @@
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 Down Expand Up @@ -64,6 +66,7 @@ const downloadGoogleFonts: FontLoader = async ({
const fontFiles: Array<{
googleFontFileUrl: string
preloadFontFile: boolean
isLatin: boolean
}> = []
let currentSubset = ''
for (const line of fontFaceDeclarations.split('\n')) {
Expand All @@ -78,17 +81,24 @@ 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 }) => {
fontFiles.map(async ({ googleFontFileUrl, preloadFontFile, isLatin }) => {
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 @@ -121,19 +131,13 @@ const downloadGoogleFonts: FontLoader = async ({

// Add fallback font
let adjustFontFallbackMetrics: AdjustFontFallback | undefined
if (adjustFontFallback) {
if (adjustFontFallback && latinFont) {
try {
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}%`,
}
adjustFontFallbackMetrics = calculateFallbackFontValues(
latinFont,
require('next/dist/server/google-font-metrics.json')[fontFamily]
.category
)
} catch {
Log.error(
`Failed to find font override values for font \`${fontFamily}\``
Expand Down
4 changes: 4 additions & 0 deletions packages/font/src/google/utils.ts
@@ -1,3 +1,4 @@
import fs from 'fs'
// @ts-ignore
import fetch from 'next/dist/compiled/node-fetch'
import fontData from './font-data.json'
Expand Down Expand Up @@ -161,6 +162,9 @@ export async function fetchCSSFromGoogleFonts(url: string, fontFamily: string) {

export async function fetchFontFile(url: string) {
if (process.env.NEXT_FONT_GOOGLE_MOCKED_RESPONSES) {
if (url.startsWith('/')) {
return fs.readFileSync(url)
}
return Buffer.from(url)
}
const arrayBuffer = await fetch(url).then((r: any) => r.arrayBuffer())
Expand Down
32 changes: 6 additions & 26 deletions packages/font/src/local/loader.ts
@@ -1,12 +1,11 @@
// @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 { calcAzWidth, validateData } from './utils'
import { validateData } from './utils'
import { calculateFallbackFontValues } from '../utils'

const fetchFonts: FontLoader = async ({
functionName,
Expand Down Expand Up @@ -44,29 +43,10 @@ const fetchFonts: FontLoader = async ({
// 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,
azAvgWidth: calcAzWidth(fontMetadata),
})
adjustFontFallbackMetrics = {
fallbackFont,
ascentOverride: `${ascent}%`,
descentOverride: `${descent}%`,
lineGapOverride: `${lineGap}%`,
sizeAdjust: `${fallbackSizeAdjust}%`,
}
adjustFontFallbackMetrics = calculateFallbackFontValues(
fontMetadata,
adjustFontFallback === 'Times New Roman' ? 'serif' : 'sans-serif'
)
}

const fontFaceProperties = [
Expand Down
11 changes: 0 additions & 11 deletions packages/font/src/local/utils.ts
@@ -1,5 +1,3 @@
import type { Font } from 'fontkit'

const allowedDisplayValues = ['auto', 'block', 'swap', 'fallback', 'optional']

const formatValues = (values: string[]) =>
Expand Down Expand Up @@ -93,12 +91,3 @@ export function validateData(functionName: string, data: any): FontOptions {
declarations,
}
}

// Calculating the a-z average width
export function calcAzWidth(font: Font) {
const widths = font
.glyphsForString('abcdefghijklmnopqrstuvwxyz')
.map((glyph) => glyph.advanceWidth)
const totalWidth = widths.reduce((sum, width) => sum + width, 0)
return totalWidth / widths.length
}
57 changes: 57 additions & 0 deletions packages/font/src/utils.ts
@@ -0,0 +1,57 @@
import type { Font } from 'fontkit'
import type { AdjustFontFallback } from 'next/font'

const DEFAULT_SANS_SERIF_FONT = {
name: 'Arial',
azAvgWidth: 934.5116279069767,
unitsPerEm: 2048,
}
const DEFAULT_SERIF_FONT = {
name: 'Times New Roman',
azAvgWidth: 854.3953488372093,
unitsPerEm: 2048,
}

function calcAverageWidth(font: Font): number | undefined {
const avgCharacters = 'aaabcdeeeefghiijklmnnoopqrrssttuvwxyz '
const hasAllChars = font
.glyphsForString(avgCharacters)
.flatMap((glyph) => glyph.codePoints)
.every((codePoint) => font.hasGlyphForCodePoint(codePoint))

if (!hasAllChars) return undefined

const widths = font
.glyphsForString(avgCharacters)
.map((glyph) => glyph.advanceWidth)
const totalWidth = widths.reduce((sum, width) => sum + width, 0)
return totalWidth / widths.length
}

function formatOverrideValue(val: number) {
return Math.abs(val * 100).toFixed(2) + '%'
}

export function calculateFallbackFontValues(
font: Font,
category = 'serif'
): AdjustFontFallback {
const fallbackFont =
category === 'serif' ? DEFAULT_SERIF_FONT : DEFAULT_SANS_SERIF_FONT

const azAvgWidth = calcAverageWidth(font)
const { ascent, descent, lineGap, unitsPerEm } = font

const fallbackFontAvgWidth = fallbackFont.azAvgWidth / fallbackFont.unitsPerEm
let sizeAdjust = azAvgWidth
? azAvgWidth / unitsPerEm / fallbackFontAvgWidth
: 1

return {
ascentOverride: formatOverrideValue(ascent / (unitsPerEm * sizeAdjust)),
descentOverride: formatOverrideValue(descent / (unitsPerEm * sizeAdjust)),
lineGapOverride: formatOverrideValue(lineGap / (unitsPerEm * sizeAdjust)),
fallbackFont: fallbackFont.name,
sizeAdjust: formatOverrideValue(sizeAdjust),
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
22 changes: 18 additions & 4 deletions test/e2e/next-font/google-font-mocked-responses.js
@@ -1,3 +1,5 @@
const path = require('path')

module.exports = {
'https://fonts.googleapis.com/css2?family=Open+Sans:wght@300..800&display=optional': `
/* cyrillic-ext */
Expand Down Expand Up @@ -499,7 +501,10 @@ module.exports = {
font-style: normal;
font-weight: 400;
font-display: optional;
src: url(https://fonts.gstatic.com/s/fraunces/v24/6NUh8FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0K7iN7hzFUPJH58nib1603gg7S2nfgRYIctxuTBv7Tp05GNyXkb24.woff2) format('woff2');
src: url(${path.join(
__dirname,
'./fonts/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 */
Expand All @@ -508,7 +513,10 @@ module.exports = {
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');
src: url(${path.join(
__dirname,
'./fonts/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 */
Expand All @@ -517,7 +525,10 @@ module.exports = {
font-style: normal;
font-weight: 400;
font-display: optional;
src: url(https://fonts.gstatic.com/s/fraunces/v24/6NUh8FyLNQOQZAnv9bYEvDiIdE9Ea92uemAk_WBq8U_9v0c2Wa0K7iN7hzFUPJH58nib1603gg7S2nfgRYIctxuTCf7Tp05GNyXk.woff2) format('woff2');
src: url(${path.join(
__dirname,
'./fonts/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 */
Expand All @@ -526,7 +537,10 @@ module.exports = {
font-style: normal;
font-weight: 400;
font-display: optional;
src: url(https://fonts.gstatic.com/s/indieflower/v17/m8JVjfNVeKWVnh3QMuKkFcZVaUuH99GUDg.woff2) format('woff2');
src: url(${path.join(
__dirname,
'./fonts/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;
}`,
}

0 comments on commit c5111f7

Please sign in to comment.