@shotstack/shotstack-canvas 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entry.node.cjs +560 -283
- package/dist/entry.node.cjs.map +1 -1
- package/dist/entry.node.d.cts +20 -1
- package/dist/entry.node.d.ts +20 -1
- package/dist/entry.node.js +557 -282
- package/dist/entry.node.js.map +1 -1
- package/dist/entry.web.d.ts +20 -1
- package/dist/entry.web.js +525 -258
- package/dist/entry.web.js.map +1 -1
- package/package.json +10 -4
package/dist/entry.node.js
CHANGED
|
@@ -128,19 +128,28 @@ 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
|
|
@@ -163,7 +172,13 @@ var FontRegistry = class {
|
|
|
163
172
|
return;
|
|
164
173
|
}
|
|
165
174
|
this.initPromise = this._doInit();
|
|
166
|
-
|
|
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
|
+
}
|
|
167
182
|
}
|
|
168
183
|
async _doInit() {
|
|
169
184
|
try {
|
|
@@ -175,7 +190,13 @@ var FontRegistry = class {
|
|
|
175
190
|
}
|
|
176
191
|
async getHB() {
|
|
177
192
|
if (!this.hb) {
|
|
178
|
-
|
|
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
|
+
}
|
|
179
200
|
}
|
|
180
201
|
return this.hb;
|
|
181
202
|
}
|
|
@@ -183,48 +204,111 @@ var FontRegistry = class {
|
|
|
183
204
|
return `${desc.family}__${desc.weight ?? "400"}__${desc.style ?? "normal"}`;
|
|
184
205
|
}
|
|
185
206
|
async registerFromBytes(bytes, desc) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
+
}
|
|
197
224
|
}
|
|
198
225
|
async getFont(desc) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
+
}
|
|
204
240
|
}
|
|
205
241
|
async getFace(desc) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
+
}
|
|
209
251
|
}
|
|
210
252
|
async getUnitsPerEm(desc) {
|
|
211
|
-
|
|
212
|
-
|
|
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
|
+
}
|
|
213
261
|
}
|
|
214
262
|
async glyphPath(desc, glyphId) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
+
}
|
|
218
272
|
}
|
|
219
273
|
destroy() {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
+
}
|
|
228
312
|
}
|
|
229
313
|
};
|
|
230
314
|
|
|
@@ -246,112 +330,145 @@ var LayoutEngine = class {
|
|
|
246
330
|
}
|
|
247
331
|
}
|
|
248
332
|
async shapeFull(text, desc) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
+
}
|
|
261
360
|
}
|
|
262
361
|
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];
|
|
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 [];
|
|
277
367
|
}
|
|
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);
|
|
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)}`);
|
|
295
373
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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);
|
|
308
406
|
}
|
|
309
|
-
currentLine = [];
|
|
310
|
-
currentWidth = 0;
|
|
311
|
-
lastBreakIndex = i;
|
|
312
|
-
continue;
|
|
313
407
|
}
|
|
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
|
-
});
|
|
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
|
+
}
|
|
332
420
|
currentLine = [];
|
|
333
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;
|
|
334
452
|
}
|
|
335
|
-
lastBreakIndex = -1;
|
|
336
453
|
}
|
|
337
|
-
currentLine.
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
454
|
+
if (currentLine.length > 0) {
|
|
455
|
+
lines.push({
|
|
456
|
+
glyphs: currentLine,
|
|
457
|
+
width: currentWidth,
|
|
458
|
+
y: 0
|
|
459
|
+
});
|
|
341
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)}`);
|
|
342
471
|
}
|
|
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
472
|
}
|
|
356
473
|
};
|
|
357
474
|
|
|
@@ -443,7 +560,6 @@ async function buildDrawOps(p) {
|
|
|
443
560
|
path,
|
|
444
561
|
x: glyphX + p.shadow.offsetX,
|
|
445
562
|
y: glyphY + p.shadow.offsetY,
|
|
446
|
-
// @ts-ignore scale propagated to painters
|
|
447
563
|
scale,
|
|
448
564
|
fill: { kind: "solid", color: p.shadow.color, opacity: p.shadow.opacity }
|
|
449
565
|
});
|
|
@@ -454,7 +570,6 @@ async function buildDrawOps(p) {
|
|
|
454
570
|
path,
|
|
455
571
|
x: glyphX,
|
|
456
572
|
y: glyphY,
|
|
457
|
-
// @ts-ignore scale propagated to painters
|
|
458
573
|
scale,
|
|
459
574
|
width: p.stroke.width,
|
|
460
575
|
color: p.stroke.color,
|
|
@@ -466,7 +581,6 @@ async function buildDrawOps(p) {
|
|
|
466
581
|
path,
|
|
467
582
|
x: glyphX,
|
|
468
583
|
y: glyphY,
|
|
469
|
-
// @ts-ignore scale propagated to painters
|
|
470
584
|
scale,
|
|
471
585
|
fill
|
|
472
586
|
});
|
|
@@ -1042,30 +1156,32 @@ async function createNodePainter(opts) {
|
|
|
1042
1156
|
continue;
|
|
1043
1157
|
}
|
|
1044
1158
|
if (op.op === "FillPath") {
|
|
1159
|
+
const fillOp = op;
|
|
1045
1160
|
ctx.save();
|
|
1046
|
-
ctx.translate(
|
|
1047
|
-
const s =
|
|
1161
|
+
ctx.translate(fillOp.x, fillOp.y);
|
|
1162
|
+
const s = fillOp.scale ?? 1;
|
|
1048
1163
|
ctx.scale(s, -s);
|
|
1049
1164
|
ctx.beginPath();
|
|
1050
|
-
drawSvgPathOnCtx(ctx,
|
|
1051
|
-
const bbox =
|
|
1052
|
-
const fill = makeGradientFromBBox(ctx,
|
|
1165
|
+
drawSvgPathOnCtx(ctx, fillOp.path);
|
|
1166
|
+
const bbox = fillOp.gradientBBox ?? globalBox;
|
|
1167
|
+
const fill = makeGradientFromBBox(ctx, fillOp.fill, bbox);
|
|
1053
1168
|
ctx.fillStyle = fill;
|
|
1054
1169
|
ctx.fill();
|
|
1055
1170
|
ctx.restore();
|
|
1056
1171
|
continue;
|
|
1057
1172
|
}
|
|
1058
1173
|
if (op.op === "StrokePath") {
|
|
1174
|
+
const strokeOp = op;
|
|
1059
1175
|
ctx.save();
|
|
1060
|
-
ctx.translate(
|
|
1061
|
-
const s =
|
|
1176
|
+
ctx.translate(strokeOp.x, strokeOp.y);
|
|
1177
|
+
const s = strokeOp.scale ?? 1;
|
|
1062
1178
|
ctx.scale(s, -s);
|
|
1063
1179
|
const invAbs = 1 / Math.abs(s);
|
|
1064
1180
|
ctx.beginPath();
|
|
1065
|
-
drawSvgPathOnCtx(ctx,
|
|
1066
|
-
const c = parseHex6(
|
|
1181
|
+
drawSvgPathOnCtx(ctx, strokeOp.path);
|
|
1182
|
+
const c = parseHex6(strokeOp.color, strokeOp.opacity);
|
|
1067
1183
|
ctx.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1068
|
-
ctx.lineWidth =
|
|
1184
|
+
ctx.lineWidth = strokeOp.width * invAbs;
|
|
1069
1185
|
ctx.lineJoin = "round";
|
|
1070
1186
|
ctx.lineCap = "round";
|
|
1071
1187
|
ctx.stroke();
|
|
@@ -1097,17 +1213,20 @@ function makeGradientFromBBox(ctx, spec, box) {
|
|
|
1097
1213
|
const c = parseHex6(spec.color, spec.opacity);
|
|
1098
1214
|
return `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1099
1215
|
}
|
|
1100
|
-
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;
|
|
1101
1219
|
const addStops = (g) => {
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1104
|
-
|
|
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);
|
|
1105
1224
|
g.addColorStop(s.offset, `rgba(${c.r},${c.g},${c.b},${c.a})`);
|
|
1106
1225
|
}
|
|
1107
1226
|
return g;
|
|
1108
1227
|
};
|
|
1109
1228
|
if (spec.kind === "linear") {
|
|
1110
|
-
const rad =
|
|
1229
|
+
const rad = spec.angle * Math.PI / 180;
|
|
1111
1230
|
const x1 = cx + Math.cos(rad + Math.PI) * r;
|
|
1112
1231
|
const y1 = cy + Math.sin(rad + Math.PI) * r;
|
|
1113
1232
|
const x2 = cx + Math.cos(rad) * r;
|
|
@@ -1118,22 +1237,34 @@ function makeGradientFromBBox(ctx, spec, box) {
|
|
|
1118
1237
|
}
|
|
1119
1238
|
}
|
|
1120
1239
|
function computeGlobalTextBounds(ops) {
|
|
1121
|
-
let minX = Infinity
|
|
1240
|
+
let minX = Infinity;
|
|
1241
|
+
let minY = Infinity;
|
|
1242
|
+
let maxX = -Infinity;
|
|
1243
|
+
let maxY = -Infinity;
|
|
1122
1244
|
for (const op of ops) {
|
|
1123
|
-
if (op.op !== "FillPath"
|
|
1124
|
-
const
|
|
1125
|
-
|
|
1126
|
-
const
|
|
1127
|
-
const
|
|
1128
|
-
const
|
|
1129
|
-
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;
|
|
1130
1254
|
if (x1 < minX) minX = x1;
|
|
1131
1255
|
if (y1 < minY) minY = y1;
|
|
1132
1256
|
if (x2 > maxX) maxX = x2;
|
|
1133
1257
|
if (y2 > maxY) maxY = y2;
|
|
1134
1258
|
}
|
|
1135
|
-
if (minX === Infinity)
|
|
1136
|
-
|
|
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
|
+
};
|
|
1137
1268
|
}
|
|
1138
1269
|
function drawSvgPathOnCtx(ctx, d) {
|
|
1139
1270
|
const t = tokenizePath2(d);
|
|
@@ -1175,13 +1306,18 @@ function drawSvgPathOnCtx(ctx, d) {
|
|
|
1175
1306
|
ctx.closePath();
|
|
1176
1307
|
break;
|
|
1177
1308
|
}
|
|
1309
|
+
default:
|
|
1310
|
+
break;
|
|
1178
1311
|
}
|
|
1179
1312
|
}
|
|
1180
1313
|
}
|
|
1181
1314
|
function computePathBounds2(d) {
|
|
1182
1315
|
const t = tokenizePath2(d);
|
|
1183
1316
|
let i = 0;
|
|
1184
|
-
let minX = Infinity
|
|
1317
|
+
let minX = Infinity;
|
|
1318
|
+
let minY = Infinity;
|
|
1319
|
+
let maxX = -Infinity;
|
|
1320
|
+
let maxY = -Infinity;
|
|
1185
1321
|
const touch = (x, y) => {
|
|
1186
1322
|
if (x < minX) minX = x;
|
|
1187
1323
|
if (y < minY) minY = y;
|
|
@@ -1221,10 +1357,19 @@ function computePathBounds2(d) {
|
|
|
1221
1357
|
}
|
|
1222
1358
|
case "Z":
|
|
1223
1359
|
break;
|
|
1360
|
+
default:
|
|
1361
|
+
break;
|
|
1224
1362
|
}
|
|
1225
1363
|
}
|
|
1226
|
-
if (minX === Infinity)
|
|
1227
|
-
|
|
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
|
+
};
|
|
1228
1373
|
}
|
|
1229
1374
|
function roundRectPath(ctx, x, y, w, h, r) {
|
|
1230
1375
|
ctx.moveTo(x + r, y);
|
|
@@ -1253,20 +1398,54 @@ function bufferToArrayBuffer(buf) {
|
|
|
1253
1398
|
return ab.slice(byteOffset, byteOffset + byteLength);
|
|
1254
1399
|
}
|
|
1255
1400
|
async function loadFileOrHttpToArrayBuffer(pathOrUrl) {
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
const
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
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)}`);
|
|
1267
1448
|
}
|
|
1268
|
-
const buf = await readFile(pathOrUrl);
|
|
1269
|
-
return bufferToArrayBuffer(buf);
|
|
1270
1449
|
}
|
|
1271
1450
|
|
|
1272
1451
|
// src/core/video-generator.ts
|
|
@@ -1402,6 +1581,14 @@ var VideoGenerator = class {
|
|
|
1402
1581
|
}
|
|
1403
1582
|
};
|
|
1404
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
|
+
|
|
1405
1592
|
// src/env/entry.node.ts
|
|
1406
1593
|
async function createTextEngine(opts = {}) {
|
|
1407
1594
|
const width = opts.width ?? CANVAS_CONFIG.DEFAULTS.width;
|
|
@@ -1412,125 +1599,213 @@ async function createTextEngine(opts = {}) {
|
|
|
1412
1599
|
const fonts = new FontRegistry(wasmBaseURL);
|
|
1413
1600
|
const layout = new LayoutEngine(fonts);
|
|
1414
1601
|
const videoGenerator = new VideoGenerator();
|
|
1415
|
-
|
|
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
|
+
}
|
|
1416
1609
|
async function ensureFonts(asset) {
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
const
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
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
|
+
}
|
|
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;
|
|
1425
1639
|
}
|
|
1640
|
+
throw new Error(`Failed to ensure fonts: ${String(err)}`);
|
|
1426
1641
|
}
|
|
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
1642
|
}
|
|
1437
1643
|
return {
|
|
1438
1644
|
validate(input) {
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
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
|
+
}
|
|
1445
1658
|
},
|
|
1446
1659
|
async registerFontFromFile(path, desc) {
|
|
1447
|
-
|
|
1448
|
-
|
|
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
|
+
}
|
|
1449
1668
|
},
|
|
1450
1669
|
async registerFontFromUrl(url, desc) {
|
|
1451
|
-
|
|
1452
|
-
|
|
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
|
+
}
|
|
1453
1678
|
},
|
|
1454
1679
|
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
|
-
|
|
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
|
+
}
|
|
1506
1763
|
},
|
|
1507
1764
|
async createRenderer(p) {
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
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
|
+
}
|
|
1513
1776
|
},
|
|
1514
1777
|
async generateVideo(asset, options) {
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
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
|
+
}
|
|
1527
1796
|
},
|
|
1528
1797
|
destroy() {
|
|
1529
|
-
|
|
1798
|
+
try {
|
|
1799
|
+
fonts.destroy();
|
|
1800
|
+
} catch (err) {
|
|
1801
|
+
console.error(`Error during cleanup: ${err instanceof Error ? err.message : String(err)}`);
|
|
1802
|
+
}
|
|
1530
1803
|
}
|
|
1531
1804
|
};
|
|
1532
1805
|
}
|
|
1533
1806
|
export {
|
|
1534
|
-
createTextEngine
|
|
1807
|
+
createTextEngine,
|
|
1808
|
+
isGlyphFill2 as isGlyphFill,
|
|
1809
|
+
isShadowFill2 as isShadowFill
|
|
1535
1810
|
};
|
|
1536
1811
|
//# sourceMappingURL=entry.node.js.map
|