using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using Quik.FreeType; using Quik.Media.Font; using Quik.PAL; namespace Quik.Media.Defaults.Linux { /// /// Font database for Linux libfontconfig systems. /// public class FontConfigFontDatabase : IFontDataBase { private Dictionary FilesMap { get; } = new Dictionary(); private Dictionary> ByFamily { get; } = new Dictionary>(); private Dictionary SystemFonts { get; } = new Dictionary(); private List All { get; } = new List(); IEnumerable IFontDataBase.All => this.All; public FontConfigFontDatabase() { if (!FontConfig.Exists) { throw new NotSupportedException("This host doesn't have fontconfig installed."); } 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); FontConfig.PatternGetString(current, FontConfig.FILE, 0, out IntPtr pFile); string file = Marshal.PtrToStringAnsi(pFile); AddFont(face, new FileInfo(file)); } FontConfig.FontSetDestroy(fs); FontConfig.ObjectSetDestroy(os); FontConfig.PatternDestroy(pattern); (FontFace, FileInfo) serif = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.SerifFont, LinuxFonts.DefaultSerifFamilies, this); SystemFonts[SystemFontFamily.Serif] = serif.Item1; (FontFace, FileInfo) sans = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.SansFont, LinuxFonts.DefaultSansFamilies, this); SystemFonts[SystemFontFamily.Sans] = sans.Item1; (FontFace, FileInfo) mono = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.MonospaceFont, LinuxFonts.DefaultMonospaceFamilies, this); SystemFonts[SystemFontFamily.Monospace] = mono.Item1; (FontFace, FileInfo) cursive = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.CursiveFont, LinuxFonts.DefaultCursiveFamilies, this); SystemFonts[SystemFontFamily.Cursive] = cursive.Item1; (FontFace, FileInfo) fantasy = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.FantasyFont, LinuxFonts.DefaultFantasyFamilies, this); SystemFonts[SystemFontFamily.Fantasy] = fantasy.Item1; AddFont(serif.Item1, serif.Item2); AddFont(sans.Item1, sans.Item2); AddFont(mono.Item1, mono.Item2); AddFont(cursive.Item1, cursive.Item2); AddFont(fantasy.Item1, fantasy.Item2); } private void AddFont(FontFace face, FileInfo file) { if (!All.Contains(face)) All.Add(face); FilesMap.TryAdd(face, file); if (!ByFamily.TryGetValue(face.Family, out List siblings)) { siblings = new List(); ByFamily.Add(face.Family, siblings); } if (!siblings.Contains(face)) siblings.Add(face); } 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)) { List 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(); } public FontFace GetSystemFontFace(SystemFontFamily family) { return SystemFonts[family]; } } }