tegaki 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # tegaki
2
2
 
3
+ ## 0.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`706375b`](https://github.com/KurtGokhan/tegaki/commit/706375bf056caefb8fd4c4279da9e0124535b706) Thanks [@KurtGokhan](https://github.com/KurtGokhan)! - Accessibility, SSR and RSC fixes
8
+
3
9
  ## 0.3.0
4
10
 
5
11
  ### Minor Changes
package/dist/index.mjs CHANGED
@@ -519,19 +519,25 @@ const MIN_PADDING_V_EM = .2;
519
519
  const CSS_TIME = "--tegaki-time";
520
520
  const CSS_PROGRESS = "--tegaki-progress";
521
521
  const CSS_DURATION = "--tegaki-duration";
522
- if (typeof CSS !== "undefined" && "registerProperty" in CSS) for (const prop of [
523
- CSS_TIME,
524
- CSS_PROGRESS,
525
- CSS_DURATION
526
- ]) try {
527
- CSS.registerProperty({
528
- name: prop,
529
- syntax: "<number>",
530
- inherits: true,
531
- initialValue: "0"
532
- });
533
- } catch {}
522
+ let cssPropertiesRegistered = false;
523
+ function registerCssProperties() {
524
+ if (cssPropertiesRegistered) return;
525
+ cssPropertiesRegistered = true;
526
+ if (typeof CSS !== "undefined" && "registerProperty" in CSS) for (const prop of [
527
+ CSS_TIME,
528
+ CSS_PROGRESS,
529
+ CSS_DURATION
530
+ ]) try {
531
+ CSS.registerProperty({
532
+ name: prop,
533
+ syntax: "<number>",
534
+ inherits: true,
535
+ initialValue: "0"
536
+ });
537
+ } catch {}
538
+ }
534
539
  function TegakiRenderer({ ref, font, text, children, time: timeProp, onComplete, effects, segmentSize, timing, showOverlay, ...props }) {
540
+ registerCssProperties();
535
541
  const resolvedText = text ?? coerceToString(children);
536
542
  const resolvedEffects = useMemo(() => resolveEffects(effects), [effects]);
537
543
  const [seed] = useState(() => Math.random() * 1e3);
@@ -568,7 +574,7 @@ function TegakiRenderer({ ref, font, text, children, time: timeProp, onComplete,
568
574
  onTimeChangeRef.current = onTimeChange;
569
575
  const onCompleteRef = useRef(onComplete);
570
576
  onCompleteRef.current = onComplete;
571
- const [fontReady, setFontReady] = useState(() => !!font && document.fonts.check(`16px "${font?.family}"`));
577
+ const [fontReady, setFontReady] = useState(() => typeof document !== "undefined" && !!font && document.fonts.check(`16px "${font.family}"`));
572
578
  useEffect(() => {
573
579
  if (!font) {
574
580
  setFontReady(false);
@@ -638,8 +644,23 @@ function TegakiRenderer({ ref, font, text, children, time: timeProp, onComplete,
638
644
  useEffect(() => {
639
645
  if (!isControlled) onTimeChangeRef.current?.(internalTime);
640
646
  }, [internalTime, isControlled]);
647
+ const [prefersReducedMotion, setPrefersReducedMotion] = useState(() => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches);
648
+ useEffect(() => {
649
+ const mql = window.matchMedia("(prefers-reduced-motion: reduce)");
650
+ setPrefersReducedMotion(mql.matches);
651
+ const onChange = (e) => setPrefersReducedMotion(e.matches);
652
+ mql.addEventListener("change", onChange);
653
+ return () => mql.removeEventListener("change", onChange);
654
+ }, []);
655
+ useEffect(() => {
656
+ if (prefersReducedMotion && !isControlled && timeline.totalDuration > 0) setInternalTime(timeline.totalDuration);
657
+ }, [
658
+ prefersReducedMotion,
659
+ isControlled,
660
+ timeline.totalDuration
661
+ ]);
641
662
  useEffect(() => {
642
- if (isControlled || !playing || !font || !fontReady) return;
663
+ if (isControlled || !playing || !font || !fontReady || prefersReducedMotion) return;
643
664
  smoothedBoostRef.current = 0;
644
665
  let lastTs = null;
645
666
  let raf;
@@ -678,7 +699,8 @@ function TegakiRenderer({ ref, font, text, children, time: timeProp, onComplete,
678
699
  loop,
679
700
  catchUp,
680
701
  font,
681
- fontReady
702
+ fontReady,
703
+ prefersReducedMotion
682
704
  ]);
683
705
  useEffect(() => {
684
706
  const el = rootRef.current;
@@ -792,10 +814,6 @@ function TegakiRenderer({ ref, font, text, children, time: timeProp, onComplete,
792
814
  seed,
793
815
  segmentSize
794
816
  ]);
795
- if (!font || !resolvedText || !fontReady) return /* @__PURE__ */ jsx("div", {
796
- ref: rootRef,
797
- ...props
798
- });
799
817
  return /* @__PURE__ */ jsx("div", {
800
818
  ref: rootRef,
801
819
  ...props,
@@ -814,7 +832,7 @@ function TegakiRenderer({ ref, font, text, children, time: timeProp, onComplete,
814
832
  children: [
815
833
  /* @__PURE__ */ jsx("span", {
816
834
  ref: sentinelRef,
817
- "aria-hidden": true,
835
+ "aria-hidden": "true",
818
836
  style: {
819
837
  position: "absolute",
820
838
  width: 0,
@@ -829,7 +847,7 @@ function TegakiRenderer({ ref, font, text, children, time: timeProp, onComplete,
829
847
  }),
830
848
  /* @__PURE__ */ jsx("canvas", {
831
849
  ref: canvasRef,
832
- "aria-hidden": true,
850
+ "aria-hidden": "true",
833
851
  style: {
834
852
  position: "absolute",
835
853
  inset: `${-padV}px ${-padH}px`,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/lib/effects.ts","../src/lib/utils.ts","../src/lib/drawGlyph.ts","../src/lib/drawFallbackGlyph.ts","../src/lib/textLayout.ts","../src/lib/timeline.ts","../src/lib/TegakiRenderer.tsx"],"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\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 if (!effects) return [];\n\n const knownEffects: Set<string> = new Set(['glow', 'wobble', 'pressureWidth', 'taper', 'gradient']);\n const result: ResolvedEffect[] = [];\n\n for (const [key, value] of Object.entries(effects)) {\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","import type { CSSLength } from '../types.ts';\n\nconst segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });\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 return Array.from(segmenter.segment(text), (s) => s.segment);\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 { resolveCSSLength } from './utils.ts';\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/**\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 */\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 segmentSize?: number,\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\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\n // Helper: apply wobble offset to a point in font units\n const wobbleX = (x: number, y: number, idx: number) => {\n if (!wobbleEffect) return x;\n if (wobbleMode === 'noise') {\n return x + wobbleAmplitude * (noise1d(y * 0.1 + idx * 0.7, seed) * 2 - 1);\n }\n return x + wobbleAmplitude * Math.sin(wobbleFrequency * (y * 0.01 + idx * 0.7) + seed);\n };\n const wobbleY = (x: number, y: number, idx: number) => {\n if (!wobbleEffect) return y;\n if (wobbleMode === 'noise') {\n return y + wobbleAmplitude * (noise1d(x * 0.1 + idx * 0.5, seed * 1.3 + 1000) * 2 - 1);\n }\n return y + 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 const hasGradient = !!gradientEffect;\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 progress = Math.min(elapsed / stroke.a, 1);\n\n const pts = stroke.p;\n if (pts.length === 0) continue;\n\n const avgWidth = pts.reduce((s, p) => s + p[2], 0) / pts.length;\n const baseLineWidth = Math.max(avgWidth, 0.5) * scale;\n\n // --- Single-point dot ---\n if (pts.length === 1) {\n if (progress <= 0) continue;\n const p = pts[0]!;\n const dotX = px(wobbleX(p[0], p[1], 0));\n const dotY = py(wobbleY(p[0], p[1], 0));\n const perPointDot = Math.max(p[2], 0.5) * scale;\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 // --- Compute total path length ---\n let totalLen = 0;\n for (let j = 1; j < pts.length; j++) {\n const dx = pts[j]![0] - pts[j - 1]![0];\n const dy = pts[j]![1] - pts[j - 1]![1];\n totalLen += Math.sqrt(dx * dx + dy * dy);\n }\n\n const drawLen = totalLen * progress;\n if (drawLen <= 0) continue;\n\n // --- Collect drawable segments ---\n const segments: {\n x0: number;\n y0: number;\n x1: number;\n y1: number;\n width0: number;\n width1: number;\n segProgress: number;\n }[] = [];\n\n let accumulated = 0;\n for (let j = 1; j < pts.length; 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 segLen = Math.sqrt(dx * dx + dy * dy);\n\n if (accumulated + segLen <= drawLen) {\n segments.push({\n x0: px(wobbleX(prev[0], prev[1], j - 1)),\n y0: py(wobbleY(prev[0], prev[1], j - 1)),\n x1: px(wobbleX(cur[0], cur[1], j)),\n y1: py(wobbleY(cur[0], cur[1], j)),\n width0: prev[2],\n width1: cur[2],\n segProgress: (accumulated + segLen / 2) / totalLen,\n });\n accumulated += segLen;\n } else {\n const remaining = drawLen - accumulated;\n const frac = segLen > 0 ? remaining / segLen : 0;\n const ix = prev[0] + dx * frac;\n const iy = prev[1] + dy * frac;\n const iw = prev[2] + (cur[2] - prev[2]) * frac;\n segments.push({\n x0: px(wobbleX(prev[0], prev[1], j - 1)),\n y0: py(wobbleY(prev[0], prev[1], j - 1)),\n x1: px(wobbleX(ix, iy, j)),\n y1: py(wobbleY(ix, iy, j)),\n width0: prev[2],\n width1: iw,\n segProgress: (accumulated + remaining / 2) / totalLen,\n });\n break;\n }\n }\n\n if (segments.length === 0) continue;\n\n // Keep coarse segments for glow (shadowBlur is expensive per draw call)\n const coarseSegments = segments.slice();\n\n // --- Subdivide long segments for smooth effect transitions ---\n const effectsNeedSubdivision = pressureAmount > 0 || hasGradient || !!wobbleEffect || !!taperEffect;\n const resolvedSegmentSize = segmentSize ?? (effectsNeedSubdivision ? 2 : undefined);\n if (resolvedSegmentSize != null) {\n const maxSegLen = resolvedSegmentSize * scale;\n const subdivided: typeof segments = [];\n for (const seg of segments) {\n const dx = seg.x1 - seg.x0;\n const dy = seg.y1 - seg.y0;\n const len = Math.sqrt(dx * dx + dy * dy);\n const count = Math.max(1, Math.ceil(len / maxSegLen));\n for (let k = 0; k < count; k++) {\n const t0 = k / count;\n const t1 = (k + 1) / count;\n subdivided.push({\n x0: seg.x0 + dx * t0,\n y0: seg.y0 + dy * t0,\n x1: seg.x0 + dx * t1,\n y1: seg.y0 + dy * t1,\n width0: seg.width0 + (seg.width1 - seg.width0) * t0,\n width1: seg.width0 + (seg.width1 - seg.width0) * t1,\n segProgress: seg.segProgress,\n });\n }\n }\n for (let k = 0; k < subdivided.length; k++) {\n subdivided[k]!.segProgress = subdivided.length > 1 ? k / (subdivided.length - 1) : 0;\n }\n segments.length = 0;\n segments.push(...subdivided);\n }\n\n // Helper: compute segment line width with pressure and taper\n const segWidth = (seg: (typeof segments)[0]) => {\n const perPoint = ((seg.width0 + seg.width1) / 2) * scale;\n const w = Math.max(baseLineWidth + (perPoint - baseLineWidth) * pressureAmount, 0.5 * scale);\n return w * taperMultiplier(seg.segProgress);\n };\n\n const needsPerSegment = pressureAmount > 0 || taperEffect;\n\n const drawStrokePath = () => {\n if (needsPerSegment) {\n for (const seg of segments) {\n ctx.lineWidth = segWidth(seg);\n ctx.beginPath();\n ctx.moveTo(seg.x0, seg.y0);\n ctx.lineTo(seg.x1, seg.y1);\n ctx.stroke();\n }\n } else {\n ctx.lineWidth = baseLineWidth;\n ctx.beginPath();\n ctx.moveTo(segments[0]!.x0, segments[0]!.y0);\n for (const seg of segments) {\n ctx.lineTo(seg.x1, seg.y1);\n }\n ctx.stroke();\n }\n };\n\n const drawGradientPath = () => {\n for (const seg of segments) {\n ctx.strokeStyle = colorAt(seg.segProgress);\n if (needsPerSegment) ctx.lineWidth = segWidth(seg);\n ctx.beginPath();\n ctx.moveTo(seg.x0, seg.y0);\n ctx.lineTo(seg.x1, seg.y1);\n ctx.stroke();\n }\n };\n\n ctx.lineCap = lineCap;\n ctx.lineJoin = 'round';\n\n // --- Glow passes (use coarse segments to avoid expensive per-subsegment shadowBlur) ---\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 ctx.beginPath();\n ctx.moveTo(coarseSegments[0]!.x0, coarseSegments[0]!.y0);\n for (const seg of coarseSegments) {\n ctx.lineTo(seg.x1, seg.y1);\n }\n ctx.stroke();\n ctx.restore();\n }\n\n // --- Main stroke ---\n if (hasGradient) {\n drawGradientPath();\n } else {\n ctx.strokeStyle = color;\n drawStrokePath();\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 { layoutWithLines, prepareWithSegments } from '@chenglou/pretext';\nimport { graphemes } from './utils.ts';\n\nexport interface TextLayout {\n /** Character indices per line */\n lines: number[][];\n /** Width in em per character index */\n charWidths: number[];\n /** Kerning adjustment in em between character at index i and i+1 */\n kernings: number[];\n /** Intrinsic (single-line) width in em */\n intrinsicWidth: number;\n}\n\nexport function computeTextLayout(text: string, fontFamily: string, fontSize: number, lineHeight: number, maxWidth: number): TextLayout {\n const fontStr = `${fontSize}px ${fontFamily}`;\n const chars = graphemes(text);\n\n // Measure unique character widths\n const widthCache = new Map<string, number>();\n const charWidths: number[] = [];\n for (const char of chars) {\n let w = widthCache.get(char);\n if (w === undefined) {\n if (char === '\\n') {\n w = 0;\n } else {\n const p = prepareWithSegments(char, fontStr, { whiteSpace: 'pre-wrap' });\n const r = layoutWithLines(p, Infinity, lineHeight);\n w = r.lines.length > 0 ? r.lines[0]!.width / fontSize : 0;\n }\n widthCache.set(char, w);\n }\n charWidths.push(w);\n }\n\n // Compute intrinsic width (single-line, no wrapping)\n const prepared = prepareWithSegments(text, fontStr, { whiteSpace: 'pre-wrap' });\n const singleLineResult = layoutWithLines(prepared, Infinity, lineHeight);\n const intrinsicWidth = Math.max(0, ...singleLineResult.lines.map((l) => l.width)) / fontSize;\n\n // Line breaking at actual available width\n const result = layoutWithLines(prepared, maxWidth, lineHeight);\n\n // Map line texts back to character indices (grapheme-based)\n // Build a mapping from UTF-16 offset to grapheme index\n const utf16ToCodePoint: number[] = [];\n for (let ci = 0; ci < chars.length; ci++) {\n for (let j = 0; j < chars[ci]!.length; j++) {\n utf16ToCodePoint.push(ci);\n }\n }\n\n const lines: number[][] = [];\n let utf16Offset = 0;\n for (const line of result.lines) {\n const indices: number[] = [];\n const seen = new Set<number>();\n for (let i = 0; i < line.text.length; i++) {\n const cpIdx = utf16ToCodePoint[utf16Offset + i]!;\n if (!seen.has(cpIdx)) {\n seen.add(cpIdx);\n indices.push(cpIdx);\n }\n }\n utf16Offset += line.text.length;\n // Consume the newline that caused this line break\n if (utf16Offset < text.length && text[utf16Offset] === '\\n') {\n const cpIdx = utf16ToCodePoint[utf16Offset]!;\n indices.push(cpIdx);\n utf16Offset++;\n }\n lines.push(indices);\n }\n\n // Any remaining characters (shouldn't happen, but safety)\n if (utf16Offset < text.length) {\n const indices: number[] = [];\n const seen = new Set<number>();\n for (let i = utf16Offset; i < text.length; i++) {\n const cpIdx = utf16ToCodePoint[i]!;\n if (!seen.has(cpIdx)) {\n seen.add(cpIdx);\n indices.push(cpIdx);\n }\n }\n lines.push(indices);\n }\n\n // Measure kerning between adjacent character pairs\n const kernings: number[] = [];\n const pairCache = new Map<string, number>();\n for (let i = 0; i < chars.length - 1; i++) {\n const a = chars[i]!;\n const b = chars[i + 1]!;\n if (a === '\\n' || b === '\\n') {\n kernings.push(0);\n continue;\n }\n const pair = `${a}${b}`;\n let k = pairCache.get(pair);\n if (k === undefined) {\n const p = prepareWithSegments(pair, fontStr, { whiteSpace: 'pre-wrap' });\n const r = layoutWithLines(p, Infinity, lineHeight);\n const pairWidth = r.lines.length > 0 ? r.lines[0]!.width / fontSize : 0;\n k = pairWidth - (widthCache.get(a) ?? 0) - (widthCache.get(b) ?? 0);\n if (Math.abs(k) < 0.001) k = 0;\n pairCache.set(pair, k);\n }\n kernings.push(k);\n }\n\n return { lines, charWidths, kernings, intrinsicWidth };\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\nconst DEFAULTS: Required<TimelineConfig> = {\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 duration = hasGlyph ? (glyph.t ?? 1) : unknownDuration;\n entries.push({ char, offset, duration, hasGlyph });\n offset += duration;\n\n // Gap after this character\n if (char === '\\n') {\n offset += lineGap;\n } else if (char === ' ') {\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 : lastChar === ' ' ? wordGap : glyphGap;\n offset -= trailingGap;\n }\n return { entries, totalDuration: Math.max(0, offset) };\n}\n","import { type ComponentProps, type Ref, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport type { TegakiBundle, TegakiEffects } from '../types.ts';\nimport { drawFallbackGlyph } from './drawFallbackGlyph.ts';\nimport { drawGlyph } from './drawGlyph.ts';\nimport { resolveEffects } from './effects.ts';\nimport { computeTextLayout } from './textLayout.ts';\nimport type { TimelineConfig, TimelineEntry } from './timeline.ts';\nimport { computeTimeline } from './timeline.ts';\nimport type { Coercible } from './utils.ts';\nimport { coerceToString, graphemes } from './utils.ts';\n\nconst PADDING_H_EM = 0.2;\nconst MIN_LINE_HEIGHT_EM = 1.8;\nconst MIN_PADDING_V_EM = 0.2;\n\n// --- CSS custom property names ---\n\nconst CSS_TIME = '--tegaki-time';\nconst CSS_PROGRESS = '--tegaki-progress';\nconst CSS_DURATION = '--tegaki-duration';\n\n// Register custom properties so they are animatable (typed as <number>).\n// Calling registerProperty twice with the same name throws, so guard with try/catch.\nif (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\nexport type TimeControlMode = {\n controlled: {\n mode: 'controlled';\n /** Current time in seconds. */\n value: number;\n };\n uncontrolled: {\n mode: 'uncontrolled';\n /** Initial time in seconds. Default: `0` */\n initialTime?: number;\n /** Playback speed multiplier. Default: `1` */\n speed?: number;\n /** Whether animation is playing. Default: `true` */\n playing?: boolean;\n /** Loop animation when it reaches the end. Default: `false` */\n loop?: boolean;\n /**\n * Catch-up strength. When positive, playback speeds up when there is a\n * large amount of remaining animation and decays back to normal gradually.\n * `0` disables catch-up (default). Higher values ramp up more aggressively.\n * Typical range: `0.2` – `2`.\n */\n catchUp?: number;\n /** Called on every frame with the current time. */\n onTimeChange?: (time: number) => void;\n };\n css: {\n mode: 'css';\n };\n};\n\n/**\n * A plain number is shorthand for `{ mode: 'controlled', value: number }`.\n * `'css'` is shorthand for `{ mode: 'css' }`.\n * Omit for uncontrolled mode with default settings.\n */\nexport type TimeControlProp = null | undefined | number | 'css' | TimeControlMode[keyof TimeControlMode];\n\n/** Imperative handle exposed via the `ref` prop. */\nexport interface TegakiRendererHandle {\n /** The root DOM element. */\n getElement(): HTMLDivElement | null;\n /** Current animation time in seconds. */\n getCurrentTime(): number;\n /** Total timeline duration in seconds. */\n getDuration(): number;\n /** Whether the animation is currently playing (uncontrolled mode only). */\n getIsPlaying(): boolean;\n /** Whether the animation has reached the end. */\n getIsComplete(): boolean;\n /** Resume playback (uncontrolled mode only). No-op in controlled/css mode. */\n play(): void;\n /** Pause playback (uncontrolled mode only). No-op in controlled/css mode. */\n pause(): void;\n /** Jump to a specific time in seconds (uncontrolled mode only). No-op in controlled/css mode. */\n seek(time: number): void;\n /** Seek to 0 and play (uncontrolled mode only). No-op in controlled/css mode. */\n restart(): void;\n}\n\nexport interface TegakiRendererProps<E extends TegakiEffects<E> = Record<string, never>>\n extends Omit<ComponentProps<'div'>, 'children' | 'ref'> {\n /** Imperative handle ref for playback controls and DOM access. */\n ref?: Ref<TegakiRendererHandle>;\n\n /** TegakiBundle with font data and animated glyph SVGs. */\n font?: TegakiBundle;\n\n /** Text to animate. Takes precedence over children. */\n text?: string;\n\n /** Children coerced to string. Strings and numbers are kept; everything else is ignored. */\n children?: Coercible;\n\n /**\n * Time control. Accepts a number (controlled shorthand), or an object\n * specifying the mode (`'controlled'`, `'uncontrolled'`, or `'css'`).\n * Omit for uncontrolled playback with default settings.\n */\n time?: TimeControlProp;\n\n /** Called once when the animation reaches the end of the timeline. */\n onComplete?: () => void;\n\n /** Visual effects applied during canvas rendering. */\n effects?: E;\n\n /** Maximum segment size in pixels for effect subdivision. Lower values produce\n * smoother effects but cost more to render. Default: `2` */\n segmentSize?: number;\n\n /** Timeline timing configuration (gap between glyphs, words, lines, etc.). */\n timing?: TimelineConfig;\n\n /** Show debug text overlay. */\n showOverlay?: boolean;\n}\n\n// --- Component ---\n\nexport function TegakiRenderer<const E extends TegakiEffects<E> = Record<string, never>>({\n ref,\n font,\n text,\n children,\n time: timeProp,\n onComplete,\n effects,\n segmentSize,\n timing,\n showOverlay,\n ...props\n}: TegakiRendererProps<E>) {\n const resolvedText = text ?? coerceToString(children);\n\n // --- Resolve effects ---\n const resolvedEffects = useMemo(() => resolveEffects(effects as Record<string, any>), [effects]);\n const [seed] = useState(() => Math.random() * 1000);\n\n // --- Resolve time control ---\n const timeControl: TimeControlMode[keyof TimeControlMode] =\n timeProp == null\n ? { mode: 'uncontrolled' }\n : typeof timeProp === 'number'\n ? { mode: 'controlled', value: timeProp }\n : timeProp === 'css'\n ? { mode: 'css' }\n : timeProp;\n\n const isCss = timeControl.mode === 'css';\n const isControlled = timeControl.mode === 'controlled' || isCss;\n const controlledTime = timeControl.mode === 'controlled' ? timeControl.value : undefined;\n const defaultTime = timeControl.mode === 'uncontrolled' ? (timeControl.initialTime ?? 0) : 0;\n const speed = timeControl.mode === 'uncontrolled' ? (timeControl.speed ?? 1) : 1;\n const propPlaying = timeControl.mode === 'uncontrolled' ? (timeControl.playing ?? true) : false;\n const loop = timeControl.mode === 'uncontrolled' ? (timeControl.loop ?? false) : false;\n const catchUp = timeControl.mode === 'uncontrolled' ? (timeControl.catchUp ?? 0) : 0;\n const onTimeChange = timeControl.mode === 'uncontrolled' ? timeControl.onTimeChange : undefined;\n\n // Imperative playing override (undefined = follow prop)\n const [playingOverride, setPlayingOverride] = useState<boolean | undefined>(undefined);\n const playing = playingOverride ?? propPlaying;\n\n // Reset override when the prop changes so the prop regains control\n const prevPropPlaying = useRef(propPlaying);\n if (prevPropPlaying.current !== propPlaying) {\n prevPropPlaying.current = propPlaying;\n setPlayingOverride(undefined);\n }\n\n // --- Internal time (uncontrolled mode) ---\n const [internalTime, setInternalTime] = useState(defaultTime);\n // --- CSS-driven time ---\n const [cssTime, setCssTime] = useState(0);\n const currentTime = isCss ? cssTime : isControlled ? controlledTime! : internalTime;\n\n // Stable refs so the imperative handle and rAF loop always see latest values\n const currentTimeRef = useRef(currentTime);\n currentTimeRef.current = currentTime;\n const playingRef = useRef(playing);\n playingRef.current = playing;\n const isControlledRef = useRef(isControlled);\n isControlledRef.current = isControlled;\n const onTimeChangeRef = useRef(onTimeChange);\n onTimeChangeRef.current = onTimeChange;\n const onCompleteRef = useRef(onComplete);\n onCompleteRef.current = onComplete;\n\n // --- Font loading ---\n const [fontReady, setFontReady] = useState(() => !!font && document.fonts.check(`16px \"${font?.family}\"`));\n useEffect(() => {\n if (!font) {\n setFontReady(false);\n return;\n }\n // Check if the font is already loaded\n if (document.fonts.check(`16px \"${font.family}\"`)) {\n setFontReady(true);\n return;\n }\n // New font — mark not ready and start loading\n setFontReady(false);\n let cancelled = false;\n font.registerFontFace().then(() => {\n if (!cancelled) setFontReady(true);\n });\n return () => {\n cancelled = true;\n };\n }, [font]);\n\n // --- Font-derived constants ---\n const fontFamily = font?.family;\n const emHeight = font ? (font.ascender - font.descender) / font.unitsPerEm : 0;\n\n // --- Container measurement ---\n const rootRef = useRef<HTMLDivElement>(null);\n const [containerWidth, setContainerWidth] = useState(0);\n const [fontSize, setFontSize] = useState(0);\n const [lineHeight, setLineHeight] = useState(0);\n const [currentColor, setCurrentColor] = useState('');\n\n // --- Timeline ---\n const timeline = useMemo(\n () => (font && resolvedText ? computeTimeline(resolvedText, font, timing) : { entries: [] as TimelineEntry[], totalDuration: 0 }),\n [resolvedText, font, timing],\n );\n\n // Duration ref so the rAF loop always sees the latest value without restarting\n const totalDurationRef = useRef(timeline.totalDuration);\n totalDurationRef.current = timeline.totalDuration;\n\n // Smoothed catch-up boost (raw bonus on top of base speed; attack/release smoothed)\n const smoothedBoostRef = useRef(0);\n\n // --- Completion tracking ---\n const prevCompletedRef = useRef(false);\n const isComplete = timeline.totalDuration > 0 && currentTime >= timeline.totalDuration;\n\n useEffect(() => {\n if (isComplete && !prevCompletedRef.current) {\n prevCompletedRef.current = true;\n onCompleteRef.current?.();\n } else if (!isComplete) {\n prevCompletedRef.current = false;\n }\n });\n\n // --- Imperative handle (stable — reads from refs) ---\n useImperativeHandle(\n ref,\n () => ({\n getElement: () => rootRef.current,\n getCurrentTime: () => currentTimeRef.current,\n getDuration: () => totalDurationRef.current,\n getIsPlaying: () => playingRef.current,\n getIsComplete: () => totalDurationRef.current > 0 && currentTimeRef.current >= totalDurationRef.current,\n play: () => {\n if (!isControlledRef.current) setPlayingOverride(true);\n },\n pause: () => {\n if (!isControlledRef.current) setPlayingOverride(false);\n },\n seek: (time: number) => {\n if (!isControlledRef.current) setInternalTime(Math.max(0, Math.min(time, totalDurationRef.current)));\n },\n restart: () => {\n if (!isControlledRef.current) {\n setInternalTime(0);\n setPlayingOverride(true);\n }\n },\n }),\n [],\n );\n\n // --- Uncontrolled: time change notification ---\n useEffect(() => {\n if (!isControlled) {\n onTimeChangeRef.current?.(internalTime);\n }\n }, [internalTime, isControlled]);\n\n // --- Uncontrolled: rAF playback loop ---\n useEffect(() => {\n if (isControlled || !playing || !font || !fontReady) return;\n\n // Reset smoothed boost when the loop restarts\n smoothedBoostRef.current = 0;\n\n let lastTs: number | null = null;\n let raf: number;\n\n // Catch-up smoothing rates (per-second exponential factors)\n const attackRate = 4; // fast ramp-up\n const releaseRate = loop ? 30 : 2; // slow decay back to base\n\n const tick = (ts: number) => {\n if (lastTs === null) lastTs = ts;\n const dtSec = (ts - lastTs) / 1000;\n lastTs = ts;\n\n setInternalTime((prev: number) => {\n const totalDur = totalDurationRef.current;\n if (totalDur === 0 || (!loop && prev >= totalDur)) return totalDur;\n\n // Compute effective speed with catch-up\n let effectiveSpeed = speed;\n if (catchUp > 0) {\n const remaining = Math.max(0, totalDur - prev);\n const excess = Math.max(0, remaining - 2);\n const targetBoost = catchUp * excess;\n const rate = targetBoost > smoothedBoostRef.current ? attackRate : releaseRate;\n smoothedBoostRef.current += (targetBoost - smoothedBoostRef.current) * (1 - Math.exp(-rate * dtSec));\n effectiveSpeed = speed + smoothedBoostRef.current;\n }\n\n let next = prev + dtSec * effectiveSpeed;\n if (next >= totalDur) {\n next = loop ? next % totalDur : totalDur;\n smoothedBoostRef.current = 0; // reset boost on loop\n }\n return next;\n });\n\n raf = requestAnimationFrame(tick);\n };\n\n raf = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(raf);\n }, [isControlled, playing, speed, loop, catchUp, font, fontReady]);\n\n // --- Container size observation ---\n useEffect(() => {\n const el = rootRef.current;\n if (!el) return;\n const ro = new ResizeObserver(([entry]) => {\n if (entry) {\n setContainerWidth(entry.contentRect.width);\n const styles = getComputedStyle(el);\n setFontSize(Number.parseFloat(styles.fontSize));\n setLineHeight(Number.parseFloat(styles.lineHeight));\n setCurrentColor(styles.color);\n }\n });\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n // Sentinel element ref — a hidden child with `font-size: inherit` and a near-zero\n // CSS transition. When any ancestor changes font-size, the transition fires an event\n // so we can read the new value without polling getComputedStyle every render.\n const sentinelRef = useRef<HTMLSpanElement>(null);\n useEffect(() => {\n const el = sentinelRef.current;\n if (!el) return;\n const onTransition = (e: TransitionEvent) => {\n const styles = getComputedStyle(el);\n if (e.propertyName === 'font-size' || e.propertyName === 'line-height') {\n setFontSize(Number.parseFloat(styles.fontSize));\n setLineHeight(Number.parseFloat(styles.lineHeight));\n }\n if (e.propertyName === 'color') {\n setCurrentColor(styles.color);\n }\n if (e.propertyName === CSS_PROGRESS) {\n const rawProgress = Number(styles.getPropertyValue(CSS_PROGRESS));\n setCssTime(rawProgress * totalDurationRef.current);\n }\n };\n el.addEventListener('transitionend', onTransition);\n return () => el.removeEventListener('transitionend', onTransition);\n }, []);\n\n // --- Text layout ---\n const layout = useMemo(() => {\n if (!fontReady || !fontFamily || !fontSize || !containerWidth || !resolvedText) return null;\n return computeTextLayout(resolvedText, fontFamily, fontSize, lineHeight, containerWidth);\n }, [fontReady, resolvedText, fontFamily, fontSize, lineHeight, containerWidth]);\n\n // --- Canvas padding ---\n const padH = PADDING_H_EM * fontSize;\n const padV = fontSize ? Math.max(MIN_PADDING_V_EM * fontSize, (MIN_LINE_HEIGHT_EM * fontSize - lineHeight) / 2) : 0;\n\n // --- Canvas rendering ---\n const canvasRef = useRef<HTMLCanvasElement>(null);\n\n useLayoutEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas || !font?.glyphData || !layout || !fontSize) return;\n\n const dpr = window.devicePixelRatio || 1;\n const el = rootRef.current;\n if (!el) return;\n const canvasRect = canvas.getBoundingClientRect();\n const w = canvasRect.width;\n const h = canvasRect.height;\n\n // Resize canvas backing store if needed\n const needsResize = canvas.width !== Math.round(w * dpr) || canvas.height !== Math.round(h * dpr);\n if (needsResize) {\n canvas.width = Math.round(w * dpr);\n canvas.height = Math.round(h * dpr);\n }\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.clearRect(0, 0, w, h);\n ctx.translate(padH, padV);\n\n const color = currentColor || getComputedStyle(el).color;\n\n const emHeightPx = emHeight * fontSize;\n const halfLeading = (lineHeight - emHeightPx) / 2;\n const characters = graphemes(resolvedText);\n\n let y = 0;\n for (const lineIndices of layout.lines) {\n let x = 0;\n for (const charIdx of lineIndices) {\n const char = characters[charIdx]!;\n if (char === '\\n') continue;\n const entry = timeline.entries[charIdx]!;\n const charWidth = layout.charWidths[charIdx] ?? 0;\n const kerning = layout.kernings[charIdx] ?? 0;\n const glyph = font.glyphData[char];\n\n if (glyph && entry.hasGlyph) {\n const localTime = Math.max(0, Math.min(currentTime - entry.offset, entry.duration));\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 resolvedEffects,\n seed + charIdx,\n segmentSize,\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, fontFamily!, color, resolvedEffects, seed + charIdx);\n }\n\n x += (charWidth + kerning) * fontSize;\n }\n y += lineHeight;\n }\n }, [\n currentTime,\n timeline,\n layout,\n font,\n fontFamily,\n fontSize,\n lineHeight,\n resolvedText,\n emHeight,\n padH,\n padV,\n currentColor,\n resolvedEffects,\n seed,\n segmentSize,\n ]);\n\n // --- Rendering ---\n\n if (!font || !resolvedText || !fontReady) {\n return <div ref={rootRef} {...props} />;\n }\n\n return (\n <div\n ref={rootRef}\n {...props}\n style={{\n ...props.style,\n position: 'relative',\n maxWidth: '100%',\n width: 'auto',\n height: 'auto',\n ...{\n [CSS_DURATION]: timeline.totalDuration,\n [CSS_TIME]: currentTime,\n [CSS_PROGRESS]: timeline.totalDuration > 0 ? currentTime / timeline.totalDuration : 0,\n },\n }}\n >\n <div style={{ position: 'relative' }}>\n {/* Sentinel: inherits font-size & line-height; its height changes when either changes */}\n <span\n ref={sentinelRef}\n aria-hidden\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 {'\\u00A0'}\n </span>\n <canvas\n ref={canvasRef}\n aria-hidden\n style={{\n position: 'absolute',\n inset: `${-padV}px ${-padH}px`,\n width: `calc(100% + ${padH * 2}px)`,\n height: `calc(100% + ${padV * 2}px)`,\n pointerEvents: 'none',\n }}\n />\n\n <div\n style={{\n userSelect: 'auto',\n whiteSpace: 'pre-wrap',\n overflowWrap: 'break-word',\n paddingRight: 1,\n WebkitTextFillColor: showOverlay ? undefined : 'transparent',\n fontFamily,\n color: showOverlay ? 'rgba(255, 0, 0, 0.4)' : undefined,\n }}\n >\n {resolvedText}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/lib/effects.ts","../src/lib/utils.ts","../src/lib/drawGlyph.ts","../src/lib/drawFallbackGlyph.ts","../src/lib/textLayout.ts","../src/lib/timeline.ts","../src/lib/TegakiRenderer.tsx"],"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\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 if (!effects) return [];\n\n const knownEffects: Set<string> = new Set(['glow', 'wobble', 'pressureWidth', 'taper', 'gradient']);\n const result: ResolvedEffect[] = [];\n\n for (const [key, value] of Object.entries(effects)) {\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","import type { CSSLength } from '../types.ts';\n\nconst segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });\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 return Array.from(segmenter.segment(text), (s) => s.segment);\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 { resolveCSSLength } from './utils.ts';\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/**\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 */\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 segmentSize?: number,\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\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\n // Helper: apply wobble offset to a point in font units\n const wobbleX = (x: number, y: number, idx: number) => {\n if (!wobbleEffect) return x;\n if (wobbleMode === 'noise') {\n return x + wobbleAmplitude * (noise1d(y * 0.1 + idx * 0.7, seed) * 2 - 1);\n }\n return x + wobbleAmplitude * Math.sin(wobbleFrequency * (y * 0.01 + idx * 0.7) + seed);\n };\n const wobbleY = (x: number, y: number, idx: number) => {\n if (!wobbleEffect) return y;\n if (wobbleMode === 'noise') {\n return y + wobbleAmplitude * (noise1d(x * 0.1 + idx * 0.5, seed * 1.3 + 1000) * 2 - 1);\n }\n return y + 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 const hasGradient = !!gradientEffect;\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 progress = Math.min(elapsed / stroke.a, 1);\n\n const pts = stroke.p;\n if (pts.length === 0) continue;\n\n const avgWidth = pts.reduce((s, p) => s + p[2], 0) / pts.length;\n const baseLineWidth = Math.max(avgWidth, 0.5) * scale;\n\n // --- Single-point dot ---\n if (pts.length === 1) {\n if (progress <= 0) continue;\n const p = pts[0]!;\n const dotX = px(wobbleX(p[0], p[1], 0));\n const dotY = py(wobbleY(p[0], p[1], 0));\n const perPointDot = Math.max(p[2], 0.5) * scale;\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 // --- Compute total path length ---\n let totalLen = 0;\n for (let j = 1; j < pts.length; j++) {\n const dx = pts[j]![0] - pts[j - 1]![0];\n const dy = pts[j]![1] - pts[j - 1]![1];\n totalLen += Math.sqrt(dx * dx + dy * dy);\n }\n\n const drawLen = totalLen * progress;\n if (drawLen <= 0) continue;\n\n // --- Collect drawable segments ---\n const segments: {\n x0: number;\n y0: number;\n x1: number;\n y1: number;\n width0: number;\n width1: number;\n segProgress: number;\n }[] = [];\n\n let accumulated = 0;\n for (let j = 1; j < pts.length; 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 segLen = Math.sqrt(dx * dx + dy * dy);\n\n if (accumulated + segLen <= drawLen) {\n segments.push({\n x0: px(wobbleX(prev[0], prev[1], j - 1)),\n y0: py(wobbleY(prev[0], prev[1], j - 1)),\n x1: px(wobbleX(cur[0], cur[1], j)),\n y1: py(wobbleY(cur[0], cur[1], j)),\n width0: prev[2],\n width1: cur[2],\n segProgress: (accumulated + segLen / 2) / totalLen,\n });\n accumulated += segLen;\n } else {\n const remaining = drawLen - accumulated;\n const frac = segLen > 0 ? remaining / segLen : 0;\n const ix = prev[0] + dx * frac;\n const iy = prev[1] + dy * frac;\n const iw = prev[2] + (cur[2] - prev[2]) * frac;\n segments.push({\n x0: px(wobbleX(prev[0], prev[1], j - 1)),\n y0: py(wobbleY(prev[0], prev[1], j - 1)),\n x1: px(wobbleX(ix, iy, j)),\n y1: py(wobbleY(ix, iy, j)),\n width0: prev[2],\n width1: iw,\n segProgress: (accumulated + remaining / 2) / totalLen,\n });\n break;\n }\n }\n\n if (segments.length === 0) continue;\n\n // Keep coarse segments for glow (shadowBlur is expensive per draw call)\n const coarseSegments = segments.slice();\n\n // --- Subdivide long segments for smooth effect transitions ---\n const effectsNeedSubdivision = pressureAmount > 0 || hasGradient || !!wobbleEffect || !!taperEffect;\n const resolvedSegmentSize = segmentSize ?? (effectsNeedSubdivision ? 2 : undefined);\n if (resolvedSegmentSize != null) {\n const maxSegLen = resolvedSegmentSize * scale;\n const subdivided: typeof segments = [];\n for (const seg of segments) {\n const dx = seg.x1 - seg.x0;\n const dy = seg.y1 - seg.y0;\n const len = Math.sqrt(dx * dx + dy * dy);\n const count = Math.max(1, Math.ceil(len / maxSegLen));\n for (let k = 0; k < count; k++) {\n const t0 = k / count;\n const t1 = (k + 1) / count;\n subdivided.push({\n x0: seg.x0 + dx * t0,\n y0: seg.y0 + dy * t0,\n x1: seg.x0 + dx * t1,\n y1: seg.y0 + dy * t1,\n width0: seg.width0 + (seg.width1 - seg.width0) * t0,\n width1: seg.width0 + (seg.width1 - seg.width0) * t1,\n segProgress: seg.segProgress,\n });\n }\n }\n for (let k = 0; k < subdivided.length; k++) {\n subdivided[k]!.segProgress = subdivided.length > 1 ? k / (subdivided.length - 1) : 0;\n }\n segments.length = 0;\n segments.push(...subdivided);\n }\n\n // Helper: compute segment line width with pressure and taper\n const segWidth = (seg: (typeof segments)[0]) => {\n const perPoint = ((seg.width0 + seg.width1) / 2) * scale;\n const w = Math.max(baseLineWidth + (perPoint - baseLineWidth) * pressureAmount, 0.5 * scale);\n return w * taperMultiplier(seg.segProgress);\n };\n\n const needsPerSegment = pressureAmount > 0 || taperEffect;\n\n const drawStrokePath = () => {\n if (needsPerSegment) {\n for (const seg of segments) {\n ctx.lineWidth = segWidth(seg);\n ctx.beginPath();\n ctx.moveTo(seg.x0, seg.y0);\n ctx.lineTo(seg.x1, seg.y1);\n ctx.stroke();\n }\n } else {\n ctx.lineWidth = baseLineWidth;\n ctx.beginPath();\n ctx.moveTo(segments[0]!.x0, segments[0]!.y0);\n for (const seg of segments) {\n ctx.lineTo(seg.x1, seg.y1);\n }\n ctx.stroke();\n }\n };\n\n const drawGradientPath = () => {\n for (const seg of segments) {\n ctx.strokeStyle = colorAt(seg.segProgress);\n if (needsPerSegment) ctx.lineWidth = segWidth(seg);\n ctx.beginPath();\n ctx.moveTo(seg.x0, seg.y0);\n ctx.lineTo(seg.x1, seg.y1);\n ctx.stroke();\n }\n };\n\n ctx.lineCap = lineCap;\n ctx.lineJoin = 'round';\n\n // --- Glow passes (use coarse segments to avoid expensive per-subsegment shadowBlur) ---\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 ctx.beginPath();\n ctx.moveTo(coarseSegments[0]!.x0, coarseSegments[0]!.y0);\n for (const seg of coarseSegments) {\n ctx.lineTo(seg.x1, seg.y1);\n }\n ctx.stroke();\n ctx.restore();\n }\n\n // --- Main stroke ---\n if (hasGradient) {\n drawGradientPath();\n } else {\n ctx.strokeStyle = color;\n drawStrokePath();\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 { layoutWithLines, prepareWithSegments } from '@chenglou/pretext';\nimport { graphemes } from './utils.ts';\n\nexport interface TextLayout {\n /** Character indices per line */\n lines: number[][];\n /** Width in em per character index */\n charWidths: number[];\n /** Kerning adjustment in em between character at index i and i+1 */\n kernings: number[];\n /** Intrinsic (single-line) width in em */\n intrinsicWidth: number;\n}\n\nexport function computeTextLayout(text: string, fontFamily: string, fontSize: number, lineHeight: number, maxWidth: number): TextLayout {\n const fontStr = `${fontSize}px ${fontFamily}`;\n const chars = graphemes(text);\n\n // Measure unique character widths\n const widthCache = new Map<string, number>();\n const charWidths: number[] = [];\n for (const char of chars) {\n let w = widthCache.get(char);\n if (w === undefined) {\n if (char === '\\n') {\n w = 0;\n } else {\n const p = prepareWithSegments(char, fontStr, { whiteSpace: 'pre-wrap' });\n const r = layoutWithLines(p, Infinity, lineHeight);\n w = r.lines.length > 0 ? r.lines[0]!.width / fontSize : 0;\n }\n widthCache.set(char, w);\n }\n charWidths.push(w);\n }\n\n // Compute intrinsic width (single-line, no wrapping)\n const prepared = prepareWithSegments(text, fontStr, { whiteSpace: 'pre-wrap' });\n const singleLineResult = layoutWithLines(prepared, Infinity, lineHeight);\n const intrinsicWidth = Math.max(0, ...singleLineResult.lines.map((l) => l.width)) / fontSize;\n\n // Line breaking at actual available width\n const result = layoutWithLines(prepared, maxWidth, lineHeight);\n\n // Map line texts back to character indices (grapheme-based)\n // Build a mapping from UTF-16 offset to grapheme index\n const utf16ToCodePoint: number[] = [];\n for (let ci = 0; ci < chars.length; ci++) {\n for (let j = 0; j < chars[ci]!.length; j++) {\n utf16ToCodePoint.push(ci);\n }\n }\n\n const lines: number[][] = [];\n let utf16Offset = 0;\n for (const line of result.lines) {\n const indices: number[] = [];\n const seen = new Set<number>();\n for (let i = 0; i < line.text.length; i++) {\n const cpIdx = utf16ToCodePoint[utf16Offset + i]!;\n if (!seen.has(cpIdx)) {\n seen.add(cpIdx);\n indices.push(cpIdx);\n }\n }\n utf16Offset += line.text.length;\n // Consume the newline that caused this line break\n if (utf16Offset < text.length && text[utf16Offset] === '\\n') {\n const cpIdx = utf16ToCodePoint[utf16Offset]!;\n indices.push(cpIdx);\n utf16Offset++;\n }\n lines.push(indices);\n }\n\n // Any remaining characters (shouldn't happen, but safety)\n if (utf16Offset < text.length) {\n const indices: number[] = [];\n const seen = new Set<number>();\n for (let i = utf16Offset; i < text.length; i++) {\n const cpIdx = utf16ToCodePoint[i]!;\n if (!seen.has(cpIdx)) {\n seen.add(cpIdx);\n indices.push(cpIdx);\n }\n }\n lines.push(indices);\n }\n\n // Measure kerning between adjacent character pairs\n const kernings: number[] = [];\n const pairCache = new Map<string, number>();\n for (let i = 0; i < chars.length - 1; i++) {\n const a = chars[i]!;\n const b = chars[i + 1]!;\n if (a === '\\n' || b === '\\n') {\n kernings.push(0);\n continue;\n }\n const pair = `${a}${b}`;\n let k = pairCache.get(pair);\n if (k === undefined) {\n const p = prepareWithSegments(pair, fontStr, { whiteSpace: 'pre-wrap' });\n const r = layoutWithLines(p, Infinity, lineHeight);\n const pairWidth = r.lines.length > 0 ? r.lines[0]!.width / fontSize : 0;\n k = pairWidth - (widthCache.get(a) ?? 0) - (widthCache.get(b) ?? 0);\n if (Math.abs(k) < 0.001) k = 0;\n pairCache.set(pair, k);\n }\n kernings.push(k);\n }\n\n return { lines, charWidths, kernings, intrinsicWidth };\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\nconst DEFAULTS: Required<TimelineConfig> = {\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 duration = hasGlyph ? (glyph.t ?? 1) : unknownDuration;\n entries.push({ char, offset, duration, hasGlyph });\n offset += duration;\n\n // Gap after this character\n if (char === '\\n') {\n offset += lineGap;\n } else if (char === ' ') {\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 : lastChar === ' ' ? wordGap : glyphGap;\n offset -= trailingGap;\n }\n return { entries, totalDuration: Math.max(0, offset) };\n}\n","'use client';\n\nimport { type ComponentProps, type Ref, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport type { TegakiBundle, TegakiEffects } from '../types.ts';\nimport { drawFallbackGlyph } from './drawFallbackGlyph.ts';\nimport { drawGlyph } from './drawGlyph.ts';\nimport { resolveEffects } from './effects.ts';\nimport { computeTextLayout } from './textLayout.ts';\nimport type { TimelineConfig, TimelineEntry } from './timeline.ts';\nimport { computeTimeline } from './timeline.ts';\nimport type { Coercible } from './utils.ts';\nimport { coerceToString, graphemes } from './utils.ts';\n\nconst PADDING_H_EM = 0.2;\nconst MIN_LINE_HEIGHT_EM = 1.8;\nconst MIN_PADDING_V_EM = 0.2;\n\n// --- CSS custom property names ---\n\nconst CSS_TIME = '--tegaki-time';\nconst CSS_PROGRESS = '--tegaki-progress';\nconst CSS_DURATION = '--tegaki-duration';\n\n// Register custom properties so they are animatable (typed as <number>).\n// Deferred to first mount to avoid running at import time during SSR.\nlet cssPropertiesRegistered = false;\nfunction 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\nexport type TimeControlMode = {\n controlled: {\n mode: 'controlled';\n /** Current time in seconds. */\n value: number;\n };\n uncontrolled: {\n mode: 'uncontrolled';\n /** Initial time in seconds. Default: `0` */\n initialTime?: number;\n /** Playback speed multiplier. Default: `1` */\n speed?: number;\n /** Whether animation is playing. Default: `true` */\n playing?: boolean;\n /** Loop animation when it reaches the end. Default: `false` */\n loop?: boolean;\n /**\n * Catch-up strength. When positive, playback speeds up when there is a\n * large amount of remaining animation and decays back to normal gradually.\n * `0` disables catch-up (default). Higher values ramp up more aggressively.\n * Typical range: `0.2` – `2`.\n */\n catchUp?: number;\n /** Called on every frame with the current time. */\n onTimeChange?: (time: number) => void;\n };\n css: {\n mode: 'css';\n };\n};\n\n/**\n * A plain number is shorthand for `{ mode: 'controlled', value: number }`.\n * `'css'` is shorthand for `{ mode: 'css' }`.\n * Omit for uncontrolled mode with default settings.\n */\nexport type TimeControlProp = null | undefined | number | 'css' | TimeControlMode[keyof TimeControlMode];\n\n/** Imperative handle exposed via the `ref` prop. */\nexport interface TegakiRendererHandle {\n /** The root DOM element. */\n getElement(): HTMLDivElement | null;\n /** Current animation time in seconds. */\n getCurrentTime(): number;\n /** Total timeline duration in seconds. */\n getDuration(): number;\n /** Whether the animation is currently playing (uncontrolled mode only). */\n getIsPlaying(): boolean;\n /** Whether the animation has reached the end. */\n getIsComplete(): boolean;\n /** Resume playback (uncontrolled mode only). No-op in controlled/css mode. */\n play(): void;\n /** Pause playback (uncontrolled mode only). No-op in controlled/css mode. */\n pause(): void;\n /** Jump to a specific time in seconds (uncontrolled mode only). No-op in controlled/css mode. */\n seek(time: number): void;\n /** Seek to 0 and play (uncontrolled mode only). No-op in controlled/css mode. */\n restart(): void;\n}\n\nexport interface TegakiRendererProps<E extends TegakiEffects<E> = Record<string, never>>\n extends Omit<ComponentProps<'div'>, 'children' | 'ref'> {\n /** Imperative handle ref for playback controls and DOM access. */\n ref?: Ref<TegakiRendererHandle>;\n\n /** TegakiBundle with font data and animated glyph SVGs. */\n font?: TegakiBundle;\n\n /** Text to animate. Takes precedence over children. */\n text?: string;\n\n /** Children coerced to string. Strings and numbers are kept; everything else is ignored. */\n children?: Coercible;\n\n /**\n * Time control. Accepts a number (controlled shorthand), or an object\n * specifying the mode (`'controlled'`, `'uncontrolled'`, or `'css'`).\n * Omit for uncontrolled playback with default settings.\n */\n time?: TimeControlProp;\n\n /** Called once when the animation reaches the end of the timeline. */\n onComplete?: () => void;\n\n /** Visual effects applied during canvas rendering. */\n effects?: E;\n\n /** Maximum segment size in pixels for effect subdivision. Lower values produce\n * smoother effects but cost more to render. Default: `2` */\n segmentSize?: number;\n\n /** Timeline timing configuration (gap between glyphs, words, lines, etc.). */\n timing?: TimelineConfig;\n\n /** Show debug text overlay. */\n showOverlay?: boolean;\n}\n\n// --- Component ---\n\nexport function TegakiRenderer<const E extends TegakiEffects<E> = Record<string, never>>({\n ref,\n font,\n text,\n children,\n time: timeProp,\n onComplete,\n effects,\n segmentSize,\n timing,\n showOverlay,\n ...props\n}: TegakiRendererProps<E>) {\n registerCssProperties();\n\n const resolvedText = text ?? coerceToString(children);\n\n // --- Resolve effects ---\n const resolvedEffects = useMemo(() => resolveEffects(effects as Record<string, any>), [effects]);\n const [seed] = useState(() => Math.random() * 1000);\n\n // --- Resolve time control ---\n const timeControl: TimeControlMode[keyof TimeControlMode] =\n timeProp == null\n ? { mode: 'uncontrolled' }\n : typeof timeProp === 'number'\n ? { mode: 'controlled', value: timeProp }\n : timeProp === 'css'\n ? { mode: 'css' }\n : timeProp;\n\n const isCss = timeControl.mode === 'css';\n const isControlled = timeControl.mode === 'controlled' || isCss;\n const controlledTime = timeControl.mode === 'controlled' ? timeControl.value : undefined;\n const defaultTime = timeControl.mode === 'uncontrolled' ? (timeControl.initialTime ?? 0) : 0;\n const speed = timeControl.mode === 'uncontrolled' ? (timeControl.speed ?? 1) : 1;\n const propPlaying = timeControl.mode === 'uncontrolled' ? (timeControl.playing ?? true) : false;\n const loop = timeControl.mode === 'uncontrolled' ? (timeControl.loop ?? false) : false;\n const catchUp = timeControl.mode === 'uncontrolled' ? (timeControl.catchUp ?? 0) : 0;\n const onTimeChange = timeControl.mode === 'uncontrolled' ? timeControl.onTimeChange : undefined;\n\n // Imperative playing override (undefined = follow prop)\n const [playingOverride, setPlayingOverride] = useState<boolean | undefined>(undefined);\n const playing = playingOverride ?? propPlaying;\n\n // Reset override when the prop changes so the prop regains control\n const prevPropPlaying = useRef(propPlaying);\n if (prevPropPlaying.current !== propPlaying) {\n prevPropPlaying.current = propPlaying;\n setPlayingOverride(undefined);\n }\n\n // --- Internal time (uncontrolled mode) ---\n const [internalTime, setInternalTime] = useState(defaultTime);\n // --- CSS-driven time ---\n const [cssTime, setCssTime] = useState(0);\n const currentTime = isCss ? cssTime : isControlled ? controlledTime! : internalTime;\n\n // Stable refs so the imperative handle and rAF loop always see latest values\n const currentTimeRef = useRef(currentTime);\n currentTimeRef.current = currentTime;\n const playingRef = useRef(playing);\n playingRef.current = playing;\n const isControlledRef = useRef(isControlled);\n isControlledRef.current = isControlled;\n const onTimeChangeRef = useRef(onTimeChange);\n onTimeChangeRef.current = onTimeChange;\n const onCompleteRef = useRef(onComplete);\n onCompleteRef.current = onComplete;\n\n // --- Font loading ---\n const [fontReady, setFontReady] = useState(\n () => typeof document !== 'undefined' && !!font && document.fonts.check(`16px \"${font.family}\"`),\n );\n useEffect(() => {\n if (!font) {\n setFontReady(false);\n return;\n }\n // Check if the font is already loaded\n if (document.fonts.check(`16px \"${font.family}\"`)) {\n setFontReady(true);\n return;\n }\n // New font — mark not ready and start loading\n setFontReady(false);\n let cancelled = false;\n font.registerFontFace().then(() => {\n if (!cancelled) setFontReady(true);\n });\n return () => {\n cancelled = true;\n };\n }, [font]);\n\n // --- Font-derived constants ---\n const fontFamily = font?.family;\n const emHeight = font ? (font.ascender - font.descender) / font.unitsPerEm : 0;\n\n // --- Container measurement ---\n const rootRef = useRef<HTMLDivElement>(null);\n const [containerWidth, setContainerWidth] = useState(0);\n const [fontSize, setFontSize] = useState(0);\n const [lineHeight, setLineHeight] = useState(0);\n const [currentColor, setCurrentColor] = useState('');\n\n // --- Timeline ---\n const timeline = useMemo(\n () => (font && resolvedText ? computeTimeline(resolvedText, font, timing) : { entries: [] as TimelineEntry[], totalDuration: 0 }),\n [resolvedText, font, timing],\n );\n\n // Duration ref so the rAF loop always sees the latest value without restarting\n const totalDurationRef = useRef(timeline.totalDuration);\n totalDurationRef.current = timeline.totalDuration;\n\n // Smoothed catch-up boost (raw bonus on top of base speed; attack/release smoothed)\n const smoothedBoostRef = useRef(0);\n\n // --- Completion tracking ---\n const prevCompletedRef = useRef(false);\n const isComplete = timeline.totalDuration > 0 && currentTime >= timeline.totalDuration;\n\n useEffect(() => {\n if (isComplete && !prevCompletedRef.current) {\n prevCompletedRef.current = true;\n onCompleteRef.current?.();\n } else if (!isComplete) {\n prevCompletedRef.current = false;\n }\n });\n\n // --- Imperative handle (stable — reads from refs) ---\n useImperativeHandle(\n ref,\n () => ({\n getElement: () => rootRef.current,\n getCurrentTime: () => currentTimeRef.current,\n getDuration: () => totalDurationRef.current,\n getIsPlaying: () => playingRef.current,\n getIsComplete: () => totalDurationRef.current > 0 && currentTimeRef.current >= totalDurationRef.current,\n play: () => {\n if (!isControlledRef.current) setPlayingOverride(true);\n },\n pause: () => {\n if (!isControlledRef.current) setPlayingOverride(false);\n },\n seek: (time: number) => {\n if (!isControlledRef.current) setInternalTime(Math.max(0, Math.min(time, totalDurationRef.current)));\n },\n restart: () => {\n if (!isControlledRef.current) {\n setInternalTime(0);\n setPlayingOverride(true);\n }\n },\n }),\n [],\n );\n\n // --- Uncontrolled: time change notification ---\n useEffect(() => {\n if (!isControlled) {\n onTimeChangeRef.current?.(internalTime);\n }\n }, [internalTime, isControlled]);\n\n // --- Reduced motion preference ---\n const [prefersReducedMotion, setPrefersReducedMotion] = useState(\n () => typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches,\n );\n useEffect(() => {\n const mql = window.matchMedia('(prefers-reduced-motion: reduce)');\n setPrefersReducedMotion(mql.matches);\n const onChange = (e: MediaQueryListEvent) => setPrefersReducedMotion(e.matches);\n mql.addEventListener('change', onChange);\n return () => mql.removeEventListener('change', onChange);\n }, []);\n\n // When reduced motion is preferred, skip to end of timeline\n useEffect(() => {\n if (prefersReducedMotion && !isControlled && timeline.totalDuration > 0) {\n setInternalTime(timeline.totalDuration);\n }\n }, [prefersReducedMotion, isControlled, timeline.totalDuration]);\n\n // --- Uncontrolled: rAF playback loop ---\n useEffect(() => {\n if (isControlled || !playing || !font || !fontReady || prefersReducedMotion) return;\n\n // Reset smoothed boost when the loop restarts\n smoothedBoostRef.current = 0;\n\n let lastTs: number | null = null;\n let raf: number;\n\n // Catch-up smoothing rates (per-second exponential factors)\n const attackRate = 4; // fast ramp-up\n const releaseRate = loop ? 30 : 2; // slow decay back to base\n\n const tick = (ts: number) => {\n if (lastTs === null) lastTs = ts;\n const dtSec = (ts - lastTs) / 1000;\n lastTs = ts;\n\n setInternalTime((prev: number) => {\n const totalDur = totalDurationRef.current;\n if (totalDur === 0 || (!loop && prev >= totalDur)) return totalDur;\n\n // Compute effective speed with catch-up\n let effectiveSpeed = speed;\n if (catchUp > 0) {\n const remaining = Math.max(0, totalDur - prev);\n const excess = Math.max(0, remaining - 2);\n const targetBoost = catchUp * excess;\n const rate = targetBoost > smoothedBoostRef.current ? attackRate : releaseRate;\n smoothedBoostRef.current += (targetBoost - smoothedBoostRef.current) * (1 - Math.exp(-rate * dtSec));\n effectiveSpeed = speed + smoothedBoostRef.current;\n }\n\n let next = prev + dtSec * effectiveSpeed;\n if (next >= totalDur) {\n next = loop ? next % totalDur : totalDur;\n smoothedBoostRef.current = 0; // reset boost on loop\n }\n return next;\n });\n\n raf = requestAnimationFrame(tick);\n };\n\n raf = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(raf);\n }, [isControlled, playing, speed, loop, catchUp, font, fontReady, prefersReducedMotion]);\n\n // --- Container size observation ---\n useEffect(() => {\n const el = rootRef.current;\n if (!el) return;\n const ro = new ResizeObserver(([entry]) => {\n if (entry) {\n setContainerWidth(entry.contentRect.width);\n const styles = getComputedStyle(el);\n setFontSize(Number.parseFloat(styles.fontSize));\n setLineHeight(Number.parseFloat(styles.lineHeight));\n setCurrentColor(styles.color);\n }\n });\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n // Sentinel element ref — a hidden child with `font-size: inherit` and a near-zero\n // CSS transition. When any ancestor changes font-size, the transition fires an event\n // so we can read the new value without polling getComputedStyle every render.\n const sentinelRef = useRef<HTMLSpanElement>(null);\n useEffect(() => {\n const el = sentinelRef.current;\n if (!el) return;\n const onTransition = (e: TransitionEvent) => {\n const styles = getComputedStyle(el);\n if (e.propertyName === 'font-size' || e.propertyName === 'line-height') {\n setFontSize(Number.parseFloat(styles.fontSize));\n setLineHeight(Number.parseFloat(styles.lineHeight));\n }\n if (e.propertyName === 'color') {\n setCurrentColor(styles.color);\n }\n if (e.propertyName === CSS_PROGRESS) {\n const rawProgress = Number(styles.getPropertyValue(CSS_PROGRESS));\n setCssTime(rawProgress * totalDurationRef.current);\n }\n };\n el.addEventListener('transitionend', onTransition);\n return () => el.removeEventListener('transitionend', onTransition);\n }, []);\n\n // --- Text layout ---\n const layout = useMemo(() => {\n if (!fontReady || !fontFamily || !fontSize || !containerWidth || !resolvedText) return null;\n return computeTextLayout(resolvedText, fontFamily, fontSize, lineHeight, containerWidth);\n }, [fontReady, resolvedText, fontFamily, fontSize, lineHeight, containerWidth]);\n\n // --- Canvas padding ---\n const padH = PADDING_H_EM * fontSize;\n const padV = fontSize ? Math.max(MIN_PADDING_V_EM * fontSize, (MIN_LINE_HEIGHT_EM * fontSize - lineHeight) / 2) : 0;\n\n // --- Canvas rendering ---\n const canvasRef = useRef<HTMLCanvasElement>(null);\n\n useLayoutEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas || !font?.glyphData || !layout || !fontSize) return;\n\n const dpr = window.devicePixelRatio || 1;\n const el = rootRef.current;\n if (!el) return;\n const canvasRect = canvas.getBoundingClientRect();\n const w = canvasRect.width;\n const h = canvasRect.height;\n\n // Resize canvas backing store if needed\n const needsResize = canvas.width !== Math.round(w * dpr) || canvas.height !== Math.round(h * dpr);\n if (needsResize) {\n canvas.width = Math.round(w * dpr);\n canvas.height = Math.round(h * dpr);\n }\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.clearRect(0, 0, w, h);\n ctx.translate(padH, padV);\n\n const color = currentColor || getComputedStyle(el).color;\n\n const emHeightPx = emHeight * fontSize;\n const halfLeading = (lineHeight - emHeightPx) / 2;\n const characters = graphemes(resolvedText);\n\n let y = 0;\n for (const lineIndices of layout.lines) {\n let x = 0;\n for (const charIdx of lineIndices) {\n const char = characters[charIdx]!;\n if (char === '\\n') continue;\n const entry = timeline.entries[charIdx]!;\n const charWidth = layout.charWidths[charIdx] ?? 0;\n const kerning = layout.kernings[charIdx] ?? 0;\n const glyph = font.glyphData[char];\n\n if (glyph && entry.hasGlyph) {\n const localTime = Math.max(0, Math.min(currentTime - entry.offset, entry.duration));\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 resolvedEffects,\n seed + charIdx,\n segmentSize,\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, fontFamily!, color, resolvedEffects, seed + charIdx);\n }\n\n x += (charWidth + kerning) * fontSize;\n }\n y += lineHeight;\n }\n }, [\n currentTime,\n timeline,\n layout,\n font,\n fontFamily,\n fontSize,\n lineHeight,\n resolvedText,\n emHeight,\n padH,\n padV,\n currentColor,\n resolvedEffects,\n seed,\n segmentSize,\n ]);\n\n // --- Rendering ---\n\n return (\n <div\n ref={rootRef}\n {...props}\n style={{\n ...props.style,\n position: 'relative',\n maxWidth: '100%',\n width: 'auto',\n height: 'auto',\n ...{\n [CSS_DURATION]: timeline.totalDuration,\n [CSS_TIME]: currentTime,\n [CSS_PROGRESS]: timeline.totalDuration > 0 ? currentTime / timeline.totalDuration : 0,\n },\n }}\n >\n <div style={{ position: 'relative' }}>\n {/* Sentinel: inherits font-size & line-height; its height changes when either changes */}\n <span\n ref={sentinelRef}\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 {'\\u00A0'}\n </span>\n <canvas\n ref={canvasRef}\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n inset: `${-padV}px ${-padH}px`,\n width: `calc(100% + ${padH * 2}px)`,\n height: `calc(100% + ${padV * 2}px)`,\n pointerEvents: 'none',\n }}\n />\n\n <div\n style={{\n userSelect: 'auto',\n whiteSpace: 'pre-wrap',\n overflowWrap: 'break-word',\n paddingRight: 1,\n WebkitTextFillColor: showOverlay ? undefined : 'transparent',\n fontFamily,\n color: showOverlay ? 'rgba(255, 0, 0, 0.4)' : undefined,\n }}\n >\n {resolvedText}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tegaki",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "description": "React component for rendering animated handwriting from any font",
6
6
  "keywords": [
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { type ComponentProps, type Ref, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from 'react';
2
4
  import type { TegakiBundle, TegakiEffects } from '../types.ts';
3
5
  import { drawFallbackGlyph } from './drawFallbackGlyph.ts';
@@ -20,13 +22,18 @@ const CSS_PROGRESS = '--tegaki-progress';
20
22
  const CSS_DURATION = '--tegaki-duration';
21
23
 
22
24
  // Register custom properties so they are animatable (typed as <number>).
23
- // Calling registerProperty twice with the same name throws, so guard with try/catch.
24
- if (typeof CSS !== 'undefined' && 'registerProperty' in CSS) {
25
- for (const prop of [CSS_TIME, CSS_PROGRESS, CSS_DURATION]) {
26
- try {
27
- CSS.registerProperty({ name: prop, syntax: '<number>', inherits: true, initialValue: '0' });
28
- } catch {
29
- // Already registered ignore.
25
+ // Deferred to first mount to avoid running at import time during SSR.
26
+ let cssPropertiesRegistered = false;
27
+ function registerCssProperties() {
28
+ if (cssPropertiesRegistered) return;
29
+ cssPropertiesRegistered = true;
30
+ if (typeof CSS !== 'undefined' && 'registerProperty' in CSS) {
31
+ for (const prop of [CSS_TIME, CSS_PROGRESS, CSS_DURATION]) {
32
+ try {
33
+ CSS.registerProperty({ name: prop, syntax: '<number>', inherits: true, initialValue: '0' });
34
+ } catch {
35
+ // Already registered — ignore.
36
+ }
30
37
  }
31
38
  }
32
39
  }
@@ -144,6 +151,8 @@ export function TegakiRenderer<const E extends TegakiEffects<E> = Record<string,
144
151
  showOverlay,
145
152
  ...props
146
153
  }: TegakiRendererProps<E>) {
154
+ registerCssProperties();
155
+
147
156
  const resolvedText = text ?? coerceToString(children);
148
157
 
149
158
  // --- Resolve effects ---
@@ -200,7 +209,9 @@ export function TegakiRenderer<const E extends TegakiEffects<E> = Record<string,
200
209
  onCompleteRef.current = onComplete;
201
210
 
202
211
  // --- Font loading ---
203
- const [fontReady, setFontReady] = useState(() => !!font && document.fonts.check(`16px "${font?.family}"`));
212
+ const [fontReady, setFontReady] = useState(
213
+ () => typeof document !== 'undefined' && !!font && document.fonts.check(`16px "${font.family}"`),
214
+ );
204
215
  useEffect(() => {
205
216
  if (!font) {
206
217
  setFontReady(false);
@@ -294,9 +305,28 @@ export function TegakiRenderer<const E extends TegakiEffects<E> = Record<string,
294
305
  }
295
306
  }, [internalTime, isControlled]);
296
307
 
308
+ // --- Reduced motion preference ---
309
+ const [prefersReducedMotion, setPrefersReducedMotion] = useState(
310
+ () => typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches,
311
+ );
312
+ useEffect(() => {
313
+ const mql = window.matchMedia('(prefers-reduced-motion: reduce)');
314
+ setPrefersReducedMotion(mql.matches);
315
+ const onChange = (e: MediaQueryListEvent) => setPrefersReducedMotion(e.matches);
316
+ mql.addEventListener('change', onChange);
317
+ return () => mql.removeEventListener('change', onChange);
318
+ }, []);
319
+
320
+ // When reduced motion is preferred, skip to end of timeline
321
+ useEffect(() => {
322
+ if (prefersReducedMotion && !isControlled && timeline.totalDuration > 0) {
323
+ setInternalTime(timeline.totalDuration);
324
+ }
325
+ }, [prefersReducedMotion, isControlled, timeline.totalDuration]);
326
+
297
327
  // --- Uncontrolled: rAF playback loop ---
298
328
  useEffect(() => {
299
- if (isControlled || !playing || !font || !fontReady) return;
329
+ if (isControlled || !playing || !font || !fontReady || prefersReducedMotion) return;
300
330
 
301
331
  // Reset smoothed boost when the loop restarts
302
332
  smoothedBoostRef.current = 0;
@@ -341,7 +371,7 @@ export function TegakiRenderer<const E extends TegakiEffects<E> = Record<string,
341
371
 
342
372
  raf = requestAnimationFrame(tick);
343
373
  return () => cancelAnimationFrame(raf);
344
- }, [isControlled, playing, speed, loop, catchUp, font, fontReady]);
374
+ }, [isControlled, playing, speed, loop, catchUp, font, fontReady, prefersReducedMotion]);
345
375
 
346
376
  // --- Container size observation ---
347
377
  useEffect(() => {
@@ -490,10 +520,6 @@ export function TegakiRenderer<const E extends TegakiEffects<E> = Record<string,
490
520
 
491
521
  // --- Rendering ---
492
522
 
493
- if (!font || !resolvedText || !fontReady) {
494
- return <div ref={rootRef} {...props} />;
495
- }
496
-
497
523
  return (
498
524
  <div
499
525
  ref={rootRef}
@@ -515,7 +541,7 @@ export function TegakiRenderer<const E extends TegakiEffects<E> = Record<string,
515
541
  {/* Sentinel: inherits font-size & line-height; its height changes when either changes */}
516
542
  <span
517
543
  ref={sentinelRef}
518
- aria-hidden
544
+ aria-hidden="true"
519
545
  style={{
520
546
  position: 'absolute',
521
547
  width: 0,
@@ -533,7 +559,7 @@ export function TegakiRenderer<const E extends TegakiEffects<E> = Record<string,
533
559
  </span>
534
560
  <canvas
535
561
  ref={canvasRef}
536
- aria-hidden
562
+ aria-hidden="true"
537
563
  style={{
538
564
  position: 'absolute',
539
565
  inset: `${-padV}px ${-padH}px`,