Java-Gaming.org Hi !
Featured games (83)
games approved by the League of Dukes
Games in Showcase (538)
Games in Android Showcase (132)
games submitted by our members
Games in WIP (600)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  Using FontPacker with libGdx  (Read 2323 times)
0 Members and 1 Guest are viewing this topic.
Offline SHC
« Posted 2013-08-02 05:30:05 »

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
PackedFont
class we develop in this tutorial is available as a paste.

Let's start off by understanding the format.

Understanding the FntPack format


FontPacker packs the whole font into an XML file with an extension of
.fntpack
Since these are XML files, let's see an example font here.

1  
2  
3  
4  
5  
6  
7  
8  
<?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.

Parsing the FntPack file


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.

1  
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();

Next we create a new handler which parses the data with the parser.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
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.

1  
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
handle
refers to the
FileHandle
class part of LibGdx.

1  
parser.parse(handle.read(), handler);

Next I've made methods
getGlyph()
,
getAdvanceWidth()
,
getName()
and
getLineHeight()
which contain the following code.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
/**
 * @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
renderText()
method with the following code.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
/**
 * 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".

Rendering the text using the SpriteBatch


Before rendering, I've loaded the font in the
create()
method like this.

1  
2  
3  
4  
public void create()
{
    font = new PackedFont(Gdx.files.internal("data/Mistral.fntpack"));
}

And in the render method,

1  
2  
3  
4  
5  
6  
7  
8  
@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.


Offline SHC
« Reply #1 - Posted 2013-08-02 05:30:48 »

It's completed now. Get the source in pastebin here http://pastebin.java-gaming.org/dd3d452676f

Offline davedes
« Reply #2 - Posted 2013-08-02 12:01:46 »

Wouldn't it be just easier to output BMFont text files? That way we can use BitmapFont in LibGDX without having to roll our own font renderer. It also means we can take advantage of BitmapFontCache (optimized glyph rendering), Label/Scene2D UI, etc.

Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline davedes
« Reply #3 - Posted 2013-08-02 12:08:17 »

Also, a single texture for each glyph? Ouch -- that is going to be a huge impact on performance, especially android. The point of a font packer is to pack the fonts into a single texture atlas.  Pointing

A sprite batch is intended to be used with texture atlases... See here for more info:
https://github.com/mattdesl/lwjgl-basics/wiki/Sprite-Batching
https://github.com/mattdesl/lwjgl-basics/wiki/LibGDX-Textures

Pages: [1]
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

rwatson462 (28 views)
2014-12-15 21:26:44

Mr.CodeIt (19 views)
2014-12-15 07:50:38

BurntPizza (37 views)
2014-12-10 10:41:13

BurntPizza (73 views)
2014-12-08 16:46:31

JscottyBieshaar (34 views)
2014-12-06 00:39:02

SHC (47 views)
2014-12-04 04:27:13

CopyableCougar4 (42 views)
2014-11-30 09:32:03

toopeicgaming1999 (110 views)
2014-11-27 03:22:04

toopeicgaming1999 (96 views)
2014-11-27 03:20:36

toopeicgaming1999 (29 views)
2014-11-27 03:20:08
Understanding relations between setOrigin, setScale and setPosition in libGdx
by mbabuskov
2014-10-10 10:35:00

Definite guide to supporting multiple device resolutions on Android (2014)
by mbabuskov
2014-10-03 10:36:02

List of Learning Resources
by Longor1996
2014-08-16 22:40:00

List of Learning Resources
by SilverTiger
2014-08-06 07:33:27

Resources for WIP games
by CogWheelz
2014-08-02 04:20:17

Resources for WIP games
by CogWheelz
2014-08-02 04:19:50

List of Learning Resources
by SilverTiger
2014-08-01 04:29:50

List of Learning Resources
by SilverTiger
2014-08-01 04:26:06
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!