[Win32] Let GDI lay out text again unless GDI+ layout is required - #3579
Merged
HeikoKlare merged 1 commit intoSep 19, 2026
Merged
HeikoKlare merged 1 commit into
HeikoKlare merged 1 commit into
Conversation
Contributor
HeikoKlare
force-pushed
the
gdi-text-layout-for-undecorated-fonts
branch
from
September 11, 2026 09:55
c78dab0 to
800af04
Compare
HeikoKlare
marked this pull request as ready for review
September 11, 2026 09:55
There was a problem hiding this comment.
🔵 Needs a closer look
The Win32 text-rendering changes warrant final human review.
Pull request overview
Restores GDI-based text layout for Win32 advanced GCs where possible, while retaining GDI+ handling for unsupported glyphs and decorated fonts.
Changes:
- Updates GDI/GDI+ layout selection and narrows the fallback property.
- Fixes tab-expanded text bounds handling.
- Adds digit-spacing and tab-rendering regression coverage.
File summaries
| File | Summary |
|---|---|
tests/org.eclipse.swt.tests.win32/ManualTests/org/eclipse/swt/tests/win32/snippets/SWTIssue3091_GDIPlusTextRendering.java |
Updates manual rendering comparisons, sample strings, and fallback property usage. |
bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java |
Adjusts layout selection and tab bounds handling. |
bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/GCWin32Tests.java |
Adds digit-spacing and tab-rendering regression tests. |
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
HeikoKlare
force-pushed
the
gdi-text-layout-for-undecorated-fonts
branch
4 times, most recently
from
September 18, 2026 07:09
536d142 to
4951edd
Compare
Since GDI+ text layout became the default for advanced GCs, glyph advances are derived from the font's unhinted design metrics instead of the hinted, grid-fitted advances the platform uses everywhere else, i.e. in native controls, in TextLayout and in a non-advanced GC. Per glyph the two differ by less than a pixel, and across proportional text the deviations carry mixed signs and largely cancel, which is why this went unnoticed. For digits they do not cancel. A font's figures are typically tabular, so all ten share one advance and hence one error that repeats with the same sign for every digit of a group. What is a sub-pixel quantity per glyph therefore becomes a substantial one per group: for Segoe UI at 9pt the digit advance is 6.469px unhinted against 6px hinted, so a group of 20 digits comes out 10px, almost two digit widths, wider than the platform places it. Across the test fonts and sizes the deviation reaches about 8% of the group's width in either direction; it is now within the rounding of the two extents. The accumulated width is only half of the symptom. Since the glyphs are still rasterized grid-fitted while the pen advances fractionally, the surplus is paid out in whole pixels at irregular intervals, so that the gaps within a group alternate, e.g. 7 6 7 6 for Segoe UI at 9pt where GDI spaces every digit 6 pixels apart. A run of identical figures makes that rhythm obvious, which is why this reads as broken tracking rather than as slightly wider text. This change therefore restores GDI as the text layout engine and limits GDI+'s own layout to the cases where the glyph run cannot be drawn at all: text containing characters GDI has no glyph for, and fonts with an underline or strikeout style. The latter is the actual cause behind eclipse-platform#3091 : Graphics_DrawDriverString does not support font decoration and draws blank space instead of the glyphs, which is a documented GDI+ limitation whose recommended remedy is to use Graphics_DrawString for those fonts. Both cases still draw with GDI+, so this only selects which engine performs the layout, and decorated text keeps rendering exactly as it does today. Narrow the escape hatch introduced along with the GDI+ layout default accordingly, and rename it to reflect its remaining scope: it now only reverts the decorated-font case to the GDI glyph run, at the price of that text rendering blank again. It remains an internal safety net that may be removed at any point in time. Fixes eclipse-platform#3577 Assisted-by: Claude Opus 5 <noreply@anthropic.com>
HeikoKlare
force-pushed
the
gdi-text-layout-for-undecorated-fonts
branch
from
September 19, 2026 11:14
4951edd to
1bd0b16
Compare
Contributor
Author
|
This is only a partial revert #3100, so we simply restore previous behavior, which makes the change safe. The bug is also visible in the Git history view when having commit messages or tags with sequences of multiple digits, where you then see the bad kerning (e.g., in the binaries version number and the I-Build versions): |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Fixes #3577
What is wrong
Since #3100 made GDI+ lay out text for advanced GCs, glyph advances come from the font's unhinted design metrics instead of the hinted, grid-fitted advances the platform uses everywhere else, that is in native controls, in
TextLayoutand in a non-advanced GC. Per glyph the two differ by less than a pixel, and across proportional text the deviations carry mixed signs and largely cancel, which is why it went unnoticed.For digits they do not cancel. A font's figures are typically tabular, so all ten share one advance and hence one error that repeats with the same sign for every digit of a group. For Segoe UI at 9pt the digit advance is 1104/2048 em = 6.469px unhinted against 6px hinted, so every digit is 7.8% too wide and a group of 20 digits lands 10px, almost two digit widths, off from where the platform places it. Sub-pixel per glyph is substantial per group, which answers the obvious objection that a fraction of a pixel cannot be visible.
Width is only half of the symptom. Because the glyphs are still rasterized grid-fitted while the pen advances fractionally, the surplus is paid out in whole pixels at irregular intervals: for Segoe UI at 9pt consecutive gaps alternate between 7 and 6 pixels where GDI spaces every digit 6 pixels apart. A run of identical figures makes that rhythm obvious, so it reads as broken tracking rather than as slightly wider text.
This is documented GDI+ behavior rather than a defect. Microsoft KB 307208, "Why text appears different when drawn with GDIPlus versus GDI", states that "text layout with GDIPlus is resolution-independent": a string occupies the same fraction of an em at every resolution, at the price of the layout no longer matching the grid-fitted glyphs that are actually rasterized. Where those fall short of the designed width, the article's reconciliation ends in inserting blank pixels between glyphs, which is precisely the alternating gaps above.
Measured as the width of a 20 digit string, an advanced GC deviates from a non-advanced one as follows:
How it is fixed
GDI becomes the text layout engine again, and GDI+ lays out text itself only where the glyph run cannot be drawn at all: for text containing characters GDI has no glyph for, where GDI would draw missing-glyph boxes, and for fonts with an underline or strikeout style. Both cases still draw with GDI+, so this only selects the layout engine, and the reason for using GDI+ in the first place, namely alpha blending, transforms and antialiasing over transparent backgrounds, is unaffected.
Letting GDI compute the advances and GDI+ draw the resulting glyph run is what
DrawDriverStringexists for, and it is the conclusion Microsoft drew for their own stack: Windows Forms moved off GDI+ text in .NET 2.0, whereApplication.SetCompatibleTextRenderingDefaultdefaults to the GDI basedTextRendererbecause "GDI calculates character spacing and word wrapping differently from GDI+". The alternative remedy KB 307208 suggests, the typographicStringFormatthat SWT already passes plusTextRenderingHintAntiAlias, does even out the gaps by drawing unhinted glyphs at fractional positions, but ten Segoe UI digits still measure 64.7px against GDI's 60: it removes the artifact without restoring the platform's metrics, and gives up ClearType sharpness.The decorated-font case is what actually caused #3091 in the first place:
Graphics::DrawDriverStringdoes not support font decoration and renders blank space instead of the glyphs, a known GDI+ limitation whose recommended remedy is to useDrawStringfor such fonts. Routing only those fonts to GDI+'s own layout keeps that fix intact and needs no decoration drawing of SWT's own; bold and italic are unaffected and keep using the glyph run.The escape hatch introduced along with the GDI+ layout default is narrowed to that remaining purpose and renamed to
org.eclipse.swt.internal.win32.useGDITextRenderingForDecoratedFonts. It now only reverts the decorated-font case to the GDI glyph run, at the price of that text rendering blank again, and remains an internal safety net that may be removed at any point in time.A new test in
GCWin32Testspins the digit advances of an advanced GC to those of a non-advanced one across the existing proportional and monospace test fonts and several sizes, within the same one pixel rounding tolerance the tab stop tests use. The manual testSWTIssue3091_GDIPlusTextRenderinggains digits in its sample strings so this class of regression is visible there, and follows the narrowed system property. Users should see no change other than text laid out as it was before #3100, since decorated fonts keep rendering exactly as they do today.Verification against the reported symptom
IDE tab headers containing numbers, before and after:
Rendering
"Hello World 12345"through an advanced GC, as in the screenshots of #3577:master, the reported bugmasterplus the workaround flag from the issue, the "old behavior" screenshotThis branch is identical to the workaround configuration, not only in total width but pixel by pixel, in all three rows.
🤖 Generated with Claude Code