diff --git a/Quik.Media.Defaults/Linux/FontConfig.cs b/Quik.Media.Defaults/Linux/FontConfig.cs
new file mode 100644
index 0000000..7c25c92
--- /dev/null
+++ b/Quik.Media.Defaults/Linux/FontConfig.cs
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/Quik.Media.Defaults/Linux/FontConfigFontDatabase.cs b/Quik.Media.Defaults/Linux/FontConfigFontDatabase.cs
new file mode 100644
index 0000000..aa91d5e
--- /dev/null
+++ b/Quik.Media.Defaults/Linux/FontConfigFontDatabase.cs
@@ -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
+{
+ ///
+ /// 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();
+ }
+ }
+}
\ No newline at end of file