[Bug 59925] New: WPF (.NET 8) renders U+002D (HYPHEN-MINUS) as .notdef ("tofu") for some fonts under Wine — public DirectWrite is correct; defect is in WPF's font-fallback path
http://bugs.winehq.org/show_bug.cgi?id=59925 Bug ID: 59925 Summary: WPF (.NET 8) renders U+002D (HYPHEN-MINUS) as .notdef ("tofu") for some fonts under Wine — public DirectWrite is correct; defect is in WPF's font-fallback path Product: Wine Version: 11.10 Hardware: x86-64 OS: MacOS Status: UNCONFIRMED Severity: normal Priority: P2 Component: dwrite Assignee: wine-bugs@list.winehq.org Reporter: robowens@me.com Under Wine, WPF (.NET 8) renders U+002D (HYPHEN-MINUS) as .notdef ("tofu") for some fonts (Segoe UI, Eurostile Next LT Pro) while rendering it correctly for others (Futura PT). Letters, digits and spaces always render. WPF itemizes the line and splits it at every hyphen, orphans the hyphen into its own run, and its font-fallback for that run fails to produce a glyph — the box is laid down without the hyphen ever being shaped. The public DirectWrite APIs are NOT broken for U+002D (see Negative controls below): IDWriteTextLayout, IDWriteFontFallback::MapCharacters, IDWriteFont::HasCharacter(0x2d) and IDWriteFontFace::GetGlyphIndices all resolve U+002D correctly for these exact fonts. The defect is in the font-fallback path WPF exercises under Wine (which the trace shows also drives gdi32 font-linking), not the public dwrite layout/shaping surface. Component may therefore belong to gdi32 rather than dwrite — triage's call. VERSIONS REPRODUCED Reproduced on BOTH vanilla wine-devel 11.10 and wine-staging 11.10 (Gcenx macOS builds, osx64 — same upstream release, differing only by the staging patchset). The +dwrite,+font trace signature is byte-identical on the two: line split at every hyphen, the hyphen orphaned into a run that never reaches GetGlyphs (zero L"-" runs), HasCharacter(0x2d) probed during fallback. So this is upstream Wine, not a staging artifact. PLATFORM macOS 26 / Darwin 25.5, x86-64 under Rosetta 2. .NET 8 WPF (Microsoft.WindowsDesktop.App 8.0.28), software rendering (HKCU\Software\Microsoft\Avalon.Graphics\DisableHWAcceleration=1). STEPS TO REPRODUCE 1. Build the attached WPF reproducer (Program.cs + repro.csproj) on any OS with a .NET SDK (EnableWindowsTargeting lets non-Windows hosts build net8.0-windows): dotnet publish -c Release -o out 2. Run under Wine: WINEPREFIX=/path/to/prefix \ DOTNET_EnableWriteXorExecute=0 LIBGL_ALWAYS_SOFTWARE=1 \ WINEDLLOVERRIDES="mshtml=d" \ wine out/HyphenRepro.exe (WPF needs software rendering under Wine — set HKCU\Software\Microsoft\Avalon.Graphics\DisableHWAcceleration=1 or the window paints black.) 3. The window draws "A-B-C NL-C4-1" in five font/style combinations. ACTUAL RESULT Each U+002D renders as the .notdef box for some fonts; letters/digits/spaces render fine. Font-dependent, not universal, not italic-specific (see attachment repro-output.png): [1] Segoe UI, normal -> hyphen tofu [2] Eurostile Next LT Pro, normal -> hyphen tofu [3] Eurostile Next LT Pro, italic (oblique-sim) -> hyphen tofu [4] Futura PT Book, italic -> renders correctly [5] Eurostile Next LT Pro (private collection) -> hyphen tofu That Segoe UI normal drops the hyphen while Futura renders it in the same window points at the font-fallback path, not the codepoint. EXPECTED RESULT The hyphen renders for every line (as on native Windows / native .NET). ON-SCREEN vs OFFSCREEN (RenderTargetBitmap) — backend isolation Run with --render (offscreen RenderTargetBitmap; no window, no winemac.drv surface) the reproducer drops ONLY the Segoe line; the Eurostile lines render their hyphen. Same machine, same Wine, same fonts, same text (see attachment repro-offscreen-render.png): Line / font On-screen window Offscreen RTB [1] Segoe normal tofu tofu [2] Eurostile normal tofu renders [3] Eurostile italic tofu renders [4] Futura italic renders renders [5] Eurostile (file) tofu renders This split is identical on vanilla wine-devel 11.10 and wine-staging 11.10. Two consequences: - The core defect is NOT in Wine's graphics/presentation backend. The Segoe U+002D tofu reproduces in the offscreen RTB path (WPF software MIL rasterizer to a bitmap, never touches winemac.drv) — so the hyphen-dropping lives in the platform-independent WPF text/font-fallback layer. - The extra Eurostile tofu is specific to the on-screen presentation path (GDI GetGlyphOutline route the trace shows), separate from the itemization defect that drops the hyphen for Segoe everywhere. ANALYSIS (WINEDEBUG=+dwrite,+font) - dwritetextanalyzer_GetGlyphs is called per run; WPF's itemization splits the line at every U+002D. For "[1] Segoe normal A-B-C NL-C4-1" the runs reaching GetGlyphs are L"...A", L"B", L"C NL", L"C4", L"1" — spaces stay inside a run, only hyphens break it. The split is identical on all five lines, including Futura (renders fine), so the split itself is not the bug. - The hyphen is orphaned into its own run that never reaches GetGlyphs — zero L"-" runs in the entire trace. The hyphen is never shaped; the box is laid down by WPF's fallback, not by dwrite glyph mapping. - dwritefont_HasCharacter(font, 0x2d, ...) is probed during fallback. The trace logs the probe inputs but not the boolean result, so it was tested directly (Negative controls): HasCharacter(0x2d) returns TRUE for these fonts. The failure is downstream of the coverage check, inside WPF's fallback bookkeeping under Wine. - The fonts contain the glyph: U+002D -> 'hyphen' is in every cmap subtable (verified with fontTools for Eurostile OTTO/CFF, Futura, Roboto) and at runtime by GetGlyphIndices (Eurostile->15, Futura->14, Tahoma->16). NEGATIVE CONTROLS (pure DirectWrite — why these localize the bug) Attachments repro_dwrite.cpp and repro_fallback.cpp drive the public DirectWrite APIs directly (no .NET/WPF/window) over the same "A-B-C NL-C4-1" and fonts (incl. the brand .otf loaded as a private/custom collection, as WPF loads them). All pass — none reproduces the bug: IDWriteTextLayout (system family) -> one unsplit run, shaped — OK IDWriteTextLayout (Eurostile/Futura from .otf) -> real glyph shaped — OK IDWriteFontFallback::MapCharacters -> maps '-' to Tahoma, has=yes — OK IDWriteFont::HasCharacter(0x2d) -> true — OK IDWriteFontFace::GetGlyphIndices(0x2d) -> real glyph id (15/14/16) — OK The public DirectWrite surface is correct for U+002D on these fonts. Combined with the WPF reproduction, the defect is in WPF/PresentationCore's font-fallback path under Wine (and the gdi32 font-linking it drives), not in dwrite's public layout/shaping APIs. CONFIRMED IN HOST APP (Q-SYS Designer 10.4.0) The same defect renders every hyphenated schematic component label (NL-C4-1, CX-Q..., Display-Amp) as boxes. A +dwrite,+font trace of the running Designer: - IDWriteFontFallback::MapCharacters calls = 0 — the Designer uses WPF/PresentationCore's OWN fallback, not the public dwrite fallback API. - ~45x dwritefont_HasCharacter(..., 0x2d, ...) iterating its candidate font list, plus ~29k GDI GetGlyphOutline calls; the orphaned hyphen never reaches GetGlyphs. - Brand fonts load from C:\windows\fonts (registered or not) — not a missing-/ private-font issue. Font-side workarounds do not fix the host app (tested against the live prefix): Wine's Replacements key is ignored by dwrite; a font file-swap has no effect; and registering the brand fonts fixes a plain WPF TextBlock but NOT the Designer (its schematic always routes the hyphen through the broken WPF-internal fallback regardless of registration). So this needs a Wine-side fix, not a font/config workaround. NOTES FOR TRIAGE - Public DirectWrite APIs verified correct for U+002D (Negative controls) — not a basic dwrite shaping/layout bug. Reproduction is WPF-only; defect is in the font-fallback path WPF drives under Wine (dwrite-internal and/or gdi32 font-linking). Component may need to move from dwrite to gdi32. - Upstream confirmed: reproduces on vanilla wine-devel 11.10, not just staging (identical trace signature). Not a staging-patch artifact. - Latest version tested = 11.10, the newest macOS binary available at filing (Gcenx wine-devel/staging packages); upstream 11.11 has no macOS package yet. The only font/glyph-adjacent change in wine-11.10..wine-11.11 (225 commits) is "win32u: Fixed incorrect glyph outline size" — likely unrelated to the WPF font-fallback itemization (the hyphen never reaches GetGlyphs), but a retest candidate. A retest on 11.11+ will follow once a macOS build ships. - Linux not yet tested: reporter is on Apple Silicon, where x86-64 Wine will not boot under Docker/qemu — Wine's anon_mmap_fixed page-alignment assertion fails against the host's 16 KB pages (qemu: uncaught target signal 6), a hard incompatibility, not a config issue. The reproducer is OS-portable (cross-builds via EnableWindowsTargeting, no macOS dependencies) and is expected to reproduce on Linux/X11. The offscreen RenderTargetBitmap reproduction already isolates the core case away from winemac.drv, which is the main thing a Linux run would show. - Per-font divergence has three tiers: Segoe tofus everywhere (on-screen and offscreen RTB); Eurostile tofus on-screen only; Futura never. All five lines itemize identically, so the divergence is in WPF's per-font fallback bookkeeping (and, for Eurostile, the on-screen glyph-rendering path), not the run split. ATTACHMENTS (added after filing) - Program.cs, repro.csproj — canonical WPF reproducer (no-arg = window; --render <out.png> = offscreen RTB). - repro-output.png — on-screen output (wine-staging 11.10, macOS). - repro-offscreen-render.png — offscreen --render output (vanilla wine-devel 11.10). - repro_dwrite.cpp, repro_fallback.cpp — pure-DirectWrite negative controls. -- 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=59925 --- Comment #1 from Robert Owens <robowens@me.com> --- Created attachment 81258 --> http://bugs.winehq.org/attachment.cgi?id=81258 Canonical WPF reproducer (no-arg = on-screen window; --render <out.png> = offscreen RenderTargetBitmap) -- 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=59925 --- Comment #2 from Robert Owens <robowens@me.com> --- Created attachment 81259 --> http://bugs.winehq.org/attachment.cgi?id=81259 Project file for the WPF reproducer (cross-builds on any OS via EnableWindowsTargeting) -- 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=59925 --- Comment #3 from Robert Owens <robowens@me.com> --- Created attachment 81260 --> http://bugs.winehq.org/attachment.cgi?id=81260 On-screen output, wine-staging 11.10 (macOS): Segoe + Eurostile hyphen tofu, Futura 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=59925 --- Comment #4 from Robert Owens <robowens@me.com> --- Created attachment 81261 --> http://bugs.winehq.org/attachment.cgi?id=81261 Offscreen --render output, vanilla wine-devel 11.10: only Segoe tofus (backend isolation) -- 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=59925 --- Comment #5 from Robert Owens <robowens@me.com> --- Created attachment 81262 --> http://bugs.winehq.org/attachment.cgi?id=81262 Pure-DirectWrite negative control (IDWriteTextLayout / GetGlyphIndices) — no .notdef -- 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=59925 --- Comment #6 from Robert Owens <robowens@me.com> --- Created attachment 81263 --> http://bugs.winehq.org/attachment.cgi?id=81263 Pure-DirectWrite negative control (IDWriteFontFallback::MapCharacters) — no .notdef -- 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=59925 Robert Owens <robowens@me.com> changed: What |Removed |Added ---------------------------------------------------------------------------- CC| |robowens@me.com -- 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=59925 Ken Sharp <imwellcushtymelike@gmail.com> changed: What |Removed |Added ---------------------------------------------------------------------------- Severity|normal |trivial Summary|WPF (.NET 8) renders U+002D |WPF (.NET 8) renders U+002D |(HYPHEN-MINUS) as .notdef |(HYPHEN-MINUS) as .notdef |("tofu") for some fonts |("tofu") for some fonts |under Wine — public |under Wine |DirectWrite is correct; | |defect is in WPF's | |font-fallback path | -- 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=59925 --- Comment #7 from Robert Owens <robowens@me.com> --- Follow-up (codepoint discriminator + source-level diagnosis): the defect is specific to U+002D and is NOT in Wine's dwrite analyzer. CODEPOINT DISCRIMINATOR Holding the text pattern constant and varying only the mark's codepoint (offscreen RenderTargetBitmap, wine-staging 11.10), only U+002D renders as .notdef. U+2010 HYPHEN, U+2011 NON-BREAKING HYPHEN, U+2013 EN DASH, U+2014 EM DASH, U+2212 MINUS SIGN, and ASCII '=' / '.' / middle-dot all render correctly -- in both Segoe UI and Eurostile. So the defect is font-independent and specific to the single codepoint U+002D, not a dash/line-break-class or generic-fallback issue. SOURCE + TRACE DIAGNOSIS (vanilla wine-11.10 source; +dwrite,+font trace of the offscreen --render run) 1. dlls/dwrite/analyzer.c get_char_sa() does NOT special-case U+002D. The only hyphen it marks no-visual is U+00AD (SOFT HYPHEN), plus the ZWSP/ZWNJ/ZWJ/LRM/RLM range 0x200b-0x200f. U+002D is classified as a normal visible Latin/Common character, which is correct. So this is not analogous to bug 55529 (a real analyzer misclassification). 2. The hyphen is never handed to dwrite shaping. Every line itemizes as the runs L"...A", L"B", L"C NL", L"C4", L"1" -- the hyphen is orphaned into a run that never reaches dwritetextanalyzer_GetGlyphs (zero L"-" runs in the whole trace). This split is byte-identical across all five lines, including the four whose hyphen renders fine. So the orphaning/split is not the bug. 3. The hyphen character never reaches the GDI/win32u glyph path either. dlls/win32u/font.c has no U+002D special-case, and the only freetype_get_glyph_outline calls whose glyph argument is 0x2d are a sequential glyph-INDEX metrics walk (...0024,0025,...,002c,002d,002e,002f..., format GGO_METRICS) of a cached GDI font being measured for text extent -- a red herring, not the hyphen character. There are zero glyph-outline calls for the actual hyphen glyph ids (Futura 14 / Eurostile 15 / Tahoma 16). 4. IDWriteFontFallback::MapCharacters is never called (count 0). WPF runs its own fallback: IDWriteFont::HasCharacter probed 37x, load_system_links 2586x. The per-character HasCharacter walk (covering every char incl. the four 0x2d) occurs for exactly one of the five lines -- the Futura line -- and not for the Segoe line (which tofus) nor the Eurostile lines (which render here). So even within WPF's own fallback the per-font code path varies, and HasCharacter-walk presence does not track the render-vs-tofu outcome. 5. The public DirectWrite surface is already verified correct for U+002D (see the negative-control attachments): IDWriteTextLayout, MapCharacters, HasCharacter(0x2d)=true, GetGlyphIndices(0x2d)=real glyph all pass for these fonts. Net: every Wine layer that IS asked about U+002D answers correctly. The drop happens inside WPF/PresentationCore's own run-itemization + per-font fallback bookkeeping running on Wine -- it orphans the hyphen identically for the lines that render and the line that tofus, then decides render-vs-drop in a path that +dwrite/+font does not expose (managed code + glyph-run rasterization). A maintainer familiar with the PresentationCore <-> Wine boundary will likely have to look at which Wine API WPF's internal fallback queries for the orphaned single-hyphen run, and why that diverges for Segoe. -- 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=59925 Nikolay Sivov <bunglehead@gmail.com> changed: What |Removed |Added ---------------------------------------------------------------------------- Component|dwrite |-unknown -- 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