@openfairygui/functions 0.2.0-alpha.2 → 0.2.0-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -6
- package/dist/atlas-C6tbl7nn.d.ts +193 -0
- package/dist/atlas-CHsu2Y8i.d.cts +193 -0
- package/dist/index.cjs +16 -3603
- package/dist/index.d.cts +5 -294
- package/dist/index.d.ts +5 -294
- package/dist/index.js +3 -3594
- package/dist/node.cjs +256 -0
- package/dist/node.d.cts +36 -0
- package/dist/node.d.ts +36 -0
- package/dist/node.js +254 -0
- package/dist/publish-BJ_eelME.js +3267 -0
- package/dist/publish-xFWT9Slz.cjs +3338 -0
- package/dist/restore-BW2xacB3.cjs +936 -0
- package/dist/restore-BeWaJNjR.d.cts +288 -0
- package/dist/restore-Clk62n0O.js +931 -0
- package/dist/restore-Dh0-Nvms.d.ts +288 -0
- package/dist/uam-transaction.cjs +44 -1
- package/dist/uam-transaction.d.cts +16 -1
- package/dist/uam-transaction.d.ts +16 -1
- package/dist/uam-transaction.js +44 -1
- package/dist/web.cjs +274 -0
- package/dist/web.d.cts +41 -0
- package/dist/web.d.ts +41 -0
- package/dist/web.js +273 -0
- package/package.json +28 -4
- package/src/adapters/node/plugins.ts +82 -0
- package/src/adapters/node/publish.ts +130 -0
- package/src/adapters/node/restore.ts +187 -0
- package/src/adapters/web/publish.ts +159 -0
- package/src/adapters/web/raster.ts +251 -0
- package/src/atlas/font.ts +95 -0
- package/src/atlas/inputs.ts +515 -0
- package/src/atlas/jta.ts +211 -0
- package/src/atlas/packing.ts +767 -0
- package/src/atlas.ts +116 -1221
- package/src/codegen.ts +106 -67
- package/src/index.ts +43 -3
- package/src/node.ts +8 -0
- package/src/plugins/types.ts +56 -0
- package/src/publish/contracts.ts +80 -0
- package/src/publish/external-resources.ts +117 -0
- package/src/publish/options.ts +180 -0
- package/src/publish/package-context.ts +608 -0
- package/src/publish/resource-references.ts +210 -0
- package/src/publish.ts +290 -968
- package/src/restore-internals/font.ts +100 -0
- package/src/restore-internals/movie-clip.ts +104 -0
- package/src/restore-internals/output-transaction.ts +164 -0
- package/src/restore.ts +112 -311
- package/src/shared-types.ts +4 -8
- package/src/uam-transaction.ts +68 -0
- package/src/utils.ts +28 -0
- package/src/web.ts +11 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AtlasRasterBackend,
|
|
3
|
+
AtlasRasterInput,
|
|
4
|
+
AtlasRasterPipeline,
|
|
5
|
+
AtlasRasterResolvedBuffer,
|
|
6
|
+
PublishFileSystem,
|
|
7
|
+
PublishSourceFileSystem,
|
|
8
|
+
} from '../../publish/contracts.js';
|
|
9
|
+
|
|
10
|
+
type BrowserCanvas = OffscreenCanvas | HTMLCanvasElement;
|
|
11
|
+
|
|
12
|
+
interface BrowserContext {
|
|
13
|
+
clearRect(x: number, y: number, width: number, height: number): void;
|
|
14
|
+
drawImage(image: CanvasImageSource, dx: number, dy: number): void;
|
|
15
|
+
drawImage(image: CanvasImageSource, dx: number, dy: number, dw: number, dh: number): void;
|
|
16
|
+
drawImage(
|
|
17
|
+
image: CanvasImageSource,
|
|
18
|
+
sx: number,
|
|
19
|
+
sy: number,
|
|
20
|
+
sw: number,
|
|
21
|
+
sh: number,
|
|
22
|
+
dx: number,
|
|
23
|
+
dy: number,
|
|
24
|
+
dw: number,
|
|
25
|
+
dh: number,
|
|
26
|
+
): void;
|
|
27
|
+
fillRect(x: number, y: number, width: number, height: number): void;
|
|
28
|
+
getImageData(sx: number, sy: number, sw: number, sh: number): ImageData;
|
|
29
|
+
rotate(angle: number): void;
|
|
30
|
+
restore(): void;
|
|
31
|
+
save(): void;
|
|
32
|
+
translate(x: number, y: number): void;
|
|
33
|
+
fillStyle: string | CanvasGradient | CanvasPattern;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface BrowserRaster {
|
|
37
|
+
canvas: BrowserCanvas;
|
|
38
|
+
width: number;
|
|
39
|
+
height: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getBrowserContext(canvas: BrowserCanvas): BrowserContext {
|
|
43
|
+
const context = canvas.getContext('2d');
|
|
44
|
+
if (!context) throw new Error('publishBrowser: a 2D canvas context is unavailable.');
|
|
45
|
+
return context as unknown as BrowserContext;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createBrowserCanvas(width: number, height: number): BrowserCanvas {
|
|
49
|
+
if (typeof OffscreenCanvas !== 'undefined') return new OffscreenCanvas(width, height);
|
|
50
|
+
if (typeof globalThis.document === 'undefined') {
|
|
51
|
+
throw new Error('publishBrowser: OffscreenCanvas or a DOM canvas is required for atlas PNG generation.');
|
|
52
|
+
}
|
|
53
|
+
const canvas = globalThis.document.createElement('canvas');
|
|
54
|
+
canvas.width = width;
|
|
55
|
+
canvas.height = height;
|
|
56
|
+
return canvas;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function assertBrowserImageSupport(): void {
|
|
60
|
+
if (typeof createImageBitmap !== 'function') {
|
|
61
|
+
throw new Error('publishBrowser: createImageBitmap is required for atlas PNG generation.');
|
|
62
|
+
}
|
|
63
|
+
if (typeof OffscreenCanvas === 'undefined' && typeof globalThis.document === 'undefined') {
|
|
64
|
+
throw new Error('publishBrowser: OffscreenCanvas or a DOM canvas is required for atlas PNG generation.');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function createRaster(
|
|
69
|
+
width: number,
|
|
70
|
+
height: number,
|
|
71
|
+
background?: { r: number; g: number; b: number; alpha: number },
|
|
72
|
+
): BrowserRaster {
|
|
73
|
+
const canvas = createBrowserCanvas(width, height);
|
|
74
|
+
const context = getBrowserContext(canvas);
|
|
75
|
+
context.clearRect(0, 0, width, height);
|
|
76
|
+
if (background && background.alpha > 0) {
|
|
77
|
+
context.fillStyle = `rgba(${background.r}, ${background.g}, ${background.b}, ${background.alpha})`;
|
|
78
|
+
context.fillRect(0, 0, width, height);
|
|
79
|
+
}
|
|
80
|
+
return { canvas, width, height };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function imageMimeType(path: string): string {
|
|
84
|
+
if (/\.svg$/iu.test(path)) return 'image/svg+xml';
|
|
85
|
+
if (/\.jpe?g$/iu.test(path)) return 'image/jpeg';
|
|
86
|
+
if (/\.webp$/iu.test(path)) return 'image/webp';
|
|
87
|
+
if (/\.gif$/iu.test(path)) return 'image/gif';
|
|
88
|
+
return 'image/png';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function imageMimeTypeFromBytes(bytes: Uint8Array): string {
|
|
92
|
+
if (bytes[0] === 0xff && bytes[1] === 0xd8) return 'image/jpeg';
|
|
93
|
+
if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46) return 'image/gif';
|
|
94
|
+
if (bytes[0] === 0x52 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x46) return 'image/webp';
|
|
95
|
+
return 'image/png';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function canvasToPng(canvas: BrowserCanvas): Promise<Uint8Array> {
|
|
99
|
+
let blob: Blob;
|
|
100
|
+
if ('convertToBlob' in canvas && typeof canvas.convertToBlob === 'function') {
|
|
101
|
+
blob = await canvas.convertToBlob({ type: 'image/png' });
|
|
102
|
+
} else {
|
|
103
|
+
blob = await new Promise<Blob>((resolve, reject) => {
|
|
104
|
+
(canvas as HTMLCanvasElement).toBlob((value) => {
|
|
105
|
+
if (value) resolve(value);
|
|
106
|
+
else reject(new Error('publishBrowser: canvas PNG encoding failed.'));
|
|
107
|
+
}, 'image/png');
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return new Uint8Array(await blob.arrayBuffer());
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function decodeRaster(bytes: Uint8Array, mimeType: string): Promise<BrowserRaster> {
|
|
114
|
+
if (typeof createImageBitmap !== 'function') {
|
|
115
|
+
throw new Error('publishBrowser: createImageBitmap is required for atlas PNG generation.');
|
|
116
|
+
}
|
|
117
|
+
const copy = bytes.slice();
|
|
118
|
+
const bitmap = await createImageBitmap(new Blob([copy.buffer as ArrayBuffer], { type: mimeType }));
|
|
119
|
+
try {
|
|
120
|
+
const raster = createRaster(bitmap.width, bitmap.height);
|
|
121
|
+
getBrowserContext(raster.canvas).drawImage(bitmap, 0, 0);
|
|
122
|
+
return raster;
|
|
123
|
+
} finally {
|
|
124
|
+
bitmap.close();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
class BrowserImagePipeline implements AtlasRasterPipeline {
|
|
129
|
+
private rawOutput = false;
|
|
130
|
+
|
|
131
|
+
constructor(
|
|
132
|
+
private raster: Promise<BrowserRaster>,
|
|
133
|
+
private readonly decode: (bytes: Uint8Array) => Promise<BrowserRaster>,
|
|
134
|
+
private readonly write: (path: string, data: Uint8Array) => Promise<void>,
|
|
135
|
+
) {}
|
|
136
|
+
|
|
137
|
+
ensureAlpha(): this {
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
resize(options: { width: number; height: number; fit?: 'fill' }): this {
|
|
142
|
+
this.raster = this.raster.then((source) => {
|
|
143
|
+
const target = createRaster(options.width, options.height);
|
|
144
|
+
getBrowserContext(target.canvas).drawImage(source.canvas, 0, 0, options.width, options.height);
|
|
145
|
+
return target;
|
|
146
|
+
});
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
raw(): this {
|
|
151
|
+
this.rawOutput = true;
|
|
152
|
+
return this;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
extract(options: { left: number; top: number; width: number; height: number }): this {
|
|
156
|
+
this.raster = this.raster.then((source) => {
|
|
157
|
+
const target = createRaster(options.width, options.height);
|
|
158
|
+
getBrowserContext(target.canvas).drawImage(
|
|
159
|
+
source.canvas,
|
|
160
|
+
options.left,
|
|
161
|
+
options.top,
|
|
162
|
+
options.width,
|
|
163
|
+
options.height,
|
|
164
|
+
0,
|
|
165
|
+
0,
|
|
166
|
+
options.width,
|
|
167
|
+
options.height,
|
|
168
|
+
);
|
|
169
|
+
return target;
|
|
170
|
+
});
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
png(): this {
|
|
175
|
+
this.rawOutput = false;
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
rotate(angle: number): this {
|
|
180
|
+
this.raster = this.raster.then((source) => {
|
|
181
|
+
if (angle % 180 === 0) return source;
|
|
182
|
+
const target = createRaster(source.height, source.width);
|
|
183
|
+
const context = getBrowserContext(target.canvas);
|
|
184
|
+
context.save();
|
|
185
|
+
if (angle === 270 || angle === -90) {
|
|
186
|
+
context.translate(0, source.width);
|
|
187
|
+
context.rotate(-Math.PI / 2);
|
|
188
|
+
} else {
|
|
189
|
+
context.translate(source.height, 0);
|
|
190
|
+
context.rotate(Math.PI / 2);
|
|
191
|
+
}
|
|
192
|
+
context.drawImage(source.canvas, 0, 0);
|
|
193
|
+
context.restore();
|
|
194
|
+
return target;
|
|
195
|
+
});
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
composite(inputs: Array<{ input: Uint8Array; left: number; top: number }>): this {
|
|
200
|
+
this.raster = this.raster.then(async (target) => {
|
|
201
|
+
const context = getBrowserContext(target.canvas);
|
|
202
|
+
for (const input of inputs) {
|
|
203
|
+
const source = await this.decode(input.input);
|
|
204
|
+
context.drawImage(source.canvas, input.left, input.top);
|
|
205
|
+
}
|
|
206
|
+
return target;
|
|
207
|
+
});
|
|
208
|
+
return this;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async metadata(): Promise<{ width: number; height: number; channels: number; hasAlpha: boolean }> {
|
|
212
|
+
const raster = await this.raster;
|
|
213
|
+
return { width: raster.width, height: raster.height, channels: 4, hasAlpha: true };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async toBuffer(options: { resolveWithObject: true }): Promise<AtlasRasterResolvedBuffer>;
|
|
217
|
+
async toBuffer(options?: { resolveWithObject?: false }): Promise<Uint8Array>;
|
|
218
|
+
async toBuffer(options?: { resolveWithObject?: boolean }): Promise<Uint8Array | AtlasRasterResolvedBuffer> {
|
|
219
|
+
const raster = await this.raster;
|
|
220
|
+
if (options?.resolveWithObject) {
|
|
221
|
+
const data = getBrowserContext(raster.canvas).getImageData(0, 0, raster.width, raster.height).data;
|
|
222
|
+
return { data: new Uint8Array(data), info: { width: raster.width, height: raster.height, channels: 4 } };
|
|
223
|
+
}
|
|
224
|
+
if (this.rawOutput)
|
|
225
|
+
return new Uint8Array(
|
|
226
|
+
getBrowserContext(raster.canvas).getImageData(0, 0, raster.width, raster.height).data,
|
|
227
|
+
);
|
|
228
|
+
return canvasToPng(raster.canvas);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async toFile(path: string): Promise<void> {
|
|
232
|
+
const raster = await this.raster;
|
|
233
|
+
await this.write(path, await canvasToPng(raster.canvas));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function createBrowserImageEncoder(
|
|
238
|
+
sourceFileSystem: PublishSourceFileSystem,
|
|
239
|
+
outputFileSystem: PublishFileSystem,
|
|
240
|
+
): AtlasRasterBackend {
|
|
241
|
+
const decode = (bytes: Uint8Array) => decodeRaster(bytes, imageMimeTypeFromBytes(bytes));
|
|
242
|
+
return (input: AtlasRasterInput): BrowserImagePipeline => {
|
|
243
|
+
const raster =
|
|
244
|
+
typeof input === 'string'
|
|
245
|
+
? sourceFileSystem.readFileRaw(input).then((bytes) => decodeRaster(bytes, imageMimeType(input)))
|
|
246
|
+
: input instanceof Uint8Array
|
|
247
|
+
? decode(input)
|
|
248
|
+
: Promise.resolve(createRaster(input.create.width, input.create.height, input.create.background));
|
|
249
|
+
return new BrowserImagePipeline(raster, decode, outputFileSystem.writeFileRaw);
|
|
250
|
+
};
|
|
251
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export interface ParsedFontGlyph {
|
|
2
|
+
charId: number;
|
|
3
|
+
img: string | null;
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
xoffset: number;
|
|
7
|
+
yoffset: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
xadvance: number;
|
|
11
|
+
channel: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ParsedFont {
|
|
15
|
+
hasFace: boolean;
|
|
16
|
+
colored: boolean;
|
|
17
|
+
resizable: boolean;
|
|
18
|
+
hasChannel: boolean;
|
|
19
|
+
fontSize: number;
|
|
20
|
+
xadvance: number;
|
|
21
|
+
lineHeight: number;
|
|
22
|
+
glyphs: ParsedFontGlyph[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Parse a BMFont .fnt text file into structured data for binary encoding. */
|
|
26
|
+
export function parseFnt(text: string): ParsedFont {
|
|
27
|
+
const lines = text.split(/\r?\n/);
|
|
28
|
+
let hasFace = false;
|
|
29
|
+
let colored = false;
|
|
30
|
+
let resizable = false;
|
|
31
|
+
let hasChannel = false;
|
|
32
|
+
let fontSize = 0;
|
|
33
|
+
let globalXadvance = 0;
|
|
34
|
+
let lineHeight = 0;
|
|
35
|
+
const glyphs: ParsedFontGlyph[] = [];
|
|
36
|
+
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
const trimmed = line.trim();
|
|
39
|
+
if (!trimmed) continue;
|
|
40
|
+
const parts = trimmed.split(/\s+/);
|
|
41
|
+
const attrs: Record<string, string> = {};
|
|
42
|
+
for (let index = 1; index < parts.length; index += 1) {
|
|
43
|
+
const entry = parts[index]?.split('=') ?? [];
|
|
44
|
+
if (entry.length === 2 && entry[0]) attrs[entry[0]] = entry[1] ?? '';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
switch (parts[0]) {
|
|
48
|
+
case 'info':
|
|
49
|
+
hasFace = attrs.face != null;
|
|
50
|
+
colored = hasFace;
|
|
51
|
+
if (attrs.colored !== undefined) colored = attrs.colored === 'true';
|
|
52
|
+
fontSize = parseInt(attrs.size ?? '', 10) || 0;
|
|
53
|
+
resizable = attrs.resizable === 'true';
|
|
54
|
+
break;
|
|
55
|
+
case 'common':
|
|
56
|
+
lineHeight = parseInt(attrs.lineHeight ?? '', 10) || 0;
|
|
57
|
+
globalXadvance = parseInt(attrs.xadvance ?? '', 10) || 0;
|
|
58
|
+
if (fontSize === 0) fontSize = lineHeight;
|
|
59
|
+
else if (lineHeight === 0) lineHeight = fontSize;
|
|
60
|
+
break;
|
|
61
|
+
case 'char': {
|
|
62
|
+
const charId = parseInt(attrs.id ?? '', 10) || 0;
|
|
63
|
+
if (charId === 0) continue;
|
|
64
|
+
const img = attrs.img || null;
|
|
65
|
+
if (!hasFace && !img) continue;
|
|
66
|
+
const channel = parseInt(attrs.chnl ?? '', 10) || 0;
|
|
67
|
+
if (channel !== 0 && channel !== 15) hasChannel = true;
|
|
68
|
+
glyphs.push({
|
|
69
|
+
charId,
|
|
70
|
+
img,
|
|
71
|
+
x: parseInt(attrs.x ?? '', 10) || 0,
|
|
72
|
+
y: parseInt(attrs.y ?? '', 10) || 0,
|
|
73
|
+
xoffset: parseInt(attrs.xoffset ?? '', 10) || 0,
|
|
74
|
+
yoffset: parseInt(attrs.yoffset ?? '', 10) || 0,
|
|
75
|
+
width: parseInt(attrs.width ?? '', 10) || 0,
|
|
76
|
+
height: parseInt(attrs.height ?? '', 10) || 0,
|
|
77
|
+
xadvance: parseInt(attrs.xadvance ?? '', 10) || 0,
|
|
78
|
+
channel,
|
|
79
|
+
});
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
hasFace,
|
|
87
|
+
colored,
|
|
88
|
+
resizable: fontSize > 0 ? resizable : false,
|
|
89
|
+
hasChannel,
|
|
90
|
+
fontSize,
|
|
91
|
+
xadvance: globalXadvance,
|
|
92
|
+
lineHeight,
|
|
93
|
+
glyphs,
|
|
94
|
+
};
|
|
95
|
+
}
|