addFontFile from Resources
Asked Answered
S

5

14

I have added a custom font using below code:

PrivateFontCollection pfc = new PrivateFontCollection();
pfc.AddFontFile("C:\\Path To\\YourFont.ttf");
label1.Font = new System.Drawing.Font(pfc.Families[0], 16, FontStyle.Regular);

I added the font file in resources. How do I add with addFontFile from resources?

Shearwater answered 11/4, 2013 at 12:40 Comment(0)
C
8

If you included your font in the resources

Try this function

private void AddFontFromMemory()
{
    Stream fontStream = this.GetType().Assembly.GetManifestResourceStream("yourfont.ttf");
 
    byte[] fontdata = new byte[fontStream.Length];
    fontStream.Read(fontdata,0,(int)fontStream.Length);
    fontStream.Close();

    unsafe
    {
        fixed(byte * pFontData = fontdata)
        {
            pfc.AddMemoryFont((System.IntPtr)pFontData,fontdata.Length);
        }
    }
}

Edited

How load resource from assembly:(YourNamespace.file.ttf)

Stream fontStream = Assembly.GetExecutingAssembly()
 .GetManifestResourceStream("WindowsFormsApplication1.SBADR.TTF");

My solution explorer:

enter image description here

Cantu answered 11/4, 2013 at 12:42 Comment(10)
and how I change the font of label after this?Shearwater
Try this label.Font=new Font(pfc.Families[0],18,FontStyle.Regular);Cantu
I'm getting null exception in ` Stream fontStream = this.GetType().Assembly.GetManifestResourceStream("BOOKOS.TTF"); ` but I have already add the font file in resources. The build property of BOOKOS.TFF is Embedded ...it's right?Shearwater
@illDev:after addfontfrommemory() runes dose pfc have a font family? trace your codeCantu
private void addfontfrommemory() { Stream fontStream = this.GetType().Assembly.GetManifestResourceStream("LoadImagesFromFolder.Resources.BOOKOS.ttf"); byte[] fontdata = new byte[fontStream.Length]; fontStream.Read(fontdata, 0, (int)fontStream.Length); fontStream.Close(); unsafe { fixed (byte* pFontData = fontdata) { pfc.AddMemoryFont((System.IntPtr)pFontData, fontdata.Length); } } }Shearwater
fontStream is null but I don't know whyShearwater
@illDev:see my solution explorerCantu
Thx, it works! I did use System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceNames() to get the Resource nameShearwater
I recommend using this post in combination with #2535427Chess
This is a very old question, but just in case anyone is viewing this in 2019, Corrpside's answer is better, but you can also entirely cut out the FontStream in this answer and instead define FontData as byte[] FontData = Resources.FontName; as .ttf resources are stored as bytes already.Antipersonnel
C
13
private static void AddFontFromResource(PrivateFontCollection privateFontCollection, string fontResourceName)
{
    var fontBytes = GetFontResourceBytes(typeof (App).Assembly, fontResourceName);
    var fontData = Marshal.AllocCoTaskMem(fontBytes.Length);
    Marshal.Copy(fontBytes, 0, fontData, fontBytes.Length);
    privateFontCollection.AddMemoryFont(fontData, fontBytes.Length);
    // Marshal.FreeCoTaskMem(fontData);  Nasty bug alert, read the comment
}

private static byte[] GetFontResourceBytes(Assembly assembly, string fontResourceName)
{
    var resourceStream = assembly.GetManifestResourceStream(fontResourceName);
    if (resourceStream == null)
        throw new Exception(string.Format("Unable to find font '{0}' in embedded resources.", fontResourceName));
    var fontBytes = new byte[resourceStream.Length];
    resourceStream.Read(fontBytes, 0, (int)resourceStream.Length);
    resourceStream.Close();
    return fontBytes;
}
Chess answered 14/5, 2014 at 15:8 Comment(4)
This to me, is a better solution avoiding the usage of 'unsafe{}' by using System.Runtime.InteropServices.Marshal class. Also, I've used 'GetFontResourceBytes(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Assembly, fontResourceName)' for my solution.Ginn
I also much preferred this solution instead of changing the the entire build process for the only unsafe call. Also for the assembly i used GetFontResourceBytes(Assembly.GetExecutingAssembly(), fontResourceName);Disturbed
This code has a very nasty bug that gets copy/pasted over and over again from the top Google hit. It is not correct to call Marshal.FreeCoTaskMem(), the memory must not be released until after you stop using the font. Practically only after you called PrivateFontCollection.Dispose(). Which about nobody ever does since it tends to be pretty impractical, leaving the font loaded for the life of the app is pretty normal. Unfortunately freeing the memory too early does not cause a crash often enough.Ent
@HansPassant : Check out: link. His use of 'AddFontMemResourceEx()' allows for both not using 'UseCompatibleTextRendering' and being able to safely call 'Marshal.FreeCoTaskMem()' (PS. I know, this is very old post!)Truckle
C
8

If you included your font in the resources

Try this function

private void AddFontFromMemory()
{
    Stream fontStream = this.GetType().Assembly.GetManifestResourceStream("yourfont.ttf");
 
    byte[] fontdata = new byte[fontStream.Length];
    fontStream.Read(fontdata,0,(int)fontStream.Length);
    fontStream.Close();

    unsafe
    {
        fixed(byte * pFontData = fontdata)
        {
            pfc.AddMemoryFont((System.IntPtr)pFontData,fontdata.Length);
        }
    }
}

Edited

How load resource from assembly:(YourNamespace.file.ttf)

Stream fontStream = Assembly.GetExecutingAssembly()
 .GetManifestResourceStream("WindowsFormsApplication1.SBADR.TTF");

My solution explorer:

enter image description here

Cantu answered 11/4, 2013 at 12:42 Comment(10)
and how I change the font of label after this?Shearwater
Try this label.Font=new Font(pfc.Families[0],18,FontStyle.Regular);Cantu
I'm getting null exception in ` Stream fontStream = this.GetType().Assembly.GetManifestResourceStream("BOOKOS.TTF"); ` but I have already add the font file in resources. The build property of BOOKOS.TFF is Embedded ...it's right?Shearwater
@illDev:after addfontfrommemory() runes dose pfc have a font family? trace your codeCantu
private void addfontfrommemory() { Stream fontStream = this.GetType().Assembly.GetManifestResourceStream("LoadImagesFromFolder.Resources.BOOKOS.ttf"); byte[] fontdata = new byte[fontStream.Length]; fontStream.Read(fontdata, 0, (int)fontStream.Length); fontStream.Close(); unsafe { fixed (byte* pFontData = fontdata) { pfc.AddMemoryFont((System.IntPtr)pFontData, fontdata.Length); } } }Shearwater
fontStream is null but I don't know whyShearwater
@illDev:see my solution explorerCantu
Thx, it works! I did use System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceNames() to get the Resource nameShearwater
I recommend using this post in combination with #2535427Chess
This is a very old question, but just in case anyone is viewing this in 2019, Corrpside's answer is better, but you can also entirely cut out the FontStream in this answer and instead define FontData as byte[] FontData = Resources.FontName; as .ttf resources are stored as bytes already.Antipersonnel
I
5

This is the way I do it.

First get your Font.ttf file and using Visual Studio, drag and drop the file to the root folder or resource folder.

In Solution Explorer, right-click the file and click properties. Select Build Action = Content. This will show the file in the Application Files under Project Properties > Publish > Application Files. You will see that the file now can be selected (By default it's automatically included).

ClickOnce will now copy the file to the StartupPath

To use it, follow this sample:

PrivateFontCollection pfc = new PrivateFontCollection();

pfc.AddFontFile(Path.Combine(Application.StartupPath, "font_name.ttf"));

textBox1.Font = new Font(pfc.Families[0], 18, FontStyle.Regular);
Intermediary answered 27/11, 2014 at 6:6 Comment(1)
If your font file in root folder, I think you can't bind it with Application.StartupPath because you will get the path with "bin/debug" folder. So you have to use like that; string path = Application.StartupPath.Substring(0, Application.StartupPath.IndexOf("bin")) + fontName.ttf;Barbaresi
Z
4

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.

Zestful answered 23/2, 2020 at 0:31 Comment(1)
(This looks like a great answer. However we would rather not have requests for upvotes here - if people find something useful they will upvote organically. Thanks!)Bumblebee
L
0

This will load a font into a private font collection, and avoid any object reference and memory runtime errors you may see using the examples above.

For performance reasons, we only wanted to load the font once, and keep references to the font for multiple drawing operations between calls. The trick is to ensure the PrivateFontCollection does no go out of scope if you keep a reference to the Font object that you have created.

Add some static (shared) variables

Private Shared _sharedFont As Font
Private Shared _sharedFontCollection As Text.PrivateFontCollection
Private Shared _sharedFontSize As Integer

Then declare these functions

Private Function LoadSharedFont(ByVal fontName As String, ByVal size As Integer, ByVal style As FontStyle) As Font
    'Check if font name or size has changed, then clear cache
    If size <> _sharedFontSize Then _sharedFont = Nothing

    If _sharedFont Is Nothing Then

        'Make this shared so this variable doesnt go out of scope and is garbage collected
        _sharedFontCollection = New Text.PrivateFontCollection()
        _sharedFont = LoadFont(fontName, size, style)
        _sharedFontSize = size
    End If

    Return _sharedFont
End Function

and

Private Function LoadFont(ByVal fontName As String, ByVal size As Integer, ByVal style As FontStyle) As Font
    Dim executingAssembly As System.Reflection.Assembly = Reflection.Assembly.GetCallingAssembly()
    Dim myNamespace As String = executingAssembly.GetName().Name.ToString()

    Try
        Using fontstream = executingAssembly.GetManifestResourceStream(myNamespace + "." + fontName)
            Dim fontBytes(CInt(fontstream.Length)) As Byte
            fontstream.Read(fontBytes, 0, CInt(fontstream.Length))

            Dim fontData As System.IntPtr = Marshal.AllocCoTaskMem(fontBytes.Length)
            Marshal.Copy(fontBytes, 0, fontData, fontBytes.Length)
            _sharedFontCollection.AddMemoryFont(fontData, fontBytes.Length)
            Marshal.FreeCoTaskMem(fontData)
        End Using

        Return New Font(_sharedFontCollection.Families(0), size, style)

    Catch ex As Exception
        'An unexpected error has occurred so return a default Font just in case.
        Return New Drawing.Font("Arial", size, FontStyle.Regular)
    End Try

End Function

Use as follows:

Dim font = LoadSharedFont("OpenSans-CondBold.ttf", 12, FontStyle.Bold)
Lanham answered 23/10, 2017 at 9:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.