Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to render a font from privatefontcollection memory to editable controls

This is a continuation of Loading a font from resources into PrivateFontCollection results in corruption

The answer supplied here is sufficient for controls that have the UseCompatibleTextRendering method available, however it does not appear to be available for other common controls which text is the primary focus such as :

  • ListView
  • TextBox
  • RichTextBox
  • ComboBox
  • ... and many more...

I have attempted the information from here which is basically toying with the Application.SetCompatibleTextRenderingDefault line in Program.cs (no one clarified where this setting is by default so I am documenting it here). I have also played around with Telerik, DevExpress, and Infragistics text controls, all except Telerik do not have the ability for compatible text rendering built in. Teleriks control has the method, however it has zero effect including failure to set forecolor to what is stored in the property (a different animal, just noting the glitchiness of the Telerik radTextBox control).

It seems no matter how I slice it, any control that is actually useful with text will not render the text properly displaying the square characters as depicted in the original post noted above.

In summary :

  • The font is loading from a resource into memory into PrivateFontCollection
  • The application does not crash
  • The same font is being used on labels successfully (UseCompatibleTextRendering works on them) - on the same form, in the same project.

  • The controls that are affected by this (new ?) problem are strictly any control that one can potentially 'type' in such as TextEdit, ListView, RichText, Combo, etc

  • When speaking of toggling, and toying or playing with -- what this means is that I have attempted all possible combinations of said controls and/or code that I have been supplied with. For example: Application.SetCompatibleTextRenderingDefault has only 3 possible combinations in itself. (true) (false) or completely omitted. After having completed those 3 combinations, I then proceeded (basic troubleshooting here but I find it necessary to explain to cover all bases) to add a Telerik control, then try all combinations in the Telerik control, in conjunction with all combinations of the Application.SetCompatibleTextRenderingDefault command. The number of tests is exponential being the number of possible combinations of rendering possibilities multiplied by the number of controls attempted multiplied by the number of possibilities for rendering control each of those controls have, and so on.

like image 221
Kraang Prime Avatar asked Oct 14 '15 03:10

Kraang Prime


1 Answers

After a lot of digging around, and coming up empty on this (including the drive-by downvote on this question which still puzzles me), I have found a solution to this problem which it seems many have faced and have also come up empty-handed resorting to just installing the font as a file on the users system. As I thought, this is not required at all and you CAN embed the font in a simple way which allows it to be used on any control including TextBox, ComboBox, etc. Whatever your heart desires.

I will write this as a step by-step guide on how to embed a font into your project AND have it display properly on any control you desire.

Step 1 - Choosing your victim (the font choice)

For demonstration purposes (and popular demand), I will be using the FontAwesome font (v 4.1 at the time of writing this)

  • Inside your project, go to Project > <your project name> Properties
  • Click Resources (should be on the left side)
  • Click the dropdown arrow to the right of the Add Resource button, and select Add Existing File.

enter image description here

  • Make sure the All Files (*.*) is selected from the drop down and then browse to where the font file is your wish to embed, select it, and click the [Open] button. enter image description here

You should now see something like the following : enter image description here

  • Press [CTRL]+[S] to save the project.

Step 2 - Diving into the code

Copy the following code and save it as 'MemoryFonts.cs' then add it to your project

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Runtime.InteropServices;
using System.Drawing.Text;
using System.Drawing;

public static class MemoryFonts {

    [DllImport( "gdi32.dll" )]
    private static extern IntPtr AddFontMemResourceEx( IntPtr pbFont, uint cbFont, IntPtr pdv, [In] ref uint pcFonts );
    private static PrivateFontCollection pfc { get; set; }

    static MemoryFonts() {
        if ( pfc==null ) { pfc=new PrivateFontCollection(); }
    }

    public static void AddMemoryFont(byte[] fontResource) {
        IntPtr p;
        uint c = 0;

        p=Marshal.AllocCoTaskMem( fontResource.Length );
        Marshal.Copy( fontResource, 0, p, fontResource.Length );
        AddFontMemResourceEx( p, (uint)fontResource.Length, IntPtr.Zero, ref c );
        pfc.AddMemoryFont( p, fontResource.Length );
        Marshal.FreeCoTaskMem( p );

        p = IntPtr.Zero;
    }

    public static Font GetFont( int fontIndex, float fontSize = 20, FontStyle fontStyle = FontStyle.Regular ) {
        return new Font(pfc.Families[fontIndex], fontSize, fontStyle);
    }

    // Useful method for passing a 4 digit hex string to return the unicode character
    // Some fonts like FontAwesome require this conversion in order to access the characters
    public static string UnicodeToChar( string hex ) {
        int code=int.Parse( hex, System.Globalization.NumberStyles.HexNumber );
        string unicodeString=char.ConvertFromUtf32( code );
        return unicodeString;
    }

}

Step 3 - Making it pretty (using the font)

  • On the main form, add a TextBox control from your toolbox enter image description here

  • Inside the form_Load event, put the following code (in my case the resource is fontawesome_webfont. change that to whatever your font resource is named)

    private void Form1_Load( object sender, EventArgs e ) {
        MemoryFonts.AddMemoryFont( Properties.Resources.fontawesome_webfont );
        textBox1.Font = MemoryFonts.GetFont(
                            // using 0 since this is the first font in the collection
                            0,
    
                            // this is the size of the font
                            20, 
    
                            // the font style if any. Bold / Italic / etc
                            FontStyle.Regular
                        );
    
        // since I am using FontAwesome, I would like to display one of the icons
        // the icon I chose is the Automobile (fa-automobile). Looking up the unicode
        // value using the cheat sheet https://fortawesome.github.io/Font-Awesome/cheatsheet/
        // shows :  fa-automobile (alias) [&#xf1b9;]
        // so I pass 'f1b9' to my UnicodeToChar method which returns the Automobile icon 
        textBox1.Text = MemoryFonts.UnicodeToChar( "f1b9" );
    }
    

The end result

enter image description here


You may or may not have noticed that I made this method keeping in mind that you may wish to add multiple embedded fonts. In which case, you just call AddMemoryFont() for each of the fonts you want to add, and then use the appropriate index value (zero based) when using GetFont()

Contrary to the document regarding PrivateFontCollection.AddMemoryFont()'s documentation from Microsoft, you DO NOT NEED to use UseCompatibleTextRendering or SetCompatibleTextRenderingDefault at all. The method I outlined above allows for natural rendering of the font in objects/controls.

like image 168
Kraang Prime Avatar answered Sep 19 '22 08:09

Kraang Prime