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