package stephencarmody.fonttexture;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
public final class Texture extends JLabel {
private final class GlyphMetrics {
public float x, y, ascent, boundx, boundy, boundwidth, boundheight;
/** Sets metrics */
public void setMetrics(java.awt.font.GlyphMetrics gm) {
Rectangle2D bounds = gm.getBounds2D();
boundwidth = (float)(bounds.getWidth() + 2);
boundheight = (float)(bounds.getHeight() + 2);
// Check for start of new row
if ((nextx + boundwidth) > image.getWidth()) {
nexty += maxHeight;
nextx = maxHeight = 0;
}
ascent = (float)-bounds.getY();
x = nextx - (float)bounds.getX() + 1;
y = nexty + ascent + 1;
boundx = nextx;
boundy = nexty;
if (boundheight > maxHeight) {
maxHeight = boundheight;
}
nextx += boundwidth;
}
}
private static final long serialVersionUID = 0;
// Array of characters to draw to texture
private static char[] chars = "{|}$()[\\]j/@QABCDEFGHIJKLMNOPRSTUVWXYZ#%&0123456789bdfghiklpqy!?;tacemnorsuvwxz+:<>~',-.*^_`=\"".toCharArray();
// Image of texture, graphics and fontrender contexts
private BufferedImage image;
private Graphics2D g;
private FontRenderContext frc;
// Array of metrics
private GlyphMetrics[] metrics;
// Calculated size of font
private int size;
// The Font used
private Font font;
private String name;
private int style;
// Layout variables
private static float nextx, nexty, maxHeight;
/** Creates a new instance of Texture */
public Texture(int size) {
// Create and initialise array of GlyphMetrics
metrics = new GlyphMetrics[chars.length];
for ( int i = 0; i < chars.length; i++ ) {
metrics[i] = new GlyphMetrics();
}
setSize(size);
}
/**
* Sets the size of the texture.
*
* size is rounded to the nearest value that meets the rule:
*
* log(size) / log(2) = whole integer
*
* i.e. 2, 4, 8, 16, 32, 64, 128, 256, 512, etc...
*/
public int setSize(int size) {
double n = Math.log(size) / Math.log(2);
size = (int) Math.pow(2, Math.round(n));
if ( image != null && image.getWidth() == size ) return size;
image = new BufferedImage(size, size, BufferedImage.TYPE_BYTE_BINARY);
g = (Graphics2D) image.getGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
frc = g.getFontRenderContext();
layoutGlyphs();
return size;
}
/** Set the font. */
public void setFont(String name, int style) {
if ( this.name == name && this.style == style ) return;
this.name = name;
this.style = style;
layoutGlyphs();
}
/**
* Saves font texture to specified file.
*
* @throws IOException
*/
public void save(File file) throws IOException {
// Write the texture to the specified file
ImageIO.write(image, ImageFilter.getExtension(file), file);
// Open a data output stream on the file to append metrics
FileOutputStream fstream = new FileOutputStream(file, true);
DataOutputStream out = new DataOutputStream(fstream);
// Start with a marker so we can determine which PNG's are font textures
out.writeBytes("FTX");
// Write character|x|y|width|height|ascent for each glyph
for ( int i = 0; i < chars.length; i++ ) {
GlyphMetrics g = metrics[i];
out.writeByte(chars[i]);
out.writeInt(Math.round(g.boundx));
out.writeInt(Math.round(g.boundy));
out.writeByte(Math.round(g.boundwidth));
out.writeByte(Math.round(g.boundheight));
out.writeByte(Math.round(g.ascent));
}
// Close the stream
out.close();
}
/** Layouts out glyphs on the texture. */
private void layoutGlyphs() {
if ( name == null ) return;
GlyphVector glyphVector;
java.awt.font.GlyphMetrics gm = null;
int lowest = 1;
int highest = 200;
float overrun;
int lastsize = 0;
size = 100;
// Binary search for optimal font size
do {
font = new Font(name, style, size);
glyphVector = font.createGlyphVector(frc, chars);
nextx = maxHeight = 0;
nexty = 1;
for (int i = 0; i < chars.length; i++) {
gm = glyphVector.getGlyphMetrics(i);
metrics[i].setMetrics(gm);
}
overrun = (metrics[chars.length-1].boundy + maxHeight) - image.getHeight();
if ( overrun > 0 ) {
lastsize = highest = size;
} else {
lastsize = lowest = size;
}
size = (lowest + highest) / 2;
} while ( overrun > 0 || lastsize != size );
// Clear image, set font and colour
g.clearRect(0, 0, image.getWidth(), image.getHeight());
g.setFont(font);
g.setColor(Color.WHITE);
// Draw each glyph to the texture
for (int i = 0; i < chars.length; i++) {
g.drawChars(chars, i, 1, (int)Math.ceil(metrics[i].x),
(int)Math.ceil(metrics[i].y));
}
setIcon(new ImageIcon(image));
}
}