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;
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" ) );
}
/**

View File

@@ -352,7 +352,7 @@ class DemoFrame
fontMenu.addSeparator();
ArrayList<String> 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 );

View File

@@ -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<String> 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 ========
{

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 {
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