In this article, I’ll show how to render the text from fonts exported by my FontPacker in libGdx. These are the required steps to render those fonts.
- Understanding the FntPack format
- Parsing with SAXParser and read Glyphs
- Rendering Text
The [icode]PackedFont[/icode] class we develop in this tutorial is available as a paste.
Let’s start off by understanding the format.
[h1]Understanding the FntPack format[/h1]
FontPacker packs the whole font into an XML file with an extension of [icode].fntpack[/icode] Since these are XML files, let’s see an example font here.
<?xml version="1.0" encoding="UTF-8"?>
<FontPacker> <!-- /* Root element */ -->
<Font name="Name of font here"> <!-- /* Font tag with name */ -->
<Glyph char="A" <!-- /* Glyph tags with char attribute */ -->
data="Base64 encoded PNG image data here" <!-- /* Base64 encoded image data in PNG format */ -->
xadvance="33" /> <!-- /* The width of the character in pixels */ -->
</Font>
</FontPacker>
I’ve only included a sample file with same structure written by hand. The version produced by the tool doesn’t include any comments or have indentation. They are added just to make you understand the format easily.
[h1]Parsing the FntPack file[/h1]
Though LibGdx has a built-in reader, I’ve chosen to use the SAX parser since LibGdx’s XmlReader doesn’t support UTF-8 encoding. So I’m using the SAX parser. First, we need to create a SAXParser instance.
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
Next we create a new handler which parses the data with the parser.
DefaultHandler handler = new DefaultHandler()
{
@Override
public void startElement(String uri, String localName, String qName, Attributes attr)
{
// Read the font name
if (qName.equalsIgnoreCase("Font"))
{
name = attr.getValue("name");
}
// Read every glyph from the font.
if (qName.equalsIgnoreCase("Glyph"))
{
// Get the character value
char ch = attr.getValue("char").charAt(0);
// Get the advance width
int xadvance = Integer.parseInt(attr.getValue("xadvance"));
// Decode the glyph image
String data = attr.getValue("data");
byte[] png = Base64Coder.decode(data);
Texture tex = new Texture(new Pixmap(png, 0, png.length));
// Map the Glyph and AdvanceWidth with the character
glyphs.put(ch, tex);
advances.put(ch, xadvance);
}
}
};
This code creates a GdxException saying that “Texture’s width and height must be power of two”. To get rid of that, we need to add this line before creating the handler.
Texture.setEnforcePotImages(false);
This resizes the textures in memory to make them “power of two”. Next we call the parser’s parse method with content of the XML file as a string. Here [icode]handle[/icode] refers to the [icode]FileHandle[/icode] class part of LibGdx.
parser.parse(handle.read(), handler);
Next I’ve made methods [icode]getGlyph()[/icode], [icode]getAdvanceWidth()[/icode], [icode]getName()[/icode] and [icode]getLineHeight()[/icode] which contain the following code.
/**
* @param id The character id
* @return The Texture of the Glyph
*/
public Texture getGlyph(char id)
{
if (glyphs.containsKey(id))
{
return glyphs.get(id);
}
else
{
return glyphs.get(' ');
}
}
/**
* @param id The character id
* @return The advance width of that character
*/
public int getAdvanceWidth(char id)
{
if (advances.containsKey(id))
{
return advances.get(id);
}
else
{
return advances.get(' ');
}
}
/**
* @return The line height of this font
*/
public int getLineHeight()
{
return getGlyph(' ').getHeight();
}
/**
* @return The name of this font.
*/
public String getName()
{
return name;
}
In every font, the line-height is the height of the ‘space’ character. Next, we make the [icode]renderText()[/icode] method with the following code.
/**
* Renders a string at a position x, y using SpriteBatch.
* Carriage returns are ignored and you can use '\n' for
* new lines.
*
* @param text The text to be rendered
* @param x The x-coordinate of the view
* @param y The y-coordinate of the view
* @param batch The SpriteBatch to use to render textures
*/
public void renderText(String text, float x, float y, SpriteBatch batch)
{
// Local positions
float xpos = x;
float ypos = y;
// Loop over each char and render it.
for (char ch: text.toCharArray())
{
// Ignore carriage returns
if (ch == '\r')
{
continue;
}
// '\n' moves to new line
if (ch == '\n')
{
ypos -= getLineHeight();
xpos = x;
continue;
}
// Get the texture of a Glyph
Texture tex = getGlyph(ch);
// Render the glyph
batch.begin();
{
batch.draw(tex, xpos, ypos);
}
batch.end();
// Advance the position
xpos += getAdvanceWidth(ch);
}
}
It’s that simple. Now it’s time for testing a sample font. I’ve tested the font “Mistral”.
[h1]Rendering the text using the SpriteBatch[/h1]
Before rendering, I’ve loaded the font in the [icode]create()[/icode] method like this.
public void create()
{
font = new PackedFont(Gdx.files.internal("data/Mistral.fntpack"));
}
And in the render method,
@Override
public void render()
{
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
font.renderText("Hello World\nMade by Sri Harsha Chilakapati\nThis font is " + font.getName(), 40, 500, batch);
}
And voila, it produced the following output.