From a2b11492271090dc0c99b17c768360e13bbf59ec Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Sat, 28 Feb 2026 13:03:00 +0000 Subject: [PATCH] Add AppBuilder.UseTextShapingSubsystem (#20761) * Add AppBuilder.UseTextShapingSubsystem * Add subsystem defaults to error messages * Use HarfBuzz in IntegrationTests.Win32 --- src/Avalonia.Controls/AppBuilder.cs | 31 ++++++++++++++++++- .../HarfBuzzApplicationExtensions.cs | 4 ++- .../AvaloniaHeadlessPlatform.cs | 3 +- .../HeadlessUnitTestSession.cs | 9 ++++-- .../Avalonia.IntegrationTests.Win32.csproj | 1 + .../Infrastructure/AppManager.cs | 1 + 6 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/AppBuilder.cs b/src/Avalonia.Controls/AppBuilder.cs index 9259be8594..bc299bdbe1 100644 --- a/src/Avalonia.Controls/AppBuilder.cs +++ b/src/Avalonia.Controls/AppBuilder.cs @@ -60,6 +60,16 @@ namespace Avalonia /// public string? RenderingSubsystemName { get; private set; } + /// + /// Gets or sets a method to call the initialize the text shaping subsystem. + /// + public Action? TextShapingSubsystemInitializer { get; private set; } + + /// + /// Gets the name of the currently selected text shaping subsystem. + /// + public string? TextShapingSubsystemName { get; private set; } + /// /// Gets a method to call after the is setup. /// @@ -224,6 +234,19 @@ namespace Avalonia RenderingSubsystemName = name; return Self; } + + /// + /// Specifies a text shaping subsystem to use. + /// + /// The method to call to initialize the text shaping subsystem. + /// The name of the text shaping subsystem. + /// An instance. + public AppBuilder UseTextShapingSubsystem(Action initializer, string name = "") + { + TextShapingSubsystemInitializer = initializer; + TextShapingSubsystemName = name; + return Self; + } /// /// Specifies a runtime platform subsystem to use. @@ -308,7 +331,12 @@ namespace Avalonia if (RenderingSubsystemInitializer == null) { - throw new InvalidOperationException("No rendering system configured."); + throw new InvalidOperationException("No rendering system configured. Consider calling UseSkia()."); + } + + if (TextShapingSubsystemInitializer == null) + { + throw new InvalidOperationException("No text shaping system configured. Consider calling UseHarfBuzz()."); } if (_appFactory == null) @@ -333,6 +361,7 @@ namespace Avalonia { _optionsInitializers?.Invoke(); RuntimePlatformServicesInitializer?.Invoke(); + TextShapingSubsystemInitializer?.Invoke(); RenderingSubsystemInitializer?.Invoke(); WindowingSubsystemInitializer?.Invoke(); AfterPlatformServicesSetupCallback?.Invoke(Self); diff --git a/src/HarfBuzz/Avalonia.HarfBuzz/HarfBuzzApplicationExtensions.cs b/src/HarfBuzz/Avalonia.HarfBuzz/HarfBuzzApplicationExtensions.cs index 508c9ef525..e07ae82627 100644 --- a/src/HarfBuzz/Avalonia.HarfBuzz/HarfBuzzApplicationExtensions.cs +++ b/src/HarfBuzz/Avalonia.HarfBuzz/HarfBuzzApplicationExtensions.cs @@ -20,7 +20,9 @@ namespace Avalonia /// The configured instance. public static AppBuilder UseHarfBuzz(this AppBuilder builder) { - return builder.With(new HarfBuzzTextShaper()); + return builder.UseTextShapingSubsystem( + () => AvaloniaLocator.CurrentMutable.Bind().ToConstant(new HarfBuzzTextShaper()), + "HarfBuzz"); } } diff --git a/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs b/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs index 667a8ba3ee..48c5f5d84e 100644 --- a/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs +++ b/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs @@ -121,7 +121,8 @@ namespace Avalonia.Headless builder = builder.UseRenderingSubsystem(HeadlessPlatformRenderInterface.Initialize, "Headless"); return builder .UseStandardRuntimePlatformSubsystem() - .UseWindowingSubsystem(() => AvaloniaHeadlessPlatform.Initialize(opts), "Headless"); + .UseWindowingSubsystem(() => AvaloniaHeadlessPlatform.Initialize(opts), "Headless") + .UseHarfBuzz(); } } } diff --git a/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs b/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs index 0e5a6b0c8b..cda9c4ae75 100644 --- a/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs +++ b/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs @@ -236,9 +236,12 @@ public sealed class HeadlessUnitTestSession : IDisposable, IAsyncDisposable // If windowing subsystem wasn't initialized by user, force headless with default parameters. if (appBuilder.WindowingSubsystemName != "Headless") { - appBuilder = appBuilder - .UseHeadless(new AvaloniaHeadlessPlatformOptions()) - .UseHarfBuzz(); + appBuilder = appBuilder.UseHeadless(new AvaloniaHeadlessPlatformOptions()); + } + + if (appBuilder.TextShapingSubsystemInitializer is null) + { + appBuilder = appBuilder.UseHarfBuzz(); } // ReSharper disable once AccessToModifiedClosure diff --git a/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj b/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj index 797265af46..2732b6d549 100644 --- a/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj +++ b/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj @@ -9,6 +9,7 @@ + diff --git a/tests/Avalonia.IntegrationTests.Win32/Infrastructure/AppManager.cs b/tests/Avalonia.IntegrationTests.Win32/Infrastructure/AppManager.cs index 8ac5cc24ed..cba1a8ad73 100644 --- a/tests/Avalonia.IntegrationTests.Win32/Infrastructure/AppManager.cs +++ b/tests/Avalonia.IntegrationTests.Win32/Infrastructure/AppManager.cs @@ -24,6 +24,7 @@ internal static class AppManager .Configure() .UseWin32() .UseSkia() + .UseHarfBuzz() .SetupWithoutStarting(); appBuilder.Instance!.Styles.Add(new FluentTheme());