| 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
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.
|