Testing: FlatPaintingStringTest:

- added "Fonts" combobox to test various fonts
- reworked/fixed text painting/sizing to get correct results
This commit is contained in:
Karl Tauber
2022-06-07 11:03:34 +02:00
parent d2acb2c98a
commit f9a4f9771c
2 changed files with 229 additions and 61 deletions

View File

@@ -22,12 +22,14 @@ import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.util.Arrays;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.text.StyleContext;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.Graphics2DProxy;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.JavaCompatibility;
@@ -57,18 +59,49 @@ public class FlatPaintingStringTest
FlatPaintingStringTest() {
initComponents();
String[] availableFontFamilyNames = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getAvailableFontFamilyNames().clone();
Arrays.sort( availableFontFamilyNames );
Font currentFont = UIManager.getFont( "Label.font" );
String currentFamily = currentFont.getFamily();
// initialize font families combobox
String[] families = {
"Arial", "Cantarell", "DejaVu Sans",
"Dialog", "Inter", "Liberation Sans", "Noto Sans", "Open Sans", "Roboto",
"SansSerif", "Segoe UI", "Tahoma", "Ubuntu", "Verdana" };
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 )
model.addElement( family );
}
fontField.setModel( model );
fontField.setSelectedItem( currentFamily );
updateFontMetricsLabel();
add( new JLabel() );
add( new JLabel( "none" ) );
add( new JLabel( "flatlaf" ) );
add( new JLabel( "0.25*scale" ) );
add( new JLabel( "0.5*scale" ) );
add( new JLabel() );
if( SystemInfo.isJava_9_orLater ) {
add( new JLabel( "0.125" ) );
add( new JLabel( "0.25" ) );
add( new JLabel( "0.5" ) );
add( new JLabel( "0.625" ) );
add( new JLabel( "0.75" ) );
add( new JLabel( "0.875" ) );
add( new JLabel( "1" ) );
add( new JLabel( "1.25" ) );
add( new JLabel() );
add( new JLabel( "0.25*scale" ) );
add( new JLabel( "0.5*scale" ) );
add( new JLabel( "ty" ) );
} else {
add( new JLabel( "0.25*scale" ) );
add( new JLabel( "0.5*scale" ) );
add( new JLabel( "0.625*scale" ) );
add( new JLabel( "0.75*scale" ) );
add( new JLabel( "0.875*scale" ) );
@@ -80,16 +113,14 @@ public class FlatPaintingStringTest
? HiDPIUtils.computeTextYCorrection( g )
: (scaleFactor > 1 ? -(0.625f * scaleFactor) : 0);
};
YCorrectionFunction oneQSysScale = (g, scaleFactor) -> -(0.25f * scaleFactor);
YCorrectionFunction halfSysScale = (g, scaleFactor) -> -(0.5f * scaleFactor);
YCorrectionFunction fiveEightsQSysScale = (g, scaleFactor) -> -(0.625f * scaleFactor);
YCorrectionFunction threeQSysScale = (g, scaleFactor) -> -(0.75f * scaleFactor);
YCorrectionFunction sevenEightsSysScale = (g, scaleFactor) -> -(0.875f * scaleFactor);
YCorrectionFunction oneQ = (g, scaleFactor) -> -0.25f;
YCorrectionFunction half = (g, scaleFactor) -> -0.5f;
YCorrectionFunction fiveEights = (g, scaleFactor) -> -0.625f;
YCorrectionFunction threeQ = (g, scaleFactor) -> -0.75f;
YCorrectionFunction sevenEights = (g, scaleFactor) -> -0.875f;
YCorrectionFunction ty = (g, scaleFactor) -> {
// Based on translateY, which is the scaled Y coordinate translation of the graphics context.
// When painting whole window, translateY is from top of window, and this works fine.
// But when repainting only parts of the window, then translateY starts somewhere
// else and the text if (re-)painted at the wrong Y location.
double y = g.getTransform().getTranslateY();
return (float) -(y - (int) y);
};
float[] scaleFactors = new float[] { 1f, 1.25f, 1.5f, 1.75f, 2f, 2.25f, 2.5f, 3f, 3.5f, 4f };
@@ -98,34 +129,72 @@ public class FlatPaintingStringTest
add( scaleFactor, none );
add( scaleFactor, flatlaf );
add( scaleFactor, oneQSysScale );
add( scaleFactor, halfSysScale );
add( new JLabel( " " ) );
if( SystemInfo.isJava_9_orLater ) {
add( scaleFactor, oneQ );
add( scaleFactor, half );
add( scaleFactor, fiveEights );
add( scaleFactor, threeQ );
add( scaleFactor, sevenEights );
add( scaleFactor, (g, sf) -> -0.125f );
add( scaleFactor, (g, sf) -> -0.25f );
add( scaleFactor, (g, sf) -> -0.5f );
add( scaleFactor, (g, sf) -> -0.625f );
add( scaleFactor, (g, sf) -> -0.75f );
add( scaleFactor, (g, sf) -> -0.875f );
add( scaleFactor, (g, sf) -> -1f );
add( scaleFactor, (g, sf) -> -1.25f );
add( new JLabel( " " ) );
add( scaleFactor, (g, sf) -> -(0.25f * sf) );
add( scaleFactor, (g, sf) -> -(0.5f * sf) );
add( scaleFactor, ty );
} else {
add( scaleFactor, fiveEightsQSysScale );
add( scaleFactor, threeQSysScale );
add( scaleFactor, sevenEightsSysScale );
add( scaleFactor, (g, sf) -> -(0.25f * sf) );
add( scaleFactor, (g, sf) -> -(0.5f * sf) );
add( scaleFactor, (g, sf) -> -(0.625f * sf) );
add( scaleFactor, (g, sf) -> -(0.75f * sf) );
add( scaleFactor, (g, sf) -> -(0.875f * sf) );
}
add( new JLabel( String.valueOf( scaleFactor ) ) );
}
}
private void add( float scaleFactor, YCorrectionFunction correctionFunction ) {
if( SystemInfo.isJava_9_orLater ) {
add( new Painter( scaleFactor, correctionFunction, 0 ), "split 4, gapx 0 0" );
add( new Painter( scaleFactor, correctionFunction, 0.25f ), "gapx 0 0" );
add( new Painter( scaleFactor, correctionFunction, 0.5f ), "gapx 0 0" );
add( new Painter( scaleFactor, correctionFunction, 0.75f ), "gapx 0 0" );
add( new Painter( scaleFactor, correctionFunction, 1 ), "gapx 0 0" );
add( new Painter( scaleFactor, correctionFunction, 2 ), "gapx 0 0" );
add( new Painter( scaleFactor, correctionFunction, 3 ), "gapx 0 0" );
} else
add( new Painter( scaleFactor, correctionFunction, 0 ) );
}
private void fontChanged() {
String family = (String) fontField.getSelectedItem();
Font font = UIManager.getFont( "defaultFont" );
if( font.getFamily().equals( family ) )
return;
Font newFont = StyleContext.getDefaultStyleContext().getFont( family, font.getStyle(), font.getSize() );
UIManager.put( "defaultFont", newFont );
updateFontMetricsLabel();
FlatLaf.updateUILater();
}
private void updateFontMetricsLabel() {
Font font = UIManager.getFont( "defaultFont" );
FontMetrics fm = getFontMetrics( font );
fontMetricsLabel.setText( String.format( "%s %d height %d ascent %d descent %d max ascent %d max descent %d leading %d",
font.getFamily(), font.getSize(),
fm.getHeight(), fm.getAscent(), fm.getDescent(),
fm.getMaxAscent(), fm.getMaxDescent(), fm.getLeading()
) );
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
JPanel panel1 = new JPanel();
JLabel fontLabel = new JLabel();
fontField = new JComboBox<>();
fontMetricsLabel = new JLabel();
//======== this ========
setBorder(null);
@@ -134,11 +203,40 @@ public class FlatPaintingStringTest
// columns
"[fill]",
// rows
"[top]unrel" +
"[top]"));
//======== panel1 ========
{
panel1.setLayout(new MigLayout(
"hidemode 3",
// columns
"[fill]" +
"[fill]" +
"[fill]",
// rows
"[]"));
//---- fontLabel ----
fontLabel.setText("Font:");
panel1.add(fontLabel, "cell 0 0");
//---- fontField ----
fontField.setMaximumRowCount(20);
fontField.addActionListener(e -> fontChanged());
panel1.add(fontField, "cell 1 0");
//---- fontMetricsLabel ----
fontMetricsLabel.setText("text");
panel1.add(fontMetricsLabel, "cell 2 0");
}
add(panel1, "north");
// JFormDesigner - End of component initialization //GEN-END:initComponents
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
private JComboBox<String> fontField;
private JLabel fontMetricsLabel;
// JFormDesigner - End of variables declaration //GEN-END:variables
private interface YCorrectionFunction {
@@ -152,17 +250,29 @@ public class FlatPaintingStringTest
{
private final float scaleFactor;
private final YCorrectionFunction correctionFunction;
private final float yOffset;
private final int yOffset;
public Painter( float scaleFactor, YCorrectionFunction correctionFunction, float yOffset ) {
public Painter( float scaleFactor, YCorrectionFunction correctionFunction, int yOffset ) {
super( "E" );
this.scaleFactor = scaleFactor;
this.correctionFunction = correctionFunction;
this.yOffset = yOffset;
setBorder( new EmptyBorder( 2, 0, 2, 0 ) );
updateFont();
}
@Override
public void updateUI() {
super.updateUI();
updateFont();
}
private void updateFont() {
if( scaleFactor == 0 )
return; // invoked from super constructor
if( !SystemInfo.isJava_9_orLater ) {
Font font = getFont();
Font font = UIManager.getFont( "defaultFont" );
setFont( font.deriveFont( (float) Math.round( font.getSize() * scaleFactor ) ) );
}
}
@@ -170,35 +280,36 @@ public class FlatPaintingStringTest
@Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
Insets insets = getInsets();
int leftRight = insets.left + insets.right;
return new Dimension(
scale( size.width -leftRight ) + leftRight,
scale( size.height ) );
if( SystemInfo.isJava_9_orLater ) {
// compute component size using JRE scaling
//
// The y offset is used to simulate different vertical component positions,
// which may result in different component heights.
// E.g. scaling following bounds by 150% results in different heights:
// 0,0, 10,15 --> [x=0,y=0,width=15,height=22]
// 0,1, 10,15 --> [x=0,y=1,width=15,height=23]
Rectangle r = scaleTo1x( scaleFactor, 0, yOffset, size.width, size.height );
return new Dimension( r.width, r.height );
} else
return size;
}
@Override
protected void paintComponent( Graphics g ) {
Graphics2D g2 = (Graphics2D) g;
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g2 );
// simulate component y position at a fraction
if( scaleFactor > 1 && SystemInfo.isJava_9_orLater )
g2.translate( 0, yOffset );
int width = getWidth();
int height = getHeight();
Insets insets = getInsets();
FontMetrics fm = getFontMetrics( getFont() );
// paint lines at 1x
HiDPIUtils.paintAtScale1x( g2, 0, 0, width, height,
(g2d, x2, y2, width2, height2, scaleFactor2) -> {
// g.setColor( Color.blue );
// g.drawLine( 0, 0, width2, 0 );
// g.drawLine( 0, height2 - 1, width2, height2 - 1 );
g.setColor( Color.blue );
g.drawLine( 0, 0, width2, 0 );
g.drawLine( 0, height2 - 1, width2, height2 - 1 );
int baseline = (int) Math.round( (insets.top + fm.getAscent()) * scaleFactor2
int baseline = (int) Math.round( fm.getAscent() * scaleFactor2
* (SystemInfo.isJava_9_orLater ? scaleFactor : 1f) ) - 1;
int topline = height2 - baseline - 1;
@@ -207,8 +318,15 @@ public class FlatPaintingStringTest
g.drawLine( 0, topline, width2, topline );
} );
// move x before scaling to have same left inset at all scale factors
g.translate( insets.left, 0 );
// simulate different vertical component positions
if( yOffset > 0 && SystemInfo.isJava_9_orLater ) {
double ty = yOffset * scaleFactor;
ty -= (int) ty;
if( ty == 0 )
return; // no need to paint
g2.translate( 0, ty );
}
// scale
if( SystemInfo.isJava_9_orLater )
@@ -232,21 +350,36 @@ public class FlatPaintingStringTest
// draw string
g.setColor( getForeground() );
int y = insets.top + fm.getAscent();
int y = fm.getAscent();
JavaCompatibility.drawStringUnderlineCharAt( this, cg, "E", -1, 0, y );
// set tooltip text
if( getToolTipText() == null ) {
AffineTransform t = g2.getTransform();
double textY = t.getTranslateY() + (y * t.getScaleY());
setToolTipText( textY + " + " + yCorrection + " = " + (textY + yCorrection) );
}
FlatUIUtils.resetRenderingHints( g2, oldRenderingHints );
AffineTransform t = g2.getTransform();
double textY = t.getTranslateY() + (y * t.getScaleY());
setToolTipText( textY + " + " + yCorrection + " = " + (textY + yCorrection) );
}
private int scale( int value ) {
return SystemInfo.isJava_9_orLater ? Math.round( value * scaleFactor ) : value;
/**
* Scales a rectangle in the same way as the JRE does in
* sun.java2d.pipe.PixelToParallelogramConverter.fillRectangle(),
* which is used by Graphics.fillRect().
*
* This is a copy of HiDPIUtils.scale()
*/
public static Rectangle scaleTo1x( double scaleFactor, int x, int y, int width, int height ) {
double px = (x * scaleFactor);
double py = (y * scaleFactor);
double newX = normalize( px );
double newY = normalize( py );
double newWidth = normalize( px + (width * scaleFactor) ) - newX;
double newHeight = normalize( py + (height * scaleFactor) ) - newY;
return new Rectangle( (int) Math.floor( newX ), (int) Math.floor( newY ), (int) newWidth, (int) newHeight );
}
private static double normalize( double value ) {
return Math.floor( value + 0.25 ) + 0.25;
}
}
}

View File

@@ -1,4 +1,4 @@
JFDML JFormDesigner: "7.0.2.0.298" Java: "14" encoding: "UTF-8"
JFDML JFormDesigner: "7.0.5.0.404" Java: "17.0.2" encoding: "UTF-8"
new FormModel {
contentType: "form/swing"
@@ -9,10 +9,45 @@ new FormModel {
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets dialog,hidemode 3"
"$columnConstraints": "[fill]"
"$rowConstraints": "[top]"
"$rowConstraints": "[top]unrel[top]"
} ) {
name: "this"
"border": sfield com.jformdesigner.model.FormObject NULL_VALUE
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "hidemode 3"
"$columnConstraints": "[fill][fill][fill]"
"$rowConstraints": "[]"
} ) {
name: "panel1"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "fontLabel"
"text": "Font:"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0"
} )
add( new FormComponent( "javax.swing.JComboBox" ) {
name: "fontField"
"maximumRowCount": 20
auxiliary() {
"JavaCodeGenerator.variableLocal": false
"JavaCodeGenerator.typeParameters": "String"
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "fontChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 0"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "fontMetricsLabel"
"text": "text"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 0"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "north"
} )
}, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 )
"size": new java.awt.Dimension( 450, 300 )