@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.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 || 800;
21
- const h = cfg.canvasHeight || 200;
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 || 800;
69
- const cHeight = cfg.canvasHeight || 200;
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 + (lines.length - 1) * fontSize * lineHeight;
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 || 80, 200);
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/engine/procedural/utils.ts
246
- function createCanvas(w, h) {
247
- if (typeof document !== "undefined") {
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 = w;
250
- canvas.height = h;
251
- return canvas;
349
+ canvas.width = 1;
350
+ canvas.height = 1;
351
+ _webgl2 = !!canvas.getContext("webgl2");
352
+ } catch {
353
+ _webgl2 = false;
252
354
  }
253
- if (typeof OffscreenCanvas !== "undefined") {
254
- return new OffscreenCanvas(w, h);
355
+ return _webgl2;
356
+ }
357
+ function createCanvas(width, height) {
358
+ if (supportsOffscreenCanvas()) {
359
+ return new OffscreenCanvas(width, height);
255
360
  }
256
- const runtimeCanvasFactory = globalThis.__clypraCreateCanvas;
257
- if (runtimeCanvasFactory) {
258
- return runtimeCanvasFactory(w, h);
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(w, h);
372
+ return nodeCanvas.createCanvas(width, height);
264
373
  } catch {
265
- throw new Error("No canvas implementation found in this environment.");
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: 800,
369
- canvasHeight: 200,
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/renderer.ts
625
- function createCanvas2(w, h) {
626
- if (typeof document !== "undefined") {
627
- const canvas = document.createElement("canvas");
628
- canvas.width = w;
629
- canvas.height = h;
630
- return canvas;
631
- }
632
- if (typeof OffscreenCanvas !== "undefined") {
633
- return new OffscreenCanvas(w, h);
634
- }
635
- const runtimeCanvasFactory = globalThis.__clypraCreateCanvas;
636
- if (runtimeCanvasFactory) {
637
- return runtimeCanvasFactory(w, h);
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
- try {
640
- const nodeRequire = (0, eval)("require");
641
- const nodeCanvas = nodeRequire("@napi-rs/canvas");
642
- return nodeCanvas.createCanvas(w, h);
643
- } catch {
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 || 800;
670
- const cHeight = canvasHeight || 200;
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.roundRect(px, py, pw, ph, panelRadius);
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 = hexToRgb2(bevelShadow || "#1A0A00");
946
- const coreRgb = hexToRgb2(bevelCoreColor || bevelShadow || "#3A1A00");
947
- const highlightRgb = hexToRgb2(bevelHighlight || "#FFFFFF");
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 = hexToRgb2(baseColor.startsWith("#") ? baseColor : "#000000");
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 = hexToRgb2(strokeColor);
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 = createCanvas2(128, 128);
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 = createCanvas2(cWidth, cHeight);
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 = createCanvas2(cWidth, cHeight);
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, "transparent", layer2.spread ?? 0);
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, "transparent");
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: 80,
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: 800,
1886
- canvasHeight: 200,
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
- const canvas = document.createElement("canvas");
2229
- const ctx = canvas.getContext("2d");
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 || 800;
2931
- const h = cfg.canvasHeight || 200;
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
- const off = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(w, h) : null;
2948
- if (off) {
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 || 30);
3079
+ const dt = 1 / (doc.timeline.fps || DEFAULT_FPS);
2992
3080
  const next = doc._time ?? 0;
2993
- const duration = doc.timeline.duration || 2;
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,