refactor!: Kill TextRenderer, Long Live TextRenderer (#2683)

This will:

    kill the TextRenderer inheritance chain
    incorporate the functionality of the base TextRenderer in the base TextFormatter
    rename TextFormatter to TextRenderer and appropriate references

That is because both essentially do the same job; encompass the style (or "how") information about how to render text, but using two slightly different interfaces. While that could allow for more flexibility, it is a faux choice that needlessly complicates the pipeline. By having a single interface to comply with, we still allow for custom renders while at the same time making all the code downstream simpler to use and understand.
This commit is contained in:
Luan Nico
2023-08-27 12:47:01 -07:00
committed by GitHub
parent 88d3270e2f
commit a1cb9a06ad
25 changed files with 355 additions and 268 deletions

View File

@ -3,94 +3,70 @@
Flame has some dedicated classes to help you render text. Flame has some dedicated classes to help you render text.
## TextRenderer
`TextRenderer` is the abstract class used by Flame to render text. Flame provides one
implementation for this called `TextPaint` but anyone can implement this abstraction
and create a custom way to render text.
## 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.
Example usage:
```dart
const TextPaint textPaint = TextPaint(
style: TextStyle(
fontSize: 48.0,
fontFamily: 'Awesome Font',
),
);
```
Note: there are several packages that contain the class `TextStyle`, make sure that you import
either `package:flutter/material.dart` or `package:flutter/painting.dart` and if you also need to
import `dart:ui` you need to import it like this (since that contains another class that is also
named `TextStyle`):
```dart
import 'dart:ui' hide TextStyle;
```
Some common properties of `TextStyle` are the following (here is the
[full list](https://api.flutter.dev/flutter/painting/TextStyle-class.html)):
- `fontFamily`: a commonly available font, like Arial (default), or a custom font added in your
pubspec (see [here](https://docs.flutter.dev/cookbook/design/fonts) how to do it).
- `fontSize`: font size, in pts (default `24.0`).
- `height`: height of text line, as a multiple of font size (default `null`).
- `color`: the color, as a `ui.Color` (default white).
For more information regarding colors and how to create then, see the
[Colors and the Palette](palette.md) guide.
After the creation of the `TextPaint` object you can use its `render` method to draw strings on
a canvas:
```dart
textPaint.render(canvas, "Flame is awesome", Vector2(10, 10));
```
If you want to set the anchor of the text you can also do that in the render call, with the optional
`anchor` parameter:
```dart
textPaint.render(canvas, 'Flame is awesome', Vector2(10, 10), anchor: Anchor.topCenter);
```
## Text Components ## Text Components
Flame provides two text components that make it even easier to render text in your game: The simplest way to render text with Flame is to leverage one of the provided text-rendering
`TextComponent` and `TextBoxComponent`. components:
- `TextComponent` for rendering a single line of text
- `TextBoxComponent` for bounding multi-line text within a sized box, including the possibility of a
typing effect
Both components are showcased in [this
example](https://github.com/flame-engine/flame/blob/main/examples/lib/stories/rendering/text_example.dart).
### TextComponent ### TextComponent
`TextComponent` is a simple component that renders a single line of text. `TextComponent` is a simple component that renders a single line of text.
Example usage: Simple usage:
```dart ```dart
final style = TextStyle(color: BasicPalette.white.color); class MyGame extends FlameGame {
final regular = TextPaint(style: style); @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 ab out
this interface below, the simplest implementation you can use is the `TextPaint`, which takes a
Flutter `TextStyle`:
```dart
final regular = TextPaint(
style: TextStyle(
fontSize: 48.0,
color: BasicPalette.white.color,
),
);
class MyGame extends FlameGame { class MyGame extends FlameGame {
@override @override
void onLoad() { void onLoad() {
add(TextComponent(text: 'Hello, Flame', textRenderer: regular) add(
..anchor = Anchor.topCenter TextComponent(
..x = size.width / 2 // size is a property from game text: 'Hello, Flame',
..y = 32.0); textRenderer: regular,
anchor: Anchor.topCenter,
position: Vector2(size.width / 2, 32.0),
),
);
} }
} }
``` ```
You can find all the options under [TextComponent's
API](https://pub.dev/documentation/flame/latest/components/TextComponent-class.html).
### TextBoxComponent ### TextBoxComponent
@ -101,12 +77,15 @@ You can decide if the box should grow as the text is written or if it should be
`growingBox` variable in the `TextBoxConfig`. A static box could either have a fixed size (setting `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. 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 the horizontal and vertical alignment In addition, the `align` property allows you to control the the horizontal and vertical alignment of
of the text content. For example, setting `align` to `Anchor.center` will center the text within the text content. For example, setting `align` to `Anchor.center` will center the text within its
its bounding box both vertically and horizontally. bounding box both vertically and horizontally.
If you want to change the margins of the box use the `margins` variable in the `TextBoxConfig`. 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: Example usage:
```dart ```dart
@ -130,5 +109,219 @@ class MyTextBox extends TextBoxComponent {
} }
``` ```
Both components are showcased in an example You can find all the options under [TextBoxComponent's
[here](https://github.com/flame-engine/flame/blob/main/examples/lib/stories/rendering/text_example.dart) API](https://pub.dev/documentation/flame/latest/components/TextBoxComponent-class.html).
## 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 string
- `TextElement`: 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:
```mermaid
classDiagram
%% renderers
note for TextRenderer "This just the style (how).
It knows how to take a text string and create a TextElement.
`render` is just a helper to `format(text).render(...)`. Same for `getLineMetrics`."
class TextRenderer {
TextElement format(String text)
LineMetrics getLineMetrics(String text)
void render(Canvas canvas, String text, ...)
}
class TextPaint
class SpriteFontRenderer
class DebugTextRenderer
%% elements
class TextElement {
LineMetrics metrics
render(Canvas canvas, ...)
}
class TextPainterTextElement
TextRenderer --> TextPaint
TextRenderer --> SpriteFontRenderer
TextRenderer --> DebugTextRenderer
TextRenderer *-- TextElement
TextPaint *-- TextPainterTextElement
SpriteFontRenderer *-- SpriteFontTextElement
note for TextElement "This is the text (what) and the style (how);
laid out and ready to render."
TextElement --> TextPainterTextElement
TextElement --> SpriteFontTextElement
TextElement --> Others
```
### 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 Flutter `TextPainter` to render regular text
- `SpriteFontRenderer`: uses a `SpriteFont` (a spritesheet-based font) to render bitmap text
- `DebugTextRenderer`: 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:
```dart
final textElement = textRenderer.format("Flame is awesome")
textElement.render(...)
```
However the renderer provides a helper method to directly create the element and render it:
```dart
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:
```dart
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:
```dart
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:
```dart
import 'package:flutter/painting.dart';
import 'dart:ui' hide TextStyle;
```
Some common properties of `TextStyle` are the following (here is the [full
list](https://api.flutter.dev/flutter/painting/TextStyle-class.html)):
- `fontFamily`: a commonly available font, like Arial (default), or a custom font added in your
pubspec (see [here](https://docs.flutter.dev/cookbook/design/fonts) how to do it).
- `fontSize`: font size, in pts (default `24.0`).
- `height`: height of text line, as a multiple of font size (default `null`).
- `color`: the color, as a `ui.Color` (default white).
For more information regarding colors and how to create then, see the [Colors and the
Palette](palette.md) guide.
#### SpriteFontRenderer
The other renderer option provided out of the box is `SpriteFontRenderer`, which allows you to
provide a `SpriteFont` based off of a spritesheet. 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.
## Text Elements
Text Elements are "pre-compiled", formatted and laid-out pieces of text with a specific styling
applied, ready to be rendered at any given position.
`TextElement` implements the `Element` 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:
```dart
void translate(double dx, double dy);
void draw(Canvas canvas);
```
These methods are intended to be overwritten by the implementations of `TextElement` but probably
will not be called directly by users; because a convenient `render` method is provided:
```dart
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
`TextElement`, which allows you (and the `render` implementation) to access sizing information
related to the element (width, height, ascend, etc).
```dart
LineMetrics get metrics;
```
## Elements, Nodes, and Styles
While normal renderers always work with TextElements directly, there is a bigger underlying
infrastructure that can be used to render more rich or formatter text.
Elements are a superset of TextElements 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 Nodes, which are structured pieces of text, and from Styles,
which are descriptors for how arbitrary pieces of text ought to be rendered.
So a user would use Node to describe a desired document of rich text; define Styles to apply to it;
and use that to generate an Element. Depending on the type of rendering, the Element generated will
be a TextElement, which brings us back to the normal flow of the rendering pipeline. The unique
property of the 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 Elements, Nodes and Style must be used if the intent is to create an
entire Document, enriched with formatted text. Currently these extra features of the system are not
exposed through FCS, but can be used directly.
An example of such usages can be seen in [this
example](https://github.com/flame-engine/flame/blob/main/examples/lib/stories/rendering/rich_text_example.dart).

View File

@ -208,7 +208,7 @@ class MyKeyboardDetector extends HardwareKeyboardDetector
class KeyboardKey extends PositionComponent { class KeyboardKey extends PositionComponent {
KeyboardKey({required this.text, super.position}) { KeyboardKey({required this.text, super.position}) {
textElement = textRenderer.formatter.format(text); textElement = textRenderer.format(text);
width = textElement.metrics.width + padding.x; width = textElement.metrics.width + padding.x;
height = textElement.metrics.height + padding.y; height = textElement.metrics.height + padding.y;
textElement.translate( textElement.translate(

View File

@ -1,6 +1,7 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame/game.dart'; import 'package:flame/game.dart';
import 'package:flame/palette.dart'; import 'package:flame/palette.dart';
import 'package:flame/text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class TextExample extends FlameGame { class TextExample extends FlameGame {

View File

@ -48,5 +48,5 @@ export 'src/geometry/circle_component.dart';
export 'src/geometry/polygon_component.dart'; export 'src/geometry/polygon_component.dart';
export 'src/geometry/rectangle_component.dart'; export 'src/geometry/rectangle_component.dart';
export 'src/geometry/shape_component.dart'; export 'src/geometry/shape_component.dart';
export 'src/text/text_paint.dart'; export 'src/text/renderers/text_paint.dart';
export 'src/timer.dart'; export 'src/timer.dart';

View File

@ -18,4 +18,4 @@ export 'src/game/mixins/single_game_instance.dart';
export 'src/game/notifying_vector2.dart'; export 'src/game/notifying_vector2.dart';
export 'src/game/projector.dart'; export 'src/game/projector.dart';
export 'src/game/transform2d.dart'; export 'src/game/transform2d.dart';
export 'src/text/text_paint.dart'; export 'src/text/renderers/text_paint.dart';

View File

@ -1,5 +1,5 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame/src/text/text_renderer.dart'; import 'package:flame/text.dart';
/// The [FpsTextComponent] is a [TextComponent] that writes out the current FPS. /// The [FpsTextComponent] is a [TextComponent] that writes out the current FPS.
/// It has a [FpsComponent] as a child which does the actual calculations. /// It has a [FpsComponent] as a child which does the actual calculations.

View File

@ -273,7 +273,7 @@ class TextBoxComponent<T extends TextRenderer> extends TextComponent {
line = line.substring(0, nChars); line = line.substring(0, nChars);
} }
final textElement = textRenderer.formatter.format(line); final textElement = textRenderer.format(line);
final metrics = textElement.metrics; final metrics = textElement.metrics;
final position = Vector2( final position = Vector2(

View File

@ -42,7 +42,7 @@ class TextComponent<T extends TextRenderer> extends PositionComponent {
@internal @internal
void updateBounds() { void updateBounds() {
_textElement = _textRenderer.formatter.format(_text); _textElement = _textRenderer.format(_text);
final measurements = _textElement.metrics; final measurements = _textElement.metrics;
_textElement.translate(0, measurements.ascent); _textElement.translate(0, measurements.ascent);
size.setValues(measurements.width, measurements.height); size.setValues(measurements.width, measurements.height);

View File

@ -1,7 +0,0 @@
import 'package:flame/src/text/elements/text_element.dart';
/// [TextFormatter] is an abstract interface for a class that can convert an
/// arbitrary string of text into a renderable [TextElement].
abstract class TextFormatter {
TextElement format(String text);
}

View File

@ -1,28 +0,0 @@
import 'package:flame/src/text/elements/text_painter_text_element.dart';
import 'package:flame/src/text/formatters/text_formatter.dart';
import 'package:flutter/rendering.dart';
/// [TextPainterTextFormatter] applies a Flutter [TextStyle] to a string of
/// text, creating a [TextPainterTextElement].
class TextPainterTextFormatter extends TextFormatter {
TextPainterTextFormatter({
required this.style,
this.textDirection = TextDirection.ltr,
});
final TextStyle style; // NOTE: this is a Flutter TextStyle
final TextDirection textDirection;
@override
TextPainterTextElement format(String text) {
final tp = _textToTextPainter(text);
return TextPainterTextElement(tp);
}
TextPainter _textToTextPainter(String text) {
return TextPainter(
text: TextSpan(text: text, style: style),
textDirection: textDirection,
)..layout();
}
}

View File

@ -1,6 +1,6 @@
import 'package:flame/src/text/elements/text_element.dart'; import 'package:flame/src/text/elements/text_element.dart';
import 'package:flame/src/text/formatters/text_formatter.dart';
import 'package:flame/src/text/nodes/text_node.dart'; import 'package:flame/src/text/nodes/text_node.dart';
import 'package:flame/src/text/renderers/text_renderer.dart';
import 'package:flame/src/text/styles/document_style.dart'; import 'package:flame/src/text/styles/document_style.dart';
import 'package:flame/src/text/styles/flame_text_style.dart'; import 'package:flame/src/text/styles/flame_text_style.dart';
@ -20,11 +20,11 @@ class PlainTextNode extends TextNode {
class _PlainTextLayoutBuilder extends TextNodeLayoutBuilder { class _PlainTextLayoutBuilder extends TextNodeLayoutBuilder {
_PlainTextLayoutBuilder(this.node) _PlainTextLayoutBuilder(this.node)
: formatter = node.textStyle.asTextFormatter(), : renderer = node.textStyle.asTextRenderer(),
words = node.text.split(' '); words = node.text.split(' ');
final PlainTextNode node; final PlainTextNode node;
final TextFormatter formatter; final TextRenderer renderer;
final List<String> words; final List<String> words;
int index0 = 0; int index0 = 0;
int index1 = 1; int index1 = 1;
@ -38,7 +38,7 @@ class _PlainTextLayoutBuilder extends TextNodeLayoutBuilder {
int? tentativeIndex0; int? tentativeIndex0;
while (index1 <= words.length) { while (index1 <= words.length) {
final textPiece = words.sublist(index0, index1).join(' '); final textPiece = words.sublist(index0, index1).join(' ');
final formattedPiece = formatter.format(textPiece); final formattedPiece = renderer.format(textPiece);
if (formattedPiece.metrics.width > availableWidth) { if (formattedPiece.metrics.width > availableWidth) {
break; break;
} else { } else {

View File

@ -4,12 +4,12 @@ import 'dart:ui' hide LineMetrics;
import 'package:flame/src/text/common/line_metrics.dart'; import 'package:flame/src/text/common/line_metrics.dart';
import 'package:flame/src/text/common/sprite_font.dart'; import 'package:flame/src/text/common/sprite_font.dart';
import 'package:flame/src/text/elements/sprite_font_text_element.dart'; import 'package:flame/src/text/elements/sprite_font_text_element.dart';
import 'package:flame/src/text/formatters/text_formatter.dart'; import 'package:flame/src/text/renderers/text_renderer.dart';
/// [SpriteFontTextFormatter] will render text using a [SpriteFont] font, /// [SpriteFontRenderer] will render text using a [SpriteFont] font,
/// creating a [SpriteFontTextElement]. /// creating a [SpriteFontTextElement].
class SpriteFontTextFormatter extends TextFormatter { class SpriteFontRenderer extends TextRenderer {
SpriteFontTextFormatter.fromFont( SpriteFontRenderer.fromFont(
this.font, { this.font, {
this.scale = 1.0, this.scale = 1.0,
this.letterSpacing = 0.0, this.letterSpacing = 0.0,

View File

@ -1,28 +1,24 @@
import 'package:flame/src/cache/memory_cache.dart'; import 'package:flame/cache.dart';
import 'package:flame/src/text/formatters/text_painter_text_formatter.dart'; import 'package:flame/src/text/elements/text_painter_text_element.dart';
import 'package:flame/src/text/text_renderer.dart'; import 'package:flame/src/text/renderers/text_renderer.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
/// [TextRenderer] implementation based on Flutter's [TextPainter]. /// [TextPaint] applies a Flutter [TextStyle] to a string of
/// /// text, creating a [TextPainterTextElement].
/// This renderer uses a fixed [style] to draw the text. This style cannot be class TextPaint extends TextRenderer {
/// modified dynamically, if you need to change any attribute of the text at
/// runtime, such as color, then create a new [TextPaint] object using
/// [copyWith].
class TextPaint extends TextRenderer<TextPainterTextFormatter> {
TextPaint({ TextPaint({
TextStyle? style, TextStyle? style,
TextDirection? textDirection, this.textDirection = TextDirection.ltr,
}) : super( }) : style = style ?? defaultTextStyle;
TextPainterTextFormatter(
style: style ?? defaultTextStyle,
textDirection: textDirection ?? TextDirection.ltr,
),
);
TextStyle get style => formatter.style; final TextStyle style;
final TextDirection textDirection;
TextDirection get textDirection => formatter.textDirection; @override
TextPainterTextElement format(String text) {
final tp = toTextPainter(text);
return TextPainterTextElement(tp);
}
final MemoryCache<String, TextPainter> _textPainterCache = MemoryCache(); final MemoryCache<String, TextPainter> _textPainterCache = MemoryCache();
@ -64,8 +60,8 @@ class TextPaint extends TextRenderer<TextPainterTextFormatter> {
TextDirection? textDirection, TextDirection? textDirection,
}) { }) {
return TextPaint( return TextPaint(
style: transform(formatter.style), style: transform(style),
textDirection: textDirection ?? formatter.textDirection, textDirection: textDirection ?? this.textDirection,
); );
} }
} }

View File

@ -0,0 +1,22 @@
import 'package:flame/extensions.dart';
import 'package:flame/src/anchor.dart';
import 'package:flame/text.dart';
/// [TextRenderer] is an abstract interface for a class that can convert an
/// arbitrary string of text into a renderable [TextElement].
abstract class TextRenderer {
TextElement format(String text);
LineMetrics getLineMetrics(String text) {
return format(text).metrics;
}
void render(
Canvas canvas,
String text,
Vector2 position, {
Anchor anchor = Anchor.topLeft,
}) {
format(text).render(canvas, position, anchor: anchor);
}
}

View File

@ -1,38 +0,0 @@
import 'dart:ui';
import 'package:flame/src/text/common/sprite_font.dart';
import 'package:flame/src/text/formatters/sprite_font_text_formatter.dart';
import 'package:flame/src/text/text_renderer.dart';
/// [TextRenderer] implementation that uses a spritesheet of various font glyphs
/// to render text.
///
/// For example, suppose there is a spritesheet with sprites for characters from
/// A to Z. Mapping these sprites into a [SpriteFontRenderer] allows then to
/// write text using these sprite images as the font.
///
/// Currently, this class supports monospace fonts only -- the widths and the
/// heights of all characters must be the same.
/// Extra space between letters can be added via the `letterSpacing` parameter
/// (it can also be negative to "squish" characters together). Finally, the
/// `scale` parameter allows scaling the font to be bigger/smaller relative to
/// its size in the source image.
///
/// The `paint` parameter is used to composite the character images onto the
/// canvas. Its default value will draw the character images as-is. Changing
/// the opacity of the paint's color will make the text semi-transparent.
class SpriteFontRenderer extends TextRenderer<SpriteFontTextFormatter> {
SpriteFontRenderer.fromFont(
SpriteFont font, {
Color? color,
double scale = 1,
double letterSpacing = 0,
}) : super(
SpriteFontTextFormatter.fromFont(
font,
scale: scale,
letterSpacing: letterSpacing,
color: color,
),
);
}

View File

@ -1,5 +1,5 @@
import 'package:flame/src/text/formatters/text_formatter.dart'; import 'package:flame/src/text/renderers/text_paint.dart';
import 'package:flame/src/text/formatters/text_painter_text_formatter.dart'; import 'package:flame/src/text/renderers/text_renderer.dart';
import 'package:flame/src/text/styles/style.dart'; import 'package:flame/src/text/styles/style.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
@ -24,7 +24,7 @@ class FlameTextStyle extends Style {
final FontStyle? fontStyle; final FontStyle? fontStyle;
final double? letterSpacing; final double? letterSpacing;
late final TextFormatter formatter = asTextFormatter(); late final TextRenderer renderer = asTextRenderer();
@override @override
FlameTextStyle copyWith(FlameTextStyle other) { FlameTextStyle copyWith(FlameTextStyle other) {
@ -40,8 +40,8 @@ class FlameTextStyle extends Style {
} }
@internal @internal
TextPainterTextFormatter asTextFormatter() { TextPaint asTextRenderer() {
return TextPainterTextFormatter( return TextPaint(
style: TextStyle( style: TextStyle(
color: color, color: color,
fontFamily: fontFamily, fontFamily: fontFamily,

View File

@ -1,44 +0,0 @@
import 'dart:ui';
import 'package:flame/src/anchor.dart';
import 'package:flame/text.dart';
import 'package:vector_math/vector_math_64.dart';
/// [TextRenderer] is the most basic API for drawing text.
///
/// A text renderer contains a [formatter] that embodies a particular style
/// for rendering text, such as font-family, color, size, and so on.
/// At the same time, nor the text renderer or the [formatter] are tied to a
/// specific string -- it can render any text fragment that you give it.
///
/// A text renderer object has two functions: to measure the size of a text
/// string that it will have when rendered, and to actually render that string
/// onto a canvas.
///
/// [TextRenderer] is a low-level API that may be somewhat inconvenient to use
/// directly. Instead, consider using components such as TextComponent or
/// TextBoxComponent.
///
/// See [TextFormatter] for more information about existing options.
class TextRenderer<T extends TextFormatter> {
TextRenderer(this.formatter);
final T formatter;
TextElement format(String text) {
return formatter.format(text);
}
LineMetrics getLineMetrics(String text) {
return format(text).metrics;
}
void render(
Canvas canvas,
String text,
Vector2 position, {
Anchor anchor = Anchor.topLeft,
}) {
format(text).render(canvas, position, anchor: anchor);
}
}

View File

@ -1,3 +1,5 @@
export 'package:flutter/painting.dart' show TextStyle;
export 'src/text/common/glyph.dart' show Glyph; export 'src/text/common/glyph.dart' show Glyph;
export 'src/text/common/line_metrics.dart' show LineMetrics; export 'src/text/common/line_metrics.dart' show LineMetrics;
export 'src/text/common/sprite_font.dart' show SpriteFont; export 'src/text/common/sprite_font.dart' show SpriteFont;
@ -10,11 +12,6 @@ export 'src/text/elements/sprite_font_text_element.dart'
export 'src/text/elements/text_element.dart' show TextElement; export 'src/text/elements/text_element.dart' show TextElement;
export 'src/text/elements/text_painter_text_element.dart' export 'src/text/elements/text_painter_text_element.dart'
show TextPainterTextElement; show TextPainterTextElement;
export 'src/text/formatters/sprite_font_text_formatter.dart'
show SpriteFontTextFormatter;
export 'src/text/formatters/text_formatter.dart' show TextFormatter;
export 'src/text/formatters/text_painter_text_formatter.dart'
show TextPainterTextFormatter;
export 'src/text/nodes/block_node.dart' show BlockNode; export 'src/text/nodes/block_node.dart' show BlockNode;
export 'src/text/nodes/bold_text_node.dart' show BoldTextNode; export 'src/text/nodes/bold_text_node.dart' show BoldTextNode;
export 'src/text/nodes/column_node.dart' show ColumnNode; export 'src/text/nodes/column_node.dart' show ColumnNode;
@ -26,12 +23,12 @@ export 'src/text/nodes/paragraph_node.dart' show ParagraphNode;
export 'src/text/nodes/plain_text_node.dart' show PlainTextNode; export 'src/text/nodes/plain_text_node.dart' show PlainTextNode;
export 'src/text/nodes/text_block_node.dart' show TextBlockNode; export 'src/text/nodes/text_block_node.dart' show TextBlockNode;
export 'src/text/nodes/text_node.dart' show TextNode; export 'src/text/nodes/text_node.dart' show TextNode;
export 'src/text/sprite_font_renderer.dart' show SpriteFontRenderer; export 'src/text/renderers/sprite_font_renderer.dart' show SpriteFontRenderer;
export 'src/text/renderers/text_paint.dart' show TextPaint;
export 'src/text/renderers/text_renderer.dart' show TextRenderer;
export 'src/text/renderers/text_renderer_factory.dart' show TextRendererFactory;
export 'src/text/styles/background_style.dart' show BackgroundStyle; export 'src/text/styles/background_style.dart' show BackgroundStyle;
export 'src/text/styles/block_style.dart' show BlockStyle; export 'src/text/styles/block_style.dart' show BlockStyle;
export 'src/text/styles/document_style.dart' show DocumentStyle; export 'src/text/styles/document_style.dart' show DocumentStyle;
export 'src/text/styles/flame_text_style.dart' show FlameTextStyle; export 'src/text/styles/flame_text_style.dart' show FlameTextStyle;
export 'src/text/styles/style.dart' show Style; export 'src/text/styles/style.dart' show Style;
export 'src/text/text_paint.dart' show TextPaint;
export 'src/text/text_renderer.dart' show TextRenderer;
export 'src/text/text_renderer_factory.dart' show TextRendererFactory;

View File

@ -1,9 +1,8 @@
import 'dart:ui' hide TextStyle; import 'dart:ui';
import 'package:canvas_test/canvas_test.dart'; import 'package:canvas_test/canvas_test.dart';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame/palette.dart'; import 'package:flame/palette.dart';
import 'package:flame/text.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@ -193,7 +192,7 @@ class _FramedTextBox extends TextBoxComponent {
super.position, super.position,
super.size, super.size,
}) : super( }) : super(
textRenderer: TextRenderer(DebugTextFormatter(fontSize: 22)), textRenderer: DebugTextRenderer(fontSize: 22),
); );
final Paint _borderPaint = Paint() final Paint _borderPaint = Paint()

View File

@ -12,10 +12,10 @@ void main() {
group('SpriteFontRenderer', () { group('SpriteFontRenderer', () {
test('creating SpriteFontRenderer', () async { test('creating SpriteFontRenderer', () async {
final renderer = await createRenderer(); final renderer = await createRenderer();
expect(renderer.formatter.font.source, isA<Image>()); expect(renderer.font.source, isA<Image>());
expect(renderer.formatter.font.size, 6); expect(renderer.font.size, 6);
expect(renderer.formatter.scale, 1.0); expect(renderer.scale, 1.0);
expect(renderer.formatter.letterSpacing, 0); expect(renderer.letterSpacing, 0);
expect( expect(
() => renderer.render(MockCanvas(), 'Ї', Vector2.zero()), () => renderer.render(MockCanvas(), 'Ї', Vector2.zero()),
@ -48,7 +48,7 @@ void main() {
TextComponent( TextComponent(
text: 'FLAME', text: 'FLAME',
textRenderer: (await createRenderer(scale: 25)) textRenderer: (await createRenderer(scale: 25))
..formatter.paint.color = const Color(0x44000000), ..paint.color = const Color(0x44000000),
position: Vector2(400, 500), position: Vector2(400, 500),
anchor: Anchor.center, anchor: Anchor.center,
), ),

View File

@ -32,11 +32,7 @@ void main() {
}); });
} }
class _CustomTextRenderer extends TextRenderer<_CustomTextFormatter> { class _CustomTextRenderer extends TextRenderer {
_CustomTextRenderer() : super(_CustomTextFormatter());
}
class _CustomTextFormatter extends TextFormatter {
@override @override
TextElement format(String text) { TextElement format(String text) {
return CustomTextElement(); return CustomTextElement();

View File

@ -1,6 +1,6 @@
export 'src/close_to_aabb.dart' show closeToAabb; export 'src/close_to_aabb.dart' show closeToAabb;
export 'src/close_to_vector.dart'; export 'src/close_to_vector.dart';
export 'src/debug_text_formatter.dart' show DebugTextFormatter; export 'src/debug_text_renderer.dart' show DebugTextRenderer;
export 'src/expect_double.dart'; export 'src/expect_double.dart';
export 'src/fails_assert.dart'; export 'src/fails_assert.dart';
export 'src/flame_test.dart'; export 'src/flame_test.dart';

View File

@ -8,8 +8,8 @@ import 'package:flame/text.dart';
/// Rendering regular text in golden tests is unreliable due to differences in /// Rendering regular text in golden tests is unreliable due to differences in
/// font definitions across platforms and different algorithms used for anti- /// font definitions across platforms and different algorithms used for anti-
/// aliasing. /// aliasing.
class DebugTextFormatter extends TextFormatter { class DebugTextRenderer extends TextRenderer {
DebugTextFormatter({ DebugTextRenderer({
this.color = const Color(0xFFFFFFFF), this.color = const Color(0xFFFFFFFF),
this.fontSize = 16.0, this.fontSize = 16.0,
this.lineHeight = 1.2, this.lineHeight = 1.2,
@ -45,7 +45,7 @@ class _DebugTextElement extends TextElement {
_initRects(charWidth, charHeight); _initRects(charWidth, charHeight);
} }
final DebugTextFormatter style; final DebugTextRenderer style;
final String text; final String text;
final List<Rect> rects = []; final List<Rect> rects = [];
final Paint paint = Paint(); final Paint paint = Paint();

View File

@ -12,26 +12,26 @@ void main() {
(game) async { (game) async {
game.add( game.add(
TextElementsComponent([ TextElementsComponent([
DebugTextFormatter().format('one two three')..translate(5, 5), DebugTextRenderer().format('one two three')..translate(5, 5),
DebugTextFormatter().format(' x ')..translate(5, 25), DebugTextRenderer().format(' x ')..translate(5, 25),
DebugTextFormatter().format(' ')..translate(5, 45), DebugTextRenderer().format(' ')..translate(5, 45),
DebugTextFormatter().format('')..translate(25, 45), DebugTextRenderer().format('')..translate(25, 45),
DebugTextFormatter(color: const Color(0xFFFF88AA)) DebugTextRenderer(color: const Color(0xFFFF88AA))
.format('Flame Engine') .format('Flame Engine')
..translate(5, 65), ..translate(5, 65),
DebugTextFormatter(fontWeight: FontWeight.bold).format('Blue Fire') DebugTextRenderer(fontWeight: FontWeight.bold).format('Blue Fire')
..translate(5, 85), ..translate(5, 85),
DebugTextFormatter(fontWeight: FontWeight.w900).format('Blue Fire') DebugTextRenderer(fontWeight: FontWeight.w900).format('Blue Fire')
..translate(5, 105), ..translate(5, 105),
DebugTextFormatter(fontStyle: FontStyle.italic).format('Blue Fire') DebugTextRenderer(fontStyle: FontStyle.italic).format('Blue Fire')
..translate(5, 125), ..translate(5, 125),
DebugTextFormatter( DebugTextRenderer(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
color: const Color(0xFF0088FF), color: const Color(0xFF0088FF),
).format('a b c d e f g h i') ).format('a b c d e f g h i')
..translate(5, 145), ..translate(5, 145),
DebugTextFormatter(fontSize: 10).format('www.flame-engine.org') DebugTextRenderer(fontSize: 10).format('www.flame-engine.org')
..translate(5, 165), ..translate(5, 165),
]), ]),
); );