Loading a font from resources into PrivateFontCollection results in corruption repost
Asked Answered
T

1

1

I am having some difficulty in loading a font from a resource into PrivateFontCollection.

When I started this, I was successful in loading the font from a file, however I wish to embed the font into my project (so there is less file mess on the user side, and a bit less IO while the application is running).

The following code will load the font, gets the proper name, and allows for scaling however none of the characters are showing up properly.

static class Foo {

  public static string FontAwesomeTTF { get; set; }

  public static Font FontAwesome { get; set; }

  public static float Size {  get; set; }

  public static FontStyle Style { get; set; }

  private static PrivateFontCollection pfc { get; set; }

  static Foo() {
    // This was set when loading from a file.
    //  FontAwesomeTTF = "fontawesome-webfont.ttf";
    Style = FontStyle.Regular;
    Size = 20;

    if ( pfc==null ) {
      pfc=new PrivateFontCollection();
      if ( FontAwesomeTTF==null ) {
        var fontBytes=Properties.Resources.fontawesome_webfont;
        var fontData=Marshal.AllocCoTaskMem( fontBytes.Length );
        Marshal.Copy( fontBytes, 0, fontData, fontBytes.Length );
        pfc.AddMemoryFont( fontData, fontBytes.Length );
        Marshal.FreeCoTaskMem( fontData );
      } else {
        pfc.AddFontFile( FontAwesomeTTF );
      }
    }
    FontAwesome = new Font(pfc.Families[0], Size, Style);
  }

  private static string UnicodeToChar( string hex ) {
    int code=int.Parse( hex, System.Globalization.NumberStyles.HexNumber );
    string unicodeString=char.ConvertFromUtf32( code );
    return unicodeString;
  }

  public static string glass { get { return UnicodeToChar("f000"); } }

}

Example usage :

label1.Font = Foo.FontAwesome;
label1.Text = Foo.glass;

This is what it looks like loading from memory: Loading from memory

This is what it look like loading from file: Loading from file

I am using the current FontAwesome TTF file in both the embedded resource and the file based tests. It seems that when embedded something is being lost or scrambled in the translation or when loading from embedded. I need help making this work so I can load the font from an embedded resource into PrivateFontCollection.

There are a few 'solutions' I looked at on SO however they are quite dated and some or all of the commands/accessors are no longer available in Visual Studio 2013 (article solution was from 4-5 years ago). Some example 'solutions' and why they don't work:

Solution #1 - This doesn't work as the accessor string for the font returns null. In my case, the accessor is MyProject.Properties.Resources.fontawesome_webfont

Solution #2 - This solution came the closest, but again the method used to access the resource no longer works. My code above implements a harvested version of this taking out the core concept of passing the byte[] array into memory and then loading from memory. Since the get{} property of the resource in my case returns a byte[] array already, there was no need to 'convert' it into a byte array and thus I was (seemingly) safely able to remove that portion of the code by updating it to use the newer accessor.

In either case, I would like a solution for this problem that allows me to load the font file from the embedded resources into PrivateFontCollection.

Tradesfolk answered 12/10, 2015 at 18:29 Comment(9)
If this isn't a duplicate, then what happens when you try what the answer suggested? Your instance of the problem might not crash, but the code looks basically identical. Pretty expert duplicate-finding Hans did. (For context, here's the original question this is a repost of. (See the title of this question--this isn't an accusation, just a clarification.))Kolnos
Just to rule out an XY problem, what are you trying to do that think requires having a PrivateFontCollection available? Bundling fonts as separate files is a fairly common practice (especially for DTP applications, because it means the fonts can be easily updated to newer versions by your users), and I am not sure there's an IO benefit, since fonts are dealt with by things like DirectWrite, which are specifically designed to perform as few accesses as is possible to shape text.Jovanjove
@Kolnos - you can see for yourself. The exact same issue. The font loads, but the characters are wrong.Tradesfolk
@Mike'Pomax'Kamermans - PFC remains constant, and the drawing is handled by directwrite. Other methods I have seen for loading fonts do not allow for persistance and reuse (meaning the font must be reloaded completely again on each use/assignment). If no one knows how to do this properly, I will eventually figure it out anyway or simply load from a file (once) since that part works. I would just PREFER to embed the font rather than have another file in the folder. And yes, updating the font is great, but have you seen the unicode offset difference between FontAwesome 1 and v4.1 ?Tradesfolk
@Kolnos - also ... if you note the other others code 'basically' the same, is not even close. His problem is actually caused by freeing the memory the resource was loaded into, and THEN trying to assign it to the PFC which of course would result in a crash since the memory was freed. He should be freeing the memory AFTER adding it to the collection.Tradesfolk
@SanuelJackson Good, that's the sort of info I expect to see in the new question along with a link to the answer. It could be a new part of your "potential solutions" section. The duplicate header tells you to ask a new question (at least in part) so you can include info like that and get a fresh start. It's a way to tell you how readers are interpreting your question, and using that info you can formulate a better question. Telling people to read more carefully isn't helpful: telling people what they misread is helpful.Kolnos
@Kolnos - I was pretty specific with regards to the problem, and the solution I was expecting. Your suggestion would be to cross link anything that vaguely resembles one or two lines of code in my demonstration of the problem which is unreasonable. If you note, I also did make the effort to cross-link to topics that were more akin to the issue I am having and the issues with the proposed solutions. The question is phrased correctly, "corruption" does not mean "crash". Assuming it is fellow developers responding, I assume they know the difference, and if not -- there are screenshots above.Tradesfolk
@Kolnos - Further "The following code will load the font, gets the proper name, and allows for scaling however none of the characters are showing up properly." specifically "none of the characters are showing up properly" is glaring on the 3rd line showing the code does not crash, and is an opening summary statement of the specific problem prior to going into the minute details.Tradesfolk
Remember to let DirectWrite handle the font's internals: that's what it's for. Offset differences, cmap changes, segment deltas, even BPM plane support matrices should all be irrelevant if you're using DirectWrite, since you're passing UTF code point sequences to the shaper, and it will handle the rest.Jovanjove
K
3

AddMemoryFont's remarks section says:

To use the memory font, text on a control must be rendered with GDI+. Use the SetCompatibleTextRenderingDefault method, passing true, to set GDI+ rendering on the application, or on individual controls by setting the control's UseCompatibleTextRendering property to true. Some controls cannot be rendered with GDI+.

I reproduced your issue locally and tried it out by changing your usage sample to:

label1.Font = Foo.FontAwesome;
label1.Text = Foo.glass;
label1.UseCompatibleTextRendering = true;

This turns on GDI+ rendering for that label, allowing the font added with AddMemoryFont to render correctly.

Kolnos answered 13/10, 2015 at 0:17 Comment(11)
Thank you. Will give this a try.Tradesfolk
This worked for the label controls. I am curious how I would apply this to a textedit control or textbox / rtf / etc since those controls don't seem to have that method.Tradesfolk
@SanuelJackson I tried the SetCompatibleTextRenderingDefault route with no success, unfortunately. Now I'm thinking of a workaround: would writing the file somewhere in %temp% and loading from there work for you? I'll add it to the answer if so.Kolnos
I am considering the same thing, however, it would be ideal not to clutter the users system. It might sound a bit on the anal side, however I am sick of applications just bloating and leaving so much residual garbage the user needs to cleanup. At least from my end I can do everything possible to minimize this even if it makes my job more difficult. It is easy to take the easy route and just drop one more file with the application installer so there would be a permanent location for it. What doesn't make any sense is why those controls claim to be GDI+, yet don't work with this which allegedlyTradesfolk
(continued) .. use GDI+. There must be a way to do this. I have even considered creating a TextBox control from scratch, but that is way more cumbersome than it should be. Inheriting a TextBox control leaves you with the text inside the box as well as what you paint/draw with no real way to eliminate it, so I have really hit a brick wall. Loading from memory seems to be a natural process, however it is clearly broken in .NET. Have you tried with VS2015 to see if there is a way to make it work properly there ? -- Thanks again for your time on this puzzle.Tradesfolk
Do they claim to be GDI+? The docs to me suggested that GDI+ mode only works for old things for compatibility reasons, but new things don't work with it. I'm using 2015 at the moment but I have close to no experience with windows forms, sorry, don't know what else to look for. Re: copying to temp, could you copy it temporarily and immediately delete it once it's loaded, or something like that? The copy would just to get it out of the binary so it can be used as a file.Kolnos
Reading from > #25061709 < the first answer is implied that all uses GDI+ as the Graphics object is just a wrapper for GDI+ (which is how I understood it from the Dev conference I went to around VS2013 release). As far as GDI+ vs GDI, the way I understood it, is that GDI+ is an enhanced version of GDI with more extensions, improved speed, runs faster, jumps higher .. etc. -- hence the +. I can try the copy/delete, but I am thinking that *could be a problem if loading from File doesn't persist in memory.Tradesfolk
@SanuelJackson Ah, ok, I made some bad assumptions based on the docs. As for the temp file, maybe it could be deleted when the app exits, or not at all? Since it's in the temp folder, maybe it's not an issue if it accidentally sits around due to a crash or something, since it'll be cleaned up anyway at some point. A real solution would be better though--and I might actually prefer an installer dropping a ttf down over this anyway. Tough issue, I hope you manage to find a real solution.Kolnos
I figured it out :). Editing your answer to include the solution. It's a really weird solution too.Tradesfolk
Turns out editing the comment will be very cumbersome. I will add the 'answer' as a solution to the Question that deals more specifically with the TextBox / etc stuff. Your answer is acceptable for the question here as I did not give any real indication that it was to be used outside of Labels (minor oversight on my part). (see the other question here > #33116380 -- solution being drafted up now )Tradesfolk
Finished. This solution doesn't require any flags to be set on the controls or for the project in order to use the font. >> https://mcmap.net/q/828333/-how-to-render-a-font-from-privatefontcollection-memory-to-editable-controls << and it works on any control including textbox, etc. Adding the link here so that it can be easily found by others when looking for it.Tradesfolk

© 2022 - 2024 — McMap. All rights reserved.