@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 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,16 +81,19 @@ __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,
80
88
  defaultConfig: () => defaultConfig,
81
89
  deleteKeyframe: () => deleteKeyframe,
90
+ disposeSharedCompositor: () => disposeSharedCompositor,
82
91
  downloadDotLottie: () => downloadDotLottie,
83
92
  downloadLottieJson: () => downloadLottieJson,
84
93
  downloadPngSequenceZip: () => downloadPngSequenceZip,
85
94
  downloadSceneWebM: () => downloadSceneWebM,
86
95
  drawPerCharText: () => drawPerCharText,
96
+ drawRoundedRect: () => drawRoundedRect,
87
97
  duplicateTrackAtPlayhead: () => duplicateTrackAtPlayhead,
88
98
  ease: () => ease,
89
99
  enableKeyframing: () => enableKeyframing,
@@ -131,6 +141,7 @@ __export(index_exports, {
131
141
  readLayerScalar: () => readLayerScalar,
132
142
  readStyleFromLottieLayer: () => readStyleFromLottieLayer,
133
143
  reindexLayers: () => reindexLayers,
144
+ releaseCanvas: () => releaseCanvas,
134
145
  removeKeyframe: () => removeKeyframe,
135
146
  removeTrack: () => removeTrack,
136
147
  renderPngSequence: () => renderPngSequence,
@@ -140,6 +151,7 @@ __export(index_exports, {
140
151
  resizeCharFillColors: () => resizeCharFillColors,
141
152
  resolveAnimatedScalar: () => resolveAnimatedScalar,
142
153
  resolveCustomEngineId: () => resolveCustomEngineId,
154
+ restoreLetterSpacing: () => restoreLetterSpacing,
143
155
  scanLottieFonts: () => scanLottieFonts,
144
156
  scanTextLayers: () => scanTextLayers,
145
157
  sceneToConfig: () => sceneToConfig,
@@ -149,6 +161,11 @@ __export(index_exports, {
149
161
  shouldUsePerCharFill: () => shouldUsePerCharFill,
150
162
  snapshotScene: () => snapshotScene,
151
163
  sortKeyframes: () => sortKeyframes,
164
+ supportsCtxFilter: () => supportsCtxFilter,
165
+ supportsLetterSpacing: () => supportsLetterSpacing,
166
+ supportsOffscreenCanvas: () => supportsOffscreenCanvas,
167
+ supportsRoundRect: () => supportsRoundRect,
168
+ supportsWebGL2: () => supportsWebGL2,
152
169
  syncCompositorFromScene: () => syncCompositorFromScene,
153
170
  textEffectConfigToScene: () => textEffectConfigToScene,
154
171
  trackId: () => trackId,
@@ -163,6 +180,47 @@ __export(index_exports, {
163
180
  });
164
181
  module.exports = __toCommonJS(index_exports);
165
182
 
183
+ // src/engine/schema.ts
184
+ var SCENE_VERSION = 1;
185
+ var DEFAULT_CANVAS_WIDTH = 800;
186
+ var DEFAULT_CANVAS_HEIGHT = 200;
187
+ var DEFAULT_FONT_SIZE = 80;
188
+ var DEFAULT_FPS = 30;
189
+ var DEFAULT_DURATION = 2;
190
+ var CUSTOM_ENGINE_IDS = ["ink"];
191
+ var LEGACY_RENDERER_MAP = {
192
+ InkBrushEngine: "ink"
193
+ };
194
+ var ENGINE_ID_TO_LEGACY = {
195
+ ink: "InkBrushEngine"
196
+ };
197
+ function createEmptyScene(overrides) {
198
+ return {
199
+ version: SCENE_VERSION,
200
+ effectName: "My Effect",
201
+ canvas: { width: DEFAULT_CANVAS_WIDTH, height: DEFAULT_CANVAS_HEIGHT, background: "transparent" },
202
+ text: {
203
+ content: "CLYPRA",
204
+ fontFamily: "Poppins",
205
+ fontWeight: 700,
206
+ fontStyle: "normal",
207
+ fontSize: DEFAULT_FONT_SIZE,
208
+ letterSpacing: 4,
209
+ lineHeight: 1.2,
210
+ textPosX: "center",
211
+ textPosY: "middle"
212
+ },
213
+ effectLayers: [],
214
+ customEngineId: null,
215
+ compositor: { blur: 0, bloom: 0, bloomThreshold: 0.6 },
216
+ timeline: { duration: DEFAULT_DURATION, fps: DEFAULT_FPS, loop: true, tracks: [] },
217
+ ...overrides
218
+ };
219
+ }
220
+ function newLayerId() {
221
+ return `layer-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
222
+ }
223
+
166
224
  // src/engine/textLayout.ts
167
225
  var COMPOSITION_PRESETS = [
168
226
  { id: "banner", label: "Banner", width: 800, height: 200, description: "Lower third / title bar" },
@@ -182,8 +240,8 @@ function measureLine(ctx, line, letterSpacing) {
182
240
  return w;
183
241
  }
184
242
  function getSafeRect(cfg) {
185
- const w = cfg.canvasWidth || 800;
186
- const h = cfg.canvasHeight || 200;
243
+ const w = cfg.canvasWidth || DEFAULT_CANVAS_WIDTH;
244
+ const h = cfg.canvasHeight || DEFAULT_CANVAS_HEIGHT;
187
245
  const marginX = cfg.panelEnabled ? (cfg.panelPaddingX ?? 40) + 16 : Math.min(48, w * 0.06);
188
246
  const marginY = cfg.panelEnabled ? (cfg.panelPaddingY ?? 20) + 16 : Math.min(40, h * 0.1);
189
247
  return {
@@ -230,8 +288,8 @@ function wrapTextToWidth(ctx, text, maxWidth, letterSpacing) {
230
288
  return lines.length > 0 ? lines : [""];
231
289
  }
232
290
  function layoutWithFontSize(ctx, cfg, fontSize, lines) {
233
- const cWidth = cfg.canvasWidth || 800;
234
- const cHeight = cfg.canvasHeight || 200;
291
+ const cWidth = cfg.canvasWidth || DEFAULT_CANVAS_WIDTH;
292
+ const cHeight = cfg.canvasHeight || DEFAULT_CANVAS_HEIGHT;
235
293
  const safe = getSafeRect(cfg);
236
294
  const lineHeight = cfg.lineHeight ?? 1.2;
237
295
  const letterSpacing = cfg.letterSpacing ?? 0;
@@ -239,7 +297,7 @@ function layoutWithFontSize(ctx, cfg, fontSize, lines) {
239
297
  ctx.font = fontStr;
240
298
  const lineWidths = lines.map((line) => measureLine(ctx, line, letterSpacing));
241
299
  const maxLineWidth = Math.max(...lineWidths, 1);
242
- const textBlockHeight = fontSize + (lines.length - 1) * fontSize * lineHeight;
300
+ const textBlockHeight = lines.length === 1 ? fontSize : (lines.length - 1) * fontSize * lineHeight + fontSize;
243
301
  let align = "center";
244
302
  let startX = cWidth / 2;
245
303
  if (cfg.textPosX === "left") {
@@ -287,7 +345,7 @@ function measureTextFits(ctx, cfg, fontSize, lines) {
287
345
  return layout.bounds.maxLineWidth <= safe.width + 1 && layout.bounds.textBlockHeight <= safe.height + 1;
288
346
  }
289
347
  function computeAutoFitFontSize(ctx, cfg, wrappedLines) {
290
- const max = Math.min(cfg.fontSize || 80, 200);
348
+ const max = Math.min(cfg.fontSize || DEFAULT_FONT_SIZE, 200);
291
349
  let lo = 12;
292
350
  let hi = max;
293
351
  let best = 12;
@@ -407,29 +465,111 @@ function shouldUsePerCharFill(cfg) {
407
465
  return !!cfg.perCharFillEnabled && cfg.fillType === "solid" && !cfg.customRenderer && (cfg.charFillColors?.length ?? 0) > 0;
408
466
  }
409
467
 
410
- // src/engine/procedural/utils.ts
411
- function createCanvas(w, h) {
412
- if (typeof document !== "undefined") {
468
+ // src/platform.ts
469
+ var _ctxFilter = null;
470
+ var _roundRect = null;
471
+ var _letterSpacing = null;
472
+ var _offscreenCanvas = null;
473
+ var _webgl2 = null;
474
+ function probe2d() {
475
+ if (typeof document === "undefined") return null;
476
+ try {
477
+ return document.createElement("canvas").getContext("2d");
478
+ } catch {
479
+ return null;
480
+ }
481
+ }
482
+ function supportsCtxFilter() {
483
+ if (_ctxFilter !== null) return _ctxFilter;
484
+ const ctx = probe2d();
485
+ if (!ctx) {
486
+ _ctxFilter = false;
487
+ return false;
488
+ }
489
+ try {
490
+ ctx.filter = "blur(4px)";
491
+ _ctxFilter = typeof ctx.filter === "string" && ctx.filter.includes("blur");
492
+ } catch {
493
+ _ctxFilter = false;
494
+ }
495
+ return _ctxFilter;
496
+ }
497
+ function supportsRoundRect() {
498
+ if (_roundRect !== null) return _roundRect;
499
+ const ctx = probe2d();
500
+ _roundRect = !!ctx && typeof ctx.roundRect === "function";
501
+ return _roundRect;
502
+ }
503
+ function supportsLetterSpacing() {
504
+ if (_letterSpacing !== null) return _letterSpacing;
505
+ const ctx = probe2d();
506
+ if (!ctx) {
507
+ _letterSpacing = false;
508
+ return false;
509
+ }
510
+ try {
511
+ ctx.letterSpacing = "2px";
512
+ _letterSpacing = typeof ctx.letterSpacing === "string" && ctx.letterSpacing === "2px";
513
+ } catch {
514
+ _letterSpacing = false;
515
+ }
516
+ return _letterSpacing;
517
+ }
518
+ function supportsOffscreenCanvas() {
519
+ if (_offscreenCanvas !== null) return _offscreenCanvas;
520
+ _offscreenCanvas = typeof globalThis !== "undefined" && typeof globalThis.OffscreenCanvas === "function";
521
+ return _offscreenCanvas;
522
+ }
523
+ function supportsWebGL2() {
524
+ if (_webgl2 !== null) return _webgl2;
525
+ if (typeof document === "undefined") {
526
+ _webgl2 = false;
527
+ return false;
528
+ }
529
+ try {
413
530
  const canvas = document.createElement("canvas");
414
- canvas.width = w;
415
- canvas.height = h;
416
- return canvas;
531
+ canvas.width = 1;
532
+ canvas.height = 1;
533
+ _webgl2 = !!canvas.getContext("webgl2");
534
+ } catch {
535
+ _webgl2 = false;
417
536
  }
418
- if (typeof OffscreenCanvas !== "undefined") {
419
- return new OffscreenCanvas(w, h);
537
+ return _webgl2;
538
+ }
539
+ function createCanvas(width, height) {
540
+ if (supportsOffscreenCanvas()) {
541
+ return new OffscreenCanvas(width, height);
420
542
  }
421
- const runtimeCanvasFactory = globalThis.__clypraCreateCanvas;
422
- if (runtimeCanvasFactory) {
423
- return runtimeCanvasFactory(w, h);
543
+ if (typeof document !== "undefined") {
544
+ const canvas = document.createElement("canvas");
545
+ canvas.width = width;
546
+ canvas.height = height;
547
+ return canvas;
424
548
  }
549
+ const factory = globalThis.__clypraCreateCanvas;
550
+ if (factory) return factory(width, height);
425
551
  try {
426
552
  const nodeRequire = (0, eval)("require");
427
553
  const nodeCanvas = nodeRequire("@napi-rs/canvas");
428
- return nodeCanvas.createCanvas(w, h);
554
+ return nodeCanvas.createCanvas(width, height);
429
555
  } catch {
430
- throw new Error("No canvas implementation found in this environment.");
556
+ throw new Error("[clypra/engine] No canvas implementation available in this environment. Set globalThis.__clypraCreateCanvas or install @napi-rs/canvas.");
431
557
  }
432
558
  }
559
+ function releaseCanvas(canvas) {
560
+ if (canvas instanceof OffscreenCanvas) return;
561
+ canvas.width = 0;
562
+ canvas.height = 0;
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);
@@ -1949,7 +2086,7 @@ function renderTextEffectCore(ctx, cfg) {
1949
2086
  ctx.globalCompositeOperation = "source-atop";
1950
2087
  const renderCount = Math.max(1, Math.min(20, layer2.strength ?? 1));
1951
2088
  for (let i = 0; i < renderCount; i++) {
1952
- renderWithShadowTrick("fill", layer2.color, layer2.blur, 0, 0, layer2.opacity, "transparent", layer2.spread ?? 0);
2089
+ renderWithShadowTrick("fill", layer2.color, layer2.blur, 0, 0, layer2.opacity, "#000000", layer2.spread ?? 0);
1953
2090
  }
1954
2091
  ctx.restore();
1955
2092
  }
@@ -1957,7 +2094,7 @@ function renderTextEffectCore(ctx, cfg) {
1957
2094
  if (shadowEnabled && shadowType === "inner" && shadowOpacity > 0) {
1958
2095
  ctx.save();
1959
2096
  ctx.globalCompositeOperation = "source-atop";
1960
- renderWithShadowTrick("fill", shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, shadowOpacity, "transparent");
2097
+ renderWithShadowTrick("fill", shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, shadowOpacity, "#000000");
1961
2098
  ctx.restore();
1962
2099
  }
1963
2100
  if (isGlitch) {
@@ -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,
@@ -2390,48 +2527,8 @@ async function initializeFontSystem() {
2390
2527
  }
2391
2528
  }
2392
2529
  function checkFontVariant(variantName) {
2393
- const canvas = document.createElement("canvas");
2394
- const ctx = canvas.getContext("2d");
2395
- if (!ctx) return false;
2396
- ctx.font = `16px "${variantName}"`;
2397
- const metrics = ctx.measureText("Test");
2398
- return metrics.width > 0;
2399
- }
2400
-
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)}`;
2530
+ if (typeof document === "undefined" || !document.fonts) return false;
2531
+ return document.fonts.check(`16px "${variantName}"`);
2435
2532
  }
2436
2533
 
2437
2534
  // src/engine/timelineDefaults.ts
@@ -3089,11 +3186,15 @@ function getCompositor() {
3089
3186
  }
3090
3187
  return sharedCompositor;
3091
3188
  }
3189
+ function disposeSharedCompositor() {
3190
+ sharedCompositor?.dispose();
3191
+ sharedCompositor = null;
3192
+ }
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,16 +6278,19 @@ 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,
6172
6285
  defaultConfig,
6173
6286
  deleteKeyframe,
6287
+ disposeSharedCompositor,
6174
6288
  downloadDotLottie,
6175
6289
  downloadLottieJson,
6176
6290
  downloadPngSequenceZip,
6177
6291
  downloadSceneWebM,
6178
6292
  drawPerCharText,
6293
+ drawRoundedRect,
6179
6294
  duplicateTrackAtPlayhead,
6180
6295
  ease,
6181
6296
  enableKeyframing,
@@ -6223,6 +6338,7 @@ function duplicateTrackAtPlayhead(doc, trackIndex, previewTime) {
6223
6338
  readLayerScalar,
6224
6339
  readStyleFromLottieLayer,
6225
6340
  reindexLayers,
6341
+ releaseCanvas,
6226
6342
  removeKeyframe,
6227
6343
  removeTrack,
6228
6344
  renderPngSequence,
@@ -6232,6 +6348,7 @@ function duplicateTrackAtPlayhead(doc, trackIndex, previewTime) {
6232
6348
  resizeCharFillColors,
6233
6349
  resolveAnimatedScalar,
6234
6350
  resolveCustomEngineId,
6351
+ restoreLetterSpacing,
6235
6352
  scanLottieFonts,
6236
6353
  scanTextLayers,
6237
6354
  sceneToConfig,
@@ -6241,6 +6358,11 @@ function duplicateTrackAtPlayhead(doc, trackIndex, previewTime) {
6241
6358
  shouldUsePerCharFill,
6242
6359
  snapshotScene,
6243
6360
  sortKeyframes,
6361
+ supportsCtxFilter,
6362
+ supportsLetterSpacing,
6363
+ supportsOffscreenCanvas,
6364
+ supportsRoundRect,
6365
+ supportsWebGL2,
6244
6366
  syncCompositorFromScene,
6245
6367
  textEffectConfigToScene,
6246
6368
  trackId,