Search code examples
javaswingjtextareajtextpanenimbus

JTextArea and JTextPane font rendering


It seems that JTextPane and JTextArea are rendering fonts differently. It is barely noticeable, but I still want to know why is it there.

I have set up a SSCCE, but you can't really see it. Best method would be to run a program with JTextArea, then change the code to JTextPane and run it again. They should overlap in a way that when you change from one window to the other (with alt+ tab) the difference can be seen. You will have to provide your own *.ttf file.

Why is that? Is there a way to force the JTextPane to render the text the same way as JTextArea does?

SSCCE:

import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.io.IOException;

import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.UnsupportedLookAndFeelException;


public class Main
{
public static void main(String[] args)
{
    for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels())
    {
        if ("Nimbus".equals(info.getName()))
        {
            try
            {
                UIManager.setLookAndFeel(info.getClassName());
            }
            catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e)

            {
                System.out.println("No Nimbus!");
            }

            break;
        }
    }

    JFrame a = new JFrame("Test");
    a.setSize(600, 900);
    a.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    a.getContentPane().setLayout(new BoxLayout(a.getContentPane(), BoxLayout.Y_AXIS));

    Font d = null;

    try
    {
        d = Font.createFont(Font.TRUETYPE_FONT, Main.class.getResourceAsStream("calibri_bold.ttf"));
        d = d.deriveFont(23f);
    }

    catch (FontFormatException | IOException e1)
    {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    }

    final JTextPane b = new JTextPane();



    b.setBorder(new JTextArea().getBorder());
    b.setFont(d);

    b.addFocusListener(new FocusListener() {

        @Override
        public void focusGained(FocusEvent arg0) {
            b.repaint();
        }

        @Override
        public void focusLost(FocusEvent arg0) {
            b.repaint();
        }

    });
    b.setText("It seems that JTextPane and JTextArea are rendering fonts differently. It is barely noticeable, but I still want to know why is it there. I have set up a SSCCE, but you can't really see it. Best method would be to run a program with JTextArea, then change the code to JTextPane and run it again. They should overlap in a way when you change from one window to the other (with alt+ tab) the difference can be seen. Why is that? Is there a way to force the JTextPane to render the text the same way as JTextArea does?");
    a.getContentPane().add(b);

    final JTextArea c = new JTextArea();


    c.setFont(d);
    c.setText("It seems that JTextPane and JTextArea are rendering fonts differently. It is barely noticeable, but I still want to know why is it there. I have set up a SSCCE, but you can't really see it. Best method would be to run a program with JTextArea, then change the code to JTextPane and run it again. They should overlap in a way when you change from one window to the other (with alt+ tab) the difference can be seen. Why is that? Is there a way to force the JTextPane to render the text the same way as JTextArea does?");
    c.setLineWrap(true);
    c.setWrapStyleWord(true);
    a.getContentPane().add(c);

    a.setVisible(true);
}
}

Solution

    • Nimbus L&F has a few awfull issues, we can call those issues as a Bugs

    • JTextArea and another JComponents has frozen some of Keys in UIManager

    • you can to UIManager.getLookAndFeel().uninitialize(); for most of Keys they are freeze, but some of them are able to resist against all changes, hacks, woodoo, but Font for JTextArea isn't this case

    • note you need to override all keys 3 times, not as is demonstratedin my code, see commented //...Defaults.put("TextPane.font", res);

    • initial changes in UIManager from main class

    enter image description here

    • after UIManager.getLookAndFeel().uninitialize(); is called

    enter image description here

    import java.awt.Font;
    import java.awt.GridLayout;
    import java.awt.event.ActionEvent;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javax.swing.AbstractAction;
    import javax.swing.Action;
    import javax.swing.JFrame;
    import javax.swing.JScrollPane;
    import javax.swing.JTextArea;
    import javax.swing.JTextPane;
    import javax.swing.LookAndFeel;
    import javax.swing.SwingUtilities;
    import javax.swing.UIDefaults;
    import javax.swing.UIManager;
    import javax.swing.UIManager.LookAndFeelInfo;
    import javax.swing.UnsupportedLookAndFeelException;
    import javax.swing.plaf.FontUIResource;
    
    public class Main {
    
        private JFrame frame = new JFrame("Test");
        private JTextPane textPane = new JTextPane();
        private JTextArea textArea = new JTextArea();
        private String str = "It seems that JTextPane and JTextArea are rendering fonts differently. "
                + "It is barely noticeable, but I still want to know why is it there. "
                + "I have set up a SSCCE, but you can't really see it. Best method would "
                + "be to run a program with JTextArea, then change the code to JTextPane "
                + "and run it again. They should overlap in a way when you change from one "
                + "window to the other (with alt+ tab) the difference can be seen. "
                + "Why is that? Is there a way to force the JTextPane to render the "
                + "text the same way as JTextArea does?";
        private javax.swing.Timer timer = null;
        final Font fnt = new Font("Brodway", Font.BOLD, 10);
    
        public Main() {
            textPane.setBorder(new JTextArea().getBorder());
            textPane.setText(str);
            textArea.setText(str);
            textArea.setLineWrap(true);
            textArea.setWrapStyleWord(true);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setLayout(new GridLayout(0, 1));
            frame.add(new JScrollPane(textPane));
            frame.add(new JScrollPane(textArea));
            frame.setVisible(true);
            frame.setSize(400, 400);
            start();
        }
    
        private void start() {
            timer = new javax.swing.Timer(2250, updateCol());
            timer.setRepeats(false);
            timer.start();
        }
    
        public Action updateCol() {
            return new AbstractAction("text load action") {
                private static final long serialVersionUID = 1L;
    
                @Override
                public void actionPerformed(ActionEvent e) {
    
                    try {
                        LookAndFeel lnf = UIManager.getLookAndFeel().getClass().newInstance();
                        final FontUIResource res = new FontUIResource(fnt);
                        UIDefaults uiDefaults = lnf.getDefaults();
                        //uiDefaults.put("TextPane.font", res);
                        uiDefaults.put("TextArea.font", res);
                        UIManager.getLookAndFeel().uninitialize();
                        UIManager.setLookAndFeel(lnf);
                    } catch (InstantiationException ex) {
                        Logger.getLogger(SystemFontDisplayer.class.getName()).log(Level.SEVERE, null, ex);
                    } catch (IllegalAccessException ex) {
                        Logger.getLogger(SystemFontDisplayer.class.getName()).log(Level.SEVERE, null, ex);
                    } catch (UnsupportedLookAndFeelException ex) {
                        Logger.getLogger(SystemFontDisplayer.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    UIDefaults defaults = UIManager.getDefaults();
                    final FontUIResource res = new FontUIResource(fnt);
                    //defaults.put("TextPane.font", res);
                    defaults.put("TextArea.font", res);
                    SwingUtilities.updateComponentTreeUI(frame);
                }
            };
        }
    
        public static void main(String[] args) {
            final FontUIResource res = new FontUIResource(new Font("Algerian", Font.BOLD, 10));
            for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    try {
                        UIManager.setLookAndFeel(info.getClassName());
                        UIDefaults defaults = UIManager.getDefaults();
                        defaults.put("TextPane.font", res);
                        defaults.put("TextArea.font", res);
                    } catch (ClassNotFoundException | InstantiationException |
                            IllegalAccessException | UnsupportedLookAndFeelException e) {
                        System.out.println("No Nimbus!");
                    }
                    break;
                }
            }
            java.awt.EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    new Main();
                }
            });
        }
    }