@openfairygui/functions 0.2.4 → 0.2.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfairygui/functions",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "FairyGUI Headless Authoring SDK — composable transform functions.",
5
5
  "author": "OpenFairyGUI Contributors",
6
6
  "license": "MIT",
@@ -75,7 +75,7 @@
75
75
  "dependencies": {
76
76
  "fast-xml-parser": "^5.0.0",
77
77
  "jiti": "^2.6.1",
78
- "@openfairygui/core": "0.2.4"
78
+ "@openfairygui/core": "0.2.6"
79
79
  },
80
80
  "optionalDependencies": {
81
81
  "sharp": ">=0.33.0"
@@ -50,17 +50,17 @@ async function readPluginManifest(
50
50
  fs: typeof import('node:fs/promises'),
51
51
  path: typeof import('node:path'),
52
52
  pluginDir: string,
53
- ): Promise<PluginPackageJson | null> {
53
+ ): Promise<PluginManifest | null> {
54
54
  const manifestPath = path.join(pluginDir, 'package.json');
55
55
  const content = await fs.readFile(manifestPath, 'utf-8');
56
56
  const manifest = JSON.parse(content) as PluginPackageJson;
57
57
  if (!manifest.name) throw new Error(`Codegen plugin at ${pluginDir} is missing package.json name.`);
58
58
  if (!manifest.main) throw new Error(`Codegen plugin "${manifest.name}" is missing package.json main.`);
59
- return manifest;
59
+ return manifest as PluginManifest;
60
60
  }
61
61
 
62
- function resolvePluginMain(path: typeof import('node:path'), pluginDir: string, manifest: PluginPackageJson): string {
63
- const mainPath = path.resolve(pluginDir, manifest.main!);
62
+ function resolvePluginMain(path: typeof import('node:path'), pluginDir: string, manifest: PluginManifest): string {
63
+ const mainPath = path.resolve(pluginDir, manifest.main);
64
64
  const relative = path.relative(pluginDir, mainPath);
65
65
  if (relative.startsWith('..') || path.isAbsolute(relative)) {
66
66
  throw new Error(`Codegen plugin "${manifest.name}" main must resolve inside its plugin directory.`);
@@ -19,6 +19,8 @@ export type BrowserPublishAtlasOptions = Pick<
19
19
  | 'allowRotation'
20
20
  | 'padding'
21
21
  | 'powerOfTwo'
22
+ | 'maxAtlasIndex'
23
+ | 'multipleOfFour'
22
24
  | 'square'
23
25
  | 'multiPage'
24
26
  | 'trimImage'
@@ -27,6 +27,7 @@ interface BrowserContext {
27
27
  ): void;
28
28
  fillRect(x: number, y: number, width: number, height: number): void;
29
29
  getImageData(sx: number, sy: number, sw: number, sh: number): ImageData;
30
+ putImageData(imageData: ImageData, dx: number, dy: number): void;
30
31
  rotate(angle: number): void;
31
32
  restore(): void;
32
33
  save(): void;
@@ -308,6 +309,51 @@ class BrowserImagePipeline implements AtlasRasterPipeline {
308
309
  return this;
309
310
  }
310
311
 
312
+ removeAlpha(): this {
313
+ this.raster = this.raster.then((source) => {
314
+ const context = getBrowserContext(source.canvas);
315
+ const image = context.getImageData(0, 0, source.width, source.height);
316
+ for (let index = 3; index < image.data.length; index += 4) image.data[index] = 255;
317
+ context.putImageData(image, 0, 0);
318
+ return source;
319
+ });
320
+ return this;
321
+ }
322
+
323
+ extractChannel(channel: 'alpha'): this {
324
+ if (channel !== 'alpha') throw new Error(`publishBrowser: Unsupported channel "${channel}".`);
325
+ this.raster = this.raster.then((source) => {
326
+ const context = getBrowserContext(source.canvas);
327
+ const image = context.getImageData(0, 0, source.width, source.height);
328
+ for (let index = 0; index < image.data.length; index += 4) {
329
+ const alpha = image.data[index + 3] ?? 0;
330
+ image.data[index] = alpha;
331
+ image.data[index + 1] = alpha;
332
+ image.data[index + 2] = alpha;
333
+ image.data[index + 3] = 255;
334
+ }
335
+ context.putImageData(image, 0, 0);
336
+ return source;
337
+ });
338
+ return this;
339
+ }
340
+
341
+ joinChannel(images: Uint8Array[]): this {
342
+ this.raster = Promise.all([this.raster, ...images.map((image) => this.decode(image))]).then(([source, ...channels]) => {
343
+ const context = getBrowserContext(source.canvas);
344
+ const image = context.getImageData(0, 0, source.width, source.height);
345
+ for (const [channelIndex, channel] of channels.slice(0, 2).entries()) {
346
+ const channelData = getBrowserContext(channel.canvas).getImageData(0, 0, channel.width, channel.height).data;
347
+ for (let index = 0; index < image.data.length; index += 4) {
348
+ image.data[index + channelIndex + 1] = channelData[index] ?? 0;
349
+ }
350
+ }
351
+ context.putImageData(image, 0, 0);
352
+ return source;
353
+ });
354
+ return this;
355
+ }
356
+
311
357
  resize(options: { width: number; height: number; fit?: 'fill' }): this {
312
358
  this.raster = this.raster.then((source) => {
313
359
  const target = createRaster(options.width, options.height);
@@ -1,6 +1,6 @@
1
1
  import type { Document, ILogger, Package } from '@openfairygui/core';
2
2
  import type { AtlasOptions } from '../atlas.js';
3
- import { COMPAT_NODE_RECT_FLAGS, type CompatNodeRect } from '../max-rects-compat.js';
3
+ import { COMPAT_NODE_RECT_FLAGS, type CompatNodeRect, type CompatPage } from '../max-rects-compat.js';
4
4
  import { MaxRectsPackerCompat } from '../max-rects-packer-compat.js';
5
5
  import type { AtlasRasterBackend } from '../publish/contracts.js';
6
6
  import {
@@ -289,13 +289,13 @@ function packAtlasPages(
289
289
  multipleOfFour: boolean;
290
290
  square: boolean;
291
291
  },
292
- ): ReturnType<MaxRectsPackerCompat['pack']> {
292
+ ): CompatPage[] {
293
293
  const hasDuplicatePadding = inputs.some((input) => {
294
294
  return isImageResource(input.resource) && input.resource.getDuplicatePadding?.() === true;
295
295
  });
296
296
  const packer = new MaxRectsPackerCompat({
297
297
  pot: sizeOverrides?.powerOfTwo ?? options.powerOfTwo,
298
- mof: sizeOverrides?.multipleOfFour ?? !options.powerOfTwo,
298
+ mof: sizeOverrides?.multipleOfFour ?? options.multipleOfFour,
299
299
  padding: options.padding,
300
300
  rotation: options.allowRotation,
301
301
  minWidth: 16,
@@ -433,16 +433,25 @@ async function writeAtlasPageImage(
433
433
  }
434
434
 
435
435
  const outputFile = `${options.outputPath}/${atlasFileName}`;
436
- await encoder({
436
+ const atlasPipeline = encoder({
437
437
  create: {
438
438
  width: page.width,
439
439
  height: page.height,
440
440
  channels: 4 as const,
441
441
  background: { r: 0, g: 0, b: 0, alpha: 0 },
442
442
  },
443
- })
444
- .composite(compositeInputs)
445
- .toFile(outputFile);
443
+ }).composite(compositeInputs);
444
+ if (options.extractAlpha) {
445
+ const atlasBuffer = await atlasPipeline.png().toBuffer();
446
+ await encoder(atlasBuffer).removeAlpha().png().toFile(outputFile);
447
+ const alphaBuffer = await encoder(atlasBuffer).extractChannel('alpha').png().toBuffer();
448
+ await encoder(alphaBuffer)
449
+ .joinChannel([alphaBuffer, alphaBuffer])
450
+ .png()
451
+ .toFile(`${options.outputPath}/${insertFileNameSuffix(atlasFileName, '!a')}`);
452
+ } else {
453
+ await atlasPipeline.toFile(outputFile);
454
+ }
446
455
 
447
456
  logger.info(`atlas: Generated ${atlasFileName} (${page.width}x${page.height}, ${page.outputRects.length} sprites)`);
448
457
  }
@@ -504,6 +513,9 @@ function resolveDirectOutputAtlasSize(
504
513
  if (options.powerOfTwo) {
505
514
  resolvedWidth = nextPow2(resolvedWidth);
506
515
  resolvedHeight = nextPow2(resolvedHeight);
516
+ } else if (options.multipleOfFour) {
517
+ resolvedWidth = roundUpToMultiple(resolvedWidth, 4);
518
+ resolvedHeight = roundUpToMultiple(resolvedHeight, 4);
507
519
  }
508
520
  return { width: resolvedWidth, height: resolvedHeight };
509
521
  }
@@ -663,11 +675,8 @@ export function sortResourcesByOrder(
663
675
  return ordered;
664
676
  }
665
677
 
666
- function getResourceTextureSetMode(resource: PackInputResource): TextureSetMode {
667
- if (isImageResource(resource)) {
668
- return parseTextureSetMode(resource.getTextureSetMode?.());
669
- }
670
- return parseTextureSetMode(resource.getTextureSetMode?.());
678
+ function getResourceTextureSetMode(resource: PackInputResource, maxAtlasIndex: number): TextureSetMode {
679
+ return parseTextureSetMode(resource.getTextureSetMode?.(), maxAtlasIndex);
671
680
  }
672
681
 
673
682
  function groupStandaloneInputs(
@@ -710,7 +719,7 @@ function groupStandaloneInputs(
710
719
  for (const input of inputs) {
711
720
  const branchName = getInputBranchName(input);
712
721
  const branchOrdinal = branchOrdinalByName.get(branchName) ?? 0;
713
- const mode = getResourceTextureSetMode(input.resource);
722
+ const mode = getResourceTextureSetMode(input.resource, options.maxAtlasIndex ?? 10);
714
723
  if (mode.kind === 'standalone') {
715
724
  const resourceId = getPublishedItemId(input.resource);
716
725
  const key = `${branchName}\u0000${resourceId}`;
package/src/atlas.ts CHANGED
@@ -62,6 +62,9 @@ export interface AtlasOptions {
62
62
 
63
63
  /** Constrain atlas dimensions to powers of two. Default: false. */
64
64
  powerOfTwo?: boolean;
65
+ /** Highest fixed atlas page index accepted from resource textureSetMode. Default: 10. */
66
+ maxAtlasIndex?: number;
67
+ multipleOfFour?: boolean;
65
68
 
66
69
  /** Force square atlas (width === height). Default: false. */
67
70
  square?: boolean;
@@ -151,6 +154,8 @@ const ATLAS_DEFAULTS: Required<
151
154
  allowRotation: true,
152
155
  padding: 1,
153
156
  powerOfTwo: false,
157
+ maxAtlasIndex: 10,
158
+ multipleOfFour: false,
154
159
  square: false,
155
160
  multiPage: true,
156
161
  trimImage: false,
@@ -163,7 +168,9 @@ const ATLAS_DEFAULTS: Required<
163
168
 
164
169
  interface AtlasReferenceItem {
165
170
  icon?: string | null;
171
+ selectedIcon?: string | null;
166
172
  url?: string | null;
173
+ propertyOverrides?: Array<{ value: string }>;
167
174
  }
168
175
 
169
176
  interface GearWithAtlasRefs {
@@ -183,12 +190,14 @@ interface TransitionWithAtlasRefs {
183
190
  }
184
191
 
185
192
  interface ChildWithReferenceUrls extends HasOptionalSrc, HasOptionalUrl {
193
+ getClearOnPublish?(): boolean;
186
194
  getDefaultItem?(): string;
187
195
  getIcon?(): string;
188
196
  getSelectedIcon?(): string;
189
197
  getDropdown?(): string;
190
198
  getSound?(): string;
191
199
  getText?(): string;
200
+ getAutoClearText?(): boolean;
192
201
  getFont?(): string;
193
202
  getInstanceIcon?(): string;
194
203
  getInstanceSelectedIcon?(): string;
@@ -197,7 +206,10 @@ interface ChildWithReferenceUrls extends HasOptionalSrc, HasOptionalUrl {
197
206
  getHeaderRes?(): string;
198
207
  getFooterRes?(): string;
199
208
  getInstanceComboItems?(): Array<{ icon: string | null }>;
209
+ getInstanceAutoClearItems?(): boolean;
200
210
  getListItems?(): AtlasReferenceItem[];
211
+ getAutoClearItems?(): boolean;
212
+ getPropertyOverrides?(): Array<{ value: string }>;
201
213
  listGears?(): GearWithAtlasRefs[];
202
214
  }
203
215
 
@@ -303,7 +315,7 @@ async function resolveEditorCompatibleResourceOrder(
303
315
  const refChild = child as ChildWithReferenceUrls;
304
316
  await addResource(resourceMap.get(refChild.getSrc?.() ?? ''));
305
317
  for (const ref of [
306
- refChild.getUrl?.(),
318
+ refChild.getClearOnPublish?.() ? undefined : refChild.getUrl?.(),
307
319
  refChild.getDefaultItem?.(),
308
320
  refChild.getIcon?.(),
309
321
  refChild.getSelectedIcon?.(),
@@ -319,12 +331,19 @@ async function resolveEditorCompatibleResourceOrder(
319
331
  ]) {
320
332
  await addResourceByLocalUiUrl(ref);
321
333
  }
322
- for (const item of refChild.getInstanceComboItems?.() ?? []) {
334
+ for (const item of refChild.getInstanceAutoClearItems?.() ? [] : (refChild.getInstanceComboItems?.() ?? [])) {
323
335
  await addResourceByLocalUiUrl(item.icon ?? undefined);
324
336
  }
325
- for (const item of refChild.getListItems?.() ?? []) {
337
+ for (const item of refChild.getAutoClearItems?.() ? [] : (refChild.getListItems?.() ?? [])) {
326
338
  await addResourceByLocalUiUrl(item.icon ?? undefined);
339
+ await addResourceByLocalUiUrl(item.selectedIcon ?? undefined);
327
340
  await addResourceByLocalUiUrl(item.url ?? undefined);
341
+ for (const property of item.propertyOverrides ?? []) {
342
+ await addResourceByLocalUiUrl(property.value);
343
+ }
344
+ }
345
+ for (const property of refChild.getPropertyOverrides?.() ?? []) {
346
+ await addResourceByLocalUiUrl(property.value);
328
347
  }
329
348
  for (const gear of refChild.listGears?.() ?? []) {
330
349
  await addGearIconResources(gear);
package/src/codegen.ts CHANGED
@@ -33,12 +33,13 @@ export interface ResolvedPackageCodegenPlan {
33
33
  packageFolderName: string;
34
34
  packageNamespace: string;
35
35
  binderClassName: string;
36
- settings: CliCodeGenerationSettings;
36
+ settings: Required<CliCodeGenerationSettings>;
37
37
  }
38
38
 
39
39
  interface FguiTypescriptVariant {
40
40
  binderMethod: 'setExtension';
41
41
  runtimeNamespace: 'fgui';
42
+ runtimeImport: string;
42
43
  }
43
44
 
44
45
  const FGUI_TYPESCRIPT_RUNTIME_TYPES = new Set([
@@ -65,9 +66,15 @@ const FGUI_TYPESCRIPT_RUNTIME_TYPES = new Set([
65
66
  'Transition',
66
67
  ]);
67
68
 
68
- const SHARED_FGUI_TYPESCRIPT_VARIANT: FguiTypescriptVariant = {
69
+ const LAYABOX_TYPESCRIPT_VARIANT: FguiTypescriptVariant = {
69
70
  binderMethod: 'setExtension',
70
71
  runtimeNamespace: 'fgui',
72
+ runtimeImport: '',
73
+ };
74
+
75
+ const COCOS_CREATOR_TYPESCRIPT_VARIANT: FguiTypescriptVariant = {
76
+ ...LAYABOX_TYPESCRIPT_VARIANT,
77
+ runtimeImport: 'import * as fgui from "fairygui-cc";',
71
78
  };
72
79
 
73
80
  export interface CodegenMember {
@@ -101,12 +108,14 @@ export async function publishCodeGeneration(doc: Document, options: PublishCodeG
101
108
  const settings = resolveCodeGenerationSettings(doc);
102
109
  if (!settings.allowGenCode) return;
103
110
 
104
- const plugins = options.plugins?.filter((plugin) => typeof plugin.plugin.genCode === 'function') ?? [];
111
+ const plugins = options.plugins ?? [];
105
112
  if (plugins.length > 0) {
106
113
  let handled = false;
107
114
  for (const plugin of plugins) {
115
+ const genCode = plugin.plugin.genCode;
116
+ if (!genCode) continue;
108
117
  try {
109
- await plugin.plugin.genCode(doc, settings, options);
118
+ await genCode(doc, settings, options);
110
119
  handled = true;
111
120
  logger.info(`publish: Generated code using plugin "${plugin.name}"`);
112
121
  } catch (error) {
@@ -172,7 +181,7 @@ export function resolveCodeGenerationSettings(doc: Document): Required<CliCodeGe
172
181
  };
173
182
  }
174
183
 
175
- export function resolvePackageCodegenPlan(pkg: Package, settings: CliCodeGenerationSettings, options: PublishCodeGenerationOptions): ResolvedPackageCodegenPlan | null {
184
+ export function resolvePackageCodegenPlan(pkg: Package, settings: Required<CliCodeGenerationSettings>, options: PublishCodeGenerationOptions): ResolvedPackageCodegenPlan | null {
176
185
  const rawCodePath = (pkg.getCodePath() || settings.codePath || '').trim();
177
186
  if (!rawCodePath) return null;
178
187
 
@@ -198,11 +207,11 @@ function supportsCodeGenerationLane(doc: Document, codeType: string): boolean {
198
207
  return false;
199
208
  }
200
209
 
201
- // Layabox and Cocos Creator currently share the same modern fgui TypeScript output contract.
202
210
  function resolveFguiTypescriptVariant(doc: Document): FguiTypescriptVariant | null {
203
211
  const projectType = doc.getRoot().getProjectType();
204
- if (projectType !== ProjectType.LayaBox && projectType !== ProjectType.CocosCreator) return null;
205
- return SHARED_FGUI_TYPESCRIPT_VARIANT;
212
+ if (projectType === ProjectType.LayaBox) return LAYABOX_TYPESCRIPT_VARIANT;
213
+ if (projectType === ProjectType.CocosCreator) return COCOS_CREATOR_TYPESCRIPT_VARIANT;
214
+ return null;
206
215
  }
207
216
 
208
217
  async function generateUnityCode(
@@ -528,9 +537,10 @@ function renderFguiTypescriptBinder(
528
537
  const bindLines = classes
529
538
  .map((classInfo) => `\t\t${variant.runtimeNamespace}.UIObjectFactory.${variant.binderMethod}(${classInfo.encodedClassName}.URL, ${classInfo.encodedClassName});`)
530
539
  .join('\n');
531
- const importLines = classes
540
+ const classImports = classes
532
541
  .map((classInfo) => `import ${classInfo.encodedClassName} from "./${classInfo.encodedClassName}";`)
533
542
  .join('\n');
543
+ const importLines = [variant.runtimeImport, classImports].filter(Boolean).join('\n');
534
544
 
535
545
  return renderTemplate(FGUI_TYPESCRIPT_BINDER_TEMPLATE, {
536
546
  binderClassName: plan.binderClassName,
@@ -636,6 +646,7 @@ function normalizeTypeName(value: string): string {
636
646
 
637
647
  function collectFguiTypescriptImports(classInfo: CodegenClass, variant: FguiTypescriptVariant): string {
638
648
  const imports = new Set<string>();
649
+ if (variant.runtimeImport) imports.add(variant.runtimeImport);
639
650
  for (const member of classInfo.members) {
640
651
  if (member.ignored) continue;
641
652
  const translated = translateFguiTypescriptType(member.type, variant);
@@ -65,6 +65,9 @@ export type AtlasRasterInput =
65
65
  */
66
66
  export interface AtlasRasterPipeline {
67
67
  ensureAlpha(): AtlasRasterPipeline;
68
+ removeAlpha(): AtlasRasterPipeline;
69
+ extractChannel(channel: 'alpha'): AtlasRasterPipeline;
70
+ joinChannel(images: Uint8Array[]): AtlasRasterPipeline;
68
71
  resize(options: { width: number; height: number; fit?: 'fill' }): AtlasRasterPipeline;
69
72
  raw(): AtlasRasterPipeline;
70
73
  extract(options: { left: number; top: number; width: number; height: number }): AtlasRasterPipeline;
@@ -10,6 +10,7 @@ import {
10
10
  isMiscResource,
11
11
  isSkeletonResource,
12
12
  isSoundResource,
13
+ isSwfResource,
13
14
  resolveGenericResourcePath,
14
15
  resolveImageFileName,
15
16
  resolveImagePath,
@@ -71,7 +72,7 @@ export async function exportPackageExternalResources(
71
72
  if (!basePath || !readFileRaw) {
72
73
  const hasPublishedExternal = pkg.listResources().some((resource) => {
73
74
  return (
74
- ((isMiscResource(resource) || isSkeletonResource(resource)) &&
75
+ ((isMiscResource(resource) || isSwfResource(resource) || isSkeletonResource(resource)) &&
75
76
  exportedResourceIds.has(resource.getId())) ||
76
77
  skeletonDependencyImageIds.has(resource.getId())
77
78
  );
@@ -86,20 +87,25 @@ export async function exportPackageExternalResources(
86
87
 
87
88
  for (const resource of pkg.listResources()) {
88
89
  const resourceId = resource.getId();
89
- const isSkeletonExternal =
90
- exportedResourceIds.has(resourceId) && (isMiscResource(resource) || isSkeletonResource(resource));
90
+ const isExternal =
91
+ exportedResourceIds.has(resourceId) &&
92
+ (isMiscResource(resource) || isSwfResource(resource) || isSkeletonResource(resource));
91
93
  const isSkeletonImageDependency = skeletonDependencyImageIds.has(resourceId) && isImageResource(resource);
92
- if (!isSkeletonExternal && !isSkeletonImageDependency) continue;
94
+ if (!isExternal && !isSkeletonImageDependency) continue;
93
95
 
94
96
  let sourcePath: string;
95
97
  let targetName: string;
96
98
  if (isSkeletonImageDependency) {
97
99
  sourcePath = resolveImagePath(resource, pkg, basePath);
98
100
  targetName = resolveImageFileName(resource);
99
- } else if (isMiscResource(resource) || isSkeletonResource(resource)) {
101
+ } else if (isMiscResource(resource) || isSwfResource(resource) || isSkeletonResource(resource)) {
100
102
  sourcePath = resolveGenericResourcePath(resource, pkg, basePath);
101
- targetName =
103
+ const publishedFile =
102
104
  ((resource.getExtras() as PublishFileExtras | undefined) ?? {})._publishedFile ?? resource.getFile();
105
+ targetName =
106
+ isMiscResource(resource) || isSwfResource(resource)
107
+ ? `${pkg.getPublishName() || pkg.getName()}_${publishedFile}`
108
+ : publishedFile;
103
109
  } else {
104
110
  continue;
105
111
  }
@@ -80,6 +80,8 @@ export interface ResolvedPublishAtlasOptions
80
80
  | 'allowRotation'
81
81
  | 'padding'
82
82
  | 'powerOfTwo'
83
+ | 'maxAtlasIndex'
84
+ | 'multipleOfFour'
83
85
  | 'square'
84
86
  | 'multiPage'
85
87
  | 'trimImage'
@@ -159,10 +161,14 @@ export function resolvePublishOptions(
159
161
  overrides.fileExtension ??
160
162
  (explicitLayaboxTarget ? 'fui' : resolveDefaultPublishFileExtension(projectType, publishSettings));
161
163
 
162
- let compressed = overrides.compressed ?? publishSettings.compressDesc ?? false;
163
- if (projectType === UNITY_PROJECT_TYPE) {
164
- compressed = overrides.compressed ?? false;
164
+ const runtimeRejectsCompression =
165
+ projectType === UNITY_PROJECT_TYPE || projectType === COCOS_CREATOR_PROJECT_TYPE;
166
+ if (runtimeRejectsCompression && overrides.compressed === true) {
167
+ throw new Error('publish: The selected target runtime does not support compressed package data.');
165
168
  }
169
+ const compressed = runtimeRejectsCompression
170
+ ? false
171
+ : (overrides.compressed ?? publishSettings.compressDesc ?? false);
166
172
 
167
173
  const atlasOptions: ResolvedPublishAtlasOptions = {
168
174
  maxSize: overrides.atlas?.maxSize ?? atlasSetting.maxSize ?? 2048,
@@ -171,6 +177,8 @@ export function resolvePublishOptions(
171
177
  overrides.atlas?.allowRotation ?? (explicitLayaboxTarget ? false : (atlasSetting.allowRotation ?? false)),
172
178
  padding: overrides.atlas?.padding ?? atlasSetting.padding ?? 2,
173
179
  powerOfTwo: overrides.atlas?.powerOfTwo ?? atlasSetting.sizeOption === 'pot',
180
+ maxAtlasIndex: overrides.atlas?.maxAtlasIndex ?? 10,
181
+ multipleOfFour: overrides.atlas?.multipleOfFour ?? atlasSetting.sizeOption === 'mof',
174
182
  square: overrides.atlas?.square ?? atlasSetting.forceSquare ?? false,
175
183
  multiPage: overrides.atlas?.multiPage ?? atlasSetting.paging ?? true,
176
184
  trimImage: overrides.atlas?.trimImage ?? atlasSetting.trimImage ?? false,
@@ -9,6 +9,7 @@ import {
9
9
  ProjectType,
10
10
  type SoundResource,
11
11
  type SpineResource,
12
+ type SwfResource,
12
13
  } from '@openfairygui/core';
13
14
  import type { AtlasRasterBackend } from './contracts.js';
14
15
  import { collectPackageResourceReferences } from './resource-references.js';
@@ -69,6 +70,12 @@ export function isSoundResource(resource: ReturnType<Package['listResources']>[n
69
70
  return resource.propertyType === 'SoundResource';
70
71
  }
71
72
 
73
+ export function isSwfResource(
74
+ resource: ReturnType<Package['listResources']>[number],
75
+ ): resource is SwfResource {
76
+ return resource.propertyType === 'SwfResource';
77
+ }
78
+
72
79
  function isSpineResource(resource: ReturnType<Package['listResources']>[number]): resource is SpineResource {
73
80
  return resource.propertyType === 'SpineResource';
74
81
  }
@@ -132,10 +139,15 @@ export function extname(fileName: string): string {
132
139
  }
133
140
 
134
141
  function resolvePublishedMiscFileName(resource: MiscResource, projectType: number): string {
135
- const file = resource.getFile();
136
- if (projectType !== UNITY_PROJECT_TYPE) return file;
137
- if (file.toLowerCase().endsWith('.atlas')) return `${file}.txt`;
138
- return file;
142
+ const fileName = `${getPublishedId(resource)}${extname(resource.getFile())}`;
143
+ if (projectType === UNITY_PROJECT_TYPE && fileName.toLowerCase().endsWith('.atlas')) {
144
+ return `${fileName}.txt`;
145
+ }
146
+ return fileName;
147
+ }
148
+
149
+ function resolvePublishedSwfFileName(resource: SwfResource): string {
150
+ return `${getPublishedId(resource)}${extname(resource.getFile()) || '.swf'}`;
139
151
  }
140
152
 
141
153
  function resolvePublishedSkeletonFileName(resource: SpineResource | DragonBonesResource, projectType: number): string {
@@ -235,18 +247,19 @@ function collectHighResolutionItemIds(
235
247
  resources: ReturnType<Package['listResources']>,
236
248
  publishedResourceIds: Set<string>,
237
249
  includeHighResolution: number,
250
+ excludedResourceIds: Set<string>,
238
251
  ): Map<string, Array<string | null>> {
239
252
  const result = new Map<string, Array<string | null>>();
240
253
  if (includeHighResolution <= 0) return result;
241
254
 
242
255
  const highResolutionResourceByKey = new Map<string, ImageResource | MovieClipResource>();
243
256
  for (const resource of resources) {
244
- if (!isHighResolutionResource(resource)) continue;
257
+ if (!isHighResolutionResource(resource) || excludedResourceIds.has(resource.getId())) continue;
245
258
  highResolutionResourceByKey.set(buildHighResolutionResourceKey(resource), resource);
246
259
  }
247
260
 
248
261
  for (const resource of resources) {
249
- if (!isHighResolutionResource(resource)) continue;
262
+ if (!isHighResolutionResource(resource) || excludedResourceIds.has(resource.getId())) continue;
250
263
  if (!publishedResourceIds.has(resource.getId())) continue;
251
264
  if (isHighResolutionVariantName(resource.getName())) continue;
252
265
 
@@ -290,6 +303,7 @@ function collectPackagePublishContext(
290
303
  },
291
304
  ): PackagePublishContext {
292
305
  const resources = pkg.listResources();
306
+ const excludedResourceIds = new Set(pkg.getSourceAtlasSettings().excludedResourceIds);
293
307
  const resourceMap = new Map(resources.map((resource) => [resource.getId(), resource]));
294
308
  const referencedIds = collectPackageResourceReferences(pkg).localResourceIds;
295
309
  const pixelHitTestImageIds = new Set<string>();
@@ -304,10 +318,15 @@ function collectPackagePublishContext(
304
318
  while (changed) {
305
319
  changed = false;
306
320
  for (const resourceId of [...exportedResourceIds]) {
321
+ if (excludedResourceIds.has(resourceId)) {
322
+ exportedResourceIds.delete(resourceId);
323
+ changed = true;
324
+ continue;
325
+ }
307
326
  const resource = resourcesById.get(resourceId);
308
327
  if (!resource || !isSkeletonResource(resource)) continue;
309
328
  for (const requiredId of resource.getRequireIds()) {
310
- if (!requiredId || exportedResourceIds.has(requiredId)) continue;
329
+ if (!requiredId || excludedResourceIds.has(requiredId) || exportedResourceIds.has(requiredId)) continue;
311
330
  exportedResourceIds.add(requiredId);
312
331
  changed = true;
313
332
  }
@@ -318,7 +337,7 @@ function collectPackagePublishContext(
318
337
 
319
338
  for (const atlas of pkg.listAtlases()) {
320
339
  for (const sprite of atlas.listSprites()) {
321
- spriteItemIds.add(sprite.getItemId());
340
+ if (!excludedResourceIds.has(sprite.getItemId())) spriteItemIds.add(sprite.getItemId());
322
341
  }
323
342
  }
324
343
 
@@ -331,8 +350,8 @@ function collectPackagePublishContext(
331
350
  const hitTest = component.getHitTest?.()?.trim();
332
351
  if (hitTest && !hitTest.includes(',')) {
333
352
  const targetChild = childMap.get(hitTest);
334
- const sourceId = targetChild?.getSrc?.();
335
- if (sourceId) {
353
+ const sourceId = (targetChild as { getSrc?(): string } | undefined)?.getSrc?.();
354
+ if (sourceId && !excludedResourceIds.has(sourceId)) {
336
355
  const sourceResource = resourceMap.get(sourceId);
337
356
  if (sourceResource && isImageResource(sourceResource)) {
338
357
  pixelHitTestImageIds.add(sourceId);
@@ -344,7 +363,7 @@ function collectPackagePublishContext(
344
363
  const publishedResourceIds = new Set<string>(spriteItemIds);
345
364
  for (const resource of resources) {
346
365
  const resourceId = resource.getId();
347
- if (!resourceId) continue;
366
+ if (!resourceId || excludedResourceIds.has(resourceId)) continue;
348
367
  if (isComponentResource(resource)) {
349
368
  if (resource.getExported() || referencedIds.has(resourceId)) {
350
369
  publishedResourceIds.add(resourceId);
@@ -390,6 +409,7 @@ function collectPackagePublishContext(
390
409
  resources,
391
410
  publishedResourceIds,
392
411
  options.includeHighResolution,
412
+ excludedResourceIds,
393
413
  );
394
414
 
395
415
  if (!options.includeBranches) {
@@ -576,6 +596,10 @@ export async function annotatePackagePublishArtifacts(
576
596
  setPublishedFileExtra(resource, resolvePublishedMiscFileName(resource, options.projectType));
577
597
  continue;
578
598
  }
599
+ if (isSwfResource(resource)) {
600
+ setPublishedFileExtra(resource, resolvePublishedSwfFileName(resource));
601
+ continue;
602
+ }
579
603
  if (isSkeletonResource(resource)) {
580
604
  setPublishedFileExtra(resource, resolvePublishedSkeletonFileName(resource, options.projectType));
581
605
  }