15

I'm trying list all of the monospaced fonts available on a user's machine. I can get all of the font families in Swing via:

String[] fonts = GraphicsEnvironment.getLocalGraphicsEnvironment()
                                    .getAvailableFontFamilyNames();

Is there a way to figure out which of these are monospaced?

Thanks in advance.

Michael Myers
  • 188,989
  • 46
  • 291
  • 292

5 Answers5

7

A simpler method that doesn't require making a BufferedImage to get a Graphics object etc.:

    Font fonts[] = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
    List<Font> monoFonts1 = new ArrayList<>();

    FontRenderContext frc = new FontRenderContext(null, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
    for (Font font : fonts) {
        Rectangle2D iBounds = font.getStringBounds("i", frc);
        Rectangle2D mBounds = font.getStringBounds("m", frc);
        if (iBounds.getWidth() == mBounds.getWidth()) {
            monoFonts1.add(font);
        }
    }
swpalmer
  • 3,890
  • 2
  • 23
  • 31
  • 1
    Thanks; Great pragmatic solution! I added a line 'Font testFont = new Font(font.getName(), font.getStyle(), 12)' and testing on this font instead (i.e. size 12), since the fonts coming out of "getAllFonts()" are all with size 1. This made a difference: From 354 fronts (when size 1) to 303 fonts (with the size 12). Btw, I added a confirmation-test with " " (space) - didn't budge from 303 fonts as result. – stolsvik Jan 13 '19 at 15:34
6

You could use the getWidths() method of the FontMetrics class. According to the JavaDoc:

Gets the advance widths of the first 256 characters in the Font. The advance is the distance from the leftmost point to the rightmost point on the character's baseline. Note that the advance of a String is not necessarily the sum of the advances of its characters.

You could use the charWidth(char) method of the FontMetrics class. For example:

Set<String> monospaceFontFamilyNames = new HashSet<String>();

GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
String[] fontFamilyNames = graphicsEnvironment.getAvailableFontFamilyNames();

BufferedImage bufferedImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics graphics = bufferedImage.createGraphics();

for (String fontFamilyName : fontFamilyNames) {
    boolean isMonospaced = true;

    int fontStyle = Font.PLAIN;
    int fontSize = 12;
    Font font = new Font(fontFamilyName, fontStyle, fontSize);
    FontMetrics fontMetrics = graphics.getFontMetrics(font);

    int firstCharacterWidth = 0;
    boolean hasFirstCharacterWidth = false;
    for (int codePoint = 0; codePoint < 128; codePoint++) { 
        if (Character.isValidCodePoint(codePoint) && (Character.isLetter(codePoint) || Character.isDigit(codePoint))) {
            char character = (char) codePoint;
            int characterWidth = fontMetrics.charWidth(character);
            if (hasFirstCharacterWidth) {
                if (characterWidth != firstCharacterWidth) {
                    isMonospaced = false;
                    break;
                }
            } else {
                firstCharacterWidth = characterWidth;
                hasFirstCharacterWidth = true;
            }
        }
    }

    if (isMonospaced) {
        monospaceFontFamilyNames.add(fontFamilyName);
    }
}

graphics.dispose();
Adam Paynter
  • 46,244
  • 33
  • 149
  • 164
  • Thanks for extending your example code - it wasn't obvious how to get FontMetrics for a given Font. Still, for some reason this doesn't find any monospaced fonts on my machine (OS X)... – Jonik May 28 '09 at 18:23
  • I tested with one font that's known to be monospaced (java.awt.Font[family=Andale Mono,name=Andale Mono,style=plain,size=12]). For this font getWidths() returns numbers like: 7 0 4 4 4 4 4 4 0 7 7 4 7 7 4 ... :-/ – Jonik May 28 '09 at 18:38
  • Hmm, interesting; this code finds *one* monospaced font on this system: "Lucida Sans Typewriter". But fonts like "Andale Mono" and "Courier New" still fail the test (both of these at codepoint 1). When printed, the failing characters look a lot like whitespace, so I wonder if the whitespace check works correctly... – Jonik May 28 '09 at 19:36
  • thanks adam. works on my windows machine. I'll check it on my mac at home –  May 28 '09 at 20:52
  • Jonik: Maybe you could change the codePoint check to something like Character.isLetter(...) || Character.isDigit(...)? – Adam Paynter May 28 '09 at 23:33
  • Adam, thanks - after that change, it found 8 monospaced fonts on the Mac, which is presumably all of them. Also, for the record, on my Linux machine at work, the version with !Character.isWhitespace() finds 23 monospaced fonts, while with (Character.isLetter() || Character.isDigit()) it finds 29 fonts. Perhaps you could change the example code as it seems isWhitespace() cannot always be trusted... But +1 anyways! – Jonik May 29 '09 at 06:56
3

Compare the drawn lengths of several characters (m, i, 1, . should be a good set).

For monospaced fonts they will all be equal, for variable width fonts they won't.

Benjamin Autin
  • 4,143
  • 26
  • 34
1

According to this response, Java doesn't know too much about underlying font details, so you'd have to do some comparisons of the font's dimensions.

Community
  • 1
  • 1
yalestar
  • 9,334
  • 6
  • 39
  • 52
1

Probably not applicable for your case, but if you simply want to set the font to a monospaced font, use the logical font name:

Font mono = new Font("Monospaced", Font.PLAIN, 12);

This will be a guaranteed monospaced font on your system.

James Van Huis
  • 5,481
  • 1
  • 26
  • 25