@shotstack/shotstack-canvas 1.1.0 → 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 +560 -283
- 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 +557 -282
- package/dist/entry.node.js.map +1 -1
- package/dist/entry.web.d.ts +20 -1
- package/dist/entry.web.js +525 -258
- package/dist/entry.web.js.map +1 -1
- package/package.json +10 -4
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
|
|
|
@@ -164,19 +166,28 @@ var RichTextAssetSchema = import_joi.default.object({
|
|
|
164
166
|
var hbSingleton = null;
|
|
165
167
|
async function initHB(wasmBaseURL) {
|
|
166
168
|
if (hbSingleton) return hbSingleton;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
169
|
+
try {
|
|
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;
|
|
179
|
+
}
|
|
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
|
+
);
|
|
178
190
|
}
|
|
179
|
-
return hbSingleton;
|
|
180
191
|
}
|
|
181
192
|
|
|
182
193
|
// src/core/font-registry.ts
|
|
@@ -199,7 +210,13 @@ var FontRegistry = class {
|
|
|
199
210
|
return;
|
|
200
211
|
}
|
|
201
212
|
this.initPromise = this._doInit();
|
|
202
|
-
|
|
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
|
+
}
|
|
203
220
|
}
|
|
204
221
|
async _doInit() {
|
|
205
222
|
try {
|
|
@@ -211,7 +228,13 @@ var FontRegistry = class {
|
|
|
211
228
|
}
|
|
212
229
|
async getHB() {
|
|
213
230
|
if (!this.hb) {
|
|
214
|
-
|
|
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
|
+
}
|
|
215
238
|
}
|
|
216
239
|
return this.hb;
|
|
217
240
|
}
|
|
@@ -219,48 +242,111 @@ var FontRegistry = class {
|
|
|
219
242
|
return `${desc.family}__${desc.weight ?? "400"}__${desc.style ?? "normal"}`;
|
|
220
243
|
}
|
|
221
244
|
async registerFromBytes(bytes, desc) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
+
}
|
|
233
262
|
}
|
|
234
263
|
async getFont(desc) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
+
}
|
|
240
278
|
}
|
|
241
279
|
async getFace(desc) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
+
}
|
|
245
289
|
}
|
|
246
290
|
async getUnitsPerEm(desc) {
|
|
247
|
-
|
|
248
|
-
|
|
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
|
+
}
|
|
249
299
|
}
|
|
250
300
|
async glyphPath(desc, glyphId) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
+
}
|
|
254
310
|
}
|
|
255
311
|
destroy() {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
+
}
|
|
264
350
|
}
|
|
265
351
|
};
|
|
266
352
|
|
|
@@ -282,112 +368,145 @@ var LayoutEngine = class {
|
|
|
282
368
|
}
|
|
283
369
|
}
|
|
284
370
|
async shapeFull(text, desc) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
+
}
|
|
297
398
|
}
|
|
298
399
|
async layout(params) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const shaped = await this.shapeFull(input, desc);
|
|
305
|
-
const face = await this.fonts.getFace(desc);
|
|
306
|
-
const upem = face?.upem || 1e3;
|
|
307
|
-
const scale = fontSize / upem;
|
|
308
|
-
const glyphs = shaped.map((g) => {
|
|
309
|
-
const charIndex = g.cl;
|
|
310
|
-
let char;
|
|
311
|
-
if (charIndex >= 0 && charIndex < input.length) {
|
|
312
|
-
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 [];
|
|
313
405
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
cluster: g.cl,
|
|
320
|
-
char
|
|
321
|
-
// This now correctly maps to the original character
|
|
322
|
-
};
|
|
323
|
-
});
|
|
324
|
-
const lines = [];
|
|
325
|
-
let currentLine = [];
|
|
326
|
-
let currentWidth = 0;
|
|
327
|
-
const spaceIndices = /* @__PURE__ */ new Set();
|
|
328
|
-
for (let i = 0; i < input.length; i++) {
|
|
329
|
-
if (input[i] === " ") {
|
|
330
|
-
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)}`);
|
|
331
411
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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);
|
|
344
444
|
}
|
|
345
|
-
currentLine = [];
|
|
346
|
-
currentWidth = 0;
|
|
347
|
-
lastBreakIndex = i;
|
|
348
|
-
continue;
|
|
349
445
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
} else {
|
|
363
|
-
lines.push({
|
|
364
|
-
glyphs: currentLine,
|
|
365
|
-
width: currentWidth,
|
|
366
|
-
y: 0
|
|
367
|
-
});
|
|
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
|
+
}
|
|
368
458
|
currentLine = [];
|
|
369
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;
|
|
370
490
|
}
|
|
371
|
-
lastBreakIndex = -1;
|
|
372
491
|
}
|
|
373
|
-
currentLine.
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
492
|
+
if (currentLine.length > 0) {
|
|
493
|
+
lines.push({
|
|
494
|
+
glyphs: currentLine,
|
|
495
|
+
width: currentWidth,
|
|
496
|
+
y: 0
|
|
497
|
+
});
|
|
377
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)}`);
|
|
378
509
|
}
|
|
379
|
-
if (currentLine.length > 0) {
|
|
380
|
-
lines.push({
|
|
381
|
-
glyphs: currentLine,
|
|
382
|
-
width: currentWidth,
|
|
383
|
-
y: 0
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
const lineHeight = params.lineHeight * fontSize;
|
|
387
|
-
for (let i = 0; i < lines.length; i++) {
|
|
388
|
-
lines[i].y = (i + 1) * lineHeight;
|
|
389
|
-
}
|
|
390
|
-
return lines;
|
|
391
510
|
}
|
|
392
511
|
};
|
|
393
512
|
|
|
@@ -479,7 +598,6 @@ async function buildDrawOps(p) {
|
|
|
479
598
|
path,
|
|
480
599
|
x: glyphX + p.shadow.offsetX,
|
|
481
600
|
y: glyphY + p.shadow.offsetY,
|
|
482
|
-
// @ts-ignore scale propagated to painters
|
|
483
601
|
scale,
|
|
484
602
|
fill: { kind: "solid", color: p.shadow.color, opacity: p.shadow.opacity }
|
|
485
603
|
});
|
|
@@ -490,7 +608,6 @@ async function buildDrawOps(p) {
|
|
|
490
608
|
path,
|
|
491
609
|
x: glyphX,
|
|
492
610
|
y: glyphY,
|
|
493
|
-
// @ts-ignore scale propagated to painters
|
|
494
611
|
scale,
|
|
495
612
|
width: p.stroke.width,
|
|
496
613
|
color: p.stroke.color,
|
|
@@ -502,7 +619,6 @@ async function buildDrawOps(p) {
|
|
|
502
619
|
path,
|
|
503
620
|
x: glyphX,
|
|
504
621
|
y: glyphY,
|
|
505
|
-
// @ts-ignore scale propagated to painters
|
|
506
622
|
scale,
|
|
507
623
|
fill
|
|
508
624
|
});
|
|
@@ -1078,30 +1194,32 @@ async function createNodePainter(opts) {
|
|
|
1078
1194
|
continue;
|
|
1079
1195
|
}
|
|
1080
1196
|
if (op.op === "FillPath") {
|
|
1197
|
+
const fillOp = op;
|
|
1081
1198
|
ctx.save();
|
|
1082
|
-
ctx.translate(
|
|
1083
|
-
const s =
|
|
1199
|
+
ctx.translate(fillOp.x, fillOp.y);
|
|
1200
|
+
const s = fillOp.scale ?? 1;
|
|
1084
1201
|
ctx.scale(s, -s);
|
|
1085
1202
|
ctx.beginPath();
|
|
1086
|
-
drawSvgPathOnCtx(ctx,
|
|
1087
|
-
const bbox =
|
|
1088
|
-
const fill = makeGradientFromBBox(ctx,
|
|
1203
|
+
drawSvgPathOnCtx(ctx, fillOp.path);
|
|
1204
|
+
const bbox = fillOp.gradientBBox ?? globalBox;
|
|
1205
|
+
const fill = makeGradientFromBBox(ctx, fillOp.fill, bbox);
|
|
1089
1206
|
ctx.fillStyle = fill;
|
|
1090
1207
|
ctx.fill();
|
|
1091
1208
|
ctx.restore();
|
|
1092
1209
|
continue;
|
|
1093
1210
|
}
|
|
1094
1211
|
if (op.op === "StrokePath") {
|
|
1212
|
+
const strokeOp = op;
|
|
1095
1213
|
ctx.save();
|
|
1096
|
-
ctx.translate(
|
|
1097
|
-
const s =
|
|
1214
|
+
ctx.translate(strokeOp.x, strokeOp.y);
|
|
1215
|
+
const s = strokeOp.scale ?? 1;
|
|
1098
1216
|
ctx.scale(s, -s);
|
|
1099
1217
|
const invAbs = 1 / Math.abs(s);
|
|
1100
1218
|
ctx.beginPath();
|
|
1101
|
-
drawSvgPathOnCtx(ctx,
|
|
1102
|
-
const c = parseHex6(
|
|
1219
|
+
drawSvgPathOnCtx(ctx, strokeOp.path);
|
|
1220
|
+
const c = parseHex6(strokeOp.color, strokeOp.opacity);
|
|
1103
1221
|
ctx.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1104
|
-
ctx.lineWidth =
|
|
1222
|
+
ctx.lineWidth = strokeOp.width * invAbs;
|
|
1105
1223
|
ctx.lineJoin = "round";
|
|
1106
1224
|
ctx.lineCap = "round";
|
|
1107
1225
|
ctx.stroke();
|
|
@@ -1133,17 +1251,20 @@ function makeGradientFromBBox(ctx, spec, box) {
|
|
|
1133
1251
|
const c = parseHex6(spec.color, spec.opacity);
|
|
1134
1252
|
return `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1135
1253
|
}
|
|
1136
|
-
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;
|
|
1137
1257
|
const addStops = (g) => {
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1140
|
-
|
|
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);
|
|
1141
1262
|
g.addColorStop(s.offset, `rgba(${c.r},${c.g},${c.b},${c.a})`);
|
|
1142
1263
|
}
|
|
1143
1264
|
return g;
|
|
1144
1265
|
};
|
|
1145
1266
|
if (spec.kind === "linear") {
|
|
1146
|
-
const rad =
|
|
1267
|
+
const rad = spec.angle * Math.PI / 180;
|
|
1147
1268
|
const x1 = cx + Math.cos(rad + Math.PI) * r;
|
|
1148
1269
|
const y1 = cy + Math.sin(rad + Math.PI) * r;
|
|
1149
1270
|
const x2 = cx + Math.cos(rad) * r;
|
|
@@ -1154,22 +1275,34 @@ function makeGradientFromBBox(ctx, spec, box) {
|
|
|
1154
1275
|
}
|
|
1155
1276
|
}
|
|
1156
1277
|
function computeGlobalTextBounds(ops) {
|
|
1157
|
-
let minX = Infinity
|
|
1278
|
+
let minX = Infinity;
|
|
1279
|
+
let minY = Infinity;
|
|
1280
|
+
let maxX = -Infinity;
|
|
1281
|
+
let maxY = -Infinity;
|
|
1158
1282
|
for (const op of ops) {
|
|
1159
|
-
if (op.op !== "FillPath"
|
|
1160
|
-
const
|
|
1161
|
-
|
|
1162
|
-
const
|
|
1163
|
-
const
|
|
1164
|
-
const
|
|
1165
|
-
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;
|
|
1166
1292
|
if (x1 < minX) minX = x1;
|
|
1167
1293
|
if (y1 < minY) minY = y1;
|
|
1168
1294
|
if (x2 > maxX) maxX = x2;
|
|
1169
1295
|
if (y2 > maxY) maxY = y2;
|
|
1170
1296
|
}
|
|
1171
|
-
if (minX === Infinity)
|
|
1172
|
-
|
|
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
|
+
};
|
|
1173
1306
|
}
|
|
1174
1307
|
function drawSvgPathOnCtx(ctx, d) {
|
|
1175
1308
|
const t = tokenizePath2(d);
|
|
@@ -1211,13 +1344,18 @@ function drawSvgPathOnCtx(ctx, d) {
|
|
|
1211
1344
|
ctx.closePath();
|
|
1212
1345
|
break;
|
|
1213
1346
|
}
|
|
1347
|
+
default:
|
|
1348
|
+
break;
|
|
1214
1349
|
}
|
|
1215
1350
|
}
|
|
1216
1351
|
}
|
|
1217
1352
|
function computePathBounds2(d) {
|
|
1218
1353
|
const t = tokenizePath2(d);
|
|
1219
1354
|
let i = 0;
|
|
1220
|
-
let minX = Infinity
|
|
1355
|
+
let minX = Infinity;
|
|
1356
|
+
let minY = Infinity;
|
|
1357
|
+
let maxX = -Infinity;
|
|
1358
|
+
let maxY = -Infinity;
|
|
1221
1359
|
const touch = (x, y) => {
|
|
1222
1360
|
if (x < minX) minX = x;
|
|
1223
1361
|
if (y < minY) minY = y;
|
|
@@ -1257,10 +1395,19 @@ function computePathBounds2(d) {
|
|
|
1257
1395
|
}
|
|
1258
1396
|
case "Z":
|
|
1259
1397
|
break;
|
|
1398
|
+
default:
|
|
1399
|
+
break;
|
|
1260
1400
|
}
|
|
1261
1401
|
}
|
|
1262
|
-
if (minX === Infinity)
|
|
1263
|
-
|
|
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
|
+
};
|
|
1264
1411
|
}
|
|
1265
1412
|
function roundRectPath(ctx, x, y, w, h, r) {
|
|
1266
1413
|
ctx.moveTo(x + r, y);
|
|
@@ -1289,20 +1436,54 @@ function bufferToArrayBuffer(buf) {
|
|
|
1289
1436
|
return ab.slice(byteOffset, byteOffset + byteLength);
|
|
1290
1437
|
}
|
|
1291
1438
|
async function loadFileOrHttpToArrayBuffer(pathOrUrl) {
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
const
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
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)}`);
|
|
1303
1486
|
}
|
|
1304
|
-
const buf = await (0, import_promises.readFile)(pathOrUrl);
|
|
1305
|
-
return bufferToArrayBuffer(buf);
|
|
1306
1487
|
}
|
|
1307
1488
|
|
|
1308
1489
|
// src/core/video-generator.ts
|
|
@@ -1438,6 +1619,14 @@ var VideoGenerator = class {
|
|
|
1438
1619
|
}
|
|
1439
1620
|
};
|
|
1440
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
|
+
|
|
1441
1630
|
// src/env/entry.node.ts
|
|
1442
1631
|
async function createTextEngine(opts = {}) {
|
|
1443
1632
|
const width = opts.width ?? CANVAS_CONFIG.DEFAULTS.width;
|
|
@@ -1448,126 +1637,214 @@ async function createTextEngine(opts = {}) {
|
|
|
1448
1637
|
const fonts = new FontRegistry(wasmBaseURL);
|
|
1449
1638
|
const layout = new LayoutEngine(fonts);
|
|
1450
1639
|
const videoGenerator = new VideoGenerator();
|
|
1451
|
-
|
|
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
|
+
}
|
|
1452
1647
|
async function ensureFonts(asset) {
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
const
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
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
|
+
}
|
|
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;
|
|
1461
1677
|
}
|
|
1678
|
+
throw new Error(`Failed to ensure fonts: ${String(err)}`);
|
|
1462
1679
|
}
|
|
1463
|
-
const main = asset.font ?? {
|
|
1464
|
-
family: "Roboto",
|
|
1465
|
-
weight: "400",
|
|
1466
|
-
style: "normal",
|
|
1467
|
-
size: 48,
|
|
1468
|
-
color: "#000000",
|
|
1469
|
-
opacity: 1
|
|
1470
|
-
};
|
|
1471
|
-
return main;
|
|
1472
1680
|
}
|
|
1473
1681
|
return {
|
|
1474
1682
|
validate(input) {
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
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
|
+
}
|
|
1481
1696
|
},
|
|
1482
1697
|
async registerFontFromFile(path, desc) {
|
|
1483
|
-
|
|
1484
|
-
|
|
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
|
+
}
|
|
1485
1706
|
},
|
|
1486
1707
|
async registerFontFromUrl(url, desc) {
|
|
1487
|
-
|
|
1488
|
-
|
|
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
|
+
}
|
|
1489
1716
|
},
|
|
1490
1717
|
async renderFrame(asset, tSeconds) {
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
1542
1801
|
},
|
|
1543
1802
|
async createRenderer(p) {
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
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
|
+
}
|
|
1549
1814
|
},
|
|
1550
1815
|
async generateVideo(asset, options) {
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
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
|
+
}
|
|
1563
1834
|
},
|
|
1564
1835
|
destroy() {
|
|
1565
|
-
|
|
1836
|
+
try {
|
|
1837
|
+
fonts.destroy();
|
|
1838
|
+
} catch (err) {
|
|
1839
|
+
console.error(`Error during cleanup: ${err instanceof Error ? err.message : String(err)}`);
|
|
1840
|
+
}
|
|
1566
1841
|
}
|
|
1567
1842
|
};
|
|
1568
1843
|
}
|
|
1569
1844
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1570
1845
|
0 && (module.exports = {
|
|
1571
|
-
createTextEngine
|
|
1846
|
+
createTextEngine,
|
|
1847
|
+
isGlyphFill,
|
|
1848
|
+
isShadowFill
|
|
1572
1849
|
});
|
|
1573
1850
|
//# sourceMappingURL=entry.node.cjs.map
|