Skip to content

Commit d9b7c85

Browse files
committed
feat: add stroke and glyph easing functions
1 parent 26d7b58 commit d9b7c85

4 files changed

Lines changed: 32 additions & 3 deletions

File tree

.changeset/hip-lemons-melt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tegaki": patch
3+
---
4+
5+
feat: add stroke and glyph easing functions

packages/renderer/src/core/engine.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,11 @@ export class TegakiEngine {
743743
const glyph = font.glyphData[char];
744744

745745
if (glyph && entry.hasGlyph) {
746-
const localTime = Math.max(0, Math.min(currentTime - entry.offset, entry.duration));
746+
let localTime = Math.max(0, Math.min(currentTime - entry.offset, entry.duration));
747+
const glyphEasing = this._timing?.glyphEasing;
748+
if (glyphEasing && entry.duration > 0) {
749+
localTime = glyphEasing(localTime / entry.duration) * entry.duration;
750+
}
747751
const glyphY = y + halfLeading;
748752
drawGlyph(
749753
ctx,
@@ -762,6 +766,7 @@ export class TegakiEngine {
762766
this._resolvedEffects,
763767
this._seed + charIdx,
764768
this._segmentSize,
769+
this._timing?.strokeEasing,
765770
);
766771
} else if (!entry.hasGlyph && currentTime >= entry.offset + entry.duration) {
767772
const baseline = y + halfLeading + (font.ascender / font.unitsPerEm) * fontSize;

packages/renderer/src/lib/drawGlyph.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ function noise1d(x: number, seed: number): number {
7474
return hash(i + seed * 7919) * (1 - t) + hash(i + 1 + seed * 7919) * t;
7575
}
7676

77+
/** Default stroke easing: ease-out exponential. */
78+
function defaultStrokeEasing(t: number): number {
79+
return 1 - (1 - t) * (1 - t); // Ease out quad
80+
}
81+
7782
/**
7883
* Draw a single glyph's strokes onto a canvas context, animated up to `localTime`.
7984
* `localTime` is seconds relative to this glyph's start (0 = glyph begins).
@@ -88,6 +93,7 @@ export function drawGlyph(
8893
effects: ResolvedEffect[] = [],
8994
seed = 0,
9095
segmentSize?: number,
96+
strokeEasing: ((t: number) => number) | undefined = defaultStrokeEasing,
9197
) {
9298
const scale = pos.fontSize / pos.unitsPerEm;
9399
const ox = pos.x;
@@ -157,7 +163,8 @@ export function drawGlyph(
157163
for (const stroke of glyph.s) {
158164
if (localTime < stroke.d) continue;
159165
const elapsed = localTime - stroke.d;
160-
const progress = Math.min(elapsed / stroke.a, 1);
166+
const linearProgress = Math.min(elapsed / stroke.a, 1);
167+
const progress = strokeEasing ? strokeEasing(linearProgress) : linearProgress;
161168

162169
const pts = stroke.p;
163170
if (pts.length === 0) continue;

packages/renderer/src/lib/timeline.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,21 @@ export interface TimelineConfig {
1010
lineGap?: number;
1111
/** Duration for characters without glyph data (seconds). Default: `0.2` */
1212
unknownDuration?: number;
13+
/**
14+
* Easing function for each stroke's animation progress `(0–1) → (0–1)`.
15+
* Applied per-stroke to map linear draw progress to eased progress.
16+
* Default: ease-out exponential (`1 - 2^(-10t)`).
17+
*/
18+
strokeEasing?: (t: number) => number;
19+
/**
20+
* Easing function for each glyph's local time progress `(0–1) → (0–1)`.
21+
* Applied per-glyph to map linear time within the glyph to eased time.
22+
* Default: linear (no easing).
23+
*/
24+
glyphEasing?: (t: number) => number;
1325
}
1426

15-
const DEFAULTS: Required<TimelineConfig> = {
27+
const DEFAULTS = {
1628
glyphGap: 0.1,
1729
wordGap: 0.15,
1830
lineGap: 0.3,

0 commit comments

Comments
 (0)