@bendyline/squisq 2.13.3 → 2.13.5
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/{Doc-BPhdJFS_.d.ts → Doc-BQUVDooL.d.ts} +16 -0
- package/dist/{ImageEditDoc-DDx-sw_S.d.ts → ImageEditDoc-glA5UUau.d.ts} +1 -1
- package/dist/{chunk-D3JQMZUT.js → chunk-CPD3MFX7.js} +1 -1
- package/dist/{chunk-POZCKPCO.js → chunk-CZHWFQ7C.js} +1 -1
- package/dist/{chunk-VX7MPZOK.js → chunk-F5WTRHWL.js} +4 -4
- package/dist/{chunk-JFJZ4NS6.js → chunk-FJNTG3VZ.js} +2 -2
- package/dist/{chunk-JOBMPMY5.js → chunk-FSZ6HB7S.js} +1 -1
- package/dist/{chunk-F3LRTRST.js → chunk-K75GTKSN.js} +5 -5
- package/dist/{chunk-6NWVLLMI.js → chunk-KVEOD57Z.js} +5 -0
- package/dist/{chunk-ISVJ42I3.js → chunk-SG3HXPMF.js} +3 -0
- package/dist/{chunk-5FUBK3DK.js → chunk-ZF4NWRZU.js} +384 -159
- package/dist/doc/index.d.ts +51 -7
- package/dist/doc/index.js +8 -6
- package/dist/fence/index.d.ts +1 -1
- package/dist/generate/index.d.ts +1 -1
- package/dist/imageEdit/index.d.ts +3 -3
- package/dist/index.d.ts +6 -6
- package/dist/index.js +13 -9
- package/dist/jsonForm/index.d.ts +1 -1
- package/dist/jsonForm/index.js +1 -1
- package/dist/markdown/index.d.ts +6 -1
- package/dist/markdown/index.js +5 -3
- package/dist/{materializePageSection-DnQi-0TA.d.ts → materializePageSection-_ftXlwvB.d.ts} +1 -1
- package/dist/mediaEdit/index.d.ts +2 -2
- package/dist/narration/index.d.ts +1 -1
- package/dist/narration/index.js +4 -4
- package/dist/recommend/index.js +2 -2
- package/dist/schemas/index.d.ts +4 -4
- package/dist/schemas/index.js +1 -1
- package/dist/{themeLibrary-DM7eSK1w.d.ts → themeLibrary-D6GK29l3.d.ts} +1 -1
- package/dist/timing/index.d.ts +1 -1
- package/dist/transform/index.d.ts +2 -2
- package/dist/transform/index.js +5 -5
- package/package.json +1 -1
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
profileBlockContents,
|
|
33
33
|
renderMarkdownBlocksHtml,
|
|
34
34
|
treeToTemplateData
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-CZHWFQ7C.js";
|
|
36
36
|
import {
|
|
37
37
|
anchorPoint,
|
|
38
38
|
base64ToUtf8,
|
|
@@ -81,7 +81,7 @@ import {
|
|
|
81
81
|
parseVideoEmbedUrl,
|
|
82
82
|
readFrontmatterThemeId,
|
|
83
83
|
walkMarkdownTree
|
|
84
|
-
} from "./chunk-
|
|
84
|
+
} from "./chunk-KVEOD57Z.js";
|
|
85
85
|
import {
|
|
86
86
|
resolveBlockTransition
|
|
87
87
|
} from "./chunk-P53Z4RYC.js";
|
|
@@ -1289,6 +1289,131 @@ function sectionHeader(input, context) {
|
|
|
1289
1289
|
return layers;
|
|
1290
1290
|
}
|
|
1291
1291
|
|
|
1292
|
+
// src/doc/utils/textMetrics.ts
|
|
1293
|
+
var AVERAGE_CHAR_WIDTH_EM = 0.62;
|
|
1294
|
+
var BOLD_CHAR_WIDTH_EM = 0.66;
|
|
1295
|
+
function estimateTextWidth(text, fontSize, charWidthEm = AVERAGE_CHAR_WIDTH_EM) {
|
|
1296
|
+
return text.length * fontSize * charWidthEm;
|
|
1297
|
+
}
|
|
1298
|
+
var DISPLAY_CHAR_EM = {
|
|
1299
|
+
space: 0.27,
|
|
1300
|
+
narrowLower: 0.36,
|
|
1301
|
+
wideLower: 0.86,
|
|
1302
|
+
lower: 0.57,
|
|
1303
|
+
narrowUpper: 0.4,
|
|
1304
|
+
wideUpper: 0.95,
|
|
1305
|
+
upper: 0.7,
|
|
1306
|
+
digit: 0.57,
|
|
1307
|
+
punct: 0.34,
|
|
1308
|
+
other: 0.62
|
|
1309
|
+
};
|
|
1310
|
+
function displayCharEm(ch) {
|
|
1311
|
+
if (ch === " ") return DISPLAY_CHAR_EM.space;
|
|
1312
|
+
if ("fijlrt".includes(ch)) return DISPLAY_CHAR_EM.narrowLower;
|
|
1313
|
+
if (ch === "m" || ch === "w") return DISPLAY_CHAR_EM.wideLower;
|
|
1314
|
+
if (ch >= "a" && ch <= "z") return DISPLAY_CHAR_EM.lower;
|
|
1315
|
+
if (ch === "I" || ch === "J") return DISPLAY_CHAR_EM.narrowUpper;
|
|
1316
|
+
if (ch === "M" || ch === "W") return DISPLAY_CHAR_EM.wideUpper;
|
|
1317
|
+
if (ch >= "A" && ch <= "Z") return DISPLAY_CHAR_EM.upper;
|
|
1318
|
+
if (ch >= "0" && ch <= "9") return DISPLAY_CHAR_EM.digit;
|
|
1319
|
+
if (".,:;-/'\u2019!?()".includes(ch)) return DISPLAY_CHAR_EM.punct;
|
|
1320
|
+
return DISPLAY_CHAR_EM.other;
|
|
1321
|
+
}
|
|
1322
|
+
function estimateDisplayTextWidth(text, fontSize) {
|
|
1323
|
+
let em = 0;
|
|
1324
|
+
for (const ch of text.normalize("NFD").replace(/\p{M}/gu, "")) em += displayCharEm(ch);
|
|
1325
|
+
return em * fontSize;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// src/doc/templates/frostedPlate.ts
|
|
1329
|
+
var FROSTED_PAD_X_EM = 0.45;
|
|
1330
|
+
var FROSTED_PAD_Y_EM = 0.3;
|
|
1331
|
+
var FROSTED_RADIUS_EM = 0.3;
|
|
1332
|
+
var FROSTED_BLUR_SHARE = 0.0167;
|
|
1333
|
+
var FROSTED_TINT_UNDER_LIGHT_TEXT = "rgba(12, 12, 14, 0.26)";
|
|
1334
|
+
var FROSTED_TINT_UNDER_DARK_TEXT = "rgba(250, 250, 250, 0.5)";
|
|
1335
|
+
var FROSTED_BACKDROP_CONTRAST = 0.65;
|
|
1336
|
+
function frostedUnderLightText(context) {
|
|
1337
|
+
return relativeLuminance(context.theme.colors.text) > 0.5;
|
|
1338
|
+
}
|
|
1339
|
+
function frostedBlurPx(context) {
|
|
1340
|
+
const { width, height } = context.viewport;
|
|
1341
|
+
return Math.round(FROSTED_BLUR_SHARE * Math.min(width, height));
|
|
1342
|
+
}
|
|
1343
|
+
function pct(value, total) {
|
|
1344
|
+
return `${Math.round(value / total * 1e4) / 100}%`;
|
|
1345
|
+
}
|
|
1346
|
+
function frostedPlateLayer(id, plate, context) {
|
|
1347
|
+
const { viewport } = context;
|
|
1348
|
+
const lightText = frostedUnderLightText(context);
|
|
1349
|
+
return {
|
|
1350
|
+
type: "shape",
|
|
1351
|
+
id,
|
|
1352
|
+
content: {
|
|
1353
|
+
shape: "rect",
|
|
1354
|
+
fill: lightText ? FROSTED_TINT_UNDER_LIGHT_TEXT : FROSTED_TINT_UNDER_DARK_TEXT,
|
|
1355
|
+
borderRadius: Math.round(plate.radius),
|
|
1356
|
+
backdropBlur: frostedBlurPx(context),
|
|
1357
|
+
...lightText ? { backdropContrast: FROSTED_BACKDROP_CONTRAST } : {}
|
|
1358
|
+
},
|
|
1359
|
+
position: {
|
|
1360
|
+
x: pct(plate.x, viewport.width),
|
|
1361
|
+
y: pct(plate.y, viewport.height),
|
|
1362
|
+
width: pct(plate.width, viewport.width),
|
|
1363
|
+
height: pct(plate.height, viewport.height)
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
var INK_EM = {
|
|
1368
|
+
xHeight: 0.45,
|
|
1369
|
+
capital: 0.67,
|
|
1370
|
+
ascent: 0.71,
|
|
1371
|
+
accentedCapital: 0.8,
|
|
1372
|
+
descent: 0.2,
|
|
1373
|
+
comma: 0.155
|
|
1374
|
+
};
|
|
1375
|
+
function opticalCentreShiftEm(lines) {
|
|
1376
|
+
const first = (lines[0] ?? "").normalize("NFD");
|
|
1377
|
+
const last = lines[lines.length - 1] ?? "";
|
|
1378
|
+
const top = /[A-Z]\p{M}/u.test(first) ? INK_EM.accentedCapital : /[bdfhklt]|[a-z]\p{M}/u.test(first) ? INK_EM.ascent : /[A-Z0-9]/.test(first) ? INK_EM.capital : INK_EM.xHeight;
|
|
1379
|
+
const bottom = /[gjpqyQ]/.test(last) ? INK_EM.descent : /[,;]/.test(last) ? INK_EM.comma : 0;
|
|
1380
|
+
return Math.round(((top - bottom) / 2 - INK_EM.xHeight / 2) * 1e3) / 1e3;
|
|
1381
|
+
}
|
|
1382
|
+
function wrapDisplayLine(line, fontSize, maxWidth) {
|
|
1383
|
+
const lines = [];
|
|
1384
|
+
let current = "";
|
|
1385
|
+
for (const word of line.trim().split(/\s+/).filter(Boolean)) {
|
|
1386
|
+
const candidate = current ? `${current} ${word}` : word;
|
|
1387
|
+
if (!current || estimateDisplayTextWidth(candidate, fontSize) <= maxWidth) {
|
|
1388
|
+
current = candidate;
|
|
1389
|
+
} else {
|
|
1390
|
+
lines.push(current);
|
|
1391
|
+
current = word;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
if (current) lines.push(current);
|
|
1395
|
+
return lines.length > 0 ? lines : [""];
|
|
1396
|
+
}
|
|
1397
|
+
function balancedDisplayLines(line, fontSize, maxWidth) {
|
|
1398
|
+
const greedy = wrapDisplayLine(line, fontSize, maxWidth);
|
|
1399
|
+
if (greedy.length < 2) return greedy;
|
|
1400
|
+
let lo = Math.max(
|
|
1401
|
+
...line.split(/\s+/).filter(Boolean).map((word) => estimateDisplayTextWidth(word, fontSize))
|
|
1402
|
+
);
|
|
1403
|
+
let hi = maxWidth;
|
|
1404
|
+
if (lo >= hi) return greedy;
|
|
1405
|
+
while (hi - lo > 0.5) {
|
|
1406
|
+
const mid = (lo + hi) / 2;
|
|
1407
|
+
if (wrapDisplayLine(line, fontSize, mid).length <= greedy.length) hi = mid;
|
|
1408
|
+
else lo = mid;
|
|
1409
|
+
}
|
|
1410
|
+
return wrapDisplayLine(line, fontSize, hi);
|
|
1411
|
+
}
|
|
1412
|
+
function frostedTitleBoxWidth(lines, fontSize, textWidth) {
|
|
1413
|
+
const longestChars = Math.max(...lines.map((line) => line.length));
|
|
1414
|
+
return Math.max(textWidth, longestChars * fontSize * 0.5 + 1);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1292
1417
|
// src/doc/templates/bigText.ts
|
|
1293
1418
|
var BIG_TEXT_LINE_HEIGHT = 1.05;
|
|
1294
1419
|
var BIG_TEXT_WIDTH_FRACTION = 0.92;
|
|
@@ -1338,6 +1463,65 @@ function fitBigTextSize(title, viewport, heightFraction = BIG_TEXT_HEIGHT_FRACTI
|
|
|
1338
1463
|
}
|
|
1339
1464
|
return Math.floor(low);
|
|
1340
1465
|
}
|
|
1466
|
+
var BIG_TEXT_FROSTED_WIDTH_FRACTION = 0.9;
|
|
1467
|
+
var BIG_TEXT_FROSTED_HEIGHT_SHARE = 0.45;
|
|
1468
|
+
function frostedBigTextLayout(displayTitle, viewport, band = BIG_TEXT_BANDS.center) {
|
|
1469
|
+
const maxPlateWidth = viewport.width * BIG_TEXT_FROSTED_WIDTH_FRACTION;
|
|
1470
|
+
const maxPlateHeight = Math.min(
|
|
1471
|
+
viewport.height * band.height,
|
|
1472
|
+
Math.min(viewport.width, viewport.height) * BIG_TEXT_FROSTED_HEIGHT_SHARE
|
|
1473
|
+
);
|
|
1474
|
+
const wrapAt = (size) => {
|
|
1475
|
+
const column2 = maxPlateWidth - 2 * FROSTED_PAD_X_EM * size;
|
|
1476
|
+
const lines2 = displayTitle.split("\n").flatMap((line) => balancedDisplayLines(line, size, Math.max(1, column2)));
|
|
1477
|
+
const width2 = Math.max(...lines2.map((line) => estimateDisplayTextWidth(line, size)));
|
|
1478
|
+
return { lines: lines2, width: width2, column: column2 };
|
|
1479
|
+
};
|
|
1480
|
+
const fits = (size) => {
|
|
1481
|
+
const { lines: lines2, width: width2, column: column2 } = wrapAt(size);
|
|
1482
|
+
const height = (lines2.length * BIG_TEXT_LINE_HEIGHT + 2 * FROSTED_PAD_Y_EM) * size;
|
|
1483
|
+
return width2 <= column2 && height <= maxPlateHeight;
|
|
1484
|
+
};
|
|
1485
|
+
let low = BIG_TEXT_MIN_PX;
|
|
1486
|
+
let high = maxPlateHeight / (BIG_TEXT_LINE_HEIGHT + 2 * FROSTED_PAD_Y_EM);
|
|
1487
|
+
if (high > low && fits(low)) {
|
|
1488
|
+
for (let i = 0; i < 24; i += 1) {
|
|
1489
|
+
const mid = (low + high) / 2;
|
|
1490
|
+
if (fits(mid)) low = mid;
|
|
1491
|
+
else high = mid;
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
const fontSize = Math.floor(low);
|
|
1495
|
+
const { lines, width, column } = wrapAt(fontSize);
|
|
1496
|
+
const textWidth = Math.min(width, column);
|
|
1497
|
+
const textHeight = lines.length * fontSize * BIG_TEXT_LINE_HEIGHT;
|
|
1498
|
+
const padX = FROSTED_PAD_X_EM * fontSize;
|
|
1499
|
+
const padY = FROSTED_PAD_Y_EM * fontSize;
|
|
1500
|
+
const plateWidth = textWidth + 2 * padX;
|
|
1501
|
+
const plateHeight = textHeight + 2 * padY;
|
|
1502
|
+
const cx = viewport.width / 2;
|
|
1503
|
+
const cy = viewport.height * band.y;
|
|
1504
|
+
return {
|
|
1505
|
+
fontSize,
|
|
1506
|
+
lines,
|
|
1507
|
+
plate: {
|
|
1508
|
+
x: cx - plateWidth / 2,
|
|
1509
|
+
y: cy - plateHeight / 2,
|
|
1510
|
+
width: plateWidth,
|
|
1511
|
+
height: plateHeight,
|
|
1512
|
+
radius: FROSTED_RADIUS_EM * fontSize
|
|
1513
|
+
},
|
|
1514
|
+
// As on the frosted cover, the box spans the padding: shrink-to-fit measures the
|
|
1515
|
+
// font's whole ascent and descent, more than a line of display type.
|
|
1516
|
+
title: {
|
|
1517
|
+
cx,
|
|
1518
|
+
cy,
|
|
1519
|
+
width: frostedTitleBoxWidth(lines, fontSize, textWidth),
|
|
1520
|
+
height: plateHeight
|
|
1521
|
+
},
|
|
1522
|
+
baselineShift: opticalCentreShiftEm(lines)
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1341
1525
|
function bigTextBloom(background, centerY = 0.5) {
|
|
1342
1526
|
return `radial-gradient(ellipse at 50% ${Math.round(centerY * 100)}%, ${withAlpha(background, 0.92)} 0%, ${withAlpha(background, 0.78)} 38%, ${withAlpha(background, 0.32)} 66%, ${withAlpha(background, 0)} 88%)`;
|
|
1343
1527
|
}
|
|
@@ -1347,27 +1531,28 @@ function bigText(input, context) {
|
|
|
1347
1531
|
const band = BIG_TEXT_BANDS[input.placement ?? "center"] ?? BIG_TEXT_BANDS.center;
|
|
1348
1532
|
const treatment = themedImageTreatment(context, input.imageTreatment);
|
|
1349
1533
|
const displayTitle = title.toUpperCase();
|
|
1534
|
+
const frosted = input.backdrop === "frosted" && !!imageSrc;
|
|
1350
1535
|
const titleFontSize = fitBigTextSize(displayTitle, viewport, band.height);
|
|
1351
1536
|
const layers = [];
|
|
1352
1537
|
if (imageSrc) {
|
|
1353
|
-
layers.push(
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
...treatment ? { treatment } : {}
|
|
1362
|
-
},
|
|
1363
|
-
position: { x: 0, y: 0, width: "100%", height: "100%" },
|
|
1364
|
-
animation: mapAmbientMotion(ambientMotion, 8) ?? {
|
|
1365
|
-
type: "slowZoom",
|
|
1366
|
-
duration: 8,
|
|
1367
|
-
direction: "in"
|
|
1368
|
-
}
|
|
1538
|
+
layers.push({
|
|
1539
|
+
type: "image",
|
|
1540
|
+
id: "bigtext-image",
|
|
1541
|
+
content: {
|
|
1542
|
+
src: imageSrc,
|
|
1543
|
+
alt: imageAlt || title,
|
|
1544
|
+
fit: "cover",
|
|
1545
|
+
...treatment ? { treatment } : {}
|
|
1369
1546
|
},
|
|
1370
|
-
{
|
|
1547
|
+
position: { x: 0, y: 0, width: "100%", height: "100%" },
|
|
1548
|
+
animation: mapAmbientMotion(ambientMotion, 8) ?? {
|
|
1549
|
+
type: "slowZoom",
|
|
1550
|
+
duration: 8,
|
|
1551
|
+
direction: "in"
|
|
1552
|
+
}
|
|
1553
|
+
});
|
|
1554
|
+
if (!frosted) {
|
|
1555
|
+
layers.push({
|
|
1371
1556
|
type: "shape",
|
|
1372
1557
|
id: "bigtext-bloom",
|
|
1373
1558
|
content: {
|
|
@@ -1375,8 +1560,8 @@ function bigText(input, context) {
|
|
|
1375
1560
|
fill: bigTextBloom(theme.colors.background, band.y)
|
|
1376
1561
|
},
|
|
1377
1562
|
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1380
1565
|
} else {
|
|
1381
1566
|
layers.push(
|
|
1382
1567
|
{
|
|
@@ -1399,6 +1584,38 @@ function bigText(input, context) {
|
|
|
1399
1584
|
}
|
|
1400
1585
|
);
|
|
1401
1586
|
}
|
|
1587
|
+
if (frosted) {
|
|
1588
|
+
const layout = frostedBigTextLayout(displayTitle, viewport, band);
|
|
1589
|
+
const lightText = frostedUnderLightText(context);
|
|
1590
|
+
layers.push(frostedPlateLayer("bigtext-plate", layout.plate, context), {
|
|
1591
|
+
type: "text",
|
|
1592
|
+
id: "bigtext-title",
|
|
1593
|
+
content: {
|
|
1594
|
+
text: layout.lines.join("\n"),
|
|
1595
|
+
style: {
|
|
1596
|
+
fontSize: layout.fontSize,
|
|
1597
|
+
fontFamily: getThemeFont(context, "title"),
|
|
1598
|
+
fontWeight: "bold",
|
|
1599
|
+
color: theme.colors.text,
|
|
1600
|
+
textAlign: "center",
|
|
1601
|
+
verticalAlign: "middle",
|
|
1602
|
+
lineHeight: BIG_TEXT_LINE_HEIGHT,
|
|
1603
|
+
baselineShift: layout.baselineShift,
|
|
1604
|
+
shadow: lightText,
|
|
1605
|
+
shrinkToFit: true
|
|
1606
|
+
}
|
|
1607
|
+
},
|
|
1608
|
+
position: {
|
|
1609
|
+
x: "50%",
|
|
1610
|
+
y: pct(layout.title.cy, viewport.height),
|
|
1611
|
+
anchor: "center",
|
|
1612
|
+
width: pct(layout.title.width, viewport.width),
|
|
1613
|
+
height: pct(layout.title.height, viewport.height)
|
|
1614
|
+
},
|
|
1615
|
+
animation: motionEntrance(context, { type: "fadeIn", duration: 1 })
|
|
1616
|
+
});
|
|
1617
|
+
return layers;
|
|
1618
|
+
}
|
|
1402
1619
|
layers.push({
|
|
1403
1620
|
type: "text",
|
|
1404
1621
|
id: "bigtext-title",
|
|
@@ -7042,42 +7259,6 @@ function treeBlock(input, context) {
|
|
|
7042
7259
|
return layers;
|
|
7043
7260
|
}
|
|
7044
7261
|
|
|
7045
|
-
// src/doc/utils/textMetrics.ts
|
|
7046
|
-
var AVERAGE_CHAR_WIDTH_EM = 0.62;
|
|
7047
|
-
var BOLD_CHAR_WIDTH_EM = 0.66;
|
|
7048
|
-
function estimateTextWidth(text, fontSize, charWidthEm = AVERAGE_CHAR_WIDTH_EM) {
|
|
7049
|
-
return text.length * fontSize * charWidthEm;
|
|
7050
|
-
}
|
|
7051
|
-
var DISPLAY_CHAR_EM = {
|
|
7052
|
-
space: 0.27,
|
|
7053
|
-
narrowLower: 0.36,
|
|
7054
|
-
wideLower: 0.86,
|
|
7055
|
-
lower: 0.57,
|
|
7056
|
-
narrowUpper: 0.4,
|
|
7057
|
-
wideUpper: 0.95,
|
|
7058
|
-
upper: 0.7,
|
|
7059
|
-
digit: 0.57,
|
|
7060
|
-
punct: 0.34,
|
|
7061
|
-
other: 0.62
|
|
7062
|
-
};
|
|
7063
|
-
function displayCharEm(ch) {
|
|
7064
|
-
if (ch === " ") return DISPLAY_CHAR_EM.space;
|
|
7065
|
-
if ("fijlrt".includes(ch)) return DISPLAY_CHAR_EM.narrowLower;
|
|
7066
|
-
if (ch === "m" || ch === "w") return DISPLAY_CHAR_EM.wideLower;
|
|
7067
|
-
if (ch >= "a" && ch <= "z") return DISPLAY_CHAR_EM.lower;
|
|
7068
|
-
if (ch === "I" || ch === "J") return DISPLAY_CHAR_EM.narrowUpper;
|
|
7069
|
-
if (ch === "M" || ch === "W") return DISPLAY_CHAR_EM.wideUpper;
|
|
7070
|
-
if (ch >= "A" && ch <= "Z") return DISPLAY_CHAR_EM.upper;
|
|
7071
|
-
if (ch >= "0" && ch <= "9") return DISPLAY_CHAR_EM.digit;
|
|
7072
|
-
if (".,:;-/'\u2019!?()".includes(ch)) return DISPLAY_CHAR_EM.punct;
|
|
7073
|
-
return DISPLAY_CHAR_EM.other;
|
|
7074
|
-
}
|
|
7075
|
-
function estimateDisplayTextWidth(text, fontSize) {
|
|
7076
|
-
let em = 0;
|
|
7077
|
-
for (const ch of text.normalize("NFD").replace(/\p{M}/gu, "")) em += displayCharEm(ch);
|
|
7078
|
-
return em * fontSize;
|
|
7079
|
-
}
|
|
7080
|
-
|
|
7081
7262
|
// src/doc/templates/timelineBlock.ts
|
|
7082
7263
|
var clamp01 = (value) => Math.max(0, Math.min(1, value));
|
|
7083
7264
|
var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
@@ -10153,78 +10334,125 @@ function coverScrimGradient(background) {
|
|
|
10153
10334
|
const stops = COVER_SCRIM_STOPS.map((s) => `${withAlpha(background, s.alpha)} ${s.pos}%`);
|
|
10154
10335
|
return `linear-gradient(0deg, ${stops.join(", ")})`;
|
|
10155
10336
|
}
|
|
10337
|
+
function fitFrostedText(text, size, minSize, maxPlateWidth, maxLines) {
|
|
10338
|
+
const wrapAt = (s) => {
|
|
10339
|
+
const column = maxPlateWidth - 2 * FROSTED_PAD_X_EM * s;
|
|
10340
|
+
const lines = text.split("\n").flatMap((line) => balancedDisplayLines(line, s, column));
|
|
10341
|
+
const width = Math.max(...lines.map((line) => estimateDisplayTextWidth(line, s)));
|
|
10342
|
+
return { lines, width, column };
|
|
10343
|
+
};
|
|
10344
|
+
let fontSize = size;
|
|
10345
|
+
let wrapped = wrapAt(fontSize);
|
|
10346
|
+
while ((wrapped.lines.length > maxLines || wrapped.width > wrapped.column) && fontSize > minSize) {
|
|
10347
|
+
fontSize = Math.max(minSize, Math.floor(fontSize * 0.95));
|
|
10348
|
+
wrapped = wrapAt(fontSize);
|
|
10349
|
+
}
|
|
10350
|
+
return {
|
|
10351
|
+
fontSize,
|
|
10352
|
+
lines: wrapped.lines,
|
|
10353
|
+
column: wrapped.column,
|
|
10354
|
+
textWidth: Math.min(wrapped.width, wrapped.column)
|
|
10355
|
+
};
|
|
10356
|
+
}
|
|
10156
10357
|
var FROSTED_LINE_HEIGHT = 1.12;
|
|
10157
10358
|
var FROSTED_MAX_LINES = 3;
|
|
10158
10359
|
var FROSTED_SQUARE_SHARE = 0.9;
|
|
10159
|
-
var FROSTED_PAD_X_EM = 0.45;
|
|
10160
|
-
var FROSTED_PAD_Y_EM = 0.3;
|
|
10161
|
-
var FROSTED_RADIUS_EM = 0.3;
|
|
10162
|
-
var FROSTED_BLUR_SHARE = 0.0167;
|
|
10163
10360
|
var FROSTED_BOTTOM_MARGIN_PCT = { wide: 7, tall: 17 };
|
|
10164
|
-
var
|
|
10165
|
-
var FROSTED_TINT_UNDER_DARK_TEXT = "rgba(250, 250, 250, 0.5)";
|
|
10166
|
-
var FROSTED_BACKDROP_CONTRAST = 0.65;
|
|
10361
|
+
var FROSTED_TOP_MARGIN_PCT = FROSTED_BOTTOM_MARGIN_PCT;
|
|
10167
10362
|
var COVER_SUBTITLE_BASE_PX = 40;
|
|
10168
|
-
var
|
|
10169
|
-
|
|
10170
|
-
|
|
10171
|
-
|
|
10172
|
-
|
|
10173
|
-
const bottom = /[gjpqyQ,;]/.test(last) ? INK_EM.descent : 0;
|
|
10174
|
-
return Math.round(((top - bottom) / 2 - INK_EM.xHeight / 2) * 1e3) / 1e3;
|
|
10175
|
-
}
|
|
10176
|
-
function wrapDisplayLine(line, fontSize, maxWidth) {
|
|
10177
|
-
const lines = [];
|
|
10178
|
-
let current = "";
|
|
10179
|
-
for (const word of line.trim().split(/\s+/).filter(Boolean)) {
|
|
10180
|
-
const candidate = current ? `${current} ${word}` : word;
|
|
10181
|
-
if (!current || estimateDisplayTextWidth(candidate, fontSize) <= maxWidth) {
|
|
10182
|
-
current = candidate;
|
|
10183
|
-
} else {
|
|
10184
|
-
lines.push(current);
|
|
10185
|
-
current = word;
|
|
10186
|
-
}
|
|
10187
|
-
}
|
|
10188
|
-
if (current) lines.push(current);
|
|
10189
|
-
return lines.length > 0 ? lines : [""];
|
|
10190
|
-
}
|
|
10191
|
-
function balancedDisplayLines(line, fontSize, maxWidth) {
|
|
10192
|
-
const greedy = wrapDisplayLine(line, fontSize, maxWidth);
|
|
10193
|
-
if (greedy.length < 2) return greedy;
|
|
10194
|
-
let lo = Math.max(
|
|
10195
|
-
...line.split(/\s+/).filter(Boolean).map((word) => estimateDisplayTextWidth(word, fontSize))
|
|
10196
|
-
);
|
|
10197
|
-
let hi = maxWidth;
|
|
10198
|
-
if (lo >= hi) return greedy;
|
|
10199
|
-
while (hi - lo > 0.5) {
|
|
10200
|
-
const mid = (lo + hi) / 2;
|
|
10201
|
-
if (wrapDisplayLine(line, fontSize, mid).length <= greedy.length) hi = mid;
|
|
10202
|
-
else lo = mid;
|
|
10203
|
-
}
|
|
10204
|
-
return wrapDisplayLine(line, fontSize, hi);
|
|
10205
|
-
}
|
|
10206
|
-
function frostedCoverLayout(title, subtitle, context) {
|
|
10363
|
+
var COVER_DECK_BASE_PX = 114;
|
|
10364
|
+
var FROSTED_SPLIT_TITLE_SCALE = 1.5;
|
|
10365
|
+
var FROSTED_DECK_MAX_LINES = 3;
|
|
10366
|
+
var FROSTED_SPLIT_MIN_GAP_EM = 0.5;
|
|
10367
|
+
function frostedCoverLayout(title, subtitle, context, coverLayout = "stacked") {
|
|
10207
10368
|
const { width: frameW, height: frameH } = context.viewport;
|
|
10208
10369
|
const shortSide = Math.min(frameW, frameH);
|
|
10209
10370
|
const maxPlateWidth = FROSTED_SQUARE_SHARE * shortSide;
|
|
10210
10371
|
const minSize = themedFontSize(COVER_TITLE_MIN_BASE_PX, context, true);
|
|
10211
|
-
const
|
|
10212
|
-
|
|
10213
|
-
|
|
10214
|
-
|
|
10215
|
-
|
|
10372
|
+
const tall = frameH > frameW;
|
|
10373
|
+
const marginPct = tall ? FROSTED_BOTTOM_MARGIN_PCT.tall : FROSTED_BOTTOM_MARGIN_PCT.wide;
|
|
10374
|
+
const plateBottom = frameH * (1 - marginPct / 100);
|
|
10375
|
+
const cx = frameW / 2;
|
|
10376
|
+
const fitTitle = (scale) => {
|
|
10377
|
+
const fit = fitFrostedText(
|
|
10378
|
+
title,
|
|
10379
|
+
Math.round(scale * themedFontSize(fitCoverTitleSize(title), context, true)),
|
|
10380
|
+
minSize,
|
|
10381
|
+
maxPlateWidth,
|
|
10382
|
+
FROSTED_MAX_LINES
|
|
10383
|
+
);
|
|
10384
|
+
const textHeight2 = fit.lines.length * fit.fontSize * FROSTED_LINE_HEIGHT;
|
|
10385
|
+
const padX2 = FROSTED_PAD_X_EM * fit.fontSize;
|
|
10386
|
+
const padY2 = FROSTED_PAD_Y_EM * fit.fontSize;
|
|
10387
|
+
return {
|
|
10388
|
+
...fit,
|
|
10389
|
+
textHeight: textHeight2,
|
|
10390
|
+
padX: padX2,
|
|
10391
|
+
padY: padY2,
|
|
10392
|
+
radius: FROSTED_RADIUS_EM * fit.fontSize,
|
|
10393
|
+
// The box spans the plate's padding too: shrink-to-fit measures the font's whole
|
|
10394
|
+
// ascent and descent (about 1.3 em), which overflows a box of 1.12 em lines and
|
|
10395
|
+
// would scale every title down by up to 14%.
|
|
10396
|
+
box: (plateY2) => ({
|
|
10397
|
+
cx,
|
|
10398
|
+
cy: plateY2 + padY2 + textHeight2 / 2,
|
|
10399
|
+
width: frostedTitleBoxWidth(fit.lines, fit.fontSize, fit.textWidth),
|
|
10400
|
+
height: textHeight2 + 2 * padY2
|
|
10401
|
+
})
|
|
10402
|
+
};
|
|
10216
10403
|
};
|
|
10217
|
-
|
|
10218
|
-
|
|
10219
|
-
|
|
10220
|
-
|
|
10221
|
-
|
|
10404
|
+
if (coverLayout === "split" && subtitle?.trim()) {
|
|
10405
|
+
const { fontSize: fontSize2, lines: lines2, textWidth: textWidth2, textHeight: textHeight2, padX: padX2, padY: padY2, radius: radius2, box: box2 } = fitTitle(FROSTED_SPLIT_TITLE_SCALE);
|
|
10406
|
+
const deckMin = themedFontSize(COVER_SUBTITLE_BASE_PX, context, false);
|
|
10407
|
+
const deck = fitFrostedText(
|
|
10408
|
+
subtitle.trim(),
|
|
10409
|
+
Math.max(deckMin, themedFontSize(COVER_DECK_BASE_PX, context, true)),
|
|
10410
|
+
deckMin,
|
|
10411
|
+
maxPlateWidth,
|
|
10412
|
+
FROSTED_DECK_MAX_LINES
|
|
10413
|
+
);
|
|
10414
|
+
const deckPadX = FROSTED_PAD_X_EM * deck.fontSize;
|
|
10415
|
+
const deckHeight = deck.lines.length * deck.fontSize * FROSTED_LINE_HEIGHT + 2 * FROSTED_PAD_Y_EM * deck.fontSize;
|
|
10416
|
+
const deckWidth = deck.textWidth + 2 * deckPadX;
|
|
10417
|
+
const topPct = tall ? FROSTED_TOP_MARGIN_PCT.tall : FROSTED_TOP_MARGIN_PCT.wide;
|
|
10418
|
+
const titlePlate = {
|
|
10419
|
+
x: cx - (textWidth2 + 2 * padX2) / 2,
|
|
10420
|
+
y: frameH * topPct / 100,
|
|
10421
|
+
width: textWidth2 + 2 * padX2,
|
|
10422
|
+
height: textHeight2 + 2 * padY2,
|
|
10423
|
+
radius: radius2
|
|
10424
|
+
};
|
|
10425
|
+
const deckPlate = {
|
|
10426
|
+
x: cx - deckWidth / 2,
|
|
10427
|
+
y: plateBottom - deckHeight,
|
|
10428
|
+
width: deckWidth,
|
|
10429
|
+
height: deckHeight,
|
|
10430
|
+
// The title's corners, so the two plates read as a pair.
|
|
10431
|
+
radius: Math.min(radius2, deckHeight / 2)
|
|
10432
|
+
};
|
|
10433
|
+
const gap = deckPlate.y - (titlePlate.y + titlePlate.height);
|
|
10434
|
+
if (gap >= FROSTED_SPLIT_MIN_GAP_EM * fontSize2) {
|
|
10435
|
+
return {
|
|
10436
|
+
fontSize: fontSize2,
|
|
10437
|
+
lines: lines2,
|
|
10438
|
+
plate: titlePlate,
|
|
10439
|
+
title: box2(titlePlate.y),
|
|
10440
|
+
subtitle: {
|
|
10441
|
+
cx,
|
|
10442
|
+
cy: deckPlate.y + deckHeight / 2,
|
|
10443
|
+
width: frostedTitleBoxWidth(deck.lines, deck.fontSize, deck.textWidth),
|
|
10444
|
+
height: deckHeight,
|
|
10445
|
+
fontSize: deck.fontSize,
|
|
10446
|
+
lines: deck.lines,
|
|
10447
|
+
baselineShift: opticalCentreShiftEm(deck.lines)
|
|
10448
|
+
},
|
|
10449
|
+
subtitlePlate: deckPlate,
|
|
10450
|
+
baselineShift: opticalCentreShiftEm(lines2),
|
|
10451
|
+
blur: frostedBlurPx(context)
|
|
10452
|
+
};
|
|
10453
|
+
}
|
|
10222
10454
|
}
|
|
10223
|
-
const { lines, column } =
|
|
10224
|
-
const textWidth = Math.min(wrapped.width, column);
|
|
10225
|
-
const textHeight = lines.length * fontSize * FROSTED_LINE_HEIGHT;
|
|
10226
|
-
const padX = FROSTED_PAD_X_EM * fontSize;
|
|
10227
|
-
const padY = FROSTED_PAD_Y_EM * fontSize;
|
|
10455
|
+
const { fontSize, lines, column, textWidth, textHeight, padX, padY, radius, box } = fitTitle(1);
|
|
10228
10456
|
let sub;
|
|
10229
10457
|
if (subtitle?.trim()) {
|
|
10230
10458
|
const subSize = themedFontSize(COVER_SUBTITLE_BASE_PX, context, false);
|
|
@@ -10242,12 +10470,7 @@ function frostedCoverLayout(title, subtitle, context) {
|
|
|
10242
10470
|
}
|
|
10243
10471
|
const plateWidth = Math.max(textWidth, sub?.width ?? 0) + 2 * padX;
|
|
10244
10472
|
const plateHeight = textHeight + (sub ? sub.gap + sub.height : 0) + 2 * padY;
|
|
10245
|
-
const marginPct = frameH > frameW ? FROSTED_BOTTOM_MARGIN_PCT.tall : FROSTED_BOTTOM_MARGIN_PCT.wide;
|
|
10246
|
-
const plateBottom = frameH * (1 - marginPct / 100);
|
|
10247
10473
|
const plateY = plateBottom - plateHeight;
|
|
10248
|
-
const cx = frameW / 2;
|
|
10249
|
-
const longestChars = Math.max(...lines.map((line) => line.length));
|
|
10250
|
-
const titleBoxWidth = Math.max(textWidth, longestChars * fontSize * 0.5 + 1);
|
|
10251
10474
|
return {
|
|
10252
10475
|
fontSize,
|
|
10253
10476
|
lines,
|
|
@@ -10256,17 +10479,9 @@ function frostedCoverLayout(title, subtitle, context) {
|
|
|
10256
10479
|
y: plateY,
|
|
10257
10480
|
width: plateWidth,
|
|
10258
10481
|
height: plateHeight,
|
|
10259
|
-
radius
|
|
10260
|
-
},
|
|
10261
|
-
// The box spans the plate's padding too: shrink-to-fit measures the font's whole
|
|
10262
|
-
// ascent and descent (about 1.3 em), which overflows a box of 1.12 em lines and
|
|
10263
|
-
// would scale every title down by up to 14%.
|
|
10264
|
-
title: {
|
|
10265
|
-
cx,
|
|
10266
|
-
cy: plateY + padY + textHeight / 2,
|
|
10267
|
-
width: titleBoxWidth,
|
|
10268
|
-
height: textHeight + 2 * padY
|
|
10482
|
+
radius
|
|
10269
10483
|
},
|
|
10484
|
+
title: box(plateY),
|
|
10270
10485
|
...sub ? {
|
|
10271
10486
|
subtitle: {
|
|
10272
10487
|
cx,
|
|
@@ -10281,35 +10496,15 @@ function frostedCoverLayout(title, subtitle, context) {
|
|
|
10281
10496
|
}
|
|
10282
10497
|
} : {},
|
|
10283
10498
|
baselineShift: opticalCentreShiftEm(lines),
|
|
10284
|
-
blur:
|
|
10499
|
+
blur: frostedBlurPx(context)
|
|
10285
10500
|
};
|
|
10286
10501
|
}
|
|
10287
|
-
function pct(value, total) {
|
|
10288
|
-
return `${Math.round(value / total * 1e4) / 100}%`;
|
|
10289
|
-
}
|
|
10290
10502
|
function frostedCoverLayers(input, context) {
|
|
10291
10503
|
const { theme, viewport } = context;
|
|
10292
|
-
const layout = frostedCoverLayout(input.title, input.subtitle, context);
|
|
10293
|
-
const lightText =
|
|
10294
|
-
const { plate } = layout;
|
|
10504
|
+
const layout = frostedCoverLayout(input.title, input.subtitle, context, input.coverLayout);
|
|
10505
|
+
const lightText = frostedUnderLightText(context);
|
|
10295
10506
|
const layers = [
|
|
10296
|
-
|
|
10297
|
-
type: "shape",
|
|
10298
|
-
id: "cover-plate",
|
|
10299
|
-
content: {
|
|
10300
|
-
shape: "rect",
|
|
10301
|
-
fill: lightText ? FROSTED_TINT_UNDER_LIGHT_TEXT : FROSTED_TINT_UNDER_DARK_TEXT,
|
|
10302
|
-
borderRadius: Math.round(plate.radius),
|
|
10303
|
-
backdropBlur: layout.blur,
|
|
10304
|
-
...lightText ? { backdropContrast: FROSTED_BACKDROP_CONTRAST } : {}
|
|
10305
|
-
},
|
|
10306
|
-
position: {
|
|
10307
|
-
x: pct(plate.x, viewport.width),
|
|
10308
|
-
y: pct(plate.y, viewport.height),
|
|
10309
|
-
width: pct(plate.width, viewport.width),
|
|
10310
|
-
height: pct(plate.height, viewport.height)
|
|
10311
|
-
}
|
|
10312
|
-
},
|
|
10507
|
+
frostedPlateLayer("cover-plate", layout.plate, context),
|
|
10313
10508
|
{
|
|
10314
10509
|
type: "text",
|
|
10315
10510
|
id: "cover-title",
|
|
@@ -10338,7 +10533,35 @@ function frostedCoverLayers(input, context) {
|
|
|
10338
10533
|
animation: getMotionProfile(context).coverWordReveal ? { type: "wordReveal", duration: 0.5, delay: 0.1, stagger: 0.12 } : { type: "fadeIn", duration: 0.8 }
|
|
10339
10534
|
}
|
|
10340
10535
|
];
|
|
10341
|
-
if (layout.subtitle) {
|
|
10536
|
+
if (layout.subtitle && layout.subtitlePlate) {
|
|
10537
|
+
layers.push(frostedPlateLayer("cover-subtitle-plate", layout.subtitlePlate, context), {
|
|
10538
|
+
type: "text",
|
|
10539
|
+
id: "cover-subtitle",
|
|
10540
|
+
content: {
|
|
10541
|
+
text: layout.subtitle.lines.join("\n"),
|
|
10542
|
+
style: {
|
|
10543
|
+
fontSize: layout.subtitle.fontSize,
|
|
10544
|
+
fontFamily: getThemeFont(context, "title"),
|
|
10545
|
+
fontWeight: "bold",
|
|
10546
|
+
color: theme.colors.text,
|
|
10547
|
+
textAlign: "center",
|
|
10548
|
+
verticalAlign: "middle",
|
|
10549
|
+
lineHeight: FROSTED_LINE_HEIGHT,
|
|
10550
|
+
baselineShift: layout.subtitle.baselineShift,
|
|
10551
|
+
shadow: lightText,
|
|
10552
|
+
shrinkToFit: true
|
|
10553
|
+
}
|
|
10554
|
+
},
|
|
10555
|
+
position: {
|
|
10556
|
+
x: "50%",
|
|
10557
|
+
y: pct(layout.subtitle.cy, viewport.height),
|
|
10558
|
+
anchor: "center",
|
|
10559
|
+
width: pct(layout.subtitle.width, viewport.width),
|
|
10560
|
+
height: pct(layout.subtitle.height, viewport.height)
|
|
10561
|
+
},
|
|
10562
|
+
animation: motionEntrance(context, { type: "fadeIn", duration: 0.8, delay: 0.2 })
|
|
10563
|
+
});
|
|
10564
|
+
} else if (layout.subtitle) {
|
|
10342
10565
|
layers.push({
|
|
10343
10566
|
type: "text",
|
|
10344
10567
|
id: "cover-subtitle",
|
|
@@ -10530,7 +10753,8 @@ function startBlockToCoverInput(config) {
|
|
|
10530
10753
|
ambientMotion: config.ambientMotion,
|
|
10531
10754
|
heroCredit: config.heroCredit,
|
|
10532
10755
|
heroLicense: config.heroLicense,
|
|
10533
|
-
...config.coverStyle ? { coverStyle: config.coverStyle } : {}
|
|
10756
|
+
...config.coverStyle ? { coverStyle: config.coverStyle } : {},
|
|
10757
|
+
...config.coverLayout ? { coverLayout: config.coverLayout } : {}
|
|
10534
10758
|
};
|
|
10535
10759
|
}
|
|
10536
10760
|
function expandCoverBlock(config, context, template = "cover") {
|
|
@@ -12338,6 +12562,7 @@ export {
|
|
|
12338
12562
|
adjustY,
|
|
12339
12563
|
sectionHeader,
|
|
12340
12564
|
fitBigTextSize,
|
|
12565
|
+
frostedBigTextLayout,
|
|
12341
12566
|
bigText,
|
|
12342
12567
|
contentBlock,
|
|
12343
12568
|
parseStatNumber,
|