Text Rendering¶
Flame has some dedicated classes to help you render text.
Text Components¶
The simplest way to render text with Flame is to leverage one of the provided text-rendering components:
TextComponent
for rendering a single line of textTextBoxComponent
for bounding multi-line text within a sized box, including the possibility of a typing effect. You can use thenewLineNotifier
to be notified when a new line is added. Use theonComplete
callback to execute a function when the text is completely printed.ScrollTextBoxComponent
enhances the functionality ofTextBoxComponent
by adding vertical scrolling capability when the text exceeds the boundaries of the enclosing box.
All components are showcased in this example.
TextComponent¶
TextComponent
is a simple component that renders a single line of text.
Simple usage:
class MyGame extends FlameGame {
@override
void onLoad() {
add(
TextComponent(
text: 'Hello, Flame',
position: Vector2.all(16.0),
),
);
}
}
In order to configure aspects of the rendering like font family, size, color, etc, you need to
provide (or amend) a TextRenderer
with such information; while you can read more details about
this interface below, the simplest implementation you can use is the TextPaint
, which takes a
Flutter TextStyle
:
final regular = TextPaint(
style: TextStyle(
fontSize: 48.0,
color: BasicPalette.white.color,
),
);
class MyGame extends FlameGame {
@override
void onLoad() {
add(
TextComponent(
text: 'Hello, Flame',
textRenderer: regular,
anchor: Anchor.topCenter,
position: Vector2(size.width / 2, 32.0),
),
);
}
}
You can find all the options under TextComponent’s API.
TextBoxComponent¶
TextBoxComponent
is very similar to TextComponent
, but as its name suggest it is used to render
text inside a bounding box, creating line breaks according to the provided box size.
You can decide if the box should grow as the text is written or if it should be static by the
growingBox
variable in the TextBoxConfig
. A static box could either have a fixed size (setting
the size
property of the TextBoxComponent
), or to automatically shrink to fit the text content.
In addition, the align
property allows you to control the horizontal and vertical alignment of
the text content. For example, setting align
to Anchor.center
will center the text within its
bounding box both vertically and horizontally.
If you want to change the margins of the box use the margins
variable in the TextBoxConfig
.
Finally, if you want to simulate a “typing” effect, by showing each character of the string one by
one as if being typed in real-time, you can provide the boxConfig.timePerChar
parameter.
Example usage:
class MyTextBox extends TextBoxComponent {
MyTextBox(String text) : super(
text: text,
textRenderer: tiny,
boxConfig: TextBoxConfig(timePerChar: 0.05),
);
final bgPaint = Paint()..color = Color(0xFFFF00FF);
final borderPaint = Paint()..color = Color(0xFF000000)..style = PaintingStyle.stroke;
@override
void render(Canvas canvas) {
Rect rect = Rect.fromLTWH(0, 0, width, height);
canvas.drawRect(rect, bgPaint);
canvas.drawRect(rect.deflate(boxConfig.margin), borderPaint);
super.render(canvas);
}
}
You can find all the options under TextBoxComponent’s API.
ScrollTextBoxComponent¶
The ScrollTextBoxComponent
is an advanced version of the TextBoxComponent
,
designed for displaying scrollable text within a defined area.
This component is particularly useful for creating interfaces where large amounts of text
need to be presented in a constrained space, such as dialogues or information panels.
Note that the align
property of TextBoxComponent
is not available.
Example usage:
class MyScrollableText extends ScrollTextBoxComponent {
MyScrollableText(Vector2 frameSize, String text) : super(
size: frameSize,
text: text,
textRenderer: regular,
boxConfig: TextBoxConfig(timePerChar: 0.05),
);
}
TextElementComponent¶
If you want to render an arbitrary TextElement, ranging from a single InlineTextElement to a
formatted DocumentRoot, you can use the TextElementComponent
.
A simple example is to create a DocumentRoot to render a sequence of block elements (think of an HTML “div”) containing rich text:
final document = DocumentRoot([
HeaderNode.simple('1984', level: 1),
ParagraphNode.simple(
'Anything could be true. The so-called laws of nature were nonsense.',
),
// ...
]);
final element = TextElementComponent.fromDocument(
document: document,
position: Vector2(100, 50),
size: Vector2(400, 200),
);
Note that the size can be specified in two ways; either via:
the size property common to all
PositionComponents
; orthe width/height included within the
DocumentStyle
applied.
An example applying a style to the document (which can include the size but other parameters as well):
final style = DocumentStyle(
width: 400,
height: 200,
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14),
background: BackgroundStyle(
color: const Color(0xFF4E322E),
borderColor: const Color(0xFF000000),
borderWidth: 2.0,
),
);
final document = DocumentRoot([ ... ]);
final element = TextElementComponent.fromDocument(
document: document,
style: style,
position: Vector2(100, 50),
);
See a more elaborate example of rich-text, formatted text blocks rendering.
For more details about the underlying mechanics of the text rendering pipeline, see “Text Elements, Text Nodes, and Text Styles” below.
Flame Markdown¶
In order to more easily create rich-text-based DocumentRoots, from simple strings with bold/italics
to complete structured documents, Flame provides the flame_markdown
bridge package that connects
the markdown
library with Flame’s text rendering infrastructure.
Just use the FlameMarkdown
helper class and the toDocument
method to convert a markdown string
into a DocumentRoot (which can then be used to create a TextElementComponent
):
import 'package:flame/text.dart';
import 'package:flame_markdown/flame_markdown.dart';
// ...
final component = await TextElementComponent.fromDocument(
document: FlameMarkdown.toDocument(
'# Header\n'
'\n'
'This is a **bold** text, and this is *italic*.\n'
'\n'
'This is a second paragraph.\n',
),
style: ...,
position: ...,
size: ...,
);
Infrastructure¶
If you are not using the Flame Component System, want to understand the infrastructure behind text rendering, want to customize fonts and styles used, or want to create your own custom renderers, this section is for you.
TextRenderer
: renderers know “how” to render text; in essence they contain the style information to render any stringTextElement
: an element is formatted, “laid-out” piece of text, include the string (“what”) and the style (“how”)
The following diagram showcases the class and inheritance structure of the text rendering pipeline:
TextRenderer¶
TextRenderer
is the abstract class used by Flame to render text. Implementations of TextRenderer
must include the information about the “how” the text is rendered. Font style, size, color, etc. It
should be able to combine that information with a given string of text, via the format
method, to
generate a TextElement
.
Flame provides two concrete implementations:
TextPaint
: most used, uses FlutterTextPainter
to render regular textSpriteFontRenderer
: uses aSpriteFont
(a sprite sheet-based font) to render bitmap textDebugTextRenderer
: only intended to be used for Golden Tests
But you can also provide your own if you want to extend to other customized forms of text rendering.
The main job of a TextRenderer
is to format a string of text into a TextElement
, that then can
be rendered onto the screen:
final textElement = textRenderer.format("Flame is awesome")
textElement.render(...)
However the renderer provides a helper method to directly create the element and render it:
textRenderer.render(
canvas,
'Flame is awesome',
Vector2(10, 10),
anchor: Anchor.topCenter,
);
TextPaint¶
TextPaint
is the built-in implementation of text rendering in Flame. It is based on top of
Flutter’s TextPainter
class (hence the name), and it can be configured by the style class
TextStyle
, which contains all typographical information required to render text; i.e., font size
and color, font family, etc.
Outside of the style you can also optionally provide one extra parameter which is the
textDirection
(but that is typically already set to ltr
or left-to-right).
Example usage:
const TextPaint textPaint = TextPaint(
style: TextStyle(
fontSize: 48.0,
fontFamily: 'Awesome Font',
),
);
Note: there are several packages that contain the class TextStyle
. We export the right one (from
Flutter) via the text
module:
import 'package:flame/text.dart';
But if you want to import it explicitly, make sure that you import it from
package:flutter/painting.dart
(or from material or widgets). If you also need to import dart:ui
,
you might need to hide its version of TextStyle
, since that module contains a different class with
the same name:
import 'package:flutter/painting.dart';
import 'dart:ui' hide TextStyle;
Following are some common properties of TextStyle
(see the full
list of TextStyle
properties):
fontFamily
: a commonly available font, like Arial (default), or a custom font added in your pubspec (see how to add a custom font).fontSize
: font size, in pts (default24.0
).height
: height of text line, as a multiple of font size (defaultnull
).color
: the color, as aui.Color
(default white).
For more information regarding colors and how to create them, see the Colors and Palette guide.
SpriteFontRenderer¶
The other renderer option provided out of the box is SpriteFontRenderer
, which allows you to
provide a SpriteFont
based off of a sprite sheet. TODO
DebugTextRenderer¶
This renderer is intended to be used for Golden Tests. Rendering normal font-based text in Golden Tests is unreliable due to differences in font definitions across platforms and different algorithms used for anti-aliasing. This renderer will render text as if each word was a solid rectangle, making it possible to test the layout, positioning and sizing of the elements without having to rely on font-based rendering.
Inline Text Elements¶
A TextElement
is a “pre-compiled”, formatted and laid-out piece of text with a specific styling
applied, ready to be rendered at any given position.
A InlineTextElement
implements the TextElement
interface and must implement their two methods,
one that teaches how to translate it around and another on how to draw it to the canvas:
void translate(double dx, double dy);
void draw(Canvas canvas);
These methods are intended to be overwritten by the implementations of InlineTextElement
, and
probably will not be called directly by users; because a convenient render
method is provided:
void render(
Canvas canvas,
Vector2 position, {
Anchor anchor = Anchor.topLeft,
})
That allows the element to be rendered at a specific position, using a given anchor.
The interface also mandates (and provides) a getter for the LineMetrics
object associated with
that InlineTextElement
, which allows you (and the render
implementation) to access sizing
information related to the element (width, height, ascend, etc).
LineMetrics get metrics;
Text Elements, Text Nodes, and Text Styles¶
While normal renderers always work with a InlineTextElement
directly, there is a bigger underlying
infrastructure that can be used to render more rich or formatter text.
Text Elements are a superset of Inline Text Elements that represent an arbitrary rendering block within a rich-text document. Essentially, they are concrete and “physical”: they are objects that are ready to be rendered on a canvas.
This property distinguishes them from Text Nodes, which are structured pieces of text, and from Text
Styles (called FlameTextStyle
in code to make it easier to work alongside Flutter’s TextStyle
),
which are descriptors for how arbitrary pieces of text ought to be rendered.
So, in the most general case, a user would use a TextNode
to describe a desired piece of rich
text; define a FlameTextStyle
to apply to it; and use that to generate a TextElement
. Depending
on the type of rendering, the TextElement
generated will be an InlineTextElement
, which brings
us back to the normal flow of the rendering pipeline. The unique property of the Inline-Text-type
element is that it exposes a LineMetrics that can be used for advanced rendering; while the other
elements only expose a simpler draw
method which is unaware of sizing and positioning.
However, the other types of Text Elements, Text Nodes, and Text Styles must be used if the intent is
to create an entire document (multiple blocks or paragraphs), enriched with formatted text. In order
to render an arbitrary TextElement, you can alternatively use the TextElementComponent
(see above).
Text Nodes and the Document Root¶
A DocumentRoot
is not a TextNode
(inheritance-wise) in itself but represents a grouping of
BlockNodes
that layout a “page” or “document” of rich text laid out in multiple blocks or
paragraphs. It represents the entire document and can receive a global Style.
The first step to define your rich-text document is to create a Node, which will likely be a
DocumentRoot
.
It will first contain the top-most list of Block Nodes that can define headers, paragraphs or columns.
Then each of those blocks can contain other blocks or the Inline Text Nodes, either Plain Text Nodes or some rich-text with specific formatting.
Note that the hierarchy defined by the node structure is also used for styling purposes as per
defined in the FlameTextStyle
class.
The actual nodes all inherit from TextNode
and are broken down by the following diagram:
(Flame) Text Styles¶
Text Styles can be applied to nodes to generate elements. They all inherit from FlameTextStyle
abstract class (which is named as is to avoid confusion with Flutter’s TextStyle
).
They follow a tree-like structure, always having DocumentStyle
as the root; this structure is
leveraged to apply cascading style to the analogous Node structure. In fact, they are pretty similar
to, and can be thought of as, CSS definitions.
The full inheritance chain can be seen on the following diagram:
Text Elements¶
Finally, we have the elements, that represent a combination of a node (“what”) with a style (“how”), and therefore represent a pre-compiled, laid-out piece of rich text to be rendered on the Canvas.
Inline Text Elements specifically can alternatively be thought of as a combination of a
TextRenderer
(simplified “how”) and a string (single line of “what”).
That is because an InlineTextStyle
can be converted to a specific TextRenderer
via the
asTextRenderer
method, which is then used to lay out each line of text into a unique
InlineTextElement
.
When using the renderer directly, the entire layout process is skipped, and a single
TextPainterTextElement
or SpriteFontTextElement
is returned.
As you can see, both definitions of an Element are, essentially, equivalent, all things considered. But it still leaves us with two paths for rendering text. Which one to pick? How to solve this conundrum?
When in doubt, the following guidelines can help you picking the best path for you:
for the simplest way to render text, use
TextPaint
(basic renderer implementation)you can use the FCS provided component
TextComponent
for that.
for rendering Sprite Fonts, you must use
SpriteFontRenderer
(a renderer implementation that accepts aSpriteFont
);for rendering multiple lines of text, with automatic line breaks, you have two options:
use the FCS
TextBoxComponent
, which uses any text renderer to draw each line of text as an Element, and does its own layout and line breaking;use the Text Node & Style system to create your pre-laid-out Elements. Note: there is no current FCS component for it.
finally, in order to have formatted (or rich) text, you must use Text Nodes & Styles.