Add fontconfig support.
This commit is contained in:
		
							parent
							
								
									ccb0c6ffe7
								
							
						
					
					
						commit
						21233c8173
					
				
							
								
								
									
										230
									
								
								Quik.Media.Defaults/Linux/FontConfig.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								Quik.Media.Defaults/Linux/FontConfig.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,230 @@ | |||||||
|  | using System; | ||||||
|  | using System.Runtime.InteropServices; | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  | using System.Text; | ||||||
|  | using Quik; | ||||||
|  | 
 | ||||||
|  | namespace Quik.Media.Defaults | ||||||
|  | { | ||||||
|  |     public static unsafe class FontConfig | ||||||
|  |     { | ||||||
|  |         private const string fontconfig = "fontconfig"; | ||||||
|  | 
 | ||||||
|  |         public static bool Exists { get; } | ||||||
|  | 
 | ||||||
|  |         public static IntPtr FAMILY { get; } = Marshal.StringToHGlobalAnsi("family"); | ||||||
|  |         public static IntPtr STYLE { get; } = Marshal.StringToHGlobalAnsi("style"); | ||||||
|  |         public static IntPtr FILE { get; } = Marshal.StringToHGlobalAnsi("file"); | ||||||
|  |         public static IntPtr WEIGHT { get; } = Marshal.StringToHGlobalAnsi("weight"); | ||||||
|  |         public static IntPtr SLANT { get; } = Marshal.StringToHGlobalAnsi("slant"); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         static FontConfig() | ||||||
|  |         { | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 if (FcInitLoadConfigAndFonts() == null) | ||||||
|  |                 { | ||||||
|  |                     Exists = false; | ||||||
|  |                 } | ||||||
|  |                 Exists = true; | ||||||
|  |             } | ||||||
|  |             catch | ||||||
|  |             { | ||||||
|  |                 Exists = false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [DllImport(fontconfig, EntryPoint = "FcInitLoadConfigAndFonts")] | ||||||
|  |         public static extern FcConfig* FcInitLoadConfigAndFonts(); | ||||||
|  | 
 | ||||||
|  |         [DllImport(fontconfig, EntryPoint = "FcConfigGetCurrent")] | ||||||
|  |         public static extern FcConfig ConfigGetCurrent(); | ||||||
|  | 
 | ||||||
|  |         [DllImport(fontconfig, EntryPoint = "FcPatternCreate")] | ||||||
|  |         public static extern FcPattern PatternCreate(); | ||||||
|  | 
 | ||||||
|  |         [DllImport(fontconfig, EntryPoint = "FcPatternCreate")] | ||||||
|  |         public static extern bool FcPatternAdd(FcPattern pattern, IntPtr what, FcValue value, bool append); | ||||||
|  | 
 | ||||||
|  |         [DllImport(fontconfig, EntryPoint = "FcObjectSetBuild", CallingConvention = CallingConvention.Cdecl)] | ||||||
|  |         public static extern FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2, IntPtr i3, IntPtr i4, IntPtr i5, IntPtr i6); | ||||||
|  | 
 | ||||||
|  |         public static FcObjectSet ObjectSetBuild(IntPtr i1) | ||||||
|  |         { | ||||||
|  |             return ObjectSetBuild(i1, IntPtr.Zero); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public static FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2) | ||||||
|  |         { | ||||||
|  |             return ObjectSetBuild(i1, i2, IntPtr.Zero); | ||||||
|  |         } | ||||||
|  |         public static FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2, IntPtr i3) | ||||||
|  |         { | ||||||
|  |             return ObjectSetBuild(i1, i2, i3, IntPtr.Zero); | ||||||
|  |         } | ||||||
|  |         public static FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2, IntPtr i3, IntPtr i4) | ||||||
|  |         { | ||||||
|  |             return ObjectSetBuild(i1, i2, i3, i4, IntPtr.Zero); | ||||||
|  |         } | ||||||
|  |         public static FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2, IntPtr i3, IntPtr i4, IntPtr i5) | ||||||
|  |         { | ||||||
|  |             return ObjectSetBuild(i1, i2, i3, i4, i5, IntPtr.Zero); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [DllImport(fontconfig, EntryPoint = "FcFontList")] | ||||||
|  |         public static extern FcFontSet FontList(FcConfig config, FcPattern pattern, FcObjectSet objectSet); | ||||||
|  | 
 | ||||||
|  |         [DllImport(fontconfig, EntryPoint = "FcNameUnparse")] | ||||||
|  |         public static extern IntPtr NameUnparse(FcPattern pat); | ||||||
|  | 
 | ||||||
|  |         public static string NameUnparseStr(FcPattern pat) => Marshal.PtrToStringAnsi(NameUnparse(pat)); | ||||||
|  | 
 | ||||||
|  |         [DllImport(fontconfig, EntryPoint = "FcPatternGetString")] | ||||||
|  |         public static extern FcResult PatternGetString(FcPattern p, IntPtr what, int n, out IntPtr val); | ||||||
|  | 
 | ||||||
|  |         public static FcResult PatternGetString(FcPattern p, IntPtr what, out string str) | ||||||
|  |         { | ||||||
|  |             FcResult i = PatternGetString(p, what, 0, out IntPtr ptr); | ||||||
|  | 
 | ||||||
|  |             if (i == FcResult.Match) | ||||||
|  |             { | ||||||
|  |                 str = Marshal.PtrToStringAnsi(ptr); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 str = null; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             Marshal.FreeHGlobal(ptr); | ||||||
|  | 
 | ||||||
|  |             return i; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         [DllImport(fontconfig, EntryPoint = "FcPatternGet")] | ||||||
|  |         public static extern FcResult PatternGet(FcPattern p, IntPtr what, int id, out FcValue value); | ||||||
|  | 
 | ||||||
|  |         [DllImport(fontconfig, EntryPoint = "FcFontSetDestroy")] | ||||||
|  |         public static extern void FontSetDestroy(FcFontSet fs); | ||||||
|  | 
 | ||||||
|  |         [DllImport(fontconfig, EntryPoint = "FcObjectSetDestroy")] | ||||||
|  |         public static extern void ObjectSetDestroy (FcObjectSet os); | ||||||
|  | 
 | ||||||
|  |         [DllImport(fontconfig, EntryPoint = "FcConfigDestroy")] | ||||||
|  |         public static extern void ConfigDestroy (FcConfig cfg); | ||||||
|  | 
 | ||||||
|  |         [DllImport(fontconfig, EntryPoint = "FcPatternDestroy")] | ||||||
|  |         public static extern void PatternDestroy (FcPattern os); | ||||||
|  | 
 | ||||||
|  |         #region Range | ||||||
|  | 
 | ||||||
|  |         [DllImport(fontconfig, EntryPoint = "FcRangeCreateDouble")] | ||||||
|  |         public static extern IntPtr RangeCreateDouble(double begin, double end); | ||||||
|  | 
 | ||||||
|  |         [DllImport(fontconfig, EntryPoint = "FcRangeCreateInteger")] | ||||||
|  |         public static extern IntPtr RangeCreateInteger (int begin, int end); | ||||||
|  | 
 | ||||||
|  |         [DllImport(fontconfig, EntryPoint = "FcRangeDestroy")] | ||||||
|  |         public static extern void RangeDestroy(IntPtr range); | ||||||
|  | 
 | ||||||
|  |         [DllImport(fontconfig, EntryPoint = "FcRangeCopy")] | ||||||
|  |         public static extern IntPtr RangeCopy (IntPtr range); | ||||||
|  | 
 | ||||||
|  |         [DllImport(fontconfig, EntryPoint = "FcRangeGetDouble")] | ||||||
|  |         public static extern bool RangeGetDouble(IntPtr range, out double start, out double end); | ||||||
|  | 
 | ||||||
|  |         #endregion | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public enum FcResult | ||||||
|  |     { | ||||||
|  |         Match, | ||||||
|  |         NoMatch, | ||||||
|  |         TypeMismatch, | ||||||
|  |         NoId, | ||||||
|  |         OutOfMemory | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public struct FcConfig | ||||||
|  |     { | ||||||
|  |         public readonly IntPtr Handle; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public struct FcPattern | ||||||
|  |     { | ||||||
|  |         public readonly IntPtr Handle; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public unsafe struct FcObjectSet | ||||||
|  |     { | ||||||
|  |         public readonly IntPtr Handle; | ||||||
|  | 
 | ||||||
|  |         private Accessor* AsPtr => (Accessor*)Handle; | ||||||
|  | 
 | ||||||
|  |         public int NObject => AsPtr->nobject; | ||||||
|  | 
 | ||||||
|  |         public int SObject => AsPtr->sobject; | ||||||
|  | 
 | ||||||
|  |         #pragma warning disable CS0649 // Will always have default value. | ||||||
|  |         private struct Accessor | ||||||
|  |         { | ||||||
|  |             public int nobject; | ||||||
|  |             public int sobject; | ||||||
|  |             public byte** objects; | ||||||
|  |         } | ||||||
|  |         #pragma warning restore CS0649 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public unsafe struct FcFontSet | ||||||
|  |     { | ||||||
|  |         public readonly IntPtr Handle; | ||||||
|  |         private Accessor* AsPtr => (Accessor*)Handle; | ||||||
|  | 
 | ||||||
|  |         public int NFont => AsPtr->nfont; | ||||||
|  |         public int SFont => AsPtr->sfont; | ||||||
|  | 
 | ||||||
|  |         public FcPattern this[int i] | ||||||
|  |         { | ||||||
|  |             get | ||||||
|  |             { | ||||||
|  |                 if (i < 0 || i >= NFont) | ||||||
|  |                     throw new IndexOutOfRangeException(); | ||||||
|  | 
 | ||||||
|  |                 return AsPtr->fonts[i]; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         #pragma warning disable CS0649 // Will always have default value. | ||||||
|  |         private struct Accessor | ||||||
|  |         { | ||||||
|  |             public int nfont; | ||||||
|  |             public int sfont; | ||||||
|  |             public FcPattern* fonts; | ||||||
|  |         } | ||||||
|  |         #pragma warning restore CS0649 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public enum FcType | ||||||
|  |     { | ||||||
|  |         Unknown = -1, | ||||||
|  |         Void, | ||||||
|  |         Integer, | ||||||
|  |         Double, | ||||||
|  |         String, | ||||||
|  |         Bool, | ||||||
|  |         Matrix, | ||||||
|  |         CharSet, | ||||||
|  |         FTFace, | ||||||
|  |         LangSet, | ||||||
|  |         Range | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     [StructLayout(LayoutKind.Explicit)] | ||||||
|  |     public readonly struct FcValue | ||||||
|  |     { | ||||||
|  |         [FieldOffset(0)] public readonly FcType Type; | ||||||
|  |         [FieldOffset(sizeof(FcType))] public readonly IntPtr Pointer; | ||||||
|  |         [FieldOffset(sizeof(FcType))] public readonly int Int; | ||||||
|  |         [FieldOffset(sizeof(FcType))] public readonly double Double; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										143
									
								
								Quik.Media.Defaults/Linux/FontConfigFontDatabase.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								Quik.Media.Defaults/Linux/FontConfigFontDatabase.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,143 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.IO; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Runtime.InteropServices; | ||||||
|  | using Quik.Media.Font; | ||||||
|  | using Quik.PAL; | ||||||
|  | 
 | ||||||
|  | namespace Quik.Media.Defaults.Linux | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Font database for Linux libfontconfig systems. | ||||||
|  |     /// </summary> | ||||||
|  |     public class FontConfigFontDatabase : IFontDataBase | ||||||
|  |     { | ||||||
|  |         private IReadOnlyDictionary<FontFace, FileInfo> FilesMap { get; } | ||||||
|  |         private IReadOnlyDictionary<string, IReadOnlyList<FontFace>> ByFamily { get; } | ||||||
|  | 
 | ||||||
|  |         public IEnumerable<FontFace> All { get; } | ||||||
|  | 
 | ||||||
|  |         public FontConfigFontDatabase() | ||||||
|  |         { | ||||||
|  |             if (!FontConfig.Exists) | ||||||
|  |             { | ||||||
|  |                 throw new NotSupportedException("This host doesn't have fontconfig installed."); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             List<FontFace> faces = new List<FontFace>(); | ||||||
|  |             Dictionary<FontFace, FileInfo> files = new Dictionary<FontFace, FileInfo>(); | ||||||
|  |             Dictionary<string, List<FontFace>> byFamily = new Dictionary<string, List<FontFace>>(); | ||||||
|  |              | ||||||
|  |             FcConfig config = FontConfig.ConfigGetCurrent(); | ||||||
|  |             FcPattern pattern = FontConfig.PatternCreate(); | ||||||
|  |             FcObjectSet os = FontConfig.ObjectSetBuild(FontConfig.FAMILY, FontConfig.STYLE, FontConfig.FILE); | ||||||
|  |             FcFontSet fs = FontConfig.FontList(config, pattern, os); | ||||||
|  | 
 | ||||||
|  |             for (int i = 0; i < fs.NFont; i++) | ||||||
|  |             { | ||||||
|  |                 FcPattern current = fs[i]; | ||||||
|  |                  | ||||||
|  |                 if ( | ||||||
|  |                     FontConfig.PatternGetString(current, FontConfig.FAMILY, 0, out IntPtr pFamily) != FcResult.Match || | ||||||
|  |                     FontConfig.PatternGetString(current, FontConfig.STYLE, 0, out IntPtr pStyle) != FcResult.Match) | ||||||
|  |                 { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 string family = Marshal.PtrToStringUTF8(pFamily); | ||||||
|  |                 string style = Marshal.PtrToStringUTF8(pStyle); | ||||||
|  | 
 | ||||||
|  |                 FontFace face = FontFace.Parse(family, style); | ||||||
|  |                 faces.Add(face); | ||||||
|  | 
 | ||||||
|  |                 if (!byFamily.TryGetValue(face.Family, out List<FontFace> siblings)) | ||||||
|  |                 { | ||||||
|  |                     siblings = new List<FontFace>(); | ||||||
|  |                     byFamily.Add(face.Family, siblings); | ||||||
|  |                 } | ||||||
|  |                 siblings.Add(face); | ||||||
|  | 
 | ||||||
|  |                 FontConfig.PatternGetString(current, FontConfig.FILE, 0, out IntPtr pFile); | ||||||
|  |                 string file = Marshal.PtrToStringAnsi(pFile); | ||||||
|  | 
 | ||||||
|  |                 files.TryAdd(face, new FileInfo(file)); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             FontConfig.FontSetDestroy(fs); | ||||||
|  |             FontConfig.ObjectSetDestroy(os); | ||||||
|  |             FontConfig.PatternDestroy(pattern); | ||||||
|  | 
 | ||||||
|  |             All = faces.ToArray(); | ||||||
|  |             FilesMap = files; | ||||||
|  |             ByFamily = new Dictionary<string, IReadOnlyList<FontFace>>( | ||||||
|  |                 byFamily.Select(x => | ||||||
|  |                     new KeyValuePair<string, IReadOnlyList<FontFace>>(x.Key, x.Value) | ||||||
|  |                 ) | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public IEnumerable<FontFace> Search(FontFace prototype, FontMatchCriteria criteria = FontMatchCriteria.All) | ||||||
|  |         { | ||||||
|  |             // A bit scuffed and LINQ heavy but it should work. | ||||||
|  |             IEnumerable<FontFace> candidates; | ||||||
|  |              | ||||||
|  |             if (criteria.HasFlag(FontMatchCriteria.Family)) | ||||||
|  |             { | ||||||
|  |                 IReadOnlyList<FontFace> siblings; | ||||||
|  |                  | ||||||
|  |                 if (!ByFamily.TryGetValue(prototype.Family, out siblings)) | ||||||
|  |                 { | ||||||
|  |                     return Enumerable.Empty<FontFace>(); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 candidates = siblings; | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 candidates = All; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return  | ||||||
|  |                 candidates | ||||||
|  |                     .Where(x => | ||||||
|  |                         implies(criteria.HasFlag(FontMatchCriteria.Slant), prototype.Slant == x.Slant) || | ||||||
|  |                         implies(criteria.HasFlag(FontMatchCriteria.Weight), prototype.Weight == x.Weight) || | ||||||
|  |                         implies(criteria.HasFlag(FontMatchCriteria.Stretch), prototype.Stretch == x.Stretch) | ||||||
|  |                     ) | ||||||
|  |                     .OrderByDescending(x => | ||||||
|  | 
 | ||||||
|  |                         (prototype.Slant == x.Slant ? 1 : 0) + | ||||||
|  |                         (prototype.Weight == x.Weight ? 1 : 0) + | ||||||
|  |                         (prototype.Stretch == x.Stretch ? 1 : 0) + | ||||||
|  |                         confidence(prototype.Family, x.Family) * 3 | ||||||
|  |                     ); | ||||||
|  | 
 | ||||||
|  |             // a => b = a'+b | ||||||
|  |             static bool implies(bool a, bool b) | ||||||
|  |             { | ||||||
|  |                 return !a || b; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             static int confidence(string target, string testee) | ||||||
|  |             { | ||||||
|  |                 int i; | ||||||
|  |                 for (i = 0; i < target.Length && i < testee.Length && target[i] == testee[i]; i++); | ||||||
|  |                 return i; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public FileInfo FontFileInfo(FontFace face) | ||||||
|  |         { | ||||||
|  |             if (FilesMap.TryGetValue(face, out FileInfo info)) | ||||||
|  |                 return info; | ||||||
|  |             else | ||||||
|  |                 return null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public Stream Open(FontFace face) | ||||||
|  |         { | ||||||
|  |             return FontFileInfo(face)?.OpenRead() ?? throw new FileNotFoundException(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user