@clypra/engine 1.0.1 → 1.1.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/README.md +386 -0
- package/dist/index.cjs +227 -106
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -1
- package/dist/index.d.ts +120 -1
- package/dist/index.js +211 -106
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,11 @@ var index_exports = {};
|
|
|
21
21
|
__export(index_exports, {
|
|
22
22
|
COMPOSITION_PRESETS: () => COMPOSITION_PRESETS,
|
|
23
23
|
CUSTOM_ENGINE_IDS: () => CUSTOM_ENGINE_IDS,
|
|
24
|
+
DEFAULT_CANVAS_HEIGHT: () => DEFAULT_CANVAS_HEIGHT,
|
|
25
|
+
DEFAULT_CANVAS_WIDTH: () => DEFAULT_CANVAS_WIDTH,
|
|
26
|
+
DEFAULT_DURATION: () => DEFAULT_DURATION,
|
|
27
|
+
DEFAULT_FONT_SIZE: () => DEFAULT_FONT_SIZE,
|
|
28
|
+
DEFAULT_FPS: () => DEFAULT_FPS,
|
|
24
29
|
DEFAULT_TEXT_STYLE: () => DEFAULT_TEXT_STYLE,
|
|
25
30
|
EMPHASIS_PRESETS: () => EMPHASIS_PRESETS,
|
|
26
31
|
ENGINE_ID_TO_LEGACY: () => ENGINE_ID_TO_LEGACY,
|
|
@@ -38,6 +43,7 @@ __export(index_exports, {
|
|
|
38
43
|
TextEffectRenderer: () => TextEffectRenderer,
|
|
39
44
|
WEBM_EXPORT_MAX_FRAMES: () => WEBM_EXPORT_MAX_FRAMES,
|
|
40
45
|
WebGLCompositor: () => WebGLCompositor,
|
|
46
|
+
_resetPlatformCache: () => _resetPlatformCache,
|
|
41
47
|
addImageLayer: () => addImageLayer,
|
|
42
48
|
addKeyframeAtTime: () => addKeyframeAtTime,
|
|
43
49
|
addOrUpdateKeyframe: () => addOrUpdateKeyframe,
|
|
@@ -49,6 +55,7 @@ __export(index_exports, {
|
|
|
49
55
|
advanceSceneTime: () => advanceSceneTime,
|
|
50
56
|
alignToLottieJ: () => alignToLottieJ,
|
|
51
57
|
applyFillColorToAll: () => applyFillColorToAll,
|
|
58
|
+
applyLetterSpacing: () => applyLetterSpacing,
|
|
52
59
|
applyMaskReveal: () => applyMaskReveal,
|
|
53
60
|
applyRecipeToScene: () => applyRecipeToScene,
|
|
54
61
|
applyStyleToLottie: () => applyStyleToLottie,
|
|
@@ -74,6 +81,7 @@ __export(index_exports, {
|
|
|
74
81
|
computeTextLayout: () => computeTextLayout,
|
|
75
82
|
countTextGlyphs: () => countTextGlyphs,
|
|
76
83
|
createBlankLottie: () => createBlankLottie,
|
|
84
|
+
createCanvas: () => createCanvas,
|
|
77
85
|
createDefaultRevealTrack: () => createDefaultRevealTrack,
|
|
78
86
|
createEmptyScene: () => createEmptyScene,
|
|
79
87
|
createPulseOpacityTrack: () => createPulseOpacityTrack,
|
|
@@ -84,6 +92,7 @@ __export(index_exports, {
|
|
|
84
92
|
downloadPngSequenceZip: () => downloadPngSequenceZip,
|
|
85
93
|
downloadSceneWebM: () => downloadSceneWebM,
|
|
86
94
|
drawPerCharText: () => drawPerCharText,
|
|
95
|
+
drawRoundedRect: () => drawRoundedRect,
|
|
87
96
|
duplicateTrackAtPlayhead: () => duplicateTrackAtPlayhead,
|
|
88
97
|
ease: () => ease,
|
|
89
98
|
enableKeyframing: () => enableKeyframing,
|
|
@@ -131,6 +140,7 @@ __export(index_exports, {
|
|
|
131
140
|
readLayerScalar: () => readLayerScalar,
|
|
132
141
|
readStyleFromLottieLayer: () => readStyleFromLottieLayer,
|
|
133
142
|
reindexLayers: () => reindexLayers,
|
|
143
|
+
releaseCanvas: () => releaseCanvas,
|
|
134
144
|
removeKeyframe: () => removeKeyframe,
|
|
135
145
|
removeTrack: () => removeTrack,
|
|
136
146
|
renderPngSequence: () => renderPngSequence,
|
|
@@ -140,6 +150,7 @@ __export(index_exports, {
|
|
|
140
150
|
resizeCharFillColors: () => resizeCharFillColors,
|
|
141
151
|
resolveAnimatedScalar: () => resolveAnimatedScalar,
|
|
142
152
|
resolveCustomEngineId: () => resolveCustomEngineId,
|
|
153
|
+
restoreLetterSpacing: () => restoreLetterSpacing,
|
|
143
154
|
scanLottieFonts: () => scanLottieFonts,
|
|
144
155
|
scanTextLayers: () => scanTextLayers,
|
|
145
156
|
sceneToConfig: () => sceneToConfig,
|
|
@@ -149,6 +160,11 @@ __export(index_exports, {
|
|
|
149
160
|
shouldUsePerCharFill: () => shouldUsePerCharFill,
|
|
150
161
|
snapshotScene: () => snapshotScene,
|
|
151
162
|
sortKeyframes: () => sortKeyframes,
|
|
163
|
+
supportsCtxFilter: () => supportsCtxFilter,
|
|
164
|
+
supportsLetterSpacing: () => supportsLetterSpacing,
|
|
165
|
+
supportsOffscreenCanvas: () => supportsOffscreenCanvas,
|
|
166
|
+
supportsRoundRect: () => supportsRoundRect,
|
|
167
|
+
supportsWebGL2: () => supportsWebGL2,
|
|
152
168
|
syncCompositorFromScene: () => syncCompositorFromScene,
|
|
153
169
|
textEffectConfigToScene: () => textEffectConfigToScene,
|
|
154
170
|
trackId: () => trackId,
|
|
@@ -163,6 +179,47 @@ __export(index_exports, {
|
|
|
163
179
|
});
|
|
164
180
|
module.exports = __toCommonJS(index_exports);
|
|
165
181
|
|
|
182
|
+
// src/engine/schema.ts
|
|
183
|
+
var SCENE_VERSION = 1;
|
|
184
|
+
var DEFAULT_CANVAS_WIDTH = 800;
|
|
185
|
+
var DEFAULT_CANVAS_HEIGHT = 200;
|
|
186
|
+
var DEFAULT_FONT_SIZE = 80;
|
|
187
|
+
var DEFAULT_FPS = 30;
|
|
188
|
+
var DEFAULT_DURATION = 2;
|
|
189
|
+
var CUSTOM_ENGINE_IDS = ["ink"];
|
|
190
|
+
var LEGACY_RENDERER_MAP = {
|
|
191
|
+
InkBrushEngine: "ink"
|
|
192
|
+
};
|
|
193
|
+
var ENGINE_ID_TO_LEGACY = {
|
|
194
|
+
ink: "InkBrushEngine"
|
|
195
|
+
};
|
|
196
|
+
function createEmptyScene(overrides) {
|
|
197
|
+
return {
|
|
198
|
+
version: SCENE_VERSION,
|
|
199
|
+
effectName: "My Effect",
|
|
200
|
+
canvas: { width: DEFAULT_CANVAS_WIDTH, height: DEFAULT_CANVAS_HEIGHT, background: "transparent" },
|
|
201
|
+
text: {
|
|
202
|
+
content: "CLYPRA",
|
|
203
|
+
fontFamily: "Poppins",
|
|
204
|
+
fontWeight: 700,
|
|
205
|
+
fontStyle: "normal",
|
|
206
|
+
fontSize: DEFAULT_FONT_SIZE,
|
|
207
|
+
letterSpacing: 4,
|
|
208
|
+
lineHeight: 1.2,
|
|
209
|
+
textPosX: "center",
|
|
210
|
+
textPosY: "middle"
|
|
211
|
+
},
|
|
212
|
+
effectLayers: [],
|
|
213
|
+
customEngineId: null,
|
|
214
|
+
compositor: { blur: 0, bloom: 0, bloomThreshold: 0.6 },
|
|
215
|
+
timeline: { duration: DEFAULT_DURATION, fps: DEFAULT_FPS, loop: true, tracks: [] },
|
|
216
|
+
...overrides
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function newLayerId() {
|
|
220
|
+
return `layer-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
166
223
|
// src/engine/textLayout.ts
|
|
167
224
|
var COMPOSITION_PRESETS = [
|
|
168
225
|
{ id: "banner", label: "Banner", width: 800, height: 200, description: "Lower third / title bar" },
|
|
@@ -182,8 +239,8 @@ function measureLine(ctx, line, letterSpacing) {
|
|
|
182
239
|
return w;
|
|
183
240
|
}
|
|
184
241
|
function getSafeRect(cfg) {
|
|
185
|
-
const w = cfg.canvasWidth ||
|
|
186
|
-
const h = cfg.canvasHeight ||
|
|
242
|
+
const w = cfg.canvasWidth || DEFAULT_CANVAS_WIDTH;
|
|
243
|
+
const h = cfg.canvasHeight || DEFAULT_CANVAS_HEIGHT;
|
|
187
244
|
const marginX = cfg.panelEnabled ? (cfg.panelPaddingX ?? 40) + 16 : Math.min(48, w * 0.06);
|
|
188
245
|
const marginY = cfg.panelEnabled ? (cfg.panelPaddingY ?? 20) + 16 : Math.min(40, h * 0.1);
|
|
189
246
|
return {
|
|
@@ -230,8 +287,8 @@ function wrapTextToWidth(ctx, text, maxWidth, letterSpacing) {
|
|
|
230
287
|
return lines.length > 0 ? lines : [""];
|
|
231
288
|
}
|
|
232
289
|
function layoutWithFontSize(ctx, cfg, fontSize, lines) {
|
|
233
|
-
const cWidth = cfg.canvasWidth ||
|
|
234
|
-
const cHeight = cfg.canvasHeight ||
|
|
290
|
+
const cWidth = cfg.canvasWidth || DEFAULT_CANVAS_WIDTH;
|
|
291
|
+
const cHeight = cfg.canvasHeight || DEFAULT_CANVAS_HEIGHT;
|
|
235
292
|
const safe = getSafeRect(cfg);
|
|
236
293
|
const lineHeight = cfg.lineHeight ?? 1.2;
|
|
237
294
|
const letterSpacing = cfg.letterSpacing ?? 0;
|
|
@@ -287,7 +344,7 @@ function measureTextFits(ctx, cfg, fontSize, lines) {
|
|
|
287
344
|
return layout.bounds.maxLineWidth <= safe.width + 1 && layout.bounds.textBlockHeight <= safe.height + 1;
|
|
288
345
|
}
|
|
289
346
|
function computeAutoFitFontSize(ctx, cfg, wrappedLines) {
|
|
290
|
-
const max = Math.min(cfg.fontSize ||
|
|
347
|
+
const max = Math.min(cfg.fontSize || DEFAULT_FONT_SIZE, 200);
|
|
291
348
|
let lo = 12;
|
|
292
349
|
let hi = max;
|
|
293
350
|
let best = 12;
|
|
@@ -407,29 +464,112 @@ function shouldUsePerCharFill(cfg) {
|
|
|
407
464
|
return !!cfg.perCharFillEnabled && cfg.fillType === "solid" && !cfg.customRenderer && (cfg.charFillColors?.length ?? 0) > 0;
|
|
408
465
|
}
|
|
409
466
|
|
|
410
|
-
// src/
|
|
411
|
-
|
|
412
|
-
|
|
467
|
+
// src/platform.ts
|
|
468
|
+
var _ctxFilter = null;
|
|
469
|
+
var _roundRect = null;
|
|
470
|
+
var _letterSpacing = null;
|
|
471
|
+
var _offscreenCanvas = null;
|
|
472
|
+
var _webgl2 = null;
|
|
473
|
+
function probe2d() {
|
|
474
|
+
if (typeof document === "undefined") return null;
|
|
475
|
+
try {
|
|
476
|
+
return document.createElement("canvas").getContext("2d");
|
|
477
|
+
} catch {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function supportsCtxFilter() {
|
|
482
|
+
if (_ctxFilter !== null) return _ctxFilter;
|
|
483
|
+
const ctx = probe2d();
|
|
484
|
+
if (!ctx) {
|
|
485
|
+
_ctxFilter = false;
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
try {
|
|
489
|
+
ctx.filter = "blur(4px)";
|
|
490
|
+
_ctxFilter = typeof ctx.filter === "string" && ctx.filter.includes("blur");
|
|
491
|
+
} catch {
|
|
492
|
+
_ctxFilter = false;
|
|
493
|
+
}
|
|
494
|
+
return _ctxFilter;
|
|
495
|
+
}
|
|
496
|
+
function supportsRoundRect() {
|
|
497
|
+
if (_roundRect !== null) return _roundRect;
|
|
498
|
+
const ctx = probe2d();
|
|
499
|
+
_roundRect = !!ctx && typeof ctx.roundRect === "function";
|
|
500
|
+
return _roundRect;
|
|
501
|
+
}
|
|
502
|
+
function supportsLetterSpacing() {
|
|
503
|
+
if (_letterSpacing !== null) return _letterSpacing;
|
|
504
|
+
const ctx = probe2d();
|
|
505
|
+
if (!ctx) {
|
|
506
|
+
_letterSpacing = false;
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
try {
|
|
510
|
+
ctx.letterSpacing = "2px";
|
|
511
|
+
_letterSpacing = typeof ctx.letterSpacing === "string" && ctx.letterSpacing === "2px";
|
|
512
|
+
} catch {
|
|
513
|
+
_letterSpacing = false;
|
|
514
|
+
}
|
|
515
|
+
return _letterSpacing;
|
|
516
|
+
}
|
|
517
|
+
function supportsOffscreenCanvas() {
|
|
518
|
+
if (_offscreenCanvas !== null) return _offscreenCanvas;
|
|
519
|
+
_offscreenCanvas = typeof globalThis !== "undefined" && typeof globalThis.OffscreenCanvas === "function";
|
|
520
|
+
return _offscreenCanvas;
|
|
521
|
+
}
|
|
522
|
+
function supportsWebGL2() {
|
|
523
|
+
if (_webgl2 !== null) return _webgl2;
|
|
524
|
+
if (typeof document === "undefined") {
|
|
525
|
+
_webgl2 = false;
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
try {
|
|
413
529
|
const canvas = document.createElement("canvas");
|
|
414
|
-
canvas.width =
|
|
415
|
-
canvas.height =
|
|
416
|
-
|
|
530
|
+
canvas.width = 1;
|
|
531
|
+
canvas.height = 1;
|
|
532
|
+
_webgl2 = !!canvas.getContext("webgl2");
|
|
533
|
+
} catch {
|
|
534
|
+
_webgl2 = false;
|
|
417
535
|
}
|
|
418
|
-
|
|
419
|
-
|
|
536
|
+
return _webgl2;
|
|
537
|
+
}
|
|
538
|
+
function createCanvas(width, height) {
|
|
539
|
+
if (supportsOffscreenCanvas()) {
|
|
540
|
+
return new OffscreenCanvas(width, height);
|
|
420
541
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
542
|
+
if (typeof document !== "undefined") {
|
|
543
|
+
const canvas = document.createElement("canvas");
|
|
544
|
+
canvas.width = width;
|
|
545
|
+
canvas.height = height;
|
|
546
|
+
return canvas;
|
|
424
547
|
}
|
|
548
|
+
const factory = globalThis.__clypraCreateCanvas;
|
|
549
|
+
if (factory) return factory(width, height);
|
|
425
550
|
try {
|
|
426
551
|
const nodeRequire = (0, eval)("require");
|
|
427
552
|
const nodeCanvas = nodeRequire("@napi-rs/canvas");
|
|
428
|
-
return nodeCanvas.createCanvas(
|
|
553
|
+
return nodeCanvas.createCanvas(width, height);
|
|
429
554
|
} catch {
|
|
430
|
-
throw new Error("No canvas implementation
|
|
555
|
+
throw new Error("[clypra/engine] No canvas implementation available in this environment. Set globalThis.__clypraCreateCanvas or install @napi-rs/canvas.");
|
|
431
556
|
}
|
|
432
557
|
}
|
|
558
|
+
function releaseCanvas(canvas) {
|
|
559
|
+
if (canvas instanceof OffscreenCanvas) return;
|
|
560
|
+
if (typeof document !== "undefined" && canvas.parentNode) {
|
|
561
|
+
canvas.parentNode.removeChild(canvas);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function _resetPlatformCache() {
|
|
565
|
+
_ctxFilter = null;
|
|
566
|
+
_roundRect = null;
|
|
567
|
+
_letterSpacing = null;
|
|
568
|
+
_offscreenCanvas = null;
|
|
569
|
+
_webgl2 = null;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/engine/procedural/utils.ts
|
|
433
573
|
function getCanvas2DContext(canvas) {
|
|
434
574
|
return canvas.getContext("2d");
|
|
435
575
|
}
|
|
@@ -530,8 +670,8 @@ var InkBrushEngine = class {
|
|
|
530
670
|
panelStrokeEnabled: false,
|
|
531
671
|
panelStrokeColor: "#2A2A38",
|
|
532
672
|
panelStrokeWidth: 2,
|
|
533
|
-
canvasWidth:
|
|
534
|
-
canvasHeight:
|
|
673
|
+
canvasWidth: DEFAULT_CANVAS_WIDTH,
|
|
674
|
+
canvasHeight: DEFAULT_CANVAS_HEIGHT,
|
|
535
675
|
textPosX: "center",
|
|
536
676
|
textPosY: "middle",
|
|
537
677
|
inkColor: "#FFFFFF",
|
|
@@ -786,42 +926,39 @@ var InkBrushEngine = class {
|
|
|
786
926
|
}
|
|
787
927
|
};
|
|
788
928
|
|
|
789
|
-
// src/
|
|
790
|
-
function
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
929
|
+
// src/canvas-utils.ts
|
|
930
|
+
function drawRoundedRect(ctx, x, y, w, h, r) {
|
|
931
|
+
const radius = Math.max(0, Math.min(r, Math.min(w, h) / 2));
|
|
932
|
+
ctx.beginPath();
|
|
933
|
+
if (supportsRoundRect()) {
|
|
934
|
+
ctx.roundRect(x, y, w, h, radius);
|
|
935
|
+
} else {
|
|
936
|
+
ctx.moveTo(x + radius, y);
|
|
937
|
+
ctx.lineTo(x + w - radius, y);
|
|
938
|
+
ctx.quadraticCurveTo(x + w, y, x + w, y + radius);
|
|
939
|
+
ctx.lineTo(x + w, y + h - radius);
|
|
940
|
+
ctx.quadraticCurveTo(x + w, y + h, x + w - radius, y + h);
|
|
941
|
+
ctx.lineTo(x + radius, y + h);
|
|
942
|
+
ctx.quadraticCurveTo(x, y + h, x, y + h - radius);
|
|
943
|
+
ctx.lineTo(x, y + radius);
|
|
944
|
+
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
803
945
|
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
throw new Error("No canvas implementation found in this environment.");
|
|
946
|
+
}
|
|
947
|
+
function applyLetterSpacing(ctx, value) {
|
|
948
|
+
const prev = ctx.letterSpacing ?? "0px";
|
|
949
|
+
if (value !== 0) {
|
|
950
|
+
ctx.letterSpacing = `${value}px`;
|
|
810
951
|
}
|
|
952
|
+
return prev;
|
|
953
|
+
}
|
|
954
|
+
function restoreLetterSpacing(ctx, saved) {
|
|
955
|
+
ctx.letterSpacing = saved;
|
|
811
956
|
}
|
|
957
|
+
|
|
958
|
+
// src/renderer.ts
|
|
812
959
|
function getCanvas2DContext2(canvas) {
|
|
813
960
|
return canvas.getContext("2d");
|
|
814
961
|
}
|
|
815
|
-
function hexToRgb2(hex) {
|
|
816
|
-
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
|
817
|
-
const fullHex = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b);
|
|
818
|
-
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(fullHex);
|
|
819
|
-
return result ? {
|
|
820
|
-
r: parseInt(result[1], 16),
|
|
821
|
-
g: parseInt(result[2], 16),
|
|
822
|
-
b: parseInt(result[3], 16)
|
|
823
|
-
} : { r: 255, g: 255, b: 255 };
|
|
824
|
-
}
|
|
825
962
|
function renderTextEffectCore(ctx, cfg) {
|
|
826
963
|
if (cfg.customRenderer === "InkBrushEngine") {
|
|
827
964
|
const engine = new InkBrushEngine(cfg);
|
|
@@ -831,8 +968,8 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
831
968
|
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;
|
|
832
969
|
ctx.imageSmoothingEnabled = true;
|
|
833
970
|
ctx.lineJoin = strokeLineJoin;
|
|
834
|
-
const cWidth = canvasWidth ||
|
|
835
|
-
const cHeight = canvasHeight ||
|
|
971
|
+
const cWidth = canvasWidth || DEFAULT_CANVAS_WIDTH;
|
|
972
|
+
const cHeight = canvasHeight || DEFAULT_CANVAS_HEIGHT;
|
|
836
973
|
const layout = computeTextLayout(ctx, cfg, {
|
|
837
974
|
wrap: cfg.wrapText !== false,
|
|
838
975
|
autoFit: !!cfg.autoFitText
|
|
@@ -1044,7 +1181,7 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
1044
1181
|
}
|
|
1045
1182
|
}
|
|
1046
1183
|
} else {
|
|
1047
|
-
ctx
|
|
1184
|
+
drawRoundedRect(ctx, px, py, pw, ph, panelRadius);
|
|
1048
1185
|
}
|
|
1049
1186
|
ctx.closePath();
|
|
1050
1187
|
ctx.fill();
|
|
@@ -1107,9 +1244,9 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
1107
1244
|
ctx.restore();
|
|
1108
1245
|
}
|
|
1109
1246
|
if (bevelEnabled && bevelDepth > 0) {
|
|
1110
|
-
const shadowRgb =
|
|
1111
|
-
const coreRgb =
|
|
1112
|
-
const highlightRgb =
|
|
1247
|
+
const shadowRgb = hexToRgb(bevelShadow || "#1A0A00");
|
|
1248
|
+
const coreRgb = hexToRgb(bevelCoreColor || bevelShadow || "#3A1A00");
|
|
1249
|
+
const highlightRgb = hexToRgb(bevelHighlight || "#FFFFFF");
|
|
1113
1250
|
const shadeForDepth = (t) => {
|
|
1114
1251
|
const eased = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
|
1115
1252
|
if (eased <= 0.5) {
|
|
@@ -1150,7 +1287,7 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
1150
1287
|
const t = 1 - (i - 1) / Math.max(1, bevelDepth - 1);
|
|
1151
1288
|
const aoFactor = 0.35 + 0.65 * (1 - (i - 1) / Math.max(1, bevelDepth));
|
|
1152
1289
|
const baseColor = shadeForDepth(t);
|
|
1153
|
-
const bRgb =
|
|
1290
|
+
const bRgb = hexToRgb(baseColor.startsWith("#") ? baseColor : "#000000");
|
|
1154
1291
|
const baseRgbParsed = (() => {
|
|
1155
1292
|
const m = baseColor.match(/rgb\((\d+),(\d+),(\d+)\)/);
|
|
1156
1293
|
return m ? { r: +m[1], g: +m[2], b: +m[3] } : bRgb;
|
|
@@ -1243,7 +1380,7 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
1243
1380
|
let customStrokeStyle = strokeColor;
|
|
1244
1381
|
if (sFadeRange > 0) {
|
|
1245
1382
|
const grad = ctx.createLinearGradient(0, yMin, 0, yMax);
|
|
1246
|
-
const rgb =
|
|
1383
|
+
const rgb = hexToRgb(strokeColor);
|
|
1247
1384
|
grad.addColorStop(0, `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${strokeOpacity / 100})`);
|
|
1248
1385
|
const fadeLimit = Math.min(1, sFadeRange / 100);
|
|
1249
1386
|
grad.addColorStop(fadeLimit, `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0)`);
|
|
@@ -1353,7 +1490,7 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
1353
1490
|
} else if (fillType === "pattern") {
|
|
1354
1491
|
const pType = patternType || "chalk";
|
|
1355
1492
|
const patColor = fillColor || "#ffffff";
|
|
1356
|
-
const patCanvas =
|
|
1493
|
+
const patCanvas = createCanvas(128, 128);
|
|
1357
1494
|
if (pType === "carbon") {
|
|
1358
1495
|
patCanvas.width = 8;
|
|
1359
1496
|
patCanvas.height = 8;
|
|
@@ -1787,7 +1924,7 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
1787
1924
|
};
|
|
1788
1925
|
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";
|
|
1789
1926
|
if (isInkStyle) {
|
|
1790
|
-
const tCanvas =
|
|
1927
|
+
const tCanvas = createCanvas(cWidth, cHeight);
|
|
1791
1928
|
tCanvas.width = cWidth;
|
|
1792
1929
|
tCanvas.height = cHeight;
|
|
1793
1930
|
const tCtx = getCanvas2DContext2(tCanvas);
|
|
@@ -1916,7 +2053,7 @@ function renderTextEffectCore(ctx, cfg) {
|
|
|
1916
2053
|
}
|
|
1917
2054
|
tCtx.letterSpacing = originalLSpacing;
|
|
1918
2055
|
ctx.save();
|
|
1919
|
-
const tintCanvas =
|
|
2056
|
+
const tintCanvas = createCanvas(cWidth, cHeight);
|
|
1920
2057
|
tintCanvas.width = cWidth;
|
|
1921
2058
|
tintCanvas.height = cHeight;
|
|
1922
2059
|
const tintCtx = getCanvas2DContext2(tintCanvas);
|
|
@@ -1987,7 +2124,7 @@ var defaultConfig = {
|
|
|
1987
2124
|
fontFamily: "Poppins",
|
|
1988
2125
|
fontWeight: 700,
|
|
1989
2126
|
fontStyle: "normal",
|
|
1990
|
-
fontSize:
|
|
2127
|
+
fontSize: DEFAULT_FONT_SIZE,
|
|
1991
2128
|
letterSpacing: 4,
|
|
1992
2129
|
lineHeight: 1.2,
|
|
1993
2130
|
fillType: "solid",
|
|
@@ -2047,8 +2184,8 @@ var defaultConfig = {
|
|
|
2047
2184
|
panelStrokeEnabled: false,
|
|
2048
2185
|
panelStrokeColor: "#2A2A38",
|
|
2049
2186
|
panelStrokeWidth: 2,
|
|
2050
|
-
canvasWidth:
|
|
2051
|
-
canvasHeight:
|
|
2187
|
+
canvasWidth: DEFAULT_CANVAS_WIDTH,
|
|
2188
|
+
canvasHeight: DEFAULT_CANVAS_HEIGHT,
|
|
2052
2189
|
textPosX: "center",
|
|
2053
2190
|
textPosY: "middle",
|
|
2054
2191
|
wrapText: true,
|
|
@@ -2398,42 +2535,6 @@ function checkFontVariant(variantName) {
|
|
|
2398
2535
|
return metrics.width > 0;
|
|
2399
2536
|
}
|
|
2400
2537
|
|
|
2401
|
-
// src/engine/schema.ts
|
|
2402
|
-
var SCENE_VERSION = 1;
|
|
2403
|
-
var CUSTOM_ENGINE_IDS = ["ink"];
|
|
2404
|
-
var LEGACY_RENDERER_MAP = {
|
|
2405
|
-
InkBrushEngine: "ink"
|
|
2406
|
-
};
|
|
2407
|
-
var ENGINE_ID_TO_LEGACY = {
|
|
2408
|
-
ink: "InkBrushEngine"
|
|
2409
|
-
};
|
|
2410
|
-
function createEmptyScene(overrides) {
|
|
2411
|
-
return {
|
|
2412
|
-
version: SCENE_VERSION,
|
|
2413
|
-
effectName: "My Effect",
|
|
2414
|
-
canvas: { width: 800, height: 200, background: "transparent" },
|
|
2415
|
-
text: {
|
|
2416
|
-
content: "CLYPRA",
|
|
2417
|
-
fontFamily: "Poppins",
|
|
2418
|
-
fontWeight: 700,
|
|
2419
|
-
fontStyle: "normal",
|
|
2420
|
-
fontSize: 80,
|
|
2421
|
-
letterSpacing: 4,
|
|
2422
|
-
lineHeight: 1.2,
|
|
2423
|
-
textPosX: "center",
|
|
2424
|
-
textPosY: "middle"
|
|
2425
|
-
},
|
|
2426
|
-
effectLayers: [],
|
|
2427
|
-
customEngineId: null,
|
|
2428
|
-
compositor: { blur: 0, bloom: 0, bloomThreshold: 0.6 },
|
|
2429
|
-
timeline: { duration: 2, fps: 30, loop: true, tracks: [] },
|
|
2430
|
-
...overrides
|
|
2431
|
-
};
|
|
2432
|
-
}
|
|
2433
|
-
function newLayerId() {
|
|
2434
|
-
return `layer-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
|
|
2435
|
-
}
|
|
2436
|
-
|
|
2437
2538
|
// src/engine/timelineDefaults.ts
|
|
2438
2539
|
function ensureDefaultTimeline(doc) {
|
|
2439
2540
|
if (doc.timeline.tracks.length > 0) return doc;
|
|
@@ -3092,8 +3193,8 @@ function getCompositor() {
|
|
|
3092
3193
|
function evaluateScene(doc, time, ctx, options = {}) {
|
|
3093
3194
|
const animated = applyTimelineAtTime(doc, time);
|
|
3094
3195
|
const cfg = sceneToConfig(animated);
|
|
3095
|
-
const w = cfg.canvasWidth ||
|
|
3096
|
-
const h = cfg.canvasHeight ||
|
|
3196
|
+
const w = cfg.canvasWidth || DEFAULT_CANVAS_WIDTH;
|
|
3197
|
+
const h = cfg.canvasHeight || DEFAULT_CANVAS_HEIGHT;
|
|
3097
3198
|
const filterLayers = animated.effectLayers.filter((l) => l.type === "filter" && l.enabled);
|
|
3098
3199
|
const lastFilter = filterLayers[filterLayers.length - 1]?.params;
|
|
3099
3200
|
const comp = {
|
|
@@ -3109,8 +3210,8 @@ function evaluateScene(doc, time, ctx, options = {}) {
|
|
|
3109
3210
|
finishFrame();
|
|
3110
3211
|
return;
|
|
3111
3212
|
}
|
|
3112
|
-
|
|
3113
|
-
|
|
3213
|
+
if (supportsOffscreenCanvas()) {
|
|
3214
|
+
const off = new OffscreenCanvas(w, h);
|
|
3114
3215
|
const offCtx = off.getContext("2d");
|
|
3115
3216
|
if (!offCtx) {
|
|
3116
3217
|
renderTextEffectCore(ctx, cfg);
|
|
@@ -3139,10 +3240,14 @@ function evaluateScene(doc, time, ctx, options = {}) {
|
|
|
3139
3240
|
const compositor = options.compositor ?? getCompositor();
|
|
3140
3241
|
if (compositor?.isSupported) {
|
|
3141
3242
|
compositor.renderToContext(ctx, temp, comp);
|
|
3243
|
+
temp.width = 0;
|
|
3244
|
+
temp.height = 0;
|
|
3142
3245
|
return;
|
|
3143
3246
|
}
|
|
3144
3247
|
ctx.clearRect(0, 0, w, h);
|
|
3145
3248
|
ctx.drawImage(temp, 0, 0);
|
|
3249
|
+
temp.width = 0;
|
|
3250
|
+
temp.height = 0;
|
|
3146
3251
|
return;
|
|
3147
3252
|
}
|
|
3148
3253
|
}
|
|
@@ -3153,9 +3258,9 @@ function evaluateConfig(cfg, time, ctx, options) {
|
|
|
3153
3258
|
evaluateScene(textEffectConfigToScene(cfg), time, ctx, options);
|
|
3154
3259
|
}
|
|
3155
3260
|
function advanceSceneTime(doc, steps) {
|
|
3156
|
-
const dt = 1 / (doc.timeline.fps ||
|
|
3261
|
+
const dt = 1 / (doc.timeline.fps || DEFAULT_FPS);
|
|
3157
3262
|
const next = doc._time ?? 0;
|
|
3158
|
-
const duration = doc.timeline.duration ||
|
|
3263
|
+
const duration = doc.timeline.duration || DEFAULT_DURATION;
|
|
3159
3264
|
let t = next + steps * dt;
|
|
3160
3265
|
if (doc.timeline.loop) {
|
|
3161
3266
|
t = duration > 0 ? t % duration : t;
|
|
@@ -6113,6 +6218,11 @@ function duplicateTrackAtPlayhead(doc, trackIndex, previewTime) {
|
|
|
6113
6218
|
0 && (module.exports = {
|
|
6114
6219
|
COMPOSITION_PRESETS,
|
|
6115
6220
|
CUSTOM_ENGINE_IDS,
|
|
6221
|
+
DEFAULT_CANVAS_HEIGHT,
|
|
6222
|
+
DEFAULT_CANVAS_WIDTH,
|
|
6223
|
+
DEFAULT_DURATION,
|
|
6224
|
+
DEFAULT_FONT_SIZE,
|
|
6225
|
+
DEFAULT_FPS,
|
|
6116
6226
|
DEFAULT_TEXT_STYLE,
|
|
6117
6227
|
EMPHASIS_PRESETS,
|
|
6118
6228
|
ENGINE_ID_TO_LEGACY,
|
|
@@ -6130,6 +6240,7 @@ function duplicateTrackAtPlayhead(doc, trackIndex, previewTime) {
|
|
|
6130
6240
|
TextEffectRenderer,
|
|
6131
6241
|
WEBM_EXPORT_MAX_FRAMES,
|
|
6132
6242
|
WebGLCompositor,
|
|
6243
|
+
_resetPlatformCache,
|
|
6133
6244
|
addImageLayer,
|
|
6134
6245
|
addKeyframeAtTime,
|
|
6135
6246
|
addOrUpdateKeyframe,
|
|
@@ -6141,6 +6252,7 @@ function duplicateTrackAtPlayhead(doc, trackIndex, previewTime) {
|
|
|
6141
6252
|
advanceSceneTime,
|
|
6142
6253
|
alignToLottieJ,
|
|
6143
6254
|
applyFillColorToAll,
|
|
6255
|
+
applyLetterSpacing,
|
|
6144
6256
|
applyMaskReveal,
|
|
6145
6257
|
applyRecipeToScene,
|
|
6146
6258
|
applyStyleToLottie,
|
|
@@ -6166,6 +6278,7 @@ function duplicateTrackAtPlayhead(doc, trackIndex, previewTime) {
|
|
|
6166
6278
|
computeTextLayout,
|
|
6167
6279
|
countTextGlyphs,
|
|
6168
6280
|
createBlankLottie,
|
|
6281
|
+
createCanvas,
|
|
6169
6282
|
createDefaultRevealTrack,
|
|
6170
6283
|
createEmptyScene,
|
|
6171
6284
|
createPulseOpacityTrack,
|
|
@@ -6176,6 +6289,7 @@ function duplicateTrackAtPlayhead(doc, trackIndex, previewTime) {
|
|
|
6176
6289
|
downloadPngSequenceZip,
|
|
6177
6290
|
downloadSceneWebM,
|
|
6178
6291
|
drawPerCharText,
|
|
6292
|
+
drawRoundedRect,
|
|
6179
6293
|
duplicateTrackAtPlayhead,
|
|
6180
6294
|
ease,
|
|
6181
6295
|
enableKeyframing,
|
|
@@ -6223,6 +6337,7 @@ function duplicateTrackAtPlayhead(doc, trackIndex, previewTime) {
|
|
|
6223
6337
|
readLayerScalar,
|
|
6224
6338
|
readStyleFromLottieLayer,
|
|
6225
6339
|
reindexLayers,
|
|
6340
|
+
releaseCanvas,
|
|
6226
6341
|
removeKeyframe,
|
|
6227
6342
|
removeTrack,
|
|
6228
6343
|
renderPngSequence,
|
|
@@ -6232,6 +6347,7 @@ function duplicateTrackAtPlayhead(doc, trackIndex, previewTime) {
|
|
|
6232
6347
|
resizeCharFillColors,
|
|
6233
6348
|
resolveAnimatedScalar,
|
|
6234
6349
|
resolveCustomEngineId,
|
|
6350
|
+
restoreLetterSpacing,
|
|
6235
6351
|
scanLottieFonts,
|
|
6236
6352
|
scanTextLayers,
|
|
6237
6353
|
sceneToConfig,
|
|
@@ -6241,6 +6357,11 @@ function duplicateTrackAtPlayhead(doc, trackIndex, previewTime) {
|
|
|
6241
6357
|
shouldUsePerCharFill,
|
|
6242
6358
|
snapshotScene,
|
|
6243
6359
|
sortKeyframes,
|
|
6360
|
+
supportsCtxFilter,
|
|
6361
|
+
supportsLetterSpacing,
|
|
6362
|
+
supportsOffscreenCanvas,
|
|
6363
|
+
supportsRoundRect,
|
|
6364
|
+
supportsWebGL2,
|
|
6244
6365
|
syncCompositorFromScene,
|
|
6245
6366
|
textEffectConfigToScene,
|
|
6246
6367
|
trackId,
|