0

I'm using the Segoe UI system font on Windows for JLabel. Segoe UI does not support CJK characters, but instead of defaulting to something (e.g. Font.DIALOG) for those characters, it'll just display placeholders.

I would like to use Segoe UI on Windows because it looks better for Latin characters, but I don't want to lose support for displaying CJK characters altogether.

Is there a way to combine fonts? Prefer Segoe UI for Latin characters, but default to Font.DIALOG (or any other system default) to render readable characters at the very least.

e.g.

Font font = new Font("Segoe UI", Font.PLAIN, 12);
label.setFont(font);

CJK characters are not rendered

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
rednoah
  • 1,053
  • 14
  • 41
  • 2
    You can check whether a font supports a character and switch fonts if it doesn't. But not easy to make a label implementation with multiple fonts: https://stackoverflow.com/questions/4557597/gui-how-can-i-find-out-if-certain-unicode-characters-are-supported – Erwin Bolwidt Oct 31 '20 at 05:51
  • One practical solution would be to [find all the available fonts](https://alvinalexander.com/blog/post/jfc-swing/swing-faq-list-fonts-current-platform/) on your system and pick a more complete font to use. Use the example code in the link, but add Latin characters to the output. – Gilbert Le Blanc Oct 31 '20 at 09:44
  • Could you please add an example string with your special characters so we can test possible solutions? – MDK Oct 31 '20 at 12:10
  • Sure. Try with "日本" and "한국". – rednoah Nov 01 '20 at 03:19

2 Answers2

1

Using the idea suggested in this answer you can use a JLabels ability to display html to switch your font whenever needed:

public static String transformText(String text) {
    Font defaultFont = new JLabel().getFont();
    Font segoe = new Font("Segoe UI", defaultFont.getStyle(), defaultFont.getSize());
    StringBuilder sb = new StringBuilder(
        "<html><span style=\"font-family: " + defaultFont.getFamily() + "\">");
    boolean inDefaultFont = true;

    for (int i = 0; i < text.length(); i++) {
        char current = text.charAt(i);
        if (segoe.canDisplay(current) ^ inDefaultFont) {
            // font already correct
            sb.append(current);
        } else {
            // switch font
            sb.append("</span><span style=\"font-family: "
                + (inDefaultFont ? segoe.getFamily() : defaultFont.getFamily())
                + "\">" + current);
            inDefaultFont = !inDefaultFont;
        }
    }
    sb.append("</span></html>");

    System.out.println(sb.toString());
    return sb.toString();
}

Which results from the test input 日本 한국 01 abc to the output <html><span style="font-family: Dialog">日本</span><span style="font-family: Segoe UI"> </span><span style="font-family: Dialog">한국</span><span style="font-family: Segoe UI"> 01 abc</span></html> which gets displayed as desired in a JLabel.

MDK
  • 499
  • 3
  • 14
1

The easiest solution is to use StyleContext#getFont which will call the internal api FontUtilities#getCompositeFontUIResource(Font). This function will create a composite font which will use a fallback font for unsupported characters.

Note that you don't have control over which font will be used as a fallback.

Because the returned font is an UIResource we need to wrap it to avoid it being overwritten by the LookAndFeel. The custom NonUIResourceFont class is necessary because the constructor of Font which takes another Font is protected.

public static Font createFont(final String family, final int style, final int size) {
    return new NonUIResourceFont(StyleContext.getDefaultStyleContext().getFont(family, style, size));
}

public static class NonUIResourceFont extends Font {

    public NonUIResourceFont(final Font font) {
        super(font);
    }
}
weisj
  • 932
  • 6
  • 19