Browse Source

Ability to configure font features (#14157)

* Ability to configure font features

* Minor adjustments

---------

Co-authored-by: Herman Kirshin <herman.kirshin@jetbrains.com>
Co-authored-by: Benedikt Stebner <Gillibald@users.noreply.github.com>
pull/14430/head
Herman K 2 years ago
committed by GitHub
parent
commit
88967de49e
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 7
      samples/ControlCatalog/Pages/TextBlockPage.xaml
  2. 153
      src/Avalonia.Base/Media/FontFeature.cs
  3. 10
      src/Avalonia.Base/Media/FontFeatureCollection.cs
  4. 66
      src/Avalonia.Base/Media/FormattedText.cs
  5. 24
      src/Avalonia.Base/Media/TextFormatting/GenericTextRunProperties.cs
  6. 5
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  7. 50
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  8. 10
      src/Avalonia.Base/Media/TextFormatting/TextRunProperties.cs
  9. 22
      src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs
  10. 10
      src/Avalonia.Controls/Documents/Inline.cs
  11. 37
      src/Avalonia.Controls/Documents/TextElement.cs
  12. 15
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  13. 15
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  14. 3
      src/Avalonia.Controls/SelectableTextBlock.cs
  15. 17
      src/Avalonia.Controls/TextBlock.cs
  16. 4
      src/Avalonia.Controls/TextBox.cs
  17. 25
      src/Skia/Avalonia.Skia/TextShaperImpl.cs
  18. 25
      src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs

7
samples/ControlCatalog/Pages/TextBlockPage.xaml

@ -128,6 +128,13 @@
</Span>.
</SelectableTextBlock>
</Border>
<Border>
<TextBlock FontFamily="Times New Roman">
<Run Text="ABC" FontFeatures="+c2sc, +smcp"/>
<Run Text="DEF"/>
<Run Text="0123" FontFeatures="frac"/>
</TextBlock>
</Border>
</WrapPanel>
</StackPanel>
</UserControl>

153
src/Avalonia.Base/Media/FontFeature.cs

@ -0,0 +1,153 @@
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
namespace Avalonia.Media;
/// <summary>
/// Font feature
/// </summary>
public record FontFeature
{
private const int DefaultValue = 1;
private const int InfinityEnd = -1;
private static readonly Regex s_featureRegex = new Regex(
@"^\s*(?<Value>[+-])?\s*(?<Tag>\w{4})\s*(\[\s*(?<Start>\d+)?(\s*(?<Separator>:)\s*)?(?<End>\d+)?\s*\])?\s*(?(Value)()|(=\s*(?<Value>\d+|on|off)))?\s*$",
RegexOptions.Compiled | RegexOptions.ExplicitCapture);
/// <summary>Gets or sets the tag.</summary>
public string Tag
{
get;
init;
}
/// <summary>Gets or sets the value.</summary>
public int Value
{
get;
init;
}
/// <summary>Gets or sets the start.</summary>
public int Start
{
get;
init;
}
/// <summary>Gets or sets the end.</summary>
public int End
{
get;
init;
}
/// <summary>
/// Creates an instance of FontFeature.
/// </summary>
public FontFeature()
{
Tag = string.Empty;
Value = DefaultValue;
Start = 0;
End = InfinityEnd;
}
/// <summary>
/// Parses a string to return a <see cref="FontFeature"/>.
/// Syntax is the following:
///
/// Syntax Value Start End
/// Setting value:
/// kern 1 0 ∞ Turn feature on
/// +kern 1 0 ∞ Turn feature on
/// -kern 0 0 ∞ Turn feature off
/// kern=0 0 0 ∞ Turn feature off
/// kern=1 1 0 ∞ Turn feature on
/// aalt=2 2 0 ∞ Choose 2nd alternate
/// Setting index:
/// kern[] 1 0 ∞ Turn feature on
/// kern[:] 1 0 ∞ Turn feature on
/// kern[5:] 1 5 ∞ Turn feature on, partial
/// kern[:5] 1 0 5 Turn feature on, partial
/// kern[3:5] 1 3 5 Turn feature on, range
/// kern[3] 1 3 3+1 Turn feature on, single char
/// Mixing it all:
/// aalt[3:5]=2 2 3 5 Turn 2nd alternate on for range
///
/// </summary>
/// <param name="s">The string.</param>
/// <returns>The <see cref="FontFeature"/>.</returns>
// ReSharper disable once UnusedMember.Global
public static FontFeature Parse(string s)
{
var match = s_featureRegex.Match(s);
if (!match.Success)
{
return new FontFeature();
}
var hasSeparator = match.Groups["Separator"].Value == ":";
var hasStart = int.TryParse(match.Groups["Start"].Value, NumberStyles.None, CultureInfo.InvariantCulture, out var start);
var hasEnd = int.TryParse(match.Groups["End"].Value, NumberStyles.None, CultureInfo.InvariantCulture, out var end);
var stringValue = match.Groups["Value"].Value;
if (stringValue == "-" || stringValue.ToUpperInvariant() == "OFF")
stringValue = "0";
if (stringValue == "+" || stringValue.ToUpperInvariant() == "ON")
stringValue = "1";
var result = new FontFeature
{
Tag = match.Groups["Tag"].Value,
Start = hasStart ? start : 0,
End = hasEnd ? end : hasStart && !hasSeparator ? (start + 1) : InfinityEnd,
Value = int.TryParse(stringValue, NumberStyles.None, CultureInfo.InvariantCulture, out var value) ? value : DefaultValue,
};
return result;
}
/// <summary>
/// Gets a string representation of the <see cref="FontFeature"/>.
/// </summary>
/// <returns>The string representation.</returns>
public override string ToString()
{
var result = new StringBuilder(128);
if (Value == 0)
result.Append('-');
result.Append(Tag ?? string.Empty);
if (Start != 0 || End != InfinityEnd)
{
result.Append('[');
if (Start > 0)
result.Append(Start.ToString(CultureInfo.InvariantCulture));
if (End != Start + 1)
{
result.Append(':');
if (End != InfinityEnd)
result.Append(End.ToString(CultureInfo.InvariantCulture));
}
result.Append(']');
}
if (Value is DefaultValue or 0)
{
return result.ToString();
}
result.Append('=');
result.Append(Value.ToString(CultureInfo.InvariantCulture));
return result.ToString();
}
}

10
src/Avalonia.Base/Media/FontFeatureCollection.cs

@ -0,0 +1,10 @@
using Avalonia.Collections;
namespace Avalonia.Media;
/// <summary>
/// List of font feature settings
/// </summary>
public class FontFeatureCollection : AvaloniaList<FontFeature>
{
}

66
src/Avalonia.Base/Media/FormattedText.cs

@ -3,6 +3,7 @@ using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
@ -50,6 +51,7 @@ namespace Avalonia.Media
/// <param name="typeface">Type face used to display text.</param>
/// <param name="emSize">Font em size in visual units (1/96 of an inch).</param>
/// <param name="foreground">Foreground brush used to render text.</param>
/// <param name="features">Optional list of turned on/off features.</param>
public FormattedText(
string textToFormat,
CultureInfo culture,
@ -183,6 +185,7 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
runProps.Typeface,
runProps.FontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
foregroundBrush,
@ -197,6 +200,62 @@ namespace Avalonia.Media
}
}
/// <summary>
/// Sets or changes the font features for the text object
/// </summary>
/// <param name="fontFeatures">Feature collection</param>
public void SetFontFeatures(FontFeatureCollection? fontFeatures)
{
SetFontFeatures(fontFeatures, 0, _text.Length);
}
/// <summary>
/// Sets or changes the font features for the text object
/// </summary>
/// <param name="fontFeatures">Feature collection</param>
/// <param name="startIndex">The start index of initial character to apply the change to.</param>
/// <param name="count">The number of characters the change should be applied to.</param>
public void SetFontFeatures(FontFeatureCollection? fontFeatures, int startIndex, int count)
{
var limit = ValidateRange(startIndex, count);
for (var i = startIndex; i < limit;)
{
var formatRider = new SpanRider(_formatRuns, _latestPosition, i);
i = Math.Min(limit, i + formatRider.Length);
#pragma warning disable 6506
// Presharp warns that runProps is not validated, but it can never be null
// because the rider is already checked to be in range
if (!(formatRider.CurrentElement is GenericTextRunProperties runProps))
{
throw new NotSupportedException($"{nameof(runProps)} can not be null.");
}
if ((fontFeatures == null && runProps.FontFeatures == null) ||
(fontFeatures != null && runProps.FontFeatures != null &&
fontFeatures.SequenceEqual(runProps.FontFeatures)))
{
continue;
}
var newProps = new GenericTextRunProperties(
runProps.Typeface,
fontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
runProps.BackgroundBrush,
runProps.BaselineAlignment,
runProps.CultureInfo
);
#pragma warning restore 6506
_latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition,
newProps, formatRider.SpanPosition);
}
}
/// <summary>
/// Sets or changes the font family for the text object
/// </summary>
@ -270,6 +329,7 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
new Typeface(fontFamily, oldTypeface.Style, oldTypeface.Weight),
runProps.FontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
@ -329,6 +389,7 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
runProps.Typeface,
runProps.FontFeatures,
emSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
@ -391,6 +452,7 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
runProps.Typeface,
runProps.FontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
@ -450,6 +512,7 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
new Typeface(oldTypeface.FontFamily, oldTypeface.Style, weight),
runProps.FontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
@ -506,6 +569,7 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
new Typeface(oldTypeface.FontFamily, style, oldTypeface.Weight),
runProps.FontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
@ -562,6 +626,7 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
typeface,
runProps.FontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
@ -619,6 +684,7 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
runProps.Typeface,
runProps.FontFeatures,
runProps.FontRenderingEmSize,
textDecorations,
runProps.ForegroundBrush,

24
src/Avalonia.Base/Media/TextFormatting/GenericTextRunProperties.cs

@ -1,4 +1,6 @@
using System.Globalization;
using System;
using System.Collections.Generic;
using System.Globalization;
namespace Avalonia.Media.TextFormatting
{
@ -9,9 +11,25 @@ namespace Avalonia.Media.TextFormatting
{
private const double DefaultFontRenderingEmSize = 12;
// TODO12: Remove in 12.0.0 and make fontFeatures parameter in main ctor optional
public GenericTextRunProperties(Typeface typeface, double fontRenderingEmSize = DefaultFontRenderingEmSize,
TextDecorationCollection? textDecorations = null, IBrush? foregroundBrush = null,
IBrush? backgroundBrush = null, BaselineAlignment baselineAlignment = BaselineAlignment.Baseline,
CultureInfo? cultureInfo = null) :
this(typeface, null, fontRenderingEmSize, textDecorations, foregroundBrush,
backgroundBrush, baselineAlignment, cultureInfo)
{
}
// TODO12:Change signature in 12.0.0
public GenericTextRunProperties(
Typeface typeface,
FontFeatureCollection? fontFeatures,
double fontRenderingEmSize = DefaultFontRenderingEmSize,
TextDecorationCollection? textDecorations = null,
IBrush? foregroundBrush = null,
IBrush? backgroundBrush = null,
BaselineAlignment baselineAlignment = BaselineAlignment.Baseline,
CultureInfo? cultureInfo = null)
{
Typeface = typeface;
@ -21,6 +39,7 @@ namespace Avalonia.Media.TextFormatting
BackgroundBrush = backgroundBrush;
BaselineAlignment = baselineAlignment;
CultureInfo = cultureInfo;
FontFeatures = fontFeatures;
}
/// <inheritdoc />
@ -38,6 +57,9 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc />
public override IBrush? BackgroundBrush { get; }
/// <inheritdoc />
public override FontFeatureCollection? FontFeatures { get; }
/// <inheritdoc />
public override BaselineAlignment BaselineAlignment { get; }

5
src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

@ -272,7 +272,7 @@ namespace Avalonia.Media.TextFormatting
}
var shaperOptions = new TextShaperOptions(
properties.CachedGlyphTypeface,
properties.CachedGlyphTypeface, properties.FontFeatures,
properties.FontRenderingEmSize, shapeableRun.BidiLevel, properties.CultureInfo,
paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing);
@ -976,7 +976,8 @@ namespace Avalonia.Media.TextFormatting
var cultureInfo = textRun.Properties.CultureInfo;
var shaperOptions = new TextShaperOptions(glyphTypeface, fontRenderingEmSize, (sbyte)flowDirection, cultureInfo);
var shaperOptions = new TextShaperOptions(glyphTypeface, textRun.Properties.FontFeatures,
fontRenderingEmSize, (sbyte)flowDirection, cultureInfo);
var shapedBuffer = textShaper.ShapeText(textRun.Text, shaperOptions);

50
src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

@ -17,6 +17,7 @@ namespace Avalonia.Media.TextFormatting
private int _textSourceLength;
// TODO12: Remove in 12.0.0 and make fontFeatures parameter in main ctor optional
/// <summary>
/// Initializes a new instance of the <see cref="TextLayout" /> class.
/// </summary>
@ -51,10 +52,52 @@ namespace Avalonia.Media.TextFormatting
double letterSpacing = 0,
int maxLines = 0,
IReadOnlyList<ValueSpan<TextRunProperties>>? textStyleOverrides = null)
: this(text, typeface, null, fontSize, foreground, textAlignment, textWrapping, textTrimming, textDecorations,
flowDirection, maxWidth, maxHeight, lineHeight, letterSpacing, maxLines, textStyleOverrides)
{
}
// TODO12:Change signature in 12.0.0
/// <summary>
/// Initializes a new instance of the <see cref="TextLayout" /> class.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="typeface">The typeface.</param>
/// <param name="fontSize">Size of the font.</param>
/// <param name="foreground">The foreground.</param>
/// <param name="textAlignment">The text alignment.</param>
/// <param name="textWrapping">The text wrapping.</param>
/// <param name="textTrimming">The text trimming.</param>
/// <param name="textDecorations">The text decorations.</param>
/// <param name="flowDirection">The text flow direction.</param>
/// <param name="maxWidth">The maximum width.</param>
/// <param name="maxHeight">The maximum height.</param>
/// <param name="lineHeight">The height of each line of text.</param>
/// <param name="letterSpacing">The letter spacing that is applied to rendered glyphs.</param>
/// <param name="maxLines">The maximum number of text lines.</param>
/// <param name="textStyleOverrides">The text style overrides.</param>
/// <param name="fontFeatures">Optional list of turned on/off features.</param>
public TextLayout(
string? text,
Typeface typeface,
FontFeatureCollection? fontFeatures,
double fontSize,
IBrush? foreground,
TextAlignment textAlignment = TextAlignment.Left,
TextWrapping textWrapping = TextWrapping.NoWrap,
TextTrimming? textTrimming = null,
TextDecorationCollection? textDecorations = null,
FlowDirection flowDirection = FlowDirection.LeftToRight,
double maxWidth = double.PositiveInfinity,
double maxHeight = double.PositiveInfinity,
double lineHeight = double.NaN,
double letterSpacing = 0,
int maxLines = 0,
IReadOnlyList<ValueSpan<TextRunProperties>>? textStyleOverrides = null)
{
_paragraphProperties =
CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping,
textDecorations, flowDirection, lineHeight, letterSpacing);
textDecorations, flowDirection, lineHeight, letterSpacing, fontFeatures);
_textSource = new FormattedTextSource(text ?? "", _paragraphProperties.DefaultTextRunProperties, textStyleOverrides);
@ -484,13 +527,14 @@ namespace Avalonia.Media.TextFormatting
/// <param name="flowDirection">The text flow direction.</param>
/// <param name="lineHeight">The height of each line of text.</param>
/// <param name="letterSpacing">The letter spacing that is applied to rendered glyphs.</param>
/// <param name="features">Optional list of turned on/off features.</param>
/// <returns></returns>
internal static TextParagraphProperties CreateTextParagraphProperties(Typeface typeface, double fontSize,
IBrush? foreground, TextAlignment textAlignment, TextWrapping textWrapping,
TextDecorationCollection? textDecorations, FlowDirection flowDirection, double lineHeight,
double letterSpacing)
double letterSpacing, FontFeatureCollection? features)
{
var textRunStyle = new GenericTextRunProperties(typeface, fontSize, textDecorations, foreground);
var textRunStyle = new GenericTextRunProperties(typeface, features, fontSize, textDecorations, foreground);
return new GenericTextParagraphProperties(flowDirection, textAlignment, true, false,
textRunStyle, textWrapping, lineHeight, 0, letterSpacing);

10
src/Avalonia.Base/Media/TextFormatting/TextRunProperties.cs

@ -44,6 +44,11 @@ namespace Avalonia.Media.TextFormatting
/// </summary>
public abstract CultureInfo? CultureInfo { get; }
/// <summary>
/// Optional features of used font.
/// </summary>
public virtual FontFeatureCollection? FontFeatures => null;
/// <summary>
/// Run vertical box alignment
/// </summary>
@ -64,7 +69,8 @@ namespace Avalonia.Media.TextFormatting
&& Equals(TextDecorations, other.TextDecorations) &&
Equals(ForegroundBrush, other.ForegroundBrush) &&
Equals(BackgroundBrush, other.BackgroundBrush) &&
Equals(CultureInfo, other.CultureInfo);
Equals(CultureInfo, other.CultureInfo) &&
Equals(FontFeatures, other.FontFeatures);
}
public override bool Equals(object? obj)
@ -101,7 +107,7 @@ namespace Avalonia.Media.TextFormatting
if (this is GenericTextRunProperties other && other.Typeface == typeface)
return this;
return new GenericTextRunProperties(typeface, FontRenderingEmSize,
return new GenericTextRunProperties(typeface, FontFeatures, FontRenderingEmSize,
TextDecorations, ForegroundBrush, BackgroundBrush, BaselineAlignment);
}
}

22
src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs

@ -1,4 +1,5 @@
using System.Globalization;
using System.Collections.Generic;
using System.Globalization;
namespace Avalonia.Media.TextFormatting
{
@ -7,8 +8,22 @@ namespace Avalonia.Media.TextFormatting
/// </summary>
public readonly record struct TextShaperOptions
{
// TODO12: Remove in 12.0.0 and make fontFeatures parameter in main ctor optional
public TextShaperOptions(
IGlyphTypeface typeface,
double fontRenderingEmSize = 12,
sbyte bidiLevel = 0,
CultureInfo? culture = null,
double incrementalTabWidth = 0,
double letterSpacing = 0)
: this(typeface, null, fontRenderingEmSize, bidiLevel, culture, incrementalTabWidth, letterSpacing)
{
}
// TODO12:Change signature in 12.0.0
public TextShaperOptions(
IGlyphTypeface typeface,
IReadOnlyList<FontFeature>? fontFeatures,
double fontRenderingEmSize = 12,
sbyte bidiLevel = 0,
CultureInfo? culture = null,
@ -21,6 +36,7 @@ namespace Avalonia.Media.TextFormatting
Culture = culture;
IncrementalTabWidth = incrementalTabWidth;
LetterSpacing = letterSpacing;
FontFeatures = fontFeatures;
}
/// <summary>
@ -52,5 +68,9 @@ namespace Avalonia.Media.TextFormatting
/// </summary>
public double LetterSpacing { get; }
/// <summary>
/// Get features.
/// </summary>
public IReadOnlyList<FontFeature>? FontFeatures { get; }
}
}

10
src/Avalonia.Controls/Documents/Inline.cs

@ -102,8 +102,14 @@ namespace Avalonia.Controls.Documents
fontWeight = FontWeight.Bold;
}
return new GenericTextRunProperties(new Typeface(FontFamily, fontStyle, fontWeight), FontSize,
textDecorations, Foreground, background, BaselineAlignment);
return new GenericTextRunProperties(
new Typeface(FontFamily, fontStyle, fontWeight),
FontFeatures,
FontSize,
textDecorations,
Foreground,
background,
BaselineAlignment);
}
/// <inheritdoc />

37
src/Avalonia.Controls/Documents/TextElement.cs

@ -23,6 +23,14 @@ namespace Avalonia.Controls.Documents
defaultValue: FontFamily.Default,
inherits: true);
/// <summary>
/// Defines the <see cref="FontFeatures"/> property.
/// </summary>
public static readonly AttachedProperty<FontFeatureCollection?> FontFeaturesProperty =
AvaloniaProperty.RegisterAttached<TextElement, TextElement, FontFeatureCollection?>(
nameof(FontFeatures),
inherits: true);
/// <summary>
/// Defines the <see cref="FontSize"/> property.
/// </summary>
@ -87,6 +95,15 @@ namespace Avalonia.Controls.Documents
set => SetValue(FontFamilyProperty, value);
}
/// <summary>
/// Gets or sets the font features.
/// </summary>
public FontFeatureCollection? FontFeatures
{
get => GetValue(FontFeaturesProperty);
set => SetValue(FontFeaturesProperty, value);
}
/// <summary>
/// Gets or sets the font size.
/// </summary>
@ -152,6 +169,26 @@ namespace Avalonia.Controls.Documents
control.SetValue(FontFamilyProperty, value);
}
/// <summary>
/// Gets the value of the attached <see cref="FontFeaturesProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font family.</returns>
public static FontFeatureCollection? GetFontFeatures(Control control)
{
return control.GetValue(FontFeaturesProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="FontFeaturesProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFontFeatures(Control control, FontFeatureCollection? value)
{
control.SetValue(FontFeaturesProperty, value);
}
/// <summary>
/// Gets the value of the attached <see cref="FontSizeProperty"/> on a control.
/// </summary>

15
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -152,6 +152,15 @@ namespace Avalonia.Controls.Presenters
set => TextElement.SetFontFamily(this, value);
}
/// <summary>
/// Gets or sets the font family.
/// </summary>
public FontFeatureCollection? FontFeatures
{
get => TextElement.GetFontFeatures(this);
set => TextElement.SetFontFeatures(this, value);
}
/// <summary>
/// Gets or sets the font size.
/// </summary>
@ -329,7 +338,7 @@ namespace Avalonia.Controls.Presenters
var maxWidth = MathUtilities.IsZero(constraint.Width) ? double.PositiveInfinity : constraint.Width;
var maxHeight = MathUtilities.IsZero(constraint.Height) ? double.PositiveInfinity : constraint.Height;
var textLayout = new TextLayout(text, typeface, FontSize, foreground, TextAlignment,
var textLayout = new TextLayout(text, typeface, FontFeatures, FontSize, foreground, TextAlignment,
TextWrapping, maxWidth: maxWidth, maxHeight: maxHeight, textStyleOverrides: textStyleOverrides,
flowDirection: FlowDirection, lineHeight: LineHeight, letterSpacing: LetterSpacing);
@ -531,7 +540,7 @@ namespace Avalonia.Controls.Presenters
if (!string.IsNullOrEmpty(preeditText))
{
var preeditHighlight = new ValueSpan<TextRunProperties>(caretIndex, preeditText.Length,
new GenericTextRunProperties(typeface, FontSize,
new GenericTextRunProperties(typeface, FontFeatures, FontSize,
foregroundBrush: foreground,
textDecorations: TextDecorations.Underline));
@ -547,7 +556,7 @@ namespace Avalonia.Controls.Presenters
textStyleOverrides = new[]
{
new ValueSpan<TextRunProperties>(start, length,
new GenericTextRunProperties(typeface, FontSize,
new GenericTextRunProperties(typeface, FontFeatures, FontSize,
foregroundBrush: SelectionForegroundBrush))
};
}

15
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -52,6 +52,12 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<FontFamily> FontFamilyProperty =
TextElement.FontFamilyProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="FontFeaturesProperty"/> property.
/// </summary>
public static readonly StyledProperty<FontFeatureCollection?> FontFeaturesProperty =
TextElement.FontFeaturesProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="FontSize"/> property.
/// </summary>
@ -182,6 +188,15 @@ namespace Avalonia.Controls.Primitives
set => SetValue(FontFamilyProperty, value);
}
/// <summary>
/// Gets or sets the font features turned on/off.
/// </summary>
public FontFeatureCollection? FontFeatures
{
get => GetValue(FontFeaturesProperty);
set => SetValue(FontFeaturesProperty, value);
}
/// <summary>
/// Gets or sets the size of the control's text in points.
/// </summary>

3
src/Avalonia.Controls/SelectableTextBlock.cs

@ -186,6 +186,7 @@ namespace Avalonia.Controls
var defaultProperties = new GenericTextRunProperties(
typeface,
FontFeatures,
FontSize,
TextDecorations,
Foreground);
@ -207,7 +208,7 @@ namespace Avalonia.Controls
textStyleOverrides = new[]
{
new ValueSpan<TextRunProperties>(start, length,
new GenericTextRunProperties(typeface, FontSize,
new GenericTextRunProperties(typeface, FontFeatures, FontSize,
foregroundBrush: SelectionForegroundBrush))
};
}

17
src/Avalonia.Controls/TextBlock.cs

@ -148,6 +148,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<TextDecorationCollection?> TextDecorationsProperty =
Inline.TextDecorationsProperty.AddOwner<TextBlock>();
/// <summary>
/// Defines the <see cref="FontFeatures"/> property.
/// </summary>
public static readonly StyledProperty<FontFeatureCollection?> FontFeaturesProperty =
TextElement.FontFeaturesProperty.AddOwner<TextBlock>();
/// <summary>
/// Defines the <see cref="Inlines"/> property.
/// </summary>
@ -339,6 +345,15 @@ namespace Avalonia.Controls
set => SetValue(TextDecorationsProperty, value);
}
/// <summary>
/// Gets or sets the font features.
/// </summary>
public FontFeatureCollection? FontFeatures
{
get => GetValue(FontFeaturesProperty);
set => SetValue(FontFeaturesProperty, value);
}
/// <summary>
/// Gets or sets the inlines.
/// </summary>
@ -635,6 +650,7 @@ namespace Avalonia.Controls
var defaultProperties = new GenericTextRunProperties(
typeface,
FontFeatures,
FontSize,
TextDecorations,
Foreground);
@ -806,6 +822,7 @@ namespace Avalonia.Controls
case nameof(Text):
case nameof(TextDecorations):
case nameof(FontFeatures):
case nameof(Foreground):
{
InvalidateTextLayout();

4
src/Avalonia.Controls/TextBox.cs

@ -2221,7 +2221,7 @@ namespace Avalonia.Controls
{
var fontSize = FontSize;
var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
var paragraphProperties = TextLayout.CreateTextParagraphProperties(typeface, fontSize, null, default, default, null, default, LineHeight, default);
var paragraphProperties = TextLayout.CreateTextParagraphProperties(typeface, fontSize, null, default, default, null, default, LineHeight, default, FontFeatures);
var textLayout = new TextLayout(new LineTextSource(MaxLines), paragraphProperties);
var verticalSpace = GetVerticalSpaceBetweenScrollViewerAndPresenter();
@ -2237,7 +2237,7 @@ namespace Avalonia.Controls
{
var fontSize = FontSize;
var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
var paragraphProperties = TextLayout.CreateTextParagraphProperties(typeface, fontSize, null, default, default, null, default, LineHeight, default);
var paragraphProperties = TextLayout.CreateTextParagraphProperties(typeface, fontSize, null, default, default, null, default, LineHeight, default, FontFeatures);
var textLayout = new TextLayout(new LineTextSource(MinLines), paragraphProperties);
var verticalSpace = GetVerticalSpaceBetweenScrollViewerAndPresenter();

25
src/Skia/Avalonia.Skia/TextShaperImpl.cs

@ -41,7 +41,7 @@ namespace Avalonia.Skia
var font = ((GlyphTypefaceImpl)typeface).Font;
font.Shape(buffer);
font.Shape(buffer, GetFeatures(options));
if (buffer.Direction == Direction.RightToLeft)
{
@ -176,5 +176,28 @@ namespace Avalonia.Skia
// should never happen
throw new InvalidOperationException("Memory not backed by string, array or manager");
}
private static Feature[] GetFeatures(TextShaperOptions options)
{
if (options.FontFeatures is null || options.FontFeatures.Count == 0)
{
return Array.Empty<Feature>();
}
var features = new Feature[options.FontFeatures.Count];
for (var i = 0; i < options.FontFeatures.Count; i++)
{
var fontFeature = options.FontFeatures[i];
features[i] = new Feature(
Tag.Parse(fontFeature.Tag),
(uint)fontFeature.Value,
(uint)fontFeature.Start,
(uint)fontFeature.End);
}
return features;
}
}
}

25
src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs

@ -42,7 +42,7 @@ namespace Avalonia.Direct2D1.Media
var font = ((GlyphTypefaceImpl)typeface).Font;
font.Shape(buffer);
font.Shape(buffer, GetFeatures(options));
if (buffer.Direction == Direction.RightToLeft)
{
@ -177,5 +177,28 @@ namespace Avalonia.Direct2D1.Media
// should never happen
throw new InvalidOperationException("Memory not backed by string, array or manager");
}
private static Feature[] GetFeatures(TextShaperOptions options)
{
if (options.FontFeatures is null || options.FontFeatures.Count == 0)
{
return Array.Empty<Feature>();
}
var features = new Feature[options.FontFeatures.Count];
for (var i = 0; i < options.FontFeatures.Count; i++)
{
var fontFeature = options.FontFeatures[i];
features[i] = new Feature(
Tag.Parse(fontFeature.Tag),
(uint)fontFeature.Value,
(uint)fontFeature.Start,
(uint)fontFeature.End);
}
return features;
}
}
}

Loading…
Cancel
Save