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.
- package/LICENSE +21 -0
- package/README.md +37 -0
- package/dist/autosize.d.ts +13 -0
- package/dist/autosize.d.ts.map +1 -0
- package/dist/autosize.js +72 -0
- package/dist/autosize.js.map +1 -0
- package/dist/compose.d.ts +14 -0
- package/dist/compose.d.ts.map +1 -0
- package/dist/compose.js +46 -0
- package/dist/compose.js.map +1 -0
- package/dist/emit/pixi.d.ts +49 -0
- package/dist/emit/pixi.d.ts.map +1 -0
- package/dist/emit/pixi.js +38 -0
- package/dist/emit/pixi.js.map +1 -0
- package/dist/emit/spine.d.ts +22 -0
- package/dist/emit/spine.d.ts.map +1 -0
- package/dist/emit/spine.js +43 -0
- package/dist/emit/spine.js.map +1 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +25 -0
- package/dist/errors.js.map +1 -0
- package/dist/identical.d.ts +23 -0
- package/dist/identical.d.ts.map +1 -0
- package/dist/identical.js +33 -0
- package/dist/identical.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +105 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics.d.ts +10 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +40 -0
- package/dist/metrics.js.map +1 -0
- package/dist/place.d.ts +28 -0
- package/dist/place.d.ts.map +1 -0
- package/dist/place.js +99 -0
- package/dist/place.js.map +1 -0
- package/dist/trim.d.ts +18 -0
- package/dist/trim.d.ts.map +1 -0
- package/dist/trim.js +94 -0
- package/dist/trim.js.map +1 -0
- package/dist/types.d.ts +147 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +28 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +7 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +29 -0
- package/dist/validate.js.map +1 -0
- package/package.json +55 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { DEFAULT_PACK_OPTIONS } from './types.js';
|
|
2
|
+
import { trimAlphaBounds, hashTrimmedRegion } from './trim.js';
|
|
3
|
+
import { detectIdentical } from './identical.js';
|
|
4
|
+
import { placeFrames } from './place.js';
|
|
5
|
+
import { composePage } from './compose.js';
|
|
6
|
+
import { validateInput } from './validate.js';
|
|
7
|
+
import { computeReport } from './metrics.js';
|
|
8
|
+
import { evaluateSizes, chooseBestSize } from './autosize.js';
|
|
9
|
+
export * from './types.js';
|
|
10
|
+
export { emitPixiJson, toPixiFrameEntry } from './emit/pixi.js';
|
|
11
|
+
export { emitSpineAtlasPage, spineRegionLines } from './emit/spine.js';
|
|
12
|
+
export { trimAlphaBounds, hashTrimmedRegion } from './trim.js';
|
|
13
|
+
export { detectIdentical } from './identical.js';
|
|
14
|
+
export { placeFrames } from './place.js';
|
|
15
|
+
export { validateInput } from './validate.js';
|
|
16
|
+
export { OversizedFrameError, InvalidInputError } from './errors.js';
|
|
17
|
+
export { composePage } from './compose.js';
|
|
18
|
+
export { computeReport, nextPot } from './metrics.js';
|
|
19
|
+
export { evaluateSizes, chooseBestSize, minFeasibleSize } from './autosize.js';
|
|
20
|
+
/** Общий этап: валидация → trim → detectIdentical → входы размещения (представители). */
|
|
21
|
+
function prepare(images, opts) {
|
|
22
|
+
validateInput(images);
|
|
23
|
+
const imageMap = new Map();
|
|
24
|
+
const trims = new Map();
|
|
25
|
+
for (const img of images) {
|
|
26
|
+
imageMap.set(img.name, img);
|
|
27
|
+
trims.set(img.name, opts.allowTrim
|
|
28
|
+
? trimAlphaBounds(img, opts.alphaThreshold)
|
|
29
|
+
: { trimmed: false, trimX: 0, trimY: 0, w: img.width, h: img.height });
|
|
30
|
+
}
|
|
31
|
+
const groups = opts.detectIdentical
|
|
32
|
+
? detectIdentical(images.map(img => ({ name: img.name, hash: hashTrimmedRegion(img, trims.get(img.name)) })))
|
|
33
|
+
: [...images]
|
|
34
|
+
.map(img => ({ representative: img.name, aliases: [] }))
|
|
35
|
+
.sort((a, b) => (a.representative < b.representative ? -1 : a.representative > b.representative ? 1 : 0));
|
|
36
|
+
const aliasesByRep = new Map(groups.map(g => [g.representative, g.aliases]));
|
|
37
|
+
const placementInputs = groups.map(g => {
|
|
38
|
+
const t = trims.get(g.representative);
|
|
39
|
+
return { name: g.representative, w: t.w, h: t.h };
|
|
40
|
+
});
|
|
41
|
+
const areaByName = new Map(placementInputs.map(i => [i.name, i.w * i.h]));
|
|
42
|
+
return { imageMap, trims, aliasesByRep, placementInputs, areaByName };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* What-if мониторинг БЕЗ композитинга: для каждого кандидатного размера страницы возвращает
|
|
46
|
+
* метрики (страницы, fill, VRAM, POT-VRAM). Дёшево (только размещение) — билдер может логировать
|
|
47
|
+
* это как отчёт заполненности или показывать выбор авто-подбора. Синхронно.
|
|
48
|
+
*/
|
|
49
|
+
export function analyzeSizes(images, options) {
|
|
50
|
+
const opts = { ...DEFAULT_PACK_OPTIONS, ...options };
|
|
51
|
+
if (images.length === 0)
|
|
52
|
+
return [];
|
|
53
|
+
const { placementInputs } = prepare(images, opts);
|
|
54
|
+
return evaluateSizes(placementInputs, opts);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Публичный вход: trim → detectIdentical → place (multi-heuristic, опц. авто-подбор размера) →
|
|
58
|
+
* compose. Возвращает сырые RGBA-страницы, геометрию кадров и метрики эффективности (report).
|
|
59
|
+
* См. docs/PLAN.md, docs/REQUIREMENTS.md.
|
|
60
|
+
*/
|
|
61
|
+
export async function pack(images, options) {
|
|
62
|
+
const opts = { ...DEFAULT_PACK_OPTIONS, ...options };
|
|
63
|
+
if (images.length === 0) {
|
|
64
|
+
return { pages: [], efficiency: [], report: computeReport([], new Map(), opts.maxWidth) };
|
|
65
|
+
}
|
|
66
|
+
const { imageMap, trims, aliasesByRep, placementInputs, areaByName } = prepare(images, opts);
|
|
67
|
+
// Авто-подбор лучшего размера страницы (свип через размещение, без композитинга).
|
|
68
|
+
let effMax = opts.maxWidth;
|
|
69
|
+
let sizeComparison;
|
|
70
|
+
if (opts.autoSize.enabled) {
|
|
71
|
+
sizeComparison = evaluateSizes(placementInputs, opts);
|
|
72
|
+
if (sizeComparison.length > 0) {
|
|
73
|
+
effMax = chooseBestSize(sizeComparison, opts.autoSize.objective).size;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const placements = placeFrames(placementInputs, { ...opts, maxWidth: effMax, maxHeight: effMax });
|
|
77
|
+
// Композит + геометрия по страницам (последовательно — ограничивает пиковую память, NFR-3).
|
|
78
|
+
const pages = [];
|
|
79
|
+
for (const placement of placements) {
|
|
80
|
+
const frames = placement.frames.map(pf => {
|
|
81
|
+
const img = imageMap.get(pf.name);
|
|
82
|
+
const t = trims.get(pf.name);
|
|
83
|
+
return {
|
|
84
|
+
name: pf.name,
|
|
85
|
+
x: pf.x,
|
|
86
|
+
y: pf.y,
|
|
87
|
+
w: t.w,
|
|
88
|
+
h: t.h,
|
|
89
|
+
rotated: pf.rotated,
|
|
90
|
+
trimmed: t.trimmed,
|
|
91
|
+
trimX: t.trimX,
|
|
92
|
+
trimY: t.trimY,
|
|
93
|
+
sourceW: img.width,
|
|
94
|
+
sourceH: img.height,
|
|
95
|
+
aliases: aliasesByRep.get(pf.name) ?? [],
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
const meta = { pageIndex: placement.pageIndex, width: placement.width, height: placement.height, frames };
|
|
99
|
+
const data = await composePage(meta, imageMap, opts.extrude);
|
|
100
|
+
pages.push({ ...meta, data });
|
|
101
|
+
}
|
|
102
|
+
const report = computeReport(placements, areaByName, effMax);
|
|
103
|
+
return { pages, efficiency: report.pages.map(p => p.fillRate), report, sizeComparison };
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PackReport } from './types.js';
|
|
2
|
+
import type { Placement } from './place.js';
|
|
3
|
+
/** Наименьшая степень двойки ≥ n. */
|
|
4
|
+
export declare function nextPot(n: number): number;
|
|
5
|
+
/**
|
|
6
|
+
* Отчёт метрик эффективности из размещений (мониторинг). `areaByName` — площадь контента
|
|
7
|
+
* (неповёрнутая w*h) по имени кадра; aliases не учитываются (не занимают места на странице).
|
|
8
|
+
*/
|
|
9
|
+
export declare function computeReport(placements: readonly Placement[], areaByName: ReadonlyMap<string, number>, maxSize: number): PackReport;
|
|
10
|
+
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,YAAY,CAAC;AAC1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,qCAAqC;AACrC,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAIzC;AAED;;;GAGG;AACH,wBAAgB,aAAa,CACzB,UAAU,EAAE,SAAS,SAAS,EAAE,EAChC,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACvC,OAAO,EAAE,MAAM,GAChB,UAAU,CA6BZ"}
|
package/dist/metrics.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Наименьшая степень двойки ≥ n. */
|
|
2
|
+
export function nextPot(n) {
|
|
3
|
+
let p = 1;
|
|
4
|
+
while (p < n)
|
|
5
|
+
p *= 2;
|
|
6
|
+
return p;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Отчёт метрик эффективности из размещений (мониторинг). `areaByName` — площадь контента
|
|
10
|
+
* (неповёрнутая w*h) по имени кадра; aliases не учитываются (не занимают места на странице).
|
|
11
|
+
*/
|
|
12
|
+
export function computeReport(placements, areaByName, maxSize) {
|
|
13
|
+
const pages = placements.map(p => {
|
|
14
|
+
const usedArea = p.frames.reduce((s, f) => s + (areaByName.get(f.name) ?? 0), 0);
|
|
15
|
+
const pageArea = p.width * p.height;
|
|
16
|
+
return {
|
|
17
|
+
pageIndex: p.pageIndex,
|
|
18
|
+
width: p.width,
|
|
19
|
+
height: p.height,
|
|
20
|
+
frameCount: p.frames.length,
|
|
21
|
+
usedArea,
|
|
22
|
+
fillRate: pageArea > 0 ? usedArea / pageArea : 0,
|
|
23
|
+
vramBytes: pageArea * 4,
|
|
24
|
+
potVramBytes: nextPot(p.width) * nextPot(p.height) * 4,
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
const totalUsedArea = pages.reduce((s, m) => s + m.usedArea, 0);
|
|
28
|
+
const totalPageArea = pages.reduce((s, m) => s + m.width * m.height, 0);
|
|
29
|
+
return {
|
|
30
|
+
maxSize,
|
|
31
|
+
pages,
|
|
32
|
+
pageCount: pages.length,
|
|
33
|
+
totalUsedArea,
|
|
34
|
+
totalPageArea,
|
|
35
|
+
overallFillRate: totalPageArea > 0 ? totalUsedArea / totalPageArea : 0,
|
|
36
|
+
totalVramBytes: pages.reduce((s, m) => s + m.vramBytes, 0),
|
|
37
|
+
totalPotVramBytes: pages.reduce((s, m) => s + m.potVramBytes, 0),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAGA,qCAAqC;AACrC,MAAM,UAAU,OAAO,CAAC,CAAS;IAC7B,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,CAAC;QAAE,CAAC,IAAI,CAAC,CAAC;IACrB,OAAO,CAAC,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CACzB,UAAgC,EAChC,UAAuC,EACvC,OAAe;IAEf,MAAM,KAAK,GAAkB,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;QAC5C,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACjF,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC;QACpC,OAAO;YACH,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM;YAC3B,QAAQ;YACR,QAAQ,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;YAChD,SAAS,EAAE,QAAQ,GAAG,CAAC;YACvB,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACzD,CAAC;IACN,CAAC,CAAC,CAAC;IAEH,MAAM,aAAa,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAChE,MAAM,aAAa,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAExE,OAAO;QACH,OAAO;QACP,KAAK;QACL,SAAS,EAAE,KAAK,CAAC,MAAM;QACvB,aAAa;QACb,aAAa;QACb,eAAe,EAAE,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;QACtE,cAAc,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;QAC1D,iBAAiB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC;KACnE,CAAC;AACN,CAAC"}
|
package/dist/place.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PackOptions, PackedFrame } from './types.js';
|
|
2
|
+
export interface PlacementInput {
|
|
3
|
+
name: string;
|
|
4
|
+
/** Тримленный размер контента, НЕповёрнутый. Раздувание padding/extrude — внутри place. */
|
|
5
|
+
w: number;
|
|
6
|
+
h: number;
|
|
7
|
+
}
|
|
8
|
+
export interface Placement {
|
|
9
|
+
pageIndex: number;
|
|
10
|
+
frames: Array<Pick<PackedFrame, 'name' | 'x' | 'y' | 'rotated'>>;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Размещение через maxrects-packer (FR-13..FR-20).
|
|
16
|
+
*
|
|
17
|
+
* Координатная модель — как у free-tex-packer (паритет, FR-18): каждый контент раздувается каймой
|
|
18
|
+
* gutterHalf = padding+extrude со всех сторон (бокс = w+g × h+g, g=2*gutterHalf), размещается с
|
|
19
|
+
* padding=0/border=0, затем координаты дефлейтятся на gutterHalf → x/y указывают на контент.
|
|
20
|
+
* Внутренние extrude px каймы несут edge-bleed (в compose, B2), внешние padding — dead-space;
|
|
21
|
+
* соседи разделены ≥ 2*gutterHalf.
|
|
22
|
+
*
|
|
23
|
+
* Multi-heuristic (FR-13): перебор logic × rotation, выбор по (min страниц, затем max fill-rate) —
|
|
24
|
+
* воспроизводит OptimalPacker. Размер страницы — NPOT shrink-wrap (FR-20): max край боксов.
|
|
25
|
+
* Детерминизм (NFR-1): вход сортируется по имени, tie-break пакера — по `hash`=имя.
|
|
26
|
+
*/
|
|
27
|
+
export declare function placeFrames(inputs: PlacementInput[], options: PackOptions): Placement[];
|
|
28
|
+
//# sourceMappingURL=place.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"place.d.ts","sourceRoot":"","sources":["../src/place.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG3D,MAAM,WAAW,cAAc;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,2FAA2F;IAC3F,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,SAAS;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG,GAAG,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,cAAc,EAAE,EAAE,OAAO,EAAE,WAAW,GAAG,SAAS,EAAE,CA4CvF"}
|
package/dist/place.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { MaxRectsPacker, Rectangle, PACKING_LOGIC } from 'maxrects-packer';
|
|
2
|
+
import { OversizedFrameError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* Размещение через maxrects-packer (FR-13..FR-20).
|
|
5
|
+
*
|
|
6
|
+
* Координатная модель — как у free-tex-packer (паритет, FR-18): каждый контент раздувается каймой
|
|
7
|
+
* gutterHalf = padding+extrude со всех сторон (бокс = w+g × h+g, g=2*gutterHalf), размещается с
|
|
8
|
+
* padding=0/border=0, затем координаты дефлейтятся на gutterHalf → x/y указывают на контент.
|
|
9
|
+
* Внутренние extrude px каймы несут edge-bleed (в compose, B2), внешние padding — dead-space;
|
|
10
|
+
* соседи разделены ≥ 2*gutterHalf.
|
|
11
|
+
*
|
|
12
|
+
* Multi-heuristic (FR-13): перебор logic × rotation, выбор по (min страниц, затем max fill-rate) —
|
|
13
|
+
* воспроизводит OptimalPacker. Размер страницы — NPOT shrink-wrap (FR-20): max край боксов.
|
|
14
|
+
* Детерминизм (NFR-1): вход сортируется по имени, tie-break пакера — по `hash`=имя.
|
|
15
|
+
*/
|
|
16
|
+
export function placeFrames(inputs, options) {
|
|
17
|
+
const gutterHalf = options.padding + options.extrude;
|
|
18
|
+
const g = 2 * gutterHalf;
|
|
19
|
+
// Детерминизм: стабильный порядок входа (NFR-1, FR-16).
|
|
20
|
+
const sorted = [...inputs].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
21
|
+
// Oversized-предпроверка (FR-29): раздутый бокс должен влезать в maxWidth×maxHeight.
|
|
22
|
+
// Ротация здесь не спасает: guard пакера сравнивает неповёрнутые dims с width/height, а при
|
|
23
|
+
// квадратных границах поворот не меняет вместимость. Сообщаем минимальный размер страницы.
|
|
24
|
+
for (const input of sorted) {
|
|
25
|
+
const iw = input.w + g;
|
|
26
|
+
const ih = input.h + g;
|
|
27
|
+
if (iw > options.maxWidth || ih > options.maxHeight) {
|
|
28
|
+
throw new OversizedFrameError(input.name, Math.max(iw, ih), options.maxWidth, options.maxHeight);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (sorted.length === 0)
|
|
32
|
+
return [];
|
|
33
|
+
const logics = [PACKING_LOGIC.MAX_EDGE, PACKING_LOGIC.MAX_AREA];
|
|
34
|
+
const rotationChoices = options.allowRotation ? [false, true] : [false];
|
|
35
|
+
const sourceArea = sorted.reduce((sum, i) => sum + i.w * i.h, 0);
|
|
36
|
+
let best = null;
|
|
37
|
+
let bestPages = Infinity;
|
|
38
|
+
let bestEfficiency = -1;
|
|
39
|
+
for (const logic of logics) {
|
|
40
|
+
for (const rot of rotationChoices) {
|
|
41
|
+
const placements = runPacker(sorted, options, logic, rot, gutterHalf, g);
|
|
42
|
+
const pages = placements.length;
|
|
43
|
+
const sheetArea = placements.reduce((sum, p) => sum + p.width * p.height, 0);
|
|
44
|
+
const efficiency = sheetArea > 0 ? sourceArea / sheetArea : 0;
|
|
45
|
+
if (pages < bestPages || (pages === bestPages && efficiency > bestEfficiency)) {
|
|
46
|
+
best = placements;
|
|
47
|
+
bestPages = pages;
|
|
48
|
+
bestEfficiency = efficiency;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return best;
|
|
53
|
+
}
|
|
54
|
+
function runPacker(inputs, options, logic, allowRotation, gutterHalf, g) {
|
|
55
|
+
const packer = new MaxRectsPacker(options.maxWidth, options.maxHeight, 0, {
|
|
56
|
+
// smart:false — пакуем в ПОЛНЫЙ бин maxWidth×maxHeight сразу (как free-tex-packer), а не
|
|
57
|
+
// растим постепенно: иначе maxrects открывает лишний бин вместо доупаковки в основной,
|
|
58
|
+
// хотя место есть (наблюдалось на реальной Spine-папке: 2 страницы вместо 1). Размер
|
|
59
|
+
// страницы всё равно считаем по контенту (NPOT shrink-wrap), поэтому VRAM не растёт.
|
|
60
|
+
smart: false,
|
|
61
|
+
pot: false,
|
|
62
|
+
square: false,
|
|
63
|
+
allowRotation,
|
|
64
|
+
border: 0,
|
|
65
|
+
logic,
|
|
66
|
+
});
|
|
67
|
+
// Инстансы Rectangle (не плоские объекты): при повороте библиотека сама свопает width/height
|
|
68
|
+
// через сеттер `rot`, поэтому rect.x+rect.width корректен для размера страницы.
|
|
69
|
+
const rects = inputs.map(input => {
|
|
70
|
+
const r = new Rectangle(input.w + g, input.h + g);
|
|
71
|
+
r.data = { name: input.name };
|
|
72
|
+
r.hash = input.name; // стабильный tie-break сортировки пакера
|
|
73
|
+
return r;
|
|
74
|
+
});
|
|
75
|
+
packer.addArray(rects);
|
|
76
|
+
const placements = [];
|
|
77
|
+
packer.bins.forEach((bin, pageIndex) => {
|
|
78
|
+
let width = 0;
|
|
79
|
+
let height = 0;
|
|
80
|
+
const frames = bin.rects.map(rect => {
|
|
81
|
+
const right = rect.x + rect.width; // width уже свопнут при повороте
|
|
82
|
+
const bottom = rect.y + rect.height;
|
|
83
|
+
if (right > width)
|
|
84
|
+
width = right;
|
|
85
|
+
if (bottom > height)
|
|
86
|
+
height = bottom;
|
|
87
|
+
return {
|
|
88
|
+
name: rect.data.name,
|
|
89
|
+
x: rect.x + gutterHalf,
|
|
90
|
+
y: rect.y + gutterHalf,
|
|
91
|
+
rotated: rect.rot === true,
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
frames.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
95
|
+
placements.push({ pageIndex, frames, width, height });
|
|
96
|
+
});
|
|
97
|
+
return placements;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=place.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"place.js","sourceRoot":"","sources":["../src/place.ts"],"names":[],"mappings":"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"}
|
package/dist/trim.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { InputImage, TrimResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Плотный alpha-bounding-box изображения (пиксель считается контентом при alpha > alphaThreshold).
|
|
4
|
+
* Работает по сырому RGBA `InputImage.data` (без Jimp/sharp).
|
|
5
|
+
*
|
|
6
|
+
* Инварианты (types.ts §1-2, §5): результат — в НЕповёрнутом пространстве исходника; при пороге 0
|
|
7
|
+
* трим lossless (срезаются только полностью прозрачные пиксели); полностью прозрачное изображение
|
|
8
|
+
* тримится в окно 1×1 (никогда 0×0 — нижестоящая геометрия предполагает непустые кадры).
|
|
9
|
+
*/
|
|
10
|
+
export declare function trimAlphaBounds(image: InputImage, alphaThreshold: number): TrimResult;
|
|
11
|
+
/**
|
|
12
|
+
* Стабильный контент-хэш тримленной области кадра — для detectIdentical.
|
|
13
|
+
* Хэшируется w×h + байты окна (trimX,trimY,w,h) поверх исходного буфера, без материализации.
|
|
14
|
+
* FNV-1a (детерминированный, зависимостей не тянет; коллизии для дедупа не критичны — при равном
|
|
15
|
+
* хэше detectIdentical может сверить байты, но на практике FNV-1a 32-бит достаточно для группировки).
|
|
16
|
+
*/
|
|
17
|
+
export declare function hashTrimmedRegion(image: InputImage, trim: TrimResult): string;
|
|
18
|
+
//# sourceMappingURL=trim.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trim.d.ts","sourceRoot":"","sources":["../src/trim.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEzD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,GAAG,UAAU,CA6CrF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAkB7E"}
|
package/dist/trim.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Плотный alpha-bounding-box изображения (пиксель считается контентом при alpha > alphaThreshold).
|
|
3
|
+
* Работает по сырому RGBA `InputImage.data` (без Jimp/sharp).
|
|
4
|
+
*
|
|
5
|
+
* Инварианты (types.ts §1-2, §5): результат — в НЕповёрнутом пространстве исходника; при пороге 0
|
|
6
|
+
* трим lossless (срезаются только полностью прозрачные пиксели); полностью прозрачное изображение
|
|
7
|
+
* тримится в окно 1×1 (никогда 0×0 — нижестоящая геометрия предполагает непустые кадры).
|
|
8
|
+
*/
|
|
9
|
+
export function trimAlphaBounds(image, alphaThreshold) {
|
|
10
|
+
const { data, width, height } = image;
|
|
11
|
+
const alphaAt = (x, y) => data[(y * width + x) * 4 + 3];
|
|
12
|
+
let top = -1;
|
|
13
|
+
for (let y = 0; y < height && top === -1; y++) {
|
|
14
|
+
for (let x = 0; x < width; x++) {
|
|
15
|
+
if (alphaAt(x, y) > alphaThreshold) {
|
|
16
|
+
top = y;
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// Полностью прозрачное изображение → окно 1×1 в левом-верхнем углу.
|
|
22
|
+
if (top === -1) {
|
|
23
|
+
return { trimmed: true, trimX: 0, trimY: 0, w: 1, h: 1 };
|
|
24
|
+
}
|
|
25
|
+
let bottom = top;
|
|
26
|
+
for (let y = height - 1; y > top; y--) {
|
|
27
|
+
let found = false;
|
|
28
|
+
for (let x = 0; x < width; x++) {
|
|
29
|
+
if (alphaAt(x, y) > alphaThreshold) {
|
|
30
|
+
found = true;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (found) {
|
|
35
|
+
bottom = y;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (bottom < top)
|
|
40
|
+
bottom = top;
|
|
41
|
+
let left = width;
|
|
42
|
+
let right = -1;
|
|
43
|
+
for (let y = top; y <= bottom; y++) {
|
|
44
|
+
for (let x = 0; x < left; x++) {
|
|
45
|
+
if (alphaAt(x, y) > alphaThreshold) {
|
|
46
|
+
left = x;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (let x = width - 1; x > right; x--) {
|
|
51
|
+
if (alphaAt(x, y) > alphaThreshold) {
|
|
52
|
+
right = x;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const trimX = left;
|
|
58
|
+
const trimY = top;
|
|
59
|
+
const w = right - left + 1;
|
|
60
|
+
const h = bottom - top + 1;
|
|
61
|
+
const trimmed = trimX !== 0 || trimY !== 0 || w !== width || h !== height;
|
|
62
|
+
return { trimmed, trimX, trimY, w, h };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Стабильный контент-хэш тримленной области кадра — для detectIdentical.
|
|
66
|
+
* Хэшируется w×h + байты окна (trimX,trimY,w,h) поверх исходного буфера, без материализации.
|
|
67
|
+
* FNV-1a (детерминированный, зависимостей не тянет; коллизии для дедупа не критичны — при равном
|
|
68
|
+
* хэше detectIdentical может сверить байты, но на практике FNV-1a 32-бит достаточно для группировки).
|
|
69
|
+
*/
|
|
70
|
+
export function hashTrimmedRegion(image, trim) {
|
|
71
|
+
const { data, width } = image;
|
|
72
|
+
let hash = 0x811c9dc5; // FNV offset basis
|
|
73
|
+
// Размеры в хэш, чтобы кадры разного размера не схлопывались.
|
|
74
|
+
hash = fnvStep(hash, trim.w & 0xff);
|
|
75
|
+
hash = fnvStep(hash, (trim.w >>> 8) & 0xff);
|
|
76
|
+
hash = fnvStep(hash, trim.h & 0xff);
|
|
77
|
+
hash = fnvStep(hash, (trim.h >>> 8) & 0xff);
|
|
78
|
+
for (let y = trim.trimY; y < trim.trimY + trim.h; y++) {
|
|
79
|
+
for (let x = trim.trimX; x < trim.trimX + trim.w; x++) {
|
|
80
|
+
const i = (y * width + x) * 4;
|
|
81
|
+
hash = fnvStep(hash, data[i]);
|
|
82
|
+
hash = fnvStep(hash, data[i + 1]);
|
|
83
|
+
hash = fnvStep(hash, data[i + 2]);
|
|
84
|
+
hash = fnvStep(hash, data[i + 3]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return (hash >>> 0).toString(16);
|
|
88
|
+
}
|
|
89
|
+
function fnvStep(hash, byte) {
|
|
90
|
+
hash ^= byte;
|
|
91
|
+
// hash *= 16777619 (mod 2^32), через Math.imul для 32-бит переполнения.
|
|
92
|
+
return Math.imul(hash, 0x01000193);
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=trim.js.map
|
package/dist/trim.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trim.js","sourceRoot":"","sources":["../src/trim.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core geometry contract of @gamzix/packer.
|
|
3
|
+
*
|
|
4
|
+
* COORDINATE-SPACE INVARIANTS (do not violate — emitters and the compositor both rely on them):
|
|
5
|
+
*
|
|
6
|
+
* 1. `w`/`h` of a PackedFrame are the TRIMMED size in UNROTATED orientation.
|
|
7
|
+
* A rotated frame physically occupies `h × w` pixels in the page at (`x`, `y`).
|
|
8
|
+
* 2. `trimX`/`trimY` are the left/top whitespace offsets in UNROTATED source space.
|
|
9
|
+
* They are rotation-independent: rotation happens at composite time, trim bookkeeping does not change.
|
|
10
|
+
* 3. `rotated: true` means the sprite is stored rotated 90° clockwise in the page.
|
|
11
|
+
* Emitters map it to `"rotated": true` (Pixi JSON) and `rotate: 270` (Spine .atlas) —
|
|
12
|
+
* the mapping is pinned by production Gamzix runtimes; never change one without the other.
|
|
13
|
+
* 4. Spine offsets are Y-up: `offsetY = sourceH - trimY - h` (bottom whitespace).
|
|
14
|
+
* 5. Trimming is lossless by default (`alphaThreshold: 0` — only fully transparent pixels removed),
|
|
15
|
+
* so a trimmed build must render pixel-identically to an untrimmed one.
|
|
16
|
+
*/
|
|
17
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
18
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
19
|
+
/** A source sprite handed to the packer: decoded raw RGBA, premultiplication is the caller's business. */
|
|
20
|
+
export interface InputImage {
|
|
21
|
+
/** Frame name as it should appear in the emitted atlas (no extension). */
|
|
22
|
+
name: string;
|
|
23
|
+
/** Raw RGBA8888 pixel data, length === width * height * 4. */
|
|
24
|
+
data: Buffer;
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
}
|
|
28
|
+
export interface PackOptions {
|
|
29
|
+
/** Max page dimensions (square), e.g. 4096. */
|
|
30
|
+
maxWidth: number;
|
|
31
|
+
maxHeight: number;
|
|
32
|
+
/** Transparent spacing between frames, px. */
|
|
33
|
+
padding: number;
|
|
34
|
+
/** Edge-bleed duplication around each frame, px. */
|
|
35
|
+
extrude: number;
|
|
36
|
+
/** Allow 90° CW rotation of frames for tighter packing. */
|
|
37
|
+
allowRotation: boolean;
|
|
38
|
+
/** Strip fully transparent borders (alpha <= alphaThreshold). */
|
|
39
|
+
allowTrim: boolean;
|
|
40
|
+
/** Alpha value treated as "empty" during trim. 0 = lossless. */
|
|
41
|
+
alphaThreshold: number;
|
|
42
|
+
/** Collapse byte-identical inputs into one packed frame with aliases. */
|
|
43
|
+
detectIdentical: boolean;
|
|
44
|
+
/** Auto-pick the best max page size (see AutoSizeConfig). */
|
|
45
|
+
autoSize: AutoSizeConfig;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Авто-подбор лучшего размера страницы. Свипует кандидатные квадратные размеры через ОДНО
|
|
49
|
+
* размещение (без композитинга — дёшево), считает метрики каждого и выбирает лучший по objective.
|
|
50
|
+
*/
|
|
51
|
+
export interface AutoSizeConfig {
|
|
52
|
+
/** Включить свип; иначе используется maxWidth/maxHeight как есть. */
|
|
53
|
+
enabled: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Цель оптимизации:
|
|
56
|
+
* - 'vram' — минимум суммарной площади страниц (Σ w*h);
|
|
57
|
+
* - 'potVram' — минимум суммарной площади с округлением каждой страницы до степени двойки
|
|
58
|
+
* (для рантаймов/GPU, требующих POT-текстуры);
|
|
59
|
+
* - 'fill' — максимум общего fill-rate (Σ контента / Σ площадей).
|
|
60
|
+
*/
|
|
61
|
+
objective: 'vram' | 'potVram' | 'fill';
|
|
62
|
+
/** Кандидатные квадратные размеры. null → авто-лестница степеней двойки до maxWidth. */
|
|
63
|
+
candidates: number[] | null;
|
|
64
|
+
}
|
|
65
|
+
export declare const DEFAULT_PACK_OPTIONS: Readonly<PackOptions>;
|
|
66
|
+
/** One placed sprite. See coordinate-space invariants at the top of this file. */
|
|
67
|
+
export interface PackedFrame {
|
|
68
|
+
name: string;
|
|
69
|
+
/** Top-left of the frame's pixels inside the page (past padding/extrude). */
|
|
70
|
+
x: number;
|
|
71
|
+
y: number;
|
|
72
|
+
/** Trimmed size, unrotated orientation. */
|
|
73
|
+
w: number;
|
|
74
|
+
h: number;
|
|
75
|
+
rotated: boolean;
|
|
76
|
+
trimmed: boolean;
|
|
77
|
+
/** Left/top whitespace removed by trim, unrotated source space. 0 when untrimmed. */
|
|
78
|
+
trimX: number;
|
|
79
|
+
trimY: number;
|
|
80
|
+
/** Original (untrimmed) source size. */
|
|
81
|
+
sourceW: number;
|
|
82
|
+
sourceH: number;
|
|
83
|
+
/** Names that resolve to this frame via detectIdentical (excluding `name` itself). */
|
|
84
|
+
aliases: string[];
|
|
85
|
+
}
|
|
86
|
+
export interface PackedPage {
|
|
87
|
+
/** 0-based, stable across runs for identical input (deterministic ordering). */
|
|
88
|
+
pageIndex: number;
|
|
89
|
+
width: number;
|
|
90
|
+
height: number;
|
|
91
|
+
frames: PackedFrame[];
|
|
92
|
+
/** Composited page pixels, raw RGBA8888 — encode once per output format downstream. */
|
|
93
|
+
data: Buffer;
|
|
94
|
+
}
|
|
95
|
+
export interface PackResult {
|
|
96
|
+
pages: PackedPage[];
|
|
97
|
+
/** Fill efficiency per page: sum of frame areas / page area (удобный дубль report.pages[].fillRate). */
|
|
98
|
+
efficiency: number[];
|
|
99
|
+
/** Метрики эффективности выбранной упаковки (мониторинг). */
|
|
100
|
+
report: PackReport;
|
|
101
|
+
/** При autoSize.enabled — метрики каждого оценённого кандидата размера (для сравнения/отчёта). */
|
|
102
|
+
sizeComparison?: SizeEvaluation[];
|
|
103
|
+
}
|
|
104
|
+
/** Метрики одной страницы (мониторинг эффективности). */
|
|
105
|
+
export interface PageMetrics {
|
|
106
|
+
pageIndex: number;
|
|
107
|
+
width: number;
|
|
108
|
+
height: number;
|
|
109
|
+
frameCount: number;
|
|
110
|
+
/** Σ площадей контента размещённых кадров (неповёрнутые w*h). */
|
|
111
|
+
usedArea: number;
|
|
112
|
+
/** usedArea / (width*height) — заполненность страницы. */
|
|
113
|
+
fillRate: number;
|
|
114
|
+
/** width*height*4 — VRAM, если страница грузится в GPU в объявленном размере. */
|
|
115
|
+
vramBytes: number;
|
|
116
|
+
/** VRAM, если рантайм округляет размеры страницы до степени двойки. */
|
|
117
|
+
potVramBytes: number;
|
|
118
|
+
}
|
|
119
|
+
/** Сводные метрики упаковки под конкретный max-размер. */
|
|
120
|
+
export interface PackReport {
|
|
121
|
+
/** Квадратный max-размер (cap), под которым упакованы страницы. */
|
|
122
|
+
maxSize: number;
|
|
123
|
+
pages: PageMetrics[];
|
|
124
|
+
pageCount: number;
|
|
125
|
+
totalUsedArea: number;
|
|
126
|
+
totalPageArea: number;
|
|
127
|
+
overallFillRate: number;
|
|
128
|
+
totalVramBytes: number;
|
|
129
|
+
totalPotVramBytes: number;
|
|
130
|
+
}
|
|
131
|
+
/** Оценка одного кандидата размера при авто-подборе. */
|
|
132
|
+
export interface SizeEvaluation {
|
|
133
|
+
/** Кандидатный квадратный размер (maxWidth==maxHeight==size). */
|
|
134
|
+
size: number;
|
|
135
|
+
report: PackReport;
|
|
136
|
+
}
|
|
137
|
+
/** Result of trimming one input: the crop window in source space. */
|
|
138
|
+
export interface TrimResult {
|
|
139
|
+
trimmed: boolean;
|
|
140
|
+
/** Left/top offset of retained content. */
|
|
141
|
+
trimX: number;
|
|
142
|
+
trimY: number;
|
|
143
|
+
/** Retained content size. Equals source size when not trimmed. */
|
|
144
|
+
w: number;
|
|
145
|
+
h: number;
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core geometry contract of @gamzix/packer.
|
|
3
|
+
*
|
|
4
|
+
* COORDINATE-SPACE INVARIANTS (do not violate — emitters and the compositor both rely on them):
|
|
5
|
+
*
|
|
6
|
+
* 1. `w`/`h` of a PackedFrame are the TRIMMED size in UNROTATED orientation.
|
|
7
|
+
* A rotated frame physically occupies `h × w` pixels in the page at (`x`, `y`).
|
|
8
|
+
* 2. `trimX`/`trimY` are the left/top whitespace offsets in UNROTATED source space.
|
|
9
|
+
* They are rotation-independent: rotation happens at composite time, trim bookkeeping does not change.
|
|
10
|
+
* 3. `rotated: true` means the sprite is stored rotated 90° clockwise in the page.
|
|
11
|
+
* Emitters map it to `"rotated": true` (Pixi JSON) and `rotate: 270` (Spine .atlas) —
|
|
12
|
+
* the mapping is pinned by production Gamzix runtimes; never change one without the other.
|
|
13
|
+
* 4. Spine offsets are Y-up: `offsetY = sourceH - trimY - h` (bottom whitespace).
|
|
14
|
+
* 5. Trimming is lossless by default (`alphaThreshold: 0` — only fully transparent pixels removed),
|
|
15
|
+
* so a trimmed build must render pixel-identically to an untrimmed one.
|
|
16
|
+
*/
|
|
17
|
+
export const DEFAULT_PACK_OPTIONS = {
|
|
18
|
+
maxWidth: 4096,
|
|
19
|
+
maxHeight: 4096,
|
|
20
|
+
padding: 2,
|
|
21
|
+
extrude: 1,
|
|
22
|
+
allowRotation: true,
|
|
23
|
+
allowTrim: false,
|
|
24
|
+
alphaThreshold: 0,
|
|
25
|
+
detectIdentical: true,
|
|
26
|
+
autoSize: { enabled: false, objective: 'vram', candidates: null },
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAmDH,MAAM,CAAC,MAAM,oBAAoB,GAA0B;IACvD,QAAQ,EAAE,IAAI;IACd,SAAS,EAAE,IAAI;IACf,OAAO,EAAE,CAAC;IACV,OAAO,EAAE,CAAC;IACV,aAAa,EAAE,IAAI;IACnB,SAAS,EAAE,KAAK;IAChB,cAAc,EAAE,CAAC;IACjB,eAAe,EAAE,IAAI;IACrB,QAAQ,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE;CACpE,CAAC"}
|