Browse Source

Introduce CanShapeTogether and ShapeTogether

pull/7693/head
Benedikt Stebner 4 years ago
parent
commit
d8937f90bc
  1. 4
      src/Avalonia.Controls/TextBox.cs
  2. 36
      src/Avalonia.Visuals/Media/TextFormatting/ShapeableTextCharacters.cs
  3. 12
      src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
  4. 74
      src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
  5. 27
      src/Avalonia.Visuals/Utilities/ReadOnlySlice.cs
  6. 2
      src/Skia/Avalonia.Skia/TextShaperImpl.cs

4
src/Avalonia.Controls/TextBox.cs

@ -354,8 +354,12 @@ namespace Avalonia.Controls
if (!_ignoreTextChanges)
{
var caretIndex = CaretIndex;
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
CaretIndex = CoerceCaretIndex(caretIndex, value);
SelectionStart = CoerceCaretIndex(selectionStart, value);
SelectionEnd = CoerceCaretIndex(selectionEnd, value);
if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing)
{

36
src/Avalonia.Visuals/Media/TextFormatting/ShapeableTextCharacters.cs

@ -22,5 +22,41 @@ namespace Avalonia.Media.TextFormatting
public override TextRunProperties Properties { get; }
public sbyte BidiLevel { get; }
public bool CanShapeTogether(ShapeableTextCharacters shapeableTextCharacters)
{
if (!Text.Buffer.Equals(shapeableTextCharacters.Text.Buffer))
{
return false;
}
if (Text.Start + Text.Length != shapeableTextCharacters.Text.Start)
{
return false;
}
if (BidiLevel != shapeableTextCharacters.BidiLevel)
{
return false;
}
if (!MathUtilities.AreClose(Properties.FontRenderingEmSize,
shapeableTextCharacters.Properties.FontRenderingEmSize))
{
return false;
}
if (Properties.Typeface != shapeableTextCharacters.Properties.Typeface)
{
return false;
}
if (Properties.BaselineAlignment != shapeableTextCharacters.Properties.BaselineAlignment)
{
return false;
}
return true;
}
}
}

12
src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs

@ -176,6 +176,12 @@ namespace Avalonia.Media.TextFormatting
var currentScript = currentGrapheme.FirstCodepoint.Script;
//Stop at the first missing glyph
if (!currentGrapheme.FirstCodepoint.IsBreakChar && !font.TryGetGlyph(currentGrapheme.FirstCodepoint, out _))
{
break;
}
if (currentScript != script)
{
if (script is Script.Unknown || currentScript != Script.Common &&
@ -192,12 +198,6 @@ namespace Avalonia.Media.TextFormatting
}
}
//Stop at the first missing glyph
if (!currentGrapheme.FirstCodepoint.IsBreakChar && !font.TryGetGlyph(currentGrapheme.FirstCodepoint, out _))
{
break;
}
length += currentGrapheme.Text.Length;
}

74
src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utilities;
@ -171,25 +173,83 @@ namespace Avalonia.Media.TextFormatting
resolvedFlowDirection =
(resolvedEmbeddingLevel & 1) == 0 ? FlowDirection.LeftToRight : FlowDirection.RightToLeft;
foreach (var shapeableRuns in CoalesceLevels(textRuns, biDi.ResolvedLevels))
var shapeableRuns = new List<ShapeableTextCharacters>(textRuns.Count);
foreach (var coalescedRuns in CoalesceLevels(textRuns, biDi.ResolvedLevels))
{
shapeableRuns.AddRange(coalescedRuns);
}
for (var index = 0; index < shapeableRuns.Count; index++)
{
for (var index = 0; index < shapeableRuns.Count; index++)
var currentRun = shapeableRuns[index];
var groupedRuns = new List<ShapeableTextCharacters>(2) { currentRun };
var text = currentRun.Text;
var start = currentRun.Text.Start;
var length = currentRun.Text.Length;
var bufferOffset = currentRun.Text.BufferOffset;
while (index + 1 < shapeableRuns.Count)
{
var currentRun = shapeableRuns[index];
var nextRun = shapeableRuns[index + 1];
var shapedBuffer = TextShaper.Current.ShapeText(currentRun.Text, currentRun.Properties.Typeface.GlyphTypeface,
currentRun.Properties.FontRenderingEmSize, currentRun.Properties.CultureInfo, currentRun.BidiLevel);
if (currentRun.CanShapeTogether(nextRun))
{
groupedRuns.Add(nextRun);
var shapedCharacters = new ShapedTextCharacters(shapedBuffer, currentRun.Properties);
length += nextRun.Text.Length;
if (start > nextRun.Text.Start)
{
start = nextRun.Text.Start;
}
if (bufferOffset > nextRun.Text.BufferOffset)
{
bufferOffset = nextRun.Text.BufferOffset;
}
text = new ReadOnlySlice<char>(text.Buffer, start, length, bufferOffset);
index++;
shapedTextCharacters.Add(shapedCharacters);
currentRun = nextRun;
continue;
}
break;
}
shapedTextCharacters.AddRange(ShapeTogether(groupedRuns, text));
}
return shapedTextCharacters;
}
private static IReadOnlyList<ShapedTextCharacters> ShapeTogether(
IReadOnlyList<ShapeableTextCharacters> textRuns, ReadOnlySlice<char> text)
{
var shapedRuns = new List<ShapedTextCharacters>(textRuns.Count);
var firstRun = textRuns[0];
var shapedBuffer = TextShaper.Current.ShapeText(text, firstRun.Properties.Typeface.GlyphTypeface,
firstRun.Properties.FontRenderingEmSize, firstRun.Properties.CultureInfo, firstRun.BidiLevel);
for (var i = 0; i < textRuns.Count; i++)
{
var currentRun = textRuns[i];
var splitResult = shapedBuffer.Split(currentRun.Text.Length);
shapedRuns.Add(new ShapedTextCharacters(splitResult.First, currentRun.Properties));
shapedBuffer = splitResult.Second!;
}
return shapedRuns;
}
/// <summary>
/// Coalesces ranges of the same bidi level to form <see cref="ShapeableTextCharacters"/>
/// </summary>

27
src/Avalonia.Visuals/Utilities/ReadOnlySlice.cs

@ -13,7 +13,7 @@ namespace Avalonia.Utilities
[DebuggerTypeProxy(typeof(ReadOnlySlice<>.ReadOnlySliceDebugView))]
public readonly struct ReadOnlySlice<T> : IReadOnlyList<T> where T : struct
{
private readonly int _offset;
private readonly int _bufferOffset;
/// <summary>
/// Gets an empty <see cref="ReadOnlySlice{T}"/>
@ -24,7 +24,7 @@ namespace Avalonia.Utilities
public ReadOnlySlice(ReadOnlyMemory<T> buffer) : this(buffer, 0, buffer.Length) { }
public ReadOnlySlice(ReadOnlyMemory<T> buffer, int start, int length, int offset = 0)
public ReadOnlySlice(ReadOnlyMemory<T> buffer, int start, int length, int bufferOffset = 0)
{
#if DEBUG
if (start.CompareTo(0) < 0)
@ -41,7 +41,7 @@ namespace Avalonia.Utilities
_buffer = buffer;
Start = start;
Length = length;
_offset = offset;
_bufferOffset = bufferOffset;
}
/// <summary>
@ -74,12 +74,17 @@ namespace Avalonia.Utilities
public bool IsEmpty => Length == 0;
/// <summary>
/// The underlying span.
/// Get the underlying span.
/// </summary>
public ReadOnlySpan<T> Span => _buffer.Span.Slice(_offset, Length);
public ReadOnlySpan<T> Span => _buffer.Span.Slice(_bufferOffset, Length);
/// <summary>
/// The underlying buffer.
/// Get the buffer offset.
/// </summary>
public int BufferOffset => _bufferOffset;
/// <summary>
/// Get the underlying buffer.
/// </summary>
public ReadOnlyMemory<T> Buffer => _buffer;
@ -124,17 +129,17 @@ namespace Avalonia.Utilities
return Empty;
}
if (start < 0 || _offset + start > _buffer.Length - 1)
if (start < 0 || _bufferOffset + start > _buffer.Length - 1)
{
throw new ArgumentOutOfRangeException(nameof(start));
}
if (_offset + start + length > _buffer.Length)
if (_bufferOffset + start + length > _buffer.Length)
{
throw new ArgumentOutOfRangeException(nameof(length));
}
return new ReadOnlySlice<T>(_buffer, start, length, _offset);
return new ReadOnlySlice<T>(_buffer, start, length, _bufferOffset);
}
/// <summary>
@ -154,7 +159,7 @@ namespace Avalonia.Utilities
throw new ArgumentOutOfRangeException(nameof(length));
}
return new ReadOnlySlice<T>(_buffer, Start, length, _offset);
return new ReadOnlySlice<T>(_buffer, Start, length, _bufferOffset);
}
/// <summary>
@ -174,7 +179,7 @@ namespace Avalonia.Utilities
throw new ArgumentOutOfRangeException(nameof(length));
}
return new ReadOnlySlice<T>(_buffer, Start + length, Length - length, _offset + length);
return new ReadOnlySlice<T>(_buffer, Start + length, Length - length, _bufferOffset + length);
}
/// <summary>

2
src/Skia/Avalonia.Skia/TextShaperImpl.cs

@ -69,7 +69,7 @@ namespace Avalonia.Skia
return shapedBuffer;
}
}
private static void MergeBreakPair(Buffer buffer)
{
var length = buffer.Length;

Loading…
Cancel
Save