@shotstack/shotstack-canvas 1.0.9 → 1.1.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/dist/entry.node.cjs +558 -294
- package/dist/entry.node.cjs.map +1 -1
- package/dist/entry.node.d.cts +20 -1
- package/dist/entry.node.d.ts +20 -1
- package/dist/entry.node.js +555 -292
- package/dist/entry.node.js.map +1 -1
- package/dist/entry.web.d.ts +20 -1
- package/dist/entry.web.js +523 -268
- package/dist/entry.web.js.map +1 -1
- package/package.json +10 -4
- /package/{assets/wasm → public}/hb.wasm +0 -0
package/dist/entry.node.cjs
CHANGED
|
@@ -30,7 +30,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/env/entry.node.ts
|
|
31
31
|
var entry_node_exports = {};
|
|
32
32
|
__export(entry_node_exports, {
|
|
33
|
-
createTextEngine: () => createTextEngine
|
|
33
|
+
createTextEngine: () => createTextEngine,
|
|
34
|
+
isGlyphFill: () => isGlyphFill2,
|
|
35
|
+
isShadowFill: () => isShadowFill2
|
|
34
36
|
});
|
|
35
37
|
module.exports = __toCommonJS(entry_node_exports);
|
|
36
38
|
|
|
@@ -161,35 +163,31 @@ var RichTextAssetSchema = import_joi.default.object({
|
|
|
161
163
|
}).unknown(false);
|
|
162
164
|
|
|
163
165
|
// src/wasm/hb-loader.ts
|
|
164
|
-
var import_meta = {};
|
|
165
166
|
var hbSingleton = null;
|
|
166
167
|
async function initHB(wasmBaseURL) {
|
|
167
168
|
if (hbSingleton) return hbSingleton;
|
|
168
|
-
const harfbuzzjs = await import("harfbuzzjs");
|
|
169
|
-
const factory = harfbuzzjs.default;
|
|
170
|
-
let hbUrlFromImport;
|
|
171
169
|
try {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (path.endsWith(".wasm")) {
|
|
182
|
-
return `${base}/${path}`.replace(/([^:])\/{2,}/g, "$1/");
|
|
170
|
+
const mod = await import("harfbuzzjs");
|
|
171
|
+
const candidate = mod.default;
|
|
172
|
+
let hb;
|
|
173
|
+
if (typeof candidate === "function") {
|
|
174
|
+
hb = await candidate();
|
|
175
|
+
} else if (candidate && typeof candidate.then === "function") {
|
|
176
|
+
hb = await candidate;
|
|
177
|
+
} else {
|
|
178
|
+
hb = candidate;
|
|
183
179
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
180
|
+
if (!hb || typeof hb.createBuffer !== "function" || typeof hb.createFont !== "function") {
|
|
181
|
+
throw new Error("Failed to initialize HarfBuzz: unexpected export shape from 'harfbuzzjs'.");
|
|
182
|
+
}
|
|
183
|
+
void wasmBaseURL;
|
|
184
|
+
hbSingleton = hb;
|
|
185
|
+
return hbSingleton;
|
|
186
|
+
} catch (err) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
`Failed to initialize HarfBuzz: ${err instanceof Error ? err.message : String(err)}`
|
|
189
|
+
);
|
|
191
190
|
}
|
|
192
|
-
return hbSingleton;
|
|
193
191
|
}
|
|
194
192
|
|
|
195
193
|
// src/core/font-registry.ts
|
|
@@ -212,7 +210,13 @@ var FontRegistry = class {
|
|
|
212
210
|
return;
|
|
213
211
|
}
|
|
214
212
|
this.initPromise = this._doInit();
|
|
215
|
-
|
|
213
|
+
try {
|
|
214
|
+
await this.initPromise;
|
|
215
|
+
} catch (err) {
|
|
216
|
+
throw new Error(
|
|
217
|
+
`Failed to initialize FontRegistry: ${err instanceof Error ? err.message : String(err)}`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
216
220
|
}
|
|
217
221
|
async _doInit() {
|
|
218
222
|
try {
|
|
@@ -224,7 +228,13 @@ var FontRegistry = class {
|
|
|
224
228
|
}
|
|
225
229
|
async getHB() {
|
|
226
230
|
if (!this.hb) {
|
|
227
|
-
|
|
231
|
+
try {
|
|
232
|
+
await this.init();
|
|
233
|
+
} catch (err) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
`Failed to get HarfBuzz instance: ${err instanceof Error ? err.message : String(err)}`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
228
238
|
}
|
|
229
239
|
return this.hb;
|
|
230
240
|
}
|
|
@@ -232,48 +242,111 @@ var FontRegistry = class {
|
|
|
232
242
|
return `${desc.family}__${desc.weight ?? "400"}__${desc.style ?? "normal"}`;
|
|
233
243
|
}
|
|
234
244
|
async registerFromBytes(bytes, desc) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
245
|
+
try {
|
|
246
|
+
if (!this.hb) await this.init();
|
|
247
|
+
const k = this.key(desc);
|
|
248
|
+
if (this.fonts.has(k)) return;
|
|
249
|
+
const blob = this.hb.createBlob(bytes);
|
|
250
|
+
const face = this.hb.createFace(blob, 0);
|
|
251
|
+
const font = this.hb.createFont(face);
|
|
252
|
+
const upem = face.upem || 1e3;
|
|
253
|
+
font.setScale(upem, upem);
|
|
254
|
+
this.blobs.set(k, blob);
|
|
255
|
+
this.faces.set(k, face);
|
|
256
|
+
this.fonts.set(k, font);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
throw new Error(
|
|
259
|
+
`Failed to register font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
246
262
|
}
|
|
247
263
|
async getFont(desc) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
264
|
+
try {
|
|
265
|
+
if (!this.hb) await this.init();
|
|
266
|
+
const k = this.key(desc);
|
|
267
|
+
const f = this.fonts.get(k);
|
|
268
|
+
if (!f) throw new Error(`Font not registered for ${k}`);
|
|
269
|
+
return f;
|
|
270
|
+
} catch (err) {
|
|
271
|
+
if (err instanceof Error && err.message.includes("Font not registered")) {
|
|
272
|
+
throw err;
|
|
273
|
+
}
|
|
274
|
+
throw new Error(
|
|
275
|
+
`Failed to get font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
253
278
|
}
|
|
254
279
|
async getFace(desc) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
280
|
+
try {
|
|
281
|
+
if (!this.hb) await this.init();
|
|
282
|
+
const k = this.key(desc);
|
|
283
|
+
return this.faces.get(k);
|
|
284
|
+
} catch (err) {
|
|
285
|
+
throw new Error(
|
|
286
|
+
`Failed to get face for "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
287
|
+
);
|
|
288
|
+
}
|
|
258
289
|
}
|
|
259
290
|
async getUnitsPerEm(desc) {
|
|
260
|
-
|
|
261
|
-
|
|
291
|
+
try {
|
|
292
|
+
const face = await this.getFace(desc);
|
|
293
|
+
return face?.upem || 1e3;
|
|
294
|
+
} catch (err) {
|
|
295
|
+
throw new Error(
|
|
296
|
+
`Failed to get units per em for "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
297
|
+
);
|
|
298
|
+
}
|
|
262
299
|
}
|
|
263
300
|
async glyphPath(desc, glyphId) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
301
|
+
try {
|
|
302
|
+
const font = await this.getFont(desc);
|
|
303
|
+
const path = font.glyphToPath(glyphId);
|
|
304
|
+
return path && path !== "" ? path : "M 0 0";
|
|
305
|
+
} catch (err) {
|
|
306
|
+
throw new Error(
|
|
307
|
+
`Failed to get glyph path for glyph ${glyphId} in font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
267
310
|
}
|
|
268
311
|
destroy() {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
312
|
+
try {
|
|
313
|
+
for (const [, f] of this.fonts) {
|
|
314
|
+
try {
|
|
315
|
+
f.destroy();
|
|
316
|
+
} catch (err) {
|
|
317
|
+
console.error(
|
|
318
|
+
`Error destroying font: ${err instanceof Error ? err.message : String(err)}`
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
for (const [, f] of this.faces) {
|
|
323
|
+
try {
|
|
324
|
+
f.destroy();
|
|
325
|
+
} catch (err) {
|
|
326
|
+
console.error(
|
|
327
|
+
`Error destroying face: ${err instanceof Error ? err.message : String(err)}`
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
for (const [, b] of this.blobs) {
|
|
332
|
+
try {
|
|
333
|
+
b.destroy();
|
|
334
|
+
} catch (err) {
|
|
335
|
+
console.error(
|
|
336
|
+
`Error destroying blob: ${err instanceof Error ? err.message : String(err)}`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
this.fonts.clear();
|
|
341
|
+
this.faces.clear();
|
|
342
|
+
this.blobs.clear();
|
|
343
|
+
this.hb = void 0;
|
|
344
|
+
this.initPromise = void 0;
|
|
345
|
+
} catch (err) {
|
|
346
|
+
console.error(
|
|
347
|
+
`Error during FontRegistry cleanup: ${err instanceof Error ? err.message : String(err)}`
|
|
348
|
+
);
|
|
349
|
+
}
|
|
277
350
|
}
|
|
278
351
|
};
|
|
279
352
|
|
|
@@ -295,112 +368,145 @@ var LayoutEngine = class {
|
|
|
295
368
|
}
|
|
296
369
|
}
|
|
297
370
|
async shapeFull(text, desc) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
371
|
+
try {
|
|
372
|
+
const hb = await this.fonts.getHB();
|
|
373
|
+
const buffer = hb.createBuffer();
|
|
374
|
+
try {
|
|
375
|
+
buffer.addText(text);
|
|
376
|
+
buffer.guessSegmentProperties();
|
|
377
|
+
const font = await this.fonts.getFont(desc);
|
|
378
|
+
const face = await this.fonts.getFace(desc);
|
|
379
|
+
const upem = face?.upem || 1e3;
|
|
380
|
+
font.setScale(upem, upem);
|
|
381
|
+
hb.shape(font, buffer);
|
|
382
|
+
const result = buffer.json();
|
|
383
|
+
return result;
|
|
384
|
+
} finally {
|
|
385
|
+
try {
|
|
386
|
+
buffer.destroy();
|
|
387
|
+
} catch (err) {
|
|
388
|
+
console.error(
|
|
389
|
+
`Error destroying HarfBuzz buffer: ${err instanceof Error ? err.message : String(err)}`
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
} catch (err) {
|
|
394
|
+
throw new Error(
|
|
395
|
+
`Failed to shape text with font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
396
|
+
);
|
|
397
|
+
}
|
|
310
398
|
}
|
|
311
399
|
async layout(params) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const shaped = await this.shapeFull(input, desc);
|
|
318
|
-
const face = await this.fonts.getFace(desc);
|
|
319
|
-
const upem = face?.upem || 1e3;
|
|
320
|
-
const scale = fontSize / upem;
|
|
321
|
-
const glyphs = shaped.map((g) => {
|
|
322
|
-
const charIndex = g.cl;
|
|
323
|
-
let char;
|
|
324
|
-
if (charIndex >= 0 && charIndex < input.length) {
|
|
325
|
-
char = input[charIndex];
|
|
400
|
+
try {
|
|
401
|
+
const { textTransform, desc, fontSize, letterSpacing, width } = params;
|
|
402
|
+
const input = this.transformText(params.text, textTransform);
|
|
403
|
+
if (!input || input.length === 0) {
|
|
404
|
+
return [];
|
|
326
405
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
cluster: g.cl,
|
|
333
|
-
char
|
|
334
|
-
// This now correctly maps to the original character
|
|
335
|
-
};
|
|
336
|
-
});
|
|
337
|
-
const lines = [];
|
|
338
|
-
let currentLine = [];
|
|
339
|
-
let currentWidth = 0;
|
|
340
|
-
const spaceIndices = /* @__PURE__ */ new Set();
|
|
341
|
-
for (let i = 0; i < input.length; i++) {
|
|
342
|
-
if (input[i] === " ") {
|
|
343
|
-
spaceIndices.add(i);
|
|
406
|
+
let shaped;
|
|
407
|
+
try {
|
|
408
|
+
shaped = await this.shapeFull(input, desc);
|
|
409
|
+
} catch (err) {
|
|
410
|
+
throw new Error(`Text shaping failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
344
411
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
412
|
+
let upem;
|
|
413
|
+
try {
|
|
414
|
+
const face = await this.fonts.getFace(desc);
|
|
415
|
+
upem = face?.upem || 1e3;
|
|
416
|
+
} catch (err) {
|
|
417
|
+
throw new Error(
|
|
418
|
+
`Failed to get font metrics: ${err instanceof Error ? err.message : String(err)}`
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
const scale = fontSize / upem;
|
|
422
|
+
const glyphs = shaped.map((g) => {
|
|
423
|
+
const charIndex = g.cl;
|
|
424
|
+
let char;
|
|
425
|
+
if (charIndex >= 0 && charIndex < input.length) {
|
|
426
|
+
char = input[charIndex];
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
id: g.g,
|
|
430
|
+
xAdvance: g.ax * scale + letterSpacing,
|
|
431
|
+
xOffset: g.dx * scale,
|
|
432
|
+
yOffset: -g.dy * scale,
|
|
433
|
+
cluster: g.cl,
|
|
434
|
+
char
|
|
435
|
+
};
|
|
436
|
+
});
|
|
437
|
+
const lines = [];
|
|
438
|
+
let currentLine = [];
|
|
439
|
+
let currentWidth = 0;
|
|
440
|
+
const spaceIndices = /* @__PURE__ */ new Set();
|
|
441
|
+
for (let i = 0; i < input.length; i++) {
|
|
442
|
+
if (input[i] === " ") {
|
|
443
|
+
spaceIndices.add(i);
|
|
357
444
|
}
|
|
358
|
-
currentLine = [];
|
|
359
|
-
currentWidth = 0;
|
|
360
|
-
lastBreakIndex = i;
|
|
361
|
-
continue;
|
|
362
445
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
} else {
|
|
376
|
-
lines.push({
|
|
377
|
-
glyphs: currentLine,
|
|
378
|
-
width: currentWidth,
|
|
379
|
-
y: 0
|
|
380
|
-
});
|
|
446
|
+
let lastBreakIndex = -1;
|
|
447
|
+
for (let i = 0; i < glyphs.length; i++) {
|
|
448
|
+
const glyph = glyphs[i];
|
|
449
|
+
const glyphWidth = glyph.xAdvance;
|
|
450
|
+
if (glyph.char === "\n") {
|
|
451
|
+
if (currentLine.length > 0) {
|
|
452
|
+
lines.push({
|
|
453
|
+
glyphs: currentLine,
|
|
454
|
+
width: currentWidth,
|
|
455
|
+
y: 0
|
|
456
|
+
});
|
|
457
|
+
}
|
|
381
458
|
currentLine = [];
|
|
382
459
|
currentWidth = 0;
|
|
460
|
+
lastBreakIndex = i;
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
if (currentWidth + glyphWidth > width && currentLine.length > 0) {
|
|
464
|
+
if (lastBreakIndex > -1) {
|
|
465
|
+
const breakPoint = lastBreakIndex - (i - currentLine.length) + 1;
|
|
466
|
+
const nextLine = currentLine.splice(breakPoint);
|
|
467
|
+
const lineWidth = currentLine.reduce((sum, g) => sum + g.xAdvance, 0);
|
|
468
|
+
lines.push({
|
|
469
|
+
glyphs: currentLine,
|
|
470
|
+
width: lineWidth,
|
|
471
|
+
y: 0
|
|
472
|
+
});
|
|
473
|
+
currentLine = nextLine;
|
|
474
|
+
currentWidth = nextLine.reduce((sum, g) => sum + g.xAdvance, 0);
|
|
475
|
+
} else {
|
|
476
|
+
lines.push({
|
|
477
|
+
glyphs: currentLine,
|
|
478
|
+
width: currentWidth,
|
|
479
|
+
y: 0
|
|
480
|
+
});
|
|
481
|
+
currentLine = [];
|
|
482
|
+
currentWidth = 0;
|
|
483
|
+
}
|
|
484
|
+
lastBreakIndex = -1;
|
|
485
|
+
}
|
|
486
|
+
currentLine.push(glyph);
|
|
487
|
+
currentWidth += glyphWidth;
|
|
488
|
+
if (spaceIndices.has(glyph.cluster)) {
|
|
489
|
+
lastBreakIndex = i;
|
|
383
490
|
}
|
|
384
|
-
lastBreakIndex = -1;
|
|
385
491
|
}
|
|
386
|
-
currentLine.
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
492
|
+
if (currentLine.length > 0) {
|
|
493
|
+
lines.push({
|
|
494
|
+
glyphs: currentLine,
|
|
495
|
+
width: currentWidth,
|
|
496
|
+
y: 0
|
|
497
|
+
});
|
|
390
498
|
}
|
|
499
|
+
const lineHeight = params.lineHeight * fontSize;
|
|
500
|
+
for (let i = 0; i < lines.length; i++) {
|
|
501
|
+
lines[i].y = (i + 1) * lineHeight;
|
|
502
|
+
}
|
|
503
|
+
return lines;
|
|
504
|
+
} catch (err) {
|
|
505
|
+
if (err instanceof Error) {
|
|
506
|
+
throw err;
|
|
507
|
+
}
|
|
508
|
+
throw new Error(`Layout failed: ${String(err)}`);
|
|
391
509
|
}
|
|
392
|
-
if (currentLine.length > 0) {
|
|
393
|
-
lines.push({
|
|
394
|
-
glyphs: currentLine,
|
|
395
|
-
width: currentWidth,
|
|
396
|
-
y: 0
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
const lineHeight = params.lineHeight * fontSize;
|
|
400
|
-
for (let i = 0; i < lines.length; i++) {
|
|
401
|
-
lines[i].y = (i + 1) * lineHeight;
|
|
402
|
-
}
|
|
403
|
-
return lines;
|
|
404
510
|
}
|
|
405
511
|
};
|
|
406
512
|
|
|
@@ -492,7 +598,6 @@ async function buildDrawOps(p) {
|
|
|
492
598
|
path,
|
|
493
599
|
x: glyphX + p.shadow.offsetX,
|
|
494
600
|
y: glyphY + p.shadow.offsetY,
|
|
495
|
-
// @ts-ignore scale propagated to painters
|
|
496
601
|
scale,
|
|
497
602
|
fill: { kind: "solid", color: p.shadow.color, opacity: p.shadow.opacity }
|
|
498
603
|
});
|
|
@@ -503,7 +608,6 @@ async function buildDrawOps(p) {
|
|
|
503
608
|
path,
|
|
504
609
|
x: glyphX,
|
|
505
610
|
y: glyphY,
|
|
506
|
-
// @ts-ignore scale propagated to painters
|
|
507
611
|
scale,
|
|
508
612
|
width: p.stroke.width,
|
|
509
613
|
color: p.stroke.color,
|
|
@@ -515,7 +619,6 @@ async function buildDrawOps(p) {
|
|
|
515
619
|
path,
|
|
516
620
|
x: glyphX,
|
|
517
621
|
y: glyphY,
|
|
518
|
-
// @ts-ignore scale propagated to painters
|
|
519
622
|
scale,
|
|
520
623
|
fill
|
|
521
624
|
});
|
|
@@ -1091,30 +1194,32 @@ async function createNodePainter(opts) {
|
|
|
1091
1194
|
continue;
|
|
1092
1195
|
}
|
|
1093
1196
|
if (op.op === "FillPath") {
|
|
1197
|
+
const fillOp = op;
|
|
1094
1198
|
ctx.save();
|
|
1095
|
-
ctx.translate(
|
|
1096
|
-
const s =
|
|
1199
|
+
ctx.translate(fillOp.x, fillOp.y);
|
|
1200
|
+
const s = fillOp.scale ?? 1;
|
|
1097
1201
|
ctx.scale(s, -s);
|
|
1098
1202
|
ctx.beginPath();
|
|
1099
|
-
drawSvgPathOnCtx(ctx,
|
|
1100
|
-
const bbox =
|
|
1101
|
-
const fill = makeGradientFromBBox(ctx,
|
|
1203
|
+
drawSvgPathOnCtx(ctx, fillOp.path);
|
|
1204
|
+
const bbox = fillOp.gradientBBox ?? globalBox;
|
|
1205
|
+
const fill = makeGradientFromBBox(ctx, fillOp.fill, bbox);
|
|
1102
1206
|
ctx.fillStyle = fill;
|
|
1103
1207
|
ctx.fill();
|
|
1104
1208
|
ctx.restore();
|
|
1105
1209
|
continue;
|
|
1106
1210
|
}
|
|
1107
1211
|
if (op.op === "StrokePath") {
|
|
1212
|
+
const strokeOp = op;
|
|
1108
1213
|
ctx.save();
|
|
1109
|
-
ctx.translate(
|
|
1110
|
-
const s =
|
|
1214
|
+
ctx.translate(strokeOp.x, strokeOp.y);
|
|
1215
|
+
const s = strokeOp.scale ?? 1;
|
|
1111
1216
|
ctx.scale(s, -s);
|
|
1112
1217
|
const invAbs = 1 / Math.abs(s);
|
|
1113
1218
|
ctx.beginPath();
|
|
1114
|
-
drawSvgPathOnCtx(ctx,
|
|
1115
|
-
const c = parseHex6(
|
|
1219
|
+
drawSvgPathOnCtx(ctx, strokeOp.path);
|
|
1220
|
+
const c = parseHex6(strokeOp.color, strokeOp.opacity);
|
|
1116
1221
|
ctx.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1117
|
-
ctx.lineWidth =
|
|
1222
|
+
ctx.lineWidth = strokeOp.width * invAbs;
|
|
1118
1223
|
ctx.lineJoin = "round";
|
|
1119
1224
|
ctx.lineCap = "round";
|
|
1120
1225
|
ctx.stroke();
|
|
@@ -1146,17 +1251,20 @@ function makeGradientFromBBox(ctx, spec, box) {
|
|
|
1146
1251
|
const c = parseHex6(spec.color, spec.opacity);
|
|
1147
1252
|
return `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1148
1253
|
}
|
|
1149
|
-
const cx = box.x + box.w / 2
|
|
1254
|
+
const cx = box.x + box.w / 2;
|
|
1255
|
+
const cy = box.y + box.h / 2;
|
|
1256
|
+
const r = Math.max(box.w, box.h) / 2;
|
|
1150
1257
|
const addStops = (g) => {
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1153
|
-
|
|
1258
|
+
const opacity = spec.kind === "linear" || spec.kind === "radial" ? spec.opacity : 1;
|
|
1259
|
+
const stops = spec.kind === "linear" || spec.kind === "radial" ? spec.stops : [];
|
|
1260
|
+
for (const s of stops) {
|
|
1261
|
+
const c = parseHex6(s.color, opacity);
|
|
1154
1262
|
g.addColorStop(s.offset, `rgba(${c.r},${c.g},${c.b},${c.a})`);
|
|
1155
1263
|
}
|
|
1156
1264
|
return g;
|
|
1157
1265
|
};
|
|
1158
1266
|
if (spec.kind === "linear") {
|
|
1159
|
-
const rad =
|
|
1267
|
+
const rad = spec.angle * Math.PI / 180;
|
|
1160
1268
|
const x1 = cx + Math.cos(rad + Math.PI) * r;
|
|
1161
1269
|
const y1 = cy + Math.sin(rad + Math.PI) * r;
|
|
1162
1270
|
const x2 = cx + Math.cos(rad) * r;
|
|
@@ -1167,22 +1275,34 @@ function makeGradientFromBBox(ctx, spec, box) {
|
|
|
1167
1275
|
}
|
|
1168
1276
|
}
|
|
1169
1277
|
function computeGlobalTextBounds(ops) {
|
|
1170
|
-
let minX = Infinity
|
|
1278
|
+
let minX = Infinity;
|
|
1279
|
+
let minY = Infinity;
|
|
1280
|
+
let maxX = -Infinity;
|
|
1281
|
+
let maxY = -Infinity;
|
|
1171
1282
|
for (const op of ops) {
|
|
1172
|
-
if (op.op !== "FillPath"
|
|
1173
|
-
const
|
|
1174
|
-
|
|
1175
|
-
const
|
|
1176
|
-
const
|
|
1177
|
-
const
|
|
1178
|
-
const
|
|
1283
|
+
if (op.op !== "FillPath") continue;
|
|
1284
|
+
const fillOp = op;
|
|
1285
|
+
if (fillOp.isShadow) continue;
|
|
1286
|
+
const b = computePathBounds2(fillOp.path);
|
|
1287
|
+
const s = fillOp.scale ?? 1;
|
|
1288
|
+
const x1 = fillOp.x + s * b.x;
|
|
1289
|
+
const x2 = fillOp.x + s * (b.x + b.w);
|
|
1290
|
+
const y1 = fillOp.y - s * (b.y + b.h);
|
|
1291
|
+
const y2 = fillOp.y - s * b.y;
|
|
1179
1292
|
if (x1 < minX) minX = x1;
|
|
1180
1293
|
if (y1 < minY) minY = y1;
|
|
1181
1294
|
if (x2 > maxX) maxX = x2;
|
|
1182
1295
|
if (y2 > maxY) maxY = y2;
|
|
1183
1296
|
}
|
|
1184
|
-
if (minX === Infinity)
|
|
1185
|
-
|
|
1297
|
+
if (minX === Infinity) {
|
|
1298
|
+
return { x: 0, y: 0, w: 1, h: 1 };
|
|
1299
|
+
}
|
|
1300
|
+
return {
|
|
1301
|
+
x: minX,
|
|
1302
|
+
y: minY,
|
|
1303
|
+
w: Math.max(1, maxX - minX),
|
|
1304
|
+
h: Math.max(1, maxY - minY)
|
|
1305
|
+
};
|
|
1186
1306
|
}
|
|
1187
1307
|
function drawSvgPathOnCtx(ctx, d) {
|
|
1188
1308
|
const t = tokenizePath2(d);
|
|
@@ -1224,13 +1344,18 @@ function drawSvgPathOnCtx(ctx, d) {
|
|
|
1224
1344
|
ctx.closePath();
|
|
1225
1345
|
break;
|
|
1226
1346
|
}
|
|
1347
|
+
default:
|
|
1348
|
+
break;
|
|
1227
1349
|
}
|
|
1228
1350
|
}
|
|
1229
1351
|
}
|
|
1230
1352
|
function computePathBounds2(d) {
|
|
1231
1353
|
const t = tokenizePath2(d);
|
|
1232
1354
|
let i = 0;
|
|
1233
|
-
let minX = Infinity
|
|
1355
|
+
let minX = Infinity;
|
|
1356
|
+
let minY = Infinity;
|
|
1357
|
+
let maxX = -Infinity;
|
|
1358
|
+
let maxY = -Infinity;
|
|
1234
1359
|
const touch = (x, y) => {
|
|
1235
1360
|
if (x < minX) minX = x;
|
|
1236
1361
|
if (y < minY) minY = y;
|
|
@@ -1270,10 +1395,19 @@ function computePathBounds2(d) {
|
|
|
1270
1395
|
}
|
|
1271
1396
|
case "Z":
|
|
1272
1397
|
break;
|
|
1398
|
+
default:
|
|
1399
|
+
break;
|
|
1273
1400
|
}
|
|
1274
1401
|
}
|
|
1275
|
-
if (minX === Infinity)
|
|
1276
|
-
|
|
1402
|
+
if (minX === Infinity) {
|
|
1403
|
+
return { x: 0, y: 0, w: 0, h: 0 };
|
|
1404
|
+
}
|
|
1405
|
+
return {
|
|
1406
|
+
x: minX,
|
|
1407
|
+
y: minY,
|
|
1408
|
+
w: maxX - minX,
|
|
1409
|
+
h: maxY - minY
|
|
1410
|
+
};
|
|
1277
1411
|
}
|
|
1278
1412
|
function roundRectPath(ctx, x, y, w, h, r) {
|
|
1279
1413
|
ctx.moveTo(x + r, y);
|
|
@@ -1302,20 +1436,54 @@ function bufferToArrayBuffer(buf) {
|
|
|
1302
1436
|
return ab.slice(byteOffset, byteOffset + byteLength);
|
|
1303
1437
|
}
|
|
1304
1438
|
async function loadFileOrHttpToArrayBuffer(pathOrUrl) {
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1439
|
+
try {
|
|
1440
|
+
if (/^https?:\/\//.test(pathOrUrl)) {
|
|
1441
|
+
const client = pathOrUrl.startsWith("https:") ? https : http;
|
|
1442
|
+
const buf2 = await new Promise((resolve, reject) => {
|
|
1443
|
+
const request = client.get(pathOrUrl, (res) => {
|
|
1444
|
+
const { statusCode } = res;
|
|
1445
|
+
if (statusCode && (statusCode < 200 || statusCode >= 300)) {
|
|
1446
|
+
reject(new Error(`HTTP request failed with status ${statusCode} for ${pathOrUrl}`));
|
|
1447
|
+
res.resume();
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
const chunks = [];
|
|
1451
|
+
res.on("data", (chunk) => {
|
|
1452
|
+
chunks.push(chunk);
|
|
1453
|
+
});
|
|
1454
|
+
res.on("end", () => {
|
|
1455
|
+
try {
|
|
1456
|
+
resolve(Buffer.concat(chunks));
|
|
1457
|
+
} catch (err) {
|
|
1458
|
+
reject(
|
|
1459
|
+
new Error(
|
|
1460
|
+
`Failed to concatenate response chunks: ${err instanceof Error ? err.message : String(err)}`
|
|
1461
|
+
)
|
|
1462
|
+
);
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
res.on("error", (err) => {
|
|
1466
|
+
reject(new Error(`Response error for ${pathOrUrl}: ${err.message}`));
|
|
1467
|
+
});
|
|
1468
|
+
});
|
|
1469
|
+
request.on("error", (err) => {
|
|
1470
|
+
reject(new Error(`Request error for ${pathOrUrl}: ${err.message}`));
|
|
1471
|
+
});
|
|
1472
|
+
request.setTimeout(3e4, () => {
|
|
1473
|
+
request.destroy();
|
|
1474
|
+
reject(new Error(`Request timeout after 30s for ${pathOrUrl}`));
|
|
1475
|
+
});
|
|
1476
|
+
});
|
|
1477
|
+
return bufferToArrayBuffer(buf2);
|
|
1478
|
+
}
|
|
1479
|
+
const buf = await (0, import_promises.readFile)(pathOrUrl);
|
|
1480
|
+
return bufferToArrayBuffer(buf);
|
|
1481
|
+
} catch (err) {
|
|
1482
|
+
if (err instanceof Error) {
|
|
1483
|
+
throw new Error(`Failed to load ${pathOrUrl}: ${err.message}`);
|
|
1484
|
+
}
|
|
1485
|
+
throw new Error(`Failed to load ${pathOrUrl}: ${String(err)}`);
|
|
1316
1486
|
}
|
|
1317
|
-
const buf = await (0, import_promises.readFile)(pathOrUrl);
|
|
1318
|
-
return bufferToArrayBuffer(buf);
|
|
1319
1487
|
}
|
|
1320
1488
|
|
|
1321
1489
|
// src/core/video-generator.ts
|
|
@@ -1451,6 +1619,14 @@ var VideoGenerator = class {
|
|
|
1451
1619
|
}
|
|
1452
1620
|
};
|
|
1453
1621
|
|
|
1622
|
+
// src/types.ts
|
|
1623
|
+
var isShadowFill2 = (op) => {
|
|
1624
|
+
return op.op === "FillPath" && op.isShadow === true;
|
|
1625
|
+
};
|
|
1626
|
+
var isGlyphFill2 = (op) => {
|
|
1627
|
+
return op.op === "FillPath" && op.isShadow !== true;
|
|
1628
|
+
};
|
|
1629
|
+
|
|
1454
1630
|
// src/env/entry.node.ts
|
|
1455
1631
|
async function createTextEngine(opts = {}) {
|
|
1456
1632
|
const width = opts.width ?? CANVAS_CONFIG.DEFAULTS.width;
|
|
@@ -1461,126 +1637,214 @@ async function createTextEngine(opts = {}) {
|
|
|
1461
1637
|
const fonts = new FontRegistry(wasmBaseURL);
|
|
1462
1638
|
const layout = new LayoutEngine(fonts);
|
|
1463
1639
|
const videoGenerator = new VideoGenerator();
|
|
1464
|
-
|
|
1640
|
+
try {
|
|
1641
|
+
await fonts.init();
|
|
1642
|
+
} catch (err) {
|
|
1643
|
+
throw new Error(
|
|
1644
|
+
`Failed to initialize font registry: ${err instanceof Error ? err.message : String(err)}`
|
|
1645
|
+
);
|
|
1646
|
+
}
|
|
1465
1647
|
async function ensureFonts(asset) {
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
const
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1648
|
+
try {
|
|
1649
|
+
if (asset.customFonts) {
|
|
1650
|
+
for (const cf of asset.customFonts) {
|
|
1651
|
+
try {
|
|
1652
|
+
const bytes = await loadFileOrHttpToArrayBuffer(cf.src);
|
|
1653
|
+
await fonts.registerFromBytes(bytes, {
|
|
1654
|
+
family: cf.family,
|
|
1655
|
+
weight: cf.weight ?? "400",
|
|
1656
|
+
style: cf.style ?? "normal"
|
|
1657
|
+
});
|
|
1658
|
+
} catch (err) {
|
|
1659
|
+
throw new Error(
|
|
1660
|
+
`Failed to load custom font "${cf.family}" from ${cf.src}: ${err instanceof Error ? err.message : String(err)}`
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1474
1664
|
}
|
|
1665
|
+
const main = asset.font ?? {
|
|
1666
|
+
family: "Roboto",
|
|
1667
|
+
weight: "400",
|
|
1668
|
+
style: "normal",
|
|
1669
|
+
size: 48,
|
|
1670
|
+
color: "#000000",
|
|
1671
|
+
opacity: 1
|
|
1672
|
+
};
|
|
1673
|
+
return main;
|
|
1674
|
+
} catch (err) {
|
|
1675
|
+
if (err instanceof Error) {
|
|
1676
|
+
throw err;
|
|
1677
|
+
}
|
|
1678
|
+
throw new Error(`Failed to ensure fonts: ${String(err)}`);
|
|
1475
1679
|
}
|
|
1476
|
-
const main = asset.font ?? {
|
|
1477
|
-
family: "Roboto",
|
|
1478
|
-
weight: "400",
|
|
1479
|
-
style: "normal",
|
|
1480
|
-
size: 48,
|
|
1481
|
-
color: "#000000",
|
|
1482
|
-
opacity: 1
|
|
1483
|
-
};
|
|
1484
|
-
return main;
|
|
1485
1680
|
}
|
|
1486
1681
|
return {
|
|
1487
1682
|
validate(input) {
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1683
|
+
try {
|
|
1684
|
+
const { value, error } = RichTextAssetSchema.validate(input, {
|
|
1685
|
+
abortEarly: false,
|
|
1686
|
+
convert: true
|
|
1687
|
+
});
|
|
1688
|
+
if (error) throw error;
|
|
1689
|
+
return { value };
|
|
1690
|
+
} catch (err) {
|
|
1691
|
+
if (err instanceof Error) {
|
|
1692
|
+
throw new Error(`Validation failed: ${err.message}`);
|
|
1693
|
+
}
|
|
1694
|
+
throw new Error(`Validation failed: ${String(err)}`);
|
|
1695
|
+
}
|
|
1494
1696
|
},
|
|
1495
1697
|
async registerFontFromFile(path, desc) {
|
|
1496
|
-
|
|
1497
|
-
|
|
1698
|
+
try {
|
|
1699
|
+
const bytes = await loadFileOrHttpToArrayBuffer(path);
|
|
1700
|
+
await fonts.registerFromBytes(bytes, desc);
|
|
1701
|
+
} catch (err) {
|
|
1702
|
+
throw new Error(
|
|
1703
|
+
`Failed to register font "${desc.family}" from file ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1498
1706
|
},
|
|
1499
1707
|
async registerFontFromUrl(url, desc) {
|
|
1500
|
-
|
|
1501
|
-
|
|
1708
|
+
try {
|
|
1709
|
+
const bytes = await loadFileOrHttpToArrayBuffer(url);
|
|
1710
|
+
await fonts.registerFromBytes(bytes, desc);
|
|
1711
|
+
} catch (err) {
|
|
1712
|
+
throw new Error(
|
|
1713
|
+
`Failed to register font "${desc.family}" from URL ${url}: ${err instanceof Error ? err.message : String(err)}`
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1502
1716
|
},
|
|
1503
1717
|
async renderFrame(asset, tSeconds) {
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1718
|
+
try {
|
|
1719
|
+
const main = await ensureFonts(asset);
|
|
1720
|
+
const desc = { family: main.family, weight: `${main.weight}`, style: main.style };
|
|
1721
|
+
let lines;
|
|
1722
|
+
try {
|
|
1723
|
+
lines = await layout.layout({
|
|
1724
|
+
text: asset.text,
|
|
1725
|
+
width: asset.width ?? width,
|
|
1726
|
+
letterSpacing: asset.style?.letterSpacing ?? 0,
|
|
1727
|
+
fontSize: main.size,
|
|
1728
|
+
lineHeight: asset.style?.lineHeight ?? 1.2,
|
|
1729
|
+
desc,
|
|
1730
|
+
textTransform: asset.style?.textTransform ?? "none"
|
|
1731
|
+
});
|
|
1732
|
+
} catch (err) {
|
|
1733
|
+
throw new Error(
|
|
1734
|
+
`Failed to layout text: ${err instanceof Error ? err.message : String(err)}`
|
|
1735
|
+
);
|
|
1736
|
+
}
|
|
1737
|
+
const textRect = {
|
|
1738
|
+
x: 0,
|
|
1739
|
+
y: 0,
|
|
1740
|
+
width: asset.width ?? width,
|
|
1741
|
+
height: asset.height ?? height
|
|
1742
|
+
};
|
|
1743
|
+
const canvasW = asset.width ?? width;
|
|
1744
|
+
const canvasH = asset.height ?? height;
|
|
1745
|
+
const canvasPR = asset.pixelRatio ?? pixelRatio;
|
|
1746
|
+
let ops0;
|
|
1747
|
+
try {
|
|
1748
|
+
ops0 = await buildDrawOps({
|
|
1749
|
+
canvas: { width: canvasW, height: canvasH, pixelRatio: canvasPR },
|
|
1750
|
+
textRect,
|
|
1751
|
+
lines,
|
|
1752
|
+
font: {
|
|
1753
|
+
family: main.family,
|
|
1754
|
+
size: main.size,
|
|
1755
|
+
weight: `${main.weight}`,
|
|
1756
|
+
style: main.style,
|
|
1757
|
+
color: main.color,
|
|
1758
|
+
opacity: main.opacity
|
|
1759
|
+
},
|
|
1760
|
+
style: {
|
|
1761
|
+
lineHeight: asset.style?.lineHeight ?? 1.2,
|
|
1762
|
+
textDecoration: asset.style?.textDecoration ?? "none",
|
|
1763
|
+
gradient: asset.style?.gradient
|
|
1764
|
+
},
|
|
1765
|
+
stroke: asset.stroke,
|
|
1766
|
+
shadow: asset.shadow,
|
|
1767
|
+
align: asset.align ?? { horizontal: "left", vertical: "middle" },
|
|
1768
|
+
background: asset.background,
|
|
1769
|
+
glyphPathProvider: (gid) => fonts.glyphPath(desc, gid),
|
|
1770
|
+
getUnitsPerEm: () => fonts.getUnitsPerEm(desc)
|
|
1771
|
+
});
|
|
1772
|
+
} catch (err) {
|
|
1773
|
+
throw new Error(
|
|
1774
|
+
`Failed to build draw operations: ${err instanceof Error ? err.message : String(err)}`
|
|
1775
|
+
);
|
|
1776
|
+
}
|
|
1777
|
+
try {
|
|
1778
|
+
const ops = applyAnimation(ops0, lines, {
|
|
1779
|
+
t: tSeconds,
|
|
1780
|
+
fontSize: main.size,
|
|
1781
|
+
anim: asset.animation ? {
|
|
1782
|
+
preset: asset.animation.preset,
|
|
1783
|
+
speed: asset.animation.speed,
|
|
1784
|
+
duration: asset.animation.duration,
|
|
1785
|
+
style: asset.animation.style,
|
|
1786
|
+
direction: asset.animation.direction
|
|
1787
|
+
} : void 0
|
|
1788
|
+
});
|
|
1789
|
+
return ops;
|
|
1790
|
+
} catch (err) {
|
|
1791
|
+
throw new Error(
|
|
1792
|
+
`Failed to apply animation: ${err instanceof Error ? err.message : String(err)}`
|
|
1793
|
+
);
|
|
1794
|
+
}
|
|
1795
|
+
} catch (err) {
|
|
1796
|
+
if (err instanceof Error) {
|
|
1797
|
+
throw err;
|
|
1798
|
+
}
|
|
1799
|
+
throw new Error(`Failed to render frame at time ${tSeconds}s: ${String(err)}`);
|
|
1800
|
+
}
|
|
1555
1801
|
},
|
|
1556
1802
|
async createRenderer(p) {
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1803
|
+
try {
|
|
1804
|
+
return await createNodePainter({
|
|
1805
|
+
width: p.width ?? width,
|
|
1806
|
+
height: p.height ?? height,
|
|
1807
|
+
pixelRatio: p.pixelRatio ?? pixelRatio
|
|
1808
|
+
});
|
|
1809
|
+
} catch (err) {
|
|
1810
|
+
throw new Error(
|
|
1811
|
+
`Failed to create renderer: ${err instanceof Error ? err.message : String(err)}`
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1562
1814
|
},
|
|
1563
1815
|
async generateVideo(asset, options) {
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1816
|
+
try {
|
|
1817
|
+
const finalOptions = {
|
|
1818
|
+
width: asset.width ?? width,
|
|
1819
|
+
height: asset.height ?? height,
|
|
1820
|
+
fps,
|
|
1821
|
+
duration: asset.animation?.duration ?? 3,
|
|
1822
|
+
outputPath: options.outputPath ?? "output.mp4",
|
|
1823
|
+
pixelRatio: asset.pixelRatio ?? pixelRatio
|
|
1824
|
+
};
|
|
1825
|
+
const frameGenerator = async (time) => {
|
|
1826
|
+
return this.renderFrame(asset, time);
|
|
1827
|
+
};
|
|
1828
|
+
await videoGenerator.generateVideo(frameGenerator, finalOptions);
|
|
1829
|
+
} catch (err) {
|
|
1830
|
+
throw new Error(
|
|
1831
|
+
`Failed to generate video: ${err instanceof Error ? err.message : String(err)}`
|
|
1832
|
+
);
|
|
1833
|
+
}
|
|
1576
1834
|
},
|
|
1577
1835
|
destroy() {
|
|
1578
|
-
|
|
1836
|
+
try {
|
|
1837
|
+
fonts.destroy();
|
|
1838
|
+
} catch (err) {
|
|
1839
|
+
console.error(`Error during cleanup: ${err instanceof Error ? err.message : String(err)}`);
|
|
1840
|
+
}
|
|
1579
1841
|
}
|
|
1580
1842
|
};
|
|
1581
1843
|
}
|
|
1582
1844
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1583
1845
|
0 && (module.exports = {
|
|
1584
|
-
createTextEngine
|
|
1846
|
+
createTextEngine,
|
|
1847
|
+
isGlyphFill,
|
|
1848
|
+
isShadowFill
|
|
1585
1849
|
});
|
|
1586
1850
|
//# sourceMappingURL=entry.node.cjs.map
|