tegaki 0.13.0 → 0.14.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # tegaki
2
2
 
3
+ ## 0.14.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 79a0e6a: Add Nuxt module and usage example. Fixes [#35](https://github.com/KurtGokhan/tegaki/issues/35).
8
+ - 9a0d74a: Add `quality.smoothing` option that interpolates stroke points with a centripetal Catmull-Rom spline, hiding the faceted corners visible at large render sizes where the baked polyline resolution shows through. Enabling it forces subdivision on (default `segmentSize=2` CSS px) and rebuilds the subdivision cache; the original points stay on the curve, so animation timing and wobble phase are unchanged. Default is `false` (existing bundles render identically). Also exposed on the web component as the `smoothing` attribute.
9
+
10
+ ### Patch Changes
11
+
12
+ - 84ad2b2: text layout was broken when element had transform applied
13
+ - b6967aa: canvas was not cleared when all text removed
14
+
3
15
  ## 0.13.0
4
16
 
5
17
  ### Minor Changes
@@ -1,2 +1,2 @@
1
- import { A as LineCap, B as TegakiSingletonEffectName, C as resolveEffects, D as CSSLength, E as COMPATIBLE_BUNDLE_VERSIONS, F as TegakiEffectConfigs, I as TegakiEffectName, L as TegakiEffects, M as Point, N as Stroke, O as FontOutput, P as TegakiBundle, R as TegakiGlyphData, S as ResolvedEffect, T as BUNDLE_VERSION, V as TimedPoint, _ as computeTimeline, a as CreateElementFn, b as ensureFontFace, c as TimeControlMode, d as getBundle, f as registerBundle, g as TimelineEntry, h as TimelineConfig, i as TegakiEngine, j as PathCommand, k as GlyphData, l as TimeControlProp, m as Timeline, n as buildRootProps, o as TegakiEngineOptions, p as resolveBundle, r as domCreateElement, s as TegakiQuality, t as buildChildren, u as createBundle, v as TextLayout, w as BBox, x as drawGlyph, y as computeTextLayout, z as TegakiMultiEffectName } from "../index-e-RN9Gi3.mjs";
1
+ import { A as LineCap, B as TegakiSingletonEffectName, C as resolveEffects, D as CSSLength, E as COMPATIBLE_BUNDLE_VERSIONS, F as TegakiEffectConfigs, I as TegakiEffectName, L as TegakiEffects, M as Point, N as Stroke, O as FontOutput, P as TegakiBundle, R as TegakiGlyphData, S as ResolvedEffect, T as BUNDLE_VERSION, V as TimedPoint, _ as computeTimeline, a as CreateElementFn, b as ensureFontFace, c as TimeControlMode, d as getBundle, f as registerBundle, g as TimelineEntry, h as TimelineConfig, i as TegakiEngine, j as PathCommand, k as GlyphData, l as TimeControlProp, m as Timeline, n as buildRootProps, o as TegakiEngineOptions, p as resolveBundle, r as domCreateElement, s as TegakiQuality, t as buildChildren, u as createBundle, v as TextLayout, w as BBox, x as drawGlyph, y as computeTextLayout, z as TegakiMultiEffectName } from "../index-BBRejOMe.mjs";
2
2
  export { BBox, BUNDLE_VERSION, COMPATIBLE_BUNDLE_VERSIONS, CSSLength, CreateElementFn, FontOutput, GlyphData, LineCap, PathCommand, Point, ResolvedEffect, Stroke, TegakiBundle, TegakiEffectConfigs, TegakiEffectName, TegakiEffects, TegakiEngine, TegakiEngineOptions, TegakiGlyphData, TegakiMultiEffectName, TegakiQuality, TegakiSingletonEffectName, TextLayout, TimeControlMode, TimeControlProp, TimedPoint, Timeline, TimelineConfig, TimelineEntry, buildChildren, buildRootProps, computeTextLayout, computeTimeline, createBundle, domCreateElement, drawGlyph, ensureFontFace, getBundle, registerBundle, resolveBundle, resolveEffects };
@@ -1,2 +1,2 @@
1
- import { a as createBundle, c as resolveBundle, d as computeTimeline, f as computeTextLayout, g as resolveEffects, i as domCreateElement, l as BUNDLE_VERSION, m as drawGlyph, n as buildChildren, o as getBundle, p as ensureFontFace, r as buildRootProps, s as registerBundle, t as TegakiEngine, u as COMPATIBLE_BUNDLE_VERSIONS } from "../core-Ds9ohwR7.mjs";
1
+ import { a as createBundle, c as resolveBundle, d as computeTimeline, f as computeTextLayout, g as resolveEffects, i as domCreateElement, l as BUNDLE_VERSION, m as drawGlyph, n as buildChildren, o as getBundle, p as ensureFontFace, r as buildRootProps, s as registerBundle, t as TegakiEngine, u as COMPATIBLE_BUNDLE_VERSIONS } from "../core-BYU5BEaZ.mjs";
2
2
  export { BUNDLE_VERSION, COMPATIBLE_BUNDLE_VERSIONS, TegakiEngine, buildChildren, buildRootProps, computeTextLayout, computeTimeline, createBundle, domCreateElement, drawGlyph, ensureFontFace, getBundle, registerBundle, resolveBundle, resolveEffects };
@@ -54,17 +54,98 @@ function findEffects(effects, name) {
54
54
  return effects.filter((e) => e.effect === name);
55
55
  }
56
56
  //#endregion
57
+ //#region src/lib/catmullRom.ts
58
+ /**
59
+ * Sample `count` points along a centripetal Catmull-Rom segment from `p1` to
60
+ * `p2`, with neighbor control points `p0` and `p3`. Samples are emitted at
61
+ * `u = k/count` for `k = 1..count`, so the last sample equals `p2`.
62
+ *
63
+ * Centripetal parameterization (α = 0.5) avoids the cusps and self-loops that
64
+ * uniform/chordal Catmull-Rom can produce on sharp corners — relevant for the
65
+ * baked RDP-simplified polylines the renderer consumes.
66
+ *
67
+ * For endpoint segments where a neighbor is missing, pass a phantom point
68
+ * built with {@link reflect}. Zero-length chords are clamped to a tiny epsilon
69
+ * so the knot parameterization stays non-degenerate.
70
+ */
71
+ function sampleCatmullRom(p0, p1, p2, p3, count) {
72
+ const d01 = Math.max(dist(p0, p1), 1e-6);
73
+ const d12 = Math.max(dist(p1, p2), 1e-6);
74
+ const d23 = Math.max(dist(p2, p3), 1e-6);
75
+ const t0 = 0;
76
+ const t1 = t0 + Math.sqrt(d01);
77
+ const t2 = t1 + Math.sqrt(d12);
78
+ const t3 = t2 + Math.sqrt(d23);
79
+ const out = new Array(count);
80
+ for (let k = 1; k <= count; k++) {
81
+ const t = t1 + k / count * (t2 - t1);
82
+ out[k - 1] = evalBarryGoldman(p0, p1, p2, p3, t0, t1, t2, t3, t);
83
+ }
84
+ return out;
85
+ }
86
+ /**
87
+ * Reflect `p` across `anchor` to produce a phantom neighbor for endpoint
88
+ * segments. The result lies on the extension of (p, anchor) past `anchor` at
89
+ * the same distance — equivalent to a zero-curvature extrapolation, which
90
+ * gives a natural straight start/end tangent.
91
+ */
92
+ function reflect(anchor, p) {
93
+ return [
94
+ 2 * anchor[0] - p[0],
95
+ 2 * anchor[1] - p[1],
96
+ anchor[2]
97
+ ];
98
+ }
99
+ function dist(a, b) {
100
+ const dx = b[0] - a[0];
101
+ const dy = b[1] - a[1];
102
+ return Math.sqrt(dx * dx + dy * dy);
103
+ }
104
+ function evalBarryGoldman(p0, p1, p2, p3, t0, t1, t2, t3, t) {
105
+ const a1x = lerp(p0[0], p1[0], t0, t1, t);
106
+ const a1y = lerp(p0[1], p1[1], t0, t1, t);
107
+ const a1w = lerp(p0[2], p1[2], t0, t1, t);
108
+ const a2x = lerp(p1[0], p2[0], t1, t2, t);
109
+ const a2y = lerp(p1[1], p2[1], t1, t2, t);
110
+ const a2w = lerp(p1[2], p2[2], t1, t2, t);
111
+ const a3x = lerp(p2[0], p3[0], t2, t3, t);
112
+ const a3y = lerp(p2[1], p3[1], t2, t3, t);
113
+ const a3w = lerp(p2[2], p3[2], t2, t3, t);
114
+ const b1x = lerp(a1x, a2x, t0, t2, t);
115
+ const b1y = lerp(a1y, a2y, t0, t2, t);
116
+ const b1w = lerp(a1w, a2w, t0, t2, t);
117
+ const b2x = lerp(a2x, a3x, t1, t3, t);
118
+ const b2y = lerp(a2y, a3y, t1, t3, t);
119
+ const b2w = lerp(a2w, a3w, t1, t3, t);
120
+ return {
121
+ x: lerp(b1x, b2x, t1, t2, t),
122
+ y: lerp(b1y, b2y, t1, t2, t),
123
+ width: lerp(b1w, b2w, t1, t2, t)
124
+ };
125
+ }
126
+ function lerp(a, b, ta, tb, t) {
127
+ const span = tb - ta;
128
+ if (span === 0) return a;
129
+ return a + (b - a) * ((t - ta) / span);
130
+ }
131
+ //#endregion
57
132
  //#region src/lib/strokeCache.ts
58
133
  /**
59
134
  * Subdivide a stroke so that no sub-segment exceeds `maxSegLen` font units.
60
135
  * Pass `Infinity` (or any non-finite value) to skip subdivision and return
61
136
  * the raw polyline.
62
137
  *
63
- * Output depends only on `(stroke.p, maxSegLen)` not on position, seed,
64
- * progress, or effect config so it can be cached and shared across every
65
- * instance of the same glyph at the same font size.
138
+ * When `smoothing` is true, intermediate vertices are placed on a centripetal
139
+ * Catmull-Rom spline through the original points (see `catmullRom.ts`)
140
+ * hiding the polyline facets that show up at large render sizes. The original
141
+ * points remain on the curve, so endpoints and `cumLen`/`idx` semantics are
142
+ * preserved. Has no effect when `maxSegLen` is non-finite (no subdivision).
143
+ *
144
+ * Output depends only on `(stroke.p, maxSegLen, smoothing)` — not on position,
145
+ * seed, progress, or effect config — so it can be cached and shared across
146
+ * every instance of the same glyph at the same font size.
66
147
  */
67
- function subdivideStroke(stroke, maxSegLen) {
148
+ function subdivideStroke(stroke, maxSegLen, smoothing = false) {
68
149
  const pts = stroke.p;
69
150
  const n = pts.length;
70
151
  if (n === 0) return {
@@ -86,20 +167,43 @@ function subdivideStroke(stroke, maxSegLen) {
86
167
  const cur = pts[j];
87
168
  const dx = cur[0] - prev[0];
88
169
  const dy = cur[1] - prev[1];
89
- const dw = cur[2] - prev[2];
90
- const segLen = Math.sqrt(dx * dx + dy * dy);
91
- const count = segLen > 0 && Number.isFinite(maxSegLen) && maxSegLen > 0 ? Math.max(1, Math.ceil(segLen / maxSegLen)) : 1;
92
- for (let k = 1; k <= count; k++) {
93
- const t = k / count;
94
- vertices.push({
95
- x: prev[0] + dx * t,
96
- y: prev[1] + dy * t,
97
- width: prev[2] + dw * t,
98
- cumLen: cumLen + segLen * t,
99
- idx: j - 1 + t
100
- });
170
+ const chordLen = Math.sqrt(dx * dx + dy * dy);
171
+ const count = chordLen > 0 && Number.isFinite(maxSegLen) && maxSegLen > 0 ? Math.max(1, Math.ceil(chordLen / maxSegLen)) : 1;
172
+ if (smoothing && count > 1) {
173
+ const samples = sampleCatmullRom(j >= 2 ? pts[j - 2] : reflect(prev, cur), prev, cur, j + 1 < n ? pts[j + 1] : reflect(cur, prev), count);
174
+ let px = prev[0];
175
+ let py = prev[1];
176
+ let segAccum = 0;
177
+ for (let k = 0; k < count; k++) {
178
+ const s = samples[k];
179
+ const ex = s.x - px;
180
+ const ey = s.y - py;
181
+ segAccum += Math.sqrt(ex * ex + ey * ey);
182
+ vertices.push({
183
+ x: s.x,
184
+ y: s.y,
185
+ width: s.width,
186
+ cumLen: cumLen + segAccum,
187
+ idx: j - 1 + (k + 1) / count
188
+ });
189
+ px = s.x;
190
+ py = s.y;
191
+ }
192
+ cumLen += segAccum;
193
+ } else {
194
+ const dw = cur[2] - prev[2];
195
+ for (let k = 1; k <= count; k++) {
196
+ const t = k / count;
197
+ vertices.push({
198
+ x: prev[0] + dx * t,
199
+ y: prev[1] + dy * t,
200
+ width: prev[2] + dw * t,
201
+ cumLen: cumLen + chordLen * t,
202
+ idx: j - 1 + t
203
+ });
204
+ }
205
+ cumLen += chordLen;
101
206
  }
102
- cumLen += segLen;
103
207
  }
104
208
  let widthSum = 0;
105
209
  for (const p of pts) widthSum += p[2];
@@ -427,7 +531,9 @@ function measureElement(el, fontSize) {
427
531
  charOffsets: [],
428
532
  charWidths: []
429
533
  };
430
- const elLeft = el.getBoundingClientRect().left;
534
+ const elRect = el.getBoundingClientRect();
535
+ const elLeft = elRect.left;
536
+ const scale = el.offsetWidth > 0 ? elRect.width / el.offsetWidth : 1;
431
537
  const range = document.createRange();
432
538
  const charOffsets = [];
433
539
  const charWidths = [];
@@ -458,13 +564,13 @@ function measureElement(el, fontSize) {
458
564
  continue;
459
565
  }
460
566
  const rect = rects[rects.length - 1];
461
- if (currentLine.length > 0 && rect.top - prevTop > fontSize * .25) {
567
+ if (currentLine.length > 0 && rect.top - prevTop > fontSize * .25 * scale) {
462
568
  lines.push(currentLine);
463
569
  currentLine = [];
464
570
  }
465
571
  if (currentLine.length === 0) prevTop = rect.top;
466
- charOffsets.push((rect.left - elLeft) / fontSize);
467
- charWidths.push(rect.width / fontSize);
572
+ charOffsets.push((rect.left - elLeft) / scale / fontSize);
573
+ charWidths.push(rect.width / scale / fontSize);
468
574
  currentLine.push(i);
469
575
  }
470
576
  if (currentLine.length > 0) lines.push(currentLine);
@@ -1114,7 +1220,8 @@ var TegakiEngine = class {
1114
1220
  }
1115
1221
  }
1116
1222
  if (e.propertyName === "--tegaki-progress") {
1117
- this._cssTime = Number(styles.getPropertyValue(CSS_PROGRESS)) * this._timeline.totalDuration;
1223
+ const rawProgress = Number(styles.getPropertyValue(CSS_PROGRESS));
1224
+ this._cssTime = rawProgress * this._timeline.totalDuration;
1118
1225
  changed = true;
1119
1226
  }
1120
1227
  if (changed) this._render();
@@ -1265,7 +1372,6 @@ var TegakiEngine = class {
1265
1372
  const font = this._font;
1266
1373
  const layout = this._layout;
1267
1374
  const fontSize = this._fontSize;
1268
- if (!font?.glyphData || !layout || !fontSize) return;
1269
1375
  const effectiveDpr = (window.devicePixelRatio || 1) * Math.max(this._quality?.pixelRatio ?? 1, 0);
1270
1376
  const w = canvas.offsetWidth;
1271
1377
  const h = canvas.offsetHeight;
@@ -1277,6 +1383,7 @@ var TegakiEngine = class {
1277
1383
  if (!ctx) return;
1278
1384
  ctx.setTransform(effectiveDpr, 0, 0, effectiveDpr, 0, 0);
1279
1385
  ctx.clearRect(0, 0, w, h);
1386
+ if (!font?.glyphData || !layout || !fontSize) return;
1280
1387
  const padH = PADDING_H_EM * fontSize;
1281
1388
  const lineHeight = this._lineHeight;
1282
1389
  const padV = Math.max(MIN_PADDING_V_EM * fontSize, (MIN_LINE_HEIGHT_EM * fontSize - lineHeight) / 2);
@@ -1289,10 +1396,11 @@ var TegakiEngine = class {
1289
1396
  const p = findEffect(this._resolvedEffects, "pressureWidth");
1290
1397
  return !!p && Math.max(0, Math.min(p.config.strength ?? 1, 1)) > 0;
1291
1398
  })();
1292
- const resolvedSegmentSize = this._quality?.segmentSize ?? (effectsNeedSubdivision ? 2 : void 0);
1399
+ const smoothing = this._quality?.smoothing === true;
1400
+ const resolvedSegmentSize = this._quality?.segmentSize ?? (effectsNeedSubdivision || smoothing ? 2 : void 0);
1293
1401
  const scale = fontSize / font.unitsPerEm;
1294
1402
  const maxSegLenFU = resolvedSegmentSize != null ? resolvedSegmentSize / scale : Infinity;
1295
- const cacheKey = `${font.family}|${maxSegLenFU}`;
1403
+ const cacheKey = `${font.family}|${maxSegLenFU}|${smoothing ? "s" : "l"}`;
1296
1404
  if (cacheKey !== this._strokeCacheKey) {
1297
1405
  this._strokeCache = /* @__PURE__ */ new WeakMap();
1298
1406
  this._strokeCacheKey = cacheKey;
@@ -1301,7 +1409,7 @@ var TegakiEngine = class {
1301
1409
  const getSubdivided = (stroke) => {
1302
1410
  let sub = strokeCache.get(stroke);
1303
1411
  if (!sub) {
1304
- sub = subdivideStroke(stroke, maxSegLenFU);
1412
+ sub = subdivideStroke(stroke, maxSegLenFU, smoothing);
1305
1413
  strokeCache.set(stroke, sub);
1306
1414
  }
1307
1415
  return sub;
@@ -1367,4 +1475,4 @@ var TegakiEngine = class {
1367
1475
  //#endregion
1368
1476
  export { createBundle as a, resolveBundle as c, computeTimeline as d, computeTextLayout as f, resolveEffects as g, coerceToString as h, domCreateElement as i, BUNDLE_VERSION as l, drawGlyph as m, buildChildren as n, getBundle as o, ensureFontFace as p, buildRootProps as r, registerBundle as s, TegakiEngine as t, COMPATIBLE_BUNDLE_VERSIONS as u };
1369
1477
 
1370
- //# sourceMappingURL=core-Ds9ohwR7.mjs.map
1478
+ //# sourceMappingURL=core-BYU5BEaZ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core-BYU5BEaZ.mjs","names":[],"sources":["../src/lib/effects.ts","../src/lib/catmullRom.ts","../src/lib/strokeCache.ts","../src/lib/utils.ts","../src/lib/drawGlyph.ts","../src/lib/font.ts","../src/lib/textLayout.ts","../src/lib/timeline.ts","../src/types.ts","../src/core/bundle-registry.ts","../src/core/createBundle.ts","../src/lib/css-properties.ts","../src/lib/drawFallbackGlyph.ts","../src/core/render-elements.ts","../src/core/engine.ts"],"sourcesContent":["import type { TegakiEffectConfigs, TegakiEffectName } from '../types.ts';\n\nexport interface ResolvedEffect<K extends TegakiEffectName = TegakiEffectName> {\n effect: K;\n order: number;\n config: TegakiEffectConfigs[K];\n}\n\nconst defaultEffects: Record<string, any> = { pressureWidth: true };\nconst knownEffects: Set<string> = new Set(['glow', 'wobble', 'pressureWidth', 'taper', 'gradient']);\n\n/**\n * Normalizes an effects record into a sorted array of resolved effects.\n * Known keys infer the effect name; custom keys read it from the `effect` field.\n * Boolean `true` becomes an empty config. `false`/absent entries are skipped.\n */\nexport function resolveEffects(effects: Record<string, any> | undefined): ResolvedEffect[] {\n const merged = { ...defaultEffects, ...effects };\n\n const result: ResolvedEffect[] = [];\n\n for (const [key, value] of Object.entries(merged)) {\n if (value === false || value == null) continue;\n\n let effectName: TegakiEffectName;\n let config: Record<string, any>;\n let order: number;\n\n if (value === true) {\n effectName = (knownEffects.has(key) ? key : undefined) as TegakiEffectName;\n if (!effectName) continue;\n config = {};\n order = 0;\n } else {\n if (value.enabled === false) continue;\n effectName = value.effect ?? (knownEffects.has(key) ? key : undefined);\n if (!effectName) continue;\n const { effect: _, order: o, enabled: __, ...rest } = value;\n config = rest;\n order = o ?? 0;\n }\n\n result.push({ effect: effectName, order, config });\n }\n\n result.sort((a, b) => a.order - b.order);\n return result;\n}\n\n/** Check if a specific effect is active. */\nexport function findEffect<K extends TegakiEffectName>(effects: ResolvedEffect[], name: K): ResolvedEffect<K> | undefined {\n return effects.find((e) => e.effect === name) as ResolvedEffect<K> | undefined;\n}\n\n/** Get all instances of a specific effect (for duplicates). */\nexport function findEffects<K extends TegakiEffectName>(effects: ResolvedEffect[], name: K): ResolvedEffect<K>[] {\n return effects.filter((e) => e.effect === name) as ResolvedEffect<K>[];\n}\n","/**\n * Centripetal Catmull-Rom spline sampling.\n *\n * A point is `[x, y, w]` — the same shape the stroke data uses — but the\n * third channel is carried through the interpolation opaquely, so this\n * module is self-contained and doesn't know about strokes or glyph data.\n */\n\n/** A 3-channel point `[x, y, w]`. Typed as a plain array to match the loose shape used by the glyph data format. */\nexport type Point3 = readonly number[];\n\nexport interface SmoothSample {\n x: number;\n y: number;\n /** Third channel (stroke width in the caller's use) interpolated on the same parameterization as x/y. */\n width: number;\n}\n\n/**\n * Sample `count` points along a centripetal Catmull-Rom segment from `p1` to\n * `p2`, with neighbor control points `p0` and `p3`. Samples are emitted at\n * `u = k/count` for `k = 1..count`, so the last sample equals `p2`.\n *\n * Centripetal parameterization (α = 0.5) avoids the cusps and self-loops that\n * uniform/chordal Catmull-Rom can produce on sharp corners — relevant for the\n * baked RDP-simplified polylines the renderer consumes.\n *\n * For endpoint segments where a neighbor is missing, pass a phantom point\n * built with {@link reflect}. Zero-length chords are clamped to a tiny epsilon\n * so the knot parameterization stays non-degenerate.\n */\nexport function sampleCatmullRom(p0: Point3, p1: Point3, p2: Point3, p3: Point3, count: number): SmoothSample[] {\n const d01 = Math.max(dist(p0, p1), 1e-6);\n const d12 = Math.max(dist(p1, p2), 1e-6);\n const d23 = Math.max(dist(p2, p3), 1e-6);\n const t0 = 0;\n const t1 = t0 + Math.sqrt(d01);\n const t2 = t1 + Math.sqrt(d12);\n const t3 = t2 + Math.sqrt(d23);\n\n const out: SmoothSample[] = new Array(count);\n for (let k = 1; k <= count; k++) {\n const u = k / count;\n const t = t1 + u * (t2 - t1);\n out[k - 1] = evalBarryGoldman(p0, p1, p2, p3, t0, t1, t2, t3, t);\n }\n return out;\n}\n\n/**\n * Reflect `p` across `anchor` to produce a phantom neighbor for endpoint\n * segments. The result lies on the extension of (p, anchor) past `anchor` at\n * the same distance — equivalent to a zero-curvature extrapolation, which\n * gives a natural straight start/end tangent.\n */\nexport function reflect(anchor: Point3, p: Point3): Point3 {\n return [2 * anchor[0]! - p[0]!, 2 * anchor[1]! - p[1]!, anchor[2]!];\n}\n\nfunction dist(a: Point3, b: Point3): number {\n const dx = b[0]! - a[0]!;\n const dy = b[1]! - a[1]!;\n return Math.sqrt(dx * dx + dy * dy);\n}\n\n// Barry-Goldman pyramid evaluation of a Catmull-Rom segment between p1 and p2\n// at knot value t ∈ [t1, t2]. Applies the same interpolation to x, y, and the\n// third channel so they stay smooth together.\nfunction evalBarryGoldman(\n p0: Point3,\n p1: Point3,\n p2: Point3,\n p3: Point3,\n t0: number,\n t1: number,\n t2: number,\n t3: number,\n t: number,\n): SmoothSample {\n const a1x = lerp(p0[0]!, p1[0]!, t0, t1, t);\n const a1y = lerp(p0[1]!, p1[1]!, t0, t1, t);\n const a1w = lerp(p0[2]!, p1[2]!, t0, t1, t);\n const a2x = lerp(p1[0]!, p2[0]!, t1, t2, t);\n const a2y = lerp(p1[1]!, p2[1]!, t1, t2, t);\n const a2w = lerp(p1[2]!, p2[2]!, t1, t2, t);\n const a3x = lerp(p2[0]!, p3[0]!, t2, t3, t);\n const a3y = lerp(p2[1]!, p3[1]!, t2, t3, t);\n const a3w = lerp(p2[2]!, p3[2]!, t2, t3, t);\n const b1x = lerp(a1x, a2x, t0, t2, t);\n const b1y = lerp(a1y, a2y, t0, t2, t);\n const b1w = lerp(a1w, a2w, t0, t2, t);\n const b2x = lerp(a2x, a3x, t1, t3, t);\n const b2y = lerp(a2y, a3y, t1, t3, t);\n const b2w = lerp(a2w, a3w, t1, t3, t);\n return {\n x: lerp(b1x, b2x, t1, t2, t),\n y: lerp(b1y, b2y, t1, t2, t),\n width: lerp(b1w, b2w, t1, t2, t),\n };\n}\n\nfunction lerp(a: number, b: number, ta: number, tb: number, t: number): number {\n const span = tb - ta;\n if (span === 0) return a;\n return a + (b - a) * ((t - ta) / span);\n}\n","import type { TegakiGlyphData } from '../types.ts';\nimport { reflect, sampleCatmullRom } from './catmullRom.ts';\n\ntype Stroke = TegakiGlyphData['s'][number];\n\n/**\n * A single vertex in a subdivided stroke polyline. Consecutive vertices form\n * a drawable sub-segment. Coordinates are in font units and pre-wobble: the\n * renderer applies wobble, scale, and translation at draw time so that one\n * cached subdivision can be reused across every instance of the same glyph.\n */\nexport interface SubVertex {\n /** X in font units, pre-wobble. */\n x: number;\n /** Y in font units, pre-wobble. */\n y: number;\n /** Stroke width at this vertex, in font units. */\n width: number;\n /** Accumulated length from the start of the stroke, in font units. */\n cumLen: number;\n /** Fractional original-point index (used to keep wobble phase continuous). */\n idx: number;\n}\n\nexport interface SubdividedStroke {\n /** Ordered vertices; vertices[i] → vertices[i+1] is a drawable sub-segment. */\n vertices: SubVertex[];\n /** Total polyline length in font units. */\n totalLen: number;\n /** Mean width across the original points in font units. */\n avgWidth: number;\n}\n\n/**\n * Subdivide a stroke so that no sub-segment exceeds `maxSegLen` font units.\n * Pass `Infinity` (or any non-finite value) to skip subdivision and return\n * the raw polyline.\n *\n * When `smoothing` is true, intermediate vertices are placed on a centripetal\n * Catmull-Rom spline through the original points (see `catmullRom.ts`) —\n * hiding the polyline facets that show up at large render sizes. The original\n * points remain on the curve, so endpoints and `cumLen`/`idx` semantics are\n * preserved. Has no effect when `maxSegLen` is non-finite (no subdivision).\n *\n * Output depends only on `(stroke.p, maxSegLen, smoothing)` — not on position,\n * seed, progress, or effect config — so it can be cached and shared across\n * every instance of the same glyph at the same font size.\n */\nexport function subdivideStroke(stroke: Stroke, maxSegLen: number, smoothing = false): SubdividedStroke {\n const pts = stroke.p;\n const n = pts.length;\n if (n === 0) return { vertices: [], totalLen: 0, avgWidth: 0 };\n\n const first = pts[0]!;\n const vertices: SubVertex[] = [{ x: first[0]!, y: first[1]!, width: first[2]!, cumLen: 0, idx: 0 }];\n\n let cumLen = 0;\n for (let j = 1; j < n; j++) {\n const prev = pts[j - 1]!;\n const cur = pts[j]!;\n const dx = cur[0]! - prev[0]!;\n const dy = cur[1]! - prev[1]!;\n const chordLen = Math.sqrt(dx * dx + dy * dy);\n const count = chordLen > 0 && Number.isFinite(maxSegLen) && maxSegLen > 0 ? Math.max(1, Math.ceil(chordLen / maxSegLen)) : 1;\n\n if (smoothing && count > 1) {\n const p0 = j >= 2 ? pts[j - 2]! : reflect(prev, cur);\n const p3 = j + 1 < n ? pts[j + 1]! : reflect(cur, prev);\n const samples = sampleCatmullRom(p0, prev, cur, p3, count);\n let px = prev[0]!;\n let py = prev[1]!;\n let segAccum = 0;\n for (let k = 0; k < count; k++) {\n const s = samples[k]!;\n const ex = s.x - px;\n const ey = s.y - py;\n segAccum += Math.sqrt(ex * ex + ey * ey);\n vertices.push({ x: s.x, y: s.y, width: s.width, cumLen: cumLen + segAccum, idx: j - 1 + (k + 1) / count });\n px = s.x;\n py = s.y;\n }\n cumLen += segAccum;\n } else {\n const dw = cur[2]! - prev[2]!;\n for (let k = 1; k <= count; k++) {\n const t = k / count;\n vertices.push({\n x: prev[0]! + dx * t,\n y: prev[1]! + dy * t,\n width: prev[2]! + dw * t,\n cumLen: cumLen + chordLen * t,\n idx: j - 1 + t,\n });\n }\n cumLen += chordLen;\n }\n }\n\n let widthSum = 0;\n for (const p of pts) widthSum += p[2]!;\n\n return { vertices, totalLen: cumLen, avgWidth: widthSum / n };\n}\n","import type { CSSLength, TegakiBundle } from '../types.ts';\n\nconst segmenter =\n typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function' ? new Intl.Segmenter(undefined, { granularity: 'grapheme' }) : null;\n\n/** Resolve a CSSLength to pixels. Plain numbers are px, `\"Nem\"` is N * fontSize. */\nexport function resolveCSSLength(value: CSSLength, fontSize: number): number {\n if (typeof value === 'number') return value;\n return parseFloat(value) * fontSize;\n}\n\nexport function graphemes(text: string): string[] {\n if (segmenter) return Array.from(segmenter.segment(text), (s) => s.segment);\n // Fallback for environments without Intl.Segmenter (Safari < 16.4). Splits by\n // code point so surrogate pairs stay intact; combining marks and ZWJ emoji\n // clusters degrade to one entry per code point, which is acceptable because\n // glyph data is keyed per code point anyway.\n return Array.from(text);\n}\n\n/**\n * Build the CSS `font-family` value for a bundle, including the full\n * (non-subsetted) family as fallback when the bundle was generated from a subset.\n */\nexport function cssFontFamily(bundle: TegakiBundle): string {\n if (bundle.fullFamily) return `'${bundle.family}', '${bundle.fullFamily}'`;\n return `'${bundle.family}'`;\n}\n\nexport type Coercible = string | number | boolean | null | undefined | readonly Coercible[];\n\nexport function coerceToString(value: unknown): string {\n if (value == null || typeof value === 'boolean') return '';\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'bigint') return String(value);\n if (Array.isArray(value)) return value.map(coerceToString).join('');\n return '';\n}\n","import type { LineCap, TegakiGlyphData } from '../types.ts';\nimport { findEffect, findEffects, type ResolvedEffect } from './effects.ts';\nimport { type SubdividedStroke, subdivideStroke } from './strokeCache.ts';\nimport { resolveCSSLength } from './utils.ts';\n\ntype Stroke = TegakiGlyphData['s'][number];\n\ninterface GlyphPosition {\n /** X offset in CSS pixels */\n x: number;\n /** Y offset in CSS pixels (top of em square) */\n y: number;\n /** Font size in CSS pixels */\n fontSize: number;\n /** Units per em from the font */\n unitsPerEm: number;\n /** Font ascender in font units */\n ascender: number;\n /** Font descender in font units (negative) */\n descender: number;\n}\n\n// --- Color helpers ---\n\nfunction parseColor(color: string): [number, number, number, number] {\n const h = color.replace('#', '');\n if (h.length === 3) {\n return [parseInt(h[0]! + h[0]!, 16), parseInt(h[1]! + h[1]!, 16), parseInt(h[2]! + h[2]!, 16), 1];\n }\n if (h.length === 4) {\n return [parseInt(h[0]! + h[0]!, 16), parseInt(h[1]! + h[1]!, 16), parseInt(h[2]! + h[2]!, 16), parseInt(h[3]! + h[3]!, 16) / 255];\n }\n if (h.length === 8) {\n return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16), parseInt(h.slice(6, 8), 16) / 255];\n }\n return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16), 1];\n}\n\nfunction lerpColor(a: [number, number, number, number], b: [number, number, number, number], t: number): string {\n const r = Math.round(a[0] + (b[0] - a[0]) * t);\n const g = Math.round(a[1] + (b[1] - a[1]) * t);\n const bl = Math.round(a[2] + (b[2] - a[2]) * t);\n const al = a[3] + (b[3] - a[3]) * t;\n if (al >= 1) return `rgb(${r},${g},${bl})`;\n return `rgba(${r},${g},${bl},${al.toFixed(3)})`;\n}\n\nfunction gradientColor(progress: number, colors: string[], seed: number): string {\n if (colors.length === 0) return '#000';\n if (colors.length === 1) return colors[0]!;\n const t = (((progress + seed * 0.1) % 1) + 1) % 1;\n const scaledT = t * (colors.length - 1);\n const i = Math.min(Math.floor(scaledT), colors.length - 2);\n const frac = scaledT - i;\n return lerpColor(parseColor(colors[i]!), parseColor(colors[i + 1]!), frac);\n}\n\nfunction rainbowColor(progress: number, saturation: number, lightness: number, seed: number): string {\n const hue = (progress * 360 + seed * 137.5) % 360;\n return `hsl(${hue}, ${saturation}%, ${lightness}%)`;\n}\n\n// --- Noise helper for wobble ---\n\nfunction hash(x: number): number {\n let h = (x * 2654435761) | 0;\n h = ((h >>> 16) ^ h) * 0x45d9f3b;\n h = ((h >>> 16) ^ h) * 0x45d9f3b;\n h = (h >>> 16) ^ h;\n return (h & 0x7fffffff) / 0x7fffffff; // 0-1\n}\n\nfunction noise1d(x: number, seed: number): number {\n const i = Math.floor(x);\n const f = x - i;\n const t = f * f * (3 - 2 * f); // smoothstep\n return hash(i + seed * 7919) * (1 - t) + hash(i + 1 + seed * 7919) * t;\n}\n\n/** Default stroke easing: ease-out exponential. */\nfunction defaultStrokeEasing(t: number): number {\n return 1 - (1 - t) * (1 - t); // Ease out quad\n}\n\n/**\n * Draw a single glyph's strokes onto a canvas context, animated up to `localTime`.\n * `localTime` is seconds relative to this glyph's start (0 = glyph begins).\n *\n * `getSubdivided` returns a shared, cached subdivision of each stroke (in font\n * units, pre-wobble). The engine owns the cache and invalidates it when the\n * font, fontSize, or segment size changes; if omitted here, strokes are\n * subdivided inline each call (useful for testing).\n */\nexport function drawGlyph(\n ctx: CanvasRenderingContext2D,\n glyph: TegakiGlyphData,\n pos: GlyphPosition,\n localTime: number,\n lineCap: LineCap,\n color: string,\n effects: ResolvedEffect[] = [],\n seed = 0,\n getSubdivided?: (stroke: Stroke) => SubdividedStroke,\n strokeEasing: ((t: number) => number) | undefined = defaultStrokeEasing,\n strokeScale = 1,\n) {\n const scale = pos.fontSize / pos.unitsPerEm;\n const ox = pos.x;\n const oy = pos.y;\n\n const glowEffects = findEffects(effects, 'glow');\n const wobbleEffect = findEffect(effects, 'wobble');\n const pressureEffect = findEffect(effects, 'pressureWidth');\n const taperEffect = findEffect(effects, 'taper');\n const gradientEffect = findEffect(effects, 'gradient');\n\n // Pressure params (0 = uniform avg width, 1 = fully per-point width)\n const pressureAmount = pressureEffect ? Math.max(0, Math.min(pressureEffect.config.strength ?? 1, 1)) : 0;\n\n // Wobble params\n const wobbleAmplitude = wobbleEffect ? (wobbleEffect.config.amplitude ?? 1.5) : 0;\n const wobbleFrequency = wobbleEffect ? (wobbleEffect.config.frequency ?? 8) : 0;\n const wobbleMode = wobbleEffect?.config.mode ?? 'sine';\n const hasWobble = !!wobbleEffect;\n\n // Taper params\n const taperStart = taperEffect ? Math.max(0, Math.min(taperEffect.config.startLength ?? 0.15, 1)) : 0;\n const taperEnd = taperEffect ? Math.max(0, Math.min(taperEffect.config.endLength ?? 0.15, 1)) : 0;\n\n // Gradient params\n const gradientColors = gradientEffect?.config.colors;\n const isRainbow = gradientColors === 'rainbow';\n const gradientColorStops = Array.isArray(gradientColors) ? gradientColors : undefined;\n const gradientSaturation = gradientEffect?.config.saturation ?? 80;\n const gradientLightness = gradientEffect?.config.lightness ?? 55;\n const hasGradient = !!gradientEffect;\n\n // Effects that vary per-segment require splitting the polyline into\n // individual stroke() calls. Gradient also varies per-segment but via\n // strokeStyle, not lineWidth.\n const needsPerSegment = pressureAmount > 0 || !!taperEffect;\n\n // Fallback subdivider for callers that don't thread the engine's cache\n // (tests, standalone use). Engine always provides a cached version.\n const subdivide = getSubdivided ?? ((s: Stroke) => subdivideStroke(s, Infinity));\n\n // Wobble offsets (in font units). Evaluated once per rendered sub-vertex;\n // fractional `idx` keeps the wobble phase continuous across sub-segments.\n // dx depends on y; dy depends on x — the asymmetry keeps the perpendicular\n // wobble component out of phase with the along-stroke one.\n const wobbleDx = (_x: number, y: number, idx: number): number => {\n if (!hasWobble) return 0;\n if (wobbleMode === 'noise') return wobbleAmplitude * (noise1d(y * 0.1 + idx * 0.7, seed) * 2 - 1);\n return wobbleAmplitude * Math.sin(wobbleFrequency * (y * 0.01 + idx * 0.7) + seed);\n };\n const wobbleDy = (x: number, _y: number, idx: number): number => {\n if (!hasWobble) return 0;\n if (wobbleMode === 'noise') return wobbleAmplitude * (noise1d(x * 0.1 + idx * 0.5, seed * 1.3 + 1000) * 2 - 1);\n return wobbleAmplitude * Math.cos(wobbleFrequency * (x * 0.01 + idx * 0.5) + seed * 1.3);\n };\n\n // Helper: convert font-unit point to pixel\n const px = (x: number) => ox + x * scale;\n const py = (y: number) => oy + (y + pos.ascender) * scale;\n\n // Helper: get color for a given stroke progress\n const colorAt = (progress: number): string => {\n if (isRainbow) return rainbowColor(progress, gradientSaturation, gradientLightness, seed);\n if (gradientColorStops) return gradientColor(progress, gradientColorStops, seed);\n return color;\n };\n\n // Helper: taper multiplier (0-1) for a given stroke progress\n const taperMultiplier = (progress: number): number => {\n let m = 1;\n if (taperStart > 0 && progress < taperStart) m = Math.min(m, progress / taperStart);\n if (taperEnd > 0 && progress > 1 - taperEnd) m = Math.min(m, (1 - progress) / taperEnd);\n return m;\n };\n\n for (const stroke of glyph.s) {\n if (localTime < stroke.d) continue;\n const elapsed = localTime - stroke.d;\n const linearProgress = Math.min(elapsed / stroke.a, 1);\n const progress = strokeEasing ? strokeEasing(linearProgress) : linearProgress;\n\n const rawPts = stroke.p;\n if (rawPts.length === 0) continue;\n\n // --- Single-point dot (bypass cache; there is nothing to subdivide) ---\n if (rawPts.length === 1) {\n if (progress <= 0) continue;\n const p = rawPts[0]!;\n const dotX = px(p[0]! + wobbleDx(p[0]!, p[1]!, 0));\n const dotY = py(p[1]! + wobbleDy(p[0]!, p[1]!, 0));\n const baseLineWidth = Math.max(p[2]!, 0.5) * scale * strokeScale;\n const perPointDot = Math.max(p[2]!, 0.5) * scale * strokeScale;\n let dotWidth = baseLineWidth + (perPointDot - baseLineWidth) * pressureAmount;\n dotWidth *= taperMultiplier(0.5);\n\n // Glow passes for dots\n for (const glow of glowEffects) {\n ctx.save();\n ctx.shadowBlur = resolveCSSLength(glow.config.radius ?? 8, pos.fontSize);\n ctx.shadowColor = glow.config.color ?? color;\n ctx.shadowOffsetX = (glow.config.offsetX ?? 0) * scale;\n ctx.shadowOffsetY = (glow.config.offsetY ?? 0) * scale;\n ctx.fillStyle = glow.config.color ?? color;\n ctx.beginPath();\n if (lineCap === 'round') {\n ctx.arc(dotX, dotY, dotWidth / 2, 0, Math.PI * 2);\n } else {\n ctx.rect(dotX - dotWidth / 2, dotY - dotWidth / 2, dotWidth, dotWidth);\n }\n ctx.fill();\n ctx.restore();\n }\n\n // Main dot\n ctx.fillStyle = colorAt(0);\n ctx.beginPath();\n if (lineCap === 'round') {\n ctx.arc(dotX, dotY, dotWidth / 2, 0, Math.PI * 2);\n ctx.fill();\n } else {\n ctx.fillRect(dotX - dotWidth / 2, dotY - dotWidth / 2, dotWidth, dotWidth);\n }\n continue;\n }\n\n // --- Multi-point stroke: consume cached subdivision ---\n const cached = subdivide(stroke);\n const { vertices, totalLen, avgWidth } = cached;\n if (vertices.length < 2 || totalLen <= 0) continue;\n\n const drawLen = totalLen * progress;\n if (drawLen <= 0) continue;\n\n const baseLineWidth = Math.max(avgWidth, 0.5) * scale * strokeScale;\n\n // Binary search for the last fully-included vertex — i.e. the largest i\n // with vertices[i].cumLen <= drawLen.\n let lo = 0;\n let hi = vertices.length - 1;\n while (lo < hi) {\n const mid = (lo + hi + 1) >>> 1;\n if (vertices[mid]!.cumLen <= drawLen) lo = mid;\n else hi = mid - 1;\n }\n const lastIdx = lo;\n\n // Interpolate the tail of the last, partially-drawn sub-segment.\n let tailX = 0;\n let tailY = 0;\n let tailWidth = 0;\n let tailIdx = 0;\n let tailCumLen = 0;\n let hasTail = false;\n if (lastIdx + 1 < vertices.length && drawLen > vertices[lastIdx]!.cumLen) {\n const a = vertices[lastIdx]!;\n const b = vertices[lastIdx + 1]!;\n const segLen = b.cumLen - a.cumLen;\n const t = segLen > 0 ? (drawLen - a.cumLen) / segLen : 0;\n tailX = a.x + (b.x - a.x) * t;\n tailY = a.y + (b.y - a.y) * t;\n tailWidth = a.width + (b.width - a.width) * t;\n tailIdx = a.idx + (b.idx - a.idx) * t;\n tailCumLen = drawLen;\n hasTail = true;\n }\n\n // Pre-transform every visible vertex (raw + wobble + scale + translate)\n // exactly once — glow and main passes both iterate this array, and the\n // per-segment case needs stable endpoints across its N stroke() calls.\n const tcount = lastIdx + 1 + (hasTail ? 1 : 0);\n const txs: number[] = new Array(tcount);\n const tys: number[] = new Array(tcount);\n for (let i = 0; i <= lastIdx; i++) {\n const v = vertices[i]!;\n txs[i] = px(v.x + wobbleDx(v.x, v.y, v.idx));\n tys[i] = py(v.y + wobbleDy(v.x, v.y, v.idx));\n }\n if (hasTail) {\n txs[tcount - 1] = px(tailX + wobbleDx(tailX, tailY, tailIdx));\n tys[tcount - 1] = py(tailY + wobbleDy(tailX, tailY, tailIdx));\n }\n\n ctx.lineCap = lineCap;\n ctx.lineJoin = 'round';\n\n // Trace the full visible polyline as one Path2D primitive. Used for both\n // glow (where it's critical — shadowBlur cost is per stroke() call, so\n // coalescing into one call matters) and the no-per-segment-effect main\n // draw.\n const tracePolyline = () => {\n ctx.beginPath();\n ctx.moveTo(txs[0]!, tys[0]!);\n for (let i = 1; i < tcount; i++) ctx.lineTo(txs[i]!, tys[i]!);\n };\n\n // --- Glow passes (one stroke() call per glow over the full polyline) ---\n for (const glow of glowEffects) {\n ctx.save();\n ctx.shadowBlur = resolveCSSLength(glow.config.radius ?? 8, pos.fontSize);\n ctx.shadowColor = glow.config.color ?? color;\n ctx.shadowOffsetX = (glow.config.offsetX ?? 0) * scale;\n ctx.shadowOffsetY = (glow.config.offsetY ?? 0) * scale;\n ctx.strokeStyle = glow.config.color ?? color;\n ctx.lineWidth = baseLineWidth;\n tracePolyline();\n ctx.stroke();\n ctx.restore();\n }\n\n // --- Main stroke ---\n if (!needsPerSegment && !hasGradient) {\n // Fast path: single stroke() over the whole truncated polyline.\n ctx.strokeStyle = color;\n ctx.lineWidth = baseLineWidth;\n tracePolyline();\n ctx.stroke();\n } else {\n // Per-segment path: each sub-segment is its own mini-stroke so\n // lineWidth / strokeStyle can vary. Adjacent round-capped endpoints\n // overlap to read as a continuous line.\n const invTotalLen = 1 / totalLen;\n for (let i = 1; i < tcount; i++) {\n const aCum = i - 1 <= lastIdx ? vertices[i - 1]!.cumLen : tailCumLen;\n const bCum = i <= lastIdx ? vertices[i]!.cumLen : tailCumLen;\n const aWidth = i - 1 <= lastIdx ? vertices[i - 1]!.width : tailWidth;\n const bWidth = i <= lastIdx ? vertices[i]!.width : tailWidth;\n const midProgress = (aCum + bCum) * 0.5 * invTotalLen;\n\n let lw = baseLineWidth;\n if (needsPerSegment) {\n const perPoint = (aWidth + bWidth) * 0.5 * scale * strokeScale;\n const w = Math.max(baseLineWidth + (perPoint - baseLineWidth) * pressureAmount, 0.5 * scale * strokeScale);\n lw = w * taperMultiplier(midProgress);\n }\n ctx.lineWidth = lw;\n ctx.strokeStyle = hasGradient ? colorAt(midProgress) : color;\n ctx.beginPath();\n ctx.moveTo(txs[i - 1]!, tys[i - 1]!);\n ctx.lineTo(txs[i]!, tys[i]!);\n ctx.stroke();\n }\n }\n }\n}\n","import type { TegakiBundle } from '../types.ts';\n\nconst fontFaceCache = new Map<string, Promise<void>>();\n\n/**\n * Ensures the bundle's font face is loaded and available for rendering.\n * Resolves immediately if the font is already loaded.\n */\nexport async function ensureFontFace(bundle: TegakiBundle): Promise<void> {\n await ensureFont(bundle.family, bundle.fontUrl);\n}\n\nexport function ensureFont(family: string, url: string): Promise<void> | null {\n if (typeof document === 'undefined') return Promise.resolve();\n for (const face of document.fonts) {\n if (face.family === family) {\n if (face.status === 'loaded') return null;\n if (face.status === 'loading') return face.loaded.then(() => {});\n }\n }\n let cached = fontFaceCache.get(url);\n if (!cached) {\n cached = new FontFace(family, `url(${url})`, { featureSettings: \"'calt' 0, 'liga' 0\" }).load().then((loaded) => {\n document.fonts.add(loaded);\n });\n fontFaceCache.set(url, cached);\n }\n return cached;\n}\n","import { graphemes } from './utils.ts';\n\nexport interface TextLayout {\n /** Character indices per line */\n lines: number[][];\n /** X offset within line in em per character index */\n charOffsets: number[];\n /** Width in em per character index */\n charWidths: number[];\n}\n\n/**\n * Measure text layout using the Range API on an existing DOM element.\n * The element must already be in the document with correct text content,\n * font, line-height, white-space, and width styles applied.\n */\nexport function computeTextLayout(el: HTMLElement, fontSize: number): TextLayout;\n/**\n * Measure text layout by creating a temporary off-screen DOM element.\n */\nexport function computeTextLayout(text: string, fontSize: number, fontFamily: string, lineHeight: number, maxWidth: number): TextLayout;\nexport function computeTextLayout(\n elOrText: HTMLElement | string,\n fontSize: number,\n fontFamily?: string,\n lineHeight?: number,\n maxWidth?: number,\n): TextLayout {\n if (typeof elOrText === 'string') {\n return measureWithTempElement(elOrText, fontFamily!, fontSize, lineHeight!, maxWidth!);\n }\n return measureElement(elOrText, fontSize);\n}\n\nfunction measureElement(el: HTMLElement, fontSize: number): TextLayout {\n const textNode = el.firstChild;\n if (!textNode || textNode.nodeType !== Node.TEXT_NODE) {\n return { lines: [], charOffsets: [], charWidths: [] };\n }\n\n const text = textNode.textContent ?? '';\n const chars = graphemes(text);\n if (!chars.length) return { lines: [], charOffsets: [], charWidths: [] };\n\n // Use element's left edge as reference so offsets are direction-agnostic.\n // For LTR the first char is near the left edge; for RTL it's near the right —\n // either way, subtracting elLeft produces correct visual x-positions.\n const elRect = el.getBoundingClientRect();\n const elLeft = elRect.left;\n // Ancestor CSS transforms (e.g. Remotion Studio's preview-fit scale) make\n // getClientRects() return pre-scale pixel values while getComputedStyle()\n // returns unscaled fontSize. Divide measured widths by the scale so the em\n // conversion matches fontSize. offsetWidth is layout-box width (unscaled).\n const scale = el.offsetWidth > 0 ? elRect.width / el.offsetWidth : 1;\n const range = document.createRange();\n\n const charOffsets: number[] = [];\n const charWidths: number[] = [];\n const lines: number[][] = [];\n let currentLine: number[] = [];\n let prevTop = -Infinity;\n let utf16Offset = 0;\n\n for (let i = 0; i < chars.length; i++) {\n const char = chars[i]!;\n\n if (char === '\\n') {\n charOffsets.push(0);\n charWidths.push(0);\n currentLine.push(i);\n lines.push(currentLine);\n currentLine = [];\n prevTop = -Infinity;\n utf16Offset += char.length;\n continue;\n }\n\n range.setStart(textNode, utf16Offset);\n range.setEnd(textNode, utf16Offset + char.length);\n const rects = range.getClientRects();\n utf16Offset += char.length;\n\n if (rects.length === 0) {\n charOffsets.push(0);\n charWidths.push(0);\n currentLine.push(i);\n continue;\n }\n\n const rect = rects[rects.length - 1]!;\n\n // A significant vertical shift signals a new line. Both rect.top and\n // prevTop are in scaled pixels, so compare against a scaled threshold.\n if (currentLine.length > 0 && rect.top - prevTop > fontSize * 0.25 * scale) {\n lines.push(currentLine);\n currentLine = [];\n }\n\n if (currentLine.length === 0) {\n prevTop = rect.top;\n }\n\n charOffsets.push((rect.left - elLeft) / scale / fontSize);\n charWidths.push(rect.width / scale / fontSize);\n currentLine.push(i);\n }\n if (currentLine.length > 0) lines.push(currentLine);\n\n return { lines, charOffsets, charWidths };\n}\n\nfunction measureWithTempElement(text: string, fontFamily: string, fontSize: number, lineHeight: number, maxWidth: number): TextLayout {\n const el = document.createElement('div');\n el.style.position = 'absolute';\n el.style.left = '-9999px';\n el.style.top = '-9999px';\n el.style.visibility = 'hidden';\n el.style.fontFamily = fontFamily;\n el.style.fontSize = `${fontSize}px`;\n el.style.lineHeight = `${lineHeight}px`;\n el.style.whiteSpace = 'pre-wrap';\n el.style.overflowWrap = 'break-word';\n el.style.width = `${maxWidth}px`;\n el.textContent = text;\n document.body.appendChild(el);\n\n const result = measureElement(el, fontSize);\n\n document.body.removeChild(el);\n return result;\n}\n","import type { TegakiBundle } from '../types.ts';\nimport { graphemes } from './utils.ts';\n\nexport interface TimelineConfig {\n /** Pause between glyphs (seconds). Default: `0.1` */\n glyphGap?: number;\n /** Pause after a space character (seconds). Default: `0.15` */\n wordGap?: number;\n /** Pause after a newline / line break (seconds). Default: `0.3` */\n lineGap?: number;\n /** Duration for characters without glyph data (seconds). Default: `0.2` */\n unknownDuration?: number;\n /**\n * Easing function for each stroke's animation progress `(0–1) → (0–1)`.\n * Applied per-stroke to map linear draw progress to eased progress.\n * Default: ease-out exponential (`1 - 2^(-10t)`).\n */\n strokeEasing?: (t: number) => number;\n /**\n * Easing function for each glyph's local time progress `(0–1) → (0–1)`.\n * Applied per-glyph to map linear time within the glyph to eased time.\n * Default: linear (no easing).\n */\n glyphEasing?: (t: number) => number;\n}\n\nconst DEFAULTS = {\n glyphGap: 0.1,\n wordGap: 0.15,\n lineGap: 0.3,\n unknownDuration: 0.2,\n};\n\nexport interface TimelineEntry {\n char: string;\n offset: number;\n duration: number;\n hasGlyph: boolean;\n}\n\nexport interface Timeline {\n entries: TimelineEntry[];\n totalDuration: number;\n}\n\nexport function computeTimeline(text: string, font: TegakiBundle, config?: TimelineConfig): Timeline {\n const glyphGap = config?.glyphGap ?? DEFAULTS.glyphGap;\n const wordGap = config?.wordGap ?? DEFAULTS.wordGap;\n const lineGap = config?.lineGap ?? DEFAULTS.lineGap;\n const unknownDuration = config?.unknownDuration ?? DEFAULTS.unknownDuration;\n\n const chars = graphemes(text);\n const entries: TimelineEntry[] = [];\n let offset = 0;\n for (const char of chars) {\n const glyph = font.glyphData[char];\n const hasGlyph = !!glyph;\n const isLineBreak = char === '\\n';\n const isWhitespace = isLineBreak || /^\\s+$/.test(char);\n const duration = isWhitespace ? 0 : hasGlyph ? (glyph.t ?? unknownDuration) : unknownDuration;\n entries.push({ char, offset, duration, hasGlyph });\n offset += duration;\n\n // Gap after this character\n if (isLineBreak) {\n offset += lineGap;\n } else if (isWhitespace) {\n offset += wordGap;\n } else {\n offset += glyphGap;\n }\n }\n // Remove trailing gap\n if (entries.length > 0) {\n const lastChar = chars[chars.length - 1]!;\n const trailingGap = lastChar === '\\n' ? lineGap : /^\\s+$/.test(lastChar) ? wordGap : glyphGap;\n offset -= trailingGap;\n }\n return { entries, totalDuration: Math.max(0, offset) };\n}\n","/**\n * Current bundle format version. Incremented when the bundle format changes\n * in a way that older engines cannot consume.\n */\nexport const BUNDLE_VERSION = 0;\n\n/**\n * Set of bundle versions that this engine can consume. The engine logs a\n * console warning (once per bundle) when it encounters a version outside\n * this set.\n */\nexport const COMPATIBLE_BUNDLE_VERSIONS: ReadonlySet<number> = new Set([BUNDLE_VERSION]);\n\nexport type LineCap = 'round' | 'butt' | 'square';\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport interface TimedPoint extends Point {\n t: number;\n width: number;\n}\n\nexport interface BBox {\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n}\n\nexport interface Stroke {\n points: TimedPoint[];\n order: number;\n length: number;\n animationDuration: number;\n delay: number;\n}\n\nexport interface GlyphData {\n char: string;\n unicode: number;\n advanceWidth: number;\n boundingBox: BBox;\n path: string;\n skeleton: Point[][];\n strokes: Stroke[];\n totalLength: number;\n totalAnimationDuration: number;\n}\n\nexport interface FontOutput {\n font: {\n family: string;\n style: string;\n unitsPerEm: number;\n ascender: number;\n descender: number;\n lineCap: LineCap;\n };\n glyphs: Record<string, GlyphData>;\n}\n\nexport interface PathCommand {\n type: 'M' | 'L' | 'Q' | 'C' | 'Z';\n x: number;\n y: number;\n x1?: number;\n y1?: number;\n x2?: number;\n y2?: number;\n}\n\n/**\n * Compact glyph data for rendering.\n * - `w`: advance width\n * - `t`: total animation duration\n * - `s`: strokes, each with `p` (points as `[x, y, width]` tuples), `d` (delay), `a` (animation duration)\n */\nexport interface TegakiGlyphData {\n w: number;\n t: number;\n s: {\n p: ([x: number, y: number, width: number] | number[])[];\n d: number;\n a: number;\n }[];\n}\n\ntype BaseEffectConfig = { enabled?: boolean };\n\n/** A length value: plain number is pixels, string `\"${number}em\"` is relative to font size. */\nexport type CSSLength = number | `${number}em`;\n\nexport type TegakiEffectConfigs = {\n glow: BaseEffectConfig & { radius?: CSSLength; color?: string; offsetX?: number; offsetY?: number };\n wobble: BaseEffectConfig & { amplitude?: number; frequency?: number; mode?: 'sine' | 'noise' };\n pressureWidth: BaseEffectConfig & { strength?: number };\n taper: BaseEffectConfig & { startLength?: number; endLength?: number };\n gradient: BaseEffectConfig & { colors?: string[] | 'rainbow'; saturation?: number; lightness?: number };\n};\n\nexport type TegakiEffectName = keyof TegakiEffectConfigs;\n\n/** Effects that can only appear once (cannot be used with custom keys). */\nexport type TegakiSingletonEffectName = 'pressureWidth' | 'wobble' | 'taper' | 'gradient';\n\n/** Effects that can be duplicated with custom keys. */\nexport type TegakiMultiEffectName = Exclude<TegakiEffectName, TegakiSingletonEffectName>;\n\ntype TegakiCustomEffect = {\n [K in TegakiMultiEffectName]: TegakiEffectConfigs[K] & { effect: K; order?: number };\n}[TegakiMultiEffectName];\n\n/** Validates an effects object: known keys infer `effect`, unknown keys require it (singleton effects excluded). */\nexport type TegakiEffects<T> = {\n [K in keyof T]: K extends TegakiEffectName\n ? (TegakiEffectConfigs[K] & { effect?: K; order?: number }) | boolean\n : TegakiCustomEffect | boolean;\n};\n\nexport interface TegakiBundle {\n /** Bundle format version. Used by the engine to warn about incompatible bundles. */\n version?: number;\n family: string;\n /**\n * Original font family name, used as a CSS fallback for characters not in\n * the generated glyph set. Present when the bundle was generated from a\n * subset of the font (the default). When absent, `family` is the original\n * name (full-font bundle).\n */\n fullFamily?: string;\n lineCap: LineCap;\n fontUrl: string;\n /** URL to the full (non-subsetted) font file bundled for fallback rendering. */\n fullFontUrl?: string;\n fontFaceCSS: string;\n unitsPerEm: number;\n ascender: number;\n descender: number;\n glyphData: Record<string, TegakiGlyphData>;\n}\n","import { COMPATIBLE_BUNDLE_VERSIONS, type TegakiBundle } from '../types.ts';\n\nconst bundles = new Map<string, TegakiBundle>();\nconst warnedBundles = new Set<TegakiBundle>();\n\nfunction checkBundleVersion(bundle: TegakiBundle): void {\n if (warnedBundles.has(bundle)) return;\n if (bundle.version == null || !COMPATIBLE_BUNDLE_VERSIONS.has(bundle.version)) {\n warnedBundles.add(bundle);\n console.warn(\n `[tegaki] Bundle \"${bundle.family}\" has version ${bundle.version ?? 'undefined'}, ` +\n `but this engine supports versions [${[...COMPATIBLE_BUNDLE_VERSIONS].join(', ')}]. ` +\n 'The bundle may not render correctly. Regenerate it with a compatible version of tegaki-generator.',\n );\n }\n}\n\n/** Register a font bundle so it can be referenced by family name. */\nexport function registerBundle(bundle: TegakiBundle): void {\n checkBundleVersion(bundle);\n bundles.set(bundle.family, bundle);\n}\n\n/** Look up a registered bundle by family name. */\nexport function getBundle(family: string): TegakiBundle | undefined {\n return bundles.get(family);\n}\n\nexport function resolveBundle(font: TegakiBundle | string | undefined): TegakiBundle | undefined {\n if (typeof font === 'string') {\n const bundle = getBundle(font);\n if (!bundle) throw new Error(`TegakiEngine: no bundle registered for \"${font}\". Call TegakiEngine.registerBundle() first.`);\n return bundle;\n }\n if (font) checkBundleVersion(font);\n return font;\n}\n","import { BUNDLE_VERSION, type LineCap, type TegakiBundle, type TegakiGlyphData } from '../types.ts';\n\n/**\n * Creates a {@link TegakiBundle} from its constituent parts.\n *\n * Useful when loading font data from a CDN or other source where the\n * pre-built bundle modules aren't available:\n *\n * ```js\n * const glyphData = await fetch('.../glyphData.json').then(r => r.json());\n * const bundle = createBundle({\n * family: 'Caveat',\n * fontUrl: '.../caveat.ttf',\n * glyphData,\n * });\n * ```\n */\nexport function createBundle({\n family,\n fullFamily,\n fontUrl,\n fullFontUrl,\n glyphData,\n lineCap = 'round',\n unitsPerEm = 1000,\n ascender = 800,\n descender = -200,\n}: {\n family: string;\n fullFamily?: string;\n fontUrl: string;\n fullFontUrl?: string;\n glyphData: Record<string, TegakiGlyphData>;\n lineCap?: LineCap;\n unitsPerEm?: number;\n ascender?: number;\n descender?: number;\n}): TegakiBundle {\n const rules = [`@font-face { font-family: '${family}'; src: url(${fontUrl}); }`];\n if (fullFamily && fullFontUrl) {\n rules.push(`@font-face { font-family: '${fullFamily}'; src: url(${fullFontUrl}); }`);\n }\n return {\n version: BUNDLE_VERSION,\n family,\n fullFamily,\n lineCap,\n fontUrl,\n fullFontUrl,\n fontFaceCSS: rules.join(' '),\n unitsPerEm,\n ascender,\n descender,\n glyphData,\n };\n}\n","export const CSS_TIME = '--tegaki-time';\nexport const CSS_PROGRESS = '--tegaki-progress';\nexport const CSS_DURATION = '--tegaki-duration';\n\nexport const PADDING_H_EM = 0.2;\nexport const MIN_LINE_HEIGHT_EM = 1.8;\nexport const MIN_PADDING_V_EM = 0.2;\n\n// Register custom properties so they are animatable (typed as <number>).\n// Deferred to first use to avoid running at import time during SSR.\nlet cssPropertiesRegistered = false;\nexport function registerCssProperties() {\n if (cssPropertiesRegistered) return;\n cssPropertiesRegistered = true;\n if (typeof CSS !== 'undefined' && 'registerProperty' in CSS) {\n for (const prop of [CSS_TIME, CSS_PROGRESS, CSS_DURATION]) {\n try {\n CSS.registerProperty({ name: prop, syntax: '<number>', inherits: true, initialValue: '0' });\n } catch {\n // Already registered — ignore.\n }\n }\n }\n}\n","import { findEffect, findEffects, type ResolvedEffect } from './effects.ts';\nimport { resolveCSSLength } from './utils.ts';\n\n/**\n * Draw a fallback glyph (plain text) with applicable effects (glow, gradient, wobble).\n */\nexport function drawFallbackGlyph(\n ctx: CanvasRenderingContext2D,\n char: string,\n x: number,\n baseline: number,\n fontSize: number,\n fontFamily: string,\n color: string,\n effects: ResolvedEffect[] = [],\n seed = 0,\n) {\n const glowEffects = findEffects(effects, 'glow');\n const wobbleEffect = findEffect(effects, 'wobble');\n const gradientEffect = findEffect(effects, 'gradient');\n\n // Wobble offsets\n let dx = 0;\n let dy = 0;\n if (wobbleEffect) {\n const amplitude = (wobbleEffect.config.amplitude ?? 1.5) * (fontSize / 100);\n const frequency = wobbleEffect.config.frequency ?? 8;\n dx = amplitude * Math.sin(frequency * (baseline * 0.01) + seed);\n dy = amplitude * Math.cos(frequency * (x * 0.01) + seed * 1.3);\n }\n\n const drawX = x + dx;\n const drawY = baseline + dy;\n\n // Gradient / rainbow color\n let fillColor = color;\n if (gradientEffect) {\n const colors = gradientEffect.config.colors;\n if (colors === 'rainbow') {\n const saturation = gradientEffect.config.saturation ?? 80;\n const lightness = gradientEffect.config.lightness ?? 55;\n const hue = (seed * 137.5) % 360;\n fillColor = `hsl(${hue}, ${saturation}%, ${lightness}%)`;\n } else if (Array.isArray(colors) && colors.length > 0) {\n fillColor = colors[Math.floor(seed) % colors.length]!;\n }\n }\n\n ctx.save();\n ctx.font = `${fontSize}px ${fontFamily}`;\n ctx.textBaseline = 'alphabetic';\n\n // Glow passes\n for (const glow of glowEffects) {\n ctx.save();\n ctx.shadowBlur = resolveCSSLength(glow.config.radius ?? 8, fontSize);\n ctx.shadowColor = glow.config.color ?? color;\n ctx.shadowOffsetX = glow.config.offsetX ?? 0;\n ctx.shadowOffsetY = glow.config.offsetY ?? 0;\n ctx.fillStyle = glow.config.color ?? color;\n ctx.fillText(char, drawX, drawY);\n ctx.restore();\n }\n\n // Main text\n ctx.fillStyle = fillColor;\n ctx.fillText(char, drawX, drawY);\n\n ctx.restore();\n}\n","import { CSS_DURATION, CSS_PROGRESS, CSS_TIME } from '../lib/css-properties.ts';\nimport { computeTimeline } from '../lib/timeline.ts';\nimport { cssFontFamily } from '../lib/utils.ts';\nimport { resolveBundle } from './bundle-registry.ts';\nimport type { CreateElementFn, TegakiEngineOptions } from './types.ts';\n\nexport const PAD_V_CSS = 'max(0.2em, 0.9em - 0.5lh)';\n\nexport function buildRootProps(options: TegakiEngineOptions): Record<string, any> {\n const text = options.text ?? '';\n const font = resolveBundle(options.font);\n const fontFamily = font ? cssFontFamily(font) : undefined;\n\n const duration = text && font ? computeTimeline(text, font, options.timing).totalDuration : 0;\n const timeObj = typeof options.time === 'object' ? options.time : null;\n const rawTime =\n typeof options.time === 'number'\n ? options.time\n : timeObj?.mode === 'controlled'\n ? timeObj.unit === 'progress'\n ? timeObj.value * duration\n : timeObj.value\n : timeObj?.mode === 'uncontrolled'\n ? (timeObj.initialTime ?? 0)\n : 0;\n const easing = timeObj?.mode === 'uncontrolled' ? timeObj.easing : undefined;\n const time = easing && duration > 0 ? easing(rawTime / duration) * duration : rawTime;\n const progress = duration > 0 ? time / duration : 0;\n\n return {\n 'data-tegaki': 'root',\n style: {\n position: 'relative',\n maxWidth: '100%',\n width: 'auto',\n height: 'auto',\n fontFamily,\n direction: options.direction ?? undefined,\n [CSS_DURATION]: duration,\n [CSS_TIME]: time,\n [CSS_PROGRESS]: progress,\n },\n };\n}\n\nexport function buildChildren<T>(options: TegakiEngineOptions, h: CreateElementFn<T>): T {\n const text = options.text ?? '';\n const isCss = options.time === 'css' || (typeof options.time === 'object' && options.time?.mode === 'css');\n const showOverlay = options.showOverlay;\n\n return h(\n 'span',\n { style: { display: 'block', position: 'relative' } },\n h('span', {\n 'data-tegaki': 'sentinel',\n 'aria-hidden': 'true',\n style: {\n position: 'absolute',\n width: 0,\n overflow: 'hidden',\n pointerEvents: 'none',\n fontSize: 'inherit',\n lineHeight: 'inherit',\n visibility: 'hidden',\n transition: isCss\n ? `font-size 0.001s, line-height 0.001s, color 0.001s, ${CSS_PROGRESS} 0.001s`\n : 'font-size 0.001s, line-height 0.001s, color 0.001s',\n },\n }),\n h(\n 'canvas',\n {\n 'data-tegaki': 'canvas',\n 'aria-hidden': 'true',\n style: {\n // `inset` shorthand not supported by Safari < 14.1 — expand to longhand.\n position: 'absolute',\n top: `calc(-1 * ${PAD_V_CSS})`,\n right: '-0.2em',\n bottom: `calc(-1 * ${PAD_V_CSS})`,\n left: '-0.2em',\n width: 'calc(100% + 0.4em)',\n height: `calc(100% + 2 * ${PAD_V_CSS})`,\n pointerEvents: 'none',\n overflow: 'visible',\n },\n },\n h(\n 'span',\n {\n 'data-tegaki': 'canvas-fallback',\n style: { display: 'inline-block', padding: `${PAD_V_CSS} 0.2em` },\n },\n text,\n ),\n ),\n h(\n 'span',\n {\n 'data-tegaki': 'overlay',\n style: {\n display: 'block',\n userSelect: 'auto',\n whiteSpace: 'pre-wrap',\n overflowWrap: 'break-word',\n paddingInlineEnd: 1,\n WebkitTextFillColor: showOverlay ? undefined : 'transparent',\n color: showOverlay ? 'rgba(255, 0, 0, 0.4)' : undefined,\n },\n },\n text,\n ),\n );\n}\n\n// ---------------------------------------------------------------------------\n// DOM createElement helper (for vanilla JS constructor)\n// ---------------------------------------------------------------------------\n\nexport function domCreateElement(tag: string, props: Record<string, any>, ...children: (HTMLElement | string)[]): HTMLElement {\n const el = document.createElement(tag);\n for (const [key, value] of Object.entries(props)) {\n if (key === 'style' && typeof value === 'object') {\n for (const [k, v] of Object.entries(value as Record<string, any>)) {\n if (v !== undefined && v !== null) {\n if (k.startsWith('--')) {\n el.style.setProperty(k, String(v));\n } else {\n (el.style as any)[k] = typeof v === 'number' && k !== 'opacity' && k !== 'zIndex' ? `${v}px` : v;\n }\n }\n }\n } else if (key === 'aria-hidden') {\n el.setAttribute('aria-hidden', String(value));\n } else if (key.startsWith('data-')) {\n el.setAttribute(key, String(value));\n }\n }\n for (const child of children) {\n if (typeof child === 'string') {\n el.appendChild(document.createTextNode(child));\n } else {\n el.appendChild(child);\n }\n }\n return el;\n}\n","import {\n CSS_DURATION,\n CSS_PROGRESS,\n CSS_TIME,\n MIN_LINE_HEIGHT_EM,\n MIN_PADDING_V_EM,\n PADDING_H_EM,\n registerCssProperties,\n} from '../lib/css-properties.ts';\nimport { drawFallbackGlyph } from '../lib/drawFallbackGlyph.ts';\nimport { drawGlyph } from '../lib/drawGlyph.ts';\nimport { findEffect, type ResolvedEffect, resolveEffects } from '../lib/effects.ts';\nimport { ensureFont } from '../lib/font.ts';\nimport { type SubdividedStroke, subdivideStroke } from '../lib/strokeCache.ts';\nimport type { TextLayout } from '../lib/textLayout.ts';\nimport { computeTextLayout } from '../lib/textLayout.ts';\nimport type { Timeline, TimelineConfig, TimelineEntry } from '../lib/timeline.ts';\nimport { computeTimeline } from '../lib/timeline.ts';\nimport { cssFontFamily, graphemes } from '../lib/utils.ts';\nimport type { TegakiBundle, TegakiGlyphData } from '../types.ts';\nimport { getBundle, registerBundle as registryRegisterBundle, resolveBundle } from './bundle-registry.ts';\nimport { buildChildren, buildRootProps, domCreateElement } from './render-elements.ts';\nimport type { CreateElementFn, TegakiEngineOptions, TegakiQuality, TimeControlMode, TimeControlProp } from './types.ts';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction resolveTimeControl(prop: TimeControlProp): TimeControlMode[keyof TimeControlMode] {\n if (prop == null) return { mode: 'uncontrolled' };\n if (typeof prop === 'number') return { mode: 'controlled', value: prop };\n if (prop === 'css') return { mode: 'css' };\n return prop;\n}\n\n// ---------------------------------------------------------------------------\n// TegakiEngine\n// ---------------------------------------------------------------------------\n\nexport class TegakiEngine {\n // --- Bundle registry (delegates to bundle-registry module) ---\n\n /** Register a font bundle so it can be referenced by family name. */\n static registerBundle(bundle: TegakiBundle): void {\n registryRegisterBundle(bundle);\n }\n\n /** Look up a registered bundle by family name. */\n static getBundle(family: string): TegakiBundle | undefined {\n return getBundle(family);\n }\n\n // --- DOM elements ---\n private _rootEl: HTMLElement;\n private _contentEl: HTMLElement | null = null; // non-null only in non-adopt mode\n private _sentinelEl: HTMLSpanElement;\n private _canvasEl: HTMLCanvasElement;\n private _overlayEl: HTMLElement;\n private _canvasFallbackEl: HTMLSpanElement;\n private _maskCanvas: HTMLCanvasElement | null = null;\n\n // --- Options ---\n private _text = '';\n private _font: TegakiBundle | null = null;\n private _timeControl: TimeControlMode[keyof TimeControlMode] = { mode: 'uncontrolled' };\n private _effects: Record<string, any> | undefined;\n private _timing: TimelineConfig | undefined;\n private _quality: TegakiQuality | undefined;\n private _showOverlay = false;\n private _onComplete: (() => void) | undefined;\n private _direction: 'ltr' | 'rtl' | undefined;\n\n // --- Derived / cached ---\n private _resolvedEffects: ResolvedEffect[] = resolveEffects(undefined);\n private _seed: number;\n private _timeline: Timeline = { entries: [] as TimelineEntry[], totalDuration: 0 };\n private _layout: TextLayout | null = null;\n private _layoutKey = '';\n private _fontReady = false;\n\n // Stroke subdivision cache. Shared across every instance of the same glyph\n // at the current (font, fontSize, segmentSize, effects-need-subdivision)\n // state. Replaced wholesale when that state changes — entries in the old\n // WeakMap are orphaned and GC'd along with the map.\n private _strokeCache: WeakMap<TegakiGlyphData['s'][number], SubdividedStroke> = new WeakMap();\n private _strokeCacheKey = '';\n\n // --- Measured from DOM ---\n private _containerWidth = 0;\n private _fontSize = 0;\n private _lineHeight = 0;\n private _currentColor = '';\n\n // --- Playback state ---\n private _internalTime = 0;\n private _cssTime = 0;\n private _playing = true;\n private _smoothedBoost = 0;\n private _delayRemaining = 0;\n private _loopGapRemaining = 0;\n private _lastTs: number | null = null;\n private _rafId = 0;\n private _prevCompleted = false;\n private _prefersReducedMotion = false;\n private _destroyed = false;\n\n // --- Observers & listeners ---\n private _resizeObserver: ResizeObserver;\n private _mql: MediaQueryList | null = null;\n\n /**\n * Returns the props (including style) that should be applied to the container element,\n * plus the inner content tree rendered via a framework `createElement` callback.\n *\n * Each child element receives a `data-tegaki` attribute so the engine can adopt\n * pre-rendered elements later via `new TegakiEngine(container, { adopt: true })`.\n */\n static renderElements<T>(\n options: TegakiEngineOptions,\n createElement: CreateElementFn<T>,\n ): { rootProps: Record<string, any>; content: T } {\n return {\n rootProps: buildRootProps(options),\n content: buildChildren(options, createElement),\n };\n }\n\n constructor(container: HTMLElement, options?: TegakiEngineOptions & { adopt?: boolean }) {\n registerCssProperties();\n this._seed = Math.random() * 1000;\n\n // --- Resolve DOM elements ---\n // The container itself is the root element. In adopt mode, the adapter has\n // already rendered children inside it. In non-adopt mode, we create them.\n this._rootEl = container;\n\n if (options?.adopt) {\n // Adopt pre-rendered children (created by renderElements)\n } else {\n // Create DOM from scratch\n const content = buildChildren(options ?? {}, domCreateElement);\n container.appendChild(content);\n this._contentEl = content;\n // Apply root styles to the container\n const rootProps = buildRootProps(options ?? {});\n for (const [key, value] of Object.entries(rootProps.style as Record<string, any>)) {\n if (value !== undefined && value !== null) {\n if (key.startsWith('--')) {\n container.style.setProperty(key, String(value));\n } else {\n (container.style as any)[key] = typeof value === 'number' && key !== 'opacity' && key !== 'zIndex' ? `${value}px` : value;\n }\n }\n }\n container.dataset.tegaki = 'root';\n }\n\n this._sentinelEl = container.querySelector('[data-tegaki=\"sentinel\"]') as HTMLSpanElement;\n this._canvasEl = container.querySelector('[data-tegaki=\"canvas\"]') as HTMLCanvasElement;\n this._canvasFallbackEl = container.querySelector('[data-tegaki=\"canvas-fallback\"]') as HTMLSpanElement;\n this._overlayEl = container.querySelector('[data-tegaki=\"overlay\"]') as HTMLElement;\n\n // --- ResizeObserver ---\n this._resizeObserver = new ResizeObserver(this._onResize);\n this._resizeObserver.observe(this._rootEl);\n\n // --- Sentinel transitions ---\n this._sentinelEl.addEventListener('transitionend', this._onSentinelTransition);\n\n // --- Reduced motion ---\n if (typeof window !== 'undefined') {\n this._mql = window.matchMedia('(prefers-reduced-motion: reduce)');\n this._prefersReducedMotion = this._mql.matches;\n if (this._mql.addEventListener) this._mql.addEventListener('change', this._onReducedMotionChange);\n // Safari < 14 only exposes the deprecated addListener API.\n else this._mql.addListener(this._onReducedMotionChange);\n }\n\n // --- Initial measurement (must run before update so layout has valid dimensions) ---\n this._measure();\n\n // --- Apply initial options ---\n if (options) this.update(options);\n }\n\n // =========================================================================\n // Public API\n // =========================================================================\n\n get currentTime(): number {\n const tc = this._timeControl;\n if (tc.mode === 'css') return this._cssTime;\n if (tc.mode === 'controlled') return tc.unit === 'progress' ? tc.value * this._timeline.totalDuration : tc.value;\n const totalDur = this._timeline.totalDuration;\n if (tc.easing && totalDur > 0) {\n return tc.easing(this._internalTime / totalDur) * totalDur;\n }\n return this._internalTime;\n }\n\n get duration(): number {\n return this._timeline.totalDuration;\n }\n\n get isPlaying(): boolean {\n return this._playing;\n }\n\n get isComplete(): boolean {\n const totalDur = this._timeline.totalDuration;\n if (totalDur === 0) return false;\n // For uncontrolled, check linear time so easing curves that overshoot/undershoot\n // the endpoints do not prematurely/belatedly trip completion.\n const tc = this._timeControl;\n if (tc.mode === 'uncontrolled') return this._internalTime >= totalDur;\n return this.currentTime >= totalDur;\n }\n\n get element(): HTMLElement {\n return this._rootEl;\n }\n\n play(): void {\n if (this._timeControl.mode !== 'uncontrolled') return;\n this._playing = true;\n this._evaluatePlayback();\n }\n\n pause(): void {\n if (this._timeControl.mode !== 'uncontrolled') return;\n this._playing = false;\n this._evaluatePlayback();\n }\n\n seek(time: number): void {\n if (this._timeControl.mode !== 'uncontrolled') return;\n this._internalTime = Math.max(0, Math.min(time, this._timeline.totalDuration));\n this._delayRemaining = 0;\n this._loopGapRemaining = 0;\n this._checkCompletion();\n this._notifyTimeChange();\n this._render();\n this._updateCssProperties();\n }\n\n restart(): void {\n if (this._timeControl.mode !== 'uncontrolled') return;\n this._internalTime = 0;\n this._playing = true;\n this._prevCompleted = false;\n this._delayRemaining = this._timeControl.delay ?? 0;\n this._loopGapRemaining = 0;\n this._notifyTimeChange();\n this._evaluatePlayback();\n }\n\n update(options: Partial<TegakiEngineOptions>): void {\n if (this._destroyed) return;\n\n let dirtyTimeline = false;\n let dirtyLayout = false;\n let dirtyRender = false;\n let dirtyPlayback = false;\n\n if ('text' in options) {\n const nextText = (options.text ?? '').replace(/\\r\\n?/g, '\\n');\n if (nextText !== this._text) {\n this._text = nextText;\n dirtyTimeline = true;\n dirtyLayout = true;\n }\n }\n\n if ('font' in options) {\n const resolved = resolveBundle(options.font) ?? null;\n if (resolved !== this._font) {\n this._loadFont(resolved);\n dirtyTimeline = true;\n dirtyLayout = true;\n dirtyPlayback = true;\n }\n }\n\n if ('time' in options) {\n const newTc = resolveTimeControl(options.time);\n const oldTc = this._timeControl;\n\n // Detect meaningful changes\n const modeChanged = newTc.mode !== oldTc.mode;\n const controlledValueChanged =\n newTc.mode === 'controlled' && oldTc.mode === 'controlled' && (newTc.value !== oldTc.value || newTc.unit !== oldTc.unit);\n const uncontrolledChanged =\n newTc.mode === 'uncontrolled' &&\n oldTc.mode === 'uncontrolled' &&\n (newTc.speed !== oldTc.speed ||\n newTc.duration !== oldTc.duration ||\n newTc.playing !== oldTc.playing ||\n newTc.loop !== oldTc.loop ||\n newTc.delay !== oldTc.delay ||\n newTc.loopGap !== oldTc.loopGap ||\n newTc.catchUp !== oldTc.catchUp ||\n newTc.easing !== oldTc.easing);\n\n if (modeChanged || controlledValueChanged || uncontrolledChanged) {\n this._timeControl = newTc;\n\n if (newTc.mode === 'uncontrolled') {\n this._playing = newTc.playing ?? true;\n const oldDelay = oldTc.mode === 'uncontrolled' ? (oldTc.delay ?? 0) : 0;\n const newDelay = newTc.delay ?? 0;\n if (modeChanged || oldDelay !== newDelay) {\n this._delayRemaining = newDelay;\n this._loopGapRemaining = 0;\n }\n }\n\n dirtyPlayback = true;\n dirtyRender = true;\n\n // Update sentinel transition for css mode\n this._updateSentinelTransition();\n }\n }\n\n if ('effects' in options && options.effects !== this._effects) {\n this._effects = options.effects as Record<string, any>;\n this._resolvedEffects = resolveEffects(this._effects);\n dirtyRender = true;\n }\n\n if ('timing' in options && options.timing !== this._timing) {\n this._timing = options.timing;\n dirtyTimeline = true;\n }\n\n if ('quality' in options && options.quality !== this._quality) {\n this._quality = options.quality;\n dirtyRender = true;\n }\n\n if ('direction' in options && options.direction !== this._direction) {\n this._direction = options.direction;\n dirtyLayout = true;\n dirtyRender = true;\n }\n\n if ('showOverlay' in options && options.showOverlay !== this._showOverlay) {\n this._showOverlay = options.showOverlay ?? false;\n this._updateOverlayStyle();\n dirtyRender = true;\n }\n\n if ('onComplete' in options) {\n this._onComplete = options.onComplete;\n }\n\n // --- Recompute ---\n if (dirtyTimeline) this._recomputeTimeline();\n if (dirtyRender || dirtyTimeline || dirtyLayout) this._updateDom();\n if (dirtyLayout) this._recomputeLayout();\n if (dirtyPlayback) this._evaluatePlayback();\n if (dirtyRender || dirtyTimeline || dirtyLayout) this._render();\n }\n\n destroy(): void {\n this._destroyed = true;\n this._stopLoop();\n this._resizeObserver.disconnect();\n this._sentinelEl.removeEventListener('transitionend', this._onSentinelTransition);\n if (this._mql) {\n if (this._mql.removeEventListener) this._mql.removeEventListener('change', this._onReducedMotionChange);\n else this._mql.removeListener(this._onReducedMotionChange);\n }\n // Only remove content we created (non-adopt mode). The container is owned by the caller.\n this._contentEl?.remove();\n // Drop the subdivision cache so the font's strokes aren't kept keyed\n // against this (dead) engine if a caller holds a stale reference.\n this._strokeCache = new WeakMap();\n this._strokeCacheKey = '';\n this._maskCanvas = null;\n }\n\n // =========================================================================\n // Internal: DOM updates\n // =========================================================================\n\n /** Estimate line-height from font metrics when CSS returns \"normal\". */\n private _fallbackLineHeight(fontSize: number): number {\n if (this._font) {\n return ((this._font.ascender - this._font.descender) / this._font.unitsPerEm) * fontSize;\n }\n return fontSize * 1.2;\n }\n\n private _measure(): void {\n const styles = getComputedStyle(this._rootEl);\n this._containerWidth = this._rootEl.getBoundingClientRect().width;\n this._fontSize = Number.parseFloat(styles.fontSize);\n const parsedLh = Number.parseFloat(styles.lineHeight);\n this._lineHeight = Number.isNaN(parsedLh) ? this._fallbackLineHeight(this._fontSize) : parsedLh;\n this._currentColor = styles.color;\n }\n\n private _updateDom(): void {\n // Font family\n this._rootEl.style.fontFamily = this._font ? cssFontFamily(this._font) : '';\n\n // Direction\n this._rootEl.style.direction = this._direction ?? '';\n\n // CSS custom properties\n this._updateCssProperties();\n\n // Overlay text (guard to preserve cursor position when contentEditable)\n if (this._overlayEl.textContent !== this._text) {\n this._overlayEl.textContent = this._text;\n }\n this._canvasFallbackEl.textContent = this._text;\n }\n\n private _updateCssProperties(): void {\n const time = this.currentTime;\n const dur = this._timeline.totalDuration;\n this._rootEl.style.setProperty(CSS_DURATION, String(dur));\n this._rootEl.style.setProperty(CSS_TIME, String(time));\n this._rootEl.style.setProperty(CSS_PROGRESS, String(dur > 0 ? time / dur : 0));\n }\n\n private _updateOverlayStyle(): void {\n if (this._showOverlay) {\n this._overlayEl.style.webkitTextFillColor = '';\n this._overlayEl.style.color = 'rgba(255, 0, 0, 0.4)';\n } else {\n this._overlayEl.style.webkitTextFillColor = 'transparent';\n this._overlayEl.style.color = '';\n }\n }\n\n private _updateSentinelTransition(): void {\n const isCss = this._timeControl.mode === 'css';\n this._sentinelEl.style.transition = isCss\n ? `font-size 0.001s, line-height 0.001s, color 0.001s, ${CSS_PROGRESS} 0.001s`\n : 'font-size 0.001s, line-height 0.001s, color 0.001s';\n }\n\n // =========================================================================\n // Internal: Resize & sentinel observers\n // =========================================================================\n\n private _onResize = (entries: ResizeObserverEntry[]): void => {\n const entry = entries[0];\n if (!entry) return;\n const newWidth = entry.contentRect.width;\n const styles = getComputedStyle(this._rootEl);\n const newFontSize = Number.parseFloat(styles.fontSize);\n const parsedLh = Number.parseFloat(styles.lineHeight);\n const newLineHeight = Number.isNaN(parsedLh) ? this._fallbackLineHeight(newFontSize) : parsedLh;\n const newColor = styles.color;\n\n let changed = false;\n let layoutChanged = false;\n\n if (newWidth !== this._containerWidth) {\n this._containerWidth = newWidth;\n layoutChanged = true;\n changed = true;\n }\n if (newFontSize !== this._fontSize) {\n this._fontSize = newFontSize;\n layoutChanged = true;\n changed = true;\n }\n if (newLineHeight !== this._lineHeight) {\n this._lineHeight = newLineHeight;\n layoutChanged = true;\n changed = true;\n }\n if (newColor !== this._currentColor) {\n this._currentColor = newColor;\n changed = true;\n }\n\n if (layoutChanged) this._recomputeLayout();\n if (changed) this._render();\n };\n\n private _onSentinelTransition = (e: TransitionEvent): void => {\n const styles = getComputedStyle(this._sentinelEl);\n let changed = false;\n\n if (e.propertyName === 'font-size' || e.propertyName === 'line-height') {\n const newFontSize = Number.parseFloat(styles.fontSize);\n const parsedLh = Number.parseFloat(styles.lineHeight);\n const newLineHeight = Number.isNaN(parsedLh) ? this._fallbackLineHeight(newFontSize) : parsedLh;\n if (newFontSize !== this._fontSize || newLineHeight !== this._lineHeight) {\n this._fontSize = newFontSize;\n this._lineHeight = newLineHeight;\n this._recomputeLayout();\n changed = true;\n }\n }\n\n if (e.propertyName === 'color') {\n const newColor = styles.color;\n if (newColor !== this._currentColor) {\n this._currentColor = newColor;\n changed = true;\n }\n }\n\n if (e.propertyName === CSS_PROGRESS) {\n const rawProgress = Number(styles.getPropertyValue(CSS_PROGRESS));\n this._cssTime = rawProgress * this._timeline.totalDuration;\n changed = true;\n }\n\n if (changed) this._render();\n };\n\n // =========================================================================\n // Internal: Reduced motion\n // =========================================================================\n\n private _onReducedMotionChange = (e: MediaQueryListEvent): void => {\n this._prefersReducedMotion = e.matches;\n if (this._prefersReducedMotion && this._timeControl.mode === 'uncontrolled' && this._timeline.totalDuration > 0) {\n this._internalTime = this._timeline.totalDuration;\n }\n this._evaluatePlayback();\n this._render();\n };\n\n // =========================================================================\n // Internal: Font loading\n // =========================================================================\n\n private _loadFont(font: TegakiBundle | null): void {\n this._font = font;\n this._fontReady = false;\n\n if (!font) return;\n\n const pending = ensureFont(font.family, font.fontUrl);\n if (pending === null) {\n this._fontReady = true;\n return;\n }\n\n const currentFont = font;\n pending.then(() => {\n if (this._font === currentFont && !this._destroyed) {\n this._fontReady = true;\n this._recomputeTimeline();\n this._updateDom();\n this._recomputeLayout();\n this._evaluatePlayback();\n this._render();\n }\n });\n }\n\n // =========================================================================\n // Internal: Recomputation\n // =========================================================================\n\n private _recomputeTimeline(): void {\n if (this._font && this._text) {\n this._timeline = computeTimeline(this._text, this._font, this._timing);\n } else {\n this._timeline = { entries: [] as TimelineEntry[], totalDuration: 0 };\n }\n }\n\n private _recomputeLayout(): void {\n if (this._fontReady && this._font?.family && this._fontSize && this._containerWidth && this._text) {\n const key = `${this._text}\\0${this._font.family}\\0${this._fontSize}\\0${this._lineHeight}\\0${this._containerWidth}\\0${this._direction ?? ''}`;\n if (key === this._layoutKey) return;\n this._layoutKey = key;\n this._layout = computeTextLayout(this._overlayEl, this._fontSize);\n } else {\n this._layoutKey = '';\n this._layout = null;\n }\n }\n\n // =========================================================================\n // Internal: Playback loop\n // =========================================================================\n\n private _evaluatePlayback(): void {\n const tc = this._timeControl;\n const shouldRun = tc.mode === 'uncontrolled' && this._playing && !!this._font && this._fontReady && !this._prefersReducedMotion;\n\n if (shouldRun) {\n this._startLoop();\n } else {\n this._stopLoop();\n }\n }\n\n private _startLoop(): void {\n if (this._rafId) return;\n this._lastTs = null;\n this._smoothedBoost = 0;\n this._rafId = requestAnimationFrame(this._tick);\n }\n\n private _stopLoop(): void {\n if (this._rafId) {\n cancelAnimationFrame(this._rafId);\n this._rafId = 0;\n }\n }\n\n private _tick = (ts: number): void => {\n if (this._destroyed) return;\n\n if (this._lastTs === null) this._lastTs = ts;\n const dtSec = (ts - this._lastTs) / 1000;\n this._lastTs = ts;\n\n const tc = this._timeControl;\n if (tc.mode !== 'uncontrolled') return;\n\n const loop = tc.loop ?? false;\n const totalDur = this._timeline.totalDuration;\n const durationOverride = tc.duration;\n const useDuration = durationOverride !== undefined && durationOverride > 0;\n\n if (totalDur === 0 || (!loop && this._internalTime >= totalDur)) {\n this._internalTime = totalDur;\n this._rafId = requestAnimationFrame(this._tick);\n return;\n }\n\n // --- Initial delay ---\n if (this._delayRemaining > 0) {\n this._delayRemaining = Math.max(0, this._delayRemaining - dtSec);\n this._rafId = requestAnimationFrame(this._tick);\n return;\n }\n\n // --- Loop gap (waiting between iterations) ---\n if (this._loopGapRemaining > 0) {\n this._loopGapRemaining = Math.max(0, this._loopGapRemaining - dtSec);\n if (this._loopGapRemaining <= 0) {\n this._internalTime = 0;\n this._prevCompleted = false;\n this._smoothedBoost = 0;\n }\n this._notifyTimeChange();\n this._render();\n this._updateCssProperties();\n this._rafId = requestAnimationFrame(this._tick);\n return;\n }\n\n // Compute effective speed. `duration` stretches the natural timeline to fit\n // a fixed wall-clock slot; otherwise use `speed` + optional `catchUp`.\n let effectiveSpeed: number;\n if (useDuration) {\n effectiveSpeed = totalDur / durationOverride;\n } else {\n const speed = tc.speed ?? 1;\n const catchUp = tc.catchUp ?? 0;\n effectiveSpeed = speed;\n if (catchUp > 0) {\n const remaining = Math.max(0, totalDur - this._internalTime);\n const excess = Math.max(0, remaining - 2);\n const targetBoost = catchUp * excess;\n const attackRate = 4;\n const releaseRate = loop ? 30 : 2;\n const rate = targetBoost > this._smoothedBoost ? attackRate : releaseRate;\n this._smoothedBoost += (targetBoost - this._smoothedBoost) * (1 - Math.exp(-rate * dtSec));\n effectiveSpeed = speed + this._smoothedBoost;\n }\n }\n\n let next = this._internalTime + dtSec * effectiveSpeed;\n if (next >= totalDur) {\n if (loop) {\n const loopGap = tc.loopGap ?? 0;\n if (loopGap > 0) {\n // Hold at the end and start the loop gap countdown\n next = totalDur;\n this._loopGapRemaining = loopGap;\n } else if (this._internalTime < totalDur) {\n // Render one frame at totalDur so every entry (including the\n // last fallback character) satisfies its reveal condition\n // before the animation wraps back to the start.\n next = totalDur;\n } else {\n next %= totalDur;\n }\n } else {\n next = totalDur;\n }\n this._smoothedBoost = 0;\n }\n this._internalTime = next;\n\n this._notifyTimeChange();\n this._checkCompletion();\n this._render();\n this._updateCssProperties();\n\n this._rafId = requestAnimationFrame(this._tick);\n };\n\n private _notifyTimeChange(): void {\n const tc = this._timeControl;\n if (tc.mode === 'uncontrolled' && tc.onTimeChange) {\n // Emit eased time so it matches what's drawn and what CSS variables expose.\n tc.onTimeChange(this.currentTime);\n }\n }\n\n private _checkCompletion(): void {\n const complete = this.isComplete;\n if (complete && !this._prevCompleted) {\n this._prevCompleted = true;\n this._onComplete?.();\n } else if (!complete) {\n this._prevCompleted = false;\n }\n }\n\n // =========================================================================\n // Internal: Canvas rendering\n // =========================================================================\n\n private _render(): void {\n const canvas = this._canvasEl;\n const font = this._font;\n const layout = this._layout;\n const fontSize = this._fontSize;\n\n const dpr = window.devicePixelRatio || 1;\n // Supersampling: draw into a backing canvas larger than the displayed CSS\n // size, then let the browser downsample. Improves antialiasing at a\n // quadratic cost in pixels filled.\n const pixelRatio = Math.max(this._quality?.pixelRatio ?? 1, 0);\n const effectiveDpr = dpr * pixelRatio;\n const w = canvas.offsetWidth;\n const h = canvas.offsetHeight;\n\n const needsResize = canvas.width !== Math.round(w * effectiveDpr) || canvas.height !== Math.round(h * effectiveDpr);\n if (needsResize) {\n canvas.width = Math.round(w * effectiveDpr);\n canvas.height = Math.round(h * effectiveDpr);\n }\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n ctx.setTransform(effectiveDpr, 0, 0, effectiveDpr, 0, 0);\n ctx.clearRect(0, 0, w, h);\n\n // Nothing to draw (e.g. empty text) — but the clear above still needs to\n // run so stale pixels from the previous render don't linger.\n if (!font?.glyphData || !layout || !fontSize) return;\n\n const padH = PADDING_H_EM * fontSize;\n const lineHeight = this._lineHeight;\n const padV = Math.max(MIN_PADDING_V_EM * fontSize, (MIN_LINE_HEIGHT_EM * fontSize - lineHeight) / 2);\n ctx.translate(padH, padV);\n\n const color = this._currentColor || 'black';\n const emHeight = (font.ascender - font.descender) / font.unitsPerEm;\n const emHeightPx = emHeight * fontSize;\n const halfLeading = (lineHeight - emHeightPx) / 2;\n const characters = graphemes(this._text);\n const currentTime = this.currentTime;\n\n // --- Subdivision cache setup ---\n // `maxSegLenFU` is the subdivision threshold in font units. It collapses\n // every input that matters (segmentSize in CSS px, fontSize, unitsPerEm,\n // whether any effect needs subdivision) into a single value, so the cache\n // key is just (font family, maxSegLenFU). When anything that affects\n // subdivision changes, the key changes and the WeakMap is swapped out.\n const effectsNeedSubdivision =\n !!findEffect(this._resolvedEffects, 'wobble') ||\n !!findEffect(this._resolvedEffects, 'gradient') ||\n !!findEffect(this._resolvedEffects, 'taper') ||\n (() => {\n const p = findEffect(this._resolvedEffects, 'pressureWidth');\n return !!p && Math.max(0, Math.min(p.config.strength ?? 1, 1)) > 0;\n })();\n const smoothing = this._quality?.smoothing === true;\n const userSegmentSize = this._quality?.segmentSize;\n const resolvedSegmentSize = userSegmentSize ?? (effectsNeedSubdivision || smoothing ? 2 : undefined);\n const scale = fontSize / font.unitsPerEm;\n const maxSegLenFU = resolvedSegmentSize != null ? resolvedSegmentSize / scale : Infinity;\n const cacheKey = `${font.family}|${maxSegLenFU}|${smoothing ? 's' : 'l'}`;\n if (cacheKey !== this._strokeCacheKey) {\n this._strokeCache = new WeakMap();\n this._strokeCacheKey = cacheKey;\n }\n const strokeCache = this._strokeCache;\n const getSubdivided = (stroke: TegakiGlyphData['s'][number]): SubdividedStroke => {\n let sub = strokeCache.get(stroke);\n if (!sub) {\n sub = subdivideStroke(stroke, maxSegLenFU, smoothing);\n strokeCache.set(stroke, sub);\n }\n return sub;\n };\n\n const clipText = this._quality?.clipText;\n const strokeScale = typeof clipText === 'number' ? clipText : 1;\n\n let y = 0;\n for (const lineIndices of layout.lines) {\n for (const charIdx of lineIndices) {\n const char = characters[charIdx]!;\n if (char === '\\n') continue;\n const entry = this._timeline.entries[charIdx]!;\n const x = (layout.charOffsets[charIdx] ?? 0) * fontSize;\n const glyph = font.glyphData[char];\n\n if (glyph && entry.hasGlyph) {\n let localTime = Math.max(0, Math.min(currentTime - entry.offset, entry.duration));\n const glyphEasing = this._timing?.glyphEasing;\n if (glyphEasing && entry.duration > 0) {\n localTime = glyphEasing(localTime / entry.duration) * entry.duration;\n }\n const glyphY = y + halfLeading;\n drawGlyph(\n ctx,\n glyph,\n {\n x,\n y: glyphY,\n fontSize,\n unitsPerEm: font.unitsPerEm,\n ascender: font.ascender,\n descender: font.descender,\n },\n localTime,\n font.lineCap,\n color,\n this._resolvedEffects,\n this._seed + charIdx,\n getSubdivided,\n this._timing?.strokeEasing,\n strokeScale,\n );\n } else if (!entry.hasGlyph && currentTime >= entry.offset + entry.duration) {\n const baseline = y + halfLeading + (font.ascender / font.unitsPerEm) * fontSize;\n drawFallbackGlyph(ctx, char, x, baseline, fontSize, cssFontFamily(font), color, this._resolvedEffects, this._seed + charIdx);\n }\n }\n y += lineHeight;\n }\n\n // --- Clip strokes to the filled text shape ---\n // All text characters are rendered onto a cached offscreen canvas so the\n // mask can be applied as a single destination-in drawImage call. Doing\n // fillText per-character with destination-in would erase previously-clipped\n // strokes.\n if (clipText) {\n if (!this._maskCanvas) this._maskCanvas = document.createElement('canvas');\n const maskCanvas = this._maskCanvas;\n if (maskCanvas.width !== canvas.width || maskCanvas.height !== canvas.height) {\n maskCanvas.width = canvas.width;\n maskCanvas.height = canvas.height;\n }\n const maskCtx = maskCanvas.getContext('2d')!;\n maskCtx.setTransform(effectiveDpr, 0, 0, effectiveDpr, 0, 0);\n maskCtx.clearRect(0, 0, w, h);\n maskCtx.translate(padH, padV);\n maskCtx.font = `${fontSize}px ${cssFontFamily(font)}`;\n maskCtx.textBaseline = 'alphabetic';\n let clipY = 0;\n for (const lineIndices of layout.lines) {\n for (const charIdx of lineIndices) {\n const char = characters[charIdx]!;\n if (char === '\\n') continue;\n const x = (layout.charOffsets[charIdx] ?? 0) * fontSize;\n const baseline = clipY + halfLeading + (font.ascender / font.unitsPerEm) * fontSize;\n maskCtx.fillText(char, x, baseline);\n }\n clipY += lineHeight;\n }\n\n ctx.save();\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.globalCompositeOperation = 'destination-in';\n ctx.drawImage(maskCanvas, 0, 0);\n ctx.restore();\n }\n }\n}\n"],"mappings":"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"}
@@ -420,6 +420,15 @@ interface TegakiQuality {
420
420
  * producing a result closer to the original filled text.
421
421
  */
422
422
  clipText?: boolean | number;
423
+ /**
424
+ * Smooth stroke polylines with a centripetal Catmull-Rom spline through the
425
+ * original glyph points. Hides the faceted corners visible at large render
426
+ * sizes where the baked polyline resolution shows through.
427
+ *
428
+ * Requires subdivision to be active — when enabled without a `segmentSize`,
429
+ * `segmentSize` defaults to `2` CSS px. Default: `false` (unchanged behavior).
430
+ */
431
+ smoothing?: boolean;
423
432
  }
424
433
  interface TegakiEngineOptions {
425
434
  text?: string;
@@ -537,4 +546,4 @@ declare function buildChildren<T>(options: TegakiEngineOptions, h: CreateElement
537
546
  declare function domCreateElement(tag: string, props: Record<string, any>, ...children: (HTMLElement | string)[]): HTMLElement;
538
547
  //#endregion
539
548
  export { LineCap as A, TegakiSingletonEffectName as B, resolveEffects as C, CSSLength as D, COMPATIBLE_BUNDLE_VERSIONS as E, TegakiEffectConfigs as F, TegakiEffectName as I, TegakiEffects as L, Point as M, Stroke$1 as N, FontOutput as O, TegakiBundle as P, TegakiGlyphData as R, ResolvedEffect as S, BUNDLE_VERSION as T, TimedPoint as V, computeTimeline as _, CreateElementFn as a, ensureFontFace as b, TimeControlMode as c, getBundle as d, registerBundle as f, TimelineEntry as g, TimelineConfig as h, TegakiEngine as i, PathCommand as j, GlyphData as k, TimeControlProp as l, Timeline as m, buildRootProps as n, TegakiEngineOptions as o, resolveBundle as p, domCreateElement as r, TegakiQuality as s, buildChildren as t, createBundle as u, TextLayout as v, BBox as w, drawGlyph as x, computeTextLayout as y, TegakiMultiEffectName as z };
540
- //# sourceMappingURL=index-e-RN9Gi3.d.mts.map
549
+ //# sourceMappingURL=index-BBRejOMe.d.mts.map
@@ -1,4 +1,4 @@
1
- import { L as TegakiEffects, i as TegakiEngine, o as TegakiEngineOptions } from "./index-e-RN9Gi3.mjs";
1
+ import { L as TegakiEffects, i as TegakiEngine, o as TegakiEngineOptions } from "./index-BBRejOMe.mjs";
2
2
  import * as _$react from "react";
3
3
  import { ComponentPropsWithoutRef, ElementType, Ref } from "react";
4
4
 
@@ -31,4 +31,4 @@ type TegakiRendererProps<C extends ElementType = 'div', E extends TegakiEffects<
31
31
  declare function TegakiRenderer<const C extends ElementType = 'div', const E extends TegakiEffects<E> = Record<string, never>>(props: TegakiRendererProps<C, E>): _$react.ReactElement<any, string | _$react.JSXElementConstructor<any>>;
32
32
  //#endregion
33
33
  export { TegakiRendererHandle as n, TegakiRendererProps as r, TegakiRenderer as t };
34
- //# sourceMappingURL=index-BwhATGJw.d.mts.map
34
+ //# sourceMappingURL=index-DlYttK-R.d.mts.map
package/dist/index.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- import { A as LineCap, B as TegakiSingletonEffectName, C as resolveEffects, D as CSSLength, E as COMPATIBLE_BUNDLE_VERSIONS, F as TegakiEffectConfigs, I as TegakiEffectName, L as TegakiEffects, M as Point, N as Stroke, O as FontOutput, P as TegakiBundle, R as TegakiGlyphData, S as ResolvedEffect, T as BUNDLE_VERSION, V as TimedPoint, _ as computeTimeline, a as CreateElementFn, b as ensureFontFace, c as TimeControlMode, d as getBundle, f as registerBundle, g as TimelineEntry, h as TimelineConfig, i as TegakiEngine, j as PathCommand, k as GlyphData, l as TimeControlProp, m as Timeline, n as buildRootProps, o as TegakiEngineOptions, p as resolveBundle, r as domCreateElement, s as TegakiQuality, t as buildChildren, u as createBundle, v as TextLayout, w as BBox, x as drawGlyph, y as computeTextLayout, z as TegakiMultiEffectName } from "./index-e-RN9Gi3.mjs";
2
- import { n as TegakiRendererHandle, r as TegakiRendererProps, t as TegakiRenderer } from "./index-BwhATGJw.mjs";
1
+ import { A as LineCap, B as TegakiSingletonEffectName, C as resolveEffects, D as CSSLength, E as COMPATIBLE_BUNDLE_VERSIONS, F as TegakiEffectConfigs, I as TegakiEffectName, L as TegakiEffects, M as Point, N as Stroke, O as FontOutput, P as TegakiBundle, R as TegakiGlyphData, S as ResolvedEffect, T as BUNDLE_VERSION, V as TimedPoint, _ as computeTimeline, a as CreateElementFn, b as ensureFontFace, c as TimeControlMode, d as getBundle, f as registerBundle, g as TimelineEntry, h as TimelineConfig, i as TegakiEngine, j as PathCommand, k as GlyphData, l as TimeControlProp, m as Timeline, n as buildRootProps, o as TegakiEngineOptions, p as resolveBundle, r as domCreateElement, s as TegakiQuality, t as buildChildren, u as createBundle, v as TextLayout, w as BBox, x as drawGlyph, y as computeTextLayout, z as TegakiMultiEffectName } from "./index-BBRejOMe.mjs";
2
+ import { n as TegakiRendererHandle, r as TegakiRendererProps, t as TegakiRenderer } from "./index-DlYttK-R.mjs";
3
3
  export { BBox, BUNDLE_VERSION, COMPATIBLE_BUNDLE_VERSIONS, CSSLength, CreateElementFn, FontOutput, GlyphData, LineCap, PathCommand, Point, ResolvedEffect, Stroke, TegakiBundle, TegakiEffectConfigs, TegakiEffectName, TegakiEffects, TegakiEngine, TegakiEngineOptions, TegakiGlyphData, TegakiMultiEffectName, TegakiQuality, TegakiRenderer, TegakiRendererHandle, TegakiRendererProps, TegakiSingletonEffectName, TextLayout, TimeControlMode, TimeControlProp, TimedPoint, Timeline, TimelineConfig, TimelineEntry, buildChildren, buildRootProps, computeTextLayout, computeTimeline, createBundle, domCreateElement, drawGlyph, ensureFontFace, getBundle, registerBundle, resolveBundle, resolveEffects };
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { a as createBundle, c as resolveBundle, d as computeTimeline, f as computeTextLayout, g as resolveEffects, i as domCreateElement, l as BUNDLE_VERSION, m as drawGlyph, n as buildChildren, o as getBundle, p as ensureFontFace, r as buildRootProps, s as registerBundle, t as TegakiEngine, u as COMPATIBLE_BUNDLE_VERSIONS } from "./core-Ds9ohwR7.mjs";
2
- import { t as TegakiRenderer } from "./react-D3lP9bU2.mjs";
1
+ import { a as createBundle, c as resolveBundle, d as computeTimeline, f as computeTextLayout, g as resolveEffects, i as domCreateElement, l as BUNDLE_VERSION, m as drawGlyph, n as buildChildren, o as getBundle, p as ensureFontFace, r as buildRootProps, s as registerBundle, t as TegakiEngine, u as COMPATIBLE_BUNDLE_VERSIONS } from "./core-BYU5BEaZ.mjs";
2
+ import { t as TegakiRenderer } from "./react-0gtC94df.mjs";
3
3
  export { BUNDLE_VERSION, COMPATIBLE_BUNDLE_VERSIONS, TegakiEngine, TegakiRenderer, buildChildren, buildRootProps, computeTextLayout, computeTimeline, createBundle, domCreateElement, drawGlyph, ensureFontFace, getBundle, registerBundle, resolveBundle, resolveEffects };