diff --git a/.gitignore b/.gitignore index 7070f97..897482e 100644 --- a/.gitignore +++ b/.gitignore @@ -265,4 +265,5 @@ __pycache__/ # Test Files test/ -*/Properties/* \ No newline at end of file +*/Properties/* +/native diff --git a/AssFontSubset.Avalonia/Views/MainWindow.axaml.cs b/AssFontSubset.Avalonia/Views/MainWindow.axaml.cs index c952eab..2c62666 100644 --- a/AssFontSubset.Avalonia/Views/MainWindow.axaml.cs +++ b/AssFontSubset.Avalonia/Views/MainWindow.axaml.cs @@ -64,7 +64,7 @@ private async Task AssFontSubsetByPyFT(FileInfo[] path, DirectoryInfo? fontPath, DebugMode = debug, }; Progressing.IsIndeterminate = true; - var ssFt = new SubsetByPyFT(); + var ssFt = new SubsetCore(); await ssFt.SubsetAsync(path, fontPath, outputPath, binPath, subsetConfig); Progressing.IsIndeterminate = false; await ShowMessageBox("Sucess", "子集化完成,请检查 output 文件夹"); diff --git a/AssFontSubset.Console/AssFontSubset.Console.csproj b/AssFontSubset.Console/AssFontSubset.Console.csproj index 7c504da..6f29372 100644 --- a/AssFontSubset.Console/AssFontSubset.Console.csproj +++ b/AssFontSubset.Console/AssFontSubset.Console.csproj @@ -6,7 +6,7 @@ True true - + @@ -18,4 +18,17 @@ + + + + + + + + + + + + diff --git a/AssFontSubset.Console/Program.cs b/AssFontSubset.Console/Program.cs index ccaff0a..007b678 100644 --- a/AssFontSubset.Console/Program.cs +++ b/AssFontSubset.Console/Program.cs @@ -21,6 +21,11 @@ static async Task Main(string[] args) { Description = "子集化后成品所在目录,默认为 ASS 同目录的 output 文件夹" }; + var subsetBackend = new CliOption("--subset-backend") + { + Description = "子集化使用的后端(目前只有 win-x64 支持 HarfBuzzSubset)", + DefaultValueFactory = _ => SubsetBackend.PyFontTools, + }; var binPath = new CliOption("--bin-path") { Description = "指定 pyftsubset 和 ttx 所在目录。若未指定,会使用环境变量中的" @@ -36,9 +41,9 @@ static async Task Main(string[] args) DefaultValueFactory = _ => false, }; - var rootCommand = new CliRootCommand("使用 fonttools 生成 ASS 字幕文件的字体子集,并自动修改字体名称及 ASS 文件中对应的字体名称") + var rootCommand = new CliRootCommand("使用 fonttools 或 harfbuzz-subset 生成 ASS 字幕文件的字体子集,并自动修改字体名称及 ASS 文件中对应的字体名称") { - path, fontPath, outputPath, binPath, sourceHanEllipsis, debug + path, fontPath, outputPath, subsetBackend, binPath, sourceHanEllipsis, debug }; rootCommand.SetAction(async (result, _) => @@ -47,6 +52,7 @@ await Subset( result.GetValue(path)!, result.GetValue(fontPath), result.GetValue(outputPath), + result.GetValue(subsetBackend), result.GetValue(binPath), result.GetValue(sourceHanEllipsis), result.GetValue(debug) @@ -74,12 +80,13 @@ await Subset( return exitCode; } - static async Task Subset(FileInfo[] path, DirectoryInfo? fontPath, DirectoryInfo? outputPath, DirectoryInfo? binPath, bool sourceHanEllipsis, bool debug) + static async Task Subset(FileInfo[] path, DirectoryInfo? fontPath, DirectoryInfo? outputPath, SubsetBackend subsetBackend, DirectoryInfo? binPath, bool sourceHanEllipsis, bool debug) { var subsetConfig = new SubsetConfig { SourceHanEllipsis = sourceHanEllipsis, DebugMode = debug, + Backend = subsetBackend, }; var logLevel = debug ? LogLevel.Debug : LogLevel.Information; @@ -115,7 +122,7 @@ static async Task Subset(FileInfo[] path, DirectoryInfo? fontPath, DirectoryInfo throw new ArgumentException(); } - var ssFt = new SubsetByPyFT(logger); + var ssFt = new SubsetCore(logger); try { await ssFt.SubsetAsync(path, fontPath, outputPath, binPath, subsetConfig); diff --git a/AssFontSubset.Core/AssFontSubset.Core.csproj b/AssFontSubset.Core/AssFontSubset.Core.csproj index d5b1402..0c24e90 100644 --- a/AssFontSubset.Core/AssFontSubset.Core.csproj +++ b/AssFontSubset.Core/AssFontSubset.Core.csproj @@ -2,6 +2,7 @@ enable + true @@ -10,6 +11,10 @@ + + + + diff --git a/AssFontSubset.Core/src/FontConstant.cs b/AssFontSubset.Core/src/FontConstant.cs index 7e4aa1d..80a454b 100644 --- a/AssFontSubset.Core/src/FontConstant.cs +++ b/AssFontSubset.Core/src/FontConstant.cs @@ -44,4 +44,21 @@ public static class FontConstant }; public const int LanguageIdEnUs = 1033; + + + // GDI doesn’t seem to use any features (may use vert?), and it has its own logic for handling vertical layout. + // https://learn.microsoft.com/en-us/typography/opentype/spec/features_uz#tag-vrt2 + // GDI may according it: + // OpenType font with CFF outlines to be used for vertical writing must have vrt2, otherwise fallback + // OpenType font without CFF outlines use vert map default glyphs to vertical writing glyphs + + // https://github.com/libass/libass/pull/702 + // libass seems to be trying to use features like vert to solve this problem. + // These are features related to vertical layout but are not enabled: "vchw", "vhal", "vkrn", "vpal", "vrtr". + // https://github.com/libass/libass/blob/6e83137cdbaf4006439d526fef902e123129707b/libass/ass_shaper.c#L147 + public static readonly string[] SubsetKeepFeatures = [ + "vert", "vrtr", + "vrt2", + "vkna", + ]; } \ No newline at end of file diff --git a/AssFontSubset.Core/src/HarfBuzzSubset.cs b/AssFontSubset.Core/src/HarfBuzzSubset.cs new file mode 100644 index 0000000..cadcd27 --- /dev/null +++ b/AssFontSubset.Core/src/HarfBuzzSubset.cs @@ -0,0 +1,144 @@ +using System.Runtime.InteropServices; +using System.Text; +using HarfBuzzBinding; +using Microsoft.Extensions.Logging; +using ZLogger; +using SubsetApis = HarfBuzzBinding.Native.Subset.Apis; +using HBApis = HarfBuzzBinding.Native.Apis; +using System.Diagnostics; +using HarfBuzzBinding.Native.Subset; +using OTFontFile; + +namespace AssFontSubset.Core; + +public unsafe class HarfBuzzSubset(ILogger? logger) : SubsetToolBase +{ + private Version hbssVersion = new Version(Methods.GetHarfBuzzVersion()!); + public SubsetConfig Config; + public Stopwatch? sw; + private long timer; + + public override void SubsetFonts(Dictionary> subsetFonts, string outputFolder, out Dictionary nameMap) + { + logger?.ZLogInformation($"Start subset font"); + logger?.ZLogInformation($"Font subset use harfbuzz-subset {hbssVersion}"); + nameMap = []; + logger?.ZLogDebug($"Generate randomly non repeating font names"); + var randoms = SubsetFont.GenerateRandomStrings(8, subsetFonts.Keys.Count); + + var i = 0; + foreach (var kv in subsetFonts) + { + nameMap[kv.Key] = randoms[i]; + foreach (var subsetFont in kv.Value) + { + subsetFont.RandomNewName = randoms[i]; + logger?.ZLogInformation($"Start subset {subsetFont.OriginalFontFile.Name}"); + timer = 0; + CreateFontSubset(subsetFont, outputFolder); + logger?.ZLogInformation($"Subset font completed, use {timer} ms"); + } + } + } + + public override void CreateFontSubset(SubsetFont ssf, string outputFolder) + { + if (!Path.Exists(outputFolder)) + { + new DirectoryInfo(outputFolder).Create(); + } + + var outputFileWithoutSuffix = Path.GetFileNameWithoutExtension(ssf.OriginalFontFile.Name); + var outputFile = new StringBuilder($"{outputFileWithoutSuffix}.{ssf.TrackIndex}.{ssf.RandomNewName}"); + + var originalFontFileSuffix = Path.GetExtension(ssf.OriginalFontFile.Name).AsSpan(); + var outFileWithoutSuffix = outputFile.ToString(); + outputFile.Append(originalFontFileSuffix[..3]); + switch (originalFontFileSuffix[^1]) + { + case 'c': + outputFile.Append('f'); + break; + case 'C': + outputFile.Append('F'); + break; + default: + outputFile.Append(originalFontFileSuffix[^1]); + break; + } + + var outputFileName = Path.Combine(outputFolder, outputFile.ToString()); + + ssf.Preprocessing(); + var modifyIds = GetModifyNameIds(ssf.OriginalFontFile.FullName, ssf.TrackIndex); + if (Config.DebugMode) + { + ssf.CharactersFile = Path.Combine(outputFolder, $"{outFileWithoutSuffix}.txt"); + ssf.WriteRunesToUtf8File(); + } + + sw ??= new Stopwatch(); + sw.Start(); + + _ = Methods.TryGetFontFace(ssf.OriginalFontFile.FullName, ssf.TrackIndex, out var facePtr); + facePtr = SubsetApis.hb_subset_preprocess(facePtr); + + var input = SubsetApis.hb_subset_input_create_or_fail(); + + var unicodes = SubsetApis.hb_subset_input_unicode_set(input); + foreach (var rune in ssf.Runes) + { + HBApis.hb_set_add(unicodes, (uint)rune.Value); + } + + var features = SubsetApis.hb_subset_input_set(input, hb_subset_sets_t.HB_SUBSET_SETS_LAYOUT_FEATURE_TAG); + HBApis.hb_set_clear(features); + foreach (var feature in FontConstant.SubsetKeepFeatures) + { + HBApis.hb_set_add(features, HBApis.hb_tag_from_string((sbyte*)Marshal.StringToHGlobalAnsi(feature), -1)); + } + + Methods.RenameFontname(input, + (sbyte*)Marshal.StringToHGlobalAnsi($"Processed by AssFontSubset v{System.Reflection.Assembly.GetEntryAssembly()!.GetName().Version}; harfbuzz-subset {hbssVersion}"), + (sbyte*)Marshal.StringToHGlobalAnsi(ssf.RandomNewName), + modifyIds); + + var faceNewPtr = SubsetApis.hb_subset_or_fail(facePtr, input); + + var blobPtr = HBApis.hb_face_reference_blob(faceNewPtr); + Methods.WriteFontFile(blobPtr, outputFileName); + + sw.Stop(); + timer += sw.ElapsedMilliseconds; + sw.Reset(); + + SubsetApis.hb_subset_input_destroy(input); + HBApis.hb_face_destroy(faceNewPtr); + HBApis.hb_face_destroy(facePtr); + } + + private static OpenTypeNameId[] GetModifyNameIds(string fontFileName, uint index) + { + List ids = []; + var otf = new OTFile(); + otf.open(fontFileName); + var font = otf.GetFont(index); + var nameTable = (Table_name)font!.GetTable("name")!; + for (uint i = 0; i < nameTable.NumberNameRecords; i++) + { + var nameRecord = nameTable.GetNameRecord(i); + if (nameRecord!.NameID is 0 or 1 or 3 or 4 or 6) + { + ids.Add(new OpenTypeNameId + { + NameId = nameRecord.NameID, + PlatformId = nameRecord.PlatformID, + LanguageId = nameRecord.LanguageID, + EncodingId = nameRecord.EncodingID, + }); + } + } + + return ids.ToArray(); + } +} \ No newline at end of file diff --git a/AssFontSubset.Core/src/PyFontTools.cs b/AssFontSubset.Core/src/PyFontTools.cs index be9d57f..85a42bd 100644 --- a/AssFontSubset.Core/src/PyFontTools.cs +++ b/AssFontSubset.Core/src/PyFontTools.cs @@ -6,13 +6,7 @@ namespace AssFontSubset.Core; -public struct SubsetConfig -{ - public bool SourceHanEllipsis; - public bool DebugMode; -} - -public class PyFontTools(string pyftsubset, string ttx, ILogger? logger) +public class PyFontTools(string pyftsubset, string ttx, ILogger? logger) : SubsetToolBase { private Version pyFtVersion = GetFontToolsVersion(ttx); @@ -42,7 +36,7 @@ public void SubsetFonts(List subsetFonts, string outputFolder) } } - public void SubsetFonts(Dictionary> subsetFonts, string outputFolder, out Dictionary nameMap) + public override void SubsetFonts(Dictionary> subsetFonts, string outputFolder, out Dictionary nameMap) { logger?.ZLogInformation($"Start subset font"); logger?.ZLogInformation($"Font subset use pyFontTools {pyFtVersion}"); @@ -74,7 +68,7 @@ public void SubsetFonts(Dictionary> subsetFonts, string } } - public void CreateFontSubset(SubsetFont ssf, string outputFolder) + public override void CreateFontSubset(SubsetFont ssf, string outputFolder) { if (!Path.Exists(outputFolder)) { @@ -255,21 +249,6 @@ private ProcessStartInfo GetSubsetCmd(SubsetFont ssf) { var startInfo = GetSimpleCmd(pyftsubset); - // GDI doesn’t seem to use any features (may use vert?), and it has its own logic for handling vertical layout. - // https://learn.microsoft.com/en-us/typography/opentype/spec/features_uz#tag-vrt2 - // GDI may according it: - // OpenType font with CFF outlines to be used for vertical writing must have vrt2, otherwise fallback - // OpenType font without CFF outlines use vert map default glyphs to vertical writing glyphs - - // https://github.com/libass/libass/pull/702 - // libass seems to be trying to use features like vert to solve this problem. - // These are features related to vertical layout but are not enabled: "vchw", "vhal", "vkrn", "vpal", "vrtr". - // https://github.com/libass/libass/blob/6e83137cdbaf4006439d526fef902e123129707b/libass/ass_shaper.c#L147 - string[] enableFeatures = [ - "vert", "vrtr", - "vrt2", - "vkna", - ]; string[] argus = [ ssf.OriginalFontFile.FullName, $"--text-file={ssf.CharactersFile!}", @@ -277,7 +256,7 @@ private ProcessStartInfo GetSubsetCmd(SubsetFont ssf) "--name-languages=*", $"--font-number={ssf.TrackIndex}", // "--no-layout-closure", - $"--layout-features={string.Join(",", enableFeatures)}", + $"--layout-features={string.Join(",", FontConstant.SubsetKeepFeatures)}", // "--layout-features=*", ]; foreach (var arg in argus) diff --git a/AssFontSubset.Core/src/SubsetConfig.cs b/AssFontSubset.Core/src/SubsetConfig.cs new file mode 100644 index 0000000..ca26201 --- /dev/null +++ b/AssFontSubset.Core/src/SubsetConfig.cs @@ -0,0 +1,14 @@ +namespace AssFontSubset.Core; + +public struct SubsetConfig +{ + public bool SourceHanEllipsis; + public bool DebugMode; + public SubsetBackend Backend; +} + +public enum SubsetBackend +{ + PyFontTools = 1, + HarfBuzzSubset = 2, +} \ No newline at end of file diff --git a/AssFontSubset.Core/src/SubsetByPyFT.cs b/AssFontSubset.Core/src/SubsetCore.cs similarity index 92% rename from AssFontSubset.Core/src/SubsetByPyFT.cs rename to AssFontSubset.Core/src/SubsetCore.cs index cd230a2..4ee2973 100644 --- a/AssFontSubset.Core/src/SubsetByPyFT.cs +++ b/AssFontSubset.Core/src/SubsetCore.cs @@ -8,7 +8,7 @@ namespace AssFontSubset.Core; -public class SubsetByPyFT(ILogger? logger = null) +public class SubsetCore(ILogger? logger = null) { private static readonly Stopwatch _stopwatch = new(); @@ -17,8 +17,6 @@ public async Task SubsetAsync(FileInfo[] path, DirectoryInfo? fontPath, Director var baseDir = path[0].Directory!.FullName; fontPath ??= new DirectoryInfo(Path.Combine(baseDir, "fonts")); outputPath ??= new DirectoryInfo(Path.Combine(baseDir, "output")); - var pyftsubset = binPath is null ? "pyftsubset" : Path.Combine(binPath.FullName, "pyftsubset"); - var ttx = binPath is null ? "ttx" : Path.Combine(binPath.FullName, "ttx"); foreach (var file in path) { @@ -35,10 +33,25 @@ public async Task SubsetAsync(FileInfo[] path, DirectoryInfo? fontPath, Director await Task.Run(() => { var fontInfos = GetFontInfoFromFiles(fontDir); - var pyFT = new PyFontTools(pyftsubset, ttx, logger) { Config = subsetConfig, sw = _stopwatch }; var assFonts = GetAssFontInfoFromFiles(path, optDir, out var assMulti); var subsetFonts = GetSubsetFonts(fontInfos, assFonts, out var fontMap); - pyFT.SubsetFonts(subsetFonts, optDir, out var nameMap); + Dictionary nameMap = []; + + switch (subsetConfig.Backend) + { + case SubsetBackend.PyFontTools: + var pyftsubset = binPath is null ? "pyftsubset" : Path.Combine(binPath.FullName, "pyftsubset"); + var ttx = binPath is null ? "ttx" : Path.Combine(binPath.FullName, "ttx"); + var pyFT = new PyFontTools(pyftsubset, ttx, logger) { Config = subsetConfig, sw = _stopwatch }; + pyFT.SubsetFonts(subsetFonts, optDir, out nameMap); + break; + case SubsetBackend.HarfBuzzSubset: + var hbss = new HarfBuzzSubset(logger) { Config = subsetConfig, sw = _stopwatch }; + hbss.SubsetFonts(subsetFonts, optDir, out nameMap); + break; + default: + throw new ArgumentOutOfRangeException(); + } foreach (var kv in assMulti) { diff --git a/AssFontSubset.Core/src/SubsetToolBase.cs b/AssFontSubset.Core/src/SubsetToolBase.cs new file mode 100644 index 0000000..5f0fdf3 --- /dev/null +++ b/AssFontSubset.Core/src/SubsetToolBase.cs @@ -0,0 +1,7 @@ +namespace AssFontSubset.Core; + +public abstract class SubsetToolBase +{ + public abstract void SubsetFonts(Dictionary> subsetFonts, string outputFolder, out Dictionary nameMap); + public abstract void CreateFontSubset(SubsetFont ssf, string outputFolder); +} \ No newline at end of file diff --git a/AssFontSubset.slnx b/AssFontSubset.slnx index b5f6aad..d130d02 100644 --- a/AssFontSubset.slnx +++ b/AssFontSubset.slnx @@ -3,4 +3,5 @@ + \ No newline at end of file diff --git a/HarfBuzzBinding/HarfBuzzBinding.csproj b/HarfBuzzBinding/HarfBuzzBinding.csproj new file mode 100644 index 0000000..07ffc3c --- /dev/null +++ b/HarfBuzzBinding/HarfBuzzBinding.csproj @@ -0,0 +1,11 @@ + + + + net8.0 + latest + enable + enable + true + + + diff --git a/HarfBuzzBinding/src/Methods.cs b/HarfBuzzBinding/src/Methods.cs new file mode 100644 index 0000000..3ba4e80 --- /dev/null +++ b/HarfBuzzBinding/src/Methods.cs @@ -0,0 +1,63 @@ +using System.Runtime.InteropServices; +using System.Text; +using HarfBuzzBinding.Native; +using SubsetApis = HarfBuzzBinding.Native.Subset.Apis; +using HBApis = HarfBuzzBinding.Native.Apis; + +namespace HarfBuzzBinding; + +public unsafe class Methods +{ + public static string? GetHarfBuzzVersion() => Marshal.PtrToStringAnsi((IntPtr)HBApis.hb_version_string()); + + public static bool TryGetFontFace(string fontFile, uint faceIndex, out hb_face_t* face) + { + var harfBuzzVersion = new Version(GetHarfBuzzVersion()!); + + if (harfBuzzVersion is { Major: >= 10, Minor: >= 1 }) + { + face = HBApis.hb_face_create_from_file_or_fail((sbyte*)Marshal.StringToHGlobalAnsi(fontFile), faceIndex); + return (IntPtr)face != IntPtr.Zero; + } + + var blob = HBApis.hb_blob_create_from_file_or_fail((sbyte*)Marshal.StringToHGlobalAnsi(fontFile)); + if ((IntPtr)blob == IntPtr.Zero) + { + face = (hb_face_t*)IntPtr.Zero; + return false; + } + + face = HBApis.hb_face_create(blob, faceIndex); + HBApis.hb_blob_destroy(blob); + return true; + } + + public static void WriteFontFile(hb_blob_t* blob, string destFile) + { + uint length; + var dataPtr = HBApis.hb_blob_get_data(blob, &length); + var stream = new UnmanagedMemoryStream((byte*)dataPtr, length); + + using var fileStream = new FileStream(destFile, FileMode.Create); + stream.CopyTo(fileStream); + stream.Dispose(); + + HBApis.hb_blob_destroy(blob); + } + + public static void RenameFontname(hb_subset_input_t* input, sbyte* versionString, sbyte* nameString, OpenTypeNameId[] ids) + { + foreach (var id in ids) + { + _ = SubsetApis.hb_subset_input_override_name_table(input, id.NameId, id.PlatformId, id.EncodingId, id.LanguageId, id.NameId == 0 ? versionString : nameString, -1); + } + } +} + +public struct OpenTypeNameId +{ + public uint NameId; + public uint PlatformId; + public uint LanguageId; + public uint EncodingId; +} \ No newline at end of file diff --git a/HarfBuzzBinding/src/Native/Apis.cs b/HarfBuzzBinding/src/Native/Apis.cs new file mode 100644 index 0000000..998de86 --- /dev/null +++ b/HarfBuzzBinding/src/Native/Apis.cs @@ -0,0 +1,45 @@ +using System.Runtime.InteropServices; +using static HarfBuzzBinding.Native.Library; +// ReSharper disable InconsistentNaming + +namespace HarfBuzzBinding.Native; + +public static unsafe partial class Apis +{ + [DllImport(HarfBuzzDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern sbyte* hb_version_string(); + + [DllImport(HarfBuzzDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern hb_blob_t* hb_blob_create_from_file_or_fail(sbyte* file_name); + + [DllImport(HarfBuzzDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern void* hb_blob_get_data(hb_blob_t* blob, uint* length); + + [DllImport(HarfBuzzDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern void hb_blob_destroy(hb_blob_t* blob); + + [DllImport(HarfBuzzDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern hb_face_t* hb_face_create(hb_blob_t* blob, uint index); + + [DllImport(HarfBuzzDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern hb_face_t* hb_face_create_from_file_or_fail(sbyte* file_name, uint index); + + [DllImport(HarfBuzzDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern hb_blob_t* hb_face_reference_blob(hb_face_t* face); + + [DllImport(HarfBuzzDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern void hb_face_destroy(hb_face_t* face); + + [DllImport(HarfBuzzDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern void hb_set_add(hb_set_t* set, [NativeTypeName("hb_codepoint_t")] uint codepoint); + + [DllImport(HarfBuzzDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern void hb_set_clear(hb_set_t* set); + + [DllImport(HarfBuzzDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern void hb_set_destroy(hb_set_t* set); + + [DllImport(HarfBuzzDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("hb_tag_t")] + public static extern uint hb_tag_from_string(sbyte* str, int len); +} \ No newline at end of file diff --git a/HarfBuzzBinding/src/Native/Library.cs b/HarfBuzzBinding/src/Native/Library.cs new file mode 100644 index 0000000..733c996 --- /dev/null +++ b/HarfBuzzBinding/src/Native/Library.cs @@ -0,0 +1,18 @@ +global using hb_blob_t = System.IntPtr; +global using hb_buffer_t = System.IntPtr; +global using hb_face_t = System.IntPtr; +global using hb_font_funcs_t = System.IntPtr; +global using hb_font_t = System.IntPtr; +global using hb_language_impl_t = System.IntPtr; +global using hb_map_t = System.IntPtr; +global using hb_set_t = System.IntPtr; +global using hb_shape_plan_t = System.IntPtr; +global using hb_unicode_funcs_t = System.IntPtr; + +namespace HarfBuzzBinding.Native; + +internal static class Library +{ + internal const string HarfBuzzDll = "harfbuzz"; + internal const string HarfBuzzSubsetDll = "harfbuzz-subset"; +} \ No newline at end of file diff --git a/HarfBuzzBinding/src/Native/NativeTypeNameAttribute.cs b/HarfBuzzBinding/src/Native/NativeTypeNameAttribute.cs new file mode 100644 index 0000000..6a9f9a3 --- /dev/null +++ b/HarfBuzzBinding/src/Native/NativeTypeNameAttribute.cs @@ -0,0 +1,22 @@ +using System.Diagnostics; + +namespace HarfBuzzBinding.Native +{ + /// Defines the type of a member as it was used in the native signature. + [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = false, Inherited = true)] + [Conditional("DEBUG")] + internal sealed partial class NativeTypeNameAttribute : Attribute + { + private readonly string _name; + + /// Initializes a new instance of the class. + /// The name of the type that was used in the native signature. + public NativeTypeNameAttribute(string name) + { + _name = name; + } + + /// Gets the name of the type that was used in the native signature. + public string Name => _name; + } +} diff --git a/HarfBuzzBinding/src/Native/Subset/Apis.cs b/HarfBuzzBinding/src/Native/Subset/Apis.cs new file mode 100644 index 0000000..cbc68d6 --- /dev/null +++ b/HarfBuzzBinding/src/Native/Subset/Apis.cs @@ -0,0 +1,99 @@ +global using hb_subset_input_t = System.IntPtr; +global using hb_subset_plan_t = System.IntPtr; +using System.Runtime.InteropServices; +using static HarfBuzzBinding.Native.Library; +// ReSharper disable InconsistentNaming + +namespace HarfBuzzBinding.Native.Subset +{ + public static unsafe partial class Apis + { + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern hb_subset_input_t* hb_subset_input_create_or_fail(); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern hb_subset_input_t* hb_subset_input_reference(hb_subset_input_t* input); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern void hb_subset_input_destroy(hb_subset_input_t* input); + + // [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + // [return: NativeTypeName("hb_bool_t")] + // public static extern int hb_subset_input_set_user_data(hb_subset_input_t* input, hb_user_data_key_t* key, void* data, [NativeTypeName("hb_destroy_func_t")] delegate* unmanaged[Cdecl] destroy, [NativeTypeName("hb_bool_t")] int replace); + + // [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + // public static extern void* hb_subset_input_get_user_data([NativeTypeName("const hb_subset_input_t *")] hb_subset_input_t* input, hb_user_data_key_t* key); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern void hb_subset_input_keep_everything(hb_subset_input_t* input); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern hb_set_t* hb_subset_input_unicode_set(hb_subset_input_t* input); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern hb_set_t* hb_subset_input_glyph_set(hb_subset_input_t* input); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern hb_set_t* hb_subset_input_set(hb_subset_input_t* input, hb_subset_sets_t set_type); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern hb_map_t* hb_subset_input_old_to_new_glyph_mapping(hb_subset_input_t* input); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern hb_subset_flags_t hb_subset_input_get_flags(hb_subset_input_t* input); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern void hb_subset_input_set_flags(hb_subset_input_t* input, [NativeTypeName("unsigned int")] uint value); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("hb_bool_t")] + public static extern int hb_subset_input_pin_all_axes_to_default(hb_subset_input_t* input, hb_face_t* face); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("hb_bool_t")] + public static extern int hb_subset_input_pin_axis_to_default(hb_subset_input_t* input, hb_face_t* face, [NativeTypeName("hb_tag_t")] uint axis_tag); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("hb_bool_t")] + public static extern int hb_subset_input_pin_axis_location(hb_subset_input_t* input, hb_face_t* face, [NativeTypeName("hb_tag_t")] uint axis_tag, float axis_value); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern hb_face_t* hb_subset_preprocess(hb_face_t* source); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern hb_face_t* hb_subset_or_fail(hb_face_t* source, [NativeTypeName("const hb_subset_input_t *")] hb_subset_input_t* input); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern hb_face_t* hb_subset_plan_execute_or_fail(hb_subset_plan_t* plan); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern hb_subset_plan_t* hb_subset_plan_create_or_fail(hb_face_t* face, [NativeTypeName("const hb_subset_input_t *")] hb_subset_input_t* input); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern void hb_subset_plan_destroy(hb_subset_plan_t* plan); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern hb_map_t* hb_subset_plan_old_to_new_glyph_mapping([NativeTypeName("const hb_subset_plan_t *")] hb_subset_plan_t* plan); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern hb_map_t* hb_subset_plan_new_to_old_glyph_mapping([NativeTypeName("const hb_subset_plan_t *")] hb_subset_plan_t* plan); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern hb_map_t* hb_subset_plan_unicode_to_old_glyph_mapping([NativeTypeName("const hb_subset_plan_t *")] hb_subset_plan_t* plan); + + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern hb_subset_plan_t* hb_subset_plan_reference(hb_subset_plan_t* plan); + + // [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + // [return: NativeTypeName("hb_bool_t")] + // public static extern int hb_subset_plan_set_user_data(hb_subset_plan_t* plan, hb_user_data_key_t* key, void* data, [NativeTypeName("hb_destroy_func_t")] delegate* unmanaged[Cdecl] destroy, [NativeTypeName("hb_bool_t")] int replace); + // + // [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + // public static extern void* hb_subset_plan_get_user_data([NativeTypeName("const hb_subset_plan_t *")] hb_subset_plan_t* plan, hb_user_data_key_t* key); + + // HB_EXPERIMENTAL_API + [DllImport(HarfBuzzSubsetDll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("hb_bool_t")] + public static extern int hb_subset_input_override_name_table(hb_subset_input_t* input, [NativeTypeName("hb_ot_name_id_t")] uint name_id, [NativeTypeName("unsigned int")] uint platform_id, [NativeTypeName("unsigned int")] uint encoding_id, [NativeTypeName("unsigned int")] uint language_id, [NativeTypeName("const char *")] sbyte* name_str, int str_len); + } +} diff --git a/HarfBuzzBinding/src/Native/Subset/hb_subset_flags_t.cs b/HarfBuzzBinding/src/Native/Subset/hb_subset_flags_t.cs new file mode 100644 index 0000000..ac7c776 --- /dev/null +++ b/HarfBuzzBinding/src/Native/Subset/hb_subset_flags_t.cs @@ -0,0 +1,19 @@ +// ReSharper disable InconsistentNaming + +namespace HarfBuzzBinding.Native.Subset +{ + public enum hb_subset_flags_t : uint + { + HB_SUBSET_FLAGS_DEFAULT = 0x00000000U, + HB_SUBSET_FLAGS_NO_HINTING = 0x00000001U, + HB_SUBSET_FLAGS_RETAIN_GIDS = 0x00000002U, + HB_SUBSET_FLAGS_DESUBROUTINIZE = 0x00000004U, + HB_SUBSET_FLAGS_NAME_LEGACY = 0x00000008U, + HB_SUBSET_FLAGS_SET_OVERLAPS_FLAG = 0x00000010U, + HB_SUBSET_FLAGS_PASSTHROUGH_UNRECOGNIZED = 0x00000020U, + HB_SUBSET_FLAGS_NOTDEF_OUTLINE = 0x00000040U, + HB_SUBSET_FLAGS_GLYPH_NAMES = 0x00000080U, + HB_SUBSET_FLAGS_NO_PRUNE_UNICODE_RANGES = 0x00000100U, + HB_SUBSET_FLAGS_NO_LAYOUT_CLOSURE = 0x00000200U, + } +} diff --git a/HarfBuzzBinding/src/Native/Subset/hb_subset_sets_t.cs b/HarfBuzzBinding/src/Native/Subset/hb_subset_sets_t.cs new file mode 100644 index 0000000..f1571ae --- /dev/null +++ b/HarfBuzzBinding/src/Native/Subset/hb_subset_sets_t.cs @@ -0,0 +1,16 @@ +// ReSharper disable InconsistentNaming + +namespace HarfBuzzBinding.Native.Subset +{ + public enum hb_subset_sets_t + { + HB_SUBSET_SETS_GLYPH_INDEX = 0, + HB_SUBSET_SETS_UNICODE, + HB_SUBSET_SETS_NO_SUBSET_TABLE_TAG, + HB_SUBSET_SETS_DROP_TABLE_TAG, + HB_SUBSET_SETS_NAME_ID, + HB_SUBSET_SETS_NAME_LANG_ID, + HB_SUBSET_SETS_LAYOUT_FEATURE_TAG, + HB_SUBSET_SETS_LAYOUT_SCRIPT_TAG, + } +}