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,
+ }
+}