package com.gebauz.tools;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Vector;
import javax.imageio.ImageIO;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.gebauz.Bauzoid.file.FileUtil;
import com.gebauz.Bauzoid.file.FileWriter;
import com.gebauz.Bauzoid.parser.ScanException;
import com.gebauz.Bauzoid.parser.Tokenizer;
public class FontConverter
{
public static final String LOG_TAG =
"FontPot";
public static boolean verbose =
true;
private static class CharacterRect
{
public float x
;
public float y
;
public float w
;
public float h
;
public CharacterRect
(float _x,
float _y,
float _w,
float _h
)
{
x = _x
;
y = _y
;
w = _w
;
h = _h
;
}
}
private static class CharacterOffset
{
public float x
;
public float y
;
public CharacterOffset
(float _x,
float _y
)
{
x = _x
;
y = _y
;
}
}
private static class KerningPair
{
public char first
;
public char second
;
public KerningPair
(char a,
char b
)
{
first = a
;
second = b
;
}
}
public static int getNextPot
(int number
)
{
int pot =
2;
while (pot
< number
)
{
pot
*=
2;
}
return pot
;
}
public static void convertFont
(FileHandle file
) throws ScanException
{
String fileContents = file.
readString();
Vector<Character> charList =
null;
Vector<Float> widthList =
null;
Vector<CharacterRect
> rectList =
null;
Vector<CharacterOffset
> offsetList =
null;
Vector<KerningPair
> kerningPairs =
new Vector<KerningPair
>();
Vector<Integer> kerningValues =
new Vector<Integer>();
String textureName =
"";
String texturePath =
"";
float ascent = 1.0f
;
float multiplier = 1.0f
;
BufferedImage srcImage =
null;
boolean convertGreyScaleToAlpha =
true;
//Font font = new Font(graphics);
//Texture texture = null;
try
{
Tokenizer tokenizer =
new Tokenizer
(fileContents
);
tokenizer.
setStringDelimiter(new char[] {'\'',
'"'} );
while (!tokenizer.
checkNoMoreTokens())
{
String identifier = tokenizer.
readIdentifier();
if (identifier.
equalsIgnoreCase("Define"))
{
// Define
String define = tokenizer.
readIdentifier();
if (define.
equalsIgnoreCase("CharList"))
{
charList = parseCharList
(tokenizer
);
}
else if (define.
equalsIgnoreCase("WidthList"))
{
widthList = parseWidthList
(tokenizer
);
}
else if (define.
equalsIgnoreCase("RectList"))
{
rectList = parseRectList
(tokenizer
);
}
else if (define.
equalsIgnoreCase("OffsetList"))
{
offsetList = parseOffsetList
(tokenizer
);
}
else if (define.
equalsIgnoreCase("KerningPairs"))
{
kerningPairs = parseKerningPairs
(tokenizer
);
}
else if (define.
equalsIgnoreCase("KerningValues"))
{
kerningValues = parseKerningValues
(tokenizer
);
}
tokenizer.
readToken(";");
}
else if (identifier.
equalsIgnoreCase("CreateLayer"))
{
tokenizer.
readIdentifier();
tokenizer.
readToken(";");
}
else if (identifier.
equalsIgnoreCase("LayerSetImage"))
{
tokenizer.
readIdentifier();
// TODO: actually extract path from given file
textureName = tokenizer.
readString();
texturePath = FileUtil.
extractPath(file.
path()) +
"/_" + textureName +
".png";
try
{
srcImage =
ImageIO.
read(new File(texturePath
));
}
catch (IOException e
)
{
throw new ScanException
(LOG_TAG,
"IOException: could not read file '" + texturePath +
"': " + e.
getMessage());
}
//texture = new Texture(Gdx.files.internal(filename));
tokenizer.
readToken(";");
}
else if (identifier.
equalsIgnoreCase("LayerRegionMultiplier"))
{
tokenizer.
readIdentifier();
multiplier = tokenizer.
readNumber();
tokenizer.
readToken(";");
}
else if (identifier.
equalsIgnoreCase("LayerSetAscent"))
{
tokenizer.
readIdentifier();
ascent = tokenizer.
readNumber();
//font.setAscent(ascent);
tokenizer.
readToken(";");
}
else if (identifier.
equalsIgnoreCase("LayerSetCharWidths"))
{
tokenizer.
readIdentifier();
if (tokenizer.
checkIdentifier())
{
tokenizer.
readIdentifier();
tokenizer.
readIdentifier();
}
else if (tokenizer.
checkToken("("))
{
Vector<Character> charListExtra = parseCharList
(tokenizer
);
Vector<Float> widthListExtra = parseWidthList
(tokenizer
);
charList.
addAll(charListExtra
);
widthList.
addAll(widthListExtra
);
}
tokenizer.
readToken(";");
}
else if (identifier.
equalsIgnoreCase("LayerSetImageMap"))
{
tokenizer.
readIdentifier();
tokenizer.
readIdentifier();
tokenizer.
readIdentifier();
tokenizer.
readToken(";");
}
else if (identifier.
equalsIgnoreCase("LayerSetCharOffsets"))
{
tokenizer.
readIdentifier();
tokenizer.
readIdentifier();
tokenizer.
readIdentifier();
tokenizer.
readToken(";");
}
else if (identifier.
equalsIgnoreCase("LayerSetAscentPadding"))
{
tokenizer.
readIdentifier();
tokenizer.
readNumber();
tokenizer.
readToken(";");
}
else if (identifier.
equalsIgnoreCase("LayerSetLineSpacingOffset"))
{
tokenizer.
readIdentifier();
tokenizer.
readNumber();
tokenizer.
readToken(";");
}
else if (identifier.
equalsIgnoreCase("LayerSetPointSize"))
{
tokenizer.
readIdentifier();
tokenizer.
readNumber();
tokenizer.
readToken(";");
}
else if (identifier.
equalsIgnoreCase("SetDefaultPointSize"))
{
tokenizer.
readNumber();
tokenizer.
readToken(";");
}
else if (identifier.
equalsIgnoreCase("LayerSetKerningPairs"))
{
tokenizer.
readIdentifier();
tokenizer.
readIdentifier();
tokenizer.
readIdentifier();
tokenizer.
readToken(";");
}
else if (identifier.
equalsIgnoreCase("ConvertGreyScaleToAlpha"))
{
int num =
(int)tokenizer.
readNumber();
convertGreyScaleToAlpha = num
!=
0;
tokenizer.
readToken(";");
}
}
}
catch (ScanException ex
)
{
ex.
log(LOG_TAG
);
}
{
// checks
if (srcImage ==
null)
{
throw new ScanException
(LOG_TAG,
"No font texture specified!");
}
if (widthList.
size() != charList.
size())
{
throw new ScanException
(LOG_TAG,
"Character and Width list do not match in size!");
}
if (rectList.
size() != offsetList.
size())
{
throw new ScanException
(LOG_TAG,
"Rect and Offset Lists do not match in size!");
}
if (kerningValues.
size() != kerningPairs.
size())
{
throw new ScanException
(LOG_TAG,
"Kerning Values list and Kerning Pairs list do not match in size!");
}
}
int type = srcImage.
getType();
final int MAX_WIDTH =
512;
final int CHAR_MARGINS =
1;
int maxCharHeight =
0;
int w =
0;
int numRows =
1;
// get max height of characters
for (int i =
0; i
< rectList.
size(); i++
)
{
CharacterRect ch = rectList.
elementAt(i
);
if (ch.
h > maxCharHeight
)
maxCharHeight =
(int)ch.
h;
// accumulate to find out how many rows we need
if ((w + ch.
w + CHAR_MARGINS
) > MAX_WIDTH
)
{
w =
0;
numRows++
;
}
w += ch.
w + CHAR_MARGINS
;
}
int finalTextureWidth = MAX_WIDTH
;
int finalTextureHeight = getNextPot
(maxCharHeight
* numRows
);
BufferedImage destImage =
new BufferedImage(finalTextureWidth, finalTextureHeight,
BufferedImage.
TYPE_4BYTE_ABGR);
Vector<CharacterRect
> rectListNew =
new Vector<CharacterRect
>();
int dx =
0;
int dy =
0;
for (int i =
0; i
< rectList.
size(); i++
)
{
CharacterRect ch = rectList.
elementAt(i
);
// if this character exceeds max width, start new line
if ((dx + ch.
w + CHAR_MARGINS
) > MAX_WIDTH
)
{
dx =
0;
dy += maxCharHeight
;
}
copyRect
(srcImage, destImage,
(int)ch.
x,
(int)ch.
y,
(int)ch.
w,
(int)ch.
h, dx, dy, convertGreyScaleToAlpha
);
rectListNew.
add(new CharacterRect
(dx, dy, ch.
w, ch.
h));
dx += ch.
w + CHAR_MARGINS
;
}
try
{
File outputfile =
new File(FileUtil.
extractPath(file.
path()) +
"/" + textureName +
".png");
ImageIO.
write(destImage,
"png", outputfile
);
}
catch (IOException e
)
{
throw new ScanException
(LOG_TAG,
"Could not write output file!");
}
// finally, write binary font file
String fontFileName = FileUtil.
extractPath(file.
path()) +
"/" + file.
nameWithoutExtension() +
".bzf";
try
{
FileHandle f = Gdx.
files.
absolute(fontFileName
);
FileWriter fw =
new FileWriter(f.
write(false));
fw.
writeInt(100); // Version Tag (int)
fw.
writeString(textureName +
".png"); // Texture name (String)
fw.
writeFloat(ascent
); // Ascent (float)
fw.
writeFloat(multiplier
); // Multiplier (float)
// Write character list (char * num)
fw.
writeInt(charList.
size()); // CharList size (int)
for (int i =
0; i
< charList.
size(); i++
)
{
Character c = charList.
elementAt(i
);
fw.
writeChar(c
);
}
// Write width list (float * num)
fw.
writeInt(widthList.
size()); // widthList size (int)
for (int i =
0; i
< widthList.
size(); i++
)
{
Float width = widthList.
elementAt(i
);
fw.
writeFloat(width
);
}
// Write rect list (CharRect * num)
fw.
writeInt(rectListNew.
size()); // rectList size (int)
for (int i =
0; i
< rectListNew.
size(); i++
)
{
CharacterRect r = rectListNew.
elementAt(i
);
fw.
writeFloat(r.
x);
fw.
writeFloat(r.
y);
fw.
writeFloat(r.
w);
fw.
writeFloat(r.
h);
}
// Write offset list (CharOffset * num)
fw.
writeInt(offsetList.
size()); // offsetList size (int)
for (int i =
0; i
< offsetList.
size(); i++
)
{
CharacterOffset o = offsetList.
elementAt(i
);
fw.
writeFloat(o.
x);
fw.
writeFloat(o.
y);
}
// number of kerning pairs
fw.
writeInt(kerningPairs.
size());
// Write kerning pairs + values
for (int i =
0; i
< kerningPairs.
size(); i++
)
{
KerningPair p = kerningPairs.
elementAt(i
);
Integer v = kerningValues.
elementAt(i
);
fw.
writeChar(p.
first);
fw.
writeChar(p.
second);
fw.
writeInt(v
);
}
fw.
close();
}
catch (IOException e
)
{
Gdx.
app.
log(LOG_TAG,
"Could not write file: " + fontFileName
);
}
//File fontFile = new File(fontFileName);
// calculate basic height of first character/ascent -> calc next power of two -> H
// initialize output array with 512xH
// rightExtent = 0;
// iterate character by character in charList
// get character's rightMost extent in rectList
// if rightMost extent exceeds 512
// -> new line, rightExtent <- 0, y += H
// resize array
//
// write character pixels to output PNG
//
//
// write binary array to BUfferedImage
// write info to binary file <filename>.bfont
}
private static void copyRect
(BufferedImage src,
BufferedImage dest,
int sx,
int sy,
int sw,
int sh,
int dx,
int dy,
boolean convertGreyScaleToAlpha
)
{
byte[] srcbuf =
((DataBufferByte)src.
getRaster().
getDataBuffer()).
getData();
byte[] destbuf =
((DataBufferByte)dest.
getRaster().
getDataBuffer()).
getData();
int width =
(int)sw
;
int height =
(int)sh
;
int dstoffs = dx + dy
* dest.
getWidth();
int srcoffs = sx + sy
* src.
getWidth();
for (int y =
0; y
< height
; y++
)
{
//System.arraycopy(srcbuf, srcoffs*4, destbuf, dstoffs*4, width*4);
// reinterpret into alpha channel
for (int x =
0; x
< width
; x++
)
{
if (convertGreyScaleToAlpha
)
{
byte b = srcbuf
[(srcoffs + x
) * 4 +
1]; // B
byte g = srcbuf
[(srcoffs + x
) * 4 +
2]; // G
byte r = srcbuf
[(srcoffs + x
) * 4 +
3]; // R
byte a =
(byte)((b + g + r
) /
3);
destbuf
[(dstoffs + x
) * 4 +
1] =
(byte)255;
destbuf
[(dstoffs + x
) * 4 +
2] =
(byte)255;
destbuf
[(dstoffs + x
) * 4 +
3] =
(byte)255;
//byte a = (byte) ((byte)(g + b + r) / 3);
//byte a = srcbuf[(srcoffs + x) * 4 + 0]; // A
destbuf
[(dstoffs + x
) * 4 +
0] = a
;
}
else
{
byte a = srcbuf
[(srcoffs + x
) * 4 +
0]; // A
byte b = srcbuf
[(srcoffs + x
) * 4 +
1]; // B
byte g = srcbuf
[(srcoffs + x
) * 4 +
2]; // G
byte r = srcbuf
[(srcoffs + x
) * 4 +
3]; // R
destbuf
[(dstoffs + x
) * 4 +
0] = a
;
destbuf
[(dstoffs + x
) * 4 +
1] = b
;
destbuf
[(dstoffs + x
) * 4 +
2] = g
;
destbuf
[(dstoffs + x
) * 4 +
3] = r
;
}
}
dstoffs += dest.
getWidth();
srcoffs += src.
getWidth();
}
/*private static void copySrcIntoDstAt(final BufferedImage src,
final BufferedImage dst, final int dx, final int dy) {
int[] srcbuf = ((DataBufferInt) src.getRaster().getDataBuffer()).getData();
int[] dstbuf = ((DataBufferInt) dst.getRaster().getDataBuffer()).getData();
int width = src.getWidth();
int height = src.getHeight();
int dstoffs = dx + dy * dst.getWidth();
int srcoffs = 0;
for (int y = 0 ; y < height ; y++ , dstoffs+= dst.getWidth(), srcoffs += width ) {
System.arraycopy(srcbuf, srcoffs , dstbuf, dstoffs, width);
}
}*/
}
private static Vector<Character> parseCharList
(Tokenizer t
) throws ScanException
{
Vector<Character> charList =
new Vector<Character>();
t.
readToken("(");
while (!t.
checkToken(")"))
{
String character = t.
readString();
if (character.
length() > 1)
throw new ScanException
("Character can only have length 1!", t.
getSurroundings());
charList.
add(character.
charAt(0));
if (t.
checkToken(")"))
break;
t.
readToken(",");
}
t.
readToken(")");
return charList
;
}
private static Vector<Float> parseWidthList
(Tokenizer t
) throws ScanException
{
Vector<Float> widthList =
new Vector<Float>();
t.
readToken("(");
while (!t.
checkToken(")"))
{
float width = t.
readNumber();
widthList.
add(width
);
if (t.
checkToken(")"))
break;
//Gdx.app.log(LOG_TAG, "[" + width + "]");
t.
readToken(",");
}
t.
readToken(")");
return widthList
;
}
private static Vector<CharacterRect
> parseRectList
(Tokenizer t
) throws ScanException
{
Vector<CharacterRect
> rectList =
new Vector<CharacterRect
>();
t.
readToken("(");
while (t.
checkToken("("))
{
t.
readToken("(");
float x = t.
readNumber();
t.
readToken(",");
float y = t.
readNumber();
t.
readToken(",");
float w = t.
readNumber();
t.
readToken(",");
float h = t.
readNumber();
rectList.
add(new CharacterRect
(x, y, w, h
));
t.
readToken(")");
if (!t.
checkToken(","))
break;
t.
readToken(",");
}
if (!t.
checkToken(")"))
throw new ScanException
("Syntax error in RectList!", t.
getSurroundings());
t.
readToken(")");
return rectList
;
}
private static Vector<CharacterOffset
> parseOffsetList
(Tokenizer t
) throws ScanException
{
Vector<CharacterOffset
> offsetList =
new Vector<CharacterOffset
>();
t.
readToken("(");
while (t.
checkToken("("))
{
t.
readToken("(");
float x = t.
readNumber();
t.
readToken(",");
float y = t.
readNumber();
offsetList.
add(new CharacterOffset
(x, y
));
t.
readToken(")");
if (!t.
checkToken(","))
break;
t.
readToken(",");
}
if (!t.
checkToken(")"))
throw new ScanException
("Syntax error in OffsetList!", t.
getSurroundings());
t.
readToken(")");
return offsetList
;
}
private static Vector<KerningPair
> parseKerningPairs
(Tokenizer t
) throws ScanException
{
Vector<KerningPair
> kerningPairs =
new Vector<KerningPair
>();
t.
readToken("(");
while (!t.
checkToken(")"))
{
String pair = t.
readString();
kerningPairs.
add(new KerningPair
(pair.
charAt(0), pair.
charAt(1)));
if (t.
checkToken(")"))
break;
//Gdx.app.log(LOG_TAG, "[" + width + "]");
t.
readToken(",");
}
t.
readToken(")");
return kerningPairs
;
}
private static Vector<Integer> parseKerningValues
(Tokenizer t
) throws ScanException
{
Vector<Integer> kerningValues =
new Vector<Integer>();
t.
readToken("(");
while (!t.
checkToken(")"))
{
int value =
(int)t.
readNumber();
kerningValues.
add(value
);
if (t.
checkToken(")"))
break;
//Gdx.app.log(LOG_TAG, "[" + width + "]");
t.
readToken(",");
}
t.
readToken(")");
return kerningValues
;
}
}