mirror of
https://github.com/JFormDesigner/FlatLaf.git
synced 2026-02-11 14:37:13 -06:00
Fonts: HiDPIUtils: improved vertical position correction of text (on Windows) for various fonts
This commit is contained in:
@@ -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" ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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 ========
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user