@shotstack/shotstack-canvas 1.0.2 → 1.0.3
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 +1542 -0
- package/dist/entry.node.cjs.map +1 -0
- package/dist/entry.node.d.cts +229 -0
- package/dist/entry.node.d.ts +229 -0
- package/dist/entry.node.js +1505 -0
- package/dist/entry.node.js.map +1 -0
- package/dist/entry.web.d.ts +207 -0
- package/dist/entry.web.js +1285 -0
- package/dist/entry.web.js.map +1 -0
- package/package.json +22 -3
- package/.eslintrc.cjs +0 -7
- package/.prettierrc +0 -1
- package/pnpm-workspace.yaml +0 -3
- package/scripts/vendor-harfbuzz.js +0 -64
- package/src/config/canvas-constants.ts +0 -30
- package/src/core/animations.ts +0 -570
- package/src/core/colors.ts +0 -11
- package/src/core/decoration.ts +0 -9
- package/src/core/drawops.ts +0 -206
- package/src/core/font-registry.ts +0 -77
- package/src/core/gradients.ts +0 -12
- package/src/core/layout.ts +0 -184
- package/src/core/utils.ts +0 -3
- package/src/core/video-generator.ts +0 -157
- package/src/env/entry.node.ts +0 -167
- package/src/env/entry.web.ts +0 -146
- package/src/index.ts +0 -1
- package/src/io/node.ts +0 -45
- package/src/io/web.ts +0 -5
- package/src/painters/node.ts +0 -290
- package/src/painters/web.ts +0 -224
- package/src/schema/asset-schema.ts +0 -166
- package/src/types.ts +0 -36
- package/src/wasm/hb-loader.ts +0 -31
- package/tsconfig.base.json +0 -22
- package/tsconfig.json +0 -16
- package/tsup.config.ts +0 -52
package/src/painters/web.ts
DELETED
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
import type { DrawOp, GradientSpec } from "../types";
|
|
2
|
-
import { parseHex6 } from "../core/colors";
|
|
3
|
-
|
|
4
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
5
|
-
|
|
6
|
-
export function createWebPainter(canvas: HTMLCanvasElement | OffscreenCanvas) {
|
|
7
|
-
// @ts-ignore
|
|
8
|
-
const ctx: CanvasRenderingContext2D = (canvas as any).getContext("2d");
|
|
9
|
-
if (!ctx) throw new Error("2D context unavailable");
|
|
10
|
-
|
|
11
|
-
return {
|
|
12
|
-
async render(ops: DrawOp[]) {
|
|
13
|
-
const globalBox = computeGlobalTextBounds(ops);
|
|
14
|
-
|
|
15
|
-
for (const op of ops) {
|
|
16
|
-
if (op.op === "BeginFrame") {
|
|
17
|
-
const dpr = op.pixelRatio;
|
|
18
|
-
const w = op.width,
|
|
19
|
-
h = op.height;
|
|
20
|
-
|
|
21
|
-
if ("width" in canvas && "height" in canvas) {
|
|
22
|
-
(canvas as any).width = Math.floor(w * dpr);
|
|
23
|
-
(canvas as any).height = Math.floor(h * dpr);
|
|
24
|
-
}
|
|
25
|
-
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
26
|
-
|
|
27
|
-
if (op.clear) ctx.clearRect(0, 0, w, h);
|
|
28
|
-
if (op.bg) {
|
|
29
|
-
const { color, opacity, radius } = op.bg;
|
|
30
|
-
if (color) {
|
|
31
|
-
const c = parseHex6(color, opacity);
|
|
32
|
-
ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
33
|
-
if (radius && radius > 0) {
|
|
34
|
-
drawRoundedRect(ctx, 0, 0, w, h, radius);
|
|
35
|
-
ctx.fill();
|
|
36
|
-
} else {
|
|
37
|
-
ctx.fillRect(0, 0, w, h);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (op.op === "FillPath") {
|
|
45
|
-
const p = new Path2D(op.path);
|
|
46
|
-
ctx.save();
|
|
47
|
-
ctx.translate(op.x, op.y);
|
|
48
|
-
|
|
49
|
-
const s = (op as any).scale ?? 1;
|
|
50
|
-
ctx.scale(s, -s);
|
|
51
|
-
|
|
52
|
-
const bbox = (op as any).gradientBBox ?? globalBox;
|
|
53
|
-
const fill = makeGradientFromBBox(ctx, op.fill, bbox);
|
|
54
|
-
ctx.fillStyle = fill as any;
|
|
55
|
-
ctx.fill(p);
|
|
56
|
-
ctx.restore();
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (op.op === "StrokePath") {
|
|
61
|
-
const p = new Path2D(op.path);
|
|
62
|
-
ctx.save();
|
|
63
|
-
ctx.translate(op.x, op.y);
|
|
64
|
-
|
|
65
|
-
const s = (op as any).scale ?? 1;
|
|
66
|
-
ctx.scale(s, -s);
|
|
67
|
-
const invAbs = 1 / Math.abs(s);
|
|
68
|
-
|
|
69
|
-
const c = parseHex6((op as any).color, (op as any).opacity);
|
|
70
|
-
ctx.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
71
|
-
ctx.lineWidth = (op as any).width * invAbs;
|
|
72
|
-
ctx.lineJoin = "round";
|
|
73
|
-
ctx.lineCap = "round";
|
|
74
|
-
ctx.stroke(p);
|
|
75
|
-
ctx.restore();
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (op.op === "DecorationLine") {
|
|
80
|
-
ctx.save();
|
|
81
|
-
const c = parseHex6((op as any).color, (op as any).opacity);
|
|
82
|
-
ctx.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
83
|
-
ctx.lineWidth = (op as any).width;
|
|
84
|
-
ctx.beginPath();
|
|
85
|
-
ctx.moveTo((op as any).from.x, (op as any).from.y);
|
|
86
|
-
ctx.lineTo((op as any).to.x, (op as any).to.y);
|
|
87
|
-
ctx.stroke();
|
|
88
|
-
ctx.restore();
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function drawRoundedRect(
|
|
97
|
-
ctx: CanvasRenderingContext2D,
|
|
98
|
-
x: number,
|
|
99
|
-
y: number,
|
|
100
|
-
w: number,
|
|
101
|
-
h: number,
|
|
102
|
-
r: number
|
|
103
|
-
) {
|
|
104
|
-
const p = new Path2D();
|
|
105
|
-
p.moveTo(x + r, y);
|
|
106
|
-
p.arcTo(x + w, y, x + w, y + h, r);
|
|
107
|
-
p.arcTo(x + w, y + h, x, y + h, r);
|
|
108
|
-
p.arcTo(x, y + h, x, y, r);
|
|
109
|
-
p.arcTo(x, y, x + w, y, r);
|
|
110
|
-
p.closePath();
|
|
111
|
-
ctx.save();
|
|
112
|
-
ctx.fill(p);
|
|
113
|
-
ctx.restore();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function makeGradientFromBBox(
|
|
117
|
-
ctx: CanvasRenderingContext2D,
|
|
118
|
-
spec: GradientSpec,
|
|
119
|
-
box: { x: number; y: number; w: number; h: number }
|
|
120
|
-
): CanvasGradient | string {
|
|
121
|
-
if (spec.kind === "solid") {
|
|
122
|
-
const c = parseHex6((spec as any).color, (spec as any).opacity);
|
|
123
|
-
return `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
124
|
-
}
|
|
125
|
-
const cx = box.x + box.w / 2,
|
|
126
|
-
cy = box.y + box.h / 2,
|
|
127
|
-
r = Math.max(box.w, box.h) / 2;
|
|
128
|
-
const addStops = (g: CanvasGradient) => {
|
|
129
|
-
const op = (spec as any).opacity ?? 1;
|
|
130
|
-
for (const s of (spec as any).stops) {
|
|
131
|
-
const c = parseHex6(s.color, op);
|
|
132
|
-
g.addColorStop(s.offset, `rgba(${c.r},${c.g},${c.b},${c.a})`);
|
|
133
|
-
}
|
|
134
|
-
return g;
|
|
135
|
-
};
|
|
136
|
-
if (spec.kind === "linear") {
|
|
137
|
-
const rad = (((spec as any).angle || 0) * Math.PI) / 180;
|
|
138
|
-
const x1 = cx + Math.cos(rad + Math.PI) * r;
|
|
139
|
-
const y1 = cy + Math.sin(rad + Math.PI) * r;
|
|
140
|
-
const x2 = cx + Math.cos(rad) * r;
|
|
141
|
-
const y2 = cy + Math.sin(rad) * r;
|
|
142
|
-
return addStops(ctx.createLinearGradient(x1, y1, x2, y2));
|
|
143
|
-
} else {
|
|
144
|
-
return addStops(ctx.createRadialGradient(cx, cy, 0, cx, cy, r));
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function computeGlobalTextBounds(ops: DrawOp[]) {
|
|
149
|
-
let minX = Infinity,
|
|
150
|
-
minY = Infinity,
|
|
151
|
-
maxX = -Infinity,
|
|
152
|
-
maxY = -Infinity;
|
|
153
|
-
for (const op of ops) {
|
|
154
|
-
if (op.op !== "FillPath" || (op as any).isShadow) continue;
|
|
155
|
-
const b = computePathBounds(op.path);
|
|
156
|
-
const s = (op as any).scale ?? 1;
|
|
157
|
-
const x1 = op.x + s * b.x;
|
|
158
|
-
const x2 = op.x + s * (b.x + b.w);
|
|
159
|
-
const y1 = op.y - s * (b.y + b.h);
|
|
160
|
-
const y2 = op.y - s * b.y;
|
|
161
|
-
if (x1 < minX) minX = x1;
|
|
162
|
-
if (y1 < minY) minY = y1;
|
|
163
|
-
if (x2 > maxX) maxX = x2;
|
|
164
|
-
if (y2 > maxY) maxY = y2;
|
|
165
|
-
}
|
|
166
|
-
if (minX === Infinity) return { x: 0, y: 0, w: 1, h: 1 };
|
|
167
|
-
return { x: minX, y: minY, w: Math.max(1, maxX - minX), h: Math.max(1, maxY - minY) };
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function computePathBounds(d: string) {
|
|
171
|
-
const tokens = tokenizePath(d);
|
|
172
|
-
let i = 0;
|
|
173
|
-
let minX = Infinity,
|
|
174
|
-
minY = Infinity,
|
|
175
|
-
maxX = -Infinity,
|
|
176
|
-
maxY = -Infinity;
|
|
177
|
-
const touch = (x: number, y: number) => {
|
|
178
|
-
if (x < minX) minX = x;
|
|
179
|
-
if (y < minY) minY = y;
|
|
180
|
-
if (x > maxX) maxX = x;
|
|
181
|
-
if (y > maxY) maxY = y;
|
|
182
|
-
};
|
|
183
|
-
while (i < tokens.length) {
|
|
184
|
-
const t = tokens[i++];
|
|
185
|
-
switch (t) {
|
|
186
|
-
case "M":
|
|
187
|
-
case "L": {
|
|
188
|
-
const x = parseFloat(tokens[i++]);
|
|
189
|
-
const y = parseFloat(tokens[i++]);
|
|
190
|
-
touch(x, y);
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
193
|
-
case "C": {
|
|
194
|
-
const c1x = parseFloat(tokens[i++]);
|
|
195
|
-
const c1y = parseFloat(tokens[i++]);
|
|
196
|
-
const c2x = parseFloat(tokens[i++]);
|
|
197
|
-
const c2y = parseFloat(tokens[i++]);
|
|
198
|
-
const x = parseFloat(tokens[i++]);
|
|
199
|
-
const y = parseFloat(tokens[i++]);
|
|
200
|
-
touch(c1x, c1y);
|
|
201
|
-
touch(c2x, c2y);
|
|
202
|
-
touch(x, y);
|
|
203
|
-
break;
|
|
204
|
-
}
|
|
205
|
-
case "Q": {
|
|
206
|
-
const cx = parseFloat(tokens[i++]);
|
|
207
|
-
const cy = parseFloat(tokens[i++]);
|
|
208
|
-
const x = parseFloat(tokens[i++]);
|
|
209
|
-
const y = parseFloat(tokens[i++]);
|
|
210
|
-
touch(cx, cy);
|
|
211
|
-
touch(x, y);
|
|
212
|
-
break;
|
|
213
|
-
}
|
|
214
|
-
case "Z":
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
if (minX === Infinity) return { x: 0, y: 0, w: 0, h: 0 };
|
|
219
|
-
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function tokenizePath(d: string): string[] {
|
|
223
|
-
return d.match(/[MLCQZ]|-?\d*\.?\d+(?:e[-+]?\d+)?/gi) ?? [];
|
|
224
|
-
}
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import Joi from "joi";
|
|
2
|
-
import { CANVAS_CONFIG } from "../config/canvas-constants";
|
|
3
|
-
|
|
4
|
-
const HEX6 = /^#[A-Fa-f0-9]{6}$/;
|
|
5
|
-
|
|
6
|
-
const gradientSchema = Joi.object({
|
|
7
|
-
type: Joi.string().valid("linear", "radial").default("linear"),
|
|
8
|
-
angle: Joi.number().min(0).max(360).default(0),
|
|
9
|
-
stops: Joi.array()
|
|
10
|
-
.items(
|
|
11
|
-
Joi.object({
|
|
12
|
-
offset: Joi.number().min(0).max(1).required(),
|
|
13
|
-
color: Joi.string().pattern(HEX6).required()
|
|
14
|
-
}).unknown(false)
|
|
15
|
-
)
|
|
16
|
-
.min(2)
|
|
17
|
-
.required()
|
|
18
|
-
}).unknown(false);
|
|
19
|
-
|
|
20
|
-
const shadowSchema = Joi.object({
|
|
21
|
-
offsetX: Joi.number().default(0),
|
|
22
|
-
offsetY: Joi.number().default(0),
|
|
23
|
-
blur: Joi.number().min(0).default(0),
|
|
24
|
-
color: Joi.string().pattern(HEX6).default("#000000"),
|
|
25
|
-
opacity: Joi.number().min(0).max(1).default(0.5)
|
|
26
|
-
}).unknown(false);
|
|
27
|
-
|
|
28
|
-
const strokeSchema = Joi.object({
|
|
29
|
-
width: Joi.number().min(0).default(0),
|
|
30
|
-
color: Joi.string().pattern(HEX6).default("#000000"),
|
|
31
|
-
opacity: Joi.number().min(0).max(1).default(1)
|
|
32
|
-
}).unknown(false);
|
|
33
|
-
|
|
34
|
-
const fontSchema = Joi.object({
|
|
35
|
-
family: Joi.string().default(CANVAS_CONFIG.DEFAULTS.fontFamily),
|
|
36
|
-
size: Joi.number()
|
|
37
|
-
.min(CANVAS_CONFIG.LIMITS.minFontSize)
|
|
38
|
-
.max(CANVAS_CONFIG.LIMITS.maxFontSize)
|
|
39
|
-
.default(CANVAS_CONFIG.DEFAULTS.fontSize),
|
|
40
|
-
weight: Joi.alternatives().try(Joi.string(), Joi.number()).default("400"),
|
|
41
|
-
style: Joi.string().valid("normal", "italic", "oblique").default("normal"),
|
|
42
|
-
color: Joi.string().pattern(HEX6).default(CANVAS_CONFIG.DEFAULTS.color),
|
|
43
|
-
opacity: Joi.number().min(0).max(1).default(1)
|
|
44
|
-
}).unknown(false);
|
|
45
|
-
|
|
46
|
-
const styleSchema = Joi.object({
|
|
47
|
-
letterSpacing: Joi.number().default(0),
|
|
48
|
-
lineHeight: Joi.number().min(0).max(10).default(1.2),
|
|
49
|
-
textTransform: Joi.string().valid("none", "uppercase", "lowercase", "capitalize").default("none"),
|
|
50
|
-
textDecoration: Joi.string().valid("none", "underline", "line-through").default("none"),
|
|
51
|
-
gradient: gradientSchema.optional()
|
|
52
|
-
}).unknown(false);
|
|
53
|
-
|
|
54
|
-
const alignmentSchema = Joi.object({
|
|
55
|
-
horizontal: Joi.string()
|
|
56
|
-
.valid("left", "center", "right")
|
|
57
|
-
.default(CANVAS_CONFIG.DEFAULTS.textAlign),
|
|
58
|
-
vertical: Joi.string().valid("top", "middle", "bottom").default("middle")
|
|
59
|
-
}).unknown(false);
|
|
60
|
-
|
|
61
|
-
const animationSchema = Joi.object({
|
|
62
|
-
preset: Joi.string().valid(...CANVAS_CONFIG.ANIMATION_TYPES as readonly string[]),
|
|
63
|
-
speed: Joi.number().min(0.1).max(10).default(1),
|
|
64
|
-
duration: Joi.number()
|
|
65
|
-
.min(CANVAS_CONFIG.LIMITS.minDuration)
|
|
66
|
-
.max(CANVAS_CONFIG.LIMITS.maxDuration)
|
|
67
|
-
.optional(),
|
|
68
|
-
style: Joi.string()
|
|
69
|
-
.valid("character", "word")
|
|
70
|
-
.optional()
|
|
71
|
-
.when("preset", {
|
|
72
|
-
is: Joi.valid("typewriter", "shift"),
|
|
73
|
-
then: Joi.optional(),
|
|
74
|
-
otherwise: Joi.forbidden()
|
|
75
|
-
}),
|
|
76
|
-
direction: Joi.string()
|
|
77
|
-
.optional()
|
|
78
|
-
.when("preset", {
|
|
79
|
-
switch: [
|
|
80
|
-
{ is: "ascend", then: Joi.valid("up", "down") },
|
|
81
|
-
{ is: "shift", then: Joi.valid("left", "right", "up", "down") },
|
|
82
|
-
{ is: "slideIn", then: Joi.valid("left", "right", "up", "down") },
|
|
83
|
-
{ is: "movingLetters", then: Joi.valid("left", "right", "up", "down") }
|
|
84
|
-
],
|
|
85
|
-
otherwise: Joi.forbidden()
|
|
86
|
-
})
|
|
87
|
-
}).unknown(false);
|
|
88
|
-
|
|
89
|
-
const backgroundSchema = Joi.object({
|
|
90
|
-
color: Joi.string().pattern(HEX6).optional(),
|
|
91
|
-
opacity: Joi.number().min(0).max(1).default(1),
|
|
92
|
-
borderRadius: Joi.number().min(0).default(0)
|
|
93
|
-
}).unknown(false);
|
|
94
|
-
|
|
95
|
-
const customFontSchema = Joi.object({
|
|
96
|
-
src: Joi.string().uri().required(),
|
|
97
|
-
family: Joi.string().required(),
|
|
98
|
-
weight: Joi.alternatives().try(Joi.string(), Joi.number()).optional(),
|
|
99
|
-
style: Joi.string().optional(),
|
|
100
|
-
originalFamily: Joi.string().optional()
|
|
101
|
-
}).unknown(false);
|
|
102
|
-
|
|
103
|
-
export const RichTextAssetSchema = Joi.object({
|
|
104
|
-
type: Joi.string().valid("rich-text").required(),
|
|
105
|
-
text: Joi.string().allow("").max(CANVAS_CONFIG.LIMITS.maxTextLength).default(""),
|
|
106
|
-
width: Joi.number()
|
|
107
|
-
.min(CANVAS_CONFIG.LIMITS.minWidth)
|
|
108
|
-
.max(CANVAS_CONFIG.LIMITS.maxWidth)
|
|
109
|
-
.default(CANVAS_CONFIG.DEFAULTS.width)
|
|
110
|
-
.optional(),
|
|
111
|
-
height: Joi.number()
|
|
112
|
-
.min(CANVAS_CONFIG.LIMITS.minHeight)
|
|
113
|
-
.max(CANVAS_CONFIG.LIMITS.maxHeight)
|
|
114
|
-
.default(CANVAS_CONFIG.DEFAULTS.height)
|
|
115
|
-
.optional(),
|
|
116
|
-
font: fontSchema.optional(),
|
|
117
|
-
style: styleSchema.optional(),
|
|
118
|
-
stroke: strokeSchema.optional(),
|
|
119
|
-
shadow: shadowSchema.optional(),
|
|
120
|
-
background: backgroundSchema.optional(),
|
|
121
|
-
align: alignmentSchema.optional(),
|
|
122
|
-
animation: animationSchema.optional(),
|
|
123
|
-
customFonts: Joi.array().items(customFontSchema).optional(),
|
|
124
|
-
cacheEnabled: Joi.boolean().default(true),
|
|
125
|
-
pixelRatio: Joi.number().min(1).max(3).default(CANVAS_CONFIG.DEFAULTS.pixelRatio)
|
|
126
|
-
}).unknown(false);
|
|
127
|
-
|
|
128
|
-
export type RichTextValidated = Required<{
|
|
129
|
-
type: "rich-text";
|
|
130
|
-
text: string;
|
|
131
|
-
width?: number;
|
|
132
|
-
height?: number;
|
|
133
|
-
font?: {
|
|
134
|
-
family: string;
|
|
135
|
-
size: number;
|
|
136
|
-
weight: string | number;
|
|
137
|
-
style: "normal" | "italic" | "oblique";
|
|
138
|
-
color: string;
|
|
139
|
-
opacity: number;
|
|
140
|
-
};
|
|
141
|
-
style?: {
|
|
142
|
-
letterSpacing: number;
|
|
143
|
-
lineHeight: number;
|
|
144
|
-
textTransform: "none" | "uppercase" | "lowercase" | "capitalize";
|
|
145
|
-
textDecoration: "none" | "underline" | "line-through";
|
|
146
|
-
gradient?: {
|
|
147
|
-
type: "linear" | "radial";
|
|
148
|
-
angle: number;
|
|
149
|
-
stops: { offset: number; color: string }[];
|
|
150
|
-
};
|
|
151
|
-
};
|
|
152
|
-
stroke?: { width: number; color: string; opacity: number };
|
|
153
|
-
shadow?: { offsetX: number; offsetY: number; blur: number; color: string; opacity: number };
|
|
154
|
-
background?: { color?: string; opacity: number; borderRadius: number };
|
|
155
|
-
align?: { horizontal: "left" | "center" | "right"; vertical: "top" | "middle" | "bottom" };
|
|
156
|
-
animation?: {
|
|
157
|
-
preset: typeof CANVAS_CONFIG.ANIMATION_TYPES[number];
|
|
158
|
-
speed: number;
|
|
159
|
-
duration?: number;
|
|
160
|
-
style?: "character" | "word";
|
|
161
|
-
direction?: "left" | "right" | "up" | "down";
|
|
162
|
-
};
|
|
163
|
-
customFonts?: { src: string; family: string; weight?: string | number; style?: string; originalFamily?: string }[];
|
|
164
|
-
cacheEnabled: boolean;
|
|
165
|
-
pixelRatio: number;
|
|
166
|
-
}>;
|
package/src/types.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { RichTextValidated } from "./schema/asset-schema";
|
|
2
|
-
|
|
3
|
-
export type RGBA = { r: number; g: number; b: number; a: number };
|
|
4
|
-
|
|
5
|
-
export type GradientSpec =
|
|
6
|
-
| { kind: "solid"; color: string; opacity: number }
|
|
7
|
-
| { kind: "linear"; angle: number; stops: { offset: number; color: string }[]; opacity: number }
|
|
8
|
-
| { kind: "radial"; stops: { offset: number; color: string }[]; opacity: number };
|
|
9
|
-
|
|
10
|
-
export type Glyph = {
|
|
11
|
-
id: number;
|
|
12
|
-
xAdvance: number;
|
|
13
|
-
xOffset: number;
|
|
14
|
-
yOffset: number;
|
|
15
|
-
cluster: number;
|
|
16
|
-
char?: string;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export type ShapedLine = {
|
|
20
|
-
glyphs: Glyph[];
|
|
21
|
-
width: number;
|
|
22
|
-
y: number; // baseline y
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export type DrawOp =
|
|
26
|
-
| { op: "BeginFrame"; width: number; height: number; pixelRatio: number; clear: boolean; bg?: { color?: string; opacity: number; radius: number } }
|
|
27
|
-
| { op: "FillPath"; path: string; x: number; y: number; fill: GradientSpec }
|
|
28
|
-
| { op: "StrokePath"; path: string; x: number; y: number; width: number; color: string; opacity: number }
|
|
29
|
-
| { op: "DecorationLine"; from: { x: number; y: number }; to: { x: number; y: number }; width: number; color: string; opacity: number }
|
|
30
|
-
;
|
|
31
|
-
|
|
32
|
-
export type EngineInit = { width: number; height: number; pixelRatio?: number; fps?: number };
|
|
33
|
-
|
|
34
|
-
export type Renderer = { render(ops: DrawOp[]): Promise<void>; toPNG?: () => Promise<Buffer> };
|
|
35
|
-
|
|
36
|
-
export type ValidAsset = RichTextValidated;
|
package/src/wasm/hb-loader.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// src/wasm/hb-loader.ts
|
|
2
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
-
|
|
4
|
-
let hbSingleton: any | null = null;
|
|
5
|
-
|
|
6
|
-
export async function initHB(wasmBaseURL?: string): Promise<any> {
|
|
7
|
-
if (hbSingleton) return hbSingleton;
|
|
8
|
-
|
|
9
|
-
// Simply import harfbuzzjs and let it handle WASM loading
|
|
10
|
-
const harfbuzzjs = await import('harfbuzzjs');
|
|
11
|
-
|
|
12
|
-
// harfbuzzjs default export is a Promise that resolves to the hb object
|
|
13
|
-
const hbPromise = harfbuzzjs.default;
|
|
14
|
-
|
|
15
|
-
if (typeof hbPromise === 'function') {
|
|
16
|
-
hbSingleton = await hbPromise();
|
|
17
|
-
} else if (hbPromise && typeof hbPromise.then === 'function') {
|
|
18
|
-
hbSingleton = await hbPromise;
|
|
19
|
-
} else {
|
|
20
|
-
hbSingleton = hbPromise;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Validate the API
|
|
24
|
-
if (!hbSingleton || typeof hbSingleton.createBuffer !== "function") {
|
|
25
|
-
throw new Error("Failed to initialize HarfBuzz: invalid API");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return hbSingleton;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export type HB = any;
|
package/tsconfig.base.json
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compileOnSave": false,
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"target": "ES2022",
|
|
5
|
-
"lib": ["ES2022", "DOM"],
|
|
6
|
-
"module": "ESNext",
|
|
7
|
-
"moduleResolution": "Bundler",
|
|
8
|
-
"verbatimModuleSyntax": true,
|
|
9
|
-
"isolatedModules": true,
|
|
10
|
-
"noEmit": true,
|
|
11
|
-
"strict": true,
|
|
12
|
-
"forceConsistentCasingInFileNames": true,
|
|
13
|
-
"skipLibCheck": false,
|
|
14
|
-
"allowJs": false,
|
|
15
|
-
"declaration": true,
|
|
16
|
-
"declarationMap": true,
|
|
17
|
-
"sourceMap": true,
|
|
18
|
-
"esModuleInterop": false,
|
|
19
|
-
"resolveJsonModule": true,
|
|
20
|
-
"types": []
|
|
21
|
-
}
|
|
22
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ES2022",
|
|
5
|
-
"moduleResolution": "Bundler",
|
|
6
|
-
"declaration": true,
|
|
7
|
-
"declarationMap": true,
|
|
8
|
-
"outDir": "dist",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"resolveJsonModule": true,
|
|
13
|
-
"types": ["node"]
|
|
14
|
-
},
|
|
15
|
-
"include": ["src/**/*", "scripts/**/*"]
|
|
16
|
-
}
|
package/tsup.config.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'tsup';
|
|
2
|
-
|
|
3
|
-
export default defineConfig([
|
|
4
|
-
// Node build
|
|
5
|
-
{
|
|
6
|
-
name: 'node',
|
|
7
|
-
entry: { 'entry.node': 'src/env/entry.node.ts' },
|
|
8
|
-
format: ['esm', 'cjs'],
|
|
9
|
-
dts: true,
|
|
10
|
-
sourcemap: true,
|
|
11
|
-
clean: true,
|
|
12
|
-
platform: 'node',
|
|
13
|
-
target: 'node18',
|
|
14
|
-
// Mark these as external to avoid bundling issues
|
|
15
|
-
external: [
|
|
16
|
-
'canvas',
|
|
17
|
-
'harfbuzzjs',
|
|
18
|
-
'ffmpeg-static',
|
|
19
|
-
'fluent-ffmpeg',
|
|
20
|
-
'child_process',
|
|
21
|
-
'stream',
|
|
22
|
-
'path',
|
|
23
|
-
'fs',
|
|
24
|
-
'node:fs',
|
|
25
|
-
'node:fs/promises',
|
|
26
|
-
'node:path',
|
|
27
|
-
'node:http',
|
|
28
|
-
'node:https',
|
|
29
|
-
'node:stream',
|
|
30
|
-
'node:child_process'
|
|
31
|
-
],
|
|
32
|
-
esbuildOptions(options) {
|
|
33
|
-
options.mainFields = ['module', 'main'];
|
|
34
|
-
options.conditions = ['node', 'import'];
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
// Web build
|
|
38
|
-
{
|
|
39
|
-
name: 'web',
|
|
40
|
-
entry: { 'entry.web': 'src/env/entry.web.ts' },
|
|
41
|
-
format: ['esm'],
|
|
42
|
-
dts: true,
|
|
43
|
-
sourcemap: true,
|
|
44
|
-
platform: 'browser',
|
|
45
|
-
target: 'es2020',
|
|
46
|
-
external: ['harfbuzzjs'],
|
|
47
|
-
esbuildOptions(options) {
|
|
48
|
-
options.mainFields = ['browser', 'module', 'main'];
|
|
49
|
-
options.conditions = ['browser', 'import'];
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
]);
|