@shotstack/shotstack-canvas 1.4.6 → 1.5.0
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/chunk-HYGMWVDX.js +19 -0
- package/dist/entry.node.cjs +154 -30
- package/dist/entry.node.d.cts +31 -0
- package/dist/entry.node.d.ts +31 -0
- package/dist/entry.node.js +154 -30
- package/dist/entry.web.d.ts +31 -0
- package/dist/entry.web.js +161 -33
- package/dist/hb-KXF2MJ2J.js +550 -0
- package/dist/hbjs-ZTRARROF.js +499 -0
- package/package.json +62 -62
package/dist/entry.node.js
CHANGED
|
@@ -94,11 +94,26 @@ var animationSchema = Joi.object({
|
|
|
94
94
|
otherwise: Joi.forbidden()
|
|
95
95
|
})
|
|
96
96
|
}).unknown(false);
|
|
97
|
+
var borderSchema = Joi.object({
|
|
98
|
+
width: Joi.number().min(0).default(0),
|
|
99
|
+
color: Joi.string().pattern(HEX6).default("#000000"),
|
|
100
|
+
opacity: Joi.number().min(0).max(1).default(1)
|
|
101
|
+
}).unknown(false);
|
|
97
102
|
var backgroundSchema = Joi.object({
|
|
98
103
|
color: Joi.string().pattern(HEX6).optional(),
|
|
99
104
|
opacity: Joi.number().min(0).max(1).default(1),
|
|
100
|
-
borderRadius: Joi.number().min(0).default(0)
|
|
105
|
+
borderRadius: Joi.number().min(0).default(0),
|
|
106
|
+
border: borderSchema.optional()
|
|
101
107
|
}).unknown(false);
|
|
108
|
+
var paddingSchema = Joi.alternatives().try(
|
|
109
|
+
Joi.number().min(0).default(0),
|
|
110
|
+
Joi.object({
|
|
111
|
+
top: Joi.number().min(0).default(0),
|
|
112
|
+
right: Joi.number().min(0).default(0),
|
|
113
|
+
bottom: Joi.number().min(0).default(0),
|
|
114
|
+
left: Joi.number().min(0).default(0)
|
|
115
|
+
}).unknown(false)
|
|
116
|
+
);
|
|
102
117
|
var customFontSchema = Joi.object({
|
|
103
118
|
src: Joi.string().uri().required(),
|
|
104
119
|
family: Joi.string().required(),
|
|
@@ -116,6 +131,7 @@ var RichTextAssetSchema = Joi.object({
|
|
|
116
131
|
stroke: strokeSchema.optional(),
|
|
117
132
|
shadow: shadowSchema.optional(),
|
|
118
133
|
background: backgroundSchema.optional(),
|
|
134
|
+
padding: paddingSchema.optional(),
|
|
119
135
|
align: alignmentSchema.optional(),
|
|
120
136
|
animation: animationSchema.optional(),
|
|
121
137
|
customFonts: Joi.array().items(customFontSchema).optional(),
|
|
@@ -254,6 +270,23 @@ function setupWasmInterceptors(wasmBinary) {
|
|
|
254
270
|
}
|
|
255
271
|
return originalFetch.apply(this, [input, init]);
|
|
256
272
|
};
|
|
273
|
+
const originalInstantiate = WebAssembly.instantiate;
|
|
274
|
+
WebAssembly.instantiate = async function(bufferSourceOrModule, importObject) {
|
|
275
|
+
console.log(
|
|
276
|
+
`\u{1F504} WebAssembly.instantiate called, type: ${bufferSourceOrModule instanceof WebAssembly.Module ? "Module" : "BufferSource"}`
|
|
277
|
+
);
|
|
278
|
+
if (bufferSourceOrModule instanceof WebAssembly.Module) {
|
|
279
|
+
return originalInstantiate.call(WebAssembly, bufferSourceOrModule, importObject);
|
|
280
|
+
}
|
|
281
|
+
console.log(`\u{1F504} Intercepted WebAssembly.instantiate, using pre-loaded WASM binary`);
|
|
282
|
+
const module = await WebAssembly.compile(wasmBinary);
|
|
283
|
+
const instance = await originalInstantiate.call(
|
|
284
|
+
WebAssembly,
|
|
285
|
+
module,
|
|
286
|
+
importObject
|
|
287
|
+
);
|
|
288
|
+
return { module, instance };
|
|
289
|
+
};
|
|
257
290
|
const originalInstantiateStreaming = WebAssembly.instantiateStreaming;
|
|
258
291
|
if (originalInstantiateStreaming) {
|
|
259
292
|
WebAssembly.instantiateStreaming = async function(source, importObject) {
|
|
@@ -291,21 +324,25 @@ async function initHB(wasmBaseURL) {
|
|
|
291
324
|
console.log(`\u2705 WASM binary loaded successfully (${wasmBinary.byteLength} bytes)`);
|
|
292
325
|
if (!isNode()) {
|
|
293
326
|
setupWasmInterceptors(wasmBinary);
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
}
|
|
327
|
+
window.Module = {
|
|
328
|
+
wasmBinary,
|
|
329
|
+
locateFile: (path) => {
|
|
330
|
+
console.log(`\u{1F50D} locateFile called for: ${path}`);
|
|
331
|
+
return path;
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
console.log(`\u{1F30D} Set global Module.wasmBinary (${wasmBinary.byteLength} bytes)`);
|
|
335
|
+
}
|
|
336
|
+
console.log("\u{1F504} Importing harfbuzzjs/hb.js (factory)");
|
|
337
|
+
const hbModule = await import("harfbuzzjs/hb.js");
|
|
338
|
+
const hbFactory = hbModule.default || hbModule;
|
|
339
|
+
console.log("\u{1F504} Calling hb factory with wasmBinary");
|
|
340
|
+
const hbInstance = await hbFactory({ wasmBinary });
|
|
341
|
+
console.log("\u{1F504} Importing harfbuzzjs/hbjs.js (wrapper)");
|
|
342
|
+
const hbjsModule = await import("harfbuzzjs/hbjs.js");
|
|
343
|
+
const hbjsWrapper = hbjsModule.default || hbjsModule;
|
|
344
|
+
console.log("\u{1F504} Wrapping hb instance");
|
|
345
|
+
const hb = hbjsWrapper(hbInstance);
|
|
309
346
|
if (!hb || typeof hb.createBuffer !== "function" || typeof hb.createFont !== "function") {
|
|
310
347
|
throw new Error("Failed to initialize HarfBuzz: unexpected export shape from 'harfbuzzjs'.");
|
|
311
348
|
}
|
|
@@ -768,15 +805,27 @@ function decorationGeometry(kind, p) {
|
|
|
768
805
|
}
|
|
769
806
|
|
|
770
807
|
// src/core/drawops.ts
|
|
808
|
+
function normalizePadding(padding) {
|
|
809
|
+
if (padding === void 0 || padding === null) {
|
|
810
|
+
return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
811
|
+
}
|
|
812
|
+
if (typeof padding === "number") {
|
|
813
|
+
return { top: padding, right: padding, bottom: padding, left: padding };
|
|
814
|
+
}
|
|
815
|
+
return padding;
|
|
816
|
+
}
|
|
771
817
|
async function buildDrawOps(p) {
|
|
772
818
|
const ops = [];
|
|
819
|
+
const padding = normalizePadding(p.padding);
|
|
820
|
+
const borderWidth = p.background?.border?.width ?? 0;
|
|
773
821
|
ops.push({
|
|
774
822
|
op: "BeginFrame",
|
|
775
823
|
width: p.canvas.width,
|
|
776
824
|
height: p.canvas.height,
|
|
777
825
|
pixelRatio: p.canvas.pixelRatio,
|
|
778
826
|
clear: true,
|
|
779
|
-
bg:
|
|
827
|
+
bg: void 0
|
|
828
|
+
// Background will be drawn as a separate layer with proper padding/border
|
|
780
829
|
});
|
|
781
830
|
if (p.lines.length === 0) return ops;
|
|
782
831
|
const upem = Math.max(1, await p.getUnitsPerEm());
|
|
@@ -786,33 +835,34 @@ async function buildDrawOps(p) {
|
|
|
786
835
|
let blockY;
|
|
787
836
|
switch (p.align.vertical) {
|
|
788
837
|
case "top":
|
|
789
|
-
blockY = p.font.size;
|
|
838
|
+
blockY = p.font.size + padding.top;
|
|
790
839
|
break;
|
|
791
840
|
case "bottom":
|
|
792
|
-
blockY = p.textRect.height - (numLines - 1) * lineHeightPx;
|
|
841
|
+
blockY = p.textRect.height - (numLines - 1) * lineHeightPx + padding.top;
|
|
793
842
|
break;
|
|
794
843
|
case "middle":
|
|
795
844
|
default:
|
|
796
845
|
const capHeightRatio = 0.35;
|
|
797
846
|
const visualOffset = p.font.size * capHeightRatio;
|
|
798
|
-
blockY = (p.textRect.height - (numLines - 1) * lineHeightPx) / 2 + visualOffset;
|
|
847
|
+
blockY = (p.textRect.height - (numLines - 1) * lineHeightPx) / 2 + visualOffset + padding.top;
|
|
799
848
|
break;
|
|
800
849
|
}
|
|
801
850
|
const fill = p.style.gradient ? gradientSpecFrom(p.style.gradient, 1) : { kind: "solid", color: p.font.color, opacity: p.font.opacity };
|
|
802
851
|
const decoColor = p.style.gradient ? p.style.gradient.stops[p.style.gradient.stops.length - 1]?.color ?? p.font.color : p.font.color;
|
|
852
|
+
const textOps = [];
|
|
803
853
|
let gMinX = Infinity, gMinY = Infinity, gMaxX = -Infinity, gMaxY = -Infinity;
|
|
804
854
|
for (const line of p.lines) {
|
|
805
855
|
let lineX;
|
|
806
856
|
switch (p.align.horizontal) {
|
|
807
857
|
case "left":
|
|
808
|
-
lineX =
|
|
858
|
+
lineX = padding.left;
|
|
809
859
|
break;
|
|
810
860
|
case "right":
|
|
811
|
-
lineX = p.textRect.width - line.width;
|
|
861
|
+
lineX = p.textRect.width - line.width + padding.left;
|
|
812
862
|
break;
|
|
813
863
|
case "center":
|
|
814
864
|
default:
|
|
815
|
-
lineX = (p.textRect.width - line.width) / 2;
|
|
865
|
+
lineX = (p.textRect.width - line.width) / 2 + padding.left;
|
|
816
866
|
break;
|
|
817
867
|
}
|
|
818
868
|
let xCursor = lineX;
|
|
@@ -836,7 +886,7 @@ async function buildDrawOps(p) {
|
|
|
836
886
|
if (x2 > gMaxX) gMaxX = x2;
|
|
837
887
|
if (y2 > gMaxY) gMaxY = y2;
|
|
838
888
|
if (p.shadow && p.shadow.blur > 0) {
|
|
839
|
-
|
|
889
|
+
textOps.push({
|
|
840
890
|
isShadow: true,
|
|
841
891
|
op: "FillPath",
|
|
842
892
|
path,
|
|
@@ -847,7 +897,7 @@ async function buildDrawOps(p) {
|
|
|
847
897
|
});
|
|
848
898
|
}
|
|
849
899
|
if (p.stroke && p.stroke.width > 0) {
|
|
850
|
-
|
|
900
|
+
textOps.push({
|
|
851
901
|
op: "StrokePath",
|
|
852
902
|
path,
|
|
853
903
|
x: glyphX,
|
|
@@ -858,7 +908,7 @@ async function buildDrawOps(p) {
|
|
|
858
908
|
opacity: p.stroke.opacity
|
|
859
909
|
});
|
|
860
910
|
}
|
|
861
|
-
|
|
911
|
+
textOps.push({
|
|
862
912
|
op: "FillPath",
|
|
863
913
|
path,
|
|
864
914
|
x: glyphX,
|
|
@@ -875,7 +925,7 @@ async function buildDrawOps(p) {
|
|
|
875
925
|
lineWidth: line.width,
|
|
876
926
|
xStart: lineX
|
|
877
927
|
});
|
|
878
|
-
|
|
928
|
+
textOps.push({
|
|
879
929
|
op: "DecorationLine",
|
|
880
930
|
from: { x: deco.x1, y: deco.y },
|
|
881
931
|
to: { x: deco.x2, y: deco.y },
|
|
@@ -887,12 +937,46 @@ async function buildDrawOps(p) {
|
|
|
887
937
|
}
|
|
888
938
|
if (gMinX !== Infinity) {
|
|
889
939
|
const gbox = { x: gMinX, y: gMinY, w: Math.max(1, gMaxX - gMinX), h: Math.max(1, gMaxY - gMinY) };
|
|
890
|
-
for (const op of
|
|
940
|
+
for (const op of textOps) {
|
|
891
941
|
if (op.op === "FillPath" && !op.isShadow) {
|
|
892
942
|
op.gradientBBox = gbox;
|
|
893
943
|
}
|
|
894
944
|
}
|
|
895
945
|
}
|
|
946
|
+
if (p.background && (p.background.color || p.background.border)) {
|
|
947
|
+
const bgX = 0;
|
|
948
|
+
const bgY = 0;
|
|
949
|
+
const bgWidth = p.canvas.width;
|
|
950
|
+
const bgHeight = p.canvas.height;
|
|
951
|
+
if (p.background.color) {
|
|
952
|
+
ops.push({
|
|
953
|
+
op: "Rectangle",
|
|
954
|
+
x: bgX,
|
|
955
|
+
y: bgY,
|
|
956
|
+
width: bgWidth,
|
|
957
|
+
height: bgHeight,
|
|
958
|
+
fill: { kind: "solid", color: p.background.color, opacity: p.background.opacity },
|
|
959
|
+
borderRadius: p.background.borderRadius
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
if (p.background.border && p.background.border.width > 0) {
|
|
963
|
+
const halfBorder = p.background.border.width / 2;
|
|
964
|
+
ops.push({
|
|
965
|
+
op: "RectangleStroke",
|
|
966
|
+
x: bgX + halfBorder,
|
|
967
|
+
y: bgY + halfBorder,
|
|
968
|
+
width: bgWidth - p.background.border.width,
|
|
969
|
+
height: bgHeight - p.background.border.width,
|
|
970
|
+
stroke: {
|
|
971
|
+
width: p.background.border.width,
|
|
972
|
+
color: p.background.border.color,
|
|
973
|
+
opacity: p.background.border.opacity
|
|
974
|
+
},
|
|
975
|
+
borderRadius: Math.max(0, p.background.borderRadius - halfBorder)
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
ops.push(...textOps);
|
|
896
980
|
return ops;
|
|
897
981
|
}
|
|
898
982
|
function tokenizePath(d) {
|
|
@@ -1702,6 +1786,44 @@ async function createNodePainter(opts) {
|
|
|
1702
1786
|
});
|
|
1703
1787
|
continue;
|
|
1704
1788
|
}
|
|
1789
|
+
if (op.op === "Rectangle") {
|
|
1790
|
+
renderToBoth((context) => {
|
|
1791
|
+
context.save();
|
|
1792
|
+
const fill = makeGradientFromBBox(context, op.fill, {
|
|
1793
|
+
x: op.x,
|
|
1794
|
+
y: op.y,
|
|
1795
|
+
w: op.width,
|
|
1796
|
+
h: op.height
|
|
1797
|
+
});
|
|
1798
|
+
context.fillStyle = fill;
|
|
1799
|
+
if (op.borderRadius && op.borderRadius > 0) {
|
|
1800
|
+
context.beginPath();
|
|
1801
|
+
roundRectPath(context, op.x, op.y, op.width, op.height, op.borderRadius);
|
|
1802
|
+
context.fill();
|
|
1803
|
+
} else {
|
|
1804
|
+
context.fillRect(op.x, op.y, op.width, op.height);
|
|
1805
|
+
}
|
|
1806
|
+
context.restore();
|
|
1807
|
+
});
|
|
1808
|
+
continue;
|
|
1809
|
+
}
|
|
1810
|
+
if (op.op === "RectangleStroke") {
|
|
1811
|
+
renderToBoth((context) => {
|
|
1812
|
+
context.save();
|
|
1813
|
+
const c = parseHex6(op.stroke.color, op.stroke.opacity);
|
|
1814
|
+
context.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1815
|
+
context.lineWidth = op.stroke.width;
|
|
1816
|
+
if (op.borderRadius && op.borderRadius > 0) {
|
|
1817
|
+
context.beginPath();
|
|
1818
|
+
roundRectPath(context, op.x, op.y, op.width, op.height, op.borderRadius);
|
|
1819
|
+
context.stroke();
|
|
1820
|
+
} else {
|
|
1821
|
+
context.strokeRect(op.x, op.y, op.width, op.height);
|
|
1822
|
+
}
|
|
1823
|
+
context.restore();
|
|
1824
|
+
});
|
|
1825
|
+
continue;
|
|
1826
|
+
}
|
|
1705
1827
|
}
|
|
1706
1828
|
if (needsAlphaExtraction) {
|
|
1707
1829
|
const whiteData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
@@ -2265,14 +2387,15 @@ async function createTextEngine(opts = {}) {
|
|
|
2265
2387
|
`Failed to layout text: ${err instanceof Error ? err.message : String(err)}`
|
|
2266
2388
|
);
|
|
2267
2389
|
}
|
|
2390
|
+
const padding = asset.padding ? typeof asset.padding === "number" ? { top: asset.padding, right: asset.padding, bottom: asset.padding, left: asset.padding } : asset.padding : { top: 0, right: 0, bottom: 0, left: 0 };
|
|
2268
2391
|
const textRect = {
|
|
2269
2392
|
x: 0,
|
|
2270
2393
|
y: 0,
|
|
2271
2394
|
width: asset.width ?? width,
|
|
2272
2395
|
height: asset.height ?? height
|
|
2273
2396
|
};
|
|
2274
|
-
const canvasW = asset.width ?? width;
|
|
2275
|
-
const canvasH = asset.height ?? height;
|
|
2397
|
+
const canvasW = (asset.width ?? width) + padding.left + padding.right;
|
|
2398
|
+
const canvasH = (asset.height ?? height) + padding.top + padding.bottom;
|
|
2276
2399
|
const canvasPR = asset.pixelRatio ?? pixelRatio;
|
|
2277
2400
|
let ops0;
|
|
2278
2401
|
try {
|
|
@@ -2299,6 +2422,7 @@ async function createTextEngine(opts = {}) {
|
|
|
2299
2422
|
vertical: asset.align?.vertical ?? "middle"
|
|
2300
2423
|
},
|
|
2301
2424
|
background: asset.background,
|
|
2425
|
+
padding: asset.padding,
|
|
2302
2426
|
glyphPathProvider: (gid, fontDesc) => fonts.glyphPath(fontDesc || desc, gid),
|
|
2303
2427
|
getUnitsPerEm: (fontDesc) => fonts.getUnitsPerEm(fontDesc || desc)
|
|
2304
2428
|
});
|
package/dist/entry.web.d.ts
CHANGED
|
@@ -64,6 +64,17 @@ type RichTextValidated = Required<{
|
|
|
64
64
|
color?: string;
|
|
65
65
|
opacity: number;
|
|
66
66
|
borderRadius: number;
|
|
67
|
+
border?: {
|
|
68
|
+
width: number;
|
|
69
|
+
color: string;
|
|
70
|
+
opacity: number;
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
padding?: number | {
|
|
74
|
+
top: number;
|
|
75
|
+
right: number;
|
|
76
|
+
bottom: number;
|
|
77
|
+
left: number;
|
|
67
78
|
};
|
|
68
79
|
align?: {
|
|
69
80
|
horizontal: "left" | "center" | "right";
|
|
@@ -177,6 +188,26 @@ type DrawOp = {
|
|
|
177
188
|
width: number;
|
|
178
189
|
color: string;
|
|
179
190
|
opacity: number;
|
|
191
|
+
} | {
|
|
192
|
+
op: "Rectangle";
|
|
193
|
+
x: number;
|
|
194
|
+
y: number;
|
|
195
|
+
width: number;
|
|
196
|
+
height: number;
|
|
197
|
+
fill: GradientSpec;
|
|
198
|
+
borderRadius?: number;
|
|
199
|
+
} | {
|
|
200
|
+
op: "RectangleStroke";
|
|
201
|
+
x: number;
|
|
202
|
+
y: number;
|
|
203
|
+
width: number;
|
|
204
|
+
height: number;
|
|
205
|
+
stroke: {
|
|
206
|
+
width: number;
|
|
207
|
+
color: string;
|
|
208
|
+
opacity: number;
|
|
209
|
+
};
|
|
210
|
+
borderRadius?: number;
|
|
180
211
|
};
|
|
181
212
|
type EngineInit = {
|
|
182
213
|
width: number;
|