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…
Reference in New Issue
Block a user