tegaki 0.10.0 → 0.11.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 +24 -0
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +2 -2
- package/dist/{core-D68zOEne.mjs → core-I9K3LqxK.mjs} +148 -120
- package/dist/core-I9K3LqxK.mjs.map +1 -0
- package/dist/{index-BdBLaKjh.d.mts → index-CZFVynOK.d.mts} +73 -28
- package/dist/{index-B2v8wlaN.d.mts → index-vDbG4xkP.d.mts} +2 -2
- 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-Cgxpndhg.mjs → react-DGPG66n-.mjs} +2 -2
- 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 +26 -11
- package/dist/wc/index.mjs.map +1 -1
- package/package.json +11 -9
- package/src/core/bundle-registry.ts +22 -0
- package/src/core/engine.ts +62 -255
- 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 +2 -1
- 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 +29 -7
- package/dist/core-D68zOEne.mjs.map +0 -1
- package/dist/react-Cgxpndhg.mjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# tegaki
|
|
2
2
|
|
|
3
|
+
## 0.11.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`5e5049f`](https://github.com/KurtGokhan/tegaki/commit/5e5049ffc86a275fd2892fcb683d1e1ad702542e) Thanks [@KurtGokhan](https://github.com/KurtGokhan)! - use correct import path for node/ssr imports
|
|
8
|
+
|
|
9
|
+
## 0.11.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [`4b7db41`](https://github.com/KurtGokhan/tegaki/commit/4b7db41fb1c247ed766ff10284e9cdabd4ab0a25) Thanks [@KurtGokhan](https://github.com/KurtGokhan)! - Implement new text layout based on DOM and text ranges
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- [`f3602b0`](https://github.com/KurtGokhan/tegaki/commit/f3602b04970c8cb88ea41e87e63ee4709b086d61) Thanks [@KurtGokhan](https://github.com/KurtGokhan)! - improve line cap detection for CJK fonts
|
|
18
|
+
|
|
19
|
+
- [`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
|
|
20
|
+
|
|
21
|
+
- [`047e5e3`](https://github.com/KurtGokhan/tegaki/commit/047e5e31d3ffabbecf25dd36b5f56d298731c630) Thanks [@KurtGokhan](https://github.com/KurtGokhan)! - Add `duration` and `easing` options to uncontrolled time mode.
|
|
22
|
+
|
|
23
|
+
- `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.
|
|
24
|
+
- `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.
|
|
25
|
+
- The web component adapter accepts a `duration` attribute; `easing` is available via the `time` JS property only (it's function-valued).
|
|
26
|
+
|
|
3
27
|
## 0.10.0
|
|
4
28
|
|
|
5
29
|
### 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
|
-
k = (r.lines.length > 0 ? r.lines[0].width / fontSize : 0) - (widthCache.get(a) ?? 0) - (widthCache.get(b) ?? 0);
|
|
444
|
-
if (Math.abs(k) < .001) k = 0;
|
|
445
|
-
pairCache.set(pair, k);
|
|
421
|
+
const rect = rects[0];
|
|
422
|
+
if (currentLine.length > 0 && rect.top - prevTop > fontSize * .25) {
|
|
423
|
+
lines.push(currentLine);
|
|
424
|
+
currentLine = [];
|
|
446
425
|
}
|
|
447
|
-
|
|
426
|
+
if (currentLine.length === 0) {
|
|
427
|
+
prevTop = rect.top;
|
|
428
|
+
lineStartX = rect.left;
|
|
429
|
+
}
|
|
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",
|
|
@@ -677,6 +702,8 @@ function domCreateElement(tag, props, ...children) {
|
|
|
677
702
|
else el.appendChild(child);
|
|
678
703
|
return el;
|
|
679
704
|
}
|
|
705
|
+
//#endregion
|
|
706
|
+
//#region src/core/engine.ts
|
|
680
707
|
function resolveTimeControl(prop) {
|
|
681
708
|
if (prop == null) return { mode: "uncontrolled" };
|
|
682
709
|
if (typeof prop === "number") return {
|
|
@@ -686,23 +713,14 @@ function resolveTimeControl(prop) {
|
|
|
686
713
|
if (prop === "css") return { mode: "css" };
|
|
687
714
|
return prop;
|
|
688
715
|
}
|
|
689
|
-
|
|
690
|
-
if (typeof font === "string") {
|
|
691
|
-
const bundle = TegakiEngine.getBundle(font);
|
|
692
|
-
if (!bundle) throw new Error(`TegakiEngine: no bundle registered for "${font}". Call TegakiEngine.registerBundle() first.`);
|
|
693
|
-
return bundle;
|
|
694
|
-
}
|
|
695
|
-
return font;
|
|
696
|
-
}
|
|
697
|
-
var TegakiEngine = class TegakiEngine {
|
|
698
|
-
static _bundles = /* @__PURE__ */ new Map();
|
|
716
|
+
var TegakiEngine = class {
|
|
699
717
|
/** Register a font bundle so it can be referenced by family name. */
|
|
700
718
|
static registerBundle(bundle) {
|
|
701
|
-
|
|
719
|
+
registerBundle(bundle);
|
|
702
720
|
}
|
|
703
721
|
/** Look up a registered bundle by family name. */
|
|
704
722
|
static getBundle(family) {
|
|
705
|
-
return
|
|
723
|
+
return getBundle(family);
|
|
706
724
|
}
|
|
707
725
|
_rootEl;
|
|
708
726
|
_contentEl = null;
|
|
@@ -725,6 +743,7 @@ var TegakiEngine = class TegakiEngine {
|
|
|
725
743
|
totalDuration: 0
|
|
726
744
|
};
|
|
727
745
|
_layout = null;
|
|
746
|
+
_layoutKey = "";
|
|
728
747
|
_fontReady = false;
|
|
729
748
|
_containerWidth = 0;
|
|
730
749
|
_fontSize = 0;
|
|
@@ -788,6 +807,8 @@ var TegakiEngine = class TegakiEngine {
|
|
|
788
807
|
const tc = this._timeControl;
|
|
789
808
|
if (tc.mode === "css") return this._cssTime;
|
|
790
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;
|
|
791
812
|
return this._internalTime;
|
|
792
813
|
}
|
|
793
814
|
get duration() {
|
|
@@ -797,7 +818,10 @@ var TegakiEngine = class TegakiEngine {
|
|
|
797
818
|
return this._playing;
|
|
798
819
|
}
|
|
799
820
|
get isComplete() {
|
|
800
|
-
|
|
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;
|
|
801
825
|
}
|
|
802
826
|
get element() {
|
|
803
827
|
return this._rootEl;
|
|
@@ -857,7 +881,7 @@ var TegakiEngine = class TegakiEngine {
|
|
|
857
881
|
const oldTc = this._timeControl;
|
|
858
882
|
const modeChanged = newTc.mode !== oldTc.mode;
|
|
859
883
|
const controlledValueChanged = newTc.mode === "controlled" && oldTc.mode === "controlled" && (newTc.value !== oldTc.value || newTc.unit !== oldTc.unit);
|
|
860
|
-
const uncontrolledChanged = newTc.mode === "uncontrolled" && oldTc.mode === "uncontrolled" && (newTc.speed !== oldTc.speed || newTc.playing !== oldTc.playing || newTc.loop !== oldTc.loop || newTc.delay !== oldTc.delay || newTc.loopGap !== oldTc.loopGap || newTc.catchUp !== oldTc.catchUp);
|
|
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);
|
|
861
885
|
if (modeChanged || controlledValueChanged || uncontrolledChanged) {
|
|
862
886
|
this._timeControl = newTc;
|
|
863
887
|
if (newTc.mode === "uncontrolled") {
|
|
@@ -894,12 +918,10 @@ var TegakiEngine = class TegakiEngine {
|
|
|
894
918
|
}
|
|
895
919
|
if ("onComplete" in options) this._onComplete = options.onComplete;
|
|
896
920
|
if (dirtyTimeline) this._recomputeTimeline();
|
|
921
|
+
if (dirtyRender || dirtyTimeline || dirtyLayout) this._updateDom();
|
|
897
922
|
if (dirtyLayout) this._recomputeLayout();
|
|
898
923
|
if (dirtyPlayback) this._evaluatePlayback();
|
|
899
|
-
if (dirtyRender || dirtyTimeline || dirtyLayout)
|
|
900
|
-
this._updateDom();
|
|
901
|
-
this._render();
|
|
902
|
-
}
|
|
924
|
+
if (dirtyRender || dirtyTimeline || dirtyLayout) this._render();
|
|
903
925
|
}
|
|
904
926
|
destroy() {
|
|
905
927
|
this._destroyed = true;
|
|
@@ -1028,9 +1050,9 @@ var TegakiEngine = class TegakiEngine {
|
|
|
1028
1050
|
if (this._font === currentFont && !this._destroyed) {
|
|
1029
1051
|
this._fontReady = true;
|
|
1030
1052
|
this._recomputeTimeline();
|
|
1053
|
+
this._updateDom();
|
|
1031
1054
|
this._recomputeLayout();
|
|
1032
1055
|
this._evaluatePlayback();
|
|
1033
|
-
this._updateDom();
|
|
1034
1056
|
this._render();
|
|
1035
1057
|
}
|
|
1036
1058
|
});
|
|
@@ -1043,9 +1065,15 @@ var TegakiEngine = class TegakiEngine {
|
|
|
1043
1065
|
};
|
|
1044
1066
|
}
|
|
1045
1067
|
_recomputeLayout() {
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
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
|
+
}
|
|
1049
1077
|
}
|
|
1050
1078
|
_evaluatePlayback() {
|
|
1051
1079
|
if (this._timeControl.mode === "uncontrolled" && this._playing && !!this._font && this._fontReady && !this._prefersReducedMotion) this._startLoop();
|
|
@@ -1070,10 +1098,10 @@ var TegakiEngine = class TegakiEngine {
|
|
|
1070
1098
|
this._lastTs = ts;
|
|
1071
1099
|
const tc = this._timeControl;
|
|
1072
1100
|
if (tc.mode !== "uncontrolled") return;
|
|
1073
|
-
const speed = tc.speed ?? 1;
|
|
1074
1101
|
const loop = tc.loop ?? false;
|
|
1075
|
-
const catchUp = tc.catchUp ?? 0;
|
|
1076
1102
|
const totalDur = this._timeline.totalDuration;
|
|
1103
|
+
const durationOverride = tc.duration;
|
|
1104
|
+
const useDuration = durationOverride !== void 0 && durationOverride > 0;
|
|
1077
1105
|
if (totalDur === 0 || !loop && this._internalTime >= totalDur) {
|
|
1078
1106
|
this._internalTime = totalDur;
|
|
1079
1107
|
this._rafId = requestAnimationFrame(this._tick);
|
|
@@ -1097,15 +1125,21 @@ var TegakiEngine = class TegakiEngine {
|
|
|
1097
1125
|
this._rafId = requestAnimationFrame(this._tick);
|
|
1098
1126
|
return;
|
|
1099
1127
|
}
|
|
1100
|
-
let effectiveSpeed
|
|
1101
|
-
if (
|
|
1102
|
-
|
|
1103
|
-
const
|
|
1104
|
-
const
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
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
|
+
}
|
|
1109
1143
|
}
|
|
1110
1144
|
let next = this._internalTime + dtSec * effectiveSpeed;
|
|
1111
1145
|
if (next >= totalDur) {
|
|
@@ -1114,7 +1148,8 @@ var TegakiEngine = class TegakiEngine {
|
|
|
1114
1148
|
if (loopGap > 0) {
|
|
1115
1149
|
next = totalDur;
|
|
1116
1150
|
this._loopGapRemaining = loopGap;
|
|
1117
|
-
} else
|
|
1151
|
+
} else if (this._internalTime < totalDur) next = totalDur;
|
|
1152
|
+
else next %= totalDur;
|
|
1118
1153
|
} else next = totalDur;
|
|
1119
1154
|
this._smoothedBoost = 0;
|
|
1120
1155
|
}
|
|
@@ -1127,10 +1162,10 @@ var TegakiEngine = class TegakiEngine {
|
|
|
1127
1162
|
};
|
|
1128
1163
|
_notifyTimeChange() {
|
|
1129
1164
|
const tc = this._timeControl;
|
|
1130
|
-
if (tc.mode === "uncontrolled" && tc.onTimeChange) tc.onTimeChange(this.
|
|
1165
|
+
if (tc.mode === "uncontrolled" && tc.onTimeChange) tc.onTimeChange(this.currentTime);
|
|
1131
1166
|
}
|
|
1132
1167
|
_checkCompletion() {
|
|
1133
|
-
const complete = this.
|
|
1168
|
+
const complete = this.isComplete;
|
|
1134
1169
|
if (complete && !this._prevCompleted) {
|
|
1135
1170
|
this._prevCompleted = true;
|
|
1136
1171
|
this._onComplete?.();
|
|
@@ -1163,36 +1198,29 @@ var TegakiEngine = class TegakiEngine {
|
|
|
1163
1198
|
const currentTime = this.currentTime;
|
|
1164
1199
|
let y = 0;
|
|
1165
1200
|
for (const lineIndices of layout.lines) {
|
|
1166
|
-
let x = 0;
|
|
1167
1201
|
for (const charIdx of lineIndices) {
|
|
1168
1202
|
const char = characters[charIdx];
|
|
1169
1203
|
if (char === "\n") continue;
|
|
1170
1204
|
const entry = this._timeline.entries[charIdx];
|
|
1171
|
-
const
|
|
1172
|
-
const kerning = layout.kernings[charIdx] ?? 0;
|
|
1205
|
+
const x = (layout.charOffsets[charIdx] ?? 0) * fontSize;
|
|
1173
1206
|
const glyph = font.glyphData[char];
|
|
1174
1207
|
if (glyph && entry.hasGlyph) {
|
|
1175
1208
|
const localTime = Math.max(0, Math.min(currentTime - entry.offset, entry.duration));
|
|
1176
|
-
const glyphY = y + halfLeading;
|
|
1177
1209
|
drawGlyph(ctx, glyph, {
|
|
1178
1210
|
x,
|
|
1179
|
-
y:
|
|
1211
|
+
y: y + halfLeading,
|
|
1180
1212
|
fontSize,
|
|
1181
1213
|
unitsPerEm: font.unitsPerEm,
|
|
1182
1214
|
ascender: font.ascender,
|
|
1183
1215
|
descender: font.descender
|
|
1184
1216
|
}, localTime, font.lineCap, color, this._resolvedEffects, this._seed + charIdx, this._segmentSize);
|
|
1185
|
-
} else if (!entry.hasGlyph && currentTime >= entry.offset + entry.duration)
|
|
1186
|
-
const baseline = y + halfLeading + font.ascender / font.unitsPerEm * fontSize;
|
|
1187
|
-
drawFallbackGlyph(ctx, char, x, baseline, fontSize, font.family, color, this._resolvedEffects, this._seed + charIdx);
|
|
1188
|
-
}
|
|
1189
|
-
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);
|
|
1190
1218
|
}
|
|
1191
1219
|
y += lineHeight;
|
|
1192
1220
|
}
|
|
1193
1221
|
}
|
|
1194
1222
|
};
|
|
1195
1223
|
//#endregion
|
|
1196
|
-
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 };
|
|
1197
1225
|
|
|
1198
|
-
//# sourceMappingURL=core-
|
|
1226
|
+
//# sourceMappingURL=core-I9K3LqxK.mjs.map
|