@ -10,6 +10,7 @@ namespace Avalonia.Media.TextFormatting
private readonly double _ paragraphWidth ;
private readonly TextParagraphProperties _ paragraphProperties ;
private TextLineMetrics _ textLineMetrics ;
private TextLineBreak ? _ textLineBreak ;
private readonly FlowDirection _ resolvedFlowDirection ;
public TextLineImpl ( TextRun [ ] textRuns , int firstTextSourceIndex , int length , double paragraphWidth ,
@ -18,7 +19,7 @@ namespace Avalonia.Media.TextFormatting
{
FirstTextSourceIndex = firstTextSourceIndex ;
Length = length ;
T extLineBreak = lineBreak ;
_ t extLineBreak = lineBreak ;
HasCollapsed = hasCollapsed ;
_ textRuns = textRuns ;
@ -38,7 +39,7 @@ namespace Avalonia.Media.TextFormatting
public override int Length { get ; }
/// <inheritdoc/>
public override TextLineBreak ? TextLineBreak { get ; }
public override TextLineBreak ? TextLineBreak = > _ textLineBreak ;
/// <inheritdoc/>
public override bool HasCollapsed { get ; }
@ -167,50 +168,54 @@ namespace Avalonia.Media.TextFormatting
{
if ( _ textRuns . Length = = 0 )
{
return new CharacterHit ( ) ;
return new CharacterHit ( FirstTextSourceIndex ) ;
}
distance - = Start ;
var firstRunIndex = 0 ;
var lastIndex = _ textRuns . Length - 1 ;
if ( _ textRuns [ firstRun Index] is TextEndOfLine )
if ( _ textRuns [ last Index] is TextEndOfLine )
{
firstRunIndex + + ;
lastIndex - - ;
}
if ( firstRunIndex > = _ textRuns . Length )
var currentPosition = FirstTextSourceIndex ;
if ( lastIndex < 0 )
{
return new CharacterHit ( FirstTextSourceIndex ) ;
return new CharacterHit ( currentPosition ) ;
}
if ( distance < = 0 )
{
var firstRun = _ textRuns [ firstRunIndex ] ;
var firstRun = _ textRuns [ 0 ] ;
return GetRunCharacterHit ( firstRun , FirstTextSourceIndex , 0 ) ;
if ( _ paragraphProperties . FlowDirection = = FlowDirection . RightToLeft )
{
currentPosition = Length - firstRun . Length ;
}
return GetRunCharacterHit ( firstRun , currentPosition , 0 ) ;
}
if ( distance > = WidthIncludingTrailingWhitespace )
{
var lastRun = _ textRuns [ _ textRuns . Length - 1 ] ;
var size = 0.0 ;
var lastRun = _ textRuns [ lastIndex ] ;
if ( lastRun is DrawableTextRun drawableTextRun )
if ( _ paragraphProperties . FlowDirection = = FlowDirection . LeftToRight )
{
size = drawableTextRun . Size . Wid th;
currentPosition = Length - lastRun . Leng th;
}
return GetRunCharacterHit ( lastRun , FirstTextSourceIndex + Length - lastRun . Length , siz e) ;
return GetRunCharacterHit ( lastRun , currentPosition , distanc e) ;
}
// process hit that happens within the line
var characterHit = new CharacterHit ( ) ;
var currentPosition = FirstTextSourceIndex ;
var currentDistance = 0.0 ;
for ( var i = 0 ; i < _ textRuns . Length ; i + + )
for ( var i = 0 ; i < = lastIndex ; i + + )
{
var currentRun = _ textRuns [ i ] ;
@ -242,7 +247,7 @@ namespace Avalonia.Media.TextFormatting
currentRun = _ textRuns [ j ] ;
if ( currentRun is not ShapedTextRun )
if ( currentRun is not ShapedTextRun )
{
continue ;
}
@ -274,10 +279,6 @@ namespace Avalonia.Media.TextFormatting
continue ;
}
}
else
{
continue ;
}
break ;
}
@ -422,10 +423,10 @@ namespace Avalonia.Media.TextFormatting
{
if ( currentGlyphRun ! = null )
{
distance = currentGlyphRun . Size . Width - distance ;
currentDistance - = currentGlyphRun . Size . Width ;
}
return Math . Max ( 0 , currentDistance - distance ) ;
return currentDistance + distance ;
}
if ( currentRun is DrawableTextRun drawableTextRun )
@ -575,386 +576,505 @@ namespace Avalonia.Media.TextFormatting
return GetPreviousCaretCharacterHit ( characterHit ) ;
}
privat e IReadOnlyList < TextBounds > GetTextBoundsLeftToRight ( int firstTextSourceIndex , int textLength )
public overrid e IReadOnlyList < TextBounds > GetTextBounds ( int firstTextSourceIndex , int textLength )
{
var characterIndex = firstTextSourceIndex + textLength ;
if ( _ textRuns . Length = = 0 )
{
return Array . Empty < TextBounds > ( ) ;
}
var result = new List < TextBounds > ( _ textRuns . Length ) ;
var lastDirection = FlowDirection . LeftToRight ;
var currentDirection = lastDirection ;
var result = new List < TextBounds > ( ) ;
var currentPosition = FirstTextSourceIndex ;
var remainingLength = textLength ;
var startX = Start ;
double currentWidth = 0 ;
var currentRect = default ( Rect ) ;
TextRunBounds lastRunBounds = default ;
for ( var index = 0 ; index < _ textRuns . Length ; index + + )
static FlowDirection GetDirection ( TextRun textRun , FlowDirection currentDirection )
{
if ( _ textRuns [ index ] is not DrawableTextRun curren tRun)
if ( textRun is ShapedTextRun shapedTextRun )
{
continue ;
return shapedTextRun . ShapedBuffer . IsLeftToRight ?
FlowDirection . LeftToRight :
FlowDirection . RightToLeft ;
}
var characterLength = 0 ;
var endX = startX ;
TextRunBounds currentRunBounds ;
return currentDirection ;
}
double combinedWidth ;
if ( _ paragraphProperties . FlowDirection = = FlowDirection . LeftToRight )
{
var currentX = Start ;
if ( currentRun is ShapedTextRun currentShapedRun )
for ( int i = 0 ; i < _ textRuns . Length ; i + + )
{
var firstCluster = currentShapedRun . GlyphRun . Metrics . FirstCluster ;
var currentRun = _ textRuns [ i ] ;
var firstRunIndex = i ;
var lastRunIndex = firstRunIndex ;
var currentDirection = GetDirection ( currentRun , FlowDirection . LeftToRight ) ;
var directionalWidth = 0.0 ;
if ( currentPosition + currentRun . Length < = firstTextSourceIndex )
if ( currentRun is DrawableTextRun currentDrawable )
{
startX + = currentRun . Size . Width ;
directionalWidth = currentDrawable . Size . Width ;
}
currentPosition + = currentRun . Length ;
// Find consecutive runs of same direction
for ( ; lastRunIndex + 1 < _ textRuns . Length ; lastRunIndex + + )
{
var nextRun = _ textRuns [ lastRunIndex + 1 ] ;
continue ;
var nextDirection = GetDirection ( nextRun , currentDirection ) ;
if ( currentDirection ! = nextDirection )
{
break ;
}
if ( nextRun is DrawableTextRun nextDrawable )
{
directionalWidth + = nextDrawable . Size . Width ;
}
}
if ( currentShapedRun . ShapedBuffer . IsLeftToRight )
//Skip runs that are not part of the hit test range
switch ( currentDirection )
{
var startIndex = firstCluster + Math . Max ( 0 , firstTextSourceIndex - currentPosition ) ;
case FlowDirection . RightToLeft :
{
for ( ; lastRunIndex > = firstRunIndex ; lastRunIndex - - )
{
currentRun = _ textRuns [ lastRunIndex ] ;
if ( currentPosition + currentRun . Length > firstTextSourceIndex )
{
break ;
}
currentPosition + = currentRun . Length ;
double startOffset ;
if ( currentRun is DrawableTextRun drawableTextRun )
{
directionalWidth - = drawableTextRun . Size . Width ;
currentX + = drawableTextRun . Size . Width ;
}
double endOffset ;
if ( lastRunIndex - 1 < 0 )
{
break ;
}
}
startOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex ) ) ;
break ;
}
default :
{
for ( ; firstRunIndex < = lastRunIndex ; firstRunIndex + + )
{
currentRun = _ textRuns [ firstRunIndex ] ;
endOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex + remainingLength ) ) ;
if ( currentPosition + currentRun . Length > firstTextSourceIndex )
{
break ;
}
startX + = startOffset ;
currentPosition + = currentRun . Length ;
endX + = endOffset ;
if ( currentRun is DrawableTextRun drawableTextRun )
{
currentX + = drawableTextRun . Size . Width ;
directionalWidth - = drawableTextRun . Size . Width ;
}
var endHit = currentShapedRun . GlyphRun . GetCharacterHitFromDistance ( endOffset , out _ ) ;
if ( firstRunIndex + 1 = = _ textRuns . Length )
{
break ;
}
}
var startHit = currentShapedRun . GlyphRun . GetCharacterHitFromDistance ( startOffset , out _ ) ;
break ;
}
}
characterLength = Math . Abs ( endHit . FirstCharacterIndex + endHit . TrailingLength - startHit . FirstCharacterIndex - startHit . TrailingLength ) ;
i = lastRunIndex ;
currentDirection = FlowDirection . LeftToRight ;
if ( directionalWidth = = 0 )
{
continue ;
}
else
var coveredLength = 0 ;
TextBounds ? textBounds = null ;
switch ( currentDirection )
{
var rightToLeftIndex = index ;
var rightToLeftWidth = currentShapedRun . Size . Width ;
while ( rightToLeftIndex + 1 < = _ textRuns . Length - 1 & & _ textRuns [ rightToLeftIndex + 1 ] is ShapedTextRun nextShapedRun )
{
if ( nextShapedRun = = null | | nextShapedRun . ShapedBuffer . IsLeftToRight )
case FlowDirection . RightToLeft :
{
textBounds = GetTextRunBoundsRightToLeft ( firstRunIndex , lastRunIndex , currentX + directionalWidth , firstTextSourceIndex ,
currentPosition , remainingLength , out coveredLength , out currentPosition ) ;
currentX + = directionalWidth ;
break ;
}
default :
{
textBounds = GetTextBoundsLeftToRight ( firstRunIndex , lastRunIndex , currentX , firstTextSourceIndex ,
currentPosition , remainingLength , out coveredLength , out currentPosition ) ;
rightToLeftIndex + + ;
rightToLeftWidth + = nextShapedRun . Size . Width ;
currentX = textBounds . Rectangle . Right ;
if ( currentPosition + nextShapedRun . Length > firstTextSourceIndex + textLength )
{
break ;
}
}
currentShapedRun = nextShapedRun ;
}
if ( coveredLength > 0 )
{
result . Add ( textBounds ) ;
remainingLength - = coveredLength ;
}
if ( remainingLength < = 0 )
{
break ;
}
}
}
else
{
var currentX = Start + WidthIncludingTrailingWhitespace ;
startX + = rightToLeftWidth ;
for ( int i = _ textRuns . Length - 1 ; i > = 0 ; i - - )
{
var currentRun = _ textRuns [ i ] ;
var firstRunIndex = i ;
var lastRunIndex = firstRunIndex ;
var currentDirection = GetDirection ( currentRun , FlowDirection . RightToLeft ) ;
var directionalWidth = 0.0 ;
currentRunBounds = GetRightToLeftTextRunBounds ( currentShapedRun , startX , firstTextSourceIndex , characterIndex , currentPosition , remainingLength ) ;
if ( currentRun is DrawableTextRun currentDrawable )
{
directionalWidth = currentDrawable . Size . Width ;
}
remainingLength - = currentRunBounds . Length ;
currentPosition = currentRunBounds . TextSourceCharacterIndex + currentRunBounds . Length ;
endX = currentRunBounds . Rectangle . Right ;
startX = currentRunBounds . Rectangle . Left ;
// Find consecutive runs of same direction
for ( ; firstRunIndex - 1 > 0 ; firstRunIndex - - )
{
var previousRun = _ textRuns [ firstRunIndex - 1 ] ;
var rightToLeftRunBounds = new List < TextRunBounds > { currentRunBounds } ;
var previousDirection = GetDirection ( previousRun , currentDirection ) ;
for ( int i = rightToLeftIndex - 1 ; i > = index ; i - - )
if ( currentDirection ! = previousDirection )
{
if ( _ textRuns [ i ] is not ShapedTextRun shapedRun )
break ;
}
if ( currentRun is DrawableTextRun previousDrawable )
{
directionalWidth + = previousDrawable . Size . Width ;
}
}
//Skip runs that are not part of the hit test range
switch ( currentDirection )
{
case FlowDirection . RightToLeft :
{
continue ;
}
for ( ; lastRunIndex > = firstRunIndex ; lastRunIndex - - )
{
currentRun = _ textRuns [ lastRunIndex ] ;
currentShapedRun = shapedRun ;
if ( currentPosition + currentRun . Length < = firstTextSourceIndex )
{
currentPosition + = currentRun . Length ;
currentRunBounds = GetRightToLeftTextRunBounds ( currentShapedRun , startX , firstTextSourceIndex , characterIndex , currentPosition , remainingLength ) ;
if ( currentRun is DrawableTextRun drawableTextRun )
{
currentX - = drawableTextRun . Size . Width ;
directionalWidth - = drawableTextRun . Size . Width ;
}
rightToLeftRunBounds . Insert ( 0 , currentRunBounds ) ;
continue ;
}
remainingLength - = currentRunBounds . Length ;
startX = currentRunBounds . Rectangle . Left ;
break ;
}
currentPosition + = currentRunBounds . Length ;
}
break ;
}
default :
{
for ( ; firstRunIndex < = lastRunIndex ; firstRunIndex + + )
{
currentRun = _ textRuns [ firstRunIndex ] ;
combinedWidth = endX - startX ;
if ( currentPosition + currentRun . Length < = firstTextSourceIndex )
{
currentPosition + = currentRun . Length ;
currentRect = new Rect ( startX , 0 , combinedWidth , Height ) ;
if ( currentRun is DrawableTextRun drawableTextRun )
{
currentX + = drawableTextRun . Size . Width ;
directionalWidth - = drawableTextRun . Size . Width ;
}
currentDirection = FlowDirection . RightToLeft ;
continue ;
}
if ( ! MathUtilities . IsZero ( combinedWidth ) )
{
result . Add ( new TextBounds ( currentRect , currentDirection , rightToLeftRunBounds ) ) ;
}
break ;
}
startX = endX ;
break ;
}
}
}
else
{
if ( currentPosition + currentRun . Length < = firstTextSourceIndex )
{
startX + = currentRun . Size . Width ;
currentPosition + = currentRun . Length ;
i = firstRunIndex ;
if ( directionalWidth = = 0 )
{
continue ;
}
if ( currentPosition < firstTextSourceIndex )
{
startX + = currentRun . Size . Width ;
}
var coveredLength = 0 ;
if ( currentPosition + currentRun . Length < = characterIndex )
TextBounds ? textBounds = null ;
switch ( currentDirection )
{
endX + = currentRun . Size . Width ;
case FlowDirection . LeftToRight :
{
textBounds = GetTextBoundsLeftToRight ( firstRunIndex , lastRunIndex , currentX - directionalWidth , firstTextSourceIndex ,
currentPosition , remainingLength , out coveredLength , out currentPosition ) ;
currentX - = directionalWidth ;
characterLength = currentRun . Length ;
break ;
}
default :
{
textBounds = GetTextRunBoundsRightToLeft ( firstRunIndex , lastRunIndex , currentX , firstTextSourceIndex ,
currentPosition , remainingLength , out coveredLength , out currentPosition ) ;
currentX = textBounds . Rectangle . Left ;
break ;
}
}
}
if ( endX < startX )
{
( endX , startX ) = ( startX , endX ) ;
}
//Visual order is always left to right so we need to insert
result . Insert ( 0 , textBounds ) ;
//Lines that only contain a linebreak need to be covered here
if ( characterLength = = 0 )
{
characterLength = NewLineLength ;
remainingLength - = coveredLength ;
if ( remainingLength < = 0 )
{
break ;
}
}
}
combinedWidth = endX - startX ;
return result ;
}
currentRunBounds = new TextRunBounds ( new Rect ( startX , 0 , combinedWidth , Height ) , currentPosition , characterLength , currentRun ) ;
private TextBounds GetTextRunBoundsRightToLeft ( int firstRunIndex , int lastRunIndex , double endX ,
int firstTextSourceIndex , int currentPosition , int remainingLength , out int coveredLength , out int newPosition )
{
coveredLength = 0 ;
var textRunBounds = new List < TextRunBounds > ( ) ;
var startX = endX ;
currentPosition + = characterLength ;
for ( int i = lastRunIndex ; i > = firstRunIndex ; i - - )
{
var currentRun = _ textRuns [ i ] ;
remainingLength - = characterLength ;
if ( currentRun is ShapedTextRun shapedTextRun )
{
var runBounds = GetRunBoundsRightToLeft ( shapedTextRun , startX , firstTextSourceIndex , remainingLength , currentPosition , out var offset ) ;
startX = endX ;
textRunBounds . Insert ( 0 , runBounds ) ;
if ( currentRunBounds . TextRun ! = null & & ! MathUtilities . IsZero ( combinedWidth ) | | NewLineLength > 0 )
{
if ( result . Count > 0 & & lastDirection = = currentDirection & & MathUtilities . AreClose ( currentRect . Left , lastRunBounds . Rectangle . Right ) )
if ( offset > 0 )
{
currentRect = currentRect . WithWidth ( currentWidth + combinedWidth ) ;
endX = runBounds . Rectangle . Right ;
var textBounds = result [ result . Count - 1 ] ;
startX = endX ;
}
textBounds . Rectangle = currentRect ;
startX - = runBounds . Rectangle . Width ;
textBounds . TextRunBounds . Add ( currentRunBounds ) ;
}
else
currentPosition + = runBounds . Length + offset ;
coveredLength + = runBounds . Length ;
remainingLength - = runBounds . Length ;
}
else
{
if ( currentRun is DrawableTextRun drawableTextRun )
{
currentRect = currentRunBounds . Rectangle ;
startX - = drawableTextRun . Size . Width ;
result . Add ( new TextBounds ( currentRect , currentDirection , new List < TextRunBounds > { currentRunBounds } ) ) ;
textRunBounds . Insert ( 0 ,
new TextRunBounds (
new Rect ( startX , 0 , drawableTextRun . Size . Width , Height ) , currentPosition , currentRun . Length , currentRun ) ) ;
}
}
lastRunBounds = currentRunBounds ;
currentPosition + = currentRun . Length ;
coveredLength + = currentRun . Length ;
currentWidth + = combinedWidth ;
remainingLength - = currentRun . Length ;
}
if ( remainingLength < = 0 | | currentPosition > = characterIndex )
if ( remainingLength < = 0 )
{
break ;
}
lastDirection = currentDirection ;
}
return result ;
}
newPosition = currentPosition ;
private IReadOnlyList < TextBounds > GetTextBoundsRightToLeft ( int firstTextSourceIndex , int textLength )
{
var characterIndex = firstTextSourceIndex + textLength ;
var runWidth = endX - startX ;
var result = new List < TextBounds > ( _ textRuns . Length ) ;
var lastDirection = FlowDirection . LeftToRight ;
var currentDirection = lastDirection ;
var bounds = new Rect ( startX , 0 , runWidth , Height ) ;
var currentPosition = FirstTextSourceIndex ;
var remainingLength = textLength ;
return new TextBounds ( bounds , FlowDirection . RightToLeft , textRunBounds ) ;
}
var startX = WidthIncludingTrailingWhitespace ;
double currentWidth = 0 ;
var currentRect = default ( Rect ) ;
private TextBounds GetTextBoundsLeftToRight ( int firstRunIndex , int lastRunIndex , double startX ,
int firstTextSourceIndex , int currentPosition , int remainingLength , out int coveredLength , out int newPosition )
{
coveredLength = 0 ;
var textRunBounds = new List < TextRunBounds > ( ) ;
var endX = startX ;
for ( var index = _ textRuns . Length - 1 ; index > = 0 ; index - - )
for ( int i = firstRunIndex ; i < = lastRunIndex ; i + + )
{
if ( _ textRuns [ index ] is not DrawableTextRun currentRun )
{
continue ;
}
if ( currentPosition + currentRun . Length < firstTextSourceIndex )
{
startX - = currentRun . Size . Width ;
currentPosition + = currentRun . Length ;
continue ;
}
var characterLength = 0 ;
var endX = startX ;
var currentRun = _ textRuns [ i ] ;
if ( currentRun is ShapedTextRun currentShaped Run)
if ( currentRun is ShapedTextRun shapedTextRun )
{
var offset = Math . Max ( 0 , firstTextSourceIndex - currentPosition ) ;
currentPosition + = offset ;
var startIndex = currentPosition ;
double startOffset ;
double endOffset ;
var runBounds = GetRunBoundsLeftToRight ( shapedTextRun , endX , firstTextSourceIndex , remainingLength , currentPosition , out var offset ) ;
if ( currentShapedRun . ShapedBuffer . IsLeftToRight )
{
if ( currentPosition < startIndex )
{
startOffset = endOffset = 0 ;
}
else
{
endOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex + remainingLength ) ) ;
textRunBounds . Add ( runBounds ) ;
startOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex ) ) ;
}
}
else
if ( offset > 0 )
{
endOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex ) ) ;
startX = runBounds . Rectangle . Left ;
startOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex + remainingLength ) ) ;
endX = startX ;
}
startX - = currentRun . Size . Width - startOffset ;
endX - = currentRun . Size . Width - endOffset ;
currentPosition + = runBounds . Length + offset ;
var endHit = currentShapedRun . GlyphRun . GetCharacterHitFromDistance ( endOffset , out _ ) ;
var startHit = currentShapedRun . GlyphRun . GetCharacterHitFromDistance ( startOffset , out _ ) ;
endX + = runBounds . Rectangle . Width ;
characterLength = Math . Abs ( startHit . FirstCharacterIndex + startHit . TrailingLength - endHit . FirstCharacterIndex - endHit . TrailingLength ) ;
coveredLength + = runBounds . Length ;
currentDirection = currentShapedRun . ShapedBuffer . IsLeftToRight ?
FlowDirection . LeftToRight :
FlowDirection . RightToLeft ;
remainingLength - = runBounds . Length ;
}
else
{
if ( currentPosition + currentRun . Length < = characterIndex )
if ( currentRun is DrawableTextRun drawableTextRun )
{
endX - = currentRun . Size . Width ;
textRunBounds . Add (
new TextRunBounds (
new Rect ( endX , 0 , drawableTextRun . Size . Width , Height ) , currentPosition , currentRun . Length , currentRun ) ) ;
endX + = drawableTextRun . Size . Width ;
}
if ( currentPosition < firstTextSourceIndex )
{
startX - = currentRun . Size . Width ;
currentPosition + = currentRun . Length ;
characterLength = currentRun . Length ;
}
}
coveredLength + = currentRun . Length ;
if ( endX < startX )
{
( endX , startX ) = ( startX , endX ) ;
remainingLength - = currentRun . Length ;
}
//Lines that only contain a linebreak need to be covered here
if ( characterLength = = 0 )
if ( remainingLength < = 0 )
{
characterLength = NewLineLength ;
break ;
}
}
var runWidth = endX - startX ;
newPosition = currentPosition ;
var currentRunBounds = new TextRunBounds ( new Rect ( Start + startX , 0 , runWidth , Height ) , currentPosition , characterLength , currentRun ) ;
var runWidth = endX - startX ;
if ( ! MathUtilities . IsZero ( runWidth ) | | NewLineLength > 0 )
{
if ( lastDirection = = currentDirection & & result . Count > 0 & & MathUtilities . AreClose ( currentRect . Right , Start + startX ) )
{
currentRect = currentRect . WithWidth ( currentWidth + runWidth ) ;
var bounds = new Rect ( startX , 0 , runWidth , Height ) ;
var textBounds = result [ result . Count - 1 ] ;
return new TextBounds ( bounds , FlowDirection . LeftToRight , textRunBounds ) ;
}
textBounds . Rectangle = currentRect ;
private TextRunBounds GetRunBoundsLeftToRight ( ShapedTextRun currentRun , double startX ,
int firstTextSourceIndex , int remainingLength , int currentPosition , out int offset )
{
var startIndex = currentPosition ;
textBounds . TextRunBounds . Add ( currentRunBounds ) ;
}
else
{
currentRect = currentRunBounds . Rectangle ;
offset = Math . Max ( 0 , firstTextSourceIndex - currentPosition ) ;
result . Add ( new TextBounds ( currentRect , currentDirection , new List < TextRunBounds > { currentRunBounds } ) ) ;
}
}
var firstCluster = currentRun . GlyphRun . Metrics . FirstCluster ;
currentWidth + = runWidth ;
currentPosition + = characterLength ;
if ( currentPosition ! = firstCluster )
{
startIndex = firstCluster + offset ;
}
else
{
startIndex + = offset ;
}
if ( currentPosition > characterIndex )
{
break ;
}
var startOffset = currentRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex ) ) ;
var endOffset = currentRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex + remainingLength ) ) ;
lastDirection = currentDirection ;
remainingLength - = characterLength ;
var endX = startX + endOffset ;
startX + = startOffset ;
if ( remainingLength < = 0 )
{
break ;
}
var startHit = currentRun . GlyphRun . GetCharacterHitFromDistance ( startOffset , out _ ) ;
var endHit = currentRun . GlyphRun . GetCharacterHitFromDistance ( endOffset , out _ ) ;
var characterLength = Math . Abs ( startHit . FirstCharacterIndex + startHit . TrailingLength - endHit . FirstCharacterIndex - endHit . TrailingLength ) ;
if ( endX < startX )
{
( endX , startX ) = ( startX , endX ) ;
}
//Lines that only contain a linebreak need to be covered here
if ( characterLength = = 0 )
{
characterLength = NewLineLength ;
}
result . Reverse ( ) ;
var runWidth = endX - startX ;
return result ;
return new TextRunBounds ( new Rect ( startX , 0 , runWidth , H eight ) , c urrentPosition , charac terLength , currentRun ) ;
}
private TextRunBounds GetRightToLeftTextRunBounds ( ShapedTextRun currentRun , double endX , int firstTextSourceIndex , int characterIndex , int currentPosition , int remainingLength )
private TextRunBounds GetRunBoundsRightToLeft ( ShapedTextRun currentRun , double endX ,
int firstTextSourceIndex , int remainingLength , int currentPosition , out int offset )
{
var startX = endX ;
var offset = Math . Max ( 0 , firstTextSourceIndex - currentPosition ) ;
var startIndex = currentPosition ;
currentPosition + = offset ;
offset = Math . Max ( 0 , firstTextSourceIndex - currentPosition ) ;
var startIndex = currentPosition ;
var firstCluster = currentRun . GlyphRun . Metrics . FirstCluster ;
double startOffset ;
double endOffset ;
if ( currentPosition ! = firstCluster )
{
startIndex = firstCluster + offset ;
}
else
{
startIndex + = offset ;
}
endOffset = currentRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex ) ) ;
var endOffset = currentRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex ) ) ;
startOffset = currentRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex + remainingLength ) ) ;
var startOffset = currentRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex + remainingLength ) ) ;
startX - = currentRun . Size . Width - startOffset ;
endX - = currentRun . Size . Width - endOffset ;
@ -980,16 +1100,6 @@ namespace Avalonia.Media.TextFormatting
return new TextRunBounds ( new Rect ( Start + startX , 0 , runWidth , Height ) , currentPosition , characterLength , currentRun ) ;
}
public override IReadOnlyList < TextBounds > GetTextBounds ( int firstTextSourceIndex , int textLength )
{
if ( _ paragraphProperties . FlowDirection = = FlowDirection . LeftToRight )
{
return GetTextBoundsLeftToRight ( firstTextSourceIndex , textLength ) ;
}
return GetTextBoundsRightToLeft ( firstTextSourceIndex , textLength ) ;
}
public override void Dispose ( )
{
for ( int i = 0 ; i < _ textRuns . Length ; i + + )
@ -1005,6 +1115,11 @@ namespace Avalonia.Media.TextFormatting
{
_ textLineMetrics = CreateLineMetrics ( ) ;
if ( _ textLineBreak is null & & _ textRuns . Length > 1 & & _ textRuns [ _ textRuns . Length - 1 ] is TextEndOfLine textEndOfLine )
{
_ textLineBreak = new TextLineBreak ( textEndOfLine ) ;
}
BidiReorderer . Instance . BidiReorder ( _ textRuns , _ resolvedFlowDirection ) ;
}
@ -1328,7 +1443,7 @@ namespace Avalonia.Media.TextFormatting
{
width = widthIncludingWhitespace + textRun . GlyphRun . Metrics . Width ;
trailingWhitespaceLength = textRun . GlyphRun . Metrics . TrailingWhitespaceLength ;
newLineLength = textRun . GlyphRun . Metrics . NewLineLength ;
newLineLength + = textRun . GlyphRun . Metrics . NewLineLength ;
}
widthIncludingWhitespace + = textRun . Size . Width ;
@ -1340,31 +1455,10 @@ namespace Avalonia.Media.TextFormatting
{
widthIncludingWhitespace + = drawableTextRun . Size . Width ;
switch ( _ paragraphProperties . FlowDirection )
if ( index = = lastRunIndex )
{
case FlowDirection . LeftToRight :
{
if ( index = = lastRunIndex )
{
width = widthIncludingWhitespace ;
trailingWhitespaceLength = 0 ;
newLineLength = 0 ;
}
break ;
}
case FlowDirection . RightToLeft :
{
if ( index = = lastRunIndex )
{
width = widthIncludingWhitespace ;
trailingWhitespaceLength = 0 ;
newLineLength = 0 ;
}
break ;
}
width = widthIncludingWhitespace ;
trailingWhitespaceLength = 0 ;
}
if ( drawableTextRun . Size . Height > height )