56b097a8fb
<!-- 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>
205 lines
5.1 KiB
TypeScript
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 ''
|
|
}
|
|
}
|