@clypra/engine 1.0.2 → 1.1.1
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/index.cjs +237 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +129 -2
- package/dist/index.d.ts +129 -2
- package/dist/index.js +220 -115
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,44 @@
|
|
|
1
|
+
// src/engine/schema.ts
|
|
2
|
+
var SCENE_VERSION = 1;
|
|
3
|
+
var DEFAULT_CANVAS_WIDTH = 800;
|
|
4
|
+
var DEFAULT_CANVAS_HEIGHT = 200;
|
|
5
|
+
var DEFAULT_FONT_SIZE = 80;
|
|
6
|
+
var DEFAULT_FPS = 30;
|
|
7
|
+
var DEFAULT_DURATION = 2;
|
|
8
|
+
var CUSTOM_ENGINE_IDS = ["ink"];
|
|
9
|
+
var LEGACY_RENDERER_MAP = {
|
|
10
|
+
InkBrushEngine: "ink"
|
|
11
|
+
};
|
|
12
|
+
var ENGINE_ID_TO_LEGACY = {
|
|
13
|
+
ink: "InkBrushEngine"
|
|
14
|
+
};
|
|
15
|
+
function createEmptyScene(overrides) {
|
|
16
|
+
return {
|
|
17
|
+
version: SCENE_VERSION,
|
|
18
|
+
effectName: "My Effect",
|
|
19
|
+
canvas: { width: DEFAULT_CANVAS_WIDTH, height: DEFAULT_CANVAS_HEIGHT, background: "transparent" },
|
|
20
|
+
text: {
|
|
21
|
+
content: "CLYPRA",
|
|
22
|
+
fontFamily: "Poppins",
|
|
23
|
+
fontWeight: 700,
|
|
24
|
+
fontStyle: "normal",
|
|
25
|
+
fontSize: DEFAULT_FONT_SIZE,
|
|
26
|
+
letterSpacing: 4,
|
|
27
|
+
lineHeight: 1.2,
|
|
28
|
+
textPosX: "center",
|
|
29
|
+
textPosY: "middle"
|
|
30
|
+
},
|
|
31
|
+
effectLayers: [],
|
|
32
|
+
customEngineId: null,
|
|
33
|
+
compositor: { blur: 0, bloom: 0, bloomThreshold: 0.6 },
|
|
34
|
+
timeline: { duration: DEFAULT_DURATION, fps: DEFAULT_FPS, loop: true, tracks: [] },
|
|
35
|
+
...overrides
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function newLayerId() {
|
|
39
|
+
return `layer-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
1
42
|
// src/engine/textLayout.ts
|
|
2
43
|
var COMPOSITION_PRESETS = [
|
|
3
44
|
{ id: "banner", label: "Banner", width: 800, height: 200, description: "Lower third / title bar" },
|
|
@@ -17,8 +58,8 @@ function measureLine(ctx, line, letterSpacing) {
|
|
|
17
58
|
return w;
|
|
18
59
|
}
|
|
19
60
|
function getSafeRect(cfg) {
|
|
20
|
-
const w = cfg.canvasWidth ||
|
|
21
|
-
const h = cfg.canvasHeight ||
|
|
61
|
+
const w = cfg.canvasWidth || DEFAULT_CANVAS_WIDTH;
|
|
62
|
+
const h = cfg.canvasHeight || DEFAULT_CANVAS_HEIGHT;
|
|
22
63
|
const marginX = cfg.panelEnabled ? (cfg.panelPaddingX ?? 40) + 16 : Math.min(48, w * 0.06);
|
|
23
64
|
const marginY = cfg.panelEnabled ? (cfg.panelPaddingY ?? 20) + 16 : Math.min(40, h * 0.1);
|
|
24
65
|
return {
|
|
@@ -65,8 +106,8 @@ function wrapTextToWidth(ctx, text, maxWidth, letterSpacing) {
|
|
|
65
106
|
return lines.length > 0 ? lines : [""];
|
|
66
107
|
}
|
|
67
108
|
function layoutWithFontSize(ctx, cfg, fontSize, lines) {
|
|
68
|
-
const cWidth = cfg.canvasWidth ||
|
|
69
|
-
const cHeight = cfg.canvasHeight ||
|
|
109
|
+
const cWidth = cfg.canvasWidth || DEFAULT_CANVAS_WIDTH;
|
|
110
|
+
const cHeight = cfg.canvasHeight || DEFAULT_CANVAS_HEIGHT;
|
|
70
111
|
const safe = getSafeRect(cfg);
|
|
71
112
|
const lineHeight = cfg.lineHeight ?? 1.2;
|
|
72
113
|
const letterSpacing = cfg.letterSpacing ?? 0;
|
|
@@ -74,7 +115,7 @@ function layoutWithFontSize(ctx, cfg, fontSize, lines) {
|
|
|
74
115
|
ctx.font = fontStr;
|
|
75
116
|
const lineWidths = lines.map((line) => measureLine(ctx, line, letterSpacing));
|
|
76
117
|
const maxLineWidth = Math.max(...lineWidths, 1);
|
|
77
|
-
const textBlockHeight = fontSize
|
|
118
|
+
const textBlockHeight = lines.length === 1 ? fontSize : (lines.length - 1) * fontSize * lineHeight + fontSize;
|
|
78
119
|
let align = "center";
|
|
79
120
|
let startX = cWidth / 2;
|
|
80
121
|
if (cfg.textPosX === "left") {
|
|
@@ -122,7 +163,7 @@ function measureTextFits(ctx, cfg, fontSize, lines) {
|
|
|
122
163
|
return layout.bounds.maxLineWidth <= safe.width + 1 && layout.bounds.textBlockHeight <= safe.height + 1;
|
|
123
164
|
}
|
|
124
165
|
function computeAutoFitFontSize(ctx, cfg, wrappedLines) {
|
|
125
|
-
const max = Math.min(cfg.fontSize ||
|
|
166
|
+
const max = Math.min(cfg.fontSize || DEFAULT_FONT_SIZE, 200);
|
|
126
167
|
let lo = 12;
|
|
127
168
|
let hi = max;
|
|
128
169
|
let best = 12;
|
|
@@ -242,29 +283,111 @@ function shouldUsePerCharFill(cfg) {
|
|
|
242
283
|
return !!cfg.perCharFillEnabled && cfg.fillType === "solid" && !cfg.customRenderer && (cfg.charFillColors?.length ?? 0) > 0;
|
|
243
284
|
}
|
|
244
285
|
|
|
245
|
-
// src/
|
|
246
|
-
|
|
247
|
-
|
|
286
|
+
// src/platform.ts
|
|
287
|
+
var _ctxFilter = null;
|
|
288
|
+
var _roundRect = null;
|
|
289
|
+
var _letterSpacing = null;
|
|
290
|
+
var _offscreenCanvas = null;
|
|
291
|
+
var _webgl2 = null;
|
|
292
|
+
function probe2d() {
|
|
293
|
+
if (typeof document === "undefined") return null;
|
|
294
|
+
try {
|
|
295
|
+
return document.createElement("canvas").getContext("2d");
|
|
296
|
+
} catch {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function supportsCtxFilter() {
|
|
301
|
+
if (_ctxFilter !== null) return _ctxFilter;
|
|
302
|
+
const ctx = probe2d();
|
|
303
|
+
if (!ctx) {
|
|
304
|
+
_ctxFilter = false;
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
ctx.filter = "blur(4px)";
|
|
309
|
+
_ctxFilter = typeof ctx.filter === "string" && ctx.filter.includes("blur");
|
|
310
|
+
} catch {
|
|
311
|
+
_ctxFilter = false;
|
|
312
|
+
}
|
|
313
|
+
return _ctxFilter;
|
|
314
|
+
}
|
|
315
|
+
function supportsRoundRect() {
|
|
316
|
+
if (_roundRect !== null) return _roundRect;
|
|
317
|
+
const ctx = probe2d();
|
|
318
|
+
_roundRect = !!ctx && typeof ctx.roundRect === "function";
|
|
319
|
+
return _roundRect;
|
|
320
|
+
}
|
|
321
|
+
function supportsLetterSpacing() {
|
|
322
|
+
if (_letterSpacing !== null) return _letterSpacing;
|
|
323
|
+
const ctx = probe2d();
|
|
324
|
+
if (!ctx) {
|
|
325
|
+
_letterSpacing = false;
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
ctx.letterSpacing = "2px";
|
|
330
|
+
_letterSpacing = typeof ctx.letterSpacing === "string" && ctx.letterSpacing === "2px";
|
|
331
|
+
} catch {
|
|
332
|
+
_letterSpacing = false;
|
|
333
|
+
}
|
|
334
|
+
return _letterSpacing;
|
|
335
|
+
}
|
|
336
|
+
function supportsOffscreenCanvas() {
|
|
337
|
+
if (_offscreenCanvas !== null) return _offscreenCanvas;
|
|
338
|
+
_offscreenCanvas = typeof globalThis !== "undefined" && typeof globalThis.OffscreenCanvas === "function";
|
|
339
|
+
return _offscreenCanvas;
|
|
340
|
+
}
|
|
341
|
+
function supportsWebGL2() {
|
|
342
|
+
if (_webgl2 !== null) return _webgl2;
|
|
343
|
+
if (typeof document === "undefined") {
|
|
344
|
+
_webgl2 = false;
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
248
348
|
const canvas = document.createElement("canvas");
|
|
249
|
-
canvas.width =
|
|
250
|
-
canvas.height =
|
|
251
|
-
|
|
349
|
+
canvas.width = 1;
|
|
350
|
+
canvas.height = 1;
|
|
351
|
+
_webgl2 = !!canvas.getContext("webgl2");
|
|
352
|
+
} catch {
|
|
353
|
+
_webgl2 = false;
|
|
252
354
|
}
|
|
253
|
-
|
|
254
|
-
|
|
355
|
+
return _webgl2;
|
|
356
|
+
}
|
|
357
|
+
function createCanvas(width, height) {
|
|
358
|
+
if (supportsOffscreenCanvas()) {
|
|
359
|
+
return new OffscreenCanvas(width, height);
|
|
255
360
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
361
|
+
if (typeof document !== "undefined") {
|
|
362
|
+
const canvas = document.createElement("canvas");
|
|
363
|
+
canvas.width = width;
|
|
364
|
+
canvas.height = height;
|
|
365
|
+
return canvas;
|
|
259
366
|
}
|
|
367
|
+
const factory = globalThis.__clypraCreateCanvas;
|
|
368
|
+
if (factory) return factory(width, height);
|
|
260
369
|
try {
|
|
261
370
|
const nodeRequire = (0, eval)("require");
|
|
262
371
|
const nodeCanvas = nodeRequire("@napi-rs/canvas");
|
|
263
|
-
return nodeCanvas.createCanvas(
|
|
372
|
+
return nodeCanvas.createCanvas(width, height);
|
|
264
373
|
} catch {
|
|
265
|
-
throw new Error("No canvas implementation
|
|
374
|
+
throw new Error("[clypra/engine] No canvas implementation available in this environment. Set globalThis.__clypraCreateCanvas or install @napi-rs/canvas.");
|
|
266
375
|
}
|
|
267
376
|
}
|
|
377
|
+
function releaseCanvas(canvas) {
|
|
378
|
+
if (canvas instanceof OffscreenCanvas) return;
|
|
379
|
+
canvas.width = 0;
|
|
380
|
+
canvas.height = 0;
|
|
381
|
+
}
|
|
382
|
+
function _resetPlatformCache() {
|
|
383
|
+
_ctxFilter = null;
|
|
384
|
+
_roundRect = null;
|
|
385
|
+
_letterSpacing = null;
|
|
386
|
+
_offscreenCanvas = null;
|
|
387
|
+
_webgl2 = null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/engine/procedural/utils.ts
|
|
268
391
|
function getCanvas2DContext(canvas) {
|
|
269
392
|
return canvas.getContext("2d");
|
|
270
393
|
}
|
|
@@ -365,8 +488,8 @@ var InkBrushEngine = class {
|
|
|
365
488
|
panelStrokeEnabled: false,
|
|
366
489
|
panelStrokeColor: "#2A2A38",
|
|
367
490
|
panelStrokeWidth: 2,
|
|
368
|
-
canvasWidth:
|
|
369
|
-
canvasHeight:
|
|
491
|
+
canvasWidth: DEFAULT_CANVAS_WIDTH,
|
|
492
|
+
canvasHeight: DEFAULT_CANVAS_HEIGHT,
|
|
370
493
|
textPosX: "center",
|
|
371
494
|
textPosY: "middle",
|
|
372
495
|
inkColor: "#FFFFFF",
|
|
@@ -621,42 +744,39 @@ var InkBrushEngine = class {
|
|
|
621
744
|
}
|
|
622
745
|
};
|
|
623
746
|
|
|
624
|
-
// src/
|
|
625
|
-
function
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
747
|
+
// src/canvas-utils.ts
|
|
748
|
+
function drawRoundedRect(ctx, x, y, w, h, r) {
|
|
749
|
+
const radius = Math.max(0, Math.min(r, Math.min(w, h) / 2));
|
|
750
|
+
ctx.beginPath();
|
|
751
|
+
if (supportsRoundRect()) {
|
|
752
|
+
ctx.roundRect(x, y, w, h, radius);
|
|
753
|
+
} else {
|
|
754
|
+
ctx.moveTo(x + radius, y);
|
|
755
|
+
ctx.lineTo(x + w - radius, y);
|
|
756
|
+
ctx.quadraticCurveTo(x + w, y, x + w, y + radius);
|
|
757
|
+
ctx.lineTo(x + w, y + h - radius);
|
|
758
|
+
ctx.quadraticCurveTo(x + w, y + h, x + w - radius, y + h);
|
|
759
|
+
ctx.lineTo(x + radius, y + h);
|
|
760
|
+
ctx.quadraticCurveTo(x, y + h, x, y + h - radius);
|
|
761
|
+
ctx.lineTo(x, y + radius);
|
|
762
|
+
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
638
763
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
throw new Error("No canvas implementation found in this environment.");
|
|
764
|
+
}
|
|
765
|
+
function applyLetterSpacing(ctx, value) {
|
|
766
|
+
const prev = ctx.letterSpacing ?? "0px";
|
|
767
|
+
if (value !== 0) {
|
|
768
|
+
ctx.letterSpacing = `${value}px`;
|
|
645
769
|
}
|
|
770
|
+
return prev;
|
|
771
|
+
}
|
|
772
|
+
function restoreLetterSpacing(ctx, saved) {
|
|
773
|
+
ctx.letterSpacing = saved;
|
|
646
774
|
}
|
|
775
|
+
|
|
776
|
+
// src/renderer.ts
|
|
647
777
|
function getCanvas2DContext2(canvas) {
|
|
648
778
|
return canvas.getContext("2d");
|
|
649
779
|
}
|
|
650
|
-
function hexToRgb2(hex) {
|
|
651
|
-
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
|
652
|
-
const fullHex = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b);
|
|
653
|
-
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(fullHex);
|
|
654
|
-
return result ? {
|
|
655
|
-
r: parseInt(result[1], 16),
|
|
656
|
-
g: parseInt(result[2], 16),
|
|
657
|
-
b: parseInt(result[3], 16)
|
|
658
|
-
} : { r: 255, g: 255, b: 255 };
|
|
659
|
-
}
|
|
660
780
|
function renderTextEffectCore(ctx, cfg) {
|
|
661
781
|
if (cfg.customRenderer === "InkBrushEngine") {
|
|
662
782
|
const engine = new InkBrushEngine(cfg);
|
|
@@ -666,8 +786,8 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
666
786
|
const { text, fontFamily, fontWeight, fontStyle, fontSize, letterSpacing, lineHeight, fillType, fillColor, fillGradientAngle, fillGradientStops, patternType, strokeEnabled, strokeColor, strokeWidth, strokePosition, strokeOpacity, strokeLineJoin, strokeBlur, strokeType, strokeColorSecondary, strokeWidthSecondary, strokeFadeRange, glowLayers, shadowEnabled, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, shadowOpacity, shadowType, bevelEnabled, bevelDepth, bevelHighlight, bevelShadow, bevelDirection, bevelCoreColor, bevelEdgeColor, bevelEdgeWidth, bevelBlur, bevelBlurColor, bevelPerspectiveEnabled, bevelVanishingPointX, bevelVanishingPointY, bevelFocalLength, stackEnabled, stackCount, stackOffsetX, stackOffsetY, stackOpacityDecay, stackColor1, stackColor2, stackColor3, stackColor4, panelEnabled, panelColor, panelOpacity, panelRadius, panelPaddingX, panelPaddingY, panelStrokeEnabled, panelStrokeColor, panelStrokeWidth, canvasWidth, canvasHeight, textPosX, textPosY } = cfg;
|
|
667
787
|
ctx.imageSmoothingEnabled = true;
|
|
668
788
|
ctx.lineJoin = strokeLineJoin;
|
|
669
|
-
const cWidth = canvasWidth ||
|
|
670
|
-
const cHeight = canvasHeight ||
|
|
789
|
+
const cWidth = canvasWidth || DEFAULT_CANVAS_WIDTH;
|
|
790
|
+
const cHeight = canvasHeight || DEFAULT_CANVAS_HEIGHT;
|
|
671
791
|
const layout = computeTextLayout(ctx, cfg, {
|
|
672
792
|
wrap: cfg.wrapText !== false,
|
|
673
793
|
autoFit: !!cfg.autoFitText
|
|
@@ -879,7 +999,7 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
879
999
|
}
|
|
880
1000
|
}
|
|
881
1001
|
} else {
|
|
882
|
-
ctx
|
|
1002
|
+
drawRoundedRect(ctx, px, py, pw, ph, panelRadius);
|
|
883
1003
|
}
|
|
884
1004
|
ctx.closePath();
|
|
885
1005
|
ctx.fill();
|
|
@@ -942,9 +1062,9 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
942
1062
|
ctx.restore();
|
|
943
1063
|
}
|
|
944
1064
|
if (bevelEnabled && bevelDepth > 0) {
|
|
945
|
-
const shadowRgb =
|
|
946
|
-
const coreRgb =
|
|
947
|
-
const highlightRgb =
|
|
1065
|
+
const shadowRgb = hexToRgb(bevelShadow || "#1A0A00");
|
|
1066
|
+
const coreRgb = hexToRgb(bevelCoreColor || bevelShadow || "#3A1A00");
|
|
1067
|
+
const highlightRgb = hexToRgb(bevelHighlight || "#FFFFFF");
|
|
948
1068
|
const shadeForDepth = (t) => {
|
|
949
1069
|
const eased = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
|
950
1070
|
if (eased <= 0.5) {
|
|
@@ -985,7 +1105,7 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
985
1105
|
const t = 1 - (i - 1) / Math.max(1, bevelDepth - 1);
|
|
986
1106
|
const aoFactor = 0.35 + 0.65 * (1 - (i - 1) / Math.max(1, bevelDepth));
|
|
987
1107
|
const baseColor = shadeForDepth(t);
|
|
988
|
-
const bRgb =
|
|
1108
|
+
const bRgb = hexToRgb(baseColor.startsWith("#") ? baseColor : "#000000");
|
|
989
1109
|
const baseRgbParsed = (() => {
|
|
990
1110
|
const m = baseColor.match(/rgb\((\d+),(\d+),(\d+)\)/);
|
|
991
1111
|
return m ? { r: +m[1], g: +m[2], b: +m[3] } : bRgb;
|
|
@@ -1078,7 +1198,7 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
1078
1198
|
let customStrokeStyle = strokeColor;
|
|
1079
1199
|
if (sFadeRange > 0) {
|
|
1080
1200
|
const grad = ctx.createLinearGradient(0, yMin, 0, yMax);
|
|
1081
|
-
const rgb =
|
|
1201
|
+
const rgb = hexToRgb(strokeColor);
|
|
1082
1202
|
grad.addColorStop(0, `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${strokeOpacity / 100})`);
|
|
1083
1203
|
const fadeLimit = Math.min(1, sFadeRange / 100);
|
|
1084
1204
|
grad.addColorStop(fadeLimit, `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0)`);
|
|
@@ -1188,7 +1308,7 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
1188
1308
|
} else if (fillType === "pattern") {
|
|
1189
1309
|
const pType = patternType || "chalk";
|
|
1190
1310
|
const patColor = fillColor || "#ffffff";
|
|
1191
|
-
const patCanvas =
|
|
1311
|
+
const patCanvas = createCanvas(128, 128);
|
|
1192
1312
|
if (pType === "carbon") {
|
|
1193
1313
|
patCanvas.width = 8;
|
|
1194
1314
|
patCanvas.height = 8;
|
|
@@ -1622,7 +1742,7 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
1622
1742
|
};
|
|
1623
1743
|
const isInkStyle = cfg.effectName.toLowerCase().includes("ink") || cfg.fontFamily.toLowerCase().includes("brush") || cfg.effectName.toLowerCase().includes("grunge") || cfg.effectName.toLowerCase().includes("scratch") || cfg.fontFamily === "Permanent Marker";
|
|
1624
1744
|
if (isInkStyle) {
|
|
1625
|
-
const tCanvas =
|
|
1745
|
+
const tCanvas = createCanvas(cWidth, cHeight);
|
|
1626
1746
|
tCanvas.width = cWidth;
|
|
1627
1747
|
tCanvas.height = cHeight;
|
|
1628
1748
|
const tCtx = getCanvas2DContext2(tCanvas);
|
|
@@ -1751,7 +1871,7 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
1751
1871
|
}
|
|
1752
1872
|
tCtx.letterSpacing = originalLSpacing;
|
|
1753
1873
|
ctx.save();
|
|
1754
|
-
const tintCanvas =
|
|
1874
|
+
const tintCanvas = createCanvas(cWidth, cHeight);
|
|
1755
1875
|
tintCanvas.width = cWidth;
|
|
1756
1876
|
tintCanvas.height = cHeight;
|
|
1757
1877
|
const tintCtx = getCanvas2DContext2(tintCanvas);
|
|
@@ -1784,7 +1904,7 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
1784
1904
|
ctx.globalCompositeOperation = "source-atop";
|
|
1785
1905
|
const renderCount = Math.max(1, Math.min(20, layer2.strength ?? 1));
|
|
1786
1906
|
for (let i = 0; i < renderCount; i++) {
|
|
1787
|
-
renderWithShadowTrick("fill", layer2.color, layer2.blur, 0, 0, layer2.opacity, "
|
|
1907
|
+
renderWithShadowTrick("fill", layer2.color, layer2.blur, 0, 0, layer2.opacity, "#000000", layer2.spread ?? 0);
|
|
1788
1908
|
}
|
|
1789
1909
|
ctx.restore();
|
|
1790
1910
|
}
|
|
@@ -1792,7 +1912,7 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
1792
1912
|
if (shadowEnabled && shadowType === "inner" && shadowOpacity > 0) {
|
|
1793
1913
|
ctx.save();
|
|
1794
1914
|
ctx.globalCompositeOperation = "source-atop";
|
|
1795
|
-
renderWithShadowTrick("fill", shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, shadowOpacity, "
|
|
1915
|
+
renderWithShadowTrick("fill", shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, shadowOpacity, "#000000");
|
|
1796
1916
|
ctx.restore();
|
|
1797
1917
|
}
|
|
1798
1918
|
if (isGlitch) {
|
|
@@ -1822,7 +1942,7 @@ var defaultConfig = {
|
|
|
1822
1942
|
fontFamily: "Poppins",
|
|
1823
1943
|
fontWeight: 700,
|
|
1824
1944
|
fontStyle: "normal",
|
|
1825
|
-
fontSize:
|
|
1945
|
+
fontSize: DEFAULT_FONT_SIZE,
|
|
1826
1946
|
letterSpacing: 4,
|
|
1827
1947
|
lineHeight: 1.2,
|
|
1828
1948
|
fillType: "solid",
|
|
@@ -1882,8 +2002,8 @@ var defaultConfig = {
|
|
|
1882
2002
|
panelStrokeEnabled: false,
|
|
1883
2003
|
panelStrokeColor: "#2A2A38",
|
|
1884
2004
|
panelStrokeWidth: 2,
|
|
1885
|
-
canvasWidth:
|
|
1886
|
-
canvasHeight:
|
|
2005
|
+
canvasWidth: DEFAULT_CANVAS_WIDTH,
|
|
2006
|
+
canvasHeight: DEFAULT_CANVAS_HEIGHT,
|
|
1887
2007
|
textPosX: "center",
|
|
1888
2008
|
textPosY: "middle",
|
|
1889
2009
|
wrapText: true,
|
|
@@ -2225,48 +2345,8 @@ async function initializeFontSystem() {
|
|
|
2225
2345
|
}
|
|
2226
2346
|
}
|
|
2227
2347
|
function checkFontVariant(variantName) {
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
if (!ctx) return false;
|
|
2231
|
-
ctx.font = `16px "${variantName}"`;
|
|
2232
|
-
const metrics = ctx.measureText("Test");
|
|
2233
|
-
return metrics.width > 0;
|
|
2234
|
-
}
|
|
2235
|
-
|
|
2236
|
-
// src/engine/schema.ts
|
|
2237
|
-
var SCENE_VERSION = 1;
|
|
2238
|
-
var CUSTOM_ENGINE_IDS = ["ink"];
|
|
2239
|
-
var LEGACY_RENDERER_MAP = {
|
|
2240
|
-
InkBrushEngine: "ink"
|
|
2241
|
-
};
|
|
2242
|
-
var ENGINE_ID_TO_LEGACY = {
|
|
2243
|
-
ink: "InkBrushEngine"
|
|
2244
|
-
};
|
|
2245
|
-
function createEmptyScene(overrides) {
|
|
2246
|
-
return {
|
|
2247
|
-
version: SCENE_VERSION,
|
|
2248
|
-
effectName: "My Effect",
|
|
2249
|
-
canvas: { width: 800, height: 200, background: "transparent" },
|
|
2250
|
-
text: {
|
|
2251
|
-
content: "CLYPRA",
|
|
2252
|
-
fontFamily: "Poppins",
|
|
2253
|
-
fontWeight: 700,
|
|
2254
|
-
fontStyle: "normal",
|
|
2255
|
-
fontSize: 80,
|
|
2256
|
-
letterSpacing: 4,
|
|
2257
|
-
lineHeight: 1.2,
|
|
2258
|
-
textPosX: "center",
|
|
2259
|
-
textPosY: "middle"
|
|
2260
|
-
},
|
|
2261
|
-
effectLayers: [],
|
|
2262
|
-
customEngineId: null,
|
|
2263
|
-
compositor: { blur: 0, bloom: 0, bloomThreshold: 0.6 },
|
|
2264
|
-
timeline: { duration: 2, fps: 30, loop: true, tracks: [] },
|
|
2265
|
-
...overrides
|
|
2266
|
-
};
|
|
2267
|
-
}
|
|
2268
|
-
function newLayerId() {
|
|
2269
|
-
return `layer-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
|
|
2348
|
+
if (typeof document === "undefined" || !document.fonts) return false;
|
|
2349
|
+
return document.fonts.check(`16px "${variantName}"`);
|
|
2270
2350
|
}
|
|
2271
2351
|
|
|
2272
2352
|
// src/engine/timelineDefaults.ts
|
|
@@ -2924,11 +3004,15 @@ function getCompositor() {
|
|
|
2924
3004
|
}
|
|
2925
3005
|
return sharedCompositor;
|
|
2926
3006
|
}
|
|
3007
|
+
function disposeSharedCompositor() {
|
|
3008
|
+
sharedCompositor?.dispose();
|
|
3009
|
+
sharedCompositor = null;
|
|
3010
|
+
}
|
|
2927
3011
|
function evaluateScene(doc, time, ctx, options = {}) {
|
|
2928
3012
|
const animated = applyTimelineAtTime(doc, time);
|
|
2929
3013
|
const cfg = sceneToConfig(animated);
|
|
2930
|
-
const w = cfg.canvasWidth ||
|
|
2931
|
-
const h = cfg.canvasHeight ||
|
|
3014
|
+
const w = cfg.canvasWidth || DEFAULT_CANVAS_WIDTH;
|
|
3015
|
+
const h = cfg.canvasHeight || DEFAULT_CANVAS_HEIGHT;
|
|
2932
3016
|
const filterLayers = animated.effectLayers.filter((l) => l.type === "filter" && l.enabled);
|
|
2933
3017
|
const lastFilter = filterLayers[filterLayers.length - 1]?.params;
|
|
2934
3018
|
const comp = {
|
|
@@ -2944,8 +3028,8 @@ function evaluateScene(doc, time, ctx, options = {}) {
|
|
|
2944
3028
|
finishFrame();
|
|
2945
3029
|
return;
|
|
2946
3030
|
}
|
|
2947
|
-
|
|
2948
|
-
|
|
3031
|
+
if (supportsOffscreenCanvas()) {
|
|
3032
|
+
const off = new OffscreenCanvas(w, h);
|
|
2949
3033
|
const offCtx = off.getContext("2d");
|
|
2950
3034
|
if (!offCtx) {
|
|
2951
3035
|
renderTextEffectCore(ctx, cfg);
|
|
@@ -2974,10 +3058,14 @@ function evaluateScene(doc, time, ctx, options = {}) {
|
|
|
2974
3058
|
const compositor = options.compositor ?? getCompositor();
|
|
2975
3059
|
if (compositor?.isSupported) {
|
|
2976
3060
|
compositor.renderToContext(ctx, temp, comp);
|
|
3061
|
+
temp.width = 0;
|
|
3062
|
+
temp.height = 0;
|
|
2977
3063
|
return;
|
|
2978
3064
|
}
|
|
2979
3065
|
ctx.clearRect(0, 0, w, h);
|
|
2980
3066
|
ctx.drawImage(temp, 0, 0);
|
|
3067
|
+
temp.width = 0;
|
|
3068
|
+
temp.height = 0;
|
|
2981
3069
|
return;
|
|
2982
3070
|
}
|
|
2983
3071
|
}
|
|
@@ -2988,9 +3076,9 @@ function evaluateConfig(cfg, time, ctx, options) {
|
|
|
2988
3076
|
evaluateScene(textEffectConfigToScene(cfg), time, ctx, options);
|
|
2989
3077
|
}
|
|
2990
3078
|
function advanceSceneTime(doc, steps) {
|
|
2991
|
-
const dt = 1 / (doc.timeline.fps ||
|
|
3079
|
+
const dt = 1 / (doc.timeline.fps || DEFAULT_FPS);
|
|
2992
3080
|
const next = doc._time ?? 0;
|
|
2993
|
-
const duration = doc.timeline.duration ||
|
|
3081
|
+
const duration = doc.timeline.duration || DEFAULT_DURATION;
|
|
2994
3082
|
let t = next + steps * dt;
|
|
2995
3083
|
if (doc.timeline.loop) {
|
|
2996
3084
|
t = duration > 0 ? t % duration : t;
|
|
@@ -5947,6 +6035,11 @@ function duplicateTrackAtPlayhead(doc, trackIndex, previewTime) {
|
|
|
5947
6035
|
export {
|
|
5948
6036
|
COMPOSITION_PRESETS,
|
|
5949
6037
|
CUSTOM_ENGINE_IDS,
|
|
6038
|
+
DEFAULT_CANVAS_HEIGHT,
|
|
6039
|
+
DEFAULT_CANVAS_WIDTH,
|
|
6040
|
+
DEFAULT_DURATION,
|
|
6041
|
+
DEFAULT_FONT_SIZE,
|
|
6042
|
+
DEFAULT_FPS,
|
|
5950
6043
|
DEFAULT_TEXT_STYLE,
|
|
5951
6044
|
EMPHASIS_PRESETS,
|
|
5952
6045
|
ENGINE_ID_TO_LEGACY,
|
|
@@ -5964,6 +6057,7 @@ export {
|
|
|
5964
6057
|
TextEffectRenderer,
|
|
5965
6058
|
WEBM_EXPORT_MAX_FRAMES,
|
|
5966
6059
|
WebGLCompositor,
|
|
6060
|
+
_resetPlatformCache,
|
|
5967
6061
|
addImageLayer,
|
|
5968
6062
|
addKeyframeAtTime,
|
|
5969
6063
|
addOrUpdateKeyframe,
|
|
@@ -5975,6 +6069,7 @@ export {
|
|
|
5975
6069
|
advanceSceneTime,
|
|
5976
6070
|
alignToLottieJ,
|
|
5977
6071
|
applyFillColorToAll,
|
|
6072
|
+
applyLetterSpacing,
|
|
5978
6073
|
applyMaskReveal,
|
|
5979
6074
|
applyRecipeToScene,
|
|
5980
6075
|
applyStyleToLottie,
|
|
@@ -6000,16 +6095,19 @@ export {
|
|
|
6000
6095
|
computeTextLayout,
|
|
6001
6096
|
countTextGlyphs,
|
|
6002
6097
|
createBlankLottie,
|
|
6098
|
+
createCanvas,
|
|
6003
6099
|
createDefaultRevealTrack,
|
|
6004
6100
|
createEmptyScene,
|
|
6005
6101
|
createPulseOpacityTrack,
|
|
6006
6102
|
defaultConfig,
|
|
6007
6103
|
deleteKeyframe,
|
|
6104
|
+
disposeSharedCompositor,
|
|
6008
6105
|
downloadDotLottie,
|
|
6009
6106
|
downloadLottieJson,
|
|
6010
6107
|
downloadPngSequenceZip,
|
|
6011
6108
|
downloadSceneWebM,
|
|
6012
6109
|
drawPerCharText,
|
|
6110
|
+
drawRoundedRect,
|
|
6013
6111
|
duplicateTrackAtPlayhead,
|
|
6014
6112
|
ease,
|
|
6015
6113
|
enableKeyframing,
|
|
@@ -6057,6 +6155,7 @@ export {
|
|
|
6057
6155
|
readLayerScalar,
|
|
6058
6156
|
readStyleFromLottieLayer,
|
|
6059
6157
|
reindexLayers,
|
|
6158
|
+
releaseCanvas,
|
|
6060
6159
|
removeKeyframe,
|
|
6061
6160
|
removeTrack,
|
|
6062
6161
|
renderPngSequence,
|
|
@@ -6066,6 +6165,7 @@ export {
|
|
|
6066
6165
|
resizeCharFillColors,
|
|
6067
6166
|
resolveAnimatedScalar,
|
|
6068
6167
|
resolveCustomEngineId,
|
|
6168
|
+
restoreLetterSpacing,
|
|
6069
6169
|
scanLottieFonts,
|
|
6070
6170
|
scanTextLayers,
|
|
6071
6171
|
sceneToConfig,
|
|
@@ -6075,6 +6175,11 @@ export {
|
|
|
6075
6175
|
shouldUsePerCharFill,
|
|
6076
6176
|
snapshotScene,
|
|
6077
6177
|
sortKeyframes,
|
|
6178
|
+
supportsCtxFilter,
|
|
6179
|
+
supportsLetterSpacing,
|
|
6180
|
+
supportsOffscreenCanvas,
|
|
6181
|
+
supportsRoundRect,
|
|
6182
|
+
supportsWebGL2,
|
|
6078
6183
|
syncCompositorFromScene,
|
|
6079
6184
|
textEffectConfigToScene,
|
|
6080
6185
|
trackId,
|