@blorkfield/overlay-core 0.5.0 → 0.5.2
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/README.md +71 -2
- package/dist/index.cjs +234 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -46
- package/dist/index.d.ts +76 -46
- package/dist/index.js +234 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -464,7 +464,7 @@ function createBodyFromVertices(id, x, y, vertices, renderOptions) {
|
|
|
464
464
|
function createBoundariesWithFloorConfig(bounds, floorConfig) {
|
|
465
465
|
const width = bounds.right - bounds.left;
|
|
466
466
|
const height = bounds.bottom - bounds.top;
|
|
467
|
-
const
|
|
467
|
+
const wallOptions = { isStatic: true, render: { visible: false } };
|
|
468
468
|
const walls = [
|
|
469
469
|
// Left wall
|
|
470
470
|
Matter2.Bodies.rectangle(
|
|
@@ -472,7 +472,7 @@ function createBoundariesWithFloorConfig(bounds, floorConfig) {
|
|
|
472
472
|
bounds.top + height / 2,
|
|
473
473
|
BOUNDARY_THICKNESS,
|
|
474
474
|
height,
|
|
475
|
-
{ ...
|
|
475
|
+
{ ...wallOptions, label: "leftWall" }
|
|
476
476
|
),
|
|
477
477
|
// Right wall
|
|
478
478
|
Matter2.Bodies.rectangle(
|
|
@@ -480,23 +480,44 @@ function createBoundariesWithFloorConfig(bounds, floorConfig) {
|
|
|
480
480
|
bounds.top + height / 2,
|
|
481
481
|
BOUNDARY_THICKNESS,
|
|
482
482
|
height,
|
|
483
|
-
{ ...
|
|
483
|
+
{ ...wallOptions, label: "rightWall" }
|
|
484
484
|
)
|
|
485
485
|
];
|
|
486
486
|
const segmentCount = floorConfig?.segments ?? 1;
|
|
487
|
-
const segmentWidth = width / segmentCount;
|
|
488
487
|
const floorSegments = [];
|
|
488
|
+
let segmentWidths;
|
|
489
|
+
if (floorConfig?.segmentWidths && floorConfig.segmentWidths.length === segmentCount) {
|
|
490
|
+
const sum = floorConfig.segmentWidths.reduce((a, b) => a + b, 0);
|
|
491
|
+
segmentWidths = floorConfig.segmentWidths.map((w) => w / sum * width);
|
|
492
|
+
} else {
|
|
493
|
+
const equalWidth = width / segmentCount;
|
|
494
|
+
segmentWidths = Array(segmentCount).fill(equalWidth);
|
|
495
|
+
}
|
|
496
|
+
let currentX = bounds.left;
|
|
489
497
|
for (let i = 0; i < segmentCount; i++) {
|
|
490
|
-
const
|
|
498
|
+
const segmentWidth = segmentWidths[i];
|
|
499
|
+
const thickness = floorConfig?.thickness !== void 0 ? Array.isArray(floorConfig.thickness) ? floorConfig.thickness[i] ?? BOUNDARY_THICKNESS : floorConfig.thickness : BOUNDARY_THICKNESS;
|
|
500
|
+
const color = floorConfig?.color !== void 0 ? Array.isArray(floorConfig.color) ? floorConfig.color[i] : floorConfig.color : void 0;
|
|
501
|
+
const segmentX = currentX + segmentWidth / 2;
|
|
502
|
+
const segmentY = bounds.bottom - thickness / 2;
|
|
503
|
+
const segmentOptions = {
|
|
504
|
+
isStatic: true,
|
|
505
|
+
label: `floor-segment-${i}`,
|
|
506
|
+
render: {
|
|
507
|
+
visible: color !== void 0,
|
|
508
|
+
fillStyle: color ?? "#888888"
|
|
509
|
+
}
|
|
510
|
+
};
|
|
491
511
|
floorSegments.push(
|
|
492
512
|
Matter2.Bodies.rectangle(
|
|
493
513
|
segmentX,
|
|
494
|
-
|
|
514
|
+
segmentY,
|
|
495
515
|
segmentWidth,
|
|
496
|
-
|
|
497
|
-
|
|
516
|
+
thickness,
|
|
517
|
+
segmentOptions
|
|
498
518
|
)
|
|
499
519
|
);
|
|
520
|
+
currentX += segmentWidth;
|
|
500
521
|
}
|
|
501
522
|
return { walls, floorSegments };
|
|
502
523
|
}
|
|
@@ -1188,6 +1209,7 @@ var OverlayScene = class {
|
|
|
1188
1209
|
this.boundaries = [...boundariesResult.walls, ...boundariesResult.floorSegments];
|
|
1189
1210
|
this.floorSegments = boundariesResult.floorSegments;
|
|
1190
1211
|
Matter5.Composite.add(this.engine.world, this.boundaries);
|
|
1212
|
+
this.checkInitialFloorIntegrity();
|
|
1191
1213
|
this.mouse = Matter5.Mouse.create(canvas);
|
|
1192
1214
|
this.mouseConstraint = Matter5.MouseConstraint.create(this.engine, {
|
|
1193
1215
|
mouse: this.mouse,
|
|
@@ -1306,7 +1328,8 @@ var OverlayScene = class {
|
|
|
1306
1328
|
if (onObstacles.has(dyn.id)) continue;
|
|
1307
1329
|
const dynBounds = dyn.body.bounds;
|
|
1308
1330
|
const horizontalOverlap = dynBounds.max.x > segmentBounds.min.x && dynBounds.min.x < segmentBounds.max.x;
|
|
1309
|
-
|
|
1331
|
+
const nearFloor = dynBounds.max.y >= segmentBounds.min.y - 10;
|
|
1332
|
+
if (horizontalOverlap && nearFloor) {
|
|
1310
1333
|
resting.add(dyn.id);
|
|
1311
1334
|
}
|
|
1312
1335
|
}
|
|
@@ -1332,17 +1355,50 @@ var OverlayScene = class {
|
|
|
1332
1355
|
const objectIds = this.floorSegmentPressure.get(i);
|
|
1333
1356
|
const pressure = objectIds ? this.calculateWeightedPressure(objectIds) : 0;
|
|
1334
1357
|
if (pressure >= threshold) {
|
|
1335
|
-
this.collapseFloorSegment(i, pressure
|
|
1358
|
+
this.collapseFloorSegment(i, `pressure ${pressure} >= threshold ${threshold}`);
|
|
1336
1359
|
}
|
|
1337
1360
|
}
|
|
1338
1361
|
}
|
|
1339
1362
|
/** Collapse a single floor segment */
|
|
1340
|
-
collapseFloorSegment(index,
|
|
1363
|
+
collapseFloorSegment(index, reason) {
|
|
1341
1364
|
if (this.collapsedSegments.has(index)) return;
|
|
1342
1365
|
this.collapsedSegments.add(index);
|
|
1343
1366
|
const segment = this.floorSegments[index];
|
|
1344
1367
|
Matter5.Composite.remove(this.engine.world, segment);
|
|
1345
|
-
|
|
1368
|
+
logger.debug("OverlayScene", `Floor segment ${index} collapsed: ${reason}`);
|
|
1369
|
+
this.checkFloorIntegrity();
|
|
1370
|
+
}
|
|
1371
|
+
/** Check if floor integrity requirement is violated and collapse all remaining if so */
|
|
1372
|
+
checkFloorIntegrity() {
|
|
1373
|
+
const minIntegrity = this.config.floorConfig?.minIntegrity;
|
|
1374
|
+
if (minIntegrity === void 0) return;
|
|
1375
|
+
const totalSegments = this.floorSegments.length;
|
|
1376
|
+
const remainingSegments = totalSegments - this.collapsedSegments.size;
|
|
1377
|
+
if (remainingSegments < minIntegrity && remainingSegments > 0) {
|
|
1378
|
+
logger.debug("OverlayScene", `Floor integrity failed: ${remainingSegments} remaining < ${minIntegrity} required. Collapsing all.`);
|
|
1379
|
+
for (let i = 0; i < totalSegments; i++) {
|
|
1380
|
+
if (!this.collapsedSegments.has(i)) {
|
|
1381
|
+
this.collapsedSegments.add(i);
|
|
1382
|
+
const segment = this.floorSegments[i];
|
|
1383
|
+
Matter5.Composite.remove(this.engine.world, segment);
|
|
1384
|
+
logger.debug("OverlayScene", `Floor segment ${i} collapsed: integrity failure cascade`);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
/** Check floor integrity on initialization (handles minIntegrity > segments) */
|
|
1390
|
+
checkInitialFloorIntegrity() {
|
|
1391
|
+
const minIntegrity = this.config.floorConfig?.minIntegrity;
|
|
1392
|
+
if (minIntegrity === void 0) return;
|
|
1393
|
+
const totalSegments = this.floorSegments.length;
|
|
1394
|
+
if (totalSegments < minIntegrity) {
|
|
1395
|
+
logger.debug("OverlayScene", `Floor integrity impossible: ${totalSegments} segments < ${minIntegrity} required. Collapsing all immediately.`);
|
|
1396
|
+
for (let i = 0; i < totalSegments; i++) {
|
|
1397
|
+
this.collapsedSegments.add(i);
|
|
1398
|
+
const segment = this.floorSegments[i];
|
|
1399
|
+
Matter5.Composite.remove(this.engine.world, segment);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1346
1402
|
}
|
|
1347
1403
|
/** Log a summary of pressure on all obstacles, grouped by word */
|
|
1348
1404
|
logPressureSummary() {
|
|
@@ -1461,7 +1517,6 @@ var OverlayScene = class {
|
|
|
1461
1517
|
const shadowElement = entry.domElement.cloneNode(true);
|
|
1462
1518
|
shadowElement.style.opacity = String(opacity);
|
|
1463
1519
|
shadowElement.style.pointerEvents = "none";
|
|
1464
|
-
shadowElement.style.transform = entry.domOriginalTransform || "";
|
|
1465
1520
|
entry.domElement.parentNode?.insertBefore(shadowElement, entry.domElement);
|
|
1466
1521
|
entry.domShadowElement = shadowElement;
|
|
1467
1522
|
return;
|
|
@@ -1598,6 +1653,7 @@ var OverlayScene = class {
|
|
|
1598
1653
|
this.collapsedSegments.clear();
|
|
1599
1654
|
this.floorSegmentPressure.clear();
|
|
1600
1655
|
Matter5.Composite.add(this.engine.world, this.boundaries);
|
|
1656
|
+
this.checkInitialFloorIntegrity();
|
|
1601
1657
|
this.render.options.width = width;
|
|
1602
1658
|
this.render.options.height = height;
|
|
1603
1659
|
this.render.canvas.width = width;
|
|
@@ -1614,6 +1670,21 @@ var OverlayScene = class {
|
|
|
1614
1670
|
* Without 'falling' tag, object is static.
|
|
1615
1671
|
*/
|
|
1616
1672
|
spawnObject(config) {
|
|
1673
|
+
if (config.element) {
|
|
1674
|
+
const result = this.addDOMObstacleInternal({
|
|
1675
|
+
element: config.element,
|
|
1676
|
+
x: config.x,
|
|
1677
|
+
y: config.y,
|
|
1678
|
+
width: config.width,
|
|
1679
|
+
height: config.height,
|
|
1680
|
+
tags: config.tags,
|
|
1681
|
+
pressureThreshold: config.pressureThreshold,
|
|
1682
|
+
weight: config.weight,
|
|
1683
|
+
shadow: config.shadow === true ? { opacity: 0.3 } : config.shadow || void 0,
|
|
1684
|
+
clickToFall: config.clickToFall
|
|
1685
|
+
});
|
|
1686
|
+
return result.id;
|
|
1687
|
+
}
|
|
1617
1688
|
const id = crypto.randomUUID();
|
|
1618
1689
|
const tags = config.tags ?? [];
|
|
1619
1690
|
const isStatic = !tags.includes("falling");
|
|
@@ -1633,6 +1704,17 @@ var OverlayScene = class {
|
|
|
1633
1704
|
} else {
|
|
1634
1705
|
body = createObstacle(id, config, isStatic);
|
|
1635
1706
|
}
|
|
1707
|
+
let pressureThreshold;
|
|
1708
|
+
if (config.pressureThreshold) {
|
|
1709
|
+
pressureThreshold = typeof config.pressureThreshold.value === "number" ? config.pressureThreshold.value : config.pressureThreshold.value[0];
|
|
1710
|
+
}
|
|
1711
|
+
let shadow;
|
|
1712
|
+
if (config.shadow === true) {
|
|
1713
|
+
shadow = { opacity: 0.3 };
|
|
1714
|
+
} else if (config.shadow && typeof config.shadow === "object") {
|
|
1715
|
+
shadow = { opacity: config.shadow.opacity ?? 0.3 };
|
|
1716
|
+
}
|
|
1717
|
+
const clicksRemaining = config.clickToFall?.clicks;
|
|
1636
1718
|
const entry = {
|
|
1637
1719
|
id,
|
|
1638
1720
|
body,
|
|
@@ -1640,10 +1722,17 @@ var OverlayScene = class {
|
|
|
1640
1722
|
spawnTime: performance.now(),
|
|
1641
1723
|
ttl: config.ttl,
|
|
1642
1724
|
despawnEffect: config.despawnEffect,
|
|
1643
|
-
weight: config.weight ?? 1
|
|
1725
|
+
weight: config.weight ?? 1,
|
|
1726
|
+
pressureThreshold,
|
|
1727
|
+
shadow,
|
|
1728
|
+
originalPosition: shadow || clicksRemaining !== void 0 ? { x: config.x, y: config.y } : void 0,
|
|
1729
|
+
clicksRemaining
|
|
1644
1730
|
};
|
|
1645
1731
|
this.objects.set(id, entry);
|
|
1646
1732
|
Matter5.Composite.add(this.engine.world, body);
|
|
1733
|
+
if (isStatic && pressureThreshold !== void 0) {
|
|
1734
|
+
this.obstaclePressure.set(id, /* @__PURE__ */ new Set());
|
|
1735
|
+
}
|
|
1647
1736
|
return id;
|
|
1648
1737
|
}
|
|
1649
1738
|
/**
|
|
@@ -1670,6 +1759,17 @@ var OverlayScene = class {
|
|
|
1670
1759
|
} else {
|
|
1671
1760
|
body = await createObstacleAsync(id, config, isStatic);
|
|
1672
1761
|
}
|
|
1762
|
+
let pressureThreshold;
|
|
1763
|
+
if (config.pressureThreshold) {
|
|
1764
|
+
pressureThreshold = typeof config.pressureThreshold.value === "number" ? config.pressureThreshold.value : config.pressureThreshold.value[0];
|
|
1765
|
+
}
|
|
1766
|
+
let shadow;
|
|
1767
|
+
if (config.shadow === true) {
|
|
1768
|
+
shadow = { opacity: 0.3 };
|
|
1769
|
+
} else if (config.shadow && typeof config.shadow === "object") {
|
|
1770
|
+
shadow = { opacity: config.shadow.opacity ?? 0.3 };
|
|
1771
|
+
}
|
|
1772
|
+
const clicksRemaining = config.clickToFall?.clicks;
|
|
1673
1773
|
const entry = {
|
|
1674
1774
|
id,
|
|
1675
1775
|
body,
|
|
@@ -1677,10 +1777,17 @@ var OverlayScene = class {
|
|
|
1677
1777
|
spawnTime: performance.now(),
|
|
1678
1778
|
ttl: config.ttl,
|
|
1679
1779
|
despawnEffect: config.despawnEffect,
|
|
1680
|
-
weight: config.weight ?? 1
|
|
1780
|
+
weight: config.weight ?? 1,
|
|
1781
|
+
pressureThreshold,
|
|
1782
|
+
shadow,
|
|
1783
|
+
originalPosition: shadow || clicksRemaining !== void 0 ? { x: config.x, y: config.y } : void 0,
|
|
1784
|
+
clicksRemaining
|
|
1681
1785
|
};
|
|
1682
1786
|
this.objects.set(id, entry);
|
|
1683
1787
|
Matter5.Composite.add(this.engine.world, body);
|
|
1788
|
+
if (isStatic && pressureThreshold !== void 0) {
|
|
1789
|
+
this.obstaclePressure.set(id, /* @__PURE__ */ new Set());
|
|
1790
|
+
}
|
|
1684
1791
|
return id;
|
|
1685
1792
|
}
|
|
1686
1793
|
/**
|
|
@@ -1939,17 +2046,12 @@ var OverlayScene = class {
|
|
|
1939
2046
|
areFontsInitialized() {
|
|
1940
2047
|
return this.fontsInitialized;
|
|
1941
2048
|
}
|
|
1942
|
-
// ==================== DOM OBSTACLE METHODS ====================
|
|
2049
|
+
// ==================== DOM OBSTACLE METHODS (INTERNAL) ====================
|
|
1943
2050
|
/**
|
|
1944
|
-
* Attach a DOM element to physics.
|
|
1945
|
-
*
|
|
1946
|
-
*
|
|
1947
|
-
* When the element collapses (becomes dynamic), its CSS transform will be
|
|
1948
|
-
* updated each frame to match the physics body position and rotation.
|
|
1949
|
-
*
|
|
1950
|
-
* Shadow creates a cloned DOM element that stays at the original position.
|
|
2051
|
+
* Internal: Attach a DOM element to physics.
|
|
2052
|
+
* Called by spawnObject when element is provided.
|
|
1951
2053
|
*/
|
|
1952
|
-
|
|
2054
|
+
addDOMObstacleInternal(config) {
|
|
1953
2055
|
const { element, x, y } = config;
|
|
1954
2056
|
const width = config.width ?? element.offsetWidth;
|
|
1955
2057
|
const height = config.height ?? element.offsetHeight;
|
|
@@ -1986,6 +2088,7 @@ var OverlayScene = class {
|
|
|
1986
2088
|
};
|
|
1987
2089
|
this.objects.set(id, entry);
|
|
1988
2090
|
Matter5.Composite.add(this.engine.world, body);
|
|
2091
|
+
this.updateDOMElementTransform(entry);
|
|
1989
2092
|
if (isStatic && pressureThreshold !== void 0) {
|
|
1990
2093
|
this.obstaclePressure.set(id, /* @__PURE__ */ new Set());
|
|
1991
2094
|
}
|
|
@@ -2092,11 +2195,61 @@ var OverlayScene = class {
|
|
|
2092
2195
|
maxDimension = Math.max(maxDimension, dims.width, dims.height);
|
|
2093
2196
|
}
|
|
2094
2197
|
if (maxDimension === 0) maxDimension = 100;
|
|
2198
|
+
const calculateLineWidth = (line) => {
|
|
2199
|
+
let width = 0;
|
|
2200
|
+
for (const char of line) {
|
|
2201
|
+
if (char === " ") {
|
|
2202
|
+
width += 20;
|
|
2203
|
+
} else if (/^[A-Za-z0-9]$/.test(char)) {
|
|
2204
|
+
const dims = charDimensions.get(char);
|
|
2205
|
+
if (dims) {
|
|
2206
|
+
const scale = letterSize / Math.max(dims.width, dims.height);
|
|
2207
|
+
const scaledWidth = dims.width * scale;
|
|
2208
|
+
const extraSpacing = config.letterSpacing !== void 0 ? config.letterSpacing - scaledWidth : 0;
|
|
2209
|
+
width += scaledWidth + Math.max(0, extraSpacing);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
return width;
|
|
2214
|
+
};
|
|
2215
|
+
const lineWidths = lines.map((line) => calculateLineWidth(line));
|
|
2216
|
+
const align = config.align ?? "left";
|
|
2217
|
+
const maxLineWidth = Math.max(...lineWidths, 0);
|
|
2218
|
+
let boundsLeft;
|
|
2219
|
+
let boundsRight;
|
|
2220
|
+
switch (align) {
|
|
2221
|
+
case "center":
|
|
2222
|
+
boundsLeft = config.x - maxLineWidth / 2;
|
|
2223
|
+
boundsRight = config.x + maxLineWidth / 2;
|
|
2224
|
+
break;
|
|
2225
|
+
case "right":
|
|
2226
|
+
boundsLeft = config.x - maxLineWidth;
|
|
2227
|
+
boundsRight = config.x;
|
|
2228
|
+
break;
|
|
2229
|
+
default:
|
|
2230
|
+
boundsLeft = config.x;
|
|
2231
|
+
boundsRight = config.x + maxLineWidth;
|
|
2232
|
+
}
|
|
2233
|
+
const boundsTop = config.y - letterSize / 2;
|
|
2234
|
+
const totalHeight = lines.length > 0 ? (lines.length - 1) * lineHeight + letterSize : 0;
|
|
2235
|
+
const boundsBottom = boundsTop + totalHeight;
|
|
2095
2236
|
let currentY = config.y;
|
|
2096
2237
|
let globalCharIndex = 0;
|
|
2097
|
-
for (
|
|
2238
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
2239
|
+
const line = lines[lineIndex];
|
|
2098
2240
|
const chars = line.split("");
|
|
2099
|
-
|
|
2241
|
+
const lineWidth = lineWidths[lineIndex];
|
|
2242
|
+
let currentX;
|
|
2243
|
+
switch (align) {
|
|
2244
|
+
case "center":
|
|
2245
|
+
currentX = config.x - lineWidth / 2;
|
|
2246
|
+
break;
|
|
2247
|
+
case "right":
|
|
2248
|
+
currentX = config.x - lineWidth;
|
|
2249
|
+
break;
|
|
2250
|
+
default:
|
|
2251
|
+
currentX = config.x;
|
|
2252
|
+
}
|
|
2100
2253
|
if (inWord) {
|
|
2101
2254
|
currentWordIndex++;
|
|
2102
2255
|
inWord = false;
|
|
@@ -2212,12 +2365,21 @@ var OverlayScene = class {
|
|
|
2212
2365
|
letterColor,
|
|
2213
2366
|
lineCount: lines.length
|
|
2214
2367
|
});
|
|
2368
|
+
const bounds = {
|
|
2369
|
+
left: boundsLeft,
|
|
2370
|
+
right: boundsRight,
|
|
2371
|
+
top: boundsTop,
|
|
2372
|
+
bottom: boundsBottom,
|
|
2373
|
+
width: boundsRight - boundsLeft,
|
|
2374
|
+
height: boundsBottom - boundsTop
|
|
2375
|
+
};
|
|
2215
2376
|
return {
|
|
2216
2377
|
letterIds,
|
|
2217
2378
|
stringTag,
|
|
2218
2379
|
wordTags,
|
|
2219
2380
|
letterMap,
|
|
2220
|
-
letterDebugInfo: debugInfo
|
|
2381
|
+
letterDebugInfo: debugInfo,
|
|
2382
|
+
bounds
|
|
2221
2383
|
};
|
|
2222
2384
|
}
|
|
2223
2385
|
/**
|
|
@@ -2290,10 +2452,43 @@ var OverlayScene = class {
|
|
|
2290
2452
|
const fontInfo = this.fonts.find((f) => f.fontUrl === fontUrl);
|
|
2291
2453
|
const fontFamily = fontInfo?.name ?? "sans-serif";
|
|
2292
2454
|
const lines = text.split("\n");
|
|
2455
|
+
const lineWidths = lines.map((line) => measureText(loadedFont, line, fontSize));
|
|
2456
|
+
const align = config.align ?? "left";
|
|
2457
|
+
const maxLineWidth = Math.max(...lineWidths, 0);
|
|
2458
|
+
let boundsLeft;
|
|
2459
|
+
let boundsRight;
|
|
2460
|
+
switch (align) {
|
|
2461
|
+
case "center":
|
|
2462
|
+
boundsLeft = x - maxLineWidth / 2;
|
|
2463
|
+
boundsRight = x + maxLineWidth / 2;
|
|
2464
|
+
break;
|
|
2465
|
+
case "right":
|
|
2466
|
+
boundsLeft = x - maxLineWidth;
|
|
2467
|
+
boundsRight = x;
|
|
2468
|
+
break;
|
|
2469
|
+
default:
|
|
2470
|
+
boundsLeft = x;
|
|
2471
|
+
boundsRight = x + maxLineWidth;
|
|
2472
|
+
}
|
|
2473
|
+
const boundsTop = y - fontSize * 0.8;
|
|
2474
|
+
const totalHeight = lines.length > 0 ? (lines.length - 1) * lineHeight + fontSize : 0;
|
|
2475
|
+
const boundsBottom = boundsTop + totalHeight;
|
|
2293
2476
|
let currentY = y;
|
|
2294
2477
|
let globalCharIndex = 0;
|
|
2295
|
-
for (
|
|
2296
|
-
|
|
2478
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
2479
|
+
const line = lines[lineIndex];
|
|
2480
|
+
const lineWidth = lineWidths[lineIndex];
|
|
2481
|
+
let currentX;
|
|
2482
|
+
switch (align) {
|
|
2483
|
+
case "center":
|
|
2484
|
+
currentX = x - lineWidth / 2;
|
|
2485
|
+
break;
|
|
2486
|
+
case "right":
|
|
2487
|
+
currentX = x - lineWidth;
|
|
2488
|
+
break;
|
|
2489
|
+
default:
|
|
2490
|
+
currentX = x;
|
|
2491
|
+
}
|
|
2297
2492
|
if (inWord) {
|
|
2298
2493
|
currentWordIndex++;
|
|
2299
2494
|
inWord = false;
|
|
@@ -2403,12 +2598,21 @@ var OverlayScene = class {
|
|
|
2403
2598
|
wordTags,
|
|
2404
2599
|
lineCount: lines.length
|
|
2405
2600
|
});
|
|
2601
|
+
const bounds = {
|
|
2602
|
+
left: boundsLeft,
|
|
2603
|
+
right: boundsRight,
|
|
2604
|
+
top: boundsTop,
|
|
2605
|
+
bottom: boundsBottom,
|
|
2606
|
+
width: boundsRight - boundsLeft,
|
|
2607
|
+
height: boundsBottom - boundsTop
|
|
2608
|
+
};
|
|
2406
2609
|
return {
|
|
2407
2610
|
letterIds,
|
|
2408
2611
|
stringTag,
|
|
2409
2612
|
wordTags,
|
|
2410
2613
|
letterMap,
|
|
2411
|
-
letterDebugInfo: []
|
|
2614
|
+
letterDebugInfo: [],
|
|
2615
|
+
bounds
|
|
2412
2616
|
};
|
|
2413
2617
|
}
|
|
2414
2618
|
/**
|