@shotstack/shotstack-canvas 1.0.9 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entry.node.cjs +558 -294
- package/dist/entry.node.cjs.map +1 -1
- package/dist/entry.node.d.cts +20 -1
- package/dist/entry.node.d.ts +20 -1
- package/dist/entry.node.js +555 -292
- package/dist/entry.node.js.map +1 -1
- package/dist/entry.web.d.ts +20 -1
- package/dist/entry.web.js +523 -268
- package/dist/entry.web.js.map +1 -1
- package/package.json +10 -4
- /package/{assets/wasm → public}/hb.wasm +0 -0
package/dist/entry.node.js
CHANGED
|
@@ -128,31 +128,28 @@ var RichTextAssetSchema = Joi.object({
|
|
|
128
128
|
var hbSingleton = null;
|
|
129
129
|
async function initHB(wasmBaseURL) {
|
|
130
130
|
if (hbSingleton) return hbSingleton;
|
|
131
|
-
const harfbuzzjs = await import("harfbuzzjs");
|
|
132
|
-
const factory = harfbuzzjs.default;
|
|
133
|
-
let hbUrlFromImport;
|
|
134
131
|
try {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (path.endsWith(".wasm")) {
|
|
145
|
-
return `${base}/${path}`.replace(/([^:])\/{2,}/g, "$1/");
|
|
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;
|
|
146
141
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
+
);
|
|
154
152
|
}
|
|
155
|
-
return hbSingleton;
|
|
156
153
|
}
|
|
157
154
|
|
|
158
155
|
// src/core/font-registry.ts
|
|
@@ -175,7 +172,13 @@ var FontRegistry = class {
|
|
|
175
172
|
return;
|
|
176
173
|
}
|
|
177
174
|
this.initPromise = this._doInit();
|
|
178
|
-
|
|
175
|
+
try {
|
|
176
|
+
await this.initPromise;
|
|
177
|
+
} catch (err) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`Failed to initialize FontRegistry: ${err instanceof Error ? err.message : String(err)}`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
179
182
|
}
|
|
180
183
|
async _doInit() {
|
|
181
184
|
try {
|
|
@@ -187,7 +190,13 @@ var FontRegistry = class {
|
|
|
187
190
|
}
|
|
188
191
|
async getHB() {
|
|
189
192
|
if (!this.hb) {
|
|
190
|
-
|
|
193
|
+
try {
|
|
194
|
+
await this.init();
|
|
195
|
+
} catch (err) {
|
|
196
|
+
throw new Error(
|
|
197
|
+
`Failed to get HarfBuzz instance: ${err instanceof Error ? err.message : String(err)}`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
191
200
|
}
|
|
192
201
|
return this.hb;
|
|
193
202
|
}
|
|
@@ -195,48 +204,111 @@ var FontRegistry = class {
|
|
|
195
204
|
return `${desc.family}__${desc.weight ?? "400"}__${desc.style ?? "normal"}`;
|
|
196
205
|
}
|
|
197
206
|
async registerFromBytes(bytes, desc) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
207
|
+
try {
|
|
208
|
+
if (!this.hb) await this.init();
|
|
209
|
+
const k = this.key(desc);
|
|
210
|
+
if (this.fonts.has(k)) return;
|
|
211
|
+
const blob = this.hb.createBlob(bytes);
|
|
212
|
+
const face = this.hb.createFace(blob, 0);
|
|
213
|
+
const font = this.hb.createFont(face);
|
|
214
|
+
const upem = face.upem || 1e3;
|
|
215
|
+
font.setScale(upem, upem);
|
|
216
|
+
this.blobs.set(k, blob);
|
|
217
|
+
this.faces.set(k, face);
|
|
218
|
+
this.fonts.set(k, font);
|
|
219
|
+
} catch (err) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
`Failed to register font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
209
224
|
}
|
|
210
225
|
async getFont(desc) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
226
|
+
try {
|
|
227
|
+
if (!this.hb) await this.init();
|
|
228
|
+
const k = this.key(desc);
|
|
229
|
+
const f = this.fonts.get(k);
|
|
230
|
+
if (!f) throw new Error(`Font not registered for ${k}`);
|
|
231
|
+
return f;
|
|
232
|
+
} catch (err) {
|
|
233
|
+
if (err instanceof Error && err.message.includes("Font not registered")) {
|
|
234
|
+
throw err;
|
|
235
|
+
}
|
|
236
|
+
throw new Error(
|
|
237
|
+
`Failed to get font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
216
240
|
}
|
|
217
241
|
async getFace(desc) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
242
|
+
try {
|
|
243
|
+
if (!this.hb) await this.init();
|
|
244
|
+
const k = this.key(desc);
|
|
245
|
+
return this.faces.get(k);
|
|
246
|
+
} catch (err) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
`Failed to get face for "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
221
251
|
}
|
|
222
252
|
async getUnitsPerEm(desc) {
|
|
223
|
-
|
|
224
|
-
|
|
253
|
+
try {
|
|
254
|
+
const face = await this.getFace(desc);
|
|
255
|
+
return face?.upem || 1e3;
|
|
256
|
+
} catch (err) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
`Failed to get units per em for "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
225
261
|
}
|
|
226
262
|
async glyphPath(desc, glyphId) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
263
|
+
try {
|
|
264
|
+
const font = await this.getFont(desc);
|
|
265
|
+
const path = font.glyphToPath(glyphId);
|
|
266
|
+
return path && path !== "" ? path : "M 0 0";
|
|
267
|
+
} catch (err) {
|
|
268
|
+
throw new Error(
|
|
269
|
+
`Failed to get glyph path for glyph ${glyphId} in font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
230
272
|
}
|
|
231
273
|
destroy() {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
274
|
+
try {
|
|
275
|
+
for (const [, f] of this.fonts) {
|
|
276
|
+
try {
|
|
277
|
+
f.destroy();
|
|
278
|
+
} catch (err) {
|
|
279
|
+
console.error(
|
|
280
|
+
`Error destroying font: ${err instanceof Error ? err.message : String(err)}`
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
for (const [, f] of this.faces) {
|
|
285
|
+
try {
|
|
286
|
+
f.destroy();
|
|
287
|
+
} catch (err) {
|
|
288
|
+
console.error(
|
|
289
|
+
`Error destroying face: ${err instanceof Error ? err.message : String(err)}`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
for (const [, b] of this.blobs) {
|
|
294
|
+
try {
|
|
295
|
+
b.destroy();
|
|
296
|
+
} catch (err) {
|
|
297
|
+
console.error(
|
|
298
|
+
`Error destroying blob: ${err instanceof Error ? err.message : String(err)}`
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
this.fonts.clear();
|
|
303
|
+
this.faces.clear();
|
|
304
|
+
this.blobs.clear();
|
|
305
|
+
this.hb = void 0;
|
|
306
|
+
this.initPromise = void 0;
|
|
307
|
+
} catch (err) {
|
|
308
|
+
console.error(
|
|
309
|
+
`Error during FontRegistry cleanup: ${err instanceof Error ? err.message : String(err)}`
|
|
310
|
+
);
|
|
311
|
+
}
|
|
240
312
|
}
|
|
241
313
|
};
|
|
242
314
|
|
|
@@ -258,112 +330,145 @@ var LayoutEngine = class {
|
|
|
258
330
|
}
|
|
259
331
|
}
|
|
260
332
|
async shapeFull(text, desc) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
333
|
+
try {
|
|
334
|
+
const hb = await this.fonts.getHB();
|
|
335
|
+
const buffer = hb.createBuffer();
|
|
336
|
+
try {
|
|
337
|
+
buffer.addText(text);
|
|
338
|
+
buffer.guessSegmentProperties();
|
|
339
|
+
const font = await this.fonts.getFont(desc);
|
|
340
|
+
const face = await this.fonts.getFace(desc);
|
|
341
|
+
const upem = face?.upem || 1e3;
|
|
342
|
+
font.setScale(upem, upem);
|
|
343
|
+
hb.shape(font, buffer);
|
|
344
|
+
const result = buffer.json();
|
|
345
|
+
return result;
|
|
346
|
+
} finally {
|
|
347
|
+
try {
|
|
348
|
+
buffer.destroy();
|
|
349
|
+
} catch (err) {
|
|
350
|
+
console.error(
|
|
351
|
+
`Error destroying HarfBuzz buffer: ${err instanceof Error ? err.message : String(err)}`
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
} catch (err) {
|
|
356
|
+
throw new Error(
|
|
357
|
+
`Failed to shape text with font "${desc.family}": ${err instanceof Error ? err.message : String(err)}`
|
|
358
|
+
);
|
|
359
|
+
}
|
|
273
360
|
}
|
|
274
361
|
async layout(params) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const shaped = await this.shapeFull(input, desc);
|
|
281
|
-
const face = await this.fonts.getFace(desc);
|
|
282
|
-
const upem = face?.upem || 1e3;
|
|
283
|
-
const scale = fontSize / upem;
|
|
284
|
-
const glyphs = shaped.map((g) => {
|
|
285
|
-
const charIndex = g.cl;
|
|
286
|
-
let char;
|
|
287
|
-
if (charIndex >= 0 && charIndex < input.length) {
|
|
288
|
-
char = input[charIndex];
|
|
362
|
+
try {
|
|
363
|
+
const { textTransform, desc, fontSize, letterSpacing, width } = params;
|
|
364
|
+
const input = this.transformText(params.text, textTransform);
|
|
365
|
+
if (!input || input.length === 0) {
|
|
366
|
+
return [];
|
|
289
367
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
cluster: g.cl,
|
|
296
|
-
char
|
|
297
|
-
// This now correctly maps to the original character
|
|
298
|
-
};
|
|
299
|
-
});
|
|
300
|
-
const lines = [];
|
|
301
|
-
let currentLine = [];
|
|
302
|
-
let currentWidth = 0;
|
|
303
|
-
const spaceIndices = /* @__PURE__ */ new Set();
|
|
304
|
-
for (let i = 0; i < input.length; i++) {
|
|
305
|
-
if (input[i] === " ") {
|
|
306
|
-
spaceIndices.add(i);
|
|
368
|
+
let shaped;
|
|
369
|
+
try {
|
|
370
|
+
shaped = await this.shapeFull(input, desc);
|
|
371
|
+
} catch (err) {
|
|
372
|
+
throw new Error(`Text shaping failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
307
373
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
374
|
+
let upem;
|
|
375
|
+
try {
|
|
376
|
+
const face = await this.fonts.getFace(desc);
|
|
377
|
+
upem = face?.upem || 1e3;
|
|
378
|
+
} catch (err) {
|
|
379
|
+
throw new Error(
|
|
380
|
+
`Failed to get font metrics: ${err instanceof Error ? err.message : String(err)}`
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
const scale = fontSize / upem;
|
|
384
|
+
const glyphs = shaped.map((g) => {
|
|
385
|
+
const charIndex = g.cl;
|
|
386
|
+
let char;
|
|
387
|
+
if (charIndex >= 0 && charIndex < input.length) {
|
|
388
|
+
char = input[charIndex];
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
id: g.g,
|
|
392
|
+
xAdvance: g.ax * scale + letterSpacing,
|
|
393
|
+
xOffset: g.dx * scale,
|
|
394
|
+
yOffset: -g.dy * scale,
|
|
395
|
+
cluster: g.cl,
|
|
396
|
+
char
|
|
397
|
+
};
|
|
398
|
+
});
|
|
399
|
+
const lines = [];
|
|
400
|
+
let currentLine = [];
|
|
401
|
+
let currentWidth = 0;
|
|
402
|
+
const spaceIndices = /* @__PURE__ */ new Set();
|
|
403
|
+
for (let i = 0; i < input.length; i++) {
|
|
404
|
+
if (input[i] === " ") {
|
|
405
|
+
spaceIndices.add(i);
|
|
320
406
|
}
|
|
321
|
-
currentLine = [];
|
|
322
|
-
currentWidth = 0;
|
|
323
|
-
lastBreakIndex = i;
|
|
324
|
-
continue;
|
|
325
407
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
} else {
|
|
339
|
-
lines.push({
|
|
340
|
-
glyphs: currentLine,
|
|
341
|
-
width: currentWidth,
|
|
342
|
-
y: 0
|
|
343
|
-
});
|
|
408
|
+
let lastBreakIndex = -1;
|
|
409
|
+
for (let i = 0; i < glyphs.length; i++) {
|
|
410
|
+
const glyph = glyphs[i];
|
|
411
|
+
const glyphWidth = glyph.xAdvance;
|
|
412
|
+
if (glyph.char === "\n") {
|
|
413
|
+
if (currentLine.length > 0) {
|
|
414
|
+
lines.push({
|
|
415
|
+
glyphs: currentLine,
|
|
416
|
+
width: currentWidth,
|
|
417
|
+
y: 0
|
|
418
|
+
});
|
|
419
|
+
}
|
|
344
420
|
currentLine = [];
|
|
345
421
|
currentWidth = 0;
|
|
422
|
+
lastBreakIndex = i;
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (currentWidth + glyphWidth > width && currentLine.length > 0) {
|
|
426
|
+
if (lastBreakIndex > -1) {
|
|
427
|
+
const breakPoint = lastBreakIndex - (i - currentLine.length) + 1;
|
|
428
|
+
const nextLine = currentLine.splice(breakPoint);
|
|
429
|
+
const lineWidth = currentLine.reduce((sum, g) => sum + g.xAdvance, 0);
|
|
430
|
+
lines.push({
|
|
431
|
+
glyphs: currentLine,
|
|
432
|
+
width: lineWidth,
|
|
433
|
+
y: 0
|
|
434
|
+
});
|
|
435
|
+
currentLine = nextLine;
|
|
436
|
+
currentWidth = nextLine.reduce((sum, g) => sum + g.xAdvance, 0);
|
|
437
|
+
} else {
|
|
438
|
+
lines.push({
|
|
439
|
+
glyphs: currentLine,
|
|
440
|
+
width: currentWidth,
|
|
441
|
+
y: 0
|
|
442
|
+
});
|
|
443
|
+
currentLine = [];
|
|
444
|
+
currentWidth = 0;
|
|
445
|
+
}
|
|
446
|
+
lastBreakIndex = -1;
|
|
447
|
+
}
|
|
448
|
+
currentLine.push(glyph);
|
|
449
|
+
currentWidth += glyphWidth;
|
|
450
|
+
if (spaceIndices.has(glyph.cluster)) {
|
|
451
|
+
lastBreakIndex = i;
|
|
346
452
|
}
|
|
347
|
-
lastBreakIndex = -1;
|
|
348
453
|
}
|
|
349
|
-
currentLine.
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
454
|
+
if (currentLine.length > 0) {
|
|
455
|
+
lines.push({
|
|
456
|
+
glyphs: currentLine,
|
|
457
|
+
width: currentWidth,
|
|
458
|
+
y: 0
|
|
459
|
+
});
|
|
353
460
|
}
|
|
461
|
+
const lineHeight = params.lineHeight * fontSize;
|
|
462
|
+
for (let i = 0; i < lines.length; i++) {
|
|
463
|
+
lines[i].y = (i + 1) * lineHeight;
|
|
464
|
+
}
|
|
465
|
+
return lines;
|
|
466
|
+
} catch (err) {
|
|
467
|
+
if (err instanceof Error) {
|
|
468
|
+
throw err;
|
|
469
|
+
}
|
|
470
|
+
throw new Error(`Layout failed: ${String(err)}`);
|
|
354
471
|
}
|
|
355
|
-
if (currentLine.length > 0) {
|
|
356
|
-
lines.push({
|
|
357
|
-
glyphs: currentLine,
|
|
358
|
-
width: currentWidth,
|
|
359
|
-
y: 0
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
const lineHeight = params.lineHeight * fontSize;
|
|
363
|
-
for (let i = 0; i < lines.length; i++) {
|
|
364
|
-
lines[i].y = (i + 1) * lineHeight;
|
|
365
|
-
}
|
|
366
|
-
return lines;
|
|
367
472
|
}
|
|
368
473
|
};
|
|
369
474
|
|
|
@@ -455,7 +560,6 @@ async function buildDrawOps(p) {
|
|
|
455
560
|
path,
|
|
456
561
|
x: glyphX + p.shadow.offsetX,
|
|
457
562
|
y: glyphY + p.shadow.offsetY,
|
|
458
|
-
// @ts-ignore scale propagated to painters
|
|
459
563
|
scale,
|
|
460
564
|
fill: { kind: "solid", color: p.shadow.color, opacity: p.shadow.opacity }
|
|
461
565
|
});
|
|
@@ -466,7 +570,6 @@ async function buildDrawOps(p) {
|
|
|
466
570
|
path,
|
|
467
571
|
x: glyphX,
|
|
468
572
|
y: glyphY,
|
|
469
|
-
// @ts-ignore scale propagated to painters
|
|
470
573
|
scale,
|
|
471
574
|
width: p.stroke.width,
|
|
472
575
|
color: p.stroke.color,
|
|
@@ -478,7 +581,6 @@ async function buildDrawOps(p) {
|
|
|
478
581
|
path,
|
|
479
582
|
x: glyphX,
|
|
480
583
|
y: glyphY,
|
|
481
|
-
// @ts-ignore scale propagated to painters
|
|
482
584
|
scale,
|
|
483
585
|
fill
|
|
484
586
|
});
|
|
@@ -1054,30 +1156,32 @@ async function createNodePainter(opts) {
|
|
|
1054
1156
|
continue;
|
|
1055
1157
|
}
|
|
1056
1158
|
if (op.op === "FillPath") {
|
|
1159
|
+
const fillOp = op;
|
|
1057
1160
|
ctx.save();
|
|
1058
|
-
ctx.translate(
|
|
1059
|
-
const s =
|
|
1161
|
+
ctx.translate(fillOp.x, fillOp.y);
|
|
1162
|
+
const s = fillOp.scale ?? 1;
|
|
1060
1163
|
ctx.scale(s, -s);
|
|
1061
1164
|
ctx.beginPath();
|
|
1062
|
-
drawSvgPathOnCtx(ctx,
|
|
1063
|
-
const bbox =
|
|
1064
|
-
const fill = makeGradientFromBBox(ctx,
|
|
1165
|
+
drawSvgPathOnCtx(ctx, fillOp.path);
|
|
1166
|
+
const bbox = fillOp.gradientBBox ?? globalBox;
|
|
1167
|
+
const fill = makeGradientFromBBox(ctx, fillOp.fill, bbox);
|
|
1065
1168
|
ctx.fillStyle = fill;
|
|
1066
1169
|
ctx.fill();
|
|
1067
1170
|
ctx.restore();
|
|
1068
1171
|
continue;
|
|
1069
1172
|
}
|
|
1070
1173
|
if (op.op === "StrokePath") {
|
|
1174
|
+
const strokeOp = op;
|
|
1071
1175
|
ctx.save();
|
|
1072
|
-
ctx.translate(
|
|
1073
|
-
const s =
|
|
1176
|
+
ctx.translate(strokeOp.x, strokeOp.y);
|
|
1177
|
+
const s = strokeOp.scale ?? 1;
|
|
1074
1178
|
ctx.scale(s, -s);
|
|
1075
1179
|
const invAbs = 1 / Math.abs(s);
|
|
1076
1180
|
ctx.beginPath();
|
|
1077
|
-
drawSvgPathOnCtx(ctx,
|
|
1078
|
-
const c = parseHex6(
|
|
1181
|
+
drawSvgPathOnCtx(ctx, strokeOp.path);
|
|
1182
|
+
const c = parseHex6(strokeOp.color, strokeOp.opacity);
|
|
1079
1183
|
ctx.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1080
|
-
ctx.lineWidth =
|
|
1184
|
+
ctx.lineWidth = strokeOp.width * invAbs;
|
|
1081
1185
|
ctx.lineJoin = "round";
|
|
1082
1186
|
ctx.lineCap = "round";
|
|
1083
1187
|
ctx.stroke();
|
|
@@ -1109,17 +1213,20 @@ function makeGradientFromBBox(ctx, spec, box) {
|
|
|
1109
1213
|
const c = parseHex6(spec.color, spec.opacity);
|
|
1110
1214
|
return `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1111
1215
|
}
|
|
1112
|
-
const cx = box.x + box.w / 2
|
|
1216
|
+
const cx = box.x + box.w / 2;
|
|
1217
|
+
const cy = box.y + box.h / 2;
|
|
1218
|
+
const r = Math.max(box.w, box.h) / 2;
|
|
1113
1219
|
const addStops = (g) => {
|
|
1114
|
-
const
|
|
1115
|
-
|
|
1116
|
-
|
|
1220
|
+
const opacity = spec.kind === "linear" || spec.kind === "radial" ? spec.opacity : 1;
|
|
1221
|
+
const stops = spec.kind === "linear" || spec.kind === "radial" ? spec.stops : [];
|
|
1222
|
+
for (const s of stops) {
|
|
1223
|
+
const c = parseHex6(s.color, opacity);
|
|
1117
1224
|
g.addColorStop(s.offset, `rgba(${c.r},${c.g},${c.b},${c.a})`);
|
|
1118
1225
|
}
|
|
1119
1226
|
return g;
|
|
1120
1227
|
};
|
|
1121
1228
|
if (spec.kind === "linear") {
|
|
1122
|
-
const rad =
|
|
1229
|
+
const rad = spec.angle * Math.PI / 180;
|
|
1123
1230
|
const x1 = cx + Math.cos(rad + Math.PI) * r;
|
|
1124
1231
|
const y1 = cy + Math.sin(rad + Math.PI) * r;
|
|
1125
1232
|
const x2 = cx + Math.cos(rad) * r;
|
|
@@ -1130,22 +1237,34 @@ function makeGradientFromBBox(ctx, spec, box) {
|
|
|
1130
1237
|
}
|
|
1131
1238
|
}
|
|
1132
1239
|
function computeGlobalTextBounds(ops) {
|
|
1133
|
-
let minX = Infinity
|
|
1240
|
+
let minX = Infinity;
|
|
1241
|
+
let minY = Infinity;
|
|
1242
|
+
let maxX = -Infinity;
|
|
1243
|
+
let maxY = -Infinity;
|
|
1134
1244
|
for (const op of ops) {
|
|
1135
|
-
if (op.op !== "FillPath"
|
|
1136
|
-
const
|
|
1137
|
-
|
|
1138
|
-
const
|
|
1139
|
-
const
|
|
1140
|
-
const
|
|
1141
|
-
const
|
|
1245
|
+
if (op.op !== "FillPath") continue;
|
|
1246
|
+
const fillOp = op;
|
|
1247
|
+
if (fillOp.isShadow) continue;
|
|
1248
|
+
const b = computePathBounds2(fillOp.path);
|
|
1249
|
+
const s = fillOp.scale ?? 1;
|
|
1250
|
+
const x1 = fillOp.x + s * b.x;
|
|
1251
|
+
const x2 = fillOp.x + s * (b.x + b.w);
|
|
1252
|
+
const y1 = fillOp.y - s * (b.y + b.h);
|
|
1253
|
+
const y2 = fillOp.y - s * b.y;
|
|
1142
1254
|
if (x1 < minX) minX = x1;
|
|
1143
1255
|
if (y1 < minY) minY = y1;
|
|
1144
1256
|
if (x2 > maxX) maxX = x2;
|
|
1145
1257
|
if (y2 > maxY) maxY = y2;
|
|
1146
1258
|
}
|
|
1147
|
-
if (minX === Infinity)
|
|
1148
|
-
|
|
1259
|
+
if (minX === Infinity) {
|
|
1260
|
+
return { x: 0, y: 0, w: 1, h: 1 };
|
|
1261
|
+
}
|
|
1262
|
+
return {
|
|
1263
|
+
x: minX,
|
|
1264
|
+
y: minY,
|
|
1265
|
+
w: Math.max(1, maxX - minX),
|
|
1266
|
+
h: Math.max(1, maxY - minY)
|
|
1267
|
+
};
|
|
1149
1268
|
}
|
|
1150
1269
|
function drawSvgPathOnCtx(ctx, d) {
|
|
1151
1270
|
const t = tokenizePath2(d);
|
|
@@ -1187,13 +1306,18 @@ function drawSvgPathOnCtx(ctx, d) {
|
|
|
1187
1306
|
ctx.closePath();
|
|
1188
1307
|
break;
|
|
1189
1308
|
}
|
|
1309
|
+
default:
|
|
1310
|
+
break;
|
|
1190
1311
|
}
|
|
1191
1312
|
}
|
|
1192
1313
|
}
|
|
1193
1314
|
function computePathBounds2(d) {
|
|
1194
1315
|
const t = tokenizePath2(d);
|
|
1195
1316
|
let i = 0;
|
|
1196
|
-
let minX = Infinity
|
|
1317
|
+
let minX = Infinity;
|
|
1318
|
+
let minY = Infinity;
|
|
1319
|
+
let maxX = -Infinity;
|
|
1320
|
+
let maxY = -Infinity;
|
|
1197
1321
|
const touch = (x, y) => {
|
|
1198
1322
|
if (x < minX) minX = x;
|
|
1199
1323
|
if (y < minY) minY = y;
|
|
@@ -1233,10 +1357,19 @@ function computePathBounds2(d) {
|
|
|
1233
1357
|
}
|
|
1234
1358
|
case "Z":
|
|
1235
1359
|
break;
|
|
1360
|
+
default:
|
|
1361
|
+
break;
|
|
1236
1362
|
}
|
|
1237
1363
|
}
|
|
1238
|
-
if (minX === Infinity)
|
|
1239
|
-
|
|
1364
|
+
if (minX === Infinity) {
|
|
1365
|
+
return { x: 0, y: 0, w: 0, h: 0 };
|
|
1366
|
+
}
|
|
1367
|
+
return {
|
|
1368
|
+
x: minX,
|
|
1369
|
+
y: minY,
|
|
1370
|
+
w: maxX - minX,
|
|
1371
|
+
h: maxY - minY
|
|
1372
|
+
};
|
|
1240
1373
|
}
|
|
1241
1374
|
function roundRectPath(ctx, x, y, w, h, r) {
|
|
1242
1375
|
ctx.moveTo(x + r, y);
|
|
@@ -1265,20 +1398,54 @@ function bufferToArrayBuffer(buf) {
|
|
|
1265
1398
|
return ab.slice(byteOffset, byteOffset + byteLength);
|
|
1266
1399
|
}
|
|
1267
1400
|
async function loadFileOrHttpToArrayBuffer(pathOrUrl) {
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
const
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1401
|
+
try {
|
|
1402
|
+
if (/^https?:\/\//.test(pathOrUrl)) {
|
|
1403
|
+
const client = pathOrUrl.startsWith("https:") ? https : http;
|
|
1404
|
+
const buf2 = await new Promise((resolve, reject) => {
|
|
1405
|
+
const request = client.get(pathOrUrl, (res) => {
|
|
1406
|
+
const { statusCode } = res;
|
|
1407
|
+
if (statusCode && (statusCode < 200 || statusCode >= 300)) {
|
|
1408
|
+
reject(new Error(`HTTP request failed with status ${statusCode} for ${pathOrUrl}`));
|
|
1409
|
+
res.resume();
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
const chunks = [];
|
|
1413
|
+
res.on("data", (chunk) => {
|
|
1414
|
+
chunks.push(chunk);
|
|
1415
|
+
});
|
|
1416
|
+
res.on("end", () => {
|
|
1417
|
+
try {
|
|
1418
|
+
resolve(Buffer.concat(chunks));
|
|
1419
|
+
} catch (err) {
|
|
1420
|
+
reject(
|
|
1421
|
+
new Error(
|
|
1422
|
+
`Failed to concatenate response chunks: ${err instanceof Error ? err.message : String(err)}`
|
|
1423
|
+
)
|
|
1424
|
+
);
|
|
1425
|
+
}
|
|
1426
|
+
});
|
|
1427
|
+
res.on("error", (err) => {
|
|
1428
|
+
reject(new Error(`Response error for ${pathOrUrl}: ${err.message}`));
|
|
1429
|
+
});
|
|
1430
|
+
});
|
|
1431
|
+
request.on("error", (err) => {
|
|
1432
|
+
reject(new Error(`Request error for ${pathOrUrl}: ${err.message}`));
|
|
1433
|
+
});
|
|
1434
|
+
request.setTimeout(3e4, () => {
|
|
1435
|
+
request.destroy();
|
|
1436
|
+
reject(new Error(`Request timeout after 30s for ${pathOrUrl}`));
|
|
1437
|
+
});
|
|
1438
|
+
});
|
|
1439
|
+
return bufferToArrayBuffer(buf2);
|
|
1440
|
+
}
|
|
1441
|
+
const buf = await readFile(pathOrUrl);
|
|
1442
|
+
return bufferToArrayBuffer(buf);
|
|
1443
|
+
} catch (err) {
|
|
1444
|
+
if (err instanceof Error) {
|
|
1445
|
+
throw new Error(`Failed to load ${pathOrUrl}: ${err.message}`);
|
|
1446
|
+
}
|
|
1447
|
+
throw new Error(`Failed to load ${pathOrUrl}: ${String(err)}`);
|
|
1279
1448
|
}
|
|
1280
|
-
const buf = await readFile(pathOrUrl);
|
|
1281
|
-
return bufferToArrayBuffer(buf);
|
|
1282
1449
|
}
|
|
1283
1450
|
|
|
1284
1451
|
// src/core/video-generator.ts
|
|
@@ -1414,6 +1581,14 @@ var VideoGenerator = class {
|
|
|
1414
1581
|
}
|
|
1415
1582
|
};
|
|
1416
1583
|
|
|
1584
|
+
// src/types.ts
|
|
1585
|
+
var isShadowFill2 = (op) => {
|
|
1586
|
+
return op.op === "FillPath" && op.isShadow === true;
|
|
1587
|
+
};
|
|
1588
|
+
var isGlyphFill2 = (op) => {
|
|
1589
|
+
return op.op === "FillPath" && op.isShadow !== true;
|
|
1590
|
+
};
|
|
1591
|
+
|
|
1417
1592
|
// src/env/entry.node.ts
|
|
1418
1593
|
async function createTextEngine(opts = {}) {
|
|
1419
1594
|
const width = opts.width ?? CANVAS_CONFIG.DEFAULTS.width;
|
|
@@ -1424,125 +1599,213 @@ async function createTextEngine(opts = {}) {
|
|
|
1424
1599
|
const fonts = new FontRegistry(wasmBaseURL);
|
|
1425
1600
|
const layout = new LayoutEngine(fonts);
|
|
1426
1601
|
const videoGenerator = new VideoGenerator();
|
|
1427
|
-
|
|
1602
|
+
try {
|
|
1603
|
+
await fonts.init();
|
|
1604
|
+
} catch (err) {
|
|
1605
|
+
throw new Error(
|
|
1606
|
+
`Failed to initialize font registry: ${err instanceof Error ? err.message : String(err)}`
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1428
1609
|
async function ensureFonts(asset) {
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
const
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1610
|
+
try {
|
|
1611
|
+
if (asset.customFonts) {
|
|
1612
|
+
for (const cf of asset.customFonts) {
|
|
1613
|
+
try {
|
|
1614
|
+
const bytes = await loadFileOrHttpToArrayBuffer(cf.src);
|
|
1615
|
+
await fonts.registerFromBytes(bytes, {
|
|
1616
|
+
family: cf.family,
|
|
1617
|
+
weight: cf.weight ?? "400",
|
|
1618
|
+
style: cf.style ?? "normal"
|
|
1619
|
+
});
|
|
1620
|
+
} catch (err) {
|
|
1621
|
+
throw new Error(
|
|
1622
|
+
`Failed to load custom font "${cf.family}" from ${cf.src}: ${err instanceof Error ? err.message : String(err)}`
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1437
1626
|
}
|
|
1627
|
+
const main = asset.font ?? {
|
|
1628
|
+
family: "Roboto",
|
|
1629
|
+
weight: "400",
|
|
1630
|
+
style: "normal",
|
|
1631
|
+
size: 48,
|
|
1632
|
+
color: "#000000",
|
|
1633
|
+
opacity: 1
|
|
1634
|
+
};
|
|
1635
|
+
return main;
|
|
1636
|
+
} catch (err) {
|
|
1637
|
+
if (err instanceof Error) {
|
|
1638
|
+
throw err;
|
|
1639
|
+
}
|
|
1640
|
+
throw new Error(`Failed to ensure fonts: ${String(err)}`);
|
|
1438
1641
|
}
|
|
1439
|
-
const main = asset.font ?? {
|
|
1440
|
-
family: "Roboto",
|
|
1441
|
-
weight: "400",
|
|
1442
|
-
style: "normal",
|
|
1443
|
-
size: 48,
|
|
1444
|
-
color: "#000000",
|
|
1445
|
-
opacity: 1
|
|
1446
|
-
};
|
|
1447
|
-
return main;
|
|
1448
1642
|
}
|
|
1449
1643
|
return {
|
|
1450
1644
|
validate(input) {
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1645
|
+
try {
|
|
1646
|
+
const { value, error } = RichTextAssetSchema.validate(input, {
|
|
1647
|
+
abortEarly: false,
|
|
1648
|
+
convert: true
|
|
1649
|
+
});
|
|
1650
|
+
if (error) throw error;
|
|
1651
|
+
return { value };
|
|
1652
|
+
} catch (err) {
|
|
1653
|
+
if (err instanceof Error) {
|
|
1654
|
+
throw new Error(`Validation failed: ${err.message}`);
|
|
1655
|
+
}
|
|
1656
|
+
throw new Error(`Validation failed: ${String(err)}`);
|
|
1657
|
+
}
|
|
1457
1658
|
},
|
|
1458
1659
|
async registerFontFromFile(path, desc) {
|
|
1459
|
-
|
|
1460
|
-
|
|
1660
|
+
try {
|
|
1661
|
+
const bytes = await loadFileOrHttpToArrayBuffer(path);
|
|
1662
|
+
await fonts.registerFromBytes(bytes, desc);
|
|
1663
|
+
} catch (err) {
|
|
1664
|
+
throw new Error(
|
|
1665
|
+
`Failed to register font "${desc.family}" from file ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
1666
|
+
);
|
|
1667
|
+
}
|
|
1461
1668
|
},
|
|
1462
1669
|
async registerFontFromUrl(url, desc) {
|
|
1463
|
-
|
|
1464
|
-
|
|
1670
|
+
try {
|
|
1671
|
+
const bytes = await loadFileOrHttpToArrayBuffer(url);
|
|
1672
|
+
await fonts.registerFromBytes(bytes, desc);
|
|
1673
|
+
} catch (err) {
|
|
1674
|
+
throw new Error(
|
|
1675
|
+
`Failed to register font "${desc.family}" from URL ${url}: ${err instanceof Error ? err.message : String(err)}`
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1465
1678
|
},
|
|
1466
1679
|
async renderFrame(asset, tSeconds) {
|
|
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
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1680
|
+
try {
|
|
1681
|
+
const main = await ensureFonts(asset);
|
|
1682
|
+
const desc = { family: main.family, weight: `${main.weight}`, style: main.style };
|
|
1683
|
+
let lines;
|
|
1684
|
+
try {
|
|
1685
|
+
lines = await layout.layout({
|
|
1686
|
+
text: asset.text,
|
|
1687
|
+
width: asset.width ?? width,
|
|
1688
|
+
letterSpacing: asset.style?.letterSpacing ?? 0,
|
|
1689
|
+
fontSize: main.size,
|
|
1690
|
+
lineHeight: asset.style?.lineHeight ?? 1.2,
|
|
1691
|
+
desc,
|
|
1692
|
+
textTransform: asset.style?.textTransform ?? "none"
|
|
1693
|
+
});
|
|
1694
|
+
} catch (err) {
|
|
1695
|
+
throw new Error(
|
|
1696
|
+
`Failed to layout text: ${err instanceof Error ? err.message : String(err)}`
|
|
1697
|
+
);
|
|
1698
|
+
}
|
|
1699
|
+
const textRect = {
|
|
1700
|
+
x: 0,
|
|
1701
|
+
y: 0,
|
|
1702
|
+
width: asset.width ?? width,
|
|
1703
|
+
height: asset.height ?? height
|
|
1704
|
+
};
|
|
1705
|
+
const canvasW = asset.width ?? width;
|
|
1706
|
+
const canvasH = asset.height ?? height;
|
|
1707
|
+
const canvasPR = asset.pixelRatio ?? pixelRatio;
|
|
1708
|
+
let ops0;
|
|
1709
|
+
try {
|
|
1710
|
+
ops0 = await buildDrawOps({
|
|
1711
|
+
canvas: { width: canvasW, height: canvasH, pixelRatio: canvasPR },
|
|
1712
|
+
textRect,
|
|
1713
|
+
lines,
|
|
1714
|
+
font: {
|
|
1715
|
+
family: main.family,
|
|
1716
|
+
size: main.size,
|
|
1717
|
+
weight: `${main.weight}`,
|
|
1718
|
+
style: main.style,
|
|
1719
|
+
color: main.color,
|
|
1720
|
+
opacity: main.opacity
|
|
1721
|
+
},
|
|
1722
|
+
style: {
|
|
1723
|
+
lineHeight: asset.style?.lineHeight ?? 1.2,
|
|
1724
|
+
textDecoration: asset.style?.textDecoration ?? "none",
|
|
1725
|
+
gradient: asset.style?.gradient
|
|
1726
|
+
},
|
|
1727
|
+
stroke: asset.stroke,
|
|
1728
|
+
shadow: asset.shadow,
|
|
1729
|
+
align: asset.align ?? { horizontal: "left", vertical: "middle" },
|
|
1730
|
+
background: asset.background,
|
|
1731
|
+
glyphPathProvider: (gid) => fonts.glyphPath(desc, gid),
|
|
1732
|
+
getUnitsPerEm: () => fonts.getUnitsPerEm(desc)
|
|
1733
|
+
});
|
|
1734
|
+
} catch (err) {
|
|
1735
|
+
throw new Error(
|
|
1736
|
+
`Failed to build draw operations: ${err instanceof Error ? err.message : String(err)}`
|
|
1737
|
+
);
|
|
1738
|
+
}
|
|
1739
|
+
try {
|
|
1740
|
+
const ops = applyAnimation(ops0, lines, {
|
|
1741
|
+
t: tSeconds,
|
|
1742
|
+
fontSize: main.size,
|
|
1743
|
+
anim: asset.animation ? {
|
|
1744
|
+
preset: asset.animation.preset,
|
|
1745
|
+
speed: asset.animation.speed,
|
|
1746
|
+
duration: asset.animation.duration,
|
|
1747
|
+
style: asset.animation.style,
|
|
1748
|
+
direction: asset.animation.direction
|
|
1749
|
+
} : void 0
|
|
1750
|
+
});
|
|
1751
|
+
return ops;
|
|
1752
|
+
} catch (err) {
|
|
1753
|
+
throw new Error(
|
|
1754
|
+
`Failed to apply animation: ${err instanceof Error ? err.message : String(err)}`
|
|
1755
|
+
);
|
|
1756
|
+
}
|
|
1757
|
+
} catch (err) {
|
|
1758
|
+
if (err instanceof Error) {
|
|
1759
|
+
throw err;
|
|
1760
|
+
}
|
|
1761
|
+
throw new Error(`Failed to render frame at time ${tSeconds}s: ${String(err)}`);
|
|
1762
|
+
}
|
|
1518
1763
|
},
|
|
1519
1764
|
async createRenderer(p) {
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1765
|
+
try {
|
|
1766
|
+
return await createNodePainter({
|
|
1767
|
+
width: p.width ?? width,
|
|
1768
|
+
height: p.height ?? height,
|
|
1769
|
+
pixelRatio: p.pixelRatio ?? pixelRatio
|
|
1770
|
+
});
|
|
1771
|
+
} catch (err) {
|
|
1772
|
+
throw new Error(
|
|
1773
|
+
`Failed to create renderer: ${err instanceof Error ? err.message : String(err)}`
|
|
1774
|
+
);
|
|
1775
|
+
}
|
|
1525
1776
|
},
|
|
1526
1777
|
async generateVideo(asset, options) {
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1778
|
+
try {
|
|
1779
|
+
const finalOptions = {
|
|
1780
|
+
width: asset.width ?? width,
|
|
1781
|
+
height: asset.height ?? height,
|
|
1782
|
+
fps,
|
|
1783
|
+
duration: asset.animation?.duration ?? 3,
|
|
1784
|
+
outputPath: options.outputPath ?? "output.mp4",
|
|
1785
|
+
pixelRatio: asset.pixelRatio ?? pixelRatio
|
|
1786
|
+
};
|
|
1787
|
+
const frameGenerator = async (time) => {
|
|
1788
|
+
return this.renderFrame(asset, time);
|
|
1789
|
+
};
|
|
1790
|
+
await videoGenerator.generateVideo(frameGenerator, finalOptions);
|
|
1791
|
+
} catch (err) {
|
|
1792
|
+
throw new Error(
|
|
1793
|
+
`Failed to generate video: ${err instanceof Error ? err.message : String(err)}`
|
|
1794
|
+
);
|
|
1795
|
+
}
|
|
1539
1796
|
},
|
|
1540
1797
|
destroy() {
|
|
1541
|
-
|
|
1798
|
+
try {
|
|
1799
|
+
fonts.destroy();
|
|
1800
|
+
} catch (err) {
|
|
1801
|
+
console.error(`Error during cleanup: ${err instanceof Error ? err.message : String(err)}`);
|
|
1802
|
+
}
|
|
1542
1803
|
}
|
|
1543
1804
|
};
|
|
1544
1805
|
}
|
|
1545
1806
|
export {
|
|
1546
|
-
createTextEngine
|
|
1807
|
+
createTextEngine,
|
|
1808
|
+
isGlyphFill2 as isGlyphFill,
|
|
1809
|
+
isShadowFill2 as isShadowFill
|
|
1547
1810
|
};
|
|
1548
1811
|
//# sourceMappingURL=entry.node.js.map
|