@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.js
CHANGED
|
@@ -128,29 +128,42 @@ var RichTextAssetSchema = Joi.object({
|
|
|
128
128
|
var hbSingleton = null;
|
|
129
129
|
async function initHB(wasmBaseURL) {
|
|
130
130
|
if (hbSingleton) return hbSingleton;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
131
|
+
try {
|
|
132
|
+
const mod = await import("harfbuzzjs");
|
|
133
|
+
const candidate = mod.default;
|
|
134
|
+
let hb;
|
|
135
|
+
if (typeof candidate === "function") {
|
|
136
|
+
hb = await candidate();
|
|
137
|
+
} else if (candidate && typeof candidate.then === "function") {
|
|
138
|
+
hb = await candidate;
|
|
139
|
+
} else {
|
|
140
|
+
hb = candidate;
|
|
141
|
+
}
|
|
142
|
+
if (!hb || typeof hb.createBuffer !== "function" || typeof hb.createFont !== "function") {
|
|
143
|
+
throw new Error("Failed to initialize HarfBuzz: unexpected export shape from 'harfbuzzjs'.");
|
|
144
|
+
}
|
|
145
|
+
void wasmBaseURL;
|
|
146
|
+
hbSingleton = hb;
|
|
147
|
+
return hbSingleton;
|
|
148
|
+
} catch (err) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Failed to initialize HarfBuzz: ${err instanceof Error ? err.message : String(err)}`
|
|
151
|
+
);
|
|
142
152
|
}
|
|
143
|
-
return hbSingleton;
|
|
144
153
|
}
|
|
145
154
|
|
|
146
155
|
// src/core/font-registry.ts
|
|
147
|
-
var FontRegistry = class {
|
|
156
|
+
var FontRegistry = class _FontRegistry {
|
|
148
157
|
hb;
|
|
149
158
|
faces = /* @__PURE__ */ new Map();
|
|
150
159
|
fonts = /* @__PURE__ */ new Map();
|
|
151
160
|
blobs = /* @__PURE__ */ new Map();
|
|
152
161
|
wasmBaseURL;
|
|
153
162
|
initPromise;
|
|
163
|
+
static fallbackLoader;
|
|
164
|
+
static setFallbackLoader(loader) {
|
|
165
|
+
_FontRegistry.fallbackLoader = loader;
|
|
166
|
+
}
|
|
154
167
|
constructor(wasmBaseURL) {
|
|
155
168
|
this.wasmBaseURL = wasmBaseURL;
|
|
156
169
|
}
|
|
@@ -159,11 +172,15 @@ var FontRegistry = class {
|
|
|
159
172
|
await this.initPromise;
|
|
160
173
|
return;
|
|
161
174
|
}
|
|
162
|
-
if (this.hb)
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
175
|
+
if (this.hb) return;
|
|
165
176
|
this.initPromise = this._doInit();
|
|
166
|
-
|
|
177
|
+
try {
|
|
178
|
+
await this.initPromise;
|
|
179
|
+
} catch (err) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`Failed to initialize FontRegistry: ${err instanceof Error ? err.message : String(err)}`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
167
184
|
}
|
|
168
185
|
async _doInit() {
|
|
169
186
|
try {
|
|
@@ -175,7 +192,13 @@ var FontRegistry = class {
|
|
|
175
192
|
}
|
|
176
193
|
async getHB() {
|
|
177
194
|
if (!this.hb) {
|
|
178
|
-
|
|
195
|
+
try {
|
|
196
|
+
await this.init();
|
|
197
|
+
} catch (err) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`Failed to get HarfBuzz instance: ${err instanceof Error ? err.message : String(err)}`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
179
202
|
}
|
|
180
203
|
return this.hb;
|
|
181
204
|
}
|
|
@@ -183,48 +206,140 @@ var FontRegistry = class {
|
|
|
183
206
|
return `${desc.family}__${desc.weight ?? "400"}__${desc.style ?? "normal"}`;
|
|
184
207
|
}
|
|
185
208
|
async registerFromBytes(bytes, desc) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
209
|
+
try {
|
|
210
|
+
if (!this.hb) await this.init();
|
|
211
|
+
const k = this.key(desc);
|
|
212
|
+
if (this.fonts.has(k)) return;
|
|
213
|
+
const blob = this.hb.createBlob(bytes);
|
|
214
|
+
const face = this.hb.createFace(blob, 0);
|
|
215
|
+
const font = this.hb.createFont(face);
|
|
216
|
+
const upem = face.upem || 1e3;
|
|
217
|
+
font.setScale(upem, upem);
|
|
218
|
+
this.blobs.set(k, blob);
|
|
219
|
+
this.faces.set(k, face);
|
|
220
|
+
this.fonts.set(k, font);
|
|
221
|
+
} catch (err) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
`Failed to register font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async tryFallbackInstall(desc) {
|
|
228
|
+
const loader = _FontRegistry.fallbackLoader;
|
|
229
|
+
if (!loader) return false;
|
|
230
|
+
try {
|
|
231
|
+
const bytes = await loader({
|
|
232
|
+
family: desc.family,
|
|
233
|
+
weight: desc.weight ?? "400",
|
|
234
|
+
style: desc.style ?? "normal"
|
|
235
|
+
});
|
|
236
|
+
if (!bytes) return false;
|
|
237
|
+
await this.registerFromBytes(bytes, {
|
|
238
|
+
family: desc.family,
|
|
239
|
+
weight: desc.weight ?? "400",
|
|
240
|
+
style: desc.style ?? "normal"
|
|
241
|
+
});
|
|
242
|
+
return true;
|
|
243
|
+
} catch {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
197
246
|
}
|
|
198
247
|
async getFont(desc) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
248
|
+
try {
|
|
249
|
+
if (!this.hb) await this.init();
|
|
250
|
+
const k = this.key(desc);
|
|
251
|
+
let f = this.fonts.get(k);
|
|
252
|
+
if (!f) {
|
|
253
|
+
const installed = await this.tryFallbackInstall(desc);
|
|
254
|
+
f = installed ? this.fonts.get(k) : void 0;
|
|
255
|
+
}
|
|
256
|
+
if (!f) throw new Error(`Font not registered for ${k}`);
|
|
257
|
+
return f;
|
|
258
|
+
} catch (err) {
|
|
259
|
+
if (err instanceof Error && err.message.includes("Font not registered")) {
|
|
260
|
+
throw err;
|
|
261
|
+
}
|
|
262
|
+
throw new Error(
|
|
263
|
+
`Failed to get font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
204
266
|
}
|
|
205
267
|
async getFace(desc) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
268
|
+
try {
|
|
269
|
+
if (!this.hb) await this.init();
|
|
270
|
+
const k = this.key(desc);
|
|
271
|
+
let face = this.faces.get(k);
|
|
272
|
+
if (!face) {
|
|
273
|
+
const installed = await this.tryFallbackInstall(desc);
|
|
274
|
+
face = installed ? this.faces.get(k) : void 0;
|
|
275
|
+
}
|
|
276
|
+
return face;
|
|
277
|
+
} catch (err) {
|
|
278
|
+
throw new Error(
|
|
279
|
+
`Failed to get face for "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
209
282
|
}
|
|
210
283
|
async getUnitsPerEm(desc) {
|
|
211
|
-
|
|
212
|
-
|
|
284
|
+
try {
|
|
285
|
+
const face = await this.getFace(desc);
|
|
286
|
+
return face?.upem || 1e3;
|
|
287
|
+
} catch (err) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
`Failed to get units per em for "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
213
292
|
}
|
|
214
293
|
async glyphPath(desc, glyphId) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
294
|
+
try {
|
|
295
|
+
const font = await this.getFont(desc);
|
|
296
|
+
const path = font.glyphToPath(glyphId);
|
|
297
|
+
return path && path !== "" ? path : "M 0 0";
|
|
298
|
+
} catch (err) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
`Failed to get glyph path for glyph ${glyphId} in font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
218
303
|
}
|
|
219
304
|
destroy() {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
305
|
+
try {
|
|
306
|
+
for (const [, f] of this.fonts) {
|
|
307
|
+
try {
|
|
308
|
+
f.destroy();
|
|
309
|
+
} catch (err) {
|
|
310
|
+
console.error(
|
|
311
|
+
`Error destroying font: ${err instanceof Error ? err.message : String(err)}`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
for (const [, f] of this.faces) {
|
|
316
|
+
try {
|
|
317
|
+
f.destroy();
|
|
318
|
+
} catch (err) {
|
|
319
|
+
console.error(
|
|
320
|
+
`Error destroying face: ${err instanceof Error ? err.message : String(err)}`
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
for (const [, b] of this.blobs) {
|
|
325
|
+
try {
|
|
326
|
+
b.destroy();
|
|
327
|
+
} catch (err) {
|
|
328
|
+
console.error(
|
|
329
|
+
`Error destroying blob: ${err instanceof Error ? err.message : String(err)}`
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
this.fonts.clear();
|
|
334
|
+
this.faces.clear();
|
|
335
|
+
this.blobs.clear();
|
|
336
|
+
this.hb = void 0;
|
|
337
|
+
this.initPromise = void 0;
|
|
338
|
+
} catch (err) {
|
|
339
|
+
console.error(
|
|
340
|
+
`Error during FontRegistry cleanup: ${err instanceof Error ? err.message : String(err)}`
|
|
341
|
+
);
|
|
342
|
+
}
|
|
228
343
|
}
|
|
229
344
|
};
|
|
230
345
|
|
|
@@ -246,112 +361,145 @@ var LayoutEngine = class {
|
|
|
246
361
|
}
|
|
247
362
|
}
|
|
248
363
|
async shapeFull(text, desc) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
364
|
+
try {
|
|
365
|
+
const hb = await this.fonts.getHB();
|
|
366
|
+
const buffer = hb.createBuffer();
|
|
367
|
+
try {
|
|
368
|
+
buffer.addText(text);
|
|
369
|
+
buffer.guessSegmentProperties();
|
|
370
|
+
const font = await this.fonts.getFont(desc);
|
|
371
|
+
const face = await this.fonts.getFace(desc);
|
|
372
|
+
const upem = face?.upem || 1e3;
|
|
373
|
+
font.setScale(upem, upem);
|
|
374
|
+
hb.shape(font, buffer);
|
|
375
|
+
const result = buffer.json();
|
|
376
|
+
return result;
|
|
377
|
+
} finally {
|
|
378
|
+
try {
|
|
379
|
+
buffer.destroy();
|
|
380
|
+
} catch (err) {
|
|
381
|
+
console.error(
|
|
382
|
+
`Error destroying HarfBuzz buffer: ${err instanceof Error ? err.message : String(err)}`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
} catch (err) {
|
|
387
|
+
throw new Error(
|
|
388
|
+
`Failed to shape text with font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
261
391
|
}
|
|
262
392
|
async layout(params) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const shaped = await this.shapeFull(input, desc);
|
|
269
|
-
const face = await this.fonts.getFace(desc);
|
|
270
|
-
const upem = face?.upem || 1e3;
|
|
271
|
-
const scale = fontSize / upem;
|
|
272
|
-
const glyphs = shaped.map((g) => {
|
|
273
|
-
const charIndex = g.cl;
|
|
274
|
-
let char;
|
|
275
|
-
if (charIndex >= 0 && charIndex < input.length) {
|
|
276
|
-
char = input[charIndex];
|
|
393
|
+
try {
|
|
394
|
+
const { textTransform, desc, fontSize, letterSpacing, width } = params;
|
|
395
|
+
const input = this.transformText(params.text, textTransform);
|
|
396
|
+
if (!input || input.length === 0) {
|
|
397
|
+
return [];
|
|
277
398
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
cluster: g.cl,
|
|
284
|
-
char
|
|
285
|
-
// This now correctly maps to the original character
|
|
286
|
-
};
|
|
287
|
-
});
|
|
288
|
-
const lines = [];
|
|
289
|
-
let currentLine = [];
|
|
290
|
-
let currentWidth = 0;
|
|
291
|
-
const spaceIndices = /* @__PURE__ */ new Set();
|
|
292
|
-
for (let i = 0; i < input.length; i++) {
|
|
293
|
-
if (input[i] === " ") {
|
|
294
|
-
spaceIndices.add(i);
|
|
399
|
+
let shaped;
|
|
400
|
+
try {
|
|
401
|
+
shaped = await this.shapeFull(input, desc);
|
|
402
|
+
} catch (err) {
|
|
403
|
+
throw new Error(`Text shaping failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
295
404
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
405
|
+
let upem;
|
|
406
|
+
try {
|
|
407
|
+
const face = await this.fonts.getFace(desc);
|
|
408
|
+
upem = face?.upem || 1e3;
|
|
409
|
+
} catch (err) {
|
|
410
|
+
throw new Error(
|
|
411
|
+
`Failed to get font metrics: ${err instanceof Error ? err.message : String(err)}`
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
const scale = fontSize / upem;
|
|
415
|
+
const glyphs = shaped.map((g) => {
|
|
416
|
+
const charIndex = g.cl;
|
|
417
|
+
let char;
|
|
418
|
+
if (charIndex >= 0 && charIndex < input.length) {
|
|
419
|
+
char = input[charIndex];
|
|
420
|
+
}
|
|
421
|
+
return {
|
|
422
|
+
id: g.g,
|
|
423
|
+
xAdvance: g.ax * scale + letterSpacing,
|
|
424
|
+
xOffset: g.dx * scale,
|
|
425
|
+
yOffset: -g.dy * scale,
|
|
426
|
+
cluster: g.cl,
|
|
427
|
+
char
|
|
428
|
+
};
|
|
429
|
+
});
|
|
430
|
+
const lines = [];
|
|
431
|
+
let currentLine = [];
|
|
432
|
+
let currentWidth = 0;
|
|
433
|
+
const spaceIndices = /* @__PURE__ */ new Set();
|
|
434
|
+
for (let i = 0; i < input.length; i++) {
|
|
435
|
+
if (input[i] === " ") {
|
|
436
|
+
spaceIndices.add(i);
|
|
308
437
|
}
|
|
309
|
-
currentLine = [];
|
|
310
|
-
currentWidth = 0;
|
|
311
|
-
lastBreakIndex = i;
|
|
312
|
-
continue;
|
|
313
438
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
} else {
|
|
327
|
-
lines.push({
|
|
328
|
-
glyphs: currentLine,
|
|
329
|
-
width: currentWidth,
|
|
330
|
-
y: 0
|
|
331
|
-
});
|
|
439
|
+
let lastBreakIndex = -1;
|
|
440
|
+
for (let i = 0; i < glyphs.length; i++) {
|
|
441
|
+
const glyph = glyphs[i];
|
|
442
|
+
const glyphWidth = glyph.xAdvance;
|
|
443
|
+
if (glyph.char === "\n") {
|
|
444
|
+
if (currentLine.length > 0) {
|
|
445
|
+
lines.push({
|
|
446
|
+
glyphs: currentLine,
|
|
447
|
+
width: currentWidth,
|
|
448
|
+
y: 0
|
|
449
|
+
});
|
|
450
|
+
}
|
|
332
451
|
currentLine = [];
|
|
333
452
|
currentWidth = 0;
|
|
453
|
+
lastBreakIndex = i;
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (currentWidth + glyphWidth > width && currentLine.length > 0) {
|
|
457
|
+
if (lastBreakIndex > -1) {
|
|
458
|
+
const breakPoint = lastBreakIndex - (i - currentLine.length) + 1;
|
|
459
|
+
const nextLine = currentLine.splice(breakPoint);
|
|
460
|
+
const lineWidth = currentLine.reduce((sum, g) => sum + g.xAdvance, 0);
|
|
461
|
+
lines.push({
|
|
462
|
+
glyphs: currentLine,
|
|
463
|
+
width: lineWidth,
|
|
464
|
+
y: 0
|
|
465
|
+
});
|
|
466
|
+
currentLine = nextLine;
|
|
467
|
+
currentWidth = nextLine.reduce((sum, g) => sum + g.xAdvance, 0);
|
|
468
|
+
} else {
|
|
469
|
+
lines.push({
|
|
470
|
+
glyphs: currentLine,
|
|
471
|
+
width: currentWidth,
|
|
472
|
+
y: 0
|
|
473
|
+
});
|
|
474
|
+
currentLine = [];
|
|
475
|
+
currentWidth = 0;
|
|
476
|
+
}
|
|
477
|
+
lastBreakIndex = -1;
|
|
478
|
+
}
|
|
479
|
+
currentLine.push(glyph);
|
|
480
|
+
currentWidth += glyphWidth;
|
|
481
|
+
if (spaceIndices.has(glyph.cluster)) {
|
|
482
|
+
lastBreakIndex = i;
|
|
334
483
|
}
|
|
335
|
-
lastBreakIndex = -1;
|
|
336
484
|
}
|
|
337
|
-
currentLine.
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
485
|
+
if (currentLine.length > 0) {
|
|
486
|
+
lines.push({
|
|
487
|
+
glyphs: currentLine,
|
|
488
|
+
width: currentWidth,
|
|
489
|
+
y: 0
|
|
490
|
+
});
|
|
341
491
|
}
|
|
492
|
+
const lineHeight = params.lineHeight * fontSize;
|
|
493
|
+
for (let i = 0; i < lines.length; i++) {
|
|
494
|
+
lines[i].y = (i + 1) * lineHeight;
|
|
495
|
+
}
|
|
496
|
+
return lines;
|
|
497
|
+
} catch (err) {
|
|
498
|
+
if (err instanceof Error) {
|
|
499
|
+
throw err;
|
|
500
|
+
}
|
|
501
|
+
throw new Error(`Layout failed: ${String(err)}`);
|
|
342
502
|
}
|
|
343
|
-
if (currentLine.length > 0) {
|
|
344
|
-
lines.push({
|
|
345
|
-
glyphs: currentLine,
|
|
346
|
-
width: currentWidth,
|
|
347
|
-
y: 0
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
const lineHeight = params.lineHeight * fontSize;
|
|
351
|
-
for (let i = 0; i < lines.length; i++) {
|
|
352
|
-
lines[i].y = (i + 1) * lineHeight;
|
|
353
|
-
}
|
|
354
|
-
return lines;
|
|
355
503
|
}
|
|
356
504
|
};
|
|
357
505
|
|
|
@@ -443,7 +591,6 @@ async function buildDrawOps(p) {
|
|
|
443
591
|
path,
|
|
444
592
|
x: glyphX + p.shadow.offsetX,
|
|
445
593
|
y: glyphY + p.shadow.offsetY,
|
|
446
|
-
// @ts-ignore scale propagated to painters
|
|
447
594
|
scale,
|
|
448
595
|
fill: { kind: "solid", color: p.shadow.color, opacity: p.shadow.opacity }
|
|
449
596
|
});
|
|
@@ -454,7 +601,6 @@ async function buildDrawOps(p) {
|
|
|
454
601
|
path,
|
|
455
602
|
x: glyphX,
|
|
456
603
|
y: glyphY,
|
|
457
|
-
// @ts-ignore scale propagated to painters
|
|
458
604
|
scale,
|
|
459
605
|
width: p.stroke.width,
|
|
460
606
|
color: p.stroke.color,
|
|
@@ -466,7 +612,6 @@ async function buildDrawOps(p) {
|
|
|
466
612
|
path,
|
|
467
613
|
x: glyphX,
|
|
468
614
|
y: glyphY,
|
|
469
|
-
// @ts-ignore scale propagated to painters
|
|
470
615
|
scale,
|
|
471
616
|
fill
|
|
472
617
|
});
|
|
@@ -1042,30 +1187,32 @@ async function createNodePainter(opts) {
|
|
|
1042
1187
|
continue;
|
|
1043
1188
|
}
|
|
1044
1189
|
if (op.op === "FillPath") {
|
|
1190
|
+
const fillOp = op;
|
|
1045
1191
|
ctx.save();
|
|
1046
|
-
ctx.translate(
|
|
1047
|
-
const s =
|
|
1192
|
+
ctx.translate(fillOp.x, fillOp.y);
|
|
1193
|
+
const s = fillOp.scale ?? 1;
|
|
1048
1194
|
ctx.scale(s, -s);
|
|
1049
1195
|
ctx.beginPath();
|
|
1050
|
-
drawSvgPathOnCtx(ctx,
|
|
1051
|
-
const bbox =
|
|
1052
|
-
const fill = makeGradientFromBBox(ctx,
|
|
1196
|
+
drawSvgPathOnCtx(ctx, fillOp.path);
|
|
1197
|
+
const bbox = fillOp.gradientBBox ?? globalBox;
|
|
1198
|
+
const fill = makeGradientFromBBox(ctx, fillOp.fill, bbox);
|
|
1053
1199
|
ctx.fillStyle = fill;
|
|
1054
1200
|
ctx.fill();
|
|
1055
1201
|
ctx.restore();
|
|
1056
1202
|
continue;
|
|
1057
1203
|
}
|
|
1058
1204
|
if (op.op === "StrokePath") {
|
|
1205
|
+
const strokeOp = op;
|
|
1059
1206
|
ctx.save();
|
|
1060
|
-
ctx.translate(
|
|
1061
|
-
const s =
|
|
1207
|
+
ctx.translate(strokeOp.x, strokeOp.y);
|
|
1208
|
+
const s = strokeOp.scale ?? 1;
|
|
1062
1209
|
ctx.scale(s, -s);
|
|
1063
1210
|
const invAbs = 1 / Math.abs(s);
|
|
1064
1211
|
ctx.beginPath();
|
|
1065
|
-
drawSvgPathOnCtx(ctx,
|
|
1066
|
-
const c = parseHex6(
|
|
1212
|
+
drawSvgPathOnCtx(ctx, strokeOp.path);
|
|
1213
|
+
const c = parseHex6(strokeOp.color, strokeOp.opacity);
|
|
1067
1214
|
ctx.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1068
|
-
ctx.lineWidth =
|
|
1215
|
+
ctx.lineWidth = strokeOp.width * invAbs;
|
|
1069
1216
|
ctx.lineJoin = "round";
|
|
1070
1217
|
ctx.lineCap = "round";
|
|
1071
1218
|
ctx.stroke();
|
|
@@ -1097,17 +1244,20 @@ function makeGradientFromBBox(ctx, spec, box) {
|
|
|
1097
1244
|
const c = parseHex6(spec.color, spec.opacity);
|
|
1098
1245
|
return `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1099
1246
|
}
|
|
1100
|
-
const cx = box.x + box.w / 2
|
|
1247
|
+
const cx = box.x + box.w / 2;
|
|
1248
|
+
const cy = box.y + box.h / 2;
|
|
1249
|
+
const r = Math.max(box.w, box.h) / 2;
|
|
1101
1250
|
const addStops = (g) => {
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1104
|
-
|
|
1251
|
+
const opacity = spec.kind === "linear" || spec.kind === "radial" ? spec.opacity : 1;
|
|
1252
|
+
const stops = spec.kind === "linear" || spec.kind === "radial" ? spec.stops : [];
|
|
1253
|
+
for (const s of stops) {
|
|
1254
|
+
const c = parseHex6(s.color, opacity);
|
|
1105
1255
|
g.addColorStop(s.offset, `rgba(${c.r},${c.g},${c.b},${c.a})`);
|
|
1106
1256
|
}
|
|
1107
1257
|
return g;
|
|
1108
1258
|
};
|
|
1109
1259
|
if (spec.kind === "linear") {
|
|
1110
|
-
const rad =
|
|
1260
|
+
const rad = spec.angle * Math.PI / 180;
|
|
1111
1261
|
const x1 = cx + Math.cos(rad + Math.PI) * r;
|
|
1112
1262
|
const y1 = cy + Math.sin(rad + Math.PI) * r;
|
|
1113
1263
|
const x2 = cx + Math.cos(rad) * r;
|
|
@@ -1118,22 +1268,34 @@ function makeGradientFromBBox(ctx, spec, box) {
|
|
|
1118
1268
|
}
|
|
1119
1269
|
}
|
|
1120
1270
|
function computeGlobalTextBounds(ops) {
|
|
1121
|
-
let minX = Infinity
|
|
1271
|
+
let minX = Infinity;
|
|
1272
|
+
let minY = Infinity;
|
|
1273
|
+
let maxX = -Infinity;
|
|
1274
|
+
let maxY = -Infinity;
|
|
1122
1275
|
for (const op of ops) {
|
|
1123
|
-
if (op.op !== "FillPath"
|
|
1124
|
-
const
|
|
1125
|
-
|
|
1126
|
-
const
|
|
1127
|
-
const
|
|
1128
|
-
const
|
|
1129
|
-
const
|
|
1276
|
+
if (op.op !== "FillPath") continue;
|
|
1277
|
+
const fillOp = op;
|
|
1278
|
+
if (fillOp.isShadow) continue;
|
|
1279
|
+
const b = computePathBounds2(fillOp.path);
|
|
1280
|
+
const s = fillOp.scale ?? 1;
|
|
1281
|
+
const x1 = fillOp.x + s * b.x;
|
|
1282
|
+
const x2 = fillOp.x + s * (b.x + b.w);
|
|
1283
|
+
const y1 = fillOp.y - s * (b.y + b.h);
|
|
1284
|
+
const y2 = fillOp.y - s * b.y;
|
|
1130
1285
|
if (x1 < minX) minX = x1;
|
|
1131
1286
|
if (y1 < minY) minY = y1;
|
|
1132
1287
|
if (x2 > maxX) maxX = x2;
|
|
1133
1288
|
if (y2 > maxY) maxY = y2;
|
|
1134
1289
|
}
|
|
1135
|
-
if (minX === Infinity)
|
|
1136
|
-
|
|
1290
|
+
if (minX === Infinity) {
|
|
1291
|
+
return { x: 0, y: 0, w: 1, h: 1 };
|
|
1292
|
+
}
|
|
1293
|
+
return {
|
|
1294
|
+
x: minX,
|
|
1295
|
+
y: minY,
|
|
1296
|
+
w: Math.max(1, maxX - minX),
|
|
1297
|
+
h: Math.max(1, maxY - minY)
|
|
1298
|
+
};
|
|
1137
1299
|
}
|
|
1138
1300
|
function drawSvgPathOnCtx(ctx, d) {
|
|
1139
1301
|
const t = tokenizePath2(d);
|
|
@@ -1175,13 +1337,18 @@ function drawSvgPathOnCtx(ctx, d) {
|
|
|
1175
1337
|
ctx.closePath();
|
|
1176
1338
|
break;
|
|
1177
1339
|
}
|
|
1340
|
+
default:
|
|
1341
|
+
break;
|
|
1178
1342
|
}
|
|
1179
1343
|
}
|
|
1180
1344
|
}
|
|
1181
1345
|
function computePathBounds2(d) {
|
|
1182
1346
|
const t = tokenizePath2(d);
|
|
1183
1347
|
let i = 0;
|
|
1184
|
-
let minX = Infinity
|
|
1348
|
+
let minX = Infinity;
|
|
1349
|
+
let minY = Infinity;
|
|
1350
|
+
let maxX = -Infinity;
|
|
1351
|
+
let maxY = -Infinity;
|
|
1185
1352
|
const touch = (x, y) => {
|
|
1186
1353
|
if (x < minX) minX = x;
|
|
1187
1354
|
if (y < minY) minY = y;
|
|
@@ -1221,10 +1388,19 @@ function computePathBounds2(d) {
|
|
|
1221
1388
|
}
|
|
1222
1389
|
case "Z":
|
|
1223
1390
|
break;
|
|
1391
|
+
default:
|
|
1392
|
+
break;
|
|
1224
1393
|
}
|
|
1225
1394
|
}
|
|
1226
|
-
if (minX === Infinity)
|
|
1227
|
-
|
|
1395
|
+
if (minX === Infinity) {
|
|
1396
|
+
return { x: 0, y: 0, w: 0, h: 0 };
|
|
1397
|
+
}
|
|
1398
|
+
return {
|
|
1399
|
+
x: minX,
|
|
1400
|
+
y: minY,
|
|
1401
|
+
w: maxX - minX,
|
|
1402
|
+
h: maxY - minY
|
|
1403
|
+
};
|
|
1228
1404
|
}
|
|
1229
1405
|
function roundRectPath(ctx, x, y, w, h, r) {
|
|
1230
1406
|
ctx.moveTo(x + r, y);
|
|
@@ -1253,20 +1429,54 @@ function bufferToArrayBuffer(buf) {
|
|
|
1253
1429
|
return ab.slice(byteOffset, byteOffset + byteLength);
|
|
1254
1430
|
}
|
|
1255
1431
|
async function loadFileOrHttpToArrayBuffer(pathOrUrl) {
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
const
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1432
|
+
try {
|
|
1433
|
+
if (/^https?:\/\//.test(pathOrUrl)) {
|
|
1434
|
+
const client = pathOrUrl.startsWith("https:") ? https : http;
|
|
1435
|
+
const buf2 = await new Promise((resolve, reject) => {
|
|
1436
|
+
const request = client.get(pathOrUrl, (res) => {
|
|
1437
|
+
const { statusCode } = res;
|
|
1438
|
+
if (statusCode && (statusCode < 200 || statusCode >= 300)) {
|
|
1439
|
+
reject(new Error(`HTTP request failed with status ${statusCode} for ${pathOrUrl}`));
|
|
1440
|
+
res.resume();
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
const chunks = [];
|
|
1444
|
+
res.on("data", (chunk) => {
|
|
1445
|
+
chunks.push(chunk);
|
|
1446
|
+
});
|
|
1447
|
+
res.on("end", () => {
|
|
1448
|
+
try {
|
|
1449
|
+
resolve(Buffer.concat(chunks));
|
|
1450
|
+
} catch (err) {
|
|
1451
|
+
reject(
|
|
1452
|
+
new Error(
|
|
1453
|
+
`Failed to concatenate response chunks: ${err instanceof Error ? err.message : String(err)}`
|
|
1454
|
+
)
|
|
1455
|
+
);
|
|
1456
|
+
}
|
|
1457
|
+
});
|
|
1458
|
+
res.on("error", (err) => {
|
|
1459
|
+
reject(new Error(`Response error for ${pathOrUrl}: ${err.message}`));
|
|
1460
|
+
});
|
|
1461
|
+
});
|
|
1462
|
+
request.on("error", (err) => {
|
|
1463
|
+
reject(new Error(`Request error for ${pathOrUrl}: ${err.message}`));
|
|
1464
|
+
});
|
|
1465
|
+
request.setTimeout(3e4, () => {
|
|
1466
|
+
request.destroy();
|
|
1467
|
+
reject(new Error(`Request timeout after 30s for ${pathOrUrl}`));
|
|
1468
|
+
});
|
|
1469
|
+
});
|
|
1470
|
+
return bufferToArrayBuffer(buf2);
|
|
1471
|
+
}
|
|
1472
|
+
const buf = await readFile(pathOrUrl);
|
|
1473
|
+
return bufferToArrayBuffer(buf);
|
|
1474
|
+
} catch (err) {
|
|
1475
|
+
if (err instanceof Error) {
|
|
1476
|
+
throw new Error(`Failed to load ${pathOrUrl}: ${err.message}`);
|
|
1477
|
+
}
|
|
1478
|
+
throw new Error(`Failed to load ${pathOrUrl}: ${String(err)}`);
|
|
1267
1479
|
}
|
|
1268
|
-
const buf = await readFile(pathOrUrl);
|
|
1269
|
-
return bufferToArrayBuffer(buf);
|
|
1270
1480
|
}
|
|
1271
1481
|
|
|
1272
1482
|
// src/core/video-generator.ts
|
|
@@ -1402,6 +1612,14 @@ var VideoGenerator = class {
|
|
|
1402
1612
|
}
|
|
1403
1613
|
};
|
|
1404
1614
|
|
|
1615
|
+
// src/types.ts
|
|
1616
|
+
var isShadowFill2 = (op) => {
|
|
1617
|
+
return op.op === "FillPath" && op.isShadow === true;
|
|
1618
|
+
};
|
|
1619
|
+
var isGlyphFill2 = (op) => {
|
|
1620
|
+
return op.op === "FillPath" && op.isShadow !== true;
|
|
1621
|
+
};
|
|
1622
|
+
|
|
1405
1623
|
// src/env/entry.node.ts
|
|
1406
1624
|
async function createTextEngine(opts = {}) {
|
|
1407
1625
|
const width = opts.width ?? CANVAS_CONFIG.DEFAULTS.width;
|
|
@@ -1412,125 +1630,213 @@ async function createTextEngine(opts = {}) {
|
|
|
1412
1630
|
const fonts = new FontRegistry(wasmBaseURL);
|
|
1413
1631
|
const layout = new LayoutEngine(fonts);
|
|
1414
1632
|
const videoGenerator = new VideoGenerator();
|
|
1415
|
-
|
|
1633
|
+
try {
|
|
1634
|
+
await fonts.init();
|
|
1635
|
+
} catch (err) {
|
|
1636
|
+
throw new Error(
|
|
1637
|
+
`Failed to initialize font registry: ${err instanceof Error ? err.message : String(err)}`
|
|
1638
|
+
);
|
|
1639
|
+
}
|
|
1416
1640
|
async function ensureFonts(asset) {
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
const
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1641
|
+
try {
|
|
1642
|
+
if (asset.customFonts) {
|
|
1643
|
+
for (const cf of asset.customFonts) {
|
|
1644
|
+
try {
|
|
1645
|
+
const bytes = await loadFileOrHttpToArrayBuffer(cf.src);
|
|
1646
|
+
await fonts.registerFromBytes(bytes, {
|
|
1647
|
+
family: cf.family,
|
|
1648
|
+
weight: cf.weight ?? "400",
|
|
1649
|
+
style: cf.style ?? "normal"
|
|
1650
|
+
});
|
|
1651
|
+
} catch (err) {
|
|
1652
|
+
throw new Error(
|
|
1653
|
+
`Failed to load custom font "${cf.family}" from ${cf.src}: ${err instanceof Error ? err.message : String(err)}`
|
|
1654
|
+
);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1425
1657
|
}
|
|
1658
|
+
const main = asset.font ?? {
|
|
1659
|
+
family: "Roboto",
|
|
1660
|
+
weight: "400",
|
|
1661
|
+
style: "normal",
|
|
1662
|
+
size: 48,
|
|
1663
|
+
color: "#000000",
|
|
1664
|
+
opacity: 1
|
|
1665
|
+
};
|
|
1666
|
+
return main;
|
|
1667
|
+
} catch (err) {
|
|
1668
|
+
if (err instanceof Error) {
|
|
1669
|
+
throw err;
|
|
1670
|
+
}
|
|
1671
|
+
throw new Error(`Failed to ensure fonts: ${String(err)}`);
|
|
1426
1672
|
}
|
|
1427
|
-
const main = asset.font ?? {
|
|
1428
|
-
family: "Roboto",
|
|
1429
|
-
weight: "400",
|
|
1430
|
-
style: "normal",
|
|
1431
|
-
size: 48,
|
|
1432
|
-
color: "#000000",
|
|
1433
|
-
opacity: 1
|
|
1434
|
-
};
|
|
1435
|
-
return main;
|
|
1436
1673
|
}
|
|
1437
1674
|
return {
|
|
1438
1675
|
validate(input) {
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1676
|
+
try {
|
|
1677
|
+
const { value, error } = RichTextAssetSchema.validate(input, {
|
|
1678
|
+
abortEarly: false,
|
|
1679
|
+
convert: true
|
|
1680
|
+
});
|
|
1681
|
+
if (error) throw error;
|
|
1682
|
+
return { value };
|
|
1683
|
+
} catch (err) {
|
|
1684
|
+
if (err instanceof Error) {
|
|
1685
|
+
throw new Error(`Validation failed: ${err.message}`);
|
|
1686
|
+
}
|
|
1687
|
+
throw new Error(`Validation failed: ${String(err)}`);
|
|
1688
|
+
}
|
|
1445
1689
|
},
|
|
1446
1690
|
async registerFontFromFile(path, desc) {
|
|
1447
|
-
|
|
1448
|
-
|
|
1691
|
+
try {
|
|
1692
|
+
const bytes = await loadFileOrHttpToArrayBuffer(path);
|
|
1693
|
+
await fonts.registerFromBytes(bytes, desc);
|
|
1694
|
+
} catch (err) {
|
|
1695
|
+
throw new Error(
|
|
1696
|
+
`Failed to register font "${desc.family}" from file ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
1697
|
+
);
|
|
1698
|
+
}
|
|
1449
1699
|
},
|
|
1450
1700
|
async registerFontFromUrl(url, desc) {
|
|
1451
|
-
|
|
1452
|
-
|
|
1701
|
+
try {
|
|
1702
|
+
const bytes = await loadFileOrHttpToArrayBuffer(url);
|
|
1703
|
+
await fonts.registerFromBytes(bytes, desc);
|
|
1704
|
+
} catch (err) {
|
|
1705
|
+
throw new Error(
|
|
1706
|
+
`Failed to register font "${desc.family}" from URL ${url}: ${err instanceof Error ? err.message : String(err)}`
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1453
1709
|
},
|
|
1454
1710
|
async renderFrame(asset, tSeconds) {
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1711
|
+
try {
|
|
1712
|
+
const main = await ensureFonts(asset);
|
|
1713
|
+
const desc = { family: main.family, weight: `${main.weight}`, style: main.style };
|
|
1714
|
+
let lines;
|
|
1715
|
+
try {
|
|
1716
|
+
lines = await layout.layout({
|
|
1717
|
+
text: asset.text,
|
|
1718
|
+
width: asset.width ?? width,
|
|
1719
|
+
letterSpacing: asset.style?.letterSpacing ?? 0,
|
|
1720
|
+
fontSize: main.size,
|
|
1721
|
+
lineHeight: asset.style?.lineHeight ?? 1.2,
|
|
1722
|
+
desc,
|
|
1723
|
+
textTransform: asset.style?.textTransform ?? "none"
|
|
1724
|
+
});
|
|
1725
|
+
} catch (err) {
|
|
1726
|
+
throw new Error(
|
|
1727
|
+
`Failed to layout text: ${err instanceof Error ? err.message : String(err)}`
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1730
|
+
const textRect = {
|
|
1731
|
+
x: 0,
|
|
1732
|
+
y: 0,
|
|
1733
|
+
width: asset.width ?? width,
|
|
1734
|
+
height: asset.height ?? height
|
|
1735
|
+
};
|
|
1736
|
+
const canvasW = asset.width ?? width;
|
|
1737
|
+
const canvasH = asset.height ?? height;
|
|
1738
|
+
const canvasPR = asset.pixelRatio ?? pixelRatio;
|
|
1739
|
+
let ops0;
|
|
1740
|
+
try {
|
|
1741
|
+
ops0 = await buildDrawOps({
|
|
1742
|
+
canvas: { width: canvasW, height: canvasH, pixelRatio: canvasPR },
|
|
1743
|
+
textRect,
|
|
1744
|
+
lines,
|
|
1745
|
+
font: {
|
|
1746
|
+
family: main.family,
|
|
1747
|
+
size: main.size,
|
|
1748
|
+
weight: `${main.weight}`,
|
|
1749
|
+
style: main.style,
|
|
1750
|
+
color: main.color,
|
|
1751
|
+
opacity: main.opacity
|
|
1752
|
+
},
|
|
1753
|
+
style: {
|
|
1754
|
+
lineHeight: asset.style?.lineHeight ?? 1.2,
|
|
1755
|
+
textDecoration: asset.style?.textDecoration ?? "none",
|
|
1756
|
+
gradient: asset.style?.gradient
|
|
1757
|
+
},
|
|
1758
|
+
stroke: asset.stroke,
|
|
1759
|
+
shadow: asset.shadow,
|
|
1760
|
+
align: asset.align ?? { horizontal: "left", vertical: "middle" },
|
|
1761
|
+
background: asset.background,
|
|
1762
|
+
glyphPathProvider: (gid) => fonts.glyphPath(desc, gid),
|
|
1763
|
+
getUnitsPerEm: () => fonts.getUnitsPerEm(desc)
|
|
1764
|
+
});
|
|
1765
|
+
} catch (err) {
|
|
1766
|
+
throw new Error(
|
|
1767
|
+
`Failed to build draw operations: ${err instanceof Error ? err.message : String(err)}`
|
|
1768
|
+
);
|
|
1769
|
+
}
|
|
1770
|
+
try {
|
|
1771
|
+
const ops = applyAnimation(ops0, lines, {
|
|
1772
|
+
t: tSeconds,
|
|
1773
|
+
fontSize: main.size,
|
|
1774
|
+
anim: asset.animation ? {
|
|
1775
|
+
preset: asset.animation.preset,
|
|
1776
|
+
speed: asset.animation.speed,
|
|
1777
|
+
duration: asset.animation.duration,
|
|
1778
|
+
style: asset.animation.style,
|
|
1779
|
+
direction: asset.animation.direction
|
|
1780
|
+
} : void 0
|
|
1781
|
+
});
|
|
1782
|
+
return ops;
|
|
1783
|
+
} catch (err) {
|
|
1784
|
+
throw new Error(
|
|
1785
|
+
`Failed to apply animation: ${err instanceof Error ? err.message : String(err)}`
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
} catch (err) {
|
|
1789
|
+
if (err instanceof Error) {
|
|
1790
|
+
throw err;
|
|
1791
|
+
}
|
|
1792
|
+
throw new Error(`Failed to render frame at time ${tSeconds}s: ${String(err)}`);
|
|
1793
|
+
}
|
|
1506
1794
|
},
|
|
1507
1795
|
async createRenderer(p) {
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1796
|
+
try {
|
|
1797
|
+
return await createNodePainter({
|
|
1798
|
+
width: p.width ?? width,
|
|
1799
|
+
height: p.height ?? height,
|
|
1800
|
+
pixelRatio: p.pixelRatio ?? pixelRatio
|
|
1801
|
+
});
|
|
1802
|
+
} catch (err) {
|
|
1803
|
+
throw new Error(
|
|
1804
|
+
`Failed to create renderer: ${err instanceof Error ? err.message : String(err)}`
|
|
1805
|
+
);
|
|
1806
|
+
}
|
|
1513
1807
|
},
|
|
1514
1808
|
async generateVideo(asset, options) {
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1809
|
+
try {
|
|
1810
|
+
const finalOptions = {
|
|
1811
|
+
width: asset.width ?? width,
|
|
1812
|
+
height: asset.height ?? height,
|
|
1813
|
+
fps,
|
|
1814
|
+
duration: asset.animation?.duration ?? 3,
|
|
1815
|
+
outputPath: options.outputPath ?? "output.mp4",
|
|
1816
|
+
pixelRatio: asset.pixelRatio ?? pixelRatio
|
|
1817
|
+
};
|
|
1818
|
+
const frameGenerator = async (time) => {
|
|
1819
|
+
return this.renderFrame(asset, time);
|
|
1820
|
+
};
|
|
1821
|
+
await videoGenerator.generateVideo(frameGenerator, finalOptions);
|
|
1822
|
+
} catch (err) {
|
|
1823
|
+
throw new Error(
|
|
1824
|
+
`Failed to generate video: ${err instanceof Error ? err.message : String(err)}`
|
|
1825
|
+
);
|
|
1826
|
+
}
|
|
1527
1827
|
},
|
|
1528
1828
|
destroy() {
|
|
1529
|
-
|
|
1829
|
+
try {
|
|
1830
|
+
fonts.destroy();
|
|
1831
|
+
} catch (err) {
|
|
1832
|
+
console.error(`Error during cleanup: ${err instanceof Error ? err.message : String(err)}`);
|
|
1833
|
+
}
|
|
1530
1834
|
}
|
|
1531
1835
|
};
|
|
1532
1836
|
}
|
|
1533
1837
|
export {
|
|
1534
|
-
createTextEngine
|
|
1838
|
+
createTextEngine,
|
|
1839
|
+
isGlyphFill2 as isGlyphFill,
|
|
1840
|
+
isShadowFill2 as isShadowFill
|
|
1535
1841
|
};
|
|
1536
1842
|
//# sourceMappingURL=entry.node.js.map
|