Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Create Subset Fonts in .NET?

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.

like image 440
Todd Main Avatar asked Jul 14 '10 19:07

Todd Main


3 Answers

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

like image 169
josh3736 Avatar answered Nov 03 '22 07:11

josh3736


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" />
like image 26
Eugene Cheverda Avatar answered Nov 03 '22 07:11

Eugene Cheverda


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
like image 4
Todd Main Avatar answered Nov 03 '22 07:11

Todd Main