@cesdk/node-native 1.77.0-nightly.20260610

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 (47) hide show
  1. package/LICENSE.md +137 -0
  2. package/README.md +474 -0
  3. package/ThirdPartyLicenses.md +1041 -0
  4. package/assets/ly.img.cesdk/fonts/imgly_font_inter_semibold.otf +0 -0
  5. package/assets/ly.img.cesdk/icons/ErrorAudio.svg +5 -0
  6. package/assets/ly.img.cesdk/icons/ErrorConnection.svg +5 -0
  7. package/assets/ly.img.cesdk/icons/ErrorImage.svg +6 -0
  8. package/assets/ly.img.cesdk/icons/ErrorUnknown.svg +3 -0
  9. package/assets/ly.img.cesdk/icons/ErrorVideo.svg +6 -0
  10. package/assets/ly.img.cesdk/icons/Move.svg +3 -0
  11. package/assets/ly.img.cesdk/icons/RotateIndicator.svg +5 -0
  12. package/assets/ly.img.cesdk/icu/icudt74l.dat +0 -0
  13. package/assets/ly.img.cesdk/presets/.keep +0 -0
  14. package/assets/ly.img.cesdk/shaders/adjustments.sksl +106 -0
  15. package/assets/ly.img.cesdk/shaders/black_and_white_color_mixer.sksl +152 -0
  16. package/assets/ly.img.cesdk/shaders/common/ubq_adjustments.sksl +102 -0
  17. package/assets/ly.img.cesdk/shaders/common/ubq_color_conversions.sksl +354 -0
  18. package/assets/ly.img.cesdk/shaders/common/ubq_constants.sksl +13 -0
  19. package/assets/ly.img.cesdk/shaders/common/ubq_hue_constants.sksl +86 -0
  20. package/assets/ly.img.cesdk/shaders/common/ubq_noise.sksl +82 -0
  21. package/assets/ly.img.cesdk/shaders/cross_cut.sksl +38 -0
  22. package/assets/ly.img.cesdk/shaders/dot_pattern.sksl +29 -0
  23. package/assets/ly.img.cesdk/shaders/duotone_filter.sksl +26 -0
  24. package/assets/ly.img.cesdk/shaders/extrude_blur.sksl +85 -0
  25. package/assets/ly.img.cesdk/shaders/glow.sksl +31 -0
  26. package/assets/ly.img.cesdk/shaders/half_tone.sksl +14 -0
  27. package/assets/ly.img.cesdk/shaders/linocut.sksl +30 -0
  28. package/assets/ly.img.cesdk/shaders/liquid.sksl +19 -0
  29. package/assets/ly.img.cesdk/shaders/lut_filter.sksl +70 -0
  30. package/assets/ly.img.cesdk/shaders/mask_color.sksl +16 -0
  31. package/assets/ly.img.cesdk/shaders/mirror.sksl +21 -0
  32. package/assets/ly.img.cesdk/shaders/outliner.sksl +40 -0
  33. package/assets/ly.img.cesdk/shaders/pixelize.sksl +10 -0
  34. package/assets/ly.img.cesdk/shaders/placeholder_overlay_lines.sksl +18 -0
  35. package/assets/ly.img.cesdk/shaders/posterize.sksl +8 -0
  36. package/assets/ly.img.cesdk/shaders/radial_pixel.sksl +22 -0
  37. package/assets/ly.img.cesdk/shaders/recolor.sksl +57 -0
  38. package/assets/ly.img.cesdk/shaders/sharpie.sksl +74 -0
  39. package/assets/ly.img.cesdk/shaders/shifter.sksl +22 -0
  40. package/assets/ly.img.cesdk/shaders/tiltshift.sksl +21 -0
  41. package/assets/ly.img.cesdk/shaders/tv_glitch.sksl +25 -0
  42. package/assets/ly.img.cesdk/shaders/vignette.sksl +12 -0
  43. package/example.js +31 -0
  44. package/lib/index.d.ts +4072 -0
  45. package/lib/index.js +4922 -0
  46. package/lib/index.js.map +7 -0
  47. package/package.json +59 -0
package/lib/index.js ADDED
@@ -0,0 +1,4922 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // ts/src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ANIMATION_TYPES: () => ANIMATION_TYPES,
34
+ BLUR_TYPES: () => BLUR_TYPES,
35
+ CompressionFormat: () => CompressionFormat,
36
+ CompressionLevel: () => CompressionLevel,
37
+ DESIGN_BLOCK_TYPES: () => DESIGN_BLOCK_TYPES,
38
+ EFFECT_TYPES: () => EFFECT_TYPES,
39
+ FILL_TYPES: () => FILL_TYPES,
40
+ LogLevel: () => LogLevel,
41
+ MimeType: () => MimeType,
42
+ SHAPE_TYPES: () => SHAPE_TYPES,
43
+ default: () => index_default,
44
+ defaultLogger: () => defaultLogger,
45
+ isCMYKColor: () => isCMYKColor,
46
+ isRGBAColor: () => isRGBAColor,
47
+ isSpotColor: () => isSpotColor
48
+ });
49
+ module.exports = __toCommonJS(index_exports);
50
+
51
+ // ts/src/CreativeEngine.ts
52
+ var path = __toESM(require("node:path"));
53
+ var fs2 = __toESM(require("node:fs"));
54
+ var crypto = __toESM(require("node:crypto"));
55
+ var import_node_url2 = require("node:url");
56
+
57
+ // ../shared/ts/src/types.ts
58
+ var MimeType = {
59
+ Png: "image/png",
60
+ Jpeg: "image/jpeg",
61
+ WebP: "image/webp",
62
+ Tga: "image/x-tga",
63
+ Svg: "image/svg+xml",
64
+ Wav: "audio/wav",
65
+ Mp4Audio: "audio/mp4",
66
+ Mp4: "video/mp4",
67
+ QuickTime: "video/quicktime",
68
+ Binary: "application/octet-stream",
69
+ Pdf: "application/pdf",
70
+ Zip: "application/zip"
71
+ };
72
+
73
+ // ../shared/ts/src/blockTypes.ts
74
+ var DESIGN_BLOCK_TYPES = [
75
+ "scene",
76
+ "stack",
77
+ "camera",
78
+ "page",
79
+ "graphic",
80
+ "audio",
81
+ "text",
82
+ "group",
83
+ "cutout",
84
+ "track",
85
+ "caption",
86
+ "captionTrack"
87
+ ];
88
+ var SHAPE_TYPES = [
89
+ "rect",
90
+ "line",
91
+ "ellipse",
92
+ "polygon",
93
+ "star",
94
+ "vector_path"
95
+ ];
96
+ var FILL_TYPES = [
97
+ "color",
98
+ "gradient/linear",
99
+ "gradient/radial",
100
+ "gradient/conical",
101
+ "image",
102
+ "video",
103
+ "pixelStream"
104
+ ];
105
+ var EFFECT_TYPES = [
106
+ "adjustments",
107
+ "cross_cut",
108
+ "dot_pattern",
109
+ "duotone_filter",
110
+ "extrude_blur",
111
+ "glow",
112
+ "green_screen",
113
+ "half_tone",
114
+ "linocut",
115
+ "liquid",
116
+ "lut_filter",
117
+ "mirror",
118
+ "outliner",
119
+ "pixelize",
120
+ "posterize",
121
+ "radial_pixel",
122
+ "recolor",
123
+ "sharpie",
124
+ "shifter",
125
+ "tilt_shift",
126
+ "tv_glitch",
127
+ "vignette"
128
+ ];
129
+ var BLUR_TYPES = ["uniform", "linear", "mirrored", "radial"];
130
+ var ANIMATION_TYPES = [
131
+ "slide",
132
+ "pan",
133
+ "fade",
134
+ "blur",
135
+ "grow",
136
+ "zoom",
137
+ "pop",
138
+ "wipe",
139
+ "baseline",
140
+ "crop_zoom",
141
+ "spin",
142
+ "spin_loop",
143
+ "fade_loop",
144
+ "blur_loop",
145
+ "pulsating_loop",
146
+ "breathing_loop",
147
+ "jump_loop",
148
+ "squeeze_loop",
149
+ "sway_loop",
150
+ "scale_loop",
151
+ "typewriter_text",
152
+ "block_swipe_text",
153
+ "spread_text",
154
+ "merge_text",
155
+ "ken_burns"
156
+ ];
157
+
158
+ // ../shared/ts/src/color-guards.ts
159
+ function isRGBAColor(color) {
160
+ return "r" in color && "a" in color && color.r !== void 0 && color.a !== void 0;
161
+ }
162
+ function isSpotColor(color) {
163
+ return "name" in color && color.name !== void 0;
164
+ }
165
+ function isCMYKColor(color) {
166
+ return "c" in color && "m" in color && "y" in color && "k" in color && color.c !== void 0 && color.m !== void 0 && color.y !== void 0 && color.k !== void 0;
167
+ }
168
+
169
+ // ../shared/ts/src/Logger.ts
170
+ var LogLevel = {
171
+ Info: "Info",
172
+ Warning: "Warning",
173
+ Error: "Error"
174
+ };
175
+
176
+ // ../shared/ts/src/defaultLogger.ts
177
+ function defaultLogger(message, level) {
178
+ const prefixedMessage = `[UBQ] ${message}`;
179
+ switch (level) {
180
+ case "Warning": {
181
+ console.warn(prefixedMessage);
182
+ break;
183
+ }
184
+ case "Error": {
185
+ console.error(prefixedMessage);
186
+ break;
187
+ }
188
+ case "Info": {
189
+ console.info(prefixedMessage);
190
+ break;
191
+ }
192
+ default: {
193
+ console.log(prefixedMessage);
194
+ }
195
+ }
196
+ }
197
+
198
+ // ../shared/ts/src/api/VariableAPI.ts
199
+ var VariableAPI = class {
200
+ #engine;
201
+ constructor(engine) {
202
+ this.#engine = engine;
203
+ }
204
+ /**
205
+ * Get all text variable names currently stored in the engine.
206
+ * @returns List of variable names.
207
+ */
208
+ findAll() {
209
+ return this.#engine.findAllVariables();
210
+ }
211
+ /**
212
+ * Set a text variable's value. Creates the variable if it doesn't exist.
213
+ * @param key - The variable's key.
214
+ * @param value - The text value to assign.
215
+ */
216
+ setString(key, value) {
217
+ this.#engine.setVariableString(key, value);
218
+ }
219
+ /**
220
+ * Get a text variable's value.
221
+ * @param key - The variable's key.
222
+ * @returns The text value of the variable.
223
+ */
224
+ getString(key) {
225
+ return this.#engine.getVariableString(key);
226
+ }
227
+ /**
228
+ * Remove a text variable from the engine.
229
+ * @param key - The variable's key to remove.
230
+ */
231
+ remove(key) {
232
+ this.#engine.removeVariable(key);
233
+ }
234
+ };
235
+
236
+ // ts/src/platform-packages.ts
237
+ var PLATFORM_PACKAGES = Object.freeze({
238
+ "darwin-arm64": "@cesdk/node-native-darwin-arm64",
239
+ "darwin-x64": "@cesdk/node-native-darwin-x64",
240
+ "linux-x64": "@cesdk/node-native-linux-x64"
241
+ });
242
+ function platformPackageFor(platform, arch) {
243
+ return PLATFORM_PACKAGES[`${platform}-${arch}`];
244
+ }
245
+
246
+ // ts/src/builtin-typeface.ts
247
+ var BUILTIN_TYPEFACE_SOURCE_ID = "ly.img.typeface.builtin";
248
+ var BUILTIN_TYPEFACE_JSON = JSON.stringify({
249
+ version: "6.0.0",
250
+ id: BUILTIN_TYPEFACE_SOURCE_ID,
251
+ assets: [
252
+ {
253
+ id: "ly.img.typeface.builtin.inter",
254
+ label: { en: "Inter" },
255
+ payload: {
256
+ typeface: {
257
+ name: "Inter",
258
+ fonts: [
259
+ {
260
+ uri: "bundle://ly.img.cesdk/fonts/imgly_font_inter_semibold.otf",
261
+ subFamily: "Semi Bold",
262
+ weight: "semiBold",
263
+ style: "normal"
264
+ }
265
+ ]
266
+ }
267
+ }
268
+ }
269
+ ]
270
+ });
271
+
272
+ // ts/src/addDefaultAssetSources.ts
273
+ var fs = __toESM(require("node:fs/promises"));
274
+ var import_node_url = require("node:url");
275
+ var DEFAULT_SOURCE_IDS = [
276
+ "ly.img.sticker",
277
+ "ly.img.vectorpath",
278
+ "ly.img.colors.defaultPalette",
279
+ "ly.img.filter.lut",
280
+ "ly.img.filter.duotone",
281
+ "ly.img.effect",
282
+ "ly.img.blur",
283
+ "ly.img.typeface",
284
+ "ly.img.page.presets",
285
+ "ly.img.page.presets.video",
286
+ "ly.img.crop.presets",
287
+ "ly.img.text"
288
+ ];
289
+ var DEMO_CONTENT_JSON_IDS = [
290
+ "ly.img.image",
291
+ "ly.img.video",
292
+ "ly.img.audio"
293
+ ];
294
+ async function readContentJSON(url) {
295
+ if (url.startsWith("file://")) {
296
+ return fs.readFile((0, import_node_url.fileURLToPath)(url), "utf8");
297
+ }
298
+ if (url.startsWith("http://") || url.startsWith("https://")) {
299
+ const res = await fetch(url);
300
+ if (!res.ok) {
301
+ throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
302
+ }
303
+ return res.text();
304
+ }
305
+ return fs.readFile(url, "utf8");
306
+ }
307
+ async function loadOne(asset, sourceId, baseURL) {
308
+ const normalizedBase = baseURL.replace(/\/+$/, "");
309
+ const contentURL = `${normalizedBase}/${sourceId}/content.json`;
310
+ try {
311
+ const json = await readContentJSON(contentURL);
312
+ await asset.addLocalAssetSourceFromJSONString(json, normalizedBase);
313
+ } catch (err) {
314
+ const msg = err instanceof Error ? err.message : String(err);
315
+ console.warn(
316
+ `[@cesdk/node-native] Failed to load asset source '${sourceId}' from ${contentURL}: ${msg}. Skipping.`
317
+ );
318
+ }
319
+ }
320
+ async function loadDefaultAssetSources(asset, baseURL, exclude) {
321
+ const tasks = DEFAULT_SOURCE_IDS.filter((id) => !exclude.has(id)).map(
322
+ (id) => loadOne(asset, id, baseURL)
323
+ );
324
+ if (!exclude.has("ly.img.captionPresets")) {
325
+ tasks.push(loadOne(asset, "ly.img.captionPresets", baseURL));
326
+ }
327
+ await Promise.all(tasks);
328
+ }
329
+ var UPLOAD_MIMES = {
330
+ image: {
331
+ Design: ["image/jpeg", "image/png", "image/webp", "image/svg+xml", "image/bmp", "image/gif", "image/apng"],
332
+ Video: ["image/jpeg", "image/png", "image/webp", "image/svg+xml", "image/bmp"]
333
+ },
334
+ video: ["application/json", "video/mp4", "video/quicktime", "video/webm", "video/matroska", "image/gif", "image/apng"],
335
+ audio: ["audio/mpeg", "audio/mp3", "audio/x-m4a", "audio/wav"]
336
+ };
337
+ async function loadDemoAssetSources(asset, baseURL, exclude, options = {}) {
338
+ const {
339
+ withUploadAssetSources = false,
340
+ sceneMode = "Design",
341
+ sceneAPI
342
+ } = options;
343
+ const manager = asset;
344
+ const existing = new Set(manager.findAllSources?.() ?? []);
345
+ if (withUploadAssetSources) {
346
+ if (!exclude.has("ly.img.image.upload") && !existing.has("ly.img.image.upload")) {
347
+ manager.addLocalSource("ly.img.image.upload", [...UPLOAD_MIMES.image[sceneMode]]);
348
+ }
349
+ if (sceneMode === "Video" && !exclude.has("ly.img.video.upload") && !existing.has("ly.img.video.upload")) {
350
+ manager.addLocalSource("ly.img.video.upload", [...UPLOAD_MIMES.video]);
351
+ }
352
+ if (sceneMode === "Video" && !exclude.has("ly.img.audio.upload") && !existing.has("ly.img.audio.upload")) {
353
+ manager.addLocalSource("ly.img.audio.upload", [...UPLOAD_MIMES.audio]);
354
+ }
355
+ }
356
+ const contentJsonIds = DEMO_CONTENT_JSON_IDS.filter((id) => {
357
+ if (exclude.has(id)) return false;
358
+ if (sceneMode === "Design" && (id === "ly.img.video" || id === "ly.img.audio")) {
359
+ }
360
+ return true;
361
+ });
362
+ const registerTemplateSource = (id) => {
363
+ if (exclude.has(id) || existing.has(id)) return;
364
+ if (!sceneAPI) {
365
+ console.warn(
366
+ `[@cesdk/node-native] Cannot register ${id} without a sceneAPI handle. Templates will be visible but applying them will be a no-op. Pass \`{ sceneAPI: engine.scene }\` to engine.addDemoAssetSources().`
367
+ );
368
+ manager.addLocalSource(id);
369
+ return;
370
+ }
371
+ manager.addLocalSource(id, void 0, async (assetForApply) => {
372
+ const meta = assetForApply.meta;
373
+ const sceneUri = meta?.uri;
374
+ if (!sceneUri) return void 0;
375
+ if (sceneUri.startsWith("UBQ1")) {
376
+ await sceneAPI.applyTemplateFromString(sceneUri);
377
+ } else {
378
+ await sceneAPI.applyTemplateFromURL(sceneUri);
379
+ }
380
+ return void 0;
381
+ });
382
+ };
383
+ registerTemplateSource("ly.img.template");
384
+ if (sceneMode === "Video") {
385
+ registerTemplateSource("ly.img.video.template");
386
+ }
387
+ if (!exclude.has("ly.img.textComponents") && !existing.has("ly.img.textComponents")) {
388
+ contentJsonIds.push("ly.img.textComponents");
389
+ }
390
+ await Promise.all(contentJsonIds.map((id) => loadOne(asset, id, baseURL)));
391
+ }
392
+
393
+ // ts/src/configureEngine.ts
394
+ var KNOWN_FEATURE_FLAGS = /* @__PURE__ */ new Set([
395
+ "preventScrolling",
396
+ "singlePageMode",
397
+ "fileSystemUsageEnabled"
398
+ ]);
399
+ var REMOVED_KEYS = [
400
+ { key: "scene" },
401
+ { key: "page" },
402
+ { key: "initialSceneURL" },
403
+ { key: "initialSceneString" },
404
+ { key: "initialSceneFile" },
405
+ { key: "initialSceneBlob" },
406
+ { key: "initialImageURL" }
407
+ ];
408
+ function configureEngine(engine, config, _options) {
409
+ const rawLogger = config?.logger ?? defaultLog;
410
+ const logger = (message, level) => {
411
+ try {
412
+ rawLogger(message, level);
413
+ } catch {
414
+ }
415
+ };
416
+ const role = config?.role ?? "Creator";
417
+ try {
418
+ engine.editor.setRole(role);
419
+ } catch (err) {
420
+ logger(`setRole failed: ${describeError(err)}`, "Warning");
421
+ }
422
+ if (config?.featureFlags) {
423
+ const flags = config.featureFlags;
424
+ for (const flag of Object.keys(flags)) {
425
+ if (!KNOWN_FEATURE_FLAGS.has(flag)) {
426
+ logger(
427
+ `Unknown featureFlag '${flag}' will be ignored. Known flags: ${[
428
+ ...KNOWN_FEATURE_FLAGS
429
+ ].join(", ")}.`,
430
+ "Warning"
431
+ );
432
+ }
433
+ }
434
+ if (flags.preventScrolling) {
435
+ tryWarnOnFail(
436
+ () => engine.editor.setSettingBool("touch/singlePointPanning", false),
437
+ logger
438
+ );
439
+ tryWarnOnFail(
440
+ () => engine.editor.setSettingBool("touch/dragStartCanSelect", false),
441
+ logger
442
+ );
443
+ }
444
+ const singlePageMode = !!flags.singlePageMode;
445
+ tryWarnOnFail(
446
+ () => engine.editor.setSettingBool(
447
+ "features/singlePageModeEnabled",
448
+ singlePageMode
449
+ ),
450
+ logger
451
+ );
452
+ const fileSystemUsageEnabled = flags.fileSystemUsageEnabled !== false;
453
+ tryWarnOnFail(
454
+ () => engine.editor.setSettingBool(
455
+ "features/fileSystemUsageEnabled",
456
+ fileSystemUsageEnabled
457
+ ),
458
+ logger
459
+ );
460
+ }
461
+ try {
462
+ engine.editor.startTracking(
463
+ config?.license ?? "",
464
+ config?.userId ?? "",
465
+ ""
466
+ );
467
+ } catch (err) {
468
+ logger(`startTracking failed: ${describeError(err)}`, "Warning");
469
+ }
470
+ if (config) {
471
+ const seen = config;
472
+ for (const entry of REMOVED_KEYS) {
473
+ if (entry.key in seen) {
474
+ logger(
475
+ `Configuration.${entry.key} is no longer supported and will be ignored.`,
476
+ "Warning"
477
+ );
478
+ }
479
+ }
480
+ }
481
+ if (config?.presets?.typefaces && Object.keys(config.presets.typefaces).length > 0) {
482
+ logger(
483
+ "Configuration.presets.typefaces is deprecated. Register typefaces via engine.asset.addLocalSource / addLocalAssetSourceFromJSONString instead.",
484
+ "Warning"
485
+ );
486
+ }
487
+ }
488
+ function tryWarnOnFail(fn, _logger) {
489
+ try {
490
+ fn();
491
+ } catch {
492
+ }
493
+ }
494
+ function describeError(err) {
495
+ if (err instanceof Error) return err.message;
496
+ return String(err);
497
+ }
498
+ function defaultLog(message, level) {
499
+ const prefixed = `[UBQ] ${message}`;
500
+ switch (level) {
501
+ case "Warning":
502
+ console.warn(prefixed);
503
+ break;
504
+ case "Error":
505
+ console.error(prefixed);
506
+ break;
507
+ case "Info":
508
+ console.info(prefixed);
509
+ break;
510
+ default:
511
+ console.log(prefixed);
512
+ }
513
+ }
514
+
515
+ // ts/src/license.ts
516
+ var LICENSE_ENDPOINT_PRODUCTION = "https://api.img.ly/activate";
517
+ var LICENSE_ENDPOINT_STAGING = "https://api.staging.img.ly/activate";
518
+ var LICENSE_ACTIVATION_TIMEOUT_MS = 1e4;
519
+ function pickEndpoint() {
520
+ if (process.env.CESDK_LICENSE_ENDPOINT) return process.env.CESDK_LICENSE_ENDPOINT;
521
+ if (process.env.NODE_ENV === "development" || process.env.CESDK_STAGING === "1") {
522
+ return LICENSE_ENDPOINT_STAGING;
523
+ }
524
+ return LICENSE_ENDPOINT_PRODUCTION;
525
+ }
526
+ async function downloadAndCheckLicense(config, fetchImpl = globalThis.fetch) {
527
+ if (!config.license || config.license.length === 0 || config.license === "evaluation-license") {
528
+ return "";
529
+ }
530
+ if (config.license.length >= 128) {
531
+ return config.license;
532
+ }
533
+ if (!fetchImpl) {
534
+ throw new Error(
535
+ "No global `fetch` available. Pass a `fetch` polyfill or use Node 18+."
536
+ );
537
+ }
538
+ const endpoint = pickEndpoint();
539
+ const controller = new AbortController();
540
+ const timer = setTimeout(
541
+ () => controller.abort(),
542
+ LICENSE_ACTIVATION_TIMEOUT_MS
543
+ );
544
+ let response;
545
+ try {
546
+ response = await fetchImpl(endpoint, {
547
+ method: "POST",
548
+ headers: { "Content-Type": "application/json" },
549
+ body: JSON.stringify({
550
+ apiKey: config.license,
551
+ userId: config.userId
552
+ }),
553
+ signal: controller.signal
554
+ });
555
+ } catch (e) {
556
+ if (controller.signal.aborted) {
557
+ throw new Error(
558
+ `License activation timed out after ${LICENSE_ACTIVATION_TIMEOUT_MS}ms (${endpoint}). Check network connectivity or set CESDK_LICENSE_ENDPOINT.`
559
+ );
560
+ }
561
+ throw e;
562
+ } finally {
563
+ clearTimeout(timer);
564
+ }
565
+ if (!response.ok) {
566
+ throw new Error(
567
+ "Unfortunately we are experiencing a server down time and your License key cannot be validated. We're already working on a fix and will restore service soon. Should services not restore within the hour, kindly get in touch with our support team."
568
+ );
569
+ }
570
+ const data = await response.json();
571
+ if (data.status === "valid") return data.license;
572
+ if (data.status === "expired") {
573
+ throw new Error(
574
+ "Thanks for using IMG.LY for creative editing. Please note that your license file or commercial use is expired. Please get in touch with our sales team to discuss extension of your commercial license."
575
+ );
576
+ }
577
+ throw new Error(
578
+ "The License Key (API Key) you are using to access the IMG.LY SDK is invalid. Please ensure that you are using the license key tied to your subscription and get in touch with our support team."
579
+ );
580
+ }
581
+
582
+ // ts/src/CreativeEngine.ts
583
+ function isRGBAColor2(color) {
584
+ return "r" in color && "a" in color && color.r !== void 0 && color.a !== void 0;
585
+ }
586
+ function isSpotColor2(color) {
587
+ return "name" in color && color.name !== void 0;
588
+ }
589
+ function isCMYKColor2(color) {
590
+ return "c" in color && "m" in color && "y" in color && "k" in color && color.c !== void 0 && color.m !== void 0 && color.y !== void 0 && color.k !== void 0;
591
+ }
592
+ var NativeColorInternal;
593
+ ((NativeColorInternal2) => {
594
+ let ColorSpace;
595
+ ((ColorSpace2) => {
596
+ ColorSpace2[ColorSpace2["sRGB"] = 0] = "sRGB";
597
+ ColorSpace2[ColorSpace2["CMYK"] = 1] = "CMYK";
598
+ ColorSpace2[ColorSpace2["SpotColor"] = 2] = "SpotColor";
599
+ })(ColorSpace = NativeColorInternal2.ColorSpace || (NativeColorInternal2.ColorSpace = {}));
600
+ function toColor(color) {
601
+ switch (color.colorSpace) {
602
+ case 0 /* sRGB */:
603
+ return {
604
+ r: color.components.x,
605
+ g: color.components.y,
606
+ b: color.components.z,
607
+ a: color.components.w
608
+ };
609
+ case 2 /* SpotColor */: {
610
+ return {
611
+ name: color.spotColorName,
612
+ tint: color.tint,
613
+ externalReference: color.externalReference
614
+ };
615
+ }
616
+ case 1 /* CMYK */:
617
+ return {
618
+ c: color.components.x,
619
+ m: color.components.y,
620
+ y: color.components.z,
621
+ k: color.components.w,
622
+ tint: color.tint
623
+ };
624
+ default:
625
+ throw new Error("Unknown color space!");
626
+ }
627
+ }
628
+ NativeColorInternal2.toColor = toColor;
629
+ function fromColor(color) {
630
+ if (isRGBAColor2(color)) {
631
+ return {
632
+ colorSpace: 0 /* sRGB */,
633
+ components: { x: color.r, y: color.g, z: color.b, w: color.a },
634
+ spotColorName: "",
635
+ tint: 1,
636
+ externalReference: ""
637
+ };
638
+ } else if (isCMYKColor2(color)) {
639
+ return {
640
+ colorSpace: 1 /* CMYK */,
641
+ components: { x: color.c, y: color.m, z: color.y, w: color.k },
642
+ spotColorName: "",
643
+ tint: color.tint ?? 1,
644
+ externalReference: ""
645
+ };
646
+ } else if (isSpotColor2(color)) {
647
+ return {
648
+ colorSpace: 2 /* SpotColor */,
649
+ components: { x: 0, y: 0, z: 0, w: 0 },
650
+ spotColorName: color.name,
651
+ tint: color.tint,
652
+ externalReference: color.externalReference
653
+ };
654
+ }
655
+ throw new Error("Unknown color space!");
656
+ }
657
+ NativeColorInternal2.fromColor = fromColor;
658
+ })(NativeColorInternal || (NativeColorInternal = {}));
659
+ function convertUTF16ToGraphemeRange(text, from, to) {
660
+ if (from < 0 && to < 0) {
661
+ return { from, to };
662
+ }
663
+ const segmenter = new Intl.Segmenter("en");
664
+ const codeUnitToGrapheme = new Array(text.length + 1);
665
+ let codeUnit = 0;
666
+ let graphemeIdx = 0;
667
+ for (const seg of segmenter.segment(text)) {
668
+ for (let i = codeUnit; i <= seg.index; i++) {
669
+ codeUnitToGrapheme[i] = graphemeIdx;
670
+ }
671
+ codeUnit = seg.index + 1;
672
+ graphemeIdx++;
673
+ }
674
+ for (let i = codeUnit; i <= text.length; i++) {
675
+ codeUnitToGrapheme[i] = graphemeIdx;
676
+ }
677
+ const fromGrapheme = from < 0 ? from : codeUnitToGrapheme[Math.min(from, text.length)];
678
+ const toGrapheme = to < 0 ? to : codeUnitToGrapheme[Math.min(to, text.length)];
679
+ return { from: fromGrapheme, to: toGrapheme };
680
+ }
681
+ function convertGraphemeToUTF16Range(text, from, to) {
682
+ if (from < 0 && to < 0) {
683
+ return { from, to };
684
+ }
685
+ const segmenter = new Intl.Segmenter("en");
686
+ const graphemeStarts = [];
687
+ for (const seg of segmenter.segment(text)) {
688
+ graphemeStarts.push(seg.index);
689
+ }
690
+ graphemeStarts.push(text.length);
691
+ const fromUtf16 = from < 0 ? from : from >= graphemeStarts.length ? text.length : graphemeStarts[from];
692
+ const toUtf16 = to < 0 ? to : to >= graphemeStarts.length ? text.length : graphemeStarts[to];
693
+ return { from: fromUtf16, to: toUtf16 };
694
+ }
695
+ var NATIVE_DEBUG_ENABLED = process.env.CESDK_DEBUG === "1" || /(^|,)cesdk(:native)?(,|$)/.test(process.env.DEBUG ?? "");
696
+ function nativeDebug(message) {
697
+ if (NATIVE_DEBUG_ENABLED) {
698
+ console.error(`[cesdk:native] ${message}`);
699
+ }
700
+ }
701
+ async function verifyAddonSha256(addonPath) {
702
+ if (process.env.CESDK_SKIP_BINARY_VERIFY === "1") {
703
+ nativeDebug("SHA-256 verification disabled via CESDK_SKIP_BINARY_VERIFY=1");
704
+ return;
705
+ }
706
+ const shaPath = `${addonPath}.sha256`;
707
+ if (!fs2.existsSync(shaPath)) {
708
+ return;
709
+ }
710
+ const shaContents = (await fs2.promises.readFile(shaPath, "utf8")).trim();
711
+ const expected = shaContents.split(/\s+/)[0];
712
+ if (!/^[0-9a-f]{64}$/i.test(expected)) {
713
+ const err = new Error(
714
+ `@cesdk/node-native: malformed SHA-256 sidecar at ${shaPath} \u2014 expected a 64-char hex digest. Likely a corrupted or incomplete install. Re-install @cesdk/node-native (verify the package signature with \`npm audit signatures\`), or set CESDK_SKIP_BINARY_VERIFY=1 to bypass (not recommended in production).`
715
+ );
716
+ err.code = "ERR_CESDK_BINARY_TAMPERED";
717
+ throw err;
718
+ }
719
+ const hasher = crypto.createHash("sha256");
720
+ await new Promise((resolve, reject) => {
721
+ const stream = fs2.createReadStream(addonPath, { highWaterMark: 64 * 1024 });
722
+ stream.on("data", (chunk) => hasher.update(chunk));
723
+ stream.on("error", reject);
724
+ stream.on("end", () => resolve());
725
+ });
726
+ const actual = hasher.digest("hex");
727
+ if (actual !== expected.toLowerCase()) {
728
+ const err = new Error(
729
+ `@cesdk/node-native: SHA-256 mismatch for native binary at ${addonPath}.
730
+ expected ${expected}
731
+ actual ${actual}
732
+ The binary does not match its checksum sidecar \u2014 likely a corrupted or incomplete install. Re-install @cesdk/node-native (and verify the package signature with \`npm audit signatures\`), or set CESDK_SKIP_BINARY_VERIFY=1 to bypass (not recommended in production).`
733
+ );
734
+ err.code = "ERR_CESDK_BINARY_TAMPERED";
735
+ throw err;
736
+ }
737
+ nativeDebug(`SHA-256 verified for ${addonPath}`);
738
+ }
739
+ function recordLoadError(bucket, source, e) {
740
+ if (e instanceof Error) {
741
+ const maybeCoded = e;
742
+ bucket.push({
743
+ source,
744
+ message: e.message,
745
+ code: typeof maybeCoded.code === "string" ? maybeCoded.code : void 0
746
+ });
747
+ } else {
748
+ bucket.push({ source, message: String(e) });
749
+ }
750
+ }
751
+ function rethrowIfTampered(e) {
752
+ if (e?.code === "ERR_CESDK_BINARY_TAMPERED") {
753
+ throw e;
754
+ }
755
+ }
756
+ function assertSupportedRuntime() {
757
+ const g = globalThis;
758
+ if (typeof g.Bun !== "undefined") {
759
+ throw new Error(
760
+ "@cesdk/node-native is not supported on Bun. Use @cesdk/node (WASM) instead."
761
+ );
762
+ }
763
+ if (typeof g.Deno !== "undefined") {
764
+ throw new Error(
765
+ "@cesdk/node-native is not supported on Deno. Use @cesdk/node (WASM) instead."
766
+ );
767
+ }
768
+ }
769
+ async function resolveFromEnvOverride() {
770
+ const envOverride = process.env.CESDK_NATIVE_BINARY_PATH;
771
+ if (envOverride === void 0 || envOverride === "") {
772
+ return void 0;
773
+ }
774
+ nativeDebug(`CESDK_NATIVE_BINARY_PATH override -> ${envOverride}`);
775
+ if (!path.isAbsolute(envOverride)) {
776
+ throw new Error(
777
+ `CESDK_NATIVE_BINARY_PATH must be an absolute path (got '${envOverride}').`
778
+ );
779
+ }
780
+ if (!envOverride.endsWith(".node")) {
781
+ throw new Error(
782
+ `CESDK_NATIVE_BINARY_PATH must point to a .node file (got '${envOverride}').`
783
+ );
784
+ }
785
+ let resolvedPath;
786
+ try {
787
+ resolvedPath = fs2.realpathSync(envOverride);
788
+ } catch (e) {
789
+ throw new Error(
790
+ `CESDK_NATIVE_BINARY_PATH at '${envOverride}' cannot be resolved: ${e instanceof Error ? e.message : String(e)}`
791
+ );
792
+ }
793
+ if (!resolvedPath.endsWith(".node")) {
794
+ throw new Error(
795
+ `CESDK_NATIVE_BINARY_PATH symlink target '${resolvedPath}' is not a .node file (override path: '${envOverride}').`
796
+ );
797
+ }
798
+ await verifyAddonSha256(resolvedPath);
799
+ try {
800
+ const addon = require(resolvedPath);
801
+ return { addon, addonPath: resolvedPath };
802
+ } catch (e) {
803
+ throw new Error(
804
+ `Failed to load CESDK_NATIVE_BINARY_PATH override at '${resolvedPath}': ${e instanceof Error ? e.message : String(e)}`
805
+ );
806
+ }
807
+ }
808
+ async function resolveFromSiblingPackage(siblingPkg, errors) {
809
+ try {
810
+ const pkgJsonPath = require.resolve(`${siblingPkg}/package.json`);
811
+ const loadPath = path.join(path.dirname(pkgJsonPath), "cesdk_native.node");
812
+ nativeDebug(`trying sibling package -> ${loadPath}`);
813
+ await verifyAddonSha256(loadPath);
814
+ const addon = require(loadPath);
815
+ return { addon, addonPath: loadPath };
816
+ } catch (e) {
817
+ rethrowIfTampered(e);
818
+ recordLoadError(errors, `${siblingPkg}/cesdk_native.node`, e);
819
+ return void 0;
820
+ }
821
+ }
822
+ async function resolveFromInPackage(errors) {
823
+ const inPkgPath = path.join(__dirname, "cesdk_native.node");
824
+ nativeDebug(`trying in-package -> ${inPkgPath}`);
825
+ try {
826
+ await verifyAddonSha256(inPkgPath);
827
+ const addon = require(inPkgPath);
828
+ return { addon, addonPath: inPkgPath };
829
+ } catch (e) {
830
+ rethrowIfTampered(e);
831
+ recordLoadError(errors, inPkgPath, e);
832
+ return void 0;
833
+ }
834
+ }
835
+ function buildLoadFailureError(key, siblingPkg, isProductionInstall, productionErrors, devErrors) {
836
+ const recovery = siblingPkg !== void 0 ? [
837
+ `Recovery:`,
838
+ ` npm install --include=optional ${siblingPkg}`,
839
+ ` (or re-run the install with optional dependencies enabled)`,
840
+ ` Or use @cesdk/node (WASM) for broader install coverage.`
841
+ ].join("\n") : [
842
+ `Your platform/arch '${key}' is not supported by @cesdk/node-native.`,
843
+ `Use @cesdk/node (WASM) for broader platform coverage.`
844
+ ].join("\n");
845
+ const visibleErrors = isProductionInstall ? productionErrors : [...productionErrors, ...devErrors];
846
+ const message = [
847
+ `Failed to load the @cesdk/node-native addon for ${key}.`,
848
+ ``,
849
+ recovery,
850
+ ``,
851
+ `Tried:`,
852
+ ...visibleErrors.map(
853
+ (e) => ` - ${e.source}: ${e.message}${e.code ? ` [${e.code}]` : ""}`
854
+ )
855
+ ].join("\n");
856
+ const err = new Error(message);
857
+ const codedError = visibleErrors.find((e) => e.code !== void 0);
858
+ if (codedError?.code) {
859
+ err.code = codedError.code;
860
+ }
861
+ return err;
862
+ }
863
+ async function loadNativeAddon() {
864
+ assertSupportedRuntime();
865
+ const platform = process.platform;
866
+ const arch = process.arch;
867
+ const key = `${platform}-${arch}`;
868
+ nativeDebug(`platform=${platform} arch=${arch} key=${key}`);
869
+ const fromEnv = await resolveFromEnvOverride();
870
+ if (fromEnv !== void 0) {
871
+ return fromEnv;
872
+ }
873
+ const isProductionInstall = __dirname.includes(
874
+ `${path.sep}node_modules${path.sep}`
875
+ );
876
+ const productionErrors = [];
877
+ const devErrors = [];
878
+ const siblingPkg = platformPackageFor(platform, arch);
879
+ if (siblingPkg !== void 0) {
880
+ const fromSibling = await resolveFromSiblingPackage(
881
+ siblingPkg,
882
+ productionErrors
883
+ );
884
+ if (fromSibling !== void 0) {
885
+ return fromSibling;
886
+ }
887
+ }
888
+ const fromInPackage = await resolveFromInPackage(devErrors);
889
+ if (fromInPackage !== void 0) {
890
+ return fromInPackage;
891
+ }
892
+ throw buildLoadFailureError(
893
+ key,
894
+ siblingPkg,
895
+ isProductionInstall,
896
+ productionErrors,
897
+ devErrors
898
+ );
899
+ }
900
+ var _loadedAddon;
901
+ function getNativeAddon() {
902
+ if (_loadedAddon === void 0) {
903
+ _loadedAddon = loadNativeAddon().catch((e) => {
904
+ _loadedAddon = void 0;
905
+ throw e;
906
+ });
907
+ }
908
+ return _loadedAddon;
909
+ }
910
+ function resolveDefaultAssetsDir(addonPath) {
911
+ const candidates = [
912
+ path.join(__dirname, "..", "assets"),
913
+ path.join(__dirname, "..", "..", "assets"),
914
+ path.join(path.dirname(addonPath), "assets")
915
+ ];
916
+ for (const dir of candidates) {
917
+ try {
918
+ if (fs2.statSync(dir).isDirectory()) {
919
+ nativeDebug(`resolveDefaultAssetsDir -> ${dir}`);
920
+ return dir;
921
+ }
922
+ } catch {
923
+ }
924
+ }
925
+ return void 0;
926
+ }
927
+ function convertFontSizeToPoints(value, unit, dpi) {
928
+ switch (unit) {
929
+ case "Point":
930
+ return value;
931
+ case "Pixel":
932
+ return 72 * value / dpi;
933
+ case "Millimeter":
934
+ return value * 72 / 25.4;
935
+ case "Inch":
936
+ return value * 72;
937
+ default:
938
+ return value;
939
+ }
940
+ }
941
+ function convertFontSizeFromPoints(valueInPoints, unit, dpi) {
942
+ switch (unit) {
943
+ case "Point":
944
+ return valueInPoints;
945
+ case "Pixel":
946
+ return valueInPoints * dpi / 72;
947
+ case "Millimeter":
948
+ return valueInPoints * 25.4 / 72;
949
+ case "Inch":
950
+ return valueInPoints / 72;
951
+ default:
952
+ return valueInPoints;
953
+ }
954
+ }
955
+ function normalizeBaseURL(raw) {
956
+ if (!raw) return raw;
957
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(raw)) {
958
+ return raw.endsWith("/") ? raw : `${raw}/`;
959
+ }
960
+ try {
961
+ const asUrl = (0, import_node_url2.pathToFileURL)(raw).href;
962
+ return asUrl.endsWith("/") ? asUrl : `${asUrl}/`;
963
+ } catch {
964
+ return raw.endsWith("/") ? raw : `${raw}/`;
965
+ }
966
+ }
967
+ function resolveEngineDevice(config) {
968
+ return config?.device ?? (process.env.CESDK_FORCE_CPU === "1" ? "cpu" : "auto");
969
+ }
970
+ function resolveEngineBaseURL(config, nativeAddonPath) {
971
+ let baseURL;
972
+ if (config?.baseURL !== void 0) {
973
+ baseURL = normalizeBaseURL(config.baseURL);
974
+ } else {
975
+ const assetsDir = resolveDefaultAssetsDir(nativeAddonPath);
976
+ baseURL = assetsDir ? `${(0, import_node_url2.pathToFileURL)(assetsDir).href}/` : "";
977
+ }
978
+ let enginePath = baseURL;
979
+ if (enginePath.startsWith("file://")) {
980
+ try {
981
+ enginePath = (0, import_node_url2.fileURLToPath)(enginePath);
982
+ } catch {
983
+ }
984
+ }
985
+ return { baseURL, enginePath };
986
+ }
987
+ async function registerBuiltinTypeface(engine) {
988
+ if (process.env.CESDK_SUPPRESS_BUILTIN_TYPEFACE === "1") {
989
+ return;
990
+ }
991
+ try {
992
+ await engine.asset.addLocalAssetSourceFromJSONString(BUILTIN_TYPEFACE_JSON);
993
+ } catch {
994
+ }
995
+ }
996
+ var BlockAPI = class {
997
+ #engine;
998
+ constructor(engine) {
999
+ this.#engine = engine;
1000
+ }
1001
+ // ==========================================================================
1002
+ // Block Lifecycle
1003
+ // ==========================================================================
1004
+ /**
1005
+ * Create a new block of the specified type.
1006
+ * @param type - The block type (e.g., 'graphic', 'text', 'page', 'audio').
1007
+ * @returns The ID of the newly created block.
1008
+ */
1009
+ create(type) {
1010
+ return this.#engine.create(type);
1011
+ }
1012
+ /**
1013
+ * Create a new fill block of the specified type.
1014
+ * @param type - The fill type (e.g., 'color', 'gradient/linear', 'gradient/radial', 'image', 'video').
1015
+ * @returns The ID of the newly created fill block.
1016
+ */
1017
+ createFill(type) {
1018
+ return this.#engine.createFill(type);
1019
+ }
1020
+ /**
1021
+ * Create a new shape block of the specified type.
1022
+ * @param type - The shape type (e.g., 'rect', 'ellipse', 'polygon', 'star', 'line').
1023
+ * @returns The ID of the newly created shape block.
1024
+ */
1025
+ createShape(type) {
1026
+ return this.#engine.createShape(type);
1027
+ }
1028
+ /**
1029
+ * Create a new effect block of the specified type.
1030
+ * @param type - The effect type (e.g., 'adjustments', 'pixelize', 'glow', 'half_tone').
1031
+ * @returns The ID of the newly created effect block.
1032
+ */
1033
+ createEffect(type) {
1034
+ return this.#engine.createEffect(type);
1035
+ }
1036
+ /**
1037
+ * Create a new blur block of the specified type.
1038
+ * @param type - The blur type (e.g., 'uniform', 'linear', 'mirrored', 'radial').
1039
+ * @returns The ID of the newly created blur block.
1040
+ */
1041
+ createBlur(type) {
1042
+ return this.#engine.createBlur(type);
1043
+ }
1044
+ /**
1045
+ * Create a new animation block of the specified type.
1046
+ * @param type - The animation type.
1047
+ * @returns The ID of the newly created animation block.
1048
+ */
1049
+ createAnimation(type) {
1050
+ return this.#engine.createAnimation(type);
1051
+ }
1052
+ /**
1053
+ * Duplicate an existing block.
1054
+ * @param block - The ID of the block to duplicate.
1055
+ * @param attachToParent - Whether to attach the duplicate to the same parent (default: true).
1056
+ * @returns The ID of the duplicated block.
1057
+ */
1058
+ duplicate(block, attachToParent = true) {
1059
+ return this.#engine.duplicate(block, attachToParent);
1060
+ }
1061
+ /**
1062
+ * Destroy a block and release its resources.
1063
+ * @param block - The ID of the block to destroy.
1064
+ */
1065
+ destroy(block) {
1066
+ this.#engine.destroy(block);
1067
+ }
1068
+ /**
1069
+ * Check if a block ID refers to a valid, existing block.
1070
+ * @param block - The ID to check.
1071
+ * @returns True if the block exists and is valid, false otherwise.
1072
+ */
1073
+ isValid(block) {
1074
+ return this.#engine.isValid(block);
1075
+ }
1076
+ // ==========================================================================
1077
+ // Find Methods
1078
+ // ==========================================================================
1079
+ /**
1080
+ * Find all blocks in the scene.
1081
+ * @returns Array of all block IDs.
1082
+ */
1083
+ findAll() {
1084
+ return this.#engine.findAll();
1085
+ }
1086
+ /**
1087
+ * Find all placeholder blocks in the scene.
1088
+ * @returns Array of placeholder block IDs.
1089
+ */
1090
+ findAllPlaceholders() {
1091
+ return this.#engine.findAllPlaceholders();
1092
+ }
1093
+ /**
1094
+ * Find all blocks that are not part of the active scene tree (orphans).
1095
+ * Useful for diagnosing leaks where blocks are created with `create(...)`
1096
+ * but never appended to a parent.
1097
+ * @returns Array of unreferenced block IDs.
1098
+ */
1099
+ findAllUnused() {
1100
+ return this.#engine.findAllUnused();
1101
+ }
1102
+ /**
1103
+ * Find blocks by their name.
1104
+ * @param name - The name to search for.
1105
+ * @returns Array of matching block IDs.
1106
+ */
1107
+ findByName(name) {
1108
+ return this.#engine.findByName(name);
1109
+ }
1110
+ /**
1111
+ * Find blocks by their type.
1112
+ * @param type - The type to search for (e.g., '//ly.img.ubq/graphic').
1113
+ * @returns Array of matching block IDs.
1114
+ */
1115
+ findByType(type) {
1116
+ return this.#engine.findByType(type);
1117
+ }
1118
+ /**
1119
+ * Find blocks by their kind.
1120
+ * @param kind - The kind to search for (e.g., 'image', 'text', 'sticker').
1121
+ * @returns Array of matching block IDs.
1122
+ */
1123
+ findByKind(kind) {
1124
+ return this.#engine.findByKind(kind);
1125
+ }
1126
+ /**
1127
+ * Find all currently selected blocks.
1128
+ * @returns Array of selected block IDs.
1129
+ */
1130
+ findAllSelected() {
1131
+ return this.#engine.findAllSelected();
1132
+ }
1133
+ // ==========================================================================
1134
+ // Hierarchy
1135
+ // ==========================================================================
1136
+ /**
1137
+ * Get the parent of a block.
1138
+ * @param block - The ID of the block.
1139
+ * @returns The ID of the parent block, or null if no parent.
1140
+ */
1141
+ getParent(block) {
1142
+ if (!this.#engine.hasParent(block)) {
1143
+ return null;
1144
+ }
1145
+ return this.#engine.getParent(block);
1146
+ }
1147
+ /**
1148
+ * Check if a block has a parent.
1149
+ * @param block - The ID of the block.
1150
+ * @returns True if the block has a parent.
1151
+ */
1152
+ hasParent(block) {
1153
+ return this.#engine.hasParent(block);
1154
+ }
1155
+ /**
1156
+ * Get all children of a block.
1157
+ * @param block - The ID of the parent block.
1158
+ * @returns Array of child block IDs.
1159
+ */
1160
+ getChildren(block) {
1161
+ return this.#engine.getChildren(block);
1162
+ }
1163
+ /**
1164
+ * Insert a child block at a specific index.
1165
+ * @param parent - The ID of the parent block.
1166
+ * @param child - The ID of the child block to insert.
1167
+ * @param index - The index at which to insert the child.
1168
+ */
1169
+ insertChild(parent, child, index) {
1170
+ this.#engine.insertChild(parent, child, index);
1171
+ }
1172
+ /**
1173
+ * Append a child block to the end of the parent's children.
1174
+ * @param parent - The ID of the parent block.
1175
+ * @param child - The ID of the child block to append.
1176
+ */
1177
+ appendChild(parent, child) {
1178
+ this.#engine.appendChild(parent, child);
1179
+ }
1180
+ // ==========================================================================
1181
+ // Properties - Generic
1182
+ // ==========================================================================
1183
+ /**
1184
+ * Get the type of a block.
1185
+ * @param block - The ID of the block.
1186
+ * @returns The block type string (e.g., '//ly.img.ubq/graphic').
1187
+ */
1188
+ getType(block) {
1189
+ return this.#engine.getType(block);
1190
+ }
1191
+ /**
1192
+ * Get the kind of a block.
1193
+ * @param block - The ID of the block.
1194
+ * @returns The block kind string (e.g., 'image', 'text', 'sticker').
1195
+ */
1196
+ getKind(block) {
1197
+ return this.#engine.getKind(block);
1198
+ }
1199
+ /**
1200
+ * Set the kind of a block.
1201
+ * @param block - The ID of the block.
1202
+ * @param kind - The new kind value.
1203
+ */
1204
+ setKind(block, kind) {
1205
+ this.#engine.setKind(block, kind);
1206
+ }
1207
+ /**
1208
+ * Get the name of a block.
1209
+ * @param block - The ID of the block.
1210
+ * @returns The block name.
1211
+ */
1212
+ getName(block) {
1213
+ return this.#engine.getName(block);
1214
+ }
1215
+ /**
1216
+ * Set the name of a block.
1217
+ * @param block - The ID of the block.
1218
+ * @param name - The new name.
1219
+ */
1220
+ setName(block, name) {
1221
+ this.#engine.setName(block, name);
1222
+ }
1223
+ /**
1224
+ * Get the UUID of a block.
1225
+ * @param block - The ID of the block.
1226
+ * @returns The block's UUID string.
1227
+ */
1228
+ getUUID(block) {
1229
+ return this.#engine.getUUID(block);
1230
+ }
1231
+ // Properties - typed
1232
+ getBool(block, property) {
1233
+ return this.#engine.getBool(block, property);
1234
+ }
1235
+ setBool(block, property, value) {
1236
+ this.#engine.setBool(block, property, value);
1237
+ }
1238
+ getInt(block, property) {
1239
+ return this.#engine.getInt(block, property);
1240
+ }
1241
+ setInt(block, property, value) {
1242
+ this.#engine.setInt(block, property, value);
1243
+ }
1244
+ getFloat(block, property) {
1245
+ return this.#engine.getFloat(block, property);
1246
+ }
1247
+ setFloat(block, property, value) {
1248
+ this.#engine.setFloat(block, property, value);
1249
+ }
1250
+ getDouble(block, property) {
1251
+ return this.#engine.getDouble(block, property);
1252
+ }
1253
+ setDouble(block, property, value) {
1254
+ this.#engine.setDouble(block, property, value);
1255
+ }
1256
+ getString(block, property) {
1257
+ return this.#engine.getString(block, property);
1258
+ }
1259
+ setString(block, property, value) {
1260
+ this.#engine.setString(block, property, value);
1261
+ }
1262
+ getColor(block, property) {
1263
+ const internalColor = this.#engine.getColor(
1264
+ block,
1265
+ property
1266
+ );
1267
+ return NativeColorInternal.toColor(internalColor);
1268
+ }
1269
+ setColor(block, property, value) {
1270
+ const internalColor = NativeColorInternal.fromColor(value);
1271
+ this.#engine.setColor(block, property, internalColor);
1272
+ }
1273
+ getColorRGBA(block, property) {
1274
+ const color = this.getColor(block, property);
1275
+ if (!isRGBAColor2(color)) {
1276
+ throw new Error(
1277
+ "Color is not in sRGB color space. Use getColor() for non-RGBA colors."
1278
+ );
1279
+ }
1280
+ return { r: color.r, g: color.g, b: color.b, a: color.a };
1281
+ }
1282
+ setColorRGBA(block, property, r, g, b, a) {
1283
+ this.setColor(block, property, { r, g, b, a });
1284
+ }
1285
+ getEnum(block, property) {
1286
+ return this.#engine.getEnum(block, property);
1287
+ }
1288
+ setEnum(block, property, value) {
1289
+ this.#engine.setEnum(block, property, value);
1290
+ }
1291
+ // Transform
1292
+ getPositionX(block) {
1293
+ return this.#engine.getPositionX(block);
1294
+ }
1295
+ setPositionX(block, value) {
1296
+ this.#engine.setPositionX(block, value);
1297
+ }
1298
+ getPositionY(block) {
1299
+ return this.#engine.getPositionY(block);
1300
+ }
1301
+ setPositionY(block, value) {
1302
+ this.#engine.setPositionY(block, value);
1303
+ }
1304
+ getPositionXMode(block) {
1305
+ return this.#engine.getPositionXMode(block);
1306
+ }
1307
+ setPositionXMode(block, mode) {
1308
+ this.#engine.setPositionXMode(block, mode);
1309
+ }
1310
+ getPositionYMode(block) {
1311
+ return this.#engine.getPositionYMode(block);
1312
+ }
1313
+ setPositionYMode(block, mode) {
1314
+ this.#engine.setPositionYMode(block, mode);
1315
+ }
1316
+ getWidth(block) {
1317
+ return this.#engine.getWidth(block);
1318
+ }
1319
+ setWidth(block, value) {
1320
+ this.#engine.setWidth(block, value);
1321
+ }
1322
+ getWidthMode(block) {
1323
+ return this.#engine.getWidthMode(block);
1324
+ }
1325
+ setWidthMode(block, mode) {
1326
+ this.#engine.setWidthMode(block, mode);
1327
+ }
1328
+ getHeight(block) {
1329
+ return this.#engine.getHeight(block);
1330
+ }
1331
+ setHeight(block, value) {
1332
+ this.#engine.setHeight(block, value);
1333
+ }
1334
+ getHeightMode(block) {
1335
+ return this.#engine.getHeightMode(block);
1336
+ }
1337
+ setHeightMode(block, mode) {
1338
+ this.#engine.setHeightMode(block, mode);
1339
+ }
1340
+ getRotation(block) {
1341
+ return this.#engine.getRotation(block);
1342
+ }
1343
+ setRotation(block, value) {
1344
+ this.#engine.setRotation(block, value);
1345
+ }
1346
+ // Flip - WASM API has separate getters (no combined getFlip)
1347
+ getFlipHorizontal(block) {
1348
+ return this.#engine.getFlip(block).horizontal;
1349
+ }
1350
+ getFlipVertical(block) {
1351
+ return this.#engine.getFlip(block).vertical;
1352
+ }
1353
+ setFlipHorizontal(block, flip) {
1354
+ this.#engine.setFlipHorizontal(block, flip);
1355
+ }
1356
+ setFlipVertical(block, flip) {
1357
+ this.#engine.setFlipVertical(block, flip);
1358
+ }
1359
+ // WASM API convenience method for position
1360
+ setPosition(block, x, y, options) {
1361
+ if (options?.positionMode) {
1362
+ this.#engine.setPositionXMode(block, options.positionMode);
1363
+ this.#engine.setPositionYMode(block, options.positionMode);
1364
+ }
1365
+ this.#engine.setPositionX(block, x);
1366
+ this.#engine.setPositionY(block, y);
1367
+ }
1368
+ // WASM API convenience method for size
1369
+ setSize(block, width, height, options) {
1370
+ if (options?.sizeMode) {
1371
+ this.#engine.setWidthMode(block, options.sizeMode);
1372
+ this.#engine.setHeightMode(block, options.sizeMode);
1373
+ }
1374
+ this.#engine.setWidth(block, width);
1375
+ this.#engine.setHeight(block, height);
1376
+ }
1377
+ scale(block, scale, anchorX, anchorY) {
1378
+ this.#engine.scale(block, scale, anchorX, anchorY);
1379
+ }
1380
+ fillParent(block) {
1381
+ this.#engine.fillParent(block);
1382
+ }
1383
+ resizeContentAware(blocks, width, height) {
1384
+ return this.#engine.resizeContentAware(blocks, width, height);
1385
+ }
1386
+ // Global bounding box
1387
+ getGlobalBoundingBoxX(block) {
1388
+ return this.#engine.getGlobalBoundingBoxX(block);
1389
+ }
1390
+ getGlobalBoundingBoxY(block) {
1391
+ return this.#engine.getGlobalBoundingBoxY(block);
1392
+ }
1393
+ getGlobalBoundingBoxWidth(block) {
1394
+ return this.#engine.getGlobalBoundingBoxWidth(block);
1395
+ }
1396
+ getGlobalBoundingBoxHeight(block) {
1397
+ return this.#engine.getGlobalBoundingBoxHeight(block);
1398
+ }
1399
+ getScreenSpaceBoundingBoxXYWH(blocks) {
1400
+ return this.#engine.getScreenSpaceBoundingBoxXYWH(blocks);
1401
+ }
1402
+ // Fill - WASM also has hasFillColor and isFillColorEnabled
1403
+ supportsFill(block) {
1404
+ return this.#engine.supportsFill(block);
1405
+ }
1406
+ hasFill(block) {
1407
+ return this.#engine.hasFill(block);
1408
+ }
1409
+ hasFillColor(block) {
1410
+ return this.hasFill(block);
1411
+ }
1412
+ isFillColorEnabled(block) {
1413
+ return this.#engine.getFillEnabled(block);
1414
+ }
1415
+ setFillColorEnabled(block, enabled) {
1416
+ this.#engine.setFillEnabled(block, enabled);
1417
+ }
1418
+ getFillColorRGBA(block) {
1419
+ const color = this.getFillSolidColor(block);
1420
+ if (!isRGBAColor2(color)) {
1421
+ throw new Error(
1422
+ "Fill color is not in sRGB color space. Use getFillSolidColor() for non-RGBA colors."
1423
+ );
1424
+ }
1425
+ return { r: color.r, g: color.g, b: color.b, a: color.a };
1426
+ }
1427
+ setFillColorRGBA(block, r, g, b, a) {
1428
+ this.setFillSolidColor(block, r, g, b, a);
1429
+ }
1430
+ getFill(block) {
1431
+ return this.#engine.getFill(block);
1432
+ }
1433
+ setFill(block, fill) {
1434
+ this.#engine.setFill(block, fill);
1435
+ }
1436
+ getFillEnabled(block) {
1437
+ return this.#engine.getFillEnabled(block);
1438
+ }
1439
+ isFillEnabled(block) {
1440
+ return this.#engine.getFillEnabled(block);
1441
+ }
1442
+ setFillEnabled(block, enabled) {
1443
+ this.#engine.setFillEnabled(block, enabled);
1444
+ }
1445
+ getFillSolidColor(block) {
1446
+ return this.#engine.getFillSolidColor(block);
1447
+ }
1448
+ setFillSolidColor(block, r, g, b, a = 1) {
1449
+ this.#engine.setFillSolidColor(block, { r, g, b, a });
1450
+ }
1451
+ getGradientColorStops(block, property) {
1452
+ const raw = this.#engine.getGradientColorStops(block, property);
1453
+ return raw.map((s) => ({
1454
+ color: NativeColorInternal.toColor(s.color),
1455
+ stop: s.stop
1456
+ }));
1457
+ }
1458
+ setGradientColorStops(block, property, stops) {
1459
+ const engineStops = stops.map((s) => ({
1460
+ color: NativeColorInternal.fromColor(s.color),
1461
+ stop: s.stop
1462
+ }));
1463
+ this.#engine.setGradientColorStops(block, property, engineStops);
1464
+ }
1465
+ // Stroke
1466
+ supportsStroke(block) {
1467
+ return this.#engine.supportsStroke(block);
1468
+ }
1469
+ hasStroke(block) {
1470
+ return this.#engine.hasStroke(block);
1471
+ }
1472
+ getStroke(block) {
1473
+ return this.#engine.getStroke(block);
1474
+ }
1475
+ setStroke(block, stroke) {
1476
+ this.#engine.setStroke(block, stroke);
1477
+ }
1478
+ getStrokeEnabled(block) {
1479
+ return this.#engine.getStrokeEnabled(block);
1480
+ }
1481
+ isStrokeEnabled(block) {
1482
+ return this.#engine.getStrokeEnabled(block);
1483
+ }
1484
+ setStrokeEnabled(block, enabled) {
1485
+ this.#engine.setStrokeEnabled(block, enabled);
1486
+ }
1487
+ getStrokeColor(block) {
1488
+ const internalColor = this.#engine.getStrokeColor(
1489
+ block
1490
+ );
1491
+ return NativeColorInternal.toColor(internalColor);
1492
+ }
1493
+ setStrokeColor(block, color) {
1494
+ const internalColor = NativeColorInternal.fromColor(color);
1495
+ this.#engine.setStrokeColor(block, internalColor);
1496
+ }
1497
+ getStrokeWidth(block) {
1498
+ return this.#engine.getStrokeWidth(block);
1499
+ }
1500
+ setStrokeWidth(block, width) {
1501
+ this.#engine.setStrokeWidth(block, width);
1502
+ }
1503
+ getStrokeStyle(block) {
1504
+ return this.#engine.getStrokeStyle(block);
1505
+ }
1506
+ setStrokeStyle(block, style) {
1507
+ this.#engine.setStrokeStyle(block, style);
1508
+ }
1509
+ getStrokePosition(block) {
1510
+ return this.#engine.getStrokePosition(block);
1511
+ }
1512
+ setStrokePosition(block, position) {
1513
+ this.#engine.setStrokePosition(block, position);
1514
+ }
1515
+ getStrokeCornerGeometry(block) {
1516
+ return this.#engine.getStrokeCornerGeometry(block);
1517
+ }
1518
+ setStrokeCornerGeometry(block, geometry) {
1519
+ this.#engine.setStrokeCornerGeometry(block, geometry);
1520
+ }
1521
+ // ─── Stroke caps + dash pattern (line shapes / dashed strokes) ──────────────
1522
+ getStrokeCap(block) {
1523
+ return this.#engine.getStrokeCap(block);
1524
+ }
1525
+ setStrokeCap(block, cap) {
1526
+ this.#engine.setStrokeCap(block, cap);
1527
+ }
1528
+ getStrokeStartCap(block) {
1529
+ return this.#engine.getStrokeStartCap(block);
1530
+ }
1531
+ setStrokeStartCap(block, cap) {
1532
+ this.#engine.setStrokeStartCap(block, cap);
1533
+ }
1534
+ getStrokeEndCap(block) {
1535
+ return this.#engine.getStrokeEndCap(block);
1536
+ }
1537
+ setStrokeEndCap(block, cap) {
1538
+ this.#engine.setStrokeEndCap(block, cap);
1539
+ }
1540
+ getStrokeDashStartCap(block) {
1541
+ return this.#engine.getStrokeDashStartCap(block);
1542
+ }
1543
+ setStrokeDashStartCap(block, cap) {
1544
+ this.#engine.setStrokeDashStartCap(block, cap);
1545
+ }
1546
+ getStrokeDashEndCap(block) {
1547
+ return this.#engine.getStrokeDashEndCap(block);
1548
+ }
1549
+ setStrokeDashEndCap(block, cap) {
1550
+ this.#engine.setStrokeDashEndCap(block, cap);
1551
+ }
1552
+ getStrokeDashArray(block) {
1553
+ return this.#engine.getStrokeDashArray(block);
1554
+ }
1555
+ setStrokeDashArray(block, dashArray) {
1556
+ this.#engine.setStrokeDashArray(block, dashArray);
1557
+ }
1558
+ getStrokeDashOffset(block) {
1559
+ return this.#engine.getStrokeDashOffset(block);
1560
+ }
1561
+ setStrokeDashOffset(block, dashOffset) {
1562
+ this.#engine.setStrokeDashOffset(block, dashOffset);
1563
+ }
1564
+ isLineOrigin(block) {
1565
+ return this.#engine.isLineOrigin(block);
1566
+ }
1567
+ getStrokeColorRGBA(block) {
1568
+ return this.#engine.getStrokeColorRGBA(block);
1569
+ }
1570
+ setStrokeColorRGBA(block, r, g, b, a) {
1571
+ this.#engine.setStrokeColorRGBA(block, r, g, b, a);
1572
+ }
1573
+ // Effects
1574
+ supportsEffects(block) {
1575
+ return this.#engine.supportsEffects(block);
1576
+ }
1577
+ hasEffects(block) {
1578
+ return this.#engine.hasEffects(block);
1579
+ }
1580
+ getEffects(block) {
1581
+ return this.#engine.getEffects(block);
1582
+ }
1583
+ appendEffect(block, effect) {
1584
+ this.#engine.appendEffect(block, effect);
1585
+ }
1586
+ insertEffect(block, effect, index) {
1587
+ this.#engine.insertEffect(block, effect, index);
1588
+ }
1589
+ removeEffect(block, index) {
1590
+ this.#engine.removeEffect(block, index);
1591
+ }
1592
+ isEffectEnabled(effect) {
1593
+ return this.#engine.isEffectEnabled(effect);
1594
+ }
1595
+ hasEffectEnabled(effect) {
1596
+ return this.isEffectEnabled(effect);
1597
+ }
1598
+ setEffectEnabled(effect, enabled) {
1599
+ this.#engine.setEffectEnabled(effect, enabled);
1600
+ }
1601
+ // Blur - WASM API uses isBlurEnabled
1602
+ supportsBlur(block) {
1603
+ return this.#engine.supportsBlur(block);
1604
+ }
1605
+ hasBlur(block) {
1606
+ return this.#engine.hasBlur(block);
1607
+ }
1608
+ getBlur(block) {
1609
+ return this.#engine.getBlur(block);
1610
+ }
1611
+ setBlur(block, blur) {
1612
+ this.#engine.setBlur(block, blur);
1613
+ }
1614
+ getBlurEnabled(block) {
1615
+ return this.#engine.getBlurEnabled(block);
1616
+ }
1617
+ isBlurEnabled(block) {
1618
+ return this.#engine.getBlurEnabled(block);
1619
+ }
1620
+ setBlurEnabled(block, enabled) {
1621
+ this.#engine.setBlurEnabled(block, enabled);
1622
+ }
1623
+ // Shape
1624
+ supportsShape(block) {
1625
+ return this.#engine.supportsShape(block);
1626
+ }
1627
+ getShape(block) {
1628
+ return this.#engine.getShape(block);
1629
+ }
1630
+ setShape(block, shape) {
1631
+ this.#engine.setShape(block, shape);
1632
+ }
1633
+ hasShape(block) {
1634
+ return this.supportsShape(block);
1635
+ }
1636
+ // Animation
1637
+ supportsAnimation(block) {
1638
+ return this.#engine.supportsAnimation(block);
1639
+ }
1640
+ getInAnimation(block) {
1641
+ return this.#engine.getInAnimation(block);
1642
+ }
1643
+ setInAnimation(block, animation) {
1644
+ this.#engine.setInAnimation(block, animation);
1645
+ }
1646
+ getOutAnimation(block) {
1647
+ return this.#engine.getOutAnimation(block);
1648
+ }
1649
+ setOutAnimation(block, animation) {
1650
+ this.#engine.setOutAnimation(block, animation);
1651
+ }
1652
+ getLoopAnimation(block) {
1653
+ return this.#engine.getLoopAnimation(block);
1654
+ }
1655
+ setLoopAnimation(block, animation) {
1656
+ this.#engine.setLoopAnimation(block, animation);
1657
+ }
1658
+ // Content fill mode - WASM also has hasContentFillMode
1659
+ supportsContentFillMode(block) {
1660
+ return this.#engine.supportsContentFillMode(block);
1661
+ }
1662
+ hasContentFillMode(block) {
1663
+ return this.supportsContentFillMode(block);
1664
+ }
1665
+ getContentFillMode(block) {
1666
+ return this.#engine.getContentFillMode(block);
1667
+ }
1668
+ setContentFillMode(block, mode) {
1669
+ this.#engine.setContentFillMode(block, mode);
1670
+ }
1671
+ // Content-fill alignment — applies to `Contain` / `Cover` fill modes.
1672
+ // Has no visible effect in `Crop`.
1673
+ setContentFillHorizontalAlignment(block, alignment) {
1674
+ this.#engine.setContentFillHorizontalAlignment(block, alignment);
1675
+ }
1676
+ getContentFillHorizontalAlignment(block) {
1677
+ return this.#engine.getContentFillHorizontalAlignment(
1678
+ block
1679
+ );
1680
+ }
1681
+ setContentFillVerticalAlignment(block, alignment) {
1682
+ this.#engine.setContentFillVerticalAlignment(block, alignment);
1683
+ }
1684
+ getContentFillVerticalAlignment(block) {
1685
+ return this.#engine.getContentFillVerticalAlignment(
1686
+ block
1687
+ );
1688
+ }
1689
+ // Crop - WASM also has hasCrop
1690
+ supportsCrop(block) {
1691
+ return this.#engine.supportsCrop(block);
1692
+ }
1693
+ hasCrop(block) {
1694
+ return this.supportsCrop(block);
1695
+ }
1696
+ resetCrop(block) {
1697
+ this.#engine.resetCrop(block);
1698
+ }
1699
+ // Match @cesdk/node — accept the `minScaleRatio` argument. The C++ binding
1700
+ // already forwards it (defaults to 1.0 when omitted, see
1701
+ // engine_binding_impl/appearance.cpp). The engine returns void; the WASM
1702
+ // wrapper's `: number` declaration is a typing artefact (it forwards an
1703
+ // `unpackResult` of a `Result<void>`, which is `undefined` at runtime), so
1704
+ // native stays honest with `: void`.
1705
+ adjustCropToFillFrame(block, minScaleRatio) {
1706
+ this.#engine.adjustCropToFillFrame(block, minScaleRatio);
1707
+ }
1708
+ getCropRotation(block) {
1709
+ return this.#engine.getCropRotation(block);
1710
+ }
1711
+ setCropRotation(block, rotation) {
1712
+ this.#engine.setCropRotation(block, rotation);
1713
+ }
1714
+ getCropScaleX(block) {
1715
+ return this.#engine.getCropScaleX(block);
1716
+ }
1717
+ setCropScaleX(block, scale) {
1718
+ this.#engine.setCropScaleX(block, scale);
1719
+ }
1720
+ getCropScaleY(block) {
1721
+ return this.#engine.getCropScaleY(block);
1722
+ }
1723
+ setCropScaleY(block, scale) {
1724
+ this.#engine.setCropScaleY(block, scale);
1725
+ }
1726
+ getCropScaleRatio(block) {
1727
+ return this.#engine.getCropScaleRatio(block);
1728
+ }
1729
+ setCropScaleRatio(block, ratio) {
1730
+ this.#engine.setCropScaleRatio(block, ratio);
1731
+ }
1732
+ getCropTranslationX(block) {
1733
+ return this.#engine.getCropTranslationX(block);
1734
+ }
1735
+ setCropTranslationX(block, translation) {
1736
+ this.#engine.setCropTranslationX(block, translation);
1737
+ }
1738
+ getCropTranslationY(block) {
1739
+ return this.#engine.getCropTranslationY(block);
1740
+ }
1741
+ setCropTranslationY(block, translation) {
1742
+ this.#engine.setCropTranslationY(block, translation);
1743
+ }
1744
+ isCropAspectRatioLocked(block) {
1745
+ return this.#engine.isCropAspectRatioLocked(block);
1746
+ }
1747
+ setCropAspectRatioLocked(block, locked) {
1748
+ this.#engine.setCropAspectRatioLocked(block, locked);
1749
+ }
1750
+ flipCropHorizontal(block) {
1751
+ this.#engine.flipCropHorizontal(block);
1752
+ }
1753
+ flipCropVertical(block) {
1754
+ this.#engine.flipCropVertical(block);
1755
+ }
1756
+ // Drop shadow
1757
+ supportsDropShadow(block) {
1758
+ return this.#engine.supportsDropShadow(block);
1759
+ }
1760
+ hasDropShadow(block) {
1761
+ return this.supportsDropShadow(block);
1762
+ }
1763
+ isDropShadowEnabled(block) {
1764
+ return this.#engine.isDropShadowEnabled(block);
1765
+ }
1766
+ setDropShadowEnabled(block, enabled) {
1767
+ this.#engine.setDropShadowEnabled(block, enabled);
1768
+ }
1769
+ getDropShadowColor(block) {
1770
+ const internalColor = this.#engine.getDropShadowColor(
1771
+ block
1772
+ );
1773
+ return NativeColorInternal.toColor(internalColor);
1774
+ }
1775
+ setDropShadowColor(block, color) {
1776
+ const internalColor = NativeColorInternal.fromColor(color);
1777
+ this.#engine.setDropShadowColor(block, internalColor);
1778
+ }
1779
+ getDropShadowColorRGBA(block) {
1780
+ return this.#engine.getDropShadowColorRGBA(block);
1781
+ }
1782
+ setDropShadowColorRGBA(block, r, g, b, a) {
1783
+ this.#engine.setDropShadowColorRGBA(block, r, g, b, a);
1784
+ }
1785
+ getDropShadowOffsetX(block) {
1786
+ return this.#engine.getDropShadowOffsetX(block);
1787
+ }
1788
+ setDropShadowOffsetX(block, offset) {
1789
+ this.#engine.setDropShadowOffsetX(block, offset);
1790
+ }
1791
+ getDropShadowOffsetY(block) {
1792
+ return this.#engine.getDropShadowOffsetY(block);
1793
+ }
1794
+ setDropShadowOffsetY(block, offset) {
1795
+ this.#engine.setDropShadowOffsetY(block, offset);
1796
+ }
1797
+ getDropShadowBlurRadiusX(block) {
1798
+ return this.#engine.getDropShadowBlurRadiusX(block);
1799
+ }
1800
+ setDropShadowBlurRadiusX(block, radius) {
1801
+ this.#engine.setDropShadowBlurRadiusX(block, radius);
1802
+ }
1803
+ getDropShadowBlurRadiusY(block) {
1804
+ return this.#engine.getDropShadowBlurRadiusY(block);
1805
+ }
1806
+ setDropShadowBlurRadiusY(block, radius) {
1807
+ this.#engine.setDropShadowBlurRadiusY(block, radius);
1808
+ }
1809
+ getDropShadowClip(block) {
1810
+ return this.#engine.getDropShadowClip(block);
1811
+ }
1812
+ setDropShadowClip(block, clip) {
1813
+ this.#engine.setDropShadowClip(block, clip);
1814
+ }
1815
+ // Blend mode
1816
+ supportsBlendMode(block) {
1817
+ return this.#engine.supportsBlendMode(block);
1818
+ }
1819
+ getBlendMode(block) {
1820
+ return this.#engine.getBlendMode(block);
1821
+ }
1822
+ setBlendMode(block, mode) {
1823
+ this.#engine.setBlendMode(block, mode);
1824
+ }
1825
+ hasBlendMode(block) {
1826
+ return this.supportsBlendMode(block);
1827
+ }
1828
+ // Opacity
1829
+ supportsOpacity(block) {
1830
+ return this.#engine.supportsOpacity(block);
1831
+ }
1832
+ hasOpacity(block) {
1833
+ return this.supportsOpacity(block);
1834
+ }
1835
+ getOpacity(block) {
1836
+ return this.#engine.getOpacity(block);
1837
+ }
1838
+ setOpacity(block, opacity) {
1839
+ this.#engine.setOpacity(block, opacity);
1840
+ }
1841
+ // Visibility
1842
+ getVisible(block) {
1843
+ return this.#engine.getVisible(block);
1844
+ }
1845
+ isVisible(block) {
1846
+ return this.#engine.getVisible(block);
1847
+ }
1848
+ setVisible(block, visible) {
1849
+ this.#engine.setVisible(block, visible);
1850
+ }
1851
+ getClipped(block) {
1852
+ return this.#engine.getClipped(block);
1853
+ }
1854
+ isClipped(block) {
1855
+ return this.#engine.getClipped(block);
1856
+ }
1857
+ setClipped(block, clipped) {
1858
+ this.#engine.setClipped(block, clipped);
1859
+ }
1860
+ // Selection
1861
+ setSelected(block, selected) {
1862
+ this.#engine.setSelected(block, selected);
1863
+ }
1864
+ isSelected(block) {
1865
+ return this.#engine.isSelected(block);
1866
+ }
1867
+ select(block) {
1868
+ this.#engine.select(block);
1869
+ }
1870
+ // Ordering
1871
+ bringToFront(block) {
1872
+ this.#engine.bringToFront(block);
1873
+ }
1874
+ sendToBack(block) {
1875
+ this.#engine.sendToBack(block);
1876
+ }
1877
+ bringForward(block) {
1878
+ this.#engine.bringForward(block);
1879
+ }
1880
+ sendBackward(block) {
1881
+ this.#engine.sendBackward(block);
1882
+ }
1883
+ setAlwaysOnTop(block, enabled) {
1884
+ this.#engine.setAlwaysOnTop(block, enabled);
1885
+ }
1886
+ setAlwaysOnBottom(block, enabled) {
1887
+ this.#engine.setAlwaysOnBottom(block, enabled);
1888
+ }
1889
+ isAlwaysOnTop(block) {
1890
+ return this.#engine.isAlwaysOnTop(block);
1891
+ }
1892
+ isAlwaysOnBottom(block) {
1893
+ return this.#engine.isAlwaysOnBottom(block);
1894
+ }
1895
+ // Alignment and distribution
1896
+ alignHorizontally(blocks, alignment) {
1897
+ this.#engine.alignHorizontally(blocks, alignment);
1898
+ }
1899
+ alignVertically(blocks, alignment) {
1900
+ this.#engine.alignVertically(blocks, alignment);
1901
+ }
1902
+ isAlignable(blocks) {
1903
+ return this.#engine.isAlignable(blocks);
1904
+ }
1905
+ distributeHorizontally(blocks) {
1906
+ this.#engine.distributeHorizontally(blocks);
1907
+ }
1908
+ distributeVertically(blocks) {
1909
+ this.#engine.distributeVertically(blocks);
1910
+ }
1911
+ isDistributable(blocks) {
1912
+ return this.#engine.isDistributable(blocks);
1913
+ }
1914
+ // Grouping
1915
+ group(blocks) {
1916
+ return this.#engine.group(blocks);
1917
+ }
1918
+ ungroup(group) {
1919
+ this.#engine.ungroup(group);
1920
+ }
1921
+ isGroupable(blocks) {
1922
+ return this.#engine.isGroupable(blocks);
1923
+ }
1924
+ enterGroup(group) {
1925
+ this.#engine.enterGroup(group);
1926
+ }
1927
+ exitGroup(block) {
1928
+ this.#engine.exitGroup(block);
1929
+ }
1930
+ // Combine
1931
+ combine(blocks, operation) {
1932
+ const validOps = ["Union", "Difference", "Intersection", "XOR"];
1933
+ if (!validOps.includes(operation)) {
1934
+ throw new Error(`Unknown boolean operation: ${operation}`);
1935
+ }
1936
+ return this.#engine.combine(blocks, operation);
1937
+ }
1938
+ isCombinable(blocks) {
1939
+ return this.#engine.isCombinable(blocks);
1940
+ }
1941
+ // Duration/Timeline
1942
+ supportsDuration(block) {
1943
+ return this.#engine.supportsDuration(block);
1944
+ }
1945
+ hasDuration(block) {
1946
+ return this.supportsDuration(block);
1947
+ }
1948
+ getDuration(block) {
1949
+ return this.#engine.getDuration(block);
1950
+ }
1951
+ setDuration(block, duration) {
1952
+ this.#engine.setDuration(block, duration);
1953
+ }
1954
+ supportsTimeOffset(block) {
1955
+ return this.#engine.supportsTimeOffset(block);
1956
+ }
1957
+ hasTimeOffset(block) {
1958
+ return this.supportsTimeOffset(block);
1959
+ }
1960
+ getTimeOffset(block) {
1961
+ return this.#engine.getTimeOffset(block);
1962
+ }
1963
+ setTimeOffset(block, offset) {
1964
+ this.#engine.setTimeOffset(block, offset);
1965
+ }
1966
+ supportsTrim(block) {
1967
+ return this.#engine.supportsTrim(block);
1968
+ }
1969
+ hasTrim(block) {
1970
+ return this.supportsTrim(block);
1971
+ }
1972
+ getTrimOffset(block) {
1973
+ return this.#engine.getTrimOffset(block);
1974
+ }
1975
+ setTrimOffset(block, offset) {
1976
+ this.#engine.setTrimOffset(block, offset);
1977
+ }
1978
+ getTrimLength(block) {
1979
+ return this.#engine.getTrimLength(block);
1980
+ }
1981
+ setTrimLength(block, length) {
1982
+ this.#engine.setTrimLength(block, length);
1983
+ }
1984
+ // Playback
1985
+ supportsPlaybackControl(block) {
1986
+ return this.#engine.supportsPlaybackControl(block);
1987
+ }
1988
+ hasPlaybackControl(block) {
1989
+ return this.supportsPlaybackControl(block);
1990
+ }
1991
+ supportsPlaybackTime(block) {
1992
+ return this.#engine.supportsPlaybackTime(block);
1993
+ }
1994
+ hasPlaybackTime(block) {
1995
+ return this.supportsPlaybackTime(block);
1996
+ }
1997
+ getPlaybackTime(block) {
1998
+ return this.#engine.getPlaybackTime(block);
1999
+ }
2000
+ setPlaybackTime(block, time) {
2001
+ this.#engine.setPlaybackTime(block, time);
2002
+ }
2003
+ isPlaying(block) {
2004
+ return this.#engine.isPlaying(block);
2005
+ }
2006
+ setPlaying(block, playing) {
2007
+ this.#engine.setPlaying(block, playing);
2008
+ }
2009
+ isLooping(block) {
2010
+ return this.#engine.isLooping(block);
2011
+ }
2012
+ setLooping(block, looping) {
2013
+ this.#engine.setLooping(block, looping);
2014
+ }
2015
+ getPlaybackSpeed(block) {
2016
+ return this.#engine.getPlaybackSpeed(block);
2017
+ }
2018
+ setPlaybackSpeed(block, speed) {
2019
+ this.#engine.setPlaybackSpeed(block, speed);
2020
+ }
2021
+ isMuted(block) {
2022
+ return this.#engine.isMuted(block);
2023
+ }
2024
+ setMuted(block, muted) {
2025
+ this.#engine.setMuted(block, muted);
2026
+ }
2027
+ getVolume(block) {
2028
+ return this.#engine.getVolume(block);
2029
+ }
2030
+ setVolume(block, volume) {
2031
+ this.#engine.setVolume(block, volume);
2032
+ }
2033
+ isSoloPlaybackEnabled(block) {
2034
+ return this.#engine.isSoloPlaybackEnabled(block);
2035
+ }
2036
+ setSoloPlaybackEnabled(block, enabled) {
2037
+ this.#engine.setSoloPlaybackEnabled(block, enabled);
2038
+ }
2039
+ isForceMuted(block) {
2040
+ return this.#engine.isForceMuted(block);
2041
+ }
2042
+ getTotalSceneDuration(scene) {
2043
+ return this.#engine.getTotalSceneDuration(scene);
2044
+ }
2045
+ isVisibleAtCurrentPlaybackTime(block) {
2046
+ return this.#engine.isVisibleAtCurrentPlaybackTime(block);
2047
+ }
2048
+ // Locking
2049
+ isTransformLocked(block) {
2050
+ return this.#engine.isTransformLocked(block);
2051
+ }
2052
+ setTransformLocked(block, locked) {
2053
+ this.#engine.setTransformLocked(block, locked);
2054
+ }
2055
+ // Include in export
2056
+ isIncludedInExport(block) {
2057
+ return this.#engine.isIncludedInExport(block);
2058
+ }
2059
+ setIncludedInExport(block, included) {
2060
+ this.#engine.setIncludedInExport(block, included);
2061
+ }
2062
+ // Placeholder
2063
+ isPlaceholderEnabled(block) {
2064
+ return this.#engine.isPlaceholderEnabled(block);
2065
+ }
2066
+ setPlaceholderEnabled(block, enabled) {
2067
+ this.#engine.setPlaceholderEnabled(block, enabled);
2068
+ }
2069
+ supportsPlaceholderBehavior(block) {
2070
+ return this.#engine.supportsPlaceholderBehavior(block);
2071
+ }
2072
+ hasPlaceholderBehavior(block) {
2073
+ return this.supportsPlaceholderBehavior(block);
2074
+ }
2075
+ isPlaceholderBehaviorEnabled(block) {
2076
+ return this.#engine.isPlaceholderBehaviorEnabled(block);
2077
+ }
2078
+ setPlaceholderBehaviorEnabled(block, enabled) {
2079
+ this.#engine.setPlaceholderBehaviorEnabled(block, enabled);
2080
+ }
2081
+ supportsPlaceholderControls(block) {
2082
+ return this.#engine.supportsPlaceholderControls(block);
2083
+ }
2084
+ hasPlaceholderControls(block) {
2085
+ return this.supportsPlaceholderControls(block);
2086
+ }
2087
+ isPlaceholderControlsOverlayEnabled(block) {
2088
+ return this.#engine.isPlaceholderControlsOverlayEnabled(block);
2089
+ }
2090
+ setPlaceholderControlsOverlayEnabled(block, enabled) {
2091
+ this.#engine.setPlaceholderControlsOverlayEnabled(block, enabled);
2092
+ }
2093
+ isPlaceholderControlsButtonEnabled(block) {
2094
+ return this.#engine.isPlaceholderControlsButtonEnabled(block);
2095
+ }
2096
+ setPlaceholderControlsButtonEnabled(block, enabled) {
2097
+ this.#engine.setPlaceholderControlsButtonEnabled(block, enabled);
2098
+ }
2099
+ // Background color - WASM also has hasBackgroundColor and RGBA methods
2100
+ supportsBackgroundColor(block) {
2101
+ return this.#engine.supportsBackgroundColor(block);
2102
+ }
2103
+ hasBackgroundColor(block) {
2104
+ return this.supportsBackgroundColor(block);
2105
+ }
2106
+ isBackgroundColorEnabled(block) {
2107
+ return this.#engine.isBackgroundColorEnabled(block);
2108
+ }
2109
+ setBackgroundColorEnabled(block, enabled) {
2110
+ this.#engine.setBackgroundColorEnabled(block, enabled);
2111
+ }
2112
+ getBackgroundColorRGBA(block) {
2113
+ const internalColor = this.#engine.getColor(
2114
+ block,
2115
+ "backgroundColor"
2116
+ );
2117
+ const color = NativeColorInternal.toColor(internalColor);
2118
+ if (!isRGBAColor2(color)) {
2119
+ throw new Error("Expected RGBA color for background color");
2120
+ }
2121
+ return { r: color.r, g: color.g, b: color.b, a: color.a };
2122
+ }
2123
+ setBackgroundColorRGBA(block, r, g, b, a) {
2124
+ this.setColor(block, "backgroundColor", { r, g, b, a });
2125
+ }
2126
+ // Metadata
2127
+ hasMetadata(block, key) {
2128
+ return this.#engine.hasMetadata(block, key);
2129
+ }
2130
+ getMetadata(block, key) {
2131
+ return this.#engine.getMetadata(block, key);
2132
+ }
2133
+ setMetadata(block, key, value) {
2134
+ this.#engine.setMetadata(block, key, value);
2135
+ }
2136
+ removeMetadata(block, key) {
2137
+ this.#engine.removeMetadata(block, key);
2138
+ }
2139
+ findAllMetadata(block) {
2140
+ return this.#engine.findAllMetadata(block);
2141
+ }
2142
+ // ==========================================================================
2143
+ // Text — range-aware methods accept UTF-16 indices (`from`/`to`) like WASM.
2144
+ // The wrapper looks up the current text and converts to grapheme indices via
2145
+ // `convertUTF16ToGraphemeRange` before forwarding to the engine.
2146
+ // ==========================================================================
2147
+ /**
2148
+ * Resolve UTF-16 `from`/`to` to engine grapheme range. Returns the original
2149
+ * range when both ends are negative ("fall back to current selection / full
2150
+ * text"), so we avoid the engine round-trip when the caller didn't ask for
2151
+ * a sub-range.
2152
+ */
2153
+ #toGraphemeRange(block, from, to) {
2154
+ if (from < 0 && to < 0) return { from, to };
2155
+ let text = "";
2156
+ try {
2157
+ text = this.#engine.getString(block, "text/text");
2158
+ } catch {
2159
+ return { from, to };
2160
+ }
2161
+ return convertUTF16ToGraphemeRange(text, from, to);
2162
+ }
2163
+ replaceText(block, text, from = -1, to = -1) {
2164
+ const range = this.#toGraphemeRange(block, from, to);
2165
+ this.#engine.replaceText(block, text, range.from, range.to);
2166
+ }
2167
+ removeText(block, from = -1, to = -1) {
2168
+ const range = this.#toGraphemeRange(block, from, to);
2169
+ this.#engine.removeText(block, range.from, range.to);
2170
+ }
2171
+ getTextFontSizes(block, fromOrOptions, to = -1) {
2172
+ let from = -1;
2173
+ let optTo = to;
2174
+ let unit = "Point";
2175
+ if (typeof fromOrOptions === "object" && fromOrOptions !== null) {
2176
+ from = fromOrOptions.from ?? -1;
2177
+ optTo = fromOrOptions.to ?? -1;
2178
+ unit = fromOrOptions.unit ?? "Point";
2179
+ } else if (typeof fromOrOptions === "number") {
2180
+ from = fromOrOptions;
2181
+ }
2182
+ const range = this.#toGraphemeRange(block, from, optTo);
2183
+ const sizesInPoints = this.#engine.getTextFontSizes(
2184
+ block,
2185
+ range.from,
2186
+ range.to
2187
+ );
2188
+ if (unit === "Point") return sizesInPoints;
2189
+ const scenes = this.findByType("//ly.img.ubq/scene");
2190
+ if (scenes.length === 0) {
2191
+ throw new Error("No scene found for unit conversion");
2192
+ }
2193
+ const dpi = this.getFloat(scenes[0], "scene/dpi");
2194
+ return sizesInPoints.map((s) => convertFontSizeFromPoints(s, unit, dpi));
2195
+ }
2196
+ setTextFontSize(block, size, fromOrOptions, to = -1) {
2197
+ let fontSizeInPoints = size;
2198
+ let from = -1;
2199
+ let optTo = to;
2200
+ if (typeof fromOrOptions === "object" && fromOrOptions !== null) {
2201
+ const {
2202
+ unit = "Point",
2203
+ from: optFrom = -1,
2204
+ to: optionsTo = -1
2205
+ } = fromOrOptions;
2206
+ from = optFrom;
2207
+ optTo = optionsTo;
2208
+ if (unit !== "Point") {
2209
+ const scenes = this.findByType("//ly.img.ubq/scene");
2210
+ if (scenes.length === 0) {
2211
+ throw new Error("No scene found for unit conversion");
2212
+ }
2213
+ const dpi = this.getFloat(scenes[0], "scene/dpi");
2214
+ fontSizeInPoints = convertFontSizeToPoints(size, unit, dpi);
2215
+ }
2216
+ } else if (typeof fromOrOptions === "number") {
2217
+ from = fromOrOptions;
2218
+ }
2219
+ const range = this.#toGraphemeRange(block, from, optTo);
2220
+ this.#engine.setTextFontSize(block, fontSizeInPoints, range.from, range.to);
2221
+ }
2222
+ getTextFontStyles(block, from = -1, to = -1) {
2223
+ const range = this.#toGraphemeRange(block, from, to);
2224
+ return this.#engine.getTextFontStyles(block, range.from, range.to);
2225
+ }
2226
+ setTextFontStyle(block, style, from = -1, to = -1) {
2227
+ const range = this.#toGraphemeRange(block, from, to);
2228
+ this.#engine.setTextFontStyle(block, style, range.from, range.to);
2229
+ }
2230
+ getTextFontWeights(block, from = -1, to = -1) {
2231
+ const range = this.#toGraphemeRange(block, from, to);
2232
+ return this.#engine.getTextFontWeights(block, range.from, range.to);
2233
+ }
2234
+ setTextFontWeight(block, weight, from = -1, to = -1) {
2235
+ const range = this.#toGraphemeRange(block, from, to);
2236
+ this.#engine.setTextFontWeight(block, weight, range.from, range.to);
2237
+ }
2238
+ getTextColors(block, from = -1, to = -1) {
2239
+ const range = this.#toGraphemeRange(block, from, to);
2240
+ const internalColors = this.#engine.getTextColors(
2241
+ block,
2242
+ range.from,
2243
+ range.to
2244
+ );
2245
+ return internalColors.map((c) => NativeColorInternal.toColor(c));
2246
+ }
2247
+ setTextColor(block, color, from = -1, to = -1) {
2248
+ const internalColor = NativeColorInternal.fromColor(color);
2249
+ const range = this.#toGraphemeRange(block, from, to);
2250
+ this.#engine.setTextColor(block, internalColor, range.from, range.to);
2251
+ }
2252
+ getTextCases(block, from = -1, to = -1) {
2253
+ const range = this.#toGraphemeRange(block, from, to);
2254
+ return this.#engine.getTextCases(block, range.from, range.to);
2255
+ }
2256
+ setTextCase(block, textCase, from = -1, to = -1) {
2257
+ const range = this.#toGraphemeRange(block, from, to);
2258
+ this.#engine.setTextCase(block, textCase, range.from, range.to);
2259
+ }
2260
+ getTypefaces(block, from, to) {
2261
+ const range = this.#toGraphemeRange(block, from ?? -1, to ?? -1);
2262
+ return this.#engine.getTypefaces(block, range.from, range.to);
2263
+ }
2264
+ setTypeface(block, typeface, from = -1, to = -1) {
2265
+ const range = this.#toGraphemeRange(block, from, to);
2266
+ this.#engine.setTypeface(block, typeface, range.from, range.to);
2267
+ }
2268
+ getTypeface(block) {
2269
+ return this.#engine.getTypeface(block);
2270
+ }
2271
+ /**
2272
+ * Set extra character-level kerning offset for a grapheme range. The
2273
+ * value is in em units (1.0 = one full em); 0.0 resets to no extra
2274
+ * offset. Matches the node wasm binding's `BlockAPI.setTextKerning`.
2275
+ */
2276
+ setTextKerning(block, kerning, from = -1, to = -1) {
2277
+ const range = this.#toGraphemeRange(block, from, to);
2278
+ this.#engine.setTextKerning(block, kerning, range.from, range.to);
2279
+ }
2280
+ /**
2281
+ * Return the unique kerning values seen across the grapheme range.
2282
+ * Mirrors `getTextFontSizes` / `getTextFontStyles` — `[0]` for a clean
2283
+ * block, `[v1, v2, …]` when the range spans multiple distinct values.
2284
+ */
2285
+ getTextKernings(block, from, to) {
2286
+ const range = this.#toGraphemeRange(block, from ?? -1, to ?? -1);
2287
+ return this.#engine.getTextKernings(block, range.from, range.to);
2288
+ }
2289
+ setFont(block, fontUri, typeface) {
2290
+ this.#engine.setFont(block, fontUri, typeface);
2291
+ }
2292
+ canToggleBoldFont(block) {
2293
+ return this.#engine.canToggleBoldFont(block);
2294
+ }
2295
+ toggleBoldFont(block) {
2296
+ this.#engine.toggleBoldFont(block);
2297
+ }
2298
+ canToggleItalicFont(block) {
2299
+ return this.#engine.canToggleItalicFont(block);
2300
+ }
2301
+ toggleItalicFont(block) {
2302
+ this.#engine.toggleItalicFont(block);
2303
+ }
2304
+ /**
2305
+ * The text block currently being edited, or `undefined` when not in Text
2306
+ * edit mode. Used to convert between UTF-16 and grapheme indices when
2307
+ * exposing cursor ranges.
2308
+ */
2309
+ #findActiveTextBlock() {
2310
+ try {
2311
+ const editMode = this.#engine.getEditMode();
2312
+ if (editMode !== "Text") return void 0;
2313
+ } catch {
2314
+ return void 0;
2315
+ }
2316
+ const selected = this.#engine.findAllSelected();
2317
+ for (const block of selected) {
2318
+ try {
2319
+ if (this.#engine.getType(block) === "//ly.img.ubq/text") {
2320
+ return block;
2321
+ }
2322
+ } catch {
2323
+ continue;
2324
+ }
2325
+ }
2326
+ return void 0;
2327
+ }
2328
+ /**
2329
+ * Returns the current text cursor range as UTF-16 indices. When no block
2330
+ * is currently being edited, returns `{ from: -1, to: -1 }`.
2331
+ */
2332
+ getTextCursorRange() {
2333
+ const grapheme = this.#engine.getTextCursorRange();
2334
+ if (grapheme.from < 0 && grapheme.to < 0) return grapheme;
2335
+ const block = this.#findActiveTextBlock();
2336
+ if (block == null) return grapheme;
2337
+ let text = "";
2338
+ try {
2339
+ text = this.#engine.getString(block, "text/text");
2340
+ } catch {
2341
+ return grapheme;
2342
+ }
2343
+ return convertGraphemeToUTF16Range(text, grapheme.from, grapheme.to);
2344
+ }
2345
+ /**
2346
+ * Sets the text cursor range. `from`/`to` are UTF-16 indices. Negative
2347
+ * values mean "from start" / "to end" (so `{ from: -1, to: -1 }` selects
2348
+ * the entire text).
2349
+ */
2350
+ setTextCursorRange(range) {
2351
+ const block = this.#findActiveTextBlock();
2352
+ if (block == null) {
2353
+ throw new Error("No text block is currently being edited.");
2354
+ }
2355
+ let { from, to } = range;
2356
+ if (from >= 0 || to >= 0) {
2357
+ const text = this.#engine.getString(block, "text/text");
2358
+ ({ from, to } = convertUTF16ToGraphemeRange(text, from, to));
2359
+ }
2360
+ this.#engine.setTextCursorRange(from, to);
2361
+ }
2362
+ getTextCursorPositionInScreenSpaceX(block) {
2363
+ return this.#engine.getTextCursorPositionInScreenSpaceX(block);
2364
+ }
2365
+ getTextCursorPositionInScreenSpaceY(block) {
2366
+ return this.#engine.getTextCursorPositionInScreenSpaceY(block);
2367
+ }
2368
+ getTextVisibleLineCount(block) {
2369
+ return this.#engine.getTextVisibleLineCount(block);
2370
+ }
2371
+ getTextVisibleLineContent(block, lineIndex) {
2372
+ return this.#engine.getTextVisibleLineContent(block, lineIndex);
2373
+ }
2374
+ getTextVisibleLineGlobalBoundingBoxXYWH(block, lineIndex) {
2375
+ return this.#engine.getTextVisibleLineGlobalBoundingBoxXYWH(
2376
+ block,
2377
+ lineIndex
2378
+ );
2379
+ }
2380
+ /**
2381
+ * Returns the tight ink-paint bounding box for each grapheme in the
2382
+ * range. One entry per grapheme in [from, to). Non-printable graphemes
2383
+ * (e.g. newline) get a zero-rect entry. Coordinates are in global
2384
+ * scene space.
2385
+ *
2386
+ * @param block - The text block to query.
2387
+ * @param from - Start UTF-16 index (-1 = start of cursor selection or 0).
2388
+ * @param to - End UTF-16 index (-1 = end of cursor selection or text length).
2389
+ */
2390
+ getTextCharacterInkBoxes(block, from = -1, to = -1) {
2391
+ const range = this.#toGraphemeRange(block, from, to);
2392
+ return this.#engine.getTextCharacterInkBoxes(block, range.from, range.to);
2393
+ }
2394
+ getTextEffectiveHorizontalAlignment(block) {
2395
+ return this.#engine.getTextEffectiveHorizontalAlignment(block);
2396
+ }
2397
+ /**
2398
+ * Returns the horizontal alignment override for a specific paragraph, or
2399
+ * the block-level alignment for negative paragraph indices. Returns
2400
+ * `undefined` when no override is set for the given paragraph.
2401
+ * @param block - The text block to query.
2402
+ * @param paragraphIndex - 0-based paragraph index. Negative values return
2403
+ * the block-level `text/horizontalAlignment` setting.
2404
+ */
2405
+ getTextHorizontalAlignment(block, paragraphIndex = -1) {
2406
+ return this.#engine.getTextHorizontalAlignment(block, paragraphIndex);
2407
+ }
2408
+ /**
2409
+ * Sets the horizontal alignment override for a specific paragraph of a text
2410
+ * block. Pass `undefined` for `alignment` to clear the paragraph-level
2411
+ * override. Negative `paragraphIndex` clears all paragraph overrides and
2412
+ * applies the alignment block-wide.
2413
+ */
2414
+ setTextHorizontalAlignment(block, alignment, paragraphIndex = -1) {
2415
+ this.#engine.setTextHorizontalAlignment(block, alignment, paragraphIndex);
2416
+ }
2417
+ /**
2418
+ * Returns the ordered list of unique text-decoration configurations within
2419
+ * the given grapheme range. `from` / `to` are UTF-16 indices and are
2420
+ * converted to grapheme indices to match WASM semantics.
2421
+ */
2422
+ getTextDecorations(block, from = -1, to = -1) {
2423
+ const range = this.#toGraphemeRange(block, from, to);
2424
+ const raw = this.#engine.getTextDecorations(
2425
+ block,
2426
+ range.from,
2427
+ range.to
2428
+ );
2429
+ return raw.map((c) => {
2430
+ const result = { lines: c.lines ?? [] };
2431
+ if (c.style != null) result.style = c.style;
2432
+ if (c.underlineColor != null)
2433
+ result.underlineColor = NativeColorInternal.toColor(c.underlineColor);
2434
+ if (c.underlineThickness != null)
2435
+ result.underlineThickness = c.underlineThickness;
2436
+ if (c.underlineOffset != null) result.underlineOffset = c.underlineOffset;
2437
+ if (c.skipInk != null) result.skipInk = c.skipInk;
2438
+ return result;
2439
+ });
2440
+ }
2441
+ /**
2442
+ * Sets the given text-decoration config for the given grapheme range. Set
2443
+ * `lines: ['None']` to clear all decorations.
2444
+ */
2445
+ setTextDecoration(block, config, from = -1, to = -1) {
2446
+ const range = this.#toGraphemeRange(block, from, to);
2447
+ const internal = {
2448
+ lines: config.lines,
2449
+ style: config.style,
2450
+ underlineColor: config.underlineColor != null ? NativeColorInternal.fromColor(config.underlineColor) : void 0,
2451
+ underlineThickness: config.underlineThickness,
2452
+ underlineOffset: config.underlineOffset,
2453
+ skipInk: config.skipInk
2454
+ };
2455
+ this.#engine.setTextDecoration(block, internal, range.from, range.to);
2456
+ }
2457
+ toggleTextDecorationUnderline(block, from = -1, to = -1) {
2458
+ const range = this.#toGraphemeRange(block, from, to);
2459
+ this.#engine.toggleTextDecorationUnderline(block, range.from, range.to);
2460
+ }
2461
+ toggleTextDecorationStrikethrough(block, from = -1, to = -1) {
2462
+ const range = this.#toGraphemeRange(block, from, to);
2463
+ this.#engine.toggleTextDecorationStrikethrough(block, range.from, range.to);
2464
+ }
2465
+ toggleTextDecorationOverline(block, from = -1, to = -1) {
2466
+ const range = this.#toGraphemeRange(block, from, to);
2467
+ this.#engine.toggleTextDecorationOverline(block, range.from, range.to);
2468
+ }
2469
+ // Text lists
2470
+ getTextListStyle(block, paragraphIndex) {
2471
+ return this.#engine.getTextListStyle(block, paragraphIndex);
2472
+ }
2473
+ setTextListStyle(block, listStyle, paragraphIndex = -1, listLevel) {
2474
+ this.#engine.setTextListStyle(block, listStyle, paragraphIndex, listLevel);
2475
+ }
2476
+ getTextListLevel(block, paragraphIndex) {
2477
+ return this.#engine.getTextListLevel(block, paragraphIndex);
2478
+ }
2479
+ setTextListLevel(block, listLevel, paragraphIndex = -1) {
2480
+ this.#engine.setTextListLevel(block, listLevel, paragraphIndex);
2481
+ }
2482
+ /**
2483
+ * Returns the 0-based paragraph indices that overlap the given UTF-16 range.
2484
+ * Range is half-open: `from` inclusive, `to` exclusive. Negative values mean
2485
+ * "all paragraphs". When `from === to`, returns the paragraph containing the
2486
+ * cursor at `from`.
2487
+ */
2488
+ getTextParagraphIndices(block, from = -1, to = -1) {
2489
+ const range = this.#toGraphemeRange(block, from, to);
2490
+ return this.#engine.getTextParagraphIndices(block, range.from, range.to);
2491
+ }
2492
+ getTextLineHeight(block, paragraphIndex) {
2493
+ return this.#engine.getTextLineHeight(block, paragraphIndex);
2494
+ }
2495
+ setTextLineHeight(block, lineHeight, paragraphIndex = -1) {
2496
+ this.#engine.setTextLineHeight(
2497
+ block,
2498
+ lineHeight ?? void 0,
2499
+ paragraphIndex
2500
+ );
2501
+ }
2502
+ getFontMetrics(fontFileUri) {
2503
+ return this.#engine.getFontMetrics(fontFileUri);
2504
+ }
2505
+ // Source sets
2506
+ getSourceSet(block, property) {
2507
+ return this.#engine.getSourceSet(block, property);
2508
+ }
2509
+ setSourceSet(block, property, sources) {
2510
+ this.#engine.setSourceSet(block, property, sources);
2511
+ }
2512
+ addImageFileURIToSourceSet(block, property, uri) {
2513
+ return this.#engine.addImageFileURIToSourceSet(block, property, uri);
2514
+ }
2515
+ addVideoFileURIToSourceSet(block, property, uri) {
2516
+ return this.#engine.addVideoFileURIToSourceSet(block, property, uri);
2517
+ }
2518
+ // Video/Audio info
2519
+ getVideoWidth(block) {
2520
+ return this.#engine.getVideoWidth(block);
2521
+ }
2522
+ getVideoHeight(block) {
2523
+ return this.#engine.getVideoHeight(block);
2524
+ }
2525
+ getAudioTrackCountFromVideo(block) {
2526
+ return this.#engine.getAudioTrackCountFromVideo(block);
2527
+ }
2528
+ getAVResourceTotalDuration(block) {
2529
+ return this.#engine.getAVResourceTotalDuration(block);
2530
+ }
2531
+ /**
2532
+ * Splits the block at the given time. The original block keeps the
2533
+ * leading portion; the trailing portion is returned as a newly
2534
+ * created block id (matches `@cesdk/node`'s return contract).
2535
+ *
2536
+ * @remarks Matches `@cesdk/node`'s `SplitOptions` shape:
2537
+ * `attachToParent` and `selectNewBlock` default to `true`,
2538
+ * `createParentTrackIfNeeded` defaults to `false`. Non-default values
2539
+ * are accepted for shape parity but not yet plumbed to the C++ binding
2540
+ * — when set, a warning is emitted so callers can detect the gap.
2541
+ *
2542
+ * @param block - The block to split.
2543
+ * @param atTime - The time (seconds) at which to split.
2544
+ * @param options - Split-behavior overrides.
2545
+ * @returns The id of the newly created trailing block.
2546
+ * @public
2547
+ */
2548
+ split(block, atTime, options = {}) {
2549
+ const {
2550
+ attachToParent = true,
2551
+ createParentTrackIfNeeded = false,
2552
+ selectNewBlock = true
2553
+ } = options;
2554
+ if (attachToParent !== true || createParentTrackIfNeeded !== false || selectNewBlock !== true) {
2555
+ console.warn(
2556
+ "[@cesdk/node-native] block.split() options are accepted for shape parity but not yet plumbed through the C++ binding. The engine will use defaults (attachToParent: true, createParentTrackIfNeeded: false, selectNewBlock: true)."
2557
+ );
2558
+ }
2559
+ return this.#engine.split(block, atTime);
2560
+ }
2561
+ // Frame position/size - WASM API uses getFrameX/Y/Width/Height
2562
+ getFrameX(block) {
2563
+ return this.#engine.getLastFrameX(block);
2564
+ }
2565
+ getFrameY(block) {
2566
+ return this.#engine.getLastFrameY(block);
2567
+ }
2568
+ getFrameWidth(block) {
2569
+ return this.#engine.getLastFrameWidth(block);
2570
+ }
2571
+ getFrameHeight(block) {
2572
+ return this.#engine.getLastFrameHeight(block);
2573
+ }
2574
+ // Page duration source
2575
+ supportsPageDurationSource(page, block) {
2576
+ return this.#engine.supportsPageDurationSource(page, block);
2577
+ }
2578
+ setPageDurationSource(page, block) {
2579
+ this.#engine.setPageDurationSource(page, block);
2580
+ }
2581
+ removePageDurationSource(page) {
2582
+ this.#engine.removePageDurationSource(page);
2583
+ }
2584
+ isPageDurationSource(block) {
2585
+ return this.#engine.isPageDurationSource(block);
2586
+ }
2587
+ // Scope - block-level methods only (findAllScopes, getGlobalScope, setGlobalScope are on EditorAPI)
2588
+ isScopeEnabled(block, scope) {
2589
+ return this.#engine.isScopeEnabled(block, scope);
2590
+ }
2591
+ setScopeEnabled(block, scope, enabled) {
2592
+ this.#engine.setScopeEnabled(block, scope, enabled);
2593
+ }
2594
+ isAllowedByScope(block, scope) {
2595
+ return this.#engine.isAllowedByScope(block, scope);
2596
+ }
2597
+ // Properties
2598
+ findAllProperties(block) {
2599
+ return this.#engine.findAllProperties(block);
2600
+ }
2601
+ getPropertyType(blockOrProperty, property) {
2602
+ const propName = typeof blockOrProperty === "string" ? blockOrProperty : property;
2603
+ return this.#engine.getPropertyType(propName);
2604
+ }
2605
+ isPropertyReadable(blockOrProperty, property) {
2606
+ const propName = typeof blockOrProperty === "string" ? blockOrProperty : property;
2607
+ return this.#engine.isPropertyReadable(propName);
2608
+ }
2609
+ isPropertyWritable(blockOrProperty, property) {
2610
+ const propName = typeof blockOrProperty === "string" ? blockOrProperty : property;
2611
+ return this.#engine.isPropertyWritable(propName);
2612
+ }
2613
+ getEnumValues(blockOrProperty, property) {
2614
+ const propName = typeof blockOrProperty === "string" ? blockOrProperty : property;
2615
+ return this.#engine.getEnumValues(propName);
2616
+ }
2617
+ // State
2618
+ getState(block) {
2619
+ return this.#engine.getState(block);
2620
+ }
2621
+ async export(block, mimeTypeOrOptions, optionsOrUndefined) {
2622
+ let mimeType;
2623
+ let options;
2624
+ if (typeof mimeTypeOrOptions === "string") {
2625
+ mimeType = mimeTypeOrOptions;
2626
+ options = optionsOrUndefined;
2627
+ } else {
2628
+ options = mimeTypeOrOptions;
2629
+ mimeType = options?.mimeType ?? "image/png";
2630
+ }
2631
+ const buffer = await this.exportToBuffer(block, mimeType, options);
2632
+ return new Blob([buffer], { type: mimeType });
2633
+ }
2634
+ exportToBuffer(block, mimeType, options) {
2635
+ const exportOptions = {
2636
+ targetWidth: options?.targetWidth ?? 0,
2637
+ targetHeight: options?.targetHeight ?? 0,
2638
+ useTargetSize: options?.targetWidth != null && options?.targetHeight != null,
2639
+ jpegQuality: options?.jpegQuality ?? 0.9,
2640
+ webpQuality: options?.webpQuality ?? 1,
2641
+ pngCompressionLevel: options?.pngCompressionLevel ?? 5,
2642
+ exportPdfWithHighCompatibility: options?.exportPdfWithHighCompatibility ?? true,
2643
+ exportPdfWithDeviceCMYK: options?.exportPdfWithDeviceCMYK ?? false,
2644
+ exportPdfWithUnderlayer: options?.exportPdfWithUnderlayer ?? false,
2645
+ underlayerSpotColorName: options?.underlayerSpotColorName ?? "",
2646
+ underlayerOffset: options?.underlayerOffset ?? 0,
2647
+ underlayerRenderRatio: options?.underlayerRenderRatio ?? 1,
2648
+ underlayerMaxError: options?.underlayerMaxError ?? 2,
2649
+ allowTextOverhang: options?.allowTextOverhang ?? false
2650
+ };
2651
+ return this.#engine.exportToBuffer(block, mimeType, exportOptions);
2652
+ }
2653
+ async exportWithColorMask(block, arg2, arg3, arg4, arg5, arg6) {
2654
+ let mimeType;
2655
+ let maskColorR;
2656
+ let maskColorG;
2657
+ let maskColorB;
2658
+ let options;
2659
+ if (typeof arg2 === "number") {
2660
+ maskColorR = arg2;
2661
+ maskColorG = arg3;
2662
+ maskColorB = arg4;
2663
+ options = typeof arg5 === "number" ? void 0 : arg5;
2664
+ mimeType = options?.mimeType ?? "image/png";
2665
+ } else {
2666
+ mimeType = arg2 ?? "image/png";
2667
+ maskColorR = arg3;
2668
+ maskColorG = arg4;
2669
+ maskColorB = arg5 ?? 0;
2670
+ options = arg6;
2671
+ }
2672
+ const exportOptions = {
2673
+ targetWidth: options?.targetWidth ?? 0,
2674
+ targetHeight: options?.targetHeight ?? 0,
2675
+ useTargetSize: options?.targetWidth != null && options?.targetHeight != null,
2676
+ jpegQuality: options?.jpegQuality ?? 0.9,
2677
+ webpQuality: options?.webpQuality ?? 1,
2678
+ pngCompressionLevel: options?.pngCompressionLevel ?? 5,
2679
+ exportPdfWithHighCompatibility: options?.exportPdfWithHighCompatibility ?? true,
2680
+ exportPdfWithDeviceCMYK: options?.exportPdfWithDeviceCMYK ?? false,
2681
+ exportPdfWithUnderlayer: options?.exportPdfWithUnderlayer ?? false,
2682
+ underlayerSpotColorName: options?.underlayerSpotColorName ?? "",
2683
+ underlayerOffset: options?.underlayerOffset ?? 0,
2684
+ underlayerRenderRatio: options?.underlayerRenderRatio ?? 1,
2685
+ underlayerMaxError: options?.underlayerMaxError ?? 2,
2686
+ allowTextOverhang: options?.allowTextOverhang ?? false
2687
+ };
2688
+ const buffers = await this.#engine.exportWithColorMaskToBuffer(
2689
+ block,
2690
+ mimeType,
2691
+ maskColorR,
2692
+ maskColorG,
2693
+ maskColorB,
2694
+ exportOptions
2695
+ );
2696
+ return buffers.map((buf) => new Blob([buf], { type: mimeType }));
2697
+ }
2698
+ async exportVideo(block, mimeTypeOrOptions, progressCallbackOrUndefined, optionsOrUndefined) {
2699
+ let mimeType;
2700
+ let progressCallback;
2701
+ let options;
2702
+ if (typeof mimeTypeOrOptions === "string") {
2703
+ mimeType = mimeTypeOrOptions;
2704
+ progressCallback = progressCallbackOrUndefined;
2705
+ options = optionsOrUndefined;
2706
+ } else {
2707
+ const bag = mimeTypeOrOptions;
2708
+ mimeType = bag?.mimeType ?? "video/mp4";
2709
+ progressCallback = bag?.onProgress;
2710
+ options = bag;
2711
+ }
2712
+ const buffer = await this.exportVideoToBuffer(
2713
+ block,
2714
+ mimeType,
2715
+ options,
2716
+ progressCallback
2717
+ );
2718
+ return new Blob([buffer], { type: mimeType });
2719
+ }
2720
+ exportVideoToBuffer(block, mimeType, options, progressCallback) {
2721
+ return new Promise((resolve, reject) => {
2722
+ const defaultOptions = {
2723
+ h264Profile: 77,
2724
+ // Main (H264VideoProfile::Main = 77)
2725
+ h264Level: 52,
2726
+ videoBitrate: 0,
2727
+ audioBitrate: 0,
2728
+ framerate: 30,
2729
+ useTargetSize: false,
2730
+ targetWidth: 0,
2731
+ targetHeight: 0,
2732
+ allowTextOverhang: false,
2733
+ ...options
2734
+ };
2735
+ this.#engine.exportVideoToBuffer(
2736
+ block,
2737
+ options?.timeOffset ?? 0,
2738
+ options?.duration ?? this.getDuration(block),
2739
+ mimeType,
2740
+ progressCallback ?? (() => {
2741
+ }),
2742
+ (errorOrNull, data) => {
2743
+ if (errorOrNull) {
2744
+ reject(
2745
+ errorOrNull instanceof Error ? errorOrNull : new Error(String(errorOrNull))
2746
+ );
2747
+ } else if (data) {
2748
+ resolve(data);
2749
+ } else {
2750
+ reject(new Error("Video export returned no data"));
2751
+ }
2752
+ },
2753
+ defaultOptions
2754
+ );
2755
+ });
2756
+ }
2757
+ async exportAudio(block, mimeTypeOrOptions, optionsOrUndefined) {
2758
+ let mimeType;
2759
+ let options;
2760
+ if (typeof mimeTypeOrOptions === "string") {
2761
+ mimeType = mimeTypeOrOptions;
2762
+ options = optionsOrUndefined;
2763
+ } else {
2764
+ options = mimeTypeOrOptions;
2765
+ mimeType = options?.mimeType ?? "audio/wav";
2766
+ }
2767
+ const buffer = await this.exportAudioToBuffer(block, mimeType, options);
2768
+ return new Blob([buffer], { type: mimeType });
2769
+ }
2770
+ exportAudioToBuffer(block, mimeType, options) {
2771
+ return new Promise((resolve, reject) => {
2772
+ const defaultOptions = {
2773
+ sampleRate: 48e3,
2774
+ numberOfChannels: 2,
2775
+ skipEncoding: false,
2776
+ ...options
2777
+ };
2778
+ this.#engine.exportAudioToBuffer(
2779
+ block,
2780
+ options?.timeOffset ?? 0,
2781
+ options?.duration ?? this.getDuration(block),
2782
+ mimeType,
2783
+ options?.onProgress ?? (() => {
2784
+ }),
2785
+ (errorOrNull, data) => {
2786
+ if (errorOrNull) {
2787
+ reject(
2788
+ errorOrNull instanceof Error ? errorOrNull : new Error(String(errorOrNull))
2789
+ );
2790
+ } else if (data) {
2791
+ resolve(data);
2792
+ } else {
2793
+ reject(new Error("Audio export returned no data"));
2794
+ }
2795
+ },
2796
+ defaultOptions
2797
+ );
2798
+ });
2799
+ }
2800
+ // Loading/Saving blocks
2801
+ loadFromString(content) {
2802
+ if (content.length === 0) {
2803
+ return Promise.reject(new Error("Cannot load from empty string"));
2804
+ }
2805
+ return this.#engine.loadBlocksFromString(content);
2806
+ }
2807
+ loadFromURL(url) {
2808
+ return this.#engine.loadBlocksFromURL(url);
2809
+ }
2810
+ /**
2811
+ * Load blocks from an archive URL.
2812
+ * Also supports data URIs (data:application/zip;base64,...) which are converted to buffer:// protocol internally.
2813
+ * @param url - The URL of the archive file, or a data URI containing base64-encoded archive data.
2814
+ * @returns A promise that resolves with an array of block IDs.
2815
+ */
2816
+ async loadFromArchiveURL(url) {
2817
+ if (url.startsWith("data:")) {
2818
+ const matches = url.match(/^data:([^;,]+)?(?:;base64)?,(.*)$/);
2819
+ if (!matches) {
2820
+ throw new Error("Invalid data URI format");
2821
+ }
2822
+ const base64Data = matches[2];
2823
+ const buffer = Buffer.from(base64Data, "base64");
2824
+ const bufferUri = this.#engine.createBuffer();
2825
+ this.#engine.setBufferData(bufferUri, 0, new Uint8Array(buffer));
2826
+ try {
2827
+ return await this.#engine.loadBlocksFromArchiveURL(bufferUri);
2828
+ } finally {
2829
+ this.#engine.destroyBuffer(bufferUri);
2830
+ }
2831
+ }
2832
+ return this.#engine.loadBlocksFromArchiveURL(url);
2833
+ }
2834
+ saveToString(blocks) {
2835
+ return this.#engine.saveBlocksToString(blocks);
2836
+ }
2837
+ // BlockAPI.saveToArchive matches @cesdk/node and returns Promise<Blob>. The
2838
+ // raw Uint8Array form remains accessible via the engine binding for
2839
+ // power-users that want to skip the wrapper.
2840
+ async saveToArchive(blocks) {
2841
+ const buffer = await this.#engine.saveBlocksToArchive(blocks);
2842
+ return new Blob([buffer]);
2843
+ }
2844
+ /**
2845
+ * Force-load the audio/video resource for an audio block or video-fill block.
2846
+ * Resolves once the asset is fully fetched and demuxed (so subsequent
2847
+ * `exportVideo` calls have data immediately available on frame 0). Without
2848
+ * this prime, the encoder can race ahead of the HTTP fetch and produce
2849
+ * silent audio / black-frame video segments.
2850
+ */
2851
+ forceLoadAVResource(block) {
2852
+ return this.#engine.forceLoadAVResource(block);
2853
+ }
2854
+ /**
2855
+ * Force-load every resource referenced by `blocks`. Useful before
2856
+ * `exportVideo` on a scene with multiple remote assets — saves N network
2857
+ * round-trips compared to calling `forceLoadAVResource` per block.
2858
+ */
2859
+ forceLoadResources(blocks) {
2860
+ return this.#engine.forceLoadResources(blocks);
2861
+ }
2862
+ // -------------------------------------------------------------------------
2863
+ // Composition helpers (parity with @cesdk/node's `addImage` / `addVideo` /
2864
+ // `applyAnimation` / `applyDropShadow`).
2865
+ //
2866
+ // These are TS-level convenience wrappers, not engine primitives. WASM
2867
+ // ships them on its BlockAPI; native must too so a drop-in `addImage`
2868
+ // call doesn't throw `TypeError: not a function`.
2869
+ // -------------------------------------------------------------------------
2870
+ /**
2871
+ * Apply an animation set (in/loop/out) to a block. Mirrors
2872
+ * `@cesdk/node`'s `BlockAPI.applyAnimation` exactly.
2873
+ *
2874
+ * @public
2875
+ */
2876
+ applyAnimation(block, animation = {}) {
2877
+ const apply = (entry, setter) => {
2878
+ if (entry == null) return;
2879
+ const animBlock = this.createAnimation(entry.type);
2880
+ setter(block, animBlock);
2881
+ if (entry.duration !== void 0) {
2882
+ this.setDuration(animBlock, entry.duration);
2883
+ }
2884
+ if (entry.easing != null) {
2885
+ try {
2886
+ const enumValues = this.getEnumValues("animationEasing");
2887
+ if (enumValues.includes(entry.easing)) {
2888
+ this.setEnum(animBlock, "animationEasing", entry.easing);
2889
+ }
2890
+ } catch {
2891
+ }
2892
+ }
2893
+ };
2894
+ apply(animation.in, (b, a) => this.setInAnimation(b, a));
2895
+ apply(animation.out, (b, a) => this.setOutAnimation(b, a));
2896
+ apply(animation.loop, (b, a) => this.setLoopAnimation(b, a));
2897
+ }
2898
+ /**
2899
+ * Apply a drop shadow effect to a block. Mirrors `@cesdk/node`'s
2900
+ * `BlockAPI.applyDropShadow` exactly.
2901
+ *
2902
+ * @public
2903
+ */
2904
+ applyDropShadow(block, options = {}) {
2905
+ this.setDropShadowEnabled(block, true);
2906
+ if (options.color != null) {
2907
+ this.setDropShadowColor(block, options.color);
2908
+ }
2909
+ if (options.offset != null) {
2910
+ if (options.offset.x != null)
2911
+ this.setDropShadowOffsetX(block, options.offset.x);
2912
+ if (options.offset.y != null)
2913
+ this.setDropShadowOffsetY(block, options.offset.y);
2914
+ }
2915
+ if (options.blur != null) {
2916
+ if (options.blur.x != null)
2917
+ this.setDropShadowBlurRadiusX(block, options.blur.x);
2918
+ if (options.blur.y != null)
2919
+ this.setDropShadowBlurRadiusY(block, options.blur.y);
2920
+ }
2921
+ }
2922
+ /**
2923
+ * Add an image to the current page as a graphic block with an image fill.
2924
+ * Mirrors `@cesdk/node`'s `BlockAPI.addImage` shape — same options bag,
2925
+ * same chained-call sequence.
2926
+ *
2927
+ * @remarks Unlike WASM (which uses `new Image()` to auto-detect size when
2928
+ * `options.size` is omitted), Node has no DOM `Image` global. In Node,
2929
+ * `options.size` must be supplied; calling without it throws a clear
2930
+ * error with the suggested fix.
2931
+ *
2932
+ * @public
2933
+ */
2934
+ addImage(url, options = {}) {
2935
+ const {
2936
+ x,
2937
+ y,
2938
+ size,
2939
+ timeline,
2940
+ animation,
2941
+ shadow,
2942
+ cornerRadius,
2943
+ sizeMode = "Absolute",
2944
+ positionMode = "Absolute"
2945
+ } = options;
2946
+ let finalWidth;
2947
+ let finalHeight;
2948
+ if (size != null) {
2949
+ if (typeof size === "number") {
2950
+ finalWidth = size;
2951
+ finalHeight = size;
2952
+ } else {
2953
+ finalWidth = size.width;
2954
+ finalHeight = size.height;
2955
+ }
2956
+ } else {
2957
+ throw new Error(
2958
+ "[@cesdk/node-native] block.addImage requires `options.size` in Node (no DOM Image global for auto-sizing). Pass `{ size: { width, height } }` or `{ size: <px> }`."
2959
+ );
2960
+ }
2961
+ const scenes = this.findByType("//ly.img.ubq/scene");
2962
+ if (scenes.length === 0) {
2963
+ throw new Error("No scene found. Call engine.scene.create() first.");
2964
+ }
2965
+ const pages = this.findByType("//ly.img.ubq/page");
2966
+ if (pages.length === 0) {
2967
+ throw new Error("No page found in scene. Create a page first.");
2968
+ }
2969
+ const page = pages[0];
2970
+ const graphicBlock = this.create("graphic");
2971
+ this.appendChild(page, graphicBlock);
2972
+ this.setPositionXMode(graphicBlock, positionMode);
2973
+ this.setPositionYMode(graphicBlock, positionMode);
2974
+ this.setWidthMode(graphicBlock, sizeMode);
2975
+ this.setHeightMode(graphicBlock, sizeMode);
2976
+ if (finalWidth != null) this.setWidth(graphicBlock, finalWidth);
2977
+ if (finalHeight != null) this.setHeight(graphicBlock, finalHeight);
2978
+ if (x != null) {
2979
+ this.setPositionX(graphicBlock, x);
2980
+ } else {
2981
+ this.alignHorizontally([graphicBlock], "Center");
2982
+ }
2983
+ if (y != null) {
2984
+ this.setPositionY(graphicBlock, y);
2985
+ } else {
2986
+ this.alignVertically([graphicBlock], "Center");
2987
+ }
2988
+ const shapeBlock = this.createShape("rect");
2989
+ this.setShape(graphicBlock, shapeBlock);
2990
+ if (cornerRadius != null) {
2991
+ this.setFloat(shapeBlock, "shape/rect/cornerRadiusBL", cornerRadius);
2992
+ this.setFloat(shapeBlock, "shape/rect/cornerRadiusBR", cornerRadius);
2993
+ this.setFloat(shapeBlock, "shape/rect/cornerRadiusTL", cornerRadius);
2994
+ this.setFloat(shapeBlock, "shape/rect/cornerRadiusTR", cornerRadius);
2995
+ }
2996
+ const fillBlock = this.createFill("image");
2997
+ this.setFill(graphicBlock, fillBlock);
2998
+ this.setString(fillBlock, "fill/image/imageFileURI", url);
2999
+ if (shadow != null) this.applyDropShadow(graphicBlock, shadow);
3000
+ if (timeline?.timeOffset != null)
3001
+ this.setTimeOffset(graphicBlock, timeline.timeOffset);
3002
+ if (timeline?.duration != null)
3003
+ this.setDuration(graphicBlock, timeline.duration);
3004
+ if (animation != null) this.applyAnimation(graphicBlock, animation);
3005
+ return graphicBlock;
3006
+ }
3007
+ /**
3008
+ * Add a video to the current page as a graphic block with a video fill.
3009
+ * Mirrors `@cesdk/node`'s `BlockAPI.addVideo` shape.
3010
+ *
3011
+ * @remarks Like `addImage`, requires explicit `options.size` in Node.
3012
+ *
3013
+ * @public
3014
+ */
3015
+ addVideo(url, options = {}) {
3016
+ const {
3017
+ x,
3018
+ y,
3019
+ size,
3020
+ timeline,
3021
+ animation,
3022
+ shadow,
3023
+ cornerRadius,
3024
+ sizeMode = "Absolute",
3025
+ positionMode = "Absolute",
3026
+ trimOffset,
3027
+ loop,
3028
+ muted
3029
+ } = options;
3030
+ if (size == null) {
3031
+ throw new Error(
3032
+ "[@cesdk/node-native] block.addVideo requires `options.size` in Node (no auto-probe). Pass `{ size: { width, height } }`."
3033
+ );
3034
+ }
3035
+ const finalWidth = typeof size === "number" ? size : size.width;
3036
+ const finalHeight = typeof size === "number" ? size : size.height;
3037
+ const pages = this.findByType("//ly.img.ubq/page");
3038
+ if (pages.length === 0) {
3039
+ throw new Error("No page found. Create a page first.");
3040
+ }
3041
+ const page = pages[0];
3042
+ const graphicBlock = this.create("graphic");
3043
+ this.appendChild(page, graphicBlock);
3044
+ this.setPositionXMode(graphicBlock, positionMode);
3045
+ this.setPositionYMode(graphicBlock, positionMode);
3046
+ this.setWidthMode(graphicBlock, sizeMode);
3047
+ this.setHeightMode(graphicBlock, sizeMode);
3048
+ this.setWidth(graphicBlock, finalWidth);
3049
+ this.setHeight(graphicBlock, finalHeight);
3050
+ if (x != null) this.setPositionX(graphicBlock, x);
3051
+ else this.alignHorizontally([graphicBlock], "Center");
3052
+ if (y != null) this.setPositionY(graphicBlock, y);
3053
+ else this.alignVertically([graphicBlock], "Center");
3054
+ const shapeBlock = this.createShape("rect");
3055
+ this.setShape(graphicBlock, shapeBlock);
3056
+ if (cornerRadius != null) {
3057
+ this.setFloat(shapeBlock, "shape/rect/cornerRadiusBL", cornerRadius);
3058
+ this.setFloat(shapeBlock, "shape/rect/cornerRadiusBR", cornerRadius);
3059
+ this.setFloat(shapeBlock, "shape/rect/cornerRadiusTL", cornerRadius);
3060
+ this.setFloat(shapeBlock, "shape/rect/cornerRadiusTR", cornerRadius);
3061
+ }
3062
+ const fillBlock = this.createFill("video");
3063
+ this.setFill(graphicBlock, fillBlock);
3064
+ this.setString(fillBlock, "fill/video/fileURI", url);
3065
+ if (loop !== void 0) {
3066
+ try {
3067
+ this.setBool(fillBlock, "fill/video/looping", loop);
3068
+ } catch {
3069
+ }
3070
+ }
3071
+ if (muted !== void 0) {
3072
+ try {
3073
+ this.setBool(fillBlock, "fill/video/muted", muted);
3074
+ } catch {
3075
+ }
3076
+ }
3077
+ if (trimOffset !== void 0) {
3078
+ try {
3079
+ this.setFloat(fillBlock, "fill/video/trimOffset", trimOffset);
3080
+ } catch {
3081
+ }
3082
+ }
3083
+ if (shadow != null) this.applyDropShadow(graphicBlock, shadow);
3084
+ if (timeline?.timeOffset != null)
3085
+ this.setTimeOffset(graphicBlock, timeline.timeOffset);
3086
+ if (timeline?.duration != null)
3087
+ this.setDuration(graphicBlock, timeline.duration);
3088
+ if (animation != null) this.applyAnimation(graphicBlock, animation);
3089
+ return graphicBlock;
3090
+ }
3091
+ /**
3092
+ * Subscribe to state changes on a set of blocks. Matches WASM's
3093
+ * `engine.block.onStateChanged(ids, callback)` contract — pass an empty
3094
+ * array to monitor every block in the scene. The native C++ side
3095
+ * already implements this via `subscribeToBlockState`; this wrapper
3096
+ * exposes it on `engine.block` so portable consumers (e.g.
3097
+ * `@imgly/psd-importer`'s transient-relocation tests) can subscribe the
3098
+ * same way on both bindings.
3099
+ *
3100
+ * @returns A function to unsubscribe.
3101
+ */
3102
+ onStateChanged(ids, callback) {
3103
+ const id = this.#engine.subscribeToBlockState(ids ?? [], callback);
3104
+ return () => this.#engine.unsubscribe(id);
3105
+ }
3106
+ /**
3107
+ * Subscribe to selection changes. Matches WASM's
3108
+ * `engine.block.onSelectionChanged(callback)` contract — fires when the
3109
+ * set of selected blocks changes. The native C++ side already implements
3110
+ * this via `subscribeToSelectionChange`; this wrapper exposes it so
3111
+ * portable consumers (selection-reactive UI) work on both bindings.
3112
+ *
3113
+ * @returns A function to unsubscribe.
3114
+ */
3115
+ onSelectionChanged(callback) {
3116
+ const id = this.#engine.subscribeToSelectionChange(callback);
3117
+ return () => this.#engine.unsubscribe(id);
3118
+ }
3119
+ /**
3120
+ * Subscribe to block clicks. Matches WASM's
3121
+ * `engine.block.onClicked((id) => …)` contract. In a headless Node
3122
+ * context the engine never fires this event (no UI input), but the
3123
+ * subscription surface exists for API parity so the same code compiles
3124
+ * and runs against both bindings.
3125
+ *
3126
+ * @returns A function to unsubscribe.
3127
+ */
3128
+ onClicked(callback) {
3129
+ const id = this.#engine.subscribeToBlockClicked(callback);
3130
+ return () => this.#engine.unsubscribe(id);
3131
+ }
3132
+ };
3133
+ var SceneAPI = class {
3134
+ #engine;
3135
+ constructor(engine) {
3136
+ this.#engine = engine;
3137
+ }
3138
+ /**
3139
+ * Create a new design scene, along with its own camera.
3140
+ * @param sceneLayout - The layout of the scene.
3141
+ * @returns The scene's handle.
3142
+ */
3143
+ create(sceneLayout = "Free", options) {
3144
+ const scene = this.#engine.createScene(sceneLayout);
3145
+ if (options) {
3146
+ this.#createSceneElements(scene, options);
3147
+ }
3148
+ return scene;
3149
+ }
3150
+ /**
3151
+ * Create a new scene in video mode, along with its own camera.
3152
+ * @returns The scene's handle.
3153
+ */
3154
+ createVideo(options) {
3155
+ const scene = this.#engine.createVideoScene();
3156
+ this.#engine.setSceneMode(scene, "Video");
3157
+ if (options) {
3158
+ this.#createSceneElements(scene, options);
3159
+ }
3160
+ return scene;
3161
+ }
3162
+ #createSceneElements(scene, options) {
3163
+ if (options.designUnit != null) {
3164
+ this.#engine.setDesignUnit(scene, options.designUnit);
3165
+ const pairedFontUnit = options.designUnit === "Pixel" ? "Pixel" : "Point";
3166
+ this.#engine.setFontSizeUnit(
3167
+ scene,
3168
+ options.fontSizeUnit ?? pairedFontUnit
3169
+ );
3170
+ } else if (options.fontSizeUnit != null) {
3171
+ this.#engine.setFontSizeUnit(scene, options.fontSizeUnit);
3172
+ }
3173
+ if (!options.page) {
3174
+ return;
3175
+ }
3176
+ let finalWidth = 0;
3177
+ let finalHeight = 0;
3178
+ if (typeof options.page.size === "number") {
3179
+ finalWidth = options.page.size;
3180
+ finalHeight = options.page.size;
3181
+ } else if (typeof options.page.size === "object") {
3182
+ finalWidth = options.page.size.width;
3183
+ finalHeight = options.page.size.height;
3184
+ } else {
3185
+ return;
3186
+ }
3187
+ const page = this.#engine.create("page");
3188
+ this.#engine.appendChild(scene, page);
3189
+ this.#engine.setWidth(page, finalWidth);
3190
+ this.#engine.setHeight(page, finalHeight);
3191
+ if (options.page.color) {
3192
+ const fill = this.#engine.createFill("color");
3193
+ this.#engine.setFill(page, fill);
3194
+ this.#engine.setColor(
3195
+ page,
3196
+ "fill/solid/color",
3197
+ NativeColorInternal.fromColor(options.page.color)
3198
+ );
3199
+ }
3200
+ }
3201
+ /**
3202
+ * Return the currently active scene.
3203
+ * @returns The scene or null, if none was created yet.
3204
+ */
3205
+ get() {
3206
+ const scenes = this.#engine.findByType("scene");
3207
+ return scenes.length > 0 ? scenes[0] : null;
3208
+ }
3209
+ /**
3210
+ * Load the contents of a scene file.
3211
+ * @param sceneContent - The scene file contents, a base64 string.
3212
+ * @param overrideEditorConfig - Whether to override editor configuration.
3213
+ * @param waitForResources - Whether to wait for all resources to finish loading.
3214
+ * @returns A handle to the loaded scene.
3215
+ */
3216
+ async loadFromString(sceneContent, overrideEditorConfig = false, waitForResources = false) {
3217
+ return this.#engine.loadSceneFromString(
3218
+ sceneContent,
3219
+ overrideEditorConfig,
3220
+ waitForResources
3221
+ );
3222
+ }
3223
+ /**
3224
+ * Load a scene from the URL to the scene file.
3225
+ * @param url - The URL of the scene file.
3226
+ * @param overrideEditorConfig - Whether to override editor configuration.
3227
+ * @param waitForResources - Whether to wait for all resources to finish loading.
3228
+ * @returns A promise that resolves with the scene ID.
3229
+ */
3230
+ async loadFromURL(url, overrideEditorConfig = false, waitForResources = false) {
3231
+ return this.#engine.loadSceneFromURL(
3232
+ url,
3233
+ overrideEditorConfig,
3234
+ waitForResources
3235
+ );
3236
+ }
3237
+ /**
3238
+ * Load a previously archived scene from the URL to the scene file.
3239
+ * Also supports data URIs (data:application/zip;base64,...) which are converted to buffer:// protocol internally.
3240
+ * @param url - The URL of the scene archive file, or a data URI containing base64-encoded archive data.
3241
+ * @param overrideEditorConfig - Whether to override editor configuration.
3242
+ * @param waitForResources - Whether to wait for all resources to finish loading.
3243
+ * @returns A promise that resolves with the scene ID.
3244
+ */
3245
+ async loadFromArchiveURL(url, overrideEditorConfig = false, waitForResources = false) {
3246
+ if (url.startsWith("data:")) {
3247
+ const matches = url.match(/^data:([^;,]+)?(?:;base64)?,(.*)$/);
3248
+ if (!matches) {
3249
+ throw new Error("Invalid data URI format");
3250
+ }
3251
+ const base64Data = matches[2];
3252
+ const buffer = Buffer.from(base64Data, "base64");
3253
+ const bufferUri = this.#engine.createBuffer();
3254
+ this.#engine.setBufferData(bufferUri, 0, new Uint8Array(buffer));
3255
+ try {
3256
+ return await this.#engine.loadSceneFromArchiveURL(
3257
+ bufferUri,
3258
+ overrideEditorConfig,
3259
+ waitForResources
3260
+ );
3261
+ } finally {
3262
+ this.#engine.destroyBuffer(bufferUri);
3263
+ }
3264
+ }
3265
+ return this.#engine.loadSceneFromArchiveURL(
3266
+ url,
3267
+ overrideEditorConfig,
3268
+ waitForResources
3269
+ );
3270
+ }
3271
+ /**
3272
+ * Serializes the current scene into a string.
3273
+ *
3274
+ * Optionally accepts a `compression` option to apply Zstd compression to
3275
+ * the output. When compression is enabled the returned string contains
3276
+ * raw compressed bytes (not base64) — pass it back to `loadFromString`
3277
+ * to round-trip.
3278
+ *
3279
+ * @returns A promise that resolves with the serialized scene.
3280
+ */
3281
+ async saveToString(options) {
3282
+ const scene = this.get();
3283
+ if (scene === null) {
3284
+ throw new Error("No scene available.");
3285
+ }
3286
+ return this.#engine.saveSceneToString(scene, options);
3287
+ }
3288
+ /**
3289
+ * Saves the current scene and all of its referenced assets into an archive.
3290
+ * @returns A promise that resolves with a Uint8Array.
3291
+ */
3292
+ async saveToArchive() {
3293
+ const scene = this.get();
3294
+ if (scene === null) {
3295
+ throw new Error("No scene available.");
3296
+ }
3297
+ const buffer = await this.#engine.saveSceneToArchive(scene);
3298
+ return new Blob([buffer]);
3299
+ }
3300
+ /**
3301
+ * Loads the given image and creates a scene with a single page showing the image.
3302
+ * @param url - The image URL.
3303
+ * @param dpi - The scene's DPI.
3304
+ * @param pixelScaleFactor - The display's pixel scale factor.
3305
+ * @returns A promise that resolves with the scene ID.
3306
+ */
3307
+ /**
3308
+ * Loads the given image and creates a scene with a single page showing the image.
3309
+ *
3310
+ * @remarks The 4th/5th/6th args (`sceneLayout`, `spacing`,
3311
+ * `spacingInScreenSpace`) are part of `@cesdk/node`'s signature and
3312
+ * accepted here for shape parity. The C++ binding currently hardcodes
3313
+ * `Free`/`0.f`/`false`; non-default values are not yet plumbed
3314
+ * through. A warning is emitted when a caller passes a non-default
3315
+ * value so the discrepancy is visible.
3316
+ *
3317
+ * @public
3318
+ */
3319
+ async createFromImage(url, dpi = 300, pixelScaleFactor = 1, sceneLayout, spacing, spacingInScreenSpace) {
3320
+ if (sceneLayout !== void 0 && sceneLayout !== "Free" || spacing !== void 0 && spacing !== 0 || spacingInScreenSpace !== void 0 && spacingInScreenSpace !== false) {
3321
+ console.warn(
3322
+ `[@cesdk/node-native] scene.createFromImage() arguments 'sceneLayout' / 'spacing' / 'spacingInScreenSpace' are accepted for shape parity but not yet plumbed through the C++ binding. The engine will use defaults (Free / 0 / false).`
3323
+ );
3324
+ }
3325
+ return this.#engine.createSceneFromImage(url, dpi, pixelScaleFactor);
3326
+ }
3327
+ /**
3328
+ * Loads the given video and creates a scene with a single page showing the video.
3329
+ * @param url - The video URL.
3330
+ * @returns A promise that resolves with the scene ID.
3331
+ */
3332
+ async createFromVideo(url) {
3333
+ return this.#engine.createSceneFromVideo(url);
3334
+ }
3335
+ /**
3336
+ * Applies the contents of the given template scene to the currently loaded scene.
3337
+ * @param content - The template scene file contents, a base64 string.
3338
+ * @returns A Promise that resolves once the template was applied.
3339
+ */
3340
+ async applyTemplateFromString(content) {
3341
+ return this.#engine.applyTemplateFromString(content);
3342
+ }
3343
+ /**
3344
+ * Applies the contents of the given template scene to the currently loaded scene.
3345
+ * @param url - The url to the template scene file.
3346
+ * @returns A Promise that resolves once the template was applied.
3347
+ */
3348
+ async applyTemplateFromURL(url) {
3349
+ return this.#engine.applyTemplateFromURL(url);
3350
+ }
3351
+ /**
3352
+ * Get the current scene mode.
3353
+ * @deprecated Scene mode no longer affects engine behavior. All features work regardless of mode.
3354
+ * @returns The current mode of the scene, or null if no mode has been set.
3355
+ */
3356
+ getMode() {
3357
+ const scene = this.get();
3358
+ if (scene === null) {
3359
+ return null;
3360
+ }
3361
+ try {
3362
+ return this.#engine.getSceneMode(scene);
3363
+ } catch {
3364
+ return null;
3365
+ }
3366
+ }
3367
+ /**
3368
+ * Set the mode of the current scene.
3369
+ * @param mode - The new mode for the scene ('Design' or 'Video').
3370
+ */
3371
+ setMode(mode) {
3372
+ const scene = this.get();
3373
+ if (scene === null) {
3374
+ throw new Error("No scene available.");
3375
+ }
3376
+ this.#engine.setSceneMode(scene, mode);
3377
+ }
3378
+ /**
3379
+ * Converts all values of the current scene into the given design unit.
3380
+ * @param designUnit - The new design unit of the scene.
3381
+ */
3382
+ setDesignUnit(designUnit) {
3383
+ const scene = this.get();
3384
+ if (scene === null) {
3385
+ throw new Error("No scene available.");
3386
+ }
3387
+ this.#engine.setDesignUnit(scene, designUnit);
3388
+ }
3389
+ /**
3390
+ * Returns the design unit of the current scene.
3391
+ * @returns The current design unit.
3392
+ */
3393
+ getDesignUnit() {
3394
+ const scene = this.get();
3395
+ if (scene === null) {
3396
+ throw new Error("No scene available.");
3397
+ }
3398
+ return this.#engine.getDesignUnit(scene);
3399
+ }
3400
+ /**
3401
+ * Sets the unit ('Pixel' | 'Point') in which the current scene's font sizes
3402
+ * are interpreted at the API boundary (setTextFontSize / getTextFontSizes).
3403
+ * The engine stores font sizes in points internally.
3404
+ * @param unit - The new font-size unit.
3405
+ */
3406
+ setFontSizeUnit(unit) {
3407
+ const scene = this.get();
3408
+ if (scene === null) {
3409
+ throw new Error("No scene available.");
3410
+ }
3411
+ this.#engine.setFontSizeUnit(scene, unit);
3412
+ }
3413
+ /**
3414
+ * Returns the font-size unit of the current scene ('Pixel' | 'Point').
3415
+ * @returns The current font-size unit.
3416
+ */
3417
+ getFontSizeUnit() {
3418
+ const scene = this.get();
3419
+ if (scene === null) {
3420
+ throw new Error("No scene available.");
3421
+ }
3422
+ return this.#engine.getFontSizeUnit(scene);
3423
+ }
3424
+ /** Start or stop playback of the current page. */
3425
+ setPlaying(play) {
3426
+ const page = this.getCurrentPage();
3427
+ if (page == null) {
3428
+ throw new Error("No page to play");
3429
+ }
3430
+ this.#engine.setPlaying(page, play);
3431
+ }
3432
+ /**
3433
+ * Get the layout of the current scene.
3434
+ * @returns The current layout of the scene.
3435
+ */
3436
+ getLayout() {
3437
+ const scene = this.get();
3438
+ if (scene === null) {
3439
+ throw new Error("No scene available.");
3440
+ }
3441
+ return this.#engine.getSceneLayout(scene);
3442
+ }
3443
+ /**
3444
+ * Set the layout of the current scene.
3445
+ * @param layout - The new layout for the scene.
3446
+ */
3447
+ setLayout(layout) {
3448
+ const scene = this.get();
3449
+ if (scene === null) {
3450
+ throw new Error("No scene available.");
3451
+ }
3452
+ this.#engine.setSceneLayout(scene, layout);
3453
+ }
3454
+ /**
3455
+ * Get the sorted list of pages in the scene.
3456
+ * @returns The sorted list of pages in the scene.
3457
+ */
3458
+ getPages() {
3459
+ const scene = this.get();
3460
+ if (scene === null) {
3461
+ return [];
3462
+ }
3463
+ return this.#engine.getPages(scene);
3464
+ }
3465
+ /**
3466
+ * Add a new page to the scene.
3467
+ * @param scene - Optional scene ID. If not provided, uses the current scene.
3468
+ * @returns The ID of the newly created page.
3469
+ */
3470
+ addPage(scene) {
3471
+ const sceneId = scene ?? this.get();
3472
+ if (sceneId === null) {
3473
+ throw new Error("No scene available.");
3474
+ }
3475
+ const page = this.#engine.create("page");
3476
+ this.#engine.appendChild(sceneId, page);
3477
+ return page;
3478
+ }
3479
+ /**
3480
+ * Get the current page.
3481
+ * @returns The current page in the scene or null when there is no scene
3482
+ * available or no current page is set.
3483
+ */
3484
+ getCurrentPage(scene) {
3485
+ const sceneId = scene ?? this.get();
3486
+ if (sceneId === null) {
3487
+ return null;
3488
+ }
3489
+ return this.#engine.getCurrentPage(sceneId);
3490
+ }
3491
+ /**
3492
+ * Find all blocks with the given type sorted by the distance to viewport center.
3493
+ * @param type - The type to search for.
3494
+ * @returns A list of block ids sorted by distance to viewport center.
3495
+ */
3496
+ findNearestToViewPortCenterByType(type) {
3497
+ const scene = this.get();
3498
+ if (scene === null) {
3499
+ return [];
3500
+ }
3501
+ return this.#engine.findNearestToViewPortCenterByType(scene, type) || [];
3502
+ }
3503
+ /**
3504
+ * Find all blocks with the given kind sorted by the distance to viewport center.
3505
+ * @param kind - The kind to search for.
3506
+ * @returns A list of block ids sorted by distance to viewport center.
3507
+ */
3508
+ findNearestToViewPortCenterByKind(kind) {
3509
+ const scene = this.get();
3510
+ if (scene === null) {
3511
+ return [];
3512
+ }
3513
+ return this.#engine.findNearestToViewPortCenterByKind(scene, kind) || [];
3514
+ }
3515
+ /**
3516
+ * Set the zoom level of the scene.
3517
+ * @param zoomLevel - The new zoom level.
3518
+ */
3519
+ setZoomLevel(zoomLevel = 1) {
3520
+ const scene = this.get();
3521
+ if (scene === null) {
3522
+ throw new Error("No scene available.");
3523
+ }
3524
+ this.#engine.setZoomLevel(scene, zoomLevel);
3525
+ }
3526
+ /**
3527
+ * Get the zoom level of the scene.
3528
+ * @returns The zoom level of the scene.
3529
+ */
3530
+ getZoomLevel() {
3531
+ const scene = this.get();
3532
+ if (scene === null) {
3533
+ throw new Error("No scene available.");
3534
+ }
3535
+ return this.#engine.getZoomLevel(scene);
3536
+ }
3537
+ async zoomToBlock(id, paddingLeftOrOptions, paddingTop, paddingRight, paddingBottom) {
3538
+ const options = typeof paddingLeftOrOptions === "object" ? paddingLeftOrOptions : {
3539
+ padding: {
3540
+ left: paddingLeftOrOptions ?? 0,
3541
+ top: paddingTop ?? 0,
3542
+ right: paddingRight ?? 0,
3543
+ bottom: paddingBottom ?? 0
3544
+ }
3545
+ };
3546
+ const paddings = {
3547
+ left: 0,
3548
+ top: 0,
3549
+ right: 0,
3550
+ bottom: 0
3551
+ };
3552
+ if (options.padding != null) {
3553
+ if (typeof options.padding === "number") {
3554
+ paddings.left = options.padding;
3555
+ paddings.top = options.padding;
3556
+ paddings.right = options.padding;
3557
+ paddings.bottom = options.padding;
3558
+ } else {
3559
+ if ("x" in options.padding || "y" in options.padding) {
3560
+ paddings.left = options.padding.x ?? paddings.left;
3561
+ paddings.right = options.padding.x ?? paddings.right;
3562
+ paddings.top = options.padding.y ?? paddings.top;
3563
+ paddings.bottom = options.padding.y ?? paddings.bottom;
3564
+ }
3565
+ if ("left" in options.padding || "top" in options.padding || "right" in options.padding || "bottom" in options.padding) {
3566
+ paddings.left = options.padding.left ?? paddings.left;
3567
+ paddings.top = options.padding.top ?? paddings.top;
3568
+ paddings.right = options.padding.right ?? paddings.right;
3569
+ paddings.bottom = options.padding.bottom ?? paddings.bottom;
3570
+ }
3571
+ }
3572
+ }
3573
+ if (options.animate) {
3574
+ const animationOptions = {
3575
+ duration: 0.5,
3576
+ easing: "EaseInOut",
3577
+ interruptible: false
3578
+ };
3579
+ if (typeof options.animate === "object") {
3580
+ animationOptions.duration = options.animate.duration ?? animationOptions.duration;
3581
+ animationOptions.easing = options.animate.easing ?? animationOptions.easing;
3582
+ animationOptions.interruptible = options.animate.interruptible ?? animationOptions.interruptible;
3583
+ }
3584
+ return this.#engine.zoomToBlockWithAnimation(
3585
+ id,
3586
+ paddings.left,
3587
+ paddings.top,
3588
+ paddings.right,
3589
+ paddings.bottom,
3590
+ animationOptions.duration,
3591
+ animationOptions.easing,
3592
+ animationOptions.interruptible
3593
+ );
3594
+ } else {
3595
+ return this.#engine.zoomToBlock(
3596
+ id,
3597
+ paddings.left,
3598
+ paddings.top,
3599
+ paddings.right,
3600
+ paddings.bottom
3601
+ );
3602
+ }
3603
+ }
3604
+ /**
3605
+ * Continually adjust the zoom level to fit the block's bounding box on the
3606
+ * chosen axis. For 'Horizontal'/'Vertical', the second/third arguments are
3607
+ * `paddingBefore`/`paddingAfter` along that axis. For 'Both' the four
3608
+ * arguments map to left/top/right/bottom.
3609
+ *
3610
+ * @param id - The block to follow.
3611
+ * @param axis - 'Horizontal' | 'Vertical' | 'Both'.
3612
+ */
3613
+ enableZoomAutoFit(id, axis, paddingBeforeOrLeft = 0, paddingAfterOrTop = 0, paddingRight = 0, paddingBottom = 0) {
3614
+ if (axis === "Horizontal") {
3615
+ this.#engine.enableZoomAutoFit(
3616
+ id,
3617
+ axis,
3618
+ paddingBeforeOrLeft,
3619
+ 0,
3620
+ paddingAfterOrTop,
3621
+ 0
3622
+ );
3623
+ return;
3624
+ }
3625
+ if (axis === "Vertical") {
3626
+ this.#engine.enableZoomAutoFit(
3627
+ id,
3628
+ axis,
3629
+ 0,
3630
+ paddingBeforeOrLeft,
3631
+ 0,
3632
+ paddingAfterOrTop
3633
+ );
3634
+ return;
3635
+ }
3636
+ this.#engine.enableZoomAutoFit(
3637
+ id,
3638
+ axis,
3639
+ paddingBeforeOrLeft,
3640
+ paddingAfterOrTop,
3641
+ paddingRight,
3642
+ paddingBottom
3643
+ );
3644
+ }
3645
+ /**
3646
+ * Disables any previously set zoom auto-fit.
3647
+ * @param blockOrScene - The scene or a block in the scene.
3648
+ */
3649
+ disableZoomAutoFit(blockOrScene) {
3650
+ this.#engine.disableZoomAutoFit(blockOrScene);
3651
+ }
3652
+ /**
3653
+ * Queries whether zoom auto-fit is enabled for the given block.
3654
+ * @param blockOrScene - The scene or a block in the scene.
3655
+ * @returns True if auto-fit is set, false otherwise.
3656
+ */
3657
+ isZoomAutoFitEnabled(blockOrScene) {
3658
+ return this.#engine.isZoomAutoFitEnabled(blockOrScene);
3659
+ }
3660
+ /**
3661
+ * Continually clamp the camera position so that the given blocks stay
3662
+ * within view (with optional padding).
3663
+ * @experimental
3664
+ */
3665
+ unstable_enableCameraPositionClamping(blocks, paddingLeft = 0, paddingTop = 0, paddingRight = 0, paddingBottom = 0, scaledPaddingLeft = 0, scaledPaddingTop = 0, scaledPaddingRight = 0, scaledPaddingBottom = 0) {
3666
+ this.#engine.unstable_enableCameraPositionClamping(
3667
+ blocks,
3668
+ paddingLeft,
3669
+ paddingTop,
3670
+ paddingRight,
3671
+ paddingBottom,
3672
+ scaledPaddingLeft,
3673
+ scaledPaddingTop,
3674
+ scaledPaddingRight,
3675
+ scaledPaddingBottom
3676
+ );
3677
+ }
3678
+ unstable_disableCameraPositionClamping(block) {
3679
+ const target = block ?? this.get();
3680
+ if (target == null) throw new Error("No scene available.");
3681
+ this.#engine.unstable_disableCameraPositionClamping(target);
3682
+ }
3683
+ unstable_isCameraPositionClampingEnabled(block) {
3684
+ const target = block ?? this.get();
3685
+ if (target == null) throw new Error("No scene available.");
3686
+ return this.#engine.unstable_isCameraPositionClampingEnabled(target);
3687
+ }
3688
+ /**
3689
+ * Continually clamp the camera zoom level to the range
3690
+ * `[minZoomLimit, maxZoomLimit]` while showing the given blocks (with
3691
+ * optional padding).
3692
+ * @experimental
3693
+ */
3694
+ unstable_enableCameraZoomClamping(blocks, minZoomLimit = -1, maxZoomLimit = -1, paddingLeft = 0, paddingTop = 0, paddingRight = 0, paddingBottom = 0) {
3695
+ this.#engine.unstable_enableCameraZoomClamping(
3696
+ blocks,
3697
+ minZoomLimit,
3698
+ maxZoomLimit,
3699
+ paddingLeft,
3700
+ paddingTop,
3701
+ paddingRight,
3702
+ paddingBottom
3703
+ );
3704
+ }
3705
+ unstable_disableCameraZoomClamping(block) {
3706
+ const target = block ?? this.get();
3707
+ if (target == null) throw new Error("No scene available.");
3708
+ this.#engine.unstable_disableCameraZoomClamping(target);
3709
+ }
3710
+ unstable_isCameraZoomClampingEnabled(block) {
3711
+ const target = block ?? this.get();
3712
+ if (target == null) throw new Error("No scene available.");
3713
+ return this.#engine.unstable_isCameraZoomClampingEnabled(target);
3714
+ }
3715
+ /**
3716
+ * Subscribe to zoom-level changes.
3717
+ * @param callback - Called whenever the zoom level changes.
3718
+ * @returns A function that unsubscribes when called.
3719
+ */
3720
+ onZoomLevelChanged(callback) {
3721
+ const id = this.#engine.subscribeToZoomLevel(callback);
3722
+ return () => this.#engine.unsubscribe(id);
3723
+ }
3724
+ /**
3725
+ * Subscribe to active-scene changes.
3726
+ * @param callback - Called whenever the active scene changes.
3727
+ * @returns A function that unsubscribes when called.
3728
+ */
3729
+ onActiveChanged(callback) {
3730
+ const id = this.#engine.subscribeToActiveSceneChange(callback);
3731
+ return () => this.#engine.unsubscribe(id);
3732
+ }
3733
+ };
3734
+ var EditorAPI = class {
3735
+ #engine;
3736
+ constructor(engine) {
3737
+ this.#engine = engine;
3738
+ }
3739
+ getEditMode() {
3740
+ return this.#engine.getEditMode();
3741
+ }
3742
+ setEditMode(mode) {
3743
+ this.#engine.setEditMode(mode);
3744
+ }
3745
+ /**
3746
+ * Returns the JWT string of the currently active license. An empty string
3747
+ * indicates the engine is in Evaluation mode (the trial license created
3748
+ * when init() is called with an empty/null/undefined license). Throws if
3749
+ * the engine has no active license at all.
3750
+ *
3751
+ * Mirrors @cesdk/node's `engine.editor.getActiveLicense()`.
3752
+ * @internal
3753
+ */
3754
+ getActiveLicense() {
3755
+ return this.#engine.getActiveLicense();
3756
+ }
3757
+ /**
3758
+ * Activate (or re-activate) the engine with a license key. Pass an empty
3759
+ * string to drop into Evaluation mode. Mirrors
3760
+ * `@cesdk/node`'s `engine.editor.unlockWithLicense()`.
3761
+ *
3762
+ * Most callers should pass `license` via `CreativeEngine.init({ license })`
3763
+ * — `init` calls this internally. Use this method only to switch licenses
3764
+ * at runtime (e.g. trial → paid upgrade in place).
3765
+ */
3766
+ unlockWithLicense(license) {
3767
+ this.#engine.unlockWithLicense(license);
3768
+ }
3769
+ // Settings
3770
+ getSettingBool(key) {
3771
+ const settingType = this.getSettingType(key);
3772
+ if (settingType !== "Bool") {
3773
+ throw new Error(
3774
+ `Setting '${key}' is of type '${settingType}', not 'Bool'`
3775
+ );
3776
+ }
3777
+ return this.#engine.getSettingBool(key);
3778
+ }
3779
+ setSettingBool(key, value) {
3780
+ this.#engine.setSettingBool(key, value);
3781
+ }
3782
+ getSettingInt(key) {
3783
+ return this.#engine.getSettingInt(key);
3784
+ }
3785
+ setSettingInt(key, value) {
3786
+ this.#engine.setSettingInt(key, value);
3787
+ }
3788
+ getSettingFloat(key) {
3789
+ return this.#engine.getSettingFloat(key);
3790
+ }
3791
+ setSettingFloat(key, value) {
3792
+ this.#engine.setSettingFloat(key, value);
3793
+ }
3794
+ getSettingString(key) {
3795
+ return this.#engine.getSettingString(key);
3796
+ }
3797
+ setSettingString(key, value) {
3798
+ this.#engine.setSettingString(key, value);
3799
+ }
3800
+ getSettingColor(key) {
3801
+ const internalColor = this.#engine.getSettingColor(
3802
+ key
3803
+ );
3804
+ return NativeColorInternal.toColor(internalColor);
3805
+ }
3806
+ setSettingColor(key, value) {
3807
+ const internalColor = NativeColorInternal.fromColor(value);
3808
+ this.#engine.setSettingColor(key, internalColor);
3809
+ }
3810
+ getSettingEnum(key) {
3811
+ return this.#engine.getSettingEnum(key);
3812
+ }
3813
+ setSettingEnum(key, value) {
3814
+ const settingType = this.getSettingType(key);
3815
+ if (settingType !== "Enum") {
3816
+ throw new Error(
3817
+ `Setting '${key}' is of type '${settingType}', not 'Enum'`
3818
+ );
3819
+ }
3820
+ this.#engine.setSettingEnum(key, value);
3821
+ }
3822
+ findAllSettings() {
3823
+ return this.#engine.findAllSettings();
3824
+ }
3825
+ getSettingType(key) {
3826
+ const allSettings = this.#engine.findAllSettings();
3827
+ if (!allSettings.includes(key)) {
3828
+ throw new Error(`Setting '${key}' does not exist`);
3829
+ }
3830
+ return this.#engine.getSettingType(key);
3831
+ }
3832
+ getSettingEnumOptions(key) {
3833
+ return this.#engine.getSettingEnumOptions(key);
3834
+ }
3835
+ /**
3836
+ * Generic setting getter that dispatches to the typed getter based on
3837
+ * `getSettingType`.
3838
+ */
3839
+ getSetting(key) {
3840
+ const settingType = this.getSettingType(key);
3841
+ switch (settingType) {
3842
+ case "Bool":
3843
+ return this.#engine.getSettingBool(key);
3844
+ case "Int":
3845
+ return this.#engine.getSettingInt(key);
3846
+ case "Float":
3847
+ return this.#engine.getSettingFloat(key);
3848
+ case "String":
3849
+ return this.#engine.getSettingString(key);
3850
+ case "Color": {
3851
+ const internalColor = this.#engine.getSettingColor(
3852
+ key
3853
+ );
3854
+ return NativeColorInternal.toColor(internalColor);
3855
+ }
3856
+ case "Enum":
3857
+ return this.#engine.getSettingEnum(key);
3858
+ default:
3859
+ throw new Error(
3860
+ `Unknown setting type for key '${key}': ${settingType}`
3861
+ );
3862
+ }
3863
+ }
3864
+ /**
3865
+ * Generic setting setter — dispatches to the typed setter via
3866
+ * `getSettingType`.
3867
+ */
3868
+ setSetting(key, value) {
3869
+ const settingType = this.getSettingType(key);
3870
+ switch (settingType) {
3871
+ case "Bool":
3872
+ this.#engine.setSettingBool(key, value);
3873
+ return;
3874
+ case "Int":
3875
+ this.#engine.setSettingInt(key, value);
3876
+ return;
3877
+ case "Float":
3878
+ this.#engine.setSettingFloat(key, value);
3879
+ return;
3880
+ case "String":
3881
+ this.#engine.setSettingString(key, value);
3882
+ return;
3883
+ case "Color": {
3884
+ const internalColor = NativeColorInternal.fromColor(value);
3885
+ this.#engine.setSettingColor(key, internalColor);
3886
+ return;
3887
+ }
3888
+ case "Enum":
3889
+ this.#engine.setSettingEnum(key, value);
3890
+ return;
3891
+ default:
3892
+ throw new Error(
3893
+ `Unknown setting type for key '${key}': ${settingType}`
3894
+ );
3895
+ }
3896
+ }
3897
+ /** Get the font metrics for a font URI. */
3898
+ getFontMetrics(fontFileUri) {
3899
+ return this.#engine.getFontMetrics(fontFileUri);
3900
+ }
3901
+ /**
3902
+ * Set one or more rules limiting how far blocks may be moved outside their
3903
+ * parent page during user interactions.
3904
+ */
3905
+ setMovementConstraint(rules) {
3906
+ const list = Array.isArray(rules) ? rules : [rules];
3907
+ for (const rule of list) {
3908
+ const targets = "block" in rule ? [rule.block] : "blockType" in rule ? [rule.blockType] : [];
3909
+ this.#engine.setMovementConstraint(targets, rule.overshoot);
3910
+ }
3911
+ }
3912
+ /**
3913
+ * Get the effective movement constraint for a block (most specific rule
3914
+ * wins: block > parent page > blockType > scene-wide). Returns `null`
3915
+ * when unconstrained.
3916
+ */
3917
+ getMovementConstraint(id) {
3918
+ const value = this.#engine.getMovementConstraint(id);
3919
+ return value < 0 ? null : { overshoot: value };
3920
+ }
3921
+ /**
3922
+ * Remove movement constraints. No argument removes the scene-wide default.
3923
+ * `{ block }` / `{ blockType }` (or array) removes the matching scope(s).
3924
+ */
3925
+ removeMovementConstraint(scopes) {
3926
+ if (scopes === void 0) {
3927
+ this.#engine.removeMovementConstraint([]);
3928
+ return;
3929
+ }
3930
+ const list = Array.isArray(scopes) ? scopes : [scopes];
3931
+ if (list.length === 0) return;
3932
+ const targets = list.map(
3933
+ (scope) => "block" in scope ? scope.block : scope.blockType
3934
+ );
3935
+ this.#engine.removeMovementConstraint(targets);
3936
+ }
3937
+ // Cursor
3938
+ getCursorType() {
3939
+ return this.#engine.getCursorType();
3940
+ }
3941
+ getCursorRotation() {
3942
+ return this.#engine.getCursorRotation();
3943
+ }
3944
+ // Safe area insets
3945
+ setSafeAreaInsets(insets) {
3946
+ this.#engine.setSafeAreaInsets(
3947
+ insets.left,
3948
+ insets.top,
3949
+ insets.right,
3950
+ insets.bottom
3951
+ );
3952
+ }
3953
+ getSafeAreaInsets() {
3954
+ return this.#engine.getSafeAreaInsets();
3955
+ }
3956
+ // Memory
3957
+ getUsedMemory() {
3958
+ return this.#engine.getUsedMemory();
3959
+ }
3960
+ getAvailableMemory() {
3961
+ return this.#engine.getAvailableMemory();
3962
+ }
3963
+ // Color
3964
+ convertColorToColorSpace(color, colorSpace) {
3965
+ const internalColor = NativeColorInternal.fromColor(color);
3966
+ let colorSpaceNum;
3967
+ if (colorSpace === "sRGB") {
3968
+ colorSpaceNum = 0 /* sRGB */;
3969
+ } else if (colorSpace === "CMYK") {
3970
+ colorSpaceNum = 1 /* CMYK */;
3971
+ } else if (colorSpace === "SpotColor") {
3972
+ colorSpaceNum = 2 /* SpotColor */;
3973
+ } else {
3974
+ colorSpaceNum = parseInt(colorSpace, 10);
3975
+ if (Number.isNaN(colorSpaceNum)) {
3976
+ throw new Error(`Unknown color space: ${colorSpace}`);
3977
+ }
3978
+ }
3979
+ const resultInternal = this.#engine.convertColorToColorSpace(
3980
+ internalColor,
3981
+ colorSpaceNum
3982
+ );
3983
+ return NativeColorInternal.toColor(resultInternal);
3984
+ }
3985
+ supportsP3() {
3986
+ return this.#engine.supportsP3();
3987
+ }
3988
+ // URI
3989
+ getAbsoluteURI(relativeUri) {
3990
+ return this.#engine.getAbsoluteURI(relativeUri);
3991
+ }
3992
+ getMimeType(uri) {
3993
+ return this.#engine.getMimeType(uri);
3994
+ }
3995
+ defaultURIResolver(relativePath) {
3996
+ return this.#engine.defaultURIResolver(relativePath);
3997
+ }
3998
+ /**
3999
+ * Register a synchronous URI resolver. Matches `@cesdk/node`'s
4000
+ * `(uri, defaultResolver) => string` signature
4001
+ * (`bindings/wasm/js_web/src/EditorAPI.ts`). The
4002
+ * `defaultResolver` argument is the engine's built-in resolver — call
4003
+ * it to delegate the lookup chain.
4004
+ *
4005
+ * @public
4006
+ */
4007
+ setURIResolver(resolver) {
4008
+ if (resolver == null) {
4009
+ this.#engine.setURIResolver(
4010
+ (uri) => this.defaultURIResolver(uri)
4011
+ );
4012
+ return;
4013
+ }
4014
+ const wrapped = (uri) => {
4015
+ const defaultFn = (u) => this.defaultURIResolver(u);
4016
+ return resolver(uri, defaultFn);
4017
+ };
4018
+ this.#engine.setURIResolver(wrapped);
4019
+ }
4020
+ /**
4021
+ * Register an asynchronous URI resolver. Matches `@cesdk/node`'s
4022
+ * `(uri, defaultResolver) => Promise<string> | string` signature
4023
+ * (`bindings/wasm/js_web/src/EditorAPI.ts`). Bridges to the
4024
+ * native callback-style binding by adapting the returned Promise.
4025
+ *
4026
+ * Pass `null` to restore the default resolver.
4027
+ *
4028
+ * @public
4029
+ */
4030
+ setURIResolverAsync(resolver) {
4031
+ if (resolver == null) {
4032
+ this.#engine.setURIResolverAsync(
4033
+ (uri, cb) => {
4034
+ this.getAbsoluteURI(uri).then(
4035
+ (resolved) => cb(resolved),
4036
+ (err) => cb(err instanceof Error ? err : new Error(String(err)))
4037
+ );
4038
+ }
4039
+ );
4040
+ return;
4041
+ }
4042
+ const defaultFn = async (uri) => this.getAbsoluteURI(uri);
4043
+ const adapter = (uri, cb) => {
4044
+ try {
4045
+ const out = resolver(uri, defaultFn);
4046
+ Promise.resolve(out).then(
4047
+ (resolved) => cb(resolved),
4048
+ (err) => cb(err instanceof Error ? err : new Error(String(err)))
4049
+ );
4050
+ } catch (err) {
4051
+ cb(err instanceof Error ? err : new Error(String(err)));
4052
+ }
4053
+ };
4054
+ this.#engine.setURIResolverAsync(adapter);
4055
+ }
4056
+ relocateResource(currentUri, newUri) {
4057
+ this.#engine.relocateResource(currentUri, newUri);
4058
+ }
4059
+ findAllMediaURIs() {
4060
+ return this.#engine.findAllMediaURIs();
4061
+ }
4062
+ /**
4063
+ * List all transient (in-engine) resources — typically `buffer://` URIs
4064
+ * created by importers (psd, pdf, idml) before they're uploaded to a
4065
+ * permanent location. Each entry is `{ uri, size }`.
4066
+ *
4067
+ * Note: WASM uses `URL` as the key name; consumers that target both
4068
+ * bindings should accept either (see e.g. psd-importer's
4069
+ * `transient-resource-relocation.test.ts`).
4070
+ */
4071
+ findAllTransientResources() {
4072
+ return this.#engine.findAllTransientResources();
4073
+ }
4074
+ // Spot colors
4075
+ findAllSpotColors() {
4076
+ return this.#engine.findAllSpotColors();
4077
+ }
4078
+ getSpotColorRGBA(name) {
4079
+ const result = this.#engine.getSpotColorRGB(name);
4080
+ const tuple = Array.isArray(result) ? result.slice(0, 4) : [
4081
+ result.r,
4082
+ result.g,
4083
+ result.b,
4084
+ result.a
4085
+ ];
4086
+ const rgba = tuple;
4087
+ rgba.r = tuple[0];
4088
+ rgba.g = tuple[1];
4089
+ rgba.b = tuple[2];
4090
+ rgba.a = tuple[3];
4091
+ return rgba;
4092
+ }
4093
+ getSpotColorCMYK(name) {
4094
+ const result = this.#engine.getSpotColorCMYK(name);
4095
+ const tuple = Array.isArray(result) ? result.slice(0, 4) : [
4096
+ result.c,
4097
+ result.m,
4098
+ result.y,
4099
+ result.k
4100
+ ];
4101
+ const cmyk = tuple;
4102
+ cmyk.c = tuple[0];
4103
+ cmyk.m = tuple[1];
4104
+ cmyk.y = tuple[2];
4105
+ cmyk.k = tuple[3];
4106
+ cmyk.tint = !Array.isArray(result) && result.tint !== void 0 ? result.tint : 1;
4107
+ return cmyk;
4108
+ }
4109
+ setSpotColorRGB(name, r, g, b) {
4110
+ this.#engine.setSpotColorRGB(name, r, g, b);
4111
+ }
4112
+ setSpotColorCMYK(name, c, m, y, k) {
4113
+ this.#engine.setSpotColorCMYK(name, c, m, y, k);
4114
+ }
4115
+ removeSpotColor(name) {
4116
+ this.#engine.removeSpotColor(name);
4117
+ }
4118
+ // Role and scope - WASM EditorAPI methods
4119
+ getRole() {
4120
+ return this.#engine.getRole();
4121
+ }
4122
+ setRole(role) {
4123
+ this.#engine.setRole(role);
4124
+ }
4125
+ /**
4126
+ * Start the engine's analytics tracking pipeline. Mirrors
4127
+ * `@cesdk/node`'s `editor.startTracking(license, userId)`; invoked
4128
+ * automatically from {@link CreativeEngine.init} when `license` and/or
4129
+ * `userId` are passed in the Configuration. Safe to call multiple times.
4130
+ *
4131
+ * The C++ binding accepts a third `deviceId` argument that the engine
4132
+ * itself uses for tracking-pipeline disambiguation; WASM always passes
4133
+ * `''` (see `bindings/wasm/js_web/src/EditorAPI.ts`) — we
4134
+ * mirror that here so the no-args / "engine picks a default device id"
4135
+ * path is honored.
4136
+ *
4137
+ * @public
4138
+ */
4139
+ startTracking(license, userId, deviceId = "") {
4140
+ this.#engine.startTracking(license, userId, deviceId);
4141
+ }
4142
+ findAllScopes() {
4143
+ return this.#engine.findAllScopes();
4144
+ }
4145
+ getGlobalScope(scope) {
4146
+ return this.#engine.getGlobalScope(scope);
4147
+ }
4148
+ setGlobalScope(scope, value) {
4149
+ this.#engine.setGlobalScope(scope, value);
4150
+ }
4151
+ // History management (matches WASM EditorAPI)
4152
+ createHistory() {
4153
+ return this.#engine.createHistory();
4154
+ }
4155
+ destroyHistory(history) {
4156
+ this.#engine.destroyHistory(history);
4157
+ }
4158
+ getActiveHistory() {
4159
+ return this.#engine.getActiveHistory();
4160
+ }
4161
+ setActiveHistory(history) {
4162
+ this.#engine.setActiveHistory(history);
4163
+ }
4164
+ /**
4165
+ * Undo one step in the active history if an undo step is available.
4166
+ * Use {@link canUndo} beforehand to determine whether a step is available.
4167
+ */
4168
+ undo() {
4169
+ this.#engine.undo();
4170
+ }
4171
+ /**
4172
+ * Redo one step in the active history if a redo step is available.
4173
+ * Use {@link canRedo} beforehand to determine whether a step is available.
4174
+ */
4175
+ redo() {
4176
+ this.#engine.redo();
4177
+ }
4178
+ canUndo() {
4179
+ return this.#engine.canUndo();
4180
+ }
4181
+ canRedo() {
4182
+ return this.#engine.canRedo();
4183
+ }
4184
+ addUndoStep() {
4185
+ this.#engine.addUndoStep();
4186
+ }
4187
+ removeUndoStep() {
4188
+ this.#engine.removeUndoStep();
4189
+ }
4190
+ /**
4191
+ * Subscribe to editor-state changes.
4192
+ * @param callback - Called whenever the editor state changes.
4193
+ * @returns A function that unsubscribes when called.
4194
+ */
4195
+ onStateChanged(callback) {
4196
+ const id = this.#engine.subscribeToEditorState(callback);
4197
+ return () => this.#engine.unsubscribe(id);
4198
+ }
4199
+ /**
4200
+ * Subscribe to editor settings changes. Matches WASM's
4201
+ * `engine.editor.onSettingsChanged(callback)`. The native C++ side
4202
+ * implements this via `subscribeToSettings`; this wrapper exposes it on
4203
+ * the editor API so portable consumers (settings-reactive UI) work on
4204
+ * both bindings.
4205
+ * @returns A function that unsubscribes when called.
4206
+ */
4207
+ onSettingsChanged(callback) {
4208
+ const id = this.#engine.subscribeToSettings(callback);
4209
+ return () => this.#engine.unsubscribe(id);
4210
+ }
4211
+ /**
4212
+ * Subscribe to history updates.
4213
+ * @deprecated Use {@link onHistoryUpdatedWithKind} instead, which also
4214
+ * reports the kind of update.
4215
+ * @param callback - Called when the undo/redo history changes.
4216
+ * @returns A function that unsubscribes when called.
4217
+ */
4218
+ onHistoryUpdated(callback) {
4219
+ return this.onHistoryUpdatedWithKind(() => callback());
4220
+ }
4221
+ /**
4222
+ * Subscribe to history updates. The callback receives a `HistoryUpdate`
4223
+ * describing what kind of update happened (matches the node wasm
4224
+ * binding's `EditorAPI` surface) so consumers can distinguish a
4225
+ * snapshot mutation from an `Activated` event triggered by
4226
+ * `setActiveHistory` — important for save-button logic that should NOT
4227
+ * fire on activation alone.
4228
+ * @param callback - Called with the kind of update.
4229
+ * @returns A function that unsubscribes when called.
4230
+ */
4231
+ onHistoryUpdatedWithKind(callback) {
4232
+ const id = this.#engine.subscribeToHistoryWithKind((value) => {
4233
+ callback(value === 1 ? "Activated" : "Updated");
4234
+ });
4235
+ return () => this.#engine.unsubscribe(id);
4236
+ }
4237
+ /**
4238
+ * Subscribe to role changes.
4239
+ * @param callback - Called with the new role when it changes.
4240
+ * @returns A function that unsubscribes when called.
4241
+ */
4242
+ onRoleChanged(callback) {
4243
+ const id = this.#engine.subscribeToRoleChange(callback);
4244
+ return () => this.#engine.unsubscribe(id);
4245
+ }
4246
+ // Buffer management (matching WASM/Android EditorAPI)
4247
+ /**
4248
+ * Create a new buffer and return its URI.
4249
+ * @returns A URI to identify the created buffer (buffer://N format).
4250
+ */
4251
+ createBuffer() {
4252
+ return this.#engine.createBuffer();
4253
+ }
4254
+ /**
4255
+ * Destroy a buffer and free its resources.
4256
+ * @param uri - The URI of the buffer to destroy.
4257
+ */
4258
+ destroyBuffer(uri) {
4259
+ this.#engine.destroyBuffer(uri);
4260
+ }
4261
+ /**
4262
+ * Set the data of a buffer at a given offset.
4263
+ * @param uri - The URI of the buffer to update.
4264
+ * @param offset - The offset in bytes at which to start writing.
4265
+ * @param data - The data to write.
4266
+ */
4267
+ setBufferData(uri, offset, data) {
4268
+ this.#engine.setBufferData(uri, offset, data);
4269
+ }
4270
+ /**
4271
+ * Get the data of a buffer at a given offset.
4272
+ * @param uri - The URI of the buffer to query.
4273
+ * @param offset - The offset in bytes at which to start reading.
4274
+ * @param length - The number of bytes to read.
4275
+ * @returns The data at the given offset.
4276
+ */
4277
+ getBufferData(uri, offset, length) {
4278
+ return this.#engine.getBufferData(uri, offset, length);
4279
+ }
4280
+ /**
4281
+ * Get the length of a buffer.
4282
+ * @param uri - The URI of the buffer to query.
4283
+ * @returns The length of the buffer in bytes.
4284
+ */
4285
+ getBufferLength(uri) {
4286
+ return this.#engine.getBufferLength(uri);
4287
+ }
4288
+ /**
4289
+ * Resize a buffer. Truncates if `length` is smaller than the current
4290
+ * size; zero-extends if larger. Matches the WASM `editor.setBufferLength`
4291
+ * shape used by psd/pdf/idml importers to pre-allocate a buffer before
4292
+ * writing data with `setBufferData`.
4293
+ */
4294
+ setBufferLength(uri, length) {
4295
+ this.#engine.setBufferLength(uri, length);
4296
+ }
4297
+ };
4298
+ var EventAPI = class {
4299
+ constructor(engine) {
4300
+ /**
4301
+ * Subscribe to block lifecycle events.
4302
+ * Events are bundled and delivered at the end of each engine update cycle.
4303
+ * @param blocks - List of blocks to filter events by. If empty, events for all blocks are sent.
4304
+ * @param callback - Function called with bundled events.
4305
+ * @returns A method to unsubscribe from the events.
4306
+ */
4307
+ this.subscribe = (blocks, callback) => {
4308
+ const safeCallback = (events) => {
4309
+ try {
4310
+ callback(events);
4311
+ } catch (err) {
4312
+ console.error("[cesdk_native] block-event subscriber threw:", err);
4313
+ }
4314
+ };
4315
+ const subscription = this.#engine.subscribe(blocks, safeCallback);
4316
+ return () => {
4317
+ this.#engine.unsubscribe(subscription);
4318
+ };
4319
+ };
4320
+ this.#engine = engine;
4321
+ }
4322
+ #engine;
4323
+ };
4324
+ var AssetAPI = class {
4325
+ #engine;
4326
+ constructor(engine) {
4327
+ this.#engine = engine;
4328
+ }
4329
+ /**
4330
+ * Add a custom asset source with callbacks.
4331
+ *
4332
+ * This allows you to provide custom implementations for finding, fetching,
4333
+ * and applying assets from any data source.
4334
+ *
4335
+ * @param source - The asset source configuration with callbacks.
4336
+ */
4337
+ addSource(source) {
4338
+ this.#engine.addAssetSource({
4339
+ id: source.id,
4340
+ findAssets: (query, resultCb) => {
4341
+ Promise.resolve(source.findAssets(query)).then(
4342
+ (result) => resultCb(
4343
+ null,
4344
+ result ?? { assets: [], currentPage: 0, total: 0, nextPage: -1 }
4345
+ )
4346
+ ).catch((err) => resultCb(err));
4347
+ },
4348
+ fetchAsset: source.fetchAsset ? (id, params, resultCb) => {
4349
+ Promise.resolve(source.fetchAsset(id, params)).then((result) => resultCb(null, result)).catch((err) => resultCb(err));
4350
+ } : void 0,
4351
+ getGroups: source.getGroups ? (resultCb) => {
4352
+ Promise.resolve(source.getGroups()).then((result) => resultCb(null, result)).catch((err) => resultCb(err));
4353
+ } : void 0,
4354
+ credits: source.credits,
4355
+ license: source.license,
4356
+ getSupportedMimeTypes: source.getSupportedMimeTypes,
4357
+ applyAsset: source.applyAsset ? (asset, resultCb) => {
4358
+ Promise.resolve(source.applyAsset(asset)).then((result) => resultCb(null, result)).catch((err) => resultCb(err));
4359
+ } : void 0,
4360
+ applyAssetToBlock: source.applyAssetToBlock ? (asset, block, resultCb) => {
4361
+ Promise.resolve(source.applyAssetToBlock(asset, block)).then(() => resultCb(null)).catch((err) => resultCb(err));
4362
+ } : void 0,
4363
+ applyAssetProperty: source.applyAssetProperty ? (asset, property, resultCb) => {
4364
+ Promise.resolve(source.applyAssetProperty(asset, property)).then(() => resultCb(null)).catch((err) => resultCb(err));
4365
+ } : void 0,
4366
+ addAsset: source.addAsset,
4367
+ removeAsset: source.removeAsset
4368
+ });
4369
+ }
4370
+ /**
4371
+ * Add a local asset source.
4372
+ *
4373
+ * Mirrors `@cesdk/node`'s 4-argument shape
4374
+ * (`bindings/wasm/js_web/src/AssetAPI.ts`): when `applyAsset`
4375
+ * or `applyAssetToBlock` is supplied, we wrap the local source as a
4376
+ * synthetic `AssetSource` via {@link addSource} so the callbacks are
4377
+ * honored. Without callbacks we go through the cheap `addLocalSource`
4378
+ * fast path.
4379
+ *
4380
+ * @param id - Unique identifier for the asset source.
4381
+ * @param supportedMimeTypes - The mime types of assets that are allowed to be added.
4382
+ * @param applyAsset - Optional callback invoked when the asset is applied to a new block.
4383
+ * @param applyAssetToBlock - Optional callback invoked when the asset is applied to an existing block.
4384
+ */
4385
+ addLocalSource(id, supportedMimeTypes, applyAsset, applyAssetToBlock) {
4386
+ if (applyAsset == null && applyAssetToBlock == null) {
4387
+ this.#engine.addLocalAssetSource(id, supportedMimeTypes ?? []);
4388
+ return;
4389
+ }
4390
+ this.#engine.addLocalAssetSource(id, supportedMimeTypes ?? []);
4391
+ if (applyAsset != null) {
4392
+ this.registerApplyMiddleware(id, async (asset, next) => {
4393
+ const result = await applyAsset(asset);
4394
+ if (result !== void 0) return result;
4395
+ return next(asset);
4396
+ });
4397
+ }
4398
+ if (applyAssetToBlock != null) {
4399
+ this.registerApplyToBlockMiddleware(id, async (asset, block, next) => {
4400
+ await applyAssetToBlock(asset, block);
4401
+ await next(asset, block);
4402
+ });
4403
+ }
4404
+ }
4405
+ /**
4406
+ * Creates a new local asset source from a JSON string.
4407
+ * @param contentJSON - The JSON string containing the asset definitions.
4408
+ * @param basePath - Optional base path for resolving relative URLs.
4409
+ * @param options - Optional configuration with matcher patterns.
4410
+ * @returns The ID of the created asset source.
4411
+ */
4412
+ addLocalAssetSourceFromJSONString(contentJSON, basePath, options) {
4413
+ try {
4414
+ const sourceId = this.#engine.addLocalAssetSourceFromJSONString(
4415
+ contentJSON,
4416
+ basePath,
4417
+ options?.matcher
4418
+ );
4419
+ return Promise.resolve(sourceId);
4420
+ } catch (e) {
4421
+ return Promise.reject(e);
4422
+ }
4423
+ }
4424
+ /**
4425
+ * Creates a new local asset source from a JSON URI.
4426
+ * @param contentURI - The URI for the JSON file to load.
4427
+ * @param options - Optional configuration with matcher patterns.
4428
+ * @returns The ID of the created asset source.
4429
+ */
4430
+ addLocalAssetSourceFromJSONURI(contentURI, options) {
4431
+ return this.#engine.addLocalAssetSourceFromJSONURI(
4432
+ contentURI,
4433
+ options?.matcher
4434
+ );
4435
+ }
4436
+ /**
4437
+ * Remove a registered asset source.
4438
+ * @param id - The ID of the asset source to remove.
4439
+ */
4440
+ removeSource(id) {
4441
+ this.#engine.removeAssetSource(id);
4442
+ }
4443
+ /**
4444
+ * Get all registered asset source IDs.
4445
+ * @returns A list with the IDs of all registered asset sources.
4446
+ */
4447
+ findAllSources() {
4448
+ return this.#engine.findAllAssetSources();
4449
+ }
4450
+ /**
4451
+ * Search for assets in a specific source.
4452
+ *
4453
+ * @param sourceId - The ID of the asset source.
4454
+ * @param query - Query options to filter and sort the search results.
4455
+ * @returns Promise resolving to paginated search results.
4456
+ */
4457
+ async findAssets(sourceId, query) {
4458
+ const q = query;
4459
+ const tagsRaw = q?.tags;
4460
+ const tags = Array.isArray(tagsRaw) ? tagsRaw : typeof tagsRaw === "string" ? [tagsRaw] : tagsRaw && typeof tagsRaw === "object" ? tagsRaw : [];
4461
+ const normalizedQuery = {
4462
+ perPage: q?.perPage ?? 0,
4463
+ page: q?.page ?? 0,
4464
+ query: q?.query ?? "",
4465
+ tags,
4466
+ groups: q?.groups ?? [],
4467
+ excludeGroups: q?.excludeGroups ?? [],
4468
+ locale: q?.locale ?? "",
4469
+ sortingOrder: q?.sortingOrder ?? "None",
4470
+ sortKey: q?.sortKey ?? "",
4471
+ sortActiveFirst: q?.sortActiveFirst ?? false,
4472
+ filter: q?.filter ?? []
4473
+ };
4474
+ const result = await this.#engine.findAssetSourceAssets(
4475
+ sourceId,
4476
+ normalizedQuery
4477
+ );
4478
+ const r = result;
4479
+ return {
4480
+ ...result,
4481
+ nextPage: r.nextPage === -1 ? void 0 : r.nextPage
4482
+ };
4483
+ }
4484
+ /**
4485
+ * Fetch a specific asset by id from an asset source.
4486
+ * Returns `null` when the asset is not found.
4487
+ */
4488
+ async fetchAsset(sourceId, assetId, params = {}) {
4489
+ const fetchParams = { locale: params.locale ?? "" };
4490
+ return this.#engine.fetchAssetSourceAsset(sourceId, assetId, fetchParams);
4491
+ }
4492
+ /**
4493
+ * Get available asset groups from a source.
4494
+ * @param id - The ID of the asset source.
4495
+ * @returns Promise resolving to list of available group names.
4496
+ */
4497
+ async getGroups(id) {
4498
+ return this.#engine.getAssetSourceGroups(id);
4499
+ }
4500
+ /**
4501
+ * Get license information for an asset source.
4502
+ * @param sourceId - The ID of the asset source.
4503
+ * @returns The asset source's license info.
4504
+ */
4505
+ getLicense(sourceId) {
4506
+ const license = this.#engine.getAssetSourceLicense(sourceId);
4507
+ if (!license || !license.name && !license.url) {
4508
+ return void 0;
4509
+ }
4510
+ return { name: license.name, url: license.url || void 0 };
4511
+ }
4512
+ /**
4513
+ * Get attribution credits for an asset source.
4514
+ * @param sourceId - The ID of the asset source.
4515
+ * @returns The asset source's credits info.
4516
+ */
4517
+ getCredits(sourceId) {
4518
+ const credits = this.#engine.getAssetSourceCredits(sourceId);
4519
+ if (!credits || !credits.name && !credits.url) {
4520
+ return void 0;
4521
+ }
4522
+ return { name: credits.name, url: credits.url || void 0 };
4523
+ }
4524
+ /**
4525
+ * Get supported MIME types for an asset source.
4526
+ * @param sourceId - The ID of the asset source.
4527
+ * @returns Array of supported MIME type strings.
4528
+ */
4529
+ getSupportedMimeTypes(sourceId) {
4530
+ return this.#engine.getAssetSourceSupportedMimeTypes(sourceId);
4531
+ }
4532
+ /**
4533
+ * Add an asset to a local asset source.
4534
+ * @param sourceId - The local asset source ID.
4535
+ * @param asset - The asset definition to add.
4536
+ */
4537
+ addAssetToSource(sourceId, asset) {
4538
+ this.#engine.addAssetToSource(sourceId, asset);
4539
+ }
4540
+ /**
4541
+ * Remove an asset from a local asset source.
4542
+ * @param sourceId - The ID of the local asset source.
4543
+ * @param assetId - The ID of the asset to remove.
4544
+ */
4545
+ removeAssetFromSource(sourceId, assetId) {
4546
+ this.#engine.removeAssetFromSource(sourceId, assetId);
4547
+ }
4548
+ /**
4549
+ * Notify the engine that an asset source's contents have changed.
4550
+ * @param sourceID - The asset source whose contents changed.
4551
+ */
4552
+ assetSourceContentsChanged(sourceID) {
4553
+ this.#engine.assetSourceContentsChanged(sourceID);
4554
+ }
4555
+ /**
4556
+ * Subscribe to "asset source added" events.
4557
+ * @param callback - Called with the source ID whenever a new asset source is registered.
4558
+ * @returns A function that unsubscribes when called.
4559
+ */
4560
+ onAssetSourceAdded(callback) {
4561
+ const id = this.#engine.subscribeToAssetSourceAdded(callback);
4562
+ return () => this.#engine.unsubscribe(id);
4563
+ }
4564
+ /**
4565
+ * Subscribe to "asset source removed" events.
4566
+ * @param callback - Called with the source ID whenever an asset source is removed.
4567
+ * @returns A function that unsubscribes when called.
4568
+ */
4569
+ onAssetSourceRemoved(callback) {
4570
+ const id = this.#engine.subscribeToAssetSourceRemoved(callback);
4571
+ return () => this.#engine.unsubscribe(id);
4572
+ }
4573
+ /**
4574
+ * Subscribe to "asset source updated" events.
4575
+ * @param callback - Called with the source ID whenever an asset source's contents change.
4576
+ * @returns A function that unsubscribes when called.
4577
+ */
4578
+ onAssetSourceUpdated(callback) {
4579
+ const id = this.#engine.subscribeToAssetSourceUpdated(callback);
4580
+ return () => this.#engine.unsubscribe(id);
4581
+ }
4582
+ // -------------------------------------------------------------------------
4583
+ // Apply-asset surface (parity with @cesdk/node's AssetAPI:955-1195).
4584
+ //
4585
+ // The C++ binding already exposes the four primitives (`applyAssetSourceAsset`,
4586
+ // `applyAssetSourceAssetToBlock`, `applyAssetSourceProperty`,
4587
+ // `defaultApplyAsset`, `defaultApplyAssetToBlock`) — we only need to bridge
4588
+ // them through a TS-level middleware chain so registerApplyMiddleware /
4589
+ // registerApplyToBlockMiddleware work.
4590
+ // -------------------------------------------------------------------------
4591
+ #applyMiddleware = /* @__PURE__ */ new Map();
4592
+ #applyToBlockMiddleware = /* @__PURE__ */ new Map();
4593
+ /**
4594
+ * Apply an asset to the current scene. Honors any middleware registered
4595
+ * via {@link registerApplyMiddleware} and falls back to
4596
+ * {@link defaultApplyAsset}.
4597
+ *
4598
+ * @public
4599
+ */
4600
+ async apply(sourceId, asset) {
4601
+ const tail = (a) => this.defaultApplyAsset(a);
4602
+ const middlewares = this.#applyMiddleware.get(sourceId) ?? [];
4603
+ let next = tail;
4604
+ for (let i = middlewares.length - 1; i >= 0; i--) {
4605
+ const mw = middlewares[i];
4606
+ const prev = next;
4607
+ next = (a) => mw(a, prev);
4608
+ }
4609
+ return next(asset);
4610
+ }
4611
+ /**
4612
+ * Apply an asset to a specific block. Honors any middleware registered
4613
+ * via {@link registerApplyToBlockMiddleware} and falls back to
4614
+ * {@link defaultApplyAssetToBlock}.
4615
+ *
4616
+ * @public
4617
+ */
4618
+ async applyToBlock(sourceId, asset, block) {
4619
+ const tail = (a, b) => this.defaultApplyAssetToBlock(a, b);
4620
+ const middlewares = this.#applyToBlockMiddleware.get(sourceId) ?? [];
4621
+ let next = tail;
4622
+ for (let i = middlewares.length - 1; i >= 0; i--) {
4623
+ const mw = middlewares[i];
4624
+ const prev = next;
4625
+ next = (a, b) => mw(a, b, prev);
4626
+ }
4627
+ return next(asset, block);
4628
+ }
4629
+ /**
4630
+ * Apply a property change from an asset payload.
4631
+ *
4632
+ * @public
4633
+ */
4634
+ async applyProperty(sourceId, asset, property) {
4635
+ return this.#engine.applyAssetSourceProperty(sourceId, asset, property);
4636
+ }
4637
+ /**
4638
+ * Default apply-asset implementation (no middleware). Calls the engine's
4639
+ * built-in apply logic; used as the tail of the middleware chain in
4640
+ * {@link apply}.
4641
+ *
4642
+ * @public
4643
+ */
4644
+ async defaultApplyAsset(asset) {
4645
+ const result = await this.#engine.defaultApplyAsset(asset);
4646
+ return result === -1 || result == null ? void 0 : result;
4647
+ }
4648
+ /**
4649
+ * Default apply-to-block implementation (no middleware). Used as the
4650
+ * tail of the middleware chain in {@link applyToBlock}.
4651
+ *
4652
+ * @public
4653
+ */
4654
+ async defaultApplyAssetToBlock(asset, block) {
4655
+ await this.#engine.defaultApplyAssetToBlock(asset, block);
4656
+ }
4657
+ /**
4658
+ * Register a middleware in the apply-asset chain for a source.
4659
+ *
4660
+ * @param sourceId - The source to install the middleware on.
4661
+ * @param middleware - Callable that may consume the asset, defer to
4662
+ * the next middleware, or short-circuit. Returns the created block id
4663
+ * (or undefined to fall through).
4664
+ * @public
4665
+ */
4666
+ registerApplyMiddleware(sourceId, middleware) {
4667
+ const arr = this.#applyMiddleware.get(sourceId) ?? [];
4668
+ arr.push(middleware);
4669
+ this.#applyMiddleware.set(sourceId, arr);
4670
+ }
4671
+ /**
4672
+ * Register a middleware in the apply-asset-to-block chain for a source.
4673
+ *
4674
+ * @public
4675
+ */
4676
+ registerApplyToBlockMiddleware(sourceId, middleware) {
4677
+ const arr = this.#applyToBlockMiddleware.get(sourceId) ?? [];
4678
+ arr.push(middleware);
4679
+ this.#applyToBlockMiddleware.set(sourceId, arr);
4680
+ }
4681
+ /**
4682
+ * Whether the named source supports `addAssetToSource` /
4683
+ * `removeAssetFromSource`. Local sources return `true` by default; custom
4684
+ * sources return whatever they declared via `canManageAssets` in their
4685
+ * registration object.
4686
+ *
4687
+ * @public
4688
+ */
4689
+ canManageAssets(sourceId) {
4690
+ try {
4691
+ this.#engine.removeAssetFromSource(
4692
+ sourceId,
4693
+ "__cesdk_probe_does_not_exist__"
4694
+ );
4695
+ return true;
4696
+ } catch {
4697
+ return false;
4698
+ }
4699
+ }
4700
+ };
4701
+ var _version = true ? "1.77.0-nightly.20260610" : "0.0.0";
4702
+ var CreativeEngine = class _CreativeEngine {
4703
+ constructor(engine) {
4704
+ this.version = _CreativeEngine.version;
4705
+ this.#updateInterval = null;
4706
+ this.#baseURL = "";
4707
+ this.#engine = engine;
4708
+ this.asset = new AssetAPI(engine);
4709
+ this.block = new BlockAPI(engine);
4710
+ this.editor = new EditorAPI(engine);
4711
+ this.event = new EventAPI(engine);
4712
+ this.scene = new SceneAPI(engine);
4713
+ this.variable = new VariableAPI(engine);
4714
+ this.#startUpdateLoop();
4715
+ }
4716
+ static {
4717
+ this.version = _version;
4718
+ }
4719
+ #engine;
4720
+ #updateInterval;
4721
+ #baseURL;
4722
+ #startUpdateLoop() {
4723
+ if (this.#updateInterval) return;
4724
+ const updateLoop = () => {
4725
+ let needsUpdate = false;
4726
+ if (this.#engine) {
4727
+ needsUpdate = this.update();
4728
+ }
4729
+ this.#updateInterval = needsUpdate ? setImmediate(updateLoop) : setTimeout(updateLoop, 16);
4730
+ };
4731
+ this.#updateInterval = setImmediate(updateLoop);
4732
+ }
4733
+ #stopUpdateLoop() {
4734
+ if (this.#updateInterval) {
4735
+ clearTimeout(this.#updateInterval);
4736
+ clearImmediate(this.#updateInterval);
4737
+ this.#updateInterval = null;
4738
+ }
4739
+ }
4740
+ /**
4741
+ * Update the engine's internal state
4742
+ * @returns Whether the engine needed an update.
4743
+ */
4744
+ update() {
4745
+ return this.#engine.update();
4746
+ }
4747
+ /**
4748
+ * Dispose the engine and clean up all resources
4749
+ */
4750
+ dispose() {
4751
+ this.#stopUpdateLoop();
4752
+ this.#engine.dispose();
4753
+ this.#engine = null;
4754
+ this.asset = null;
4755
+ this.block = null;
4756
+ this.editor = null;
4757
+ this.event = null;
4758
+ this.scene = null;
4759
+ this.variable = null;
4760
+ }
4761
+ /**
4762
+ * Initialize a CreativeEngine.
4763
+ *
4764
+ * Mirrors `@cesdk/node`'s {@link Configuration} shape exactly. The
4765
+ * native-only `device` field selects the render backend
4766
+ * (`'auto'` | `'cpu'` | `'gpu'`); web/WASM Node accept the field for shape
4767
+ * parity but ignore non-`'auto'` values.
4768
+ *
4769
+ * @param config - Engine configuration. All fields optional; defaults
4770
+ * match `@cesdk/node` + `'auto'` device selection.
4771
+ * @returns A ready-to-use engine instance.
4772
+ * @public
4773
+ */
4774
+ static async init(config) {
4775
+ const { addon: nativeAddon, addonPath: nativeAddonPath } = await getNativeAddon();
4776
+ const engineDevice = resolveEngineDevice(config);
4777
+ const { baseURL, enginePath } = resolveEngineBaseURL(
4778
+ config,
4779
+ nativeAddonPath
4780
+ );
4781
+ const engine = nativeAddon.createEngine(enginePath, engineDevice);
4782
+ let resolvedLicense = "";
4783
+ try {
4784
+ resolvedLicense = await downloadAndCheckLicense(config ?? {});
4785
+ } catch (err) {
4786
+ try {
4787
+ engine.dispose();
4788
+ } catch {
4789
+ }
4790
+ throw err;
4791
+ }
4792
+ engine.unlockWithLicense(resolvedLicense);
4793
+ const creativeEngine = new _CreativeEngine(engine);
4794
+ creativeEngine.#baseURL = baseURL;
4795
+ configureEngine(creativeEngine, config, { baseURL });
4796
+ await registerBuiltinTypeface(creativeEngine);
4797
+ return creativeEngine;
4798
+ }
4799
+ /**
4800
+ * Get the base URL for engine assets
4801
+ */
4802
+ getBaseURL() {
4803
+ return this.#baseURL;
4804
+ }
4805
+ /**
4806
+ * Register a set of default asset sources containing default assets.
4807
+ *
4808
+ * Mirrors `@cesdk/node`'s instance method
4809
+ * (bindings/wasm/js_node/src/CreativeEngine.ts). Loads
4810
+ * `${baseURL}/<source-id>/content.json` for each id in the default set
4811
+ * and registers the contents as a local asset source. baseURL defaults
4812
+ * to the engine's configured baseURL (`file://<addon>/assets/` if none
4813
+ * was passed to init).
4814
+ *
4815
+ * @public
4816
+ */
4817
+ async addDefaultAssetSources({
4818
+ baseURL,
4819
+ excludeAssetSourceIds = []
4820
+ } = {}) {
4821
+ const resolvedBaseURL = baseURL ?? this.#baseURL;
4822
+ if (!resolvedBaseURL) {
4823
+ throw new Error(
4824
+ "addDefaultAssetSources: no baseURL configured. Pass { baseURL } or set baseURL on init."
4825
+ );
4826
+ }
4827
+ await loadDefaultAssetSources(
4828
+ this.asset,
4829
+ resolvedBaseURL,
4830
+ new Set(excludeAssetSourceIds)
4831
+ );
4832
+ }
4833
+ /**
4834
+ * Register a set of demo asset sources containing example assets.
4835
+ *
4836
+ * Mirrors `@cesdk/node`'s `bindings/wasm/js_web/src/addDemoAssetSources.ts`
4837
+ * — accepts `sceneMode`, `withUploadAssetSources`, and the full demo
4838
+ * source-ID union (templates + textComponents + uploads + media).
4839
+ *
4840
+ * @public
4841
+ */
4842
+ async addDemoAssetSources({
4843
+ baseURL,
4844
+ excludeAssetSourceIds = [],
4845
+ sceneMode = "Design",
4846
+ withUploadAssetSources = false
4847
+ } = {}) {
4848
+ const resolvedBaseURL = baseURL ?? this.#baseURL;
4849
+ if (!resolvedBaseURL) {
4850
+ throw new Error(
4851
+ "addDemoAssetSources: no baseURL configured. Pass { baseURL } or set baseURL on init."
4852
+ );
4853
+ }
4854
+ await loadDemoAssetSources(
4855
+ this.asset,
4856
+ resolvedBaseURL,
4857
+ new Set(excludeAssetSourceIds),
4858
+ {
4859
+ sceneMode,
4860
+ withUploadAssetSources,
4861
+ // Pass the scene API so template sources have something to call.
4862
+ sceneAPI: {
4863
+ applyTemplateFromString: (s) => this.scene.applyTemplateFromString(s),
4864
+ applyTemplateFromURL: (u) => this.scene.applyTemplateFromURL(u)
4865
+ }
4866
+ }
4867
+ );
4868
+ }
4869
+ };
4870
+
4871
+ // ts/src/types.ts
4872
+ var CompressionFormat = /* @__PURE__ */ ((CompressionFormat2) => {
4873
+ CompressionFormat2[CompressionFormat2["None"] = 0] = "None";
4874
+ CompressionFormat2[CompressionFormat2["Zstd"] = 1] = "Zstd";
4875
+ return CompressionFormat2;
4876
+ })(CompressionFormat || {});
4877
+ var CompressionLevel = /* @__PURE__ */ ((CompressionLevel2) => {
4878
+ CompressionLevel2[CompressionLevel2["Fastest"] = 0] = "Fastest";
4879
+ CompressionLevel2[CompressionLevel2["Default"] = 1] = "Default";
4880
+ CompressionLevel2[CompressionLevel2["Best"] = 2] = "Best";
4881
+ return CompressionLevel2;
4882
+ })(CompressionLevel || {});
4883
+
4884
+ // ts/src/index.ts
4885
+ var index_default = CreativeEngine;
4886
+ module.exports = CreativeEngine;
4887
+ Object.defineProperty(module.exports, "__esModule", { value: true });
4888
+ Object.defineProperty(module.exports, "default", { value: CreativeEngine });
4889
+ Object.defineProperties(module.exports, {
4890
+ MimeType: { value: MimeType, enumerable: true },
4891
+ LogLevel: { value: LogLevel, enumerable: true },
4892
+ defaultLogger: { value: defaultLogger, enumerable: true },
4893
+ isRGBAColor: { value: isRGBAColor, enumerable: true },
4894
+ isCMYKColor: { value: isCMYKColor, enumerable: true },
4895
+ isSpotColor: { value: isSpotColor, enumerable: true },
4896
+ ANIMATION_TYPES: { value: ANIMATION_TYPES, enumerable: true },
4897
+ DESIGN_BLOCK_TYPES: { value: DESIGN_BLOCK_TYPES, enumerable: true },
4898
+ SHAPE_TYPES: { value: SHAPE_TYPES, enumerable: true },
4899
+ FILL_TYPES: { value: FILL_TYPES, enumerable: true },
4900
+ EFFECT_TYPES: { value: EFFECT_TYPES, enumerable: true },
4901
+ BLUR_TYPES: { value: BLUR_TYPES, enumerable: true },
4902
+ CompressionFormat: { value: CompressionFormat, enumerable: true },
4903
+ CompressionLevel: { value: CompressionLevel, enumerable: true }
4904
+ });
4905
+ // Annotate the CommonJS export names for ESM import in node:
4906
+ 0 && (module.exports = {
4907
+ ANIMATION_TYPES,
4908
+ BLUR_TYPES,
4909
+ CompressionFormat,
4910
+ CompressionLevel,
4911
+ DESIGN_BLOCK_TYPES,
4912
+ EFFECT_TYPES,
4913
+ FILL_TYPES,
4914
+ LogLevel,
4915
+ MimeType,
4916
+ SHAPE_TYPES,
4917
+ defaultLogger,
4918
+ isCMYKColor,
4919
+ isRGBAColor,
4920
+ isSpotColor
4921
+ });
4922
+ //# sourceMappingURL=index.js.map