@vosjs/studio-core 0.34.0 → 0.35.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +87 -6
- package/dist/index.js +308 -49
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -2701,7 +2701,10 @@ function camBubbleRect(cam, W, H = 1080) {
|
|
|
2701
2701
|
radius: cam.shape === "rounded" ? (cam.radius ?? 18) * s : size / 2
|
|
2702
2702
|
};
|
|
2703
2703
|
}
|
|
2704
|
-
function focusBounds(level, layout, camera = "card") {
|
|
2704
|
+
function focusBounds(level, layout, camera = "card", screen) {
|
|
2705
|
+
if (camera === "stage" && screen && level > 1.001) {
|
|
2706
|
+
return { minX: 0, maxX: 1, minY: 0, maxY: 1 };
|
|
2707
|
+
}
|
|
2705
2708
|
if (level <= 1.001) {
|
|
2706
2709
|
return { minX: 0.5, maxX: 0.5, minY: 0.5, maxY: 0.5 };
|
|
2707
2710
|
}
|
|
@@ -2764,8 +2767,8 @@ function stageAxisBounds(anchorOff, anchorSize, coverOff, coverSize, viewport, l
|
|
|
2764
2767
|
}
|
|
2765
2768
|
return { min: clamp013(lo), max: clamp013(hi) };
|
|
2766
2769
|
}
|
|
2767
|
-
function clampFocus(cx, cy, level, layout, camera = "card") {
|
|
2768
|
-
const b = focusBounds(level, layout, camera);
|
|
2770
|
+
function clampFocus(cx, cy, level, layout, camera = "card", screen) {
|
|
2771
|
+
const b = focusBounds(level, layout, camera, screen);
|
|
2769
2772
|
return {
|
|
2770
2773
|
cx: Math.min(b.maxX, Math.max(b.minX, cx)),
|
|
2771
2774
|
cy: Math.min(b.maxY, Math.max(b.minY, cy))
|
|
@@ -2774,6 +2777,9 @@ function clampFocus(cx, cy, level, layout, camera = "card") {
|
|
|
2774
2777
|
function clamp013(v) {
|
|
2775
2778
|
return Math.max(0, Math.min(1, v));
|
|
2776
2779
|
}
|
|
2780
|
+
function screenPoint(layout, camera, screen) {
|
|
2781
|
+
return camera === "stage" && screen ? { ox: screen.x * layout.W, oy: screen.y * layout.H } : { ox: layout.W / 2, oy: layout.H / 2 };
|
|
2782
|
+
}
|
|
2777
2783
|
function cameraModel(frame) {
|
|
2778
2784
|
return frame?.camera === "stage" ? "stage" : "card";
|
|
2779
2785
|
}
|
|
@@ -2783,34 +2789,36 @@ function cameraCentring(level) {
|
|
|
2783
2789
|
const u = clamp013((level - 1) / CAMERA_CENTRE_RAMP);
|
|
2784
2790
|
return u * u * (3 - 2 * u);
|
|
2785
2791
|
}
|
|
2786
|
-
function zoomView(level, cx, cy, layout, camera = "card", centring) {
|
|
2792
|
+
function zoomView(level, cx, cy, layout, camera = "card", centring, screen) {
|
|
2787
2793
|
const L = Math.max(1, level);
|
|
2788
2794
|
const fx = layout.dx + cx * layout.dw;
|
|
2789
2795
|
const fy = layout.dy + cy * layout.dh;
|
|
2790
|
-
const ox = layout
|
|
2791
|
-
const oy = layout.H / 2;
|
|
2796
|
+
const { ox, oy } = screenPoint(layout, camera, screen);
|
|
2792
2797
|
const t = camera === "stage" ? clamp013(centring ?? 1) : 0;
|
|
2793
2798
|
const wcx = fx + (ox - fx) / L * (1 - t);
|
|
2794
2799
|
const wcy = fy + (oy - fy) / L * (1 - t);
|
|
2795
2800
|
return { level: L, fx, fy, wcx, wcy, ox, oy };
|
|
2796
2801
|
}
|
|
2797
|
-
function zoomViewport(level, cx, cy, layout, camera = "card", centring) {
|
|
2798
|
-
const v = zoomView(level, cx, cy, layout, camera, centring);
|
|
2802
|
+
function zoomViewport(level, cx, cy, layout, camera = "card", centring, screen) {
|
|
2803
|
+
const v = zoomView(level, cx, cy, layout, camera, centring, screen);
|
|
2799
2804
|
const size = 1 / v.level;
|
|
2805
|
+
const mx = v.wcx + (layout.W / 2 - v.ox) / v.level;
|
|
2806
|
+
const my = v.wcy + (layout.H / 2 - v.oy) / v.level;
|
|
2800
2807
|
return {
|
|
2801
|
-
x: (
|
|
2802
|
-
y: (
|
|
2808
|
+
x: (mx - layout.W / (2 * v.level)) / layout.W,
|
|
2809
|
+
y: (my - layout.H / (2 * v.level)) / layout.H,
|
|
2803
2810
|
w: size,
|
|
2804
2811
|
h: size
|
|
2805
2812
|
};
|
|
2806
2813
|
}
|
|
2807
|
-
function focusForViewportCentre(cX, cY, level, layout, camera = "card", centring) {
|
|
2814
|
+
function focusForViewportCentre(cX, cY, level, layout, camera = "card", centring, screen) {
|
|
2808
2815
|
const L = Math.max(1, level);
|
|
2809
2816
|
const t = camera === "stage" ? clamp013(centring ?? 1) : 0;
|
|
2810
2817
|
const a = 1 - (1 - t) / L;
|
|
2811
2818
|
if (a <= 1e-6) return { cx: 0.5, cy: 0.5 };
|
|
2812
|
-
const
|
|
2813
|
-
const
|
|
2819
|
+
const { ox, oy } = screenPoint(layout, camera, screen);
|
|
2820
|
+
const fx = (cX * layout.W - ox * (1 - t) / L - (layout.W / 2 - ox) / L) / a;
|
|
2821
|
+
const fy = (cY * layout.H - oy * (1 - t) / L - (layout.H / 2 - oy) / L) / a;
|
|
2814
2822
|
return { cx: (fx - layout.dx) / layout.dw, cy: (fy - layout.dy) / layout.dh };
|
|
2815
2823
|
}
|
|
2816
2824
|
function levelForFocusFraction(frac) {
|
|
@@ -3337,14 +3345,17 @@ function overlayFaceFor(clip) {
|
|
|
3337
3345
|
function overlayFontFaces(doc) {
|
|
3338
3346
|
const faces = [...OVERLAY_FONT_FACES];
|
|
3339
3347
|
const seen = new Set(faces.map((f) => `${f.family}|${f.weight}`));
|
|
3340
|
-
|
|
3341
|
-
if (
|
|
3342
|
-
const face = overlayFaceFor(o);
|
|
3343
|
-
if (!face) continue;
|
|
3348
|
+
const add = (face) => {
|
|
3349
|
+
if (!face) return;
|
|
3344
3350
|
const key = `${face.family}|${face.weight}`;
|
|
3345
|
-
if (seen.has(key))
|
|
3351
|
+
if (seen.has(key)) return;
|
|
3346
3352
|
seen.add(key);
|
|
3347
3353
|
faces.push(face);
|
|
3354
|
+
};
|
|
3355
|
+
for (const o of doc.overlays ?? []) {
|
|
3356
|
+
if (o.kind !== "text") continue;
|
|
3357
|
+
add(overlayFaceFor(o));
|
|
3358
|
+
add(resolveEmphasis(o)?.face);
|
|
3348
3359
|
}
|
|
3349
3360
|
return faces;
|
|
3350
3361
|
}
|
|
@@ -3352,6 +3363,102 @@ function overlayLines(text) {
|
|
|
3352
3363
|
const lines = text.split("\n");
|
|
3353
3364
|
return lines.length ? lines : [""];
|
|
3354
3365
|
}
|
|
3366
|
+
var EM_OPEN = "";
|
|
3367
|
+
var EM_CLOSE = "";
|
|
3368
|
+
function parseEmphasis(text) {
|
|
3369
|
+
let any = false;
|
|
3370
|
+
const lines = text.split("\n").map((line) => {
|
|
3371
|
+
const chars = [];
|
|
3372
|
+
const bold = [];
|
|
3373
|
+
const marker = [];
|
|
3374
|
+
for (let i = 0; i < line.length; i++) {
|
|
3375
|
+
if (line[i] === "\\" && line[i + 1] === "*") {
|
|
3376
|
+
chars.push("*");
|
|
3377
|
+
marker.push(false);
|
|
3378
|
+
i++;
|
|
3379
|
+
} else {
|
|
3380
|
+
chars.push(line[i]);
|
|
3381
|
+
marker.push(line[i] === "*");
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
const keep = chars.map(() => true);
|
|
3385
|
+
for (let i = 0; i < chars.length; i++) {
|
|
3386
|
+
if (!marker[i]) continue;
|
|
3387
|
+
let j = i + 1;
|
|
3388
|
+
while (j < chars.length && !marker[j]) j++;
|
|
3389
|
+
if (j >= chars.length) break;
|
|
3390
|
+
const inner = chars.slice(i + 1, j).join("");
|
|
3391
|
+
if (!inner.trim()) {
|
|
3392
|
+
i = j - 1;
|
|
3393
|
+
continue;
|
|
3394
|
+
}
|
|
3395
|
+
keep[i] = false;
|
|
3396
|
+
keep[j] = false;
|
|
3397
|
+
for (let k = i + 1; k < j; k++) bold[k] = true;
|
|
3398
|
+
any = true;
|
|
3399
|
+
i = j;
|
|
3400
|
+
}
|
|
3401
|
+
const kept = [];
|
|
3402
|
+
for (let i = 0; i < chars.length; i++) {
|
|
3403
|
+
if (keep[i])
|
|
3404
|
+
kept.push({ c: chars[i], b: !!bold[i] && !/\s/.test(chars[i]) });
|
|
3405
|
+
}
|
|
3406
|
+
let out = "";
|
|
3407
|
+
for (let i = 0; i < kept.length; i++) {
|
|
3408
|
+
const { c, b } = kept[i];
|
|
3409
|
+
if (b && !(i > 0 && kept[i - 1].b)) out += EM_OPEN;
|
|
3410
|
+
out += c;
|
|
3411
|
+
if (b && !(i + 1 < kept.length && kept[i + 1].b)) out += EM_CLOSE;
|
|
3412
|
+
}
|
|
3413
|
+
return out;
|
|
3414
|
+
});
|
|
3415
|
+
return any ? lines.join("\n") : null;
|
|
3416
|
+
}
|
|
3417
|
+
function overlayDisplayText(clip) {
|
|
3418
|
+
if (!clip.emphasis) return clip.text;
|
|
3419
|
+
return parseEmphasis(clip.text) ?? clip.text;
|
|
3420
|
+
}
|
|
3421
|
+
function stripEmphasis(text) {
|
|
3422
|
+
return text.split(EM_OPEN).join("").split(EM_CLOSE).join("");
|
|
3423
|
+
}
|
|
3424
|
+
var EMPHASIS_WEIGHT = 700;
|
|
3425
|
+
function resolveEmphasis(clip) {
|
|
3426
|
+
if (!clip.emphasis || parseEmphasis(clip.text) === null) return null;
|
|
3427
|
+
const style = resolveOverlayStyle(clip);
|
|
3428
|
+
const entry = findFontFamily2(
|
|
3429
|
+
clip.family ?? PRESET_FAMILY[clip.preset in TEXT_PRESETS ? clip.preset : "title"]
|
|
3430
|
+
);
|
|
3431
|
+
const wanted = clip.emphasis?.weight ?? Math.max(EMPHASIS_WEIGHT, style.weight);
|
|
3432
|
+
const weight = entry ? nearestFontWeight2(entry, wanted) : wanted;
|
|
3433
|
+
const inBase = OVERLAY_FONT_FACES.some(
|
|
3434
|
+
(f) => entry && f.family === entry.family && f.weight === weight
|
|
3435
|
+
);
|
|
3436
|
+
return {
|
|
3437
|
+
weight,
|
|
3438
|
+
...clip.emphasis?.color ? { color: clip.emphasis.color } : {},
|
|
3439
|
+
...entry && !inBase ? {
|
|
3440
|
+
face: {
|
|
3441
|
+
family: entry.family,
|
|
3442
|
+
weight,
|
|
3443
|
+
url: fontFaceUrl2(entry.slug, weight)
|
|
3444
|
+
}
|
|
3445
|
+
} : {}
|
|
3446
|
+
};
|
|
3447
|
+
}
|
|
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;
|
|
3458
|
+
}
|
|
3459
|
+
if (run) w += measure(run, b ? emFont : font);
|
|
3460
|
+
return w;
|
|
3461
|
+
}
|
|
3355
3462
|
function overlayTokens(line) {
|
|
3356
3463
|
return line.match(/\S+\s*/g) ?? [line];
|
|
3357
3464
|
}
|
|
@@ -3402,9 +3509,10 @@ function resolveOverlayFx(clip, clipDuration) {
|
|
|
3402
3509
|
const spec = overlayFxSpec(clip) ?? clip.fx ?? null;
|
|
3403
3510
|
if (!spec) return null;
|
|
3404
3511
|
const unit = spec.unit ?? "block";
|
|
3405
|
-
const
|
|
3512
|
+
const text = overlayDisplayText(clip);
|
|
3513
|
+
const units = overlaySegments(text, unit);
|
|
3406
3514
|
const n = unit === "block" ? 1 : units.reduce((sum, line) => sum + line.length, 0);
|
|
3407
|
-
const upperN = unit === "line" && clip.maxWidth ? overlayLines(
|
|
3515
|
+
const upperN = unit === "line" && clip.maxWidth ? overlayLines(text).reduce(
|
|
3408
3516
|
(sum, line) => sum + overlayTokens(line).length,
|
|
3409
3517
|
0
|
|
3410
3518
|
) : n;
|
|
@@ -3451,13 +3559,13 @@ function overlayRect(clip, measure, frameW, frameH = 1080, mediaAspect) {
|
|
|
3451
3559
|
const style = resolveOverlayStyle(clip);
|
|
3452
3560
|
const font = overlayFontString(style, scale, 1);
|
|
3453
3561
|
const ls = style.letterSpacing * scale;
|
|
3454
|
-
const
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
) : overlayLines(
|
|
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);
|
|
3459
3567
|
let w = 0;
|
|
3460
|
-
for (const line of lines) w = Math.max(w,
|
|
3568
|
+
for (const line of lines) w = Math.max(w, width(line));
|
|
3461
3569
|
const lineH = style.size * scale * style.lineHeight;
|
|
3462
3570
|
const box = resolveOverlayBox(clip);
|
|
3463
3571
|
const padX = box ? box.padX * scale : 0;
|
|
@@ -3963,14 +4071,16 @@ function pinRectOnScreen(rect, t, layout, camera, zoomTrack) {
|
|
|
3963
4071
|
let cx = 0.5;
|
|
3964
4072
|
let cy = 0.5;
|
|
3965
4073
|
let centring;
|
|
4074
|
+
let screen;
|
|
3966
4075
|
if (zoomTrack && zoomTrack.keyframes.length) {
|
|
3967
4076
|
const z = sample(zoomTrack, t, lerpArray);
|
|
3968
4077
|
level = z[0];
|
|
3969
4078
|
cx = z[1];
|
|
3970
4079
|
cy = z[2];
|
|
3971
4080
|
if (z.length > 3) centring = z[3];
|
|
4081
|
+
if (z.length > 5) screen = { x: z[4], y: z[5] };
|
|
3972
4082
|
}
|
|
3973
|
-
const v = zoomView(level, cx, cy, layout, camera, centring);
|
|
4083
|
+
const v = zoomView(level, cx, cy, layout, camera, centring, screen);
|
|
3974
4084
|
const map = (nx, ny) => {
|
|
3975
4085
|
const px = layout.dx + nx * layout.dw;
|
|
3976
4086
|
const py = layout.dy + ny * layout.dh;
|
|
@@ -5012,6 +5122,19 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
5012
5122
|
} catch (e) {}
|
|
5013
5123
|
}
|
|
5014
5124
|
}
|
|
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
|
|
5131
|
+
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 () {})
|
|
5135
|
+
} catch (e) {}
|
|
5136
|
+
}
|
|
5137
|
+
}
|
|
5015
5138
|
}
|
|
5016
5139
|
ovC.save()
|
|
5017
5140
|
ovC.globalAlpha = olA
|
|
@@ -5027,6 +5150,69 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
5027
5150
|
ovC.textAlign = 'center'
|
|
5028
5151
|
ovC.textBaseline = 'middle'
|
|
5029
5152
|
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
|
|
5195
|
+
if (ol.stroke) {
|
|
5196
|
+
ovC.strokeStyle = ol.stroke.c
|
|
5197
|
+
ovC.lineWidth = ol.stroke.w * olMS * s
|
|
5198
|
+
ovC.lineJoin = 'round'
|
|
5199
|
+
ovC.strokeText(drun, dx, y)
|
|
5200
|
+
}
|
|
5201
|
+
ovC.fillText(drun, dx, y)
|
|
5202
|
+
dx += ovC.measureText(drun).width
|
|
5203
|
+
ovC.fillStyle = dfs
|
|
5204
|
+
drun = ''
|
|
5205
|
+
}
|
|
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
|
|
5212
|
+
}
|
|
5213
|
+
dflush()
|
|
5214
|
+
ovC.font = olFN
|
|
5215
|
+
}
|
|
5030
5216
|
var olLines = ol.lines || ['']
|
|
5031
5217
|
// maxWidth wrap (ol.mw = frame-width fraction): greedy over word tokens
|
|
5032
5218
|
// (/\\S+\\s*/ \u2014 the SAME tokenization fx uses, trailing spaces kept, so
|
|
@@ -5038,7 +5224,7 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
5038
5224
|
var olWrapped = []
|
|
5039
5225
|
for (var olwl = 0; olwl < olLines.length; olwl++) {
|
|
5040
5226
|
var olWLine = olLines[olwl]
|
|
5041
|
-
if (!olWLine ||
|
|
5227
|
+
if (!olWLine || olMeasure(olWLine) <= olWMax) {
|
|
5042
5228
|
olWrapped.push(olWLine)
|
|
5043
5229
|
continue
|
|
5044
5230
|
}
|
|
@@ -5046,7 +5232,7 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
5046
5232
|
var olCur = ''
|
|
5047
5233
|
for (var olti = 0; olti < olToks.length; olti++) {
|
|
5048
5234
|
if (!olCur) { olCur = olToks[olti]; continue }
|
|
5049
|
-
if (
|
|
5235
|
+
if (olMeasure(olCur + olToks[olti]) <= olWMax) {
|
|
5050
5236
|
olCur += olToks[olti]
|
|
5051
5237
|
} else {
|
|
5052
5238
|
olWrapped.push(olCur)
|
|
@@ -5096,10 +5282,10 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
5096
5282
|
olFx = { k: olFx.k, u: olFx.u, d: olFx.d, st: olFx.st, dur: olFx.dur, tt: olFx.tt, units: olRe, n: olReN }
|
|
5097
5283
|
}
|
|
5098
5284
|
var olLWs = null, olMaxW = 0
|
|
5099
|
-
if (ol.box || ol.align || olFx) {
|
|
5285
|
+
if (ol.box || ol.align || olFx || olEm) {
|
|
5100
5286
|
olLWs = []
|
|
5101
5287
|
for (var olwi = 0; olwi < olLines.length; olwi++) {
|
|
5102
|
-
var olw =
|
|
5288
|
+
var olw = olMeasure(olLines[olwi])
|
|
5103
5289
|
olLWs.push(olw)
|
|
5104
5290
|
if (olw > olMaxW) olMaxW = olw
|
|
5105
5291
|
}
|
|
@@ -5135,6 +5321,12 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
5135
5321
|
: (olMaxW - olLWs[ok]) / 2
|
|
5136
5322
|
}
|
|
5137
5323
|
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
|
+
}
|
|
5138
5330
|
if (ol.stroke) {
|
|
5139
5331
|
ovC.strokeStyle = ol.stroke.c
|
|
5140
5332
|
ovC.lineWidth = ol.stroke.w * olMS * s
|
|
@@ -5171,7 +5363,10 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
5171
5363
|
: olIdx
|
|
5172
5364
|
var olT2 = olT - olOrd * olFx.st
|
|
5173
5365
|
var olNext = olPref + olUs[ou]
|
|
5174
|
-
var olNW =
|
|
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
|
|
5175
5370
|
var olUW = olNW - olPW
|
|
5176
5371
|
var olUX = olXb + olPW
|
|
5177
5372
|
var olUA = 1, olUu = 1
|
|
@@ -5200,6 +5395,11 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
5200
5395
|
ovC.filter = 'blur(' + ((1 - olUu) * olPx * 0.12).toFixed(2) + 'px)'
|
|
5201
5396
|
}
|
|
5202
5397
|
}
|
|
5398
|
+
if (olEm) {
|
|
5399
|
+
olDraw(olUnit, olUX, olLY2, olUB)
|
|
5400
|
+
ovC.restore()
|
|
5401
|
+
continue
|
|
5402
|
+
}
|
|
5203
5403
|
if (ol.stroke) {
|
|
5204
5404
|
ovC.strokeStyle = ol.stroke.c
|
|
5205
5405
|
ovC.lineWidth = ol.stroke.w * olMS * s
|
|
@@ -5848,15 +6048,22 @@ function zoomTrackFromDoc(zoom, segments, style = ZOOM_STYLES[DEFAULT_ZOOM_STYLE
|
|
|
5848
6048
|
return ext ? [{ z, tIn: ext.start, tOut: ext.end }] : [];
|
|
5849
6049
|
});
|
|
5850
6050
|
const { keyframes, push: push2 } = trackEmitter();
|
|
6051
|
+
const placedAt = (z) => z.screen && z.focusMode !== "auto" ? z.screen : null;
|
|
6052
|
+
const placed = mapped.some(({ z }) => placedAt(z) !== null);
|
|
6053
|
+
const sc = (z) => {
|
|
6054
|
+
if (!placed) return [];
|
|
6055
|
+
const s = placedAt(z);
|
|
6056
|
+
return s ? [s.x, s.y] : [0.5, 0.5];
|
|
6057
|
+
};
|
|
5851
6058
|
let chained = false;
|
|
5852
6059
|
for (let i = 0; i < mapped.length; i++) {
|
|
5853
6060
|
const { z, tIn, tOut } = mapped[i];
|
|
5854
|
-
const entry = [clampZoomLevel(z.level), z.cx, z.cy, 1];
|
|
6061
|
+
const entry = [clampZoomLevel(z.level), z.cx, z.cy, 1, ...sc(z)];
|
|
5855
6062
|
let cur = entry;
|
|
5856
6063
|
const m = transitionMult(z.transition);
|
|
5857
6064
|
if (!chained) {
|
|
5858
6065
|
const startWanted = tIn - (style.rampIn - style.rampInOverlap) * m;
|
|
5859
|
-
const start = push2(startWanted, [1, z.cx, z.cy, 0], "none");
|
|
6066
|
+
const start = push2(startWanted, [1, z.cx, z.cy, 0, ...sc(z)], "none");
|
|
5860
6067
|
push2(
|
|
5861
6068
|
rampLanding(start, startWanted, style.rampIn * m, tOut),
|
|
5862
6069
|
entry,
|
|
@@ -5868,7 +6075,7 @@ function zoomTrackFromDoc(zoom, segments, style = ZOOM_STYLES[DEFAULT_ZOOM_STYLE
|
|
|
5868
6075
|
const eOut = sourceToTimeline(segments, e.t);
|
|
5869
6076
|
if (eOut === null || eOut <= tIn || eOut >= tOut) continue;
|
|
5870
6077
|
if (eOut - tIn < RAMP_FLOOR || tOut - eOut < RAMP_FLOOR) continue;
|
|
5871
|
-
const next2 = [cur[0], e.cx, e.cy, 1];
|
|
6078
|
+
const next2 = [cur[0], e.cx, e.cy, 1, ...cur.slice(4)];
|
|
5872
6079
|
if (e.path) {
|
|
5873
6080
|
if (!inPath) {
|
|
5874
6081
|
push2(Math.max(tIn, eOut - style.followRecenter), cur, "none");
|
|
@@ -5888,7 +6095,13 @@ function zoomTrackFromDoc(zoom, segments, style = ZOOM_STYLES[DEFAULT_ZOOM_STYLE
|
|
|
5888
6095
|
const landed = keyframes.at(-1)?.t;
|
|
5889
6096
|
if (drift > 0 && z.focusMode !== "auto" && !(z.followEvents ?? []).length && landed !== void 0 && tOut > landed) {
|
|
5890
6097
|
const grow = Math.min(HOLD_DRIFT_MAX, drift * (tOut - landed));
|
|
5891
|
-
cur = [
|
|
6098
|
+
cur = [
|
|
6099
|
+
clampZoomLevel(cur[0] * (1 + grow)),
|
|
6100
|
+
cur[1],
|
|
6101
|
+
cur[2],
|
|
6102
|
+
cur[3],
|
|
6103
|
+
...cur.slice(4)
|
|
6104
|
+
];
|
|
5892
6105
|
push2(tOut, cur, "linear");
|
|
5893
6106
|
} else {
|
|
5894
6107
|
push2(tOut, cur, "none");
|
|
@@ -5896,7 +6109,13 @@ function zoomTrackFromDoc(zoom, segments, style = ZOOM_STYLES[DEFAULT_ZOOM_STYLE
|
|
|
5896
6109
|
const next = mapped.at(i + 1);
|
|
5897
6110
|
if (next && next.tIn - tOut <= style.chainGap) {
|
|
5898
6111
|
const mNext = transitionMult(next.z.transition);
|
|
5899
|
-
const nextValue = [
|
|
6112
|
+
const nextValue = [
|
|
6113
|
+
clampZoomLevel(next.z.level),
|
|
6114
|
+
next.z.cx,
|
|
6115
|
+
next.z.cy,
|
|
6116
|
+
1,
|
|
6117
|
+
...sc(next.z)
|
|
6118
|
+
];
|
|
5900
6119
|
push2(
|
|
5901
6120
|
Math.max(
|
|
5902
6121
|
tOut + RAMP_FLOOR,
|
|
@@ -5912,7 +6131,7 @@ function zoomTrackFromDoc(zoom, segments, style = ZOOM_STYLES[DEFAULT_ZOOM_STYLE
|
|
|
5912
6131
|
} else {
|
|
5913
6132
|
push2(
|
|
5914
6133
|
tOut + style.rampOut * m,
|
|
5915
|
-
[1, cur[1], cur[2], 0],
|
|
6134
|
+
[1, cur[1], cur[2], 0, ...cur.slice(4)],
|
|
5916
6135
|
spanEase(z.ease, style.ease)
|
|
5917
6136
|
);
|
|
5918
6137
|
chained = false;
|
|
@@ -7140,7 +7359,7 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
7140
7359
|
// current zoom \u2014 a standard keyframe track in OUTPUT time (hold + arrival pairs
|
|
7141
7360
|
// expanded by the lowering), sampled with the shared deterministic interpolator.
|
|
7142
7361
|
// Sampled here, before the rect math, so a cover crop can follow it.
|
|
7143
|
-
var lvl = 1, zx = 0.5, zy = 0.5, zc = -1
|
|
7362
|
+
var lvl = 1, zx = 0.5, zy = 0.5, zc = -1, zsx = 0.5, zsy = 0.5
|
|
7144
7363
|
var zt = d.zoomTrack
|
|
7145
7364
|
if (zt && zt.keyframes && zt.keyframes.length) {
|
|
7146
7365
|
var z = TL.sample(zt, tpT, TL.lerpArray)
|
|
@@ -7149,6 +7368,9 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
7149
7368
|
// with the level); a three-component track (a document lowered before
|
|
7150
7369
|
// the component existed) falls back to the level band below.
|
|
7151
7370
|
if (z.length > 3) zc = z[3]
|
|
7371
|
+
// A span placed on screen carries where its target lands (frame
|
|
7372
|
+
// fractions); a track without them lands it at the centre.
|
|
7373
|
+
if (z.length > 5) { zsx = z[4]; zsy = z[5] }
|
|
7152
7374
|
}
|
|
7153
7375
|
var barH = bar.kind && bar.kind !== 'none' ? (bar.height || 44) * s2 : 0
|
|
7154
7376
|
var availW = Math.max(1, W - ipL - ipR), availH = Math.max(1, H - ipT - ipB - barH)
|
|
@@ -7209,6 +7431,10 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
7209
7431
|
var camStage = cfr.camera === 'stage'
|
|
7210
7432
|
var fx = dx + zx * dw, fy = dy + zy * dh
|
|
7211
7433
|
var wcx = fx, wcy = fy
|
|
7434
|
+
// The point the stage camera lands its focus on: the span's place on
|
|
7435
|
+
// screen, else the centre (0.5\xB7W is W / 2 exactly). MIRRORS zoomView's
|
|
7436
|
+
// screenPoint \u2014 change together.
|
|
7437
|
+
var cox = camStage ? zsx * W : W / 2, coy = camStage ? zsy * H : H / 2
|
|
7212
7438
|
if (lvl > 1.001 && !d.zoomSuppressed) {
|
|
7213
7439
|
if (camStage) {
|
|
7214
7440
|
var ctt = zc
|
|
@@ -7216,9 +7442,9 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
7216
7442
|
var ctu = Math.max(0, Math.min(1, (lvl - 1) / ${CAMERA_CENTRE_RAMP}))
|
|
7217
7443
|
ctt = ctu * ctu * (3 - 2 * ctu)
|
|
7218
7444
|
}
|
|
7219
|
-
wcx = fx + ((
|
|
7220
|
-
wcy = fy + ((
|
|
7221
|
-
c.translate(
|
|
7445
|
+
wcx = fx + ((cox - fx) / lvl) * (1 - ctt)
|
|
7446
|
+
wcy = fy + ((coy - fy) / lvl) * (1 - ctt)
|
|
7447
|
+
c.translate(cox, coy); c.scale(lvl, lvl); c.translate(-wcx, -wcy)
|
|
7222
7448
|
} else {
|
|
7223
7449
|
c.translate(fx, fy); c.scale(lvl, lvl); c.translate(-fx, -fy)
|
|
7224
7450
|
}
|
|
@@ -7618,7 +7844,7 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
7618
7844
|
if (trOut) {
|
|
7619
7845
|
var tqX = ax, tqY = ay
|
|
7620
7846
|
if (lvl > 1.001 && !d.zoomSuppressed) {
|
|
7621
|
-
if (camStage) { tqX =
|
|
7847
|
+
if (camStage) { tqX = cox + (ax - wcx) * lvl; tqY = coy + (ay - wcy) * lvl }
|
|
7622
7848
|
else { tqX = fx + (ax - fx) * lvl; tqY = fy + (ay - fy) * lvl }
|
|
7623
7849
|
}
|
|
7624
7850
|
if (tpG) trQa = [tqX, tqY]
|
|
@@ -7740,8 +7966,8 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
7740
7966
|
}
|
|
7741
7967
|
if (lvl > 1.001 && !d.zoomSuppressed) {
|
|
7742
7968
|
if (camStage) {
|
|
7743
|
-
pqAx =
|
|
7744
|
-
pqBx =
|
|
7969
|
+
pqAx = cox + (pqAx - wcx) * lvl; pqAy = coy + (pqAy - wcy) * lvl
|
|
7970
|
+
pqBx = cox + (pqBx - wcx) * lvl; pqBy = coy + (pqBy - wcy) * lvl
|
|
7745
7971
|
} else {
|
|
7746
7972
|
pqAx = fx + (pqAx - fx) * lvl; pqAy = fy + (pqAy - fy) * lvl
|
|
7747
7973
|
pqBx = fx + (pqBx - fx) * lvl; pqBy = fy + (pqBy - fy) * lvl
|
|
@@ -8050,10 +8276,27 @@ function studioLayerData(layers, duration, pins) {
|
|
|
8050
8276
|
}
|
|
8051
8277
|
const st = resolveOverlayStyle(o);
|
|
8052
8278
|
const bx = resolveOverlayBox(o);
|
|
8279
|
+
const em = resolveEmphasis(o);
|
|
8053
8280
|
return {
|
|
8054
8281
|
...base,
|
|
8055
8282
|
text: o.text,
|
|
8056
|
-
lines:
|
|
8283
|
+
// The DISPLAYED lines: emphasis marks inline, else the source.
|
|
8284
|
+
lines: overlayLines(overlayDisplayText(o)),
|
|
8285
|
+
// Emphasis set (weight, colour, face to lazy-load), only when
|
|
8286
|
+
// the text marks a word: an unmarked clip's data is unchanged.
|
|
8287
|
+
...em ? {
|
|
8288
|
+
em: {
|
|
8289
|
+
w: em.weight,
|
|
8290
|
+
...em.color ? { c: em.color } : {},
|
|
8291
|
+
...em.face ? {
|
|
8292
|
+
face: {
|
|
8293
|
+
f: em.face.family,
|
|
8294
|
+
w: em.face.weight,
|
|
8295
|
+
u: em.face.url
|
|
8296
|
+
}
|
|
8297
|
+
} : {}
|
|
8298
|
+
}
|
|
8299
|
+
} : {},
|
|
8057
8300
|
fs: st.size,
|
|
8058
8301
|
weight: st.weight,
|
|
8059
8302
|
stack: st.stack,
|
|
@@ -8297,7 +8540,15 @@ function loweredZoomSpans(doc, rated, transitions, layout, camera, zoomStyle) {
|
|
|
8297
8540
|
}
|
|
8298
8541
|
return {
|
|
8299
8542
|
...z,
|
|
8300
|
-
|
|
8543
|
+
// A span placed on screen is the author's composition: no cover band.
|
|
8544
|
+
...clampFocus(
|
|
8545
|
+
z.cx,
|
|
8546
|
+
z.cy,
|
|
8547
|
+
clampZoomLevel(z.level),
|
|
8548
|
+
layout,
|
|
8549
|
+
camera,
|
|
8550
|
+
z.focusMode !== "auto" ? z.screen : void 0
|
|
8551
|
+
)
|
|
8301
8552
|
};
|
|
8302
8553
|
});
|
|
8303
8554
|
}
|
|
@@ -8850,8 +9101,9 @@ function sceneChanges(bins, opts = {}) {
|
|
|
8850
9101
|
// src/digest/framing.ts
|
|
8851
9102
|
function zoomWindow(span, layout, camera = "card") {
|
|
8852
9103
|
const L = Math.max(1, span.level);
|
|
8853
|
-
const
|
|
8854
|
-
const
|
|
9104
|
+
const screen = span.focusMode !== "auto" ? span.screen : void 0;
|
|
9105
|
+
const { cx, cy } = clampFocus(span.cx, span.cy, L, layout, camera, screen);
|
|
9106
|
+
const v = zoomViewport(L, cx, cy, layout, camera, void 0, screen);
|
|
8855
9107
|
const px0 = v.x * layout.W;
|
|
8856
9108
|
const px1 = (v.x + v.w) * layout.W;
|
|
8857
9109
|
const py0 = v.y * layout.H;
|
|
@@ -11577,6 +11829,8 @@ export {
|
|
|
11577
11829
|
DRAG_FIT_LEVEL,
|
|
11578
11830
|
DRAG_MIN_S,
|
|
11579
11831
|
DRAG_MIN_TRAVEL_FRAC,
|
|
11832
|
+
EM_CLOSE,
|
|
11833
|
+
EM_OPEN,
|
|
11580
11834
|
END_CARD_FROM,
|
|
11581
11835
|
END_CARD_RECEDE,
|
|
11582
11836
|
END_CARD_SECONDS,
|
|
@@ -11808,6 +12062,7 @@ export {
|
|
|
11808
12062
|
lowerProgramDoc,
|
|
11809
12063
|
lowerStudioDoc,
|
|
11810
12064
|
lowerToComposition,
|
|
12065
|
+
measureEmphasized,
|
|
11811
12066
|
mediaAtOutput,
|
|
11812
12067
|
mediaDuration,
|
|
11813
12068
|
mediaFrame,
|
|
@@ -11831,6 +12086,7 @@ export {
|
|
|
11831
12086
|
outputEnd,
|
|
11832
12087
|
outputRangeToSource,
|
|
11833
12088
|
overlapFraction,
|
|
12089
|
+
overlayDisplayText,
|
|
11834
12090
|
overlayFaceFor,
|
|
11835
12091
|
overlayFontFaces,
|
|
11836
12092
|
overlayFontString,
|
|
@@ -11845,6 +12101,7 @@ export {
|
|
|
11845
12101
|
ownFreezes,
|
|
11846
12102
|
pageDisplayUrl,
|
|
11847
12103
|
parseCssColour,
|
|
12104
|
+
parseEmphasis,
|
|
11848
12105
|
parsePoseId,
|
|
11849
12106
|
pickStyle,
|
|
11850
12107
|
pinBox,
|
|
@@ -11872,6 +12129,7 @@ export {
|
|
|
11872
12129
|
relativeLuminance,
|
|
11873
12130
|
removeSourceRange,
|
|
11874
12131
|
removeSpeedInRange,
|
|
12132
|
+
resolveEmphasis,
|
|
11875
12133
|
resolveExportSize,
|
|
11876
12134
|
resolveOverlayBox,
|
|
11877
12135
|
resolveOverlayStyle,
|
|
@@ -11892,6 +12150,7 @@ export {
|
|
|
11892
12150
|
smoothCursor,
|
|
11893
12151
|
spanOutputExtent,
|
|
11894
12152
|
speedLane,
|
|
12153
|
+
stripEmphasis,
|
|
11895
12154
|
studioAudioPlan,
|
|
11896
12155
|
studioEntry,
|
|
11897
12156
|
studioLayerData,
|