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