This is a little class I hacked together for my game, and I think it might be useful enough to add to the slick library. It allows fonts to be scaled to any size with minimal effort and very low processing overhead.
It's heavily dependent on the AngelCodeFont written by Kev. It's essentially just a group of AngelFonts that are switched between seamlessly. To use this, you will need a bunch of fonts saved in heiro at different sizes, saved into a .jar. The contents of the Jar should look like this:
Code:
8.fnt
8.png
16.fnt
16.png
24.fnt
24.png
25.fnt
25.png
There are no constraints on which fonts you have in the directory; it will just scale the closest available font to the appropriate size if the desired height isn't there. Although, the more fonts you put in here, the cleaner the font will look when it renders. The overhead on initializing these things is negligible, so this really is a wonderful solution to the scaling font question.
I'm considering having the fonts load from some sort of archive file rather than a directory. If I set that up I'll post up a newer version here.
Code:
import org.newdawn.slick.Color;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.newdawn.slick.AngelCodeFont;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.SlickException;
import org.lwjgl.opengl.GL11;
/**
* A font implementation heavily dependent on AngelCodeFont. This is essentially
* a collection of AngelCodeFonts at different heights loaded from a Jar.
* It allows smooth and clean scaling of fonts to any height.
* The files should be named: "{8.fnt, 8.png, 9.fnt, 9.png, 10.fnt, 10.png...}".
* There is no need to include more than one AngelCodeFont in the jar. It will
* Automatically use the closest-fitting font.
* @author JoshuaD
*/
public class DivineFont {
//TODO: Add an option to render the font at a specific height
ArrayList <SizedAngelCodeFont> fonts;
int fontHeight;
public DivineFont(String JarFileName, int fontHeight ) throws Exception {
this(JarFileName, fontHeight, false);
}
public DivineFont(int fontHeight) {
fonts = new ArrayList<SizedAngelCodeFont>(100);
this.fontHeight = fontHeight;
}
public DivineFont(String jarFileName, int fontHeight, boolean caching ) throws Exception {
this.fontHeight = fontHeight;
JarFile jarfile = new JarFile(jarFileName);
Enumeration<JarEntry> em = jarfile.entries();
ArrayList<Integer> fontHeights = new ArrayList<Integer>(100);
for (Enumeration<JarEntry> em1 = jarfile.entries(); em1.hasMoreElements(); ) {
String fileName = em1.nextElement().toString();
if( fileName.endsWith(".fnt") ) {
fontHeights.add( Integer.parseInt(fileName.split("\\.")[0] ) );
}
}
fonts = new ArrayList<SizedAngelCodeFont>(100);
SizedAngelCodeFont buffer;
int number;
for(int k = 0, n = fontHeights.size(); k < n; k++ ) {
number = fontHeights.get(k);
InputStream fntFileStream = jarfile.getInputStream(jarfile.getEntry(number + ".fnt"));
InputStream pngFileStream = jarfile.getInputStream(jarfile.getEntry(number + ".png"));
buffer = new SizedAngelCodeFont( new AngelCodeFont(number + ".png", fntFileStream, pngFileStream ), number );
fonts.add(buffer);
}
Collections.sort(fonts);
}
private static void copyFile(DataInputStream in, DataOutputStream out, int length) throws Exception {
byte[] data = new byte[length];
int bytesRead = 0;
bytesRead = in.read(data);
out.write(data);
if( bytesRead != data.length ) System.out.println("We wrote less than we expected");
}
public void setHeight( int size ) {
this.fontHeight = size;
}
public void addFont(AngelCodeFont font, int height) {
SizedAngelCodeFont insertMe = new SizedAngelCodeFont( font, height);
int fontCount = fonts.size();
if( fontCount <= 0 ) {
fonts.add(insertMe);
return;
}
for( int k = 0; k < fontCount; k++ ) {
if( fonts.get(k).height >= height ) {
fonts.add( k, insertMe );
return;
}
}
}
public void addFont( String fntFile, String pngFile, int height ) throws SlickException {
addFont( new AngelCodeFont(fntFile, pngFile), height );
}
public void drawString(Graphics g, String string, int size, float x, float y, Color col ) {
drawString(g, string, size/((float)fontHeight), x, y, col);
}
public void drawString(Graphics g, String string, float scale, float x, float y, Color col ) {
int size = (int)(fontHeight * scale);
SizedAngelCodeFont closestMatch = getClosestMatch(size);
float fontScale = size / (float)closestMatch.height;
GL11.glPushMatrix();
GL11.glScalef(fontScale, fontScale, 0);
closestMatch.font.drawString( x / fontScale, y /fontScale, string, col );
GL11.glPopMatrix();
}
public void drawString(Graphics g, String string, float scale, float x, float y ) {
drawString(g, string, scale, x, y, g.getColor() );
}
public void drawHCenteredString(Graphics g, String string, float scale, float x, float y, Color col ) {
float width = getWidth(string, scale);
drawString(g, string, scale, x - width/2, y, col);
}
public void drawHCenteredString(Graphics g, String string, float scale, float x, float y ) {
drawHCenteredString(g, string, scale, x, y, g.getColor() );
}
private SizedAngelCodeFont getClosestMatch(int targetHeight) {
if(fonts.isEmpty()) return null;
for (int i = 0, n = fonts.size(); i < n; i++) {
int fontHeight = fonts.get(i).height;
if (fontHeight >= targetHeight) {
if (i > 0 && Math.abs(targetHeight - fonts.get(i - 1).height) < Math.abs(targetHeight - fontHeight)) i--;
return fonts.get(i);
}
}
return fonts.get(fonts.size() - 1);
}
//TODO: Fix these
public float getHeight(String text) {
SizedAngelCodeFont closestMatch= getClosestMatch(fontHeight);
float fontScale = fontHeight / (float)closestMatch.height;
return closestMatch.font.getHeight(text) * fontScale;
}
public float getHeight(String text, float scale) {
int targetHeight = (int)(fontHeight * scale);
SizedAngelCodeFont closestMatch = getClosestMatch(targetHeight);
float fontScale = targetHeight / (float)closestMatch.height;
return closestMatch.font.getHeight(text) * fontScale;
}
public float getWidth(String text) {
SizedAngelCodeFont closestMatch= getClosestMatch(fontHeight);
float fontScale = fontHeight / (float)closestMatch.height;
return closestMatch.font.getWidth(text) * fontScale;
}
public float getWidth(String text, float scale) {
int targetHeight = (int)(fontHeight * scale);
SizedAngelCodeFont closestMatch = getClosestMatch(targetHeight);
float fontScale = targetHeight / (float)closestMatch.height;
return closestMatch.font.getWidth(text) * fontScale;
}
public float getLineHeight() {
SizedAngelCodeFont closestMatch= getClosestMatch(fontHeight);
float fontScale = fontHeight / (float)closestMatch.height;
return closestMatch.font.getLineHeight() * fontScale;
}
public float getYOffset(String text) {
SizedAngelCodeFont closestMatch = getClosestMatch(fontHeight);
float fontScale = fontHeight / (float)closestMatch.height;
return closestMatch.font.getYOffset(text) * fontScale;
}
}
class SizedAngelCodeFont implements Comparable <SizedAngelCodeFont> {
public AngelCodeFont font;
public int height;
public SizedAngelCodeFont(AngelCodeFont font, int height ) {
this.font = font;
this.height = height;
}
public int compareTo(SizedAngelCodeFont target) {
if (height < target.height ) return -1;
if( height > target.height ) return 1;
else return 0;
}
}
Kev: If you decide to add it to the library, just let me know if you need anything from me. I'd be glad to donate the code.
Oh, as a small aside, this code might be broken:
Code:
public int getYOffset(String text) {
return fonts.get(fontHeight).getYOffset(text);
}
I wasn't sure what value this was getting, and so I wasn't sure if it should scale if the rest of the font does (like in getHeight() ). Kev?