8

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.

Todd Main
  • 28,951
  • 11
  • 82
  • 146

5 Answers5

6

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).

josh3736
  • 139,160
  • 33
  • 216
  • 263
  • This is interesting, thanks for the link. Was hoping something would be in .NET, or at least an example of using this from .NET. – Todd Main Jul 26 '10 at 09:21
  • @Otaku: [This post](http://social.msdn.microsoft.com/Forums/en/csharplanguage/thread/1652a9fb-87ab-4725-8a73-7f1015519a71) has the necessary P/Invoke code (see second post) to use the API in .NET. – josh3736 Jul 26 '10 at 16:44
6

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" />
Eugene Cheverda
  • 8,760
  • 2
  • 33
  • 18
  • I looked at http://msdn.microsoft.com/en-us/library/ms753303.aspx and it specifically states that WPF doesn't have the ability to create subset fonts. I'm also not using Blend at all for this, so I'm looking for a way to do this programmatically in .NET. – Todd Main Jul 26 '10 at 09:22
  • See update. If you not using Blend this may help you (I hope). – Eugene Cheverda Jul 26 '10 at 09:46
  • This looks *really* promising - in fact, if I could get it to work, this would be exactly what I need. This page gives a sample: http://www.codeproject.com/KB/silverlight/NewIndianRupeeSymbolDemo.aspx. The sample works, but when I try to do this on my own in a seperate project, it fails. I don't get a `` from the IDE, but even manually adding it doesn't work. Not sure what I'm doing wrong. – Todd Main Jul 26 '10 at 17:52
  • Try to install MS Expression Blend at least trial version in order this extension to appear. – Eugene Cheverda Jul 26 '10 at 18:07
  • Yeah, I tried that. No luck. Do you get **BlendEmbeddedFont** in Build Action the IDE when adding a font? – Todd Main Jul 26 '10 at 18:32
  • No, I haven't on my home machine, only at work (i have installed Blend 3.0). You should look to `` this kind of tags. Experiment with them and take a look to versions of your software, i mean if you have SL 4.0 then use appropriate. Get example from CP and try to find out what is needed to use this extension. – Eugene Cheverda Jul 26 '10 at 18:51
  • Yeah, I'll be toying around with it to see if I can get it to work. – Todd Main Jul 26 '10 at 19:03
  • What environment do you have on your machine, I mean versions of VS, SL and Blend? – Eugene Cheverda Jul 26 '10 at 19:17
  • Win XP, VS2008 w/SP1, SL3, SL 3 SDK, Expressions Blend 3 (Trial) – Todd Main Jul 26 '10 at 21:18
  • Finally got it to work - beautiful! Note: Expressions Blend is not needed - just the Blend 3.0 or 4.0 SDK (free) will work. – Todd Main Jul 27 '10 at 05:36
  • Great! I'm glad for you! Thanks for note. – Eugene Cheverda Jul 27 '10 at 07:23
4

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
Todd Main
  • 28,951
  • 11
  • 82
  • 146
  • I wondered what purpose the declaration of gt serves? It doesn't get used after it's been declared... – Brian THOMAS Sep 09 '16 at 14:13
  • @BrianTHOMAS, yeah, sorry about that, it was an artifact from more code. it's not used in this example. feel free to edit the code. – Todd Main Sep 16 '16 at 00:46
1

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.

aghidini
  • 2,855
  • 5
  • 29
  • 32
0

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.

NakedBrunch
  • 48,713
  • 13
  • 73
  • 98