diff options
| author | kj_sh604 | 2026-03-15 16:19:35 -0400 |
|---|---|---|
| committer | kj_sh604 | 2026-03-15 16:19:35 -0400 |
| commit | 6ec259a0e71174651bae95d4628138bf6fd68742 (patch) | |
| tree | 5e33c6a5ec091ecabfcb257fdc7b6a88ed8754ac /packages/excalidraw/subset/subset-shared.chunk.ts | |
| parent | 16c8578b15c727f22921f8a80a56ee4d4e7f2272 (diff) | |
refactor: packages/
Diffstat (limited to 'packages/excalidraw/subset/subset-shared.chunk.ts')
| -rw-r--r-- | packages/excalidraw/subset/subset-shared.chunk.ts | 81 |
1 files changed, 81 insertions, 0 deletions
diff --git a/packages/excalidraw/subset/subset-shared.chunk.ts b/packages/excalidraw/subset/subset-shared.chunk.ts new file mode 100644 index 0000000..b64a382 --- /dev/null +++ b/packages/excalidraw/subset/subset-shared.chunk.ts @@ -0,0 +1,81 @@ +/** + * DON'T depend on anything from the outside like `promiseTry`, as this module is part of a separate lazy-loaded chunk. + * + * Including anything from the main chunk would include the whole chunk by default. + * Even it it would be tree-shaken during build, it won't be tree-shaken in dev. + * + * In the future consider separating common utils into a separate shared chunk. + */ + +import loadWoff2 from "./woff2/woff2-loader"; +import loadHbSubset from "./harfbuzz/harfbuzz-loader"; + +/** + * Shared commands between the main thread and worker threads. + */ +export const Commands = { + Subset: "SUBSET", +} as const; + +/** + * Used by browser (main thread), node and jsdom, to subset the font based on the passed codepoints. + * + * @returns woff2 font as a base64 encoded string + */ +export const subsetToBase64 = async ( + arrayBuffer: ArrayBuffer, + codePoints: Array<number>, +): Promise<string> => { + try { + const buffer = await subsetToBinary(arrayBuffer, codePoints); + return toBase64(buffer); + } catch (e) { + console.error("Skipped glyph subsetting", e); + // Fallback to encoding whole font in case of errors + return toBase64(arrayBuffer); + } +}; + +/** + * Used by browser (worker thread) and as part of `subsetToBase64`, to subset the font based on the passed codepoints. + * + * @eturns woff2 font as an ArrayBuffer, to avoid copying large strings between worker threads and the main thread. + */ +export const subsetToBinary = async ( + arrayBuffer: ArrayBuffer, + codePoints: Array<number>, +): Promise<ArrayBuffer> => { + // lazy loaded wasm modules to avoid multiple initializations in case of concurrent triggers + // IMPORTANT: could be expensive, as each new worker instance lazy loads these to their own memory ~ keep the # of workes small! + const { compress, decompress } = await loadWoff2(); + const { subset } = await loadHbSubset(); + + const decompressedBinary = decompress(arrayBuffer).buffer; + const snftSubset = subset(decompressedBinary, new Set(codePoints)); + const compressedBinary = compress(snftSubset.buffer); + + return compressedBinary.buffer; +}; + +/** + * Util for isomoprhic browser (main thread), node and jsdom usage. + * + * Isn't used inside the worker to avoid copying large binary strings (as dataurl) between worker threads and the main thread. + */ +export const toBase64 = async (arrayBuffer: ArrayBuffer) => { + let base64: string; + + if (typeof Buffer !== "undefined") { + // node, jsdom + base64 = Buffer.from(arrayBuffer).toString("base64"); + } else { + // browser (main thread) + // it's perfectly fine to treat each byte independently, + // as we care only about turning individual bytes into codepoints, + // not about multi-byte unicode characters + const byteString = String.fromCharCode(...new Uint8Array(arrayBuffer)); + base64 = btoa(byteString); + } + + return `data:font/woff2;base64,${base64}`; +}; |
