René Nyffenegger's collection of things on the web
René Nyffenegger on Oracle - Most wanted - Feedback -
 

Modifying Laszlo's TTF2FFT.java

Macromedia Flash internally uses the Macromedia Flash Font Format (=FFT) to store how a font looks like. If one wants to dynamically (for example with Ming or JGenerator) create a Flash movie with embedded text, (s)he must have the font in the FFT format.
There is a ttf2fft.exe coming with JGenerator which promises to convert true type fonts (ttf) into Flash fonts (fft). However, at least on my Laptop, it couldn't be executed: it threw the following Message Box at me:
16 bit MS-DOS Subsystem
C:\WINDOWS\system32\cmd.exe - \rene\ttf2fft.exe
The NTVDM CPU has encountered an illegal instruction.
CS:0dda IP:0109 OP:63 74 79 70 65 Choose 'Close' to terminate the application.
Luckily, I found a TTF2FFT class within the Laszlo Framework which I modified so that it can be run without the Laszlo framework:
TTF2FFT.java
/******************************************************************************
 * TTF2FFT.java
 * ****************************************************************************/

//LZ_JAVA_COPYRIGHT_BEGIN
/* *****************************************************************************
* Copyright (c) 2001-2004 Laszlo Systems, Inc.
* All Rights Reserved.
*
* This software is the proprietary information of Laszlo Systems, Inc.
* Use is subject to license terms.
*
* ****************************************************************************/
//LZ_JAVA_COPYRIGHT_END


import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.io.File;
import java.awt.geom.Rectangle2D;
import java.awt.Rectangle;

// Apache Batik TrueType Font Parser
import org.apache.batik.svggen.font.*;
import org.apache.batik.svggen.font.table.*;

class Util {
  private static final int[] BITS_LENGTH = {
//  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F
    0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4
  };
  public static int getMinBitsU(int v) {
    int n = 0;
    if( (v & ~0xffff) != 0 ) {
      n+=16; v >>>= 16;
    }
    if( (v & ~0x00ff) != 0 ) {
      n+= 8; v >>>=  8;
    }
    if( (v & ~0x000f) != 0 ) {
      n+= 4; v >>>=  4;
    }
    // for( ; v != 0; n++ ) v >>>= 1;
    n += BITS_LENGTH[v];
    return n;
  }
  public static int getMinBitsS(int v) {
    if (v < 0) v = -v;
    return getMinBitsU(v)+1;
  }
  public static int getMax( int a, int b ) {
    if( a < 0 ) a = -a;
    if( b < 0 ) b = -b;
    if( a >= b ) return a;
    return b;
  }
  public static int getMax( int a, int b, int c ) {
    if( a < 0 ) a = -a;
    if( b < 0 ) b = -b;
    if( c < 0 ) c = -c;
    if( a >= b && a >= c ) return a;
    if( b >= a && b >= c ) return b;
    return c;
  }
  public static int getMax( int a, int b, int c, int d ) {
    if( a < 0 ) a = -a;
    if( b < 0 ) b = -b;
    if( c < 0 ) c = -c;
    if( d < 0 ) d = -d;

    if( a > b ) {
      if( a > c ) {
        if( a > d ) {
          return a;
        } else {
          return d;
        }
      } else {
        if( c > d ) {
          return c;
        } else {
          return d;
        }
      }
    } else {
      if( b > c ) {
        if( b > d ) {
          return b;
        } else {
          return d;
        }
      } else {
        if( c > d ) {
          return c;
        } else {
          return d;
        }
      }
    }
  }
}

abstract class FlashItem {
  public abstract void write(FlashOutput fob);
}

class FlashBuffer {

  // bit buffer and position
  private int bitBuf;
  private int bitPos;

  private byte buf[];

  // current position in the buffer for reading and writing
  private int pos;

  // size of the buffer
  private int size;

  public FlashBuffer( int capacity ) {
    this( new byte[capacity], 0, 0 );
  }

  public FlashBuffer( byte[] buf, int pos, int size ) {
    init( buf, pos, size );
  }

  public void init( byte[] buf, int pos, int size ) {
    this.buf = buf;
    this.pos = pos;
    this.size = size;
    this.bitBuf = 0;
    this.bitPos = 0;
  }

  public final void ensureCapacity( int cap ) {
    if( cap > buf.length ) {
      int max = buf.length*2;
      if( cap > max ) max = cap+16;
      if( max < 4096 ) max = 4096;
      byte[] newBuf = new byte[max];
      System.arraycopy(buf, 0, newBuf, 0, buf.length);
      buf = newBuf;
    }
  }

  public final int getPos() {
    return pos;
  }

  public final int getSize() {
    return size;
  }

  public final void setPos( int pos ) {
    this.pos = pos;
    if( pos > size ) size = pos;
  }

  public final void incPos() {
    if( ++pos > size ) size = pos;
  }

  public final void skip( int inc ) {
    setPos( pos + inc );
  }

  public final byte[] getBuf() {
    return buf;
  }

  public final void writeWordAt( int b, int pos ) {
    buf[pos] = (byte) b;
    buf[pos+1] = (byte) (b>>8);
  }

  public final void writeDWordAt( int b, int pos ) {
    buf[pos] = (byte) b;
    buf[pos+1] = (byte) (b>>8);
    buf[pos+2] = (byte) (b>>16);
    buf[pos+3] = (byte) (b>>24);
  }

  public final void writeByte( int b ) {
    ensureCapacity( pos+1 );
    buf[pos] = (byte) b;
    incPos();
  }

  public final void writeWord( int b ) {
    ensureCapacity( pos+2 );
    buf[pos] = (byte) b;
    buf[pos+1] = (byte) (b>>8);
    setPos( pos+2 );
  }

  public final void writeDWord( int b ) {
    ensureCapacity( pos+4 );
    buf[pos] = (byte) b;
    buf[pos+1] = (byte) (b>>8);
    buf[pos+2] = (byte) (b>>16);
    buf[pos+3] = (byte) (b>>24);
    setPos( pos+4 );
  }

  public final void writeFOB( FlashBuffer fob ) {
    int len = fob.getSize();
    ensureCapacity( pos+len );
    System.arraycopy(fob.getBuf(), 0, buf, pos, len);
    setPos( pos+len );
  }

  public final void writeStringL( String s ) {
    byte chars[];
    try {
      // Macromedia claims to use ISO8859-1, but appears to use Cp1252
      chars = s.getBytes("Cp1252");
    } catch (Exception e) {
      System.out.println("exception in writeStringL");
      chars = s.getBytes();
    }
    ensureCapacity( pos+chars.length+1 );
    buf[pos++] = (byte) chars.length;
    for( int i=0; i<chars.length; i++ ) {
      buf[pos++] = chars[i];
    }
    setPos( pos );  // to update size
  }

  public final void writeLongTagAt( int tagCode, int tagSize, int pos ) {
    writeWordAt( (tagCode<<6) | 0x3f, pos );
    writeDWordAt( tagSize, pos+2 );
  }

  public final void writeBit( int b ) {
    writeBits( b, 1 );
  }

  public final void writeBits(int v, int len) {
    ensureCapacity( pos+4 );
    for(;;) {
      v = v & ((1<<len)-1);
      int l = 8-bitPos;
      int s = l-len;
      if( s >= 0 ) {
        bitBuf = (bitBuf<<len) | v;
        bitPos += len;
        return;
      } else {
        s = -s;
        int bb = (bitBuf<<l) | (v>>>s);
        buf[pos] = (byte)bb;
        incPos();
        len = s;
        bitBuf = 0;
        bitPos = 0;
      }
    }
  }

  public final void flushBits() {
    if( bitPos != 0 ) {
      int bb = bitBuf << (8-bitPos);
      writeByte( bb );
    }
    bitBuf = 0;
    bitPos = 0;
  }

  public final void initBits() {
    bitBuf = 0;
    bitPos = 0;
  }

  public final int getBits( int n ) {
    // get n bits from the stream.
    int v = 0;

    for (;;) {
      int s = n - bitPos;
      if( s > 0 ) {
        // Consume the entire buffer
        v |= bitBuf << s;
        n -= bitPos;

        // get the next buffer
        bitBuf = getUByte();
        bitPos = 8;
      } else {
        // Consume a portion of the buffer
        s = -s;
        v |= bitBuf >> s;
        bitPos = s;
        bitBuf &= (1<<s)-1;   // mask off the consumed bits
        return v;
      }
    }
  }

  public final int getUByte() {
    return buf[pos++] & 0xff;
  }

  public InputStream getInputStream() {
    return new FlashBufferInputStream();
  }

  public class FlashBufferInputStream extends InputStream {

    private int curPos = 0;

    public int read() throws IOException {
      if( curPos >= size ) return -1;
      return buf[curPos++] & 0xff;
    }

  }

  public void write( Rectangle2D r ) {
    initBits();

    int xmin = (int) r.getMinX();
    int xmax = (int) r.getMaxX();
    int ymin = (int) r.getMinY();
    int ymax = (int) r.getMaxY();

    int nBits = Util.getMinBitsS(Util.getMax(xmin,xmax,ymin,ymax) );
    writeBits( nBits, 5 );
    writeBits( xmin, nBits );
    writeBits( xmax, nBits );
    writeBits( ymin, nBits );
    writeBits( ymax, nBits );
    flushBits();
  }
}

final class CurvedEdgeRecord extends FlashItem {

  private int controlDeltaX;
  private int controlDeltaY;
  private int anchorDeltaX;
  private int anchorDeltaY;

  public CurvedEdgeRecord( int controlDeltaX, int controlDeltaY, int anchorDeltaX, int anchorDeltaY ) {
    setControlDeltaX(controlDeltaX);
    setControlDeltaY(controlDeltaY);
    setAnchorDeltaX(anchorDeltaX);
    setAnchorDeltaY(anchorDeltaY);
  }

  public void setControlDeltaX( int controlDeltaX ) {
    this.controlDeltaX = controlDeltaX;
  }

  public void setControlDeltaY( int controlDeltaY ) {
    this.controlDeltaY = controlDeltaY;
  }

  public void setAnchorDeltaX( int anchorDeltaX ) {
    this.anchorDeltaX = anchorDeltaX;
  }

  public void setAnchorDeltaY( int anchorDeltaY ) {
    this.anchorDeltaY = anchorDeltaY;
  }

  public void write( FlashOutput fob ) {
    fob.writeBits(0x2, 2);
    int nBits = Util.getMinBitsS( Util.getMax(controlDeltaX, controlDeltaY, anchorDeltaX, anchorDeltaY) );
    if( nBits < 3 ) nBits = 3;
    fob.writeBits(nBits-2, 4);
    fob.writeBits(controlDeltaX, nBits);
    fob.writeBits(controlDeltaY, nBits);
    fob.writeBits(anchorDeltaX, nBits);
    fob.writeBits(anchorDeltaY, nBits);
  }
}

class FlashOutput  extends FlashBuffer  {

  public FlashOutput( int size ) {
    super(size);
  }

}

class IVVector implements Cloneable {

  protected Object[] objects;
  protected int top;
  protected static final int INIT_CAPACITY = 20;

  public IVVector() {
    this(INIT_CAPACITY);
  }

  public IVVector(int capacity) {
    init(capacity);
  }

  public final void ensureCapacity( int cap ) {
    if(cap >= objects.length ) {
      Object[] newObjs = new Object[cap*2];
      System.arraycopy(objects, 0, newObjs, 0, objects.length);
      objects = newObjs;
    }
  }

  public final void addElement( Object o ) {
    ensureCapacity( top );
    objects[top++] = o;
  }

  public final Object elementAt(int index) throws Exception {
    if( index >= top ) {
      throw new Exception("elementAt" + index + " >= " + top );
    }
    return objects[index];
  }

  public final int size() {
    return top;
  }

  protected final void init(int capacity) {
    init( capacity, 0 );
  }

  protected final void init(int capacity, int top) {
    this.top = top;
    if( capacity <= 0 ) capacity = 1;
    objects = new Object[capacity];
  }

}

final class StyleChangeRecord extends FlashItem {

  public static final int NEW_STYLES = 0x10;
  public static final int LINESTYLE  = 0x08;
  public static final int FILLSTYLE1 = 0x04;
  public static final int FILLSTYLE0 = 0x02;
  public static final int MOVETO   = 0x01;

  private int flags;
  private int deltaX;
  private int deltaY;
  private int fillStyle0;
  private int fillStyle1;
  private int lineStyle;

  public void setFlags( int flags ) {
    this.flags = flags;
  }

  public void addFlags( int flags ) {
    this.flags |= flags;
  }

  public void setDeltaX( int deltaX ) {
    this.deltaX = deltaX;
  }

  public void setDeltaY( int deltaY ) {
    this.deltaY = deltaY;
  }

  public void setFillStyle1( int fillStyle1 ) {
    this.fillStyle1 = fillStyle1;
  }

  public void setLineStyle( int lineStyle ) {
    this.lineStyle = lineStyle;
  }

  public void write(FlashOutput fob) {
    write(fob, 0, 0);
  }

  public void write( FlashOutput fob, int nFillBits, int nLineBits ) {
    fob.writeBits(flags, 6);
    if( (flags&MOVETO) != 0 ) {
      int nBits = Util.getMinBitsS( Util.getMax(deltaX, deltaY) );
      fob.writeBits(nBits, 5);
      fob.writeBits(deltaX, nBits);
      fob.writeBits(deltaY, nBits);
    }
    if( (flags&FILLSTYLE0) != 0 ) {
      fob.writeBits(fillStyle0, nFillBits);
    }
    if( (flags&FILLSTYLE1) != 0 ) {
      fob.writeBits(fillStyle1, nFillBits);
    }
    if( (flags&LINESTYLE) != 0 ) {
      fob.writeBits(lineStyle, nLineBits);
    }
  }

}


final class FixedTag  {

  private int code;

  public FixedTag( int code ) {
    this.code = code;
  }

  public void write(FlashOutput fob) {
    fob.writeWord(code << 6);
  }
}

final class StrightEdgeRecord extends FlashItem {
  public static final int GENERAL_LINE = 0;
  public static final int VERT_LINE  = 1;
  public static final int HORIZ_LINE   = 2;

  private int type;     // type of this record
  private int deltaX;     // delta X
  private int deltaY;     // delta Y

  public void setType(int type) {
    this.type = type;
  }

  public void setDeltaX( int deltaX ) {
    this.deltaX = deltaX;
  }

  public void setDeltaY( int deltaY ) {
    this.deltaY = deltaY;
  }

  public static StrightEdgeRecord newLine( int deltaX, int deltaY ) {
    StrightEdgeRecord sr = new StrightEdgeRecord();
    sr.setType( GENERAL_LINE );
    sr.setDeltaX( deltaX );
    sr.setDeltaY( deltaY );
    return sr;
  }

  public static StrightEdgeRecord newHLine( int deltaX ) {
    StrightEdgeRecord sr = new StrightEdgeRecord();
    sr.setType( HORIZ_LINE );
    sr.setDeltaX( deltaX );
    return sr;
  }

  public static StrightEdgeRecord newVLine( int deltaY ) {
    StrightEdgeRecord sr = new StrightEdgeRecord();
    sr.setType( VERT_LINE );
    sr.setDeltaY( deltaY );
    return sr;
  }

  public void write( FlashOutput fob ) {
    fob.writeBits(0x3, 2);
    switch( type ) {
      case GENERAL_LINE: {
        int nBits = Util.getMinBitsS( Util.getMax(deltaX, deltaY) );
        if( nBits < 3 ) nBits = 3;
        fob.writeBits(nBits-2, 4);
        fob.writeBit(1);
        fob.writeBits(deltaX, nBits);
        fob.writeBits(deltaY, nBits);
        break;
      }
      case VERT_LINE: {
        int nBits = Util.getMinBitsS( deltaY );
        if( nBits < 3 ) nBits = 3;
        fob.writeBits(nBits-2, 4);
        fob.writeBit(0);
        fob.writeBit(1);
        fob.writeBits(deltaY, nBits);
        break;
      }
      case HORIZ_LINE: {
        int nBits = Util.getMinBitsS( deltaX );
        if( nBits < 3 ) nBits = 3;
        fob.writeBits(nBits-2, 4);
        fob.writeBit(0);
        fob.writeBit(0);
        fob.writeBits(deltaX, nBits);
        break;
      }
    }
  }
}
class ShapeRecords  {

  private IVVector shape_records;

  private int last_x = Integer.MAX_VALUE;
  private int last_y = Integer.MAX_VALUE;

  public ShapeRecords() {
    this(new IVVector());
  }

  public ShapeRecords(IVVector shape_records) {
    this.shape_records = shape_records;
  }

  public IVVector getShapeRecords() {
    return shape_records;
  }

  public void drawCurveTo( int cx, int cy, int ax, int ay ) {
    shape_records.addElement( new CurvedEdgeRecord(cx-last_x, cy-last_y, ax-cx, ay-cy) );
    last_x = ax;
    last_y = ay;
  }

  public void drawLineTo( int x, int y ) {
    int deltaX = x-last_x;
    int deltaY = y-last_y;
    if( deltaX == 0 ) {
      if( deltaY == 0 ) return;
      shape_records.addElement(StrightEdgeRecord.newVLine(deltaY));
    } else if( deltaY == 0 ) {
      shape_records.addElement( StrightEdgeRecord.newHLine(deltaX) );
    } else {
      shape_records.addElement( StrightEdgeRecord.newLine(deltaX,deltaY) );
    }
    last_x = x;
    last_y = y;
  }

  public void movePenTo( int x, int y ) {
    try {
    if( last_x != x || last_y != y ) {
      StyleChangeRecord sc = getStyleChange();
      sc.addFlags( StyleChangeRecord.MOVETO );
      sc.setDeltaX(x);
      sc.setDeltaY(y);
      last_x = x;
      last_y = y;
    }
    }
    catch (Exception e) {
    e.printStackTrace();
    }
  }

  public StyleChangeRecord addStyleChangeRecord( StyleChangeRecord scr ) {
    shape_records.addElement(scr);
    return scr;
  }

  public StyleChangeRecord addStyleChangeRecord() {
    StyleChangeRecord scr = new StyleChangeRecord();
    shape_records.addElement(scr);
    return scr;
  }

  protected StyleChangeRecord getStyleChange() throws Exception {
    if( shape_records.size() > 0 ) {
      FlashItem item = (FlashItem) shape_records.elementAt( shape_records.size()-1 );
      if( item instanceof StyleChangeRecord ) return (StyleChangeRecord) item;
    }
    return addStyleChangeRecord();
  }

  public void write(FlashOutput fob, int nFillBits, int nLineBits) throws Exception {
    fob.initBits();
    for( int i=0; i<shape_records.size(); i++ ) {
      FlashItem item = (FlashItem) shape_records.elementAt(i);
      if( item instanceof StyleChangeRecord ) {
        StyleChangeRecord scr = (StyleChangeRecord) item;
        scr.write(fob, nFillBits, nLineBits);
      } else {
        item.write(fob);
      }
    }
  }
}

final class Shape {

  private StyleBlock style_block;
  private Rectangle2D bounds;   // bounding box of this shape

  public ShapeRecords getShapeRecords() {
    return style_block.shapeRecords;
  }


  public void newStyleBlock() {
    StyleBlock sb = new StyleBlock();
    sb.prev = style_block;

    // check whether last style block has stylechange with flag NEW_STYLES
    // add this record if there is no such stylechange
    if( style_block != null ) {
      IVVector shape_records = style_block.shapeRecords.getShapeRecords();
      if( shape_records.size() > 0 ) {
        try {
        Object o = shape_records.elementAt(shape_records.size()-1);
        if( o instanceof StyleChangeRecord ) {
          StyleChangeRecord sr = (StyleChangeRecord) o;
          sr.addFlags(StyleChangeRecord.NEW_STYLES);
        } else {
          StyleChangeRecord sr = new StyleChangeRecord();
          sr.addFlags(StyleChangeRecord.NEW_STYLES);
          shape_records.addElement(sr);
        }
        }
        catch (Exception e) {
        e.printStackTrace(); 
        }
      }
    }

    style_block = sb;
  }

  private static class StyleBlock {

    StyleBlock prev;

    ShapeRecords shapeRecords;    // collection of shape records

    StyleBlock() {
      this.shapeRecords = new ShapeRecords();
    }

  }
  

  public void setBounds( Rectangle2D bounds ) {
    this.bounds = bounds;
  }

  public void drawCurveTo( int cx, int cy, int ax, int ay ) {
    getShapeRecords().drawCurveTo(cx, cy, ax, ay);
  }

  public void drawLineTo( int x, int y ) {
    getShapeRecords().drawLineTo(x, y);
  }

  public void movePenTo( int x, int y ) {
    getShapeRecords().movePenTo(x, y);
  }

}

class TTFGlyph {

  private Point[] points;

  public TTFGlyph(GlyphDescription gd) {
    int endPtIndex = 0;
    points = new Point[gd.getPointCount()];
    for (int i = 0; i < gd.getPointCount(); i++) {
      boolean endPt = gd.getEndPtOfContours(endPtIndex) == i;
      if (endPt) {
        endPtIndex++;
      }
      points[i] = new Point(
          gd.getXCoordinate(i),
          gd.getYCoordinate(i),
          (gd.getFlags(i) & GlyfDescript.onCurve) != 0,
          endPt);
    }
  }

  public Point getPoint(int i) {
    return points[i];
  }

  public int getNumPoints() {
    return points.length;
  }
  
  public void scale(double factor) {
    for (int i = 0; i < points.length; i++) {
      points[i].x *= factor;
      points[i].y *= -factor;
    }
  }
}

public class TTF2FFT {
  private static final int HAS_LAYOUT   = 0x0080;
  private static final int ITALIC       = 0x0002;
  private static final int BOLD         = 0x0001;
  private static final int ANSI         = 0x0010;
  private static final int UNICODE      = 0x0020;
  private static final int WIDE_CODES   = 0x0004;
  private static final int WIDE_OFFSETS = 0x0008;

  private static final int TAG_END       = 0;

  private static final int TAG_DEFINEFONT2      = 48;

  private static final FixedTag END_TAG = new FixedTag(TAG_END);

  public static void main(String argv[]) {
    try {
      File ttf_file = new File (argv[0]);

      OutputStream fft_out_stream = new FileOutputStream(argv[1]);
      InputStream  fft_in_stream  = TTF2FFT.convert(ttf_file);

      int size=1024;

      byte[] buffer = new byte[size];
      int b = 0;
      while ((b = fft_in_stream.read(buffer)) > 0) {
        fft_out_stream.write(buffer, 0, b);
      }

      fft_out_stream.flush();
      fft_out_stream.close();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  private final static int LFC_TMARK = 160;

  /** Units per EmSquare for FFTs */
  private static final int FFT_UnitsPerEm = 1024;

  public static InputStream convert(File input) 
    throws Exception, FileNotFoundException {

    String path = input.getPath();

    if (!input.exists()) {
      throw new FileNotFoundException(path);
    }

    if (!input.canRead()) {
      throw new FileNotFoundException("Can't read: " + path);
    }

    org.apache.batik.svggen.font.Font ttf;
    ttf = org.apache.batik.svggen.font.Font.create(input.getPath());

    NameTable nameTable = ttf.getNameTable();
    String  fontName = "";
    if (nameTable == null) {
      fontName = input.getName();
      int index = fontName.indexOf(".");
      if (index > 0) {
        fontName = fontName.substring(0, index);
      }
    } else {
      fontName = nameTable.getRecord((short)1); 
    }
    HeadTable headTable = ttf.getHeadTable();
    HmtxTable hmtxTable = ttf.getHmtxTable();

    if (headTable == null) {
      // Bitmap fonts aren't required to have the head table.
      // We don't support them yet. XXX
      throw new Exception(path + " missing ttf head table; this ttf font not supported");
    }

    if (hmtxTable == null) {
      throw new Exception(path + " missing ttf hmtx (horiz. metrics) table; this ttf font not supported");
    }

    int   flags  = 0;

    // Is font bold, italic, or bold-italic?
    int   macStyle = headTable.getMacStyle();
    boolean isBold   = (macStyle & 0x1) != 0;
    boolean isItalic = (macStyle & 0x2) != 0;

    boolean isUnicode = false;
                     
                     
    if (isBold)
      flags |= BOLD;
    if (isItalic)
      flags |= ITALIC;

    // We have font metric info for the ttf
    flags |= HAS_LAYOUT;

    final int maxCodes = 0xffff;
    int     numCodes = 0;

    int [] codeTable  = new int[maxCodes];
    int [] indexTable = new int[maxCodes];
    int  maxCode  = 0;

    // Add Code 0 (not sure why this is needed.  Probably some lfc reason
    codeTable[0] = 0;
    indexTable[0] = 0;
    numCodes = 1;

    // 3 tries
    final int NUM_TRIES = 3;
    short [] cmapPlats = {
      Table.platformMicrosoft,
      Table.platformMacintosh,
      Table.platformMicrosoft,
    };

    short [] cmapEncodes = {
      Table.encodingUGL,
      Table.encodingRoman,
      Table.encodingUndefined,
    };

    boolean [] cmapIsUnicode = {
      true,
      false,
      false,
    };

    int tries = 0;

    CmapFormat cmapFmt = null;
    boolean hasTmark   = false;
    int spaceIndex     = 0;

    for (int t = 0; t < NUM_TRIES; t++) {

      cmapFmt = ttf.getCmapTable().getCmapFormat(cmapPlats[t], cmapEncodes[t]);
      // Find char codes
      if (cmapFmt != null) {
        for (int ch = 0; ch < 0xffff; ch++) {
          int index = cmapFmt.mapCharCode(ch);

          if (ch == 32) {
            spaceIndex = index;
          }
    
          if (index != 0) {
            if (ch == LFC_TMARK) {
              hasTmark = true;
            }
            codeTable[numCodes] = ch;
            indexTable[numCodes] = index;
            numCodes++;
            if (ch > maxCode) {
              maxCode = ch;
            }
          }
        }
      }
      if (numCodes > 1) {
        break;
      }
      isUnicode = cmapIsUnicode[t];
    }

    if (cmapFmt == null) {
      throw new Exception("Can't find a cmap table in " + path);
    }

    if (!hasTmark) {
      if (LFC_TMARK > maxCode) {
        maxCode = LFC_TMARK;
      }

      codeTable[numCodes] = LFC_TMARK;
      indexTable[numCodes] = spaceIndex;
      numCodes++;
    }

    if (isUnicode)
      flags |= UNICODE;
    else 
      flags |= ANSI;
  
    boolean useWideCodes = (maxCode > 255);
    if (useWideCodes)
      flags |= WIDE_CODES;

    GlyfTable glyfTable = (GlyfTable)ttf.getTable(
        org.apache.batik.svggen.font.table.Table.glyf);

    int        numGlyphs = numCodes;
    Shape []     shapeTable  = new Shape[numGlyphs];
    Rectangle2D []   boundsTable = new Rectangle2D[numGlyphs];

    int unitsPerEm = headTable.getUnitsPerEm();
    double factor = (double)FFT_UnitsPerEm / (double)unitsPerEm;

    // Get glyph shapes, and bounds.
    for (int i = 0; i < numGlyphs; i++) {
      int      index = indexTable[i];
      int      code  = codeTable[i];
      GlyfDescript glyf  = glyfTable.getDescription(index);
      TTFGlyph   glyph = null;

      if (glyf != null) {
        glyph = new TTFGlyph(glyf);
        glyph.scale(factor);
      } else {
      }


      Shape shape = new Shape();
      shape.newStyleBlock();
      convertGlyphToShape(glyph, shape);
      shapeTable[i] = shape;
      
      int x, w, y, h;

      if (glyf != null) {
        x = (int)Math.round(glyf.getXMinimum() * factor);
        y = (int)Math.round(glyf.getYMaximum() * -factor);
        w = (int)Math.round((glyf.getXMaximum() - glyf.getXMinimum()) * factor);
        h = (int)Math.round((glyf.getYMaximum() - glyf.getYMinimum()) * factor);
      } else {
        // Heuristic that hopefully works out ok for
        // missing glyfs.  First try space.  Then try index0
        glyf = glyfTable.getDescription(spaceIndex);
        if (glyf == null) {
          glyf = glyfTable.getDescription(0);
        }
        if (glyf != null) {
          w = (int)Math.round((glyf.getXMaximum() - glyf.getXMinimum()) * factor);
        } else {
          w = 0;
        }
        x = y = h = 0;
      }
      boundsTable[i] = new Rectangle(x, y, w, h);
      shape.setBounds(boundsTable[i]);
    }

    // Create a 40K buffer for generating the FFT
    FlashOutput buf = new FlashOutput( 40*1024 ); 

    // write header.
    final int TWIP = 20;

    buf.writeByte( 'F' );
    buf.writeByte( 'W' );
    buf.writeByte( 'S' );
    // write version
    buf.writeByte( 5 );
    // skip file size
    buf.skip(4);
    // write rect
    buf.write( new Rectangle(0, 0, 5*TWIP, 5*TWIP) );     
    // write frame rate
    buf.writeWord( 10 << 8 ); 

    // Frame count
    buf.writeWord(0);

    // Remember position
    int tagPos = buf.getPos();

    // Skip definefont2 tag header
    buf.skip(6);

    // Write font id
    buf.writeWord(1);

    // Skip flags
    int flagsPos = buf.getPos();
    buf.skip(2);

    // Write font name
    buf.writeStringL(fontName);

    // Write number of glyphs
    buf.writeWord(numGlyphs);

    int [] offsetTable = new int [numGlyphs]; 

    // Write out the converted shapes into a temporary buffer
    // And remember their offsets
    FlashOutput glyphBuf = new FlashOutput(20*1024);
    for( int i=0; i < numGlyphs; i++ ) {

      offsetTable[i] = glyphBuf.getPos();

      // 1 bit of line and fill
      glyphBuf.writeByte(0x11);

      ShapeRecords shapeRecords = shapeTable[i].getShapeRecords();
      shapeRecords.write(glyphBuf, 1, 1);
      // Write end of shape records
      glyphBuf.writeBits(0, 6);
      glyphBuf.flushBits();
    }

    // UseWideOffset if glyph buf + offset table + codeTable offset
    // is bigger than 16bit int
    boolean useWideOffsets = glyphBuf.getSize() + (numGlyphs+1)*2 > 0xffff;

    // Write offsets and codeTable offset
    if (useWideOffsets) {
      int offset = (numGlyphs+1)*4;
      flags |= WIDE_OFFSETS;
      for(int i = 0; i < numGlyphs; i++) {
        buf.writeDWord(offsetTable[i] + offset);
      }
      buf.writeDWord( glyphBuf.getSize() + offset );
    }
    else {
      int offset = (numGlyphs+1)*2;
      for(int i = 0; i < numGlyphs; i++) {
        buf.writeWord(offsetTable[i] + offset);
      }
      buf.writeWord( glyphBuf.getSize() + offset );
    }

    // Write shapes
    buf.writeFOB(glyphBuf);

    // Write out char code table. (glyph index to char code)
    for( int i=0; i<numCodes; i++ ) {
      if(useWideCodes) {
        buf.writeWord( codeTable[i] );
      } else {
        buf.writeByte( codeTable[i] );
      }
    }

    // Write ascent, descent, (external) leading
    int ascent = (int)Math.round((ttf.getAscent() * factor));
    int descent = (int)Math.round((ttf.getDescent() * -factor));
    int leading = ascent + descent - FFT_UnitsPerEm;

    buf.writeWord(ascent );
    buf.writeWord(descent);
    buf.writeWord(leading);

    // Write advance table 
    for( int i=0; i<numCodes; i++ )  {
      int index = indexTable[i];
      buf.writeWord((int)Math.round(hmtxTable.getAdvanceWidth(index) *factor));
    }

    // Write bounds table
    for( int i=0; i<numCodes; i++ ) {
      buf.write( boundsTable[i] );
    }

    // Write kerning tables 
    int nKern = 0;

    KernTable kernTable = (KernTable)ttf.getTable(Table.kern);
    // TODO: [2003-11-05 bloch] this should be passed in as an argument and taken
    // from the font definition in the LZX file
    boolean doKern = true;

    if (kernTable != null) {
      if (doKern) {
        KernSubtable kst = kernTable.getSubtable(0);
        nKern = kst.getKerningPairCount();
        int goodKern = nKern;
        for (int i = 0; i < nKern; i++) {
          if (kst.getKerningPair(i).getValue() == 0) {
            goodKern--;
          }
        }
        buf.writeWord(goodKern);
        for (int i = 0; i < nKern; i++) {
          KerningPair pair = kst.getKerningPair(i);
          if (pair.getValue() != 0) {
            if (useWideCodes) {
              buf.writeWord( codeTable[pair.getLeft()] );
              buf.writeWord( codeTable[pair.getRight()] );
            } else {
              buf.writeByte( codeTable[pair.getLeft()] );
              buf.writeByte( codeTable[pair.getRight()] ) ;
            }
            buf.writeWord( (int)Math.round(pair.getValue()*factor) );
          }
        }
      } else {
      }
    } else {
      buf.writeWord( 0 );
    }

    // Write the DEFINEFONT2 tag
    int x = buf.getPos() - tagPos - 6;
    buf.writeLongTagAt(TAG_DEFINEFONT2, x, tagPos);
    // Write the flags
    buf.writeWordAt(flags, flagsPos);

    // Write the END tag
    END_TAG.write(buf);

    // Write the file size back at the beginning.
    int filesize = buf.getSize();
    buf.writeDWordAt( filesize, 4 );

    return buf.getInputStream();
  }

  private static void convertGlyphToShape(TTFGlyph glyph, Shape shape) {

    if (glyph == null) {
      return;
    }
    int firstIndex = 0;
    int count = 0;

    // Add each contour to the shape.
    for (int i = 0; i < glyph.getNumPoints(); i++) {
      count++;
      if (glyph.getPoint(i).endOfContour) {
        addContourToShape(shape, glyph, firstIndex, count);
        firstIndex = i + 1;
        count = 0;
      }
    }
  }

  private static void addContourToShape(Shape shape, 
      TTFGlyph glyph, int startIndex, int count) {

    // If this is a single point on it's own, we can't do anything with it
    if (glyph.getPoint(startIndex).endOfContour) {
      return;
    }

    int offset = 0;

    while (offset < count) {
      Point p0  = glyph.getPoint(startIndex + offset%count);
      Point p1  = glyph.getPoint(startIndex + (offset+1)%count);

      if (offset == 0) {
        shape.movePenTo(p0.x, p0.y);
        if (startIndex == 0) {
          StyleChangeRecord scr = new StyleChangeRecord();
          scr.setFlags(StyleChangeRecord.FILLSTYLE1 | 
                 StyleChangeRecord.LINESTYLE);
          scr.setFillStyle1(1);
          scr.setLineStyle(0);
          shape.getShapeRecords().addStyleChangeRecord(scr);
        }
      }

      if (p0.onCurve) {
        if (p1.onCurve) {
          shape.drawLineTo(p1.x, p1.y);
          offset++;
        } else {
          Point p2;
          p2 = glyph.getPoint(startIndex + (offset+2)%count);

          if (p2.onCurve) {
            shape.drawCurveTo(p1.x, p1.y, p2.x, p2.y);
          } else {
            shape.drawCurveTo(p1.x, p1.y,
                  midValue(p1.x, p2.x), 
                  midValue(p1.y, p2.y));
          }
          offset+=2;
        } 
      } else {
        if (!p1.onCurve) {
          shape.drawCurveTo(p0.x, p0.y,
                    midValue(p0.x, p1.x),
                    midValue(p0.y, p1.y));
        } else {
          shape.drawCurveTo(p0.x, p0.y, p1.x, p1.y);
        }
        offset++;
      }
    }
  }

  private static int midValue(int a, int b) {
    return (a + b) / 2;
  }
}
In order to compile it with java, it neets the batik-svggen.jar which can be found here.
javac -classpath .;\batik-current\batik-1.5.1\lib\batik-svggen.jar TTF2FFT.java
After compiling, it is executed as follows:
java -classpath .;\batik-current\batik-1.5.1\lib\batik-svggen.jar TTF2FFT \WINDOWS\Fonts\verdana.ttf verdana.fft
The first argument specifies the true type font to be converted while the second one is the name of the generated fft file.