Build tweaks for server-side, fixes - WIP
This commit is contained in:
parent
6ce17a80c4
commit
1e19a446b8
|
@ -62,10 +62,8 @@ export class ExcalidrawFontFace implements IExcalidrawFontFace {
|
||||||
|
|
||||||
return new Promise<string>((resolve) => {
|
return new Promise<string>((resolve) => {
|
||||||
this.getContent(codePoints).then((content) => {
|
this.getContent(codePoints).then((content) => {
|
||||||
resolve(`@font-face {
|
resolve(`
|
||||||
font-family: ${this.fontFace.family};
|
@font-face { font-family: ${this.fontFace.family}; src: url(${content}); }`);
|
||||||
src: url(${content});
|
|
||||||
}`);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,27 +15,23 @@ const load = (): Promise<{
|
||||||
}> => {
|
}> => {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
WebAssembly.instantiate(binary).then((module) => {
|
const module = await WebAssembly.instantiate(binary);
|
||||||
const harfbuzzJsWasm = module.instance.exports;
|
const harfbuzzJsWasm = module.instance.exports;
|
||||||
// @ts-expect-error since `.buffer` is custom prop
|
// @ts-expect-error since `.buffer` is custom prop
|
||||||
const heapu8 = new Uint8Array(harfbuzzJsWasm.memory.buffer);
|
const heapu8 = new Uint8Array(harfbuzzJsWasm.memory.buffer);
|
||||||
|
|
||||||
const hbSubset = {
|
const hbSubset = {
|
||||||
subset: (
|
subset: (fontBuffer: ArrayBuffer, codePoints: ReadonlySet<number>) => {
|
||||||
fontBuffer: ArrayBuffer,
|
return bindings.subset(
|
||||||
codePoints: ReadonlySet<number>,
|
harfbuzzJsWasm,
|
||||||
) => {
|
heapu8,
|
||||||
return bindings.subset(
|
fontBuffer,
|
||||||
harfbuzzJsWasm,
|
codePoints,
|
||||||
heapu8,
|
);
|
||||||
fontBuffer,
|
},
|
||||||
codePoints,
|
};
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
resolve(hbSubset);
|
resolve(hbSubset);
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ const Module = (function () {
|
||||||
moduleOverrides[key] = Module[key];
|
moduleOverrides[key] = Module[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let arguments_ = [];
|
let arguments_ = [];
|
||||||
let thisProgram = "./this.program";
|
let thisProgram = "./this.program";
|
||||||
let quit_ = function (status, toThrow) {
|
let quit_ = function (status, toThrow) {
|
||||||
|
@ -3917,6 +3918,7 @@ const Module = (function () {
|
||||||
let calledRun;
|
let calledRun;
|
||||||
Module.then = function (func) {
|
Module.then = function (func) {
|
||||||
if (calledRun) {
|
if (calledRun) {
|
||||||
|
throw new Error("1 This error should be silently swallowed");
|
||||||
func(Module);
|
func(Module);
|
||||||
} else {
|
} else {
|
||||||
const old = Module.onRuntimeInitialized;
|
const old = Module.onRuntimeInitialized;
|
||||||
|
@ -3924,6 +3926,7 @@ const Module = (function () {
|
||||||
if (old) {
|
if (old) {
|
||||||
old();
|
old();
|
||||||
}
|
}
|
||||||
|
throw new Error("This error should be silently swallowed 1");
|
||||||
func(Module);
|
func(Module);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -4046,3 +4049,5 @@ const Module = (function () {
|
||||||
})();
|
})();
|
||||||
|
|
||||||
export default Module;
|
export default Module;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,37 +15,36 @@ const load = (): Promise<{
|
||||||
}> => {
|
}> => {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
|
// re-map from internal vector into byte array
|
||||||
|
function convertFromVecToUint8Array(vector: Vector): Uint8Array {
|
||||||
|
const arr = [];
|
||||||
|
for (let i = 0, l = vector.size(); i < l; i++) {
|
||||||
|
arr.push(vector.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Uint8Array(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO_CHINESE: bindings implements .then, but rejections will still likely be not caught
|
||||||
// initializing the module manually, so that we could pass in the wasm binary
|
// initializing the module manually, so that we could pass in the wasm binary
|
||||||
bindings({ wasmBinary: binary }).then(
|
const module: {
|
||||||
(module: {
|
woff2Enc: (buffer: ArrayBuffer, byteLength: number) => Vector;
|
||||||
woff2Enc: (buffer: ArrayBuffer, byteLength: number) => Vector;
|
woff2Dec: (buffer: ArrayBuffer, byteLength: number) => Vector;
|
||||||
woff2Dec: (buffer: ArrayBuffer, byteLength: number) => Vector;
|
} = await bindings({ wasmBinary: binary });
|
||||||
}) => {
|
|
||||||
// re-map from internal vector into byte array
|
|
||||||
function convertFromVecToUint8Array(vector: Vector): Uint8Array {
|
|
||||||
const arr = [];
|
|
||||||
for (let i = 0, l = vector.size(); i < l; i++) {
|
|
||||||
arr.push(vector.get(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Uint8Array(arr);
|
// re-exporting only compress and decompress functions (also avoids infinite loop inside emscripten bindings)
|
||||||
}
|
const woff2 = {
|
||||||
|
compress: (buffer: ArrayBuffer) =>
|
||||||
|
convertFromVecToUint8Array(
|
||||||
|
module.woff2Enc(buffer, buffer.byteLength),
|
||||||
|
),
|
||||||
|
decompress: (buffer: ArrayBuffer) =>
|
||||||
|
convertFromVecToUint8Array(
|
||||||
|
module.woff2Dec(buffer, buffer.byteLength),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
// re-exporting only compress and decompress functions (also avoids infinite loop inside emscripten bindings)
|
resolve(woff2);
|
||||||
const woff2 = {
|
|
||||||
compress: (buffer: ArrayBuffer) =>
|
|
||||||
convertFromVecToUint8Array(
|
|
||||||
module.woff2Enc(buffer, buffer.byteLength),
|
|
||||||
),
|
|
||||||
decompress: (buffer: ArrayBuffer) =>
|
|
||||||
convertFromVecToUint8Array(
|
|
||||||
module.woff2Dec(buffer, buffer.byteLength),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
resolve(woff2);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,8 +31,8 @@ export class WorkerPool<T, R> {
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
this.workerUrl = workerUrl;
|
this.workerUrl = workerUrl;
|
||||||
// by default, active & idle workers will be terminated after 10 seconds of inactivity
|
// by default, active & idle workers will be terminated after 5 seconds of inactivity
|
||||||
this.workerTTL = options.ttl || 10_000;
|
this.workerTTL = options.ttl || 5_000;
|
||||||
|
|
||||||
this.initWorker = options.initWorker;
|
this.initWorker = options.initWorker;
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,6 @@ const rawConfig = {
|
||||||
entryPoints: ["index.ts"],
|
entryPoints: ["index.ts"],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
format: "esm",
|
format: "esm",
|
||||||
packages: "external",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// const BASE_PATH = `${path.resolve(`${__dirname}/..`)}`;
|
// const BASE_PATH = `${path.resolve(`${__dirname}/..`)}`;
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
# 1. Created "XiaolaiNotoEmoji" through FontForge
|
||||||
|
|
||||||
|
- Merged `XiaolaiSC-Regular.ttf` with `NotoEmoji-Regular.ttf` (kept in the codebasefor future reference)
|
||||||
|
- Adjusted glyphs of the merged font to fit the Em box
|
||||||
|
- Added merged copyright & license
|
||||||
|
- Generated versions for different Em sizes 1000 and 2048 (otherwise pyftmerge throws)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -47,7 +47,7 @@ module.exports.woff2BrowserPlugin = () => {
|
||||||
* 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)
|
* 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)
|
* - merging multiple woff2 into one ttf (for same families with different unicode ranges)
|
||||||
* - deduplicating glyphs due to the merge process
|
* - deduplicating glyphs due to the merge process
|
||||||
* - merging emoji font for each
|
* - merging fallback font for each
|
||||||
* - printing out font metrics
|
* - printing out font metrics
|
||||||
*
|
*
|
||||||
* @returns {import("esbuild").Plugin}
|
* @returns {import("esbuild").Plugin}
|
||||||
|
@ -176,6 +176,11 @@ module.exports.woff2ServerPlugin = (options = {}) => {
|
||||||
|
|
||||||
// for now we are interested in the regular families only
|
// for now we are interested in the regular families only
|
||||||
for (const [family, { Regular }] of sortedFonts) {
|
for (const [family, { Regular }] of sortedFonts) {
|
||||||
|
if (family === "Xiaolai SC") {
|
||||||
|
// don't generate ttf for Xiaolai SC, as we have it hardcoded
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const baseFont = Regular[0];
|
const baseFont = Regular[0];
|
||||||
|
|
||||||
const tempFilePaths = Regular.map((_, index) =>
|
const tempFilePaths = Regular.map((_, index) =>
|
||||||
|
@ -192,41 +197,34 @@ module.exports.woff2ServerPlugin = (options = {}) => {
|
||||||
fs.writeFileSync(tempFilePaths[index], font.write({ type: "ttf" }));
|
fs.writeFileSync(tempFilePaths[index], font.write({ type: "ttf" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
const emojiFilePath = path.resolve(
|
const fallbackFilePath2048 = path.resolve(
|
||||||
__dirname,
|
__dirname,
|
||||||
"./assets/NotoEmoji-Regular.ttf",
|
"./assets/XiaolaiNotoEmoji-2048.ttf",
|
||||||
);
|
);
|
||||||
|
const fallbackFilePath1000 = path.resolve(
|
||||||
const emojiBuffer = fs.readFileSync(emojiFilePath);
|
__dirname,
|
||||||
const emojiFont = Font.create(emojiBuffer, { type: "ttf" });
|
"./assets/XiaolaiNotoEmoji-1000.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 fallbackBuffer = fs.readFileSync(fallbackFilePath2048);
|
||||||
|
const fallbackFont = Font.create(fallbackBuffer, { type: "ttf" });
|
||||||
const mergedFontPath = path.resolve(outputDir, `${family}.ttf`);
|
const mergedFontPath = path.resolve(outputDir, `${family}.ttf`);
|
||||||
|
|
||||||
execSync(
|
if (baseFont.data.head.unitsPerEm !== 1000) {
|
||||||
`pyftmerge --output-file="${mergedFontPath}" "${tempFilePaths.join(
|
execSync(
|
||||||
'" "',
|
`pyftmerge --output-file="${mergedFontPath}" "${tempFilePaths.join(
|
||||||
)}" "${emojiTempFilePath}"`,
|
'" "',
|
||||||
);
|
)}" "${fallbackFilePath2048}"`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
execSync(
|
||||||
|
`pyftmerge --output-file="${mergedFontPath}" "${tempFilePaths.join(
|
||||||
|
'" "',
|
||||||
|
)}" "${fallbackFilePath1000}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
fs.rmSync(emojiTempFilePath);
|
|
||||||
for (const path of tempFilePaths) {
|
for (const path of tempFilePaths) {
|
||||||
fs.rmSync(path);
|
fs.rmSync(path);
|
||||||
}
|
}
|
||||||
|
@ -243,8 +241,8 @@ module.exports.woff2ServerPlugin = (options = {}) => {
|
||||||
...mergedFont.data,
|
...mergedFont.data,
|
||||||
name: {
|
name: {
|
||||||
...mergedFont.data.name,
|
...mergedFont.data.name,
|
||||||
copyright: `${baseFont.data.name.copyright} & ${emojiFont.data.name.copyright}`,
|
copyright: `${baseFont.data.name.copyright} & ${fallbackFont.data.name.copyright}`,
|
||||||
licence: `${baseFont.data.name.licence} & ${emojiFont.data.name.licence}`,
|
licence: `${baseFont.data.name.licence} & ${fallbackFont.data.name.licence}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -255,7 +253,7 @@ module.exports.woff2ServerPlugin = (options = {}) => {
|
||||||
console.info(`Generated "${family}"`);
|
console.info(`Generated "${family}"`);
|
||||||
if (Regular.length > 1) {
|
if (Regular.length > 1) {
|
||||||
console.info(
|
console.info(
|
||||||
`- by merging ${Regular.length} woff2 files and 1 emoji ttf file`,
|
`- by merging ${Regular.length} woff2 files and 1 fallback ttf file`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
console.info(
|
console.info(
|
||||||
|
|
Loading…
Reference in New Issue