4

TLDNR: It is 2017. How can I set font weight when using System.Drawing.createfont

Longer version: Under GDI32 I would create a device context, fill in a LOGFONT structure including a weight value like 400,500,600 etc, and have the font created. The font mapper would run an algorithm to match my request to the best available matching font on the system and return a handle to it.

Under system.drawing the font constructor has 13 overloads but none take an obvious weight. There is a FontFamily param but the only weight effect this has is to add a binary 'bold' option which is not the same as setting the desired font weight.

Why does this matter: Fonts come in families and weight variants. Weight is liek thickness or darkness of the letters. In typesetting, if I need a Semi-bold font then I really must get a semi-bold version of the font assuming it is installed on the machine the code runs on.

What have I done so far? A lot of web-based research via google, SO and MSDN.And looked at all the suggested questions as I wrote this. As yet I have not found a way forward and I am at that point where you know you are asking the wrong questions and looking for someone to point me back in the right direction.

It was possible under GDI - what is the system.drawing equiv.

EDIT: This question came up from a current project which is actually using directWrite (dw), not system.drawing. But one of the requirements is to know that a requested font is available. With a GDI32-mindset there seemed no obvious solution in dw hence we turned to system.drawing, and the question came up.

After some more research triggered by initial response comments to the question we had a revealing moment. This was that the way fonts are requested in dw is entirely different to GDI32. Dw uses the font family / weight / width / slope font model for font matching. It also makes font family variant enumeration easy.

We are coming from a legacy use case where our data includes the GDI32 font name. This is usually the family name, but various font foundaries use this in different ways, you can observe this if you have a font internals tool like TypeTool - one foundary might call their bold italic version 'MyFont Bold Italic' whilst another calls theirs 'MyFont' and leaves the bold italic specifiers to be set in the other font parameters.

Meanwhile Windows (at least Windows 10) seems to have updated the way it maps fonts on install, recognises the crappy names and presents then as a family. MS Office seems not to have joined this approach (witness the crappy names in PowerPoint font selection list, for example), but you can see it if you browse Windows/fonts folder.

Now, this is all very nice but if you just take a legacy GDI32 font name and try to instantiate a font in dw it will fail. The solution appears to be pre-processing the GDI32 font name to chop out the weight, width and slope enum words. Once you have that done then dw will match your font.

This approach seems to be quite viable in initial testing, plus we can easily return some ugly default such as 'Courier' when a font is truly not present, plus we can cheaply get a list of the font family members so could if we wanted select another member if the desired one is not present (I know we lean toward re-inventing the font mapper if we go down that road).

Conclusion: dw font naming is different to GDI32. The more exotic font names you might have used with GDI32 are not guaranteed to work with dw, and need massaging into a dw format, but it IS do-able.

I will update later the result of the above approach.

Vanquished Wombat
  • 9,075
  • 5
  • 28
  • 67
  • 1
    I *believe* you have to specify the exact name of the font when constructing the Font object. This allows you to do something like "Vanquished Wombat Compressed Extra Light". The "weight" in GDI was never very useful; very few fonts supported it, even those that actually did come in multiple weights, since these are almost always distributed as separate font files (e.g., .TTF). – Cody Gray - on strike Jun 20 '17 at 11:32
  • Thanks Cody. Do you mean I should rename all my fonts internally as per your scheme? Given I have data that refer to font weight like '200' I would need to interpret to the font names I think. How does this fit with the 32 char limit on font names? – Vanquished Wombat Jun 20 '17 at 11:35
  • 1
    Seems like WinForms does not have the feature, but WPF does. You could also use P/Invoke. More info in [this question](https://stackoverflow.com/q/25249167) – Dialecticus Jun 20 '17 at 11:37
  • 1
    "Semi-bold" is an OpenType attribute. System.Drawing does not support OpenType, only TrueType. LOGFONT dates from the days where synthesizing fonts still made sense, those days are long gone. An Open Type font may support TrueType as well, the property is then encapsulated in the font name to stay compatible with GDI. OpenType is supported by WPF, it is closer to 2017. – Hans Passant Jun 20 '17 at 11:39
  • 1
    No, no. The fonts already *come* with names. You just need to use those names when you create the font. As far as the 31-character limit (32 includes the NUL terminator), you obviously need to use the short name. Again, this is specified by the vendor in the font file. Yes, you would need to have some kind of interpretive mapping between "200" and the font name, since that is completely arbitrarily chosen by the vendor. Which is why the renderer can't do it successfully, either. If you like the GDI way, create a GDI+ font from a LOGFONT. Or use OpenType, as others have suggested. – Cody Gray - on strike Jun 20 '17 at 11:41
  • Thanks @Dialecticus. I am creating a service - would WPF still be usable? I concur that P/Invoke is a fallback, though I am trying to be a bit more modern. I am not knocking GDI or P/Invoke, it just seems anachronistic to be relying on unmanaged objects etc given the age and maturity of dot net, so I hope for a more gift-wrapped solution. – Vanquished Wombat Jun 20 '17 at 11:42
  • @CodyGray - thanks for the update I will run some experiments and see if I can make something work with font names. From your comments I assert that the system.drawing font mapper is not the same as the GDI font mapper - this because GDI used the logfont structure whereas system.drawing relies more on the font family name plus font style flags. Is that correct? – Vanquished Wombat Jun 20 '17 at 11:46
  • 1
    I honestly don't know exactly how the GDI+ font mapper works. (But checking the documentation, it looks like you're right—some attributes of LOGFONT are ignored by the GDI+ ctor). Font-rendering in GDI+ is ugly and broken. GDI is far superior, so that's what I use. This is probably what you should be using in WinForms, too: `TextRenderer.DrawText`. The only exception is when you're printing, but for that, you won't want to use screen fonts anyway. I'm also a bit puzzled by your comment that you're creating a service. A service shouldn't display UI, so why do you need fonts? – Cody Gray - on strike Jun 20 '17 at 11:52
  • @CodyGray re a service - this is for template based web to print, therefore we need precise font selection, text measuring and placement. Works perfectly under GDI32 - the current project is a re-write in c# using DirectWrite. Why a question on system.drawing.fonts - we seek a way to know if a requested font is actually realised and have not yet found a means under DirectWrite. Am not finding this easy. – Vanquished Wombat Jun 20 '17 at 12:13
  • How do you plan to determine whether the requested font was actually realized using GDI+/System.Drawing? Just like GDI, it's going to fall back to *something*. I guess you'd end up just checking properties of the resulting Font object to see if it's a match? That doesn't seem like a very good implementation. Plus, if you're using DirectWrite, that means you can use OTF fonts, which aren't supported by System.Drawing, so you'd actually be getting false negatives. Since you're using DirectWrite, why not just call `GetPanose`, and check the fields of the filled-in `DWRITE_PANOSE` structure? – Cody Gray - on strike Jun 20 '17 at 12:28
  • Yes @CodyGray you are correct that we are throwing functionality overboard to stop the boat from sinking. Will look in to panose. Thanks. – Vanquished Wombat Jun 20 '17 at 13:06
  • @CodyGray Thanks for your multiple responses - this caused us to re-assess and return to seek a directwrite solution which is not in R&D.I will update the final conclusions when we have them. – Vanquished Wombat Jun 21 '17 at 10:57
  • 1
    Cool. I had left this tab open, considering testing out exactly how the font mapper works in GDI+ and how that interacts with the .NET wrapper (System.Drawing) and posting a thorough answer of how to do what you want, but it sounds like you don't need that anymore. I'm still willing to do it, if you think it would be helpful, but after hearing more about your design and requirements, I would still stand by my final comment, that it makes more sense to just stick with doing everything in DirectWrite. Sounds like your team agrees with that, too. Sorry to upend your goals! – Cody Gray - on strike Jun 21 '17 at 11:37
  • @CodyGray - no worries. And correction on a typo in last comment - I meant a solution IS in R&D now, and I will report back. I am feeding them Redbull so it may be soon. – Vanquished Wombat Jun 21 '17 at 14:10

0 Answers0