@empty-sekai/sekai-custom-profile-sdk 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/LICENSE +235 -0
  2. package/LICENSE-EXCEPTION +32 -0
  3. package/NOTICE +11 -0
  4. package/README.en.md +456 -0
  5. package/README.md +458 -0
  6. package/dist/allium_renderer_wasm.js +14 -0
  7. package/dist/allium_renderer_wasm.wasm +0 -0
  8. package/dist/authoring.d.ts +14 -0
  9. package/dist/authoring.js +34 -0
  10. package/dist/cache/glyphPersistentCache.d.ts +145 -0
  11. package/dist/cache/glyphPersistentCache.js +346 -0
  12. package/dist/cache/indexedDbGlyphRecordStore.d.ts +26 -0
  13. package/dist/cache/indexedDbGlyphRecordStore.js +214 -0
  14. package/dist/cache/sessionImageResourceCache.d.ts +39 -0
  15. package/dist/cache/sessionImageResourceCache.js +156 -0
  16. package/dist/emscripten.d.ts +11 -0
  17. package/dist/emscripten.js +1 -0
  18. package/dist/fontProvider.d.ts +36 -0
  19. package/dist/fontProvider.js +84 -0
  20. package/dist/fontSdfAtlas.d.ts +92 -0
  21. package/dist/fontSdfAtlas.js +296 -0
  22. package/dist/gpu/browserSemanticResources.d.ts +64 -0
  23. package/dist/gpu/browserSemanticResources.js +232 -0
  24. package/dist/gpu/generalTextRenderPlacement.d.ts +8 -0
  25. package/dist/gpu/generalTextRenderPlacement.js +131 -0
  26. package/dist/gpu/previewTransformTextureLayout.d.ts +1 -0
  27. package/dist/gpu/previewTransformTextureLayout.js +12 -0
  28. package/dist/gpu/semanticCommandGeometry.d.ts +21 -0
  29. package/dist/gpu/semanticCommandGeometry.js +226 -0
  30. package/dist/gpu/semanticCommandPlanner.d.ts +131 -0
  31. package/dist/gpu/semanticCommandPlanner.js +203 -0
  32. package/dist/gpu/semanticTextGlyphBridge.d.ts +3 -0
  33. package/dist/gpu/semanticTextGlyphBridge.js +22 -0
  34. package/dist/gpu/semanticWebglSceneRenderer.d.ts +70 -0
  35. package/dist/gpu/semanticWebglSceneRenderer.js +211 -0
  36. package/dist/gpu/slotRanges.d.ts +5 -0
  37. package/dist/gpu/slotRanges.js +14 -0
  38. package/dist/gpu/webglSdfAtlasTexture.d.ts +17 -0
  39. package/dist/gpu/webglSdfAtlasTexture.js +72 -0
  40. package/dist/gpu/webglSdfGlyphPipeline.d.ts +26 -0
  41. package/dist/gpu/webglSdfGlyphPipeline.js +267 -0
  42. package/dist/gpu/webglSemanticCommandExecutor.d.ts +79 -0
  43. package/dist/gpu/webglSemanticCommandExecutor.js +686 -0
  44. package/dist/index.d.ts +18 -0
  45. package/dist/index.js +8 -0
  46. package/dist/interaction/numericTextRegions.d.ts +46 -0
  47. package/dist/interaction/numericTextRegions.js +44 -0
  48. package/dist/localizationProvider.d.ts +33 -0
  49. package/dist/localizationProvider.js +74 -0
  50. package/dist/originPrebuiltSdfAtlasPackage.d.ts +39 -0
  51. package/dist/originPrebuiltSdfAtlasPackage.js +316 -0
  52. package/dist/prebuiltSdfAtlas.d.ts +36 -0
  53. package/dist/prebuiltSdfAtlas.js +261 -0
  54. package/dist/protocol.d.ts +456 -0
  55. package/dist/protocol.js +1 -0
  56. package/dist/renderer.d.ts +218 -0
  57. package/dist/renderer.js +641 -0
  58. package/dist/resourceProvider.d.ts +24 -0
  59. package/dist/resourceProvider.js +41 -0
  60. package/dist/telemetry/rendererTelemetry.d.ts +158 -0
  61. package/dist/telemetry/rendererTelemetry.js +232 -0
  62. package/dist/third-party/freetype/FTL.txt +169 -0
  63. package/dist/types/atlas.d.ts +90 -0
  64. package/dist/types/atlas.js +1 -0
  65. package/dist/types/authoring.d.ts +81 -0
  66. package/dist/types/authoring.js +1 -0
  67. package/dist/types/core.d.ts +128 -0
  68. package/dist/types/core.js +1 -0
  69. package/dist/types/freeType.d.ts +54 -0
  70. package/dist/types/freeType.js +13 -0
  71. package/dist/types/glyph.d.ts +33 -0
  72. package/dist/types/glyph.js +1 -0
  73. package/dist/types/layout.d.ts +25 -0
  74. package/dist/types/layout.js +1 -0
  75. package/dist/types/text.d.ts +28 -0
  76. package/dist/types/text.js +1 -0
  77. package/dist/worker/glyphWorkScheduler.d.ts +29 -0
  78. package/dist/worker/glyphWorkScheduler.js +107 -0
  79. package/dist/worker-client.d.ts +114 -0
  80. package/dist/worker-client.js +413 -0
  81. package/dist/worker.d.ts +1 -0
  82. package/dist/worker.js +708 -0
  83. package/package.json +46 -0
  84. package/src/atlas.rs +652 -0
  85. package/src/authoring.ts +42 -0
  86. package/src/authoring_runtime.rs +303 -0
  87. package/src/cache/glyphPersistentCache.ts +463 -0
  88. package/src/cache/indexedDbGlyphRecordStore.ts +238 -0
  89. package/src/cache/sessionImageResourceCache.ts +186 -0
  90. package/src/edt.rs +97 -0
  91. package/src/emscripten.ts +17 -0
  92. package/src/fontProvider.ts +127 -0
  93. package/src/fontSdfAtlas.ts +431 -0
  94. package/src/geometry.rs +149 -0
  95. package/src/glyph_plan.rs +226 -0
  96. package/src/gpu/browserSemanticResources.ts +294 -0
  97. package/src/gpu/generalTextRenderPlacement.ts +175 -0
  98. package/src/gpu/previewTransformTextureLayout.ts +12 -0
  99. package/src/gpu/semanticCommandGeometry.ts +260 -0
  100. package/src/gpu/semanticCommandPlanner.ts +280 -0
  101. package/src/gpu/semanticTextGlyphBridge.ts +35 -0
  102. package/src/gpu/semanticWebglSceneRenderer.ts +261 -0
  103. package/src/gpu/slotRanges.ts +15 -0
  104. package/src/gpu/webglSdfAtlasTexture.ts +69 -0
  105. package/src/gpu/webglSdfGlyphPipeline.ts +299 -0
  106. package/src/gpu/webglSemanticCommandExecutor.ts +739 -0
  107. package/src/index.ts +84 -0
  108. package/src/interaction/numericTextRegions.ts +80 -0
  109. package/src/layout.rs +1882 -0
  110. package/src/lib.rs +1443 -0
  111. package/src/localizationProvider.ts +113 -0
  112. package/src/main.rs +55 -0
  113. package/src/masterdata_runtime.rs +496 -0
  114. package/src/originPrebuiltSdfAtlasPackage.ts +403 -0
  115. package/src/prebuiltSdfAtlas.ts +306 -0
  116. package/src/protocol.ts +150 -0
  117. package/src/renderer.ts +792 -0
  118. package/src/resourceProvider.ts +69 -0
  119. package/src/scene.rs +665 -0
  120. package/src/telemetry/rendererTelemetry.ts +358 -0
  121. package/src/types/atlas.ts +80 -0
  122. package/src/types/authoring.ts +85 -0
  123. package/src/types/core.ts +110 -0
  124. package/src/types/freeType.ts +63 -0
  125. package/src/types/glyph.ts +33 -0
  126. package/src/types/layout.ts +28 -0
  127. package/src/types/text.ts +28 -0
  128. package/src/worker/glyphWorkScheduler.ts +130 -0
  129. package/src/worker-client.ts +484 -0
  130. package/src/worker.ts +895 -0
package/dist/worker.js ADDED
@@ -0,0 +1,708 @@
1
+ /// <reference lib="webworker" />
2
+ import { RENDERER_WORKER_PROTOCOL, } from "./protocol.js";
3
+ const scope = globalThis;
4
+ const ATLAS_TRANSIENT_HARD_BYTES = 48 * 1024 * 1024;
5
+ const scenes = new Map();
6
+ const masterDataSessions = new Map();
7
+ const atlasSessions = new Map();
8
+ const authoringSessions = new Map();
9
+ const fonts = new Map();
10
+ const fontSources = new Map();
11
+ let modulePromise = null;
12
+ let moduleInstance = null;
13
+ const counters = {
14
+ protocol: RENDERER_WORKER_PROTOCOL,
15
+ initialized: false,
16
+ scenes: 0,
17
+ masterDataSessions: 0,
18
+ atlasSessions: 0,
19
+ authoringSessions: 0,
20
+ fonts: 0,
21
+ requests: 0,
22
+ failures: 0,
23
+ wasmMs: 0,
24
+ bridgeBytes: 0,
25
+ };
26
+ scope.onmessage = (event) => {
27
+ void dispatch(event.data).catch((error) => {
28
+ counters.failures += 1;
29
+ post({
30
+ id: event.data.id,
31
+ ok: false,
32
+ error: {
33
+ code: error instanceof WorkerError ? error.code : "RENDERER_WORKER_FAILED",
34
+ message: error instanceof Error ? error.message : String(error),
35
+ },
36
+ });
37
+ });
38
+ };
39
+ async function dispatch(request) {
40
+ counters.requests += 1;
41
+ const started = performance.now();
42
+ let result;
43
+ let transfers = [];
44
+ switch (request.kind) {
45
+ case "init":
46
+ await initialize(request.payload.moduleUrl, request.payload.wasmUrl);
47
+ result = { kind: "init", protocol: RENDERER_WORKER_PROTOCOL };
48
+ break;
49
+ case "contract": {
50
+ const contract = callJson(await loadedModule(), "sdf_layout_freetype_contract_json", [], []);
51
+ result = { kind: "contract", contract };
52
+ break;
53
+ }
54
+ case "registerFont": {
55
+ assertHash(request.payload.sourceHash);
56
+ fonts.set(fontKey(request.payload), request.payload.bytes);
57
+ fontSources.set(request.payload.family, {
58
+ region: request.payload.region,
59
+ family: request.payload.family,
60
+ sourceHash: request.payload.sourceHash,
61
+ });
62
+ counters.fonts = fonts.size;
63
+ counters.bridgeBytes += request.payload.bytes.byteLength;
64
+ result = { kind: "registerFont", registered: true };
65
+ break;
66
+ }
67
+ case "mapGlyphs": {
68
+ const batch = await buildGlyphBatch(request.payload, true);
69
+ result = { kind: "mapGlyphs", batch };
70
+ break;
71
+ }
72
+ case "planGlyphs": {
73
+ result = { kind: "planGlyphs", plan: await planGlyphBatch(request.payload) };
74
+ break;
75
+ }
76
+ case "buildGlyphs": {
77
+ const batch = await buildGlyphBatch(request.payload, false);
78
+ transfers = materializeGlyphPixels(batch);
79
+ result = { kind: "buildGlyphs", batch };
80
+ break;
81
+ }
82
+ case "createAtlas": {
83
+ const response = callJsonWithInput(await loadedModule(), "sdf_atlas_create_json", request.payload);
84
+ if (!Number.isInteger(response.handle) || response.handle <= 0)
85
+ throw new WorkerError("ATLAS_CREATE_FAILED", "WASM returned an invalid atlas handle");
86
+ const atlasId = `atlas:${response.handle}`;
87
+ atlasSessions.set(atlasId, response.handle);
88
+ counters.atlasSessions = atlasSessions.size;
89
+ result = { kind: "createAtlas", atlasId, stats: response.stats };
90
+ break;
91
+ }
92
+ case "resolveAtlas": {
93
+ const mod = await loadedModule();
94
+ const handle = atlasHandle(request.payload.atlasId);
95
+ const warm = resolveAtlasRecords(mod, handle, request.payload.keys, request.payload.cached);
96
+ const missing = new Set(warm.missingKeys);
97
+ const generated = [];
98
+ for (const group of request.payload.generate) {
99
+ const glyphs = group.glyphs.filter((entry) => missing.has(entry.key));
100
+ if (glyphs.length > 0)
101
+ generated.push(...await generateAtlasRecords({ ...group, glyphs, chars: glyphs.map((entry) => entry.char) }));
102
+ }
103
+ const cold = generated.length > 0 ? resolveAtlasRecords(mod, handle, [...missing], generated) : null;
104
+ const missingKeys = cold?.missingKeys ?? warm.missingKeys;
105
+ if (missingKeys.length > 0)
106
+ throw new WorkerError("ATLAS_GLYPH_MISSING", `Atlas could not resolve ${missingKeys.length} glyph resource(s)`);
107
+ transfers = generated.map((record) => record.pixels.buffer);
108
+ counters.bridgeBytes += generated.reduce((sum, record) => sum + record.pixels.byteLength, 0);
109
+ result = { kind: "resolveAtlas", result: {
110
+ leases: [warm.lease, cold?.lease].filter((lease) => lease != null),
111
+ placements: [...warm.placements, ...(cold?.placements ?? [])],
112
+ missingKeys,
113
+ generated,
114
+ stats: cold?.stats ?? warm.stats,
115
+ } };
116
+ break;
117
+ }
118
+ case "atlasPages": {
119
+ const mod = await loadedModule();
120
+ const handle = atlasHandle(request.payload.atlasId);
121
+ const metadata = callJsonWithInput(mod, "sdf_atlas_pages_since_json", { revisions: request.payload.revisions }, [handle]);
122
+ const updates = metadata.map((update) => materializeAtlasPage(mod, handle, update));
123
+ transfers = updates.flatMap((update) => update.fullUpload
124
+ ? [update.pixels.buffer]
125
+ : update.dirtyRects.map((rect) => rect.pixels.buffer));
126
+ counters.bridgeBytes += updates.reduce((sum, update) => sum + (update.fullUpload
127
+ ? update.pixels.byteLength
128
+ : update.dirtyRects.reduce((rectSum, rect) => rectSum + rect.pixels.byteLength, 0)), 0);
129
+ result = { kind: "atlasPages", updates };
130
+ break;
131
+ }
132
+ case "releaseAtlas": {
133
+ const status = (await loadedModule()).ccall("sdf_atlas_release", "number", ["number", "number"], [atlasHandle(request.payload.atlasId), request.payload.lease]);
134
+ result = { kind: "releaseAtlas", released: status === 1 };
135
+ break;
136
+ }
137
+ case "destroyAtlas": {
138
+ const handle = atlasHandle(request.payload.atlasId);
139
+ const status = (await loadedModule()).ccall("sdf_atlas_destroy", "number", ["number"], [handle]);
140
+ atlasSessions.delete(request.payload.atlasId);
141
+ counters.atlasSessions = atlasSessions.size;
142
+ result = { kind: "destroyAtlas", destroyed: status === 1 };
143
+ break;
144
+ }
145
+ case "createAuthoringBlank": {
146
+ const response = callJson(await loadedModule(), "sdf_renderer_authoring_create_blank_json", [], []);
147
+ const authoringId = registerAuthoring(response.handle);
148
+ result = { kind: "createAuthoring", authoringId, revision: response.revision, document: response.document };
149
+ break;
150
+ }
151
+ case "importAuthoringProfile": {
152
+ const response = callJsonWithInput(await loadedModule(), "sdf_renderer_authoring_import_profile_json", request.payload.profile);
153
+ const authoringId = registerAuthoring(response.handle);
154
+ result = { kind: "createAuthoring", authoringId, revision: response.revision, document: response.document };
155
+ break;
156
+ }
157
+ case "restoreAuthoringCheckpoint": {
158
+ const response = callJsonWithInput(await loadedModule(), "sdf_renderer_authoring_restore_checkpoint_json", request.payload.checkpoint);
159
+ const authoringId = registerAuthoring(response.handle);
160
+ result = { kind: "createAuthoring", authoringId, revision: response.revision, document: response.document };
161
+ break;
162
+ }
163
+ case "applyAuthoring":
164
+ result = { kind: "authoringDelta", delta: callJsonWithInput(await loadedModule(), "sdf_renderer_authoring_apply_json", request.payload.command, [authoringHandle(request.payload.authoringId)]) };
165
+ break;
166
+ case "selectAuthoring":
167
+ result = { kind: "authoringDelta", delta: callJsonWithInput(await loadedModule(), "sdf_renderer_authoring_select_json", { id: request.payload.id }, [authoringHandle(request.payload.authoringId)]) };
168
+ break;
169
+ case "elementsAuthoring":
170
+ result = { kind: "elementsAuthoring", elements: callJson(await loadedModule(), "sdf_renderer_authoring_elements_json", ["number"], [authoringHandle(request.payload.authoringId)]) };
171
+ break;
172
+ case "beginAuthoringGesture":
173
+ result = { kind: "authoringDelta", delta: callJsonWithInput(await loadedModule(), "sdf_renderer_authoring_begin_gesture_json", { id: request.payload.id }, [authoringHandle(request.payload.authoringId)]) };
174
+ break;
175
+ case "previewAuthoringGesture":
176
+ result = { kind: "authoringDelta", delta: callJsonWithInput(await loadedModule(), "sdf_renderer_authoring_preview_gesture_json", request.payload.command, [authoringHandle(request.payload.authoringId)]) };
177
+ break;
178
+ case "commitAuthoringGesture":
179
+ result = { kind: "authoringDelta", delta: callJson(await loadedModule(), "sdf_renderer_authoring_commit_gesture_json", ["number"], [authoringHandle(request.payload.authoringId)]) };
180
+ break;
181
+ case "cancelAuthoringGesture":
182
+ result = { kind: "authoringDelta", delta: callJson(await loadedModule(), "sdf_renderer_authoring_cancel_gesture_json", ["number"], [authoringHandle(request.payload.authoringId)]) };
183
+ break;
184
+ case "appendAuthoringPage":
185
+ result = { kind: "authoringDelta", delta: callJson(await loadedModule(), "sdf_renderer_authoring_append_page_json", ["number"], [authoringHandle(request.payload.authoringId)]) };
186
+ break;
187
+ case "duplicateAuthoringPage":
188
+ result = { kind: "authoringDelta", delta: callJsonWithInput(await loadedModule(), "sdf_renderer_authoring_duplicate_page_json", { page: request.payload.page }, [authoringHandle(request.payload.authoringId)]) };
189
+ break;
190
+ case "deleteAuthoringPage":
191
+ result = { kind: "authoringDelta", delta: callJsonWithInput(await loadedModule(), "sdf_renderer_authoring_delete_page_json", { page: request.payload.page }, [authoringHandle(request.payload.authoringId)]) };
192
+ break;
193
+ case "moveAuthoringPage":
194
+ result = { kind: "authoringDelta", delta: callJsonWithInput(await loadedModule(), "sdf_renderer_authoring_move_page_json", { fromPage: request.payload.fromPage, page: request.payload.page }, [authoringHandle(request.payload.authoringId)]) };
195
+ break;
196
+ case "undoAuthoring":
197
+ result = { kind: "authoringDelta", delta: callJson(await loadedModule(), "sdf_renderer_authoring_undo_json", ["number"], [authoringHandle(request.payload.authoringId)]) };
198
+ break;
199
+ case "redoAuthoring":
200
+ result = { kind: "authoringDelta", delta: callJson(await loadedModule(), "sdf_renderer_authoring_redo_json", ["number"], [authoringHandle(request.payload.authoringId)]) };
201
+ break;
202
+ case "exportAuthoring":
203
+ result = { kind: "exportAuthoring", document: callJson(await loadedModule(), "sdf_renderer_authoring_export_json", ["number"], [authoringHandle(request.payload.authoringId)]) };
204
+ break;
205
+ case "checkpointAuthoring":
206
+ result = { kind: "checkpointAuthoring", checkpoint: callJson(await loadedModule(), "sdf_renderer_authoring_checkpoint_json", ["number"], [authoringHandle(request.payload.authoringId)]) };
207
+ break;
208
+ case "destroyAuthoring": {
209
+ const handle = authoringHandle(request.payload.authoringId);
210
+ const status = (await loadedModule()).ccall("sdf_renderer_authoring_destroy", "number", ["number"], [handle]);
211
+ authoringSessions.delete(request.payload.authoringId);
212
+ counters.authoringSessions = authoringSessions.size;
213
+ result = { kind: "destroyAuthoring", destroyed: status === 1 };
214
+ break;
215
+ }
216
+ case "layoutText": {
217
+ const batch = callJsonWithInput(await loadedModule(), "sdf_layout_freetype_build_layout_json", request.payload.request);
218
+ result = { kind: "layoutText", batch };
219
+ break;
220
+ }
221
+ case "glyphDemand": {
222
+ const batch = callJsonWithInput(await loadedModule(), "sdf_layout_freetype_glyph_demand_json", request.payload.request);
223
+ result = { kind: "glyphDemand", batch };
224
+ break;
225
+ }
226
+ case "createMasterData": {
227
+ const report = callJsonWithInput(await loadedModule(), "sdf_renderer_core_masterdata_create_json", request.payload);
228
+ const handle = Number(report.handle);
229
+ if (!Number.isInteger(handle) || handle <= 0)
230
+ throw new WorkerError("MASTERDATA_CREATE_FAILED", "Renderer returned an invalid master-data handle");
231
+ const masterDataId = `${request.payload.region}:${request.payload.revision}:${handle}`;
232
+ masterDataSessions.set(masterDataId, handle);
233
+ counters.masterDataSessions = masterDataSessions.size;
234
+ result = { kind: "createMasterData", masterDataId, report };
235
+ break;
236
+ }
237
+ case "putMasterDataTable":
238
+ result = { kind: "masterDataReport", report: callJsonWithInput(await loadedModule(), "sdf_renderer_core_masterdata_put_table_json", { name: request.payload.name, table: request.payload.table }, [masterDataHandle(request.payload.masterDataId)]) };
239
+ break;
240
+ case "sealMasterData":
241
+ result = { kind: "masterDataReport", report: callJson(await loadedModule(), "sdf_renderer_core_masterdata_seal_json", ["number"], [masterDataHandle(request.payload.masterDataId)]) };
242
+ break;
243
+ case "prepareProfile": {
244
+ const mod = await loadedModule();
245
+ const preparation = callJsonWithInput(mod, "sdf_renderer_core_profile_prepare_json", request.payload.request, [masterDataHandle(request.payload.masterDataId)]);
246
+ const profileRequest = preparationRecord(request.payload.request, "request");
247
+ if (profileRequest.fontDemandOnly === true) {
248
+ const families = preparation.font_families;
249
+ if (!Array.isArray(families) || families.some((family) => typeof family !== "string" || family.length === 0)) {
250
+ throw new WorkerError("INVALID_PROFILE_PREPARATION", "Profile preparation did not return valid font families");
251
+ }
252
+ result = {
253
+ kind: "prepareProfile",
254
+ preparation: { fontDemands: [...families] },
255
+ };
256
+ break;
257
+ }
258
+ const glyphLayers = completeGlyphLayers(preparation);
259
+ const glyphDemand = callJsonWithInput(mod, "sdf_layout_freetype_glyph_demand_json", { layers: glyphLayers });
260
+ result = {
261
+ kind: "prepareProfile",
262
+ preparation: {
263
+ ...preparation,
264
+ glyph_demand: glyphDemand,
265
+ layout_request: { layers: completeLayoutLayers(preparation) },
266
+ },
267
+ };
268
+ break;
269
+ }
270
+ case "createProfileScene": {
271
+ const layout = callJsonWithInput(await loadedModule(), "sdf_layout_freetype_build_layout_json", request.payload.layoutRequest);
272
+ const response = callJsonWithInput(await loadedModule(), "sdf_renderer_core_profile_create_json", profileCompileRequest(request.payload.request, layout.dynamicPrograms), [masterDataHandle(request.payload.masterDataId)]);
273
+ assertSchema(response.snapshot.schema_major);
274
+ const sceneId = `${response.snapshot.scene_id}:${response.handle}`;
275
+ scenes.set(sceneId, response.handle);
276
+ counters.scenes = scenes.size;
277
+ result = { kind: "createProfileScene", sceneId, response, layout };
278
+ break;
279
+ }
280
+ case "destroyMasterData": {
281
+ const mod = await loadedModule();
282
+ const handle = masterDataHandle(request.payload.masterDataId);
283
+ const status = mod.ccall("sdf_renderer_core_masterdata_destroy", "number", ["number"], [handle]);
284
+ masterDataSessions.delete(request.payload.masterDataId);
285
+ counters.masterDataSessions = masterDataSessions.size;
286
+ result = { kind: "destroyMasterData", destroyed: status === 1 };
287
+ break;
288
+ }
289
+ case "createScene": {
290
+ const mod = await loadedModule();
291
+ const response = callJsonWithInput(mod, "sdf_renderer_core_profile_scene_create_json", request.payload.request);
292
+ assertSchema(response.snapshot.schema_major);
293
+ const sceneId = `${response.snapshot.scene_id}:${response.handle}`;
294
+ scenes.set(sceneId, response.handle);
295
+ counters.scenes = scenes.size;
296
+ result = { kind: "createScene", sceneId, response };
297
+ break;
298
+ }
299
+ case "advance":
300
+ result = { kind: "delta", delta: callSceneNoInput("sdf_renderer_core_scene_advance_json", request.payload.sceneId, [tick(request.payload.tick)]) };
301
+ break;
302
+ case "setLayerMask":
303
+ result = { kind: "delta", delta: callSceneWithInput("sdf_renderer_core_scene_set_mask_json", request.payload.sceneId, {
304
+ layer_id: request.payload.layerId,
305
+ visible: request.payload.visible,
306
+ }) };
307
+ break;
308
+ case "setLayerMasks":
309
+ result = { kind: "delta", delta: callSceneWithInput("sdf_renderer_core_scene_set_masks_json", request.payload.sceneId, {
310
+ expected_layer_table_revision: request.payload.expectedLayerTableRevision,
311
+ overrides: request.payload.overrides.map((entry) => ({
312
+ layer_id: entry.layerId,
313
+ mask_override: entry.visible == null ? "inherit_authored" : entry.visible ? "force_visible" : "force_hidden",
314
+ })),
315
+ }) };
316
+ break;
317
+ case "setTab":
318
+ result = { kind: "delta", delta: callSceneWithInput("sdf_renderer_core_scene_set_tab_json", request.payload.sceneId, {
319
+ control_id: request.payload.controlId,
320
+ value: request.payload.value,
321
+ }) };
322
+ break;
323
+ case "scroll":
324
+ result = { kind: "delta", delta: callSceneWithInput("sdf_renderer_core_scene_scroll_json", request.payload.sceneId, {
325
+ control_id: request.payload.controlId,
326
+ offset: request.payload.offset,
327
+ delta: request.payload.delta,
328
+ }) };
329
+ break;
330
+ case "dumpScene":
331
+ result = { kind: "dumpScene", dump: callSceneNoInput("sdf_renderer_core_scene_dump_json", request.payload.sceneId) };
332
+ break;
333
+ case "destroyScene": {
334
+ const mod = await loadedModule();
335
+ const handle = sceneHandle(request.payload.sceneId);
336
+ const status = mod.ccall("sdf_renderer_core_scene_destroy", "number", ["number"], [handle]);
337
+ scenes.delete(request.payload.sceneId);
338
+ counters.scenes = scenes.size;
339
+ result = { kind: "destroyScene", destroyed: status === 1 };
340
+ break;
341
+ }
342
+ case "stats":
343
+ result = { kind: "stats", stats: { ...counters } };
344
+ break;
345
+ default:
346
+ throw new WorkerError("RENDERER_WORKER_PROTOCOL", "Unsupported renderer worker request");
347
+ }
348
+ counters.wasmMs += performance.now() - started;
349
+ post({ id: request.id, ok: true, result }, transfers);
350
+ }
351
+ function profileCompileRequest(request, dynamicPrograms) {
352
+ if (!request || typeof request !== "object" || Array.isArray(request)) {
353
+ throw new WorkerError("INVALID_PROFILE_REQUEST", "Profile compile request must be an object");
354
+ }
355
+ const source = request;
356
+ if ("lineIndent" in source || "dynamicPrograms" in source) {
357
+ throw new WorkerError("INVALID_PROFILE_REQUEST", "Dynamic programs are owned by the renderer worker");
358
+ }
359
+ return { ...source, dynamicPrograms };
360
+ }
361
+ function completeGlyphLayers(preparation) {
362
+ if (!Array.isArray(preparation.glyph_layers)) {
363
+ throw new WorkerError("INVALID_PROFILE_PREPARATION", "Profile preparation did not return complete glyph inputs");
364
+ }
365
+ return preparation.glyph_layers.map((entry) => {
366
+ const value = preparationRecord(entry, "glyph input");
367
+ const family = preparationString(value.font_family, "font_family");
368
+ const source = registeredFontSource(family);
369
+ return {
370
+ text: preparationString(value.text, "text"),
371
+ region: source.region,
372
+ fontFamily: source.family,
373
+ fontSourceHash: source.sourceHash,
374
+ };
375
+ });
376
+ }
377
+ function completeLayoutLayers(preparation) {
378
+ if (!Array.isArray(preparation.layout_layers)) {
379
+ throw new WorkerError("INVALID_PROFILE_PREPARATION", "Profile preparation did not return complete layout inputs");
380
+ }
381
+ return preparation.layout_layers.map((entry) => {
382
+ const value = preparationRecord(entry, "layout input");
383
+ const family = preparationString(value.fontFamily, "fontFamily");
384
+ const source = registeredFontSource(family);
385
+ return {
386
+ ...value,
387
+ region: source.region,
388
+ fontSourceHash: source.sourceHash,
389
+ };
390
+ });
391
+ }
392
+ function registeredFontSource(family) {
393
+ const source = fontSources.get(family);
394
+ if (!source)
395
+ throw new WorkerError("FONT_NOT_REGISTERED", `Required font is not registered: ${family}`);
396
+ return source;
397
+ }
398
+ function preparationRecord(value, field) {
399
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
400
+ throw new WorkerError("INVALID_PROFILE_PREPARATION", `Profile ${field} is invalid`);
401
+ }
402
+ return value;
403
+ }
404
+ function preparationString(value, field) {
405
+ if (typeof value !== "string")
406
+ throw new WorkerError("INVALID_PROFILE_PREPARATION", `Profile ${field} is invalid`);
407
+ return value;
408
+ }
409
+ async function initialize(moduleUrl, wasmUrl) {
410
+ modulePromise ??= import(moduleUrl).then(async (namespace) => {
411
+ const factory = (namespace.default ?? namespace.Module);
412
+ if (typeof factory !== "function")
413
+ throw new WorkerError("WASM_FACTORY_MISSING", "The renderer module does not export an Emscripten factory");
414
+ const mod = await factory({
415
+ locateFile: (path) => path.endsWith(".wasm") && wasmUrl ? wasmUrl : new URL(path, moduleUrl).toString(),
416
+ printErr: (...values) => console.warn("[sekai-custom-profile-sdk]", ...values),
417
+ });
418
+ moduleInstance = mod;
419
+ counters.initialized = true;
420
+ return mod;
421
+ });
422
+ return modulePromise;
423
+ }
424
+ async function loadedModule() {
425
+ if (!modulePromise)
426
+ throw new WorkerError("WORKER_NOT_INITIALIZED", "Initialize the renderer worker before using it");
427
+ return modulePromise;
428
+ }
429
+ async function buildGlyphBatch(request, mapOnly) {
430
+ const mod = await loadedModule();
431
+ const bytes = fonts.get(fontKey(request));
432
+ if (!bytes)
433
+ throw new WorkerError("FONT_NOT_REGISTERED", `Font is not registered: ${request.region}/${request.family}`);
434
+ const font = new Uint8Array(bytes);
435
+ const codepoints = request.chars.map((value) => value.codePointAt(0)).filter((value) => value != null);
436
+ const fontPointer = mod._malloc(font.byteLength);
437
+ const codePointer = mod._malloc(codepoints.length * 4);
438
+ let resultPointer = 0;
439
+ try {
440
+ mod.HEAPU8.set(font, fontPointer);
441
+ mod.HEAPU32.set(codepoints, codePointer >>> 2);
442
+ const edt = !mapOnly && (request.backend ?? "edt") === "edt";
443
+ const name = mapOnly
444
+ ? "sdf_layout_freetype_map_glyphs_json"
445
+ : edt ? "sdf_layout_freetype_build_glyph_json_edt" : "sdf_layout_freetype_build_glyph_json";
446
+ const argumentTypes = ["number", "number", "number", "number", "string", "string", "string"];
447
+ const arguments_ = [
448
+ fontPointer, font.byteLength, codePointer, codepoints.length,
449
+ request.region, request.family, request.sourceHash,
450
+ ];
451
+ if (edt) {
452
+ argumentTypes.push("number");
453
+ arguments_.push(Math.max(1, Math.floor(request.supersample ?? 2)));
454
+ }
455
+ resultPointer = mod.ccall(name, "number", argumentTypes, arguments_);
456
+ const result = JSON.parse(readCString(mod, resultPointer));
457
+ if (result.error)
458
+ throw new WorkerError("GLYPH_BUILD_FAILED", result.error);
459
+ return result;
460
+ }
461
+ finally {
462
+ if (resultPointer)
463
+ mod.ccall("sdf_layout_freetype_free_string", null, ["number"], [resultPointer]);
464
+ mod._free(codePointer);
465
+ mod._free(fontPointer);
466
+ }
467
+ }
468
+ async function planGlyphBatch(request) {
469
+ const mod = await loadedModule();
470
+ const bytes = fonts.get(fontKey(request));
471
+ if (!bytes)
472
+ throw new WorkerError("FONT_NOT_REGISTERED", `Font is not registered: ${request.region}/${request.family}`);
473
+ const font = new Uint8Array(bytes);
474
+ const codepoints = request.chars.map((value) => value.codePointAt(0)).filter((value) => value != null);
475
+ const fontPointer = mod._malloc(font.byteLength);
476
+ const codePointer = mod._malloc(codepoints.length * 4);
477
+ let resultPointer = 0;
478
+ try {
479
+ mod.HEAPU8.set(font, fontPointer);
480
+ mod.HEAPU32.set(codepoints, codePointer >>> 2);
481
+ resultPointer = mod.ccall("sdf_layout_freetype_plan_glyphs_json", "number", ["number", "number", "number", "number", "string", "string", "string", "string", "number"], [
482
+ fontPointer,
483
+ font.byteLength,
484
+ codePointer,
485
+ codepoints.length,
486
+ request.region,
487
+ request.family,
488
+ request.sourceHash,
489
+ request.backend ?? "edt",
490
+ Math.max(0, Math.floor(request.supersample ?? 2)),
491
+ ]);
492
+ const plan = JSON.parse(readCString(mod, resultPointer));
493
+ if (plan.error)
494
+ throw new WorkerError("GLYPH_PLAN_FAILED", plan.error);
495
+ return plan;
496
+ }
497
+ finally {
498
+ if (resultPointer)
499
+ mod.ccall("sdf_layout_freetype_free_string", null, ["number"], [resultPointer]);
500
+ mod._free(codePointer);
501
+ mod._free(fontPointer);
502
+ }
503
+ }
504
+ function materializeGlyphPixels(batch) {
505
+ const transfers = [];
506
+ for (const glyph of batch.glyphs) {
507
+ if (!glyph.pixels_base64)
508
+ continue;
509
+ const raw = atob(glyph.pixels_base64);
510
+ const bytes = new Uint8Array(raw.length);
511
+ for (let index = 0; index < raw.length; index += 1)
512
+ bytes[index] = raw.charCodeAt(index);
513
+ glyph.pixels = bytes;
514
+ glyph.pixels_base64 = undefined;
515
+ counters.bridgeBytes += bytes.byteLength;
516
+ transfers.push(bytes.buffer);
517
+ }
518
+ return transfers;
519
+ }
520
+ function resolveAtlasRecords(mod, handle, keys, records) {
521
+ const bytes = records.reduce((sum, record) => sum + record.pixels.byteLength, 0);
522
+ if (bytes > ATLAS_TRANSIENT_HARD_BYTES)
523
+ throw new WorkerError("MEMORY_BUDGET_EXCEEDED", `Atlas raster bridge requires ${bytes} bytes, above the ${ATLAS_TRANSIENT_HARD_BYTES}-byte transient budget`);
524
+ return callJsonWithInput(mod, "sdf_atlas_resolve_json", {
525
+ keys,
526
+ records: records.map((record) => ({
527
+ key: record.key,
528
+ width: record.width,
529
+ height: record.height,
530
+ pixelsBase64: bytesToBase64(record.pixels),
531
+ })),
532
+ }, [handle]);
533
+ }
534
+ async function generateAtlasRecords(request) {
535
+ const batch = await buildGlyphBatch(request, false);
536
+ const byCharacter = new Map(batch.glyphs.map((glyph) => [glyph.ch, glyph]));
537
+ return request.glyphs.map((entry) => {
538
+ const glyph = byCharacter.get(entry.char);
539
+ if (!glyph)
540
+ throw new WorkerError("GLYPH_BUILD_FAILED", `FreeType omitted an atlas glyph for U+${entry.char.codePointAt(0)?.toString(16).toUpperCase()}`);
541
+ return {
542
+ key: entry.key,
543
+ glyphIndex: entry.glyphIndex,
544
+ width: glyph.width,
545
+ height: glyph.height,
546
+ advance: glyph.advance,
547
+ xOffset: glyph.x_offset,
548
+ yOffset: glyph.y_offset,
549
+ planeBearingX: glyph.plane_bearing_x,
550
+ planeBearingY: glyph.plane_bearing_y,
551
+ planeWidth: glyph.plane_width,
552
+ planeHeight: glyph.plane_height,
553
+ drawable: glyph.drawable,
554
+ pixels: glyphPixels(glyph),
555
+ };
556
+ });
557
+ }
558
+ function glyphPixels(glyph) {
559
+ if (glyph.pixels instanceof Uint8Array)
560
+ return glyph.pixels;
561
+ if (Array.isArray(glyph.pixels))
562
+ return new Uint8Array(glyph.pixels);
563
+ if (!glyph.pixels_base64)
564
+ return new Uint8Array(glyph.width * glyph.height);
565
+ return base64ToBytes(glyph.pixels_base64);
566
+ }
567
+ function materializeAtlasPage(mod, handle, metadata) {
568
+ const pointer = mod.ccall("sdf_atlas_page_pixels_ptr", "number", ["number", "number"], [handle, metadata.page]);
569
+ const length = mod.ccall("sdf_atlas_page_pixels_len", "number", ["number", "number"], [handle, metadata.page]);
570
+ if (!pointer || length <= 0)
571
+ throw new WorkerError("ATLAS_PAGE_FAILED", `WASM returned an invalid atlas page ${metadata.page}`);
572
+ const page = mod.HEAPU8.subarray(pointer, pointer + length);
573
+ const pageWidth = metadata.pageWidth;
574
+ if (!Number.isInteger(pageWidth) || pageWidth <= 0 || length % pageWidth !== 0)
575
+ throw new WorkerError("ATLAS_PAGE_FAILED", `Atlas page ${metadata.page} has invalid R8 dimensions`);
576
+ if (metadata.fullUpload)
577
+ return { ...metadata, pixels: new Uint8Array(page), dirtyRects: [] };
578
+ return {
579
+ ...metadata,
580
+ pixels: new Uint8Array(),
581
+ dirtyRects: metadata.dirtyRects.map((rect) => ({ ...rect, pixels: copyAtlasRect(page, pageWidth, rect) })),
582
+ };
583
+ }
584
+ function copyAtlasRect(page, pageWidth, rect) {
585
+ const pixels = new Uint8Array(rect.width * rect.height);
586
+ for (let row = 0; row < rect.height; row += 1) {
587
+ pixels.set(page.subarray((rect.y + row) * pageWidth + rect.x, (rect.y + row) * pageWidth + rect.x + rect.width), row * rect.width);
588
+ }
589
+ return pixels;
590
+ }
591
+ function bytesToBase64(bytes) {
592
+ let binary = "";
593
+ const chunk = 0x8000;
594
+ for (let offset = 0; offset < bytes.length; offset += chunk)
595
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + chunk));
596
+ return btoa(binary);
597
+ }
598
+ function base64ToBytes(value) {
599
+ const binary = atob(value);
600
+ const bytes = new Uint8Array(binary.length);
601
+ for (let index = 0; index < binary.length; index += 1)
602
+ bytes[index] = binary.charCodeAt(index);
603
+ return bytes;
604
+ }
605
+ function callSceneWithInput(name, sceneId, input) {
606
+ const mod = requireLoadedModule();
607
+ return callJsonWithInput(mod, name, input, [sceneHandle(sceneId)]);
608
+ }
609
+ function callSceneNoInput(name, sceneId, suffix = []) {
610
+ const mod = requireLoadedModule();
611
+ return callJson(mod, name, ["number", ...suffix.map(() => "number")], [sceneHandle(sceneId), ...suffix]);
612
+ }
613
+ function callJsonWithInput(mod, name, input, prefix = []) {
614
+ const bytes = new TextEncoder().encode(JSON.stringify(input));
615
+ const pointer = mod._malloc(bytes.byteLength);
616
+ try {
617
+ mod.HEAPU8.set(bytes, pointer);
618
+ return callJson(mod, name, [...prefix.map(() => "number"), "number", "number"], [...prefix, pointer, bytes.byteLength]);
619
+ }
620
+ finally {
621
+ mod._free(pointer);
622
+ }
623
+ }
624
+ function callJson(mod, name, argumentTypes, arguments_) {
625
+ const pointer = mod.ccall(name, "number", argumentTypes, arguments_);
626
+ try {
627
+ const value = JSON.parse(readCString(mod, pointer));
628
+ if (value.error)
629
+ throw new WorkerError("WASM_CALL_FAILED", value.error);
630
+ return value;
631
+ }
632
+ finally {
633
+ mod.ccall("sdf_layout_freetype_free_string", null, ["number"], [pointer]);
634
+ }
635
+ }
636
+ function readCString(mod, pointer) {
637
+ let end = pointer;
638
+ while (mod.HEAPU8[end] !== 0)
639
+ end += 1;
640
+ return new TextDecoder().decode(new Uint8Array(mod.HEAPU8.subarray(pointer, end)));
641
+ }
642
+ function requireLoadedModule() {
643
+ if (!modulePromise)
644
+ throw new WorkerError("WORKER_NOT_INITIALIZED", "Initialize the renderer worker before using it");
645
+ if (!moduleInstance)
646
+ throw new WorkerError("WORKER_BUSY", "The renderer module is still initializing");
647
+ return moduleInstance;
648
+ }
649
+ function sceneHandle(sceneId) {
650
+ const handle = scenes.get(sceneId);
651
+ if (handle == null)
652
+ throw new WorkerError("SCENE_NOT_FOUND", `Unknown scene: ${sceneId}`);
653
+ return handle;
654
+ }
655
+ function masterDataHandle(masterDataId) {
656
+ const handle = masterDataSessions.get(masterDataId);
657
+ if (handle == null)
658
+ throw new WorkerError("MASTERDATA_NOT_FOUND", `Unknown master-data session: ${masterDataId}`);
659
+ return handle;
660
+ }
661
+ function atlasHandle(atlasId) {
662
+ const handle = atlasSessions.get(atlasId);
663
+ if (handle == null)
664
+ throw new WorkerError("ATLAS_NOT_FOUND", `Unknown atlas session: ${atlasId}`);
665
+ return handle;
666
+ }
667
+ function registerAuthoring(handle) {
668
+ if (!Number.isInteger(handle) || handle <= 0) {
669
+ throw new WorkerError("AUTHORING_CREATE_FAILED", "WASM returned an invalid authoring handle");
670
+ }
671
+ const authoringId = `authoring:${handle}`;
672
+ authoringSessions.set(authoringId, handle);
673
+ counters.authoringSessions = authoringSessions.size;
674
+ return authoringId;
675
+ }
676
+ function authoringHandle(authoringId) {
677
+ const handle = authoringSessions.get(authoringId);
678
+ if (handle == null)
679
+ throw new WorkerError("AUTHORING_NOT_FOUND", `Unknown authoring session: ${authoringId}`);
680
+ return handle;
681
+ }
682
+ function fontKey(value) {
683
+ return `${value.region}\u0000${value.sourceHash}\u0000${value.family}`;
684
+ }
685
+ function assertHash(value) {
686
+ if (!/^[0-9a-f]{64}$/i.test(value))
687
+ throw new WorkerError("INVALID_FONT_HASH", "Font sourceHash must be a full SHA-256 digest");
688
+ }
689
+ function assertSchema(major) {
690
+ if (major !== 1)
691
+ throw new WorkerError("SCHEMA_MAJOR_UNSUPPORTED", `Unsupported renderer schema major: ${major}`);
692
+ }
693
+ function tick(value) {
694
+ if (!Number.isFinite(value) || value < 0)
695
+ throw new WorkerError("INVALID_TICK", "Tick must be a non-negative finite number");
696
+ return Math.floor(value);
697
+ }
698
+ function post(message, transfers = []) {
699
+ scope.postMessage(message, transfers);
700
+ }
701
+ class WorkerError extends Error {
702
+ code;
703
+ constructor(code, message) {
704
+ super(message);
705
+ this.code = code;
706
+ this.name = "WorkerError";
707
+ }
708
+ }