Sunday, December 9, 2012

Font Rendering with Freetype and Libpng (Part 2)

Hi there,

this is part two of rendering a font with freetype into a PNG image. In this second part I will show how to cross-compile the program we created in the first part for Windows x64.
I'm going to use the mingw-w64 project which makes it very easy. Just install the mingw-w64 package.
$ sudo aptitude install mingw-w64
After successful installation you can use the mingw-w64 toolchain:
$ x86_64-w64-mingw32-gcc --version
x86_64-w64-mingw32-gcc (GCC) 4.6.3
Copyright (C) 2011 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Now we just have to use this to compile freetype, zlib and libpng. Unfortunately the average build system is not really prepared for cross-compilation and even if it is it's not necessarily built with mingw-w64 in mind. Therefore it is usually necessary to fiddle around with various parameters to configure and make.

Cross-Compiling the Libraries

$ cd freetype-2.4.10
$ make clean
$ ./configure --disable-shared CC=x86_64-w64-mingw32-gcc RANLIB=x86_64-w64-mingw32-ranlib AR=x86_64-w64-mingw32-ar --host=x86_64
$ make
$ cp objs/.libs/libfreetype.a ../font-render/libs/win64/

$ cd ../zlib-1.2.7
$ make clean
$ ./configure --static
$ make CC=x86_64-w64-mingw32-gcc RANLIB=x86_64-w64-mingw32-ranlib AR=x86_64-w64-mingw32-ar
$ cp libz.a ../font-render/libs/win64/

$ cd ../libpng-1.5.13
$ make clean
$ ./configure --disable-shared CC=x86_64-w64-mingw32-gcc RANLIB=x86_64-w64-mingw32-ranlib AR=x86_64-w64-mingw32-ar --host=x86_64 CFLAGS=-L../zlib-1.2.7
$ make CFLAGS='-L../zlib-1.2.7 -I../zlib-1.2.7'
$ cp .libs/libpng15.a ../font-render/libs/win64/
Now you should be able to compile the program for Windows x64:
$ cd ../font-render
$ x86_64-w64-mingw32-gcc -Wall -pedantic -std=c99 -Iinclude -Llibs/win64 render-text.c -o render-text.exe -lfreetype -lpng15 -lz -lm
And that's it.

To keep it short and sweet, I will postpone the rendering of multiple characters to a third part. Don't worry, I will post it soon.

As always, if you have any comments, critique, suggestions or just want to tell me how much I suck please use the comment form below. I would appreciate your feedback.

Font Rendering with Freetype and Libpng (Part 1)

Namaste,

long time, no see. Not that anyone is reading this stuff, but I say it anyway: I'm still alive and this blog is definitely not dead :-)

So, without further ado, let's talk about some codez.
Today I want to show a simple example of rendering a character string using freetype and libpng. The codez are also available on github: https://github.com/sendtehcodez/font-rendering/tree/part1. Of course, it's free to use for any purpose and hereby released under the terms of the WTFPL license. Yay!

Note that I will use gcc for this exercise and I will compile this on (X)ubuntu linux. For bonus points I'm going to cross-compile it for Windows as well, but I'm going to cheat and use mingw-w64 which makes it so easy that even I can manage it. It's going to be glorious.
And, just so you know, because I'm apparently a crazy masochist I'm actually doing this in a virtual machine running on Windows 7. But that's just how I roll.

So, enough drivel, let's code the codez. My project directory structure is going to look like this:
font-render/         # the project root directory
    include/         # I will put include files from libs here
    libs/
        linux64/     # this will contain the libs for linux64
        win64/       # here we put the win64 libs
    render-text.c    # our codez
    arial.ttf        # just a random font I copied for testing so I 
                     # don't have to specify a long path to 
                     # /usr/share/fonts/... every time
To start off, let's create a simple stub for text-render.c:
#include <stdio.h>

int main(int argc, char **argv) {
  puts("booyah");
} 
Exciting, isn't it? I'm going to compile this:
$ gcc -Wall -pedantic -std=c99 render-text.c -o render-text
$ ./render-text
booyah
OK, so it works. I like repeatable builds and standalone executables and freetype and libpng don't ship with Windows anyway so I'm going to download the source and build a nice static library to link into my executable.
$ wget http://download.savannah.gnu.org/releases/freetype/freetype-2.4.10.tar.bz2
$ tar xjf freetype-2.4.10.tar.bz2
$ cd freetype-2.4.10
$ ./configure --disable-shared && make
$ cp objs/.libs/libfreetype.a ../font-render/libs/linux32/
$ cp -R include/* ../font-render/include/
So far so good. Let's actually load a font file now. To make life easier let's quickly implement some simple argument handling:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct {
  char *font_file;
  char *text;
  int size;
  int anti_alias;
} conf_t;

char *parse_args(int argc, char **argv, conf_t *conf) {
  memset(conf, 0, sizeof(conf_t));
  conf->size = 72;
  if (argc < 2) {
    return "missing font file argument";
  } else if (argc < 3) {
    return "missing text argument";
  }
  conf->font_file = argv[1];
  conf->text = argv[2];
  if (argc > 3) {
    conf->size = atoi(argv[3]);
  }
  if (argc > 4) {
    conf->anti_alias = strcmp(argv[4], "yes") == 0;
  }
  return NULL;
}

int main(int argc, char **argv) {
  conf_t conf;
  char *conf_err = parse_args(argc, argv, &conf);
  if (conf_err != NULL) {
    printf("Error: %s\n", conf_err);
    puts("Usage: render-text <font> <text> [<size> [<anti-alias>]]");
    puts("Example: render-text myfont.ttf foobar 24 yes");
    puts("         render-text myfont.ttf lorem");
    return 1;
  }

  printf("font: %s, text: %s, size: %d, anti-alias: %s\n",
         conf.font_file,
         conf.text,
         conf.size,
         conf.anti_alias ? "yes" : "no");

  return 0;
}
Recompiling this and testing it out it should work fine.
$ ./render-text 
Error: missing font file argument
Usage: render-text <font> <text> [<size> [<anti-alias>]]
Example: render-text myfont.ttf foobar 24 yes
         render-text myfont.ttf lorem
$ ./render-text foo.ttf blabla
font: foo.ttf, text: blabla, size: 72, anti-alias: no


Rendering a Glyph with Freetype

So let's add freetype into the mix. Add this to the includes:
#include <ft2build.h>
#include FT_FREETYPE_H
As a first step, let's just render the first character of the text. To keep it simple I put everything into a single function. This will be refactored when we render more than a single character. For error handling I simply return NULL for success and an error string on failure.
char *render_glyph(FT_Face *face, conf_t conf) {
  FT_Library ft;
  FT_Error err;

  err = FT_Init_FreeType(&ft);
  if (err) return "freetype init error";

  err = FT_New_Face(ft, conf.font_file, 0, face);
  if (err == FT_Err_Unknown_File_Format)
    return "unknown font file format";
  else if (err)
    return "error reading font file";

  err = FT_Set_Pixel_Sizes(*face, 0, conf.size);
  if (err) return "error setting font size";

  FT_UInt index = FT_Get_Char_Index(*face, *conf.text);
  if (index == 0) return "no glyph found for char";

  err = FT_Load_Glyph(*face, index, FT_LOAD_DEFAULT);
  if (err) return "error loading glyph";

  err = FT_Render_Glyph((*face)->glyph, conf.anti_alias ? 
                        FT_RENDER_MODE_NORMAL : 
                        FT_RENDER_MODE_MONO);
  if (err) return "error rendering glyph";

  return NULL;
}

Now we can use render_glyph in the main function, just add this at the end (before the return of course):
  FT_Face face;
  char *ft_err = render_glyph(&face, conf);
  if (ft_err != NULL) {
    printf("freetype error: %s\n", ft_err);
    return 2;
  }

  printf("bitmap rows: %d, width: %d\n", 
         face->glyph->bitmap.rows,
         face->glyph->bitmap.width);

When recompiling make sure to give it the correct paths for includes and libs. For example:
$ gcc -Wall -pedantic -std=c99 -Iinclude \
  render-text.c -Llibs/linux64 -lfreetype -o render-text

$ ./render-text arial.ttf blabla
font: arial.ttf, text: blabla, size: 72, anti-alias: no
bitmap rows: 53, width: 33

$ ./render-text arial.ttf blabla 200
font: arial.ttf, text: blabla, size: 200, anti-alias: no
bitmap rows: 145, width: 90

Alrighty. Looks good so far. Now we just need to write a PNG with the bitmap data.

Writing the PNG

Let's build zlib:
$ wget http://prdownloads.sourceforge.net/libpng/zlib-1.2.7.tar.gz?download -O zlib-1.2.7.tar.gz
$ tar xzf zlib-1.2.7.tar.gz
$ cd zlib-1.2.7
$ ./configure --static && make
$ cp libz.a ../font-render/libs/linux64/
$ cp zlib.h ../font-render/include/
And then libpng:
$ wget http://prdownloads.sourceforge.net/libpng/libpng-1.5.13.tar.gz?download -O libpng-1.5.13.tar.gz
$ tar xzf libpng-1.5.13.tar.gz
$ cd libpng-1.5.13
$ ./configure CFLAGS='-L../zlib-1.2.7' --disable-shared
$ make CFLAGS='-I../zlib-1.2.7 -L../zlib-1.2.7'
$ cp .libs/libpng15.a ../font-render/libs/linux64/
$ cp png*.h ../font-render/include/
Now we're ready to use libpng. Behold, the glorious render_png function:
char *render_png(FT_Face face, char *out, int aa) {
  FILE *f = fopen(out, "wb");
  if (!f) return "failed to open output file";

  png_structp png_out = png_create_write_struct(
    PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  if (!png_out) return "failed to create png write struct";
  png_infop png_info = png_create_info_struct(png_out);
  if (!png_info)
    return "failed to create png info struct";

  if (setjmp(png_jmpbuf(png_out)))
    return "png init io error";
  png_init_io(png_out, f);

  if (setjmp(png_jmpbuf(png_out)))
    return "IHDR write error";
  png_set_IHDR(png_out,
               png_info,
               face->glyph->bitmap.width,
               face->glyph->bitmap.rows,
               aa ? 8 : 1,
               PNG_COLOR_TYPE_GRAY,
               PNG_INTERLACE_NONE,
               PNG_COMPRESSION_TYPE_DEFAULT,
               PNG_FILTER_TYPE_DEFAULT);
  png_write_info(png_out, png_info);

  if (setjmp(png_jmpbuf(png_out)))
    return "png write error";

  for (int i = 0; i < face->glyph->bitmap.rows; ++i) {
    const unsigned char *rowptr = face->glyph->bitmap.buffer +
      (face->glyph->bitmap.pitch * i);
    png_write_row(png_out, rowptr);
  }

  if (setjmp(png_jmpbuf(png_out)))
    return "png end error";
  png_write_end(png_out, NULL);

  fclose(f);
  return NULL;
}

Note that this is not properly cleaning up in case of errors because I was too lazy to do it The Right WayTM but for this program it doesn't really matter anyway. I'm also using the same lazy error handling technique as before. Oh, and don't forget to include the "png.h" header file which isn't shown here.

Now we just need to use that function in main. This is the complete main function at this point:
int main(int argc, char **argv) {
  conf_t conf;
  char *conf_err = parse_args(argc, argv, &conf);
  if (conf_err != NULL) {
    printf("Error: %s\n", conf_err);
    puts("Usage: render-text <font> <text> [<size> [<anti-alias>]]");
    puts("Example: render-text myfont.ttf foobar 24 yes");
    puts("         render-text myfont.ttf lorem");
    return 1;
  }

  printf("font: %s, text: %s, size: %d, anti-alias: %s\n",
         conf.font_file,
         conf.text,
         conf.size,
         conf.anti_alias ? "yes" : "no");

  FT_Face face;
  char *ft_err = render_glyph(&face, conf);
  if (ft_err != NULL) {
    printf("freetype error: %s\n", ft_err);
    return 2;
  }

  printf("bitmap rows: %d, width: %d\n",
         face->glyph->bitmap.rows,
         face->glyph->bitmap.width);

  char *png_err = render_png(face, "a.png", conf.anti_alias);
  if (png_err != NULL) {
    printf("png error: %s\n", png_err);
    return 3;
  }

  return 0;
}

To compile:
$ gcc -Wall -pedantic -std=c99 -Iinclude/ render-text.c -o render-text -Llibs/linux64 -lfreetype -lpng15 -lz -lm
If you execute the result with the proper arguments it should successfully create a file called "a.png" in the current directory. I leave it as an exercise for the reader to replace the hardcoded output file name with a command line argument.

So Long and Thanks for All the Codez

That's it for this first part. Next time (soon!) I'll show how to cross-compile this program for Windows with mingw-w64 and how to render multiple characters. If you spot any errors, have any suggestions or just want to yell at me, feel free to leave a comment below.

Here's the complete code (and again, you can find the complete project on github here: https://github.com/sendtehcodez/font-rendering/tree/part1):
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <ft2build.h>
#include FT_FREETYPE_H

#include <png.h>

typedef struct {
  char *font_file;
  char *text;
  int size;
  int anti_alias;
} conf_t;

char *parse_args(int argc, char **argv, conf_t *conf) {
  memset(conf, 0, sizeof(conf_t));
  conf->size = 72;
  if (argc < 2) {
    return "missing font file argument";
  } else if (argc < 3) {
    return "missing text argument";
  }
  conf->font_file = argv[1];
  conf->text = argv[2];
  if (argc > 3) {
    conf->size = atoi(argv[3]);
  }
  if (argc > 4) {
    conf->anti_alias = strcmp(argv[4], "yes") == 0;
  }
  return NULL;
}

char *render_glyph(FT_Face *face, conf_t conf) {
  FT_Library ft;
  FT_Error err;

  err = FT_Init_FreeType(&ft);
  if (err) return "freetype init error";

  err = FT_New_Face(ft, conf.font_file, 0, face);
  if (err == FT_Err_Unknown_File_Format)
    return "unknown font file format";
  else if (err)
    return "error reading font file";

  err = FT_Set_Pixel_Sizes(*face, 0, conf.size);
  if (err) return "error setting font size";

  FT_UInt index = FT_Get_Char_Index(*face, *conf.text);
  if (index == 0) return "no glyph found for char";

  err = FT_Load_Glyph(*face, index, FT_LOAD_DEFAULT);
  if (err) return "error loading glyph";

  err = FT_Render_Glyph((*face)->glyph, conf.anti_alias ?
                        FT_RENDER_MODE_NORMAL :
                        FT_RENDER_MODE_MONO);
  if (err) return "error rendering glyph";

  return NULL;
}

char *render_png(FT_Face face, char *out, int aa) {
  FILE *f = fopen(out, "wb");
  if (!f) return "failed to open output file";

  png_structp png_out = png_create_write_struct(
    PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  if (!png_out) return "failed to create png write struct";
  png_infop png_info = png_create_info_struct(png_out);
  if (!png_info)
    return "failed to create png info struct";

  if (setjmp(png_jmpbuf(png_out)))
    return "png init io error";
  png_init_io(png_out, f);

  if (setjmp(png_jmpbuf(png_out)))
    return "IHDR write error";
  png_set_IHDR(png_out,
               png_info,
               face->glyph->bitmap.width,
               face->glyph->bitmap.rows,
               aa ? 8 : 1,
               PNG_COLOR_TYPE_GRAY,
               PNG_INTERLACE_NONE,
               PNG_COMPRESSION_TYPE_DEFAULT,
               PNG_FILTER_TYPE_DEFAULT);
  png_write_info(png_out, png_info);

  if (setjmp(png_jmpbuf(png_out)))
    return "png write error";

  for (int i = 0; i < face->glyph->bitmap.rows; ++i) {
    const unsigned char *rowptr = face->glyph->bitmap.buffer +
      (face->glyph->bitmap.pitch * i);
    png_write_row(png_out, rowptr);
  }

  if (setjmp(png_jmpbuf(png_out)))
    return "png end error";
  png_write_end(png_out, NULL);

  fclose(f);
  return NULL;
}

int main(int argc, char **argv) {
  conf_t conf;
  char *conf_err = parse_args(argc, argv, &conf);
  if (conf_err != NULL) {
    printf("Error: %sn", conf_err);
    puts("Usage: render-text <font> <text> [<size> [<anti-alias>]]");
    puts("Example: render-text myfont.ttf foobar 24 yes");
    puts("         render-text myfont.ttf lorem");
    return 1;
  }

  printf("font: %s, text: %s, size: %d, anti-alias: %sn",
         conf.font_file,
         conf.text,
         conf.size,
         conf.anti_alias ? "yes" : "no");

  FT_Face face;
  char *ft_err = render_glyph(&face, conf);
  if (ft_err != NULL) {
    printf("freetype error: %sn", ft_err);
    return 2;
  }

  printf("bitmap rows: %d, width: %dn",
         face->glyph->bitmap.rows,
         face->glyph->bitmap.width);

  char *png_err = render_png(face, "a.png", conf.anti_alias);
  if (png_err != NULL) {
    printf("png error: %sn", png_err);
    return 3;
  }

  return 0;
}

Monday, January 2, 2012

Java/Swing: JTable Filter Highlighting

When you have a large table of textual data it is often useful to have a way to filter it and since Java 6 this is possible without third-party libraries using the RowFilter API. But when you search through multiple columns it is often not immediately apparent which cell contains the match.

One obvious solution to this problem is to highlight the matching text fragments. In principle this can easily be achieved by implementing a custom cell renderer that is aware of the filter string. Following the implementation of the DefaultCellRenderer we would just need a label where you can specify individual background colors for arbitrary substrings which you could then use as a basis for your custom cell renderer.

Fortunately JIDE already did the hard work for us with their StyledLabel implementation which is part of the open sourced JIDE Common Layer. With StyledLabel as a base for our custom cell renderer the implementation becomes rather trivial.

So to make it more interesting here are the requirements for our example:
  • must match in arbitrary user-specified columns
  • the filter string must be split into multiple terms at whitespace boundaries and a row must only match if each of those terms is a substring of at least one cell
  • the substring matching must be case-insensitive
  • the renderer must support alternating colors for different terms but must highlight same terms with same colors
  • the colors used for highlighting must be customizable
Seems we've got our work cut out for us so let's do it.

First of all we need a simple table:
static JTable createTable() {
    Object[][] data = new Object[][] {
        { "foo bar baz", "lorem ipsum dolor", "quux fizz buzz" },
        { "java scala clojure", "lisp haskell ml", "ruby python php" },
        { "blue green red", "orange yellow magenta", "pink cyan black" }
    };
    JTable tbl = new JTable(data, new Object[] {"1", "2", "3"});
    tbl.setAutoCreateRowSorter(true);
    return tbl;
}
Then we create a simple frame to test-drive our table:
public static void main(String[] args) throws Exception {
    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

    final Integer[] cols = new Integer[] { 0, 1, 2 };
    final JTable tbl = createTable();
    final TableRowSorter<TableModel> sort =
        (TableRowSorter<TableModel>)tbl.getRowSorter();
    final JTextField filter = new JTextField();
    filter.addKeyListener(new KeyAdapter() {
        @Override public void keyReleased(KeyEvent e) {
            String text = filter.getText().trim();
            if (text.length() > 0) {
                final List<String> tokens = split(text.toLowerCase());
                sort.setRowFilter(makeRowFilter(tokens, cols));
            } else {
                sort.setRowFilter(null);
            }
        }
    });

    JScrollPane scroll = new JScrollPane(tbl);
    scroll.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0,
        UIManager.getColor("Panel.background").darker()));
    scroll.setVerticalScrollBarPolicy(
        JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

    final JPanel content = new JPanel(new MigLayout("fill, ins 0"));
    content.add(new JLabel("Filter:"), "gapleft 4");
    content.add(filter, "wrap, growx, pushx, gap 4 4 4");
    content.add(scroll, "grow, pushy, span");

    final JFrame f = new JFrame("JTable Filter Highlighting");
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setContentPane(content);
    f.setSize(400, 300);
    f.setLocationRelativeTo(null);
    f.setVisible(true);
}

For the above to compile we also need to declare the makeRowFilter method:
static RowFilter<TableModel,Integer>
makeRowFilter(final List<String> tokens, final Integer... cols) {
    return new RowFilter<TableModel,Integer>() {
        @Override public boolean
        include(Entry<? extends TableModel,? extends Integer> en) {
            for (String tok : tokens) {
                for (int i = 0; i < cols.length; ++i) {
                    String val = en.getStringValue(cols[i]).toLowerCase();
                    if (val.contains(tok)) {
                        break;
                    } else if (i == cols.length - 1) {
                        return false;
                    }
                }
            }
            return true;
        }
    };
}
So now we have a table with all the filtering features that we specified. The only thing missing is the highlighting. As I already said, this is trivial to implement on top of StyledLabel. Here's the complete renderer implementation:
static class HighlightCellRenderer
    extends StyledLabel implements TableCellRenderer {
    private List<String> terms = new ArrayList<String>();
    private Color[] colors = new Color[] {
        Color.yellow, Color.pink, Color.cyan };
    private final List<Integer> cols;
    public HighlightCellRenderer(List<Integer> cols) {
        this.cols = cols;
        setBorder(new EmptyBorder(1, 1, 1, 1));
        setOpaque(true);
    }
    public void setTerms(List<String> terms) {
        this.terms = terms;
    }
    public void clearTerms() {
        terms.clear();
    }
    public void setColors(Color... colors) {
        this.colors = colors;
    }
    public Component getTableCellRendererComponent(JTable tbl, Object val,
        boolean sel, boolean hasFocus, int row, int col) {
        String text = val != null ? val.toString() : "";
        setText(text);
        clearStyleRanges();
        final Color fg = sel ?
            tbl.getSelectionForeground() : tbl.getForeground();
        final Color bg = sel ?
            tbl.getSelectionBackground() : tbl.getBackground();
        setForeground(fg);
        setBackground(bg);
        if (cols.contains(col)) {
            String textLc = text.toLowerCase();
            for (int i = 0; i < terms.size(); ++i) {
                String term = terms.get(i);
                int offset = textLc.indexOf(term);
                while (offset != -1) {
                    addStyleRange(new StyleRange(offset, term.length(),
                        getFont().getStyle(), sel ? bg : getForeground(),
                        sel ? fg : colors[i % colors.length], 0));
                    offset = textLc.indexOf(term, offset + 1);
                }
            }
        }

        configureBorder(tbl, sel, hasFocus, row, col);

        return this;
    }

    private void configureBorder(
        JTable tbl, boolean sel, boolean hasFocus,int row, int col) {
        // copied from DefaultTableCellRenderer
        if (hasFocus) {
            Border border = null;
            if (sel) {
                border = DefaultLookup.getBorder(
                    this, ui, "Table.focusSelectedCellHighlightBorder");
            }
            if (border == null) {
                border = DefaultLookup.getBorder(
                    this, ui, "Table.focusCellHighlightBorder");
            }
            setBorder(border);
            if (!sel && tbl.isCellEditable(row, col)) {
                Color color;
                color = DefaultLookup.getColor(
                    this, ui, "Table.focusCellForeground");
                if (color != null) {
                    super.setForeground(color);
                }
                color = DefaultLookup.getColor(
                    this, ui, "Table.focusCellBackground");
                if (color != null) {
                    super.setBackground(color);
                }
            }
        } else {
            setBorder(getNoFocusBorder());
        }
    }

    // copied from DefaultTableCellRenderer
    static final Border SAFE_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1);
    static final Border DEFAULT_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1);
    protected static Border noFocusBorder = DEFAULT_NO_FOCUS_BORDER;
    private Border getNoFocusBorder() {
        Border border = DefaultLookup.getBorder(
            this, ui, "Table.cellNoFocusBorder");
        if (System.getSecurityManager() != null) {
            if (border != null) return border;
            return SAFE_NO_FOCUS_BORDER;
        } else if (border != null) {
            if (noFocusBorder == null
                || noFocusBorder == DEFAULT_NO_FOCUS_BORDER) {
                return border;
            }
        }
        return noFocusBorder;
    }

    // overridden for performance reasons
    @Override public void invalidate() {}
    @Override public void validate() {}
    @Override public void revalidate() {}
    @Override public void repaint(long tm, int x, int y, int w, int h) {}
    @Override public void repaint(Rectangle r) { }
    @Override public void repaint() {}
}
To use it we just need to change a few lines in our test code:
final HighlightCellRenderer renderer = new HighlightCellRenderer(
    Arrays.asList(cols));
tbl.setDefaultRenderer(Object.class, renderer);
final JTextField filter = new JTextField();
filter.addKeyListener(new KeyAdapter() {
    @Override public void keyReleased(KeyEvent e) {
        String text = filter.getText().trim();
        if (text.length() > 0) {
            final List<String> tokens = split(text.toLowerCase());
            sort.setRowFilter(makeRowFilter(tokens, cols));
            renderer.setTerms(tokens);
        } else {
            sort.setRowFilter(null);
            renderer.clearTerms();
        }
    }
});

And that's it:
































Here's the complete code for this example (coded against com.jidesoft:jide-oss:2.10.2 and com.miglayout:miglayout-swing:4.1):
package sendtehcodez;

import java.awt.Color;
import java.awt.Component;
import java.awt.Rectangle;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.RowFilter;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;

import net.miginfocom.swing.MigLayout;
import sun.swing.DefaultLookup;

import com.jidesoft.swing.StyleRange;
import com.jidesoft.swing.StyledLabel;

public class JTableFilterHighlighting {
    public static void main(String[] args) throws Exception {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

        final Integer[] cols = new Integer[] { 0, 1, 2 };
        final JTable tbl = createTable();
        final TableRowSorter<TableModel> sort =
            (TableRowSorter<TableModel>)tbl.getRowSorter();
        final HighlightCellRenderer renderer = new HighlightCellRenderer(
            Arrays.asList(cols));
        tbl.setDefaultRenderer(Object.class, renderer);
        final JTextField filter = new JTextField();
        filter.addKeyListener(new KeyAdapter() {
            @Override public void keyReleased(KeyEvent e) {
                String text = filter.getText().trim();
                if (text.length() > 0) {
                    final List<String> tokens = split(text.toLowerCase());
                    sort.setRowFilter(makeRowFilter(tokens, cols));
                    renderer.setTerms(tokens);
                } else {
                    sort.setRowFilter(null);
                    renderer.clearTerms();
                }
            }
        });

        JScrollPane scroll = new JScrollPane(tbl);
        scroll.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0,
            UIManager.getColor("Panel.background").darker()));
        scroll.setVerticalScrollBarPolicy(
            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

        final JPanel content = new JPanel(new MigLayout("fill, ins 0"));
        content.add(new JLabel("Filter:"), "gapleft 4");
        content.add(filter, "wrap, growx, pushx, gap 4 4 4");
        content.add(scroll, "grow, pushy, span");

        final JFrame f = new JFrame("JTable Filter Highlighting");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setContentPane(content);
        f.setSize(400, 300);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    static RowFilter<TableModel,Integer>
    makeRowFilter(final List<String> tokens, final Integer... cols) {
        return new RowFilter<TableModel,Integer>() {
            @Override public boolean
            include(Entry<? extends TableModel,? extends Integer> en) {
                for (String tok : tokens) {
                    for (int i = 0; i < cols.length; ++i) {
                        String val = en.getStringValue(cols[i]).toLowerCase();
                        if (val.contains(tok)) {
                            break;
                        } else if (i == cols.length - 1) {
                            return false;
                        }
                    }
                }
                return true;
            }
        };
    }

    static List<String> split(String str) {
        List<String> tokens = new ArrayList<String>();
        StringTokenizer tok = new StringTokenizer(str);
        while (tok.hasMoreTokens()) {
            String token = tok.nextToken().trim();
            if (token.length() > 0) {
                tokens.add(token);
            }
        }
        return tokens;
    }

    static JTable createTable() {
        Object[][] data = new Object[][] {
            { "foo bar baz", "lorem ipsum dolor", "quux fizz buzz" },
            { "java scala clojure", "lisp haskell ml", "ruby python php" },
            { "blue green red", "orange yellow magenta", "pink cyan black" }
        };
        JTable tbl = new JTable(data, new Object[] {"1", "2", "3"});
        tbl.setAutoCreateRowSorter(true);
        return tbl;
    }

    static class HighlightCellRenderer
        extends StyledLabel implements TableCellRenderer {
        private List<String> terms = new ArrayList<String>();
        private Color[] colors = new Color[] {
            Color.yellow, Color.pink, Color.cyan };
        private final List<Integer> cols;
        public HighlightCellRenderer(List<Integer> cols) {
            this.cols = cols;
            setBorder(new EmptyBorder(1, 1, 1, 1));
            setOpaque(true);
        }
        public void setTerms(List<String> terms) {
            this.terms = terms;
        }
        public void clearTerms() {
            terms.clear();
        }
        public void setColors(Color... colors) {
            this.colors = colors;
        }
        public Component getTableCellRendererComponent(JTable tbl, Object val,
            boolean sel, boolean hasFocus, int row, int col) {
            String text = val != null ? val.toString() : "";
            setText(text);
            clearStyleRanges();
            final Color fg = sel ?
                tbl.getSelectionForeground() : tbl.getForeground();
            final Color bg = sel ?
                tbl.getSelectionBackground() : tbl.getBackground();
            setForeground(fg);
            setBackground(bg);
            if (cols.contains(col)) {
                String textLc = text.toLowerCase();
                for (int i = 0; i < terms.size(); ++i) {
                    String term = terms.get(i);
                    int offset = textLc.indexOf(term);
                    while (offset != -1) {
                        addStyleRange(new StyleRange(offset, term.length(),
                            getFont().getStyle(), sel ? bg : getForeground(),
                            sel ? fg : colors[i % colors.length], 0));
                        offset = textLc.indexOf(term, offset + 1);
                    }
                }
            }

            configureBorder(tbl, sel, hasFocus, row, col);

            return this;
        }

        private void configureBorder(
            JTable tbl, boolean sel, boolean hasFocus,int row, int col) {
            // copied from DefaultTableCellRenderer
            if (hasFocus) {
                Border border = null;
                if (sel) {
                    border = DefaultLookup.getBorder(
                        this, ui, "Table.focusSelectedCellHighlightBorder");
                }
                if (border == null) {
                    border = DefaultLookup.getBorder(
                        this, ui, "Table.focusCellHighlightBorder");
                }
                setBorder(border);
                if (!sel && tbl.isCellEditable(row, col)) {
                    Color color;
                    color = DefaultLookup.getColor(
                        this, ui, "Table.focusCellForeground");
                    if (color != null) {
                        super.setForeground(color);
                    }
                    color = DefaultLookup.getColor(
                        this, ui, "Table.focusCellBackground");
                    if (color != null) {
                        super.setBackground(color);
                    }
                }
            } else {
                setBorder(getNoFocusBorder());
            }
        }

        // copied from DefaultTableCellRenderer
        static final Border SAFE_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1);
        static final Border DEFAULT_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1);
        protected static Border noFocusBorder = DEFAULT_NO_FOCUS_BORDER;
        private Border getNoFocusBorder() {
            Border border = DefaultLookup.getBorder(
                this, ui, "Table.cellNoFocusBorder");
            if (System.getSecurityManager() != null) {
                if (border != null) return border;
                return SAFE_NO_FOCUS_BORDER;
            } else if (border != null) {
                if (noFocusBorder == null
                    || noFocusBorder == DEFAULT_NO_FOCUS_BORDER) {
                    return border;
                }
            }
            return noFocusBorder;
        }

        // overridden for performance reasons
        @Override public void invalidate() {}
        @Override public void validate() {}
        @Override public void revalidate() {}
        @Override public void repaint(long tm, int x, int y, int w, int h) {}
        @Override public void repaint(Rectangle r) { }
        @Override public void repaint() {}
    }
}