diff --git a/src/UglyToad.PdfPig/Fonts/SystemFonts/SystemFontFinder.cs b/src/UglyToad.PdfPig/Fonts/SystemFonts/SystemFontFinder.cs new file mode 100644 index 00000000..21f78693 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/SystemFonts/SystemFontFinder.cs @@ -0,0 +1,291 @@ +namespace UglyToad.PdfPig.Fonts.SystemFonts +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using IO; + using TrueType; + using TrueType.Parser; + + internal class SystemFontFinder + { + private readonly TrueTypeFontParser trueTypeFontParser; + private readonly Lazy> availableFonts; + + private readonly Dictionary cache = new Dictionary(); + + public SystemFontFinder(TrueTypeFontParser trueTypeFontParser) + { + this.trueTypeFontParser = trueTypeFontParser; + ISystemFontLister lister; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + lister = new WindowsSystemFontLister(); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + lister = new MacSystemFontLister(); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + lister = new LinuxSystemFontLister(); + } + else + { + throw new NotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}."); + } + + availableFonts = new Lazy>(() => lister.GetAllFonts().ToList()); + } + + public TrueTypeFontProgram GetTrueTypeFont(string name) + { + foreach (var record in availableFonts.Value) + { + if (record.Type == SystemFontType.TrueType) + { + using (var fileStream = File.OpenRead(record.Path)) + { + var input = new StreamInputBytes(fileStream); + var trueType = trueTypeFontParser.Parse(new TrueTypeDataBytes(input)); + //TODO: cache. + } + } + else if (record.Type == SystemFontType.OpenType) + { + throw new NotImplementedException($"TODO: OpenType fonts. Found system font: {record.Path}."); + } + } + + return null; + } + } + + internal interface ISystemFontLister + { + IEnumerable GetAllFonts(); + } + + internal class WindowsSystemFontLister : ISystemFontLister + { + public IEnumerable GetAllFonts() + { + // TODO: Could use System.Drawing InstalledFontCollection to do this? + + var winDir = Environment.GetFolderPath(Environment.SpecialFolder.Windows); + + var fonts = Path.Combine(winDir, "Fonts"); + + if (Directory.Exists(fonts)) + { + var files = Directory.GetFiles(fonts); + + foreach (var file in files) + { + if (SystemFontRecord.TryCreate(file, out var record)) + { + yield return record; + } + } + } + + var psFonts = Path.Combine(winDir, "PSFonts"); + + if (Directory.Exists(psFonts)) + { + var files = Directory.GetFiles(fonts); + + foreach (var file in files) + { + if (SystemFontRecord.TryCreate(file, out var record)) + { + yield return record; + } + } + } + } + } + + internal class LinuxSystemFontLister : ISystemFontLister + { + public IEnumerable GetAllFonts() + { + var directories = new List + { + "/usr/local/fonts", // local + "/usr/local/share/fonts", // local shared + "/usr/share/fonts", // system + "/usr/X11R6/lib/X11/fonts" // X + }; + + try + { + var folder = Environment.GetEnvironmentVariable("$HOME"); + + if (!string.IsNullOrWhiteSpace(folder)) + { + directories.Add($"{folder}/.fonts"); + } + } + catch + { + // ignored + } + + foreach (var directory in directories) + { + try + { + if (!Directory.Exists(directory)) + { + continue; + } + } + catch + { + continue; + } + + string[] files; + + try + { + files = Directory.GetFiles(directory); + } + catch + { + continue; + } + + foreach (var file in files) + { + if (SystemFontRecord.TryCreate(file, out var record)) + { + yield return record; + } + } + } + } + } + + internal class MacSystemFontLister : ISystemFontLister + { + public IEnumerable GetAllFonts() + { + var directories = new List + { + "/Library/Fonts/", // local + "/System/Library/Fonts/", // system + "/Network/Library/Fonts/" // network + }; + + try + { + var folder = Environment.GetEnvironmentVariable("$HOME"); + + if (!string.IsNullOrWhiteSpace(folder)) + { + directories.Add($"{folder}/Library/Fonts"); + } + } + catch + { + // ignored + } + + foreach (var directory in directories) + { + try + { + if (!Directory.Exists(directory)) + { + continue; + } + } + catch + { + continue; + } + + string[] files; + + try + { + files = Directory.GetFiles(directory); + } + catch + { + continue; + } + + foreach (var file in files) + { + if (SystemFontRecord.TryCreate(file, out var record)) + { + yield return record; + } + } + } + } + } + + internal struct SystemFontRecord + { + public string Path { get; } + + public SystemFontType Type { get; } + + public SystemFontRecord(string path, SystemFontType type) + { + Path = path ?? throw new ArgumentNullException(nameof(path)); + Type = type; + } + + public static bool TryCreate(string path, out SystemFontRecord type) + { + type = default(SystemFontRecord); + + SystemFontType fontType; + if (path.EndsWith(".ttf")) + { + fontType = SystemFontType.TrueType; + } + else if (path.EndsWith(".otf")) + { + fontType = SystemFontType.OpenType; + } + else if (path.EndsWith(".ttc")) + { + fontType = SystemFontType.TrueTypeCollection; + } + else if (path.EndsWith(".otc")) + { + fontType = SystemFontType.OpenTypeCollection; + } + else if (path.EndsWith(".pfb")) + { + fontType = SystemFontType.Type1; + } + else + { + return false; + } + + type = new SystemFontRecord(path, fontType); + + return true; + } + } + + internal enum SystemFontType + { + Unknown = 0, + TrueType = 1, + OpenType = 2, + Type1 = 3, + TrueTypeCollection = 4, + OpenTypeCollection = 5 + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/NameTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/NameTable.cs new file mode 100644 index 00000000..4b8f7bb3 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/NameTable.cs @@ -0,0 +1,59 @@ +namespace UglyToad.PdfPig.Fonts.TrueType.Tables +{ + internal class NameTable + { + public string Tag => TrueTypeHeaderTable.Name; + + public TrueTypeHeaderTable DirectoryTable { get; } + + public static void Load(TrueTypeDataBytes data, TrueTypeHeaderTable table) + { + data.Seek(table.Offset); + var format = data.ReadUnsignedShort(); + var count = data.ReadUnsignedShort(); + var stringOffset = data.ReadUnsignedShort(); + + var names = new NameRecord[count]; + for (var i = 0; i < count; i++) + { + names[i] = NameRecord.Read(data); + } + + } + + public struct NameRecord + { + public int PlatformId { get; } + + public int PlatformSpecificId { get; } + + public int LanguageId { get; } + + public int NameId { get; } + + public int Length { get; } + + public int Offset { get; } + + public NameRecord(int platformId, int platformSpecificId, int languageId, int nameId, int length, int offset) + { + PlatformId = platformId; + PlatformSpecificId = platformSpecificId; + LanguageId = languageId; + NameId = nameId; + Length = length; + Offset = offset; + } + + public static NameRecord Read(TrueTypeDataBytes data) + { + return new NameRecord(data.ReadUnsignedShort(), + data.ReadUnsignedShort(), + data.ReadUnsignedShort(), + data.ReadUnsignedShort(), + data.ReadUnsignedShort(), + data.ReadUnsignedShort()); + } + } + } +}