A Swing/JNI Tale (Fonts Book)

You can download Jype 0.7 or its source code (GPL).

A few minutes ago I was discussing with Scott Delap about various things when JNI popped in. This reminded me of an application I wrote almost two years ago for a friend of mine. It is a fonts viewer written in Swing and called Jype. Despite its flaws, that is being commented in french and licensed under the GPL, Jype is an interesting tool for several reasons. But let me first introduce it to you:

This screenshot shows the main and only window of Jype. The bottom panel contains a combo box to choose a font and several buttons. These buttons are used to navigate back and forth in the list, split the window vertically and horizontally to easily compare many fonts at once, unsplit the window and export the current font view as a PNG.

Here you can see Jype running on MacOS X. Everything works fine but you can notice a slight difference in the combo box: the icons have disappeared. These icons are either red or blue. The red ones indicate TTF fonts whereas the blue ones indicate Type1 fonts.

And here you see the list of available fonts. Whenever you choose one, the view changes to display it in different sizes. The view provides a few letters, capitalized or not, and numbers. The view also shows a larger chunk of text to better see how the font looks like when used in a document.

The mot powerful feature of Jype is the splitted view. You can split indefinitely, horizontally and vertically. This is very useful to compare fonts. The larger your screen, the better. Note you can unsplit to get back to the single view very quickly.

Finally, this is what the PNG export feature produces. This clean picture is drawn with Java2D and can be stored away for future reference.

While very useful, this tool still lacks a lot of features like the ability to search through the fonts base or to load a font from a specified file. Anyway, what I'd like to talk about today is the JNI layer of this application. I use it to know whether a font is TTF or Type1. Some people, like my friend, seem to care a lot about this. Here is the source code on the Java side:

public class FontTypeSupport
{
  private static boolean isValid = true;

  static
  {
    try
    {
      System.loadLibrary("JypeFontTypeSupport");
    } catch (Exception e) {
      isValid = false;
    } catch (Error err) {
      isValid = false;
    }
  }

  public static        boolean isValid() { return isValid; }
  public static native boolean isTrueType(Font f);
  public static native boolean isType1(Font f);
}

Each call to isTrueType() or isType1() must be preceded by a call to isValid(). On MacOS X for instance, isValid() returns false. Now here is the C code responsible for driving the class FontTypeSupport:

#include <windows.h>
#include <jni.h>
#include <string.h>
#include "JypeFontTypeSupport.h"

static byte bCallbackResult = 0;

static BOOL isOfType(JNIEnv* env, jclass c, jobject font, FONTENUMPROC proc)
{
  jclass cls    = (*env)->GetObjectClass(env, font);
  jmethodID mid = (*env)->GetMethodID(env, cls, "getPSName", "()Ljava/lang/String;");
  jstring str   = (*env)->CallObjectMethod(env, font, mid);
  
  const char* szFontPSName = (*env)->GetStringUTFChars(env, str, 0);
  HWND hwndDesktop         = FindWindow("SysListView32", "FolderView");
  HDC  hDCDesktop          = GetDC(hwndDesktop);

  bCallbackResult = 0;
  EnumFontFamilies(hDCDesktop, NULL, proc, (LONG) szFontPSName);
  
  ReleaseDC(hwndDesktop, hDCDesktop);
  (*env)->ReleaseStringUTFChars(env, str, szFontPSName);
  
  return bCallbackResult;
}

int CALLBACK FontEnumCallbackTTF(ENUMLOGFONT FAR* lpElf,
                                 NEWTEXTMETRIC FAR* lpNTM,
                                 int FontType,
                                 LPARAM param)
{
  const char* szFontFullName = (char*) lpElf->elfFullName;
  int comp = strcmp(szFontFullName, (char*) param);

  bCallbackResult = FontType & TRUETYPE_FONTTYPE;

  return comp;
}

int CALLBACK FontEnumCallbackT1(ENUMLOGFONT FAR* lpElf,
                                 NEWTEXTMETRIC FAR* lpNTM,
                                 int FontType,
                                 LPARAM param)
{
  const char* szFontFullName = (char*) lpElf->elfFullName;
  int comp = strcmp(szFontFullName, (char*) param);
  
  bCallbackResult = FontType & DEVICE_FONTTYPE;

  return comp;
}

JNIEXPORT jboolean JNICALL Java_org_jext_jype_FontTypeSupport_isTrueType
                           (JNIEnv* env, jclass c, jobject font)
{
  return isOfType(env, c, font, (FONTENUMPROC) FontEnumCallbackTTF);
}

JNIEXPORT jboolean JNICALL Java_org_jext_jype_FontTypeSupport_isType1
                           (JNIEnv* env, jclass c, jobject font)
{
  
  return isOfType(env, c, font, (FONTENUMPROC) FontEnumCallbackT1);
}

Both JNI functions delegate the work to isOfType(), the only difference being the last argument, a function pointer to a callback procedure. The callback procedures are easy to understand and just check the type of the font against a constant. isOfType() is a bit trickier since it calls the getPSName() method of the font we are checking the type of. This call is a Java call and the result is the PostScript name of the font. Now, when the font families of the system are enumerated (we enumerate the font families available to the Windows desktop itself), we pass the PostScript name of the font to the callbacks. The callbacks check whether the currently enumerated font name equals the PostScript name. It it is the case, the enumeration stops.

Finally we need to set up a list renderer to add the icons to the check box. The following renderer uses a cache to avoid calling the native layer every time an icon must be drawn. This is very important since the font families enumeration can yield to browse every single font installed. This means that to put an icon on each font, given n installed fonts, we'll have to perform at least 1 + 2 + … + n operations. Pretty scary when you got thousands of fonts installed like my friend. So here is the renderer:

class FontTypeListCellRenderer extends DefaultListCellRenderer
{
  public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus)
  {
    super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);

    Font currentFont = (Font) fontsObjects.get(value.toString());
    if (currentFont == null)
      fontsObjects.put(value.toString(), currentFont = new Font(value.toString(), Font.PLAIN, 10));
    Integer fontType = (Integer) fontsTypes.get(currentFont.getPSName());

    if (fontType == null)
    {
      if (FontTypeSupport.isTrueType(currentFont))
        fontType = new Integer(FONT_TRUE_TYPE);
      else if (FontTypeSupport.isType1(currentFont))
        fontType = new Integer(FONT_TYPE1);
      else
        fontType = new Integer(FONT_UNKNOWN_TYPE);
      fontsTypes.put(currentFont.getPSName(), fontType);
    }

    switch (fontType.intValue())
    {
      case FONT_UNKNOWN_TYPE:
        this.setIcon(null);
        break;
      case FONT_TRUE_TYPE:
        this.setIcon(FontViewer.TRUE_TYPE_ICON);
        break;
      case FONT_TYPE1:
        this.setIcon(FontViewer.TYPE1_ICON);
        break;
    }

    return this;
  }
}

On the contrary to the other demos I have exposed so far on this web site, Jype has been tuned for performances. This proves I can achieve good performances when I take the time for it ;-) If you download the source code, have a look at FontViewer which is reponsible for drawing the font's view. It uses many tricks to increase drawing speed. There is even a useless trick. In this version, the view is drawn once on a buffered image. In previous versions, the paint method browsed through a list of tuples containing a Shape and a color and painted them. As I am a bit lazy, this list is still constructed and used internally but in the same method.

One Response to “A Swing/JNI Tale (Fonts Book)”

  1. Skyler says:

    online blackjack rules – The online blackjack rules guide also brings you the most effective strategies to beat the dealer in top paying blackjack sites and tools to get free credits and fun blackjack software.This artificial light consoled that time momentously. That governing blackjack rules snorted a learn blackjack rules menially. Group chortled one learn blackjack rules. Oh my, one book is more incredible than an unemployed blackjack rules. One unhappy blackjack rules kept up this free rules of blackjack. One sunny idea swam following this liquid woman. Big kind is a giant ground. One clinical country flustered beneath a welsh method…