@shotstack/shotstack-canvas 1.1.0 → 1.1.2
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 +595 -287
- 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 +592 -286
- package/dist/entry.node.js.map +1 -1
- package/dist/entry.web.d.ts +20 -1
- package/dist/entry.web.js +597 -262
- 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,29 +166,42 @@ 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
|
|
183
|
-
var FontRegistry = class {
|
|
194
|
+
var FontRegistry = class _FontRegistry {
|
|
184
195
|
hb;
|
|
185
196
|
faces = /* @__PURE__ */ new Map();
|
|
186
197
|
fonts = /* @__PURE__ */ new Map();
|
|
187
198
|
blobs = /* @__PURE__ */ new Map();
|
|
188
199
|
wasmBaseURL;
|
|
189
200
|
initPromise;
|
|
201
|
+
static fallbackLoader;
|
|
202
|
+
static setFallbackLoader(loader) {
|
|
203
|
+
_FontRegistry.fallbackLoader = loader;
|
|
204
|
+
}
|
|
190
205
|
constructor(wasmBaseURL) {
|
|
191
206
|
this.wasmBaseURL = wasmBaseURL;
|
|
192
207
|
}
|
|
@@ -195,11 +210,15 @@ var FontRegistry = class {
|
|
|
195
210
|
await this.initPromise;
|
|
196
211
|
return;
|
|
197
212
|
}
|
|
198
|
-
if (this.hb)
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
213
|
+
if (this.hb) return;
|
|
201
214
|
this.initPromise = this._doInit();
|
|
202
|
-
|
|
215
|
+
try {
|
|
216
|
+
await this.initPromise;
|
|
217
|
+
} catch (err) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
`Failed to initialize FontRegistry: ${err instanceof Error ? err.message : String(err)}`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
203
222
|
}
|
|
204
223
|
async _doInit() {
|
|
205
224
|
try {
|
|
@@ -211,7 +230,13 @@ var FontRegistry = class {
|
|
|
211
230
|
}
|
|
212
231
|
async getHB() {
|
|
213
232
|
if (!this.hb) {
|
|
214
|
-
|
|
233
|
+
try {
|
|
234
|
+
await this.init();
|
|
235
|
+
} catch (err) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
`Failed to get HarfBuzz instance: ${err instanceof Error ? err.message : String(err)}`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
215
240
|
}
|
|
216
241
|
return this.hb;
|
|
217
242
|
}
|
|
@@ -219,48 +244,140 @@ var FontRegistry = class {
|
|
|
219
244
|
return `${desc.family}__${desc.weight ?? "400"}__${desc.style ?? "normal"}`;
|
|
220
245
|
}
|
|
221
246
|
async registerFromBytes(bytes, desc) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
247
|
+
try {
|
|
248
|
+
if (!this.hb) await this.init();
|
|
249
|
+
const k = this.key(desc);
|
|
250
|
+
if (this.fonts.has(k)) return;
|
|
251
|
+
const blob = this.hb.createBlob(bytes);
|
|
252
|
+
const face = this.hb.createFace(blob, 0);
|
|
253
|
+
const font = this.hb.createFont(face);
|
|
254
|
+
const upem = face.upem || 1e3;
|
|
255
|
+
font.setScale(upem, upem);
|
|
256
|
+
this.blobs.set(k, blob);
|
|
257
|
+
this.faces.set(k, face);
|
|
258
|
+
this.fonts.set(k, font);
|
|
259
|
+
} catch (err) {
|
|
260
|
+
throw new Error(
|
|
261
|
+
`Failed to register font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async tryFallbackInstall(desc) {
|
|
266
|
+
const loader = _FontRegistry.fallbackLoader;
|
|
267
|
+
if (!loader) return false;
|
|
268
|
+
try {
|
|
269
|
+
const bytes = await loader({
|
|
270
|
+
family: desc.family,
|
|
271
|
+
weight: desc.weight ?? "400",
|
|
272
|
+
style: desc.style ?? "normal"
|
|
273
|
+
});
|
|
274
|
+
if (!bytes) return false;
|
|
275
|
+
await this.registerFromBytes(bytes, {
|
|
276
|
+
family: desc.family,
|
|
277
|
+
weight: desc.weight ?? "400",
|
|
278
|
+
style: desc.style ?? "normal"
|
|
279
|
+
});
|
|
280
|
+
return true;
|
|
281
|
+
} catch {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
233
284
|
}
|
|
234
285
|
async getFont(desc) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
286
|
+
try {
|
|
287
|
+
if (!this.hb) await this.init();
|
|
288
|
+
const k = this.key(desc);
|
|
289
|
+
let f = this.fonts.get(k);
|
|
290
|
+
if (!f) {
|
|
291
|
+
const installed = await this.tryFallbackInstall(desc);
|
|
292
|
+
f = installed ? this.fonts.get(k) : void 0;
|
|
293
|
+
}
|
|
294
|
+
if (!f) throw new Error(`Font not registered for ${k}`);
|
|
295
|
+
return f;
|
|
296
|
+
} catch (err) {
|
|
297
|
+
if (err instanceof Error && err.message.includes("Font not registered")) {
|
|
298
|
+
throw err;
|
|
299
|
+
}
|
|
300
|
+
throw new Error(
|
|
301
|
+
`Failed to get font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
240
304
|
}
|
|
241
305
|
async getFace(desc) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
306
|
+
try {
|
|
307
|
+
if (!this.hb) await this.init();
|
|
308
|
+
const k = this.key(desc);
|
|
309
|
+
let face = this.faces.get(k);
|
|
310
|
+
if (!face) {
|
|
311
|
+
const installed = await this.tryFallbackInstall(desc);
|
|
312
|
+
face = installed ? this.faces.get(k) : void 0;
|
|
313
|
+
}
|
|
314
|
+
return face;
|
|
315
|
+
} catch (err) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
`Failed to get face for "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
318
|
+
);
|
|
319
|
+
}
|
|
245
320
|
}
|
|
246
321
|
async getUnitsPerEm(desc) {
|
|
247
|
-
|
|
248
|
-
|
|
322
|
+
try {
|
|
323
|
+
const face = await this.getFace(desc);
|
|
324
|
+
return face?.upem || 1e3;
|
|
325
|
+
} catch (err) {
|
|
326
|
+
throw new Error(
|
|
327
|
+
`Failed to get units per em for "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
328
|
+
);
|
|
329
|
+
}
|
|
249
330
|
}
|
|
250
331
|
async glyphPath(desc, glyphId) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
332
|
+
try {
|
|
333
|
+
const font = await this.getFont(desc);
|
|
334
|
+
const path = font.glyphToPath(glyphId);
|
|
335
|
+
return path && path !== "" ? path : "M 0 0";
|
|
336
|
+
} catch (err) {
|
|
337
|
+
throw new Error(
|
|
338
|
+
`Failed to get glyph path for glyph ${glyphId} in font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
254
341
|
}
|
|
255
342
|
destroy() {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
343
|
+
try {
|
|
344
|
+
for (const [, f] of this.fonts) {
|
|
345
|
+
try {
|
|
346
|
+
f.destroy();
|
|
347
|
+
} catch (err) {
|
|
348
|
+
console.error(
|
|
349
|
+
`Error destroying font: ${err instanceof Error ? err.message : String(err)}`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
for (const [, f] of this.faces) {
|
|
354
|
+
try {
|
|
355
|
+
f.destroy();
|
|
356
|
+
} catch (err) {
|
|
357
|
+
console.error(
|
|
358
|
+
`Error destroying face: ${err instanceof Error ? err.message : String(err)}`
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
for (const [, b] of this.blobs) {
|
|
363
|
+
try {
|
|
364
|
+
b.destroy();
|
|
365
|
+
} catch (err) {
|
|
366
|
+
console.error(
|
|
367
|
+
`Error destroying blob: ${err instanceof Error ? err.message : String(err)}`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
this.fonts.clear();
|
|
372
|
+
this.faces.clear();
|
|
373
|
+
this.blobs.clear();
|
|
374
|
+
this.hb = void 0;
|
|
375
|
+
this.initPromise = void 0;
|
|
376
|
+
} catch (err) {
|
|
377
|
+
console.error(
|
|
378
|
+
`Error during FontRegistry cleanup: ${err instanceof Error ? err.message : String(err)}`
|
|
379
|
+
);
|
|
380
|
+
}
|
|
264
381
|
}
|
|
265
382
|
};
|
|
266
383
|
|
|
@@ -282,112 +399,145 @@ var LayoutEngine = class {
|
|
|
282
399
|
}
|
|
283
400
|
}
|
|
284
401
|
async shapeFull(text, desc) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
402
|
+
try {
|
|
403
|
+
const hb = await this.fonts.getHB();
|
|
404
|
+
const buffer = hb.createBuffer();
|
|
405
|
+
try {
|
|
406
|
+
buffer.addText(text);
|
|
407
|
+
buffer.guessSegmentProperties();
|
|
408
|
+
const font = await this.fonts.getFont(desc);
|
|
409
|
+
const face = await this.fonts.getFace(desc);
|
|
410
|
+
const upem = face?.upem || 1e3;
|
|
411
|
+
font.setScale(upem, upem);
|
|
412
|
+
hb.shape(font, buffer);
|
|
413
|
+
const result = buffer.json();
|
|
414
|
+
return result;
|
|
415
|
+
} finally {
|
|
416
|
+
try {
|
|
417
|
+
buffer.destroy();
|
|
418
|
+
} catch (err) {
|
|
419
|
+
console.error(
|
|
420
|
+
`Error destroying HarfBuzz buffer: ${err instanceof Error ? err.message : String(err)}`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
} catch (err) {
|
|
425
|
+
throw new Error(
|
|
426
|
+
`Failed to shape text with font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
427
|
+
);
|
|
428
|
+
}
|
|
297
429
|
}
|
|
298
430
|
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];
|
|
431
|
+
try {
|
|
432
|
+
const { textTransform, desc, fontSize, letterSpacing, width } = params;
|
|
433
|
+
const input = this.transformText(params.text, textTransform);
|
|
434
|
+
if (!input || input.length === 0) {
|
|
435
|
+
return [];
|
|
313
436
|
}
|
|
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);
|
|
437
|
+
let shaped;
|
|
438
|
+
try {
|
|
439
|
+
shaped = await this.shapeFull(input, desc);
|
|
440
|
+
} catch (err) {
|
|
441
|
+
throw new Error(`Text shaping failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
331
442
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
443
|
+
let upem;
|
|
444
|
+
try {
|
|
445
|
+
const face = await this.fonts.getFace(desc);
|
|
446
|
+
upem = face?.upem || 1e3;
|
|
447
|
+
} catch (err) {
|
|
448
|
+
throw new Error(
|
|
449
|
+
`Failed to get font metrics: ${err instanceof Error ? err.message : String(err)}`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
const scale = fontSize / upem;
|
|
453
|
+
const glyphs = shaped.map((g) => {
|
|
454
|
+
const charIndex = g.cl;
|
|
455
|
+
let char;
|
|
456
|
+
if (charIndex >= 0 && charIndex < input.length) {
|
|
457
|
+
char = input[charIndex];
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
id: g.g,
|
|
461
|
+
xAdvance: g.ax * scale + letterSpacing,
|
|
462
|
+
xOffset: g.dx * scale,
|
|
463
|
+
yOffset: -g.dy * scale,
|
|
464
|
+
cluster: g.cl,
|
|
465
|
+
char
|
|
466
|
+
};
|
|
467
|
+
});
|
|
468
|
+
const lines = [];
|
|
469
|
+
let currentLine = [];
|
|
470
|
+
let currentWidth = 0;
|
|
471
|
+
const spaceIndices = /* @__PURE__ */ new Set();
|
|
472
|
+
for (let i = 0; i < input.length; i++) {
|
|
473
|
+
if (input[i] === " ") {
|
|
474
|
+
spaceIndices.add(i);
|
|
344
475
|
}
|
|
345
|
-
currentLine = [];
|
|
346
|
-
currentWidth = 0;
|
|
347
|
-
lastBreakIndex = i;
|
|
348
|
-
continue;
|
|
349
476
|
}
|
|
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
|
-
});
|
|
477
|
+
let lastBreakIndex = -1;
|
|
478
|
+
for (let i = 0; i < glyphs.length; i++) {
|
|
479
|
+
const glyph = glyphs[i];
|
|
480
|
+
const glyphWidth = glyph.xAdvance;
|
|
481
|
+
if (glyph.char === "\n") {
|
|
482
|
+
if (currentLine.length > 0) {
|
|
483
|
+
lines.push({
|
|
484
|
+
glyphs: currentLine,
|
|
485
|
+
width: currentWidth,
|
|
486
|
+
y: 0
|
|
487
|
+
});
|
|
488
|
+
}
|
|
368
489
|
currentLine = [];
|
|
369
490
|
currentWidth = 0;
|
|
491
|
+
lastBreakIndex = i;
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if (currentWidth + glyphWidth > width && currentLine.length > 0) {
|
|
495
|
+
if (lastBreakIndex > -1) {
|
|
496
|
+
const breakPoint = lastBreakIndex - (i - currentLine.length) + 1;
|
|
497
|
+
const nextLine = currentLine.splice(breakPoint);
|
|
498
|
+
const lineWidth = currentLine.reduce((sum, g) => sum + g.xAdvance, 0);
|
|
499
|
+
lines.push({
|
|
500
|
+
glyphs: currentLine,
|
|
501
|
+
width: lineWidth,
|
|
502
|
+
y: 0
|
|
503
|
+
});
|
|
504
|
+
currentLine = nextLine;
|
|
505
|
+
currentWidth = nextLine.reduce((sum, g) => sum + g.xAdvance, 0);
|
|
506
|
+
} else {
|
|
507
|
+
lines.push({
|
|
508
|
+
glyphs: currentLine,
|
|
509
|
+
width: currentWidth,
|
|
510
|
+
y: 0
|
|
511
|
+
});
|
|
512
|
+
currentLine = [];
|
|
513
|
+
currentWidth = 0;
|
|
514
|
+
}
|
|
515
|
+
lastBreakIndex = -1;
|
|
516
|
+
}
|
|
517
|
+
currentLine.push(glyph);
|
|
518
|
+
currentWidth += glyphWidth;
|
|
519
|
+
if (spaceIndices.has(glyph.cluster)) {
|
|
520
|
+
lastBreakIndex = i;
|
|
370
521
|
}
|
|
371
|
-
lastBreakIndex = -1;
|
|
372
522
|
}
|
|
373
|
-
currentLine.
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
523
|
+
if (currentLine.length > 0) {
|
|
524
|
+
lines.push({
|
|
525
|
+
glyphs: currentLine,
|
|
526
|
+
width: currentWidth,
|
|
527
|
+
y: 0
|
|
528
|
+
});
|
|
377
529
|
}
|
|
530
|
+
const lineHeight = params.lineHeight * fontSize;
|
|
531
|
+
for (let i = 0; i < lines.length; i++) {
|
|
532
|
+
lines[i].y = (i + 1) * lineHeight;
|
|
533
|
+
}
|
|
534
|
+
return lines;
|
|
535
|
+
} catch (err) {
|
|
536
|
+
if (err instanceof Error) {
|
|
537
|
+
throw err;
|
|
538
|
+
}
|
|
539
|
+
throw new Error(`Layout failed: ${String(err)}`);
|
|
378
540
|
}
|
|
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
541
|
}
|
|
392
542
|
};
|
|
393
543
|
|
|
@@ -479,7 +629,6 @@ async function buildDrawOps(p) {
|
|
|
479
629
|
path,
|
|
480
630
|
x: glyphX + p.shadow.offsetX,
|
|
481
631
|
y: glyphY + p.shadow.offsetY,
|
|
482
|
-
// @ts-ignore scale propagated to painters
|
|
483
632
|
scale,
|
|
484
633
|
fill: { kind: "solid", color: p.shadow.color, opacity: p.shadow.opacity }
|
|
485
634
|
});
|
|
@@ -490,7 +639,6 @@ async function buildDrawOps(p) {
|
|
|
490
639
|
path,
|
|
491
640
|
x: glyphX,
|
|
492
641
|
y: glyphY,
|
|
493
|
-
// @ts-ignore scale propagated to painters
|
|
494
642
|
scale,
|
|
495
643
|
width: p.stroke.width,
|
|
496
644
|
color: p.stroke.color,
|
|
@@ -502,7 +650,6 @@ async function buildDrawOps(p) {
|
|
|
502
650
|
path,
|
|
503
651
|
x: glyphX,
|
|
504
652
|
y: glyphY,
|
|
505
|
-
// @ts-ignore scale propagated to painters
|
|
506
653
|
scale,
|
|
507
654
|
fill
|
|
508
655
|
});
|
|
@@ -1078,30 +1225,32 @@ async function createNodePainter(opts) {
|
|
|
1078
1225
|
continue;
|
|
1079
1226
|
}
|
|
1080
1227
|
if (op.op === "FillPath") {
|
|
1228
|
+
const fillOp = op;
|
|
1081
1229
|
ctx.save();
|
|
1082
|
-
ctx.translate(
|
|
1083
|
-
const s =
|
|
1230
|
+
ctx.translate(fillOp.x, fillOp.y);
|
|
1231
|
+
const s = fillOp.scale ?? 1;
|
|
1084
1232
|
ctx.scale(s, -s);
|
|
1085
1233
|
ctx.beginPath();
|
|
1086
|
-
drawSvgPathOnCtx(ctx,
|
|
1087
|
-
const bbox =
|
|
1088
|
-
const fill = makeGradientFromBBox(ctx,
|
|
1234
|
+
drawSvgPathOnCtx(ctx, fillOp.path);
|
|
1235
|
+
const bbox = fillOp.gradientBBox ?? globalBox;
|
|
1236
|
+
const fill = makeGradientFromBBox(ctx, fillOp.fill, bbox);
|
|
1089
1237
|
ctx.fillStyle = fill;
|
|
1090
1238
|
ctx.fill();
|
|
1091
1239
|
ctx.restore();
|
|
1092
1240
|
continue;
|
|
1093
1241
|
}
|
|
1094
1242
|
if (op.op === "StrokePath") {
|
|
1243
|
+
const strokeOp = op;
|
|
1095
1244
|
ctx.save();
|
|
1096
|
-
ctx.translate(
|
|
1097
|
-
const s =
|
|
1245
|
+
ctx.translate(strokeOp.x, strokeOp.y);
|
|
1246
|
+
const s = strokeOp.scale ?? 1;
|
|
1098
1247
|
ctx.scale(s, -s);
|
|
1099
1248
|
const invAbs = 1 / Math.abs(s);
|
|
1100
1249
|
ctx.beginPath();
|
|
1101
|
-
drawSvgPathOnCtx(ctx,
|
|
1102
|
-
const c = parseHex6(
|
|
1250
|
+
drawSvgPathOnCtx(ctx, strokeOp.path);
|
|
1251
|
+
const c = parseHex6(strokeOp.color, strokeOp.opacity);
|
|
1103
1252
|
ctx.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1104
|
-
ctx.lineWidth =
|
|
1253
|
+
ctx.lineWidth = strokeOp.width * invAbs;
|
|
1105
1254
|
ctx.lineJoin = "round";
|
|
1106
1255
|
ctx.lineCap = "round";
|
|
1107
1256
|
ctx.stroke();
|
|
@@ -1133,17 +1282,20 @@ function makeGradientFromBBox(ctx, spec, box) {
|
|
|
1133
1282
|
const c = parseHex6(spec.color, spec.opacity);
|
|
1134
1283
|
return `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1135
1284
|
}
|
|
1136
|
-
const cx = box.x + box.w / 2
|
|
1285
|
+
const cx = box.x + box.w / 2;
|
|
1286
|
+
const cy = box.y + box.h / 2;
|
|
1287
|
+
const r = Math.max(box.w, box.h) / 2;
|
|
1137
1288
|
const addStops = (g) => {
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1140
|
-
|
|
1289
|
+
const opacity = spec.kind === "linear" || spec.kind === "radial" ? spec.opacity : 1;
|
|
1290
|
+
const stops = spec.kind === "linear" || spec.kind === "radial" ? spec.stops : [];
|
|
1291
|
+
for (const s of stops) {
|
|
1292
|
+
const c = parseHex6(s.color, opacity);
|
|
1141
1293
|
g.addColorStop(s.offset, `rgba(${c.r},${c.g},${c.b},${c.a})`);
|
|
1142
1294
|
}
|
|
1143
1295
|
return g;
|
|
1144
1296
|
};
|
|
1145
1297
|
if (spec.kind === "linear") {
|
|
1146
|
-
const rad =
|
|
1298
|
+
const rad = spec.angle * Math.PI / 180;
|
|
1147
1299
|
const x1 = cx + Math.cos(rad + Math.PI) * r;
|
|
1148
1300
|
const y1 = cy + Math.sin(rad + Math.PI) * r;
|
|
1149
1301
|
const x2 = cx + Math.cos(rad) * r;
|
|
@@ -1154,22 +1306,34 @@ function makeGradientFromBBox(ctx, spec, box) {
|
|
|
1154
1306
|
}
|
|
1155
1307
|
}
|
|
1156
1308
|
function computeGlobalTextBounds(ops) {
|
|
1157
|
-
let minX = Infinity
|
|
1309
|
+
let minX = Infinity;
|
|
1310
|
+
let minY = Infinity;
|
|
1311
|
+
let maxX = -Infinity;
|
|
1312
|
+
let maxY = -Infinity;
|
|
1158
1313
|
for (const op of ops) {
|
|
1159
|
-
if (op.op !== "FillPath"
|
|
1160
|
-
const
|
|
1161
|
-
|
|
1162
|
-
const
|
|
1163
|
-
const
|
|
1164
|
-
const
|
|
1165
|
-
const
|
|
1314
|
+
if (op.op !== "FillPath") continue;
|
|
1315
|
+
const fillOp = op;
|
|
1316
|
+
if (fillOp.isShadow) continue;
|
|
1317
|
+
const b = computePathBounds2(fillOp.path);
|
|
1318
|
+
const s = fillOp.scale ?? 1;
|
|
1319
|
+
const x1 = fillOp.x + s * b.x;
|
|
1320
|
+
const x2 = fillOp.x + s * (b.x + b.w);
|
|
1321
|
+
const y1 = fillOp.y - s * (b.y + b.h);
|
|
1322
|
+
const y2 = fillOp.y - s * b.y;
|
|
1166
1323
|
if (x1 < minX) minX = x1;
|
|
1167
1324
|
if (y1 < minY) minY = y1;
|
|
1168
1325
|
if (x2 > maxX) maxX = x2;
|
|
1169
1326
|
if (y2 > maxY) maxY = y2;
|
|
1170
1327
|
}
|
|
1171
|
-
if (minX === Infinity)
|
|
1172
|
-
|
|
1328
|
+
if (minX === Infinity) {
|
|
1329
|
+
return { x: 0, y: 0, w: 1, h: 1 };
|
|
1330
|
+
}
|
|
1331
|
+
return {
|
|
1332
|
+
x: minX,
|
|
1333
|
+
y: minY,
|
|
1334
|
+
w: Math.max(1, maxX - minX),
|
|
1335
|
+
h: Math.max(1, maxY - minY)
|
|
1336
|
+
};
|
|
1173
1337
|
}
|
|
1174
1338
|
function drawSvgPathOnCtx(ctx, d) {
|
|
1175
1339
|
const t = tokenizePath2(d);
|
|
@@ -1211,13 +1375,18 @@ function drawSvgPathOnCtx(ctx, d) {
|
|
|
1211
1375
|
ctx.closePath();
|
|
1212
1376
|
break;
|
|
1213
1377
|
}
|
|
1378
|
+
default:
|
|
1379
|
+
break;
|
|
1214
1380
|
}
|
|
1215
1381
|
}
|
|
1216
1382
|
}
|
|
1217
1383
|
function computePathBounds2(d) {
|
|
1218
1384
|
const t = tokenizePath2(d);
|
|
1219
1385
|
let i = 0;
|
|
1220
|
-
let minX = Infinity
|
|
1386
|
+
let minX = Infinity;
|
|
1387
|
+
let minY = Infinity;
|
|
1388
|
+
let maxX = -Infinity;
|
|
1389
|
+
let maxY = -Infinity;
|
|
1221
1390
|
const touch = (x, y) => {
|
|
1222
1391
|
if (x < minX) minX = x;
|
|
1223
1392
|
if (y < minY) minY = y;
|
|
@@ -1257,10 +1426,19 @@ function computePathBounds2(d) {
|
|
|
1257
1426
|
}
|
|
1258
1427
|
case "Z":
|
|
1259
1428
|
break;
|
|
1429
|
+
default:
|
|
1430
|
+
break;
|
|
1260
1431
|
}
|
|
1261
1432
|
}
|
|
1262
|
-
if (minX === Infinity)
|
|
1263
|
-
|
|
1433
|
+
if (minX === Infinity) {
|
|
1434
|
+
return { x: 0, y: 0, w: 0, h: 0 };
|
|
1435
|
+
}
|
|
1436
|
+
return {
|
|
1437
|
+
x: minX,
|
|
1438
|
+
y: minY,
|
|
1439
|
+
w: maxX - minX,
|
|
1440
|
+
h: maxY - minY
|
|
1441
|
+
};
|
|
1264
1442
|
}
|
|
1265
1443
|
function roundRectPath(ctx, x, y, w, h, r) {
|
|
1266
1444
|
ctx.moveTo(x + r, y);
|
|
@@ -1289,20 +1467,54 @@ function bufferToArrayBuffer(buf) {
|
|
|
1289
1467
|
return ab.slice(byteOffset, byteOffset + byteLength);
|
|
1290
1468
|
}
|
|
1291
1469
|
async function loadFileOrHttpToArrayBuffer(pathOrUrl) {
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
const
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1470
|
+
try {
|
|
1471
|
+
if (/^https?:\/\//.test(pathOrUrl)) {
|
|
1472
|
+
const client = pathOrUrl.startsWith("https:") ? https : http;
|
|
1473
|
+
const buf2 = await new Promise((resolve, reject) => {
|
|
1474
|
+
const request = client.get(pathOrUrl, (res) => {
|
|
1475
|
+
const { statusCode } = res;
|
|
1476
|
+
if (statusCode && (statusCode < 200 || statusCode >= 300)) {
|
|
1477
|
+
reject(new Error(`HTTP request failed with status ${statusCode} for ${pathOrUrl}`));
|
|
1478
|
+
res.resume();
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
const chunks = [];
|
|
1482
|
+
res.on("data", (chunk) => {
|
|
1483
|
+
chunks.push(chunk);
|
|
1484
|
+
});
|
|
1485
|
+
res.on("end", () => {
|
|
1486
|
+
try {
|
|
1487
|
+
resolve(Buffer.concat(chunks));
|
|
1488
|
+
} catch (err) {
|
|
1489
|
+
reject(
|
|
1490
|
+
new Error(
|
|
1491
|
+
`Failed to concatenate response chunks: ${err instanceof Error ? err.message : String(err)}`
|
|
1492
|
+
)
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1496
|
+
res.on("error", (err) => {
|
|
1497
|
+
reject(new Error(`Response error for ${pathOrUrl}: ${err.message}`));
|
|
1498
|
+
});
|
|
1499
|
+
});
|
|
1500
|
+
request.on("error", (err) => {
|
|
1501
|
+
reject(new Error(`Request error for ${pathOrUrl}: ${err.message}`));
|
|
1502
|
+
});
|
|
1503
|
+
request.setTimeout(3e4, () => {
|
|
1504
|
+
request.destroy();
|
|
1505
|
+
reject(new Error(`Request timeout after 30s for ${pathOrUrl}`));
|
|
1506
|
+
});
|
|
1507
|
+
});
|
|
1508
|
+
return bufferToArrayBuffer(buf2);
|
|
1509
|
+
}
|
|
1510
|
+
const buf = await (0, import_promises.readFile)(pathOrUrl);
|
|
1511
|
+
return bufferToArrayBuffer(buf);
|
|
1512
|
+
} catch (err) {
|
|
1513
|
+
if (err instanceof Error) {
|
|
1514
|
+
throw new Error(`Failed to load ${pathOrUrl}: ${err.message}`);
|
|
1515
|
+
}
|
|
1516
|
+
throw new Error(`Failed to load ${pathOrUrl}: ${String(err)}`);
|
|
1303
1517
|
}
|
|
1304
|
-
const buf = await (0, import_promises.readFile)(pathOrUrl);
|
|
1305
|
-
return bufferToArrayBuffer(buf);
|
|
1306
1518
|
}
|
|
1307
1519
|
|
|
1308
1520
|
// src/core/video-generator.ts
|
|
@@ -1438,6 +1650,14 @@ var VideoGenerator = class {
|
|
|
1438
1650
|
}
|
|
1439
1651
|
};
|
|
1440
1652
|
|
|
1653
|
+
// src/types.ts
|
|
1654
|
+
var isShadowFill2 = (op) => {
|
|
1655
|
+
return op.op === "FillPath" && op.isShadow === true;
|
|
1656
|
+
};
|
|
1657
|
+
var isGlyphFill2 = (op) => {
|
|
1658
|
+
return op.op === "FillPath" && op.isShadow !== true;
|
|
1659
|
+
};
|
|
1660
|
+
|
|
1441
1661
|
// src/env/entry.node.ts
|
|
1442
1662
|
async function createTextEngine(opts = {}) {
|
|
1443
1663
|
const width = opts.width ?? CANVAS_CONFIG.DEFAULTS.width;
|
|
@@ -1448,126 +1668,214 @@ async function createTextEngine(opts = {}) {
|
|
|
1448
1668
|
const fonts = new FontRegistry(wasmBaseURL);
|
|
1449
1669
|
const layout = new LayoutEngine(fonts);
|
|
1450
1670
|
const videoGenerator = new VideoGenerator();
|
|
1451
|
-
|
|
1671
|
+
try {
|
|
1672
|
+
await fonts.init();
|
|
1673
|
+
} catch (err) {
|
|
1674
|
+
throw new Error(
|
|
1675
|
+
`Failed to initialize font registry: ${err instanceof Error ? err.message : String(err)}`
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1452
1678
|
async function ensureFonts(asset) {
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
const
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1679
|
+
try {
|
|
1680
|
+
if (asset.customFonts) {
|
|
1681
|
+
for (const cf of asset.customFonts) {
|
|
1682
|
+
try {
|
|
1683
|
+
const bytes = await loadFileOrHttpToArrayBuffer(cf.src);
|
|
1684
|
+
await fonts.registerFromBytes(bytes, {
|
|
1685
|
+
family: cf.family,
|
|
1686
|
+
weight: cf.weight ?? "400",
|
|
1687
|
+
style: cf.style ?? "normal"
|
|
1688
|
+
});
|
|
1689
|
+
} catch (err) {
|
|
1690
|
+
throw new Error(
|
|
1691
|
+
`Failed to load custom font "${cf.family}" from ${cf.src}: ${err instanceof Error ? err.message : String(err)}`
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1461
1695
|
}
|
|
1696
|
+
const main = asset.font ?? {
|
|
1697
|
+
family: "Roboto",
|
|
1698
|
+
weight: "400",
|
|
1699
|
+
style: "normal",
|
|
1700
|
+
size: 48,
|
|
1701
|
+
color: "#000000",
|
|
1702
|
+
opacity: 1
|
|
1703
|
+
};
|
|
1704
|
+
return main;
|
|
1705
|
+
} catch (err) {
|
|
1706
|
+
if (err instanceof Error) {
|
|
1707
|
+
throw err;
|
|
1708
|
+
}
|
|
1709
|
+
throw new Error(`Failed to ensure fonts: ${String(err)}`);
|
|
1462
1710
|
}
|
|
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
1711
|
}
|
|
1473
1712
|
return {
|
|
1474
1713
|
validate(input) {
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1714
|
+
try {
|
|
1715
|
+
const { value, error } = RichTextAssetSchema.validate(input, {
|
|
1716
|
+
abortEarly: false,
|
|
1717
|
+
convert: true
|
|
1718
|
+
});
|
|
1719
|
+
if (error) throw error;
|
|
1720
|
+
return { value };
|
|
1721
|
+
} catch (err) {
|
|
1722
|
+
if (err instanceof Error) {
|
|
1723
|
+
throw new Error(`Validation failed: ${err.message}`);
|
|
1724
|
+
}
|
|
1725
|
+
throw new Error(`Validation failed: ${String(err)}`);
|
|
1726
|
+
}
|
|
1481
1727
|
},
|
|
1482
1728
|
async registerFontFromFile(path, desc) {
|
|
1483
|
-
|
|
1484
|
-
|
|
1729
|
+
try {
|
|
1730
|
+
const bytes = await loadFileOrHttpToArrayBuffer(path);
|
|
1731
|
+
await fonts.registerFromBytes(bytes, desc);
|
|
1732
|
+
} catch (err) {
|
|
1733
|
+
throw new Error(
|
|
1734
|
+
`Failed to register font "${desc.family}" from file ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
1735
|
+
);
|
|
1736
|
+
}
|
|
1485
1737
|
},
|
|
1486
1738
|
async registerFontFromUrl(url, desc) {
|
|
1487
|
-
|
|
1488
|
-
|
|
1739
|
+
try {
|
|
1740
|
+
const bytes = await loadFileOrHttpToArrayBuffer(url);
|
|
1741
|
+
await fonts.registerFromBytes(bytes, desc);
|
|
1742
|
+
} catch (err) {
|
|
1743
|
+
throw new Error(
|
|
1744
|
+
`Failed to register font "${desc.family}" from URL ${url}: ${err instanceof Error ? err.message : String(err)}`
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1489
1747
|
},
|
|
1490
1748
|
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
|
-
|
|
1749
|
+
try {
|
|
1750
|
+
const main = await ensureFonts(asset);
|
|
1751
|
+
const desc = { family: main.family, weight: `${main.weight}`, style: main.style };
|
|
1752
|
+
let lines;
|
|
1753
|
+
try {
|
|
1754
|
+
lines = await layout.layout({
|
|
1755
|
+
text: asset.text,
|
|
1756
|
+
width: asset.width ?? width,
|
|
1757
|
+
letterSpacing: asset.style?.letterSpacing ?? 0,
|
|
1758
|
+
fontSize: main.size,
|
|
1759
|
+
lineHeight: asset.style?.lineHeight ?? 1.2,
|
|
1760
|
+
desc,
|
|
1761
|
+
textTransform: asset.style?.textTransform ?? "none"
|
|
1762
|
+
});
|
|
1763
|
+
} catch (err) {
|
|
1764
|
+
throw new Error(
|
|
1765
|
+
`Failed to layout text: ${err instanceof Error ? err.message : String(err)}`
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1768
|
+
const textRect = {
|
|
1769
|
+
x: 0,
|
|
1770
|
+
y: 0,
|
|
1771
|
+
width: asset.width ?? width,
|
|
1772
|
+
height: asset.height ?? height
|
|
1773
|
+
};
|
|
1774
|
+
const canvasW = asset.width ?? width;
|
|
1775
|
+
const canvasH = asset.height ?? height;
|
|
1776
|
+
const canvasPR = asset.pixelRatio ?? pixelRatio;
|
|
1777
|
+
let ops0;
|
|
1778
|
+
try {
|
|
1779
|
+
ops0 = await buildDrawOps({
|
|
1780
|
+
canvas: { width: canvasW, height: canvasH, pixelRatio: canvasPR },
|
|
1781
|
+
textRect,
|
|
1782
|
+
lines,
|
|
1783
|
+
font: {
|
|
1784
|
+
family: main.family,
|
|
1785
|
+
size: main.size,
|
|
1786
|
+
weight: `${main.weight}`,
|
|
1787
|
+
style: main.style,
|
|
1788
|
+
color: main.color,
|
|
1789
|
+
opacity: main.opacity
|
|
1790
|
+
},
|
|
1791
|
+
style: {
|
|
1792
|
+
lineHeight: asset.style?.lineHeight ?? 1.2,
|
|
1793
|
+
textDecoration: asset.style?.textDecoration ?? "none",
|
|
1794
|
+
gradient: asset.style?.gradient
|
|
1795
|
+
},
|
|
1796
|
+
stroke: asset.stroke,
|
|
1797
|
+
shadow: asset.shadow,
|
|
1798
|
+
align: asset.align ?? { horizontal: "left", vertical: "middle" },
|
|
1799
|
+
background: asset.background,
|
|
1800
|
+
glyphPathProvider: (gid) => fonts.glyphPath(desc, gid),
|
|
1801
|
+
getUnitsPerEm: () => fonts.getUnitsPerEm(desc)
|
|
1802
|
+
});
|
|
1803
|
+
} catch (err) {
|
|
1804
|
+
throw new Error(
|
|
1805
|
+
`Failed to build draw operations: ${err instanceof Error ? err.message : String(err)}`
|
|
1806
|
+
);
|
|
1807
|
+
}
|
|
1808
|
+
try {
|
|
1809
|
+
const ops = applyAnimation(ops0, lines, {
|
|
1810
|
+
t: tSeconds,
|
|
1811
|
+
fontSize: main.size,
|
|
1812
|
+
anim: asset.animation ? {
|
|
1813
|
+
preset: asset.animation.preset,
|
|
1814
|
+
speed: asset.animation.speed,
|
|
1815
|
+
duration: asset.animation.duration,
|
|
1816
|
+
style: asset.animation.style,
|
|
1817
|
+
direction: asset.animation.direction
|
|
1818
|
+
} : void 0
|
|
1819
|
+
});
|
|
1820
|
+
return ops;
|
|
1821
|
+
} catch (err) {
|
|
1822
|
+
throw new Error(
|
|
1823
|
+
`Failed to apply animation: ${err instanceof Error ? err.message : String(err)}`
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1826
|
+
} catch (err) {
|
|
1827
|
+
if (err instanceof Error) {
|
|
1828
|
+
throw err;
|
|
1829
|
+
}
|
|
1830
|
+
throw new Error(`Failed to render frame at time ${tSeconds}s: ${String(err)}`);
|
|
1831
|
+
}
|
|
1542
1832
|
},
|
|
1543
1833
|
async createRenderer(p) {
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1834
|
+
try {
|
|
1835
|
+
return await createNodePainter({
|
|
1836
|
+
width: p.width ?? width,
|
|
1837
|
+
height: p.height ?? height,
|
|
1838
|
+
pixelRatio: p.pixelRatio ?? pixelRatio
|
|
1839
|
+
});
|
|
1840
|
+
} catch (err) {
|
|
1841
|
+
throw new Error(
|
|
1842
|
+
`Failed to create renderer: ${err instanceof Error ? err.message : String(err)}`
|
|
1843
|
+
);
|
|
1844
|
+
}
|
|
1549
1845
|
},
|
|
1550
1846
|
async generateVideo(asset, options) {
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1847
|
+
try {
|
|
1848
|
+
const finalOptions = {
|
|
1849
|
+
width: asset.width ?? width,
|
|
1850
|
+
height: asset.height ?? height,
|
|
1851
|
+
fps,
|
|
1852
|
+
duration: asset.animation?.duration ?? 3,
|
|
1853
|
+
outputPath: options.outputPath ?? "output.mp4",
|
|
1854
|
+
pixelRatio: asset.pixelRatio ?? pixelRatio
|
|
1855
|
+
};
|
|
1856
|
+
const frameGenerator = async (time) => {
|
|
1857
|
+
return this.renderFrame(asset, time);
|
|
1858
|
+
};
|
|
1859
|
+
await videoGenerator.generateVideo(frameGenerator, finalOptions);
|
|
1860
|
+
} catch (err) {
|
|
1861
|
+
throw new Error(
|
|
1862
|
+
`Failed to generate video: ${err instanceof Error ? err.message : String(err)}`
|
|
1863
|
+
);
|
|
1864
|
+
}
|
|
1563
1865
|
},
|
|
1564
1866
|
destroy() {
|
|
1565
|
-
|
|
1867
|
+
try {
|
|
1868
|
+
fonts.destroy();
|
|
1869
|
+
} catch (err) {
|
|
1870
|
+
console.error(`Error during cleanup: ${err instanceof Error ? err.message : String(err)}`);
|
|
1871
|
+
}
|
|
1566
1872
|
}
|
|
1567
1873
|
};
|
|
1568
1874
|
}
|
|
1569
1875
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1570
1876
|
0 && (module.exports = {
|
|
1571
|
-
createTextEngine
|
|
1877
|
+
createTextEngine,
|
|
1878
|
+
isGlyphFill,
|
|
1879
|
+
isShadowFill
|
|
1572
1880
|
});
|
|
1573
1881
|
//# sourceMappingURL=entry.node.cjs.map
|