tegaki 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +2 -2
- package/dist/{core-Y5z-Gwut.mjs → core-I9K3LqxK.mjs} +198 -129
- package/dist/core-I9K3LqxK.mjs.map +1 -0
- package/dist/{index-CZUvsAZl.d.mts → index-CZFVynOK.d.mts} +78 -20
- package/dist/index-vDbG4xkP.d.mts +34 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +3 -3
- package/dist/react/index.d.mts +3 -3
- package/dist/react/index.mjs +3 -3
- package/dist/react-DGPG66n-.mjs +88 -0
- package/dist/react-DGPG66n-.mjs.map +1 -0
- package/dist/solid/index.d.mts +2 -2
- package/dist/solid/index.mjs +2 -2
- package/dist/solid/index.mjs.map +1 -1
- package/dist/wc/index.d.mts +3 -3
- package/dist/wc/index.mjs +32 -9
- package/dist/wc/index.mjs.map +1 -1
- package/package.json +3 -5
- package/src/core/bundle-registry.ts +22 -0
- package/src/core/engine.ts +123 -249
- package/src/core/index.ts +4 -7
- package/src/core/render-elements.ts +141 -0
- package/src/core/types.ts +101 -0
- package/src/lib/textLayout.ts +97 -89
- package/src/lib/utils.ts +1 -0
- package/src/react/TegakiRenderer.tsx +88 -27
- package/src/solid/TegakiRenderer.tsx +2 -1
- package/src/svelte/TegakiRenderer.svelte +2 -1
- package/src/vue/TegakiRenderer.vue +2 -2
- package/src/wc/TegakiElement.ts +35 -7
- package/dist/core-Y5z-Gwut.mjs.map +0 -1
- package/dist/index-itOttKxn.d.mts +0 -39
- package/dist/react-Cb1yd0jB.mjs +0 -60
- package/dist/react-Cb1yd0jB.mjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# tegaki
|
|
2
2
|
|
|
3
|
+
## 0.11.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`4b7db41`](https://github.com/KurtGokhan/tegaki/commit/4b7db41fb1c247ed766ff10284e9cdabd4ab0a25) Thanks [@KurtGokhan](https://github.com/KurtGokhan)! - Implement new text layout based on DOM and text ranges
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [`f3602b0`](https://github.com/KurtGokhan/tegaki/commit/f3602b04970c8cb88ea41e87e63ee4709b086d61) Thanks [@KurtGokhan](https://github.com/KurtGokhan)! - improve line cap detection for CJK fonts
|
|
12
|
+
|
|
13
|
+
- [`28f58c6`](https://github.com/KurtGokhan/tegaki/commit/28f58c67f9eae8e0123a915d0efea03eaccd5e27) Thanks [@KurtGokhan](https://github.com/KurtGokhan)! - fixed a bug with generator that caused it to not load all characters in a font, especially CJK
|
|
14
|
+
|
|
15
|
+
- [`047e5e3`](https://github.com/KurtGokhan/tegaki/commit/047e5e31d3ffabbecf25dd36b5f56d298731c630) Thanks [@KurtGokhan](https://github.com/KurtGokhan)! - Add `duration` and `easing` options to uncontrolled time mode.
|
|
16
|
+
|
|
17
|
+
- `duration` stretches or compresses one iteration to take exactly N seconds, derived from the natural timeline inside the engine. Mutually exclusive with `speed` / `catchUp` at the type level (discriminated union); when both are set at runtime, `duration` takes precedence.
|
|
18
|
+
- `easing: (t: number) => number` maps linear progress (0–1) to displayed progress (0–1). Applied at read-time, so `currentTime`, `onTimeChange`, and the `--tegaki-time` / `--tegaki-progress` CSS custom properties all reflect the eased value. Completion is evaluated against linear progress so overshoot/undershoot curves (e.g. `easeOutBack`) don't trip completion early or late.
|
|
19
|
+
- The web component adapter accepts a `duration` attribute; `easing` is available via the `time` JS property only (it's function-valued).
|
|
20
|
+
|
|
21
|
+
## 0.10.0
|
|
22
|
+
|
|
23
|
+
### Minor Changes
|
|
24
|
+
|
|
25
|
+
- [`7198553`](https://github.com/KurtGokhan/tegaki/commit/719855392734a8f1b6056db9f0718ac7a8213527) Thanks [@KurtGokhan](https://github.com/KurtGokhan)! - Add controlled progress mode to allow users to specify the exact progress of the animation that is a value between 0 and 1.
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- [`b326f00`](https://github.com/KurtGokhan/tegaki/commit/b326f00d52b97ef19e0214cb4595bd31cd501cf4) Thanks [@KurtGokhan](https://github.com/KurtGokhan)! - Add delay and loop gap for uncontrolled animations
|
|
30
|
+
|
|
31
|
+
- [`1449890`](https://github.com/KurtGokhan/tegaki/commit/144989014c0d9cdbf80fafbb77af646b96065832) Thanks [@KurtGokhan](https://github.com/KurtGokhan)! - Add docs and example for using Tegaki with Remotion. The example is a simple composition that renders a single text prop, but the same principles apply to more complex compositions and dynamic props.
|
|
32
|
+
|
|
33
|
+
- [`1449890`](https://github.com/KurtGokhan/tegaki/commit/144989014c0d9cdbf80fafbb77af646b96065832) Thanks [@KurtGokhan](https://github.com/KurtGokhan)! - Fix rendering when zoom level was not 100%.
|
|
34
|
+
|
|
3
35
|
## 0.9.0
|
|
4
36
|
|
|
5
37
|
### Minor Changes
|
package/dist/core/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
export { BBox, CSSLength, CreateElementFn, FontOutput, GlyphData, LineCap, PathCommand, Point, ResolvedEffect, Stroke, TegakiBundle, TegakiEffectConfigs, TegakiEffectName, TegakiEffects, TegakiEngine, TegakiEngineOptions, TegakiGlyphData, TegakiMultiEffectName, TegakiSingletonEffectName, TextLayout, TimeControlMode, TimeControlProp, TimedPoint, Timeline, TimelineConfig, TimelineEntry, computeTextLayout, computeTimeline, createBundle, drawGlyph, ensureFontFace, resolveEffects };
|
|
1
|
+
import { A as Stroke, C as BBox, D as LineCap, E as GlyphData, F as TegakiGlyphData, I as TegakiMultiEffectName, L as TegakiSingletonEffectName, M as TegakiEffectConfigs, N as TegakiEffectName, O as PathCommand, P as TegakiEffects, R as TimedPoint, S as resolveEffects, T as FontOutput, _ as TextLayout, a as CreateElementFn, b as drawGlyph, c as TimeControlProp, d as registerBundle, f as resolveBundle, g as computeTimeline, h as TimelineEntry, i as TegakiEngine, j as TegakiBundle, k as Point, l as createBundle, m as TimelineConfig, n as buildRootProps, o as TegakiEngineOptions, p as Timeline, r as domCreateElement, s as TimeControlMode, t as buildChildren, u as getBundle, v as computeTextLayout, w as CSSLength, x as ResolvedEffect, y as ensureFontFace } from "../index-CZFVynOK.mjs";
|
|
2
|
+
export { BBox, CSSLength, CreateElementFn, FontOutput, GlyphData, LineCap, PathCommand, Point, ResolvedEffect, Stroke, TegakiBundle, TegakiEffectConfigs, TegakiEffectName, TegakiEffects, TegakiEngine, TegakiEngineOptions, TegakiGlyphData, TegakiMultiEffectName, TegakiSingletonEffectName, TextLayout, TimeControlMode, TimeControlProp, TimedPoint, Timeline, TimelineConfig, TimelineEntry, buildChildren, buildRootProps, computeTextLayout, computeTimeline, createBundle, domCreateElement, drawGlyph, ensureFontFace, getBundle, registerBundle, resolveBundle, resolveEffects };
|
package/dist/core/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { TegakiEngine, computeTextLayout, computeTimeline, createBundle, drawGlyph, ensureFontFace, resolveEffects };
|
|
1
|
+
import { a as createBundle, c as resolveBundle, d as ensureFontFace, f as drawGlyph, i as domCreateElement, l as computeTimeline, m as resolveEffects, n as buildChildren, o as getBundle, r as buildRootProps, s as registerBundle, t as TegakiEngine, u as computeTextLayout } from "../core-I9K3LqxK.mjs";
|
|
2
|
+
export { TegakiEngine, buildChildren, buildRootProps, computeTextLayout, computeTimeline, createBundle, domCreateElement, drawGlyph, ensureFontFace, getBundle, registerBundle, resolveBundle, resolveEffects };
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { layoutWithLines, prepareWithSegments } from "@chenglou/pretext";
|
|
2
1
|
//#region src/lib/effects.ts
|
|
3
2
|
const defaultEffects = { pressureWidth: true };
|
|
4
3
|
const knownEffects = new Set([
|
|
@@ -372,87 +371,91 @@ function ensureFont(family, url) {
|
|
|
372
371
|
}
|
|
373
372
|
//#endregion
|
|
374
373
|
//#region src/lib/textLayout.ts
|
|
375
|
-
function computeTextLayout(
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
374
|
+
function computeTextLayout(elOrText, fontSize, fontFamily, lineHeight, maxWidth) {
|
|
375
|
+
if (typeof elOrText === "string") return measureWithTempElement(elOrText, fontFamily, fontSize, lineHeight, maxWidth);
|
|
376
|
+
return measureElement(elOrText, fontSize);
|
|
377
|
+
}
|
|
378
|
+
function measureElement(el, fontSize) {
|
|
379
|
+
const textNode = el.firstChild;
|
|
380
|
+
if (!textNode || textNode.nodeType !== Node.TEXT_NODE) return {
|
|
381
|
+
lines: [],
|
|
382
|
+
charOffsets: [],
|
|
383
|
+
charWidths: []
|
|
384
|
+
};
|
|
385
|
+
const chars = graphemes(textNode.textContent ?? "");
|
|
386
|
+
if (!chars.length) return {
|
|
387
|
+
lines: [],
|
|
388
|
+
charOffsets: [],
|
|
389
|
+
charWidths: []
|
|
390
|
+
};
|
|
391
|
+
const range = document.createRange();
|
|
392
|
+
const charOffsets = [];
|
|
379
393
|
const charWidths = [];
|
|
380
|
-
for (const char of chars) {
|
|
381
|
-
let w = widthCache.get(char);
|
|
382
|
-
if (w === void 0) {
|
|
383
|
-
if (char === "\n") w = 0;
|
|
384
|
-
else {
|
|
385
|
-
const r = layoutWithLines(prepareWithSegments(char, fontStr, { whiteSpace: "pre-wrap" }), Infinity, lineHeight);
|
|
386
|
-
w = r.lines.length > 0 ? r.lines[0].width / fontSize : 0;
|
|
387
|
-
}
|
|
388
|
-
widthCache.set(char, w);
|
|
389
|
-
}
|
|
390
|
-
charWidths.push(w);
|
|
391
|
-
}
|
|
392
|
-
const prepared = prepareWithSegments(text, fontStr, { whiteSpace: "pre-wrap" });
|
|
393
|
-
const singleLineResult = layoutWithLines(prepared, Infinity, lineHeight);
|
|
394
|
-
const intrinsicWidth = Math.max(0, ...singleLineResult.lines.map((l) => l.width)) / fontSize;
|
|
395
|
-
const result = layoutWithLines(prepared, maxWidth, lineHeight);
|
|
396
|
-
const utf16ToCodePoint = [];
|
|
397
|
-
for (let ci = 0; ci < chars.length; ci++) for (let j = 0; j < chars[ci].length; j++) utf16ToCodePoint.push(ci);
|
|
398
394
|
const lines = [];
|
|
395
|
+
let currentLine = [];
|
|
396
|
+
let prevTop = -Infinity;
|
|
397
|
+
let lineStartX = 0;
|
|
399
398
|
let utf16Offset = 0;
|
|
400
|
-
for (
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
if (utf16Offset < text.length && text[utf16Offset] === "\n") {
|
|
412
|
-
const cpIdx = utf16ToCodePoint[utf16Offset];
|
|
413
|
-
indices.push(cpIdx);
|
|
414
|
-
utf16Offset++;
|
|
415
|
-
}
|
|
416
|
-
lines.push(indices);
|
|
417
|
-
}
|
|
418
|
-
if (utf16Offset < text.length) {
|
|
419
|
-
const indices = [];
|
|
420
|
-
const seen = /* @__PURE__ */ new Set();
|
|
421
|
-
for (let i = utf16Offset; i < text.length; i++) {
|
|
422
|
-
const cpIdx = utf16ToCodePoint[i];
|
|
423
|
-
if (!seen.has(cpIdx)) {
|
|
424
|
-
seen.add(cpIdx);
|
|
425
|
-
indices.push(cpIdx);
|
|
426
|
-
}
|
|
399
|
+
for (let i = 0; i < chars.length; i++) {
|
|
400
|
+
const char = chars[i];
|
|
401
|
+
if (char === "\n") {
|
|
402
|
+
charOffsets.push(0);
|
|
403
|
+
charWidths.push(0);
|
|
404
|
+
currentLine.push(i);
|
|
405
|
+
lines.push(currentLine);
|
|
406
|
+
currentLine = [];
|
|
407
|
+
prevTop = -Infinity;
|
|
408
|
+
utf16Offset += char.length;
|
|
409
|
+
continue;
|
|
427
410
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
kernings.push(0);
|
|
411
|
+
range.setStart(textNode, utf16Offset);
|
|
412
|
+
range.setEnd(textNode, utf16Offset + char.length);
|
|
413
|
+
const rects = range.getClientRects();
|
|
414
|
+
utf16Offset += char.length;
|
|
415
|
+
if (rects.length === 0) {
|
|
416
|
+
charOffsets.push(0);
|
|
417
|
+
charWidths.push(0);
|
|
418
|
+
currentLine.push(i);
|
|
437
419
|
continue;
|
|
438
420
|
}
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
421
|
+
const rect = rects[0];
|
|
422
|
+
if (currentLine.length > 0 && rect.top - prevTop > fontSize * .25) {
|
|
423
|
+
lines.push(currentLine);
|
|
424
|
+
currentLine = [];
|
|
425
|
+
}
|
|
426
|
+
if (currentLine.length === 0) {
|
|
427
|
+
prevTop = rect.top;
|
|
428
|
+
lineStartX = rect.left;
|
|
446
429
|
}
|
|
447
|
-
|
|
430
|
+
charOffsets.push((rect.left - lineStartX) / fontSize);
|
|
431
|
+
charWidths.push(rect.width / fontSize);
|
|
432
|
+
currentLine.push(i);
|
|
448
433
|
}
|
|
434
|
+
if (currentLine.length > 0) lines.push(currentLine);
|
|
449
435
|
return {
|
|
450
436
|
lines,
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
intrinsicWidth
|
|
437
|
+
charOffsets,
|
|
438
|
+
charWidths
|
|
454
439
|
};
|
|
455
440
|
}
|
|
441
|
+
function measureWithTempElement(text, fontFamily, fontSize, lineHeight, maxWidth) {
|
|
442
|
+
const el = document.createElement("div");
|
|
443
|
+
el.style.position = "absolute";
|
|
444
|
+
el.style.left = "-9999px";
|
|
445
|
+
el.style.top = "-9999px";
|
|
446
|
+
el.style.visibility = "hidden";
|
|
447
|
+
el.style.fontFamily = fontFamily;
|
|
448
|
+
el.style.fontSize = `${fontSize}px`;
|
|
449
|
+
el.style.lineHeight = `${lineHeight}px`;
|
|
450
|
+
el.style.whiteSpace = "pre-wrap";
|
|
451
|
+
el.style.overflowWrap = "break-word";
|
|
452
|
+
el.style.width = `${maxWidth}px`;
|
|
453
|
+
el.textContent = text;
|
|
454
|
+
document.body.appendChild(el);
|
|
455
|
+
const result = measureElement(el, fontSize);
|
|
456
|
+
document.body.removeChild(el);
|
|
457
|
+
return result;
|
|
458
|
+
}
|
|
456
459
|
//#endregion
|
|
457
460
|
//#region src/lib/timeline.ts
|
|
458
461
|
const DEFAULTS = {
|
|
@@ -494,6 +497,25 @@ function computeTimeline(text, font, config) {
|
|
|
494
497
|
};
|
|
495
498
|
}
|
|
496
499
|
//#endregion
|
|
500
|
+
//#region src/core/bundle-registry.ts
|
|
501
|
+
const bundles = /* @__PURE__ */ new Map();
|
|
502
|
+
/** Register a font bundle so it can be referenced by family name. */
|
|
503
|
+
function registerBundle(bundle) {
|
|
504
|
+
bundles.set(bundle.family, bundle);
|
|
505
|
+
}
|
|
506
|
+
/** Look up a registered bundle by family name. */
|
|
507
|
+
function getBundle(family) {
|
|
508
|
+
return bundles.get(family);
|
|
509
|
+
}
|
|
510
|
+
function resolveBundle(font) {
|
|
511
|
+
if (typeof font === "string") {
|
|
512
|
+
const bundle = getBundle(font);
|
|
513
|
+
if (!bundle) throw new Error(`TegakiEngine: no bundle registered for "${font}". Call TegakiEngine.registerBundle() first.`);
|
|
514
|
+
return bundle;
|
|
515
|
+
}
|
|
516
|
+
return font;
|
|
517
|
+
}
|
|
518
|
+
//#endregion
|
|
497
519
|
//#region src/core/createBundle.ts
|
|
498
520
|
/**
|
|
499
521
|
* Creates a {@link TegakiBundle} from its constituent parts.
|
|
@@ -593,14 +615,17 @@ function drawFallbackGlyph(ctx, char, x, baseline, fontSize, fontFamily, color,
|
|
|
593
615
|
ctx.restore();
|
|
594
616
|
}
|
|
595
617
|
//#endregion
|
|
596
|
-
//#region src/core/
|
|
618
|
+
//#region src/core/render-elements.ts
|
|
597
619
|
const PAD_V_CSS = "max(0.2em, 0.9em - 0.5lh)";
|
|
598
620
|
function buildRootProps(options) {
|
|
599
621
|
const text = options.text ?? "";
|
|
600
622
|
const font = resolveBundle(options.font);
|
|
601
623
|
const fontFamily = font?.family;
|
|
602
624
|
const duration = text && font ? computeTimeline(text, font, options.timing).totalDuration : 0;
|
|
603
|
-
const
|
|
625
|
+
const timeObj = typeof options.time === "object" ? options.time : null;
|
|
626
|
+
const rawTime = typeof options.time === "number" ? options.time : timeObj?.mode === "controlled" ? timeObj.unit === "progress" ? timeObj.value * duration : timeObj.value : timeObj?.mode === "uncontrolled" ? timeObj.initialTime ?? 0 : 0;
|
|
627
|
+
const easing = timeObj?.mode === "uncontrolled" ? timeObj.easing : void 0;
|
|
628
|
+
const time = easing && duration > 0 ? easing(rawTime / duration) * duration : rawTime;
|
|
604
629
|
const progress = duration > 0 ? time / duration : 0;
|
|
605
630
|
return {
|
|
606
631
|
"data-tegaki": "root",
|
|
@@ -620,7 +645,10 @@ function buildChildren(options, h) {
|
|
|
620
645
|
const text = options.text ?? "";
|
|
621
646
|
const isCss = options.time === "css" || typeof options.time === "object" && options.time?.mode === "css";
|
|
622
647
|
const showOverlay = options.showOverlay;
|
|
623
|
-
return h("
|
|
648
|
+
return h("span", { style: {
|
|
649
|
+
display: "block",
|
|
650
|
+
position: "relative"
|
|
651
|
+
} }, h("span", {
|
|
624
652
|
"data-tegaki": "sentinel",
|
|
625
653
|
"aria-hidden": "true",
|
|
626
654
|
style: {
|
|
@@ -650,9 +678,10 @@ function buildChildren(options, h) {
|
|
|
650
678
|
display: "inline-block",
|
|
651
679
|
padding: `${PAD_V_CSS} 0.2em`
|
|
652
680
|
}
|
|
653
|
-
}, text)), h("
|
|
681
|
+
}, text)), h("span", {
|
|
654
682
|
"data-tegaki": "overlay",
|
|
655
683
|
style: {
|
|
684
|
+
display: "block",
|
|
656
685
|
userSelect: "auto",
|
|
657
686
|
whiteSpace: "pre-wrap",
|
|
658
687
|
overflowWrap: "break-word",
|
|
@@ -673,6 +702,8 @@ function domCreateElement(tag, props, ...children) {
|
|
|
673
702
|
else el.appendChild(child);
|
|
674
703
|
return el;
|
|
675
704
|
}
|
|
705
|
+
//#endregion
|
|
706
|
+
//#region src/core/engine.ts
|
|
676
707
|
function resolveTimeControl(prop) {
|
|
677
708
|
if (prop == null) return { mode: "uncontrolled" };
|
|
678
709
|
if (typeof prop === "number") return {
|
|
@@ -682,23 +713,14 @@ function resolveTimeControl(prop) {
|
|
|
682
713
|
if (prop === "css") return { mode: "css" };
|
|
683
714
|
return prop;
|
|
684
715
|
}
|
|
685
|
-
|
|
686
|
-
if (typeof font === "string") {
|
|
687
|
-
const bundle = TegakiEngine.getBundle(font);
|
|
688
|
-
if (!bundle) throw new Error(`TegakiEngine: no bundle registered for "${font}". Call TegakiEngine.registerBundle() first.`);
|
|
689
|
-
return bundle;
|
|
690
|
-
}
|
|
691
|
-
return font;
|
|
692
|
-
}
|
|
693
|
-
var TegakiEngine = class TegakiEngine {
|
|
694
|
-
static _bundles = /* @__PURE__ */ new Map();
|
|
716
|
+
var TegakiEngine = class {
|
|
695
717
|
/** Register a font bundle so it can be referenced by family name. */
|
|
696
718
|
static registerBundle(bundle) {
|
|
697
|
-
|
|
719
|
+
registerBundle(bundle);
|
|
698
720
|
}
|
|
699
721
|
/** Look up a registered bundle by family name. */
|
|
700
722
|
static getBundle(family) {
|
|
701
|
-
return
|
|
723
|
+
return getBundle(family);
|
|
702
724
|
}
|
|
703
725
|
_rootEl;
|
|
704
726
|
_contentEl = null;
|
|
@@ -721,6 +743,7 @@ var TegakiEngine = class TegakiEngine {
|
|
|
721
743
|
totalDuration: 0
|
|
722
744
|
};
|
|
723
745
|
_layout = null;
|
|
746
|
+
_layoutKey = "";
|
|
724
747
|
_fontReady = false;
|
|
725
748
|
_containerWidth = 0;
|
|
726
749
|
_fontSize = 0;
|
|
@@ -730,6 +753,8 @@ var TegakiEngine = class TegakiEngine {
|
|
|
730
753
|
_cssTime = 0;
|
|
731
754
|
_playing = true;
|
|
732
755
|
_smoothedBoost = 0;
|
|
756
|
+
_delayRemaining = 0;
|
|
757
|
+
_loopGapRemaining = 0;
|
|
733
758
|
_lastTs = null;
|
|
734
759
|
_rafId = 0;
|
|
735
760
|
_prevCompleted = false;
|
|
@@ -781,7 +806,9 @@ var TegakiEngine = class TegakiEngine {
|
|
|
781
806
|
get currentTime() {
|
|
782
807
|
const tc = this._timeControl;
|
|
783
808
|
if (tc.mode === "css") return this._cssTime;
|
|
784
|
-
if (tc.mode === "controlled") return tc.value;
|
|
809
|
+
if (tc.mode === "controlled") return tc.unit === "progress" ? tc.value * this._timeline.totalDuration : tc.value;
|
|
810
|
+
const totalDur = this._timeline.totalDuration;
|
|
811
|
+
if (tc.easing && totalDur > 0) return tc.easing(this._internalTime / totalDur) * totalDur;
|
|
785
812
|
return this._internalTime;
|
|
786
813
|
}
|
|
787
814
|
get duration() {
|
|
@@ -791,7 +818,10 @@ var TegakiEngine = class TegakiEngine {
|
|
|
791
818
|
return this._playing;
|
|
792
819
|
}
|
|
793
820
|
get isComplete() {
|
|
794
|
-
|
|
821
|
+
const totalDur = this._timeline.totalDuration;
|
|
822
|
+
if (totalDur === 0) return false;
|
|
823
|
+
if (this._timeControl.mode === "uncontrolled") return this._internalTime >= totalDur;
|
|
824
|
+
return this.currentTime >= totalDur;
|
|
795
825
|
}
|
|
796
826
|
get element() {
|
|
797
827
|
return this._rootEl;
|
|
@@ -809,6 +839,8 @@ var TegakiEngine = class TegakiEngine {
|
|
|
809
839
|
seek(time) {
|
|
810
840
|
if (this._timeControl.mode !== "uncontrolled") return;
|
|
811
841
|
this._internalTime = Math.max(0, Math.min(time, this._timeline.totalDuration));
|
|
842
|
+
this._delayRemaining = 0;
|
|
843
|
+
this._loopGapRemaining = 0;
|
|
812
844
|
this._checkCompletion();
|
|
813
845
|
this._notifyTimeChange();
|
|
814
846
|
this._render();
|
|
@@ -819,6 +851,8 @@ var TegakiEngine = class TegakiEngine {
|
|
|
819
851
|
this._internalTime = 0;
|
|
820
852
|
this._playing = true;
|
|
821
853
|
this._prevCompleted = false;
|
|
854
|
+
this._delayRemaining = this._timeControl.delay ?? 0;
|
|
855
|
+
this._loopGapRemaining = 0;
|
|
822
856
|
this._notifyTimeChange();
|
|
823
857
|
this._evaluatePlayback();
|
|
824
858
|
}
|
|
@@ -846,11 +880,19 @@ var TegakiEngine = class TegakiEngine {
|
|
|
846
880
|
const newTc = resolveTimeControl(options.time);
|
|
847
881
|
const oldTc = this._timeControl;
|
|
848
882
|
const modeChanged = newTc.mode !== oldTc.mode;
|
|
849
|
-
const controlledValueChanged = newTc.mode === "controlled" && oldTc.mode === "controlled" && newTc.value !== oldTc.value;
|
|
850
|
-
const uncontrolledChanged = newTc.mode === "uncontrolled" && oldTc.mode === "uncontrolled" && (newTc.speed !== oldTc.speed || newTc.playing !== oldTc.playing || newTc.loop !== oldTc.loop || newTc.catchUp !== oldTc.catchUp);
|
|
883
|
+
const controlledValueChanged = newTc.mode === "controlled" && oldTc.mode === "controlled" && (newTc.value !== oldTc.value || newTc.unit !== oldTc.unit);
|
|
884
|
+
const uncontrolledChanged = newTc.mode === "uncontrolled" && oldTc.mode === "uncontrolled" && (newTc.speed !== oldTc.speed || newTc.duration !== oldTc.duration || newTc.playing !== oldTc.playing || newTc.loop !== oldTc.loop || newTc.delay !== oldTc.delay || newTc.loopGap !== oldTc.loopGap || newTc.catchUp !== oldTc.catchUp || newTc.easing !== oldTc.easing);
|
|
851
885
|
if (modeChanged || controlledValueChanged || uncontrolledChanged) {
|
|
852
886
|
this._timeControl = newTc;
|
|
853
|
-
if (newTc.mode === "uncontrolled")
|
|
887
|
+
if (newTc.mode === "uncontrolled") {
|
|
888
|
+
this._playing = newTc.playing ?? true;
|
|
889
|
+
const oldDelay = oldTc.mode === "uncontrolled" ? oldTc.delay ?? 0 : 0;
|
|
890
|
+
const newDelay = newTc.delay ?? 0;
|
|
891
|
+
if (modeChanged || oldDelay !== newDelay) {
|
|
892
|
+
this._delayRemaining = newDelay;
|
|
893
|
+
this._loopGapRemaining = 0;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
854
896
|
dirtyPlayback = true;
|
|
855
897
|
dirtyRender = true;
|
|
856
898
|
this._updateSentinelTransition();
|
|
@@ -876,12 +918,10 @@ var TegakiEngine = class TegakiEngine {
|
|
|
876
918
|
}
|
|
877
919
|
if ("onComplete" in options) this._onComplete = options.onComplete;
|
|
878
920
|
if (dirtyTimeline) this._recomputeTimeline();
|
|
921
|
+
if (dirtyRender || dirtyTimeline || dirtyLayout) this._updateDom();
|
|
879
922
|
if (dirtyLayout) this._recomputeLayout();
|
|
880
923
|
if (dirtyPlayback) this._evaluatePlayback();
|
|
881
|
-
if (dirtyRender || dirtyTimeline || dirtyLayout)
|
|
882
|
-
this._updateDom();
|
|
883
|
-
this._render();
|
|
884
|
-
}
|
|
924
|
+
if (dirtyRender || dirtyTimeline || dirtyLayout) this._render();
|
|
885
925
|
}
|
|
886
926
|
destroy() {
|
|
887
927
|
this._destroyed = true;
|
|
@@ -907,7 +947,7 @@ var TegakiEngine = class TegakiEngine {
|
|
|
907
947
|
_updateDom() {
|
|
908
948
|
this._rootEl.style.fontFamily = this._font?.family ?? "";
|
|
909
949
|
this._updateCssProperties();
|
|
910
|
-
this._overlayEl.textContent = this._text;
|
|
950
|
+
if (this._overlayEl.textContent !== this._text) this._overlayEl.textContent = this._text;
|
|
911
951
|
this._canvasFallbackEl.textContent = this._text;
|
|
912
952
|
}
|
|
913
953
|
_updateCssProperties() {
|
|
@@ -1010,9 +1050,9 @@ var TegakiEngine = class TegakiEngine {
|
|
|
1010
1050
|
if (this._font === currentFont && !this._destroyed) {
|
|
1011
1051
|
this._fontReady = true;
|
|
1012
1052
|
this._recomputeTimeline();
|
|
1053
|
+
this._updateDom();
|
|
1013
1054
|
this._recomputeLayout();
|
|
1014
1055
|
this._evaluatePlayback();
|
|
1015
|
-
this._updateDom();
|
|
1016
1056
|
this._render();
|
|
1017
1057
|
}
|
|
1018
1058
|
});
|
|
@@ -1025,9 +1065,15 @@ var TegakiEngine = class TegakiEngine {
|
|
|
1025
1065
|
};
|
|
1026
1066
|
}
|
|
1027
1067
|
_recomputeLayout() {
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1068
|
+
if (this._fontReady && this._font?.family && this._fontSize && this._containerWidth && this._text) {
|
|
1069
|
+
const key = `${this._text}\0${this._font.family}\0${this._fontSize}\0${this._lineHeight}\0${this._containerWidth}`;
|
|
1070
|
+
if (key === this._layoutKey) return;
|
|
1071
|
+
this._layoutKey = key;
|
|
1072
|
+
this._layout = computeTextLayout(this._overlayEl, this._fontSize);
|
|
1073
|
+
} else {
|
|
1074
|
+
this._layoutKey = "";
|
|
1075
|
+
this._layout = null;
|
|
1076
|
+
}
|
|
1031
1077
|
}
|
|
1032
1078
|
_evaluatePlayback() {
|
|
1033
1079
|
if (this._timeControl.mode === "uncontrolled" && this._playing && !!this._font && this._fontReady && !this._prefersReducedMotion) this._startLoop();
|
|
@@ -1052,28 +1098,59 @@ var TegakiEngine = class TegakiEngine {
|
|
|
1052
1098
|
this._lastTs = ts;
|
|
1053
1099
|
const tc = this._timeControl;
|
|
1054
1100
|
if (tc.mode !== "uncontrolled") return;
|
|
1055
|
-
const speed = tc.speed ?? 1;
|
|
1056
1101
|
const loop = tc.loop ?? false;
|
|
1057
|
-
const catchUp = tc.catchUp ?? 0;
|
|
1058
1102
|
const totalDur = this._timeline.totalDuration;
|
|
1103
|
+
const durationOverride = tc.duration;
|
|
1104
|
+
const useDuration = durationOverride !== void 0 && durationOverride > 0;
|
|
1059
1105
|
if (totalDur === 0 || !loop && this._internalTime >= totalDur) {
|
|
1060
1106
|
this._internalTime = totalDur;
|
|
1061
1107
|
this._rafId = requestAnimationFrame(this._tick);
|
|
1062
1108
|
return;
|
|
1063
1109
|
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1110
|
+
if (this._delayRemaining > 0) {
|
|
1111
|
+
this._delayRemaining = Math.max(0, this._delayRemaining - dtSec);
|
|
1112
|
+
this._rafId = requestAnimationFrame(this._tick);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
if (this._loopGapRemaining > 0) {
|
|
1116
|
+
this._loopGapRemaining = Math.max(0, this._loopGapRemaining - dtSec);
|
|
1117
|
+
if (this._loopGapRemaining <= 0) {
|
|
1118
|
+
this._internalTime = 0;
|
|
1119
|
+
this._prevCompleted = false;
|
|
1120
|
+
this._smoothedBoost = 0;
|
|
1121
|
+
}
|
|
1122
|
+
this._notifyTimeChange();
|
|
1123
|
+
this._render();
|
|
1124
|
+
this._updateCssProperties();
|
|
1125
|
+
this._rafId = requestAnimationFrame(this._tick);
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
let effectiveSpeed;
|
|
1129
|
+
if (useDuration) effectiveSpeed = totalDur / durationOverride;
|
|
1130
|
+
else {
|
|
1131
|
+
const speed = tc.speed ?? 1;
|
|
1132
|
+
const catchUp = tc.catchUp ?? 0;
|
|
1133
|
+
effectiveSpeed = speed;
|
|
1134
|
+
if (catchUp > 0) {
|
|
1135
|
+
const remaining = Math.max(0, totalDur - this._internalTime);
|
|
1136
|
+
const targetBoost = catchUp * Math.max(0, remaining - 2);
|
|
1137
|
+
const attackRate = 4;
|
|
1138
|
+
const releaseRate = loop ? 30 : 2;
|
|
1139
|
+
const rate = targetBoost > this._smoothedBoost ? attackRate : releaseRate;
|
|
1140
|
+
this._smoothedBoost += (targetBoost - this._smoothedBoost) * (1 - Math.exp(-rate * dtSec));
|
|
1141
|
+
effectiveSpeed = speed + this._smoothedBoost;
|
|
1142
|
+
}
|
|
1073
1143
|
}
|
|
1074
1144
|
let next = this._internalTime + dtSec * effectiveSpeed;
|
|
1075
1145
|
if (next >= totalDur) {
|
|
1076
|
-
|
|
1146
|
+
if (loop) {
|
|
1147
|
+
const loopGap = tc.loopGap ?? 0;
|
|
1148
|
+
if (loopGap > 0) {
|
|
1149
|
+
next = totalDur;
|
|
1150
|
+
this._loopGapRemaining = loopGap;
|
|
1151
|
+
} else if (this._internalTime < totalDur) next = totalDur;
|
|
1152
|
+
else next %= totalDur;
|
|
1153
|
+
} else next = totalDur;
|
|
1077
1154
|
this._smoothedBoost = 0;
|
|
1078
1155
|
}
|
|
1079
1156
|
this._internalTime = next;
|
|
@@ -1085,10 +1162,10 @@ var TegakiEngine = class TegakiEngine {
|
|
|
1085
1162
|
};
|
|
1086
1163
|
_notifyTimeChange() {
|
|
1087
1164
|
const tc = this._timeControl;
|
|
1088
|
-
if (tc.mode === "uncontrolled" && tc.onTimeChange) tc.onTimeChange(this.
|
|
1165
|
+
if (tc.mode === "uncontrolled" && tc.onTimeChange) tc.onTimeChange(this.currentTime);
|
|
1089
1166
|
}
|
|
1090
1167
|
_checkCompletion() {
|
|
1091
|
-
const complete = this.
|
|
1168
|
+
const complete = this.isComplete;
|
|
1092
1169
|
if (complete && !this._prevCompleted) {
|
|
1093
1170
|
this._prevCompleted = true;
|
|
1094
1171
|
this._onComplete?.();
|
|
@@ -1101,9 +1178,8 @@ var TegakiEngine = class TegakiEngine {
|
|
|
1101
1178
|
const fontSize = this._fontSize;
|
|
1102
1179
|
if (!font?.glyphData || !layout || !fontSize) return;
|
|
1103
1180
|
const dpr = window.devicePixelRatio || 1;
|
|
1104
|
-
const
|
|
1105
|
-
const
|
|
1106
|
-
const h = canvasRect.height;
|
|
1181
|
+
const w = canvas.offsetWidth;
|
|
1182
|
+
const h = canvas.offsetHeight;
|
|
1107
1183
|
if (canvas.width !== Math.round(w * dpr) || canvas.height !== Math.round(h * dpr)) {
|
|
1108
1184
|
canvas.width = Math.round(w * dpr);
|
|
1109
1185
|
canvas.height = Math.round(h * dpr);
|
|
@@ -1122,36 +1198,29 @@ var TegakiEngine = class TegakiEngine {
|
|
|
1122
1198
|
const currentTime = this.currentTime;
|
|
1123
1199
|
let y = 0;
|
|
1124
1200
|
for (const lineIndices of layout.lines) {
|
|
1125
|
-
let x = 0;
|
|
1126
1201
|
for (const charIdx of lineIndices) {
|
|
1127
1202
|
const char = characters[charIdx];
|
|
1128
1203
|
if (char === "\n") continue;
|
|
1129
1204
|
const entry = this._timeline.entries[charIdx];
|
|
1130
|
-
const
|
|
1131
|
-
const kerning = layout.kernings[charIdx] ?? 0;
|
|
1205
|
+
const x = (layout.charOffsets[charIdx] ?? 0) * fontSize;
|
|
1132
1206
|
const glyph = font.glyphData[char];
|
|
1133
1207
|
if (glyph && entry.hasGlyph) {
|
|
1134
1208
|
const localTime = Math.max(0, Math.min(currentTime - entry.offset, entry.duration));
|
|
1135
|
-
const glyphY = y + halfLeading;
|
|
1136
1209
|
drawGlyph(ctx, glyph, {
|
|
1137
1210
|
x,
|
|
1138
|
-
y:
|
|
1211
|
+
y: y + halfLeading,
|
|
1139
1212
|
fontSize,
|
|
1140
1213
|
unitsPerEm: font.unitsPerEm,
|
|
1141
1214
|
ascender: font.ascender,
|
|
1142
1215
|
descender: font.descender
|
|
1143
1216
|
}, localTime, font.lineCap, color, this._resolvedEffects, this._seed + charIdx, this._segmentSize);
|
|
1144
|
-
} else if (!entry.hasGlyph && currentTime >= entry.offset + entry.duration)
|
|
1145
|
-
const baseline = y + halfLeading + font.ascender / font.unitsPerEm * fontSize;
|
|
1146
|
-
drawFallbackGlyph(ctx, char, x, baseline, fontSize, font.family, color, this._resolvedEffects, this._seed + charIdx);
|
|
1147
|
-
}
|
|
1148
|
-
x += (charWidth + kerning) * fontSize;
|
|
1217
|
+
} else if (!entry.hasGlyph && currentTime >= entry.offset + entry.duration) drawFallbackGlyph(ctx, char, x, y + halfLeading + font.ascender / font.unitsPerEm * fontSize, fontSize, font.family, color, this._resolvedEffects, this._seed + charIdx);
|
|
1149
1218
|
}
|
|
1150
1219
|
y += lineHeight;
|
|
1151
1220
|
}
|
|
1152
1221
|
}
|
|
1153
1222
|
};
|
|
1154
1223
|
//#endregion
|
|
1155
|
-
export {
|
|
1224
|
+
export { createBundle as a, resolveBundle as c, ensureFontFace as d, drawGlyph as f, domCreateElement as i, computeTimeline as l, resolveEffects as m, buildChildren as n, getBundle as o, coerceToString as p, buildRootProps as r, registerBundle as s, TegakiEngine as t, computeTextLayout as u };
|
|
1156
1225
|
|
|
1157
|
-
//# sourceMappingURL=core-
|
|
1226
|
+
//# sourceMappingURL=core-I9K3LqxK.mjs.map
|