Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does DirectWrite (CreateTextFormat) pick the fallback font(s)?

The documentation for CreateTextFormat says nothing about font fallback selection, but if the default (NULL = system) collection is chosen, then DirectWrite clearly implements font fallback. For example, if I add two glyphs not found in the Gabriola font to the test string used by the DirectWrite SDK demo app, then DirectWrite picks the missing glyphs from the Segoe UI Symbol font. This happens with the basic DrawText call and also with a custom renderer (that does nothing custom for font fallback) as shown below (the only modification is the test string):

enter image description here

The checkmark and wite star come from Segoe UI Symbol, not from Gabriola, even though only Gabriola is specified in the demo app. So, does anyone know how the fallback font(s) is/are selected by DirectWrite (CreateTextFormat)?

Update. I see there is a GetSystemFontFallback that can list the fallback fonts, but it's only available in Windows 8.1 (because it's in IDWriteFactory2). I guess they've noticed the gap in the API with respect to enumerating the fallback fonts. So I'm guessing there's no way to do this before Windows 8.1, but if anyone knows a hack/workaround...

Update2. Quoting a MSFT employee:

DirectWrite has fallback data that is not read from the registry or in any way configurable. In Windows 8.1, though, APIs have been introduced that allow an app to specify its own fallback. (It's similar to the WPF APIs for creating a composite font definition.)

Which still doesn't explain exactly what the hardcoded algorithm/replacement scheme actually is.

like image 523
Fizz Avatar asked Oct 20 '22 02:10

Fizz


1 Answers

IDWriteTextLayout calls IDWriteFontFallback::MapCharacters to map each Unicode character to an ordered list of font families that are tried until that character is satisfied. Think of a loop reading each character one-by-one, mapping the code point value and language tag to a Unicode range, and stopping at the first font which supports the character in the cmap table. Pseudocode:

for each ch in text
    if ch is a combining mark or other similar extending character
        if the previously selected font supports the combining mark too
            use the previously selected font
            continue
        endif
    endif
    find first mapping for ch within the Unicode range, which matches
        the current language and base font family too (if pertinent)
    if mapping found
        for each font in mapping (starting with first listed)
            if ch in font cmap
                use current font
            endif
        endfor
    else
        use base font and undefined (.notdef) glyph
    endif
endfor

There is also logic to determine fallback locales (like for zh-Hans/Hant or the general ja matching the more specific ja-jp). It is somewhat similar to (but less convoluted than 😅) the font fallback algorithm specified by CSS for browsers [http://www.w3.org/TR/css3-fonts/#font-matching-algorithm] and similar to the fallback used by WPF/XAML/Silverlight.

See the IDWriteFontFallbackBuilder::AddMapping API (Win 8.1+), used to build a custom fallback list, for an idea of the inputs used.

See C:\Windows\Fonts\GlobalUserInterface.CompositeFont for example data (note this file is actually for WPF, not quite the same definition which DWrite uses).

<FontFamilyMap
    Unicode  = "3000-30FF, 31F0-31FF"
    Language = "ja"
    Target   = "Meiryo UI, Meiryo, Microsoft YaHei UI, Microsoft YaHei, MS Gothic, MingLiu, Arial Unicode MS"
    Scale    = "1.0" />
like image 105
Dwayne Robinson Avatar answered Oct 24 '22 02:10

Dwayne Robinson