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.FontMetrics;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Insets; import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.util.Arrays;
import javax.swing.*; 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.FlatSystemProperties;
import com.formdev.flatlaf.ui.FlatUIUtils;
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;
@@ -57,18 +59,49 @@ public class FlatPaintingStringTest
FlatPaintingStringTest() { FlatPaintingStringTest() {
initComponents(); 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() );
add( new JLabel( "none" ) ); add( new JLabel( "none" ) );
add( new JLabel( "flatlaf" ) ); add( new JLabel( "flatlaf" ) );
add( new JLabel( "0.25*scale" ) ); add( new JLabel() );
add( new JLabel( "0.5*scale" ) );
if( SystemInfo.isJava_9_orLater ) { if( SystemInfo.isJava_9_orLater ) {
add( new JLabel( "0.125" ) );
add( new JLabel( "0.25" ) ); add( new JLabel( "0.25" ) );
add( new JLabel( "0.5" ) ); add( new JLabel( "0.5" ) );
add( new JLabel( "0.625" ) ); add( new JLabel( "0.625" ) );
add( new JLabel( "0.75" ) ); add( new JLabel( "0.75" ) );
add( new JLabel( "0.875" ) ); 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 { } else {
add( new JLabel( "0.25*scale" ) );
add( new JLabel( "0.5*scale" ) );
add( new JLabel( "0.625*scale" ) ); add( new JLabel( "0.625*scale" ) );
add( new JLabel( "0.75*scale" ) ); add( new JLabel( "0.75*scale" ) );
add( new JLabel( "0.875*scale" ) ); add( new JLabel( "0.875*scale" ) );
@@ -80,16 +113,14 @@ public class FlatPaintingStringTest
? HiDPIUtils.computeTextYCorrection( g ) ? HiDPIUtils.computeTextYCorrection( g )
: (scaleFactor > 1 ? -(0.625f * scaleFactor) : 0); : (scaleFactor > 1 ? -(0.625f * scaleFactor) : 0);
}; };
YCorrectionFunction oneQSysScale = (g, scaleFactor) -> -(0.25f * scaleFactor); YCorrectionFunction ty = (g, scaleFactor) -> {
YCorrectionFunction halfSysScale = (g, scaleFactor) -> -(0.5f * scaleFactor); // Based on translateY, which is the scaled Y coordinate translation of the graphics context.
YCorrectionFunction fiveEightsQSysScale = (g, scaleFactor) -> -(0.625f * scaleFactor); // When painting whole window, translateY is from top of window, and this works fine.
YCorrectionFunction threeQSysScale = (g, scaleFactor) -> -(0.75f * scaleFactor); // But when repainting only parts of the window, then translateY starts somewhere
YCorrectionFunction sevenEightsSysScale = (g, scaleFactor) -> -(0.875f * scaleFactor); // else and the text if (re-)painted at the wrong Y location.
YCorrectionFunction oneQ = (g, scaleFactor) -> -0.25f; double y = g.getTransform().getTranslateY();
YCorrectionFunction half = (g, scaleFactor) -> -0.5f; return (float) -(y - (int) y);
YCorrectionFunction fiveEights = (g, scaleFactor) -> -0.625f; };
YCorrectionFunction threeQ = (g, scaleFactor) -> -0.75f;
YCorrectionFunction sevenEights = (g, scaleFactor) -> -0.875f;
float[] scaleFactors = new float[] { 1f, 1.25f, 1.5f, 1.75f, 2f, 2.25f, 2.5f, 3f, 3.5f, 4f }; 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, none );
add( scaleFactor, flatlaf ); add( scaleFactor, flatlaf );
add( scaleFactor, oneQSysScale ); add( new JLabel( " " ) );
add( scaleFactor, halfSysScale );
if( SystemInfo.isJava_9_orLater ) { if( SystemInfo.isJava_9_orLater ) {
add( scaleFactor, oneQ ); add( scaleFactor, (g, sf) -> -0.125f );
add( scaleFactor, half ); add( scaleFactor, (g, sf) -> -0.25f );
add( scaleFactor, fiveEights ); add( scaleFactor, (g, sf) -> -0.5f );
add( scaleFactor, threeQ ); add( scaleFactor, (g, sf) -> -0.625f );
add( scaleFactor, sevenEights ); 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 { } else {
add( scaleFactor, fiveEightsQSysScale ); add( scaleFactor, (g, sf) -> -(0.25f * sf) );
add( scaleFactor, threeQSysScale ); add( scaleFactor, (g, sf) -> -(0.5f * sf) );
add( scaleFactor, sevenEightsSysScale ); 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 ) { private void add( float scaleFactor, YCorrectionFunction correctionFunction ) {
if( SystemInfo.isJava_9_orLater ) { if( SystemInfo.isJava_9_orLater ) {
add( new Painter( scaleFactor, correctionFunction, 0 ), "split 4, gapx 0 0" ); 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, 1 ), "gapx 0 0" );
add( new Painter( scaleFactor, correctionFunction, 0.5f ), "gapx 0 0" ); add( new Painter( scaleFactor, correctionFunction, 2 ), "gapx 0 0" );
add( new Painter( scaleFactor, correctionFunction, 0.75f ), "gapx 0 0" ); add( new Painter( scaleFactor, correctionFunction, 3 ), "gapx 0 0" );
} else } else
add( new Painter( scaleFactor, correctionFunction, 0 ) ); 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() { private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN: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 ======== //======== this ========
setBorder(null); setBorder(null);
@@ -134,11 +203,40 @@ public class FlatPaintingStringTest
// columns // columns
"[fill]", "[fill]",
// rows // rows
"[top]unrel" +
"[top]")); "[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 - End of component initialization //GEN-END:initComponents
} }
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
private JComboBox<String> fontField;
private JLabel fontMetricsLabel;
// JFormDesigner - End of variables declaration //GEN-END:variables // JFormDesigner - End of variables declaration //GEN-END:variables
private interface YCorrectionFunction { private interface YCorrectionFunction {
@@ -152,17 +250,29 @@ public class FlatPaintingStringTest
{ {
private final float scaleFactor; private final float scaleFactor;
private final YCorrectionFunction correctionFunction; 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" ); super( "E" );
this.scaleFactor = scaleFactor; this.scaleFactor = scaleFactor;
this.correctionFunction = correctionFunction; this.correctionFunction = correctionFunction;
this.yOffset = yOffset; 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 ) { if( !SystemInfo.isJava_9_orLater ) {
Font font = getFont(); Font font = UIManager.getFont( "defaultFont" );
setFont( font.deriveFont( (float) Math.round( font.getSize() * scaleFactor ) ) ); setFont( font.deriveFont( (float) Math.round( font.getSize() * scaleFactor ) ) );
} }
} }
@@ -170,35 +280,36 @@ public class FlatPaintingStringTest
@Override @Override
public Dimension getPreferredSize() { public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize(); Dimension size = super.getPreferredSize();
Insets insets = getInsets(); if( SystemInfo.isJava_9_orLater ) {
int leftRight = insets.left + insets.right; // compute component size using JRE scaling
return new Dimension( //
scale( size.width -leftRight ) + leftRight, // The y offset is used to simulate different vertical component positions,
scale( size.height ) ); // 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 @Override
protected void paintComponent( Graphics g ) { protected void paintComponent( Graphics g ) {
Graphics2D g2 = (Graphics2D) 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 width = getWidth();
int height = getHeight(); int height = getHeight();
Insets insets = getInsets();
FontMetrics fm = getFontMetrics( getFont() ); FontMetrics fm = getFontMetrics( getFont() );
// paint lines at 1x // paint lines at 1x
HiDPIUtils.paintAtScale1x( g2, 0, 0, width, height, HiDPIUtils.paintAtScale1x( g2, 0, 0, width, height,
(g2d, x2, y2, width2, height2, scaleFactor2) -> { (g2d, x2, y2, width2, height2, scaleFactor2) -> {
// g.setColor( Color.blue ); g.setColor( Color.blue );
// g.drawLine( 0, 0, width2, 0 ); g.drawLine( 0, 0, width2, 0 );
// g.drawLine( 0, height2 - 1, width2, height2 - 1 ); 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; * (SystemInfo.isJava_9_orLater ? scaleFactor : 1f) ) - 1;
int topline = height2 - baseline - 1; int topline = height2 - baseline - 1;
@@ -207,8 +318,15 @@ public class FlatPaintingStringTest
g.drawLine( 0, topline, width2, topline ); g.drawLine( 0, topline, width2, topline );
} ); } );
// move x before scaling to have same left inset at all scale factors // simulate different vertical component positions
g.translate( insets.left, 0 ); 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 // scale
if( SystemInfo.isJava_9_orLater ) if( SystemInfo.isJava_9_orLater )
@@ -232,21 +350,36 @@ public class FlatPaintingStringTest
// draw string // draw string
g.setColor( getForeground() ); g.setColor( getForeground() );
int y = insets.top + fm.getAscent(); int y = fm.getAscent();
JavaCompatibility.drawStringUnderlineCharAt( this, cg, "E", -1, 0, y ); JavaCompatibility.drawStringUnderlineCharAt( this, cg, "E", -1, 0, y );
// set tooltip text // set tooltip text
if( getToolTipText() == null ) { AffineTransform t = g2.getTransform();
AffineTransform t = g2.getTransform(); double textY = t.getTranslateY() + (y * t.getScaleY());
double textY = t.getTranslateY() + (y * t.getScaleY()); setToolTipText( textY + " + " + yCorrection + " = " + (textY + yCorrection) );
setToolTipText( textY + " + " + yCorrection + " = " + (textY + yCorrection) );
}
FlatUIUtils.resetRenderingHints( g2, oldRenderingHints );
} }
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 { new FormModel {
contentType: "form/swing" contentType: "form/swing"
@@ -9,10 +9,45 @@ 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]" "$rowConstraints": "[top]unrel[top]"
} ) { } ) {
name: "this" name: "this"
"border": sfield com.jformdesigner.model.FormObject NULL_VALUE "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 ) { }, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 ) "location": new java.awt.Point( 0, 0 )
"size": new java.awt.Dimension( 450, 300 ) "size": new java.awt.Dimension( 450, 300 )