Skip to content

Commit

Permalink
Provide utilities to allow dpi dependent zooming of fonts
Browse files Browse the repository at this point in the history
This contribution extends the support for dynamical adaption on SWT applications on DPI related zoom changes in the win32 implementation by adding support to provider and cache scaled variants of Font. It consists of:
- internal font registries to enable reusage of scaled variants of fonts. If a font is disposed from external, it will be automatically recreated on demand. There are two registries:
- a default registry to mimic the existing behavior, that all fonts are scaled to the native zoom of the primary monitor
- a registry to create and manage scaled variants of fonts
The SWTFontProvider is configured based on the "swt.autoScale.updateOnRuntime"-flag to use one of the two font registries. The creation of system fonts is adapted to use the provider

Contributes to eclipse-platform#62
and eclipse-platform#127
  • Loading branch information
akoch-yatta committed Apr 19, 2024
1 parent 06b7539 commit 5a64656
Show file tree
Hide file tree
Showing 15 changed files with 752 additions and 20 deletions.
16 changes: 16 additions & 0 deletions bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -493,6 +504,10 @@ private static <T> ElementAtZoom<T> getElementAtZoom(Function<Integer, T> elemen
return null;
}

public static int getNativeDeviceZoom() {
return nativeDeviceZoom;
}

public static int getDeviceZoom() {
return deviceZoom;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -905,6 +916,7 @@ protected void release () {
fontCollection = 0;
Gdip.GdiplusShutdown (gdipToken[0]);
}
SWTFontProvider.disposeFontRegistry(this);
gdipToken = null;
scripts = null;
logFonts = null;
Expand Down Expand Up @@ -946,5 +958,4 @@ void setEnableAutoScaling(boolean value) {
protected int getDeviceZoom () {
return DPIUtil.mapDPIToZoom ( _getDPIx ());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@


import org.eclipse.swt.*;
import org.eclipse.swt.internal.*;
import org.eclipse.swt.internal.win32.*;

/**
Expand Down Expand Up @@ -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);
}

/**
Expand All @@ -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();
}
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -145,13 +161,15 @@ 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();
}

/*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();
}
Expand Down Expand Up @@ -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)};
}

/**
Expand All @@ -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);
Expand Down Expand Up @@ -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.
* <p>
Expand All @@ -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
Expand All @@ -278,4 +312,74 @@ public static Font win32_new(Device device, long handle) {
return font;
}

/**
* Invokes platform specific functionality to allocate a new font.
* <p>
* <b>IMPORTANT:</b> This method is <em>not</em> part of the public
* API for <code>Font</code>. 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.
* </p>
*
* @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.
* <p>
* <b>IMPORTANT:</b> This method is <em>not</em> part of the public
* API for <code>Font</code>. 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.
* </p>
*
* @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.
* <p>
* <b>IMPORTANT:</b> This method is <em>not</em> part of the public
* API for <code>Font</code>. 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.
* </p>
*
* @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);
}
}
Original file line number Diff line number Diff line change
@@ -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<FontData, Font> 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();
}
}
}
}
Loading

0 comments on commit 5a64656

Please sign in to comment.