@ -184,6 +184,10 @@ namespace Avalonia.Media.TextFormatting
{
characterHit = shapedRun . GlyphRun . GetCharacterHitFromDistance ( distance , out _ ) ;
var offset = Math . Max ( 0 , currentPosition - shapedRun . Text . Start ) ;
characterHit = new CharacterHit ( characterHit . FirstCharacterIndex + offset , characterHit . TrailingLength ) ;
break ;
}
default :
@ -215,9 +219,11 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/>
public override double GetDistanceFromCharacterHit ( CharacterHit characterHit )
{
var characterIndex = characterHit . FirstCharacterIndex + ( characterHit . TrailingLength ! = 0 ? 1 : 0 ) ;
var isTrailingHit = characterHit . TrailingLength > 0 ;
var characterIndex = characterHit . FirstCharacterIndex + characterHit . TrailingLength ;
var currentDistance = Start ;
var currentPosition = FirstTextSourceIndex ;
var remainingLength = characterIndex - FirstTextSourceIndex ;
GlyphRun ? lastRun = null ;
@ -242,8 +248,10 @@ namespace Avalonia.Media.TextFormatting
}
//Look for a hit in within the current run
if ( characterIndex > = textRun . Text . Start & & characterIndex < = textRun . Text . Start + textRun . Text . Length )
if ( currentPosition + remainingLength < = currentPosition + textRun . Text . Length )
{
characterHit = new CharacterHit ( textRun . Text . Start + remainingLength ) ;
var distance = currentRun . GetDistanceFromCharacterHit ( characterHit ) ;
return currentDistance + distance ;
@ -254,28 +262,27 @@ namespace Avalonia.Media.TextFormatting
{
if ( _f lowDirection = = FlowDirection . LeftToRight & & ( lastRun = = null | | lastRun . IsLeftToRight ) )
{
if ( characterIndex < = textRun . Text . Start )
if ( characterIndex < = currentPosition )
{
return currentDistance ;
}
}
else
{
if ( characterIndex = = textRun . Text . Start )
if ( characterIndex = = currentPosition )
{
return currentDistance ;
}
}
if ( characterIndex = = textRun . Text . Start + textRun . Text . Length & &
characterHit . TrailingLength > 0 )
if ( characterIndex = = currentPosition + textRun . Text . Length & & isTrailingHit )
{
return currentDistance + currentRun . Size . Width ;
}
}
else
{
if ( characterIndex = = textRun . Text . Start )
if ( characterIndex = = currentPosition )
{
return currentDistance + currentRun . Size . Width ;
}
@ -286,20 +293,24 @@ namespace Avalonia.Media.TextFormatting
if ( nextRun ! = null )
{
if ( characterHit . FirstCharacterIndex = = textRun . Text . End & &
nextRun . ShapedBuffer . IsLeftToRight )
if ( nextRun . ShapedBuffer . IsLeftToRight )
{
return currentDistance ;
if ( characterIndex = = currentPosition + textRun . Text . Length )
{
return currentDistance ;
}
}
if ( characterIndex > textRun . Text . End & & nextRun . Text . End < textRun . Text . End )
else
{
return currentDistance ;
if ( currentPosition + nextRun . Text . Length = = characterIndex )
{
return currentDistance ;
}
}
}
else
{
if ( characterIndex > textRun . Text . End )
if ( characterIndex > currentPosition + textRun . Text . Length )
{
return currentDistance ;
}
@ -329,6 +340,12 @@ namespace Avalonia.Media.TextFormatting
//No hit hit found so we add the full width
currentDistance + = textRun . Size . Width ;
currentPosition + = textRun . TextSourceLength ;
remainingLength - = textRun . TextSourceLength ;
if ( remainingLength < = 0 )
{
break ;
}
}
return currentDistance ;
@ -394,210 +411,299 @@ namespace Avalonia.Media.TextFormatting
return GetPreviousCaretCharacterHit ( characterHit ) ;
}
public overrid e IReadOnlyList < TextBounds > GetTextBounds ( int firstTextSourceCharacter Index , int textLength )
privat e IReadOnlyList < TextBounds > GetTextBoundsLeftToRight ( int firstTextSourceIndex , int textLength )
{
if ( firstTextSourceCharacterIndex + textLength < = FirstTextSourceIndex )
{
return Array . Empty < TextBounds > ( ) ;
}
var characterIndex = firstTextSourceIndex + textLength ;
var result = new List < TextBounds > ( TextRuns . Count ) ;
var lastDirection = _f lowDirection ;
var lastDirection = FlowDirection . LeftToRight ;
var currentDirection = lastDirection ;
var currentPosition = FirstTextSourceIndex ;
var currentRect = Rect . Empty ;
var remainingLength = textLength ;
var startX = Start ;
double currentWidth = 0 ;
var currentRect = Rect . Empty ;
//A portion of the line is covered.
for ( var index = 0 ; index < TextRuns . Count ; index + + )
{
var currentRun = TextRuns [ index ] as DrawableTextRun ;
if ( currentRun is null )
if ( TextRuns [ index ] is not DrawableTextRun currentRun )
{
continue ;
}
TextRun ? nextRun = null ;
if ( index + 1 < TextRuns . Count )
if ( currentPosition + currentRun . TextSourceLength < = firstTextSourceIndex )
{
nextRun = TextRuns [ index + 1 ] ;
startX + = currentRun . Size . Width ;
currentPosition + = currentRun . TextSourceLength ;
continue ;
}
if ( nextRun ! = null )
var characterLength = 0 ;
var endX = startX ;
if ( currentRun is ShapedTextCharacters currentShapedRun )
{
switch ( nextRun )
{
case ShapedTextCharacters when currentRun is ShapedTextCharacters :
{
if ( nextRun . Text . Start < currentRun . Text . Start & & firstTextSourceCharacterIndex + textLength < currentRun . Text . End )
{
goto skip ;
}
var offset = Math . Max ( 0 , firstTextSourceIndex - currentPosition ) ;
if ( currentRun . Text . Start > = firstTextSourceCharacterIndex + textLength )
{
goto skip ;
}
currentPosition + = offset ;
if ( currentRun . Text . Start > nextRun . Text . Start & & currentRun . Text . Start < firstTextSourceCharacterIndex )
{
goto skip ;
}
var startIndex = currentRun . Text . Start + offset ;
if ( currentRun . Text . End < firstTextSourceCharacterIndex )
{
goto skip ;
}
var endOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit (
currentShapedRun . ShapedBuffer . IsLeftToRight ?
new CharacterHit ( startIndex + remainingLength ) :
new CharacterHit ( startIndex ) ) ;
goto noop ;
}
default :
{
goto noop ;
}
}
endX + = endOffset ;
var startOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit (
currentShapedRun . ShapedBuffer . IsLeftToRight ?
new CharacterHit ( startIndex ) :
new CharacterHit ( startIndex + remainingLength ) ) ;
startX + = startOffset ;
var endHit = currentShapedRun . GlyphRun . GetCharacterHitFromDistance ( endOffset , out _ ) ;
var startHit = currentShapedRun . GlyphRun . GetCharacterHitFromDistance ( startOffset , out _ ) ;
characterLength = Math . Abs ( endHit . FirstCharacterIndex + endHit . TrailingLength - startHit . FirstCharacterIndex - startHit . TrailingLength ) ;
skip :
currentDirection = currentShapedRun . ShapedBuffer . IsLeftToRight ?
FlowDirection . LeftToRight :
FlowDirection . RightToLeft ;
}
else
{
if ( currentPosition < firstTextSourceIndex )
{
startX + = currentRun . Size . Width ;
currentPosition + = currentRun . TextSourceLength ;
}
continue ;
noop :
if ( currentPosition + currentRun . TextSourceLength < = characterIndex )
{
endX + = currentRun . Size . Width ;
characterLength = currentRun . TextSourceLength ;
}
}
var endX = startX ;
var endOffset = 0d ;
if ( endX < startX )
{
( endX , startX ) = ( startX , endX ) ;
}
switch ( currentRun )
//Lines that only contain a linebreak need to be covered here
if ( characterLength = = 0 )
{
case ShapedTextCharacters shapedRun :
{
endOffset = shapedRun . GlyphRun . GetDistanceFromCharacterHit (
shapedRun . ShapedBuffer . IsLeftToRight ?
new CharacterHit ( firstTextSourceCharacterIndex + textLength ) :
new CharacterHit ( firstTextSourceCharacterIndex ) ) ;
characterLength = NewLineLength ;
}
endX + = endOffset ;
var runwidth = endX - startX ;
var currentRunBounds = new TextRunBounds ( new Rect ( startX , 0 , runwidth , Height ) , currentPosition , characterLength , currentRun ) ;
var startOffset = shapedRun . GlyphRun . GetDistanceFromCharacterHit (
shapedRun . ShapedBuffer . IsLeftToRight ?
new CharacterHit ( firstTextSourceCharacterIndex ) :
new CharacterHit ( firstTextSourceCharacterIndex + textLength ) ) ;
if ( lastDirection = = currentDirection & & result . Count > 0 & & MathUtilities . AreClose ( currentRect . Right , startX ) )
{
currentRect = currentRect . WithWidth ( currentWidth + runwidth ) ;
startX + = startOffset ;
var textBounds = result [ result . Count - 1 ] ;
var characterHit = shapedRun . GlyphRun . IsLeftToRight ?
shapedRun . GlyphRun . GetCharacterHitFromDistance ( endOffset , out _ ) :
shapedRun . GlyphRun . GetCharacterHitFromDistance ( startOffset , out _ ) ;
textBounds . Rectangle = currentRect ;
currentPosition = characterHit . FirstCharacterIndex + characterHit . TrailingLength ;
textBounds . TextRunBounds . Add ( currentRunBounds ) ;
}
else
{
currentRect = currentRunBounds . Rectangle ;
currentDirection = shapedRun . ShapedBuffer . IsLeftToRight ?
FlowDirection . LeftToRight :
FlowDirection . RightToLeft ;
result . Add ( new TextBounds ( currentRect , currentDirection , new List < TextRunBounds > { currentRunBounds } ) ) ;
}
if ( nextRun is ShapedTextCharacters nextShaped )
{
if ( shapedRun . ShapedBuffer . IsLeftToRight = = nextShaped . ShapedBuffer . IsLeftToRight )
{
endOffset = nextShaped . GlyphRun . GetDistanceFromCharacterHit (
nextShaped . ShapedBuffer . IsLeftToRight ?
new CharacterHit ( firstTextSourceCharacterIndex + textLength ) :
new CharacterHit ( firstTextSourceCharacterIndex ) ) ;
currentWidth + = runwidth ;
currentPosition + = characterLength ;
index + + ;
if ( currentDirection = = FlowDirection . LeftToRight )
{
if ( currentPosition > characterIndex )
{
break ;
}
}
else
{
if ( currentPosition < = firstTextSourceIndex )
{
break ;
}
}
endX + = endOffset ;
startX = endX ;
lastDirection = currentDirection ;
remainingLength - = characterLength ;
currentRun = nextShaped ;
if ( remainingLength < = 0 )
{
break ;
}
}
if ( nextShaped . ShapedBuffer . IsLeftToRight )
{
characterHit = nextShaped . GlyphRun . GetCharacterHitFromDistance ( endOffset , out _ ) ;
return result ;
}
currentPosition = characterHit . FirstCharacterIndex + characterHit . TrailingLength ;
}
}
}
private IReadOnlyList < TextBounds > GetTextBoundsRightToLeft ( int firstTextSourceIndex , int textLength )
{
var characterIndex = firstTextSourceIndex + textLength ;
break ;
}
default :
{
if ( currentPosition + currentRun . TextSourceLength < = firstTextSourceCharacterIndex + textLength )
{
endX + = currentRun . Size . Width ;
}
var result = new List < TextBounds > ( TextRuns . Count ) ;
var lastDirection = FlowDirection . LeftToRight ;
var currentDirection = lastDirection ;
if ( currentPosition < firstTextSourceCharacterIndex )
{
startX + = currentRun . Size . Width ;
}
var currentPosition = FirstTextSourceIndex ;
var remainingLength = textLength ;
currentPosition + = currentRun . TextSourceLength ;
var startX = Start + WidthIncludingTrailingWhitespace ;
double currentWidth = 0 ;
var currentRect = Rect . Empty ;
break ;
}
for ( var index = TextRuns . Count - 1 ; index > = 0 ; index - - )
{
if ( TextRuns [ index ] is not DrawableTextRun currentRun )
{
continue ;
}
if ( endX < startX )
if ( currentPosition + currentRun . TextSourceLength < = firstTextSourceIndex )
{
( endX , startX ) = ( startX , endX ) ;
startX - = currentRun . Size . Width ;
currentPosition + = currentRun . TextSourceLength ;
continue ;
}
var width = endX - startX ;
var characterLength = 0 ;
var endX = startX ;
if ( ! MathUtilities . IsZero ( width ) )
if ( currentRun is ShapedTextCharacters currentShapedRun )
{
if ( lastDirection = = currentDirection & & result . Count > 0 & & MathUtilities . AreClose ( currentRect . Right , startX ) )
{
currentRect = currentRect . WithWidth ( currentRect . Width + width ) ;
var offset = Math . Max ( 0 , firstTextSourceIndex - currentPosition ) ;
currentPosition + = offset ;
var startIndex = currentRun . Text . Start + offset ;
var endOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit (
currentShapedRun . ShapedBuffer . IsLeftToRight ?
new CharacterHit ( startIndex + remainingLength ) :
new CharacterHit ( startIndex ) ) ;
endX + = endOffset - currentShapedRun . Size . Width ;
var startOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit (
currentShapedRun . ShapedBuffer . IsLeftToRight ?
new CharacterHit ( startIndex ) :
new CharacterHit ( startIndex + remainingLength ) ) ;
var textBounds = new TextBounds ( currentRect , currentDirection ) ;
startX + = startOffset - currentShapedRun . Size . Width ;
result [ result . Count - 1 ] = textBounds ;
var endHit = currentShapedRun . GlyphRun . GetCharacterHitFromDistance ( endOffset , out _ ) ;
var startHit = currentShapedRun . GlyphRun . GetCharacterHitFromDistance ( startOffset , out _ ) ;
characterLength = Math . Abs ( startHit . FirstCharacterIndex + startHit . TrailingLength - endHit . FirstCharacterIndex - endHit . TrailingLength ) ;
currentDirection = currentShapedRun . ShapedBuffer . IsLeftToRight ?
FlowDirection . LeftToRight :
FlowDirection . RightToLeft ;
}
else
{
if ( currentPosition + currentRun . TextSourceLength < = characterIndex )
{
endX - = currentRun . Size . Width ;
}
else
if ( currentPosition < firstTextSourceIndex )
{
startX - = currentRun . Size . Width ;
characterLength = currentRun . TextSourceLength ;
}
}
if ( endX < startX )
{
( endX , startX ) = ( startX , endX ) ;
}
currentRect = new Rect ( startX , 0 , width , Height ) ;
//Lines that only contain a linebreak need to be covered here
if ( characterLength = = 0 )
{
characterLength = NewLineLength ;
}
result . Add ( new TextBounds ( currentRect , currentDirection ) ) ;
var runWidth = endX - startX ;
var currentRunBounds = new TextRunBounds ( new Rect ( startX , 0 , runWidth , Height ) , currentPosition , characterLength , currentRun ) ;
}
if ( lastDirection = = currentDirection & & result . Count > 0 & & MathUtilities . AreClose ( currentRect . Right , startX ) )
{
currentRect = currentRect . WithWidth ( currentWidth + runWidth ) ;
var textBounds = result [ result . Count - 1 ] ;
textBounds . Rectangle = currentRect ;
textBounds . TextRunBounds . Add ( currentRunBounds ) ;
}
else
{
currentRect = currentRunBounds . Rectangle ;
result . Add ( new TextBounds ( currentRect , currentDirection , new List < TextRunBounds > { currentRunBounds } ) ) ;
}
currentWidth + = runWidth ;
currentPosition + = characterLength ;
if ( currentDirection = = FlowDirection . LeftToRight )
{
if ( currentPosition > firstTextSourceCharacterIndex + textLength )
if ( currentPosition > characterIndex )
{
break ;
}
}
else
{
if ( currentPosition < = firstTextSourceCharacterIndex )
if ( currentPosition < = firstTextSourceIndex )
{
break ;
}
endX + = currentRun . Size . Width - endOffset ;
}
lastDirection = currentDirection ;
startX = endX ;
remainingLength - = characterLength ;
if ( remainingLength < = 0 )
{
break ;
}
}
return result ;
}
public override IReadOnlyList < TextBounds > GetTextBounds ( int firstTextSourceIndex , int textLength )
{
if ( _ paragraphProperties . FlowDirection = = FlowDirection . LeftToRight )
{
return GetTextBoundsLeftToRight ( firstTextSourceIndex , textLength ) ;
}
return GetTextBoundsRightToLeft ( firstTextSourceIndex , textLength ) ;
}
public TextLineImpl FinalizeLine ( )
{
_ textLineMetrics = CreateLineMetrics ( ) ;