@vosjs/studio-core 0.36.0 → 0.37.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/dist/index.js CHANGED
@@ -3239,6 +3239,169 @@ function htmlLayerPayload(clip) {
3239
3239
  };
3240
3240
  }
3241
3241
 
3242
+ // src/richText/layout.ts
3243
+ var sameStyle = (a, b) => (a.f || 0) === (b.f || 0) && a.c === b.c && a.u === b.u && a.s === b.s && a.h === b.h;
3244
+ function graphemesOf(text) {
3245
+ if (typeof Intl !== "undefined" && typeof Intl.Segmenter === "function") {
3246
+ const seg = new Intl.Segmenter(void 0, { granularity: "grapheme" });
3247
+ const out = [];
3248
+ for (const s of seg.segment(text)) out.push(s.segment);
3249
+ return out;
3250
+ }
3251
+ return Array.from(text);
3252
+ }
3253
+ function tokenRanges(line) {
3254
+ const out = [];
3255
+ const re = /\S+\s*/g;
3256
+ let m;
3257
+ while ((m = re.exec(line)) !== null) {
3258
+ out.push([m.index, m.index + m[0].length]);
3259
+ }
3260
+ return out.length ? out : [[0, line.length]];
3261
+ }
3262
+ function layoutText(input, measure) {
3263
+ const memo = {};
3264
+ const adv = (t, f) => {
3265
+ if (!t) return 0;
3266
+ const key = f + "\0" + t;
3267
+ let w = memo[key];
3268
+ if (w === void 0) {
3269
+ w = measure(t, f);
3270
+ memo[key] = w;
3271
+ }
3272
+ return w;
3273
+ };
3274
+ const slice = (runs, a, b) => {
3275
+ const out = [];
3276
+ let at = 0;
3277
+ for (let i = 0; i < runs.length; i++) {
3278
+ const r = runs[i];
3279
+ const from = Math.max(a, at);
3280
+ const to = Math.min(b, at + r.t.length);
3281
+ if (to > from) {
3282
+ const t = r.t.slice(from - at, to - at);
3283
+ const last = out[out.length - 1];
3284
+ if (last && sameStyle(last, r)) last.t += t;
3285
+ else out.push({ ...r, t });
3286
+ }
3287
+ at += r.t.length;
3288
+ if (at >= b) break;
3289
+ }
3290
+ return out;
3291
+ };
3292
+ const widthOf = (runs) => {
3293
+ let w = 0;
3294
+ for (let i = 0; i < runs.length; i++) w += adv(runs[i].t, runs[i].f || 0);
3295
+ return w;
3296
+ };
3297
+ const partsIn = (frags, a, b) => {
3298
+ const out = [];
3299
+ for (let i = 0; i < frags.length; i++) {
3300
+ const fr = frags[i];
3301
+ const from = Math.max(a, fr.o);
3302
+ const to = Math.min(b, fr.o + fr.t.length);
3303
+ if (to <= from) continue;
3304
+ const f = fr.f || 0;
3305
+ const before = adv(fr.t.slice(0, from - fr.o), f);
3306
+ out.push({
3307
+ ...fr,
3308
+ t: fr.t.slice(from - fr.o, to - fr.o),
3309
+ x: fr.x + before,
3310
+ w: adv(fr.t.slice(0, to - fr.o), f) - before,
3311
+ o: from
3312
+ });
3313
+ }
3314
+ return out;
3315
+ };
3316
+ const unitOf = (frags, a, b) => {
3317
+ const parts = partsIn(frags, a, b);
3318
+ if (!parts.length) return { x: 0, w: 0, parts };
3319
+ const last = parts[parts.length - 1];
3320
+ return { x: parts[0].x, w: last.x + last.w - parts[0].x, parts };
3321
+ };
3322
+ const lines = [];
3323
+ const max = input.maxWidth && input.maxWidth > 0 ? input.maxWidth : 0;
3324
+ let width = 0;
3325
+ let base = 0;
3326
+ const push2 = (runs, a, b) => {
3327
+ const cut = slice(runs, a, b);
3328
+ const frags = [];
3329
+ let x = 0;
3330
+ let o = 0;
3331
+ for (let i = 0; i < cut.length; i++) {
3332
+ const w = adv(cut[i].t, cut[i].f || 0);
3333
+ frags.push({ ...cut[i], x, w, o });
3334
+ x += w;
3335
+ o += cut[i].t.length;
3336
+ }
3337
+ const line = { w: x, frags, a: base + a, n: b - a };
3338
+ if (input.unit) {
3339
+ const text = cut.map((r) => r.t).join("");
3340
+ const units = [];
3341
+ if (input.unit === "line") units.push(unitOf(frags, 0, text.length));
3342
+ else if (input.unit === "word") {
3343
+ const toks = tokenRanges(text);
3344
+ for (let i = 0; i < toks.length; i++) {
3345
+ units.push(unitOf(frags, toks[i][0], toks[i][1]));
3346
+ }
3347
+ } else {
3348
+ let at = 0;
3349
+ const gs = graphemesOf(text);
3350
+ for (let i = 0; i < gs.length; i++) {
3351
+ units.push(unitOf(frags, at, at + gs[i].length));
3352
+ at += gs[i].length;
3353
+ }
3354
+ }
3355
+ line.units = units;
3356
+ }
3357
+ if (x > width) width = x;
3358
+ lines.push(line);
3359
+ };
3360
+ for (let li = 0; li < input.lines.length; li++) {
3361
+ const runs = input.lines[li];
3362
+ let len = 0;
3363
+ for (let i = 0; i < runs.length; i++) len += runs[i].t.length;
3364
+ if (!max || !len || widthOf(slice(runs, 0, len)) <= max) {
3365
+ push2(runs, 0, len);
3366
+ } else {
3367
+ const text = runs.map((r) => r.t).join("");
3368
+ const toks = tokenRanges(text);
3369
+ let a = 0;
3370
+ let b = toks[0][1];
3371
+ for (let i = 1; i < toks.length; i++) {
3372
+ if (widthOf(slice(runs, a, toks[i][1])) <= max) b = toks[i][1];
3373
+ else {
3374
+ push2(runs, a, b);
3375
+ a = toks[i][0];
3376
+ b = toks[i][1];
3377
+ }
3378
+ }
3379
+ push2(runs, a, b);
3380
+ }
3381
+ base += len + 1;
3382
+ }
3383
+ if (!lines.length) lines.push({ w: 0, frags: [], a: 0, n: 0 });
3384
+ return { lines, width };
3385
+ }
3386
+ var UNDERLINE_OFFSET_EM = 0.42;
3387
+ var DECORATION_THICKNESS_EM = 0.065;
3388
+ var STRIKE_OFFSET_EM = 0.02;
3389
+ var HIGHLIGHT_HALF_EM = 0.58;
3390
+ function decorationRect(kind, x, w, y, px) {
3391
+ const x0 = Math.round(x);
3392
+ const x1 = Math.round(x + w);
3393
+ if (kind === "h") {
3394
+ const top2 = Math.round(y - HIGHLIGHT_HALF_EM * px);
3395
+ const bottom = Math.round(y + HIGHLIGHT_HALF_EM * px);
3396
+ return { x: x0, y: top2, w: x1 - x0, h: bottom - top2 };
3397
+ }
3398
+ const t = Math.max(1, Math.round(DECORATION_THICKNESS_EM * px));
3399
+ const top = Math.round(
3400
+ y + (kind === "u" ? UNDERLINE_OFFSET_EM : STRIKE_OFFSET_EM) * px
3401
+ );
3402
+ return { x: x0, y: top, w: x1 - x0, h: t };
3403
+ }
3404
+
3242
3405
  // src/overlayText.ts
3243
3406
  var OVERLAY_BOX_PAD_X = 0.6;
3244
3407
  var OVERLAY_BOX_PAD_Y = 0.35;
@@ -3445,19 +3608,44 @@ function resolveEmphasis(clip) {
3445
3608
  } : {}
3446
3609
  };
3447
3610
  }
3448
- function measureEmphasized(text, measure, font, emFont, bold = false) {
3449
- let w = 0;
3450
- let run = "";
3451
- let b = bold;
3452
- for (const c of text) {
3453
- if (c === EM_OPEN || c === EM_CLOSE) {
3454
- if (run) w += measure(run, b ? emFont : font);
3455
- run = "";
3456
- b = c === EM_OPEN;
3457
- } else run += c;
3611
+ function hashOf(text) {
3612
+ let h = 2166136261;
3613
+ for (let i = 0; i < text.length; i++) {
3614
+ h ^= text.charCodeAt(i);
3615
+ h = Math.imul(h, 16777619);
3458
3616
  }
3459
- if (run) w += measure(run, b ? emFont : font);
3460
- return w;
3617
+ return (h >>> 0).toString(36);
3618
+ }
3619
+ function styledTextOf(clip) {
3620
+ const em = resolveEmphasis(clip);
3621
+ if (!em) return null;
3622
+ const l = overlayLines(overlayDisplayText(clip)).map((line) => {
3623
+ const runs = [];
3624
+ let run = "";
3625
+ let bold = false;
3626
+ const flush = () => {
3627
+ if (!run) return;
3628
+ runs.push(
3629
+ bold ? { t: run, f: 1, ...em.color ? { c: em.color } : {} } : { t: run }
3630
+ );
3631
+ run = "";
3632
+ };
3633
+ for (const c of line) {
3634
+ if (c === EM_OPEN || c === EM_CLOSE) {
3635
+ flush();
3636
+ bold = c === EM_OPEN;
3637
+ } else run += c;
3638
+ }
3639
+ flush();
3640
+ return runs;
3641
+ });
3642
+ const fs = [{ w: resolveOverlayStyle(clip).weight }, { w: em.weight }];
3643
+ return {
3644
+ l,
3645
+ fs,
3646
+ ...em.face ? { faces: [{ f: em.face.family, w: em.face.weight, u: em.face.url }] } : {},
3647
+ h: hashOf(JSON.stringify([l, fs]))
3648
+ };
3461
3649
  }
3462
3650
  function overlayTokens(line) {
3463
3651
  return line.match(/\S+\s*/g) ?? [line];
@@ -3487,7 +3675,7 @@ function wrapOverlayLines(lines, measure, maxPx) {
3487
3675
  }
3488
3676
  return out.length ? out : [""];
3489
3677
  }
3490
- function graphemesOf(line) {
3678
+ function graphemesOf2(line) {
3491
3679
  if (typeof Intl !== "undefined" && typeof Intl.Segmenter === "function") {
3492
3680
  return [
3493
3681
  ...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(
@@ -3502,14 +3690,14 @@ function overlaySegments(text, unit) {
3502
3690
  if (unit === "block") return [];
3503
3691
  if (unit === "line") return lines.map((l) => [l]);
3504
3692
  if (unit === "word") return lines.map(overlayTokens);
3505
- return lines.map(graphemesOf);
3693
+ return lines.map(graphemesOf2);
3506
3694
  }
3507
3695
  var FX_DIR = { forward: 0, reverse: 1, center: 2 };
3508
3696
  function resolveOverlayFx(clip, clipDuration) {
3509
3697
  const spec = overlayFxSpec(clip) ?? clip.fx ?? null;
3510
3698
  if (!spec) return null;
3511
3699
  const unit = spec.unit ?? "block";
3512
- const text = overlayDisplayText(clip);
3700
+ const text = stripEmphasis(overlayDisplayText(clip));
3513
3701
  const units = overlaySegments(text, unit);
3514
3702
  const n = unit === "block" ? 1 : units.reduce((sum, line) => sum + line.length, 0);
3515
3703
  const upperN = unit === "line" && clip.maxWidth ? overlayLines(text).reduce(
@@ -3559,13 +3747,25 @@ function overlayRect(clip, measure, frameW, frameH = 1080, mediaAspect) {
3559
3747
  const style = resolveOverlayStyle(clip);
3560
3748
  const font = overlayFontString(style, scale, 1);
3561
3749
  const ls = style.letterSpacing * scale;
3562
- const em = resolveEmphasis(clip);
3563
- const emFont = em ? overlayFontString({ ...style, weight: em.weight }, scale, 1) : "";
3564
- const width = (t) => em ? measureEmphasized(t, (run, f) => measure(run, f, ls), font, emFont) : measure(t, font, ls);
3565
- const text = overlayDisplayText(clip);
3566
- const lines = clip.maxWidth ? wrapOverlayLines(overlayLines(text), width, clip.maxWidth * frameW) : overlayLines(text);
3750
+ const styled = styledTextOf(clip);
3567
3751
  let w = 0;
3568
- for (const line of lines) w = Math.max(w, width(line));
3752
+ let lineCount = 0;
3753
+ if (styled) {
3754
+ const fonts = styled.fs.map(
3755
+ (f) => overlayFontString({ ...style, weight: f.w }, scale, 1)
3756
+ );
3757
+ const laid = layoutText(
3758
+ { lines: styled.l, maxWidth: clip.maxWidth ? clip.maxWidth * frameW : 0 },
3759
+ (t, f) => measure(t, fonts[f], ls)
3760
+ );
3761
+ w = laid.width;
3762
+ lineCount = laid.lines.length;
3763
+ } else {
3764
+ const width = (t) => measure(t, font, ls);
3765
+ const lines = clip.maxWidth ? wrapOverlayLines(overlayLines(clip.text), width, clip.maxWidth * frameW) : overlayLines(clip.text);
3766
+ for (const line of lines) w = Math.max(w, width(line));
3767
+ lineCount = lines.length;
3768
+ }
3569
3769
  const lineH = style.size * scale * style.lineHeight;
3570
3770
  const box = resolveOverlayBox(clip);
3571
3771
  const padX = box ? box.padX * scale : 0;
@@ -3574,7 +3774,7 @@ function overlayRect(clip, measure, frameW, frameH = 1080, mediaAspect) {
3574
3774
  ...base,
3575
3775
  w: Math.max(w, style.size * scale * 0.6) + padX * 2,
3576
3776
  // empty text still selectable
3577
- h: lines.length * lineH + padY * 2
3777
+ h: lineCount * lineH + padY * 2
3578
3778
  };
3579
3779
  }
3580
3780
  function overlayHit(rect, px, py, padPx = 8) {
@@ -4317,6 +4517,11 @@ function push(keys, t, a) {
4317
4517
 
4318
4518
  // src/lower/studioEntry.ts
4319
4519
  import { timelineRuntimeCode } from "@vosjs/timeline/bundle";
4520
+
4521
+ // src/richText/layoutCode.ts
4522
+ var textLayoutCode = '"use strict";var __vosTextLayout=(()=>{var E=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var N=Object.prototype.hasOwnProperty;var W=(n,r)=>{for(var h in r)E(n,h,{get:r[h],enumerable:!0})},j=(n,r,h,l)=>{if(r&&typeof r=="object"||typeof r=="function")for(let f of A(r))!N.call(n,f)&&f!==h&&E(n,f,{get:()=>r[f],enumerable:!(l=k(r,f))||l.enumerable});return n};var C=n=>j(E({},"__esModule",{value:!0}),n);var v={};W(v,{DECORATION_THICKNESS_EM:()=>U,HIGHLIGHT_HALF_EM:()=>I,STRIKE_OFFSET_EM:()=>H,UNDERLINE_OFFSET_EM:()=>O,decorationRect:()=>K,graphemesOf:()=>_,layoutText:()=>G,tokenRanges:()=>F});var D=(n,r)=>(n.f||0)===(r.f||0)&&n.c===r.c&&n.u===r.u&&n.s===r.s&&n.h===r.h;function _(n){if(typeof Intl<"u"&&typeof Intl.Segmenter=="function"){let r=new Intl.Segmenter(void 0,{granularity:"grapheme"}),h=[];for(let l of r.segment(n))h.push(l.segment);return h}return Array.from(n)}function F(n){let r=[],h=/\\S+\\s*/g,l;for(;(l=h.exec(n))!==null;)r.push([l.index,l.index+l[0].length]);return r.length?r:[[0,n.length]]}function G(n,r){let h={},l=(i,u)=>{if(!i)return 0;let s=u+"\\0"+i,t=h[s];return t===void 0&&(t=r(i,u),h[s]=t),t},f=(i,u,s)=>{let t=[],o=0;for(let e=0;e<i.length;e++){let c=i[e],a=Math.max(u,o),m=Math.min(s,o+c.t.length);if(m>a){let g=c.t.slice(a-o,m-o),x=t[t.length-1];x&&D(x,c)?x.t+=g:t.push({...c,t:g})}if(o+=c.t.length,o>=s)break}return t},p=i=>{let u=0;for(let s=0;s<i.length;s++)u+=l(i[s].t,i[s].f||0);return u},R=(i,u,s)=>{let t=[];for(let o=0;o<i.length;o++){let e=i[o],c=Math.max(u,e.o),a=Math.min(s,e.o+e.t.length);if(a<=c)continue;let m=e.f||0,g=l(e.t.slice(0,c-e.o),m);t.push({...e,t:e.t.slice(c-e.o,a-e.o),x:e.x+g,w:l(e.t.slice(0,a-e.o),m)-g,o:c})}return t},L=(i,u,s)=>{let t=R(i,u,s);if(!t.length)return{x:0,w:0,parts:t};let o=t[t.length-1];return{x:t[0].x,w:o.x+o.w-t[0].x,parts:t}},y=[],d=n.maxWidth&&n.maxWidth>0?n.maxWidth:0,w=0,T=0,S=(i,u,s)=>{let t=f(i,u,s),o=[],e=0,c=0;for(let m=0;m<t.length;m++){let g=l(t[m].t,t[m].f||0);o.push({...t[m],x:e,w:g,o:c}),e+=g,c+=t[m].t.length}let a={w:e,frags:o,a:T+u,n:s-u};if(n.unit){let m=t.map(x=>x.t).join(""),g=[];if(n.unit==="line")g.push(L(o,0,m.length));else if(n.unit==="word"){let x=F(m);for(let b=0;b<x.length;b++)g.push(L(o,x[b][0],x[b][1]))}else{let x=0,b=_(m);for(let M=0;M<b.length;M++)g.push(L(o,x,x+b[M].length)),x+=b[M].length}a.units=g}e>w&&(w=e),y.push(a)};for(let i=0;i<n.lines.length;i++){let u=n.lines[i],s=0;for(let t=0;t<u.length;t++)s+=u[t].t.length;if(!d||!s||p(f(u,0,s))<=d)S(u,0,s);else{let t=u.map(a=>a.t).join(""),o=F(t),e=0,c=o[0][1];for(let a=1;a<o.length;a++)p(f(u,e,o[a][1]))<=d||(S(u,e,c),e=o[a][0]),c=o[a][1];S(u,e,c)}T+=s+1}return y.length||y.push({w:0,frags:[],a:0,n:0}),{lines:y,width:w}}var O=.42,U=.065,H=.02,I=.58;function K(n,r,h,l,f){let p=Math.round(r),R=Math.round(r+h);if(n==="h"){let d=Math.round(l-I*f),w=Math.round(l+I*f);return{x:p,y:d,w:R-p,h:w-d}}let L=Math.max(1,Math.round(U*f)),y=Math.round(l+(n==="u"?O:H)*f);return{x:p,y,w:R-p,h:L}}return C(v);})();';
4523
+
4524
+ // src/lower/studioEntry.ts
4320
4525
  var STUDIO_ENTRY_ID = "vosso.studio";
4321
4526
  function studioEntry(data) {
4322
4527
  return {
@@ -4713,6 +4918,18 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
4713
4918
  var s = H / 1080 // scale design-px controls to comp px
4714
4919
  var ns = window.__vos__ || {}
4715
4920
  var playing = ns.isPaused === false
4921
+ // The styled-text layout (richText/layout.ts, generated), installed once:
4922
+ // the page lays a text layer out with the code the host picks with.
4923
+ if (!ns.textLayout) { ${textLayoutCode}; ns.textLayout = __vosTextLayout }
4924
+ var TXL = ns.textLayout
4925
+ // The font epoch counts faces landing. A face changes what text measures
4926
+ // and what it paints, so the epoch is part of every layout's key and of
4927
+ // the layer's signature: a late face repaints a held layer once.
4928
+ var ofBump = function () { ns.fontEpoch = (ns.fontEpoch || 0) + 1 }
4929
+ if (!ns.fontWatch && typeof document !== 'undefined' && document.fonts && document.fonts.addEventListener) {
4930
+ ns.fontWatch = 1
4931
+ document.fonts.addEventListener('loadingdone', ofBump)
4932
+ }
4716
4933
 
4717
4934
  // --- audio clips (music/SFX): Web Audio scheduler against the transport ---
4718
4935
  // Buffer sources are one-shot: (re)schedule everything on play / clip-data change
@@ -4840,7 +5057,19 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
4840
5057
  if (t < ol0.start || t > ol0.start + ol0.dur) continue
4841
5058
  // A page that taints on foreignObject draws no HTML layer (setup's probe).
4842
5059
  if (ol0.html && ns.htmlSafe === false) continue
4843
- olVisSig += ol0.id + ':' + (ol0.text || ol0.key) + ':' + ol0.x + ',' + ol0.y + ',' + ol0.scale + ',' + ol0.rot + ',' + (ol0.fs || ol0.w) + ',' + (ol0.color || ol0.radius) + ',' + (ol0.opacity == null ? 1 : ol0.opacity) + (ol0.fx ? ',' + ol0.fx.k + ol0.fx.u + ol0.fx.d + ol0.fx.st : '') + (ol0.mw ? ',w' + ol0.mw : '') + ';'
5060
+ olVisSig += ol0.id + ':' + (ol0.text || ol0.key) + ':' + ol0.x + ',' + ol0.y + ',' + ol0.scale + ',' + ol0.rot + ',' + (ol0.fs || ol0.w) + ',' + (ol0.color || ol0.radius) + ',' + (ol0.opacity == null ? 1 : ol0.opacity) + (ol0.fx ? ',' + ol0.fx.k + ol0.fx.u + ol0.fx.d + ol0.fx.st : '') + (ol0.mw ? ',w' + ol0.mw : '')
5061
+ // Everything else a held layer's pixels depend on. The signature is the
5062
+ // ONLY thing that repaints a hold, so a field missing here is an edit
5063
+ // that does not show until something else moves.
5064
+ if (ol0.lines) {
5065
+ olVisSig += '|' + ol0.lines.join('\\n') + '|' + ol0.weight + '|' + ol0.stack + '|' + (ol0.sty || '') + '|' + (ol0.ls || 0) + '|' + (ol0.lh || 0) + '|' + (ol0.align || '') + '|' + ol0.shadow
5066
+ + (ol0.stroke ? '|s' + ol0.stroke.c + ol0.stroke.w : '')
5067
+ + (ol0.box ? '|b' + ol0.box.c + ol0.box.o + ',' + ol0.box.px + ',' + ol0.box.py + ',' + ol0.box.r : '')
5068
+ + (ol0.rt ? '|r' + ol0.rt.h : '')
5069
+ } else {
5070
+ olVisSig += '|' + (ol0.shadow || '') + (ol0.border ? '|' + ol0.border.width + ol0.border.color : '') + (ol0.crop ? '|c' + ol0.crop.x + ',' + ol0.crop.y + ',' + ol0.crop.w + ',' + ol0.crop.h : '')
5071
+ }
5072
+ olVisSig += ';'
4844
5073
  // fx widens the entrance window to the whole staggered span (tt);
4845
5074
  // without fx it is the legacy enter transition window.
4846
5075
  var olEW = ol0.fx ? ol0.fx.tt : olTD
@@ -4878,8 +5107,20 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
4878
5107
  if (ns.htmlErrors) delete ns.htmlErrors[hlK]
4879
5108
  }
4880
5109
  }
4881
- var ovSig = W + 'x' + H + '|' + olVisSig
4882
- var ovDirty = ov.sig !== ovSig || ov.active || olAnim
5110
+ // A styled layer's cached layout leaves with its clip.
5111
+ if (ns.textLayouts) {
5112
+ for (var tlK in ns.textLayouts) {
5113
+ var tlSeen = false
5114
+ for (var tlI = 0; tlI < ols.length; tlI++) if (ols[tlI].id === tlK) { tlSeen = true; break }
5115
+ if (!tlSeen) delete ns.textLayouts[tlK]
5116
+ }
5117
+ }
5118
+ var ovSig = W + 'x' + H + '|' + (ns.fontEpoch || 0) + '|' + (ns.htmlSafe === false ? 0 : 1) + '|' + olVisSig
5119
+ // The layer repaints when what it shows changed (the signature), while
5120
+ // something on it animates, and ONCE more after an animation's last frame:
5121
+ // that frame was drawn a moment short of settled, and a picture that
5122
+ // landed between two frames is drawn by it too. A hold costs nothing.
5123
+ var ovDirty = ov.sig !== ovSig || olAnim || ov.wasAnim
4883
5124
  // The layer cards visible this frame, with their pose, for the card
4884
5125
  // planes below (the overlay loop collects them instead of painting).
4885
5126
  var lcList = []
@@ -5108,7 +5349,7 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
5108
5349
  try {
5109
5350
  var ofBF = new FontFace(ofB.family, 'url(' + ofB.url + ')', { weight: String(ofB.weight) })
5110
5351
  document.fonts.add(ofBF)
5111
- ofBF.load().catch(function () {})
5352
+ ofBF.load().then(ofBump, function () {})
5112
5353
  } catch (e) {}
5113
5354
  }
5114
5355
  if (ol.face) {
@@ -5118,20 +5359,22 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
5118
5359
  try {
5119
5360
  var ofFace = new FontFace(ol.face.f, 'url(' + ol.face.u + ')', { weight: String(ol.face.w) })
5120
5361
  document.fonts.add(ofFace)
5121
- ofFace.load().catch(function () {})
5362
+ ofFace.load().then(ofBump, function () {})
5122
5363
  } catch (e) {}
5123
5364
  }
5124
5365
  }
5125
- // The emphasis weight's face, the same way (a live edit that adds the
5126
- // first *marked* word paints without a LOAD).
5127
- if (ol.em && ol.em.face) {
5128
- var oeKey = ol.em.face.f + '|' + ol.em.face.w
5129
- if (!ofSet[oeKey]) {
5130
- ofSet[oeKey] = 1
5366
+ // A styled layer's other faces, the same way (a live edit that styles
5367
+ // its first run paints without a LOAD).
5368
+ if (ol.rt && ol.rt.faces) {
5369
+ for (var rfj = 0; rfj < ol.rt.faces.length; rfj++) {
5370
+ var rtF = ol.rt.faces[rfj]
5371
+ var rtFK = rtF.f + '|' + rtF.w
5372
+ if (ofSet[rtFK]) continue
5373
+ ofSet[rtFK] = 1
5131
5374
  try {
5132
- var oeFace = new FontFace(ol.em.face.f, 'url(' + ol.em.face.u + ')', { weight: String(ol.em.face.w) })
5133
- document.fonts.add(oeFace)
5134
- oeFace.load().catch(function () {})
5375
+ var rtFace = new FontFace(rtF.f, 'url(' + rtF.u + ')', { weight: String(rtF.w) })
5376
+ document.fonts.add(rtFace)
5377
+ rtFace.load().then(ofBump, function () {})
5135
5378
  } catch (e) {}
5136
5379
  }
5137
5380
  }
@@ -5150,68 +5393,143 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
5150
5393
  ovC.textAlign = 'center'
5151
5394
  ovC.textBaseline = 'middle'
5152
5395
  ovC.letterSpacing = ((ol.ls || 0) * olMS * s) + 'px'
5153
- // Emphasis (ol.em, baked only when the text marks a word): the lines
5154
- // carry \\u0001/\\u0002 around every emphasized word, and the normal and
5155
- // emphasis fonts alternate at them, run by run. MIRRORS measureEmphasized
5156
- // in overlayText.ts \u2014 change together. A clip without ol.em never reaches
5157
- // these helpers: it keeps its plain measureText/fillText calls.
5158
- var olEm = ol.em || null
5159
- var olFN = ovC.font
5160
- var olFB = olEm ? (ol.sty ? ol.sty + ' ' : '') + olEm.w + ' ' + olPx + 'px ' + ol.stack : ''
5161
- var olMeasure = function (t, b0) {
5162
- if (!olEm) return ovC.measureText(t).width
5163
- var mw = 0, mrun = '', mb = !!b0
5164
- for (var mi = 0; mi < t.length; mi++) {
5165
- var mc = t.charAt(mi)
5166
- if (mc === '\\u0001' || mc === '\\u0002') {
5167
- if (mrun) { ovC.font = mb ? olFB : olFN; mw += ovC.measureText(mrun).width }
5168
- mrun = ''
5169
- mb = mc === '\\u0001'
5170
- } else mrun += mc
5171
- }
5172
- if (mrun) { ovC.font = mb ? olFB : olFN; mw += ovC.measureText(mrun).width }
5173
- ovC.font = olFN
5174
- return mw
5175
- }
5176
- // The emphasis state after a run of text (a char unit starts in it).
5177
- var olStateAfter = function (t, b0) {
5178
- var sb = !!b0
5179
- for (var si = 0; si < t.length; si++) {
5180
- var sc = t.charAt(si)
5181
- if (sc === '\\u0001') sb = true
5182
- else if (sc === '\\u0002') sb = false
5183
- }
5184
- return sb
5185
- }
5186
- // Draw t LEFT-aligned from x, run by run: the font switches at the
5187
- // marks, an emphasized run takes ol.em.c when set, stroke under fill.
5188
- var olDraw = function (t, x, y, b0) {
5189
- var drun = '', db = !!b0, dx = x
5190
- var dflush = function () {
5191
- if (!drun) return
5192
- ovC.font = db ? olFB : olFN
5193
- var dfs = ovC.fillStyle
5194
- if (db && olEm.c) ovC.fillStyle = olEm.c
5396
+ // A STYLED layer (ol.rt: lines of runs, each set its own way) is laid
5397
+ // out by the layout module, ONCE: the layout is kept per clip under a
5398
+ // key of everything it was measured with, so a frame that only moves,
5399
+ // fades or reveals the layer measures nothing. Fragments draw left to
5400
+ // right from each line's left edge; a per-unit entrance animates the
5401
+ // layout's units, each a few parts of fragments.
5402
+ if (ol.rt) {
5403
+ var rtFonts = [ovC.font]
5404
+ for (var rfi = 1; rfi < ol.rt.fs.length; rfi++) {
5405
+ rtFonts.push((ol.sty ? ol.sty + ' ' : '') + ol.rt.fs[rfi].w + ' ' + olPx + 'px ' + ol.stack)
5406
+ }
5407
+ var rtFx = ol.fx && ol.fx.units.length ? ol.fx : null
5408
+ var rtMax = ol.mw ? ol.mw * W : 0
5409
+ var rtKey = ol.rt.h + '|' + rtFonts[0] + '|' + ((ol.ls || 0) * olMS * s) + '|' + rtMax + '|' + (rtFx ? rtFx.u : '') + '|' + (ns.fontEpoch || 0)
5410
+ var rtAll = ns.textLayouts || (ns.textLayouts = {})
5411
+ var rtHit = rtAll[ol.id]
5412
+ if (!rtHit || rtHit.k !== rtKey) {
5413
+ rtHit = rtAll[ol.id] = {
5414
+ k: rtKey,
5415
+ L: TXL.layoutText(
5416
+ { lines: ol.rt.l, maxWidth: rtMax, unit: rtFx ? rtFx.u : undefined },
5417
+ function (mt, mf) { ovC.font = rtFonts[mf]; return ovC.measureText(mt).width }
5418
+ )
5419
+ }
5420
+ ovC.font = rtFonts[0]
5421
+ }
5422
+ var rtL = rtHit.L
5423
+ var rtN = rtL.lines.length
5424
+ var rtLH = olPx * (ol.lh || ${OVERLAY_LINE_HEIGHT})
5425
+ var rtY0 = -((rtN - 1) * rtLH) / 2
5426
+ // A line's left edge: lines sit centred on the anchor and align
5427
+ // against the widest, as the plain layer's do.
5428
+ var rtLeft = function (lw) {
5429
+ var xo = ol.align === 'left' ? (lw - rtL.width) / 2 : ol.align === 'right' ? (rtL.width - lw) / 2 : 0
5430
+ return xo - lw / 2
5431
+ }
5432
+ if (ol.box) {
5433
+ var rbPX = ol.box.px * olMS * s
5434
+ var rbPY = ol.box.py * olMS * s
5435
+ var rbW = rtL.width + rbPX * 2
5436
+ var rbH = rtN * rtLH + rbPY * 2
5437
+ ovC.save()
5438
+ ovC.globalAlpha = olA * ol.box.o
5439
+ ovC.fillStyle = ol.box.c
5440
+ rr(-rbW / 2, -rbH / 2, rbW, rbH, Math.min(ol.box.r * olMS * s, rbH / 2), ovC)
5441
+ ovC.fill()
5442
+ ovC.restore()
5443
+ }
5444
+ // Highlights sit behind the words and take no legibility shadow.
5445
+ for (var rhi = 0; rhi < rtN; rhi++) {
5446
+ var rhLine = rtL.lines[rhi]
5447
+ for (var rhj = 0; rhj < rhLine.frags.length; rhj++) {
5448
+ var rhF = rhLine.frags[rhj]
5449
+ if (!rhF.h) continue
5450
+ var rhR = TXL.decorationRect('h', rtLeft(rhLine.w) + rhF.x, rhF.w, rtY0 + rhi * rtLH, olPx)
5451
+ ovC.fillStyle = rhF.h
5452
+ ovC.fillRect(rhR.x, rhR.y, rhR.w, rhR.h)
5453
+ }
5454
+ }
5455
+ if (ol.shadow > 0) {
5456
+ ovC.shadowColor = 'rgba(0,0,0,' + ol.shadow + ')'
5457
+ ovC.shadowBlur = olPx * 0.25
5458
+ ovC.shadowOffsetY = olPx * 0.04
5459
+ }
5460
+ ovC.textAlign = 'left'
5461
+ // One fragment (or the part of one a unit holds): the underline under
5462
+ // the glyphs, stroke under fill, the strikethrough over them.
5463
+ var rtPart = function (p, bx, y) {
5464
+ ovC.font = rtFonts[p.f || 0]
5465
+ ovC.fillStyle = p.c || ol.color
5466
+ if (p.u) {
5467
+ var ru = TXL.decorationRect('u', bx + p.x, p.w, y, olPx)
5468
+ ovC.fillRect(ru.x, ru.y, ru.w, ru.h)
5469
+ }
5195
5470
  if (ol.stroke) {
5196
5471
  ovC.strokeStyle = ol.stroke.c
5197
5472
  ovC.lineWidth = ol.stroke.w * olMS * s
5198
5473
  ovC.lineJoin = 'round'
5199
- ovC.strokeText(drun, dx, y)
5474
+ ovC.strokeText(p.t, bx + p.x, y)
5475
+ }
5476
+ ovC.fillText(p.t, bx + p.x, y)
5477
+ if (p.s) {
5478
+ var rs = TXL.decorationRect('s', bx + p.x, p.w, y, olPx)
5479
+ ovC.fillRect(rs.x, rs.y, rs.w, rs.h)
5200
5480
  }
5201
- ovC.fillText(drun, dx, y)
5202
- dx += ovC.measureText(drun).width
5203
- ovC.fillStyle = dfs
5204
- drun = ''
5205
5481
  }
5206
- for (var di = 0; di < t.length; di++) {
5207
- var dc = t.charAt(di)
5208
- if (dc === '\\u0001' || dc === '\\u0002') {
5209
- dflush()
5210
- db = dc === '\\u0001'
5211
- } else drun += dc
5482
+ if (!rtFx) {
5483
+ for (var rli = 0; rli < rtN; rli++) {
5484
+ var rLine = rtL.lines[rli]
5485
+ var rBX = rtLeft(rLine.w)
5486
+ for (var rfk = 0; rfk < rLine.frags.length; rfk++) rtPart(rLine.frags[rfk], rBX, rtY0 + rli * rtLH)
5487
+ }
5488
+ } else {
5489
+ // Per-unit entrance, the plain layer's own arithmetic over the
5490
+ // layout's units: delay = order(index) x st, eased over dur.
5491
+ var rtCount = 0
5492
+ for (var rci = 0; rci < rtN; rci++) rtCount += rtL.lines[rci].units.length
5493
+ var rtIdx = 0
5494
+ for (var rui = 0; rui < rtN; rui++) {
5495
+ var rULine = rtL.lines[rui]
5496
+ var rUBX = rtLeft(rULine.w)
5497
+ var rUY = rtY0 + rui * rtLH
5498
+ for (var ruj = 0; ruj < rULine.units.length; ruj++, rtIdx++) {
5499
+ var rU = rULine.units[ruj]
5500
+ var rOrd = rtFx.d === 1 ? (rtCount - 1 - rtIdx) : rtFx.d === 2 ? Math.abs(rtIdx - (rtCount - 1) / 2) : rtIdx
5501
+ var rT2 = olT - rOrd * rtFx.st
5502
+ var rUA = 1, rUu = 1
5503
+ if (rtFx.k === 'typewriter') {
5504
+ rUA = rT2 >= 0 ? 1 : 0
5505
+ } else {
5506
+ rUu = Math.max(0, Math.min(1, rT2 / rtFx.dur))
5507
+ var rUE = 1 - Math.pow(1 - rUu, 3)
5508
+ rUA = rtFx.k === 'pop' ? Math.min(1, rUu * 2) : rUE
5509
+ }
5510
+ if (rUA <= 0.004) continue
5511
+ ovC.save()
5512
+ ovC.globalAlpha = olA * rUA
5513
+ if (rUu < 1) {
5514
+ if (rtFx.k === 'rise') {
5515
+ ovC.translate(0, (1 - (1 - Math.pow(1 - rUu, 3))) * 24 * s)
5516
+ } else if (rtFx.k === 'pop') {
5517
+ var rPS = 1 + 2.70158 * Math.pow(rUu - 1, 3) + 1.70158 * Math.pow(rUu - 1, 2)
5518
+ var rCX = rUBX + rU.x + rU.w / 2
5519
+ ovC.translate(rCX, rUY)
5520
+ ovC.scale(rPS, rPS)
5521
+ ovC.translate(-rCX, -rUY)
5522
+ } else if (rtFx.k === 'blur') {
5523
+ ovC.filter = 'blur(' + ((1 - rUu) * olPx * 0.12).toFixed(2) + 'px)'
5524
+ }
5525
+ }
5526
+ for (var rpk = 0; rpk < rU.parts.length; rpk++) rtPart(rU.parts[rpk], rUBX, rUY)
5527
+ ovC.restore()
5528
+ }
5529
+ }
5212
5530
  }
5213
- dflush()
5214
- ovC.font = olFN
5531
+ ovC.restore()
5532
+ continue
5215
5533
  }
5216
5534
  var olLines = ol.lines || ['']
5217
5535
  // maxWidth wrap (ol.mw = frame-width fraction): greedy over word tokens
@@ -5224,7 +5542,7 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
5224
5542
  var olWrapped = []
5225
5543
  for (var olwl = 0; olwl < olLines.length; olwl++) {
5226
5544
  var olWLine = olLines[olwl]
5227
- if (!olWLine || olMeasure(olWLine) <= olWMax) {
5545
+ if (!olWLine || ovC.measureText(olWLine).width <= olWMax) {
5228
5546
  olWrapped.push(olWLine)
5229
5547
  continue
5230
5548
  }
@@ -5232,7 +5550,7 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
5232
5550
  var olCur = ''
5233
5551
  for (var olti = 0; olti < olToks.length; olti++) {
5234
5552
  if (!olCur) { olCur = olToks[olti]; continue }
5235
- if (olMeasure(olCur + olToks[olti]) <= olWMax) {
5553
+ if (ovC.measureText(olCur + olToks[olti]).width <= olWMax) {
5236
5554
  olCur += olToks[olti]
5237
5555
  } else {
5238
5556
  olWrapped.push(olCur)
@@ -5282,10 +5600,10 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
5282
5600
  olFx = { k: olFx.k, u: olFx.u, d: olFx.d, st: olFx.st, dur: olFx.dur, tt: olFx.tt, units: olRe, n: olReN }
5283
5601
  }
5284
5602
  var olLWs = null, olMaxW = 0
5285
- if (ol.box || ol.align || olFx || olEm) {
5603
+ if (ol.box || ol.align || olFx) {
5286
5604
  olLWs = []
5287
5605
  for (var olwi = 0; olwi < olLines.length; olwi++) {
5288
- var olw = olMeasure(olLines[olwi])
5606
+ var olw = ovC.measureText(olLines[olwi]).width
5289
5607
  olLWs.push(olw)
5290
5608
  if (olw > olMaxW) olMaxW = olw
5291
5609
  }
@@ -5321,12 +5639,6 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
5321
5639
  : (olMaxW - olLWs[ok]) / 2
5322
5640
  }
5323
5641
  var olLY = olY0 + ok * olLH
5324
- if (olEm) {
5325
- // Runs draw LEFT-aligned from the centred line's left edge.
5326
- ovC.textAlign = 'left'
5327
- olDraw(olLines[ok], olXof - olLWs[ok] / 2, olLY, false)
5328
- continue
5329
- }
5330
5642
  if (ol.stroke) {
5331
5643
  ovC.strokeStyle = ol.stroke.c
5332
5644
  ovC.lineWidth = ol.stroke.w * olMS * s
@@ -5363,10 +5675,7 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
5363
5675
  : olIdx
5364
5676
  var olT2 = olT - olOrd * olFx.st
5365
5677
  var olNext = olPref + olUs[ou]
5366
- var olNW = olMeasure(olNext)
5367
- // The emphasis state this unit starts in (a char unit inside a
5368
- // marked word carries no mark of its own).
5369
- var olUB = olEm ? olStateAfter(olPref) : false
5678
+ var olNW = ovC.measureText(olNext).width
5370
5679
  var olUW = olNW - olPW
5371
5680
  var olUX = olXb + olPW
5372
5681
  var olUA = 1, olUu = 1
@@ -5395,11 +5704,6 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
5395
5704
  ovC.filter = 'blur(' + ((1 - olUu) * olPx * 0.12).toFixed(2) + 'px)'
5396
5705
  }
5397
5706
  }
5398
- if (olEm) {
5399
- olDraw(olUnit, olUX, olLY2, olUB)
5400
- ovC.restore()
5401
- continue
5402
- }
5403
5707
  if (ol.stroke) {
5404
5708
  ovC.strokeStyle = ol.stroke.c
5405
5709
  ovC.lineWidth = ol.stroke.w * olMS * s
@@ -5414,7 +5718,7 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
5414
5718
  ovC.restore()
5415
5719
  }
5416
5720
  ov.sig = ovSig
5417
- ov.active = olVisSig !== ''
5721
+ ov.wasAnim = olAnim
5418
5722
  if (ov.texture) ov.texture.needsUpdate = true
5419
5723
  }
5420
5724
 
@@ -8367,27 +8671,16 @@ function studioLayerData(layers, duration, pins) {
8367
8671
  }
8368
8672
  const st = resolveOverlayStyle(o);
8369
8673
  const bx = resolveOverlayBox(o);
8370
- const em = resolveEmphasis(o);
8674
+ const rt = styledTextOf(o);
8371
8675
  return {
8372
8676
  ...base,
8373
8677
  text: o.text,
8374
- // The DISPLAYED lines: emphasis marks inline, else the source.
8375
- lines: overlayLines(overlayDisplayText(o)),
8376
- // Emphasis set (weight, colour, face to lazy-load), only when
8377
- // the text marks a word: an unmarked clip's data is unchanged.
8378
- ...em ? {
8379
- em: {
8380
- w: em.weight,
8381
- ...em.color ? { c: em.color } : {},
8382
- ...em.face ? {
8383
- face: {
8384
- f: em.face.family,
8385
- w: em.face.weight,
8386
- u: em.face.url
8387
- }
8388
- } : {}
8389
- }
8390
- } : {},
8678
+ // The lines a person reads. A styled layer (rt) is painted from
8679
+ // its runs; these are then its words, for the signature.
8680
+ lines: overlayLines(stripEmphasis(overlayDisplayText(o))),
8681
+ // The styled payload, only when the layer is set in more than
8682
+ // one style: a plain clip's data is unchanged.
8683
+ ...rt ? { rt } : {},
8391
8684
  fs: st.size,
8392
8685
  weight: st.weight,
8393
8686
  stack: st.stack,
@@ -10520,6 +10813,68 @@ function applyLook(frame, look, size, video, placement) {
10520
10813
  return out;
10521
10814
  }
10522
10815
 
10816
+ // src/richText/caret.ts
10817
+ function caretStops(line, measure) {
10818
+ const stops = [{ o: line.a, x: 0 }];
10819
+ for (const fr of line.frags) {
10820
+ let at = 0;
10821
+ for (const g of graphemesOf(fr.t)) {
10822
+ at += g.length;
10823
+ stops.push({
10824
+ o: line.a + fr.o + at,
10825
+ x: fr.x + measure(fr.t.slice(0, at), fr.f || 0)
10826
+ });
10827
+ }
10828
+ }
10829
+ return stops;
10830
+ }
10831
+ function lineOfOffset(layout, offset) {
10832
+ const lines = layout.lines;
10833
+ for (let i = lines.length - 1; i >= 0; i--) {
10834
+ if (offset >= lines[i].a) return i;
10835
+ }
10836
+ return 0;
10837
+ }
10838
+ function caretAt(layout, offset, measure) {
10839
+ const li = lineOfOffset(layout, offset);
10840
+ const stops = caretStops(layout.lines[li], measure);
10841
+ let x = stops[stops.length - 1].x;
10842
+ for (const s of stops) {
10843
+ if (s.o >= offset) {
10844
+ x = s.x;
10845
+ break;
10846
+ }
10847
+ }
10848
+ return { line: li, x };
10849
+ }
10850
+ function offsetAtX(line, x, measure) {
10851
+ const stops = caretStops(line, measure);
10852
+ let best = stops[0];
10853
+ for (const s of stops) {
10854
+ if (Math.abs(s.x - x) < Math.abs(best.x - x)) best = s;
10855
+ }
10856
+ return best.o;
10857
+ }
10858
+ function rangeRects(layout, start, end, measure, breakTail = 0) {
10859
+ const out = [];
10860
+ if (end <= start) return out;
10861
+ layout.lines.forEach((line, i) => {
10862
+ const lineEnd = line.a + line.n;
10863
+ if (end <= line.a || start > lineEnd) return;
10864
+ const stops = caretStops(line, measure);
10865
+ const xOf = (o) => {
10866
+ for (const s of stops) if (s.o >= o) return s.x;
10867
+ return stops[stops.length - 1].x;
10868
+ };
10869
+ const x0 = xOf(Math.max(start, line.a));
10870
+ const x1 = xOf(Math.min(end, lineEnd));
10871
+ const next = layout.lines[i + 1];
10872
+ const tail = next && next.a > lineEnd && end > lineEnd ? breakTail : 0;
10873
+ if (x1 + tail > x0) out.push({ line: i, x: x0, w: x1 - x0 + tail });
10874
+ });
10875
+ return out;
10876
+ }
10877
+
10523
10878
  // src/timeline/lanes.ts
10524
10879
  import {
10525
10880
  mapTime as mapTime3,
@@ -11900,6 +12255,7 @@ export {
11900
12255
  CURSOR_IDLE_FADE_OUT,
11901
12256
  CURSOR_IDLE_HOLD,
11902
12257
  DARK_GROUND,
12258
+ DECORATION_THICKNESS_EM,
11903
12259
  DEFAULT_BROWSER_BAR,
11904
12260
  DEFAULT_CAM_POSE,
11905
12261
  DEFAULT_CAM_STYLE,
@@ -11937,6 +12293,7 @@ export {
11937
12293
  FREEZE_DEFAULT_SECONDS,
11938
12294
  FREEZE_SECONDS_MAX,
11939
12295
  FREEZE_SECONDS_MIN,
12296
+ HIGHLIGHT_HALF_EM,
11940
12297
  HOLD_DRIFT_MAX,
11941
12298
  HOUSE_GRADIENT,
11942
12299
  HOUSE_REGISTER,
@@ -11986,6 +12343,7 @@ export {
11986
12343
  SPEED_SPAN_MIN,
11987
12344
  STAGE_CLIP_PREFIX,
11988
12345
  STILL_DEFAULT,
12346
+ STRIKE_OFFSET_EM,
11989
12347
  STUDIO_ENTRY_ID,
11990
12348
  STYLE_FIELDS,
11991
12349
  TEXT3D_DEPTH_DEFAULT,
@@ -12010,6 +12368,7 @@ export {
12010
12368
  TRANSITION_SECONDS_MAX,
12011
12369
  TRANSITION_SECONDS_MIN,
12012
12370
  TRANSITION_SPEED_MULT,
12371
+ UNDERLINE_OFFSET_EM,
12013
12372
  WINDOW_FOCUS_MIN,
12014
12373
  ZOOM_CHAIN_GAP,
12015
12374
  ZOOM_EASE,
@@ -12053,6 +12412,8 @@ export {
12053
12412
  cardInset,
12054
12413
  cardPointToScreen,
12055
12414
  cardPoseTrack,
12415
+ caretAt,
12416
+ caretStops,
12056
12417
  clampCamFrac,
12057
12418
  clampCamSize,
12058
12419
  clampFocus,
@@ -12076,6 +12437,7 @@ export {
12076
12437
  cursorFromRecords,
12077
12438
  cursorFromTrace,
12078
12439
  cursorIdleFade,
12440
+ decorationRect,
12079
12441
  deltaE,
12080
12442
  deriveViewportCrop,
12081
12443
  destinationById,
@@ -12146,14 +12508,15 @@ export {
12146
12508
  lastLayerEnd,
12147
12509
  layerMedia,
12148
12510
  layoutOf,
12511
+ layoutText,
12149
12512
  levelForFocusFraction,
12513
+ lineOfOffset,
12150
12514
  liveZoomStyleName,
12151
12515
  lookFromBrand,
12152
12516
  lookKindForGround,
12153
12517
  lowerProgramDoc,
12154
12518
  lowerStudioDoc,
12155
12519
  lowerToComposition,
12156
- measureEmphasized,
12157
12520
  mediaAtOutput,
12158
12521
  mediaDuration,
12159
12522
  mediaFrame,
@@ -12173,6 +12536,7 @@ export {
12173
12536
  objectMotionBase,
12174
12537
  objectMotionPoseAt,
12175
12538
  objectsLane,
12539
+ offsetAtX,
12176
12540
  outputDurationOf,
12177
12541
  outputEnd,
12178
12542
  outputRangeToSource,
@@ -12213,6 +12577,7 @@ export {
12213
12577
  programStillTime,
12214
12578
  projectFromArtifact,
12215
12579
  pumpFreeChainGap,
12580
+ rangeRects,
12216
12581
  ratedSegments,
12217
12582
  recommendedExportResolution,
12218
12583
  refillAudioBeds,
@@ -12245,8 +12610,10 @@ export {
12245
12610
  studioAudioPlan,
12246
12611
  studioEntry,
12247
12612
  studioLayerData,
12613
+ styledTextOf,
12248
12614
  tiltLane,
12249
12615
  tiltTrackFromDoc,
12616
+ tokenRanges,
12250
12617
  traceKindOf,
12251
12618
  transitionMult,
12252
12619
  transitionSeconds,