@shotstack/shotstack-canvas 1.9.5 → 1.9.6
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/README.md +13 -13
- package/dist/entry.node.cjs +50 -9
- package/dist/entry.node.js +50 -9
- package/dist/entry.web.js +66 -20
- package/package.json +65 -65
- package/scripts/postinstall.js +58 -58
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
# @shotstack/shotstack-canvas
|
|
2
|
-
|
|
3
|
-
One package → identical text shaping/wrapping/animation on Web & Node.
|
|
4
|
-
- HarfBuzz WASM for shaping and glyph outlines (via `harfbuzzjs`).
|
|
5
|
-
- Device-independent draw-ops.
|
|
6
|
-
- Painters: Canvas2D (web) and node-canvas (node).
|
|
7
|
-
- Deterministic time-driven animations.
|
|
8
|
-
|
|
9
|
-
## Install
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
pnpm add @shotstack/shotstack-canvas
|
|
13
|
-
# or npm i / yarn add
|
|
1
|
+
# @shotstack/shotstack-canvas
|
|
2
|
+
|
|
3
|
+
One package → identical text shaping/wrapping/animation on Web & Node.
|
|
4
|
+
- HarfBuzz WASM for shaping and glyph outlines (via `harfbuzzjs`).
|
|
5
|
+
- Device-independent draw-ops.
|
|
6
|
+
- Painters: Canvas2D (web) and node-canvas (node).
|
|
7
|
+
- Deterministic time-driven animations.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @shotstack/shotstack-canvas
|
|
13
|
+
# or npm i / yarn add
|
package/dist/entry.node.cjs
CHANGED
|
@@ -608,6 +608,9 @@ var FontRegistry = class _FontRegistry {
|
|
|
608
608
|
const normalizedWeight = normalizeWeight(desc.weight);
|
|
609
609
|
return `${desc.family}__${normalizedWeight}`;
|
|
610
610
|
}
|
|
611
|
+
hasRegisteredFace(desc) {
|
|
612
|
+
return this.faces.has(this.key(desc));
|
|
613
|
+
}
|
|
611
614
|
async registerFromBytes(bytes, desc) {
|
|
612
615
|
try {
|
|
613
616
|
if (!this.hb) await this.init();
|
|
@@ -3705,16 +3708,56 @@ async function createTextEngine(opts = {}) {
|
|
|
3705
3708
|
}
|
|
3706
3709
|
const layout = new LayoutEngine(fonts);
|
|
3707
3710
|
const videoGenerator = new VideoGenerator();
|
|
3711
|
+
const customFontLoadCache = /* @__PURE__ */ new Map();
|
|
3712
|
+
const directRegistrationCache = /* @__PURE__ */ new Map();
|
|
3713
|
+
const normalizeWeight2 = (weight) => `${weight ?? "400"}`;
|
|
3714
|
+
const fontDescKey = (family, weight) => `${family}__${normalizeWeight2(weight)}`;
|
|
3715
|
+
const registerFontFromSource = async (source, desc) => {
|
|
3716
|
+
const normalizedDesc = {
|
|
3717
|
+
family: desc.family,
|
|
3718
|
+
weight: normalizeWeight2(desc.weight)
|
|
3719
|
+
};
|
|
3720
|
+
const cacheKey = `${source}|${fontDescKey(normalizedDesc.family, normalizedDesc.weight)}`;
|
|
3721
|
+
const cached = directRegistrationCache.get(cacheKey);
|
|
3722
|
+
if (cached) {
|
|
3723
|
+
return cached;
|
|
3724
|
+
}
|
|
3725
|
+
const registrationTask = (async () => {
|
|
3726
|
+
if (fonts.hasRegisteredFace(normalizedDesc)) {
|
|
3727
|
+
return;
|
|
3728
|
+
}
|
|
3729
|
+
const bytes = await loadFileOrHttpToArrayBuffer(source);
|
|
3730
|
+
await fonts.registerFromBytes(bytes, normalizedDesc);
|
|
3731
|
+
})();
|
|
3732
|
+
directRegistrationCache.set(cacheKey, registrationTask);
|
|
3733
|
+
return registrationTask;
|
|
3734
|
+
};
|
|
3735
|
+
const ensureCustomFontLoaded = async (customFont) => {
|
|
3736
|
+
const normalizedDesc = {
|
|
3737
|
+
family: customFont.family,
|
|
3738
|
+
weight: normalizeWeight2(customFont.weight)
|
|
3739
|
+
};
|
|
3740
|
+
const cacheKey = `${customFont.src}|${fontDescKey(normalizedDesc.family, normalizedDesc.weight)}`;
|
|
3741
|
+
const cached = customFontLoadCache.get(cacheKey);
|
|
3742
|
+
if (cached) {
|
|
3743
|
+
return cached;
|
|
3744
|
+
}
|
|
3745
|
+
const loadTask = (async () => {
|
|
3746
|
+
if (fonts.hasRegisteredFace(normalizedDesc)) {
|
|
3747
|
+
return;
|
|
3748
|
+
}
|
|
3749
|
+
const bytes = await loadFileOrHttpToArrayBuffer(customFont.src);
|
|
3750
|
+
await fonts.registerFromBytes(bytes, normalizedDesc);
|
|
3751
|
+
})();
|
|
3752
|
+
customFontLoadCache.set(cacheKey, loadTask);
|
|
3753
|
+
return loadTask;
|
|
3754
|
+
};
|
|
3708
3755
|
async function ensureFonts(asset) {
|
|
3709
3756
|
try {
|
|
3710
3757
|
if (asset.customFonts) {
|
|
3711
3758
|
for (const cf of asset.customFonts) {
|
|
3712
3759
|
try {
|
|
3713
|
-
|
|
3714
|
-
await fonts.registerFromBytes(bytes, {
|
|
3715
|
-
family: cf.family,
|
|
3716
|
-
weight: cf.weight ?? "400"
|
|
3717
|
-
});
|
|
3760
|
+
await ensureCustomFontLoaded(cf);
|
|
3718
3761
|
if (fonts.isColorEmojiFont(cf.family)) {
|
|
3719
3762
|
const emojiBytes = fonts.getColorEmojiFontBytes(cf.family);
|
|
3720
3763
|
if (emojiBytes) {
|
|
@@ -3754,8 +3797,7 @@ async function createTextEngine(opts = {}) {
|
|
|
3754
3797
|
},
|
|
3755
3798
|
async registerFontFromFile(path, desc) {
|
|
3756
3799
|
try {
|
|
3757
|
-
|
|
3758
|
-
await fonts.registerFromBytes(bytes, desc);
|
|
3800
|
+
await registerFontFromSource(path, desc);
|
|
3759
3801
|
} catch (err) {
|
|
3760
3802
|
throw new Error(
|
|
3761
3803
|
`Failed to register font "${desc.family}" from file ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -3764,8 +3806,7 @@ async function createTextEngine(opts = {}) {
|
|
|
3764
3806
|
},
|
|
3765
3807
|
async registerFontFromUrl(url, desc) {
|
|
3766
3808
|
try {
|
|
3767
|
-
|
|
3768
|
-
await fonts.registerFromBytes(bytes, desc);
|
|
3809
|
+
await registerFontFromSource(url, desc);
|
|
3769
3810
|
} catch (err) {
|
|
3770
3811
|
throw new Error(
|
|
3771
3812
|
`Failed to register font "${desc.family}" from URL ${url}: ${err instanceof Error ? err.message : String(err)}`
|
package/dist/entry.node.js
CHANGED
|
@@ -564,6 +564,9 @@ var FontRegistry = class _FontRegistry {
|
|
|
564
564
|
const normalizedWeight = normalizeWeight(desc.weight);
|
|
565
565
|
return `${desc.family}__${normalizedWeight}`;
|
|
566
566
|
}
|
|
567
|
+
hasRegisteredFace(desc) {
|
|
568
|
+
return this.faces.has(this.key(desc));
|
|
569
|
+
}
|
|
567
570
|
async registerFromBytes(bytes, desc) {
|
|
568
571
|
try {
|
|
569
572
|
if (!this.hb) await this.init();
|
|
@@ -3661,16 +3664,56 @@ async function createTextEngine(opts = {}) {
|
|
|
3661
3664
|
}
|
|
3662
3665
|
const layout = new LayoutEngine(fonts);
|
|
3663
3666
|
const videoGenerator = new VideoGenerator();
|
|
3667
|
+
const customFontLoadCache = /* @__PURE__ */ new Map();
|
|
3668
|
+
const directRegistrationCache = /* @__PURE__ */ new Map();
|
|
3669
|
+
const normalizeWeight2 = (weight) => `${weight ?? "400"}`;
|
|
3670
|
+
const fontDescKey = (family, weight) => `${family}__${normalizeWeight2(weight)}`;
|
|
3671
|
+
const registerFontFromSource = async (source, desc) => {
|
|
3672
|
+
const normalizedDesc = {
|
|
3673
|
+
family: desc.family,
|
|
3674
|
+
weight: normalizeWeight2(desc.weight)
|
|
3675
|
+
};
|
|
3676
|
+
const cacheKey = `${source}|${fontDescKey(normalizedDesc.family, normalizedDesc.weight)}`;
|
|
3677
|
+
const cached = directRegistrationCache.get(cacheKey);
|
|
3678
|
+
if (cached) {
|
|
3679
|
+
return cached;
|
|
3680
|
+
}
|
|
3681
|
+
const registrationTask = (async () => {
|
|
3682
|
+
if (fonts.hasRegisteredFace(normalizedDesc)) {
|
|
3683
|
+
return;
|
|
3684
|
+
}
|
|
3685
|
+
const bytes = await loadFileOrHttpToArrayBuffer(source);
|
|
3686
|
+
await fonts.registerFromBytes(bytes, normalizedDesc);
|
|
3687
|
+
})();
|
|
3688
|
+
directRegistrationCache.set(cacheKey, registrationTask);
|
|
3689
|
+
return registrationTask;
|
|
3690
|
+
};
|
|
3691
|
+
const ensureCustomFontLoaded = async (customFont) => {
|
|
3692
|
+
const normalizedDesc = {
|
|
3693
|
+
family: customFont.family,
|
|
3694
|
+
weight: normalizeWeight2(customFont.weight)
|
|
3695
|
+
};
|
|
3696
|
+
const cacheKey = `${customFont.src}|${fontDescKey(normalizedDesc.family, normalizedDesc.weight)}`;
|
|
3697
|
+
const cached = customFontLoadCache.get(cacheKey);
|
|
3698
|
+
if (cached) {
|
|
3699
|
+
return cached;
|
|
3700
|
+
}
|
|
3701
|
+
const loadTask = (async () => {
|
|
3702
|
+
if (fonts.hasRegisteredFace(normalizedDesc)) {
|
|
3703
|
+
return;
|
|
3704
|
+
}
|
|
3705
|
+
const bytes = await loadFileOrHttpToArrayBuffer(customFont.src);
|
|
3706
|
+
await fonts.registerFromBytes(bytes, normalizedDesc);
|
|
3707
|
+
})();
|
|
3708
|
+
customFontLoadCache.set(cacheKey, loadTask);
|
|
3709
|
+
return loadTask;
|
|
3710
|
+
};
|
|
3664
3711
|
async function ensureFonts(asset) {
|
|
3665
3712
|
try {
|
|
3666
3713
|
if (asset.customFonts) {
|
|
3667
3714
|
for (const cf of asset.customFonts) {
|
|
3668
3715
|
try {
|
|
3669
|
-
|
|
3670
|
-
await fonts.registerFromBytes(bytes, {
|
|
3671
|
-
family: cf.family,
|
|
3672
|
-
weight: cf.weight ?? "400"
|
|
3673
|
-
});
|
|
3716
|
+
await ensureCustomFontLoaded(cf);
|
|
3674
3717
|
if (fonts.isColorEmojiFont(cf.family)) {
|
|
3675
3718
|
const emojiBytes = fonts.getColorEmojiFontBytes(cf.family);
|
|
3676
3719
|
if (emojiBytes) {
|
|
@@ -3710,8 +3753,7 @@ async function createTextEngine(opts = {}) {
|
|
|
3710
3753
|
},
|
|
3711
3754
|
async registerFontFromFile(path, desc) {
|
|
3712
3755
|
try {
|
|
3713
|
-
|
|
3714
|
-
await fonts.registerFromBytes(bytes, desc);
|
|
3756
|
+
await registerFontFromSource(path, desc);
|
|
3715
3757
|
} catch (err) {
|
|
3716
3758
|
throw new Error(
|
|
3717
3759
|
`Failed to register font "${desc.family}" from file ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -3720,8 +3762,7 @@ async function createTextEngine(opts = {}) {
|
|
|
3720
3762
|
},
|
|
3721
3763
|
async registerFontFromUrl(url, desc) {
|
|
3722
3764
|
try {
|
|
3723
|
-
|
|
3724
|
-
await fonts.registerFromBytes(bytes, desc);
|
|
3765
|
+
await registerFontFromSource(url, desc);
|
|
3725
3766
|
} catch (err) {
|
|
3726
3767
|
throw new Error(
|
|
3727
3768
|
`Failed to register font "${desc.family}" from URL ${url}: ${err instanceof Error ? err.message : String(err)}`
|
package/dist/entry.web.js
CHANGED
|
@@ -564,6 +564,9 @@ var _FontRegistry = class _FontRegistry {
|
|
|
564
564
|
const normalizedWeight = normalizeWeight(desc.weight);
|
|
565
565
|
return `${desc.family}__${normalizedWeight}`;
|
|
566
566
|
}
|
|
567
|
+
hasRegisteredFace(desc) {
|
|
568
|
+
return this.faces.has(this.key(desc));
|
|
569
|
+
}
|
|
567
570
|
async registerFromBytes(bytes, desc) {
|
|
568
571
|
try {
|
|
569
572
|
if (!this.hb) await this.init();
|
|
@@ -3380,16 +3383,56 @@ async function createTextEngine(opts = {}) {
|
|
|
3380
3383
|
);
|
|
3381
3384
|
}
|
|
3382
3385
|
const layout = new LayoutEngine(fonts);
|
|
3386
|
+
const customFontLoadCache = /* @__PURE__ */ new Map();
|
|
3387
|
+
const directRegistrationCache = /* @__PURE__ */ new Map();
|
|
3388
|
+
const normalizeWeight2 = (weight) => `${weight ?? "400"}`;
|
|
3389
|
+
const fontDescKey = (family, weight) => `${family}__${normalizeWeight2(weight)}`;
|
|
3390
|
+
const registerFontFromSource = async (source, desc) => {
|
|
3391
|
+
const normalizedDesc = {
|
|
3392
|
+
family: desc.family,
|
|
3393
|
+
weight: normalizeWeight2(desc.weight)
|
|
3394
|
+
};
|
|
3395
|
+
const cacheKey = `${source}|${fontDescKey(normalizedDesc.family, normalizedDesc.weight)}`;
|
|
3396
|
+
const cached = directRegistrationCache.get(cacheKey);
|
|
3397
|
+
if (cached) {
|
|
3398
|
+
return cached;
|
|
3399
|
+
}
|
|
3400
|
+
const registrationTask = (async () => {
|
|
3401
|
+
if (fonts.hasRegisteredFace(normalizedDesc)) {
|
|
3402
|
+
return;
|
|
3403
|
+
}
|
|
3404
|
+
const bytes = await fetchToArrayBuffer(source);
|
|
3405
|
+
await fonts.registerFromBytes(bytes, normalizedDesc);
|
|
3406
|
+
})();
|
|
3407
|
+
directRegistrationCache.set(cacheKey, registrationTask);
|
|
3408
|
+
return registrationTask;
|
|
3409
|
+
};
|
|
3410
|
+
const ensureCustomFontLoaded = async (customFont) => {
|
|
3411
|
+
const normalizedDesc = {
|
|
3412
|
+
family: customFont.family,
|
|
3413
|
+
weight: normalizeWeight2(customFont.weight)
|
|
3414
|
+
};
|
|
3415
|
+
const cacheKey = `${customFont.src}|${fontDescKey(normalizedDesc.family, normalizedDesc.weight)}`;
|
|
3416
|
+
const cached = customFontLoadCache.get(cacheKey);
|
|
3417
|
+
if (cached) {
|
|
3418
|
+
return cached;
|
|
3419
|
+
}
|
|
3420
|
+
const loadTask = (async () => {
|
|
3421
|
+
if (fonts.hasRegisteredFace(normalizedDesc)) {
|
|
3422
|
+
return;
|
|
3423
|
+
}
|
|
3424
|
+
const bytes = await fetchToArrayBuffer(customFont.src);
|
|
3425
|
+
await fonts.registerFromBytes(bytes, normalizedDesc);
|
|
3426
|
+
})();
|
|
3427
|
+
customFontLoadCache.set(cacheKey, loadTask);
|
|
3428
|
+
return loadTask;
|
|
3429
|
+
};
|
|
3383
3430
|
async function ensureFonts(asset) {
|
|
3384
3431
|
try {
|
|
3385
3432
|
if (asset.customFonts) {
|
|
3386
3433
|
for (const cf of asset.customFonts) {
|
|
3387
3434
|
try {
|
|
3388
|
-
|
|
3389
|
-
await fonts.registerFromBytes(bytes, {
|
|
3390
|
-
family: cf.family,
|
|
3391
|
-
weight: cf.weight ?? "400"
|
|
3392
|
-
});
|
|
3435
|
+
await ensureCustomFontLoaded(cf);
|
|
3393
3436
|
} catch (err) {
|
|
3394
3437
|
throw new Error(
|
|
3395
3438
|
`Failed to load custom font "${cf.family}" from ${cf.src}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -3411,11 +3454,13 @@ async function createTextEngine(opts = {}) {
|
|
|
3411
3454
|
} catch {
|
|
3412
3455
|
const wantsDefaultRoboto = (main.family || "Roboto").toLowerCase() === "roboto" && `${main.weight}` === "400";
|
|
3413
3456
|
if (wantsDefaultRoboto) {
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3457
|
+
if (!fonts.hasRegisteredFace({ family: "Roboto", weight: "400" })) {
|
|
3458
|
+
const bytes = await fetchToArrayBuffer(DEFAULT_ROBOTO_URL);
|
|
3459
|
+
await fonts.registerFromBytes(bytes, {
|
|
3460
|
+
family: "Roboto",
|
|
3461
|
+
weight: "400"
|
|
3462
|
+
});
|
|
3463
|
+
}
|
|
3419
3464
|
} else {
|
|
3420
3465
|
throw new Error(`Font not registered for ${desc.family}__${desc.weight}`);
|
|
3421
3466
|
}
|
|
@@ -3439,8 +3484,7 @@ async function createTextEngine(opts = {}) {
|
|
|
3439
3484
|
},
|
|
3440
3485
|
async registerFontFromUrl(url, desc) {
|
|
3441
3486
|
try {
|
|
3442
|
-
|
|
3443
|
-
await fonts.registerFromBytes(bytes, desc);
|
|
3487
|
+
await registerFontFromSource(url, desc);
|
|
3444
3488
|
} catch (err) {
|
|
3445
3489
|
throw new Error(
|
|
3446
3490
|
`Failed to register font "${desc.family}" from URL ${url}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -3451,13 +3495,8 @@ async function createTextEngine(opts = {}) {
|
|
|
3451
3495
|
try {
|
|
3452
3496
|
let bytes;
|
|
3453
3497
|
if (typeof source === "string") {
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
} catch (err) {
|
|
3457
|
-
throw new Error(
|
|
3458
|
-
`Failed to fetch font from ${source}: ${err instanceof Error ? err.message : String(err)}`
|
|
3459
|
-
);
|
|
3460
|
-
}
|
|
3498
|
+
await registerFontFromSource(source, desc);
|
|
3499
|
+
return;
|
|
3461
3500
|
} else {
|
|
3462
3501
|
try {
|
|
3463
3502
|
bytes = await source.arrayBuffer();
|
|
@@ -3467,7 +3506,14 @@ async function createTextEngine(opts = {}) {
|
|
|
3467
3506
|
);
|
|
3468
3507
|
}
|
|
3469
3508
|
}
|
|
3470
|
-
|
|
3509
|
+
const normalizedDesc = {
|
|
3510
|
+
family: desc.family,
|
|
3511
|
+
weight: normalizeWeight2(desc.weight)
|
|
3512
|
+
};
|
|
3513
|
+
if (fonts.hasRegisteredFace(normalizedDesc)) {
|
|
3514
|
+
return;
|
|
3515
|
+
}
|
|
3516
|
+
await fonts.registerFromBytes(bytes, normalizedDesc);
|
|
3471
3517
|
} catch (err) {
|
|
3472
3518
|
if (err instanceof Error) {
|
|
3473
3519
|
throw err;
|
package/package.json
CHANGED
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@shotstack/shotstack-canvas",
|
|
3
|
-
"version": "1.9.
|
|
4
|
-
"description": "Text layout & animation engine (HarfBuzz) for Node & Web - fully self-contained.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/entry.node.cjs",
|
|
7
|
-
"module": "./dist/entry.node.js",
|
|
8
|
-
"browser": "./dist/entry.web.js",
|
|
9
|
-
"types": "./dist/entry.node.d.ts",
|
|
10
|
-
"exports": {
|
|
11
|
-
".": {
|
|
12
|
-
"node": {
|
|
13
|
-
"import": "./dist/entry.node.js",
|
|
14
|
-
"require": "./dist/entry.node.cjs"
|
|
15
|
-
},
|
|
16
|
-
"browser": "./dist/entry.web.js",
|
|
17
|
-
"default": "./dist/entry.web.js"
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"files": [
|
|
21
|
-
"dist/**",
|
|
22
|
-
"scripts/postinstall.js",
|
|
23
|
-
"README.md",
|
|
24
|
-
"LICENSE"
|
|
25
|
-
],
|
|
26
|
-
"scripts": {
|
|
27
|
-
"dev": "tsup --watch",
|
|
28
|
-
"build": "tsup",
|
|
29
|
-
"postinstall": "node scripts/postinstall.js",
|
|
30
|
-
"vendor:harfbuzz": "node scripts/vendor-harfbuzz.js",
|
|
31
|
-
"example:node": "node examples/node-example.mjs",
|
|
32
|
-
"example:video": "node examples/node-video.mjs",
|
|
33
|
-
"example:web": "vite dev examples/web-example",
|
|
34
|
-
"prepublishOnly": "npm run build"
|
|
35
|
-
},
|
|
36
|
-
"publishConfig": {
|
|
37
|
-
"access": "public",
|
|
38
|
-
"registry": "https://registry.npmjs.org/"
|
|
39
|
-
},
|
|
40
|
-
"engines": {
|
|
41
|
-
"node": ">=18"
|
|
42
|
-
},
|
|
43
|
-
"sideEffects": false,
|
|
44
|
-
"dependencies": {
|
|
45
|
-
"@resvg/resvg-js": "^2.6.2",
|
|
46
|
-
"@resvg/resvg-wasm": "^2.6.2",
|
|
47
|
-
"@shotstack/schemas": "^1.5.4",
|
|
48
|
-
"canvas": "npm:@napi-rs/canvas@^0.1.54",
|
|
49
|
-
"ffmpeg-static": "^5.2.0",
|
|
50
|
-
"fontkit": "^2.0.4",
|
|
51
|
-
"harfbuzzjs": "0.4.12",
|
|
52
|
-
"opentype.js": "^1.3.4",
|
|
53
|
-
"zod": "^4.2.0"
|
|
54
|
-
},
|
|
55
|
-
"devDependencies": {
|
|
56
|
-
"@types/fluent-ffmpeg": "2.1.27",
|
|
57
|
-
"@types/node": "^20.14.10",
|
|
58
|
-
"fluent-ffmpeg": "^2.1.3",
|
|
59
|
-
"tsup": "^8.2.3",
|
|
60
|
-
"typescript": "^5.5.3",
|
|
61
|
-
"vite": "^5.3.3",
|
|
62
|
-
"vite-plugin-top-level-await": "1.6.0",
|
|
63
|
-
"vite-plugin-wasm": "3.5.0"
|
|
64
|
-
}
|
|
65
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@shotstack/shotstack-canvas",
|
|
3
|
+
"version": "1.9.6",
|
|
4
|
+
"description": "Text layout & animation engine (HarfBuzz) for Node & Web - fully self-contained.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/entry.node.cjs",
|
|
7
|
+
"module": "./dist/entry.node.js",
|
|
8
|
+
"browser": "./dist/entry.web.js",
|
|
9
|
+
"types": "./dist/entry.node.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"node": {
|
|
13
|
+
"import": "./dist/entry.node.js",
|
|
14
|
+
"require": "./dist/entry.node.cjs"
|
|
15
|
+
},
|
|
16
|
+
"browser": "./dist/entry.web.js",
|
|
17
|
+
"default": "./dist/entry.web.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist/**",
|
|
22
|
+
"scripts/postinstall.js",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"dev": "tsup --watch",
|
|
28
|
+
"build": "tsup",
|
|
29
|
+
"postinstall": "node scripts/postinstall.js",
|
|
30
|
+
"vendor:harfbuzz": "node scripts/vendor-harfbuzz.js",
|
|
31
|
+
"example:node": "node examples/node-example.mjs",
|
|
32
|
+
"example:video": "node examples/node-video.mjs",
|
|
33
|
+
"example:web": "vite dev examples/web-example",
|
|
34
|
+
"prepublishOnly": "npm run build"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public",
|
|
38
|
+
"registry": "https://registry.npmjs.org/"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18"
|
|
42
|
+
},
|
|
43
|
+
"sideEffects": false,
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@resvg/resvg-js": "^2.6.2",
|
|
46
|
+
"@resvg/resvg-wasm": "^2.6.2",
|
|
47
|
+
"@shotstack/schemas": "^1.5.4",
|
|
48
|
+
"canvas": "npm:@napi-rs/canvas@^0.1.54",
|
|
49
|
+
"ffmpeg-static": "^5.2.0",
|
|
50
|
+
"fontkit": "^2.0.4",
|
|
51
|
+
"harfbuzzjs": "0.4.12",
|
|
52
|
+
"opentype.js": "^1.3.4",
|
|
53
|
+
"zod": "^4.2.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/fluent-ffmpeg": "2.1.27",
|
|
57
|
+
"@types/node": "^20.14.10",
|
|
58
|
+
"fluent-ffmpeg": "^2.1.3",
|
|
59
|
+
"tsup": "^8.2.3",
|
|
60
|
+
"typescript": "^5.5.3",
|
|
61
|
+
"vite": "^5.3.3",
|
|
62
|
+
"vite-plugin-top-level-await": "1.6.0",
|
|
63
|
+
"vite-plugin-wasm": "3.5.0"
|
|
64
|
+
}
|
|
65
|
+
}
|
package/scripts/postinstall.js
CHANGED
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Postinstall script to verify native canvas bindings are available
|
|
5
|
-
* This helps catch issues early and provides helpful guidance
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { platform as _platform, arch as _arch } from 'os';
|
|
9
|
-
import { dirname } from 'path';
|
|
10
|
-
import { readdirSync } from 'fs';
|
|
11
|
-
import { createRequire } from 'module';
|
|
12
|
-
|
|
13
|
-
const require = createRequire(import.meta.url);
|
|
14
|
-
|
|
15
|
-
const platform = _platform();
|
|
16
|
-
const arch = _arch();
|
|
17
|
-
|
|
18
|
-
// Map platform/arch to package names
|
|
19
|
-
const platformMap = {
|
|
20
|
-
'darwin-arm64': '@napi-rs/canvas-darwin-arm64',
|
|
21
|
-
'darwin-x64': '@napi-rs/canvas-darwin-x64',
|
|
22
|
-
'linux-arm64': '@napi-rs/canvas-linux-arm64-gnu',
|
|
23
|
-
'linux-x64': '@napi-rs/canvas-linux-x64-gnu',
|
|
24
|
-
'win32-x64': '@napi-rs/canvas-win32-x64-msvc',
|
|
25
|
-
'linux-arm': '@napi-rs/canvas-linux-arm-gnueabihf',
|
|
26
|
-
'android-arm64': '@napi-rs/canvas-android-arm64',
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const platformKey = `${platform}-${arch}`;
|
|
30
|
-
const requiredPackage = platformMap[platformKey];
|
|
31
|
-
|
|
32
|
-
if (!requiredPackage) {
|
|
33
|
-
console.warn(`\n⚠️ Warning: Unsupported platform ${platformKey} for @napi-rs/canvas`);
|
|
34
|
-
console.warn(' Canvas rendering may not work on this platform.\n');
|
|
35
|
-
process.exit(0);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Check if the native binding package is installed
|
|
39
|
-
try {
|
|
40
|
-
const packagePath = require.resolve(`${requiredPackage}/package.json`);
|
|
41
|
-
const packageDir = dirname(packagePath);
|
|
42
|
-
|
|
43
|
-
// Verify the .node file exists
|
|
44
|
-
const nodeFiles = readdirSync(packageDir).filter(f => f.endsWith('.node'));
|
|
45
|
-
|
|
46
|
-
if (nodeFiles.length > 0) {
|
|
47
|
-
console.log(`✅ @shotstack/shotstack-canvas: Native canvas binding found for ${platformKey}`);
|
|
48
|
-
} else {
|
|
49
|
-
throw new Error('No .node file found');
|
|
50
|
-
}
|
|
51
|
-
} catch (error) {
|
|
52
|
-
console.warn(`\n⚠️ Warning: Native canvas binding not found for ${platformKey}`);
|
|
53
|
-
console.warn(` Expected package: ${requiredPackage}`);
|
|
54
|
-
console.warn('\n If you see "Cannot find native binding" errors, try:');
|
|
55
|
-
console.warn(' 1. Delete node_modules and package-lock.json');
|
|
56
|
-
console.warn(' 2. Run: npm install');
|
|
57
|
-
console.warn(` 3. Or manually install: npm install ${requiredPackage}\n`);
|
|
58
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Postinstall script to verify native canvas bindings are available
|
|
5
|
+
* This helps catch issues early and provides helpful guidance
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { platform as _platform, arch as _arch } from 'os';
|
|
9
|
+
import { dirname } from 'path';
|
|
10
|
+
import { readdirSync } from 'fs';
|
|
11
|
+
import { createRequire } from 'module';
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
|
|
15
|
+
const platform = _platform();
|
|
16
|
+
const arch = _arch();
|
|
17
|
+
|
|
18
|
+
// Map platform/arch to package names
|
|
19
|
+
const platformMap = {
|
|
20
|
+
'darwin-arm64': '@napi-rs/canvas-darwin-arm64',
|
|
21
|
+
'darwin-x64': '@napi-rs/canvas-darwin-x64',
|
|
22
|
+
'linux-arm64': '@napi-rs/canvas-linux-arm64-gnu',
|
|
23
|
+
'linux-x64': '@napi-rs/canvas-linux-x64-gnu',
|
|
24
|
+
'win32-x64': '@napi-rs/canvas-win32-x64-msvc',
|
|
25
|
+
'linux-arm': '@napi-rs/canvas-linux-arm-gnueabihf',
|
|
26
|
+
'android-arm64': '@napi-rs/canvas-android-arm64',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const platformKey = `${platform}-${arch}`;
|
|
30
|
+
const requiredPackage = platformMap[platformKey];
|
|
31
|
+
|
|
32
|
+
if (!requiredPackage) {
|
|
33
|
+
console.warn(`\n⚠️ Warning: Unsupported platform ${platformKey} for @napi-rs/canvas`);
|
|
34
|
+
console.warn(' Canvas rendering may not work on this platform.\n');
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check if the native binding package is installed
|
|
39
|
+
try {
|
|
40
|
+
const packagePath = require.resolve(`${requiredPackage}/package.json`);
|
|
41
|
+
const packageDir = dirname(packagePath);
|
|
42
|
+
|
|
43
|
+
// Verify the .node file exists
|
|
44
|
+
const nodeFiles = readdirSync(packageDir).filter(f => f.endsWith('.node'));
|
|
45
|
+
|
|
46
|
+
if (nodeFiles.length > 0) {
|
|
47
|
+
console.log(`✅ @shotstack/shotstack-canvas: Native canvas binding found for ${platformKey}`);
|
|
48
|
+
} else {
|
|
49
|
+
throw new Error('No .node file found');
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.warn(`\n⚠️ Warning: Native canvas binding not found for ${platformKey}`);
|
|
53
|
+
console.warn(` Expected package: ${requiredPackage}`);
|
|
54
|
+
console.warn('\n If you see "Cannot find native binding" errors, try:');
|
|
55
|
+
console.warn(' 1. Delete node_modules and package-lock.json');
|
|
56
|
+
console.warn(' 2. Run: npm install');
|
|
57
|
+
console.warn(` 3. Or manually install: npm install ${requiredPackage}\n`);
|
|
58
|
+
}
|