From 6b7707049a9ef42e12ec02d17bf7273d0b5fe9fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Foidl?= Date: Sat, 6 Dec 2025 01:14:37 +0100 Subject: [PATCH 01/10] FontMap and FontFamily added --- demos/PangoDemo/PangoDemo.csproj | 4 + demos/PangoDemo/Program.cs | 64 +++++++++- .../GObject/GObjectException.cs | 32 +++++ .../GObject/GObjectNative.cs | 9 ++ .../CairoSharp.Extensions/GObject/TypeDefs.cs | 13 ++ .../Loading/LoadingException.cs | 25 +--- .../Loading/LoadingNative.cs | 10 +- .../Loading/Marshalling.cs | 4 +- .../Loading/PDF/PopplerException.cs | 2 + .../Loading/SVG/LibRSvgException.cs | 2 + .../Loading/SVG/LibRSvgExtensions.cs | 8 +- .../CairoSharp.Extensions/Loading/TypeDefs.cs | 10 -- .../CairoSharp.Extensions/Pango/FontFamily.cs | 75 +++++++++++ .../Pango/FontFamilyNative.cs | 27 ++++ source/CairoSharp.Extensions/Pango/FontMap.cs | 119 ++++++++++++++++++ .../Pango/FontMapNative.cs | 25 ++++ .../Pango/PangoException.cs | 15 +++ .../Pango/PangoLayout.cs | 1 + .../Pango/PangoNative.Resolver.cs | 7 ++ .../Pango/PangoNative.cs | 8 +- source/CairoSharp.Extensions/Pango/ReadMe.md | 1 + .../CairoSharp.Extensions/Pango/TypeDefs.cs | 18 +++ 22 files changed, 423 insertions(+), 56 deletions(-) create mode 100644 source/CairoSharp.Extensions/GObject/GObjectException.cs create mode 100644 source/CairoSharp.Extensions/GObject/TypeDefs.cs create mode 100644 source/CairoSharp.Extensions/Pango/FontFamily.cs create mode 100644 source/CairoSharp.Extensions/Pango/FontFamilyNative.cs create mode 100644 source/CairoSharp.Extensions/Pango/FontMap.cs create mode 100644 source/CairoSharp.Extensions/Pango/FontMapNative.cs create mode 100644 source/CairoSharp.Extensions/Pango/PangoException.cs create mode 100644 source/CairoSharp.Extensions/Pango/TypeDefs.cs diff --git a/demos/PangoDemo/PangoDemo.csproj b/demos/PangoDemo/PangoDemo.csproj index 0354f07..1903878 100644 --- a/demos/PangoDemo/PangoDemo.csproj +++ b/demos/PangoDemo/PangoDemo.csproj @@ -8,4 +8,8 @@ + + + + diff --git a/demos/PangoDemo/Program.cs b/demos/PangoDemo/Program.cs index c5d7eb8..580a18f 100644 --- a/demos/PangoDemo/Program.cs +++ b/demos/PangoDemo/Program.cs @@ -21,6 +21,7 @@ PangoNative.LibPangoName => IOPath.Combine(@"C:\Program Files\msys64\ucrt64\bin", "libpango-1.0-0.dll"), PangoNative.LibPangoCairoName => IOPath.Combine(@"C:\Program Files\msys64\ucrt64\bin", "libpangocairo-1.0-0.dll"), PangoNative.LibGObjectName => IOPath.Combine(@"C:\Program Files\msys64\ucrt64\bin", "libgobject-2.0-0.dll"), + PangoNative.LibGLibName => IOPath.Combine(@"C:\Program Files\msys64\ucrt64\bin", "libglib-2.0-0.dll"), _ => null }; @@ -40,6 +41,7 @@ DemoComparisonWithCairoText(); DemoFromPangoDocsWithTextAroundCircle(); DemoPangoFeatures(); +DemoFontMap(); //----------------------------------------------------------------------------- static void DemoComparisonWithCairoText() { @@ -278,7 +280,7 @@ static void DemoPangoFeatures() { cr.Color = KnownColors.DeepSkyBlue; cr.MoveTo(10, curY); - pangoLayout.SetText("Another long text, to showcase how justify works. And as we get on at least one more line, we can test next how JustifyLastLine works."); + pangoLayout.SetMarkup("Another long text, to showcase how Justify works. And as we get on at least one more line, we can test next how JustifyLastLine works."u8); pangoLayout.ShowLayout(); pangoLayout.GetSize(out width, out height); @@ -347,3 +349,63 @@ static void DemoPangoFeatures() tee.WriteToPng("features.png"); } +//----------------------------------------------------------------------------- +static void DemoFontMap() +{ + Console.WriteLine(); + using StreamWriter sw = File.CreateText("font-families.csv"); + sw.WriteLine("Name;IsMonospace;IsVariable"); + + using FontMap fontMap = FontMap.CairoFontMapGetDefault(); + fontMap.AddFontFile(IOPath.Combine(AppContext.BaseDirectory, "fonts", "SanRemo.ttf")); + + List families = []; + foreach (FontFamily fontFamily in fontMap.ListFamilies()) + { + families.Add(fontFamily); + } + + string fontFamilyName = ""; + foreach (FontFamily fontFamily in families.OrderBy(f => f.Name)) + { + fontFamilyName = fontFamily.Name; + + Console.WriteLine($"{fontFamily}\n"); + sw.WriteLine($"{fontFamilyName};{fontFamily.IsMonospace};{fontFamily.IsVariable}"); + } + + const int Width = 600; + const int Height = 120; + + using SvgSurface svg = new("font-map.svg", Width, Height); + using PdfSurface pdf = new("font-map.pdf", Width, Height); + using TeeSurface tee = new(svg); + using CairoContext cr = new(tee); + tee.Add(pdf); + + using PangoLayout pangoLayout = new(cr); + double curY = 10; + + cr.MoveTo(10, curY); + pangoLayout.SetFontDescriptionFromString($"{fontFamilyName} Normal 22"); + pangoLayout.SetText($"Font: {fontFamilyName}"); + pangoLayout.ShowLayout(); + pangoLayout.GetSize(out double width, out double height); + curY += height; + + cr.MoveTo(10, curY); + pangoLayout.SetFontDescriptionFromString("Viner Hand ITC, Normal 22"); + pangoLayout.SetText("Font: Viner Hand ITC"); + pangoLayout.ShowLayout(); + pangoLayout.GetSize(out width, out height); + curY += height; + + cr.MoveTo(10, curY); + pangoLayout.SetFontDescriptionFromString("San Remo, Normal 22"); + pangoLayout.SetText("Font: San Remo"); + pangoLayout.ShowLayout(); + pangoLayout.GetSize(out width, out height); + curY += height; + + tee.WriteToPng("font-map.png"); +} diff --git a/source/CairoSharp.Extensions/GObject/GObjectException.cs b/source/CairoSharp.Extensions/GObject/GObjectException.cs new file mode 100644 index 0000000..c5a1974 --- /dev/null +++ b/source/CairoSharp.Extensions/GObject/GObjectException.cs @@ -0,0 +1,32 @@ +// (c) gfoidl, all rights reserved + +namespace Cairo.Extensions.GObject; + +[Serializable] +public abstract class GObjectException : Exception +{ + public int Domain { get; } + public int Code { get; } + + protected GObjectException() { } + protected GObjectException(string message) : base(message) { } + protected GObjectException(string message, Exception inner) : base(message, inner) { } + + private protected unsafe GObjectException(GError* error) : this(GetErrorMessage(error)) + { + this.Domain = error->Domain; + this.Code = error->Code; + } + + private static unsafe string GetErrorMessage(GError* error) + { + string msg = new(error->Message); + + // From the docs, e.g. https://gnome.pages.gitlab.gnome.org/librsvg/Rsvg-2.0/method.Handle.render_document.html + // In case of error, the argument will be set to a newly allocated GError; the caller will take + // ownership of the data, and be responsible for freeing it. + GObjectNative.g_error_free(error); + + return msg; + } +} diff --git a/source/CairoSharp.Extensions/GObject/GObjectNative.cs b/source/CairoSharp.Extensions/GObject/GObjectNative.cs index f9e4865..f2fc1e8 100644 --- a/source/CairoSharp.Extensions/GObject/GObjectNative.cs +++ b/source/CairoSharp.Extensions/GObject/GObjectNative.cs @@ -6,9 +6,18 @@ namespace Cairo.Extensions.GObject; internal static unsafe partial class GObjectNative { + public const string LibGLibName = "libglib-2.0.so.0"; public const string LibGObjectName = "libgobject-2.0.so.0"; [LibraryImport(LibGObjectName)] [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] internal static partial void g_object_unref(void* @object); + + [LibraryImport(LibGLibName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial void g_free(void* mem); + + [LibraryImport(LibGLibName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial void g_error_free(GError* error); } diff --git a/source/CairoSharp.Extensions/GObject/TypeDefs.cs b/source/CairoSharp.Extensions/GObject/TypeDefs.cs new file mode 100644 index 0000000..ca49877 --- /dev/null +++ b/source/CairoSharp.Extensions/GObject/TypeDefs.cs @@ -0,0 +1,13 @@ +// (c) gfoidl, all rights reserved + +using System.Runtime.InteropServices; + +namespace Cairo.Extensions.GObject; + +[StructLayout(LayoutKind.Sequential)] +internal unsafe struct GError +{ + public int Domain; + public int Code; + public sbyte* Message; +} diff --git a/source/CairoSharp.Extensions/Loading/LoadingException.cs b/source/CairoSharp.Extensions/Loading/LoadingException.cs index 66a20ad..65de6bc 100644 --- a/source/CairoSharp.Extensions/Loading/LoadingException.cs +++ b/source/CairoSharp.Extensions/Loading/LoadingException.cs @@ -1,32 +1,15 @@ // (c) gfoidl, all rights reserved +using Cairo.Extensions.GObject; + namespace Cairo.Extensions.Loading; [Serializable] -public abstract class LoadingException : Exception +public abstract class LoadingException : GObjectException { - public int Domain { get; } - public int Code { get; } - protected LoadingException() { } protected LoadingException(string message) : base(message) { } protected LoadingException(string message, Exception inner) : base(message, inner) { } - private protected unsafe LoadingException(GError* error) : this(GetErrorMessage(error)) - { - this.Domain = error->Domain; - this.Code = error->Code; - } - - private static unsafe string GetErrorMessage(GError* error) - { - string msg = new(error->Message); - - // From the docs, e.g. https://gnome.pages.gitlab.gnome.org/librsvg/Rsvg-2.0/method.Handle.render_document.html - // In case of error, the argument will be set to a newly allocated GError; the caller will take - // ownership of the data, and be responsible for freeing it. - LoadingNative.g_error_free(error); - - return msg; - } + private protected unsafe LoadingException(GError* error) : base(error) { } } diff --git a/source/CairoSharp.Extensions/Loading/LoadingNative.cs b/source/CairoSharp.Extensions/Loading/LoadingNative.cs index 3d66c65..59594fd 100644 --- a/source/CairoSharp.Extensions/Loading/LoadingNative.cs +++ b/source/CairoSharp.Extensions/Loading/LoadingNative.cs @@ -14,7 +14,7 @@ namespace Cairo.Extensions.Loading; public static unsafe partial class LoadingNative { - public const string LibGLibName = "libglib-2.0.so.0"; + public const string LibGLibName = GObjectNative.LibGLibName; public const string LibGObjectName = GObjectNative.LibGObjectName; public const string LibGioName = "libgio-2.0.so.0"; public const string LibRSvgName = "librsvg-2.so.2"; @@ -93,14 +93,6 @@ private static int GetPopplerVersionAsInt() return CairoAPI.VersionEncode(major, minor, patch); } //------------------------------------------------------------------------- - [LibraryImport(LibGLibName)] - [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] - internal static partial void g_free(void* mem); - - [LibraryImport(LibGLibName)] - [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] - internal static partial void g_error_free(GError* error); - //------------------------------------------------------------------------- [LibraryImport(LibGioName, StringMarshalling = StringMarshalling.Utf8)] [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] internal static partial GFile* g_file_new_for_path(string path); diff --git a/source/CairoSharp.Extensions/Loading/Marshalling.cs b/source/CairoSharp.Extensions/Loading/Marshalling.cs index ef27a86..9ba9632 100644 --- a/source/CairoSharp.Extensions/Loading/Marshalling.cs +++ b/source/CairoSharp.Extensions/Loading/Marshalling.cs @@ -1,7 +1,7 @@ // (c) gfoidl, all rights reserved using System.Runtime.InteropServices.Marshalling; -using static Cairo.Extensions.Loading.LoadingNative; +using Cairo.Extensions.GObject; namespace Cairo.Extensions.Loading; @@ -15,5 +15,5 @@ internal static unsafe class GCharMarshaller : new string(utf8); } //------------------------------------------------------------------------- - public static void Free(sbyte* utf8) => g_free(utf8); + public static void Free(sbyte* utf8) => GObjectNative.g_free(utf8); } diff --git a/source/CairoSharp.Extensions/Loading/PDF/PopplerException.cs b/source/CairoSharp.Extensions/Loading/PDF/PopplerException.cs index a494f72..79f632b 100644 --- a/source/CairoSharp.Extensions/Loading/PDF/PopplerException.cs +++ b/source/CairoSharp.Extensions/Loading/PDF/PopplerException.cs @@ -1,5 +1,7 @@ // (c) gfoidl, all rights reserved +using Cairo.Extensions.GObject; + namespace Cairo.Extensions.Loading.PDF; [Serializable] diff --git a/source/CairoSharp.Extensions/Loading/SVG/LibRSvgException.cs b/source/CairoSharp.Extensions/Loading/SVG/LibRSvgException.cs index 798924a..c7c758c 100644 --- a/source/CairoSharp.Extensions/Loading/SVG/LibRSvgException.cs +++ b/source/CairoSharp.Extensions/Loading/SVG/LibRSvgException.cs @@ -1,5 +1,7 @@ // (c) gfoidl, all rights reserved +using Cairo.Extensions.GObject; + namespace Cairo.Extensions.Loading.SVG; [Serializable] diff --git a/source/CairoSharp.Extensions/Loading/SVG/LibRSvgExtensions.cs b/source/CairoSharp.Extensions/Loading/SVG/LibRSvgExtensions.cs index e29d9e3..6de7ce9 100644 --- a/source/CairoSharp.Extensions/Loading/SVG/LibRSvgExtensions.cs +++ b/source/CairoSharp.Extensions/Loading/SVG/LibRSvgExtensions.cs @@ -1,5 +1,6 @@ // (c) gfoidl, all rights reserved +using Cairo.Extensions.GObject; using static Cairo.Extensions.Loading.LoadingNative; namespace Cairo.Extensions.Loading.SVG; @@ -73,18 +74,13 @@ public void LoadSvg(ReadOnlySpan svgData, RsvgRectangle viewPort, RsvgHand /// /// SVG document /// viewport size at which the whole SVG would be fitted - /// - /// the DPI at which the SVG will be rendered in cairo. Common values are 75, 90 and 300 DPI. See - /// Resolution of the rendered image (dots per inch, or DPI) - /// for further information. Current CSS assumes a default DPI of 96 (the default used here). - /// /// /// The gives the position and size at which the whole SVG document will /// be rendered. The document is scaled proportionally to fit into this viewport. /// /// is null /// an error occured - public void LoadSvg(SvgDocument svgDocument, RsvgRectangle viewPort, double dpi = 96d) + public void LoadSvg(SvgDocument svgDocument, RsvgRectangle viewPort) { ArgumentNullException.ThrowIfNull(svgDocument); diff --git a/source/CairoSharp.Extensions/Loading/TypeDefs.cs b/source/CairoSharp.Extensions/Loading/TypeDefs.cs index c813ce5..ea544aa 100644 --- a/source/CairoSharp.Extensions/Loading/TypeDefs.cs +++ b/source/CairoSharp.Extensions/Loading/TypeDefs.cs @@ -1,20 +1,10 @@ // (c) gfoidl, all rights reserved -using System.Runtime.InteropServices; - namespace Cairo.Extensions.Loading; internal struct GFile; internal struct GInputStream; -[StructLayout(LayoutKind.Sequential)] -internal unsafe struct GError -{ - public int Domain; - public int Code; - public sbyte* Message; -} - internal struct RsvgHandle; internal struct PopplerDocument; diff --git a/source/CairoSharp.Extensions/Pango/FontFamily.cs b/source/CairoSharp.Extensions/Pango/FontFamily.cs new file mode 100644 index 0000000..a84b19a --- /dev/null +++ b/source/CairoSharp.Extensions/Pango/FontFamily.cs @@ -0,0 +1,75 @@ +// (c) gfoidl, all rights reserved + +using static Cairo.Extensions.Pango.FontFamilyNative; + +namespace Cairo.Extensions.Pango; + +/// +/// A is used to represent a family of related font faces. +/// +/// +/// The font faces in a family share a common design, but differ in slant, weight, width +/// or other aspects. +/// +public sealed unsafe class FontFamily : CairoObject +{ + internal FontFamily(pango_font_family* family) : base(family, isOwnedByCairo: true, needsDestroy: false) { } + + protected override void DisposeCore(pango_font_family* handle) + => throw new InvalidOperationException("PangoFontFamily must not be freed"); + + /// + /// Gets the name of the family. + /// + /// + /// The name is unique among all fonts for the font backend and can be used in a + /// PangoFontDescription to specify that a face from this family is desired. + /// + public string Name + { + get + { + this.CheckDisposed(); + return pango_font_family_get_name(this.Handle); + } + } + + /// + /// A monospace font is a font designed for text display where the characters form a regular grid. + /// + /// + /// For Western languages this would mean that the advance width of all characters are the same, but + /// this categorization also includes Asian fonts which include double-width characters: characters + /// that occupy two grid cells. g_unichar_iswide() returns a result that indicates whether a character + /// is typically double-width in a monospace font. + /// + public bool IsMonospace + { + get + { + this.CheckDisposed(); + return pango_font_family_is_monospace(this.Handle); + } + } + + /// + /// A variable font is a font which has axes that can be modified to produce different faces. + /// + /// + /// Such axes are also known as variations. + /// + public bool IsVariable + { + get + { + this.CheckDisposed(); + return pango_font_family_is_variable(this.Handle); + } + } + + public override string ToString() => $""" + Name: {this.Name} + IsMonospace: {this.IsMonospace} + IsVariable: {this.IsVariable} + """; +} diff --git a/source/CairoSharp.Extensions/Pango/FontFamilyNative.cs b/source/CairoSharp.Extensions/Pango/FontFamilyNative.cs new file mode 100644 index 0000000..184d490 --- /dev/null +++ b/source/CairoSharp.Extensions/Pango/FontFamilyNative.cs @@ -0,0 +1,27 @@ +// (c) gfoidl, all rights reserved + +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace Cairo.Extensions.Pango; + +internal static unsafe partial class FontFamilyNative +{ + // https://docs.gtk.org/Pango/method.FontFamily.get_name.html + [LibraryImport(PangoNative.LibPangoName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + [return: MarshalUsing(typeof(NativeConstCharMarshaller))] + internal static partial string pango_font_family_get_name(pango_font_family* family); + + // https://docs.gtk.org/Pango/method.FontFamily.is_monospace.html + [LibraryImport(PangoNative.LibPangoName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + [return: MarshalAs(UnmanagedType.U4)] + internal static partial bool pango_font_family_is_monospace(pango_font_family* family); + + // https://docs.gtk.org/Pango/method.FontFamily.is_variable.html + [LibraryImport(PangoNative.LibPangoName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + [return: MarshalAs(UnmanagedType.U4)] + internal static partial bool pango_font_family_is_variable(pango_font_family* family); +} diff --git a/source/CairoSharp.Extensions/Pango/FontMap.cs b/source/CairoSharp.Extensions/Pango/FontMap.cs new file mode 100644 index 0000000..ea51742 --- /dev/null +++ b/source/CairoSharp.Extensions/Pango/FontMap.cs @@ -0,0 +1,119 @@ +// (c) gfoidl, all rights reserved + +using Cairo.Extensions.GObject; +using static Cairo.Extensions.Pango.FontMapNative; + +namespace Cairo.Extensions.Pango; + +/// +/// A represents the set of fonts available for a particular rendering system. +/// +/// +/// This is a virtual object with implementations being specific to particular rendering systems. +/// +public sealed unsafe class FontMap : CairoObject +{ + private FontMap(pango_font_map* fontMap) : base(fontMap, isOwnedByCairo: true, needsDestroy: false) { } + + /// + /// Gets a default PangoCairoFontMap to use with cairo. + /// + /// + /// The default PangoCairo fontmap for the current thread. This object is owned by Pango and must + /// not be freed (note: you still should Dispose the object, but here disposal won't free the native + /// resource). + /// + public static FontMap CairoFontMapGetDefault() + { + pango_font_map* fontMap = pango_cairo_font_map_get_default(); + return new FontMap(fontMap); + } + + protected override void DisposeCore(pango_font_map* handle) + => throw new InvalidOperationException("PangoFontMap must not be freed"); + + /// + /// Loads a font file with one or more fonts into the . + /// + /// Absolute path to the font file. + /// + /// The added fonts will take precedence over preexisting fonts with the same name. + /// + /// An error occured while loading the font from the file. + public void AddFontFile(string fileName) + { + this.CheckDisposed(); + ArgumentNullException.ThrowIfNull(fileName); + + GError* error = null; + + if (!pango_font_map_add_font_file(this.Handle, fileName, &error)) + { + throw new PangoException(error); + } + } + + /// + /// List all families for a fontmap. + /// + /// All families for a fontmap. + /// + /// Note that the returned families are not in any particular order. + /// + public FontFamilyIterator ListFamilies() => new(this.Handle); + + public struct FontFamilyIterator : IDisposable + { + private readonly pango_font_map* _fontMap; + + private pango_font_family** _families; + private int _count; + private int _i = -1; + + internal FontFamilyIterator(pango_font_map* fontMap) => _fontMap = fontMap; + + public FontFamilyIterator GetEnumerator() => this; + + public readonly void Dispose() + { + if (_families is not null) + { + GObjectNative.g_free(_families); + } + } + + public readonly FontFamily Current + { + get + { + if (_i < 0) + { + throw new InvalidOperationException("Must call MoveNext() before accessing the first element"); + } + + pango_font_family* family = _families[_i]; + return new FontFamily(family); + } + } + + public bool MoveNext() + { + if (_i < 0) + { + fixed (pango_font_family*** families = &_families) + fixed (int* count = &_count) + { + pango_font_map_list_families(_fontMap, families, count); + } + + _i = 0; + } + else + { + _i++; + } + + return _i < _count; + } + } +} diff --git a/source/CairoSharp.Extensions/Pango/FontMapNative.cs b/source/CairoSharp.Extensions/Pango/FontMapNative.cs new file mode 100644 index 0000000..8f4b734 --- /dev/null +++ b/source/CairoSharp.Extensions/Pango/FontMapNative.cs @@ -0,0 +1,25 @@ +// (c) gfoidl, all rights reserved + +using System.Runtime.InteropServices; +using Cairo.Extensions.GObject; + +namespace Cairo.Extensions.Pango; + +internal static unsafe partial class FontMapNative +{ + // https://docs.gtk.org/PangoCairo/type_func.FontMap.get_default.html + [LibraryImport(PangoNative.LibPangoCairoName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial pango_font_map* pango_cairo_font_map_get_default(); + + // https://docs.gtk.org/Pango/method.FontMap.add_font_file.html + [LibraryImport(PangoNative.LibPangoName, StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + [return: MarshalAs(UnmanagedType.U4)] + internal static partial bool pango_font_map_add_font_file(pango_font_map* fontmap, string filename, GError** error); + + // https://docs.gtk.org/Pango/method.FontMap.list_families.html + [LibraryImport(PangoNative.LibPangoName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial void pango_font_map_list_families(pango_font_map* fontmap, pango_font_family*** families, int* n_families); +} diff --git a/source/CairoSharp.Extensions/Pango/PangoException.cs b/source/CairoSharp.Extensions/Pango/PangoException.cs new file mode 100644 index 0000000..870f52a --- /dev/null +++ b/source/CairoSharp.Extensions/Pango/PangoException.cs @@ -0,0 +1,15 @@ +// (c) gfoidl, all rights reserved + +using Cairo.Extensions.GObject; + +namespace Cairo.Extensions.Pango; + +[Serializable] +public class PangoException : GObjectException +{ + public PangoException() { } + public PangoException(string message) : base(message) { } + public PangoException(string message, Exception inner) : base(message, inner) { } + + internal unsafe PangoException(GError* error) : base(error) { } +} diff --git a/source/CairoSharp.Extensions/Pango/PangoLayout.cs b/source/CairoSharp.Extensions/Pango/PangoLayout.cs index 71babe0..91fb18d 100644 --- a/source/CairoSharp.Extensions/Pango/PangoLayout.cs +++ b/source/CairoSharp.Extensions/Pango/PangoLayout.cs @@ -48,6 +48,7 @@ public PangoLayout(CairoContext cr, double resolution = 72d) : base(Create(cr)) } } + [StackTraceHidden] private static pango_layout* Create(CairoContext cr) { ArgumentNullException.ThrowIfNull(cr); diff --git a/source/CairoSharp.Extensions/Pango/PangoNative.Resolver.cs b/source/CairoSharp.Extensions/Pango/PangoNative.Resolver.cs index cd28505..4db3c60 100644 --- a/source/CairoSharp.Extensions/Pango/PangoNative.Resolver.cs +++ b/source/CairoSharp.Extensions/Pango/PangoNative.Resolver.cs @@ -23,9 +23,15 @@ static partial class PangoNative "libgobject-2.0-0.dll", // Windows "libgobject-2.0.0.dylib"); // MacOS + private static readonly Native.LibNames s_glibLibNames = new( + GObjectNative.LibGLibName, // Linux + "libglib-2.0-0.dll", // Windows + "libglib-2.0.0.dylib"); // MacOS + private static nint s_libPangoHandle; private static nint s_libPangoCairoHandle; private static nint s_libGObjectHandle; + private static nint s_libGLibHandle; //------------------------------------------------------------------------- [DisallowNull] public static DllImportResolver? DllImportResolver { get; set; } = static (libraryName, assembly, searchPath) => @@ -35,6 +41,7 @@ static partial class PangoNative LibPangoName => ResolveCore(ref s_libPangoHandle , s_pangoLibNames), LibPangoCairoName => ResolveCore(ref s_libPangoCairoHandle, s_pangoCairoLibNames), GObjectNative.LibGObjectName => ResolveCore(ref s_libGObjectHandle , s_gobjectLibNames), + GObjectNative.LibGLibName => ResolveCore(ref s_libGLibHandle , s_glibLibNames), _ => default }; }; diff --git a/source/CairoSharp.Extensions/Pango/PangoNative.cs b/source/CairoSharp.Extensions/Pango/PangoNative.cs index de7453e..4562499 100644 --- a/source/CairoSharp.Extensions/Pango/PangoNative.cs +++ b/source/CairoSharp.Extensions/Pango/PangoNative.cs @@ -1,23 +1,17 @@ // (c) gfoidl, all rights reserved -using System.ComponentModel; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; using Cairo.Extensions.GObject; namespace Cairo.Extensions.Pango; -[EditorBrowsable(EditorBrowsableState.Never)] -public struct pango_layout; -internal struct pango_context; -internal struct pango_font_description; -internal struct pango_attr_list; - public static unsafe partial class PangoNative { public const string LibPangoName = "libpango.so.1"; public const string LibPangoCairoName = "libpangocairo.so.1"; public const string LibGObjectName = GObjectNative.LibGObjectName; + public const string LibGLibName = GObjectNative.LibGLibName; // https://docs.gtk.org/PangoCairo/func.create_layout.html [LibraryImport(LibPangoCairoName)] diff --git a/source/CairoSharp.Extensions/Pango/ReadMe.md b/source/CairoSharp.Extensions/Pango/ReadMe.md index 94225a2..5a3b762 100644 --- a/source/CairoSharp.Extensions/Pango/ReadMe.md +++ b/source/CairoSharp.Extensions/Pango/ReadMe.md @@ -26,6 +26,7 @@ if (OperatingSystem.IsWindows()) PangoNative.LibPangoName => IOPath.Combine(@"C:\Program Files\msys64\ucrt64\bin", "libpango-1.0-0.dll"), PangoNative.LibPangoCairoName => IOPath.Combine(@"C:\Program Files\msys64\ucrt64\bin", "libpangocairo-1.0-0.dll"), PangoNative.LibGObjectName => IOPath.Combine(@"C:\Program Files\msys64\ucrt64\bin", "libgobject-2.0-0.dll"), + PangoNative.LibGLibName => IOPath.Combine(@"C:\Program Files\msys64\ucrt64\bin", "libglib-2.0-0.dll"), _ => null }; diff --git a/source/CairoSharp.Extensions/Pango/TypeDefs.cs b/source/CairoSharp.Extensions/Pango/TypeDefs.cs new file mode 100644 index 0000000..20f9efb --- /dev/null +++ b/source/CairoSharp.Extensions/Pango/TypeDefs.cs @@ -0,0 +1,18 @@ +// (c) gfoidl, all rights reserved + +using System.ComponentModel; + +namespace Cairo.Extensions.Pango; + +[EditorBrowsable(EditorBrowsableState.Never)] +public struct pango_layout; + +internal struct pango_context; +internal struct pango_font_description; +internal struct pango_attr_list; + +[EditorBrowsable(EditorBrowsableState.Never)] +public struct pango_font_map; + +[EditorBrowsable(EditorBrowsableState.Never)] +public struct pango_font_family; From fc9e23714279b0eabc7d45d844a6cc16544d769c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Foidl?= Date: Sat, 6 Dec 2025 12:40:08 +0100 Subject: [PATCH 02/10] Demo with Pango and recording surface (it just works :-)) --- demos/PangoDemo/Program.cs | 82 +++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 9 deletions(-) diff --git a/demos/PangoDemo/Program.cs b/demos/PangoDemo/Program.cs index 580a18f..36bfe19 100644 --- a/demos/PangoDemo/Program.cs +++ b/demos/PangoDemo/Program.cs @@ -8,6 +8,7 @@ using Cairo.Extensions.Pango; using Cairo.Fonts; using Cairo.Surfaces.PDF; +using Cairo.Surfaces.Recording; using Cairo.Surfaces.SVG; using Cairo.Surfaces.Tee; using IOPath = System.IO.Path; @@ -38,12 +39,13 @@ Directory.CreateDirectory("output"); Environment.CurrentDirectory = IOPath.Combine(Environment.CurrentDirectory, "output"); -DemoComparisonWithCairoText(); -DemoFromPangoDocsWithTextAroundCircle(); -DemoPangoFeatures(); -DemoFontMap(); +ComparisonWithCairoTextDemo(); +PangoDocsDemoWithTextAroundCircle(); +PangoFeaturesDemo(); +FontMapDemo(); +RecordingSurfaceDemo(); //----------------------------------------------------------------------------- -static void DemoComparisonWithCairoText() +static void ComparisonWithCairoTextDemo() { const int Width = 600; const int Height = 150; @@ -105,7 +107,7 @@ static void DemoComparisonWithCairoText() tee.WriteToPng("cairo_comparison.png"); } //----------------------------------------------------------------------------- -static void DemoFromPangoDocsWithTextAroundCircle() +static void PangoDocsDemoWithTextAroundCircle() { // Demo from https://docs.gtk.org/PangoCairo/pango_cairo.html @@ -163,7 +165,7 @@ static void DemoFromPangoDocsWithTextAroundCircle() tee.WriteToPng("sample_from_docs.png"); } //----------------------------------------------------------------------------- -static void DemoPangoFeatures() +static void PangoFeaturesDemo() { const int Width = 600; const int Height = 620; @@ -350,7 +352,7 @@ static void DemoPangoFeatures() tee.WriteToPng("features.png"); } //----------------------------------------------------------------------------- -static void DemoFontMap() +static void FontMapDemo() { Console.WriteLine(); using StreamWriter sw = File.CreateText("font-families.csv"); @@ -384,7 +386,7 @@ static void DemoFontMap() tee.Add(pdf); using PangoLayout pangoLayout = new(cr); - double curY = 10; + double curY = 10; cr.MoveTo(10, curY); pangoLayout.SetFontDescriptionFromString($"{fontFamilyName} Normal 22"); @@ -409,3 +411,65 @@ static void DemoFontMap() tee.WriteToPng("font-map.png"); } +//----------------------------------------------------------------------------- +static void RecordingSurfaceDemo() +{ + using FontMap fontMap = FontMap.CairoFontMapGetDefault(); + fontMap.AddFontFile(IOPath.Combine(AppContext.BaseDirectory, "fonts", "SanRemo.ttf")); + + List families = []; + foreach (FontFamily fontFamily in fontMap.ListFamilies()) + { + families.Add(fontFamily); + } + + string fontFamilyName = ""; + foreach (FontFamily fontFamily in families.OrderBy(f => f.Name)) + { + fontFamilyName = fontFamily.Name; + } + + using RecordingSurface surface = new(); + using (CairoContext cr = new(surface)) + { + using PangoLayout pangoLayout = new(cr); + double curY = 10; + + cr.MoveTo(10, curY); + pangoLayout.SetFontDescriptionFromString($"{fontFamilyName} Normal 22"); + pangoLayout.SetText($"Font: {fontFamilyName}"); + pangoLayout.ShowLayout(); + pangoLayout.GetSize(out double width, out double height); + curY += height; + + cr.MoveTo(10, curY); + pangoLayout.SetFontDescriptionFromString("Viner Hand ITC, Normal 22"); + pangoLayout.SetText("Font: Viner Hand ITC"); + pangoLayout.ShowLayout(); + pangoLayout.GetSize(out width, out height); + curY += height; + + cr.MoveTo(10, curY); + pangoLayout.SetFontDescriptionFromString("San Remo, Normal 22"); + pangoLayout.SetText("Font: San Remo"); + pangoLayout.ShowLayout(); + pangoLayout.GetSize(out width, out height); + curY += height; + } + + Rectangle recordingExtents = surface.GetInkExtents(); + const int Padding = 10; + + using SvgSurface svg = new("recording.svg", recordingExtents.Width + 2 * Padding, recordingExtents.Height + 2 * Padding); + using PdfSurface pdf = new("recording.pdf", recordingExtents.Width + 2 * Padding, recordingExtents.Height + 2 * Padding); + using TeeSurface tee = new(svg); + using (CairoContext cr = new(tee)) + { + tee.Add(pdf); + + cr.SetSourceSurface(surface, Padding - recordingExtents.X, Padding - recordingExtents.Y); + cr.Paint(); + } + + tee.WriteToPng("recording.png"); +} From 0434b496eec2ede803119f553168d1130bdeefe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Foidl?= Date: Sat, 6 Dec 2025 13:00:02 +0100 Subject: [PATCH 03/10] FontListDemo that prints a line for each font in the default font map --- demos/PangoDemo/Program.cs | 66 +++++++++++++++++++ source/CairoSharp.Extensions/Pango/FontMap.cs | 3 + source/CairoSharp/Surfaces/StreamSurface.cs | 1 + source/CairoSharp/Surfaces/Surface.cs | 4 +- 4 files changed, 72 insertions(+), 2 deletions(-) diff --git a/demos/PangoDemo/Program.cs b/demos/PangoDemo/Program.cs index 36bfe19..82d9ccd 100644 --- a/demos/PangoDemo/Program.cs +++ b/demos/PangoDemo/Program.cs @@ -44,6 +44,7 @@ PangoFeaturesDemo(); FontMapDemo(); RecordingSurfaceDemo(); +FontListDemo(); //----------------------------------------------------------------------------- static void ComparisonWithCairoTextDemo() { @@ -410,6 +411,8 @@ static void FontMapDemo() curY += height; tee.WriteToPng("font-map.png"); + + families.ForEach(f => f.Dispose()); } //----------------------------------------------------------------------------- static void RecordingSurfaceDemo() @@ -472,4 +475,67 @@ static void RecordingSurfaceDemo() } tee.WriteToPng("recording.png"); + + families.ForEach(f => f.Dispose()); +} +//----------------------------------------------------------------------------- +static void FontListDemo() +{ + using FontMap fontMap = FontMap.CairoFontMapGetDefault(); + fontMap.AddFontFile(IOPath.Combine(AppContext.BaseDirectory, "fonts", "SanRemo.ttf")); + + List families = []; + foreach (FontFamily fontFamily in fontMap.ListFamilies()) + { + families.Add(fontFamily); + } + families = [.. families + .OrderBy(f => f.IsMonospace) + .ThenBy (f => f.Name) + ]; + + using RecordingSurface surface = new(); + using (CairoContext cr = new(surface)) + { + using PangoLayout pangoLayout = new(cr); + double curY = 10; + + foreach(FontFamily fontFamily in families) + { + cr.MoveTo(10, curY); + string familyName = fontFamily.Name; + pangoLayout.SetFontDescriptionFromString($"{familyName}, Normal 22"); // note: trailing , + pangoLayout.SetText($"Font: {familyName}; AV and VA to see kerning or not"); + pangoLayout.ShowLayout(); + pangoLayout.GetSize(out double width, out double height); + curY += height; + } + } + + Rectangle recordingExtents = surface.GetInkExtents(); + const int Padding = 10; + double surfaceWidth = recordingExtents.Width + 2 * Padding; + double surfaceHeight = recordingExtents.Height + 2 * Padding; + + using SvgSurface svg = new("font-list.svg", surfaceWidth, surfaceHeight); + using PdfSurface pdf = new("font-list.pdf", surfaceWidth, surfaceHeight); + using TeeSurface tee = new(svg); + using (CairoContext cr = new(tee)) + { + tee.Add(pdf); + + cr.Color = KnownColors.White; + cr.Paint(); + cr.Color = Color.Default; + cr.LineWidth = 4; + cr.Rectangle(0, 0, surfaceWidth, surfaceHeight); + cr.Stroke(); + + cr.SetSourceSurface(surface, Padding - recordingExtents.X, Padding - recordingExtents.Y); + cr.Paint(); + } + + tee.WriteToPng("font-list.png"); + + families.ForEach(f => f.Dispose()); } diff --git a/source/CairoSharp.Extensions/Pango/FontMap.cs b/source/CairoSharp.Extensions/Pango/FontMap.cs index ea51742..31a4cb1 100644 --- a/source/CairoSharp.Extensions/Pango/FontMap.cs +++ b/source/CairoSharp.Extensions/Pango/FontMap.cs @@ -62,6 +62,9 @@ public void AddFontFile(string fileName) /// public FontFamilyIterator ListFamilies() => new(this.Handle); + /// + /// Enumerator for . + /// public struct FontFamilyIterator : IDisposable { private readonly pango_font_map* _fontMap; diff --git a/source/CairoSharp/Surfaces/StreamSurface.cs b/source/CairoSharp/Surfaces/StreamSurface.cs index 07394e7..0468644 100644 --- a/source/CairoSharp/Surfaces/StreamSurface.cs +++ b/source/CairoSharp/Surfaces/StreamSurface.cs @@ -29,6 +29,7 @@ protected override void DisposeCore(cairo_surface_t* surface) // Need to free the surface first, so that the write function (if any) // can be called on a valid handle. // So it's like: dispose -> write func -> stream handle free + // See also Surface.Finish for more info. if (_stateHandle.IsAllocated) { _stateHandle.Free(); diff --git a/source/CairoSharp/Surfaces/Surface.cs b/source/CairoSharp/Surfaces/Surface.cs index ee73707..3119abd 100644 --- a/source/CairoSharp/Surfaces/Surface.cs +++ b/source/CairoSharp/Surfaces/Surface.cs @@ -184,12 +184,12 @@ public Status Status /// This method finishes the surface and drops all references to external resources. For example, /// for the Xlib backend it means that cairo will no longer access the drawable, which can be freed. /// After calling the only valid operations on a surface are checking status, getting - /// and setting user, referencing and destroying, and flushing and finishing it. Further drawing to the + /// and setting user data, referencing and destroying, and flushing and finishing it. Further drawing to the /// surface will not affect the surface but will instead trigger a error. /// /// /// When the last call to decreases the reference count to zero, - /// cairo will call cairo_surface_finish() if it hasn't been called already, before freeing the resources + /// cairo will call cairo_surface_finish() if it hasn't been called already, before freeing the resources /// associated with the surface. /// public void Finish() From 156c2f807d5f50e04a834c72b76d70e886b92328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Foidl?= Date: Sat, 6 Dec 2025 13:55:14 +0100 Subject: [PATCH 04/10] FontFace added --- .../CairoSharp.Extensions/Pango/FontFace.cs | 141 ++++++++++++++++++ .../Pango/FontFaceNative.cs | 33 ++++ .../CairoSharp.Extensions/Pango/FontFamily.cs | 96 ++++++++++++ .../Pango/FontFamilyNative.cs | 13 ++ source/CairoSharp.Extensions/Pango/FontMap.cs | 8 +- .../Pango/PangoLayout.cs | 19 +++ .../CairoSharp.Extensions/Pango/TypeDefs.cs | 3 + 7 files changed, 311 insertions(+), 2 deletions(-) create mode 100644 source/CairoSharp.Extensions/Pango/FontFace.cs create mode 100644 source/CairoSharp.Extensions/Pango/FontFaceNative.cs diff --git a/source/CairoSharp.Extensions/Pango/FontFace.cs b/source/CairoSharp.Extensions/Pango/FontFace.cs new file mode 100644 index 0000000..8e4c63e --- /dev/null +++ b/source/CairoSharp.Extensions/Pango/FontFace.cs @@ -0,0 +1,141 @@ +// (c) gfoidl, all rights reserved + +using Cairo.Extensions.GObject; +using static Cairo.Extensions.Pango.FontFaceNative; + +namespace Cairo.Extensions.Pango; + +/// +/// A is used to represent a group of fonts with the same family, +/// slant, weight, and width, but varying sizes. +/// +public sealed unsafe class FontFace : CairoObject +{ + private readonly FontFamily _family; + + internal FontFace(FontFamily family, pango_font_face* face) : base(face, isOwnedByCairo: true, needsDestroy: false) + => _family = family; + + protected override void DisposeCore(pango_font_face* handle) + => throw new InvalidOperationException("PangoFontFace must not be freed"); + + /// + /// Gets a name representing the style of this face. + /// + /// + /// Note that a font family may contain multiple faces with the same name (e.g. a variable + /// and a non-variable face for the same style). + /// + public string FaceName + { + get + { + this.CheckDisposed(); + return pango_font_face_get_face_name(this.Handle); + } + } + + /// + /// Returns whether a is synthesized. + /// + /// + /// This will be the case if the underlying font rendering engine creates this face from + /// another face, by shearing, emboldening, lightening or modifying it in some other way. + /// + public bool IsSynthesized + { + get + { + this.CheckDisposed(); + return pango_font_face_is_synthesized(this.Handle); + } + } + + /// + /// Gets the that face belongs to. + /// + public FontFamily FontFamily => _family; + + /// + /// List the available sizes for a font. + /// + /// List of sizes for the font face, or an empty list for scalable fonts. + /// + /// This is only applicable to bitmap fonts. The sizes returned are in cairo units and + /// are sorted in ascending order. + /// + /// Note: the native Pango API returns the values in Pango units, whilst this API + /// returns cairo units.
+ /// + /// Pango unit = cairo unit * Pango.Scale + /// + ///
+ ///
+ public FontFaceSizeIterator ListSizes() + { + this.CheckDisposed(); + return new FontFaceSizeIterator(this.Handle); + } + + /// + /// Enumerator for the sizes in the . + /// + public struct FontFaceSizeIterator : IDisposable + { + private readonly pango_font_face* _face; + + private int* _sizes; + private int _count; + private int _i; + + internal FontFaceSizeIterator(pango_font_face* face) => _face = face; + + public readonly FontFaceSizeIterator GetEnumerator() => this; + + public readonly void Dispose() + { + if (_sizes is not null) + { + GObjectNative.g_free(_sizes); + } + } + + public readonly double Current + { + get + { + if (_i < 0) + { + throw new InvalidOperationException("Must call MoveNext() before accessing the first element"); + } + + return _sizes[_i] / (double)Pango.Scale; + } + } + + public bool MoveNext() + { + if (_i < 0) + { + fixed (int** sizes = &_sizes) + fixed (int* count = &_count) + { + pango_font_face_list_sizes(_face, sizes, count); + } + + // For scalable fonts, stores null at the location pointed to by sizes + // and 0 at the location pointed to by n_sizes. + if (_sizes is null || _count == 0) + { + return false; + } + } + else + { + _i++; + } + + return _i < _count; + } + } +} diff --git a/source/CairoSharp.Extensions/Pango/FontFaceNative.cs b/source/CairoSharp.Extensions/Pango/FontFaceNative.cs new file mode 100644 index 0000000..b512008 --- /dev/null +++ b/source/CairoSharp.Extensions/Pango/FontFaceNative.cs @@ -0,0 +1,33 @@ +// (c) gfoidl, all rights reserved + +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace Cairo.Extensions.Pango; + +internal static unsafe partial class FontFaceNative +{ + // https://docs.gtk.org/Pango/method.FontFace.describe.html + [LibraryImport(PangoNative.LibPangoName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial pango_font_description* pango_font_face_describe(pango_font_face* face); + + // https://docs.gtk.org/Pango/method.FontFace.get_face_name.html + [LibraryImport(PangoNative.LibPangoName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + [return: MarshalUsing(typeof(NativeConstCharMarshaller))] + [SuppressGCTransition] + internal static partial string pango_font_face_get_face_name(pango_font_face* face); + + // https://docs.gtk.org/Pango/method.FontFace.is_synthesized.html + [LibraryImport(PangoNative.LibPangoName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + [return: MarshalAs(UnmanagedType.U4)] + [SuppressGCTransition] + internal static partial bool pango_font_face_is_synthesized(pango_font_face* face); + + // https://docs.gtk.org/Pango/method.FontFace.list_sizes.html + [LibraryImport(PangoNative.LibPangoName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial void pango_font_face_list_sizes(pango_font_face* face, int** sizes, int* n_sizes); +} diff --git a/source/CairoSharp.Extensions/Pango/FontFamily.cs b/source/CairoSharp.Extensions/Pango/FontFamily.cs index a84b19a..62b0a20 100644 --- a/source/CairoSharp.Extensions/Pango/FontFamily.cs +++ b/source/CairoSharp.Extensions/Pango/FontFamily.cs @@ -1,5 +1,6 @@ // (c) gfoidl, all rights reserved +using Cairo.Extensions.GObject; using static Cairo.Extensions.Pango.FontFamilyNative; namespace Cairo.Extensions.Pango; @@ -72,4 +73,99 @@ public override string ToString() => $""" IsMonospace: {this.IsMonospace} IsVariable: {this.IsVariable} """; + + /// + /// Gets the of family with the given name. + /// + /// + /// The name of a face. If the name is null, the family’s default face + /// (fontconfig calls it "Regular") will be returned. + /// + /// + /// The , or null if no face with the given name exists. + /// + public FontFace? GetFace(string? name) + { + this.CheckDisposed(); + + pango_font_face* face = pango_font_family_get_face(this.Handle, name); + + return face is not null + ? new FontFace(this, face) + : null; + } + + /// + /// Lists the different font faces that make up . + /// + /// List of font faces for the family. + /// + /// The faces in a family share a common design, but differ in slant, weight, width + /// and other aspects. + /// + /// Note that the returned faces are not in any particular order, and multiple faces may + /// have the same name or characteristics. + /// + /// + public FontFaceIterator ListFaces() + { + this.CheckDisposed(); + return new FontFaceIterator(this); + } + + /// + /// Enumerator for for the . + /// + public struct FontFaceIterator : IDisposable + { + private readonly FontFamily _family; + + private pango_font_face** _faces; + private int _count; + private int _i = -1; + + internal FontFaceIterator(FontFamily family) => _family = family; + + public readonly FontFaceIterator GetEnumerator() => this; + + public readonly void Dispose() + { + if (_faces is not null) + { + GObjectNative.g_free(_faces); + } + } + + public readonly FontFace Current + { + get + { + if (_i < 0) + { + throw new InvalidOperationException("Must call MoveNext() before accessing the first element"); + } + + pango_font_face* face = _faces[_i]; + return new FontFace(_family, face); + } + } + + public bool MoveNext() + { + if (_i < 0) + { + fixed (pango_font_face*** faces = &_faces) + fixed (int* count = &_count) + { + pango_font_family_list_faces(_family.Handle, faces, count); + } + } + else + { + _i++; + } + + return _i < _count; + } + } } diff --git a/source/CairoSharp.Extensions/Pango/FontFamilyNative.cs b/source/CairoSharp.Extensions/Pango/FontFamilyNative.cs index 184d490..04249a2 100644 --- a/source/CairoSharp.Extensions/Pango/FontFamilyNative.cs +++ b/source/CairoSharp.Extensions/Pango/FontFamilyNative.cs @@ -11,17 +11,30 @@ internal static unsafe partial class FontFamilyNative [LibraryImport(PangoNative.LibPangoName)] [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] [return: MarshalUsing(typeof(NativeConstCharMarshaller))] + [SuppressGCTransition] internal static partial string pango_font_family_get_name(pango_font_family* family); // https://docs.gtk.org/Pango/method.FontFamily.is_monospace.html [LibraryImport(PangoNative.LibPangoName)] [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] [return: MarshalAs(UnmanagedType.U4)] + [SuppressGCTransition] internal static partial bool pango_font_family_is_monospace(pango_font_family* family); // https://docs.gtk.org/Pango/method.FontFamily.is_variable.html [LibraryImport(PangoNative.LibPangoName)] [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] [return: MarshalAs(UnmanagedType.U4)] + [SuppressGCTransition] internal static partial bool pango_font_family_is_variable(pango_font_family* family); + + // https://docs.gtk.org/Pango/method.FontFamily.list_faces.html + [LibraryImport(PangoNative.LibPangoName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial void pango_font_family_list_faces(pango_font_family* family, pango_font_face*** face, int* n_faces); + + // https://docs.gtk.org/Pango/method.FontFamily.get_face.html + [LibraryImport(PangoNative.LibPangoName, StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial pango_font_face* pango_font_family_get_face(pango_font_family* family, string? name); } diff --git a/source/CairoSharp.Extensions/Pango/FontMap.cs b/source/CairoSharp.Extensions/Pango/FontMap.cs index 31a4cb1..45729c1 100644 --- a/source/CairoSharp.Extensions/Pango/FontMap.cs +++ b/source/CairoSharp.Extensions/Pango/FontMap.cs @@ -60,7 +60,11 @@ public void AddFontFile(string fileName) /// /// Note that the returned families are not in any particular order. /// - public FontFamilyIterator ListFamilies() => new(this.Handle); + public FontFamilyIterator ListFamilies() + { + this.CheckDisposed(); + return new FontFamilyIterator(this.Handle); + } /// /// Enumerator for . @@ -75,7 +79,7 @@ public struct FontFamilyIterator : IDisposable internal FontFamilyIterator(pango_font_map* fontMap) => _fontMap = fontMap; - public FontFamilyIterator GetEnumerator() => this; + public readonly FontFamilyIterator GetEnumerator() => this; public readonly void Dispose() { diff --git a/source/CairoSharp.Extensions/Pango/PangoLayout.cs b/source/CairoSharp.Extensions/Pango/PangoLayout.cs index 91fb18d..2a1a745 100644 --- a/source/CairoSharp.Extensions/Pango/PangoLayout.cs +++ b/source/CairoSharp.Extensions/Pango/PangoLayout.cs @@ -100,6 +100,25 @@ public void SetFontDescriptionFromString(string? fontDescription) } } + /// + /// Sets the font description that matches the face. + /// + /// The font face. + /// + /// The resulting font description will have the family, style, variant, weight and stretch + /// of the face, but its size field will be unset. + /// + public void SetFontDescription(FontFace fontFace) + { + this.CheckDisposed(); + fontFace.CheckDisposed(); + ArgumentNullException.ThrowIfNull(fontFace); + + pango_font_description* desc = FontFaceNative.pango_font_face_describe(fontFace.Handle); + pango_layout_set_font_description(this.Handle, desc); + pango_font_description_free(desc); + } + /// /// Determines the logical width and height of a in cairo units. /// diff --git a/source/CairoSharp.Extensions/Pango/TypeDefs.cs b/source/CairoSharp.Extensions/Pango/TypeDefs.cs index 20f9efb..0097afb 100644 --- a/source/CairoSharp.Extensions/Pango/TypeDefs.cs +++ b/source/CairoSharp.Extensions/Pango/TypeDefs.cs @@ -16,3 +16,6 @@ public struct pango_font_map; [EditorBrowsable(EditorBrowsableState.Never)] public struct pango_font_family; + +[EditorBrowsable(EditorBrowsableState.Never)] +public struct pango_font_face; From 7ca816907bb715901c5c5717e1462172a89cd508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Foidl?= Date: Sat, 6 Dec 2025 13:57:43 +0100 Subject: [PATCH 05/10] Naming of type with Pango prefix to avoid clashes with cairo names --- demos/PangoDemo/Program.cs | 25 +++++++++-------- .../Pango/{FontFace.cs => PangoFontFace.cs} | 18 ++++++------ ...ntFaceNative.cs => PangoFontFaceNative.cs} | 2 +- .../{FontFamily.cs => PangoFontFamily.cs} | 28 +++++++++---------- ...milyNative.cs => PangoFontFamilyNative.cs} | 2 +- .../Pango/{FontMap.cs => PangoFontMap.cs} | 20 ++++++------- ...FontMapNative.cs => PangoFontMapNative.cs} | 2 +- .../Pango/PangoLayout.cs | 4 +-- 8 files changed, 51 insertions(+), 50 deletions(-) rename source/CairoSharp.Extensions/Pango/{FontFace.cs => PangoFontFace.cs} (84%) rename source/CairoSharp.Extensions/Pango/{FontFaceNative.cs => PangoFontFaceNative.cs} (96%) rename source/CairoSharp.Extensions/Pango/{FontFamily.cs => PangoFontFamily.cs} (81%) rename source/CairoSharp.Extensions/Pango/{FontFamilyNative.cs => PangoFontFamilyNative.cs} (97%) rename source/CairoSharp.Extensions/Pango/{FontMap.cs => PangoFontMap.cs} (84%) rename source/CairoSharp.Extensions/Pango/{FontMapNative.cs => PangoFontMapNative.cs} (95%) diff --git a/demos/PangoDemo/Program.cs b/demos/PangoDemo/Program.cs index 82d9ccd..71d9204 100644 --- a/demos/PangoDemo/Program.cs +++ b/demos/PangoDemo/Program.cs @@ -359,17 +359,17 @@ static void FontMapDemo() using StreamWriter sw = File.CreateText("font-families.csv"); sw.WriteLine("Name;IsMonospace;IsVariable"); - using FontMap fontMap = FontMap.CairoFontMapGetDefault(); + using PangoFontMap fontMap = PangoFontMap.CairoFontMapGetDefault(); fontMap.AddFontFile(IOPath.Combine(AppContext.BaseDirectory, "fonts", "SanRemo.ttf")); - List families = []; - foreach (FontFamily fontFamily in fontMap.ListFamilies()) + List families = []; + foreach (PangoFontFamily fontFamily in fontMap.ListFamilies()) { families.Add(fontFamily); } string fontFamilyName = ""; - foreach (FontFamily fontFamily in families.OrderBy(f => f.Name)) + foreach (PangoFontFamily fontFamily in families.OrderBy(f => f.Name)) { fontFamilyName = fontFamily.Name; @@ -417,17 +417,17 @@ static void FontMapDemo() //----------------------------------------------------------------------------- static void RecordingSurfaceDemo() { - using FontMap fontMap = FontMap.CairoFontMapGetDefault(); + using PangoFontMap fontMap = PangoFontMap.CairoFontMapGetDefault(); fontMap.AddFontFile(IOPath.Combine(AppContext.BaseDirectory, "fonts", "SanRemo.ttf")); - List families = []; - foreach (FontFamily fontFamily in fontMap.ListFamilies()) + List families = []; + foreach (PangoFontFamily fontFamily in fontMap.ListFamilies()) { families.Add(fontFamily); } string fontFamilyName = ""; - foreach (FontFamily fontFamily in families.OrderBy(f => f.Name)) + foreach (PangoFontFamily fontFamily in families.OrderBy(f => f.Name)) { fontFamilyName = fontFamily.Name; } @@ -481,11 +481,11 @@ static void RecordingSurfaceDemo() //----------------------------------------------------------------------------- static void FontListDemo() { - using FontMap fontMap = FontMap.CairoFontMapGetDefault(); + using PangoFontMap fontMap = PangoFontMap.CairoFontMapGetDefault(); fontMap.AddFontFile(IOPath.Combine(AppContext.BaseDirectory, "fonts", "SanRemo.ttf")); - List families = []; - foreach (FontFamily fontFamily in fontMap.ListFamilies()) + List families = []; + foreach (PangoFontFamily fontFamily in fontMap.ListFamilies()) { families.Add(fontFamily); } @@ -500,10 +500,11 @@ static void FontListDemo() using PangoLayout pangoLayout = new(cr); double curY = 10; - foreach(FontFamily fontFamily in families) + foreach (PangoFontFamily fontFamily in families) { cr.MoveTo(10, curY); string familyName = fontFamily.Name; + pangoLayout.SetFontDescriptionFromString($"{familyName}, Normal 22"); // note: trailing , pangoLayout.SetText($"Font: {familyName}; AV and VA to see kerning or not"); pangoLayout.ShowLayout(); diff --git a/source/CairoSharp.Extensions/Pango/FontFace.cs b/source/CairoSharp.Extensions/Pango/PangoFontFace.cs similarity index 84% rename from source/CairoSharp.Extensions/Pango/FontFace.cs rename to source/CairoSharp.Extensions/Pango/PangoFontFace.cs index 8e4c63e..5d7c7c0 100644 --- a/source/CairoSharp.Extensions/Pango/FontFace.cs +++ b/source/CairoSharp.Extensions/Pango/PangoFontFace.cs @@ -1,19 +1,19 @@ // (c) gfoidl, all rights reserved using Cairo.Extensions.GObject; -using static Cairo.Extensions.Pango.FontFaceNative; +using static Cairo.Extensions.Pango.PangoFontFaceNative; namespace Cairo.Extensions.Pango; /// -/// A is used to represent a group of fonts with the same family, +/// A is used to represent a group of fonts with the same family, /// slant, weight, and width, but varying sizes. /// -public sealed unsafe class FontFace : CairoObject +public sealed unsafe class PangoFontFace : CairoObject { - private readonly FontFamily _family; + private readonly PangoFontFamily _family; - internal FontFace(FontFamily family, pango_font_face* face) : base(face, isOwnedByCairo: true, needsDestroy: false) + internal PangoFontFace(PangoFontFamily family, pango_font_face* face) : base(face, isOwnedByCairo: true, needsDestroy: false) => _family = family; protected override void DisposeCore(pango_font_face* handle) @@ -36,7 +36,7 @@ public string FaceName } /// - /// Returns whether a is synthesized. + /// Returns whether a is synthesized. /// /// /// This will be the case if the underlying font rendering engine creates this face from @@ -52,9 +52,9 @@ public bool IsSynthesized } /// - /// Gets the that face belongs to. + /// Gets the that face belongs to. /// - public FontFamily FontFamily => _family; + public PangoFontFamily FontFamily => _family; /// /// List the available sizes for a font. @@ -78,7 +78,7 @@ public FontFaceSizeIterator ListSizes() } /// - /// Enumerator for the sizes in the . + /// Enumerator for the sizes in the . /// public struct FontFaceSizeIterator : IDisposable { diff --git a/source/CairoSharp.Extensions/Pango/FontFaceNative.cs b/source/CairoSharp.Extensions/Pango/PangoFontFaceNative.cs similarity index 96% rename from source/CairoSharp.Extensions/Pango/FontFaceNative.cs rename to source/CairoSharp.Extensions/Pango/PangoFontFaceNative.cs index b512008..8471177 100644 --- a/source/CairoSharp.Extensions/Pango/FontFaceNative.cs +++ b/source/CairoSharp.Extensions/Pango/PangoFontFaceNative.cs @@ -5,7 +5,7 @@ namespace Cairo.Extensions.Pango; -internal static unsafe partial class FontFaceNative +internal static unsafe partial class PangoFontFaceNative { // https://docs.gtk.org/Pango/method.FontFace.describe.html [LibraryImport(PangoNative.LibPangoName)] diff --git a/source/CairoSharp.Extensions/Pango/FontFamily.cs b/source/CairoSharp.Extensions/Pango/PangoFontFamily.cs similarity index 81% rename from source/CairoSharp.Extensions/Pango/FontFamily.cs rename to source/CairoSharp.Extensions/Pango/PangoFontFamily.cs index 62b0a20..d3ca2cc 100644 --- a/source/CairoSharp.Extensions/Pango/FontFamily.cs +++ b/source/CairoSharp.Extensions/Pango/PangoFontFamily.cs @@ -1,20 +1,20 @@ // (c) gfoidl, all rights reserved using Cairo.Extensions.GObject; -using static Cairo.Extensions.Pango.FontFamilyNative; +using static Cairo.Extensions.Pango.PangoFontFamilyNative; namespace Cairo.Extensions.Pango; /// -/// A is used to represent a family of related font faces. +/// A is used to represent a family of related font faces. /// /// /// The font faces in a family share a common design, but differ in slant, weight, width /// or other aspects. /// -public sealed unsafe class FontFamily : CairoObject +public sealed unsafe class PangoFontFamily : CairoObject { - internal FontFamily(pango_font_family* family) : base(family, isOwnedByCairo: true, needsDestroy: false) { } + internal PangoFontFamily(pango_font_family* family) : base(family, isOwnedByCairo: true, needsDestroy: false) { } protected override void DisposeCore(pango_font_family* handle) => throw new InvalidOperationException("PangoFontFamily must not be freed"); @@ -75,28 +75,28 @@ public override string ToString() => $""" """; /// - /// Gets the of family with the given name. + /// Gets the of family with the given name. /// /// /// The name of a face. If the name is null, the family’s default face /// (fontconfig calls it "Regular") will be returned. /// /// - /// The , or null if no face with the given name exists. + /// The , or null if no face with the given name exists. /// - public FontFace? GetFace(string? name) + public PangoFontFace? GetFace(string? name) { this.CheckDisposed(); pango_font_face* face = pango_font_family_get_face(this.Handle, name); return face is not null - ? new FontFace(this, face) + ? new PangoFontFace(this, face) : null; } /// - /// Lists the different font faces that make up . + /// Lists the different font faces that make up . /// /// List of font faces for the family. /// @@ -114,17 +114,17 @@ public FontFaceIterator ListFaces() } /// - /// Enumerator for for the . + /// Enumerator for for the . /// public struct FontFaceIterator : IDisposable { - private readonly FontFamily _family; + private readonly PangoFontFamily _family; private pango_font_face** _faces; private int _count; private int _i = -1; - internal FontFaceIterator(FontFamily family) => _family = family; + internal FontFaceIterator(PangoFontFamily family) => _family = family; public readonly FontFaceIterator GetEnumerator() => this; @@ -136,7 +136,7 @@ public readonly void Dispose() } } - public readonly FontFace Current + public readonly PangoFontFace Current { get { @@ -146,7 +146,7 @@ public readonly FontFace Current } pango_font_face* face = _faces[_i]; - return new FontFace(_family, face); + return new PangoFontFace(_family, face); } } diff --git a/source/CairoSharp.Extensions/Pango/FontFamilyNative.cs b/source/CairoSharp.Extensions/Pango/PangoFontFamilyNative.cs similarity index 97% rename from source/CairoSharp.Extensions/Pango/FontFamilyNative.cs rename to source/CairoSharp.Extensions/Pango/PangoFontFamilyNative.cs index 04249a2..248b523 100644 --- a/source/CairoSharp.Extensions/Pango/FontFamilyNative.cs +++ b/source/CairoSharp.Extensions/Pango/PangoFontFamilyNative.cs @@ -5,7 +5,7 @@ namespace Cairo.Extensions.Pango; -internal static unsafe partial class FontFamilyNative +internal static unsafe partial class PangoFontFamilyNative { // https://docs.gtk.org/Pango/method.FontFamily.get_name.html [LibraryImport(PangoNative.LibPangoName)] diff --git a/source/CairoSharp.Extensions/Pango/FontMap.cs b/source/CairoSharp.Extensions/Pango/PangoFontMap.cs similarity index 84% rename from source/CairoSharp.Extensions/Pango/FontMap.cs rename to source/CairoSharp.Extensions/Pango/PangoFontMap.cs index 45729c1..5508440 100644 --- a/source/CairoSharp.Extensions/Pango/FontMap.cs +++ b/source/CairoSharp.Extensions/Pango/PangoFontMap.cs @@ -1,19 +1,19 @@ // (c) gfoidl, all rights reserved using Cairo.Extensions.GObject; -using static Cairo.Extensions.Pango.FontMapNative; +using static Cairo.Extensions.Pango.PangoFontMapNative; namespace Cairo.Extensions.Pango; /// -/// A represents the set of fonts available for a particular rendering system. +/// A represents the set of fonts available for a particular rendering system. /// /// /// This is a virtual object with implementations being specific to particular rendering systems. /// -public sealed unsafe class FontMap : CairoObject +public sealed unsafe class PangoFontMap : CairoObject { - private FontMap(pango_font_map* fontMap) : base(fontMap, isOwnedByCairo: true, needsDestroy: false) { } + private PangoFontMap(pango_font_map* fontMap) : base(fontMap, isOwnedByCairo: true, needsDestroy: false) { } /// /// Gets a default PangoCairoFontMap to use with cairo. @@ -23,17 +23,17 @@ private FontMap(pango_font_map* fontMap) : base(fontMap, isOwnedByCairo: true, n /// not be freed (note: you still should Dispose the object, but here disposal won't free the native /// resource). /// - public static FontMap CairoFontMapGetDefault() + public static PangoFontMap CairoFontMapGetDefault() { pango_font_map* fontMap = pango_cairo_font_map_get_default(); - return new FontMap(fontMap); + return new PangoFontMap(fontMap); } protected override void DisposeCore(pango_font_map* handle) => throw new InvalidOperationException("PangoFontMap must not be freed"); /// - /// Loads a font file with one or more fonts into the . + /// Loads a font file with one or more fonts into the . /// /// Absolute path to the font file. /// @@ -67,7 +67,7 @@ public FontFamilyIterator ListFamilies() } /// - /// Enumerator for . + /// Enumerator for . /// public struct FontFamilyIterator : IDisposable { @@ -89,7 +89,7 @@ public readonly void Dispose() } } - public readonly FontFamily Current + public readonly PangoFontFamily Current { get { @@ -99,7 +99,7 @@ public readonly FontFamily Current } pango_font_family* family = _families[_i]; - return new FontFamily(family); + return new PangoFontFamily(family); } } diff --git a/source/CairoSharp.Extensions/Pango/FontMapNative.cs b/source/CairoSharp.Extensions/Pango/PangoFontMapNative.cs similarity index 95% rename from source/CairoSharp.Extensions/Pango/FontMapNative.cs rename to source/CairoSharp.Extensions/Pango/PangoFontMapNative.cs index 8f4b734..d74b750 100644 --- a/source/CairoSharp.Extensions/Pango/FontMapNative.cs +++ b/source/CairoSharp.Extensions/Pango/PangoFontMapNative.cs @@ -5,7 +5,7 @@ namespace Cairo.Extensions.Pango; -internal static unsafe partial class FontMapNative +internal static unsafe partial class PangoFontMapNative { // https://docs.gtk.org/PangoCairo/type_func.FontMap.get_default.html [LibraryImport(PangoNative.LibPangoCairoName)] diff --git a/source/CairoSharp.Extensions/Pango/PangoLayout.cs b/source/CairoSharp.Extensions/Pango/PangoLayout.cs index 2a1a745..d145bf1 100644 --- a/source/CairoSharp.Extensions/Pango/PangoLayout.cs +++ b/source/CairoSharp.Extensions/Pango/PangoLayout.cs @@ -108,13 +108,13 @@ public void SetFontDescriptionFromString(string? fontDescription) /// The resulting font description will have the family, style, variant, weight and stretch /// of the face, but its size field will be unset. /// - public void SetFontDescription(FontFace fontFace) + public void SetFontDescription(PangoFontFace fontFace) { this.CheckDisposed(); fontFace.CheckDisposed(); ArgumentNullException.ThrowIfNull(fontFace); - pango_font_description* desc = FontFaceNative.pango_font_face_describe(fontFace.Handle); + pango_font_description* desc = PangoFontFaceNative.pango_font_face_describe(fontFace.Handle); pango_layout_set_font_description(this.Handle, desc); pango_font_description_free(desc); } From 665365d7d43f05cf437d8bd6fe52735ce079dea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Foidl?= Date: Sat, 6 Dec 2025 16:33:03 +0100 Subject: [PATCH 06/10] Fixed font size in SetFontDescription --- demos/PangoDemo/Program.cs | 65 +++++++++++++++++-- .../Pango/PangoFontFace.cs | 2 +- .../Pango/PangoFontMap.cs | 13 ++++ .../Pango/PangoFontMapNative.cs | 5 ++ .../Pango/PangoLayout.cs | 10 ++- .../Pango/PangoNative.cs | 10 +++ 6 files changed, 99 insertions(+), 6 deletions(-) diff --git a/demos/PangoDemo/Program.cs b/demos/PangoDemo/Program.cs index 71d9204..240f4ad 100644 --- a/demos/PangoDemo/Program.cs +++ b/demos/PangoDemo/Program.cs @@ -40,6 +40,7 @@ Environment.CurrentDirectory = IOPath.Combine(Environment.CurrentDirectory, "output"); ComparisonWithCairoTextDemo(); +FontSelectionDemo(); PangoDocsDemoWithTextAroundCircle(); PangoFeaturesDemo(); FontMapDemo(); @@ -108,6 +109,57 @@ static void ComparisonWithCairoTextDemo() tee.WriteToPng("cairo_comparison.png"); } //----------------------------------------------------------------------------- +static void FontSelectionDemo() +{ + const int Width = 700; + const int Height = 100; + const string Text = "Hello from CairoSharp, w/o any AV and VA"; + + using SvgSurface svg = new("font-selection.svg", Width, Height); + using PdfSurface pdf = new("font-selection.pdf", Width, Height); + using TeeSurface tee = new(svg); + using CairoContext cr = new(tee); + tee.Add(pdf); + + cr.Color = KnownColors.White; + cr.Paint(); + cr.LineWidth = 6; + cr.Color = Color.Default; + cr.Rectangle(0, 0, Width, Height); + cr.Stroke(); + cr.LineWidth = 1; + + double curY = 10; + using PangoLayout pangoLayout = new(cr); + + cr.MoveTo(10, curY); + pangoLayout.SetText($"{Text} (Pango font from string)"); + pangoLayout.SetFontDescriptionFromString("Arial Normal 22"); + pangoLayout.ShowLayout(); + pangoLayout.GetSize(out double width, out double height); + + curY += height; + cr.MoveTo(10, curY); + using PangoFontMap fontMap = PangoFontMap.CairoFontMapGetDefault(); + using PangoFontFamily fontFamily = fontMap.GetFamily("Arial"); + using PangoFontFace? fontFace = fontFamily.GetFace(null); + Debug.Assert(fontFace is not null); + pangoLayout.SetText($"{Text} (Pango font selected)"); + pangoLayout.SetFontDescription(fontFace, size: 22); + pangoLayout.ShowLayout(); + pangoLayout.GetSize(out width, out height); + curY += height; + + cr.SelectFontFace("Arial"); + cr.SetFontSize(22); + cr.FontExtents(out FontExtents fontExtents); + curY += fontExtents.Ascent; + cr.MoveTo(10, curY); + cr.ShowText($"{Text} (cairo)"); + + tee.WriteToPng("font-selection.png"); +} +//----------------------------------------------------------------------------- static void PangoDocsDemoWithTextAroundCircle() { // Demo from https://docs.gtk.org/PangoCairo/pango_cairo.html @@ -502,11 +554,16 @@ static void FontListDemo() foreach (PangoFontFamily fontFamily in families) { - cr.MoveTo(10, curY); - string familyName = fontFamily.Name; + using PangoFontFace? fontFace = fontFamily.GetFace(null); - pangoLayout.SetFontDescriptionFromString($"{familyName}, Normal 22"); // note: trailing , - pangoLayout.SetText($"Font: {familyName}; AV and VA to see kerning or not"); + if (fontFace is null) + { + continue; + } + + cr.MoveTo(10, curY); + pangoLayout.SetText($"Font: {fontFamily.Name} {fontFace.Name}; AV and VA to see kerning or not"); + pangoLayout.SetFontDescription(fontFace, size: 22); pangoLayout.ShowLayout(); pangoLayout.GetSize(out double width, out double height); curY += height; diff --git a/source/CairoSharp.Extensions/Pango/PangoFontFace.cs b/source/CairoSharp.Extensions/Pango/PangoFontFace.cs index 5d7c7c0..1dd62dd 100644 --- a/source/CairoSharp.Extensions/Pango/PangoFontFace.cs +++ b/source/CairoSharp.Extensions/Pango/PangoFontFace.cs @@ -26,7 +26,7 @@ protected override void DisposeCore(pango_font_face* handle) /// Note that a font family may contain multiple faces with the same name (e.g. a variable /// and a non-variable face for the same style). /// - public string FaceName + public string Name { get { diff --git a/source/CairoSharp.Extensions/Pango/PangoFontMap.cs b/source/CairoSharp.Extensions/Pango/PangoFontMap.cs index 5508440..fdd0ee2 100644 --- a/source/CairoSharp.Extensions/Pango/PangoFontMap.cs +++ b/source/CairoSharp.Extensions/Pango/PangoFontMap.cs @@ -53,6 +53,19 @@ public void AddFontFile(string fileName) } } + /// + /// Gets a font family by name. + /// + /// A family name. + /// The font family. + public PangoFontFamily GetFamily(string name) + { + this.CheckDisposed(); + + pango_font_family* family = pango_font_map_get_family(this.Handle, name); + return new PangoFontFamily(family); + } + /// /// List all families for a fontmap. /// diff --git a/source/CairoSharp.Extensions/Pango/PangoFontMapNative.cs b/source/CairoSharp.Extensions/Pango/PangoFontMapNative.cs index d74b750..bd29c06 100644 --- a/source/CairoSharp.Extensions/Pango/PangoFontMapNative.cs +++ b/source/CairoSharp.Extensions/Pango/PangoFontMapNative.cs @@ -18,6 +18,11 @@ internal static unsafe partial class PangoFontMapNative [return: MarshalAs(UnmanagedType.U4)] internal static partial bool pango_font_map_add_font_file(pango_font_map* fontmap, string filename, GError** error); + // https://docs.gtk.org/Pango/method.FontMap.get_family.html + [LibraryImport(PangoNative.LibPangoName, StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial pango_font_family* pango_font_map_get_family(pango_font_map* fontmap, string name); + // https://docs.gtk.org/Pango/method.FontMap.list_families.html [LibraryImport(PangoNative.LibPangoName)] [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] diff --git a/source/CairoSharp.Extensions/Pango/PangoLayout.cs b/source/CairoSharp.Extensions/Pango/PangoLayout.cs index d145bf1..b64a1fb 100644 --- a/source/CairoSharp.Extensions/Pango/PangoLayout.cs +++ b/source/CairoSharp.Extensions/Pango/PangoLayout.cs @@ -104,17 +104,25 @@ public void SetFontDescriptionFromString(string? fontDescription) /// Sets the font description that matches the face. /// /// The font face. + /// + /// The size of the font in points + /// /// /// The resulting font description will have the family, style, variant, weight and stretch /// of the face, but its size field will be unset. /// - public void SetFontDescription(PangoFontFace fontFace) + public void SetFontDescription(PangoFontFace fontFace, int size) { this.CheckDisposed(); fontFace.CheckDisposed(); ArgumentNullException.ThrowIfNull(fontFace); pango_font_description* desc = PangoFontFaceNative.pango_font_face_describe(fontFace.Handle); + + // Must be set before assigning it to the layout. + pango_font_description_set_size(desc, size * Pango.Scale); + //pango_font_description_set_absolute_size(desc, size * Pango.Scale); + pango_layout_set_font_description(this.Handle, desc); pango_font_description_free(desc); } diff --git a/source/CairoSharp.Extensions/Pango/PangoNative.cs b/source/CairoSharp.Extensions/Pango/PangoNative.cs index 4562499..2dd0349 100644 --- a/source/CairoSharp.Extensions/Pango/PangoNative.cs +++ b/source/CairoSharp.Extensions/Pango/PangoNative.cs @@ -28,6 +28,16 @@ public static unsafe partial class PangoNative [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] internal static partial pango_font_description* pango_font_description_from_string(string str); + // https://docs.gtk.org/Pango/method.FontDescription.set_size.html + [LibraryImport(LibPangoName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial void pango_font_description_set_size(pango_font_description* desc, int size); + + // https://docs.gtk.org/Pango/method.FontDescription.set_absolute_size.html + [LibraryImport(LibPangoName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial void pango_font_description_set_absolute_size(pango_font_description* desc, double size); + // https://docs.gtk.org/Pango/method.FontDescription.free.html [LibraryImport(LibPangoName)] [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] From 46be7c618ff703d409dfa62813677eb6eb39487b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Foidl?= Date: Sat, 6 Dec 2025 16:48:38 +0100 Subject: [PATCH 07/10] Fixed enumerator in PangoFontFace and PangoFontFamily --- demos/PangoDemo/Program.cs | 16 +++++++++++++++- .../CairoSharp.Extensions/Pango/PangoFontFace.cs | 2 ++ .../Pango/PangoFontFamily.cs | 2 ++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/demos/PangoDemo/Program.cs b/demos/PangoDemo/Program.cs index 240f4ad..b4f8911 100644 --- a/demos/PangoDemo/Program.cs +++ b/demos/PangoDemo/Program.cs @@ -425,8 +425,22 @@ static void FontMapDemo() { fontFamilyName = fontFamily.Name; - Console.WriteLine($"{fontFamily}\n"); sw.WriteLine($"{fontFamilyName};{fontFamily.IsMonospace};{fontFamily.IsVariable}"); + Console.WriteLine(fontFamily); + Console.WriteLine("Faces:"); + + foreach (PangoFontFace fontFace in fontFamily.ListFaces()) + { + Console.WriteLine($"\t{fontFace.Name}\tsynthesized: {fontFace.IsSynthesized}"); + Console.WriteLine("\tSizes:"); + + foreach (double size in fontFace.ListSizes()) + { + Console.WriteLine($"\t\t{size}"); + } + } + + Console.WriteLine(); } const int Width = 600; diff --git a/source/CairoSharp.Extensions/Pango/PangoFontFace.cs b/source/CairoSharp.Extensions/Pango/PangoFontFace.cs index 1dd62dd..0f05b60 100644 --- a/source/CairoSharp.Extensions/Pango/PangoFontFace.cs +++ b/source/CairoSharp.Extensions/Pango/PangoFontFace.cs @@ -129,6 +129,8 @@ public bool MoveNext() { return false; } + + _i = 0; } else { diff --git a/source/CairoSharp.Extensions/Pango/PangoFontFamily.cs b/source/CairoSharp.Extensions/Pango/PangoFontFamily.cs index d3ca2cc..00e6714 100644 --- a/source/CairoSharp.Extensions/Pango/PangoFontFamily.cs +++ b/source/CairoSharp.Extensions/Pango/PangoFontFamily.cs @@ -159,6 +159,8 @@ public bool MoveNext() { pango_font_family_list_faces(_family.Handle, faces, count); } + + _i = 0; } else { From cd60320b2d2029d7882bb1cf48186839797e4fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Foidl?= Date: Sat, 6 Dec 2025 16:52:56 +0100 Subject: [PATCH 08/10] Fixed xml doc comments --- source/CairoSharp.Extensions/Pango/PangoLayout.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/CairoSharp.Extensions/Pango/PangoLayout.cs b/source/CairoSharp.Extensions/Pango/PangoLayout.cs index b64a1fb..f5ea7df 100644 --- a/source/CairoSharp.Extensions/Pango/PangoLayout.cs +++ b/source/CairoSharp.Extensions/Pango/PangoLayout.cs @@ -25,7 +25,7 @@ public sealed unsafe class PangoLayout : CairoObject /// font sizes given in Pango match the font size given in cairo's user space units. /// /// - /// This layout can then be used for text measurement with functions like + /// This layout can then be used for text measurement with functions like /// or drawing with functions like . If you change the transformation or target /// surface for , you need to call . /// @@ -214,7 +214,8 @@ public void ShowLayout() } /// - /// Updates the private PangoContext of a created with + /// Updates the private PangoContext of a created with + /// /// to match the current transformation and target surface of a . /// public void UpdateLayout() From eb31db18ecd30156e1088b44ae878b9f009a98cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Foidl?= Date: Sun, 7 Dec 2025 12:46:43 +0100 Subject: [PATCH 09/10] Fixed Pango demo to run on Linux too --- demos/PangoDemo/Program.cs | 12 +++++++----- .../Pango/PangoLayout.cs | 19 +++++++++++++++++-- .../Pango/PangoNative.cs | 5 +++++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/demos/PangoDemo/Program.cs b/demos/PangoDemo/Program.cs index b4f8911..68c1af5 100644 --- a/demos/PangoDemo/Program.cs +++ b/demos/PangoDemo/Program.cs @@ -111,7 +111,7 @@ static void ComparisonWithCairoTextDemo() //----------------------------------------------------------------------------- static void FontSelectionDemo() { - const int Width = 700; + const int Width = 800; const int Height = 100; const string Text = "Hello from CairoSharp, w/o any AV and VA"; @@ -134,23 +134,25 @@ static void FontSelectionDemo() cr.MoveTo(10, curY); pangoLayout.SetText($"{Text} (Pango font from string)"); - pangoLayout.SetFontDescriptionFromString("Arial Normal 22"); + string? fontDescriptionAsString = pangoLayout.SetFontDescriptionFromString("DejaVu Serif, Normal 22"); + Console.WriteLine(fontDescriptionAsString); pangoLayout.ShowLayout(); pangoLayout.GetSize(out double width, out double height); curY += height; cr.MoveTo(10, curY); using PangoFontMap fontMap = PangoFontMap.CairoFontMapGetDefault(); - using PangoFontFamily fontFamily = fontMap.GetFamily("Arial"); + using PangoFontFamily fontFamily = fontMap.GetFamily("DejaVu Serif"); using PangoFontFace? fontFace = fontFamily.GetFace(null); Debug.Assert(fontFace is not null); pangoLayout.SetText($"{Text} (Pango font selected)"); - pangoLayout.SetFontDescription(fontFace, size: 22); + fontDescriptionAsString = pangoLayout.SetFontDescription(fontFace, size: 22); + Console.WriteLine(fontDescriptionAsString); pangoLayout.ShowLayout(); pangoLayout.GetSize(out width, out height); curY += height; - cr.SelectFontFace("Arial"); + cr.SelectFontFace("DejaVu Serif"); cr.SetFontSize(22); cr.FontExtents(out FontExtents fontExtents); curY += fontExtents.Ascent; diff --git a/source/CairoSharp.Extensions/Pango/PangoLayout.cs b/source/CairoSharp.Extensions/Pango/PangoLayout.cs index f5ea7df..63d0225 100644 --- a/source/CairoSharp.Extensions/Pango/PangoLayout.cs +++ b/source/CairoSharp.Extensions/Pango/PangoLayout.cs @@ -81,22 +81,30 @@ protected override void DisposeCore(pango_layout* handle) /// When null is set, the current font description is unset. /// /// + /// A string representation of a font description. /// /// If no font description is set on the layout, the font description from the layout’s context is used. /// - public void SetFontDescriptionFromString(string? fontDescription) + public string? SetFontDescriptionFromString(string? fontDescription) { this.CheckDisposed(); if (fontDescription is null) { pango_layout_set_font_description(this.Handle, null); + return null; } else { pango_font_description* desc = pango_font_description_from_string(fontDescription); pango_layout_set_font_description(this.Handle, desc); + + sbyte* tmp = pango_font_description_to_string(desc); pango_font_description_free(desc); + + string res = new(tmp); + GObjectNative.g_free(tmp); + return res; } } @@ -107,11 +115,12 @@ public void SetFontDescriptionFromString(string? fontDescription) /// /// The size of the font in points /// + /// A string representation of a font description. /// /// The resulting font description will have the family, style, variant, weight and stretch /// of the face, but its size field will be unset. /// - public void SetFontDescription(PangoFontFace fontFace, int size) + public string SetFontDescription(PangoFontFace fontFace, int size) { this.CheckDisposed(); fontFace.CheckDisposed(); @@ -124,7 +133,13 @@ public void SetFontDescription(PangoFontFace fontFace, int size) //pango_font_description_set_absolute_size(desc, size * Pango.Scale); pango_layout_set_font_description(this.Handle, desc); + + sbyte* tmp = pango_font_description_to_string(desc); pango_font_description_free(desc); + + string res = new(tmp); + GObjectNative.g_free(tmp); + return res; } /// diff --git a/source/CairoSharp.Extensions/Pango/PangoNative.cs b/source/CairoSharp.Extensions/Pango/PangoNative.cs index 2dd0349..46846f1 100644 --- a/source/CairoSharp.Extensions/Pango/PangoNative.cs +++ b/source/CairoSharp.Extensions/Pango/PangoNative.cs @@ -38,6 +38,11 @@ public static unsafe partial class PangoNative [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] internal static partial void pango_font_description_set_absolute_size(pango_font_description* desc, double size); + // https://docs.gtk.org/Pango/method.FontDescription.to_string.html + [LibraryImport(LibPangoName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial sbyte* pango_font_description_to_string(pango_font_description* desc); + // https://docs.gtk.org/Pango/method.FontDescription.free.html [LibraryImport(LibPangoName)] [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] From afb229329c1f2f3e68088bd2b6fcc2476c2f165d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Foidl?= Date: Sun, 7 Dec 2025 12:47:09 +0100 Subject: [PATCH 10/10] More default fonts for CairoSharp.Extensions --- demos/FreeTypeDemo/Program.cs | 6 ++ .../Fonts/DefaultFonts.cs | 64 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/demos/FreeTypeDemo/Program.cs b/demos/FreeTypeDemo/Program.cs index f7046d5..79a2517 100644 --- a/demos/FreeTypeDemo/Program.cs +++ b/demos/FreeTypeDemo/Program.cs @@ -150,9 +150,15 @@ static void Core(string fileName, string[] fontNames, Func { yield return (DefaultFonts.SansSerif , "Helvetica"); yield return (DefaultFonts.SansSerifBold , "Helvetica (bold)"); + yield return (DefaultFonts.SansSerifItalic , "Helvetica (italic)"); yield return (DefaultFonts.SansSerifBoldItalic, "Helvetica (bold, italic)"); + yield return (DefaultFonts.Serif , "DejaVu Serif"); + yield return (DefaultFonts.SerifBold , "DejaVu Serif (bold)"); + yield return (DefaultFonts.SerifItalic , "DejaVu Serif (italic)"); + yield return (DefaultFonts.SerifBoldItalic , "DejaVu Serif (bold, italic)"); yield return (DefaultFonts.MonoSpace , "Source Code Pro (monospace)"); yield return (DefaultFonts.MonoSpaceBold , "Source Code Pro (monospace, bold)"); + yield return (DefaultFonts.MonoSpaceItalic , "Source Code Pro (monospace, italic)"); yield return (DefaultFonts.MonoSpaceBoldItalic, "Source Code Pro (monospace, bold, italic)"); } } diff --git a/source/CairoSharp.Extensions/Fonts/DefaultFonts.cs b/source/CairoSharp.Extensions/Fonts/DefaultFonts.cs index 8ecc73d..bad0c52 100644 --- a/source/CairoSharp.Extensions/Fonts/DefaultFonts.cs +++ b/source/CairoSharp.Extensions/Fonts/DefaultFonts.cs @@ -78,6 +78,70 @@ public static class DefaultFonts LazyThreadSafetyMode.ExecutionAndPublication); #endif + /// + /// DejaVu Serif + /// + /// + /// A created for DejaVu Serif. + /// +#if USE_THREADSTATIC + [ThreadStatic] private static FontFace? t_serif; + public static FontFace Serif => t_serif ??= new ToyFontFace("DejaVu Serif"); +#else + public static FontFace Serif => s_serif.Value; + private static readonly Lazy s_serif = new( + () => new ToyFontFace("DejaVu Serif"), + LazyThreadSafetyMode.ExecutionAndPublication); +#endif + + /// + /// DejaVu Serif (bold) + /// + /// + /// A created for DejaVu Serif. + /// +#if USE_THREADSTATIC + [ThreadStatic] private static FontFace? t_serifBold; + public static FontFace SerifBold => t_serifBold ??= new ToyFontFace("DejaVu Serif", weight: Drawing.Text.FontWeight.Bold); +#else + public static FontFace SerifBold => s_serifBold.Value; + private static readonly Lazy s_serifBold = new( + () => new ToyFontFace("DejaVu Serif", weight: Drawing.Text.FontWeight.Bold), + LazyThreadSafetyMode.ExecutionAndPublication); +#endif + + /// + /// DejaVu Serif (italic) + /// + /// + /// A created for DejaVu Serif. + /// +#if USE_THREADSTATIC + [ThreadStatic] private static FontFace? t_serifItalic; + public static FontFace SerifItalic => t_serifItalic ??= new ToyFontFace("DejaVu Serif", slant: Drawing.Text.FontSlant.Italic); +#else + public static FontFace SerifItalic => s_serifItalic.Value; + private static readonly Lazy s_serifItalic = new( + () => new ToyFontFace("DejaVu Serif", slant: Drawing.Text.FontSlant.Italic), + LazyThreadSafetyMode.ExecutionAndPublication); +#endif + + /// + /// DejaVu Serif (bold italic) + /// + /// + /// A created for DejaVu Serif. + /// +#if USE_THREADSTATIC + [ThreadStatic] private static FontFace? t_serifBoldItalic; + public static FontFace SerifBoldItalic => t_serifBoldItalic ??= new ToyFontFace("DejaVu Serif", slant: Drawing.Text.FontSlant.Italic, weight: Drawing.Text.FontWeight.Bold); +#else + public static FontFace SerifBoldItalic => s_serifBoldItalic.Value; + private static readonly Lazy s_serifBoldItalic = new( + () => new ToyFontFace("DejaVu Serif", slant: Drawing.Text.FontSlant.Italic, weight: Drawing.Text.FontWeight.Bold), + LazyThreadSafetyMode.ExecutionAndPublication); +#endif + /// /// Source Code Pro ///