Fonts: HiDPIUtils: improved vertical position correction of text (on Windows) for various fonts

This commit is contained in:
Karl Tauber
2022-11-18 16:27:21 +01:00
parent f23c523baf
commit 0d2e1e6d18
4 changed files with 112 additions and 40 deletions

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf.util; package com.formdev.flatlaf.util;
import java.awt.Font;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.font.GlyphVector; import java.awt.font.GlyphVector;
@@ -141,37 +142,101 @@ public class HiDPIUtils
if( !useTextYCorrection() || !SystemInfo.isWindows ) if( !useTextYCorrection() || !SystemInfo.isWindows )
return 0; return 0;
if( !SystemInfo.isJava_9_orLater ) if( !SystemInfo.isJava_9_orLater ) {
return UIScale.getUserScaleFactor() > 1 ? -UIScale.scale( 0.625f ) : 0; // 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(); case "Noto Sans":
double scaleY = t.getScaleY(); case "Open Sans":
if( scaleY < 1.25 ) return -(0.3f * scaleFactor);
return 0;
// Text is painted at slightly different Y positions depending on scale factor case "Verdana":
// and Y position of component. return -((scaleFactor < 2 ? 0.4f : 0.3f) * scaleFactor);
// 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 } else {
// - maybe different rounding methods for drawing primitives (e.g. rectangle) and text // Java 9 and later
// - 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 // Text is painted at slightly different Y positions depending on scale factor
// (tested with class FlatPaintingStringTest on Windows 10 with font "Segoe UI") // and Y position of component.
if( scaleY <= 1.25 ) // The exact reason is not yet known (to me), but there are several factors:
return -0.875f; // - fractional scale factors result in fractional component Y device coordinates
if( scaleY <= 1.5 ) // - fractional text Y device coordinates are rounded for horizontal lines of characters
return -0.625f; // - maybe different rounding methods for drawing primitives (e.g. rectangle) and text
if( scaleY <= 1.75 ) // - Java adds 0.5 to X/Y positions before drawing string in BufferedTextPipe.enqueueGlyphList()
return -0.875f;
if( scaleY <= 2.0 ) // this is not the optimal solution, but works very good in most cases
return -0.75f; // (tested with class FlatPaintingStringTest on Windows 11)
if( scaleY <= 2.25 )
return -0.875f; switch( g.getFont().getFamily() ) {
if( scaleY <= 3.5 ) case "Segoe UI":
return -0.75f; case "Segoe UI Light":
return -0.875f; 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" ) );
} }
/** /**

View File

@@ -352,7 +352,7 @@ class DemoFrame
fontMenu.addSeparator(); fontMenu.addSeparator();
ArrayList<String> families = new ArrayList<>( Arrays.asList( ArrayList<String> families = new ArrayList<>( Arrays.asList(
"Arial", "Cantarell", "Comic Sans MS", "DejaVu Sans", "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" ) ); "SansSerif", "Segoe UI", "Serif", "Tahoma", "Ubuntu", "Verdana" ) );
if( !families.contains( currentFamily ) ) if( !families.contains( currentFamily ) )
families.add( currentFamily ); families.add( currentFamily );

View File

@@ -30,6 +30,8 @@ import javax.swing.*;
import javax.swing.text.StyleContext; import javax.swing.text.StyleContext;
import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatSystemProperties; 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.Graphics2DProxy;
import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.JavaCompatibility; import com.formdev.flatlaf.util.JavaCompatibility;
@@ -45,8 +47,12 @@ public class FlatPaintingStringTest
public static void main( String[] args ) { public static void main( String[] args ) {
System.setProperty( FlatSystemProperties.UI_SCALE, "1x" ); System.setProperty( FlatSystemProperties.UI_SCALE, "1x" );
System.setProperty( "sun.java2d.uiScale", "1x" ); System.setProperty( "sun.java2d.uiScale", "1x" );
System.setProperty( "FlatLaf.debug.HiDPIUtils.useDebugScaleFactor", "true" );
SwingUtilities.invokeLater( () -> { SwingUtilities.invokeLater( () -> {
FlatInterFont.install();
FlatJetBrainsMonoFont.install();
FlatTestFrame frame = FlatTestFrame.create( args, "FlatPaintingStringTest" ); FlatTestFrame frame = FlatTestFrame.create( args, "FlatPaintingStringTest" );
ToolTipManager.sharedInstance().setInitialDelay( 0 ); ToolTipManager.sharedInstance().setInitialDelay( 0 );
@@ -70,30 +76,32 @@ public class FlatPaintingStringTest
String[] families = { String[] families = {
// regular // regular
"Arial", "Cantarell", "DejaVu Sans", "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", "SansSerif", "Segoe UI", "Tahoma", "Ubuntu", "Verdana", ".SF NS Text",
FlatInterFont.FAMILY,
// light, semibold // light, semibold
"Segoe UI Light", "Segoe UI Semibold", "Segoe UI Light", "Segoe UI Semibold",
"HelveticaNeue-Thin", "HelveticaNeue-Medium", "HelveticaNeue-Thin", "HelveticaNeue-Medium",
"Lato Light", "Ubuntu Light", "Cantarell Light", "Lato Light", "Ubuntu Light", "Cantarell Light",
"Lato Semibold", "Ubuntu Medium", "Montserrat SemiBold", "Lato Semibold", "Ubuntu Medium", "Montserrat SemiBold",
FlatInterFont.FAMILY_LIGHT, FlatInterFont.FAMILY_SEMIBOLD,
// monospaced // monospaced
"Monospaced", "Consolas", "Courier New", "Menlo", "Liberation Mono", "Ubuntu Mono", "Monospaced", "Consolas", "Courier New", "Menlo", "Liberation Mono", "Ubuntu Mono",
FlatJetBrainsMonoFont.FAMILY,
}; };
Arrays.sort( families, String.CASE_INSENSITIVE_ORDER ); Arrays.sort( families, String.CASE_INSENSITIVE_ORDER );
DefaultComboBoxModel<String> model = new DefaultComboBoxModel<>(); DefaultComboBoxModel<String> model = new DefaultComboBoxModel<>();
model.addElement( currentFamily );
for( String family : families ) { for( String family : families ) {
if( !family.equals( currentFamily ) && Arrays.binarySearch( availableFontFamilyNames, family ) >= 0 ) if( Arrays.binarySearch( availableFontFamilyNames, family ) >= 0 )
model.addElement( family ); model.addElement( family );
} }
fontField.setModel( model ); fontField.setModel( model );
fontField.setSelectedItem( currentFamily ); fontField.setSelectedItem( currentFamily );
updateFontMetricsLabel(); updateFontMetricsLabel();
add( new JLabel() ); add( new JLabel(), "newLine" );
add( new JLabel( "none" ) ); add( new JLabel( "none" ) );
add( new JLabel( "flatlaf" ) ); add( new JLabel( "flatlaf" ) );
add( new JLabel() ); add( new JLabel() );
@@ -120,9 +128,8 @@ public class FlatPaintingStringTest
YCorrectionFunction none = (g, scaleFactor) -> 0; YCorrectionFunction none = (g, scaleFactor) -> 0;
YCorrectionFunction flatlaf = (g, scaleFactor) -> { YCorrectionFunction flatlaf = (g, scaleFactor) -> {
return SystemInfo.isJava_9_orLater System.setProperty( "FlatLaf.debug.HiDPIUtils.debugScaleFactor", Float.toString( scaleFactor ) );
? HiDPIUtils.computeTextYCorrection( g ) return HiDPIUtils.computeTextYCorrection( g );
: (scaleFactor > 1 ? -(0.625f * scaleFactor) : 0);
}; };
YCorrectionFunction ty = (g, scaleFactor) -> { YCorrectionFunction ty = (g, scaleFactor) -> {
// Based on translateY, which is the scaled Y coordinate translation of the graphics context. // Based on translateY, which is the scaled Y coordinate translation of the graphics context.
@@ -214,8 +221,8 @@ public class FlatPaintingStringTest
// columns // columns
"[fill]", "[fill]",
// rows // rows
"[top]unrel" + "[shrink 0,top]unrel" +
"[top]")); "[shrink 0,top]"));
//======== panel1 ======== //======== panel1 ========
{ {

View File

@@ -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 { new FormModel {
contentType: "form/swing" contentType: "form/swing"
@@ -9,7 +9,7 @@ new FormModel {
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets dialog,hidemode 3" "$layoutConstraints": "insets dialog,hidemode 3"
"$columnConstraints": "[fill]" "$columnConstraints": "[fill]"
"$rowConstraints": "[top]unrel[top]" "$rowConstraints": "[shrink 0,top]unrel[shrink 0,top]"
} ) { } ) {
name: "this" name: "this"
"border": sfield com.jformdesigner.model.FormObject NULL_VALUE "border": sfield com.jformdesigner.model.FormObject NULL_VALUE