n00bster
n00bster

Reputation: 2595

JTextPane/JTextField strange behaviour with rare characters

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);
}
}

Upvotes: 5

Views: 2899

Answers (3)

mKorbel
mKorbel

Reputation: 109813

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();
            }
        });
    }
}

Upvotes: 2

mKorbel
mKorbel

Reputation: 109813

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();
            }
        });
    }
}

Upvotes: 0

Laf
Laf

Reputation: 8195

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.

Upvotes: 2

Related Questions