diff --git a/packages/flame/lib/src/text/common/glyph_data.dart b/packages/flame/lib/src/text/common/glyph_data.dart new file mode 100644 index 000000000..2dcdd9546 --- /dev/null +++ b/packages/flame/lib/src/text/common/glyph_data.dart @@ -0,0 +1,19 @@ +class GlyphData { + const GlyphData({ + required this.left, + required this.top, + this.right, + this.bottom, + }); + + const GlyphData.fromLTWH(this.left, this.top, double width, double height) + : right = left + width, + bottom = top + height; + + const GlyphData.fromLTRB(this.left, this.top, this.right, this.bottom); + + final double left; + final double top; + final double? right; + final double? bottom; +} diff --git a/packages/flame/lib/src/text/common/glyph_info.dart b/packages/flame/lib/src/text/common/glyph_info.dart new file mode 100644 index 000000000..6f836805c --- /dev/null +++ b/packages/flame/lib/src/text/common/glyph_info.dart @@ -0,0 +1,14 @@ +/// Helper class that stores dimensions of a single glyph for a spritesheet- +/// based font. +class GlyphInfo { + double srcLeft = 0; + double srcTop = 0; + double srcRight = 0; + double srcBottom = 0; + double rstSCos = 1; + double rstSSin = 0; + double rstTx = 0; + double rstTy = 0; + double width = 0; + double height = 0; +} diff --git a/packages/flame/lib/src/text/common/line_metrics.dart b/packages/flame/lib/src/text/common/line_metrics.dart new file mode 100644 index 000000000..1d2e6c752 --- /dev/null +++ b/packages/flame/lib/src/text/common/line_metrics.dart @@ -0,0 +1,98 @@ +import 'dart:ui'; + +import 'package:flame/src/text/common/text_line.dart'; + +/// The [LineMetrics] object contains measurements of a [TextLine]. +/// +/// A line of text can be thought of as surrounded by a box (rect) that outlines +/// the boundaries of the text, plus there is a [baseline] inside the box which +/// is the line on top of which the text is placed. +/// +/// The [LineMetrics] box surrounding a piece of text is not necessarily tight: +/// there's usually some amount of space above and below the text glyphs to +/// improve legibility of multi-line text. +class LineMetrics { + LineMetrics({ + double left = 0, + double baseline = 0, + double width = 0, + double? ascent, + double? descent, + double? height, + }) : _left = left, + _baseline = baseline, + _width = width, + _ascent = ascent ?? (height == null ? 0 : height - (descent ?? 0)), + _descent = + descent ?? (height == null ? 0 : height - (ascent ?? height)); + + /// X-coordinate of the left edge of the box. + double get left => _left; + double _left; + + /// Y-coordinate of the baseline of the box. When several line fragments are + /// placed next to each other, their baselines will match. + double get baseline => _baseline; + double _baseline; + + /// The total width of the box. + double get width => _width; + double _width; + + /// The distance from the baseline to the top of the box. + double get ascent => _ascent; + double _ascent; + + /// The distance from the baseline to the bottom of the box. + double get descent => _descent; + double _descent; + + double get right => left + width; + double get top => baseline - ascent; + double get bottom => baseline + descent; + double get height => ascent + descent; + + /// Moves the [LineMetrics] box by the specified offset [dx], [dy] leaving its + /// width and height unmodified. + void translate(double dx, double dy) { + _left += dx; + _baseline += dy; + } + + /// Moves this [LineMetrics] box to the origin, setting [left] and [baseline] + /// to 0. + void moveToOrigin() { + _left = 0; + _baseline = 0; + } + + /// Sets the position of the left edge of this [LineMetrics] box, leaving the + /// [right] edge in place. + void setLeftEdge(double x) { + _width = right - x; + _left = x; + } + + /// Appends another [LineMetrics] box that is adjacent to the current and on + /// the same baseline. The current object will be modified to encompass the + /// [other] box. + void append(LineMetrics other) { + assert( + baseline == other.baseline, + 'Baselines do not match: $baseline vs ${other.baseline}', + ); + _width = other.right - left; + if (_ascent < other.ascent) { + _ascent = other.ascent; + } + if (_descent < other.descent) { + _descent = other.descent; + } + } + + Rect toRect() => Rect.fromLTWH(left, top, width, height); + + @override + String toString() => 'LineMetrics(left: $left, baseline: $baseline, ' + 'width: $width, ascent: $ascent, descent: $descent)'; +} diff --git a/packages/flame/lib/src/text/common/text_line.dart b/packages/flame/lib/src/text/common/text_line.dart new file mode 100644 index 000000000..632df774b --- /dev/null +++ b/packages/flame/lib/src/text/common/text_line.dart @@ -0,0 +1,15 @@ +import 'package:flame/src/text/common/line_metrics.dart'; +import 'package:flame/src/text/inline/text_element.dart'; + +/// [TextLine] is an abstract class describing a single line (or a fragment of +/// a line) of a laid-out text. +/// +/// More specifically, after any [TextElement] has been laid out, its layout +/// will be described by one or more [TextLine]s. +abstract class TextLine { + /// The dimensions of this line. + LineMetrics get metrics; + + /// Move the text within this [TextLine] by the specified offsets [dx], [dy]. + void translate(double dx, double dy); +} diff --git a/packages/flame/lib/src/text/formatter_text_renderer.dart b/packages/flame/lib/src/text/formatter_text_renderer.dart new file mode 100644 index 000000000..bc1650329 --- /dev/null +++ b/packages/flame/lib/src/text/formatter_text_renderer.dart @@ -0,0 +1,35 @@ +import 'dart:ui'; + +import 'package:flame/src/anchor.dart'; +import 'package:flame/src/text/formatters/text_formatter.dart'; +import 'package:flame/text.dart'; +import 'package:vector_math/vector_math_64.dart'; + +/// Helper class that implements a [TextRenderer] using a [TextFormatter]. +class FormatterTextRenderer extends TextRenderer { + FormatterTextRenderer(this.formatter); + + final T formatter; + + @override + Vector2 measureText(String text) { + final box = formatter.format(text).lastLine.metrics; + return Vector2(box.width, box.height); + } + + @override + void render( + Canvas canvas, + String text, + Vector2 position, { + Anchor anchor = Anchor.topLeft, + }) { + final txt = formatter.format(text); + final box = txt.lastLine.metrics; + txt.lastLine.translate( + position.x - box.width * anchor.x, + position.y - box.height * anchor.y - box.top, + ); + txt.render(canvas); + } +} diff --git a/packages/flame/lib/src/text/formatters/sprite_font_text_formatter.dart b/packages/flame/lib/src/text/formatters/sprite_font_text_formatter.dart new file mode 100644 index 000000000..8f43ae7ee --- /dev/null +++ b/packages/flame/lib/src/text/formatters/sprite_font_text_formatter.dart @@ -0,0 +1,83 @@ +import 'dart:typed_data'; +import 'dart:ui' hide LineMetrics; + +import 'package:flame/src/text/common/glyph_data.dart'; +import 'package:flame/src/text/common/glyph_info.dart'; +import 'package:flame/src/text/common/line_metrics.dart'; +import 'package:flame/src/text/formatters/text_formatter.dart'; +import 'package:flame/src/text/inline/sprite_font_text_element.dart'; + +class SpriteFontTextFormatter extends TextFormatter { + SpriteFontTextFormatter({ + required this.source, + required double charWidth, + required double charHeight, + required Map glyphs, + this.scale = 1, + this.letterSpacing = 0, + }) : scaledCharWidth = charWidth * scale, + scaledCharHeight = charHeight * scale, + _glyphs = glyphs.map((char, rect) { + assert( + char.length == 1, + 'A glyph must have a single character: "$char"', + ); + final info = GlyphInfo(); + info.srcLeft = rect.left; + info.srcTop = rect.top; + info.srcRight = rect.right ?? rect.left + charWidth; + info.srcBottom = rect.bottom ?? rect.top + charHeight; + info.rstSCos = scale; + info.rstTy = (charHeight - (info.srcBottom - info.srcTop)) * scale; + info.width = charWidth * scale; + info.height = charHeight * scale; + return MapEntry(char.codeUnitAt(0), info); + }); + + final Image source; + final paint = Paint()..color = const Color(0xFFFFFFFF); + final double letterSpacing; + final double scale; + final double scaledCharWidth; + final double scaledCharHeight; + final Map _glyphs; + + @override + SpriteFontTextElement format(String text) { + final rstTransforms = Float32List(4 * text.length); + final rects = Float32List(4 * text.length); + var j = 0; + var x0 = 0.0; + final y0 = -scaledCharHeight; + for (final glyph in _textToGlyphs(text)) { + rects[j + 0] = glyph.srcLeft; + rects[j + 1] = glyph.srcTop; + rects[j + 2] = glyph.srcRight; + rects[j + 3] = glyph.srcBottom; + rstTransforms[j + 0] = glyph.rstSCos; + rstTransforms[j + 1] = glyph.rstSSin; + rstTransforms[j + 2] = x0 + glyph.rstTx; + rstTransforms[j + 3] = y0 + glyph.rstTy; + x0 += glyph.width + letterSpacing; + j += 4; + } + return SpriteFontTextElement( + source: source, + transforms: rstTransforms, + rects: rects, + paint: paint, + metrics: LineMetrics(width: x0, height: scaledCharHeight, descent: 0), + ); + } + + Iterable _textToGlyphs(String text) { + return text.codeUnits.map((int i) { + final glyph = _glyphs[i]; + assert( + glyph != null, + 'No glyph for character "${String.fromCharCode(i)}"', + ); + return glyph!; + }); + } +} diff --git a/packages/flame/lib/src/text/formatters/text_formatter.dart b/packages/flame/lib/src/text/formatters/text_formatter.dart new file mode 100644 index 000000000..46b30f890 --- /dev/null +++ b/packages/flame/lib/src/text/formatters/text_formatter.dart @@ -0,0 +1,7 @@ +import 'package:flame/src/text/inline/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 new file mode 100644 index 000000000..88daa3745 --- /dev/null +++ b/packages/flame/lib/src/text/formatters/text_painter_text_formatter.dart @@ -0,0 +1,38 @@ +import 'package:flame/src/text/formatters/text_formatter.dart'; +import 'package:flame/src/text/inline/debug_text_painter_text_element.dart'; +import 'package:flame/src/text/inline/text_painter_text_element.dart'; +import 'package:flutter/rendering.dart'; + +/// [TextPainterTextFormatter] applies a Flutter [TextStyle] to a string of +/// text, creating a [TextPainterTextElement]. +/// +/// If the [debugMode] is true, this formatter will wrap the text with a +/// [DebugTextPainterTextElement] instead. This mode is mostly useful for tests. +class TextPainterTextFormatter extends TextFormatter { + TextPainterTextFormatter({ + required this.style, + this.textDirection = TextDirection.ltr, + this.debugMode = false, + }); + + final TextStyle style; + final TextDirection textDirection; + final bool debugMode; + + @override + TextPainterTextElement format(String text) { + final tp = _textToTextPainter(text); + if (debugMode) { + return DebugTextPainterTextElement(tp); + } else { + 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/inline/debug_text_painter_text_element.dart b/packages/flame/lib/src/text/inline/debug_text_painter_text_element.dart new file mode 100644 index 000000000..9d9c076db --- /dev/null +++ b/packages/flame/lib/src/text/inline/debug_text_painter_text_element.dart @@ -0,0 +1,21 @@ +import 'dart:ui'; + +import 'package:flame/src/text/inline/text_painter_text_element.dart'; + +/// Replacement class for [TextPainterTextElement] which draws solid rectangles +/// instead of regular text. +/// +/// This class is useful for testing purposes: different test environments may +/// have slightly different font definitions and mechanisms for anti-aliased +/// font rendering, which makes it impossible to create golden tests with +/// regular text painter. +class DebugTextPainterTextElement extends TextPainterTextElement { + DebugTextPainterTextElement(super.textPainter); + + final paint = Paint()..color = const Color(0xFFFFFFFF); + + @override + void render(Canvas canvas) { + canvas.drawRect(metrics.toRect(), paint); + } +} diff --git a/packages/flame/lib/src/text/inline/sprite_font_text_element.dart b/packages/flame/lib/src/text/inline/sprite_font_text_element.dart new file mode 100644 index 000000000..9eb0c7862 --- /dev/null +++ b/packages/flame/lib/src/text/inline/sprite_font_text_element.dart @@ -0,0 +1,42 @@ +import 'dart:typed_data'; +import 'dart:ui'; + +import 'package:flame/src/text/common/line_metrics.dart'; +import 'package:flame/src/text/common/text_line.dart'; +import 'package:flame/src/text/inline/text_element.dart'; + +class SpriteFontTextElement extends TextElement implements TextLine { + SpriteFontTextElement({ + required this.source, + required this.transforms, + required this.rects, + required this.paint, + required LineMetrics metrics, + }) : _box = metrics; + + final Image source; + final Float32List transforms; + final Float32List rects; + final Paint paint; + final LineMetrics _box; + + @override + TextLine get lastLine => this; + + @override + LineMetrics get metrics => _box; + + @override + void translate(double dx, double dy) { + _box.translate(dx, dy); + for (var i = 0; i < transforms.length; i += 4) { + transforms[i + 2] += dx; + transforms[i + 3] += dy; + } + } + + @override + void render(Canvas canvas) { + canvas.drawRawAtlas(source, transforms, rects, null, null, null, paint); + } +} diff --git a/packages/flame/lib/src/text/inline/text_element.dart b/packages/flame/lib/src/text/inline/text_element.dart new file mode 100644 index 000000000..3aa551ea1 --- /dev/null +++ b/packages/flame/lib/src/text/inline/text_element.dart @@ -0,0 +1,22 @@ +import 'dart:ui' hide LineMetrics; + +import 'package:flame/src/text/common/text_line.dart'; + +/// [TextElement] is the base class describing a span of text that has *inline* +/// placement rules. +/// +/// Concrete implementations of this class must know how to perform own layout +/// (i.e. determine the exact placement and size of each internal piece), and +/// then render on a canvas afterwards. +abstract class TextElement { + TextLine get lastLine; + + /// Renders the text on the [canvas], at positions determined during the + /// layout. + /// + /// This method should only be invoked after the text was laid out. + /// + /// In order to render the text at a different location, consider applying a + /// translation transform to the canvas. + void render(Canvas canvas); +} diff --git a/packages/flame/lib/src/text/inline/text_painter_text_element.dart b/packages/flame/lib/src/text/inline/text_painter_text_element.dart new file mode 100644 index 000000000..4d0b62aa2 --- /dev/null +++ b/packages/flame/lib/src/text/inline/text_painter_text_element.dart @@ -0,0 +1,33 @@ +import 'dart:ui'; + +import 'package:flame/src/text/common/line_metrics.dart'; +import 'package:flame/src/text/common/text_line.dart'; +import 'package:flame/src/text/inline/text_element.dart'; +import 'package:flutter/rendering.dart' show TextBaseline, TextPainter; + +class TextPainterTextElement extends TextElement implements TextLine { + TextPainterTextElement(this._textPainter) + : _box = LineMetrics( + ascent: _textPainter + .computeDistanceToActualBaseline(TextBaseline.alphabetic), + width: _textPainter.width, + height: _textPainter.height, + ); + + final TextPainter _textPainter; + final LineMetrics _box; + + @override + LineMetrics get metrics => _box; + + @override + TextLine get lastLine => this; + + @override + void translate(double dx, double dy) => _box.translate(dx, dy); + + @override + void render(Canvas canvas) { + _textPainter.paint(canvas, Offset(_box.left, _box.top)); + } +} diff --git a/packages/flame/lib/src/text/sprite_font_renderer.dart b/packages/flame/lib/src/text/sprite_font_renderer.dart index f8dbe098c..b05dd44c9 100644 --- a/packages/flame/lib/src/text/sprite_font_renderer.dart +++ b/packages/flame/lib/src/text/sprite_font_renderer.dart @@ -1,9 +1,9 @@ -import 'dart:typed_data'; import 'dart:ui'; -import 'package:flame/src/anchor.dart'; +import 'package:flame/src/text/common/glyph_data.dart'; +import 'package:flame/src/text/formatter_text_renderer.dart'; +import 'package:flame/src/text/formatters/sprite_font_text_formatter.dart'; import 'package:flame/src/text/text_renderer.dart'; -import 'package:vector_math/vector_math_64.dart'; /// [TextRenderer] implementation that uses a spritesheet of various font glyphs /// to render text. @@ -14,134 +14,31 @@ import 'package:vector_math/vector_math_64.dart'; /// /// 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 +/// 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 +/// `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 +/// 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 { +class SpriteFontRenderer + extends FormatterTextRenderer { SpriteFontRenderer({ - required this.source, + required Image source, required double charWidth, required double charHeight, required Map glyphs, - this.scale = 1, - this.letterSpacing = 0, - }) : scaledCharWidth = charWidth * scale, - scaledCharHeight = charHeight * scale, - _glyphs = glyphs.map((char, rect) { - assert( - char.length == 1, - 'A glyph must have a single character: "$char"', - ); - final info = _GlyphInfo(); - info.srcLeft = rect.left; - info.srcTop = rect.top; - info.srcRight = rect.right ?? rect.left + charWidth; - info.srcBottom = rect.bottom ?? rect.top + charHeight; - info.rstSCos = scale; - info.rstTy = (charHeight - (info.srcBottom - info.srcTop)) * scale; - info.width = charWidth * scale; - info.height = charHeight * scale; - return MapEntry(char.codeUnitAt(0), info); - }); - - final Image source; - final Map _glyphs; - final double letterSpacing; - final double scale; - final double scaledCharWidth; - final double scaledCharHeight; - bool get isMonospace => true; - - Paint paint = Paint()..color = const Color(0xFFFFFFFF); - - @override - double measureTextHeight(String text) => scaledCharHeight; - - @override - double measureTextWidth(String text) { - final n = text.length; - return n > 0 ? scaledCharWidth * n + letterSpacing * (n - 1) : 0; - } - - @override - Vector2 measureText(String text) { - return Vector2(measureTextWidth(text), measureTextHeight(text)); - } - - @override - void render( - Canvas canvas, - String text, - Vector2 position, { - Anchor anchor = Anchor.topLeft, - }) { - final rstTransforms = Float32List(4 * text.length); - final rects = Float32List(4 * text.length); - var j = 0; - var x0 = position.x; - final y0 = position.y; - for (final glyph in _textToGlyphs(text)) { - rects[j + 0] = glyph.srcLeft; - rects[j + 1] = glyph.srcTop; - rects[j + 2] = glyph.srcRight; - rects[j + 3] = glyph.srcBottom; - rstTransforms[j + 0] = glyph.rstSCos; - rstTransforms[j + 1] = glyph.rstSSin; - rstTransforms[j + 2] = x0 + glyph.rstTx; - rstTransforms[j + 3] = y0 + glyph.rstTy; - x0 += glyph.width + letterSpacing; - j += 4; - } - canvas.drawRawAtlas(source, rstTransforms, rects, null, null, null, paint); - } - - Iterable<_GlyphInfo> _textToGlyphs(String text) { - return text.codeUnits.map(_getGlyphFromCodeUnit); - } - - _GlyphInfo _getGlyphFromCodeUnit(int i) { - final glyph = _glyphs[i]; - if (glyph == null) { - throw ArgumentError('No glyph for character "${String.fromCharCode(i)}"'); - } - return glyph; - } -} - -class GlyphData { - const GlyphData({ - required this.left, - required this.top, - this.right, - this.bottom, - }); - - const GlyphData.fromLTWH(this.left, this.top, double width, double height) - : right = left + width, - bottom = top + height; - - const GlyphData.fromLTRB(this.left, this.top, this.right, this.bottom); - - final double left; - final double top; - final double? right; - final double? bottom; -} - -class _GlyphInfo { - double srcLeft = 0; - double srcTop = 0; - double srcRight = 0; - double srcBottom = 0; - double rstSCos = 1; - double rstSSin = 0; - double rstTx = 0; - double rstTy = 0; - double width = 0; - double height = 0; + double scale = 1, + double letterSpacing = 0, + }) : super( + SpriteFontTextFormatter( + source: source, + charWidth: charWidth, + charHeight: charHeight, + glyphs: glyphs, + scale: scale, + letterSpacing: letterSpacing, + ), + ); } diff --git a/packages/flame/lib/src/text/text_paint.dart b/packages/flame/lib/src/text/text_paint.dart index 6a690a96e..a51e24cd0 100644 --- a/packages/flame/lib/src/text/text_paint.dart +++ b/packages/flame/lib/src/text/text_paint.dart @@ -1,6 +1,6 @@ -import 'package:flame/src/anchor.dart'; import 'package:flame/src/cache/memory_cache.dart'; -import 'package:flame/src/extensions/vector2.dart'; +import 'package:flame/src/text/formatter_text_renderer.dart'; +import 'package:flame/src/text/formatters/text_painter_text_formatter.dart'; import 'package:flame/src/text/text_renderer.dart'; import 'package:flutter/rendering.dart'; @@ -10,14 +10,19 @@ import 'package:flutter/rendering.dart'; /// 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({TextStyle? style, TextDirection? textDirection}) - : style = style ?? defaultTextStyle, - textDirection = textDirection ?? TextDirection.ltr; +class TextPaint extends FormatterTextRenderer { + TextPaint({TextStyle? style, TextDirection? textDirection, bool? debugMode}) + : super( + TextPainterTextFormatter( + style: style ?? defaultTextStyle, + textDirection: textDirection ?? TextDirection.ltr, + debugMode: debugMode ?? false, + ), + ); - final TextStyle style; + TextStyle get style => formatter.style; - final TextDirection textDirection; + TextDirection get textDirection => formatter.textDirection; final MemoryCache _textPainterCache = MemoryCache(); @@ -27,27 +32,6 @@ class TextPaint extends TextRenderer { fontSize: 24, ); - @override - void render( - Canvas canvas, - String text, - Vector2 p, { - Anchor anchor = Anchor.topLeft, - }) { - final tp = toTextPainter(text); - final translatedPosition = Offset( - p.x - tp.width * anchor.x, - p.y - tp.height * anchor.y, - ); - tp.paint(canvas, translatedPosition); - } - - @override - Vector2 measureText(String text) { - final tp = toTextPainter(text); - return Vector2(tp.width, tp.height); - } - /// Returns a [TextPainter] that allows for text rendering and size /// measuring. /// @@ -79,6 +63,9 @@ class TextPaint extends TextRenderer { TextStyle Function(TextStyle) transform, { TextDirection? textDirection, }) { - return TextPaint(style: transform(style), textDirection: textDirection); + return TextPaint( + style: transform(formatter.style), + textDirection: textDirection ?? formatter.textDirection, + ); } } diff --git a/packages/flame/lib/text.dart b/packages/flame/lib/text.dart index a06be9325..2af56f3d7 100644 --- a/packages/flame/lib/text.dart +++ b/packages/flame/lib/text.dart @@ -1,3 +1,4 @@ -export 'src/text/sprite_font_renderer.dart' show SpriteFontRenderer, GlyphData; +export 'src/text/common/glyph_data.dart' show GlyphData; +export 'src/text/sprite_font_renderer.dart' show SpriteFontRenderer; export 'src/text/text_paint.dart' show TextPaint; export 'src/text/text_renderer.dart' show TextRenderer; diff --git a/packages/flame/test/_goldens/text_box_component_test_1.png b/packages/flame/test/_goldens/text_box_component_test_1.png index 800c7f40a..16ad6651b 100644 Binary files a/packages/flame/test/_goldens/text_box_component_test_1.png and b/packages/flame/test/_goldens/text_box_component_test_1.png differ diff --git a/packages/flame/test/components/text_box_component_test.dart b/packages/flame/test/components/text_box_component_test.dart index e08a27b9e..3db7e40e5 100644 --- a/packages/flame/test/components/text_box_component_test.dart +++ b/packages/flame/test/components/text_box_component_test.dart @@ -119,7 +119,6 @@ void main() { ]); }, goldenFile: '../_goldens/text_box_component_test_1.png', - skip: true, ); }); } @@ -130,7 +129,7 @@ class _FramedTextBox extends TextBoxComponent { super.align, super.position, super.size, - }); + }) : super(textRenderer: TextPaint(debugMode: true)); final Paint _borderPaint = Paint() ..style = PaintingStyle.stroke diff --git a/packages/flame/test/text/common/line_metrics_test.dart b/packages/flame/test/text/common/line_metrics_test.dart new file mode 100644 index 000000000..fbc6aca57 --- /dev/null +++ b/packages/flame/test/text/common/line_metrics_test.dart @@ -0,0 +1,122 @@ +import 'package:flame/src/text/common/line_metrics.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:test/test.dart'; + +void main() { + group('LineMetrics', () { + test('default LineMetrics box', () { + final box = LineMetrics(left: 10, baseline: 20); + expect(box.left, 10); + expect(box.right, 10); + expect(box.width, 0); + expect(box.baseline, 20); + expect(box.top, 20); + expect(box.bottom, 20); + expect(box.ascent, 0); + expect(box.descent, 0); + expect(box.height, 0); + }); + + test('with height only', () { + final box = LineMetrics(baseline: 15, height: 10); + expect(box.baseline, 15); + expect(box.height, 10); + expect(box.top, 15 - 10); + expect(box.bottom, 15); + expect(box.ascent, 10); + expect(box.descent, 0); + }); + + test('with height and ascent', () { + final box = LineMetrics(baseline: 15, height: 10, ascent: 8); + expect(box.baseline, 15); + expect(box.height, 10); + expect(box.top, 15 - 8); + expect(box.bottom, 15 - 8 + 10); + expect(box.ascent, 8); + expect(box.descent, 10 - 8); + }); + + test('with height and descent', () { + final box = LineMetrics(baseline: 15, height: 10, descent: 3); + expect(box.baseline, 15); + expect(box.height, 10); + expect(box.top, 15 + 3 - 10); + expect(box.bottom, 15 + 3); + expect(box.ascent, 10 - 3); + expect(box.descent, 3); + }); + + test('with ascent and descent', () { + final box = LineMetrics(baseline: 15, ascent: 10, descent: 3); + expect(box.baseline, 15); + expect(box.height, 10 + 3); + expect(box.top, 15 - 10); + expect(box.bottom, 15 + 3); + expect(box.ascent, 10); + expect(box.descent, 3); + }); + + test('translate', () { + final box = LineMetrics(width: 40, descent: 2, ascent: 8); + expect(box.left, 0); + expect(box.baseline, 0); + box.translate(5, 11); + expect(box.left, 5); + expect(box.baseline, 11); + expect(box.width, 40); + expect(box.height, 10); + expect(box.ascent, 8); + box.translate(-1, -1); + expect(box.left, 4); + expect(box.baseline, 10); + }); + + test('moveToOrigin', () { + final box = LineMetrics(left: 33, baseline: 78, ascent: 8, descent: 2); + box.moveToOrigin(); + expect(box.left, 0); + expect(box.baseline, 0); + expect(box.top, -8); + expect(box.bottom, 2); + }); + + test('setLeftEdge', () { + final box = LineMetrics(left: 33, baseline: 78, width: 101); + expect(box.right, 33 + 101); + box.setLeftEdge(49); + expect(box.right, 33 + 101); + expect(box.left, 49); + }); + + test('append', () { + final box1 = LineMetrics(left: 4, width: 10, baseline: 17, height: 6); + final box2 = LineMetrics( + left: 18, + width: 40, + baseline: 17, + ascent: 8, + descent: 2, + ); + box1.append(box2); + expect(box1.left, 4); + expect(box1.right, 18 + 40); + expect(box1.top, 17 - 8); + expect(box1.bottom, 17 + 2); + + expect( + () => box1.append(LineMetrics()), + failsAssert('Baselines do not match: 17.0 vs 0.0'), + ); + }); + + test('toString', () { + final box = LineMetrics(left: 33, baseline: 78, width: 101, height: 11); + expect( + box.toString(), + 'LineMetrics(left: 33.0, baseline: 78.0, width: 101.0, ascent: 11.0, ' + 'descent: 0.0)', + ); + }); + }); +} diff --git a/packages/flame/test/text/sprite_font_renderer_test.dart b/packages/flame/test/text/sprite_font_renderer_test.dart index 408746077..6a8b19e3a 100644 --- a/packages/flame/test/text/sprite_font_renderer_test.dart +++ b/packages/flame/test/text/sprite_font_renderer_test.dart @@ -12,15 +12,14 @@ void main() { group('SpriteFontRenderer', () { test('creating SpriteFontRenderer', () async { final renderer = await createRenderer(); - expect(renderer.source, isA()); - expect(renderer.scaledCharWidth, 6); - expect(renderer.scaledCharHeight, 6); - expect(renderer.letterSpacing, 0); - expect(renderer.isMonospace, true); + expect(renderer.formatter.source, isA()); + expect(renderer.formatter.scaledCharWidth, 6); + expect(renderer.formatter.scaledCharHeight, 6); + expect(renderer.formatter.letterSpacing, 0); expect( () => renderer.render(MockCanvas(), 'Ї', Vector2.zero()), - throwsArgumentError, + failsAssert('No glyph for character "Ї"'), ); }); @@ -43,7 +42,7 @@ void main() { TextComponent( text: 'FLAME', textRenderer: (await createRenderer(scale: 25)) - ..paint.color = const Color(0x44000000), + ..formatter.paint.color = const Color(0x44000000), position: Vector2(400, 500), anchor: Anchor.center, ),