forked from vercel/next.js
/
font-utils.ts
145 lines (128 loc) · 3.56 KB
/
font-utils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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 = {
experimentalAdjustFallbacks?: boolean
inlineFonts: 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 calculateOverrideCSS(font: string, fontMetrics: any) {
const fontName = font.toLowerCase().trim().replace(/ /g, '-')
const fontKey = font.toLowerCase().trim().replace(/ /g, '')
const { category, ascentOverride, descentOverride, lineGapOverride } =
fontMetrics[fontKey]
const fallbackFont =
category === 'serif' ? DEFAULT_SERIF_FONT : DEFAULT_SANS_SERIF_FONT
const ascent = (ascentOverride * 100).toFixed(2)
const descent = (descentOverride * 100).toFixed(2)
const lineGap = (lineGapOverride * 100).toFixed(2)
return `
@font-face {
font-family: "${fontName}-fallback";
ascent-override: ${ascent}%;
descent-override: ${descent}%;
line-gap-override: ${lineGap}%;
src: local("${fallbackFont}");
}
`
}
export function getFontOverrideCss(url: string, css: string) {
if (!isGoogleFont(url)) {
return ''
}
try {
const fontNames = parseGoogleFontName(css)
const fontMetrics = googleFontsMetrics
const fontCss = fontNames.reduce((cssStr, fontName) => {
cssStr += calculateOverrideCSS(fontName, fontMetrics)
return cssStr
}, '')
return fontCss
} catch (e) {
console.log('Error getting font override values - ', e)
return ''
}
}