asset-builder-packer 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -0
  3. package/dist/autosize.d.ts +13 -0
  4. package/dist/autosize.d.ts.map +1 -0
  5. package/dist/autosize.js +72 -0
  6. package/dist/autosize.js.map +1 -0
  7. package/dist/compose.d.ts +14 -0
  8. package/dist/compose.d.ts.map +1 -0
  9. package/dist/compose.js +46 -0
  10. package/dist/compose.js.map +1 -0
  11. package/dist/emit/pixi.d.ts +49 -0
  12. package/dist/emit/pixi.d.ts.map +1 -0
  13. package/dist/emit/pixi.js +38 -0
  14. package/dist/emit/pixi.js.map +1 -0
  15. package/dist/emit/spine.d.ts +22 -0
  16. package/dist/emit/spine.d.ts.map +1 -0
  17. package/dist/emit/spine.js +43 -0
  18. package/dist/emit/spine.js.map +1 -0
  19. package/dist/errors.d.ts +16 -0
  20. package/dist/errors.d.ts.map +1 -0
  21. package/dist/errors.js +25 -0
  22. package/dist/errors.js.map +1 -0
  23. package/dist/identical.d.ts +23 -0
  24. package/dist/identical.d.ts.map +1 -0
  25. package/dist/identical.js +33 -0
  26. package/dist/identical.js.map +1 -0
  27. package/dist/index.d.ts +29 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +105 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/metrics.d.ts +10 -0
  32. package/dist/metrics.d.ts.map +1 -0
  33. package/dist/metrics.js +40 -0
  34. package/dist/metrics.js.map +1 -0
  35. package/dist/place.d.ts +28 -0
  36. package/dist/place.d.ts.map +1 -0
  37. package/dist/place.js +99 -0
  38. package/dist/place.js.map +1 -0
  39. package/dist/trim.d.ts +18 -0
  40. package/dist/trim.d.ts.map +1 -0
  41. package/dist/trim.js +94 -0
  42. package/dist/trim.js.map +1 -0
  43. package/dist/types.d.ts +147 -0
  44. package/dist/types.d.ts.map +1 -0
  45. package/dist/types.js +28 -0
  46. package/dist/types.js.map +1 -0
  47. package/dist/validate.d.ts +7 -0
  48. package/dist/validate.d.ts.map +1 -0
  49. package/dist/validate.js +29 -0
  50. package/dist/validate.js.map +1 -0
  51. package/package.json +55 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nonamezzz322
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # asset-builder-packer
2
+
3
+ A texture-atlas packer for **Pixi** and **Spine**, built on `sharp`/libvips and
4
+ `maxrects-packer`. It takes decoded RGBA sprites and produces composited atlas pages plus typed
5
+ Pixi spritesheet JSON and Spine `.atlas` output.
6
+
7
+ Why another packer:
8
+
9
+ - **sharp/libvips compositing** — raw-RGBA pages end-to-end; encode once per output format
10
+ (PNG/WebP/AVIF/JPG) downstream, no intermediate re-encoding.
11
+ - **Typed emitters, not templates/regex** — Pixi JSON and Spine `.atlas` are built from data;
12
+ format variants come from a second emitter call, never string replacement.
13
+ - **First-class trim + rotation for Spine** — correct `offset`/`orig` math with Y-up offsets
14
+ (many pipelines hardcode `offset: 0,0` and can't trim Spine attachments).
15
+ - **Deterministic** — identical input + options ⇒ byte-identical pages and emitter output, so it
16
+ composes cleanly with content-hash build caches.
17
+ - **Fill-rate metrics** — per-page efficiency for VRAM budgeting.
18
+
19
+ Originally built to replace [free-tex-packer-core](https://github.com/odrick/free-tex-packer-core)
20
+ in an internal asset pipeline; published as a standalone library.
21
+
22
+ ```ts
23
+ import { pack, emitPixiJson, emitSpineAtlasPage } from 'asset-builder-packer';
24
+
25
+ const result = await pack(images, { maxWidth: 4096, maxHeight: 4096, allowTrim: true });
26
+ for (const page of result.pages) {
27
+ const json = emitPixiJson(page, { imageName: `${name}-${page.pageIndex}.png`, scale: 1 });
28
+ // page.data is raw RGBA — encode with sharp to png/webp/avif, once per format
29
+ }
30
+ ```
31
+
32
+ Status: phase 0 (contract + emitters + tests). Roadmap: [docs/PLAN.md](docs/PLAN.md).
33
+ Working agreements and invariants: [CLAUDE.md](CLAUDE.md).
34
+
35
+ - `npm test` — vitest
36
+ - `npm run typecheck` — tsc noEmit
37
+ - `npm run build` — compile to `dist/`
@@ -0,0 +1,13 @@
1
+ import type { PackOptions, SizeEvaluation, AutoSizeConfig } from './types.js';
2
+ import { type PlacementInput } from './place.js';
3
+ /** Наименьший квадратный размер, вмещающий самый большой раздутый кадр (нижняя граница кандидатов). */
4
+ export declare function minFeasibleSize(inputs: readonly PlacementInput[], options: PackOptions): number;
5
+ /**
6
+ * Оценивает кандидатные размеры страницы ЧЕРЕЗ РАЗМЕЩЕНИЕ (без композитинга — дёшево).
7
+ * Кандидаты меньше минимально допустимого пропускаются. По одной оценке на валидный кандидат,
8
+ * отсортированы по возрастанию размера.
9
+ */
10
+ export declare function evaluateSizes(inputs: readonly PlacementInput[], options: PackOptions): SizeEvaluation[];
11
+ /** Выбирает лучшую оценку по цели. При равенстве счёта — меньший размер (детерминизм, меньше риск одной гигантской текстуры). */
12
+ export declare function chooseBestSize(evals: readonly SizeEvaluation[], objective: AutoSizeConfig['objective']): SizeEvaluation;
13
+ //# sourceMappingURL=autosize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autosize.d.ts","sourceRoot":"","sources":["../src/autosize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC9E,OAAO,EAAe,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAI9D,uGAAuG;AACvG,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,EAAE,OAAO,EAAE,WAAW,GAAG,MAAM,CAK/F;AAYD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,EAAE,OAAO,EAAE,WAAW,GAAG,cAAc,EAAE,CAqBvG;AAED,iIAAiI;AACjI,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,EAAE,SAAS,EAAE,cAAc,CAAC,WAAW,CAAC,GAAG,cAAc,CAkBvH"}
@@ -0,0 +1,72 @@
1
+ import { placeFrames } from './place.js';
2
+ import { computeReport } from './metrics.js';
3
+ import { OversizedFrameError } from './errors.js';
4
+ /** Наименьший квадратный размер, вмещающий самый большой раздутый кадр (нижняя граница кандидатов). */
5
+ export function minFeasibleSize(inputs, options) {
6
+ const g = 2 * (options.padding + options.extrude);
7
+ let m = 1;
8
+ for (const i of inputs)
9
+ m = Math.max(m, i.w + g, i.h + g);
10
+ return m;
11
+ }
12
+ /** Лестница степеней двойки [min..max], включая сам max (может быть не-POT). */
13
+ function potLadder(min, max) {
14
+ const out = [];
15
+ let p = 1;
16
+ while (p < min)
17
+ p *= 2;
18
+ for (; p < max; p *= 2)
19
+ out.push(p);
20
+ out.push(max);
21
+ return out;
22
+ }
23
+ /**
24
+ * Оценивает кандидатные размеры страницы ЧЕРЕЗ РАЗМЕЩЕНИЕ (без композитинга — дёшево).
25
+ * Кандидаты меньше минимально допустимого пропускаются. По одной оценке на валидный кандидат,
26
+ * отсортированы по возрастанию размера.
27
+ */
28
+ export function evaluateSizes(inputs, options) {
29
+ if (inputs.length === 0)
30
+ return [];
31
+ const areaByName = new Map(inputs.map(i => [i.name, i.w * i.h]));
32
+ const min = minFeasibleSize(inputs, options);
33
+ const raw = options.autoSize.candidates ?? potLadder(min, options.maxWidth);
34
+ const candidates = [...new Set(raw)]
35
+ .filter(s => s >= min && s <= options.maxWidth)
36
+ .sort((a, b) => a - b);
37
+ const evals = [];
38
+ for (const size of candidates) {
39
+ try {
40
+ const placements = placeFrames([...inputs], { ...options, maxWidth: size, maxHeight: size });
41
+ evals.push({ size, report: computeReport(placements, areaByName, size) });
42
+ }
43
+ catch (e) {
44
+ if (e instanceof OversizedFrameError)
45
+ continue; // размер слишком мал — пропускаем
46
+ throw e;
47
+ }
48
+ }
49
+ return evals;
50
+ }
51
+ /** Выбирает лучшую оценку по цели. При равенстве счёта — меньший размер (детерминизм, меньше риск одной гигантской текстуры). */
52
+ export function chooseBestSize(evals, objective) {
53
+ if (evals.length === 0)
54
+ throw new Error('chooseBestSize: no candidate size fits the input');
55
+ const score = (e) => {
56
+ switch (objective) {
57
+ case 'vram': return e.report.totalVramBytes;
58
+ case 'potVram': return e.report.totalPotVramBytes;
59
+ case 'fill': return -e.report.overallFillRate; // максимизируем fill → минимизируем отрицание
60
+ }
61
+ };
62
+ return evals.reduce((best, e) => {
63
+ const s = score(e);
64
+ const bs = score(best);
65
+ if (s < bs)
66
+ return e;
67
+ if (s === bs && e.size < best.size)
68
+ return e;
69
+ return best;
70
+ });
71
+ }
72
+ //# sourceMappingURL=autosize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autosize.js","sourceRoot":"","sources":["../src/autosize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ /// <reference types="node" resolution-mode="require"/>
3
+ import type { InputImage, PackedPage } from './types.js';
4
+ /**
5
+ * Собирает размещённые кадры в сырой RGBA-буфер страницы (FR-22..FR-24).
6
+ *
7
+ * На кадр: извлекается трим-окно из исходника → при rotated поворот 90° CW (sharp.rotate(90),
8
+ * направление подтверждено пиксельным тестом — соответствует Pixi `rotated:true` / Spine
9
+ * `rotate:270`) → extrude-кайма репликацией краёв (extendWith:'copy') → композит в позицию.
10
+ * Один composite-проход на страницу. Aliases не рендерятся отдельно — они ссылаются на тот же
11
+ * регион на этапе эмиттеров. Выход — сырой RGBA (кодирование форматов — снаружи, FR-24).
12
+ */
13
+ export declare function composePage(page: Omit<PackedPage, 'data'>, images: Map<string, InputImage>, extrude: number): Promise<Buffer>;
14
+ //# sourceMappingURL=compose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":";;AACA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEzD;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAC7B,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,EAC9B,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EAC/B,OAAO,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,CAsCjB"}
@@ -0,0 +1,46 @@
1
+ import sharp from 'sharp';
2
+ /**
3
+ * Собирает размещённые кадры в сырой RGBA-буфер страницы (FR-22..FR-24).
4
+ *
5
+ * На кадр: извлекается трим-окно из исходника → при rotated поворот 90° CW (sharp.rotate(90),
6
+ * направление подтверждено пиксельным тестом — соответствует Pixi `rotated:true` / Spine
7
+ * `rotate:270`) → extrude-кайма репликацией краёв (extendWith:'copy') → композит в позицию.
8
+ * Один composite-проход на страницу. Aliases не рендерятся отдельно — они ссылаются на тот же
9
+ * регион на этапе эмиттеров. Выход — сырой RGBA (кодирование форматов — снаружи, FR-24).
10
+ */
11
+ export async function composePage(page, images, extrude) {
12
+ const composites = [];
13
+ for (const frame of page.frames) {
14
+ const src = images.get(frame.name);
15
+ if (!src)
16
+ throw new Error(`composePage: no source image for frame "${frame.name}"`);
17
+ // 1) Материализуем трим-окно (снимает неоднозначность порядка extract/rotate в sharp).
18
+ const trimmed = await sharp(src.data, { raw: { width: src.width, height: src.height, channels: 4 } })
19
+ .extract({ left: frame.trimX, top: frame.trimY, width: frame.w, height: frame.h })
20
+ .raw()
21
+ .toBuffer();
22
+ // 2) Свежий pipeline: поворот (90° CW) → extrude.
23
+ let region = sharp(trimmed, { raw: { width: frame.w, height: frame.h, channels: 4 } });
24
+ if (frame.rotated) {
25
+ region = region.rotate(90);
26
+ }
27
+ if (extrude > 0) {
28
+ region = region.extend({ top: extrude, bottom: extrude, left: extrude, right: extrude, extendWith: 'copy' });
29
+ }
30
+ const { data, info } = await region.raw().toBuffer({ resolveWithObject: true });
31
+ // Контент лежит в (frame.x, frame.y); extrude-кайма выступает наружу на `extrude` px.
32
+ // Слева/сверху места достаточно: frame.x = boxX + (padding+extrude) ≥ extrude.
33
+ composites.push({
34
+ input: data,
35
+ raw: { width: info.width, height: info.height, channels: 4 },
36
+ left: frame.x - extrude,
37
+ top: frame.y - extrude,
38
+ });
39
+ }
40
+ const base = sharp({
41
+ create: { width: page.width, height: page.height, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } },
42
+ });
43
+ const { data } = await base.composite(composites).raw().toBuffer({ resolveWithObject: true });
44
+ return data;
45
+ }
46
+ //# sourceMappingURL=compose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC7B,IAA8B,EAC9B,MAA+B,EAC/B,OAAe;IAEf,MAAM,UAAU,GAA2B,EAAE,CAAC;IAE9C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC;QAEpF,uFAAuF;QACvF,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;aAChG,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;aACjF,GAAG,EAAE;aACL,QAAQ,EAAE,CAAC;QAEhB,kDAAkD;QAClD,IAAI,MAAM,GAAG,KAAK,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACvF,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YAChB,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC/B,CAAC;QACD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YACd,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;QACjH,CAAC;QACD,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;QAEhF,sFAAsF;QACtF,+EAA+E;QAC/E,UAAU,CAAC,IAAI,CAAC;YACZ,KAAK,EAAE,IAAI;YACX,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE;YAC5D,IAAI,EAAE,KAAK,CAAC,CAAC,GAAG,OAAO;YACvB,GAAG,EAAE,KAAK,CAAC,CAAC,GAAG,OAAO;SACzB,CAAC,CAAC;IACP,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC;QACf,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;KAC9G,CAAC,CAAC;IACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9F,OAAO,IAAI,CAAC;AAChB,CAAC"}
@@ -0,0 +1,49 @@
1
+ import type { PackedFrame, PackedPage } from '../types.js';
2
+ export interface PixiFrameEntry {
3
+ frame: {
4
+ x: number;
5
+ y: number;
6
+ w: number;
7
+ h: number;
8
+ };
9
+ rotated: boolean;
10
+ trimmed: boolean;
11
+ spriteSourceSize: {
12
+ x: number;
13
+ y: number;
14
+ w: number;
15
+ h: number;
16
+ };
17
+ sourceSize: {
18
+ w: number;
19
+ h: number;
20
+ };
21
+ }
22
+ export interface PixiAtlasJson {
23
+ frames: Record<string, PixiFrameEntry>;
24
+ meta: {
25
+ app: string;
26
+ image: string;
27
+ format: 'RGBA8888';
28
+ size: {
29
+ w: number;
30
+ h: number;
31
+ };
32
+ scale: number;
33
+ related_multi_packs?: string[];
34
+ };
35
+ }
36
+ export declare function toPixiFrameEntry(f: PackedFrame): PixiFrameEntry;
37
+ export interface EmitPixiOptions {
38
+ /** Texture filename referenced by meta.image (variant-specific: .png / _webp.webp / _avif.avif). */
39
+ imageName: string;
40
+ scale: number;
41
+ relatedMultiPacks?: string[];
42
+ }
43
+ /**
44
+ * Emit a Pixi spritesheet JSON object for one page. Serialization is the caller's business —
45
+ * format variants (webp/avif/jpg) MUST be produced by calling this again with a different
46
+ * `imageName`, never by string-replacing serialized output.
47
+ */
48
+ export declare function emitPixiJson(page: PackedPage, opts: EmitPixiOptions): PixiAtlasJson;
49
+ //# sourceMappingURL=pixi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pixi.d.ts","sourceRoot":"","sources":["../../src/emit/pixi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE3D,MAAM,WAAW,cAAc;IAC3B,KAAK,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE,UAAU,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACxC;AAED,MAAM,WAAW,aAAa;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACvC,IAAI,EAAE;QACF,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,UAAU,CAAC;QACnB,IAAI,EAAE;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC/B,KAAK,EAAE,MAAM,CAAC;QACd,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;KAClC,CAAC;CACL;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,WAAW,GAAG,cAAc,CAU/D;AAED,MAAM,WAAW,eAAe;IAC5B,oGAAoG;IACpG,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,eAAe,GAAG,aAAa,CAoBnF"}
@@ -0,0 +1,38 @@
1
+ export function toPixiFrameEntry(f) {
2
+ return {
3
+ // Pixi convention (same as TexturePacker / free-tex-packer): frame w/h stay in
4
+ // UNROTATED orientation; the loader swaps them when `rotated` is set.
5
+ frame: { x: f.x, y: f.y, w: f.w, h: f.h },
6
+ rotated: f.rotated,
7
+ trimmed: f.trimmed,
8
+ spriteSourceSize: { x: f.trimX, y: f.trimY, w: f.w, h: f.h },
9
+ sourceSize: { w: f.sourceW, h: f.sourceH },
10
+ };
11
+ }
12
+ /**
13
+ * Emit a Pixi spritesheet JSON object for one page. Serialization is the caller's business —
14
+ * format variants (webp/avif/jpg) MUST be produced by calling this again with a different
15
+ * `imageName`, never by string-replacing serialized output.
16
+ */
17
+ export function emitPixiJson(page, opts) {
18
+ const frames = {};
19
+ for (const f of page.frames) {
20
+ const entry = toPixiFrameEntry(f);
21
+ frames[f.name] = entry;
22
+ for (const alias of f.aliases) {
23
+ frames[alias] = entry;
24
+ }
25
+ }
26
+ const meta = {
27
+ app: '@gamzix/packer',
28
+ image: opts.imageName,
29
+ format: 'RGBA8888',
30
+ size: { w: page.width, h: page.height },
31
+ scale: opts.scale,
32
+ };
33
+ if (opts.relatedMultiPacks && opts.relatedMultiPacks.length > 0) {
34
+ meta.related_multi_packs = opts.relatedMultiPacks;
35
+ }
36
+ return { frames, meta };
37
+ }
38
+ //# sourceMappingURL=pixi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pixi.js","sourceRoot":"","sources":["../../src/emit/pixi.ts"],"names":[],"mappings":"AAsBA,MAAM,UAAU,gBAAgB,CAAC,CAAc;IAC3C,OAAO;QACH,+EAA+E;QAC/E,sEAAsE;QACtE,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE;QACzC,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,gBAAgB,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE;QAC5D,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE;KAC7C,CAAC;AACN,CAAC;AASD;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,IAAgB,EAAE,IAAqB;IAChE,MAAM,MAAM,GAAmC,EAAE,CAAC;IAClD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC;QAClC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;QACvB,KAAK,MAAM,KAAK,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;YAC5B,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;QAC1B,CAAC;IACL,CAAC;IACD,MAAM,IAAI,GAA0B;QAChC,GAAG,EAAE,gBAAgB;QACrB,KAAK,EAAE,IAAI,CAAC,SAAS;QACrB,MAAM,EAAE,UAAU;QAClB,IAAI,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE;QACvC,KAAK,EAAE,IAAI,CAAC,KAAK;KACpB,CAAC;IACF,IAAI,IAAI,CAAC,iBAAiB,IAAI,IAAI,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9D,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,iBAAiB,CAAC;IACtD,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { PackedFrame, PackedPage } from '../types.js';
2
+ export interface EmitSpineOptions {
3
+ /** Texture filename for the page header (variant-specific: .png / _webp.webp / _avif.avif). */
4
+ textureName: string;
5
+ }
6
+ /**
7
+ * Emit one Spine `.atlas` page section as text.
8
+ *
9
+ * Geometry (see invariants in types.ts):
10
+ * - `size` / `orig` are in UNROTATED orientation.
11
+ * - `offset` is in unrotated source space with Spine's Y-UP convention:
12
+ * offsetX = left whitespace, offsetY = BOTTOM whitespace = sourceH - trimY - h.
13
+ * Rotation does not enter the offset math at all.
14
+ * - `rotate: 270` is the pinned mapping for our 90° CW page rotation (see types.ts §3);
15
+ * verified against production Gamzix runtimes, matches the legacy transcoder.
16
+ *
17
+ * Note: free-tex-packer's own Spine exporter hardcodes `offset: 0,0` (broken for trim);
18
+ * this emitter is the correct replacement, not a port.
19
+ */
20
+ export declare function emitSpineAtlasPage(page: PackedPage, opts: EmitSpineOptions): string;
21
+ export declare function spineRegionLines(name: string, f: PackedFrame): string[];
22
+ //# sourceMappingURL=spine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spine.d.ts","sourceRoot":"","sources":["../../src/emit/spine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE3D,MAAM,WAAW,gBAAgB;IAC7B,+FAA+F;IAC/F,WAAW,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAiBnF;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,GAAG,MAAM,EAAE,CAYvE"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Emit one Spine `.atlas` page section as text.
3
+ *
4
+ * Geometry (see invariants in types.ts):
5
+ * - `size` / `orig` are in UNROTATED orientation.
6
+ * - `offset` is in unrotated source space with Spine's Y-UP convention:
7
+ * offsetX = left whitespace, offsetY = BOTTOM whitespace = sourceH - trimY - h.
8
+ * Rotation does not enter the offset math at all.
9
+ * - `rotate: 270` is the pinned mapping for our 90° CW page rotation (see types.ts §3);
10
+ * verified against production Gamzix runtimes, matches the legacy transcoder.
11
+ *
12
+ * Note: free-tex-packer's own Spine exporter hardcodes `offset: 0,0` (broken for trim);
13
+ * this emitter is the correct replacement, not a port.
14
+ */
15
+ export function emitSpineAtlasPage(page, opts) {
16
+ const lines = [];
17
+ lines.push('');
18
+ lines.push(opts.textureName);
19
+ lines.push(`size: ${page.width},${page.height}`);
20
+ lines.push('format: RGBA8888');
21
+ lines.push('filter: Linear,Linear');
22
+ lines.push('repeat: none');
23
+ for (const f of page.frames) {
24
+ for (const name of [f.name, ...f.aliases]) {
25
+ lines.push(...spineRegionLines(name, f));
26
+ }
27
+ }
28
+ return lines.join('\n') + '\n';
29
+ }
30
+ export function spineRegionLines(name, f) {
31
+ const offsetX = f.trimX;
32
+ const offsetY = f.sourceH - f.trimY - f.h;
33
+ return [
34
+ name,
35
+ ` rotate: ${f.rotated ? '270' : '0'}`,
36
+ ` xy: ${f.x}, ${f.y}`,
37
+ ` size: ${f.w}, ${f.h}`,
38
+ ` orig: ${f.sourceW}, ${f.sourceH}`,
39
+ ` offset: ${offsetX}, ${offsetY}`,
40
+ ' index: -1',
41
+ ];
42
+ }
43
+ //# sourceMappingURL=spine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spine.js","sourceRoot":"","sources":["../../src/emit/spine.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAgB,EAAE,IAAsB;IACvE,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC7B,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACjD,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAC/B,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IACpC,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAE3B,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAC1B,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;YACxC,KAAK,CAAC,IAAI,CAAC,GAAG,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7C,CAAC;IACL,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,CAAc;IACzD,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC;IACxB,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;IAC1C,OAAO;QACH,IAAI;QACJ,aAAa,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE;QACtC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;QACtB,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;QACxB,WAAW,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,EAAE;QACpC,aAAa,OAAO,KAAK,OAAO,EAAE;QAClC,aAAa;KAChB,CAAC;AACN,CAAC"}
@@ -0,0 +1,16 @@
1
+ /** Ошибки пакера. Типизированные, чтобы вызывающий (билдер) мог реагировать осмысленно. */
2
+ /**
3
+ * Кадр (с учётом каймы 2*(padding+extrude)) не влезает в maxWidth×maxHeight ни в одной
4
+ * ориентации. Билдер по этой ошибке решает, поднимать ли maxTextureSize (FR-29).
5
+ */
6
+ export declare class OversizedFrameError extends Error {
7
+ readonly frameName: string;
8
+ /** Минимальный размер страницы (кв.), при котором кадр поместится. */
9
+ readonly minPageSize: number;
10
+ constructor(frameName: string, minPageSize: number, maxWidth: number, maxHeight: number);
11
+ }
12
+ /** Некорректный вход: битый RGBA-буфер, дубли имён, небезопасные для формата имена (FR-1/4/30). */
13
+ export declare class InvalidInputError extends Error {
14
+ constructor(message: string);
15
+ }
16
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAE3F;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAEjB,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;CAS1F;AAED,mGAAmG;AACnG,qBAAa,iBAAkB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI9B"}
package/dist/errors.js ADDED
@@ -0,0 +1,25 @@
1
+ /** Ошибки пакера. Типизированные, чтобы вызывающий (билдер) мог реагировать осмысленно. */
2
+ /**
3
+ * Кадр (с учётом каймы 2*(padding+extrude)) не влезает в maxWidth×maxHeight ни в одной
4
+ * ориентации. Билдер по этой ошибке решает, поднимать ли maxTextureSize (FR-29).
5
+ */
6
+ export class OversizedFrameError extends Error {
7
+ frameName;
8
+ /** Минимальный размер страницы (кв.), при котором кадр поместится. */
9
+ minPageSize;
10
+ constructor(frameName, minPageSize, maxWidth, maxHeight) {
11
+ super(`Frame "${frameName}" does not fit into ${maxWidth}x${maxHeight} even with padding/extrude; ` +
12
+ `needs a page of at least ${minPageSize}px.`);
13
+ this.name = 'OversizedFrameError';
14
+ this.frameName = frameName;
15
+ this.minPageSize = minPageSize;
16
+ }
17
+ }
18
+ /** Некорректный вход: битый RGBA-буфер, дубли имён, небезопасные для формата имена (FR-1/4/30). */
19
+ export class InvalidInputError extends Error {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = 'InvalidInputError';
23
+ }
24
+ }
25
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAE3F;;;GAGG;AACH,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IACjC,SAAS,CAAS;IAC3B,sEAAsE;IAC7D,WAAW,CAAS;IAE7B,YAAY,SAAiB,EAAE,WAAmB,EAAE,QAAgB,EAAE,SAAiB;QACnF,KAAK,CACD,UAAU,SAAS,uBAAuB,QAAQ,IAAI,SAAS,8BAA8B;YAC7F,4BAA4B,WAAW,KAAK,CAC/C,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACnC,CAAC;CACJ;AAED,mGAAmG;AACnG,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACxC,YAAY,OAAe;QACvB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IACpC,CAAC;CACJ"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Внутрипапочный дедуп (detectIdentical, FR-10..FR-12): побайтово идентичные (после trim) кадры
3
+ * схлопываются в один размещаемый экземпляр + aliases. Идентичность — по контент-хэшу тримленной
4
+ * области (см. hashTrimmedRegion), который уже включает размеры окна, поэтому кадры разного размера
5
+ * не схлопываются даже при совпадении пикселей перекрытия.
6
+ *
7
+ * Только в пределах одного вызова pack(). Кросс-атласный дедуп (linkedFrames) — слой выше.
8
+ */
9
+ export interface IdenticalGroup {
10
+ /** Имя-представитель — первое по code-unit сортировке; оно размещается и рендерится. */
11
+ representative: string;
12
+ /** Остальные имена той же группы (сортированы) — станут aliases кадра-представителя. */
13
+ aliases: string[];
14
+ }
15
+ /**
16
+ * Группирует по хэшу. Возвращает по одной группе на уникальный контент (в т.ч. синглтоны с пустыми
17
+ * aliases), отсортированные по имени представителя — детерминированно (NFR-1).
18
+ */
19
+ export declare function detectIdentical(items: ReadonlyArray<{
20
+ name: string;
21
+ hash: string;
22
+ }>): IdenticalGroup[];
23
+ //# sourceMappingURL=identical.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identical.d.ts","sourceRoot":"","sources":["../src/identical.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,cAAc;IAC3B,wFAAwF;IACxF,cAAc,EAAE,MAAM,CAAC;IACvB,wFAAwF;IACxF,OAAO,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,cAAc,EAAE,CAetG"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Внутрипапочный дедуп (detectIdentical, FR-10..FR-12): побайтово идентичные (после trim) кадры
3
+ * схлопываются в один размещаемый экземпляр + aliases. Идентичность — по контент-хэшу тримленной
4
+ * области (см. hashTrimmedRegion), который уже включает размеры окна, поэтому кадры разного размера
5
+ * не схлопываются даже при совпадении пикселей перекрытия.
6
+ *
7
+ * Только в пределах одного вызова pack(). Кросс-атласный дедуп (linkedFrames) — слой выше.
8
+ */
9
+ /**
10
+ * Группирует по хэшу. Возвращает по одной группе на уникальный контент (в т.ч. синглтоны с пустыми
11
+ * aliases), отсортированные по имени представителя — детерминированно (NFR-1).
12
+ */
13
+ export function detectIdentical(items) {
14
+ const byHash = new Map();
15
+ for (const { name, hash } of items) {
16
+ const bucket = byHash.get(hash);
17
+ if (bucket)
18
+ bucket.push(name);
19
+ else
20
+ byHash.set(hash, [name]);
21
+ }
22
+ const groups = [];
23
+ for (const names of byHash.values()) {
24
+ names.sort(codeUnit);
25
+ groups.push({ representative: names[0], aliases: names.slice(1) });
26
+ }
27
+ groups.sort((a, b) => codeUnit(a.representative, b.representative));
28
+ return groups;
29
+ }
30
+ function codeUnit(a, b) {
31
+ return a < b ? -1 : a > b ? 1 : 0;
32
+ }
33
+ //# sourceMappingURL=identical.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identical.js","sourceRoot":"","sources":["../src/identical.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAAoD;IAChF,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC3C,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,KAAK,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,MAAM;YAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;;YACzB,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,MAAM,GAAqB,EAAE,CAAC;IACpC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrB,MAAM,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC;IACpE,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS,EAAE,CAAS;IAClC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACtC,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { InputImage, PackOptions, PackResult, SizeEvaluation } from './types.js';
2
+ export * from './types.js';
3
+ export { emitPixiJson, toPixiFrameEntry } from './emit/pixi.js';
4
+ export type { PixiAtlasJson, PixiFrameEntry, EmitPixiOptions } from './emit/pixi.js';
5
+ export { emitSpineAtlasPage, spineRegionLines } from './emit/spine.js';
6
+ export type { EmitSpineOptions } from './emit/spine.js';
7
+ export { trimAlphaBounds, hashTrimmedRegion } from './trim.js';
8
+ export { detectIdentical } from './identical.js';
9
+ export type { IdenticalGroup } from './identical.js';
10
+ export { placeFrames } from './place.js';
11
+ export type { PlacementInput, Placement } from './place.js';
12
+ export { validateInput } from './validate.js';
13
+ export { OversizedFrameError, InvalidInputError } from './errors.js';
14
+ export { composePage } from './compose.js';
15
+ export { computeReport, nextPot } from './metrics.js';
16
+ export { evaluateSizes, chooseBestSize, minFeasibleSize } from './autosize.js';
17
+ /**
18
+ * What-if мониторинг БЕЗ композитинга: для каждого кандидатного размера страницы возвращает
19
+ * метрики (страницы, fill, VRAM, POT-VRAM). Дёшево (только размещение) — билдер может логировать
20
+ * это как отчёт заполненности или показывать выбор авто-подбора. Синхронно.
21
+ */
22
+ export declare function analyzeSizes(images: InputImage[], options?: Partial<PackOptions>): SizeEvaluation[];
23
+ /**
24
+ * Публичный вход: trim → detectIdentical → place (multi-heuristic, опц. авто-подбор размера) →
25
+ * compose. Возвращает сырые RGBA-страницы, геометрию кадров и метрики эффективности (report).
26
+ * См. docs/PLAN.md, docs/REQUIREMENTS.md.
27
+ */
28
+ export declare function pack(images: InputImage[], options?: Partial<PackOptions>): Promise<PackResult>;
29
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAuC,cAAc,EAAE,MAAM,YAAY,CAAC;AAU3H,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAChE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACvE,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AA0C/E;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,cAAc,EAAE,CAKnG;AAED;;;;GAIG;AACH,wBAAsB,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAiDpG"}