tegaki 0.18.0 → 0.20.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 +23 -0
- package/FONTS-LICENSE.md +6 -0
- package/README.md +22 -4
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +233 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +3 -2
- package/dist/{core-MwnsJAbN.mjs → core-VIBhSqFM.mjs} +88 -650
- package/dist/core-VIBhSqFM.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/nanum-pen-script/bundle.d.mts +7144 -0
- package/dist/fonts/nanum-pen-script/bundle.mjs +161041 -0
- package/dist/fonts/nanum-pen-script/bundle.mjs.map +1 -0
- package/dist/fonts/nanum-pen-script/nanum-pen-script-38efadb5.ttf +0 -0
- package/dist/fonts/nanum-pen-script/nanum-pen-script.ttf +0 -0
- 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-B2rqZbJt.d.mts → index-BmhPyEv9.d.mts} +4 -5
- package/dist/{index-BVYJFRMq.d.mts → index-D-lHv505.d.mts} +71 -3
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +4 -3
- package/dist/react/index.d.mts +4 -4
- package/dist/react/index.mjs +4 -3
- package/dist/{react-174En_ya.mjs → react-wE5kRPvk.mjs} +4 -2
- package/dist/{react-174En_ya.mjs.map → react-wE5kRPvk.mjs.map} +1 -1
- package/dist/shaper-harfbuzz/index.d.mts +1 -1
- package/dist/shaper-harfbuzz/index.mjs +1 -1
- package/dist/shaper-harfbuzz/index.mjs.map +1 -1
- package/dist/{shaper-registry-CrfNiB1j.d.mts → shaper-registry-DmlJGnE3.d.mts} +1 -1
- package/dist/textToSvg-GTQMM8Le.mjs +906 -0
- package/dist/textToSvg-GTQMM8Le.mjs.map +1 -0
- package/dist/wc/index.d.mts +3 -3
- package/dist/wc/index.mjs +3 -2
- package/dist/wc/index.mjs.map +1 -1
- package/fonts/nanum-pen-script/bundle.ts +20 -0
- package/fonts/nanum-pen-script/glyphData.json +1 -0
- package/fonts/nanum-pen-script/nanum-pen-script-38efadb5.ttf +0 -0
- package/fonts/nanum-pen-script/nanum-pen-script.ttf +0 -0
- package/package.json +31 -20
- package/src/cli.ts +293 -0
- package/src/core/engine.ts +95 -0
- package/src/core/index.ts +1 -0
- package/src/lib/svgExport.ts +290 -0
- package/src/lib/textToSvg.test.ts +85 -0
- package/src/lib/textToSvg.ts +162 -0
- package/dist/core-MwnsJAbN.mjs.map +0 -1
- package/dist/solid/index.d.mts +0 -20
- package/dist/solid/index.mjs +0 -91
- package/dist/solid/index.mjs.map +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { a as CSS_PROGRESS, c as MIN_PADDING_V_EM, f as cssFontFamily, g as subdivideStroke, h as resolveCSSLength, i as CSS_DURATION, l as PADDING_H_EM, m as lookupGlyphData, n as computeTimeline, o as CSS_TIME, p as graphemes, r as placementsToSvg, s as MIN_LINE_HEIGHT_EM, u as registerCssProperties } from "./textToSvg-GTQMM8Le.mjs";
|
|
1
2
|
//#region src/lib/effects.ts
|
|
2
3
|
const defaultEffects = { pressureWidth: true };
|
|
3
4
|
const knownEffects = {
|
|
@@ -85,211 +86,6 @@ function hasRenderHooks(effects) {
|
|
|
85
86
|
return false;
|
|
86
87
|
}
|
|
87
88
|
//#endregion
|
|
88
|
-
//#region src/lib/catmullRom.ts
|
|
89
|
-
/**
|
|
90
|
-
* Sample `count` points along a centripetal Catmull-Rom segment from `p1` to
|
|
91
|
-
* `p2`, with neighbor control points `p0` and `p3`. Samples are emitted at
|
|
92
|
-
* `u = k/count` for `k = 1..count`, so the last sample equals `p2`.
|
|
93
|
-
*
|
|
94
|
-
* Centripetal parameterization (α = 0.5) avoids the cusps and self-loops that
|
|
95
|
-
* uniform/chordal Catmull-Rom can produce on sharp corners — relevant for the
|
|
96
|
-
* baked RDP-simplified polylines the renderer consumes.
|
|
97
|
-
*
|
|
98
|
-
* For endpoint segments where a neighbor is missing, pass a phantom point
|
|
99
|
-
* built with {@link reflect}. Zero-length chords are clamped to a tiny epsilon
|
|
100
|
-
* so the knot parameterization stays non-degenerate.
|
|
101
|
-
*/
|
|
102
|
-
function sampleCatmullRom(p0, p1, p2, p3, count) {
|
|
103
|
-
const d01 = Math.max(dist(p0, p1), 1e-6);
|
|
104
|
-
const d12 = Math.max(dist(p1, p2), 1e-6);
|
|
105
|
-
const d23 = Math.max(dist(p2, p3), 1e-6);
|
|
106
|
-
const t0 = 0;
|
|
107
|
-
const t1 = t0 + Math.sqrt(d01);
|
|
108
|
-
const t2 = t1 + Math.sqrt(d12);
|
|
109
|
-
const t3 = t2 + Math.sqrt(d23);
|
|
110
|
-
const out = new Array(count);
|
|
111
|
-
for (let k = 1; k <= count; k++) {
|
|
112
|
-
const t = t1 + k / count * (t2 - t1);
|
|
113
|
-
out[k - 1] = evalBarryGoldman(p0, p1, p2, p3, t0, t1, t2, t3, t);
|
|
114
|
-
}
|
|
115
|
-
return out;
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Reflect `p` across `anchor` to produce a phantom neighbor for endpoint
|
|
119
|
-
* segments. The result lies on the extension of (p, anchor) past `anchor` at
|
|
120
|
-
* the same distance — equivalent to a zero-curvature extrapolation, which
|
|
121
|
-
* gives a natural straight start/end tangent.
|
|
122
|
-
*/
|
|
123
|
-
function reflect(anchor, p) {
|
|
124
|
-
return [
|
|
125
|
-
2 * anchor[0] - p[0],
|
|
126
|
-
2 * anchor[1] - p[1],
|
|
127
|
-
anchor[2]
|
|
128
|
-
];
|
|
129
|
-
}
|
|
130
|
-
function dist(a, b) {
|
|
131
|
-
const dx = b[0] - a[0];
|
|
132
|
-
const dy = b[1] - a[1];
|
|
133
|
-
return Math.sqrt(dx * dx + dy * dy);
|
|
134
|
-
}
|
|
135
|
-
function evalBarryGoldman(p0, p1, p2, p3, t0, t1, t2, t3, t) {
|
|
136
|
-
const a1x = lerp(p0[0], p1[0], t0, t1, t);
|
|
137
|
-
const a1y = lerp(p0[1], p1[1], t0, t1, t);
|
|
138
|
-
const a1w = lerp(p0[2], p1[2], t0, t1, t);
|
|
139
|
-
const a2x = lerp(p1[0], p2[0], t1, t2, t);
|
|
140
|
-
const a2y = lerp(p1[1], p2[1], t1, t2, t);
|
|
141
|
-
const a2w = lerp(p1[2], p2[2], t1, t2, t);
|
|
142
|
-
const a3x = lerp(p2[0], p3[0], t2, t3, t);
|
|
143
|
-
const a3y = lerp(p2[1], p3[1], t2, t3, t);
|
|
144
|
-
const a3w = lerp(p2[2], p3[2], t2, t3, t);
|
|
145
|
-
const b1x = lerp(a1x, a2x, t0, t2, t);
|
|
146
|
-
const b1y = lerp(a1y, a2y, t0, t2, t);
|
|
147
|
-
const b1w = lerp(a1w, a2w, t0, t2, t);
|
|
148
|
-
const b2x = lerp(a2x, a3x, t1, t3, t);
|
|
149
|
-
const b2y = lerp(a2y, a3y, t1, t3, t);
|
|
150
|
-
const b2w = lerp(a2w, a3w, t1, t3, t);
|
|
151
|
-
return {
|
|
152
|
-
x: lerp(b1x, b2x, t1, t2, t),
|
|
153
|
-
y: lerp(b1y, b2y, t1, t2, t),
|
|
154
|
-
width: lerp(b1w, b2w, t1, t2, t)
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
function lerp(a, b, ta, tb, t) {
|
|
158
|
-
const span = tb - ta;
|
|
159
|
-
if (span === 0) return a;
|
|
160
|
-
return a + (b - a) * ((t - ta) / span);
|
|
161
|
-
}
|
|
162
|
-
//#endregion
|
|
163
|
-
//#region src/lib/strokeCache.ts
|
|
164
|
-
/**
|
|
165
|
-
* Subdivide a stroke so that no sub-segment exceeds `maxSegLen` font units.
|
|
166
|
-
* Pass `Infinity` (or any non-finite value) to skip subdivision and return
|
|
167
|
-
* the raw polyline.
|
|
168
|
-
*
|
|
169
|
-
* When `smoothing` is true, intermediate vertices are placed on a centripetal
|
|
170
|
-
* Catmull-Rom spline through the original points (see `catmullRom.ts`) —
|
|
171
|
-
* hiding the polyline facets that show up at large render sizes. The original
|
|
172
|
-
* points remain on the curve, so endpoints and `cumLen`/`idx` semantics are
|
|
173
|
-
* preserved. Has no effect when `maxSegLen` is non-finite (no subdivision).
|
|
174
|
-
*
|
|
175
|
-
* Output depends only on `(stroke.p, maxSegLen, smoothing)` — not on position,
|
|
176
|
-
* seed, progress, or effect config — so it can be cached and shared across
|
|
177
|
-
* every instance of the same glyph at the same font size.
|
|
178
|
-
*/
|
|
179
|
-
function subdivideStroke(stroke, maxSegLen, smoothing = false) {
|
|
180
|
-
const pts = stroke.p;
|
|
181
|
-
const n = pts.length;
|
|
182
|
-
if (n === 0) return {
|
|
183
|
-
vertices: [],
|
|
184
|
-
totalLen: 0,
|
|
185
|
-
avgWidth: 0
|
|
186
|
-
};
|
|
187
|
-
const first = pts[0];
|
|
188
|
-
const vertices = [{
|
|
189
|
-
x: first[0],
|
|
190
|
-
y: first[1],
|
|
191
|
-
width: first[2],
|
|
192
|
-
cumLen: 0,
|
|
193
|
-
idx: 0
|
|
194
|
-
}];
|
|
195
|
-
let cumLen = 0;
|
|
196
|
-
for (let j = 1; j < n; j++) {
|
|
197
|
-
const prev = pts[j - 1];
|
|
198
|
-
const cur = pts[j];
|
|
199
|
-
const dx = cur[0] - prev[0];
|
|
200
|
-
const dy = cur[1] - prev[1];
|
|
201
|
-
const chordLen = Math.sqrt(dx * dx + dy * dy);
|
|
202
|
-
const count = chordLen > 0 && Number.isFinite(maxSegLen) && maxSegLen > 0 ? Math.max(1, Math.ceil(chordLen / maxSegLen)) : 1;
|
|
203
|
-
if (smoothing && count > 1) {
|
|
204
|
-
const samples = sampleCatmullRom(j >= 2 ? pts[j - 2] : reflect(prev, cur), prev, cur, j + 1 < n ? pts[j + 1] : reflect(cur, prev), count);
|
|
205
|
-
let px = prev[0];
|
|
206
|
-
let py = prev[1];
|
|
207
|
-
let segAccum = 0;
|
|
208
|
-
for (let k = 0; k < count; k++) {
|
|
209
|
-
const s = samples[k];
|
|
210
|
-
const ex = s.x - px;
|
|
211
|
-
const ey = s.y - py;
|
|
212
|
-
segAccum += Math.sqrt(ex * ex + ey * ey);
|
|
213
|
-
vertices.push({
|
|
214
|
-
x: s.x,
|
|
215
|
-
y: s.y,
|
|
216
|
-
width: s.width,
|
|
217
|
-
cumLen: cumLen + segAccum,
|
|
218
|
-
idx: j - 1 + (k + 1) / count
|
|
219
|
-
});
|
|
220
|
-
px = s.x;
|
|
221
|
-
py = s.y;
|
|
222
|
-
}
|
|
223
|
-
cumLen += segAccum;
|
|
224
|
-
} else {
|
|
225
|
-
const dw = cur[2] - prev[2];
|
|
226
|
-
for (let k = 1; k <= count; k++) {
|
|
227
|
-
const t = k / count;
|
|
228
|
-
vertices.push({
|
|
229
|
-
x: prev[0] + dx * t,
|
|
230
|
-
y: prev[1] + dy * t,
|
|
231
|
-
width: prev[2] + dw * t,
|
|
232
|
-
cumLen: cumLen + chordLen * t,
|
|
233
|
-
idx: j - 1 + t
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
cumLen += chordLen;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
let widthSum = 0;
|
|
240
|
-
for (const p of pts) widthSum += p[2];
|
|
241
|
-
return {
|
|
242
|
-
vertices,
|
|
243
|
-
totalLen: cumLen,
|
|
244
|
-
avgWidth: widthSum / n
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
//#endregion
|
|
248
|
-
//#region src/lib/utils.ts
|
|
249
|
-
const segmenter = typeof Intl !== "undefined" && typeof Intl.Segmenter === "function" ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
|
|
250
|
-
/** Resolve a CSSLength to pixels. Plain numbers are px, `"Nem"` is N * fontSize. */
|
|
251
|
-
function resolveCSSLength(value, fontSize) {
|
|
252
|
-
if (typeof value === "number") return value;
|
|
253
|
-
return parseFloat(value) * fontSize;
|
|
254
|
-
}
|
|
255
|
-
function graphemes(text) {
|
|
256
|
-
if (segmenter) return Array.from(segmenter.segment(text), (s) => s.segment);
|
|
257
|
-
return Array.from(text);
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Build the CSS `font-family` value for a bundle, including the full
|
|
261
|
-
* (non-subsetted) family as fallback when the bundle was generated from a subset.
|
|
262
|
-
*/
|
|
263
|
-
function cssFontFamily(bundle) {
|
|
264
|
-
if (bundle.fullFamily) return `'${bundle.family}', '${bundle.fullFamily}'`;
|
|
265
|
-
return `'${bundle.family}'`;
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Look up `glyphData` for a grapheme cluster, with a fallback to its leading
|
|
269
|
-
* codepoint. Devanagari clusters like `"हि"` or `"न्दी"` have no entry in the
|
|
270
|
-
* single-codepoint-keyed `glyphData`; without the fallback every shaped glyph
|
|
271
|
-
* inside such a cluster that the variant map skipped (i.e. nominal forms like
|
|
272
|
-
* the bare `ह`) would resolve to `undefined`, get tagged `hasGlyph: false`,
|
|
273
|
-
* and collapse onto the 0.2s `unknownDuration` slot — drawn at end-of-slot
|
|
274
|
-
* via DOM `fillText`. The fallback resolves the cluster's first codepoint
|
|
275
|
-
* (e.g. `ह` from `"हि"`), so the nominal glyph picks up its real stroke data
|
|
276
|
-
* and animates instead of popping in.
|
|
277
|
-
*/
|
|
278
|
-
function lookupGlyphData(font, char) {
|
|
279
|
-
const direct = font.glyphData[char];
|
|
280
|
-
if (direct || char.length <= 1) return direct;
|
|
281
|
-
const cp = char.codePointAt(0);
|
|
282
|
-
if (cp === void 0) return void 0;
|
|
283
|
-
return font.glyphData[String.fromCodePoint(cp)];
|
|
284
|
-
}
|
|
285
|
-
function coerceToString(value) {
|
|
286
|
-
if (value == null || typeof value === "boolean") return "";
|
|
287
|
-
if (typeof value === "string") return value;
|
|
288
|
-
if (typeof value === "number" || typeof value === "bigint") return String(value);
|
|
289
|
-
if (Array.isArray(value)) return value.map(coerceToString).join("");
|
|
290
|
-
return "";
|
|
291
|
-
}
|
|
292
|
-
//#endregion
|
|
293
89
|
//#region src/lib/drawGlyph.ts
|
|
294
90
|
function parseColor(color) {
|
|
295
91
|
const h = color.replace("#", "");
|
|
@@ -565,7 +361,7 @@ function drawGlyph(ctx, glyph, pos, localTime, lineCap, color, effects = [], see
|
|
|
565
361
|
* explicit enables. (Explicit *disables* are fine — `-fina` suppressing the
|
|
566
362
|
* automatic fina is exactly the user's intent.)
|
|
567
363
|
*/
|
|
568
|
-
const SHAPER_MANAGED_FEATURES = new Set([
|
|
364
|
+
const SHAPER_MANAGED_FEATURES = /* @__PURE__ */ new Set([
|
|
569
365
|
"init",
|
|
570
366
|
"medi",
|
|
571
367
|
"fina",
|
|
@@ -869,422 +665,6 @@ function measureWithTempElement(text, fontFamily, fontSize, lineHeight, maxWidth
|
|
|
869
665
|
return result;
|
|
870
666
|
}
|
|
871
667
|
//#endregion
|
|
872
|
-
//#region src/lib/timeline.ts
|
|
873
|
-
const DEFAULTS = {
|
|
874
|
-
glyphGap: .1,
|
|
875
|
-
wordGap: .15,
|
|
876
|
-
lineGap: .3,
|
|
877
|
-
unknownDuration: .2,
|
|
878
|
-
deferDots: true
|
|
879
|
-
};
|
|
880
|
-
function computeTimeline(text, font, config, shaper) {
|
|
881
|
-
if (shaper && font.glyphDataById) return computeShapedTimeline(text, font, config, shaper);
|
|
882
|
-
return computeGraphemeTimeline(text, font, config);
|
|
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
|
-
};
|
|
957
|
-
/** Decompose a glyph into a body phase and an optional dot phase. */
|
|
958
|
-
function partitionGlyph(glyph, fallbackTotal, deferDots) {
|
|
959
|
-
const strokes = glyph.s;
|
|
960
|
-
let bodyDuration = 0;
|
|
961
|
-
let dotMinD = Infinity;
|
|
962
|
-
let dotMaxEnd = 0;
|
|
963
|
-
const dotIndices = [];
|
|
964
|
-
const dotDelays = [];
|
|
965
|
-
for (let i = 0; i < strokes.length; i++) {
|
|
966
|
-
const s = strokes[i];
|
|
967
|
-
const end = s.d + s.a;
|
|
968
|
-
if (deferDots && (s.r ?? 0) < 0) {
|
|
969
|
-
dotIndices.push(i);
|
|
970
|
-
dotDelays.push(s.d);
|
|
971
|
-
if (s.d < dotMinD) dotMinD = s.d;
|
|
972
|
-
if (end > dotMaxEnd) dotMaxEnd = end;
|
|
973
|
-
} else if (end > bodyDuration) bodyDuration = end;
|
|
974
|
-
}
|
|
975
|
-
if (dotIndices.length === 0) return {
|
|
976
|
-
bodyDuration: glyph.t ?? fallbackTotal,
|
|
977
|
-
dotDuration: 0,
|
|
978
|
-
dotMinD: 0,
|
|
979
|
-
dotIndices,
|
|
980
|
-
dotDelays
|
|
981
|
-
};
|
|
982
|
-
if (bodyDuration === 0) return {
|
|
983
|
-
bodyDuration: glyph.t ?? fallbackTotal,
|
|
984
|
-
dotDuration: 0,
|
|
985
|
-
dotMinD: 0,
|
|
986
|
-
dotIndices: [],
|
|
987
|
-
dotDelays: []
|
|
988
|
-
};
|
|
989
|
-
return {
|
|
990
|
-
bodyDuration,
|
|
991
|
-
dotDuration: dotMaxEnd - dotMinD,
|
|
992
|
-
dotMinD,
|
|
993
|
-
dotIndices,
|
|
994
|
-
dotDelays
|
|
995
|
-
};
|
|
996
|
-
}
|
|
997
|
-
var Scheduler = class {
|
|
998
|
-
glyphGap;
|
|
999
|
-
wordGap;
|
|
1000
|
-
lineGap;
|
|
1001
|
-
entries = [];
|
|
1002
|
-
group = [];
|
|
1003
|
-
offset = 0;
|
|
1004
|
-
/** Gap appended by the last `separate` call; stripped on `finalize` when no further content followed. */
|
|
1005
|
-
lastGap = 0;
|
|
1006
|
-
constructor(glyphGap, wordGap, lineGap) {
|
|
1007
|
-
this.glyphGap = glyphGap;
|
|
1008
|
-
this.wordGap = wordGap;
|
|
1009
|
-
this.lineGap = lineGap;
|
|
1010
|
-
}
|
|
1011
|
-
add(p) {
|
|
1012
|
-
this.lastGap = 0;
|
|
1013
|
-
this.group.push(p);
|
|
1014
|
-
}
|
|
1015
|
-
/**
|
|
1016
|
-
* Close the current word group and advance by a separator gap. Optionally
|
|
1017
|
-
* emits a zero-width marker entry (e.g. a whitespace grapheme / cluster)
|
|
1018
|
-
* between the group end and the gap.
|
|
1019
|
-
*/
|
|
1020
|
-
separate(sep, marker) {
|
|
1021
|
-
this.flushGroup();
|
|
1022
|
-
if (marker) {
|
|
1023
|
-
this.entries.push({
|
|
1024
|
-
...marker.fields,
|
|
1025
|
-
offset: this.offset,
|
|
1026
|
-
duration: marker.duration
|
|
1027
|
-
});
|
|
1028
|
-
this.offset += marker.duration;
|
|
1029
|
-
}
|
|
1030
|
-
const gap = sep === "line" ? this.lineGap : this.wordGap;
|
|
1031
|
-
this.offset += gap;
|
|
1032
|
-
this.lastGap = gap;
|
|
1033
|
-
}
|
|
1034
|
-
finalize() {
|
|
1035
|
-
this.flushGroup();
|
|
1036
|
-
return {
|
|
1037
|
-
entries: this.entries,
|
|
1038
|
-
totalDuration: Math.max(0, this.offset - this.lastGap)
|
|
1039
|
-
};
|
|
1040
|
-
}
|
|
1041
|
-
flushGroup() {
|
|
1042
|
-
if (this.group.length === 0) return;
|
|
1043
|
-
const group = this.group;
|
|
1044
|
-
const bodyStarts = new Array(group.length);
|
|
1045
|
-
let cursor = this.offset;
|
|
1046
|
-
for (let i = 0; i < group.length; i++) {
|
|
1047
|
-
bodyStarts[i] = cursor;
|
|
1048
|
-
cursor += group[i].bodyDuration;
|
|
1049
|
-
if (i < group.length - 1) cursor += this.glyphGap;
|
|
1050
|
-
}
|
|
1051
|
-
const bodyEnd = cursor;
|
|
1052
|
-
const hasAnyDots = group.some((p) => p.dotIndices.length > 0);
|
|
1053
|
-
const dotStarts = new Array(group.length);
|
|
1054
|
-
let groupEnd = bodyEnd;
|
|
1055
|
-
if (hasAnyDots) {
|
|
1056
|
-
cursor = bodyEnd + this.glyphGap;
|
|
1057
|
-
for (let i = 0; i < group.length; i++) {
|
|
1058
|
-
const p = group[i];
|
|
1059
|
-
if (p.dotIndices.length === 0) continue;
|
|
1060
|
-
dotStarts[i] = cursor;
|
|
1061
|
-
cursor += p.dotDuration + this.glyphGap;
|
|
1062
|
-
}
|
|
1063
|
-
groupEnd = cursor - this.glyphGap;
|
|
1064
|
-
}
|
|
1065
|
-
for (let i = 0; i < group.length; i++) {
|
|
1066
|
-
const p = group[i];
|
|
1067
|
-
const bodyStart = bodyStarts[i];
|
|
1068
|
-
const dotStart = dotStarts[i];
|
|
1069
|
-
let strokeDelays;
|
|
1070
|
-
let endTime = bodyStart + p.bodyDuration;
|
|
1071
|
-
if (dotStart !== void 0 && p.dotIndices.length > 0) {
|
|
1072
|
-
strokeDelays = [];
|
|
1073
|
-
for (let k = 0; k < p.dotIndices.length; k++) {
|
|
1074
|
-
const strokeIdx = p.dotIndices[k];
|
|
1075
|
-
strokeDelays[strokeIdx] = dotStart - bodyStart + (p.dotDelays[k] - p.dotMinD);
|
|
1076
|
-
}
|
|
1077
|
-
endTime = dotStart + p.dotDuration;
|
|
1078
|
-
}
|
|
1079
|
-
this.entries.push({
|
|
1080
|
-
...p.fields,
|
|
1081
|
-
offset: bodyStart,
|
|
1082
|
-
duration: endTime - bodyStart,
|
|
1083
|
-
...strokeDelays ? { strokeDelays } : {}
|
|
1084
|
-
});
|
|
1085
|
-
}
|
|
1086
|
-
this.group.length = 0;
|
|
1087
|
-
this.offset = groupEnd;
|
|
1088
|
-
this.lastGap = 0;
|
|
1089
|
-
}
|
|
1090
|
-
};
|
|
1091
|
-
function computeGraphemeTimeline(text, font, config) {
|
|
1092
|
-
const glyphGap = config?.glyphGap ?? DEFAULTS.glyphGap;
|
|
1093
|
-
const wordGap = config?.wordGap ?? DEFAULTS.wordGap;
|
|
1094
|
-
const lineGap = config?.lineGap ?? DEFAULTS.lineGap;
|
|
1095
|
-
const unknownDuration = config?.unknownDuration ?? DEFAULTS.unknownDuration;
|
|
1096
|
-
const deferDots = config?.deferDots ?? DEFAULTS.deferDots;
|
|
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
|
-
}
|
|
1132
|
-
const sched = new Scheduler(glyphGap, wordGap, lineGap);
|
|
1133
|
-
for (let i = 0; i < chars.length; i++) {
|
|
1134
|
-
const char = chars[i];
|
|
1135
|
-
const isLineBreak = char === "\n";
|
|
1136
|
-
const isWhitespace = !isLineBreak && /^\s+$/.test(char);
|
|
1137
|
-
if (isLineBreak) {
|
|
1138
|
-
sched.separate("line");
|
|
1139
|
-
continue;
|
|
1140
|
-
}
|
|
1141
|
-
if (isWhitespace) {
|
|
1142
|
-
sched.separate("word", {
|
|
1143
|
-
fields: {
|
|
1144
|
-
char,
|
|
1145
|
-
graphemeIndex: i,
|
|
1146
|
-
hasGlyph: false
|
|
1147
|
-
},
|
|
1148
|
-
duration: 0
|
|
1149
|
-
});
|
|
1150
|
-
continue;
|
|
1151
|
-
}
|
|
1152
|
-
const glyph = lookupGlyphData(font, char);
|
|
1153
|
-
if (glyph) {
|
|
1154
|
-
const part = partitionGlyph(glyph, unknownDuration, deferDots);
|
|
1155
|
-
sched.add({
|
|
1156
|
-
fields: {
|
|
1157
|
-
char,
|
|
1158
|
-
graphemeIndex: i,
|
|
1159
|
-
hasGlyph: true
|
|
1160
|
-
},
|
|
1161
|
-
bodyDuration: part.bodyDuration,
|
|
1162
|
-
dotDuration: part.dotDuration,
|
|
1163
|
-
dotMinD: part.dotMinD,
|
|
1164
|
-
dotIndices: part.dotIndices,
|
|
1165
|
-
dotDelays: part.dotDelays
|
|
1166
|
-
});
|
|
1167
|
-
} else sched.add({
|
|
1168
|
-
fields: {
|
|
1169
|
-
char,
|
|
1170
|
-
graphemeIndex: i,
|
|
1171
|
-
hasGlyph: false
|
|
1172
|
-
},
|
|
1173
|
-
bodyDuration: unknownDuration,
|
|
1174
|
-
dotDuration: 0,
|
|
1175
|
-
dotMinD: 0,
|
|
1176
|
-
dotIndices: [],
|
|
1177
|
-
dotDelays: []
|
|
1178
|
-
});
|
|
1179
|
-
}
|
|
1180
|
-
return sched.finalize();
|
|
1181
|
-
}
|
|
1182
|
-
function computeShapedTimeline(text, font, config, shaper) {
|
|
1183
|
-
const glyphGap = config?.glyphGap ?? DEFAULTS.glyphGap;
|
|
1184
|
-
const wordGap = config?.wordGap ?? DEFAULTS.wordGap;
|
|
1185
|
-
const lineGap = config?.lineGap ?? DEFAULTS.lineGap;
|
|
1186
|
-
const unknownDuration = config?.unknownDuration ?? DEFAULTS.unknownDuration;
|
|
1187
|
-
const deferDots = config?.deferDots ?? DEFAULTS.deferDots;
|
|
1188
|
-
const chars = graphemes(text);
|
|
1189
|
-
const utf16ToGrapheme = new Int32Array(text.length + 1).fill(-1);
|
|
1190
|
-
{
|
|
1191
|
-
let u = 0;
|
|
1192
|
-
for (let i = 0; i < chars.length; i++) {
|
|
1193
|
-
utf16ToGrapheme[u] = i;
|
|
1194
|
-
u += chars[i].length;
|
|
1195
|
-
}
|
|
1196
|
-
utf16ToGrapheme[text.length] = chars.length;
|
|
1197
|
-
}
|
|
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);
|
|
1200
|
-
let lineStart = 0;
|
|
1201
|
-
for (let i = 0; i <= text.length; i++) {
|
|
1202
|
-
const atEnd = i === text.length;
|
|
1203
|
-
if (!atEnd && text[i] !== "\n") continue;
|
|
1204
|
-
const lineText = text.slice(lineStart, i);
|
|
1205
|
-
if (lineText.length > 0) {
|
|
1206
|
-
const shaped = shaper.shape(lineText);
|
|
1207
|
-
const order = shaped.map((_, idx) => idx);
|
|
1208
|
-
order.sort((a, b) => shaped[a].cl - shaped[b].cl || a - b);
|
|
1209
|
-
for (let k = 0; k < order.length; k++) {
|
|
1210
|
-
const glyph = shaped[order[k]];
|
|
1211
|
-
const clusterStart = lineStart + glyph.cl;
|
|
1212
|
-
const nextOrder = k + 1 < order.length ? order[k + 1] : -1;
|
|
1213
|
-
const clusterEnd = nextOrder >= 0 ? lineStart + shaped[nextOrder].cl : i;
|
|
1214
|
-
const graphemeIdx = utf16ToGrapheme[clusterStart] ?? -1;
|
|
1215
|
-
if (graphemeIdx < 0) continue;
|
|
1216
|
-
const clusterText = text.slice(clusterStart, clusterEnd);
|
|
1217
|
-
const firstChar = chars[graphemeIdx];
|
|
1218
|
-
const isWhitespace = /^\s+$/.test(clusterText);
|
|
1219
|
-
const data = font.glyphDataById?.[glyph.g] ?? lookupGlyphData(font, firstChar);
|
|
1220
|
-
const hasGlyph = !!data;
|
|
1221
|
-
if (isWhitespace) {
|
|
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", {
|
|
1231
|
-
fields: {
|
|
1232
|
-
char: firstChar,
|
|
1233
|
-
graphemeIndex: graphemeIdx,
|
|
1234
|
-
glyphId: glyph.g,
|
|
1235
|
-
hasGlyph
|
|
1236
|
-
},
|
|
1237
|
-
duration: 0
|
|
1238
|
-
});
|
|
1239
|
-
continue;
|
|
1240
|
-
}
|
|
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) {
|
|
1250
|
-
const part = partitionGlyph(data, unknownDuration, deferDots);
|
|
1251
|
-
sched.add({
|
|
1252
|
-
fields: {
|
|
1253
|
-
char: firstChar,
|
|
1254
|
-
graphemeIndex: graphemeIdx,
|
|
1255
|
-
glyphId: glyph.g,
|
|
1256
|
-
hasGlyph: true
|
|
1257
|
-
},
|
|
1258
|
-
bodyDuration: part.bodyDuration,
|
|
1259
|
-
dotDuration: part.dotDuration,
|
|
1260
|
-
dotMinD: part.dotMinD,
|
|
1261
|
-
dotIndices: part.dotIndices,
|
|
1262
|
-
dotDelays: part.dotDelays
|
|
1263
|
-
});
|
|
1264
|
-
} else sched.add({
|
|
1265
|
-
fields: {
|
|
1266
|
-
char: firstChar,
|
|
1267
|
-
graphemeIndex: graphemeIdx,
|
|
1268
|
-
glyphId: glyph.g,
|
|
1269
|
-
hasGlyph: false
|
|
1270
|
-
},
|
|
1271
|
-
bodyDuration: unknownDuration,
|
|
1272
|
-
dotDuration: 0,
|
|
1273
|
-
dotMinD: 0,
|
|
1274
|
-
dotIndices: [],
|
|
1275
|
-
dotDelays: []
|
|
1276
|
-
});
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
if (!atEnd) {
|
|
1280
|
-
if (staggerSched) staggerSched.separator("line");
|
|
1281
|
-
else sched.separate("line");
|
|
1282
|
-
lineStart = i + 1;
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
return staggerSched ? staggerSched.finalize() : sched.finalize();
|
|
1286
|
-
}
|
|
1287
|
-
//#endregion
|
|
1288
668
|
//#region src/types.ts
|
|
1289
669
|
/**
|
|
1290
670
|
* Current bundle format version. Incremented when the bundle format changes
|
|
@@ -1296,7 +676,7 @@ const BUNDLE_VERSION = 0;
|
|
|
1296
676
|
* console warning (once per bundle) when it encounters a version outside
|
|
1297
677
|
* this set.
|
|
1298
678
|
*/
|
|
1299
|
-
const COMPATIBLE_BUNDLE_VERSIONS = new Set([0]);
|
|
679
|
+
const COMPATIBLE_BUNDLE_VERSIONS = /* @__PURE__ */ new Set([0]);
|
|
1300
680
|
//#endregion
|
|
1301
681
|
//#region src/core/bundle-registry.ts
|
|
1302
682
|
const bundles = /* @__PURE__ */ new Map();
|
|
@@ -1362,31 +742,6 @@ function createBundle({ family, fullFamily, fontUrl, fullFontUrl, glyphData, lin
|
|
|
1362
742
|
};
|
|
1363
743
|
}
|
|
1364
744
|
//#endregion
|
|
1365
|
-
//#region src/lib/css-properties.ts
|
|
1366
|
-
const CSS_TIME = "--tegaki-time";
|
|
1367
|
-
const CSS_PROGRESS = "--tegaki-progress";
|
|
1368
|
-
const CSS_DURATION = "--tegaki-duration";
|
|
1369
|
-
const PADDING_H_EM = .2;
|
|
1370
|
-
const MIN_LINE_HEIGHT_EM = 1.8;
|
|
1371
|
-
const MIN_PADDING_V_EM = .2;
|
|
1372
|
-
let cssPropertiesRegistered = false;
|
|
1373
|
-
function registerCssProperties() {
|
|
1374
|
-
if (cssPropertiesRegistered) return;
|
|
1375
|
-
cssPropertiesRegistered = true;
|
|
1376
|
-
if (typeof CSS !== "undefined" && "registerProperty" in CSS) for (const prop of [
|
|
1377
|
-
CSS_TIME,
|
|
1378
|
-
CSS_PROGRESS,
|
|
1379
|
-
CSS_DURATION
|
|
1380
|
-
]) try {
|
|
1381
|
-
CSS.registerProperty({
|
|
1382
|
-
name: prop,
|
|
1383
|
-
syntax: "<number>",
|
|
1384
|
-
inherits: true,
|
|
1385
|
-
initialValue: "0"
|
|
1386
|
-
});
|
|
1387
|
-
} catch {}
|
|
1388
|
-
}
|
|
1389
|
-
//#endregion
|
|
1390
745
|
//#region src/lib/drawFallbackGlyph.ts
|
|
1391
746
|
/**
|
|
1392
747
|
* Draw a fallback glyph (plain text) with applicable effects (glow, strokeGradient, wobble).
|
|
@@ -1745,6 +1100,89 @@ var TegakiEngine = class {
|
|
|
1745
1100
|
get element() {
|
|
1746
1101
|
return this._rootEl;
|
|
1747
1102
|
}
|
|
1103
|
+
/**
|
|
1104
|
+
* The backing `<canvas>` strokes are drawn onto. Exposed so export tooling
|
|
1105
|
+
* can snapshot the current frame (PNG) or sample it over time (WebM/GIF)
|
|
1106
|
+
* without reaching through the DOM. Reflects the latest `_render()`.
|
|
1107
|
+
*/
|
|
1108
|
+
get canvas() {
|
|
1109
|
+
return this._canvasEl;
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Serialize the current text to an SVG string, reusing the engine's measured
|
|
1113
|
+
* layout and timeline so glyph positions match the canvas render exactly.
|
|
1114
|
+
*
|
|
1115
|
+
* `animated: true` (default) emits a self-drawing SVG — each stroke is
|
|
1116
|
+
* revealed through a dashed-centerline mask over its own timeline window, so
|
|
1117
|
+
* the file draws itself when opened. `animated: false` emits the finished
|
|
1118
|
+
* artwork (every stroke fully drawn).
|
|
1119
|
+
*
|
|
1120
|
+
* `loop: true` emits a looping CSS-keyframe animation (constant width) that
|
|
1121
|
+
* draws, holds, fades, and repeats forever — reliable in `<img>`-embedded
|
|
1122
|
+
* SVGs (e.g. a README hero). Implies `animated`.
|
|
1123
|
+
*
|
|
1124
|
+
* Variable stroke width (pressureWidth) is honoured in single-play mode (not
|
|
1125
|
+
* `loop`). Glow, wobble, gradient, taper, and clip-to-text are not modelled
|
|
1126
|
+
* in the SVG output.
|
|
1127
|
+
*/
|
|
1128
|
+
toSVG(opts = {}) {
|
|
1129
|
+
const font = this._font;
|
|
1130
|
+
const layout = this._layout;
|
|
1131
|
+
const fontSize = this._fontSize;
|
|
1132
|
+
const canvas = this._canvasEl;
|
|
1133
|
+
const width = canvas.offsetWidth;
|
|
1134
|
+
const height = canvas.offsetHeight;
|
|
1135
|
+
if (!font?.glyphData || !layout || !fontSize) return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}"></svg>`;
|
|
1136
|
+
const padH = PADDING_H_EM * fontSize;
|
|
1137
|
+
const lineHeight = this._lineHeight;
|
|
1138
|
+
const padV = Math.max(MIN_PADDING_V_EM * fontSize, (MIN_LINE_HEIGHT_EM * fontSize - lineHeight) / 2);
|
|
1139
|
+
const halfLeading = (lineHeight - (font.ascender - font.descender) / font.unitsPerEm * fontSize) / 2;
|
|
1140
|
+
const scale = fontSize / font.unitsPerEm;
|
|
1141
|
+
const characters = graphemes(this._text);
|
|
1142
|
+
const pressureEffect = findEffect(this._resolvedEffects, "pressureWidth");
|
|
1143
|
+
const pressure = pressureEffect ? Math.max(0, Math.min(pressureEffect.config.strength ?? 1, 1)) : 0;
|
|
1144
|
+
const smoothing = this._quality?.smoothing === true;
|
|
1145
|
+
const resolvedSegmentSize = this._quality?.segmentSize ?? (pressure > 0 || smoothing ? 2 : void 0);
|
|
1146
|
+
const segmentLengthFU = resolvedSegmentSize != null ? resolvedSegmentSize / scale : Infinity;
|
|
1147
|
+
const clipText = this._quality?.clipText;
|
|
1148
|
+
const strokeScale = typeof clipText === "number" ? clipText : 1;
|
|
1149
|
+
const graphemeToLine = new Int32Array(characters.length).fill(-1);
|
|
1150
|
+
for (let li = 0; li < layout.lines.length; li++) for (const charIdx of layout.lines[li]) graphemeToLine[charIdx] = li;
|
|
1151
|
+
const placements = [];
|
|
1152
|
+
for (const entry of this._timeline.entries) {
|
|
1153
|
+
if (entry.char === "\n" || !entry.hasGlyph) continue;
|
|
1154
|
+
const charIdx = entry.graphemeIndex;
|
|
1155
|
+
const lineIdx = graphemeToLine[charIdx] ?? -1;
|
|
1156
|
+
if (lineIdx < 0) continue;
|
|
1157
|
+
const glyph = (entry.glyphId !== void 0 ? font.glyphDataById?.[entry.glyphId] : void 0) ?? lookupGlyphData(font, entry.char);
|
|
1158
|
+
if (!glyph) continue;
|
|
1159
|
+
const y = lineIdx * lineHeight;
|
|
1160
|
+
const lineLeftEm = layout.lineLefts?.[lineIdx];
|
|
1161
|
+
const x = entry.xOffsetEm !== void 0 && lineLeftEm !== void 0 ? (lineLeftEm + entry.xOffsetEm) * fontSize : (layout.charOffsets[charIdx] ?? 0) * fontSize;
|
|
1162
|
+
const glyphY = y + halfLeading + (entry.yOffsetEm ?? 0) * fontSize;
|
|
1163
|
+
placements.push({
|
|
1164
|
+
glyph,
|
|
1165
|
+
ox: padH + x,
|
|
1166
|
+
oy: padV + glyphY,
|
|
1167
|
+
scale,
|
|
1168
|
+
ascender: font.ascender,
|
|
1169
|
+
offset: entry.offset
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
return placementsToSvg(placements, {
|
|
1173
|
+
width,
|
|
1174
|
+
height,
|
|
1175
|
+
lineCap: font.lineCap,
|
|
1176
|
+
color: this._currentColor || "black",
|
|
1177
|
+
pressure,
|
|
1178
|
+
segmentLengthFU,
|
|
1179
|
+
smoothing,
|
|
1180
|
+
strokeScale,
|
|
1181
|
+
animated: opts.animated ?? true,
|
|
1182
|
+
loop: opts.loop ?? false,
|
|
1183
|
+
totalDuration: this._timeline.totalDuration
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1748
1186
|
play() {
|
|
1749
1187
|
if (this._timeControl.mode !== "uncontrolled") return;
|
|
1750
1188
|
this._playing = true;
|
|
@@ -2295,6 +1733,6 @@ var TegakiEngine = class {
|
|
|
2295
1733
|
}
|
|
2296
1734
|
};
|
|
2297
1735
|
//#endregion
|
|
2298
|
-
export {
|
|
1736
|
+
export { getEffectDefinition as _, createBundle as a, resolveBundle as c, computeLayoutBbox as d, computeTextLayout as f, findEffects as g, findEffect as h, domCreateElement as i, BUNDLE_VERSION as l, drawGlyph as m, buildChildren as n, getBundle as o, ensureFontFace as p, buildRootProps as r, registerBundle as s, TegakiEngine as t, COMPATIBLE_BUNDLE_VERSIONS as u, hasRenderHooks as v, resolveEffects as y };
|
|
2299
1737
|
|
|
2300
|
-
//# sourceMappingURL=core-
|
|
1738
|
+
//# sourceMappingURL=core-VIBhSqFM.mjs.map
|