@myrmidon/gve-snapshot-rendition 2.0.1 → 2.0.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/dist/index.cjs.min.js +2 -2
- package/dist/index.cjs.min.js.map +1 -1
- package/dist/index.js +139 -167
- package/dist/index.js.map +1 -1
- package/dist/src/rendering/text-renderer.d.ts +11 -14
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1385,7 +1385,7 @@ class TextRenderer {
|
|
|
1385
1385
|
}
|
|
1386
1386
|
// Apply r_t-value override if specified
|
|
1387
1387
|
// This overrides the operation's value (the text being added) for display only
|
|
1388
|
-
if (config.textValue) {
|
|
1388
|
+
if (config.textValue !== null && config.textValue !== undefined) {
|
|
1389
1389
|
this._logger.debug("TextRenderer", `Applying r_t-value override: "${config.textValue}" (original text had ${nodes.length} characters)`);
|
|
1390
1390
|
// Replace node data with characters from r_t-value
|
|
1391
1391
|
// If r_t-value has fewer characters than nodes, we only override the first N nodes
|
|
@@ -1417,124 +1417,186 @@ class TextRenderer {
|
|
|
1417
1417
|
// Use first RBR for positioning (additional text doesn't repeat per RBR)
|
|
1418
1418
|
const rbr = rbrs[0];
|
|
1419
1419
|
this._logger.debug("TextRenderer", `Using RBR for positioning`, rbr);
|
|
1420
|
-
// 2. Calculate
|
|
1421
|
-
const position = config.textPosition || "o";
|
|
1422
|
-
const targetPos = this.calculateTargetPosition(position, rbr);
|
|
1423
|
-
// Parse offsets using RBR bounds (offsets can be like "0.5th" = half of RBR height)
|
|
1420
|
+
// 2. Calculate position and offsets
|
|
1421
|
+
const position = config.textPosition || "o";
|
|
1424
1422
|
const offsetX = parseOffset(config.textOffsetX || 0, rbr.height, rbr.width);
|
|
1425
1423
|
const offsetY = parseOffset(config.textOffsetY || 0, rbr.height, rbr.width);
|
|
1426
|
-
//
|
|
1427
|
-
//
|
|
1428
|
-
//
|
|
1429
|
-
//
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1424
|
+
// 3. Render characters at preliminary positions (baseline y=0) into a hidden
|
|
1425
|
+
// temporary group so we can measure the actual visual EBR via getBBox().
|
|
1426
|
+
// This mirrors the hint renderer's approach: render → measure → reposition.
|
|
1427
|
+
// Using config font metrics here ensures correct character widths.
|
|
1428
|
+
const initialPositions = this.calculateAdditionalTextPositions(nodes, 0, 0, config);
|
|
1429
|
+
const tempGroup = createSVGElement("g");
|
|
1430
|
+
tempGroup.setAttribute("visibility", "hidden");
|
|
1431
|
+
rootSvg.appendChild(tempGroup);
|
|
1432
|
+
const measuredElements = [];
|
|
1433
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
1434
|
+
const node = nodes[i];
|
|
1435
|
+
const pos = initialPositions[i];
|
|
1436
|
+
if (isLineBreak(node) || isSpace(node))
|
|
1437
|
+
continue;
|
|
1438
|
+
const textEl = createTextElement(node.data, {
|
|
1439
|
+
id: `n_${node.id}`,
|
|
1440
|
+
class: `node version-${versionTag}`,
|
|
1441
|
+
x: pos.x,
|
|
1442
|
+
y: pos.y,
|
|
1443
|
+
});
|
|
1444
|
+
applyTextStyle(textEl, {
|
|
1445
|
+
fontFamily: config.fontFamily,
|
|
1446
|
+
fontSize: config.fontSize,
|
|
1447
|
+
foreColor: config.foreColor,
|
|
1448
|
+
backColor: config.backColor,
|
|
1449
|
+
italic: config.italic,
|
|
1450
|
+
bold: config.bold,
|
|
1451
|
+
});
|
|
1452
|
+
tempGroup.appendChild(textEl);
|
|
1453
|
+
measuredElements.push({ el: textEl, node, initX: pos.x, initY: pos.y });
|
|
1454
|
+
}
|
|
1455
|
+
// 4. Measure actual visual EBR from the temp group
|
|
1456
|
+
const ebrBbox = getSafeBBox(tempGroup);
|
|
1457
|
+
const ebr = {
|
|
1458
|
+
x: ebrBbox.x,
|
|
1459
|
+
y: ebrBbox.y,
|
|
1460
|
+
width: ebrBbox.width,
|
|
1461
|
+
height: ebrBbox.height,
|
|
1462
|
+
right: ebrBbox.x + ebrBbox.width,
|
|
1463
|
+
bottom: ebrBbox.y + ebrBbox.height,
|
|
1464
|
+
};
|
|
1465
|
+
// 5. Compute translation: align EBR with RBR using the same logic as hints
|
|
1466
|
+
const rbrAlignPoint = this.calculateRBRAlignmentPoint(position, rbr);
|
|
1467
|
+
const ebrAlignPoint = this.calculateEBRAlignmentPoint(position, ebr);
|
|
1468
|
+
const dx = rbrAlignPoint.x - ebrAlignPoint.x + offsetX;
|
|
1469
|
+
const dy = rbrAlignPoint.y - ebrAlignPoint.y + offsetY;
|
|
1470
|
+
tempGroup.remove();
|
|
1449
1471
|
this._logger.debug("TextRenderer", `Text position calculated`, {
|
|
1450
1472
|
position,
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1473
|
+
rbr,
|
|
1474
|
+
ebr,
|
|
1475
|
+
rbrAlignPoint,
|
|
1476
|
+
ebrAlignPoint,
|
|
1454
1477
|
offsets: { x: offsetX, y: offsetY },
|
|
1455
|
-
|
|
1478
|
+
translation: { dx, dy },
|
|
1456
1479
|
});
|
|
1457
|
-
//
|
|
1458
|
-
const
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1480
|
+
// 6. Compute final textBounds for prolog check
|
|
1481
|
+
const textBounds = {
|
|
1482
|
+
x: ebr.x + dx,
|
|
1483
|
+
y: ebr.y + dy,
|
|
1484
|
+
width: ebr.width,
|
|
1485
|
+
height: ebr.height,
|
|
1486
|
+
right: ebr.right + dx,
|
|
1487
|
+
bottom: ebr.bottom + dy,
|
|
1488
|
+
};
|
|
1489
|
+
// 7. Check if prolog panning is needed
|
|
1463
1490
|
if (panZoomInstance &&
|
|
1464
1491
|
viewportWidth &&
|
|
1465
1492
|
viewportHeight &&
|
|
1466
|
-
this._settings.prologDuration > 0
|
|
1467
|
-
textBounds) {
|
|
1493
|
+
this._settings.prologDuration > 0) {
|
|
1468
1494
|
const isVisible = this._animationEngine.isElementVisible(textBounds, panZoomInstance, viewportWidth, viewportHeight);
|
|
1469
1495
|
if (!isVisible) {
|
|
1470
1496
|
this._logger.debug("TextRenderer", `Additional text for ${versionTag} would be outside viewport, executing prolog`);
|
|
1471
1497
|
await this._animationEngine.animateProlog(panZoomInstance, textBounds, viewportWidth, viewportHeight, this._settings.prologDuration);
|
|
1472
1498
|
}
|
|
1473
1499
|
}
|
|
1474
|
-
//
|
|
1500
|
+
// 8. Get animation function if specified
|
|
1475
1501
|
const animationFn = this._settings.charAnimationId
|
|
1476
1502
|
? this._animationEngine
|
|
1477
1503
|
.getFactory()
|
|
1478
1504
|
.resolveAnimation(`#${this._settings.charAnimationId}`, this._settings.animations, "char")
|
|
1479
1505
|
: undefined;
|
|
1480
|
-
//
|
|
1481
|
-
for (
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1506
|
+
// 9. Move each element to its final position and add to the SVG
|
|
1507
|
+
for (const { el, node, initX, initY } of measuredElements) {
|
|
1508
|
+
el.setAttribute("x", String(initX + dx));
|
|
1509
|
+
el.setAttribute("y", String(initY + dy));
|
|
1510
|
+
rootSvg.appendChild(el);
|
|
1511
|
+
const bbox = getSafeBBox(el);
|
|
1512
|
+
this._boundsCache.set(`n_${node.id}`, bbox);
|
|
1513
|
+
if (animationFn) {
|
|
1514
|
+
el.style.opacity = "0";
|
|
1515
|
+
await this._animationEngine.animate(el, animationFn, rootSvg);
|
|
1487
1516
|
}
|
|
1488
|
-
await this.renderCharacter(node, pos, rootSvg, versionTag, animationFn, config);
|
|
1489
1517
|
}
|
|
1490
1518
|
this._logger.timeEnd(`renderAdditionalText-${versionTag}`);
|
|
1491
1519
|
}
|
|
1492
1520
|
/**
|
|
1493
1521
|
* Calculate positions for additional text characters.
|
|
1494
1522
|
* Additional text flows left to right from the base position.
|
|
1523
|
+
* When config is provided its font metrics are used for character width
|
|
1524
|
+
* measurement, matching what applyTextStyle will actually render.
|
|
1495
1525
|
*/
|
|
1496
|
-
calculateAdditionalTextPositions(nodes, baseX, baseY) {
|
|
1526
|
+
calculateAdditionalTextPositions(nodes, baseX, baseY, config) {
|
|
1497
1527
|
const positions = [];
|
|
1498
1528
|
let currentX = baseX;
|
|
1499
1529
|
const currentY = baseY;
|
|
1530
|
+
const fontSize = config?.fontSize ?? this._settings.fontSize;
|
|
1531
|
+
const fontFamily = config?.fontFamily ?? this._settings.fontFamily;
|
|
1532
|
+
const bold = config?.bold ?? this._settings.bold;
|
|
1533
|
+
const italic = config?.italic ?? this._settings.italic;
|
|
1500
1534
|
for (let i = 0; i < nodes.length; i++) {
|
|
1501
1535
|
const node = nodes[i];
|
|
1502
1536
|
if (isLineBreak(node)) {
|
|
1503
|
-
|
|
1504
|
-
positions.push({
|
|
1505
|
-
x: currentX,
|
|
1506
|
-
y: currentY,
|
|
1507
|
-
nodeId: node.id,
|
|
1508
|
-
lineNumber: 0,
|
|
1509
|
-
});
|
|
1537
|
+
positions.push({ x: currentX, y: currentY, nodeId: node.id, lineNumber: 0 });
|
|
1510
1538
|
}
|
|
1511
1539
|
else if (isSpace(node)) {
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
positions.push({
|
|
1515
|
-
x: currentX,
|
|
1516
|
-
y: currentY,
|
|
1517
|
-
nodeId: node.id,
|
|
1518
|
-
lineNumber: 0,
|
|
1519
|
-
});
|
|
1540
|
+
const spaceWidth = fontSize * 0.33;
|
|
1541
|
+
positions.push({ x: currentX, y: currentY, nodeId: node.id, lineNumber: 0 });
|
|
1520
1542
|
currentX += spaceWidth + this._settings.charSpacing;
|
|
1521
1543
|
}
|
|
1522
1544
|
else {
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
x: currentX,
|
|
1526
|
-
y: currentY,
|
|
1527
|
-
nodeId: node.id,
|
|
1528
|
-
lineNumber: 0,
|
|
1529
|
-
});
|
|
1530
|
-
// Calculate character width for next position
|
|
1531
|
-
// Use measurement root for consistent font metrics with actual rendering
|
|
1532
|
-
const charWidth = getTextWidth(node.data, this._settings.fontFamily, this._settings.fontSize, this._settings.bold, this._settings.italic, this._measurementRoot);
|
|
1545
|
+
positions.push({ x: currentX, y: currentY, nodeId: node.id, lineNumber: 0 });
|
|
1546
|
+
const charWidth = getTextWidth(node.data, fontFamily, fontSize, bold, italic, this._measurementRoot);
|
|
1533
1547
|
currentX += charWidth + this._settings.charSpacing;
|
|
1534
1548
|
}
|
|
1535
1549
|
}
|
|
1536
1550
|
return positions;
|
|
1537
1551
|
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Calculate the RBR anchor point for a given position type.
|
|
1554
|
+
* This is the point on the RBR where the corresponding EBR point will land.
|
|
1555
|
+
*/
|
|
1556
|
+
calculateRBRAlignmentPoint(position, rbr) {
|
|
1557
|
+
const centerX = rbr.x + rbr.width / 2;
|
|
1558
|
+
const centerY = rbr.y + rbr.height / 2;
|
|
1559
|
+
switch (position) {
|
|
1560
|
+
case "n": return { x: centerX, y: rbr.y };
|
|
1561
|
+
case "ne": return { x: rbr.right, y: rbr.y };
|
|
1562
|
+
case "e": return { x: rbr.right, y: centerY };
|
|
1563
|
+
case "se": return { x: rbr.right, y: rbr.bottom };
|
|
1564
|
+
case "s": return { x: centerX, y: rbr.bottom };
|
|
1565
|
+
case "sw": return { x: rbr.x, y: rbr.bottom };
|
|
1566
|
+
case "w": return { x: rbr.x, y: centerY };
|
|
1567
|
+
case "nw": return { x: rbr.x, y: rbr.y };
|
|
1568
|
+
case "inw": return { x: rbr.x, y: rbr.y };
|
|
1569
|
+
case "ine": return { x: rbr.right, y: rbr.y };
|
|
1570
|
+
case "isw": return { x: rbr.x, y: rbr.bottom };
|
|
1571
|
+
case "ise": return { x: rbr.right, y: rbr.bottom };
|
|
1572
|
+
case "o":
|
|
1573
|
+
default: return { x: centerX, y: centerY };
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Calculate the EBR anchor point for a given position type.
|
|
1578
|
+
* This is the point on the EBR that should coincide with the RBR anchor point.
|
|
1579
|
+
*/
|
|
1580
|
+
calculateEBRAlignmentPoint(position, ebr) {
|
|
1581
|
+
const centerX = ebr.x + ebr.width / 2;
|
|
1582
|
+
const centerY = ebr.y + ebr.height / 2;
|
|
1583
|
+
switch (position) {
|
|
1584
|
+
case "n": return { x: centerX, y: ebr.bottom };
|
|
1585
|
+
case "ne": return { x: ebr.x, y: ebr.bottom };
|
|
1586
|
+
case "e": return { x: ebr.x, y: centerY };
|
|
1587
|
+
case "se": return { x: ebr.x, y: ebr.y };
|
|
1588
|
+
case "s": return { x: centerX, y: ebr.y };
|
|
1589
|
+
case "sw": return { x: ebr.right, y: ebr.y };
|
|
1590
|
+
case "w": return { x: ebr.right, y: centerY };
|
|
1591
|
+
case "nw": return { x: ebr.right, y: ebr.bottom };
|
|
1592
|
+
case "inw": return { x: ebr.x, y: ebr.y };
|
|
1593
|
+
case "ine": return { x: ebr.right, y: ebr.y };
|
|
1594
|
+
case "isw": return { x: ebr.x, y: ebr.bottom };
|
|
1595
|
+
case "ise": return { x: ebr.right, y: ebr.bottom };
|
|
1596
|
+
case "o":
|
|
1597
|
+
default: return { x: centerX, y: centerY };
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1538
1600
|
/**
|
|
1539
1601
|
* Calculate RBRs from reference nodes.
|
|
1540
1602
|
* Nodes on different lines create separate RBRs.
|
|
@@ -1577,96 +1639,6 @@ class TextRenderer {
|
|
|
1577
1639
|
}
|
|
1578
1640
|
return rbrs;
|
|
1579
1641
|
}
|
|
1580
|
-
/**
|
|
1581
|
-
* Calculate target position on the RBR based on position type.
|
|
1582
|
-
*/
|
|
1583
|
-
calculateTargetPosition(position, rbr) {
|
|
1584
|
-
const centerX = rbr.x + rbr.width / 2;
|
|
1585
|
-
const centerY = rbr.y + rbr.height / 2;
|
|
1586
|
-
switch (position) {
|
|
1587
|
-
case "n": // North (top center)
|
|
1588
|
-
return { x: centerX, y: rbr.y };
|
|
1589
|
-
case "ne": // Northeast (top right)
|
|
1590
|
-
return { x: rbr.right, y: rbr.y };
|
|
1591
|
-
case "e": // East (middle right)
|
|
1592
|
-
return { x: rbr.right, y: centerY };
|
|
1593
|
-
case "se": // Southeast (bottom right)
|
|
1594
|
-
return { x: rbr.right, y: rbr.bottom };
|
|
1595
|
-
case "s": // South (bottom center)
|
|
1596
|
-
return { x: centerX, y: rbr.bottom };
|
|
1597
|
-
case "sw": // Southwest (bottom left)
|
|
1598
|
-
return { x: rbr.x, y: rbr.bottom };
|
|
1599
|
-
case "w": // West (middle left)
|
|
1600
|
-
return { x: rbr.x, y: centerY };
|
|
1601
|
-
case "nw": // Northwest (top left)
|
|
1602
|
-
return { x: rbr.x, y: rbr.y };
|
|
1603
|
-
case "o": // Origin (center)
|
|
1604
|
-
default:
|
|
1605
|
-
return { x: centerX, y: centerY };
|
|
1606
|
-
}
|
|
1607
|
-
}
|
|
1608
|
-
/**
|
|
1609
|
-
* Calculate total rendered width of additional text nodes.
|
|
1610
|
-
* Mirrors the spacing logic in calculateAdditionalTextPositions so the result
|
|
1611
|
-
* is the exact horizontal span from the first character's left edge to the last
|
|
1612
|
-
* character's right edge.
|
|
1613
|
-
*/
|
|
1614
|
-
calculateTotalTextWidth(nodes) {
|
|
1615
|
-
let totalWidth = 0;
|
|
1616
|
-
let count = 0;
|
|
1617
|
-
for (const node of nodes) {
|
|
1618
|
-
if (isLineBreak(node))
|
|
1619
|
-
continue;
|
|
1620
|
-
if (isSpace(node)) {
|
|
1621
|
-
totalWidth += this._settings.fontSize * 0.33 + this._settings.charSpacing;
|
|
1622
|
-
count++;
|
|
1623
|
-
continue;
|
|
1624
|
-
}
|
|
1625
|
-
const charWidth = getTextWidth(node.data, this._settings.fontFamily, this._settings.fontSize, this._settings.bold, this._settings.italic, this._measurementRoot);
|
|
1626
|
-
totalWidth += charWidth + this._settings.charSpacing;
|
|
1627
|
-
count++;
|
|
1628
|
-
}
|
|
1629
|
-
// charSpacing is added after every character; remove the trailing one
|
|
1630
|
-
if (count > 0) {
|
|
1631
|
-
totalWidth -= this._settings.charSpacing;
|
|
1632
|
-
}
|
|
1633
|
-
return Math.max(0, totalWidth);
|
|
1634
|
-
}
|
|
1635
|
-
/**
|
|
1636
|
-
* Calculate bounding rectangle for a set of positioned characters.
|
|
1637
|
-
*/
|
|
1638
|
-
calculateTextBounds(nodes, positions) {
|
|
1639
|
-
if (nodes.length === 0)
|
|
1640
|
-
return null;
|
|
1641
|
-
// We need to estimate bounds before rendering
|
|
1642
|
-
// Use approximate character dimensions based on settings
|
|
1643
|
-
const charWidth = this._settings.fontSize * 0.6; // Approximate
|
|
1644
|
-
const charHeight = this._settings.fontSize;
|
|
1645
|
-
let minX = Infinity;
|
|
1646
|
-
let minY = Infinity;
|
|
1647
|
-
let maxX = -Infinity;
|
|
1648
|
-
let maxY = -Infinity;
|
|
1649
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
1650
|
-
const pos = positions[i];
|
|
1651
|
-
const node = nodes[i];
|
|
1652
|
-
if (isLineBreak(node) || isSpace(node))
|
|
1653
|
-
continue;
|
|
1654
|
-
minX = Math.min(minX, pos.x);
|
|
1655
|
-
minY = Math.min(minY, pos.y - charHeight);
|
|
1656
|
-
maxX = Math.max(maxX, pos.x + charWidth);
|
|
1657
|
-
maxY = Math.max(maxY, pos.y);
|
|
1658
|
-
}
|
|
1659
|
-
if (!isFinite(minX))
|
|
1660
|
-
return null;
|
|
1661
|
-
return {
|
|
1662
|
-
x: minX,
|
|
1663
|
-
y: minY,
|
|
1664
|
-
width: maxX - minX,
|
|
1665
|
-
height: maxY - minY,
|
|
1666
|
-
right: maxX,
|
|
1667
|
-
bottom: maxY,
|
|
1668
|
-
};
|
|
1669
|
-
}
|
|
1670
1642
|
/**
|
|
1671
1643
|
* Update settings.
|
|
1672
1644
|
*/
|
|
@@ -25945,7 +25917,7 @@ class GveSnapshotRendition extends HTMLElement {
|
|
|
25945
25917
|
* of the web component is loaded.
|
|
25946
25918
|
*/
|
|
25947
25919
|
static get version() {
|
|
25948
|
-
return "2.0.
|
|
25920
|
+
return "2.0.2";
|
|
25949
25921
|
}
|
|
25950
25922
|
constructor() {
|
|
25951
25923
|
super();
|
|
@@ -40832,7 +40804,7 @@ function requireD () {
|
|
|
40832
40804
|
+ 'pragma private protected public pure ref return scope shared static struct '
|
|
40833
40805
|
+ 'super switch synchronized template this throw try typedef typeid typeof union '
|
|
40834
40806
|
+ 'unittest version void volatile while with __FILE__ __LINE__ __gshared|10 '
|
|
40835
|
-
+ '__thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ 2.0.
|
|
40807
|
+
+ '__thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ 2.0.2',
|
|
40836
40808
|
built_in:
|
|
40837
40809
|
'bool cdouble cent cfloat char creal dchar delegate double dstring float function '
|
|
40838
40810
|
+ 'idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar '
|