From 9250ccb18afb5d538b6e300a05c606da2d38d3a6 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 8 Oct 2022 13:35:13 -0400 Subject: [PATCH 01/84] Add missing base constructor call --- .../ColorSpectrum/ColorSpectrum.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index bd44161a42..200a652d3b 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -95,7 +95,7 @@ namespace Avalonia.Controls.Primitives /// /// Initializes a new instance of the class. /// - public ColorSpectrum() + public ColorSpectrum() : base() { _shapeFromLastBitmapCreation = Shape; _componentsFromLastBitmapCreation = Components; From 5c3a56a9533eab9060a216831df2a9a8c5e08e12 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 8 Oct 2022 14:25:02 -0400 Subject: [PATCH 02/84] Fix the color spectrum selection ellipse position Specifically, when updated and not part of the visual tree --- .../ColorSpectrum/ColorSpectrum.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index 200a652d3b..9261e5892d 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -171,6 +171,18 @@ namespace Avalonia.Controls.Primitives { base.OnAttachedToVisualTree(e); + // If the color was updated while this ColorSpectrum was not part of the visual tree, + // the selection ellipse may be in an incorrect position. This is because the spectrum + // renders based on layout scaling to avoid color banding; however, layout scale is only + // available when the control is attached to the visual tree. The ColorSpectrum's color + // may be updated from code-behind or from binding with another control when it's not + // part of the visual tree. + // + // See discussion: https://github.com/AvaloniaUI/Avalonia/discussions/9077 + // + // To work-around this issue the selection ellipse is refreshed here. + UpdateEllipse(); + // OnAttachedToVisualTree is called after OnApplyTemplate so events cannot be connected here } @@ -832,6 +844,8 @@ namespace Avalonia.Controls.Primitives } // Remember the bitmap size follows physical device pixels + // Warning: LayoutHelper.GetLayoutScale() doesn't work unless the control is visible + // This will not be true in all cases if the color is updated from another control or code-behind var scale = LayoutHelper.GetLayoutScale(this); Canvas.SetLeft(_selectionEllipsePanel, (xPosition / scale) - (_selectionEllipsePanel.Width / 2)); Canvas.SetTop(_selectionEllipsePanel, (yPosition / scale) - (_selectionEllipsePanel.Height / 2)); From 55e27213f958304679d449ef4aeb25ca9e8743ea Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 8 Oct 2022 14:25:26 -0400 Subject: [PATCH 03/84] Comment additional ColorSpectrum methods --- .../ColorSpectrum/ColorSpectrum.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index 9261e5892d..f272ce850e 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -600,6 +600,10 @@ namespace Avalonia.Controls.Primitives RaiseColorChanged(); } + /// + /// Updates the selected and based on a point within the color spectrum. + /// + /// The point on the spectrum representing the color. private void UpdateColorFromPoint(PointerPoint point) { // If we haven't initialized our HSV value array yet, then we should just ignore any user input - @@ -676,6 +680,9 @@ namespace Avalonia.Controls.Primitives UpdateColor(hsvAtPoint); } + /// + /// Updates the position of the selection ellipse on the spectrum which indicates the selected color. + /// private void UpdateEllipse() { if (_selectionEllipsePanel == null) From 8b070919398878acda991efc0140f7a02cd2a8b7 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 9 Oct 2022 12:21:08 -0400 Subject: [PATCH 04/84] Fix formatting --- .../Themes/Fluent/ColorView.xaml | 30 +++++++++---------- .../Themes/Simple/ColorView.xaml | 30 +++++++++---------- 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index 16bc2acdd1..cf836aff4a 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -241,23 +241,21 @@ - + - + - + - + Date: Sun, 2 Oct 2022 11:51:25 -0400 Subject: [PATCH 05/84] Implement WriteableBitmap caching/reuse and disposal in ColorSlider --- .../ColorSlider/ColorSlider.cs | 42 ++++++++++++++++++- .../Helpers/ColorPickerHelpers.cs | 19 ++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index b662d20223..4e907f610d 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -2,6 +2,7 @@ using Avalonia.Controls.Metadata; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Media.Imaging; using Avalonia.Utilities; namespace Avalonia.Controls.Primitives @@ -31,6 +32,8 @@ namespace Avalonia.Controls.Primitives protected bool ignorePropertyChanged = false; + private WriteableBitmap? _backgroundBitmap; + /// /// Initializes a new instance of the class. /// @@ -38,6 +41,26 @@ namespace Avalonia.Controls.Primitives { } + /// + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + // Bitmaps were released when detached from the visual tree so they must be re-built + UpdateBackground(); + } + + /// + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); + + // Clean-up all bitmaps + // https://github.com/AvaloniaUI/Avalonia/issues/9051 + _backgroundBitmap?.Dispose(); + _backgroundBitmap = null; + } + /// /// Updates the visual state of the control by applying latest PseudoClasses. /// @@ -110,7 +133,19 @@ namespace Avalonia.Controls.Primitives if (bitmap != null) { - Background = new ImageBrush(ColorPickerHelpers.CreateBitmapFromPixelData(bitmap, pixelWidth, pixelHeight)); + if (_backgroundBitmap != null) + { + // Re-use the existing WriteableBitmap + // This assumes the height, width and byte counts are the same and must be set to null + // elsewhere if that assumption is ever not true. + ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bitmap); + } + else + { + _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bitmap, pixelWidth, pixelHeight); + } + + Background = new ImageBrush(_backgroundBitmap); } } } @@ -399,6 +434,11 @@ namespace Avalonia.Controls.Primitives } else if (change.Property == BoundsProperty) { + // If the control's overall dimensions have changed the background bitmap size also needs to change. + // This means the existing bitmap must be released to be recreated correctly in UpdateBackground(). + _backgroundBitmap?.Dispose(); + _backgroundBitmap = null; + UpdateBackground(); } else if (change.Property == ValueProperty || diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs index c1904a3c30..f2683e4677 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs @@ -617,7 +617,6 @@ namespace Avalonia.Controls.Primitives PixelFormat.Bgra8888, AlphaFormat.Premul); - // Warning: This is highly questionable using (var frameBuffer = bitmap.Lock()) { Marshal.Copy(bgraPixelData.ToArray(), 0, frameBuffer.Address, bgraPixelData.Count); @@ -625,5 +624,23 @@ namespace Avalonia.Controls.Primitives return bitmap; } + + /// + /// Updates the given with new, raw BGRA pre-multiplied alpha pixel data. + /// WARNING: The bitmap's width, height and byte count MUST not have changed and MUST be enforced externally. + /// + /// The existing to update. + /// The bitmap (in raw BGRA pre-multiplied alpha pixels). + public static void UpdateBitmapFromPixelData( + WriteableBitmap bitmap, + IList bgraPixelData) + { + using (var frameBuffer = bitmap.Lock()) + { + Marshal.Copy(bgraPixelData.ToArray(), 0, frameBuffer.Address, bgraPixelData.Count); + } + + return; + } } } From ca7543f1d613f9d7b9f2cfed978f8e23379d6684 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 2 Oct 2022 14:24:10 -0400 Subject: [PATCH 06/84] Add new ArrayList to avoid an extra copy when creating spectrum bitmaps --- .../ColorSlider/ColorSlider.cs | 8 +-- .../ColorSpectrum/ColorSpectrum.cs | 61 +++++++++------- .../Helpers/ArrayList.cs | 71 +++++++++++++++++++ .../Helpers/ColorPickerHelpers.cs | 18 ++--- 4 files changed, 118 insertions(+), 40 deletions(-) create mode 100644 src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index 4e907f610d..8bcdef5558 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -121,7 +121,7 @@ namespace Avalonia.Controls.Primitives if (pixelWidth != 0 && pixelHeight != 0) { - var bitmap = await ColorPickerHelpers.CreateComponentBitmapAsync( + ArrayList bgraPixelData = await ColorPickerHelpers.CreateComponentBitmapAsync( pixelWidth, pixelHeight, Orientation, @@ -131,18 +131,18 @@ namespace Avalonia.Controls.Primitives IsAlphaMaxForced, IsSaturationValueMaxForced); - if (bitmap != null) + if (bgraPixelData != null) { if (_backgroundBitmap != null) { // Re-use the existing WriteableBitmap // This assumes the height, width and byte counts are the same and must be set to null // elsewhere if that assumption is ever not true. - ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bitmap); + ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bgraPixelData); } else { - _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bitmap, pixelWidth, pixelHeight); + _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight); } Background = new ImageBrush(_backgroundBitmap); diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index f272ce850e..3604894833 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -994,13 +994,13 @@ namespace Avalonia.Controls.Primitives // The middle 4 are only needed and used in the case of hue as the third dimension. // Saturation and luminosity need only a min and max. - List bgraMinPixelData = new List(); - List bgraMiddle1PixelData = new List(); - List bgraMiddle2PixelData = new List(); - List bgraMiddle3PixelData = new List(); - List bgraMiddle4PixelData = new List(); - List bgraMaxPixelData = new List(); - List newHsvValues = new List(); + ArrayList bgraMinPixelData; + ArrayList bgraMiddle1PixelData; + ArrayList bgraMiddle2PixelData; + ArrayList bgraMiddle3PixelData; + ArrayList bgraMiddle4PixelData; + ArrayList bgraMaxPixelData; + List newHsvValues; // In Avalonia, Bounds returns the actual device-independent pixel size of a control. // However, this is not necessarily the size of the control rendered on a display. @@ -1011,20 +1011,27 @@ namespace Avalonia.Controls.Primitives int pixelDimension = (int)Math.Round(minDimension * scale); var pixelCount = pixelDimension * pixelDimension; var pixelDataSize = pixelCount * 4; - bgraMinPixelData.Capacity = pixelDataSize; + + bgraMinPixelData = new ArrayList(pixelDataSize); + bgraMaxPixelData = new ArrayList(pixelDataSize); + newHsvValues = new List(pixelCount); // We'll only save pixel data for the middle bitmaps if our third dimension is hue. if (components == ColorSpectrumComponents.ValueSaturation || components == ColorSpectrumComponents.SaturationValue) { - bgraMiddle1PixelData.Capacity = pixelDataSize; - bgraMiddle2PixelData.Capacity = pixelDataSize; - bgraMiddle3PixelData.Capacity = pixelDataSize; - bgraMiddle4PixelData.Capacity = pixelDataSize; + bgraMiddle1PixelData = new ArrayList(pixelDataSize); + bgraMiddle2PixelData = new ArrayList(pixelDataSize); + bgraMiddle3PixelData = new ArrayList(pixelDataSize); + bgraMiddle4PixelData = new ArrayList(pixelDataSize); + } + else + { + bgraMiddle1PixelData = new ArrayList(0); + bgraMiddle2PixelData = new ArrayList(0); + bgraMiddle3PixelData = new ArrayList(0); + bgraMiddle4PixelData = new ArrayList(0); } - - bgraMaxPixelData.Capacity = pixelDataSize; - newHsvValues.Capacity = pixelCount; await Task.Run(() => { @@ -1132,12 +1139,12 @@ namespace Avalonia.Controls.Primitives double maxSaturation, double minValue, double maxValue, - List bgraMinPixelData, - List bgraMiddle1PixelData, - List bgraMiddle2PixelData, - List bgraMiddle3PixelData, - List bgraMiddle4PixelData, - List bgraMaxPixelData, + ArrayList bgraMinPixelData, + ArrayList bgraMiddle1PixelData, + ArrayList bgraMiddle2PixelData, + ArrayList bgraMiddle3PixelData, + ArrayList bgraMiddle4PixelData, + ArrayList bgraMaxPixelData, List newHsvValues) { double hMin = minHue; @@ -1292,12 +1299,12 @@ namespace Avalonia.Controls.Primitives double maxSaturation, double minValue, double maxValue, - List bgraMinPixelData, - List bgraMiddle1PixelData, - List bgraMiddle2PixelData, - List bgraMiddle3PixelData, - List bgraMiddle4PixelData, - List bgraMaxPixelData, + ArrayList bgraMinPixelData, + ArrayList bgraMiddle1PixelData, + ArrayList bgraMiddle2PixelData, + ArrayList bgraMiddle3PixelData, + ArrayList bgraMiddle4PixelData, + ArrayList bgraMaxPixelData, List newHsvValues) { double hMin = minHue; diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs new file mode 100644 index 0000000000..0b4c2f8579 --- /dev/null +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs @@ -0,0 +1,71 @@ +namespace Avalonia.Controls.Primitives +{ + /// + /// A thin wrapper over an that allows some additional list-like functionality. + /// + /// + /// This is only for internal ColorPicker-related functionality and should not be used elsewhere. + /// It is added for performance to enjoy the simplicity of the IList.Add() method without requiring + /// an additional copy to turn a list into an array for bitmaps. + /// + /// The type of items in the array. + internal class ArrayList + { + private int _nextIndex = 0; + + /// + /// Initializes a new instance of the class. + /// + public ArrayList(int capacity) + { + Capacity = capacity; + Array = new T[capacity]; + } + + /// + /// Provides access to the underlying array by index. + /// This exists for simplification and the property + /// may also be used. + /// + /// The index of the item to get or set. + /// The item at the given index. + public T this[int i] + { + get => Array[i]; + set => Array[i] = value; + } + + /// + /// Gets the underlying array. + /// + public T[] Array { get; private set; } + + /// + /// Gets the fixed capacity/size of the array. + /// This must be set during construction. + /// + public int Capacity { get; private set; } + + /// + /// Adds the given item to the array at the next available index. + /// WARNING: This must be used carefully and only once, in sequence. + /// + /// The item to add. + public void Add(T item) + { + if (_nextIndex >= 0 && + _nextIndex < Capacity) + { + Array[_nextIndex] = item; + _nextIndex++; + } + else + { + // If necessary an exception could be thrown here + // throw new IndexOutOfRangeException(); + } + + return; + } + } +} diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs index f2683e4677..2378813f52 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs @@ -37,7 +37,7 @@ namespace Avalonia.Controls.Primitives /// during calculation with the HSVA color model. /// This will ensure colors are always discernible regardless of saturation/value. /// A new bitmap representing a gradient of color component values. - public static async Task CreateComponentBitmapAsync( + public static async Task> CreateComponentBitmapAsync( int width, int height, Orientation orientation, @@ -49,14 +49,14 @@ namespace Avalonia.Controls.Primitives { if (width == 0 || height == 0) { - return new byte[0]; + return new ArrayList(0); } - var bitmap = await Task.Run(() => + var bitmap = await Task.Run>(() => { int pixelDataIndex = 0; double componentStep; - byte[] bgraPixelData; + ArrayList bgraPixelData; Color baseRgbColor = Colors.White; Color rgbColor; int bgraPixelDataHeight; @@ -64,7 +64,7 @@ namespace Avalonia.Controls.Primitives // Allocate the buffer // BGRA formatted color components 1 byte each (4 bytes in a pixel) - bgraPixelData = new byte[width * height * 4]; + bgraPixelData = new ArrayList(width * height * 4); bgraPixelDataHeight = height * 4; bgraPixelDataWidth = width * 4; @@ -604,7 +604,7 @@ namespace Avalonia.Controls.Primitives /// The pixel height of the bitmap. /// A new . public static WriteableBitmap CreateBitmapFromPixelData( - IList bgraPixelData, + ArrayList bgraPixelData, int pixelWidth, int pixelHeight) { @@ -619,7 +619,7 @@ namespace Avalonia.Controls.Primitives using (var frameBuffer = bitmap.Lock()) { - Marshal.Copy(bgraPixelData.ToArray(), 0, frameBuffer.Address, bgraPixelData.Count); + Marshal.Copy(bgraPixelData.Array, 0, frameBuffer.Address, bgraPixelData.Array.Length); } return bitmap; @@ -633,11 +633,11 @@ namespace Avalonia.Controls.Primitives /// The bitmap (in raw BGRA pre-multiplied alpha pixels). public static void UpdateBitmapFromPixelData( WriteableBitmap bitmap, - IList bgraPixelData) + ArrayList bgraPixelData) { using (var frameBuffer = bitmap.Lock()) { - Marshal.Copy(bgraPixelData.ToArray(), 0, frameBuffer.Address, bgraPixelData.Count); + Marshal.Copy(bgraPixelData.Array, 0, frameBuffer.Address, bgraPixelData.Array.Length); } return; From 98a58217ce22d55f9343216fd4fbc727618c7306 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 2 Oct 2022 14:25:24 -0400 Subject: [PATCH 07/84] Fix ColorSlider update/refresh for all property changes --- .../ColorSlider/ColorSlider.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index 8bcdef5558..6f20238b70 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -385,11 +385,11 @@ namespace Avalonia.Controls.Primitives return; } - // Always keep the two color properties in sync if (change.Property == ColorProperty) { ignorePropertyChanged = true; + // Always keep the two color properties in sync HsvColor = Color.ToHsv(); SetColorToSliderValues(); @@ -402,7 +402,10 @@ namespace Avalonia.Controls.Primitives ignorePropertyChanged = false; } - else if (change.Property == ColorModelProperty) + else if (change.Property == ColorComponentProperty || + change.Property == ColorModelProperty || + change.Property == IsAlphaMaxForcedProperty || + change.Property == IsSaturationValueMaxForcedProperty) { ignorePropertyChanged = true; @@ -416,6 +419,7 @@ namespace Avalonia.Controls.Primitives { ignorePropertyChanged = true; + // Always keep the two color properties in sync Color = HsvColor.ToRgb(); SetColorToSliderValues(); @@ -440,6 +444,7 @@ namespace Avalonia.Controls.Primitives _backgroundBitmap = null; UpdateBackground(); + UpdatePseudoClasses(); } else if (change.Property == ValueProperty || change.Property == MinimumProperty || From 4f1d315b4b11cf84ca7c3b4ffc65c41d35e24436 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 2 Oct 2022 14:26:28 -0400 Subject: [PATCH 08/84] Make all ColorSpectrum bitmaps globally accessible by the control for future disposal --- .../ColorSpectrum/ColorSpectrum.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index 3604894833..6b06e1eac1 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -73,6 +73,8 @@ namespace Avalonia.Controls.Primitives private WriteableBitmap? _saturationMaximumBitmap; private WriteableBitmap? _valueBitmap; + private WriteableBitmap? _minBitmap; + private WriteableBitmap? _maxBitmap; // Fields used by UpdateEllipse() to ensure that it's using the data // associated with the last call to CreateBitmapsAndColorMap(), @@ -1084,28 +1086,28 @@ namespace Avalonia.Controls.Primitives ColorSpectrumComponents components2 = Components; - WriteableBitmap minBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMinPixelData, pixelWidth, pixelHeight); - WriteableBitmap maxBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMaxPixelData, pixelWidth, pixelHeight); + _minBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMinPixelData, pixelWidth, pixelHeight); + _maxBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMaxPixelData, pixelWidth, pixelHeight); switch (components2) { case ColorSpectrumComponents.HueValue: case ColorSpectrumComponents.ValueHue: - _saturationMinimumBitmap = minBitmap; - _saturationMaximumBitmap = maxBitmap; + _saturationMinimumBitmap = _minBitmap; + _saturationMaximumBitmap = _maxBitmap; break; case ColorSpectrumComponents.HueSaturation: case ColorSpectrumComponents.SaturationHue: - _valueBitmap = maxBitmap; + _valueBitmap = _maxBitmap; break; case ColorSpectrumComponents.ValueSaturation: case ColorSpectrumComponents.SaturationValue: - _hueRedBitmap = minBitmap; + _hueRedBitmap = _minBitmap; _hueYellowBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle1PixelData, pixelWidth, pixelHeight); _hueGreenBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle2PixelData, pixelWidth, pixelHeight); _hueCyanBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle3PixelData, pixelWidth, pixelHeight); _hueBlueBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle4PixelData, pixelWidth, pixelHeight); - _huePurpleBitmap = maxBitmap; + _huePurpleBitmap = _maxBitmap; break; } From 667b02d2c0d6e713980f0ad05c843d96e0c5a6f4 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 2 Oct 2022 14:33:24 -0400 Subject: [PATCH 09/84] Disable WriteableBitmap re-use in ColorSlider due to crashes --- .../ColorSlider/ColorSlider.cs | 8 +++++++- .../Helpers/ColorPickerHelpers.cs | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index 6f20238b70..8b624958dd 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -135,10 +135,16 @@ namespace Avalonia.Controls.Primitives { if (_backgroundBitmap != null) { + // CURRENTLY DISABLED DUE TO INTERMITTENT CRASHES IN SKIA/RENDERER + // // Re-use the existing WriteableBitmap // This assumes the height, width and byte counts are the same and must be set to null // elsewhere if that assumption is ever not true. - ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bgraPixelData); + // ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bgraPixelData); + + // ALSO DISABLED DISPOSE DUE TO INTERMITTENT CRASHES + //_backgroundBitmap?.Dispose(); + _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight); } else { diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs index 2378813f52..01e5a0868f 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs @@ -627,6 +627,7 @@ namespace Avalonia.Controls.Primitives /// /// Updates the given with new, raw BGRA pre-multiplied alpha pixel data. + /// WARNING: THIS METHOD IS CURRENTLY PROVIDED AS REFERENCE BUT CAUSES INTERMITTENT CRASHES IF USED. /// WARNING: The bitmap's width, height and byte count MUST not have changed and MUST be enforced externally. /// /// The existing to update. From bc927d03129fa201248d2bcab78143f7a1c8f93f Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 9 Oct 2022 21:07:58 -0400 Subject: [PATCH 10/84] Remove ColorSlider background disposal on attach/detach from visual tree --- .../ColorSlider/ColorSlider.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index 8b624958dd..0b9f7f9146 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -45,20 +45,12 @@ namespace Avalonia.Controls.Primitives protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); - - // Bitmaps were released when detached from the visual tree so they must be re-built - UpdateBackground(); } /// protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); - - // Clean-up all bitmaps - // https://github.com/AvaloniaUI/Avalonia/issues/9051 - _backgroundBitmap?.Dispose(); - _backgroundBitmap = null; } /// From 75b3abbdd86720e2e688687283b49ef95e2ca63b Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 10 Oct 2022 18:25:44 +0200 Subject: [PATCH 11/84] Properly arrange Embedded controls during ArrangeOverride --- .../Documents/IInlineHost.cs | 2 - src/Avalonia.Controls/Documents/InlineRun.cs | 42 +++++++++ .../Documents/InlineUIContainer.cs | 48 +--------- src/Avalonia.Controls/RichTextBlock.cs | 87 ++++++++++++------- .../RichTextBlockTests.cs | 36 ++++++++ 5 files changed, 137 insertions(+), 78 deletions(-) create mode 100644 src/Avalonia.Controls/Documents/InlineRun.cs diff --git a/src/Avalonia.Controls/Documents/IInlineHost.cs b/src/Avalonia.Controls/Documents/IInlineHost.cs index da72c207be..5d142952ab 100644 --- a/src/Avalonia.Controls/Documents/IInlineHost.cs +++ b/src/Avalonia.Controls/Documents/IInlineHost.cs @@ -4,8 +4,6 @@ namespace Avalonia.Controls.Documents { internal interface IInlineHost : ILogical { - void AddVisualChild(IControl child); - void Invalidate(); } } diff --git a/src/Avalonia.Controls/Documents/InlineRun.cs b/src/Avalonia.Controls/Documents/InlineRun.cs new file mode 100644 index 0000000000..68c61ca3fa --- /dev/null +++ b/src/Avalonia.Controls/Documents/InlineRun.cs @@ -0,0 +1,42 @@ +using Avalonia.Media; +using Avalonia.Media.TextFormatting; +using Avalonia.Utilities; + +namespace Avalonia.Controls.Documents +{ + internal class EmbeddedControlRun : DrawableTextRun + { + public EmbeddedControlRun(IControl control, TextRunProperties properties) + { + Control = control; + Properties = properties; + } + + public IControl Control { get; } + + public override TextRunProperties? Properties { get; } + + public override Size Size => Control.DesiredSize; + + public override double Baseline + { + get + { + double baseline = Size.Height; + double baselineOffsetValue = Control.GetValue(TextBlock.BaselineOffsetProperty); + + if (!MathUtilities.IsZero(baselineOffsetValue)) + { + baseline = baselineOffsetValue; + } + + return -baseline; + } + } + + public override void Draw(DrawingContext drawingContext, Point origin) + { + //noop + } + } +} diff --git a/src/Avalonia.Controls/Documents/InlineUIContainer.cs b/src/Avalonia.Controls/Documents/InlineUIContainer.cs index d632e5fea7..7107fb7fed 100644 --- a/src/Avalonia.Controls/Documents/InlineUIContainer.cs +++ b/src/Avalonia.Controls/Documents/InlineUIContainer.cs @@ -3,7 +3,6 @@ using System.Text; using Avalonia.Media; using Avalonia.Media.TextFormatting; using Avalonia.Metadata; -using Avalonia.Utilities; namespace Avalonia.Controls.Documents { @@ -59,56 +58,11 @@ namespace Avalonia.Controls.Documents internal override void BuildTextRun(IList textRuns) { - if(InlineHost == null) - { - return; - } - - ((ISetLogicalParent)Child).SetParent(InlineHost); - - InlineHost.AddVisualChild(Child); - - textRuns.Add(new InlineRun(Child, CreateTextRunProperties())); + textRuns.Add(new EmbeddedControlRun(Child, CreateTextRunProperties())); } internal override void AppendText(StringBuilder stringBuilder) { } - - private class InlineRun : DrawableTextRun - { - public InlineRun(IControl control, TextRunProperties properties) - { - Control = control; - Properties = properties; - } - - public IControl Control { get; } - - public override TextRunProperties? Properties { get; } - - public override Size Size => Control.DesiredSize; - - public override double Baseline - { - get - { - double baseline = Size.Height; - double baselineOffsetValue = Control.GetValue(TextBlock.BaselineOffsetProperty); - - if (!MathUtilities.IsZero(baselineOffsetValue)) - { - baseline = baselineOffsetValue; - } - - return -baseline; - } - } - - public override void Draw(DrawingContext drawingContext, Point origin) - { - //noop - } - } } } diff --git a/src/Avalonia.Controls/RichTextBlock.cs b/src/Avalonia.Controls/RichTextBlock.cs index 906a038ec3..a7eee504ec 100644 --- a/src/Avalonia.Controls/RichTextBlock.cs +++ b/src/Avalonia.Controls/RichTextBlock.cs @@ -61,6 +61,7 @@ namespace Avalonia.Controls private int _selectionStart; private int _selectionEnd; private int _wordSelectionStart = -1; + private IReadOnlyList? _textRuns; static RichTextBlock() { @@ -277,8 +278,8 @@ namespace Avalonia.Controls protected override void SetText(string? text) { var oldValue = GetText(); - - AddText(text); + + AddText(text); RaisePropertyChanged(TextProperty, oldValue, text); } @@ -301,18 +302,9 @@ namespace Avalonia.Controls ITextSource textSource; - if (HasComplexContent) + if (_textRuns != null) { - var inlines = Inlines!; - - var textRuns = new List(); - - foreach (var inline in inlines) - { - inline.BuildTextRun(textRuns); - } - - textSource = new InlinesTextSource(textRuns); + textSource = new InlinesTextSource(_textRuns); } else { @@ -546,27 +538,72 @@ namespace Avalonia.Controls protected override Size MeasureOverride(Size availableSize) { - foreach (var child in VisualChildren) + LogicalChildren.Clear(); + + VisualChildren.Clear(); + + if (Inlines != null && Inlines.Count > 0) { - if (child is Control control) + var inlines = Inlines; + + var textRuns = new List(); + + foreach (var inline in inlines) { - control.Measure(Size.Infinity); + inline.BuildTextRun(textRuns); + } + + foreach (var textRun in textRuns) + { + if (textRun is EmbeddedControlRun controlRun && + controlRun.Control is Control control) + { + ((ISetLogicalParent)control).SetParent(this); + + VisualChildren.Add(control); + + control.Measure(Size.Infinity); + } } + + _textRuns = textRuns; + } + else + { + _textRuns = null; } - + return base.MeasureOverride(availableSize); } protected override Size ArrangeOverride(Size finalSize) { - foreach (var child in VisualChildren) + if (HasComplexContent) { - if (child is Control control) + var currentY = 0.0; + + foreach (var textLine in TextLayout.TextLines) { - control.Arrange(new Rect(control.DesiredSize)); + var currentX = textLine.Start; + + foreach (var run in textLine.TextRuns) + { + if (run is DrawableTextRun drawable) + { + if (drawable is EmbeddedControlRun controlRun + && controlRun.Control is Control control) + { + control.Arrange(new Rect(new Point(currentX, currentY), control.DesiredSize)); + } + + currentX += drawable.Size.Width; + } + } + + currentY += textLine.Height; } } - + return base.ArrangeOverride(finalSize); } @@ -618,14 +655,6 @@ namespace Avalonia.Controls } } - void IInlineHost.AddVisualChild(IControl child) - { - if (child.VisualParent == null) - { - VisualChildren.Add(child); - } - } - void IInlineHost.Invalidate() { InvalidateTextLayout(); diff --git a/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs b/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs index c74f13b808..05007e4f2e 100644 --- a/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs +++ b/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs @@ -1,4 +1,6 @@ using Avalonia.Controls.Documents; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Templates; using Avalonia.Media; using Avalonia.UnitTests; using Xunit; @@ -92,5 +94,39 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(target, run.Parent); } } + + [Fact] + public void InlineUIContainer_Child_Schould_Be_Arranged() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var target = new RichTextBlock(); + + var button = new Button { Content = "12345678" }; + + button.Template = new FuncControlTemplate public bool IsFixedPitch => PlatformImpl.IsFixedPitch; + /// + /// Gets the number of glyphs held by this glyph typeface. + /// + public int GlyphCount => PlatformImpl.GlyphCount; + /// /// Returns an glyph index for the specified codepoint. /// diff --git a/src/Avalonia.Base/Platform/IGlyphRunBuffer.cs b/src/Avalonia.Base/Platform/IGlyphRunBuffer.cs new file mode 100644 index 0000000000..c1fc7a5967 --- /dev/null +++ b/src/Avalonia.Base/Platform/IGlyphRunBuffer.cs @@ -0,0 +1,22 @@ +using System; +using System.Drawing; + +namespace Avalonia.Platform +{ + public interface IGlyphRunBuffer + { + Span GlyphIndices { get; } + + IGlyphRunImpl Build(); + } + + public interface IHorizontalGlyphRunBuffer : IGlyphRunBuffer + { + Span GlyphPositions { get; } + } + + public interface IPositionedGlyphRunBuffer : IGlyphRunBuffer + { + Span GlyphPositions { get; } + } +} diff --git a/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs b/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs index 415f34fb29..5a38e133ff 100644 --- a/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs +++ b/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs @@ -51,6 +51,11 @@ namespace Avalonia.Platform /// bool IsFixedPitch { get; } + /// + /// Gets the number of glyphs held by this glyph typeface. + /// + int GlyphCount { get; } + /// /// Returns an glyph index for the specified codepoint. /// diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs index e39a4e23df..93105b21a4 100644 --- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs @@ -171,11 +171,40 @@ namespace Avalonia.Platform IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride); /// - /// Creates a platform implementation of a glyph run. + /// Allocates a platform glyph run buffer. /// - /// The glyph run. - /// - IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun); + /// The glyph typeface. + /// The font rendering em size. + /// The length. + /// An . + /// + /// This buffer only holds glyph indices. + /// + IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); + + /// + /// Allocates a horizontal platform glyph run buffer. + /// + /// The glyph typeface. + /// The font rendering em size. + /// The length. + /// An . + /// + /// This buffer holds glyph indices and glyph advances. + /// + IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); + + /// + /// Allocates a positioned platform glyph run buffer. + /// + /// The glyph typeface. + /// The font rendering em size. + /// The length. + /// An . + /// + /// This buffer holds glyph indices, glyph advances and glyph positions. + /// + IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); /// /// Gets a value indicating whether the platform directly supports rectangles with rounded corners. diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index cb23c6c336..12a9a016f3 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -110,14 +110,24 @@ namespace Avalonia.Headless return new HeadlessBitmapStub(destinationSize, new Vector(96, 96)); } - public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) + public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) { - return new HeadlessGlyphRunStub(); + return new HeadlessGeometryStub(new Rect(glyphRun.Size)); } - public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) + public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { - return new HeadlessGeometryStub(new Rect(glyphRun.Size)); + return new HeadlessGlyphRunBufferStub(); + } + + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return new HeadlessHorizontalGlyphRunBufferStub(); + } + + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return new HeadlessPositionedGlyphRunBufferStub(); } class HeadlessGeometryStub : IGeometryImpl @@ -203,6 +213,26 @@ namespace Avalonia.Headless public Matrix Transform { get; } } + class HeadlessGlyphRunBufferStub : IGlyphRunBuffer + { + public Span GlyphIndices => Span.Empty; + + public IGlyphRunImpl Build() + { + return new HeadlessGlyphRunStub(); + } + } + + class HeadlessHorizontalGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IHorizontalGlyphRunBuffer + { + public Span GlyphPositions => Span.Empty; + } + + class HeadlessPositionedGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IPositionedGlyphRunBuffer + { + public Span GlyphPositions => Span.Empty; + } + class HeadlessGlyphRunStub : IGlyphRunImpl { public void Dispose() diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index ed51529a96..b3bf92f06b 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -95,6 +95,8 @@ namespace Avalonia.Headless public bool IsFixedPitch => true; + public int GlyphCount => 1337; + public void Dispose() { } diff --git a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs index dcb4eac7ca..a3174010a1 100644 --- a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs +++ b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs @@ -55,6 +55,8 @@ namespace Avalonia.Skia IsFixedPitch = Typeface.IsFixedPitch; + GlyphCount = Typeface.GlyphCount; + IsFakeBold = isFakeBold; IsFakeItalic = isFakeItalic; @@ -94,6 +96,8 @@ namespace Avalonia.Skia /// public bool IsFixedPitch { get; } + + public int GlyphCount { get; } public bool IsFakeBold { get; } diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 91fe4fc085..aa9225ba64 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -1,7 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; -using System.IO; +using System.IO; using System.Linq; using System.Threading; @@ -12,6 +12,8 @@ using Avalonia.OpenGL.Imaging; using Avalonia.Platform; using Avalonia.Media.Imaging; using SkiaSharp; +using System.Runtime.InteropServices; +using System.Drawing; namespace Avalonia.Skia { @@ -33,13 +35,17 @@ namespace Avalonia.Skia } var gl = AvaloniaLocator.Current.GetService(); - if (gl != null) + if (gl != null) _skiaGpu = new GlSkiaGpu(gl, maxResourceBytes); - - //TODO: SKFont crashes when disposed in finalizer so we keep it alive - GC.SuppressFinalize(s_font); } + + public bool SupportsIndividualRoundRects => true; + + public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul; + + public PixelFormat DefaultPixelFormat { get; } + public IGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect); public IGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2); @@ -228,133 +234,95 @@ namespace Avalonia.Skia return new WriteableBitmapImpl(size, dpi, format, alphaFormat); } - private static readonly SKFont s_font = new SKFont - { - Subpixel = true, - Edging = SKFontEdging.SubpixelAntialias, - Hinting = SKFontHinting.Full, - LinearMetrics = true - }; - - private static readonly ThreadLocal s_textBlobBuilderThreadLocal = new ThreadLocal(() => new SKTextBlobBuilder()); - - /// - public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) + public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) { - var count = glyphRun.GlyphIndices.Count; - var textBlobBuilder = s_textBlobBuilderThreadLocal.Value; - - var glyphTypeface = (GlyphTypefaceImpl)glyphRun.GlyphTypeface.PlatformImpl; + if (_skiaGpu is IOpenGlAwareSkiaGpu glAware) + return glAware.CreateOpenGlBitmap(size, dpi); + if (_skiaGpu == null) + throw new PlatformNotSupportedException("GPU acceleration is not available"); + throw new PlatformNotSupportedException( + "Current GPU acceleration backend does not support OpenGL integration"); + } - var typeface = glyphTypeface.Typeface; + public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + => new SKGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); - s_font.Size = (float)glyphRun.FontRenderingEmSize; - s_font.Typeface = typeface; - s_font.Embolden = glyphTypeface.IsFakeBold; - s_font.SkewX = glyphTypeface.IsFakeItalic ? -0.2f : 0; + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + => new SKHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); - SKTextBlob textBlob; + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + => new SKPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); - var scale = (float)(glyphRun.FontRenderingEmSize / glyphTypeface.DesignEmHeight); + private abstract class SKGlyphRunBufferBase : IGlyphRunBuffer + { + protected readonly SKTextBlobBuilder _builder; + protected readonly SKFont _font; - if (glyphRun.GlyphOffsets == null) + public SKGlyphRunBufferBase(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { - if (glyphTypeface.IsFixedPitch) - { - var buffer = textBlobBuilder.AllocateRun(s_font, glyphRun.GlyphIndices.Count, 0, 0); - - var glyphs = buffer.GetGlyphSpan(); + _builder = new SKTextBlobBuilder(); - for (int i = 0; i < glyphs.Length; i++) - { - glyphs[i] = glyphRun.GlyphIndices[i]; - } + var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface.PlatformImpl; - textBlob = textBlobBuilder.Build(); - } - else + _font = new SKFont { - var buffer = textBlobBuilder.AllocateHorizontalRun(s_font, count, 0); - - var positions = buffer.GetPositionSpan(); - - var width = 0d; - - for (var i = 0; i < count; i++) - { - positions[i] = (float)width; - - if (glyphRun.GlyphAdvances == null) - { - width += glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]) * scale; - } - else - { - width += glyphRun.GlyphAdvances[i]; - } - } - - var glyphs = buffer.GetGlyphSpan(); + Subpixel = true, + Edging = SKFontEdging.SubpixelAntialias, + Hinting = SKFontHinting.Full, + LinearMetrics = true, + Size = fontRenderingEmSize, + Typeface = glyphTypefaceImpl.Typeface, + Embolden = glyphTypefaceImpl.IsFakeBold, + SkewX = glyphTypefaceImpl.IsFakeItalic ? -0.2f : 0 + }; + } - for (int i = 0; i < glyphs.Length; i++) - { - glyphs[i] = glyphRun.GlyphIndices[i]; - } + public abstract Span GlyphIndices { get; } - textBlob = textBlobBuilder.Build(); - } - } - else + public IGlyphRunImpl Build() { - var buffer = textBlobBuilder.AllocatePositionedRun(s_font, count); - - var glyphPositions = buffer.GetPositionSpan(); + return new GlyphRunImpl(_builder.Build()); + } + } - var currentX = 0.0; + private sealed class SKGlyphRunBuffer : SKGlyphRunBufferBase + { + private readonly SKRunBuffer _buffer; - for (var i = 0; i < count; i++) - { - var glyphOffset = glyphRun.GlyphOffsets[i]; - - glyphPositions[i] = new SKPoint((float)(currentX + glyphOffset.X), (float)glyphOffset.Y); - - if (glyphRun.GlyphAdvances == null) - { - currentX += glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]) * scale; - } - else - { - currentX += glyphRun.GlyphAdvances[i]; - } - } + public SKGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) + { + _buffer = _builder.AllocateRun(_font, length, 0, 0); + } - var glyphs = buffer.GetGlyphSpan(); + public override Span GlyphIndices => _buffer.GetGlyphSpan(); + } - for (int i = 0; i < glyphs.Length; i++) - { - glyphs[i] = glyphRun.GlyphIndices[i]; - } + private sealed class SKHorizontalGlyphRunBuffer : SKGlyphRunBufferBase, IHorizontalGlyphRunBuffer + { + private readonly SKHorizontalRunBuffer _buffer; - textBlob = textBlobBuilder.Build(); + public SKHorizontalGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) + { + _buffer = _builder.AllocateHorizontalRun(_font, length, 0); } - return new GlyphRunImpl(textBlob); + public override Span GlyphIndices => _buffer.GetGlyphSpan(); + + public Span GlyphPositions => _buffer.GetPositionSpan(); } - public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) + private sealed class SKPositionedGlyphRunBuffer : SKGlyphRunBufferBase, IPositionedGlyphRunBuffer { - if (_skiaGpu is IOpenGlAwareSkiaGpu glAware) - return glAware.CreateOpenGlBitmap(size, dpi); - if (_skiaGpu == null) - throw new PlatformNotSupportedException("GPU acceleration is not available"); - throw new PlatformNotSupportedException( - "Current GPU acceleration backend does not support OpenGL integration"); - } + private readonly SKPositionedRunBuffer _buffer; - public bool SupportsIndividualRoundRects => true; + public SKPositionedGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) + { + _buffer = _builder.AllocatePositionedRun(_font, length); + } - public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul; + public override Span GlyphIndices => _buffer.GetGlyphSpan(); - public PixelFormat DefaultPixelFormat { get; } + public Span GlyphPositions => MemoryMarshal.Cast(_buffer.GetPositionSpan()); + } } } diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 81fa8c4bce..45744e6efa 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -12,6 +12,8 @@ using SharpDX.DirectWrite; using GlyphRun = Avalonia.Media.GlyphRun; using TextAlignment = Avalonia.Media.TextAlignment; using SharpDX.Mathematics.Interop; +using System.Runtime.InteropServices; +using System.Drawing; namespace Avalonia { @@ -258,69 +260,66 @@ namespace Avalonia.Direct2D1 return new WicBitmapImpl(format, alphaFormat, data, size, dpi, stride); } - public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) + private class DWGlyphRunBuffer : IGlyphRunBuffer { - var glyphTypeface = (GlyphTypefaceImpl)glyphRun.GlyphTypeface.PlatformImpl; + protected readonly SharpDX.DirectWrite.GlyphRun _dwRun; - var glyphCount = glyphRun.GlyphIndices.Count; - - var run = new SharpDX.DirectWrite.GlyphRun + public DWGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { - FontFace = glyphTypeface.FontFace, - FontSize = (float)glyphRun.FontRenderingEmSize - }; - - var indices = new short[glyphCount]; + var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface.PlatformImpl; - for (var i = 0; i < glyphCount; i++) - { - indices[i] = (short)glyphRun.GlyphIndices[i]; + _dwRun = new SharpDX.DirectWrite.GlyphRun + { + FontFace = glyphTypefaceImpl.FontFace, + FontSize = fontRenderingEmSize, + Indices = new short[length] + }; } - run.Indices = indices; - - run.Advances = new float[glyphCount]; + public Span GlyphIndices => MemoryMarshal.Cast(_dwRun.Indices.AsSpan()); - var scale = (float)(glyphRun.FontRenderingEmSize / glyphTypeface.DesignEmHeight); - - if (glyphRun.GlyphAdvances == null) + public IGlyphRunImpl Build() { - for (var i = 0; i < glyphCount; i++) - { - var advance = glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]) * scale; - - run.Advances[i] = advance; - } + return new GlyphRunImpl(_dwRun); } - else - { - for (var i = 0; i < glyphCount; i++) - { - var advance = (float)glyphRun.GlyphAdvances[i]; + } - run.Advances[i] = advance; - } + private class DWHorizontalGlyphRunBuffer : DWGlyphRunBuffer, IHorizontalGlyphRunBuffer + { + public DWHorizontalGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + : base(glyphTypeface, fontRenderingEmSize, length) + { + _dwRun.Advances = new float[length]; } - if (glyphRun.GlyphOffsets == null) + public Span GlyphPositions => _dwRun.Advances.AsSpan(); + } + + private class DWPositionedGlyphRunBuffer : DWGlyphRunBuffer, IPositionedGlyphRunBuffer + { + public DWPositionedGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + : base(glyphTypeface, fontRenderingEmSize, length) { - return new GlyphRunImpl(run); + _dwRun.Advances = new float[length]; + _dwRun.Offsets = new GlyphOffset[length]; } - run.Offsets = new GlyphOffset[glyphCount]; + public Span GlyphPositions => MemoryMarshal.Cast(_dwRun.Offsets.AsSpan()); + } - for (var i = 0; i < glyphCount; i++) - { - var (x, y) = glyphRun.GlyphOffsets[i]; + public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return new DWGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); + } - run.Offsets[i] = new GlyphOffset - { - AdvanceOffset = (float)x, - AscenderOffset = (float)y - }; - } + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return new DWHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); + } - return new GlyphRunImpl(run); + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return new DWPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); } public bool SupportsIndividualRoundRects => false; diff --git a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs index 4154b44702..443bb8a207 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs @@ -117,6 +117,8 @@ namespace Avalonia.Direct2D1.Media /// public bool IsFixedPitch { get; } + public int GlyphCount { get; set; } + /// public ushort GetGlyph(uint codepoint) { diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs index 1f0b82b465..d21879f349 100644 --- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs @@ -126,6 +126,21 @@ namespace Avalonia.Base.UnitTests.VisualTree throw new NotImplementedException(); } + public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + throw new NotImplementedException(); + } + + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + throw new NotImplementedException(); + } + + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + throw new NotImplementedException(); + } + class MockStreamGeometry : IStreamGeometryImpl { private MockStreamGeometryContext _impl = new MockStreamGeometryContext(); diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index 0193f5d772..a2c2936a4d 100644 --- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs +++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs @@ -112,12 +112,22 @@ namespace Avalonia.Benchmarks return new MockFontManagerImpl(); } - public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) + public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) { - return new NullGlyphRun(); + return new MockStreamGeometryImpl(); } - public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) + public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + throw new NotImplementedException(); + } + + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + throw new NotImplementedException(); + } + + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { throw new NotImplementedException(); } diff --git a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs index 32e0434cd4..04f316e5b4 100644 --- a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs +++ b/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs @@ -44,6 +44,8 @@ namespace Avalonia.UnitTests IsFixedPitch = GetGlyphAdvance(GetGlyph('a')) == GetGlyphAdvance(GetGlyph('b')); + GlyphCount = Face.GlyphCount; + IsFakeBold = isFakeBold; IsFakeItalic = isFakeItalic; @@ -79,6 +81,8 @@ namespace Avalonia.UnitTests /// public bool IsFixedPitch { get; } + + public int GlyphCount { get; set; } public bool IsFakeBold { get; } diff --git a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs b/tests/Avalonia.UnitTests/MockGlyphTypeface.cs index c9c59a6e32..0b7beeb4fb 100644 --- a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs +++ b/tests/Avalonia.UnitTests/MockGlyphTypeface.cs @@ -14,6 +14,7 @@ namespace Avalonia.UnitTests public int StrikethroughPosition { get; } public int StrikethroughThickness { get; } public bool IsFixedPitch { get; } + public int GlyphCount => 1337; public ushort GetGlyph(uint codepoint) { diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index f6a127b573..314cc95041 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -152,6 +152,21 @@ namespace Avalonia.UnitTests return Mock.Of(); } + public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return Mock.Of(); + } + + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return Mock.Of(); + } + + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return Mock.Of(); + } + public bool SupportsIndividualRoundRects { get; set; } public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul; From 5a5080b1a5698456961624de63af0c0e4ba203ec Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Wed, 12 Oct 2022 14:14:46 +0200 Subject: [PATCH 14/84] Rework GlyphTypeface --- samples/RenderDemo/Pages/GlyphRunPage.xaml.cs | 4 +- src/Avalonia.Base/Media/FontManager.cs | 12 +- src/Avalonia.Base/Media/FontMetrics.cs | 63 +++++++++ src/Avalonia.Base/Media/GlyphRun.cs | 18 +-- src/Avalonia.Base/Media/GlyphTypeface.cs | 130 ------------------ .../IGlyphTypeface.cs} | 73 ++++------ src/Avalonia.Base/Media/TextDecoration.cs | 16 +-- .../Media/TextFormatting/ShapedBuffer.cs | 6 +- .../TextFormatting/ShapedTextCharacters.cs | 9 +- .../Media/TextFormatting/TextLineImpl.cs | 26 ++-- .../{FontMetrics.cs => TextMetrics.cs} | 24 ++-- .../Media/TextFormatting/TextShaperOptions.cs | 4 +- .../Media/TextFormatting/Unicode/Codepoint.cs | 5 +- src/Avalonia.Base/Media/Typeface.cs | 2 +- .../Platform/IFontManagerImpl.cs | 2 +- .../Platform/IPlatformRenderInterface.cs | 6 +- .../Composition/Server/FpsCounter.cs | 2 +- src/Avalonia.Base/Utilities/ReadOnlySlice.cs | 2 + .../HeadlessPlatformStubs.cs | 22 ++- src/Skia/Avalonia.Skia/FontManagerImpl.cs | 2 +- src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs | 92 +++++-------- .../Avalonia.Skia/PlatformRenderInterface.cs | 18 +-- .../Media/FontManagerImpl.cs | 2 +- .../Media/GlyphTypefaceImpl.cs | 105 ++++++-------- .../Media/CustomFontManagerImpl.cs | 2 +- .../TextFormatting/TextFormatterTests.cs | 2 +- .../Media/TextFormatting/TextLayoutTests.cs | 4 +- .../HarfBuzzFontManagerImpl.cs | 2 +- .../HarfBuzzGlyphTypefaceImpl.cs | 96 ++++++------- .../Avalonia.UnitTests/MockFontManagerImpl.cs | 2 +- tests/Avalonia.UnitTests/MockGlyphTypeface.cs | 34 +++-- 31 files changed, 352 insertions(+), 435 deletions(-) create mode 100644 src/Avalonia.Base/Media/FontMetrics.cs delete mode 100644 src/Avalonia.Base/Media/GlyphTypeface.cs rename src/Avalonia.Base/{Platform/IGlyphTypefaceImpl.cs => Media/IGlyphTypeface.cs} (53%) rename src/Avalonia.Base/Media/TextFormatting/{FontMetrics.cs => TextMetrics.cs} (69%) diff --git a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs index 674ed8e61f..5c31e138e0 100644 --- a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs +++ b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs @@ -22,7 +22,7 @@ namespace RenderDemo.Pages public class GlyphRunControl : Control { - private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface; + private IGlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface; private readonly Random _rand = new Random(); private ushort[] _glyphIndices = new ushort[1]; private char[] _characters = new char[1]; @@ -81,7 +81,7 @@ namespace RenderDemo.Pages public class GlyphRunGeometryControl : Control { - private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface; + private IGlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface; private readonly Random _rand = new Random(); private ushort[] _glyphIndices = new ushort[1]; private char[] _characters = new char[1]; diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs index 37091b82e3..dcb29ffebf 100644 --- a/src/Avalonia.Base/Media/FontManager.cs +++ b/src/Avalonia.Base/Media/FontManager.cs @@ -13,8 +13,8 @@ namespace Avalonia.Media /// public sealed class FontManager { - private readonly ConcurrentDictionary _glyphTypefaceCache = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _glyphTypefaceCache = + new ConcurrentDictionary(); private readonly FontFamily _defaultFontFamily; private readonly IReadOnlyList? _fontFallbacks; @@ -81,13 +81,13 @@ namespace Avalonia.Media PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates); /// - /// Returns a new , or an existing one if a matching exists. + /// Returns a new , or an existing one if a matching exists. /// /// The typeface. /// - /// The . + /// The . /// - public GlyphTypeface GetOrAddGlyphTypeface(Typeface typeface) + public IGlyphTypeface GetOrAddGlyphTypeface(Typeface typeface) { while (true) { @@ -96,7 +96,7 @@ namespace Avalonia.Media return glyphTypeface; } - glyphTypeface = new GlyphTypeface(typeface); + glyphTypeface = PlatformImpl.CreateGlyphTypeface(typeface); if (_glyphTypefaceCache.TryAdd(typeface, glyphTypeface)) { diff --git a/src/Avalonia.Base/Media/FontMetrics.cs b/src/Avalonia.Base/Media/FontMetrics.cs new file mode 100644 index 0000000000..3924119a3e --- /dev/null +++ b/src/Avalonia.Base/Media/FontMetrics.cs @@ -0,0 +1,63 @@ +namespace Avalonia.Media +{ + /// + /// The font metrics is holding information about a font's ascent, descent, etc. in design em units. + /// + public readonly struct FontMetrics + { + /// + /// Gets the font design units per em. + /// + public short DesignEmHeight { get; init; } + + /// + /// A value indicating whether all glyphs in the font have the same advancement. + /// + public bool IsFixedPitch { get; init; } + + /// + /// Gets the recommended distance above the baseline in design em size. + /// + public int Ascent { get; init; } + + /// + /// Gets the recommended distance under the baseline in design em size. + /// + public int Descent { get; init; } + + /// + /// Gets the recommended additional space between two lines of text in design em size. + /// + public int LineGap { get; init; } + + /// + /// Gets the recommended line spacing of a formed text line. + /// + public int LineSpacing => Descent - Ascent + LineGap; + + /// + /// Gets a value that indicates the distance of the underline from the baseline in design em size. + /// + public int UnderlinePosition { get; init; } + + /// + /// Gets a value that indicates the thickness of the underline in design em size. + /// + public int UnderlineThickness { get; init; } + + /// + /// Gets a value that indicates the distance of the strikethrough from the baseline in design em size. + /// + public int StrikethroughPosition { get; init; } + + /// + /// Gets a value that indicates the thickness of the underline in design em size. + /// + public int StrikethroughThickness { get; init; } + } +} + +namespace System.Runtime.CompilerServices +{ + public class IsExternalInit { } +} diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index ce436ee017..a1cb00e209 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -16,7 +16,7 @@ namespace Avalonia.Media private static readonly IComparer s_descendingComparer = new ReverseComparer(); private IGlyphRunImpl? _glyphRunImpl; - private GlyphTypeface _glyphTypeface; + private IGlyphTypeface _glyphTypeface; private double _fontRenderingEmSize; private int _biDiLevel; private Point? _baselineOrigin; @@ -43,7 +43,7 @@ namespace Avalonia.Media /// The glyph clusters. /// The bidi level. public GlyphRun( - GlyphTypeface glyphTypeface, + IGlyphTypeface glyphTypeface, double fontRenderingEmSize, ReadOnlySlice characters, IReadOnlyList glyphIndices, @@ -70,9 +70,9 @@ namespace Avalonia.Media } /// - /// Gets the for the . + /// Gets the for the . /// - public GlyphTypeface GlyphTypeface => _glyphTypeface; + public IGlyphTypeface GlyphTypeface => _glyphTypeface; /// /// Gets or sets the em size used for rendering the . @@ -172,7 +172,7 @@ namespace Avalonia.Media /// /// Gets the scale of the current /// - internal double Scale => FontRenderingEmSize / GlyphTypeface.DesignEmHeight; + internal double Scale => FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight; /// /// Returns true if the text direction is left-to-right. Otherwise, returns false. @@ -613,7 +613,7 @@ namespace Avalonia.Media /// The baseline origin. private Point CalculateBaselineOrigin() { - return new Point(0, -GlyphTypeface.Ascent * Scale); + return new Point(0, -GlyphTypeface.Metrics.Ascent * Scale); } private GlyphRunMetrics CreateGlyphRunMetrics() @@ -637,7 +637,7 @@ namespace Avalonia.Media } var isReversed = firstCluster > lastCluster; - var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale; + var height = GlyphTypeface.Metrics.LineSpacing * Scale; var widthIncludingTrailingWhitespace = 0d; var trailingWhitespaceLength = GetTrailingWhitespaceLength(isReversed, out var newLineLength, out var glyphCount); @@ -864,11 +864,11 @@ namespace Avalonia.Media var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService(); var count = GlyphIndices.Count; - var scale = (float)(FontRenderingEmSize / GlyphTypeface.DesignEmHeight); + var scale = (float)(FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight); if (GlyphOffsets == null) { - if (GlyphTypeface.IsFixedPitch) + if (GlyphTypeface.Metrics.IsFixedPitch) { var buffer = platformRenderInterface.AllocateGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count); diff --git a/src/Avalonia.Base/Media/GlyphTypeface.cs b/src/Avalonia.Base/Media/GlyphTypeface.cs deleted file mode 100644 index 36b6f3a5f8..0000000000 --- a/src/Avalonia.Base/Media/GlyphTypeface.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using Avalonia.Platform; - -namespace Avalonia.Media -{ - public sealed class GlyphTypeface : IDisposable - { - public GlyphTypeface(Typeface typeface) - : this(FontManager.Current.PlatformImpl.CreateGlyphTypeface(typeface)) - { - } - - public GlyphTypeface(IGlyphTypefaceImpl platformImpl) - { - PlatformImpl = platformImpl; - } - - public IGlyphTypefaceImpl PlatformImpl { get; } - - /// - /// Gets the font design units per em. - /// - public short DesignEmHeight => PlatformImpl.DesignEmHeight; - - /// - /// Gets the recommended distance above the baseline in design em size. - /// - public int Ascent => PlatformImpl.Ascent; - - /// - /// Gets the recommended distance under the baseline in design em size. - /// - public int Descent => PlatformImpl.Descent; - - /// - /// Gets the recommended additional space between two lines of text in design em size. - /// - public int LineGap => PlatformImpl.LineGap; - - /// - /// Gets the recommended line height. - /// - public int LineHeight => Descent - Ascent + LineGap; - - /// - /// Gets a value that indicates the distance of the underline from the baseline in design em size. - /// - public int UnderlinePosition => PlatformImpl.UnderlinePosition; - - /// - /// Gets a value that indicates the thickness of the underline in design em size. - /// - public int UnderlineThickness => PlatformImpl.UnderlineThickness; - - /// - /// Gets a value that indicates the distance of the strikethrough from the baseline in design em size. - /// - public int StrikethroughPosition => PlatformImpl.StrikethroughPosition; - - /// - /// Gets a value that indicates the thickness of the underline in design em size. - /// - public int StrikethroughThickness => PlatformImpl.StrikethroughThickness; - - /// - /// A value indicating whether all glyphs in the font have the same advancement. - /// - public bool IsFixedPitch => PlatformImpl.IsFixedPitch; - - /// - /// Gets the number of glyphs held by this glyph typeface. - /// - public int GlyphCount => PlatformImpl.GlyphCount; - - /// - /// Returns an glyph index for the specified codepoint. - /// - /// - /// Returns a replacement glyph if a glyph isn't found. - /// - /// The codepoint. - /// - /// A glyph index. - /// - public ushort GetGlyph(uint codepoint) => PlatformImpl.GetGlyph(codepoint); - - /// - /// Tries to get an glyph index for specified codepoint. - /// - /// The codepoint. - /// A glyph index. - /// - /// true if an glyph index was found, false otherwise. - /// - public bool TryGetGlyph(uint codepoint, out ushort glyph) - { - glyph = PlatformImpl.GetGlyph(codepoint); - - return glyph != 0; - } - - /// - /// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as 0. - /// - /// The codepoints to map. - /// - public ushort[] GetGlyphs(ReadOnlySpan codepoints) => PlatformImpl.GetGlyphs(codepoints); - - /// - /// Returns the glyph advance for the specified glyph. - /// - /// The glyph. - /// - /// The advance. - /// - public int GetGlyphAdvance(ushort glyph) => PlatformImpl.GetGlyphAdvance(glyph); - - /// - /// Returns an array of glyph advances in design em size. - /// - /// The glyph indices. - /// - public int[] GetGlyphAdvances(ReadOnlySpan glyphs) => PlatformImpl.GetGlyphAdvances(glyphs); - - void IDisposable.Dispose() - { - PlatformImpl?.Dispose(); - } - } -} diff --git a/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs b/src/Avalonia.Base/Media/IGlyphTypeface.cs similarity index 53% rename from src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs rename to src/Avalonia.Base/Media/IGlyphTypeface.cs index 5a38e133ff..de2a2309ee 100644 --- a/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs +++ b/src/Avalonia.Base/Media/IGlyphTypeface.cs @@ -1,60 +1,23 @@ using System; using Avalonia.Metadata; -namespace Avalonia.Platform +namespace Avalonia.Media { [Unstable] - public interface IGlyphTypefaceImpl : IDisposable + public interface IGlyphTypeface : IDisposable { /// - /// Gets the font design units per em. - /// - short DesignEmHeight { get; } - - /// - /// Gets the recommended distance above the baseline in design em size. - /// - int Ascent { get; } - - /// - /// Gets the recommended distance under the baseline in design em size. - /// - int Descent { get; } - - /// - /// Gets the recommended additional space between two lines of text in design em size. - /// - int LineGap { get; } - - /// - /// Gets a value that indicates the distance of the underline from the baseline in design em size. - /// - int UnderlinePosition { get; } - - /// - /// Gets a value that indicates the thickness of the underline in design em size. - /// - int UnderlineThickness { get; } - - /// - /// Gets a value that indicates the distance of the strikethrough from the baseline in design em size. - /// - int StrikethroughPosition { get; } - - /// - /// Gets a value that indicates the thickness of the underline in design em size. - /// - int StrikethroughThickness { get; } - - /// - /// A value indicating whether all glyphs in the font have the same advancement. + /// Gets the number of glyphs held by this glyph typeface. /// - bool IsFixedPitch { get; } + int GlyphCount { get; } /// - /// Gets the number of glyphs held by this glyph typeface. + /// Gets the font metrics. /// - int GlyphCount { get; } + /// + /// The font metrics. + /// + FontMetrics Metrics { get; } /// /// Returns an glyph index for the specified codepoint. @@ -68,6 +31,16 @@ namespace Avalonia.Platform /// ushort GetGlyph(uint codepoint); + /// + /// Tries to get an glyph index for specified codepoint. + /// + /// The codepoint. + /// A glyph index. + /// + /// true if an glyph index was found, false otherwise. + /// + bool TryGetGlyph(uint codepoint, out ushort glyph); + /// /// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as 0. /// @@ -94,5 +67,13 @@ namespace Avalonia.Platform /// An array of glyph advances. /// int[] GetGlyphAdvances(ReadOnlySpan glyphs); + + /// + /// Returns the contents of the table data for the specified tag. + /// + /// The table tag to get the data for. + /// The contents of the table data for the specified tag. + /// Returns true if the content exists, otherwise false. + bool TryGetTable(uint tag, out byte[] table); } } diff --git a/src/Avalonia.Base/Media/TextDecoration.cs b/src/Avalonia.Base/Media/TextDecoration.cs index 4c9764af96..0a7328125a 100644 --- a/src/Avalonia.Base/Media/TextDecoration.cs +++ b/src/Avalonia.Base/Media/TextDecoration.cs @@ -155,9 +155,9 @@ namespace Avalonia.Media /// /// The drawing context. /// The decorated run. - /// The font metrics of the decorated run. + /// The font metrics of the decorated run. /// The default brush that is used to draw the decoration. - internal void Draw(DrawingContext drawingContext, GlyphRun glyphRun, FontMetrics fontMetrics, IBrush defaultBrush) + internal void Draw(DrawingContext drawingContext, GlyphRun glyphRun, TextMetrics textMetrics, IBrush defaultBrush) { var baselineOrigin = glyphRun.BaselineOrigin; var thickness = StrokeThickness; @@ -168,16 +168,16 @@ namespace Avalonia.Media switch (Location) { case TextDecorationLocation.Underline: - thickness = fontMetrics.UnderlineThickness; + thickness = textMetrics.UnderlineThickness; break; case TextDecorationLocation.Strikethrough: - thickness = fontMetrics.StrikethroughThickness; + thickness = textMetrics.StrikethroughThickness; break; } break; case TextDecorationUnit.FontRenderingEmSize: - thickness = fontMetrics.FontRenderingEmSize * thickness; + thickness = textMetrics.FontRenderingEmSize * thickness; break; } @@ -189,17 +189,17 @@ namespace Avalonia.Media origin += glyphRun.BaselineOrigin; break; case TextDecorationLocation.Strikethrough: - origin += new Point(baselineOrigin.X, baselineOrigin.Y + fontMetrics.StrikethroughPosition); + origin += new Point(baselineOrigin.X, baselineOrigin.Y + textMetrics.StrikethroughPosition); break; case TextDecorationLocation.Underline: - origin += new Point(baselineOrigin.X, baselineOrigin.Y + fontMetrics.UnderlinePosition); + origin += new Point(baselineOrigin.X, baselineOrigin.Y + textMetrics.UnderlinePosition); break; } switch (StrokeOffsetUnit) { case TextDecorationUnit.FontRenderingEmSize: - origin += new Point(0, StrokeOffset * fontMetrics.FontRenderingEmSize); + origin += new Point(0, StrokeOffset * textMetrics.FontRenderingEmSize); break; case TextDecorationUnit.Pixel: origin += new Point(0, StrokeOffset); diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs index 47a6334e39..85924a3d32 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs @@ -8,13 +8,13 @@ namespace Avalonia.Media.TextFormatting { private static readonly IComparer s_clusterComparer = new CompareClusters(); - public ShapedBuffer(ReadOnlySlice text, int length, GlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) + public ShapedBuffer(ReadOnlySlice text, int length, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) : this(text, new GlyphInfo[length], glyphTypeface, fontRenderingEmSize, bidiLevel) { } - internal ShapedBuffer(ReadOnlySlice text, ArraySlice glyphInfos, GlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) + internal ShapedBuffer(ReadOnlySlice text, ArraySlice glyphInfos, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) { Text = text; GlyphInfos = glyphInfos; @@ -29,7 +29,7 @@ namespace Avalonia.Media.TextFormatting public int Length => GlyphInfos.Length; - public GlyphTypeface GlyphTypeface { get; } + public IGlyphTypeface GlyphTypeface { get; } public double FontRenderingEmSize { get; } diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs index 53287a264d..21101f462c 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Utilities; @@ -18,7 +17,7 @@ namespace Avalonia.Media.TextFormatting Text = shapedBuffer.Text; Properties = properties; TextSourceLength = Text.Length; - FontMetrics = new FontMetrics(properties.Typeface, properties.FontRenderingEmSize); + TextMetrics = new TextMetrics(properties.Typeface, properties.FontRenderingEmSize); } public bool IsReversed { get; private set; } @@ -36,9 +35,9 @@ namespace Avalonia.Media.TextFormatting /// public override int TextSourceLength { get; } - public FontMetrics FontMetrics { get; } + public TextMetrics TextMetrics { get; } - public override double Baseline => -FontMetrics.Ascent; + public override double Baseline => -TextMetrics.Ascent; public override Size Size => GlyphRun.Size; @@ -89,7 +88,7 @@ namespace Avalonia.Media.TextFormatting foreach (var textDecoration in Properties.TextDecorations) { - textDecoration.Draw(drawingContext, GlyphRun, FontMetrics, Properties.ForegroundBrush); + textDecoration.Draw(drawingContext, GlyphRun, TextMetrics, Properties.ForegroundBrush); } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index aba8008fb9..96f88d1f44 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -1378,17 +1378,17 @@ namespace Avalonia.Media.TextFormatting private TextLineMetrics CreateLineMetrics() { - var glyphTypeface = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface; + var fontMetrics = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface.Metrics; var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize; - var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight; + var scale = fontRenderingEmSize / fontMetrics.DesignEmHeight; var width = 0d; var widthIncludingWhitespace = 0d; var trailingWhitespaceLength = 0; var newLineLength = 0; - var ascent = glyphTypeface.Ascent * scale; - var descent = glyphTypeface.Descent * scale; - var lineGap = glyphTypeface.LineGap * scale; + var ascent = fontMetrics.Ascent * scale; + var descent = fontMetrics.Descent * scale; + var lineGap = fontMetrics.LineGap * scale; var height = descent - ascent + lineGap; @@ -1400,26 +1400,26 @@ namespace Avalonia.Media.TextFormatting { case ShapedTextCharacters textRun: { - var fontMetrics = - new FontMetrics(textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize); + var textMetrics = + new TextMetrics(textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize); if (fontRenderingEmSize < textRun.Properties.FontRenderingEmSize) { fontRenderingEmSize = textRun.Properties.FontRenderingEmSize; - if (ascent > fontMetrics.Ascent) + if (ascent > textMetrics.Ascent) { - ascent = fontMetrics.Ascent; + ascent = textMetrics.Ascent; } - if (descent < fontMetrics.Descent) + if (descent < textMetrics.Descent) { - descent = fontMetrics.Descent; + descent = textMetrics.Descent; } - if (lineGap < fontMetrics.LineGap) + if (lineGap < textMetrics.LineGap) { - lineGap = fontMetrics.LineGap; + lineGap = textMetrics.LineGap; } if (descent - ascent + lineGap > height) diff --git a/src/Avalonia.Base/Media/TextFormatting/FontMetrics.cs b/src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs similarity index 69% rename from src/Avalonia.Base/Media/TextFormatting/FontMetrics.cs rename to src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs index e01bba00a4..0382e66b5a 100644 --- a/src/Avalonia.Base/Media/TextFormatting/FontMetrics.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs @@ -1,33 +1,33 @@ namespace Avalonia.Media.TextFormatting { /// - /// A metric that holds information about font specific measurements. + /// A metric that holds information about text specific measurements. /// - public readonly struct FontMetrics + public readonly struct TextMetrics { - public FontMetrics(Typeface typeface, double fontRenderingEmSize) + public TextMetrics(Typeface typeface, double fontRenderingEmSize) { - var glyphTypeface = typeface.GlyphTypeface; + var fontMetrics = typeface.GlyphTypeface.Metrics; - var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight; + var scale = fontRenderingEmSize / fontMetrics.DesignEmHeight; FontRenderingEmSize = fontRenderingEmSize; - Ascent = glyphTypeface.Ascent * scale; + Ascent = fontMetrics.Ascent * scale; - Descent = glyphTypeface.Descent * scale; + Descent = fontMetrics.Descent * scale; - LineGap = glyphTypeface.LineGap * scale; + LineGap = fontMetrics.LineGap * scale; LineHeight = Descent - Ascent + LineGap; - UnderlineThickness = glyphTypeface.UnderlineThickness * scale; + UnderlineThickness = fontMetrics.UnderlineThickness * scale; - UnderlinePosition = glyphTypeface.UnderlinePosition * scale; + UnderlinePosition = fontMetrics.UnderlinePosition * scale; - StrikethroughThickness = glyphTypeface.StrikethroughThickness * scale; + StrikethroughThickness = fontMetrics.StrikethroughThickness * scale; - StrikethroughPosition = glyphTypeface.StrikethroughPosition * scale; + StrikethroughPosition = fontMetrics.StrikethroughPosition * scale; } /// diff --git a/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs b/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs index 4e75bb921e..0d00bed51e 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs @@ -8,7 +8,7 @@ namespace Avalonia.Media.TextFormatting public readonly struct TextShaperOptions { public TextShaperOptions( - GlyphTypeface typeface, + IGlyphTypeface typeface, double fontRenderingEmSize = 12, sbyte bidiLevel = 0, CultureInfo? culture = null, @@ -24,7 +24,7 @@ namespace Avalonia.Media.TextFormatting /// /// Get the typeface. /// - public GlyphTypeface Typeface { get; } + public IGlyphTypeface Typeface { get; } /// /// Get the font rendering em size. /// diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs index ce9cdde044..de40839853 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using System; +using System.Runtime.CompilerServices; using Avalonia.Utilities; namespace Avalonia.Media.TextFormatting.Unicode @@ -165,7 +166,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// The index to read at. /// The count of character that were read. /// - public static Codepoint ReadAt(ReadOnlySlice text, int index, out int count) + public static Codepoint ReadAt(ReadOnlySpan text, int index, out int count) { count = 1; diff --git a/src/Avalonia.Base/Media/Typeface.cs b/src/Avalonia.Base/Media/Typeface.cs index f0daa841d9..e6047bf96c 100644 --- a/src/Avalonia.Base/Media/Typeface.cs +++ b/src/Avalonia.Base/Media/Typeface.cs @@ -81,7 +81,7 @@ namespace Avalonia.Media /// /// The glyph typeface. /// - public GlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this); + public IGlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this); public static bool operator !=(Typeface a, Typeface b) { diff --git a/src/Avalonia.Base/Platform/IFontManagerImpl.cs b/src/Avalonia.Base/Platform/IFontManagerImpl.cs index 932249bd52..cd6e64abaf 100644 --- a/src/Avalonia.Base/Platform/IFontManagerImpl.cs +++ b/src/Avalonia.Base/Platform/IFontManagerImpl.cs @@ -43,6 +43,6 @@ namespace Avalonia.Platform /// 0 /// The created glyph typeface. Can be Null if it was not possible to create a glyph typeface. /// - IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface); + IGlyphTypeface CreateGlyphTypeface(Typeface typeface); } } diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs index 93105b21a4..9d0d7974b4 100644 --- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs @@ -180,7 +180,7 @@ namespace Avalonia.Platform /// /// This buffer only holds glyph indices. /// - IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); + IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); /// /// Allocates a horizontal platform glyph run buffer. @@ -192,7 +192,7 @@ namespace Avalonia.Platform /// /// This buffer holds glyph indices and glyph advances. /// - IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); + IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); /// /// Allocates a positioned platform glyph run buffer. @@ -204,7 +204,7 @@ namespace Avalonia.Platform /// /// This buffer holds glyph indices, glyph advances and glyph positions. /// - IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); + IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); /// /// Gets a value indicating whether the platform directly supports rectangles with rounded corners. diff --git a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs index b0b3982ed5..06fb526736 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs @@ -25,7 +25,7 @@ internal class FpsCounter // ASCII chars private GlyphRun[] _runs = new GlyphRun[LastChar - FirstChar + 1]; - public FpsCounter(GlyphTypeface typeface) + public FpsCounter(IGlyphTypeface typeface) { for (var c = FirstChar; c <= LastChar; c++) { diff --git a/src/Avalonia.Base/Utilities/ReadOnlySlice.cs b/src/Avalonia.Base/Utilities/ReadOnlySlice.cs index ad545b2923..583a3139b9 100644 --- a/src/Avalonia.Base/Utilities/ReadOnlySlice.cs +++ b/src/Avalonia.Base/Utilities/ReadOnlySlice.cs @@ -214,6 +214,8 @@ namespace Avalonia.Utilities return new ReadOnlySlice(memory); } + public static implicit operator ReadOnlySpan(ReadOnlySlice slice) => slice.Span; + internal class ReadOnlySliceDebugView { private readonly ReadOnlySlice _readOnlySlice; diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index b3bf92f06b..c8ac947c16 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -75,8 +75,13 @@ namespace Avalonia.Headless public TimeSpan TouchDoubleClickTime => DoubleClickTime; } - class HeadlessGlyphTypefaceImpl : IGlyphTypefaceImpl + class HeadlessGlyphTypefaceImpl : IGlyphTypeface { + public FontMetrics Metrics => new FontMetrics + { + + }; + public short DesignEmHeight => 10; public int Ascent => 5; @@ -106,6 +111,13 @@ namespace Avalonia.Headless return 1; } + public bool TryGetGlyph(uint codepoint, out ushort glyph) + { + glyph = 1; + + return true; + } + public int GetGlyphAdvance(ushort glyph) { return 1; @@ -120,6 +132,12 @@ namespace Avalonia.Headless { return codepoints.ToArray().Select(x => (ushort)x).ToArray(); } + + public bool TryGetTable(uint tag, out byte[] table) + { + table = null; + return false; + } } class HeadlessTextShaperStub : ITextShaperImpl @@ -136,7 +154,7 @@ namespace Avalonia.Headless class HeadlessFontManagerStub : IFontManagerImpl { - public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) + public IGlyphTypeface CreateGlyphTypeface(Typeface typeface) { return new HeadlessGlyphTypefaceImpl(); } diff --git a/src/Skia/Avalonia.Skia/FontManagerImpl.cs b/src/Skia/Avalonia.Skia/FontManagerImpl.cs index 125dd0e455..6b5e0b3db5 100644 --- a/src/Skia/Avalonia.Skia/FontManagerImpl.cs +++ b/src/Skia/Avalonia.Skia/FontManagerImpl.cs @@ -102,7 +102,7 @@ namespace Avalonia.Skia return false; } - public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) + public IGlyphTypeface CreateGlyphTypeface(Typeface typeface) { SKTypeface skTypeface = null; diff --git a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs index a3174010a1..7f3faf251f 100644 --- a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs +++ b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs @@ -1,14 +1,14 @@ using System; using System.Runtime.InteropServices; +using Avalonia.Media; using Avalonia.Metadata; -using Avalonia.Platform; using HarfBuzzSharp; using SkiaSharp; namespace Avalonia.Skia { [Unstable] - public class GlyphTypefaceImpl : IGlyphTypefaceImpl + public class GlyphTypefaceImpl : IGlyphTypeface { private bool _isDisposed; @@ -25,35 +25,30 @@ namespace Avalonia.Skia Font.SetFunctionsOpenType(); - DesignEmHeight = (short)Typeface.UnitsPerEm; - var metrics = Typeface.ToFont().Metrics; const double defaultFontRenderingEmSize = 12.0; - Ascent = (int)(metrics.Ascent / defaultFontRenderingEmSize * Typeface.UnitsPerEm); - - Descent = (int)(metrics.Descent / defaultFontRenderingEmSize * Typeface.UnitsPerEm); - - LineGap = (int)(metrics.Leading / defaultFontRenderingEmSize * Typeface.UnitsPerEm); - - UnderlinePosition = metrics.UnderlinePosition != null ? + Metrics = new FontMetrics + { + DesignEmHeight = (short)Typeface.UnitsPerEm, + Ascent = (int)(metrics.Ascent / defaultFontRenderingEmSize * Typeface.UnitsPerEm), + Descent = (int)(metrics.Descent / defaultFontRenderingEmSize * Typeface.UnitsPerEm), + LineGap = (int)(metrics.Leading / defaultFontRenderingEmSize * Typeface.UnitsPerEm), + UnderlinePosition = metrics.UnderlinePosition != null ? (int)(metrics.UnderlinePosition / defaultFontRenderingEmSize * Typeface.UnitsPerEm) : - 0; - - UnderlineThickness = metrics.UnderlineThickness != null ? + 0, + UnderlineThickness = metrics.UnderlineThickness != null ? (int)(metrics.UnderlineThickness / defaultFontRenderingEmSize * Typeface.UnitsPerEm) : - 0; - - StrikethroughPosition = metrics.StrikeoutPosition != null ? + 0, + StrikethroughPosition = metrics.StrikeoutPosition != null ? (int)(metrics.StrikeoutPosition / defaultFontRenderingEmSize * Typeface.UnitsPerEm) : - 0; - - StrikethroughThickness = metrics.StrikeoutThickness != null ? + 0, + StrikethroughThickness = metrics.StrikeoutThickness != null ? (int)(metrics.StrikeoutThickness / defaultFontRenderingEmSize * Typeface.UnitsPerEm) : - 0; - - IsFixedPitch = Typeface.IsFixedPitch; + 0, + IsFixedPitch = Typeface.IsFixedPitch + }; GlyphCount = Typeface.GlyphCount; @@ -69,41 +64,16 @@ namespace Avalonia.Skia public SKTypeface Typeface { get; } public int ReplacementCodepoint { get; } - - /// - public short DesignEmHeight { get; } - - /// - public int Ascent { get; } - - /// - public int Descent { get; } - - /// - public int LineGap { get; } - /// - public int UnderlinePosition { get; } - - /// - public int UnderlineThickness { get; } - - /// - public int StrikethroughPosition { get; } - - /// - public int StrikethroughThickness { get; } - - /// - public bool IsFixedPitch { get; } + public FontMetrics Metrics { get; } public int GlyphCount { get; } - + public bool IsFakeBold { get; } - + public bool IsFakeItalic { get; } - /// + /// public ushort GetGlyph(uint codepoint) { if (Font.TryGetGlyph(codepoint, out var glyph)) @@ -114,7 +84,14 @@ namespace Avalonia.Skia return 0; } - /// + public bool TryGetGlyph(uint codepoint, out ushort glyph) + { + glyph = GetGlyph(codepoint); + + return glyph != 0; + } + + /// public ushort[] GetGlyphs(ReadOnlySpan codepoints) { var glyphs = new ushort[codepoints.Length]; @@ -130,13 +107,13 @@ namespace Avalonia.Skia return glyphs; } - /// + /// public int GetGlyphAdvance(ushort glyph) { return Font.GetHorizontalGlyphAdvance(glyph); } - /// + /// public int[] GetGlyphAdvances(ReadOnlySpan glyphs) { var glyphIndices = new uint[glyphs.Length]; @@ -184,5 +161,10 @@ namespace Avalonia.Skia Dispose(true); GC.SuppressFinalize(this); } + + public bool TryGetTable(uint tag, out byte[] table) + { + return Typeface.TryGetTableData(tag, out table); + } } } diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index aa9225ba64..a9696efbd4 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -70,7 +70,7 @@ namespace Avalonia.Skia public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) { - if (glyphRun.GlyphTypeface.PlatformImpl is not GlyphTypefaceImpl glyphTypeface) + if (glyphRun.GlyphTypeface is not GlyphTypefaceImpl glyphTypeface) { throw new InvalidOperationException("PlatformImpl can't be null."); } @@ -244,13 +244,13 @@ namespace Avalonia.Skia "Current GPU acceleration backend does not support OpenGL integration"); } - public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) => new SKGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) => new SKHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) => new SKPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); private abstract class SKGlyphRunBufferBase : IGlyphRunBuffer @@ -258,11 +258,11 @@ namespace Avalonia.Skia protected readonly SKTextBlobBuilder _builder; protected readonly SKFont _font; - public SKGlyphRunBufferBase(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public SKGlyphRunBufferBase(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { _builder = new SKTextBlobBuilder(); - var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface.PlatformImpl; + var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface; _font = new SKFont { @@ -289,7 +289,7 @@ namespace Avalonia.Skia { private readonly SKRunBuffer _buffer; - public SKGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) + public SKGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) { _buffer = _builder.AllocateRun(_font, length, 0, 0); } @@ -301,7 +301,7 @@ namespace Avalonia.Skia { private readonly SKHorizontalRunBuffer _buffer; - public SKHorizontalGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) + public SKHorizontalGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) { _buffer = _builder.AllocateHorizontalRun(_font, length, 0); } @@ -315,7 +315,7 @@ namespace Avalonia.Skia { private readonly SKPositionedRunBuffer _buffer; - public SKPositionedGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) + public SKPositionedGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) { _buffer = _builder.AllocatePositionedRun(_font, length); } diff --git a/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs index c996337520..b98ed3ffe6 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs @@ -62,7 +62,7 @@ namespace Avalonia.Direct2D1.Media return false; } - public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) + public IGlyphTypeface CreateGlyphTypeface(Typeface typeface) { return new GlyphTypefaceImpl(typeface); } diff --git a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs index 443bb8a207..77d0e58d3d 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs @@ -1,14 +1,15 @@ using System; +using System.Drawing.Drawing2D; using Avalonia.Media; using Avalonia.Metadata; -using Avalonia.Platform; using HarfBuzzSharp; using SharpDX.DirectWrite; +using FontMetrics = Avalonia.Media.FontMetrics; namespace Avalonia.Direct2D1.Media { [Unstable] - public class GlyphTypefaceImpl : IGlyphTypefaceImpl + public class GlyphTypefaceImpl : IGlyphTypeface { private bool _isDisposed; @@ -26,40 +27,28 @@ namespace Avalonia.Direct2D1.Media Font.GetScale(out var xScale, out _); - DesignEmHeight = (short)xScale; - if (!Font.TryGetHorizontalFontExtents(out var fontExtents)) { Font.TryGetVerticalFontExtents(out fontExtents); } - Ascent = -fontExtents.Ascender; - - Descent = -fontExtents.Descender; - - LineGap = fontExtents.LineGap; - - if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition)) - { - UnderlinePosition = underlinePosition; - } - - if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness)) - { - UnderlineThickness = underlineThickness; - } + Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition); + Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness); + Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition); + Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness); - if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition)) + Metrics = new FontMetrics { - StrikethroughPosition = strikethroughPosition; - } - - if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness)) - { - StrikethroughThickness = strikethroughThickness; - } - - IsFixedPitch = FontFace.IsMonospacedFont; + DesignEmHeight = (short)xScale, + Ascent = -fontExtents.Ascender, + Descent = -fontExtents.Descender, + LineGap = fontExtents.LineGap, + UnderlinePosition = underlinePosition, + UnderlineThickness = underlineThickness, + StrikethroughPosition = strikethroughPosition, + StrikethroughThickness = strikethroughThickness, + IsFixedPitch = FontFace.IsMonospacedFont + }; } private Blob GetTable(Face face, Tag tag) @@ -89,37 +78,11 @@ namespace Avalonia.Direct2D1.Media public HarfBuzzSharp.Font Font { get; } - /// - public short DesignEmHeight { get; } - - /// - public int Ascent { get; } - - /// - public int Descent { get; } - - /// - public int LineGap { get; } - - //ToDo: Read font table for these values - /// - public int UnderlinePosition { get; } - - /// - public int UnderlineThickness { get; } - - /// - public int StrikethroughPosition { get; } - - /// - public int StrikethroughThickness { get; } - - /// - public bool IsFixedPitch { get; } + public FontMetrics Metrics { get; } public int GlyphCount { get; set; } - /// + /// public ushort GetGlyph(uint codepoint) { if (Font.TryGetGlyph(codepoint, out var glyph)) @@ -130,7 +93,14 @@ namespace Avalonia.Direct2D1.Media return 0; } - /// + public bool TryGetGlyph(uint codepoint, out ushort glyph) + { + glyph = GetGlyph(codepoint); + + return glyph != 0; + } + + /// public ushort[] GetGlyphs(ReadOnlySpan codepoints) { var glyphs = new ushort[codepoints.Length]; @@ -146,13 +116,13 @@ namespace Avalonia.Direct2D1.Media return glyphs; } - /// + /// public int GetGlyphAdvance(ushort glyph) { return Font.GetHorizontalGlyphAdvance(glyph); } - /// + /// public int[] GetGlyphAdvances(ReadOnlySpan glyphs) { var glyphIndices = new uint[glyphs.Length]; @@ -189,6 +159,21 @@ namespace Avalonia.Direct2D1.Media Dispose(true); GC.SuppressFinalize(this); } + + public bool TryGetTable(uint tag, out byte[] table) + { + table = null; + var blob = Face.ReferenceTable(tag); + + if (blob.Length > 0) + { + table = blob.AsSpan().ToArray(); + + return true; + } + + return false; + } } } diff --git a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs index 7d0cf05b0b..7998c95877 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs @@ -64,7 +64,7 @@ namespace Avalonia.Skia.UnitTests.Media return true; } - public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) + public IGlyphTypeface CreateGlyphTypeface(Typeface typeface) { SKTypeface skTypeface; diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs index 960c409058..a315158e1b 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs @@ -237,7 +237,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var glyph = typeface.GlyphTypeface.GetGlyph('a'); var advance = typeface.GlyphTypeface.GetGlyphAdvance(glyph) * - (12.0 / typeface.GlyphTypeface.DesignEmHeight); + (12.0 / typeface.GlyphTypeface.Metrics.DesignEmHeight); var paragraphWidth = advance * numberOfCharactersPerLine; diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index e21986d3a2..d6da2c77c4 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -578,9 +578,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { var glyphTypeface = Typeface.Default.GlyphTypeface; - var emHeight = glyphTypeface.DesignEmHeight; + var emHeight = glyphTypeface.Metrics.DesignEmHeight; - var lineHeight = (glyphTypeface.Descent - glyphTypeface.Ascent) * (12.0 / emHeight); + var lineHeight = glyphTypeface.Metrics.LineSpacing * (12.0 / emHeight); var layout = new TextLayout( text, diff --git a/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs index 9f958bdcdd..2b1685f358 100644 --- a/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs +++ b/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs @@ -58,7 +58,7 @@ namespace Avalonia.UnitTests return false; } - public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) + public IGlyphTypeface CreateGlyphTypeface(Typeface typeface) { var fontFamily = typeface.FontFamily; diff --git a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs index 04f316e5b4..3bcbc2efbd 100644 --- a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs +++ b/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs @@ -1,11 +1,11 @@ using System; using System.IO; -using Avalonia.Platform; +using Avalonia.Media; using HarfBuzzSharp; namespace Avalonia.UnitTests { - public class HarfBuzzGlyphTypefaceImpl : IGlyphTypefaceImpl + public class HarfBuzzGlyphTypefaceImpl : IGlyphTypeface { private bool _isDisposed; private Blob _blob; @@ -21,28 +21,28 @@ namespace Avalonia.UnitTests Font.SetFunctionsOpenType(); Font.GetScale(out var scale, out _); - - DesignEmHeight = (short)scale; - - var metrics = Font.OpenTypeMetrics; const double defaultFontRenderingEmSize = 12.0; - Ascent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalAscender) / defaultFontRenderingEmSize * DesignEmHeight); - - Descent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalDescender) / defaultFontRenderingEmSize * DesignEmHeight); + var metrics = Font.OpenTypeMetrics; - LineGap = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalLineGap) / defaultFontRenderingEmSize * DesignEmHeight); + Metrics = new FontMetrics + { + DesignEmHeight = (short)scale, + Ascent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalAscender) / defaultFontRenderingEmSize * scale), + Descent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalDescender) / defaultFontRenderingEmSize * scale), + LineGap = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalLineGap) / defaultFontRenderingEmSize * scale), - UnderlinePosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineOffset) / defaultFontRenderingEmSize * DesignEmHeight); + UnderlinePosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineOffset) / defaultFontRenderingEmSize * scale), - UnderlineThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineSize) / defaultFontRenderingEmSize * DesignEmHeight); + UnderlineThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineSize) / defaultFontRenderingEmSize * scale), - StrikethroughPosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutOffset) / defaultFontRenderingEmSize * DesignEmHeight); + StrikethroughPosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutOffset) / defaultFontRenderingEmSize * scale), - StrikethroughThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutSize) / defaultFontRenderingEmSize * DesignEmHeight); + StrikethroughThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutSize) / defaultFontRenderingEmSize * scale), - IsFixedPitch = GetGlyphAdvance(GetGlyph('a')) == GetGlyphAdvance(GetGlyph('b')); + IsFixedPitch = GetGlyphAdvance(GetGlyph('a')) == GetGlyphAdvance(GetGlyph('b')) + }; GlyphCount = Face.GlyphCount; @@ -51,44 +51,19 @@ namespace Avalonia.UnitTests IsFakeItalic = isFakeItalic; } + public FontMetrics Metrics { get; } + public Face Face { get; } public Font Font { get; } - /// - public short DesignEmHeight { get; } - - /// - public int Ascent { get; } - - /// - public int Descent { get; } - - /// - public int LineGap { get; } - - /// - public int UnderlinePosition { get; } - - /// - public int UnderlineThickness { get; } - - /// - public int StrikethroughPosition { get; } - - /// - public int StrikethroughThickness { get; } - - /// - public bool IsFixedPitch { get; } - public int GlyphCount { get; set; } public bool IsFakeBold { get; } public bool IsFakeItalic { get; } - /// + /// public ushort GetGlyph(uint codepoint) { if (Font.TryGetGlyph(codepoint, out var glyph)) @@ -99,7 +74,21 @@ namespace Avalonia.UnitTests return 0; } - /// + public bool TryGetGlyph(uint codepoint,out ushort glyph) + { + glyph = 0; + + if (Font.TryGetGlyph(codepoint, out var glyphId)) + { + glyph = (ushort)glyphId; + + return true; + } + + return false; + } + + /// public ushort[] GetGlyphs(ReadOnlySpan codepoints) { var glyphs = new ushort[codepoints.Length]; @@ -115,13 +104,13 @@ namespace Avalonia.UnitTests return glyphs; } - /// + /// public int GetGlyphAdvance(ushort glyph) { return Font.GetHorizontalGlyphAdvance(glyph); } - /// + /// public int[] GetGlyphAdvances(ReadOnlySpan glyphs) { var glyphIndices = new uint[glyphs.Length]; @@ -134,6 +123,21 @@ namespace Avalonia.UnitTests return Font.GetHorizontalGlyphAdvances(glyphIndices); } + public bool TryGetTable(uint tag, out byte[] table) + { + table = null; + var blob = Face.ReferenceTable(tag); + + if (blob.Length > 0) + { + table = blob.AsSpan().ToArray(); + + return true; + } + + return false; + } + private void Dispose(bool disposing) { if (_isDisposed) diff --git a/tests/Avalonia.UnitTests/MockFontManagerImpl.cs b/tests/Avalonia.UnitTests/MockFontManagerImpl.cs index e2678394df..e9b923a367 100644 --- a/tests/Avalonia.UnitTests/MockFontManagerImpl.cs +++ b/tests/Avalonia.UnitTests/MockFontManagerImpl.cs @@ -33,7 +33,7 @@ namespace Avalonia.UnitTests return false; } - public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) + public IGlyphTypeface CreateGlyphTypeface(Typeface typeface) { return new MockGlyphTypeface(); } diff --git a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs b/tests/Avalonia.UnitTests/MockGlyphTypeface.cs index 0b7beeb4fb..a1c492a7f1 100644 --- a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs +++ b/tests/Avalonia.UnitTests/MockGlyphTypeface.cs @@ -1,19 +1,18 @@ using System; -using Avalonia.Platform; +using Avalonia.Media; namespace Avalonia.UnitTests { - public class MockGlyphTypeface : IGlyphTypefaceImpl + public class MockGlyphTypeface : IGlyphTypeface { - public short DesignEmHeight => 10; - public int Ascent => 2; - public int Descent => 10; - public int LineGap { get; } - public int UnderlinePosition { get; } - public int UnderlineThickness { get; } - public int StrikethroughPosition { get; } - public int StrikethroughThickness { get; } - public bool IsFixedPitch { get; } + public FontMetrics Metrics => new FontMetrics + { + DesignEmHeight = 10, + Ascent = 2, + Descent = 10, + IsFixedPitch = true + }; + public int GlyphCount => 1337; public ushort GetGlyph(uint codepoint) @@ -31,6 +30,13 @@ namespace Avalonia.UnitTests return 8; } + public bool TryGetGlyph(uint codepoint, out ushort glyph) + { + glyph = 8; + + return true; + } + public int[] GetGlyphAdvances(ReadOnlySpan glyphs) { var advances = new int[glyphs.Length]; @@ -44,5 +50,11 @@ namespace Avalonia.UnitTests } public void Dispose() { } + + public bool TryGetTable(uint tag, out byte[] table) + { + table = null; + return false; + } } } From a4a24c2fa56c54753102df216acb76ff0e285c88 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Wed, 12 Oct 2022 15:41:13 +0200 Subject: [PATCH 15/84] Remove GlyphTypeface.PlatformImpl --- src/Avalonia.Base/Media/FontManager.cs | 2 +- .../HeadlessPlatformRenderInterface.cs | 6 +++--- src/Skia/Avalonia.Skia/TextShaperImpl.cs | 2 +- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 16 ++++++++-------- .../Avalonia.Direct2D1/Media/TextShaperImpl.cs | 2 +- .../Media/GlyphRunTests.cs | 2 +- .../VisualTree/MockRenderInterface.cs | 6 +++--- .../Avalonia.Benchmarks/NullRenderingPlatform.cs | 6 +++--- .../Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs | 2 +- .../MockPlatformRenderInterface.cs | 6 +++--- 10 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs index dcb29ffebf..d92d003c2a 100644 --- a/src/Avalonia.Base/Media/FontManager.cs +++ b/src/Avalonia.Base/Media/FontManager.cs @@ -81,7 +81,7 @@ namespace Avalonia.Media PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates); /// - /// Returns a new , or an existing one if a matching exists. + /// Returns a new , or an existing one if a matching exists. /// /// The typeface. /// diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 12a9a016f3..fcd7f1e31f 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -115,17 +115,17 @@ namespace Avalonia.Headless return new HeadlessGeometryStub(new Rect(glyphRun.Size)); } - public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { return new HeadlessGlyphRunBufferStub(); } - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { return new HeadlessHorizontalGlyphRunBufferStub(); } - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { return new HeadlessPositionedGlyphRunBufferStub(); } diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs index 6da8a20f6a..b07deb1f4d 100644 --- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs +++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs @@ -31,7 +31,7 @@ namespace Avalonia.Skia buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); - var font = ((GlyphTypefaceImpl)typeface.PlatformImpl).Font; + var font = ((GlyphTypefaceImpl)typeface).Font; font.Shape(buffer); diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 45744e6efa..8103f89dad 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -162,7 +162,7 @@ namespace Avalonia.Direct2D1 public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) { - if (glyphRun.GlyphTypeface.PlatformImpl is not GlyphTypefaceImpl glyphTypeface) + if (glyphRun.GlyphTypeface is not GlyphTypefaceImpl glyphTypeface) { throw new InvalidOperationException("PlatformImpl can't be null."); } @@ -264,9 +264,9 @@ namespace Avalonia.Direct2D1 { protected readonly SharpDX.DirectWrite.GlyphRun _dwRun; - public DWGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public DWGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { - var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface.PlatformImpl; + var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface; _dwRun = new SharpDX.DirectWrite.GlyphRun { @@ -286,7 +286,7 @@ namespace Avalonia.Direct2D1 private class DWHorizontalGlyphRunBuffer : DWGlyphRunBuffer, IHorizontalGlyphRunBuffer { - public DWHorizontalGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public DWHorizontalGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) { _dwRun.Advances = new float[length]; @@ -297,7 +297,7 @@ namespace Avalonia.Direct2D1 private class DWPositionedGlyphRunBuffer : DWGlyphRunBuffer, IPositionedGlyphRunBuffer { - public DWPositionedGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public DWPositionedGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) { _dwRun.Advances = new float[length]; @@ -307,17 +307,17 @@ namespace Avalonia.Direct2D1 public Span GlyphPositions => MemoryMarshal.Cast(_dwRun.Offsets.AsSpan()); } - public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { return new DWGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); } - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { return new DWHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); } - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { return new DWPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); } diff --git a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs index bda36e6c8c..064320f809 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs @@ -31,7 +31,7 @@ namespace Avalonia.Direct2D1.Media buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); - var font = ((GlyphTypefaceImpl)typeface.PlatformImpl).Font; + var font = ((GlyphTypefaceImpl)typeface).Font; font.Shape(buffer); diff --git a/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs b/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs index cd0586233f..df16c1b34f 100644 --- a/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs +++ b/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs @@ -185,7 +185,7 @@ namespace Avalonia.Base.UnitTests.Media var characters = new ReadOnlySlice(Enumerable.Repeat('a', count).ToArray(), start, count); - return new GlyphRun(new GlyphTypeface(new MockGlyphTypeface()), 10, characters, glyphIndices, glyphAdvances, + return new GlyphRun(new MockGlyphTypeface(), 10, characters, glyphIndices, glyphAdvances, glyphClusters: glyphClusters, biDiLevel: bidiLevel); } } diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs index d21879f349..a7a55653b7 100644 --- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs @@ -126,17 +126,17 @@ namespace Avalonia.Base.UnitTests.VisualTree throw new NotImplementedException(); } - public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { throw new NotImplementedException(); } - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { throw new NotImplementedException(); } - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { throw new NotImplementedException(); } diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index a2c2936a4d..34f0dfef11 100644 --- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs +++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs @@ -117,17 +117,17 @@ namespace Avalonia.Benchmarks return new MockStreamGeometryImpl(); } - public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { throw new NotImplementedException(); } - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { throw new NotImplementedException(); } - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { throw new NotImplementedException(); } diff --git a/tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs index 459ae3a549..7b7488bd5a 100644 --- a/tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs +++ b/tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs @@ -30,7 +30,7 @@ namespace Avalonia.UnitTests buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); - var font = ((HarfBuzzGlyphTypefaceImpl)typeface.PlatformImpl).Font; + var font = ((HarfBuzzGlyphTypefaceImpl)typeface).Font; font.Shape(buffer); diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index 314cc95041..586436ef7f 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -152,17 +152,17 @@ namespace Avalonia.UnitTests return Mock.Of(); } - public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { return Mock.Of(); } - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { return Mock.Of(); } - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { return Mock.Of(); } From 85ac29c788305238e7dd097971bcf71befd8c38e Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 12 Oct 2022 14:58:59 +0200 Subject: [PATCH 16/84] fix: CA1802 --- .editorconfig | 3 +++ src/Windows/Avalonia.Win32/Win32Platform.cs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index 179491eece..917bd21bfe 100644 --- a/.editorconfig +++ b/.editorconfig @@ -137,6 +137,9 @@ space_within_single_line_array_initializer_braces = true #Net Analyzer dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomment when all violations are fixed. +# CA1802: Use literals where appropriate +dotnet_diagnostic.CA1802.severity = warning + # Wrapping preferences csharp_wrap_before_ternary_opsigns = false diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 29b4baeeb0..3cdc3586dc 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -241,8 +241,8 @@ namespace Avalonia.Win32 }); } - private static readonly int SignalW = unchecked((int) 0xdeadbeaf); - private static readonly int SignalL = unchecked((int)0x12345678); + private const int SignalW = unchecked((int)0xdeadbeaf); + private const int SignalL = unchecked((int)0x12345678); public void Signal(DispatcherPriority prio) { From dd7e941c3773fb43608a49ab3655bac52b0a06d0 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Thu, 13 Oct 2022 00:13:27 +0800 Subject: [PATCH 17/84] feat: remove header from DatePicker. --- .../Pages/DateTimePickerPage.xaml | 8 ++--- .../DateTimePickers/DatePicker.cs | 30 ------------------- .../Accents/FluentControlResourcesDark.xaml | 2 -- .../Accents/FluentControlResourcesLight.xaml | 2 -- .../Controls/DatePicker.xaml | 13 +------- .../Controls/DatePicker.xaml | 13 +------- 6 files changed, 6 insertions(+), 62 deletions(-) diff --git a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml index 29cd939520..8b6b7f6c16 100644 --- a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml +++ b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml @@ -13,17 +13,17 @@ Margin="16" HorizontalAlignment="Stretch" Spacing="16"> - A simple DatePicker with a header + A simple DatePicker - + - <DatePicker Header="Pick a date" /> + <DatePicker/> @@ -33,7 +33,7 @@ - + diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs index fb27feb521..bb05cd1b1f 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs @@ -40,18 +40,6 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect(nameof(DayVisible), x => x.DayVisible, (x, v) => x.DayVisible = v); - /// - /// Defines the Property - /// - public static readonly StyledProperty HeaderProperty = - AvaloniaProperty.Register(nameof(Header)); - - /// - /// Defines the Property - /// - public static readonly StyledProperty HeaderTemplateProperty = - AvaloniaProperty.Register(nameof(HeaderTemplate)); - /// /// Defines the Property /// @@ -152,24 +140,6 @@ namespace Avalonia.Controls } } - /// - /// Gets or sets the DatePicker header - /// - public object Header - { - get => GetValue(HeaderProperty); - set => SetValue(HeaderProperty, value); - } - - /// - /// Gets or sets the header template - /// - public IDataTemplate HeaderTemplate - { - get => GetValue(HeaderTemplateProperty); - set => SetValue(HeaderTemplateProperty, value); - } - /// /// Gets or sets the maximum year for the picker /// diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml index 88b9f18eee..785766214c 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml @@ -424,7 +424,6 @@ - @@ -595,7 +594,6 @@ - diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml index b51a0bf259..9a9a7f93c6 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml @@ -420,7 +420,6 @@ - @@ -589,7 +588,6 @@ - diff --git a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml index 6f1e4fccfe..1585dada90 100644 --- a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml @@ -28,7 +28,6 @@ - 0,0,0,4 40 40 41 @@ -84,18 +83,8 @@ - - - +