Master merge first attempt

This commit is contained in:
Mark Tolmacs 2025-01-07 09:41:32 +01:00
parent 1e819a2acf
commit 550c874c9b
No known key found for this signature in database
419 changed files with 24252 additions and 3446 deletions

Binary file not shown.

Binary file not shown.

View file

@ -11,7 +11,7 @@ const { Font } = require("fonteditor-core");
* 2. convert all the imported fonts (including those from cdn) at build time into .ttf (since Resvg does not support woff2, neither inlined dataurls - https://github.com/RazrFalcon/resvg/issues/541)
* - merging multiple woff2 into one ttf (for same families with different unicode ranges)
* - deduplicating glyphs due to the merge process
* - merging emoji font for each
* - merging fallback font for each
* - printing out font metrics
*
* @returns {import("esbuild").Plugin}
@ -93,7 +93,6 @@ module.exports.woff2ServerPlugin = (options = {}) => {
},
);
// TODO: strip away some unnecessary glyphs
build.onEnd(async () => {
if (!generateTtf) {
return;
@ -109,15 +108,48 @@ module.exports.woff2ServerPlugin = (options = {}) => {
return;
}
const xiaolaiPath = path.resolve(
__dirname,
"./assets/Xiaolai-Regular.ttf",
);
const emojiPath = path.resolve(
__dirname,
"./assets/NotoEmoji-Regular.ttf",
);
// need to use the same em size as built-in fonts, otherwise pyftmerge throws (modified manually with font forge)
const emojiPath_2048 = path.resolve(
__dirname,
"./assets/NotoEmoji-Regular-2048.ttf",
);
const xiaolaiFont = Font.create(fs.readFileSync(xiaolaiPath), {
type: "ttf",
});
const emojiFont = Font.create(fs.readFileSync(emojiPath), {
type: "ttf",
});
const sortedFonts = Array.from(fonts.entries()).sort(
([family1], [family2]) => (family1 > family2 ? 1 : -1),
);
// for now we are interested in the regular families only
for (const [family, { Regular }] of sortedFonts) {
const baseFont = Regular[0];
if (family.includes("Xiaolai")) {
// don't generate ttf for Xiaolai, as we have it hardcoded as one ttf
continue;
}
const tempFilePaths = Regular.map((_, index) =>
const fallbackFontsPaths = [];
const shouldIncludeXiaolaiFallback = family.includes("Excalifont");
if (shouldIncludeXiaolaiFallback) {
fallbackFontsPaths.push(xiaolaiPath);
}
const baseFont = Regular[0];
const tempPaths = Regular.map((_, index) =>
path.resolve(outputDir, `temp_${family}_${index}.ttf`),
);
@ -128,45 +160,28 @@ module.exports.woff2ServerPlugin = (options = {}) => {
}
// write down the buffer
fs.writeFileSync(tempFilePaths[index], font.write({ type: "ttf" }));
fs.writeFileSync(tempPaths[index], font.write({ type: "ttf" }));
}
const emojiFilePath = path.resolve(
__dirname,
"./assets/NotoEmoji-Regular.ttf",
);
const emojiBuffer = fs.readFileSync(emojiFilePath);
const emojiFont = Font.create(emojiBuffer, { type: "ttf" });
// hack so that:
// - emoji font has same metrics as the base font, otherwise pyftmerge throws due to different unitsPerEm
// - emoji font glyphs are adjusted based to the base font glyphs, otherwise the glyphs don't match
const patchedEmojiFont = Font.create({
...baseFont.data,
glyf: baseFont.find({ unicode: [65] }), // adjust based on the "A" glyph (does not have to be first)
}).merge(emojiFont, { adjustGlyf: true });
const emojiTempFilePath = path.resolve(
outputDir,
`temp_${family}_Emoji.ttf`,
);
fs.writeFileSync(
emojiTempFilePath,
patchedEmojiFont.write({ type: "ttf" }),
);
const mergedFontPath = path.resolve(outputDir, `${family}.ttf`);
if (baseFont.data.head.unitsPerEm === 2048) {
fallbackFontsPaths.push(emojiPath_2048);
} else {
fallbackFontsPaths.push(emojiPath);
}
// drop Vertical related metrics, otherwise it does not allow us to merge the fonts
// vhea (Vertical Header Table)
// vmtx (Vertical Metrics Table)
execSync(
`pyftmerge --output-file="${mergedFontPath}" "${tempFilePaths.join(
`pyftmerge --drop-tables=vhea,vmtx --output-file="${mergedFontPath}" "${tempPaths.join(
'" "',
)}" "${emojiTempFilePath}"`,
)}" "${fallbackFontsPaths.join('" "')}"`,
);
// cleanup
fs.rmSync(emojiTempFilePath);
for (const path of tempFilePaths) {
for (const path of tempPaths) {
fs.rmSync(path);
}
@ -177,13 +192,22 @@ module.exports.woff2ServerPlugin = (options = {}) => {
hinting: true,
});
// keep copyright & licence per both fonts, as per the OFL licence
const getNameField = (field) => {
const base = baseFont.data.name[field];
const xiaolai = xiaolaiFont.data.name[field];
const emoji = emojiFont.data.name[field];
return shouldIncludeXiaolaiFallback
? `${base} & ${xiaolai} & ${emoji}`
: `${base} & ${emoji}`;
};
mergedFont.set({
...mergedFont.data,
name: {
...mergedFont.data.name,
copyright: `${baseFont.data.name.copyright} & ${emojiFont.data.name.copyright}`,
licence: `${baseFont.data.name.licence} & ${emojiFont.data.name.licence}`,
copyright: getNameField("copyright"),
licence: getNameField("licence"),
},
});
@ -194,7 +218,7 @@ module.exports.woff2ServerPlugin = (options = {}) => {
console.info(`Generated "${family}"`);
if (Regular.length > 1) {
console.info(
`- by merging ${Regular.length} woff2 files and 1 emoji ttf file`,
`- by merging ${Regular.length} woff2 fonts and related fallback fonts`,
);
}
console.info(

View file

@ -1,6 +1,6 @@
// `EXCALIDRAW_ASSET_PATH` as a SSOT
const OSS_FONTS_CDN =
"https://excalidraw.nyc3.cdn.digitaloceanspaces.com/fonts/oss/";
// define `EXCALIDRAW_ASSET_PATH` as a SSOT
const OSS_FONTS_CDN = "https://excalidraw.nyc3.cdn.digitaloceanspaces.com/oss/";
const OSS_FONTS_FALLBACK = "/";
/**
* Custom vite plugin for auto-prefixing `EXCALIDRAW_ASSET_PATH` woff2 fonts in `excalidraw-app`.
@ -19,12 +19,12 @@ module.exports.woff2BrowserPlugin = () => {
transform(code, id) {
// using copy / replace as fonts defined in the `.css` don't have to be manually copied over (vite/rollup does this automatically),
// but at the same time can't be easily prefixed with the `EXCALIDRAW_ASSET_PATH` only for the `excalidraw-app`
if (!isDev && id.endsWith("/excalidraw/fonts/assets/fonts.css")) {
if (!isDev && id.endsWith("/excalidraw/fonts/fonts.css")) {
return `/* WARN: The following content is generated during excalidraw-app build */
@font-face {
font-family: "Assistant";
src: url(${OSS_FONTS_CDN}Assistant-Regular-DVxZuzxb.woff2)
src: url(${OSS_FONTS_CDN}fonts/Assistant/Assistant-Regular.woff2)
format("woff2"),
url(./Assistant-Regular.woff2) format("woff2");
font-weight: 400;
@ -34,7 +34,7 @@ module.exports.woff2BrowserPlugin = () => {
@font-face {
font-family: "Assistant";
src: url(${OSS_FONTS_CDN}Assistant-Medium-DrcxCXg3.woff2)
src: url(${OSS_FONTS_CDN}fonts/Assistant/Assistant-Medium.woff2)
format("woff2"),
url(./Assistant-Medium.woff2) format("woff2");
font-weight: 500;
@ -44,7 +44,7 @@ module.exports.woff2BrowserPlugin = () => {
@font-face {
font-family: "Assistant";
src: url(${OSS_FONTS_CDN}Assistant-SemiBold-SCI4bEL9.woff2)
src: url(${OSS_FONTS_CDN}fonts/Assistant/Assistant-SemiBold.woff2)
format("woff2"),
url(./Assistant-SemiBold.woff2) format("woff2");
font-weight: 600;
@ -54,7 +54,7 @@ module.exports.woff2BrowserPlugin = () => {
@font-face {
font-family: "Assistant";
src: url(${OSS_FONTS_CDN}Assistant-Bold-gm-uSS1B.woff2)
src: url(${OSS_FONTS_CDN}fonts/Assistant/Assistant-Bold.woff2)
format("woff2"),
url(./Assistant-Bold.woff2) format("woff2");
font-weight: 700;
@ -70,14 +70,14 @@ module.exports.woff2BrowserPlugin = () => {
// point into our CDN in prod, fallback to root (excalidraw.com) domain in case of issues
window.EXCALIDRAW_ASSET_PATH = [
"${OSS_FONTS_CDN}",
"/",
"${OSS_FONTS_FALLBACK}",
];
</script>
<!-- Preload all default fonts and Virgil for backwards compatibility to avoid swap on init -->
<!-- Preload all default fonts to avoid swap on init -->
<link
rel="preload"
href="${OSS_FONTS_CDN}Excalifont-Regular-C9eKQy_N.woff2"
href="${OSS_FONTS_CDN}fonts/Excalifont/Excalifont-Regular-a88b72a24fb54c9f94e3b5fdaa7481c9.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
@ -85,21 +85,14 @@ module.exports.woff2BrowserPlugin = () => {
<!-- For Nunito only preload the latin range, which should be good enough for now -->
<link
rel="preload"
href="${OSS_FONTS_CDN}Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg-DqUjjPte.woff2"
href="${OSS_FONTS_CDN}fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
/>
<link
rel="preload"
href="${OSS_FONTS_CDN}ComicShanns-Regular-D0c8wzsC.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
/>
<link
rel="preload"
href="${OSS_FONTS_CDN}Virgil-Regular-hO16qHwV.woff2"
href="${OSS_FONTS_CDN}fonts/ComicShanns/ComicShanns-Regular-279a7b317d12eb88de06167bd672b4b4.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"