tegaki 0.17.0 → 0.18.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 +18 -0
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +1 -1
- package/dist/{core-BrU5Htjs.mjs → core-MwnsJAbN.mjs} +179 -12
- package/dist/core-MwnsJAbN.mjs.map +1 -0
- package/dist/fonts/amiri/bundle.mjs.map +1 -1
- package/dist/fonts/caveat/bundle.mjs.map +1 -1
- package/dist/fonts/italianno/bundle.mjs.map +1 -1
- package/dist/fonts/klee-one/bundle.mjs.map +1 -1
- package/dist/fonts/parisienne/bundle.mjs.map +1 -1
- package/dist/fonts/suez-one/bundle.mjs.map +1 -1
- package/dist/fonts/tangerine/bundle.mjs.map +1 -1
- package/dist/fonts/tillana/bundle.mjs.map +1 -1
- package/dist/{index-JT-cQ_gO.d.mts → index-B2rqZbJt.d.mts} +3 -3
- package/dist/{index-vDGB0qjx.d.mts → index-BVYJFRMq.d.mts} +58 -6
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +2 -2
- package/dist/react/index.d.mts +4 -4
- package/dist/react/index.mjs +2 -2
- package/dist/{react-DBKcqp07.mjs → react-174En_ya.mjs} +2 -2
- package/dist/{react-DBKcqp07.mjs.map → react-174En_ya.mjs.map} +1 -1
- package/dist/shaper-harfbuzz/index.d.mts +1 -1
- package/dist/shaper-harfbuzz/index.mjs +25 -34
- package/dist/shaper-harfbuzz/index.mjs.map +1 -1
- package/dist/{shaper-registry-DVS5R37Q.d.mts → shaper-registry-CrfNiB1j.d.mts} +2 -2
- package/dist/solid/index.d.mts +3 -3
- package/dist/solid/index.mjs +1 -1
- package/dist/solid/index.mjs.map +1 -1
- package/dist/wc/index.d.mts +4 -4
- package/dist/wc/index.mjs +3 -2
- package/dist/wc/index.mjs.map +1 -1
- package/package.json +7 -7
- package/src/astro/TegakiRenderer.astro +31 -5
- package/src/core/bundle-registry.ts +3 -0
- package/src/core/engine.ts +35 -3
- package/src/core/index.ts +7 -1
- package/src/core/types.ts +3 -1
- package/src/env.d.ts +2 -0
- package/src/lib/drawGlyph.ts +12 -2
- package/src/lib/font.ts +1 -1
- package/src/lib/timeline.test.ts +173 -0
- package/src/lib/timeline.ts +179 -10
- package/src/nuxt/module.ts +0 -3
- package/src/shaper-harfbuzz/index.ts +24 -42
- package/src/types.ts +1 -1
- package/src/vue/TegakiRenderer.vue +2 -2
- package/src/vue/index.ts +1 -0
- package/src/wc/TegakiElement.ts +5 -2
- package/dist/core-BrU5Htjs.mjs.map +0 -1
- package/src/lib/harfbuzzjs.d.ts +0 -71
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# tegaki
|
|
2
2
|
|
|
3
|
+
## 0.18.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- b6e883b: Update Harfbuzz to v1.1.0
|
|
8
|
+
- d358cbd: Add `stagger` timing mode where each glyph starts a fixed advance (seconds or `"N%"` of the previous glyph's effective duration) after the previous one, with an optional static per-glyph duration that scales strokes to fit. Exposed in the website previewer via the new `st` / `sa` / `sd` URL keys.
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- 150296e: Fix the Astro adapter so passing `font={bundle}` hydrates without an explicit `bundle` prop and lookups by human-friendly font name resolve correctly. Animations now also re-hydrate after Astro View Transitions navigations.
|
|
13
|
+
- 150296e: Make the Vue adapter's `effects` prop generic so custom effect configs are correctly type-inferred. Also drop a redundant Nuxt config type augmentation.
|
|
14
|
+
|
|
15
|
+
## 0.17.1
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- affe5a5: Add shorthand for setting time property to a percentage for controlled progress mode.
|
|
20
|
+
|
|
3
21
|
## 0.17.0
|
|
4
22
|
|
|
5
23
|
### Minor Changes
|
package/dist/core/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { S as TimedPoint, _ as TegakiEffectName, a as BBox, b as TegakiMultiEffectName, c as CSSLength, d as LineCap, f as PathCommand, g as TegakiEffectConfigs, h as TegakiBundle, i as ShapedGlyph, l as FontOutput, m as Stroke, o as BUNDLE_VERSION, p as Point, r as BundleShaper, s as COMPATIBLE_BUNDLE_VERSIONS, t as ShaperFactory, u as GlyphData, v as TegakiEffects, x as TegakiSingletonEffectName, y as TegakiGlyphData } from "../shaper-registry-
|
|
2
|
-
import { A as TimelineEntry, C as resolveEffects, D as computeTextLayout, E as computeLayoutBbox, O as Timeline, S as hasRenderHooks, T as TextLayout, _ as RenderStageContext, a as CreateElementFn, b as findEffects, c as TimeControlMode, d as getBundle, f as registerBundle, g as EffectDefinition, h as drawGlyph, i as TegakiEngine, j as
|
|
3
|
-
export { BBox, BUNDLE_VERSION, BundleShaper, COMPATIBLE_BUNDLE_VERSIONS, CSSLength, CreateElementFn, EffectDefinition, FontOutput, GlyphData, LayoutBBox, LineCap, PathCommand, Point, RenderStageContext, ResolvedEffect, ShapedGlyph, ShaperFactory, Stroke, TegakiBundle, TegakiEffectConfigs, TegakiEffectName, TegakiEffects, TegakiEngine, TegakiEngineOptions, TegakiGlyphData, TegakiMultiEffectName, TegakiQuality, TegakiSingletonEffectName, TextLayout, TimeControlMode, TimeControlProp, TimedPoint, Timeline, TimelineConfig, TimelineEntry, buildChildren, buildRootProps, computeLayoutBbox, computeTextLayout, computeTimeline, createBundle, domCreateElement, drawGlyph, ensureFontFace, findEffect, findEffects, getBundle, getEffectDefinition, hasRenderHooks, registerBundle, resolveBundle, resolveEffects };
|
|
1
|
+
import { S as TimedPoint, _ as TegakiEffectName, a as BBox, b as TegakiMultiEffectName, c as CSSLength, d as LineCap, f as PathCommand, g as TegakiEffectConfigs, h as TegakiBundle, i as ShapedGlyph, l as FontOutput, m as Stroke, o as BUNDLE_VERSION, p as Point, r as BundleShaper, s as COMPATIBLE_BUNDLE_VERSIONS, t as ShaperFactory, u as GlyphData, v as TegakiEffects, x as TegakiSingletonEffectName, y as TegakiGlyphData } from "../shaper-registry-CrfNiB1j.mjs";
|
|
2
|
+
import { A as TimelineEntry, C as resolveEffects, D as computeTextLayout, E as computeLayoutBbox, M as computeTimeline, O as Timeline, S as hasRenderHooks, T as TextLayout, _ as RenderStageContext, a as CreateElementFn, b as findEffects, c as TimeControlMode, d as getBundle, f as registerBundle, g as EffectDefinition, h as drawGlyph, i as TegakiEngine, j as TimelineStaggerConfig, k as TimelineConfig, l as TimeControlProp, m as ensureFontFace, n as buildRootProps, o as TegakiEngineOptions, p as resolveBundle, r as domCreateElement, s as TegakiQuality, t as buildChildren, u as createBundle, v as ResolvedEffect, w as LayoutBBox, x as getEffectDefinition, y as findEffect } from "../index-BVYJFRMq.mjs";
|
|
3
|
+
export { BBox, BUNDLE_VERSION, BundleShaper, COMPATIBLE_BUNDLE_VERSIONS, CSSLength, CreateElementFn, EffectDefinition, FontOutput, GlyphData, LayoutBBox, LineCap, PathCommand, Point, RenderStageContext, ResolvedEffect, ShapedGlyph, ShaperFactory, Stroke, TegakiBundle, TegakiEffectConfigs, TegakiEffectName, TegakiEffects, TegakiEngine, TegakiEngineOptions, TegakiGlyphData, TegakiMultiEffectName, TegakiQuality, TegakiSingletonEffectName, TextLayout, TimeControlMode, TimeControlProp, TimedPoint, Timeline, TimelineConfig, TimelineEntry, TimelineStaggerConfig, buildChildren, buildRootProps, computeLayoutBbox, computeTextLayout, computeTimeline, createBundle, domCreateElement, drawGlyph, ensureFontFace, findEffect, findEffects, getBundle, getEffectDefinition, hasRenderHooks, registerBundle, resolveBundle, resolveEffects };
|
package/dist/core/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as findEffect, a as createBundle, b as hasRenderHooks, c as resolveBundle, d as computeTimeline, f as computeLayoutBbox, h as drawGlyph, i as domCreateElement, l as BUNDLE_VERSION, m as ensureFontFace, n as buildChildren, o as getBundle, p as computeTextLayout, r as buildRootProps, s as registerBundle, t as TegakiEngine, u as COMPATIBLE_BUNDLE_VERSIONS, v as findEffects, x as resolveEffects, y as getEffectDefinition } from "../core-
|
|
1
|
+
import { _ as findEffect, a as createBundle, b as hasRenderHooks, c as resolveBundle, d as computeTimeline, f as computeLayoutBbox, h as drawGlyph, i as domCreateElement, l as BUNDLE_VERSION, m as ensureFontFace, n as buildChildren, o as getBundle, p as computeTextLayout, r as buildRootProps, s as registerBundle, t as TegakiEngine, u as COMPATIBLE_BUNDLE_VERSIONS, v as findEffects, x as resolveEffects, y as getEffectDefinition } from "../core-MwnsJAbN.mjs";
|
|
2
2
|
export { BUNDLE_VERSION, COMPATIBLE_BUNDLE_VERSIONS, TegakiEngine, buildChildren, buildRootProps, computeLayoutBbox, computeTextLayout, computeTimeline, createBundle, domCreateElement, drawGlyph, ensureFontFace, findEffect, findEffects, getBundle, getEffectDefinition, hasRenderHooks, registerBundle, resolveBundle, resolveEffects };
|
|
@@ -368,8 +368,13 @@ function defaultStrokeEasing(t) {
|
|
|
368
368
|
* delay relative to `localTime = 0`. Used by the timeline scheduler to defer
|
|
369
369
|
* priority-tagged strokes (disconnected marks / i-dots / Arabic nuqṭa) to
|
|
370
370
|
* after every body stroke in the word has drawn.
|
|
371
|
+
*
|
|
372
|
+
* `strokeTimeScale` multiplies both bundled `d` and `a` so a glyph's strokes
|
|
373
|
+
* fit a stretched/compressed time slot (used by stagger mode with a static
|
|
374
|
+
* `duration`). Defaults to `1` (no scaling). `strokeDelays` are already
|
|
375
|
+
* scheduler-relative seconds and are not affected by this scale.
|
|
371
376
|
*/
|
|
372
|
-
function drawGlyph(ctx, glyph, pos, localTime, lineCap, color, effects = [], seed = 0, getSubdivided, strokeEasing = defaultStrokeEasing, strokeScale = 1, strokeStyleOverride, strokeDelays) {
|
|
377
|
+
function drawGlyph(ctx, glyph, pos, localTime, lineCap, color, effects = [], seed = 0, getSubdivided, strokeEasing = defaultStrokeEasing, strokeScale = 1, strokeStyleOverride, strokeDelays, strokeTimeScale = 1) {
|
|
373
378
|
const defaultStrokePaint = strokeStyleOverride ?? color;
|
|
374
379
|
const scale = pos.fontSize / pos.unitsPerEm;
|
|
375
380
|
const ox = pos.x;
|
|
@@ -419,10 +424,11 @@ function drawGlyph(ctx, glyph, pos, localTime, lineCap, color, effects = [], see
|
|
|
419
424
|
};
|
|
420
425
|
for (let si = 0; si < glyph.s.length; si++) {
|
|
421
426
|
const stroke = glyph.s[si];
|
|
422
|
-
const delay = strokeDelays?.[si] ?? stroke.d;
|
|
427
|
+
const delay = strokeDelays?.[si] ?? stroke.d * strokeTimeScale;
|
|
423
428
|
if (localTime < delay) continue;
|
|
424
429
|
const elapsed = localTime - delay;
|
|
425
|
-
const
|
|
430
|
+
const animDuration = stroke.a * strokeTimeScale;
|
|
431
|
+
const linearProgress = animDuration > 0 ? Math.min(elapsed / animDuration, 1) : 1;
|
|
426
432
|
const progress = strokeEasing ? strokeEasing(linearProgress) : linearProgress;
|
|
427
433
|
const rawPts = stroke.p;
|
|
428
434
|
if (rawPts.length === 0) continue;
|
|
@@ -875,6 +881,79 @@ function computeTimeline(text, font, config, shaper) {
|
|
|
875
881
|
if (shaper && font.glyphDataById) return computeShapedTimeline(text, font, config, shaper);
|
|
876
882
|
return computeGraphemeTimeline(text, font, config);
|
|
877
883
|
}
|
|
884
|
+
/** Parse a stagger advance value into seconds, given the previous glyph's bundled duration. */
|
|
885
|
+
function resolveAdvance(advance, prevBundled) {
|
|
886
|
+
if (typeof advance === "number") return Math.max(0, advance);
|
|
887
|
+
const m = /^(-?\d+(?:\.\d+)?)\s*%$/.exec(advance);
|
|
888
|
+
if (m) {
|
|
889
|
+
const pct = Number(m[1]) / 100;
|
|
890
|
+
return Math.max(0, pct * prevBundled);
|
|
891
|
+
}
|
|
892
|
+
const n = Number(advance);
|
|
893
|
+
return Number.isFinite(n) ? Math.max(0, n) : 0;
|
|
894
|
+
}
|
|
895
|
+
var StaggerScheduler = class {
|
|
896
|
+
wordGap;
|
|
897
|
+
lineGap;
|
|
898
|
+
advance;
|
|
899
|
+
staticDuration;
|
|
900
|
+
entries = [];
|
|
901
|
+
offset = 0;
|
|
902
|
+
/**
|
|
903
|
+
* Effective duration of the most recent glyph (= `staticDuration` when set,
|
|
904
|
+
* else its bundled `glyph.t`). Basis for percent advances — so
|
|
905
|
+
* `advance: '100%'` always means "start once the previous glyph finishes",
|
|
906
|
+
* independent of whether the duration was overridden.
|
|
907
|
+
*/
|
|
908
|
+
prevEffective = 0;
|
|
909
|
+
hasPrev = false;
|
|
910
|
+
/** Accumulated word/line gap pending until the next glyph (or finalize). */
|
|
911
|
+
pendingGap = 0;
|
|
912
|
+
constructor(wordGap, lineGap, advance, staticDuration) {
|
|
913
|
+
this.wordGap = wordGap;
|
|
914
|
+
this.lineGap = lineGap;
|
|
915
|
+
this.advance = advance;
|
|
916
|
+
this.staticDuration = staticDuration;
|
|
917
|
+
}
|
|
918
|
+
addGlyph(fields, bundledDuration) {
|
|
919
|
+
if (this.hasPrev) {
|
|
920
|
+
this.offset += resolveAdvance(this.advance, this.prevEffective) + this.pendingGap;
|
|
921
|
+
this.pendingGap = 0;
|
|
922
|
+
}
|
|
923
|
+
const duration = this.staticDuration ?? bundledDuration;
|
|
924
|
+
const strokeTimeScale = bundledDuration > 0 ? duration / bundledDuration : 1;
|
|
925
|
+
this.entries.push({
|
|
926
|
+
...fields,
|
|
927
|
+
offset: this.offset,
|
|
928
|
+
duration,
|
|
929
|
+
...strokeTimeScale !== 1 ? { strokeTimeScale } : {}
|
|
930
|
+
});
|
|
931
|
+
this.prevEffective = duration;
|
|
932
|
+
this.hasPrev = true;
|
|
933
|
+
}
|
|
934
|
+
/** Emit a zero-duration marker (e.g. whitespace) at the current offset without advancing the cursor. */
|
|
935
|
+
addMarker(fields) {
|
|
936
|
+
this.entries.push({
|
|
937
|
+
...fields,
|
|
938
|
+
offset: this.offset,
|
|
939
|
+
duration: 0
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
separator(sep) {
|
|
943
|
+
this.pendingGap += sep === "line" ? this.lineGap : this.wordGap;
|
|
944
|
+
}
|
|
945
|
+
finalize() {
|
|
946
|
+
let total = 0;
|
|
947
|
+
for (const e of this.entries) {
|
|
948
|
+
const end = e.offset + e.duration;
|
|
949
|
+
if (end > total) total = end;
|
|
950
|
+
}
|
|
951
|
+
return {
|
|
952
|
+
entries: this.entries,
|
|
953
|
+
totalDuration: total
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
};
|
|
878
957
|
/** Decompose a glyph into a body phase and an optional dot phase. */
|
|
879
958
|
function partitionGlyph(glyph, fallbackTotal, deferDots) {
|
|
880
959
|
const strokes = glyph.s;
|
|
@@ -916,6 +995,9 @@ function partitionGlyph(glyph, fallbackTotal, deferDots) {
|
|
|
916
995
|
};
|
|
917
996
|
}
|
|
918
997
|
var Scheduler = class {
|
|
998
|
+
glyphGap;
|
|
999
|
+
wordGap;
|
|
1000
|
+
lineGap;
|
|
919
1001
|
entries = [];
|
|
920
1002
|
group = [];
|
|
921
1003
|
offset = 0;
|
|
@@ -1013,6 +1095,40 @@ function computeGraphemeTimeline(text, font, config) {
|
|
|
1013
1095
|
const unknownDuration = config?.unknownDuration ?? DEFAULTS.unknownDuration;
|
|
1014
1096
|
const deferDots = config?.deferDots ?? DEFAULTS.deferDots;
|
|
1015
1097
|
const chars = graphemes(text);
|
|
1098
|
+
if (config?.stagger) {
|
|
1099
|
+
const staticDur = config.stagger.duration === "auto" || config.stagger.duration === void 0 ? void 0 : config.stagger.duration;
|
|
1100
|
+
const sched = new StaggerScheduler(wordGap, lineGap, config.stagger.advance, staticDur);
|
|
1101
|
+
for (let i = 0; i < chars.length; i++) {
|
|
1102
|
+
const char = chars[i];
|
|
1103
|
+
const isLineBreak = char === "\n";
|
|
1104
|
+
const isWhitespace = !isLineBreak && /^\s+$/.test(char);
|
|
1105
|
+
if (isLineBreak) {
|
|
1106
|
+
sched.separator("line");
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
if (isWhitespace) {
|
|
1110
|
+
sched.addMarker({
|
|
1111
|
+
char,
|
|
1112
|
+
graphemeIndex: i,
|
|
1113
|
+
hasGlyph: false
|
|
1114
|
+
});
|
|
1115
|
+
sched.separator("word");
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
const glyph = lookupGlyphData(font, char);
|
|
1119
|
+
if (glyph) sched.addGlyph({
|
|
1120
|
+
char,
|
|
1121
|
+
graphemeIndex: i,
|
|
1122
|
+
hasGlyph: true
|
|
1123
|
+
}, glyph.t ?? unknownDuration);
|
|
1124
|
+
else sched.addGlyph({
|
|
1125
|
+
char,
|
|
1126
|
+
graphemeIndex: i,
|
|
1127
|
+
hasGlyph: false
|
|
1128
|
+
}, unknownDuration);
|
|
1129
|
+
}
|
|
1130
|
+
return sched.finalize();
|
|
1131
|
+
}
|
|
1016
1132
|
const sched = new Scheduler(glyphGap, wordGap, lineGap);
|
|
1017
1133
|
for (let i = 0; i < chars.length; i++) {
|
|
1018
1134
|
const char = chars[i];
|
|
@@ -1079,7 +1195,8 @@ function computeShapedTimeline(text, font, config, shaper) {
|
|
|
1079
1195
|
}
|
|
1080
1196
|
utf16ToGrapheme[text.length] = chars.length;
|
|
1081
1197
|
}
|
|
1082
|
-
const
|
|
1198
|
+
const staggerSched = config?.stagger ? new StaggerScheduler(wordGap, lineGap, config.stagger.advance, config.stagger.duration === "auto" || config.stagger.duration === void 0 ? void 0 : config.stagger.duration) : null;
|
|
1199
|
+
const sched = staggerSched ? null : new Scheduler(glyphGap, wordGap, lineGap);
|
|
1083
1200
|
let lineStart = 0;
|
|
1084
1201
|
for (let i = 0; i <= text.length; i++) {
|
|
1085
1202
|
const atEnd = i === text.length;
|
|
@@ -1102,7 +1219,15 @@ function computeShapedTimeline(text, font, config, shaper) {
|
|
|
1102
1219
|
const data = font.glyphDataById?.[glyph.g] ?? lookupGlyphData(font, firstChar);
|
|
1103
1220
|
const hasGlyph = !!data;
|
|
1104
1221
|
if (isWhitespace) {
|
|
1105
|
-
|
|
1222
|
+
if (staggerSched) {
|
|
1223
|
+
staggerSched.addMarker({
|
|
1224
|
+
char: firstChar,
|
|
1225
|
+
graphemeIndex: graphemeIdx,
|
|
1226
|
+
glyphId: glyph.g,
|
|
1227
|
+
hasGlyph
|
|
1228
|
+
});
|
|
1229
|
+
staggerSched.separator("word");
|
|
1230
|
+
} else sched.separate("word", {
|
|
1106
1231
|
fields: {
|
|
1107
1232
|
char: firstChar,
|
|
1108
1233
|
graphemeIndex: graphemeIdx,
|
|
@@ -1113,7 +1238,15 @@ function computeShapedTimeline(text, font, config, shaper) {
|
|
|
1113
1238
|
});
|
|
1114
1239
|
continue;
|
|
1115
1240
|
}
|
|
1116
|
-
if (
|
|
1241
|
+
if (staggerSched) {
|
|
1242
|
+
const bundled = hasGlyph && data ? data.t ?? unknownDuration : unknownDuration;
|
|
1243
|
+
staggerSched.addGlyph({
|
|
1244
|
+
char: firstChar,
|
|
1245
|
+
graphemeIndex: graphemeIdx,
|
|
1246
|
+
glyphId: glyph.g,
|
|
1247
|
+
hasGlyph: !!(hasGlyph && data)
|
|
1248
|
+
}, bundled);
|
|
1249
|
+
} else if (hasGlyph && data) {
|
|
1117
1250
|
const part = partitionGlyph(data, unknownDuration, deferDots);
|
|
1118
1251
|
sched.add({
|
|
1119
1252
|
fields: {
|
|
@@ -1144,11 +1277,12 @@ function computeShapedTimeline(text, font, config, shaper) {
|
|
|
1144
1277
|
}
|
|
1145
1278
|
}
|
|
1146
1279
|
if (!atEnd) {
|
|
1147
|
-
|
|
1280
|
+
if (staggerSched) staggerSched.separator("line");
|
|
1281
|
+
else sched.separate("line");
|
|
1148
1282
|
lineStart = i + 1;
|
|
1149
1283
|
}
|
|
1150
1284
|
}
|
|
1151
|
-
return sched.finalize();
|
|
1285
|
+
return staggerSched ? staggerSched.finalize() : sched.finalize();
|
|
1152
1286
|
}
|
|
1153
1287
|
//#endregion
|
|
1154
1288
|
//#region src/types.ts
|
|
@@ -1178,6 +1312,7 @@ function checkBundleVersion(bundle) {
|
|
|
1178
1312
|
function registerBundle(bundle) {
|
|
1179
1313
|
checkBundleVersion(bundle);
|
|
1180
1314
|
bundles.set(bundle.family, bundle);
|
|
1315
|
+
if (bundle.fullFamily && bundle.fullFamily !== bundle.family) bundles.set(bundle.fullFamily, bundle);
|
|
1181
1316
|
}
|
|
1182
1317
|
/** Look up a registered bundle by family name. */
|
|
1183
1318
|
function getBundle(family) {
|
|
@@ -1425,13 +1560,34 @@ function getShaperForBundle(bundle) {
|
|
|
1425
1560
|
}
|
|
1426
1561
|
//#endregion
|
|
1427
1562
|
//#region src/core/engine.ts
|
|
1563
|
+
/**
|
|
1564
|
+
* Parse a percentage string like `"50%"` into a 0–1 fraction. Returns `null`
|
|
1565
|
+
* for non-percentage strings or unparseable input. Whitespace around the
|
|
1566
|
+
* value is tolerated; the numeric part is parsed with `Number(...)`, so any
|
|
1567
|
+
* finite numeric form (including negatives and decimals) is accepted.
|
|
1568
|
+
*/
|
|
1569
|
+
function parsePercentage(s) {
|
|
1570
|
+
const trimmed = s.trim();
|
|
1571
|
+
if (!trimmed.endsWith("%")) return null;
|
|
1572
|
+
const num = Number(trimmed.slice(0, -1));
|
|
1573
|
+
return Number.isFinite(num) ? num / 100 : null;
|
|
1574
|
+
}
|
|
1428
1575
|
function resolveTimeControl(prop) {
|
|
1429
1576
|
if (prop == null) return { mode: "uncontrolled" };
|
|
1430
1577
|
if (typeof prop === "number") return {
|
|
1431
1578
|
mode: "controlled",
|
|
1432
1579
|
value: prop
|
|
1433
1580
|
};
|
|
1434
|
-
if (prop === "
|
|
1581
|
+
if (typeof prop === "string") {
|
|
1582
|
+
if (prop === "css") return { mode: "css" };
|
|
1583
|
+
const pct = parsePercentage(prop);
|
|
1584
|
+
if (pct != null) return {
|
|
1585
|
+
mode: "controlled",
|
|
1586
|
+
value: pct,
|
|
1587
|
+
unit: "progress"
|
|
1588
|
+
};
|
|
1589
|
+
return { mode: "uncontrolled" };
|
|
1590
|
+
}
|
|
1435
1591
|
return prop;
|
|
1436
1592
|
}
|
|
1437
1593
|
var TegakiEngine = class {
|
|
@@ -1599,9 +1755,20 @@ var TegakiEngine = class {
|
|
|
1599
1755
|
this._playing = false;
|
|
1600
1756
|
this._evaluatePlayback();
|
|
1601
1757
|
}
|
|
1758
|
+
/**
|
|
1759
|
+
* Seek the (uncontrolled) timeline to an absolute time. Accepts seconds
|
|
1760
|
+
* (number) or a percentage string like `"50%"`, which is interpreted as
|
|
1761
|
+
* a fraction of the timeline's total duration.
|
|
1762
|
+
*/
|
|
1602
1763
|
seek(time) {
|
|
1603
1764
|
if (this._timeControl.mode !== "uncontrolled") return;
|
|
1604
|
-
|
|
1765
|
+
let resolved;
|
|
1766
|
+
if (typeof time === "string") {
|
|
1767
|
+
const pct = parsePercentage(time);
|
|
1768
|
+
if (pct == null) return;
|
|
1769
|
+
resolved = pct * this._timeline.totalDuration;
|
|
1770
|
+
} else resolved = time;
|
|
1771
|
+
this._internalTime = Math.max(0, Math.min(resolved, this._timeline.totalDuration));
|
|
1605
1772
|
this._delayRemaining = 0;
|
|
1606
1773
|
this._loopGapRemaining = 0;
|
|
1607
1774
|
this._checkCompletion();
|
|
@@ -2082,7 +2249,7 @@ var TegakiEngine = class {
|
|
|
2082
2249
|
unitsPerEm: font.unitsPerEm,
|
|
2083
2250
|
ascender: font.ascender,
|
|
2084
2251
|
descender: font.descender
|
|
2085
|
-
}, localTime, font.lineCap, color, this._resolvedEffects, this._seed + charIdx, getSubdivided, this._timing?.strokeEasing, strokeScale, stage?.strokeStyle, entry.strokeDelays);
|
|
2252
|
+
}, localTime, font.lineCap, color, this._resolvedEffects, this._seed + charIdx, getSubdivided, this._timing?.strokeEasing, strokeScale, stage?.strokeStyle, entry.strokeDelays, entry.strokeTimeScale);
|
|
2086
2253
|
} else if (!entry.hasGlyph && currentTime >= entry.offset + entry.duration) {
|
|
2087
2254
|
const baseline = y + halfLeading + font.ascender / font.unitsPerEm * fontSize;
|
|
2088
2255
|
drawFallbackGlyph(ctx, entry.char, x, baseline, fontSize, cssFontFamily(font), color, this._resolvedEffects, this._seed + charIdx);
|
|
@@ -2130,4 +2297,4 @@ var TegakiEngine = class {
|
|
|
2130
2297
|
//#endregion
|
|
2131
2298
|
export { findEffect as _, createBundle as a, hasRenderHooks as b, resolveBundle as c, computeTimeline as d, computeLayoutBbox as f, coerceToString as g, drawGlyph as h, domCreateElement as i, BUNDLE_VERSION as l, ensureFontFace as m, buildChildren as n, getBundle as o, computeTextLayout as p, buildRootProps as r, registerBundle as s, TegakiEngine as t, COMPATIBLE_BUNDLE_VERSIONS as u, findEffects as v, resolveEffects as x, getEffectDefinition as y };
|
|
2132
2299
|
|
|
2133
|
-
//# sourceMappingURL=core-
|
|
2300
|
+
//# sourceMappingURL=core-MwnsJAbN.mjs.map
|