Menu ▾ ▴

#2519 [Win32] GDI family names of a weight ("Fira Code Light") draw with a fallback font under DirectWrite; GDI fakes their bold

Bug
open
nobody
5
4 days ago
5 days ago
Pyre
No
Font lists built with EnumFontFamiliesEx give GDI family names (the fonts' Win32 family name, name ID 1). For families with weights other than regular and bold, or with other stretches, these names include the weight or stretch: "Fira Code Light", "Fira Code Medium", "Bahnschrift SemiBold SemiConden" (GDI truncates names to 31 characters). Applications store these names in their styles, so Scintilla receives them as font names.


1. DirectWrite only knows typographic family names ("Fira Code"). CreateTextFormat with "Fira Code Light" doesn't find the family and draws with a fallback font: the text changes font entirely when switching to a DirectWrite technology.
2. Under GDI, bold of such a family is synthesised (emboldened) instead of using the family's real heavier font. For monospaced fonts this widens every glyph by a pixel, so columns no longer line up with regular text.

The attached patch (against 5.6.7, win32 only):

- SurfaceD2D: a family name DirectWrite doesn't find is matched to its DirectWrite family, weight, stretch and style through the fonts' Win32 family names (including the 31-character truncation), else through IDWriteGdiInterop::CreateFontFromLOGFONT when that doesn't simulate. The requested weight is applied relative to the matched weight, as GDI does. Matches are cached per name. HFont() of such a font gives GDI its GDI name back.
- SurfaceGDI: bold (and other weights) of a GDI weight family uses the family's real font closest to the requested weight when there is one (GdiLogFont), else GDI's synthesis as before.
- ScintillaWin: the IME composition font uses the same mapping.

Names DirectWrite already knows ("Fira Code", "Consolas") take the unchanged path.

Test: a Scintilla window drawing "iiiii" and "WWWWW" in each family name, regular and bold, GDI and DirectWrite, with width and total ink (darkness) measured, Wine 9, 96 DPI:

  font               bold  tech         5.6.7              5.6.7 + patch
  Fira Code Light    no    DirectWrite  nothing drawn(*)   ink 884748   (GDI: 884589)
  Fira Code Light    yes   DirectWrite  nothing drawn(*)   ink 1564539  (GDI: 1565508)
  Fira Code Medium   no    DirectWrite  nothing drawn(*)   ink 1377513  (GDI: 1377529)
  Fira Code Light    yes   GDI          width 70 (faked)   width 65, ink 1565508 (a real heavier font)
  Fira Code Medium   yes   GDI          width 70 (faked)   width 65
  Fira Code          any   both         unchanged          unchanged

  (*) Wine draws nothing for an unknown family; Windows draws a fallback font.

Builds with g++ --std=c++17 -Wpedantic -Wall -Wextra without warnings; scripts/HeaderCheck.py is clean; Scintilla.dll links with MinGW-w64. The same code has been used in Notepad++ builds, tested with static and variable fonts and their named instances, and on Windows ("Bahnschrift Light" with DirectWrite draws its Light weight).

This patch was prepared with the help of an AI assistant (Claude), then reviewed and tested.

Windows check done: "Bahnschrift Light" with DirectWrite draws its Light weight (in the description).

1 Attachments

Discussion

  • Zufu Liu

    Zufu Liu - 5 days ago
    • labels: --> Scintilla, win32, font, GDI, directwrite
     
  • Pyre

    Pyre - 5 days ago

    Hi @zufuliu,

    Thanks for referencing those here.

    Having read them, I understand the conclusion: mapping GDI typeface names is a naming policy that belongs to the application, which can use IDWriteGdiInterop and pass the DirectWrite family with SCI_STYLESETWEIGHT and, since 5.5.2, SCI_STYLESETSTRETCH. I'll do it in the application, so please consider the patch withdrawn unless you find this worth keeping.

    One thing that may still help others: ScintillaDoc.html doesn't seem to mention the difference. Would a short note under SCI_STYLESETFONT be welcome? For example: "With SC_TECHNOLOGY_DEFAULT (GDI), fontName is a typeface name as listed by EnumFontFamiliesEx or returned by ChooseFont, such as "Fira Code Light". The DirectWrite technologies take a family name, such as "Fira Code", and select the font within the family with the weight, stretch and italic attributes. IDWriteGdiInterop::CreateFontFromLOGFONT can map a typeface name to these."
    The same section has a small typo: the SCI_STYLESETSTRETCH paragraph says "The weight is a number between 1 and 9".

     
    • Neil Hodgson

      Neil Hodgson - 4 days ago

      While a clarifying note may be helpful, mentioning EnumFontFamiliesEx anchors the explanation in legacy GDI terms and concepts.

      In general, DirectWrite should be treated as the default and GDI as a legacy API with some differences. Eventually GDI will be depreacted.

       

Log in to post a comment.