overleaf/services/web/frontend/fonts/build-fonts.mjs

187 lines
7 KiB
JavaScript
Raw Normal View History

import fs from 'node:fs/promises'
import { createWriteStream } from 'node:fs'
import { basename, join } from 'node:path'
import { tmpdir } from 'node:os'
import { execSync } from 'node:child_process'
import yauzl from 'yauzl'
// brew install woff2
const families = [
{
folder: 'dm-mono',
url: 'https://github.com/googlefonts/dm-mono',
fonts: [
'https://github.com/googlefonts/dm-mono/raw/refs/heads/main/exports/DMMono-Italic.ttf',
'https://github.com/googlefonts/dm-mono/raw/refs/heads/main/exports/DMMono-Medium.ttf',
'https://github.com/googlefonts/dm-mono/raw/refs/heads/main/exports/DMMono-MediumItalic.ttf',
'https://github.com/googlefonts/dm-mono/raw/refs/heads/main/exports/DMMono-Regular.ttf',
],
},
{
folder: 'font-awesome',
url: 'https://fontawesome.com/v4/',
archive: 'https://fontawesome.com/v4/assets/font-awesome-4.7.0.zip',
fonts: ['font-awesome-4.7.0/fonts/fontawesome-webfont.woff2'],
},
{
folder: 'lato',
url: 'https://www.latofonts.com/',
archive: 'https://www.latofonts.com/download/lato2oflweb-zip/',
fonts: [
'Lato2OFLWeb/LatoLatin/fonts/LatoLatin-Bold.woff2',
'Lato2OFLWeb/LatoLatin/fonts/LatoLatin-BoldItalic.woff2',
'Lato2OFLWeb/LatoLatin/fonts/LatoLatin-Italic.woff2',
'Lato2OFLWeb/LatoLatin/fonts/LatoLatin-Regular.woff2',
],
},
{
folder: 'merriweather',
url: 'https://github.com/SorkinType/Merriweather',
fonts: [
'https://github.com/SorkinType/Merriweather/raw/refs/heads/master/fonts/webfonts/Merriweather-Bold.woff2',
'https://github.com/SorkinType/Merriweather/raw/refs/heads/master/fonts/webfonts/Merriweather-BoldItalic.woff2',
'https://github.com/SorkinType/Merriweather/raw/refs/heads/master/fonts/webfonts/Merriweather-Italic.woff2',
'https://github.com/SorkinType/Merriweather/raw/refs/heads/master/fonts/webfonts/Merriweather-Regular.woff2',
],
license:
'https://github.com/SorkinType/Merriweather/raw/refs/heads/master/OFL.txt',
},
{
folder: 'noto-sans',
url: 'https://notofonts.github.io/#latin-greek-cyrillic',
fonts: [
'https://github.com/notofonts/notofonts.github.io/raw/refs/heads/main/fonts/NotoSans/full/ttf/NotoSans-Italic.ttf',
'https://github.com/notofonts/notofonts.github.io/raw/refs/heads/main/fonts/NotoSans/full/ttf/NotoSans-Medium.ttf',
'https://github.com/notofonts/notofonts.github.io/raw/refs/heads/main/fonts/NotoSans/full/ttf/NotoSans-MediumItalic.ttf',
'https://github.com/notofonts/notofonts.github.io/raw/refs/heads/main/fonts/NotoSans/full/ttf/NotoSans-Regular.ttf',
'https://github.com/notofonts/notofonts.github.io/raw/refs/heads/main/fonts/NotoSans/full/ttf/NotoSans-SemiBold.ttf',
'https://github.com/notofonts/notofonts.github.io/raw/refs/heads/main/fonts/NotoSans/full/ttf/NotoSans-SemiBoldItalic.ttf',
],
license:
'https://raw.githubusercontent.com/notofonts/latin-greek-cyrillic/refs/heads/main/OFL.txt',
},
{
folder: 'noto-serif',
url: 'https://notofonts.github.io/#latin-greek-cyrillic',
fonts: [
'https://github.com/notofonts/notofonts.github.io/raw/refs/heads/main/fonts/NotoSerif/unhinted/slim-variable-ttf/NotoSerif%5Bwght%5D.ttf',
'https://github.com/notofonts/notofonts.github.io/raw/refs/heads/main/fonts/NotoSerif/unhinted/slim-variable-ttf/NotoSerif-Italic%5Bwght%5D.ttf',
],
license:
'https://raw.githubusercontent.com/notofonts/latin-greek-cyrillic/refs/heads/main/OFL.txt',
},
{
folder: 'open-sans',
url: 'https://github.com/googlefonts/opensans',
fonts: [
'https://github.com/googlefonts/opensans/raw/refs/heads/main/fonts/ttf/OpenSans-Bold.ttf',
'https://github.com/googlefonts/opensans/raw/refs/heads/main/fonts/ttf/OpenSans-Light.ttf',
'https://github.com/googlefonts/opensans/raw/refs/heads/main/fonts/ttf/OpenSans-Regular.ttf',
'https://github.com/googlefonts/opensans/raw/refs/heads/main/fonts/ttf/OpenSans-SemiBold.ttf',
],
license:
'https://raw.githubusercontent.com/googlefonts/opensans/refs/heads/main/OFL.txt',
},
{
folder: 'source-code-pro',
url: 'https://github.com/adobe-fonts/source-code-pro',
fonts: [
'https://github.com/adobe-fonts/source-code-pro/raw/refs/heads/release/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2',
],
license:
'https://raw.githubusercontent.com/adobe-fonts/source-code-pro/refs/heads/release/LICENSE.md',
},
{
folder: 'STIXTwoMath',
url: 'https://github.com/stipub/stixfonts',
fonts: [
'https://github.com/stipub/stixfonts/raw/refs/heads/master/fonts/static_otf_woff2/STIXTwoMath-Regular.woff2',
],
license:
'https://raw.githubusercontent.com/stipub/stixfonts/refs/heads/master/OFL.txt',
},
]
const fetchFile = async (url, path) => {
console.log(`${url}\n${path}`)
const response = await fetch(url)
if (!response.ok) {
throw new Error(response.statusText)
}
await fs.writeFile(path, response.body)
}
for (const family of families) {
if (!family.folder) {
throw new Error('Missing family information')
}
await fs.mkdir(family.folder, { recursive: true })
const fonts = new Set(family.fonts)
if (family.archive) {
const dir = await fs.mkdtemp(join(tmpdir(), 'fonts-'))
const filename = decodeURIComponent(basename(family.archive))
const path = `${dir}/${filename}`
await fetchFile(family.archive, path)
await new Promise((resolve, reject) => {
yauzl.open(path, { lazyEntries: true }, (err, zipfile) => {
if (err) {
reject(err)
} else {
zipfile.on('entry', entry => {
if (fonts.has(entry.fileName)) {
console.log(entry.fileName)
zipfile.openReadStream(entry, (err, readStream) => {
if (err) {
reject(err)
} else {
const path = `${family.folder}/${basename(entry.fileName)}`
const output = createWriteStream(path)
readStream.on('end', async () => {
output.close()
if (path.endsWith('.ttf')) {
execSync(`woff2_compress "${path}"`)
await fs.unlink(path)
}
zipfile.readEntry()
})
readStream.pipe(output)
}
})
} else {
zipfile.readEntry()
}
})
zipfile.on('error', reject)
zipfile.on('end', resolve)
zipfile.readEntry()
}
})
})
await fs.unlink(path)
} else {
for (const url of fonts) {
const filename = decodeURIComponent(basename(url))
const path = `${family.folder}/${filename}`
await fetchFile(url, path)
if (path.endsWith('.ttf')) {
execSync(`woff2_compress "${path}"`)
await fs.unlink(path)
}
}
}
if (family.license) {
const url = family.license
const filename = decodeURIComponent(basename(url))
const path = `${family.folder}/${filename}`
await fetchFile(url, path)
}
}