How to: Extend the Font Description Processor to Support Additional Characters

In a font description (.spritefont) file, the <CharacterRegions> area can be used to add additional characters to a font description, allowing an additional range of characters to be rendered using a SpriteFont.

For some languages this approach is not ideal. For example, Chinese and Japanese both have many thousands of characters. Adding the full range of characters to <CharacterRegions> would increase the size of the font asset, and the time required to build the font asset, dramatically. A better solution would add individual characters as needed. You can create a custom content processor to implement this solution.

This document describes the process of developing a custom content processor to add additional characters to a FontDescription object based on the text that is required by the game. In this example, all of the text that must be rendered by the game is contained in a file called messages.txt. The custom processor will add all of the characters contained in the text in this file to a FontDescription, and then process the resulting object in the standard way using the base FontDescriptionProcessor functionality. All of the characters in messages.txt will then be available to the resulting SpriteFont object at run time.

To specify the character regions and messages to process

  1. Add a new Sprite Font to a game project by right-clicking on the game project name in Solution Explorer and clicking Add, then clicking New Item. Select the Sprite Font template, then click Add to add the new sprite font to the game.
  2. The new sprite font (.spritefont) file will contain a <CharacterRegions> tag describing the range of characters to make available in the font. If you want access to the full range of ASCII printable characters, keep the default <CharacterRegions>. However, if you want to support only those characters specified in messages.txt and no additional characters, remove the regions specified in this area. Once you've removed the regions, the XML will look like this.

    
                <CharacterRegions/>
              
    
  3. Add a file named messages.txt to the game project. Right-click on the game project name in Solution Explorer and click Add, and then click New Item. Select the Text File template, enter messages.txt for the file name, and then click Add to add the text file to the game.
  4. In the new text file, enter any messages that will be printed by the font described in the sprite font file.

    Bb447751.caution(en-US,XNAGameStudio.10).gifCaution
    We will use the method File.ReadAllText to read the text in this file. This method requires a carriage return ("\r") or line feed ("\n") after the last string, so be sure to follow the last line of text in the file with a carriage return or line feed.

To create the new content processor project

The Content Pipeline is part of the build process and is separate from your game code. Therefore, you need to create a new assembly that contains the code developed in this topic. Creating this new assembly project is the first step in developing a new processor.

Bb447751.note(en-US,XNAGameStudio.10).gifNote
It is assumed that you have an existing game project that you will modify. For the purposes of this example, the game project is called FontGame.
  1. Add the new processor project to the game solution. From Solution Explorer, right-click the Solution node, click Add, and then click New Project. From this dialog box, select the Windows Game Library template, enter FontGameProcessors in the Name field, and then click OK.
  2. The new project automatically contains a reference to the XNA Framework run-time assemblies. However, this project also needs to reference the design–time Content Pipeline assembly. To add this assembly, right-click the References node of the new processor project and click Add Reference. From the .NET tab of this dialog box, click Microsoft.Xna.Framework.Content.Pipeline, and then click OK.

To extend the font processor

  1. Create a new type in the processors project called MyFontProcessor. In MyFontProcessor, add references to the appropriate namespaces.

    using System.IO;
    using Microsoft.Xna.Framework.Content.Pipeline;
    using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
    using Microsoft.Xna.Framework.Content.Pipeline.Processors;
    
  2. Add a ContentProcessor attribute to MyFontProcessor, and specify that it extends FontDescriptionProcessor.

    [ContentProcessor]
    public class MyFontProcessor : FontDescriptionProcessor
    {
    }
    
  3. Register a Content Pipeline dependency on messages.txt. This dependency tells the Content Pipeline that if messages.txt changes, the font must be rebuilt.

    public override SpriteFontContent Process(FontDescription input, ContentProcessorContext context)
    {
        string fullPath = Path.GetFullPath( "messages.txt" );
        context.AddDependency( fullPath );
    
  4. Read the contents of the file and add each letter to the input font one by one. Note that the Characters collection keeps track of duplicates automatically. It is not necessary for the user to make sure that each letter is added only once. The characters collection will contain only one instance of each character; no matter how many times Add has been called.

    string letters = File.ReadAllText( fullPath, System.Text.Encoding.UTF8 );
    
    foreach ( char c in letters )
    {
        input.Characters.Add( c );
    }
    
    Bb447751.note(en-US,XNAGameStudio.10).gifNote

    In this example, messages.txt has been saved with Unicode UTF-8 encoding, which is why this encoding format is specified in the call to File.ReadAllText. The default file encoding format for text files that have been added to a Visual Studio project is Western European (Windows) encoding, corresponding to code page 1252. If your text file uses the default encoding, specify the character encoding as follows:

    string letters = File.ReadAllText( fullPath, System.Text.Encoding.GetEncoding( 1252 ) );
  5. Finally, call the existing Process method of the base FontDescriptionProcessor to build the font with the newly requested characters.

    return base.Process( input, context );
    

To associate the custom font processor with the sprite font

  1. Compile the solution to build MyFontProcessor.
  2. Add MyFontProcessor as an available content processor for the game. In Solution Explorer, right-click on the FontGame project, and then click Properties. Click the Content Pipeline tab, and then click the Add button. Navigate to the location of FontGameProcessors.dll, and then click Open.
  3. To ensure that the processors project is always up to date when the main game is built, create a project dependency. In Solution Explorer, right-click the solution name, and then click Project Dependencies. Select the check box next to FontGameProcessors, and then click OK to add a new dependency so that FontGame depends on FontGameProcessors.
  4. Finally, change the content processor for the .spritefont file from Sprite Font Description - XNA Framework to the newly created processor called MyFontProcessor. Select the .spritefont file, and then in the Properties window, choose MyFontProcessor from the drop-down list associated with the ContentProcessor field.

When you build the solution, the new processor adds the characters in messages.txt to the list of characters available to the SpriteFont.

Bb447751.note(en-US,XNAGameStudio.10).gifTip

To debug a Content Pipeline importer or processor, add the following line to the processor code to launch the debugger.

System.Diagnostics.Debugger.Launch();

Complete Processor Class

// MyFontProcessor.cs
// Note:  This class file can be built as part of a Windows Game Library
//        project within your game solution.  In order to build it, though,
//        you must first add a reference to Microsoft.Xna.Framework.Content.Pipeline 
//        in the project. To to this, right-click on References in Solution
//        Explorer and click Add Reference. Then, in the .NET pane, scroll down
//        and select Microsoft.Xna.Framework.Content.Pipeline.
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;
using System.IO;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;

[ContentProcessor]
public class MyFontProcessor : FontDescriptionProcessor
{
    public override SpriteFontContent Process(FontDescription input, ContentProcessorContext context)
    {
        string fullPath = Path.GetFullPath( "messages.txt" );
        context.AddDependency( fullPath );

        string letters = File.ReadAllText( fullPath, System.Text.Encoding.UTF8 );

        foreach ( char c in letters )
        {
            input.Characters.Add( c );
        }

        return base.Process( input, context );
    }
}
Show: