@openfairygui/functions 0.2.0-alpha.2 → 0.2.0-alpha.21

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 (54) hide show
  1. package/README.md +48 -6
  2. package/dist/atlas-C6tbl7nn.d.ts +193 -0
  3. package/dist/atlas-CHsu2Y8i.d.cts +193 -0
  4. package/dist/index.cjs +16 -3603
  5. package/dist/index.d.cts +5 -294
  6. package/dist/index.d.ts +5 -294
  7. package/dist/index.js +3 -3594
  8. package/dist/node.cjs +256 -0
  9. package/dist/node.d.cts +36 -0
  10. package/dist/node.d.ts +36 -0
  11. package/dist/node.js +254 -0
  12. package/dist/publish-BJ_eelME.js +3267 -0
  13. package/dist/publish-xFWT9Slz.cjs +3338 -0
  14. package/dist/restore-BW2xacB3.cjs +936 -0
  15. package/dist/restore-BeWaJNjR.d.cts +288 -0
  16. package/dist/restore-Clk62n0O.js +931 -0
  17. package/dist/restore-Dh0-Nvms.d.ts +288 -0
  18. package/dist/uam-transaction.cjs +44 -1
  19. package/dist/uam-transaction.d.cts +16 -1
  20. package/dist/uam-transaction.d.ts +16 -1
  21. package/dist/uam-transaction.js +44 -1
  22. package/dist/web.cjs +274 -0
  23. package/dist/web.d.cts +41 -0
  24. package/dist/web.d.ts +41 -0
  25. package/dist/web.js +273 -0
  26. package/package.json +28 -4
  27. package/src/adapters/node/plugins.ts +82 -0
  28. package/src/adapters/node/publish.ts +130 -0
  29. package/src/adapters/node/restore.ts +187 -0
  30. package/src/adapters/web/publish.ts +159 -0
  31. package/src/adapters/web/raster.ts +251 -0
  32. package/src/atlas/font.ts +95 -0
  33. package/src/atlas/inputs.ts +515 -0
  34. package/src/atlas/jta.ts +211 -0
  35. package/src/atlas/packing.ts +767 -0
  36. package/src/atlas.ts +116 -1221
  37. package/src/codegen.ts +106 -67
  38. package/src/index.ts +43 -3
  39. package/src/node.ts +8 -0
  40. package/src/plugins/types.ts +56 -0
  41. package/src/publish/contracts.ts +80 -0
  42. package/src/publish/external-resources.ts +117 -0
  43. package/src/publish/options.ts +180 -0
  44. package/src/publish/package-context.ts +608 -0
  45. package/src/publish/resource-references.ts +210 -0
  46. package/src/publish.ts +290 -968
  47. package/src/restore-internals/font.ts +100 -0
  48. package/src/restore-internals/movie-clip.ts +104 -0
  49. package/src/restore-internals/output-transaction.ts +164 -0
  50. package/src/restore.ts +112 -311
  51. package/src/shared-types.ts +4 -8
  52. package/src/uam-transaction.ts +68 -0
  53. package/src/utils.ts +28 -0
  54. package/src/web.ts +11 -0
@@ -0,0 +1,3267 @@
1
+ import { BinaryWriter, GearType, ProjectType, TransitionActionType } from "@openfairygui/core";
2
+ //#region src/utils.ts
3
+ /**
4
+ * Wraps a transform function, assigning it a name for the transform stack.
5
+ */
6
+ function createTransform(name, fn) {
7
+ Object.defineProperty(fn, "name", { value: name });
8
+ return fn;
9
+ }
10
+ function parseTextureSetMode(value) {
11
+ const raw = value?.trim() ?? "";
12
+ if (!raw) return {
13
+ kind: "auto",
14
+ raw: ""
15
+ };
16
+ if (raw === "alone") return {
17
+ kind: "standalone",
18
+ raw,
19
+ sizeMode: "default"
20
+ };
21
+ if (raw === "alone_npot") return {
22
+ kind: "standalone",
23
+ raw,
24
+ sizeMode: "npot"
25
+ };
26
+ if (raw === "alone_mof") return {
27
+ kind: "standalone",
28
+ raw,
29
+ sizeMode: "multipleOf4"
30
+ };
31
+ if (/^\d+$/.test(raw)) {
32
+ const pageIndex = Number(raw);
33
+ if (pageIndex >= 0 && pageIndex <= 10) return {
34
+ kind: "page",
35
+ raw,
36
+ pageIndex
37
+ };
38
+ }
39
+ return {
40
+ kind: "auto",
41
+ raw
42
+ };
43
+ }
44
+ //#endregion
45
+ //#region src/publish/resource-references.ts
46
+ function addReference(target, ownerPackageId, packageId, resourceId) {
47
+ if (!packageId || !resourceId) return;
48
+ if (packageId === ownerPackageId) target.localResourceIds.add(resourceId);
49
+ else target.packageIds.add(packageId);
50
+ }
51
+ function addUiReference(target, ownerPackageId, value) {
52
+ if (!value?.startsWith("ui://")) return;
53
+ const reference = value.slice(5);
54
+ const slashIndex = reference.indexOf("/");
55
+ if (slashIndex >= 0) {
56
+ addReference(target, ownerPackageId, reference.slice(0, slashIndex), reference.slice(slashIndex + 1));
57
+ return;
58
+ }
59
+ if (reference.length > 8) addReference(target, ownerPackageId, reference.slice(0, 8), reference.slice(8));
60
+ }
61
+ function addTextReferences(target, ownerPackageId, value) {
62
+ if (!value) return;
63
+ for (const match of value.matchAll(/ui:\/\/([0-9a-z]{8})\/?([0-9a-z]+)/giu)) addReference(target, ownerPackageId, match[1] ?? "", match[2] ?? "");
64
+ }
65
+ function addUnknownReferences(target, ownerPackageId, value) {
66
+ if (Array.isArray(value)) {
67
+ for (const entry of value) addUnknownReferences(target, ownerPackageId, entry);
68
+ return;
69
+ }
70
+ if (typeof value === "string") {
71
+ addUiReference(target, ownerPackageId, value);
72
+ addTextReferences(target, ownerPackageId, value);
73
+ }
74
+ }
75
+ function addFontReferences(target, ownerPackageId, value) {
76
+ if (Array.isArray(value)) {
77
+ for (const entry of value) addUiReference(target, ownerPackageId, entry);
78
+ return;
79
+ }
80
+ addUiReference(target, ownerPackageId, value);
81
+ }
82
+ function collectComponentReferences(target, ownerPackageId, component) {
83
+ const referenceComponent = component;
84
+ for (const child of referenceComponent.listChildren()) {
85
+ const sourceId = child.getSrc?.();
86
+ if (sourceId) if (sourceId.startsWith("ui://")) addUiReference(target, ownerPackageId, sourceId);
87
+ else target.localResourceIds.add(sourceId);
88
+ const sourcePackageId = child.getPackageId?.()?.trim();
89
+ if (sourcePackageId && sourcePackageId !== ownerPackageId) target.packageIds.add(sourcePackageId);
90
+ addFontReferences(target, ownerPackageId, child.getFont?.());
91
+ addTextReferences(target, ownerPackageId, child.getText?.());
92
+ for (const reference of [
93
+ child.getUrl?.(),
94
+ child.getDefaultItem?.(),
95
+ child.getIcon?.(),
96
+ child.getSelectedIcon?.(),
97
+ child.getDropdown?.(),
98
+ child.getSound?.(),
99
+ child.getInstanceSound?.(),
100
+ child.getInstanceIcon?.(),
101
+ child.getInstanceSelectedIcon?.(),
102
+ child.getVtScrollBarRes?.(),
103
+ child.getHzScrollBarRes?.(),
104
+ child.getHeaderRes?.(),
105
+ child.getFooterRes?.()
106
+ ]) addUiReference(target, ownerPackageId, reference);
107
+ for (const item of child.getInstanceComboItems?.() ?? []) addUiReference(target, ownerPackageId, item.icon);
108
+ for (const item of child.getListItems?.() ?? []) {
109
+ addUiReference(target, ownerPackageId, item.icon);
110
+ addUiReference(target, ownerPackageId, item.url);
111
+ }
112
+ for (const gear of child.listGears?.() ?? []) {
113
+ addUnknownReferences(target, ownerPackageId, gear.getValues?.());
114
+ addUnknownReferences(target, ownerPackageId, gear.getDefaultValue?.());
115
+ }
116
+ }
117
+ addFontReferences(target, ownerPackageId, referenceComponent.getFont?.());
118
+ for (const reference of [
119
+ referenceComponent.getDropdown?.(),
120
+ referenceComponent.getHeaderRes?.(),
121
+ referenceComponent.getFooterRes?.(),
122
+ referenceComponent.getVtScrollBarRes?.(),
123
+ referenceComponent.getHzScrollBarRes?.(),
124
+ referenceComponent.getSound?.()
125
+ ]) addUiReference(target, ownerPackageId, reference);
126
+ for (const transition of referenceComponent.listTransitions?.() ?? []) for (const item of transition.listItems?.() ?? []) {
127
+ addUnknownReferences(target, ownerPackageId, item.getStartValue?.());
128
+ addUnknownReferences(target, ownerPackageId, item.getEndValue?.());
129
+ }
130
+ }
131
+ /**
132
+ * Enumerates package-local resource IDs and external package IDs referenced by
133
+ * component content. Callers retain policy decisions such as atlas selection
134
+ * and dependency ordering.
135
+ */
136
+ function collectPackageResourceReferences(pkg) {
137
+ const references = {
138
+ localResourceIds: /* @__PURE__ */ new Set(),
139
+ packageIds: /* @__PURE__ */ new Set()
140
+ };
141
+ for (const resource of pkg.listResources()) if (resource.propertyType === "Component") collectComponentReferences(references, pkg.getId(), resource);
142
+ return references;
143
+ }
144
+ //#endregion
145
+ //#region src/atlas/font.ts
146
+ /** Parse a BMFont .fnt text file into structured data for binary encoding. */
147
+ function parseFnt(text) {
148
+ const lines = text.split(/\r?\n/);
149
+ let hasFace = false;
150
+ let colored = false;
151
+ let resizable = false;
152
+ let hasChannel = false;
153
+ let fontSize = 0;
154
+ let globalXadvance = 0;
155
+ let lineHeight = 0;
156
+ const glyphs = [];
157
+ for (const line of lines) {
158
+ const trimmed = line.trim();
159
+ if (!trimmed) continue;
160
+ const parts = trimmed.split(/\s+/);
161
+ const attrs = {};
162
+ for (let index = 1; index < parts.length; index += 1) {
163
+ const entry = parts[index]?.split("=") ?? [];
164
+ if (entry.length === 2 && entry[0]) attrs[entry[0]] = entry[1] ?? "";
165
+ }
166
+ switch (parts[0]) {
167
+ case "info":
168
+ hasFace = attrs.face != null;
169
+ colored = hasFace;
170
+ if (attrs.colored !== void 0) colored = attrs.colored === "true";
171
+ fontSize = parseInt(attrs.size ?? "", 10) || 0;
172
+ resizable = attrs.resizable === "true";
173
+ break;
174
+ case "common":
175
+ lineHeight = parseInt(attrs.lineHeight ?? "", 10) || 0;
176
+ globalXadvance = parseInt(attrs.xadvance ?? "", 10) || 0;
177
+ if (fontSize === 0) fontSize = lineHeight;
178
+ else if (lineHeight === 0) lineHeight = fontSize;
179
+ break;
180
+ case "char": {
181
+ const charId = parseInt(attrs.id ?? "", 10) || 0;
182
+ if (charId === 0) continue;
183
+ const img = attrs.img || null;
184
+ if (!hasFace && !img) continue;
185
+ const channel = parseInt(attrs.chnl ?? "", 10) || 0;
186
+ if (channel !== 0 && channel !== 15) hasChannel = true;
187
+ glyphs.push({
188
+ charId,
189
+ img,
190
+ x: parseInt(attrs.x ?? "", 10) || 0,
191
+ y: parseInt(attrs.y ?? "", 10) || 0,
192
+ xoffset: parseInt(attrs.xoffset ?? "", 10) || 0,
193
+ yoffset: parseInt(attrs.yoffset ?? "", 10) || 0,
194
+ width: parseInt(attrs.width ?? "", 10) || 0,
195
+ height: parseInt(attrs.height ?? "", 10) || 0,
196
+ xadvance: parseInt(attrs.xadvance ?? "", 10) || 0,
197
+ channel
198
+ });
199
+ break;
200
+ }
201
+ }
202
+ }
203
+ return {
204
+ hasFace,
205
+ colored,
206
+ resizable: fontSize > 0 ? resizable : false,
207
+ hasChannel,
208
+ fontSize,
209
+ xadvance: globalXadvance,
210
+ lineHeight,
211
+ glyphs
212
+ };
213
+ }
214
+ //#endregion
215
+ //#region src/atlas/jta.ts
216
+ const PNG_SIGNATURE = new Uint8Array([
217
+ 137,
218
+ 80,
219
+ 78,
220
+ 71,
221
+ 13,
222
+ 10,
223
+ 26,
224
+ 10
225
+ ]);
226
+ function extractJtaFrames(data) {
227
+ const frames = [];
228
+ let offset = 0;
229
+ let firstPngOffset = -1;
230
+ while (offset < data.length) {
231
+ const signatureIndex = findPngSignature(data, offset);
232
+ if (signatureIndex === -1) break;
233
+ if (firstPngOffset === -1) firstPngOffset = signatureIndex;
234
+ const end = findPngEnd(data, signatureIndex);
235
+ if (end === -1) break;
236
+ frames.push(data.subarray(signatureIndex, end));
237
+ offset = end;
238
+ }
239
+ if (firstPngOffset === -1 || frames.length === 0) return { frames: [] };
240
+ return {
241
+ frames,
242
+ meta: parseJtaHeader(data, firstPngOffset, frames.length)
243
+ };
244
+ }
245
+ function findPngSignature(data, fromIndex) {
246
+ for (let index = fromIndex; index <= data.length - PNG_SIGNATURE.length; index += 1) {
247
+ let matched = true;
248
+ for (let signatureIndex = 0; signatureIndex < PNG_SIGNATURE.length; signatureIndex += 1) if (data[index + signatureIndex] !== PNG_SIGNATURE[signatureIndex]) {
249
+ matched = false;
250
+ break;
251
+ }
252
+ if (matched) return index;
253
+ }
254
+ return -1;
255
+ }
256
+ function findPngEnd(data, start) {
257
+ let position = start + PNG_SIGNATURE.length;
258
+ while (position + 8 <= data.length) {
259
+ const length = readUint32BE(data, position);
260
+ position += 8;
261
+ if (position + length + 4 > data.length) return -1;
262
+ const isEnd = data[position - 4] === 73 && data[position - 3] === 69 && data[position - 2] === 78 && data[position - 1] === 68;
263
+ position += length + 4;
264
+ if (isEnd) return position;
265
+ }
266
+ return -1;
267
+ }
268
+ function parseJtaHeader(data, firstPngOffset, frameCount) {
269
+ if (data.length < 10) return void 0;
270
+ const state = { offset: 0 };
271
+ const end = Math.min(firstPngOffset, data.length);
272
+ if (!readUtfBE(data, state, end)) return void 0;
273
+ const version = readInt32BEAt(data, state, end);
274
+ if (version == null) return void 0;
275
+ const fpsRaw = readInt8At(data, state, end);
276
+ if (fpsRaw == null) return void 0;
277
+ const fps = fpsRaw > 0 ? fpsRaw : 24;
278
+ if (state.offset + 3 > end) return void 0;
279
+ state.offset += 3;
280
+ if (version < 102) return void 0;
281
+ readUint16BEAt(data, state, end);
282
+ readUint16BEAt(data, state, end);
283
+ const width = readUint16BEAt(data, state, end);
284
+ const height = readUint16BEAt(data, state, end);
285
+ if (width == null || height == null) return void 0;
286
+ const speedRaw = readUint8At(data, state, end);
287
+ const repeatDelayRaw = readUint8At(data, state, end);
288
+ const swingRaw = readInt8At(data, state, end);
289
+ const frameTableCount = readInt16BEAt(data, state, end);
290
+ if (speedRaw == null || repeatDelayRaw == null || swingRaw == null || frameTableCount == null) return void 0;
291
+ const frames = [];
292
+ for (let index = 0; index < frameTableCount; index += 1) {
293
+ const delayRaw = readInt16BEAt(data, state, end);
294
+ const offsetX = readInt16BEAt(data, state, end);
295
+ const offsetY = readInt16BEAt(data, state, end);
296
+ const frameWidth = readInt16BEAt(data, state, end);
297
+ const frameHeight = readInt16BEAt(data, state, end);
298
+ const textureIndex = readInt16BEAt(data, state, end);
299
+ if (delayRaw == null || offsetX == null || offsetY == null || frameWidth == null || frameHeight == null || textureIndex == null) break;
300
+ frames.push({
301
+ addDelay: Math.trunc(1e3 / fps * delayRaw),
302
+ offsetX,
303
+ offsetY,
304
+ width: frameWidth,
305
+ height: frameHeight,
306
+ textureIndex
307
+ });
308
+ }
309
+ return {
310
+ interval: Math.trunc(1e3 / fps * (speedRaw || 1)),
311
+ repeatDelay: Math.trunc(1e3 / fps * repeatDelayRaw),
312
+ swing: swingRaw === 1,
313
+ width,
314
+ height,
315
+ frames: frames.length === 0 && frameCount > 0 ? [] : frames
316
+ };
317
+ }
318
+ function readUtfBE(data, state, end) {
319
+ const length = readUint16BEAt(data, state, end);
320
+ if (length == null || state.offset + length > end) return null;
321
+ const value = new TextDecoder().decode(data.subarray(state.offset, state.offset + length));
322
+ state.offset += length;
323
+ return value;
324
+ }
325
+ function readUint8At(data, state, end) {
326
+ if (state.offset + 1 > end) return null;
327
+ const value = data[state.offset];
328
+ state.offset += 1;
329
+ return value ?? 0;
330
+ }
331
+ function readInt8At(data, state, end) {
332
+ if (state.offset + 1 > end) return null;
333
+ const value = new DataView(data.buffer, data.byteOffset, data.byteLength).getInt8(state.offset);
334
+ state.offset += 1;
335
+ return value;
336
+ }
337
+ function readUint16BEAt(data, state, end) {
338
+ if (state.offset + 2 > end) return null;
339
+ const value = readUint16BE(data, state.offset);
340
+ state.offset += 2;
341
+ return value;
342
+ }
343
+ function readInt16BEAt(data, state, end) {
344
+ if (state.offset + 2 > end) return null;
345
+ const value = new DataView(data.buffer, data.byteOffset, data.byteLength).getInt16(state.offset, false);
346
+ state.offset += 2;
347
+ return value;
348
+ }
349
+ function readInt32BEAt(data, state, end) {
350
+ if (state.offset + 4 > end) return null;
351
+ const value = new DataView(data.buffer, data.byteOffset, data.byteLength).getInt32(state.offset, false);
352
+ state.offset += 4;
353
+ return value;
354
+ }
355
+ function readUint16BE(data, offset) {
356
+ if (offset + 1 >= data.length) return 0;
357
+ return data[offset] << 8 | data[offset + 1];
358
+ }
359
+ function readUint32BE(data, offset) {
360
+ if (offset + 3 >= data.length) return 0;
361
+ return data[offset] * 16777216 + ((data[offset + 1] ?? 0) << 16) + ((data[offset + 2] ?? 0) << 8) + (data[offset + 3] ?? 0);
362
+ }
363
+ //#endregion
364
+ //#region src/atlas/inputs.ts
365
+ function getPublishedItemId(resource) {
366
+ return (resource.getExtras() ?? {})._publishedId ?? resource.getId();
367
+ }
368
+ function resolveFontFileName(fontName) {
369
+ return /\.fnt$/i.test(fontName) ? fontName : `${fontName}.fnt`;
370
+ }
371
+ function resolveImageFileName$1(resource) {
372
+ const extras = resource.getExtras();
373
+ return resource.getFileName() || extras._fileName || resource.getName();
374
+ }
375
+ /**
376
+ * Trim transparent edges from an image using the host raster backend.
377
+ * Returns the trimmed buffer, dimensions, and offsets.
378
+ * Falls back to the original image if trim fails (e.g. no alpha channel, no transparent edges).
379
+ */
380
+ async function _trimImage(encoder, input, originalWidth, originalHeight) {
381
+ try {
382
+ const trimResult = await encoder(input).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
383
+ if (!isResolvedBuffer(trimResult)) throw new Error("atlas: encoder raw alpha trim did not return resolved metadata.");
384
+ const { data, info } = trimResult;
385
+ const width = info.width;
386
+ const height = info.height;
387
+ const channels = info.channels || 4;
388
+ let minX = width;
389
+ let minY = height;
390
+ let maxX = -1;
391
+ let maxY = -1;
392
+ for (let y = 0; y < height; y += 1) for (let x = 0; x < width; x += 1) {
393
+ if ((data[(y * width + x) * channels + 3] ?? 0) === 0) continue;
394
+ if (x < minX) minX = x;
395
+ if (y < minY) minY = y;
396
+ if (x > maxX) maxX = x;
397
+ if (y > maxY) maxY = y;
398
+ }
399
+ if (maxX < minX || maxY < minY) return {
400
+ buffer: new Uint8Array(0),
401
+ width: 0,
402
+ height: 0,
403
+ offsetX: 0,
404
+ offsetY: 0,
405
+ originalWidth,
406
+ originalHeight
407
+ };
408
+ const trimmedWidth = maxX - minX + 1;
409
+ const trimmedHeight = maxY - minY + 1;
410
+ return {
411
+ buffer: await encoder(input).extract({
412
+ left: minX,
413
+ top: minY,
414
+ width: trimmedWidth,
415
+ height: trimmedHeight
416
+ }).toBuffer(),
417
+ width: trimmedWidth,
418
+ height: trimmedHeight,
419
+ offsetX: minX,
420
+ offsetY: minY,
421
+ originalWidth,
422
+ originalHeight
423
+ };
424
+ } catch {
425
+ return {
426
+ buffer: await encoder(input).png().toBuffer(),
427
+ width: originalWidth,
428
+ height: originalHeight,
429
+ offsetX: 0,
430
+ offsetY: 0,
431
+ originalWidth,
432
+ originalHeight
433
+ };
434
+ }
435
+ }
436
+ /**
437
+ * Resolve an ImageResource to its actual file path on disk.
438
+ */
439
+ function resolveImagePath$1(resource, pkg, basePath) {
440
+ const imgPath = resource.getPath() ?? "/";
441
+ const fileName = resolveImageFileName$1(resource);
442
+ const branchName = resource.getBranch?.() ?? "";
443
+ const normalizedBasePath = basePath.replace(/[/\\]+$/, "");
444
+ return `${!branchName ? normalizedBasePath : /[\\/]assets$/i.test(normalizedBasePath) ? normalizedBasePath.replace(/([\\/])assets$/i, `$1assets_${branchName}`) : `${normalizedBasePath}_${branchName}`}/${pkg.getName()}${imgPath}${fileName}`;
445
+ }
446
+ /** Collect a single ImageResource into the inputs array. */
447
+ async function collectImage(resource, pkg, inputs, encoder, options, doTrim, logger) {
448
+ let origW = resource.getWidth() ?? 0;
449
+ let origH = resource.getHeight() ?? 0;
450
+ const declaredWidth = origW;
451
+ const declaredHeight = origH;
452
+ let sourceHasAlpha = false;
453
+ let rasterizedBuffer;
454
+ if (encoder && options.basePath) {
455
+ const filePath = resolveImagePath$1(resource, pkg, options.basePath);
456
+ try {
457
+ const metadata = await encoder(filePath).metadata();
458
+ if (origW === 0 || origH === 0) {
459
+ origW = metadata.width ?? 0;
460
+ origH = metadata.height ?? 0;
461
+ resource.setWidth(origW);
462
+ resource.setHeight(origH);
463
+ }
464
+ sourceHasAlpha = metadata.hasAlpha === true || metadata.channels === 4;
465
+ if (/\.svg$/i.test(resolveImageFileName$1(resource)) && declaredWidth > 0 && declaredHeight > 0) {
466
+ rasterizedBuffer = await encoder(filePath).resize({
467
+ width: declaredWidth,
468
+ height: declaredHeight,
469
+ fit: "fill"
470
+ }).png().toBuffer();
471
+ sourceHasAlpha = true;
472
+ }
473
+ } catch {
474
+ if (options.strictOutput) throw new Error(`atlas: Could not read image "${filePath}".`);
475
+ if (origW === 0 || origH === 0) {
476
+ logger.warn(`atlas: Could not read image "${filePath}", skipping.`);
477
+ return;
478
+ }
479
+ }
480
+ }
481
+ if (origW <= 0 || origH <= 0) return;
482
+ let packW = origW, packH = origH, offX = 0, offY = 0;
483
+ let trimBuf;
484
+ if (doTrim && sourceHasAlpha && options.basePath && encoder) {
485
+ const filePath = resolveImagePath$1(resource, pkg, options.basePath);
486
+ try {
487
+ const trimResult = await _trimImage(encoder, rasterizedBuffer ?? filePath, origW, origH);
488
+ packW = trimResult.width;
489
+ packH = trimResult.height;
490
+ offX = trimResult.offsetX;
491
+ offY = trimResult.offsetY;
492
+ trimBuf = trimResult.buffer;
493
+ } catch {
494
+ logger.warn(`atlas: Could not trim "${filePath}", using original.`);
495
+ }
496
+ }
497
+ inputs.push({
498
+ id: getPublishedItemId(resource),
499
+ width: packW,
500
+ height: packH,
501
+ originalWidth: origW,
502
+ originalHeight: origH,
503
+ offsetX: offX,
504
+ offsetY: offY,
505
+ resource,
506
+ trimBuffer: trimBuf,
507
+ rasterizedBuffer,
508
+ sourceKind: "image"
509
+ });
510
+ }
511
+ /** Collect MovieClip frame textures from a .jta file into the inputs array. */
512
+ async function collectMovieClipFrames(doc, resource, pkg, inputs, encoder, options, logger) {
513
+ if (!options.basePath || !options.readFileRaw) {
514
+ if (options.strictOutput) throw new Error(`atlas: MovieClip "${resource.getId()}" requires basePath and readFileRaw for complete raster output.`);
515
+ return;
516
+ }
517
+ if (!encoder && options.strictOutput) throw new Error(`atlas: MovieClip "${resource.getId()}" requires an encoder for complete raster output.`);
518
+ const mcId = resource.getId();
519
+ const mcName = resource.getName() + ".jta";
520
+ const mcPath = resource.getPath() ?? "/";
521
+ const filePath = `${options.basePath}/${pkg.getName()}${mcPath}${mcName}`;
522
+ try {
523
+ const jta = extractJtaFrames(await options.readFileRaw(filePath));
524
+ if (jta.frames.length === 0) return;
525
+ const frameMetas = jta.meta?.frames ?? [];
526
+ for (const frame of resource.listFrames()) resource.removeFrame(frame);
527
+ resource.setInterval(jta.meta?.interval ?? 100).setSwing(jta.meta?.swing ?? false).setRepeatDelay(jta.meta?.repeatDelay ?? 0);
528
+ if (frameMetas.length > 0) {
529
+ const firstFrameIndexByTextureIndex = /* @__PURE__ */ new Map();
530
+ for (let frameIndex = 0; frameIndex < frameMetas.length; frameIndex += 1) {
531
+ const meta = frameMetas[frameIndex];
532
+ const textureIndex = Number.isFinite(meta.textureIndex) ? meta.textureIndex : frameIndex;
533
+ if (!firstFrameIndexByTextureIndex.has(textureIndex)) firstFrameIndexByTextureIndex.set(textureIndex, frameIndex);
534
+ }
535
+ const spriteIdByTextureIndex = /* @__PURE__ */ new Map();
536
+ for (let textureIndex = 0; textureIndex < jta.frames.length; textureIndex += 1) {
537
+ const exportFrameIndex = firstFrameIndexByTextureIndex.get(textureIndex);
538
+ if (exportFrameIndex === void 0) continue;
539
+ const itemId = `${mcId}_${exportFrameIndex}`;
540
+ const input = await createMovieClipFrameInput(jta.frames[textureIndex], itemId, resource, encoder, options.strictOutput);
541
+ if (!input) continue;
542
+ inputs.push(input);
543
+ spriteIdByTextureIndex.set(textureIndex, itemId);
544
+ }
545
+ for (let frameIndex = 0; frameIndex < frameMetas.length; frameIndex += 1) {
546
+ const meta = frameMetas[frameIndex];
547
+ const textureIndex = Number.isFinite(meta.textureIndex) ? meta.textureIndex : frameIndex;
548
+ const frame = doc.createMovieFrame(`${mcId}_${frameIndex}`);
549
+ frame.setRectX(meta.offsetX).setRectY(meta.offsetY).setRectWidth(meta.width).setRectHeight(meta.height).setAddDelay(meta.addDelay).setSpriteId(spriteIdByTextureIndex.get(textureIndex) ?? "");
550
+ resource.addFrame(frame);
551
+ }
552
+ } else for (let frameIndex = 0; frameIndex < jta.frames.length; frameIndex += 1) {
553
+ const itemId = `${mcId}_${frameIndex}`;
554
+ const input = await createMovieClipFrameInput(jta.frames[frameIndex], itemId, resource, encoder, options.strictOutput);
555
+ if (!input) continue;
556
+ inputs.push(input);
557
+ const frame = doc.createMovieFrame(itemId);
558
+ frame.setRectX(0).setRectY(0).setRectWidth(input.originalWidth).setRectHeight(input.originalHeight).setAddDelay(0).setSpriteId(itemId);
559
+ resource.addFrame(frame);
560
+ }
561
+ if ((jta.meta?.width ?? 0) > 0 && (jta.meta?.height ?? 0) > 0) {
562
+ resource.setWidth(jta.meta?.width ?? 0);
563
+ resource.setHeight(jta.meta?.height ?? 0);
564
+ }
565
+ } catch {
566
+ const message = `atlas: Could not parse MovieClip "${filePath}".`;
567
+ if (options.strictOutput) throw new Error(message);
568
+ logger.warn(`${message} Skipping frames.`);
569
+ }
570
+ }
571
+ async function createMovieClipFrameInput(buffer, itemId, resource, encoder, strictOutput) {
572
+ if (!encoder || buffer.length === 0) return null;
573
+ try {
574
+ const meta = await encoder(buffer).metadata();
575
+ const width = meta.width ?? 0;
576
+ const height = meta.height ?? 0;
577
+ if (width <= 0 || height <= 0) return null;
578
+ return {
579
+ id: itemId,
580
+ width,
581
+ height,
582
+ originalWidth: width,
583
+ originalHeight: height,
584
+ offsetX: 0,
585
+ offsetY: 0,
586
+ resource,
587
+ trimBuffer: buffer,
588
+ sourceKind: "movieclip-frame"
589
+ };
590
+ } catch {
591
+ if (strictOutput) throw new Error(`atlas: Could not decode MovieClip frame "${itemId}".`);
592
+ return null;
593
+ }
594
+ }
595
+ /** Collect a Bitmap Font's texture image, packed under the font's ID. */
596
+ async function collectFontTexture(doc, fontRes, pkg, options) {
597
+ const textureId = fontRes.getTextureId?.() ?? "";
598
+ if (textureId) {
599
+ const fontId = fontRes.getId();
600
+ fontRes.setExtras({
601
+ ...fontRes.getExtras(),
602
+ _fontSpriteAlias: {
603
+ fontId,
604
+ textureId
605
+ }
606
+ });
607
+ }
608
+ if (options.readFileRaw && options.basePath) {
609
+ const fontName = resolveFontFileName(fontRes.getName());
610
+ const fontPath = fontRes.getPath() ?? "/";
611
+ const pkgName = pkg.getName();
612
+ const fntFile = `${options.basePath}/${pkgName}${fontPath}${fontName}`;
613
+ try {
614
+ const fntData = await options.readFileRaw(fntFile);
615
+ const fntParsed = parseFnt(new TextDecoder().decode(fntData));
616
+ for (const glyph of fontRes.listGlyphs()) fontRes.removeGlyph(glyph);
617
+ fontRes.setTtf(fntParsed.hasFace).setTint(fntParsed.colored).setAutoScale(fntParsed.resizable).setHasChannel(fntParsed.hasChannel).setFontSize(fntParsed.fontSize).setXAdvance(fntParsed.xadvance).setLineHeight(fntParsed.lineHeight);
618
+ for (const item of fntParsed.glyphs) {
619
+ const glyph = doc.createFontGlyph(`${fontRes.getId()}_${item.charId}`);
620
+ glyph.setCharId(item.charId).setChar(item.charId > 0 ? String.fromCodePoint(item.charId) : "").setImg(item.img ?? "").setX(item.x).setY(item.y).setXOffset(item.xoffset).setYOffset(item.yoffset).setWidth(item.width).setHeight(item.height).setAdvance(item.xadvance).setLineHeight(fntParsed.lineHeight).setChannel(item.channel);
621
+ fontRes.addGlyph(glyph);
622
+ }
623
+ } catch {}
624
+ }
625
+ }
626
+ function isComponentResource$1(resource) {
627
+ return resource.propertyType === "Component";
628
+ }
629
+ function isImageResource$1(resource) {
630
+ return resource.propertyType === "ImageResource";
631
+ }
632
+ function isMovieClipResource$1(resource) {
633
+ return resource.propertyType === "MovieClipResource";
634
+ }
635
+ function isSkeletonResource$1(resource) {
636
+ return resource.propertyType === "SpineResource" || resource.propertyType === "DragonBonesResource";
637
+ }
638
+ function isFontResource$1(resource) {
639
+ return resource.propertyType === "FontResource";
640
+ }
641
+ function isPackableResource(resource) {
642
+ return isImageResource$1(resource) || isMovieClipResource$1(resource) || isFontResource$1(resource);
643
+ }
644
+ function isResolvedBuffer(value) {
645
+ return typeof value === "object" && value !== null && "data" in value && "info" in value;
646
+ }
647
+ //#endregion
648
+ //#region src/max-rects-compat.ts
649
+ const NO_ROTATION = 2;
650
+ const MAX_SCORE = 2147483647;
651
+ const MAX_RECTS_METHOD = {
652
+ BestShortSideFit: 0,
653
+ BestLongSideFit: 1,
654
+ BestAreaFit: 2,
655
+ BottomLeftRule: 3,
656
+ ContactPointRule: 4
657
+ };
658
+ const COMPAT_NODE_RECT_FLAGS = {
659
+ DUPLICATE_PADDING: 1,
660
+ NO_ROTATION
661
+ };
662
+ var MaxRectsCompat = class MaxRectsCompat {
663
+ static helperRect = createNodeRect();
664
+ binWidth = 0;
665
+ binHeight = 0;
666
+ allowRotations = false;
667
+ usedRectangles = [];
668
+ freeRectangles = [];
669
+ init(width, height, allowRotations = false) {
670
+ this.binWidth = width;
671
+ this.binHeight = height;
672
+ this.allowRotations = allowRotations;
673
+ this.usedRectangles.length = 0;
674
+ this.freeRectangles.length = 0;
675
+ this.freeRectangles.push({
676
+ ...createNodeRect(),
677
+ x: 0,
678
+ y: 0,
679
+ width,
680
+ height
681
+ });
682
+ }
683
+ insert(rect, method) {
684
+ const newNode = this.scoreRect(rect, method);
685
+ if (newNode.height === 0) return null;
686
+ const placed = cloneNodeRect(newNode);
687
+ this.placeRect(placed);
688
+ return placed;
689
+ }
690
+ pack(rects, method) {
691
+ const remaining = rects.map(cloneNodeRect);
692
+ while (remaining.length > 0) {
693
+ let bestIndex = -1;
694
+ const bestNode = createNodeRect();
695
+ bestNode.score1 = MAX_SCORE;
696
+ bestNode.score2 = MAX_SCORE;
697
+ for (let index = 0; index < remaining.length; index += 1) {
698
+ const candidate = this.scoreRect(remaining[index], method);
699
+ if (candidate.score1 < bestNode.score1 || candidate.score1 === bestNode.score1 && candidate.score2 < bestNode.score2) {
700
+ copyNodeRect(bestNode, candidate);
701
+ bestIndex = index;
702
+ }
703
+ }
704
+ if (bestIndex === -1) break;
705
+ this.placeRect(bestNode);
706
+ remaining.splice(bestIndex, 1);
707
+ }
708
+ const result = this.getResult();
709
+ result.remainingRects = remaining;
710
+ return result;
711
+ }
712
+ getResult() {
713
+ let width = 0;
714
+ let height = 0;
715
+ for (const rect of this.usedRectangles) {
716
+ width = Math.max(width, rect.x + rect.width);
717
+ height = Math.max(height, rect.y + rect.height);
718
+ }
719
+ return {
720
+ outputRects: this.usedRectangles.map(cloneNodeRect),
721
+ remainingRects: [],
722
+ occupancy: this.getOccupancy(),
723
+ width,
724
+ height
725
+ };
726
+ }
727
+ getOccupancy() {
728
+ let usedSurface = 0;
729
+ for (const rect of this.usedRectangles) usedSurface += rect.width * rect.height;
730
+ return usedSurface / (this.binWidth * this.binHeight);
731
+ }
732
+ placeRect(rect) {
733
+ for (let index = 0; index < this.freeRectangles.length; index += 1) if (this.splitFreeNode(this.freeRectangles[index], rect)) {
734
+ this.freeRectangles.splice(index, 1);
735
+ index -= 1;
736
+ }
737
+ this.pruneFreeList();
738
+ this.usedRectangles.push(rect);
739
+ }
740
+ scoreRect(rect, method) {
741
+ const helper = MaxRectsCompat.helperRect;
742
+ helper.height = 0;
743
+ let newNode;
744
+ switch (method) {
745
+ case MAX_RECTS_METHOD.BestShortSideFit:
746
+ newNode = this.findPositionForNewNodeBestShortSideFit(rect.width, rect.height, allowRotation(rect));
747
+ break;
748
+ case MAX_RECTS_METHOD.BestLongSideFit:
749
+ newNode = this.findPositionForNewNodeBestLongSideFit(rect.width, rect.height, allowRotation(rect));
750
+ break;
751
+ case MAX_RECTS_METHOD.BestAreaFit:
752
+ newNode = this.findPositionForNewNodeBestAreaFit(rect.width, rect.height, allowRotation(rect));
753
+ break;
754
+ case MAX_RECTS_METHOD.BottomLeftRule:
755
+ newNode = this.findPositionForNewNodeBottomLeft(rect.width, rect.height, allowRotation(rect));
756
+ break;
757
+ case MAX_RECTS_METHOD.ContactPointRule:
758
+ newNode = this.findPositionForNewNodeContactPoint(rect.width, rect.height, allowRotation(rect));
759
+ newNode.score1 = -newNode.score1;
760
+ break;
761
+ default:
762
+ newNode = helper;
763
+ break;
764
+ }
765
+ if (newNode.height === 0) {
766
+ newNode.score1 = MAX_SCORE;
767
+ newNode.score2 = MAX_SCORE;
768
+ }
769
+ newNode.index = rect.index;
770
+ newNode.subIndex = rect.subIndex;
771
+ newNode.flags = rect.flags;
772
+ newNode.sourceKind = rect.sourceKind;
773
+ return cloneNodeRect(newNode);
774
+ }
775
+ findPositionForNewNodeBottomLeft(width, height, allowRectRotation) {
776
+ const bestNode = MaxRectsCompat.helperRect;
777
+ bestNode.score1 = MAX_SCORE;
778
+ bestNode.score2 = 0;
779
+ for (const freeRect of this.freeRectangles) {
780
+ if (freeRect.width >= width && freeRect.height >= height) {
781
+ const topSideY = freeRect.y + height;
782
+ if (topSideY < bestNode.score1 || topSideY === bestNode.score1 && freeRect.x < bestNode.score2) setNodeRect(bestNode, freeRect.x, freeRect.y, width, height, false, topSideY, freeRect.x);
783
+ }
784
+ if (this.allowRotations && allowRectRotation && freeRect.width >= height && freeRect.height >= width) {
785
+ const topSideY = freeRect.y + width;
786
+ if (topSideY < bestNode.score1 || topSideY === bestNode.score1 && freeRect.x < bestNode.score2) setNodeRect(bestNode, freeRect.x, freeRect.y, height, width, true, topSideY, freeRect.x);
787
+ }
788
+ }
789
+ return bestNode;
790
+ }
791
+ findPositionForNewNodeBestShortSideFit(width, height, allowRectRotation) {
792
+ const bestNode = MaxRectsCompat.helperRect;
793
+ bestNode.score1 = MAX_SCORE;
794
+ bestNode.score2 = 0;
795
+ for (const freeRect of this.freeRectangles) {
796
+ if (freeRect.width >= width && freeRect.height >= height) {
797
+ const leftoverHoriz = Math.abs(freeRect.width - width);
798
+ const leftoverVert = Math.abs(freeRect.height - height);
799
+ const shortSideFit = Math.min(leftoverHoriz, leftoverVert);
800
+ const longSideFit = Math.max(leftoverHoriz, leftoverVert);
801
+ if (shortSideFit < bestNode.score1 || shortSideFit === bestNode.score1 && longSideFit < bestNode.score2) setNodeRect(bestNode, freeRect.x, freeRect.y, width, height, false, shortSideFit, longSideFit);
802
+ }
803
+ if (this.allowRotations && allowRectRotation && freeRect.width >= height && freeRect.height >= width) {
804
+ const leftoverHoriz = Math.abs(freeRect.width - height);
805
+ const leftoverVert = Math.abs(freeRect.height - width);
806
+ const shortSideFit = Math.min(leftoverHoriz, leftoverVert);
807
+ const longSideFit = Math.max(leftoverHoriz, leftoverVert);
808
+ if (shortSideFit < bestNode.score1 || shortSideFit === bestNode.score1 && longSideFit < bestNode.score2) setNodeRect(bestNode, freeRect.x, freeRect.y, height, width, true, shortSideFit, longSideFit);
809
+ }
810
+ }
811
+ return bestNode;
812
+ }
813
+ findPositionForNewNodeBestLongSideFit(width, height, allowRectRotation) {
814
+ const bestNode = MaxRectsCompat.helperRect;
815
+ bestNode.score1 = 0;
816
+ bestNode.score2 = MAX_SCORE;
817
+ for (const freeRect of this.freeRectangles) {
818
+ if (freeRect.width >= width && freeRect.height >= height) {
819
+ const leftoverHoriz = Math.abs(freeRect.width - width);
820
+ const leftoverVert = Math.abs(freeRect.height - height);
821
+ const shortSideFit = Math.min(leftoverHoriz, leftoverVert);
822
+ const longSideFit = Math.max(leftoverHoriz, leftoverVert);
823
+ if (longSideFit < bestNode.score2 || longSideFit === bestNode.score2 && shortSideFit < bestNode.score1) setNodeRect(bestNode, freeRect.x, freeRect.y, width, height, false, shortSideFit, longSideFit);
824
+ }
825
+ if (this.allowRotations && allowRectRotation && freeRect.width >= height && freeRect.height >= width) {
826
+ const leftoverHoriz = Math.abs(freeRect.width - height);
827
+ const leftoverVert = Math.abs(freeRect.height - width);
828
+ const shortSideFit = Math.min(leftoverHoriz, leftoverVert);
829
+ const longSideFit = Math.max(leftoverHoriz, leftoverVert);
830
+ if (longSideFit < bestNode.score2 || longSideFit === bestNode.score2 && shortSideFit < bestNode.score1) setNodeRect(bestNode, freeRect.x, freeRect.y, height, width, true, shortSideFit, longSideFit);
831
+ }
832
+ }
833
+ return bestNode;
834
+ }
835
+ findPositionForNewNodeBestAreaFit(width, height, allowRectRotation) {
836
+ const bestNode = MaxRectsCompat.helperRect;
837
+ bestNode.score1 = MAX_SCORE;
838
+ bestNode.score2 = 0;
839
+ for (const freeRect of this.freeRectangles) {
840
+ const areaFit = freeRect.width * freeRect.height - width * height;
841
+ if (freeRect.width >= width && freeRect.height >= height) {
842
+ const leftoverHoriz = Math.abs(freeRect.width - width);
843
+ const leftoverVert = Math.abs(freeRect.height - height);
844
+ const shortSideFit = Math.min(leftoverHoriz, leftoverVert);
845
+ if (areaFit < bestNode.score1 || areaFit === bestNode.score1 && shortSideFit < bestNode.score2) setNodeRect(bestNode, freeRect.x, freeRect.y, width, height, false, areaFit, shortSideFit);
846
+ }
847
+ if (this.allowRotations && allowRectRotation && freeRect.width >= height && freeRect.height >= width) {
848
+ const leftoverHoriz = Math.abs(freeRect.width - height);
849
+ const leftoverVert = Math.abs(freeRect.height - width);
850
+ const shortSideFit = Math.min(leftoverHoriz, leftoverVert);
851
+ if (areaFit < bestNode.score1 || areaFit === bestNode.score1 && shortSideFit < bestNode.score2) setNodeRect(bestNode, freeRect.x, freeRect.y, height, width, true, areaFit, shortSideFit);
852
+ }
853
+ }
854
+ return bestNode;
855
+ }
856
+ findPositionForNewNodeContactPoint(width, height, allowRectRotation) {
857
+ const bestNode = MaxRectsCompat.helperRect;
858
+ bestNode.score1 = -1;
859
+ bestNode.score2 = 0;
860
+ for (const freeRect of this.freeRectangles) {
861
+ if (freeRect.width >= width && freeRect.height >= height) {
862
+ const score = this.contactPointScoreNode(freeRect.x, freeRect.y, width, height);
863
+ if (score > bestNode.score1) setNodeRect(bestNode, freeRect.x, freeRect.y, width, height, false, score, bestNode.score2);
864
+ }
865
+ if (this.allowRotations && allowRectRotation && freeRect.width >= height && freeRect.height >= width) {
866
+ const score = this.contactPointScoreNode(freeRect.x, freeRect.y, height, width);
867
+ if (score > bestNode.score1) setNodeRect(bestNode, freeRect.x, freeRect.y, height, width, true, score, bestNode.score2);
868
+ }
869
+ }
870
+ return bestNode;
871
+ }
872
+ contactPointScoreNode(x, y, width, height) {
873
+ let score = 0;
874
+ if (x === 0 || x + width === this.binWidth) score += height;
875
+ if (y === 0 || y + height === this.binHeight) score += width;
876
+ for (const rect of this.usedRectangles) {
877
+ if (rect.x === x + width || rect.x + rect.width === x) score += commonIntervalLength(rect.y, rect.y + rect.height, y, y + height);
878
+ if (rect.y === y + height || rect.y + rect.height === y) score += commonIntervalLength(rect.x, rect.x + rect.width, x, x + width);
879
+ }
880
+ return score;
881
+ }
882
+ splitFreeNode(freeNode, usedNode) {
883
+ if (usedNode.x >= freeNode.x + freeNode.width || usedNode.x + usedNode.width <= freeNode.x || usedNode.y >= freeNode.y + freeNode.height || usedNode.y + usedNode.height <= freeNode.y) return false;
884
+ if (usedNode.x < freeNode.x + freeNode.width && usedNode.x + usedNode.width > freeNode.x) {
885
+ if (usedNode.y > freeNode.y && usedNode.y < freeNode.y + freeNode.height) {
886
+ const newNode = cloneNodeRect(freeNode);
887
+ newNode.height = usedNode.y - newNode.y;
888
+ this.freeRectangles.push(newNode);
889
+ }
890
+ if (usedNode.y + usedNode.height < freeNode.y + freeNode.height) {
891
+ const newNode = cloneNodeRect(freeNode);
892
+ newNode.y = usedNode.y + usedNode.height;
893
+ newNode.height = freeNode.y + freeNode.height - (usedNode.y + usedNode.height);
894
+ this.freeRectangles.push(newNode);
895
+ }
896
+ }
897
+ if (usedNode.y < freeNode.y + freeNode.height && usedNode.y + usedNode.height > freeNode.y) {
898
+ if (usedNode.x > freeNode.x && usedNode.x < freeNode.x + freeNode.width) {
899
+ const newNode = cloneNodeRect(freeNode);
900
+ newNode.width = usedNode.x - newNode.x;
901
+ this.freeRectangles.push(newNode);
902
+ }
903
+ if (usedNode.x + usedNode.width < freeNode.x + freeNode.width) {
904
+ const newNode = cloneNodeRect(freeNode);
905
+ newNode.x = usedNode.x + usedNode.width;
906
+ newNode.width = freeNode.x + freeNode.width - (usedNode.x + usedNode.width);
907
+ this.freeRectangles.push(newNode);
908
+ }
909
+ }
910
+ return true;
911
+ }
912
+ pruneFreeList() {
913
+ let length = this.freeRectangles.length;
914
+ let left = 0;
915
+ while (left < length) {
916
+ let right = left + 1;
917
+ while (right < length) {
918
+ if (isContainedIn(this.freeRectangles[left], this.freeRectangles[right])) {
919
+ this.freeRectangles.splice(left, 1);
920
+ length -= 1;
921
+ break;
922
+ }
923
+ if (isContainedIn(this.freeRectangles[right], this.freeRectangles[left])) {
924
+ this.freeRectangles.splice(right, 1);
925
+ length -= 1;
926
+ }
927
+ right += 1;
928
+ }
929
+ left += 1;
930
+ }
931
+ }
932
+ };
933
+ function createNodeRect() {
934
+ return {
935
+ x: 0,
936
+ y: 0,
937
+ width: 0,
938
+ height: 0,
939
+ rotated: false,
940
+ index: 0,
941
+ subIndex: -1,
942
+ flags: 0,
943
+ score1: 0,
944
+ score2: 0,
945
+ sourceKind: void 0
946
+ };
947
+ }
948
+ function cloneNodeRect(rect) {
949
+ return { ...rect };
950
+ }
951
+ function copyNodeRect(target, source) {
952
+ target.x = source.x;
953
+ target.y = source.y;
954
+ target.width = source.width;
955
+ target.height = source.height;
956
+ target.rotated = source.rotated;
957
+ target.index = source.index;
958
+ target.subIndex = source.subIndex;
959
+ target.flags = source.flags;
960
+ target.score1 = source.score1;
961
+ target.score2 = source.score2;
962
+ target.sourceKind = source.sourceKind;
963
+ }
964
+ function setNodeRect(target, x, y, width, height, rotated, score1, score2) {
965
+ target.x = x;
966
+ target.y = y;
967
+ target.width = width;
968
+ target.height = height;
969
+ target.rotated = rotated;
970
+ target.score1 = score1;
971
+ target.score2 = score2;
972
+ }
973
+ function allowRotation(rect) {
974
+ return (rect.flags & NO_ROTATION) === 0;
975
+ }
976
+ function commonIntervalLength(startA, endA, startB, endB) {
977
+ if (endA < startB || endB < startA) return 0;
978
+ return Math.min(endA, endB) - Math.max(startA, startB);
979
+ }
980
+ function isContainedIn(left, right) {
981
+ return left.x >= right.x && left.y >= right.y && left.x + left.width <= right.x + right.width && left.y + left.height <= right.y + right.height;
982
+ }
983
+ //#endregion
984
+ //#region src/max-rects-packer-compat.ts
985
+ const DEFAULT_SETTINGS = {
986
+ pot: true,
987
+ mof: true,
988
+ padding: 2,
989
+ rotation: false,
990
+ minWidth: 16,
991
+ minHeight: 16,
992
+ maxWidth: 2048,
993
+ maxHeight: 2048,
994
+ square: false,
995
+ fast: true,
996
+ edgePadding: false,
997
+ duplicatePadding: false,
998
+ multiPage: false,
999
+ preserveInputOrderOnTie: false
1000
+ };
1001
+ let sizeScheme = null;
1002
+ var BinarySearchCompat = class {
1003
+ min;
1004
+ max;
1005
+ fuzziness;
1006
+ low;
1007
+ high;
1008
+ current;
1009
+ constructor(min, max, fuzziness, pot, mof) {
1010
+ this.pot = pot;
1011
+ this.mof = mof;
1012
+ this.fuzziness = pot ? 0 : fuzziness;
1013
+ if (pot) {
1014
+ this.min = Math.log(MaxRectsPackerCompat.getNextPowerOfTwo(min)) / Math.log(2);
1015
+ this.max = Math.log(MaxRectsPackerCompat.getNextPowerOfTwo(max)) / Math.log(2);
1016
+ } else if (mof) {
1017
+ this.min = min / 4;
1018
+ this.max = max / 4;
1019
+ } else {
1020
+ this.min = min;
1021
+ this.max = max;
1022
+ }
1023
+ this.low = this.min;
1024
+ this.high = this.max;
1025
+ this.current = this.min;
1026
+ }
1027
+ reset() {
1028
+ this.low = this.min;
1029
+ this.high = this.max;
1030
+ this.current = this.low + this.high >>> 1;
1031
+ return this.getCurrent();
1032
+ }
1033
+ next(failed) {
1034
+ if (this.low >= this.high) return -1;
1035
+ if (failed) this.low = this.current + 1;
1036
+ else this.high = this.current - 1;
1037
+ this.current = this.low + this.high >>> 1;
1038
+ if (Math.abs(this.low - this.high) < this.fuzziness) return -1;
1039
+ return this.getCurrent();
1040
+ }
1041
+ getCurrent() {
1042
+ if (this.pot) return Math.trunc(2 ** this.current);
1043
+ if (this.mof) return this.current * 4;
1044
+ return this.current;
1045
+ }
1046
+ };
1047
+ var MaxRectsPackerCompat = class MaxRectsPackerCompat {
1048
+ maxRects = new MaxRectsCompat();
1049
+ settings;
1050
+ constructor(settings = {}) {
1051
+ this.settings = {
1052
+ ...DEFAULT_SETTINGS,
1053
+ ...settings
1054
+ };
1055
+ }
1056
+ static getNextPowerOfTwo(value) {
1057
+ if (Number.isInteger(value) && value > 0 && (value & value - 1) === 0) return value;
1058
+ let result = 1;
1059
+ const target = value - 1e-9;
1060
+ while (result < target) result <<= 1;
1061
+ return result;
1062
+ }
1063
+ pack(inputRects) {
1064
+ const rects = inputRects.map(cloneCompatRect);
1065
+ if (this.settings.fast) vectorSortCompat(rects, this.settings.preserveInputOrderOnTie ? this.settings.rotation ? compareNodeRectStable : compareNodeRect2Stable : this.settings.rotation ? compareNodeRect : compareNodeRect2);
1066
+ const padding = this.settings.padding;
1067
+ let hasDuplicatePadding = false;
1068
+ for (const rect of rects) {
1069
+ if (duplicatePadding(rect)) hasDuplicatePadding = true;
1070
+ if (this.settings.maxWidth - rect.width > padding || duplicatePadding(rect)) rect.width += padding;
1071
+ if (this.settings.maxHeight - rect.height > padding || duplicatePadding(rect)) rect.height += padding;
1072
+ }
1073
+ const pages = [];
1074
+ let remaining = rects;
1075
+ while (remaining.length > 0) {
1076
+ const page = this.packPage(remaining);
1077
+ if (!page) return null;
1078
+ if (this.settings.pot) {
1079
+ page.width = MaxRectsPackerCompat.getNextPowerOfTwo(page.width);
1080
+ page.height = MaxRectsPackerCompat.getNextPowerOfTwo(page.height);
1081
+ } else if (this.settings.mof) {
1082
+ page.width = Math.ceil(page.width / 4) * 4;
1083
+ page.height = Math.ceil(page.height / 4) * 4;
1084
+ }
1085
+ if (this.settings.square) {
1086
+ const side = Math.max(page.width, page.height);
1087
+ page.width = side;
1088
+ page.height = side;
1089
+ }
1090
+ pages.push(page);
1091
+ remaining = page.remainingRects.map(cloneCompatRect);
1092
+ }
1093
+ pages.sort(comparePage);
1094
+ for (const page of pages) {
1095
+ for (const rect of page.outputRects) {
1096
+ shrinkRectForPadding(rect, padding, this.settings.maxWidth, this.settings.maxHeight);
1097
+ if (hasDuplicatePadding) {
1098
+ if (rect.width !== page.width) rect.x += Math.floor(padding / 2);
1099
+ if (rect.height !== page.height) rect.y += Math.floor(padding / 2);
1100
+ }
1101
+ }
1102
+ for (const rect of page.remainingRects) shrinkRectForPadding(rect, padding, this.settings.maxWidth, this.settings.maxHeight);
1103
+ }
1104
+ return pages;
1105
+ }
1106
+ packPage(rects) {
1107
+ if (!sizeScheme) sizeScheme = initSizeScheme();
1108
+ const edgePadding = this.settings.edgePadding ? this.settings.padding : 0;
1109
+ let totalArea = 0;
1110
+ for (const rect of rects) totalArea += rect.width * rect.height;
1111
+ const candidates = sizeScheme.filter((entry) => entry.area >= totalArea && entry.width <= this.settings.maxWidth && entry.height <= this.settings.maxHeight);
1112
+ if (candidates.length === 0) candidates.push({
1113
+ width: this.settings.maxWidth,
1114
+ height: this.settings.maxHeight,
1115
+ area: 0,
1116
+ aspectRatio: 0,
1117
+ len: 0
1118
+ });
1119
+ let page = null;
1120
+ let selectedWidth = 0;
1121
+ let selectedHeight = 0;
1122
+ for (let index = 0; index < candidates.length; index += 1) {
1123
+ selectedWidth = candidates[index].width;
1124
+ selectedHeight = candidates[index].height;
1125
+ page = this.packAtSize(index !== candidates.length - 1, selectedWidth - edgePadding, selectedHeight - edgePadding, rects);
1126
+ if (page) break;
1127
+ }
1128
+ if (page && !this.settings.pot && page.remainingRects.length === 0) {
1129
+ let bestRefined = null;
1130
+ if (this.settings.square) {
1131
+ const search = new BinarySearchCompat(Math.min(selectedWidth / 2, selectedHeight / 2), Math.max(selectedWidth, selectedHeight), this.settings.fast ? 25 : 15, this.settings.pot, this.settings.mof);
1132
+ let current = search.reset();
1133
+ while (current !== -1) {
1134
+ const refined = this.packAtSize(true, current - edgePadding, current - edgePadding, rects);
1135
+ bestRefined = getBestPage(bestRefined, refined);
1136
+ current = search.next(refined == null);
1137
+ }
1138
+ } else {
1139
+ const widthSearch = new BinarySearchCompat(selectedWidth / 2, selectedWidth, this.settings.fast ? 25 : 15, this.settings.pot, this.settings.mof);
1140
+ const heightSearch = new BinarySearchCompat(selectedHeight / 2, selectedHeight, this.settings.fast ? 25 : 15, this.settings.pot, this.settings.mof);
1141
+ let currentHeight = heightSearch.reset();
1142
+ let currentWidth = widthSearch.reset();
1143
+ while (true) {
1144
+ let bestForHeight = null;
1145
+ while (currentWidth !== -1) {
1146
+ const refined = this.packAtSize(true, currentWidth - edgePadding, currentHeight - edgePadding, rects);
1147
+ bestForHeight = getBestPage(bestForHeight, refined);
1148
+ currentWidth = widthSearch.next(refined == null);
1149
+ }
1150
+ bestRefined = getBestPage(bestRefined, bestForHeight);
1151
+ currentHeight = heightSearch.next(bestForHeight == null);
1152
+ if (currentHeight === -1) break;
1153
+ currentWidth = widthSearch.reset();
1154
+ }
1155
+ }
1156
+ if (bestRefined) page = bestRefined;
1157
+ }
1158
+ return page;
1159
+ }
1160
+ packAtSize(requireFullFit, width, height, rects) {
1161
+ const methods = [
1162
+ MAX_RECTS_METHOD.BestShortSideFit,
1163
+ MAX_RECTS_METHOD.BestLongSideFit,
1164
+ MAX_RECTS_METHOD.BestAreaFit
1165
+ ];
1166
+ let best = null;
1167
+ for (const method of methods) {
1168
+ this.maxRects.init(width, height, this.settings.rotation);
1169
+ let page;
1170
+ if (!this.settings.fast) page = this.maxRects.pack(rects, method);
1171
+ else {
1172
+ const remaining = [];
1173
+ let index = 0;
1174
+ while (index < rects.length) {
1175
+ if (this.maxRects.insert(rects[index], method) == null) {
1176
+ while (index < rects.length) {
1177
+ remaining.push(cloneCompatRect(rects[index]));
1178
+ index += 1;
1179
+ }
1180
+ break;
1181
+ }
1182
+ index += 1;
1183
+ }
1184
+ page = this.maxRects.getResult();
1185
+ page.remainingRects = remaining;
1186
+ }
1187
+ if (!(requireFullFit && page.remainingRects.length > 0) && page.outputRects.length !== 0) best = getBestPage(best, page);
1188
+ }
1189
+ return best;
1190
+ }
1191
+ };
1192
+ function vectorSortCompat(items, compare) {
1193
+ if (items.length <= 1) return;
1194
+ avmQuickSortCompat(items, 0, items.length - 1, compare);
1195
+ }
1196
+ function avmQuickSortCompat(items, initialLo, initialHi, compare) {
1197
+ if (initialLo >= initialHi) return;
1198
+ const stack = [];
1199
+ let lo = initialLo;
1200
+ let hi = initialHi;
1201
+ while (true) {
1202
+ const size = hi - lo + 1;
1203
+ if (size < 4) {
1204
+ if (size === 3) {
1205
+ if (compare(items[lo], items[lo + 1]) > 0) {
1206
+ swapCompat(items, lo, lo + 1);
1207
+ if (compare(items[lo + 1], items[lo + 2]) > 0) {
1208
+ swapCompat(items, lo + 1, lo + 2);
1209
+ if (compare(items[lo], items[lo + 1]) > 0) swapCompat(items, lo, lo + 1);
1210
+ }
1211
+ } else if (compare(items[lo + 1], items[lo + 2]) > 0) {
1212
+ swapCompat(items, lo + 1, lo + 2);
1213
+ if (compare(items[lo], items[lo + 1]) > 0) swapCompat(items, lo, lo + 1);
1214
+ }
1215
+ } else if (size === 2 && compare(items[lo], items[lo + 1]) > 0) swapCompat(items, lo, lo + 1);
1216
+ } else {
1217
+ swapCompat(items, lo + (size >> 1), lo);
1218
+ let left = lo;
1219
+ let right = hi + 1;
1220
+ while (true) {
1221
+ do
1222
+ left += 1;
1223
+ while (left <= hi && compare(items[left], items[lo]) <= 0);
1224
+ do
1225
+ right -= 1;
1226
+ while (right > lo && compare(items[right], items[lo]) >= 0);
1227
+ if (right < left) break;
1228
+ swapCompat(items, left, right);
1229
+ }
1230
+ swapCompat(items, lo, right);
1231
+ if (right - 1 - lo >= hi - left) {
1232
+ if (lo + 1 < right) stack.push({
1233
+ lo,
1234
+ hi: right - 1
1235
+ });
1236
+ if (left < hi) {
1237
+ lo = left;
1238
+ continue;
1239
+ }
1240
+ } else {
1241
+ if (left < hi) stack.push({
1242
+ lo: left,
1243
+ hi
1244
+ });
1245
+ if (lo + 1 < right) {
1246
+ hi = right - 1;
1247
+ continue;
1248
+ }
1249
+ }
1250
+ }
1251
+ if (stack.length === 0) return;
1252
+ const frame = stack.pop();
1253
+ lo = frame.lo;
1254
+ hi = frame.hi;
1255
+ }
1256
+ }
1257
+ function swapCompat(items, left, right) {
1258
+ const value = items[left];
1259
+ items[left] = items[right];
1260
+ items[right] = value;
1261
+ }
1262
+ function initSizeScheme() {
1263
+ const result = [];
1264
+ for (let w = 5; w <= 13; w += 1) for (let h = 5; h <= 13; h += 1) {
1265
+ const width = 2 ** w;
1266
+ const height = 2 ** h;
1267
+ const area = width * height;
1268
+ const aspectRatio = width > height ? width / height : height / width;
1269
+ result.push({
1270
+ width,
1271
+ height,
1272
+ area,
1273
+ aspectRatio,
1274
+ len: Math.max(width, height)
1275
+ });
1276
+ }
1277
+ result.sort(compareSizeScheme);
1278
+ return result;
1279
+ }
1280
+ function compareSizeScheme(left, right) {
1281
+ if (left.len < right.len) return -1;
1282
+ if (left.len > right.len) return 1;
1283
+ if (left.area < right.area) return -1;
1284
+ if (left.area > right.area) return 1;
1285
+ if (left.aspectRatio < right.aspectRatio) return -1;
1286
+ if (left.aspectRatio > right.aspectRatio) return 1;
1287
+ if (left.width > left.height) return -1;
1288
+ if (right.width > right.height) return 1;
1289
+ return 0;
1290
+ }
1291
+ function getBestPage(left, right) {
1292
+ if (!left) return right;
1293
+ if (!right) return left;
1294
+ return left.occupancy > right.occupancy ? left : right;
1295
+ }
1296
+ function comparePage(left, right) {
1297
+ return right.outputRects.length - left.outputRects.length;
1298
+ }
1299
+ function compareNodeRect(left, right) {
1300
+ const leftEdge = left.width > left.height ? left.width : left.height;
1301
+ return (right.width > right.height ? right.width : right.height) - leftEdge;
1302
+ }
1303
+ function compareNodeRectStable(left, right) {
1304
+ const delta = compareNodeRect(left, right);
1305
+ if (delta !== 0) return delta;
1306
+ if (left.sourceKind === "movieclip-frame" && right.sourceKind === "movieclip-frame") {
1307
+ const areaDelta = right.width * right.height - left.width * left.height;
1308
+ if (areaDelta !== 0) return areaDelta;
1309
+ const widthDelta = right.width - left.width;
1310
+ if (widthDelta !== 0) return widthDelta;
1311
+ }
1312
+ return left.index - right.index;
1313
+ }
1314
+ function compareNodeRect2(left, right) {
1315
+ return right.width - left.width;
1316
+ }
1317
+ function compareNodeRect2Stable(left, right) {
1318
+ const delta = compareNodeRect2(left, right);
1319
+ if (delta !== 0) return delta;
1320
+ if (left.sourceKind === "movieclip-frame" && right.sourceKind === "movieclip-frame") {
1321
+ const areaDelta = right.width * right.height - left.width * left.height;
1322
+ if (areaDelta !== 0) return areaDelta;
1323
+ const heightDelta = right.height - left.height;
1324
+ if (heightDelta !== 0) return heightDelta;
1325
+ }
1326
+ return left.index - right.index;
1327
+ }
1328
+ function duplicatePadding(rect) {
1329
+ return (rect.flags & COMPAT_NODE_RECT_FLAGS.DUPLICATE_PADDING) !== 0;
1330
+ }
1331
+ function shrinkRectForPadding(rect, padding, maxWidth, maxHeight) {
1332
+ if (!rect.rotated) {
1333
+ if (maxWidth - rect.width > padding || duplicatePadding(rect)) rect.width -= padding;
1334
+ if (maxHeight - rect.height > padding || duplicatePadding(rect)) rect.height -= padding;
1335
+ } else {
1336
+ if (maxHeight - rect.width > padding || duplicatePadding(rect)) rect.width -= padding;
1337
+ if (maxWidth - rect.height > padding || duplicatePadding(rect)) rect.height -= padding;
1338
+ }
1339
+ }
1340
+ function cloneCompatRect(rect) {
1341
+ return { ...rect };
1342
+ }
1343
+ //#endregion
1344
+ //#region src/atlas/packing.ts
1345
+ async function emitAtlasInputs(input) {
1346
+ const { doc, pkg, allResources, inputs, options, encoder, logger } = input;
1347
+ let totalPageCount = 0;
1348
+ let usedDirectOutput = false;
1349
+ const { autoInputs, fixedPageGroups, standaloneGroups, reservedPageIndexes } = groupStandaloneInputs(doc, inputs, options);
1350
+ const branchGroups = buildBranchAtlasGroups(doc, autoInputs, options);
1351
+ const branchPageOffsets = /* @__PURE__ */ new Map();
1352
+ for (const group of branchGroups) {
1353
+ const directOutput = fixedPageGroups.length === 0 && standaloneGroups.length === 0 ? resolveDirectImageOutput(group.inputs, options) : null;
1354
+ if (directOutput) {
1355
+ await emitDirectImageOutput(doc, pkg, directOutput, encoder, options, logger, group.branchName, group.branchOrdinal);
1356
+ usedDirectOutput = true;
1357
+ totalPageCount += 1;
1358
+ continue;
1359
+ }
1360
+ const pageStart = reserveAutoPageStart(branchPageOffsets, group.branchOrdinal, reservedPageIndexes);
1361
+ const emittedPageCount = await emitPagedAtlasGroup(doc, pkg, allResources, group.inputs, {
1362
+ branchName: group.branchName,
1363
+ branchOrdinal: group.branchOrdinal,
1364
+ pageStart,
1365
+ fileNameAt: (pageIndex) => resolveAtlasOutputFileName(pkg, pageIndex, group.branchName),
1366
+ options,
1367
+ encoder,
1368
+ logger
1369
+ });
1370
+ totalPageCount += emittedPageCount;
1371
+ branchPageOffsets.set(group.branchOrdinal, pageStart + emittedPageCount);
1372
+ }
1373
+ for (const group of fixedPageGroups) {
1374
+ const emittedPageCount = await emitPagedAtlasGroup(doc, pkg, allResources, group.inputs, {
1375
+ branchName: group.branchName,
1376
+ branchOrdinal: group.branchOrdinal,
1377
+ pageStart: group.pageIndex,
1378
+ forceSinglePage: true,
1379
+ fileNameAt: () => resolveAtlasOutputFileName(pkg, group.pageIndex, group.branchName),
1380
+ options,
1381
+ encoder,
1382
+ logger
1383
+ });
1384
+ totalPageCount += emittedPageCount;
1385
+ }
1386
+ const standalonePageOffsets = new Map(branchPageOffsets);
1387
+ for (const group of fixedPageGroups) {
1388
+ const nextPageIndex = group.pageIndex + 1;
1389
+ if (nextPageIndex > (standalonePageOffsets.get(group.branchOrdinal) ?? 0)) standalonePageOffsets.set(group.branchOrdinal, nextPageIndex);
1390
+ }
1391
+ for (const group of standaloneGroups) {
1392
+ const emittedPageCount = await emitStandaloneAtlasGroup(doc, pkg, group, {
1393
+ atlasIndexStart: standalonePageOffsets.get(group.branchOrdinal) ?? 0,
1394
+ options,
1395
+ encoder,
1396
+ logger
1397
+ });
1398
+ totalPageCount += emittedPageCount;
1399
+ standalonePageOffsets.set(group.branchOrdinal, (standalonePageOffsets.get(group.branchOrdinal) ?? 0) + emittedPageCount);
1400
+ }
1401
+ if (usedDirectOutput) logger.info(`atlas: Direct output for single image package "${pkg.getName()}".`);
1402
+ logger.info(`atlas: Packed ${inputs.length} images into ${totalPageCount} atlas(es) for package "${pkg.getName()}".`);
1403
+ }
1404
+ function buildBranchAtlasGroups(doc, inputs, options) {
1405
+ if (!options.separatedAtlasForBranch) return [{
1406
+ branchName: "",
1407
+ branchOrdinal: 0,
1408
+ inputs
1409
+ }];
1410
+ const discoveredBranchNames = [...new Set(inputs.map((input) => getInputBranchName(input)).filter((branchName) => !!branchName))];
1411
+ if (discoveredBranchNames.length === 0) return [{
1412
+ branchName: "",
1413
+ branchOrdinal: 0,
1414
+ inputs
1415
+ }];
1416
+ const orderedBranchNames = doc.getRoot().listBranches().filter((branchName) => discoveredBranchNames.includes(branchName));
1417
+ for (const branchName of discoveredBranchNames) if (!orderedBranchNames.includes(branchName)) orderedBranchNames.push(branchName);
1418
+ const groups = /* @__PURE__ */ new Map();
1419
+ groups.set("", []);
1420
+ for (const branchName of orderedBranchNames) groups.set(branchName, []);
1421
+ for (const input of inputs) {
1422
+ const branchName = getInputBranchName(input);
1423
+ const key = groups.has(branchName) ? branchName : "";
1424
+ groups.get(key).push(input);
1425
+ }
1426
+ const orderedKeys = [""];
1427
+ for (const branchName of orderedBranchNames) if ((groups.get(branchName)?.length ?? 0) > 0) orderedKeys.push(branchName);
1428
+ return orderedKeys.filter((branchName) => (groups.get(branchName)?.length ?? 0) > 0).map((branchName, index) => ({
1429
+ branchName,
1430
+ branchOrdinal: index,
1431
+ inputs: groups.get(branchName) ?? []
1432
+ }));
1433
+ }
1434
+ function reserveAutoPageStart(branchPageOffsets, branchOrdinal, reservedPageIndexes) {
1435
+ let pageIndex = branchPageOffsets.get(branchOrdinal) ?? 0;
1436
+ while (branchOrdinal === 0 && reservedPageIndexes.has(pageIndex)) pageIndex += 1;
1437
+ return pageIndex;
1438
+ }
1439
+ async function emitPagedAtlasGroup(doc, pkg, allResources, inputs, context) {
1440
+ if (inputs.length === 0) return 0;
1441
+ const pages = packAtlasPages(inputs, context.options, context.forceSinglePage === true);
1442
+ assertPackedInputCoverage(pages, inputs.length, `package "${pkg.getName()}"`);
1443
+ for (let pageOffset = 0; pageOffset < pages.length; pageOffset += 1) {
1444
+ const page = pages[pageOffset];
1445
+ const pageIndex = context.pageStart + pageOffset;
1446
+ const atlasNode = doc.createAtlas(`atlas${resolveAtlasIndex(context.branchOrdinal, pageIndex)}`);
1447
+ atlasNode.setIndex(resolveAtlasIndex(context.branchOrdinal, pageIndex));
1448
+ atlasNode.setFile(context.fileNameAt(pageIndex));
1449
+ atlasNode.setWidth(page.width);
1450
+ atlasNode.setHeight(page.height);
1451
+ pkg.addAtlas(atlasNode);
1452
+ attachSpritesToAtlas(doc, allResources, inputs, page.outputRects, atlasNode);
1453
+ await writeAtlasPageImage(pkg, inputs, page, atlasNode.getFile(), context.encoder, context.options, context.logger);
1454
+ }
1455
+ return pages.length;
1456
+ }
1457
+ async function emitStandaloneAtlasGroup(doc, pkg, group, context) {
1458
+ if (group.inputs.length === 0) return 0;
1459
+ const pages = packAtlasPages(group.inputs, context.options, true, group.sizeMode === "npot" ? {
1460
+ powerOfTwo: false,
1461
+ multipleOfFour: false,
1462
+ square: false
1463
+ } : group.sizeMode === "multipleOf4" ? {
1464
+ powerOfTwo: false,
1465
+ multipleOfFour: true,
1466
+ square: false
1467
+ } : void 0);
1468
+ assertPackedInputCoverage(pages, group.inputs.length, `standalone texture in package "${pkg.getName()}"`);
1469
+ for (let pageOffset = 0; pageOffset < pages.length; pageOffset += 1) {
1470
+ const page = pages[pageOffset];
1471
+ const baseFileName = resolveStandaloneAtlasOutputFileName(pkg, group.resource, group.branchName);
1472
+ const atlasFileName = pages.length <= 1 ? baseFileName : insertFileNameSuffix(baseFileName, `_${pageOffset}`);
1473
+ const atlasIndex = context.atlasIndexStart + pageOffset;
1474
+ const atlasNode = doc.createAtlas(`atlas${resolveAtlasIndex(group.branchOrdinal, atlasIndex)}`);
1475
+ atlasNode.setIndex(resolveAtlasIndex(group.branchOrdinal, atlasIndex));
1476
+ atlasNode.setFile(atlasFileName);
1477
+ const standaloneSize = resolveStandaloneAtlasSize(page.width, page.height, group.sizeMode, context.options);
1478
+ atlasNode.setWidth(standaloneSize.width);
1479
+ atlasNode.setHeight(standaloneSize.height);
1480
+ pkg.addAtlas(atlasNode);
1481
+ attachSpritesToAtlas(doc, [], group.inputs, page.outputRects, atlasNode);
1482
+ await writeAtlasPageImage(pkg, group.inputs, {
1483
+ ...page,
1484
+ width: standaloneSize.width,
1485
+ height: standaloneSize.height
1486
+ }, atlasFileName, context.encoder, context.options, context.logger);
1487
+ }
1488
+ return pages.length;
1489
+ }
1490
+ function packAtlasPages(inputs, options, forceSinglePage, sizeOverrides) {
1491
+ const hasDuplicatePadding = inputs.some((input) => {
1492
+ return isImageResource$1(input.resource) && input.resource.getDuplicatePadding?.() === true;
1493
+ });
1494
+ return new MaxRectsPackerCompat({
1495
+ pot: sizeOverrides?.powerOfTwo ?? options.powerOfTwo,
1496
+ mof: sizeOverrides?.multipleOfFour ?? !options.powerOfTwo,
1497
+ padding: options.padding,
1498
+ rotation: options.allowRotation,
1499
+ minWidth: 16,
1500
+ minHeight: 16,
1501
+ maxWidth: options.maxSize,
1502
+ maxHeight: options.maxSize,
1503
+ square: sizeOverrides?.square ?? options.square,
1504
+ fast: options.fast,
1505
+ edgePadding: false,
1506
+ duplicatePadding: hasDuplicatePadding,
1507
+ multiPage: forceSinglePage ? false : options.multiPage,
1508
+ preserveInputOrderOnTie: options.preserveInputOrderOnTie
1509
+ }).pack(inputs.map((input, index) => inputToCompatRect(input, index))) ?? [];
1510
+ }
1511
+ function assertPackedInputCoverage(pages, inputCount, label) {
1512
+ const packedIndexes = /* @__PURE__ */ new Set();
1513
+ for (const page of pages) for (const outputRect of page.outputRects) packedIndexes.add(outputRect.index);
1514
+ const hasEveryInput = Array.from({ length: inputCount }, (_, index) => packedIndexes.has(index)).every(Boolean);
1515
+ if (packedIndexes.size !== inputCount || !hasEveryInput) throw new Error(`atlas: Could not pack every input for ${label}.`);
1516
+ }
1517
+ function attachSpritesToAtlas(doc, allResources, inputs, outputRects, atlasNode) {
1518
+ for (const packedRect of outputRects) {
1519
+ const input = inputs[packedRect.index];
1520
+ if (!input) continue;
1521
+ const packedSize = resolvePackedRectSize(input, packedRect.width, packedRect.height, packedRect.rotated);
1522
+ const sprite = doc.createSprite();
1523
+ sprite.setItemId(input.id);
1524
+ sprite.setRectX(packedRect.x);
1525
+ sprite.setRectY(packedRect.y);
1526
+ sprite.setRectWidth(packedSize.width);
1527
+ sprite.setRectHeight(packedSize.height);
1528
+ sprite.setRotated(packedRect.rotated);
1529
+ sprite.setOffsetX(input.offsetX);
1530
+ sprite.setOffsetY(input.offsetY);
1531
+ sprite.setOriginalWidth(input.originalWidth);
1532
+ sprite.setOriginalHeight(input.originalHeight);
1533
+ sprite.setAtlas(atlasNode);
1534
+ atlasNode.addSprite(sprite);
1535
+ }
1536
+ for (const resource of allResources) {
1537
+ if (!isFontResource$1(resource)) continue;
1538
+ const alias = resource.getExtras()?._fontSpriteAlias;
1539
+ if (!alias) continue;
1540
+ const imageSprite = outputRects.find((result) => inputs[result.index]?.id === alias.textureId);
1541
+ if (!imageSprite) continue;
1542
+ const imageInput = inputs[imageSprite.index];
1543
+ const fontSprite = doc.createSprite();
1544
+ fontSprite.setItemId(alias.fontId);
1545
+ fontSprite.setRectX(imageSprite.x);
1546
+ fontSprite.setRectY(imageSprite.y);
1547
+ fontSprite.setRectWidth(imageSprite.width);
1548
+ fontSprite.setRectHeight(imageSprite.height);
1549
+ fontSprite.setRotated(imageSprite.rotated);
1550
+ if (imageInput) {
1551
+ fontSprite.setOffsetX(imageInput.offsetX);
1552
+ fontSprite.setOffsetY(imageInput.offsetY);
1553
+ fontSprite.setOriginalWidth(imageInput.originalWidth);
1554
+ fontSprite.setOriginalHeight(imageInput.originalHeight);
1555
+ }
1556
+ fontSprite.setAtlas(atlasNode);
1557
+ atlasNode.addSprite(fontSprite);
1558
+ }
1559
+ }
1560
+ async function writeAtlasPageImage(pkg, inputs, page, atlasFileName, encoder, options, logger) {
1561
+ if (!encoder || !options.outputPath) return;
1562
+ if (options.mkdir) await options.mkdir(options.outputPath);
1563
+ const compositeInputs = [];
1564
+ for (const packedRect of page.outputRects) {
1565
+ const input = inputs[packedRect.index];
1566
+ if (!input) continue;
1567
+ if (packedRect.width <= 0 || packedRect.height <= 0 || input.width <= 0 || input.height <= 0) continue;
1568
+ try {
1569
+ let imageBuffer;
1570
+ if (input.trimBuffer) {
1571
+ imageBuffer = input.trimBuffer;
1572
+ if (imageBuffer.length === 0) continue;
1573
+ } else if (input.rasterizedBuffer) imageBuffer = input.rasterizedBuffer;
1574
+ else {
1575
+ if (!isImageResource$1(input.resource)) {
1576
+ const message = `atlas: Non-image input "${input.id}" is missing inline buffer.`;
1577
+ if (options.strictOutput) throw new Error(message);
1578
+ logger.warn(`${message} Skipping compositing.`);
1579
+ continue;
1580
+ }
1581
+ imageBuffer = await encoder(resolveImagePath$1(input.resource, pkg, options.basePath)).toBuffer();
1582
+ }
1583
+ if (packedRect.rotated) imageBuffer = await encoder(imageBuffer).rotate(270).toBuffer();
1584
+ compositeInputs.push({
1585
+ input: imageBuffer,
1586
+ left: packedRect.x,
1587
+ top: packedRect.y
1588
+ });
1589
+ } catch {
1590
+ const message = `atlas: Could not read image "${input.id}" for compositing.`;
1591
+ if (options.strictOutput) throw new Error(message);
1592
+ logger.warn(message);
1593
+ }
1594
+ }
1595
+ const outputFile = `${options.outputPath}/${atlasFileName}`;
1596
+ await encoder({ create: {
1597
+ width: page.width,
1598
+ height: page.height,
1599
+ channels: 4,
1600
+ background: {
1601
+ r: 0,
1602
+ g: 0,
1603
+ b: 0,
1604
+ alpha: 0
1605
+ }
1606
+ } }).composite(compositeInputs).toFile(outputFile);
1607
+ logger.info(`atlas: Generated ${atlasFileName} (${page.width}x${page.height}, ${page.outputRects.length} sprites)`);
1608
+ }
1609
+ function inputToCompatRect(input, index) {
1610
+ const duplicatePadding = isImageResource$1(input.resource) && input.resource.getDuplicatePadding?.() === true;
1611
+ return {
1612
+ x: 0,
1613
+ y: 0,
1614
+ width: input.width,
1615
+ height: input.height,
1616
+ rotated: false,
1617
+ index,
1618
+ subIndex: -1,
1619
+ flags: duplicatePadding ? COMPAT_NODE_RECT_FLAGS.DUPLICATE_PADDING : 0,
1620
+ score1: 0,
1621
+ score2: 0,
1622
+ sourceKind: input.sourceKind
1623
+ };
1624
+ }
1625
+ function resolvePackedRectSize(input, width, height, rectRotated) {
1626
+ if (!rectRotated) return {
1627
+ width,
1628
+ height
1629
+ };
1630
+ return {
1631
+ width: input.height,
1632
+ height: input.width
1633
+ };
1634
+ }
1635
+ function resolveDirectImageOutput(inputs, options) {
1636
+ if (!options.directSingleImageOutput || options.extractAlpha) return null;
1637
+ if (inputs.length !== 1) return null;
1638
+ const [input] = inputs;
1639
+ if (!input || input.sourceKind !== "image" || !isImageResource$1(input.resource)) return null;
1640
+ if (input.resource.getDuplicatePadding?.() === true) return null;
1641
+ if (input.width !== input.originalWidth || input.height !== input.originalHeight) return null;
1642
+ if (!resolveImageFileName$1(input.resource).toLowerCase().endsWith(".png")) return null;
1643
+ return input;
1644
+ }
1645
+ function resolveDirectOutputAtlasSize(width, height, options) {
1646
+ let resolvedWidth = width;
1647
+ let resolvedHeight = height;
1648
+ if (options.square) {
1649
+ const side = Math.max(resolvedWidth, resolvedHeight);
1650
+ resolvedWidth = side;
1651
+ resolvedHeight = side;
1652
+ }
1653
+ if (options.powerOfTwo) {
1654
+ resolvedWidth = nextPow2(resolvedWidth);
1655
+ resolvedHeight = nextPow2(resolvedHeight);
1656
+ }
1657
+ return {
1658
+ width: resolvedWidth,
1659
+ height: resolvedHeight
1660
+ };
1661
+ }
1662
+ async function emitDirectImageOutput(doc, pkg, input, encoder, options, logger, branchName = "", branchOrdinal = 0) {
1663
+ const atlasFileName = resolveAtlasOutputFileName(pkg, 0, branchName);
1664
+ const atlasSize = resolveDirectOutputAtlasSize(input.originalWidth, input.originalHeight, options);
1665
+ const atlasNode = doc.createAtlas(`atlas${resolveAtlasIndex(branchOrdinal, 0)}`);
1666
+ atlasNode.setIndex(resolveAtlasIndex(branchOrdinal, 0));
1667
+ atlasNode.setFile(atlasFileName);
1668
+ atlasNode.setWidth(atlasSize.width);
1669
+ atlasNode.setHeight(atlasSize.height);
1670
+ pkg.addAtlas(atlasNode);
1671
+ const sprite = doc.createSprite();
1672
+ sprite.setItemId(input.id);
1673
+ sprite.setRectX(0);
1674
+ sprite.setRectY(0);
1675
+ sprite.setRectWidth(input.originalWidth);
1676
+ sprite.setRectHeight(input.originalHeight);
1677
+ sprite.setRotated(false);
1678
+ sprite.setOffsetX(0);
1679
+ sprite.setOffsetY(0);
1680
+ sprite.setOriginalWidth(input.originalWidth);
1681
+ sprite.setOriginalHeight(input.originalHeight);
1682
+ sprite.setAtlas(atlasNode);
1683
+ atlasNode.addSprite(sprite);
1684
+ if (!encoder || !options.outputPath || !isImageResource$1(input.resource) || !options.basePath) return;
1685
+ if (options.mkdir) await options.mkdir(options.outputPath);
1686
+ const outputFile = `${options.outputPath}/${atlasFileName}`;
1687
+ const filePath = resolveImagePath$1(input.resource, pkg, options.basePath);
1688
+ try {
1689
+ if (atlasSize.width === input.originalWidth && atlasSize.height === input.originalHeight) await encoder(filePath).png().toFile(outputFile);
1690
+ else {
1691
+ const imageBuffer = await encoder(filePath).png().toBuffer();
1692
+ await encoder({ create: {
1693
+ width: atlasSize.width,
1694
+ height: atlasSize.height,
1695
+ channels: 4,
1696
+ background: {
1697
+ r: 0,
1698
+ g: 0,
1699
+ b: 0,
1700
+ alpha: 0
1701
+ }
1702
+ } }).composite([{
1703
+ input: imageBuffer,
1704
+ left: 0,
1705
+ top: 0
1706
+ }]).png().toFile(outputFile);
1707
+ }
1708
+ } catch {
1709
+ const message = `atlas: Could not write direct-output atlas "${atlasFileName}".`;
1710
+ if (options.strictOutput) throw new Error(message);
1711
+ logger.warn(message);
1712
+ }
1713
+ }
1714
+ function getInputBranchName(input) {
1715
+ return input.resource.getBranch?.() ?? "";
1716
+ }
1717
+ function resolveAtlasIndex(branchOrdinal, pageIndex) {
1718
+ if (branchOrdinal <= 0) return pageIndex;
1719
+ return branchOrdinal * 100 + pageIndex;
1720
+ }
1721
+ function resolveAtlasOutputFileName(pkg, pageIndex, branchName) {
1722
+ const suffix = branchName ? `_${branchName}` : "";
1723
+ return `${pkg.getPublishName() || pkg.getName()}_atlas${pageIndex}${suffix}.png`;
1724
+ }
1725
+ function resolveStandaloneAtlasOutputFileName(pkg, resource, branchName) {
1726
+ const baseName = `${pkg.getPublishName() || pkg.getName()}_atlas_${getPublishedItemId(resource)}`;
1727
+ const suffix = branchName ? `_${branchName}` : "";
1728
+ if (isImageResource$1(resource)) return `${baseName}${suffix}${extname$1(resolveImageFileName$1(resource)) || ".png"}`;
1729
+ return `${baseName}${suffix}.png`;
1730
+ }
1731
+ function resolveStandaloneAtlasSize(width, height, sizeMode, options) {
1732
+ if (sizeMode === "npot") return {
1733
+ width,
1734
+ height
1735
+ };
1736
+ if (sizeMode === "multipleOf4") return {
1737
+ width: roundUpToMultiple(width, 4),
1738
+ height: roundUpToMultiple(height, 4)
1739
+ };
1740
+ return resolveDirectOutputAtlasSize(width, height, options);
1741
+ }
1742
+ function extname$1(fileName) {
1743
+ const normalized = fileName.replace(/\\/g, "/");
1744
+ const lastSlash = normalized.lastIndexOf("/");
1745
+ const lastDot = normalized.lastIndexOf(".");
1746
+ if (lastDot <= lastSlash) return "";
1747
+ return normalized.slice(lastDot);
1748
+ }
1749
+ function insertFileNameSuffix(fileName, suffix) {
1750
+ const extension = extname$1(fileName);
1751
+ if (!extension) return `${fileName}${suffix}`;
1752
+ return `${fileName.slice(0, -extension.length)}${suffix}${extension}`;
1753
+ }
1754
+ function nextPow2(value) {
1755
+ if (value <= 1) return 1;
1756
+ return 2 ** Math.ceil(Math.log2(value));
1757
+ }
1758
+ function roundUpToMultiple(value, base) {
1759
+ if (value <= 0) return 0;
1760
+ return Math.ceil(value / base) * base;
1761
+ }
1762
+ function sortResourcesByOrder(resources, orderMap, inputOrderMap) {
1763
+ const ordered = [...resources];
1764
+ ordered.sort((left, right) => {
1765
+ const leftId = left.getId();
1766
+ const rightId = right.getId();
1767
+ const leftOrder = leftId && orderMap.has(leftId) ? orderMap.get(leftId) ?? Number.MAX_SAFE_INTEGER : Number.MAX_SAFE_INTEGER;
1768
+ const rightOrder = rightId && orderMap.has(rightId) ? orderMap.get(rightId) ?? Number.MAX_SAFE_INTEGER : Number.MAX_SAFE_INTEGER;
1769
+ if (leftOrder !== rightOrder) return leftOrder - rightOrder;
1770
+ const leftInputOrder = leftId && inputOrderMap.has(leftId) ? inputOrderMap.get(leftId) ?? Number.MAX_SAFE_INTEGER : Number.MAX_SAFE_INTEGER;
1771
+ const rightInputOrder = rightId && inputOrderMap.has(rightId) ? inputOrderMap.get(rightId) ?? Number.MAX_SAFE_INTEGER : Number.MAX_SAFE_INTEGER;
1772
+ if (leftInputOrder !== rightInputOrder) return leftInputOrder - rightInputOrder;
1773
+ return (leftId ?? "").localeCompare(rightId ?? "");
1774
+ });
1775
+ return ordered;
1776
+ }
1777
+ function getResourceTextureSetMode(resource) {
1778
+ if (isImageResource$1(resource)) return parseTextureSetMode(resource.getTextureSetMode?.());
1779
+ return parseTextureSetMode(resource.getTextureSetMode?.());
1780
+ }
1781
+ function groupStandaloneInputs(doc, inputs, options) {
1782
+ const autoInputs = [];
1783
+ const fixedInputsByPage = /* @__PURE__ */ new Map();
1784
+ const standaloneGroups = /* @__PURE__ */ new Map();
1785
+ const reservedPageIndexes = /* @__PURE__ */ new Set();
1786
+ const discoveredBranchNames = [...new Set(inputs.map((input) => getInputBranchName(input)).filter((branchName) => !!branchName))];
1787
+ const orderedBranchNames = doc.getRoot().listBranches().filter((branchName) => discoveredBranchNames.includes(branchName));
1788
+ for (const branchName of discoveredBranchNames) if (!orderedBranchNames.includes(branchName)) orderedBranchNames.push(branchName);
1789
+ const branchOrdinalByName = /* @__PURE__ */ new Map();
1790
+ branchOrdinalByName.set("", 0);
1791
+ if (options.separatedAtlasForBranch) {
1792
+ let ordinal = 1;
1793
+ for (const branchName of orderedBranchNames) branchOrdinalByName.set(branchName, ordinal++);
1794
+ } else for (const branchName of orderedBranchNames) branchOrdinalByName.set(branchName, 0);
1795
+ for (const input of inputs) {
1796
+ const branchName = getInputBranchName(input);
1797
+ const branchOrdinal = branchOrdinalByName.get(branchName) ?? 0;
1798
+ const mode = getResourceTextureSetMode(input.resource);
1799
+ if (mode.kind === "standalone") {
1800
+ const key = `${branchName}\u0000${getPublishedItemId(input.resource)}`;
1801
+ const existing = standaloneGroups.get(key);
1802
+ if (existing) existing.inputs.push(input);
1803
+ else standaloneGroups.set(key, {
1804
+ resource: input.resource,
1805
+ branchName,
1806
+ branchOrdinal,
1807
+ sizeMode: mode.sizeMode,
1808
+ inputs: [input]
1809
+ });
1810
+ continue;
1811
+ }
1812
+ if (mode.kind === "page") {
1813
+ reservedPageIndexes.add(mode.pageIndex);
1814
+ const key = `${branchName}\u0000${mode.pageIndex}`;
1815
+ const existing = fixedInputsByPage.get(key);
1816
+ if (existing) existing.inputs.push(input);
1817
+ else fixedInputsByPage.set(key, {
1818
+ pageIndex: mode.pageIndex,
1819
+ branchName,
1820
+ branchOrdinal,
1821
+ inputs: [input]
1822
+ });
1823
+ continue;
1824
+ }
1825
+ autoInputs.push(input);
1826
+ }
1827
+ return {
1828
+ autoInputs,
1829
+ fixedPageGroups: [...fixedInputsByPage.values()].sort((left, right) => left.branchOrdinal - right.branchOrdinal || left.pageIndex - right.pageIndex),
1830
+ standaloneGroups: [...standaloneGroups.values()].sort((left, right) => left.branchOrdinal - right.branchOrdinal || getPublishedItemId(left.resource).localeCompare(getPublishedItemId(right.resource))),
1831
+ reservedPageIndexes
1832
+ };
1833
+ }
1834
+ //#endregion
1835
+ //#region src/atlas.ts
1836
+ const ATLAS_DEFAULTS = {
1837
+ maxSize: 2048,
1838
+ fast: true,
1839
+ allowRotation: true,
1840
+ padding: 1,
1841
+ powerOfTwo: false,
1842
+ square: false,
1843
+ multiPage: true,
1844
+ trimImage: false,
1845
+ preserveInputOrderOnTie: false,
1846
+ directSingleImageOutput: false,
1847
+ extractAlpha: false,
1848
+ separatedAtlasForBranch: false,
1849
+ strictOutput: false
1850
+ };
1851
+ function getSelectedSkeletonDependencyImageIds(resources) {
1852
+ const imageIds = /* @__PURE__ */ new Set();
1853
+ const resourcesById = new Map(resources.map((resource) => [resource.getId(), resource]));
1854
+ for (const resource of resources) {
1855
+ if (!isSkeletonResource$1(resource)) continue;
1856
+ for (const requiredId of resource.getRequireIds()) {
1857
+ if (!requiredId) continue;
1858
+ const required = resourcesById.get(requiredId);
1859
+ if (required && isImageResource$1(required)) imageIds.add(requiredId);
1860
+ }
1861
+ }
1862
+ return imageIds;
1863
+ }
1864
+ async function resolveEditorCompatibleResourceOrder(pkg, allResources, options) {
1865
+ const pkgId = pkg.getId();
1866
+ const resourceMap = new Map(allResources.map((resource) => [resource.getId(), resource]));
1867
+ const ordered = [];
1868
+ const added = /* @__PURE__ */ new Set();
1869
+ const componentStack = [];
1870
+ async function addResource(resource) {
1871
+ if (!resource) return;
1872
+ const resourceId = resource.getId();
1873
+ if (!resourceId || added.has(resourceId)) return;
1874
+ added.add(resourceId);
1875
+ ordered.push(resource);
1876
+ if (isFontResource$1(resource)) {
1877
+ await addResource(resourceMap.get(resource.getTextureId?.() ?? ""));
1878
+ if (options.readFileRaw && options.basePath) {
1879
+ const fontName = resolveFontFileName(resource.getName());
1880
+ const fontPath = resource.getPath() ?? "/";
1881
+ const fntFile = `${options.basePath}/${pkg.getName()}${fontPath}${fontName}`;
1882
+ try {
1883
+ const fntData = await options.readFileRaw(fntFile);
1884
+ const fntText = new TextDecoder().decode(fntData);
1885
+ for (const line of fntText.split(/\r?\n/)) {
1886
+ const imgMatch = line.match(/\bimg=(\w+)/);
1887
+ if (imgMatch) await addResource(resourceMap.get(imgMatch[1] ?? ""));
1888
+ }
1889
+ } catch {}
1890
+ }
1891
+ }
1892
+ if (isComponentResource$1(resource)) componentStack.push(resource);
1893
+ }
1894
+ async function addResourceByLocalUiUrl(value) {
1895
+ if (!value || typeof value !== "string" || !value.startsWith("ui://")) return;
1896
+ const normalized = value.slice(5).split(",")[0] ?? "";
1897
+ if (!normalized) return;
1898
+ let resourceId = "";
1899
+ const slashIndex = normalized.indexOf("/");
1900
+ if (slashIndex >= 0) {
1901
+ if (normalized.slice(0, slashIndex) !== pkgId) return;
1902
+ resourceId = normalized.slice(slashIndex + 1);
1903
+ } else if (normalized.length > 8) {
1904
+ if (normalized.slice(0, 8) !== pkgId) return;
1905
+ resourceId = normalized.slice(8);
1906
+ }
1907
+ if (!resourceId) return;
1908
+ await addResource(resourceMap.get(resourceId));
1909
+ }
1910
+ async function addGearIconResources(gear) {
1911
+ if (gear.getGearType?.() !== GearType.Icon) return;
1912
+ const values = gear.getValues?.();
1913
+ if (typeof values === "string" && values) for (const value of values.split("|")) await addResourceByLocalUiUrl(value.trim());
1914
+ const defaultValue = gear.getDefaultValue?.();
1915
+ if (typeof defaultValue === "string") await addResourceByLocalUiUrl(defaultValue);
1916
+ }
1917
+ for (const resource of allResources) if (resource.getExported()) await addResource(resource);
1918
+ while (componentStack.length > 0) {
1919
+ const component = componentStack.pop();
1920
+ if (!component) continue;
1921
+ for (const child of component.listChildren()) {
1922
+ const refChild = child;
1923
+ await addResource(resourceMap.get(refChild.getSrc?.() ?? ""));
1924
+ for (const ref of [
1925
+ refChild.getUrl?.(),
1926
+ refChild.getDefaultItem?.(),
1927
+ refChild.getIcon?.(),
1928
+ refChild.getSelectedIcon?.(),
1929
+ refChild.getFont?.(),
1930
+ refChild.getDropdown?.(),
1931
+ refChild.getVtScrollBarRes?.(),
1932
+ refChild.getHzScrollBarRes?.(),
1933
+ refChild.getHeaderRes?.(),
1934
+ refChild.getFooterRes?.(),
1935
+ refChild.getSound?.(),
1936
+ refChild.getInstanceIcon?.(),
1937
+ refChild.getInstanceSelectedIcon?.()
1938
+ ]) await addResourceByLocalUiUrl(ref);
1939
+ for (const item of refChild.getInstanceComboItems?.() ?? []) await addResourceByLocalUiUrl(item.icon ?? void 0);
1940
+ for (const item of refChild.getListItems?.() ?? []) {
1941
+ await addResourceByLocalUiUrl(item.icon ?? void 0);
1942
+ await addResourceByLocalUiUrl(item.url ?? void 0);
1943
+ }
1944
+ for (const gear of refChild.listGears?.() ?? []) await addGearIconResources(gear);
1945
+ }
1946
+ for (const ref of [
1947
+ component.getDropdown?.(),
1948
+ component.getVtScrollBarRes?.(),
1949
+ component.getHzScrollBarRes?.(),
1950
+ component.getHeaderRes?.(),
1951
+ component.getFooterRes?.(),
1952
+ component.getSound?.()
1953
+ ]) await addResourceByLocalUiUrl(ref);
1954
+ for (const transition of component.listTransitions?.() ?? []) for (const item of transition.listItems?.() ?? []) {
1955
+ const actionType = item.getActionType?.();
1956
+ if (actionType !== TransitionActionType.Sound && actionType !== TransitionActionType.Icon) continue;
1957
+ for (const value of [item.getStartValue?.(), item.getEndValue?.()]) if (Array.isArray(value)) {
1958
+ for (const entry of value) if (typeof entry === "string") await addResourceByLocalUiUrl(entry);
1959
+ } else if (typeof value === "string") await addResourceByLocalUiUrl(value);
1960
+ }
1961
+ }
1962
+ for (const resource of allResources) await addResource(resource);
1963
+ return ordered;
1964
+ }
1965
+ /**
1966
+ * Packs image resources into texture atlases.
1967
+ *
1968
+ * This transform performs MaxRects bin-packing on all ImageResource items
1969
+ * within each package, creating Atlas and Sprite property nodes. When an
1970
+ * a raster backend is provided, it also composites the actual PNG files.
1971
+ *
1972
+ * When `trimImage` is enabled and encoder is available, transparent pixels
1973
+ * at image edges are trimmed before packing. The trimmed offset and original
1974
+ * dimensions are stored in the Sprite nodes for runtime reconstruction.
1975
+ *
1976
+ * ```ts
1977
+ * import sharp from 'sharp';
1978
+ * await doc.transform(atlas({
1979
+ * encoder: sharp,
1980
+ * maxSize: 2048,
1981
+ * trimImage: true,
1982
+ * basePath: './assets/',
1983
+ * outputPath: './dist/',
1984
+ * }));
1985
+ * ```
1986
+ */
1987
+ function atlas(_options = {}) {
1988
+ const options = {
1989
+ ...ATLAS_DEFAULTS,
1990
+ ..._options
1991
+ };
1992
+ return createTransform("atlas", async (doc) => {
1993
+ const root = doc.getRoot();
1994
+ const logger = doc.getLogger();
1995
+ const encoder = options.encoder;
1996
+ const doTrim = options.trimImage && !!encoder && !!options.basePath;
1997
+ const packageFilter = options.packages ? new Set(options.packages) : null;
1998
+ for (const pkg of root.listPackages()) {
1999
+ if (packageFilter && !packageFilter.has(pkg.getName())) continue;
2000
+ const publishedResourceIds = pkg.getExtras()?.publishedResourceIds;
2001
+ const selectedPublishIds = new Set(publishedResourceIds);
2002
+ const allResources = publishedResourceIds !== void 0 && (options.strictOutput || selectedPublishIds.size > 0) ? pkg.listResources().filter((resource) => selectedPublishIds.has(resource.getId())) : pkg.listResources();
2003
+ const skeletonDependencyImageIds = getSelectedSkeletonDependencyImageIds(allResources);
2004
+ const orderedResources = await resolveEditorCompatibleResourceOrder(pkg, allResources, options);
2005
+ const orderedAllResources = sortResourcesByOrder(allResources, new Map(orderedResources.map((resource, index) => [resource.getId(), index])), new Map(allResources.map((resource, index) => [resource.getId(), index])));
2006
+ if (!allResources.some((resource) => {
2007
+ if (isImageResource$1(resource) && skeletonDependencyImageIds.has(resource.getId())) return false;
2008
+ return isPackableResource(resource);
2009
+ })) continue;
2010
+ const inputs = [];
2011
+ const referencedIds = collectPackageResourceReferences(pkg).localResourceIds;
2012
+ for (const res of orderedAllResources) {
2013
+ if (isSkeletonResource$1(res) && referencedIds.has(res.getId())) {
2014
+ for (const requiredId of res.getRequireIds()) if (requiredId) referencedIds.add(requiredId);
2015
+ }
2016
+ if (isFontResource$1(res)) {
2017
+ const textureId = res.getTextureId?.() ?? "";
2018
+ if (textureId) referencedIds.add(textureId);
2019
+ if (options.readFileRaw && options.basePath) {
2020
+ const fontName = resolveFontFileName(res.getName());
2021
+ const fontPath = res.getPath() ?? "/";
2022
+ const fntFile = `${options.basePath}/${pkg.getName()}${fontPath}${fontName}`;
2023
+ try {
2024
+ const fntData = await options.readFileRaw(fntFile);
2025
+ const fntText = new TextDecoder().decode(fntData);
2026
+ for (const line of fntText.split(/\r?\n/)) {
2027
+ const match = line.match(/img=(\w+)/);
2028
+ if (match) referencedIds.add(match[1]);
2029
+ }
2030
+ } catch {}
2031
+ }
2032
+ }
2033
+ }
2034
+ for (const res of orderedAllResources) if (isImageResource$1(res)) {
2035
+ const resId = res.getId();
2036
+ if (skeletonDependencyImageIds.has(resId)) continue;
2037
+ if (selectedPublishIds.size === 0 && !res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
2038
+ await collectImage(res, pkg, inputs, encoder, options, doTrim, logger);
2039
+ } else if (isMovieClipResource$1(res)) {
2040
+ const resId = res.getId();
2041
+ if (selectedPublishIds.size === 0 && !res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
2042
+ await collectMovieClipFrames(doc, res, pkg, inputs, encoder, options, logger);
2043
+ } else if (isFontResource$1(res)) {
2044
+ const resId = res.getId();
2045
+ if (selectedPublishIds.size === 0 && !res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
2046
+ await collectFontTexture(doc, res, pkg, options);
2047
+ }
2048
+ if (inputs.length === 0) continue;
2049
+ if (options.strictOutput && (!encoder || !options.basePath || !options.outputPath)) throw new Error(`atlas: Package "${pkg.getName()}" requires encoder, basePath, and outputPath for complete raster output.`);
2050
+ await emitAtlasInputs({
2051
+ doc,
2052
+ pkg,
2053
+ allResources,
2054
+ inputs,
2055
+ options,
2056
+ encoder,
2057
+ logger
2058
+ });
2059
+ }
2060
+ });
2061
+ }
2062
+ //#endregion
2063
+ //#region src/codegen-templates.ts
2064
+ const UNITY_COMPONENT_TEMPLATE = `{{generatedMark}}
2065
+
2066
+ using FairyGUI;
2067
+ using FairyGUI.Utils;
2068
+
2069
+ namespace {{namespaceName}}
2070
+ {
2071
+ \tpublic partial class {{className}} : {{componentType}}
2072
+ \t{
2073
+ \t\tpublic const string URL = "{{url}}";
2074
+ {{variableLines}}
2075
+ \t\tpublic static {{className}} CreateInstance()
2076
+ \t\t{
2077
+ \t\t\treturn ({{className}})UIPackage.CreateObject("{{packageName}}", "{{componentName}}");
2078
+ \t\t}
2079
+
2080
+ \t\tpublic override void ConstructFromXML(XML xml)
2081
+ \t\t{
2082
+ \t\t\tbase.ConstructFromXML(xml);
2083
+ {{assignmentLines}}
2084
+ \t\t}
2085
+ \t}
2086
+ }
2087
+ `;
2088
+ const UNITY_BINDER_TEMPLATE = `{{generatedMark}}
2089
+
2090
+ using FairyGUI;
2091
+
2092
+ namespace {{namespaceName}}
2093
+ {
2094
+ \tpublic static class {{binderClassName}}
2095
+ \t{
2096
+ \t\tpublic static void BindAll()
2097
+ \t\t{
2098
+ {{bindLines}}
2099
+ \t\t}
2100
+ \t}
2101
+ }
2102
+ `;
2103
+ const FGUI_TYPESCRIPT_COMPONENT_TEMPLATE = `{{generatedMark}}
2104
+
2105
+ {{importLines}}export default class {{className}} extends {{componentType}}
2106
+ {
2107
+ \tpublic static URL:string = "{{url}}";
2108
+ {{variableLines}}
2109
+ \tpublic static createInstance():{{className}}
2110
+ \t{
2111
+ \t\treturn <{{className}}><any>({{runtimeNamespace}}.UIPackage.createObject("{{packageName}}","{{componentName}}"));
2112
+ \t}
2113
+
2114
+ \tprotected onConstruct():void
2115
+ \t{
2116
+ {{assignmentLines}}\t}
2117
+ }
2118
+ `;
2119
+ const FGUI_TYPESCRIPT_BINDER_TEMPLATE = `{{generatedMark}}
2120
+
2121
+ {{importLines}}export default class {{binderClassName}}
2122
+ {
2123
+ \tpublic static bindAll():void
2124
+ \t{
2125
+ {{bindLines}}\t}
2126
+ }
2127
+ `;
2128
+ //#endregion
2129
+ //#region src/plugins/types.ts
2130
+ function formatPluginError(error) {
2131
+ return error instanceof Error ? error.message : String(error);
2132
+ }
2133
+ //#endregion
2134
+ //#region src/codegen.ts
2135
+ const AUTO_GENERATED_CODE_MARK = "/** This is an automatically generated class by FairyGUI. Please do not modify it. **/";
2136
+ const DEFAULT_CLASS_NAME_PREFIX = "UI_";
2137
+ const DEFAULT_MEMBER_NAME_PREFIX = "m_";
2138
+ const FGUI_TYPESCRIPT_RUNTIME_TYPES = new Set([
2139
+ "Controller",
2140
+ "GButton",
2141
+ "GComboBox",
2142
+ "GComponent",
2143
+ "GGraph",
2144
+ "GGroup",
2145
+ "GImage",
2146
+ "GLabel",
2147
+ "GList",
2148
+ "GLoader",
2149
+ "GLoader3D",
2150
+ "GMovieClip",
2151
+ "GProgressBar",
2152
+ "GRichTextField",
2153
+ "GScrollBar",
2154
+ "GSlider",
2155
+ "GSwfObject",
2156
+ "GTextField",
2157
+ "GTextInput",
2158
+ "GTree",
2159
+ "Transition"
2160
+ ]);
2161
+ const SHARED_FGUI_TYPESCRIPT_VARIANT = {
2162
+ binderMethod: "setExtension",
2163
+ runtimeNamespace: "fgui"
2164
+ };
2165
+ async function publishCodeGeneration(doc, options) {
2166
+ const logger = doc.getLogger();
2167
+ const settings = resolveCodeGenerationSettings(doc);
2168
+ if (!settings.allowGenCode) return;
2169
+ const plugins = options.plugins?.filter((plugin) => typeof plugin.plugin.genCode === "function") ?? [];
2170
+ if (plugins.length > 0) {
2171
+ let handled = false;
2172
+ for (const plugin of plugins) try {
2173
+ await plugin.plugin.genCode(doc, settings, options);
2174
+ handled = true;
2175
+ logger.info(`publish: Generated code using plugin "${plugin.name}"`);
2176
+ } catch (error) {
2177
+ logger.warn(`publish: Code generation plugin "${plugin.name}" failed: ${formatPluginError(error)}`);
2178
+ }
2179
+ if (handled) return;
2180
+ }
2181
+ for (const pkg of options.packages) {
2182
+ if (!pkg.getGenCode()) continue;
2183
+ const plan = resolvePackageCodegenPlan(pkg, settings, options);
2184
+ if (!plan) {
2185
+ logger.warn(`publish: Code generation skipped for package "${pkg.getName()}" because no codePath was resolved.`);
2186
+ continue;
2187
+ }
2188
+ if (!supportsCodeGenerationLane(doc, settings.codeType)) {
2189
+ logger.warn(`publish: Code generation skipped for package "${pkg.getName()}" because project/codeType is not supported yet.`);
2190
+ continue;
2191
+ }
2192
+ const fguiTypescriptVariant = resolveFguiTypescriptVariant(doc);
2193
+ if (fguiTypescriptVariant) await generateFguiTypescriptCode(doc, pkg, plan, options.fs, fguiTypescriptVariant);
2194
+ else await generateUnityCode(doc, pkg, plan, options.fs);
2195
+ logger.info(`publish: Generated code for package "${pkg.getName()}" into ${plan.outputDir}`);
2196
+ }
2197
+ }
2198
+ function resolveCodeGenerationSettings(doc) {
2199
+ const codeGeneration = ((doc.getRoot().getSettings?.() ?? {}).publish ?? {}).codeGeneration;
2200
+ if (!codeGeneration) return {
2201
+ allowGenCode: true,
2202
+ classNamePrefix: "UI_",
2203
+ memberNamePrefix: "m_",
2204
+ packageName: "",
2205
+ ignoreNoname: false,
2206
+ getMemberByName: false,
2207
+ codePath: "",
2208
+ codeType: ""
2209
+ };
2210
+ return {
2211
+ allowGenCode: codeGeneration.allowGenCode ?? true,
2212
+ classNamePrefix: codeGeneration.classNamePrefix ?? DEFAULT_CLASS_NAME_PREFIX,
2213
+ memberNamePrefix: codeGeneration.memberNamePrefix ?? DEFAULT_MEMBER_NAME_PREFIX,
2214
+ packageName: codeGeneration.packageName ?? "",
2215
+ ignoreNoname: codeGeneration.ignoreNoname ?? false,
2216
+ getMemberByName: Boolean(codeGeneration.getMemberByName),
2217
+ codePath: codeGeneration.codePath ?? "",
2218
+ codeType: codeGeneration.codeType?.trim() ?? ""
2219
+ };
2220
+ }
2221
+ function resolvePackageCodegenPlan(pkg, settings, options) {
2222
+ const rawCodePath = (pkg.getCodePath() || settings.codePath || "").trim();
2223
+ if (!rawCodePath) return null;
2224
+ const packageFolderName = normalizeTypeName(pkg.getName()) || "Package";
2225
+ return {
2226
+ outputDir: resolveCodePath(rawCodePath, options.basePath, options.fs),
2227
+ packageFolderName,
2228
+ packageNamespace: settings.packageName ? `${settings.packageName}.${packageFolderName}` : packageFolderName,
2229
+ binderClassName: `${packageFolderName}Binder`,
2230
+ settings
2231
+ };
2232
+ }
2233
+ function supportsCodeGenerationLane(doc, codeType) {
2234
+ const projectType = doc.getRoot().getProjectType();
2235
+ if (projectType === ProjectType.Unity) return codeType === "";
2236
+ if (projectType === ProjectType.LayaBox || projectType === ProjectType.CocosCreator) return true;
2237
+ return false;
2238
+ }
2239
+ function resolveFguiTypescriptVariant(doc) {
2240
+ const projectType = doc.getRoot().getProjectType();
2241
+ if (projectType !== ProjectType.LayaBox && projectType !== ProjectType.CocosCreator) return null;
2242
+ return SHARED_FGUI_TYPESCRIPT_VARIANT;
2243
+ }
2244
+ async function generateUnityCode(doc, pkg, plan, fs) {
2245
+ const packageDir = fs.join(plan.outputDir, plan.packageFolderName);
2246
+ await fs.mkdir(plan.outputDir);
2247
+ await fs.mkdir(packageDir);
2248
+ await cleanupGeneratedFiles(packageDir, fs);
2249
+ const classes = buildCodegenClasses(doc, pkg, plan);
2250
+ for (const classInfo of classes) await writeTextFile(fs, fs.join(packageDir, `${classInfo.encodedClassName}.cs`), renderUnityComponentClass(classInfo, plan));
2251
+ await writeTextFile(fs, fs.join(packageDir, `${plan.binderClassName}.cs`), renderUnityBinder(classes, plan));
2252
+ }
2253
+ async function generateFguiTypescriptCode(doc, pkg, plan, fs, variant) {
2254
+ const packageDir = fs.join(plan.outputDir, plan.packageFolderName);
2255
+ await fs.mkdir(plan.outputDir);
2256
+ await fs.mkdir(packageDir);
2257
+ await cleanupGeneratedFiles(packageDir, fs, ".ts");
2258
+ const classes = buildCodegenClasses(doc, pkg, plan);
2259
+ for (const classInfo of classes) await writeTextFile(fs, fs.join(packageDir, `${classInfo.encodedClassName}.ts`), renderFguiTypescriptComponentClass(classInfo, plan, variant));
2260
+ await writeTextFile(fs, fs.join(packageDir, `${plan.binderClassName}.ts`), renderFguiTypescriptBinder(classes, plan, variant));
2261
+ }
2262
+ async function cleanupGeneratedFiles(directory, fs, extension = ".cs") {
2263
+ if (!fs.readdir || !fs.readFileRaw || !fs.deleteFile) return;
2264
+ let entries;
2265
+ try {
2266
+ entries = await fs.readdir(directory);
2267
+ } catch {
2268
+ return;
2269
+ }
2270
+ for (const entry of entries) {
2271
+ if (!entry.toLowerCase().endsWith(extension)) continue;
2272
+ const filePath = fs.join(directory, entry);
2273
+ try {
2274
+ if (decodeText(await fs.readFileRaw(filePath)).startsWith("/** This is an automatically generated class by FairyGUI. Please do not modify it. **/")) await fs.deleteFile(filePath);
2275
+ } catch {}
2276
+ }
2277
+ }
2278
+ function buildCodegenClasses(doc, pkg, plan) {
2279
+ const codegenComponents = pkg.listComponents().sort((left, right) => left.getId().localeCompare(right.getId()));
2280
+ const generatedById = /* @__PURE__ */ new Map();
2281
+ for (const component of codegenComponents) {
2282
+ const encodedClassName = `${plan.settings.classNamePrefix}${normalizeTypeName(component.getName()) || "Component"}`;
2283
+ generatedById.set(component.getId(), {
2284
+ classId: component.getId(),
2285
+ className: component.getName(),
2286
+ encodedClassName,
2287
+ componentType: resolveComponentBaseType(component),
2288
+ componentName: component.getName(),
2289
+ packageName: pkg.getName(),
2290
+ url: `ui://${pkg.getId()}${component.getId()}`,
2291
+ members: []
2292
+ });
2293
+ }
2294
+ for (const component of codegenComponents) {
2295
+ const classInfo = generatedById.get(component.getId());
2296
+ if (!classInfo) continue;
2297
+ classInfo.members = buildCodegenMembers(doc, pkg, component, plan, generatedById);
2298
+ }
2299
+ for (const [componentId, classInfo] of generatedById) if (classInfo.members.every((member) => member.ignored)) generatedById.delete(componentId);
2300
+ for (const component of codegenComponents) {
2301
+ const classInfo = generatedById.get(component.getId());
2302
+ if (!classInfo) continue;
2303
+ classInfo.members = buildCodegenMembers(doc, pkg, component, plan, generatedById);
2304
+ }
2305
+ return [...generatedById.values()];
2306
+ }
2307
+ function buildCodegenMembers(doc, pkg, component, plan, generatedById) {
2308
+ const members = [];
2309
+ const ownerType = resolveComponentBaseType(component);
2310
+ let controllerIndex = 0;
2311
+ let childIndex = 0;
2312
+ let transitionIndex = 0;
2313
+ for (const controller of component.listControllers()) members.push(createMember(ownerType, "controller", "Controller", controller.getName(), controllerIndex++, plan));
2314
+ for (const child of component.listChildren()) {
2315
+ if (!isRuntimeChild(child)) continue;
2316
+ const index = childIndex++;
2317
+ const resolvedChild = resolveChildType(doc, pkg, child, generatedById);
2318
+ members.push(createMember(ownerType, "child", resolvedChild.type, child.getName(), index, plan, resolvedChild.referencedComponent));
2319
+ }
2320
+ for (const transition of component.listTransitions()) members.push(createMember(ownerType, "transition", "Transition", transition.getName(), transitionIndex++, plan));
2321
+ const usedNames = /* @__PURE__ */ new Map();
2322
+ for (const member of members) {
2323
+ if (member.ignored) continue;
2324
+ const key = applyMemberNamePrefix(member.originalName, plan.settings.memberNamePrefix);
2325
+ const current = usedNames.get(key) ?? 0;
2326
+ if (current > 0) member.name = `${key}_${current + 1}`;
2327
+ usedNames.set(key, current + 1);
2328
+ }
2329
+ return members;
2330
+ }
2331
+ function isRuntimeChild(child) {
2332
+ return child.propertyType !== "GGroup" || child.getAdvanced?.() === true;
2333
+ }
2334
+ function createMember(ownerType, kind, type, originalName, index, plan, referencedComponent) {
2335
+ const ignored = plan.settings.ignoreNoname && isDefaultMemberName(ownerType, kind, originalName);
2336
+ return {
2337
+ index,
2338
+ kind,
2339
+ name: applyMemberNamePrefix(originalName, plan.settings.memberNamePrefix),
2340
+ originalName,
2341
+ type,
2342
+ ignored,
2343
+ referencedComponent
2344
+ };
2345
+ }
2346
+ function resolveChildType(doc, pkg, child, generatedById) {
2347
+ const src = child.getSrc?.();
2348
+ if (src) {
2349
+ let referencedComponent = null;
2350
+ if (src.startsWith("ui://")) {
2351
+ const rest = src.slice(5);
2352
+ const pkgId = rest.slice(0, 8);
2353
+ const resourceId = rest.slice(8);
2354
+ const targetPackage = doc.getRoot().listPackages().find((candidate) => candidate.getId() === pkgId);
2355
+ const targetResource = targetPackage?.getResourceById(resourceId);
2356
+ if (targetPackage && targetResource?.propertyType === "Component") referencedComponent = {
2357
+ component: targetResource,
2358
+ package: targetPackage
2359
+ };
2360
+ } else {
2361
+ const packageId = child.getPackageId?.();
2362
+ const targetPackage = packageId ? doc.getRoot().listPackages().find((candidate) => candidate.getId() === packageId) : pkg;
2363
+ const targetResource = targetPackage?.getResourceById(src);
2364
+ if (targetPackage && targetResource?.propertyType === "Component") referencedComponent = {
2365
+ component: targetResource,
2366
+ package: targetPackage
2367
+ };
2368
+ }
2369
+ if (referencedComponent) return {
2370
+ type: (referencedComponent.package === pkg ? generatedById.get(referencedComponent.component.getId()) : void 0)?.encodedClassName ?? resolveComponentBaseType(referencedComponent.component),
2371
+ referencedComponent
2372
+ };
2373
+ }
2374
+ const instanceExtType = child.getInstanceExtType?.();
2375
+ if (instanceExtType) return { type: `G${instanceExtType}` };
2376
+ return { type: child.propertyType };
2377
+ }
2378
+ function resolveComponentBaseType(component) {
2379
+ const extensionType = component.getExtensionType();
2380
+ return extensionType ? `G${extensionType}` : "GComponent";
2381
+ }
2382
+ function renderUnityComponentClass(classInfo, plan) {
2383
+ const variableLines = classInfo.members.filter((member) => !member.ignored).map((member) => `\t\tpublic ${member.type} ${member.name};`).join("\n");
2384
+ const contentLines = classInfo.members.map((member) => renderMemberAssignment(member, plan.settings.getMemberByName)).filter((line) => Boolean(line)).join("\n");
2385
+ return renderTemplate(UNITY_COMPONENT_TEMPLATE, {
2386
+ assignmentLines: contentLines ? `${contentLines}\n` : "",
2387
+ className: classInfo.encodedClassName,
2388
+ componentName: escapeCSharpString(classInfo.className),
2389
+ componentType: classInfo.componentType,
2390
+ generatedMark: AUTO_GENERATED_CODE_MARK,
2391
+ namespaceName: plan.packageNamespace,
2392
+ packageName: escapeCSharpString(classInfo.packageName),
2393
+ url: escapeCSharpString(classInfo.url),
2394
+ variableLines: variableLines ? `${variableLines}\n` : ""
2395
+ });
2396
+ }
2397
+ function renderUnityBinder(classes, plan) {
2398
+ const bindLines = classes.map((classInfo) => `\t\t\tUIObjectFactory.SetPackageItemExtension(${classInfo.encodedClassName}.URL, typeof(${classInfo.encodedClassName}));`).join("\n");
2399
+ return renderTemplate(UNITY_BINDER_TEMPLATE, {
2400
+ binderClassName: plan.binderClassName,
2401
+ bindLines: bindLines ? `${bindLines}\n` : "",
2402
+ generatedMark: AUTO_GENERATED_CODE_MARK,
2403
+ namespaceName: plan.packageNamespace
2404
+ });
2405
+ }
2406
+ function renderFguiTypescriptComponentClass(classInfo, plan, variant) {
2407
+ const variableLines = classInfo.members.filter((member) => !member.ignored).map((member) => `\tpublic ${member.name}:${translateFguiTypescriptType(member.type, variant)};`).join("\n");
2408
+ const assignmentLines = classInfo.members.map((member) => renderFguiTypescriptMemberAssignment(member, plan.settings.getMemberByName, variant)).filter((line) => Boolean(line)).join("\n");
2409
+ const importLines = collectFguiTypescriptImports(classInfo, variant);
2410
+ return renderTemplate(FGUI_TYPESCRIPT_COMPONENT_TEMPLATE, {
2411
+ assignmentLines: assignmentLines ? `${assignmentLines}\n` : "",
2412
+ className: classInfo.encodedClassName,
2413
+ componentName: escapeTypeScriptString(classInfo.className),
2414
+ componentType: translateFguiTypescriptType(classInfo.componentType, variant),
2415
+ generatedMark: AUTO_GENERATED_CODE_MARK,
2416
+ importLines,
2417
+ packageName: escapeTypeScriptString(classInfo.packageName),
2418
+ runtimeNamespace: variant.runtimeNamespace,
2419
+ url: escapeTypeScriptString(classInfo.url),
2420
+ variableLines: variableLines ? `${variableLines}\n` : ""
2421
+ });
2422
+ }
2423
+ function renderFguiTypescriptBinder(classes, plan, variant) {
2424
+ const bindLines = classes.map((classInfo) => `\t\t${variant.runtimeNamespace}.UIObjectFactory.${variant.binderMethod}(${classInfo.encodedClassName}.URL, ${classInfo.encodedClassName});`).join("\n");
2425
+ const importLines = classes.map((classInfo) => `import ${classInfo.encodedClassName} from "./${classInfo.encodedClassName}";`).join("\n");
2426
+ return renderTemplate(FGUI_TYPESCRIPT_BINDER_TEMPLATE, {
2427
+ binderClassName: plan.binderClassName,
2428
+ bindLines: bindLines ? `${bindLines}\n` : "",
2429
+ generatedMark: AUTO_GENERATED_CODE_MARK,
2430
+ importLines: importLines ? `${importLines}\n\n` : ""
2431
+ });
2432
+ }
2433
+ function renderMemberAssignment(member, getMemberByName) {
2434
+ if (member.ignored) return null;
2435
+ if (member.type === "Controller") return getMemberByName ? `\t\t\t${member.name} = this.GetController("${escapeCSharpString(member.originalName)}");` : `\t\t\t${member.name} = this.GetControllerAt(${member.index});`;
2436
+ if (member.type === "Transition") return getMemberByName ? `\t\t\t${member.name} = this.GetTransition("${escapeCSharpString(member.originalName)}");` : `\t\t\t${member.name} = this.GetTransitionAt(${member.index});`;
2437
+ return getMemberByName ? `\t\t\t${member.name} = (${member.type})this.GetChild("${escapeCSharpString(member.originalName)}");` : `\t\t\t${member.name} = (${member.type})this.GetChildAt(${member.index});`;
2438
+ }
2439
+ function renderFguiTypescriptMemberAssignment(member, getMemberByName, variant) {
2440
+ if (member.ignored) return null;
2441
+ if (member.type === "Controller") return getMemberByName ? `\t\tthis.${member.name} = this.getController("${escapeTypeScriptString(member.originalName)}");` : `\t\tthis.${member.name} = this.getControllerAt(${member.index});`;
2442
+ if (member.type === "Transition") return getMemberByName ? `\t\tthis.${member.name} = this.getTransition("${escapeTypeScriptString(member.originalName)}");` : `\t\tthis.${member.name} = this.getTransitionAt(${member.index});`;
2443
+ const translatedType = translateFguiTypescriptType(member.type, variant);
2444
+ return getMemberByName ? `\t\tthis.${member.name} = <${translatedType}><any>(this.getChild("${escapeTypeScriptString(member.originalName)}"));` : `\t\tthis.${member.name} = <${translatedType}><any>(this.getChildAt(${member.index}));`;
2445
+ }
2446
+ function resolveCodePath(codePath, basePath, fs) {
2447
+ if (isAbsolutePath(codePath)) return trimTrailingSlashes$1(codePath);
2448
+ const projectBasePath = resolveProjectBasePath(basePath);
2449
+ return projectBasePath ? trimTrailingSlashes$1(fs.join(projectBasePath, codePath)) : trimTrailingSlashes$1(codePath);
2450
+ }
2451
+ function resolveProjectBasePath(basePath) {
2452
+ if (!basePath) return "";
2453
+ const normalized = trimTrailingSlashes$1(basePath);
2454
+ const assetsMatch = normalized.match(/^(.*)[/\\]assets(?:_[^/\\]+)?$/i);
2455
+ if (assetsMatch?.[1]) return assetsMatch[1];
2456
+ return dirname$1(normalized);
2457
+ }
2458
+ function dirname$1(filePath) {
2459
+ return trimTrailingSlashes$1(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
2460
+ }
2461
+ function trimTrailingSlashes$1(value) {
2462
+ return value.replace(/[/\\]+$/, "");
2463
+ }
2464
+ function isAbsolutePath(value) {
2465
+ return /^[a-z]:[/\\]/i.test(value) || value.startsWith("/") || value.startsWith("\\\\");
2466
+ }
2467
+ function isDefaultMemberName(ownerType, kind, name) {
2468
+ if (kind === "controller") return (ownerType === "GButton" || ownerType === "GComboBox") && name === "button";
2469
+ if (kind === "transition") return false;
2470
+ if (ownerType === "GButton" || ownerType === "GLabel" || ownerType === "GComboBox") {
2471
+ if (name === "title" || name === "icon") return true;
2472
+ }
2473
+ if (ownerType === "GProgressBar") {
2474
+ if (name === "bar" || name === "bar_v" || name === "title" || name === "ani") return true;
2475
+ }
2476
+ if (ownerType === "GSlider") {
2477
+ if (name === "bar" || name === "bar_v" || name === "grip" || name === "title" || name === "ani") return true;
2478
+ }
2479
+ return /^n\d+(?:_.*)?$/i.test(name);
2480
+ }
2481
+ function applyMemberNamePrefix(name, prefix) {
2482
+ const normalized = normalizeMemberName(name) || "member";
2483
+ return prefix ? `${prefix}${normalized}` : normalized;
2484
+ }
2485
+ function normalizeMemberName(value) {
2486
+ const cleaned = value.replace(/[^0-9A-Za-z_]+/g, "_").replace(/^_+|_+$/g, "");
2487
+ if (!cleaned) return "";
2488
+ return /^[0-9]/.test(cleaned) ? `_${cleaned}` : cleaned;
2489
+ }
2490
+ function normalizeTypeName(value) {
2491
+ const cleaned = value.replace(/[^0-9A-Za-z_]+/g, "_").replace(/^_+|_+$/g, "");
2492
+ if (!cleaned) return "";
2493
+ const normalized = cleaned.split(/_+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
2494
+ return /^[0-9]/.test(normalized) ? `_${normalized}` : normalized;
2495
+ }
2496
+ function collectFguiTypescriptImports(classInfo, variant) {
2497
+ const imports = /* @__PURE__ */ new Set();
2498
+ for (const member of classInfo.members) {
2499
+ if (member.ignored) continue;
2500
+ const translated = translateFguiTypescriptType(member.type, variant);
2501
+ if (!translated.includes(".")) imports.add(`import ${translated} from "./${translated}";`);
2502
+ }
2503
+ return imports.size > 0 ? `${[...imports].sort().join("\n")}\n\n` : "";
2504
+ }
2505
+ function translateFguiTypescriptType(typeName, variant) {
2506
+ if (FGUI_TYPESCRIPT_RUNTIME_TYPES.has(typeName)) return `${variant.runtimeNamespace}.${typeName}`;
2507
+ return typeName;
2508
+ }
2509
+ function renderTemplate(template, data) {
2510
+ let output = template;
2511
+ for (const [key, value] of Object.entries(data)) output = output.replaceAll(`{{${key}}}`, value);
2512
+ return output;
2513
+ }
2514
+ function escapeCSharpString(value) {
2515
+ return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
2516
+ }
2517
+ function escapeTypeScriptString(value) {
2518
+ return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
2519
+ }
2520
+ async function writeTextFile(fs, filePath, content) {
2521
+ await fs.writeFileRaw(filePath, encodeText(content));
2522
+ }
2523
+ function encodeText(value) {
2524
+ return new TextEncoder().encode(value);
2525
+ }
2526
+ function decodeText(value) {
2527
+ return new TextDecoder().decode(value);
2528
+ }
2529
+ //#endregion
2530
+ //#region src/publish/package-context.ts
2531
+ const UNITY_PROJECT_TYPE$1 = ProjectType.Unity;
2532
+ function isComponentResource(resource) {
2533
+ return resource.propertyType === "Component";
2534
+ }
2535
+ function isImageResource(resource) {
2536
+ return resource.propertyType === "ImageResource";
2537
+ }
2538
+ function isMovieClipResource(resource) {
2539
+ return resource.propertyType === "MovieClipResource";
2540
+ }
2541
+ function isHighResolutionResource(resource) {
2542
+ return isImageResource(resource) || isMovieClipResource(resource);
2543
+ }
2544
+ function isMiscResource(resource) {
2545
+ return resource.propertyType === "MiscResource";
2546
+ }
2547
+ function isFontResource(resource) {
2548
+ return resource.propertyType === "FontResource";
2549
+ }
2550
+ function isSoundResource(resource) {
2551
+ return resource.propertyType === "SoundResource";
2552
+ }
2553
+ function isSpineResource(resource) {
2554
+ return resource.propertyType === "SpineResource";
2555
+ }
2556
+ function isDragonBonesResource(resource) {
2557
+ return resource.propertyType === "DragonBonesResource";
2558
+ }
2559
+ function isSkeletonResource(resource) {
2560
+ return isSpineResource(resource) || isDragonBonesResource(resource);
2561
+ }
2562
+ function resolvePackageAssetsBasePath(basePath, resource) {
2563
+ const branchName = resource?.getBranch?.() ?? "";
2564
+ if (!branchName) return basePath;
2565
+ const normalized = basePath.replace(/[/\\]+$/, "");
2566
+ if (/[\\/]assets$/i.test(normalized)) return normalized.replace(/([\\/])assets$/i, `$1assets_${branchName}`);
2567
+ return `${normalized}_${branchName}`;
2568
+ }
2569
+ function resolveImagePath(resource, pkg, basePath) {
2570
+ const fileName = resolveImageFileName(resource);
2571
+ const resourcePath = resource.getPath() ?? "/";
2572
+ return `${resolvePackageAssetsBasePath(basePath, resource)}/${pkg.getName()}${resourcePath}${fileName}`;
2573
+ }
2574
+ function resolveImageFileName(resource) {
2575
+ const extras = resource.getExtras() ?? {};
2576
+ return resource.getFileName() || extras._fileName || resource.getName();
2577
+ }
2578
+ function resolveSoundPath(resource, pkg, basePath) {
2579
+ const resourcePath = resource.getPath() ?? "/";
2580
+ return `${resolvePackageAssetsBasePath(basePath, resource)}/${pkg.getName()}${resourcePath}${resource.getFile()}`;
2581
+ }
2582
+ function resolveGenericResourcePath(resource, pkg, basePath) {
2583
+ const resourcePath = resource.getPath() ?? "/";
2584
+ return `${resolvePackageAssetsBasePath(basePath, resource)}/${pkg.getName()}${resourcePath}${resource.getFile()}`;
2585
+ }
2586
+ function extname(fileName) {
2587
+ const normalized = fileName.replace(/\\/g, "/");
2588
+ const lastSlash = normalized.lastIndexOf("/");
2589
+ const lastDot = normalized.lastIndexOf(".");
2590
+ if (lastDot <= lastSlash) return "";
2591
+ return normalized.slice(lastDot);
2592
+ }
2593
+ function resolvePublishedMiscFileName(resource, projectType) {
2594
+ const file = resource.getFile();
2595
+ if (projectType !== UNITY_PROJECT_TYPE$1) return file;
2596
+ if (file.toLowerCase().endsWith(".atlas")) return `${file}.txt`;
2597
+ return file;
2598
+ }
2599
+ function resolvePublishedSkeletonFileName(resource, projectType) {
2600
+ if (projectType === UNITY_PROJECT_TYPE$1 && isSpineResource(resource) && resource.getFile().toLowerCase().endsWith(".skel")) return `${resource.getFile()}.bytes`;
2601
+ return resource.getFile();
2602
+ }
2603
+ function setPublishedFileExtra(resource, fileName) {
2604
+ const extras = resource.getExtras() ?? {};
2605
+ resource.setExtras({
2606
+ ...extras,
2607
+ _publishedFile: fileName
2608
+ });
2609
+ }
2610
+ function setPublishedIdExtra(resource, effectiveId) {
2611
+ const extras = resource.getExtras() ?? {};
2612
+ if (!effectiveId || effectiveId === resource.getId()) {
2613
+ if (!("_publishedId" in extras)) return;
2614
+ const { _publishedId: _ignored, ...rest } = extras;
2615
+ resource.setExtras(rest);
2616
+ return;
2617
+ }
2618
+ resource.setExtras({
2619
+ ...extras,
2620
+ _publishedId: effectiveId
2621
+ });
2622
+ }
2623
+ function getPublishedId(resource) {
2624
+ return (resource.getExtras() ?? {})._publishedId ?? resource.getId();
2625
+ }
2626
+ function getBranchName(resource) {
2627
+ return resource?.getBranch?.() ?? "";
2628
+ }
2629
+ function buildBranchResourceKey(resource) {
2630
+ return `${resource.propertyType}|${resource.getPath() ?? ""}|${resource.getName() ?? ""}`;
2631
+ }
2632
+ const HIGH_RESOLUTION_LEVELS = [
2633
+ {
2634
+ scale: 2,
2635
+ bit: 1,
2636
+ slot: 0
2637
+ },
2638
+ {
2639
+ scale: 3,
2640
+ bit: 2,
2641
+ slot: 1
2642
+ },
2643
+ {
2644
+ scale: 4,
2645
+ bit: 4,
2646
+ slot: 2
2647
+ }
2648
+ ];
2649
+ function buildHighResolutionResourceKey(resource, name = resource.getName()) {
2650
+ return `${resource.propertyType}|${resource.getBranch?.() ?? ""}|${resource.getPath() ?? ""}|${name}`;
2651
+ }
2652
+ function isHighResolutionVariantName(name) {
2653
+ return /@(?:2|3|4)x(?:\.[^./\\]+)?$/iu.test(name);
2654
+ }
2655
+ function appendHighResolutionScaleToName(name, scale) {
2656
+ const extensionIndex = name.lastIndexOf(".");
2657
+ if (extensionIndex > 0) return `${name.slice(0, extensionIndex)}@${scale}x${name.slice(extensionIndex)}`;
2658
+ return `${name}@${scale}x`;
2659
+ }
2660
+ function trimTrailingMissingHighResolutionIds(ids) {
2661
+ while (ids.length > 0 && !ids[ids.length - 1]) ids.pop();
2662
+ return ids;
2663
+ }
2664
+ function collectHighResolutionItemIds(resources, publishedResourceIds, includeHighResolution) {
2665
+ const result = /* @__PURE__ */ new Map();
2666
+ if (includeHighResolution <= 0) return result;
2667
+ const highResolutionResourceByKey = /* @__PURE__ */ new Map();
2668
+ for (const resource of resources) {
2669
+ if (!isHighResolutionResource(resource)) continue;
2670
+ highResolutionResourceByKey.set(buildHighResolutionResourceKey(resource), resource);
2671
+ }
2672
+ for (const resource of resources) {
2673
+ if (!isHighResolutionResource(resource)) continue;
2674
+ if (!publishedResourceIds.has(resource.getId())) continue;
2675
+ if (isHighResolutionVariantName(resource.getName())) continue;
2676
+ const ids = [];
2677
+ for (const level of HIGH_RESOLUTION_LEVELS) {
2678
+ if ((includeHighResolution & level.bit) === 0) {
2679
+ ids[level.slot] = null;
2680
+ continue;
2681
+ }
2682
+ const highResolutionResource = highResolutionResourceByKey.get(buildHighResolutionResourceKey(resource, appendHighResolutionScaleToName(resource.getName(), level.scale)));
2683
+ if (!highResolutionResource) {
2684
+ ids[level.slot] = null;
2685
+ continue;
2686
+ }
2687
+ const highResolutionId = highResolutionResource.getId();
2688
+ publishedResourceIds.add(highResolutionId);
2689
+ ids[level.slot] = highResolutionId;
2690
+ }
2691
+ trimTrailingMissingHighResolutionIds(ids);
2692
+ if (ids.length > 0) result.set(resource.getId(), ids);
2693
+ }
2694
+ return result;
2695
+ }
2696
+ function collectPackagePublishContext(pkg, options) {
2697
+ const resources = pkg.listResources();
2698
+ const resourceMap = new Map(resources.map((resource) => [resource.getId(), resource]));
2699
+ const referencedIds = collectPackageResourceReferences(pkg).localResourceIds;
2700
+ const pixelHitTestImageIds = /* @__PURE__ */ new Set();
2701
+ const spriteItemIds = /* @__PURE__ */ new Set();
2702
+ const collectExportedResourceIds = (sourceResources, sourcePublishedResourceIds) => {
2703
+ const exportedResourceIds = new Set(sourcePublishedResourceIds);
2704
+ const resourcesById = new Map(sourceResources.map((resource) => [resource.getId(), resource]));
2705
+ let changed = true;
2706
+ while (changed) {
2707
+ changed = false;
2708
+ for (const resourceId of [...exportedResourceIds]) {
2709
+ const resource = resourcesById.get(resourceId);
2710
+ if (!resource || !isSkeletonResource(resource)) continue;
2711
+ for (const requiredId of resource.getRequireIds()) {
2712
+ if (!requiredId || exportedResourceIds.has(requiredId)) continue;
2713
+ exportedResourceIds.add(requiredId);
2714
+ changed = true;
2715
+ }
2716
+ }
2717
+ }
2718
+ return exportedResourceIds;
2719
+ };
2720
+ for (const atlas of pkg.listAtlases()) for (const sprite of atlas.listSprites()) spriteItemIds.add(sprite.getItemId());
2721
+ for (const resource of resources) {
2722
+ if (!isComponentResource(resource)) continue;
2723
+ const component = resource;
2724
+ const children = component.listChildren();
2725
+ const childMap = new Map(children.map((child) => [child.getId?.() ?? "", child]));
2726
+ const hitTest = component.getHitTest?.()?.trim();
2727
+ if (hitTest && !hitTest.includes(",")) {
2728
+ const sourceId = childMap.get(hitTest)?.getSrc?.();
2729
+ if (sourceId) {
2730
+ const sourceResource = resourceMap.get(sourceId);
2731
+ if (sourceResource && isImageResource(sourceResource)) pixelHitTestImageIds.add(sourceId);
2732
+ }
2733
+ }
2734
+ }
2735
+ const publishedResourceIds = new Set(spriteItemIds);
2736
+ for (const resource of resources) {
2737
+ const resourceId = resource.getId();
2738
+ if (!resourceId) continue;
2739
+ if (isComponentResource(resource)) {
2740
+ if (resource.getExported() || referencedIds.has(resourceId)) publishedResourceIds.add(resourceId);
2741
+ continue;
2742
+ }
2743
+ if (isImageResource(resource)) {
2744
+ if (resource.getExported() || referencedIds.has(resourceId) || spriteItemIds.has(resourceId) || pixelHitTestImageIds.has(resourceId)) publishedResourceIds.add(resourceId);
2745
+ continue;
2746
+ }
2747
+ if (isMovieClipResource(resource) || isSoundResource(resource)) {
2748
+ if (resource.getExported() || referencedIds.has(resourceId)) publishedResourceIds.add(resourceId);
2749
+ continue;
2750
+ }
2751
+ if (isMiscResource(resource) || isSkeletonResource(resource)) {
2752
+ if (resource.getExported() || referencedIds.has(resourceId)) publishedResourceIds.add(resourceId);
2753
+ continue;
2754
+ }
2755
+ if (isFontResource(resource)) {
2756
+ if (resource.getExported() || referencedIds.has(resourceId)) publishedResourceIds.add(resourceId);
2757
+ continue;
2758
+ }
2759
+ if (resource.getExported() || referencedIds.has(resourceId)) publishedResourceIds.add(resourceId);
2760
+ }
2761
+ for (const resourceId of collectExportedResourceIds(resources, publishedResourceIds)) publishedResourceIds.add(resourceId);
2762
+ const highResolutionItemIds = collectHighResolutionItemIds(resources, publishedResourceIds, options.includeHighResolution);
2763
+ if (!options.includeBranches) {
2764
+ const mainByKey = /* @__PURE__ */ new Map();
2765
+ const activeBranchByKey = /* @__PURE__ */ new Map();
2766
+ for (const resource of resources) {
2767
+ const branchName = getBranchName(resource);
2768
+ const key = buildBranchResourceKey(resource);
2769
+ if (!branchName) mainByKey.set(key, resource);
2770
+ else if (branchName === options.activeBranch) activeBranchByKey.set(key, resource);
2771
+ }
2772
+ const mergedPublishedResourceIds = /* @__PURE__ */ new Set();
2773
+ const effectiveResourceIds = /* @__PURE__ */ new Map();
2774
+ for (const resource of resources) {
2775
+ const resourceId = resource.getId();
2776
+ if (!publishedResourceIds.has(resourceId)) continue;
2777
+ const branchName = getBranchName(resource);
2778
+ const key = buildBranchResourceKey(resource);
2779
+ if (branchName) {
2780
+ if (branchName !== options.activeBranch) continue;
2781
+ const mainResource = mainByKey.get(key);
2782
+ mergedPublishedResourceIds.add(resourceId);
2783
+ effectiveResourceIds.set(resourceId, mainResource?.getId() ?? resourceId);
2784
+ continue;
2785
+ }
2786
+ const override = activeBranchByKey.get(key);
2787
+ if (override) {
2788
+ mergedPublishedResourceIds.add(override.getId());
2789
+ effectiveResourceIds.set(override.getId(), resourceId);
2790
+ continue;
2791
+ }
2792
+ mergedPublishedResourceIds.add(resourceId);
2793
+ effectiveResourceIds.set(resourceId, resourceId);
2794
+ }
2795
+ publishedResourceIds.clear();
2796
+ for (const resourceId of mergedPublishedResourceIds) publishedResourceIds.add(resourceId);
2797
+ const mergedPixelHitTestImageIds = /* @__PURE__ */ new Set();
2798
+ for (const resource of resources) {
2799
+ if (!isImageResource(resource)) continue;
2800
+ const resourceId = resource.getId();
2801
+ if (!publishedResourceIds.has(resourceId)) continue;
2802
+ const effectiveId = effectiveResourceIds.get(resourceId) ?? resourceId;
2803
+ if (pixelHitTestImageIds.has(effectiveId)) mergedPixelHitTestImageIds.add(resourceId);
2804
+ }
2805
+ pixelHitTestImageIds.clear();
2806
+ for (const resourceId of mergedPixelHitTestImageIds) pixelHitTestImageIds.add(resourceId);
2807
+ return {
2808
+ referencedIds,
2809
+ publishedResourceIds,
2810
+ exportedResourceIds: collectExportedResourceIds(resources, publishedResourceIds),
2811
+ pixelHitTestImageIds,
2812
+ highResolutionItemIds,
2813
+ effectiveResourceIds,
2814
+ includeBranches: false
2815
+ };
2816
+ }
2817
+ return {
2818
+ referencedIds,
2819
+ publishedResourceIds,
2820
+ exportedResourceIds: collectExportedResourceIds(resources, publishedResourceIds),
2821
+ pixelHitTestImageIds,
2822
+ highResolutionItemIds,
2823
+ effectiveResourceIds: new Map([...publishedResourceIds].map((resourceId) => [resourceId, resourceId])),
2824
+ includeBranches: true
2825
+ };
2826
+ }
2827
+ async function applyPixelHitTests(pkg, imageIds, basePath, encoder) {
2828
+ const images = pkg.listImageResources();
2829
+ for (const image of images) image.setPixelHitTestData(null);
2830
+ if (!basePath || !encoder || imageIds.size === 0) return;
2831
+ for (const image of images) {
2832
+ const imageId = image.getId();
2833
+ if (!imageIds.has(imageId)) continue;
2834
+ try {
2835
+ const sourcePath = resolveImagePath(image, pkg, basePath);
2836
+ const metadata = await encoder(sourcePath).metadata();
2837
+ if (!metadata.width || !metadata.height) continue;
2838
+ const resizedWidth = Math.max(1, Math.floor(metadata.width / 2));
2839
+ const resizedHeight = Math.max(1, Math.floor(metadata.height / 2));
2840
+ const { data, info } = await encoder(sourcePath).ensureAlpha().resize({
2841
+ width: resizedWidth,
2842
+ height: resizedHeight,
2843
+ fit: "fill"
2844
+ }).raw().toBuffer({ resolveWithObject: true });
2845
+ const pixelCount = info.width * info.height;
2846
+ const maskBytes = new Uint8Array(Math.ceil(pixelCount / 8));
2847
+ let byteValue = 0;
2848
+ let bitIndex = 0;
2849
+ let maskIndex = 0;
2850
+ for (let pixel = 0; pixel < pixelCount; pixel++) {
2851
+ if (data[pixel * info.channels + 3] > 10) byteValue |= 1 << bitIndex;
2852
+ bitIndex++;
2853
+ if (bitIndex === 8) {
2854
+ maskBytes[maskIndex++] = byteValue;
2855
+ bitIndex = 0;
2856
+ byteValue = 0;
2857
+ }
2858
+ }
2859
+ if (bitIndex !== 0) maskBytes[maskIndex] = byteValue;
2860
+ image.setPixelHitTestData({
2861
+ pixelWidth: info.width,
2862
+ scaleDenominator: 2,
2863
+ pixels: maskBytes
2864
+ });
2865
+ } catch {
2866
+ image.setPixelHitTestData(null);
2867
+ }
2868
+ }
2869
+ }
2870
+ async function annotatePackagePublishArtifacts(pkg, basePath, encoder, options) {
2871
+ const { publishedResourceIds, exportedResourceIds, pixelHitTestImageIds, highResolutionItemIds, effectiveResourceIds, includeBranches } = collectPackagePublishContext(pkg, options);
2872
+ for (const resource of pkg.listResources()) {
2873
+ setPublishedIdExtra(resource, effectiveResourceIds.get(resource.getId()) ?? null);
2874
+ if (isHighResolutionResource(resource)) resource.setHighResolutionItemIds(highResolutionItemIds.get(resource.getId()) ?? []);
2875
+ }
2876
+ await applyPixelHitTests(pkg, pixelHitTestImageIds, basePath, encoder);
2877
+ const extras = pkg.getExtras() ?? {};
2878
+ pkg.setExtras({
2879
+ ...extras,
2880
+ publishedResourceIds: [...publishedResourceIds].sort((a, b) => a.localeCompare(b)),
2881
+ exportedResourceIds: [...exportedResourceIds].sort((a, b) => a.localeCompare(b)),
2882
+ publishedIncludeBranches: includeBranches,
2883
+ publishedEffectiveResourceIds: Object.fromEntries(effectiveResourceIds)
2884
+ });
2885
+ for (const resource of pkg.listResources()) {
2886
+ if (isMiscResource(resource)) {
2887
+ setPublishedFileExtra(resource, resolvePublishedMiscFileName(resource, options.projectType));
2888
+ continue;
2889
+ }
2890
+ if (isSkeletonResource(resource)) setPublishedFileExtra(resource, resolvePublishedSkeletonFileName(resource, options.projectType));
2891
+ }
2892
+ }
2893
+ function getAnnotatedPublishedResourceIds(pkg) {
2894
+ const extras = pkg.getExtras() ?? {};
2895
+ return new Set(extras.publishedResourceIds ?? []);
2896
+ }
2897
+ function getAnnotatedExportedResourceIds(pkg) {
2898
+ const extras = pkg.getExtras() ?? {};
2899
+ return new Set(extras.exportedResourceIds ?? []);
2900
+ }
2901
+ function getPublishedSkeletonDependencyImageIds(pkg, publishedResourceIds) {
2902
+ const imageIds = /* @__PURE__ */ new Set();
2903
+ const resourcesById = new Map(pkg.listResources().map((resource) => [resource.getId(), resource]));
2904
+ for (const resource of pkg.listResources()) {
2905
+ if (!isSkeletonResource(resource)) continue;
2906
+ if (!publishedResourceIds.has(resource.getId())) continue;
2907
+ for (const requiredId of resource.getRequireIds()) {
2908
+ if (!requiredId) continue;
2909
+ const required = resourcesById.get(requiredId);
2910
+ if (required && isImageResource(required)) imageIds.add(requiredId);
2911
+ }
2912
+ }
2913
+ return imageIds;
2914
+ }
2915
+ //#endregion
2916
+ //#region src/publish/external-resources.ts
2917
+ async function exportPackageSounds(pkg, outputDir, basePath, fs, readFileRaw) {
2918
+ const publishedResourceIds = getAnnotatedPublishedResourceIds(pkg);
2919
+ if (publishedResourceIds.size === 0) return;
2920
+ if (!basePath || !readFileRaw) {
2921
+ if (pkg.listResources().some((resource) => {
2922
+ return isSoundResource(resource) && publishedResourceIds.has(resource.getId());
2923
+ })) throw new Error(`publish: Sound resources in package "${pkg.getName()}" require basePath and readFileRaw for output.`);
2924
+ return;
2925
+ }
2926
+ for (const resource of pkg.listResources()) {
2927
+ if (!isSoundResource(resource)) continue;
2928
+ if (!publishedResourceIds.has(resource.getId())) continue;
2929
+ const sourcePath = resolveSoundPath(resource, pkg, basePath);
2930
+ const targetName = `${pkg.getPublishName() || pkg.getName()}_${getPublishedId(resource)}${extname(resource.getFile() || "")}`;
2931
+ const targetPath = fs.join(outputDir, targetName);
2932
+ try {
2933
+ const data = await readFileRaw(sourcePath);
2934
+ await fs.writeFileRaw(targetPath, data);
2935
+ } catch {
2936
+ throw new Error(`publish: Could not export sound "${resource.getId()}" from package "${pkg.getName()}".`);
2937
+ }
2938
+ }
2939
+ }
2940
+ async function exportPackageExternalResources(pkg, outputDir, basePath, fs, readFileRaw) {
2941
+ const exportedResourceIds = getAnnotatedExportedResourceIds(pkg);
2942
+ const skeletonDependencyImageIds = getPublishedSkeletonDependencyImageIds(pkg, exportedResourceIds);
2943
+ if (exportedResourceIds.size === 0) return;
2944
+ if (!basePath || !readFileRaw) {
2945
+ if (pkg.listResources().some((resource) => {
2946
+ return (isMiscResource(resource) || isSkeletonResource(resource)) && exportedResourceIds.has(resource.getId()) || skeletonDependencyImageIds.has(resource.getId());
2947
+ })) throw new Error(`publish: External resources in package "${pkg.getName()}" require basePath and readFileRaw for output.`);
2948
+ return;
2949
+ }
2950
+ for (const resource of pkg.listResources()) {
2951
+ const resourceId = resource.getId();
2952
+ const isSkeletonExternal = exportedResourceIds.has(resourceId) && (isMiscResource(resource) || isSkeletonResource(resource));
2953
+ const isSkeletonImageDependency = skeletonDependencyImageIds.has(resourceId) && isImageResource(resource);
2954
+ if (!isSkeletonExternal && !isSkeletonImageDependency) continue;
2955
+ let sourcePath;
2956
+ let targetName;
2957
+ if (isSkeletonImageDependency) {
2958
+ sourcePath = resolveImagePath(resource, pkg, basePath);
2959
+ targetName = resolveImageFileName(resource);
2960
+ } else if (isMiscResource(resource) || isSkeletonResource(resource)) {
2961
+ sourcePath = resolveGenericResourcePath(resource, pkg, basePath);
2962
+ targetName = (resource.getExtras() ?? {})._publishedFile ?? resource.getFile();
2963
+ } else continue;
2964
+ const targetPath = fs.join(outputDir, targetName);
2965
+ try {
2966
+ const data = await readFileRaw(sourcePath);
2967
+ await fs.writeFileRaw(targetPath, data);
2968
+ } catch {
2969
+ throw new Error(`publish: Could not export external resource "${resource.getId()}" from package "${pkg.getName()}".`);
2970
+ }
2971
+ }
2972
+ }
2973
+ //#endregion
2974
+ //#region src/publish/options.ts
2975
+ const UNITY_PROJECT_TYPE = ProjectType.Unity;
2976
+ const COCOS_CREATOR_PROJECT_TYPE = ProjectType.CocosCreator;
2977
+ function resolveDefaultPublishFileExtension(projectType, publishSettings) {
2978
+ if (projectType === UNITY_PROJECT_TYPE) return "bytes";
2979
+ if (projectType === COCOS_CREATOR_PROJECT_TYPE) return publishSettings.fileExtension || "bin";
2980
+ return publishSettings.fileExtension || "fui";
2981
+ }
2982
+ function resolvePublishAtlasRuntimeOptions(fileExtension) {
2983
+ return {
2984
+ preserveInputOrderOnTie: fileExtension === "fui",
2985
+ directSingleImageOutput: fileExtension === "bytes"
2986
+ };
2987
+ }
2988
+ function resolvePublishFileName(publishName, fileExtension) {
2989
+ if (fileExtension === "bytes") return `${publishName}_fui.bytes`;
2990
+ return `${publishName}.${fileExtension}`;
2991
+ }
2992
+ /**
2993
+ * Resolve publish defaults from the document's project settings.
2994
+ *
2995
+ * This keeps the editor-aligned publish rules reusable across environments,
2996
+ * while callers still provide environment-specific concerns such as fs/encoder/basePath.
2997
+ */
2998
+ function resolvePublishOptions(doc, overrides = {}) {
2999
+ const root = doc.getRoot();
3000
+ const publishSettings = (root.getSettings?.() ?? {}).publish ?? {};
3001
+ const atlasSetting = publishSettings.atlasSetting ?? {};
3002
+ const projectType = root.getProjectType();
3003
+ const fileExtension = overrides.fileExtension ?? resolveDefaultPublishFileExtension(projectType, publishSettings);
3004
+ let compressed = overrides.compressed ?? publishSettings.compressDesc ?? false;
3005
+ if (projectType === UNITY_PROJECT_TYPE) compressed = overrides.compressed ?? false;
3006
+ const atlasOptions = {
3007
+ maxSize: overrides.atlas?.maxSize ?? atlasSetting.maxSize ?? 2048,
3008
+ fast: overrides.atlas?.fast ?? atlasSetting.fast ?? true,
3009
+ allowRotation: overrides.atlas?.allowRotation ?? atlasSetting.allowRotation ?? false,
3010
+ padding: overrides.atlas?.padding ?? atlasSetting.padding ?? 2,
3011
+ powerOfTwo: overrides.atlas?.powerOfTwo ?? atlasSetting.sizeOption === "pot",
3012
+ square: overrides.atlas?.square ?? atlasSetting.forceSquare ?? false,
3013
+ multiPage: overrides.atlas?.multiPage ?? atlasSetting.paging ?? true,
3014
+ trimImage: overrides.atlas?.trimImage ?? atlasSetting.trimImage ?? false,
3015
+ extractAlpha: overrides.atlas?.extractAlpha ?? atlasSetting.extractAlpha ?? false
3016
+ };
3017
+ return {
3018
+ compressed,
3019
+ fileExtension,
3020
+ packages: overrides.packages,
3021
+ atlas: atlasOptions
3022
+ };
3023
+ }
3024
+ //#endregion
3025
+ //#region src/publish.ts
3026
+ async function runPublishPluginHook(plugins, hook, doc, options) {
3027
+ const logger = doc.getLogger();
3028
+ for (const plugin of plugins) {
3029
+ const fn = plugin.plugin[hook];
3030
+ if (typeof fn !== "function") continue;
3031
+ try {
3032
+ await fn(doc, options);
3033
+ } catch (error) {
3034
+ logger.warn(`publish: Plugin "${plugin.name}" ${hook} failed: ${formatPluginError(error)}`);
3035
+ }
3036
+ }
3037
+ }
3038
+ function trimTrailingSlashes(value) {
3039
+ return value.replace(/[/\\]+$/, "");
3040
+ }
3041
+ function isAbsolutePathLike(value) {
3042
+ return /^(?:[a-zA-Z]:[/\\]|[/\\]{1,2})/u.test(value);
3043
+ }
3044
+ function joinPathSegments(left, right) {
3045
+ const normalizedLeft = trimTrailingSlashes(left);
3046
+ const normalizedRight = right.replace(/^[/\\]+/, "");
3047
+ if (!normalizedLeft) return normalizedRight;
3048
+ if (!normalizedRight) return normalizedLeft;
3049
+ return `${normalizedLeft}${normalizedLeft.includes("\\") ? "\\" : "/"}${normalizedRight}`;
3050
+ }
3051
+ function dirname(filePath) {
3052
+ return filePath.replace(/[/\\]+$/, "").match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
3053
+ }
3054
+ function createUnsupportedFsOperation(name) {
3055
+ return async () => {
3056
+ throw new Error(`publish: FileSystem.${name}() is not available in the publish writer adapter.`);
3057
+ };
3058
+ }
3059
+ function toBinaryWriterFileSystem(fs) {
3060
+ return {
3061
+ readFile: createUnsupportedFsOperation("readFile"),
3062
+ readFileRaw: createUnsupportedFsOperation("readFileRaw"),
3063
+ writeFile: createUnsupportedFsOperation("writeFile"),
3064
+ writeFileRaw: fs.writeFileRaw,
3065
+ mkdir: fs.mkdir,
3066
+ readdir: createUnsupportedFsOperation("readdir"),
3067
+ exists: createUnsupportedFsOperation("exists"),
3068
+ join: fs.join,
3069
+ dirname
3070
+ };
3071
+ }
3072
+ /**
3073
+ * Publishes a FairyGUI project.
3074
+ *
3075
+ * Orchestrates:
3076
+ * 1. Atlas packing (MaxRects layout + optional raster compositing)
3077
+ * 2. Per-package .fui binary serialization
3078
+ * 3. File writing to the output directory
3079
+ *
3080
+ * This is the capability-injected core. Standard hosts should use
3081
+ * `publishNode()` or `publishBrowser()` through their dedicated entries.
3082
+ *
3083
+ * ```ts
3084
+ * import { NodeIO } from '@openfairygui/core/node';
3085
+ * import { publishNode } from '@openfairygui/functions/node';
3086
+ * const doc = await new NodeIO().readProject('./project.fairy');
3087
+ *
3088
+ * await publishNode({
3089
+ * document: doc,
3090
+ * output: './release/',
3091
+ * compressed: true,
3092
+ * assetsPath: './assets/',
3093
+ * fileExtension: 'bytes',
3094
+ * });
3095
+ * ```
3096
+ */
3097
+ function publish(options) {
3098
+ return createTransform("publish", async (doc) => {
3099
+ const resolveConfiguredOutputPath = (value, projectBasePath) => {
3100
+ const trimmed = value?.trim();
3101
+ if (!trimmed) return void 0;
3102
+ if (isAbsolutePathLike(trimmed) || !projectBasePath) return trimTrailingSlashes(trimmed);
3103
+ return trimTrailingSlashes(options.fs ? options.fs.join(projectBasePath, trimmed) : joinPathSegments(projectBasePath, trimmed));
3104
+ };
3105
+ const resolveProjectPublishConfig = () => {
3106
+ const publishSettings = (doc.getRoot().getSettings?.() ?? {}).publish ?? {};
3107
+ const resolved = resolvePublishOptions(doc, {
3108
+ compressed: options.compressed,
3109
+ fileExtension: options.fileExtension,
3110
+ packages: options.packages,
3111
+ atlas: options.atlas
3112
+ });
3113
+ const includeBranches = (publishSettings.branchProcessing ?? 0) === 0;
3114
+ return {
3115
+ ...resolved,
3116
+ projectType: doc.getRoot().getProjectType(),
3117
+ includeBranches,
3118
+ activeBranch: includeBranches ? "" : options.branch ?? "",
3119
+ includeHighResolution: publishSettings.includeHighResolution ?? 0,
3120
+ separatedAtlasForBranch: includeBranches && publishSettings.seperatedAtlasForBranch === true,
3121
+ globalOutputPath: publishSettings.path?.trim() ?? "",
3122
+ globalBranchOutputPath: publishSettings.branchPath?.trim() ?? ""
3123
+ };
3124
+ };
3125
+ const resolvePackagePublishPlan = (pkg, config, projectBasePath) => {
3126
+ let outputDir;
3127
+ if (options.output) outputDir = trimTrailingSlashes(options.output);
3128
+ else {
3129
+ const candidates = [];
3130
+ if (!config.includeBranches && config.activeBranch) candidates.push(pkg.getPublishBranchPath(), config.globalBranchOutputPath);
3131
+ candidates.push(pkg.getPublishPath(), config.globalOutputPath);
3132
+ for (const candidate of candidates) {
3133
+ const resolved = resolveConfiguredOutputPath(candidate, projectBasePath);
3134
+ if (!resolved) continue;
3135
+ outputDir = resolved;
3136
+ break;
3137
+ }
3138
+ }
3139
+ const publishName = pkg.getPublishName() || pkg.getName();
3140
+ return {
3141
+ pkg,
3142
+ outputDir,
3143
+ publishName,
3144
+ fileName: resolvePublishFileName(publishName, config.fileExtension),
3145
+ compressed: config.compressed,
3146
+ fileExtension: config.fileExtension,
3147
+ includeBranches: config.includeBranches,
3148
+ activeBranch: config.activeBranch,
3149
+ includeHighResolution: config.includeHighResolution,
3150
+ separatedAtlasForBranch: config.separatedAtlasForBranch,
3151
+ atlas: config.atlas
3152
+ };
3153
+ };
3154
+ const createNoopPublishFs = () => ({
3155
+ async writeFileRaw() {},
3156
+ async mkdir() {},
3157
+ join(...paths) {
3158
+ return paths.join("/");
3159
+ }
3160
+ });
3161
+ const publishPackage = async (plan, writerFs, packageIndex) => {
3162
+ if (options.fs && !plan.outputDir) throw new Error("publish: no output directory resolved. Provide --output, or configure global publish.path / package publishPath.");
3163
+ if (options.fs) {
3164
+ await options.fs.mkdir(plan.outputDir);
3165
+ await exportPackageSounds(plan.pkg, plan.outputDir, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw);
3166
+ await exportPackageExternalResources(plan.pkg, plan.outputDir, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw);
3167
+ }
3168
+ const atlasRuntimeOptions = resolvePublishAtlasRuntimeOptions(plan.fileExtension);
3169
+ await atlas({
3170
+ ...plan.atlas,
3171
+ ...options.atlas ?? {},
3172
+ separatedAtlasForBranch: plan.separatedAtlasForBranch,
3173
+ encoder: options.encoder,
3174
+ basePath: options.basePath,
3175
+ outputPath: options.fs ? plan.outputDir : void 0,
3176
+ mkdir: options.fs ? options.fs.mkdir : void 0,
3177
+ readFileRaw: options.atlas?.readFileRaw ?? options.fs?.readFileRaw,
3178
+ strictOutput: options.fs !== void 0,
3179
+ packages: [plan.pkg.getName()],
3180
+ ...atlasRuntimeOptions
3181
+ })(doc);
3182
+ if (!options.fs) return;
3183
+ const filePath = options.fs.join(plan.outputDir, plan.fileName);
3184
+ const bwOptions = {
3185
+ compressed: plan.compressed,
3186
+ packageIndex
3187
+ };
3188
+ await new BinaryWriter(writerFs).write(doc, filePath, bwOptions);
3189
+ logger.info(`publish: Written ${plan.fileName}`);
3190
+ };
3191
+ const root = doc.getRoot();
3192
+ const logger = doc.getLogger();
3193
+ const projectBasePath = resolveProjectBasePath(options.basePath) || doc.getProjectDir?.() || "";
3194
+ const plugins = options.plugins ?? [];
3195
+ await runPublishPluginHook(plugins, "onPublishStart", doc, options);
3196
+ const resolved = resolveProjectPublishConfig();
3197
+ let allPackages = root.listPackages();
3198
+ if (resolved.packages && resolved.packages.length > 0) {
3199
+ const names = new Set(resolved.packages);
3200
+ allPackages = allPackages.filter((p) => names.has(p.getName()));
3201
+ }
3202
+ if (allPackages.length === 0) {
3203
+ logger.warn("publish: No packages to publish.");
3204
+ await runPublishPluginHook(plugins, "onPublishEnd", doc, options);
3205
+ return;
3206
+ }
3207
+ const allDocPackages = root.listPackages();
3208
+ const pkgMap = /* @__PURE__ */ new Map();
3209
+ for (const p of allDocPackages) pkgMap.set(p.getId(), p);
3210
+ for (const pkg of allPackages) {
3211
+ _computeDependencies(doc, pkg, pkgMap);
3212
+ await annotatePackagePublishArtifacts(pkg, options.basePath, options.encoder, {
3213
+ projectType: resolved.projectType,
3214
+ includeBranches: resolved.includeBranches,
3215
+ activeBranch: resolved.activeBranch,
3216
+ includeHighResolution: resolved.includeHighResolution
3217
+ });
3218
+ }
3219
+ const plans = allPackages.map((pkg) => resolvePackagePublishPlan(pkg, resolved, projectBasePath));
3220
+ if (!options.fs) {
3221
+ const outputPlan = plans.find((plan) => !!plan.outputDir);
3222
+ if (outputPlan) throw new Error(`publish: Output for package "${outputPlan.pkg.getName()}" requires a filesystem. Omit output and publish paths to run a layout-only transform.`);
3223
+ logger.info(`publish: Layout computed for ${allPackages.length} package(s); no output directory was requested.`);
3224
+ const noopWriterFs = toBinaryWriterFileSystem(createNoopPublishFs());
3225
+ for (const plan of plans) await publishPackage(plan, noopWriterFs, allDocPackages.indexOf(plan.pkg));
3226
+ await runPublishPluginHook(plugins, "onPublishEnd", doc, options);
3227
+ return;
3228
+ }
3229
+ const unresolvedPlan = plans.find((plan) => !plan.outputDir);
3230
+ if (unresolvedPlan) throw new Error(`publish: no output directory resolved for package "${unresolvedPlan.pkg.getName()}". Provide --output, or configure global publish.path / package publishPath.`);
3231
+ const writerFs = toBinaryWriterFileSystem(options.fs);
3232
+ for (const plan of plans) await publishPackage(plan, writerFs, allDocPackages.indexOf(plan.pkg));
3233
+ if (options.codeGeneration !== false) await publishCodeGeneration(doc, {
3234
+ basePath: options.basePath,
3235
+ fs: options.fs,
3236
+ packages: allPackages,
3237
+ plugins
3238
+ });
3239
+ const publishedTargets = [...new Set(plans.map((plan) => plan.outputDir).filter((value) => Boolean(value)))];
3240
+ logger.info(publishedTargets.length > 0 ? `publish: Published ${allPackages.length} package(s) to ${publishedTargets.join(", ")}` : `publish: Published ${allPackages.length} package(s)`);
3241
+ await runPublishPluginHook(plugins, "onPublishEnd", doc, options);
3242
+ });
3243
+ }
3244
+ /**
3245
+ * Scan component children for font="ui://..." references to build dependency list.
3246
+ * The editor only adds dependencies for packages referenced via bitmap font URLs.
3247
+ * @internal
3248
+ */
3249
+ function _computeDependencies(doc, pkg, pkgMap) {
3250
+ const referencedPkgIds = collectPackageResourceReferences(pkg).packageIds;
3251
+ const packageOrder = new Map(doc.getRoot().listPackages().map((entry, index) => [entry.getId(), index]));
3252
+ for (const dep of pkg.listDependencies()) pkg.removeDependency(dep);
3253
+ if (referencedPkgIds.size > 0) {
3254
+ const sortedIds = [...referencedPkgIds].sort((a, b) => {
3255
+ const orderA = packageOrder.get(a) ?? Number.MAX_SAFE_INTEGER;
3256
+ const orderB = packageOrder.get(b) ?? Number.MAX_SAFE_INTEGER;
3257
+ if (orderA !== orderB) return orderA - orderB;
3258
+ return a.localeCompare(b);
3259
+ });
3260
+ for (const refId of sortedIds) {
3261
+ const depPkg = pkgMap.get(refId);
3262
+ if (depPkg) pkg.addDependency(depPkg);
3263
+ }
3264
+ }
3265
+ }
3266
+ //#endregion
3267
+ export { decodeText as a, resolvePackageCodegenPlan as c, atlas as d, createTransform as f, buildCodegenClasses as i, resolveProjectBasePath as l, resolvePublishOptions as n, encodeText as o, AUTO_GENERATED_CODE_MARK as r, publishCodeGeneration as s, publish as t, formatPluginError as u };