Introduction to Feathers Text Renderers

There are muliple different approaches to displaying text on the GPU, each with advantages and disadvantages. None of these approaches are ultimately better than the others. With that in mind, when Feathers needs to display text in a component, it provides APIs to allow you to choose the appropriate text renderer based on your project's requirements.

Different text renderers may be more appropriate for some situations than others. You should keep a number of factors in mind when choosing a text renderer, including (but not necessarily limited to) the following:

  • the length of the text
  • how often the text changes
  • the language of the text that needs to be displayed

These factors may impact things like performance and memory usage, depending on which text renderer that you choose. Since you can mix and match text renderers among different components within a single scene, you have the ability to fine-tune the entire scene for the best results. One component may perform best with one text renderer while another right next to it is better off with a different text renderer.

Feathers provides three different text renderers. We'll learn the capabilities of each, along with their advantages and disadvantages. These text renderers are listed below:

  • BitmapFontTextRenderer uses bitmap fonts to display characters as separate textured quads.
  • TextFieldTextRenderer uses flash.text.TextField to render text in software and the result is drawn to BitmapData and uploaded as a texture to the GPU.
  • TextBlockTextRenderer uses flash.text.engine.TextBlock to render text in software and the result is drawn to BitmapData and uploaded as a texture to the GPU.

We'll look at the capabilities of each text renderer in more detail a bit later, and we'll even consider options for creating custom text renderers.

The default text renderer factory

In many cases, most of the components in your app will use the same type of text renderer. To keep from repeating yourself by passing the same factory (a function that creates the text renderer) to each component separately, you can specify a global default text renderer factory to tell all Feathers components in your app how to create a new text renderer. Then, if some of your components need a different text renderer, you can pass them a separate text renderer factory that will override the default one.

By default, the default text renderer factory returns a BitmapFontTextRenderer that renders bitmap fonts. For games, one of the primary targets for Starling and Feathers, bitmap fonts are often a good choice because they tend to display shorter strings that change a lot.

However, when using a theme, you should check which text renderer is selected as the default. Themes will often embed a custom font, and it is completely up to the theme which text renderer it wants to use to render that font. Many of the Feathers example apps use vector fonts to easily support many different languages and text styles.

When an individual component doesn't have a custom text renderer factory specified, it calls the function FeathersControl.defaultTextRendererFactory(). A button's text, a header's title, and a toggle switch's on and off text are all examples of places where the default text renderer will be used if a custom text renderer is not specified.

FeathersControl.defaultTextRendererFactory is a static variable that may be changed to a different function, as needed. The default implementation of this function looks like this:

function():ITextRenderer
{
    return new BitmapFontTextRenderer();
}

If you would prefer to use a different text renderer as the default in your app, you can easily change the variable to point to a different function. For instance, you might want to add this code to your application to use the TextBlockTextRenderer instead:

FeathersControl.defaultTextRendererFactory = function():ITextRenderer
{
    return new TextBlockTextRenderer();
};

Using a different text renderer on an individual component

You can tell a specific UI control not to use the default text renderer. For instance, on a button, you can pass in a labelFactory that will be used to create the button's label text renderer:

button.labelFactory = function():ITextRenderer
{
    return new TextFieldTextRenderer();
}

You can even apply font styles and other properties in the factory before returning the text renderer:

button.labelFactory = function():ITextRenderer
{
    var textRenderer:TextFieldTextRenderer = new TextFieldTextRenderer();
    textRenderer.textFormat = new TextFormat( "Source Sans Pro", 16, 0xffffff );
    textRenderer.embedFonts = true;
    return textRenderer;
}
Be careful, if you're using a theme. The theme applies its styles after this function returns. That means that the theme may end up replacing any properties that you set in the factory. See Extending Feathers Themes for details about how to customize a theme's styles.

Other components with the ability to display text may have a different name for their text renderer factories. For example, the factory for the title text renderer of a Header component is called titleFactory. Check the API documentation for a specific component to learn the names of any properties that allow you to change the factories for its text renderers.

The Label Component

The Label component is a component designed strictly to display text and only text. It's not a text renderer, but it has one child, and that child is a text renderer. In general, if you're looking for a Feathers component to display arbitrary text, you want a Label. Maybe you want to display a score in a game, or to label or form field, or perhaps to display a small paragraph of text to provide some instructions. That's what Label is for.

Why not just instantiate a text renderer, such as BitmapFontTextRenderer, directly? The Label component exists to help you abstract the choice of a text renderer away from your core application. For instance, it allows you to easily switch to a different themes that has different text renderers. You can also refactor a theme more easily if you decide that you're prefer to use a different text renderer than the one that you originally selected. If you simply instantiated a text renderer directly when you wanted to display arbitrary text, you need to make changes to many classes throughout your project instead of in one place separated from the rest of your application: the theme.

Put another way, you wouldn't want to do something like this when you want to add a label to a form item:

var label:TextFieldTextRenderer = new TextFieldTextRenderer();
label.text = "Email Address:";
this.addChild( label );

Instead, you should create a Label component:

var label:Label = new Label();
label.text = "Email Address:";
this.addChild( label );

The choice of a text renderer can be left to your theme instead of cluttering up the rest of the application, and text styles may be customized appropriately. For complete details about how to set properties on a Label component in your theme, see How to use the Feathers Label component.

BitmapFontTextRenderer

Bitmap fonts separate each character into sub-textures inside an atlas. These sub-textures are displayed as images placed next to each other to form words and paragraphs. If the text has a particularly stylized appearance, such as gradients and outlines, bitmap fonts provide the best performance because the styles can be calculated at design time rather than runtime.

Bitmap fonts are often great for games in situations when you need to display a limited amount of text around the edges of the screen. Values that change often, such as score, ammo, health, etc. can quickly swap out characters without uploading new textures to the GPU.

Bitmap fonts can sometimes be useful for longer passages of text (assuming that you need a uniform font style throughout the whole passage) because each character is a separate sub-texture and can be reused without requiring more memory on the GPU. However, since each new character is a new image to render on Starling's display list, the transformation calculations for all of those separate display objects may eventually overwhelm the CPU as the number of characters increases. It may require testing to determine how many characters a particular device's CPU can handle at once.

While the English language has only 26 letters in the alphabet (in addition to any punctuation and other supporting characters that you might need), some languages require many hundreds of characters. A texture that contains all of those characters may be impossible to use with bitmap fonts because it hits texture memory limits imposed by the Flash runtime or the GPU. In these situations, you may have no choice but to use device fonts.

Bitmap fonts may be scaled, but because they use bitmaps, only scaling down is recommended. Even then, you may lose out on text hinting that would make vector fonts more readable at smaller sizes. It's common to include separate font sizes as separate textures for bitmap fonts to achieve the best looking results, and that can require a lot of memory.

BitmapFontTextRenderer does not support multiple font styles. A BitmapFontTextRenderer must use a single bitmap font to render its entire text.

Using BitmapFontTextRenderer

To display text with bitmap fonts, use the BitmapFontTextRenderer class.

var textRenderer:BitmapFontTextRenderer = new BitmapFontTextRenderer();
textRenderer.text = "I am the very model of a modern Major-General";

Font styles may be customized by passing a BitmapFontTextFormat instance to the textFormat property. The first parameter of the constructor accepts either name of a font registered with TextField.registerBitmapFont() or any BitmapFont instance, regardless of whether it has been registered or not. Additionally, you may specify the font size (or set it to NaN to use the original font size and the color of the text. The text color is applied using Starling's tinting capabilities). Finally, you may specify the alignment of the text as the final argument to the constructor.

textRenderer.textFormat = new BitmapFontTextFormat( "FontName", 14, 0xcccccc, TextFormatAlign.CENTER );

To use wordWrap, set it to true and give the BitmapFontTextRenderer (or a Label that is using a BitmapFontTextRenderer) a width or maxWidth value. You can also add manual line breaks using the \n character.

textRenderer.width = 100;
textRenderer.wordWrap = true;

BitmapFontTextRenderer provides a number of other advanced properties that may be customized, but aren't included in this quick introduction. For complete details about available properties, please take a look at the API reference.

TextFieldTextRenderer

flash.text.TextField may be used to render device fonts or embedded fonts using a software-based vector renderer. Inside Feathers, this text is drawn to BitmapData and uploaded as a texture to the GPU. You can use fonts installed on the target device, or you can embed the glyphs from a TTF or OTF font.

Since embedded vector fonts require less memory than embedded bitmap fonts, you may still be able to use custom fonts with languages with too many characters for bitmap fonts. However, even when the vector glyphs require too much memory, you can always fall back to using device fonts (the fonts installed on the user's operating system) to draw your text. For some languages, device fonts may be the only option.

Changing vector-based text on the GPU is slower than with bitmap fonts because the text needs to be redrawn to BitmapData and then it needs to be uploaded to a texture on the GPU. However, once this texture is on the GPU, performance will be very smooth as long as the text doesn't change again. For text that changes often, the texture upload time may become a bottleneck.

Because each passage of vector text needs to be drawn to BitmapData, each separate renderer requires its own separate texture on the GPU. This results in more state changes and draw calls, which can create more work for the GPU and affect performance if you have many different instances of TextFieldTextRenderer on screen at the same time.

flash.text.TextField has some known issues and limitations:

  • TextField may render incorrectly when drawn to BitmapData immediately after its properties have been changed. As a workaround, TextFieldTextRenderer waits one frame before drawing to BitmapData and uploading as a texture when the text or font styles are changed. Often, this delay will not be an issue, but it can be seen if watching closely.
  • TextField offers limited support for some languages, including right-to-left and bidirectional languages, and Flash Text Engine is recommended for these languages.

TextFieldTextRenderer supports a limited subset of HTML courtesy of flash.text.TextField. This may be used to render richer text with multiple font styles.

Using TextFieldTextRenderer

To render text in software with flash.text.TextField and display a snapshot of it as a texture on the GPU, use the TextFieldTextRenderer class.

var textRenderer:TextFieldTextRenderer = new TextFieldTextRenderer();
textRenderer.text = "About binomial theorem I'm teeming with a lot o' news";

Font styles may be customized using the native flash.text.TextFormat class. Many of the property names defined by TextField are duplicated on TextFieldTextRenderer, including (but not limited to) textFormat and embedFonts:

textRenderer.textFormat = new TextFormat( "Source Sans Pro", 16, 0xcccccc );
textRenderer.embedFonts = true;

To enable word-wrapping, pass in a width or maxWidth value and set wordWrap to true.

textRenderer.width = 100;
textRenderer.wordWrap = true;

To render the text property of the TextFieldTextRenderer using a limited subset of HTML, set the isHTML property to true:

textRenderer.text = "<span class='heading'>hello</span> world!";
textRenderer.isHTML = true;

TextFieldTextRenderer provides a number of other advanced properties that may be customized, but aren't included in this quick introduction. For complete details about available properties, please take a look at the API reference.

TextBlockTextRenderer

flash.text.engine.TextBlock may be used to render device fonts or embedded fonts using a software-based vector renderer. Inside Feathers, this text is drawn to BitmapData and uploaded as a texture to the GPU. You can use fonts installed on the target device, or you can embed the glyphs from a TTF or OTF font.

Since embedded vector fonts require less memory than embedded bitmap fonts, you may still be able to use custom fonts with languages with too many characters for bitmap fonts. However, even when the vector glyphs require too much memory, you can always fall back to using device fonts (the fonts installed on the user's operating system) to draw your text. For some languages, device fonts may be the only option.

Changing vector-based text on the GPU is slower than with bitmap fonts because the text needs to be redrawn to BitmapData and then it needs to be uploaded to a texture on the GPU. However, once this texture is on the GPU, performance will be very smooth as long as the text doesn't change again. For text that changes often, the texture upload time may become a bottleneck.

Because each passage of vector text needs to be drawn to BitmapData, each separate renderer requires its own separate texture on the GPU. This results in more state changes and draw calls, which can create more work for the GPU and affect performance if you have many different instances of TextBlockTextRenderer on screen at the same time.

Flash Text Engine may render a bit slower than flash.text.TextField sometimes. In general, this performance difference is negligible, and the more advanced capabilities of FTE are often more compelling than a minor risk of reduced performance.

TextBlockTextRenderer optionally supports rich text, but it needs to be constructed manually adding multiple TextElement objects, each with different ElementFormat values, to a GroupElement object. You may pass the GroupElement to the text renderer's content property. TextBlockTextRenderer does not support the simple subset of HTML that TextFieldTextRenderer can display.

Using TextBlockTextRenderer

To render text in software with flash.text.engine.TextBlock and display it as a texture on the GPU, use the TextBlockTextRenderer class.

var textRenderer:TextBlockTextRenderer = new TextBlockTextRenderer();
textRenderer.text = "I understand equations, both the simple and quadratical";

Many font styles may be customized using the native flash.text.engine.ElementFormat class. Pass an instance of ElementFormat to the elementFormat property:

var fontDescription:FontDescription = new FontDescription( "Source Sans Pro", FontWeight.NORMAL, FontPosture.NORMAL, FontLookup.EMBEDDED_CFF, RenderingMode.CFF, CFFHinting.NONE );
textRenderer.elementFormat = new ElementFormat( fontDescription, 16, 0xcccccc );

The first parameter to the ElementFormat constructor is a FontDescription object. This class is provided by Flash Text Engine to handle font lookup, including name, weight, posture, whether the font is embedded or not, and how the font is rendered.

Text alignment is not included in the FontDescription or the ElementFormat. Instead, set the textAlign property:

textRenderer.textAlign = TextBlockTextRenderer.TEXT_ALIGN_CENTER;

The TextBlockTextRenderer defines TEXT_ALIGN_CENTER and some other constants that the textAlign property accepts.

To enable word-wrapping, pass in a width or maxWidth value and set wordWrap to true.

textRenderer.width = 100;
textRenderer.wordWrap = true;

TextBlockTextRenderer provides a number of other advanced properties that may be customized, but aren't included in this quick introduction. For complete details about available properties, please take a look at the API reference.

Custom Text Renderers

If you'd like to use a different approach to rendering text, you may implement the ITextRenderer interface. This interface provides a simple API for passing a string to a text renderer to display and measuring the text. One example of a custom text renderer might be one that uses Text Layout Framework (TLF) to provide a number of advanced capabilities.

Unless your custom renderer is capable of drawing directly to the GPU, chances are that you will need to implement some form of texture snapshots, similar to the TextFieldTextRenderer or TextBlockTextRenderer. Since Feathers is open source, feel free to look through the source code for one of these text renderer classes for inspiration.

Alternatives

We have some other options for displaying text, including some options included in Feathers and some custom text renderers developed by the community. As with the core text renderers detailed above, these alternatives have their own advantages and disadvantages.

Feathers ScrollText Component

Sometimes, very long text may be too large for text renderers like TextFieldTextRenderer and TextBlockTextRenderer to display because the total width or height is so large that there isn't enough memory on the GPU to store the required textures. However, you may still want to use vector-based fonts in a flash.text.TextField.

In this case, you might consider using the ScrollText component instead. ScrollText displays a native flash.text.TextField overlay on the native stage above Starling and Stage 3D. Rather than being converted into a texture on the GPU, this text is rendered in software and displayed above Stage 3D on the classic display list. It may be constrained to a rectangle in Starling coordinates, clipped, and scrolled.

There are some disadvantages, though. Because it is drawn entirely by the software renderer, it may scroll slowly on mobile devices with high screen resolutions because the CPU may not be able to keep up with rendering so much text in software. Moreover, you will not be able to display Starling content above the ScrollText component. ScrollText is not rendered by the GPU, so it is completely outside of Starling and Stage 3D. ScrollText is always above Starling, and nothing inside Starling can appear on top. If you want to display something above ScrollText, it will also need to be on the native display list. This content will not be GPU accelerated, and it may perform poorly.

firetype

firetype is a third-party library that renders text on the GPU using polygons instead of textures. Text displayed with firetype is resolution independent and uses less CPU than software rendering. This library includes support for Starling.

Distance Field Fonts

DistanceFieldFont is a third-party Starling extension that implements distance fields for bitmap fonts. Distance fields allow bitmap fonts to use less texture memory and scale up much more smoothly than standard bitmap fonts. This extension includes a Feathers text renderer.

Draw Calls

Please see the FAQ: Draw Calls and Feathers Text article for a detailed explanation about how text in Starling and Feathers (and text on the GPU, in general) affects draw calls.

  feathers/text-renderers.txt · Last modified: 2014/10/27 18:21 by joshtynjala
 
Powered by DokuWiki