tags:

views:

1378

answers:

3

Is there a way in SWT to get a monospaced font simply, that works across various operating systems?

For example. this works on Linux, but not Windows:


Font mono = new Font(parent.getDisplay(), "Mono", 10, SWT.NONE);

or do I need to have a method that tries loading varying fonts (Consolas, Terminal, Monaco, Mono) until one isn't null? Alternatively I could specify it in a properties file on startup.

I tried getting the system font from Display, but that wasn't monospaced.

+3  A: 

According to the section on Font Configuration Files in the JDK documentation of Internationalization Support-related APIs, the concept of Logical Fonts is used to define certain platform-independent fonts which are mapped to physical fonts in the default font configuration files:

The Java Platform defines five logical font names that every implementation must support: Serif, SansSerif, Monospaced, Dialog, and DialogInput. These logical font names are mapped to physical fonts in implementation dependent ways.

So in your case, I'd try

Font mono = new Font(parent.getDisplay(), "Monospaced", 10, SWT.NONE);

to get a handle to the physical monospaced font of the current platform your code is running on.

Edit: It seems that SWT doesn't know anything about logical fonts (Bug 48055 on eclipse.org describes this in detail). In this bug report a hackish workaround was suggested, where the name of the physical font may be retrieved from an AWT font...

fhe
It appears (from testing) that SWT doesn't adhere to these guidelines (which possibly should go in as a bug), but thanks for your answer which is surely applicable for AWT and Swing.
JeeBee
You're right. I've edited my post accordingly and gave some more details on this. Good luck!
fhe
+4  A: 

To the best of my knowledge, the AWT API does not expose underlying Font information. If you can get to it, I would expect it to be implementation dependent. Certainly, comparing the font mapping files in a couple of JRE lib directories, I can see that they are not defined in a consistent way.

You could load your own fonts, but that seems a little wasteful given you know the platform comes with what you need.

This is a hack that loads a JRE font:

private static Font loadMonospacedFont(Display display) {
 String jreHome = System.getProperty("java.home");
 File file = new File(jreHome, "/lib/fonts/LucidaTypewriterRegular.ttf");
 if (!file.exists()) {
  throw new IllegalStateException(file.toString());
 }
 if (!display.loadFont(file.toString())) {
  throw new IllegalStateException(file.toString());
 }
 final Font font = new Font(display, "Lucida Sans Typewriter", 10,
   SWT.NORMAL);
 display.addListener(SWT.Dispose, new Listener() {
  public void handleEvent(Event event) {
   font.dispose();
  }
 });
 return font;
}

It works on IBM/Win32/JRE1.4, Sun/Win32/JRE1.6, Sun/Linux/JRE1.6, but is a pretty fragile approach. Depending on your needs for I18N, it could be trouble there too (I haven't checked).

Another hack would be to test the fonts available on the platform:

public class Monotest {

    private static boolean isMonospace(GC gc) {
     final String wide = "wgh8";
     final String narrow = "1l;.";
     assert wide.length() == narrow.length();
     return gc.textExtent(wide).x == gc.textExtent(narrow).x;
    }

    private static void testFont(Display display, Font font) {
     Image image = new Image(display, 100, 100);
     try {
      GC gc = new GC(image);
      try {
       gc.setFont(font);
       System.out.println(isMonospace(gc) + "\t"
         + font.getFontData()[0].getName());
      } finally {
       gc.dispose();
      }
     } finally {
      image.dispose();
     }
    }

    private static void walkFonts(Display display) {
     final boolean scalable = true;
     for (FontData fontData : display.getFontList(null, scalable)) {
      Font font = new Font(display, fontData);
      try {
       testFont(display, font);
      } finally {
       font.dispose();
      }
     }
    }

    public static void main(String[] args) {
     Display display = new Display();
     try {
      walkFonts(display);
     } finally {
      display.dispose();
     }
    }

}

This probably isn't a good approach as it may leave you exposed to locale issues. Besides, you don't know if the first monospaced font you come across isn't some windings icon set.

The best approach may just be to take your best guess based on a font/locale mapping whitelist and make sure that the users can easily reconfigure the UI to suit themselves via FontDialog.

McDowell
Thanks for the detailed answer. I believe the best option is as you say - specific a bland default like Courier that is available on all platforms, and allow the user to change this property with a font configuration dialog.
JeeBee
Is there any way to load 'Font' from Java resource or 'InputStream' rather than file path in SWT?
parxier
@parxier - not that I am aware of. This would be better as a new question.
McDowell
Thanks, new question (http://stackoverflow.com/questions/2734106/how-to-load-font-from-inputstream-in-swt) created.
parxier
A: 

If you want just a Monospaced font use "Courier" => new Font(display, "Courier", 10, SWT.NORMAL)