@openfairygui/cli 0.2.0-alpha.34 → 0.2.0-alpha.35

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 (2) hide show
  1. package/dist/cli.mjs +677 -524
  2. package/package.json +4 -4
package/dist/cli.mjs CHANGED
@@ -1250,13 +1250,11 @@ var Root = class extends ExtensibleProperty {
1250
1250
  return [...this.get("branches")];
1251
1251
  }
1252
1252
  setBranches(branches) {
1253
- return this.set("branches", [...branches]);
1253
+ return this.set("branches", [...new Set(branches)].sort((left, right) => left.localeCompare(right)));
1254
1254
  }
1255
1255
  addBranch(branch) {
1256
1256
  if (!branch) return this;
1257
- const branches = this.listBranches();
1258
- if (!branches.includes(branch)) branches.push(branch);
1259
- return this.set("branches", branches);
1257
+ return this.setBranches([...this.listBranches(), branch]);
1260
1258
  }
1261
1259
  getSettings() {
1262
1260
  return this.get("settings");
@@ -1317,6 +1315,19 @@ var Package = class extends ExtensibleProperty {
1317
1315
  publishPackageCount: 0,
1318
1316
  genCode: false,
1319
1317
  codePath: "",
1318
+ sourceAtlasSettings: {
1319
+ useGlobal: true,
1320
+ maxSize: 2048,
1321
+ sizeOption: "pot",
1322
+ forceSquare: false,
1323
+ allowRotation: false,
1324
+ paging: true,
1325
+ extractAlpha: false,
1326
+ maxIndex: 10,
1327
+ atlases: [],
1328
+ excludedResourceIds: []
1329
+ },
1330
+ branchNames: [],
1320
1331
  resourceFolders: [],
1321
1332
  resources: new RefSet(),
1322
1333
  atlases: new RefSet(),
@@ -1377,6 +1388,31 @@ var Package = class extends ExtensibleProperty {
1377
1388
  setCodePath(path) {
1378
1389
  return this.set("codePath", path);
1379
1390
  }
1391
+ getSourceAtlasSettings() {
1392
+ const settings = this.get("sourceAtlasSettings");
1393
+ return {
1394
+ ...settings,
1395
+ atlases: settings.atlases.map((atlas) => ({ ...atlas })),
1396
+ excludedResourceIds: [...settings.excludedResourceIds]
1397
+ };
1398
+ }
1399
+ setSourceAtlasSettings(settings) {
1400
+ return this.set("sourceAtlasSettings", {
1401
+ ...settings,
1402
+ atlases: settings.atlases.map((atlas) => ({ ...atlas })),
1403
+ excludedResourceIds: [...settings.excludedResourceIds]
1404
+ });
1405
+ }
1406
+ listBranchNames() {
1407
+ return [...this.get("branchNames")];
1408
+ }
1409
+ setBranchNames(names) {
1410
+ return this.set("branchNames", [...names]);
1411
+ }
1412
+ addBranchName(name) {
1413
+ if (!this.get("branchNames").includes(name)) this.set("branchNames", [...this.get("branchNames"), name]);
1414
+ return this;
1415
+ }
1380
1416
  listResourceFolders() {
1381
1417
  return this.get("resourceFolders").map((folder) => ({ ...folder }));
1382
1418
  }
@@ -9548,65 +9584,89 @@ function ensureArray(v) {
9548
9584
  }
9549
9585
  //#endregion
9550
9586
  //#region ../core/src/utils/jta-parser.ts
9551
- /**
9552
- * Parser for FairyGUI `.jta` animation files.
9553
- *
9554
- * Extracts individual frame textures (PNG/JPG byte arrays) from the binary format.
9555
- * Used by the atlas packer to include MovieClip frames in texture atlases.
9556
- *
9557
- * @internal
9558
- */
9559
9587
  const FILE_MARK = "yytou";
9588
+ var JtaCursor = class {
9589
+ offset = 0;
9590
+ view;
9591
+ constructor(data) {
9592
+ this.data = data;
9593
+ this.view = new DataView(data.buffer, data.byteOffset, data.byteLength);
9594
+ }
9595
+ readUint8(label) {
9596
+ this.ensure(1, label);
9597
+ return this.view.getUint8(this.offset++);
9598
+ }
9599
+ readInt8(label) {
9600
+ this.ensure(1, label);
9601
+ return this.view.getInt8(this.offset++);
9602
+ }
9603
+ readUint16(label) {
9604
+ this.ensure(2, label);
9605
+ const value = this.view.getUint16(this.offset, false);
9606
+ this.offset += 2;
9607
+ return value;
9608
+ }
9609
+ readInt16(label) {
9610
+ this.ensure(2, label);
9611
+ const value = this.view.getInt16(this.offset, false);
9612
+ this.offset += 2;
9613
+ return value;
9614
+ }
9615
+ readInt32(label) {
9616
+ this.ensure(4, label);
9617
+ const value = this.view.getInt32(this.offset, false);
9618
+ this.offset += 4;
9619
+ return value;
9620
+ }
9621
+ readBytes(length, label) {
9622
+ if (!Number.isInteger(length) || length < 0) throw new Error(`Invalid .jta file: negative ${label} length`);
9623
+ this.ensure(length, label);
9624
+ const value = this.data.subarray(this.offset, this.offset + length);
9625
+ this.offset += length;
9626
+ return value;
9627
+ }
9628
+ skip(length, label) {
9629
+ this.ensure(length, label);
9630
+ this.offset += length;
9631
+ }
9632
+ ensure(length, label) {
9633
+ if (this.offset + length > this.data.byteLength) throw new Error(`Invalid .jta file: truncated ${label}`);
9634
+ }
9635
+ };
9560
9636
  /**
9561
9637
  * Parse a `.jta` binary buffer into frame and texture data.
9562
9638
  */
9563
9639
  function parseJta(data) {
9564
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
9565
- let pos = 0;
9566
- const markLen = view.getUint16(pos);
9567
- pos += 2;
9568
- if (pos + markLen > data.length) throw new Error("Invalid .jta file: truncated file mark");
9569
- const mark = new TextDecoder("utf-8").decode(data.subarray(pos, pos + markLen));
9570
- pos += markLen;
9640
+ const cursor = new JtaCursor(data);
9641
+ const markLen = cursor.readUint16("file mark length");
9642
+ const mark = new TextDecoder("utf-8").decode(cursor.readBytes(markLen, "file mark"));
9571
9643
  if (mark !== FILE_MARK) throw new Error(`Invalid .jta file: expected "${FILE_MARK}", got "${mark}"`);
9572
- const version = view.getInt32(pos);
9573
- pos += 4;
9644
+ const version = cursor.readInt32("version");
9574
9645
  if (version < 100 || version > 102) throw new Error(`Unsupported .jta version: ${version}`);
9575
- let fps = view.getInt8(pos);
9576
- pos += 1;
9646
+ let fps = cursor.readInt8("fps");
9647
+ if (fps < 0) throw new Error(`Invalid .jta file: negative fps ${fps}`);
9577
9648
  if (fps === 0) fps = 24;
9578
- pos += 3;
9649
+ cursor.skip(3, "reserved header");
9579
9650
  let boundsWidth = 0, boundsHeight = 0;
9580
9651
  if (version >= 102) {
9581
- pos += 4;
9582
- boundsWidth = view.getUint16(pos);
9583
- pos += 2;
9584
- boundsHeight = view.getUint16(pos);
9585
- pos += 2;
9586
- }
9587
- const speed = view.getUint8(pos);
9588
- pos += 1;
9589
- const repeatDelay = view.getUint8(pos);
9590
- pos += 1;
9591
- const swing = view.getInt8(pos) === 1;
9592
- pos += 1;
9593
- const frameCount = view.getInt16(pos);
9594
- pos += 2;
9652
+ cursor.skip(4, "bounds origin");
9653
+ boundsWidth = cursor.readUint16("bounds width");
9654
+ boundsHeight = cursor.readUint16("bounds height");
9655
+ }
9656
+ const speed = cursor.readUint8("speed");
9657
+ const repeatDelay = cursor.readUint8("repeat delay");
9658
+ const swing = cursor.readInt8("swing") === 1;
9659
+ const frameCount = cursor.readInt16("frame count");
9595
9660
  if (frameCount < 0) throw new Error("Invalid .jta file: negative frame count");
9596
9661
  const frames = [];
9597
9662
  for (let i = 0; i < frameCount; i++) {
9598
- const delay = view.getInt16(pos);
9599
- pos += 2;
9600
- const rectX = view.getInt16(pos);
9601
- pos += 2;
9602
- const rectY = view.getInt16(pos);
9603
- pos += 2;
9604
- const rectWidth = view.getInt16(pos);
9605
- pos += 2;
9606
- const rectHeight = view.getInt16(pos);
9607
- pos += 2;
9608
- const textureIndex = view.getInt16(pos);
9609
- pos += 2;
9663
+ const delay = cursor.readInt16(`frame ${i} delay`);
9664
+ const rectX = cursor.readInt16(`frame ${i} rect x`);
9665
+ const rectY = cursor.readInt16(`frame ${i} rect y`);
9666
+ const rectWidth = cursor.readInt16(`frame ${i} rect width`);
9667
+ const rectHeight = cursor.readInt16(`frame ${i} rect height`);
9668
+ const textureIndex = cursor.readInt16(`frame ${i} texture index`);
9669
+ if (delay < 0 || rectWidth < 0 || rectHeight < 0) throw new Error(`Invalid .jta file: frame ${i} has negative delay or dimensions`);
9610
9670
  frames.push({
9611
9671
  delay,
9612
9672
  rectX,
@@ -9616,27 +9676,17 @@ function parseJta(data) {
9616
9676
  textureIndex
9617
9677
  });
9618
9678
  }
9619
- const textureCount = view.getInt16(pos);
9620
- pos += 2;
9679
+ const textureCount = cursor.readInt16("texture count");
9621
9680
  if (textureCount < 0) throw new Error("Invalid .jta file: negative texture count");
9622
9681
  const textures = [];
9623
9682
  for (let i = 0; i < textureCount; i++) {
9624
- const rawLen = view.getInt32(pos);
9625
- pos += 4;
9626
- if (rawLen < 0 || pos + rawLen > data.length) throw new Error("Invalid .jta file: truncated texture data");
9627
- let raw;
9628
- if (rawLen > 0) {
9629
- raw = data.subarray(pos, pos + rawLen);
9630
- pos += rawLen;
9631
- } else raw = new Uint8Array(0);
9632
- textures.push({ raw });
9683
+ const rawLen = cursor.readInt32(`texture ${i} length`);
9684
+ textures.push({ raw: cursor.readBytes(rawLen, `texture ${i} data`) });
9633
9685
  }
9634
9686
  if (version === 101) {
9635
- pos += 4;
9636
- boundsWidth = view.getUint16(pos);
9637
- pos += 2;
9638
- boundsHeight = view.getUint16(pos);
9639
- pos += 2;
9687
+ cursor.skip(4, "bounds origin");
9688
+ boundsWidth = cursor.readUint16("bounds width");
9689
+ boundsHeight = cursor.readUint16("bounds height");
9640
9690
  } else if (version === 100) {
9641
9691
  let minX = Number.POSITIVE_INFINITY;
9642
9692
  let minY = Number.POSITIVE_INFINITY;
@@ -9654,6 +9704,10 @@ function parseJta(data) {
9654
9704
  boundsHeight = maxY - Math.min(minY, 0);
9655
9705
  }
9656
9706
  }
9707
+ for (let index = 0; index < frames.length; index += 1) {
9708
+ const textureIndex = frames[index].textureIndex;
9709
+ if (textureIndex < -1 || textureIndex >= textures.length) throw new Error(`Invalid .jta file: frame ${index} texture index ${textureIndex} is outside -1..${textures.length - 1}`);
9710
+ }
9657
9711
  return {
9658
9712
  version,
9659
9713
  fps,
@@ -9666,211 +9720,37 @@ function parseJta(data) {
9666
9720
  textures
9667
9721
  };
9668
9722
  }
9669
- function tryReadJtaSize(data) {
9670
- try {
9671
- const parsed = parseJta(data);
9672
- return {
9723
+ /** Converts parsed JTA frame units to the millisecond-based Document/UAM model. */
9724
+ function deriveMovieClipModel(parsed) {
9725
+ const millisecondsPerFrame = 1e3 / parsed.fps;
9726
+ return {
9727
+ dimensions: {
9673
9728
  width: parsed.boundsWidth,
9674
9729
  height: parsed.boundsHeight
9675
- };
9676
- } catch {
9677
- return null;
9678
- }
9679
- }
9680
- //#endregion
9681
- //#region ../core/src/document.ts
9682
- /**
9683
- * Wraps a FairyGUI project and its resources for easier modification.
9684
- *
9685
- * Documents manage FairyGUI assets and the relationships among dependencies using an
9686
- * internal property graph. A new resource is created by calling 'create' methods on the
9687
- * document. Resources are destroyed by calling {@link Property.dispose}().
9688
- *
9689
- * Usage:
9690
- *
9691
- * ```ts
9692
- * const document = new Document();
9693
- * const pkg = document.createPackage('MyPackage');
9694
- * const component = document.createComponent('Button');
9695
- * const image = document.createGImage('bg');
9696
- * component.addChild(image);
9697
- * pkg.addResource(component);
9698
- * ```
9699
- *
9700
- * @category Documents
9701
- */
9702
- var Document = class Document {
9703
- _graph = new Graph();
9704
- _root = new Root(this._graph);
9705
- _logger = Logger.DEFAULT_INSTANCE;
9706
- _projectDir = "";
9707
- static _GRAPH_DOCUMENTS = /* @__PURE__ */ new WeakMap();
9708
- static fromGraph(graph) {
9709
- return Document._GRAPH_DOCUMENTS.get(graph) || null;
9710
- }
9711
- constructor() {
9712
- Document._GRAPH_DOCUMENTS.set(this._graph, this);
9713
- }
9714
- getRoot() {
9715
- return this._root;
9716
- }
9717
- /** @hidden */
9718
- getGraph() {
9719
- return this._graph;
9720
- }
9721
- getLogger() {
9722
- return this._logger;
9723
- }
9724
- setLogger(logger) {
9725
- this._logger = logger;
9726
- return this;
9727
- }
9728
- getProjectDir() {
9729
- return this._projectDir;
9730
- }
9731
- setProjectDir(projectDir) {
9732
- this._projectDir = projectDir;
9733
- return this;
9734
- }
9735
- async transform(...transforms) {
9736
- const stack = transforms.map((fn) => fn.name);
9737
- for (const transform of transforms) await transform(this, { stack });
9738
- return this;
9739
- }
9740
- /****** Extension factory methods ******/
9741
- createExtension(ctor) {
9742
- const extensionName = ctor.EXTENSION_NAME;
9743
- return this.getRoot().listExtensionsUsed().find((ext) => ext.extensionName === extensionName) || new ctor(this);
9744
- }
9745
- /****** Property factory methods ******/
9746
- createPackage(name = "") {
9747
- return new Package(this._graph, name);
9748
- }
9749
- createImageResource(name = "") {
9750
- return new ImageResource(this._graph, name);
9751
- }
9752
- createSoundResource(name = "") {
9753
- return new SoundResource(this._graph, name);
9754
- }
9755
- createMiscResource(name = "") {
9756
- return new MiscResource(this._graph, name);
9757
- }
9758
- createFontResource(name = "") {
9759
- return new FontResource(this._graph, name);
9760
- }
9761
- createMovieClipResource(name = "") {
9762
- return new MovieClipResource(this._graph, name);
9763
- }
9764
- createSpineResource(name = "") {
9765
- return new SpineResource(this._graph, name);
9766
- }
9767
- createDragonBonesResource(name = "") {
9768
- return new DragonBonesResource(this._graph, name);
9769
- }
9770
- createComponent(name = "") {
9771
- return new Component(this._graph, name);
9772
- }
9773
- createAtlas(name = "") {
9774
- return new Atlas(this._graph, name);
9775
- }
9776
- createSprite(name = "") {
9777
- return new Sprite(this._graph, name);
9778
- }
9779
- createBuffer(name = "") {
9780
- return new FairyBuffer(this._graph, name);
9781
- }
9782
- createGImage(name = "") {
9783
- return new GImage(this._graph, name);
9784
- }
9785
- createGTextField(name = "") {
9786
- return new GTextField(this._graph, name);
9787
- }
9788
- createGRichTextField(name = "") {
9789
- return new GRichTextField(this._graph, name);
9790
- }
9791
- createGTextInput(name = "") {
9792
- return new GTextInput(this._graph, name);
9793
- }
9794
- createGGraph(name = "") {
9795
- return new GGraph(this._graph, name);
9796
- }
9797
- createGGroup(name = "") {
9798
- return new GGroup(this._graph, name);
9799
- }
9800
- createGLoader(name = "") {
9801
- return new GLoader(this._graph, name);
9802
- }
9803
- createGLoader3D(name = "") {
9804
- return new GLoader3D(this._graph, name);
9805
- }
9806
- createGMovieClip(name = "") {
9807
- return new GMovieClip(this._graph, name);
9808
- }
9809
- createGComponent(name = "") {
9810
- return new GComponent(this._graph, name);
9811
- }
9812
- createGList(name = "") {
9813
- return new GList(this._graph, name);
9814
- }
9815
- createGTree(name = "") {
9816
- return new GTree(this._graph, name);
9817
- }
9818
- createGButton(name = "") {
9819
- return new GButton(this._graph, name);
9820
- }
9821
- createGLabel(name = "") {
9822
- return new GLabel(this._graph, name);
9823
- }
9824
- createGComboBox(name = "") {
9825
- return new GComboBox(this._graph, name);
9826
- }
9827
- createGProgressBar(name = "") {
9828
- return new GProgressBar(this._graph, name);
9829
- }
9830
- createGSlider(name = "") {
9831
- return new GSlider(this._graph, name);
9832
- }
9833
- createGScrollBar(name = "") {
9834
- return new GScrollBar(this._graph, name);
9835
- }
9836
- createController(name = "") {
9837
- return new Controller(this._graph, name);
9838
- }
9839
- createControllerPage(name = "") {
9840
- return new ControllerPage(this._graph, name);
9841
- }
9842
- createControllerAction(name = "") {
9843
- return new ControllerAction(this._graph, name);
9844
- }
9845
- createTransition(name = "") {
9846
- return new Transition(this._graph, name);
9847
- }
9848
- createTransitionItem(name = "") {
9849
- return new TransitionItem(this._graph, name);
9850
- }
9851
- createGear(name = "") {
9852
- return new Gear(this._graph, name);
9853
- }
9854
- createFontGlyph(name = "") {
9855
- return new FontGlyph(this._graph, name);
9856
- }
9857
- createMovieFrame(name = "") {
9858
- return new MovieFrame(this._graph, name);
9859
- }
9860
- };
9861
- //#endregion
9862
- //#region ../core/src/utils/resource-folder.ts
9863
- function normalizeResourceFolderPath(value) {
9864
- const segments = value.replace(/\\/g, "/").split("/").filter(Boolean);
9865
- return segments.length > 0 ? `/${segments.join("/")}/` : "/";
9730
+ },
9731
+ interval: Math.trunc(millisecondsPerFrame * (parsed.speed || 1)),
9732
+ repeatDelay: Math.trunc(millisecondsPerFrame * parsed.repeatDelay),
9733
+ swing: parsed.swing,
9734
+ frames: parsed.frames.map((frame) => ({
9735
+ rectX: frame.rectX,
9736
+ rectY: frame.rectY,
9737
+ rectWidth: frame.rectWidth,
9738
+ rectHeight: frame.rectHeight,
9739
+ addDelay: Math.trunc(millisecondsPerFrame * frame.delay),
9740
+ textureIndex: frame.textureIndex
9741
+ }))
9742
+ };
9866
9743
  }
9867
- function resourceFolderParentPath(value) {
9868
- const segments = normalizeResourceFolderPath(value).split("/").filter(Boolean);
9869
- segments.pop();
9870
- return segments.length > 0 ? `/${segments.join("/")}/` : "/";
9744
+ /** Parses JTA bytes and derives the Document/UAM MovieClip model. */
9745
+ function deriveMovieClipModelFromJta(data) {
9746
+ return deriveMovieClipModel(parseJta(data));
9871
9747
  }
9872
- function resourceFolderName(value) {
9873
- return normalizeResourceFolderPath(value).split("/").filter(Boolean).pop() ?? "";
9748
+ /** Applies a fully parsed JTA model without changing XML-owned MovieClip settings such as smoothing. */
9749
+ function applyDerivedMovieClipModel(doc, resource, model) {
9750
+ const frames = model.frames.map((frame, index) => doc.createMovieFrame(`${resource.getId()}_${index}`).setRectX(frame.rectX).setRectY(frame.rectY).setRectWidth(frame.rectWidth).setRectHeight(frame.rectHeight).setAddDelay(frame.addDelay).setSpriteId(""));
9751
+ for (const frame of resource.listFrames()) resource.removeFrame(frame);
9752
+ resource.setWidth(model.dimensions.width).setHeight(model.dimensions.height).setInterval(model.interval).setRepeatDelay(model.repeatDelay).setSwing(model.swing);
9753
+ for (const frame of frames) resource.addFrame(frame);
9874
9754
  }
9875
9755
  //#endregion
9876
9756
  //#region ../../node_modules/.pnpm/jpeg-js@0.4.4/node_modules/jpeg-js/lib/encoder.js
@@ -15743,7 +15623,7 @@ var inflateRaw_1 = inflateRaw;
15743
15623
  //#endregion
15744
15624
  //#region ../core/src/utils/image-info.ts
15745
15625
  var import_jpeg_js = require_jpeg_js();
15746
- const PNG_SIGNATURE$1 = [
15626
+ const PNG_SIGNATURE = [
15747
15627
  137,
15748
15628
  80,
15749
15629
  78,
@@ -15889,9 +15769,9 @@ function validatePngImageData(parts, width, height, bitDepth, colorType, interla
15889
15769
  }
15890
15770
  }
15891
15771
  function readPngInfo(data, validateImageData) {
15892
- if (data.length < (validateImageData ? 45 : 33) || PNG_SIGNATURE$1.some((byte, index) => data[index] !== byte)) return null;
15772
+ if (data.length < (validateImageData ? 45 : 33) || PNG_SIGNATURE.some((byte, index) => data[index] !== byte)) return null;
15893
15773
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
15894
- let offset = PNG_SIGNATURE$1.length;
15774
+ let offset = PNG_SIGNATURE.length;
15895
15775
  let width = 0;
15896
15776
  let height = 0;
15897
15777
  let bitDepth = 0;
@@ -16118,43 +15998,260 @@ function readJpegInfo(data, decodePixels) {
16118
15998
  }
16119
15999
  return null;
16120
16000
  }
16001
+ /**
16002
+ * Strictly validate a complete PNG or JPEG source and return its decoded dimensions.
16003
+ *
16004
+ * Unlike `probeRasterImageDimensions`, this validates PNG image data and fully
16005
+ * decodes JPEG pixels. Unsupported formats and malformed inputs return `null`.
16006
+ */
16007
+ function probeRasterImage(data) {
16008
+ if (asyncProbeResults.has(data)) return asyncProbeResults.get(data) ?? null;
16009
+ if (data.byteLength > MAX_SYNC_RASTER_BYTES) return null;
16010
+ return readPngInfo(data, true) ?? readJpegInfo(data, true);
16011
+ }
16121
16012
  function probeRasterImageDimensions(data) {
16122
16013
  if (asyncProbeResults.has(data)) return asyncProbeResults.get(data) ?? null;
16123
16014
  if (data.byteLength > MAX_SYNC_RASTER_BYTES) return null;
16124
16015
  return readPngInfo(data, false) ?? readJpegInfo(data, false);
16125
16016
  }
16126
16017
  //#endregion
16127
- //#region ../core/src/io/project-xml-protocol.ts
16128
- const mergeAttrs = (...parts) => Object.assign({}, ...parts);
16129
- const mergeChildren = (...parts) => Object.assign({}, ...parts);
16130
- const mergeContainers = (...parts) => Object.assign({}, ...parts);
16131
- const defineContainer = (items) => ({
16132
- kind: "orderedVariants",
16133
- items
16134
- });
16135
- const defineNode = (attrs, children, containers) => ({
16136
- attrs,
16137
- ...children ? { children } : {},
16138
- ...containers ? { containers } : {}
16139
- });
16140
- const PACKAGE_DESCRIPTION_ATTRS = {
16141
- id: { canonical: "id" },
16142
- hasFavorites: { canonical: "hasFavorites" },
16143
- compressPNG: { canonical: "compressPNG" },
16144
- jpegQuality: { canonical: "jpegQuality" }
16145
- };
16146
- const BRANCH_DESCRIPTION_ATTRS = {};
16147
- const PACKAGE_PUBLISH_ATTRS = {
16018
+ //#region ../core/src/document.ts
16019
+ /**
16020
+ * Wraps a FairyGUI project and its resources for easier modification.
16021
+ *
16022
+ * Documents manage FairyGUI assets and the relationships among dependencies using an
16023
+ * internal property graph. A new resource is created by calling 'create' methods on the
16024
+ * document. Resources are destroyed by calling {@link Property.dispose}().
16025
+ *
16026
+ * Usage:
16027
+ *
16028
+ * ```ts
16029
+ * const document = new Document();
16030
+ * const pkg = document.createPackage('MyPackage');
16031
+ * const component = document.createComponent('Button');
16032
+ * const image = document.createGImage('bg');
16033
+ * component.addChild(image);
16034
+ * pkg.addResource(component);
16035
+ * ```
16036
+ *
16037
+ * @category Documents
16038
+ */
16039
+ var Document = class Document {
16040
+ _graph = new Graph();
16041
+ _root = new Root(this._graph);
16042
+ _logger = Logger.DEFAULT_INSTANCE;
16043
+ _projectDir = "";
16044
+ static _GRAPH_DOCUMENTS = /* @__PURE__ */ new WeakMap();
16045
+ static fromGraph(graph) {
16046
+ return Document._GRAPH_DOCUMENTS.get(graph) || null;
16047
+ }
16048
+ constructor() {
16049
+ Document._GRAPH_DOCUMENTS.set(this._graph, this);
16050
+ }
16051
+ getRoot() {
16052
+ return this._root;
16053
+ }
16054
+ /** @hidden */
16055
+ getGraph() {
16056
+ return this._graph;
16057
+ }
16058
+ getLogger() {
16059
+ return this._logger;
16060
+ }
16061
+ setLogger(logger) {
16062
+ this._logger = logger;
16063
+ return this;
16064
+ }
16065
+ getProjectDir() {
16066
+ return this._projectDir;
16067
+ }
16068
+ setProjectDir(projectDir) {
16069
+ this._projectDir = projectDir;
16070
+ return this;
16071
+ }
16072
+ async transform(...transforms) {
16073
+ const stack = transforms.map((fn) => fn.name);
16074
+ for (const transform of transforms) await transform(this, { stack });
16075
+ return this;
16076
+ }
16077
+ /****** Extension factory methods ******/
16078
+ createExtension(ctor) {
16079
+ const extensionName = ctor.EXTENSION_NAME;
16080
+ return this.getRoot().listExtensionsUsed().find((ext) => ext.extensionName === extensionName) || new ctor(this);
16081
+ }
16082
+ /****** Property factory methods ******/
16083
+ createPackage(name = "") {
16084
+ return new Package(this._graph, name);
16085
+ }
16086
+ createImageResource(name = "") {
16087
+ return new ImageResource(this._graph, name);
16088
+ }
16089
+ createSoundResource(name = "") {
16090
+ return new SoundResource(this._graph, name);
16091
+ }
16092
+ createMiscResource(name = "") {
16093
+ return new MiscResource(this._graph, name);
16094
+ }
16095
+ createFontResource(name = "") {
16096
+ return new FontResource(this._graph, name);
16097
+ }
16098
+ createMovieClipResource(name = "") {
16099
+ return new MovieClipResource(this._graph, name);
16100
+ }
16101
+ createSpineResource(name = "") {
16102
+ return new SpineResource(this._graph, name);
16103
+ }
16104
+ createDragonBonesResource(name = "") {
16105
+ return new DragonBonesResource(this._graph, name);
16106
+ }
16107
+ createComponent(name = "") {
16108
+ return new Component(this._graph, name);
16109
+ }
16110
+ createAtlas(name = "") {
16111
+ return new Atlas(this._graph, name);
16112
+ }
16113
+ createSprite(name = "") {
16114
+ return new Sprite(this._graph, name);
16115
+ }
16116
+ createBuffer(name = "") {
16117
+ return new FairyBuffer(this._graph, name);
16118
+ }
16119
+ createGImage(name = "") {
16120
+ return new GImage(this._graph, name);
16121
+ }
16122
+ createGTextField(name = "") {
16123
+ return new GTextField(this._graph, name);
16124
+ }
16125
+ createGRichTextField(name = "") {
16126
+ return new GRichTextField(this._graph, name);
16127
+ }
16128
+ createGTextInput(name = "") {
16129
+ return new GTextInput(this._graph, name);
16130
+ }
16131
+ createGGraph(name = "") {
16132
+ return new GGraph(this._graph, name);
16133
+ }
16134
+ createGGroup(name = "") {
16135
+ return new GGroup(this._graph, name);
16136
+ }
16137
+ createGLoader(name = "") {
16138
+ return new GLoader(this._graph, name);
16139
+ }
16140
+ createGLoader3D(name = "") {
16141
+ return new GLoader3D(this._graph, name);
16142
+ }
16143
+ createGMovieClip(name = "") {
16144
+ return new GMovieClip(this._graph, name);
16145
+ }
16146
+ createGComponent(name = "") {
16147
+ return new GComponent(this._graph, name);
16148
+ }
16149
+ createGList(name = "") {
16150
+ return new GList(this._graph, name);
16151
+ }
16152
+ createGTree(name = "") {
16153
+ return new GTree(this._graph, name);
16154
+ }
16155
+ createGButton(name = "") {
16156
+ return new GButton(this._graph, name);
16157
+ }
16158
+ createGLabel(name = "") {
16159
+ return new GLabel(this._graph, name);
16160
+ }
16161
+ createGComboBox(name = "") {
16162
+ return new GComboBox(this._graph, name);
16163
+ }
16164
+ createGProgressBar(name = "") {
16165
+ return new GProgressBar(this._graph, name);
16166
+ }
16167
+ createGSlider(name = "") {
16168
+ return new GSlider(this._graph, name);
16169
+ }
16170
+ createGScrollBar(name = "") {
16171
+ return new GScrollBar(this._graph, name);
16172
+ }
16173
+ createController(name = "") {
16174
+ return new Controller(this._graph, name);
16175
+ }
16176
+ createControllerPage(name = "") {
16177
+ return new ControllerPage(this._graph, name);
16178
+ }
16179
+ createControllerAction(name = "") {
16180
+ return new ControllerAction(this._graph, name);
16181
+ }
16182
+ createTransition(name = "") {
16183
+ return new Transition(this._graph, name);
16184
+ }
16185
+ createTransitionItem(name = "") {
16186
+ return new TransitionItem(this._graph, name);
16187
+ }
16188
+ createGear(name = "") {
16189
+ return new Gear(this._graph, name);
16190
+ }
16191
+ createFontGlyph(name = "") {
16192
+ return new FontGlyph(this._graph, name);
16193
+ }
16194
+ createMovieFrame(name = "") {
16195
+ return new MovieFrame(this._graph, name);
16196
+ }
16197
+ };
16198
+ //#endregion
16199
+ //#region ../core/src/utils/resource-folder.ts
16200
+ function normalizeResourceFolderPath(value) {
16201
+ const segments = value.replace(/\\/g, "/").split("/").filter(Boolean);
16202
+ return segments.length > 0 ? `/${segments.join("/")}/` : "/";
16203
+ }
16204
+ function resourceFolderParentPath(value) {
16205
+ const segments = normalizeResourceFolderPath(value).split("/").filter(Boolean);
16206
+ segments.pop();
16207
+ return segments.length > 0 ? `/${segments.join("/")}/` : "/";
16208
+ }
16209
+ function resourceFolderName(value) {
16210
+ return normalizeResourceFolderPath(value).split("/").filter(Boolean).pop() ?? "";
16211
+ }
16212
+ //#endregion
16213
+ //#region ../core/src/io/project-xml-protocol.ts
16214
+ const mergeAttrs = (...parts) => Object.assign({}, ...parts);
16215
+ const mergeChildren = (...parts) => Object.assign({}, ...parts);
16216
+ const mergeContainers = (...parts) => Object.assign({}, ...parts);
16217
+ const defineContainer = (items) => ({
16218
+ kind: "orderedVariants",
16219
+ items
16220
+ });
16221
+ const defineNode = (attrs, children, containers) => ({
16222
+ attrs,
16223
+ ...children ? { children } : {},
16224
+ ...containers ? { containers } : {}
16225
+ });
16226
+ const PACKAGE_DESCRIPTION_ATTRS = {
16227
+ id: { canonical: "id" },
16228
+ hasFavorites: { canonical: "hasFavorites" },
16229
+ compressPNG: { canonical: "compressPNG" },
16230
+ jpegQuality: { canonical: "jpegQuality" },
16231
+ branchNames: { canonical: "branchNames" }
16232
+ };
16233
+ const BRANCH_DESCRIPTION_ATTRS = {};
16234
+ const PACKAGE_PUBLISH_ATTRS = {
16148
16235
  name: { canonical: "name" },
16149
16236
  path: { canonical: "path" },
16150
16237
  branchPath: { canonical: "branchPath" },
16151
16238
  packageCount: { canonical: "packageCount" },
16152
16239
  genCode: { canonical: "genCode" },
16153
- codePath: { canonical: "codePath" }
16240
+ codePath: { canonical: "codePath" },
16241
+ maxAtlasSize: { canonical: "maxAtlasSize" },
16242
+ sizeOption: { canonical: "sizeOption" },
16243
+ npot: { canonical: "npot" },
16244
+ square: { canonical: "square" },
16245
+ rotation: { canonical: "rotation" },
16246
+ multiPage: { canonical: "multiPage" },
16247
+ extractAlpha: { canonical: "extractAlpha" },
16248
+ maxAtlasIndex: { canonical: "maxAtlasIndex" },
16249
+ excluded: { canonical: "excluded" }
16154
16250
  };
16155
16251
  const PACKAGE_PUBLISH_ATLAS_ATTRS = {
16156
16252
  name: { canonical: "name" },
16157
- index: { canonical: "index" }
16253
+ index: { canonical: "index" },
16254
+ compression: { canonical: "compression" }
16158
16255
  };
16159
16256
  const PACKAGE_RESOURCE_BASE_ATTRS = {
16160
16257
  id: { canonical: "id" },
@@ -16187,7 +16284,10 @@ const PACKAGE_FONT_RESOURCE_ATTRS = {
16187
16284
  renderMode: { canonical: "renderMode" },
16188
16285
  samplePointSize: { canonical: "samplePointSize" }
16189
16286
  };
16190
- const PACKAGE_MOVIE_CLIP_RESOURCE_ATTRS = { atlas: { canonical: "atlas" } };
16287
+ const PACKAGE_MOVIE_CLIP_RESOURCE_ATTRS = {
16288
+ atlas: { canonical: "atlas" },
16289
+ smoothing: { canonical: "smoothing" }
16290
+ };
16191
16291
  const PACKAGE_SKELETON_RESOURCE_ATTRS = {
16192
16292
  width: { canonical: "width" },
16193
16293
  height: { canonical: "height" },
@@ -18572,8 +18672,11 @@ function assignSetting(settings, key, value) {
18572
18672
  case "adaptation":
18573
18673
  settings.adaptation = value;
18574
18674
  break;
18575
- default:
18576
- settings[key] = value;
18675
+ case "customProperties":
18676
+ settings.customProperties = value;
18677
+ break;
18678
+ case "i18n":
18679
+ settings.i18n = value;
18577
18680
  break;
18578
18681
  }
18579
18682
  }
@@ -18617,6 +18720,7 @@ var ProjectReader = class {
18617
18720
  }
18618
18721
  const branchNames = await this._readPackageBranches(ctx, options);
18619
18722
  if (branchNames.length > 0) doc.getRoot().setBranches(branchNames);
18723
+ this._linkPackageBranchItems(doc);
18620
18724
  for (const [_key, resource] of ctx.resourceMap) {
18621
18725
  if (resource.propertyType !== "Component") continue;
18622
18726
  const comp = resource;
@@ -18630,6 +18734,22 @@ var ProjectReader = class {
18630
18734
  }
18631
18735
  return doc;
18632
18736
  }
18737
+ _linkPackageBranchItems(doc) {
18738
+ for (const pkg of doc.getRoot().listPackages()) {
18739
+ const branchNames = pkg.listBranchNames();
18740
+ if (branchNames.length === 0) continue;
18741
+ const variants = /* @__PURE__ */ new Map();
18742
+ for (const resource of pkg.listResources()) {
18743
+ const branchName = resource.getBranch();
18744
+ if (!branchName) continue;
18745
+ variants.set(`${branchName}\0${resource.propertyType}\0${resource.getPath()}\0${resource.getName()}`, resource.getId());
18746
+ }
18747
+ for (const resource of pkg.listResources()) {
18748
+ if (resource.getBranch()) continue;
18749
+ resource.setBranchItemIds(branchNames.map((branchName) => variants.get(`${branchName}\0${resource.propertyType}\0${resource.getPath()}\0${resource.getName()}`) ?? ""));
18750
+ }
18751
+ }
18752
+ }
18633
18753
  async _readPackageBranches(ctx, options) {
18634
18754
  const fs = this._fs;
18635
18755
  let dirNames = [];
@@ -18696,6 +18816,7 @@ var ProjectReader = class {
18696
18816
  if (!desc) return;
18697
18817
  let pkg = ctx.document.getRoot().getPackage(dirName);
18698
18818
  if (!pkg) pkg = ctx.document.createPackage(dirName);
18819
+ if (branchName) pkg.addBranchName(branchName);
18699
18820
  pkg.setExtras({
18700
18821
  ...pkg.getExtras(),
18701
18822
  _preservePackageResourceOrder: true
@@ -18703,6 +18824,17 @@ var ProjectReader = class {
18703
18824
  if (!branchName) {
18704
18825
  const packageId = readXmlAttr(desc, PROJECT_XML_PROTOCOL.packageDescription.attrs.id) || "";
18705
18826
  pkg.setId(packageId);
18827
+ const serializedBranchNames = readXmlAttr(desc, PROJECT_XML_PROTOCOL.packageDescription.attrs.branchNames);
18828
+ if (serializedBranchNames !== void 0) {
18829
+ let parsedBranchNames;
18830
+ try {
18831
+ parsedBranchNames = JSON.parse(serializedBranchNames);
18832
+ } catch {
18833
+ throw new Error(`Invalid package branchNames for "${dirName}".`);
18834
+ }
18835
+ if (!Array.isArray(parsedBranchNames) || !parsedBranchNames.every((name) => typeof name === "string" && name.length > 0) || new Set(parsedBranchNames).size !== parsedBranchNames.length) throw new Error(`Invalid package branchNames for "${dirName}".`);
18836
+ pkg.setBranchNames(parsedBranchNames);
18837
+ }
18706
18838
  const compressPNG = readXmlAttr(desc, PROJECT_XML_PROTOCOL.packageDescription.attrs.compressPNG);
18707
18839
  if (compressPNG !== void 0) pkg.setCompressPNG(parseBool(compressPNG));
18708
18840
  const jpegQuality = readXmlAttr(desc, PROJECT_XML_PROTOCOL.packageDescription.attrs.jpegQuality);
@@ -18717,6 +18849,32 @@ var ProjectReader = class {
18717
18849
  pkg.setPublishPackageCount(parseInt2(readXmlAttr(publish, PROJECT_XML_PROTOCOL.packagePublish.attrs.packageCount), 0));
18718
18850
  pkg.setGenCode(parseBool(readXmlAttr(publish, PROJECT_XML_PROTOCOL.packagePublish.attrs.genCode)));
18719
18851
  pkg.setCodePath(readXmlAttr(publish, PROJECT_XML_PROTOCOL.packagePublish.attrs.codePath) || "");
18852
+ const globalAtlas = ctx.settings.publish?.atlasSetting;
18853
+ const maxAtlasSize = readXmlAttr(publish, PROJECT_XML_PROTOCOL.packagePublish.attrs.maxAtlasSize);
18854
+ const sizeOption = parseBool(readXmlAttr(publish, PROJECT_XML_PROTOCOL.packagePublish.attrs.npot)) ? "npot" : readXmlAttr(publish, PROJECT_XML_PROTOCOL.packagePublish.attrs.sizeOption) || globalAtlas?.sizeOption || "pot";
18855
+ const square = readXmlAttr(publish, PROJECT_XML_PROTOCOL.packagePublish.attrs.square);
18856
+ const rotation = readXmlAttr(publish, PROJECT_XML_PROTOCOL.packagePublish.attrs.rotation);
18857
+ const multiPage = readXmlAttr(publish, PROJECT_XML_PROTOCOL.packagePublish.attrs.multiPage);
18858
+ pkg.setSourceAtlasSettings({
18859
+ useGlobal: maxAtlasSize === void 0,
18860
+ maxSize: parseInt2(maxAtlasSize, globalAtlas?.maxSize ?? 2048),
18861
+ sizeOption: sizeOption === "npot" || sizeOption === "mof" ? sizeOption : "pot",
18862
+ forceSquare: square === void 0 ? globalAtlas?.forceSquare ?? false : parseBool(square),
18863
+ allowRotation: rotation === void 0 ? globalAtlas?.allowRotation ?? false : parseBool(rotation),
18864
+ paging: multiPage === void 0 ? globalAtlas?.paging ?? true : parseBool(multiPage),
18865
+ extractAlpha: parseBool(readXmlAttr(publish, PROJECT_XML_PROTOCOL.packagePublish.attrs.extractAlpha)),
18866
+ maxIndex: parseInt2(readXmlAttr(publish, PROJECT_XML_PROTOCOL.packagePublish.attrs.maxAtlasIndex), 10),
18867
+ atlases: ensureArray(publish.atlas).flatMap((value) => {
18868
+ const atlas = getXmlNode(value);
18869
+ if (!atlas) return [];
18870
+ return [{
18871
+ index: parseInt2(readXmlAttr(atlas, PROJECT_XML_PROTOCOL.packagePublishAtlas.attrs.index)),
18872
+ name: readXmlAttr(atlas, PROJECT_XML_PROTOCOL.packagePublishAtlas.attrs.name) || "",
18873
+ compression: parseBool(readXmlAttr(atlas, PROJECT_XML_PROTOCOL.packagePublishAtlas.attrs.compression))
18874
+ }];
18875
+ }),
18876
+ excludedResourceIds: (readXmlAttr(publish, PROJECT_XML_PROTOCOL.packagePublish.attrs.excluded) || "").split(",").filter(Boolean)
18877
+ });
18720
18878
  }
18721
18879
  if (pkg.getId()) ctx.packageMap.set(pkg.getId(), pkg);
18722
18880
  const packageDir = branchName ? fs.join(ctx.basePath, `assets_${branchName}`, dirName) : fs.join(ctx.basePath, "assets", dirName);
@@ -18831,10 +18989,9 @@ var ProjectReader = class {
18831
18989
  if (resource.propertyType === "ImageResource") {
18832
18990
  const size = probeRasterImageDimensions(data);
18833
18991
  if (size) resource.setWidth(size.width).setHeight(size.height);
18834
- } else if (resource.propertyType === "MovieClipResource") {
18835
- const size = tryReadJtaSize(data);
18836
- if (size) resource.setWidth(size.width).setHeight(size.height);
18837
- }
18992
+ } else if (resource.propertyType === "MovieClipResource") try {
18993
+ applyDerivedMovieClipModel(doc, resource, deriveMovieClipModelFromJta(data));
18994
+ } catch {}
18838
18995
  } catch {}
18839
18996
  }
18840
18997
  }
@@ -19014,6 +19171,8 @@ var ProjectReader = class {
19014
19171
  res.setFavorite(favorite);
19015
19172
  const textureSetMode = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.packageMovieClipResource.attrs.atlas);
19016
19173
  if (textureSetMode !== void 0) res.setTextureSetMode(textureSetMode);
19174
+ const smoothing = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.packageMovieClipResource.attrs.smoothing);
19175
+ res.setSmoothing(smoothing !== "false");
19017
19176
  pkg.addResource(res);
19018
19177
  ctx.registerResource(pkg.getId(), id, res);
19019
19178
  return res;
@@ -20367,25 +20526,44 @@ var ProjectWriter = class {
20367
20526
  const basePath = fs.dirname(projectPath);
20368
20527
  const currentSourceFilePaths = /* @__PURE__ */ new Set();
20369
20528
  const currentResourceFolderPaths = /* @__PURE__ */ new Set();
20529
+ const currentBranchDirectoryPaths = /* @__PURE__ */ new Set();
20370
20530
  const staleSourceFilePaths = new Set((options.staleSourceFiles ?? []).map((source) => this._projectSourceFilePath(basePath, source)));
20531
+ const staleBranchDirectoryPaths = new Set((options.staleBranchDirectories ?? []).map((directory) => this._projectBranchDirectoryPath(basePath, directory)));
20532
+ if (staleBranchDirectoryPaths.size > 0 && !fs.rmdir) throw new Error("Project branch cleanup requires a FileSystem.rmdir() implementation.");
20371
20533
  for (const pkg of root.listPackages()) this._assertPackageOutputTargets(pkg);
20372
- const fairyXml = `<?xml version="1.0" encoding="utf-8"?>\n<projectDescription id="${root.getProjectId()}" type="${this._projectTypeName(root.getProjectType())}" version="${root.getVersion() || "3.0"}"/>\n`;
20373
- await fs.writeFile(projectPath, fairyXml);
20374
20534
  const settings = root.getSettings?.() ?? {};
20375
20535
  const settingsPath = fs.join(basePath, "settings");
20536
+ const staleOptionalSettings = [];
20537
+ for (const [fileName, key] of [["CustomProperties.json", "customProperties"], ["i18n.json", "i18n"]]) {
20538
+ const filePath = fs.join(settingsPath, fileName);
20539
+ if (settings[key] === void 0 && await fs.exists(filePath)) staleOptionalSettings.push(filePath);
20540
+ }
20541
+ if (staleOptionalSettings.length > 0 && !fs.unlink) throw new Error("Project settings cleanup requires a FileSystem.unlink() implementation.");
20542
+ const fairyXml = `<?xml version="1.0" encoding="utf-8"?>\n<projectDescription id="${root.getProjectId()}" type="${this._projectTypeName(root.getProjectType())}" version="${root.getVersion() || "3.0"}"/>\n`;
20543
+ await fs.writeFile(projectPath, fairyXml);
20376
20544
  await fs.mkdir(settingsPath);
20377
20545
  for (const [fileName, key] of Object.entries({
20378
20546
  "Publish.json": "publish",
20379
20547
  "Common.json": "common",
20380
- "Adaptation.json": "adaptation"
20548
+ "Adaptation.json": "adaptation",
20549
+ "CustomProperties.json": "customProperties",
20550
+ "i18n.json": "i18n"
20381
20551
  })) if (settings[key]) await fs.writeFile(fs.join(settingsPath, fileName), JSON.stringify(settings[key], null, " "));
20552
+ for (const filePath of staleOptionalSettings) await fs.unlink(filePath);
20382
20553
  const assetsPath = fs.join(basePath, "assets");
20383
20554
  await fs.mkdir(assetsPath);
20384
- for (const pkg of root.listPackages()) await this._writePackage(doc, pkg, assetsPath, currentSourceFilePaths, currentResourceFolderPaths);
20555
+ for (const branchName of root.listBranches()) {
20556
+ this._assertSafePathSegment(branchName, "branch name");
20557
+ const branchPath = fs.join(basePath, `assets_${branchName}`);
20558
+ await fs.mkdir(branchPath);
20559
+ currentBranchDirectoryPaths.add(branchPath);
20560
+ }
20561
+ for (const pkg of root.listPackages()) await this._writePackage(doc, pkg, assetsPath, currentSourceFilePaths, currentResourceFolderPaths, currentBranchDirectoryPaths);
20385
20562
  await this._removeStaleSourceFiles(currentSourceFilePaths, staleSourceFilePaths);
20386
20563
  await this._removeStaleResourceFolders(currentResourceFolderPaths, new Set((options.staleResourceFolders ?? []).map((folder) => this._projectResourceFolderPath(basePath, folder))));
20564
+ await this._removeStaleBranchDirectories(currentBranchDirectoryPaths, staleBranchDirectoryPaths);
20387
20565
  }
20388
- async _writePackage(_doc, pkg, assetsPath, currentSourceFilePaths, currentResourceFolderPaths) {
20566
+ async _writePackage(_doc, pkg, assetsPath, currentSourceFilePaths, currentResourceFolderPaths, currentBranchDirectoryPaths) {
20389
20567
  const fs = this._fs;
20390
20568
  this._assertSafePathSegment(pkg.getName(), "package name");
20391
20569
  const pkgDir = fs.join(assetsPath, pkg.getName());
@@ -20414,6 +20592,8 @@ var ProjectWriter = class {
20414
20592
  const codePath = pkg.getCodePath();
20415
20593
  const packageDescriptionAttrs = {};
20416
20594
  writeXmlAttr(packageDescriptionAttrs, PROJECT_XML_PROTOCOL.packageDescription.attrs.id, pkg.getId());
20595
+ const packageBranchNames = pkg.listBranchNames();
20596
+ writeXmlAttr(packageDescriptionAttrs, PROJECT_XML_PROTOCOL.packageDescription.attrs.branchNames, packageBranchNames.length > 0 ? JSON.stringify(packageBranchNames) : void 0);
20417
20597
  if (pkg.listResources().some((resource) => resource.getFavorite?.()) || pkg.listResourceFolders().some((folder) => folder.favorite)) writeXmlAttr(packageDescriptionAttrs, PROJECT_XML_PROTOCOL.packageDescription.attrs.hasFavorites, "true");
20418
20598
  const compressPNG = pkg.getCompressPNG();
20419
20599
  if (compressPNG !== null) writeXmlAttr(packageDescriptionAttrs, PROJECT_XML_PROTOCOL.packageDescription.attrs.compressPNG, compressPNG ? "true" : "false");
@@ -20426,11 +20606,22 @@ var ProjectWriter = class {
20426
20606
  writeXmlAttr(publishAttrs, PROJECT_XML_PROTOCOL.packagePublish.attrs.packageCount, publishPackageCount > 0 ? publishPackageCount : void 0);
20427
20607
  writeXmlAttr(publishAttrs, PROJECT_XML_PROTOCOL.packagePublish.attrs.genCode, genCode ? "true" : void 0);
20428
20608
  writeXmlAttr(publishAttrs, PROJECT_XML_PROTOCOL.packagePublish.attrs.codePath, codePath || void 0);
20429
- const publishAtlases = pkg.listAtlases().map((atlas) => {
20609
+ const sourceAtlasSettings = pkg.getSourceAtlasSettings();
20610
+ if (!sourceAtlasSettings.useGlobal) {
20611
+ writeXmlAttr(publishAttrs, PROJECT_XML_PROTOCOL.packagePublish.attrs.maxAtlasSize, String(sourceAtlasSettings.maxSize));
20612
+ writeXmlAttr(publishAttrs, PROJECT_XML_PROTOCOL.packagePublish.attrs.sizeOption, sourceAtlasSettings.sizeOption);
20613
+ writeXmlAttr(publishAttrs, PROJECT_XML_PROTOCOL.packagePublish.attrs.square, sourceAtlasSettings.forceSquare ? "true" : "false");
20614
+ writeXmlAttr(publishAttrs, PROJECT_XML_PROTOCOL.packagePublish.attrs.rotation, sourceAtlasSettings.allowRotation ? "true" : "false");
20615
+ writeXmlAttr(publishAttrs, PROJECT_XML_PROTOCOL.packagePublish.attrs.multiPage, sourceAtlasSettings.paging ? "true" : "false");
20616
+ }
20617
+ writeXmlAttr(publishAttrs, PROJECT_XML_PROTOCOL.packagePublish.attrs.extractAlpha, sourceAtlasSettings.extractAlpha ? "true" : void 0);
20618
+ writeXmlAttr(publishAttrs, PROJECT_XML_PROTOCOL.packagePublish.attrs.maxAtlasIndex, sourceAtlasSettings.maxIndex === 10 ? void 0 : String(sourceAtlasSettings.maxIndex));
20619
+ writeXmlAttr(publishAttrs, PROJECT_XML_PROTOCOL.packagePublish.attrs.excluded, sourceAtlasSettings.excludedResourceIds.length > 0 ? sourceAtlasSettings.excludedResourceIds.join(",") : void 0);
20620
+ const publishAtlases = [...sourceAtlasSettings.atlases].sort((left, right) => left.index - right.index).map((atlas) => {
20430
20621
  const attrs = {};
20431
- const index = atlas.getIndex?.() ?? 0;
20432
- writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.packagePublishAtlas.attrs.name, index === 0 ? "Default" : atlas.getName());
20433
- writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.packagePublishAtlas.attrs.index, String(index));
20622
+ writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.packagePublishAtlas.attrs.name, atlas.name || void 0);
20623
+ writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.packagePublishAtlas.attrs.index, String(atlas.index));
20624
+ writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.packagePublishAtlas.attrs.compression, atlas.compression ? "true" : void 0);
20434
20625
  return attrs;
20435
20626
  });
20436
20627
  if (publishAtlases.length > 0) publishAttrs.atlas = publishAtlases;
@@ -20444,7 +20635,11 @@ var ProjectWriter = class {
20444
20635
  await writeComponent(this._fs, comp, pkgDir, this._componentSourceRelativePath(comp));
20445
20636
  }
20446
20637
  await this._writeResourceSourceFiles(mainResources, pkgDir, currentSourceFilePaths);
20447
- const branchNames = new Set([...resourcesByBranch.keys(), ...foldersByBranch.keys()]);
20638
+ const branchNames = new Set([
20639
+ ...pkg.listBranchNames(),
20640
+ ...resourcesByBranch.keys(),
20641
+ ...foldersByBranch.keys()
20642
+ ]);
20448
20643
  for (const branchName of branchNames) {
20449
20644
  if (!branchName) continue;
20450
20645
  const branchResources = resourcesByBranch.get(branchName) ?? [];
@@ -20452,6 +20647,7 @@ var ProjectWriter = class {
20452
20647
  this._assertSafePathSegment(branchName, "branch name");
20453
20648
  const branchPkgDir = fs.join(basePath, `assets_${branchName}`, pkg.getName());
20454
20649
  await fs.mkdir(branchPkgDir);
20650
+ currentBranchDirectoryPaths.add(branchPkgDir);
20455
20651
  const branchDescriptorPath = fs.join(branchPkgDir, "package_branch.xml");
20456
20652
  await fs.writeFile(branchDescriptorPath, this._renderBranchDescriptionXml(branchFolders, branchResources, preserveResourceOrder));
20457
20653
  currentSourceFilePaths.add(branchDescriptorPath);
@@ -20507,6 +20703,13 @@ var ProjectWriter = class {
20507
20703
  await this._fs.rmdir(folderPath);
20508
20704
  }
20509
20705
  }
20706
+ async _removeStaleBranchDirectories(currentBranchDirectoryPaths, staleBranchDirectoryPaths) {
20707
+ const candidates = [...staleBranchDirectoryPaths].filter((directoryPath) => !currentBranchDirectoryPaths.has(directoryPath)).sort((left, right) => right.length - left.length);
20708
+ for (const directoryPath of candidates) {
20709
+ if (!await this._fs.exists(directoryPath)) continue;
20710
+ await this._fs.rmdir(directoryPath);
20711
+ }
20712
+ }
20510
20713
  _assertPackageOutputTargets(pkg) {
20511
20714
  this._assertSafePathSegment(pkg.getName(), "package name");
20512
20715
  const resourcesByBranch = /* @__PURE__ */ new Map();
@@ -20522,7 +20725,11 @@ var ProjectWriter = class {
20522
20725
  bucket.push(folder);
20523
20726
  foldersByBranch.set(folder.branch, bucket);
20524
20727
  }
20525
- for (const branchName of new Set([...resourcesByBranch.keys(), ...foldersByBranch.keys()])) {
20728
+ for (const branchName of new Set([
20729
+ ...pkg.listBranchNames(),
20730
+ ...resourcesByBranch.keys(),
20731
+ ...foldersByBranch.keys()
20732
+ ])) {
20526
20733
  const resources = resourcesByBranch.get(branchName) ?? [];
20527
20734
  if (branchName) this._assertSafePathSegment(branchName, "branch name");
20528
20735
  const targets = new Map([[branchName ? "package_branch.xml" : "package.xml", "package descriptor"]]);
@@ -20557,6 +20764,13 @@ var ProjectWriter = class {
20557
20764
  const assetRoot = folder.branch ? `assets_${folder.branch}` : "assets";
20558
20765
  return this._fs.join(basePath, assetRoot, folder.packageName, relativePath);
20559
20766
  }
20767
+ _projectBranchDirectoryPath(basePath, directory) {
20768
+ this._assertSafePathSegment(directory.branch, "stale branch name");
20769
+ const branchRoot = this._fs.join(basePath, `assets_${directory.branch}`);
20770
+ if (!directory.packageName) return branchRoot;
20771
+ this._assertSafePathSegment(directory.packageName, "stale branch package name");
20772
+ return this._fs.join(branchRoot, directory.packageName);
20773
+ }
20560
20774
  _resourceSourceRelativePath(resource, fileName) {
20561
20775
  if (!fileName) return "";
20562
20776
  this._assertSafePathSegment(fileName, "resource file name");
@@ -20571,7 +20785,7 @@ var ProjectWriter = class {
20571
20785
  return this._normalizeSourceRelativePath([componentPath, `${name}.xml`].filter(Boolean).join("/"));
20572
20786
  }
20573
20787
  _assertSafePathSegment(value, label) {
20574
- if (!value || value === "." || value === ".." || /[\\/:]/.test(value)) throw new Error(`Invalid ${label} "${value}".`);
20788
+ if (!value || value.trim() !== value || value === "." || value === ".." || /[\\/:]/.test(value) || /[. ]$/.test(value) || /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/i.test(value)) throw new Error(`Invalid ${label} "${value}".`);
20575
20789
  }
20576
20790
  _normalizeSourceRelativePath(value) {
20577
20791
  const segments = value.replace(/\\/g, "/").split("/").filter(Boolean);
@@ -20716,8 +20930,10 @@ var ProjectWriter = class {
20716
20930
  if (samplePointSize !== 0) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.packageFontResource.attrs.samplePointSize, String(samplePointSize));
20717
20931
  }
20718
20932
  if (res.propertyType === "MovieClipResource") {
20719
- const textureSetMode = res.getTextureSetMode?.() ?? "";
20933
+ const movieClipRes = res;
20934
+ const textureSetMode = movieClipRes.getTextureSetMode?.() ?? "";
20720
20935
  if (textureSetMode) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.packageMovieClipResource.attrs.atlas, textureSetMode);
20936
+ if (movieClipRes.getSmoothing?.() === false) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.packageMovieClipResource.attrs.smoothing, "false");
20721
20937
  }
20722
20938
  if (res.propertyType === "SpineResource" || res.propertyType === "DragonBonesResource") {
20723
20939
  const skeletonRes = res;
@@ -22050,6 +22266,7 @@ var BinaryReader = class {
22050
22266
  if (packageBranches.length > 0) for (const branchName of packageBranches) doc.getRoot().addBranch(branchName);
22051
22267
  const pkg = getOrCreatePackage(doc, packageId, packageName);
22052
22268
  if (pkg.listResources().length > 0 || pkg.listAtlases().length > 0) throw new Error(`Package "${packageName}" (${packageId}) has already been read.`);
22269
+ pkg.setBranchNames(packageBranches);
22053
22270
  const atlasMap = /* @__PURE__ */ new Map();
22054
22271
  for (const dep of dependencies) {
22055
22272
  if (!dep.id || dep.id === packageId) continue;
@@ -24033,7 +24250,8 @@ var BinaryWriter = class {
24033
24250
  id: dep.getId(),
24034
24251
  name: dep.getName()
24035
24252
  })).filter((dep) => !!dep.id);
24036
- const branchNames = includeBranches ? getPackageBranchNames(doc, resources) : [];
24253
+ const declaredBranchNames = pkg.listBranchNames();
24254
+ const branchNames = includeBranches ? declaredBranchNames.length > 0 ? declaredBranchNames : getPackageBranchNames(doc, resources) : [];
24037
24255
  const branchItemIdsMap = buildBranchItemIdsMap(pkg, branchNames);
24038
24256
  const publishedItemIdMap = new Map(resources.map((resource) => [resource.getId(), getPublishedItemId$1(resource)]));
24039
24257
  const sprites = [];
@@ -24509,9 +24727,11 @@ function getItemBranchName(item) {
24509
24727
  return item.getBranch?.() ?? "";
24510
24728
  }
24511
24729
  function getPackageBranchNames(doc, resources) {
24512
- const packageBranchSet = new Set(resources.map((resource) => getItemBranchName(resource)).filter((branchName) => !!branchName));
24513
- if (packageBranchSet.size === 0) return [];
24514
- return doc.getRoot().listBranches().filter((branchName) => packageBranchSet.has(branchName));
24730
+ const packageBranchNames = new Set(resources.map((resource) => getItemBranchName(resource)).filter((branchName) => !!branchName));
24731
+ const rootBranchNames = doc.getRoot().listBranches();
24732
+ const unknownBranchName = [...packageBranchNames].find((branchName) => !rootBranchNames.includes(branchName));
24733
+ if (unknownBranchName) throw new Error(`Package resource references unknown branch "${unknownBranchName}".`);
24734
+ return rootBranchNames.filter((branchName) => packageBranchNames.has(branchName));
24515
24735
  }
24516
24736
  function buildBranchResourceKey$1(resource) {
24517
24737
  const path = resource.getPath?.() ?? "";
@@ -25170,153 +25390,76 @@ function parseFnt(text) {
25170
25390
  }
25171
25391
  //#endregion
25172
25392
  //#region ../functions/src/atlas/jta.ts
25173
- const PNG_SIGNATURE = new Uint8Array([
25174
- 137,
25175
- 80,
25176
- 78,
25177
- 71,
25178
- 13,
25179
- 10,
25180
- 26,
25181
- 10
25182
- ]);
25183
25393
  function extractJtaFrames(data) {
25184
- const frames = [];
25185
- let offset = 0;
25186
- let firstPngOffset = -1;
25187
- while (offset < data.length) {
25188
- const signatureIndex = findPngSignature(data, offset);
25189
- if (signatureIndex === -1) break;
25190
- if (firstPngOffset === -1) firstPngOffset = signatureIndex;
25191
- const end = findPngEnd(data, signatureIndex);
25192
- if (end === -1) break;
25193
- frames.push(data.subarray(signatureIndex, end));
25194
- offset = end;
25195
- }
25196
- if (firstPngOffset === -1 || frames.length === 0) return { frames: [] };
25394
+ const parsed = parseJta(data);
25395
+ const derived = deriveMovieClipModel(parsed);
25197
25396
  return {
25198
- frames,
25199
- meta: parseJtaHeader(data, firstPngOffset, frames.length)
25200
- };
25201
- }
25202
- function findPngSignature(data, fromIndex) {
25203
- for (let index = fromIndex; index <= data.length - PNG_SIGNATURE.length; index += 1) {
25204
- let matched = true;
25205
- for (let signatureIndex = 0; signatureIndex < PNG_SIGNATURE.length; signatureIndex += 1) if (data[index + signatureIndex] !== PNG_SIGNATURE[signatureIndex]) {
25206
- matched = false;
25207
- break;
25397
+ frames: parsed.textures.map((texture) => texture.raw),
25398
+ meta: {
25399
+ interval: derived.interval,
25400
+ repeatDelay: derived.repeatDelay,
25401
+ swing: derived.swing,
25402
+ width: derived.dimensions.width,
25403
+ height: derived.dimensions.height,
25404
+ frames: derived.frames.map((frame) => ({
25405
+ addDelay: frame.addDelay,
25406
+ offsetX: frame.rectX,
25407
+ offsetY: frame.rectY,
25408
+ width: frame.rectWidth,
25409
+ height: frame.rectHeight,
25410
+ textureIndex: frame.textureIndex
25411
+ }))
25208
25412
  }
25209
- if (matched) return index;
25210
- }
25211
- return -1;
25413
+ };
25212
25414
  }
25213
- function findPngEnd(data, start) {
25214
- let position = start + PNG_SIGNATURE.length;
25215
- while (position + 8 <= data.length) {
25216
- const length = readUint32BE(data, position);
25217
- position += 8;
25218
- if (position + length + 4 > data.length) return -1;
25219
- const isEnd = data[position - 4] === 73 && data[position - 3] === 69 && data[position - 2] === 78 && data[position - 1] === 68;
25220
- position += length + 4;
25221
- if (isEnd) return position;
25222
- }
25223
- return -1;
25415
+ function detectSupportedRasterFormat(data) {
25416
+ if (data.length >= 8 && data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71 && data[4] === 13 && data[5] === 10 && data[6] === 26 && data[7] === 10) return "png";
25417
+ if (data.length >= 2 && data[0] === 255 && data[1] === 216) return "jpeg";
25418
+ return null;
25224
25419
  }
25225
- function parseJtaHeader(data, firstPngOffset, frameCount) {
25226
- if (data.length < 10) return void 0;
25227
- const state = { offset: 0 };
25228
- const end = Math.min(firstPngOffset, data.length);
25229
- if (!readUtfBE(data, state, end)) return void 0;
25230
- const version = readInt32BEAt(data, state, end);
25231
- if (version == null) return void 0;
25232
- const fpsRaw = readInt8At(data, state, end);
25233
- if (fpsRaw == null) return void 0;
25234
- const fps = fpsRaw > 0 ? fpsRaw : 24;
25235
- if (state.offset + 3 > end) return void 0;
25236
- state.offset += 3;
25237
- if (version < 102) return void 0;
25238
- readUint16BEAt(data, state, end);
25239
- readUint16BEAt(data, state, end);
25240
- const width = readUint16BEAt(data, state, end);
25241
- const height = readUint16BEAt(data, state, end);
25242
- if (width == null || height == null) return void 0;
25243
- const speedRaw = readUint8At(data, state, end);
25244
- const repeatDelayRaw = readUint8At(data, state, end);
25245
- const swingRaw = readInt8At(data, state, end);
25246
- const frameTableCount = readInt16BEAt(data, state, end);
25247
- if (speedRaw == null || repeatDelayRaw == null || swingRaw == null || frameTableCount == null) return void 0;
25248
- const frames = [];
25249
- for (let index = 0; index < frameTableCount; index += 1) {
25250
- const delayRaw = readInt16BEAt(data, state, end);
25251
- const offsetX = readInt16BEAt(data, state, end);
25252
- const offsetY = readInt16BEAt(data, state, end);
25253
- const frameWidth = readInt16BEAt(data, state, end);
25254
- const frameHeight = readInt16BEAt(data, state, end);
25255
- const textureIndex = readInt16BEAt(data, state, end);
25256
- if (delayRaw == null || offsetX == null || offsetY == null || frameWidth == null || frameHeight == null || textureIndex == null) break;
25257
- frames.push({
25258
- addDelay: Math.trunc(1e3 / fps * delayRaw),
25259
- offsetX,
25260
- offsetY,
25261
- width: frameWidth,
25262
- height: frameHeight,
25263
- textureIndex
25420
+ function couldNotDecode(filePath, frameIndex, textureIndex) {
25421
+ return /* @__PURE__ */ new Error(`atlas: Could not decode MovieClip "${filePath}" frame ${frameIndex} (texture ${textureIndex}).`);
25422
+ }
25423
+ async function prepareJtaForPublish(data, encoder, filePath) {
25424
+ const extracted = extractJtaFrames(data);
25425
+ const firstFrameIndexByTextureIndex = /* @__PURE__ */ new Map();
25426
+ for (let frameIndex = 0; frameIndex < extracted.meta.frames.length; frameIndex += 1) {
25427
+ const textureIndex = extracted.meta.frames[frameIndex].textureIndex;
25428
+ if (textureIndex >= 0 && !firstFrameIndexByTextureIndex.has(textureIndex)) firstFrameIndexByTextureIndex.set(textureIndex, frameIndex);
25429
+ }
25430
+ const referencedTextures = [];
25431
+ for (let textureIndex = 0; textureIndex < extracted.frames.length; textureIndex += 1) {
25432
+ const firstFrameIndex = firstFrameIndexByTextureIndex.get(textureIndex);
25433
+ if (firstFrameIndex === void 0) continue;
25434
+ const raw = extracted.frames[textureIndex];
25435
+ if (raw.byteLength === 0) throw new Error(`atlas: MovieClip "${filePath}" frame ${firstFrameIndex} references empty texture ${textureIndex}.`);
25436
+ const detectedFormat = detectSupportedRasterFormat(raw);
25437
+ if (!detectedFormat) throw new Error(`atlas: MovieClip "${filePath}" frame ${firstFrameIndex} (texture ${textureIndex}) uses an unsupported raster format; only PNG and JPEG are supported.`);
25438
+ const imageInfo = probeRasterImage(raw);
25439
+ if (!imageInfo || imageInfo.format !== detectedFormat) throw couldNotDecode(filePath, firstFrameIndex, textureIndex);
25440
+ let buffer = raw;
25441
+ if (encoder) {
25442
+ try {
25443
+ buffer = await encoder(raw).png().toBuffer();
25444
+ } catch {
25445
+ throw couldNotDecode(filePath, firstFrameIndex, textureIndex);
25446
+ }
25447
+ const normalizedInfo = probeRasterImage(buffer);
25448
+ if (!normalizedInfo || normalizedInfo.format !== "png" || normalizedInfo.width !== imageInfo.width || normalizedInfo.height !== imageInfo.height) throw couldNotDecode(filePath, firstFrameIndex, textureIndex);
25449
+ }
25450
+ referencedTextures.push({
25451
+ textureIndex,
25452
+ firstFrameIndex,
25453
+ buffer,
25454
+ width: imageInfo.width,
25455
+ height: imageInfo.height
25264
25456
  });
25265
25457
  }
25266
25458
  return {
25267
- interval: Math.trunc(1e3 / fps * (speedRaw || 1)),
25268
- repeatDelay: Math.trunc(1e3 / fps * repeatDelayRaw),
25269
- swing: swingRaw === 1,
25270
- width,
25271
- height,
25272
- frames: frames.length === 0 && frameCount > 0 ? [] : frames
25459
+ ...extracted,
25460
+ referencedTextures
25273
25461
  };
25274
25462
  }
25275
- function readUtfBE(data, state, end) {
25276
- const length = readUint16BEAt(data, state, end);
25277
- if (length == null || state.offset + length > end) return null;
25278
- const value = new TextDecoder().decode(data.subarray(state.offset, state.offset + length));
25279
- state.offset += length;
25280
- return value;
25281
- }
25282
- function readUint8At(data, state, end) {
25283
- if (state.offset + 1 > end) return null;
25284
- const value = data[state.offset];
25285
- state.offset += 1;
25286
- return value ?? 0;
25287
- }
25288
- function readInt8At(data, state, end) {
25289
- if (state.offset + 1 > end) return null;
25290
- const value = new DataView(data.buffer, data.byteOffset, data.byteLength).getInt8(state.offset);
25291
- state.offset += 1;
25292
- return value;
25293
- }
25294
- function readUint16BEAt(data, state, end) {
25295
- if (state.offset + 2 > end) return null;
25296
- const value = readUint16BE(data, state.offset);
25297
- state.offset += 2;
25298
- return value;
25299
- }
25300
- function readInt16BEAt(data, state, end) {
25301
- if (state.offset + 2 > end) return null;
25302
- const value = new DataView(data.buffer, data.byteOffset, data.byteLength).getInt16(state.offset, false);
25303
- state.offset += 2;
25304
- return value;
25305
- }
25306
- function readInt32BEAt(data, state, end) {
25307
- if (state.offset + 4 > end) return null;
25308
- const value = new DataView(data.buffer, data.byteOffset, data.byteLength).getInt32(state.offset, false);
25309
- state.offset += 4;
25310
- return value;
25311
- }
25312
- function readUint16BE(data, offset) {
25313
- if (offset + 1 >= data.length) return 0;
25314
- return data[offset] << 8 | data[offset + 1];
25315
- }
25316
- function readUint32BE(data, offset) {
25317
- if (offset + 3 >= data.length) return 0;
25318
- return data[offset] * 16777216 + ((data[offset + 1] ?? 0) << 16) + ((data[offset + 2] ?? 0) << 8) + (data[offset + 3] ?? 0);
25319
- }
25320
25463
  //#endregion
25321
25464
  //#region ../functions/src/atlas/inputs.ts
25322
25465
  function getPublishedItemId(resource) {
@@ -25386,6 +25529,27 @@ async function _trimImage(encoder, input, originalWidth, originalHeight) {
25386
25529
  };
25387
25530
  }
25388
25531
  }
25532
+ function resolveMovieClipSourcePath(resource, pkg, basePath) {
25533
+ const fileName = `${resource.getName()}.jta`;
25534
+ const resourcePath = resource.getPath() ?? "/";
25535
+ return `${basePath}/${pkg.getName()}${resourcePath}${fileName}`;
25536
+ }
25537
+ async function prepareMovieClipResource(resource, pkg, encoder, basePath, readFileRaw) {
25538
+ const filePath = resolveMovieClipSourcePath(resource, pkg, basePath);
25539
+ let raw;
25540
+ try {
25541
+ raw = await readFileRaw(filePath);
25542
+ } catch {
25543
+ throw new Error(`atlas: Could not read MovieClip "${filePath}".`);
25544
+ }
25545
+ try {
25546
+ return await prepareJtaForPublish(raw, encoder, filePath);
25547
+ } catch (error) {
25548
+ if (error instanceof Error && error.message.startsWith("atlas:")) throw error;
25549
+ const detail = error instanceof Error ? ` ${error.message}` : "";
25550
+ throw new Error(`atlas: Could not parse MovieClip "${filePath}".${detail}`);
25551
+ }
25552
+ }
25389
25553
  /** Collect a single ImageResource into the inputs array. */
25390
25554
  async function collectImage(resource, pkg, inputs, encoder, options, doTrim, logger) {
25391
25555
  let origW = resource.getWidth() ?? 0;
@@ -25459,82 +25623,45 @@ async function collectMovieClipFrames(doc, resource, pkg, inputs, encoder, optio
25459
25623
  }
25460
25624
  if (!encoder && options.strictOutput) throw new Error(`atlas: MovieClip "${resource.getId()}" requires an encoder for complete raster output.`);
25461
25625
  const mcId = resource.getId();
25462
- const mcName = resource.getName() + ".jta";
25463
- const mcPath = resource.getPath() ?? "/";
25464
- const filePath = `${options.basePath}/${pkg.getName()}${mcPath}${mcName}`;
25626
+ const filePath = resolveMovieClipSourcePath(resource, pkg, options.basePath);
25465
25627
  try {
25466
- const jta = extractJtaFrames(await options.readFileRaw(filePath));
25467
- if (jta.frames.length === 0) return;
25468
- const frameMetas = jta.meta?.frames ?? [];
25628
+ const jta = options.preparedMovieClips?.get(resource) ?? await prepareMovieClipResource(resource, pkg, encoder, options.basePath, options.readFileRaw);
25469
25629
  for (const frame of resource.listFrames()) resource.removeFrame(frame);
25470
- resource.setInterval(jta.meta?.interval ?? 100).setSwing(jta.meta?.swing ?? false).setRepeatDelay(jta.meta?.repeatDelay ?? 0);
25471
- if (frameMetas.length > 0) {
25472
- const firstFrameIndexByTextureIndex = /* @__PURE__ */ new Map();
25473
- for (let frameIndex = 0; frameIndex < frameMetas.length; frameIndex += 1) {
25474
- const meta = frameMetas[frameIndex];
25475
- const textureIndex = Number.isFinite(meta.textureIndex) ? meta.textureIndex : frameIndex;
25476
- if (!firstFrameIndexByTextureIndex.has(textureIndex)) firstFrameIndexByTextureIndex.set(textureIndex, frameIndex);
25477
- }
25478
- const spriteIdByTextureIndex = /* @__PURE__ */ new Map();
25479
- for (let textureIndex = 0; textureIndex < jta.frames.length; textureIndex += 1) {
25480
- const exportFrameIndex = firstFrameIndexByTextureIndex.get(textureIndex);
25481
- if (exportFrameIndex === void 0) continue;
25482
- const itemId = `${mcId}_${exportFrameIndex}`;
25483
- const input = await createMovieClipFrameInput(jta.frames[textureIndex], itemId, resource, encoder, options.strictOutput);
25484
- if (!input) continue;
25485
- inputs.push(input);
25486
- spriteIdByTextureIndex.set(textureIndex, itemId);
25487
- }
25488
- for (let frameIndex = 0; frameIndex < frameMetas.length; frameIndex += 1) {
25489
- const meta = frameMetas[frameIndex];
25490
- const textureIndex = Number.isFinite(meta.textureIndex) ? meta.textureIndex : frameIndex;
25491
- const frame = doc.createMovieFrame(`${mcId}_${frameIndex}`);
25492
- frame.setRectX(meta.offsetX).setRectY(meta.offsetY).setRectWidth(meta.width).setRectHeight(meta.height).setAddDelay(meta.addDelay).setSpriteId(spriteIdByTextureIndex.get(textureIndex) ?? "");
25493
- resource.addFrame(frame);
25494
- }
25495
- } else for (let frameIndex = 0; frameIndex < jta.frames.length; frameIndex += 1) {
25496
- const itemId = `${mcId}_${frameIndex}`;
25497
- const input = await createMovieClipFrameInput(jta.frames[frameIndex], itemId, resource, encoder, options.strictOutput);
25498
- if (!input) continue;
25499
- inputs.push(input);
25500
- const frame = doc.createMovieFrame(itemId);
25501
- frame.setRectX(0).setRectY(0).setRectWidth(input.originalWidth).setRectHeight(input.originalHeight).setAddDelay(0).setSpriteId(itemId);
25630
+ resource.setInterval(jta.meta.interval).setSwing(jta.meta.swing).setRepeatDelay(jta.meta.repeatDelay);
25631
+ const spriteIdByTextureIndex = /* @__PURE__ */ new Map();
25632
+ for (const texture of jta.referencedTextures) {
25633
+ if (texture.width <= 0 || texture.height <= 0) continue;
25634
+ const itemId = `${mcId}_${texture.firstFrameIndex}`;
25635
+ inputs.push({
25636
+ id: itemId,
25637
+ width: texture.width,
25638
+ height: texture.height,
25639
+ originalWidth: texture.width,
25640
+ originalHeight: texture.height,
25641
+ offsetX: 0,
25642
+ offsetY: 0,
25643
+ resource,
25644
+ trimBuffer: texture.buffer,
25645
+ sourceKind: "movieclip-frame"
25646
+ });
25647
+ spriteIdByTextureIndex.set(texture.textureIndex, itemId);
25648
+ }
25649
+ for (let frameIndex = 0; frameIndex < jta.meta.frames.length; frameIndex += 1) {
25650
+ const meta = jta.meta.frames[frameIndex];
25651
+ const frame = doc.createMovieFrame(`${mcId}_${frameIndex}`);
25652
+ frame.setRectX(meta.offsetX).setRectY(meta.offsetY).setRectWidth(meta.width).setRectHeight(meta.height).setAddDelay(meta.addDelay).setSpriteId(meta.textureIndex === -1 ? "" : spriteIdByTextureIndex.get(meta.textureIndex) ?? "");
25502
25653
  resource.addFrame(frame);
25503
25654
  }
25504
- if ((jta.meta?.width ?? 0) > 0 && (jta.meta?.height ?? 0) > 0) {
25505
- resource.setWidth(jta.meta?.width ?? 0);
25506
- resource.setHeight(jta.meta?.height ?? 0);
25655
+ if (jta.meta.width > 0 && jta.meta.height > 0) {
25656
+ resource.setWidth(jta.meta.width);
25657
+ resource.setHeight(jta.meta.height);
25507
25658
  }
25508
- } catch {
25509
- const message = `atlas: Could not parse MovieClip "${filePath}".`;
25510
- if (options.strictOutput) throw new Error(message);
25659
+ } catch (error) {
25660
+ const message = error instanceof Error ? error.message : `atlas: Could not parse MovieClip "${filePath}".`;
25661
+ if (options.strictOutput) throw error;
25511
25662
  logger.warn(`${message} Skipping frames.`);
25512
25663
  }
25513
25664
  }
25514
- async function createMovieClipFrameInput(buffer, itemId, resource, encoder, strictOutput) {
25515
- if (!encoder || buffer.length === 0) return null;
25516
- try {
25517
- const meta = await encoder(buffer).metadata();
25518
- const width = meta.width ?? 0;
25519
- const height = meta.height ?? 0;
25520
- if (width <= 0 || height <= 0) return null;
25521
- return {
25522
- id: itemId,
25523
- width,
25524
- height,
25525
- originalWidth: width,
25526
- originalHeight: height,
25527
- offsetX: 0,
25528
- offsetY: 0,
25529
- resource,
25530
- trimBuffer: buffer,
25531
- sourceKind: "movieclip-frame"
25532
- };
25533
- } catch {
25534
- if (strictOutput) throw new Error(`atlas: Could not decode MovieClip frame "${itemId}".`);
25535
- return null;
25536
- }
25537
- }
25538
25665
  /** Collect a Bitmap Font's texture image, packed under the font's ID. */
25539
25666
  async function collectFontTexture(doc, fontRes, pkg, options) {
25540
25667
  const textureId = fontRes.getTextureId?.() ?? "";
@@ -27958,6 +28085,14 @@ var RestoreWorkflow = class {
27958
28085
  common: {},
27959
28086
  adaptation: {}
27960
28087
  });
28088
+ for (const pkg of doc.getRoot().listPackages()) pkg.setSourceAtlasSettings({
28089
+ ...pkg.getSourceAtlasSettings(),
28090
+ atlases: pkg.listAtlases().map((atlas) => ({
28091
+ index: atlas.getIndex(),
28092
+ name: atlas.getIndex() === 0 ? "Default" : atlas.getName(),
28093
+ compression: false
28094
+ }))
28095
+ });
27961
28096
  }
27962
28097
  _initializeImageFileNames(doc) {
27963
28098
  for (const pkg of doc.getRoot().listPackages()) for (const resource of pkg.listResources()) {
@@ -28611,7 +28746,7 @@ function publish(options) {
28611
28746
  return paths.join("/");
28612
28747
  }
28613
28748
  });
28614
- const publishPackage = async (plan, writerFs, packageIndex) => {
28749
+ const publishPackage = async (plan, writerFs, packageIndex, preparedMovieClips) => {
28615
28750
  if (options.fs && !plan.outputDir) throw new Error("publish: no output directory resolved. Provide --output, or configure global publish.path / package publishPath.");
28616
28751
  if (options.fs) {
28617
28752
  await options.fs.mkdir(plan.outputDir);
@@ -28629,6 +28764,7 @@ function publish(options) {
28629
28764
  mkdir: options.fs ? options.fs.mkdir : void 0,
28630
28765
  readFileRaw: options.atlas?.readFileRaw ?? options.fs?.readFileRaw,
28631
28766
  strictOutput: options.fs !== void 0,
28767
+ preparedMovieClips,
28632
28768
  packages: [plan.pkg.getName()],
28633
28769
  ...atlasRuntimeOptions
28634
28770
  })(doc);
@@ -28681,8 +28817,25 @@ function publish(options) {
28681
28817
  }
28682
28818
  const unresolvedPlan = plans.find((plan) => !plan.outputDir);
28683
28819
  if (unresolvedPlan) throw new Error(`publish: no output directory resolved for package "${unresolvedPlan.pkg.getName()}". Provide --output, or configure global publish.path / package publishPath.`);
28820
+ const publishedMovieClips = allPackages.flatMap((pkg) => {
28821
+ const publishedResourceIds = getAnnotatedPublishedResourceIds(pkg);
28822
+ return pkg.listResources().filter((resource) => {
28823
+ return publishedResourceIds.has(resource.getId()) && isMovieClipResource(resource);
28824
+ }).map((resource) => ({
28825
+ pkg,
28826
+ resource
28827
+ }));
28828
+ });
28829
+ const preparedMovieClips = /* @__PURE__ */ new Map();
28830
+ if (publishedMovieClips.length > 0) {
28831
+ if (!options.encoder) throw new Error("publish: MovieClip output requires an encoder.");
28832
+ if (!options.basePath) throw new Error("publish: MovieClip output requires basePath.");
28833
+ const readFileRaw = options.atlas?.readFileRaw ?? options.fs.readFileRaw;
28834
+ if (!readFileRaw) throw new Error("publish: MovieClip output requires readFileRaw.");
28835
+ for (const { pkg, resource } of publishedMovieClips) preparedMovieClips.set(resource, await prepareMovieClipResource(resource, pkg, options.encoder, options.basePath, readFileRaw));
28836
+ }
28684
28837
  const writerFs = toBinaryWriterFileSystem(options.fs);
28685
- for (const plan of plans) await publishPackage(plan, writerFs, allDocPackages.indexOf(plan.pkg));
28838
+ for (const plan of plans) await publishPackage(plan, writerFs, allDocPackages.indexOf(plan.pkg), preparedMovieClips);
28686
28839
  if (options.codeGeneration !== false) await publishCodeGeneration(doc, {
28687
28840
  basePath: options.basePath,
28688
28841
  fs: options.fs,