5

I've discovered a strange bug in JTextPane/JTextField (or somewhere in the font rendering underneath them). I wonder if anyone else has encountered the same and might have a solution for this.

I'm trying to display some "special" or rare characters in a JTextPane, and as soon as I change the font of the JTextField (which is completely unrelated to JTextPane!), the JTextPane "breaks up", and no longer displays these characters.

This should do a better job explaining what I mean:

public class Scrap {

public static void main(String[] args) {
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(200, 200);
    frame.setLayout(new BorderLayout());

    JTextField field = new JTextField();

    // Uncomment this line... and the JTextPane nor the JTextField
    // no longer display the characters
    // field.setFont(new Font("Arial", Font.PLAIN, 14));

    frame.add(field, BorderLayout.SOUTH);

    JTextPane textPane = new JTextPane();
    textPane.setFont(new Font("Arial", Font.PLAIN, 14));

    JScrollPane scroll = new JScrollPane(textPane);
    frame.add(scroll, BorderLayout.CENTER);

    StyledDocument doc = (StyledDocument) textPane.getDocument();

    try {
        String str = "◕ ◡◡ ◕";

        doc.insertString(doc.getLength(), str, null);

    } catch (BadLocationException e) {
        e.printStackTrace();
    }

    frame.setVisible(true);
    frame.setLocationRelativeTo(null);
}
}

EDIT: Here's a better example of the problem. It appears to be related to the size of the Font. Move the Slider and you'll notice how the size 14 does not render the glyphs, and 14 happens to be the size of the JTextField's Font.

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.*;
import java.awt.*;

public class Scrap {

public static void main(String[] args) {
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(600, 200);
    frame.setLayout(new BorderLayout());

    final JTextField field = new JTextField(10);

    final JTextPane textPane = new JTextPane();

    StyledDocument doc = (StyledDocument) textPane.getDocument();

    JPanel panel = new JPanel();
    frame.add(panel, BorderLayout.SOUTH);

    // Set the Font of the JTextField, and the JTextPane
    // no longer displays the text of that size correctly...

    int changeMe = 14;

    field.setFont(new Font("Tahoma", Font.PLAIN, changeMe));

    // If we change the Font Family, the problem goes away...
    // field.setFont(new Font("Dialog", Font.PLAIN, 14));

    panel.add(field);

    final JLabel label = new JLabel();

    final JSlider slider = new JSlider(6, 32);
    slider.addChangeListener(new ChangeListener() {
        @Override
        public void stateChanged(ChangeEvent e) {
            textPane.setFont(new Font("Tahoma", Font.PLAIN, slider.getValue()));

            textPane.selectAll();

            SimpleAttributeSet attr = new SimpleAttributeSet();
            StyleConstants.setFontSize(attr, slider.getValue());
            textPane.setCharacterAttributes(attr, true);

            label.setText("" + slider.getValue());
        }
    });

    slider.setValue(14);

    panel.add(slider);

    panel.add(label);

    JScrollPane scroll = new JScrollPane(textPane);
    frame.add(scroll, BorderLayout.CENTER);

    Style s = doc.addStyle("test", null);

    try {
        String str = "◕ ◡◡ ◕";

        doc.insertString(doc.getLength(), str, doc.getStyle("test"));

    } catch (BadLocationException e) {
        e.printStackTrace();
    }

    frame.setVisible(true);
    frame.setLocationRelativeTo(null);
}
}
n00bster
  • 2,525
  • 2
  • 16
  • 15
  • 1
    +1 for the copy/paste example – Duncan Jones Oct 15 '12 at 19:29
  • I read your code way too fast - that is *strange* behaviour. I've deleted my answer for now. – Duncan Jones Oct 15 '12 at 19:49
  • @Duncan Jones you can to undelete your answer here, and to use my code, than I'll delete my answer here – mKorbel Oct 15 '12 at 20:20
  • @mKorbel Is it possible you've also misunderstood the original question? The issue is not about Fonts and glyphs, the question is about why changing the font on *one* component can mess with the character display on two components. – Duncan Jones Oct 15 '12 at 20:23
  • @n00bster maybe better could be to use \uXXXX value – mKorbel Oct 15 '12 at 20:23
  • @mDuncan Jones right I'll simulating your shadowed issue, – mKorbel Oct 15 '12 at 20:30
  • Glad I found this. I was debugging for hours to find the problem, but it seems my code was ok after all. Did you came across a good solution for this? The config change seems like a hack to me. I would love tho find something more accurate to allow my program to be used by a user. I discovered, that in fact the jTextPane can display those symbols for any font-size. it just for some reason bugs out if you change it on the fly... – Haeri Mar 06 '15 at 01:19

3 Answers3

2

I had a similar problem when I tried to make an application that would have to support multiple languages (including languages with "non-standard" characters, such as chinese). I used to set the font of my widgets to Arial, and had the problem. The following solution fixed my problem, but it might not fix yours.

Java has a fallback mechanism whenever it encounters characters from specific charsets it can't display. It can be configured using the fontconfig.properties file, which is provided with the JRE (the file is originally provided as "fontconfig.properties.src", you have to manually rename it).

When you force a font that isn't among Dialog, Serif, SansSerif, Monospaced or DialogInput, Java has no way to use a different charset if the current one (in your case Arial) cannot represent the character (or glyph) you are trying to draw on screen.

If you look at the fontconfig.properties.src file, you will see that is has many entries for many types of fonts (e.g. Dialog.plain, Serif.bold, etc). These are the actual fallback fonts to use whenever the fonts above cannot display a specific glyph. So, settings your widgets' font to let's say Font.DIALOG will allow Java to try a list of fonts to display your characters.

More information is available on Oracle's website (here for Java 7). Note that the use of fontconfig.properties isn't officially supported by Oracle.

Laf
  • 7,965
  • 4
  • 37
  • 52
  • Although not perhaps the ideal solution to the problem, this worked. I don't actually use Arial in my application, but Consolas. I set the default monospaced font to Consolas in fontconfig.properties, and the problem went away. However, there was a slight gap between some of the rows in the JTextPane afterwards, and I don't know what caused it... I'll see if I can figure it out. – n00bster Oct 16 '12 at 13:14
  • @n00bster I wasn't sure either that it was the ideal solution, but at the same time, I never really found another solution which allows you to define fallback fonts whenever the current font cannot render a specific glyph. While this file isn't officially supported, it works pretty well, and is simple enough to use. – Laf Oct 16 '12 at 13:24
2

no deepest idea whats happened, and not why, but have to set

textPane.setContentType("text/html");

enter image description here

import java.awt.BorderLayout;
import java.awt.Font;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.StyledDocument;

public class Scrap {


    public  Scrap() {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(200, 200);
        frame.setLayout(new BorderLayout());
        JTextField field = new JTextField();
        // Uncomment this line... and the JTextPane nor the JTextField
        // no longer display the characters
        field.setFont(new Font("Arial", Font.PLAIN, 14));
        frame.add(field, BorderLayout.SOUTH);
        JTextPane textPane = new JTextPane();
        textPane.setFont(new Font("Arial", Font.PLAIN, 14));
        textPane.setContentType("text/html");
        JScrollPane scroll = new JScrollPane(textPane);
        frame.add(scroll, BorderLayout.CENTER);
        StyledDocument doc = (StyledDocument) textPane.getDocument();
        try {
            String str = "\uD0180, \u2460, \u2760, \u2380, \u2C60, \u5000, \u03E0";
            doc.insertString(doc.getLength(), str, null);
        } catch (BadLocationException e) {
            e.printStackTrace();
        }
        frame.setVisible(true);
        frame.setLocationRelativeTo(null);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                Scrap fs = new Scrap();
            }
        });
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • @StanislavL can you please to explain (better could be to send answer ) whats happened with (Plain???)Document and why – mKorbel Oct 16 '12 at 09:49
0

simple simulations Fonts and Glyps,

import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;

public class Fonts implements Runnable {

    private String[] fnt;
    private JFrame frm;
    private JScrollPane jsp;
    private JTextPane jta;
    private JTextField field;
    private int width = 450;
    private int height = 300;
    private GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    private StyledDocument doc;
    private MutableAttributeSet mas;
    private int cp = 0;
    private Highlighter.HighlightPainter cyanPainter = new DefaultHighlighter.DefaultHighlightPainter(Color.cyan);
    private Highlighter.HighlightPainter redPainter = new DefaultHighlighter.DefaultHighlightPainter(Color.red);
    private Highlighter.HighlightPainter whitePainter = new DefaultHighlighter.DefaultHighlightPainter(Color.white);
    private int _count = 0;
    private int _lenght = 0;

    public Fonts() {
        jta = new JTextPane();
        doc = jta.getStyledDocument();
        jsp = new JScrollPane(jta);
        jsp.setPreferredSize(new Dimension(height, width));
        frm = new JFrame("awesome");
        frm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frm.setLayout(new BorderLayout());
        frm.add(jsp, BorderLayout.CENTER);
        field = new JTextField();
        field.setText("\u2460, \u2760, \u2380, \u2C60, \u5000, \u03E0");
        frm.add(field, BorderLayout.SOUTH);
        frm.setLocation(100, 100);
        frm.pack();
        frm.setVisible(true);
        jta.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        fnt = ge.getAvailableFontFamilyNames();
        mas = jta.getInputAttributes();
        new Thread(this).start();
    }

    @Override
    public void run() {
        for (int i = 0; i < fnt.length; i++) {
            StyleConstants.setBold(mas, false);
            StyleConstants.setItalic(mas, false);
            StyleConstants.setFontFamily(mas, fnt[i]);
            StyleConstants.setFontSize(mas, 16);
            //dis(fnt[i]);
            dis("\u2460, \u2760, \u2380, \u2C60, \u5000, \u03E0");
            field.setFont(new Font(fnt[i], Font.PLAIN, 14));
            try {
                Thread.sleep(450);
            } catch (Exception e) {
                e.printStackTrace();
            }
            /*StyleConstants.setBold(mas, true);
            dis(fnt[i] + " Bold");
            try {
                Thread.sleep(75);
            } catch (Exception e) {
                e.printStackTrace();
            }
            StyleConstants.setItalic(mas, true);
            dis(fnt[i] + " Bold & Italic");
            try {
                Thread.sleep(75);
            } catch (Exception e) {
                e.printStackTrace();
            }
            StyleConstants.setBold(mas, false);
            dis(fnt[i] + " Italic");
            try {
                Thread.sleep(75);
            } catch (Exception e) {
                e.printStackTrace();
            }*/
        }
        jta.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    }

    public void dis(String s) {
        _count++;
        _lenght = jta.getText().length();
        try {
            doc.insertString(cp, s, mas);
            doc.insertString(cp, "\n", mas);
        } catch (Exception bla_bla_bla_bla) {
            bla_bla_bla_bla.printStackTrace();
        }
        if (_count % 2 == 0) {
            try {
                jta.getHighlighter().addHighlight(1, _lenght - 1, cyanPainter);
            } catch (BadLocationException bla_bla_bla_bla) {
            }
        } else if (_count % 3 == 0) {
            try {
                jta.getHighlighter().addHighlight(1, _lenght - 1, redPainter);
            } catch (BadLocationException bla_bla_bla_bla) {
            }
        } else {
            try {
                jta.getHighlighter().addHighlight(1, _lenght - 1, whitePainter);
            } catch (BadLocationException bla_bla_bla_bla) {
            }
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                Fonts fs = new Fonts();
            }
        });
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319