lotio 1.0.0-test

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.
@@ -0,0 +1,410 @@
1
+ // Load WASM module
2
+ let Module = null;
3
+
4
+ export async function initLotio(wasmPath = './lotio.wasm') {
5
+ // Load Emscripten module
6
+ let jsPath = wasmPath.replace('.wasm', '.js');
7
+
8
+ // Keep relative paths as-is - script tags resolve relative to the page's base URL
9
+ // Script tags work best with relative paths like './browser/lotio.js'
10
+ // Only convert to absolute if it's already an absolute URL (http/https)
11
+ if (!jsPath.startsWith('http://') && !jsPath.startsWith('https://') && !jsPath.startsWith('//')) {
12
+ // Path is relative - keep it relative for script tags
13
+ // Script tags will resolve it relative to the current page automatically
14
+ // No need to convert to absolute path
15
+ }
16
+
17
+ // Check if already loaded
18
+ if (typeof createLotioModule !== 'undefined') {
19
+ Module = await createLotioModule();
20
+ return true;
21
+ }
22
+
23
+ // Load the module using a script tag approach for CommonJS compatibility
24
+ return new Promise((resolve, reject) => {
25
+ // Load the script
26
+ const script = document.createElement('script');
27
+ script.src = jsPath;
28
+ script.async = true;
29
+ script.onload = () => {
30
+ // Wait for the script to execute and define createLotioModule
31
+ const tryLoad = () => {
32
+ // The script should define createLotioModule globally
33
+ const factory = window.createLotioModule || globalThis.createLotioModule || (typeof createLotioModule !== 'undefined' ? createLotioModule : null);
34
+ if (typeof factory === 'function') {
35
+ factory().then(mod => {
36
+ Module = mod;
37
+ // Verify Module has required functions and properties
38
+ if (!Module._malloc || !Module._free || !Module._lotio_init) {
39
+ console.error('Module object:', Module);
40
+ console.error('Available functions:', Object.keys(Module).filter(k => typeof Module[k] === 'function').slice(0, 20));
41
+ reject(new Error('Module is missing required functions (_malloc, _free, or _lotio_init)'));
42
+ return;
43
+ }
44
+ // HEAP arrays should be exported directly, but if not, try to create them
45
+ // First check if they're already available
46
+ if (Module.HEAP8 && Module.HEAP32 && Module.HEAPF32 && Module.HEAPU8) {
47
+ console.log('HEAP arrays already available');
48
+ } else {
49
+ // Try to find the memory buffer
50
+ let memoryBuffer = null;
51
+
52
+ // Check if HEAP arrays exist but weren't detected
53
+ if (Module.HEAP8 && Module.HEAP8.buffer) {
54
+ memoryBuffer = Module.HEAP8.buffer;
55
+ } else if (Module.HEAPU8 && Module.HEAPU8.buffer) {
56
+ memoryBuffer = Module.HEAPU8.buffer;
57
+ } else if (Module.memory && Module.memory.buffer) {
58
+ memoryBuffer = Module.memory.buffer;
59
+ } else if (Module.buffer) {
60
+ memoryBuffer = Module.buffer;
61
+ } else {
62
+ // Last resort: try to get memory from WASM instance
63
+ // The Module might have the memory internally
64
+ console.warn('HEAP arrays not found, but continuing - they may be created on first use');
65
+ // Don't reject, let's see if they get created when needed
66
+ }
67
+
68
+ if (memoryBuffer) {
69
+ // Create HEAP arrays from the buffer
70
+ Module.HEAP8 = new Int8Array(memoryBuffer);
71
+ Module.HEAPU8 = new Uint8Array(memoryBuffer);
72
+ Module.HEAP16 = new Int16Array(memoryBuffer);
73
+ Module.HEAPU16 = new Uint16Array(memoryBuffer);
74
+ Module.HEAP32 = new Int32Array(memoryBuffer);
75
+ Module.HEAPU32 = new Uint32Array(memoryBuffer);
76
+ Module.HEAPF32 = new Float32Array(memoryBuffer);
77
+ Module.HEAPF64 = new Float64Array(memoryBuffer);
78
+ console.log('Created HEAP arrays from memory buffer');
79
+ }
80
+ }
81
+ console.log('Module initialized successfully');
82
+ resolve(true);
83
+ }).catch(reject);
84
+ } else {
85
+ // Retry after a short delay
86
+ if (typeof window.createLotioModule === 'undefined' && typeof createLotioModule === 'undefined') {
87
+ setTimeout(tryLoad, 50);
88
+ } else {
89
+ console.error('Available globals:', Object.keys(window).filter(k => k.includes('Lotio') || k.includes('lotio') || k.includes('create')));
90
+ reject(new Error('createLotioModule not found after loading script. Check console for available globals.'));
91
+ }
92
+ }
93
+ };
94
+ tryLoad();
95
+ };
96
+ script.onerror = (error) => {
97
+ reject(new Error(`Failed to load script: ${jsPath} - ${error}`));
98
+ };
99
+ document.head.appendChild(script);
100
+ });
101
+ }
102
+
103
+ export function createAnimation(jsonData, layerOverrides = null, textPadding = 0.97, textMeasurementMode = 'accurate') {
104
+ if (!Module) {
105
+ throw new Error('WASM module not initialized. Call initLotio() first.');
106
+ }
107
+
108
+ // Check if Module has the required functions
109
+ if (!Module._malloc || !Module._free || !Module._lotio_init) {
110
+ console.error('Module object:', Module);
111
+ console.error('Available functions:', Object.keys(Module).filter(k => typeof Module[k] === 'function'));
112
+ throw new Error('Module is not properly initialized. Missing _malloc, _free, or _lotio_init functions.');
113
+ }
114
+
115
+ // Ensure HEAP arrays are available - create them from memory if needed
116
+ if (!Module.HEAP32 || !Module.HEAPF32 || !Module.HEAPU8) {
117
+ // Try to find the memory buffer
118
+ let memoryBuffer = null;
119
+
120
+ // Check various ways the memory might be exposed
121
+ if (Module.HEAP8 && Module.HEAP8.buffer) {
122
+ memoryBuffer = Module.HEAP8.buffer;
123
+ } else if (Module.HEAPU8 && Module.HEAPU8.buffer) {
124
+ memoryBuffer = Module.HEAPU8.buffer;
125
+ } else if (Module.memory && Module.memory.buffer) {
126
+ memoryBuffer = Module.memory.buffer;
127
+ } else if (Module.buffer) {
128
+ memoryBuffer = Module.buffer;
129
+ } else {
130
+ // Try to get memory from WASM instance
131
+ // After first malloc, memory should be initialized
132
+ // Allocate a small buffer to trigger memory initialization
133
+ const testPtr = Module._malloc(1);
134
+ if (testPtr !== null && testPtr !== 0) {
135
+ // Now try to access memory
136
+ // The memory should be available via Module.HEAP* after first allocation
137
+ if (Module.HEAP8) {
138
+ memoryBuffer = Module.HEAP8.buffer;
139
+ } else {
140
+ Module._free(testPtr);
141
+ throw new Error('Cannot access WASM memory buffer even after allocation');
142
+ }
143
+ Module._free(testPtr);
144
+ } else {
145
+ throw new Error('Failed to allocate test memory - WASM memory not initialized');
146
+ }
147
+ }
148
+
149
+ if (memoryBuffer) {
150
+ Module.HEAP8 = new Int8Array(memoryBuffer);
151
+ Module.HEAPU8 = new Uint8Array(memoryBuffer);
152
+ Module.HEAP16 = new Int16Array(memoryBuffer);
153
+ Module.HEAPU16 = new Uint16Array(memoryBuffer);
154
+ Module.HEAP32 = new Int32Array(memoryBuffer);
155
+ Module.HEAPU32 = new Uint32Array(memoryBuffer);
156
+ Module.HEAPF32 = new Float32Array(memoryBuffer);
157
+ Module.HEAPF64 = new Float64Array(memoryBuffer);
158
+ }
159
+ }
160
+
161
+ const jsonStr = typeof jsonData === 'string' ? jsonData : JSON.stringify(jsonData);
162
+ const layerOverridesStr = layerOverrides ? (typeof layerOverrides === 'string' ? layerOverrides : JSON.stringify(layerOverrides)) : null;
163
+
164
+ // Convert textMeasurementMode string to integer (0=FAST, 1=ACCURATE, 2=PIXEL_PERFECT)
165
+ const modeStr = String(textMeasurementMode).toLowerCase();
166
+ let modeInt = 1; // Default to ACCURATE
167
+ if (modeStr === 'fast') {
168
+ modeInt = 0;
169
+ } else if (modeStr === 'accurate') {
170
+ modeInt = 1;
171
+ } else if (modeStr === 'pixel-perfect' || modeStr === 'pixelperfect') {
172
+ modeInt = 2;
173
+ }
174
+
175
+ // Allocate memory using exported _malloc
176
+ const jsonPtr = Module._malloc(jsonStr.length + 1);
177
+ if (!jsonPtr) {
178
+ throw new Error('Failed to allocate memory for JSON data');
179
+ }
180
+ Module.stringToUTF8(jsonStr, jsonPtr, jsonStr.length + 1);
181
+
182
+ let layerOverridesPtr = 0;
183
+ let layerOverridesLen = 0;
184
+ if (layerOverridesStr) {
185
+ layerOverridesPtr = Module._malloc(layerOverridesStr.length + 1);
186
+ if (!layerOverridesPtr) {
187
+ Module._free(jsonPtr);
188
+ throw new Error('Failed to allocate memory for layer overrides');
189
+ }
190
+ layerOverridesLen = layerOverridesStr.length;
191
+ Module.stringToUTF8(layerOverridesStr, layerOverridesPtr, layerOverridesStr.length + 1);
192
+ }
193
+
194
+ const result = Module._lotio_init(jsonPtr, jsonStr.length, layerOverridesPtr, layerOverridesLen, textPadding, modeInt);
195
+
196
+ Module._free(jsonPtr);
197
+ if (layerOverridesPtr) {
198
+ Module._free(layerOverridesPtr);
199
+ }
200
+
201
+ if (result !== 0) {
202
+ throw new Error('Failed to initialize animation');
203
+ }
204
+
205
+ // Get animation info
206
+ const widthPtr = Module._malloc(4);
207
+ const heightPtr = Module._malloc(4);
208
+ const durationPtr = Module._malloc(4);
209
+ const fpsPtr = Module._malloc(4);
210
+
211
+ Module._lotio_get_info(widthPtr, heightPtr, durationPtr, fpsPtr);
212
+
213
+ const info = {
214
+ width: Module.HEAP32[widthPtr / 4],
215
+ height: Module.HEAP32[heightPtr / 4],
216
+ duration: Module.HEAPF32[durationPtr / 4],
217
+ fps: Module.HEAPF32[fpsPtr / 4]
218
+ };
219
+
220
+ Module._free(widthPtr);
221
+ Module._free(heightPtr);
222
+ Module._free(durationPtr);
223
+ Module._free(fpsPtr);
224
+
225
+ return info;
226
+ }
227
+
228
+ export function renderFrameRGBA(time) {
229
+ if (!Module) {
230
+ throw new Error('WASM module not initialized.');
231
+ }
232
+
233
+ // Ensure HEAP arrays are available - use same logic as createAnimation
234
+ if (!Module.HEAP32 || !Module.HEAPU8) {
235
+ let memoryBuffer = null;
236
+ if (Module.HEAP8 && Module.HEAP8.buffer) {
237
+ memoryBuffer = Module.HEAP8.buffer;
238
+ } else if (Module.HEAPU8 && Module.HEAPU8.buffer) {
239
+ memoryBuffer = Module.HEAPU8.buffer;
240
+ } else if (Module.memory && Module.memory.buffer) {
241
+ memoryBuffer = Module.memory.buffer;
242
+ } else {
243
+ // Trigger memory initialization with a test allocation
244
+ const testPtr = Module._malloc(1);
245
+ if (testPtr !== null && testPtr !== 0) {
246
+ if (Module.HEAP8) {
247
+ memoryBuffer = Module.HEAP8.buffer;
248
+ }
249
+ Module._free(testPtr);
250
+ }
251
+ }
252
+ if (memoryBuffer) {
253
+ Module.HEAPU8 = new Uint8Array(memoryBuffer);
254
+ Module.HEAP32 = new Int32Array(memoryBuffer);
255
+ }
256
+ }
257
+
258
+ // Get animation info to determine buffer size
259
+ const widthPtr = Module._malloc(4);
260
+ const heightPtr = Module._malloc(4);
261
+ Module._lotio_get_info(widthPtr, heightPtr, null, null);
262
+ const width = Module.HEAP32[widthPtr / 4];
263
+ const height = Module.HEAP32[heightPtr / 4];
264
+ Module._free(widthPtr);
265
+ Module._free(heightPtr);
266
+
267
+ const bufferSize = width * height * 4; // RGBA
268
+ const bufferPtr = Module._malloc(bufferSize);
269
+
270
+ const result = Module._lotio_render_frame(time, bufferPtr, bufferSize);
271
+
272
+ if (result !== 0) {
273
+ Module._free(bufferPtr);
274
+ const errorMsg = result === 1 ? 'Animation not initialized' :
275
+ result === 2 ? 'Buffer too small' :
276
+ result === 3 ? 'Failed to create surface' :
277
+ result === 4 ? 'Failed to create image snapshot' :
278
+ result === 5 ? 'Failed to read pixels' :
279
+ result === 6 ? 'Failed to create conversion surface' :
280
+ result === 7 ? 'Failed to create converted image' :
281
+ `Unknown error: ${result}`;
282
+ throw new Error(`Failed to render frame: ${errorMsg} (code ${result})`);
283
+ }
284
+
285
+ // Copy data to Uint8Array
286
+ // Create a view into the WASM memory
287
+ const bufferView = new Uint8Array(Module.HEAPU8.buffer, bufferPtr, bufferSize);
288
+ // Create a copy to avoid issues with memory being freed
289
+ const rgba = new Uint8Array(bufferView);
290
+
291
+ Module._free(bufferPtr);
292
+
293
+ return { rgba, width, height };
294
+ }
295
+
296
+ export function getVersion() {
297
+ if (!Module) {
298
+ throw new Error('WASM module not initialized. Call initLotio() first.');
299
+ }
300
+ return Module.UTF8ToString(Module._lotio_get_version());
301
+ }
302
+
303
+ export function registerFont(fontName, fontData) {
304
+ if (!Module) {
305
+ throw new Error('WASM module not initialized. Call initLotio() first.');
306
+ }
307
+
308
+ if (!Module._lotio_register_font) {
309
+ throw new Error('Font registration not available. Rebuild with _lotio_register_font exported.');
310
+ }
311
+
312
+ // Ensure HEAP arrays are available
313
+ if (!Module.HEAPU8) {
314
+ if (Module.memory && Module.memory.buffer) {
315
+ Module.HEAPU8 = new Uint8Array(Module.memory.buffer);
316
+ } else {
317
+ throw new Error('Cannot access WASM memory for font registration.');
318
+ }
319
+ }
320
+
321
+ // Allocate memory for font data
322
+ const fontDataPtr = Module._malloc(fontData.length);
323
+ if (!fontDataPtr) {
324
+ throw new Error('Failed to allocate memory for font data');
325
+ }
326
+
327
+ // Copy font data to WASM memory
328
+ Module.HEAPU8.set(fontData, fontDataPtr);
329
+
330
+ // Allocate memory for font name
331
+ const fontNamePtr = Module._malloc(fontName.length + 1);
332
+ if (!fontNamePtr) {
333
+ Module._free(fontDataPtr);
334
+ throw new Error('Failed to allocate memory for font name');
335
+ }
336
+ Module.stringToUTF8(fontName, fontNamePtr, fontName.length + 1);
337
+
338
+ // Register font
339
+ const result = Module._lotio_register_font(fontNamePtr, fontDataPtr, fontData.length);
340
+
341
+ // Free memory
342
+ Module._free(fontDataPtr);
343
+ Module._free(fontNamePtr);
344
+
345
+ if (result !== 0) {
346
+ throw new Error(`Failed to register font: ${fontName} (error code: ${result})`);
347
+ }
348
+
349
+ console.log(`Font registered: ${fontName}`);
350
+ }
351
+
352
+ export function cleanup() {
353
+ if (Module) {
354
+ Module._lotio_cleanup();
355
+ }
356
+ }
357
+
358
+ // Helper to render frame to canvas
359
+ export function renderFrameToCanvas(canvas, time, bgColor = '#2a2a2a') {
360
+ const { rgba, width, height } = renderFrameRGBA(time);
361
+
362
+ // Set canvas size
363
+ canvas.width = width;
364
+ canvas.height = height;
365
+
366
+ const ctx = canvas.getContext('2d');
367
+
368
+ // Fill with background color first
369
+ ctx.fillStyle = bgColor;
370
+ ctx.fillRect(0, 0, width, height);
371
+
372
+ const imageData = ctx.createImageData(width, height);
373
+
374
+ // Copy RGBA data - ensure we have the right length
375
+ if (rgba.length !== width * height * 4) {
376
+ console.error(`RGBA buffer size mismatch: expected ${width * height * 4}, got ${rgba.length}`);
377
+ throw new Error(`Invalid RGBA buffer size: expected ${width * height * 4}, got ${rgba.length}`);
378
+ }
379
+
380
+ // Create a temporary canvas to composite the image over the background
381
+ // This ensures the background color shows through transparent areas
382
+ const tempCanvas = document.createElement('canvas');
383
+ tempCanvas.width = width;
384
+ tempCanvas.height = height;
385
+ const tempCtx = tempCanvas.getContext('2d');
386
+
387
+ // Fill temp canvas with background color
388
+ tempCtx.fillStyle = bgColor;
389
+ tempCtx.fillRect(0, 0, width, height);
390
+
391
+ // Draw the RGBA image data on top
392
+ imageData.data.set(rgba);
393
+ tempCtx.putImageData(imageData, 0, 0);
394
+
395
+ // Draw the composited result to the main canvas
396
+ ctx.drawImage(tempCanvas, 0, 0);
397
+ }
398
+
399
+ // Helper to render frame as ImageData
400
+ export function renderFrameAsImageData(time) {
401
+ const { rgba, width, height } = renderFrameRGBA(time);
402
+ const canvas = document.createElement('canvas');
403
+ canvas.width = width;
404
+ canvas.height = height;
405
+ const ctx = canvas.getContext('2d');
406
+ const imageData = ctx.createImageData(width, height);
407
+ imageData.data.set(rgba);
408
+ return imageData;
409
+ }
410
+
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "lotio",
3
+ "version": "1.0.0-test",
4
+ "description": "High-performance Lottie animation frame renderer for the browser",
5
+ "main": "browser/lotio.js",
6
+ "module": "browser/lotio.js",
7
+ "files": [
8
+ "browser"
9
+ ],
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/matrunchyk/lotio.git"
13
+ },
14
+ "keywords": [
15
+ "lottie",
16
+ "animation",
17
+ "renderer",
18
+ "skia",
19
+ "webassembly",
20
+ "wasm"
21
+ ],
22
+ "author": "matrunchyk",
23
+ "license": "MIT"
24
+ }