Question

I'm using g.drawString(str, x, y) to draw a String with a Graphics2D object g. The current font of g does not cover all the characters of str (I have e.g. Chinese chars in there). On Mac OS X, a fallback font seems to be automatically used, but not on Windows, where black square outlines appear instead of the wanted characters.

  • Why is the behavior different depending on the platform?
  • How do I specify a fallback font (or several fallback fonts) in case of missing characters?

(For instance, one of the nice fonts there.)

Update/More Info

So, the original font that doesn't support all characters is not one of the JVM's logical fonts, but is a bundled font that comes with my app and was obtained with Font.createFont(). So, adding fonts to the JRE's lib/fonts/fallback folder doesn't work here.

Was it helpful?

Solution

We could attribute string to switch font on "bad" symbols and use Graphics2D.drawString(AttributedCharacterIterator iterator, int x, int y) to render result. Advantage: this will work with any font. Drawback: without some sort of caching of intermediate objects this will work slower and dirtier.

So, i suggest using AttributedString with main font attribute on whole string:

AttributedString astr = new AttributedString(text);
astr.addAttribute(TextAttribute.FONT, mainFont, 0, textLength);

and with fallback font on specific parts:

astr.addAttribute(TextAttribute.FONT, fallbackFont, fallbackBegin, fallbackEnd);

The rendering itself:

g2d.drawString(astr.getIterator(), 20, 30);

The result (Physical "Segoe Print" as main font, logical "Serif" as fallback):

enter image description here

Complete supposed-to-be-SSCCE code:

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.TextAttribute;
import java.text.AttributedString;

import javax.swing.JComponent;
import javax.swing.JFrame;

public class FontTest extends JFrame {

    public FontTest() {
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        getContentPane().setLayout(new BorderLayout());
        getContentPane().add(new TestStringComponent());
        pack();
    }

    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new FontTest().setVisible(true);
            }
        });
    }
}

class TestStringComponent extends JComponent {

    protected void paintComponent(Graphics g) {
        Graphics2D g2d = (Graphics2D) g;
        g.setColor(getBackground());
        g.fillRect(0, 0, getWidth(), getHeight());

        g.setColor(getForeground());

        Font mainFont = new Font("Segoe Print", Font.PLAIN, 25);
        Font fallbackFont = new Font("Serif", Font.PLAIN, 25);

        String s = "Test 漢鼎繁古印 Test 漢鼎繁古印 Test";

        g2d.drawString(createFallbackString(s, mainFont, fallbackFont).getIterator(), 20, 30);
    }

    public Dimension getPreferredSize() {
        return new Dimension(500, 40);
    }

    private AttributedString createFallbackString(String text, Font mainFont, Font fallbackFont) {
        AttributedString result = new AttributedString(text);

        int textLength = text.length(); 
        result.addAttribute(TextAttribute.FONT, mainFont, 0, textLength);

        boolean fallback = false;
        int fallbackBegin = 0;
        for (int i = 0; i < text.length(); i++) {
            boolean curFallback = !mainFont.canDisplay(text.charAt(i));
            if (curFallback != fallback) {
                fallback = curFallback;
                if (fallback) {
                    fallbackBegin = i;
                } else {
                    result.addAttribute(TextAttribute.FONT, fallbackFont, fallbackBegin, i);
                }
            }
        }
        return result;
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top