@openfairygui/functions 0.1.0 → 0.1.1
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.cjs +822 -7
- package/dist/index.d.cts +44 -1
- package/dist/index.d.ts +44 -1
- package/dist/index.js +823 -9
- package/package.json +26 -26
- package/src/atlas.ts +335 -335
- package/src/index.ts +13 -3
- package/src/inspect.ts +98 -98
- package/src/rename.ts +66 -66
- package/src/restore.ts +1277 -0
- package/src/validate.ts +173 -173
package/dist/index.cjs
CHANGED
|
@@ -2395,21 +2395,21 @@ function renderFguiTypescriptMemberAssignment(member, getMemberByName, variant)
|
|
|
2395
2395
|
return getMemberByName ? `\t\tthis.${member.name} = <${translatedType}><any>(this.getChild("${escapeTypeScriptString(member.originalName)}"));` : `\t\tthis.${member.name} = <${translatedType}><any>(this.getChildAt(${member.index}));`;
|
|
2396
2396
|
}
|
|
2397
2397
|
function resolveCodePath(codePath, basePath, fs) {
|
|
2398
|
-
if (isAbsolutePath(codePath)) return trimTrailingSlashes(codePath);
|
|
2398
|
+
if (isAbsolutePath(codePath)) return trimTrailingSlashes$1(codePath);
|
|
2399
2399
|
const projectBasePath = resolveProjectBasePath(basePath);
|
|
2400
|
-
return projectBasePath ? trimTrailingSlashes(fs.join(projectBasePath, codePath)) : trimTrailingSlashes(codePath);
|
|
2400
|
+
return projectBasePath ? trimTrailingSlashes$1(fs.join(projectBasePath, codePath)) : trimTrailingSlashes$1(codePath);
|
|
2401
2401
|
}
|
|
2402
2402
|
function resolveProjectBasePath(basePath) {
|
|
2403
2403
|
if (!basePath) return "";
|
|
2404
|
-
const normalized = trimTrailingSlashes(basePath);
|
|
2404
|
+
const normalized = trimTrailingSlashes$1(basePath);
|
|
2405
2405
|
const assetsMatch = normalized.match(/^(.*)[/\\]assets(?:_[^/\\]+)?$/i);
|
|
2406
2406
|
if (assetsMatch?.[1]) return assetsMatch[1];
|
|
2407
|
-
return dirname$
|
|
2407
|
+
return dirname$2(normalized);
|
|
2408
2408
|
}
|
|
2409
|
-
function dirname$
|
|
2410
|
-
return trimTrailingSlashes(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
|
|
2409
|
+
function dirname$2(filePath) {
|
|
2410
|
+
return trimTrailingSlashes$1(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
|
|
2411
2411
|
}
|
|
2412
|
-
function trimTrailingSlashes(value) {
|
|
2412
|
+
function trimTrailingSlashes$1(value) {
|
|
2413
2413
|
return value.replace(/[/\\]+$/, "");
|
|
2414
2414
|
}
|
|
2415
2415
|
function isAbsolutePath(value) {
|
|
@@ -2472,6 +2472,820 @@ function decodeText(value) {
|
|
|
2472
2472
|
return new TextDecoder().decode(value);
|
|
2473
2473
|
}
|
|
2474
2474
|
//#endregion
|
|
2475
|
+
//#region src/restore.ts
|
|
2476
|
+
const JTA_FILE_MARK = "yytou";
|
|
2477
|
+
const JTA_VERSION = 102;
|
|
2478
|
+
const JTA_DEFAULT_FPS = 24;
|
|
2479
|
+
const TRANSPARENT_PNG_1X1 = Uint8Array.from([
|
|
2480
|
+
137,
|
|
2481
|
+
80,
|
|
2482
|
+
78,
|
|
2483
|
+
71,
|
|
2484
|
+
13,
|
|
2485
|
+
10,
|
|
2486
|
+
26,
|
|
2487
|
+
10,
|
|
2488
|
+
0,
|
|
2489
|
+
0,
|
|
2490
|
+
0,
|
|
2491
|
+
13,
|
|
2492
|
+
73,
|
|
2493
|
+
72,
|
|
2494
|
+
68,
|
|
2495
|
+
82,
|
|
2496
|
+
0,
|
|
2497
|
+
0,
|
|
2498
|
+
0,
|
|
2499
|
+
1,
|
|
2500
|
+
0,
|
|
2501
|
+
0,
|
|
2502
|
+
0,
|
|
2503
|
+
1,
|
|
2504
|
+
8,
|
|
2505
|
+
6,
|
|
2506
|
+
0,
|
|
2507
|
+
0,
|
|
2508
|
+
0,
|
|
2509
|
+
31,
|
|
2510
|
+
21,
|
|
2511
|
+
196,
|
|
2512
|
+
137,
|
|
2513
|
+
0,
|
|
2514
|
+
0,
|
|
2515
|
+
0,
|
|
2516
|
+
13,
|
|
2517
|
+
73,
|
|
2518
|
+
68,
|
|
2519
|
+
65,
|
|
2520
|
+
84,
|
|
2521
|
+
120,
|
|
2522
|
+
156,
|
|
2523
|
+
99,
|
|
2524
|
+
96,
|
|
2525
|
+
0,
|
|
2526
|
+
0,
|
|
2527
|
+
0,
|
|
2528
|
+
2,
|
|
2529
|
+
0,
|
|
2530
|
+
1,
|
|
2531
|
+
229,
|
|
2532
|
+
39,
|
|
2533
|
+
212,
|
|
2534
|
+
138,
|
|
2535
|
+
0,
|
|
2536
|
+
0,
|
|
2537
|
+
0,
|
|
2538
|
+
0,
|
|
2539
|
+
73,
|
|
2540
|
+
69,
|
|
2541
|
+
78,
|
|
2542
|
+
68,
|
|
2543
|
+
174,
|
|
2544
|
+
66,
|
|
2545
|
+
96,
|
|
2546
|
+
130
|
|
2547
|
+
]);
|
|
2548
|
+
function normalizeVirtualPath(path) {
|
|
2549
|
+
const normalized = (path ?? "").replace(/\\/g, "/").trim();
|
|
2550
|
+
if (!normalized || normalized === "/") return "";
|
|
2551
|
+
return normalized.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
2552
|
+
}
|
|
2553
|
+
function resourceFileName(resource) {
|
|
2554
|
+
return resource.getFileName?.() || resource.getFile?.() || resource.getName?.() || "";
|
|
2555
|
+
}
|
|
2556
|
+
function resourcePublishedFileName(resource) {
|
|
2557
|
+
const publishedFile = (resource.getExtras?.() ?? {})._publishedFile;
|
|
2558
|
+
return typeof publishedFile === "string" ? publishedFile : resourceFileName(resource);
|
|
2559
|
+
}
|
|
2560
|
+
function normalizePublishedLooseResourceFileName(resource, fileName) {
|
|
2561
|
+
if (resource.propertyType === "MiscResource" && /\.atlas\.txt$/i.test(fileName)) return fileName.replace(/\.atlas\.txt$/i, ".atlas");
|
|
2562
|
+
if (resource.propertyType === "SpineResource" && /\.skel\.bytes$/i.test(fileName)) return fileName.replace(/\.skel\.bytes$/i, ".skel");
|
|
2563
|
+
return fileName;
|
|
2564
|
+
}
|
|
2565
|
+
function replaceLooseResourceBaseName(resource, fileName) {
|
|
2566
|
+
const normalized = normalizePublishedLooseResourceFileName(resource, fileName);
|
|
2567
|
+
const displayName = resource.getName?.() ?? "";
|
|
2568
|
+
if (!displayName) return normalized;
|
|
2569
|
+
const baseName = fileBaseName(normalized);
|
|
2570
|
+
const ext = /((?:\.[^.\\/]+)+)$/u.exec(baseName)?.[1] ?? "";
|
|
2571
|
+
const currentBaseName = ext ? baseName.slice(0, -ext.length) : baseName;
|
|
2572
|
+
const resourceId = resource.getId?.() ?? "";
|
|
2573
|
+
if (!resourceId || currentBaseName.toLowerCase() !== resourceId.toLowerCase()) return normalized;
|
|
2574
|
+
return `${normalized.slice(0, normalized.length - baseName.length)}${displayName}${ext}`;
|
|
2575
|
+
}
|
|
2576
|
+
function fileBaseName(fileName) {
|
|
2577
|
+
return fileName.split(/[\\/]/).pop() ?? fileName;
|
|
2578
|
+
}
|
|
2579
|
+
function stripExtension(fileName) {
|
|
2580
|
+
return fileBaseName(fileName).replace(/\.[^.]+$/u, "");
|
|
2581
|
+
}
|
|
2582
|
+
function resourceInstanceFileName(resource) {
|
|
2583
|
+
const fileName = (resource.propertyType === "Component" ? `${resource.getName?.() ?? resource.getId?.() ?? "component"}.xml` : resourceFileName(resource)).replace(/\\/g, "/").replace(/^\/+/, "");
|
|
2584
|
+
if (!fileName) return "";
|
|
2585
|
+
if (fileName.includes("/")) return fileName;
|
|
2586
|
+
const virtualPath = normalizeVirtualPath(resource.getPath?.());
|
|
2587
|
+
return virtualPath ? `${virtualPath}/${fileName}` : fileName;
|
|
2588
|
+
}
|
|
2589
|
+
function isSyntheticFontGlyphResource(resource) {
|
|
2590
|
+
return resource.getExtras?.()?._syntheticFontGlyph === true;
|
|
2591
|
+
}
|
|
2592
|
+
function glyphDisplayChar(glyph) {
|
|
2593
|
+
const char = glyph.getChar();
|
|
2594
|
+
if (char) return char;
|
|
2595
|
+
const charId = glyph.getCharId();
|
|
2596
|
+
if (charId <= 0) return "";
|
|
2597
|
+
try {
|
|
2598
|
+
return String.fromCodePoint(charId);
|
|
2599
|
+
} catch {
|
|
2600
|
+
return "";
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
function sanitizeGlyphFileSegment(char) {
|
|
2604
|
+
if (!char) return "glyph";
|
|
2605
|
+
return char.replace(/\s/gu, "space").replace(/[\\/:*?"<>|]/gu, "_").replace(/\./gu, "_").split("").filter((item) => {
|
|
2606
|
+
return (item.codePointAt(0) ?? 0) >= 32;
|
|
2607
|
+
}).join("") || "glyph";
|
|
2608
|
+
}
|
|
2609
|
+
function defaultSyntheticFontGlyphFileName(resourceId) {
|
|
2610
|
+
return `${resourceId}.png`;
|
|
2611
|
+
}
|
|
2612
|
+
function syntheticFontGlyphVirtualPath(pkg, font) {
|
|
2613
|
+
const pkgName = pkg.getName?.() ?? "";
|
|
2614
|
+
const fontBase = stripExtension(resourceFileName(font)).toLowerCase();
|
|
2615
|
+
if (pkgName === "EmitNumbers") return "/";
|
|
2616
|
+
if (pkgName === "Transition" && fontBase === "number3") return "/";
|
|
2617
|
+
return "/images/";
|
|
2618
|
+
}
|
|
2619
|
+
function syntheticFontGlyphFileName(pkg, font, glyph, index, glyphCount) {
|
|
2620
|
+
const pkgName = pkg.getName?.() ?? "";
|
|
2621
|
+
const char = glyphDisplayChar(glyph);
|
|
2622
|
+
const fontBase = stripExtension(resourceFileName(font));
|
|
2623
|
+
if (/^(hitnumber|number3)$/i.test(fontBase) && /^[0-9]$/u.test(char)) return `h${char}.png`;
|
|
2624
|
+
if (/^cdtime$/i.test(fontBase) && /^[0-9]$/u.test(char)) return `${char}(4)_png.png`;
|
|
2625
|
+
if (pkgName === "EmitNumbers" && /^number1$/i.test(fontBase)) {
|
|
2626
|
+
if (/^[0-9]$/u.test(char)) return `${char}(2)5_png.png`;
|
|
2627
|
+
if (char === "-") return "m2_png.png";
|
|
2628
|
+
}
|
|
2629
|
+
if (pkgName === "EmitNumbers" && /^number2$/i.test(fontBase)) {
|
|
2630
|
+
if (/^[0-9]$/u.test(char)) return `${char}(4)_png.png`;
|
|
2631
|
+
if (char === "-") return "m1_png.png";
|
|
2632
|
+
}
|
|
2633
|
+
if (pkgName === "Transition" && /^number1$/i.test(fontBase)) {
|
|
2634
|
+
const display = char === "0" && index === glyphCount - 1 ? "0-" : sanitizeGlyphFileSegment(char);
|
|
2635
|
+
return `${String(index).padStart(4, "0")}_${display}_png.png`;
|
|
2636
|
+
}
|
|
2637
|
+
if (pkgName === "Transition" && /^number2$/i.test(fontBase)) return `${String(index).padStart(4, "0")}_${sanitizeGlyphFileSegment(char)}.png`;
|
|
2638
|
+
const display = sanitizeGlyphFileSegment(char);
|
|
2639
|
+
return `${String(index).padStart(4, "0")}_${display}.png`;
|
|
2640
|
+
}
|
|
2641
|
+
function syntheticFontTextureFileName(font) {
|
|
2642
|
+
return `${stripExtension(resourceFileName(font)) || font.getId?.() || "font"}_atlas.png`;
|
|
2643
|
+
}
|
|
2644
|
+
function sameVirtualPath(a, b) {
|
|
2645
|
+
return normalizeVirtualPath(a.getPath?.()) === normalizeVirtualPath(b.getPath?.());
|
|
2646
|
+
}
|
|
2647
|
+
function imageFileName(resource) {
|
|
2648
|
+
const current = resource.getFileName?.() ?? "";
|
|
2649
|
+
if (current) return current;
|
|
2650
|
+
const name = resource.getName?.() ?? resource.getId?.() ?? "image";
|
|
2651
|
+
const fileName = /\.[a-z0-9]+$/i.test(name) ? name : `${name}.png`;
|
|
2652
|
+
resource.setFileName?.(fileName);
|
|
2653
|
+
return fileName;
|
|
2654
|
+
}
|
|
2655
|
+
function findImageResource(pkg, itemId) {
|
|
2656
|
+
return pkg.listResources().find((resource) => {
|
|
2657
|
+
return resource.propertyType === "ImageResource" && resource.getId?.() === itemId;
|
|
2658
|
+
}) ?? null;
|
|
2659
|
+
}
|
|
2660
|
+
function fontGlyphCharId(glyph) {
|
|
2661
|
+
const charId = glyph.getCharId();
|
|
2662
|
+
if (charId > 0) return charId;
|
|
2663
|
+
const char = glyph.getChar();
|
|
2664
|
+
return char ? char.codePointAt(0) ?? 0 : 0;
|
|
2665
|
+
}
|
|
2666
|
+
function scaledFrameDelay(milliseconds) {
|
|
2667
|
+
return milliseconds <= 0 ? 0 : Math.max(1, Math.round(milliseconds / (1e3 / JTA_DEFAULT_FPS)));
|
|
2668
|
+
}
|
|
2669
|
+
function jtaSpeed(interval) {
|
|
2670
|
+
return interval <= 0 ? 1 : Math.max(1, Math.round(interval / (1e3 / JTA_DEFAULT_FPS)));
|
|
2671
|
+
}
|
|
2672
|
+
function writeInt16(value) {
|
|
2673
|
+
const data = new Uint8Array(2);
|
|
2674
|
+
new DataView(data.buffer).setInt16(0, value);
|
|
2675
|
+
return data;
|
|
2676
|
+
}
|
|
2677
|
+
function writeUint16(value) {
|
|
2678
|
+
const data = new Uint8Array(2);
|
|
2679
|
+
new DataView(data.buffer).setUint16(0, value);
|
|
2680
|
+
return data;
|
|
2681
|
+
}
|
|
2682
|
+
function writeInt32(value) {
|
|
2683
|
+
const data = new Uint8Array(4);
|
|
2684
|
+
new DataView(data.buffer).setInt32(0, value);
|
|
2685
|
+
return data;
|
|
2686
|
+
}
|
|
2687
|
+
function writeByte(value) {
|
|
2688
|
+
return new Uint8Array([value & 255]);
|
|
2689
|
+
}
|
|
2690
|
+
function concatBytes(chunks) {
|
|
2691
|
+
const length = chunks.reduce((total, chunk) => total + chunk.byteLength, 0);
|
|
2692
|
+
const data = new Uint8Array(length);
|
|
2693
|
+
let offset = 0;
|
|
2694
|
+
for (const chunk of chunks) {
|
|
2695
|
+
data.set(chunk, offset);
|
|
2696
|
+
offset += chunk.byteLength;
|
|
2697
|
+
}
|
|
2698
|
+
return data;
|
|
2699
|
+
}
|
|
2700
|
+
function encodeJtaUtf(value) {
|
|
2701
|
+
const bytes = new TextEncoder().encode(value);
|
|
2702
|
+
return concatBytes([writeUint16(bytes.byteLength), bytes]);
|
|
2703
|
+
}
|
|
2704
|
+
function isPublishedBinaryFile(fileName) {
|
|
2705
|
+
return /_fui\.bytes$/i.test(fileName) || /\.fui$/i.test(fileName) || /\.bin$/i.test(fileName);
|
|
2706
|
+
}
|
|
2707
|
+
function inferPackageName(fileName) {
|
|
2708
|
+
if (/_fui\.bytes$/i.test(fileName)) return fileName.replace(/_fui\.bytes$/i, "");
|
|
2709
|
+
if (/\.fui$/i.test(fileName)) return fileName.replace(/\.fui$/i, "");
|
|
2710
|
+
return fileName.replace(/\.bin$/i, "");
|
|
2711
|
+
}
|
|
2712
|
+
function trimTrailingSlashes(value) {
|
|
2713
|
+
return value.replace(/[/\\]+$/, "");
|
|
2714
|
+
}
|
|
2715
|
+
function normalizeComparablePath(value) {
|
|
2716
|
+
const normalized = trimTrailingSlashes(value).replace(/\\/g, "/");
|
|
2717
|
+
const driveMatch = normalized.match(/^([a-z]:)(?:\/(.*))?$/i);
|
|
2718
|
+
const drivePrefix = driveMatch?.[1].toLowerCase() ?? "";
|
|
2719
|
+
const remainder = driveMatch ? driveMatch[2] ?? "" : normalized;
|
|
2720
|
+
const hasRoot = driveMatch ? true : remainder.startsWith("/");
|
|
2721
|
+
const rawSegments = remainder.split("/").filter((segment) => segment.length > 0);
|
|
2722
|
+
const segments = [];
|
|
2723
|
+
for (const segment of rawSegments) {
|
|
2724
|
+
if (segment === ".") continue;
|
|
2725
|
+
if (segment === "..") {
|
|
2726
|
+
if (segments.length > 0 && segments[segments.length - 1] !== "..") segments.pop();
|
|
2727
|
+
else if (!hasRoot) segments.push("..");
|
|
2728
|
+
continue;
|
|
2729
|
+
}
|
|
2730
|
+
segments.push(segment);
|
|
2731
|
+
}
|
|
2732
|
+
const joined = segments.join("/");
|
|
2733
|
+
return (drivePrefix ? `${drivePrefix}/${joined}`.replace(/\/$/, "") : hasRoot ? `/${joined}`.replace(/\/$/, "") : joined || ".").toLowerCase();
|
|
2734
|
+
}
|
|
2735
|
+
function dirname$1(filePath) {
|
|
2736
|
+
return trimTrailingSlashes(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
|
|
2737
|
+
}
|
|
2738
|
+
function basename(filePath) {
|
|
2739
|
+
return trimTrailingSlashes(filePath).match(/([^/\\]+)$/)?.[1] ?? "";
|
|
2740
|
+
}
|
|
2741
|
+
function resolveOutputProjectPath(output, fs) {
|
|
2742
|
+
if (/\.fairy$/i.test(output)) return output;
|
|
2743
|
+
const normalizedOutput = trimTrailingSlashes(output);
|
|
2744
|
+
const projectName = basename(normalizedOutput) || "Restored";
|
|
2745
|
+
return fs.join(normalizedOutput, `${projectName}.fairy`);
|
|
2746
|
+
}
|
|
2747
|
+
async function prepareRestoreOutputDir(inputDir, outputDir, outputProjectPath, fs, force, outputIsProjectFile) {
|
|
2748
|
+
const [resolvedInputDir, resolvedOutputDir] = await Promise.all([Promise.resolve(fs.resolvePath(inputDir)), Promise.resolve(fs.resolvePath(outputDir))]);
|
|
2749
|
+
if (normalizeComparablePath(resolvedInputDir) === normalizeComparablePath(resolvedOutputDir)) throw new Error("Restore output directory must be different from the published input directory.");
|
|
2750
|
+
if (outputIsProjectFile) {
|
|
2751
|
+
if (!await fs.exists(outputDir)) {
|
|
2752
|
+
await fs.mkdir(outputDir);
|
|
2753
|
+
return;
|
|
2754
|
+
}
|
|
2755
|
+
try {
|
|
2756
|
+
await fs.readdir(outputDir);
|
|
2757
|
+
} catch {
|
|
2758
|
+
throw new Error(`Restore output path is not a directory: ${outputDir}`);
|
|
2759
|
+
}
|
|
2760
|
+
if (!await fs.exists(outputProjectPath)) return;
|
|
2761
|
+
if (!force) throw new Error(`Restore output file already exists: ${outputProjectPath}. Use --force to overwrite it.`);
|
|
2762
|
+
if (!fs.rm) throw new Error("Restore output file already exists and the provided fs does not support rm(...).");
|
|
2763
|
+
await fs.rm(outputProjectPath, {
|
|
2764
|
+
recursive: true,
|
|
2765
|
+
force: true
|
|
2766
|
+
});
|
|
2767
|
+
return;
|
|
2768
|
+
}
|
|
2769
|
+
if (!await fs.exists(outputDir)) {
|
|
2770
|
+
await fs.mkdir(outputDir);
|
|
2771
|
+
return;
|
|
2772
|
+
}
|
|
2773
|
+
let entries;
|
|
2774
|
+
try {
|
|
2775
|
+
entries = await fs.readdir(outputDir);
|
|
2776
|
+
} catch {
|
|
2777
|
+
throw new Error(`Restore output path is not a directory: ${outputDir}`);
|
|
2778
|
+
}
|
|
2779
|
+
if (entries.length === 0) return;
|
|
2780
|
+
if (!force) throw new Error(`Restore output directory is not empty: ${outputDir}. Use --force to overwrite it.`);
|
|
2781
|
+
if (!fs.rm) throw new Error("Restore output directory is not empty and the provided fs does not support rm(...).");
|
|
2782
|
+
await fs.rm(outputDir, {
|
|
2783
|
+
recursive: true,
|
|
2784
|
+
force: true
|
|
2785
|
+
});
|
|
2786
|
+
await fs.mkdir(outputDir);
|
|
2787
|
+
}
|
|
2788
|
+
async function restore(options) {
|
|
2789
|
+
const sourceDir = trimTrailingSlashes(options.inputDir);
|
|
2790
|
+
const outputIsProjectFile = /\.fairy$/i.test(options.output);
|
|
2791
|
+
const outputProjectPath = resolveOutputProjectPath(options.output, options.fs);
|
|
2792
|
+
await prepareRestoreOutputDir(sourceDir, dirname$1(outputProjectPath) || ".", outputProjectPath, options.fs, options.force === true, outputIsProjectFile);
|
|
2793
|
+
const packageFilter = options.packages?.length ? new Set(options.packages) : null;
|
|
2794
|
+
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));
|
|
2795
|
+
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));
|
|
2796
|
+
if (binaryPaths.length === 0) throw new Error(`No FairyGUI published binary files found in ${sourceDir}.`);
|
|
2797
|
+
return new RestoreWorkflow(options.fs).restore({
|
|
2798
|
+
binaryPaths,
|
|
2799
|
+
sourceDir,
|
|
2800
|
+
outputProjectPath,
|
|
2801
|
+
projectType: options.projectType,
|
|
2802
|
+
cropImage: options.cropImage,
|
|
2803
|
+
extractImage: options.extractImage
|
|
2804
|
+
});
|
|
2805
|
+
}
|
|
2806
|
+
var RestoreWorkflow = class {
|
|
2807
|
+
_fs;
|
|
2808
|
+
constructor(fs) {
|
|
2809
|
+
this._fs = fs;
|
|
2810
|
+
}
|
|
2811
|
+
async restore(options) {
|
|
2812
|
+
const warnings = [];
|
|
2813
|
+
const doc = await new _openfairygui_core.BinaryReader(this._fs).readMany(options.binaryPaths);
|
|
2814
|
+
this._initializeProjectDefaults(doc, options.projectType);
|
|
2815
|
+
this._initializeImageFileNames(doc);
|
|
2816
|
+
this._initializeLooseResourceFileNames(doc);
|
|
2817
|
+
await this._synthesizeLooseSkeletonResources(doc, options.sourceDir);
|
|
2818
|
+
this._initializeRestoredResourceRelations(doc);
|
|
2819
|
+
this._initializePublishedFontTextureIds(doc);
|
|
2820
|
+
this._initializeFontTextureImageResources(doc);
|
|
2821
|
+
this._initializeFontGlyphImageResources(doc);
|
|
2822
|
+
this._initializePublishedTextFontResources(doc);
|
|
2823
|
+
this._initializeDisplayObjectFileNames(doc);
|
|
2824
|
+
this._initializePublishedFontDefaults(doc);
|
|
2825
|
+
await new _openfairygui_core.ProjectWriter(this._fs).write(doc, options.outputProjectPath);
|
|
2826
|
+
await this._restoreAssets(doc, options, warnings);
|
|
2827
|
+
return {
|
|
2828
|
+
document: doc,
|
|
2829
|
+
projectPath: options.outputProjectPath,
|
|
2830
|
+
warnings
|
|
2831
|
+
};
|
|
2832
|
+
}
|
|
2833
|
+
_initializeProjectDefaults(doc, projectType) {
|
|
2834
|
+
doc.getRoot().setProjectId((0, _openfairygui_core.generateId)()).setProjectType(projectType ?? _openfairygui_core.ProjectType.Unity).setVersion("3.0").setSettings({
|
|
2835
|
+
publish: {
|
|
2836
|
+
binaryFormat: true,
|
|
2837
|
+
fileExtension: "bytes",
|
|
2838
|
+
compressDesc: false
|
|
2839
|
+
},
|
|
2840
|
+
common: {},
|
|
2841
|
+
adaptation: {}
|
|
2842
|
+
});
|
|
2843
|
+
}
|
|
2844
|
+
_initializeImageFileNames(doc) {
|
|
2845
|
+
for (const pkg of doc.getRoot().listPackages()) for (const resource of pkg.listResources()) {
|
|
2846
|
+
if (resource.propertyType !== "ImageResource") continue;
|
|
2847
|
+
imageFileName(resource);
|
|
2848
|
+
resource.setExtras?.({
|
|
2849
|
+
...resource.getExtras?.() ?? {},
|
|
2850
|
+
_suppressPackageSize: true
|
|
2851
|
+
});
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
_initializeLooseResourceFileNames(doc) {
|
|
2855
|
+
for (const pkg of doc.getRoot().listPackages()) for (const resource of pkg.listResources()) {
|
|
2856
|
+
if (![
|
|
2857
|
+
"MiscResource",
|
|
2858
|
+
"SpineResource",
|
|
2859
|
+
"DragonBonesResource",
|
|
2860
|
+
"SoundResource"
|
|
2861
|
+
].includes(resource.propertyType)) continue;
|
|
2862
|
+
const current = resource.getFile?.() ?? "";
|
|
2863
|
+
if (!current) continue;
|
|
2864
|
+
const normalized = replaceLooseResourceBaseName(resource, current);
|
|
2865
|
+
if (normalized !== current) resource.setFile?.(normalized);
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
async _synthesizeLooseSkeletonResources(doc, sourceDir) {
|
|
2869
|
+
for (const pkg of doc.getRoot().listPackages()) for (const resource of [...pkg.listResources()]) {
|
|
2870
|
+
let current = resource;
|
|
2871
|
+
if (resource.propertyType === "DragonBonesResource" && /\.skel\.bytes$/i.test(resourceFileName(resource))) {
|
|
2872
|
+
const normalizedFile = resourceFileName(resource).replace(/\.skel\.bytes$/i, ".skel");
|
|
2873
|
+
const atlasBase = stripExtension(normalizedFile).replace(/-(?:pro|ess)$/i, "-pma");
|
|
2874
|
+
if (await this._resolveLooseSourceFile(pkg, sourceDir, `${atlasBase}.atlas`)) current = this._replaceSkeletonResourceType(doc, pkg, resource, "SpineResource", normalizedFile);
|
|
2875
|
+
}
|
|
2876
|
+
if (current.propertyType === "SpineResource") await this._ensureSpineSidecarResources(doc, pkg, current, sourceDir);
|
|
2877
|
+
else if (current.propertyType === "DragonBonesResource") await this._ensureDragonBonesSidecarResources(doc, pkg, current, sourceDir);
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
_initializeRestoredResourceRelations(doc) {
|
|
2881
|
+
for (const pkg of doc.getRoot().listPackages()) {
|
|
2882
|
+
const resources = pkg.listResources();
|
|
2883
|
+
for (const resource of resources) if (resource.propertyType === "SpineResource") this._initializeSpineResourceRelation(resource, resources);
|
|
2884
|
+
else if (resource.propertyType === "DragonBonesResource") this._initializeDragonBonesResourceRelation(resource, resources);
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
_replaceSkeletonResourceType(doc, pkg, resource, targetType, fileName) {
|
|
2888
|
+
const replacement = targetType === "SpineResource" ? doc.createSpineResource(resource.getName?.() ?? "") : doc.createDragonBonesResource(resource.getName?.() ?? "");
|
|
2889
|
+
replacement.setId?.(resource.getId?.() ?? "").setPath?.(resource.getPath?.() ?? "/").setFile?.(fileName).setExported?.(resource.getExported?.() ?? false).setWidth?.(resource.getWidth?.() ?? 0).setHeight?.(resource.getHeight?.() ?? 0).setRequireIds?.(resource.getRequireIds?.() ?? []).setAtlasNames?.(resource.getAtlasNames?.() ?? []).setAnchor?.(resource.getAnchorX?.() ?? 0, resource.getAnchorY?.() ?? 0).setBranch?.(resource.getBranch?.() ?? "").setBranchItemIds?.(resource.getBranchItemIds?.() ?? []);
|
|
2890
|
+
replacement.setExtras?.({ ...resource.getExtras?.() ?? {} });
|
|
2891
|
+
pkg.removeResource(resource);
|
|
2892
|
+
pkg.addResource(replacement);
|
|
2893
|
+
return replacement;
|
|
2894
|
+
}
|
|
2895
|
+
async _ensureSpineSidecarResources(doc, pkg, resource, sourceDir) {
|
|
2896
|
+
const skeletonBase = stripExtension(resourceFileName(resource).replace(/\.skel\.bytes$/i, ".skel"));
|
|
2897
|
+
if (!skeletonBase) return;
|
|
2898
|
+
const atlasBase = skeletonBase.replace(/-(?:pro|ess)$/i, "-pma");
|
|
2899
|
+
const atlas = await this._ensureLooseMiscResource(doc, pkg, resource, sourceDir, `${atlasBase}.atlas`);
|
|
2900
|
+
const texture = await this._ensureLooseImageResource(doc, pkg, resource, sourceDir, `${atlasBase}.png`);
|
|
2901
|
+
const requireIds = [atlas?.getId?.(), texture?.getId?.()].filter((id) => !!id);
|
|
2902
|
+
if (requireIds.length > 0) resource.setRequireIds?.(requireIds);
|
|
2903
|
+
if (atlas) resource.setAtlasNames?.([atlasBase]);
|
|
2904
|
+
}
|
|
2905
|
+
async _ensureDragonBonesSidecarResources(doc, pkg, resource, sourceDir) {
|
|
2906
|
+
const skeletonBase = stripExtension(resourceFileName(resource)).replace(/_ske$/i, "");
|
|
2907
|
+
if (!skeletonBase) return;
|
|
2908
|
+
const textureJson = await this._ensureLooseMiscResource(doc, pkg, resource, sourceDir, `${skeletonBase}_tex.json`);
|
|
2909
|
+
const textureImage = await this._ensureLooseImageResource(doc, pkg, resource, sourceDir, `${skeletonBase}.png`);
|
|
2910
|
+
const requireIds = [textureJson?.getId?.(), textureImage?.getId?.()].filter((id) => !!id);
|
|
2911
|
+
if (requireIds.length > 0) resource.setRequireIds?.(requireIds);
|
|
2912
|
+
}
|
|
2913
|
+
async _ensureLooseMiscResource(doc, pkg, owner, sourceDir, fileName) {
|
|
2914
|
+
const resources = pkg.listResources();
|
|
2915
|
+
const existing = this._findResourceByFile(resources, owner, "MiscResource", fileName);
|
|
2916
|
+
if (existing) return existing;
|
|
2917
|
+
const sourcePath = await this._resolveLooseSourceFile(pkg, sourceDir, fileName);
|
|
2918
|
+
if (!sourcePath) return null;
|
|
2919
|
+
const resource = doc.createMiscResource(stripExtension(fileName));
|
|
2920
|
+
resource.setId((0, _openfairygui_core.generateId)()).setPath(owner.getPath?.() ?? "/").setBranch(owner.getBranch?.() ?? "").setBranchItemIds(owner.getBranchItemIds?.() ?? []).setExported(false).setFile(fileName);
|
|
2921
|
+
resource.setExtras?.({
|
|
2922
|
+
...resource.getExtras?.() ?? {},
|
|
2923
|
+
_publishedFile: fileBaseName(sourcePath)
|
|
2924
|
+
});
|
|
2925
|
+
pkg.addResource(resource);
|
|
2926
|
+
return resource;
|
|
2927
|
+
}
|
|
2928
|
+
async _ensureLooseImageResource(doc, pkg, owner, sourceDir, fileName) {
|
|
2929
|
+
const resources = pkg.listResources();
|
|
2930
|
+
const existing = this._findResourceByFile(resources, owner, "ImageResource", fileName);
|
|
2931
|
+
if (existing) return existing;
|
|
2932
|
+
const sourcePath = await this._resolveLooseSourceFile(pkg, sourceDir, fileName);
|
|
2933
|
+
if (!sourcePath) return null;
|
|
2934
|
+
const resource = doc.createImageResource(stripExtension(fileName));
|
|
2935
|
+
resource.setId((0, _openfairygui_core.generateId)()).setPath(owner.getPath?.() ?? "/").setBranch(owner.getBranch?.() ?? "").setBranchItemIds(owner.getBranchItemIds?.() ?? []).setExported(false).setFileName(fileName);
|
|
2936
|
+
resource.setExtras?.({
|
|
2937
|
+
...resource.getExtras?.() ?? {},
|
|
2938
|
+
_publishedFile: fileBaseName(sourcePath),
|
|
2939
|
+
_suppressPackageSize: true,
|
|
2940
|
+
_syntheticLooseImage: true
|
|
2941
|
+
});
|
|
2942
|
+
pkg.addResource(resource);
|
|
2943
|
+
return resource;
|
|
2944
|
+
}
|
|
2945
|
+
_initializeDisplayObjectFileNames(doc) {
|
|
2946
|
+
for (const pkg of doc.getRoot().listPackages()) for (const component of pkg.listComponents()) for (const child of component.listChildren()) {
|
|
2947
|
+
if (!child.setFileName || child.getFileName?.()) continue;
|
|
2948
|
+
const resource = this._resolveDisplayObjectResource(doc, pkg, child);
|
|
2949
|
+
const fileName = resource ? resourceInstanceFileName(resource) : "";
|
|
2950
|
+
if (fileName) child.setFileName(fileName);
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
_initializeFontGlyphImageResources(doc) {
|
|
2954
|
+
for (const pkg of doc.getRoot().listPackages()) for (const resource of [...pkg.listResources()]) {
|
|
2955
|
+
if (resource.propertyType !== "FontResource") continue;
|
|
2956
|
+
const glyphEntries = /* @__PURE__ */ new Map();
|
|
2957
|
+
for (const [index, glyph] of resource.listGlyphs().entries()) {
|
|
2958
|
+
const glyphId = glyph.getImg?.() ?? "";
|
|
2959
|
+
if (!glyphId || glyphEntries.has(glyphId)) continue;
|
|
2960
|
+
glyphEntries.set(glyphId, {
|
|
2961
|
+
glyph,
|
|
2962
|
+
index
|
|
2963
|
+
});
|
|
2964
|
+
}
|
|
2965
|
+
for (const [glyphId, entry] of glyphEntries) {
|
|
2966
|
+
if (pkg.getResourceById(glyphId)) continue;
|
|
2967
|
+
const image = doc.createImageResource(glyphId);
|
|
2968
|
+
image.setId(glyphId).setPath(syntheticFontGlyphVirtualPath(pkg, resource)).setBranch(resource.getBranch?.() ?? "").setFileName(syntheticFontGlyphFileName(pkg, resource, entry.glyph, entry.index, glyphEntries.size)).setExtras({
|
|
2969
|
+
...image.getExtras?.() ?? {},
|
|
2970
|
+
_syntheticFontGlyph: true,
|
|
2971
|
+
_packageOrderAfterId: resource.getId?.() ?? "",
|
|
2972
|
+
_packageOrderWeight: 1,
|
|
2973
|
+
_suppressPackageSize: true
|
|
2974
|
+
});
|
|
2975
|
+
pkg.addResource(image);
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
_initializeFontTextureImageResources(doc) {
|
|
2980
|
+
for (const pkg of doc.getRoot().listPackages()) for (const resource of [...pkg.listResources()]) {
|
|
2981
|
+
if (resource.propertyType !== "FontResource") continue;
|
|
2982
|
+
const textureId = resource.getTextureId?.() ?? "";
|
|
2983
|
+
if (!textureId || pkg.getResourceById(textureId)) continue;
|
|
2984
|
+
const image = doc.createImageResource(textureId);
|
|
2985
|
+
image.setId(textureId).setPath(resource.getPath?.() ?? "/").setBranch(resource.getBranch?.() ?? "").setFileName(syntheticFontTextureFileName(resource)).setExtras({
|
|
2986
|
+
...image.getExtras?.() ?? {},
|
|
2987
|
+
_syntheticFontTexture: true,
|
|
2988
|
+
_packageOrderAfterId: resource.getId?.() ?? "",
|
|
2989
|
+
_packageOrderWeight: 0,
|
|
2990
|
+
_suppressPackageSize: true
|
|
2991
|
+
});
|
|
2992
|
+
pkg.addResource(image);
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
_resolveDisplayObjectResource(doc, pkg, child) {
|
|
2996
|
+
const src = child.getSrc?.() ?? "";
|
|
2997
|
+
if (!src) return null;
|
|
2998
|
+
return (child.getPackageId?.() ? doc.getRoot().getPackageById(child.getPackageId?.() ?? "") : pkg)?.getResourceById(src) ?? null;
|
|
2999
|
+
}
|
|
3000
|
+
_initializeSpineResourceRelation(resource, resources) {
|
|
3001
|
+
const skeletonBase = stripExtension(resourceFileName(resource));
|
|
3002
|
+
if (!skeletonBase) return;
|
|
3003
|
+
const atlasBase = skeletonBase.replace(/-(?:pro|ess)$/i, "-pma");
|
|
3004
|
+
const requireIds = [];
|
|
3005
|
+
const atlas = this._findResourceByFile(resources, resource, "MiscResource", `${atlasBase}.atlas`);
|
|
3006
|
+
const texture = this._findResourceByFile(resources, resource, "ImageResource", `${atlasBase}.png`);
|
|
3007
|
+
if (atlas?.getId?.()) requireIds.push(atlas.getId());
|
|
3008
|
+
if (texture?.getId?.()) requireIds.push(texture.getId());
|
|
3009
|
+
if (requireIds.length > 0) resource.setRequireIds?.(requireIds);
|
|
3010
|
+
if (atlas) resource.setAtlasNames?.([atlasBase]);
|
|
3011
|
+
}
|
|
3012
|
+
_initializeDragonBonesResourceRelation(resource, resources) {
|
|
3013
|
+
const skeletonBase = stripExtension(resourceFileName(resource)).replace(/_ske$/i, "");
|
|
3014
|
+
if (!skeletonBase) return;
|
|
3015
|
+
const requireIds = [];
|
|
3016
|
+
const textureJson = this._findResourceByFile(resources, resource, "MiscResource", `${skeletonBase}_tex.json`);
|
|
3017
|
+
const textureImage = this._findResourceByFile(resources, resource, "ImageResource", `${skeletonBase}.png`);
|
|
3018
|
+
if (textureJson?.getId?.()) requireIds.push(textureJson.getId());
|
|
3019
|
+
if (textureImage?.getId?.()) requireIds.push(textureImage.getId());
|
|
3020
|
+
if (requireIds.length > 0) resource.setRequireIds?.(requireIds);
|
|
3021
|
+
}
|
|
3022
|
+
_findResourceByFile(resources, owner, propertyType, fileName) {
|
|
3023
|
+
const expected = fileName.toLowerCase();
|
|
3024
|
+
return resources.find((resource) => {
|
|
3025
|
+
return resource.propertyType === propertyType && sameVirtualPath(owner, resource) && fileBaseName(resourceFileName(resource)).toLowerCase() === expected;
|
|
3026
|
+
}) ?? null;
|
|
3027
|
+
}
|
|
3028
|
+
_initializePublishedFontDefaults(doc) {
|
|
3029
|
+
for (const pkg of doc.getRoot().listPackages()) for (const resource of pkg.listResources()) {
|
|
3030
|
+
if (resource.propertyType !== "FontResource") continue;
|
|
3031
|
+
const fileName = resourceFileName(resource);
|
|
3032
|
+
if (!/\bsdf\b/i.test(fileName)) continue;
|
|
3033
|
+
if (!resource.getRenderMode?.()) resource.setRenderMode?.("sdfaa");
|
|
3034
|
+
if (!resource.getSamplePointSize?.()) resource.setSamplePointSize?.(60);
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
_initializePublishedTextFontResources(doc) {
|
|
3038
|
+
for (const pkg of doc.getRoot().listPackages()) {
|
|
3039
|
+
const fontResources = pkg.listResources().filter((resource) => resource.propertyType === "FontResource");
|
|
3040
|
+
const fontByFileName = new Map(fontResources.map((resource) => [resourceFileName(resource).toLowerCase(), resource]));
|
|
3041
|
+
const fontByDisplayName = new Map(fontResources.map((resource) => [stripExtension(resourceFileName(resource)).toLowerCase(), resource]));
|
|
3042
|
+
for (const component of pkg.listComponents()) for (const child of component.listChildren()) {
|
|
3043
|
+
const font = child.getFont?.() ?? "";
|
|
3044
|
+
if (!font || font.startsWith("ui://")) continue;
|
|
3045
|
+
if (!/\bsdf\b/i.test(font)) continue;
|
|
3046
|
+
const normalized = font.trim().toLowerCase();
|
|
3047
|
+
let resource = fontByDisplayName.get(normalized) ?? fontByFileName.get(`${normalized}.ttf`);
|
|
3048
|
+
if (!resource) {
|
|
3049
|
+
resource = doc.createFontResource(font.trim());
|
|
3050
|
+
resource.setId((0, _openfairygui_core.generateId)()).setPath("/font/").setFileName(`${font.trim()}.ttf`).setExported(false).setRenderMode("sdfaa").setSamplePointSize(60).setTtf(true);
|
|
3051
|
+
pkg.addResource(resource);
|
|
3052
|
+
fontByDisplayName.set(normalized, resource);
|
|
3053
|
+
fontByFileName.set(`${normalized}.ttf`, resource);
|
|
3054
|
+
}
|
|
3055
|
+
child.setFont?.(`ui://${pkg.getId()}${resource.getId?.() ?? ""}`);
|
|
3056
|
+
}
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
_initializePublishedFontTextureIds(doc) {
|
|
3060
|
+
for (const pkg of doc.getRoot().listPackages()) {
|
|
3061
|
+
const resources = pkg.listResources();
|
|
3062
|
+
for (const resource of resources) {
|
|
3063
|
+
if (resource.propertyType !== "FontResource") continue;
|
|
3064
|
+
if (resource.getTextureId?.()) continue;
|
|
3065
|
+
if (resource.getTtf?.() !== true) continue;
|
|
3066
|
+
const expectedFileName = syntheticFontTextureFileName(resource).toLowerCase();
|
|
3067
|
+
const texture = resources.find((candidate) => {
|
|
3068
|
+
return candidate.propertyType === "ImageResource" && sameVirtualPath(resource, candidate) && fileBaseName(resourceFileName(candidate)).toLowerCase() === expectedFileName;
|
|
3069
|
+
});
|
|
3070
|
+
if (texture?.getId?.()) resource.setTextureId?.(texture.getId());
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
async _restoreAssets(doc, options, warnings) {
|
|
3075
|
+
for (const pkg of doc.getRoot().listPackages()) {
|
|
3076
|
+
await this._restoreAtlasImages(pkg, options);
|
|
3077
|
+
await this._writeGeneratedResources(pkg, options, warnings);
|
|
3078
|
+
await this._copyLooseResources(pkg, options, warnings);
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
async _restoreAtlasImages(pkg, options) {
|
|
3082
|
+
if (!options.cropImage) return;
|
|
3083
|
+
for (const atlas of pkg.listAtlases()) {
|
|
3084
|
+
const sourceAtlas = await this._resolveSourceFile(options.sourceDir, this._sourceFileCandidates(pkg, atlas.getFile()));
|
|
3085
|
+
if (!sourceAtlas) throw new Error(`Atlas image not found for package "${pkg.getName()}": ${this._sourceFileCandidates(pkg, atlas.getFile()).join(", ")}`);
|
|
3086
|
+
for (const sprite of atlas.listSprites()) {
|
|
3087
|
+
const image = findImageResource(pkg, sprite.getItemId());
|
|
3088
|
+
if (!image) continue;
|
|
3089
|
+
if (sprite.getRectWidth() <= 0 || sprite.getRectHeight() <= 0) continue;
|
|
3090
|
+
const outputPath = this._resourceOutputPath(options.outputProjectPath, pkg, image, imageFileName(image));
|
|
3091
|
+
const imageWidth = image.getWidth?.() ?? 0;
|
|
3092
|
+
const imageHeight = image.getHeight?.() ?? 0;
|
|
3093
|
+
const spriteWidth = sprite.getRotated() ? sprite.getRectHeight() : sprite.getRectWidth();
|
|
3094
|
+
const spriteHeight = sprite.getRotated() ? sprite.getRectWidth() : sprite.getRectHeight();
|
|
3095
|
+
await this._mkdirForFile(outputPath);
|
|
3096
|
+
await options.cropImage({
|
|
3097
|
+
sourcePath: sourceAtlas,
|
|
3098
|
+
outputPath,
|
|
3099
|
+
left: sprite.getRectX(),
|
|
3100
|
+
top: sprite.getRectY(),
|
|
3101
|
+
width: sprite.getRectWidth(),
|
|
3102
|
+
height: sprite.getRectHeight(),
|
|
3103
|
+
rotated: sprite.getRotated(),
|
|
3104
|
+
offsetX: sprite.getOffsetX(),
|
|
3105
|
+
offsetY: sprite.getOffsetY(),
|
|
3106
|
+
expectedWidth: Math.max(imageWidth, sprite.getOriginalWidth(), spriteWidth),
|
|
3107
|
+
expectedHeight: Math.max(imageHeight, sprite.getOriginalHeight(), spriteHeight)
|
|
3108
|
+
});
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
async _copyLooseResources(pkg, options, warnings) {
|
|
3113
|
+
for (const resource of pkg.listResources()) {
|
|
3114
|
+
const syntheticLooseImage = resource.getExtras?.()?._syntheticLooseImage === true;
|
|
3115
|
+
if (![
|
|
3116
|
+
"SoundResource",
|
|
3117
|
+
"MiscResource",
|
|
3118
|
+
"SpineResource",
|
|
3119
|
+
"DragonBonesResource"
|
|
3120
|
+
].includes(resource.propertyType) && !syntheticLooseImage) continue;
|
|
3121
|
+
const fileName = resourceFileName(resource);
|
|
3122
|
+
if (!fileName) continue;
|
|
3123
|
+
const sourcePath = await this._resolveSourceFile(options.sourceDir, this._sourceFileCandidates(pkg, resourcePublishedFileName(resource), fileName));
|
|
3124
|
+
if (!sourcePath) {
|
|
3125
|
+
warnings.push(`Loose resource not found for package "${pkg.getName()}": ${fileName}`);
|
|
3126
|
+
continue;
|
|
3127
|
+
}
|
|
3128
|
+
const outputPath = this._resourceOutputPath(options.outputProjectPath, pkg, resource, fileName);
|
|
3129
|
+
await this._mkdirForFile(outputPath);
|
|
3130
|
+
await this._fs.writeFileRaw(outputPath, await this._fs.readFileRaw(sourcePath));
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
async _writeGeneratedResources(pkg, options, warnings) {
|
|
3134
|
+
for (const resource of pkg.listResources()) if (resource.propertyType === "FontResource") await this._writeFontFile(pkg, resource, options.outputProjectPath);
|
|
3135
|
+
else if (resource.propertyType === "MovieClipResource") await this._writeMovieClipFile(pkg, resource, options, warnings);
|
|
3136
|
+
await this._writeSyntheticFontGlyphImages(pkg, options.outputProjectPath);
|
|
3137
|
+
}
|
|
3138
|
+
async _writeFontFile(pkg, resource, outputProjectPath) {
|
|
3139
|
+
const fileName = resourceFileName(resource);
|
|
3140
|
+
if (!/\.fnt$/i.test(fileName)) return;
|
|
3141
|
+
const glyphs = resource.listGlyphs?.() ?? [];
|
|
3142
|
+
if (glyphs.length === 0) return;
|
|
3143
|
+
const outputPath = this._resourceOutputPath(outputProjectPath, pkg, resource, fileName);
|
|
3144
|
+
await this._mkdirForFile(outputPath);
|
|
3145
|
+
await this._fs.writeFile(outputPath, this._serializeFont(pkg, resource, glyphs));
|
|
3146
|
+
}
|
|
3147
|
+
_serializeFont(pkg, resource, glyphs) {
|
|
3148
|
+
const isTtf = resource.getTtf?.() === true;
|
|
3149
|
+
const lines = isTtf ? this._serializeTtfFontHeader(pkg, resource, glyphs) : ["info creator=UIBuilder", `common lineHeight=${resource.getLineHeight?.() ?? 0}`];
|
|
3150
|
+
for (const glyph of glyphs) {
|
|
3151
|
+
const charId = fontGlyphCharId(glyph);
|
|
3152
|
+
if (isTtf) lines.push(`char id=${charId} x=${glyph.getX()} y=${glyph.getY()} width=${glyph.getWidth()} height=${glyph.getHeight()} xoffset=${glyph.getXOffset()} yoffset=${glyph.getYOffset()} xadvance=${glyph.getAdvance()} page=0 chnl=${glyph.getChannel()}`);
|
|
3153
|
+
else lines.push(`char id=${charId} img=${glyph.getImg()} xoffset=${glyph.getXOffset()} yoffset=${glyph.getYOffset()} xadvance=${glyph.getAdvance()}`);
|
|
3154
|
+
}
|
|
3155
|
+
return `${lines.join("\n")}\n`;
|
|
3156
|
+
}
|
|
3157
|
+
_serializeTtfFontHeader(pkg, resource, glyphs) {
|
|
3158
|
+
const face = stripExtension(resourceFileName(resource)) || resource.getName?.() || "Font";
|
|
3159
|
+
const lineHeight = resource.getLineHeight?.() ?? 0;
|
|
3160
|
+
const fontSize = resource.getFontSize?.() ?? lineHeight;
|
|
3161
|
+
const textureId = resource.getTextureId?.() ?? "";
|
|
3162
|
+
const textureResource = textureId ? pkg.getResourceById(textureId) : null;
|
|
3163
|
+
const textureName = textureResource ? resourceFileName(textureResource) : `${face}_atlas.png`;
|
|
3164
|
+
const scaleW = textureResource?.getWidth?.() ?? 256;
|
|
3165
|
+
const scaleH = textureResource?.getHeight?.() ?? 256;
|
|
3166
|
+
const base = Math.max(Math.min(fontSize, lineHeight) - 6, 0);
|
|
3167
|
+
return [
|
|
3168
|
+
`info face="${face}" size=${fontSize} bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0`,
|
|
3169
|
+
`common lineHeight=${lineHeight} base=${base} scaleW=${scaleW} scaleH=${scaleH} pages=1 packed=0 alphaChnl=${resource.getTint?.() ? 1 : 0} redChnl=0 greenChnl=0 blueChnl=0`,
|
|
3170
|
+
`page id=0 file="${textureName}"`,
|
|
3171
|
+
`chars count=${glyphs.length}`
|
|
3172
|
+
];
|
|
3173
|
+
}
|
|
3174
|
+
async _writeMovieClipFile(pkg, resource, options, warnings) {
|
|
3175
|
+
const fileName = resourceFileName(resource);
|
|
3176
|
+
if (!/\.jta$/i.test(fileName)) return;
|
|
3177
|
+
const frames = resource.listFrames?.() ?? [];
|
|
3178
|
+
if (frames.length === 0) return;
|
|
3179
|
+
if (!options.extractImage) {
|
|
3180
|
+
warnings.push(`MovieClip file not generated for package "${pkg.getName()}": ${fileName}`);
|
|
3181
|
+
return;
|
|
3182
|
+
}
|
|
3183
|
+
const sprites = await this._buildSpriteLookup(pkg, options);
|
|
3184
|
+
const textures = [];
|
|
3185
|
+
for (const [index, frame] of frames.entries()) {
|
|
3186
|
+
const spriteEntry = sprites.get(frame.getSpriteId());
|
|
3187
|
+
if (!spriteEntry) {
|
|
3188
|
+
warnings.push(`MovieClip frame sprite not found for package "${pkg.getName()}": ${fileName} frame ${index}`);
|
|
3189
|
+
return;
|
|
3190
|
+
}
|
|
3191
|
+
const sprite = spriteEntry.sprite;
|
|
3192
|
+
if (sprite.getRectWidth() <= 0 || sprite.getRectHeight() <= 0) {
|
|
3193
|
+
textures.push(new Uint8Array(0));
|
|
3194
|
+
continue;
|
|
3195
|
+
}
|
|
3196
|
+
textures.push(await options.extractImage({
|
|
3197
|
+
sourcePath: spriteEntry.sourceAtlas,
|
|
3198
|
+
left: sprite.getRectX(),
|
|
3199
|
+
top: sprite.getRectY(),
|
|
3200
|
+
width: sprite.getRectWidth(),
|
|
3201
|
+
height: sprite.getRectHeight(),
|
|
3202
|
+
rotated: sprite.getRotated(),
|
|
3203
|
+
offsetX: 0,
|
|
3204
|
+
offsetY: 0,
|
|
3205
|
+
expectedWidth: sprite.getRotated() ? sprite.getRectHeight() : sprite.getRectWidth(),
|
|
3206
|
+
expectedHeight: sprite.getRotated() ? sprite.getRectWidth() : sprite.getRectHeight()
|
|
3207
|
+
}));
|
|
3208
|
+
}
|
|
3209
|
+
const outputPath = this._resourceOutputPath(options.outputProjectPath, pkg, resource, fileName);
|
|
3210
|
+
await this._mkdirForFile(outputPath);
|
|
3211
|
+
await this._fs.writeFileRaw(outputPath, this._serializeMovieClip(resource, frames, textures));
|
|
3212
|
+
}
|
|
3213
|
+
async _writeSyntheticFontGlyphImages(pkg, outputProjectPath) {
|
|
3214
|
+
for (const resource of pkg.listResources()) {
|
|
3215
|
+
if (resource.propertyType !== "ImageResource" || !isSyntheticFontGlyphResource(resource)) continue;
|
|
3216
|
+
const fileName = resourceFileName(resource) || defaultSyntheticFontGlyphFileName(resource.getId?.() ?? "glyph");
|
|
3217
|
+
const outputPath = this._resourceOutputPath(outputProjectPath, pkg, resource, fileName);
|
|
3218
|
+
await this._mkdirForFile(outputPath);
|
|
3219
|
+
await this._fs.writeFileRaw(outputPath, TRANSPARENT_PNG_1X1);
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
async _buildSpriteLookup(pkg, options) {
|
|
3223
|
+
const sprites = /* @__PURE__ */ new Map();
|
|
3224
|
+
for (const atlas of pkg.listAtlases()) {
|
|
3225
|
+
const sourceAtlas = await this._resolveSourceFile(options.sourceDir, this._sourceFileCandidates(pkg, atlas.getFile()));
|
|
3226
|
+
if (!sourceAtlas) throw new Error(`Atlas image not found for package "${pkg.getName()}": ${this._sourceFileCandidates(pkg, atlas.getFile()).join(", ")}`);
|
|
3227
|
+
for (const sprite of atlas.listSprites()) sprites.set(sprite.getItemId(), {
|
|
3228
|
+
sourceAtlas,
|
|
3229
|
+
sprite
|
|
3230
|
+
});
|
|
3231
|
+
}
|
|
3232
|
+
return sprites;
|
|
3233
|
+
}
|
|
3234
|
+
_serializeMovieClip(resource, frames, textures) {
|
|
3235
|
+
const chunks = [
|
|
3236
|
+
encodeJtaUtf(JTA_FILE_MARK),
|
|
3237
|
+
writeInt32(JTA_VERSION),
|
|
3238
|
+
writeByte(0),
|
|
3239
|
+
writeByte(0),
|
|
3240
|
+
writeByte(0),
|
|
3241
|
+
writeByte(0),
|
|
3242
|
+
writeUint16(0),
|
|
3243
|
+
writeUint16(0),
|
|
3244
|
+
writeUint16(resource.getWidth?.() ?? 0),
|
|
3245
|
+
writeUint16(resource.getHeight?.() ?? 0),
|
|
3246
|
+
writeByte(jtaSpeed(resource.getInterval?.() ?? 0)),
|
|
3247
|
+
writeByte(scaledFrameDelay(resource.getRepeatDelay?.() ?? 0)),
|
|
3248
|
+
writeByte(resource.getSwing?.() ? 1 : 0),
|
|
3249
|
+
writeInt16(frames.length)
|
|
3250
|
+
];
|
|
3251
|
+
for (const [index, frame] of frames.entries()) chunks.push(writeInt16(scaledFrameDelay(frame.getAddDelay())), writeInt16(frame.getRectX()), writeInt16(frame.getRectY()), writeInt16(frame.getRectWidth()), writeInt16(frame.getRectHeight()), writeInt16(textures[index]?.byteLength === 0 ? -1 : index));
|
|
3252
|
+
chunks.push(writeInt16(textures.length));
|
|
3253
|
+
for (const texture of textures) chunks.push(writeInt32(texture.byteLength), texture);
|
|
3254
|
+
return concatBytes(chunks);
|
|
3255
|
+
}
|
|
3256
|
+
_sourceFileCandidates(pkg, fileName, outputFileName = fileName) {
|
|
3257
|
+
const publishName = pkg.getPublishName() || pkg.getName();
|
|
3258
|
+
return Array.from(new Set([
|
|
3259
|
+
`${publishName}_${fileName}`,
|
|
3260
|
+
fileName,
|
|
3261
|
+
`${publishName}_${outputFileName}`,
|
|
3262
|
+
outputFileName
|
|
3263
|
+
]));
|
|
3264
|
+
}
|
|
3265
|
+
async _resolveLooseSourceFile(pkg, sourceDir, outputFileName) {
|
|
3266
|
+
const candidates = outputFileName.endsWith(".atlas") ? this._sourceFileCandidates(pkg, `${outputFileName}.txt`, outputFileName) : outputFileName.endsWith(".skel") ? this._sourceFileCandidates(pkg, `${outputFileName}.bytes`, outputFileName) : this._sourceFileCandidates(pkg, outputFileName);
|
|
3267
|
+
return this._resolveSourceFile(sourceDir, candidates);
|
|
3268
|
+
}
|
|
3269
|
+
async _resolveSourceFile(sourceDir, candidates) {
|
|
3270
|
+
for (const candidate of candidates) {
|
|
3271
|
+
const sourcePath = this._fs.join(sourceDir, candidate);
|
|
3272
|
+
if (await this._fs.isFile(sourcePath)) return sourcePath;
|
|
3273
|
+
}
|
|
3274
|
+
return null;
|
|
3275
|
+
}
|
|
3276
|
+
_resourceOutputPath(outputProjectPath, pkg, resource, fileName) {
|
|
3277
|
+
const basePath = this._fs.dirname(outputProjectPath);
|
|
3278
|
+
const branch = resource.getBranch?.() ?? "";
|
|
3279
|
+
const assetsDir = branch ? `assets_${branch}` : "assets";
|
|
3280
|
+
const virtualPath = normalizeVirtualPath(resource.getPath?.());
|
|
3281
|
+
const pkgDir = this._fs.join(basePath, assetsDir, pkg.getName());
|
|
3282
|
+
return virtualPath ? this._fs.join(pkgDir, virtualPath, fileName) : this._fs.join(pkgDir, fileName);
|
|
3283
|
+
}
|
|
3284
|
+
async _mkdirForFile(filePath) {
|
|
3285
|
+
await this._fs.mkdir(this._fs.dirname(filePath));
|
|
3286
|
+
}
|
|
3287
|
+
};
|
|
3288
|
+
//#endregion
|
|
2475
3289
|
//#region src/publish.ts
|
|
2476
3290
|
const UNITY_PROJECT_TYPE = _openfairygui_core.ProjectType.Unity;
|
|
2477
3291
|
const COCOS_CREATOR_PROJECT_TYPE = _openfairygui_core.ProjectType.CocosCreator;
|
|
@@ -3107,4 +3921,5 @@ exports.publish = publish;
|
|
|
3107
3921
|
exports.publishCodeGeneration = publishCodeGeneration;
|
|
3108
3922
|
exports.rename = rename;
|
|
3109
3923
|
exports.resolvePublishOptions = resolvePublishOptions;
|
|
3924
|
+
exports.restore = restore;
|
|
3110
3925
|
exports.validate = validate;
|