From f9a4f9771cec63afe932499894689a967e286b14 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 7 Jun 2022 11:03:34 +0200 Subject: [PATCH] Testing: FlatPaintingStringTest: - added "Fonts" combobox to test various fonts - reworked/fixed text painting/sizing to get correct results --- .../testing/FlatPaintingStringTest.java | 251 ++++++++++++++---- .../testing/FlatPaintingStringTest.jfd | 39 ++- 2 files changed, 229 insertions(+), 61 deletions(-) diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.java index d7fa7c3e..f9f3e238 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.java @@ -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 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 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; } } } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.jfd index c5fb3d83..2ee59802 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.jfd @@ -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 )