|
|
@@ -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 + distance;
|
|
|
+ continue;
|
|
|
}
|
|
|
+
|
|
|
+ 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 = currentGlyphRun.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 false;
|
|
|
}
|
|
|
|
|
|
/// <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 <= characterIndex)
|
|
|
{
|
|
|
- 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);
|
|
|
|