Reputation: 1003
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?
Upvotes: 14
Views: 37107
Reputation: 10143
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:
Upvotes: 8
Reputation: 1486
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
[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.
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.
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.
Upvotes: 4
Reputation: 6258
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;
}
Upvotes: 13
Reputation: 11444
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)
Upvotes: 0
Reputation: 99
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);
Upvotes: 6