Compare commits

...

1 Commits

Author SHA1 Message Date
Bane Stojkovic 92a742d167 scaling 2025-12-25 16:19:25 +01:00
2 changed files with 353 additions and 0 deletions
@@ -33,6 +33,7 @@ import org.diylc.appframework.miscutils.PropertyInjector;
import org.diylc.appframework.miscutils.Utils;
import org.diylc.swingframework.export.DrawingExporter;
import org.diylc.swingframework.fonts.FontOptimizer;
import org.diylc.utils.DPIScalingUtils;
import com.thoughtworks.xstream.XStream;
import org.diylc.common.IPlugInPort;
import org.diylc.core.IView;
@@ -137,6 +138,12 @@ public class DIYLCStarter {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// Scale all UIManager font defaults for high DPI displays
// This applies globally to all Swing components, fixing font scaling
// issues on Windows with high DPI displays where HTML rendering may
// not properly scale fonts.
DPIScalingUtils.scaleUIManagerFonts();
} catch (Exception e) {
LOG.error("Could not set Look&Feel", e);
}
@@ -0,0 +1,346 @@
/*
*
* DIY Layout Creator (DIYLC).
* Copyright (c) 2009-2025 held jointly by the individual authors.
*
* This file is part of DIYLC.
*
* DIYLC is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* DIYLC is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.diylc.utils;
import java.awt.Font;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.util.Enumeration;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
/**
* Utility class for handling DPI scaling on high-resolution displays.
*
* This is particularly important on Windows where HTML rendering in Swing
* components (like JLabel) can fail to properly scale fonts on high DPI displays.
*
* @author Branislav Stojkovic
*/
public class DPIScalingUtils {
private static Double dpiScaleFactor = null;
private static final double SCALING_THRESHOLD = 0.85; // 85% threshold to avoid double-scaling
private static final double OVERSCALE_THRESHOLD = 1.3; // 130% - if font is this much larger than expected, it's over-scaled
private static final float BASE_FONT_SIZE_96DPI = 12.0f; // Standard font size at 96 DPI
private static final float MINIMUM_FONT_SIZE = 10.0f;
private static final float MAXIMUM_REASONABLE_FONT_SIZE = 20.0f;
/**
* Gets the DPI scale factor for the current display.
* This is particularly important on Windows with high DPI displays where
* HTML rendering in Swing components can fail to properly scale fonts.
*
* @return The DPI scale factor (1.0 for normal displays, > 1.0 for high DPI)
*/
public static double getDpiScaleFactor() {
if (dpiScaleFactor == null) {
try {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gd.getDefaultConfiguration();
// Get the transform scale factor from GraphicsConfiguration
// This is the most reliable way to detect DPI scaling
double scaleX = gc.getDefaultTransform().getScaleX();
double scaleY = gc.getDefaultTransform().getScaleY();
double transformScale = Math.max(scaleX, scaleY);
// On Windows, Java's DPI awareness can be inconsistent, especially with HTML rendering
// Check if we're on Windows and if the transform indicates scaling
if (System.getProperty("os.name", "").toLowerCase().contains("windows")) {
// If transform scale is > 1.0, we have DPI scaling
// HTML rendering in Swing on Windows may not respect this, so we need to compensate
if (transformScale > 1.0) {
dpiScaleFactor = transformScale;
} else {
// Fallback: check screen resolution as a secondary indicator
int screenResolution = Toolkit.getDefaultToolkit().getScreenResolution();
if (screenResolution > 96) {
// High DPI display detected via resolution
dpiScaleFactor = screenResolution / 96.0;
} else {
dpiScaleFactor = 1.0;
}
}
} else {
// On non-Windows systems, trust the transform scale
dpiScaleFactor = Math.max(1.0, transformScale);
}
} catch (Exception e) {
// Fallback to 1.0 if there's any error
dpiScaleFactor = 1.0;
}
}
return dpiScaleFactor;
}
/**
* Calculates the expected font size for a given base size at the current DPI.
*
* @param baseSize The base font size (typically at 96 DPI)
* @return The expected font size at the current DPI
*/
private static float getExpectedFontSize(float baseSize) {
double scaleFactor = getDpiScaleFactor();
return (float) (baseSize * scaleFactor);
}
/**
* Scales a font to match the DPI scaling of the display.
* This is useful for components that use HTML rendering (like JLabel with HTML text),
* which may not properly scale fonts on Windows with high DPI displays.
*
* @param font The font to scale
* @return A scaled font if high DPI is detected and scaling is needed, otherwise the original font
*/
public static Font scaleFontForDPI(Font font) {
if (font == null) {
return null;
}
double scaleFactor = getDpiScaleFactor();
// Only apply scaling when high DPI is detected
if (scaleFactor <= 1.0) {
return font;
}
float currentSize = font.getSize();
// Calculate what the base size would be (assuming current size is at 96 DPI)
float assumedBaseSize = currentSize;
// Calculate what the expected size should be at this DPI
float expectedSize = getExpectedFontSize(assumedBaseSize);
// Calculate the ratio of current to expected size
double sizeRatio = currentSize / expectedSize;
// Only scale if the current size is significantly smaller than expected
// This prevents double-scaling on systems that already handle it correctly
if (sizeRatio < SCALING_THRESHOLD) {
// Scale to expected size, but ensure minimum readability
float scaledSize = Math.max(expectedSize, MINIMUM_FONT_SIZE);
return font.deriveFont(scaledSize);
}
return font;
}
/**
* Downscales a font that appears to be over-scaled.
* This calculates what the font size should be based on the actual DPI
* and scales it down proportionally if it's significantly larger than expected.
*
* @param font The font to downscale
* @return A downscaled font if needed, otherwise the original font
*/
private static Font downscaleFontForDPI(Font font) {
if (font == null) {
return font;
}
float currentSize = font.getSize();
double scaleFactor = getDpiScaleFactor();
// Calculate what the base size would be if we reverse the DPI scaling
// This assumes the font was scaled from a base size at 96 DPI
float estimatedBaseSize = (float) (currentSize / scaleFactor);
// Calculate what the expected size should be at this DPI
float expectedSize = getExpectedFontSize(BASE_FONT_SIZE_96DPI);
// Calculate the ratio of current to expected size
double sizeRatio = currentSize / expectedSize;
// If the font is significantly larger than expected (over-scaled), downscale it
if (sizeRatio > OVERSCALE_THRESHOLD) {
// Calculate target size: use the base size if reasonable, otherwise use expected size
float targetSize;
if (estimatedBaseSize >= MINIMUM_FONT_SIZE && estimatedBaseSize <= MAXIMUM_REASONABLE_FONT_SIZE) {
// Use the calculated base size if it's reasonable
targetSize = estimatedBaseSize;
} else {
// Otherwise, use the expected size for the current DPI
targetSize = expectedSize;
}
// Ensure target is within reasonable bounds
targetSize = Math.max(MINIMUM_FONT_SIZE, Math.min(targetSize, MAXIMUM_REASONABLE_FONT_SIZE));
// Only apply if the reduction is significant enough to matter (>10% change)
if (Math.abs(targetSize - currentSize) / currentSize > 0.1) {
return font.deriveFont(targetSize);
}
}
return font;
}
/**
* Scales a font size value to match the DPI scaling of the display.
*
* @param fontSize The base font size to scale
* @return The scaled font size if high DPI is detected, otherwise the original size
*/
public static float scaleFontSizeForDPI(float fontSize) {
double scaleFactor = getDpiScaleFactor();
if (scaleFactor > 1.0) {
return (float) (fontSize * scaleFactor);
}
return fontSize;
}
/**
* Scales an integer font size value to match the DPI scaling of the display.
*
* @param fontSize The base font size to scale
* @return The scaled font size if high DPI is detected, otherwise the original size
*/
public static int scaleFontSizeForDPI(int fontSize) {
return Math.round(scaleFontSizeForDPI((float) fontSize));
}
/**
* Resets the cached DPI scale factor. This is useful if the display configuration
* changes at runtime (e.g., moving the window to a different display).
*/
public static void resetCache() {
dpiScaleFactor = null;
}
/**
* Checks if the current display is a high DPI display.
*
* @return true if the DPI scale factor is greater than 1.0
*/
public static boolean isHighDPIDisplay() {
return getDpiScaleFactor() > 1.0;
}
/**
* Scales all UIManager font defaults to match the DPI scaling of the display.
* This applies globally to all Swing components, ensuring consistent font scaling
* across the entire application without needing to modify individual components.
*
* This should be called once during application initialization, after setting the
* Look and Feel but before creating any UI components.
*
* On Windows: Fixes HTML rendering issues where fonts don't scale properly.
* On Linux: May downscale fonts if they appear to be over-scaled by the system.
* On macOS: Does nothing (macOS handles DPI scaling correctly).
*
* The scaling is conservative - it only scales fonts that appear to need adjustment,
* preventing double-scaling on systems that already handle it correctly.
*/
public static void scaleUIManagerFonts() {
String osName = System.getProperty("os.name", "").toLowerCase();
boolean isWindows = osName.contains("windows");
boolean isLinux = osName.contains("linux");
boolean isMac = osName.contains("mac");
// On macOS, Java/Swing handles DPI scaling perfectly, so do nothing
if (isMac) {
return;
}
double scaleFactor = getDpiScaleFactor();
try {
UIDefaults defaults = UIManager.getDefaults();
Enumeration<Object> keys = defaults.keys();
// Get a reference font size to check if fonts are already scaled
// Typical default font size is 12-13pt at 96 DPI
Font defaultFont = UIManager.getFont("Label.font");
if (defaultFont == null) {
defaultFont = UIManager.getFont("Button.font");
}
if (defaultFont == null) {
return;
}
float defaultSize = defaultFont.getSize();
// Calculate expected font size at current DPI (assuming base size of 12pt at 96 DPI)
float expectedSize = getExpectedFontSize(BASE_FONT_SIZE_96DPI);
// Calculate the ratio of actual to expected size
double sizeRatio = defaultSize / expectedSize;
// Determine if we need to scale up or down based on the ratio
boolean needsUpScaling = false;
boolean needsDownScaling = false;
if (isWindows && scaleFactor > 1.0) {
// On Windows with high DPI, fonts may be too small due to HTML rendering issues
// If font is significantly smaller than expected, it needs up-scaling
if (sizeRatio < SCALING_THRESHOLD) {
needsUpScaling = true;
}
} else if (isLinux) {
// On Linux, fonts may be over-scaled by the system
// If font is significantly larger than expected, it needs down-scaling
if (sizeRatio > OVERSCALE_THRESHOLD) {
needsDownScaling = true;
}
}
// If no scaling is needed, return early
if (!needsUpScaling && !needsDownScaling) {
return;
}
while (keys.hasMoreElements()) {
Object key = keys.nextElement();
Object value = defaults.get(key);
// Check if this is a font property
if (value instanceof Font) {
Font font = (Font) value;
Font adjustedFont = font;
if (needsUpScaling) {
// Scale up for Windows (fix HTML rendering issues)
adjustedFont = scaleFontForDPI(font);
} else if (needsDownScaling) {
// Scale down for Linux (fix over-scaling)
adjustedFont = downscaleFontForDPI(font);
}
// Only update if the font was actually adjusted
if (adjustedFont != font) {
defaults.put(key, adjustedFont);
}
}
}
} catch (Exception e) {
// Log error but don't fail - font scaling is a nice-to-have feature
System.err.println("Warning: Could not scale UIManager fonts: " + e.getMessage());
}
}
}