@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/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 || 800;
186
- const h = cfg.canvasHeight || 200;
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 || 800;
234
- const cHeight = cfg.canvasHeight || 200;
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 || 80, 200);
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/engine/procedural/utils.ts
411
- function createCanvas(w, h) {
412
- if (typeof document !== "undefined") {
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 = w;
415
- canvas.height = h;
416
- return canvas;
530
+ canvas.width = 1;
531
+ canvas.height = 1;
532
+ _webgl2 = !!canvas.getContext("webgl2");
533
+ } catch {
534
+ _webgl2 = false;
417
535
  }
418
- if (typeof OffscreenCanvas !== "undefined") {
419
- return new OffscreenCanvas(w, h);
536
+ return _webgl2;
537
+ }
538
+ function createCanvas(width, height) {
539
+ if (supportsOffscreenCanvas()) {
540
+ return new OffscreenCanvas(width, height);
420
541
  }
421
- const runtimeCanvasFactory = globalThis.__clypraCreateCanvas;
422
- if (runtimeCanvasFactory) {
423
- return runtimeCanvasFactory(w, h);
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(w, h);
553
+ return nodeCanvas.createCanvas(width, height);
429
554
  } catch {
430
- throw new Error("No canvas implementation found in this environment.");
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: 800,
534
- canvasHeight: 200,
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/renderer.ts
790
- function createCanvas2(w, h) {
791
- if (typeof document !== "undefined") {
792
- const canvas = document.createElement("canvas");
793
- canvas.width = w;
794
- canvas.height = h;
795
- return canvas;
796
- }
797
- if (typeof OffscreenCanvas !== "undefined") {
798
- return new OffscreenCanvas(w, h);
799
- }
800
- const runtimeCanvasFactory = globalThis.__clypraCreateCanvas;
801
- if (runtimeCanvasFactory) {
802
- return runtimeCanvasFactory(w, h);
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
- try {
805
- const nodeRequire = (0, eval)("require");
806
- const nodeCanvas = nodeRequire("@napi-rs/canvas");
807
- return nodeCanvas.createCanvas(w, h);
808
- } catch {
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 || 800;
835
- const cHeight = canvasHeight || 200;
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.roundRect(px, py, pw, ph, panelRadius);
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 = hexToRgb2(bevelShadow || "#1A0A00");
1111
- const coreRgb = hexToRgb2(bevelCoreColor || bevelShadow || "#3A1A00");
1112
- const highlightRgb = hexToRgb2(bevelHighlight || "#FFFFFF");
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 = hexToRgb2(baseColor.startsWith("#") ? baseColor : "#000000");
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 = hexToRgb2(strokeColor);
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 = createCanvas2(128, 128);
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 = createCanvas2(cWidth, cHeight);
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 = createCanvas2(cWidth, cHeight);
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: 80,
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: 800,
2051
- canvasHeight: 200,
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 || 800;
3096
- const h = cfg.canvasHeight || 200;
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
- const off = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(w, h) : null;
3113
- if (off) {
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 || 30);
3261
+ const dt = 1 / (doc.timeline.fps || DEFAULT_FPS);
3157
3262
  const next = doc._time ?? 0;
3158
- const duration = doc.timeline.duration || 2;
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,