Tutorial #01: Bitmap Fonts

Bitmap fonts offer the possibility to display pre-built, multi-colored characters.

Using this method allows an exact fine-tuning of the design. However, the disadvantage is that all characters have to be provided as graphic resources.

Font Converter

In order to convert pre-build characters into a format suitable for textures, a graphic strip of all characters and a description of their allocation have to be created.

  • data/orig/arial_color_24.png
  • data/orig/arial_color_48.png
  • data/orig/arial_color.txt

Both of the PNG files contain the characters in different sizes. The text file includes the character allocation of the graphics in UTF-8 encoding.

The following script files (Unix/Windows) contain the calls which direct the font converter to create the corresponding textures and metadata.

  • scripts/convert_fonts.sh
  • scripts/convert_fonts.cmd

If the script files are now called, the following files are created:

  • data/packages/main.murlres/arial_color_24_glyphs.murl
  • data/packages/main.murlres/arial_color_24_map.png
  • data/packages/main.murlres/arial_color_48_glyphs.murl
  • data/packages/main.murlres/arial_color_48_map.png

These files can directly be used in the graph node.

Additionally, the following character information can be passed as parameter to the font_converter:

  • --space_width 10 (or –sw) Width of the space character in pixel
  • --digit_width 10 (or –dw) Fixed with of all digits (0–9) in pixel
  • --spacing -5 (or –s) Horizontal space of the characters to each other in pixel
  • --leading -5 (or –l) Vertical space of the characters to each other in pixel

There are several other parameters, which remain unmentioned in this example.

Attention should be paid to the fact that there is enough space between all characters in the strip in order to get recognized as individual characters. The minimum separator between two characters is one column of pixels with an alpha value of 0. In order to connect "separated" characters, an "invisible" connection with an alpha value of >=1 needs to be drawn.

Scene Graph

As a first step, bitmap font resources have to be added to the package.

<?xml version="1.0" ?>
<!-- Copyright 2013 Spraylight GmbH -->
<Package id="package_main">
    
    <!-- Font resources -->
    <Resource id="arial_color_24_glyphs" fileName="arial_color_24_glyphs.murl"/>
    <Resource id="arial_color_24_map"    fileName="arial_color_24_map.png"/>
    <Resource id="arial_color_48_glyphs" fileName="arial_color_48_glyphs.murl"/>
    <Resource id="arial_color_48_map"    fileName="arial_color_48_map.png"/>
    
    <!-- Graph resources -->
    <Resource id="graph_main"       fileName="graph_main.xml"/>
    <Resource id="graph_materials"  fileName="graph_materials.xml"/>
    <Resource id="graph_textures"   fileName="graph_textures.xml"/>
    
    <!-- Graph instances -->
    <Instance graphResourceId="graph_materials"/>
    <Instance graphResourceId="graph_textures"/>
    <Instance graphResourceId="graph_main"/>
    
</Package>

The instances for texture, material and main sub-graph are instantiated with the node <Instance> directly in the package.xml file.

Bitmap Font Texture Definition

In order to use bitmap font textures more than once, nodes are recommended for referencing.

<?xml version="1.0" ?>
<!-- Copyright 2013 Spraylight GmbH -->
<Graph>
    <Namespace id="textures" activeAndVisible="no">
        
        <Node id="arial_color_24">
            <FlatTexture id="arial_color_24_map"
            imageResourceId="package_main:arial_color_24_map"
            pixelFormat="R8_G8_B8_A8"
            useMipMaps="no"/>
            <TextureState textureId="arial_color_24_map" slot="0" unit="0"/>
        </Node>
        
        <Node id="arial_color_48">
            <FlatTexture id="arial_color_48_map"
            imageResourceId="package_main:arial_color_48_map"
            pixelFormat="R8_G8_B8_A8"
            useMipMaps="no"/>
            <TextureState textureId="arial_color_48_map" slot="0" unit="0"/>
        </Node>
        
    </Namespace>
</Graph>

Main Graph

<?xml version="1.0" ?>
<!-- Copyright 2013 Spraylight GmbH -->
<Graph>
    <Namespace id="main">
        
        <View id="view"/>
        
        <Camera id="camera"
        viewId="view"
        fieldOfViewX="400"
        nearPlane="400" farPlane="2500"
        clearColorBuffer="yes"/>
        <CameraTransform cameraId="camera"
        posX="0" posY="0" posZ="800"/>
        <CameraState cameraId="camera"/>
        
        <FixedParameters id="screen_parameters"/>
        <ParametersState parametersId="screen_parameters"/>
        
        <Transform id="position">
            
            <Reference targetId="/materials/state_plain_color"/>
            
            <FixedParameters diffuseColor="200i, 200i, 200i, 255i">
                <PlaneGeometry posX="0" posY="80"
                frameSizeX="500" frameSizeY="250"/>
            </FixedParameters>
            
            <FixedParameters diffuseColor="200i, 200i, 200i, 255i">
                <PlaneGeometry posX="0" posY="-150"
                frameSizeX="400" frameSizeY="130"/>
            </FixedParameters>
            
            <Reference targetId="/materials/state_plain_tex_color"/>
            
            <Reference targetId="/textures/arial_color_24"/>
            <TextGeometry id="edit_text"
            fontResourceId="package_main:arial_color_24_glyphs"
            containerSizeX="500" containerSizeY="250"
            posX="0" posY="80"
            text="Spraylight"/>
            
            <Reference targetId="/textures/arial_color_48"/>
            <TextGeometry id="info_text"
            fontResourceId="package_main:arial_color_48_glyphs"
            containerSizeX="400" containerSizeY="130"
            textAlignmentX="LEFT" textAlignmentY="CENTER"
            posX="0" posY="-150"/>
            
        </Transform>
        
    </Namespace>
</Graph>

TextGeometry Node

The TextGeometry node requires the corresponding texture with its characters to be active. This can be achieved with <Reference targetId="/textures/arial_color_24"/>.

The resource containing the character definitions of the texture is specified with the attribute fontResourceId="package_main:arial_color_24_glyphs".

A virtual container for text alignment can be defined with the attributes containerSizeX="500" and containerSizeY="250".

For the horizontal text alignment (textAlignmentX) the following values are available:

  • "CENTER" centered in the middle of the texture (default setting)
  • "LEFT" aligned to the left margin of the texture
  • "RIGHT" aligned to the right margin of the texture

For the vertical text alignment (textAlignmentY) the following values are available:

  • "CENTER" centered in the middle of the texture (default setting)
  • "TOP" aligned to the upper margin of the texture
  • "BOTTOM" aligned to the lower margin of the texture

It is possible to specify the text to be shown with the attribute text="…" directly in the definition of the graph or to change it afterwards in the program code.

Application

The bitmap font application provides a simple function to edit a text and to display information.

Logic Initialization

Bool App::BitmapFontLogic::OnInit(const Logic::IState* state)
{
    Graph::IRoot* root = state->GetGraphRoot();
    
    if (state->GetPlatformConfiguration()->IsTargetClassMatching(IEnums::TARGET_CLASS_HANDHELD))
    {
        Logic::TransformNode position;
        position.GetReference(root, "/main/position");
        if (position.IsValid())
        {
            position->SetPositionY(200);
        }
    }
    
    AddGraphNode(mEditTextNode.GetReference(root, "/main/edit_text"));
    AddGraphNode(mInfoTextNode.GetReference(root, "/main/info_text"));
    
    if (!AreGraphNodesValid())
    {
        return false;
    }
    
    mEditText = mEditTextNode->GetText();
    UpdateText(true);
    
    return true;
}

In order to make room for the on-screen keyboard on mobile devices, the position of all text texture is moved upward in the first code block.

Afterwards, the references to both of the text texture nodes are created and the processor base class is added.

The security query if (!AreGraphNodesValid()) is recommended, if nodes are accessed already during the initialization. Otherwise, the application might crash, if a node were not found, but used despite.

Finally, the edit text is read from the text node and the text display is updated.

Logic Processing

void App::BitmapFontLogic::OnProcessTick(const Logic::IState* state)
{
    Logic::IDeviceHandler* deviceHandler = state->GetDeviceHandler();
    
    if (!deviceHandler->IsKeyboardShowing())
    {
        state->GetDeviceHandler()->ShowKeyboard();
    }
    
    const StringArray& keys = deviceHandler->GetKeys();
    for (UInt32 i = 0; i < keys.GetCount(); i++)
    {
        Bool isPrintable = true;
        const String& utf8key = keys[i];
        
        IEnums::KeyCode keyCode = IEnums::KEYCODE_NONE;
        if (utf8key.GetLength() == 1)
        {
            keyCode = static_cast<IEnums::KeyCode>(utf8key[0]);
        }
        if (keyCode == IEnums::KEYCODE_BACKSPACE)
        {
            mEditText.RemoveLastUTF8();
        }
        else if (keyCode == IEnums::KEYCODE_RETURN)
        {
            mEditText += "\n";
        }
        else
        {
            if ((keyCode >= IEnums::KEYCODE_SPACE) || (keyCode == IEnums::KEYCODE_NONE))
            {
                if (mEditTextNode->IsCharacterPrintable(utf8key))
                {
                    mEditText += utf8key;
                }
                else
                {
                    isPrintable = false;
                }
            }
        }
        UpdateText(isPrintable);
    }
}

In the first code block we ensure that the on-screen keyboard is visible. If the keyboard is invisible, it will be displayed.

The second block implements a simple edit function. Only the input of new characters at the end of the text and the deletion of the last character will work.

Checking with if (mEditTextNode->IsCharacterPrintable(utf8key)) is important in order to determine, if it is possible to display the required character. If it is not possible, the character will not beattached. Furthermore, isPrintable is set to false to show the reason, why the character is ignored.

Text Display

void App::BitmapFontLogic::UpdateText(Bool isPrintable)
{
    if (isPrintable)
    {
        mEditTextNode->SetText(mEditText + "_");
    }
    else
    {
        mEditTextNode->SetText(mEditText + "_(non printable)");
    }
    
    String infoText;
    infoText += " " + Util::UInt32ToString(mEditText.GetLengthUTF8());
    infoText += " Characters\n";
    infoText += " " + Util::UInt32ToString(mEditText.GetLength());
    infoText += " Bytes";
    mInfoTextNode->SetText(infoText);
}

This auxiliary function shows the edited text with an attached underscore or with the comment "non printable", if a character cannot be displayed.

The last block shows information about the edited text. It can be seen that all characters are saved in UTF-8 encoding and do not necessarily have to be in strict conformity with the byte length of the text, e.g. an umlaut or certain symbols.

tut0201_bitmap_font.png
Bitmap Font output window


Copyright © 2011-2024 Spraylight GmbH.