[Bug 60172] New: WPF font fallback does not work: the families named by GlobalUserInterface.CompositeFont do not resolve under Wine
http://bugs.winehq.org/show_bug.cgi?id=60172 Bug ID: 60172 Summary: WPF font fallback does not work: the families named by GlobalUserInterface.CompositeFont do not resolve under Wine Product: Wine Version: 11.15 Hardware: x86-64 OS: Linux Status: UNCONFIRMED Severity: normal Priority: P2 Component: gdi32 Assignee: wine-bugs@list.winehq.org Reporter: wehrwolfmann@gmail.com Target Milestone: --- Distribution: --- Created attachment 81815 --> http://bugs.winehq.org/attachment.cgi?id=81815 family resolution and glyph outlines for Latin and Cyrillic WPF (.NET Framework, PresentationCore) does not use DirectWrite layout or the dwrite fallback API -- it carries its own per-character fallback table, the GlobalUserInterface.CompositeFont file installed with the framework. That table is written entirely in terms of Windows system font families. Under Wine essentially none of those families exist, so the table has nowhere to go and WPF emits .notdef for the character instead. Measured on this prefix (Wine 11.15, WINEARCH=win64, reported Windows 10 build 19045, native .NET Framework 4.8, host CachyOS/Arch with the usual Noto, DejaVu and Liberation font packages installed), against the RS5 section of GlobalUserInterface.CompositeFont, which is the section .NET selects when Wine reports Windows 10: families named as fallback targets 44 resolvable through Wine 1 (Tahoma) not resolvable 43 (Segoe UI, Segoe UI Symbol, Ebrima, Nirmala UI, Leelawadee UI, Malgun Gothic, Gulim, Gadugi, Yu Gothic UI, Meiryo UI, Microsoft YaHei UI, Microsoft JhengHei UI, SimSun, MS Gothic, MingLiU, Calibri, Cambria Math, Myanmar Text, MV Boli, Mongolian Baiti, Simplified Arabic, ... ) The single map that matters most is the first one: <FontFamilyMap Unicode = "0000-052F, 0590-05FF, ..." Target = "Segoe UI, Segoe UI Symbol, Ebrima" /> U+0000-U+052F is Latin, Latin Extended, IPA, Greek and Cyrillic; the same map also carries Hebrew. All three targets are missing, so under Wine a WPF application has no fallback at all for those scripts. A plain WPF application does not notice, because its default typeface comes from SystemParametersInfo(SPI_GETNONCLIENTMETRICS) and Wine answers "Tahoma", which covers Latin, Greek and Cyrillic by itself. An application that ships its own branded font does notice, and branded fonts are usually Latin-only. WHAT THIS LOOKS LIKE IN A REAL APPLICATION Razer Central 7.23.0.1220 (the launcher bundled with Razer Axon) draws its tray icon context menu with WPF, in its own font "RazerF5" (SIL OFL, derived from Titillium, embedded in the executable and also shipped as a separate download). RazerF5 has 229 glyphs: Latin and punctuation, no Cyrillic. With the application's language set to Russian, every Latin menu entry renders and every Cyrillic menu entry is a row of empty boxes. On Windows the same build renders the Russian entries, because the fallback lands on Segoe UI. MINIMAL REPRODUCER wpf_fonts.cs (attached). It is a console program; build it with the csc.exe that comes with the framework already installed in the prefix, no SDK needed: wine "C:\windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe" /nologo \ /nowarn:0618 /target:exe /out:C:\wpf_fonts.exe \ "/r:C:\windows\Microsoft.NET\Framework64\v4.0.30319\WPF\PresentationCore.dll" \ "/r:C:\windows\Microsoft.NET\Framework64\v4.0.30319\WPF\PresentationFramework.dll" \ "/r:C:\windows\Microsoft.NET\Framework64\v4.0.30319\WPF\WindowsBase.dll" \ "/r:C:\windows\Microsoft.NET\assembly\GAC_MSIL\System.Xaml\v4.0_4.0.0.0__b77a5c561934e089\System.Xaml.dll" \ C:\wpf_fonts.cs For every family name given on the command line it prints whether the family resolves, whether the resolved face covers Latin and Cyrillic, and the bounding box of the outlines FormattedText.BuildGeometry() actually produced for the Latin string "Settings" and the Cyrillic string "Настройки". An empty bounding box means WPF produced no glyph outline at all -- the tofu case. It also writes wpf_fonts.png so the failure can be seen rather than read. Run it with any font that covers Latin but not Cyrillic; RazerF5 is the one used here (razerf5-light.otf, freely downloadable, SIL OFL): family "RazerF5" resolved=True latin=True cyrillic=False latin width= 55.26 height= 18.66 outline=53.7x14.8 cyrillic width= 33.84 height= 18.66 outline=EMPTY (nothing drawn) family "Tahoma" resolved=True latin=True cyrillic=True latin width= 56.50 height= 19.31 outline=55.9x15.0 cyrillic width= 77.58 height= 19.31 outline=75.6x15.0 family "Segoe UI" resolved=False Tahoma proves the text path itself is fine. RazerF5 shows that once the requested face lacks the character, nothing takes over. THE GDI CONTRAST gdi_cyrillic.c (attached) does the same two strings through plain GDI: CreateFontIndirectA, GetGlyphIndicesW(GGI_MARK_NONEXISTING_GLYPHS), TextOutW into a DIB, counting painted pixels. face "Segoe UI" latin missing_glyphs=0/8 painted_pixels=522 (GetTextFace Liberation Sans) cyrillic missing_glyphs=0/9 painted_pixels=637 (GetTextFace Liberation Sans) face "RazerF5" latin missing_glyphs=0/8 painted_pixels=489 (GetTextFace RazerF5) cyrillic missing_glyphs=9/9 painted_pixels=653 (GetTextFace RazerF5) So Wine already degrades gracefully when a Win32 application asks for a Windows font by name -- "Segoe UI" quietly becomes Liberation Sans and the Cyrillic renders. That graceful degradation happens at font *selection* time and is invisible to any consumer that first asks whether the family exists. WPF asks exactly that question, gets "no", and its whole fallback table dies with it. (GDI's own per-glyph behaviour for RazerF5 -- 9 unmapped glyphs, boxes painted -- matches Windows; GDI TextOutW does not font-link either. The difference is only in the family lookup.) A HARSHER MANIFESTATION If the *requested* family also fails to resolve, WPF does not fall back to a default -- the process dies silently inside text formatting. With the reproducer above: wine wpf_fonts.exe "Segoe UI" family "Segoe UI" resolved=False <process gone, no exception, no message> This looks like the same root cause as bug 50021 (PS Remote Play, assertion in MS.Internal.Shaping.TypefaceMap.MapUnresolvedCharacters, "installing corefonts changes the behaviour"): MapUnresolvedCharacters has nothing left to map to. Related but distinct: bug 59925 is also WPF fallback failing, but there the requested families do resolve and a single character (U+002D) is orphaned into a run of its own. Here the fallback targets do not exist as families at all, so the table has nothing to reach in the first place. SUGGESTED FIX Wine already has the mechanism, it just has no default entries for these names: HKCU\Software\Wine\Fonts\Replacements, honoured by both win32u/font.c (load_gdi_font_replacements) and dwrite/font.c. It enumerates an existing family under an additional name, and it steps aside automatically if a real font of that name is ever installed. REG_MULTI_SZ values are already supported and the first available entry wins, so one value works across distributions: [HKEY_CURRENT_USER\Software\Wine\Fonts\Replacements] "Segoe UI"=hex(7):... Noto Sans, DejaVu Sans, Liberation Sans, Arial, Tahoma "Segoe UI Symbol"=hex(7):... DejaVu Sans, Noto Sans, Liberation Sans, Arial, Tahoma "Ebrima"=hex(7):... Noto Sans, DejaVu Sans, Liberation Sans, Arial, Tahoma Adding just those three restores WPF fallback for Latin Extended, Greek, Cyrillic and Hebrew. Verified on this prefix with the reproducer, with GlobalUserInterface.CompositeFont left untouched: before family "RazerF5" cyrillic outline=EMPTY (nothing drawn) after family "RazerF5" cyrillic outline=80.8x16.0 (Noto Sans metrics) Tahoma as the last entry is a safe floor: Wine ships it, and it covers Latin, Greek and Cyrillic. The same treatment would extend naturally to the CJK and Indic target names for hosts that have suitable fonts. An independent cross-check that the composite font really is the mechanism: appending an existing family to every Target attribute in the prefix's *.CompositeFont files fixes it too, with no registry change at all. The registry route is preferable because it needs no edits to files that belong to the framework. VERSIONS Reproduced on wine-11.15 (Arch/CachyOS package), WINEARCH=win64, native .NET Framework 4.8, reported Windows version 10 (build 19045). Not tested on Wine-Mono; Wine-Mono's WPF carries its own copy of the same composite font files, so the same reasoning should apply. ATTACHMENTS wpf_fonts.cs WPF reproducer described above gdi_cyrillic.c the GDI counterpart used for the contrast wpf_fonts_before.png offscreen render, stock prefix: Cyrillic missing wpf_fonts_after.png the same render once "Segoe UI" resolves -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60172 Nikolay Sivov <bunglehead@gmail.com> changed: What |Removed |Added ---------------------------------------------------------------------------- Component|gdi32 |-unknown Severity|normal |trivial -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60172 --- Comment #1 from Wehrwolfmann <wehrwolfmann@gmail.com> --- Created attachment 81816 --> http://bugs.winehq.org/attachment.cgi?id=81816 GDI counterpart: the same two strings through CreateFontIndirect/TextOutW -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60172 --- Comment #2 from Wehrwolfmann <wehrwolfmann@gmail.com> --- Created attachment 81817 --> http://bugs.winehq.org/attachment.cgi?id=81817 Offscreen render on a stock prefix: Cyrillic missing -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60172 --- Comment #3 from Wehrwolfmann <wehrwolfmann@gmail.com> --- Created attachment 81818 --> http://bugs.winehq.org/attachment.cgi?id=81818 The same render once "Segoe UI" resolves -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60172 --- Comment #4 from Nikolay Sivov <bunglehead@gmail.com> --- Please next time try to describe the issue in your own words, instead of using generated wall of text, leaving it struggling to find an interested reader. GDI behavior does not have much to do with what WPF needs. Finding proper replacement for Segoe UI will be a challenge but we probably could use some default configuration for that, which is also not very hard to add manually when needed. -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60172 --- Comment #5 from Wehrwolfmann <wehrwolfmann@gmail.com> --- Fair point about the length, sorry -- I will keep it short from now on. The short version: WPF never asks Wine to draw with a missing font. It asks whether the family exists, gets "no" for all 43 fallback families its composite font names, and stops. So the character is dropped before any drawing happens, and only applications that ship their own Latin-only font notice. That is why I mentioned GDI at all: it shows the difference is in the family lookup, not in drawing. On your last point -- default configuration is exactly what I was suggesting. Three Fonts\Replacements entries (Segoe UI, Segoe UI Symbol, Ebrima, each a REG_MULTI_SZ list ending in Tahoma) restore fallback for Latin, Greek, Cyrillic and Hebrew. Would you take that as a patch? -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60172 --- Comment #6 from Wehrwolfmann <wehrwolfmann@gmail.com> --- I have a reproducer now that needs nothing but a prefix with dotnet48 in it. The attached bug60172.cs is twenty lines of code and builds with the csc that .NET Framework installs itself; the build line is in the file header. With no arguments it asks for Segoe UI and never reaches a window - it dies in MS.Internal.Shaping.TypefaceMap.MapUnresolvedCharacters through Environment.FailFast. Hand it any installed family without Cyrillic and you get the .notdef boxes from my first report instead. Both screenshots are attached. You were right that GDI fallback is not what WPF needs, but the replacement list does reach it. My prefix has none of the families GlobalUserInterface.CompositeFont names for U+0000-U+052F, so five entries under HKCU\Software\Wine\Fonts\Replacements cover it: Segoe UI, Segoe UI Symbol, Ebrima, Microsoft Sans Serif and Lucida Sans Unicode. As REG_MULTI_SZ they prefer Noto Sans and end at Tahoma, which Wine ships anyway. Patch for wine.inf attached. -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60172 --- Comment #7 from Wehrwolfmann <wehrwolfmann@gmail.com> --- Created attachment 81900 --> http://bugs.winehq.org/attachment.cgi?id=81900 bug60172.cs - twenty line WPF reproducer, build line in the header -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60172 --- Comment #8 from Wehrwolfmann <wehrwolfmann@gmail.com> --- Created attachment 81901 --> http://bugs.winehq.org/attachment.cgi?id=81901 proposed wine.inf font replacements -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60172 --- Comment #9 from Wehrwolfmann <wehrwolfmann@gmail.com> --- Created attachment 81902 --> http://bugs.winehq.org/attachment.cgi?id=81902 before: Cyrillic renders as .notdef boxes -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60172 --- Comment #10 from Wehrwolfmann <wehrwolfmann@gmail.com> --- Created attachment 81903 --> http://bugs.winehq.org/attachment.cgi?id=81903 after the five replacements: text renders -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60172 Wehrwolfmann <wehrwolfmann@gmail.com> changed: What |Removed |Added ---------------------------------------------------------------------------- Attachment #81902|wine-60172-до.png |wine-60172-before.png filename| | -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60172 --- Comment #11 from Wehrwolfmann <wehrwolfmann@gmail.com> --- Created attachment 81904 --> http://bugs.winehq.org/attachment.cgi?id=81904 Segoe UI case: window opens instead of FailFast -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60172 --- Comment #12 from Wehrwolfmann <wehrwolfmann@gmail.com> --- Created attachment 81905 --> http://bugs.winehq.org/attachment.cgi?id=81905 FailFast trace when Segoe UI is requested -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
participants (1)
-
WineHQ Bugzilla