1// Adapted from opencode (MIT). Copyright (c) 2025 opencode. See LICENSE.opencode.
2import { openWordDocument, pageSize } from "./word-document";
3import type { WordMethods } from "./word-protocol";
4import { serveWorker, Transfer } from "./worker-rpc";
5
6// One worker holds one open Word document. The window terminates the worker when the document closes, which is the only
7// way the engine's memory is given back.
8
9/** The document this worker opened, once it has. */
10const opened: { value: Awaited<ReturnType<typeof openWordDocument>> | undefined } = {
11 value: undefined,
12};
13
14serveWorker<WordMethods>({
15 open: async (input) => {
16 if (opened.value) throw new Error("The worker already holds a document");
17
18 // SAFETY: a worker's global scope is a FontFaceSource, whose `fonts` is a FontFaceSet (CSS Font Loading, exposed to
19 // workers). The window's types declare it on documents only. Without one, the canvas keeps the system's fonts.
20 const fonts = "fonts" in self ? (self.fonts as FontFaceSet) : undefined;
21 const loaded = await openWordDocument(new Uint8Array(input.bytes), fonts);
22
23 opened.value = loaded;
24
25 return { pages: loaded.pages().map(pageSize), loading: loaded.loading() };
26 },
27 layout: () => required().step(),
28 paint: async (input, signal) => {
29 const painted = await required().paint(input.index, input.scale, input.ratio, signal);
30
31 return painted ? new Transfer(painted, [painted.bitmap]) : undefined;
32 },
33 vectors: (input) => {
34 required().vectors(input);
35
36 return undefined;
37 },
38 build: (input) => {
39 required().built(input.index);
40
41 return undefined;
42 },
43 text: (input) => required().text(input.index),
44 bookmark: (input) => required().target(input.name),
45});
46
47function required() {
48 if (!opened.value) throw new Error("The worker holds no document");
49
50 return opened.value;
51}