From 15a714faed6bbdf769eefc388d54582a7185cb95 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Mon, 22 Jun 2020 13:45:56 +0200 Subject: [PATCH] fixed/improved vertical position of text when scaled on HiDPI screens on Windows when running on Java 9 or later --- CHANGELOG.md | 2 + .../formdev/flatlaf/ui/FlatEditorPaneUI.java | 7 + .../com/formdev/flatlaf/ui/FlatLabelUI.java | 9 + .../flatlaf/ui/FlatPasswordFieldUI.java | 5 +- .../formdev/flatlaf/ui/FlatTextAreaUI.java | 7 + .../formdev/flatlaf/ui/FlatTextFieldUI.java | 4 +- .../formdev/flatlaf/ui/FlatTextPaneUI.java | 7 + .../com/formdev/flatlaf/ui/FlatToolTipUI.java | 3 +- .../com/formdev/flatlaf/ui/FlatUIUtils.java | 28 +-- .../com/formdev/flatlaf/util/HiDPIUtils.java | 114 +++++++++ .../testing/FlatPaintingStringTest.java | 223 ++++++++++++++++++ .../testing/FlatPaintingStringTest.jfd | 21 ++ 12 files changed, 413 insertions(+), 17 deletions(-) create mode 100644 flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.java create mode 100644 flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.jfd diff --git a/CHANGELOG.md b/CHANGELOG.md index 04b63293..dc8c5f33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ FlatLaf Change Log `Spinner.buttonStyle` to `button` (default) or `none`). - TableHeader: Support top/bottom/left positioned sort arrow when using [Glazed Lists](https://github.com/glazedlists/glazedlists). (issue #113) +- Fixed/improved vertical position of text when scaled on HiDPI screens on + Windows when running on Java 9 or later. ## 0.36 diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatEditorPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatEditorPaneUI.java index f988d89e..45484034 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatEditorPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatEditorPaneUI.java @@ -19,6 +19,7 @@ package com.formdev.flatlaf.ui; import static com.formdev.flatlaf.util.UIScale.scale; import java.awt.Dimension; import java.awt.Graphics; +import java.awt.Graphics2D; import java.beans.PropertyChangeEvent; import javax.swing.JComponent; import javax.swing.JEditorPane; @@ -28,6 +29,7 @@ import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicEditorPaneUI; import javax.swing.text.JTextComponent; import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.util.HiDPIUtils; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JEditorPane}. @@ -119,6 +121,11 @@ public class FlatEditorPaneUI return size; } + @Override + protected void paintSafely( Graphics g ) { + super.paintSafely( HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g ) ); + } + @Override protected void paintBackground( Graphics g ) { JTextComponent c = getComponent(); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLabelUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLabelUI.java index 6ed86360..1b4cc2ed 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLabelUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLabelUI.java @@ -19,6 +19,7 @@ package com.formdev.flatlaf.ui; import java.awt.Color; import java.awt.FontMetrics; import java.awt.Graphics; +import java.awt.Graphics2D; import java.awt.Rectangle; import java.beans.PropertyChangeEvent; import javax.swing.Icon; @@ -29,7 +30,9 @@ import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicHTML; import javax.swing.plaf.basic.BasicLabelUI; +import javax.swing.text.View; import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.UIScale; /** @@ -124,6 +127,12 @@ public class FlatLabelUI BasicHTML.updateRenderer( c, text ); } + @Override + public void paint( Graphics g, JComponent c ) { + View v = (View) c.getClientProperty( BasicHTML.propertyKey ); + super.paint( (v != null) ? HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g ) : g, c ); + } + @Override protected void paintEnabledText( JLabel l, Graphics g, String s, int textX, int textY ) { int mnemIndex = FlatLaf.isShowMnemonics() ? l.getDisplayedMnemonicIndex() : -1; diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java index 92b4c9f5..0ca2abda 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java @@ -19,6 +19,7 @@ package com.formdev.flatlaf.ui; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; +import java.awt.Graphics2D; import java.awt.Toolkit; import java.awt.event.FocusListener; import java.awt.event.KeyAdapter; @@ -33,6 +34,7 @@ import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicPasswordFieldUI; import javax.swing.text.Caret; import javax.swing.text.JTextComponent; +import com.formdev.flatlaf.util.HiDPIUtils; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JPasswordField}. @@ -153,7 +155,8 @@ public class FlatPasswordFieldUI FlatTextFieldUI.paintBackground( g, getComponent(), isIntelliJTheme ); FlatTextFieldUI.paintPlaceholder( g, getComponent(), placeholderForeground ); paintCapsLock( g ); - super.paintSafely( g ); + + super.paintSafely( HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g ) ); } protected void paintCapsLock( Graphics g ) { diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextAreaUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextAreaUI.java index 226e57e5..5d640192 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextAreaUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextAreaUI.java @@ -19,6 +19,7 @@ package com.formdev.flatlaf.ui; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; +import java.awt.Graphics2D; import java.beans.PropertyChangeEvent; import javax.swing.JComponent; import javax.swing.JTextArea; @@ -27,6 +28,7 @@ import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicTextAreaUI; import javax.swing.text.JTextComponent; +import com.formdev.flatlaf.util.HiDPIUtils; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JTextArea}. @@ -89,6 +91,11 @@ public class FlatTextAreaUI FlatEditorPaneUI.propertyChange( getComponent(), e ); } + @Override + protected void paintSafely( Graphics g ) { + super.paintSafely( HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g ) ); + } + @Override protected void paintBackground( Graphics g ) { JTextComponent c = getComponent(); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java index 07600998..edcc3c0b 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java @@ -38,6 +38,7 @@ import javax.swing.plaf.basic.BasicTextFieldUI; import javax.swing.text.Caret; import javax.swing.text.JTextComponent; import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.util.HiDPIUtils; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JTextField}. @@ -146,7 +147,8 @@ public class FlatTextFieldUI protected void paintSafely( Graphics g ) { paintBackground( g, getComponent(), isIntelliJTheme ); paintPlaceholder( g, getComponent(), placeholderForeground ); - super.paintSafely( g ); + + super.paintSafely( HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g ) ); } @Override diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextPaneUI.java index 9a8715c5..0def747f 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextPaneUI.java @@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui; import java.awt.Dimension; import java.awt.Graphics; +import java.awt.Graphics2D; import java.beans.PropertyChangeEvent; import javax.swing.JComponent; import javax.swing.JEditorPane; @@ -26,6 +27,7 @@ import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicTextPaneUI; import javax.swing.text.JTextComponent; +import com.formdev.flatlaf.util.HiDPIUtils; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JTextPane}. @@ -99,6 +101,11 @@ public class FlatTextPaneUI return FlatEditorPaneUI.applyMinimumWidth( c, super.getMinimumSize( c ), minimumWidth ); } + @Override + protected void paintSafely( Graphics g ) { + super.paintSafely( HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g ) ); + } + @Override protected void paintBackground( Graphics g ) { JTextComponent c = getComponent(); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolTipUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolTipUI.java index 2b9c1b57..e6a24c35 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolTipUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolTipUI.java @@ -29,6 +29,7 @@ import javax.swing.SwingUtilities; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicHTML; import javax.swing.plaf.basic.BasicToolTipUI; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.StringUtils; /** @@ -130,7 +131,7 @@ public class FlatToolTipUI FlatUIUtils.drawString( c, g, line, leftToRight ? x : x2 - SwingUtilities.computeStringWidth( fm, line ), y ); } } else - super.paint( g, c ); + super.paint( HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g ), c ); } private boolean isMultiLine( JComponent c ) { diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java index 492f6ba3..8e977fba 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java @@ -46,7 +46,6 @@ import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.util.DerivedColor; import com.formdev.flatlaf.util.Graphics2DProxy; import com.formdev.flatlaf.util.HiDPIUtils; -import com.formdev.flatlaf.util.JavaCompatibility; import com.formdev.flatlaf.util.UIScale; /** @@ -449,23 +448,24 @@ public class FlatUIUtils } /** - * Draws the given string at the specified location using text properties - * and anti-aliasing hints from the provided component. - * - * Use this method instead of Graphics.drawString() for correct anti-aliasing. - * - * Replacement for SwingUtilities2.drawString() + * Draws the given string at the specified location. + * The provided component is used to query text properties and anti-aliasing hints. + *

+ * Use this method instead of {@link Graphics#drawString(String, int, int)} for correct anti-aliasing. + *

+ * Replacement for {@code SwingUtilities2.drawString()}. + * Uses {@link HiDPIUtils#drawStringWithYCorrection(JComponent, Graphics2D, String, int, int)}. */ public static void drawString( JComponent c, Graphics g, String text, int x, int y ) { - JavaCompatibility.drawStringUnderlineCharAt( c, g, text, -1, x, y ); + HiDPIUtils.drawStringWithYCorrection( c, (Graphics2D) g, text, x, y ); } /** - * Draws the given string at the specified location underlining the specified - * character. The provided component is used to query text properties and - * anti-aliasing hints. - * - * Replacement for SwingUtilities2.drawStringUnderlineCharAt() + * Draws the given string at the specified location underlining the specified character. + * The provided component is used to query text properties and anti-aliasing hints. + *

+ * Replacement for {@code SwingUtilities2.drawStringUnderlineCharAt()}. + * Uses {@link HiDPIUtils#drawStringUnderlineCharAtWithYCorrection(JComponent, Graphics2D, String, int, int, int)}. */ public static void drawStringUnderlineCharAt( JComponent c, Graphics g, String text, int underlinedIndex, int x, int y ) @@ -487,7 +487,7 @@ public class FlatUIUtils }; } - JavaCompatibility.drawStringUnderlineCharAt( c, g, text, underlinedIndex, x, y ); + HiDPIUtils.drawStringUnderlineCharAtWithYCorrection( c, (Graphics2D) g, text, underlinedIndex, x, y ); } public static boolean hasOpaqueBeenExplicitlySet( JComponent c ) { diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java index c49048b2..71dbc156 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java @@ -16,9 +16,11 @@ package com.formdev.flatlaf.util; +import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; +import java.text.AttributedCharacterIterator; import javax.swing.JComponent; /** @@ -95,4 +97,116 @@ public class HiDPIUtils private static double normalize( double value ) { return Math.floor( value + 0.25 ) + 0.25; } + + + private static Boolean useTextYCorrection; + + private static boolean useTextYCorrection() { + if( useTextYCorrection == null ) + useTextYCorrection = Boolean.valueOf( System.getProperty( "flatlaf.useTextYCorrection", "true" ) ); + return useTextYCorrection; + } + + /** + * When painting text on HiDPI screens and the JRE scales, then the text is + * painted too far down on some operating systems. + * The higher the system scale factor is, the more. + *

+ * This methods computes a correction value for the Y position. + */ + public static float computeTextYCorrection( Graphics2D g ) { + if( !useTextYCorrection() || !SystemInfo.IS_WINDOWS || !SystemInfo.IS_JAVA_9_OR_LATER ) + return 0; + + AffineTransform t = g.getTransform(); + double scaleY = t.getScaleY(); + if( scaleY < 1.25 ) + return 0; + + // 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 in 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 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; + } + + /** + * Applies Y correction and draws the given string at the specified location. + * The provided component is used to query text properties and anti-aliasing hints. + *

+ * Use this method instead of {@link Graphics#drawString(String, int, int)} for correct anti-aliasing. + *

+ * Replacement for {@code SwingUtilities2.drawString()}. + */ + public static void drawStringWithYCorrection( JComponent c, Graphics2D g, String text, int x, int y ) { + drawStringUnderlineCharAtWithYCorrection( c, g, text, -1, x, y ); + } + + /** + * Applies Y correction and draws the given string at the specified location underlining the specified character. + * The provided component is used to query text properties and anti-aliasing hints. + *

+ * Replacement for {@code SwingUtilities2.drawStringUnderlineCharAt()}. + */ + public static void drawStringUnderlineCharAtWithYCorrection( JComponent c, + Graphics2D g, String text, int underlinedIndex, int x, int y ) + { + float yCorrection = computeTextYCorrection( g ); + if( yCorrection != 0 ) { + g.translate( 0, yCorrection ); + JavaCompatibility.drawStringUnderlineCharAt( c, g, text, underlinedIndex, x, y ); + g.translate( 0, -yCorrection ); + } else + JavaCompatibility.drawStringUnderlineCharAt( c, g, text, underlinedIndex, x, y ); + } + + /** + * Creates a graphics object and applies Y correction to string drawing methods. + * If no Y correction is necessary, the passed in graphics object is returned. + */ + public static Graphics2D createGraphicsTextYCorrection( Graphics2D g ) { + float yCorrection = computeTextYCorrection( g ); + if( yCorrection == 0 ) + return g; + + return new Graphics2DProxy( g ) { + @Override + public void drawString( String str, int x, int y ) { + super.drawString( str, x, y + yCorrection ); + } + + @Override + public void drawString( String str, float x, float y ) { + super.drawString( str, x, y + yCorrection ); + } + + @Override + public void drawString( AttributedCharacterIterator iterator, int x, int y ) { + super.drawString( iterator, x, y + yCorrection ); + } + + @Override + public void drawString( AttributedCharacterIterator iterator, float x, float y ) { + super.drawString( iterator, x, y + yCorrection ); + } + }; + } } 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 new file mode 100644 index 00000000..1cd5a9e5 --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.java @@ -0,0 +1,223 @@ +/* + * Copyright 2020 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf.testing; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.geom.AffineTransform; +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import com.formdev.flatlaf.ui.FlatUIUtils; +import com.formdev.flatlaf.util.Graphics2DProxy; +import com.formdev.flatlaf.util.HiDPIUtils; +import com.formdev.flatlaf.util.JavaCompatibility; +import com.formdev.flatlaf.util.SystemInfo; +import net.miginfocom.swing.*; + +/** + * @author Karl Tauber + */ +public class FlatPaintingStringTest + extends JPanel +{ + public static void main( String[] args ) { + System.setProperty( "sun.java2d.uiScale", "1x" ); + + SwingUtilities.invokeLater( () -> { + FlatTestFrame frame = FlatTestFrame.create( args, "FlatPaintingStringTest" ); + + ToolTipManager.sharedInstance().setInitialDelay( 0 ); + ToolTipManager.sharedInstance().setDismissDelay( 10000 ); + + frame.showFrame( FlatPaintingStringTest::new ); + } ); + } + + FlatPaintingStringTest() { + initComponents(); + + if( !SystemInfo.IS_JAVA_9_OR_LATER ) { + add( new JLabel( "requires Java 9 or later" ) ); + return; + } + + 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( "0.25" ) ); + add( new JLabel( "0.5" ) ); + add( new JLabel( "0.625" ) ); + add( new JLabel( "0.75" ) ); + add( new JLabel( "0.875" ) ); + + YCorrectionFunction none = (g, scaleFactor) -> 0; + YCorrectionFunction flatlaf = (g, scaleFactor) -> HiDPIUtils.computeTextYCorrection( g ); + YCorrectionFunction oneQSysScale = (g, scaleFactor) -> -(0.25f * scaleFactor); + YCorrectionFunction halfSysScale = (g, scaleFactor) -> -(0.5f * 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; + + float[] scaleFactors = new float[] { 1f, 1.25f, 1.5f, 1.75f, 2f, 2.25f, 2.5f, 3f, 3.5f, 4f }; + + for( float scaleFactor : scaleFactors ) { + add( new JLabel( String.valueOf( scaleFactor ) ), "newLine" ); + + add( scaleFactor, none ); + add( scaleFactor, flatlaf ); + add( scaleFactor, oneQSysScale ); + add( scaleFactor, halfSysScale ); + add( scaleFactor, oneQ ); + add( scaleFactor, half ); + add( scaleFactor, fiveEights ); + add( scaleFactor, threeQ ); + add( scaleFactor, sevenEights ); + } + } + + private void add( float scaleFactor, YCorrectionFunction correctionFunction ) { + 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" ); + } + + private void initComponents() { + // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents + + //======== this ======== + setBorder(null); + setLayout(new MigLayout( + "insets dialog,hidemode 3", + // columns + "[fill]", + // rows + "[fill]")); + // JFormDesigner - End of component initialization //GEN-END:initComponents + } + + // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables + // JFormDesigner - End of variables declaration //GEN-END:variables + + private interface YCorrectionFunction { + float computeTextYCorrection( Graphics2D g, float scaleFactor ); + } + + //---- class Painter ------------------------------------------------------ + + public static class Painter + extends JLabel + { + private final float scaleFactor; + private final YCorrectionFunction correctionFunction; + private final float yOffset; + + public Painter( float scaleFactor, YCorrectionFunction correctionFunction, float yOffset ) { + super( "E" ); + this.scaleFactor = scaleFactor; + this.correctionFunction = correctionFunction; + this.yOffset = yOffset; + setBorder( new EmptyBorder( 2, 4, 2, 0 ) ); + } + + @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 ) ); + } + + @Override + protected void paintComponent( Graphics g ) { + Graphics2D g2 = (Graphics2D) g; + FlatUIUtils.setRenderingHints( g2 ); + + // simulate component y position at a fraction + if( scaleFactor > 1 ) + 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 ); + + int baseline = (int) Math.round( (insets.top + fm.getAscent()) * scaleFactor2 * scaleFactor ) - 1; + int topline = height2 - baseline - 1; + + g.setColor( Color.red ); + g.drawLine( 0, baseline, width2, baseline ); + g.drawLine( 0, topline, width2, topline ); + } ); + + // move x before scaling to have same left inset at all scale factors + g.translate( insets.left, 0 ); + + // scale + ((Graphics2D)g).scale( scaleFactor, scaleFactor ); + + // compute Y correction + float yCorrection = correctionFunction.computeTextYCorrection( g2, scaleFactor ); + + // create graphics that applies Y correction + Graphics2D cg = new Graphics2DProxy( g2 ) { + @Override + public void drawString( String str, int x, int y ) { + super.drawString( str, x, y + yCorrection ); + } + + @Override + public void drawString( String str, float x, float y ) { + super.drawString( str, x, y + yCorrection ); + } + }; + + // draw string + g.setColor( getForeground() ); + int y = insets.top + 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) ); + } + } + + private int scale( int value ) { + return Math.round( value * scaleFactor ); + } + } +} 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 new file mode 100644 index 00000000..4f04dbeb --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingStringTest.jfd @@ -0,0 +1,21 @@ +JFDML JFormDesigner: "7.0.2.0.298" Java: "14" encoding: "UTF-8" + +new FormModel { + contentType: "form/swing" + root: new FormRoot { + auxiliary() { + "JavaCodeGenerator.defaultVariableLocal": true + } + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "insets dialog,hidemode 3" + "$columnConstraints": "[fill]" + "$rowConstraints": "[fill]" + } ) { + name: "this" + "border": sfield com.jformdesigner.model.FormObject NULL_VALUE + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 0, 0 ) + "size": new java.awt.Dimension( 450, 300 ) + } ) + } +}