aboutsummaryrefslogtreecommitdiffstats
path: root/packages/excalidraw/subset/subset-main.ts
diff options
context:
space:
mode:
authorkj_sh6042026-03-15 16:19:35 -0400
committerkj_sh6042026-03-15 16:19:35 -0400
commit6ec259a0e71174651bae95d4628138bf6fd68742 (patch)
tree5e33c6a5ec091ecabfcb257fdc7b6a88ed8754ac /packages/excalidraw/subset/subset-main.ts
parent16c8578b15c727f22921f8a80a56ee4d4e7f2272 (diff)
refactor: packages/
Diffstat (limited to 'packages/excalidraw/subset/subset-main.ts')
-rw-r--r--packages/excalidraw/subset/subset-main.ts129
1 files changed, 129 insertions, 0 deletions
diff --git a/packages/excalidraw/subset/subset-main.ts b/packages/excalidraw/subset/subset-main.ts
new file mode 100644
index 0000000..afccf0d
--- /dev/null
+++ b/packages/excalidraw/subset/subset-main.ts
@@ -0,0 +1,129 @@
+import { WorkerPool } from "../workers";
+import { isServerEnv, promiseTry } from "../utils";
+import { WorkerInTheMainChunkError, WorkerUrlNotDefinedError } from "../errors";
+
+import type { Commands } from "./subset-shared.chunk";
+
+let shouldUseWorkers = typeof Worker !== "undefined";
+
+/**
+ * Tries to subset glyphs in a font based on the used codepoints, returning the font as dataurl.
+ * Under the hood utilizes worker threads (Web Workers, if available), otherwise fallbacks to the main thread.
+ *
+ * Check the following diagram for details: link.excalidraw.com/readonly/MbbnWPSWXgadXdtmzgeO
+ *
+ * @param arrayBuffer font data buffer in the woff2 format
+ * @param codePoints codepoints used to subset the glyphs
+ *
+ * @returns font with subsetted glyphs (all glyphs in case of errors) converted into a dataurl
+ */
+export const subsetWoff2GlyphsByCodepoints = async (
+ arrayBuffer: ArrayBuffer,
+ codePoints: Array<number>,
+): Promise<string> => {
+ const { Commands, subsetToBase64, toBase64 } =
+ await lazyLoadSharedSubsetChunk();
+
+ if (!shouldUseWorkers) {
+ return subsetToBase64(arrayBuffer, codePoints);
+ }
+
+ return promiseTry(async () => {
+ try {
+ const workerPool = await getOrCreateWorkerPool();
+ // copy the buffer to avoid working on top of the detached array buffer in the fallback
+ // i.e. in case the worker throws, the array buffer does not get automatically detached, even if the worker is terminated
+ const arrayBufferCopy = arrayBuffer.slice(0);
+ const result = await workerPool.postMessage(
+ {
+ command: Commands.Subset,
+ arrayBuffer: arrayBufferCopy,
+ codePoints,
+ } as const,
+ { transfer: [arrayBufferCopy] },
+ );
+
+ // encode on the main thread to avoid copying large binary strings (as dataurl) between threads
+ return toBase64(result);
+ } catch (e) {
+ // don't use workers if they are failing
+ shouldUseWorkers = false;
+
+ if (
+ // don't log the expected errors server-side
+ !(
+ isServerEnv() &&
+ (e instanceof WorkerUrlNotDefinedError ||
+ e instanceof WorkerInTheMainChunkError)
+ )
+ ) {
+ // eslint-disable-next-line no-console
+ console.error(
+ "Failed to use workers for subsetting, falling back to the main thread.",
+ e,
+ );
+ }
+
+ // fallback to the main thread
+ return subsetToBase64(arrayBuffer, codePoints);
+ }
+ });
+};
+
+// lazy-loaded and cached chunks
+let subsetWorker: Promise<typeof import("./subset-worker.chunk")> | null = null;
+let subsetShared: Promise<typeof import("./subset-shared.chunk")> | null = null;
+
+const lazyLoadWorkerSubsetChunk = async () => {
+ if (!subsetWorker) {
+ subsetWorker = import("./subset-worker.chunk");
+ }
+
+ return subsetWorker;
+};
+
+const lazyLoadSharedSubsetChunk = async () => {
+ if (!subsetShared) {
+ // load dynamically to force create a shared chunk reused between main thread and the worker thread
+ subsetShared = import("./subset-shared.chunk");
+ }
+
+ return subsetShared;
+};
+
+// could be extended with multiple commands in the future
+type SubsetWorkerData = {
+ command: typeof Commands.Subset;
+ arrayBuffer: ArrayBuffer;
+ codePoints: Array<number>;
+};
+
+type SubsetWorkerResult<T extends SubsetWorkerData["command"]> =
+ T extends typeof Commands.Subset ? ArrayBuffer : never;
+
+let workerPool: Promise<
+ WorkerPool<SubsetWorkerData, SubsetWorkerResult<SubsetWorkerData["command"]>>
+> | null = null;
+
+/**
+ * Lazy initialize or get the worker pool singleton.
+ *
+ * @throws implicitly if anything goes wrong - worker pool creation, loading wasm, initializing worker, etc.
+ */
+const getOrCreateWorkerPool = () => {
+ if (!workerPool) {
+ // immediate concurrent-friendly return, to ensure we have only one pool instance
+ workerPool = promiseTry(async () => {
+ const { WorkerUrl } = await lazyLoadWorkerSubsetChunk();
+
+ const pool = WorkerPool.create<
+ SubsetWorkerData,
+ SubsetWorkerResult<SubsetWorkerData["command"]>
+ >(WorkerUrl);
+
+ return pool;
+ });
+ }
+
+ return workerPool;
+};