René Nyffenegger's collection of things on the web | |
René Nyffenegger on Oracle - Most wanted - Feedback
- Follow @renenyffenegger
|
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
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.
|