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 @@
+
+
+
+
+
+
+