@openfairygui/functions 0.2.0-alpha.11 → 0.2.0-alpha.13

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.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
+ import { a as decodeText, c as resolvePackageCodegenPlan, d as atlas, f as createTransform, i as buildCodegenClasses, n as resolvePublishOptions, o as encodeText, r as AUTO_GENERATED_CODE_MARK, s as publishCodeGeneration, t as publish } from "./publish-Bl-GK9kt.js";
1
2
  import { applyUamTransactionApp } from "./uam-transaction.js";
2
- import { BinaryReader, BinaryWriter, GearType, ProjectType, ProjectWriter, TransitionActionType, generateId } from "@openfairygui/core";
3
- import { createJiti } from "jiti";
3
+ import { BinaryReader, ProjectType, ProjectWriter, generateId } from "@openfairygui/core";
4
4
  //#region src/inspect.ts
5
5
  function mapResource(resource) {
6
6
  return {
@@ -103,49 +103,6 @@ function inspect(doc) {
103
103
  };
104
104
  }
105
105
  //#endregion
106
- //#region src/utils.ts
107
- /**
108
- * Wraps a transform function, assigning it a name for the transform stack.
109
- */
110
- function createTransform(name, fn) {
111
- Object.defineProperty(fn, "name", { value: name });
112
- return fn;
113
- }
114
- function parseTextureSetMode(value) {
115
- const raw = value?.trim() ?? "";
116
- if (!raw) return {
117
- kind: "auto",
118
- raw: ""
119
- };
120
- if (raw === "alone") return {
121
- kind: "standalone",
122
- raw,
123
- sizeMode: "default"
124
- };
125
- if (raw === "alone_npot") return {
126
- kind: "standalone",
127
- raw,
128
- sizeMode: "npot"
129
- };
130
- if (raw === "alone_mof") return {
131
- kind: "standalone",
132
- raw,
133
- sizeMode: "multipleOf4"
134
- };
135
- if (/^\d+$/.test(raw)) {
136
- const pageIndex = Number(raw);
137
- if (pageIndex >= 0 && pageIndex <= 10) return {
138
- kind: "page",
139
- raw,
140
- pageIndex
141
- };
142
- }
143
- return {
144
- kind: "auto",
145
- raw
146
- };
147
- }
148
- //#endregion
149
106
  //#region src/validate.ts
150
107
  /**
151
108
  * Severity of a validation issue.
@@ -339,2477 +296,28 @@ function prune(_options = {}) {
339
296
  * newName: 'PrimaryButton',
340
297
  * }));
341
298
  * ```
342
- */
343
- function rename(options) {
344
- const updateReferences = options.updateReferences ?? true;
345
- return createTransform("rename", (doc) => {
346
- const root = doc.getRoot();
347
- const logger = doc.getLogger();
348
- const pkg = root.listPackages().find((p) => p.getName() === options.packageName);
349
- if (!pkg) {
350
- logger.warn(`rename: Package "${options.packageName}" not found.`);
351
- return;
352
- }
353
- const resource = pkg.listResources().find((r) => r.getName() === options.resourceName) || pkg.listComponents().find((c) => c.getName() === options.resourceName);
354
- if (!resource) {
355
- logger.warn(`rename: Resource "${options.resourceName}" not found in package "${options.packageName}".`);
356
- return;
357
- }
358
- const oldName = resource.getName();
359
- resource.setName(options.newName);
360
- logger.info(`rename: Renamed "${oldName}" → "${options.newName}" in package "${options.packageName}".`);
361
- if (updateReferences) logger.info(`rename: References use resource IDs — no src updates needed.`);
362
- });
363
- }
364
- //#endregion
365
- //#region src/max-rects-compat.ts
366
- const NO_ROTATION = 2;
367
- const MAX_SCORE = 2147483647;
368
- const MAX_RECTS_METHOD = {
369
- BestShortSideFit: 0,
370
- BestLongSideFit: 1,
371
- BestAreaFit: 2,
372
- BottomLeftRule: 3,
373
- ContactPointRule: 4
374
- };
375
- const COMPAT_NODE_RECT_FLAGS = {
376
- DUPLICATE_PADDING: 1,
377
- NO_ROTATION
378
- };
379
- var MaxRectsCompat = class MaxRectsCompat {
380
- static helperRect = createNodeRect();
381
- binWidth = 0;
382
- binHeight = 0;
383
- allowRotations = false;
384
- usedRectangles = [];
385
- freeRectangles = [];
386
- init(width, height, allowRotations = false) {
387
- this.binWidth = width;
388
- this.binHeight = height;
389
- this.allowRotations = allowRotations;
390
- this.usedRectangles.length = 0;
391
- this.freeRectangles.length = 0;
392
- this.freeRectangles.push({
393
- ...createNodeRect(),
394
- x: 0,
395
- y: 0,
396
- width,
397
- height
398
- });
399
- }
400
- insert(rect, method) {
401
- const newNode = this.scoreRect(rect, method);
402
- if (newNode.height === 0) return null;
403
- const placed = cloneNodeRect(newNode);
404
- this.placeRect(placed);
405
- return placed;
406
- }
407
- pack(rects, method) {
408
- const remaining = rects.map(cloneNodeRect);
409
- while (remaining.length > 0) {
410
- let bestIndex = -1;
411
- const bestNode = createNodeRect();
412
- bestNode.score1 = MAX_SCORE;
413
- bestNode.score2 = MAX_SCORE;
414
- for (let index = 0; index < remaining.length; index += 1) {
415
- const candidate = this.scoreRect(remaining[index], method);
416
- if (candidate.score1 < bestNode.score1 || candidate.score1 === bestNode.score1 && candidate.score2 < bestNode.score2) {
417
- copyNodeRect(bestNode, candidate);
418
- bestIndex = index;
419
- }
420
- }
421
- if (bestIndex === -1) break;
422
- this.placeRect(bestNode);
423
- remaining.splice(bestIndex, 1);
424
- }
425
- const result = this.getResult();
426
- result.remainingRects = remaining;
427
- return result;
428
- }
429
- getResult() {
430
- let width = 0;
431
- let height = 0;
432
- for (const rect of this.usedRectangles) {
433
- width = Math.max(width, rect.x + rect.width);
434
- height = Math.max(height, rect.y + rect.height);
435
- }
436
- return {
437
- outputRects: this.usedRectangles.map(cloneNodeRect),
438
- remainingRects: [],
439
- occupancy: this.getOccupancy(),
440
- width,
441
- height
442
- };
443
- }
444
- getOccupancy() {
445
- let usedSurface = 0;
446
- for (const rect of this.usedRectangles) usedSurface += rect.width * rect.height;
447
- return usedSurface / (this.binWidth * this.binHeight);
448
- }
449
- placeRect(rect) {
450
- for (let index = 0; index < this.freeRectangles.length; index += 1) if (this.splitFreeNode(this.freeRectangles[index], rect)) {
451
- this.freeRectangles.splice(index, 1);
452
- index -= 1;
453
- }
454
- this.pruneFreeList();
455
- this.usedRectangles.push(rect);
456
- }
457
- scoreRect(rect, method) {
458
- const helper = MaxRectsCompat.helperRect;
459
- helper.height = 0;
460
- let newNode;
461
- switch (method) {
462
- case MAX_RECTS_METHOD.BestShortSideFit:
463
- newNode = this.findPositionForNewNodeBestShortSideFit(rect.width, rect.height, allowRotation(rect));
464
- break;
465
- case MAX_RECTS_METHOD.BestLongSideFit:
466
- newNode = this.findPositionForNewNodeBestLongSideFit(rect.width, rect.height, allowRotation(rect));
467
- break;
468
- case MAX_RECTS_METHOD.BestAreaFit:
469
- newNode = this.findPositionForNewNodeBestAreaFit(rect.width, rect.height, allowRotation(rect));
470
- break;
471
- case MAX_RECTS_METHOD.BottomLeftRule:
472
- newNode = this.findPositionForNewNodeBottomLeft(rect.width, rect.height, allowRotation(rect));
473
- break;
474
- case MAX_RECTS_METHOD.ContactPointRule:
475
- newNode = this.findPositionForNewNodeContactPoint(rect.width, rect.height, allowRotation(rect));
476
- newNode.score1 = -newNode.score1;
477
- break;
478
- default:
479
- newNode = helper;
480
- break;
481
- }
482
- if (newNode.height === 0) {
483
- newNode.score1 = MAX_SCORE;
484
- newNode.score2 = MAX_SCORE;
485
- }
486
- newNode.index = rect.index;
487
- newNode.subIndex = rect.subIndex;
488
- newNode.flags = rect.flags;
489
- newNode.sourceKind = rect.sourceKind;
490
- return cloneNodeRect(newNode);
491
- }
492
- findPositionForNewNodeBottomLeft(width, height, allowRectRotation) {
493
- const bestNode = MaxRectsCompat.helperRect;
494
- bestNode.score1 = MAX_SCORE;
495
- bestNode.score2 = 0;
496
- for (const freeRect of this.freeRectangles) {
497
- if (freeRect.width >= width && freeRect.height >= height) {
498
- const topSideY = freeRect.y + height;
499
- if (topSideY < bestNode.score1 || topSideY === bestNode.score1 && freeRect.x < bestNode.score2) setNodeRect(bestNode, freeRect.x, freeRect.y, width, height, false, topSideY, freeRect.x);
500
- }
501
- if (this.allowRotations && allowRectRotation && freeRect.width >= height && freeRect.height >= width) {
502
- const topSideY = freeRect.y + width;
503
- if (topSideY < bestNode.score1 || topSideY === bestNode.score1 && freeRect.x < bestNode.score2) setNodeRect(bestNode, freeRect.x, freeRect.y, height, width, true, topSideY, freeRect.x);
504
- }
505
- }
506
- return bestNode;
507
- }
508
- findPositionForNewNodeBestShortSideFit(width, height, allowRectRotation) {
509
- const bestNode = MaxRectsCompat.helperRect;
510
- bestNode.score1 = MAX_SCORE;
511
- bestNode.score2 = 0;
512
- for (const freeRect of this.freeRectangles) {
513
- if (freeRect.width >= width && freeRect.height >= height) {
514
- const leftoverHoriz = Math.abs(freeRect.width - width);
515
- const leftoverVert = Math.abs(freeRect.height - height);
516
- const shortSideFit = Math.min(leftoverHoriz, leftoverVert);
517
- const longSideFit = Math.max(leftoverHoriz, leftoverVert);
518
- if (shortSideFit < bestNode.score1 || shortSideFit === bestNode.score1 && longSideFit < bestNode.score2) setNodeRect(bestNode, freeRect.x, freeRect.y, width, height, false, shortSideFit, longSideFit);
519
- }
520
- if (this.allowRotations && allowRectRotation && freeRect.width >= height && freeRect.height >= width) {
521
- const leftoverHoriz = Math.abs(freeRect.width - height);
522
- const leftoverVert = Math.abs(freeRect.height - width);
523
- const shortSideFit = Math.min(leftoverHoriz, leftoverVert);
524
- const longSideFit = Math.max(leftoverHoriz, leftoverVert);
525
- if (shortSideFit < bestNode.score1 || shortSideFit === bestNode.score1 && longSideFit < bestNode.score2) setNodeRect(bestNode, freeRect.x, freeRect.y, height, width, true, shortSideFit, longSideFit);
526
- }
527
- }
528
- return bestNode;
529
- }
530
- findPositionForNewNodeBestLongSideFit(width, height, allowRectRotation) {
531
- const bestNode = MaxRectsCompat.helperRect;
532
- bestNode.score1 = 0;
533
- bestNode.score2 = MAX_SCORE;
534
- for (const freeRect of this.freeRectangles) {
535
- if (freeRect.width >= width && freeRect.height >= height) {
536
- const leftoverHoriz = Math.abs(freeRect.width - width);
537
- const leftoverVert = Math.abs(freeRect.height - height);
538
- const shortSideFit = Math.min(leftoverHoriz, leftoverVert);
539
- const longSideFit = Math.max(leftoverHoriz, leftoverVert);
540
- if (longSideFit < bestNode.score2 || longSideFit === bestNode.score2 && shortSideFit < bestNode.score1) setNodeRect(bestNode, freeRect.x, freeRect.y, width, height, false, shortSideFit, longSideFit);
541
- }
542
- if (this.allowRotations && allowRectRotation && freeRect.width >= height && freeRect.height >= width) {
543
- const leftoverHoriz = Math.abs(freeRect.width - height);
544
- const leftoverVert = Math.abs(freeRect.height - width);
545
- const shortSideFit = Math.min(leftoverHoriz, leftoverVert);
546
- const longSideFit = Math.max(leftoverHoriz, leftoverVert);
547
- if (longSideFit < bestNode.score2 || longSideFit === bestNode.score2 && shortSideFit < bestNode.score1) setNodeRect(bestNode, freeRect.x, freeRect.y, height, width, true, shortSideFit, longSideFit);
548
- }
549
- }
550
- return bestNode;
551
- }
552
- findPositionForNewNodeBestAreaFit(width, height, allowRectRotation) {
553
- const bestNode = MaxRectsCompat.helperRect;
554
- bestNode.score1 = MAX_SCORE;
555
- bestNode.score2 = 0;
556
- for (const freeRect of this.freeRectangles) {
557
- const areaFit = freeRect.width * freeRect.height - width * height;
558
- if (freeRect.width >= width && freeRect.height >= height) {
559
- const leftoverHoriz = Math.abs(freeRect.width - width);
560
- const leftoverVert = Math.abs(freeRect.height - height);
561
- const shortSideFit = Math.min(leftoverHoriz, leftoverVert);
562
- if (areaFit < bestNode.score1 || areaFit === bestNode.score1 && shortSideFit < bestNode.score2) setNodeRect(bestNode, freeRect.x, freeRect.y, width, height, false, areaFit, shortSideFit);
563
- }
564
- if (this.allowRotations && allowRectRotation && freeRect.width >= height && freeRect.height >= width) {
565
- const leftoverHoriz = Math.abs(freeRect.width - height);
566
- const leftoverVert = Math.abs(freeRect.height - width);
567
- const shortSideFit = Math.min(leftoverHoriz, leftoverVert);
568
- if (areaFit < bestNode.score1 || areaFit === bestNode.score1 && shortSideFit < bestNode.score2) setNodeRect(bestNode, freeRect.x, freeRect.y, height, width, true, areaFit, shortSideFit);
569
- }
570
- }
571
- return bestNode;
572
- }
573
- findPositionForNewNodeContactPoint(width, height, allowRectRotation) {
574
- const bestNode = MaxRectsCompat.helperRect;
575
- bestNode.score1 = -1;
576
- bestNode.score2 = 0;
577
- for (const freeRect of this.freeRectangles) {
578
- if (freeRect.width >= width && freeRect.height >= height) {
579
- const score = this.contactPointScoreNode(freeRect.x, freeRect.y, width, height);
580
- if (score > bestNode.score1) setNodeRect(bestNode, freeRect.x, freeRect.y, width, height, false, score, bestNode.score2);
581
- }
582
- if (this.allowRotations && allowRectRotation && freeRect.width >= height && freeRect.height >= width) {
583
- const score = this.contactPointScoreNode(freeRect.x, freeRect.y, height, width);
584
- if (score > bestNode.score1) setNodeRect(bestNode, freeRect.x, freeRect.y, height, width, true, score, bestNode.score2);
585
- }
586
- }
587
- return bestNode;
588
- }
589
- contactPointScoreNode(x, y, width, height) {
590
- let score = 0;
591
- if (x === 0 || x + width === this.binWidth) score += height;
592
- if (y === 0 || y + height === this.binHeight) score += width;
593
- for (const rect of this.usedRectangles) {
594
- if (rect.x === x + width || rect.x + rect.width === x) score += commonIntervalLength(rect.y, rect.y + rect.height, y, y + height);
595
- if (rect.y === y + height || rect.y + rect.height === y) score += commonIntervalLength(rect.x, rect.x + rect.width, x, x + width);
596
- }
597
- return score;
598
- }
599
- splitFreeNode(freeNode, usedNode) {
600
- 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;
601
- if (usedNode.x < freeNode.x + freeNode.width && usedNode.x + usedNode.width > freeNode.x) {
602
- if (usedNode.y > freeNode.y && usedNode.y < freeNode.y + freeNode.height) {
603
- const newNode = cloneNodeRect(freeNode);
604
- newNode.height = usedNode.y - newNode.y;
605
- this.freeRectangles.push(newNode);
606
- }
607
- if (usedNode.y + usedNode.height < freeNode.y + freeNode.height) {
608
- const newNode = cloneNodeRect(freeNode);
609
- newNode.y = usedNode.y + usedNode.height;
610
- newNode.height = freeNode.y + freeNode.height - (usedNode.y + usedNode.height);
611
- this.freeRectangles.push(newNode);
612
- }
613
- }
614
- if (usedNode.y < freeNode.y + freeNode.height && usedNode.y + usedNode.height > freeNode.y) {
615
- if (usedNode.x > freeNode.x && usedNode.x < freeNode.x + freeNode.width) {
616
- const newNode = cloneNodeRect(freeNode);
617
- newNode.width = usedNode.x - newNode.x;
618
- this.freeRectangles.push(newNode);
619
- }
620
- if (usedNode.x + usedNode.width < freeNode.x + freeNode.width) {
621
- const newNode = cloneNodeRect(freeNode);
622
- newNode.x = usedNode.x + usedNode.width;
623
- newNode.width = freeNode.x + freeNode.width - (usedNode.x + usedNode.width);
624
- this.freeRectangles.push(newNode);
625
- }
626
- }
627
- return true;
628
- }
629
- pruneFreeList() {
630
- let length = this.freeRectangles.length;
631
- let left = 0;
632
- while (left < length) {
633
- let right = left + 1;
634
- while (right < length) {
635
- if (isContainedIn(this.freeRectangles[left], this.freeRectangles[right])) {
636
- this.freeRectangles.splice(left, 1);
637
- length -= 1;
638
- break;
639
- }
640
- if (isContainedIn(this.freeRectangles[right], this.freeRectangles[left])) {
641
- this.freeRectangles.splice(right, 1);
642
- length -= 1;
643
- }
644
- right += 1;
645
- }
646
- left += 1;
647
- }
648
- }
649
- };
650
- function createNodeRect() {
651
- return {
652
- x: 0,
653
- y: 0,
654
- width: 0,
655
- height: 0,
656
- rotated: false,
657
- index: 0,
658
- subIndex: -1,
659
- flags: 0,
660
- score1: 0,
661
- score2: 0,
662
- sourceKind: void 0
663
- };
664
- }
665
- function cloneNodeRect(rect) {
666
- return { ...rect };
667
- }
668
- function copyNodeRect(target, source) {
669
- target.x = source.x;
670
- target.y = source.y;
671
- target.width = source.width;
672
- target.height = source.height;
673
- target.rotated = source.rotated;
674
- target.index = source.index;
675
- target.subIndex = source.subIndex;
676
- target.flags = source.flags;
677
- target.score1 = source.score1;
678
- target.score2 = source.score2;
679
- target.sourceKind = source.sourceKind;
680
- }
681
- function setNodeRect(target, x, y, width, height, rotated, score1, score2) {
682
- target.x = x;
683
- target.y = y;
684
- target.width = width;
685
- target.height = height;
686
- target.rotated = rotated;
687
- target.score1 = score1;
688
- target.score2 = score2;
689
- }
690
- function allowRotation(rect) {
691
- return (rect.flags & NO_ROTATION) === 0;
692
- }
693
- function commonIntervalLength(startA, endA, startB, endB) {
694
- if (endA < startB || endB < startA) return 0;
695
- return Math.min(endA, endB) - Math.max(startA, startB);
696
- }
697
- function isContainedIn(left, right) {
698
- 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;
699
- }
700
- //#endregion
701
- //#region src/max-rects-packer-compat.ts
702
- const DEFAULT_SETTINGS = {
703
- pot: true,
704
- mof: true,
705
- padding: 2,
706
- rotation: false,
707
- minWidth: 16,
708
- minHeight: 16,
709
- maxWidth: 2048,
710
- maxHeight: 2048,
711
- square: false,
712
- fast: true,
713
- edgePadding: false,
714
- duplicatePadding: false,
715
- multiPage: false,
716
- preserveInputOrderOnTie: false
717
- };
718
- let sizeScheme = null;
719
- var BinarySearchCompat = class {
720
- min;
721
- max;
722
- fuzziness;
723
- low;
724
- high;
725
- current;
726
- constructor(min, max, fuzziness, pot, mof) {
727
- this.pot = pot;
728
- this.mof = mof;
729
- this.fuzziness = pot ? 0 : fuzziness;
730
- if (pot) {
731
- this.min = Math.log(MaxRectsPackerCompat.getNextPowerOfTwo(min)) / Math.log(2);
732
- this.max = Math.log(MaxRectsPackerCompat.getNextPowerOfTwo(max)) / Math.log(2);
733
- } else if (mof) {
734
- this.min = min / 4;
735
- this.max = max / 4;
736
- } else {
737
- this.min = min;
738
- this.max = max;
739
- }
740
- this.low = this.min;
741
- this.high = this.max;
742
- this.current = this.min;
743
- }
744
- reset() {
745
- this.low = this.min;
746
- this.high = this.max;
747
- this.current = this.low + this.high >>> 1;
748
- return this.getCurrent();
749
- }
750
- next(failed) {
751
- if (this.low >= this.high) return -1;
752
- if (failed) this.low = this.current + 1;
753
- else this.high = this.current - 1;
754
- this.current = this.low + this.high >>> 1;
755
- if (Math.abs(this.low - this.high) < this.fuzziness) return -1;
756
- return this.getCurrent();
757
- }
758
- getCurrent() {
759
- if (this.pot) return Math.trunc(2 ** this.current);
760
- if (this.mof) return this.current * 4;
761
- return this.current;
762
- }
763
- };
764
- var MaxRectsPackerCompat = class MaxRectsPackerCompat {
765
- maxRects = new MaxRectsCompat();
766
- settings;
767
- constructor(settings = {}) {
768
- this.settings = {
769
- ...DEFAULT_SETTINGS,
770
- ...settings
771
- };
772
- }
773
- static getNextPowerOfTwo(value) {
774
- if (Number.isInteger(value) && value > 0 && (value & value - 1) === 0) return value;
775
- let result = 1;
776
- const target = value - 1e-9;
777
- while (result < target) result <<= 1;
778
- return result;
779
- }
780
- pack(inputRects) {
781
- const rects = inputRects.map(cloneCompatRect);
782
- if (this.settings.fast) vectorSortCompat(rects, this.settings.preserveInputOrderOnTie ? this.settings.rotation ? compareNodeRectStable : compareNodeRect2Stable : this.settings.rotation ? compareNodeRect : compareNodeRect2);
783
- const padding = this.settings.padding;
784
- let hasDuplicatePadding = false;
785
- for (const rect of rects) {
786
- if (duplicatePadding(rect)) hasDuplicatePadding = true;
787
- if (this.settings.maxWidth - rect.width > padding || duplicatePadding(rect)) rect.width += padding;
788
- if (this.settings.maxHeight - rect.height > padding || duplicatePadding(rect)) rect.height += padding;
789
- }
790
- const pages = [];
791
- let remaining = rects;
792
- while (remaining.length > 0) {
793
- const page = this.packPage(remaining);
794
- if (!page) return null;
795
- if (this.settings.pot) {
796
- page.width = MaxRectsPackerCompat.getNextPowerOfTwo(page.width);
797
- page.height = MaxRectsPackerCompat.getNextPowerOfTwo(page.height);
798
- } else if (this.settings.mof) {
799
- page.width = Math.ceil(page.width / 4) * 4;
800
- page.height = Math.ceil(page.height / 4) * 4;
801
- }
802
- if (this.settings.square) {
803
- const side = Math.max(page.width, page.height);
804
- page.width = side;
805
- page.height = side;
806
- }
807
- pages.push(page);
808
- remaining = page.remainingRects.map(cloneCompatRect);
809
- }
810
- pages.sort(comparePage);
811
- for (const page of pages) {
812
- for (const rect of page.outputRects) {
813
- shrinkRectForPadding(rect, padding, this.settings.maxWidth, this.settings.maxHeight);
814
- if (hasDuplicatePadding) {
815
- if (rect.width !== page.width) rect.x += Math.floor(padding / 2);
816
- if (rect.height !== page.height) rect.y += Math.floor(padding / 2);
817
- }
818
- }
819
- for (const rect of page.remainingRects) shrinkRectForPadding(rect, padding, this.settings.maxWidth, this.settings.maxHeight);
820
- }
821
- return pages;
822
- }
823
- packPage(rects) {
824
- if (!sizeScheme) sizeScheme = initSizeScheme();
825
- const edgePadding = this.settings.edgePadding ? this.settings.padding : 0;
826
- let totalArea = 0;
827
- for (const rect of rects) totalArea += rect.width * rect.height;
828
- const candidates = sizeScheme.filter((entry) => entry.area >= totalArea && entry.width <= this.settings.maxWidth && entry.height <= this.settings.maxHeight);
829
- if (candidates.length === 0) candidates.push({
830
- width: this.settings.maxWidth,
831
- height: this.settings.maxHeight,
832
- area: 0,
833
- aspectRatio: 0,
834
- len: 0
835
- });
836
- let page = null;
837
- let selectedWidth = 0;
838
- let selectedHeight = 0;
839
- for (let index = 0; index < candidates.length; index += 1) {
840
- selectedWidth = candidates[index].width;
841
- selectedHeight = candidates[index].height;
842
- page = this.packAtSize(index !== candidates.length - 1, selectedWidth - edgePadding, selectedHeight - edgePadding, rects);
843
- if (page) break;
844
- }
845
- if (page && !this.settings.pot && page.remainingRects.length === 0) {
846
- let bestRefined = null;
847
- if (this.settings.square) {
848
- 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);
849
- let current = search.reset();
850
- while (current !== -1) {
851
- const refined = this.packAtSize(true, current - edgePadding, current - edgePadding, rects);
852
- bestRefined = getBestPage(bestRefined, refined);
853
- current = search.next(refined == null);
854
- }
855
- } else {
856
- const widthSearch = new BinarySearchCompat(selectedWidth / 2, selectedWidth, this.settings.fast ? 25 : 15, this.settings.pot, this.settings.mof);
857
- const heightSearch = new BinarySearchCompat(selectedHeight / 2, selectedHeight, this.settings.fast ? 25 : 15, this.settings.pot, this.settings.mof);
858
- let currentHeight = heightSearch.reset();
859
- let currentWidth = widthSearch.reset();
860
- while (true) {
861
- let bestForHeight = null;
862
- while (currentWidth !== -1) {
863
- const refined = this.packAtSize(true, currentWidth - edgePadding, currentHeight - edgePadding, rects);
864
- bestForHeight = getBestPage(bestForHeight, refined);
865
- currentWidth = widthSearch.next(refined == null);
866
- }
867
- bestRefined = getBestPage(bestRefined, bestForHeight);
868
- currentHeight = heightSearch.next(bestForHeight == null);
869
- if (currentHeight === -1) break;
870
- currentWidth = widthSearch.reset();
871
- }
872
- }
873
- if (bestRefined) page = bestRefined;
874
- }
875
- return page;
876
- }
877
- packAtSize(requireFullFit, width, height, rects) {
878
- const methods = [
879
- MAX_RECTS_METHOD.BestShortSideFit,
880
- MAX_RECTS_METHOD.BestLongSideFit,
881
- MAX_RECTS_METHOD.BestAreaFit
882
- ];
883
- let best = null;
884
- for (const method of methods) {
885
- this.maxRects.init(width, height, this.settings.rotation);
886
- let page;
887
- if (!this.settings.fast) page = this.maxRects.pack(rects, method);
888
- else {
889
- const remaining = [];
890
- let index = 0;
891
- while (index < rects.length) {
892
- if (this.maxRects.insert(rects[index], method) == null) {
893
- while (index < rects.length) {
894
- remaining.push(cloneCompatRect(rects[index]));
895
- index += 1;
896
- }
897
- break;
898
- }
899
- index += 1;
900
- }
901
- page = this.maxRects.getResult();
902
- page.remainingRects = remaining;
903
- }
904
- if (!(requireFullFit && page.remainingRects.length > 0) && page.outputRects.length !== 0) best = getBestPage(best, page);
905
- }
906
- return best;
907
- }
908
- };
909
- function vectorSortCompat(items, compare) {
910
- if (items.length <= 1) return;
911
- avmQuickSortCompat(items, 0, items.length - 1, compare);
912
- }
913
- function avmQuickSortCompat(items, initialLo, initialHi, compare) {
914
- if (initialLo >= initialHi) return;
915
- const stack = [];
916
- let lo = initialLo;
917
- let hi = initialHi;
918
- while (true) {
919
- const size = hi - lo + 1;
920
- if (size < 4) {
921
- if (size === 3) {
922
- if (compare(items[lo], items[lo + 1]) > 0) {
923
- swapCompat(items, lo, lo + 1);
924
- if (compare(items[lo + 1], items[lo + 2]) > 0) {
925
- swapCompat(items, lo + 1, lo + 2);
926
- if (compare(items[lo], items[lo + 1]) > 0) swapCompat(items, lo, lo + 1);
927
- }
928
- } else if (compare(items[lo + 1], items[lo + 2]) > 0) {
929
- swapCompat(items, lo + 1, lo + 2);
930
- if (compare(items[lo], items[lo + 1]) > 0) swapCompat(items, lo, lo + 1);
931
- }
932
- } else if (size === 2 && compare(items[lo], items[lo + 1]) > 0) swapCompat(items, lo, lo + 1);
933
- } else {
934
- swapCompat(items, lo + (size >> 1), lo);
935
- let left = lo;
936
- let right = hi + 1;
937
- while (true) {
938
- do
939
- left += 1;
940
- while (left <= hi && compare(items[left], items[lo]) <= 0);
941
- do
942
- right -= 1;
943
- while (right > lo && compare(items[right], items[lo]) >= 0);
944
- if (right < left) break;
945
- swapCompat(items, left, right);
946
- }
947
- swapCompat(items, lo, right);
948
- if (right - 1 - lo >= hi - left) {
949
- if (lo + 1 < right) stack.push({
950
- lo,
951
- hi: right - 1
952
- });
953
- if (left < hi) {
954
- lo = left;
955
- continue;
956
- }
957
- } else {
958
- if (left < hi) stack.push({
959
- lo: left,
960
- hi
961
- });
962
- if (lo + 1 < right) {
963
- hi = right - 1;
964
- continue;
965
- }
966
- }
967
- }
968
- if (stack.length === 0) return;
969
- const frame = stack.pop();
970
- lo = frame.lo;
971
- hi = frame.hi;
972
- }
973
- }
974
- function swapCompat(items, left, right) {
975
- const value = items[left];
976
- items[left] = items[right];
977
- items[right] = value;
978
- }
979
- function initSizeScheme() {
980
- const result = [];
981
- for (let w = 5; w <= 13; w += 1) for (let h = 5; h <= 13; h += 1) {
982
- const width = 2 ** w;
983
- const height = 2 ** h;
984
- const area = width * height;
985
- const aspectRatio = width > height ? width / height : height / width;
986
- result.push({
987
- width,
988
- height,
989
- area,
990
- aspectRatio,
991
- len: Math.max(width, height)
992
- });
993
- }
994
- result.sort(compareSizeScheme);
995
- return result;
996
- }
997
- function compareSizeScheme(left, right) {
998
- if (left.len < right.len) return -1;
999
- if (left.len > right.len) return 1;
1000
- if (left.area < right.area) return -1;
1001
- if (left.area > right.area) return 1;
1002
- if (left.aspectRatio < right.aspectRatio) return -1;
1003
- if (left.aspectRatio > right.aspectRatio) return 1;
1004
- if (left.width > left.height) return -1;
1005
- if (right.width > right.height) return 1;
1006
- return 0;
1007
- }
1008
- function getBestPage(left, right) {
1009
- if (!left) return right;
1010
- if (!right) return left;
1011
- return left.occupancy > right.occupancy ? left : right;
1012
- }
1013
- function comparePage(left, right) {
1014
- return right.outputRects.length - left.outputRects.length;
1015
- }
1016
- function compareNodeRect(left, right) {
1017
- const leftEdge = left.width > left.height ? left.width : left.height;
1018
- return (right.width > right.height ? right.width : right.height) - leftEdge;
1019
- }
1020
- function compareNodeRectStable(left, right) {
1021
- const delta = compareNodeRect(left, right);
1022
- if (delta !== 0) return delta;
1023
- if (left.sourceKind === "movieclip-frame" && right.sourceKind === "movieclip-frame") {
1024
- const areaDelta = right.width * right.height - left.width * left.height;
1025
- if (areaDelta !== 0) return areaDelta;
1026
- const widthDelta = right.width - left.width;
1027
- if (widthDelta !== 0) return widthDelta;
1028
- }
1029
- return left.index - right.index;
1030
- }
1031
- function compareNodeRect2(left, right) {
1032
- return right.width - left.width;
1033
- }
1034
- function compareNodeRect2Stable(left, right) {
1035
- const delta = compareNodeRect2(left, right);
1036
- if (delta !== 0) return delta;
1037
- if (left.sourceKind === "movieclip-frame" && right.sourceKind === "movieclip-frame") {
1038
- const areaDelta = right.width * right.height - left.width * left.height;
1039
- if (areaDelta !== 0) return areaDelta;
1040
- const heightDelta = right.height - left.height;
1041
- if (heightDelta !== 0) return heightDelta;
1042
- }
1043
- return left.index - right.index;
1044
- }
1045
- function duplicatePadding(rect) {
1046
- return (rect.flags & COMPAT_NODE_RECT_FLAGS.DUPLICATE_PADDING) !== 0;
1047
- }
1048
- function shrinkRectForPadding(rect, padding, maxWidth, maxHeight) {
1049
- if (!rect.rotated) {
1050
- if (maxWidth - rect.width > padding || duplicatePadding(rect)) rect.width -= padding;
1051
- if (maxHeight - rect.height > padding || duplicatePadding(rect)) rect.height -= padding;
1052
- } else {
1053
- if (maxHeight - rect.width > padding || duplicatePadding(rect)) rect.width -= padding;
1054
- if (maxWidth - rect.height > padding || duplicatePadding(rect)) rect.height -= padding;
1055
- }
1056
- }
1057
- function cloneCompatRect(rect) {
1058
- return { ...rect };
1059
- }
1060
- //#endregion
1061
- //#region src/atlas.ts
1062
- const ATLAS_DEFAULTS = {
1063
- maxSize: 2048,
1064
- fast: true,
1065
- allowRotation: true,
1066
- padding: 1,
1067
- powerOfTwo: false,
1068
- square: false,
1069
- multiPage: true,
1070
- trimImage: false,
1071
- preserveInputOrderOnTie: false,
1072
- directSingleImageOutput: false,
1073
- extractAlpha: false,
1074
- separatedAtlasForBranch: false
1075
- };
1076
- function getPublishedItemId(resource) {
1077
- return (resource.getExtras() ?? {})._publishedId ?? resource.getId();
1078
- }
1079
- function getSelectedSkeletonDependencyImageIds(resources) {
1080
- const imageIds = /* @__PURE__ */ new Set();
1081
- const resourcesById = new Map(resources.map((resource) => [resource.getId(), resource]));
1082
- for (const resource of resources) {
1083
- if (!isSkeletonResource$1(resource)) continue;
1084
- for (const requiredId of resource.getRequireIds()) {
1085
- if (!requiredId) continue;
1086
- const required = resourcesById.get(requiredId);
1087
- if (required && isImageResource$1(required)) imageIds.add(requiredId);
1088
- }
1089
- }
1090
- return imageIds;
1091
- }
1092
- function resolveFontFileName(fontName) {
1093
- return /\.fnt$/i.test(fontName) ? fontName : `${fontName}.fnt`;
1094
- }
1095
- async function resolveEditorCompatibleResourceOrder(pkg, allResources, options) {
1096
- const pkgId = pkg.getId();
1097
- const resourceMap = new Map(allResources.map((resource) => [resource.getId(), resource]));
1098
- const ordered = [];
1099
- const added = /* @__PURE__ */ new Set();
1100
- const componentStack = [];
1101
- async function addResource(resource) {
1102
- if (!resource) return;
1103
- const resourceId = resource.getId();
1104
- if (!resourceId || added.has(resourceId)) return;
1105
- added.add(resourceId);
1106
- ordered.push(resource);
1107
- if (isFontResource$1(resource)) {
1108
- await addResource(resourceMap.get(resource.getTextureId?.() ?? ""));
1109
- if (options.readFileRaw && options.basePath) {
1110
- const fontName = resolveFontFileName(resource.getName());
1111
- const fontPath = resource.getPath() ?? "/";
1112
- const fntFile = `${options.basePath}/${pkg.getName()}${fontPath}${fontName}`;
1113
- try {
1114
- const fntData = await options.readFileRaw(fntFile);
1115
- const fntText = new TextDecoder().decode(fntData);
1116
- for (const line of fntText.split(/\r?\n/)) {
1117
- const imgMatch = line.match(/\bimg=(\w+)/);
1118
- if (imgMatch) await addResource(resourceMap.get(imgMatch[1] ?? ""));
1119
- }
1120
- } catch {}
1121
- }
1122
- }
1123
- if (isComponentResource$1(resource)) componentStack.push(resource);
1124
- }
1125
- async function addResourceByLocalUiUrl(value) {
1126
- if (!value || typeof value !== "string" || !value.startsWith("ui://")) return;
1127
- const normalized = value.slice(5).split(",")[0] ?? "";
1128
- if (!normalized) return;
1129
- let resourceId = "";
1130
- const slashIndex = normalized.indexOf("/");
1131
- if (slashIndex >= 0) {
1132
- if (normalized.slice(0, slashIndex) !== pkgId) return;
1133
- resourceId = normalized.slice(slashIndex + 1);
1134
- } else if (normalized.length > 8) {
1135
- if (normalized.slice(0, 8) !== pkgId) return;
1136
- resourceId = normalized.slice(8);
1137
- }
1138
- if (!resourceId) return;
1139
- await addResource(resourceMap.get(resourceId));
1140
- }
1141
- async function addGearIconResources(gear) {
1142
- if (gear.getGearType?.() !== GearType.Icon) return;
1143
- const values = gear.getValues?.();
1144
- if (typeof values === "string" && values) for (const value of values.split("|")) await addResourceByLocalUiUrl(value.trim());
1145
- const defaultValue = gear.getDefaultValue?.();
1146
- if (typeof defaultValue === "string") await addResourceByLocalUiUrl(defaultValue);
1147
- }
1148
- for (const resource of allResources) if (resource.getExported()) await addResource(resource);
1149
- while (componentStack.length > 0) {
1150
- const component = componentStack.pop();
1151
- if (!component) continue;
1152
- for (const child of component.listChildren()) {
1153
- const refChild = child;
1154
- await addResource(resourceMap.get(refChild.getSrc?.() ?? ""));
1155
- for (const ref of [
1156
- refChild.getUrl?.(),
1157
- refChild.getDefaultItem?.(),
1158
- refChild.getIcon?.(),
1159
- refChild.getSelectedIcon?.(),
1160
- refChild.getFont?.(),
1161
- refChild.getDropdown?.(),
1162
- refChild.getVtScrollBarRes?.(),
1163
- refChild.getHzScrollBarRes?.(),
1164
- refChild.getHeaderRes?.(),
1165
- refChild.getFooterRes?.(),
1166
- refChild.getSound?.(),
1167
- refChild.getInstanceIcon?.(),
1168
- refChild.getInstanceSelectedIcon?.()
1169
- ]) await addResourceByLocalUiUrl(ref);
1170
- for (const item of refChild.getInstanceComboItems?.() ?? []) await addResourceByLocalUiUrl(item.icon ?? void 0);
1171
- for (const item of refChild.getListItems?.() ?? []) {
1172
- await addResourceByLocalUiUrl(item.icon ?? void 0);
1173
- await addResourceByLocalUiUrl(item.url ?? void 0);
1174
- }
1175
- for (const gear of refChild.listGears?.() ?? []) await addGearIconResources(gear);
1176
- }
1177
- for (const ref of [
1178
- component.getDropdown?.(),
1179
- component.getVtScrollBarRes?.(),
1180
- component.getHzScrollBarRes?.(),
1181
- component.getHeaderRes?.(),
1182
- component.getFooterRes?.(),
1183
- component.getSound?.()
1184
- ]) await addResourceByLocalUiUrl(ref);
1185
- for (const transition of component.listTransitions?.() ?? []) for (const item of transition.listItems?.() ?? []) {
1186
- const actionType = item.getActionType?.();
1187
- if (actionType !== TransitionActionType.Sound && actionType !== TransitionActionType.Icon) continue;
1188
- for (const value of [item.getStartValue?.(), item.getEndValue?.()]) if (Array.isArray(value)) {
1189
- for (const entry of value) if (typeof entry === "string") await addResourceByLocalUiUrl(entry);
1190
- } else if (typeof value === "string") await addResourceByLocalUiUrl(value);
1191
- }
1192
- }
1193
- for (const resource of allResources) await addResource(resource);
1194
- return ordered;
1195
- }
1196
- /**
1197
- * Packs image resources into texture atlases.
1198
- *
1199
- * This transform performs MaxRects bin-packing on all ImageResource items
1200
- * within each package, creating Atlas and Sprite property nodes. When an
1201
- * `encoder` (sharp) is provided, it also composites the actual PNG files.
1202
- *
1203
- * When `trimImage` is enabled and encoder is available, transparent pixels
1204
- * at image edges are trimmed before packing. The trimmed offset and original
1205
- * dimensions are stored in the Sprite nodes for runtime reconstruction.
1206
- *
1207
- * ```ts
1208
- * import sharp from 'sharp';
1209
- * await doc.transform(atlas({
1210
- * encoder: sharp,
1211
- * maxSize: 2048,
1212
- * trimImage: true,
1213
- * basePath: './assets/',
1214
- * outputPath: './dist/',
1215
- * }));
1216
- * ```
1217
- */
1218
- function atlas(_options = {}) {
1219
- const options = {
1220
- ...ATLAS_DEFAULTS,
1221
- ..._options
1222
- };
1223
- return createTransform("atlas", async (doc) => {
1224
- const root = doc.getRoot();
1225
- const logger = doc.getLogger();
1226
- const encoder = options.encoder;
1227
- const doTrim = options.trimImage && !!encoder && !!options.basePath;
1228
- const packageFilter = options.packages ? new Set(options.packages) : null;
1229
- for (const pkg of root.listPackages()) {
1230
- if (packageFilter && !packageFilter.has(pkg.getName())) continue;
1231
- const selectedPublishIds = new Set((pkg.getExtras() ?? {}).publishedResourceIds ?? []);
1232
- const allResources = selectedPublishIds.size > 0 ? pkg.listResources().filter((resource) => selectedPublishIds.has(resource.getId())) : pkg.listResources();
1233
- const skeletonDependencyImageIds = getSelectedSkeletonDependencyImageIds(allResources);
1234
- const orderedResources = await resolveEditorCompatibleResourceOrder(pkg, allResources, options);
1235
- const orderedAllResources = sortResourcesByOrder(allResources, new Map(orderedResources.map((resource, index) => [resource.getId(), index])), new Map(allResources.map((resource, index) => [resource.getId(), index])));
1236
- if (!allResources.some((resource) => {
1237
- if (isImageResource$1(resource) && skeletonDependencyImageIds.has(resource.getId())) return false;
1238
- return isPackableResource(resource);
1239
- })) continue;
1240
- const inputs = [];
1241
- const referencedIds = /* @__PURE__ */ new Set();
1242
- const resourceMap = /* @__PURE__ */ new Map();
1243
- for (const res of allResources) {
1244
- const id = res.getId();
1245
- if (id) resourceMap.set(id, res);
1246
- }
1247
- function collectRefs(component, visited) {
1248
- for (const child of component.listChildren()) {
1249
- const refChild = child;
1250
- const src = refChild.getSrc?.();
1251
- if (src && !visited.has(src)) {
1252
- referencedIds.add(src);
1253
- visited.add(src);
1254
- const srcRes = resourceMap.get(src);
1255
- if (srcRes && isComponentResource$1(srcRes)) collectRefs(srcRes, visited);
1256
- }
1257
- for (const ref of [
1258
- refChild.getIcon?.(),
1259
- refChild.getSelectedIcon?.(),
1260
- refChild.getFont?.(),
1261
- refChild.getDropdown?.(),
1262
- refChild.getInstanceIcon?.(),
1263
- refChild.getInstanceSelectedIcon?.(),
1264
- refChild.getVtScrollBarRes?.(),
1265
- refChild.getHzScrollBarRes?.(),
1266
- refChild.getHeaderRes?.(),
1267
- refChild.getFooterRes?.(),
1268
- refChild.getUrl?.()
1269
- ]) addUiResourceRef(referencedIds, ref);
1270
- addUiResourceRefsFromText(referencedIds, refChild.getText?.());
1271
- for (const item of refChild.getInstanceComboItems?.() ?? []) addUiResourceRef(referencedIds, item.icon ?? void 0);
1272
- for (const item of refChild.getListItems?.() ?? []) addUiResourceRef(referencedIds, item.icon ?? void 0);
1273
- for (const gear of refChild.listGears?.() ?? []) {
1274
- addUiResourceRefsFromUnknown(referencedIds, gear.getValues?.());
1275
- addUiResourceRefsFromUnknown(referencedIds, gear.getDefaultValue?.());
1276
- }
1277
- }
1278
- for (const transition of component.listTransitions?.() ?? []) for (const item of transition.listItems?.() ?? []) {
1279
- addUiResourceRefsFromUnknown(referencedIds, item.getStartValue?.());
1280
- addUiResourceRefsFromUnknown(referencedIds, item.getEndValue?.());
1281
- }
1282
- }
1283
- for (const res of orderedAllResources) {
1284
- if (isComponentResource$1(res)) collectRefs(res, /* @__PURE__ */ new Set());
1285
- if (isSkeletonResource$1(res) && referencedIds.has(res.getId())) {
1286
- for (const requiredId of res.getRequireIds()) if (requiredId) referencedIds.add(requiredId);
1287
- }
1288
- if (isFontResource$1(res)) {
1289
- const textureId = res.getTextureId?.() ?? "";
1290
- if (textureId) referencedIds.add(textureId);
1291
- if (options.readFileRaw && options.basePath) {
1292
- const fontName = resolveFontFileName(res.getName());
1293
- const fontPath = res.getPath() ?? "/";
1294
- const fntFile = `${options.basePath}/${pkg.getName()}${fontPath}${fontName}`;
1295
- try {
1296
- const fntData = await options.readFileRaw(fntFile);
1297
- const fntText = new TextDecoder().decode(fntData);
1298
- for (const line of fntText.split(/\r?\n/)) {
1299
- const match = line.match(/img=(\w+)/);
1300
- if (match) referencedIds.add(match[1]);
1301
- }
1302
- } catch {}
1303
- }
1304
- }
1305
- }
1306
- for (const res of orderedAllResources) if (isImageResource$1(res)) {
1307
- const resId = res.getId();
1308
- if (skeletonDependencyImageIds.has(resId)) continue;
1309
- if (selectedPublishIds.size === 0 && !res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
1310
- await _collectImage(res, pkg, inputs, encoder, options, doTrim, logger);
1311
- } else if (isMovieClipResource$1(res)) {
1312
- const resId = res.getId();
1313
- if (selectedPublishIds.size === 0 && !res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
1314
- await _collectMovieClipFrames(doc, res, pkg, inputs, encoder, options, logger);
1315
- } else if (isFontResource$1(res)) {
1316
- const resId = res.getId();
1317
- if (selectedPublishIds.size === 0 && !res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
1318
- await _collectFontTexture(doc, res, pkg, options);
1319
- }
1320
- if (inputs.length === 0) continue;
1321
- let totalPageCount = 0;
1322
- let usedDirectOutput = false;
1323
- const { autoInputs, fixedPageGroups, standaloneGroups, reservedPageIndexes } = groupStandaloneInputs(doc, inputs, options);
1324
- const branchGroups = buildBranchAtlasGroups(doc, autoInputs, options);
1325
- const branchPageOffsets = /* @__PURE__ */ new Map();
1326
- for (const group of branchGroups) {
1327
- const directOutput = fixedPageGroups.length === 0 && standaloneGroups.length === 0 ? resolveDirectImageOutput(group.inputs, options) : null;
1328
- if (directOutput) {
1329
- await emitDirectImageOutput(doc, pkg, directOutput, encoder, options, logger, group.branchName, group.branchOrdinal);
1330
- usedDirectOutput = true;
1331
- totalPageCount += 1;
1332
- continue;
1333
- }
1334
- const pageStart = reserveAutoPageStart(branchPageOffsets, group.branchOrdinal, reservedPageIndexes);
1335
- const emittedPageCount = await emitPagedAtlasGroup(doc, pkg, allResources, group.inputs, {
1336
- branchName: group.branchName,
1337
- branchOrdinal: group.branchOrdinal,
1338
- pageStart,
1339
- fileNameAt: (pageIndex) => resolveAtlasOutputFileName(pkg, pageIndex, group.branchName),
1340
- options,
1341
- encoder,
1342
- logger
1343
- });
1344
- totalPageCount += emittedPageCount;
1345
- branchPageOffsets.set(group.branchOrdinal, pageStart + emittedPageCount);
1346
- }
1347
- for (const group of fixedPageGroups) {
1348
- const emittedPageCount = await emitPagedAtlasGroup(doc, pkg, allResources, group.inputs, {
1349
- branchName: group.branchName,
1350
- branchOrdinal: group.branchOrdinal,
1351
- pageStart: group.pageIndex,
1352
- forceSinglePage: true,
1353
- fileNameAt: () => resolveAtlasOutputFileName(pkg, group.pageIndex, group.branchName),
1354
- options,
1355
- encoder,
1356
- logger
1357
- });
1358
- totalPageCount += emittedPageCount;
1359
- }
1360
- const standalonePageOffsets = new Map(branchPageOffsets);
1361
- for (const group of fixedPageGroups) {
1362
- const nextPageIndex = group.pageIndex + 1;
1363
- if (nextPageIndex > (standalonePageOffsets.get(group.branchOrdinal) ?? 0)) standalonePageOffsets.set(group.branchOrdinal, nextPageIndex);
1364
- }
1365
- for (const group of standaloneGroups) {
1366
- const emittedPageCount = await emitStandaloneAtlasGroup(doc, pkg, group, {
1367
- atlasIndexStart: standalonePageOffsets.get(group.branchOrdinal) ?? 0,
1368
- options,
1369
- encoder,
1370
- logger
1371
- });
1372
- totalPageCount += emittedPageCount;
1373
- standalonePageOffsets.set(group.branchOrdinal, (standalonePageOffsets.get(group.branchOrdinal) ?? 0) + emittedPageCount);
1374
- }
1375
- if (usedDirectOutput) logger.info(`atlas: Direct output for single image package "${pkg.getName()}".`);
1376
- logger.info(`atlas: Packed ${inputs.length} images into ${totalPageCount} atlas(es) for package "${pkg.getName()}".`);
1377
- }
1378
- });
1379
- }
1380
- function buildBranchAtlasGroups(doc, inputs, options) {
1381
- if (!options.separatedAtlasForBranch) return [{
1382
- branchName: "",
1383
- branchOrdinal: 0,
1384
- inputs
1385
- }];
1386
- const discoveredBranchNames = [...new Set(inputs.map((input) => getInputBranchName(input)).filter((branchName) => !!branchName))];
1387
- if (discoveredBranchNames.length === 0) return [{
1388
- branchName: "",
1389
- branchOrdinal: 0,
1390
- inputs
1391
- }];
1392
- const orderedBranchNames = doc.getRoot().listBranches().filter((branchName) => discoveredBranchNames.includes(branchName));
1393
- for (const branchName of discoveredBranchNames) if (!orderedBranchNames.includes(branchName)) orderedBranchNames.push(branchName);
1394
- const groups = /* @__PURE__ */ new Map();
1395
- groups.set("", []);
1396
- for (const branchName of orderedBranchNames) groups.set(branchName, []);
1397
- for (const input of inputs) {
1398
- const branchName = getInputBranchName(input);
1399
- const key = groups.has(branchName) ? branchName : "";
1400
- groups.get(key).push(input);
1401
- }
1402
- const orderedKeys = [""];
1403
- for (const branchName of orderedBranchNames) if ((groups.get(branchName)?.length ?? 0) > 0) orderedKeys.push(branchName);
1404
- return orderedKeys.filter((branchName) => (groups.get(branchName)?.length ?? 0) > 0).map((branchName, index) => ({
1405
- branchName,
1406
- branchOrdinal: index,
1407
- inputs: groups.get(branchName) ?? []
1408
- }));
1409
- }
1410
- function reserveAutoPageStart(branchPageOffsets, branchOrdinal, reservedPageIndexes) {
1411
- let pageIndex = branchPageOffsets.get(branchOrdinal) ?? 0;
1412
- while (branchOrdinal === 0 && reservedPageIndexes.has(pageIndex)) pageIndex += 1;
1413
- return pageIndex;
1414
- }
1415
- async function emitPagedAtlasGroup(doc, pkg, allResources, inputs, context) {
1416
- if (inputs.length === 0) return 0;
1417
- const pages = packAtlasPages(inputs, context.options, context.forceSinglePage === true);
1418
- if (pages.length === 0) return 0;
1419
- for (let pageOffset = 0; pageOffset < pages.length; pageOffset += 1) {
1420
- const page = pages[pageOffset];
1421
- const pageIndex = context.pageStart + pageOffset;
1422
- const atlasNode = doc.createAtlas(`atlas${resolveAtlasIndex(context.branchOrdinal, pageIndex)}`);
1423
- atlasNode.setIndex(resolveAtlasIndex(context.branchOrdinal, pageIndex));
1424
- atlasNode.setFile(context.fileNameAt(pageIndex));
1425
- atlasNode.setWidth(page.width);
1426
- atlasNode.setHeight(page.height);
1427
- pkg.addAtlas(atlasNode);
1428
- attachSpritesToAtlas(doc, allResources, inputs, page.outputRects, atlasNode);
1429
- await writeAtlasPageImage(pkg, inputs, page, atlasNode.getFile(), context.encoder, context.options, context.logger);
1430
- }
1431
- return pages.length;
1432
- }
1433
- async function emitStandaloneAtlasGroup(doc, pkg, group, context) {
1434
- if (group.inputs.length === 0) return 0;
1435
- const pages = packAtlasPages(group.inputs, context.options, true, group.sizeMode === "npot" ? {
1436
- powerOfTwo: false,
1437
- multipleOfFour: false,
1438
- square: false
1439
- } : group.sizeMode === "multipleOf4" ? {
1440
- powerOfTwo: false,
1441
- multipleOfFour: true,
1442
- square: false
1443
- } : void 0);
1444
- if (pages.length === 0) return 0;
1445
- for (let pageOffset = 0; pageOffset < pages.length; pageOffset += 1) {
1446
- const page = pages[pageOffset];
1447
- const baseFileName = resolveStandaloneAtlasOutputFileName(pkg, group.resource, group.branchName);
1448
- const atlasFileName = pages.length <= 1 ? baseFileName : insertFileNameSuffix(baseFileName, `_${pageOffset}`);
1449
- const atlasIndex = context.atlasIndexStart + pageOffset;
1450
- const atlasNode = doc.createAtlas(`atlas${resolveAtlasIndex(group.branchOrdinal, atlasIndex)}`);
1451
- atlasNode.setIndex(resolveAtlasIndex(group.branchOrdinal, atlasIndex));
1452
- atlasNode.setFile(atlasFileName);
1453
- const standaloneSize = resolveStandaloneAtlasSize(page.width, page.height, group.sizeMode, context.options);
1454
- atlasNode.setWidth(standaloneSize.width);
1455
- atlasNode.setHeight(standaloneSize.height);
1456
- pkg.addAtlas(atlasNode);
1457
- attachSpritesToAtlas(doc, [], group.inputs, page.outputRects, atlasNode);
1458
- await writeAtlasPageImage(pkg, group.inputs, {
1459
- ...page,
1460
- width: standaloneSize.width,
1461
- height: standaloneSize.height
1462
- }, atlasFileName, context.encoder, context.options, context.logger);
1463
- }
1464
- return pages.length;
1465
- }
1466
- function packAtlasPages(inputs, options, forceSinglePage, sizeOverrides) {
1467
- const hasDuplicatePadding = inputs.some((input) => {
1468
- return isImageResource$1(input.resource) && input.resource.getDuplicatePadding?.() === true;
1469
- });
1470
- return new MaxRectsPackerCompat({
1471
- pot: sizeOverrides?.powerOfTwo ?? options.powerOfTwo,
1472
- mof: sizeOverrides?.multipleOfFour ?? !options.powerOfTwo,
1473
- padding: options.padding,
1474
- rotation: options.allowRotation,
1475
- minWidth: 16,
1476
- minHeight: 16,
1477
- maxWidth: options.maxSize,
1478
- maxHeight: options.maxSize,
1479
- square: sizeOverrides?.square ?? options.square,
1480
- fast: options.fast,
1481
- edgePadding: false,
1482
- duplicatePadding: hasDuplicatePadding,
1483
- multiPage: forceSinglePage ? false : options.multiPage,
1484
- preserveInputOrderOnTie: options.preserveInputOrderOnTie
1485
- }).pack(inputs.map((input, index) => inputToCompatRect(input, index))) ?? [];
1486
- }
1487
- function attachSpritesToAtlas(doc, allResources, inputs, outputRects, atlasNode) {
1488
- for (const packedRect of outputRects) {
1489
- const input = inputs[packedRect.index];
1490
- if (!input) continue;
1491
- const packedSize = resolvePackedRectSize(input, packedRect.width, packedRect.height, packedRect.rotated);
1492
- const sprite = doc.createSprite();
1493
- sprite.setItemId(input.id);
1494
- sprite.setRectX(packedRect.x);
1495
- sprite.setRectY(packedRect.y);
1496
- sprite.setRectWidth(packedSize.width);
1497
- sprite.setRectHeight(packedSize.height);
1498
- sprite.setRotated(packedRect.rotated);
1499
- sprite.setOffsetX(input.offsetX);
1500
- sprite.setOffsetY(input.offsetY);
1501
- sprite.setOriginalWidth(input.originalWidth);
1502
- sprite.setOriginalHeight(input.originalHeight);
1503
- sprite.setAtlas(atlasNode);
1504
- atlasNode.addSprite(sprite);
1505
- }
1506
- for (const resource of allResources) {
1507
- if (!isFontResource$1(resource)) continue;
1508
- const alias = resource.getExtras()?._fontSpriteAlias;
1509
- if (!alias) continue;
1510
- const imageSprite = outputRects.find((result) => inputs[result.index]?.id === alias.textureId);
1511
- if (!imageSprite) continue;
1512
- const imageInput = inputs[imageSprite.index];
1513
- const fontSprite = doc.createSprite();
1514
- fontSprite.setItemId(alias.fontId);
1515
- fontSprite.setRectX(imageSprite.x);
1516
- fontSprite.setRectY(imageSprite.y);
1517
- fontSprite.setRectWidth(imageSprite.width);
1518
- fontSprite.setRectHeight(imageSprite.height);
1519
- fontSprite.setRotated(imageSprite.rotated);
1520
- if (imageInput) {
1521
- fontSprite.setOffsetX(imageInput.offsetX);
1522
- fontSprite.setOffsetY(imageInput.offsetY);
1523
- fontSprite.setOriginalWidth(imageInput.originalWidth);
1524
- fontSprite.setOriginalHeight(imageInput.originalHeight);
1525
- }
1526
- fontSprite.setAtlas(atlasNode);
1527
- atlasNode.addSprite(fontSprite);
1528
- }
1529
- }
1530
- async function writeAtlasPageImage(pkg, inputs, page, atlasFileName, encoder, options, logger) {
1531
- if (!encoder || !options.outputPath) return;
1532
- if (options.mkdir) await options.mkdir(options.outputPath);
1533
- const compositeInputs = [];
1534
- for (const packedRect of page.outputRects) {
1535
- const input = inputs[packedRect.index];
1536
- if (!input) continue;
1537
- if (packedRect.width <= 0 || packedRect.height <= 0 || input.width <= 0 || input.height <= 0) continue;
1538
- try {
1539
- let imageBuffer;
1540
- if (input.trimBuffer) {
1541
- imageBuffer = input.trimBuffer;
1542
- if (imageBuffer.length === 0) continue;
1543
- } else if (input.rasterizedBuffer) imageBuffer = input.rasterizedBuffer;
1544
- else {
1545
- if (!isImageResource$1(input.resource)) {
1546
- logger.warn(`atlas: Non-image input "${input.id}" is missing inline buffer, skipping compositing.`);
1547
- continue;
1548
- }
1549
- imageBuffer = await encoder(_resolveImagePath(input.resource, pkg, options.basePath)).toBuffer();
1550
- }
1551
- if (packedRect.rotated) imageBuffer = await encoder(imageBuffer).rotate(270).toBuffer();
1552
- compositeInputs.push({
1553
- input: imageBuffer,
1554
- left: packedRect.x,
1555
- top: packedRect.y
1556
- });
1557
- } catch {
1558
- logger.warn(`atlas: Could not read image "${input.id}" for compositing.`);
1559
- }
1560
- }
1561
- const outputFile = `${options.outputPath}/${atlasFileName}`;
1562
- await encoder({ create: {
1563
- width: page.width,
1564
- height: page.height,
1565
- channels: 4,
1566
- background: {
1567
- r: 0,
1568
- g: 0,
1569
- b: 0,
1570
- alpha: 0
1571
- }
1572
- } }).composite(compositeInputs).toFile(outputFile);
1573
- logger.info(`atlas: Generated ${atlasFileName} (${page.width}x${page.height}, ${page.outputRects.length} sprites)`);
1574
- }
1575
- function inputToCompatRect(input, index) {
1576
- const duplicatePadding = isImageResource$1(input.resource) && input.resource.getDuplicatePadding?.() === true;
1577
- return {
1578
- x: 0,
1579
- y: 0,
1580
- width: input.width,
1581
- height: input.height,
1582
- rotated: false,
1583
- index,
1584
- subIndex: -1,
1585
- flags: duplicatePadding ? COMPAT_NODE_RECT_FLAGS.DUPLICATE_PADDING : 0,
1586
- score1: 0,
1587
- score2: 0,
1588
- sourceKind: input.sourceKind
1589
- };
1590
- }
1591
- function resolvePackedRectSize(input, width, height, rectRotated) {
1592
- if (!rectRotated) return {
1593
- width,
1594
- height
1595
- };
1596
- return {
1597
- width: input.height,
1598
- height: input.width
1599
- };
1600
- }
1601
- function resolveDirectImageOutput(inputs, options) {
1602
- if (!options.directSingleImageOutput || options.extractAlpha) return null;
1603
- if (inputs.length !== 1) return null;
1604
- const [input] = inputs;
1605
- if (!input || input.sourceKind !== "image" || !isImageResource$1(input.resource)) return null;
1606
- if (input.resource.getDuplicatePadding?.() === true) return null;
1607
- if (input.width !== input.originalWidth || input.height !== input.originalHeight) return null;
1608
- if (!resolveImageFileName$1(input.resource).toLowerCase().endsWith(".png")) return null;
1609
- return input;
1610
- }
1611
- function resolveDirectOutputAtlasSize(width, height, options) {
1612
- let resolvedWidth = width;
1613
- let resolvedHeight = height;
1614
- if (options.square) {
1615
- const side = Math.max(resolvedWidth, resolvedHeight);
1616
- resolvedWidth = side;
1617
- resolvedHeight = side;
1618
- }
1619
- if (options.powerOfTwo) {
1620
- resolvedWidth = nextPow2(resolvedWidth);
1621
- resolvedHeight = nextPow2(resolvedHeight);
1622
- }
1623
- return {
1624
- width: resolvedWidth,
1625
- height: resolvedHeight
1626
- };
1627
- }
1628
- async function emitDirectImageOutput(doc, pkg, input, encoder, options, logger, branchName = "", branchOrdinal = 0) {
1629
- const atlasFileName = resolveAtlasOutputFileName(pkg, 0, branchName);
1630
- const atlasSize = resolveDirectOutputAtlasSize(input.originalWidth, input.originalHeight, options);
1631
- const atlasNode = doc.createAtlas(`atlas${resolveAtlasIndex(branchOrdinal, 0)}`);
1632
- atlasNode.setIndex(resolveAtlasIndex(branchOrdinal, 0));
1633
- atlasNode.setFile(atlasFileName);
1634
- atlasNode.setWidth(atlasSize.width);
1635
- atlasNode.setHeight(atlasSize.height);
1636
- pkg.addAtlas(atlasNode);
1637
- const sprite = doc.createSprite();
1638
- sprite.setItemId(input.id);
1639
- sprite.setRectX(0);
1640
- sprite.setRectY(0);
1641
- sprite.setRectWidth(input.originalWidth);
1642
- sprite.setRectHeight(input.originalHeight);
1643
- sprite.setRotated(false);
1644
- sprite.setOffsetX(0);
1645
- sprite.setOffsetY(0);
1646
- sprite.setOriginalWidth(input.originalWidth);
1647
- sprite.setOriginalHeight(input.originalHeight);
1648
- sprite.setAtlas(atlasNode);
1649
- atlasNode.addSprite(sprite);
1650
- if (!encoder || !options.outputPath || !isImageResource$1(input.resource) || !options.basePath) return;
1651
- if (options.mkdir) await options.mkdir(options.outputPath);
1652
- const outputFile = `${options.outputPath}/${atlasFileName}`;
1653
- const filePath = _resolveImagePath(input.resource, pkg, options.basePath);
1654
- try {
1655
- if (atlasSize.width === input.originalWidth && atlasSize.height === input.originalHeight) await encoder(filePath).png().toFile(outputFile);
1656
- else {
1657
- const imageBuffer = await encoder(filePath).png().toBuffer();
1658
- await encoder({ create: {
1659
- width: atlasSize.width,
1660
- height: atlasSize.height,
1661
- channels: 4,
1662
- background: {
1663
- r: 0,
1664
- g: 0,
1665
- b: 0,
1666
- alpha: 0
1667
- }
1668
- } }).composite([{
1669
- input: imageBuffer,
1670
- left: 0,
1671
- top: 0
1672
- }]).png().toFile(outputFile);
1673
- }
1674
- } catch {
1675
- logger.warn(`atlas: Could not write direct-output atlas "${atlasFileName}".`);
1676
- }
1677
- }
1678
- function getInputBranchName(input) {
1679
- return input.resource.getBranch?.() ?? "";
1680
- }
1681
- function resolveAtlasIndex(branchOrdinal, pageIndex) {
1682
- if (branchOrdinal <= 0) return pageIndex;
1683
- return branchOrdinal * 100 + pageIndex;
1684
- }
1685
- function resolveAtlasOutputFileName(pkg, pageIndex, branchName) {
1686
- const suffix = branchName ? `_${branchName}` : "";
1687
- return `${pkg.getPublishName() || pkg.getName()}_atlas${pageIndex}${suffix}.png`;
1688
- }
1689
- function resolveStandaloneAtlasOutputFileName(pkg, resource, branchName) {
1690
- const baseName = `${pkg.getPublishName() || pkg.getName()}_atlas_${getPublishedItemId(resource)}`;
1691
- const suffix = branchName ? `_${branchName}` : "";
1692
- if (isImageResource$1(resource)) return `${baseName}${suffix}${extname$1(resolveImageFileName$1(resource)) || ".png"}`;
1693
- return `${baseName}${suffix}.png`;
1694
- }
1695
- function resolveStandaloneAtlasSize(width, height, sizeMode, options) {
1696
- if (sizeMode === "npot") return {
1697
- width,
1698
- height
1699
- };
1700
- if (sizeMode === "multipleOf4") return {
1701
- width: roundUpToMultiple(width, 4),
1702
- height: roundUpToMultiple(height, 4)
1703
- };
1704
- return resolveDirectOutputAtlasSize(width, height, options);
1705
- }
1706
- function resolveImageFileName$1(resource) {
1707
- const extras = resource.getExtras();
1708
- return resource.getFileName() || extras._fileName || resource.getName();
1709
- }
1710
- function extname$1(fileName) {
1711
- const normalized = fileName.replace(/\\/g, "/");
1712
- const lastSlash = normalized.lastIndexOf("/");
1713
- const lastDot = normalized.lastIndexOf(".");
1714
- if (lastDot <= lastSlash) return "";
1715
- return normalized.slice(lastDot);
1716
- }
1717
- function insertFileNameSuffix(fileName, suffix) {
1718
- const extension = extname$1(fileName);
1719
- if (!extension) return `${fileName}${suffix}`;
1720
- return `${fileName.slice(0, -extension.length)}${suffix}${extension}`;
1721
- }
1722
- function nextPow2(value) {
1723
- if (value <= 1) return 1;
1724
- return 2 ** Math.ceil(Math.log2(value));
1725
- }
1726
- function roundUpToMultiple(value, base) {
1727
- if (value <= 0) return 0;
1728
- return Math.ceil(value / base) * base;
1729
- }
1730
- function sortResourcesByOrder(resources, orderMap, inputOrderMap) {
1731
- const ordered = [...resources];
1732
- ordered.sort((left, right) => {
1733
- const leftId = left.getId();
1734
- const rightId = right.getId();
1735
- const leftOrder = leftId && orderMap.has(leftId) ? orderMap.get(leftId) ?? Number.MAX_SAFE_INTEGER : Number.MAX_SAFE_INTEGER;
1736
- const rightOrder = rightId && orderMap.has(rightId) ? orderMap.get(rightId) ?? Number.MAX_SAFE_INTEGER : Number.MAX_SAFE_INTEGER;
1737
- if (leftOrder !== rightOrder) return leftOrder - rightOrder;
1738
- const leftInputOrder = leftId && inputOrderMap.has(leftId) ? inputOrderMap.get(leftId) ?? Number.MAX_SAFE_INTEGER : Number.MAX_SAFE_INTEGER;
1739
- const rightInputOrder = rightId && inputOrderMap.has(rightId) ? inputOrderMap.get(rightId) ?? Number.MAX_SAFE_INTEGER : Number.MAX_SAFE_INTEGER;
1740
- if (leftInputOrder !== rightInputOrder) return leftInputOrder - rightInputOrder;
1741
- return (leftId ?? "").localeCompare(rightId ?? "");
1742
- });
1743
- return ordered;
1744
- }
1745
- function getResourceTextureSetMode(resource) {
1746
- if (isImageResource$1(resource)) return parseTextureSetMode(resource.getTextureSetMode?.());
1747
- return parseTextureSetMode(resource.getTextureSetMode?.());
1748
- }
1749
- function groupStandaloneInputs(doc, inputs, options) {
1750
- const autoInputs = [];
1751
- const fixedInputsByPage = /* @__PURE__ */ new Map();
1752
- const standaloneGroups = /* @__PURE__ */ new Map();
1753
- const reservedPageIndexes = /* @__PURE__ */ new Set();
1754
- const discoveredBranchNames = [...new Set(inputs.map((input) => getInputBranchName(input)).filter((branchName) => !!branchName))];
1755
- const orderedBranchNames = doc.getRoot().listBranches().filter((branchName) => discoveredBranchNames.includes(branchName));
1756
- for (const branchName of discoveredBranchNames) if (!orderedBranchNames.includes(branchName)) orderedBranchNames.push(branchName);
1757
- const branchOrdinalByName = /* @__PURE__ */ new Map();
1758
- branchOrdinalByName.set("", 0);
1759
- if (options.separatedAtlasForBranch) {
1760
- let ordinal = 1;
1761
- for (const branchName of orderedBranchNames) branchOrdinalByName.set(branchName, ordinal++);
1762
- } else for (const branchName of orderedBranchNames) branchOrdinalByName.set(branchName, 0);
1763
- for (const input of inputs) {
1764
- const branchName = getInputBranchName(input);
1765
- const branchOrdinal = branchOrdinalByName.get(branchName) ?? 0;
1766
- const mode = getResourceTextureSetMode(input.resource);
1767
- if (mode.kind === "standalone") {
1768
- const key = `${branchName}\u0000${getPublishedItemId(input.resource)}`;
1769
- const existing = standaloneGroups.get(key);
1770
- if (existing) existing.inputs.push(input);
1771
- else standaloneGroups.set(key, {
1772
- resource: input.resource,
1773
- branchName,
1774
- branchOrdinal,
1775
- sizeMode: mode.sizeMode,
1776
- inputs: [input]
1777
- });
1778
- continue;
1779
- }
1780
- if (mode.kind === "page") {
1781
- reservedPageIndexes.add(mode.pageIndex);
1782
- const key = `${branchName}\u0000${mode.pageIndex}`;
1783
- const existing = fixedInputsByPage.get(key);
1784
- if (existing) existing.inputs.push(input);
1785
- else fixedInputsByPage.set(key, {
1786
- pageIndex: mode.pageIndex,
1787
- branchName,
1788
- branchOrdinal,
1789
- inputs: [input]
1790
- });
1791
- continue;
1792
- }
1793
- autoInputs.push(input);
1794
- }
1795
- return {
1796
- autoInputs,
1797
- fixedPageGroups: [...fixedInputsByPage.values()].sort((left, right) => left.branchOrdinal - right.branchOrdinal || left.pageIndex - right.pageIndex),
1798
- standaloneGroups: [...standaloneGroups.values()].sort((left, right) => left.branchOrdinal - right.branchOrdinal || getPublishedItemId(left.resource).localeCompare(getPublishedItemId(right.resource))),
1799
- reservedPageIndexes
1800
- };
1801
- }
1802
- /**
1803
- * Trim transparent edges from an image using sharp.
1804
- * Returns the trimmed buffer, dimensions, and offsets.
1805
- * Falls back to the original image if trim fails (e.g. no alpha channel, no transparent edges).
1806
- */
1807
- async function _trimImage(encoder, input, originalWidth, originalHeight) {
1808
- try {
1809
- const trimResult = await encoder(input).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
1810
- if (!isResolvedBuffer(trimResult)) throw new Error("atlas: encoder raw alpha trim did not return resolved metadata.");
1811
- const { data, info } = trimResult;
1812
- const width = info.width;
1813
- const height = info.height;
1814
- const channels = info.channels || 4;
1815
- let minX = width;
1816
- let minY = height;
1817
- let maxX = -1;
1818
- let maxY = -1;
1819
- for (let y = 0; y < height; y += 1) for (let x = 0; x < width; x += 1) {
1820
- if ((data[(y * width + x) * channels + 3] ?? 0) === 0) continue;
1821
- if (x < minX) minX = x;
1822
- if (y < minY) minY = y;
1823
- if (x > maxX) maxX = x;
1824
- if (y > maxY) maxY = y;
1825
- }
1826
- if (maxX < minX || maxY < minY) return {
1827
- buffer: new Uint8Array(0),
1828
- width: 0,
1829
- height: 0,
1830
- offsetX: 0,
1831
- offsetY: 0,
1832
- originalWidth,
1833
- originalHeight
1834
- };
1835
- const trimmedWidth = maxX - minX + 1;
1836
- const trimmedHeight = maxY - minY + 1;
1837
- return {
1838
- buffer: await encoder(input).extract({
1839
- left: minX,
1840
- top: minY,
1841
- width: trimmedWidth,
1842
- height: trimmedHeight
1843
- }).toBuffer(),
1844
- width: trimmedWidth,
1845
- height: trimmedHeight,
1846
- offsetX: minX,
1847
- offsetY: minY,
1848
- originalWidth,
1849
- originalHeight
1850
- };
1851
- } catch {
1852
- return {
1853
- buffer: await encoder(input).png().toBuffer(),
1854
- width: originalWidth,
1855
- height: originalHeight,
1856
- offsetX: 0,
1857
- offsetY: 0,
1858
- originalWidth,
1859
- originalHeight
1860
- };
1861
- }
1862
- }
1863
- /**
1864
- * Resolve an ImageResource to its actual file path on disk.
1865
- */
1866
- function _resolveImagePath(resource, pkg, basePath) {
1867
- const imgPath = resource.getPath() ?? "/";
1868
- const fileName = resolveImageFileName$1(resource);
1869
- const branchName = resource.getBranch?.() ?? "";
1870
- const normalizedBasePath = basePath.replace(/[/\\]+$/, "");
1871
- return `${!branchName ? normalizedBasePath : /[\\/]assets$/i.test(normalizedBasePath) ? normalizedBasePath.replace(/([\\/])assets$/i, `$1assets_${branchName}`) : `${normalizedBasePath}_${branchName}`}/${pkg.getName()}${imgPath}${fileName}`;
1872
- }
1873
- /** Collect a single ImageResource into the inputs array. */
1874
- async function _collectImage(resource, pkg, inputs, encoder, options, doTrim, logger) {
1875
- let origW = resource.getWidth() ?? 0;
1876
- let origH = resource.getHeight() ?? 0;
1877
- const declaredWidth = origW;
1878
- const declaredHeight = origH;
1879
- let sourceHasAlpha = false;
1880
- let rasterizedBuffer;
1881
- if (encoder && options.basePath) {
1882
- const filePath = _resolveImagePath(resource, pkg, options.basePath);
1883
- try {
1884
- const metadata = await encoder(filePath).metadata();
1885
- if (origW === 0 || origH === 0) {
1886
- origW = metadata.width ?? 0;
1887
- origH = metadata.height ?? 0;
1888
- resource.setWidth(origW);
1889
- resource.setHeight(origH);
1890
- }
1891
- sourceHasAlpha = metadata.hasAlpha === true || metadata.channels === 4;
1892
- if (/\.svg$/i.test(resolveImageFileName$1(resource)) && declaredWidth > 0 && declaredHeight > 0) {
1893
- rasterizedBuffer = await encoder(filePath).resize(declaredWidth, declaredHeight, { fit: "fill" }).png().toBuffer();
1894
- sourceHasAlpha = true;
1895
- }
1896
- } catch {
1897
- if (origW === 0 || origH === 0) {
1898
- logger.warn(`atlas: Could not read image "${filePath}", skipping.`);
1899
- return;
1900
- }
1901
- }
1902
- }
1903
- if (origW <= 0 || origH <= 0) return;
1904
- let packW = origW, packH = origH, offX = 0, offY = 0;
1905
- let trimBuf;
1906
- if (doTrim && sourceHasAlpha && options.basePath && encoder) {
1907
- const filePath = _resolveImagePath(resource, pkg, options.basePath);
1908
- try {
1909
- const trimResult = await _trimImage(encoder, rasterizedBuffer ?? filePath, origW, origH);
1910
- packW = trimResult.width;
1911
- packH = trimResult.height;
1912
- offX = trimResult.offsetX;
1913
- offY = trimResult.offsetY;
1914
- trimBuf = trimResult.buffer;
1915
- } catch {
1916
- logger.warn(`atlas: Could not trim "${filePath}", using original.`);
1917
- }
1918
- }
1919
- inputs.push({
1920
- id: getPublishedItemId(resource),
1921
- width: packW,
1922
- height: packH,
1923
- originalWidth: origW,
1924
- originalHeight: origH,
1925
- offsetX: offX,
1926
- offsetY: offY,
1927
- resource,
1928
- trimBuffer: trimBuf,
1929
- rasterizedBuffer,
1930
- sourceKind: "image"
1931
- });
1932
- }
1933
- /** Collect MovieClip frame textures from a .jta file into the inputs array. */
1934
- async function _collectMovieClipFrames(doc, resource, pkg, inputs, encoder, options, logger) {
1935
- if (!options.basePath || !options.readFileRaw) return;
1936
- const mcId = resource.getId();
1937
- const mcName = resource.getName() + ".jta";
1938
- const mcPath = resource.getPath() ?? "/";
1939
- const filePath = `${options.basePath}/${pkg.getName()}${mcPath}${mcName}`;
1940
- try {
1941
- const jta = _extractJtaFrames(await options.readFileRaw(filePath));
1942
- if (jta.frames.length === 0) return;
1943
- const frameMetas = jta.meta?.frames ?? [];
1944
- for (const frame of resource.listFrames()) resource.removeFrame(frame);
1945
- resource.setInterval(jta.meta?.interval ?? 100).setSwing(jta.meta?.swing ?? false).setRepeatDelay(jta.meta?.repeatDelay ?? 0);
1946
- if (frameMetas.length > 0) {
1947
- const firstFrameIndexByTextureIndex = /* @__PURE__ */ new Map();
1948
- for (let frameIndex = 0; frameIndex < frameMetas.length; frameIndex += 1) {
1949
- const meta = frameMetas[frameIndex];
1950
- const textureIndex = Number.isFinite(meta.textureIndex) ? meta.textureIndex : frameIndex;
1951
- if (!firstFrameIndexByTextureIndex.has(textureIndex)) firstFrameIndexByTextureIndex.set(textureIndex, frameIndex);
1952
- }
1953
- const spriteIdByTextureIndex = /* @__PURE__ */ new Map();
1954
- for (let textureIndex = 0; textureIndex < jta.frames.length; textureIndex += 1) {
1955
- const exportFrameIndex = firstFrameIndexByTextureIndex.get(textureIndex);
1956
- if (exportFrameIndex === void 0) continue;
1957
- const itemId = `${mcId}_${exportFrameIndex}`;
1958
- const input = await _createMovieClipFrameInput(jta.frames[textureIndex], itemId, resource, encoder);
1959
- if (!input) continue;
1960
- inputs.push(input);
1961
- spriteIdByTextureIndex.set(textureIndex, itemId);
1962
- }
1963
- for (let frameIndex = 0; frameIndex < frameMetas.length; frameIndex += 1) {
1964
- const meta = frameMetas[frameIndex];
1965
- const textureIndex = Number.isFinite(meta.textureIndex) ? meta.textureIndex : frameIndex;
1966
- const frame = doc.createMovieFrame(`${mcId}_${frameIndex}`);
1967
- frame.setRectX(meta.offsetX).setRectY(meta.offsetY).setRectWidth(meta.width).setRectHeight(meta.height).setAddDelay(meta.addDelay).setSpriteId(spriteIdByTextureIndex.get(textureIndex) ?? "");
1968
- resource.addFrame(frame);
1969
- }
1970
- } else for (let frameIndex = 0; frameIndex < jta.frames.length; frameIndex += 1) {
1971
- const itemId = `${mcId}_${frameIndex}`;
1972
- const input = await _createMovieClipFrameInput(jta.frames[frameIndex], itemId, resource, encoder);
1973
- if (!input) continue;
1974
- inputs.push(input);
1975
- const frame = doc.createMovieFrame(itemId);
1976
- frame.setRectX(0).setRectY(0).setRectWidth(input.originalWidth).setRectHeight(input.originalHeight).setAddDelay(0).setSpriteId(itemId);
1977
- resource.addFrame(frame);
1978
- }
1979
- if ((jta.meta?.width ?? 0) > 0 && (jta.meta?.height ?? 0) > 0) {
1980
- resource.setWidth(jta.meta?.width ?? 0);
1981
- resource.setHeight(jta.meta?.height ?? 0);
1982
- }
1983
- } catch {
1984
- logger.warn(`atlas: Could not parse MovieClip "${filePath}", skipping frames.`);
1985
- }
1986
- }
1987
- async function _createMovieClipFrameInput(buffer, itemId, resource, encoder) {
1988
- if (!encoder || buffer.length === 0) return null;
1989
- try {
1990
- const meta = await encoder(buffer).metadata();
1991
- const width = meta.width ?? 0;
1992
- const height = meta.height ?? 0;
1993
- if (width <= 0 || height <= 0) return null;
1994
- return {
1995
- id: itemId,
1996
- width,
1997
- height,
1998
- originalWidth: width,
1999
- originalHeight: height,
2000
- offsetX: 0,
2001
- offsetY: 0,
2002
- resource,
2003
- trimBuffer: buffer,
2004
- sourceKind: "movieclip-frame"
2005
- };
2006
- } catch {
2007
- return null;
2008
- }
2009
- }
2010
- const PNG_SIGNATURE = new Uint8Array([
2011
- 137,
2012
- 80,
2013
- 78,
2014
- 71,
2015
- 13,
2016
- 10,
2017
- 26,
2018
- 10
2019
- ]);
2020
- function _extractJtaFrames(data) {
2021
- const frames = [];
2022
- let offset = 0;
2023
- let firstPngOffset = -1;
2024
- while (offset < data.length) {
2025
- const sigIndex = _findPngSignature(data, offset);
2026
- if (sigIndex === -1) break;
2027
- if (firstPngOffset === -1) firstPngOffset = sigIndex;
2028
- const end = _findPngEnd(data, sigIndex);
2029
- if (end === -1) break;
2030
- frames.push(data.subarray(sigIndex, end));
2031
- offset = end;
2032
- }
2033
- if (firstPngOffset === -1 || frames.length === 0) return { frames: [] };
2034
- return {
2035
- frames,
2036
- meta: _parseJtaHeader(data, firstPngOffset, frames.length)
2037
- };
2038
- }
2039
- function _findPngSignature(data, fromIndex) {
2040
- for (let index = fromIndex; index <= data.length - PNG_SIGNATURE.length; index += 1) {
2041
- let matched = true;
2042
- for (let sigIndex = 0; sigIndex < PNG_SIGNATURE.length; sigIndex += 1) if (data[index + sigIndex] !== PNG_SIGNATURE[sigIndex]) {
2043
- matched = false;
2044
- break;
2045
- }
2046
- if (matched) return index;
2047
- }
2048
- return -1;
2049
- }
2050
- function _findPngEnd(data, start) {
2051
- let pos = start + PNG_SIGNATURE.length;
2052
- while (pos + 8 <= data.length) {
2053
- const length = _readUint32BE(data, pos);
2054
- pos += 8;
2055
- if (pos + length + 4 > data.length) return -1;
2056
- const isIEND = data[pos - 4] === 73 && data[pos - 3] === 69 && data[pos - 2] === 78 && data[pos - 1] === 68;
2057
- pos += length + 4;
2058
- if (isIEND) return pos;
2059
- }
2060
- return -1;
2061
- }
2062
- function _parseJtaHeader(data, firstPngOffset, frameCount) {
2063
- if (data.length < 10) return void 0;
2064
- const state = { offset: 0 };
2065
- const end = Math.min(firstPngOffset, data.length);
2066
- if (!_readUtfBE(data, state, end)) return void 0;
2067
- const version = _readInt32BEAt(data, state, end);
2068
- if (version == null) return void 0;
2069
- const fpsRaw = _readInt8At(data, state, end);
2070
- if (fpsRaw == null) return void 0;
2071
- const fps = fpsRaw > 0 ? fpsRaw : 24;
2072
- if (state.offset + 3 > end) return void 0;
2073
- state.offset += 3;
2074
- if (version < 102) return void 0;
2075
- _readUint16BEAt(data, state, end);
2076
- _readUint16BEAt(data, state, end);
2077
- const width = _readUint16BEAt(data, state, end);
2078
- const height = _readUint16BEAt(data, state, end);
2079
- if (width == null || height == null) return void 0;
2080
- const speedRaw = _readUint8At(data, state, end);
2081
- const repeatDelayRaw = _readUint8At(data, state, end);
2082
- const swingRaw = _readInt8At(data, state, end);
2083
- const frameTableCount = _readInt16BEAt(data, state, end);
2084
- if (speedRaw == null || repeatDelayRaw == null || swingRaw == null || frameTableCount == null) return void 0;
2085
- const frames = [];
2086
- for (let index = 0; index < frameTableCount; index += 1) {
2087
- const delayRaw = _readInt16BEAt(data, state, end);
2088
- const offsetX = _readInt16BEAt(data, state, end);
2089
- const offsetY = _readInt16BEAt(data, state, end);
2090
- const frameWidth = _readInt16BEAt(data, state, end);
2091
- const frameHeight = _readInt16BEAt(data, state, end);
2092
- const textureIndex = _readInt16BEAt(data, state, end);
2093
- if (delayRaw == null || offsetX == null || offsetY == null || frameWidth == null || frameHeight == null || textureIndex == null) break;
2094
- frames.push({
2095
- addDelay: Math.trunc(1e3 / fps * delayRaw),
2096
- offsetX,
2097
- offsetY,
2098
- width: frameWidth,
2099
- height: frameHeight,
2100
- textureIndex
2101
- });
2102
- }
2103
- return {
2104
- interval: Math.trunc(1e3 / fps * (speedRaw || 1)),
2105
- repeatDelay: Math.trunc(1e3 / fps * repeatDelayRaw),
2106
- swing: swingRaw === 1,
2107
- width,
2108
- height,
2109
- frames: frames.length === 0 && frameCount > 0 ? [] : frames
2110
- };
2111
- }
2112
- function _readUtfBE(data, state, end) {
2113
- const length = _readUint16BEAt(data, state, end);
2114
- if (length == null || state.offset + length > end) return null;
2115
- const value = new TextDecoder().decode(data.subarray(state.offset, state.offset + length));
2116
- state.offset += length;
2117
- return value;
2118
- }
2119
- function _readUint8At(data, state, end) {
2120
- if (state.offset + 1 > end) return null;
2121
- const value = data[state.offset];
2122
- state.offset += 1;
2123
- return value ?? 0;
2124
- }
2125
- function _readInt8At(data, state, end) {
2126
- if (state.offset + 1 > end) return null;
2127
- const value = new DataView(data.buffer, data.byteOffset, data.byteLength).getInt8(state.offset);
2128
- state.offset += 1;
2129
- return value;
2130
- }
2131
- function _readUint16BEAt(data, state, end) {
2132
- if (state.offset + 2 > end) return null;
2133
- const value = _readUint16BE(data, state.offset);
2134
- state.offset += 2;
2135
- return value;
2136
- }
2137
- function _readInt16BEAt(data, state, end) {
2138
- if (state.offset + 2 > end) return null;
2139
- const value = new DataView(data.buffer, data.byteOffset, data.byteLength).getInt16(state.offset, false);
2140
- state.offset += 2;
2141
- return value;
2142
- }
2143
- function _readInt32BEAt(data, state, end) {
2144
- if (state.offset + 4 > end) return null;
2145
- const value = new DataView(data.buffer, data.byteOffset, data.byteLength).getInt32(state.offset, false);
2146
- state.offset += 4;
2147
- return value;
2148
- }
2149
- function _readUint16BE(data, offset) {
2150
- if (offset + 1 >= data.length) return 0;
2151
- return data[offset] << 8 | data[offset + 1];
2152
- }
2153
- function _readUint32BE(data, offset) {
2154
- if (offset + 3 >= data.length) return 0;
2155
- return data[offset] * 16777216 + ((data[offset + 1] ?? 0) << 16) + ((data[offset + 2] ?? 0) << 8) + (data[offset + 3] ?? 0);
2156
- }
2157
- /** Collect a Bitmap Font's texture image, packed under the font's ID. */
2158
- async function _collectFontTexture(doc, fontRes, pkg, options) {
2159
- const textureId = fontRes.getTextureId?.() ?? "";
2160
- if (textureId) {
2161
- const fontId = fontRes.getId();
2162
- fontRes.setExtras({
2163
- ...fontRes.getExtras(),
2164
- _fontSpriteAlias: {
2165
- fontId,
2166
- textureId
2167
- }
2168
- });
2169
- }
2170
- if (options.readFileRaw && options.basePath) {
2171
- const fontName = resolveFontFileName(fontRes.getName());
2172
- const fontPath = fontRes.getPath() ?? "/";
2173
- const pkgName = pkg.getName();
2174
- const fntFile = `${options.basePath}/${pkgName}${fontPath}${fontName}`;
2175
- try {
2176
- const fntData = await options.readFileRaw(fntFile);
2177
- const fntParsed = _parseFnt(new TextDecoder().decode(fntData));
2178
- for (const glyph of fontRes.listGlyphs()) fontRes.removeGlyph(glyph);
2179
- fontRes.setTtf(fntParsed.hasFace).setTint(fntParsed.colored).setAutoScale(fntParsed.resizable).setHasChannel(fntParsed.hasChannel).setFontSize(fntParsed.fontSize).setXAdvance(fntParsed.xadvance).setLineHeight(fntParsed.lineHeight);
2180
- for (const item of fntParsed.glyphs) {
2181
- const glyph = doc.createFontGlyph(`${fontRes.getId()}_${item.charId}`);
2182
- 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);
2183
- fontRes.addGlyph(glyph);
2184
- }
2185
- } catch {}
2186
- }
2187
- }
2188
- /** Parse a BMFont .fnt text file into structured data for binary encoding. */
2189
- function _parseFnt(text) {
2190
- const lines = text.split(/\r?\n/);
2191
- let hasFace = false, colored = false, resizable = false, hasChannel = false;
2192
- let fontSize = 0, globalXadvance = 0, lineHeight = 0;
2193
- const glyphs = [];
2194
- for (const line of lines) {
2195
- const trimmed = line.trim();
2196
- if (!trimmed) continue;
2197
- const parts = trimmed.split(/\s+/);
2198
- const attrs = {};
2199
- for (let i = 1; i < parts.length; i++) {
2200
- const eq = parts[i].split("=");
2201
- if (eq.length === 2) attrs[eq[0]] = eq[1];
2202
- }
2203
- switch (parts[0]) {
2204
- case "info":
2205
- hasFace = attrs.face != null;
2206
- colored = hasFace;
2207
- if (attrs.colored !== void 0) colored = attrs.colored === "true";
2208
- fontSize = parseInt(attrs.size, 10) || 0;
2209
- resizable = attrs.resizable === "true";
2210
- break;
2211
- case "common":
2212
- lineHeight = parseInt(attrs.lineHeight, 10) || 0;
2213
- globalXadvance = parseInt(attrs.xadvance, 10) || 0;
2214
- if (fontSize === 0) fontSize = lineHeight;
2215
- else if (lineHeight === 0) lineHeight = fontSize;
2216
- break;
2217
- case "char": {
2218
- const charId = parseInt(attrs.id, 10) || 0;
2219
- if (charId === 0) continue;
2220
- const img = attrs.img || null;
2221
- if (!hasFace && !img) continue;
2222
- const chnl = parseInt(attrs.chnl, 10) || 0;
2223
- if (chnl !== 0 && chnl !== 15) hasChannel = true;
2224
- glyphs.push({
2225
- charId,
2226
- img,
2227
- x: parseInt(attrs.x, 10) || 0,
2228
- y: parseInt(attrs.y, 10) || 0,
2229
- xoffset: parseInt(attrs.xoffset, 10) || 0,
2230
- yoffset: parseInt(attrs.yoffset, 10) || 0,
2231
- width: parseInt(attrs.width, 10) || 0,
2232
- height: parseInt(attrs.height, 10) || 0,
2233
- xadvance: parseInt(attrs.xadvance, 10) || 0,
2234
- channel: chnl
2235
- });
2236
- break;
2237
- }
2238
- }
2239
- }
2240
- return {
2241
- hasFace,
2242
- colored,
2243
- resizable: fontSize > 0 ? resizable : false,
2244
- hasChannel,
2245
- fontSize,
2246
- xadvance: globalXadvance,
2247
- lineHeight,
2248
- glyphs
2249
- };
2250
- }
2251
- function isComponentResource$1(resource) {
2252
- return resource.propertyType === "Component";
2253
- }
2254
- function isImageResource$1(resource) {
2255
- return resource.propertyType === "ImageResource";
2256
- }
2257
- function isMovieClipResource$1(resource) {
2258
- return resource.propertyType === "MovieClipResource";
2259
- }
2260
- function isSkeletonResource$1(resource) {
2261
- return resource.propertyType === "SpineResource" || resource.propertyType === "DragonBonesResource";
2262
- }
2263
- function isFontResource$1(resource) {
2264
- return resource.propertyType === "FontResource";
2265
- }
2266
- function isPackableResource(resource) {
2267
- return isImageResource$1(resource) || isMovieClipResource$1(resource) || isFontResource$1(resource);
2268
- }
2269
- function addUiResourceRef(target, value) {
2270
- if (!value?.startsWith("ui://")) return;
2271
- const refId = value.slice(5).slice(8);
2272
- if (refId) target.add(refId);
2273
- }
2274
- function addUiResourceRefsFromText(target, value) {
2275
- if (!value || typeof value !== "string") return;
2276
- const matches = value.matchAll(/ui:\/\/[0-9a-z]{8}([0-9a-z]+)/gi);
2277
- for (const match of matches) {
2278
- const refId = match[1] ?? "";
2279
- if (refId) target.add(refId);
2280
- }
2281
- }
2282
- function addUiResourceRefsFromUnknown(target, value) {
2283
- if (Array.isArray(value)) {
2284
- for (const entry of value) addUiResourceRefsFromUnknown(target, entry);
2285
- return;
2286
- }
2287
- if (typeof value === "string") {
2288
- addUiResourceRef(target, value);
2289
- addUiResourceRefsFromText(target, value);
2290
- }
2291
- }
2292
- function isResolvedBuffer(value) {
2293
- return typeof value === "object" && value !== null && "data" in value && "info" in value;
2294
- }
2295
- //#endregion
2296
- //#region src/codegen-templates.ts
2297
- const UNITY_COMPONENT_TEMPLATE = `{{generatedMark}}
2298
-
2299
- using FairyGUI;
2300
- using FairyGUI.Utils;
2301
-
2302
- namespace {{namespaceName}}
2303
- {
2304
- \tpublic partial class {{className}} : {{componentType}}
2305
- \t{
2306
- \t\tpublic const string URL = "{{url}}";
2307
- {{variableLines}}
2308
- \t\tpublic static {{className}} CreateInstance()
2309
- \t\t{
2310
- \t\t\treturn ({{className}})UIPackage.CreateObject("{{packageName}}", "{{componentName}}");
2311
- \t\t}
2312
-
2313
- \t\tpublic override void ConstructFromXML(XML xml)
2314
- \t\t{
2315
- \t\t\tbase.ConstructFromXML(xml);
2316
- {{assignmentLines}}
2317
- \t\t}
2318
- \t}
2319
- }
2320
- `;
2321
- const UNITY_BINDER_TEMPLATE = `{{generatedMark}}
2322
-
2323
- using FairyGUI;
2324
-
2325
- namespace {{namespaceName}}
2326
- {
2327
- \tpublic static class {{binderClassName}}
2328
- \t{
2329
- \t\tpublic static void BindAll()
2330
- \t\t{
2331
- {{bindLines}}
2332
- \t\t}
2333
- \t}
2334
- }
2335
- `;
2336
- const FGUI_TYPESCRIPT_COMPONENT_TEMPLATE = `{{generatedMark}}
2337
-
2338
- {{importLines}}export default class {{className}} extends {{componentType}}
2339
- {
2340
- \tpublic static URL:string = "{{url}}";
2341
- {{variableLines}}
2342
- \tpublic static createInstance():{{className}}
2343
- \t{
2344
- \t\treturn <{{className}}><any>({{runtimeNamespace}}.UIPackage.createObject("{{packageName}}","{{componentName}}"));
2345
- \t}
2346
-
2347
- \tprotected onConstruct():void
2348
- \t{
2349
- {{assignmentLines}}\t}
2350
- }
2351
- `;
2352
- const FGUI_TYPESCRIPT_BINDER_TEMPLATE = `{{generatedMark}}
2353
-
2354
- {{importLines}}export default class {{binderClassName}}
2355
- {
2356
- \tpublic static bindAll():void
2357
- \t{
2358
- {{bindLines}}\t}
2359
- }
2360
- `;
2361
- //#endregion
2362
- //#region src/plugins/loader.ts
2363
- const importNative = new Function("id", "return import(id)");
2364
- async function loadPlugins(doc, pluginsDir) {
2365
- if (!pluginsDir) return [];
2366
- const fs = await importNative("node:fs/promises");
2367
- const path = await importNative("node:path");
2368
- let entries;
2369
- try {
2370
- entries = await fs.readdir(pluginsDir, { withFileTypes: true });
2371
- } catch {
2372
- return [];
2373
- }
2374
- const plugins = [];
2375
- for (const entry of entries) {
2376
- if (!entry.isDirectory()) continue;
2377
- const pluginDir = path.join(pluginsDir, entry.name);
2378
- try {
2379
- const manifest = await readPluginManifest(fs, path, pluginDir);
2380
- if (!manifest) continue;
2381
- const plugin = await loadPlugin(resolvePluginMain(path, pluginDir, manifest));
2382
- plugins.push({
2383
- name: manifest.name,
2384
- plugin
2385
- });
2386
- } catch (error) {
2387
- doc.getLogger().warn(`publish: Plugin "${entry.name}" was skipped: ${formatPluginError(error)}`);
2388
- }
2389
- }
2390
- return plugins;
2391
- }
2392
- function formatPluginError(error) {
2393
- return error instanceof Error ? error.message : String(error);
2394
- }
2395
- async function readPluginManifest(fs, path, pluginDir) {
2396
- const manifestPath = path.join(pluginDir, "package.json");
2397
- const content = await fs.readFile(manifestPath, "utf-8");
2398
- const manifest = JSON.parse(content);
2399
- if (!manifest.name) throw new Error(`Codegen plugin at ${pluginDir} is missing package.json name.`);
2400
- if (!manifest.main) throw new Error(`Codegen plugin "${manifest.name}" is missing package.json main.`);
2401
- return manifest;
2402
- }
2403
- function resolvePluginMain(path, pluginDir, manifest) {
2404
- const mainPath = path.resolve(pluginDir, manifest.main);
2405
- const relative = path.relative(pluginDir, mainPath);
2406
- if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`Codegen plugin "${manifest.name}" main must resolve inside its plugin directory.`);
2407
- return mainPath;
2408
- }
2409
- async function loadPlugin(mainPath) {
2410
- const mod = await createJiti(import.meta.url).import(mainPath);
2411
- const defaultExport = mod.default;
2412
- return isObject(defaultExport) ? defaultExport : mod;
2413
- }
2414
- function isObject(value) {
2415
- return value !== null && typeof value === "object";
2416
- }
2417
- //#endregion
2418
- //#region src/codegen.ts
2419
- const AUTO_GENERATED_CODE_MARK = "/** This is an automatically generated class by FairyGUI. Please do not modify it. **/";
2420
- const DEFAULT_CLASS_NAME_PREFIX = "UI_";
2421
- const DEFAULT_MEMBER_NAME_PREFIX = "m_";
2422
- const FGUI_TYPESCRIPT_RUNTIME_TYPES = new Set([
2423
- "Controller",
2424
- "GButton",
2425
- "GComboBox",
2426
- "GComponent",
2427
- "GGraph",
2428
- "GGroup",
2429
- "GImage",
2430
- "GLabel",
2431
- "GList",
2432
- "GLoader",
2433
- "GLoader3D",
2434
- "GMovieClip",
2435
- "GProgressBar",
2436
- "GRichTextField",
2437
- "GScrollBar",
2438
- "GSlider",
2439
- "GSwfObject",
2440
- "GTextField",
2441
- "GTextInput",
2442
- "GTree",
2443
- "Transition"
2444
- ]);
2445
- const SHARED_FGUI_TYPESCRIPT_VARIANT = {
2446
- binderMethod: "setExtension",
2447
- runtimeNamespace: "fgui"
2448
- };
2449
- async function publishCodeGeneration(doc, options) {
2450
- const logger = doc.getLogger();
2451
- const settings = resolveCodeGenerationSettings(doc);
2452
- if (!settings.allowGenCode) return;
2453
- const plugins = options.plugins?.filter((plugin) => typeof plugin.plugin.genCode === "function") ?? [];
2454
- if (plugins.length > 0) {
2455
- let handled = false;
2456
- for (const plugin of plugins) try {
2457
- await plugin.plugin.genCode(doc, settings, options);
2458
- handled = true;
2459
- logger.info(`publish: Generated code using plugin "${plugin.name}"`);
2460
- } catch (error) {
2461
- logger.warn(`publish: Code generation plugin "${plugin.name}" failed: ${formatPluginError(error)}`);
2462
- }
2463
- if (handled) return;
2464
- }
2465
- for (const pkg of options.packages) {
2466
- if (!pkg.getGenCode()) continue;
2467
- const plan = resolvePackageCodegenPlan(pkg, settings, options);
2468
- if (!plan) {
2469
- logger.warn(`publish: Code generation skipped for package "${pkg.getName()}" because no codePath was resolved.`);
2470
- continue;
2471
- }
2472
- if (!supportsCodeGenerationLane(doc, settings.codeType)) {
2473
- logger.warn(`publish: Code generation skipped for package "${pkg.getName()}" because project/codeType is not supported yet.`);
2474
- continue;
2475
- }
2476
- const fguiTypescriptVariant = resolveFguiTypescriptVariant(doc);
2477
- if (fguiTypescriptVariant) await generateFguiTypescriptCode(doc, pkg, plan, options.fs, fguiTypescriptVariant);
2478
- else await generateUnityCode(doc, pkg, plan, options.fs);
2479
- logger.info(`publish: Generated code for package "${pkg.getName()}" into ${plan.outputDir}`);
2480
- }
2481
- }
2482
- function resolveCodeGenerationSettings(doc) {
2483
- const codeGeneration = ((doc.getRoot().getSettings?.() ?? {}).publish ?? {}).codeGeneration;
2484
- if (!codeGeneration) return {
2485
- allowGenCode: true,
2486
- classNamePrefix: "UI_",
2487
- memberNamePrefix: "m_",
2488
- packageName: "",
2489
- ignoreNoname: false,
2490
- getMemberByName: false,
2491
- codePath: "",
2492
- codeType: ""
2493
- };
2494
- return {
2495
- allowGenCode: codeGeneration.allowGenCode ?? true,
2496
- classNamePrefix: codeGeneration.classNamePrefix ?? DEFAULT_CLASS_NAME_PREFIX,
2497
- memberNamePrefix: codeGeneration.memberNamePrefix ?? DEFAULT_MEMBER_NAME_PREFIX,
2498
- packageName: codeGeneration.packageName ?? "",
2499
- ignoreNoname: codeGeneration.ignoreNoname ?? false,
2500
- getMemberByName: Boolean(codeGeneration.getMemberByName),
2501
- codePath: codeGeneration.codePath ?? "",
2502
- codeType: codeGeneration.codeType?.trim() ?? ""
2503
- };
2504
- }
2505
- function resolvePackageCodegenPlan(pkg, settings, options) {
2506
- const rawCodePath = (pkg.getCodePath() || settings.codePath || "").trim();
2507
- if (!rawCodePath) return null;
2508
- const packageFolderName = normalizeTypeName(pkg.getName()) || "Package";
2509
- return {
2510
- outputDir: resolveCodePath(rawCodePath, options.basePath, options.fs),
2511
- packageFolderName,
2512
- packageNamespace: settings.packageName ? `${settings.packageName}.${packageFolderName}` : packageFolderName,
2513
- binderClassName: `${packageFolderName}Binder`,
2514
- settings
2515
- };
2516
- }
2517
- function supportsCodeGenerationLane(doc, codeType) {
2518
- const projectType = doc.getRoot().getProjectType();
2519
- if (projectType === ProjectType.Unity) return codeType === "";
2520
- if (projectType === ProjectType.LayaBox || projectType === ProjectType.CocosCreator) return true;
2521
- return false;
2522
- }
2523
- function resolveFguiTypescriptVariant(doc) {
2524
- const projectType = doc.getRoot().getProjectType();
2525
- if (projectType !== ProjectType.LayaBox && projectType !== ProjectType.CocosCreator) return null;
2526
- return SHARED_FGUI_TYPESCRIPT_VARIANT;
2527
- }
2528
- async function generateUnityCode(doc, pkg, plan, fs) {
2529
- const packageDir = fs.join(plan.outputDir, plan.packageFolderName);
2530
- await fs.mkdir(plan.outputDir);
2531
- await fs.mkdir(packageDir);
2532
- await cleanupGeneratedFiles(packageDir, fs);
2533
- const classes = buildCodegenClasses(doc, pkg, plan);
2534
- for (const classInfo of classes) await writeTextFile(fs, fs.join(packageDir, `${classInfo.encodedClassName}.cs`), renderUnityComponentClass(classInfo, plan));
2535
- await writeTextFile(fs, fs.join(packageDir, `${plan.binderClassName}.cs`), renderUnityBinder(classes, plan));
2536
- }
2537
- async function generateFguiTypescriptCode(doc, pkg, plan, fs, variant) {
2538
- const packageDir = fs.join(plan.outputDir, plan.packageFolderName);
2539
- await fs.mkdir(plan.outputDir);
2540
- await fs.mkdir(packageDir);
2541
- await cleanupGeneratedFiles(packageDir, fs, ".ts");
2542
- const classes = buildCodegenClasses(doc, pkg, plan);
2543
- for (const classInfo of classes) await writeTextFile(fs, fs.join(packageDir, `${classInfo.encodedClassName}.ts`), renderFguiTypescriptComponentClass(classInfo, plan, variant));
2544
- await writeTextFile(fs, fs.join(packageDir, `${plan.binderClassName}.ts`), renderFguiTypescriptBinder(classes, plan, variant));
2545
- }
2546
- async function cleanupGeneratedFiles(directory, fs, extension = ".cs") {
2547
- if (!fs.readdir || !fs.readFileRaw || !fs.deleteFile) return;
2548
- let entries;
2549
- try {
2550
- entries = await fs.readdir(directory);
2551
- } catch {
2552
- return;
2553
- }
2554
- for (const entry of entries) {
2555
- if (!entry.toLowerCase().endsWith(extension)) continue;
2556
- const filePath = fs.join(directory, entry);
2557
- try {
2558
- if (decodeText(await fs.readFileRaw(filePath)).startsWith("/** This is an automatically generated class by FairyGUI. Please do not modify it. **/")) await fs.deleteFile(filePath);
2559
- } catch {}
2560
- }
2561
- }
2562
- function buildCodegenClasses(doc, pkg, plan) {
2563
- const codegenComponents = pkg.listComponents().sort((left, right) => left.getId().localeCompare(right.getId()));
2564
- const generatedById = /* @__PURE__ */ new Map();
2565
- for (const component of codegenComponents) {
2566
- const encodedClassName = `${plan.settings.classNamePrefix}${normalizeTypeName(component.getName()) || "Component"}`;
2567
- generatedById.set(component.getId(), {
2568
- classId: component.getId(),
2569
- className: component.getName(),
2570
- encodedClassName,
2571
- componentType: resolveComponentBaseType(component),
2572
- componentName: component.getName(),
2573
- packageName: pkg.getName(),
2574
- url: `ui://${pkg.getId()}${component.getId()}`,
2575
- members: []
2576
- });
2577
- }
2578
- for (const component of codegenComponents) {
2579
- const classInfo = generatedById.get(component.getId());
2580
- if (!classInfo) continue;
2581
- classInfo.members = buildCodegenMembers(doc, pkg, component, plan, generatedById);
2582
- }
2583
- for (const [componentId, classInfo] of generatedById) if (classInfo.members.every((member) => member.ignored)) generatedById.delete(componentId);
2584
- for (const component of codegenComponents) {
2585
- const classInfo = generatedById.get(component.getId());
2586
- if (!classInfo) continue;
2587
- classInfo.members = buildCodegenMembers(doc, pkg, component, plan, generatedById);
2588
- }
2589
- return [...generatedById.values()];
2590
- }
2591
- function buildCodegenMembers(doc, pkg, component, plan, generatedById) {
2592
- const members = [];
2593
- const ownerType = resolveComponentBaseType(component);
2594
- let controllerIndex = 0;
2595
- let childIndex = 0;
2596
- let transitionIndex = 0;
2597
- for (const controller of component.listControllers()) members.push(createMember(ownerType, "controller", "Controller", controller.getName(), controllerIndex++, plan));
2598
- for (const child of component.listChildren()) {
2599
- if (!isRuntimeChild(child)) continue;
2600
- const index = childIndex++;
2601
- const resolvedChild = resolveChildType(doc, pkg, child, generatedById);
2602
- members.push(createMember(ownerType, "child", resolvedChild.type, child.getName(), index, plan, resolvedChild.referencedComponent));
2603
- }
2604
- for (const transition of component.listTransitions()) members.push(createMember(ownerType, "transition", "Transition", transition.getName(), transitionIndex++, plan));
2605
- const usedNames = /* @__PURE__ */ new Map();
2606
- for (const member of members) {
2607
- if (member.ignored) continue;
2608
- const key = applyMemberNamePrefix(member.originalName, plan.settings.memberNamePrefix);
2609
- const current = usedNames.get(key) ?? 0;
2610
- if (current > 0) member.name = `${key}_${current + 1}`;
2611
- usedNames.set(key, current + 1);
2612
- }
2613
- return members;
2614
- }
2615
- function isRuntimeChild(child) {
2616
- return child.propertyType !== "GGroup" || child.getAdvanced?.() === true;
2617
- }
2618
- function createMember(ownerType, kind, type, originalName, index, plan, referencedComponent) {
2619
- const ignored = plan.settings.ignoreNoname && isDefaultMemberName(ownerType, kind, originalName);
2620
- return {
2621
- index,
2622
- kind,
2623
- name: applyMemberNamePrefix(originalName, plan.settings.memberNamePrefix),
2624
- originalName,
2625
- type,
2626
- ignored,
2627
- referencedComponent
2628
- };
2629
- }
2630
- function resolveChildType(doc, pkg, child, generatedById) {
2631
- const src = child.getSrc?.();
2632
- if (src) {
2633
- let referencedComponent = null;
2634
- if (src.startsWith("ui://")) {
2635
- const rest = src.slice(5);
2636
- const pkgId = rest.slice(0, 8);
2637
- const resourceId = rest.slice(8);
2638
- const targetPackage = doc.getRoot().listPackages().find((candidate) => candidate.getId() === pkgId);
2639
- const targetResource = targetPackage?.getResourceById(resourceId);
2640
- if (targetPackage && targetResource?.propertyType === "Component") referencedComponent = {
2641
- component: targetResource,
2642
- package: targetPackage
2643
- };
2644
- } else {
2645
- const packageId = child.getPackageId?.();
2646
- const targetPackage = packageId ? doc.getRoot().listPackages().find((candidate) => candidate.getId() === packageId) : pkg;
2647
- const targetResource = targetPackage?.getResourceById(src);
2648
- if (targetPackage && targetResource?.propertyType === "Component") referencedComponent = {
2649
- component: targetResource,
2650
- package: targetPackage
2651
- };
299
+ */
300
+ function rename(options) {
301
+ const updateReferences = options.updateReferences ?? true;
302
+ return createTransform("rename", (doc) => {
303
+ const root = doc.getRoot();
304
+ const logger = doc.getLogger();
305
+ const pkg = root.listPackages().find((p) => p.getName() === options.packageName);
306
+ if (!pkg) {
307
+ logger.warn(`rename: Package "${options.packageName}" not found.`);
308
+ return;
2652
309
  }
2653
- if (referencedComponent) return {
2654
- type: (referencedComponent.package === pkg ? generatedById.get(referencedComponent.component.getId()) : void 0)?.encodedClassName ?? resolveComponentBaseType(referencedComponent.component),
2655
- referencedComponent
2656
- };
2657
- }
2658
- const instanceExtType = child.getInstanceExtType?.();
2659
- if (instanceExtType) return { type: `G${instanceExtType}` };
2660
- return { type: child.propertyType };
2661
- }
2662
- function resolveComponentBaseType(component) {
2663
- const extensionType = component.getExtensionType();
2664
- return extensionType ? `G${extensionType}` : "GComponent";
2665
- }
2666
- function renderUnityComponentClass(classInfo, plan) {
2667
- const variableLines = classInfo.members.filter((member) => !member.ignored).map((member) => `\t\tpublic ${member.type} ${member.name};`).join("\n");
2668
- const contentLines = classInfo.members.map((member) => renderMemberAssignment(member, plan.settings.getMemberByName)).filter((line) => Boolean(line)).join("\n");
2669
- return renderTemplate(UNITY_COMPONENT_TEMPLATE, {
2670
- assignmentLines: contentLines ? `${contentLines}\n` : "",
2671
- className: classInfo.encodedClassName,
2672
- componentName: escapeCSharpString(classInfo.className),
2673
- componentType: classInfo.componentType,
2674
- generatedMark: AUTO_GENERATED_CODE_MARK,
2675
- namespaceName: plan.packageNamespace,
2676
- packageName: escapeCSharpString(classInfo.packageName),
2677
- url: escapeCSharpString(classInfo.url),
2678
- variableLines: variableLines ? `${variableLines}\n` : ""
2679
- });
2680
- }
2681
- function renderUnityBinder(classes, plan) {
2682
- const bindLines = classes.map((classInfo) => `\t\t\tUIObjectFactory.SetPackageItemExtension(${classInfo.encodedClassName}.URL, typeof(${classInfo.encodedClassName}));`).join("\n");
2683
- return renderTemplate(UNITY_BINDER_TEMPLATE, {
2684
- binderClassName: plan.binderClassName,
2685
- bindLines: bindLines ? `${bindLines}\n` : "",
2686
- generatedMark: AUTO_GENERATED_CODE_MARK,
2687
- namespaceName: plan.packageNamespace
2688
- });
2689
- }
2690
- function renderFguiTypescriptComponentClass(classInfo, plan, variant) {
2691
- const variableLines = classInfo.members.filter((member) => !member.ignored).map((member) => `\tpublic ${member.name}:${translateFguiTypescriptType(member.type, variant)};`).join("\n");
2692
- const assignmentLines = classInfo.members.map((member) => renderFguiTypescriptMemberAssignment(member, plan.settings.getMemberByName, variant)).filter((line) => Boolean(line)).join("\n");
2693
- const importLines = collectFguiTypescriptImports(classInfo, variant);
2694
- return renderTemplate(FGUI_TYPESCRIPT_COMPONENT_TEMPLATE, {
2695
- assignmentLines: assignmentLines ? `${assignmentLines}\n` : "",
2696
- className: classInfo.encodedClassName,
2697
- componentName: escapeTypeScriptString(classInfo.className),
2698
- componentType: translateFguiTypescriptType(classInfo.componentType, variant),
2699
- generatedMark: AUTO_GENERATED_CODE_MARK,
2700
- importLines,
2701
- packageName: escapeTypeScriptString(classInfo.packageName),
2702
- runtimeNamespace: variant.runtimeNamespace,
2703
- url: escapeTypeScriptString(classInfo.url),
2704
- variableLines: variableLines ? `${variableLines}\n` : ""
2705
- });
2706
- }
2707
- function renderFguiTypescriptBinder(classes, plan, variant) {
2708
- const bindLines = classes.map((classInfo) => `\t\t${variant.runtimeNamespace}.UIObjectFactory.${variant.binderMethod}(${classInfo.encodedClassName}.URL, ${classInfo.encodedClassName});`).join("\n");
2709
- const importLines = classes.map((classInfo) => `import ${classInfo.encodedClassName} from "./${classInfo.encodedClassName}";`).join("\n");
2710
- return renderTemplate(FGUI_TYPESCRIPT_BINDER_TEMPLATE, {
2711
- binderClassName: plan.binderClassName,
2712
- bindLines: bindLines ? `${bindLines}\n` : "",
2713
- generatedMark: AUTO_GENERATED_CODE_MARK,
2714
- importLines: importLines ? `${importLines}\n\n` : ""
310
+ const resource = pkg.listResources().find((r) => r.getName() === options.resourceName) || pkg.listComponents().find((c) => c.getName() === options.resourceName);
311
+ if (!resource) {
312
+ logger.warn(`rename: Resource "${options.resourceName}" not found in package "${options.packageName}".`);
313
+ return;
314
+ }
315
+ const oldName = resource.getName();
316
+ resource.setName(options.newName);
317
+ logger.info(`rename: Renamed "${oldName}" "${options.newName}" in package "${options.packageName}".`);
318
+ if (updateReferences) logger.info(`rename: References use resource IDs — no src updates needed.`);
2715
319
  });
2716
320
  }
2717
- function renderMemberAssignment(member, getMemberByName) {
2718
- if (member.ignored) return null;
2719
- 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});`;
2720
- 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});`;
2721
- 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});`;
2722
- }
2723
- function renderFguiTypescriptMemberAssignment(member, getMemberByName, variant) {
2724
- if (member.ignored) return null;
2725
- if (member.type === "Controller") return getMemberByName ? `\t\tthis.${member.name} = this.getController("${escapeTypeScriptString(member.originalName)}");` : `\t\tthis.${member.name} = this.getControllerAt(${member.index});`;
2726
- if (member.type === "Transition") return getMemberByName ? `\t\tthis.${member.name} = this.getTransition("${escapeTypeScriptString(member.originalName)}");` : `\t\tthis.${member.name} = this.getTransitionAt(${member.index});`;
2727
- const translatedType = translateFguiTypescriptType(member.type, variant);
2728
- return getMemberByName ? `\t\tthis.${member.name} = <${translatedType}><any>(this.getChild("${escapeTypeScriptString(member.originalName)}"));` : `\t\tthis.${member.name} = <${translatedType}><any>(this.getChildAt(${member.index}));`;
2729
- }
2730
- function resolveCodePath(codePath, basePath, fs) {
2731
- if (isAbsolutePath(codePath)) return trimTrailingSlashes$2(codePath);
2732
- const projectBasePath = resolveProjectBasePath(basePath);
2733
- return projectBasePath ? trimTrailingSlashes$2(fs.join(projectBasePath, codePath)) : trimTrailingSlashes$2(codePath);
2734
- }
2735
- function resolveProjectBasePath(basePath) {
2736
- if (!basePath) return "";
2737
- const normalized = trimTrailingSlashes$2(basePath);
2738
- const assetsMatch = normalized.match(/^(.*)[/\\]assets(?:_[^/\\]+)?$/i);
2739
- if (assetsMatch?.[1]) return assetsMatch[1];
2740
- return dirname$2(normalized);
2741
- }
2742
- function dirname$2(filePath) {
2743
- return trimTrailingSlashes$2(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
2744
- }
2745
- function trimTrailingSlashes$2(value) {
2746
- return value.replace(/[/\\]+$/, "");
2747
- }
2748
- function isAbsolutePath(value) {
2749
- return /^[a-z]:[/\\]/i.test(value) || value.startsWith("/") || value.startsWith("\\\\");
2750
- }
2751
- function isDefaultMemberName(ownerType, kind, name) {
2752
- if (kind === "controller") return (ownerType === "GButton" || ownerType === "GComboBox") && name === "button";
2753
- if (kind === "transition") return false;
2754
- if (ownerType === "GButton" || ownerType === "GLabel" || ownerType === "GComboBox") {
2755
- if (name === "title" || name === "icon") return true;
2756
- }
2757
- if (ownerType === "GProgressBar") {
2758
- if (name === "bar" || name === "bar_v" || name === "title" || name === "ani") return true;
2759
- }
2760
- if (ownerType === "GSlider") {
2761
- if (name === "bar" || name === "bar_v" || name === "grip" || name === "title" || name === "ani") return true;
2762
- }
2763
- return /^n\d+(?:_.*)?$/i.test(name);
2764
- }
2765
- function applyMemberNamePrefix(name, prefix) {
2766
- const normalized = normalizeMemberName(name) || "member";
2767
- return prefix ? `${prefix}${normalized}` : normalized;
2768
- }
2769
- function normalizeMemberName(value) {
2770
- const cleaned = value.replace(/[^0-9A-Za-z_]+/g, "_").replace(/^_+|_+$/g, "");
2771
- if (!cleaned) return "";
2772
- return /^[0-9]/.test(cleaned) ? `_${cleaned}` : cleaned;
2773
- }
2774
- function normalizeTypeName(value) {
2775
- const cleaned = value.replace(/[^0-9A-Za-z_]+/g, "_").replace(/^_+|_+$/g, "");
2776
- if (!cleaned) return "";
2777
- const normalized = cleaned.split(/_+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
2778
- return /^[0-9]/.test(normalized) ? `_${normalized}` : normalized;
2779
- }
2780
- function collectFguiTypescriptImports(classInfo, variant) {
2781
- const imports = /* @__PURE__ */ new Set();
2782
- for (const member of classInfo.members) {
2783
- if (member.ignored) continue;
2784
- const translated = translateFguiTypescriptType(member.type, variant);
2785
- if (!translated.includes(".")) imports.add(`import ${translated} from "./${translated}";`);
2786
- }
2787
- return imports.size > 0 ? `${[...imports].sort().join("\n")}\n\n` : "";
2788
- }
2789
- function translateFguiTypescriptType(typeName, variant) {
2790
- if (FGUI_TYPESCRIPT_RUNTIME_TYPES.has(typeName)) return `${variant.runtimeNamespace}.${typeName}`;
2791
- return typeName;
2792
- }
2793
- function renderTemplate(template, data) {
2794
- let output = template;
2795
- for (const [key, value] of Object.entries(data)) output = output.replaceAll(`{{${key}}}`, value);
2796
- return output;
2797
- }
2798
- function escapeCSharpString(value) {
2799
- return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
2800
- }
2801
- function escapeTypeScriptString(value) {
2802
- return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
2803
- }
2804
- async function writeTextFile(fs, filePath, content) {
2805
- await fs.writeFileRaw(filePath, encodeText(content));
2806
- }
2807
- function encodeText(value) {
2808
- return new TextEncoder().encode(value);
2809
- }
2810
- function decodeText(value) {
2811
- return new TextDecoder().decode(value);
2812
- }
2813
321
  //#endregion
2814
322
  //#region src/restore.ts
2815
323
  const JTA_FILE_MARK = "yytou";
@@ -3048,11 +556,11 @@ function inferPackageName(fileName) {
3048
556
  if (/\.fui$/i.test(fileName)) return fileName.replace(/\.fui$/i, "");
3049
557
  return fileName.replace(/\.bin$/i, "");
3050
558
  }
3051
- function trimTrailingSlashes$1(value) {
559
+ function trimTrailingSlashes(value) {
3052
560
  return value.replace(/[/\\]+$/, "");
3053
561
  }
3054
562
  function normalizeComparablePath(value) {
3055
- const normalized = trimTrailingSlashes$1(value).replace(/\\/g, "/");
563
+ const normalized = trimTrailingSlashes(value).replace(/\\/g, "/");
3056
564
  const driveMatch = normalized.match(/^([a-z]:)(?:\/(.*))?$/i);
3057
565
  const drivePrefix = driveMatch?.[1].toLowerCase() ?? "";
3058
566
  const remainder = driveMatch ? driveMatch[2] ?? "" : normalized;
@@ -3071,15 +579,15 @@ function normalizeComparablePath(value) {
3071
579
  const joined = segments.join("/");
3072
580
  return (drivePrefix ? `${drivePrefix}/${joined}`.replace(/\/$/, "") : hasRoot ? `/${joined}`.replace(/\/$/, "") : joined || ".").toLowerCase();
3073
581
  }
3074
- function dirname$1(filePath) {
3075
- return trimTrailingSlashes$1(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
582
+ function dirname(filePath) {
583
+ return trimTrailingSlashes(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
3076
584
  }
3077
585
  function basename(filePath) {
3078
- return trimTrailingSlashes$1(filePath).match(/([^/\\]+)$/)?.[1] ?? "";
586
+ return trimTrailingSlashes(filePath).match(/([^/\\]+)$/)?.[1] ?? "";
3079
587
  }
3080
588
  function resolveOutputProjectPath(output, fs) {
3081
589
  if (/\.fairy$/i.test(output)) return output;
3082
- const normalizedOutput = trimTrailingSlashes$1(output);
590
+ const normalizedOutput = trimTrailingSlashes(output);
3083
591
  const projectName = basename(normalizedOutput) || "Restored";
3084
592
  return fs.join(normalizedOutput, `${projectName}.fairy`);
3085
593
  }
@@ -3125,10 +633,10 @@ async function prepareRestoreOutputDir(inputDir, outputDir, outputProjectPath, f
3125
633
  await fs.mkdir(outputDir);
3126
634
  }
3127
635
  async function restore(options) {
3128
- const sourceDir = trimTrailingSlashes$1(options.inputDir);
636
+ const sourceDir = trimTrailingSlashes(options.inputDir);
3129
637
  const outputIsProjectFile = /\.fairy$/i.test(options.output);
3130
638
  const outputProjectPath = resolveOutputProjectPath(options.output, options.fs);
3131
- await prepareRestoreOutputDir(sourceDir, dirname$1(outputProjectPath) || ".", outputProjectPath, options.fs, options.force === true, outputIsProjectFile);
639
+ await prepareRestoreOutputDir(sourceDir, dirname(outputProjectPath) || ".", outputProjectPath, options.fs, options.force === true, outputIsProjectFile);
3132
640
  const packageFilter = options.packages?.length ? new Set(options.packages) : null;
3133
641
  const candidateBinaryPaths = (await options.fs.readdir(sourceDir)).filter((name) => isPublishedBinaryFile(name)).filter((name) => !packageFilter || packageFilter.has(inferPackageName(name))).map((name) => options.fs.join(sourceDir, name)).sort((left, right) => left.localeCompare(right));
3134
642
  const binaryPaths = (await Promise.all(candidateBinaryPaths.map(async (filePath) => await options.fs.isFile(filePath) ? filePath : null))).filter((filePath) => !!filePath).sort((left, right) => left.localeCompare(right));
@@ -3632,900 +1140,4 @@ var RestoreWorkflow = class {
3632
1140
  }
3633
1141
  };
3634
1142
  //#endregion
3635
- //#region src/publish.ts
3636
- async function runPublishPluginHook(plugins, hook, doc, options) {
3637
- const logger = doc.getLogger();
3638
- for (const plugin of plugins) {
3639
- const fn = plugin.plugin[hook];
3640
- if (typeof fn !== "function") continue;
3641
- try {
3642
- await fn(doc, options);
3643
- } catch (error) {
3644
- logger.warn(`publish: Plugin "${plugin.name}" ${hook} failed: ${formatPluginError(error)}`);
3645
- }
3646
- }
3647
- }
3648
- function resolvePublishPluginsDir(doc, options) {
3649
- const fs = options.fs;
3650
- const projectDir = doc.getProjectDir?.() ?? "";
3651
- if (projectDir) return fs?.join ? fs.join(projectDir, "plugins") : `${projectDir.replace(/[/\\]+$/, "")}/plugins`;
3652
- const projectBasePath = resolveProjectBasePath(options.basePath);
3653
- if (!projectBasePath) return "";
3654
- return fs?.join ? fs.join(projectBasePath, "plugins") : `${projectBasePath.replace(/[/\\]+$/, "")}/plugins`;
3655
- }
3656
- const UNITY_PROJECT_TYPE = ProjectType.Unity;
3657
- const COCOS_CREATOR_PROJECT_TYPE = ProjectType.CocosCreator;
3658
- function resolveDefaultPublishFileExtension(projectType, publishSettings) {
3659
- if (projectType === UNITY_PROJECT_TYPE) return "bytes";
3660
- if (projectType === COCOS_CREATOR_PROJECT_TYPE) return publishSettings.fileExtension || "bin";
3661
- return publishSettings.fileExtension || "fui";
3662
- }
3663
- function resolvePublishAtlasRuntimeOptions(fileExtension) {
3664
- return {
3665
- preserveInputOrderOnTie: fileExtension === "fui",
3666
- directSingleImageOutput: fileExtension === "bytes"
3667
- };
3668
- }
3669
- function resolvePublishFileName(publishName, fileExtension) {
3670
- if (fileExtension === "bytes") return `${publishName}_fui.bytes`;
3671
- return `${publishName}.${fileExtension}`;
3672
- }
3673
- /**
3674
- * Resolve publish defaults from the document's project settings.
3675
- *
3676
- * This keeps the editor-aligned publish rules reusable across environments,
3677
- * while callers still provide environment-specific concerns such as fs/encoder/basePath.
3678
- */
3679
- function resolvePublishOptions(doc, overrides = {}) {
3680
- const root = doc.getRoot();
3681
- const publishSettings = (root.getSettings?.() ?? {}).publish ?? {};
3682
- const atlasSetting = publishSettings.atlasSetting ?? {};
3683
- const projectType = root.getProjectType();
3684
- const fileExtension = overrides.fileExtension ?? resolveDefaultPublishFileExtension(projectType, publishSettings);
3685
- let compressed = overrides.compressed ?? publishSettings.compressDesc ?? false;
3686
- if (projectType === UNITY_PROJECT_TYPE) compressed = overrides.compressed ?? false;
3687
- const atlasOptions = {
3688
- maxSize: overrides.atlas?.maxSize ?? atlasSetting.maxSize ?? 2048,
3689
- fast: overrides.atlas?.fast ?? atlasSetting.fast ?? true,
3690
- allowRotation: overrides.atlas?.allowRotation ?? atlasSetting.allowRotation ?? false,
3691
- padding: overrides.atlas?.padding ?? atlasSetting.padding ?? 2,
3692
- powerOfTwo: overrides.atlas?.powerOfTwo ?? atlasSetting.sizeOption === "pot",
3693
- square: overrides.atlas?.square ?? atlasSetting.forceSquare ?? false,
3694
- multiPage: overrides.atlas?.multiPage ?? atlasSetting.paging ?? true,
3695
- trimImage: overrides.atlas?.trimImage ?? atlasSetting.trimImage ?? false,
3696
- extractAlpha: overrides.atlas?.extractAlpha ?? atlasSetting.extractAlpha ?? false
3697
- };
3698
- return {
3699
- compressed,
3700
- fileExtension,
3701
- packages: overrides.packages,
3702
- atlas: atlasOptions
3703
- };
3704
- }
3705
- function trimTrailingSlashes(value) {
3706
- return value.replace(/[/\\]+$/, "");
3707
- }
3708
- function isAbsolutePathLike(value) {
3709
- return /^(?:[a-zA-Z]:[/\\]|[/\\]{1,2})/u.test(value);
3710
- }
3711
- function joinPathSegments(left, right) {
3712
- const normalizedLeft = trimTrailingSlashes(left);
3713
- const normalizedRight = right.replace(/^[/\\]+/, "");
3714
- if (!normalizedLeft) return normalizedRight;
3715
- if (!normalizedRight) return normalizedLeft;
3716
- return `${normalizedLeft}${normalizedLeft.includes("\\") ? "\\" : "/"}${normalizedRight}`;
3717
- }
3718
- function dirname(filePath) {
3719
- return filePath.replace(/[/\\]+$/, "").match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
3720
- }
3721
- function createUnsupportedFsOperation(name) {
3722
- return async () => {
3723
- throw new Error(`publish: FileSystem.${name}() is not available in the publish writer adapter.`);
3724
- };
3725
- }
3726
- function toBinaryWriterFileSystem(fs) {
3727
- return {
3728
- readFile: createUnsupportedFsOperation("readFile"),
3729
- readFileRaw: createUnsupportedFsOperation("readFileRaw"),
3730
- writeFile: createUnsupportedFsOperation("writeFile"),
3731
- writeFileRaw: fs.writeFileRaw,
3732
- mkdir: fs.mkdir,
3733
- readdir: createUnsupportedFsOperation("readdir"),
3734
- exists: createUnsupportedFsOperation("exists"),
3735
- join: fs.join,
3736
- dirname
3737
- };
3738
- }
3739
- function isComponentResource(resource) {
3740
- return resource.propertyType === "Component";
3741
- }
3742
- function isImageResource(resource) {
3743
- return resource.propertyType === "ImageResource";
3744
- }
3745
- function isMovieClipResource(resource) {
3746
- return resource.propertyType === "MovieClipResource";
3747
- }
3748
- function isHighResolutionResource(resource) {
3749
- return isImageResource(resource) || isMovieClipResource(resource);
3750
- }
3751
- function isMiscResource(resource) {
3752
- return resource.propertyType === "MiscResource";
3753
- }
3754
- function isFontResource(resource) {
3755
- return resource.propertyType === "FontResource";
3756
- }
3757
- function isSoundResource(resource) {
3758
- return resource.propertyType === "SoundResource";
3759
- }
3760
- function isSpineResource(resource) {
3761
- return resource.propertyType === "SpineResource";
3762
- }
3763
- function isDragonBonesResource(resource) {
3764
- return resource.propertyType === "DragonBonesResource";
3765
- }
3766
- function isSkeletonResource(resource) {
3767
- return isSpineResource(resource) || isDragonBonesResource(resource);
3768
- }
3769
- function addLocalUiResourceRef(target, pkgId, value) {
3770
- if (!value || typeof value !== "string" || !value.startsWith(`ui://${pkgId}`) || value.length <= 13) return;
3771
- target.add(value.slice(13));
3772
- }
3773
- function addLocalUiResourceRefsFromText(target, pkgId, value) {
3774
- if (!value || typeof value !== "string") return;
3775
- const prefix = `ui://${pkgId}`;
3776
- let index = value.indexOf(prefix);
3777
- while (index !== -1) {
3778
- const start = index + prefix.length;
3779
- let end = start;
3780
- while (end < value.length && /[0-9a-z]/i.test(value[end] ?? "")) end++;
3781
- if (end > start) target.add(value.slice(start, end));
3782
- index = value.indexOf(prefix, end);
3783
- }
3784
- }
3785
- function addLocalUiResourceRefsFromUnknown(target, pkgId, value) {
3786
- if (Array.isArray(value)) {
3787
- for (const entry of value) addLocalUiResourceRefsFromUnknown(target, pkgId, entry);
3788
- return;
3789
- }
3790
- if (typeof value === "string") {
3791
- addLocalUiResourceRef(target, pkgId, value);
3792
- addLocalUiResourceRefsFromText(target, pkgId, value);
3793
- }
3794
- }
3795
- function addLocalFontRef(target, pkgId, value) {
3796
- if (Array.isArray(value)) {
3797
- for (const entry of value) addLocalUiResourceRef(target, pkgId, entry);
3798
- return;
3799
- }
3800
- addLocalUiResourceRef(target, pkgId, value ?? void 0);
3801
- }
3802
- function resolvePackageAssetsBasePath(basePath, resource) {
3803
- const branchName = resource?.getBranch?.() ?? "";
3804
- if (!branchName) return basePath;
3805
- const normalized = basePath.replace(/[/\\]+$/, "");
3806
- if (/[\\/]assets$/i.test(normalized)) return normalized.replace(/([\\/])assets$/i, `$1assets_${branchName}`);
3807
- return `${normalized}_${branchName}`;
3808
- }
3809
- function resolveImagePath(resource, pkg, basePath) {
3810
- const fileName = resolveImageFileName(resource);
3811
- const resourcePath = resource.getPath() ?? "/";
3812
- return `${resolvePackageAssetsBasePath(basePath, resource)}/${pkg.getName()}${resourcePath}${fileName}`;
3813
- }
3814
- function resolveImageFileName(resource) {
3815
- const extras = resource.getExtras() ?? {};
3816
- return resource.getFileName() || extras._fileName || resource.getName();
3817
- }
3818
- function resolveSoundPath(resource, pkg, basePath) {
3819
- const resourcePath = resource.getPath() ?? "/";
3820
- return `${resolvePackageAssetsBasePath(basePath, resource)}/${pkg.getName()}${resourcePath}${resource.getFile()}`;
3821
- }
3822
- function resolveGenericResourcePath(resource, pkg, basePath) {
3823
- const resourcePath = resource.getPath() ?? "/";
3824
- return `${resolvePackageAssetsBasePath(basePath, resource)}/${pkg.getName()}${resourcePath}${resource.getFile()}`;
3825
- }
3826
- function extname(fileName) {
3827
- const normalized = fileName.replace(/\\/g, "/");
3828
- const lastSlash = normalized.lastIndexOf("/");
3829
- const lastDot = normalized.lastIndexOf(".");
3830
- if (lastDot <= lastSlash) return "";
3831
- return normalized.slice(lastDot);
3832
- }
3833
- function resolvePublishedMiscFileName(resource, projectType) {
3834
- const file = resource.getFile();
3835
- if (projectType !== UNITY_PROJECT_TYPE) return file;
3836
- if (file.toLowerCase().endsWith(".atlas")) return `${file}.txt`;
3837
- return file;
3838
- }
3839
- function resolvePublishedSkeletonFileName(resource, projectType) {
3840
- if (projectType === UNITY_PROJECT_TYPE && isSpineResource(resource) && resource.getFile().toLowerCase().endsWith(".skel")) return `${resource.getFile()}.bytes`;
3841
- return resource.getFile();
3842
- }
3843
- function setPublishedFileExtra(resource, fileName) {
3844
- const extras = resource.getExtras() ?? {};
3845
- resource.setExtras({
3846
- ...extras,
3847
- _publishedFile: fileName
3848
- });
3849
- }
3850
- function setPublishedIdExtra(resource, effectiveId) {
3851
- const extras = resource.getExtras() ?? {};
3852
- if (!effectiveId || effectiveId === resource.getId()) {
3853
- if (!("_publishedId" in extras)) return;
3854
- const { _publishedId: _ignored, ...rest } = extras;
3855
- resource.setExtras(rest);
3856
- return;
3857
- }
3858
- resource.setExtras({
3859
- ...extras,
3860
- _publishedId: effectiveId
3861
- });
3862
- }
3863
- function getPublishedId(resource) {
3864
- return (resource.getExtras() ?? {})._publishedId ?? resource.getId();
3865
- }
3866
- function getBranchName(resource) {
3867
- return resource?.getBranch?.() ?? "";
3868
- }
3869
- function buildBranchResourceKey(resource) {
3870
- return `${resource.propertyType}|${resource.getPath() ?? ""}|${resource.getName() ?? ""}`;
3871
- }
3872
- const HIGH_RESOLUTION_LEVELS = [
3873
- {
3874
- scale: 2,
3875
- bit: 1,
3876
- slot: 0
3877
- },
3878
- {
3879
- scale: 3,
3880
- bit: 2,
3881
- slot: 1
3882
- },
3883
- {
3884
- scale: 4,
3885
- bit: 4,
3886
- slot: 2
3887
- }
3888
- ];
3889
- function buildHighResolutionResourceKey(resource, name = resource.getName()) {
3890
- return `${resource.propertyType}|${resource.getBranch?.() ?? ""}|${resource.getPath() ?? ""}|${name}`;
3891
- }
3892
- function isHighResolutionVariantName(name) {
3893
- return /@(?:2|3|4)x(?:\.[^./\\]+)?$/iu.test(name);
3894
- }
3895
- function appendHighResolutionScaleToName(name, scale) {
3896
- const extensionIndex = name.lastIndexOf(".");
3897
- if (extensionIndex > 0) return `${name.slice(0, extensionIndex)}@${scale}x${name.slice(extensionIndex)}`;
3898
- return `${name}@${scale}x`;
3899
- }
3900
- function trimTrailingMissingHighResolutionIds(ids) {
3901
- while (ids.length > 0 && !ids[ids.length - 1]) ids.pop();
3902
- return ids;
3903
- }
3904
- function collectHighResolutionItemIds(resources, publishedResourceIds, includeHighResolution) {
3905
- const result = /* @__PURE__ */ new Map();
3906
- if (includeHighResolution <= 0) return result;
3907
- const highResolutionResourceByKey = /* @__PURE__ */ new Map();
3908
- for (const resource of resources) {
3909
- if (!isHighResolutionResource(resource)) continue;
3910
- highResolutionResourceByKey.set(buildHighResolutionResourceKey(resource), resource);
3911
- }
3912
- for (const resource of resources) {
3913
- if (!isHighResolutionResource(resource)) continue;
3914
- if (!publishedResourceIds.has(resource.getId())) continue;
3915
- if (isHighResolutionVariantName(resource.getName())) continue;
3916
- const ids = [];
3917
- for (const level of HIGH_RESOLUTION_LEVELS) {
3918
- if ((includeHighResolution & level.bit) === 0) {
3919
- ids[level.slot] = null;
3920
- continue;
3921
- }
3922
- const highResolutionResource = highResolutionResourceByKey.get(buildHighResolutionResourceKey(resource, appendHighResolutionScaleToName(resource.getName(), level.scale)));
3923
- if (!highResolutionResource) {
3924
- ids[level.slot] = null;
3925
- continue;
3926
- }
3927
- const highResolutionId = highResolutionResource.getId();
3928
- publishedResourceIds.add(highResolutionId);
3929
- ids[level.slot] = highResolutionId;
3930
- }
3931
- trimTrailingMissingHighResolutionIds(ids);
3932
- if (ids.length > 0) result.set(resource.getId(), ids);
3933
- }
3934
- return result;
3935
- }
3936
- function collectPackagePublishContext(pkg, options) {
3937
- const pkgId = pkg.getId();
3938
- const resources = pkg.listResources();
3939
- const resourceMap = new Map(resources.map((resource) => [resource.getId(), resource]));
3940
- const referencedIds = /* @__PURE__ */ new Set();
3941
- const pixelHitTestImageIds = /* @__PURE__ */ new Set();
3942
- const spriteItemIds = /* @__PURE__ */ new Set();
3943
- const collectExportedResourceIds = (sourceResources, sourcePublishedResourceIds) => {
3944
- const exportedResourceIds = new Set(sourcePublishedResourceIds);
3945
- const resourcesById = new Map(sourceResources.map((resource) => [resource.getId(), resource]));
3946
- let changed = true;
3947
- while (changed) {
3948
- changed = false;
3949
- for (const resourceId of [...exportedResourceIds]) {
3950
- const resource = resourcesById.get(resourceId);
3951
- if (!resource || !isSkeletonResource(resource)) continue;
3952
- for (const requiredId of resource.getRequireIds()) {
3953
- if (!requiredId || exportedResourceIds.has(requiredId)) continue;
3954
- exportedResourceIds.add(requiredId);
3955
- changed = true;
3956
- }
3957
- }
3958
- }
3959
- return exportedResourceIds;
3960
- };
3961
- for (const atlas of pkg.listAtlases()) for (const sprite of atlas.listSprites()) spriteItemIds.add(sprite.getItemId());
3962
- for (const resource of resources) {
3963
- if (!isComponentResource(resource)) continue;
3964
- const component = resource;
3965
- const children = component.listChildren();
3966
- const childMap = new Map(children.map((child) => [child.getId?.() ?? "", child]));
3967
- const hitTest = component.getHitTest?.()?.trim();
3968
- if (hitTest && !hitTest.includes(",")) {
3969
- const sourceId = childMap.get(hitTest)?.getSrc?.();
3970
- if (sourceId) {
3971
- const sourceResource = resourceMap.get(sourceId);
3972
- if (sourceResource && isImageResource(sourceResource)) pixelHitTestImageIds.add(sourceId);
3973
- }
3974
- }
3975
- for (const child of children) {
3976
- const src = child.getSrc?.();
3977
- if (src) referencedIds.add(src);
3978
- addLocalFontRef(referencedIds, pkgId, child.getFont?.());
3979
- addLocalUiResourceRefsFromText(referencedIds, pkgId, child.getText?.());
3980
- for (const ref of [
3981
- child.getUrl?.(),
3982
- child.getDefaultItem?.(),
3983
- child.getIcon?.(),
3984
- child.getSelectedIcon?.(),
3985
- child.getDropdown?.(),
3986
- child.getSound?.(),
3987
- child.getInstanceSound?.(),
3988
- child.getInstanceIcon?.(),
3989
- child.getInstanceSelectedIcon?.(),
3990
- child.getVtScrollBarRes?.(),
3991
- child.getHzScrollBarRes?.(),
3992
- child.getHeaderRes?.(),
3993
- child.getFooterRes?.()
3994
- ]) addLocalUiResourceRef(referencedIds, pkgId, ref);
3995
- for (const item of child.getInstanceComboItems?.() ?? []) addLocalUiResourceRef(referencedIds, pkgId, item.icon ?? void 0);
3996
- for (const item of child.getListItems?.() ?? []) {
3997
- addLocalUiResourceRef(referencedIds, pkgId, item.icon ?? void 0);
3998
- addLocalUiResourceRef(referencedIds, pkgId, item.url ?? void 0);
3999
- }
4000
- for (const gear of child.listGears?.() ?? []) {
4001
- addLocalUiResourceRefsFromUnknown(referencedIds, pkgId, gear.getValues?.());
4002
- addLocalUiResourceRefsFromUnknown(referencedIds, pkgId, gear.getDefaultValue?.());
4003
- }
4004
- }
4005
- addLocalFontRef(referencedIds, pkgId, component.getFont?.());
4006
- for (const ref of [
4007
- component.getDropdown?.(),
4008
- component.getHeaderRes?.(),
4009
- component.getFooterRes?.(),
4010
- component.getVtScrollBarRes?.(),
4011
- component.getHzScrollBarRes?.(),
4012
- component.getSound?.()
4013
- ]) addLocalUiResourceRef(referencedIds, pkgId, ref);
4014
- for (const transition of component.listTransitions?.() ?? []) for (const item of transition.listItems?.() ?? []) {
4015
- addLocalUiResourceRefsFromUnknown(referencedIds, pkgId, item.getStartValue?.());
4016
- addLocalUiResourceRefsFromUnknown(referencedIds, pkgId, item.getEndValue?.());
4017
- }
4018
- }
4019
- const publishedResourceIds = new Set(spriteItemIds);
4020
- for (const resource of resources) {
4021
- const resourceId = resource.getId();
4022
- if (!resourceId) continue;
4023
- if (isComponentResource(resource)) {
4024
- if (resource.getExported() || referencedIds.has(resourceId)) publishedResourceIds.add(resourceId);
4025
- continue;
4026
- }
4027
- if (isImageResource(resource)) {
4028
- if (resource.getExported() || referencedIds.has(resourceId) || spriteItemIds.has(resourceId) || pixelHitTestImageIds.has(resourceId)) publishedResourceIds.add(resourceId);
4029
- continue;
4030
- }
4031
- if (isMovieClipResource(resource) || isSoundResource(resource)) {
4032
- if (resource.getExported() || referencedIds.has(resourceId)) publishedResourceIds.add(resourceId);
4033
- continue;
4034
- }
4035
- if (isMiscResource(resource) || isSkeletonResource(resource)) {
4036
- if (resource.getExported() || referencedIds.has(resourceId)) publishedResourceIds.add(resourceId);
4037
- continue;
4038
- }
4039
- if (isFontResource(resource)) {
4040
- if (resource.getExported() || referencedIds.has(resourceId)) publishedResourceIds.add(resourceId);
4041
- continue;
4042
- }
4043
- if (resource.getExported() || referencedIds.has(resourceId)) publishedResourceIds.add(resourceId);
4044
- }
4045
- for (const resourceId of collectExportedResourceIds(resources, publishedResourceIds)) publishedResourceIds.add(resourceId);
4046
- const highResolutionItemIds = collectHighResolutionItemIds(resources, publishedResourceIds, options.includeHighResolution);
4047
- if (!options.includeBranches) {
4048
- const mainByKey = /* @__PURE__ */ new Map();
4049
- const activeBranchByKey = /* @__PURE__ */ new Map();
4050
- for (const resource of resources) {
4051
- const branchName = getBranchName(resource);
4052
- const key = buildBranchResourceKey(resource);
4053
- if (!branchName) mainByKey.set(key, resource);
4054
- else if (branchName === options.activeBranch) activeBranchByKey.set(key, resource);
4055
- }
4056
- const mergedPublishedResourceIds = /* @__PURE__ */ new Set();
4057
- const effectiveResourceIds = /* @__PURE__ */ new Map();
4058
- for (const resource of resources) {
4059
- const resourceId = resource.getId();
4060
- if (!publishedResourceIds.has(resourceId)) continue;
4061
- const branchName = getBranchName(resource);
4062
- const key = buildBranchResourceKey(resource);
4063
- if (branchName) {
4064
- if (branchName !== options.activeBranch) continue;
4065
- const mainResource = mainByKey.get(key);
4066
- mergedPublishedResourceIds.add(resourceId);
4067
- effectiveResourceIds.set(resourceId, mainResource?.getId() ?? resourceId);
4068
- continue;
4069
- }
4070
- const override = activeBranchByKey.get(key);
4071
- if (override) {
4072
- mergedPublishedResourceIds.add(override.getId());
4073
- effectiveResourceIds.set(override.getId(), resourceId);
4074
- continue;
4075
- }
4076
- mergedPublishedResourceIds.add(resourceId);
4077
- effectiveResourceIds.set(resourceId, resourceId);
4078
- }
4079
- publishedResourceIds.clear();
4080
- for (const resourceId of mergedPublishedResourceIds) publishedResourceIds.add(resourceId);
4081
- const mergedPixelHitTestImageIds = /* @__PURE__ */ new Set();
4082
- for (const resource of resources) {
4083
- if (!isImageResource(resource)) continue;
4084
- const resourceId = resource.getId();
4085
- if (!publishedResourceIds.has(resourceId)) continue;
4086
- const effectiveId = effectiveResourceIds.get(resourceId) ?? resourceId;
4087
- if (pixelHitTestImageIds.has(effectiveId)) mergedPixelHitTestImageIds.add(resourceId);
4088
- }
4089
- pixelHitTestImageIds.clear();
4090
- for (const resourceId of mergedPixelHitTestImageIds) pixelHitTestImageIds.add(resourceId);
4091
- return {
4092
- referencedIds,
4093
- publishedResourceIds,
4094
- exportedResourceIds: collectExportedResourceIds(resources, publishedResourceIds),
4095
- pixelHitTestImageIds,
4096
- highResolutionItemIds,
4097
- effectiveResourceIds,
4098
- includeBranches: false
4099
- };
4100
- }
4101
- return {
4102
- referencedIds,
4103
- publishedResourceIds,
4104
- exportedResourceIds: collectExportedResourceIds(resources, publishedResourceIds),
4105
- pixelHitTestImageIds,
4106
- highResolutionItemIds,
4107
- effectiveResourceIds: new Map([...publishedResourceIds].map((resourceId) => [resourceId, resourceId])),
4108
- includeBranches: true
4109
- };
4110
- }
4111
- async function applyPixelHitTests(pkg, imageIds, basePath, encoder) {
4112
- const images = pkg.listImageResources();
4113
- for (const image of images) image.setPixelHitTestData(null);
4114
- if (!basePath || !encoder || imageIds.size === 0) return;
4115
- for (const image of images) {
4116
- const imageId = image.getId();
4117
- if (!imageIds.has(imageId)) continue;
4118
- try {
4119
- const sourcePath = resolveImagePath(image, pkg, basePath);
4120
- const metadata = await encoder(sourcePath).metadata();
4121
- if (!metadata.width || !metadata.height) continue;
4122
- const resizedWidth = Math.max(1, Math.floor(metadata.width / 2));
4123
- const resizedHeight = Math.max(1, Math.floor(metadata.height / 2));
4124
- const { data, info } = await encoder(sourcePath).ensureAlpha().resize({
4125
- width: resizedWidth,
4126
- height: resizedHeight,
4127
- fit: "fill"
4128
- }).raw().toBuffer({ resolveWithObject: true });
4129
- const pixelCount = info.width * info.height;
4130
- const maskBytes = new Uint8Array(Math.ceil(pixelCount / 8));
4131
- let byteValue = 0;
4132
- let bitIndex = 0;
4133
- let maskIndex = 0;
4134
- for (let pixel = 0; pixel < pixelCount; pixel++) {
4135
- if (data[pixel * info.channels + 3] > 10) byteValue |= 1 << bitIndex;
4136
- bitIndex++;
4137
- if (bitIndex === 8) {
4138
- maskBytes[maskIndex++] = byteValue;
4139
- bitIndex = 0;
4140
- byteValue = 0;
4141
- }
4142
- }
4143
- if (bitIndex !== 0) maskBytes[maskIndex] = byteValue;
4144
- image.setPixelHitTestData({
4145
- pixelWidth: info.width,
4146
- scaleDenominator: 2,
4147
- pixels: maskBytes
4148
- });
4149
- } catch {
4150
- image.setPixelHitTestData(null);
4151
- }
4152
- }
4153
- }
4154
- async function annotatePackagePublishArtifacts(pkg, basePath, encoder, options) {
4155
- const { publishedResourceIds, exportedResourceIds, pixelHitTestImageIds, highResolutionItemIds, effectiveResourceIds, includeBranches } = collectPackagePublishContext(pkg, options);
4156
- for (const resource of pkg.listResources()) {
4157
- setPublishedIdExtra(resource, effectiveResourceIds.get(resource.getId()) ?? null);
4158
- if (isHighResolutionResource(resource)) resource.setHighResolutionItemIds(highResolutionItemIds.get(resource.getId()) ?? []);
4159
- }
4160
- await applyPixelHitTests(pkg, pixelHitTestImageIds, basePath, encoder);
4161
- const extras = pkg.getExtras() ?? {};
4162
- pkg.setExtras({
4163
- ...extras,
4164
- publishedResourceIds: [...publishedResourceIds].sort((a, b) => a.localeCompare(b)),
4165
- exportedResourceIds: [...exportedResourceIds].sort((a, b) => a.localeCompare(b)),
4166
- publishedIncludeBranches: includeBranches,
4167
- publishedEffectiveResourceIds: Object.fromEntries(effectiveResourceIds)
4168
- });
4169
- for (const resource of pkg.listResources()) {
4170
- if (isMiscResource(resource)) {
4171
- setPublishedFileExtra(resource, resolvePublishedMiscFileName(resource, options.projectType));
4172
- continue;
4173
- }
4174
- if (isSkeletonResource(resource)) setPublishedFileExtra(resource, resolvePublishedSkeletonFileName(resource, options.projectType));
4175
- }
4176
- }
4177
- function getAnnotatedPublishedResourceIds(pkg) {
4178
- const extras = pkg.getExtras() ?? {};
4179
- return new Set(extras.publishedResourceIds ?? []);
4180
- }
4181
- function getAnnotatedExportedResourceIds(pkg) {
4182
- const extras = pkg.getExtras() ?? {};
4183
- return new Set(extras.exportedResourceIds ?? []);
4184
- }
4185
- function getPublishedSkeletonDependencyImageIds(pkg, publishedResourceIds) {
4186
- const imageIds = /* @__PURE__ */ new Set();
4187
- const resourcesById = new Map(pkg.listResources().map((resource) => [resource.getId(), resource]));
4188
- for (const resource of pkg.listResources()) {
4189
- if (!isSkeletonResource(resource)) continue;
4190
- if (!publishedResourceIds.has(resource.getId())) continue;
4191
- for (const requiredId of resource.getRequireIds()) {
4192
- if (!requiredId) continue;
4193
- const required = resourcesById.get(requiredId);
4194
- if (required && isImageResource(required)) imageIds.add(requiredId);
4195
- }
4196
- }
4197
- return imageIds;
4198
- }
4199
- async function exportPackageSounds(pkg, outputDir, basePath, fs, readFileRaw, logger) {
4200
- const publishedResourceIds = getAnnotatedPublishedResourceIds(pkg);
4201
- if (publishedResourceIds.size === 0) return;
4202
- if (!basePath || !readFileRaw) {
4203
- if (pkg.listResources().some((resource) => {
4204
- return isSoundResource(resource) && publishedResourceIds.has(resource.getId());
4205
- })) logger.warn(`publish: Sound resources in package "${pkg.getName()}" were not exported because basePath/readFileRaw is unavailable.`);
4206
- return;
4207
- }
4208
- for (const resource of pkg.listResources()) {
4209
- if (!isSoundResource(resource)) continue;
4210
- if (!publishedResourceIds.has(resource.getId())) continue;
4211
- const sourcePath = resolveSoundPath(resource, pkg, basePath);
4212
- const targetName = `${pkg.getPublishName() || pkg.getName()}_${getPublishedId(resource)}${extname(resource.getFile() || "")}`;
4213
- const targetPath = fs.join(outputDir, targetName);
4214
- try {
4215
- const data = await readFileRaw(sourcePath);
4216
- await fs.writeFileRaw(targetPath, data);
4217
- } catch {
4218
- logger.warn(`publish: Could not export sound "${resource.getId()}" from package "${pkg.getName()}".`);
4219
- }
4220
- }
4221
- }
4222
- async function exportPackageExternalResources(pkg, outputDir, basePath, fs, readFileRaw, logger) {
4223
- const exportedResourceIds = getAnnotatedExportedResourceIds(pkg);
4224
- const skeletonDependencyImageIds = getPublishedSkeletonDependencyImageIds(pkg, exportedResourceIds);
4225
- if (exportedResourceIds.size === 0) return;
4226
- if (!basePath || !readFileRaw) {
4227
- if (pkg.listResources().some((resource) => {
4228
- return (isMiscResource(resource) || isSkeletonResource(resource)) && exportedResourceIds.has(resource.getId()) || skeletonDependencyImageIds.has(resource.getId());
4229
- })) logger.warn(`publish: External resources in package "${pkg.getName()}" were not exported because basePath/readFileRaw is unavailable.`);
4230
- return;
4231
- }
4232
- for (const resource of pkg.listResources()) {
4233
- const resourceId = resource.getId();
4234
- const isSkeletonExternal = exportedResourceIds.has(resourceId) && (isMiscResource(resource) || isSkeletonResource(resource));
4235
- const isSkeletonImageDependency = skeletonDependencyImageIds.has(resourceId) && isImageResource(resource);
4236
- if (!isSkeletonExternal && !isSkeletonImageDependency) continue;
4237
- let sourcePath;
4238
- let targetName;
4239
- if (isSkeletonImageDependency) {
4240
- sourcePath = resolveImagePath(resource, pkg, basePath);
4241
- targetName = resolveImageFileName(resource);
4242
- } else if (isMiscResource(resource) || isSkeletonResource(resource)) {
4243
- sourcePath = resolveGenericResourcePath(resource, pkg, basePath);
4244
- targetName = (resource.getExtras() ?? {})._publishedFile ?? resource.getFile();
4245
- } else continue;
4246
- const targetPath = fs.join(outputDir, targetName);
4247
- try {
4248
- const data = await readFileRaw(sourcePath);
4249
- await fs.writeFileRaw(targetPath, data);
4250
- } catch {
4251
- logger.warn(`publish: Could not export external resource "${resource.getId()}" from package "${pkg.getName()}".`);
4252
- }
4253
- }
4254
- }
4255
- /**
4256
- * Publishes a FairyGUI project.
4257
- *
4258
- * Orchestrates:
4259
- * 1. Atlas packing (MaxRects layout + optional sharp compositing)
4260
- * 2. Per-package .fui binary serialization
4261
- * 3. File writing to the output directory
4262
- *
4263
- * ```ts
4264
- * import sharp from 'sharp';
4265
- * const io = new NodeIO();
4266
- * const doc = await io.readProject('./project.fairy');
4267
- *
4268
- * await doc.transform(publish({
4269
- * output: './release/',
4270
- * compressed: true,
4271
- * encoder: sharp,
4272
- * basePath: './assets/',
4273
- * fileExtension: 'bytes',
4274
- * fs: io.createFileSystem(),
4275
- * }));
4276
- * ```
4277
- */
4278
- function publish(options) {
4279
- return createTransform("publish", async (doc) => {
4280
- const resolveConfiguredOutputPath = (value, projectBasePath) => {
4281
- const trimmed = value?.trim();
4282
- if (!trimmed) return void 0;
4283
- if (isAbsolutePathLike(trimmed) || !projectBasePath) return trimTrailingSlashes(trimmed);
4284
- return trimTrailingSlashes(options.fs ? options.fs.join(projectBasePath, trimmed) : joinPathSegments(projectBasePath, trimmed));
4285
- };
4286
- const resolveProjectPublishConfig = () => {
4287
- const publishSettings = (doc.getRoot().getSettings?.() ?? {}).publish ?? {};
4288
- const resolved = resolvePublishOptions(doc, {
4289
- compressed: options.compressed,
4290
- fileExtension: options.fileExtension,
4291
- packages: options.packages,
4292
- atlas: options.atlas
4293
- });
4294
- const includeBranches = (publishSettings.branchProcessing ?? 0) === 0;
4295
- return {
4296
- ...resolved,
4297
- projectType: doc.getRoot().getProjectType(),
4298
- includeBranches,
4299
- activeBranch: includeBranches ? "" : options.branch ?? "",
4300
- includeHighResolution: publishSettings.includeHighResolution ?? 0,
4301
- separatedAtlasForBranch: includeBranches && publishSettings.seperatedAtlasForBranch === true,
4302
- globalOutputPath: publishSettings.path?.trim() ?? "",
4303
- globalBranchOutputPath: publishSettings.branchPath?.trim() ?? ""
4304
- };
4305
- };
4306
- const resolvePackagePublishPlan = (pkg, config, projectBasePath) => {
4307
- let outputDir;
4308
- if (options.output) outputDir = trimTrailingSlashes(options.output);
4309
- else {
4310
- const candidates = [];
4311
- if (!config.includeBranches && config.activeBranch) candidates.push(pkg.getPublishBranchPath(), config.globalBranchOutputPath);
4312
- candidates.push(pkg.getPublishPath(), config.globalOutputPath);
4313
- for (const candidate of candidates) {
4314
- const resolved = resolveConfiguredOutputPath(candidate, projectBasePath);
4315
- if (!resolved) continue;
4316
- outputDir = resolved;
4317
- break;
4318
- }
4319
- }
4320
- const publishName = pkg.getPublishName() || pkg.getName();
4321
- return {
4322
- pkg,
4323
- outputDir,
4324
- publishName,
4325
- fileName: resolvePublishFileName(publishName, config.fileExtension),
4326
- compressed: config.compressed,
4327
- fileExtension: config.fileExtension,
4328
- includeBranches: config.includeBranches,
4329
- activeBranch: config.activeBranch,
4330
- includeHighResolution: config.includeHighResolution,
4331
- separatedAtlasForBranch: config.separatedAtlasForBranch,
4332
- atlas: config.atlas
4333
- };
4334
- };
4335
- const createNoopPublishFs = () => ({
4336
- async writeFileRaw() {},
4337
- async mkdir() {},
4338
- join(...paths) {
4339
- return paths.join("/");
4340
- }
4341
- });
4342
- const publishPackage = async (plan, writerFs, packageIndex) => {
4343
- const atlasRuntimeOptions = resolvePublishAtlasRuntimeOptions(plan.fileExtension);
4344
- await atlas({
4345
- ...plan.atlas,
4346
- ...options.atlas ?? {},
4347
- separatedAtlasForBranch: plan.separatedAtlasForBranch,
4348
- encoder: options.encoder,
4349
- basePath: options.basePath,
4350
- outputPath: options.fs ? plan.outputDir : void 0,
4351
- mkdir: options.fs ? options.fs.mkdir : void 0,
4352
- readFileRaw: options.atlas?.readFileRaw ?? options.fs?.readFileRaw,
4353
- packages: [plan.pkg.getName()],
4354
- ...atlasRuntimeOptions
4355
- })(doc);
4356
- if (!options.fs) return;
4357
- if (!plan.outputDir) throw new Error("publish: no output directory resolved. Provide --output, or configure global publish.path / package publishPath.");
4358
- await options.fs.mkdir(plan.outputDir);
4359
- const filePath = options.fs.join(plan.outputDir, plan.fileName);
4360
- const bwOptions = {
4361
- compressed: plan.compressed,
4362
- packageIndex
4363
- };
4364
- await new BinaryWriter(writerFs).write(doc, filePath, bwOptions);
4365
- await exportPackageSounds(plan.pkg, plan.outputDir, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw, logger);
4366
- await exportPackageExternalResources(plan.pkg, plan.outputDir, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw, logger);
4367
- logger.info(`publish: Written ${plan.fileName}`);
4368
- };
4369
- const root = doc.getRoot();
4370
- const logger = doc.getLogger();
4371
- const projectBasePath = resolveProjectBasePath(options.basePath) || doc.getProjectDir?.() || "";
4372
- const pluginsDir = resolvePublishPluginsDir(doc, options);
4373
- const plugins = pluginsDir ? await loadPlugins(doc, pluginsDir) : [];
4374
- await runPublishPluginHook(plugins, "onPublishStart", doc, options);
4375
- const resolved = resolveProjectPublishConfig();
4376
- let allPackages = root.listPackages();
4377
- if (resolved.packages && resolved.packages.length > 0) {
4378
- const names = new Set(resolved.packages);
4379
- allPackages = allPackages.filter((p) => names.has(p.getName()));
4380
- }
4381
- if (allPackages.length === 0) {
4382
- logger.warn("publish: No packages to publish.");
4383
- await runPublishPluginHook(plugins, "onPublishEnd", doc, options);
4384
- return;
4385
- }
4386
- const allDocPackages = root.listPackages();
4387
- const pkgMap = /* @__PURE__ */ new Map();
4388
- for (const p of allDocPackages) pkgMap.set(p.getId(), p);
4389
- for (const pkg of allPackages) {
4390
- _computeDependencies(doc, pkg, pkgMap);
4391
- await annotatePackagePublishArtifacts(pkg, options.basePath, options.encoder, {
4392
- projectType: resolved.projectType,
4393
- includeBranches: resolved.includeBranches,
4394
- activeBranch: resolved.activeBranch,
4395
- includeHighResolution: resolved.includeHighResolution
4396
- });
4397
- }
4398
- const plans = allPackages.map((pkg) => resolvePackagePublishPlan(pkg, resolved, projectBasePath));
4399
- if (!options.fs) {
4400
- logger.info(`publish: No fs provided — layout computed for ${allPackages.length} package(s), skipping file output.`);
4401
- const noopWriterFs = toBinaryWriterFileSystem(createNoopPublishFs());
4402
- for (const plan of plans) await publishPackage(plan, noopWriterFs, allDocPackages.indexOf(plan.pkg));
4403
- await runPublishPluginHook(plugins, "onPublishEnd", doc, options);
4404
- return;
4405
- }
4406
- const unresolvedPlan = plans.find((plan) => !plan.outputDir);
4407
- if (unresolvedPlan) throw new Error(`publish: no output directory resolved for package "${unresolvedPlan.pkg.getName()}". Provide --output, or configure global publish.path / package publishPath.`);
4408
- const writerFs = toBinaryWriterFileSystem(options.fs);
4409
- for (const plan of plans) await publishPackage(plan, writerFs, allDocPackages.indexOf(plan.pkg));
4410
- await publishCodeGeneration(doc, {
4411
- basePath: options.basePath,
4412
- fs: options.fs,
4413
- packages: allPackages,
4414
- plugins
4415
- });
4416
- const publishedTargets = [...new Set(plans.map((plan) => plan.outputDir).filter((value) => Boolean(value)))];
4417
- logger.info(publishedTargets.length > 0 ? `publish: Published ${allPackages.length} package(s) to ${publishedTargets.join(", ")}` : `publish: Published ${allPackages.length} package(s)`);
4418
- await runPublishPluginHook(plugins, "onPublishEnd", doc, options);
4419
- });
4420
- }
4421
- /**
4422
- * Scan component children for font="ui://..." references to build dependency list.
4423
- * The editor only adds dependencies for packages referenced via bitmap font URLs.
4424
- * @internal
4425
- */
4426
- function _computeDependencies(doc, pkg, pkgMap) {
4427
- const referencedPkgIds = /* @__PURE__ */ new Set();
4428
- const pkgId = pkg.getId();
4429
- const packageOrder = new Map(doc.getRoot().listPackages().map((entry, index) => [entry.getId(), index]));
4430
- const addDependencyPackageId = (dependencyPkgId) => {
4431
- const normalized = dependencyPkgId?.trim() ?? "";
4432
- if (!normalized || normalized === pkgId) return;
4433
- referencedPkgIds.add(normalized);
4434
- };
4435
- const extractPackageIdFromUiUrl = (value) => {
4436
- if (!value.startsWith("ui://")) return null;
4437
- const rest = value.slice(5);
4438
- if (!rest) return null;
4439
- const slashIndex = rest.indexOf("/");
4440
- if (slashIndex >= 0) return rest.slice(0, slashIndex) || null;
4441
- if (rest.length >= 8) return rest.slice(0, 8);
4442
- return null;
4443
- };
4444
- const addDependencyPackageIdFromUiValue = (value) => {
4445
- if (!value || typeof value !== "string") return;
4446
- addDependencyPackageId(extractPackageIdFromUiUrl(value));
4447
- };
4448
- const addDependencyPackageIdsFromText = (value) => {
4449
- if (!value || typeof value !== "string") return;
4450
- const matches = value.matchAll(/ui:\/\/([0-9a-z]{8})/giu);
4451
- for (const match of matches) addDependencyPackageId(match[1] ?? "");
4452
- };
4453
- const addDependencyPackageIdsFromUnknown = (value) => {
4454
- if (Array.isArray(value)) {
4455
- for (const entry of value) addDependencyPackageIdsFromUnknown(entry);
4456
- return;
4457
- }
4458
- if (typeof value === "string") {
4459
- addDependencyPackageIdFromUiValue(value);
4460
- addDependencyPackageIdsFromText(value);
4461
- }
4462
- };
4463
- const addDependencyFontRef = (value) => {
4464
- if (Array.isArray(value)) {
4465
- for (const entry of value) addDependencyPackageIdFromUiValue(entry);
4466
- return;
4467
- }
4468
- addDependencyPackageIdFromUiValue(value ?? void 0);
4469
- };
4470
- for (const res of pkg.listResources()) {
4471
- if (res.propertyType !== "Component") continue;
4472
- const component = res;
4473
- for (const child of component.listChildren?.() ?? []) {
4474
- addDependencyPackageId(child.getPackageId?.());
4475
- addDependencyFontRef(child.getFont?.());
4476
- addDependencyPackageIdsFromText(child.getText?.());
4477
- for (const ref of [
4478
- child.getUrl?.(),
4479
- child.getDefaultItem?.(),
4480
- child.getIcon?.(),
4481
- child.getSelectedIcon?.(),
4482
- child.getDropdown?.(),
4483
- child.getSound?.(),
4484
- child.getInstanceSound?.(),
4485
- child.getInstanceIcon?.(),
4486
- child.getInstanceSelectedIcon?.(),
4487
- child.getVtScrollBarRes?.(),
4488
- child.getHzScrollBarRes?.(),
4489
- child.getHeaderRes?.(),
4490
- child.getFooterRes?.()
4491
- ]) addDependencyPackageIdFromUiValue(ref);
4492
- for (const item of child.getInstanceComboItems?.() ?? []) addDependencyPackageIdFromUiValue(item.icon ?? void 0);
4493
- for (const item of child.getListItems?.() ?? []) {
4494
- addDependencyPackageIdFromUiValue(item.icon ?? void 0);
4495
- addDependencyPackageIdFromUiValue(item.url ?? void 0);
4496
- }
4497
- for (const gear of child.listGears?.() ?? []) {
4498
- addDependencyPackageIdsFromUnknown(gear.getValues?.());
4499
- addDependencyPackageIdsFromUnknown(gear.getDefaultValue?.());
4500
- }
4501
- }
4502
- addDependencyFontRef(component.getFont?.());
4503
- for (const ref of [
4504
- component.getDropdown?.(),
4505
- component.getHeaderRes?.(),
4506
- component.getFooterRes?.(),
4507
- component.getVtScrollBarRes?.(),
4508
- component.getHzScrollBarRes?.(),
4509
- component.getSound?.()
4510
- ]) addDependencyPackageIdFromUiValue(ref);
4511
- for (const transition of component.listTransitions?.() ?? []) for (const item of transition.listItems?.() ?? []) {
4512
- addDependencyPackageIdsFromUnknown(item.getStartValue?.());
4513
- addDependencyPackageIdsFromUnknown(item.getEndValue?.());
4514
- }
4515
- }
4516
- for (const dep of pkg.listDependencies()) pkg.removeDependency(dep);
4517
- if (referencedPkgIds.size > 0) {
4518
- const sortedIds = [...referencedPkgIds].sort((a, b) => {
4519
- const orderA = packageOrder.get(a) ?? Number.MAX_SAFE_INTEGER;
4520
- const orderB = packageOrder.get(b) ?? Number.MAX_SAFE_INTEGER;
4521
- if (orderA !== orderB) return orderA - orderB;
4522
- return a.localeCompare(b);
4523
- });
4524
- for (const refId of sortedIds) {
4525
- const depPkg = pkgMap.get(refId);
4526
- if (depPkg) pkg.addDependency(depPkg);
4527
- }
4528
- }
4529
- }
4530
- //#endregion
4531
1143
  export { AUTO_GENERATED_CODE_MARK, ValidationSeverity, applyUamTransactionApp, atlas, buildCodegenClasses, createTransform, decodeText, encodeText, inspect, prune, publish, publishCodeGeneration, rename, resolvePackageCodegenPlan, resolvePublishOptions, restore, validate };