Dashboard/Quik.Media.Defaults/Linux/FontConfigFontDatabase.cs

168 lines
6.4 KiB
C#
Raw Normal View History

2024-04-28 14:39:12 +02:00
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
2024-05-01 14:22:22 +02:00
using System.Runtime.CompilerServices;
2024-04-28 14:39:12 +02:00
using System.Runtime.InteropServices;
2024-05-01 14:22:22 +02:00
using System.Text;
using Quik.FreeType;
2024-04-28 14:39:12 +02:00
using Quik.Media.Font;
using Quik.PAL;
namespace Quik.Media.Defaults.Linux
{
/// <summary>
/// Font database for Linux libfontconfig systems.
/// </summary>
public class FontConfigFontDatabase : IFontDataBase
{
2024-05-01 14:22:22 +02:00
private Dictionary<FontFace, FileInfo> FilesMap { get; } = new Dictionary<FontFace, FileInfo>();
private Dictionary<string, List<FontFace>> ByFamily { get; } = new Dictionary<string, List<FontFace>>();
private Dictionary<SystemFontFamily, FontFace> SystemFonts { get; } = new Dictionary<SystemFontFamily, FontFace>();
private List<FontFace> All { get; } = new List<FontFace>();
2024-04-28 14:39:12 +02:00
2024-05-01 14:22:22 +02:00
IEnumerable<FontFace> IFontDataBase.All => this.All;
2024-04-28 14:39:12 +02:00
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);
2024-05-01 14:22:22 +02:00
AddFont(face, new FileInfo(file));
2024-04-28 14:39:12 +02:00
}
FontConfig.FontSetDestroy(fs);
FontConfig.ObjectSetDestroy(os);
FontConfig.PatternDestroy(pattern);
2024-05-01 14:22:22 +02:00
(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<FontFace> siblings))
{
siblings = new List<FontFace>();
ByFamily.Add(face.Family, siblings);
}
if (!siblings.Contains(face))
siblings.Add(face);
2024-04-28 14:39:12 +02:00
}
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))
{
2024-05-01 14:22:22 +02:00
List<FontFace> siblings;
2024-04-28 14:39:12 +02:00
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();
}
2024-05-01 14:22:22 +02:00
public FontFace GetSystemFontFace(SystemFontFamily family)
{
return SystemFonts[family];
}
2024-04-28 14:39:12 +02:00
}
}