diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os.c b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os.c index d1c9397ff64..72f6818b723 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os.c +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os.c @@ -9128,6 +9128,22 @@ JNIEXPORT jboolean JNICALL OS_NATIVE(SystemParametersInfo__II_3II) } #endif +#ifndef NO_SystemParametersInfoForDpi +JNIEXPORT jboolean JNICALL OS_NATIVE(SystemParametersInfoForDpi) + (JNIEnv *env, jclass that, jint arg0, jint arg1, jobject arg2, jint arg3, jint arg4) +{ + NONCLIENTMETRICS _arg2, *lparg2=NULL; + jboolean rc = 0; + OS_NATIVE_ENTER(env, that, SystemParametersInfoForDpi_FUNC); + if (arg2) if ((lparg2 = getNONCLIENTMETRICSFields(env, arg2, &_arg2)) == NULL) goto fail; + rc = (jboolean)SystemParametersInfoForDpi(arg0, arg1, lparg2, arg3, arg4); +fail: + if (arg2 && lparg2) setNONCLIENTMETRICSFields(env, arg2, lparg2); + OS_NATIVE_EXIT(env, that, SystemParametersInfoForDpi_FUNC); + return rc; +} +#endif + #ifndef NO_TBBUTTONINFO_1sizeof JNIEXPORT jint JNICALL OS_NATIVE(TBBUTTONINFO_1sizeof) (JNIEnv *env, jclass that) diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os_stats.h b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os_stats.h index d8bf36e349f..6654f0e9f02 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os_stats.h +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os_stats.h @@ -680,6 +680,7 @@ typedef enum { SystemParametersInfo__IILorg_eclipse_swt_internal_win32_NONCLIENTMETRICS_2I_FUNC, SystemParametersInfo__IILorg_eclipse_swt_internal_win32_RECT_2I_FUNC, SystemParametersInfo__II_3II_FUNC, + SystemParametersInfoForDpi_FUNC, TBBUTTONINFO_1sizeof_FUNC, TBBUTTON_1sizeof_FUNC, TCHITTESTINFO_1sizeof_FUNC, diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java index c3cfd904f28..36f939a6f53 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java @@ -39,6 +39,7 @@ public class OS extends C { /** * Values taken from https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions */ + public static final int WIN32_BUILD_WIN10_1607 = 14393; // "Windows 10 August 2016 Update" public static final int WIN32_BUILD_WIN10_1809 = 17763; // "Windows 10 October 2018 Update" public static final int WIN32_BUILD_WIN10_2004 = 19041; // "Windows 10 May 2020 Update" public static final int WIN32_BUILD_WIN11_21H2 = 22000; // Initial Windows 11 release @@ -4436,6 +4437,7 @@ public static int HRESULT_FROM_WIN32(int x) { public static final native boolean SystemParametersInfo (int uiAction, int uiParam, RECT pvParam, int fWinIni); public static final native boolean SystemParametersInfo (int uiAction, int uiParam, NONCLIENTMETRICS pvParam, int fWinIni); public static final native boolean SystemParametersInfo (int uiAction, int uiParam, int [] pvParam, int fWinIni); +public static final native boolean SystemParametersInfoForDpi (int uiAction, int uiParam, NONCLIENTMETRICS pvParam, int fWinIni, int dpi); /** * @param lpKeyState cast=(PBYTE) * @param pwszBuff cast=(LPWSTR) diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java index 0b554773d41..c517dda3594 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java @@ -419,6 +419,17 @@ public static int mapDPIToZoom (int dpi) { return roundedZoom; } +/** + * Compute the DPI value value based on the zoom. + * + * @return zoom + */ +public static int mapZoomToDPI (int dpi) { + double zoom = (double) dpi / 100 * DPI_ZOOM_100; + int roundedZoom = (int) Math.round (zoom); + return roundedZoom; +} + /** * Represents an element, such as some image data, at a specific zoom level. * @@ -493,6 +504,10 @@ private static ElementAtZoom getElementAtZoom(Function elemen return null; } +public static int getNativeDeviceZoom() { + return nativeDeviceZoom; +} + public static int getDeviceZoom() { return deviceZoom; } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Device.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Device.java index a3cc4f554a1..9e83aa6ee0e 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Device.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Device.java @@ -264,6 +264,10 @@ int computePixels(float height) { } float computePoints(LOGFONT logFont, long hFont) { + return computePoints(logFont, hFont, -1); +} + +float computePoints(LOGFONT logFont, long hFont, int currentFontDPI) { long hDC = internal_new_GC (null); int logPixelsY = OS.GetDeviceCaps(hDC, OS.LOGPIXELSY); int pixels = 0; @@ -284,7 +288,14 @@ float computePoints(LOGFONT logFont, long hFont) { pixels = -logFont.lfHeight; } internal_dispose_GC (hDC, null); - return pixels * 72f / logPixelsY; + float adjustedZoomFactor = 1.0f; + if (currentFontDPI > 0) { + // as Device::computePoints will always return point on the basis of the + // primary monitor zoom, a custom zoomFactor must be calculated if the font + // is used for a different zoom level + adjustedZoomFactor *= (float) logPixelsY / (float) currentFontDPI; + } + return adjustedZoomFactor * pixels * 72f / logPixelsY; } /** @@ -905,6 +916,7 @@ protected void release () { fontCollection = 0; Gdip.GdiplusShutdown (gdipToken[0]); } + SWTFontProvider.disposeFontRegistry(this); gdipToken = null; scripts = null; logFonts = null; @@ -946,5 +958,4 @@ void setEnableAutoScaling(boolean value) { protected int getDeviceZoom () { return DPIUtil.mapDPIToZoom ( _getDPIx ()); } - } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Font.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Font.java index 63492501df4..9d5b67860c0 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Font.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Font.java @@ -15,6 +15,7 @@ import org.eclipse.swt.*; +import org.eclipse.swt.internal.*; import org.eclipse.swt.internal.win32.*; /** @@ -49,11 +50,17 @@ public final class Font extends Resource { */ public long handle; + /** + * The zoom in % of the standard resolution used for conversion of point height to pixel height + * (Warning: This field is platform dependent) + */ + int zoom; /** * Prevents uninitialized instances from being created outside the package. */ Font(Device device) { super(device); + this.zoom = extractZoom(this.device); } /** @@ -78,6 +85,14 @@ public final class Font extends Resource { */ public Font(Device device, FontData fd) { super(device); + this.zoom = extractZoom(this.device); + init(fd); + init(); +} + +private Font(Device device, FontData fd, int zoom) { + super(device); + this.zoom = zoom; init(fd); init(); } @@ -114,6 +129,7 @@ public Font(Device device, FontData[] fds) { for (FontData fd : fds) { if (fd == null) SWT.error(SWT.ERROR_INVALID_ARGUMENT); } + this.zoom = extractZoom(this.device); init(fds[0]); init(); } @@ -145,6 +161,7 @@ public Font(Device device, FontData[] fds) { public Font(Device device, String name, int height, int style) { super(device); if (name == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + this.zoom = extractZoom(this.device); init(new FontData (name, height, style)); init(); } @@ -152,6 +169,7 @@ public Font(Device device, String name, int height, int style) { /*public*/ Font(Device device, String name, float height, int style) { super(device); if (name == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + this.zoom = extractZoom(this.device); init(new FontData (name, height, style)); init(); } @@ -195,7 +213,8 @@ public FontData[] getFontData() { if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED); LOGFONT logFont = new LOGFONT (); OS.GetObject(handle, LOGFONT.sizeof, logFont); - return new FontData[] {FontData.win32_new(logFont, device.computePoints(logFont, handle))}; + float heightInPoints = device.computePoints(logFont, handle, DPIUtil.mapZoomToDPI(zoom)); + return new FontData[] {FontData.win32_new(logFont, heightInPoints)}; } /** @@ -218,6 +237,13 @@ void init (FontData fd) { LOGFONT logFont = fd.data; int lfHeight = logFont.lfHeight; logFont.lfHeight = device.computePixels(fd.height); + + int primaryZoom = DPIUtil.mapDPIToZoom (device._getDPIx()); + if (zoom != primaryZoom) { + float scaleFactor = 1f * zoom / primaryZoom; + logFont.lfHeight *= scaleFactor; + } + handle = OS.CreateFontIndirect(logFont); logFont.lfHeight = lfHeight; if (handle == 0) SWT.error(SWT.ERROR_NO_HANDLES); @@ -250,6 +276,13 @@ public String toString () { return "Font {" + handle + "}"; } +private static int extractZoom(Device device) { + if (device == null) { + return DPIUtil.getNativeDeviceZoom(); + } + return DPIUtil.mapDPIToZoom(device._getDPIx()); +} + /** * Invokes platform specific functionality to allocate a new font. *

@@ -268,6 +301,7 @@ public String toString () { */ public static Font win32_new(Device device, long handle) { Font font = new Font(device); + font.zoom = extractZoom(font.device); font.handle = handle; /* * When created this way, Font doesn't own its .handle, and @@ -278,4 +312,74 @@ public static Font win32_new(Device device, long handle) { return font; } +/** + * Invokes platform specific functionality to allocate a new font. + *

+ * IMPORTANT: This method is not part of the public + * API for Font. It is marked public only so that it + * can be shared within the packages provided by SWT. It is not + * available on all platforms, and should never be called from + * application code. + *

+ * + * @param device the device on which to allocate the font + * @param handle the handle for the font + * @param zoom zoom in % of the standard resolution + * @return a new font object containing the specified device and handle + * + * @noreference This method is not intended to be referenced by clients. + * @since 3.126 + */ +public static Font win32_new(Device device, long handle, int zoom) { + Font font = win32_new(device, handle); + font.zoom = zoom; + return font; +} + +/** + * Invokes platform specific private constructor to allocate a new font. + *

+ * IMPORTANT: This method is not part of the public + * API for Font. It is marked public only so that it + * can be shared within the packages provided by SWT. It is not + * available on all platforms, and should never be called from + * application code. + *

+ * + * @param device the device on which to allocate the font + * @param fontData font data to create the font for + * @param zoom zoom in % of the standard resolution + * @return a new font object containing the specified device and handle + * + * @noreference This method is not intended to be referenced by clients. + * @since 3.126 + */ +public static Font win32_new(Device device, FontData fontData, int zoom) { + return new Font(device, fontData, zoom); +} + +/** + * Used to receive a font for the given zoom in the context + * of the current configuration of SWT at runtime. + *

+ * IMPORTANT: This method is not part of the public + * API for Font. It is marked public only so that it + * can be shared within the packages provided by SWT. It is not + * available on all platforms, and should never be called from + * application code. + *

+ * + * @param font font to create a font for the given target zoom + * @param targetZoom zoom in % of the standard resolution + * @return a new font object containing the specified device and handle + * + * @noreference This method is not intended to be referenced by clients. + * @since 3.126 + */ +public static Font win32_new(Font font, int targetZoom) { + if (targetZoom == font.zoom) { + return font; + } + return SWTFontProvider.getFont(font.getDevice(), font.getFontData()[0], targetZoom); +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/DefaultSWTFontRegistry.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/DefaultSWTFontRegistry.java new file mode 100644 index 00000000000..5113b14108d --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/DefaultSWTFontRegistry.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (c) 2024 Yatta Solutions + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Yatta Solutions - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.internal; + +import java.util.*; + +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.internal.win32.*; + +/** + * This class is used in the win32 implementation only to support + * unscaled fonts in multiple DPI zoom levels. + * + * As this class is only intended to be used internally via {@code SWTFontProvider}, + * it should neither be instantiated nor referenced in a client application. + * The behavior can change any time in a future release. + * + * @noreference This class is not intended to be referenced by clients. + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public final class DefaultSWTFontRegistry implements SWTFontRegistry { + private static FontData KEY_SYSTEM_FONTS = new FontData(); + private Map fontsMap = new HashMap<>(); + private Device device; + + public DefaultSWTFontRegistry(Device device) { + this.device = device; + } + + @Override + public Font getSystemFont(int zoom) { + if (fontsMap.containsKey(KEY_SYSTEM_FONTS)) { + return fontsMap.get(KEY_SYSTEM_FONTS); + } + + long hFont = 0; + NONCLIENTMETRICS info = new NONCLIENTMETRICS (); + info.cbSize = NONCLIENTMETRICS.sizeof; + if (OS.SystemParametersInfo (OS.SPI_GETNONCLIENTMETRICS, 0, info, 0)) { + hFont = OS.CreateFontIndirect (info.lfMessageFont); + } + if (hFont == 0) hFont = OS.GetStockObject (OS.DEFAULT_GUI_FONT); + if (hFont == 0) hFont = OS.GetStockObject (OS.SYSTEM_FONT); + Font font = Font.win32_new(device, hFont); + registerFont(KEY_SYSTEM_FONTS, font); + return font; + } + + @Override + public Font getFont(FontData fontData, int zoom) { + if (fontsMap.containsKey(fontData)) { + Font font = fontsMap.get(fontData); + if (font.isDisposed()) { + // Remove disposed cached fonts + fontsMap.remove(fontData); + } else { + return font; + } + } + Font font = new Font(device, fontData); + registerFont(fontData, font); + return font; + } + + private Font registerFont(FontData fontData, Font font) { + fontsMap.put(fontData, font); + return font; + } + + @Override + public void dispose() { + for (Font font : fontsMap.values()) { + if (font != null) { + font.dispose(); + } + } + } +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/SWTFontProvider.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/SWTFontProvider.java new file mode 100644 index 00000000000..e833058ac34 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/SWTFontProvider.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2024 Yatta Solutions and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Yatta Solutions - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.internal; + +import java.util.*; + +import org.eclipse.swt.graphics.*; + +/** + * This internal class is used to provide and cache fonts scaled for different zoom levels in the win32 + * implementation. Depending on the configuration of the SWT application, either a default behavior or + * the scaling behavior is used. The default behavior mimics the existing behavior that fonts are scaled + * to the zoom of the primary monitor and are not updated on runtime. The scaling behavior will always + * take the provided values for the zoom into consideration and return scaled variant of a font if necessary. + */ +public class SWTFontProvider { + private static Map fontRegistries = new HashMap<>(); + + private static SWTFontRegistry getFontRegistry(Device device) { + return fontRegistries.computeIfAbsent(device, SWTFontProvider::newFontRegistry); + } + + public static Font getSystemFont(Device device, int zoom) { + return getFontRegistry(device).getSystemFont(zoom); + } + + public static Font getFont(Device device, FontData fontData, int zoom) { + return getFontRegistry(device).getFont(fontData, zoom); + } + + public static void disposeFontRegistry(Device device) { + SWTFontRegistry fontRegistry = fontRegistries.remove(device); + if (fontRegistry != null) { + fontRegistry.dispose(); + } + } + + private static SWTFontRegistry newFontRegistry(Device device) { + if (DPIUtil.autoScaleOnRuntime) { + return new ScalingSWTFontRegistry(device); + } + return new DefaultSWTFontRegistry(device); + } +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/SWTFontRegistry.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/SWTFontRegistry.java new file mode 100644 index 00000000000..1d3c69a1b8d --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/SWTFontRegistry.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2024 Yatta Solutions and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Yatta Solutions - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.internal; + +import org.eclipse.swt.graphics.*; + +/** + * This class is used in the win32 implementation only to support + * re-usage of fonts. + *

+ * IMPORTANT: This class is not part of the public + * API for SWT. It is marked public only so that it can be shared + * within the packages provided by SWT. It is not available on all + * platforms, and should never be called from application code. + *

+ * @see Sample code and further information + * @noreference This class is not intended to be referenced by clients + */ +public interface SWTFontRegistry { + + /** + * Returns a system font optimally suited for the specified zoom. + * + * @param zoom zoom in % of the standard resolution to determine the appropriate system font + * @return the system font best suited for the specified zoom + */ + Font getSystemFont(int zoom); + + /** + * Provides a font optimally suited for the specified zoom. Fonts created in this manner + * are managed by the font registry and should not be disposed of externally. + * + * @param fontData the data used to create the font + * @param zoom zoom in % of the standard resolution to determine the appropriate font + * @return the font best suited for the specified zoom + */ + Font getFont(FontData fontData, int zoom); + + /** + * Disposes of all fonts managed by the font registry. + */ + void dispose(); +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/ScalingSWTFontRegistry.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/ScalingSWTFontRegistry.java new file mode 100644 index 00000000000..0b7a5a6f6dd --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/ScalingSWTFontRegistry.java @@ -0,0 +1,191 @@ +/******************************************************************************* + * Copyright (c) 2024 Yatta Solutions + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Yatta Solutions - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.internal; + +import java.util.*; +import java.util.Map.*; + +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.internal.win32.*; + +/** + * This class is used in the win32 implementation only to support + * scaling of fonts in multiple DPI zoom levels. + * + * As this class is only intended to be used internally via {@code SWTFontProvider}, + * it should neither be instantiated nor referenced in a client application. + * The behavior can change any time in a future release. + * + * @noreference This class is not intended to be referenced by clients. + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public final class ScalingSWTFontRegistry implements SWTFontRegistry { + private class ScaledFontContainer { + // the first (unknown) font to be requested as scaled variant + // usually it is scaled to the primary monitor zoom, but that is not guaranteed + private Font baseFont; + private Map scaledFonts = new HashMap<>(); + + ScaledFontContainer(Font baseFont, int fontZoom) { + this.baseFont = baseFont; + scaledFonts.put(fontZoom, baseFont); + } + + private Font getScaledFont(int targetZoom) { + if (scaledFonts.containsKey(targetZoom)) { + Font font = scaledFonts.get(targetZoom); + if (font.isDisposed()) { + scaledFonts.remove(targetZoom); + return null; + } + return font; + } + return null; + } + + private Font scaleFont(int zoom) { + FontData fontData = baseFont.getFontData()[0]; + fontData.data.lfHeight = computePixels(zoom, fontData); + Font scaledFont = Font.win32_new(device, fontData, zoom); + addScaledFont(zoom, scaledFont); + return scaledFont; + } + + private void addScaledFont(int targetZoom, Font scaledFont) { + scaledFonts.put(targetZoom, scaledFont); + } + } + + private static FontData KEY_SYSTEM_FONTS = new FontData(); + private Map fontHandleMap = new HashMap<>(); + private Map fontKeyMap = new HashMap<>(); + private Device device; + + public ScalingSWTFontRegistry(Device device) { + this.device = device; + } + + @Override + public Font getSystemFont(int zoom) { + ScaledFontContainer container = getOrCreateBaseSystemFontContainer(device); + + Font systemFont = container.getScaledFont(zoom); + if (systemFont != null) { + return systemFont; + } + long systemFontHandle = createSystemFont(zoom); + systemFont = Font.win32_new(device, systemFontHandle, zoom); + container.addScaledFont(zoom, systemFont); + return systemFont; + } + + private ScaledFontContainer getOrCreateBaseSystemFontContainer(Device device) { + ScaledFontContainer systemFontContainer = fontKeyMap.get(KEY_SYSTEM_FONTS); + if (systemFontContainer == null) { + int targetZoom = DPIUtil.mapDPIToZoom(device.getDPI().x); + long systemFontHandle = createSystemFont(targetZoom); + Font systemFont = Font.win32_new(device, systemFontHandle); + systemFontContainer = new ScaledFontContainer(systemFont, targetZoom); + fontHandleMap.put(systemFont.handle, systemFontContainer); + fontKeyMap.put(KEY_SYSTEM_FONTS, systemFontContainer); + } + return systemFontContainer; + } + + private long createSystemFont(int targetZoom) { + long hFont = 0; + NONCLIENTMETRICS info = new NONCLIENTMETRICS(); + info.cbSize = NONCLIENTMETRICS.sizeof; + if (fetchSystemParametersInfo(info, targetZoom)) { + LOGFONT logFont = info.lfMessageFont; + hFont = OS.CreateFontIndirect(logFont); + } + if (hFont == 0) + hFont = OS.GetStockObject(OS.DEFAULT_GUI_FONT); + if (hFont == 0) + hFont = OS.GetStockObject(OS.SYSTEM_FONT); + return hFont; + } + + private static boolean fetchSystemParametersInfo(NONCLIENTMETRICS info, int targetZoom) { + if (OS.WIN32_BUILD >= OS.WIN32_BUILD_WIN10_1607) { + return OS.SystemParametersInfoForDpi(OS.SPI_GETNONCLIENTMETRICS, NONCLIENTMETRICS.sizeof, info, 0, + DPIUtil.mapZoomToDPI(targetZoom)); + } else { + return OS.SystemParametersInfo(OS.SPI_GETNONCLIENTMETRICS, 0, info, 0); + } + } + + @Override + public Font getFont(FontData fontData, int zoom) { + ScaledFontContainer container; + if (fontKeyMap.containsKey(fontData)) { + container = fontKeyMap.get(fontData); + } else { + int calculatedZoom = computeZoom(fontData); + Font newFont = Font.win32_new(device, fontData, calculatedZoom); + container = new ScaledFontContainer(newFont, calculatedZoom); + fontHandleMap.put(newFont.handle, container); + fontKeyMap.put(fontData, container); + } + return getOrCreateFont(container, zoom); + } + + @Override + public void dispose() { + for (Entry fontContainerEntry : fontKeyMap.entrySet()) { + if (KEY_SYSTEM_FONTS.equals(fontContainerEntry.getKey())) { + // do not dispose the system fonts here, they are not tied to the device of this registry + continue; + } + ScaledFontContainer scaledFontContainer = fontContainerEntry.getValue(); + for (Font font : scaledFontContainer.scaledFonts.values()) { + font.dispose(); + } + } + fontKeyMap.clear(); + } + + private Font getOrCreateFont(ScaledFontContainer container, int zoom) { + Font scaledFont = container.getScaledFont(zoom); + if (scaledFont == null) { + scaledFont = container.scaleFont(zoom); + fontHandleMap.put(scaledFont.handle, container); + fontKeyMap.put(scaledFont.getFontData()[0], container); + } + return scaledFont; + } + + private int computeZoom(FontData fontData) { + int dpi = device.getDPI().x; + int pixelsAtPrimaryMonitorZoom = computePixels(fontData.height); + int value = DPIUtil.mapDPIToZoom(dpi) * fontData.data.lfHeight / pixelsAtPrimaryMonitorZoom; + return value; + } + + private int computePixels(int zoom, FontData fontData) { + int dpi = device.getDPI().x; + int adjustedLogFontHeight = computePixels(fontData.height); + int primaryZoom = DPIUtil.mapDPIToZoom(dpi); + if (zoom != primaryZoom) { + adjustedLogFontHeight *= (1f * zoom / primaryZoom); + } + return adjustedLogFontHeight; + } + + private int computePixels(float height) { + int dpi = device.getDPI().x; + return -(int)(0.5f + (height * dpi / 72f)); + } +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Control.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Control.java index 832b5cee72e..c43255242ac 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Control.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Control.java @@ -711,8 +711,8 @@ int defaultBackground () { return OS.GetSysColor (OS.COLOR_BTNFACE); } -long defaultFont () { - return display.getSystemFont ().handle; +long defaultFont() { + return display.getSystemFont(getShell().getNativeZoom()).handle; } int defaultForeground () { @@ -1304,7 +1304,7 @@ public Font getFont () { if (font != null) return font; long hFont = OS.SendMessage (handle, OS.WM_GETFONT, 0, 0); if (hFont == 0) hFont = defaultFont (); - return Font.win32_new (display, hFont); + return Font.win32_new (display, hFont, getShell().getNativeZoom()); } /** @@ -3309,7 +3309,7 @@ public void setCursor (Cursor cursor) { } void setDefaultFont () { - long hFont = display.getSystemFont ().handle; + long hFont = display.getSystemFont (getShell().getNativeZoom()).handle; OS.SendMessage (handle, OS.WM_SETFONT, hFont, 0); } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java index 9fb96b8ebb5..825f16f2d2a 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java @@ -379,7 +379,9 @@ public class Display extends Device implements Executor { long hwndMessage, messageProc; /* System Resources */ + @Deprecated(since = "3.126.0") LOGFONT lfSystemFont; + @Deprecated(since = "3.126.0") Font systemFont; Image errorImage, infoImage, questionImage, warningIcon; Cursor [] cursors = new Cursor [SWT.CURSOR_HAND + 1]; @@ -538,6 +540,7 @@ public class Display extends Device implements Executor { }; } + /* * TEMPORARY CODE. */ @@ -2445,19 +2448,21 @@ public Cursor getSystemCursor (int id) { */ @Override public Font getSystemFont () { + return getSystemFont(getPrimaryMonitor().getZoom()); +} + +Font getSystemFont (int zoom) { checkDevice (); - if (systemFont != null) return systemFont; - long hFont = 0; - NONCLIENTMETRICS info = new NONCLIENTMETRICS (); - info.cbSize = NONCLIENTMETRICS.sizeof; - if (OS.SystemParametersInfo (OS.SPI_GETNONCLIENTMETRICS, 0, info, 0)) { - LOGFONT logFont = info.lfMessageFont; - hFont = OS.CreateFontIndirect (logFont); - lfSystemFont = hFont != 0 ? logFont : null; + Font systemFont = SWTFontProvider.getSystemFont(this, zoom); + if (this.systemFont == null) { + // fill the deprecated fields for backwards compatibility + this.systemFont = systemFont; + if (systemFont != null) { + this.lfSystemFont = systemFont.getFontData()[0].data; } - if (hFont == 0) hFont = OS.GetStockObject (OS.DEFAULT_GUI_FONT); - if (hFont == 0) hFont = OS.GetStockObject (OS.SYSTEM_FONT); - return systemFont = Font.win32_new (this, hFont); + } + + return systemFont; } /** @@ -3817,10 +3822,8 @@ void releaseDisplay () { windowProc = 0; /* Release the System fonts */ - if (systemFont != null) systemFont.dispose (); systemFont = null; lfSystemFont = null; - /* Release the System Images */ if (errorImage != null) errorImage.dispose (); if (infoImage != null) infoImage.dispose (); diff --git a/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/internal/DefaultSWTFontRegistryTests.java b/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/internal/DefaultSWTFontRegistryTests.java new file mode 100644 index 00000000000..ce5ebb55c8f --- /dev/null +++ b/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/internal/DefaultSWTFontRegistryTests.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2024 Yatta Solutions + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Yatta Solutions - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.widgets.Display; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class DefaultSWTFontRegistryTests { + private static String TEST_FONT = "Helvetica"; + private Display display; + private SWTFontRegistry fontRegistry; + + @Before + public void setUp() { + this.display = Display.getDefault(); + this.fontRegistry = new DefaultSWTFontRegistry(display); + } + + @After + public void tearDown() { + if (this.fontRegistry != null) { + this.fontRegistry.dispose(); + } + } + + @Test + public void systemFontsAreCached() { + Font font1 = fontRegistry.getSystemFont(100); + Font font2 = fontRegistry.getSystemFont(100); + assertTrue("System fonts for same zoom factor must be reused", font1 == font2); + } + + @Test + public void systemFontsAlwaysDependOnPrimaryZoom() { + int primaryZoom = display.getPrimaryMonitor().getZoom(); + FontData fontPrimary = fontRegistry.getSystemFont(primaryZoom).getFontData()[0]; + FontData font100 = fontRegistry.getSystemFont(100).getFontData()[0]; + assertEquals("Point height must be equal for all zoom levels", fontPrimary.getHeight(), font100.getHeight()); + FontData font200 = fontRegistry.getSystemFont(200).getFontData()[0]; + assertEquals("Point height must be equal for all zoom levels", fontPrimary.getHeight(), font200.getHeight()); + + int heightFontPrimary = fontPrimary.data.lfHeight; + int heightFont100 = font100.data.lfHeight; + assertEquals("Pixel height must not differ between primary monitor and 100% zoom", heightFontPrimary, heightFont100); + int heightFont200 = font200.data.lfHeight; + assertEquals("Pixel height must not differ between primary monitor and 200% zoom", heightFontPrimary, heightFont200); + } + + @Test + public void fontsAreCached() { + int primaryZoom = display.getPrimaryMonitor().getZoom(); + FontData fontData = new FontData(TEST_FONT, 10, SWT.NORMAL); + Font font1 = fontRegistry.getFont(fontData, primaryZoom); + FontData fontData2 = new FontData(TEST_FONT, 10, SWT.NORMAL); + Font font2 = fontRegistry.getFont(fontData2, primaryZoom); + assertTrue("Fonts for same font data and zoom levels must be reused", font1 == font2); + } + + @Test + public void fontsAlwaysDependOnPrimaryZoom() { + int primaryZoom = display.getPrimaryMonitor().getZoom(); + FontData fontData = new FontData(TEST_FONT, 10, SWT.NORMAL); + FontData fontPrimary = fontRegistry.getFont(fontData, primaryZoom).getFontData()[0]; + FontData font100 = fontRegistry.getFont(fontData, 100).getFontData()[0]; + assertEquals("Point height must be equal for all zoom levels", fontPrimary.getHeight(), font100.getHeight()); + FontData font200 = fontRegistry.getFont(fontData, 200).getFontData()[0]; + assertEquals("Point height must be equal for all zoom levels", fontPrimary.getHeight(), font200.getHeight()); + + int heightFontPrimary = fontPrimary.data.lfHeight; + int heightFont100 = font100.data.lfHeight; + assertEquals("Pixel height must not differ between primary monitor and 100% zoom", heightFontPrimary, heightFont100); + int heightFont200 = font200.data.lfHeight; + assertEquals("Pixel height must not differ between primary monitor and 200% zoom", heightFontPrimary, heightFont200); + } +} diff --git a/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/internal/ScalingSWTFontRegistryTests.java b/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/internal/ScalingSWTFontRegistryTests.java new file mode 100644 index 00000000000..dcaf0e709e0 --- /dev/null +++ b/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/internal/ScalingSWTFontRegistryTests.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2024 Yatta Solutions + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Yatta Solutions - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.widgets.Display; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ScalingSWTFontRegistryTests { + private static String TEST_FONT = "Helvetica"; + private SWTFontRegistry fontRegistry; + + @Before + public void setUp() { + this.fontRegistry = new ScalingSWTFontRegistry(Display.getDefault()); + } + + @After + public void tearDown() { + if (this.fontRegistry != null) { + this.fontRegistry.dispose(); + } + } + + @Test + public void systemFontsAreCached() { + Font font100_1 = fontRegistry.getSystemFont(100); + Font font100_2 = fontRegistry.getSystemFont(100); + assertTrue("System fonts for same zoom factor must be reused", font100_1 == font100_2); + } + + @Test + public void systemFontsAreScaled() { + FontData font100 = fontRegistry.getSystemFont(100).getFontData()[0]; + FontData font200 = fontRegistry.getSystemFont(200).getFontData()[0]; + assertEquals("Point height must be equal for all zoom factors", font100.getHeight(), font200.getHeight()); + + int heightFont100 = font100.data.lfHeight; + int heightFont200 = font200.data.lfHeight; + assertEquals("Pixel height must be doubled between 100% and 200% zoom factor", heightFont100 * 2, heightFont200); + } + + @Test + public void fontsAreCached() { + FontData fontData = new FontData(TEST_FONT, 10, SWT.NORMAL); + Font font100_1 = fontRegistry.getFont(fontData, 100); + FontData fontData2 = new FontData(TEST_FONT, 10, SWT.NORMAL); + Font font100_2 = fontRegistry.getFont(fontData2, 100); + assertTrue("Fonts for same font data and zoom factor must be reused", font100_1 == font100_2); + } + + @Test + public void fontsAreScaled() { + FontData fontData = new FontData(TEST_FONT, 10, SWT.NORMAL); + FontData font100 = fontRegistry.getFont(fontData, 100).getFontData()[0]; + FontData font200 = fontRegistry.getFont(fontData, 200).getFontData()[0]; + assertEquals("Point height must be equal for all zoom factors", font100.getHeight(), font200.getHeight()); + + int heightFont100 = font100.data.lfHeight; + int heightFont200 = font200.data.lfHeight; + assertEquals("Pixel height must be doubled between 100% and 200% zoom factor", heightFont100 * 2, heightFont200); + } + + @Test + public void recreateDisposedFonts() { + FontData fontData = new FontData(TEST_FONT, 10, SWT.NORMAL); + Font font200 = fontRegistry.getFont(fontData, 200); + assertFalse("Font must not be disposed", font200.isDisposed()); + + font200.dispose(); + Font font200New = fontRegistry.getFont(fontData, 200); + assertFalse("Disposed fonts must not be reused in the font registry", font200 == font200New); + assertFalse("Font must not be disposed", font200New.isDisposed()); + } +} diff --git a/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/tests/win32/AllWin32Tests.java b/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/tests/win32/AllWin32Tests.java index 4d0df294284..15f636e6ecd 100644 --- a/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/tests/win32/AllWin32Tests.java +++ b/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/tests/win32/AllWin32Tests.java @@ -14,6 +14,8 @@ */ package org.eclipse.swt.tests.win32; +import org.eclipse.swt.internal.DefaultSWTFontRegistryTests; +import org.eclipse.swt.internal.ScalingSWTFontRegistryTests; import org.eclipse.swt.tests.win32.widgets.TestTreeColumn; import org.eclipse.swt.tests.win32.widgets.Test_org_eclipse_swt_widgets_Display; import org.junit.runner.JUnitCore; @@ -23,6 +25,7 @@ @RunWith(Suite.class) @Suite.SuiteClasses({ + ScalingSWTFontRegistryTests.class, Test_org_eclipse_swt_dnd_DND.class, Test_org_eclipse_swt_events_KeyEvent.class, Test_org_eclipse_swt_widgets_Display.class,