[PATCH v3 0/3] MR10672: win32u: Initialize Uniscribe fallback registry keys on startup.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=27641 @aricstewart I thought about the trade-offs between the two approaches (this and https://gitlab.winehq.org/wine/wine/-/merge_requests/10630) and finally decided to go through with this, because this would allow for a more better UX. How does this look? If it looks better, we can close the previous MR. Also I will open a separate MR for the script tag, which will hopefully have a better solution than just using one script tag for a script. -- v3: gdi32/uniscribe: Update font fallback mechanism. win32u: Initialize Uniscribe fallback registry keys on startup. win32u: Implement fontconfig_get_default_font_for_char(). https://gitlab.winehq.org/wine/wine/-/merge_requests/10672
From: समीर सिंह Sameer Singh <lumarzeli30@gmail.com> This would allow Uniscribe to dynamically populate script based fallback mappings in the registry based on the system's available fonts. --- dlls/win32u/freetype.c | 45 +++++++++++++++++++++++++++++++++++++ dlls/win32u/ntgdi_private.h | 1 + 2 files changed, 46 insertions(+) diff --git a/dlls/win32u/freetype.c b/dlls/win32u/freetype.c index a660b166a0f..b5238ab337c 100644 --- a/dlls/win32u/freetype.c +++ b/dlls/win32u/freetype.c @@ -131,6 +131,10 @@ MAKE_FUNCPTR(FcDefaultSubstitute); MAKE_FUNCPTR(FcFontList); MAKE_FUNCPTR(FcFontMatch); MAKE_FUNCPTR(FcFontSetDestroy); +MAKE_FUNCPTR(FcCharSetAddChar); +MAKE_FUNCPTR(FcCharSetCreate); +MAKE_FUNCPTR(FcCharSetDestroy); +MAKE_FUNCPTR(FcPatternAddCharSet); MAKE_FUNCPTR(FcInit); MAKE_FUNCPTR(FcPatternAddString); MAKE_FUNCPTR(FcPatternCreate); @@ -1249,6 +1253,10 @@ static void init_fontconfig(void) LOAD_FUNCPTR(FcFontList); LOAD_FUNCPTR(FcFontMatch); LOAD_FUNCPTR(FcFontSetDestroy); + LOAD_FUNCPTR(FcCharSetAddChar); + LOAD_FUNCPTR(FcCharSetCreate); + LOAD_FUNCPTR(FcCharSetDestroy); + LOAD_FUNCPTR(FcPatternAddCharSet); LOAD_FUNCPTR(FcInit); LOAD_FUNCPTR(FcPatternAddString); LOAD_FUNCPTR(FcPatternCreate); @@ -2027,6 +2035,42 @@ static BOOL fontconfig_enum_family_fallbacks( UINT pitch_and_family, int index, return FALSE; } +/************************************************************* + * fontconfig_get_default_font_for_char + */ +static void fontconfig_get_default_font_for_char( DWORD ch, WCHAR *font_name ) +{ +#ifdef SONAME_LIBFONTCONFIG + FcPattern *pattern, *match; + FcResult result; + const char *name = NULL; + FcCharSet *charset; + DWORD len; + + *font_name = 0; + + pattern = pFcPatternCreate(); + charset = pFcCharSetCreate(); + pFcCharSetAddChar( charset, ch ); + pFcPatternAddCharSet( pattern, FC_CHARSET, charset ); + pFcCharSetDestroy( charset ); + + pFcConfigSubstitute( NULL, pattern, FcMatchPattern ); + pFcDefaultSubstitute( pattern ); + match = pFcFontMatch( NULL, pattern, &result ); + if (match) + { + if (pFcPatternGetString( match, FC_FAMILY, 0, (FcChar8 **)&name ) == FcResultMatch && name) + { + RtlUTF8ToUnicodeN( font_name, (LF_FACESIZE - 1) * sizeof(WCHAR), &len, name, strlen(name) ); + font_name[len / sizeof(WCHAR)] = 0; + } + pFcPatternDestroy( match ); + } + pFcPatternDestroy( pattern ); +#endif +} + static DWORD get_ttc_offset( FT_Face ft_face, UINT face_index ) { FT_ULong len; @@ -3873,6 +3917,7 @@ static UINT freetype_get_kerning_pairs( struct gdi_font *font, KERNINGPAIR **pai static const struct font_backend_funcs font_funcs = { freetype_load_fonts, + fontconfig_get_default_font_for_char, fontconfig_enum_family_fallbacks, freetype_add_font, freetype_add_mem_font, diff --git a/dlls/win32u/ntgdi_private.h b/dlls/win32u/ntgdi_private.h index fcfbce58fea..ac77d2aa6ed 100644 --- a/dlls/win32u/ntgdi_private.h +++ b/dlls/win32u/ntgdi_private.h @@ -320,6 +320,7 @@ struct gdi_font struct font_backend_funcs { void (*load_fonts)(void); + void (*get_default_font_for_char)( DWORD ch, WCHAR *font_name ); BOOL (*enum_family_fallbacks)( UINT pitch_and_family, int index, WCHAR buffer[LF_FACESIZE] ); INT (*add_font)( const WCHAR *file, UINT flags ); INT (*add_mem_font)( void *ptr, SIZE_T size, UINT flags ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10672
From: समीर सिंह Sameer Singh <lumarzeli30@gmail.com> Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=27641 --- dlls/win32u/font.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/dlls/win32u/font.c b/dlls/win32u/font.c index dcc75e8096b..7dcc7da52b9 100644 --- a/dlls/win32u/font.c +++ b/dlls/win32u/font.c @@ -6700,6 +6700,83 @@ static void load_registry_fonts(void) NtClose( hkey ); } +static void init_uniscribe_fallbacks(void) +{ + HKEY hkey; + DWORD disp; + WCHAR font_name[LF_FACESIZE]; + int i; + + /* Table kept in sync with scriptInformation[] in dlls/gdi32/uniscribe/usp10.c */ + static const struct + { + DWORD ch; + DWORD tag; + } + fallbacks[] = + { + {0x0531, MS_MAKE_TAG('a', 'r', 'm', 'n')}, + {0x05d0, MS_MAKE_TAG('h', 'e', 'b', 'r')}, + {0x0627, MS_MAKE_TAG('a', 'r', 'a', 'b')}, + {0x0710, MS_MAKE_TAG('s', 'y', 'r', 'c')}, + {0x0780, MS_MAKE_TAG('t', 'h', 'a', 'a')}, + {0x07c0, MS_MAKE_TAG('n', 'k', 'o', ' ')}, + {0x0905, MS_MAKE_TAG('d', 'e', 'v', 'a')}, + {0x0985, MS_MAKE_TAG('b', 'e', 'n', 'g')}, + {0x0a05, MS_MAKE_TAG('g', 'u', 'r', 'u')}, + {0x0a85, MS_MAKE_TAG('g', 'u', 'j', 'r')}, + {0x0b05, MS_MAKE_TAG('o', 'r', 'y', 'a')}, + {0x0b85, MS_MAKE_TAG('t', 'a', 'm', 'l')}, + {0x0c05, MS_MAKE_TAG('t', 'e', 'l', 'u')}, + {0x0c85, MS_MAKE_TAG('k', 'n', 'd', 'a')}, + {0x0d05, MS_MAKE_TAG('m', 'l', 'y', 'm')}, + {0x0d85, MS_MAKE_TAG('s', 'i', 'n', 'h')}, + {0x0e01, MS_MAKE_TAG('t', 'h', 'a', 'i')}, + {0x0e81, MS_MAKE_TAG('l', 'a', 'o', ' ')}, + {0x0f40, MS_MAKE_TAG('t', 'i', 'b', 't')}, + {0x10a0, MS_MAKE_TAG('g', 'e', 'o', 'r')}, + {0x1000, MS_MAKE_TAG('m', 'y', 'm', 'r')}, + {0x1200, MS_MAKE_TAG('e', 't', 'h', 'i')}, + {0x13a0, MS_MAKE_TAG('c', 'h', 'e', 'r')}, + {0x1401, MS_MAKE_TAG('c', 'a', 'n', 's')}, + {0x1681, MS_MAKE_TAG('o', 'g', 'a', 'm')}, + {0x16a0, MS_MAKE_TAG('r', 'u', 'n', 'r')}, + {0x1780, MS_MAKE_TAG('k', 'h', 'm', 'r')}, + {0x1820, MS_MAKE_TAG('m', 'o', 'n', 'g')}, + {0x1950, MS_MAKE_TAG('t', 'a', 'l', 'o')}, + {0x1980, MS_MAKE_TAG('t', 'a', 'l', 'u')}, + {0x2801, MS_MAKE_TAG('b', 'r', 'a', 'i')}, + {0x2d30, MS_MAKE_TAG('t', 'f', 'n', 'g')}, + {0x3041, MS_MAKE_TAG('k', 'a', 'n', 'a')}, + {0x3105, MS_MAKE_TAG('b', 'o', 'p', 'o')}, + {0x4e00, MS_MAKE_TAG('h', 'a', 'n', 'i')}, + {0xa840, MS_MAKE_TAG('p', 'h', 'a', 'g')}, + {0xac00, MS_MAKE_TAG('h', 'a', 'n', 'g')}, + {0xa000, MS_MAKE_TAG('y', 'i', ' ', ' ')}, + {0xa500, MS_MAKE_TAG('v', 'a', 'i', ' ')}, + {0x10400, MS_MAKE_TAG('d', 's', 'r', 't')}, + {0x10480, MS_MAKE_TAG('o', 's', 'm', 'a')}, + {0x1d400, MS_MAKE_TAG('m', 'a', 't', 'h')}, + }; + + hkey = reg_create_ascii_key(hkcu_key, "Software\\Wine\\Uniscribe\\SystemFallback", 0, &disp); + if (!hkey) return; + + for (i = 0; i < ARRAY_SIZE(fallbacks); i++) + { + font_funcs->get_default_font_for_char(fallbacks[i].ch, font_name); + if (font_name[0]) + { + WCHAR nameW[9]; + char tag_str[9]; + sprintf(tag_str, "%x", fallbacks[i].tag); + ascii_to_unicode(nameW, tag_str, strlen(tag_str) + 1); + set_reg_value(hkey, nameW, REG_SZ, font_name, (lstrlenW(font_name) + 1) * sizeof(WCHAR)); + } + } + NtClose(hkey); +} + static HKEY open_hkcu(void) { char buffer[256]; @@ -6781,6 +6858,7 @@ UINT font_init(void) reorder_font_list(); load_gdi_font_subst(); load_gdi_font_replacements(); + init_uniscribe_fallbacks(); load_system_links(); dump_gdi_font_list(); dump_gdi_font_subst(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10672
From: समीर सिंह Sameer Singh <lumarzeli30@gmail.com> The previous fallback only consulted the Uniscribe\Fallback registry key and a hardcoded Windows compatible font name. If the hardcoded font was not installed, rendering would fail entirely. The fallback order is now: 1. Uniscribe\Fallback registry key. 2. Hardcoded Windows compatible font name, if installed. 3. Uniscribe\SystemFallback registry key. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=27641 --- dlls/gdi32/uniscribe/usp10.c | 48 ++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/dlls/gdi32/uniscribe/usp10.c b/dlls/gdi32/uniscribe/usp10.c index 43afbaac125..c42327205fb 100644 --- a/dlls/gdi32/uniscribe/usp10.c +++ b/dlls/gdi32/uniscribe/usp10.c @@ -1869,7 +1869,22 @@ static BOOL requires_fallback(HDC hdc, SCRIPT_CACHE *psc, SCRIPT_ANALYSIS *psa, return FALSE; } -static void find_fallback_font(enum usp10_script scriptid, WCHAR *FaceName) +static INT CALLBACK is_font_installed_proc(const LOGFONTW *elf, const TEXTMETRICW *ntm, DWORD type, LPARAM lParam) +{ + return 0; +} + +static BOOL is_font_installed(HDC hdc, const WCHAR *name) +{ + BOOL ret = FALSE; + + if(!EnumFontFamiliesW(hdc, name, is_font_installed_proc, 0)) + ret = TRUE; + + return ret; +} + +static void find_fallback_font(HDC hdc, enum usp10_script scriptid, WCHAR *FaceName) { HKEY hkey; @@ -1880,12 +1895,35 @@ static void find_fallback_font(enum usp10_script scriptid, WCHAR *FaceName) DWORD type; swprintf(value, ARRAY_SIZE(value), L"%x", scriptInformation[scriptid].scriptTag); - if (RegQueryValueExW(hkey, value, 0, &type, (BYTE *)FaceName, &count)) - lstrcpyW(FaceName,scriptInformation[scriptid].fallbackFont); + if (!RegQueryValueExW(hkey, value, 0, &type, (BYTE *)FaceName, &count)) + { + RegCloseKey(hkey); + return; + } RegCloseKey(hkey); } - else + + if (scriptInformation[scriptid].fallbackFont[0] && + is_font_installed(hdc, scriptInformation[scriptid].fallbackFont)) + { lstrcpyW(FaceName,scriptInformation[scriptid].fallbackFont); + return; + } + + if (!RegOpenKeyA(HKEY_CURRENT_USER, "Software\\Wine\\Uniscribe\\SystemFallback", &hkey)) + { + WCHAR value[10]; + DWORD count = LF_FACESIZE * sizeof(WCHAR); + DWORD type; + + swprintf(value, ARRAY_SIZE(value), L"%x", scriptInformation[scriptid].scriptTag); + if (!RegQueryValueExW(hkey, value, 0, &type, (BYTE *)FaceName, &count)) + { + RegCloseKey(hkey); + return; + } + RegCloseKey(hkey); + } } /*********************************************************************** @@ -2025,7 +2063,7 @@ HRESULT WINAPI ScriptStringAnalyse(HDC hdc, const void *pString, int cString, GetObjectW(GetCurrentObject(hdc, OBJ_FONT), sizeof(lf), & lf); lf.lfCharSet = scriptInformation[analysis->pItem[i].a.eScript].props.bCharSet; lf.lfFaceName[0] = 0; - find_fallback_font(analysis->pItem[i].a.eScript, lf.lfFaceName); + find_fallback_font(hdc, analysis->pItem[i].a.eScript, lf.lfFaceName); if (lf.lfFaceName[0]) { analysis->glyphs[i].fallbackFont = CreateFontIndirectW(&lf); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10672
Aric Stewart (@aricstewart) commented about dlls/gdi32/uniscribe/usp10.c:
return FALSE; }
-static void find_fallback_font(enum usp10_script scriptid, WCHAR *FaceName) +static INT CALLBACK is_font_installed_proc(const LOGFONTW *elf, const TEXTMETRICW *ntm, DWORD type, LPARAM lParam) +{ + return 0; +} + +static BOOL is_font_installed(HDC hdc, const WCHAR *name) +{ + BOOL ret = FALSE; + + if(!EnumFontFamiliesW(hdc, name, is_font_installed_proc, 0))
`EnumFontFamiliesW` is deprecated and really you should be calling `EnumFontFamiliesEx`. I am also thinking there is a flaw in this logic. `EnumFontFamilies` returns the last value returned from the callback or 0 if nothing matches. Your call back returns 0, which while it stops enumeration it also means that return value from `EnumFontFamilies` is identical on success and failure. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10672#note_137974
Aric Stewart (@aricstewart) commented about dlls/gdi32/uniscribe/usp10.c:
DWORD type;
swprintf(value, ARRAY_SIZE(value), L"%x", scriptInformation[scriptid].scriptTag); - if (RegQueryValueExW(hkey, value, 0, &type, (BYTE *)FaceName, &count)) - lstrcpyW(FaceName,scriptInformation[scriptid].fallbackFont); + if (!RegQueryValueExW(hkey, value, 0, &type, (BYTE *)FaceName, &count)) + { + RegCloseKey(hkey); + return;
This `return` would mean if there is not a fallback defined in `Software\\Wine\\Uniscribe\\Fallback` then you cannot have one in `Software\\Wine\\Uniscribe\\SystemFallback` It may be nicer to fall through to the below code so that if a fallback font is not defined we can still use your logic to search for a system one. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10672#note_137976
On Tue Apr 28 12:24:57 2026 +0000, Aric Stewart wrote:
This `return` would mean if there is not a fallback defined in `Software\\Wine\\Uniscribe\\Fallback` then you cannot have one in `Software\\Wine\\Uniscribe\\SystemFallback` It may be nicer to fall through to the below code so that if a fallback font is not defined we can still use your logic to search for a system one. unless I am reading wrong, the code does fall through, if a fallback font is not defined in the Fallback regkey, right? Or do you mean to say the order should be Fallback regkey -\> SystemFallback regkey -\> hardcoded fallback?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10672#note_137990
On Tue Apr 28 13:29:48 2026 +0000, समीरसिंह Sameer Singh wrote:
unless I am reading wrong, the code does fall through, if a fallback font is not defined in the Fallback regkey, right? Or do you mean to say the order should be Fallback regkey -\> SystemFallback regkey -\> hardcoded fallback? My bad, I misread the code and se you have a `!` in the logic now. All good.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10672#note_137992
participants (3)
-
Aric Stewart (@aricstewart) -
समीर सिंह Sameer Singh -
समीरसिंह Sameer Singh (@ss141309)