@shotstack/shotstack-canvas 2.0.8 → 2.0.10
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 +147 -55
- package/dist/entry.node.d.cts +21 -18
- package/dist/entry.node.d.ts +21 -18
- package/dist/entry.node.js +148 -57
- package/dist/entry.web.d.ts +18 -18
- package/dist/entry.web.js +98 -51
- package/package.json +65 -65
package/dist/entry.web.js
CHANGED
|
@@ -17897,8 +17897,7 @@ import {
|
|
|
17897
17897
|
svgTransformSchema,
|
|
17898
17898
|
svgGradientStopSchema,
|
|
17899
17899
|
richCaptionActiveSchema as baseCaptionActiveSchema,
|
|
17900
|
-
richCaptionWordAnimationSchema as baseCaptionWordAnimationSchema
|
|
17901
|
-
wordTimingSchema as baseWordTimingSchema
|
|
17900
|
+
richCaptionWordAnimationSchema as baseCaptionWordAnimationSchema
|
|
17902
17901
|
} from "@shotstack/schemas/zod";
|
|
17903
17902
|
|
|
17904
17903
|
// src/config/canvas-constants.ts
|
|
@@ -18055,7 +18054,7 @@ var CanvasRichTextAssetSchema = richTextAssetSchema.extend({
|
|
|
18055
18054
|
customFonts: external_exports.array(customFontSchema).optional()
|
|
18056
18055
|
}).strict();
|
|
18057
18056
|
var CanvasSvgAssetSchema = svgAssetSchema;
|
|
18058
|
-
var wordTimingSchema =
|
|
18057
|
+
var wordTimingSchema = external_exports.object({
|
|
18059
18058
|
text: external_exports.string().min(1),
|
|
18060
18059
|
start: external_exports.number().min(0),
|
|
18061
18060
|
end: external_exports.number().min(0),
|
|
@@ -18096,27 +18095,18 @@ var richCaptionAssetSchema = external_exports.object({
|
|
|
18096
18095
|
stroke: canvasStrokeSchema.optional(),
|
|
18097
18096
|
shadow: canvasShadowSchema.optional(),
|
|
18098
18097
|
background: canvasBackgroundSchema.optional(),
|
|
18098
|
+
border: borderSchema.optional(),
|
|
18099
18099
|
padding: paddingSchema.optional(),
|
|
18100
18100
|
align: canvasAlignmentSchema.optional(),
|
|
18101
18101
|
active: richCaptionActiveSchema.optional(),
|
|
18102
18102
|
wordAnimation: richCaptionWordAnimationSchema.optional(),
|
|
18103
|
-
position: external_exports.enum(["top", "center", "bottom"]).default("bottom"),
|
|
18104
|
-
maxWidth: external_exports.number().min(0.1).max(1).default(0.9),
|
|
18105
|
-
maxLines: external_exports.number().int().min(1).max(10).default(2),
|
|
18106
18103
|
customFonts: external_exports.array(customFontSchema).optional()
|
|
18107
18104
|
}).superRefine((data, ctx) => {
|
|
18108
|
-
if (data.src && data.words) {
|
|
18109
|
-
ctx.addIssue({
|
|
18110
|
-
code: external_exports.ZodIssueCode.custom,
|
|
18111
|
-
message: "src and words are mutually exclusive",
|
|
18112
|
-
path: ["src"]
|
|
18113
|
-
});
|
|
18114
|
-
}
|
|
18115
18105
|
if (!data.src && !data.words) {
|
|
18116
18106
|
ctx.addIssue({
|
|
18117
18107
|
code: external_exports.ZodIssueCode.custom,
|
|
18118
18108
|
message: "Either src or words must be provided",
|
|
18119
|
-
path: ["
|
|
18109
|
+
path: ["src"]
|
|
18120
18110
|
});
|
|
18121
18111
|
}
|
|
18122
18112
|
});
|
|
@@ -35624,7 +35614,7 @@ var CaptionLayoutEngine = class {
|
|
|
35624
35614
|
}
|
|
35625
35615
|
}
|
|
35626
35616
|
const wordGroups = groupWordsByPause(store, config2.pauseThreshold);
|
|
35627
|
-
const pixelMaxWidth = config2.
|
|
35617
|
+
const pixelMaxWidth = config2.availableWidth;
|
|
35628
35618
|
let spaceWidth;
|
|
35629
35619
|
if (config2.measureTextWidth) {
|
|
35630
35620
|
const fontString = `${config2.fontWeight} ${config2.fontSize}px "${config2.fontFamily}"`;
|
|
@@ -35654,31 +35644,45 @@ var CaptionLayoutEngine = class {
|
|
|
35654
35644
|
};
|
|
35655
35645
|
});
|
|
35656
35646
|
const allWordIndices = lines.flatMap((l) => l.wordIndices);
|
|
35647
|
+
if (allWordIndices.length === 0) {
|
|
35648
|
+
return null;
|
|
35649
|
+
}
|
|
35657
35650
|
return {
|
|
35658
35651
|
wordIndices: allWordIndices,
|
|
35659
35652
|
startTime: store.startTimes[allWordIndices[0]],
|
|
35660
35653
|
endTime: store.endTimes[allWordIndices[allWordIndices.length - 1]],
|
|
35661
35654
|
lines
|
|
35662
35655
|
};
|
|
35663
|
-
});
|
|
35656
|
+
}).filter((g) => g !== null);
|
|
35664
35657
|
});
|
|
35665
35658
|
const calculateGroupY = (group) => {
|
|
35666
35659
|
const totalHeight = group.lines.length * config2.fontSize * config2.lineHeight;
|
|
35667
|
-
switch (config2.
|
|
35660
|
+
switch (config2.verticalAlign) {
|
|
35668
35661
|
case "top":
|
|
35669
35662
|
return config2.fontSize * 1.5;
|
|
35670
35663
|
case "bottom":
|
|
35671
35664
|
return config2.frameHeight - totalHeight - config2.fontSize * 0.5;
|
|
35672
|
-
case "
|
|
35665
|
+
case "middle":
|
|
35673
35666
|
default:
|
|
35674
35667
|
return (config2.frameHeight - totalHeight) / 2 + config2.fontSize;
|
|
35675
35668
|
}
|
|
35676
35669
|
};
|
|
35670
|
+
const calculateLineX = (lineWidth) => {
|
|
35671
|
+
switch (config2.horizontalAlign) {
|
|
35672
|
+
case "left":
|
|
35673
|
+
return config2.paddingLeft;
|
|
35674
|
+
case "right":
|
|
35675
|
+
return config2.frameWidth - lineWidth - config2.paddingLeft;
|
|
35676
|
+
case "center":
|
|
35677
|
+
default:
|
|
35678
|
+
return (config2.frameWidth - lineWidth) / 2;
|
|
35679
|
+
}
|
|
35680
|
+
};
|
|
35677
35681
|
for (const group of groups) {
|
|
35678
35682
|
const baseY = calculateGroupY(group);
|
|
35679
35683
|
for (let lineIdx = 0; lineIdx < group.lines.length; lineIdx++) {
|
|
35680
35684
|
const line = group.lines[lineIdx];
|
|
35681
|
-
line.x = (
|
|
35685
|
+
line.x = calculateLineX(line.width);
|
|
35682
35686
|
line.y = baseY + lineIdx * config2.fontSize * config2.lineHeight;
|
|
35683
35687
|
let xCursor = line.x;
|
|
35684
35688
|
for (const wordIdx of line.wordIndices) {
|
|
@@ -35969,6 +35973,7 @@ function calculateNoneState(ctx) {
|
|
|
35969
35973
|
};
|
|
35970
35974
|
}
|
|
35971
35975
|
function calculateWordAnimationState(wordStart, wordEnd, currentTime, config2, activeScale = 1, charCount = 0, fontSize = 48) {
|
|
35976
|
+
const safeSpeed = config2.speed > 0 ? config2.speed : 1;
|
|
35972
35977
|
const ctx = {
|
|
35973
35978
|
wordStart,
|
|
35974
35979
|
wordEnd,
|
|
@@ -35979,25 +35984,25 @@ function calculateWordAnimationState(wordStart, wordEnd, currentTime, config2, a
|
|
|
35979
35984
|
let partialState;
|
|
35980
35985
|
switch (config2.style) {
|
|
35981
35986
|
case "karaoke":
|
|
35982
|
-
partialState = calculateKaraokeState(ctx,
|
|
35987
|
+
partialState = calculateKaraokeState(ctx, safeSpeed);
|
|
35983
35988
|
break;
|
|
35984
35989
|
case "highlight":
|
|
35985
35990
|
partialState = calculateHighlightState(ctx);
|
|
35986
35991
|
break;
|
|
35987
35992
|
case "pop":
|
|
35988
|
-
partialState = calculatePopState(ctx, activeScale,
|
|
35993
|
+
partialState = calculatePopState(ctx, activeScale, safeSpeed);
|
|
35989
35994
|
break;
|
|
35990
35995
|
case "fade":
|
|
35991
|
-
partialState = calculateFadeState(ctx,
|
|
35996
|
+
partialState = calculateFadeState(ctx, safeSpeed);
|
|
35992
35997
|
break;
|
|
35993
35998
|
case "slide":
|
|
35994
|
-
partialState = calculateSlideState(ctx, config2.direction,
|
|
35999
|
+
partialState = calculateSlideState(ctx, config2.direction, safeSpeed, fontSize);
|
|
35995
36000
|
break;
|
|
35996
36001
|
case "bounce":
|
|
35997
|
-
partialState = calculateBounceState(ctx,
|
|
36002
|
+
partialState = calculateBounceState(ctx, safeSpeed, fontSize);
|
|
35998
36003
|
break;
|
|
35999
36004
|
case "typewriter":
|
|
36000
|
-
partialState = calculateTypewriterState(ctx, charCount,
|
|
36005
|
+
partialState = calculateTypewriterState(ctx, charCount, safeSpeed);
|
|
36001
36006
|
break;
|
|
36002
36007
|
case "none":
|
|
36003
36008
|
default:
|
|
@@ -36121,6 +36126,22 @@ function extractCaptionBackground(asset) {
|
|
|
36121
36126
|
opacity: bg.opacity ?? 1
|
|
36122
36127
|
};
|
|
36123
36128
|
}
|
|
36129
|
+
function extractCaptionBackgroundBorderRadius(asset) {
|
|
36130
|
+
const bg = asset.background;
|
|
36131
|
+
return bg?.borderRadius ?? 0;
|
|
36132
|
+
}
|
|
36133
|
+
function extractCaptionBorder(asset) {
|
|
36134
|
+
const border = asset.border;
|
|
36135
|
+
if (!border || !border.width || border.width <= 0) {
|
|
36136
|
+
return void 0;
|
|
36137
|
+
}
|
|
36138
|
+
return {
|
|
36139
|
+
width: border.width,
|
|
36140
|
+
color: border.color ?? "#000000",
|
|
36141
|
+
opacity: border.opacity ?? 1,
|
|
36142
|
+
radius: border.radius ?? 0
|
|
36143
|
+
};
|
|
36144
|
+
}
|
|
36124
36145
|
function extractAnimationConfig(asset) {
|
|
36125
36146
|
const wordAnim = asset.wordAnimation;
|
|
36126
36147
|
if (!wordAnim) {
|
|
@@ -36166,6 +36187,28 @@ function createDrawCaptionWordOp(word, animState, asset, fontConfig) {
|
|
|
36166
36187
|
background: extractBackgroundConfig(asset, isActive)
|
|
36167
36188
|
};
|
|
36168
36189
|
}
|
|
36190
|
+
function calculateGroupBounds(activeGroup, padding) {
|
|
36191
|
+
let minX = Infinity;
|
|
36192
|
+
let maxX = -Infinity;
|
|
36193
|
+
let minY = Infinity;
|
|
36194
|
+
let maxY = -Infinity;
|
|
36195
|
+
for (const line of activeGroup.lines) {
|
|
36196
|
+
const lineX = line.x;
|
|
36197
|
+
const lineRight = line.x + line.width;
|
|
36198
|
+
const lineY = line.y - line.height * 0.8;
|
|
36199
|
+
const lineBottom = line.y + line.height * 0.2;
|
|
36200
|
+
if (lineX < minX) minX = lineX;
|
|
36201
|
+
if (lineRight > maxX) maxX = lineRight;
|
|
36202
|
+
if (lineY < minY) minY = lineY;
|
|
36203
|
+
if (lineBottom > maxY) maxY = lineBottom;
|
|
36204
|
+
}
|
|
36205
|
+
return {
|
|
36206
|
+
bgX: minX - padding.left,
|
|
36207
|
+
bgY: minY - padding.top,
|
|
36208
|
+
bgWidth: maxX - minX + padding.left + padding.right,
|
|
36209
|
+
bgHeight: maxY - minY + padding.top + padding.bottom
|
|
36210
|
+
};
|
|
36211
|
+
}
|
|
36169
36212
|
function generateRichCaptionDrawOps(asset, layout, frameTimeMs, layoutEngine, _config) {
|
|
36170
36213
|
if (layout.store.length === 0) {
|
|
36171
36214
|
return [];
|
|
@@ -36185,36 +36228,40 @@ function generateRichCaptionDrawOps(asset, layout, frameTimeMs, layoutEngine, _c
|
|
|
36185
36228
|
fontConfig.size
|
|
36186
36229
|
);
|
|
36187
36230
|
const ops = [];
|
|
36188
|
-
const
|
|
36189
|
-
|
|
36190
|
-
|
|
36191
|
-
|
|
36192
|
-
);
|
|
36193
|
-
|
|
36194
|
-
|
|
36195
|
-
|
|
36196
|
-
let maxX = -Infinity;
|
|
36197
|
-
let minY = Infinity;
|
|
36198
|
-
let maxY = -Infinity;
|
|
36199
|
-
for (const line of activeGroup.lines) {
|
|
36200
|
-
const lineX = line.x;
|
|
36201
|
-
const lineRight = line.x + line.width;
|
|
36202
|
-
const lineY = line.y - line.height * 0.8;
|
|
36203
|
-
const lineBottom = line.y + line.height * 0.2;
|
|
36204
|
-
if (lineX < minX) minX = lineX;
|
|
36205
|
-
if (lineRight > maxX) maxX = lineRight;
|
|
36206
|
-
if (lineY < minY) minY = lineY;
|
|
36207
|
-
if (lineBottom > maxY) maxY = lineBottom;
|
|
36208
|
-
}
|
|
36231
|
+
const activeGroup = layout.groups.find(
|
|
36232
|
+
(g) => frameTimeMs >= g.startTime && frameTimeMs <= g.endTime
|
|
36233
|
+
);
|
|
36234
|
+
if (activeGroup && activeGroup.lines.length > 0) {
|
|
36235
|
+
const padding = extractCaptionPadding(asset);
|
|
36236
|
+
const { bgX, bgY, bgWidth, bgHeight } = calculateGroupBounds(activeGroup, padding);
|
|
36237
|
+
const captionBg = extractCaptionBackground(asset);
|
|
36238
|
+
if (captionBg) {
|
|
36209
36239
|
ops.push({
|
|
36210
36240
|
op: "DrawCaptionBackground",
|
|
36211
|
-
x:
|
|
36212
|
-
y:
|
|
36213
|
-
width:
|
|
36214
|
-
height:
|
|
36241
|
+
x: bgX,
|
|
36242
|
+
y: bgY,
|
|
36243
|
+
width: bgWidth,
|
|
36244
|
+
height: bgHeight,
|
|
36215
36245
|
color: captionBg.color,
|
|
36216
36246
|
opacity: captionBg.opacity,
|
|
36217
|
-
borderRadius:
|
|
36247
|
+
borderRadius: extractCaptionBackgroundBorderRadius(asset)
|
|
36248
|
+
});
|
|
36249
|
+
}
|
|
36250
|
+
const borderConfig = extractCaptionBorder(asset);
|
|
36251
|
+
if (borderConfig) {
|
|
36252
|
+
const halfBorder = borderConfig.width / 2;
|
|
36253
|
+
ops.push({
|
|
36254
|
+
op: "RectangleStroke",
|
|
36255
|
+
x: bgX + halfBorder,
|
|
36256
|
+
y: bgY + halfBorder,
|
|
36257
|
+
width: bgWidth - borderConfig.width,
|
|
36258
|
+
height: bgHeight - borderConfig.width,
|
|
36259
|
+
stroke: {
|
|
36260
|
+
width: borderConfig.width,
|
|
36261
|
+
color: borderConfig.color,
|
|
36262
|
+
opacity: borderConfig.opacity
|
|
36263
|
+
},
|
|
36264
|
+
borderRadius: borderConfig.radius > 0 ? borderConfig.radius : void 0
|
|
36218
36265
|
});
|
|
36219
36266
|
}
|
|
36220
36267
|
}
|
package/package.json
CHANGED
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@shotstack/shotstack-canvas",
|
|
3
|
-
"version": "2.0.
|
|
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
|
-
"test:caption-web": "vite dev examples/caption-tests",
|
|
35
|
-
"prepublishOnly": "npm run build"
|
|
36
|
-
},
|
|
37
|
-
"publishConfig": {
|
|
38
|
-
"access": "public",
|
|
39
|
-
"registry": "https://registry.npmjs.org/"
|
|
40
|
-
},
|
|
41
|
-
"engines": {
|
|
42
|
-
"node": ">=18"
|
|
43
|
-
},
|
|
44
|
-
"sideEffects": false,
|
|
45
|
-
"dependencies": {
|
|
46
|
-
"@resvg/resvg-js": "^2.6.2",
|
|
47
|
-
"@resvg/resvg-wasm": "^2.6.2",
|
|
48
|
-
"@shotstack/schemas": "1.8.
|
|
49
|
-
"canvas": "npm:@napi-rs/canvas@^0.1.54",
|
|
50
|
-
"ffmpeg-static": "^5.2.0",
|
|
51
|
-
"fontkit": "^2.0.4",
|
|
52
|
-
"harfbuzzjs": "0.4.12",
|
|
53
|
-
"lru-cache": "^11.2.5",
|
|
54
|
-
"mp4-muxer": "^5.1.3",
|
|
55
|
-
"zod": "^4.2.0"
|
|
56
|
-
},
|
|
57
|
-
"devDependencies": {
|
|
58
|
-
"@types/node": "^20.14.10",
|
|
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": "2.0.10",
|
|
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
|
+
"test:caption-web": "vite dev examples/caption-tests",
|
|
35
|
+
"prepublishOnly": "npm run build"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public",
|
|
39
|
+
"registry": "https://registry.npmjs.org/"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"sideEffects": false,
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@resvg/resvg-js": "^2.6.2",
|
|
47
|
+
"@resvg/resvg-wasm": "^2.6.2",
|
|
48
|
+
"@shotstack/schemas": "1.8.7",
|
|
49
|
+
"canvas": "npm:@napi-rs/canvas@^0.1.54",
|
|
50
|
+
"ffmpeg-static": "^5.2.0",
|
|
51
|
+
"fontkit": "^2.0.4",
|
|
52
|
+
"harfbuzzjs": "0.4.12",
|
|
53
|
+
"lru-cache": "^11.2.5",
|
|
54
|
+
"mp4-muxer": "^5.1.3",
|
|
55
|
+
"zod": "^4.2.0"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "^20.14.10",
|
|
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
|
+
}
|