Repository navigation
Description
Splitting this out of #194931 as requested by @gaaclarke in #194931 (comment). The framework-side default-locale fix is tracked by PR #195997; this issue is the second item: an opt-in rendering-mode switch for small CJK text on Windows.
Problem
At 96 DPI / 100% scaling (still ~52% of desktops per the Steam Hardware Survey, Jan 2026), 14px CJK text rendered by Flutter/Impeller reads visibly softer than GDI/DirectWrite output in the same apps. We ship an enterprise IM client where users read dense 14px CJK for hours; our design team cannot sign off on text weights at this size, and every side-by-side against native Windows apps reads as lower quality.
What we measured (grayscale on both sides, so chroma is out of the picture)
Same face (Microsoft YaHei), same 14px, ink-pixel statistics over one full line — from my measurements in #194931:
| advance (px) | ink mass | inked px | solid (L<64) | fringe (L≥64) | |
|---|---|---|---|---|---|
GDI / DirectWrite, ANTIALIASED_QUALITY |
196 | 743 | 1020 | 59.1% | 40.9% |
| GDI / DirectWrite, ClearType converted to gray (luma) | 196 | 858 | 1310 | 43.7% | 56.3% |
| Flutter 3.47 / Impeller, YaHei | 200 | 811 | 1276 | 41.4% | 58.6% |
Flutter's grayscale output has almost exactly the coverage profile of ClearType converted to gray (41.4% vs 43.7% solid) and is far from GDI's real grayscale path (59.1%). A 1px CJK stem becomes one near-solid column under GDI's grid-fitting; Flutter spreads the same stem over two gray columns.
Root cause per @Rusino (#194931 (comment))
- This is not missing hinting: Impeller already requests
SkFontHinting::kSlight, and Skia's DirectWrite scaler keeps grid fitting on for every level exceptkNone. - The difference from GDI comes from:
- the rendering mode Skia picks —
NATURAL/NATURAL_SYMMETRICfor fonts with a gasp table, while GDI uses classic mode; and setSubpixel(true)in the Impeller glyph atlas, which places glyphs at fractional x positions and spreads 1px stems over two columns.
- the rendering mode Skia picks —
- Rusino's suggested first experiment: "An opt-in Windows switch that turns off subpixel positioning for small CJK text (and possibly asks for a GDI-classic rendering mode)."
The ask
An opt-in switch on the Windows embedder / engine, grayscale only, default off, e.g.:
- disable subpixel (fractional-x) glyph positioning for small text, and/or
- request a GDI-classic rendering mode from the DirectWrite scaler.
Zero change to defaults, so no risk to existing apps. Even vertical stem snapping alone would close most of the remaining gap for low-DPI CJK.
Scope notes (deliberately out)
- No chroma / subpixel AA — declined in [Windows][Impeller 3.47 SDF] CJK text rendering shows no improvement over Skia at 1080p — real-world enterprise IM case and request for DirectWrite rendering path #194931 (gaaclarke, 2026-11-03); we accept that call.
- No Skia gamma defines — Impeller already does its own gamma correction; forcing
SK_GAMMA_EXPONENT/SK_GAMMA_CONTRASTwould double-correct (measured in #194931, section 4).
Environment
Windows 10 22H2 (10.0.19045), Intel HD Graphics 630, 1920x1080 @ 100% (FontSmoothing=2, ClearType on), Flutter 3.47.1 stable, engine 11d79658c444, Impeller OpenGLESSDF.
Happy to supply captures, the pixel-diff harness (~150-line Flutter app + ~60-line diff script), and validation on any branch.
Activity
puneetkukreja98 commentedon Nov 5, 2026
Routing this to team-engine for further investigation.
As previously discussed on that #194931 (comment).
SuperAiPm commentedon Nov 10, 2026
Friendly ping @gaaclarke — following up on the issue you asked me to file
(#194931 (comment)).
It was routed to team-engine on Nov 5 but hasn't been picked up since.
Where does this stand relative to #195997? If an opt-in hinted-text switch
is something team-engine wants to evaluate, I'm glad to help with
measurements or local testing — my measurements so far are in the OP.
SuperAiPm commentedon Nov 13, 2026
Hi @gaaclarke — now that the locale-fallback fix (#195997, ec93269) has
landed, the font-selection half of this is resolved on master. Thanks for
that. The remaining ask here is the separate hinting/positioning switch,
which you asked me to file as its own issue. Is the next step a design doc
from your side, or would you prefer I prototype it behind a flag first?
Happy to do either — just want to avoid sitting here idle.
gaaclarke commentedon Nov 13, 2026
hey @SuperAiPm this issue doesn't live in a vacuum it's prioritized against all the other work we have to do. Switches while easy to implement often have a higher maintenance cost. If you want to post a PR we can take a look but know that just because it works technically there are other variables to consider when approving it or not.