@ -0,0 +1,32 @@ |
|||||
|
namespace Avalonia.Media |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Enum specifying where a box should be positioned Vertically
|
||||
|
/// </summary>
|
||||
|
public enum BaselineAlignment |
||||
|
{ |
||||
|
/// <summary>Align top toward top of container</summary>
|
||||
|
Top, |
||||
|
|
||||
|
/// <summary>Center vertically</summary>
|
||||
|
Center, |
||||
|
|
||||
|
/// <summary>Align bottom toward bottom of container</summary>
|
||||
|
Bottom, |
||||
|
|
||||
|
/// <summary>Align at baseline</summary>
|
||||
|
Baseline, |
||||
|
|
||||
|
/// <summary>Align toward text's top of container</summary>
|
||||
|
TextTop, |
||||
|
|
||||
|
/// <summary>Align toward text's bottom of container</summary>
|
||||
|
TextBottom, |
||||
|
|
||||
|
/// <summary>Align baseline to subscript position of container</summary>
|
||||
|
Subscript, |
||||
|
|
||||
|
/// <summary>Align baseline to superscript position of container</summary>
|
||||
|
Superscript, |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
namespace Avalonia.Media |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The 'flow-direction' property specifies whether the primary text advance
|
||||
|
/// direction shall be left-to-right or right-to-left.
|
||||
|
/// </summary>
|
||||
|
public enum FlowDirection |
||||
|
{ |
||||
|
/// <internalonly>
|
||||
|
/// Sets the primary text advance direction to left-to-right, and the line
|
||||
|
/// progression direction to top-to-bottom as is common in most Roman-based
|
||||
|
/// documents. For most characters, the current text position is advanced
|
||||
|
/// from left to right after each glyph is rendered. The 'direction' property
|
||||
|
/// is set to 'ltr'.
|
||||
|
/// </internalonly>
|
||||
|
LeftToRight, |
||||
|
|
||||
|
/// <internalonly>
|
||||
|
/// Sets the primary text advance direction to right-to-left, and the line
|
||||
|
/// progression direction to top-to-bottom as is common in Arabic or Hebrew
|
||||
|
/// scripts. The direction property is set to 'rtl'.
|
||||
|
/// </internalonly>
|
||||
|
RightToLeft |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
namespace Avalonia.Media |
||||
|
{ |
||||
|
public readonly struct GlyphRunMetrics |
||||
|
{ |
||||
|
public GlyphRunMetrics(double width, double widthIncludingTrailingWhitespace, int trailingWhitespaceLength, |
||||
|
int newlineLength, double height) |
||||
|
{ |
||||
|
Width = width; |
||||
|
WidthIncludingTrailingWhitespace = widthIncludingTrailingWhitespace; |
||||
|
TrailingWhitespaceLength = trailingWhitespaceLength; |
||||
|
NewlineLength = newlineLength; |
||||
|
Height = height; |
||||
|
} |
||||
|
|
||||
|
public double Width { get; } |
||||
|
|
||||
|
public double WidthIncludingTrailingWhitespace { get; } |
||||
|
|
||||
|
public int TrailingWhitespaceLength { get; } |
||||
|
|
||||
|
public int NewlineLength { get; } |
||||
|
|
||||
|
public double Height { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
namespace Avalonia.Media.TextFormatting |
||||
|
{ |
||||
|
public enum LogicalDirection |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Backward, or from right to left.
|
||||
|
/// </summary>
|
||||
|
Backward, |
||||
|
/// <summary>
|
||||
|
/// Forward, or from left to right.
|
||||
|
/// </summary>
|
||||
|
Forward |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Media.TextFormatting; |
||||
|
using Avalonia.UnitTests; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Skia.UnitTests.Media |
||||
|
{ |
||||
|
public class GlyphRunTests |
||||
|
{ |
||||
|
[InlineData("ABC \r", 29, 4, 1)] |
||||
|
[InlineData("ABC \r", 23, 3, 1)] |
||||
|
[InlineData("ABC \r", 17, 2, 1)] |
||||
|
[InlineData("ABC \r", 11, 1, 1)] |
||||
|
[InlineData("ABC \r", 7, 1, 0)] |
||||
|
[InlineData("ABC \r", 5, 0, 1)] |
||||
|
[InlineData("ABC \r", 2, 0, 0)] |
||||
|
[Theory] |
||||
|
public void Should_Get_Distance_From_CharacterHit(string text, double distance, int expectedIndex, |
||||
|
int expectedTrailingLength) |
||||
|
{ |
||||
|
using (Start()) |
||||
|
{ |
||||
|
var glyphRun = |
||||
|
TextShaper.Current.ShapeText(text.AsMemory(), Typeface.Default, 10, CultureInfo.CurrentCulture); |
||||
|
|
||||
|
var characterHit = glyphRun.GetCharacterHitFromDistance(distance, out _); |
||||
|
|
||||
|
Assert.Equal(expectedIndex, characterHit.FirstCharacterIndex); |
||||
|
|
||||
|
Assert.Equal(expectedTrailingLength, characterHit.TrailingLength); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static IDisposable Start() |
||||
|
{ |
||||
|
var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface |
||||
|
.With(renderInterface: new PlatformRenderInterface(null), |
||||
|
textShaperImpl: new TextShaperImpl(), |
||||
|
fontManagerImpl: new CustomFontManagerImpl())); |
||||
|
|
||||
|
return disposable; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,38 +0,0 @@ |
|||||
using System; |
|
||||
using Avalonia.Media.TextFormatting; |
|
||||
using Avalonia.Utilities; |
|
||||
|
|
||||
namespace Avalonia.Skia.UnitTests.Media.TextFormatting |
|
||||
{ |
|
||||
internal class FormattableTextSource : ITextSource |
|
||||
{ |
|
||||
private readonly ReadOnlySlice<char> _text; |
|
||||
private readonly TextRunProperties _defaultStyle; |
|
||||
private ReadOnlySlice<ValueSpan<TextRunProperties>> _styleSpans; |
|
||||
|
|
||||
public FormattableTextSource(string text, TextRunProperties defaultStyle, |
|
||||
ReadOnlySlice<ValueSpan<TextRunProperties>> styleSpans) |
|
||||
{ |
|
||||
_text = text.AsMemory(); |
|
||||
|
|
||||
_defaultStyle = defaultStyle; |
|
||||
|
|
||||
_styleSpans = styleSpans; |
|
||||
} |
|
||||
|
|
||||
public TextRun GetTextRun(int textSourceIndex) |
|
||||
{ |
|
||||
if (_styleSpans.IsEmpty) |
|
||||
{ |
|
||||
return new TextEndOfParagraph(); |
|
||||
} |
|
||||
|
|
||||
var currentSpan = _styleSpans[0]; |
|
||||
|
|
||||
_styleSpans = _styleSpans.Skip(1); |
|
||||
|
|
||||
return new TextCharacters(_text.AsSlice(currentSpan.Start, currentSpan.Length), |
|
||||
_defaultStyle); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,121 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Media.TextFormatting; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
namespace Avalonia.Skia.UnitTests.Media.TextFormatting |
||||
|
{ |
||||
|
internal readonly struct FormattedTextSource : ITextSource |
||||
|
{ |
||||
|
private readonly ReadOnlySlice<char> _text; |
||||
|
private readonly TextRunProperties _defaultProperties; |
||||
|
private readonly IReadOnlyList<ValueSpan<TextRunProperties>> _textModifier; |
||||
|
|
||||
|
public FormattedTextSource(ReadOnlySlice<char> text, TextRunProperties defaultProperties, |
||||
|
IReadOnlyList<ValueSpan<TextRunProperties>> textModifier) |
||||
|
{ |
||||
|
_text = text; |
||||
|
_defaultProperties = defaultProperties; |
||||
|
_textModifier = textModifier; |
||||
|
} |
||||
|
|
||||
|
public TextRun GetTextRun(int textSourceIndex) |
||||
|
{ |
||||
|
if (textSourceIndex > _text.End) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var runText = _text.Skip(textSourceIndex); |
||||
|
|
||||
|
if (runText.IsEmpty) |
||||
|
{ |
||||
|
return new TextEndOfParagraph(); |
||||
|
} |
||||
|
|
||||
|
var textStyleRun = CreateTextStyleRun(runText, _defaultProperties, _textModifier); |
||||
|
|
||||
|
return new TextCharacters(runText.Take(textStyleRun.Length), textStyleRun.Value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates a span of text run properties that has modifier applied.
|
||||
|
/// </summary>
|
||||
|
/// <param name="text">The text to create the properties for.</param>
|
||||
|
/// <param name="defaultProperties">The default text properties.</param>
|
||||
|
/// <param name="textModifier">The text properties modifier.</param>
|
||||
|
/// <returns>
|
||||
|
/// The created text style run.
|
||||
|
/// </returns>
|
||||
|
private static ValueSpan<TextRunProperties> CreateTextStyleRun(ReadOnlySlice<char> text, |
||||
|
TextRunProperties defaultProperties, IReadOnlyList<ValueSpan<TextRunProperties>> textModifier) |
||||
|
{ |
||||
|
if (textModifier == null || textModifier.Count == 0) |
||||
|
{ |
||||
|
return new ValueSpan<TextRunProperties>(text.Start, text.Length, defaultProperties); |
||||
|
} |
||||
|
|
||||
|
var currentProperties = defaultProperties; |
||||
|
|
||||
|
var hasOverride = false; |
||||
|
|
||||
|
var i = 0; |
||||
|
|
||||
|
var length = 0; |
||||
|
|
||||
|
for (; i < textModifier.Count; i++) |
||||
|
{ |
||||
|
var propertiesOverride = textModifier[i]; |
||||
|
|
||||
|
var textRange = new TextRange(propertiesOverride.Start, propertiesOverride.Length); |
||||
|
|
||||
|
if (textRange.End < text.Start) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (textRange.Start > text.End) |
||||
|
{ |
||||
|
length = text.Length; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (textRange.Start > text.Start) |
||||
|
{ |
||||
|
if (propertiesOverride.Value != currentProperties) |
||||
|
{ |
||||
|
length = Math.Min(Math.Abs(textRange.Start - text.Start), text.Length); |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
length += Math.Min(text.Length - length, textRange.Length); |
||||
|
|
||||
|
if (hasOverride) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
hasOverride = true; |
||||
|
|
||||
|
currentProperties = propertiesOverride.Value; |
||||
|
} |
||||
|
|
||||
|
if (length < text.Length && i == textModifier.Count) |
||||
|
{ |
||||
|
if (currentProperties == defaultProperties) |
||||
|
{ |
||||
|
length = text.Length; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (length != text.Length) |
||||
|
{ |
||||
|
text = text.Take(length); |
||||
|
} |
||||
|
|
||||
|
return new ValueSpan<TextRunProperties>(text.Start, length, currentProperties); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 404 B After Width: | Height: | Size: 403 B |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 370 B After Width: | Height: | Size: 370 B |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |