Browse Source

Font features in test app (#19493)

Co-authored-by: Jan Kučera <[email protected]>
Co-authored-by: Benedikt Stebner <[email protected]>
Jan Kučera 1 month ago
parent
commit
c3de4bbbe0

+ 20 - 0
samples/TextTestApp/FontFeatureCollectionConverter.cs

@@ -0,0 +1,20 @@
+using System;
+using System.ComponentModel;
+using System.Globalization;
+using Avalonia.Media;
+
+namespace TextTestApp
+{
+    public class FontFeatureCollectionConverter : TypeConverter
+    {
+        public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
+        {
+            return sourceType == typeof(string);
+        }
+
+        public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
+        {
+            return FontFeatureCollection.Parse((string)value);
+        }
+    }
+}

+ 26 - 2
samples/TextTestApp/InteractiveLineControl.cs

@@ -1,5 +1,7 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
 using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using Avalonia;
@@ -374,6 +376,14 @@ namespace TextTestApp
                     InvalidateTextRunProperties();
                     break;
 
+                case nameof(FontFeatures):
+                    if (change.OldValue is FontFeatureCollection oc)
+                        oc.CollectionChanged -= OnFeatureCollectionChanged;
+                    if (change.NewValue is FontFeatureCollection nc)
+                        nc.CollectionChanged += OnFeatureCollectionChanged;
+                    OnFeatureCollectionChanged(null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+                    break;
+
                 case nameof(FontStyle):
                 case nameof(FontWeight):
                 case nameof(FontStretch):
@@ -422,6 +432,11 @@ namespace TextTestApp
             base.OnPropertyChanged(change);
         }
 
+        private void OnFeatureCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
+        {
+            InvalidateTextRunProperties();
+        }
+
         protected override Size MeasureOverride(Size availableSize)
         {
             if (TextLine == null)
@@ -433,6 +448,7 @@ namespace TextTestApp
         private const double VerticalSpacing = 5;
         private const double HorizontalSpacing = 5;
         private const double ArrowSize = 5;
+        private const double LabelFontSize = 9;
 
         private Dictionary<string, FormattedText> _labelsCache = new();
         protected FormattedText GetOrCreateLabel(string label, IBrush brush, bool disableCache = false)
@@ -440,7 +456,7 @@ namespace TextTestApp
             if (_labelsCache.TryGetValue(label, out var text))
                 return text;
 
-            text = new FormattedText(label, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, Typeface.Default, 8, brush);
+            text = new FormattedText(label, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, Typeface.Default, LabelFontSize, brush);
 
             if (!disableCache)
                 _labelsCache[label] = text;
@@ -519,7 +535,7 @@ namespace TextTestApp
                     }
                 }
 
-                double y = inkBounds.Bottom - lineBounds.Top + VerticalSpacing * 2;
+                double y = Math.Max(inkBounds.Bottom, lineBounds.Bottom) + VerticalSpacing * 2;
 
                 if (NextHitStroke != null)
                 {
@@ -565,6 +581,14 @@ namespace TextTestApp
                             leftLabelX -= nextLabel.WidthIncludingTrailingWhitespace;
                         }
 
+                        if (BackspaceHitStroke != null)
+                        {
+                            CharacterHit backHit = textLine.GetBackspaceCaretCharacterHit(hit);
+                            var x1 = textLine.GetDistanceFromCharacterHit(new CharacterHit(backHit.FirstCharacterIndex, 0));
+                            var x2 = textLine.GetDistanceFromCharacterHit(new CharacterHit(backHit.FirstCharacterIndex + backHit.TrailingLength, 0));
+                            RenderHorizontalPoint(context, x1, x2, y, BackspaceHitPen, ArrowSize);
+                        }
+
                         if (PreviousHitStroke != null)
                         {
                             prevHit = textLine.GetPreviousCaretCharacterHit(hit);

+ 3 - 0
samples/TextTestApp/MainWindow.axaml

@@ -23,6 +23,8 @@
         <StackPanel Orientation="Horizontal" DockPanel.Dock="Right">
           <Label Content="_Font:" Target="{Binding ElementName=_font}" VerticalAlignment="Center" Margin="5,0,0,0" />
           <ComboBox Name="_font" ItemsSource="{Binding SystemFonts, Source={x:Static FontManager.Current}}" />
+          <Label Content="Fea_tures:" Target="{Binding ElementName=_features}" VerticalAlignment="Center" Margin="5,0,0,0" />
+          <TextBox Name="_features" VerticalAlignment="Center" Text="calt clig kern liga" />
           <Label Content="_Size:" Target="{Binding ElementName=_size}" VerticalAlignment="Center" Margin="5,0,0,0" />
           <TextBox Name="_size" VerticalAlignment="Center" Text="64" />
           <Button VerticalAlignment="Center" Click="OnNewWindowClick" ToolTip.Tip="New window" Margin="5,0,0,0">+</Button>
@@ -37,6 +39,7 @@
                                   
                                       Text="{Binding Text, ElementName=_text}"
                                       FontFamily="{Binding SelectedValue, ElementName=_font}"
+                                      FontFeatures="{Binding Text, ElementName=_features}"
                                       FontSize="{Binding Text, ElementName=_size}"
                                       Background="BlanchedAlmond"
                                       ExtentStroke="Black"

+ 4 - 0
samples/TextTestApp/Program.cs

@@ -1,5 +1,7 @@
 using System;
+using System.ComponentModel;
 using Avalonia;
+using Avalonia.Media;
 
 namespace TextTestApp
 {
@@ -11,6 +13,8 @@ namespace TextTestApp
         [STAThread]
         public static void Main(string[] args)
         {
+            TypeDescriptor.AddAttributes(typeof(FontFeatureCollection), new TypeConverterAttribute(typeof(FontFeatureCollectionConverter)));
+
             BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
         }
 

+ 48 - 0
src/Avalonia.Base/Media/FontFeatureCollection.cs

@@ -1,4 +1,6 @@
+using System.Collections.Generic;
 using Avalonia.Collections;
+using Avalonia.Utilities;
 
 namespace Avalonia.Media;
 
@@ -7,4 +9,50 @@ namespace Avalonia.Media;
 /// </summary>
 public class FontFeatureCollection : AvaloniaList<FontFeature>
 {
+    /// <summary>
+    /// Initializes a new instance of the <see cref="FontFeatureCollection"/>.
+    /// </summary>
+    public FontFeatureCollection()
+    {
+
+    }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="FontFeatureCollection"/> that is empty and has the specified initial capacity.
+    /// </summary>
+    /// <param name="capacity">The number of font features that the new collection can initially store.</param>
+    public FontFeatureCollection(int capacity) : base(capacity)
+    {
+
+    }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="FontFeatureCollection"/> that contains font features copied from the specified collection.
+    /// </summary>
+    /// <param name="fontFeatures">The collection whose font features are copied to the new collection.</param>
+    public FontFeatureCollection(IEnumerable<FontFeature> fontFeatures) : base(fontFeatures)
+    {
+
+    }
+
+    /// <summary>
+    /// Parses a <see cref="FontFeatureCollection"/> string.
+    /// </summary>
+    /// <param name="s">The string.</param>
+    /// <returns>The <see cref="FontFeatureCollection"/>.</returns>
+    public static FontFeatureCollection Parse(string s)
+    {
+        var features = new List<FontFeature>();
+
+        using (var tokenizer = new SpanStringTokenizer(s, ',', "Invalid font feature specification."))
+        {
+            while (tokenizer.TryReadSpan(out var token))
+            {
+                FontFeature feature = FontFeature.Parse(token.ToString());
+                features.Add(feature);
+            }
+        }
+
+        return new FontFeatureCollection(features);
+    }
 }