H. Utku Maden
a1f4e6a4dc
Due to unforseen naming conflicts, the project has been rebranded under the ReFuel umbrealla and will now be referred to as Dashboard from now on. Other changes will occur to suit the library more for the engine whilst keeping the freestanding nature of the library. Rename folder. Rename to Dashboard.OpenTK Rename to Dashboard.Media.Defaults. Do the last renames and path fixes.
269 lines
9.4 KiB
C#
269 lines
9.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.Json.Serialization;
|
|
using System.Text.Json;
|
|
using ReFuel.FreeType;
|
|
using Dashboard.Media.Font;
|
|
using Dashboard.PAL;
|
|
using Dashboard.Media.Defaults.Linux;
|
|
|
|
namespace Dashboard.Media.Defaults.Fallback
|
|
{
|
|
public class FallbackFontDatabase : IFontDataBase
|
|
{
|
|
private readonly string DbPath =
|
|
Environment.GetEnvironmentVariable(EnvironmentVariables.FallbackFontDatabase) ??
|
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "QUIK/fontdb.json");
|
|
|
|
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>();
|
|
|
|
IEnumerable<FontFace> IFontDataBase.All => this.All;
|
|
|
|
public FallbackFontDatabase(bool rebuild = false)
|
|
{
|
|
// Load existing database if desired.
|
|
List<DbEntry> database;
|
|
|
|
if(!rebuild)
|
|
{
|
|
database = LoadDatabase();
|
|
}
|
|
else
|
|
{
|
|
database = new List<DbEntry>();
|
|
}
|
|
|
|
VerifyDatabase(database);
|
|
FlushDatabase(database);
|
|
|
|
database.ForEach(x => AddFont(x.Face, new FileInfo(x.FilePath)));
|
|
|
|
(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;
|
|
}
|
|
|
|
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 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))
|
|
{
|
|
List<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 FontFace GetSystemFontFace(SystemFontFamily family)
|
|
{
|
|
return SystemFonts[family];
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
private List<DbEntry> LoadDatabase()
|
|
{
|
|
FileInfo info = new FileInfo(DbPath);
|
|
|
|
if (!info.Exists)
|
|
return new List<DbEntry>();
|
|
|
|
using Stream str = info.OpenRead();
|
|
try
|
|
{
|
|
return JsonSerializer.Deserialize<List<DbEntry>>(str);
|
|
}
|
|
catch
|
|
{
|
|
return new List<DbEntry>();
|
|
}
|
|
}
|
|
|
|
private void VerifyDatabase(List<DbEntry> db)
|
|
{
|
|
// Very slow way to do this but how many fonts could a system have on average?
|
|
Dictionary<string, DbEntry> entires = new Dictionary<string, DbEntry>();
|
|
|
|
foreach (DbEntry entry in db)
|
|
{
|
|
FileInfo info = new FileInfo(entry.FilePath);
|
|
|
|
// Reprocess fonts that appear like this.
|
|
if (!info.Exists) continue;
|
|
else if (info.LastWriteTime > entry.AccessTime) continue;
|
|
}
|
|
|
|
string fontpath = null;
|
|
try
|
|
{
|
|
fontpath = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
|
|
if (string.IsNullOrEmpty(fontpath))
|
|
throw new Exception();
|
|
}
|
|
catch
|
|
{
|
|
foreach (string path in FontPaths)
|
|
{
|
|
if (Directory.Exists(path))
|
|
{
|
|
fontpath = path;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// rip
|
|
if (string.IsNullOrEmpty(fontpath))
|
|
return;
|
|
}
|
|
|
|
SearchPathForFonts(entires, fontpath);
|
|
|
|
db.Clear();
|
|
db.AddRange(entires.Values);
|
|
}
|
|
|
|
private static void SearchPathForFonts(Dictionary<string, DbEntry> entries, string path)
|
|
{
|
|
DirectoryInfo dir = new DirectoryInfo(path);
|
|
|
|
foreach (FileInfo file in dir.EnumerateFiles())
|
|
{
|
|
SearchFileForFonts(entries, file);
|
|
}
|
|
|
|
foreach (DirectoryInfo directory in dir.EnumerateDirectories())
|
|
{
|
|
SearchPathForFonts(entries, directory.FullName);
|
|
}
|
|
}
|
|
|
|
private static void SearchFileForFonts(Dictionary<string, DbEntry> entries, FileInfo file)
|
|
{
|
|
if (entries.ContainsKey(file.FullName))
|
|
return;
|
|
|
|
if (FT.NewFace(FTProvider.Ft, file.FullName, 0, out FTFace face) != FTError.None)
|
|
return;
|
|
|
|
FontFace facename = FontFace.Parse(face.FamilyName, face.StyleName);
|
|
|
|
DbEntry entry = new DbEntry(facename, file.FullName);
|
|
entries.Add(file.FullName, entry);
|
|
FT.DoneFace(face);
|
|
}
|
|
|
|
private void FlushDatabase(List<DbEntry> db)
|
|
{
|
|
FileInfo info = new FileInfo(DbPath);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(DbPath));
|
|
using Stream str = info.Open(FileMode.Create);
|
|
JsonSerializer.Serialize(str, db);
|
|
}
|
|
|
|
private static readonly string[] FontPaths = new string[] {
|
|
"/usr/share/fonts",
|
|
};
|
|
|
|
[JsonSerializable(typeof(DbEntry))]
|
|
private class DbEntry
|
|
{
|
|
[JsonIgnore] public FontFace Face => new FontFace(Family, Slant, Weight, Stretch);
|
|
public string Family { get; set; }
|
|
public FontSlant Slant { get; set; }
|
|
public FontWeight Weight { get; set; }
|
|
public FontStretch Stretch { get; set; }
|
|
public string FilePath { get; set; }
|
|
public DateTime AccessTime { get; set; }
|
|
|
|
public DbEntry() {}
|
|
public DbEntry(FontFace face, string path)
|
|
{
|
|
Family = face.Family;
|
|
Slant = face.Slant;
|
|
Weight = face.Weight;
|
|
Stretch = face.Stretch;
|
|
FilePath = path;
|
|
AccessTime = DateTime.Now;
|
|
}
|
|
}
|
|
}
|
|
} |