@@ -77,6 +77,7 @@ export class MTextInputBox {
7777
7878 private width : number ;
7979 private position : THREE . Vector3 ;
80+ private mtextInsertionOffset = new THREE . Vector3 ( 0 , 0 , 0 ) ;
8081 private enableWordWrap : boolean ;
8182
8283 private mtextString = '' ;
@@ -322,6 +323,9 @@ export class MTextInputBox {
322323 } ) ;
323324
324325 const cursorStyle : Partial < CursorStyle > = { ...( options . cursorStyle ?? { } ) } ;
326+ if ( cursorStyle . height === undefined && cursorStyle . heightMode !== 'fixed' ) {
327+ cursorStyle . height = 0 ;
328+ }
325329 cursorStyle . color ??= '#ffffff' ;
326330 cursorStyle . glowColor ??= '#ffffff' ;
327331
@@ -886,14 +890,20 @@ export class MTextInputBox {
886890
887891 /** Returns cursor world position for IME/caret anchoring. */
888892 public getCursorWorldPosition ( ) : { x : number ; y : number ; z : number } {
889- const cursor = this . cursorLogic . getCursorState ( ) . position ;
893+ const cursorState = this . cursorLogic . getCursorState ( ) ;
894+ const cursor = this . getActiveCursorRenderState ( cursorState . position , cursorState . lineInfo . height ) . position ;
890895 return {
891896 x : this . position . x + cursor . x ,
892897 y : this . position . y + cursor . y ,
893898 z : this . position . z
894899 } ;
895900 }
896901
902+ /** Returns the MTEXT insertion point that preserves the editor's visual top-left placement. */
903+ public getMTextInsertionPoint ( ) : THREE . Vector3 {
904+ return this . position . clone ( ) . add ( this . mtextInsertionOffset ) ;
905+ }
906+
897907 /** Returns current internal state snapshot. */
898908 public getState ( ) : EditorState {
899909 return {
@@ -1179,6 +1189,7 @@ export class MTextInputBox {
11791189
11801190 private relayout ( ) : void {
11811191 if ( ! this . rendererReady ) {
1192+ this . mtextInsertionOffset . set ( 0 , 0 , 0 ) ;
11821193 const fallback = this . createFallbackCharBoxes ( ) ;
11831194 this . layoutContainer = fallback . containerBox ;
11841195 this . updateCursorData ( fallback . charBoxes , fallback . lineBreakIndices , fallback . lineLayouts ) ;
@@ -1201,12 +1212,14 @@ export class MTextInputBox {
12011212 this . replaceRenderedObject ( object ) ;
12021213
12031214 const rendered = this . extractBoxesFromRenderedObject ( object ) ;
1215+ this . mtextInsertionOffset . set ( 0 , 0 , 0 ) ;
12041216 this . normalizeRenderedTopAlignment ( object , rendered ) ;
12051217 this . layoutContainer = rendered . containerBox ;
12061218 this . updateCursorData ( rendered . charBoxes , rendered . lineBreakIndices , rendered . lineLayouts ) ;
12071219 this . updateBoundingBoxGeometry ( ) ;
12081220 } catch ( error ) {
12091221 console . error ( '[mtext-input-box] Failed to sync render MTEXT' , error ) ;
1222+ this . mtextInsertionOffset . set ( 0 , 0 , 0 ) ;
12101223 const fallback = this . createFallbackCharBoxes ( ) ;
12111224 this . layoutContainer = fallback . containerBox ;
12121225 this . updateCursorData ( fallback . charBoxes , fallback . lineBreakIndices , fallback . lineLayouts ) ;
@@ -1349,9 +1362,11 @@ export class MTextInputBox {
13491362 object : MTextObject ,
13501363 rendered : CursorLayoutData
13511364 ) : void {
1352- const dy = - rendered . containerBox . y ;
1365+ const topY = rendered . containerBox . y + rendered . containerBox . height ;
1366+ const dy = - topY ;
13531367 if ( ! Number . isFinite ( dy ) || Math . abs ( dy ) < 1e-8 ) return ;
13541368
1369+ this . mtextInsertionOffset . set ( 0 , dy , 0 ) ;
13551370 object . position . y += dy ;
13561371 object . updateMatrixWorld ( true ) ;
13571372
@@ -1393,38 +1408,39 @@ export class MTextInputBox {
13931408 const lineLayouts : LineLayoutInput [ ] = [ ] ;
13941409
13951410 let x = 0 ;
1396- let y = 0 ;
1397- let maxY = lineHeight ;
1398- lineLayouts . push ( { y : y + lineHeight / 2 , height : lineHeight } ) ;
1411+ let lineTop = 0 ;
1412+ let minY = - lineHeight ;
1413+ lineLayouts . push ( { y : lineTop - lineHeight / 2 , height : lineHeight } ) ;
13991414
14001415 for ( const char of this . getChars ( ) ) {
14011416 if ( char === '\n' ) {
14021417 lineBreakIndices . push ( charBoxes . length ) ;
14031418 x = 0 ;
1404- y + = lineHeight ;
1405- maxY = Math . max ( maxY , y + lineHeight ) ;
1406- lineLayouts . push ( { y : y + lineHeight / 2 , height : lineHeight } ) ;
1419+ lineTop - = lineHeight ;
1420+ minY = Math . min ( minY , lineTop - lineHeight ) ;
1421+ lineLayouts . push ( { y : lineTop - lineHeight / 2 , height : lineHeight } ) ;
14071422 continue ;
14081423 }
14091424
14101425 const width = Math . max ( 1 , this . currentFormat . fontSize * 0.6 ) ;
14111426 if ( this . enableWordWrap && x > 0 && x + width > this . width ) {
14121427 lineBreakIndices . push ( charBoxes . length ) ;
14131428 x = 0 ;
1414- y += lineHeight ;
1429+ lineTop -= lineHeight ;
1430+ minY = Math . min ( minY , lineTop - lineHeight ) ;
1431+ lineLayouts . push ( { y : lineTop - lineHeight / 2 , height : lineHeight } ) ;
14151432 }
14161433
1417- charBoxes . push ( { x, y : y + lineHeight / 2 , width, height : lineHeight } ) ;
1434+ charBoxes . push ( { x, y : lineTop - lineHeight / 2 , width, height : lineHeight } ) ;
14181435 x += width ;
1419- maxY = Math . max ( maxY , y + lineHeight ) ;
14201436 }
14211437
14221438 return {
14231439 containerBox : {
14241440 x : 0 ,
1425- y : 0 ,
1441+ y : minY ,
14261442 width : this . width ,
1427- height : Math . max ( 1 , maxY )
1443+ height : Math . max ( 1 , - minY )
14281444 } ,
14291445 charBoxes,
14301446 lineBreakIndices,
@@ -1763,7 +1779,7 @@ export class MTextInputBox {
17631779 // Best source: renderer-provided line layout already represents
17641780 // the actual first-line center in editor local coordinates.
17651781 position : { x : fallbackPosition . x , y : firstLine . y } ,
1766- height : Math . max ( 1 , lineHeight * 0.8 )
1782+ height : lineHeight
17671783 } ;
17681784 }
17691785
@@ -1772,12 +1788,12 @@ export class MTextInputBox {
17721788 position : {
17731789 x : fallbackPosition . x ,
17741790 // When explicit line layout is unavailable, infer first-line center
1775- // from top-left container coordinates:
1776- // lineCenterY = containerTopY + lineHeight / 2
1791+ // from the container's lower edge in y-up coordinates:
1792+ // lineCenterY = containerBottomY + lineHeight / 2
17771793 // This keeps caret aligned with the top row for empty MTEXT.
17781794 y : this . layoutContainer . y + inferredLineHeight / 2
17791795 } ,
1780- height : Math . max ( 1 , inferredLineHeight * 0.8 )
1796+ height : inferredLineHeight
17811797 } ;
17821798 }
17831799
@@ -1796,7 +1812,7 @@ export class MTextInputBox {
17961812 }
17971813 }
17981814
1799- const emptyLineHeight = ( line . height ?? fallbackHeight ) * 0.8 ;
1815+ const emptyLineHeight = line . height ?? fallbackHeight ;
18001816 return {
18011817 position : { x : fallbackPosition . x , y : line . y ?? fallbackPosition . y } ,
18021818 height : Math . max ( 1 , emptyLineHeight )
0 commit comments