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