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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/atlas.ts CHANGED
@@ -1,6 +1,20 @@
1
- import { GearType, TransitionActionType, type Component, type Document, type DragonBonesResource, type FontResource, type ILogger, type ImageResource, type MovieClipResource, type Package, type SpineResource, type Transform } from '@openfairygui/core';
1
+ import {
2
+ GearType,
3
+ TransitionActionType,
4
+ type Component,
5
+ type Document,
6
+ type DragonBonesResource,
7
+ type FontResource,
8
+ type ILogger,
9
+ type ImageResource,
10
+ type MovieClipResource,
11
+ type Package,
12
+ type SpineResource,
13
+ type Transform,
14
+ } from '@openfairygui/core';
2
15
  import { COMPAT_NODE_RECT_FLAGS, type CompatNodeRect } from './max-rects-compat.js';
3
16
  import { MaxRectsPackerCompat } from './max-rects-packer-compat.js';
17
+ import type { AtlasRasterBackend, AtlasRasterInput, AtlasRasterResolvedBuffer } from './publish/contracts.js';
4
18
  import type { ExtrasMap, HasOptionalSrc, HasOptionalUrl } from './shared-types.js';
5
19
  import { createTransform, parseTextureSetMode, type TextureSetMode } from './utils.js';
6
20
 
@@ -12,7 +26,7 @@ export interface AtlasOptions {
12
26
  packages?: string[];
13
27
 
14
28
  /**
15
- * Sharp module instance, injected by the caller.
29
+ * Raster backend, injected by the host adapter.
16
30
  * Required for actual image compositing and trimImage.
17
31
  *
18
32
  * ```ts
@@ -20,7 +34,7 @@ export interface AtlasOptions {
20
34
  * await doc.transform(atlas({ encoder: sharp }));
21
35
  * ```
22
36
  */
23
- encoder?: unknown;
37
+ encoder?: AtlasRasterBackend;
24
38
 
25
39
  /** Maximum atlas texture size (width and height). Default: 2048. */
26
40
  maxSize?: number;
@@ -45,7 +59,7 @@ export interface AtlasOptions {
45
59
 
46
60
  /**
47
61
  * Trim transparent pixels from image edges before packing.
48
- * Requires encoder (sharp). Stores offset/originalSize in Sprite nodes.
62
+ * Requires a raster backend. Stores offset/originalSize in Sprite nodes.
49
63
  * Default: false.
50
64
  */
51
65
  trimImage?: boolean;
@@ -98,10 +112,11 @@ export interface AtlasOptions {
98
112
  * separate atlas pages/files per branch instead of mixing them with main.
99
113
  */
100
114
  separatedAtlasForBranch?: boolean;
101
-
102
115
  }
103
116
 
104
- const ATLAS_DEFAULTS: Required<Omit<AtlasOptions, 'packages' | 'encoder' | 'basePath' | 'outputPath' | 'mkdir' | 'readFileRaw'>> = {
117
+ const ATLAS_DEFAULTS: Required<
118
+ Omit<AtlasOptions, 'packages' | 'encoder' | 'basePath' | 'outputPath' | 'mkdir' | 'readFileRaw'>
119
+ > = {
105
120
  maxSize: 2048,
106
121
  fast: true,
107
122
  allowRotation: true,
@@ -206,26 +221,6 @@ interface BranchAtlasGroup {
206
221
  inputs: InputItem[];
207
222
  }
208
223
 
209
- interface AtlasEncoderMetadata {
210
- width?: number;
211
- height?: number;
212
- channels?: number;
213
- hasAlpha?: boolean;
214
- trimOffsetLeft?: number;
215
- trimOffsetTop?: number;
216
- }
217
-
218
- interface AtlasEncoderResolvedBuffer {
219
- data: Uint8Array;
220
- info: Required<Pick<AtlasEncoderMetadata, 'width' | 'height' | 'channels'>> & AtlasEncoderMetadata;
221
- }
222
-
223
- interface AtlasCompositeInput {
224
- input: Uint8Array;
225
- left: number;
226
- top: number;
227
- }
228
-
229
224
  function getSelectedSkeletonDependencyImageIds(resources: PackageResource[]): Set<string> {
230
225
  const imageIds = new Set<string>();
231
226
  const resourcesById = new Map(resources.map((resource) => [resource.getId(), resource] as const));
@@ -240,35 +235,6 @@ function getSelectedSkeletonDependencyImageIds(resources: PackageResource[]): Se
240
235
  return imageIds;
241
236
  }
242
237
 
243
- interface AtlasEncoderPipeline {
244
- ensureAlpha(): AtlasEncoderPipeline;
245
- resize(width: number, height: number, options?: { fit?: 'fill' }): AtlasEncoderPipeline;
246
- raw(): AtlasEncoderPipeline;
247
- extract(options: { left: number; top: number; width: number; height: number }): AtlasEncoderPipeline;
248
- toBuffer(options: { resolveWithObject: true }): Promise<AtlasEncoderResolvedBuffer>;
249
- toBuffer(options?: { resolveWithObject?: false }): Promise<Uint8Array>;
250
- toBuffer(options?: { resolveWithObject?: boolean }): Promise<Uint8Array | AtlasEncoderResolvedBuffer>;
251
- png(): AtlasEncoderPipeline;
252
- metadata(): Promise<AtlasEncoderMetadata>;
253
- rotate(angle: number): AtlasEncoderPipeline;
254
- composite(inputs: AtlasCompositeInput[]): AtlasEncoderPipeline;
255
- toFile(path: string): Promise<unknown>;
256
- }
257
-
258
- type AtlasEncoderInput =
259
- | string
260
- | Uint8Array
261
- | {
262
- create: {
263
- width: number;
264
- height: number;
265
- channels: 4;
266
- background: { r: number; g: number; b: number; alpha: number };
267
- };
268
- };
269
-
270
- type AtlasEncoder = (input: AtlasEncoderInput) => AtlasEncoderPipeline;
271
-
272
238
  function resolveFontFileName(fontName: string): string {
273
239
  return /\.fnt$/i.test(fontName) ? fontName : `${fontName}.fnt`;
274
240
  }
@@ -303,7 +269,9 @@ async function resolveEditorCompatibleResourceOrder(
303
269
  const imgMatch = line.match(/\bimg=(\w+)/);
304
270
  if (imgMatch) await addResource(resourceMap.get(imgMatch[1] ?? ''));
305
271
  }
306
- } catch { /* ignore */ }
272
+ } catch {
273
+ /* ignore */
274
+ }
307
275
  }
308
276
  }
309
277
  if (isComponentResource(resource)) {
@@ -392,7 +360,9 @@ async function resolveEditorCompatibleResourceOrder(
392
360
  ]) {
393
361
  await addResourceByLocalUiUrl(ref);
394
362
  }
395
- for (const transition of (component as Component & { listTransitions?(): TransitionWithAtlasRefs[] }).listTransitions?.() ?? []) {
363
+ for (const transition of (
364
+ component as Component & { listTransitions?(): TransitionWithAtlasRefs[] }
365
+ ).listTransitions?.() ?? []) {
396
366
  for (const item of transition.listItems?.() ?? []) {
397
367
  const actionType = item.getActionType?.();
398
368
  if (actionType !== TransitionActionType.Sound && actionType !== TransitionActionType.Icon) continue;
@@ -421,7 +391,7 @@ async function resolveEditorCompatibleResourceOrder(
421
391
  *
422
392
  * This transform performs MaxRects bin-packing on all ImageResource items
423
393
  * within each package, creating Atlas and Sprite property nodes. When an
424
- * `encoder` (sharp) is provided, it also composites the actual PNG files.
394
+ * a raster backend is provided, it also composites the actual PNG files.
425
395
  *
426
396
  * When `trimImage` is enabled and encoder is available, transparent pixels
427
397
  * at image edges are trimmed before packing. The trimmed offset and original
@@ -444,17 +414,20 @@ export function atlas(_options: AtlasOptions = {}): Transform {
444
414
  return createTransform('atlas', async (doc: Document): Promise<void> => {
445
415
  const root = doc.getRoot();
446
416
  const logger = doc.getLogger();
447
- const encoder = options.encoder as AtlasEncoder | undefined;
417
+ const encoder = options.encoder;
448
418
  const doTrim = options.trimImage && !!encoder && !!options.basePath;
449
419
  const packageFilter = options.packages ? new Set(options.packages) : null;
450
420
 
451
421
  for (const pkg of root.listPackages()) {
452
422
  if (packageFilter && !packageFilter.has(pkg.getName())) continue;
453
423
  // Respect publish-selected resources when publish() precomputes a merged branch view.
454
- const selectedPublishIds = new Set(((pkg.getExtras() as PackageAtlasExtras | undefined) ?? {}).publishedResourceIds ?? []);
455
- const allResources = selectedPublishIds.size > 0
456
- ? pkg.listResources().filter((resource) => selectedPublishIds.has(resource.getId()))
457
- : pkg.listResources();
424
+ const selectedPublishIds = new Set(
425
+ ((pkg.getExtras() as PackageAtlasExtras | undefined) ?? {}).publishedResourceIds ?? [],
426
+ );
427
+ const allResources =
428
+ selectedPublishIds.size > 0
429
+ ? pkg.listResources().filter((resource) => selectedPublishIds.has(resource.getId()))
430
+ : pkg.listResources();
458
431
  const skeletonDependencyImageIds = getSelectedSkeletonDependencyImageIds(allResources);
459
432
  // Process resources in declaration order (matching editor behavior)
460
433
  const orderedResources = await resolveEditorCompatibleResourceOrder(pkg, allResources, options);
@@ -517,7 +490,9 @@ export function atlas(_options: AtlasOptions = {}): Transform {
517
490
  addUiResourceRefsFromUnknown(referencedIds, gear.getDefaultValue?.());
518
491
  }
519
492
  }
520
- for (const transition of (component as Component & { listTransitions?(): TransitionWithAtlasRefs[] }).listTransitions?.() ?? []) {
493
+ for (const transition of (
494
+ component as Component & { listTransitions?(): TransitionWithAtlasRefs[] }
495
+ ).listTransitions?.() ?? []) {
521
496
  for (const item of transition.listItems?.() ?? []) {
522
497
  addUiResourceRefsFromUnknown(referencedIds, item.getStartValue?.());
523
498
  addUiResourceRefsFromUnknown(referencedIds, item.getEndValue?.());
@@ -549,7 +524,9 @@ export function atlas(_options: AtlasOptions = {}): Transform {
549
524
  const match = line.match(/img=(\w+)/);
550
525
  if (match) referencedIds.add(match[1]);
551
526
  }
552
- } catch { /* .fnt file not found — OK */ }
527
+ } catch {
528
+ /* .fnt file not found — OK */
529
+ }
553
530
  }
554
531
  }
555
532
  }
@@ -559,15 +536,33 @@ export function atlas(_options: AtlasOptions = {}): Transform {
559
536
  // Pack referenced images, plus explicitly exported standalone images.
560
537
  const resId = res.getId();
561
538
  if (skeletonDependencyImageIds.has(resId)) continue;
562
- if (selectedPublishIds.size === 0 && !res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
539
+ if (
540
+ selectedPublishIds.size === 0 &&
541
+ !res.getExported() &&
542
+ referencedIds.size > 0 &&
543
+ !referencedIds.has(resId)
544
+ )
545
+ continue;
563
546
  await _collectImage(res, pkg, inputs, encoder, options, doTrim, logger);
564
547
  } else if (isMovieClipResource(res)) {
565
548
  const resId = res.getId();
566
- if (selectedPublishIds.size === 0 && !res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
549
+ if (
550
+ selectedPublishIds.size === 0 &&
551
+ !res.getExported() &&
552
+ referencedIds.size > 0 &&
553
+ !referencedIds.has(resId)
554
+ )
555
+ continue;
567
556
  await _collectMovieClipFrames(doc, res, pkg, inputs, encoder, options, logger);
568
557
  } else if (isFontResource(res)) {
569
558
  const resId = res.getId();
570
- if (selectedPublishIds.size === 0 && !res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
559
+ if (
560
+ selectedPublishIds.size === 0 &&
561
+ !res.getExported() &&
562
+ referencedIds.size > 0 &&
563
+ !referencedIds.has(resId)
564
+ )
565
+ continue;
571
566
  await _collectFontTexture(doc, res, pkg, options);
572
567
  }
573
568
  }
@@ -575,16 +570,30 @@ export function atlas(_options: AtlasOptions = {}): Transform {
575
570
  if (inputs.length === 0) continue;
576
571
  let totalPageCount = 0;
577
572
  let usedDirectOutput = false;
578
- const { autoInputs, fixedPageGroups, standaloneGroups, reservedPageIndexes } = groupStandaloneInputs(doc, inputs, options);
573
+ const { autoInputs, fixedPageGroups, standaloneGroups, reservedPageIndexes } = groupStandaloneInputs(
574
+ doc,
575
+ inputs,
576
+ options,
577
+ );
579
578
  const branchGroups = buildBranchAtlasGroups(doc, autoInputs, options);
580
579
  const branchPageOffsets = new Map<number, number>();
581
580
 
582
581
  for (const group of branchGroups) {
583
- const directOutput = fixedPageGroups.length === 0 && standaloneGroups.length === 0
584
- ? resolveDirectImageOutput(group.inputs, options)
585
- : null;
582
+ const directOutput =
583
+ fixedPageGroups.length === 0 && standaloneGroups.length === 0
584
+ ? resolveDirectImageOutput(group.inputs, options)
585
+ : null;
586
586
  if (directOutput) {
587
- await emitDirectImageOutput(doc, pkg, directOutput, encoder, options, logger, group.branchName, group.branchOrdinal);
587
+ await emitDirectImageOutput(
588
+ doc,
589
+ pkg,
590
+ directOutput,
591
+ encoder,
592
+ options,
593
+ logger,
594
+ group.branchName,
595
+ group.branchOrdinal,
596
+ );
588
597
  usedDirectOutput = true;
589
598
  totalPageCount += 1;
590
599
  continue;
@@ -632,13 +641,18 @@ export function atlas(_options: AtlasOptions = {}): Transform {
632
641
  logger,
633
642
  });
634
643
  totalPageCount += emittedPageCount;
635
- standalonePageOffsets.set(group.branchOrdinal, (standalonePageOffsets.get(group.branchOrdinal) ?? 0) + emittedPageCount);
644
+ standalonePageOffsets.set(
645
+ group.branchOrdinal,
646
+ (standalonePageOffsets.get(group.branchOrdinal) ?? 0) + emittedPageCount,
647
+ );
636
648
  }
637
649
 
638
650
  if (usedDirectOutput) {
639
651
  logger.info(`atlas: Direct output for single image package "${pkg.getName()}".`);
640
652
  }
641
- logger.info(`atlas: Packed ${inputs.length} images into ${totalPageCount} atlas(es) for package "${pkg.getName()}".`);
653
+ logger.info(
654
+ `atlas: Packed ${inputs.length} images into ${totalPageCount} atlas(es) for package "${pkg.getName()}".`,
655
+ );
642
656
  }
643
657
  });
644
658
  }
@@ -648,14 +662,17 @@ function buildBranchAtlasGroups(doc: Document, inputs: InputItem[], options: Atl
648
662
  return [{ branchName: '', branchOrdinal: 0, inputs }];
649
663
  }
650
664
 
651
- const discoveredBranchNames = [...new Set(inputs
652
- .map((input) => getInputBranchName(input))
653
- .filter((branchName) => !!branchName))];
665
+ const discoveredBranchNames = [
666
+ ...new Set(inputs.map((input) => getInputBranchName(input)).filter((branchName) => !!branchName)),
667
+ ];
654
668
  if (discoveredBranchNames.length === 0) {
655
669
  return [{ branchName: '', branchOrdinal: 0, inputs }];
656
670
  }
657
671
 
658
- const orderedBranchNames = doc.getRoot().listBranches().filter((branchName) => discoveredBranchNames.includes(branchName));
672
+ const orderedBranchNames = doc
673
+ .getRoot()
674
+ .listBranches()
675
+ .filter((branchName) => discoveredBranchNames.includes(branchName));
659
676
  for (const branchName of discoveredBranchNames) {
660
677
  if (!orderedBranchNames.includes(branchName)) orderedBranchNames.push(branchName);
661
678
  }
@@ -709,7 +726,7 @@ async function emitPagedAtlasGroup(
709
726
  pageStart: number;
710
727
  fileNameAt: (pageIndex: number) => string;
711
728
  options: AtlasOptions;
712
- encoder: AtlasEncoder | undefined;
729
+ encoder: AtlasRasterBackend | undefined;
713
730
  logger: ILogger;
714
731
  forceSinglePage?: boolean;
715
732
  },
@@ -729,7 +746,15 @@ async function emitPagedAtlasGroup(
729
746
  pkg.addAtlas(atlasNode);
730
747
 
731
748
  attachSpritesToAtlas(doc, allResources, inputs, page.outputRects, atlasNode);
732
- await writeAtlasPageImage(pkg, inputs, page, atlasNode.getFile(), context.encoder, context.options, context.logger);
749
+ await writeAtlasPageImage(
750
+ pkg,
751
+ inputs,
752
+ page,
753
+ atlasNode.getFile(),
754
+ context.encoder,
755
+ context.options,
756
+ context.logger,
757
+ );
733
758
  }
734
759
 
735
760
  return pages.length;
@@ -742,7 +767,7 @@ async function emitStandaloneAtlasGroup(
742
767
  context: {
743
768
  atlasIndexStart: number;
744
769
  options: AtlasOptions;
745
- encoder: AtlasEncoder | undefined;
770
+ encoder: AtlasRasterBackend | undefined;
746
771
  logger: ILogger;
747
772
  },
748
773
  ): Promise<number> {
@@ -874,9 +899,13 @@ function attachSpritesToAtlas(
874
899
  async function writeAtlasPageImage(
875
900
  pkg: Package,
876
901
  inputs: InputItem[],
877
- page: { width: number; height: number; outputRects: Array<{ index: number; x: number; y: number; width: number; height: number; rotated: boolean }> },
902
+ page: {
903
+ width: number;
904
+ height: number;
905
+ outputRects: Array<{ index: number; x: number; y: number; width: number; height: number; rotated: boolean }>;
906
+ },
878
907
  atlasFileName: string,
879
- encoder: AtlasEncoder | undefined,
908
+ encoder: AtlasRasterBackend | undefined,
880
909
  options: AtlasOptions,
881
910
  logger: ILogger,
882
911
  ): Promise<void> {
@@ -948,7 +977,12 @@ function inputToCompatRect(input: InputItem, index: number): CompatNodeRect {
948
977
  };
949
978
  }
950
979
 
951
- function resolvePackedRectSize(input: InputItem, width: number, height: number, rectRotated: boolean): { width: number; height: number } {
980
+ function resolvePackedRectSize(
981
+ input: InputItem,
982
+ width: number,
983
+ height: number,
984
+ rectRotated: boolean,
985
+ ): { width: number; height: number } {
952
986
  if (!rectRotated) return { width, height };
953
987
  return {
954
988
  width: input.height,
@@ -968,7 +1002,11 @@ function resolveDirectImageOutput(inputs: InputItem[], options: AtlasOptions): I
968
1002
  return input;
969
1003
  }
970
1004
 
971
- function resolveDirectOutputAtlasSize(width: number, height: number, options: AtlasOptions): { width: number; height: number } {
1005
+ function resolveDirectOutputAtlasSize(
1006
+ width: number,
1007
+ height: number,
1008
+ options: AtlasOptions,
1009
+ ): { width: number; height: number } {
972
1010
  let resolvedWidth = width;
973
1011
  let resolvedHeight = height;
974
1012
  if (options.square) {
@@ -987,7 +1025,7 @@ async function emitDirectImageOutput(
987
1025
  doc: Document,
988
1026
  pkg: Package,
989
1027
  input: InputItem,
990
- encoder: AtlasEncoder | undefined,
1028
+ encoder: AtlasRasterBackend | undefined,
991
1029
  options: AtlasOptions,
992
1030
  logger: ILogger,
993
1031
  branchName: string = '',
@@ -1060,11 +1098,7 @@ function resolveAtlasOutputFileName(pkg: Package, pageIndex: number, branchName:
1060
1098
  return `${pkg.getPublishName() || pkg.getName()}_atlas${pageIndex}${suffix}.png`;
1061
1099
  }
1062
1100
 
1063
- function resolveStandaloneAtlasOutputFileName(
1064
- pkg: Package,
1065
- resource: PackInputResource,
1066
- branchName: string,
1067
- ): string {
1101
+ function resolveStandaloneAtlasOutputFileName(pkg: Package, resource: PackInputResource, branchName: string): string {
1068
1102
  const baseName = `${pkg.getPublishName() || pkg.getName()}_atlas_${getPublishedItemId(resource)}`;
1069
1103
  const suffix = branchName ? `_${branchName}` : '';
1070
1104
  if (isImageResource(resource)) {
@@ -1130,11 +1164,23 @@ function sortResourcesByOrder(
1130
1164
  ordered.sort((left, right) => {
1131
1165
  const leftId = left.getId();
1132
1166
  const rightId = right.getId();
1133
- const leftOrder = leftId && orderMap.has(leftId) ? (orderMap.get(leftId) ?? Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER;
1134
- const rightOrder = rightId && orderMap.has(rightId) ? (orderMap.get(rightId) ?? Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER;
1167
+ const leftOrder =
1168
+ leftId && orderMap.has(leftId)
1169
+ ? (orderMap.get(leftId) ?? Number.MAX_SAFE_INTEGER)
1170
+ : Number.MAX_SAFE_INTEGER;
1171
+ const rightOrder =
1172
+ rightId && orderMap.has(rightId)
1173
+ ? (orderMap.get(rightId) ?? Number.MAX_SAFE_INTEGER)
1174
+ : Number.MAX_SAFE_INTEGER;
1135
1175
  if (leftOrder !== rightOrder) return leftOrder - rightOrder;
1136
- const leftInputOrder = leftId && inputOrderMap.has(leftId) ? (inputOrderMap.get(leftId) ?? Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER;
1137
- const rightInputOrder = rightId && inputOrderMap.has(rightId) ? (inputOrderMap.get(rightId) ?? Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER;
1176
+ const leftInputOrder =
1177
+ leftId && inputOrderMap.has(leftId)
1178
+ ? (inputOrderMap.get(leftId) ?? Number.MAX_SAFE_INTEGER)
1179
+ : Number.MAX_SAFE_INTEGER;
1180
+ const rightInputOrder =
1181
+ rightId && inputOrderMap.has(rightId)
1182
+ ? (inputOrderMap.get(rightId) ?? Number.MAX_SAFE_INTEGER)
1183
+ : Number.MAX_SAFE_INTEGER;
1138
1184
  if (leftInputOrder !== rightInputOrder) return leftInputOrder - rightInputOrder;
1139
1185
  return (leftId ?? '').localeCompare(rightId ?? '');
1140
1186
  });
@@ -1185,10 +1231,13 @@ function groupStandaloneInputs(
1185
1231
  const fixedInputsByPage = new Map<string, PagedAtlasGroup>();
1186
1232
  const standaloneGroups = new Map<string, StandaloneAtlasGroup>();
1187
1233
  const reservedPageIndexes = new Set<number>();
1188
- const discoveredBranchNames = [...new Set(inputs
1189
- .map((input) => getInputBranchName(input))
1190
- .filter((branchName) => !!branchName))];
1191
- const orderedBranchNames = doc.getRoot().listBranches().filter((branchName) => discoveredBranchNames.includes(branchName));
1234
+ const discoveredBranchNames = [
1235
+ ...new Set(inputs.map((input) => getInputBranchName(input)).filter((branchName) => !!branchName)),
1236
+ ];
1237
+ const orderedBranchNames = doc
1238
+ .getRoot()
1239
+ .listBranches()
1240
+ .filter((branchName) => discoveredBranchNames.includes(branchName));
1192
1241
  for (const branchName of discoveredBranchNames) {
1193
1242
  if (!orderedBranchNames.includes(branchName)) orderedBranchNames.push(branchName);
1194
1243
  }
@@ -1247,34 +1296,31 @@ function groupStandaloneInputs(
1247
1296
 
1248
1297
  return {
1249
1298
  autoInputs,
1250
- fixedPageGroups: [...fixedInputsByPage.values()].sort((left, right) =>
1251
- left.branchOrdinal - right.branchOrdinal
1252
- || left.pageIndex - right.pageIndex,
1299
+ fixedPageGroups: [...fixedInputsByPage.values()].sort(
1300
+ (left, right) => left.branchOrdinal - right.branchOrdinal || left.pageIndex - right.pageIndex,
1253
1301
  ),
1254
- standaloneGroups: [...standaloneGroups.values()].sort((left, right) =>
1255
- left.branchOrdinal - right.branchOrdinal
1256
- || getPublishedItemId(left.resource).localeCompare(getPublishedItemId(right.resource)),
1302
+ standaloneGroups: [...standaloneGroups.values()].sort(
1303
+ (left, right) =>
1304
+ left.branchOrdinal - right.branchOrdinal ||
1305
+ getPublishedItemId(left.resource).localeCompare(getPublishedItemId(right.resource)),
1257
1306
  ),
1258
1307
  reservedPageIndexes,
1259
1308
  };
1260
1309
  }
1261
1310
 
1262
1311
  /**
1263
- * Trim transparent edges from an image using sharp.
1312
+ * Trim transparent edges from an image using the host raster backend.
1264
1313
  * Returns the trimmed buffer, dimensions, and offsets.
1265
1314
  * Falls back to the original image if trim fails (e.g. no alpha channel, no transparent edges).
1266
1315
  */
1267
1316
  async function _trimImage(
1268
- encoder: AtlasEncoder,
1269
- input: AtlasEncoderInput,
1317
+ encoder: AtlasRasterBackend,
1318
+ input: AtlasRasterInput,
1270
1319
  originalWidth: number,
1271
1320
  originalHeight: number,
1272
1321
  ): Promise<TrimInfo> {
1273
1322
  try {
1274
- const trimResult = await encoder(input)
1275
- .ensureAlpha()
1276
- .raw()
1277
- .toBuffer({ resolveWithObject: true });
1323
+ const trimResult = await encoder(input).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
1278
1324
  if (!isResolvedBuffer(trimResult)) {
1279
1325
  throw new Error('atlas: encoder raw alpha trim did not return resolved metadata.');
1280
1326
  }
@@ -1362,10 +1408,16 @@ function _resolveImagePath(resource: ImageResource, pkg: Package, basePath: stri
1362
1408
  }
1363
1409
 
1364
1410
  type InputItem = {
1365
- id: string; width: number; height: number;
1366
- originalWidth: number; originalHeight: number;
1367
- offsetX: number; offsetY: number;
1368
- resource: PackInputResource; trimBuffer?: Uint8Array; rasterizedBuffer?: Uint8Array;
1411
+ id: string;
1412
+ width: number;
1413
+ height: number;
1414
+ originalWidth: number;
1415
+ originalHeight: number;
1416
+ offsetX: number;
1417
+ offsetY: number;
1418
+ resource: PackInputResource;
1419
+ trimBuffer?: Uint8Array;
1420
+ rasterizedBuffer?: Uint8Array;
1369
1421
  sourceKind: 'image' | 'movieclip-frame';
1370
1422
  };
1371
1423
 
@@ -1389,7 +1441,7 @@ async function _collectImage(
1389
1441
  resource: ImageResource,
1390
1442
  pkg: Package,
1391
1443
  inputs: InputItem[],
1392
- encoder: AtlasEncoder | undefined,
1444
+ encoder: AtlasRasterBackend | undefined,
1393
1445
  options: AtlasOptions,
1394
1446
  doTrim: boolean,
1395
1447
  logger: ILogger,
@@ -1414,7 +1466,7 @@ async function _collectImage(
1414
1466
  sourceHasAlpha = metadata.hasAlpha === true || metadata.channels === 4;
1415
1467
  if (/\.svg$/i.test(resolveImageFileName(resource)) && declaredWidth > 0 && declaredHeight > 0) {
1416
1468
  rasterizedBuffer = await encoder(filePath)
1417
- .resize(declaredWidth, declaredHeight, { fit: 'fill' })
1469
+ .resize({ width: declaredWidth, height: declaredHeight, fit: 'fill' })
1418
1470
  .png()
1419
1471
  .toBuffer();
1420
1472
  sourceHasAlpha = true;
@@ -1429,7 +1481,10 @@ async function _collectImage(
1429
1481
 
1430
1482
  if (origW <= 0 || origH <= 0) return;
1431
1483
 
1432
- let packW = origW, packH = origH, offX = 0, offY = 0;
1484
+ let packW = origW,
1485
+ packH = origH,
1486
+ offX = 0,
1487
+ offY = 0;
1433
1488
  let trimBuf: Uint8Array | undefined;
1434
1489
 
1435
1490
  if (doTrim && sourceHasAlpha && options.basePath && encoder) {
@@ -1447,9 +1502,13 @@ async function _collectImage(
1447
1502
  }
1448
1503
 
1449
1504
  inputs.push({
1450
- id: getPublishedItemId(resource), width: packW, height: packH,
1451
- originalWidth: origW, originalHeight: origH,
1452
- offsetX: offX, offsetY: offY,
1505
+ id: getPublishedItemId(resource),
1506
+ width: packW,
1507
+ height: packH,
1508
+ originalWidth: origW,
1509
+ originalHeight: origH,
1510
+ offsetX: offX,
1511
+ offsetY: offY,
1453
1512
  resource,
1454
1513
  trimBuffer: trimBuf,
1455
1514
  rasterizedBuffer,
@@ -1463,7 +1522,7 @@ async function _collectMovieClipFrames(
1463
1522
  resource: MovieClipResource,
1464
1523
  pkg: Package,
1465
1524
  inputs: InputItem[],
1466
- encoder: AtlasEncoder | undefined,
1525
+ encoder: AtlasRasterBackend | undefined,
1467
1526
  options: AtlasOptions,
1468
1527
  logger: ILogger,
1469
1528
  ): Promise<void> {
@@ -1553,7 +1612,7 @@ async function _createMovieClipFrameInput(
1553
1612
  buffer: Uint8Array,
1554
1613
  itemId: string,
1555
1614
  resource: MovieClipResource,
1556
- encoder: AtlasEncoder | undefined,
1615
+ encoder: AtlasRasterBackend | undefined,
1557
1616
  ): Promise<InputItem | null> {
1558
1617
  if (!encoder || buffer.length === 0) return null;
1559
1618
  try {
@@ -1626,10 +1685,7 @@ function _findPngEnd(data: Uint8Array, start: number): number {
1626
1685
  pos += 8;
1627
1686
  if (pos + length + 4 > data.length) return -1;
1628
1687
  const isIEND =
1629
- data[pos - 4] === 0x49 &&
1630
- data[pos - 3] === 0x45 &&
1631
- data[pos - 2] === 0x4e &&
1632
- data[pos - 1] === 0x44;
1688
+ data[pos - 4] === 0x49 && data[pos - 3] === 0x45 && data[pos - 2] === 0x4e && data[pos - 1] === 0x44;
1633
1689
  pos += length + 4;
1634
1690
  if (isIEND) return pos;
1635
1691
  }
@@ -1760,7 +1816,7 @@ function _readUint16BE(data: Uint8Array, offset: number): number {
1760
1816
  function _readUint32BE(data: Uint8Array, offset: number): number {
1761
1817
  if (offset + 3 >= data.length) return 0;
1762
1818
  return (
1763
- (data[offset] * 0x1000000) +
1819
+ data[offset] * 0x1000000 +
1764
1820
  ((data[offset + 1] ?? 0) << 16) +
1765
1821
  ((data[offset + 2] ?? 0) << 8) +
1766
1822
  (data[offset + 3] ?? 0)
@@ -1823,27 +1879,53 @@ async function _collectFontTexture(
1823
1879
  .setChannel(item.channel);
1824
1880
  fontRes.addGlyph(glyph);
1825
1881
  }
1826
- } catch { /* .fnt not found */ }
1882
+ } catch {
1883
+ /* .fnt not found */
1884
+ }
1827
1885
  }
1828
1886
  }
1829
1887
 
1830
1888
  /** Parse a BMFont .fnt text file into structured data for binary encoding. */
1831
1889
  function _parseFnt(text: string): {
1832
- hasFace: boolean; colored: boolean; resizable: boolean; hasChannel: boolean;
1833
- fontSize: number; xadvance: number; lineHeight: number;
1890
+ hasFace: boolean;
1891
+ colored: boolean;
1892
+ resizable: boolean;
1893
+ hasChannel: boolean;
1894
+ fontSize: number;
1895
+ xadvance: number;
1896
+ lineHeight: number;
1834
1897
  glyphs: Array<{
1835
- charId: number; img: string | null;
1836
- x: number; y: number; xoffset: number; yoffset: number;
1837
- width: number; height: number; xadvance: number; channel: number;
1898
+ charId: number;
1899
+ img: string | null;
1900
+ x: number;
1901
+ y: number;
1902
+ xoffset: number;
1903
+ yoffset: number;
1904
+ width: number;
1905
+ height: number;
1906
+ xadvance: number;
1907
+ channel: number;
1838
1908
  }>;
1839
1909
  } {
1840
1910
  const lines = text.split(/\r?\n/);
1841
- let hasFace = false, colored = false, resizable = false, hasChannel = false;
1842
- let fontSize = 0, globalXadvance = 0, lineHeight = 0;
1911
+ let hasFace = false,
1912
+ colored = false,
1913
+ resizable = false,
1914
+ hasChannel = false;
1915
+ let fontSize = 0,
1916
+ globalXadvance = 0,
1917
+ lineHeight = 0;
1843
1918
  const glyphs: Array<{
1844
- charId: number; img: string | null;
1845
- x: number; y: number; xoffset: number; yoffset: number;
1846
- width: number; height: number; xadvance: number; channel: number;
1919
+ charId: number;
1920
+ img: string | null;
1921
+ x: number;
1922
+ y: number;
1923
+ xoffset: number;
1924
+ yoffset: number;
1925
+ width: number;
1926
+ height: number;
1927
+ xadvance: number;
1928
+ channel: number;
1847
1929
  }> = [];
1848
1930
 
1849
1931
  for (const line of lines) {
@@ -1878,7 +1960,8 @@ function _parseFnt(text: string): {
1878
1960
  const chnl = parseInt(attrs.chnl, 10) || 0;
1879
1961
  if (chnl !== 0 && chnl !== 15) hasChannel = true;
1880
1962
  glyphs.push({
1881
- charId, img,
1963
+ charId,
1964
+ img,
1882
1965
  x: parseInt(attrs.x, 10) || 0,
1883
1966
  y: parseInt(attrs.y, 10) || 0,
1884
1967
  xoffset: parseInt(attrs.xoffset, 10) || 0,
@@ -1893,7 +1976,16 @@ function _parseFnt(text: string): {
1893
1976
  }
1894
1977
  }
1895
1978
 
1896
- return { hasFace, colored, resizable: fontSize > 0 ? resizable : false, hasChannel, fontSize, xadvance: globalXadvance, lineHeight, glyphs };
1979
+ return {
1980
+ hasFace,
1981
+ colored,
1982
+ resizable: fontSize > 0 ? resizable : false,
1983
+ hasChannel,
1984
+ fontSize,
1985
+ xadvance: globalXadvance,
1986
+ lineHeight,
1987
+ glyphs,
1988
+ };
1897
1989
  }
1898
1990
 
1899
1991
  function isComponentResource(resource: PackageResource): resource is Component {
@@ -1946,6 +2038,6 @@ function addUiResourceRefsFromUnknown(target: Set<string>, value: unknown): void
1946
2038
  }
1947
2039
  }
1948
2040
 
1949
- function isResolvedBuffer(value: Uint8Array | AtlasEncoderResolvedBuffer): value is AtlasEncoderResolvedBuffer {
2041
+ function isResolvedBuffer(value: Uint8Array | AtlasRasterResolvedBuffer): value is AtlasRasterResolvedBuffer {
1950
2042
  return typeof value === 'object' && value !== null && 'data' in value && 'info' in value;
1951
2043
  }