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 { /// /// Font database for Linux libfontconfig systems. /// public class FontConfigFontDatabase : IFontDataBase { private IReadOnlyDictionary FilesMap { get; } private IReadOnlyDictionary> ByFamily { get; } public IEnumerable All { get; } public FontConfigFontDatabase() { if (!FontConfig.Exists) { throw new NotSupportedException("This host doesn't have fontconfig installed."); } List faces = new List(); Dictionary files = new Dictionary(); Dictionary> byFamily = new Dictionary>(); 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 siblings)) { siblings = new List(); 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>( byFamily.Select(x => new KeyValuePair>(x.Key, x.Value) ) ); } public IEnumerable Search(FontFace prototype, FontMatchCriteria criteria = FontMatchCriteria.All) { // A bit scuffed and LINQ heavy but it should work. IEnumerable candidates; if (criteria.HasFlag(FontMatchCriteria.Family)) { IReadOnlyList siblings; if (!ByFamily.TryGetValue(prototype.Family, out siblings)) { return Enumerable.Empty(); } 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(); } } }