diff --git a/Directory.Build.Override.props b/Directory.Build.Override.props index 3ec3a76eb25b..8115232d291d 100644 --- a/Directory.Build.Override.props +++ b/Directory.Build.Override.props @@ -7,7 +7,7 @@ <_IncludeWindows> <_IncludeTizen> <_IncludeGtk>true - <_IncludeAndroid>true + <_IncludeAndroid>false <_IncludeIos> <_IncludeMacCatalyst> <_IncludeMacOS> diff --git a/Directory.Build.props b/Directory.Build.props index 7abf80742891..24c2612b10d4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -148,7 +148,7 @@ <_MauiBuildTasksLocation Condition="'$(_MauiBuildTasksLocation)' == ''">$(MSBuildThisFileDirectory).buildtasks\ <_MauiAOTProfileLocation>$(MauiSrcDirectory)Controls\src\Build.Tasks\nuget\buildTransitive\netstandard2.0\ $(MauiRootDirectory)eng/microsoft.maui.controls.snk - true + false portable true true diff --git a/Microsoft.Maui.Gtk.slnf b/Microsoft.Maui.Gtk.slnf index a45a8f95510c..cad95068ccad 100644 --- a/Microsoft.Maui.Gtk.slnf +++ b/Microsoft.Maui.Gtk.slnf @@ -8,11 +8,14 @@ "src\\BlazorWebView\\src\\Gtk\\Microsoft.AspNetCore.Components.WebView.Gtk.csproj", "src\\BlazorWebView\\src\\Maui\\Microsoft.AspNetCore.Components.WebView.Maui.csproj", "src\\Compatibility\\Core\\src\\Compatibility.csproj", + "src\\Controls\\Foldable\\src\\Controls.Foldable.csproj", + "src\\Controls\\Maps\\src\\Controls.Maps.csproj", "src\\Controls\\samples\\Controls.Sample.Gtk\\Controls.Sample.Gtk.csproj", "src\\Controls\\samples\\Controls.Sample\\Maui.Controls.Sample.csproj", "src\\Controls\\src\\Build.Tasks\\Controls.Build.Tasks.csproj", "src\\Controls\\src\\Core\\Controls.Core.csproj", "src\\Controls\\src\\Xaml\\Controls.Xaml.csproj", + "src\\Core\\maps\\src\\Maps.csproj", "src\\Core\\src\\Core.csproj", "src\\Essentials\\src\\Essentials.csproj", "src\\Graphics\\samples\\GraphicsTester.Gtk\\GraphicsTester.Gtk.csproj", diff --git a/NuGet.config b/NuGet.config index 7c8de06d63f3..5b1b0a2a5de3 100644 --- a/NuGet.config +++ b/NuGet.config @@ -6,6 +6,7 @@ + diff --git a/eng/Versions.props b/eng/Versions.props index f39295b01c56..9b809845e7e1 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -50,7 +50,7 @@ 8.0.0 8.0.0 - 8.0.0 + 9.0.0 3.3.4 3.3.4 4.5.0 diff --git a/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj b/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj index 533fa5eee4a4..df1a37e1179b 100644 --- a/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj +++ b/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj @@ -1,7 +1,7 @@  - $(_MauiDotNetTfm);$(MauiPlatforms) + $(_MauiDotNetTfm) enable $(DefineConstants);WEBVIEW2_MAUI true diff --git a/src/Controls/samples/Controls.Sample.Gtk/Properties/launchSettings.json b/src/Controls/samples/Controls.Sample.Gtk/Properties/launchSettings.json new file mode 100644 index 000000000000..322b624761c8 --- /dev/null +++ b/src/Controls/samples/Controls.Sample.Gtk/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "Controls.Sample.Gtk": { + "commandName": "Project" + }, + "WSL": { + "commandName": "WSL2", + "distributionName": "" + } + } +} \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample/Maui.Controls.Sample.csproj b/src/Controls/samples/Controls.Sample/Maui.Controls.Sample.csproj index ff72f827a19f..2d9c0d669d3c 100644 --- a/src/Controls/samples/Controls.Sample/Maui.Controls.Sample.csproj +++ b/src/Controls/samples/Controls.Sample/Maui.Controls.Sample.csproj @@ -39,24 +39,24 @@ - - + + - - - + + + - + diff --git a/src/Controls/samples/Controls.Sample/Properties/launchSettings.json b/src/Controls/samples/Controls.Sample/Properties/launchSettings.json index af97d49d1598..30f0afacb90c 100644 --- a/src/Controls/samples/Controls.Sample/Properties/launchSettings.json +++ b/src/Controls/samples/Controls.Sample/Properties/launchSettings.json @@ -3,6 +3,14 @@ "Windows Machine": { "commandName": "MsixPackage", "nativeDebugging": true + }, + "WSL": { + "commandName": "WSL2", + "distributionName": "", + "environmentVariables": { + "DISPLAY": "127.0.0.1:0.0", + "DOTNET_ENVIRONMENT": "Development" + } } } } \ No newline at end of file diff --git a/src/Core/src/Core.csproj b/src/Core/src/Core.csproj index 275496ee8471..df9eed1e3f28 100644 --- a/src/Core/src/Core.csproj +++ b/src/Core/src/Core.csproj @@ -26,6 +26,7 @@ + @@ -75,32 +76,14 @@ - + <_ValuesToReplace Include="VERSION" PropertyName="PackageReferenceVersion" /> - - + + - + diff --git a/src/Core/src/Fonts/EmbeddedFontLoader.Gtk.cs b/src/Core/src/Fonts/EmbeddedFontLoader.Gtk.cs index 7d21de574a66..d0cafbeb5531 100644 --- a/src/Core/src/Fonts/EmbeddedFontLoader.Gtk.cs +++ b/src/Core/src/Fonts/EmbeddedFontLoader.Gtk.cs @@ -1,4 +1,10 @@ using System; +using System.IO; +using System.Linq; +using Cairo; +using Microsoft.Maui.GtkInterop; +using Pango; +using Path = System.IO.Path; namespace Microsoft.Maui { @@ -6,16 +12,84 @@ public partial class EmbeddedFontLoader { public string? LoadFont(EmbeddedFont font) { - // gtk has no methods to load fonts - // see: http://mces.blogspot.com/2015/05/how-to-use-custom-application-fonts.html + var cfg = DllImportFontConfig.FcConfigGetCurrent(); + if (cfg == IntPtr.Zero) + { + cfg = DllImportFontConfig.FcInitLoadConfigAndFonts(); + DllImportFontConfig.FcConfigSetCurrent(cfg); + } - throw new NotImplementedException(); + RegisterFromStream(font.ResourceStream!, font.FontName!, cfg); + DllImportFontConfig.PangoFcFontMapConfigChanged(); - // freetype is needed. look at: - // https://github.com/Robmaister/SharpFont + // For debugging: list all active fonts + // var fonts = DllImportFontConfig.ListActiveFonts(); + // GetPangoCairoFonts(); + return font.FontName; + } + + /// + /// Registers a font from the specified stream into the font configuration. + /// + /// This method reads the font data from the provided stream, writes it to a temporary file, and + /// registers the file with the specified font configuration. The temporary file is created in the system's temporary + /// directory and will persist for the duration of the application's execution. + /// The containing the font data. The stream must be readable. + /// The name of the font file, including its extension. This is used to determine the font type and generate a + /// temporary file. + /// A handle to the font configuration object where the font will be registered. + /// Thrown if the font cannot be added to the font configuration. + static void RegisterFromStream(Stream fontStream, string filename, nint cfg) + { + var extension = Path.GetExtension(filename).ToLowerInvariant(); + var pureFilename = Path.GetFileNameWithoutExtension(filename); + + // Stream → byte[] + byte[] bytes; + if (fontStream is MemoryStream ms && ms.TryGetBuffer(out var seg)) + { + bytes = seg.Array is null ? ms.ToArray() : seg.Array.AsSpan(seg.Offset, seg.Count).ToArray(); + } + else + { + using var tmp = new MemoryStream(); + fontStream.CopyTo(tmp); + bytes = tmp.ToArray(); + } + + var baseDir = Path.Combine(Path.GetTempPath(), "maui-app-fonts"); + Directory.CreateDirectory(baseDir); + var tempPath = Path.Combine(baseDir, filename); + + if (!File.Exists(tempPath)) + File.WriteAllBytes(tempPath, bytes); + + bool ok = DllImportFontConfig.FcConfigAppFontAddFile(cfg, tempPath); + if (!ok) + throw new InvalidOperationException($"FcConfigAppFontAddFile fehlgeschlagen: {tempPath}"); + } + + static FontFamily[] GetPangoCairoFonts(bool writeOutput = true) + { + using var surf = new ImageSurface(Format.ARGB32, 1, 1); + using var cr = new Cairo.Context(surf); + using var layout = CairoHelper.CreateLayout(cr); + + var fontMap = layout.Context.FontMap; + var families = fontMap.Families; + if (writeOutput) + { + foreach (var family in families) + { + Console.WriteLine(family.Name); + foreach (var face in family.Faces) + Console.WriteLine($" - {face.FaceName} ({face.Describe()})"); + } + } + return families; } } } diff --git a/src/Core/src/Fonts/FontManager.Gtk.cs b/src/Core/src/Fonts/FontManager.Gtk.cs index 8cd1ed9ceab4..9ba469df3033 100644 --- a/src/Core/src/Fonts/FontManager.Gtk.cs +++ b/src/Core/src/Fonts/FontManager.Gtk.cs @@ -7,59 +7,7 @@ using static Microsoft.Maui.GtkInterop.DllImportFontConfig; namespace Microsoft.Maui -{ - - // see: https://docs.gtk.org/Pango/struct.FontDescription.html - /* - public enum Pango.Weight - { - Thin = 100, // 0x00000064 - Ultralight = 200, // 0x000000C8 - Light = 300, // 0x0000012C - Semilight = 350, // 0x0000015E - Book = 380, // 0x0000017C - Normal = 400, // 0x00000190 - Medium = 500, // 0x000001F4 - Semibold = 600, // 0x00000258 - Bold = 700, // 0x000002BC - Ultrabold = 800, // 0x00000320 - Heavy = 900, // 0x00000384 - Ultraheavy = 1000, // 0x000003E8 - } - - public enum Stretch - { - UltraCondensed, - ExtraCondensed, - Condensed, - SemiCondensed, - Normal, - SemiExpanded, - Expanded, - ExtraExpanded, - UltraExpanded, - } - - - public enum Style { Normal, Oblique, Italic } - - public enum Variant - { - Normal, - SmallCaps, - } - - public enum Gravity - { - South, - East, - North, - West, - Auto, - } - - */ - +{ public class FontManager : IFontManager { @@ -85,8 +33,18 @@ public FontDescription DefaultFontFamily public double DefaultFontSize => _defaultFontSize ??= DefaultFontFamily?.GetSize() ?? 0; - public FontDescription GetFontFamily(Font font) => - font == default ? SystemContext.FontDescription : font.ToFontDescription(); + public FontDescription GetFontFamily(Font font) + { + if (font != default) + { + if(!string.IsNullOrWhiteSpace(font.Family)) _fontRegistrar.GetFont(font.Family); + return font.ToFontDescription(); + } + else + { + return SystemContext.FontDescription; + } + } public double GetFontSize(Font font) { diff --git a/src/Core/src/Fonts/FontRegistrar.Gtk.cs b/src/Core/src/Fonts/FontRegistrar.Gtk.cs index 34135a35ebbb..63dbce9e78f9 100644 --- a/src/Core/src/Fonts/FontRegistrar.Gtk.cs +++ b/src/Core/src/Fonts/FontRegistrar.Gtk.cs @@ -1,9 +1,39 @@ #nullable enable +using System.IO; + namespace Microsoft.Maui { public partial class FontRegistrar : IFontRegistrar { - string? LoadNativeAppFont(string font, string filename, string? alias) => null; + string? LoadNativeAppFont(string font, string filename, string? alias) + { + if(GtkBuildSettings.MauiFontBehavior == GtkBuildSettings.MauiResourceBehavior.EmbedFiles) + { + var (assembly, resourceName) = Storage.FileSystemUtils.GetMauiRessource(Storage.FileSystemUtils.MauiResourceType.MauiFont, filename); + + var stream = assembly.GetManifestResourceStream(resourceName); + + if(stream == null) + throw new FileNotFoundException($"Embedded font with the name {filename} was not found."); + + return LoadEmbeddedFont(font, filename, alias, stream); + } + else + { + using var stream = GetNativeFontStream(filename, alias); + + return LoadEmbeddedFont(font, filename, alias, stream); + } + } + + static FileStream GetNativeFontStream(string filename, string? alias) + { + var fontPath = Storage.FileSystemUtils.GetFilePath(Storage.FileSystemUtils.MauiResourceType.MauiFont, filename); + if (File.Exists(fontPath)) + return File.OpenRead(fontPath); + + throw new FileNotFoundException($"Native font with the name {filename} was not found."); + } } } \ No newline at end of file diff --git a/src/Core/src/Handlers/Label/LabelHandler.Gtk.cs b/src/Core/src/Handlers/Label/LabelHandler.Gtk.cs index a18aee112e92..f46a947bfc78 100644 --- a/src/Core/src/Handlers/Label/LabelHandler.Gtk.cs +++ b/src/Core/src/Handlers/Label/LabelHandler.Gtk.cs @@ -70,6 +70,23 @@ public static void MapFont(ILabelHandler handler, ILabel label) { var fontManager = handler.GetRequiredService(); + // GTK does not support FontAttributes in the FontFamily name, so we need to extract them here + var property = label.GetType().GetProperty("FontAttributes"); + var fontAttributes = property?.GetValue(label); + + if (fontAttributes != null && (int)fontAttributes == 0 && !string.IsNullOrWhiteSpace(label.Font.Family)) //0 means FontAttributes.None + { + if (label.Font.Family.Contains("bold", StringComparison.InvariantCultureIgnoreCase)) + { + property!.SetValue(label, 1 << 0); //0001 means FontAttributes.Bold + } + + if (label.Font.Family.Contains("italic", StringComparison.InvariantCultureIgnoreCase)) + { + property!.SetValue(label, 1 << 1); //0010 means FontAttributes.Italic + } + } + handler.PlatformView?.UpdateFont(label, fontManager); } diff --git a/src/Core/src/ImageSources/FileImageSourceService/FileImageSourceService.Gtk.cs b/src/Core/src/ImageSources/FileImageSourceService/FileImageSourceService.Gtk.cs index c54e5252ad05..a8554328b764 100644 --- a/src/Core/src/ImageSources/FileImageSourceService/FileImageSourceService.Gtk.cs +++ b/src/Core/src/ImageSources/FileImageSourceService/FileImageSourceService.Gtk.cs @@ -3,9 +3,11 @@ using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Microsoft.Maui.Storage; using NativeImage = Gdk.Pixbuf; namespace Microsoft.Maui @@ -23,40 +25,48 @@ public partial class FileImageSourceService return FromResult(null); var filename = imageSource.File; - - NativeImage? TryLoadFile(string file) + + NativeImage? TryLoadFile() { - if (File.Exists(file)) + if (File.Exists(filename)) return new NativeImage(filename); - return null; + if (GtkBuildSettings.MauiImageBehavior != GtkBuildSettings.MauiResourceBehavior.CopyFiles) return default; + + var filePath = FileSystemUtils.GetFilePath(FileSystemUtils.MauiResourceType.MauiImage, filename, scale); + + if (filePath != null && File.Exists(filePath)) + return new NativeImage(filePath); + + filePath = FileSystemUtils.GetFilePath(FileSystemUtils.MauiResourceType.MauiImage, filename, scale); + + if (filePath != null && File.Exists(filePath)) + return new NativeImage(filePath); + + return default; } - NativeImage? TryLoadResource(string file) + NativeImage? TryLoadEmbededMauiImage() { - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) - { - var names = assembly.GetManifestResourceNames(); - var res = names.FirstOrDefault(r => r.EndsWith($".{file}")); + if (GtkBuildSettings.MauiImageBehavior != GtkBuildSettings.MauiResourceBehavior.EmbedFiles) return default; + + var (assembly, resourceName) = FileSystemUtils.GetMauiRessource(FileSystemUtils.MauiResourceType.MauiImage, filename, scale); - if (res != null) - { - return new(assembly, res); - } + if (assembly != null && !string.IsNullOrWhiteSpace(resourceName)) + { + return new(assembly, resourceName); } return default; - } try { - var image = TryLoadFile(filename); + var image = TryLoadFile(); if (image == null) { - image = TryLoadResource(filename); - + image = TryLoadEmbededMauiImage(); } if (image == null) @@ -77,6 +87,7 @@ public partial class FileImageSourceService static Task?> FromResult(IImageSourceServiceResult? result) => Task.FromResult(result); + } } \ No newline at end of file diff --git a/src/Core/src/ImageSources/FontImageSourceService/FontImageSourceService.Gtk.cs b/src/Core/src/ImageSources/FontImageSourceService/FontImageSourceService.Gtk.cs index f3d8b283427f..c0eea083878c 100644 --- a/src/Core/src/ImageSources/FontImageSourceService/FontImageSourceService.Gtk.cs +++ b/src/Core/src/ImageSources/FontImageSourceService/FontImageSourceService.Gtk.cs @@ -2,8 +2,10 @@ using System; using System.Threading; using System.Threading.Tasks; +using Cairo; using Microsoft.Extensions.Logging; using Microsoft.Maui.Graphics; +using Pango; using NativeImage = Gdk.Pixbuf; @@ -43,28 +45,41 @@ public partial class FontImageSourceService internal NativeImage RenderImage(IFontImageSource imageSource, float scale) { - throw new NotImplementedException(); - // var font = FontManager.GetFont(imageSource.Font); - // var color = (imageSource.Color ?? Colors.White).ToNative(); - // var glyph = (NSString)imageSource.Glyph; - // - // var attString = new NSAttributedString(glyph, font, color); - // var imagesize = glyph.GetSizeUsingAttributes(attString.GetUIKitAttributes(0, out _)); - // - // UIGraphics.BeginImageContextWithOptions(imagesize, false, scale); - // var ctx = new NSStringDrawingContext(); - // - // var boundingRect = attString.GetBoundingRect(imagesize, 0, ctx); - // attString.DrawString(new CGRect( - // imagesize.Width / 2 - boundingRect.Size.Width / 2, - // imagesize.Height / 2 - boundingRect.Size.Height / 2, - // imagesize.Width, - // imagesize.Height)); - // - // var image = UIGraphics.GetImageFromCurrentImageContext(); - // UIGraphics.EndImageContext(); - // - // return image.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal); + var fontManager = FontManager; + + var fontDescription = fontManager.GetFontFamily(imageSource.Font); + + var fontSize = fontManager.GetFontSize(imageSource.Font); + + var color = imageSource.Color ?? Colors.White; + + var width = (int)(fontSize * scale); + var height = (int)(fontSize * scale); + + using (var surface = new ImageSurface(Format.ARGB32, width, height)) + using (var context = new Cairo.Context(surface)) + { + context.SetSourceRGBA(1, 1, 1, 0); + context.Paint(); + + var layout = CairoHelper.CreateLayout(context); + + layout.FontDescription = fontDescription; + + layout.SetText(imageSource.Glyph); + + layout.GetPixelSize(out int textWidth, out int textHeight); + double x = (width - textWidth) / 2.0; + double y = (height - textHeight) / 2.0; + context.MoveTo(x, y); + + context.SetSourceRGBA(color.Red, color.Green, color.Blue, color.Alpha); + + CairoHelper.ShowLayout(context, layout); + + return new NativeImage(surface, 0, 0, width, height); + } } + } } \ No newline at end of file diff --git a/src/Core/src/Platform/Gtk/GtkInterop/DllImportFontConfig.cs b/src/Core/src/Platform/Gtk/GtkInterop/DllImportFontConfig.cs index cc2a5160b560..26a0616cbc04 100644 --- a/src/Core/src/Platform/Gtk/GtkInterop/DllImportFontConfig.cs +++ b/src/Core/src/Platform/Gtk/GtkInterop/DllImportFontConfig.cs @@ -1,4 +1,8 @@ +using System; +using System.Collections.Generic; using System.Runtime.InteropServices; +using Cairo; +using Pango; // ReSharper disable CA2211 @@ -14,7 +18,154 @@ public static class DllImportFontConfig public delegate bool d_FcConfigAppFontAddFile(System.IntPtr config, string fontPath); public static d_FcConfigAppFontAddFile FcConfigAppFontAddFile = FuncLoader.LoadFunction(FuncLoader.GetProcAddress(GLibrary.Load(Library.Fontconfig), "FcConfigAppFontAddFile")); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate IntPtr d_FcInitLoadConfigAndFonts(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate IntPtr d_FcObjectSetCreate(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate bool d_FcObjectSetAdd(IntPtr os, string @object); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void d_FcObjectSetDestroy(IntPtr os); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate IntPtr d_FcFontList(IntPtr config, IntPtr pattern, IntPtr objectSet); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int d_FcPatternGetString(IntPtr pattern, string @object, int n, out IntPtr s); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void d_FcFontSetDestroy(IntPtr fontset); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate IntPtr d_FcConfigGetCurrent(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate bool d_FcConfigSetCurrent(IntPtr config); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate IntPtr d_FcPatternCreate(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void d_FcPatternDestroy(IntPtr pattern); + + + + private static readonly IntPtr s_fcHandle = GLibrary.Load(Library.Fontconfig); + + public static readonly d_FcInitLoadConfigAndFonts FcInitLoadConfigAndFonts = FuncLoader.LoadFunction(FuncLoader.GetProcAddress(s_fcHandle, "FcInitLoadConfigAndFonts")); + + public static readonly d_FcObjectSetCreate FcObjectSetCreate = FuncLoader.LoadFunction(FuncLoader.GetProcAddress(s_fcHandle, "FcObjectSetCreate")); + + public static readonly d_FcObjectSetAdd FcObjectSetAdd = FuncLoader.LoadFunction(FuncLoader.GetProcAddress(s_fcHandle, "FcObjectSetAdd")); + + public static readonly d_FcObjectSetDestroy FcObjectSetDestroy = FuncLoader.LoadFunction(FuncLoader.GetProcAddress(s_fcHandle, "FcObjectSetDestroy")); + + public static readonly d_FcFontList FcFontList = FuncLoader.LoadFunction(FuncLoader.GetProcAddress(s_fcHandle, "FcFontList")); + + public static readonly d_FcPatternGetString FcPatternGetString = FuncLoader.LoadFunction(FuncLoader.GetProcAddress(s_fcHandle, "FcPatternGetString")); + + public static readonly d_FcFontSetDestroy FcFontSetDestroy = FuncLoader.LoadFunction(FuncLoader.GetProcAddress(s_fcHandle, "FcFontSetDestroy")); + + public static readonly d_FcConfigGetCurrent FcConfigGetCurrent = FuncLoader.LoadFunction(FuncLoader.GetProcAddress(s_fcHandle, "FcConfigGetCurrent")); + + public static readonly d_FcConfigSetCurrent FcConfigSetCurrent = FuncLoader.LoadFunction(FuncLoader.GetProcAddress(s_fcHandle, "FcConfigSetCurrent")); + + public static readonly d_FcPatternCreate FcPatternCreate = FuncLoader.LoadFunction(FuncLoader.GetProcAddress(s_fcHandle, "FcPatternCreate")); + public static readonly d_FcPatternDestroy FcPatternDestroy = FuncLoader.LoadFunction(FuncLoader.GetProcAddress(s_fcHandle, "FcPatternDestroy")); + + [StructLayout(LayoutKind.Sequential)] + private struct FcFontSet + { + public int nfont; + public int sfont; + public IntPtr fonts; // pointer to array of FcPattern* + } + + public record FontInfo(string Family, string Style, string File); + + public static List<(string family, string style, string file)> ListActiveFonts() + { + var cfg = FcConfigGetCurrent(); + if (cfg == IntPtr.Zero) + cfg = FcInitLoadConfigAndFonts(); + + var pattern = FcPatternCreate(); + if (pattern == IntPtr.Zero) + throw new InvalidOperationException("FcPatternCreate failed."); + + var os = FcObjectSetCreate(); + if (os == IntPtr.Zero) + { + FcPatternDestroy(pattern); + throw new InvalidOperationException("FcObjectSetCreate failed."); + } + if (!FcObjectSetAdd(os, "family")) + { FcObjectSetDestroy(os); FcPatternDestroy(pattern); throw new InvalidOperationException("FcObjectSetAdd(family) failed."); } + if (!FcObjectSetAdd(os, "style")) + { FcObjectSetDestroy(os); FcPatternDestroy(pattern); throw new InvalidOperationException("FcObjectSetAdd(style) failed."); } + if (!FcObjectSetAdd(os, "file")) + { FcObjectSetDestroy(os); FcPatternDestroy(pattern); throw new InvalidOperationException("FcObjectSetAdd(file) failed."); } + + var fsPtr = FcFontList(cfg, pattern, os); + + FcObjectSetDestroy(os); + FcPatternDestroy(pattern); + + var list = new List<(string, string, string)>(); + if (fsPtr == IntPtr.Zero) + return list; + + var fs = Marshal.PtrToStructure(fsPtr); + for (int i = 0; i < fs.nfont; i++) + { + var pat = Marshal.ReadIntPtr(fs.fonts, i * IntPtr.Size); + string fam = TryGetStr(pat, "family") ?? "?"; + string sty = TryGetStr(pat, "style") ?? "?"; + string file = TryGetStr(pat, "file") ?? "?"; + list.Add((fam, sty, file)); + } + + FcFontSetDestroy(fsPtr); + return list; + } + + static string? TryGetStr(IntPtr pat, string key) + { + return (FcPatternGetString(pat, key, 0, out var p) == 0 && p != IntPtr.Zero) + ? Marshal.PtrToStringUTF8(p) + : null; + } + + #region Pango + static readonly IntPtr s_PangoFtHandle = GLibrary.Load(Library.PangoFt); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate void d_pango_fc_font_map_config_changed(IntPtr fontmap); + + static d_pango_fc_font_map_config_changed pango_fc_font_map_config_changed = FuncLoader.LoadFunction(FuncLoader.GetProcAddress(s_PangoFtHandle, "pango_fc_font_map_config_changed")); + + /// + /// Notifies the Pango font configuration system that the font map configuration has changed. + /// + /// Can be removed after implementation in PangoSharp + public static void PangoFcFontMapConfigChanged() + { + using var surf = new ImageSurface(Format.ARGB32, 1, 1); + using var cr = new Cairo.Context(surf); + using var layout = CairoHelper.CreateLayout(cr); + + var fontMap = layout.Context.FontMap; + var handleProp = fontMap.GetType().GetProperty("Handle", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); + var ptr = (IntPtr)(handleProp?.GetValue(fontMap) ?? IntPtr.Zero); + if (ptr != IntPtr.Zero) + pango_fc_font_map_config_changed(ptr); + } + #endregion Pango } } \ No newline at end of file diff --git a/src/Core/src/Platform/Gtk/GtkInterop/GLibrary.cs b/src/Core/src/Platform/Gtk/GtkInterop/GLibrary.cs index 1c3da5333cc6..6f584888ad19 100644 --- a/src/Core/src/Platform/Gtk/GtkInterop/GLibrary.cs +++ b/src/Core/src/Platform/Gtk/GtkInterop/GLibrary.cs @@ -34,6 +34,7 @@ static GLibrary() _libraryDefinitions[Library.Fontconfig] = new[] { "fontconfig.dll", "libfontconfig.so.1", "libfontconfig.1.dylib", "fontconfig.dll" }; // _libraryDefinitions[Library.Webkit] = new[] { "libwebkitgtk-3.0-0.dll", "libwebkitgtk-3.0.so.0", "libwebkitgtk-3.0.0.dylib", "libwebkitgtk-3.dll" }; + _libraryDefinitions[Library.PangoFt] = new[] { "libpangoft2-1.0-0.dll", "libpangoft2-1.0.so.0", "libpangoft2-1.0.0.dylib", "pangoft2-1.dll" }; } public static IntPtr Load(Library library) diff --git a/src/Core/src/Platform/Gtk/GtkInterop/Library.cs b/src/Core/src/Platform/Gtk/GtkInterop/Library.cs index d1d6fb12aec6..1b14978bdc12 100644 --- a/src/Core/src/Platform/Gtk/GtkInterop/Library.cs +++ b/src/Core/src/Platform/Gtk/GtkInterop/Library.cs @@ -15,8 +15,8 @@ enum Library GdkPixbuf, Gtk, Fontconfig, - Webkit - + Webkit, + PangoFt } } \ No newline at end of file diff --git a/src/Core/src/Platform/Gtk/MauiGtkApplication.cs b/src/Core/src/Platform/Gtk/MauiGtkApplication.cs index 3e9b56094554..d987c2d11b8b 100644 --- a/src/Core/src/Platform/Gtk/MauiGtkApplication.cs +++ b/src/Core/src/Platform/Gtk/MauiGtkApplication.cs @@ -1,9 +1,16 @@ using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using Gdk; using Gtk; using Microsoft.Extensions.DependencyInjection; using Microsoft.Maui.Dispatching; using Microsoft.Maui.Hosting; using Microsoft.Maui.LifecycleEvents; +using Pango; +using static System.Net.Mime.MediaTypeNames; namespace Microsoft.Maui { @@ -63,7 +70,7 @@ protected void OnOpened(object o, GLib.OpenedArgs args) protected void OnActivated(object sender, EventArgs args) { - StartupLauch(sender, args); + StartupLaunch(sender, args); Services?.InvokeLifecycleEvents(del => del(CurrentGtkApplication, args)); } @@ -90,7 +97,7 @@ protected void OnWindowAdded(object o, WindowAddedArgs args) // future use: to have notifications at cross platform Window level } - protected void StartupLauch(object sender, EventArgs args) + protected void StartupLaunch(object sender, EventArgs args) { IPlatformApplication.Current = this; @@ -126,5 +133,49 @@ protected void Launch(EventArgs args) ((GLib.Application)app).Run(); } + + #region Splash Screen + int GetScaleFactor() + { + try + { + var display = Gdk.Display.Default; + if (display == null) + return 1; + + var monitor = display.PrimaryMonitor; + if (monitor == null) monitor = display.GetMonitor(0); // Fallback in my Dev Env PrimaryMonitor is null + + if (monitor == null) return 1; + + return monitor.ScaleFactor; // 1, 2, … + } + catch + { + return 1; + } + } + + (Assembly?, string?) GetSplashResource() + { + if (GtkBuildSettings.MauiSplashBehavior == GtkBuildSettings.MauiResourceBehavior.CopyFiles) return (null, null); + + return Storage.FileSystemUtils.GetMauiRessource(Storage.FileSystemUtils.MauiResourceType.MauiSplashScreen, null, GetScaleFactor()); + } + + string? GetSplashImagePath() + { + if (GtkBuildSettings.MauiImageBehavior != GtkBuildSettings.MauiResourceBehavior.CopyFiles) + return default; + + var filePath = Storage.FileSystemUtils.GetFilePath(Storage.FileSystemUtils.MauiResourceType.MauiSplashScreen); + + if (filePath != null && File.Exists(filePath)) + return filePath; + + return default; + } + + #endregion Splash Screen } } \ No newline at end of file diff --git a/src/Essentials/src/Essentials.csproj b/src/Essentials/src/Essentials.csproj index d04dc47706b1..85d55948a2e8 100644 --- a/src/Essentials/src/Essentials.csproj +++ b/src/Essentials/src/Essentials.csproj @@ -70,6 +70,13 @@ + + + + + + +