@vosjs/studio-core 0.33.3 → 0.35.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.d.ts +113 -6
- package/dist/index.js +412 -88
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1420,7 +1420,7 @@ function docStillTime(doc) {
|
|
|
1420
1420
|
}
|
|
1421
1421
|
}
|
|
1422
1422
|
if (!(footage > 0)) return r3(clampOut(STILL_DEFAULT));
|
|
1423
|
-
let hero =
|
|
1423
|
+
let hero = entranceEnd(cardEnter(doc.frame));
|
|
1424
1424
|
for (const clip of doc.overlays ?? []) {
|
|
1425
1425
|
if (clip.start > OPENING_WINDOW) continue;
|
|
1426
1426
|
const step = enterOf(clip.anim);
|
|
@@ -1488,6 +1488,14 @@ function entranceSeconds(e) {
|
|
|
1488
1488
|
if (!e || e.kind === "none") return 0;
|
|
1489
1489
|
return Math.max(0.2, Math.min(3, e.seconds ?? ENTRANCE_SECONDS));
|
|
1490
1490
|
}
|
|
1491
|
+
function entranceAt(e) {
|
|
1492
|
+
if (!entranceSeconds(e)) return 0;
|
|
1493
|
+
const at = e?.at;
|
|
1494
|
+
return typeof at === "number" && at > 0 ? Math.min(30, at) : 0;
|
|
1495
|
+
}
|
|
1496
|
+
function entranceEnd(e) {
|
|
1497
|
+
return entranceAt(e) + entranceSeconds(e);
|
|
1498
|
+
}
|
|
1491
1499
|
function exitSeconds(e) {
|
|
1492
1500
|
if (!e || e.kind === "none") return 0;
|
|
1493
1501
|
const house = e.kind === "fade" ? CARD_FADE_SECONDS : END_CARD_RECEDE;
|
|
@@ -1551,22 +1559,26 @@ function withHolds(docSegments, rated) {
|
|
|
1551
1559
|
function entranceTiltKeyframes(e) {
|
|
1552
1560
|
const s = entranceSeconds(e);
|
|
1553
1561
|
if (!s || e?.kind !== "tilt-in") return [];
|
|
1562
|
+
const a = entranceAt(e);
|
|
1554
1563
|
return [
|
|
1555
1564
|
{ t: 0, value: [...TILT_IN_POSE], ease: "none" },
|
|
1556
|
-
{ t:
|
|
1565
|
+
...a ? [{ t: a, value: [...TILT_IN_POSE], ease: "none" }] : [],
|
|
1566
|
+
{ t: a + s, value: [0, 0], ease: "power3.out" }
|
|
1557
1567
|
];
|
|
1558
1568
|
}
|
|
1559
1569
|
function entranceZoomKeyframes(e) {
|
|
1560
1570
|
const s = entranceSeconds(e);
|
|
1561
1571
|
if (!s) return [];
|
|
1572
|
+
const a = entranceAt(e);
|
|
1562
1573
|
if (e?.kind === "pull-out")
|
|
1563
1574
|
return [
|
|
1564
1575
|
{ t: 0, value: [PULL_OUT_LEVEL, 0.5, 0.42], ease: "none" },
|
|
1565
|
-
{ t:
|
|
1576
|
+
...a ? [{ t: a, value: [PULL_OUT_LEVEL, 0.5, 0.42], ease: "none" }] : [],
|
|
1577
|
+
{ t: a + s, value: [1, 0.5, 0.5], ease: "power3.out" }
|
|
1566
1578
|
];
|
|
1567
1579
|
return [
|
|
1568
1580
|
{ t: 0, value: [1, 0.5, 0.5], ease: "none" },
|
|
1569
|
-
{ t: s, value: [1, 0.5, 0.5], ease: "none" }
|
|
1581
|
+
{ t: a + s, value: [1, 0.5, 0.5], ease: "none" }
|
|
1570
1582
|
];
|
|
1571
1583
|
}
|
|
1572
1584
|
function prependEntrance(track, head) {
|
|
@@ -1582,15 +1594,18 @@ var RECEDE_MID = [0.91, -0.018, 0.8];
|
|
|
1582
1594
|
function cardPoseTrack(enter, exit, footageEnd, outputEnd2 = footageEnd) {
|
|
1583
1595
|
const keyframes = [];
|
|
1584
1596
|
const s = entranceSeconds(enter);
|
|
1597
|
+
const a = entranceAt(enter);
|
|
1585
1598
|
if (s) {
|
|
1586
1599
|
const from = enter?.kind === "rise" ? [0.96, 0.08, 0.7] : enter?.kind === "pull-out" || enter?.kind === "fade" || enter?.kind === "slide" ? enter.kind === "fade" ? [1, 0, 0] : [1, 0, 1] : [0.94, 0.05, 0.7];
|
|
1600
|
+
if (a) from[2] = 0;
|
|
1587
1601
|
keyframes.push({ t: 0, value: from, ease: "none" });
|
|
1588
|
-
keyframes.push({ t:
|
|
1602
|
+
if (a) keyframes.push({ t: a, value: [...from], ease: "none" });
|
|
1603
|
+
keyframes.push({ t: a + s, value: [1, 0, 1], ease: "power3.out" });
|
|
1589
1604
|
}
|
|
1590
1605
|
const x = exitSeconds(exit);
|
|
1591
1606
|
let last = [1, 0, 1];
|
|
1592
1607
|
if (x > 0 && footageEnd > 0) {
|
|
1593
|
-
const t0 = round3(Math.max(s, footageEnd - x));
|
|
1608
|
+
const t0 = round3(Math.max(a + s, footageEnd - x));
|
|
1594
1609
|
const t1 = round3(Math.max(t0 + 0.05, footageEnd));
|
|
1595
1610
|
last = exitPose(exit);
|
|
1596
1611
|
keyframes.push({ t: t0, value: [1, 0, 1], ease: "none" });
|
|
@@ -1812,6 +1827,7 @@ function liveZoomStyleName(name) {
|
|
|
1812
1827
|
if (name in ZOOM_STYLES) return name;
|
|
1813
1828
|
return RETIRED_ZOOM_STYLES[name]?.style ?? null;
|
|
1814
1829
|
}
|
|
1830
|
+
var HOLD_DRIFT_MAX = 0.15;
|
|
1815
1831
|
var PUMP_REST = 1;
|
|
1816
1832
|
function pumpFreeChainGap(p) {
|
|
1817
1833
|
return p.rampOut + (p.rampIn - p.rampInOverlap) + PUMP_REST;
|
|
@@ -2685,7 +2701,10 @@ function camBubbleRect(cam, W, H = 1080) {
|
|
|
2685
2701
|
radius: cam.shape === "rounded" ? (cam.radius ?? 18) * s : size / 2
|
|
2686
2702
|
};
|
|
2687
2703
|
}
|
|
2688
|
-
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
|
+
}
|
|
2689
2708
|
if (level <= 1.001) {
|
|
2690
2709
|
return { minX: 0.5, maxX: 0.5, minY: 0.5, maxY: 0.5 };
|
|
2691
2710
|
}
|
|
@@ -2748,8 +2767,8 @@ function stageAxisBounds(anchorOff, anchorSize, coverOff, coverSize, viewport, l
|
|
|
2748
2767
|
}
|
|
2749
2768
|
return { min: clamp013(lo), max: clamp013(hi) };
|
|
2750
2769
|
}
|
|
2751
|
-
function clampFocus(cx, cy, level, layout, camera = "card") {
|
|
2752
|
-
const b = focusBounds(level, layout, camera);
|
|
2770
|
+
function clampFocus(cx, cy, level, layout, camera = "card", screen) {
|
|
2771
|
+
const b = focusBounds(level, layout, camera, screen);
|
|
2753
2772
|
return {
|
|
2754
2773
|
cx: Math.min(b.maxX, Math.max(b.minX, cx)),
|
|
2755
2774
|
cy: Math.min(b.maxY, Math.max(b.minY, cy))
|
|
@@ -2758,6 +2777,9 @@ function clampFocus(cx, cy, level, layout, camera = "card") {
|
|
|
2758
2777
|
function clamp013(v) {
|
|
2759
2778
|
return Math.max(0, Math.min(1, v));
|
|
2760
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
|
+
}
|
|
2761
2783
|
function cameraModel(frame) {
|
|
2762
2784
|
return frame?.camera === "stage" ? "stage" : "card";
|
|
2763
2785
|
}
|
|
@@ -2767,34 +2789,36 @@ function cameraCentring(level) {
|
|
|
2767
2789
|
const u = clamp013((level - 1) / CAMERA_CENTRE_RAMP);
|
|
2768
2790
|
return u * u * (3 - 2 * u);
|
|
2769
2791
|
}
|
|
2770
|
-
function zoomView(level, cx, cy, layout, camera = "card", centring) {
|
|
2792
|
+
function zoomView(level, cx, cy, layout, camera = "card", centring, screen) {
|
|
2771
2793
|
const L = Math.max(1, level);
|
|
2772
2794
|
const fx = layout.dx + cx * layout.dw;
|
|
2773
2795
|
const fy = layout.dy + cy * layout.dh;
|
|
2774
|
-
const ox = layout
|
|
2775
|
-
const oy = layout.H / 2;
|
|
2796
|
+
const { ox, oy } = screenPoint(layout, camera, screen);
|
|
2776
2797
|
const t = camera === "stage" ? clamp013(centring ?? 1) : 0;
|
|
2777
2798
|
const wcx = fx + (ox - fx) / L * (1 - t);
|
|
2778
2799
|
const wcy = fy + (oy - fy) / L * (1 - t);
|
|
2779
2800
|
return { level: L, fx, fy, wcx, wcy, ox, oy };
|
|
2780
2801
|
}
|
|
2781
|
-
function zoomViewport(level, cx, cy, layout, camera = "card", centring) {
|
|
2782
|
-
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);
|
|
2783
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;
|
|
2784
2807
|
return {
|
|
2785
|
-
x: (
|
|
2786
|
-
y: (
|
|
2808
|
+
x: (mx - layout.W / (2 * v.level)) / layout.W,
|
|
2809
|
+
y: (my - layout.H / (2 * v.level)) / layout.H,
|
|
2787
2810
|
w: size,
|
|
2788
2811
|
h: size
|
|
2789
2812
|
};
|
|
2790
2813
|
}
|
|
2791
|
-
function focusForViewportCentre(cX, cY, level, layout, camera = "card", centring) {
|
|
2814
|
+
function focusForViewportCentre(cX, cY, level, layout, camera = "card", centring, screen) {
|
|
2792
2815
|
const L = Math.max(1, level);
|
|
2793
2816
|
const t = camera === "stage" ? clamp013(centring ?? 1) : 0;
|
|
2794
2817
|
const a = 1 - (1 - t) / L;
|
|
2795
2818
|
if (a <= 1e-6) return { cx: 0.5, cy: 0.5 };
|
|
2796
|
-
const
|
|
2797
|
-
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;
|
|
2798
2822
|
return { cx: (fx - layout.dx) / layout.dw, cy: (fy - layout.dy) / layout.dh };
|
|
2799
2823
|
}
|
|
2800
2824
|
function levelForFocusFraction(frac) {
|
|
@@ -3321,14 +3345,17 @@ function overlayFaceFor(clip) {
|
|
|
3321
3345
|
function overlayFontFaces(doc) {
|
|
3322
3346
|
const faces = [...OVERLAY_FONT_FACES];
|
|
3323
3347
|
const seen = new Set(faces.map((f) => `${f.family}|${f.weight}`));
|
|
3324
|
-
|
|
3325
|
-
if (
|
|
3326
|
-
const face = overlayFaceFor(o);
|
|
3327
|
-
if (!face) continue;
|
|
3348
|
+
const add = (face) => {
|
|
3349
|
+
if (!face) return;
|
|
3328
3350
|
const key = `${face.family}|${face.weight}`;
|
|
3329
|
-
if (seen.has(key))
|
|
3351
|
+
if (seen.has(key)) return;
|
|
3330
3352
|
seen.add(key);
|
|
3331
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);
|
|
3332
3359
|
}
|
|
3333
3360
|
return faces;
|
|
3334
3361
|
}
|
|
@@ -3336,6 +3363,102 @@ function overlayLines(text) {
|
|
|
3336
3363
|
const lines = text.split("\n");
|
|
3337
3364
|
return lines.length ? lines : [""];
|
|
3338
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
|
+
}
|
|
3339
3462
|
function overlayTokens(line) {
|
|
3340
3463
|
return line.match(/\S+\s*/g) ?? [line];
|
|
3341
3464
|
}
|
|
@@ -3386,9 +3509,10 @@ function resolveOverlayFx(clip, clipDuration) {
|
|
|
3386
3509
|
const spec = overlayFxSpec(clip) ?? clip.fx ?? null;
|
|
3387
3510
|
if (!spec) return null;
|
|
3388
3511
|
const unit = spec.unit ?? "block";
|
|
3389
|
-
const
|
|
3512
|
+
const text = overlayDisplayText(clip);
|
|
3513
|
+
const units = overlaySegments(text, unit);
|
|
3390
3514
|
const n = unit === "block" ? 1 : units.reduce((sum, line) => sum + line.length, 0);
|
|
3391
|
-
const upperN = unit === "line" && clip.maxWidth ? overlayLines(
|
|
3515
|
+
const upperN = unit === "line" && clip.maxWidth ? overlayLines(text).reduce(
|
|
3392
3516
|
(sum, line) => sum + overlayTokens(line).length,
|
|
3393
3517
|
0
|
|
3394
3518
|
) : n;
|
|
@@ -3435,13 +3559,13 @@ function overlayRect(clip, measure, frameW, frameH = 1080, mediaAspect) {
|
|
|
3435
3559
|
const style = resolveOverlayStyle(clip);
|
|
3436
3560
|
const font = overlayFontString(style, scale, 1);
|
|
3437
3561
|
const ls = style.letterSpacing * scale;
|
|
3438
|
-
const
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
) : 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);
|
|
3443
3567
|
let w = 0;
|
|
3444
|
-
for (const line of lines) w = Math.max(w,
|
|
3568
|
+
for (const line of lines) w = Math.max(w, width(line));
|
|
3445
3569
|
const lineH = style.size * scale * style.lineHeight;
|
|
3446
3570
|
const box = resolveOverlayBox(clip);
|
|
3447
3571
|
const padX = box ? box.padX * scale : 0;
|
|
@@ -3590,7 +3714,7 @@ function docTransitions(doc) {
|
|
|
3590
3714
|
if (open?.kind === "slide" && extents.length) {
|
|
3591
3715
|
const first = extents[0];
|
|
3592
3716
|
const m = move(open, "enter", Math.max(0, first.end - first.start) / 2);
|
|
3593
|
-
if (m) out.push({ t:
|
|
3717
|
+
if (m) out.push({ t: round32(entranceAt(open)), d: m.d, in: m });
|
|
3594
3718
|
}
|
|
3595
3719
|
for (let i = 0; i + 1 < segments.length; i++) {
|
|
3596
3720
|
const a = segments[i];
|
|
@@ -3947,14 +4071,16 @@ function pinRectOnScreen(rect, t, layout, camera, zoomTrack) {
|
|
|
3947
4071
|
let cx = 0.5;
|
|
3948
4072
|
let cy = 0.5;
|
|
3949
4073
|
let centring;
|
|
4074
|
+
let screen;
|
|
3950
4075
|
if (zoomTrack && zoomTrack.keyframes.length) {
|
|
3951
4076
|
const z = sample(zoomTrack, t, lerpArray);
|
|
3952
4077
|
level = z[0];
|
|
3953
4078
|
cx = z[1];
|
|
3954
4079
|
cy = z[2];
|
|
3955
4080
|
if (z.length > 3) centring = z[3];
|
|
4081
|
+
if (z.length > 5) screen = { x: z[4], y: z[5] };
|
|
3956
4082
|
}
|
|
3957
|
-
const v = zoomView(level, cx, cy, layout, camera, centring);
|
|
4083
|
+
const v = zoomView(level, cx, cy, layout, camera, centring, screen);
|
|
3958
4084
|
const map = (nx, ny) => {
|
|
3959
4085
|
const px = layout.dx + nx * layout.dw;
|
|
3960
4086
|
const py = layout.dy + ny * layout.dh;
|
|
@@ -4996,6 +5122,19 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
4996
5122
|
} catch (e) {}
|
|
4997
5123
|
}
|
|
4998
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
|
+
}
|
|
4999
5138
|
}
|
|
5000
5139
|
ovC.save()
|
|
5001
5140
|
ovC.globalAlpha = olA
|
|
@@ -5011,6 +5150,69 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
5011
5150
|
ovC.textAlign = 'center'
|
|
5012
5151
|
ovC.textBaseline = 'middle'
|
|
5013
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
|
+
}
|
|
5014
5216
|
var olLines = ol.lines || ['']
|
|
5015
5217
|
// maxWidth wrap (ol.mw = frame-width fraction): greedy over word tokens
|
|
5016
5218
|
// (/\\S+\\s*/ \u2014 the SAME tokenization fx uses, trailing spaces kept, so
|
|
@@ -5022,7 +5224,7 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
5022
5224
|
var olWrapped = []
|
|
5023
5225
|
for (var olwl = 0; olwl < olLines.length; olwl++) {
|
|
5024
5226
|
var olWLine = olLines[olwl]
|
|
5025
|
-
if (!olWLine ||
|
|
5227
|
+
if (!olWLine || olMeasure(olWLine) <= olWMax) {
|
|
5026
5228
|
olWrapped.push(olWLine)
|
|
5027
5229
|
continue
|
|
5028
5230
|
}
|
|
@@ -5030,7 +5232,7 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
5030
5232
|
var olCur = ''
|
|
5031
5233
|
for (var olti = 0; olti < olToks.length; olti++) {
|
|
5032
5234
|
if (!olCur) { olCur = olToks[olti]; continue }
|
|
5033
|
-
if (
|
|
5235
|
+
if (olMeasure(olCur + olToks[olti]) <= olWMax) {
|
|
5034
5236
|
olCur += olToks[olti]
|
|
5035
5237
|
} else {
|
|
5036
5238
|
olWrapped.push(olCur)
|
|
@@ -5080,10 +5282,10 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
5080
5282
|
olFx = { k: olFx.k, u: olFx.u, d: olFx.d, st: olFx.st, dur: olFx.dur, tt: olFx.tt, units: olRe, n: olReN }
|
|
5081
5283
|
}
|
|
5082
5284
|
var olLWs = null, olMaxW = 0
|
|
5083
|
-
if (ol.box || ol.align || olFx) {
|
|
5285
|
+
if (ol.box || ol.align || olFx || olEm) {
|
|
5084
5286
|
olLWs = []
|
|
5085
5287
|
for (var olwi = 0; olwi < olLines.length; olwi++) {
|
|
5086
|
-
var olw =
|
|
5288
|
+
var olw = olMeasure(olLines[olwi])
|
|
5087
5289
|
olLWs.push(olw)
|
|
5088
5290
|
if (olw > olMaxW) olMaxW = olw
|
|
5089
5291
|
}
|
|
@@ -5119,6 +5321,12 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
5119
5321
|
: (olMaxW - olLWs[ok]) / 2
|
|
5120
5322
|
}
|
|
5121
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
|
+
}
|
|
5122
5330
|
if (ol.stroke) {
|
|
5123
5331
|
ovC.strokeStyle = ol.stroke.c
|
|
5124
5332
|
ovC.lineWidth = ol.stroke.w * olMS * s
|
|
@@ -5155,7 +5363,10 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
5155
5363
|
: olIdx
|
|
5156
5364
|
var olT2 = olT - olOrd * olFx.st
|
|
5157
5365
|
var olNext = olPref + olUs[ou]
|
|
5158
|
-
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
|
|
5159
5370
|
var olUW = olNW - olPW
|
|
5160
5371
|
var olUX = olXb + olPW
|
|
5161
5372
|
var olUA = 1, olUu = 1
|
|
@@ -5184,6 +5395,11 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
5184
5395
|
ovC.filter = 'blur(' + ((1 - olUu) * olPx * 0.12).toFixed(2) + 'px)'
|
|
5185
5396
|
}
|
|
5186
5397
|
}
|
|
5398
|
+
if (olEm) {
|
|
5399
|
+
olDraw(olUnit, olUX, olLY2, olUB)
|
|
5400
|
+
ovC.restore()
|
|
5401
|
+
continue
|
|
5402
|
+
}
|
|
5187
5403
|
if (ol.stroke) {
|
|
5188
5404
|
ovC.strokeStyle = ol.stroke.c
|
|
5189
5405
|
ovC.lineWidth = ol.stroke.w * olMS * s
|
|
@@ -5832,15 +6048,22 @@ function zoomTrackFromDoc(zoom, segments, style = ZOOM_STYLES[DEFAULT_ZOOM_STYLE
|
|
|
5832
6048
|
return ext ? [{ z, tIn: ext.start, tOut: ext.end }] : [];
|
|
5833
6049
|
});
|
|
5834
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
|
+
};
|
|
5835
6058
|
let chained = false;
|
|
5836
6059
|
for (let i = 0; i < mapped.length; i++) {
|
|
5837
6060
|
const { z, tIn, tOut } = mapped[i];
|
|
5838
|
-
const entry = [clampZoomLevel(z.level), z.cx, z.cy, 1];
|
|
6061
|
+
const entry = [clampZoomLevel(z.level), z.cx, z.cy, 1, ...sc(z)];
|
|
5839
6062
|
let cur = entry;
|
|
5840
6063
|
const m = transitionMult(z.transition);
|
|
5841
6064
|
if (!chained) {
|
|
5842
6065
|
const startWanted = tIn - (style.rampIn - style.rampInOverlap) * m;
|
|
5843
|
-
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");
|
|
5844
6067
|
push2(
|
|
5845
6068
|
rampLanding(start, startWanted, style.rampIn * m, tOut),
|
|
5846
6069
|
entry,
|
|
@@ -5852,7 +6075,7 @@ function zoomTrackFromDoc(zoom, segments, style = ZOOM_STYLES[DEFAULT_ZOOM_STYLE
|
|
|
5852
6075
|
const eOut = sourceToTimeline(segments, e.t);
|
|
5853
6076
|
if (eOut === null || eOut <= tIn || eOut >= tOut) continue;
|
|
5854
6077
|
if (eOut - tIn < RAMP_FLOOR || tOut - eOut < RAMP_FLOOR) continue;
|
|
5855
|
-
const next2 = [cur[0], e.cx, e.cy, 1];
|
|
6078
|
+
const next2 = [cur[0], e.cx, e.cy, 1, ...cur.slice(4)];
|
|
5856
6079
|
if (e.path) {
|
|
5857
6080
|
if (!inPath) {
|
|
5858
6081
|
push2(Math.max(tIn, eOut - style.followRecenter), cur, "none");
|
|
@@ -5868,11 +6091,31 @@ function zoomTrackFromDoc(zoom, segments, style = ZOOM_STYLES[DEFAULT_ZOOM_STYLE
|
|
|
5868
6091
|
}
|
|
5869
6092
|
cur = next2;
|
|
5870
6093
|
}
|
|
5871
|
-
|
|
6094
|
+
const drift = Math.max(0, style.holdDrift ?? 0);
|
|
6095
|
+
const landed = keyframes.at(-1)?.t;
|
|
6096
|
+
if (drift > 0 && z.focusMode !== "auto" && !(z.followEvents ?? []).length && landed !== void 0 && tOut > landed) {
|
|
6097
|
+
const grow = Math.min(HOLD_DRIFT_MAX, drift * (tOut - landed));
|
|
6098
|
+
cur = [
|
|
6099
|
+
clampZoomLevel(cur[0] * (1 + grow)),
|
|
6100
|
+
cur[1],
|
|
6101
|
+
cur[2],
|
|
6102
|
+
cur[3],
|
|
6103
|
+
...cur.slice(4)
|
|
6104
|
+
];
|
|
6105
|
+
push2(tOut, cur, "linear");
|
|
6106
|
+
} else {
|
|
6107
|
+
push2(tOut, cur, "none");
|
|
6108
|
+
}
|
|
5872
6109
|
const next = mapped.at(i + 1);
|
|
5873
6110
|
if (next && next.tIn - tOut <= style.chainGap) {
|
|
5874
6111
|
const mNext = transitionMult(next.z.transition);
|
|
5875
|
-
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
|
+
];
|
|
5876
6119
|
push2(
|
|
5877
6120
|
Math.max(
|
|
5878
6121
|
tOut + RAMP_FLOOR,
|
|
@@ -5888,7 +6131,7 @@ function zoomTrackFromDoc(zoom, segments, style = ZOOM_STYLES[DEFAULT_ZOOM_STYLE
|
|
|
5888
6131
|
} else {
|
|
5889
6132
|
push2(
|
|
5890
6133
|
tOut + style.rampOut * m,
|
|
5891
|
-
[1, cur[1], cur[2], 0],
|
|
6134
|
+
[1, cur[1], cur[2], 0, ...cur.slice(4)],
|
|
5892
6135
|
spanEase(z.ease, style.ease)
|
|
5893
6136
|
);
|
|
5894
6137
|
chained = false;
|
|
@@ -6930,37 +7173,71 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
6930
7173
|
bgC.fillStyle = '#0b0b0c'
|
|
6931
7174
|
bgC.fillStyle = (function () {
|
|
6932
7175
|
var bgcss = frame.background || '#0b0b0c'
|
|
6933
|
-
if (typeof bgcss
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
7176
|
+
if (typeof bgcss !== 'string') return '#0b0b0c'
|
|
7177
|
+
var gKind = bgcss.indexOf('linear-gradient') === 0 ? 'linear' : bgcss.indexOf('radial-gradient') === 0 ? 'radial' : ''
|
|
7178
|
+
if (!gKind) return bgcss
|
|
7179
|
+
// Split on TOP-LEVEL commas only: an rgba() or hsl() stop has commas of
|
|
7180
|
+
// its own, and splitting inside one hands canvas half a colour.
|
|
7181
|
+
var gin = bgcss.substring(bgcss.indexOf('(') + 1, bgcss.lastIndexOf(')'))
|
|
7182
|
+
var gps = [], gDepth = 0, gCur = ''
|
|
7183
|
+
for (var gi = 0; gi < gin.length; gi++) {
|
|
7184
|
+
var gch = gin.charAt(gi)
|
|
7185
|
+
if (gch === '(') gDepth++
|
|
7186
|
+
else if (gch === ')') gDepth--
|
|
7187
|
+
if (gch === ',' && gDepth === 0) { gps.push(gCur.trim()); gCur = '' } else gCur += gch
|
|
7188
|
+
}
|
|
7189
|
+
gps.push(gCur.trim())
|
|
7190
|
+
// The geometry (an angle, a side, a shape and centre) leads; every other
|
|
7191
|
+
// part is a STOP, a colour with an optional position ('#2b6f64 40%').
|
|
7192
|
+
var ang = 135, rcx = 0.5, rcy = 0.5, gStops = []
|
|
7193
|
+
for (var gj = 0; gj < gps.length; gj++) {
|
|
7194
|
+
var gp = gps[gj]
|
|
7195
|
+
if (!gp) continue
|
|
7196
|
+
if (gKind === 'linear' && gj === 0 && gp.indexOf('deg') >= 0) { ang = parseFloat(gp); continue }
|
|
7197
|
+
if (gKind === 'linear' && gj === 0 && gp.indexOf('to ') === 0) {
|
|
7198
|
+
var tdx = gp.indexOf('right') >= 0 ? 1 : gp.indexOf('left') >= 0 ? -1 : 0
|
|
7199
|
+
var tdy = gp.indexOf('bottom') >= 0 ? 1 : gp.indexOf('top') >= 0 ? -1 : 0
|
|
7200
|
+
ang = Math.atan2(tdx, -tdy) * 180 / Math.PI
|
|
7201
|
+
continue
|
|
7202
|
+
}
|
|
7203
|
+
if (gKind === 'radial' && gj === 0 && (gp.indexOf('circle') === 0 || gp.indexOf('ellipse') === 0 || gp.indexOf('at ') === 0 || gp.indexOf('closest') === 0 || gp.indexOf('farthest') === 0)) {
|
|
7204
|
+
var rat = /at\\s+([\\d.]+)%\\s+([\\d.]+)%/.exec(gp)
|
|
7205
|
+
if (rat) { rcx = parseFloat(rat[1]) / 100; rcy = parseFloat(rat[2]) / 100 }
|
|
7206
|
+
continue
|
|
6940
7207
|
}
|
|
6941
|
-
|
|
7208
|
+
var gSp = gp.lastIndexOf(' ')
|
|
7209
|
+
var gTail = gSp > 0 ? gp.substring(gSp + 1) : ''
|
|
7210
|
+
if (gTail.charAt(gTail.length - 1) === '%' && !isNaN(parseFloat(gTail))) {
|
|
7211
|
+
gStops.push({ c: gp.substring(0, gSp).trim(), p: parseFloat(gTail) / 100 })
|
|
7212
|
+
} else gStops.push({ c: gp, p: null })
|
|
7213
|
+
}
|
|
7214
|
+
if (!gStops.length) return '#0b0b0c'
|
|
7215
|
+
if (gStops.length === 1) gStops.push({ c: gStops[0].c, p: null })
|
|
7216
|
+
// CSS's own fix-up: an unplaced first stop sits at 0 and last at 1, a
|
|
7217
|
+
// position never runs backwards, and the unplaced stops between two
|
|
7218
|
+
// placed ones spread evenly. Canvas throws outside 0..1, so clamp.
|
|
7219
|
+
var gN = gStops.length
|
|
7220
|
+
if (gStops[0].p === null) gStops[0].p = 0
|
|
7221
|
+
if (gStops[gN - 1].p === null) gStops[gN - 1].p = 1
|
|
7222
|
+
var gMax = 0
|
|
7223
|
+
for (var gk = 0; gk < gN; gk++) {
|
|
7224
|
+
if (gStops[gk].p !== null) { gStops[gk].p = Math.min(1, Math.max(gMax, gStops[gk].p)); gMax = gStops[gk].p }
|
|
7225
|
+
}
|
|
7226
|
+
for (var gm = 1; gm < gN; gm++) {
|
|
7227
|
+
if (gStops[gm].p !== null) continue
|
|
7228
|
+
var gEnd = gm
|
|
7229
|
+
while (gStops[gEnd].p === null) gEnd++
|
|
7230
|
+
var gFrom = gStops[gm - 1].p, gTo = gStops[gEnd].p, gRun = gEnd - gm + 1
|
|
7231
|
+
for (var gq = gm; gq < gEnd; gq++) gStops[gq].p = gFrom + (gTo - gFrom) * (gq - gm + 1) / gRun
|
|
7232
|
+
}
|
|
7233
|
+
var g
|
|
7234
|
+
if (gKind === 'linear') {
|
|
6942
7235
|
var rad = (ang - 90) * Math.PI / 180
|
|
6943
7236
|
var ux = Math.cos(rad), uy = Math.sin(rad)
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
// Radial: 'radial-gradient([circle|ellipse] [at X% Y%,] A, B)'. Canvas
|
|
6949
|
-
// cannot take the string, so it is built here like the linear one, with
|
|
6950
|
-
// CSS's own default extent (farthest corner) so the second colour lands
|
|
6951
|
-
// exactly where a browser would put it.
|
|
6952
|
-
if (typeof bgcss === 'string' && bgcss.indexOf('radial-gradient') === 0) {
|
|
6953
|
-
var rin = bgcss.substring(bgcss.indexOf('(') + 1, bgcss.lastIndexOf(')'))
|
|
6954
|
-
var rps = rin.split(',').map(function (x) { return x.trim() })
|
|
6955
|
-
var rcx = 0.5, rcy = 0.5, rcols = []
|
|
6956
|
-
for (var ri = 0; ri < rps.length; ri++) {
|
|
6957
|
-
var rp = rps[ri]
|
|
6958
|
-
if (rp.indexOf('circle') === 0 || rp.indexOf('ellipse') === 0 || rp.indexOf('at ') === 0) {
|
|
6959
|
-
var rat = /at\\s+([\\d.]+)%\\s+([\\d.]+)%/.exec(rp)
|
|
6960
|
-
if (rat) { rcx = parseFloat(rat[1]) / 100; rcy = parseFloat(rat[2]) / 100 }
|
|
6961
|
-
} else rcols.push(rp)
|
|
6962
|
-
}
|
|
6963
|
-
if (rcols.length < 2) rcols = [rcols[0] || '#000', rcols[0] || '#000']
|
|
7237
|
+
g = bgC.createLinearGradient(W / 2 - ux * W / 2, H / 2 - uy * H / 2, W / 2 + ux * W / 2, H / 2 + uy * H / 2)
|
|
7238
|
+
} else {
|
|
7239
|
+
// CSS's default extent (farthest corner), so the last stop lands
|
|
7240
|
+
// exactly where a browser would put it.
|
|
6964
7241
|
var rpx = rcx * W, rpy = rcy * H
|
|
6965
7242
|
var rr = Math.max(
|
|
6966
7243
|
Math.sqrt(rpx * rpx + rpy * rpy),
|
|
@@ -6968,11 +7245,15 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
6968
7245
|
Math.sqrt(rpx * rpx + (H - rpy) * (H - rpy)),
|
|
6969
7246
|
Math.sqrt((W - rpx) * (W - rpx) + (H - rpy) * (H - rpy))
|
|
6970
7247
|
)
|
|
6971
|
-
|
|
6972
|
-
rg.addColorStop(0, rcols[0]); rg.addColorStop(1, rcols[rcols.length - 1])
|
|
6973
|
-
return rg
|
|
7248
|
+
g = bgC.createRadialGradient(rpx, rpy, 0, rpx, rpy, rr)
|
|
6974
7249
|
}
|
|
6975
|
-
|
|
7250
|
+
// A stop canvas still cannot parse (a colour keyword it lacks, a second
|
|
7251
|
+
// position) lands on the known ground: a background must never take the
|
|
7252
|
+
// frame down with it.
|
|
7253
|
+
try {
|
|
7254
|
+
for (var gs = 0; gs < gN; gs++) g.addColorStop(gStops[gs].p, gStops[gs].c)
|
|
7255
|
+
} catch (e) { return '#0b0b0c' }
|
|
7256
|
+
return g
|
|
6976
7257
|
})()
|
|
6977
7258
|
bgC.fillRect(0, 0, W, H)
|
|
6978
7259
|
|
|
@@ -7078,7 +7359,7 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
7078
7359
|
// current zoom \u2014 a standard keyframe track in OUTPUT time (hold + arrival pairs
|
|
7079
7360
|
// expanded by the lowering), sampled with the shared deterministic interpolator.
|
|
7080
7361
|
// Sampled here, before the rect math, so a cover crop can follow it.
|
|
7081
|
-
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
|
|
7082
7363
|
var zt = d.zoomTrack
|
|
7083
7364
|
if (zt && zt.keyframes && zt.keyframes.length) {
|
|
7084
7365
|
var z = TL.sample(zt, tpT, TL.lerpArray)
|
|
@@ -7087,6 +7368,9 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
7087
7368
|
// with the level); a three-component track (a document lowered before
|
|
7088
7369
|
// the component existed) falls back to the level band below.
|
|
7089
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] }
|
|
7090
7374
|
}
|
|
7091
7375
|
var barH = bar.kind && bar.kind !== 'none' ? (bar.height || 44) * s2 : 0
|
|
7092
7376
|
var availW = Math.max(1, W - ipL - ipR), availH = Math.max(1, H - ipT - ipB - barH)
|
|
@@ -7147,6 +7431,10 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
7147
7431
|
var camStage = cfr.camera === 'stage'
|
|
7148
7432
|
var fx = dx + zx * dw, fy = dy + zy * dh
|
|
7149
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
|
|
7150
7438
|
if (lvl > 1.001 && !d.zoomSuppressed) {
|
|
7151
7439
|
if (camStage) {
|
|
7152
7440
|
var ctt = zc
|
|
@@ -7154,9 +7442,9 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
7154
7442
|
var ctu = Math.max(0, Math.min(1, (lvl - 1) / ${CAMERA_CENTRE_RAMP}))
|
|
7155
7443
|
ctt = ctu * ctu * (3 - 2 * ctu)
|
|
7156
7444
|
}
|
|
7157
|
-
wcx = fx + ((
|
|
7158
|
-
wcy = fy + ((
|
|
7159
|
-
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)
|
|
7160
7448
|
} else {
|
|
7161
7449
|
c.translate(fx, fy); c.scale(lvl, lvl); c.translate(-fx, -fy)
|
|
7162
7450
|
}
|
|
@@ -7556,7 +7844,7 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
7556
7844
|
if (trOut) {
|
|
7557
7845
|
var tqX = ax, tqY = ay
|
|
7558
7846
|
if (lvl > 1.001 && !d.zoomSuppressed) {
|
|
7559
|
-
if (camStage) { tqX =
|
|
7847
|
+
if (camStage) { tqX = cox + (ax - wcx) * lvl; tqY = coy + (ay - wcy) * lvl }
|
|
7560
7848
|
else { tqX = fx + (ax - fx) * lvl; tqY = fy + (ay - fy) * lvl }
|
|
7561
7849
|
}
|
|
7562
7850
|
if (tpG) trQa = [tqX, tqY]
|
|
@@ -7678,8 +7966,8 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
7678
7966
|
}
|
|
7679
7967
|
if (lvl > 1.001 && !d.zoomSuppressed) {
|
|
7680
7968
|
if (camStage) {
|
|
7681
|
-
pqAx =
|
|
7682
|
-
pqBx =
|
|
7969
|
+
pqAx = cox + (pqAx - wcx) * lvl; pqAy = coy + (pqAy - wcy) * lvl
|
|
7970
|
+
pqBx = cox + (pqBx - wcx) * lvl; pqBy = coy + (pqBy - wcy) * lvl
|
|
7683
7971
|
} else {
|
|
7684
7972
|
pqAx = fx + (pqAx - fx) * lvl; pqAy = fy + (pqAy - fy) * lvl
|
|
7685
7973
|
pqBx = fx + (pqBx - fx) * lvl; pqBy = fy + (pqBy - fy) * lvl
|
|
@@ -7988,10 +8276,27 @@ function studioLayerData(layers, duration, pins) {
|
|
|
7988
8276
|
}
|
|
7989
8277
|
const st = resolveOverlayStyle(o);
|
|
7990
8278
|
const bx = resolveOverlayBox(o);
|
|
8279
|
+
const em = resolveEmphasis(o);
|
|
7991
8280
|
return {
|
|
7992
8281
|
...base,
|
|
7993
8282
|
text: o.text,
|
|
7994
|
-
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
|
+
} : {},
|
|
7995
8300
|
fs: st.size,
|
|
7996
8301
|
weight: st.weight,
|
|
7997
8302
|
stack: st.stack,
|
|
@@ -8235,7 +8540,15 @@ function loweredZoomSpans(doc, rated, transitions, layout, camera, zoomStyle) {
|
|
|
8235
8540
|
}
|
|
8236
8541
|
return {
|
|
8237
8542
|
...z,
|
|
8238
|
-
|
|
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
|
+
)
|
|
8239
8552
|
};
|
|
8240
8553
|
});
|
|
8241
8554
|
}
|
|
@@ -8788,8 +9101,9 @@ function sceneChanges(bins, opts = {}) {
|
|
|
8788
9101
|
// src/digest/framing.ts
|
|
8789
9102
|
function zoomWindow(span, layout, camera = "card") {
|
|
8790
9103
|
const L = Math.max(1, span.level);
|
|
8791
|
-
const
|
|
8792
|
-
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);
|
|
8793
9107
|
const px0 = v.x * layout.W;
|
|
8794
9108
|
const px1 = (v.x + v.w) * layout.W;
|
|
8795
9109
|
const py0 = v.y * layout.H;
|
|
@@ -11515,6 +11829,8 @@ export {
|
|
|
11515
11829
|
DRAG_FIT_LEVEL,
|
|
11516
11830
|
DRAG_MIN_S,
|
|
11517
11831
|
DRAG_MIN_TRAVEL_FRAC,
|
|
11832
|
+
EM_CLOSE,
|
|
11833
|
+
EM_OPEN,
|
|
11518
11834
|
END_CARD_FROM,
|
|
11519
11835
|
END_CARD_RECEDE,
|
|
11520
11836
|
END_CARD_SECONDS,
|
|
@@ -11530,6 +11846,7 @@ export {
|
|
|
11530
11846
|
FREEZE_DEFAULT_SECONDS,
|
|
11531
11847
|
FREEZE_SECONDS_MAX,
|
|
11532
11848
|
FREEZE_SECONDS_MIN,
|
|
11849
|
+
HOLD_DRIFT_MAX,
|
|
11533
11850
|
HOUSE_GRADIENT,
|
|
11534
11851
|
HOUSE_REGISTER,
|
|
11535
11852
|
HTML_LAYER_FOREIGN_FIELDS,
|
|
@@ -11689,6 +12006,8 @@ export {
|
|
|
11689
12006
|
endCardClips,
|
|
11690
12007
|
enterKey,
|
|
11691
12008
|
enterOf,
|
|
12009
|
+
entranceAt,
|
|
12010
|
+
entranceEnd,
|
|
11692
12011
|
entranceSeconds,
|
|
11693
12012
|
envelopeAt,
|
|
11694
12013
|
envelopeValueAt,
|
|
@@ -11743,6 +12062,7 @@ export {
|
|
|
11743
12062
|
lowerProgramDoc,
|
|
11744
12063
|
lowerStudioDoc,
|
|
11745
12064
|
lowerToComposition,
|
|
12065
|
+
measureEmphasized,
|
|
11746
12066
|
mediaAtOutput,
|
|
11747
12067
|
mediaDuration,
|
|
11748
12068
|
mediaFrame,
|
|
@@ -11766,6 +12086,7 @@ export {
|
|
|
11766
12086
|
outputEnd,
|
|
11767
12087
|
outputRangeToSource,
|
|
11768
12088
|
overlapFraction,
|
|
12089
|
+
overlayDisplayText,
|
|
11769
12090
|
overlayFaceFor,
|
|
11770
12091
|
overlayFontFaces,
|
|
11771
12092
|
overlayFontString,
|
|
@@ -11780,6 +12101,7 @@ export {
|
|
|
11780
12101
|
ownFreezes,
|
|
11781
12102
|
pageDisplayUrl,
|
|
11782
12103
|
parseCssColour,
|
|
12104
|
+
parseEmphasis,
|
|
11783
12105
|
parsePoseId,
|
|
11784
12106
|
pickStyle,
|
|
11785
12107
|
pinBox,
|
|
@@ -11807,6 +12129,7 @@ export {
|
|
|
11807
12129
|
relativeLuminance,
|
|
11808
12130
|
removeSourceRange,
|
|
11809
12131
|
removeSpeedInRange,
|
|
12132
|
+
resolveEmphasis,
|
|
11810
12133
|
resolveExportSize,
|
|
11811
12134
|
resolveOverlayBox,
|
|
11812
12135
|
resolveOverlayStyle,
|
|
@@ -11827,6 +12150,7 @@ export {
|
|
|
11827
12150
|
smoothCursor,
|
|
11828
12151
|
spanOutputExtent,
|
|
11829
12152
|
speedLane,
|
|
12153
|
+
stripEmphasis,
|
|
11830
12154
|
studioAudioPlan,
|
|
11831
12155
|
studioEntry,
|
|
11832
12156
|
studioLayerData,
|