@ -119,7 +119,7 @@ namespace Avalonia.Media.TextFormatting
}
/// <inheritdoc/>
public override TextLine Collapse ( params TextCollapsingProperties [ ] collapsingPropertiesList )
public override TextLine Collapse ( params TextCollapsingProperties ? [ ] collapsingPropertiesList )
{
if ( collapsingPropertiesList . Length = = 0 )
{
@ -128,6 +128,11 @@ namespace Avalonia.Media.TextFormatting
var collapsingProperties = collapsingPropertiesList [ 0 ] ;
if ( collapsingProperties is null )
{
return this ;
}
var collapsedRuns = collapsingProperties . Collapse ( this ) ;
if ( collapsedRuns is null )
@ -166,58 +171,122 @@ namespace Avalonia.Media.TextFormatting
if ( distance < = 0 )
{
// hit happens before the line, return the first position
var firstRun = _ textRuns [ 0 ] ;
if ( firstRun is ShapedTextCharacters shapedTextCharacters )
{
return shapedTextCharacters . GlyphRun . GetCharacterHitFromDistance ( distance , out _ ) ;
}
return GetRunCharacterHit ( firstRun , FirstTextSourceIndex , 0 ) ;
}
return _ resolvedFlowDirection = = FlowDirection . LeftToRight ?
new CharacterHit ( FirstTextSourceIndex ) :
new CharacterHit ( FirstTextSourceIndex + Length ) ;
if ( distance > = WidthIncludingTrailingWhitespace )
{
var lastRun = _ textRuns [ _ textRuns . Count - 1 ] ;
return GetRunCharacterHit ( lastRun , FirstTextSourceIndex + Length - lastRun . TextSourceLength , lastRun . Size . Width ) ;
}
// process hit that happens within the line
var characterHit = new CharacterHit ( ) ;
var currentPosition = FirstTextSourceIndex ;
var currentDistance = 0.0 ;
foreach ( var currentRun in _ textRuns )
for ( var i = 0 ; i < _ textRuns . Count ; i + + )
{
switch ( currentRun )
var currentRun = _ textRuns [ i ] ;
if ( currentRun is ShapedTextCharacters shapedRun & & ! shapedRun . ShapedBuffer . IsLeftToRight )
{
case ShapedTextCharacters shapedRun :
var rightToLeftIndex = i ;
currentPosition + = currentRun . TextSourceLength ;
while ( rightToLeftIndex + 1 < = _ textRuns . Count - 1 )
{
var nextShaped = _ textRuns [ rightToLeftIndex + 1 ] as ShapedTextCharacters ;
if ( nextShaped = = null | | nextShaped . ShapedBuffer . IsLeftToRight )
{
characterHit = shapedRun . GlyphRun . GetCharacterHitFromDistance ( distance , out _ ) ;
break ;
}
var offset = Math . Max ( 0 , currentPosition - shapedRun . Text . Start ) ;
currentPosition + = nextShaped . TextSourceLength ;
characterHit = new CharacterHit ( characterHit . FirstCharacterIndex + offset , characterHit . TrailingLength ) ;
rightToLeftIndex + + ;
}
for ( var j = i ; i < = rightToLeftIndex ; j + + )
{
if ( j > _ textRuns . Count - 1 )
{
break ;
}
default :
currentRun = _ textRuns [ j ] ;
if ( currentDistance + currentRun . Size . Width < = distance )
{
if ( distance < currentRun . Size . Width / 2 )
{
characterHit = new CharacterHit ( currentPosition ) ;
}
else
{
characterHit = new CharacterHit ( currentPosition , currentRun . TextSourceLength ) ;
}
break ;
currentDistance + = currentRun . Size . Width ;
currentPosition - = currentRun . TextSourceLength ;
continue ;
}
characterHit = GetRunCharacterHit ( currentRun , currentPosition , distance - currentDistance ) ;
break ;
}
}
if ( distance < = currentRun . Size . Width )
if ( currentDistance + currentRun . Size . Width < distance )
{
break ;
currentDistance + = currentRun . Size . Width ;
currentPosition + = currentRun . TextSourceLength ;
continue ;
}
distance - = currentRun . Size . Width ;
currentPosition + = currentRun . TextSourceLength ;
characterHit = GetRunCharacterHit ( currentRun , currentPosition , distance - currentDistance ) ;
break ;
}
return characterHit ;
}
private static CharacterHit GetRunCharacterHit ( DrawableTextRun run , int currentPosition , double distance )
{
CharacterHit characterHit ;
switch ( run )
{
case ShapedTextCharacters shapedRun :
{
characterHit = shapedRun . GlyphRun . GetCharacterHitFromDistance ( distance , out _ ) ;
var offset = 0 ;
if ( shapedRun . GlyphRun . IsLeftToRight )
{
offset = Math . Max ( 0 , currentPosition - shapedRun . Text . Start ) ;
}
//else
//{
// offset = Math.Max(0, currentPosition - shapedRun.Text.Start + shapedRun.Text.Length);
//}
characterHit = new CharacterHit ( characterHit . FirstCharacterIndex + offset , characterHit . TrailingLength ) ;
break ;
}
default :
{
if ( distance < run . Size . Width / 2 )
{
characterHit = new CharacterHit ( currentPosition ) ;
}
else
{
characterHit = new CharacterHit ( currentPosition , run . TextSourceLength ) ;
}
break ;
}
}
return characterHit ;
@ -226,136 +295,168 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/>
public override double GetDistanceFromCharacterHit ( CharacterHit characterHit )
{
var isTrailingHit = characterHit . TrailingLength > 0 ;
var flowDirection = _ paragraphProperties . FlowDirection ;
var characterIndex = characterHit . FirstCharacterIndex + characterHit . TrailingLength ;
var currentDistance = Start ;
var currentPosition = FirstTextSourceIndex ;
var remainingLength = characterIndex - FirstTextSourceIndex ;
GlyphRun ? lastRun = null ;
var currentDistance = Start ;
for ( var index = 0 ; index < _ textRuns . Count ; index + + )
if ( flowDirection = = FlowDirection . LeftToRight )
{
var textRun = _ textRuns [ index ] ;
switch ( textRun )
for ( var index = 0 ; index < _ textRuns . Count ; index + + )
{
case ShapedTextCharacters shapedTextCharacters :
{
var currentRun = shapedTextCharacters . GlyphRun ;
var currentRun = _ textRuns [ index ] ;
if ( lastRun ! = null )
{
if ( ! lastRun . IsLeftToRight & & currentRun . IsLeftToRight & &
currentRun . Characters . Start = = characterHit . FirstCharacterIndex & &
characterHit . TrailingLength = = 0 )
{
return currentDistance ;
}
}
if ( currentRun is ShapedTextCharacters shapedRun & & ! shapedRun . ShapedBuffer . IsLeftToRight )
{
var i = index ;
var rightToLeftWidth = currentRun . Size . Width ;
//Look for a hit in within the current run
if ( currentPosition + remainingLength < = currentPosition + textRun . Text . Length )
while ( i + 1 < = _ textRuns . Count - 1 )
{
var nextRun = _ textRuns [ i + 1 ] ;
if ( nextRun is ShapedTextCharacters nextShapedRun & & ! nextShapedRun . ShapedBuffer . IsLeftToRight )
{
characterHit = new CharacterHit ( textRun . Text . Start + remainingLength ) ;
i + + ;
var distance = currentRun . GetDistanceFromCharacterHit ( characterHit ) ;
rightToLeftWidth + = nextRun . Size . Width ;
return currentDistance + distanc e;
continu e;
}
break ;
}
//Look at the left and right edge of the current run
if ( currentRun . IsLeftToRight )
if ( i > index )
{
while ( i > = index )
{
if ( _ resolvedFlowDirection = = FlowDirection . LeftToRight & & ( lastRun = = null | | lastRun . IsLeftToRight ) )
{
if ( characterIndex < = currentPosition )
{
return currentDistance ;
}
}
else
{
if ( characterIndex = = currentPosition )
{
return currentDistance ;
}
}
currentRun = _ textRuns [ i ] ;
if ( characterIndex = = currentPosition + textRun . Text . Length & & isTrailingHit )
{
return currentDistance + currentRun . Size . Width ;
}
}
else
{
if ( characterIndex = = currentPosition )
rightToLeftWidth - = currentRun . Size . Width ;
if ( currentPosition + currentRun . TextSourceLength > = characterIndex )
{
return currentDistance + currentRun . Size . Width ;
break ;
}
var nextRun = index + 1 < _ textRuns . Count ?
_ textRuns [ index + 1 ] as ShapedTextCharacters :
null ;
currentPosition + = currentRun . TextSourceLength ;
if ( nextRun ! = null )
{
if ( nextRun . ShapedBuffer . IsLeftToRight )
{
if ( characterIndex = = currentPosition + textRun . Text . Length )
{
return currentDistance ;
}
}
else
{
if ( currentPosition + nextRun . Text . Length = = characterIndex )
{
return currentDistance ;
}
}
}
else
{
if ( characterIndex > currentPosition + textRun . Text . Length )
{
return currentDistance ;
}
}
remainingLength - = currentRun . TextSourceLength ;
i - - ;
}
lastRun = currentRun ;
currentDistance + = rightToLeftWidth ;
}
}
if ( currentPosition + currentRun . TextSourceLength > = characterIndex & &
TryGetDistanceFromCharacterHit ( currentRun , characterHit , currentPosition , remainingLength , flowDirection , out var distance , out _ ) )
{
return Math . Max ( 0 , currentDistance + distance ) ;
}
break ;
//No hit hit found so we add the full width
currentDistance + = currentRun . Size . Width ;
currentPosition + = currentRun . TextSourceLength ;
remainingLength - = currentRun . TextSourceLength ;
}
}
else
{
currentDistance + = WidthIncludingTrailingWhitespace ;
for ( var index = _ textRuns . Count - 1 ; index > = 0 ; index - - )
{
var currentRun = _ textRuns [ index ] ;
if ( TryGetDistanceFromCharacterHit ( currentRun , characterHit , currentPosition , remainingLength ,
flowDirection , out var distance , out var currentGlyphRun ) )
{
if ( currentGlyphRun ! = null )
{
distance = currentGlyphRun . Size . Width - distance ;
}
default :
return Math . Max ( 0 , currentDistance - distance ) ;
}
//No hit hit found so we add the full width
currentDistance - = currentRun . Size . Width ;
currentPosition + = currentRun . TextSourceLength ;
remainingLength - = currentRun . TextSourceLength ;
}
}
return Math . Max ( 0 , currentDistance ) ;
}
private static bool TryGetDistanceFromCharacterHit (
DrawableTextRun currentRun ,
CharacterHit characterHit ,
int currentPosition ,
int remainingLength ,
FlowDirection flowDirection ,
out double distance ,
out GlyphRun ? currentGlyphRun )
{
var characterIndex = characterHit . FirstCharacterIndex + characterHit . TrailingLength ;
var isTrailingHit = characterHit . TrailingLength > 0 ;
distance = 0 ;
currentGlyphRun = null ;
switch ( currentRun )
{
case ShapedTextCharacters shapedTextCharacters :
{
currentGlyphRun = shapedTextCharacters . GlyphRun ;
if ( currentPosition + remainingLength < = currentPosition + currentRun . Text . Length )
{
if ( characterIndex = = currentPosition )
{
return currentDistance ;
}
characterHit = new CharacterHit ( currentRun . Text . Start + remainingLength ) ;
distance = currentGlyphRun . GetDistanceFromCharacterHit ( characterHit ) ;
return true ;
}
if ( characterIndex = = currentPosition + textRun . TextSourceLength )
if ( currentPosition + remainingLength = = currentPosition + currentRun . Text . Length & & isTrailingHit )
{
if ( currentGlyphRun . IsLeftToRight | | flowDirection = = FlowDirection . RightToLeft )
{
return currentDistance + textRun . Size . Width ;
distance = currentGlyph Run. Size . Width ;
}
break ;
return true ;
}
}
//No hit hit found so we add the full width
currentDistance + = textRun . Size . Width ;
currentPosition + = textRun . TextSourceLength ;
remainingLength - = textRun . TextSourceLength ;
break ;
}
default :
{
if ( characterIndex = = currentPosition )
{
return true ;
}
if ( remainingLength < = 0 )
{
break ;
}
if ( characterIndex = = currentPosition + currentRun . TextSourceLength )
{
distance = currentRun . Size . Width ;
return true ;
}
break ;
}
}
return currentDistance ;
return fals e;
}
/// <inheritdoc/>
@ -440,121 +541,168 @@ namespace Avalonia.Media.TextFormatting
continue ;
}
if ( currentPosition + currentRun . TextSourceLength < = firstTextSourceIndex )
{
startX + = currentRun . Size . Width ;
currentPosition + = currentRun . TextSourceLength ;
continue ;
}
var characterLength = 0 ;
var endX = startX ;
var runWidth = 0.0 ;
TextRunBounds ? currentRunBounds = null ;
if ( currentRun is ShapedTextCharacters currentShapedRun )
{
var offset = Math . Max ( 0 , firstTextSourceIndex - currentPosition ) ;
currentPosition + = offset ;
var currentShapedRun = currentRun as ShapedTextCharacters ;
var startIndex = currentRun . Text . Start + offset ;
if ( currentShapedRun ! = null & & ! currentShapedRun . ShapedBuffer . IsLeftToRight )
{
var rightToLeftIndex = index ;
startX + = currentShapedRun . Size . Width ;
var endOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit (
currentShapedRun . ShapedBuffer . IsLeftToRight ?
new CharacterHit ( startIndex + remainingLength ) :
new CharacterHit ( startIndex ) ) ;
while ( rightToLeftIndex + 1 < = _ textRuns . Count - 1 )
{
var nextShapedRun = _ textRuns [ rightToLeftIndex + 1 ] as ShapedTextCharacters ;
endX + = endOffset ;
if ( nextShapedRun = = null | | nextShapedRun . ShapedBuffer . IsLeftToRight )
{
break ;
}
var startOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit (
currentShapedRun . ShapedBuffer . IsLeftToRight ?
new CharacterHit ( startIndex ) :
new CharacterHit ( startIndex + remainingLength ) ) ;
startX + = nextShapedRun . Size . Width ;
startX + = startOffset ;
rightToLeftIndex + + ;
}
var endHit = currentShapedRun . GlyphRun . GetCharacterHitFromDistance ( endOffset , out _ ) ;
var startHit = currentShapedRun . GlyphRun . GetCharacterHitFromDistance ( startOffset , out _ ) ;
if ( TryGetTextRunBoundsRightToLeft ( startX , firstTextSourceIndex , characterIndex , rightToLeftIndex , ref currentPosition , ref remainingLength , out currentRunBounds ) )
{
startX = currentRunBounds ! . Rectangle . Left ;
endX = currentRunBounds . Rectangle . Right ;
characterLength = Math . Abs ( endHit . FirstCharacterIndex + endHit . TrailingLength - startHit . FirstCharacterIndex - startHit . TrailingLength ) ;
runWidth = currentRunBounds . Rectangle . Width ;
}
currentDirection = currentShapedRun . ShapedBuffer . IsLeftToRight ?
FlowDirection . LeftToRight :
FlowDirection . RightToLeft ;
currentDirection = FlowDirection . RightToLeft ;
}
else
{
if ( currentPosition < firstTextSourceIndex )
if ( currentShapedRun ! = null )
{
startX + = currentRun . Size . Width ;
}
if ( currentPosition + currentRun . TextSourceLength < = firstTextSourceIndex )
{
startX + = currentRun . Size . Width ;
if ( currentPosition + currentRun . TextSourceLength < = characterIndex )
{
endX + = currentRun . Size . Width ;
currentPosition + = currentRun . TextSourceLength ;
characterLength = currentRun . TextSourceLength ;
continue ;
}
var offset = Math . Max ( 0 , firstTextSourceIndex - currentPosition ) ;
currentPosition + = offset ;
var startIndex = currentRun . Text . Start + offset ;
double startOffset ;
double endOffset ;
if ( currentShapedRun . ShapedBuffer . IsLeftToRight )
{
startOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex ) ) ;
endOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex + remainingLength ) ) ;
}
else
{
endOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex ) ) ;
if ( currentPosition < startIndex )
{
startOffset = endOffset ;
}
else
{
startOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex + remainingLength ) ) ;
}
}
startX + = startOffset ;
endX + = endOffset ;
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 ) ;
currentDirection = FlowDirection . LeftToRight ;
}
}
else
{
if ( currentPosition + currentRun . TextSourceLength < = firstTextSourceIndex )
{
startX + = currentRun . Size . Width ;
if ( endX < startX )
{
( endX , startX ) = ( startX , endX ) ;
}
currentPosition + = currentRun . TextSourceLength ;
//Lines that only contain a linebreak need to be covered here
if ( characterLength = = 0 )
{
characterLength = NewLineLength ;
}
continue ;
}
var runwidth = endX - startX ;
var currentRunBounds = new TextRunBounds ( new Rect ( startX , 0 , runwidth , Height ) , currentPosition , characterLength , currentRun ) ;
if ( currentPosition < firstTextSourceIndex )
{
startX + = currentRun . Size . Width ;
}
if ( lastDirection = = currentDirection & & result . Count > 0 & & MathUtilities . AreClose ( currentRect . Right , startX ) )
{
currentRect = currentRect . WithWidth ( currentWidth + runwidth ) ;
if ( currentPosition + currentRun . TextSourceLength < = characterIndex )
{
endX + = currentRun . Size . Width ;
var textBounds = result [ result . Count - 1 ] ;
characterLength = currentRun . TextSourceLength ;
}
}
textBounds . Rectangle = currentRect ;
if ( endX < startX )
{
( endX , startX ) = ( startX , endX ) ;
}
textBounds . TextRunBounds . Add ( currentRunBounds ) ;
}
else
{
currentRect = currentRunBounds . Rectangle ;
//Lines that only contain a linebreak need to be covered here
if ( characterLength = = 0 )
{
characterLength = NewLineLength ;
}
result . Add ( new TextBounds ( currentRect , currentDirection , new List < TextRunBounds > { currentRunBounds } ) ) ;
}
runWidth = endX - startX ;
currentRunBounds = new TextRunBounds ( new Rect ( startX , 0 , runWidth , Height ) , currentPosition , characterLength , currentRun ) ;
currentWidth + = runwidth ;
currentPosition + = characterLength ;
currentPosition + = characterLength ;
remainingLength - = characterLength ;
}
if ( currentDirection = = FlowDirection . LeftToRight )
if ( currentRunBounds ! = null & & ! MathUtilities . IsZero ( runWidth ) | | NewLineLength > 0 )
{
if ( currentPosition > characterIndex )
if ( lastDirection = = currentDirection & & result . Count > 0 & & MathUtilities . AreClose ( currentRect . Right , startX ) )
{
break ;
currentRect = currentRect . WithWidth ( currentWidth + runWidth ) ;
var textBounds = result [ result . Count - 1 ] ;
textBounds . Rectangle = currentRect ;
textBounds . TextRunBounds . Add ( currentRunBounds ! ) ;
}
}
else
{
if ( currentPosition < = firstTextSourceIndex )
else
{
break ;
currentRect = currentRunBounds ! . Rectangle ;
result . Add ( new TextBounds ( currentRect , currentDirection , new List < TextRunBounds > { currentRunBounds } ) ) ;
}
}
startX = endX ;
lastDirection = currentDirection ;
remainingLength - = characterLength ;
currentWidth + = runWidth ;
if ( remainingLength < = 0 )
if ( remainingLength < = 0 | | currentPosition > = characterIndex )
{
break ;
}
startX = endX ;
lastDirection = currentDirection ;
}
return result ;
@ -571,7 +719,7 @@ namespace Avalonia.Media.TextFormatting
var currentPosition = FirstTextSourceIndex ;
var remainingLength = textLength ;
var startX = Start + WidthIncludingTrailingWhitespace ;
var startX = WidthIncludingTrailingWhitespace ;
double currentWidth = 0 ;
var currentRect = Rect . Empty ;
@ -582,7 +730,7 @@ namespace Avalonia.Media.TextFormatting
continue ;
}
if ( currentPosition + currentRun . TextSourceLength < = firstTextSourceIndex )
if ( currentPosition + currentRun . TextSourceLength < firstTextSourceIndex )
{
startX - = currentRun . Size . Width ;
@ -601,20 +749,31 @@ namespace Avalonia.Media.TextFormatting
currentPosition + = offset ;
var startIndex = currentRun . Text . Start + offset ;
double startOffset ;
double endOffset ;
var endOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit (
currentShapedRun . ShapedBuffer . IsLeftToRight ?
new CharacterHit ( startIndex + remainingLength ) :
new CharacterHit ( startIndex ) ) ;
if ( currentShapedRun . ShapedBuffer . IsLeftToRight )
{
if ( currentPosition < startIndex )
{
startOffset = endOffset = 0 ;
}
else
{
endOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex + remainingLength ) ) ;
endX + = endOffset - currentShapedRun . Size . Width ;
startOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex ) ) ;
}
}
else
{
endOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex ) ) ;
var startOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit (
currentShapedRun . ShapedBuffer . IsLeftToRight ?
new CharacterHit ( startIndex ) :
new CharacterHit ( startIndex + remainingLength ) ) ;
startOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex + remainingLength ) ) ;
}
startX + = startOffset - currentShapedRun . Size . Width ;
startX - = currentRun . Size . Width - startOffset ;
endX - = currentRun . Size . Width - endOffset ;
var endHit = currentShapedRun . GlyphRun . GetCharacterHitFromDistance ( endOffset , out _ ) ;
var startHit = currentShapedRun . GlyphRun . GetCharacterHitFromDistance ( startOffset , out _ ) ;
@ -652,53 +811,150 @@ namespace Avalonia.Media.TextFormatting
}
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 ) )
var currentRunBounds = new TextRunBounds ( new Rect ( Start + startX , 0 , runWidth , Height ) , currentPosition , characterLength , currentRun ) ;
if ( ! MathUtilities . IsZero ( runWidth ) | | NewLineLength > 0 )
{
currentRect = currentRect . WithWidth ( currentWidth + runWidth ) ;
if ( lastDirection = = currentDirection & & result . Count > 0 & & MathUtilities . AreClose ( currentRect . Right , Start + startX ) )
{
currentRect = currentRect . WithWidth ( currentWidth + runWidth ) ;
var textBounds = result [ result . Count - 1 ] ;
var textBounds = result [ result . Count - 1 ] ;
textBounds . Rectangle = currentRect ;
textBounds . Rectangle = currentRect ;
textBounds . TextRunBounds . Add ( currentRunBounds ) ;
}
else
{
currentRect = currentRunBounds . Rectangle ;
textBounds . TextRunBounds . Add ( currentRunBounds ) ;
}
else
{
currentRect = currentRunBounds . Rectangle ;
result . Add ( new TextBounds ( currentRect , currentDirection , new List < TextRunBounds > { currentRunBounds } ) ) ;
result . Add ( new TextBounds ( currentRect , currentDirection , new List < TextRunBounds > { currentRunBounds } ) ) ;
}
}
currentWidth + = runWidth ;
currentPosition + = characterLength ;
if ( currentDirection = = FlowDirection . LeftToRight )
if ( currentPosition > characterIndex )
{
break ;
}
lastDirection = currentDirection ;
remainingLength - = characterLength ;
if ( remainingLength < = 0 )
{
break ;
}
}
result . Reverse ( ) ;
return result ;
}
private bool TryGetTextRunBoundsRightToLeft ( double startX , int firstTextSourceIndex , int characterIndex , int runIndex , ref int currentPosition , ref int remainingLength , out TextRunBounds ? textRunBounds )
{
textRunBounds = null ;
for ( var index = runIndex ; index > = 0 ; index - - )
{
if ( TextRuns [ index ] is not DrawableTextRun currentRun )
{
continue ;
}
if ( currentPosition + currentRun . TextSourceLength < = firstTextSourceIndex )
{
if ( currentPosition > characterIndex )
startX - = currentRun . Size . Width ;
currentPosition + = currentRun . TextSourceLength ;
continue ;
}
var characterLength = 0 ;
var endX = startX ;
if ( currentRun is ShapedTextCharacters currentShapedRun )
{
var offset = Math . Max ( 0 , firstTextSourceIndex - currentPosition ) ;
currentPosition + = offset ;
var startIndex = currentRun . Text . Start + offset ;
double startOffset ;
double endOffset ;
if ( currentShapedRun . ShapedBuffer . IsLeftToRight )
{
break ;
if ( currentPosition < startIndex )
{
startOffset = endOffset = 0 ;
}
else
{
endOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex + remainingLength ) ) ;
startOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex ) ) ;
}
}
else
{
endOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex ) ) ;
startOffset = currentShapedRun . GlyphRun . GetDistanceFromCharacterHit ( new CharacterHit ( startIndex + remainingLength ) ) ;
}
startX - = currentRun . Size . Width - startOffset ;
endX - = currentRun . Size . Width - endOffset ;
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 ) ;
}
else
{
if ( currentPosition < = firstTextSourceIndex )
if ( currentPosition + currentRun . TextSourceLength < = character Index)
{
break ;
endX - = currentRun . Size . Width ;
}
if ( currentPosition < firstTextSourceIndex )
{
startX - = currentRun . Size . Width ;
characterLength = currentRun . TextSourceLength ;
}
}
lastDirection = currentDirection ;
remainingLength - = characterLength ;
if ( endX < startX )
{
( endX , startX ) = ( startX , endX ) ;
}
if ( remainingLength < = 0 )
//Lines that only contain a linebreak need to be covered here
if ( characterLength = = 0 )
{
break ;
characterLength = NewLineLength ;
}
var runWidth = endX - startX ;
remainingLength - = characterLength ;
currentPosition + = characterLength ;
textRunBounds = new TextRunBounds ( new Rect ( Start + startX , 0 , runWidth , Height ) , currentPosition , characterLength , currentRun ) ;
return true ;
}
return result ;
return false ;
}
public override IReadOnlyList < TextBounds > GetTextBounds ( int firstTextSourceIndex , int textLength )
@ -1280,6 +1536,11 @@ namespace Avalonia.Media.TextFormatting
var textAlignment = _ paragraphProperties . TextAlignment ;
var paragraphFlowDirection = _ paragraphProperties . FlowDirection ;
if ( textAlignment = = TextAlignment . Justify )
{
textAlignment = TextAlignment . Start ;
}
switch ( textAlignment )
{
case TextAlignment . Start :
@ -1302,8 +1563,14 @@ namespace Avalonia.Media.TextFormatting
switch ( textAlignment )
{
case TextAlignment . Center :
return Math . Max ( 0 , ( _ paragraphWidth - width ) / 2 ) ;
var start = ( _ paragraphWidth - width ) / 2 ;
if ( paragraphFlowDirection = = FlowDirection . RightToLeft )
{
start - = ( widthIncludingTrailingWhitespace - width ) ;
}
return Math . Max ( 0 , start ) ;
case TextAlignment . Right :
return Math . Max ( 0 , _ paragraphWidth - widthIncludingTrailingWhitespace ) ;