You can exploit the fact that JComponents are able to display HTML, and override the font for characters which the JComponent's font cannot display, by placing those characters in a <span>
:
import java.util.Formatter;
import java.awt.Font;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import sun.font.FontUtilities;
public class TestFonts implements Runnable
{
/**
* Replaces plain text meant to be displayed in a JComponent with
* HTML that forces the font to Dialog for any characters which the
* specified font cannot natively display.
*
* @param originalText text to transform to HTML, with forced fonts
* where needed
* @param font default font which will be used to display text
*
* @return HTML version of original text, which forces fonts where
* necessary to ensure all characters will be displayed
*/
static String toCompositeFontText(String originalText,
Font font) {
Formatter html = new Formatter();
html.format("%s", "<html><body style='white-space: nowrap'>");
boolean fontOverride = false;
int len = originalText.length();
for (int i = 0; i < len; i = originalText.offsetByCodePoints(i, 1)) {
int c = originalText.codePointAt(i);
if (font.canDisplay(c)) {
if (fontOverride) {
html.format("%s", "</span>");
fontOverride = false;
}
} else {
if (!fontOverride) {
html.format("<span style='font-family: \"%s\"'>",
Font.DIALOG);
fontOverride = true;
}
}
if (c == '<' || c == '>' || c == '&' || c < 32 || c >= 127) {
html.format("&#%d;", c);
} else {
html.format("%c", c);
}
}
if (fontOverride) {
html.format("%s", "</span>");
}
html.format("%s", "</body></html>");
return html.toString();
}
/**
* Replaces text of the specified JLabel with HTML that contains the
* same text, but forcing the font to Dialog for any characters which
* the JLabel's current font cannot display.
*
* @param label JLabel whose text will be adjusted and replaced
*/
static void adjustText(JLabel label) {
label.setText(toCompositeFontText(label.getText(), label.getFont()));
}
@Override
public void run()
{
Font font = new Font("Arial", Font.PLAIN, 20);
JLabel label1 = new JLabel("Before \u30C6\u30B9\u30C8");
label1.setFont(font);
JLabel label2 = new JLabel("After \u30C6\u30B9\u30C8");
label2.setFont(FontUtilities.getCompositeFontUIResource(font));
JLabel label3 = new JLabel("Corrected \u30C6\u30B9\u30C8");
label3.setFont(font);
adjustText(label3);
JFrame frame = new JFrame("Font Test");
frame.setLayout(new GridLayout(3, 1));
frame.add(label1);
frame.add(label2);
frame.add(label3);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new TestFonts());
}
}
Update:
In addition, you can monitor a label's text
property, to make this happen automatically:
label.addPropertyChangeListener("text", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
String text = (String) event.getNewValue();
if (text != null && !text.startsWith("<html>")) {
adjustText((JLabel) event.getSource());
}
}
});
The obvious drawback is that there is no (easy) way to adjust text which already starts with <html>
. (Actually I'm fairly sure even that could be done by loading the label's text as an HTMLDocument.)