mirror of
				https://github.com/flame-engine/flame.git
				synced 2025-11-01 01:18:38 +08:00 
			
		
		
		
	feat: Add support for styles propagating through the text node tree (#1915)
This PR continues the work for enabling rich text support within Flame. Here I add support for different text fragments having different TextStyles, and allow those styles to be inheritable within the text node tree.
This commit is contained in:
		| @ -11,7 +11,7 @@ class RichTextExample extends FlameGame { | ||||
|  | ||||
|   @override | ||||
|   Future<void> onLoad() async { | ||||
|     add(MyTextComponent()..position = Vector2.all(100)); | ||||
|     add(MyTextComponent()..position = Vector2(100, 50)); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -29,8 +29,7 @@ class MyTextComponent extends PositionComponent { | ||||
|         borderColor: const Color(0xFF000000), | ||||
|         borderWidth: 2.0, | ||||
|       ), | ||||
|       paragraphStyle: BlockStyle( | ||||
|         margin: const EdgeInsets.symmetric(vertical: 6), | ||||
|       paragraph: BlockStyle( | ||||
|         padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 6), | ||||
|         background: BackgroundStyle( | ||||
|           color: const Color(0xFFFFF0CB), | ||||
| @ -39,6 +38,7 @@ class MyTextComponent extends PositionComponent { | ||||
|       ), | ||||
|     ); | ||||
|     final document = DocumentNode([ | ||||
|       HeaderNode.simple('1984', level: 1), | ||||
|       ParagraphNode.simple( | ||||
|         'Anything could be true. The so-called laws of nature were nonsense.', | ||||
|       ), | ||||
| @ -48,11 +48,16 @@ class MyTextComponent extends PositionComponent { | ||||
|         'out. "If he thinks he floats off the floor, and I simultaneously ' | ||||
|         'think I can see him do it, then the thing happens."', | ||||
|       ), | ||||
|       ParagraphNode.simple( | ||||
|         'Suddenly, like a lump of submerged wreckage breaking the surface of ' | ||||
|         'water, the thought burst into his mind: "It doesn\'t really happen. ' | ||||
|         'We imagine it. It is hallucination."', | ||||
|       ), | ||||
|       ParagraphNode.group([ | ||||
|         PlainTextNode( | ||||
|             'Suddenly, like a lump of submerged wreckage breaking the surface ' | ||||
|             'of water, the thought burst into his mind: '), | ||||
|         ItalicTextNode.group([ | ||||
|           PlainTextNode('"It doesn\'t really happen. We imagine it. It is '), | ||||
|           BoldTextNode.simple('hallucination'), | ||||
|           PlainTextNode('."'), | ||||
|         ]), | ||||
|       ]), | ||||
|       ParagraphNode.simple( | ||||
|         'He pushed the thought under instantly. The fallacy was obvious. It ' | ||||
|         'presupposed that somewhere or other, outside oneself, there was a ' | ||||
|  | ||||
| @ -46,8 +46,8 @@ class TextComponent<T extends TextRenderer> extends PositionComponent { | ||||
|     if (_textRenderer is FormatterTextRenderer) { | ||||
|       _textElement = | ||||
|           (_textRenderer as FormatterTextRenderer).formatter.format(_text); | ||||
|       final measurements = _textElement!.lastLine.metrics; | ||||
|       _textElement!.lastLine.translate(0, measurements.ascent); | ||||
|       final measurements = _textElement!.metrics; | ||||
|       _textElement!.translate(0, measurements.ascent); | ||||
|       size.setValues(measurements.width, measurements.height); | ||||
|     } else { | ||||
|       final expectedSize = textRenderer.measureText(_text); | ||||
|  | ||||
| @ -1,8 +1,6 @@ | ||||
| import 'dart:ui'; | ||||
|  | ||||
| import 'package:flame/src/text/common/text_line.dart'; | ||||
|  | ||||
| /// The [LineMetrics] object contains measurements of a [TextLine]. | ||||
| /// The [LineMetrics] object contains measurements of a text line. | ||||
| /// | ||||
| /// 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 | ||||
|  | ||||
| @ -1,15 +0,0 @@ | ||||
| import 'package:flame/src/text/common/line_metrics.dart'; | ||||
| import 'package:flame/src/text/elements/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); | ||||
| } | ||||
| @ -1,5 +1,10 @@ | ||||
| import 'dart:math'; | ||||
|  | ||||
| import 'package:flame/src/text/elements/element.dart'; | ||||
| import 'package:flame/src/text/elements/group_element.dart'; | ||||
| import 'package:flame/src/text/elements/rect_element.dart'; | ||||
| import 'package:flame/src/text/elements/rrect_element.dart'; | ||||
| import 'package:flame/src/text/styles/background_style.dart'; | ||||
| import 'package:meta/meta.dart'; | ||||
|  | ||||
| @internal | ||||
| @ -10,3 +15,51 @@ double collapseMargin(double margin1, double margin2) { | ||||
|     return (margin2 < 0) ? min(margin1, margin2) : margin1 + margin2; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @internal | ||||
| Element? makeBackground(BackgroundStyle? style, double width, double height) { | ||||
|   if (style == null) { | ||||
|     return null; | ||||
|   } | ||||
|   final out = <Element>[]; | ||||
|   final backgroundPaint = style.backgroundPaint; | ||||
|   final borderPaint = style.borderPaint; | ||||
|   final borders = style.borderWidths; | ||||
|   final radius = style.borderRadius; | ||||
|  | ||||
|   if (backgroundPaint != null) { | ||||
|     if (radius == 0) { | ||||
|       out.add(RectElement(width, height, backgroundPaint)); | ||||
|     } else { | ||||
|       out.add(RRectElement(width, height, radius, backgroundPaint)); | ||||
|     } | ||||
|   } | ||||
|   if (borderPaint != null) { | ||||
|     if (radius == 0) { | ||||
|       out.add( | ||||
|         RectElement( | ||||
|           width - borders.horizontal / 2, | ||||
|           height - borders.vertical / 2, | ||||
|           borderPaint, | ||||
|         )..translate(borders.left / 2, borders.top / 2), | ||||
|       ); | ||||
|     } else { | ||||
|       out.add( | ||||
|         RRectElement( | ||||
|           width - borders.horizontal / 2, | ||||
|           height - borders.vertical / 2, | ||||
|           radius, | ||||
|           borderPaint, | ||||
|         )..translate(borders.left / 2, borders.top / 2), | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
|   if (out.isEmpty) { | ||||
|     return null; | ||||
|   } | ||||
|   if (out.length == 1) { | ||||
|     return out.first; | ||||
|   } else { | ||||
|     return GroupElement(width: width, height: height, children: out); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -7,6 +7,7 @@ import 'package:flame/src/text/elements/element.dart'; | ||||
| /// such as `<div>` or `<blockquote>`. | ||||
| abstract class BlockElement extends Element { | ||||
|   BlockElement(this.width, this.height); | ||||
|   double width; | ||||
|   double height; | ||||
|  | ||||
|   final double width; | ||||
|   final double height; | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import 'dart:ui'; | ||||
| 
 | ||||
| import 'package:flame/src/text/inline/text_painter_text_element.dart'; | ||||
| import 'package:flame/src/text/elements/text_painter_text_element.dart'; | ||||
| 
 | ||||
| /// Replacement class for [TextPainterTextElement] which draws solid rectangles | ||||
| /// instead of regular text. | ||||
| @ -1,15 +1,17 @@ | ||||
| import 'dart:ui'; | ||||
| import 'package:flame/src/text/styles/style.dart'; | ||||
|  | ||||
| /// An [Element] is a basic building block of a rich-text document. | ||||
| /// An [Element] is a basic rendering block of a rich-text document. | ||||
| /// | ||||
| /// Elements 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 | ||||
| /// structured pieces of text), and from [Style]s (which are descriptors for how | ||||
| /// arbitrary pieces of text ought to be rendered). | ||||
| /// | ||||
| /// Elements are at the final stage of the text rendering pipeline, they are | ||||
| /// created during the layout step. | ||||
| abstract class Element { | ||||
|   /// Moves the element by ([dx], [dy]) relative to its current location. | ||||
|   void translate(double dx, double dy); | ||||
|  | ||||
|   /// Renders the element on the [canvas], at coordinates determined during the | ||||
| @ -17,6 +19,6 @@ abstract class Element { | ||||
|   /// | ||||
|   /// In order to render the element at a different location, consider either | ||||
|   /// calling the [translate] method, or applying a translation transform to the | ||||
|   /// canvas beforehand. | ||||
|   /// canvas itself. | ||||
|   void render(Canvas canvas); | ||||
| } | ||||
|  | ||||
| @ -1,10 +1,13 @@ | ||||
| import 'dart:ui'; | ||||
|  | ||||
| import 'package:flame/src/text/elements/block_element.dart'; | ||||
| import 'package:flame/src/text/elements/element.dart'; | ||||
| import 'package:flutter/rendering.dart' hide TextStyle; | ||||
|  | ||||
| class GroupElement extends BlockElement { | ||||
|   GroupElement(super.width, super.height, this.children); | ||||
|   GroupElement({ | ||||
|     required double width, | ||||
|     required double height, | ||||
|     required this.children, | ||||
|   }) : super(width, height); | ||||
|  | ||||
|   final List<Element> children; | ||||
|  | ||||
|  | ||||
							
								
								
									
										48
									
								
								packages/flame/lib/src/text/elements/group_text_element.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								packages/flame/lib/src/text/elements/group_text_element.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | ||||
| import 'dart:math'; | ||||
| import 'dart:ui'; | ||||
|  | ||||
| import 'package:flame/src/text/common/line_metrics.dart'; | ||||
| import 'package:flame/src/text/elements/text_element.dart'; | ||||
|  | ||||
| class GroupTextElement extends TextElement { | ||||
|   GroupTextElement(List<TextElement> children) | ||||
|       : assert(children.isNotEmpty, 'The children list cannot be empty'), | ||||
|         _children = children, | ||||
|         _metrics = _computeMetrics(children); | ||||
|  | ||||
|   final List<TextElement> _children; | ||||
|   final LineMetrics _metrics; | ||||
|  | ||||
|   @override | ||||
|   LineMetrics get metrics => _metrics; | ||||
|  | ||||
|   @override | ||||
|   void render(Canvas canvas) { | ||||
|     for (final child in _children) { | ||||
|       child.render(canvas); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void translate(double dx, double dy) { | ||||
|     _metrics.translate(dx, dy); | ||||
|     for (final child in _children) { | ||||
|       child.translate(dx, dy); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   static LineMetrics _computeMetrics(List<TextElement> elements) { | ||||
|     var width = 0.0; | ||||
|     var ascent = 0.0; | ||||
|     var descent = 0.0; | ||||
|     for (final element in elements) { | ||||
|       final metrics = element.metrics; | ||||
|       assert(metrics.left == width); | ||||
|       assert(metrics.baseline == 0); | ||||
|       width += metrics.width; | ||||
|       ascent = max(ascent, metrics.ascent); | ||||
|       descent = max(descent, metrics.descent); | ||||
|     } | ||||
|     return LineMetrics(width: width, ascent: ascent, descent: descent); | ||||
|   } | ||||
| } | ||||
| @ -2,10 +2,9 @@ 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/elements/text_element.dart'; | ||||
| 
 | ||||
| class SpriteFontTextElement extends TextElement implements TextLine { | ||||
| class SpriteFontTextElement extends TextElement { | ||||
|   SpriteFontTextElement({ | ||||
|     required this.source, | ||||
|     required this.transforms, | ||||
| @ -20,9 +19,6 @@ class SpriteFontTextElement extends TextElement implements TextLine { | ||||
|   final Paint paint; | ||||
|   final LineMetrics _box; | ||||
| 
 | ||||
|   @override | ||||
|   TextLine get lastLine => this; | ||||
| 
 | ||||
|   @override | ||||
|   LineMetrics get metrics => _box; | ||||
| 
 | ||||
| @ -1,12 +1,9 @@ | ||||
| import 'package:flame/src/text/common/text_line.dart'; | ||||
| import 'package:flame/src/text/common/line_metrics.dart'; | ||||
| import 'package:flame/src/text/elements/element.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. | ||||
| /// [TextElement] is the base class that represents a single line of text, laid | ||||
| /// out and prepared for rendering. | ||||
| abstract class TextElement extends Element { | ||||
|   TextLine get lastLine; | ||||
|   /// The dimensions of this line. | ||||
|   LineMetrics get metrics; | ||||
| } | ||||
|  | ||||
| @ -1,29 +1,25 @@ | ||||
| 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/elements/element.dart'; | ||||
| import 'package:flame/src/text/elements/text_element.dart'; | ||||
| import 'package:flutter/rendering.dart' show TextBaseline, TextPainter; | ||||
| import 'package:flutter/rendering.dart' as flutter; | ||||
| 
 | ||||
| class TextPainterTextElement extends TextElement implements TextLine, Element { | ||||
| class TextPainterTextElement extends TextElement { | ||||
|   TextPainterTextElement(this._textPainter) | ||||
|       : _box = LineMetrics( | ||||
|           ascent: _textPainter | ||||
|               .computeDistanceToActualBaseline(TextBaseline.alphabetic), | ||||
|           ascent: _textPainter.computeDistanceToActualBaseline( | ||||
|             flutter.TextBaseline.alphabetic, | ||||
|           ), | ||||
|           width: _textPainter.width, | ||||
|           height: _textPainter.height, | ||||
|         ); | ||||
| 
 | ||||
|   final TextPainter _textPainter; | ||||
|   final flutter.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); | ||||
| 
 | ||||
| @ -12,7 +12,7 @@ class FormatterTextRenderer<T extends TextFormatter> extends TextRenderer { | ||||
|  | ||||
|   @override | ||||
|   Vector2 measureText(String text) { | ||||
|     final box = formatter.format(text).lastLine.metrics; | ||||
|     final box = formatter.format(text).metrics; | ||||
|     return Vector2(box.width, box.height); | ||||
|   } | ||||
|  | ||||
| @ -24,8 +24,8 @@ class FormatterTextRenderer<T extends TextFormatter> extends TextRenderer { | ||||
|     Anchor anchor = Anchor.topLeft, | ||||
|   }) { | ||||
|     final txt = formatter.format(text); | ||||
|     final box = txt.lastLine.metrics; | ||||
|     txt.lastLine.translate( | ||||
|     final box = txt.metrics; | ||||
|     txt.translate( | ||||
|       position.x - box.width * anchor.x, | ||||
|       position.y - box.height * anchor.y - box.top, | ||||
|     ); | ||||
|  | ||||
| @ -5,9 +5,9 @@ import 'package:flame/src/text/common/glyph.dart'; | ||||
| import 'package:flame/src/text/common/glyph_data.dart'; | ||||
| 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/elements/text_element.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 { | ||||
|   @Deprecated('Use SpriteFontTextFormatter.fromFont() instead; this ' | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import 'package:flame/src/text/elements/debug_text_painter_text_element.dart'; | ||||
| import 'package:flame/src/text/elements/text_painter_text_element.dart'; | ||||
| 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 | ||||
| @ -16,7 +16,7 @@ class TextPainterTextFormatter extends TextFormatter { | ||||
|         this.debugMode = false, | ||||
|   }); | ||||
|  | ||||
|   final TextStyle style; | ||||
|   final TextStyle style; // NOTE: this is a Flutter TextStyle | ||||
|   final TextDirection textDirection; | ||||
|   @Deprecated('Use DebugTextFormatter instead. Will be removed in 1.5.0') | ||||
|   final bool debugMode; | ||||
|  | ||||
| @ -1,41 +0,0 @@ | ||||
| import 'package:flame/src/text/nodes/block_node.dart'; | ||||
|  | ||||
| class GroupBlockNode extends BlockNode { | ||||
|   GroupBlockNode(this.children); | ||||
|  | ||||
|   final List<BlockNode> children; | ||||
| } | ||||
|  | ||||
| class BlockquoteNode extends GroupBlockNode { | ||||
|   BlockquoteNode(super.children); | ||||
| } | ||||
|  | ||||
| abstract class TextNode {} | ||||
|  | ||||
| class PlainTextNode extends TextNode { | ||||
|   PlainTextNode(this.text); | ||||
|  | ||||
|   final String text; | ||||
| } | ||||
|  | ||||
| class GroupTextNode extends TextNode { | ||||
|   GroupTextNode(this.children); | ||||
|  | ||||
|   final List<TextNode> children; | ||||
| } | ||||
|  | ||||
| class BoldTextNode extends GroupTextNode { | ||||
|   BoldTextNode(super.children); | ||||
| } | ||||
|  | ||||
| class ItalicTextNode extends GroupTextNode { | ||||
|   ItalicTextNode(super.children); | ||||
| } | ||||
|  | ||||
| class StrikethroughTextNode extends GroupTextNode { | ||||
|   StrikethroughTextNode(super.children); | ||||
| } | ||||
|  | ||||
| class HighlightedTextNode extends GroupTextNode { | ||||
|   HighlightedTextNode(super.children); | ||||
| } | ||||
| @ -1,55 +1,20 @@ | ||||
| import 'package:flame/src/text/elements/element.dart'; | ||||
| import 'package:flame/src/text/elements/group_element.dart'; | ||||
| import 'package:flame/src/text/elements/rect_element.dart'; | ||||
| import 'package:flame/src/text/elements/rrect_element.dart'; | ||||
| import 'package:flame/src/text/styles/background_style.dart'; | ||||
| import 'package:flame/src/text/elements/block_element.dart'; | ||||
| import 'package:flame/src/text/styles/block_style.dart'; | ||||
| import 'package:flame/src/text/styles/document_style.dart'; | ||||
| import 'package:flame/src/text/styles/flame_text_style.dart'; | ||||
|  | ||||
| /// An abstract base class for all entities with "block" placement rules. | ||||
| /// [BlockNode] is a base class for all nodes with "block" placement rules; it | ||||
| /// roughly corresponds to `<div/>` in HTML. | ||||
| /// | ||||
| /// A block node should be able to find its style in the root stylesheet, via | ||||
| /// the method [fillStyles], and then based on that style build the | ||||
| /// corresponding element in the [format] method. Both of these methods must be | ||||
| /// implemented by subclasses. | ||||
| abstract class BlockNode { | ||||
|   Element? makeBackground(BackgroundStyle? style, double width, double height) { | ||||
|     if (style == null) { | ||||
|       return null; | ||||
|     } | ||||
|     final out = <Element>[]; | ||||
|     final backgroundPaint = style.backgroundPaint; | ||||
|     final borderPaint = style.borderPaint; | ||||
|     final borders = style.borderWidths; | ||||
|     final radius = style.borderRadius; | ||||
|   /// The runtime style applied to this node, this will be set by [fillStyles]. | ||||
|   late BlockStyle style; | ||||
|  | ||||
|     if (backgroundPaint != null) { | ||||
|       if (radius == 0) { | ||||
|         out.add(RectElement(width, height, backgroundPaint)); | ||||
|       } else { | ||||
|         out.add(RRectElement(width, height, radius, backgroundPaint)); | ||||
|       } | ||||
|     } | ||||
|     if (borderPaint != null) { | ||||
|       if (radius == 0) { | ||||
|         out.add( | ||||
|           RectElement( | ||||
|             width - borders.horizontal / 2, | ||||
|             height - borders.vertical / 2, | ||||
|             borderPaint, | ||||
|           )..translate(borders.left / 2, borders.top / 2), | ||||
|         ); | ||||
|       } else { | ||||
|         out.add( | ||||
|           RRectElement( | ||||
|             width - borders.horizontal / 2, | ||||
|             height - borders.vertical / 2, | ||||
|             radius, | ||||
|             borderPaint, | ||||
|           )..translate(borders.left / 2, borders.top / 2), | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|     if (out.isEmpty) { | ||||
|       return null; | ||||
|     } | ||||
|     if (out.length == 1) { | ||||
|       return out.first; | ||||
|     } else { | ||||
|       return GroupElement(width, height, out); | ||||
|     } | ||||
|   } | ||||
|   BlockElement format(double availableWidth); | ||||
|  | ||||
|   void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle); | ||||
| } | ||||
|  | ||||
							
								
								
									
										29
									
								
								packages/flame/lib/src/text/nodes/bold_text_node.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								packages/flame/lib/src/text/nodes/bold_text_node.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| import 'dart:ui'; | ||||
|  | ||||
| import 'package:flame/src/text/nodes/group_text_node.dart'; | ||||
| import 'package:flame/src/text/nodes/plain_text_node.dart'; | ||||
| import 'package:flame/src/text/nodes/text_node.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/style.dart'; | ||||
|  | ||||
| class BoldTextNode extends TextNode { | ||||
|   BoldTextNode(this.child); | ||||
|  | ||||
|   BoldTextNode.simple(String text) : child = PlainTextNode(text); | ||||
|  | ||||
|   BoldTextNode.group(List<TextNode> children) : child = GroupTextNode(children); | ||||
|  | ||||
|   final TextNode child; | ||||
|  | ||||
|   static final defaultStyle = FlameTextStyle(fontWeight: FontWeight.bold); | ||||
|  | ||||
|   @override | ||||
|   void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle) { | ||||
|     textStyle = Style.merge(parentTextStyle, stylesheet.boldText)!; | ||||
|     child.fillStyles(stylesheet, textStyle); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   TextNodeLayoutBuilder get layoutBuilder => child.layoutBuilder; | ||||
| } | ||||
							
								
								
									
										64
									
								
								packages/flame/lib/src/text/nodes/column_node.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								packages/flame/lib/src/text/nodes/column_node.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | ||||
| import 'package:flame/src/text/common/utils.dart'; | ||||
| import 'package:flame/src/text/elements/block_element.dart'; | ||||
| import 'package:flame/src/text/elements/element.dart'; | ||||
| import 'package:flame/src/text/elements/group_element.dart'; | ||||
| import 'package:flame/src/text/nodes/block_node.dart'; | ||||
| import 'package:flame/src/text/styles/document_style.dart'; | ||||
| import 'package:flame/src/text/styles/flame_text_style.dart'; | ||||
| import 'package:meta/meta.dart'; | ||||
|  | ||||
| /// [ColumnNode] is a block node containing other block nodes arranged as a | ||||
| /// column. | ||||
| /// | ||||
| /// During formatting, produces an element which is as wide as the available | ||||
| /// width, and tall enough to fit all the available children elements. | ||||
| abstract class ColumnNode extends BlockNode { | ||||
|   ColumnNode(this.children); | ||||
|  | ||||
|   final List<BlockNode> children; | ||||
|  | ||||
|   @override | ||||
|   BlockElement format(double availableWidth) { | ||||
|     final out = <Element>[]; | ||||
|     final blockWidth = availableWidth; | ||||
|     final padding = style.padding; | ||||
|  | ||||
|     var verticalOffset = 0.0; | ||||
|     var currentMargin = padding.top; | ||||
|     for (final node in children) { | ||||
|       final nodeMargins = node.style.margin; | ||||
|       final marginLeft = collapseMargin(padding.left, nodeMargins.left); | ||||
|       final marginRight = collapseMargin(padding.right, nodeMargins.right); | ||||
|       final nodeAvailableWidth = blockWidth - marginLeft - marginRight; | ||||
|       final nodeElement = node.format(nodeAvailableWidth); | ||||
|       out.add(nodeElement); | ||||
|  | ||||
|       verticalOffset += collapseMargin(currentMargin, nodeMargins.top); | ||||
|       nodeElement.translate(marginLeft, verticalOffset); | ||||
|       verticalOffset += nodeElement.height; | ||||
|       currentMargin = nodeMargins.bottom; | ||||
|     } | ||||
|     // Do not collapse padding if there are no children. | ||||
|     final blockHeight = children.isEmpty | ||||
|         ? padding.vertical | ||||
|         : verticalOffset + collapseMargin(currentMargin, padding.bottom); | ||||
|     final background = | ||||
|         makeBackground(style.background, blockWidth, blockHeight); | ||||
|     if (background != null) { | ||||
|       out.insert(0, background); | ||||
|     } | ||||
|     return GroupElement( | ||||
|       width: blockWidth, | ||||
|       height: blockHeight, | ||||
|       children: out, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @mustCallSuper | ||||
|   @override | ||||
|   void fillStyles(DocumentStyle rootStyle, FlameTextStyle parentTextStyle) { | ||||
|     for (final node in children) { | ||||
|       node.fillStyles(rootStyle, parentTextStyle); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -3,13 +3,14 @@ import 'dart:math'; | ||||
| import 'package:flame/src/text/common/utils.dart'; | ||||
| import 'package:flame/src/text/elements/element.dart'; | ||||
| import 'package:flame/src/text/elements/group_element.dart'; | ||||
| import 'package:flame/src/text/nodes.dart'; | ||||
| import 'package:flame/src/text/nodes/paragraph_node.dart'; | ||||
| import 'package:flame/src/text/nodes/block_node.dart'; | ||||
| import 'package:flame/src/text/styles/document_style.dart'; | ||||
| import 'package:flutter/painting.dart'; | ||||
|  | ||||
| class DocumentNode extends GroupBlockNode { | ||||
|   DocumentNode(super.children); | ||||
| class DocumentNode { | ||||
|   DocumentNode(this.children); | ||||
|  | ||||
|   final List<BlockNode> children; | ||||
|  | ||||
|   /// Applies [style] to this document, producing an object that can be rendered | ||||
|   /// on a canvas. Parameters [width] and [height] serve as the fallback values | ||||
| @ -21,21 +22,19 @@ class DocumentNode extends GroupBlockNode { | ||||
|       'Width must be either provided explicitly or set in the stylesheet', | ||||
|     ); | ||||
|     final out = <Element>[]; | ||||
|     final border = style.backgroundStyle?.borderWidths ?? EdgeInsets.zero; | ||||
|     final border = style.background?.borderWidths ?? EdgeInsets.zero; | ||||
|     final padding = style.padding; | ||||
|  | ||||
|     final pageWidth = style.width ?? width!; | ||||
|     final contentWidth = pageWidth - padding.horizontal - border.horizontal; | ||||
|     final horizontalOffset = padding.left + border.left; | ||||
|     final contentWidth = pageWidth - padding.horizontal; | ||||
|     final horizontalOffset = padding.left; | ||||
|     var verticalOffset = border.top; | ||||
|     var currentMargin = padding.top; | ||||
|     for (final node in children) { | ||||
|       final blockStyle = style.styleFor(node); | ||||
|       node.fillStyles(style, style.text); | ||||
|       final blockStyle = node.style; | ||||
|       verticalOffset += collapseMargin(currentMargin, blockStyle.margin.top); | ||||
|       final nodeElement = (node as ParagraphNode).format( | ||||
|         blockStyle, | ||||
|         parentWidth: contentWidth, | ||||
|       ); | ||||
|       final nodeElement = node.format(contentWidth); | ||||
|       nodeElement.translate(horizontalOffset, verticalOffset); | ||||
|       out.add(nodeElement); | ||||
|       currentMargin = blockStyle.margin.bottom; | ||||
| @ -48,13 +47,13 @@ class DocumentNode extends GroupBlockNode { | ||||
|           border.bottom, | ||||
|     ); | ||||
|     final background = makeBackground( | ||||
|       style.backgroundStyle, | ||||
|       style.background, | ||||
|       pageWidth, | ||||
|       pageHeight, | ||||
|     ); | ||||
|     if (background != null) { | ||||
|       out.insert(0, background); | ||||
|     } | ||||
|     return GroupElement(pageWidth, pageHeight, out); | ||||
|     return GroupElement(width: pageWidth, height: pageHeight, children: out); | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										66
									
								
								packages/flame/lib/src/text/nodes/group_text_node.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								packages/flame/lib/src/text/nodes/group_text_node.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | ||||
| import 'package:flame/src/text/elements/group_text_element.dart'; | ||||
| import 'package:flame/src/text/elements/text_element.dart'; | ||||
| import 'package:flame/src/text/nodes/text_node.dart'; | ||||
| import 'package:flame/src/text/styles/document_style.dart'; | ||||
| import 'package:flame/src/text/styles/flame_text_style.dart'; | ||||
|  | ||||
| class GroupTextNode extends TextNode { | ||||
|   GroupTextNode(this.children); | ||||
|  | ||||
|   final List<TextNode> children; | ||||
|  | ||||
|   @override | ||||
|   void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle) { | ||||
|     textStyle = parentTextStyle; | ||||
|     for (final node in children) { | ||||
|       node.fillStyles(stylesheet, textStyle); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   TextNodeLayoutBuilder get layoutBuilder => _GroupTextLayoutBuilder(this); | ||||
| } | ||||
|  | ||||
| class _GroupTextLayoutBuilder extends TextNodeLayoutBuilder { | ||||
|   _GroupTextLayoutBuilder(this.node); | ||||
|  | ||||
|   final GroupTextNode node; | ||||
|   int _currentChildIndex = 0; | ||||
|   TextNodeLayoutBuilder? _currentChildBuilder; | ||||
|  | ||||
|   @override | ||||
|   bool get isDone => _currentChildIndex == node.children.length; | ||||
|  | ||||
|   @override | ||||
|   TextElement? layOutNextLine(double availableWidth) { | ||||
|     assert(!isDone); | ||||
|     final out = <TextElement>[]; | ||||
|     var usedWidth = 0.0; | ||||
|     while (true) { | ||||
|       if (_currentChildBuilder?.isDone ?? false) { | ||||
|         _currentChildBuilder = null; | ||||
|         _currentChildIndex += 1; | ||||
|         if (_currentChildIndex == node.children.length) { | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|       _currentChildBuilder ??= node.children[_currentChildIndex].layoutBuilder; | ||||
|  | ||||
|       final maybeLine = | ||||
|           _currentChildBuilder!.layOutNextLine(availableWidth - usedWidth); | ||||
|       if (maybeLine == null) { | ||||
|         break; | ||||
|       } else { | ||||
|         assert(maybeLine.metrics.left == 0 && maybeLine.metrics.baseline == 0); | ||||
|         maybeLine.translate(usedWidth, 0); | ||||
|         out.add(maybeLine); | ||||
|         usedWidth += maybeLine.metrics.width; | ||||
|       } | ||||
|     } | ||||
|     if (out.isEmpty) { | ||||
|       return null; | ||||
|     } else { | ||||
|       return GroupTextElement(out); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1,9 +1,56 @@ | ||||
| import 'package:flame/src/text/nodes.dart'; | ||||
| import 'package:flame/src/text/nodes/block_node.dart'; | ||||
| import 'dart:ui'; | ||||
|  | ||||
| class HeaderNode extends BlockNode { | ||||
|   HeaderNode(this.child, {required this.level}); | ||||
| import 'package:flame/src/text/nodes/plain_text_node.dart'; | ||||
| import 'package:flame/src/text/nodes/text_block_node.dart'; | ||||
| import 'package:flame/src/text/styles/block_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/style.dart'; | ||||
| import 'package:flutter/rendering.dart' show EdgeInsets; | ||||
|  | ||||
| class HeaderNode extends TextBlockNode { | ||||
|   HeaderNode(super.child, {required this.level}); | ||||
|  | ||||
|   HeaderNode.simple(String text, {required this.level}) | ||||
|       : super(PlainTextNode(text)); | ||||
|  | ||||
|   final GroupTextNode child; | ||||
|   final int level; | ||||
|  | ||||
|   static BlockStyle defaultStyleH1 = BlockStyle( | ||||
|     text: FlameTextStyle(fontScale: 2.0, fontWeight: FontWeight.bold), | ||||
|     margin: const EdgeInsets.fromLTRB(0, 24, 0, 12), | ||||
|   ); | ||||
|   static BlockStyle defaultStyleH2 = BlockStyle( | ||||
|     text: FlameTextStyle(fontScale: 1.5, fontWeight: FontWeight.bold), | ||||
|     margin: const EdgeInsets.fromLTRB(0, 24, 0, 8), | ||||
|   ); | ||||
|   static BlockStyle defaultStyleH3 = BlockStyle( | ||||
|     text: FlameTextStyle(fontScale: 1.25, fontWeight: FontWeight.bold), | ||||
|   ); | ||||
|   static BlockStyle defaultStyleH4 = BlockStyle( | ||||
|     text: FlameTextStyle(fontScale: 1.0, fontWeight: FontWeight.bold), | ||||
|   ); | ||||
|   static BlockStyle defaultStyleH5 = BlockStyle( | ||||
|     text: FlameTextStyle(fontScale: 0.875, fontWeight: FontWeight.bold), | ||||
|   ); | ||||
|   static BlockStyle defaultStyleH6 = BlockStyle( | ||||
|     text: FlameTextStyle(fontScale: 0.85, fontWeight: FontWeight.bold), | ||||
|   ); | ||||
|  | ||||
|   @override | ||||
|   void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle) { | ||||
|     style = level == 1 | ||||
|         ? stylesheet.header1 | ||||
|         : level == 2 | ||||
|             ? stylesheet.header2 | ||||
|             : level == 3 | ||||
|                 ? stylesheet.header3 | ||||
|                 : level == 4 | ||||
|                     ? stylesheet.header4 | ||||
|                     : level == 5 | ||||
|                         ? stylesheet.header5 | ||||
|                         : stylesheet.header6; | ||||
|     final textStyle = Style.merge(parentTextStyle, style.text)!; | ||||
|     super.fillStyles(stylesheet, textStyle); | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										30
									
								
								packages/flame/lib/src/text/nodes/italic_text_node.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								packages/flame/lib/src/text/nodes/italic_text_node.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| import 'dart:ui'; | ||||
|  | ||||
| import 'package:flame/src/text/nodes/group_text_node.dart'; | ||||
| import 'package:flame/src/text/nodes/plain_text_node.dart'; | ||||
| import 'package:flame/src/text/nodes/text_node.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/style.dart'; | ||||
|  | ||||
| class ItalicTextNode extends TextNode { | ||||
|   ItalicTextNode(this.child); | ||||
|  | ||||
|   ItalicTextNode.simple(String text) : child = PlainTextNode(text); | ||||
|  | ||||
|   ItalicTextNode.group(List<TextNode> children) | ||||
|       : child = GroupTextNode(children); | ||||
|  | ||||
|   final TextNode child; | ||||
|  | ||||
|   static final defaultStyle = FlameTextStyle(fontStyle: FontStyle.italic); | ||||
|  | ||||
|   @override | ||||
|   void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle) { | ||||
|     textStyle = Style.merge(parentTextStyle, stylesheet.italicText)!; | ||||
|     child.fillStyles(stylesheet, textStyle); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   TextNodeLayoutBuilder get layoutBuilder => child.layoutBuilder; | ||||
| } | ||||
| @ -1,59 +1,29 @@ | ||||
| import 'package:flame/src/text/elements/block_element.dart'; | ||||
| import 'package:flame/src/text/elements/group_element.dart'; | ||||
| import 'package:flame/src/text/formatters/text_painter_text_formatter.dart'; | ||||
| import 'package:flame/src/text/inline/text_painter_text_element.dart'; | ||||
| import 'package:flame/src/text/nodes.dart'; | ||||
| import 'package:flame/src/text/nodes/block_node.dart'; | ||||
| import 'package:flame/src/text/nodes/group_text_node.dart'; | ||||
| import 'package:flame/src/text/nodes/plain_text_node.dart'; | ||||
| import 'package:flame/src/text/nodes/text_block_node.dart'; | ||||
| import 'package:flame/src/text/nodes/text_node.dart'; | ||||
| import 'package:flame/src/text/styles/block_style.dart'; | ||||
| import 'package:flutter/painting.dart' as painting; | ||||
| 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/style.dart'; | ||||
| import 'package:flutter/rendering.dart' show EdgeInsets; | ||||
|  | ||||
| class ParagraphNode extends BlockNode { | ||||
|   ParagraphNode.simple(String text) | ||||
|       : child = GroupTextNode([PlainTextNode(text)]); | ||||
| class ParagraphNode extends TextBlockNode { | ||||
|   ParagraphNode(super.child); | ||||
|  | ||||
|   final GroupTextNode child; | ||||
|   ParagraphNode.simple(String text) : super(PlainTextNode(text)); | ||||
|  | ||||
|   BlockElement format(BlockStyle style, {required double parentWidth}) { | ||||
|     final text = (child.children.first as PlainTextNode).text; | ||||
|     final formatter = TextPainterTextFormatter( | ||||
|       style: const painting.TextStyle(fontSize: 16), | ||||
|     ); | ||||
|     final contentWidth = parentWidth - style.padding.horizontal; | ||||
|     final horizontalOffset = style.padding.left; | ||||
|   ParagraphNode.group(List<TextNode> fragments) | ||||
|       : super(GroupTextNode(fragments)); | ||||
|  | ||||
|     final words = text.split(' '); | ||||
|     final lines = <TextPainterTextElement>[]; | ||||
|     var verticalOffset = style.padding.top; | ||||
|     var i0 = 0; | ||||
|     var i1 = 1; | ||||
|     var startNewLine = true; | ||||
|     while (i1 <= words.length) { | ||||
|       final lineText = words.sublist(i0, i1).join(' '); | ||||
|       final formattedLine = formatter.format(lineText); | ||||
|       if (formattedLine.metrics.width <= contentWidth || i1 - i0 == 1) { | ||||
|         formattedLine.translate( | ||||
|           horizontalOffset, | ||||
|           verticalOffset + formattedLine.metrics.ascent, | ||||
|         ); | ||||
|         if (startNewLine) { | ||||
|           lines.add(formattedLine); | ||||
|           startNewLine = false; | ||||
|         } else { | ||||
|           lines[lines.length - 1] = formattedLine; | ||||
|         } | ||||
|         i1++; | ||||
|       } else { | ||||
|         i0 = i1 - 1; | ||||
|         startNewLine = true; | ||||
|         verticalOffset += lines.last.metrics.height; | ||||
|       } | ||||
|     } | ||||
|     if (!startNewLine) { | ||||
|       verticalOffset += lines.last.metrics.height; | ||||
|     } | ||||
|     verticalOffset += style.padding.bottom; | ||||
|     final bg = makeBackground(style.background, parentWidth, verticalOffset); | ||||
|     final elements = bg == null ? lines : [bg, ...lines]; | ||||
|     return GroupElement(parentWidth, verticalOffset, elements); | ||||
|   static const defaultStyle = BlockStyle( | ||||
|     margin: EdgeInsets.all(6), | ||||
|   ); | ||||
|  | ||||
|   @override | ||||
|   void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle) { | ||||
|     style = stylesheet.paragraph; | ||||
|     final textStyle = Style.merge(parentTextStyle, style.text)!; | ||||
|     super.fillStyles(stylesheet, textStyle); | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										58
									
								
								packages/flame/lib/src/text/nodes/plain_text_node.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								packages/flame/lib/src/text/nodes/plain_text_node.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,58 @@ | ||||
| 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/styles/document_style.dart'; | ||||
| import 'package:flame/src/text/styles/flame_text_style.dart'; | ||||
|  | ||||
| class PlainTextNode extends TextNode { | ||||
|   PlainTextNode(this.text); | ||||
|  | ||||
|   final String text; | ||||
|  | ||||
|   @override | ||||
|   void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle) { | ||||
|     textStyle = parentTextStyle; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   TextNodeLayoutBuilder get layoutBuilder => _PlainTextLayoutBuilder(this); | ||||
| } | ||||
|  | ||||
| class _PlainTextLayoutBuilder extends TextNodeLayoutBuilder { | ||||
|   _PlainTextLayoutBuilder(this.node) | ||||
|       : formatter = node.textStyle.asTextFormatter(), | ||||
|         words = node.text.split(' '); | ||||
|  | ||||
|   final PlainTextNode node; | ||||
|   final TextFormatter formatter; | ||||
|   final List<String> words; | ||||
|   int index0 = 0; | ||||
|   int index1 = 1; | ||||
|  | ||||
|   @override | ||||
|   bool get isDone => index1 > words.length; | ||||
|  | ||||
|   @override | ||||
|   TextElement? layOutNextLine(double availableWidth) { | ||||
|     TextElement? tentativeLine; | ||||
|     int? tentativeIndex0; | ||||
|     while (index1 <= words.length) { | ||||
|       final textPiece = words.sublist(index0, index1).join(' '); | ||||
|       final formattedPiece = formatter.format(textPiece); | ||||
|       if (formattedPiece.metrics.width > availableWidth) { | ||||
|         break; | ||||
|       } else { | ||||
|         tentativeLine = formattedPiece; | ||||
|         tentativeIndex0 = index1; | ||||
|         index1 += 1; | ||||
|       } | ||||
|     } | ||||
|     if (tentativeLine != null) { | ||||
|       assert(tentativeIndex0 != 0 && tentativeIndex0! > index0); | ||||
|       index0 = tentativeIndex0!; | ||||
|       return tentativeLine; | ||||
|     } else { | ||||
|       return null; | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										58
									
								
								packages/flame/lib/src/text/nodes/text_block_node.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								packages/flame/lib/src/text/nodes/text_block_node.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,58 @@ | ||||
| import 'package:flame/src/text/common/utils.dart'; | ||||
| import 'package:flame/src/text/elements/block_element.dart'; | ||||
| import 'package:flame/src/text/elements/group_element.dart'; | ||||
| import 'package:flame/src/text/elements/text_element.dart'; | ||||
| import 'package:flame/src/text/nodes/block_node.dart'; | ||||
| import 'package:flame/src/text/nodes/text_node.dart'; | ||||
| import 'package:flame/src/text/styles/document_style.dart'; | ||||
| import 'package:flame/src/text/styles/flame_text_style.dart'; | ||||
| import 'package:meta/meta.dart'; | ||||
|  | ||||
| abstract class TextBlockNode extends BlockNode { | ||||
|   TextBlockNode(this.child); | ||||
|  | ||||
|   final TextNode child; | ||||
|  | ||||
|   @mustCallSuper | ||||
|   @override | ||||
|   void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle) { | ||||
|     child.fillStyles(stylesheet, parentTextStyle); | ||||
|   } | ||||
|  | ||||
|   /// Converts this node into a [BlockElement]. | ||||
|   /// | ||||
|   /// All late variables must be initialized prior to calling this method. | ||||
|   @override | ||||
|   BlockElement format(double availableWidth) { | ||||
|     final layoutBuilder = child.layoutBuilder; | ||||
|     final blockWidth = availableWidth; | ||||
|     final contentWidth = blockWidth - style.padding.horizontal; | ||||
|  | ||||
|     final lines = <TextElement>[]; | ||||
|     final horizontalOffset = style.padding.left; | ||||
|     var verticalOffset = style.padding.top; | ||||
|     while (!layoutBuilder.isDone) { | ||||
|       final element = layoutBuilder.layOutNextLine(contentWidth); | ||||
|       if (element == null) { | ||||
|         // Not enough horizontal space to lay out. For now we just stop the | ||||
|         // layout altogether cutting off the remainder of the content. But is | ||||
|         // there a better alternative? | ||||
|         break; | ||||
|       } else { | ||||
|         final metrics = element.metrics; | ||||
|         assert(metrics.left == 0 && metrics.baseline == 0); | ||||
|         element.translate(horizontalOffset, verticalOffset + metrics.ascent); | ||||
|         lines.add(element); | ||||
|         verticalOffset += metrics.height; | ||||
|       } | ||||
|     } | ||||
|     verticalOffset += style.padding.bottom; | ||||
|     final bg = makeBackground(style.background, blockWidth, verticalOffset); | ||||
|     final elements = bg == null ? lines : [bg, ...lines]; | ||||
|     return GroupElement( | ||||
|       width: blockWidth, | ||||
|       height: verticalOffset, | ||||
|       children: elements, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										16
									
								
								packages/flame/lib/src/text/nodes/text_node.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								packages/flame/lib/src/text/nodes/text_node.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| import 'package:flame/src/text/elements/text_element.dart'; | ||||
| import 'package:flame/src/text/styles/document_style.dart'; | ||||
| import 'package:flame/src/text/styles/flame_text_style.dart'; | ||||
|  | ||||
| abstract class TextNode { | ||||
|   late FlameTextStyle textStyle; | ||||
|  | ||||
|   void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle); | ||||
|  | ||||
|   TextNodeLayoutBuilder get layoutBuilder; | ||||
| } | ||||
|  | ||||
| abstract class TextNodeLayoutBuilder { | ||||
|   TextElement? layOutNextLine(double availableWidth); | ||||
|   bool get isDone; | ||||
| } | ||||
| @ -1,6 +1,8 @@ | ||||
| import 'package:flame/src/text/styles/style.dart'; | ||||
| import 'package:flutter/rendering.dart'; | ||||
| import 'package:meta/meta.dart'; | ||||
|  | ||||
| @immutable | ||||
| class BackgroundStyle extends Style { | ||||
|   BackgroundStyle({ | ||||
|     Color? color, | ||||
| @ -13,45 +15,28 @@ class BackgroundStyle extends Style { | ||||
|           'Parameters `paint` and `color` are exclusive', | ||||
|         ), | ||||
|         borderWidths = EdgeInsets.all(borderWidth ?? 0), | ||||
|         borderRadius = borderRadius ?? 0 { | ||||
|     if (paint != null) { | ||||
|       backgroundPaint = paint; | ||||
|     } else if (color != null) { | ||||
|       backgroundPaint = Paint()..color = color; | ||||
|     } else { | ||||
|       backgroundPaint = null; | ||||
|     } | ||||
|     if (borderColor != null) { | ||||
|       borderPaint = Paint() | ||||
|         ..color = borderColor | ||||
|         ..style = PaintingStyle.stroke | ||||
|         ..strokeWidth = borderWidth ?? 0; | ||||
|     } else { | ||||
|       borderPaint = null; | ||||
|     } | ||||
|   } | ||||
|         borderRadius = borderRadius ?? 0, | ||||
|         backgroundPaint = | ||||
|             paint ?? (color != null ? (Paint()..color = color) : null), | ||||
|         borderPaint = borderColor != null | ||||
|             ? (Paint() | ||||
|               ..color = borderColor | ||||
|               ..style = PaintingStyle.stroke | ||||
|               ..strokeWidth = borderWidth ?? 0) | ||||
|             : null; | ||||
|  | ||||
|   late final Paint? backgroundPaint; | ||||
|   late final Paint? borderPaint; | ||||
|   final Paint? backgroundPaint; | ||||
|   final Paint? borderPaint; | ||||
|   final double borderRadius; | ||||
|   final EdgeInsets borderWidths; | ||||
|  | ||||
|   @override | ||||
|   BackgroundStyle clone() => copyWith(); | ||||
|  | ||||
|   BackgroundStyle copyWith({ | ||||
|     Color? color, | ||||
|     Paint? paint, | ||||
|     Color? borderColor, | ||||
|     double? borderRadius, | ||||
|     double? borderWidth, | ||||
|   }) { | ||||
|   BackgroundStyle copyWith(BackgroundStyle other) { | ||||
|     return BackgroundStyle( | ||||
|       color: color ?? (paint == null ? backgroundPaint?.color : null), | ||||
|       paint: paint ?? backgroundPaint, | ||||
|       borderColor: borderColor ?? borderPaint?.color, | ||||
|       borderRadius: borderRadius ?? this.borderRadius, | ||||
|       borderWidth: borderWidth ?? borderWidths.left, | ||||
|       paint: other.backgroundPaint ?? backgroundPaint, | ||||
|       borderColor: other.borderPaint?.color ?? borderPaint?.color, | ||||
|       borderRadius: other.borderRadius, | ||||
|       borderWidth: other.borderWidths.top, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,30 +1,36 @@ | ||||
| import 'package:flame/src/text/styles/background_style.dart'; | ||||
| import 'package:flame/src/text/styles/flame_text_style.dart'; | ||||
| import 'package:flame/src/text/styles/style.dart'; | ||||
| import 'package:flutter/painting.dart'; | ||||
| import 'package:flutter/painting.dart' hide TextStyle; | ||||
| import 'package:meta/meta.dart'; | ||||
|  | ||||
| /// [BlockStyle] is a generic descriptor for a visual appearance of a block- | ||||
| /// level element. | ||||
| @immutable | ||||
| class BlockStyle extends Style { | ||||
|   BlockStyle({ | ||||
|     this.margin = EdgeInsets.zero, | ||||
|     this.padding = EdgeInsets.zero, | ||||
|     this.background, | ||||
|   }); | ||||
|  | ||||
|   EdgeInsets margin; | ||||
|   EdgeInsets padding; | ||||
|   BackgroundStyle? background; | ||||
|  | ||||
|   @override | ||||
|   BlockStyle clone() => copyWith(); | ||||
|  | ||||
|   BlockStyle copyWith({ | ||||
|   const BlockStyle({ | ||||
|     EdgeInsets? margin, | ||||
|     EdgeInsets? padding, | ||||
|     BackgroundStyle? background, | ||||
|   }) { | ||||
|     this.background, | ||||
|     this.text, | ||||
|   })  : _margin = margin, | ||||
|         _padding = padding; | ||||
|  | ||||
|   final EdgeInsets? _margin; | ||||
|   final EdgeInsets? _padding; | ||||
|   final BackgroundStyle? background; | ||||
|   final FlameTextStyle? text; | ||||
|  | ||||
|   EdgeInsets get margin => _margin ?? EdgeInsets.zero; | ||||
|   EdgeInsets get padding => _padding ?? EdgeInsets.zero; | ||||
|  | ||||
|   @override | ||||
|   BlockStyle copyWith(BlockStyle other) { | ||||
|     return BlockStyle( | ||||
|       margin: margin ?? this.margin, | ||||
|       padding: padding ?? this.padding, | ||||
|       background: background ?? this.background, | ||||
|       margin: other._margin ?? _margin, | ||||
|       padding: other._padding ?? _padding, | ||||
|       background: other.background ?? background, | ||||
|       text: Style.merge(text, other.text), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,12 +1,13 @@ | ||||
| import 'package:flame/src/text/nodes/block_node.dart'; | ||||
| import 'package:flame/src/text/nodes/bold_text_node.dart'; | ||||
| import 'package:flame/src/text/nodes/header_node.dart'; | ||||
| import 'package:flame/src/text/nodes/italic_text_node.dart'; | ||||
| import 'package:flame/src/text/nodes/paragraph_node.dart'; | ||||
| import 'package:flame/src/text/styles/background_style.dart'; | ||||
| import 'package:flame/src/text/styles/block_style.dart'; | ||||
| import 'package:flame/src/text/styles/flame_text_style.dart'; | ||||
| import 'package:flame/src/text/styles/overflow.dart'; | ||||
| import 'package:flame/src/text/styles/style.dart'; | ||||
| import 'package:flutter/painting.dart' show EdgeInsets; | ||||
| import 'package:meta/meta.dart'; | ||||
|  | ||||
| /// [DocumentStyle] is a user-facing description of how to render an entire | ||||
| /// body of text; it roughly corresponds to a stylesheet in HTML. | ||||
| @ -21,25 +22,39 @@ class DocumentStyle extends Style { | ||||
|   DocumentStyle({ | ||||
|     this.width, | ||||
|     this.height, | ||||
|     EdgeInsets? padding, | ||||
|     BackgroundStyle? background, | ||||
|     BlockStyle? paragraphStyle, | ||||
|     BlockStyle? header1Style, | ||||
|     BlockStyle? header2Style, | ||||
|     BlockStyle? header3Style, | ||||
|     BlockStyle? header4Style, | ||||
|     BlockStyle? header5Style, | ||||
|     BlockStyle? header6Style, | ||||
|   }) : padding = padding ?? EdgeInsets.zero { | ||||
|     backgroundStyle = acquire(background); | ||||
|     this.paragraphStyle = acquire(paragraphStyle ?? defaultParagraphStyle)!; | ||||
|     this.header1Style = acquire(header1Style ?? defaultHeader1Style)!; | ||||
|     this.header2Style = acquire(header2Style ?? defaultHeader2Style)!; | ||||
|     this.header3Style = acquire(header3Style ?? defaultHeader3Style)!; | ||||
|     this.header4Style = acquire(header4Style ?? defaultHeader4Style)!; | ||||
|     this.header5Style = acquire(header5Style ?? defaultHeader5Style)!; | ||||
|     this.header6Style = acquire(header6Style ?? defaultHeader6Style)!; | ||||
|   } | ||||
|     this.padding = EdgeInsets.zero, | ||||
|     this.background, | ||||
|     FlameTextStyle? text, | ||||
|     FlameTextStyle? boldText, | ||||
|     FlameTextStyle? italicText, | ||||
|     BlockStyle? paragraph, | ||||
|     BlockStyle? header1, | ||||
|     BlockStyle? header2, | ||||
|     BlockStyle? header3, | ||||
|     BlockStyle? header4, | ||||
|     BlockStyle? header5, | ||||
|     BlockStyle? header6, | ||||
|   })  : _text = Style.merge(text, DocumentStyle.defaultTextStyle), | ||||
|         _boldText = Style.merge(boldText, BoldTextNode.defaultStyle), | ||||
|         _italicText = Style.merge(italicText, ItalicTextNode.defaultStyle), | ||||
|         _paragraph = Style.merge(paragraph, ParagraphNode.defaultStyle), | ||||
|         _header1 = Style.merge(header1, HeaderNode.defaultStyleH1), | ||||
|         _header2 = Style.merge(header2, HeaderNode.defaultStyleH2), | ||||
|         _header3 = Style.merge(header3, HeaderNode.defaultStyleH3), | ||||
|         _header4 = Style.merge(header4, HeaderNode.defaultStyleH4), | ||||
|         _header5 = Style.merge(header5, HeaderNode.defaultStyleH5), | ||||
|         _header6 = Style.merge(header6, HeaderNode.defaultStyleH6); | ||||
|  | ||||
|   final FlameTextStyle? _text; | ||||
|   final FlameTextStyle? _boldText; | ||||
|   final FlameTextStyle? _italicText; | ||||
|   final BlockStyle? _paragraph; | ||||
|   final BlockStyle? _header1; | ||||
|   final BlockStyle? _header2; | ||||
|   final BlockStyle? _header3; | ||||
|   final BlockStyle? _header4; | ||||
|   final BlockStyle? _header5; | ||||
|   final BlockStyle? _header6; | ||||
|  | ||||
|   /// Outer width of the document page. | ||||
|   /// | ||||
| @ -65,7 +80,7 @@ class DocumentStyle extends Style { | ||||
|   /// Behavior of the document when the amount of content that needs to be laid | ||||
|   /// out exceeds the provided [height]. See the [Overflow] enum description for | ||||
|   /// more details. | ||||
|   final Overflow overflow = Overflow.expand; | ||||
|   Overflow get overflow => Overflow.expand; | ||||
|  | ||||
|   /// The distance from the outer edges of the page to the inner content box of | ||||
|   /// the document. | ||||
| @ -79,89 +94,51 @@ class DocumentStyle extends Style { | ||||
|  | ||||
|   /// If present, describes what kind of background and borders to draw for the | ||||
|   /// document page(s). | ||||
|   late final BackgroundStyle? backgroundStyle; | ||||
|   final BackgroundStyle? background; | ||||
|  | ||||
|   /// Style for paragraph nodes in the document. | ||||
|   late final BlockStyle paragraphStyle; | ||||
|   FlameTextStyle get text => _text!; | ||||
|   FlameTextStyle get boldText => _boldText!; | ||||
|   FlameTextStyle get italicText => _italicText!; | ||||
|  | ||||
|   /// Style for level-1 headers. | ||||
|   late final BlockStyle header1Style; | ||||
|   /// Style for [ParagraphNode]s. | ||||
|   BlockStyle get paragraph => _paragraph!; | ||||
|  | ||||
|   /// Style for level-2 headers. | ||||
|   late final BlockStyle header2Style; | ||||
|   /// Styles for [HeaderNode]s, levels 1 to 6. | ||||
|   BlockStyle get header1 => _header1!; | ||||
|   BlockStyle get header2 => _header2!; | ||||
|   BlockStyle get header3 => _header3!; | ||||
|   BlockStyle get header4 => _header4!; | ||||
|   BlockStyle get header5 => _header5!; | ||||
|   BlockStyle get header6 => _header6!; | ||||
|  | ||||
|   /// Style for level-3 headers. | ||||
|   late final BlockStyle header3Style; | ||||
|  | ||||
|   /// Style for level-4 headers. | ||||
|   late final BlockStyle header4Style; | ||||
|  | ||||
|   /// Style for level-5 headers. | ||||
|   late final BlockStyle header5Style; | ||||
|  | ||||
|   /// Style for level-6 headers. | ||||
|   late final BlockStyle header6Style; | ||||
|  | ||||
|   static BlockStyle defaultParagraphStyle = BlockStyle(); | ||||
|   static BlockStyle defaultHeader1Style = BlockStyle(); | ||||
|   static BlockStyle defaultHeader2Style = BlockStyle(); | ||||
|   static BlockStyle defaultHeader3Style = BlockStyle(); | ||||
|   static BlockStyle defaultHeader4Style = BlockStyle(); | ||||
|   static BlockStyle defaultHeader5Style = BlockStyle(); | ||||
|   static BlockStyle defaultHeader6Style = BlockStyle(); | ||||
|   static FlameTextStyle defaultTextStyle = FlameTextStyle(fontSize: 16.0); | ||||
|  | ||||
|   @override | ||||
|   DocumentStyle clone() => copyWith(); | ||||
|  | ||||
|   DocumentStyle copyWith({ | ||||
|     double? width, | ||||
|     double? height, | ||||
|     EdgeInsets? padding, | ||||
|     BackgroundStyle? background, | ||||
|     BlockStyle? paragraphStyle, | ||||
|     BlockStyle? header1Style, | ||||
|     BlockStyle? header2Style, | ||||
|     BlockStyle? header3Style, | ||||
|     BlockStyle? header4Style, | ||||
|     BlockStyle? header5Style, | ||||
|     BlockStyle? header6Style, | ||||
|   }) { | ||||
|   DocumentStyle copyWith(DocumentStyle other) { | ||||
|     return DocumentStyle( | ||||
|       width: width ?? this.width, | ||||
|       height: height ?? this.height, | ||||
|       padding: padding ?? this.padding, | ||||
|       background: background ?? backgroundStyle, | ||||
|       paragraphStyle: paragraphStyle ?? this.paragraphStyle, | ||||
|       header1Style: header1Style ?? this.header1Style, | ||||
|       header2Style: header2Style ?? this.header2Style, | ||||
|       header3Style: header3Style ?? this.header3Style, | ||||
|       header4Style: header4Style ?? this.header4Style, | ||||
|       header5Style: header5Style ?? this.header5Style, | ||||
|       header6Style: header6Style ?? this.header6Style, | ||||
|       width: other.width ?? width, | ||||
|       height: other.height ?? height, | ||||
|       padding: other.padding, | ||||
|       background: merge(other.background, background)! as BackgroundStyle, | ||||
|       paragraph: merge(other.paragraph, paragraph)! as BlockStyle, | ||||
|       header1: merge(other.header1, header1)! as BlockStyle, | ||||
|       header2: merge(other.header2, header2)! as BlockStyle, | ||||
|       header3: merge(other.header3, header3)! as BlockStyle, | ||||
|       header4: merge(other.header4, header4)! as BlockStyle, | ||||
|       header5: merge(other.header5, header5)! as BlockStyle, | ||||
|       header6: merge(other.header6, header6)! as BlockStyle, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @internal | ||||
|   BlockStyle styleFor(BlockNode node) { | ||||
|     if (node is ParagraphNode) { | ||||
|       return paragraphStyle; | ||||
|   final Map<Style, Map<Style, Style>> _mergedStylesCache = {}; | ||||
|   Style? merge(Style? style1, Style? style2) { | ||||
|     if (style1 == null) { | ||||
|       return style2; | ||||
|     } else if (style2 == null) { | ||||
|       return style1; | ||||
|     } else { | ||||
|       return (_mergedStylesCache[style1] ??= {})[style2] ??= | ||||
|           style1.copyWith(style2); | ||||
|     } | ||||
|     if (node is HeaderNode) { | ||||
|       switch (node.level) { | ||||
|         case 1: | ||||
|           return header1Style; | ||||
|         case 2: | ||||
|           return header2Style; | ||||
|         case 3: | ||||
|           return header3Style; | ||||
|         case 4: | ||||
|           return header4Style; | ||||
|         case 5: | ||||
|           return header5Style; | ||||
|         default: | ||||
|           return header6Style; | ||||
|       } | ||||
|     } | ||||
|     return BlockStyle(); | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										55
									
								
								packages/flame/lib/src/text/styles/flame_text_style.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								packages/flame/lib/src/text/styles/flame_text_style.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | ||||
| 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/styles/style.dart'; | ||||
| import 'package:flutter/rendering.dart'; | ||||
| import 'package:meta/meta.dart'; | ||||
|  | ||||
| @immutable | ||||
| class FlameTextStyle extends Style { | ||||
|   FlameTextStyle({ | ||||
|     this.color, | ||||
|     this.fontFamily, | ||||
|     this.fontSize, | ||||
|     this.fontScale, | ||||
|     this.fontWeight, | ||||
|     this.fontStyle, | ||||
|     this.letterSpacing, | ||||
|   }); | ||||
|  | ||||
|   final Color? color; | ||||
|   final String? fontFamily; | ||||
|   final double? fontSize; | ||||
|   final double? fontScale; | ||||
|   final FontWeight? fontWeight; | ||||
|   final FontStyle? fontStyle; | ||||
|   final double? letterSpacing; | ||||
|  | ||||
|   late final TextFormatter formatter = asTextFormatter(); | ||||
|  | ||||
|   @override | ||||
|   FlameTextStyle copyWith(FlameTextStyle other) { | ||||
|     return FlameTextStyle( | ||||
|       color: color ?? other.color, | ||||
|       fontFamily: fontFamily ?? other.fontFamily, | ||||
|       fontSize: fontSize ?? other.fontSize, | ||||
|       fontScale: fontScale ?? other.fontScale, | ||||
|       fontWeight: fontWeight ?? other.fontWeight, | ||||
|       fontStyle: fontStyle ?? other.fontStyle, | ||||
|       letterSpacing: letterSpacing ?? other.letterSpacing, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @internal | ||||
|   TextPainterTextFormatter asTextFormatter() { | ||||
|     return TextPainterTextFormatter( | ||||
|       style: TextStyle( | ||||
|         color: color, | ||||
|         fontFamily: fontFamily, | ||||
|         fontSize: fontSize! * (fontScale ?? 1.0), | ||||
|         fontWeight: fontWeight, | ||||
|         fontStyle: fontStyle, | ||||
|         letterSpacing: letterSpacing, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -1,5 +1,4 @@ | ||||
| import 'package:flame/src/text/styles/document_style.dart'; | ||||
| import 'package:meta/meta.dart'; | ||||
|  | ||||
| /// A [Style] is a base class for several classes that collectively describe | ||||
| /// the desired visual appearance of a "rich-text" document. | ||||
| @ -14,27 +13,18 @@ import 'package:meta/meta.dart'; | ||||
| /// | ||||
| /// The tree of [Style]s is roughly equivalent to a CSS stylesheet. | ||||
| abstract class Style { | ||||
|   /// The owner of the current style. | ||||
|   /// | ||||
|   /// Usually, styles are organized into a tree, and this property allows | ||||
|   /// traversing up this tree. This property can be null when the style hasn't | ||||
|   /// been put into a tree yet, or when it is the root of the tree. | ||||
|   Style? get parent => _parent; | ||||
|   Style? _parent; | ||||
|   const Style(); | ||||
|  | ||||
|   /// Creates and returns a copy of the current object. | ||||
|   Style clone(); | ||||
|   Style copyWith(covariant Style other); | ||||
|  | ||||
|   /// Marks [style] as being owned by the current object and returns it. | ||||
|   /// However, if the [style] is already owned by some other object, then clones | ||||
|   /// the [style], marks the copy as being owned, and returns it. | ||||
|   @protected | ||||
|   S? acquire<S extends Style>(S? style) { | ||||
|     if (style == null) { | ||||
|       return null; | ||||
|   static T? merge<T extends Style>(T? style1, T? style2) { | ||||
|     if (style1 == null) { | ||||
|       return style2; | ||||
|     } else if (style2 == null) { | ||||
|       return style1; | ||||
|     } else { | ||||
|       assert(style1.runtimeType == style2.runtimeType); | ||||
|       return style1.copyWith(style2) as T; | ||||
|     } | ||||
|     final useStyle = style._parent == null ? style : style.clone() as S; | ||||
|     useStyle._parent = this; | ||||
|     return useStyle; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -3,21 +3,36 @@ export 'src/text/common/glyph.dart' show Glyph; | ||||
| export 'src/text/common/glyph_data.dart' show GlyphData; | ||||
| export 'src/text/common/line_metrics.dart' show LineMetrics; | ||||
| export 'src/text/common/sprite_font.dart' show SpriteFont; | ||||
| export 'src/text/common/text_line.dart' show TextLine; | ||||
| export 'src/text/elements/block_element.dart' show BlockElement; | ||||
| export 'src/text/elements/element.dart' show Element; | ||||
| export 'src/text/elements/rect_element.dart' show RectElement; | ||||
| export 'src/text/elements/rrect_element.dart' show RRectElement; | ||||
| export 'src/text/elements/sprite_font_text_element.dart' | ||||
|     show SpriteFontTextElement; | ||||
| 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; | ||||
| export 'src/text/nodes/document_node.dart' show DocumentNode; | ||||
| export 'src/text/nodes/group_text_node.dart' show GroupTextNode; | ||||
| export 'src/text/nodes/header_node.dart' show HeaderNode; | ||||
| export 'src/text/nodes/italic_text_node.dart' show ItalicTextNode; | ||||
| 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/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; | ||||
|  | ||||
| @ -1,12 +1,11 @@ | ||||
| import 'package:flame/components.dart'; | ||||
| import 'package:flame/text.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/rendering.dart' as flutter; | ||||
| import 'package:test/test.dart'; | ||||
|  | ||||
| void main() { | ||||
|   group('TextPaint', () { | ||||
|     test('copyWith returns a new instance with the new values', () { | ||||
|       const style = TextStyle(fontSize: 12, fontFamily: 'Times'); | ||||
|       const style = flutter.TextStyle(fontSize: 12, fontFamily: 'Times'); | ||||
|       final tp = TextPaint(style: style) | ||||
|           .copyWith((t) => t.copyWith(fontFamily: 'Helvetica')); | ||||
|       expect(tp.style.fontSize, 12); | ||||
|  | ||||
| @ -27,7 +27,7 @@ class DebugTextFormatter extends TextFormatter { | ||||
|   TextElement format(String text) => _DebugTextElement(this, text); | ||||
| } | ||||
|  | ||||
| class _DebugTextElement extends TextElement implements TextLine { | ||||
| class _DebugTextElement extends TextElement { | ||||
|   _DebugTextElement(this.style, this.text) { | ||||
|     final charWidth = style.fontSize * 1.0; | ||||
|     final charHeight = style.fontSize; | ||||
| @ -52,9 +52,6 @@ class _DebugTextElement extends TextElement implements TextLine { | ||||
|   @override | ||||
|   late final LineMetrics metrics; | ||||
|  | ||||
|   @override | ||||
|   TextLine get lastLine => this; | ||||
|  | ||||
|   @override | ||||
|   void render(Canvas canvas) { | ||||
|     canvas.save(); | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Pasha Stetsenko
					Pasha Stetsenko