@@ -1468,6 +1468,97 @@ export class MTextInputBox {
14681468 this . syncStateFromCursor ( ) ;
14691469 }
14701470
1471+ /**
1472+ * Editor vertical bounds for the selection box / bounding overlay.
1473+ * Renderer `layout.lines` strips are often taller than actual glyphs (leading
1474+ * above the first row, uniform line-height slots, etc.). When a line has
1475+ * glyph boxes, use those for that row; keep the strip only for empty lines.
1476+ */
1477+ private computeEditorVerticalBounds (
1478+ lineLayouts : LineLayoutInput [ ] ,
1479+ charBoxes : Box [ ] ,
1480+ objectLocal : Box
1481+ ) : { minY : number ; maxY : number } {
1482+ const fallback = ( ) : { minY : number ; maxY : number } => ( {
1483+ minY : objectLocal . y - objectLocal . height / 2 ,
1484+ maxY : objectLocal . y + objectLocal . height / 2
1485+ } ) ;
1486+
1487+ const overlapY = ( a0 : number , a1 : number , b0 : number , b1 : number ) : boolean =>
1488+ a0 < b1 + 1e-4 && a1 > b0 - 1e-4 ;
1489+
1490+ if ( lineLayouts . length === 0 && charBoxes . length === 0 ) {
1491+ return fallback ( ) ;
1492+ }
1493+
1494+ if ( lineLayouts . length === 0 ) {
1495+ let minY = Number . POSITIVE_INFINITY ;
1496+ let maxY = Number . NEGATIVE_INFINITY ;
1497+ for ( const b of charBoxes ) {
1498+ minY = Math . min ( minY , b . y - b . height / 2 ) ;
1499+ maxY = Math . max ( maxY , b . y + b . height / 2 ) ;
1500+ }
1501+ return Number . isFinite ( minY ) && Number . isFinite ( maxY ) && maxY >= minY
1502+ ? { minY, maxY }
1503+ : fallback ( ) ;
1504+ }
1505+
1506+ if ( charBoxes . length === 0 ) {
1507+ let minY = Number . POSITIVE_INFINITY ;
1508+ let maxY = Number . NEGATIVE_INFINITY ;
1509+ for ( const line of lineLayouts ) {
1510+ const lo = line . y - line . height / 2 ;
1511+ const hi = line . y + line . height / 2 ;
1512+ minY = Math . min ( minY , lo ) ;
1513+ maxY = Math . max ( maxY , hi ) ;
1514+ }
1515+ return Number . isFinite ( minY ) && Number . isFinite ( maxY ) && maxY >= minY
1516+ ? { minY, maxY }
1517+ : fallback ( ) ;
1518+ }
1519+
1520+ let minY = Number . POSITIVE_INFINITY ;
1521+ let maxY = Number . NEGATIVE_INFINITY ;
1522+
1523+ for ( const line of lineLayouts ) {
1524+ const lo = line . y - line . height / 2 ;
1525+ const hi = line . y + line . height / 2 ;
1526+ const onLine = charBoxes . filter ( ( b ) => {
1527+ const c0 = b . y - b . height / 2 ;
1528+ const c1 = b . y + b . height / 2 ;
1529+ return overlapY ( c0 , c1 , lo , hi ) ;
1530+ } ) ;
1531+ if ( onLine . length === 0 ) {
1532+ minY = Math . min ( minY , lo ) ;
1533+ maxY = Math . max ( maxY , hi ) ;
1534+ } else {
1535+ for ( const b of onLine ) {
1536+ minY = Math . min ( minY , b . y - b . height / 2 ) ;
1537+ maxY = Math . max ( maxY , b . y + b . height / 2 ) ;
1538+ }
1539+ }
1540+ }
1541+
1542+ for ( const b of charBoxes ) {
1543+ const c0 = b . y - b . height / 2 ;
1544+ const c1 = b . y + b . height / 2 ;
1545+ const overlapsAnyLine = lineLayouts . some ( ( line ) => {
1546+ const lo = line . y - line . height / 2 ;
1547+ const hi = line . y + line . height / 2 ;
1548+ return overlapY ( c0 , c1 , lo , hi ) ;
1549+ } ) ;
1550+ if ( ! overlapsAnyLine ) {
1551+ minY = Math . min ( minY , c0 ) ;
1552+ maxY = Math . max ( maxY , c1 ) ;
1553+ }
1554+ }
1555+
1556+ if ( ! Number . isFinite ( minY ) || ! Number . isFinite ( maxY ) || maxY < minY ) {
1557+ return fallback ( ) ;
1558+ }
1559+ return { minY, maxY } ;
1560+ }
1561+
14711562 private extractBoxesFromRenderedObject ( object : MTextObject ) : CursorLayoutData {
14721563 const layout = object . createLayoutData ( ) ;
14731564 const charBoxes : Box [ ] = [ ] ;
@@ -1495,17 +1586,11 @@ export class MTextInputBox {
14951586 . filter ( ( value ) => value >= 0 && value <= charBoxes . length ) ;
14961587
14971588 const local = this . toLocalBox ( object . box ) ;
1498- let containerTop = local . y - local . height / 2 ;
1499- let containerBottom = local . y + local . height / 2 ;
1500-
1501- if ( lineLayouts . length > 0 ) {
1502- for ( const line of lineLayouts ) {
1503- const top = line . y - line . height / 2 ;
1504- const bottom = line . y + line . height / 2 ;
1505- containerTop = Math . min ( containerTop , top ) ;
1506- containerBottom = Math . max ( containerBottom , bottom ) ;
1507- }
1508- }
1589+ const { minY : containerTop , maxY : containerBottom } = this . computeEditorVerticalBounds (
1590+ lineLayouts ,
1591+ charBoxes ,
1592+ local
1593+ ) ;
15091594
15101595 const containerBox = {
15111596 x : local . x ,
@@ -1515,7 +1600,12 @@ export class MTextInputBox {
15151600 } ;
15161601 containerBox . x = 0 ;
15171602 containerBox . width = this . width ;
1518- containerBox . height = Math . max ( containerBox . height , this . getFallbackLineAdvance ( ) ) ;
1603+ const minHeight = this . getFallbackLineAdvance ( ) ;
1604+ if ( containerBox . height < minHeight ) {
1605+ const delta = minHeight - containerBox . height ;
1606+ containerBox . y -= delta ;
1607+ containerBox . height = minHeight ;
1608+ }
15191609
15201610 return {
15211611 containerBox,
0 commit comments