From 549187eb4da7ec4309ca4d015cb79fb43035b3b7 Mon Sep 17 00:00:00 2001 From: Heiko Klare Date: Tue, 2 Jun 2026 09:50:51 +0200 Subject: [PATCH] [Win32] Fix underline/strikeout styles lost for fallback fonts in GC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In GC.createGdipFont(), when the requested font family is not found in the GDI+ font collection, a fallback font is constructed by reading the LOGFONT from the original GDI HFONT and building a new GDI+ Font from it. The style flags passed to the new Font() constructor were only derived from the Bold and Italic LOGFONT fields; lfUnderline and lfStrikeOut were silently ignored. As a result, text drawn with a fallback font that had underline or strikeout requested would render without those decorations. Fix by also mapping lfUnderline → FontStyleUnderline and lfStrikeOut → FontStyleStrikeout in the fallback-font style computation. Regression test: GCWin32Tests.fallbackFontPreservesUnderlineAndStrikeout The test is in GCWin32Tests because it relies on Win32-specific font internals: FontData.data (a LOGFONT) exposes lfUnderline and lfStrikeOut as direct fields, which have no equivalent in the GTK or Cocoa font models (those platforms apply underline/strikeout as text-level attributes via TextLayout/TextStyle, not as font-level attributes). PlatformSpecificExecutionExtension ensures the class is skipped automatically on non-Win32 platforms. See https://github.com/eclipse-platform/eclipse.platform.swt/issues/2978 Co-Authored-By: Claude Sonnet 4.6 --- .../eclipse/swt/graphics/GCWin32Tests.java | 82 ++++++++++++++++++- .../win32/org/eclipse/swt/graphics/GC.java | 2 + 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/GCWin32Tests.java b/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/GCWin32Tests.java index 70ce62fc183..6b4cb998c86 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/GCWin32Tests.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/GCWin32Tests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Yatta Solutions + * Copyright (c) 2024, 2026 Yatta Solutions * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -14,6 +14,8 @@ package org.eclipse.swt.graphics; import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.concurrent.*; @@ -62,4 +64,82 @@ public void drawnElementsShouldScaleUpToTheRightZoomLevel() { gc.getGCData().lineWidth = 10; assertEquals("Drawn elements should scale to the right value", gc.getGCData().lineWidth, gc.getLineWidth() * scalingFactor, 0); } + + /** + * Verifies that underline and strikeout styles requested via a font's + * {@link FontData} are preserved when GDI+ cannot find the font family and + * constructs a substitute font from the {@code LOGFONT} fields instead. + *

+ * "Courier" (without "New") is used because it is a legacy GDI font whose + * family is not available in GDI+, triggering the fallback. The fallback + * remaps it to "Courier New", which is available to both GDI and GDI+, + * ensuring that glyph rendering remains consistent between the two. + *

+ * The test string includes U+FFFE, a Unicode non-character with no glyph in + * any standard font. Its absence forces SWT to use the GDI+ rendering path + * that honours the font's underline and strikeout decoration; the path used + * for plain ASCII text does not render those decorations. Advanced (GDI+) + * mode must be enabled on the GC so the fallback font's style flags are + * applied at all. + * + * @see Issue 2978 + */ + @Test + public void fallbackFontPreservesUnderlineAndStrikeout() { + Display display = Display.getDefault(); + Font normalFont = new Font(display, "Courier", 24, SWT.NORMAL); + FontData underlineFD = new FontData("Courier", 24, SWT.NORMAL); + underlineFD.data.lfUnderline = 1; + Font underlineFont = new Font(display, underlineFD); + FontData strikeoutFD = new FontData("Courier", 24, SWT.NORMAL); + strikeoutFD.data.lfStrikeOut = 1; + Font strikeoutFont = new Font(display, strikeoutFD); + Image testImage = new Image(display, 400, 100); + try { + // U+FFFE has no glyph in any standard font; forces the rendering path + // that honours font decoration flags such as underline and strikeout. + String testString = "Hello" + (char) 0xFFFE; + int normalPixelCount = renderTextAndCountNonWhitePixels(testImage, normalFont, testString); + int underlinePixelCount = renderTextAndCountNonWhitePixels(testImage, underlineFont, testString); + int strikeoutPixelCount = renderTextAndCountNonWhitePixels(testImage, strikeoutFont, testString); + assertAll( + () -> assertTrue(underlinePixelCount > normalPixelCount, + "Underline font via fallback path should produce more pixels than normal font. " + + "Normal: " + normalPixelCount + ", Underline: " + underlinePixelCount), + () -> assertTrue(strikeoutPixelCount > normalPixelCount, + "Strikeout font via fallback path should produce more pixels than normal font. " + + "Normal: " + normalPixelCount + ", Strikeout: " + strikeoutPixelCount) + ); + } finally { + normalFont.dispose(); + underlineFont.dispose(); + strikeoutFont.dispose(); + testImage.dispose(); + } + } + + private static int renderTextAndCountNonWhitePixels(Image target, Font font, String text) { + GC testGC = new GC(target); + try { + testGC.setAdvanced(true); // required so font style flags (underline, strikeout) are applied during rendering + testGC.setBackground(new Color(255, 255, 255)); + testGC.fillRectangle(target.getBounds()); + testGC.setForeground(new Color(0, 0, 0)); + testGC.setFont(font); + testGC.drawText(text, 5, 5); + } finally { + testGC.dispose(); + } + ImageData imageData = target.getImageData(DPIUtil.getDeviceZoom()); + int count = 0; + for (int y = 0; y < imageData.height; y++) { + for (int x = 0; x < imageData.width; x++) { + RGB rgb = imageData.palette.getRGB(imageData.getPixel(x, y)); + if (rgb.red != 255 || rgb.green != 255 || rgb.blue != 255) { + count++; + } + } + } + return count; + } } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java index 75889472a5f..bcd36c6ab14 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java @@ -649,6 +649,8 @@ static long createGdipFont(long hDC, long hFont, long graphics, long fontCollect int style = Gdip.FontStyleRegular; if (logFont.lfWeight == 700) style |= Gdip.FontStyleBold; if (logFont.lfItalic != 0) style |= Gdip.FontStyleItalic; + if (logFont.lfUnderline != 0) style |= Gdip.FontStyleUnderline; + if (logFont.lfStrikeOut != 0) style |= Gdip.FontStyleStrikeout; char[] chars = logFont.lfFaceName; int index = 0; while (index < chars.length) {