diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java index c4671518..16fec05a 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java @@ -16,6 +16,7 @@ package com.formdev.flatlaf.util; +import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.font.GlyphVector; @@ -141,37 +142,101 @@ public class HiDPIUtils if( !useTextYCorrection() || !SystemInfo.isWindows ) return 0; - if( !SystemInfo.isJava_9_orLater ) - return UIScale.getUserScaleFactor() > 1 ? -UIScale.scale( 0.625f ) : 0; + if( !SystemInfo.isJava_9_orLater ) { + // Java 8 + float scaleFactor = getUserScaleFactor(); + if( scaleFactor > 1 ) { + switch( g.getFont().getFamily() ) { + case "Segoe UI": + case "Segoe UI Light": + case "Segoe UI Semibold": + return -((scaleFactor == 2.25f || scaleFactor == 4f ? 0.875f : 0.625f) * scaleFactor); - AffineTransform t = g.getTransform(); - double scaleY = t.getScaleY(); - if( scaleY < 1.25 ) - return 0; + case "Noto Sans": + case "Open Sans": + return -(0.3f * scaleFactor); - // Text is painted at slightly different Y positions depending on scale factor - // and Y position of component. - // The exact reason is not yet known (to me), but there are several factors: - // - fractional scale factors result in fractional component Y device coordinates - // - fractional text Y device coordinates are rounded for horizontal lines of characters - // - maybe different rounding methods for drawing primitives (e.g. rectangle) and text - // - Java adds 0.5 to X/Y positions before drawing string in BufferedTextPipe.enqueueGlyphList() + case "Verdana": + return -((scaleFactor < 2 ? 0.4f : 0.3f) * scaleFactor); + } + } + } else { + // Java 9 and later - // this is not the optimal solution, but works very good in most cases - // (tested with class FlatPaintingStringTest on Windows 10 with font "Segoe UI") - if( scaleY <= 1.25 ) - return -0.875f; - if( scaleY <= 1.5 ) - return -0.625f; - if( scaleY <= 1.75 ) - return -0.875f; - if( scaleY <= 2.0 ) - return -0.75f; - if( scaleY <= 2.25 ) - return -0.875f; - if( scaleY <= 3.5 ) - return -0.75f; - return -0.875f; + // Text is painted at slightly different Y positions depending on scale factor + // and Y position of component. + // The exact reason is not yet known (to me), but there are several factors: + // - fractional scale factors result in fractional component Y device coordinates + // - fractional text Y device coordinates are rounded for horizontal lines of characters + // - maybe different rounding methods for drawing primitives (e.g. rectangle) and text + // - Java adds 0.5 to X/Y positions before drawing string in BufferedTextPipe.enqueueGlyphList() + + // this is not the optimal solution, but works very good in most cases + // (tested with class FlatPaintingStringTest on Windows 11) + + switch( g.getFont().getFamily() ) { + case "Segoe UI": + case "Segoe UI Light": + case "Segoe UI Semibold": + case "Verdana": + case Font.DIALOG: + case Font.SANS_SERIF: + return correctionForScaleY( g, CORRECTION_SEGOE_UI ); + + case "Tahoma": + return correctionForScaleY( g, CORRECTION_TAHOMA ); + + case "Inter": + case "Inter Light": + case "Inter Semi Bold": + case "Roboto": + return correctionForScaleY( g, CORRECTION_INTER ); + + case "Noto Sans": + case "Open Sans": + return correctionForScaleY( g, CORRECTION_OPEN_SANS ); + } + } + + return 0; + } + + private static final float[] + SCALE_FACTORS = { 1.25f, 1.5f, 1.75f, 2f, 2.25f, 2.5f, 3f, 3.5f, 4f }, + + CORRECTION_SEGOE_UI = { -0.5f, -0.5f, -0.625f, -0.75f, -0.75f, -0.75f, -0.75f, -0.75f, -0.875f }, + CORRECTION_TAHOMA = { -0.25f, -0.25f, -0.25f, -0f, -0.125f, -0.125f, -0.125f, -0.125f, -0f }, + CORRECTION_INTER = { -0.25f, -0.25f, -0.25f, -0f, -0.125f, -0.125f, -0f, -0.25f, -0f }, + CORRECTION_OPEN_SANS = { -0.5f, -0.25f, -0.25f, -0f, -0.25f, -0.25f, -0f, -0.25f, -0.25f }; + + private static float correctionForScaleY( Graphics2D g, float[] correction ) { + if( correction.length != 9 ) + throw new IllegalArgumentException(); + + double scaleY = g.getTransform().getScaleY(); + return (scaleY < 1.25) ? 0 : correction[scaleFactor2index( (float) scaleY )]; + } + + private static int scaleFactor2index( float scaleFactor ) { + for( int i = 0; i < SCALE_FACTORS.length; i++ ) { + if( scaleFactor <= SCALE_FACTORS[i] ) + return i; + } + return SCALE_FACTORS.length - 1; + } + + private static Boolean useDebugScaleFactor; + + private static boolean useDebugScaleFactor() { + if( useDebugScaleFactor == null ) + useDebugScaleFactor = FlatSystemProperties.getBoolean( "FlatLaf.debug.HiDPIUtils.useDebugScaleFactor", false ); + return useDebugScaleFactor; + } + + private static float getUserScaleFactor() { + return !useDebugScaleFactor() + ? UIScale.getUserScaleFactor() + : Float.parseFloat( System.getProperty( "FlatLaf.debug.HiDPIUtils.debugScaleFactor", "1" ) ); } /** diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java index db4943de..a8f5851f 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java @@ -352,7 +352,7 @@ class DemoFrame fontMenu.addSeparator(); ArrayList families = new ArrayList<>( Arrays.asList( "Arial", "Cantarell", "Comic Sans MS", "DejaVu Sans", - "Dialog", "Inter", "Liberation Sans", "Noto Sans", "Roboto", + "Dialog", "Inter", "Liberation Sans", "Noto Sans", "Open Sans", "Roboto", "SansSerif", "Segoe UI", "Serif", "Tahoma", "Ubuntu", "Verdana" ) ); if( !families.contains( currentFamily ) ) families.add( currentFamily ); diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.java index 62e054cc..187ef6ed 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.java @@ -30,6 +30,8 @@ import javax.swing.*; import javax.swing.text.StyleContext; import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatSystemProperties; +import com.formdev.flatlaf.fonts.inter.FlatInterFont; +import com.formdev.flatlaf.fonts.jetbrains_mono.FlatJetBrainsMonoFont; import com.formdev.flatlaf.util.Graphics2DProxy; import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.JavaCompatibility; @@ -45,8 +47,12 @@ public class FlatPaintingStringTest public static void main( String[] args ) { System.setProperty( FlatSystemProperties.UI_SCALE, "1x" ); System.setProperty( "sun.java2d.uiScale", "1x" ); + System.setProperty( "FlatLaf.debug.HiDPIUtils.useDebugScaleFactor", "true" ); SwingUtilities.invokeLater( () -> { + FlatInterFont.install(); + FlatJetBrainsMonoFont.install(); + FlatTestFrame frame = FlatTestFrame.create( args, "FlatPaintingStringTest" ); ToolTipManager.sharedInstance().setInitialDelay( 0 ); @@ -70,30 +76,32 @@ public class FlatPaintingStringTest String[] families = { // regular "Arial", "Cantarell", "DejaVu Sans", - "Dialog", "Helvetica Neue", "Inter", "Liberation Sans", "Noto Sans", "Open Sans", "Roboto", + "Dialog", "Helvetica Neue", "Liberation Sans", "Noto Sans", "Open Sans", "Roboto", "SansSerif", "Segoe UI", "Tahoma", "Ubuntu", "Verdana", ".SF NS Text", + FlatInterFont.FAMILY, // light, semibold "Segoe UI Light", "Segoe UI Semibold", "HelveticaNeue-Thin", "HelveticaNeue-Medium", "Lato Light", "Ubuntu Light", "Cantarell Light", "Lato Semibold", "Ubuntu Medium", "Montserrat SemiBold", + FlatInterFont.FAMILY_LIGHT, FlatInterFont.FAMILY_SEMIBOLD, // monospaced "Monospaced", "Consolas", "Courier New", "Menlo", "Liberation Mono", "Ubuntu Mono", + FlatJetBrainsMonoFont.FAMILY, }; Arrays.sort( families, String.CASE_INSENSITIVE_ORDER ); DefaultComboBoxModel model = new DefaultComboBoxModel<>(); - model.addElement( currentFamily ); for( String family : families ) { - if( !family.equals( currentFamily ) && Arrays.binarySearch( availableFontFamilyNames, family ) >= 0 ) + if( Arrays.binarySearch( availableFontFamilyNames, family ) >= 0 ) model.addElement( family ); } fontField.setModel( model ); fontField.setSelectedItem( currentFamily ); updateFontMetricsLabel(); - add( new JLabel() ); + add( new JLabel(), "newLine" ); add( new JLabel( "none" ) ); add( new JLabel( "flatlaf" ) ); add( new JLabel() ); @@ -120,9 +128,8 @@ public class FlatPaintingStringTest YCorrectionFunction none = (g, scaleFactor) -> 0; YCorrectionFunction flatlaf = (g, scaleFactor) -> { - return SystemInfo.isJava_9_orLater - ? HiDPIUtils.computeTextYCorrection( g ) - : (scaleFactor > 1 ? -(0.625f * scaleFactor) : 0); + System.setProperty( "FlatLaf.debug.HiDPIUtils.debugScaleFactor", Float.toString( scaleFactor ) ); + return HiDPIUtils.computeTextYCorrection( g ); }; YCorrectionFunction ty = (g, scaleFactor) -> { // Based on translateY, which is the scaled Y coordinate translation of the graphics context. @@ -214,8 +221,8 @@ public class FlatPaintingStringTest // columns "[fill]", // rows - "[top]unrel" + - "[top]")); + "[shrink 0,top]unrel" + + "[shrink 0,top]")); //======== panel1 ======== { diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.jfd index 2ee59802..b3bfd328 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "7.0.5.0.404" Java: "17.0.2" encoding: "UTF-8" +JFDML JFormDesigner: "8.0.0.0.194" Java: "17.0.2" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -9,7 +9,7 @@ new FormModel { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "insets dialog,hidemode 3" "$columnConstraints": "[fill]" - "$rowConstraints": "[top]unrel[top]" + "$rowConstraints": "[shrink 0,top]unrel[shrink 0,top]" } ) { name: "this" "border": sfield com.jformdesigner.model.FormObject NULL_VALUE