@shotstack/shotstack-canvas 1.4.7 → 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/entry.node.cjs +118 -15
- package/dist/entry.node.d.cts +31 -0
- package/dist/entry.node.d.ts +31 -0
- package/dist/entry.node.js +118 -15
- package/dist/entry.web.d.ts +31 -0
- package/dist/entry.web.js +122 -15
- package/package.json +62 -62
package/dist/entry.node.cjs
CHANGED
|
@@ -132,11 +132,26 @@ var animationSchema = import_joi.default.object({
|
|
|
132
132
|
otherwise: import_joi.default.forbidden()
|
|
133
133
|
})
|
|
134
134
|
}).unknown(false);
|
|
135
|
+
var borderSchema = import_joi.default.object({
|
|
136
|
+
width: import_joi.default.number().min(0).default(0),
|
|
137
|
+
color: import_joi.default.string().pattern(HEX6).default("#000000"),
|
|
138
|
+
opacity: import_joi.default.number().min(0).max(1).default(1)
|
|
139
|
+
}).unknown(false);
|
|
135
140
|
var backgroundSchema = import_joi.default.object({
|
|
136
141
|
color: import_joi.default.string().pattern(HEX6).optional(),
|
|
137
142
|
opacity: import_joi.default.number().min(0).max(1).default(1),
|
|
138
|
-
borderRadius: import_joi.default.number().min(0).default(0)
|
|
143
|
+
borderRadius: import_joi.default.number().min(0).default(0),
|
|
144
|
+
border: borderSchema.optional()
|
|
139
145
|
}).unknown(false);
|
|
146
|
+
var paddingSchema = import_joi.default.alternatives().try(
|
|
147
|
+
import_joi.default.number().min(0).default(0),
|
|
148
|
+
import_joi.default.object({
|
|
149
|
+
top: import_joi.default.number().min(0).default(0),
|
|
150
|
+
right: import_joi.default.number().min(0).default(0),
|
|
151
|
+
bottom: import_joi.default.number().min(0).default(0),
|
|
152
|
+
left: import_joi.default.number().min(0).default(0)
|
|
153
|
+
}).unknown(false)
|
|
154
|
+
);
|
|
140
155
|
var customFontSchema = import_joi.default.object({
|
|
141
156
|
src: import_joi.default.string().uri().required(),
|
|
142
157
|
family: import_joi.default.string().required(),
|
|
@@ -154,6 +169,7 @@ var RichTextAssetSchema = import_joi.default.object({
|
|
|
154
169
|
stroke: strokeSchema.optional(),
|
|
155
170
|
shadow: shadowSchema.optional(),
|
|
156
171
|
background: backgroundSchema.optional(),
|
|
172
|
+
padding: paddingSchema.optional(),
|
|
157
173
|
align: alignmentSchema.optional(),
|
|
158
174
|
animation: animationSchema.optional(),
|
|
159
175
|
customFonts: import_joi.default.array().items(customFontSchema).optional(),
|
|
@@ -828,15 +844,27 @@ function decorationGeometry(kind, p) {
|
|
|
828
844
|
}
|
|
829
845
|
|
|
830
846
|
// src/core/drawops.ts
|
|
847
|
+
function normalizePadding(padding) {
|
|
848
|
+
if (padding === void 0 || padding === null) {
|
|
849
|
+
return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
850
|
+
}
|
|
851
|
+
if (typeof padding === "number") {
|
|
852
|
+
return { top: padding, right: padding, bottom: padding, left: padding };
|
|
853
|
+
}
|
|
854
|
+
return padding;
|
|
855
|
+
}
|
|
831
856
|
async function buildDrawOps(p) {
|
|
832
857
|
const ops = [];
|
|
858
|
+
const padding = normalizePadding(p.padding);
|
|
859
|
+
const borderWidth = p.background?.border?.width ?? 0;
|
|
833
860
|
ops.push({
|
|
834
861
|
op: "BeginFrame",
|
|
835
862
|
width: p.canvas.width,
|
|
836
863
|
height: p.canvas.height,
|
|
837
864
|
pixelRatio: p.canvas.pixelRatio,
|
|
838
865
|
clear: true,
|
|
839
|
-
bg:
|
|
866
|
+
bg: void 0
|
|
867
|
+
// Background will be drawn as a separate layer with proper padding/border
|
|
840
868
|
});
|
|
841
869
|
if (p.lines.length === 0) return ops;
|
|
842
870
|
const upem = Math.max(1, await p.getUnitsPerEm());
|
|
@@ -846,33 +874,34 @@ async function buildDrawOps(p) {
|
|
|
846
874
|
let blockY;
|
|
847
875
|
switch (p.align.vertical) {
|
|
848
876
|
case "top":
|
|
849
|
-
blockY = p.font.size;
|
|
877
|
+
blockY = p.font.size + padding.top;
|
|
850
878
|
break;
|
|
851
879
|
case "bottom":
|
|
852
|
-
blockY = p.textRect.height - (numLines - 1) * lineHeightPx;
|
|
880
|
+
blockY = p.textRect.height - (numLines - 1) * lineHeightPx + padding.top;
|
|
853
881
|
break;
|
|
854
882
|
case "middle":
|
|
855
883
|
default:
|
|
856
884
|
const capHeightRatio = 0.35;
|
|
857
885
|
const visualOffset = p.font.size * capHeightRatio;
|
|
858
|
-
blockY = (p.textRect.height - (numLines - 1) * lineHeightPx) / 2 + visualOffset;
|
|
886
|
+
blockY = (p.textRect.height - (numLines - 1) * lineHeightPx) / 2 + visualOffset + padding.top;
|
|
859
887
|
break;
|
|
860
888
|
}
|
|
861
889
|
const fill = p.style.gradient ? gradientSpecFrom(p.style.gradient, 1) : { kind: "solid", color: p.font.color, opacity: p.font.opacity };
|
|
862
890
|
const decoColor = p.style.gradient ? p.style.gradient.stops[p.style.gradient.stops.length - 1]?.color ?? p.font.color : p.font.color;
|
|
891
|
+
const textOps = [];
|
|
863
892
|
let gMinX = Infinity, gMinY = Infinity, gMaxX = -Infinity, gMaxY = -Infinity;
|
|
864
893
|
for (const line of p.lines) {
|
|
865
894
|
let lineX;
|
|
866
895
|
switch (p.align.horizontal) {
|
|
867
896
|
case "left":
|
|
868
|
-
lineX =
|
|
897
|
+
lineX = padding.left;
|
|
869
898
|
break;
|
|
870
899
|
case "right":
|
|
871
|
-
lineX = p.textRect.width - line.width;
|
|
900
|
+
lineX = p.textRect.width - line.width + padding.left;
|
|
872
901
|
break;
|
|
873
902
|
case "center":
|
|
874
903
|
default:
|
|
875
|
-
lineX = (p.textRect.width - line.width) / 2;
|
|
904
|
+
lineX = (p.textRect.width - line.width) / 2 + padding.left;
|
|
876
905
|
break;
|
|
877
906
|
}
|
|
878
907
|
let xCursor = lineX;
|
|
@@ -896,7 +925,7 @@ async function buildDrawOps(p) {
|
|
|
896
925
|
if (x2 > gMaxX) gMaxX = x2;
|
|
897
926
|
if (y2 > gMaxY) gMaxY = y2;
|
|
898
927
|
if (p.shadow && p.shadow.blur > 0) {
|
|
899
|
-
|
|
928
|
+
textOps.push({
|
|
900
929
|
isShadow: true,
|
|
901
930
|
op: "FillPath",
|
|
902
931
|
path,
|
|
@@ -907,7 +936,7 @@ async function buildDrawOps(p) {
|
|
|
907
936
|
});
|
|
908
937
|
}
|
|
909
938
|
if (p.stroke && p.stroke.width > 0) {
|
|
910
|
-
|
|
939
|
+
textOps.push({
|
|
911
940
|
op: "StrokePath",
|
|
912
941
|
path,
|
|
913
942
|
x: glyphX,
|
|
@@ -918,7 +947,7 @@ async function buildDrawOps(p) {
|
|
|
918
947
|
opacity: p.stroke.opacity
|
|
919
948
|
});
|
|
920
949
|
}
|
|
921
|
-
|
|
950
|
+
textOps.push({
|
|
922
951
|
op: "FillPath",
|
|
923
952
|
path,
|
|
924
953
|
x: glyphX,
|
|
@@ -935,7 +964,7 @@ async function buildDrawOps(p) {
|
|
|
935
964
|
lineWidth: line.width,
|
|
936
965
|
xStart: lineX
|
|
937
966
|
});
|
|
938
|
-
|
|
967
|
+
textOps.push({
|
|
939
968
|
op: "DecorationLine",
|
|
940
969
|
from: { x: deco.x1, y: deco.y },
|
|
941
970
|
to: { x: deco.x2, y: deco.y },
|
|
@@ -947,12 +976,46 @@ async function buildDrawOps(p) {
|
|
|
947
976
|
}
|
|
948
977
|
if (gMinX !== Infinity) {
|
|
949
978
|
const gbox = { x: gMinX, y: gMinY, w: Math.max(1, gMaxX - gMinX), h: Math.max(1, gMaxY - gMinY) };
|
|
950
|
-
for (const op of
|
|
979
|
+
for (const op of textOps) {
|
|
951
980
|
if (op.op === "FillPath" && !op.isShadow) {
|
|
952
981
|
op.gradientBBox = gbox;
|
|
953
982
|
}
|
|
954
983
|
}
|
|
955
984
|
}
|
|
985
|
+
if (p.background && (p.background.color || p.background.border)) {
|
|
986
|
+
const bgX = 0;
|
|
987
|
+
const bgY = 0;
|
|
988
|
+
const bgWidth = p.canvas.width;
|
|
989
|
+
const bgHeight = p.canvas.height;
|
|
990
|
+
if (p.background.color) {
|
|
991
|
+
ops.push({
|
|
992
|
+
op: "Rectangle",
|
|
993
|
+
x: bgX,
|
|
994
|
+
y: bgY,
|
|
995
|
+
width: bgWidth,
|
|
996
|
+
height: bgHeight,
|
|
997
|
+
fill: { kind: "solid", color: p.background.color, opacity: p.background.opacity },
|
|
998
|
+
borderRadius: p.background.borderRadius
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
if (p.background.border && p.background.border.width > 0) {
|
|
1002
|
+
const halfBorder = p.background.border.width / 2;
|
|
1003
|
+
ops.push({
|
|
1004
|
+
op: "RectangleStroke",
|
|
1005
|
+
x: bgX + halfBorder,
|
|
1006
|
+
y: bgY + halfBorder,
|
|
1007
|
+
width: bgWidth - p.background.border.width,
|
|
1008
|
+
height: bgHeight - p.background.border.width,
|
|
1009
|
+
stroke: {
|
|
1010
|
+
width: p.background.border.width,
|
|
1011
|
+
color: p.background.border.color,
|
|
1012
|
+
opacity: p.background.border.opacity
|
|
1013
|
+
},
|
|
1014
|
+
borderRadius: Math.max(0, p.background.borderRadius - halfBorder)
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
ops.push(...textOps);
|
|
956
1019
|
return ops;
|
|
957
1020
|
}
|
|
958
1021
|
function tokenizePath(d) {
|
|
@@ -1762,6 +1825,44 @@ async function createNodePainter(opts) {
|
|
|
1762
1825
|
});
|
|
1763
1826
|
continue;
|
|
1764
1827
|
}
|
|
1828
|
+
if (op.op === "Rectangle") {
|
|
1829
|
+
renderToBoth((context) => {
|
|
1830
|
+
context.save();
|
|
1831
|
+
const fill = makeGradientFromBBox(context, op.fill, {
|
|
1832
|
+
x: op.x,
|
|
1833
|
+
y: op.y,
|
|
1834
|
+
w: op.width,
|
|
1835
|
+
h: op.height
|
|
1836
|
+
});
|
|
1837
|
+
context.fillStyle = fill;
|
|
1838
|
+
if (op.borderRadius && op.borderRadius > 0) {
|
|
1839
|
+
context.beginPath();
|
|
1840
|
+
roundRectPath(context, op.x, op.y, op.width, op.height, op.borderRadius);
|
|
1841
|
+
context.fill();
|
|
1842
|
+
} else {
|
|
1843
|
+
context.fillRect(op.x, op.y, op.width, op.height);
|
|
1844
|
+
}
|
|
1845
|
+
context.restore();
|
|
1846
|
+
});
|
|
1847
|
+
continue;
|
|
1848
|
+
}
|
|
1849
|
+
if (op.op === "RectangleStroke") {
|
|
1850
|
+
renderToBoth((context) => {
|
|
1851
|
+
context.save();
|
|
1852
|
+
const c = parseHex6(op.stroke.color, op.stroke.opacity);
|
|
1853
|
+
context.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1854
|
+
context.lineWidth = op.stroke.width;
|
|
1855
|
+
if (op.borderRadius && op.borderRadius > 0) {
|
|
1856
|
+
context.beginPath();
|
|
1857
|
+
roundRectPath(context, op.x, op.y, op.width, op.height, op.borderRadius);
|
|
1858
|
+
context.stroke();
|
|
1859
|
+
} else {
|
|
1860
|
+
context.strokeRect(op.x, op.y, op.width, op.height);
|
|
1861
|
+
}
|
|
1862
|
+
context.restore();
|
|
1863
|
+
});
|
|
1864
|
+
continue;
|
|
1865
|
+
}
|
|
1765
1866
|
}
|
|
1766
1867
|
if (needsAlphaExtraction) {
|
|
1767
1868
|
const whiteData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
@@ -2325,14 +2426,15 @@ async function createTextEngine(opts = {}) {
|
|
|
2325
2426
|
`Failed to layout text: ${err instanceof Error ? err.message : String(err)}`
|
|
2326
2427
|
);
|
|
2327
2428
|
}
|
|
2429
|
+
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 };
|
|
2328
2430
|
const textRect = {
|
|
2329
2431
|
x: 0,
|
|
2330
2432
|
y: 0,
|
|
2331
2433
|
width: asset.width ?? width,
|
|
2332
2434
|
height: asset.height ?? height
|
|
2333
2435
|
};
|
|
2334
|
-
const canvasW = asset.width ?? width;
|
|
2335
|
-
const canvasH = asset.height ?? height;
|
|
2436
|
+
const canvasW = (asset.width ?? width) + padding.left + padding.right;
|
|
2437
|
+
const canvasH = (asset.height ?? height) + padding.top + padding.bottom;
|
|
2336
2438
|
const canvasPR = asset.pixelRatio ?? pixelRatio;
|
|
2337
2439
|
let ops0;
|
|
2338
2440
|
try {
|
|
@@ -2359,6 +2461,7 @@ async function createTextEngine(opts = {}) {
|
|
|
2359
2461
|
vertical: asset.align?.vertical ?? "middle"
|
|
2360
2462
|
},
|
|
2361
2463
|
background: asset.background,
|
|
2464
|
+
padding: asset.padding,
|
|
2362
2465
|
glyphPathProvider: (gid, fontDesc) => fonts.glyphPath(fontDesc || desc, gid),
|
|
2363
2466
|
getUnitsPerEm: (fontDesc) => fonts.getUnitsPerEm(fontDesc || desc)
|
|
2364
2467
|
});
|
package/dist/entry.node.d.cts
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;
|
package/dist/entry.node.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;
|
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(),
|
|
@@ -789,15 +805,27 @@ function decorationGeometry(kind, p) {
|
|
|
789
805
|
}
|
|
790
806
|
|
|
791
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
|
+
}
|
|
792
817
|
async function buildDrawOps(p) {
|
|
793
818
|
const ops = [];
|
|
819
|
+
const padding = normalizePadding(p.padding);
|
|
820
|
+
const borderWidth = p.background?.border?.width ?? 0;
|
|
794
821
|
ops.push({
|
|
795
822
|
op: "BeginFrame",
|
|
796
823
|
width: p.canvas.width,
|
|
797
824
|
height: p.canvas.height,
|
|
798
825
|
pixelRatio: p.canvas.pixelRatio,
|
|
799
826
|
clear: true,
|
|
800
|
-
bg:
|
|
827
|
+
bg: void 0
|
|
828
|
+
// Background will be drawn as a separate layer with proper padding/border
|
|
801
829
|
});
|
|
802
830
|
if (p.lines.length === 0) return ops;
|
|
803
831
|
const upem = Math.max(1, await p.getUnitsPerEm());
|
|
@@ -807,33 +835,34 @@ async function buildDrawOps(p) {
|
|
|
807
835
|
let blockY;
|
|
808
836
|
switch (p.align.vertical) {
|
|
809
837
|
case "top":
|
|
810
|
-
blockY = p.font.size;
|
|
838
|
+
blockY = p.font.size + padding.top;
|
|
811
839
|
break;
|
|
812
840
|
case "bottom":
|
|
813
|
-
blockY = p.textRect.height - (numLines - 1) * lineHeightPx;
|
|
841
|
+
blockY = p.textRect.height - (numLines - 1) * lineHeightPx + padding.top;
|
|
814
842
|
break;
|
|
815
843
|
case "middle":
|
|
816
844
|
default:
|
|
817
845
|
const capHeightRatio = 0.35;
|
|
818
846
|
const visualOffset = p.font.size * capHeightRatio;
|
|
819
|
-
blockY = (p.textRect.height - (numLines - 1) * lineHeightPx) / 2 + visualOffset;
|
|
847
|
+
blockY = (p.textRect.height - (numLines - 1) * lineHeightPx) / 2 + visualOffset + padding.top;
|
|
820
848
|
break;
|
|
821
849
|
}
|
|
822
850
|
const fill = p.style.gradient ? gradientSpecFrom(p.style.gradient, 1) : { kind: "solid", color: p.font.color, opacity: p.font.opacity };
|
|
823
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 = [];
|
|
824
853
|
let gMinX = Infinity, gMinY = Infinity, gMaxX = -Infinity, gMaxY = -Infinity;
|
|
825
854
|
for (const line of p.lines) {
|
|
826
855
|
let lineX;
|
|
827
856
|
switch (p.align.horizontal) {
|
|
828
857
|
case "left":
|
|
829
|
-
lineX =
|
|
858
|
+
lineX = padding.left;
|
|
830
859
|
break;
|
|
831
860
|
case "right":
|
|
832
|
-
lineX = p.textRect.width - line.width;
|
|
861
|
+
lineX = p.textRect.width - line.width + padding.left;
|
|
833
862
|
break;
|
|
834
863
|
case "center":
|
|
835
864
|
default:
|
|
836
|
-
lineX = (p.textRect.width - line.width) / 2;
|
|
865
|
+
lineX = (p.textRect.width - line.width) / 2 + padding.left;
|
|
837
866
|
break;
|
|
838
867
|
}
|
|
839
868
|
let xCursor = lineX;
|
|
@@ -857,7 +886,7 @@ async function buildDrawOps(p) {
|
|
|
857
886
|
if (x2 > gMaxX) gMaxX = x2;
|
|
858
887
|
if (y2 > gMaxY) gMaxY = y2;
|
|
859
888
|
if (p.shadow && p.shadow.blur > 0) {
|
|
860
|
-
|
|
889
|
+
textOps.push({
|
|
861
890
|
isShadow: true,
|
|
862
891
|
op: "FillPath",
|
|
863
892
|
path,
|
|
@@ -868,7 +897,7 @@ async function buildDrawOps(p) {
|
|
|
868
897
|
});
|
|
869
898
|
}
|
|
870
899
|
if (p.stroke && p.stroke.width > 0) {
|
|
871
|
-
|
|
900
|
+
textOps.push({
|
|
872
901
|
op: "StrokePath",
|
|
873
902
|
path,
|
|
874
903
|
x: glyphX,
|
|
@@ -879,7 +908,7 @@ async function buildDrawOps(p) {
|
|
|
879
908
|
opacity: p.stroke.opacity
|
|
880
909
|
});
|
|
881
910
|
}
|
|
882
|
-
|
|
911
|
+
textOps.push({
|
|
883
912
|
op: "FillPath",
|
|
884
913
|
path,
|
|
885
914
|
x: glyphX,
|
|
@@ -896,7 +925,7 @@ async function buildDrawOps(p) {
|
|
|
896
925
|
lineWidth: line.width,
|
|
897
926
|
xStart: lineX
|
|
898
927
|
});
|
|
899
|
-
|
|
928
|
+
textOps.push({
|
|
900
929
|
op: "DecorationLine",
|
|
901
930
|
from: { x: deco.x1, y: deco.y },
|
|
902
931
|
to: { x: deco.x2, y: deco.y },
|
|
@@ -908,12 +937,46 @@ async function buildDrawOps(p) {
|
|
|
908
937
|
}
|
|
909
938
|
if (gMinX !== Infinity) {
|
|
910
939
|
const gbox = { x: gMinX, y: gMinY, w: Math.max(1, gMaxX - gMinX), h: Math.max(1, gMaxY - gMinY) };
|
|
911
|
-
for (const op of
|
|
940
|
+
for (const op of textOps) {
|
|
912
941
|
if (op.op === "FillPath" && !op.isShadow) {
|
|
913
942
|
op.gradientBBox = gbox;
|
|
914
943
|
}
|
|
915
944
|
}
|
|
916
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);
|
|
917
980
|
return ops;
|
|
918
981
|
}
|
|
919
982
|
function tokenizePath(d) {
|
|
@@ -1723,6 +1786,44 @@ async function createNodePainter(opts) {
|
|
|
1723
1786
|
});
|
|
1724
1787
|
continue;
|
|
1725
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
|
+
}
|
|
1726
1827
|
}
|
|
1727
1828
|
if (needsAlphaExtraction) {
|
|
1728
1829
|
const whiteData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
@@ -2286,14 +2387,15 @@ async function createTextEngine(opts = {}) {
|
|
|
2286
2387
|
`Failed to layout text: ${err instanceof Error ? err.message : String(err)}`
|
|
2287
2388
|
);
|
|
2288
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 };
|
|
2289
2391
|
const textRect = {
|
|
2290
2392
|
x: 0,
|
|
2291
2393
|
y: 0,
|
|
2292
2394
|
width: asset.width ?? width,
|
|
2293
2395
|
height: asset.height ?? height
|
|
2294
2396
|
};
|
|
2295
|
-
const canvasW = asset.width ?? width;
|
|
2296
|
-
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;
|
|
2297
2399
|
const canvasPR = asset.pixelRatio ?? pixelRatio;
|
|
2298
2400
|
let ops0;
|
|
2299
2401
|
try {
|
|
@@ -2320,6 +2422,7 @@ async function createTextEngine(opts = {}) {
|
|
|
2320
2422
|
vertical: asset.align?.vertical ?? "middle"
|
|
2321
2423
|
},
|
|
2322
2424
|
background: asset.background,
|
|
2425
|
+
padding: asset.padding,
|
|
2323
2426
|
glyphPathProvider: (gid, fontDesc) => fonts.glyphPath(fontDesc || desc, gid),
|
|
2324
2427
|
getUnitsPerEm: (fontDesc) => fonts.getUnitsPerEm(fontDesc || desc)
|
|
2325
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;
|
package/dist/entry.web.js
CHANGED
|
@@ -98,11 +98,26 @@ var animationSchema = Joi.object({
|
|
|
98
98
|
otherwise: Joi.forbidden()
|
|
99
99
|
})
|
|
100
100
|
}).unknown(false);
|
|
101
|
+
var borderSchema = Joi.object({
|
|
102
|
+
width: Joi.number().min(0).default(0),
|
|
103
|
+
color: Joi.string().pattern(HEX6).default("#000000"),
|
|
104
|
+
opacity: Joi.number().min(0).max(1).default(1)
|
|
105
|
+
}).unknown(false);
|
|
101
106
|
var backgroundSchema = Joi.object({
|
|
102
107
|
color: Joi.string().pattern(HEX6).optional(),
|
|
103
108
|
opacity: Joi.number().min(0).max(1).default(1),
|
|
104
|
-
borderRadius: Joi.number().min(0).default(0)
|
|
109
|
+
borderRadius: Joi.number().min(0).default(0),
|
|
110
|
+
border: borderSchema.optional()
|
|
105
111
|
}).unknown(false);
|
|
112
|
+
var paddingSchema = Joi.alternatives().try(
|
|
113
|
+
Joi.number().min(0).default(0),
|
|
114
|
+
Joi.object({
|
|
115
|
+
top: Joi.number().min(0).default(0),
|
|
116
|
+
right: Joi.number().min(0).default(0),
|
|
117
|
+
bottom: Joi.number().min(0).default(0),
|
|
118
|
+
left: Joi.number().min(0).default(0)
|
|
119
|
+
}).unknown(false)
|
|
120
|
+
);
|
|
106
121
|
var customFontSchema = Joi.object({
|
|
107
122
|
src: Joi.string().uri().required(),
|
|
108
123
|
family: Joi.string().required(),
|
|
@@ -120,6 +135,7 @@ var RichTextAssetSchema = Joi.object({
|
|
|
120
135
|
stroke: strokeSchema.optional(),
|
|
121
136
|
shadow: shadowSchema.optional(),
|
|
122
137
|
background: backgroundSchema.optional(),
|
|
138
|
+
padding: paddingSchema.optional(),
|
|
123
139
|
align: alignmentSchema.optional(),
|
|
124
140
|
animation: animationSchema.optional(),
|
|
125
141
|
customFonts: Joi.array().items(customFontSchema).optional(),
|
|
@@ -794,15 +810,27 @@ function decorationGeometry(kind, p) {
|
|
|
794
810
|
}
|
|
795
811
|
|
|
796
812
|
// src/core/drawops.ts
|
|
813
|
+
function normalizePadding(padding) {
|
|
814
|
+
if (padding === void 0 || padding === null) {
|
|
815
|
+
return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
816
|
+
}
|
|
817
|
+
if (typeof padding === "number") {
|
|
818
|
+
return { top: padding, right: padding, bottom: padding, left: padding };
|
|
819
|
+
}
|
|
820
|
+
return padding;
|
|
821
|
+
}
|
|
797
822
|
async function buildDrawOps(p) {
|
|
798
823
|
const ops = [];
|
|
824
|
+
const padding = normalizePadding(p.padding);
|
|
825
|
+
const borderWidth = p.background?.border?.width ?? 0;
|
|
799
826
|
ops.push({
|
|
800
827
|
op: "BeginFrame",
|
|
801
828
|
width: p.canvas.width,
|
|
802
829
|
height: p.canvas.height,
|
|
803
830
|
pixelRatio: p.canvas.pixelRatio,
|
|
804
831
|
clear: true,
|
|
805
|
-
bg:
|
|
832
|
+
bg: void 0
|
|
833
|
+
// Background will be drawn as a separate layer with proper padding/border
|
|
806
834
|
});
|
|
807
835
|
if (p.lines.length === 0) return ops;
|
|
808
836
|
const upem = Math.max(1, await p.getUnitsPerEm());
|
|
@@ -812,33 +840,34 @@ async function buildDrawOps(p) {
|
|
|
812
840
|
let blockY;
|
|
813
841
|
switch (p.align.vertical) {
|
|
814
842
|
case "top":
|
|
815
|
-
blockY = p.font.size;
|
|
843
|
+
blockY = p.font.size + padding.top;
|
|
816
844
|
break;
|
|
817
845
|
case "bottom":
|
|
818
|
-
blockY = p.textRect.height - (numLines - 1) * lineHeightPx;
|
|
846
|
+
blockY = p.textRect.height - (numLines - 1) * lineHeightPx + padding.top;
|
|
819
847
|
break;
|
|
820
848
|
case "middle":
|
|
821
849
|
default:
|
|
822
850
|
const capHeightRatio = 0.35;
|
|
823
851
|
const visualOffset = p.font.size * capHeightRatio;
|
|
824
|
-
blockY = (p.textRect.height - (numLines - 1) * lineHeightPx) / 2 + visualOffset;
|
|
852
|
+
blockY = (p.textRect.height - (numLines - 1) * lineHeightPx) / 2 + visualOffset + padding.top;
|
|
825
853
|
break;
|
|
826
854
|
}
|
|
827
855
|
const fill = p.style.gradient ? gradientSpecFrom(p.style.gradient, 1) : { kind: "solid", color: p.font.color, opacity: p.font.opacity };
|
|
828
856
|
const decoColor = p.style.gradient ? p.style.gradient.stops[p.style.gradient.stops.length - 1]?.color ?? p.font.color : p.font.color;
|
|
857
|
+
const textOps = [];
|
|
829
858
|
let gMinX = Infinity, gMinY = Infinity, gMaxX = -Infinity, gMaxY = -Infinity;
|
|
830
859
|
for (const line of p.lines) {
|
|
831
860
|
let lineX;
|
|
832
861
|
switch (p.align.horizontal) {
|
|
833
862
|
case "left":
|
|
834
|
-
lineX =
|
|
863
|
+
lineX = padding.left;
|
|
835
864
|
break;
|
|
836
865
|
case "right":
|
|
837
|
-
lineX = p.textRect.width - line.width;
|
|
866
|
+
lineX = p.textRect.width - line.width + padding.left;
|
|
838
867
|
break;
|
|
839
868
|
case "center":
|
|
840
869
|
default:
|
|
841
|
-
lineX = (p.textRect.width - line.width) / 2;
|
|
870
|
+
lineX = (p.textRect.width - line.width) / 2 + padding.left;
|
|
842
871
|
break;
|
|
843
872
|
}
|
|
844
873
|
let xCursor = lineX;
|
|
@@ -862,7 +891,7 @@ async function buildDrawOps(p) {
|
|
|
862
891
|
if (x2 > gMaxX) gMaxX = x2;
|
|
863
892
|
if (y2 > gMaxY) gMaxY = y2;
|
|
864
893
|
if (p.shadow && p.shadow.blur > 0) {
|
|
865
|
-
|
|
894
|
+
textOps.push({
|
|
866
895
|
isShadow: true,
|
|
867
896
|
op: "FillPath",
|
|
868
897
|
path,
|
|
@@ -873,7 +902,7 @@ async function buildDrawOps(p) {
|
|
|
873
902
|
});
|
|
874
903
|
}
|
|
875
904
|
if (p.stroke && p.stroke.width > 0) {
|
|
876
|
-
|
|
905
|
+
textOps.push({
|
|
877
906
|
op: "StrokePath",
|
|
878
907
|
path,
|
|
879
908
|
x: glyphX,
|
|
@@ -884,7 +913,7 @@ async function buildDrawOps(p) {
|
|
|
884
913
|
opacity: p.stroke.opacity
|
|
885
914
|
});
|
|
886
915
|
}
|
|
887
|
-
|
|
916
|
+
textOps.push({
|
|
888
917
|
op: "FillPath",
|
|
889
918
|
path,
|
|
890
919
|
x: glyphX,
|
|
@@ -901,7 +930,7 @@ async function buildDrawOps(p) {
|
|
|
901
930
|
lineWidth: line.width,
|
|
902
931
|
xStart: lineX
|
|
903
932
|
});
|
|
904
|
-
|
|
933
|
+
textOps.push({
|
|
905
934
|
op: "DecorationLine",
|
|
906
935
|
from: { x: deco.x1, y: deco.y },
|
|
907
936
|
to: { x: deco.x2, y: deco.y },
|
|
@@ -913,12 +942,46 @@ async function buildDrawOps(p) {
|
|
|
913
942
|
}
|
|
914
943
|
if (gMinX !== Infinity) {
|
|
915
944
|
const gbox = { x: gMinX, y: gMinY, w: Math.max(1, gMaxX - gMinX), h: Math.max(1, gMaxY - gMinY) };
|
|
916
|
-
for (const op of
|
|
945
|
+
for (const op of textOps) {
|
|
917
946
|
if (op.op === "FillPath" && !op.isShadow) {
|
|
918
947
|
op.gradientBBox = gbox;
|
|
919
948
|
}
|
|
920
949
|
}
|
|
921
950
|
}
|
|
951
|
+
if (p.background && (p.background.color || p.background.border)) {
|
|
952
|
+
const bgX = 0;
|
|
953
|
+
const bgY = 0;
|
|
954
|
+
const bgWidth = p.canvas.width;
|
|
955
|
+
const bgHeight = p.canvas.height;
|
|
956
|
+
if (p.background.color) {
|
|
957
|
+
ops.push({
|
|
958
|
+
op: "Rectangle",
|
|
959
|
+
x: bgX,
|
|
960
|
+
y: bgY,
|
|
961
|
+
width: bgWidth,
|
|
962
|
+
height: bgHeight,
|
|
963
|
+
fill: { kind: "solid", color: p.background.color, opacity: p.background.opacity },
|
|
964
|
+
borderRadius: p.background.borderRadius
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
if (p.background.border && p.background.border.width > 0) {
|
|
968
|
+
const halfBorder = p.background.border.width / 2;
|
|
969
|
+
ops.push({
|
|
970
|
+
op: "RectangleStroke",
|
|
971
|
+
x: bgX + halfBorder,
|
|
972
|
+
y: bgY + halfBorder,
|
|
973
|
+
width: bgWidth - p.background.border.width,
|
|
974
|
+
height: bgHeight - p.background.border.width,
|
|
975
|
+
stroke: {
|
|
976
|
+
width: p.background.border.width,
|
|
977
|
+
color: p.background.border.color,
|
|
978
|
+
opacity: p.background.border.opacity
|
|
979
|
+
},
|
|
980
|
+
borderRadius: Math.max(0, p.background.borderRadius - halfBorder)
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
ops.push(...textOps);
|
|
922
985
|
return ops;
|
|
923
986
|
}
|
|
924
987
|
function tokenizePath(d) {
|
|
@@ -1676,6 +1739,48 @@ function createWebPainter(canvas) {
|
|
|
1676
1739
|
ctx.restore();
|
|
1677
1740
|
continue;
|
|
1678
1741
|
}
|
|
1742
|
+
if (op.op === "Rectangle") {
|
|
1743
|
+
ctx.save();
|
|
1744
|
+
const fill = makeGradientFromBBox(ctx, op.fill, {
|
|
1745
|
+
x: op.x,
|
|
1746
|
+
y: op.y,
|
|
1747
|
+
w: op.width,
|
|
1748
|
+
h: op.height
|
|
1749
|
+
});
|
|
1750
|
+
ctx.fillStyle = fill;
|
|
1751
|
+
if (op.borderRadius && op.borderRadius > 0) {
|
|
1752
|
+
drawRoundedRect(ctx, op.x, op.y, op.width, op.height, op.borderRadius);
|
|
1753
|
+
} else {
|
|
1754
|
+
ctx.fillRect(op.x, op.y, op.width, op.height);
|
|
1755
|
+
}
|
|
1756
|
+
ctx.restore();
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
1759
|
+
if (op.op === "RectangleStroke") {
|
|
1760
|
+
ctx.save();
|
|
1761
|
+
const c = parseHex6(op.stroke.color, op.stroke.opacity);
|
|
1762
|
+
ctx.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1763
|
+
ctx.lineWidth = op.stroke.width;
|
|
1764
|
+
if (op.borderRadius && op.borderRadius > 0) {
|
|
1765
|
+
const p = new Path2D();
|
|
1766
|
+
const r = op.borderRadius;
|
|
1767
|
+
const x = op.x;
|
|
1768
|
+
const y = op.y;
|
|
1769
|
+
const w = op.width;
|
|
1770
|
+
const h = op.height;
|
|
1771
|
+
p.moveTo(x + r, y);
|
|
1772
|
+
p.arcTo(x + w, y, x + w, y + h, r);
|
|
1773
|
+
p.arcTo(x + w, y + h, x, y + h, r);
|
|
1774
|
+
p.arcTo(x, y + h, x, y, r);
|
|
1775
|
+
p.arcTo(x, y, x + w, y, r);
|
|
1776
|
+
p.closePath();
|
|
1777
|
+
ctx.stroke(p);
|
|
1778
|
+
} else {
|
|
1779
|
+
ctx.strokeRect(op.x, op.y, op.width, op.height);
|
|
1780
|
+
}
|
|
1781
|
+
ctx.restore();
|
|
1782
|
+
continue;
|
|
1783
|
+
}
|
|
1679
1784
|
}
|
|
1680
1785
|
}
|
|
1681
1786
|
};
|
|
@@ -2005,14 +2110,15 @@ async function createTextEngine(opts = {}) {
|
|
|
2005
2110
|
`Failed to layout text: ${err instanceof Error ? err.message : String(err)}`
|
|
2006
2111
|
);
|
|
2007
2112
|
}
|
|
2113
|
+
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 };
|
|
2008
2114
|
const textRect = {
|
|
2009
2115
|
x: 0,
|
|
2010
2116
|
y: 0,
|
|
2011
2117
|
width: asset.width ?? width,
|
|
2012
2118
|
height: asset.height ?? height
|
|
2013
2119
|
};
|
|
2014
|
-
const canvasW = asset.width ?? width;
|
|
2015
|
-
const canvasH = asset.height ?? height;
|
|
2120
|
+
const canvasW = (asset.width ?? width) + padding.left + padding.right;
|
|
2121
|
+
const canvasH = (asset.height ?? height) + padding.top + padding.bottom;
|
|
2016
2122
|
const canvasPR = asset.pixelRatio ?? pixelRatio;
|
|
2017
2123
|
let ops0;
|
|
2018
2124
|
try {
|
|
@@ -2039,6 +2145,7 @@ async function createTextEngine(opts = {}) {
|
|
|
2039
2145
|
vertical: asset.align?.vertical ?? "middle"
|
|
2040
2146
|
},
|
|
2041
2147
|
background: asset.background,
|
|
2148
|
+
padding: asset.padding,
|
|
2042
2149
|
glyphPathProvider: (gid, fontDesc) => fonts.glyphPath(fontDesc || desc, gid),
|
|
2043
2150
|
getUnitsPerEm: (fontDesc) => fonts.getUnitsPerEm(fontDesc || desc)
|
|
2044
2151
|
});
|
package/package.json
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@shotstack/shotstack-canvas",
|
|
3
|
-
"version": "1.
|
|
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
|
-
"canvas": "npm:@napi-rs/canvas@^0.1.54",
|
|
46
|
-
"ffmpeg-static": "^5.2.0",
|
|
47
|
-
"fontkit": "^2.0.4",
|
|
48
|
-
"harfbuzzjs": "0.4.12",
|
|
49
|
-
"joi": "^17.13.3",
|
|
50
|
-
"opentype.js": "^1.3.4"
|
|
51
|
-
},
|
|
52
|
-
"devDependencies": {
|
|
53
|
-
"@types/fluent-ffmpeg": "2.1.27",
|
|
54
|
-
"@types/node": "^20.14.10",
|
|
55
|
-
"fluent-ffmpeg": "^2.1.3",
|
|
56
|
-
"tsup": "^8.2.3",
|
|
57
|
-
"typescript": "^5.5.3",
|
|
58
|
-
"vite": "^5.3.3",
|
|
59
|
-
"vite-plugin-top-level-await": "1.6.0",
|
|
60
|
-
"vite-plugin-wasm": "3.5.0"
|
|
61
|
-
}
|
|
62
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@shotstack/shotstack-canvas",
|
|
3
|
+
"version": "1.5.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
|
+
"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
|
+
"canvas": "npm:@napi-rs/canvas@^0.1.54",
|
|
46
|
+
"ffmpeg-static": "^5.2.0",
|
|
47
|
+
"fontkit": "^2.0.4",
|
|
48
|
+
"harfbuzzjs": "0.4.12",
|
|
49
|
+
"joi": "^17.13.3",
|
|
50
|
+
"opentype.js": "^1.3.4"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/fluent-ffmpeg": "2.1.27",
|
|
54
|
+
"@types/node": "^20.14.10",
|
|
55
|
+
"fluent-ffmpeg": "^2.1.3",
|
|
56
|
+
"tsup": "^8.2.3",
|
|
57
|
+
"typescript": "^5.5.3",
|
|
58
|
+
"vite": "^5.3.3",
|
|
59
|
+
"vite-plugin-top-level-await": "1.6.0",
|
|
60
|
+
"vite-plugin-wasm": "3.5.0"
|
|
61
|
+
}
|
|
62
|
+
}
|