rsnext/packages/next/server/font-utils.ts
Janicklas Ralph 56b097a8fb
Updating size-adjust calculation (#41406)
<!--
Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that
you follow the checklist sections below.
Choose the right checklist for the change that you're making:




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

Updating size-adjust calc to use azAvgWidth instead of xAvgCharWidth

Co-authored-by: Hannes Bornö <hannes.borno@vercel.com>
Co-authored-by: Balázs Orbán <info@balazsorban.com>
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-10-20 10:58:26 -07:00

205 lines
5.1 KiB
TypeScript

import * as Log from '../build/output/log'
import {
GOOGLE_FONT_PROVIDER,
DEFAULT_SERIF_FONT,
DEFAULT_SANS_SERIF_FONT,
} from '../shared/lib/constants'
const googleFontsMetrics = require('./google-font-metrics.json')
const https = require('https')
const CHROME_UA =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36'
const IE_UA = 'Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko'
export type FontManifest = Array<{
url: string
content: string
}>
export type FontConfig = boolean
function isGoogleFont(url: string): boolean {
return url.startsWith(GOOGLE_FONT_PROVIDER)
}
function getFontForUA(url: string, UA: string): Promise<String> {
return new Promise((resolve, reject) => {
let rawData: any = ''
https
.get(
url,
{
headers: {
'user-agent': UA,
},
},
(res: any) => {
res.on('data', (chunk: any) => {
rawData += chunk
})
res.on('end', () => {
resolve(rawData.toString('utf8'))
})
}
)
.on('error', (e: Error) => {
reject(e)
})
})
}
export async function getFontDefinitionFromNetwork(
url: string
): Promise<string> {
let result = ''
/**
* The order of IE -> Chrome is important, other wise chrome starts loading woff1.
* CSS cascading 🤷‍♂️.
*/
try {
if (isGoogleFont(url)) {
result += await getFontForUA(url, IE_UA)
}
result += await getFontForUA(url, CHROME_UA)
} catch (e) {
Log.warn(
`Failed to download the stylesheet for ${url}. Skipped optimizing this font.`
)
return ''
}
return result
}
export function getFontDefinitionFromManifest(
url: string,
manifest: FontManifest
): string {
return (
manifest.find((font) => {
if (font && font.url === url) {
return true
}
return false
})?.content || ''
)
}
function parseGoogleFontName(css: string): Array<string> {
const regex = /font-family: ([^;]*)/g
const matches = css.matchAll(regex)
const fontNames = new Set<string>()
for (let font of matches) {
const fontFamily = font[1].replace(/^['"]|['"]$/g, '')
fontNames.add(fontFamily)
}
return [...fontNames]
}
function formatOverrideValue(val: number) {
return Math.abs(val * 100).toFixed(2)
}
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)
descent = formatOverrideValue(descent / unitsPerEm)
lineGap = formatOverrideValue(lineGap / unitsPerEm)
return {
ascent,
descent,
lineGap,
fallbackFont: fallbackFont.name,
}
}
export function calculateSizeAdjustValues(fontMetrics: any) {
let { category, ascent, descent, lineGap, unitsPerEm, azAvgWidth } =
fontMetrics
const fallbackFont =
category === 'serif' ? DEFAULT_SERIF_FONT : DEFAULT_SANS_SERIF_FONT
const mainFontAvgWidth = azAvgWidth / unitsPerEm
const fallbackFontAvgWidth = fallbackFont.azAvgWidth / fallbackFont.unitsPerEm
let sizeAdjust = azAvgWidth ? mainFontAvgWidth / fallbackFontAvgWidth : 1
ascent = formatOverrideValue(ascent / (unitsPerEm * sizeAdjust))
descent = formatOverrideValue(descent / (unitsPerEm * sizeAdjust))
lineGap = formatOverrideValue(lineGap / (unitsPerEm * sizeAdjust))
return {
ascent,
descent,
lineGap,
fallbackFont: fallbackFont.name,
sizeAdjust: formatOverrideValue(sizeAdjust),
}
}
function calculateOverrideCSS(font: string, fontMetrics: any) {
const fontName = font.trim()
const { ascent, descent, lineGap, fallbackFont } = calculateOverrideValues(
fontMetrics[fontName]
)
return `
@font-face {
font-family: "${fontName} Fallback";
ascent-override: ${ascent}%;
descent-override: ${descent}%;
line-gap-override: ${lineGap}%;
src: local("${fallbackFont}");
}
`
}
function calculateSizeAdjustCSS(font: string, fontMetrics: any) {
const fontName = font.trim()
const { ascent, descent, lineGap, fallbackFont, sizeAdjust } =
calculateSizeAdjustValues(fontMetrics[fontName])
return `
@font-face {
font-family: "${fontName} Fallback";
ascent-override: ${ascent}%;
descent-override: ${descent}%;
line-gap-override: ${lineGap}%;
size-adjust: ${sizeAdjust}%;
src: local("${fallbackFont}");
}
`
}
export function getFontOverrideCss(
url: string,
css: string,
useSizeAdjust = false
) {
if (!isGoogleFont(url)) {
return ''
}
const calcFn = useSizeAdjust ? calculateSizeAdjustCSS : calculateOverrideCSS
try {
const fontNames = parseGoogleFontName(css)
const fontMetrics = googleFontsMetrics
const fontCss = fontNames.reduce((cssStr, fontName) => {
cssStr += calcFn(fontName, fontMetrics)
return cssStr
}, '')
return fontCss
} catch (e) {
console.log('Error getting font override values - ', e)
return ''
}
}