Parse/understand weight suffixes in font names #13350

Open
opened 2026-01-31 03:40:23 +00:00 by claunia · 12 comments
Owner

Originally created by @miniksa on GitHub (Apr 7, 2021).

From #9375

  • C. In the future, we'll perhaps try to parse weight suffixes out of the specified font name and transform them into weight values or something more complicated. Backlog item.

Originally posted by @miniksa in https://github.com/microsoft/terminal/issues/9375#issuecomment-811522579

EDIT: See #10777 for some progress here that never ended up getting cleaned up and finished

Originally created by @miniksa on GitHub (Apr 7, 2021). From #9375 - C. In the future, we'll perhaps try to parse weight suffixes out of the specified font name and transform them into weight values or something more complicated. Backlog item. _Originally posted by @miniksa in https://github.com/microsoft/terminal/issues/9375#issuecomment-811522579_ EDIT: See #10777 for some progress here that never ended up getting cleaned up and finished
claunia added the Area-RenderingArea-SettingsIssue-TaskProduct-Terminal labels 2026-01-31 03:40:23 +00:00
Author
Owner

@fdwr commented on GitHub (Jul 23, 2021):

Advice: rely on DWrite as much as possible, and don't parse it yourself 🦉. Having worked on DirectWrite for a decade, manually attempting this is a land mine 💣, and there's already pretty some pretty capable logic inside DWrite for resolving them 🤓 (WPF Font Selection Model by Mikhail Leonov and David Brown).

There are many possible ways of organizing/clustering a set of fonts in an IDWriteFontSet 📚 (interface available since Windows 10 RTM, and IDWriteFontSet1 since 1709 Redstone 3 "Fall Creators Update" 16299), with the classic IDWriteFontCollection being one possible projection of fonts into a weight/width/slope model (the most familiar one). You can think of IDWriteFontSet as the data set underlying the IDWriteFontCollection 📕 / IDWriteFontFamily 📗 / IDWriteFontList 📘 objects, with all the string properties and loose fonts (no heirarchy here in a set).

If a user tries to use the full name "Arial Nova Bold Italic" rather than the family name "Arial Nova" + wght:700 + slnt:-12, you can resolve that via (GetFilteredFonts | GetMatchingFonts) + CreateFontFace.

  1. Either call:
    a. IDWriteFontSet1::GetMatchingFonts directly by passing the font name into each font property, called in sequence: {DWRITE_FONT_PROPERTY_ID_TYPOGRAPHIC_FAMILY_NAME (the preferred typographic name, e.g. "Arial" + Bold + Italic + Narrow), DWRITE_FONT_PROPERTY_ID_WEIGHT_STRETCH_STYLE_FAMILY_NAME (the typical WWS model, used by CSS/WPF which in common cases is identical to the typographic family name, e.g. "Arial" + Bold + Italic + Narrow) DWRITE_FONT_PROPERTY_ID_WIN32_FAMILY_NAME (the old GDI-style name, e.g. "Arial Narrow" + Bold + Italic), DWRITE_FONT_PROPERTY_ID_FULL_NAME (the full name, e.g. "Arial Narrow Bold Italic"}. Check for an empty set return value (IDWriteFontSet::GetFontCount() == 0) each time until the list is non-zero.
    b. Or IDWriteFontSet1::GetFilteredFonts, passing the full array {DWRITE_FONT_PROPERTY_ID_TYPOGRAPHIC_FAMILY_NAME, DWRITE_FONT_PROPERTY_ID_WIN32_FAMILY_NAME, DWRITE_FONT_PROPERTY_ID_FULL_NAME} and selectAnyProperty = true. With the filtered font set, you can get a matching font set in priority order of your font axes IDWriteFontSet1::GetMatchingFonts. Note the GetMatchingFonts optional fontProperty parameter can be null here, since you already filtered by a property in GetFilteredFonts.
  2. IDWriteFontSet1::CreateFontFace

Playing around with this little app may make it clearer (or fuzzier 🤨😅...)

https://github.com/fdwr/FontSetViewer

@fdwr commented on GitHub (Jul 23, 2021): Advice: rely on DWrite as much as possible, and don't parse it yourself 🦉. Having worked on DirectWrite for a decade, manually attempting this is a land mine 💣, and there's already pretty some pretty capable logic inside DWrite for resolving them 🤓 ([WPF Font Selection Model by Mikhail Leonov and David Brown](https://www.noesisengine.com/bugs/file_download.php?file_id=1356&type=bug)). There are many possible ways of organizing/clustering a set of fonts in an `IDWriteFontSet` 📚 (interface available since Windows 10 RTM, and `IDWriteFontSet1` since 1709 Redstone 3 "Fall Creators Update" 16299), with the classic `IDWriteFontCollection` being one possible projection of fonts into a weight/width/slope model (the most familiar one). You can think of `IDWriteFontSet` as the data set underlying the `IDWriteFontCollection` 📕 / `IDWriteFontFamily` 📗 / `IDWriteFontList` 📘 objects, with all the string properties and loose fonts (no heirarchy here in a set). If a user tries to use the full name "Arial Nova Bold Italic" rather than the family name "Arial Nova" + wght:700 + slnt:-12, you can resolve that via (`GetFilteredFonts` | `GetMatchingFonts`) + `CreateFontFace`. 1. Either call: a. [`IDWriteFontSet1::GetMatchingFonts`](https://docs.microsoft.com/en-us/windows/win32/api/dwrite_3/nf-dwrite_3-idwritefontset1-getmatchingfonts) directly by passing the font name into each font property, called in sequence: {`DWRITE_FONT_PROPERTY_ID_TYPOGRAPHIC_FAMILY_NAME` (the preferred typographic name, e.g. "Arial" + Bold + Italic + Narrow), `DWRITE_FONT_PROPERTY_ID_WEIGHT_STRETCH_STYLE_FAMILY_NAME` (the typical WWS model, used by CSS/WPF which in common cases is identical to the typographic family name, e.g. "Arial" + Bold + Italic + Narrow) `DWRITE_FONT_PROPERTY_ID_WIN32_FAMILY_NAME` (the old GDI-style name, e.g. "Arial Narrow" + Bold + Italic), `DWRITE_FONT_PROPERTY_ID_FULL_NAME` (the full name, e.g. "Arial Narrow Bold Italic"}. Check for an empty set return value (`IDWriteFontSet::GetFontCount()` == 0) each time until the list is non-zero. b. Or [`IDWriteFontSet1::GetFilteredFonts`](https://docs.microsoft.com/en-us/windows/win32/api/dwrite_3/nf-dwrite_3-idwritefontset1-getfilteredfonts(dwrite_font_propertyconst_uint32_bool_idwritefontset1)), passing the full array {`DWRITE_FONT_PROPERTY_ID_TYPOGRAPHIC_FAMILY_NAME`, `DWRITE_FONT_PROPERTY_ID_WIN32_FAMILY_NAME`, `DWRITE_FONT_PROPERTY_ID_FULL_NAME`} and `selectAnyProperty` = true. With the filtered font set, you can get a matching font set in priority order of your font axes [`IDWriteFontSet1::GetMatchingFonts`](https://docs.microsoft.com/en-us/windows/win32/api/dwrite_3/nf-dwrite_3-idwritefontset1-getmatchingfonts). Note the `GetMatchingFonts` optional `fontProperty` parameter can be null here, since you already filtered by a property in `GetFilteredFonts`. 2. [`IDWriteFontSet1::CreateFontFace`](https://docs.microsoft.com/en-us/windows/win32/api/dwrite_3/nf-dwrite_3-idwritefontset1-createfontface) Playing around with this little app may make it clearer (or fuzzier 🤨😅...) https://github.com/fdwr/FontSetViewer
Author
Owner

@miniksa commented on GitHub (Jul 23, 2021):

@miniksa : Advice: rely on DWrite as much as possible, and don't parse it yourself 🦉. Having worked on DirectWrite for a decade, manually attempting this is a land mine 💣, and there's already a pretty some pretty capable logic inside DWrite for resolving them 🤓 (WPF Font Selection Model by Mikhail Leonov and David Brown).

There are many possible ways of organizing/clustering a set of fonts in an IDWriteFontSet 📚 (interface available since Windows 10 RTM), with the classic IDWriteFontCollection being one possible projection of fonts into a weight/width/slope model (the most familiar one). You can think of IDWriteFontSet as the data set underlying the IDWriteFontCollection 📕 / IDWriteFontFamily 📗 / IDWriteFontList 📘 objects, with all the string properties and loose fonts (no heirarchy here in a set).

If a user tries to use the full name "Arial Nova Bold Italic" rather than the family name "Arial Nova" + wght:700 + slnt:-12, you can resolve that via (GetFilteredFonts | GetMatchingFonts) + CreateFontFace.

  1. Either call:
    a. GetMatchingFonts directly by passing the font name into each font property, called in sequence: {DWRITE_FONT_PROPERTY_ID_TYPOGRAPHIC_FAMILY_NAME (the preferred typographic name, e.g. "Arial" + Bold + Italic + Narrow), DWRITE_FONT_PROPERTY_ID_WIN32_FAMILY_NAME (the old GDI-style name, e.g. "Arial Narrow" + Bold + Italic), DWRITE_FONT_PROPERTY_ID_FULL_NAME (the full name, e.g. "Arial Narrow Bold Italic"}. Check for an empty set return value (IDWriteFontSet::GetFontCount() == 0) each time until the list is non-zero.
    b. Or IDWriteFontSet1::GetFilteredFonts, passing the full array {DWRITE_FONT_PROPERTY_ID_TYPOGRAPHIC_FAMILY_NAME, DWRITE_FONT_PROPERTY_ID_WIN32_FAMILY_NAME, DWRITE_FONT_PROPERTY_ID_FULL_NAME} and selectAnyProperty = true. With the filtered font set, you can get a matching font set in priority order of your font axes IDWriteFontSet1::GetMatchingFonts. Note the GetMatchingFonts optional fontProperty parameter can be null here, since you already filtered by a property in GetFilteredFonts.
  2. IDWriteFontSet1::CreateFontFace

Playing around with this little app may make it clearer (or fuzzier 🤨😅...)

fdwr/FontSetViewer

That's perfect, @fdwr. I'd much rather call one of those things and let DirectWrite parse the alternatives. I didn't know that was possible. Thank you!

@miniksa commented on GitHub (Jul 23, 2021): > @miniksa : Advice: rely on DWrite as much as possible, and don't parse it yourself 🦉. Having worked on DirectWrite for a decade, manually attempting this is a land mine 💣, and there's already a pretty some pretty capable logic inside DWrite for resolving them 🤓 ([WPF Font Selection Model by Mikhail Leonov and David Brown](https://www.noesisengine.com/bugs/file_download.php?file_id=1356&type=bug)). > > There are many possible ways of organizing/clustering a set of fonts in an `IDWriteFontSet` 📚 (interface available since Windows 10 RTM), with the classic `IDWriteFontCollection` being one possible projection of fonts into a weight/width/slope model (the most familiar one). You can think of `IDWriteFontSet` as the data set underlying the `IDWriteFontCollection` 📕 / `IDWriteFontFamily` 📗 / `IDWriteFontList` 📘 objects, with all the string properties and loose fonts (no heirarchy here in a set). > > If a user tries to use the full name "Arial Nova Bold Italic" rather than the family name "Arial Nova" + wght:700 + slnt:-12, you can resolve that via (`GetFilteredFonts` | `GetMatchingFonts`) + `CreateFontFace`. > > 1. Either call: > a. [`GetMatchingFonts`](https://docs.microsoft.com/en-us/windows/win32/api/dwrite_3/nf-dwrite_3-idwritefontset1-getmatchingfonts) directly by passing the font name into each font property, called in sequence: {`DWRITE_FONT_PROPERTY_ID_TYPOGRAPHIC_FAMILY_NAME` (the preferred typographic name, e.g. "Arial" + Bold + Italic + Narrow), `DWRITE_FONT_PROPERTY_ID_WIN32_FAMILY_NAME` (the old GDI-style name, e.g. "Arial Narrow" + Bold + Italic), `DWRITE_FONT_PROPERTY_ID_FULL_NAME` (the full name, e.g. "Arial Narrow Bold Italic"}. Check for an empty set return value (`IDWriteFontSet::GetFontCount()` == 0) each time until the list is non-zero. > b. Or [`IDWriteFontSet1::GetFilteredFonts`](https://docs.microsoft.com/en-us/windows/win32/api/dwrite_3/nf-dwrite_3-idwritefontset1-getfilteredfonts(dwrite_font_propertyconst_uint32_bool_idwritefontset1)), passing the full array {`DWRITE_FONT_PROPERTY_ID_TYPOGRAPHIC_FAMILY_NAME`, `DWRITE_FONT_PROPERTY_ID_WIN32_FAMILY_NAME`, `DWRITE_FONT_PROPERTY_ID_FULL_NAME`} and `selectAnyProperty` = true. With the filtered font set, you can get a matching font set in priority order of your font axes [`IDWriteFontSet1::GetMatchingFonts`](https://docs.microsoft.com/en-us/windows/win32/api/dwrite_3/nf-dwrite_3-idwritefontset1-getmatchingfonts). Note the `GetMatchingFonts` optional `fontProperty` parameter can be null here, since you already filtered by a property in `GetFilteredFonts`. > 2. [`IDWriteFontSet1::CreateFontFace`](https://docs.microsoft.com/en-us/windows/win32/api/dwrite_3/nf-dwrite_3-idwritefontset1-createfontface) > > Playing around with this little app may make it clearer (or fuzzier 🤨😅...) > > [fdwr/FontSetViewer](https://github.com/fdwr/FontSetViewer) That's perfect, @fdwr. I'd much rather call one of those things and let DirectWrite parse the alternatives. I didn't know that was possible. Thank you!
Author
Owner

@miniksa commented on GitHub (Jul 23, 2021):

@fdwr, take a peek at #10777. I think I got a basic handle on calling the sets, but I still have some things to iron out. Please let me know if you have any pro tips.

@miniksa commented on GitHub (Jul 23, 2021): @fdwr, take a peek at #10777. I think I got a basic handle on calling the sets, but I still have some things to iron out. Please let me know if you have any pro tips.
Author
Owner

@fdwr commented on GitHub (Jul 24, 2021):

@miniksa : Wow, you work fast. Ok, added some comments. The PR said "Not ready for review. Go away.", but I presume you intended that as a deflection for others. :b

@fdwr commented on GitHub (Jul 24, 2021): @miniksa : Wow, you work fast. Ok, added some comments. The PR said "Not ready for review. Go away.", but I presume you intended that as a deflection for *others*. :b
Author
Owner

@miniksa commented on GitHub (Jul 24, 2021):

@miniksa : Wow, you work fast. Ok, added some comments. The PR said "Not ready for review. Go away.", but I presume you intended that as a deflection for others. :b

Correct. I didn't want my team up in my business. I wanted to work it out with you first.

@miniksa commented on GitHub (Jul 24, 2021): > @miniksa : Wow, you work fast. Ok, added some comments. The PR said "Not ready for review. Go away.", but I presume you intended that as a deflection for *others*. :b Correct. I didn't want my team up in my business. I wanted to work it out with you first.
Author
Owner

@miniksa commented on GitHub (Jan 27, 2022):

Do note that I closed the PR for this as it has been parked for a LOOONG time. But if someone would like to see more notes and pick it up again, it should have historical context in #10777

@miniksa commented on GitHub (Jan 27, 2022): Do note that I closed the PR for this as it has been parked for a LOOONG time. But if someone would like to see more notes and pick it up again, it should have historical context in #10777
Author
Owner

@zadjii-msft commented on GitHub (Mar 6, 2024):

thought off the dome: @lhecker did #16821 do anything for this?

@zadjii-msft commented on GitHub (Mar 6, 2024): thought off the dome: @lhecker did #16821 do anything for this?
Author
Owner

@lhecker commented on GitHub (Mar 6, 2024):

No, it still uses FindFamilyName.

I wasn't aware about @fdwr's excellent write-up above, so I started trying it out. But now I'm not entirely sure whether it's worth doing this over the single FindFamilyName call for two reasons:

  • There doesn't seem to be any interest from our users to be able to specify such suffixes.
  • Entirely personally speaking, I'd prefer if our font setting worked exactly like CSS' font-family, because I feel like it gets us closer to the "principle of least surprise". The way it's described in the write-up, it seems like FindFamilyName already gives us exactly this behavior.
@lhecker commented on GitHub (Mar 6, 2024): No, it still uses `FindFamilyName`. I wasn't aware about @fdwr's excellent write-up above, so I started trying it out. But now I'm not entirely sure whether it's worth doing this over the single `FindFamilyName` call for two reasons: * There doesn't seem to be any interest from our users to be able to specify such suffixes. * Entirely personally speaking, I'd prefer if our font setting worked exactly like CSS' `font-family`, because I feel like it gets us closer to the "principle of least surprise". The way it's described in the write-up, it seems like `FindFamilyName` already gives us exactly this behavior.
Author
Owner

@Diablo-D3 commented on GitHub (Mar 8, 2024):

font-family can't handle width. I should be able to tell wt to do something like "Suchandsuchfont Narrow Oblique Semilight", using some combination of lines in settings.json and get what I want. My reading of FindFamilyName's documentation does not seem to indicate that this can do that.

@Diablo-D3 commented on GitHub (Mar 8, 2024): `font-family` can't handle width. I should be able to tell wt to do something like "Suchandsuchfont Narrow Oblique Semilight", using some combination of lines in settings.json and get what I want. My reading of `FindFamilyName`'s documentation does not seem to indicate that this can do that.
Author
Owner

@DHowett commented on GitHub (Mar 8, 2024):

some combination of lines in settings.json

Today, I believe that combination of lines is...

"font": {
    "face": "Suchandsuchfont",
    "axes": {
        "wdth": 10,  // width axis, change me as desired - NARROW
        "ital": 1,   // italic angle or italic on/off - OBLIQUE
        "wght": 350, // weight 350 SEMI-LIGHT; maybe 300; you can also use OPTION B below
    },
    "weight": "semi-light" // OPTION B - SEMI-LIGHT
}
@DHowett commented on GitHub (Mar 8, 2024): > some combination of lines in settings.json _Today,_ I believe that combination of lines is... ```jsonc "font": { "face": "Suchandsuchfont", "axes": { "wdth": 10, // width axis, change me as desired - NARROW "ital": 1, // italic angle or italic on/off - OBLIQUE "wght": 350, // weight 350 SEMI-LIGHT; maybe 300; you can also use OPTION B below }, "weight": "semi-light" // OPTION B - SEMI-LIGHT } ```
Author
Owner

@fdwr commented on GitHub (Mar 8, 2024):

I should be able to tell wt to do something like "Suchandsuchfont Narrow Oblique Semilight", using some combination of lines in settings.json and get what I want. My reading of FindFamilyName's documentation does not seem to indicate that this can do that.

"Suchandsuchfont Narrow Oblique Semilight" isn't a family name, but rather the full name of just one font instance within a family.

So either accessing them via the named enums or via axes Dustin gave above should work via DirectWrite.

@fdwr commented on GitHub (Mar 8, 2024): > I should be able to tell wt to do something like "Suchandsuchfont Narrow Oblique Semilight", using some combination of lines in settings.json and get what I want. My reading of `FindFamilyName`'s documentation does not seem to indicate that this can do that. "Suchandsuchfont Narrow Oblique Semilight" isn't a family name, but rather the full name of just one font instance within a family. - On Windows 7+, within the family "Suchandsuchfont", you can call [IDWriteFontFamily::GetFirstMatchingFont](https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritefontfamily-getfirstmatchingfont) with the weight/width/slope properties you want (e.g. `DWRITE_FONT_WEIGHT_SEMI_LIGHT`, `DWRITE_FONT_STRETCH_CONDENSED`, `DWRITE_FONT_STYLE_OBLIQUE`). - On Windows 10+, you could also call [IDWriteFontSet::GetMatchingFonts](https://learn.microsoft.com/en-us/windows/win32/api/dwrite_3/nf-dwrite_3-idwritefontset-getmatchingfonts(wcharconst_dwrite_font_weight_dwrite_font_stretch_dwrite_font_style_idwritefontset)) with [DWRITE_FONT_PROPERTY_ID_FULL_NAME](https://learn.microsoft.com/en-us/windows/win32/api/dwrite_3/ne-dwrite_3-dwrite_font_property_id) to *directly* match the full name "Suchandsuchfont Narrow Oblique Semilight" (followed by [IDWriteFontSet::GetFontFaceReference](https://learn.microsoft.com/en-us/windows/win32/api/dwrite_3/nf-dwrite_3-idwritefontset-getfontfacereference) 0 then [IDWriteFontFaceReference::CreateFontFace](https://learn.microsoft.com/en-us/windows/win32/api/dwrite_3/nf-dwrite_3-idwritefontfacereference-createfontface)). So either accessing them via the named enums or via axes Dustin gave above should work via DirectWrite.
Author
Owner

@Diablo-D3 commented on GitHub (Mar 9, 2024):

        "wdth": 10,  // width axis, change me as desired - NARROW

Have you tested this with Iosevka? Afaik Iosevka sets Extended to 7, but I've never gotten WT to do anything but normal width with any number (tried 0 through 20 manually). Unless a fix has been committed in the past few months that effects this, the behavior should still be the same.

It'd be nice if WT had this in the GUI and would just tell me what widths it thinks exist like how it happens in other apps.

@Diablo-D3 commented on GitHub (Mar 9, 2024): > ```js > "wdth": 10, // width axis, change me as desired - NARROW > ``` Have you tested this with Iosevka? Afaik Iosevka sets Extended to 7, but I've never gotten WT to do anything but normal width with any number (tried 0 through 20 manually). Unless a fix has been committed in the past few months that effects this, the behavior should still be the same. It'd be nice if WT had this in the GUI and would just tell me what widths it thinks exist like how it happens in other apps.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#13350