A word of warning: If you value your sanity and it's possible to avoid this by loading this font from a file then do not struggle with packaging them as resources. I am telling you now: it isn't worth it.1
The answer
[DllImport("gdi32.dll")]
private static extern IntPtr AddFontMemResourceEx(IntPtr pbFont, uint cbFont, IntPtr pdv, [In] ref uint pcFonts);
private static PrivateFontCollection pfc = new PrivateFontCollection();
private static uint cFonts = 0;
private static void AddFont(byte[] fontdata)
{
int fontLength; System.IntPtr dataPointer;
//We are going to need a pointer to the font data, so we are generating it here
dataPointer = Marshal.AllocCoTaskMem(fontdata.Length);
//Copying the fontdata into the memory designated by the pointer
Marshal.Copy(fontdata, 0, dataPointer, (int)fontdata.Length);
// Register the font with the system.
AddFontMemResourceEx(dataPointer, (uint)fontdata.Length, IntPtr.Zero, ref cFonts);
//Keep track of how many fonts we've added.
cFonts += 1;
//Finally, we can actually add the font to our collection
pfc.AddMemoryFont(dataPointer, (int)fontdata.Length);
}
Okay, there is a lot to unpack here, but for those looking for a copy/paste, you're going to have a bad time without the line of code that follows. It must be run before your first form is created and before your first font is loaded:2
Application.SetCompatibleTextRenderingDefault(true);
And you can use this code by simply calling the above function like so:
AddFont(Properties.Resources.Roboto_Light);
Now, if you're having trouble with this, what you need to deal with is the AddFontMemResourceEx
function, because it's really difficult to use correctly. Basically what this function does is keep track of fonts in memory. It seems that pfc.AddMemoryFont
doesn't actually increment cFonts
which causes it to silently fail to properly load every font after the first one. This counter must be increased by one every time a unique font family is added. Italic and Bold variants of font families that have already been added don't count as new families and the cFonts
should therefore not be incremented for these.
In theory, the return value for AddFontMemResourceEx
is a pointer referencing the location of the number of fonts currently stored in memory. It also seems impossible to get at the actual number, which is why I'm keeping track manually by incrementing cFonts.
If you fail to properly increment cFonts
then this method will silently fail. The only way to tell that it failed is to see that the font wasn't properly loaded.
Summary
This answer is quite lengthy. It's the only thing I found that works, but the fact that you need to jump through this many hoops to add a font from a resource is absurd. I don't understand why the AddFromMemory function is so broken and I feel confident I must be doing something wrong or misreading something, but this is it. This was a lot of work, and even now it's not the most stable solution when it comes to bold and italic variations of fonts. If you can explain what is going on here and why this is so weird, I would love to hear it. For now, this is the only way I know.
Footnotes
1: Also, it will break the WindowsFormsDesigner. There is no way to set compatibleTextRenderingDefault to true in the WindowsFormsDesigner environment, so you will constantly get errors and text won't render properly. I solved this by putting the font loading in a try{}catch{} and then making the catch just trying to load from a hardcoded path on my own hard drive. Since loading from a file doesn't require any of this mess, that works like a charm.
2: This is absolutely ridiculous. In theory, SetCompatibleTextRenderingDefault()
sets the default text rendering technique used by controls. It determines whether they should use a legacy text rendering technique or a newer text rendering technique. Since (at least in my case) fonts are being loaded before even the first control has been generated, I have absolutely no idea why this would affect anything. And I know it's not the fonts being legacy fonts or anything because if I load them from a file (which presumably contains the same data) this setting doesn't matter. It makes no sense whatsoever.