如何在 .NET 中创建子集字体?

发布于 2024-09-09 03:40:00 字数 685 浏览 9 评论 0原文

我有一个 Silverlight 应用程序,需要在其中嵌入一些不太常见的字体。这对我来说非常简单,只需复制 TTF/OTF 并使用我的应用程序进行编译即可。然而,在很多情况下,实际只使用了 5-10 个字符。在其他情况下,某些字体文件非常大(例如,Arial Unicode MS Regular 为 22.1 MB)。我的应用程序的快速下载时间非常重要,因此优化所使用的字体至关重要。

所以,我的想法是,我在 Expression Blend 等应用程序中看到过 用于创建只读字体,您也可以选择仅嵌入某些字符。在其他情况下,我看到人们使用仅包含某些字符的字体作为完整字体的子集(并且不在 Silverlight 中使用 ,而是仅使用子集 .TTF 为 。)这就是我所追求的,只是我没有使用表达式。

我并不是在寻找偷偷摸摸的解决方法,例如导出到 XPS 文件并获取 .odtff 文件。

是否有一种编程方式(.NET/GDI+)来创建仅包含某些字符的字体子集并将其编译为.TTF/.OTF?此外,这也需要适用于 .TTC 文件。

I have a Silverlight application that I need to embed some less-than-common fonts in. It's simple enough for me to just copy over the TTF/OTF and compile that with my app. However, in many cases, only like 5-10 of the characters are actually used. In other cases, some font files are incredibly large (Arial Unicode MS Regular is 22.1 MB, as an example). Fast download times of my app is really important, so optimizing the fonts used is paramount.

So, what I was thinking is that I've seen in applications like Expression Blend where a <Glyph/> is used to create a read-only font and you can also just choose embed only certain characters. In other circumstances, I've seen people use fonts that only contained certain characters as a sub-set of the full font (and not use a <Glyph/> in Silverlight, but rather just use the sub-set .TTF as <FontFamily/>.) That's kind of what I'm after, except I'm not using Expressions.

I'm not looking for sneaky workarounds, like exporting to an XPS file and grabbing the .odtff file.

Is there a programmatic way (.NET/GDI+) to create a sub-set of a font with only certain characters and compile it out to a .TTF/.OTF? Also, this would need to work for .TTC files as well.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

故事还在继续 2024-09-16 03:40:00

本机 API CreateFontPackage 可能就是您想要的寻找。您可以传递 TTF 和要保留的字符列表。如果您为 usSubsetFormat 传递 TTFCFP_SUBSET,您将得到一个仅包含这些字符的有效 TTF。

这里有一个线程,其中似乎是一个工作示例的代码(不幸的是,在 C 语言中) 。

The native API CreateFontPackage may be what you're looking for. You can pass a TTF and a list of characters to keep. If you pass TTFCFP_SUBSET for usSubsetFormat, you'll then get back a working TTF with only those characters.

Here's a thread with what appears to be code of a working example (in C, unfortunately).

倾城花音 2024-09-16 03:40:00

在 WPF 中,字体有静态链接和动态链接。这一切都可以在 Blend 中定义。通过静态链接字体,仅编译所需的字符并将其嵌入到程序集中。通过动态链接,所有字体集都被嵌入。因此,尝试为选定的字体设置静态链接并尝试是否有效。

UPD

尝试将以下代码添加到您的 .csproj 文件中。这里我们包括 Tahoma 字体。 AutoFill 属性设置为 true 表示我们将仅在程序集中嵌入控件中使用的字符。 标记填充点中的字符集将这些字符包含到程序集中。所有其他标签设置为 false,因为我们不需要它们。

<ItemGroup>
    <BlendEmbeddedFont Include="Fonts\tahoma.ttf">
      <IsSystemFont>True</IsSystemFont>
      <All>False</All>
      <AutoFill>True</AutoFill>
      <Characters>dasf</Characters>
      <Uppercase>False</Uppercase>
      <Lowercase>False</Lowercase>
      <Numbers>False</Numbers>
      <Punctuation>False</Punctuation>
    </BlendEmbeddedFont>
    <BlendEmbeddedFont Include="Fonts\tahomabd.ttf">
      <IsSystemFont>True</IsSystemFont>
      <All>False</All>
      <AutoFill>True</AutoFill>
      <Characters>dasf</Characters>
      <Uppercase>False</Uppercase>
      <Lowercase>False</Lowercase>
      <Numbers>False</Numbers>
      <Punctuation>False</Punctuation>
    </BlendEmbeddedFont>
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

  <Import Project="$(MSBuildExtensionsPath)\Microsoft\Expression\Blend\3.0\WPF\Microsoft.Expression.Blend.WPF.targets" />

In WPF for fonts there are static and dynamic linking. It all can be defined in Blend. With static linking of fonts only needed characters are compiled and embedded in your assembly. With dynamic linking all font set is embedded. So try to set static linking for selected fonts and try if it works.

UPD

Try to add the following code into you .csproj file. Here we including Tahoma fonts. AutoFill property set to true says that we will embed in assembly only used characters of our controls. The set of chars in <Charachters/> tag fill point to include these chars into assembly. All other tags set to false, because we don't need them.

<ItemGroup>
    <BlendEmbeddedFont Include="Fonts\tahoma.ttf">
      <IsSystemFont>True</IsSystemFont>
      <All>False</All>
      <AutoFill>True</AutoFill>
      <Characters>dasf</Characters>
      <Uppercase>False</Uppercase>
      <Lowercase>False</Lowercase>
      <Numbers>False</Numbers>
      <Punctuation>False</Punctuation>
    </BlendEmbeddedFont>
    <BlendEmbeddedFont Include="Fonts\tahomabd.ttf">
      <IsSystemFont>True</IsSystemFont>
      <All>False</All>
      <AutoFill>True</AutoFill>
      <Characters>dasf</Characters>
      <Uppercase>False</Uppercase>
      <Lowercase>False</Lowercase>
      <Numbers>False</Numbers>
      <Punctuation>False</Punctuation>
    </BlendEmbeddedFont>
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

  <Import Project="$(MSBuildExtensionsPath)\Microsoft\Expression\Blend\3.0\WPF\Microsoft.Expression.Blend.WPF.targets" />
浪漫人生路 2024-09-16 03:40:00

更改对此的接受答案,因为它是纯 .NET,没有外部引用。使用.NET 4.0:

Imports System.Windows.Media
Imports System.Text.Encoding
Imports System.Collections

Public Sub CreateSubSet(sourceText As String, fontURI As Uri)
    Dim gt As FontEmbeddingManager = New FontEmbeddingManager

    Dim glyphTypeface As GlyphTypeface = New GlyphTypeface(fontURI)
    Dim Index As Generic.ICollection(Of UShort)
    Index = New Generic.List(Of UShort)
    Dim sourceTextBytes As Byte() = Unicode.GetBytes(sourceText)
    Dim sourceTextChars As Char() = Unicode.GetChars(sourceTextBytes)
    Dim sourceTextCharVal As Integer
    Dim glyphIndex As Integer
    For sourceTextCharPos = 0 To UBound(sourceTextChars)
        sourceTextCharVal = AscW(sourceTextChars(sourceTextCharPos))
        glyphIndex = glyphTypeface.CharacterToGlyphMap(sourceTextCharVal)
        Index.Add(glyphIndex)
    Next
    Dim filebytes() As Byte = glyphTypeface.ComputeSubset(Index)
    Using fileStream As New System.IO.FileStream("C:\Users\Me\new-subset.ttf", System.IO.FileMode.Create)
        fileStream.Write(filebytes, 0, filebytes.Length)
    End Using
End Sub

Changing the accepted answer to this one as it is pure .NET with no external references. Uses .NET 4.0:

Imports System.Windows.Media
Imports System.Text.Encoding
Imports System.Collections

Public Sub CreateSubSet(sourceText As String, fontURI As Uri)
    Dim gt As FontEmbeddingManager = New FontEmbeddingManager

    Dim glyphTypeface As GlyphTypeface = New GlyphTypeface(fontURI)
    Dim Index As Generic.ICollection(Of UShort)
    Index = New Generic.List(Of UShort)
    Dim sourceTextBytes As Byte() = Unicode.GetBytes(sourceText)
    Dim sourceTextChars As Char() = Unicode.GetChars(sourceTextBytes)
    Dim sourceTextCharVal As Integer
    Dim glyphIndex As Integer
    For sourceTextCharPos = 0 To UBound(sourceTextChars)
        sourceTextCharVal = AscW(sourceTextChars(sourceTextCharPos))
        glyphIndex = glyphTypeface.CharacterToGlyphMap(sourceTextCharVal)
        Index.Add(glyphIndex)
    Next
    Dim filebytes() As Byte = glyphTypeface.ComputeSubset(Index)
    Using fileStream As New System.IO.FileStream("C:\Users\Me\new-subset.ttf", System.IO.FileMode.Create)
        fileStream.Write(filebytes, 0, filebytes.Length)
    End Using
End Sub
怪我太投入 2024-09-16 03:40:00

我知道这是一个老问题,但我发现从 C# 使用 CreateFontPackage API 非常困难(如 @josh3736 的答案所述),所以我想分享我的代码。

我将 API 与 glyphIndices 一起使用,您可以通过删除 TTFCFP_FLAGS_GLYPHLIST 标志来直接将其与字符一起使用。

这是我的代码:

public byte[] CreateSubset(byte[] inputData, IEnumerable<ushort> glyphIndices)
{
    AllocProc allocProc = Marshal.AllocHGlobal;
    ReallocProc reallocProc = (p, c) =>
        p == IntPtr.Zero
            ? Marshal.AllocHGlobal(c)
            : Marshal.ReAllocHGlobal(p, c);
    FreeProc freeProc = Marshal.FreeHGlobal;

    var resultCode = CreateFontPackage(
        inputData, (uint) inputData.Length,
        out var bufferPtr,
        out _,
        out var bytesWritten,
        TTFCFP_FLAGS_SUBSET | TTFCFP_FLAGS_GLYPHLIST,
        0,
        TTFMFP_SUBSET,
        0,
        TTFCFP_MS_PLATFORMID,
        TTFCFP_UNICODE_CHAR_SET,
        glyphIndices,
        (ushort)glyphIndices.Length,
        allocProc, reallocProc, freeProc, (IntPtr)0);

    if (resultCode != 0 || bufferPtr == IntPtr.Zero)
    {
        return null;
    }

    try
    {
        var buffer = new byte[bytesWritten];
        Marshal.Copy(bufferPtr, buffer, 0, buffer.Length);
        return buffer;
    }
    finally
    {
        freeProc(bufferPtr);
    }
}

internal const ushort TTFCFP_FLAGS_SUBSET = 0x0001;
internal const ushort TTFCFP_FLAGS_COMPRESS = 0x0002;
internal const ushort TTFCFP_FLAGS_TTC = 0x0004;
internal const ushort TTFCFP_FLAGS_GLYPHLIST = 0x0008;

internal const ushort TTFMFP_SUBSET = 0x0000;

internal const ushort TTFCFP_UNICODE_PLATFORMID = 0x0000;
internal const ushort TTFCFP_MS_PLATFORMID = 0x0003;

internal const ushort TTFCFP_UNICODE_CHAR_SET = 0x0001;

[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private delegate IntPtr AllocProc(Int32 size);

[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private delegate IntPtr ReallocProc(IntPtr memBlock, IntPtr size);

[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private delegate void FreeProc(IntPtr memBlock);

[DllImport("FontSub.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
private static extern uint CreateFontPackage(
    [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
    byte[] puchSrcBuffer,
    uint ulSrcBufferSize,
    out IntPtr puchFontPackageBufferPtr,
    out uint pulFontPackageBufferSize,
    out uint pulBytesWritten,
    ushort usFlags,
    ushort usTtcIndex,
    ushort usSubsetFormat,
    ushort usSubsetLanguage,
    ushort usSubsetPlatform,
    ushort usSubsetEncoding,
    [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 12)]
    ushort[] pusSubsetKeepList,
    ushort usSubsetKeepListCount,
    AllocProc lpfnAllocate,
    ReallocProc lpfnReAllocate,
    FreeProc lpfnFree,
    IntPtr lpvReserved
);

我仅使用 TTF 文件的代码,对于 TTC(字体集合),您必须更改一些内容,但它仍然可以工作。

I know it's an old question but I found very difficult to use the CreateFontPackage API from C# (as mentioned by @josh3736's answer) so I thought to share my code.

I'm using the API with the glyphIndices, you can use it directly with the characters by removing the TTFCFP_FLAGS_GLYPHLIST flag.

This is my code:

public byte[] CreateSubset(byte[] inputData, IEnumerable<ushort> glyphIndices)
{
    AllocProc allocProc = Marshal.AllocHGlobal;
    ReallocProc reallocProc = (p, c) =>
        p == IntPtr.Zero
            ? Marshal.AllocHGlobal(c)
            : Marshal.ReAllocHGlobal(p, c);
    FreeProc freeProc = Marshal.FreeHGlobal;

    var resultCode = CreateFontPackage(
        inputData, (uint) inputData.Length,
        out var bufferPtr,
        out _,
        out var bytesWritten,
        TTFCFP_FLAGS_SUBSET | TTFCFP_FLAGS_GLYPHLIST,
        0,
        TTFMFP_SUBSET,
        0,
        TTFCFP_MS_PLATFORMID,
        TTFCFP_UNICODE_CHAR_SET,
        glyphIndices,
        (ushort)glyphIndices.Length,
        allocProc, reallocProc, freeProc, (IntPtr)0);

    if (resultCode != 0 || bufferPtr == IntPtr.Zero)
    {
        return null;
    }

    try
    {
        var buffer = new byte[bytesWritten];
        Marshal.Copy(bufferPtr, buffer, 0, buffer.Length);
        return buffer;
    }
    finally
    {
        freeProc(bufferPtr);
    }
}

internal const ushort TTFCFP_FLAGS_SUBSET = 0x0001;
internal const ushort TTFCFP_FLAGS_COMPRESS = 0x0002;
internal const ushort TTFCFP_FLAGS_TTC = 0x0004;
internal const ushort TTFCFP_FLAGS_GLYPHLIST = 0x0008;

internal const ushort TTFMFP_SUBSET = 0x0000;

internal const ushort TTFCFP_UNICODE_PLATFORMID = 0x0000;
internal const ushort TTFCFP_MS_PLATFORMID = 0x0003;

internal const ushort TTFCFP_UNICODE_CHAR_SET = 0x0001;

[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private delegate IntPtr AllocProc(Int32 size);

[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private delegate IntPtr ReallocProc(IntPtr memBlock, IntPtr size);

[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private delegate void FreeProc(IntPtr memBlock);

[DllImport("FontSub.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
private static extern uint CreateFontPackage(
    [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
    byte[] puchSrcBuffer,
    uint ulSrcBufferSize,
    out IntPtr puchFontPackageBufferPtr,
    out uint pulFontPackageBufferSize,
    out uint pulBytesWritten,
    ushort usFlags,
    ushort usTtcIndex,
    ushort usSubsetFormat,
    ushort usSubsetLanguage,
    ushort usSubsetPlatform,
    ushort usSubsetEncoding,
    [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 12)]
    ushort[] pusSubsetKeepList,
    ushort usSubsetKeepListCount,
    AllocProc lpfnAllocate,
    ReallocProc lpfnReAllocate,
    FreeProc lpfnFree,
    IntPtr lpvReserved
);

I used with code only with TTF files, for TTC (font collections) you have to change a few things but it should work nonetheless.

两相知 2024-09-16 03:40:00

FontForge (http://fontforge.sourceforge.net/) 是一个开源字体编辑器,允许自动格式转换。看起来它只是 Python,但可能值得一试。

FontForge (http://fontforge.sourceforge.net/) is an open source font editor that allows for automated format conversions. It looks like it is Python only but it might be worth checking out.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文