143 lines
5.0 KiB
C#
143 lines
5.0 KiB
C#
|
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();
|
||
|
}
|
||
|
}
|
||
|
}
|