diff --git a/doc/flame/rendering/text_rendering.md b/doc/flame/rendering/text_rendering.md index 0097a1446..e683a4238 100644 --- a/doc/flame/rendering/text_rendering.md +++ b/doc/flame/rendering/text_rendering.md @@ -3,94 +3,70 @@ 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 -Flame provides two text components that make it even easier to render text in your game: -`TextComponent` and `TextBoxComponent`. +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 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` is a simple component that renders a single line of text. -Example usage: +Simple usage: ```dart -final style = TextStyle(color: BasicPalette.white.color); -final regular = TextPaint(style: style); +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 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 { @override void onLoad() { - add(TextComponent(text: 'Hello, Flame', textRenderer: regular) - ..anchor = Anchor.topCenter - ..x = size.width / 2 // size is a property from game - ..y = 32.0); + 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](https://pub.dev/documentation/flame/latest/components/TextComponent-class.html). + ### 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 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 -of the text content. For example, setting `align` to `Anchor.center` will center the text within -its bounding box both vertically and horizontally. +In addition, the `align` property allows you to control the 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: ```dart @@ -130,5 +109,219 @@ class MyTextBox extends TextBoxComponent { } ``` -Both components are showcased in an example -[here](https://github.com/flame-engine/flame/blob/main/examples/lib/stories/rendering/text_example.dart) +You can find all the options under [TextBoxComponent's +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). diff --git a/examples/lib/stories/input/hardware_keyboard_example.dart b/examples/lib/stories/input/hardware_keyboard_example.dart index 3a4587e0d..7c5fdc046 100644 --- a/examples/lib/stories/input/hardware_keyboard_example.dart +++ b/examples/lib/stories/input/hardware_keyboard_example.dart @@ -208,7 +208,7 @@ class MyKeyboardDetector extends HardwareKeyboardDetector class KeyboardKey extends PositionComponent { KeyboardKey({required this.text, super.position}) { - textElement = textRenderer.formatter.format(text); + textElement = textRenderer.format(text); width = textElement.metrics.width + padding.x; height = textElement.metrics.height + padding.y; textElement.translate( diff --git a/examples/lib/stories/rendering/text_example.dart b/examples/lib/stories/rendering/text_example.dart index 8ec7a8dc3..51a8df334 100644 --- a/examples/lib/stories/rendering/text_example.dart +++ b/examples/lib/stories/rendering/text_example.dart @@ -1,6 +1,7 @@ import 'package:flame/components.dart'; import 'package:flame/game.dart'; import 'package:flame/palette.dart'; +import 'package:flame/text.dart'; import 'package:flutter/material.dart'; class TextExample extends FlameGame { diff --git a/packages/flame/lib/components.dart b/packages/flame/lib/components.dart index 227b4a191..1b5952260 100644 --- a/packages/flame/lib/components.dart +++ b/packages/flame/lib/components.dart @@ -48,5 +48,5 @@ export 'src/geometry/circle_component.dart'; export 'src/geometry/polygon_component.dart'; export 'src/geometry/rectangle_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'; diff --git a/packages/flame/lib/game.dart b/packages/flame/lib/game.dart index 0f1cdb47c..6b8de0c62 100644 --- a/packages/flame/lib/game.dart +++ b/packages/flame/lib/game.dart @@ -18,4 +18,4 @@ export 'src/game/mixins/single_game_instance.dart'; export 'src/game/notifying_vector2.dart'; export 'src/game/projector.dart'; export 'src/game/transform2d.dart'; -export 'src/text/text_paint.dart'; +export 'src/text/renderers/text_paint.dart'; diff --git a/packages/flame/lib/src/components/fps_text_component.dart b/packages/flame/lib/src/components/fps_text_component.dart index e5dfbc16a..5e86eb8c9 100644 --- a/packages/flame/lib/src/components/fps_text_component.dart +++ b/packages/flame/lib/src/components/fps_text_component.dart @@ -1,5 +1,5 @@ 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. /// It has a [FpsComponent] as a child which does the actual calculations. diff --git a/packages/flame/lib/src/components/text_box_component.dart b/packages/flame/lib/src/components/text_box_component.dart index 817fa2a1c..6ebbb2848 100644 --- a/packages/flame/lib/src/components/text_box_component.dart +++ b/packages/flame/lib/src/components/text_box_component.dart @@ -273,7 +273,7 @@ class TextBoxComponent extends TextComponent { line = line.substring(0, nChars); } - final textElement = textRenderer.formatter.format(line); + final textElement = textRenderer.format(line); final metrics = textElement.metrics; final position = Vector2( diff --git a/packages/flame/lib/src/components/text_component.dart b/packages/flame/lib/src/components/text_component.dart index df54390dc..a2ed3191a 100644 --- a/packages/flame/lib/src/components/text_component.dart +++ b/packages/flame/lib/src/components/text_component.dart @@ -42,7 +42,7 @@ class TextComponent extends PositionComponent { @internal void updateBounds() { - _textElement = _textRenderer.formatter.format(_text); + _textElement = _textRenderer.format(_text); final measurements = _textElement.metrics; _textElement.translate(0, measurements.ascent); size.setValues(measurements.width, measurements.height); diff --git a/packages/flame/lib/src/text/formatters/text_formatter.dart b/packages/flame/lib/src/text/formatters/text_formatter.dart deleted file mode 100644 index 2fffd90b9..000000000 --- a/packages/flame/lib/src/text/formatters/text_formatter.dart +++ /dev/null @@ -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); -} diff --git a/packages/flame/lib/src/text/formatters/text_painter_text_formatter.dart b/packages/flame/lib/src/text/formatters/text_painter_text_formatter.dart deleted file mode 100644 index d211427cb..000000000 --- a/packages/flame/lib/src/text/formatters/text_painter_text_formatter.dart +++ /dev/null @@ -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(); - } -} diff --git a/packages/flame/lib/src/text/nodes/plain_text_node.dart b/packages/flame/lib/src/text/nodes/plain_text_node.dart index 47624b9ba..6a1ccf523 100644 --- a/packages/flame/lib/src/text/nodes/plain_text_node.dart +++ b/packages/flame/lib/src/text/nodes/plain_text_node.dart @@ -1,6 +1,6 @@ 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/renderers/text_renderer.dart'; import 'package:flame/src/text/styles/document_style.dart'; import 'package:flame/src/text/styles/flame_text_style.dart'; @@ -20,11 +20,11 @@ class PlainTextNode extends TextNode { class _PlainTextLayoutBuilder extends TextNodeLayoutBuilder { _PlainTextLayoutBuilder(this.node) - : formatter = node.textStyle.asTextFormatter(), + : renderer = node.textStyle.asTextRenderer(), words = node.text.split(' '); final PlainTextNode node; - final TextFormatter formatter; + final TextRenderer renderer; final List words; int index0 = 0; int index1 = 1; @@ -38,7 +38,7 @@ class _PlainTextLayoutBuilder extends TextNodeLayoutBuilder { int? tentativeIndex0; while (index1 <= words.length) { final textPiece = words.sublist(index0, index1).join(' '); - final formattedPiece = formatter.format(textPiece); + final formattedPiece = renderer.format(textPiece); if (formattedPiece.metrics.width > availableWidth) { break; } else { diff --git a/packages/flame/lib/src/text/formatters/sprite_font_text_formatter.dart b/packages/flame/lib/src/text/renderers/sprite_font_renderer.dart similarity index 87% rename from packages/flame/lib/src/text/formatters/sprite_font_text_formatter.dart rename to packages/flame/lib/src/text/renderers/sprite_font_renderer.dart index 50aabc2c3..d3c876501 100644 --- a/packages/flame/lib/src/text/formatters/sprite_font_text_formatter.dart +++ b/packages/flame/lib/src/text/renderers/sprite_font_renderer.dart @@ -4,12 +4,12 @@ import 'dart:ui' hide LineMetrics; import 'package:flame/src/text/common/line_metrics.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/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]. -class SpriteFontTextFormatter extends TextFormatter { - SpriteFontTextFormatter.fromFont( +class SpriteFontRenderer extends TextRenderer { + SpriteFontRenderer.fromFont( this.font, { this.scale = 1.0, this.letterSpacing = 0.0, diff --git a/packages/flame/lib/src/text/text_paint.dart b/packages/flame/lib/src/text/renderers/text_paint.dart similarity index 58% rename from packages/flame/lib/src/text/text_paint.dart rename to packages/flame/lib/src/text/renderers/text_paint.dart index 799c03d39..73d7ed48a 100644 --- a/packages/flame/lib/src/text/text_paint.dart +++ b/packages/flame/lib/src/text/renderers/text_paint.dart @@ -1,28 +1,24 @@ -import 'package:flame/src/cache/memory_cache.dart'; -import 'package:flame/src/text/formatters/text_painter_text_formatter.dart'; -import 'package:flame/src/text/text_renderer.dart'; +import 'package:flame/cache.dart'; +import 'package:flame/src/text/elements/text_painter_text_element.dart'; +import 'package:flame/src/text/renderers/text_renderer.dart'; import 'package:flutter/rendering.dart'; -/// [TextRenderer] implementation based on Flutter's [TextPainter]. -/// -/// This renderer uses a fixed [style] to draw the text. This style cannot be -/// 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 { +/// [TextPaint] applies a Flutter [TextStyle] to a string of +/// text, creating a [TextPainterTextElement]. +class TextPaint extends TextRenderer { TextPaint({ TextStyle? style, - TextDirection? textDirection, - }) : super( - TextPainterTextFormatter( - style: style ?? defaultTextStyle, - textDirection: textDirection ?? TextDirection.ltr, - ), - ); + this.textDirection = TextDirection.ltr, + }) : style = style ?? defaultTextStyle; - 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 _textPainterCache = MemoryCache(); @@ -64,8 +60,8 @@ class TextPaint extends TextRenderer { TextDirection? textDirection, }) { return TextPaint( - style: transform(formatter.style), - textDirection: textDirection ?? formatter.textDirection, + style: transform(style), + textDirection: textDirection ?? this.textDirection, ); } } diff --git a/packages/flame/lib/src/text/renderers/text_renderer.dart b/packages/flame/lib/src/text/renderers/text_renderer.dart new file mode 100644 index 000000000..2daafb90b --- /dev/null +++ b/packages/flame/lib/src/text/renderers/text_renderer.dart @@ -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); + } +} diff --git a/packages/flame/lib/src/text/text_renderer_factory.dart b/packages/flame/lib/src/text/renderers/text_renderer_factory.dart similarity index 100% rename from packages/flame/lib/src/text/text_renderer_factory.dart rename to packages/flame/lib/src/text/renderers/text_renderer_factory.dart diff --git a/packages/flame/lib/src/text/sprite_font_renderer.dart b/packages/flame/lib/src/text/sprite_font_renderer.dart deleted file mode 100644 index a67a78d95..000000000 --- a/packages/flame/lib/src/text/sprite_font_renderer.dart +++ /dev/null @@ -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 { - SpriteFontRenderer.fromFont( - SpriteFont font, { - Color? color, - double scale = 1, - double letterSpacing = 0, - }) : super( - SpriteFontTextFormatter.fromFont( - font, - scale: scale, - letterSpacing: letterSpacing, - color: color, - ), - ); -} diff --git a/packages/flame/lib/src/text/styles/flame_text_style.dart b/packages/flame/lib/src/text/styles/flame_text_style.dart index 1da932bc5..fe0a296a8 100644 --- a/packages/flame/lib/src/text/styles/flame_text_style.dart +++ b/packages/flame/lib/src/text/styles/flame_text_style.dart @@ -1,5 +1,5 @@ -import 'package:flame/src/text/formatters/text_formatter.dart'; -import 'package:flame/src/text/formatters/text_painter_text_formatter.dart'; +import 'package:flame/src/text/renderers/text_paint.dart'; +import 'package:flame/src/text/renderers/text_renderer.dart'; import 'package:flame/src/text/styles/style.dart'; import 'package:flutter/rendering.dart'; import 'package:meta/meta.dart'; @@ -24,7 +24,7 @@ class FlameTextStyle extends Style { final FontStyle? fontStyle; final double? letterSpacing; - late final TextFormatter formatter = asTextFormatter(); + late final TextRenderer renderer = asTextRenderer(); @override FlameTextStyle copyWith(FlameTextStyle other) { @@ -40,8 +40,8 @@ class FlameTextStyle extends Style { } @internal - TextPainterTextFormatter asTextFormatter() { - return TextPainterTextFormatter( + TextPaint asTextRenderer() { + return TextPaint( style: TextStyle( color: color, fontFamily: fontFamily, diff --git a/packages/flame/lib/src/text/text_renderer.dart b/packages/flame/lib/src/text/text_renderer.dart deleted file mode 100644 index 042e5fe04..000000000 --- a/packages/flame/lib/src/text/text_renderer.dart +++ /dev/null @@ -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 { - 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); - } -} diff --git a/packages/flame/lib/text.dart b/packages/flame/lib/text.dart index 35688139a..2b73ece50 100644 --- a/packages/flame/lib/text.dart +++ b/packages/flame/lib/text.dart @@ -1,3 +1,5 @@ +export 'package:flutter/painting.dart' show TextStyle; + export 'src/text/common/glyph.dart' show Glyph; export 'src/text/common/line_metrics.dart' show LineMetrics; 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_painter_text_element.dart' 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/bold_text_node.dart' show BoldTextNode; 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/text_block_node.dart' show TextBlockNode; 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/block_style.dart' show BlockStyle; export 'src/text/styles/document_style.dart' show DocumentStyle; export 'src/text/styles/flame_text_style.dart' show FlameTextStyle; 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; diff --git a/packages/flame/test/components/text_box_component_test.dart b/packages/flame/test/components/text_box_component_test.dart index ceaac33ca..4709abfac 100644 --- a/packages/flame/test/components/text_box_component_test.dart +++ b/packages/flame/test/components/text_box_component_test.dart @@ -1,9 +1,8 @@ -import 'dart:ui' hide TextStyle; +import 'dart:ui'; import 'package:canvas_test/canvas_test.dart'; import 'package:flame/components.dart'; import 'package:flame/palette.dart'; -import 'package:flame/text.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -193,7 +192,7 @@ class _FramedTextBox extends TextBoxComponent { super.position, super.size, }) : super( - textRenderer: TextRenderer(DebugTextFormatter(fontSize: 22)), + textRenderer: DebugTextRenderer(fontSize: 22), ); final Paint _borderPaint = Paint() diff --git a/packages/flame/test/text/sprite_font_renderer_test.dart b/packages/flame/test/text/sprite_font_renderer_test.dart index 77f6b19ea..8af19a378 100644 --- a/packages/flame/test/text/sprite_font_renderer_test.dart +++ b/packages/flame/test/text/sprite_font_renderer_test.dart @@ -12,10 +12,10 @@ void main() { group('SpriteFontRenderer', () { test('creating SpriteFontRenderer', () async { final renderer = await createRenderer(); - expect(renderer.formatter.font.source, isA()); - expect(renderer.formatter.font.size, 6); - expect(renderer.formatter.scale, 1.0); - expect(renderer.formatter.letterSpacing, 0); + expect(renderer.font.source, isA()); + expect(renderer.font.size, 6); + expect(renderer.scale, 1.0); + expect(renderer.letterSpacing, 0); expect( () => renderer.render(MockCanvas(), 'Ї', Vector2.zero()), @@ -48,7 +48,7 @@ void main() { TextComponent( text: 'FLAME', textRenderer: (await createRenderer(scale: 25)) - ..formatter.paint.color = const Color(0x44000000), + ..paint.color = const Color(0x44000000), position: Vector2(400, 500), anchor: Anchor.center, ), diff --git a/packages/flame/test/text/text_renderer_test.dart b/packages/flame/test/text/text_renderer_test.dart index f952e9430..36865ca0c 100644 --- a/packages/flame/test/text/text_renderer_test.dart +++ b/packages/flame/test/text/text_renderer_test.dart @@ -32,11 +32,7 @@ void main() { }); } -class _CustomTextRenderer extends TextRenderer<_CustomTextFormatter> { - _CustomTextRenderer() : super(_CustomTextFormatter()); -} - -class _CustomTextFormatter extends TextFormatter { +class _CustomTextRenderer extends TextRenderer { @override TextElement format(String text) { return CustomTextElement(); diff --git a/packages/flame_test/lib/flame_test.dart b/packages/flame_test/lib/flame_test.dart index 292e8b86a..e18f5b376 100644 --- a/packages/flame_test/lib/flame_test.dart +++ b/packages/flame_test/lib/flame_test.dart @@ -1,6 +1,6 @@ export 'src/close_to_aabb.dart' show closeToAabb; 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/fails_assert.dart'; export 'src/flame_test.dart'; diff --git a/packages/flame_test/lib/src/debug_text_formatter.dart b/packages/flame_test/lib/src/debug_text_renderer.dart similarity index 95% rename from packages/flame_test/lib/src/debug_text_formatter.dart rename to packages/flame_test/lib/src/debug_text_renderer.dart index 6d4e89629..4f9dfb548 100644 --- a/packages/flame_test/lib/src/debug_text_formatter.dart +++ b/packages/flame_test/lib/src/debug_text_renderer.dart @@ -8,8 +8,8 @@ import 'package:flame/text.dart'; /// Rendering regular text in golden tests is unreliable due to differences in /// font definitions across platforms and different algorithms used for anti- /// aliasing. -class DebugTextFormatter extends TextFormatter { - DebugTextFormatter({ +class DebugTextRenderer extends TextRenderer { + DebugTextRenderer({ this.color = const Color(0xFFFFFFFF), this.fontSize = 16.0, this.lineHeight = 1.2, @@ -45,7 +45,7 @@ class _DebugTextElement extends TextElement { _initRects(charWidth, charHeight); } - final DebugTextFormatter style; + final DebugTextRenderer style; final String text; final List rects = []; final Paint paint = Paint(); diff --git a/packages/flame_test/test/debug_text_formatter_test.dart b/packages/flame_test/test/debug_text_formatter_test.dart index ddca4caff..db651de63 100644 --- a/packages/flame_test/test/debug_text_formatter_test.dart +++ b/packages/flame_test/test/debug_text_formatter_test.dart @@ -12,26 +12,26 @@ void main() { (game) async { game.add( TextElementsComponent([ - DebugTextFormatter().format('one two three')..translate(5, 5), - DebugTextFormatter().format(' x ')..translate(5, 25), - DebugTextFormatter().format(' ')..translate(5, 45), - DebugTextFormatter().format('')..translate(25, 45), - DebugTextFormatter(color: const Color(0xFFFF88AA)) + DebugTextRenderer().format('one two three')..translate(5, 5), + DebugTextRenderer().format(' x ')..translate(5, 25), + DebugTextRenderer().format(' ')..translate(5, 45), + DebugTextRenderer().format('')..translate(25, 45), + DebugTextRenderer(color: const Color(0xFFFF88AA)) .format('Flame Engine') ..translate(5, 65), - DebugTextFormatter(fontWeight: FontWeight.bold).format('Blue Fire') + DebugTextRenderer(fontWeight: FontWeight.bold).format('Blue Fire') ..translate(5, 85), - DebugTextFormatter(fontWeight: FontWeight.w900).format('Blue Fire') + DebugTextRenderer(fontWeight: FontWeight.w900).format('Blue Fire') ..translate(5, 105), - DebugTextFormatter(fontStyle: FontStyle.italic).format('Blue Fire') + DebugTextRenderer(fontStyle: FontStyle.italic).format('Blue Fire') ..translate(5, 125), - DebugTextFormatter( + DebugTextRenderer( fontWeight: FontWeight.bold, fontStyle: FontStyle.italic, color: const Color(0xFF0088FF), ).format('a b c d e f g h i') ..translate(5, 145), - DebugTextFormatter(fontSize: 10).format('www.flame-engine.org') + DebugTextRenderer(fontSize: 10).format('www.flame-engine.org') ..translate(5, 165), ]), );